summaryrefslogtreecommitdiff
path: root/qpid/cpp/src
diff options
context:
space:
mode:
Diffstat (limited to 'qpid/cpp/src')
-rw-r--r--qpid/cpp/src/CMakeLists.txt1327
-rw-r--r--qpid/cpp/src/CMakeWinVersions.cmake57
-rw-r--r--qpid/cpp/src/QpidConfig.cmake.in30
-rw-r--r--qpid/cpp/src/QpidConfigVersion.cmake.in30
-rw-r--r--qpid/cpp/src/amqp.cmake157
-rwxr-xr-xqpid/cpp/src/check-abi105
-rw-r--r--qpid/cpp/src/config.h.cmake63
-rw-r--r--qpid/cpp/src/finddb.cmake76
-rw-r--r--qpid/cpp/src/legacystore.cmake172
-rw-r--r--qpid/cpp/src/libqpidmessaging-api-symbols.txt243
-rw-r--r--qpid/cpp/src/libqpidtypes-api-symbols.txt141
-rw-r--r--qpid/cpp/src/linearstore.cmake177
-rw-r--r--qpid/cpp/src/msvc.cmake149
-rw-r--r--qpid/cpp/src/posix/QpiddBroker.cpp250
-rwxr-xr-xqpid/cpp/src/prof39
-rw-r--r--qpid/cpp/src/qmf/Agent.cpp640
-rw-r--r--qpid/cpp/src/qmf/AgentEvent.cpp85
-rw-r--r--qpid/cpp/src/qmf/AgentEventImpl.h96
-rw-r--r--qpid/cpp/src/qmf/AgentImpl.h122
-rw-r--r--qpid/cpp/src/qmf/AgentSession.cpp1031
-rw-r--r--qpid/cpp/src/qmf/AgentSessionImpl.h168
-rw-r--r--qpid/cpp/src/qmf/AgentSubscription.cpp51
-rw-r--r--qpid/cpp/src/qmf/AgentSubscription.h52
-rw-r--r--qpid/cpp/src/qmf/ConsoleEvent.cpp82
-rw-r--r--qpid/cpp/src/qmf/ConsoleEventImpl.h84
-rw-r--r--qpid/cpp/src/qmf/ConsoleSession.cpp683
-rw-r--r--qpid/cpp/src/qmf/ConsoleSessionImpl.h127
-rw-r--r--qpid/cpp/src/qmf/Data.cpp130
-rw-r--r--qpid/cpp/src/qmf/DataAddr.cpp108
-rw-r--r--qpid/cpp/src/qmf/DataAddrImpl.h73
-rw-r--r--qpid/cpp/src/qmf/DataImpl.h84
-rw-r--r--qpid/cpp/src/qmf/EventNotifierImpl.cpp60
-rw-r--r--qpid/cpp/src/qmf/EventNotifierImpl.h48
-rw-r--r--qpid/cpp/src/qmf/Expression.cpp441
-rw-r--r--qpid/cpp/src/qmf/Expression.h73
-rw-r--r--qpid/cpp/src/qmf/Hash.cpp45
-rw-r--r--qpid/cpp/src/qmf/Hash.h44
-rw-r--r--qpid/cpp/src/qmf/PosixEventNotifier.cpp65
-rw-r--r--qpid/cpp/src/qmf/PosixEventNotifierImpl.cpp112
-rw-r--r--qpid/cpp/src/qmf/PosixEventNotifierImpl.h61
-rw-r--r--qpid/cpp/src/qmf/PrivateImplRef.h93
-rw-r--r--qpid/cpp/src/qmf/Query.cpp153
-rw-r--r--qpid/cpp/src/qmf/QueryImpl.h77
-rw-r--r--qpid/cpp/src/qmf/Schema.cpp358
-rw-r--r--qpid/cpp/src/qmf/SchemaCache.cpp91
-rw-r--r--qpid/cpp/src/qmf/SchemaCache.h56
-rw-r--r--qpid/cpp/src/qmf/SchemaId.cpp96
-rw-r--r--qpid/cpp/src/qmf/SchemaIdImpl.h83
-rw-r--r--qpid/cpp/src/qmf/SchemaImpl.h95
-rw-r--r--qpid/cpp/src/qmf/SchemaMethod.cpp186
-rw-r--r--qpid/cpp/src/qmf/SchemaMethodImpl.h75
-rw-r--r--qpid/cpp/src/qmf/SchemaProperty.cpp434
-rw-r--r--qpid/cpp/src/qmf/SchemaPropertyImpl.h93
-rw-r--r--qpid/cpp/src/qmf/Subscription.cpp88
-rw-r--r--qpid/cpp/src/qmf/SubscriptionImpl.h57
-rw-r--r--qpid/cpp/src/qmf/agentCapability.h39
-rw-r--r--qpid/cpp/src/qmf/constants.cpp77
-rw-r--r--qpid/cpp/src/qmf/constants.h83
-rw-r--r--qpid/cpp/src/qmf/exceptions.cpp37
-rw-r--r--qpid/cpp/src/qmf2.pc.in30
-rw-r--r--qpid/cpp/src/qpid.linkmap31
-rw-r--r--qpid/cpp/src/qpid.pc.in30
-rw-r--r--qpid/cpp/src/qpid/AclHost.cpp151
-rw-r--r--qpid/cpp/src/qpid/AclHost.h95
-rw-r--r--qpid/cpp/src/qpid/Address.cpp44
-rwxr-xr-xqpid/cpp/src/qpid/Address.h56
-rw-r--r--qpid/cpp/src/qpid/BufferRef.h70
-rw-r--r--qpid/cpp/src/qpid/CommonImportExport.h35
-rw-r--r--qpid/cpp/src/qpid/DataDir.cpp47
-rw-r--r--qpid/cpp/src/qpid/DataDir.h54
-rw-r--r--qpid/cpp/src/qpid/DisableExceptionLogging.h39
-rw-r--r--qpid/cpp/src/qpid/Exception.cpp71
-rw-r--r--qpid/cpp/src/qpid/Exception.h95
-rw-r--r--qpid/cpp/src/qpid/InlineAllocator.h101
-rw-r--r--qpid/cpp/src/qpid/InlineVector.h68
-rw-r--r--qpid/cpp/src/qpid/Modules.cpp94
-rw-r--r--qpid/cpp/src/qpid/Modules.h44
-rw-r--r--qpid/cpp/src/qpid/Msg.h79
-rw-r--r--qpid/cpp/src/qpid/NullSaslClient.cpp64
-rw-r--r--qpid/cpp/src/qpid/NullSaslClient.h45
-rw-r--r--qpid/cpp/src/qpid/NullSaslServer.cpp85
-rw-r--r--qpid/cpp/src/qpid/NullSaslServer.h50
-rw-r--r--qpid/cpp/src/qpid/Options.cpp335
-rw-r--r--qpid/cpp/src/qpid/Options.h203
-rw-r--r--qpid/cpp/src/qpid/OptionsTemplates.h52
-rw-r--r--qpid/cpp/src/qpid/Plugin.cpp94
-rw-r--r--qpid/cpp/src/qpid/Plugin.h129
-rw-r--r--qpid/cpp/src/qpid/RangeSet.h330
-rw-r--r--qpid/cpp/src/qpid/RefCounted.h59
-rw-r--r--qpid/cpp/src/qpid/RefCountedBuffer.cpp46
-rw-r--r--qpid/cpp/src/qpid/RefCountedBuffer.h44
-rw-r--r--qpid/cpp/src/qpid/Sasl.h61
-rw-r--r--qpid/cpp/src/qpid/SaslFactory.cpp646
-rw-r--r--qpid/cpp/src/qpid/SaslFactory.h49
-rw-r--r--qpid/cpp/src/qpid/SaslServer.h48
-rw-r--r--qpid/cpp/src/qpid/Serializer.h197
-rw-r--r--qpid/cpp/src/qpid/SessionId.cpp47
-rw-r--r--qpid/cpp/src/qpid/SessionId.h60
-rw-r--r--qpid/cpp/src/qpid/SessionState.cpp287
-rw-r--r--qpid/cpp/src/qpid/SessionState.h235
-rw-r--r--qpid/cpp/src/qpid/SharedObject.h55
-rw-r--r--qpid/cpp/src/qpid/StringUtils.cpp50
-rw-r--r--qpid/cpp/src/qpid/StringUtils.h45
-rw-r--r--qpid/cpp/src/qpid/Url.cpp281
-rw-r--r--qpid/cpp/src/qpid/Url.h96
-rw-r--r--qpid/cpp/src/qpid/UrlArray.cpp43
-rw-r--r--qpid/cpp/src/qpid/UrlArray.h36
-rwxr-xr-xqpid/cpp/src/qpid/Version.h36
-rw-r--r--qpid/cpp/src/qpid/acl/Acl.cpp450
-rw-r--r--qpid/cpp/src/qpid/acl/Acl.h134
-rw-r--r--qpid/cpp/src/qpid/acl/AclConnectionCounter.cpp320
-rw-r--r--qpid/cpp/src/qpid/acl/AclConnectionCounter.h106
-rw-r--r--qpid/cpp/src/qpid/acl/AclData.cpp890
-rw-r--r--qpid/cpp/src/qpid/acl/AclData.h323
-rw-r--r--qpid/cpp/src/qpid/acl/AclLexer.cpp141
-rw-r--r--qpid/cpp/src/qpid/acl/AclLexer.h200
-rw-r--r--qpid/cpp/src/qpid/acl/AclPlugin.cpp98
-rw-r--r--qpid/cpp/src/qpid/acl/AclReader.cpp827
-rw-r--r--qpid/cpp/src/qpid/acl/AclReader.h150
-rw-r--r--qpid/cpp/src/qpid/acl/AclResourceCounter.cpp174
-rw-r--r--qpid/cpp/src/qpid/acl/AclResourceCounter.h78
-rw-r--r--qpid/cpp/src/qpid/acl/AclTopicMatch.h89
-rw-r--r--qpid/cpp/src/qpid/acl/AclValidator.cpp536
-rw-r--r--qpid/cpp/src/qpid/acl/AclValidator.h106
-rw-r--r--qpid/cpp/src/qpid/acl/management-schema.xml85
-rw-r--r--qpid/cpp/src/qpid/amqp/CharSequence.cpp65
-rw-r--r--qpid/cpp/src/qpid/amqp/CharSequence.h52
-rw-r--r--qpid/cpp/src/qpid/amqp/Codec.h83
-rw-r--r--qpid/cpp/src/qpid/amqp/Constructor.h42
-rw-r--r--qpid/cpp/src/qpid/amqp/DataBuilder.cpp196
-rw-r--r--qpid/cpp/src/qpid/amqp/DataBuilder.h79
-rw-r--r--qpid/cpp/src/qpid/amqp/Decoder.cpp448
-rw-r--r--qpid/cpp/src/qpid/amqp/Decoder.h100
-rw-r--r--qpid/cpp/src/qpid/amqp/Descriptor.cpp148
-rw-r--r--qpid/cpp/src/qpid/amqp/Descriptor.h60
-rw-r--r--qpid/cpp/src/qpid/amqp/Encoder.cpp523
-rw-r--r--qpid/cpp/src/qpid/amqp/Encoder.h181
-rw-r--r--qpid/cpp/src/qpid/amqp/ListBuilder.cpp33
-rw-r--r--qpid/cpp/src/qpid/amqp/ListBuilder.h41
-rw-r--r--qpid/cpp/src/qpid/amqp/ListReader.h103
-rw-r--r--qpid/cpp/src/qpid/amqp/LoggingReader.h64
-rw-r--r--qpid/cpp/src/qpid/amqp/MapBuilder.cpp30
-rw-r--r--qpid/cpp/src/qpid/amqp/MapBuilder.h41
-rw-r--r--qpid/cpp/src/qpid/amqp/MapEncoder.cpp142
-rw-r--r--qpid/cpp/src/qpid/amqp/MapEncoder.h58
-rw-r--r--qpid/cpp/src/qpid/amqp/MapHandler.h53
-rw-r--r--qpid/cpp/src/qpid/amqp/MapReader.cpp309
-rw-r--r--qpid/cpp/src/qpid/amqp/MapReader.h110
-rw-r--r--qpid/cpp/src/qpid/amqp/MapSizeCalculator.cpp151
-rw-r--r--qpid/cpp/src/qpid/amqp/MapSizeCalculator.h73
-rw-r--r--qpid/cpp/src/qpid/amqp/MessageEncoder.cpp312
-rw-r--r--qpid/cpp/src/qpid/amqp/MessageEncoder.h117
-rw-r--r--qpid/cpp/src/qpid/amqp/MessageId.cpp82
-rw-r--r--qpid/cpp/src/qpid/amqp/MessageId.h57
-rw-r--r--qpid/cpp/src/qpid/amqp/MessageReader.cpp685
-rw-r--r--qpid/cpp/src/qpid/amqp/MessageReader.h161
-rw-r--r--qpid/cpp/src/qpid/amqp/Reader.h80
-rw-r--r--qpid/cpp/src/qpid/amqp/Sasl.cpp141
-rw-r--r--qpid/cpp/src/qpid/amqp/Sasl.h55
-rw-r--r--qpid/cpp/src/qpid/amqp/SaslClient.cpp154
-rw-r--r--qpid/cpp/src/qpid/amqp/SaslClient.h55
-rw-r--r--qpid/cpp/src/qpid/amqp/SaslServer.cpp183
-rw-r--r--qpid/cpp/src/qpid/amqp/SaslServer.h50
-rw-r--r--qpid/cpp/src/qpid/amqp/descriptors.h140
-rw-r--r--qpid/cpp/src/qpid/amqp/typecodes.h115
-rw-r--r--qpid/cpp/src/qpid/amqp_0_10/Codecs.cpp586
-rw-r--r--qpid/cpp/src/qpid/amqp_0_10/Codecs.h87
-rw-r--r--qpid/cpp/src/qpid/amqp_0_10/CodecsInternal.h42
-rw-r--r--qpid/cpp/src/qpid/amqp_0_10/Connection.cpp150
-rw-r--r--qpid/cpp/src/qpid/amqp_0_10/Connection.h78
-rw-r--r--qpid/cpp/src/qpid/amqp_0_10/SessionHandler.cpp333
-rw-r--r--qpid/cpp/src/qpid/amqp_0_10/SessionHandler.h118
-rw-r--r--qpid/cpp/src/qpid/assert.cpp47
-rw-r--r--qpid/cpp/src/qpid/assert.h38
-rw-r--r--qpid/cpp/src/qpid/broker/AclModule.h75
-rw-r--r--qpid/cpp/src/qpid/broker/AsyncCommandCallback.cpp70
-rw-r--r--qpid/cpp/src/qpid/broker/AsyncCommandCallback.h68
-rw-r--r--qpid/cpp/src/qpid/broker/AsyncCompletion.h209
-rw-r--r--qpid/cpp/src/qpid/broker/Bridge.cpp467
-rw-r--r--qpid/cpp/src/qpid/broker/Bridge.h158
-rw-r--r--qpid/cpp/src/qpid/broker/Broker.cpp1686
-rw-r--r--qpid/cpp/src/qpid/broker/Broker.h350
-rw-r--r--qpid/cpp/src/qpid/broker/BrokerImportExport.h42
-rw-r--r--qpid/cpp/src/qpid/broker/BrokerObserver.h68
-rw-r--r--qpid/cpp/src/qpid/broker/BrokerObservers.h73
-rw-r--r--qpid/cpp/src/qpid/broker/BrokerOptions.h88
-rw-r--r--qpid/cpp/src/qpid/broker/Connection.h53
-rw-r--r--qpid/cpp/src/qpid/broker/ConnectionHandler.cpp469
-rw-r--r--qpid/cpp/src/qpid/broker/ConnectionHandler.h118
-rw-r--r--qpid/cpp/src/qpid/broker/ConnectionObserver.h59
-rw-r--r--qpid/cpp/src/qpid/broker/ConnectionObservers.h56
-rw-r--r--qpid/cpp/src/qpid/broker/Consumer.h108
-rw-r--r--qpid/cpp/src/qpid/broker/ConsumerFactory.h73
-rw-r--r--qpid/cpp/src/qpid/broker/Credit.cpp151
-rw-r--r--qpid/cpp/src/qpid/broker/Credit.h96
-rw-r--r--qpid/cpp/src/qpid/broker/Daemon.cpp217
-rw-r--r--qpid/cpp/src/qpid/broker/Daemon.h83
-rw-r--r--qpid/cpp/src/qpid/broker/Deliverable.h47
-rw-r--r--qpid/cpp/src/qpid/broker/DeliverableMessage.cpp38
-rw-r--r--qpid/cpp/src/qpid/broker/DeliverableMessage.h45
-rw-r--r--qpid/cpp/src/qpid/broker/DeliveryId.h35
-rw-r--r--qpid/cpp/src/qpid/broker/DeliveryRecord.cpp168
-rw-r--r--qpid/cpp/src/qpid/broker/DeliveryRecord.h152
-rw-r--r--qpid/cpp/src/qpid/broker/DirectExchange.cpp214
-rw-r--r--qpid/cpp/src/qpid/broker/DirectExchange.h74
-rw-r--r--qpid/cpp/src/qpid/broker/DtxAck.cpp77
-rw-r--r--qpid/cpp/src/qpid/broker/DtxAck.h51
-rw-r--r--qpid/cpp/src/qpid/broker/DtxBuffer.cpp91
-rw-r--r--qpid/cpp/src/qpid/broker/DtxBuffer.h59
-rw-r--r--qpid/cpp/src/qpid/broker/DtxManager.cpp218
-rw-r--r--qpid/cpp/src/qpid/broker/DtxManager.h75
-rw-r--r--qpid/cpp/src/qpid/broker/DtxTimeout.cpp37
-rw-r--r--qpid/cpp/src/qpid/broker/DtxTimeout.h50
-rw-r--r--qpid/cpp/src/qpid/broker/DtxWorkRecord.cpp187
-rw-r--r--qpid/cpp/src/qpid/broker/DtxWorkRecord.h86
-rw-r--r--qpid/cpp/src/qpid/broker/Exchange.cpp504
-rw-r--r--qpid/cpp/src/qpid/broker/Exchange.h268
-rw-r--r--qpid/cpp/src/qpid/broker/ExchangeRegistry.cpp177
-rw-r--r--qpid/cpp/src/qpid/broker/ExchangeRegistry.h127
-rw-r--r--qpid/cpp/src/qpid/broker/Fairshare.cpp125
-rw-r--r--qpid/cpp/src/qpid/broker/Fairshare.h60
-rw-r--r--qpid/cpp/src/qpid/broker/FanOutExchange.cpp132
-rw-r--r--qpid/cpp/src/qpid/broker/FanOutExchange.h74
-rw-r--r--qpid/cpp/src/qpid/broker/FedOps.h38
-rw-r--r--qpid/cpp/src/qpid/broker/FifoDistributor.cpp45
-rw-r--r--qpid/cpp/src/qpid/broker/FifoDistributor.h50
-rw-r--r--qpid/cpp/src/qpid/broker/HandlerImpl.h53
-rw-r--r--qpid/cpp/src/qpid/broker/HeadersExchange.cpp417
-rw-r--r--qpid/cpp/src/qpid/broker/HeadersExchange.h119
-rw-r--r--qpid/cpp/src/qpid/broker/IndexedDeque.h226
-rw-r--r--qpid/cpp/src/qpid/broker/IngressCompletion.cpp48
-rw-r--r--qpid/cpp/src/qpid/broker/IngressCompletion.h52
-rw-r--r--qpid/cpp/src/qpid/broker/Link.cpp795
-rw-r--r--qpid/cpp/src/qpid/broker/Link.h205
-rw-r--r--qpid/cpp/src/qpid/broker/LinkRegistry.cpp435
-rw-r--r--qpid/cpp/src/qpid/broker/LinkRegistry.h155
-rw-r--r--qpid/cpp/src/qpid/broker/LossyLvq.cpp30
-rw-r--r--qpid/cpp/src/qpid/broker/LossyLvq.h52
-rw-r--r--qpid/cpp/src/qpid/broker/LossyQueue.cpp97
-rw-r--r--qpid/cpp/src/qpid/broker/LossyQueue.h41
-rw-r--r--qpid/cpp/src/qpid/broker/Lvq.cpp64
-rw-r--r--qpid/cpp/src/qpid/broker/Lvq.h45
-rw-r--r--qpid/cpp/src/qpid/broker/Message.cpp368
-rw-r--r--qpid/cpp/src/qpid/broker/Message.h227
-rw-r--r--qpid/cpp/src/qpid/broker/MessageAdapter.cpp81
-rw-r--r--qpid/cpp/src/qpid/broker/MessageAdapter.h62
-rw-r--r--qpid/cpp/src/qpid/broker/MessageBuilder.cpp122
-rw-r--r--qpid/cpp/src/qpid/broker/MessageBuilder.h59
-rw-r--r--qpid/cpp/src/qpid/broker/MessageDeque.cpp89
-rw-r--r--qpid/cpp/src/qpid/broker/MessageDeque.h55
-rw-r--r--qpid/cpp/src/qpid/broker/MessageDistributor.h53
-rw-r--r--qpid/cpp/src/qpid/broker/MessageGroupManager.cpp313
-rw-r--r--qpid/cpp/src/qpid/broker/MessageGroupManager.h129
-rw-r--r--qpid/cpp/src/qpid/broker/MessageInterceptor.h58
-rw-r--r--qpid/cpp/src/qpid/broker/MessageMap.cpp161
-rw-r--r--qpid/cpp/src/qpid/broker/MessageMap.h71
-rw-r--r--qpid/cpp/src/qpid/broker/MessageStore.h189
-rw-r--r--qpid/cpp/src/qpid/broker/MessageStoreModule.cpp173
-rw-r--r--qpid/cpp/src/qpid/broker/MessageStoreModule.h85
-rw-r--r--qpid/cpp/src/qpid/broker/Messages.h101
-rw-r--r--qpid/cpp/src/qpid/broker/NameGenerator.cpp32
-rw-r--r--qpid/cpp/src/qpid/broker/NameGenerator.h40
-rw-r--r--qpid/cpp/src/qpid/broker/NullMessageStore.cpp163
-rw-r--r--qpid/cpp/src/qpid/broker/NullMessageStore.h98
-rw-r--r--qpid/cpp/src/qpid/broker/ObjectFactory.cpp75
-rw-r--r--qpid/cpp/src/qpid/broker/ObjectFactory.h68
-rw-r--r--qpid/cpp/src/qpid/broker/Observers.h93
-rw-r--r--qpid/cpp/src/qpid/broker/OwnershipToken.h36
-rw-r--r--qpid/cpp/src/qpid/broker/PagedQueue.cpp460
-rw-r--r--qpid/cpp/src/qpid/broker/PagedQueue.h100
-rw-r--r--qpid/cpp/src/qpid/broker/Persistable.h63
-rw-r--r--qpid/cpp/src/qpid/broker/PersistableConfig.h45
-rw-r--r--qpid/cpp/src/qpid/broker/PersistableExchange.h45
-rw-r--r--qpid/cpp/src/qpid/broker/PersistableMessage.cpp71
-rw-r--r--qpid/cpp/src/qpid/broker/PersistableMessage.h95
-rw-r--r--qpid/cpp/src/qpid/broker/PersistableObject.cpp89
-rw-r--r--qpid/cpp/src/qpid/broker/PersistableObject.h73
-rw-r--r--qpid/cpp/src/qpid/broker/PersistableQueue.h77
-rw-r--r--qpid/cpp/src/qpid/broker/PriorityQueue.cpp234
-rw-r--r--qpid/cpp/src/qpid/broker/PriorityQueue.h109
-rw-r--r--qpid/cpp/src/qpid/broker/Protocol.cpp141
-rw-r--r--qpid/cpp/src/qpid/broker/Protocol.h96
-rw-r--r--qpid/cpp/src/qpid/broker/Queue.cpp1764
-rw-r--r--qpid/cpp/src/qpid/broker/Queue.h540
-rw-r--r--qpid/cpp/src/qpid/broker/QueueBindings.cpp53
-rw-r--r--qpid/cpp/src/qpid/broker/QueueBindings.h70
-rw-r--r--qpid/cpp/src/qpid/broker/QueueCleaner.cpp98
-rw-r--r--qpid/cpp/src/qpid/broker/QueueCleaner.h70
-rw-r--r--qpid/cpp/src/qpid/broker/QueueCursor.cpp44
-rw-r--r--qpid/cpp/src/qpid/broker/QueueCursor.h72
-rw-r--r--qpid/cpp/src/qpid/broker/QueueDepth.cpp127
-rw-r--r--qpid/cpp/src/qpid/broker/QueueDepth.h74
-rw-r--r--qpid/cpp/src/qpid/broker/QueueFactory.cpp142
-rw-r--r--qpid/cpp/src/qpid/broker/QueueFactory.h76
-rw-r--r--qpid/cpp/src/qpid/broker/QueueFlowLimit.cpp307
-rw-r--r--qpid/cpp/src/qpid/broker/QueueFlowLimit.h127
-rw-r--r--qpid/cpp/src/qpid/broker/QueueListeners.cpp93
-rw-r--r--qpid/cpp/src/qpid/broker/QueueListeners.h85
-rw-r--r--qpid/cpp/src/qpid/broker/QueueObserver.h76
-rw-r--r--qpid/cpp/src/qpid/broker/QueueObservers.h77
-rw-r--r--qpid/cpp/src/qpid/broker/QueueRegistry.cpp145
-rw-r--r--qpid/cpp/src/qpid/broker/QueueRegistry.h135
-rw-r--r--qpid/cpp/src/qpid/broker/QueueSettings.cpp328
-rw-r--r--qpid/cpp/src/qpid/broker/QueueSettings.h124
-rw-r--r--qpid/cpp/src/qpid/broker/QueuedMessage.cpp34
-rw-r--r--qpid/cpp/src/qpid/broker/QueuedMessage.h55
-rw-r--r--qpid/cpp/src/qpid/broker/RecoverableConfig.h45
-rw-r--r--qpid/cpp/src/qpid/broker/RecoverableExchange.h55
-rw-r--r--qpid/cpp/src/qpid/broker/RecoverableMessage.h62
-rw-r--r--qpid/cpp/src/qpid/broker/RecoverableMessageImpl.h50
-rw-r--r--qpid/cpp/src/qpid/broker/RecoverableQueue.h63
-rw-r--r--qpid/cpp/src/qpid/broker/RecoverableTransaction.h49
-rw-r--r--qpid/cpp/src/qpid/broker/RecoveredDequeue.cpp47
-rw-r--r--qpid/cpp/src/qpid/broker/RecoveredDequeue.h55
-rw-r--r--qpid/cpp/src/qpid/broker/RecoveredEnqueue.cpp44
-rw-r--r--qpid/cpp/src/qpid/broker/RecoveredEnqueue.h55
-rw-r--r--qpid/cpp/src/qpid/broker/RecoveryManager.h61
-rw-r--r--qpid/cpp/src/qpid/broker/RecoveryManagerImpl.cpp288
-rw-r--r--qpid/cpp/src/qpid/broker/RecoveryManagerImpl.h65
-rw-r--r--qpid/cpp/src/qpid/broker/RetryList.cpp56
-rw-r--r--qpid/cpp/src/qpid/broker/RetryList.h53
-rw-r--r--qpid/cpp/src/qpid/broker/SaslAuthenticator.cpp567
-rw-r--r--qpid/cpp/src/qpid/broker/SaslAuthenticator.h66
-rw-r--r--qpid/cpp/src/qpid/broker/SecureConnection.cpp90
-rw-r--r--qpid/cpp/src/qpid/broker/SecureConnection.h60
-rw-r--r--qpid/cpp/src/qpid/broker/Selector.cpp237
-rw-r--r--qpid/cpp/src/qpid/broker/Selector.h80
-rw-r--r--qpid/cpp/src/qpid/broker/SelectorExpression.cpp1016
-rw-r--r--qpid/cpp/src/qpid/broker/SelectorExpression.h44
-rw-r--r--qpid/cpp/src/qpid/broker/SelectorToken.cpp298
-rw-r--r--qpid/cpp/src/qpid/broker/SelectorToken.h126
-rw-r--r--qpid/cpp/src/qpid/broker/SelectorValue.cpp212
-rw-r--r--qpid/cpp/src/qpid/broker/SelectorValue.h121
-rw-r--r--qpid/cpp/src/qpid/broker/SelfDestructQueue.cpp45
-rw-r--r--qpid/cpp/src/qpid/broker/SelfDestructQueue.h45
-rw-r--r--qpid/cpp/src/qpid/broker/SemanticState.cpp896
-rw-r--r--qpid/cpp/src/qpid/broker/SemanticState.h295
-rw-r--r--qpid/cpp/src/qpid/broker/SessionAdapter.cpp680
-rw-r--r--qpid/cpp/src/qpid/broker/SessionAdapter.h272
-rw-r--r--qpid/cpp/src/qpid/broker/SessionContext.h61
-rw-r--r--qpid/cpp/src/qpid/broker/SessionHandler.cpp163
-rw-r--r--qpid/cpp/src/qpid/broker/SessionHandler.h121
-rw-r--r--qpid/cpp/src/qpid/broker/SessionHandlerObserver.h51
-rw-r--r--qpid/cpp/src/qpid/broker/SessionManager.cpp103
-rw-r--r--qpid/cpp/src/qpid/broker/SessionManager.h87
-rw-r--r--qpid/cpp/src/qpid/broker/SessionOutputException.h47
-rw-r--r--qpid/cpp/src/qpid/broker/SessionState.cpp527
-rw-r--r--qpid/cpp/src/qpid/broker/SessionState.h329
-rw-r--r--qpid/cpp/src/qpid/broker/SignalHandler.cpp54
-rw-r--r--qpid/cpp/src/qpid/broker/SignalHandler.h50
-rw-r--r--qpid/cpp/src/qpid/broker/System.cpp88
-rw-r--r--qpid/cpp/src/qpid/broker/System.h70
-rw-r--r--qpid/cpp/src/qpid/broker/ThresholdAlerts.cpp113
-rw-r--r--qpid/cpp/src/qpid/broker/ThresholdAlerts.h77
-rw-r--r--qpid/cpp/src/qpid/broker/TopicExchange.cpp350
-rw-r--r--qpid/cpp/src/qpid/broker/TopicExchange.h122
-rw-r--r--qpid/cpp/src/qpid/broker/TopicKeyNode.h371
-rw-r--r--qpid/cpp/src/qpid/broker/TransactionObserver.h82
-rw-r--r--qpid/cpp/src/qpid/broker/TransactionalStore.h60
-rw-r--r--qpid/cpp/src/qpid/broker/TxAccept.cpp95
-rw-r--r--qpid/cpp/src/qpid/broker/TxAccept.h64
-rw-r--r--qpid/cpp/src/qpid/broker/TxBuffer.cpp103
-rw-r--r--qpid/cpp/src/qpid/broker/TxBuffer.h148
-rw-r--r--qpid/cpp/src/qpid/broker/TxDequeue.cpp68
-rw-r--r--qpid/cpp/src/qpid/broker/TxDequeue.h54
-rw-r--r--qpid/cpp/src/qpid/broker/TxOp.h45
-rw-r--r--qpid/cpp/src/qpid/broker/Vhost.cpp54
-rw-r--r--qpid/cpp/src/qpid/broker/Vhost.h52
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Authorise.cpp149
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Authorise.h65
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/BrokerContext.cpp33
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/BrokerContext.h55
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Connection.cpp678
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Connection.h111
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/DataReader.cpp193
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/DataReader.h59
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Domain.cpp303
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Domain.h82
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Exception.cpp30
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Exception.h46
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Filter.cpp402
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Filter.h126
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Header.cpp65
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Header.h50
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Incoming.cpp155
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Incoming.h87
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Interconnect.cpp163
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Interconnect.h65
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Interconnects.cpp176
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Interconnects.h66
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/ManagedConnection.cpp214
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/ManagedConnection.h80
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/ManagedIncomingLink.cpp58
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/ManagedIncomingLink.h50
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/ManagedOutgoingLink.cpp68
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/ManagedOutgoingLink.h52
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/ManagedSession.cpp158
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/ManagedSession.h66
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Message.cpp472
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Message.h160
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/NodePolicy.cpp326
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/NodePolicy.h117
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/NodeProperties.cpp388
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/NodeProperties.h90
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Outgoing.cpp331
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Outgoing.h151
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/ProtocolPlugin.cpp168
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Relay.cpp301
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Relay.h130
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Sasl.cpp167
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Sasl.h71
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/SaslClient.cpp181
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/SaslClient.h81
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Session.cpp952
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Session.h144
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Topic.cpp209
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Topic.h91
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Translation.cpp347
-rw-r--r--qpid/cpp/src/qpid/broker/amqp/Translation.h60
-rw-r--r--qpid/cpp/src/qpid/broker/amqp_0_10/Connection.cpp549
-rw-r--r--qpid/cpp/src/qpid/broker/amqp_0_10/Connection.h232
-rw-r--r--qpid/cpp/src/qpid/broker/amqp_0_10/MessageTransfer.cpp430
-rw-r--r--qpid/cpp/src/qpid/broker/amqp_0_10/MessageTransfer.h138
-rw-r--r--qpid/cpp/src/qpid/broker/management-schema.xml624
-rw-r--r--qpid/cpp/src/qpid/broker/posix/BrokerDefaults.cpp42
-rw-r--r--qpid/cpp/src/qpid/broker/posix/SocketFDPlugin.cpp93
-rw-r--r--qpid/cpp/src/qpid/broker/windows/BrokerDefaults.cpp49
-rw-r--r--qpid/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp209
-rw-r--r--qpid/cpp/src/qpid/broker/windows/SslProtocolFactory.cpp313
-rw-r--r--qpid/cpp/src/qpid/client/AsyncSession.h38
-rw-r--r--qpid/cpp/src/qpid/client/Bounds.cpp71
-rw-r--r--qpid/cpp/src/qpid/client/Bounds.h49
-rw-r--r--qpid/cpp/src/qpid/client/ChainableFrameHandler.h47
-rw-r--r--qpid/cpp/src/qpid/client/ClientImportExport.h35
-rw-r--r--qpid/cpp/src/qpid/client/Completion.cpp43
-rw-r--r--qpid/cpp/src/qpid/client/Completion.h71
-rw-r--r--qpid/cpp/src/qpid/client/CompletionImpl.cpp34
-rw-r--r--qpid/cpp/src/qpid/client/CompletionImpl.h52
-rw-r--r--qpid/cpp/src/qpid/client/Connection.cpp161
-rw-r--r--qpid/cpp/src/qpid/client/Connection.h227
-rw-r--r--qpid/cpp/src/qpid/client/ConnectionAccess.h42
-rw-r--r--qpid/cpp/src/qpid/client/ConnectionHandler.cpp392
-rw-r--r--qpid/cpp/src/qpid/client/ConnectionHandler.h139
-rw-r--r--qpid/cpp/src/qpid/client/ConnectionImpl.cpp452
-rw-r--r--qpid/cpp/src/qpid/client/ConnectionImpl.h104
-rw-r--r--qpid/cpp/src/qpid/client/ConnectionSettings.cpp58
-rw-r--r--qpid/cpp/src/qpid/client/ConnectionSettings.h145
-rw-r--r--qpid/cpp/src/qpid/client/Connector.cpp94
-rw-r--r--qpid/cpp/src/qpid/client/Connector.h89
-rw-r--r--qpid/cpp/src/qpid/client/Demux.cpp132
-rw-r--r--qpid/cpp/src/qpid/client/Demux.h103
-rw-r--r--qpid/cpp/src/qpid/client/Dispatcher.cpp151
-rw-r--r--qpid/cpp/src/qpid/client/Dispatcher.h87
-rw-r--r--qpid/cpp/src/qpid/client/Execution.h53
-rw-r--r--qpid/cpp/src/qpid/client/FailoverListener.cpp93
-rw-r--r--qpid/cpp/src/qpid/client/FailoverListener.h88
-rw-r--r--qpid/cpp/src/qpid/client/FailoverManager.cpp133
-rw-r--r--qpid/cpp/src/qpid/client/FailoverManager.h138
-rw-r--r--qpid/cpp/src/qpid/client/FlowControl.h75
-rw-r--r--qpid/cpp/src/qpid/client/Future.cpp46
-rw-r--r--qpid/cpp/src/qpid/client/Future.h59
-rw-r--r--qpid/cpp/src/qpid/client/FutureCompletion.cpp48
-rw-r--r--qpid/cpp/src/qpid/client/FutureCompletion.h49
-rw-r--r--qpid/cpp/src/qpid/client/FutureResult.cpp43
-rw-r--r--qpid/cpp/src/qpid/client/FutureResult.h49
-rw-r--r--qpid/cpp/src/qpid/client/Handle.h72
-rw-r--r--qpid/cpp/src/qpid/client/LoadPlugins.cpp66
-rw-r--r--qpid/cpp/src/qpid/client/LoadPlugins.h35
-rw-r--r--qpid/cpp/src/qpid/client/LocalQueue.cpp52
-rw-r--r--qpid/cpp/src/qpid/client/LocalQueue.h120
-rw-r--r--qpid/cpp/src/qpid/client/LocalQueueImpl.cpp78
-rw-r--r--qpid/cpp/src/qpid/client/LocalQueueImpl.h108
-rw-r--r--qpid/cpp/src/qpid/client/Message.cpp62
-rw-r--r--qpid/cpp/src/qpid/client/Message.h175
-rw-r--r--qpid/cpp/src/qpid/client/MessageImpl.cpp71
-rw-r--r--qpid/cpp/src/qpid/client/MessageImpl.h80
-rw-r--r--qpid/cpp/src/qpid/client/MessageListener.cpp24
-rw-r--r--qpid/cpp/src/qpid/client/MessageListener.h101
-rw-r--r--qpid/cpp/src/qpid/client/MessageReplayTracker.cpp78
-rw-r--r--qpid/cpp/src/qpid/client/MessageReplayTracker.h73
-rw-r--r--qpid/cpp/src/qpid/client/PrivateImplRef.h94
-rw-r--r--qpid/cpp/src/qpid/client/QueueOptions.cpp105
-rw-r--r--qpid/cpp/src/qpid/client/QueueOptions.h95
-rw-r--r--qpid/cpp/src/qpid/client/RdmaConnector.cpp420
-rw-r--r--qpid/cpp/src/qpid/client/Results.cpp75
-rw-r--r--qpid/cpp/src/qpid/client/Results.h56
-rw-r--r--qpid/cpp/src/qpid/client/Session.h39
-rw-r--r--qpid/cpp/src/qpid/client/SessionBase_0_10.cpp85
-rw-r--r--qpid/cpp/src/qpid/client/SessionBase_0_10.h109
-rw-r--r--qpid/cpp/src/qpid/client/SessionBase_0_10Access.h42
-rw-r--r--qpid/cpp/src/qpid/client/SessionImpl.cpp714
-rw-r--r--qpid/cpp/src/qpid/client/SessionImpl.h220
-rw-r--r--qpid/cpp/src/qpid/client/SslConnector.cpp428
-rw-r--r--qpid/cpp/src/qpid/client/StateManager.cpp100
-rw-r--r--qpid/cpp/src/qpid/client/StateManager.h50
-rw-r--r--qpid/cpp/src/qpid/client/Subscription.cpp55
-rw-r--r--qpid/cpp/src/qpid/client/Subscription.h123
-rw-r--r--qpid/cpp/src/qpid/client/SubscriptionImpl.cpp169
-rw-r--r--qpid/cpp/src/qpid/client/SubscriptionImpl.h125
-rw-r--r--qpid/cpp/src/qpid/client/SubscriptionManager.cpp106
-rw-r--r--qpid/cpp/src/qpid/client/SubscriptionManager.h292
-rw-r--r--qpid/cpp/src/qpid/client/SubscriptionManagerImpl.cpp182
-rw-r--r--qpid/cpp/src/qpid/client/SubscriptionManagerImpl.h279
-rw-r--r--qpid/cpp/src/qpid/client/SubscriptionSettings.h135
-rw-r--r--qpid/cpp/src/qpid/client/TCPConnector.cpp323
-rw-r--r--qpid/cpp/src/qpid/client/TCPConnector.h116
-rw-r--r--qpid/cpp/src/qpid/client/TypedResult.h65
-rw-r--r--qpid/cpp/src/qpid/client/amqp0_10/AcceptTracker.cpp153
-rw-r--r--qpid/cpp/src/qpid/client/amqp0_10/AcceptTracker.h88
-rw-r--r--qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp1058
-rw-r--r--qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.h64
-rw-r--r--qpid/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp404
-rw-r--r--qpid/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h88
-rw-r--r--qpid/cpp/src/qpid/client/amqp0_10/IncomingMessages.cpp466
-rw-r--r--qpid/cpp/src/qpid/client/amqp0_10/IncomingMessages.h108
-rw-r--r--qpid/cpp/src/qpid/client/amqp0_10/MessageSink.h52
-rw-r--r--qpid/cpp/src/qpid/client/amqp0_10/MessageSource.h47
-rw-r--r--qpid/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp170
-rw-r--r--qpid/cpp/src/qpid/client/amqp0_10/OutgoingMessage.h60
-rw-r--r--qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.cpp263
-rw-r--r--qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h152
-rw-r--r--qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp206
-rw-r--r--qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.h162
-rw-r--r--qpid/cpp/src/qpid/client/amqp0_10/SessionImpl.cpp606
-rw-r--r--qpid/cpp/src/qpid/client/amqp0_10/SessionImpl.h259
-rw-r--r--qpid/cpp/src/qpid/client/ssl.h30
-rw-r--r--qpid/cpp/src/qpid/client/windows/ClientDllMain.cpp22
-rw-r--r--qpid/cpp/src/qpid/client/windows/SaslFactory.cpp202
-rw-r--r--qpid/cpp/src/qpid/client/windows/SslConnector.cpp147
-rw-r--r--qpid/cpp/src/qpid/framing/AMQBody.cpp64
-rw-r--r--qpid/cpp/src/qpid/framing/AMQBody.h86
-rw-r--r--qpid/cpp/src/qpid/framing/AMQContentBody.cpp46
-rw-r--r--qpid/cpp/src/qpid/framing/AMQContentBody.h55
-rw-r--r--qpid/cpp/src/qpid/framing/AMQDataBlock.h42
-rw-r--r--qpid/cpp/src/qpid/framing/AMQFrame.cpp158
-rw-r--r--qpid/cpp/src/qpid/framing/AMQFrame.h116
-rw-r--r--qpid/cpp/src/qpid/framing/AMQHeaderBody.cpp63
-rw-r--r--qpid/cpp/src/qpid/framing/AMQHeaderBody.h113
-rw-r--r--qpid/cpp/src/qpid/framing/AMQHeartbeatBody.cpp29
-rw-r--r--qpid/cpp/src/qpid/framing/AMQHeartbeatBody.h48
-rw-r--r--qpid/cpp/src/qpid/framing/AMQMethodBody.cpp28
-rw-r--r--qpid/cpp/src/qpid/framing/AMQMethodBody.h72
-rw-r--r--qpid/cpp/src/qpid/framing/AMQP_HighestVersion.h40
-rw-r--r--qpid/cpp/src/qpid/framing/AccumulatedAck.cpp164
-rw-r--r--qpid/cpp/src/qpid/framing/AccumulatedAck.h77
-rw-r--r--qpid/cpp/src/qpid/framing/Array.cpp137
-rw-r--r--qpid/cpp/src/qpid/framing/Array.h99
-rw-r--r--qpid/cpp/src/qpid/framing/Blob.cpp31
-rw-r--r--qpid/cpp/src/qpid/framing/Blob.h21
-rw-r--r--qpid/cpp/src/qpid/framing/BodyFactory.h47
-rw-r--r--qpid/cpp/src/qpid/framing/Buffer.cpp342
-rw-r--r--qpid/cpp/src/qpid/framing/Buffer.h115
-rw-r--r--qpid/cpp/src/qpid/framing/BufferTypes.h106
-rw-r--r--qpid/cpp/src/qpid/framing/ChannelHandler.h53
-rw-r--r--qpid/cpp/src/qpid/framing/Endian.h78
-rw-r--r--qpid/cpp/src/qpid/framing/FieldTable.cpp433
-rw-r--r--qpid/cpp/src/qpid/framing/FieldTable.h139
-rw-r--r--qpid/cpp/src/qpid/framing/FieldValue.cpp267
-rw-r--r--qpid/cpp/src/qpid/framing/FieldValue.h482
-rw-r--r--qpid/cpp/src/qpid/framing/FrameDecoder.cpp81
-rw-r--r--qpid/cpp/src/qpid/framing/FrameDecoder.h52
-rw-r--r--qpid/cpp/src/qpid/framing/FrameDefaultVisitor.h60
-rw-r--r--qpid/cpp/src/qpid/framing/FrameHandler.h33
-rw-r--r--qpid/cpp/src/qpid/framing/FrameSet.cpp128
-rw-r--r--qpid/cpp/src/qpid/framing/FrameSet.h130
-rw-r--r--qpid/cpp/src/qpid/framing/Handler.h103
-rw-r--r--qpid/cpp/src/qpid/framing/HeaderProperties.h44
-rw-r--r--qpid/cpp/src/qpid/framing/InitiationHandler.cpp24
-rw-r--r--qpid/cpp/src/qpid/framing/InitiationHandler.h41
-rw-r--r--qpid/cpp/src/qpid/framing/InputHandler.h41
-rw-r--r--qpid/cpp/src/qpid/framing/Invoker.h86
-rw-r--r--qpid/cpp/src/qpid/framing/IsInSequenceSet.h51
-rw-r--r--qpid/cpp/src/qpid/framing/List.cpp90
-rw-r--r--qpid/cpp/src/qpid/framing/List.h78
-rw-r--r--qpid/cpp/src/qpid/framing/MethodBodyFactory.h45
-rw-r--r--qpid/cpp/src/qpid/framing/MethodContent.h40
-rw-r--r--qpid/cpp/src/qpid/framing/ModelMethod.h49
-rw-r--r--qpid/cpp/src/qpid/framing/ProtocolInitiation.cpp83
-rw-r--r--qpid/cpp/src/qpid/framing/ProtocolInitiation.h60
-rw-r--r--qpid/cpp/src/qpid/framing/ProtocolVersion.cpp52
-rw-r--r--qpid/cpp/src/qpid/framing/ProtocolVersion.h67
-rw-r--r--qpid/cpp/src/qpid/framing/Proxy.cpp51
-rw-r--r--qpid/cpp/src/qpid/framing/Proxy.h64
-rw-r--r--qpid/cpp/src/qpid/framing/ResizableBuffer.h60
-rw-r--r--qpid/cpp/src/qpid/framing/SendContent.cpp66
-rw-r--r--qpid/cpp/src/qpid/framing/SendContent.h56
-rw-r--r--qpid/cpp/src/qpid/framing/SequenceNumber.cpp50
-rw-r--r--qpid/cpp/src/qpid/framing/SequenceNumber.h85
-rw-r--r--qpid/cpp/src/qpid/framing/SequenceNumberSet.cpp90
-rw-r--r--qpid/cpp/src/qpid/framing/SequenceNumberSet.h69
-rw-r--r--qpid/cpp/src/qpid/framing/SequenceSet.cpp127
-rw-r--r--qpid/cpp/src/qpid/framing/SequenceSet.h69
-rw-r--r--qpid/cpp/src/qpid/framing/StructHelper.h57
-rw-r--r--qpid/cpp/src/qpid/framing/TransferContent.cpp102
-rw-r--r--qpid/cpp/src/qpid/framing/TransferContent.h64
-rw-r--r--qpid/cpp/src/qpid/framing/TypeFilter.h51
-rw-r--r--qpid/cpp/src/qpid/framing/Uuid.cpp52
-rw-r--r--qpid/cpp/src/qpid/framing/Uuid.h57
-rw-r--r--qpid/cpp/src/qpid/framing/amqp_framing.h30
-rw-r--r--qpid/cpp/src/qpid/framing/amqp_types.h63
-rw-r--r--qpid/cpp/src/qpid/framing/amqp_types_full.h38
-rw-r--r--qpid/cpp/src/qpid/framing/frame_functors.h116
-rw-r--r--qpid/cpp/src/qpid/framing/variant.h91
-rw-r--r--qpid/cpp/src/qpid/ha/AlternateExchangeSetter.h74
-rw-r--r--qpid/cpp/src/qpid/ha/Backup.cpp128
-rw-r--r--qpid/cpp/src/qpid/ha/Backup.h82
-rw-r--r--qpid/cpp/src/qpid/ha/BackupConnectionExcluder.h54
-rw-r--r--qpid/cpp/src/qpid/ha/BrokerInfo.cpp120
-rw-r--r--qpid/cpp/src/qpid/ha/BrokerInfo.h89
-rw-r--r--qpid/cpp/src/qpid/ha/BrokerReplicator.cpp908
-rw-r--r--qpid/cpp/src/qpid/ha/BrokerReplicator.h177
-rw-r--r--qpid/cpp/src/qpid/ha/ConnectionObserver.cpp121
-rw-r--r--qpid/cpp/src/qpid/ha/ConnectionObserver.h83
-rw-r--r--qpid/cpp/src/qpid/ha/Event.cpp86
-rw-r--r--qpid/cpp/src/qpid/ha/Event.h193
-rw-r--r--qpid/cpp/src/qpid/ha/FailoverExchange.cpp133
-rw-r--r--qpid/cpp/src/qpid/ha/FailoverExchange.h72
-rw-r--r--qpid/cpp/src/qpid/ha/HaBroker.cpp240
-rw-r--r--qpid/cpp/src/qpid/ha/HaBroker.h137
-rw-r--r--qpid/cpp/src/qpid/ha/HaPlugin.cpp103
-rw-r--r--qpid/cpp/src/qpid/ha/IdSetter.h76
-rw-r--r--qpid/cpp/src/qpid/ha/LogPrefix.cpp35
-rw-r--r--qpid/cpp/src/qpid/ha/LogPrefix.h75
-rw-r--r--qpid/cpp/src/qpid/ha/Membership.cpp228
-rw-r--r--qpid/cpp/src/qpid/ha/Membership.h103
-rw-r--r--qpid/cpp/src/qpid/ha/Primary.cpp498
-rw-r--r--qpid/cpp/src/qpid/ha/Primary.h169
-rw-r--r--qpid/cpp/src/qpid/ha/PrimaryQueueLimits.h108
-rw-r--r--qpid/cpp/src/qpid/ha/PrimaryTxObserver.cpp307
-rw-r--r--qpid/cpp/src/qpid/ha/PrimaryTxObserver.h133
-rw-r--r--qpid/cpp/src/qpid/ha/QueueGuard.cpp123
-rw-r--r--qpid/cpp/src/qpid/ha/QueueGuard.h108
-rw-r--r--qpid/cpp/src/qpid/ha/QueueReplicator.cpp410
-rw-r--r--qpid/cpp/src/qpid/ha/QueueReplicator.h156
-rw-r--r--qpid/cpp/src/qpid/ha/QueueSnapshot.h68
-rw-r--r--qpid/cpp/src/qpid/ha/README.h149
-rw-r--r--qpid/cpp/src/qpid/ha/RemoteBackup.cpp116
-rw-r--r--qpid/cpp/src/qpid/ha/RemoteBackup.h118
-rw-r--r--qpid/cpp/src/qpid/ha/ReplicatingSubscription.cpp346
-rw-r--r--qpid/cpp/src/qpid/ha/ReplicatingSubscription.h174
-rw-r--r--qpid/cpp/src/qpid/ha/ReplicationTest.cpp75
-rw-r--r--qpid/cpp/src/qpid/ha/ReplicationTest.h76
-rw-r--r--qpid/cpp/src/qpid/ha/Role.h54
-rw-r--r--qpid/cpp/src/qpid/ha/Settings.h61
-rw-r--r--qpid/cpp/src/qpid/ha/StandAlone.h41
-rw-r--r--qpid/cpp/src/qpid/ha/StatusCheck.cpp146
-rw-r--r--qpid/cpp/src/qpid/ha/StatusCheck.h76
-rw-r--r--qpid/cpp/src/qpid/ha/TxReplicatingSubscription.cpp45
-rw-r--r--qpid/cpp/src/qpid/ha/TxReplicatingSubscription.h50
-rw-r--r--qpid/cpp/src/qpid/ha/TxReplicator.cpp273
-rw-r--r--qpid/cpp/src/qpid/ha/TxReplicator.h136
-rw-r--r--qpid/cpp/src/qpid/ha/hash.h75
-rw-r--r--qpid/cpp/src/qpid/ha/management-schema.xml63
-rw-r--r--qpid/cpp/src/qpid/ha/types.cpp139
-rw-r--r--qpid/cpp/src/qpid/ha/types.h147
-rw-r--r--qpid/cpp/src/qpid/legacystore/BindingDbt.cpp50
-rw-r--r--qpid/cpp/src/qpid/legacystore/BindingDbt.h56
-rw-r--r--qpid/cpp/src/qpid/legacystore/BufferValue.cpp56
-rw-r--r--qpid/cpp/src/qpid/legacystore/BufferValue.h46
-rw-r--r--qpid/cpp/src/qpid/legacystore/Cursor.h50
-rw-r--r--qpid/cpp/src/qpid/legacystore/DataTokenImpl.cpp28
-rw-r--r--qpid/cpp/src/qpid/legacystore/DataTokenImpl.h47
-rw-r--r--qpid/cpp/src/qpid/legacystore/IdDbt.cpp42
-rw-r--r--qpid/cpp/src/qpid/legacystore/IdDbt.h42
-rw-r--r--qpid/cpp/src/qpid/legacystore/IdSequence.cpp40
-rw-r--r--qpid/cpp/src/qpid/legacystore/IdSequence.h44
-rw-r--r--qpid/cpp/src/qpid/legacystore/JournalImpl.cpp633
-rw-r--r--qpid/cpp/src/qpid/legacystore/JournalImpl.h265
-rw-r--r--qpid/cpp/src/qpid/legacystore/MessageStoreImpl.cpp1730
-rw-r--r--qpid/cpp/src/qpid/legacystore/MessageStoreImpl.h383
-rw-r--r--qpid/cpp/src/qpid/legacystore/PreparedTransaction.cpp81
-rw-r--r--qpid/cpp/src/qpid/legacystore/PreparedTransaction.h74
-rw-r--r--qpid/cpp/src/qpid/legacystore/StoreException.h56
-rw-r--r--qpid/cpp/src/qpid/legacystore/StorePlugin.cpp81
-rw-r--r--qpid/cpp/src/qpid/legacystore/TxnCtxt.cpp184
-rw-r--r--qpid/cpp/src/qpid/legacystore/TxnCtxt.h117
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/aio.cpp41
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/aio.h153
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/aio_callback.h57
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/cvar.cpp33
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/cvar.h87
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/data_tok.cpp194
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/data_tok.h172
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/deq_hdr.h141
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/deq_rec.cpp461
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/deq_rec.h103
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/enq_hdr.h165
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/enq_map.cpp183
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/enq_map.h127
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/enq_rec.cpp640
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/enq_rec.h116
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/enums.h108
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/fcntl.cpp375
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/fcntl.h156
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/file_hdr.h211
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/jcfg.h91
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/jcntl.cpp984
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/jcntl.h722
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/jdir.cpp463
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/jdir.h379
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/jerrno.cpp253
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/jerrno.h173
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/jexception.cpp183
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/jexception.h142
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/jinf.cpp540
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/jinf.h133
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/jrec.cpp119
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/jrec.h183
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/lp_map.cpp82
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/lp_map.h83
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/lpmgr.cpp226
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/lpmgr.h303
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/pmgr.cpp215
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/pmgr.h142
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/rcvdat.h181
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/rec_hdr.h143
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/rec_tail.h98
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/rfc.cpp82
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/rfc.h193
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/rmgr.cpp698
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/rmgr.h114
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/rrfc.cpp125
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/rrfc.h179
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/slock.cpp33
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/slock.h85
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/smutex.cpp33
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/smutex.h64
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/time_ns.cpp55
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/time_ns.h105
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/txn_hdr.h125
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/txn_map.cpp256
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/txn_map.h159
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/txn_rec.cpp448
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/txn_rec.h101
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/wmgr.cpp1051
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/wmgr.h147
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/wrfc.cpp164
-rw-r--r--qpid/cpp/src/qpid/legacystore/jrnl/wrfc.h154
-rw-r--r--qpid/cpp/src/qpid/legacystore/management-schema.xml99
-rw-r--r--qpid/cpp/src/qpid/linearstore/BindingDbt.cpp50
-rw-r--r--qpid/cpp/src/qpid/linearstore/BindingDbt.h56
-rw-r--r--qpid/cpp/src/qpid/linearstore/BufferValue.cpp56
-rw-r--r--qpid/cpp/src/qpid/linearstore/BufferValue.h46
-rw-r--r--qpid/cpp/src/qpid/linearstore/Cursor.h50
-rw-r--r--qpid/cpp/src/qpid/linearstore/DataTokenImpl.cpp28
-rw-r--r--qpid/cpp/src/qpid/linearstore/DataTokenImpl.h47
-rw-r--r--qpid/cpp/src/qpid/linearstore/ISSUES224
-rw-r--r--qpid/cpp/src/qpid/linearstore/IdDbt.cpp42
-rw-r--r--qpid/cpp/src/qpid/linearstore/IdDbt.h42
-rw-r--r--qpid/cpp/src/qpid/linearstore/IdSequence.cpp40
-rw-r--r--qpid/cpp/src/qpid/linearstore/IdSequence.h43
-rw-r--r--qpid/cpp/src/qpid/linearstore/JournalImpl.cpp516
-rw-r--r--qpid/cpp/src/qpid/linearstore/JournalImpl.h252
-rw-r--r--qpid/cpp/src/qpid/linearstore/JournalLogImpl.cpp61
-rw-r--r--qpid/cpp/src/qpid/linearstore/JournalLogImpl.h47
-rw-r--r--qpid/cpp/src/qpid/linearstore/MessageStoreImpl.cpp1559
-rw-r--r--qpid/cpp/src/qpid/linearstore/MessageStoreImpl.h351
-rw-r--r--qpid/cpp/src/qpid/linearstore/PreparedTransaction.cpp81
-rw-r--r--qpid/cpp/src/qpid/linearstore/PreparedTransaction.h73
-rw-r--r--qpid/cpp/src/qpid/linearstore/StoreException.h56
-rw-r--r--qpid/cpp/src/qpid/linearstore/StorePlugin.cpp97
-rw-r--r--qpid/cpp/src/qpid/linearstore/TxnCtxt.cpp185
-rw-r--r--qpid/cpp/src/qpid/linearstore/TxnCtxt.h115
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/AtomicCounter.h133
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/Checksum.cpp45
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/Checksum.h54
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/EmptyFilePool.cpp477
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/EmptyFilePool.h118
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/EmptyFilePoolManager.cpp211
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/EmptyFilePoolManager.h83
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/EmptyFilePoolPartition.cpp199
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/EmptyFilePoolPartition.h82
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/EmptyFilePoolTypes.h57
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/JournalFile.cpp349
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/JournalFile.h132
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/JournalLog.cpp63
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/JournalLog.h60
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/LinearFileController.cpp243
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/LinearFileController.h119
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/RecoveryManager.cpp949
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/RecoveryManager.h157
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/aio.h201
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/aio_callback.h44
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/data_tok.cpp136
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/data_tok.h133
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/deq_rec.cpp313
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/deq_rec.h70
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/enq_map.cpp181
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/enq_map.h101
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/enq_rec.cpp397
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/enq_rec.h74
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/enums.h58
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/jcfg.h72
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/jcntl.cpp440
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/jcntl.h570
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/jdir.cpp457
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/jdir.h362
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/jerrno.cpp236
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/jerrno.h157
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/jexception.cpp168
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/jexception.h125
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/jrec.h122
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/pmgr.cpp192
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/pmgr.h119
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/slock.h71
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/smutex.h51
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/time_ns.cpp41
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/time_ns.h92
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/txn_map.cpp263
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/txn_map.h150
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/txn_rec.cpp305
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/txn_rec.h68
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/utils/deq_hdr.c46
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/utils/deq_hdr.h83
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/utils/enq_hdr.c63
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/utils/enq_hdr.h83
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/utils/file_hdr.c115
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/utils/file_hdr.h111
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/utils/rec_hdr.c51
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/utils/rec_hdr.h72
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/utils/rec_tail.c46
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/utils/rec_tail.h82
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/utils/txn_hdr.c33
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/utils/txn_hdr.h72
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/wmgr.cpp1086
-rw-r--r--qpid/cpp/src/qpid/linearstore/journal/wmgr.h156
-rw-r--r--qpid/cpp/src/qpid/linearstore/management-schema.xml54
-rw-r--r--qpid/cpp/src/qpid/log/Helpers.h79
-rw-r--r--qpid/cpp/src/qpid/log/Logger.cpp200
-rw-r--r--qpid/cpp/src/qpid/log/Logger.h122
-rw-r--r--qpid/cpp/src/qpid/log/Options.cpp148
-rw-r--r--qpid/cpp/src/qpid/log/Options.h57
-rw-r--r--qpid/cpp/src/qpid/log/OstreamOutput.cpp41
-rw-r--r--qpid/cpp/src/qpid/log/OstreamOutput.h41
-rw-r--r--qpid/cpp/src/qpid/log/Selector.cpp237
-rw-r--r--qpid/cpp/src/qpid/log/Selector.h99
-rw-r--r--qpid/cpp/src/qpid/log/SinkOptions.h64
-rw-r--r--qpid/cpp/src/qpid/log/Statement.cpp218
-rw-r--r--qpid/cpp/src/qpid/log/Statement.h244
-rw-r--r--qpid/cpp/src/qpid/log/posix/SinkOptions.cpp222
-rw-r--r--qpid/cpp/src/qpid/log/posix/SinkOptions.h64
-rw-r--r--qpid/cpp/src/qpid/log/windows/SinkOptions.cpp148
-rw-r--r--qpid/cpp/src/qpid/log/windows/SinkOptions.h54
-rw-r--r--qpid/cpp/src/qpid/management/Args.h44
-rw-r--r--qpid/cpp/src/qpid/management/Buffer.cpp105
-rw-r--r--qpid/cpp/src/qpid/management/Buffer.h105
-rw-r--r--qpid/cpp/src/qpid/management/ConnectionSettings.cpp40
-rw-r--r--qpid/cpp/src/qpid/management/ConnectionSettings.h118
-rw-r--r--qpid/cpp/src/qpid/management/Manageable.cpp53
-rw-r--r--qpid/cpp/src/qpid/management/Manageable.h81
-rw-r--r--qpid/cpp/src/qpid/management/ManagementAgent.cpp2832
-rw-r--r--qpid/cpp/src/qpid/management/ManagementAgent.h388
-rw-r--r--qpid/cpp/src/qpid/management/ManagementDirectExchange.cpp65
-rw-r--r--qpid/cpp/src/qpid/management/ManagementDirectExchange.h57
-rw-r--r--qpid/cpp/src/qpid/management/ManagementEvent.h53
-rw-r--r--qpid/cpp/src/qpid/management/ManagementObject.cpp385
-rw-r--r--qpid/cpp/src/qpid/management/ManagementObject.h246
-rw-r--r--qpid/cpp/src/qpid/management/ManagementTopicExchange.cpp73
-rw-r--r--qpid/cpp/src/qpid/management/ManagementTopicExchange.h61
-rw-r--r--qpid/cpp/src/qpid/management/Mutex.cpp29
-rw-r--r--qpid/cpp/src/qpid/management/Mutex.h67
-rw-r--r--qpid/cpp/src/qpid/memory.h32
-rw-r--r--qpid/cpp/src/qpid/messaging/Address.cpp111
-rw-r--r--qpid/cpp/src/qpid/messaging/AddressImpl.h45
-rw-r--r--qpid/cpp/src/qpid/messaging/AddressParser.cpp271
-rw-r--r--qpid/cpp/src/qpid/messaging/AddressParser.h67
-rw-r--r--qpid/cpp/src/qpid/messaging/Connection.cpp111
-rw-r--r--qpid/cpp/src/qpid/messaging/ConnectionImpl.h60
-rw-r--r--qpid/cpp/src/qpid/messaging/ConnectionOptions.cpp131
-rw-r--r--qpid/cpp/src/qpid/messaging/ConnectionOptions.h57
-rw-r--r--qpid/cpp/src/qpid/messaging/Duration.cpp55
-rw-r--r--qpid/cpp/src/qpid/messaging/FailoverUpdates.cpp85
-rw-r--r--qpid/cpp/src/qpid/messaging/Logger.cpp200
-rw-r--r--qpid/cpp/src/qpid/messaging/Message.cpp165
-rw-r--r--qpid/cpp/src/qpid/messaging/MessageImpl.cpp253
-rw-r--r--qpid/cpp/src/qpid/messaging/MessageImpl.h122
-rw-r--r--qpid/cpp/src/qpid/messaging/Message_io.cpp45
-rw-r--r--qpid/cpp/src/qpid/messaging/PrivateImplRef.h94
-rw-r--r--qpid/cpp/src/qpid/messaging/ProtocolRegistry.cpp200
-rw-r--r--qpid/cpp/src/qpid/messaging/ProtocolRegistry.h47
-rw-r--r--qpid/cpp/src/qpid/messaging/Receiver.cpp62
-rw-r--r--qpid/cpp/src/qpid/messaging/ReceiverImpl.h56
-rw-r--r--qpid/cpp/src/qpid/messaging/Sender.cpp49
-rw-r--r--qpid/cpp/src/qpid/messaging/SenderImpl.h50
-rw-r--r--qpid/cpp/src/qpid/messaging/Session.cpp113
-rw-r--r--qpid/cpp/src/qpid/messaging/SessionImpl.h63
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/AddressHelper.cpp781
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/AddressHelper.h95
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/ConnectionContext.cpp1317
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/ConnectionContext.h233
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/ConnectionHandle.cpp103
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/ConnectionHandle.h61
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/DriverImpl.cpp76
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/DriverImpl.h64
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/EncodedMessage.cpp366
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/EncodedMessage.h190
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/PnData.cpp246
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/PnData.h61
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/ReceiverContext.cpp140
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/ReceiverContext.h77
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/ReceiverHandle.cpp111
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/ReceiverHandle.h64
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/Sasl.cpp186
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/Sasl.h77
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/SenderContext.cpp643
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/SenderContext.h119
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/SenderHandle.cpp81
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/SenderHandle.h59
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/SessionContext.cpp258
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/SessionContext.h95
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/SessionHandle.cpp147
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/SessionHandle.h64
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/SslTransport.cpp186
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/SslTransport.h78
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/TcpTransport.cpp185
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/TcpTransport.h78
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/Transaction.cpp155
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/Transaction.h95
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/Transport.cpp50
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/Transport.h52
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/TransportContext.h50
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/util.cpp43
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/util.h36
-rw-r--r--qpid/cpp/src/qpid/messaging/amqp/windows/SslTransport.cpp136
-rw-r--r--qpid/cpp/src/qpid/messaging/exceptions.cpp65
-rw-r--r--qpid/cpp/src/qpid/pointer_to_other.h62
-rw-r--r--qpid/cpp/src/qpid/ptr_map.h57
-rw-r--r--qpid/cpp/src/qpid/store/CMakeLists.txt120
-rw-r--r--qpid/cpp/src/qpid/store/MessageStorePlugin.cpp463
-rw-r--r--qpid/cpp/src/qpid/store/MessageStorePlugin.h280
-rw-r--r--qpid/cpp/src/qpid/store/StorageProvider.h329
-rw-r--r--qpid/cpp/src/qpid/store/StoreException.h49
-rw-r--r--qpid/cpp/src/qpid/store/ms-clfs/Log.cpp182
-rw-r--r--qpid/cpp/src/qpid/store/ms-clfs/Log.h78
-rw-r--r--qpid/cpp/src/qpid/store/ms-clfs/Lsn.h36
-rw-r--r--qpid/cpp/src/qpid/store/ms-clfs/MSSqlClfsProvider.cpp1102
-rw-r--r--qpid/cpp/src/qpid/store/ms-clfs/MessageLog.cpp406
-rw-r--r--qpid/cpp/src/qpid/store/ms-clfs/MessageLog.h107
-rw-r--r--qpid/cpp/src/qpid/store/ms-clfs/Messages.cpp472
-rw-r--r--qpid/cpp/src/qpid/store/ms-clfs/Messages.h144
-rw-r--r--qpid/cpp/src/qpid/store/ms-clfs/Transaction.cpp83
-rw-r--r--qpid/cpp/src/qpid/store/ms-clfs/Transaction.h147
-rw-r--r--qpid/cpp/src/qpid/store/ms-clfs/TransactionLog.cpp428
-rw-r--r--qpid/cpp/src/qpid/store/ms-clfs/TransactionLog.h104
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/AmqpTransaction.cpp67
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/AmqpTransaction.h85
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/BindingRecordset.cpp165
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/BindingRecordset.h88
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/BlobAdapter.cpp64
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/BlobAdapter.h62
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/BlobEncoder.cpp133
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/BlobEncoder.h61
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/BlobRecordset.cpp86
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/BlobRecordset.h54
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/DatabaseConnection.cpp91
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/DatabaseConnection.h64
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/Exception.h66
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/MSSqlProvider.cpp1286
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/MessageMapRecordset.cpp267
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/MessageMapRecordset.h100
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/MessageRecordset.cpp184
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/MessageRecordset.h85
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/Recordset.cpp92
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/Recordset.h75
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/SqlTransaction.cpp71
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/SqlTransaction.h67
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/State.cpp45
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/State.h52
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/TplRecordset.cpp128
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/TplRecordset.h58
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/VariantHelper.cpp71
-rw-r--r--qpid/cpp/src/qpid/store/ms-sql/VariantHelper.h61
-rw-r--r--qpid/cpp/src/qpid/sys/AggregateOutput.cpp89
-rw-r--r--qpid/cpp/src/qpid/sys/AggregateOutput.h76
-rw-r--r--qpid/cpp/src/qpid/sys/AsynchIO.h175
-rw-r--r--qpid/cpp/src/qpid/sys/AsynchIOHandler.cpp235
-rw-r--r--qpid/cpp/src/qpid/sys/AsynchIOHandler.h82
-rw-r--r--qpid/cpp/src/qpid/sys/AtomicCount.h52
-rw-r--r--qpid/cpp/src/qpid/sys/AtomicValue.h39
-rw-r--r--qpid/cpp/src/qpid/sys/AtomicValue_gcc.h71
-rw-r--r--qpid/cpp/src/qpid/sys/AtomicValue_mutex.h83
-rw-r--r--qpid/cpp/src/qpid/sys/BlockingQueue.h129
-rw-r--r--qpid/cpp/src/qpid/sys/Codec.h52
-rw-r--r--qpid/cpp/src/qpid/sys/Condition.h33
-rw-r--r--qpid/cpp/src/qpid/sys/ConnectionCodec.h70
-rw-r--r--qpid/cpp/src/qpid/sys/ConnectionInputHandler.h51
-rw-r--r--qpid/cpp/src/qpid/sys/ConnectionInputHandlerFactory.h54
-rw-r--r--qpid/cpp/src/qpid/sys/ConnectionOutputHandler.h42
-rw-r--r--qpid/cpp/src/qpid/sys/CopyOnWriteArray.h162
-rw-r--r--qpid/cpp/src/qpid/sys/DeletionManager.h162
-rw-r--r--qpid/cpp/src/qpid/sys/DispatchHandle.cpp352
-rw-r--r--qpid/cpp/src/qpid/sys/DispatchHandle.h150
-rw-r--r--qpid/cpp/src/qpid/sys/Dispatcher.cpp40
-rw-r--r--qpid/cpp/src/qpid/sys/Dispatcher.h44
-rw-r--r--qpid/cpp/src/qpid/sys/ExceptionHolder.h71
-rwxr-xr-xqpid/cpp/src/qpid/sys/FileSysDir.h71
-rw-r--r--qpid/cpp/src/qpid/sys/Fork.h24
-rw-r--r--qpid/cpp/src/qpid/sys/FreeBSD/uuid.cpp44
-rw-r--r--qpid/cpp/src/qpid/sys/IOHandle.h36
-rw-r--r--qpid/cpp/src/qpid/sys/LockFile.h64
-rw-r--r--qpid/cpp/src/qpid/sys/LockPtr.h89
-rw-r--r--qpid/cpp/src/qpid/sys/MemStat.cpp31
-rw-r--r--qpid/cpp/src/qpid/sys/MemStat.h38
-rw-r--r--qpid/cpp/src/qpid/sys/MemoryMappedFile.h79
-rw-r--r--qpid/cpp/src/qpid/sys/Monitor.h49
-rw-r--r--qpid/cpp/src/qpid/sys/Mutex.h91
-rw-r--r--qpid/cpp/src/qpid/sys/OutputControl.h43
-rw-r--r--qpid/cpp/src/qpid/sys/OutputTask.h41
-rw-r--r--qpid/cpp/src/qpid/sys/Path.h61
-rwxr-xr-xqpid/cpp/src/qpid/sys/PipeHandle.h51
-rw-r--r--qpid/cpp/src/qpid/sys/PollableCondition.h64
-rw-r--r--qpid/cpp/src/qpid/sys/PollableQueue.h177
-rw-r--r--qpid/cpp/src/qpid/sys/Poller.h135
-rw-r--r--qpid/cpp/src/qpid/sys/Probes.h65
-rw-r--r--qpid/cpp/src/qpid/sys/RdmaIOPlugin.cpp393
-rw-r--r--qpid/cpp/src/qpid/sys/Runnable.cpp32
-rw-r--r--qpid/cpp/src/qpid/sys/Runnable.h51
-rw-r--r--qpid/cpp/src/qpid/sys/ScopedIncrement.h67
-rw-r--r--qpid/cpp/src/qpid/sys/SecurityLayer.h46
-rw-r--r--qpid/cpp/src/qpid/sys/SecuritySettings.h60
-rw-r--r--qpid/cpp/src/qpid/sys/Semaphore.h79
-rw-r--r--qpid/cpp/src/qpid/sys/Shlib.cpp38
-rw-r--r--qpid/cpp/src/qpid/sys/Shlib.h77
-rw-r--r--qpid/cpp/src/qpid/sys/ShutdownHandler.h37
-rw-r--r--qpid/cpp/src/qpid/sys/Socket.h100
-rw-r--r--qpid/cpp/src/qpid/sys/SocketAddress.h82
-rw-r--r--qpid/cpp/src/qpid/sys/SocketTransport.cpp221
-rw-r--r--qpid/cpp/src/qpid/sys/SocketTransport.h91
-rw-r--r--qpid/cpp/src/qpid/sys/SslPlugin.cpp143
-rw-r--r--qpid/cpp/src/qpid/sys/StateMonitor.h78
-rw-r--r--qpid/cpp/src/qpid/sys/StrError.h36
-rw-r--r--qpid/cpp/src/qpid/sys/SystemInfo.h109
-rw-r--r--qpid/cpp/src/qpid/sys/TCPIOPlugin.cpp61
-rw-r--r--qpid/cpp/src/qpid/sys/Thread.h73
-rw-r--r--qpid/cpp/src/qpid/sys/Time.h181
-rw-r--r--qpid/cpp/src/qpid/sys/Timer.cpp235
-rw-r--r--qpid/cpp/src/qpid/sys/Timer.h166
-rw-r--r--qpid/cpp/src/qpid/sys/TimerWarnings.cpp82
-rw-r--r--qpid/cpp/src/qpid/sys/TimerWarnings.h81
-rw-r--r--qpid/cpp/src/qpid/sys/TransportFactory.h65
-rw-r--r--qpid/cpp/src/qpid/sys/Waitable.h114
-rw-r--r--qpid/cpp/src/qpid/sys/aix/SystemInfo.cpp201
-rw-r--r--qpid/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.cpp129
-rw-r--r--qpid/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.h68
-rw-r--r--qpid/cpp/src/qpid/sys/epoll/EpollPoller.cpp677
-rw-r--r--qpid/cpp/src/qpid/sys/posix/AsynchIO.cpp655
-rw-r--r--qpid/cpp/src/qpid/sys/posix/BSDSocket.cpp264
-rw-r--r--qpid/cpp/src/qpid/sys/posix/BSDSocket.h113
-rw-r--r--qpid/cpp/src/qpid/sys/posix/Condition.cpp45
-rw-r--r--qpid/cpp/src/qpid/sys/posix/Condition.h82
-rwxr-xr-xqpid/cpp/src/qpid/sys/posix/FileSysDir.cpp80
-rw-r--r--qpid/cpp/src/qpid/sys/posix/Fork.cpp129
-rw-r--r--qpid/cpp/src/qpid/sys/posix/Fork.h82
-rw-r--r--qpid/cpp/src/qpid/sys/posix/IOHandle.cpp29
-rwxr-xr-xqpid/cpp/src/qpid/sys/posix/LockFile.cpp107
-rw-r--r--qpid/cpp/src/qpid/sys/posix/MemStat.cpp38
-rw-r--r--qpid/cpp/src/qpid/sys/posix/MemoryMappedFile.cpp125
-rw-r--r--qpid/cpp/src/qpid/sys/posix/Mutex.cpp46
-rw-r--r--qpid/cpp/src/qpid/sys/posix/Mutex.h158
-rw-r--r--qpid/cpp/src/qpid/sys/posix/Path.cpp60
-rw-r--r--qpid/cpp/src/qpid/sys/posix/PidFile.h62
-rwxr-xr-xqpid/cpp/src/qpid/sys/posix/PipeHandle.cpp64
-rw-r--r--qpid/cpp/src/qpid/sys/posix/PollableCondition.cpp118
-rw-r--r--qpid/cpp/src/qpid/sys/posix/PosixPoller.cpp793
-rw-r--r--qpid/cpp/src/qpid/sys/posix/PrivatePosix.h65
-rw-r--r--qpid/cpp/src/qpid/sys/posix/Shlib.cpp60
-rw-r--r--qpid/cpp/src/qpid/sys/posix/SocketAddress.cpp353
-rw-r--r--qpid/cpp/src/qpid/sys/posix/StrError.cpp41
-rwxr-xr-xqpid/cpp/src/qpid/sys/posix/SystemInfo.cpp201
-rw-r--r--qpid/cpp/src/qpid/sys/posix/Thread.cpp88
-rw-r--r--qpid/cpp/src/qpid/sys/posix/Time.cpp162
-rwxr-xr-xqpid/cpp/src/qpid/sys/posix/Time.h34
-rw-r--r--qpid/cpp/src/qpid/sys/posix/check.h53
-rw-r--r--qpid/cpp/src/qpid/sys/rdma/RdmaClient.cpp247
-rw-r--r--qpid/cpp/src/qpid/sys/rdma/RdmaIO.cpp724
-rw-r--r--qpid/cpp/src/qpid/sys/rdma/RdmaIO.h250
-rw-r--r--qpid/cpp/src/qpid/sys/rdma/RdmaServer.cpp210
-rw-r--r--qpid/cpp/src/qpid/sys/rdma/rdma_exception.h69
-rw-r--r--qpid/cpp/src/qpid/sys/rdma/rdma_factories.cpp105
-rw-r--r--qpid/cpp/src/qpid/sys/rdma/rdma_factories.h40
-rw-r--r--qpid/cpp/src/qpid/sys/rdma/rdma_wrap.cpp576
-rw-r--r--qpid/cpp/src/qpid/sys/rdma/rdma_wrap.h305
-rw-r--r--qpid/cpp/src/qpid/sys/regex.h81
-rw-r--r--qpid/cpp/src/qpid/sys/solaris/ECFPoller.cpp444
-rwxr-xr-xqpid/cpp/src/qpid/sys/solaris/SystemInfo.cpp110
-rw-r--r--qpid/cpp/src/qpid/sys/ssl/SslSocket.cpp379
-rw-r--r--qpid/cpp/src/qpid/sys/ssl/SslSocket.h112
-rw-r--r--qpid/cpp/src/qpid/sys/ssl/check.cpp85
-rw-r--r--qpid/cpp/src/qpid/sys/ssl/check.h57
-rw-r--r--qpid/cpp/src/qpid/sys/ssl/util.cpp127
-rw-r--r--qpid/cpp/src/qpid/sys/ssl/util.h50
-rw-r--r--qpid/cpp/src/qpid/sys/unordered_map.h41
-rw-r--r--qpid/cpp/src/qpid/sys/urlAdd.h62
-rw-r--r--qpid/cpp/src/qpid/sys/uuid.h37
-rw-r--r--qpid/cpp/src/qpid/sys/windows/AsynchIO.cpp713
-rw-r--r--qpid/cpp/src/qpid/sys/windows/AsynchIO.h235
-rwxr-xr-xqpid/cpp/src/qpid/sys/windows/AsynchIoResult.h204
-rwxr-xr-xqpid/cpp/src/qpid/sys/windows/Condition.h77
-rw-r--r--qpid/cpp/src/qpid/sys/windows/FileSysDir.cpp90
-rwxr-xr-xqpid/cpp/src/qpid/sys/windows/IOHandle.cpp29
-rwxr-xr-xqpid/cpp/src/qpid/sys/windows/IoHandlePrivate.h58
-rwxr-xr-xqpid/cpp/src/qpid/sys/windows/IocpPoller.cpp220
-rwxr-xr-xqpid/cpp/src/qpid/sys/windows/LockFile.cpp64
-rw-r--r--qpid/cpp/src/qpid/sys/windows/MemoryMappedFile.cpp58
-rwxr-xr-xqpid/cpp/src/qpid/sys/windows/Mutex.h188
-rw-r--r--qpid/cpp/src/qpid/sys/windows/Path.cpp65
-rwxr-xr-xqpid/cpp/src/qpid/sys/windows/PipeHandle.cpp101
-rw-r--r--qpid/cpp/src/qpid/sys/windows/PollableCondition.cpp114
-rw-r--r--qpid/cpp/src/qpid/sys/windows/QpidDllMain.h72
-rw-r--r--qpid/cpp/src/qpid/sys/windows/Shlib.cpp54
-rw-r--r--qpid/cpp/src/qpid/sys/windows/SocketAddress.cpp346
-rw-r--r--qpid/cpp/src/qpid/sys/windows/SslAsynchIO.cpp735
-rw-r--r--qpid/cpp/src/qpid/sys/windows/SslAsynchIO.h192
-rw-r--r--qpid/cpp/src/qpid/sys/windows/SslCredential.cpp279
-rw-r--r--qpid/cpp/src/qpid/sys/windows/SslCredential.h84
-rwxr-xr-xqpid/cpp/src/qpid/sys/windows/StrError.cpp52
-rwxr-xr-xqpid/cpp/src/qpid/sys/windows/SystemInfo.cpp208
-rwxr-xr-xqpid/cpp/src/qpid/sys/windows/Thread.cpp340
-rw-r--r--qpid/cpp/src/qpid/sys/windows/Time.cpp217
-rw-r--r--qpid/cpp/src/qpid/sys/windows/Time.h36
-rw-r--r--qpid/cpp/src/qpid/sys/windows/WinSocket.cpp276
-rw-r--r--qpid/cpp/src/qpid/sys/windows/WinSocket.h118
-rwxr-xr-xqpid/cpp/src/qpid/sys/windows/check.h49
-rw-r--r--qpid/cpp/src/qpid/sys/windows/mingw32_compat.h39
-rw-r--r--qpid/cpp/src/qpid/sys/windows/util.cpp70
-rw-r--r--qpid/cpp/src/qpid/sys/windows/util.h50
-rw-r--r--qpid/cpp/src/qpid/sys/windows/uuid.cpp68
-rw-r--r--qpid/cpp/src/qpid/types/Exception.cpp30
-rw-r--r--qpid/cpp/src/qpid/types/Uuid.cpp208
-rw-r--r--qpid/cpp/src/qpid/types/Variant.cpp962
-rw-r--r--qpid/cpp/src/qpid/types/encodings.h35
-rw-r--r--qpid/cpp/src/qpid/xml/XmlExchange.cpp474
-rw-r--r--qpid/cpp/src/qpid/xml/XmlExchange.h124
-rw-r--r--qpid/cpp/src/qpid/xml/XmlExchangePlugin.cpp70
-rw-r--r--qpid/cpp/src/qpidd.cpp115
-rw-r--r--qpid/cpp/src/qpidd.h78
-rw-r--r--qpid/cpp/src/rdma.cmake118
-rw-r--r--qpid/cpp/src/tests/.valgrind.supp179
-rw-r--r--qpid/cpp/src/tests/AccumulatedAckTest.cpp237
-rw-r--r--qpid/cpp/src/tests/Acl.cpp166
-rw-r--r--qpid/cpp/src/tests/AclHost.cpp166
-rw-r--r--qpid/cpp/src/tests/Address.cpp135
-rw-r--r--qpid/cpp/src/tests/Array.cpp84
-rw-r--r--qpid/cpp/src/tests/AsyncCompletion.cpp153
-rw-r--r--qpid/cpp/src/tests/AtomicValue.cpp54
-rw-r--r--qpid/cpp/src/tests/Blob.cpp21
-rw-r--r--qpid/cpp/src/tests/BrokerFixture.h168
-rw-r--r--qpid/cpp/src/tests/BrokerMgmtAgent.cpp387
-rw-r--r--qpid/cpp/src/tests/BrokerMgmtAgent.xml38
-rw-r--r--qpid/cpp/src/tests/BrokerOptions.cpp79
-rw-r--r--qpid/cpp/src/tests/CMakeLists.txt402
-rw-r--r--qpid/cpp/src/tests/ClientMessage.cpp46
-rw-r--r--qpid/cpp/src/tests/ClientMessageTest.cpp51
-rw-r--r--qpid/cpp/src/tests/ClientSessionTest.cpp663
-rw-r--r--qpid/cpp/src/tests/ConnectionOptions.h62
-rw-r--r--qpid/cpp/src/tests/DeliveryRecordTest.cpp67
-rw-r--r--qpid/cpp/src/tests/DispatcherTest.cpp240
-rw-r--r--qpid/cpp/src/tests/DtxWorkRecordTest.cpp193
-rw-r--r--qpid/cpp/src/tests/ExchangeTest.cpp267
-rw-r--r--qpid/cpp/src/tests/FieldTable.cpp215
-rw-r--r--qpid/cpp/src/tests/FieldValue.cpp98
-rw-r--r--qpid/cpp/src/tests/Frame.cpp84
-rw-r--r--qpid/cpp/src/tests/FrameDecoder.cpp78
-rw-r--r--qpid/cpp/src/tests/FramingTest.cpp168
-rw-r--r--qpid/cpp/src/tests/HeaderTest.cpp114
-rw-r--r--qpid/cpp/src/tests/HeadersExchangeTest.cpp194
-rw-r--r--qpid/cpp/src/tests/InlineAllocator.cpp68
-rw-r--r--qpid/cpp/src/tests/InlineVector.cpp128
-rw-r--r--qpid/cpp/src/tests/ManagementTest.cpp97
-rw-r--r--qpid/cpp/src/tests/MessageReplayTracker.cpp104
-rw-r--r--qpid/cpp/src/tests/MessageTest.cpp89
-rw-r--r--qpid/cpp/src/tests/MessageUtils.h117
-rw-r--r--qpid/cpp/src/tests/MessagingFixture.h352
-rw-r--r--qpid/cpp/src/tests/MessagingLogger.cpp149
-rw-r--r--qpid/cpp/src/tests/MessagingSessionTests.cpp1495
-rw-r--r--qpid/cpp/src/tests/MessagingThreadTests.cpp144
-rw-r--r--qpid/cpp/src/tests/PollableCondition.cpp109
-rw-r--r--qpid/cpp/src/tests/PollerTest.cpp262
-rw-r--r--qpid/cpp/src/tests/ProxyTest.cpp56
-rw-r--r--qpid/cpp/src/tests/Qmf2.cpp422
-rw-r--r--qpid/cpp/src/tests/QueueDepth.cpp105
-rw-r--r--qpid/cpp/src/tests/QueueFlowLimitTest.cpp457
-rw-r--r--qpid/cpp/src/tests/QueueOptionsTest.cpp85
-rw-r--r--qpid/cpp/src/tests/QueuePolicyTest.cpp300
-rw-r--r--qpid/cpp/src/tests/QueueRegistryTest.cpp91
-rw-r--r--qpid/cpp/src/tests/QueueTest.cpp629
-rw-r--r--qpid/cpp/src/tests/README.txt37
-rw-r--r--qpid/cpp/src/tests/RangeSet.cpp154
-rw-r--r--qpid/cpp/src/tests/RefCounted.cpp55
-rw-r--r--qpid/cpp/src/tests/RetryList.cpp111
-rw-r--r--qpid/cpp/src/tests/Selector.cpp442
-rw-r--r--qpid/cpp/src/tests/SequenceNumberTest.cpp209
-rw-r--r--qpid/cpp/src/tests/SequenceSet.cpp187
-rw-r--r--qpid/cpp/src/tests/SessionState.cpp303
-rw-r--r--qpid/cpp/src/tests/Shlib.cpp67
-rw-r--r--qpid/cpp/src/tests/Statistics.cpp131
-rw-r--r--qpid/cpp/src/tests/Statistics.h111
-rw-r--r--qpid/cpp/src/tests/StringUtils.cpp81
-rw-r--r--qpid/cpp/src/tests/SystemInfo.cpp36
-rw-r--r--qpid/cpp/src/tests/TestMessageStore.h63
-rw-r--r--qpid/cpp/src/tests/TestOptions.h79
-rw-r--r--qpid/cpp/src/tests/TimerTest.cpp176
-rw-r--r--qpid/cpp/src/tests/TopicExchangeTest.cpp408
-rw-r--r--qpid/cpp/src/tests/TransactionObserverTest.cpp147
-rw-r--r--qpid/cpp/src/tests/TxBufferTest.cpp188
-rw-r--r--qpid/cpp/src/tests/TxMocks.h236
-rw-r--r--qpid/cpp/src/tests/Url.cpp116
-rw-r--r--qpid/cpp/src/tests/Uuid.cpp150
-rw-r--r--qpid/cpp/src/tests/Variant.cpp834
-rw-r--r--qpid/cpp/src/tests/XmlClientSessionTest.cpp301
-rwxr-xr-xqpid/cpp/src/tests/acl.py3959
-rw-r--r--qpid/cpp/src/tests/ais_test.cpp23
-rwxr-xr-xqpid/cpp/src/tests/allhosts79
-rw-r--r--qpid/cpp/src/tests/assertions.py194
-rw-r--r--qpid/cpp/src/tests/background.ps155
-rw-r--r--qpid/cpp/src/tests/brokertest.py752
-rwxr-xr-xqpid/cpp/src/tests/cli_tests.py477
-rw-r--r--qpid/cpp/src/tests/config.null21
-rw-r--r--qpid/cpp/src/tests/consume.cpp131
-rw-r--r--qpid/cpp/src/tests/datagen.cpp103
-rw-r--r--qpid/cpp/src/tests/declare_queues.cpp101
-rw-r--r--qpid/cpp/src/tests/dlclose_noop.c30
-rwxr-xr-xqpid/cpp/src/tests/dynamic_log_hires_timestamp75
-rwxr-xr-xqpid/cpp/src/tests/dynamic_log_level_test90
-rw-r--r--qpid/cpp/src/tests/echotest.cpp157
-rw-r--r--qpid/cpp/src/tests/exception_test.cpp125
-rw-r--r--qpid/cpp/src/tests/failing-amqp0-10-python-tests32
-rw-r--r--qpid/cpp/src/tests/failing-amqp1.0-python-tests25
-rwxr-xr-xqpid/cpp/src/tests/fanout_perftest22
-rwxr-xr-xqpid/cpp/src/tests/federated_topic_test128
-rwxr-xr-xqpid/cpp/src/tests/federation.py2793
-rwxr-xr-xqpid/cpp/src/tests/federation_sys.py977
-rw-r--r--qpid/cpp/src/tests/find_prog.ps136
-rwxr-xr-xqpid/cpp/src/tests/ha_test.py403
-rw-r--r--qpid/cpp/src/tests/ha_test_max_queues.cpp67
-rwxr-xr-xqpid/cpp/src/tests/ha_tests.py1634
-rw-r--r--qpid/cpp/src/tests/header_test.cpp59
-rwxr-xr-xqpid/cpp/src/tests/header_test.py86
-rw-r--r--qpid/cpp/src/tests/headers_federation.py99
-rwxr-xr-xqpid/cpp/src/tests/idle_timeout_tests.py95
-rw-r--r--qpid/cpp/src/tests/install_env.sh.in26
-rwxr-xr-xqpid/cpp/src/tests/interlink_tests.py336
-rwxr-xr-xqpid/cpp/src/tests/interop_tests.py220
-rwxr-xr-xqpid/cpp/src/tests/ipv6_test120
-rw-r--r--qpid/cpp/src/tests/legacystore/.valgrind.supp35
-rw-r--r--qpid/cpp/src/tests/legacystore/.valgrindrc7
-rw-r--r--qpid/cpp/src/tests/legacystore/CMakeLists.txt133
-rw-r--r--qpid/cpp/src/tests/legacystore/MessageUtils.h105
-rw-r--r--qpid/cpp/src/tests/legacystore/TestFramework.cpp30
-rw-r--r--qpid/cpp/src/tests/legacystore/TestFramework.h37
-rw-r--r--qpid/cpp/src/tests/legacystore/clean.sh32
-rw-r--r--qpid/cpp/src/tests/legacystore/federation/Makefile.am46
-rwxr-xr-xqpid/cpp/src/tests/legacystore/federation/federation_tests_env.sh313
-rwxr-xr-xqpid/cpp/src/tests/legacystore/federation/run_federation_sys_tests96
-rwxr-xr-xqpid/cpp/src/tests/legacystore/federation/run_long_federation_sys_tests24
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/_st_auto_expand.cpp140
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/_st_basic.cpp558
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/_st_basic_txn.cpp239
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/_st_helper_fns.h882
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/_st_read.cpp460
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/_st_read_txn.cpp353
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/_ut_enq_map.cpp320
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/_ut_jdir.cpp416
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/_ut_jerrno.cpp47
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/_ut_jexception.cpp346
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/_ut_jinf.cpp402
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/_ut_lpmgr.cpp886
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/_ut_rec_hdr.cpp438
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/_ut_time_ns.cpp163
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/_ut_txn_map.cpp106
-rwxr-xr-xqpid/cpp/src/tests/legacystore/jrnl/chk_jdata32
-rwxr-xr-xqpid/cpp/src/tests/legacystore/jrnl/cp_rtest_jrnl59
-rwxr-xr-xqpid/cpp/src/tests/legacystore/jrnl/jhexdump41
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_data_src.cpp207
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_jrnl_init_params.cpp100
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_jrnl_instance.cpp178
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_read_arg.cpp146
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case.cpp113
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_result.cpp206
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_result_agregation.cpp178
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_set.cpp147
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_set.csv74
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/args.cpp226
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/args.h66
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/data_src.cpp87
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/data_src.h66
-rwxr-xr-xqpid/cpp/src/tests/legacystore/jrnl/jtt/jfile_chk.py838
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_init_params.cpp77
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_init_params.h80
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_instance.cpp439
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_instance.h121
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/jtt.csv234
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/main.cpp57
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/read_arg.cpp93
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/read_arg.h62
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case.cpp179
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case.h110
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result.cpp201
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result.h100
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result_agregation.cpp185
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result_agregation.h81
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_set.cpp169
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_set.h99
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/test_mgr.cpp218
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/jtt/test_mgr.h68
-rwxr-xr-xqpid/cpp/src/tests/legacystore/jrnl/prof32
-rwxr-xr-xqpid/cpp/src/tests/legacystore/jrnl/run-journal-tests47
-rw-r--r--qpid/cpp/src/tests/legacystore/jrnl/tests.odsbin0 -> 91064 bytes
-rw-r--r--qpid/cpp/src/tests/legacystore/persistence.py574
-rw-r--r--qpid/cpp/src/tests/legacystore/python_tests/__init__.py24
-rw-r--r--qpid/cpp/src/tests/legacystore/python_tests/client_persistence.py239
-rw-r--r--qpid/cpp/src/tests/legacystore/python_tests/resize.py170
-rw-r--r--qpid/cpp/src/tests/legacystore/python_tests/store_test.py417
-rw-r--r--qpid/cpp/src/tests/legacystore/run_long_python_tests21
-rwxr-xr-xqpid/cpp/src/tests/legacystore/run_python_tests43
-rw-r--r--qpid/cpp/src/tests/legacystore/run_short_python_tests21
-rw-r--r--qpid/cpp/src/tests/legacystore/system_test.sh51
-rw-r--r--qpid/cpp/src/tests/legacystore/unit_test.cpp28
-rw-r--r--qpid/cpp/src/tests/legacystore/unit_test.h69
-rw-r--r--qpid/cpp/src/tests/linearstore/CMakeLists.txt29
-rwxr-xr-xqpid/cpp/src/tests/linearstore/linearstoredirsetup.sh55
-rw-r--r--qpid/cpp/src/tests/linearstore/python_tests/__init__.py23
-rw-r--r--qpid/cpp/src/tests/linearstore/python_tests/client_persistence.py239
-rw-r--r--qpid/cpp/src/tests/linearstore/python_tests/store_test.py417
-rw-r--r--qpid/cpp/src/tests/linearstore/run_long_python_tests21
-rwxr-xr-xqpid/cpp/src/tests/linearstore/run_python_tests42
-rw-r--r--qpid/cpp/src/tests/linearstore/run_short_python_tests21
-rwxr-xr-xqpid/cpp/src/tests/linearstore/tx-test-soak.sh275
-rw-r--r--qpid/cpp/src/tests/logging.cpp512
-rw-r--r--qpid/cpp/src/tests/misc.py119
-rw-r--r--qpid/cpp/src/tests/msg_group_test.cpp641
-rwxr-xr-xqpid/cpp/src/tests/multiq_perftest22
-rwxr-xr-xqpid/cpp/src/tests/perfdist87
-rwxr-xr-xqpid/cpp/src/tests/ping_broker134
-rw-r--r--qpid/cpp/src/tests/policies.py209
-rw-r--r--qpid/cpp/src/tests/policy.acl20
-rw-r--r--qpid/cpp/src/tests/publish.cpp135
-rwxr-xr-xqpid/cpp/src/tests/python_tests34
-rw-r--r--qpid/cpp/src/tests/python_tests.ps142
-rwxr-xr-xqpid/cpp/src/tests/qpid-analyze-trace258
-rwxr-xr-xqpid/cpp/src/tests/qpid-build-rinstall28
-rw-r--r--qpid/cpp/src/tests/qpid-client-test.cpp139
-rwxr-xr-xqpid/cpp/src/tests/qpid-cluster-benchmark64
-rwxr-xr-xqpid/cpp/src/tests/qpid-cpp-benchmark363
-rwxr-xr-xqpid/cpp/src/tests/qpid-ctrl120
-rw-r--r--qpid/cpp/src/tests/qpid-latency-test.cpp480
-rw-r--r--qpid/cpp/src/tests/qpid-perftest.cpp760
-rw-r--r--qpid/cpp/src/tests/qpid-ping.cpp94
-rw-r--r--qpid/cpp/src/tests/qpid-receive.cpp299
-rw-r--r--qpid/cpp/src/tests/qpid-send.cpp465
-rwxr-xr-xqpid/cpp/src/tests/qpid-src-rinstall31
-rw-r--r--qpid/cpp/src/tests/qpid-stream.cpp193
-rw-r--r--qpid/cpp/src/tests/qpid-topic-listener.cpp209
-rw-r--r--qpid/cpp/src/tests/qpid-topic-publisher.cpp230
-rw-r--r--qpid/cpp/src/tests/qpid-txtest.cpp342
-rw-r--r--qpid/cpp/src/tests/qpid-txtest2.cpp363
-rw-r--r--qpid/cpp/src/tests/qpidd-empty.conf22
-rwxr-xr-xqpid/cpp/src/tests/qpidd-p046
-rwxr-xr-xqpid/cpp/src/tests/qpidd_qmfv2_tests.py278
-rw-r--r--qpid/cpp/src/tests/queue_flow_limit_tests.py376
-rw-r--r--qpid/cpp/src/tests/queue_redirect.py317
-rwxr-xr-xqpid/cpp/src/tests/quick_perftest22
-rwxr-xr-xqpid/cpp/src/tests/quick_topictest30
-rw-r--r--qpid/cpp/src/tests/quick_topictest.ps130
-rwxr-xr-xqpid/cpp/src/tests/quick_txtest22
-rw-r--r--qpid/cpp/src/tests/receiver.cpp140
-rw-r--r--qpid/cpp/src/tests/reject_release.py65
-rw-r--r--qpid/cpp/src/tests/replaying_sender.cpp165
-rw-r--r--qpid/cpp/src/tests/resuming_receiver.cpp193
-rwxr-xr-xqpid/cpp/src/tests/ring_queue_test174
-rwxr-xr-xqpid/cpp/src/tests/rsynchosts57
-rwxr-xr-xqpid/cpp/src/tests/run_acl_tests166
-rw-r--r--qpid/cpp/src/tests/run_acl_tests.ps199
-rwxr-xr-xqpid/cpp/src/tests/run_cli_tests81
-rwxr-xr-xqpid/cpp/src/tests/run_federation_sys_tests71
-rwxr-xr-xqpid/cpp/src/tests/run_federation_tests61
-rw-r--r--qpid/cpp/src/tests/run_federation_tests.ps183
-rwxr-xr-xqpid/cpp/src/tests/run_ha_tests29
-rwxr-xr-xqpid/cpp/src/tests/run_header_test31
-rw-r--r--qpid/cpp/src/tests/run_header_test.ps148
-rw-r--r--qpid/cpp/src/tests/run_headers_federation_tests49
-rwxr-xr-xqpid/cpp/src/tests/run_interlink_tests26
-rw-r--r--qpid/cpp/src/tests/run_long_federation_sys_tests24
-rwxr-xr-xqpid/cpp/src/tests/run_msg_group_tests62
-rw-r--r--qpid/cpp/src/tests/run_msg_group_tests.ps171
-rwxr-xr-xqpid/cpp/src/tests/run_msg_group_tests_soak63
-rwxr-xr-xqpid/cpp/src/tests/run_paged_queue_tests50
-rwxr-xr-xqpid/cpp/src/tests/run_perftest28
-rwxr-xr-xqpid/cpp/src/tests/run_queue_flow_limit_tests27
-rwxr-xr-xqpid/cpp/src/tests/run_queue_redirect56
-rwxr-xr-xqpid/cpp/src/tests/run_ring_queue_test36
-rw-r--r--qpid/cpp/src/tests/run_store_tests.ps1132
-rwxr-xr-xqpid/cpp/src/tests/run_test191
-rw-r--r--qpid/cpp/src/tests/run_test.ps1162
-rwxr-xr-xqpid/cpp/src/tests/sasl_fed169
-rwxr-xr-xqpid/cpp/src/tests/sasl_fed_ex283
-rwxr-xr-xqpid/cpp/src/tests/sasl_no_dir106
-rwxr-xr-xqpid/cpp/src/tests/sasl_test_setup.sh42
-rw-r--r--qpid/cpp/src/tests/sasl_version.cpp48
-rw-r--r--qpid/cpp/src/tests/sender.cpp157
-rwxr-xr-xqpid/cpp/src/tests/shared_perftest22
-rw-r--r--qpid/cpp/src/tests/shlibtest.cpp34
-rwxr-xr-xqpid/cpp/src/tests/ssl_test331
-rwxr-xr-xqpid/cpp/src/tests/store.py214
-rwxr-xr-xqpid/cpp/src/tests/swig_python_tests66
-rw-r--r--qpid/cpp/src/tests/test.xquery6
-rw-r--r--qpid/cpp/src/tests/test_env.ps1.in77
-rw-r--r--qpid/cpp/src/tests/test_env.sh.in100
-rw-r--r--qpid/cpp/src/tests/test_env_common.sh28
-rw-r--r--qpid/cpp/src/tests/test_store.cpp339
-rw-r--r--qpid/cpp/src/tests/test_tools.h106
-rwxr-xr-xqpid/cpp/src/tests/topic_perftest22
-rwxr-xr-xqpid/cpp/src/tests/topictest61
-rw-r--r--qpid/cpp/src/tests/topictest.ps173
-rw-r--r--qpid/cpp/src/tests/txjob.cpp102
-rw-r--r--qpid/cpp/src/tests/txshift.cpp193
-rw-r--r--qpid/cpp/src/tests/unit_test.cpp23
-rw-r--r--qpid/cpp/src/tests/unit_test.h74
-rw-r--r--qpid/cpp/src/tests/vg_check43
-rw-r--r--qpid/cpp/src/tests/windows/DisableWin32ErrorWindows.cpp78
-rw-r--r--qpid/cpp/src/versions.cmake51
-rw-r--r--qpid/cpp/src/windows/QpiddBroker.cpp512
-rw-r--r--qpid/cpp/src/windows/SCM.cpp332
-rw-r--r--qpid/cpp/src/windows/SCM.h109
-rw-r--r--qpid/cpp/src/windows/resources/qpid-icon.icobin0 -> 52972 bytes
-rw-r--r--qpid/cpp/src/windows/resources/template-resource.rc122
-rw-r--r--qpid/cpp/src/windows/resources/version-resource.h35
1427 files changed, 235444 insertions, 0 deletions
diff --git a/qpid/cpp/src/CMakeLists.txt b/qpid/cpp/src/CMakeLists.txt
new file mode 100644
index 0000000000..8e2b5b73e8
--- /dev/null
+++ b/qpid/cpp/src/CMakeLists.txt
@@ -0,0 +1,1327 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# library versions
+include (versions.cmake)
+
+# Option to require building optional plugins
+foreach (r ${REQUIRE})
+ set(${r}_force ON)
+ message(STATUS "Forcing ${r} to ${${r}_force}")
+endforeach(r)
+
+# Capture specified C++ compiler (if any)
+if (NOT ENV_CXX)
+ if (NOT "$ENV{CXX}" STREQUAL "")
+ set(CXX $ENV{CXX})
+ else(NOT "$ENV{CXX}" STREQUAL "")
+ set(CXX ${CMAKE_CXX_COMPILER})
+ endif(NOT "$ENV{CXX}" STREQUAL "")
+ set(ENV_CXX ${CXX} CACHE INTERNAL "C++ compiler specified in cmake environment")
+endif (NOT ENV_CXX)
+
+include(CheckFunctionExists)
+include(CheckIncludeFileCXX)
+include(CheckIncludeFiles)
+include(CheckIncludeFileCXX)
+include(CheckLibraryExists)
+include(CheckSymbolExists)
+include(CheckSizetDistinct)
+
+find_package(PkgConfig)
+find_package(Ruby)
+find_package(PythonInterp REQUIRED)
+
+find_package(Doxygen)
+
+find_program(VALGRIND_EXECUTABLE valgrind DOC "Location of the valgrind program")
+mark_as_advanced(VALGRIND_EXECUTABLE)
+find_package_handle_standard_args(VALGRIND DEFAULT_MSG VALGRIND_EXECUTABLE)
+
+find_program(SASLPASSWD2_EXECUTABLE saslpasswd2 DOC "Location of the saslpasswd2 program")
+mark_as_advanced(SASLPASSWD2_EXECUTABLE)
+
+# See if Cyrus SASL is desired and available
+CHECK_LIBRARY_EXISTS (sasl2 sasl_checkpass "" FOUND_SASL_LIB)
+CHECK_INCLUDE_FILES (sasl/sasl.h FOUND_SASL_H)
+find_package_handle_standard_args(SASL DEFAULT_MSG FOUND_SASL_LIB FOUND_SASL_H)
+
+#set (CMAKE_VERBOSE_MAKEFILE ON) # for debugging
+
+# Add a test to check the exported library API against expected API symbols
+MACRO (add_api_test libname)
+ if (NOT CMAKE_SYSTEM_NAME STREQUAL Windows AND BUILD_TESTING)
+ add_test(api_check_${libname} ${CMAKE_CURRENT_SOURCE_DIR}/check-abi "${ENV_CXX}" ${CMAKE_CURRENT_BINARY_DIR}/lib${libname}.so ${CMAKE_CURRENT_SOURCE_DIR}/lib${libname}-api-symbols.txt)
+ endif (NOT CMAKE_SYSTEM_NAME STREQUAL Windows AND BUILD_TESTING)
+ENDMACRO (add_api_test libname)
+
+
+# check if we generate source as part of the build
+# - rubygen generates the amqp spec
+# - managementgen generates the broker management code
+#
+# rubygen subdir is excluded from stable distributions
+# If the main AMQP spec is present, then check if ruby and python are
+# present, and if any sources have changed, forcing a re-gen of source code.
+find_file(QPID_AMQP_SPEC NAMES amqp.0-10-qpid-errata.stripped.xml PATHS ${qpid-cpp_SOURCE_DIR}/specs ${qpid-cpp_SOURCE_DIR}/../specs NO_DEFAULT_PATH)
+mark_as_advanced(QPID_AMQP_SPEC)
+if (NOT QPID_AMQP_SPEC)
+ message(FATAL_ERROR "Can't find amqp 0-10 spec for framing code generation")
+endif (NOT QPID_AMQP_SPEC)
+if (NOT RUBY_EXECUTABLE)
+ message(FATAL_ERROR "Can't locate ruby, needed to generate amqp 0-10 framing code.")
+endif (NOT RUBY_EXECUTABLE)
+
+set(specs ${QPID_AMQP_SPEC})
+set(regen_amqp OFF)
+set(rgen_dir ${qpid-cpp_SOURCE_DIR}/rubygen)
+file(GLOB_RECURSE rgen_progs ${rgen_dir}/*.rb)
+# If any of the specs, or any of the sources used to generate code, change
+# then regenerate the sources.
+foreach (spec_file ${specs} ${rgen_progs})
+ if (${spec_file} IS_NEWER_THAN ${CMAKE_CURRENT_BINARY_DIR}/rubygen.cmake)
+ set(regen_amqp ON)
+ endif (${spec_file} IS_NEWER_THAN ${CMAKE_CURRENT_BINARY_DIR}/rubygen.cmake)
+endforeach (spec_file ${specs})
+if (regen_amqp)
+ message(STATUS "Regenerating AMQP protocol sources")
+ execute_process(COMMAND ${RUBY_EXECUTABLE} -I ${rgen_dir} ${rgen_dir}/generate ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/../include ${specs} all rubygen.cmake
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+else (regen_amqp)
+ message(STATUS "No need to generate AMQP protocol sources")
+endif (regen_amqp)
+
+find_file(QPID_BROKER_MANAGEMENT_SPEC NAMES management-schema.xml PATHS ${CMAKE_CURRENT_SOURCE_DIR}/qpid/broker ${qpid-cpp_SOURCE_DIR}/../specs NO_DEFAULT_PATH)
+mark_as_advanced(QPID_BROKER_MANAGEMENT_SPEC)
+if (NOT QPID_BROKER_MANAGEMENT_SPEC)
+ message(FATAL_ERROR "Can't find broker management spec for code generation")
+endif (NOT QPID_BROKER_MANAGEMENT_SPEC)
+if (NOT PYTHON_EXECUTABLE)
+ message(FATAL_ERROR "Can't locate python, needed to generate broker management code.")
+endif (NOT PYTHON_EXECUTABLE)
+set(mgmt_specs ${QPID_BROKER_MANAGEMENT_SPEC}
+ ${CMAKE_CURRENT_SOURCE_DIR}/qpid/acl/management-schema.xml
+ ${CMAKE_CURRENT_SOURCE_DIR}/qpid/ha/management-schema.xml
+ ${CMAKE_CURRENT_SOURCE_DIR}/qpid/legacystore/management-schema.xml
+ ${CMAKE_CURRENT_SOURCE_DIR}/qpid/linearstore/management-schema.xml
+)
+set(mgen_dir ${qpid-cpp_SOURCE_DIR}/managementgen)
+set(regen_mgmt OFF)
+foreach (spec_file ${mgmt_specs})
+ if (${spec_file} IS_NEWER_THAN ${CMAKE_CURRENT_BINARY_DIR}/managementgen.cmake)
+ message(STATUS "${spec_file} is newer")
+ set(regen_mgmt ON)
+ endif (${spec_file} IS_NEWER_THAN ${CMAKE_CURRENT_BINARY_DIR}/managementgen.cmake)
+endforeach (spec_file ${mgmt_specs})
+if (regen_mgmt)
+ message(STATUS "Regenerating Qpid Management Framework sources")
+ execute_process(COMMAND ${PYTHON_EXECUTABLE} ${mgen_dir}/qmf-gen -c managementgen.cmake -b -l -q -o ${CMAKE_CURRENT_BINARY_DIR}/qmf ${mgmt_specs}
+ WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})
+else (regen_mgmt)
+ message(STATUS "No need to generate Qpid Management Framework sources")
+endif (regen_mgmt)
+
+# Pull in the names of the generated files, i.e. ${rgen_framing_srcs}
+include (${CMAKE_CURRENT_BINARY_DIR}/rubygen.cmake)
+include (${CMAKE_CURRENT_BINARY_DIR}/managementgen.cmake)
+
+# FindDoxygen module tries to locate doxygen and Graphviz dot
+if (DOXYGEN_FOUND)
+ option(BUILD_DOCS "Build user documentation" ON)
+else (DOXYGEN_FOUND)
+ message(STATUS "Can't locate the doxygen command; user documentation cannot be generated")
+endif (DOXYGEN_FOUND)
+
+if (VALGRIND_FOUND)
+ option(ENABLE_VALGRIND "Use valgrind to detect run-time problems" ON)
+endif (VALGRIND_FOUND)
+
+option(ENABLE_WARNINGS "Enable lots of compiler warnings (recommended)" ON)
+if (NOT ENABLE_WARNINGS)
+ set (WARNING_FLAGS "")
+endif (NOT ENABLE_WARNINGS)
+
+set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COMPILER_FLAGS} ${WARNING_FLAGS}")
+
+# Expand a bit from the basic Find_Boost; be specific about what's needed.
+# Boost.system is sometimes needed; it's handled separately, below.
+# There may be different minimum versions of boost for Windows and Unix
+if (CMAKE_SYSTEM_NAME STREQUAL Windows)
+ set (Boost_components program_options date_time thread)
+ set (Boost_minversion 1.44)
+else (CMAKE_SYSTEM_NAME STREQUAL Windows)
+ set (Boost_components program_options)
+ set (Boost_minversion 1.33)
+endif (CMAKE_SYSTEM_NAME STREQUAL Windows)
+
+# Visual Studio 2010 requires boost 1.45 or better.
+# The choice here is to fail demanding the user to update CMake to version N
+# where Boost 1.45 is supported, or we can just accept some versions using
+# the Additional_versions variable.
+if (NOT DEFINED Boost_ADDITIONAL_VERSIONS)
+ set (Boost_ADDITIONAL_VERSIONS
+ "1.45" "1.45.0" "1.46" "1.46.0" "1.47" "1.47.0"
+ "1.48" "1.48.0" "1.49" "1.49.0" "1.50" "1.50.0"
+ "1.51" "1.51.0" "1.52" "1.52.0" "1.53" "1.53.0"
+ "1.54" "1.54.0" "1.55" "1.55.0")
+endif (NOT DEFINED Boost_ADDITIONAL_VERSIONS)
+
+# Discover Boost version
+find_package(Boost ${Boost_minversion} QUIET REQUIRED)
+
+# Boost.system was introduced at Boost 1.35; it's needed secondarily by other
+# Boost libs Qpid needs, so be sure it's there.
+if (Boost_VERSION GREATER 103499)
+ list(APPEND Boost_components system)
+endif (Boost_VERSION GREATER 103499)
+
+# Boost.chrono was introduced at Boost 1.47; it's needed secondarily by other
+# Boost libs Qpid needs on Windows, so be sure it's there on Windows.
+if (Boost_VERSION GREATER 104699 AND CMAKE_SYSTEM_NAME STREQUAL Windows)
+ list(APPEND Boost_components chrono)
+endif (Boost_VERSION GREATER 104699 AND CMAKE_SYSTEM_NAME STREQUAL Windows)
+
+find_package(Boost ${Boost_minversion} REQUIRED COMPONENTS ${Boost_components})
+if(NOT Boost_FOUND)
+ message(FATAL_ERROR "Required Boost C++ libraries not found. Please install or try setting BOOST_ROOT")
+endif(NOT Boost_FOUND)
+
+if (BUILD_TESTING)
+ set (BUILD_TESTING_UNITTESTS ON)
+ find_package(Boost ${Boost_minversion} QUIET COMPONENTS unit_test_framework)
+ if(NOT Boost_UNIT_TEST_FRAMEWORK_LIBRARY)
+ message(STATUS "Could not find unit testing library - will not build unit tests")
+ set (BUILD_TESTING_UNITTESTS OFF)
+ endif(NOT Boost_UNIT_TEST_FRAMEWORK_LIBRARY)
+endif (BUILD_TESTING)
+
+# The Windows install also wants the Boost DLLs, libs and headers that the
+# release is built with. The DLLs enable everything to run, and the headers
+# and libs ensure that users building Qpid C++ client programs can compile
+# (the C++ API still exposes Boost headers, but hopefully this will be fixed
+# in the future).
+#
+# On Windows you can pick whether the static or dynamic versions of the libs
+# are used; allow this choice to the user. Since we also install the Boost
+# DLLs that are needed for the Windows package, none are needed for the
+# static link case; else drop them into the install. Do this all first, since
+# Boost on Windows can use automatic linking to pick up the correct
+# Boost libs based on compile-time touching of the headers. Since we don't
+# really need to add them to the link lines, set the names to blanks.
+option(QPID_LINK_BOOST_DYNAMIC "Link with dynamic Boost libs (OFF to link static)" ON)
+mark_as_advanced(QPID_LINK_BOOST_DYNAMIC)
+
+if (MSVC)
+ if (QPID_LINK_BOOST_DYNAMIC)
+ add_definitions( /D BOOST_ALL_DYN_LINK)
+ string (REPLACE .lib .dll
+ _boost_date_time_debug ${Boost_DATE_TIME_LIBRARY_DEBUG})
+ string (REPLACE .lib .dll
+ _boost_date_time_release ${Boost_DATE_TIME_LIBRARY_RELEASE})
+ string (REPLACE .lib .dll
+ _boost_program_options_debug ${Boost_PROGRAM_OPTIONS_LIBRARY_DEBUG})
+ string (REPLACE .lib .dll
+ _boost_program_options_release ${Boost_PROGRAM_OPTIONS_LIBRARY_RELEASE})
+ string (REPLACE .lib .dll
+ _boost_thread_debug ${Boost_THREAD_LIBRARY_DEBUG})
+ string (REPLACE .lib .dll
+ _boost_thread_release ${Boost_THREAD_LIBRARY_RELEASE})
+ if (NOT Boost_VERSION LESS 103500)
+ string (REPLACE .lib .dll
+ _boost_system_debug ${Boost_SYSTEM_LIBRARY_DEBUG})
+ string (REPLACE .lib .dll
+ _boost_system_release ${Boost_SYSTEM_LIBRARY_RELEASE})
+ endif (NOT Boost_VERSION LESS 103500)
+ if (NOT Boost_VERSION LESS 104700)
+ string (REPLACE .lib .dll
+ _boost_chrono_debug ${Boost_CHRONO_LIBRARY_DEBUG})
+ string (REPLACE .lib .dll
+ _boost_chrono_release ${Boost_CHRONO_LIBRARY_RELEASE})
+ endif (NOT Boost_VERSION LESS 104700)
+ install (PROGRAMS
+ ${_boost_date_time_debug} ${_boost_date_time_release}
+ ${_boost_program_options_debug} ${_boost_program_options_release}
+ ${_boost_thread_debug} ${_boost_thread_release}
+ ${_boost_chrono_debug} ${_boost_chrono_release}
+ ${_boost_system_debug} ${_boost_system_release}
+ DESTINATION ${QPID_INSTALL_BINDIR}
+ COMPONENT ${QPID_COMPONENT_COMMON})
+ endif (QPID_LINK_BOOST_DYNAMIC)
+
+ set(Boost_DATE_TIME_LIBRARY "")
+ set(Boost_THREAD_LIBRARY "")
+ set(Boost_PROGRAM_OPTIONS_LIBRARY "")
+ set(Boost_UNIT_TEST_FRAMEWORK_LIBRARY "")
+ set(Boost_SYSTEM_LIBRARY "")
+ set(Boost_CHRONO_LIBRARY "")
+ include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/windows/resources )
+endif (MSVC)
+
+include_directories( ${Boost_INCLUDE_DIR} )
+
+include_directories( ${CMAKE_CURRENT_SOURCE_DIR} )
+include_directories( ${CMAKE_CURRENT_SOURCE_DIR}/../include )
+include_directories( ${CMAKE_CURRENT_BINARY_DIR} )
+include_directories( ${CMAKE_CURRENT_BINARY_DIR}/../include )
+
+link_directories( ${Boost_LIBRARY_DIRS} )
+
+CHECK_SYMBOL_EXISTS(uuid_generate "uuid/uuid.h" UUID_GENERATE_IN_LIBC)
+if (UUID_GENERATE_IN_LIBC)
+ set(uuid_SRC "")
+ set(uuid_LIB "")
+else (UUID_GENERATE_IN_LIBC)
+ CHECK_LIBRARY_EXISTS (uuid uuid_generate "" UUID_GENERATE_IN_UUID)
+ if (UUID_GENERATE_IN_UUID)
+ set(uuid_SRC "")
+ set(uuid_LIB uuid)
+ else (UUID_GENERATE_IN_UUID)
+ CHECK_SYMBOL_EXISTS(uuid_create "uuid.h" UUID_CREATE_IN_LIBC)
+ if (UUID_CREATE_IN_LIBC)
+ set(uuid_SRC qpid/sys/FreeBSD/uuid.cpp)
+ set(uuid_LIB "")
+ else (UUID_CREATE_IN_LIBC)
+ CHECK_SYMBOL_EXISTS(UuidToString "rpc.h" WIN_UUID)
+ if (WIN_UUID)
+ set(uuid_SRC qpid/sys/windows/uuid.cpp)
+ set(uuid_LIB rpcrt4)
+ else (WIN_UUID)
+ message(FATAL_ERROR "No Uuid API found")
+ endif (WIN_UUID)
+ endif (UUID_CREATE_IN_LIBC)
+ endif (UUID_GENERATE_IN_UUID)
+endif (UUID_GENERATE_IN_LIBC)
+
+# These dependencies aren't found on windows
+if (NOT CMAKE_SYSTEM_NAME STREQUAL Windows)
+ # Ensure we have clock_gettime
+ CHECK_FUNCTION_EXISTS (clock_gettime CLOCK_GETTIME_IN_LIBC)
+ if (NOT CLOCK_GETTIME_IN_LIBC)
+ CHECK_LIBRARY_EXISTS (rt clock_gettime "" CLOCK_GETTIME_IN_RT)
+ if (CLOCK_GETTIME_IN_RT)
+ set(clock_gettime_LIB "rt")
+ else ()
+ message(FATAL_ERROR "Cannot find clock_gettime()")
+ endif (CLOCK_GETTIME_IN_RT)
+ endif (NOT CLOCK_GETTIME_IN_LIBC)
+
+ # Check for header file for dtrace static probes
+ check_include_files(sys/sdt.h HAVE_SDT)
+ if (HAVE_SDT)
+ # Only enable by default on Linux
+ if (CMAKE_SYSTEM_NAME STREQUAL Linux)
+ set(probes_default ON)
+ endif (CMAKE_SYSTEM_NAME STREQUAL Linux)
+ option(BUILD_PROBES "Build with DTrace/systemtap static probes" "${probes_default}")
+ endif (HAVE_SDT)
+ if (BUILD_PROBES)
+ set (HAVE_SYS_SDT_H 1)
+ else (HAVE_SDT)
+ set (HAVE_SYS_SDT_H 0)
+ endif (BUILD_PROBES)
+
+ # Check for poll/epoll header files
+ check_include_files(sys/poll.h HAVE_POLL)
+ check_include_files(sys/epoll.h HAVE_EPOLL)
+
+ # Set default poller implementation (check from general to specific to allow overriding)
+ if (HAVE_POLL)
+ set(poller_default poll)
+ endif (HAVE_POLL)
+ if (HAVE_EPOLL)
+ set(poller_default epoll)
+ endif (HAVE_EPOLL)
+ set(QPID_POLLER ${poller_default} CACHE STRING "Poller implementation (poll/epoll)")
+ mark_as_advanced(QPID_POLLER)
+endif (NOT CMAKE_SYSTEM_NAME STREQUAL Windows)
+
+check_size_t_distinct (QPID_SIZE_T_DISTINCT)
+
+option(BUILD_SASL "Build with Cyrus SASL support" ${SASL_FOUND})
+if (BUILD_SASL)
+ if (NOT SASL_FOUND)
+ message(FATAL_ERROR "Cyrus SASL support requested but libsasl2 libraryor headers not found")
+ endif (NOT SASL_FOUND)
+
+ set(qpidcommon_sasl_source
+ qpid/sys/cyrus/CyrusSecurityLayer.h
+ qpid/sys/cyrus/CyrusSecurityLayer.cpp
+ )
+ set(sasl_LIB sasl2)
+ set(HAVE_SASL 1)
+else (BUILD_SASL)
+ set(HAVE_SASL 0)
+endif (BUILD_SASL)
+set(QPID_BROKER_SASL_NAME "qpidd" CACHE STRING "SASL app name for the qpid broker")
+mark_as_advanced(QPID_BROKER_SASL_NAME)
+
+# Optional SSL/TLS support. Requires Netscape Portable Runtime on Linux.
+
+# According to some cmake docs this is not a reliable way to detect
+# pkg-configed libraries, but it's no worse than what we did under
+# autotools
+pkg_check_modules(NSS nss)
+
+set (ssl_default ${ssl_force})
+if (CMAKE_SYSTEM_NAME STREQUAL Windows)
+ set (ssl_default ON)
+else (CMAKE_SYSTEM_NAME STREQUAL Windows)
+ if (NSS_FOUND)
+ set (ssl_default ON)
+ endif (NSS_FOUND)
+endif (CMAKE_SYSTEM_NAME STREQUAL Windows)
+
+option(BUILD_SSL "Build with support for SSL" ${ssl_default})
+
+if (BUILD_SSL)
+ if (CMAKE_SYSTEM_NAME STREQUAL Windows)
+ set (sslcommon_SOURCES
+ qpid/sys/windows/SslAsynchIO.cpp
+ qpid/sys/windows/SslCredential.cpp
+ qpid/sys/windows/SslCredential.h
+ qpid/sys/windows/util.cpp
+ qpid/sys/windows/util.h
+ )
+
+ set (ssl_SOURCES
+ qpid/broker/windows/SslProtocolFactory.cpp
+ )
+
+ set (sslconnector_SOURCES
+ qpid/client/windows/SslConnector.cpp
+ )
+ set (ssl_INCLUDES "")
+ set (ssl_LIBDIRS "")
+ set (ssl_LIBS Crypt32.lib Secur32.lib)
+ set (ssl_server_LIBS Crypt32.lib Secur32.lib)
+ else (CMAKE_SYSTEM_NAME STREQUAL Windows)
+ if (NOT NSS_FOUND)
+ message(FATAL_ERROR "nss/nspr not found, required for ssl support")
+ endif (NOT NSS_FOUND)
+
+ set (sslcommon_SOURCES
+ qpid/sys/ssl/check.h
+ qpid/sys/ssl/check.cpp
+ qpid/sys/ssl/util.h
+ qpid/sys/ssl/util.cpp
+ qpid/sys/ssl/SslSocket.h
+ qpid/sys/ssl/SslSocket.cpp
+ )
+
+ set (ssl_SOURCES
+ qpid/sys/SslPlugin.cpp
+ )
+
+ set (sslconnector_SOURCES
+ qpid/client/SslConnector.cpp
+ qpid/messaging/amqp/SslTransport.cpp
+ )
+
+ set (ssl_INCLUDES "${NSS_INCLUDE_DIRS}")
+ set (ssl_LIBDIRS "${NSS_LIBRARY_DIRS}")
+ set (ssl_LIBS "${NSS_LIBRARIES}")
+ set (ssl_server_LIBS "${NSS_LIBRARIES}")
+ endif (CMAKE_SYSTEM_NAME STREQUAL Windows)
+
+ # Add include directories and link directories for NSS
+ # unfortunately this doesn't get done automatically for
+ # libraries detected by FindPkgConfig
+ include_directories(${ssl_INCLUDES})
+ link_directories(${ssl_LIBDIRS})
+endif (BUILD_SSL)
+
+# See if XML Exchange is desired and prerequisites are available
+CHECK_LIBRARY_EXISTS (xerces-c _init "" HAVE_XERCES)
+CHECK_INCLUDE_FILE_CXX (xercesc/framework/MemBufInputSource.hpp HAVE_XERCES_H)
+CHECK_INCLUDE_FILE_CXX (xqilla/xqilla-simple.hpp HAVE_XQILLA_H)
+CHECK_INCLUDE_FILE_CXX (xqilla/ast/XQEffectiveBooleanValue.hpp HAVE_XQ_EBV)
+
+set (xml_default ${xml_force})
+if (CMAKE_SYSTEM_NAME STREQUAL Windows)
+else (CMAKE_SYSTEM_NAME STREQUAL Windows)
+ if (HAVE_XERCES AND HAVE_XERCES_H)
+ if (HAVE_XQILLA_H)
+ set (xml_default ON)
+ endif (HAVE_XQILLA_H)
+ endif (HAVE_XERCES AND HAVE_XERCES_H)
+endif (CMAKE_SYSTEM_NAME STREQUAL Windows)
+
+option(BUILD_XML "Build with XML Exchange" ${xml_default})
+
+if (BUILD_XML)
+ if (NOT HAVE_XERCES)
+ message(FATAL_ERROR "XML Exchange support requested but xerces-c library not found")
+ endif (NOT HAVE_XERCES)
+ if (NOT HAVE_XERCES_H)
+ message(FATAL_ERROR "XML Exchange support requested but Xerces-C headers not found")
+ endif (NOT HAVE_XERCES_H)
+ if (NOT HAVE_XQILLA_H)
+ message(FATAL_ERROR "XML Exchange support requested but XQilla headers not found")
+ endif (NOT HAVE_XQILLA_H)
+
+ if (HAVE_XQ_EBV)
+ add_definitions(-DXQ_EFFECTIVE_BOOLEAN_VALUE_HPP)
+ endif (HAVE_XQ_EBV)
+
+ add_library (xml MODULE
+ qpid/xml/XmlExchange.cpp
+ qpid/xml/XmlExchange.h
+ qpid/xml/XmlExchangePlugin.cpp)
+ target_link_libraries (xml xerces-c xqilla qpidbroker qpidcommon)
+ set_target_properties (xml PROPERTIES
+ PREFIX ""
+ COMPILE_DEFINITIONS _IN_QPID_BROKER)
+ install (TARGETS xml
+ DESTINATION ${QPIDD_MODULE_DIR}
+ COMPONENT ${QPID_COMPONENT_BROKER})
+
+ set(xml_tests XmlClientSessionTest)
+
+endif (BUILD_XML)
+
+# Build the ACL plugin
+set (acl_default ON)
+
+option(BUILD_ACL "Build ACL enforcement broker plugin" ${acl_default})
+
+if (BUILD_ACL)
+ set (acl_SOURCES
+ qpid/acl/Acl.cpp
+ qpid/acl/Acl.h
+ qpid/acl/AclConnectionCounter.cpp
+ qpid/acl/AclConnectionCounter.h
+ qpid/acl/AclData.cpp
+ qpid/acl/AclData.h
+ qpid/acl/AclLexer.cpp
+ qpid/acl/AclLexer.h
+ qpid/acl/AclPlugin.cpp
+ qpid/acl/AclReader.cpp
+ qpid/acl/AclReader.h
+ qpid/acl/AclResourceCounter.cpp
+ qpid/acl/AclResourceCounter.h
+ qpid/acl/AclValidator.cpp
+ qpid/acl/AclValidator.h
+ )
+endif (BUILD_ACL)
+
+set (ha_default ON)
+
+option(BUILD_HA "Build Active-Passive HA plugin" ${ha_default})
+
+if (BUILD_HA)
+ set (ha_SOURCES
+ qpid/ha/AlternateExchangeSetter.h
+ qpid/ha/Backup.cpp
+ qpid/ha/Backup.h
+ qpid/ha/BackupConnectionExcluder.h
+ qpid/ha/BrokerInfo.cpp
+ qpid/ha/BrokerInfo.h
+ qpid/ha/BrokerReplicator.cpp
+ qpid/ha/BrokerReplicator.h
+ qpid/ha/ConnectionObserver.cpp
+ qpid/ha/ConnectionObserver.h
+ qpid/ha/Event.cpp
+ qpid/ha/Event.h
+ qpid/ha/FailoverExchange.cpp
+ qpid/ha/FailoverExchange.h
+ qpid/ha/HaBroker.cpp
+ qpid/ha/HaBroker.h
+ qpid/ha/HaPlugin.cpp
+ qpid/ha/IdSetter.h
+ qpid/ha/LogPrefix.cpp
+ qpid/ha/LogPrefix.h
+ qpid/ha/Membership.cpp
+ qpid/ha/Membership.h
+ qpid/ha/Primary.cpp
+ qpid/ha/Primary.h
+ qpid/ha/PrimaryQueueLimits.h
+ qpid/ha/PrimaryTxObserver.cpp
+ qpid/ha/PrimaryTxObserver.h
+ qpid/ha/QueueGuard.cpp
+ qpid/ha/QueueGuard.h
+ qpid/ha/QueueReplicator.cpp
+ qpid/ha/QueueReplicator.h
+ qpid/ha/QueueSnapshot.h
+ qpid/ha/QueueSnapshot.h
+ qpid/ha/RemoteBackup.cpp
+ qpid/ha/RemoteBackup.h
+ qpid/ha/ReplicatingSubscription.cpp
+ qpid/ha/ReplicatingSubscription.h
+ qpid/ha/ReplicationTest.cpp
+ qpid/ha/ReplicationTest.h
+ qpid/ha/Role.h
+ qpid/ha/Settings.h
+ qpid/ha/StandAlone.h
+ qpid/ha/StatusCheck.cpp
+ qpid/ha/StatusCheck.h
+ qpid/ha/TxReplicatingSubscription.cpp
+ qpid/ha/TxReplicatingSubscription.h
+ qpid/ha/TxReplicator.cpp
+ qpid/ha/TxReplicator.h
+ qpid/ha/types.cpp
+ qpid/ha/types.h
+ )
+
+ add_library (ha MODULE ${ha_SOURCES})
+ target_link_libraries (ha
+ qpidtypes qpidcommon qpidbroker qpidmessaging
+ ${Boost_PROGRAM_OPTIONS_LIBRARY})
+ set_target_properties (ha PROPERTIES
+ PREFIX ""
+ COMPILE_DEFINITIONS _IN_QPID_BROKER)
+ install (TARGETS ha
+ DESTINATION ${QPIDD_MODULE_DIR}
+ COMPONENT ${QPID_COMPONENT_BROKER})
+endif (BUILD_HA)
+
+# Check for optional RDMA support requirements
+include (rdma.cmake)
+
+# Check for optional AMQP 1.0 support requirements
+include (amqp.cmake)
+
+# Check for syslog capabilities not present on all systems
+check_symbol_exists (LOG_AUTHPRIV "sys/syslog.h" HAVE_LOG_AUTHPRIV)
+check_symbol_exists (LOG_FTP "sys/syslog.h" HAVE_LOG_FTP)
+
+# Set default Memory Status module (Null implementation)
+set (qpid_memstat_module
+ qpid/sys/MemStat.cpp
+)
+
+if (CMAKE_SYSTEM_NAME STREQUAL Windows)
+
+ set (qpidcommon_platform_SOURCES
+ qpid/log/windows/SinkOptions.cpp
+ qpid/sys/windows/AsynchIO.cpp
+ qpid/sys/windows/Path.cpp
+ qpid/sys/windows/FileSysDir.cpp
+ qpid/sys/windows/IocpPoller.cpp
+ qpid/sys/windows/IOHandle.cpp
+ qpid/sys/windows/LockFile.cpp
+ qpid/sys/windows/MemoryMappedFile.cpp
+ qpid/sys/windows/PipeHandle.cpp
+ qpid/sys/windows/PollableCondition.cpp
+ qpid/sys/windows/Shlib.cpp
+ qpid/sys/windows/WinSocket.cpp
+ qpid/sys/windows/SocketAddress.cpp
+ qpid/sys/windows/StrError.cpp
+ qpid/sys/windows/SystemInfo.cpp
+ qpid/sys/windows/Thread.cpp
+ qpid/sys/windows/Time.cpp
+ qpid/client/windows/SaslFactory.cpp
+ )
+
+ set (qpidcommon_platform_LIBS
+ ${Boost_THREAD_LIBRARY}
+ ${Boost_DATE_TIME_LIBRARY}
+ ${Boost_SYSTEM_LIBRARY}
+ ws2_32
+ )
+
+ set (qpidbroker_platform_SOURCES
+ qpid/broker/windows/BrokerDefaults.cpp
+ qpid/broker/windows/SaslAuthenticator.cpp
+ )
+
+ set (qpidclient_platform_SOURCES
+ qpid/client/windows/ClientDllMain.cpp
+ )
+
+ set (qpidd_platform_SOURCES
+ windows/QpiddBroker.cpp
+ windows/SCM.cpp
+ )
+
+else (CMAKE_SYSTEM_NAME STREQUAL Windows)
+
+ # POSIX (Non-Windows) platforms have a lot of overlap in sources; the only
+ # major difference is the poller module.
+ if (QPID_POLLER STREQUAL poll)
+ set (qpid_poller_module
+ qpid/sys/posix/PosixPoller.cpp
+ )
+ elseif (QPID_POLLER STREQUAL epoll)
+ set (qpid_poller_module
+ qpid/sys/epoll/EpollPoller.cpp
+ )
+ endif (QPID_POLLER STREQUAL poll)
+
+ # Set default System Info module
+ set (qpid_system_module
+ qpid/sys/posix/SystemInfo.cpp
+ )
+
+ if (CMAKE_SYSTEM_NAME STREQUAL Linux)
+ # On Linux override memory status module
+ set (qpid_memstat_module
+ qpid/sys/posix/MemStat.cpp
+ )
+ endif (CMAKE_SYSTEM_NAME STREQUAL Linux)
+
+ if (CMAKE_SYSTEM_NAME STREQUAL SunOS)
+ # On Solaris override the system info module
+ set (qpid_system_module
+ qpid/sys/solaris/SystemInfo.cpp
+ )
+ # On Sun we want -lpthread -lthread as the 2nd last and last libs passed to linker
+ set (qpidtypes_platform_LIBS ${qpidtypes_platform_LIBS}
+ pthread
+ thread
+ )
+ endif (CMAKE_SYSTEM_NAME STREQUAL SunOS)
+
+ if (CMAKE_SYSTEM_NAME STREQUAL AIX)
+ set (qpid_system_module
+ qpid/sys/aix/SystemInfo.cpp
+ )
+ endif (CMAKE_SYSTEM_NAME STREQUAL AIX)
+
+ if (CMAKE_CXX_COMPILER_ID STREQUAL SunPro)
+ # -lmalloc needed for mallinfo.
+ set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lmalloc")
+ set (CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -lmalloc")
+ endif (CMAKE_CXX_COMPILER_ID STREQUAL SunPro)
+
+ set (qpidcommon_platform_SOURCES
+ qpid/sys/posix/AsynchIO.cpp
+ qpid/sys/posix/Condition.cpp
+ qpid/sys/posix/Fork.cpp
+ qpid/sys/posix/Path.cpp
+ qpid/sys/posix/FileSysDir.cpp
+ qpid/sys/posix/IOHandle.cpp
+ qpid/sys/posix/LockFile.cpp
+ qpid/sys/posix/MemoryMappedFile.cpp
+ qpid/sys/posix/Mutex.cpp
+ qpid/sys/posix/PipeHandle.cpp
+ qpid/sys/posix/PollableCondition.cpp
+ qpid/sys/posix/Shlib.cpp
+ qpid/log/posix/SinkOptions.cpp
+ qpid/sys/posix/BSDSocket.cpp
+ qpid/sys/posix/SocketAddress.cpp
+ qpid/sys/posix/StrError.cpp
+ qpid/sys/posix/Thread.cpp
+ qpid/sys/posix/Time.cpp
+ qpid/SaslFactory.cpp
+
+ ${qpid_system_module}
+ ${qpid_poller_module}
+ )
+
+ set (qpidcommon_platform_LIBS
+ "${CMAKE_DL_LIBS}"
+ "${clock_gettime_LIB}"
+ )
+
+ set (qpidbroker_platform_SOURCES
+ qpid/broker/Daemon.cpp
+ qpid/broker/SaslAuthenticator.cpp
+ qpid/broker/SignalHandler.h
+ qpid/broker/SignalHandler.cpp
+ qpid/broker/posix/BrokerDefaults.cpp
+ qpid/broker/posix/SocketFDPlugin.cpp
+ )
+
+ set (qpidclient_platform_SOURCES
+ )
+
+ set (qpidd_platform_SOURCES
+ posix/QpiddBroker.cpp
+ )
+endif (CMAKE_SYSTEM_NAME STREQUAL Windows)
+
+set (qpidcommon_SOURCES
+ ${rgen_framing_srcs}
+ ${qpidcommon_platform_SOURCES}
+ ${qpidcommon_sasl_source}
+ ${sslcommon_SOURCES}
+ qpid/assert.cpp
+ qpid/AclHost.cpp
+ qpid/Address.cpp
+ qpid/DataDir.cpp
+ qpid/Exception.cpp
+ qpid/Modules.cpp
+ qpid/Options.cpp
+ qpid/Plugin.cpp
+ qpid/RefCountedBuffer.cpp
+ qpid/SessionState.cpp
+ qpid/SessionId.cpp
+ qpid/StringUtils.cpp
+ qpid/Url.cpp
+ qpid/UrlArray.cpp
+ qpid/NullSaslClient.cpp
+ qpid/NullSaslServer.cpp
+ qpid/amqp_0_10/SessionHandler.cpp
+ qpid/framing/AccumulatedAck.cpp
+ qpid/framing/AMQBody.cpp
+ qpid/framing/AMQMethodBody.cpp
+ qpid/framing/AMQContentBody.cpp
+ qpid/framing/AMQFrame.cpp
+ qpid/framing/AMQHeaderBody.cpp
+ qpid/framing/AMQHeartbeatBody.cpp
+ qpid/framing/Array.cpp
+ qpid/framing/Buffer.cpp
+ qpid/framing/FieldTable.cpp
+ qpid/framing/FieldValue.cpp
+ qpid/framing/FrameSet.cpp
+ qpid/framing/FrameDecoder.cpp
+ qpid/framing/List.cpp
+ qpid/framing/ProtocolInitiation.cpp
+ qpid/framing/ProtocolVersion.cpp
+ qpid/framing/SendContent.cpp
+ qpid/framing/SequenceNumber.cpp
+ qpid/framing/SequenceNumberSet.cpp
+ qpid/framing/SequenceSet.cpp
+ qpid/framing/Proxy.cpp
+ qpid/framing/Uuid.cpp
+ qpid/framing/TransferContent.cpp
+ qpid/log/Logger.cpp
+ qpid/log/Options.cpp
+ qpid/log/OstreamOutput.cpp
+ qpid/log/Selector.cpp
+ qpid/log/Statement.cpp
+ qpid/management/Buffer.cpp
+ qpid/management/ConnectionSettings.cpp
+ qpid/management/Mutex.cpp
+ qpid/management/Manageable.cpp
+ qpid/management/ManagementObject.cpp
+ qpid/sys/AggregateOutput.cpp
+ qpid/sys/AsynchIOHandler.cpp
+ qpid/sys/Dispatcher.cpp
+ qpid/sys/DispatchHandle.cpp
+ qpid/sys/Runnable.cpp
+ qpid/sys/Shlib.cpp
+ qpid/sys/Timer.cpp
+ qpid/sys/TimerWarnings.cpp
+ qpid/amqp_0_10/Codecs.cpp
+ qpid/amqp/CharSequence.h
+ qpid/amqp/CharSequence.cpp
+ qpid/amqp/DataBuilder.h
+ qpid/amqp/DataBuilder.cpp
+ qpid/amqp/Decoder.h
+ qpid/amqp/Decoder.cpp
+ qpid/amqp/Descriptor.h
+ qpid/amqp/Descriptor.cpp
+ qpid/amqp/Encoder.h
+ qpid/amqp/Encoder.cpp
+ qpid/amqp/ListBuilder.h
+ qpid/amqp/ListBuilder.cpp
+ qpid/amqp/MapHandler.h
+ qpid/amqp/MapEncoder.h
+ qpid/amqp/MapEncoder.cpp
+ qpid/amqp/MapSizeCalculator.h
+ qpid/amqp/MapSizeCalculator.cpp
+ qpid/amqp/MapBuilder.h
+ qpid/amqp/MapBuilder.cpp
+ qpid/amqp/MapReader.h
+ qpid/amqp/MapReader.cpp
+ qpid/amqp/MessageEncoder.h
+ qpid/amqp/MessageEncoder.cpp
+ qpid/amqp/MessageId.h
+ qpid/amqp/MessageId.cpp
+ qpid/amqp/MessageReader.h
+ qpid/amqp/MessageReader.cpp
+ qpid/amqp/Reader.h
+ qpid/amqp/Sasl.h
+ qpid/amqp/Sasl.cpp
+ qpid/amqp/SaslClient.h
+ qpid/amqp/SaslClient.cpp
+ qpid/amqp/SaslServer.h
+ qpid/amqp/SaslServer.cpp
+ qpid/messaging/amqp/Transport.h
+ qpid/messaging/amqp/Transport.cpp
+ qpid/messaging/amqp/TransportContext.h
+ ${qpid_memstat_module}
+)
+add_msvc_version (qpidcommon library dll)
+
+add_library (qpidcommon SHARED ${qpidcommon_SOURCES})
+
+target_link_libraries (qpidcommon qpidtypes
+ ${qpidcommon_platform_LIBS}
+ ${Boost_PROGRAM_OPTIONS_LIBRARY}
+ "${sasl_LIB}"
+ ${ssl_LIBS})
+
+set_target_properties (qpidcommon PROPERTIES
+ VERSION ${qpidcommon_version}
+ SOVERSION ${qpidcommon_version_major})
+
+install (TARGETS qpidcommon
+ RUNTIME DESTINATION ${QPID_INSTALL_BINDIR} COMPONENT ${QPID_COMPONENT_COMMON}
+ LIBRARY DESTINATION ${QPID_INSTALL_LIBDIR} COMPONENT ${QPID_COMPONENT_COMMON}
+ ARCHIVE DESTINATION ${QPID_INSTALL_LIBDIR} COMPONENT ${QPID_COMPONENT_COMMON})
+install_pdb (qpidcommon ${QPID_COMPONENT_COMMON})
+
+set(qpidtypes_SOURCES
+ qpid/types/Exception.cpp
+ qpid/types/Uuid.cpp
+ qpid/types/Variant.cpp
+ ${uuid_SRC}
+)
+set_source_files_properties(
+ ${qpidtypes_SOURCES}
+ PROPERTIES
+ COMPILE_FLAGS "${HIDE_SYMBOL_FLAGS}")
+
+add_msvc_version (qpidtypes library dll)
+add_library(qpidtypes SHARED ${qpidtypes_SOURCES})
+target_link_libraries(qpidtypes "${uuid_LIB}")
+set_target_properties (qpidtypes PROPERTIES
+ LINK_FLAGS "${HIDE_SYMBOL_FLAGS} ${LINK_VERSION_SCRIPT_FLAG}"
+ VERSION ${qpidtypes_version}
+ SOVERSION ${qpidtypes_version_major})
+
+install(TARGETS qpidtypes
+ RUNTIME DESTINATION ${QPID_INSTALL_BINDIR} COMPONENT ${QPID_COMPONENT_COMMON}
+ LIBRARY DESTINATION ${QPID_INSTALL_LIBDIR} COMPONENT ${QPID_COMPONENT_COMMON}
+ ARCHIVE DESTINATION ${QPID_INSTALL_LIBDIR} COMPONENT ${QPID_COMPONENT_COMMON})
+install_pdb (qpidtypes ${QPID_COMPONENT_COMMON})
+
+add_api_test(qpidtypes)
+
+set (qpidclient_SOURCES
+ ${rgen_client_srcs}
+ ${qpidclient_platform_SOURCES}
+ ${sslconnector_SOURCES}
+ qpid/client/Bounds.cpp
+ qpid/client/Completion.cpp
+ qpid/client/CompletionImpl.cpp
+ qpid/client/Connection.cpp
+ qpid/client/ConnectionHandler.cpp
+ qpid/client/ConnectionImpl.cpp
+ qpid/client/ConnectionSettings.cpp
+ qpid/client/Connector.cpp
+ qpid/client/Demux.cpp
+ qpid/client/Dispatcher.cpp
+ qpid/client/FailoverManager.cpp
+ qpid/client/FailoverListener.cpp
+ qpid/client/Future.cpp
+ qpid/client/FutureCompletion.cpp
+ qpid/client/FutureResult.cpp
+ qpid/client/LoadPlugins.cpp
+ qpid/client/LocalQueue.cpp
+ qpid/client/LocalQueueImpl.cpp
+ qpid/client/Message.cpp
+ qpid/client/MessageImpl.cpp
+ qpid/client/MessageListener.cpp
+ qpid/client/MessageReplayTracker.cpp
+ qpid/client/QueueOptions.cpp
+ qpid/client/Results.cpp
+ qpid/client/SessionBase_0_10.cpp
+ qpid/client/SessionBase_0_10Access.h
+ qpid/client/ConnectionAccess.h
+ qpid/client/SessionImpl.cpp
+ qpid/client/StateManager.cpp
+ qpid/client/Subscription.cpp
+ qpid/client/SubscriptionImpl.cpp
+ qpid/client/SubscriptionManager.cpp
+ qpid/client/SubscriptionManagerImpl.cpp
+ qpid/client/TCPConnector.cpp
+)
+add_msvc_version (qpidclient library dll)
+
+add_library (qpidclient SHARED ${qpidclient_SOURCES})
+
+target_link_libraries (qpidclient qpidcommon qpidtypes
+ ${ssl_LIBS})
+
+set_target_properties (qpidclient PROPERTIES
+ VERSION ${qpidclient_version}
+ SOVERSION ${qpidclient_version_major})
+
+install (TARGETS qpidclient
+ RUNTIME DESTINATION ${QPID_INSTALL_BINDIR} COMPONENT ${QPID_COMPONENT_CLIENT}
+ LIBRARY DESTINATION ${QPID_INSTALL_LIBDIR} COMPONENT ${QPID_COMPONENT_CLIENT}
+ ARCHIVE DESTINATION ${QPID_INSTALL_LIBDIR} COMPONENT ${QPID_COMPONENT_CLIENT})
+install (DIRECTORY ../include/qpid
+ DESTINATION ${QPID_INSTALL_INCLUDEDIR}
+ COMPONENT ${QPID_COMPONENT_CLIENT_INCLUDE}
+ PATTERN ".svn" EXCLUDE)
+install_pdb (qpidclient ${QPID_COMPONENT_CLIENT})
+
+set (qpidmessaging_SOURCES
+ ${amqpc_SOURCES}
+ qpid/messaging/AddressImpl.h
+ qpid/messaging/ConnectionImpl.h
+ qpid/messaging/ReceiverImpl.h
+ qpid/messaging/SessionImpl.h
+ qpid/messaging/SenderImpl.h
+ qpid/client/amqp0_10/AcceptTracker.h
+ qpid/client/amqp0_10/AcceptTracker.cpp
+ qpid/client/amqp0_10/AddressResolution.h
+ qpid/client/amqp0_10/AddressResolution.cpp
+ qpid/client/amqp0_10/ConnectionImpl.h
+ qpid/client/amqp0_10/ConnectionImpl.cpp
+ qpid/client/amqp0_10/IncomingMessages.h
+ qpid/client/amqp0_10/IncomingMessages.cpp
+ qpid/client/amqp0_10/MessageSink.h
+ qpid/client/amqp0_10/MessageSource.h
+ qpid/client/amqp0_10/OutgoingMessage.h
+ qpid/client/amqp0_10/OutgoingMessage.cpp
+ qpid/client/amqp0_10/ReceiverImpl.h
+ qpid/client/amqp0_10/ReceiverImpl.cpp
+ qpid/client/amqp0_10/SessionImpl.h
+ qpid/client/amqp0_10/SessionImpl.cpp
+ qpid/client/amqp0_10/SenderImpl.h
+ qpid/client/amqp0_10/SenderImpl.cpp
+ qpid/messaging/Address.cpp
+ qpid/messaging/AddressParser.h
+ qpid/messaging/AddressParser.cpp # The functions in here are not in the public interface, but qmf uses them
+ qpid/messaging/Connection.cpp
+ qpid/messaging/Duration.cpp
+ qpid/messaging/exceptions.cpp
+ qpid/messaging/FailoverUpdates.cpp
+ qpid/messaging/Logger.cpp
+ qpid/messaging/Message.cpp
+ qpid/messaging/Receiver.cpp
+ qpid/messaging/Session.cpp
+ qpid/messaging/Sender.cpp
+ #functions from the following are not in the public interface but are used by the AMQP 1.0 client module
+ qpid/messaging/ConnectionOptions.h
+ qpid/messaging/ConnectionOptions.cpp
+ qpid/messaging/MessageImpl.h
+ qpid/messaging/MessageImpl.cpp
+ qpid/messaging/Message_io.cpp
+ qpid/messaging/ProtocolRegistry.cpp
+ qpid/messaging/amqp/EncodedMessage.h
+ qpid/messaging/amqp/EncodedMessage.cpp
+)
+
+add_msvc_version (qpidmessaging library dll)
+
+add_library (qpidmessaging SHARED ${qpidmessaging_SOURCES})
+target_link_libraries (qpidmessaging qpidtypes qpidclient qpidcommon ${Proton_LIBRARIES})
+set_target_properties (qpidmessaging PROPERTIES
+ LINK_FLAGS "${HIDE_SYMBOL_FLAGS} ${LINK_VERSION_SCRIPT_FLAG}"
+ COMPILE_FLAGS "${HIDE_SYMBOL_FLAGS}"
+ VERSION ${qpidmessaging_version}
+ SOVERSION ${qpidmessaging_version_major})
+install (TARGETS qpidmessaging
+ RUNTIME DESTINATION ${QPID_INSTALL_BINDIR} COMPONENT ${QPID_COMPONENT_CLIENT}
+ LIBRARY DESTINATION ${QPID_INSTALL_LIBDIR} COMPONENT ${QPID_COMPONENT_CLIENT}
+ ARCHIVE DESTINATION ${QPID_INSTALL_LIBDIR} COMPONENT ${QPID_COMPONENT_CLIENT})
+install_pdb (qpidmessaging ${QPID_COMPONENT_CLIENT})
+
+add_api_test(qpidmessaging)
+
+if (MSVC)
+ # Install the DtcPlugin project and call it qpidxarm.
+ set(AMQP_WCF_DIR ${qpid-cpp_SOURCE_DIR}/../wcf)
+ set(qpidxarm_SOURCES ${AMQP_WCF_DIR}/src/Apache/Qpid/DtcPlugin/DtcPlugin.cpp)
+ if (EXISTS ${qpidxarm_SOURCES})
+ add_msvc_version (qpidxarm library dll)
+ add_library (qpidxarm SHARED ${qpidxarm_SOURCES})
+ target_link_libraries (qpidxarm qpidclient qpidcommon)
+ install (TARGETS qpidxarm
+ RUNTIME DESTINATION ${QPID_INSTALL_BINDIR} COMPONENT ${QPID_COMPONENT_CLIENT}
+ LIBRARY DESTINATION ${QPID_INSTALL_LIBDIR} COMPONENT ${QPID_COMPONENT_CLIENT}
+ ARCHIVE DESTINATION ${QPID_INSTALL_LIBDIR} COMPONENT ${QPID_COMPONENT_CLIENT})
+ install_pdb (qpidxarm ${QPID_COMPONENT_CLIENT})
+ endif (EXISTS ${qpidxarm_SOURCES})
+endif (MSVC)
+
+set (qpidbroker_SOURCES
+ ${mgen_broker_cpp}
+ ${qpidbroker_platform_SOURCES}
+ ${acl_SOURCES}
+ ${ssl_SOURCES}
+ qpid/amqp_0_10/Connection.h
+ qpid/amqp_0_10/Connection.cpp
+ qpid/broker/AsyncCommandCallback.h
+ qpid/broker/AsyncCommandCallback.cpp
+ qpid/broker/Broker.cpp
+ qpid/broker/Credit.cpp
+ qpid/broker/Exchange.cpp
+ qpid/broker/Fairshare.cpp
+ qpid/broker/MessageDeque.cpp
+ qpid/broker/MessageMap.cpp
+ qpid/broker/ObjectFactory.h
+ qpid/broker/ObjectFactory.cpp
+ qpid/broker/PriorityQueue.cpp
+ qpid/broker/Protocol.cpp
+ qpid/broker/Queue.cpp
+ qpid/broker/QueueCleaner.cpp
+ qpid/broker/QueueListeners.cpp
+ qpid/broker/FifoDistributor.cpp
+ qpid/broker/MessageGroupManager.cpp
+ qpid/broker/PersistableMessage.cpp
+ qpid/broker/PersistableObject.cpp
+ qpid/broker/Bridge.cpp
+ qpid/broker/amqp_0_10/Connection.cpp
+ qpid/broker/ConnectionHandler.cpp
+ qpid/broker/DeliverableMessage.cpp
+ qpid/broker/DeliveryRecord.cpp
+ qpid/broker/DirectExchange.cpp
+ qpid/broker/DtxAck.cpp
+ qpid/broker/DtxBuffer.cpp
+ qpid/broker/DtxManager.cpp
+ qpid/broker/DtxTimeout.cpp
+ qpid/broker/DtxWorkRecord.cpp
+ qpid/broker/ExchangeRegistry.cpp
+ qpid/broker/FanOutExchange.cpp
+ qpid/broker/HeadersExchange.cpp
+ qpid/broker/IngressCompletion.cpp
+ qpid/broker/Link.cpp
+ qpid/broker/LinkRegistry.cpp
+ qpid/broker/LossyLvq.cpp
+ qpid/broker/LossyQueue.cpp
+ qpid/broker/Lvq.cpp
+ qpid/broker/Message.cpp
+ qpid/broker/MessageAdapter.cpp
+ qpid/broker/MessageBuilder.cpp
+ qpid/broker/MessageStoreModule.cpp
+ qpid/broker/NameGenerator.cpp
+ qpid/broker/NullMessageStore.cpp
+ qpid/broker/PagedQueue.cpp
+ qpid/broker/QueueBindings.cpp
+ qpid/broker/QueuedMessage.cpp
+ qpid/broker/QueueCursor.cpp
+ qpid/broker/QueueDepth.cpp
+ qpid/broker/QueueFactory.cpp
+ qpid/broker/QueueRegistry.cpp
+ qpid/broker/QueueSettings.cpp
+ qpid/broker/QueueFlowLimit.cpp
+ qpid/broker/RecoveryManagerImpl.cpp
+ qpid/broker/RecoveredEnqueue.cpp
+ qpid/broker/RecoveredDequeue.cpp
+ qpid/broker/RetryList.cpp
+ qpid/broker/SecureConnection.cpp
+ qpid/broker/Selector.h
+ qpid/broker/Selector.cpp
+ qpid/broker/SelectorExpression.h
+ qpid/broker/SelectorExpression.cpp
+ qpid/broker/SelectorToken.h
+ qpid/broker/SelectorToken.cpp
+ qpid/broker/SelectorValue.h
+ qpid/broker/SelectorValue.cpp
+ qpid/broker/SelfDestructQueue.cpp
+ qpid/broker/SemanticState.h
+ qpid/broker/SemanticState.cpp
+ qpid/broker/SessionAdapter.cpp
+ qpid/broker/SessionState.h
+ qpid/broker/SessionState.cpp
+ qpid/broker/SessionManager.h
+ qpid/broker/SessionManager.cpp
+ qpid/broker/SessionContext.h
+ qpid/broker/SessionHandler.h
+ qpid/broker/SessionHandler.cpp
+ qpid/broker/System.cpp
+ qpid/broker/ThresholdAlerts.cpp
+ qpid/broker/TopicExchange.cpp
+ qpid/broker/TxAccept.cpp
+ qpid/broker/TxBuffer.cpp
+ qpid/broker/TxDequeue.h
+ qpid/broker/TxDequeue.cpp
+ qpid/broker/Vhost.cpp
+ qpid/broker/amqp_0_10/MessageTransfer.cpp
+ qpid/management/ManagementAgent.cpp
+ qpid/management/ManagementDirectExchange.cpp
+ qpid/management/ManagementTopicExchange.cpp
+ qpid/sys/SocketTransport.cpp
+ qpid/sys/TCPIOPlugin.cpp
+)
+add_msvc_version (qpidbroker library dll)
+add_library (qpidbroker SHARED ${qpidbroker_SOURCES})
+
+target_link_libraries (qpidbroker qpidcommon qpidtypes
+ "${sasl_LIB}"
+ ${ssl_server_LIBS})
+
+set_target_properties (qpidbroker PROPERTIES
+ VERSION ${qpidbroker_version}
+ SOVERSION ${qpidbroker_version_major}
+ COMPILE_DEFINITIONS _IN_QPID_BROKER)
+if (CMAKE_CXX_COMPILER_ID MATCHES XL)
+ set_target_properties (qpidbroker PROPERTIES LINK_FLAGS -Wl,-bbigtoc)
+endif (CMAKE_CXX_COMPILER_ID MATCHES XL)
+
+if (MSVC)
+ set_target_properties (qpidbroker PROPERTIES COMPILE_FLAGS /wd4290)
+endif (MSVC)
+install (TARGETS qpidbroker
+ RUNTIME DESTINATION ${QPID_INSTALL_BINDIR} COMPONENT ${QPID_COMPONENT_BROKER}
+ LIBRARY DESTINATION ${QPID_INSTALL_LIBDIR} COMPONENT ${QPID_COMPONENT_BROKER}
+ ARCHIVE DESTINATION ${QPID_INSTALL_LIBDIR} COMPONENT ${QPID_COMPONENT_BROKER})
+install_pdb (qpidbroker ${QPID_COMPONENT_BROKER})
+
+
+set (qpidd_SOURCES
+ ${qpidd_platform_SOURCES}
+ qpidd.cpp
+ qpidd.h
+)
+add_msvc_version (qpidd application exe)
+add_executable (qpidd ${qpidd_SOURCES})
+target_link_libraries (qpidd qpidbroker qpidcommon)
+set_target_properties (qpidd PROPERTIES COMPILE_DEFINITIONS _IN_QPID_BROKER)
+install (TARGETS qpidd
+ RUNTIME DESTINATION ${QPID_INSTALL_SBINDIR} COMPONENT ${QPID_COMPONENT_BROKER}
+ LIBRARY DESTINATION ${QPID_INSTALL_LIBDIR} COMPONENT ${QPID_COMPONENT_BROKER}
+ ARCHIVE DESTINATION ${QPID_INSTALL_LIBDIR} COMPONENT ${QPID_COMPONENT_BROKER})
+if (CPACK_GENERATOR STREQUAL "NSIS")
+ set (CPACK_NSIS_MENU_LINKS
+ "qpidd" "Start Qpid Broker")
+endif (CPACK_GENERATOR STREQUAL "NSIS")
+
+if (NOT WIN32)
+ set (qmf2_platform_headers
+ ../include/qmf/posix/EventNotifier.h
+ )
+ set (qmf2_platform_sources
+ qmf/PosixEventNotifier.cpp
+ qmf/PosixEventNotifierImpl.cpp
+ )
+endif (NOT WIN32)
+
+ set (qmf2_HEADERS
+ ../include/qmf/AgentEvent.h
+ ../include/qmf/Agent.h
+ ../include/qmf/AgentSession.h
+ ../include/qmf/ConsoleEvent.h
+ ../include/qmf/ConsoleSession.h
+ ../include/qmf/DataAddr.h
+ ../include/qmf/Data.h
+ ../include/qmf/exceptions.h
+ ../include/qmf/Handle.h
+ ../include/qmf/ImportExport.h
+ ../include/qmf/Query.h
+ ../include/qmf/Schema.h
+ ../include/qmf/SchemaId.h
+ ../include/qmf/SchemaMethod.h
+ ../include/qmf/SchemaProperty.h
+ ../include/qmf/SchemaTypes.h
+ ../include/qmf/Subscription.h
+ ${qmf2_platform_headers}
+ )
+
+ set (qmf2_SOURCES
+ ${qmf2_HEADERS}
+ qmf/agentCapability.h
+ qmf/Agent.cpp
+ qmf/AgentEvent.cpp
+ qmf/AgentEventImpl.h
+ qmf/AgentImpl.h
+ qmf/AgentSession.cpp
+ qmf/AgentSubscription.cpp
+ qmf/AgentSubscription.h
+ qmf/ConsoleEvent.cpp
+ qmf/ConsoleEventImpl.h
+ qmf/ConsoleSession.cpp
+ qmf/ConsoleSessionImpl.h
+ qmf/constants.cpp
+ qmf/constants.h
+ qmf/DataAddr.cpp
+ qmf/DataAddrImpl.h
+ qmf/Data.cpp
+ qmf/DataImpl.h
+ qmf/EventNotifierImpl.h
+ qmf/EventNotifierImpl.cpp
+ qmf/exceptions.cpp
+ qmf/Expression.cpp
+ qmf/Expression.h
+ qmf/Hash.cpp
+ qmf/Hash.h
+ qmf/PrivateImplRef.h
+ qmf/Query.cpp
+ qmf/QueryImpl.h
+ qmf/Schema.cpp
+ qmf/SchemaCache.cpp
+ qmf/SchemaCache.h
+ qmf/SchemaId.cpp
+ qmf/SchemaIdImpl.h
+ qmf/SchemaImpl.h
+ qmf/SchemaMethod.cpp
+ qmf/SchemaMethodImpl.h
+ qmf/SchemaProperty.cpp
+ qmf/SchemaPropertyImpl.h
+ qmf/Subscription.cpp
+ qmf/SubscriptionImpl.h
+ ${qmf2_platform_sources}
+ )
+
+ add_msvc_version (qmf2 library dll)
+ add_library (qmf2 SHARED ${qmf2_SOURCES})
+ target_link_libraries (qmf2 qpidmessaging qpidtypes qpidclient qpidcommon)
+ set_target_properties (qmf2 PROPERTIES
+ VERSION ${qmf2_version}
+ SOVERSION ${qmf2_version_major})
+ install (TARGETS qmf2 OPTIONAL
+ RUNTIME DESTINATION ${QPID_INSTALL_BINDIR} COMPONENT ${QPID_COMPONENT_QMF}
+ LIBRARY DESTINATION ${QPID_INSTALL_LIBDIR} COMPONENT ${QPID_COMPONENT_QMF}
+ ARCHIVE DESTINATION ${QPID_INSTALL_LIBDIR} COMPONENT ${QPID_COMPONENT_QMF})
+ install (FILES ${qmf2_HEADERS}
+ DESTINATION ${QPID_INSTALL_INCLUDEDIR}/qmf
+ COMPONENT ${QPID_COMPONENT_QMF})
+ install_pdb (qmf2 ${QPID_COMPONENT_QMF})
+
+#
+# Legacy store
+#
+include (legacystore.cmake)
+#
+# Linear store
+#
+include (linearstore.cmake)
+
+# Now create the config file from all the info learned above.
+configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake
+ ${CMAKE_CURRENT_BINARY_DIR}/config.h)
+add_subdirectory(qpid/store)
+add_subdirectory(tests)
+add_subdirectory(tests/legacystore)
+add_subdirectory(tests/linearstore)
+
+# Support for pkg-config
+
+# Compatible variable names used in the pkg config files also for autoconf
+set (prefix ${CMAKE_INSTALL_PREFIX})
+set (exec_prefix ${CMAKE_INSTALL_PREFIX})
+set_absolute_install_path (libdir ${QPID_INSTALL_LIBDIR})
+set_absolute_install_path (includedir ${QPID_INSTALL_INCLUDEDIR})
+set (VERSION ${QPID_VERSION_FULL})
+
+#add_custom_target(pkgconfig ALL echo DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/qpid.pc)
+#add_dependencies(pkgconfig ${CMAKE_CURRENT_BINARY_DIR}/qmf2.pc)
+configure_file(qpid.pc.in ${CMAKE_CURRENT_BINARY_DIR}/qpid.pc @ONLY)
+configure_file(qmf2.pc.in ${CMAKE_CURRENT_BINARY_DIR}/qmf2.pc @ONLY)
+install (FILES ${CMAKE_CURRENT_BINARY_DIR}/qpid.pc ${CMAKE_CURRENT_BINARY_DIR}/qmf2.pc
+ DESTINATION ${QPID_INSTALL_LIBDIR}/pkgconfig
+ COMPONENT ${QPID_COMPONENT_COMMON})
+
+if (DEFINED CMAKE_IMPORT_LIBRARY_PREFIX)
+set(QPIDMSGLIB ${CMAKE_IMPORT_LIBRARY_PREFIX}qpidmessaging${CMAKE_IMPORT_LIBRARY_SUFFIX})
+set(QPIDMSGLIBDEBUG ${CMAKE_IMPORT_LIBRARY_PREFIX}qpidmessaging${CMAKE_DEBUG_POSTFIX}${CMAKE_IMPORT_LIBRARY_SUFFIX})
+set(QPIDTYPESLIB ${CMAKE_IMPORT_LIBRARY_PREFIX}qpidtypes${CMAKE_IMPORT_LIBRARY_SUFFIX})
+set(QPIDTYPESLIBDEBUG ${CMAKE_IMPORT_LIBRARY_PREFIX}qpidtypes${CMAKE_DEBUG_POSTFIX}${CMAKE_IMPORT_LIBRARY_SUFFIX})
+else ()
+set(QPIDMSGLIB ${CMAKE_SHARED_LIBRARY_PREFIX}qpidmessaging${CMAKE_SHARED_LIBRARY_SUFFIX})
+set(QPIDMSGLIBDEBUG ${CMAKE_SHARED_LIBRARY_PREFIX}qpidmessaging${CMAKE_DEBUG_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX})
+set(QPIDTYPESLIB ${CMAKE_SHARED_LIBRARY_PREFIX}qpidtypes${CMAKE_SHARED_LIBRARY_SUFFIX})
+set(QPIDTYPESLIBDEBUG ${CMAKE_SHARED_LIBRARY_PREFIX}qpidtypes${CMAKE_DEBUG_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX})
+endif ()
+
+configure_file(QpidConfig.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/QpidConfig.cmake @ONLY)
+configure_file(QpidConfigVersion.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/QpidConfigVersion.cmake @ONLY)
+install (FILES
+ ${CMAKE_CURRENT_BINARY_DIR}/QpidConfig.cmake
+ ${CMAKE_CURRENT_BINARY_DIR}/QpidConfigVersion.cmake
+ DESTINATION ${QPID_INSTALL_LIBDIR}/cmake/Qpid
+ COMPONENT ${QPID_COMPONENT_COMMON})
+
diff --git a/qpid/cpp/src/CMakeWinVersions.cmake b/qpid/cpp/src/CMakeWinVersions.cmake
new file mode 100644
index 0000000000..0bac7cab47
--- /dev/null
+++ b/qpid/cpp/src/CMakeWinVersions.cmake
@@ -0,0 +1,57 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+#
+# Versions settings overrides for Windows dll/exe file version resource.
+# These values are compiled into the dll and exe files.
+#
+# The settings override precedence from lowest to highest:
+# 1. CPACK settings from cpp/CMakeLists.txt
+# 2. Global settings from this file
+# 3. Command line version number (only) from add_msvc_version_full call
+# 4. Per-project settings from this file
+#
+
+#
+# Specification of global settings for all projects.
+#
+# set ("winver_PACKAGE_NAME" "qpid-cpp")
+# set ("winver_DESCRIPTION_SUMMARY" "Apache Qpid C++")
+# set ("winver_FILE_VERSION_N1" "0")
+# set ("winver_FILE_VERSION_N2" "11")
+# set ("winver_FILE_VERSION_N3" "0")
+# set ("winver_FILE_VERSION_N4" "0")
+# set ("winver_PRODUCT_VERSION_N1" "0")
+# set ("winver_PRODUCT_VERSION_N2" "11")
+# set ("winver_PRODUCT_VERSION_N3" "0")
+# set ("winver_PRODUCT_VERSION_N4" "0")
+# set ("winver_LEGAL_COPYRIGHT" "")
+
+#
+# Specification of per-project settings:
+#
+# set ("winver_${projectName}_FileVersionBinary" "0,11,0,0")
+# set ("winver_${projectName}_ProductVersionBinary" "0,11,0,0")
+# set ("winver_${projectName}_FileVersionString" "0, 11, 0, 0")
+# set ("winver_${projectName}_ProductVersionString" "0, 11, 0, 0")
+# set ("winver_${projectName}_FileDescription" "qpid-cpp-qpidcommon Library")
+# set ("winver_${projectName}_LegalCopyright" "")
+# set ("winver_${projectName}_InternalName" "qpidcommon")
+# set ("winver_${projectName}_OriginalFilename" "qpidcommon.dll")
+# set ("winver_${projectName}_ProductName" "Apache Qpid C++")
diff --git a/qpid/cpp/src/QpidConfig.cmake.in b/qpid/cpp/src/QpidConfig.cmake.in
new file mode 100644
index 0000000000..3f84e3b6b0
--- /dev/null
+++ b/qpid/cpp/src/QpidConfig.cmake.in
@@ -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.
+#
+
+# Name: Qpid
+# Description: Qpid Client C library
+# Version: @VERSION@
+# URL: http://qpid.apache.org/
+
+set (Qpid_VERSION @VERSION@)
+
+set (Qpid_INCLUDE_DIRS @includedir@)
+set (Qpid_LIBRARIES optimized @libdir@/@QPIDMSGLIB@ @libdir@/@QPIDTYPESLIB@ debug @libdir@/@QPIDMSGLIBDEBUG@ @libdir@/@QPIDTYPESLIBDEBUG@)
+
+set (Qpid_FOUND True)
diff --git a/qpid/cpp/src/QpidConfigVersion.cmake.in b/qpid/cpp/src/QpidConfigVersion.cmake.in
new file mode 100644
index 0000000000..d85924ab7e
--- /dev/null
+++ b/qpid/cpp/src/QpidConfigVersion.cmake.in
@@ -0,0 +1,30 @@
+# This is a basic version file for the Config-mode of find_package().
+# It is used by write_basic_package_version_file() as input file for configure_file()
+# to create a version-file which can be installed along a config.cmake file.
+#
+# The created file sets PACKAGE_VERSION_EXACT if the current version string and
+# the requested version string are exactly the same and it sets
+# PACKAGE_VERSION_COMPATIBLE if the current version is >= requested version.
+
+set(PACKAGE_VERSION "@VERSION@")
+
+if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}" )
+ set(PACKAGE_VERSION_COMPATIBLE FALSE)
+else()
+ set(PACKAGE_VERSION_COMPATIBLE TRUE)
+ if( "${PACKAGE_FIND_VERSION}" STREQUAL "${PACKAGE_VERSION}")
+ set(PACKAGE_VERSION_EXACT TRUE)
+ endif()
+endif()
+
+# if the installed or the using project don't have CMAKE_SIZEOF_VOID_P set, ignore it:
+if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "" OR "@CMAKE_SIZEOF_VOID_P@" STREQUAL "")
+ return()
+endif()
+
+# check that the installed version has the same 32/64bit-ness as the one which is currently searching:
+if(NOT "${CMAKE_SIZEOF_VOID_P}" STREQUAL "@CMAKE_SIZEOF_VOID_P@")
+ math(EXPR installedBits "@CMAKE_SIZEOF_VOID_P@ * 8")
+ set(PACKAGE_VERSION "${PACKAGE_VERSION} (${installedBits}bit)")
+ set(PACKAGE_VERSION_UNSUITABLE TRUE)
+endif()
diff --git a/qpid/cpp/src/amqp.cmake b/qpid/cpp/src/amqp.cmake
new file mode 100644
index 0000000000..9e2bf75ac4
--- /dev/null
+++ b/qpid/cpp/src/amqp.cmake
@@ -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.
+#
+
+# Optional AMQP1.0 support. Requires proton toolkit.
+
+find_package(Proton 0.7)
+
+set (amqp_default ${amqp_force})
+set (maximum_version 0.9)
+if (Proton_FOUND)
+ if (Proton_VERSION VERSION_GREATER ${maximum_version})
+ message(WARNING "Qpid proton ${Proton_VERSION} is not a tested version and might not be compatible, ${maximum_version} is highest tested; build may not work")
+ endif (Proton_VERSION VERSION_GREATER ${maximum_version})
+ message(STATUS "Qpid proton found, amqp 1.0 support enabled")
+ set (amqp_default ON)
+ if (Proton_VERSION VERSION_GREATER 0.7)
+ set (USE_PROTON_TRANSPORT_CONDITION 1)
+ set (HAVE_PROTON_EVENTS 1)
+ endif (Proton_VERSION VERSION_GREATER 0.7)
+ if (Proton_VERSION VERSION_GREATER 0.8)
+ set (NO_PROTON_DELIVERY_TAG_T 1)
+ endif (Proton_VERSION VERSION_GREATER 0.8)
+else ()
+ message(STATUS "Qpid proton not found, amqp 1.0 support not enabled")
+endif ()
+
+option(BUILD_AMQP "Build with support for AMQP 1.0" ${amqp_default})
+if (BUILD_AMQP)
+
+ if (NOT Proton_FOUND)
+ message(FATAL_ERROR "Qpid proton not found, required for amqp 1.0 support")
+ endif ()
+
+ set (amqp_SOURCES
+ qpid/broker/amqp/Authorise.h
+ qpid/broker/amqp/Authorise.cpp
+ qpid/broker/amqp/BrokerContext.h
+ qpid/broker/amqp/BrokerContext.cpp
+ qpid/broker/amqp/Connection.h
+ qpid/broker/amqp/Connection.cpp
+ qpid/broker/amqp/DataReader.h
+ qpid/broker/amqp/DataReader.cpp
+ qpid/broker/amqp/Domain.h
+ qpid/broker/amqp/Domain.cpp
+ qpid/broker/amqp/Exception.h
+ qpid/broker/amqp/Exception.cpp
+ qpid/broker/amqp/Filter.h
+ qpid/broker/amqp/Filter.cpp
+ qpid/broker/amqp/Header.h
+ qpid/broker/amqp/Header.cpp
+ qpid/broker/amqp/Incoming.h
+ qpid/broker/amqp/Incoming.cpp
+ qpid/broker/amqp/Interconnect.h
+ qpid/broker/amqp/Interconnect.cpp
+ qpid/broker/amqp/Interconnects.h
+ qpid/broker/amqp/Interconnects.cpp
+ qpid/broker/amqp/ManagedConnection.h
+ qpid/broker/amqp/ManagedConnection.cpp
+ qpid/broker/amqp/ManagedSession.h
+ qpid/broker/amqp/ManagedSession.cpp
+ qpid/broker/amqp/ManagedIncomingLink.h
+ qpid/broker/amqp/ManagedIncomingLink.cpp
+ qpid/broker/amqp/ManagedOutgoingLink.h
+ qpid/broker/amqp/ManagedOutgoingLink.cpp
+ qpid/broker/amqp/Message.h
+ qpid/broker/amqp/Message.cpp
+ qpid/broker/amqp/NodePolicy.h
+ qpid/broker/amqp/NodePolicy.cpp
+ qpid/broker/amqp/NodeProperties.h
+ qpid/broker/amqp/NodeProperties.cpp
+ qpid/broker/amqp/Outgoing.h
+ qpid/broker/amqp/Outgoing.cpp
+ qpid/broker/amqp/ProtocolPlugin.cpp
+ qpid/broker/amqp/Relay.h
+ qpid/broker/amqp/Relay.cpp
+ qpid/broker/amqp/Sasl.h
+ qpid/broker/amqp/Sasl.cpp
+ qpid/broker/amqp/SaslClient.h
+ qpid/broker/amqp/SaslClient.cpp
+ qpid/broker/amqp/Session.h
+ qpid/broker/amqp/Session.cpp
+ qpid/broker/amqp/Topic.h
+ qpid/broker/amqp/Topic.cpp
+ qpid/broker/amqp/Translation.h
+ qpid/broker/amqp/Translation.cpp
+ )
+
+ include_directories(${Proton_INCLUDE_DIRS})
+
+ add_library (amqp MODULE ${amqp_SOURCES})
+ target_link_libraries (amqp qpidtypes qpidbroker qpidcommon ${Proton_LIBRARIES})
+ set_target_properties (amqp PROPERTIES
+ PREFIX ""
+ COMPILE_DEFINITIONS _IN_QPID_BROKER)
+
+ install (TARGETS amqp
+ DESTINATION ${QPIDD_MODULE_DIR}
+ COMPONENT ${QPID_COMPONENT_BROKER})
+
+ set (amqpc_SOURCES
+ qpid/messaging/amqp/AddressHelper.h
+ qpid/messaging/amqp/AddressHelper.cpp
+ qpid/messaging/amqp/ConnectionContext.h
+ qpid/messaging/amqp/ConnectionContext.cpp
+ qpid/messaging/amqp/ConnectionHandle.h
+ qpid/messaging/amqp/ConnectionHandle.cpp
+ qpid/messaging/amqp/DriverImpl.h
+ qpid/messaging/amqp/DriverImpl.cpp
+ qpid/messaging/amqp/PnData.h
+ qpid/messaging/amqp/PnData.cpp
+ qpid/messaging/amqp/ReceiverContext.h
+ qpid/messaging/amqp/ReceiverContext.cpp
+ qpid/messaging/amqp/ReceiverHandle.h
+ qpid/messaging/amqp/ReceiverHandle.cpp
+ qpid/messaging/amqp/Sasl.h
+ qpid/messaging/amqp/Sasl.cpp
+ qpid/messaging/amqp/SenderContext.h
+ qpid/messaging/amqp/SenderContext.cpp
+ qpid/messaging/amqp/SenderHandle.h
+ qpid/messaging/amqp/SenderHandle.cpp
+ qpid/messaging/amqp/SessionContext.h
+ qpid/messaging/amqp/SessionContext.cpp
+ qpid/messaging/amqp/SessionHandle.h
+ qpid/messaging/amqp/SessionHandle.cpp
+ qpid/messaging/amqp/TcpTransport.h
+ qpid/messaging/amqp/TcpTransport.cpp
+ qpid/messaging/amqp/Transaction.h
+ qpid/messaging/amqp/Transaction.cpp
+ qpid/messaging/amqp/util.h
+ qpid/messaging/amqp/util.cpp
+ )
+
+ if (WIN32)
+ list (APPEND amqp_SOURCES qpid/messaging/amqp/windows/SslTransport.cpp)
+ list (APPEND amqpc_SOURCES qpid/messaging/amqp/windows/SslTransport.cpp)
+ endif (WIN32)
+else (BUILD_AMQP)
+ # ensure that qpid build ignores proton
+ UNSET( amqpc_SOURCES )
+ UNSET( PROTON_LIBRARIES )
+endif (BUILD_AMQP)
diff --git a/qpid/cpp/src/check-abi b/qpid/cpp/src/check-abi
new file mode 100755
index 0000000000..3e85dab862
--- /dev/null
+++ b/qpid/cpp/src/check-abi
@@ -0,0 +1,105 @@
+#!/usr/bin/env 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.
+#
+
+MKTEMP="mktemp /tmp/tmp.XXXXXXXXXX"
+
+# Ask the compiler the implementation specific type for a standard typedeffed type
+# (int64_t, size_t etc.). Operates by test compiling and using the demangling ABI call.
+#
+# This works for gcc and clang on Unix.
+full_type_of () {
+ prog=$($MKTEMP)
+ trap "rm $prog" EXIT
+
+ ${CXX:-g++} -x c++ -o $prog - <<END-FILE
+#include <stdint.h>
+#include <stdlib.h>
+#include <cxxabi.h>
+#include <iostream>
+#include <typeinfo>
+
+int main() {
+ int status;
+ char* printable_type =
+ __cxxabiv1::__cxa_demangle(typeid($1).name(), 0, 0, &status);
+ if (printable_type) {
+ std::cout << printable_type;
+ } else {
+ std::cout << "$1";
+ }
+ ::free(printable_type);
+}
+END-FILE
+$prog
+}
+
+rc=0
+syms_desired=$($MKTEMP)
+syms_library=$($MKTEMP)
+syms_missing=$($MKTEMP)
+syms_extra=$($MKTEMP)
+
+trap 'rm $syms_desired $syms_library $syms_missing $syms_extra' EXIT
+
+CXX=$1
+export CXX
+
+LC_ALL=C
+export LC_ALL
+
+# Extract exported symbols from library
+nm -DC --defined-only -f s $2 | cut -f1 -d'|' -s | sort -u > $syms_library
+
+# Process API syms (substitute in some typedefs etc.)
+sed -e "
+ s/uint64_t/$(full_type_of uint64_t)/
+ s/uint32_t/unsigned int/
+ s/uint16_t/unsigned short/
+ s/uint8_t/unsigned char/
+ s/size_t/$(full_type_of size_t)/
+ s/int64_t/$(full_type_of int64_t)/
+ s/int32_t/int/
+ s/int16_t/short/
+ s/int8_t/signed char/
+ s/qpid::types::Variant::Map/std::map<std::string, qpid::types::Variant, std::less<std::string>, std::allocator<std::pair<std::string const, qpid::types::Variant> > >/
+ s/qpid::types::Variant::List/std::list<qpid::types::Variant, std::allocator<qpid::types::Variant> >/
+ /^\$/d
+ /^#.*\$/d
+" $3 | sort -u > $syms_desired
+
+comm -23 $syms_desired $syms_library > $syms_missing
+comm -13 $syms_desired $syms_library > $syms_extra
+
+if [ -n "$(cat $syms_missing)" ] ; then
+ (echo "Not exported from library (should be)"
+ echo "====================================="
+ cat $syms_missing ) 1>&2
+ rc=1
+fi
+
+
+if [ -n "$(cat $syms_extra)" ]; then
+ (echo "Exported by library but not in spec"
+ echo "==================================="
+ cat $syms_extra ) 1>&2
+fi
+
+exit $rc
diff --git a/qpid/cpp/src/config.h.cmake b/qpid/cpp/src/config.h.cmake
new file mode 100644
index 0000000000..1e1f4087ee
--- /dev/null
+++ b/qpid/cpp/src/config.h.cmake
@@ -0,0 +1,63 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/*
+ * This file is automatically generated and will be overwritten by the
+ * next CMake invocation.
+ */
+
+#ifndef QPID_CONFIG_H
+#define QPID_CONFIG_H
+
+// PACKAGE_NAME and PACKAGE_VERSION are carry-overs from the autoconf world.
+// They tend to cause confusion and problems when mixing headers from multiple
+// autoconf-configured packages, so it's best to remove these in favor of
+// Qpid-specific names as soon as the autoconf stuff is removed.
+#define PACKAGE_NAME "${CMAKE_PROJECT_NAME}"
+#define PACKAGE_VERSION "${qpidc_version}"
+
+#cmakedefine QPIDC_CONF_FILE "${QPIDC_CONF_FILE}"
+#cmakedefine QPIDD_CONF_FILE "${QPIDD_CONF_FILE}"
+
+#cmakedefine QPIDC_MODULE_DIR "${QPIDC_MODULE_DIR}"
+#cmakedefine QPIDD_MODULE_DIR "${QPIDD_MODULE_DIR}"
+
+#define QPID_SHLIB_PREFIX "${CMAKE_SHARED_LIBRARY_PREFIX}"
+#define QPID_MODULE_PREFIX
+#cmakedefine QPID_DEBUG_POSTFIX "${QPID_DEBUG_POSTFIX}"
+#if defined(QPID_DEBUG_POSTFIX) && defined (_DEBUG)
+# define QPID_SHLIB_POSTFIX QPID_DEBUG_POSTFIX
+# define QPID_MODULE_POSTFIX QPID_DEBUG_POSTFIX
+#else
+# define QPID_SHLIB_POSTFIX
+# define QPID_MODULE_POSTFIX
+#endif
+#define QPID_SHLIB_SUFFIX "${CMAKE_SHARED_LIBRARY_SUFFIX}"
+#define QPID_MODULE_SUFFIX "${CMAKE_SHARED_LIBRARY_SUFFIX}"
+
+#define BROKER_SASL_NAME "${QPID_BROKER_SASL_NAME}"
+#cmakedefine HAVE_SASL ${HAVE_SASL}
+
+#cmakedefine HAVE_SYS_SDT_H ${HAVE_SYS_SDT_H}
+#cmakedefine HAVE_LOG_AUTHPRIV
+#cmakedefine HAVE_LOG_FTP
+#cmakedefine QPID_SIZE_T_DISTINCT
+#cmakedefine USE_PROTON_TRANSPORT_CONDITION
+#cmakedefine HAVE_PROTON_EVENTS
+#cmakedefine NO_PROTON_DELIVERY_TAG_T
+#endif /* QPID_CONFIG_H */
diff --git a/qpid/cpp/src/finddb.cmake b/qpid/cpp/src/finddb.cmake
new file mode 100644
index 0000000000..2f2f94f469
--- /dev/null
+++ b/qpid/cpp/src/finddb.cmake
@@ -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.
+#
+#
+
+if(UNIX)
+# - Find BerkeleyDB
+# Find the BerkeleyDB includes and library
+# This module defines
+# DB_CXX_INCLUDE_DIR, where to find db_cxx.h, etc.
+# DB_LIBRARIES, the libraries needed to use BerkeleyDB.
+# DB_FOUND, If false, do not try to use BerkeleyDB.
+# also defined, but not for general use are
+# DB_LIBRARY, where to find the BerkeleyDB library.
+
+FIND_PATH(DB_CXX_INCLUDE_DIR db_cxx.h
+ /usr/local/include/db4
+ /usr/local/include/libdb4
+ /usr/local/include
+ /usr/include/db4
+ /usr/include/libdb4
+ /usr/include
+)
+
+SET(DB_NAMES ${DB_NAMES} db_cxx db_cxx-4)
+FIND_LIBRARY(DB_LIBRARY
+ NAMES ${DB_NAMES}
+ PATHS /usr/lib /usr/local/lib
+)
+
+IF (DB_LIBRARY AND DB_CXX_INCLUDE_DIR)
+ SET(DB_LIBRARIES ${DB_LIBRARY})
+ SET(DB_FOUND "YES")
+ELSE (DB_LIBRARY AND DB_CXX_INCLUDE_DIR)
+ UNSET( DB_FOUND )
+ENDIF (DB_LIBRARY AND DB_CXX_INCLUDE_DIR)
+
+
+IF (DB_FOUND)
+ IF (NOT DB_FIND_QUIETLY)
+ MESSAGE(STATUS "Found BerkeleyDB: ${DB_LIBRARIES}")
+ ENDIF (NOT DB_FIND_QUIETLY)
+ELSE (DB_FOUND)
+ IF (DB_FIND_REQUIRED)
+ MESSAGE(FATAL_ERROR "Could not find BerkeleyDB library")
+ ENDIF (DB_FIND_REQUIRED)
+ENDIF (DB_FOUND)
+
+# Deprecated declarations.
+SET (NATIVE_DB_INCLUDE_PATH ${DB_CXX_INCLUDE_DIR} )
+GET_FILENAME_COMPONENT (NATIVE_DB_LIB_PATH ${DB_LIBRARY} PATH)
+
+MARK_AS_ADVANCED(
+ DB_LIBRARY
+ DB_CXX_INCLUDE_DIR
+)
+
+else(UNIX)
+ MESSAGE(STATUS "BerkeleyDB is ignored on non-Unix platforms")
+ UNSET( DB_FOUND )
+endif(UNIX)
diff --git a/qpid/cpp/src/legacystore.cmake b/qpid/cpp/src/legacystore.cmake
new file mode 100644
index 0000000000..3cb1171b00
--- /dev/null
+++ b/qpid/cpp/src/legacystore.cmake
@@ -0,0 +1,172 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+#
+# Legacy store library CMake fragment, to be included in CMakeLists.txt
+#
+
+if (DEFINED legacystore_force)
+ set (legacystore_default ${legacystore_force})
+else (DEFINED legacystore_force)
+ set (legacystore_default OFF)
+ if (UNIX)
+ #
+ # Find required BerkeleyDB
+ #
+ include (finddb.cmake)
+ if (DB_FOUND)
+ #
+ # find libaio
+ #
+ CHECK_LIBRARY_EXISTS (aio io_queue_init "" HAVE_AIO)
+ CHECK_INCLUDE_FILES (libaio.h HAVE_AIO_H)
+ if (HAVE_AIO AND HAVE_AIO_H)
+ #
+ # allow legacystore to be built
+ #
+ message(STATUS "BerkeleyDB for C++ and libaio found, Legacystore support disabled by default (deprecated, use linearstore instead).")
+ set (legacystore_default OFF) # Disabled, deprecated. Use linearstore instead.
+ else (HAVE_AIO AND HAVE_AIO_H)
+ if (NOT HAVE_AIO)
+ message(STATUS "Legacystore requires libaio which is absent.")
+ endif (NOT HAVE_AIO)
+ if (NOT HAVE_AIO_H)
+ message(STATUS "Legacystore requires libaio.h which is absent.")
+ endif (NOT HAVE_AIO_H)
+ endif (HAVE_AIO AND HAVE_AIO_H)
+ else (DB_FOUND)
+ message(STATUS "Legacystore requires BerkeleyDB for C++ which is absent.")
+ endif (DB_FOUND)
+ endif (UNIX)
+endif (DEFINED legacystore_force)
+
+option(BUILD_LEGACYSTORE "Build legacystore persistent store" ${legacystore_default})
+
+if (BUILD_LEGACYSTORE)
+ if (NOT UNIX)
+ message(FATAL_ERROR "Legacystore produced only on Unix platforms")
+ endif (NOT UNIX)
+ if (NOT DB_FOUND)
+ message(FATAL_ERROR "Legacystore requires BerkeleyDB for C++ which is absent.")
+ endif (NOT DB_FOUND)
+ if (NOT HAVE_AIO)
+ message(FATAL_ERROR "Legacystore requires libaio which is absent.")
+ endif (NOT HAVE_AIO)
+ if (NOT HAVE_AIO_H)
+ message(FATAL_ERROR "Legacystore requires libaio.h which is absent.")
+ endif (NOT HAVE_AIO_H)
+
+ # Journal source files
+ set (legacy_jrnl_SOURCES
+ qpid/legacystore/jrnl/aio.cpp
+ qpid/legacystore/jrnl/cvar.cpp
+ qpid/legacystore/jrnl/data_tok.cpp
+ qpid/legacystore/jrnl/deq_rec.cpp
+ qpid/legacystore/jrnl/enq_map.cpp
+ qpid/legacystore/jrnl/enq_rec.cpp
+ qpid/legacystore/jrnl/fcntl.cpp
+ qpid/legacystore/jrnl/jcntl.cpp
+ qpid/legacystore/jrnl/jdir.cpp
+ qpid/legacystore/jrnl/jerrno.cpp
+ qpid/legacystore/jrnl/jexception.cpp
+ qpid/legacystore/jrnl/jinf.cpp
+ qpid/legacystore/jrnl/jrec.cpp
+ qpid/legacystore/jrnl/lp_map.cpp
+ qpid/legacystore/jrnl/lpmgr.cpp
+ qpid/legacystore/jrnl/pmgr.cpp
+ qpid/legacystore/jrnl/rmgr.cpp
+ qpid/legacystore/jrnl/rfc.cpp
+ qpid/legacystore/jrnl/rrfc.cpp
+ qpid/legacystore/jrnl/slock.cpp
+ qpid/legacystore/jrnl/smutex.cpp
+ qpid/legacystore/jrnl/time_ns.cpp
+ qpid/legacystore/jrnl/txn_map.cpp
+ qpid/legacystore/jrnl/txn_rec.cpp
+ qpid/legacystore/jrnl/wmgr.cpp
+ qpid/legacystore/jrnl/wrfc.cpp
+ )
+
+ # legacyStore source files
+ set (legacy_store_SOURCES
+ qpid/legacystore/StorePlugin.cpp
+ qpid/legacystore/BindingDbt.cpp
+ qpid/legacystore/BufferValue.cpp
+ qpid/legacystore/DataTokenImpl.cpp
+ qpid/legacystore/IdDbt.cpp
+ qpid/legacystore/IdSequence.cpp
+ qpid/legacystore/JournalImpl.cpp
+ qpid/legacystore/MessageStoreImpl.cpp
+ qpid/legacystore/PreparedTransaction.cpp
+ qpid/legacystore/TxnCtxt.cpp
+ )
+
+ set (legacystore_defines _IN_QPID_BROKER RHM_CLEAN)
+
+ if(NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/db-inc.h)
+ message(STATUS "Including BDB from ${DB_CXX_INCLUDE_DIR}/db_cxx.h")
+ file(WRITE
+ ${CMAKE_CURRENT_BINARY_DIR}/db-inc.h
+ "#include <${DB_CXX_INCLUDE_DIR}/db_cxx.h>\n")
+ endif()
+
+ add_library (legacystore MODULE
+ ${legacy_jrnl_SOURCES}
+ ${legacy_store_SOURCES}
+ ${legacy_qmf_SOURCES}
+ )
+
+ set_target_properties (legacystore PROPERTIES
+ PREFIX ""
+ COMPILE_DEFINITIONS "${legacystore_defines}"
+ OUTPUT_NAME legacystore
+ )
+
+ target_link_libraries (legacystore
+ ${clock_gettime_LIB}
+ aio
+ uuid
+ qpidcommon qpidtypes qpidbroker
+ ${DB_LIBRARY}
+ )
+
+ # For use in the store tests only
+ add_library (legacystore_shared SHARED
+ ${legacy_jrnl_SOURCES}
+ ${legacy_store_SOURCES}
+ ${legacy_qmf_SOURCES}
+ )
+
+ set_target_properties (legacystore_shared PROPERTIES
+ COMPILE_DEFINITIONS "${legacystore_defines}"
+ )
+
+ target_link_libraries (legacystore_shared
+ ${clock_gettime_LIB}
+ aio
+ uuid
+ qpidcommon qpidtypes qpidbroker
+ ${DB_LIBRARY}
+ )
+
+install(TARGETS legacystore
+ DESTINATION ${QPIDD_MODULE_DIR}
+ COMPONENT ${QPID_COMPONENT_BROKER})
+
+else (BUILD_LEGACYSTORE)
+ message(STATUS "Legacystore is excluded from build.")
+endif (BUILD_LEGACYSTORE)
diff --git a/qpid/cpp/src/libqpidmessaging-api-symbols.txt b/qpid/cpp/src/libqpidmessaging-api-symbols.txt
new file mode 100644
index 0000000000..23634857c2
--- /dev/null
+++ b/qpid/cpp/src/libqpidmessaging-api-symbols.txt
@@ -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.
+#
+
+# Address
+qpid::messaging::Address::Address()
+qpid::messaging::Address::Address(std::string const&)
+qpid::messaging::Address::Address(std::string const&, std::string const&, qpid::types::Variant::Map const&, std::string const&)
+qpid::messaging::Address::Address(qpid::messaging::Address const&)
+qpid::messaging::Address::~Address()
+qpid::messaging::Address::operator=(qpid::messaging::Address const&)
+qpid::messaging::Address::getName() const
+qpid::messaging::Address::setName(std::string const&)
+qpid::messaging::Address::getSubject() const
+qpid::messaging::Address::setSubject(std::string const&)
+qpid::messaging::Address::getOptions() const
+qpid::messaging::Address::getOptions()
+qpid::messaging::Address::setOptions(qpid::types::Variant::Map const&)
+qpid::messaging::Address::getType() const
+qpid::messaging::Address::setType(std::string const&)
+qpid::messaging::Address::str() const
+qpid::messaging::Address::operator bool() const
+qpid::messaging::Address::operator!() const
+
+qpid::messaging::operator<<(std::ostream&, qpid::messaging::Address const&)
+
+# Connection
+qpid::messaging::Connection::Connection(qpid::messaging::ConnectionImpl*)
+qpid::messaging::Connection::Connection(qpid::messaging::Connection const&)
+qpid::messaging::Connection::Connection()
+qpid::messaging::Connection::Connection(std::string const&, qpid::types::Variant::Map const&)
+qpid::messaging::Connection::Connection(std::string const&, std::string const&)
+qpid::messaging::Connection::~Connection()
+qpid::messaging::Connection::operator=(qpid::messaging::Connection const&)
+qpid::messaging::Connection::setOption(std::string const&, qpid::types::Variant const&)
+qpid::messaging::Connection::open()
+qpid::messaging::Connection::isOpen()
+qpid::messaging::Connection::isOpen() const
+qpid::messaging::Connection::close()
+qpid::messaging::Connection::createTransactionalSession(std::string const&)
+qpid::messaging::Connection::createSession(std::string const&)
+qpid::messaging::Connection::getSession(std::string const&) const
+qpid::messaging::Connection::getAuthenticatedUsername()
+
+# Duration
+qpid::messaging::Duration::Duration(uint64_t)
+qpid::messaging::Duration::getMilliseconds() const
+qpid::messaging::Duration::FOREVER
+qpid::messaging::Duration::IMMEDIATE
+qpid::messaging::Duration::SECOND
+qpid::messaging::Duration::MINUTE
+
+qpid::messaging::operator*(qpid::messaging::Duration const&, uint64_t)
+qpid::messaging::operator*(uint64_t, qpid::messaging::Duration const&)
+qpid::messaging::operator==(qpid::messaging::Duration const&, qpid::messaging::Duration const&)
+qpid::messaging::operator!=(qpid::messaging::Duration const&, qpid::messaging::Duration const&)
+
+# FailoverUpdates (this is a very strange class - more like a property of a Connection)
+qpid::messaging::FailoverUpdates::FailoverUpdates(qpid::messaging::Connection&)
+qpid::messaging::FailoverUpdates::~FailoverUpdates()
+
+# Message
+qpid::messaging::Message::Message(std::string const&)
+qpid::messaging::Message::Message(char const*, size_t)
+qpid::messaging::Message::Message(qpid::messaging::Message const&)
+qpid::messaging::Message::Message(qpid::types::Variant&)
+qpid::messaging::Message::~Message()
+qpid::messaging::Message::operator=(qpid::messaging::Message const&)
+qpid::messaging::Message::setReplyTo(qpid::messaging::Address const&)
+qpid::messaging::Message::getReplyTo() const
+qpid::messaging::Message::setSubject(std::string const&)
+qpid::messaging::Message::getSubject() const
+qpid::messaging::Message::setContentType(std::string const&)
+qpid::messaging::Message::getContentType() const
+qpid::messaging::Message::setMessageId(std::string const&)
+qpid::messaging::Message::getMessageId() const
+qpid::messaging::Message::setUserId(std::string const&)
+qpid::messaging::Message::getUserId() const
+qpid::messaging::Message::setCorrelationId(std::string const&)
+qpid::messaging::Message::getCorrelationId() const
+qpid::messaging::Message::setPriority(uint8_t)
+qpid::messaging::Message::getPriority() const
+qpid::messaging::Message::setTtl(qpid::messaging::Duration)
+qpid::messaging::Message::getTtl() const
+qpid::messaging::Message::setDurable(bool)
+qpid::messaging::Message::getDurable() const
+qpid::messaging::Message::getRedelivered() const
+qpid::messaging::Message::setRedelivered(bool)
+qpid::messaging::Message::getProperties() const
+qpid::messaging::Message::getProperties()
+qpid::messaging::Message::setContent(std::string const&)
+qpid::messaging::Message::setContent(char const*, size_t)
+qpid::messaging::Message::getContent() const
+qpid::messaging::Message::setContentBytes(std::string const&)
+qpid::messaging::Message::getContentBytes() const
+qpid::messaging::Message::setContentObject(qpid::types::Variant const&)
+qpid::messaging::Message::getContentObject()
+qpid::messaging::Message::getContentObject() const
+qpid::messaging::Message::getContentPtr() const
+qpid::messaging::Message::getContentSize() const
+qpid::messaging::Message::setProperty(std::string const&, qpid::types::Variant const&)
+
+# Logger
+qpid::messaging::Logger::configure(int, char const**, std::string const&)
+qpid::messaging::Logger::log(qpid::messaging::Level, char const*, int, char const*, std::string const&)
+qpid::messaging::Logger::setOutput(qpid::messaging::LoggerOutput&)
+qpid::messaging::Logger::usage()
+
+qpid::messaging::LoggerOutput::~LoggerOutput()
+
+# Receiver
+qpid::messaging::Receiver::Receiver(qpid::messaging::ReceiverImpl*)
+qpid::messaging::Receiver::Receiver(qpid::messaging::Receiver const&)
+qpid::messaging::Receiver::~Receiver()
+qpid::messaging::Receiver::operator=(qpid::messaging::Receiver const&)
+qpid::messaging::Receiver::get(qpid::messaging::Message&, qpid::messaging::Duration)
+qpid::messaging::Receiver::get(qpid::messaging::Duration)
+qpid::messaging::Receiver::fetch(qpid::messaging::Message&, qpid::messaging::Duration)
+qpid::messaging::Receiver::fetch(qpid::messaging::Duration)
+qpid::messaging::Receiver::setCapacity(uint32_t)
+qpid::messaging::Receiver::getCapacity()
+qpid::messaging::Receiver::getAvailable()
+qpid::messaging::Receiver::getUnsettled()
+qpid::messaging::Receiver::close()
+qpid::messaging::Receiver::isClosed() const
+qpid::messaging::Receiver::getName() const
+qpid::messaging::Receiver::getSession() const
+qpid::messaging::Receiver::getAddress() const
+
+# Sender
+qpid::messaging::Sender::Sender(qpid::messaging::SenderImpl*)
+qpid::messaging::Sender::Sender(qpid::messaging::Sender const&)
+qpid::messaging::Sender::~Sender()
+qpid::messaging::Sender::operator=(qpid::messaging::Sender const&)
+qpid::messaging::Sender::send(qpid::messaging::Message const&, bool)
+qpid::messaging::Sender::close()
+qpid::messaging::Sender::setCapacity(uint32_t)
+qpid::messaging::Sender::getCapacity()
+qpid::messaging::Sender::getUnsettled()
+qpid::messaging::Sender::getAvailable()
+qpid::messaging::Sender::getName() const
+qpid::messaging::Sender::getSession() const
+qpid::messaging::Sender::getAddress() const
+
+# Session
+qpid::messaging::Session::Session(qpid::messaging::SessionImpl*)
+qpid::messaging::Session::Session(qpid::messaging::Session const&)
+qpid::messaging::Session::~Session()
+qpid::messaging::Session::operator=(qpid::messaging::Session const&)
+qpid::messaging::Session::close()
+qpid::messaging::Session::commit()
+qpid::messaging::Session::rollback()
+qpid::messaging::Session::acknowledge(bool)
+qpid::messaging::Session::acknowledge(qpid::messaging::Message&, bool)
+qpid::messaging::Session::acknowledgeUpTo(qpid::messaging::Message&, bool)
+qpid::messaging::Session::reject(qpid::messaging::Message&)
+qpid::messaging::Session::release(qpid::messaging::Message&)
+qpid::messaging::Session::sync(bool)
+qpid::messaging::Session::getReceivable()
+qpid::messaging::Session::getUnsettledAcks()
+qpid::messaging::Session::nextReceiver(qpid::messaging::Receiver&, qpid::messaging::Duration)
+qpid::messaging::Session::nextReceiver(qpid::messaging::Duration)
+qpid::messaging::Session::createSender(qpid::messaging::Address const&)
+qpid::messaging::Session::createSender(std::string const&)
+qpid::messaging::Session::createReceiver(qpid::messaging::Address const&)
+qpid::messaging::Session::createReceiver(std::string const&)
+qpid::messaging::Session::getSender(std::string const&) const
+qpid::messaging::Session::getReceiver(std::string const&) const
+qpid::messaging::Session::getConnection() const
+qpid::messaging::Session::hasError()
+qpid::messaging::Session::checkError()
+
+# Codec routines (properly superceded now by Messsage::setContentObject()
+qpid::messaging::decode(qpid::messaging::Message const&, qpid::types::Variant::Map&, std::string const&)
+qpid::messaging::decode(qpid::messaging::Message const&, qpid::types::Variant::List&, std::string const&)
+qpid::messaging::encode(qpid::types::Variant::Map const&, qpid::messaging::Message&, std::string const&)
+qpid::messaging::encode(qpid::types::Variant::List const&, qpid::messaging::Message&, std::string const&)
+
+# Exceptions
+qpid::messaging::EncodingException::EncodingException(std::string const&)
+qpid::messaging::EncodingException::~EncodingException()
+
+qpid::messaging::MessagingException::MessagingException(std::string const&)
+qpid::messaging::MessagingException::~MessagingException()
+
+qpid::messaging::InvalidOptionString::InvalidOptionString(std::string const&)
+qpid::messaging::InvalidOptionString::~InvalidOptionString()
+qpid::messaging::KeyError::KeyError(std::string const&)
+qpid::messaging::KeyError::~KeyError()
+qpid::messaging::LinkError::LinkError(std::string const&)
+qpid::messaging::LinkError::~LinkError()
+qpid::messaging::AddressError::AddressError(std::string const&)
+qpid::messaging::AddressError::~AddressError()
+qpid::messaging::ResolutionError::ResolutionError(std::string const&)
+qpid::messaging::ResolutionError::~ResolutionError()
+qpid::messaging::AssertionFailed::AssertionFailed(std::string const&)
+qpid::messaging::AssertionFailed::~AssertionFailed()
+qpid::messaging::NotFound::NotFound(std::string const&)
+qpid::messaging::NotFound::~NotFound()
+qpid::messaging::MalformedAddress::MalformedAddress(std::string const&)
+qpid::messaging::MalformedAddress::~MalformedAddress()
+qpid::messaging::ReceiverError::ReceiverError(std::string const&)
+qpid::messaging::ReceiverError::~ReceiverError()
+qpid::messaging::FetchError::FetchError(std::string const&)
+qpid::messaging::FetchError::~FetchError()
+qpid::messaging::NoMessageAvailable::NoMessageAvailable()
+qpid::messaging::NoMessageAvailable::~NoMessageAvailable()
+qpid::messaging::SenderError::SenderError(std::string const&)
+qpid::messaging::SenderError::~SenderError()
+qpid::messaging::SendError::SendError(std::string const&)
+qpid::messaging::SendError::~SendError()
+qpid::messaging::TargetCapacityExceeded::TargetCapacityExceeded(std::string const&)
+qpid::messaging::TargetCapacityExceeded::~TargetCapacityExceeded()
+qpid::messaging::SessionError::SessionError(std::string const&)
+qpid::messaging::SessionError::~SessionError()
+qpid::messaging::TransactionError::TransactionError(std::string const&)
+qpid::messaging::TransactionError::~TransactionError()
+qpid::messaging::TransactionAborted::TransactionAborted(std::string const&)
+qpid::messaging::TransactionAborted::~TransactionAborted()
+qpid::messaging::TransactionUnknown::TransactionUnknown(std::string const&)
+qpid::messaging::TransactionUnknown::~TransactionUnknown()
+qpid::messaging::UnauthorizedAccess::UnauthorizedAccess(std::string const&)
+qpid::messaging::UnauthorizedAccess::~UnauthorizedAccess()
+qpid::messaging::ConnectionError::ConnectionError(std::string const&)
+qpid::messaging::ConnectionError::~ConnectionError()
+qpid::messaging::TransportFailure::TransportFailure(std::string const&)
+qpid::messaging::TransportFailure::~TransportFailure()
+
diff --git a/qpid/cpp/src/libqpidtypes-api-symbols.txt b/qpid/cpp/src/libqpidtypes-api-symbols.txt
new file mode 100644
index 0000000000..04e7a6775e
--- /dev/null
+++ b/qpid/cpp/src/libqpidtypes-api-symbols.txt
@@ -0,0 +1,141 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Uuid
+qpid::types::Uuid::SIZE
+qpid::types::Uuid::Uuid(bool)
+qpid::types::Uuid::Uuid(qpid::types::Uuid const&)
+qpid::types::Uuid::operator=(qpid::types::Uuid const&)
+qpid::types::Uuid::Uuid(unsigned char const*)
+qpid::types::Uuid::Uuid(char const*)
+qpid::types::Uuid::generate()
+qpid::types::Uuid::clear()
+qpid::types::Uuid::isNull() const
+qpid::types::Uuid::operator bool() const
+qpid::types::Uuid::operator!() const
+qpid::types::Uuid::str() const
+qpid::types::Uuid::size() const
+qpid::types::Uuid::data() const
+qpid::types::Uuid::hash() const
+
+qpid::types::operator==(qpid::types::Uuid const&, qpid::types::Uuid const&)
+qpid::types::operator!=(qpid::types::Uuid const&, qpid::types::Uuid const&)
+qpid::types::operator<(qpid::types::Uuid const&, qpid::types::Uuid const&)
+qpid::types::operator>(qpid::types::Uuid const&, qpid::types::Uuid const&)
+qpid::types::operator<=(qpid::types::Uuid const&, qpid::types::Uuid const&)
+qpid::types::operator>=(qpid::types::Uuid const&, qpid::types::Uuid const&)
+qpid::types::operator<<(std::ostream&, qpid::types::Uuid)
+qpid::types::operator>>(std::istream&, qpid::types::Uuid&)
+
+# VariantType
+qpid::types::getTypeName(qpid::types::VariantType)
+qpid::types::isIntegerType(qpid::types::VariantType)
+
+# Variant
+qpid::types::Variant::Variant()
+qpid::types::Variant::Variant(bool)
+qpid::types::Variant::Variant(uint8_t)
+qpid::types::Variant::Variant(uint16_t)
+qpid::types::Variant::Variant(uint32_t)
+qpid::types::Variant::Variant(uint64_t)
+qpid::types::Variant::Variant(int8_t)
+qpid::types::Variant::Variant(int16_t)
+qpid::types::Variant::Variant(int32_t)
+qpid::types::Variant::Variant(int64_t)
+qpid::types::Variant::Variant(float)
+qpid::types::Variant::Variant(double)
+qpid::types::Variant::Variant(std::string const&)
+qpid::types::Variant::Variant(char const*)
+qpid::types::Variant::Variant(qpid::types::Variant::Map const&)
+qpid::types::Variant::Variant(qpid::types::Variant::List const&)
+qpid::types::Variant::Variant(qpid::types::Variant const&)
+qpid::types::Variant::Variant(qpid::types::Uuid const&)
+qpid::types::Variant::~Variant()
+qpid::types::Variant::getType() const
+qpid::types::Variant::isVoid() const
+qpid::types::Variant::operator=(bool)
+qpid::types::Variant::operator=(uint8_t)
+qpid::types::Variant::operator=(uint16_t)
+qpid::types::Variant::operator=(uint32_t)
+qpid::types::Variant::operator=(uint64_t)
+qpid::types::Variant::operator=(int8_t)
+qpid::types::Variant::operator=(int16_t)
+qpid::types::Variant::operator=(int32_t)
+qpid::types::Variant::operator=(int64_t)
+qpid::types::Variant::operator=(float)
+qpid::types::Variant::operator=(double)
+qpid::types::Variant::operator=(std::string const&)
+qpid::types::Variant::operator=(char const*)
+qpid::types::Variant::operator=(qpid::types::Variant::Map const&)
+qpid::types::Variant::operator=(qpid::types::Variant::List const&)
+qpid::types::Variant::operator=(qpid::types::Variant const&)
+qpid::types::Variant::operator=(qpid::types::Uuid const&)
+qpid::types::Variant::parse(std::string const&)
+qpid::types::Variant::asBool() const
+qpid::types::Variant::asUint8() const
+qpid::types::Variant::asUint16() const
+qpid::types::Variant::asUint32() const
+qpid::types::Variant::asUint64() const
+qpid::types::Variant::asInt8() const
+qpid::types::Variant::asInt16() const
+qpid::types::Variant::asInt32() const
+qpid::types::Variant::asInt64() const
+qpid::types::Variant::asFloat() const
+qpid::types::Variant::asDouble() const
+qpid::types::Variant::asString() const
+qpid::types::Variant::asUuid() const
+qpid::types::Variant::asMap() const
+qpid::types::Variant::asMap()
+qpid::types::Variant::asList() const
+qpid::types::Variant::asList()
+qpid::types::Variant::getString() const
+qpid::types::Variant::getString()
+qpid::types::Variant::setEncoding(std::string const&)
+qpid::types::Variant::getEncoding() const
+qpid::types::Variant::operator bool() const
+qpid::types::Variant::operator uint8_t() const
+qpid::types::Variant::operator uint16_t() const
+qpid::types::Variant::operator uint32_t() const
+qpid::types::Variant::operator uint64_t() const
+qpid::types::Variant::operator int8_t() const
+qpid::types::Variant::operator int16_t() const
+qpid::types::Variant::operator int32_t() const
+qpid::types::Variant::operator int64_t() const
+qpid::types::Variant::operator float() const
+qpid::types::Variant::operator double() const
+qpid::types::Variant::operator std::string() const
+qpid::types::Variant::operator qpid::types::Uuid() const
+qpid::types::Variant::isEqualTo(qpid::types::Variant const&) const
+qpid::types::Variant::reset()
+
+qpid::types::operator<<(std::ostream&, qpid::types::Variant const&)
+qpid::types::operator<<(std::ostream&, qpid::types::Variant::Map const&)
+qpid::types::operator<<(std::ostream&, qpid::types::Variant::List const&)
+qpid::types::operator==(qpid::types::Variant const&, qpid::types::Variant const&)
+qpid::types::operator!=(qpid::types::Variant const&, qpid::types::Variant const&)
+
+# Root of qpid::types Exception hierarchy
+qpid::types::Exception::Exception(std::string const&)
+qpid::types::Exception::~Exception()
+qpid::types::Exception::what() const
+
+qpid::types::InvalidConversion::InvalidConversion(std::string const&)
+qpid::types::InvalidConversion::~InvalidConversion()
+
+
diff --git a/qpid/cpp/src/linearstore.cmake b/qpid/cpp/src/linearstore.cmake
new file mode 100644
index 0000000000..e876ca712a
--- /dev/null
+++ b/qpid/cpp/src/linearstore.cmake
@@ -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.
+#
+#
+# Linear store library CMake fragment, to be included in CMakeLists.txt
+#
+
+if (DEFINED linearstore_force)
+ set (linearstore_default ${linearstore_force})
+else (DEFINED linearstore_force)
+ set (linearstore_default OFF)
+ if (UNIX)
+ #
+ # Find required BerkeleyDB
+ #
+ include (finddb.cmake)
+ if (DB_FOUND)
+ #
+ # find libaio
+ #
+ CHECK_LIBRARY_EXISTS (aio io_queue_init "" HAVE_AIO)
+ CHECK_INCLUDE_FILES (libaio.h HAVE_AIO_H)
+ if (HAVE_AIO AND HAVE_AIO_H)
+ #
+ # allow linearstore to be built
+ #
+ message(STATUS "BerkeleyDB for C++ and libaio found, Linearstore support enabled.")
+ set (linearstore_default ON)
+ else (HAVE_AIO AND HAVE_AIO_H)
+ if (NOT HAVE_AIO)
+ message(STATUS "Linearstore requires libaio which is absent.")
+ endif (NOT HAVE_AIO)
+ if (NOT HAVE_AIO_H)
+ message(STATUS "Linearstore requires libaio.h which is absent.")
+ endif (NOT HAVE_AIO_H)
+ endif (HAVE_AIO AND HAVE_AIO_H)
+ else (DB_FOUND)
+ message(STATUS "Linearstore requires BerkeleyDB for C++ which is absent.")
+ endif (DB_FOUND)
+ endif (UNIX)
+endif (DEFINED linearstore_force)
+
+option(BUILD_LINEARSTORE "Build linearstore persistent store" ${linearstore_default})
+
+if (BUILD_LINEARSTORE)
+ if (NOT UNIX)
+ message(FATAL_ERROR "Linearstore produced only on Unix platforms")
+ endif (NOT UNIX)
+ if (NOT DB_FOUND)
+ message(FATAL_ERROR "Linearstore requires BerkeleyDB for C++ which is absent.")
+ endif (NOT DB_FOUND)
+ if (NOT HAVE_AIO)
+ message(FATAL_ERROR "Linearstore requires libaio which is absent.")
+ endif (NOT HAVE_AIO)
+ if (NOT HAVE_AIO_H)
+ message(FATAL_ERROR "Linearstore requires libaio.h which is absent.")
+ endif (NOT HAVE_AIO_H)
+
+ # Journal source files
+ set (linear_jrnl_SOURCES
+ qpid/linearstore/journal/Checksum.cpp
+ qpid/linearstore/journal/data_tok.cpp
+ qpid/linearstore/journal/deq_rec.cpp
+ qpid/linearstore/journal/EmptyFilePool.cpp
+ qpid/linearstore/journal/EmptyFilePoolManager.cpp
+ qpid/linearstore/journal/EmptyFilePoolPartition.cpp
+ qpid/linearstore/journal/enq_map.cpp
+ qpid/linearstore/journal/enq_rec.cpp
+ qpid/linearstore/journal/jcntl.cpp
+ qpid/linearstore/journal/jdir.cpp
+ qpid/linearstore/journal/jerrno.cpp
+ qpid/linearstore/journal/jexception.cpp
+ qpid/linearstore/journal/JournalFile.cpp
+ qpid/linearstore/journal/JournalLog.cpp
+ qpid/linearstore/journal/LinearFileController.cpp
+ qpid/linearstore/journal/pmgr.cpp
+ qpid/linearstore/journal/RecoveryManager.cpp
+ qpid/linearstore/journal/time_ns.cpp
+ qpid/linearstore/journal/txn_map.cpp
+ qpid/linearstore/journal/txn_rec.cpp
+ qpid/linearstore/journal/wmgr.cpp
+ )
+
+ # linearstore source files
+ set (linear_store_SOURCES
+ qpid/linearstore/StorePlugin.cpp
+ qpid/linearstore/BindingDbt.cpp
+ qpid/linearstore/BufferValue.cpp
+ qpid/linearstore/DataTokenImpl.cpp
+ qpid/linearstore/IdDbt.cpp
+ qpid/linearstore/IdSequence.cpp
+ qpid/linearstore/JournalImpl.cpp
+ qpid/linearstore/MessageStoreImpl.cpp
+ qpid/linearstore/PreparedTransaction.cpp
+ qpid/linearstore/JournalLogImpl.cpp
+ qpid/linearstore/TxnCtxt.cpp
+ )
+
+ set (util_SOURCES
+ qpid/linearstore/journal/utils/deq_hdr.c
+ qpid/linearstore/journal/utils/enq_hdr.c
+ qpid/linearstore/journal/utils/file_hdr.c
+ qpid/linearstore/journal/utils/rec_hdr.c
+ qpid/linearstore/journal/utils/rec_tail.c
+ qpid/linearstore/journal/utils/txn_hdr.c
+ )
+
+ # linearstore include directories
+ get_property(dirs DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY INCLUDE_DIRECTORIES)
+ set (linear_include_DIRECTORIES
+ ${dirs}
+ ${CMAKE_CURRENT_SOURCE_DIR}/qpid/linearstore
+ )
+
+ if(NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/db-inc.h)
+ message(STATUS "Including BDB from ${DB_CXX_INCLUDE_DIR}/db_cxx.h")
+ file(WRITE
+ ${CMAKE_CURRENT_BINARY_DIR}/db-inc.h
+ "#include <${DB_CXX_INCLUDE_DIR}/db_cxx.h>\n")
+ endif()
+
+ add_library (linearstoreutils SHARED
+ ${util_SOURCES}
+ )
+
+ target_link_libraries (linearstoreutils
+ rt
+ )
+
+ add_library (linearstore MODULE
+ ${linear_jrnl_SOURCES}
+ ${linear_store_SOURCES}
+ ${linear_qmf_SOURCES}
+ )
+
+ set_target_properties (linearstore PROPERTIES
+ PREFIX ""
+ COMPILE_DEFINITIONS _IN_QPID_BROKER
+ OUTPUT_NAME linearstore
+ INCLUDE_DIRECTORIES "${linear_include_DIRECTORIES}"
+ )
+
+ target_link_libraries (linearstore
+ aio
+ uuid
+ qpidcommon qpidtypes qpidbroker linearstoreutils
+ ${DB_LIBRARY}
+ )
+
+ install(TARGETS linearstore
+ DESTINATION ${QPIDD_MODULE_DIR}
+ COMPONENT ${QPID_COMPONENT_BROKER})
+
+ install (TARGETS linearstoreutils
+ RUNTIME DESTINATION ${QPID_INSTALL_BINDIR}
+ LIBRARY DESTINATION ${QPID_INSTALL_LIBDIR}
+ ARCHIVE DESTINATION ${QPID_INSTALL_LIBDIR}
+ COMPONENT ${QPID_COMPONENT_BROKER})
+
+else (BUILD_LINEARSTORE)
+ message(STATUS "Linearstore is excluded from build.")
+endif (BUILD_LINEARSTORE)
diff --git a/qpid/cpp/src/msvc.cmake b/qpid/cpp/src/msvc.cmake
new file mode 100644
index 0000000000..dc248f4f96
--- /dev/null
+++ b/qpid/cpp/src/msvc.cmake
@@ -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.
+#
+
+# This file provides library support for MSVC builds.
+# * Allows for detailed specification of file/product versions.
+# * Installs PDB files.
+
+#
+# If the compiler is Visual Studio set up installation of .pdb files
+#
+# Sample: install_pdb (qpidcommon ${QPID_COMPONENT_COMMON})
+#
+MACRO (install_pdb theLibrary theComponent)
+ if (MSVC)
+ get_target_property(library_dll ${theLibrary} LOCATION)
+ string(REPLACE .dll .pdb library_pdb ${library_dll})
+ string(REPLACE $(OutDir) \${CMAKE_INSTALL_CONFIG_NAME} library_pdb ${library_pdb})
+ string(REPLACE $(Configuration) \${CMAKE_INSTALL_CONFIG_NAME} library_pdb ${library_pdb})
+ string(REPLACE .pdb d.pdb libraryd_pdb ${library_pdb})
+ #message(STATUS "_pdb: ${library_pdb}, ${libraryd_pdb}")
+ install (PROGRAMS
+ ${library_pdb}
+ DESTINATION ${QPID_INSTALL_LIBDIR}/ReleasePDB
+ COMPONENT ${theComponent}
+ OPTIONAL
+ CONFIGURATIONS Release|MinSizeRel)
+ install (PROGRAMS
+ ${library_pdb}
+ DESTINATION ${QPID_INSTALL_LIBDIR}/ReleasePDB
+ COMPONENT ${theComponent}
+ CONFIGURATIONS RelWithDebInfo)
+ install (PROGRAMS
+ ${libraryd_pdb}
+ DESTINATION ${QPID_INSTALL_LIBDIR}/DebugPDB
+ COMPONENT ${theComponent}
+ CONFIGURATIONS Debug)
+ endif (MSVC)
+ENDMACRO (install_pdb)
+
+#
+# inherit_value - if the symbol is undefined then set it to the given value.
+# Set flag to indicate this symbol was defined here.
+#
+MACRO (inherit_value theSymbol theValue)
+ if (NOT DEFINED ${theSymbol})
+ set (${theSymbol} ${theValue})
+ # message ("Set symbol '${theSymbol}' to value '${theValue}'")
+ set (${theSymbol}_inherited = "true")
+ endif (NOT DEFINED ${theSymbol})
+ENDMACRO (inherit_value)
+
+#
+# If compiler is Visual Studio then create a "version resource" for the project.
+# Use this call to override CPACK and file global settings but not file per-project settings.
+# Two groups of four version numbers specify "file" and "product" versions separately.
+#
+# Sample: add_msvc_version_full (qmfengine library dll 1 0 0 1 1 0 0 1)
+#
+MACRO (add_msvc_version_full verProject verProjectType verProjectFileExt verFN1 verFN2 verFN3 verFN4 verPN1 verPN2 verPN3 verPN4)
+ if (MSVC)
+ # Create project-specific version strings
+ inherit_value ("winver_${verProject}_FileVersionBinary" "${verFN1},${verFN2},${verFN3},${verFN4}")
+ inherit_value ("winver_${verProject}_ProductVersionBinary" "${verPN1},${verPN2},${verPN3},${verPN4}")
+ inherit_value ("winver_${verProject}_FileVersionString" "${verFN1}, ${verFN2}, ${verFN3}, ${verFN4}")
+ inherit_value ("winver_${verProject}_ProductVersionString" "${verPN1}, ${verPN2}, ${verPN3}, ${verPN4}")
+ inherit_value ("winver_${verProject}_FileDescription" "${winver_PACKAGE_NAME}-${verProject} ${verProjectType}")
+ inherit_value ("winver_${verProject}_LegalCopyright" "${winver_LEGAL_COPYRIGHT}")
+ inherit_value ("winver_${verProject}_InternalName" "${verProject}")
+ inherit_value ("winver_${verProject}_OriginalFilename" "${verProject}.${verProjectFileExt}")
+ inherit_value ("winver_${verProject}_ProductName" "${winver_DESCRIPTION_SUMMARY}")
+
+ # Create strings to be substituted into the template file
+ set ("winverFileVersionBinary" "${winver_${verProject}_FileVersionBinary}")
+ set ("winverProductVersionBinary" "${winver_${verProject}_ProductVersionBinary}")
+ set ("winverFileVersionString" "${winver_${verProject}_FileVersionString}")
+ set ("winverProductVersionString" "${winver_${verProject}_ProductVersionString}")
+ set ("winverFileDescription" "${winver_${verProject}_FileDescription}")
+ set ("winverLegalCopyright" "${winver_${verProject}_LegalCopyright}")
+ set ("winverInternalName" "${winver_${verProject}_InternalName}")
+ set ("winverOriginalFilename" "${winver_${verProject}_OriginalFilename}")
+ set ("winverProductName" "${winver_${verProject}_ProductName}")
+
+ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/windows/resources/template-resource.rc
+ ${CMAKE_CURRENT_BINARY_DIR}/windows/resources/${verProject}-resource.rc)
+ set (${verProject}_SOURCES
+ ${${verProject}_SOURCES}
+ ${CMAKE_CURRENT_BINARY_DIR}/windows/resources/${verProject}-resource.rc
+ )
+ endif (MSVC)
+ENDMACRO (add_msvc_version_full)
+
+#
+# If compiler is Visual Studio then create a "version resource" for the project.
+# Use this call to accept file override version settings or
+# inherited CPACK_PACKAGE_VERSION version settings.
+#
+# Sample: add_msvc_version (qpidcommon library dll)
+#
+MACRO (add_msvc_version verProject verProjectType verProjectFileExt)
+ if (MSVC)
+ add_msvc_version_full (${verProject}
+ ${verProjectType}
+ ${verProjectFileExt}
+ ${winver_FILE_VERSION_N1}
+ ${winver_FILE_VERSION_N2}
+ ${winver_FILE_VERSION_N3}
+ ${winver_FILE_VERSION_N4}
+ ${winver_PRODUCT_VERSION_N1}
+ ${winver_PRODUCT_VERSION_N2}
+ ${winver_PRODUCT_VERSION_N3}
+ ${winver_PRODUCT_VERSION_N4})
+ endif (MSVC)
+ENDMACRO (add_msvc_version)
+
+#
+# Install optional windows version settings. Override variables are specified in a file.
+#
+include (./CMakeWinVersions.cmake OPTIONAL)
+
+#
+# Inherit global windows version settings from CPACK settings.
+#
+inherit_value ("winver_PACKAGE_NAME" "${CPACK_PACKAGE_NAME}")
+inherit_value ("winver_DESCRIPTION_SUMMARY" "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}")
+inherit_value ("winver_FILE_VERSION_N1" "${CPACK_PACKAGE_VERSION_MAJOR}")
+inherit_value ("winver_FILE_VERSION_N2" "${CPACK_PACKAGE_VERSION_MINOR}")
+inherit_value ("winver_FILE_VERSION_N3" "${CPACK_PACKAGE_VERSION_PATCH}")
+inherit_value ("winver_FILE_VERSION_N4" "1")
+inherit_value ("winver_PRODUCT_VERSION_N1" "${winver_FILE_VERSION_N1}")
+inherit_value ("winver_PRODUCT_VERSION_N2" "${winver_FILE_VERSION_N2}")
+inherit_value ("winver_PRODUCT_VERSION_N3" "${winver_FILE_VERSION_N3}")
+inherit_value ("winver_PRODUCT_VERSION_N4" "${winver_FILE_VERSION_N4}")
+inherit_value ("winver_LEGAL_COPYRIGHT" "")
diff --git a/qpid/cpp/src/posix/QpiddBroker.cpp b/qpid/cpp/src/posix/QpiddBroker.cpp
new file mode 100644
index 0000000000..7caffd570e
--- /dev/null
+++ b/qpid/cpp/src/posix/QpiddBroker.cpp
@@ -0,0 +1,250 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "config.h"
+#include "qpidd.h"
+#include "qpid/Exception.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/Daemon.h"
+#include "qpid/broker/SignalHandler.h"
+#include "qpid/log/Logger.h"
+
+#include <iostream>
+#include <fstream>
+#include <signal.h>
+#include <unistd.h>
+#include <sys/utsname.h>
+
+using std::cout;
+using std::endl;
+using std::ifstream;
+using std::ofstream;
+
+namespace qpid {
+namespace broker {
+
+BootstrapOptions::BootstrapOptions(const char* argv0)
+ : qpid::Options("Options"),
+ common("", QPIDD_CONF_FILE, QPIDC_CONF_FILE),
+ module(QPIDD_MODULE_DIR),
+ log(argv0)
+{
+ add(common);
+ add(module);
+ add(log);
+}
+
+void BootstrapOptions::usage() const {
+ cout << "Usage: qpidd [OPTIONS]" << endl << endl << *this << endl;
+}
+
+namespace {
+const std::string TCP = "tcp";
+}
+
+struct DaemonOptions : public qpid::Options {
+ bool daemon;
+ bool quit;
+ bool kill;
+ bool check;
+ std::vector<int> closeFd;
+ int wait;
+ std::string piddir;
+ std::string pidfile;
+ std::string transport;
+
+ DaemonOptions() : qpid::Options("Daemon options"), daemon(false), quit(false), kill(false), check(false), wait(600), transport(TCP)
+ {
+ char *home = ::getenv("HOME");
+
+ if (home == 0)
+ piddir += "/tmp";
+ else
+ piddir += home;
+ piddir += "/.qpidd";
+
+ addOptions()
+ ("daemon,d", pure_switch(daemon), "Run as a daemon. Logs to syslog by default in this mode.")
+ ("transport", optValue(transport, "TRANSPORT"), "The transport for which to return the port")
+ ("pid-dir", optValue(piddir, "DIR"), "Directory where port-specific PID file is stored")
+ ("pidfile", optValue(pidfile, "FILE"), "File name to store the PID in daemon mode. Used as-is, no directory or suffixes added.")
+ ("close-fd", optValue(closeFd, "FD"), "File descriptors that the daemon should close")
+ ("wait,w", optValue(wait, "SECONDS"), "Sets the maximum wait time to initialize or shutdown the daemon. If the daemon fails to initialize/shutdown, prints an error and returns 1")
+ ("check,c", pure_switch(check), "Prints the daemon's process ID to stdout and returns 0 if the daemon is running, otherwise returns 1")
+ ("quit,q", pure_switch(quit), "Tells the daemon to shut down with an INT signal")
+ ("kill,k", pure_switch(kill), "Kill the daemon with a KILL signal.");
+ }
+};
+
+struct QpiddPosixOptions : public QpiddOptionsPrivate {
+ DaemonOptions daemon;
+ QpiddOptions *parent;
+
+ QpiddPosixOptions(QpiddOptions *parent_) : parent(parent_) {
+ parent->add(daemon);
+ }
+};
+
+QpiddOptions::QpiddOptions(const char* argv0)
+ : qpid::Options("Options"),
+ common("", QPIDD_CONF_FILE, QPIDC_CONF_FILE),
+ module(QPIDD_MODULE_DIR),
+ log(argv0)
+{
+ add(common);
+ add(module);
+ add(broker);
+ add(log);
+
+ platform.reset(new QpiddPosixOptions(this));
+ qpid::Plugin::addOptions(*this);
+}
+
+void QpiddOptions::usage() const {
+ cout << "Usage: qpidd [OPTIONS]" << endl << endl << *this << endl;
+}
+
+// Set the broker pointer on the signal handler, then reset at end of scope.
+// This is to ensure that the signal handler doesn't keep a broker
+// reference after main() has returned.
+//
+struct ScopedSetBroker {
+ ScopedSetBroker(const boost::intrusive_ptr<Broker>& broker) {
+ qpid::broker::SignalHandler::setBroker(broker.get());
+ }
+ ~ScopedSetBroker() { qpid::broker::SignalHandler::setBroker(0); }
+};
+
+namespace {
+
+/// Write a pid file if requested
+void writePid(const std::string& filename) {
+ if (!filename.empty()) {
+ ofstream pidfile(filename.c_str());
+ pidfile << ::getpid() << endl;
+ pidfile.close();
+ }
+}
+}
+
+struct QpiddDaemon : public Daemon {
+ QpiddPosixOptions *options;
+
+ QpiddDaemon(std::string pidDir, QpiddPosixOptions *opts)
+ : Daemon(pidDir), options(opts) {}
+
+ /** Code for parent process */
+ void parent() {
+ uint16_t port = wait(options->daemon.wait);
+ if (options->parent->broker.port == 0)
+ cout << port << endl;
+ }
+
+ /** Code for forked child process */
+ void child() {
+ // Close extra FDs requested in options.
+ for (size_t i = 0; i < options->daemon.closeFd.size(); ++i)
+ ::close(options->daemon.closeFd[i]);
+ boost::intrusive_ptr<Broker> brokerPtr(new Broker(options->parent->broker));
+ ScopedSetBroker ssb(brokerPtr);
+ brokerPtr->accept();
+ uint16_t port=brokerPtr->getPort(options->daemon.transport);
+ ready(port); // Notify parent.
+ if (options->parent->broker.enableMgmt && (options->parent->broker.port == 0 || options->daemon.transport != TCP)) {
+ boost::dynamic_pointer_cast<qmf::org::apache::qpid::broker::Broker>(brokerPtr->GetManagementObject())->set_port(port);
+ }
+ writePid(options->daemon.pidfile);
+ brokerPtr->run();
+ }
+};
+
+int QpiddBroker::execute (QpiddOptions *options) {
+ // Options that affect a running daemon.
+ QpiddPosixOptions *myOptions =
+ static_cast<QpiddPosixOptions *>(options->platform.get());
+ if (myOptions == 0)
+ throw Exception("Internal error obtaining platform options");
+
+ if (myOptions->daemon.check || myOptions->daemon.quit || myOptions->daemon.kill) {
+ pid_t pid = 0;
+ if (!myOptions->daemon.pidfile.empty()) {
+ ifstream pidfile(myOptions->daemon.pidfile.c_str());
+ pidfile >> pid;
+ pidfile.close();
+ }
+ if (pid == 0) {
+ try {
+ pid = Daemon::getPid(myOptions->daemon.piddir, options->broker.port);
+ } catch (const Exception& e) {
+ // This is not a critical error, usually means broker is not running
+ QPID_LOG(notice, "Broker is not running: " << e.what());
+ return 1;
+ }
+ }
+ if (pid < 0)
+ return 1;
+ if (myOptions->daemon.check)
+ cout << pid << endl;
+ if (myOptions->daemon.quit || myOptions->daemon.kill) {
+ int signal = myOptions->daemon.kill ? SIGKILL : SIGINT;
+ if (kill(pid, signal) < 0)
+ throw Exception("Failed to stop daemon: " + qpid::sys::strError(errno));
+ // Wait for the process to die before returning
+ int retry=myOptions->daemon.wait*1000; // Try up to "--wait N" seconds, do retry every millisecond
+ while (kill(pid,0) == 0 && --retry)
+ sys::usleep(1000);
+ if (retry == 0)
+ throw Exception("Gave up waiting for daemon process to exit");
+ }
+ return 0;
+ }
+
+ // Starting the broker.
+ if (myOptions->daemon.daemon) {
+ // For daemon mode replace default stderr with syslog.
+ options->log.sinkOptions->detached();
+ qpid::log::Logger::instance().configure(options->log);
+ // Fork the daemon
+ QpiddDaemon d(myOptions->daemon.piddir, myOptions);
+ d.fork(); // Broker is stared in QpiddDaemon::child()
+ }
+ else { // Non-daemon broker.
+ boost::intrusive_ptr<Broker> brokerPtr(new Broker(options->broker));
+ ScopedSetBroker ssb(brokerPtr);
+ brokerPtr->accept();
+ if (options->broker.port == 0) {
+ uint16_t port = brokerPtr->getPort(myOptions->daemon.transport);
+ cout << port << endl;
+ if (options->broker.enableMgmt) {
+ boost::dynamic_pointer_cast<qmf::org::apache::qpid::broker::Broker>(brokerPtr->GetManagementObject())->set_port(port);
+ }
+ }
+ writePid(myOptions->daemon.pidfile);
+ brokerPtr->run();
+ }
+ return 0;
+}
+
+}} // namespace qpid::Broker
+
+int main(int argc, char* argv[])
+{
+ return qpid::broker::run_broker(argc, argv);
+}
diff --git a/qpid/cpp/src/prof b/qpid/cpp/src/prof
new file mode 100755
index 0000000000..fa470f5f84
--- /dev/null
+++ b/qpid/cpp/src/prof
@@ -0,0 +1,39 @@
+#!/usr/bin/env 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.
+#
+#
+
+
+rm /var/lib/oprofile/oprofiled.log
+
+opcontrol --reset
+opcontrol --setup --no-vmlinux --separate=library
+opcontrol --start
+# -- Do stuff here --
+./qpidd
+# -- End of stuff --
+opcontrol --stop
+opcontrol --dump
+opcontrol --shutdown
+opreport -l ./qpidd > stats.txt
+opannotate --source --output-dir=qpidd-prof ./qpidd
+
+# clear the relusts
+#opcontrol --reset
diff --git a/qpid/cpp/src/qmf/Agent.cpp b/qpid/cpp/src/qmf/Agent.cpp
new file mode 100644
index 0000000000..fa3987e0c9
--- /dev/null
+++ b/qpid/cpp/src/qmf/Agent.cpp
@@ -0,0 +1,640 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qmf/AgentImpl.h"
+#include "qmf/PrivateImplRef.h"
+#include "qmf/ConsoleEventImpl.h"
+#include "qmf/ConsoleSession.h"
+#include "qmf/DataImpl.h"
+#include "qmf/Query.h"
+#include "qmf/SchemaImpl.h"
+#include "qmf/agentCapability.h"
+#include "qmf/constants.h"
+#include "qpid/messaging/Sender.h"
+#include "qpid/messaging/AddressParser.h"
+#include "qpid/management/Buffer.h"
+#include "qpid/log/Statement.h"
+#include <boost/lexical_cast.hpp>
+
+using qpid::types::Variant;
+using qpid::messaging::Duration;
+using qpid::messaging::Message;
+using qpid::messaging::Sender;
+using namespace std;
+using namespace qmf;
+
+typedef PrivateImplRef<Agent> PI;
+
+Agent::Agent(AgentImpl* impl) { PI::ctor(*this, impl); }
+Agent::Agent(const Agent& s) : qmf::Handle<AgentImpl>() { PI::copy(*this, s); }
+Agent::~Agent() { PI::dtor(*this); }
+Agent& Agent::operator=(const Agent& s) { return PI::assign(*this, s); }
+string Agent::getName() const { return isValid() ? impl->getName() : ""; }
+uint32_t Agent::getEpoch() const { return isValid() ? impl->getEpoch() : 0; }
+string Agent::getVendor() const { return isValid() ? impl->getVendor() : ""; }
+string Agent::getProduct() const { return isValid() ? impl->getProduct() : ""; }
+string Agent::getInstance() const { return isValid() ? impl->getInstance() : ""; }
+const Variant& Agent::getAttribute(const string& k) const { return impl->getAttribute(k); }
+const Variant::Map& Agent::getAttributes() const { return impl->getAttributes(); }
+ConsoleEvent Agent::querySchema(Duration t) { return impl->querySchema(t); }
+uint32_t Agent::querySchemaAsync() { return impl->querySchemaAsync(); }
+ConsoleEvent Agent::query(const Query& q, Duration t) { return impl->query(q, t); }
+ConsoleEvent Agent::query(const string& q, Duration t) { return impl->query(q, t); }
+uint32_t Agent::queryAsync(const Query& q) { return impl->queryAsync(q); }
+uint32_t Agent::queryAsync(const string& q) { return impl->queryAsync(q); }
+ConsoleEvent Agent::callMethod(const string& m, const Variant::Map& a, const DataAddr& d, Duration t) { return impl->callMethod(m, a, d, t); }
+uint32_t Agent::callMethodAsync(const string& m, const Variant::Map& a, const DataAddr& d) { return impl->callMethodAsync(m, a, d); }
+uint32_t Agent::getPackageCount() const { return impl->getPackageCount(); }
+const string& Agent::getPackage(uint32_t i) const { return impl->getPackage(i); }
+uint32_t Agent::getSchemaIdCount(const string& p) const { return impl->getSchemaIdCount(p); }
+SchemaId Agent::getSchemaId(const string& p, uint32_t i) const { return impl->getSchemaId(p, i); }
+Schema Agent::getSchema(const SchemaId& s, Duration t) { return impl->getSchema(s, t); }
+
+
+
+AgentImpl::AgentImpl(const std::string& n, uint32_t e, ConsoleSessionImpl& s) :
+ name(n), directSubject(n), epoch(e), session(s), touched(true), untouchedCount(0), capability(0),
+ sender(session.directSender), schemaCache(s.schemaCache)
+{
+}
+
+void AgentImpl::setAttribute(const std::string& k, const qpid::types::Variant& v)
+{
+ attributes[k] = v;
+ if (k == "qmf.agent_capability")
+ try {
+ capability = v.asUint32();
+ } catch (std::exception&) {}
+ if (k == "_direct_subject")
+ try {
+ directSubject = v.asString();
+ sender = session.topicSender;
+ } catch (std::exception&) {}
+}
+
+const Variant& AgentImpl::getAttribute(const string& k) const
+{
+ Variant::Map::const_iterator iter = attributes.find(k);
+ if (iter == attributes.end())
+ throw KeyNotFound(k);
+ return iter->second;
+}
+
+
+ConsoleEvent AgentImpl::query(const Query& query, Duration timeout)
+{
+ boost::shared_ptr<SyncContext> context(new SyncContext());
+ uint32_t correlator(session.correlator());
+ ConsoleEvent result;
+
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ contextMap[correlator] = context;
+ }
+ try {
+ sendQuery(query, correlator);
+ {
+ uint64_t milliseconds = timeout.getMilliseconds();
+ qpid::sys::Mutex::ScopedLock cl(context->lock);
+ if (!context->response.isValid() || !context->response.isFinal())
+ context->cond.wait(context->lock,
+ qpid::sys::AbsTime(qpid::sys::now(),
+ qpid::sys::Duration(milliseconds * qpid::sys::TIME_MSEC)));
+ if (context->response.isValid() &&
+ ((context->response.getType() == CONSOLE_QUERY_RESPONSE && context->response.isFinal()) ||
+ (context->response.getType() == CONSOLE_EXCEPTION)))
+ result = context->response;
+ else {
+ auto_ptr<ConsoleEventImpl> impl(new ConsoleEventImpl(CONSOLE_EXCEPTION));
+ Data exception(new DataImpl());
+ exception.setProperty("error_text", "Timed out waiting for the agent to respond");
+ impl->addData(exception);
+ result = ConsoleEvent(impl.release());
+ }
+ }
+ } catch (qpid::types::Exception&) {
+ }
+
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ contextMap.erase(correlator);
+ }
+
+ return result;
+}
+
+
+ConsoleEvent AgentImpl::query(const string& text, Duration timeout)
+{
+ return query(stringToQuery(text), timeout);
+}
+
+
+uint32_t AgentImpl::queryAsync(const Query& query)
+{
+ uint32_t correlator(session.correlator());
+
+ sendQuery(query, correlator);
+ return correlator;
+}
+
+
+uint32_t AgentImpl::queryAsync(const string& text)
+{
+ return queryAsync(stringToQuery(text));
+}
+
+
+ConsoleEvent AgentImpl::callMethod(const string& method, const Variant::Map& args, const DataAddr& addr, Duration timeout)
+{
+ boost::shared_ptr<SyncContext> context(new SyncContext());
+ uint32_t correlator(session.correlator());
+ ConsoleEvent result;
+
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ contextMap[correlator] = context;
+ }
+ try {
+ sendMethod(method, args, addr, correlator);
+ {
+ uint64_t milliseconds = timeout.getMilliseconds();
+ qpid::sys::Mutex::ScopedLock cl(context->lock);
+ if (!context->response.isValid())
+ context->cond.wait(context->lock,
+ qpid::sys::AbsTime(qpid::sys::now(),
+ qpid::sys::Duration(milliseconds * qpid::sys::TIME_MSEC)));
+ if (context->response.isValid())
+ result = context->response;
+ else {
+ auto_ptr<ConsoleEventImpl> impl(new ConsoleEventImpl(CONSOLE_EXCEPTION));
+ Data exception(new DataImpl());
+ exception.setProperty("error_text", "Timed out waiting for the agent to respond");
+ impl->addData(exception);
+ result = ConsoleEvent(impl.release());
+ }
+ }
+ } catch (qpid::types::Exception&) {
+ }
+
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ contextMap.erase(correlator);
+ }
+
+ return result;
+}
+
+
+uint32_t AgentImpl::callMethodAsync(const string& method, const Variant::Map& args, const DataAddr& addr)
+{
+ uint32_t correlator(session.correlator());
+
+ sendMethod(method, args, addr, correlator);
+ return correlator;
+}
+
+
+uint32_t AgentImpl::getPackageCount() const
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+
+ //
+ // Populate the package set.
+ //
+ for (set<SchemaId, SchemaIdCompare>::const_iterator iter = schemaIdSet.begin(); iter != schemaIdSet.end(); iter++)
+ packageSet.insert(iter->getPackageName());
+
+ return packageSet.size();
+}
+
+
+const string& AgentImpl::getPackage(uint32_t idx) const
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ uint32_t count(0);
+ for (set<string>::const_iterator iter = packageSet.begin(); iter != packageSet.end(); iter++) {
+ if (idx == count)
+ return *iter;
+ count++;
+ }
+ throw IndexOutOfRange();
+}
+
+
+uint32_t AgentImpl::getSchemaIdCount(const string& pname) const
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ uint32_t count(0);
+ for (set<SchemaId, SchemaIdCompare>::const_iterator iter = schemaIdSet.begin(); iter != schemaIdSet.end(); iter++)
+ if (iter->getPackageName() == pname)
+ count++;
+ return count;
+}
+
+
+SchemaId AgentImpl::getSchemaId(const string& pname, uint32_t idx) const
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ uint32_t count(0);
+ for (set<SchemaId, SchemaIdCompare>::const_iterator iter = schemaIdSet.begin(); iter != schemaIdSet.end(); iter++) {
+ if (iter->getPackageName() == pname) {
+ if (idx == count)
+ return *iter;
+ count++;
+ }
+ }
+ throw IndexOutOfRange();
+}
+
+
+Schema AgentImpl::getSchema(const SchemaId& id, Duration timeout)
+{
+ if (!schemaCache->haveSchema(id))
+ //
+ // The desired schema is not in the cache. We need to asynchronously query the remote
+ // agent for the information. The call to schemaCache->getSchema will block waiting for
+ // the response to be received.
+ //
+ sendSchemaRequest(id);
+
+ return schemaCache->getSchema(id, timeout);
+}
+
+
+void AgentImpl::handleException(const Variant::Map& content, const Message& msg)
+{
+ const string& cid(msg.getCorrelationId());
+ Variant::Map::const_iterator aIter;
+ uint32_t correlator;
+ boost::shared_ptr<SyncContext> context;
+
+ try { correlator = boost::lexical_cast<uint32_t>(cid); }
+ catch(const boost::bad_lexical_cast&) { correlator = 0; }
+
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ map<uint32_t, boost::shared_ptr<SyncContext> >::iterator iter = contextMap.find(correlator);
+ if (iter != contextMap.end())
+ context = iter->second;
+ }
+
+ if (context.get() != 0) {
+ //
+ // This exception is associated with a synchronous request.
+ //
+ qpid::sys::Mutex::ScopedLock cl(context->lock);
+ context->response = ConsoleEvent(new ConsoleEventImpl(CONSOLE_EXCEPTION));
+ ConsoleEventImplAccess::get(context->response).addData(new DataImpl(content, this));
+ ConsoleEventImplAccess::get(context->response).setAgent(this);
+ context->cond.notify();
+ } else {
+ //
+ // This exception is associated with an asynchronous request.
+ //
+ auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_EXCEPTION));
+ eventImpl->setCorrelator(correlator);
+ eventImpl->setAgent(this);
+ eventImpl->addData(new DataImpl(content, this));
+ session.enqueueEvent(eventImpl.release());
+ }
+}
+
+
+void AgentImpl::handleMethodResponse(const Variant::Map& response, const Message& msg)
+{
+ const string& cid(msg.getCorrelationId());
+ Variant::Map::const_iterator aIter;
+ Variant::Map argMap;
+ uint32_t correlator;
+ boost::shared_ptr<SyncContext> context;
+
+ QPID_LOG(trace, "RCVD MethodResponse cid=" << cid << " map=" << response);
+
+ aIter = response.find("_arguments");
+ if (aIter != response.end())
+ argMap = aIter->second.asMap();
+
+ try { correlator = boost::lexical_cast<uint32_t>(cid); }
+ catch(const boost::bad_lexical_cast&) { correlator = 0; }
+
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ map<uint32_t, boost::shared_ptr<SyncContext> >::iterator iter = contextMap.find(correlator);
+ if (iter != contextMap.end())
+ context = iter->second;
+ }
+
+ if (context.get() != 0) {
+ //
+ // This response is associated with a synchronous request.
+ //
+ qpid::sys::Mutex::ScopedLock cl(context->lock);
+ context->response = ConsoleEvent(new ConsoleEventImpl(CONSOLE_METHOD_RESPONSE));
+ ConsoleEventImplAccess::get(context->response).setArguments(argMap);
+ ConsoleEventImplAccess::get(context->response).setAgent(this);
+ context->cond.notify();
+ } else {
+ //
+ // This response is associated with an asynchronous request.
+ //
+ auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_METHOD_RESPONSE));
+ eventImpl->setCorrelator(correlator);
+ eventImpl->setAgent(this);
+ eventImpl->setArguments(argMap);
+ session.enqueueEvent(eventImpl.release());
+ }
+}
+
+
+void AgentImpl::handleDataIndication(const Variant::List& list, const Message& msg)
+{
+ Variant::Map::const_iterator aIter;
+ const Variant::Map& props(msg.getProperties());
+ boost::shared_ptr<SyncContext> context;
+
+ aIter = props.find("qmf.content");
+ if (aIter == props.end())
+ return;
+
+ string content_type(aIter->second.asString());
+ if (content_type != "_event")
+ return;
+
+ for (Variant::List::const_iterator lIter = list.begin(); lIter != list.end(); lIter++) {
+ const Variant::Map& eventMap(lIter->asMap());
+ Data data(new DataImpl(eventMap, this));
+ int severity(SEV_NOTICE);
+ uint64_t timestamp(0);
+
+ aIter = eventMap.find("_severity");
+ if (aIter != eventMap.end())
+ severity = int(aIter->second.asInt8());
+
+ aIter = eventMap.find("_timestamp");
+ if (aIter != eventMap.end())
+ timestamp = aIter->second.asUint64();
+
+ auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_EVENT));
+ eventImpl->setAgent(this);
+ eventImpl->addData(data);
+ eventImpl->setSeverity(severity);
+ eventImpl->setTimestamp(timestamp);
+ if (data.hasSchema())
+ learnSchemaId(data.getSchemaId());
+ session.enqueueEvent(eventImpl.release());
+ }
+}
+
+
+void AgentImpl::handleQueryResponse(const Variant::List& list, const Message& msg)
+{
+ const string& cid(msg.getCorrelationId());
+ Variant::Map::const_iterator aIter;
+ const Variant::Map& props(msg.getProperties());
+ uint32_t correlator;
+ bool final(false);
+ boost::shared_ptr<SyncContext> context;
+
+ aIter = props.find("partial");
+ if (aIter == props.end())
+ final = true;
+
+ aIter = props.find("qmf.content");
+ if (aIter == props.end())
+ return;
+
+ string content_type(aIter->second.asString());
+ if (content_type != "_schema" && content_type != "_schema_id" && content_type != "_data")
+ return;
+
+ try { correlator = boost::lexical_cast<uint32_t>(cid); }
+ catch(const boost::bad_lexical_cast&) { correlator = 0; }
+
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ map<uint32_t, boost::shared_ptr<SyncContext> >::iterator iter = contextMap.find(correlator);
+ if (iter != contextMap.end())
+ context = iter->second;
+ }
+
+ if (context.get() != 0) {
+ //
+ // This response is associated with a synchronous request.
+ //
+ qpid::sys::Mutex::ScopedLock cl(context->lock);
+ if (!context->response.isValid())
+ context->response = ConsoleEvent(new ConsoleEventImpl(CONSOLE_QUERY_RESPONSE));
+
+ if (content_type == "_data")
+ for (Variant::List::const_iterator lIter = list.begin(); lIter != list.end(); lIter++) {
+ Data data(new DataImpl(lIter->asMap(), this));
+ ConsoleEventImplAccess::get(context->response).addData(data);
+ if (data.hasSchema())
+ learnSchemaId(data.getSchemaId());
+ }
+ else if (content_type == "_schema_id")
+ for (Variant::List::const_iterator lIter = list.begin(); lIter != list.end(); lIter++) {
+ SchemaId schemaId(new SchemaIdImpl(lIter->asMap()));
+ ConsoleEventImplAccess::get(context->response).addSchemaId(schemaId);
+ learnSchemaId(schemaId);
+ }
+ else if (content_type == "_schema")
+ for (Variant::List::const_iterator lIter = list.begin(); lIter != list.end(); lIter++) {
+ Schema schema(new SchemaImpl(lIter->asMap()));
+ schemaCache->declareSchema(schema);
+ }
+
+ if (final) {
+ ConsoleEventImplAccess::get(context->response).setFinal();
+ ConsoleEventImplAccess::get(context->response).setAgent(this);
+ context->cond.notify();
+ }
+ } else {
+ //
+ // This response is associated with an asynchronous request.
+ //
+ auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_QUERY_RESPONSE));
+ eventImpl->setCorrelator(correlator);
+ eventImpl->setAgent(this);
+
+ if (content_type == "_data")
+ for (Variant::List::const_iterator lIter = list.begin(); lIter != list.end(); lIter++) {
+ Data data(new DataImpl(lIter->asMap(), this));
+ eventImpl->addData(data);
+ if (data.hasSchema())
+ learnSchemaId(data.getSchemaId());
+ }
+ else if (content_type == "_schema_id")
+ for (Variant::List::const_iterator lIter = list.begin(); lIter != list.end(); lIter++) {
+ SchemaId schemaId(new SchemaIdImpl(lIter->asMap()));
+ eventImpl->addSchemaId(schemaId);
+ learnSchemaId(schemaId);
+ }
+ else if (content_type == "_schema")
+ for (Variant::List::const_iterator lIter = list.begin(); lIter != list.end(); lIter++) {
+ Schema schema(new SchemaImpl(lIter->asMap()));
+ schemaCache->declareSchema(schema);
+ }
+
+ if (final)
+ eventImpl->setFinal();
+ if (content_type != "_schema")
+ session.enqueueEvent(eventImpl.release());
+ }
+}
+
+
+Query AgentImpl::stringToQuery(const std::string& text)
+{
+ qpid::messaging::AddressParser parser(text);
+ Variant::Map map;
+ Variant::Map::const_iterator iter;
+ string className;
+ string packageName;
+
+ parser.parseMap(map);
+
+ iter = map.find("class");
+ if (iter != map.end())
+ className = iter->second.asString();
+
+ iter = map.find("package");
+ if (iter != map.end())
+ packageName = iter->second.asString();
+
+ Query query(QUERY_OBJECT, className, packageName);
+
+ iter = map.find("where");
+ if (iter != map.end())
+ query.setPredicate(iter->second.asList());
+
+ return query;
+}
+
+
+void AgentImpl::sendQuery(const Query& query, uint32_t correlator)
+{
+ Message msg;
+ Variant::Map map;
+ Variant::Map& headers(msg.getProperties());
+
+ headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_REQUEST;
+ headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_QUERY_REQUEST;
+ headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF;
+
+ msg.setReplyTo(session.replyAddress);
+ msg.setCorrelationId(boost::lexical_cast<string>(correlator));
+ msg.setSubject(directSubject);
+ string userId(session.connection.getAuthenticatedUsername());
+ if (!userId.empty())
+ msg.setUserId(userId);
+ encode(QueryImplAccess::get(query).asMap(), msg);
+ if (sender.isValid()) {
+ sender.send(msg);
+ QPID_LOG(trace, "SENT QueryRequest to=" << sender.getName() << "/" << directSubject << " cid=" << correlator);
+ }
+}
+
+
+void AgentImpl::sendMethod(const string& method, const Variant::Map& args, const DataAddr& addr, uint32_t correlator)
+{
+ Message msg;
+ Variant::Map map;
+ Variant::Map& headers(msg.getProperties());
+
+ headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_REQUEST;
+ headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_METHOD_REQUEST;
+ headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF;
+
+ map["_method_name"] = method;
+ map["_object_id"] = addr.asMap();
+ map["_arguments"] = args;
+
+ msg.setReplyTo(session.replyAddress);
+ msg.setCorrelationId(boost::lexical_cast<string>(correlator));
+ msg.setSubject(directSubject);
+ string userId(session.connection.getAuthenticatedUsername());
+ if (!userId.empty())
+ msg.setUserId(userId);
+ encode(map, msg);
+ if (sender.isValid()) {
+ sender.send(msg);
+ QPID_LOG(trace, "SENT MethodRequest method=" << method << " to=" << sender.getName() << "/" << directSubject << " content=" << map << " cid=" << correlator);
+ }
+}
+
+void AgentImpl::sendSchemaRequest(const SchemaId& id)
+{
+ uint32_t correlator(session.correlator());
+
+ if (capability >= AGENT_CAPABILITY_V2_SCHEMA) {
+ Query query(QUERY_SCHEMA, id);
+ sendQuery(query, correlator);
+ return;
+ }
+
+#define RAW_BUFFER_SIZE 1024
+ char rawBuffer[RAW_BUFFER_SIZE];
+ qpid::management::Buffer buffer(rawBuffer, RAW_BUFFER_SIZE);
+
+ buffer.putOctet('A');
+ buffer.putOctet('M');
+ buffer.putOctet('2');
+ buffer.putOctet('S');
+ buffer.putLong(correlator);
+ buffer.putShortString(id.getPackageName());
+ buffer.putShortString(id.getName());
+ buffer.putBin128(id.getHash().data());
+
+ string content(rawBuffer, buffer.getPosition());
+
+ Message msg;
+ msg.setReplyTo(session.replyAddress);
+ msg.setContent(content);
+ msg.setSubject(directSubject);
+ string userId(session.connection.getAuthenticatedUsername());
+ if (!userId.empty())
+ msg.setUserId(userId);
+ if (sender.isValid()) {
+ sender.send(msg);
+ QPID_LOG(trace, "SENT V1SchemaRequest to=" << sender.getName() << "/" << directSubject);
+ }
+}
+
+
+void AgentImpl::learnSchemaId(const SchemaId& id)
+{
+ schemaCache->declareSchemaId(id);
+ schemaIdSet.insert(id);
+}
+
+
+AgentImpl& AgentImplAccess::get(Agent& item)
+{
+ return *item.impl;
+}
+
+
+const AgentImpl& AgentImplAccess::get(const Agent& item)
+{
+ return *item.impl;
+}
diff --git a/qpid/cpp/src/qmf/AgentEvent.cpp b/qpid/cpp/src/qmf/AgentEvent.cpp
new file mode 100644
index 0000000000..2dc24ecac1
--- /dev/null
+++ b/qpid/cpp/src/qmf/AgentEvent.cpp
@@ -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.
+ *
+ */
+
+#include "qmf/AgentEventImpl.h"
+#include "qmf/PrivateImplRef.h"
+#include "qmf/SchemaImpl.h"
+
+using namespace std;
+using namespace qmf;
+using qpid::types::Variant;
+
+typedef PrivateImplRef<AgentEvent> PI;
+
+AgentEvent::AgentEvent(AgentEventImpl* impl) { PI::ctor(*this, impl); }
+AgentEvent::AgentEvent(const AgentEvent& s) : qmf::Handle<AgentEventImpl>() { PI::copy(*this, s); }
+AgentEvent::~AgentEvent() { PI::dtor(*this); }
+AgentEvent& AgentEvent::operator=(const AgentEvent& s) { return PI::assign(*this, s); }
+
+AgentEventCode AgentEvent::getType() const { return impl->getType(); }
+const string& AgentEvent::getUserId() const { return impl->getUserId(); }
+Query AgentEvent::getQuery() const { return impl->getQuery(); }
+bool AgentEvent::hasDataAddr() const { return impl->hasDataAddr(); }
+DataAddr AgentEvent::getDataAddr() const { return impl->getDataAddr(); }
+const string& AgentEvent::getMethodName() const { return impl->getMethodName(); }
+qpid::types::Variant::Map& AgentEvent::getArguments() { return impl->getArguments(); }
+qpid::types::Variant::Map& AgentEvent::getArgumentSubtypes() { return impl->getArgumentSubtypes(); }
+void AgentEvent::addReturnArgument(const std::string& k, const qpid::types::Variant& v, const std::string& s) { impl->addReturnArgument(k, v, s); }
+
+uint32_t AgentEventImpl::enqueueData(const Data& data)
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ dataQueue.push(data);
+ return dataQueue.size();
+}
+
+
+Data AgentEventImpl::dequeueData()
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ if (dataQueue.empty())
+ return Data();
+ Data data(dataQueue.front());
+ dataQueue.pop();
+ return data;
+}
+
+
+void AgentEventImpl::addReturnArgument(const string& key, const Variant& val, const string& subtype)
+{
+ if (schema.isValid() && !SchemaImplAccess::get(schema).isValidMethodOutArg(methodName, key, val))
+ throw QmfException("Output argument is unknown or the type is incompatible");
+ outArguments[key] = val;
+ if (!subtype.empty())
+ outArgumentSubtypes[key] = subtype;
+}
+
+
+AgentEventImpl& AgentEventImplAccess::get(AgentEvent& item)
+{
+ return *item.impl;
+}
+
+
+const AgentEventImpl& AgentEventImplAccess::get(const AgentEvent& item)
+{
+ return *item.impl;
+}
diff --git a/qpid/cpp/src/qmf/AgentEventImpl.h b/qpid/cpp/src/qmf/AgentEventImpl.h
new file mode 100644
index 0000000000..1ecb41775a
--- /dev/null
+++ b/qpid/cpp/src/qmf/AgentEventImpl.h
@@ -0,0 +1,96 @@
+#ifndef _QMF_AGENT_EVENT_IMPL_H_
+#define _QMF_AGENT_EVENT_IMPL_H_
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/RefCounted.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/messaging/Address.h"
+#include "qmf/AgentEvent.h"
+#include "qmf/Query.h"
+#include "qmf/DataAddr.h"
+#include "qmf/Data.h"
+#include "qmf/Schema.h"
+#include <queue>
+
+namespace qmf {
+ class AgentEventImpl : public virtual qpid::RefCounted {
+ public:
+ //
+ // Impl-only methods
+ //
+ AgentEventImpl(AgentEventCode e) : eventType(e) {}
+ void setUserId(const std::string& u) { userId = u; }
+ void setQuery(const Query& q) { query = q; }
+ void setDataAddr(const DataAddr& d) { dataAddr = d; }
+ void setMethodName(const std::string& m) { methodName = m; }
+ void setArguments(const qpid::types::Variant::Map& a) { arguments = a; }
+ void setArgumentSubtypes(const qpid::types::Variant::Map& a) { argumentSubtypes = a; }
+ void setReplyTo(const qpid::messaging::Address& r) { replyTo = r; }
+ void setSchema(const Schema& s) { schema = s; }
+ const qpid::messaging::Address& getReplyTo() { return replyTo; }
+ void setCorrelationId(const std::string& c) { correlationId = c; }
+ const std::string& getCorrelationId() { return correlationId; }
+ const qpid::types::Variant::Map& getReturnArguments() const { return outArguments; }
+ const qpid::types::Variant::Map& getReturnArgumentSubtypes() const { return outArgumentSubtypes; }
+ uint32_t enqueueData(const Data&);
+ Data dequeueData();
+
+ //
+ // Methods from API handle
+ //
+ AgentEventCode getType() const { return eventType; }
+ const std::string& getUserId() const { return userId; }
+ Query getQuery() const { return query; }
+ bool hasDataAddr() const { return dataAddr.isValid(); }
+ DataAddr getDataAddr() const { return dataAddr; }
+ const std::string& getMethodName() const { return methodName; }
+ qpid::types::Variant::Map& getArguments() { return arguments; }
+ qpid::types::Variant::Map& getArgumentSubtypes() { return argumentSubtypes; }
+ void addReturnArgument(const std::string&, const qpid::types::Variant&, const std::string&);
+
+ private:
+ const AgentEventCode eventType;
+ std::string userId;
+ qpid::messaging::Address replyTo;
+ std::string correlationId;
+ Query query;
+ DataAddr dataAddr;
+ Schema schema;
+ std::string methodName;
+ qpid::types::Variant::Map arguments;
+ qpid::types::Variant::Map argumentSubtypes;
+ qpid::types::Variant::Map outArguments;
+ qpid::types::Variant::Map outArgumentSubtypes;
+
+ qpid::sys::Mutex lock;
+ std::queue<Data> dataQueue;
+ };
+
+ struct AgentEventImplAccess
+ {
+ static AgentEventImpl& get(AgentEvent&);
+ static const AgentEventImpl& get(const AgentEvent&);
+ };
+}
+
+#endif
diff --git a/qpid/cpp/src/qmf/AgentImpl.h b/qpid/cpp/src/qmf/AgentImpl.h
new file mode 100644
index 0000000000..09754a3a7e
--- /dev/null
+++ b/qpid/cpp/src/qmf/AgentImpl.h
@@ -0,0 +1,122 @@
+#ifndef _QMF_AGENT_IMPL_H_
+#define _QMF_AGENT_IMPL_H_
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/RefCounted.h"
+#include "qmf/Agent.h"
+#include "qmf/ConsoleEventImpl.h"
+#include "qmf/ConsoleSessionImpl.h"
+#include "qmf/QueryImpl.h"
+#include "qmf/SchemaCache.h"
+#include "qpid/messaging/Session.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/messaging/Sender.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/Condition.h"
+#include <boost/shared_ptr.hpp>
+#include <map>
+#include <set>
+
+namespace qmf {
+ class AgentImpl : public virtual qpid::RefCounted {
+ public:
+ //
+ // Impl-only methods
+ //
+ AgentImpl(const std::string& n, uint32_t e, ConsoleSessionImpl& s);
+ void setAttribute(const std::string& k, const qpid::types::Variant& v);
+ void setAttribute(const std::string& k, const std::string& v) { attributes[k] = v; }
+ void touch() { touched = true; }
+ uint32_t age() { untouchedCount = touched ? 0 : untouchedCount + 1; touched = false; return untouchedCount; }
+ uint32_t getCapability() const { return capability; }
+ void handleException(const qpid::types::Variant::Map&, const qpid::messaging::Message&);
+ void handleMethodResponse(const qpid::types::Variant::Map&, const qpid::messaging::Message&);
+ void handleDataIndication(const qpid::types::Variant::List&, const qpid::messaging::Message&);
+ void handleQueryResponse(const qpid::types::Variant::List&, const qpid::messaging::Message&);
+
+ //
+ // Methods from API handle
+ //
+ const std::string& getName() const { return name; }
+ uint32_t getEpoch() const { return epoch; }
+ void setEpoch(uint32_t e) { epoch = e; }
+ std::string getVendor() const { return getAttribute("_vendor").asString(); }
+ std::string getProduct() const { return getAttribute("_product").asString(); }
+ std::string getInstance() const { return getAttribute("_instance").asString(); }
+ const qpid::types::Variant& getAttribute(const std::string& k) const;
+ const qpid::types::Variant::Map& getAttributes() const { return attributes; }
+
+ ConsoleEvent querySchema(qpid::messaging::Duration t) { return query(Query(QUERY_SCHEMA_ID), t); }
+ uint32_t querySchemaAsync() { return queryAsync(Query(QUERY_SCHEMA_ID)); }
+
+ ConsoleEvent query(const Query& q, qpid::messaging::Duration t);
+ ConsoleEvent query(const std::string& q, qpid::messaging::Duration t);
+ uint32_t queryAsync(const Query& q);
+ uint32_t queryAsync(const std::string& q);
+
+ ConsoleEvent callMethod(const std::string& m, const qpid::types::Variant::Map& a, const DataAddr&, qpid::messaging::Duration t);
+ uint32_t callMethodAsync(const std::string& m, const qpid::types::Variant::Map& a, const DataAddr&);
+
+ uint32_t getPackageCount() const;
+ const std::string& getPackage(uint32_t i) const;
+ uint32_t getSchemaIdCount(const std::string& p) const;
+ SchemaId getSchemaId(const std::string& p, uint32_t i) const;
+ Schema getSchema(const SchemaId& s, qpid::messaging::Duration t);
+
+ private:
+ struct SyncContext {
+ qpid::sys::Mutex lock;
+ qpid::sys::Condition cond;
+ ConsoleEvent response;
+ };
+
+ mutable qpid::sys::Mutex lock;
+ std::string name;
+ std::string directSubject;
+ uint32_t epoch;
+ ConsoleSessionImpl& session;
+ bool touched;
+ uint32_t untouchedCount;
+ uint32_t capability;
+ qpid::messaging::Sender sender;
+ qpid::types::Variant::Map attributes;
+ std::map<uint32_t, boost::shared_ptr<SyncContext> > contextMap;
+ boost::shared_ptr<SchemaCache> schemaCache;
+ mutable std::set<std::string> packageSet;
+ std::set<SchemaId, SchemaIdCompare> schemaIdSet;
+
+ Query stringToQuery(const std::string&);
+ void sendQuery(const Query&, uint32_t);
+ void sendSchemaIdQuery(uint32_t);
+ void sendMethod(const std::string&, const qpid::types::Variant::Map&, const DataAddr&, uint32_t);
+ void sendSchemaRequest(const SchemaId&);
+ void learnSchemaId(const SchemaId&);
+ };
+
+ struct AgentImplAccess
+ {
+ static AgentImpl& get(Agent&);
+ static const AgentImpl& get(const Agent&);
+ };
+}
+
+#endif
diff --git a/qpid/cpp/src/qmf/AgentSession.cpp b/qpid/cpp/src/qmf/AgentSession.cpp
new file mode 100644
index 0000000000..4605285448
--- /dev/null
+++ b/qpid/cpp/src/qmf/AgentSession.cpp
@@ -0,0 +1,1031 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qmf/AgentSessionImpl.h"
+
+#include <iostream>
+#include <memory>
+
+namespace qmf {
+
+using std::string;
+using std::map;
+
+using qpid::messaging::Address;
+using qpid::messaging::Connection;
+using qpid::messaging::Duration;
+using qpid::messaging::Message;
+using qpid::messaging::Receiver;
+using qpid::messaging::Sender;
+using qpid::types::Variant;
+
+AgentSession::AgentSession(AgentSessionImpl* impl) { PI::ctor(*this, impl); }
+AgentSession::AgentSession(const AgentSession& s) : qmf::Handle<AgentSessionImpl>() { PI::copy(*this, s); }
+AgentSession::~AgentSession() { PI::dtor(*this); }
+AgentSession& AgentSession::operator=(const AgentSession& s) { return PI::assign(*this, s); }
+
+AgentSession::AgentSession(Connection& c, const string& o) { PI::ctor(*this, new AgentSessionImpl(c, o)); }
+void AgentSession::setDomain(const string& d) { impl->setDomain(d); }
+void AgentSession::setVendor(const string& v) { impl->setVendor(v); }
+void AgentSession::setProduct(const string& p) { impl->setProduct(p); }
+void AgentSession::setInstance(const string& i) { impl->setInstance(i); }
+void AgentSession::setAttribute(const string& k, const qpid::types::Variant& v) { impl->setAttribute(k, v); }
+const string& AgentSession::getName() const { return impl->getName(); }
+void AgentSession::open() { impl->open(); }
+void AgentSession::close() { impl->close(); }
+bool AgentSession::nextEvent(AgentEvent& e, Duration t) { return impl->nextEvent(e, t); }
+int AgentSession::pendingEvents() const { return impl->pendingEvents(); }
+void AgentSession::registerSchema(Schema& s) { impl->registerSchema(s); }
+DataAddr AgentSession::addData(Data& d, const string& n, bool p) { return impl->addData(d, n, p); }
+void AgentSession::delData(const DataAddr& a) { impl->delData(a); }
+void AgentSession::authAccept(AgentEvent& e) { impl->authAccept(e); }
+void AgentSession::authReject(AgentEvent& e, const string& m) { impl->authReject(e, m); }
+void AgentSession::raiseException(AgentEvent& e, const string& s) { impl->raiseException(e, s); }
+void AgentSession::raiseException(AgentEvent& e, const Data& d) { impl->raiseException(e, d); }
+void AgentSession::response(AgentEvent& e, const Data& d) { impl->response(e, d); }
+void AgentSession::complete(AgentEvent& e) { impl->complete(e); }
+void AgentSession::methodSuccess(AgentEvent& e) { impl->methodSuccess(e); }
+void AgentSession::raiseEvent(const Data& d) { impl->raiseEvent(d); }
+void AgentSession::raiseEvent(const Data& d, int s) { impl->raiseEvent(d, s); }
+
+//========================================================================================
+// Impl Method Bodies
+//========================================================================================
+
+AgentSessionImpl::AgentSessionImpl(Connection& c, const string& options) :
+ connection(c), domain("default"), opened(false), eventNotifier(0), thread(0), threadCanceled(false),
+ bootSequence(1), interval(60), lastHeartbeat(0), lastVisit(0), forceHeartbeat(false),
+ externalStorage(false), autoAllowQueries(true), autoAllowMethods(true),
+ maxSubscriptions(64), minSubInterval(3000), subLifetime(300), publicEvents(true),
+ listenOnDirect(true), strictSecurity(false), maxThreadWaitTime(5),
+ schemaUpdateTime(uint64_t(qpid::sys::Duration::FromEpoch()))
+{
+ //
+ // Set Agent Capability Level
+ //
+ attributes["qmf.agent_capability"] = AGENT_CAPABILITY_0_8;
+
+ if (!options.empty()) {
+ qpid::messaging::AddressParser parser(options);
+ Variant::Map optMap;
+ Variant::Map::const_iterator iter;
+
+ parser.parseMap(optMap);
+
+ iter = optMap.find("domain");
+ if (iter != optMap.end())
+ domain = iter->second.asString();
+
+ iter = optMap.find("interval");
+ if (iter != optMap.end()) {
+ interval = iter->second.asUint32();
+ if (interval < 1)
+ interval = 1;
+ }
+
+ iter = optMap.find("external");
+ if (iter != optMap.end())
+ externalStorage = iter->second.asBool();
+
+ iter = optMap.find("allow-queries");
+ if (iter != optMap.end())
+ autoAllowQueries = iter->second.asBool();
+
+ iter = optMap.find("allow-methods");
+ if (iter != optMap.end())
+ autoAllowMethods = iter->second.asBool();
+
+ iter = optMap.find("max-subscriptions");
+ if (iter != optMap.end())
+ maxSubscriptions = iter->second.asUint32();
+
+ iter = optMap.find("min-sub-interval");
+ if (iter != optMap.end())
+ minSubInterval = iter->second.asUint32();
+
+ iter = optMap.find("sub-lifetime");
+ if (iter != optMap.end())
+ subLifetime = iter->second.asUint32();
+
+ iter = optMap.find("public-events");
+ if (iter != optMap.end())
+ publicEvents = iter->second.asBool();
+
+ iter = optMap.find("listen-on-direct");
+ if (iter != optMap.end())
+ listenOnDirect = iter->second.asBool();
+
+ iter = optMap.find("strict-security");
+ if (iter != optMap.end())
+ strictSecurity = iter->second.asBool();
+
+ iter = optMap.find("max-thread-wait-time");
+ if (iter != optMap.end())
+ maxThreadWaitTime = iter->second.asUint32();
+ }
+
+ if (maxThreadWaitTime > interval)
+ maxThreadWaitTime = interval;
+}
+
+
+AgentSessionImpl::~AgentSessionImpl()
+{
+ if (opened)
+ close();
+
+ if (thread) {
+ thread->join();
+ delete thread;
+ }
+}
+
+
+void AgentSessionImpl::open()
+{
+ if (opened)
+ throw QmfException("The session is already open");
+
+ // If the thread exists, join and delete it before creating a new one.
+ if (thread) {
+ thread->join();
+ delete thread;
+ }
+
+ const string addrArgs(";{create:never,node:{type:topic}}");
+ const string routableAddr("direct-agent.route." + qpid::types::Uuid(true).str());
+ attributes["_direct_subject"] = routableAddr;
+
+ // Establish messaging addresses
+ setAgentName();
+ directBase = "qmf." + domain + ".direct";
+ topicBase = "qmf." + domain + ".topic";
+
+ // Create AMQP session, receivers, and senders
+ session = connection.createSession();
+ Receiver directRx;
+ Receiver routableDirectRx = session.createReceiver(topicBase + "/" + routableAddr + addrArgs);
+ Receiver topicRx = session.createReceiver(topicBase + "/console.#" + addrArgs);
+
+ if (listenOnDirect && !strictSecurity) {
+ directRx = session.createReceiver(directBase + "/" + agentName + addrArgs);
+ directRx.setCapacity(64);
+ }
+
+ routableDirectRx.setCapacity(64);
+ topicRx.setCapacity(64);
+
+ if (!strictSecurity)
+ directSender = session.createSender(directBase + addrArgs);
+ topicSender = session.createSender(topicBase + addrArgs);
+
+ // Start the receiver thread
+ threadCanceled = false;
+ opened = true;
+ thread = new qpid::sys::Thread(*this);
+
+ // Send an initial agent heartbeat message
+ sendHeartbeat();
+}
+
+
+void AgentSessionImpl::closeAsync()
+{
+ if (!opened)
+ return;
+
+ // Stop the receiver thread. Don't join it until the destructor is called or open() is called.
+ threadCanceled = true;
+ opened = false;
+}
+
+
+void AgentSessionImpl::close()
+{
+ closeAsync();
+
+ if (thread) {
+ thread->join();
+ delete thread;
+ thread = 0;
+ }
+}
+
+
+bool AgentSessionImpl::nextEvent(AgentEvent& event, Duration timeout)
+{
+ uint64_t milliseconds = timeout.getMilliseconds();
+ qpid::sys::Mutex::ScopedLock l(lock);
+
+ if (eventQueue.empty() && milliseconds > 0) {
+ int64_t nsecs(qpid::sys::TIME_INFINITE);
+ if ((uint64_t)(nsecs / 1000000) > milliseconds)
+ nsecs = (int64_t) milliseconds * 1000000;
+ qpid::sys::Duration then(nsecs);
+ cond.wait(lock, qpid::sys::AbsTime(qpid::sys::now(), then));
+ }
+
+ if (!eventQueue.empty()) {
+ event = eventQueue.front();
+ eventQueue.pop();
+ if (eventQueue.empty())
+ alertEventNotifierLH(false);
+ return true;
+ }
+
+ return false;
+}
+
+
+int AgentSessionImpl::pendingEvents() const
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ return eventQueue.size();
+}
+
+
+void AgentSessionImpl::setEventNotifier(EventNotifierImpl* notifier)
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ eventNotifier = notifier;
+}
+
+EventNotifierImpl* AgentSessionImpl::getEventNotifier() const
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ return eventNotifier;
+}
+
+
+void AgentSessionImpl::registerSchema(Schema& schema)
+{
+ if (!schema.isFinalized())
+ schema.finalize();
+ const SchemaId& schemaId(schema.getSchemaId());
+
+ qpid::sys::Mutex::ScopedLock l(lock);
+ schemata[schemaId] = schema;
+ schemaIndex[schemaId] = DataIndex();
+
+ //
+ // Get the news out at the next periodic interval that there is new schema information.
+ //
+ schemaUpdateTime = uint64_t(qpid::sys::Duration::FromEpoch());
+ forceHeartbeat = true;
+}
+
+
+DataAddr AgentSessionImpl::addData(Data& data, const string& name, bool persistent)
+{
+ if (externalStorage)
+ throw QmfException("addData() must not be called when the 'external' option is enabled.");
+
+ string dataName;
+ if (name.empty())
+ dataName = qpid::types::Uuid(true).str();
+ else
+ dataName = name;
+
+ DataAddr addr(dataName, agentName, persistent ? 0 : bootSequence);
+ data.setAddr(addr);
+
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ DataIndex::const_iterator iter = globalIndex.find(addr);
+ if (iter != globalIndex.end())
+ throw QmfException("Duplicate Data Address");
+
+ globalIndex[addr] = data;
+ if (data.hasSchema())
+ schemaIndex[data.getSchemaId()][addr] = data;
+ }
+
+ //
+ // TODO: Survey active subscriptions to see if they need to hear about this new data.
+ //
+
+ return addr;
+}
+
+
+void AgentSessionImpl::delData(const DataAddr& addr)
+{
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ DataIndex::iterator iter = globalIndex.find(addr);
+ if (iter == globalIndex.end())
+ return;
+ if (iter->second.hasSchema()) {
+ const SchemaId& schemaId(iter->second.getSchemaId());
+ schemaIndex[schemaId].erase(addr);
+ }
+ globalIndex.erase(iter);
+ }
+
+ //
+ // TODO: Survey active subscriptions to see if they need to hear about this deleted data.
+ //
+}
+
+
+void AgentSessionImpl::authAccept(AgentEvent& authEvent)
+{
+ std::auto_ptr<AgentEventImpl> eventImpl(new AgentEventImpl(AGENT_QUERY));
+ eventImpl->setQuery(authEvent.getQuery());
+ eventImpl->setUserId(authEvent.getUserId());
+ eventImpl->setReplyTo(AgentEventImplAccess::get(authEvent).getReplyTo());
+ eventImpl->setCorrelationId(AgentEventImplAccess::get(authEvent).getCorrelationId());
+ AgentEvent event(eventImpl.release());
+
+ if (externalStorage) {
+ enqueueEvent(event);
+ return;
+ }
+
+ const Query& query(authEvent.getQuery());
+ if (query.getDataAddr().isValid()) {
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ DataIndex::const_iterator iter = globalIndex.find(query.getDataAddr());
+ if (iter != globalIndex.end())
+ response(event, iter->second);
+ }
+ complete(event);
+ return;
+ }
+
+ if (query.getSchemaId().isValid()) {
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ map<SchemaId, DataIndex, SchemaIdCompareNoHash>::const_iterator iter = schemaIndex.find(query.getSchemaId());
+ if (iter != schemaIndex.end())
+ for (DataIndex::const_iterator dIter = iter->second.begin(); dIter != iter->second.end(); dIter++)
+ if (query.matchesPredicate(dIter->second.getProperties()))
+ response(event, dIter->second);
+ }
+ complete(event);
+ return;
+ }
+
+ raiseException(event, "Query is Invalid");
+}
+
+
+void AgentSessionImpl::authReject(AgentEvent& event, const string& error)
+{
+ raiseException(event, "Action Forbidden - " + error);
+}
+
+
+void AgentSessionImpl::raiseException(AgentEvent& event, const string& error)
+{
+ Data exception(new DataImpl());
+ exception.setProperty("error_text", error);
+ raiseException(event, exception);
+}
+
+
+void AgentSessionImpl::raiseException(AgentEvent& event, const Data& data)
+{
+ Message msg;
+ Variant::Map map;
+ Variant::Map& headers(msg.getProperties());
+
+ headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_RESPONSE;
+ headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_EXCEPTION;
+ headers[protocol::HEADER_KEY_CONTENT] = protocol::HEADER_CONTENT_DATA;
+ headers[protocol::HEADER_KEY_AGENT] = agentName;
+ headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF;
+
+ AgentEventImpl& eventImpl(AgentEventImplAccess::get(event));
+ const DataImpl& dataImpl(DataImplAccess::get(data));
+
+ msg.setCorrelationId(eventImpl.getCorrelationId());
+ encode(dataImpl.asMap(), msg);
+ send(msg, eventImpl.getReplyTo());
+
+ QPID_LOG(trace, "SENT Exception to=" << eventImpl.getReplyTo());
+}
+
+
+void AgentSessionImpl::response(AgentEvent& event, const Data& data)
+{
+ AgentEventImpl& impl(AgentEventImplAccess::get(event));
+ uint32_t count = impl.enqueueData(data);
+ if (count >= 8)
+ flushResponses(event, false);
+}
+
+
+void AgentSessionImpl::complete(AgentEvent& event)
+{
+ flushResponses(event, true);
+}
+
+
+void AgentSessionImpl::methodSuccess(AgentEvent& event)
+{
+ Message msg;
+ Variant::Map map;
+ Variant::Map& headers(msg.getProperties());
+
+ headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_RESPONSE;
+ headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_METHOD_RESPONSE;
+ headers[protocol::HEADER_KEY_AGENT] = agentName;
+ headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF;
+
+ AgentEventImpl& eventImpl(AgentEventImplAccess::get(event));
+
+ const Variant::Map& outArgs(eventImpl.getReturnArguments());
+ const Variant::Map& outSubtypes(eventImpl.getReturnArgumentSubtypes());
+
+ map["_arguments"] = outArgs;
+ if (!outSubtypes.empty())
+ map["_subtypes"] = outSubtypes;
+
+ msg.setCorrelationId(eventImpl.getCorrelationId());
+ encode(map, msg);
+ send(msg, eventImpl.getReplyTo());
+
+ QPID_LOG(trace, "SENT MethodResponse to=" << eventImpl.getReplyTo());
+}
+
+
+void AgentSessionImpl::raiseEvent(const Data& data)
+{
+ int severity(SEV_NOTICE);
+ if (data.hasSchema()) {
+ const Schema& schema(DataImplAccess::get(data).getSchema());
+ if (schema.isValid())
+ severity = schema.getDefaultSeverity();
+ }
+
+ raiseEvent(data, severity);
+}
+
+
+void AgentSessionImpl::raiseEvent(const Data& data, int severity)
+{
+ Message msg;
+ Variant::Map map;
+ Variant::Map& headers(msg.getProperties());
+ string subject("agent.ind.event");
+
+ if (data.hasSchema()) {
+ const SchemaId& schemaId(data.getSchemaId());
+ if (schemaId.getType() != SCHEMA_TYPE_EVENT)
+ throw QmfException("Cannot call raiseEvent on data that is not an Event");
+ subject = subject + "." + schemaId.getPackageName() + "." + schemaId.getName();
+ }
+
+ if (severity < SEV_EMERG || severity > SEV_DEBUG)
+ throw QmfException("Invalid severity value");
+
+ headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_INDICATION;
+ headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_DATA_INDICATION;
+ headers[protocol::HEADER_KEY_CONTENT] = protocol::HEADER_CONTENT_EVENT;
+ headers[protocol::HEADER_KEY_AGENT] = agentName;
+ headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF;
+ msg.setSubject(subject);
+
+ Variant::List list;
+ Variant::Map dataAsMap(DataImplAccess::get(data).asMap());
+ dataAsMap["_severity"] = severity;
+ dataAsMap["_timestamp"] = uint64_t(qpid::sys::Duration::FromEpoch());
+ list.push_back(dataAsMap);
+ encode(list, msg);
+ topicSender.send(msg);
+
+ QPID_LOG(trace, "SENT EventIndication to=" << topicSender.getName() << "/" << subject);
+}
+
+
+void AgentSessionImpl::checkOpen()
+{
+ if (opened)
+ throw QmfException("Operation must be performed before calling open()");
+}
+
+
+void AgentSessionImpl::enqueueEvent(const AgentEvent& event)
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ bool notify = eventQueue.empty();
+ eventQueue.push(event);
+ if (notify) {
+ cond.notify();
+ alertEventNotifierLH(true);
+ }
+}
+
+
+void AgentSessionImpl::setAgentName()
+{
+ Variant::Map::iterator iter;
+ string vendor;
+ string product;
+ string instance;
+
+ iter = attributes.find("_vendor");
+ if (iter == attributes.end())
+ attributes["_vendor"] = vendor;
+ else
+ vendor = iter->second.asString();
+
+ iter = attributes.find("_product");
+ if (iter == attributes.end())
+ attributes["_product"] = product;
+ else
+ product = iter->second.asString();
+
+ iter = attributes.find("_instance");
+ if (iter == attributes.end()) {
+ instance = qpid::types::Uuid(true).str();
+ attributes["_instance"] = instance;
+ } else
+ instance = iter->second.asString();
+
+ agentName = vendor + ":" + product + ":" + instance;
+ attributes["_name"] = agentName;
+}
+
+
+void AgentSessionImpl::handleLocateRequest(const Variant::List& predicate, const Message& msg)
+{
+ QPID_LOG(trace, "RCVD AgentLocateRequest from=" << msg.getReplyTo());
+
+ if (!predicate.empty()) {
+ Query agentQuery(QUERY_OBJECT);
+ agentQuery.setPredicate(predicate);
+ if (!agentQuery.matchesPredicate(attributes)) {
+ QPID_LOG(trace, "AgentLocate predicate does not match this agent, ignoring");
+ return;
+ }
+ }
+
+ Message reply;
+ Variant::Map map;
+ Variant::Map& headers(reply.getProperties());
+
+ headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_INDICATION;
+ headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_AGENT_LOCATE_RESPONSE;
+ headers[protocol::HEADER_KEY_AGENT] = agentName;
+ headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF;
+
+ map["_values"] = attributes;
+ map["_values"].asMap()[protocol::AGENT_ATTR_TIMESTAMP] = uint64_t(qpid::sys::Duration::FromEpoch());
+ map["_values"].asMap()[protocol::AGENT_ATTR_HEARTBEAT_INTERVAL] = interval;
+ map["_values"].asMap()[protocol::AGENT_ATTR_EPOCH] = bootSequence;
+ map["_values"].asMap()[protocol::AGENT_ATTR_SCHEMA_UPDATED_TIMESTAMP] = schemaUpdateTime;
+
+ encode(map, reply);
+ send(reply, msg.getReplyTo());
+ QPID_LOG(trace, "SENT AgentLocateResponse to=" << msg.getReplyTo());
+}
+
+
+void AgentSessionImpl::handleMethodRequest(const Variant::Map& content, const Message& msg)
+{
+ QPID_LOG(trace, "RCVD MethodRequest map=" << content << " from=" << msg.getReplyTo() << " cid=" << msg.getCorrelationId());
+
+ //
+ // Construct an AgentEvent to be sent to the application.
+ //
+ std::auto_ptr<AgentEventImpl> eventImpl(new AgentEventImpl(AGENT_METHOD));
+ eventImpl->setUserId(msg.getUserId());
+ eventImpl->setReplyTo(msg.getReplyTo());
+ eventImpl->setCorrelationId(msg.getCorrelationId());
+
+ Variant::Map::const_iterator iter;
+
+ iter = content.find("_method_name");
+ if (iter == content.end()) {
+ AgentEvent event(eventImpl.release());
+ raiseException(event, "Malformed MethodRequest: missing _method_name field");
+ return;
+ }
+ eventImpl->setMethodName(iter->second.asString());
+
+ iter = content.find("_arguments");
+ if (iter != content.end())
+ eventImpl->setArguments(iter->second.asMap());
+
+ iter = content.find("_subtypes");
+ if (iter != content.end())
+ eventImpl->setArgumentSubtypes(iter->second.asMap());
+
+ iter = content.find("_object_id");
+ if (iter != content.end()) {
+ DataAddr addr(new DataAddrImpl(iter->second.asMap()));
+ eventImpl->setDataAddr(addr);
+ if (!externalStorage) {
+ DataIndex::const_iterator iter(globalIndex.find(addr));
+ if (iter == globalIndex.end()) {
+ AgentEvent event(eventImpl.release());
+ raiseException(event, "No data object found with the specified address");
+ return;
+ }
+
+ const Schema& schema(DataImplAccess::get(iter->second).getSchema());
+ if (schema.isValid()) {
+ eventImpl->setSchema(schema);
+ for (Variant::Map::const_iterator aIter = eventImpl->getArguments().begin();
+ aIter != eventImpl->getArguments().end(); aIter++) {
+ const Schema& schema(DataImplAccess::get(iter->second).getSchema());
+ if (!SchemaImplAccess::get(schema).isValidMethodInArg(eventImpl->getMethodName(), aIter->first, aIter->second)) {
+ AgentEvent event(eventImpl.release());
+ raiseException(event, "Invalid argument: " + aIter->first);
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ enqueueEvent(AgentEvent(eventImpl.release()));
+}
+
+
+void AgentSessionImpl::handleQueryRequest(const Variant::Map& content, const Message& msg)
+{
+ QPID_LOG(trace, "RCVD QueryRequest query=" << content << " from=" << msg.getReplyTo() << " cid=" << msg.getCorrelationId());
+
+ //
+ // Construct an AgentEvent to be sent to the application or directly handled by the agent.
+ //
+ std::auto_ptr<QueryImpl> queryImpl(new QueryImpl(content));
+ std::auto_ptr<AgentEventImpl> eventImpl(new AgentEventImpl(AGENT_AUTH_QUERY));
+ eventImpl->setUserId(msg.getUserId());
+ eventImpl->setReplyTo(msg.getReplyTo());
+ eventImpl->setCorrelationId(msg.getCorrelationId());
+ eventImpl->setQuery(queryImpl.release());
+ AgentEvent ae(eventImpl.release());
+
+ if (ae.getQuery().getTarget() == QUERY_SCHEMA_ID || ae.getQuery().getTarget() == QUERY_SCHEMA) {
+ handleSchemaRequest(ae);
+ return;
+ }
+
+ if (autoAllowQueries)
+ authAccept(ae);
+ else
+ enqueueEvent(ae);
+}
+
+
+void AgentSessionImpl::handleSchemaRequest(AgentEvent& event)
+{
+ SchemaMap::const_iterator iter;
+ string error;
+ const Query& query(event.getQuery());
+
+ Message msg;
+ Variant::List content;
+ Variant::Map map;
+ Variant::Map& headers(msg.getProperties());
+
+ headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_RESPONSE;
+ headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_QUERY_RESPONSE;
+ headers[protocol::HEADER_KEY_AGENT] = agentName;
+ headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF;
+
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ if (query.getTarget() == QUERY_SCHEMA_ID) {
+ headers[protocol::HEADER_KEY_CONTENT] = "_schema_id";
+ for (iter = schemata.begin(); iter != schemata.end(); iter++)
+ content.push_back(SchemaIdImplAccess::get(iter->first).asMap());
+ } else if (query.getSchemaId().isValid()) {
+ headers[protocol::HEADER_KEY_CONTENT] = "_schema";
+ iter = schemata.find(query.getSchemaId());
+ if (iter != schemata.end())
+ content.push_back(SchemaImplAccess::get(iter->second).asMap());
+ } else {
+ error = "Invalid Schema Query: Requests for SCHEMA must supply a valid schema ID.";
+ }
+ }
+
+ if (!error.empty()) {
+ raiseException(event, error);
+ return;
+ }
+
+ AgentEventImpl& eventImpl(AgentEventImplAccess::get(event));
+
+ msg.setCorrelationId(eventImpl.getCorrelationId());
+ encode(content, msg);
+ send(msg, eventImpl.getReplyTo());
+
+ QPID_LOG(trace, "SENT QueryResponse(Schema) to=" << eventImpl.getReplyTo());
+}
+
+
+void AgentSessionImpl::handleV1SchemaRequest(qpid::management::Buffer& buffer, uint32_t seq, const Message& msg)
+{
+ string packageName;
+ string className;
+ uint8_t hashBits[16];
+
+ buffer.getShortString(packageName);
+ buffer.getShortString(className);
+ buffer.getBin128(hashBits);
+
+ QPID_LOG(trace, "RCVD QMFv1 SchemaRequest for " << packageName << ":" << className);
+
+ qpid::types::Uuid hash(hashBits);
+ map<SchemaId, Schema, SchemaIdCompare>::const_iterator iter;
+ string replyContent;
+
+ SchemaId dataId(SCHEMA_TYPE_DATA, packageName, className);
+ dataId.setHash(hash);
+
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ iter = schemata.find(dataId);
+ if (iter != schemata.end())
+ replyContent = SchemaImplAccess::get(iter->second).asV1Content(seq);
+ else {
+ SchemaId eventId(SCHEMA_TYPE_EVENT, packageName, className);
+ eventId.setHash(hash);
+ iter = schemata.find(dataId);
+ if (iter != schemata.end())
+ replyContent = SchemaImplAccess::get(iter->second).asV1Content(seq);
+ else
+ return;
+ }
+ }
+
+ Message reply;
+ Variant::Map& headers(reply.getProperties());
+
+ headers[protocol::HEADER_KEY_AGENT] = agentName;
+ reply.setContent(replyContent);
+
+ send(reply, msg.getReplyTo());
+ QPID_LOG(trace, "SENT QMFv1 SchemaResponse to=" << msg.getReplyTo());
+}
+
+
+void AgentSessionImpl::dispatch(Message msg)
+{
+ const Variant::Map& properties(msg.getProperties());
+ Variant::Map::const_iterator iter;
+
+ //
+ // If strict-security is enabled, make sure that reply-to address complies with the
+ // strict-security addressing pattern (i.e. start with 'qmf.<domain>.topic/direct-console.').
+ //
+ if (strictSecurity && msg.getReplyTo()) {
+ if (msg.getReplyTo().getName() != topicBase || msg.getReplyTo().getSubject().find("direct-console.") != 0) {
+ QPID_LOG(warning, "Reply-to violates strict-security policy: " << msg.getReplyTo().str());
+ return;
+ }
+ }
+
+ iter = properties.find(protocol::HEADER_KEY_APP_ID);
+ if (iter != properties.end() && iter->second.asString() == protocol::HEADER_APP_ID_QMF) {
+ //
+ // Dispatch a QMFv2 formatted message
+ //
+ iter = properties.find(protocol::HEADER_KEY_OPCODE);
+ if (iter == properties.end()) {
+ QPID_LOG(trace, "Message received with no 'qmf.opcode' header");
+ return;
+ }
+
+ const string& opcode = iter->second.asString();
+
+ if (msg.getContentType() == "amqp/list") {
+ Variant::List content;
+ decode(msg, content);
+
+ if (opcode == protocol::HEADER_OPCODE_AGENT_LOCATE_REQUEST) handleLocateRequest(content, msg);
+ else {
+ QPID_LOG(trace, "Unexpected QMFv2 opcode with 'amqp/list' content: " << opcode);
+ }
+
+ } else if (msg.getContentType() == "amqp/map") {
+ Variant::Map content;
+ decode(msg, content);
+
+ if (opcode == protocol::HEADER_OPCODE_METHOD_REQUEST) handleMethodRequest(content, msg);
+ else if (opcode == protocol::HEADER_OPCODE_QUERY_REQUEST) handleQueryRequest(content, msg);
+ else {
+ QPID_LOG(trace, "Unexpected QMFv2 opcode with 'amqp/map' content: " << opcode);
+ }
+ } else {
+ QPID_LOG(trace, "Unexpected QMFv2 content type. Expected amqp/list or amqp/map");
+ }
+
+ } else {
+ //
+ // Dispatch a QMFv1 formatted message
+ //
+ const string& body(msg.getContent());
+ if (body.size() < 8)
+ return;
+ qpid::management::Buffer buffer(const_cast<char*>(body.c_str()), body.size());
+
+ if (buffer.getOctet() != 'A') return;
+ if (buffer.getOctet() != 'M') return;
+ if (buffer.getOctet() != '2') return;
+ char v1Opcode(buffer.getOctet());
+ uint32_t seq(buffer.getLong());
+
+ if (v1Opcode == 'S') handleV1SchemaRequest(buffer, seq, msg);
+ else {
+ QPID_LOG(trace, "Unknown or Unsupported QMFv1 opcode: " << v1Opcode);
+ }
+ }
+}
+
+
+void AgentSessionImpl::sendHeartbeat()
+{
+ Message msg;
+ Variant::Map map;
+ Variant::Map& headers(msg.getProperties());
+ std::stringstream address;
+
+ address << "agent.ind.heartbeat";
+
+ // append .<vendor>.<product> to address key if present.
+ Variant::Map::const_iterator v;
+ if ((v = attributes.find("_vendor")) != attributes.end() && !v->second.getString().empty()) {
+ address << "." << v->second.getString();
+ if ((v = attributes.find("_product")) != attributes.end() && !v->second.getString().empty()) {
+ address << "." << v->second.getString();
+ }
+ }
+
+ headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_INDICATION;
+ headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_AGENT_HEARTBEAT_INDICATION;
+ headers[protocol::HEADER_KEY_AGENT] = agentName;
+ headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF;
+ msg.setSubject(address.str());
+
+ map["_values"] = attributes;
+ map["_values"].asMap()[protocol::AGENT_ATTR_TIMESTAMP] = uint64_t(qpid::sys::Duration::FromEpoch());
+ map["_values"].asMap()[protocol::AGENT_ATTR_HEARTBEAT_INTERVAL] = interval;
+ map["_values"].asMap()[protocol::AGENT_ATTR_EPOCH] = bootSequence;
+ map["_values"].asMap()[protocol::AGENT_ATTR_SCHEMA_UPDATED_TIMESTAMP] = schemaUpdateTime;
+
+ encode(map, msg);
+ topicSender.send(msg);
+ QPID_LOG(trace, "SENT AgentHeartbeat name=" << agentName);
+}
+
+
+void AgentSessionImpl::send(Message msg, const Address& to)
+{
+ Sender sender;
+
+ if (strictSecurity && to.getName() != topicBase) {
+ QPID_LOG(warning, "Address violates strict-security policy: " << to);
+ return;
+ }
+
+ if (to.getName() == directBase) {
+ msg.setSubject(to.getSubject());
+ sender = directSender;
+ } else if (to.getName() == topicBase) {
+ msg.setSubject(to.getSubject());
+ sender = topicSender;
+ } else
+ sender = session.createSender(to);
+
+ sender.send(msg);
+}
+
+
+void AgentSessionImpl::flushResponses(AgentEvent& event, bool final)
+{
+ Message msg;
+ Variant::Map map;
+ Variant::Map& headers(msg.getProperties());
+
+ headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_RESPONSE;
+ headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_QUERY_RESPONSE;
+ headers[protocol::HEADER_KEY_CONTENT] = protocol::HEADER_CONTENT_DATA;
+ headers[protocol::HEADER_KEY_AGENT] = agentName;
+ headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF;
+ if (!final)
+ headers[protocol::HEADER_KEY_PARTIAL] = Variant();
+
+ Variant::List body;
+ AgentEventImpl& eventImpl(AgentEventImplAccess::get(event));
+ Data data(eventImpl.dequeueData());
+ while (data.isValid()) {
+ DataImpl& dataImpl(DataImplAccess::get(data));
+ body.push_back(dataImpl.asMap());
+ data = eventImpl.dequeueData();
+ }
+
+ msg.setCorrelationId(eventImpl.getCorrelationId());
+ encode(body, msg);
+ send(msg, eventImpl.getReplyTo());
+
+ QPID_LOG(trace, "SENT QueryResponse to=" << eventImpl.getReplyTo());
+}
+
+
+void AgentSessionImpl::periodicProcessing(uint64_t seconds)
+{
+ //
+ // The granularity of this timer is seconds. Don't waste time looking for work if
+ // it's been less than a second since we last visited.
+ //
+ if (seconds == lastVisit)
+ return;
+ //uint64_t thisInterval(seconds - lastVisit);
+ lastVisit = seconds;
+
+ //
+ // First time through, set lastHeartbeat to the current time.
+ //
+ if (lastHeartbeat == 0)
+ lastHeartbeat = seconds;
+
+ //
+ // If the hearbeat interval has elapsed, send a heartbeat.
+ //
+ if (forceHeartbeat || (seconds - lastHeartbeat >= interval)) {
+ lastHeartbeat = seconds;
+ forceHeartbeat = false;
+ sendHeartbeat();
+ }
+
+ //
+ // TODO: process any active subscriptions on their intervals.
+ //
+}
+
+
+void AgentSessionImpl::alertEventNotifierLH(bool readable)
+{
+ if (eventNotifier)
+ eventNotifier->setReadable(readable);
+}
+
+
+void AgentSessionImpl::run()
+{
+ QPID_LOG(debug, "AgentSession thread started for agent " << agentName);
+
+ try {
+ while (!threadCanceled) {
+ periodicProcessing((uint64_t) qpid::sys::Duration::FromEpoch() / qpid::sys::TIME_SEC);
+
+ Receiver rx;
+ bool valid = session.nextReceiver(rx, Duration::SECOND * maxThreadWaitTime);
+ if (threadCanceled)
+ break;
+ if (valid) {
+ try {
+ dispatch(rx.fetch());
+ } catch (qpid::types::Exception& e) {
+ QPID_LOG(error, "Exception caught in message dispatch: " << e.what());
+ }
+ session.acknowledge();
+ }
+ }
+ } catch (qpid::types::Exception& e) {
+ QPID_LOG(error, "Exception caught in message thread - exiting: " << e.what());
+ enqueueEvent(AgentEvent(new AgentEventImpl(AGENT_THREAD_FAILED)));
+ }
+
+ session.close();
+ QPID_LOG(debug, "AgentSession thread exiting for agent " << agentName);
+}
+
+
+AgentSessionImpl& AgentSessionImplAccess::get(AgentSession& session)
+{
+ return *session.impl;
+}
+
+
+const AgentSessionImpl& AgentSessionImplAccess::get(const AgentSession& session)
+{
+ return *session.impl;
+}
+
+}
diff --git a/qpid/cpp/src/qmf/AgentSessionImpl.h b/qpid/cpp/src/qmf/AgentSessionImpl.h
new file mode 100644
index 0000000000..64a39ab2e8
--- /dev/null
+++ b/qpid/cpp/src/qmf/AgentSessionImpl.h
@@ -0,0 +1,168 @@
+#ifndef __QMF_AGENT_SESSION_IMPL_H
+#define __QMF_AGENT_SESSION_IMPL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/RefCounted.h"
+#include "qmf/PrivateImplRef.h"
+#include "qmf/exceptions.h"
+#include "qmf/AgentSession.h"
+#include "qmf/AgentEventImpl.h"
+#include "qmf/EventNotifierImpl.h"
+#include "qpid/messaging/Connection.h"
+#include "qpid/sys/Runnable.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/Condition.h"
+#include "qpid/sys/Thread.h"
+#include "qpid/sys/Runnable.h"
+#include "qpid/log/Statement.h"
+#include "qpid/messaging/Connection.h"
+#include "qpid/messaging/Session.h"
+#include "qpid/messaging/Receiver.h"
+#include "qpid/messaging/Sender.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/messaging/AddressParser.h"
+#include "qpid/management/Buffer.h"
+#include "qpid/RefCounted.h"
+#include "qmf/PrivateImplRef.h"
+#include "qmf/AgentSession.h"
+#include "qmf/exceptions.h"
+#include "qmf/AgentSession.h"
+#include "qmf/SchemaIdImpl.h"
+#include "qmf/SchemaImpl.h"
+#include "qmf/DataAddrImpl.h"
+#include "qmf/DataImpl.h"
+#include "qmf/QueryImpl.h"
+#include "qmf/agentCapability.h"
+#include "qmf/constants.h"
+
+#include <queue>
+#include <map>
+
+namespace qmf {
+ typedef qmf::PrivateImplRef<AgentSession> PI;
+
+ class AgentSessionImpl : public virtual qpid::RefCounted, public qpid::sys::Runnable {
+ public:
+ ~AgentSessionImpl();
+
+ //
+ // Methods from API handle
+ //
+ AgentSessionImpl(qpid::messaging::Connection& c, const std::string& o);
+ void setDomain(const std::string& d) { checkOpen(); domain = d; }
+ void setVendor(const std::string& v) { checkOpen(); attributes["_vendor"] = v; }
+ void setProduct(const std::string& p) { checkOpen(); attributes["_product"] = p; }
+ void setInstance(const std::string& i) { checkOpen(); attributes["_instance"] = i; }
+ void setAttribute(const std::string& k, const qpid::types::Variant& v) { checkOpen(); attributes[k] = v; }
+ const std::string& getName() const { return agentName; }
+ void open();
+ void closeAsync();
+ void close();
+ bool nextEvent(AgentEvent& e, qpid::messaging::Duration t);
+ int pendingEvents() const;
+
+ void setEventNotifier(EventNotifierImpl* eventNotifier);
+ EventNotifierImpl* getEventNotifier() const;
+
+ void registerSchema(Schema& s);
+ DataAddr addData(Data& d, const std::string& n, bool persist);
+ void delData(const DataAddr&);
+
+ void authAccept(AgentEvent& e);
+ void authReject(AgentEvent& e, const std::string& m);
+ void raiseException(AgentEvent& e, const std::string& s);
+ void raiseException(AgentEvent& e, const Data& d);
+ void response(AgentEvent& e, const Data& d);
+ void complete(AgentEvent& e);
+ void methodSuccess(AgentEvent& e);
+ void raiseEvent(const Data& d);
+ void raiseEvent(const Data& d, int s);
+
+ private:
+ typedef std::map<DataAddr, Data, DataAddrCompare> DataIndex;
+ typedef std::map<SchemaId, Schema, SchemaIdCompare> SchemaMap;
+
+ mutable qpid::sys::Mutex lock;
+ qpid::sys::Condition cond;
+ qpid::messaging::Connection connection;
+ qpid::messaging::Session session;
+ qpid::messaging::Sender directSender;
+ qpid::messaging::Sender topicSender;
+ std::string domain;
+ qpid::types::Variant::Map attributes;
+ qpid::types::Variant::Map options;
+ std::string agentName;
+ bool opened;
+ std::queue<AgentEvent> eventQueue;
+ EventNotifierImpl* eventNotifier;
+ qpid::sys::Thread* thread;
+ bool threadCanceled;
+ uint32_t bootSequence;
+ uint32_t interval;
+ uint64_t lastHeartbeat;
+ uint64_t lastVisit;
+ bool forceHeartbeat;
+ bool externalStorage;
+ bool autoAllowQueries;
+ bool autoAllowMethods;
+ uint32_t maxSubscriptions;
+ uint32_t minSubInterval;
+ uint32_t subLifetime;
+ bool publicEvents;
+ bool listenOnDirect;
+ bool strictSecurity;
+ uint32_t maxThreadWaitTime;
+ uint64_t schemaUpdateTime;
+ std::string directBase;
+ std::string topicBase;
+
+ SchemaMap schemata;
+ DataIndex globalIndex;
+ std::map<SchemaId, DataIndex, SchemaIdCompareNoHash> schemaIndex;
+
+ void checkOpen();
+ void setAgentName();
+ void enqueueEvent(const AgentEvent&);
+ void alertEventNotifierLH(bool readable);
+ void handleLocateRequest(const qpid::types::Variant::List& content, const qpid::messaging::Message& msg);
+ void handleMethodRequest(const qpid::types::Variant::Map& content, const qpid::messaging::Message& msg);
+ void handleQueryRequest(const qpid::types::Variant::Map& content, const qpid::messaging::Message& msg);
+ void handleSchemaRequest(AgentEvent&);
+ void handleV1SchemaRequest(qpid::management::Buffer&, uint32_t, const qpid::messaging::Message&);
+ void dispatch(qpid::messaging::Message);
+ void sendHeartbeat();
+ void send(qpid::messaging::Message, const qpid::messaging::Address&);
+ void flushResponses(AgentEvent&, bool);
+ void periodicProcessing(uint64_t);
+ void run();
+ };
+
+ struct AgentSessionImplAccess {
+ static AgentSessionImpl& get(AgentSession& session);
+ static const AgentSessionImpl& get(const AgentSession& session);
+ };
+}
+
+
+#endif
+
diff --git a/qpid/cpp/src/qmf/AgentSubscription.cpp b/qpid/cpp/src/qmf/AgentSubscription.cpp
new file mode 100644
index 0000000000..4dc5cb74a4
--- /dev/null
+++ b/qpid/cpp/src/qmf/AgentSubscription.cpp
@@ -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.
+ *
+ */
+
+#include "qmf/AgentSubscription.h"
+
+using namespace qmf;
+
+AgentSubscription::AgentSubscription(uint64_t _id, uint64_t _interval, uint64_t _life,
+ const std::string& _replyTo, const std::string& _cid, Query _query) :
+ id(_id), interval(_interval), lifetime(_life), timeSincePublish(0), timeSinceKeepalive(0),
+ replyTo(_replyTo), cid(_cid), query(_query)
+{
+}
+
+
+AgentSubscription::~AgentSubscription()
+{
+}
+
+
+bool AgentSubscription::tick(uint64_t seconds)
+{
+ timeSinceKeepalive += seconds;
+ if (timeSinceKeepalive >= lifetime)
+ return false;
+
+ timeSincePublish += seconds;
+ if (timeSincePublish >= interval) {
+ }
+
+ return true;
+}
+
diff --git a/qpid/cpp/src/qmf/AgentSubscription.h b/qpid/cpp/src/qmf/AgentSubscription.h
new file mode 100644
index 0000000000..01e8f43e9f
--- /dev/null
+++ b/qpid/cpp/src/qmf/AgentSubscription.h
@@ -0,0 +1,52 @@
+#ifndef _QMF_AGENT_SUBSCRIPTION_H_
+#define _QMF_AGENT_SUBSCRIPTION_H_
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/types/Variant.h"
+#include "qmf/Query.h"
+#include "qmf/Data.h"
+#include <boost/shared_ptr.hpp>
+
+namespace qmf {
+ class AgentSubscription {
+ public:
+ AgentSubscription(uint64_t _id, uint64_t _interval, uint64_t _life,
+ const std::string& _replyTo, const std::string& _cid, Query _query);
+ ~AgentSubscription();
+ bool tick(uint64_t seconds);
+ void keepalive() { timeSinceKeepalive = 0; }
+
+ private:
+ uint64_t id;
+ uint64_t interval;
+ uint64_t lifetime;
+ uint64_t timeSincePublish;
+ uint64_t timeSinceKeepalive;
+ const std::string replyTo;
+ const std::string cid;
+ Query query;
+ };
+
+}
+
+#endif
diff --git a/qpid/cpp/src/qmf/ConsoleEvent.cpp b/qpid/cpp/src/qmf/ConsoleEvent.cpp
new file mode 100644
index 0000000000..b2a5e321c7
--- /dev/null
+++ b/qpid/cpp/src/qmf/ConsoleEvent.cpp
@@ -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.
+ *
+ */
+
+#include "qmf/ConsoleEventImpl.h"
+#include "qmf/PrivateImplRef.h"
+#include "qmf/exceptions.h"
+
+using namespace std;
+using namespace qmf;
+using qpid::types::Variant;
+
+typedef PrivateImplRef<ConsoleEvent> PI;
+
+ConsoleEvent::ConsoleEvent(ConsoleEventImpl* impl) { PI::ctor(*this, impl); }
+ConsoleEvent::ConsoleEvent(const ConsoleEvent& s) : qmf::Handle<ConsoleEventImpl>() { PI::copy(*this, s); }
+ConsoleEvent::~ConsoleEvent() { PI::dtor(*this); }
+ConsoleEvent& ConsoleEvent::operator=(const ConsoleEvent& s) { return PI::assign(*this, s); }
+
+ConsoleEventCode ConsoleEvent::getType() const { return impl->getType(); }
+uint32_t ConsoleEvent::getCorrelator() const { return impl->getCorrelator(); }
+Agent ConsoleEvent::getAgent() const { return impl->getAgent(); }
+AgentDelReason ConsoleEvent::getAgentDelReason() const { return impl->getAgentDelReason(); }
+uint32_t ConsoleEvent::getSchemaIdCount() const { return impl->getSchemaIdCount(); }
+SchemaId ConsoleEvent::getSchemaId(uint32_t i) const { return impl->getSchemaId(i); }
+uint32_t ConsoleEvent::getDataCount() const { return impl->getDataCount(); }
+Data ConsoleEvent::getData(uint32_t i) const { return impl->getData(i); }
+bool ConsoleEvent::isFinal() const { return impl->isFinal(); }
+const Variant::Map& ConsoleEvent::getArguments() const { return impl->getArguments(); }
+int ConsoleEvent::getSeverity() const { return impl->getSeverity(); }
+uint64_t ConsoleEvent::getTimestamp() const { return impl->getTimestamp(); }
+
+
+SchemaId ConsoleEventImpl::getSchemaId(uint32_t i) const
+{
+ uint32_t count = 0;
+ for (list<SchemaId>::const_iterator iter = newSchemaIds.begin(); iter != newSchemaIds.end(); iter++) {
+ if (count++ == i)
+ return *iter;
+ }
+ throw IndexOutOfRange();
+}
+
+
+Data ConsoleEventImpl::getData(uint32_t i) const
+{
+ uint32_t count = 0;
+ for (list<Data>::const_iterator iter = dataList.begin(); iter != dataList.end(); iter++) {
+ if (count++ == i)
+ return *iter;
+ }
+ throw IndexOutOfRange();
+}
+
+
+ConsoleEventImpl& ConsoleEventImplAccess::get(ConsoleEvent& item)
+{
+ return *item.impl;
+}
+
+
+const ConsoleEventImpl& ConsoleEventImplAccess::get(const ConsoleEvent& item)
+{
+ return *item.impl;
+}
diff --git a/qpid/cpp/src/qmf/ConsoleEventImpl.h b/qpid/cpp/src/qmf/ConsoleEventImpl.h
new file mode 100644
index 0000000000..9843971456
--- /dev/null
+++ b/qpid/cpp/src/qmf/ConsoleEventImpl.h
@@ -0,0 +1,84 @@
+#ifndef _QMF_CONSOLE_EVENT_IMPL_H_
+#define _QMF_CONSOLE_EVENT_IMPL_H_
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/RefCounted.h"
+#include "qmf/ConsoleEvent.h"
+#include "qmf/Agent.h"
+#include "qmf/Data.h"
+#include "qpid/types/Variant.h"
+#include <list>
+
+namespace qmf {
+ class ConsoleEventImpl : public virtual qpid::RefCounted {
+ public:
+ //
+ // Impl-only methods
+ //
+ ConsoleEventImpl(ConsoleEventCode e, AgentDelReason r = AGENT_DEL_AGED) :
+ eventType(e), delReason(r), correlator(0), final(false) {}
+ void setCorrelator(uint32_t c) { correlator = c; }
+ void setAgent(const Agent& a) { agent = a; }
+ void addData(const Data& d) { dataList.push_back(Data(d)); }
+ void addSchemaId(const SchemaId& s) { newSchemaIds.push_back(SchemaId(s)); }
+ void setFinal() { final = true; }
+ void setArguments(const qpid::types::Variant::Map& a) { arguments = a; }
+ void setSeverity(int s) { severity = s; }
+ void setTimestamp(uint64_t t) { timestamp = t; }
+
+ //
+ // Methods from API handle
+ //
+ ConsoleEventCode getType() const { return eventType; }
+ uint32_t getCorrelator() const { return correlator; }
+ Agent getAgent() const { return agent; }
+ AgentDelReason getAgentDelReason() const { return delReason; }
+ uint32_t getSchemaIdCount() const { return newSchemaIds.size(); }
+ SchemaId getSchemaId(uint32_t) const;
+ uint32_t getDataCount() const { return dataList.size(); }
+ Data getData(uint32_t i) const;
+ bool isFinal() const { return final; }
+ const qpid::types::Variant::Map& getArguments() const { return arguments; }
+ int getSeverity() const { return severity; }
+ uint64_t getTimestamp() const { return timestamp; }
+
+ private:
+ const ConsoleEventCode eventType;
+ const AgentDelReason delReason;
+ uint32_t correlator;
+ Agent agent;
+ bool final;
+ std::list<Data> dataList;
+ std::list<SchemaId> newSchemaIds;
+ qpid::types::Variant::Map arguments;
+ int severity;
+ uint64_t timestamp;
+ };
+
+ struct ConsoleEventImplAccess
+ {
+ static ConsoleEventImpl& get(ConsoleEvent&);
+ static const ConsoleEventImpl& get(const ConsoleEvent&);
+ };
+}
+
+#endif
diff --git a/qpid/cpp/src/qmf/ConsoleSession.cpp b/qpid/cpp/src/qmf/ConsoleSession.cpp
new file mode 100644
index 0000000000..c74d4de8db
--- /dev/null
+++ b/qpid/cpp/src/qmf/ConsoleSession.cpp
@@ -0,0 +1,683 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qmf/PrivateImplRef.h"
+#include "qmf/ConsoleSessionImpl.h"
+#include "qmf/AgentImpl.h"
+#include "qmf/SchemaId.h"
+#include "qmf/SchemaImpl.h"
+#include "qmf/ConsoleEventImpl.h"
+#include "qmf/constants.h"
+#include "qpid/log/Statement.h"
+#include "qpid/messaging/AddressParser.h"
+#include "qpid/messaging/Sender.h"
+#include "qpid/messaging/Receiver.h"
+
+using namespace std;
+using namespace qmf;
+using qpid::messaging::Address;
+using qpid::messaging::Connection;
+using qpid::messaging::Receiver;
+using qpid::messaging::Sender;
+using qpid::messaging::Duration;
+using qpid::messaging::Message;
+using qpid::types::Variant;
+
+typedef qmf::PrivateImplRef<ConsoleSession> PI;
+
+ConsoleSession::ConsoleSession(ConsoleSessionImpl* impl) { PI::ctor(*this, impl); }
+ConsoleSession::ConsoleSession(const ConsoleSession& s) : qmf::Handle<ConsoleSessionImpl>() { PI::copy(*this, s); }
+ConsoleSession::~ConsoleSession() { PI::dtor(*this); }
+ConsoleSession& ConsoleSession::operator=(const ConsoleSession& s) { return PI::assign(*this, s); }
+
+ConsoleSession::ConsoleSession(Connection& c, const string& o) { PI::ctor(*this, new ConsoleSessionImpl(c, o)); }
+void ConsoleSession::setDomain(const string& d) { impl->setDomain(d); }
+void ConsoleSession::setAgentFilter(const string& f) { impl->setAgentFilter(f); }
+void ConsoleSession::open() { impl->open(); }
+void ConsoleSession::close() { impl->close(); }
+bool ConsoleSession::nextEvent(ConsoleEvent& e, Duration t) { return impl->nextEvent(e, t); }
+int ConsoleSession::pendingEvents() const { return impl->pendingEvents(); }
+uint32_t ConsoleSession::getAgentCount() const { return impl->getAgentCount(); }
+Agent ConsoleSession::getAgent(uint32_t i) const { return impl->getAgent(i); }
+Agent ConsoleSession::getConnectedBrokerAgent() const { return impl->getConnectedBrokerAgent(); }
+Subscription ConsoleSession::subscribe(const Query& q, const string& f, const string& o) { return impl->subscribe(q, f, o); }
+Subscription ConsoleSession::subscribe(const string& q, const string& f, const string& o) { return impl->subscribe(q, f, o); }
+
+//========================================================================================
+// Impl Method Bodies
+//========================================================================================
+
+ConsoleSessionImpl::ConsoleSessionImpl(Connection& c, const string& options) :
+ connection(c), domain("default"), maxAgentAgeMinutes(5), listenOnDirect(true), strictSecurity(false), maxThreadWaitTime(5),
+ opened(false), eventNotifier(0), thread(0), threadCanceled(false), lastVisit(0), lastAgePass(0),
+ connectedBrokerInAgentList(false), schemaCache(new SchemaCache()), nextCorrelator(1)
+{
+ if (!options.empty()) {
+ qpid::messaging::AddressParser parser(options);
+ Variant::Map optMap;
+ Variant::Map::const_iterator iter;
+
+ parser.parseMap(optMap);
+
+ iter = optMap.find("domain");
+ if (iter != optMap.end())
+ domain = iter->second.asString();
+
+ iter = optMap.find("max-agent-age");
+ if (iter != optMap.end())
+ maxAgentAgeMinutes = iter->second.asUint32();
+
+ iter = optMap.find("listen-on-direct");
+ if (iter != optMap.end())
+ listenOnDirect = iter->second.asBool();
+
+ iter = optMap.find("strict-security");
+ if (iter != optMap.end())
+ strictSecurity = iter->second.asBool();
+
+ iter = optMap.find("max-thread-wait-time");
+ if (iter != optMap.end())
+ maxThreadWaitTime = iter->second.asUint32();
+ }
+
+ if (maxThreadWaitTime > 60)
+ maxThreadWaitTime = 60;
+}
+
+
+ConsoleSessionImpl::~ConsoleSessionImpl()
+{
+ if (opened)
+ close();
+
+ if (thread) {
+ thread->join();
+ delete thread;
+ }
+}
+
+
+void ConsoleSessionImpl::setAgentFilter(const string& predicate)
+{
+ agentQuery = Query(QUERY_OBJECT, predicate);
+
+ //
+ // Purge the agent list of any agents that don't match the filter.
+ //
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ map<string, Agent> toDelete;
+ for (map<string, Agent>::iterator iter = agents.begin(); iter != agents.end(); iter++)
+ if (!agentQuery.matchesPredicate(iter->second.getAttributes())) {
+ toDelete[iter->first] = iter->second;
+ if (iter->second.getName() == connectedBrokerAgent.getName())
+ connectedBrokerInAgentList = false;
+ }
+
+ for (map<string, Agent>::iterator iter = toDelete.begin(); iter != toDelete.end(); iter++) {
+ agents.erase(iter->first);
+ auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_AGENT_DEL, AGENT_DEL_FILTER));
+ eventImpl->setAgent(iter->second);
+ enqueueEventLH(eventImpl.release());
+ }
+
+ if (!connectedBrokerInAgentList && connectedBrokerAgent.isValid() &&
+ agentQuery.matchesPredicate(connectedBrokerAgent.getAttributes())) {
+ agents[connectedBrokerAgent.getName()] = connectedBrokerAgent;
+ connectedBrokerInAgentList = true;
+
+ //
+ // Enqueue a notification of the new agent.
+ //
+ auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_AGENT_ADD));
+ eventImpl->setAgent(connectedBrokerAgent);
+ enqueueEventLH(ConsoleEvent(eventImpl.release()));
+ }
+ }
+
+ //
+ // Broadcast an agent locate request with our new criteria.
+ //
+ if (opened)
+ sendAgentLocate();
+}
+
+
+void ConsoleSessionImpl::open()
+{
+ if (opened)
+ throw QmfException("The session is already open");
+
+ // If the thread exists, join and delete it before creating a new one.
+ if (thread) {
+ thread->join();
+ delete thread;
+ }
+
+ // Establish messaging addresses
+ directBase = "qmf." + domain + ".direct";
+ topicBase = "qmf." + domain + ".topic";
+
+ string myKey("direct-console." + qpid::types::Uuid(true).str());
+
+ replyAddress = Address(topicBase + "/" + myKey + ";{node:{type:topic}}");
+
+ // Create AMQP session, receivers, and senders
+ session = connection.createSession();
+ Receiver directRx = session.createReceiver(replyAddress);
+ Receiver topicRx = session.createReceiver(topicBase + "/agent.#"); // TODO: be more discriminating
+ if (!strictSecurity) {
+ Receiver legacyRx = session.createReceiver("amq.direct/" + myKey + ";{node:{type:topic}}");
+ legacyRx.setCapacity(64);
+ directSender = session.createSender(directBase + ";{create:never,node:{type:topic}}");
+ directSender.setCapacity(128);
+ }
+
+ directRx.setCapacity(64);
+ topicRx.setCapacity(128);
+
+ topicSender = session.createSender(topicBase + ";{create:never,node:{type:topic}}");
+
+ topicSender.setCapacity(128);
+
+ // Start the receiver thread
+ threadCanceled = false;
+ opened = true;
+ thread = new qpid::sys::Thread(*this);
+
+ // Send an agent_locate to direct address 'broker' to identify the connected-broker-agent.
+ sendBrokerLocate();
+ if (agentQuery)
+ sendAgentLocate();
+}
+
+
+void ConsoleSessionImpl::closeAsync()
+{
+ if (!opened)
+ throw QmfException("The session is already closed");
+
+ // Stop the receiver thread. Don't join it until the destructor is called or open() is called.
+ threadCanceled = true;
+ opened = false;
+}
+
+
+void ConsoleSessionImpl::close()
+{
+ closeAsync();
+
+ if (thread) {
+ thread->join();
+ delete thread;
+ thread = 0;
+ }
+}
+
+
+bool ConsoleSessionImpl::nextEvent(ConsoleEvent& event, Duration timeout)
+{
+ uint64_t milliseconds = timeout.getMilliseconds();
+ qpid::sys::Mutex::ScopedLock l(lock);
+
+ if (eventQueue.empty() && milliseconds > 0) {
+ int64_t nsecs(qpid::sys::TIME_INFINITE);
+ if ((uint64_t)(nsecs / 1000000) > milliseconds)
+ nsecs = (int64_t) milliseconds * 1000000;
+ qpid::sys::Duration then(nsecs);
+ cond.wait(lock, qpid::sys::AbsTime(qpid::sys::now(), then));
+ }
+
+ if (!eventQueue.empty()) {
+ event = eventQueue.front();
+ eventQueue.pop();
+ if (eventQueue.empty())
+ alertEventNotifierLH(false);
+ return true;
+ }
+
+ return false;
+}
+
+
+int ConsoleSessionImpl::pendingEvents() const
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ return eventQueue.size();
+}
+
+
+void ConsoleSessionImpl::setEventNotifier(EventNotifierImpl* notifier)
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ eventNotifier = notifier;
+}
+
+
+EventNotifierImpl* ConsoleSessionImpl::getEventNotifier() const
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ return eventNotifier;
+}
+
+
+uint32_t ConsoleSessionImpl::getAgentCount() const
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ return agents.size();
+}
+
+
+Agent ConsoleSessionImpl::getAgent(uint32_t i) const
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ uint32_t count = 0;
+ for (map<string, Agent>::const_iterator iter = agents.begin(); iter != agents.end(); iter++)
+ if (count++ == i)
+ return iter->second;
+ throw IndexOutOfRange();
+}
+
+
+Subscription ConsoleSessionImpl::subscribe(const Query&, const string&, const string&)
+{
+ return Subscription();
+}
+
+
+Subscription ConsoleSessionImpl::subscribe(const string&, const string&, const string&)
+{
+ return Subscription();
+}
+
+
+void ConsoleSessionImpl::enqueueEvent(const ConsoleEvent& event)
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ enqueueEventLH(event);
+}
+
+
+void ConsoleSessionImpl::enqueueEventLH(const ConsoleEvent& event)
+{
+ bool notify = eventQueue.empty();
+ eventQueue.push(event);
+ if (notify) {
+ cond.notify();
+ alertEventNotifierLH(true);
+ }
+}
+
+
+void ConsoleSessionImpl::dispatch(Message msg)
+{
+ const Variant::Map& properties(msg.getProperties());
+ Variant::Map::const_iterator iter;
+ Variant::Map::const_iterator oiter;
+
+ oiter = properties.find(protocol::HEADER_KEY_OPCODE);
+ iter = properties.find(protocol::HEADER_KEY_APP_ID);
+ if (iter == properties.end())
+ iter = properties.find("app_id");
+ if (iter != properties.end() && iter->second.asString() == protocol::HEADER_APP_ID_QMF && oiter != properties.end()) {
+ //
+ // Dispatch a QMFv2 formatted message
+ //
+ const string& opcode = oiter->second.asString();
+
+ iter = properties.find(protocol::HEADER_KEY_AGENT);
+ if (iter == properties.end()) {
+ QPID_LOG(trace, "Message received with no 'qmf.agent' header");
+ return;
+ }
+ const string& agentName = iter->second.asString();
+
+ Agent agent;
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ map<string, Agent>::iterator aIter = agents.find(agentName);
+ if (aIter != agents.end()) {
+ agent = aIter->second;
+ AgentImplAccess::get(agent).touch();
+ }
+ }
+
+ if (msg.getContentType() == "amqp/map" &&
+ (opcode == protocol::HEADER_OPCODE_AGENT_HEARTBEAT_INDICATION || opcode == protocol::HEADER_OPCODE_AGENT_LOCATE_RESPONSE)) {
+ //
+ // This is the one case where it's ok (necessary actually) to receive a QMFv2
+ // message from an unknown agent (how else are they going to get known?)
+ //
+ Variant::Map content;
+ decode(msg, content);
+ handleAgentUpdate(agentName, content, msg);
+ return;
+ }
+
+ if (!agent.isValid())
+ return;
+
+ AgentImpl& agentImpl(AgentImplAccess::get(agent));
+
+ if (msg.getContentType() == "amqp/map") {
+ Variant::Map content;
+ decode(msg, content);
+
+ if (opcode == protocol::HEADER_OPCODE_EXCEPTION) agentImpl.handleException(content, msg);
+ else if (opcode == protocol::HEADER_OPCODE_METHOD_RESPONSE) agentImpl.handleMethodResponse(content, msg);
+ else
+ QPID_LOG(error, "Received a map-formatted QMFv2 message with opcode=" << opcode);
+
+ return;
+ }
+
+ if (msg.getContentType() == "amqp/list") {
+ Variant::List content;
+ decode(msg, content);
+
+ if (opcode == protocol::HEADER_OPCODE_QUERY_RESPONSE) agentImpl.handleQueryResponse(content, msg);
+ else if (opcode == protocol::HEADER_OPCODE_DATA_INDICATION) agentImpl.handleDataIndication(content, msg);
+ else
+ QPID_LOG(error, "Received a list-formatted QMFv2 message with opcode=" << opcode);
+
+ return;
+ }
+ } else {
+ //
+ // Dispatch a QMFv1 formatted message
+ //
+ const string& body(msg.getContent());
+ if (body.size() < 8)
+ return;
+ qpid::management::Buffer buffer(const_cast<char*>(body.c_str()), body.size());
+
+ if (buffer.getOctet() != 'A') return;
+ if (buffer.getOctet() != 'M') return;
+ if (buffer.getOctet() != '2') return;
+ char v1Opcode(buffer.getOctet());
+ uint32_t seq(buffer.getLong());
+
+ if (v1Opcode == 's') handleV1SchemaResponse(buffer, seq, msg);
+ else {
+ QPID_LOG(trace, "Unknown or Unsupported QMFv1 opcode: " << v1Opcode);
+ }
+ }
+}
+
+
+void ConsoleSessionImpl::sendBrokerLocate()
+{
+ Message msg;
+ Variant::Map& headers(msg.getProperties());
+
+ headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_REQUEST;
+ headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_AGENT_LOCATE_REQUEST;
+ headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF;
+
+ msg.setReplyTo(replyAddress);
+ msg.setCorrelationId("broker-locate");
+ msg.setSubject("broker");
+
+ Sender sender = session.createSender(directBase + ";{create:never,node:{type:topic}}");
+ sender.send(msg);
+ sender.close();
+
+ QPID_LOG(trace, "SENT AgentLocate to broker");
+}
+
+
+void ConsoleSessionImpl::sendAgentLocate()
+{
+ Message msg;
+ Variant::Map& headers(msg.getProperties());
+ static const string subject("console.request.agent_locate");
+
+ headers[protocol::HEADER_KEY_METHOD] = protocol::HEADER_METHOD_REQUEST;
+ headers[protocol::HEADER_KEY_OPCODE] = protocol::HEADER_OPCODE_AGENT_LOCATE_REQUEST;
+ headers[protocol::HEADER_KEY_APP_ID] = protocol::HEADER_APP_ID_QMF;
+
+ msg.setReplyTo(replyAddress);
+ msg.setCorrelationId("agent-locate");
+ msg.setSubject(subject);
+ encode(agentQuery.getPredicate(), msg);
+
+ topicSender.send(msg);
+
+ QPID_LOG(trace, "SENT AgentLocate to=" << topicSender.getName() << "/" << subject);
+}
+
+
+void ConsoleSessionImpl::handleAgentUpdate(const string& agentName, const Variant::Map& content, const Message& msg)
+{
+ Variant::Map::const_iterator iter;
+ Agent agent;
+ uint32_t epoch(0);
+ string cid(msg.getCorrelationId());
+
+ iter = content.find("_values");
+ if (iter == content.end())
+ return;
+ const Variant::Map& in_attrs(iter->second.asMap());
+ Variant::Map attrs;
+
+ //
+ // Copy the map from the message to "attrs". Translate any old-style
+ // keys to their new key values in the process.
+ //
+ for (iter = in_attrs.begin(); iter != in_attrs.end(); iter++) {
+ if (iter->first == "epoch")
+ attrs[protocol::AGENT_ATTR_EPOCH] = iter->second;
+ else if (iter->first == "timestamp")
+ attrs[protocol::AGENT_ATTR_TIMESTAMP] = iter->second;
+ else if (iter->first == "heartbeat_interval")
+ attrs[protocol::AGENT_ATTR_HEARTBEAT_INTERVAL] = iter->second;
+ else
+ attrs[iter->first] = iter->second;
+ }
+
+ iter = attrs.find(protocol::AGENT_ATTR_EPOCH);
+ if (iter != attrs.end())
+ epoch = iter->second.asUint32();
+
+ if (cid == "broker-locate") {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ auto_ptr<AgentImpl> impl(new AgentImpl(agentName, epoch, *this));
+ for (iter = attrs.begin(); iter != attrs.end(); iter++)
+ if (iter->first != protocol::AGENT_ATTR_EPOCH)
+ impl->setAttribute(iter->first, iter->second);
+ agent = Agent(impl.release());
+ connectedBrokerAgent = agent;
+ if (!agentQuery || agentQuery.matchesPredicate(attrs)) {
+ connectedBrokerInAgentList = true;
+ agents[agentName] = agent;
+
+ //
+ // Enqueue a notification of the new agent.
+ //
+ auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_AGENT_ADD));
+ eventImpl->setAgent(agent);
+ enqueueEventLH(ConsoleEvent(eventImpl.release()));
+ }
+ return;
+ }
+
+ //
+ // Check this agent against the agent filter. Exit if it doesn't match.
+ // (only if this isn't the connected broker agent)
+ //
+ if (agentQuery && (!agentQuery.matchesPredicate(attrs)))
+ return;
+
+ QPID_LOG(trace, "RCVD AgentHeartbeat from an agent matching our filter: " << agentName);
+
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ map<string, Agent>::iterator aIter = agents.find(agentName);
+ if (aIter == agents.end()) {
+ //
+ // This is a new agent. We have no current record of its existence.
+ //
+ auto_ptr<AgentImpl> impl(new AgentImpl(agentName, epoch, *this));
+ for (iter = attrs.begin(); iter != attrs.end(); iter++)
+ if (iter->first != protocol::AGENT_ATTR_EPOCH)
+ impl->setAttribute(iter->first, iter->second);
+ agent = Agent(impl.release());
+ agents[agentName] = agent;
+
+ //
+ // Enqueue a notification of the new agent.
+ //
+ auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_AGENT_ADD));
+ eventImpl->setAgent(agent);
+ enqueueEventLH(ConsoleEvent(eventImpl.release()));
+ } else {
+ //
+ // This is a refresh of an agent we are already tracking.
+ //
+ bool detectedRestart(false);
+ agent = aIter->second;
+ AgentImpl& impl(AgentImplAccess::get(agent));
+ impl.touch();
+ if (impl.getEpoch() != epoch) {
+ //
+ // The agent has restarted since the last time we heard from it.
+ // Enqueue a notification.
+ //
+ impl.setEpoch(epoch);
+ auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_AGENT_RESTART));
+ eventImpl->setAgent(agent);
+ enqueueEventLH(ConsoleEvent(eventImpl.release()));
+ detectedRestart = true;
+ }
+
+ iter = attrs.find(protocol::AGENT_ATTR_SCHEMA_UPDATED_TIMESTAMP);
+ if (iter != attrs.end()) {
+ uint64_t ts(iter->second.asUint64());
+ if (ts > impl.getAttribute(protocol::AGENT_ATTR_SCHEMA_UPDATED_TIMESTAMP).asUint64()) {
+ //
+ // The agent has added new schema entries since we last heard from it.
+ // Update the attribute and, if this doesn't accompany a restart, enqueue a notification.
+ //
+ if (!detectedRestart) {
+ auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_AGENT_SCHEMA_UPDATE));
+ eventImpl->setAgent(agent);
+ enqueueEventLH(ConsoleEvent(eventImpl.release()));
+ }
+ impl.setAttribute(protocol::AGENT_ATTR_SCHEMA_UPDATED_TIMESTAMP, iter->second);
+ }
+ }
+ }
+ }
+}
+
+
+void ConsoleSessionImpl::handleV1SchemaResponse(qpid::management::Buffer& buffer, uint32_t, const Message&)
+{
+ QPID_LOG(trace, "RCVD V1SchemaResponse");
+ Schema schema(new SchemaImpl(buffer));
+ schemaCache->declareSchema(schema);
+}
+
+
+void ConsoleSessionImpl::periodicProcessing(uint64_t seconds)
+{
+ //
+ // The granularity of this timer is seconds. Don't waste time looking for work if
+ // it's been less than a second since we last visited.
+ //
+ if (seconds == lastVisit)
+ return;
+ lastVisit = seconds;
+
+ //
+ // Handle the aging of agent records
+ //
+ if (lastAgePass == 0)
+ lastAgePass = seconds;
+ if (seconds - lastAgePass >= 60) {
+ lastAgePass = seconds;
+ map<string, Agent> toDelete;
+ qpid::sys::Mutex::ScopedLock l(lock);
+
+ for (map<string, Agent>::iterator iter = agents.begin(); iter != agents.end(); iter++)
+ if ((iter->second.getName() != connectedBrokerAgent.getName()) &&
+ (AgentImplAccess::get(iter->second).age() > maxAgentAgeMinutes))
+ toDelete[iter->first] = iter->second;
+
+ for (map<string, Agent>::iterator iter = toDelete.begin(); iter != toDelete.end(); iter++) {
+ agents.erase(iter->first);
+ auto_ptr<ConsoleEventImpl> eventImpl(new ConsoleEventImpl(CONSOLE_AGENT_DEL, AGENT_DEL_AGED));
+ eventImpl->setAgent(iter->second);
+ enqueueEventLH(eventImpl.release());
+ }
+ }
+}
+
+
+void ConsoleSessionImpl::alertEventNotifierLH(bool readable)
+{
+ if (eventNotifier)
+ eventNotifier->setReadable(readable);
+}
+
+
+void ConsoleSessionImpl::run()
+{
+ QPID_LOG(debug, "ConsoleSession thread started");
+
+ try {
+ while (!threadCanceled) {
+ periodicProcessing((uint64_t) qpid::sys::Duration::FromEpoch() /
+ qpid::sys::TIME_SEC);
+
+ Receiver rx;
+ bool valid = session.nextReceiver(rx, Duration::SECOND * maxThreadWaitTime);
+ if (threadCanceled)
+ break;
+ if (valid) {
+ try {
+ dispatch(rx.fetch());
+ } catch (qpid::types::Exception& e) {
+ QPID_LOG(error, "Exception caught in message dispatch: " << e.what());
+ }
+ session.acknowledge();
+ }
+ }
+ } catch (qpid::types::Exception& e) {
+ QPID_LOG(error, "Exception caught in message thread - exiting: " << e.what());
+ enqueueEvent(ConsoleEvent(new ConsoleEventImpl(CONSOLE_THREAD_FAILED)));
+ }
+
+ session.close();
+ QPID_LOG(debug, "ConsoleSession thread exiting");
+}
+
+
+ConsoleSessionImpl& ConsoleSessionImplAccess::get(ConsoleSession& session)
+{
+ return *session.impl;
+}
+
+
+const ConsoleSessionImpl& ConsoleSessionImplAccess::get(const ConsoleSession& session)
+{
+ return *session.impl;
+}
diff --git a/qpid/cpp/src/qmf/ConsoleSessionImpl.h b/qpid/cpp/src/qmf/ConsoleSessionImpl.h
new file mode 100644
index 0000000000..2c06df030c
--- /dev/null
+++ b/qpid/cpp/src/qmf/ConsoleSessionImpl.h
@@ -0,0 +1,127 @@
+#ifndef _QMF_CONSOLE_SESSION_IMPL_H_
+#define _QMF_CONSOLE_SESSION_IMPL_H_
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/RefCounted.h"
+#include "qmf/ConsoleSession.h"
+#include "qmf/AgentImpl.h"
+#include "qmf/SchemaId.h"
+#include "qmf/Schema.h"
+#include "qmf/ConsoleEventImpl.h"
+#include "qmf/EventNotifierImpl.h"
+#include "qmf/SchemaCache.h"
+#include "qmf/Query.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/Condition.h"
+#include "qpid/sys/Thread.h"
+#include "qpid/sys/Runnable.h"
+#include "qpid/log/Statement.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/messaging/Connection.h"
+#include "qpid/messaging/Session.h"
+#include "qpid/messaging/Sender.h"
+#include "qpid/messaging/Address.h"
+#include "qpid/management/Buffer.h"
+#include "qpid/types/Variant.h"
+
+#include <boost/shared_ptr.hpp>
+#include <map>
+#include <queue>
+
+namespace qmf {
+ class ConsoleSessionImpl : public virtual qpid::RefCounted, public qpid::sys::Runnable {
+ public:
+ ~ConsoleSessionImpl();
+
+ //
+ // Methods from API handle
+ //
+ ConsoleSessionImpl(qpid::messaging::Connection& c, const std::string& o);
+ void setDomain(const std::string& d) { domain = d; }
+ void setAgentFilter(const std::string& f);
+ void open();
+ void closeAsync();
+ void close();
+ bool nextEvent(ConsoleEvent& e, qpid::messaging::Duration t);
+ int pendingEvents() const;
+
+ void setEventNotifier(EventNotifierImpl* notifier);
+ EventNotifierImpl* getEventNotifier() const;
+
+ uint32_t getAgentCount() const;
+ Agent getAgent(uint32_t i) const;
+ Agent getConnectedBrokerAgent() const { return connectedBrokerAgent; }
+ Subscription subscribe(const Query&, const std::string& agentFilter, const std::string& options);
+ Subscription subscribe(const std::string&, const std::string& agentFilter, const std::string& options);
+
+ protected:
+ mutable qpid::sys::Mutex lock;
+ qpid::sys::Condition cond;
+ qpid::messaging::Connection connection;
+ qpid::messaging::Session session;
+ qpid::messaging::Sender directSender;
+ qpid::messaging::Sender topicSender;
+ std::string domain;
+ uint32_t maxAgentAgeMinutes;
+ bool listenOnDirect;
+ bool strictSecurity;
+ uint32_t maxThreadWaitTime;
+ Query agentQuery;
+ bool opened;
+ std::queue<ConsoleEvent> eventQueue;
+ EventNotifierImpl* eventNotifier;
+ qpid::sys::Thread* thread;
+ bool threadCanceled;
+ uint64_t lastVisit;
+ uint64_t lastAgePass;
+ std::map<std::string, Agent> agents;
+ Agent connectedBrokerAgent;
+ bool connectedBrokerInAgentList;
+ qpid::messaging::Address replyAddress;
+ std::string directBase;
+ std::string topicBase;
+ boost::shared_ptr<SchemaCache> schemaCache;
+ qpid::sys::Mutex corrlock;
+ uint32_t nextCorrelator;
+
+ void enqueueEvent(const ConsoleEvent&);
+ void enqueueEventLH(const ConsoleEvent&);
+ void dispatch(qpid::messaging::Message);
+ void sendBrokerLocate();
+ void sendAgentLocate();
+ void handleAgentUpdate(const std::string&, const qpid::types::Variant::Map&, const qpid::messaging::Message&);
+ void handleV1SchemaResponse(qpid::management::Buffer&, uint32_t, const qpid::messaging::Message&);
+ void periodicProcessing(uint64_t);
+ void alertEventNotifierLH(bool readable);
+ void run();
+ uint32_t correlator() { qpid::sys::Mutex::ScopedLock l(corrlock); return nextCorrelator++; }
+
+ friend class AgentImpl;
+ };
+
+ struct ConsoleSessionImplAccess {
+ static ConsoleSessionImpl& get(ConsoleSession& session);
+ static const ConsoleSessionImpl& get(const ConsoleSession& session);
+ };
+}
+
+#endif
diff --git a/qpid/cpp/src/qmf/Data.cpp b/qpid/cpp/src/qmf/Data.cpp
new file mode 100644
index 0000000000..c503bab445
--- /dev/null
+++ b/qpid/cpp/src/qmf/Data.cpp
@@ -0,0 +1,130 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qmf/DataImpl.h"
+#include "qmf/DataAddrImpl.h"
+#include "qmf/SchemaImpl.h"
+#include "qmf/SchemaIdImpl.h"
+#include "qmf/PrivateImplRef.h"
+#include "qmf/SchemaProperty.h"
+
+using namespace std;
+using namespace qmf;
+using qpid::types::Variant;
+
+typedef PrivateImplRef<Data> PI;
+
+Data::Data(DataImpl* impl) { PI::ctor(*this, impl); }
+Data::Data(const Data& s) : qmf::Handle<DataImpl>() { PI::copy(*this, s); }
+Data::~Data() { PI::dtor(*this); }
+Data& Data::operator=(const Data& s) { return PI::assign(*this, s); }
+
+Data::Data(const Schema& s) { PI::ctor(*this, new DataImpl(s)); }
+void Data::setAddr(const DataAddr& a) { impl->setAddr(a); }
+void Data::setProperty(const string& k, const qpid::types::Variant& v) { impl->setProperty(k, v); }
+void Data::overwriteProperties(const qpid::types::Variant::Map& m) { impl->overwriteProperties(m); }
+bool Data::hasSchema() const { return impl->hasSchema(); }
+bool Data::hasAddr() const { return impl->hasAddr(); }
+const SchemaId& Data::getSchemaId() const { return impl->getSchemaId(); }
+const DataAddr& Data::getAddr() const { return impl->getAddr(); }
+const Variant& Data::getProperty(const string& k) const { return impl->getProperty(k); }
+const Variant::Map& Data::getProperties() const { return impl->getProperties(); }
+bool Data::hasAgent() const { return impl->hasAgent(); }
+const Agent& Data::getAgent() const { return impl->getAgent(); }
+
+
+void DataImpl::overwriteProperties(const Variant::Map& m) {
+ for (Variant::Map::const_iterator iter = m.begin(); iter != m.end(); iter++)
+ properties[iter->first] = iter->second;
+}
+
+const Variant& DataImpl::getProperty(const string& k) const {
+ Variant::Map::const_iterator iter = properties.find(k);
+ if (iter == properties.end())
+ throw KeyNotFound(k);
+ return iter->second;
+}
+
+
+DataImpl::DataImpl(const qpid::types::Variant::Map& map, const Agent& a)
+{
+ Variant::Map::const_iterator iter;
+
+ agent = a;
+
+ iter = map.find("_values");
+ if (iter != map.end())
+ properties = iter->second.asMap();
+
+ iter = map.find("_object_id");
+ if (iter != map.end())
+ dataAddr = DataAddr(new DataAddrImpl(iter->second.asMap()));
+
+ iter = map.find("_schema_id");
+ if (iter != map.end())
+ schemaId = SchemaId(new SchemaIdImpl(iter->second.asMap()));
+}
+
+
+Variant::Map DataImpl::asMap() const
+{
+ Variant::Map result;
+
+ result["_values"] = properties;
+
+ if (hasAddr()) {
+ const DataAddrImpl& aImpl(DataAddrImplAccess::get(getAddr()));
+ result["_object_id"] = aImpl.asMap();
+ }
+
+ if (hasSchema()) {
+ const SchemaIdImpl& sImpl(SchemaIdImplAccess::get(getSchemaId()));
+ result["_schema_id"] = sImpl.asMap();
+ }
+
+ return result;
+}
+
+
+void DataImpl::setProperty(const std::string& k, const qpid::types::Variant& v)
+{
+ if (schema.isValid()) {
+ //
+ // If we have a valid schema, make sure that the property is included in the
+ // schema and that the variant type is compatible with the schema type.
+ //
+ if (!SchemaImplAccess::get(schema).isValidProperty(k, v))
+ throw QmfException("Property '" + k + "' either not in the schema or value is of incompatible type");
+ }
+ properties[k] = v;
+}
+
+
+DataImpl& DataImplAccess::get(Data& item)
+{
+ return *item.impl;
+}
+
+
+const DataImpl& DataImplAccess::get(const Data& item)
+{
+ return *item.impl;
+}
diff --git a/qpid/cpp/src/qmf/DataAddr.cpp b/qpid/cpp/src/qmf/DataAddr.cpp
new file mode 100644
index 0000000000..08b64d5b5d
--- /dev/null
+++ b/qpid/cpp/src/qmf/DataAddr.cpp
@@ -0,0 +1,108 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qmf/DataAddrImpl.h"
+#include "qmf/PrivateImplRef.h"
+#include "qmf/DataAddr.h"
+#include <iostream>
+
+using namespace std;
+using namespace qmf;
+using qpid::types::Variant;
+
+typedef PrivateImplRef<DataAddr> PI;
+
+DataAddr::DataAddr(DataAddrImpl* impl) { PI::ctor(*this, impl); }
+DataAddr::DataAddr(const DataAddr& s) : qmf::Handle<DataAddrImpl>() { PI::copy(*this, s); }
+DataAddr::~DataAddr() { PI::dtor(*this); }
+DataAddr& DataAddr::operator=(const DataAddr& s) { return PI::assign(*this, s); }
+
+bool DataAddr::operator==(const DataAddr& o) { return *impl == *o.impl; }
+bool DataAddr::operator==(const DataAddr& o) const { return *impl == *o.impl; }
+bool DataAddr::operator<(const DataAddr& o) { return *impl < *o.impl; }
+bool DataAddr::operator<(const DataAddr& o) const { return *impl < *o.impl; }
+
+DataAddr::DataAddr(const qpid::types::Variant::Map& m) { PI::ctor(*this, new DataAddrImpl(m)); }
+DataAddr::DataAddr(const string& n, const string& a, uint32_t e) { PI::ctor(*this, new DataAddrImpl(n, a, e)); }
+const string& DataAddr::getName() const { return impl->getName(); }
+const string& DataAddr::getAgentName() const { return impl->getAgentName(); }
+uint32_t DataAddr::getAgentEpoch() const { return impl->getAgentEpoch(); }
+Variant::Map DataAddr::asMap() const { return impl->asMap(); }
+
+bool DataAddrImpl::operator==(const DataAddrImpl& other) const
+{
+ return
+ agentName == other.agentName &&
+ name == other.name &&
+ agentEpoch == other.agentEpoch;
+}
+
+
+bool DataAddrImpl::operator<(const DataAddrImpl& other) const
+{
+ if (agentName < other.agentName) return true;
+ if (agentName > other.agentName) return false;
+ if (name < other.name) return true;
+ if (name > other.name) return false;
+ return agentEpoch < other.agentEpoch;
+}
+
+
+DataAddrImpl::DataAddrImpl(const Variant::Map& map) : agentEpoch(0)
+{
+ Variant::Map::const_iterator iter;
+
+ iter = map.find("_agent_name");
+ if (iter != map.end())
+ agentName = iter->second.asString();
+
+ iter = map.find("_object_name");
+ if (iter != map.end())
+ name = iter->second.asString();
+
+ iter = map.find("_agent_epoch");
+ if (iter != map.end())
+ agentEpoch = (uint32_t) iter->second.asUint64();
+}
+
+
+Variant::Map DataAddrImpl::asMap() const
+{
+ Variant::Map result;
+
+ result["_agent_name"] = agentName;
+ result["_object_name"] = name;
+ if (agentEpoch > 0)
+ result["_agent_epoch"] = agentEpoch;
+ return result;
+}
+
+
+DataAddrImpl& DataAddrImplAccess::get(DataAddr& item)
+{
+ return *item.impl;
+}
+
+
+const DataAddrImpl& DataAddrImplAccess::get(const DataAddr& item)
+{
+ return *item.impl;
+}
diff --git a/qpid/cpp/src/qmf/DataAddrImpl.h b/qpid/cpp/src/qmf/DataAddrImpl.h
new file mode 100644
index 0000000000..11d512f0c4
--- /dev/null
+++ b/qpid/cpp/src/qmf/DataAddrImpl.h
@@ -0,0 +1,73 @@
+#ifndef _QMF_DATA_ADDR_IMPL_H_
+#define _QMF_DATA_ADDR_IMPL_H_
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/RefCounted.h"
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/types/Variant.h"
+#include "qmf/DataAddr.h"
+
+namespace qmf {
+ class DataAddrImpl : public virtual qpid::RefCounted {
+ public:
+ //
+ // Impl-only methods
+ //
+ void setName(const std::string& n) { name = n; }
+ void setAgent(const std::string& n, uint32_t e=0) { agentName = n; agentEpoch = e; }
+
+ //
+ // Methods from API handle
+ //
+ bool operator==(const DataAddrImpl&) const;
+ bool operator<(const DataAddrImpl&) const;
+ DataAddrImpl(const qpid::types::Variant::Map&);
+ DataAddrImpl(const std::string& _name, const std::string& _agentName, uint32_t _agentEpoch=0) :
+ agentName(_agentName), name(_name), agentEpoch(_agentEpoch) {}
+ const std::string& getName() const { return name; }
+ const std::string& getAgentName() const { return agentName; }
+ uint32_t getAgentEpoch() const { return agentEpoch; }
+ qpid::types::Variant::Map asMap() const;
+
+ private:
+ std::string agentName;
+ std::string name;
+ uint32_t agentEpoch;
+ };
+
+ struct DataAddrImplAccess
+ {
+ static DataAddrImpl& get(DataAddr&);
+ static const DataAddrImpl& get(const DataAddr&);
+ };
+
+ struct DataAddrCompare {
+ bool operator() (const DataAddr& lhs, const DataAddr& rhs) const
+ {
+ if (lhs.getName() != rhs.getName())
+ return lhs.getName() < rhs.getName();
+ return lhs.getAgentName() < rhs.getAgentName();
+ }
+ };
+}
+
+#endif
diff --git a/qpid/cpp/src/qmf/DataImpl.h b/qpid/cpp/src/qmf/DataImpl.h
new file mode 100644
index 0000000000..4ac3197da0
--- /dev/null
+++ b/qpid/cpp/src/qmf/DataImpl.h
@@ -0,0 +1,84 @@
+#ifndef _QMF_DATA_IMPL_H_
+#define _QMF_DATA_IMPL_H_
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/RefCounted.h"
+#include "qmf/Data.h"
+#include "qmf/SchemaId.h"
+#include "qmf/Schema.h"
+#include "qmf/DataAddr.h"
+#include "qmf/Agent.h"
+#include "qmf/AgentSubscription.h"
+#include "qpid/types/Variant.h"
+
+namespace qmf {
+ class DataImpl : public virtual qpid::RefCounted {
+ public:
+ //
+ // Public impl-only methods
+ //
+ DataImpl(const qpid::types::Variant::Map&, const Agent&);
+ qpid::types::Variant::Map asMap() const;
+ DataImpl() {}
+ void addSubscription(boost::shared_ptr<AgentSubscription>);
+ void delSubscription(uint64_t);
+ qpid::types::Variant::Map publishSubscription(uint64_t);
+ const Schema& getSchema() const { return schema; }
+
+ //
+ // Methods from API handle
+ //
+ DataImpl(const Schema& s) : schema(s) {}
+ void setAddr(const DataAddr& a) { dataAddr = a; }
+ void setProperty(const std::string& k, const qpid::types::Variant& v);
+ void overwriteProperties(const qpid::types::Variant::Map& m);
+ bool hasSchema() const { return schemaId.isValid() || schema.isValid(); }
+ bool hasAddr() const { return dataAddr.isValid(); }
+ const SchemaId& getSchemaId() const { if (schema.isValid()) return schema.getSchemaId(); else return schemaId; }
+ const DataAddr& getAddr() const { return dataAddr; }
+ const qpid::types::Variant& getProperty(const std::string& k) const;
+ const qpid::types::Variant::Map& getProperties() const { return properties; }
+ bool hasAgent() const { return agent.isValid(); }
+ const Agent& getAgent() const { return agent; }
+
+ private:
+ struct Subscr {
+ boost::shared_ptr<AgentSubscription> subscription;
+ qpid::types::Variant::Map deltas;
+ };
+ std::map<uint64_t, boost::shared_ptr<Subscr> > subscriptions;
+
+ SchemaId schemaId;
+ Schema schema;
+ DataAddr dataAddr;
+ qpid::types::Variant::Map properties;
+ Agent agent;
+ };
+
+ struct DataImplAccess
+ {
+ static DataImpl& get(Data&);
+ static const DataImpl& get(const Data&);
+ };
+}
+
+#endif
diff --git a/qpid/cpp/src/qmf/EventNotifierImpl.cpp b/qpid/cpp/src/qmf/EventNotifierImpl.cpp
new file mode 100644
index 0000000000..81b6d637a3
--- /dev/null
+++ b/qpid/cpp/src/qmf/EventNotifierImpl.cpp
@@ -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.
+ */
+
+#include "qmf/EventNotifierImpl.h"
+#include "qmf/AgentSessionImpl.h"
+#include "qmf/ConsoleSessionImpl.h"
+
+namespace qmf {
+
+EventNotifierImpl::EventNotifierImpl(AgentSession& agentSession)
+ : readable(false), agent(agentSession)
+{
+ AgentSessionImplAccess::get(agent).setEventNotifier(this);
+}
+
+
+EventNotifierImpl::EventNotifierImpl(ConsoleSession& consoleSession)
+ : readable(false), console(consoleSession)
+{
+ ConsoleSessionImplAccess::get(console).setEventNotifier(this);
+}
+
+
+EventNotifierImpl::~EventNotifierImpl()
+{
+ if (agent.isValid())
+ AgentSessionImplAccess::get(agent).setEventNotifier(NULL);
+ if (console.isValid())
+ ConsoleSessionImplAccess::get(console).setEventNotifier(NULL);
+}
+
+void EventNotifierImpl::setReadable(bool readable)
+{
+ update(readable);
+ this->readable = readable;
+}
+
+
+bool EventNotifierImpl::isReadable() const
+{
+ return this->readable;
+}
+
+}
diff --git a/qpid/cpp/src/qmf/EventNotifierImpl.h b/qpid/cpp/src/qmf/EventNotifierImpl.h
new file mode 100644
index 0000000000..d85f9979d2
--- /dev/null
+++ b/qpid/cpp/src/qmf/EventNotifierImpl.h
@@ -0,0 +1,48 @@
+#ifndef __QMF_EVENT_NOTIFIER_IMPL_H
+#define __QMF_EVENT_NOTIFIER_IMPL_H
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "qmf/AgentSession.h"
+#include "qmf/ConsoleSession.h"
+
+namespace qmf
+{
+ class EventNotifierImpl {
+ private:
+ bool readable;
+ AgentSession agent;
+ ConsoleSession console;
+
+ public:
+ EventNotifierImpl(AgentSession& agentSession);
+ EventNotifierImpl(ConsoleSession& consoleSession);
+ virtual ~EventNotifierImpl();
+
+ void setReadable(bool readable);
+ bool isReadable() const;
+
+ protected:
+ virtual void update(bool readable) = 0;
+ };
+}
+
+#endif
+
diff --git a/qpid/cpp/src/qmf/Expression.cpp b/qpid/cpp/src/qmf/Expression.cpp
new file mode 100644
index 0000000000..7d48678c15
--- /dev/null
+++ b/qpid/cpp/src/qmf/Expression.cpp
@@ -0,0 +1,441 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qmf/exceptions.h"
+#include "qmf/Expression.h"
+#include <iostream>
+
+using namespace std;
+using namespace qmf;
+using namespace qpid::types;
+
+Expression::Expression(const Variant::List& expr)
+{
+ static int level(0);
+ level++;
+ Variant::List::const_iterator iter(expr.begin());
+ string op(iter->asString());
+ iter++;
+
+ if (op == "not") logicalOp = LOGICAL_NOT;
+ else if (op == "and") logicalOp = LOGICAL_AND;
+ else if (op == "or") logicalOp = LOGICAL_OR;
+ else {
+ logicalOp = LOGICAL_ID;
+ if (op == "eq") boolOp = BOOL_EQ;
+ else if (op == "ne") boolOp = BOOL_NE;
+ else if (op == "lt") boolOp = BOOL_LT;
+ else if (op == "le") boolOp = BOOL_LE;
+ else if (op == "gt") boolOp = BOOL_GT;
+ else if (op == "ge") boolOp = BOOL_GE;
+ else if (op == "re_match") boolOp = BOOL_RE_MATCH;
+ else if (op == "exists") boolOp = BOOL_EXISTS;
+ else if (op == "true") boolOp = BOOL_TRUE;
+ else if (op == "false") boolOp = BOOL_FALSE;
+ else
+ throw QmfException("Invalid operator in predicate expression");
+ }
+
+ if (logicalOp == LOGICAL_ID) {
+ switch (boolOp) {
+ case BOOL_EQ:
+ case BOOL_NE:
+ case BOOL_LT:
+ case BOOL_LE:
+ case BOOL_GT:
+ case BOOL_GE:
+ case BOOL_RE_MATCH:
+ //
+ // Binary operator: get two operands.
+ //
+ operandCount = 2;
+ break;
+
+ case BOOL_EXISTS:
+ //
+ // Unary operator: get one operand.
+ //
+ operandCount = 1;
+ break;
+
+ case BOOL_TRUE:
+ case BOOL_FALSE:
+ //
+ // Literal operator: no operands.
+ //
+ operandCount = 0;
+ break;
+ }
+
+ for (int idx = 0; idx < operandCount; idx++) {
+ if (iter == expr.end())
+ throw QmfException("Too few operands for operation: " + op);
+ if (iter->getType() == VAR_STRING) {
+ quoted[idx] = false;
+ operands[idx] = *iter;
+ } else if (iter->getType() == VAR_LIST) {
+ const Variant::List& sublist(iter->asList());
+ Variant::List::const_iterator subIter(sublist.begin());
+ if (subIter != sublist.end() && subIter->asString() == "quote") {
+ quoted[idx] = true;
+ subIter++;
+ if (subIter != sublist.end()) {
+ operands[idx] = *subIter;
+ subIter++;
+ if (subIter != sublist.end())
+ throw QmfException("Extra tokens at end of 'quote'");
+ }
+ } else
+ throw QmfException("Expected '[quote, <token>]'");
+ } else
+ throw QmfException("Expected string or list as operand for: " + op);
+ iter++;
+ }
+
+ if (iter != expr.end())
+ throw QmfException("Too many operands for operation: " + op);
+
+ } else {
+ //
+ // This is a logical expression, collect sub-expressions
+ //
+ while (iter != expr.end()) {
+ if (iter->getType() != VAR_LIST)
+ throw QmfException("Operands of " + op + " must be lists");
+ expressionList.push_back(boost::shared_ptr<Expression>(new Expression(iter->asList())));
+ iter++;
+ }
+ }
+ level--;
+}
+
+
+bool Expression::evaluate(const Variant::Map& data) const
+{
+ list<boost::shared_ptr<Expression> >::const_iterator iter;
+
+ switch (logicalOp) {
+ case LOGICAL_ID:
+ return boolEval(data);
+
+ case LOGICAL_NOT:
+ for (iter = expressionList.begin(); iter != expressionList.end(); iter++)
+ if ((*iter)->evaluate(data))
+ return false;
+ return true;
+
+ case LOGICAL_AND:
+ for (iter = expressionList.begin(); iter != expressionList.end(); iter++)
+ if (!(*iter)->evaluate(data))
+ return false;
+ return true;
+
+ case LOGICAL_OR:
+ for (iter = expressionList.begin(); iter != expressionList.end(); iter++)
+ if ((*iter)->evaluate(data))
+ return true;
+ return false;
+ }
+
+ return false;
+}
+
+
+bool Expression::boolEval(const Variant::Map& data) const
+{
+ Variant val[2];
+ bool exists[2];
+
+ for (int idx = 0; idx < operandCount; idx++) {
+ if (quoted[idx]) {
+ exists[idx] = true;
+ val[idx] = operands[idx];
+ } else {
+ Variant::Map::const_iterator mIter(data.find(operands[idx].asString()));
+ if (mIter == data.end()) {
+ exists[idx] = false;
+ } else {
+ exists[idx] = true;
+ val[idx] = mIter->second;
+ }
+ }
+ }
+
+ switch (boolOp) {
+ case BOOL_EQ: return (exists[0] && exists[1] && (val[0].asString() == val[1].asString()));
+ case BOOL_NE: return (exists[0] && exists[1] && (val[0].asString() != val[1].asString()));
+ case BOOL_LT: return (exists[0] && exists[1] && lessThan(val[0], val[1]));
+ case BOOL_LE: return (exists[0] && exists[1] && lessEqual(val[0], val[1]));
+ case BOOL_GT: return (exists[0] && exists[1] && greaterThan(val[0], val[1]));
+ case BOOL_GE: return (exists[0] && exists[1] && greaterEqual(val[0], val[1]));
+ case BOOL_RE_MATCH: return false; // TODO
+ case BOOL_EXISTS: return exists[0];
+ case BOOL_TRUE: return true;
+ case BOOL_FALSE: return false;
+ }
+
+ return false;
+}
+
+bool Expression::lessThan(const Variant& left, const Variant& right) const
+{
+ switch (left.getType()) {
+ case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64:
+ case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64:
+ switch (right.getType()) {
+ case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64:
+ case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64:
+ return left.asInt64() < right.asInt64();
+ case VAR_STRING:
+ try {
+ return left.asInt64() < right.asInt64();
+ } catch (std::exception&) {}
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case VAR_FLOAT: case VAR_DOUBLE:
+ switch (right.getType()) {
+ case VAR_FLOAT: case VAR_DOUBLE:
+ return left.asDouble() < right.asDouble();
+ case VAR_STRING:
+ try {
+ return left.asDouble() < right.asDouble();
+ } catch (std::exception&) {}
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case VAR_STRING:
+ switch (right.getType()) {
+ case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64:
+ case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64:
+ try {
+ return left.asInt64() < right.asInt64();
+ } catch (std::exception&) {}
+ break;
+
+ case VAR_FLOAT: case VAR_DOUBLE:
+ try {
+ return left.asDouble() < right.asDouble();
+ } catch (std::exception&) {}
+ break;
+
+ case VAR_STRING:
+ return left.asString() < right.asString();
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+
+ return false;
+}
+
+
+bool Expression::lessEqual(const Variant& left, const Variant& right) const
+{
+ switch (left.getType()) {
+ case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64:
+ case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64:
+ switch (right.getType()) {
+ case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64:
+ case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64:
+ return left.asInt64() <= right.asInt64();
+ case VAR_STRING:
+ try {
+ return left.asInt64() <= right.asInt64();
+ } catch (std::exception&) {}
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case VAR_FLOAT: case VAR_DOUBLE:
+ switch (right.getType()) {
+ case VAR_FLOAT: case VAR_DOUBLE:
+ return left.asDouble() <= right.asDouble();
+ case VAR_STRING:
+ try {
+ return left.asDouble() <= right.asDouble();
+ } catch (std::exception&) {}
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case VAR_STRING:
+ switch (right.getType()) {
+ case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64:
+ case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64:
+ try {
+ return left.asInt64() <= right.asInt64();
+ } catch (std::exception&) {}
+ break;
+
+ case VAR_FLOAT: case VAR_DOUBLE:
+ try {
+ return left.asDouble() <= right.asDouble();
+ } catch (std::exception&) {}
+ break;
+
+ case VAR_STRING:
+ return left.asString() <= right.asString();
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+
+ return false;
+}
+
+
+bool Expression::greaterThan(const Variant& left, const Variant& right) const
+{
+ switch (left.getType()) {
+ case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64:
+ case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64:
+ switch (right.getType()) {
+ case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64:
+ case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64:
+ return left.asInt64() > right.asInt64();
+ case VAR_STRING:
+ try {
+ return left.asInt64() > right.asInt64();
+ } catch (std::exception&) {}
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case VAR_FLOAT: case VAR_DOUBLE:
+ switch (right.getType()) {
+ case VAR_FLOAT: case VAR_DOUBLE:
+ return left.asDouble() > right.asDouble();
+ case VAR_STRING:
+ try {
+ return left.asDouble() > right.asDouble();
+ } catch (std::exception&) {}
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case VAR_STRING:
+ switch (right.getType()) {
+ case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64:
+ case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64:
+ try {
+ return left.asInt64() > right.asInt64();
+ } catch (std::exception&) {}
+ break;
+
+ case VAR_FLOAT: case VAR_DOUBLE:
+ try {
+ return left.asDouble() > right.asDouble();
+ } catch (std::exception&) {}
+ break;
+
+ case VAR_STRING:
+ return left.asString() > right.asString();
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+
+ return false;
+}
+
+
+bool Expression::greaterEqual(const Variant& left, const Variant& right) const
+{
+ switch (left.getType()) {
+ case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64:
+ case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64:
+ switch (right.getType()) {
+ case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64:
+ case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64:
+ return left.asInt64() >= right.asInt64();
+ case VAR_STRING:
+ try {
+ return left.asInt64() >= right.asInt64();
+ } catch (std::exception&) {}
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case VAR_FLOAT: case VAR_DOUBLE:
+ switch (right.getType()) {
+ case VAR_FLOAT: case VAR_DOUBLE:
+ return left.asDouble() >= right.asDouble();
+ case VAR_STRING:
+ try {
+ return left.asDouble() >= right.asDouble();
+ } catch (std::exception&) {}
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case VAR_STRING:
+ switch (right.getType()) {
+ case VAR_UINT8: case VAR_UINT16: case VAR_UINT32: case VAR_UINT64:
+ case VAR_INT8: case VAR_INT16: case VAR_INT32: case VAR_INT64:
+ try {
+ return left.asInt64() >= right.asInt64();
+ } catch (std::exception&) {}
+ break;
+
+ case VAR_FLOAT: case VAR_DOUBLE:
+ try {
+ return left.asDouble() >= right.asDouble();
+ } catch (std::exception&) {}
+ break;
+
+ case VAR_STRING:
+ return left.asString() >= right.asString();
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+
+ return false;
+}
+
+
diff --git a/qpid/cpp/src/qmf/Expression.h b/qpid/cpp/src/qmf/Expression.h
new file mode 100644
index 0000000000..6fbfdbc4ba
--- /dev/null
+++ b/qpid/cpp/src/qmf/Expression.h
@@ -0,0 +1,73 @@
+#ifndef _QMF_EXPRESSION_H_
+#define _QMF_EXPRESSION_H_
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/types/Variant.h"
+#include <string>
+#include <list>
+#include <boost/shared_ptr.hpp>
+
+namespace qmf {
+
+ enum LogicalOp {
+ LOGICAL_ID = 1,
+ LOGICAL_NOT = 2,
+ LOGICAL_AND = 3,
+ LOGICAL_OR = 4
+ };
+
+ enum BooleanOp {
+ BOOL_EQ = 1,
+ BOOL_NE = 2,
+ BOOL_LT = 3,
+ BOOL_LE = 4,
+ BOOL_GT = 5,
+ BOOL_GE = 6,
+ BOOL_RE_MATCH = 7,
+ BOOL_EXISTS = 8,
+ BOOL_TRUE = 9,
+ BOOL_FALSE = 10
+ };
+
+ class Expression {
+ public:
+ Expression(const qpid::types::Variant::List& expr);
+ bool evaluate(const qpid::types::Variant::Map& data) const;
+ private:
+ LogicalOp logicalOp;
+ BooleanOp boolOp;
+ int operandCount;
+ qpid::types::Variant operands[2];
+ bool quoted[2];
+ std::list<boost::shared_ptr<Expression> > expressionList;
+
+ bool boolEval(const qpid::types::Variant::Map& data) const;
+ bool lessThan(const qpid::types::Variant& left, const qpid::types::Variant& right) const;
+ bool lessEqual(const qpid::types::Variant& left, const qpid::types::Variant& right) const;
+ bool greaterThan(const qpid::types::Variant& left, const qpid::types::Variant& right) const;
+ bool greaterEqual(const qpid::types::Variant& left, const qpid::types::Variant& right) const;
+ };
+
+}
+
+#endif
+
diff --git a/qpid/cpp/src/qmf/Hash.cpp b/qpid/cpp/src/qmf/Hash.cpp
new file mode 100644
index 0000000000..86738dda2f
--- /dev/null
+++ b/qpid/cpp/src/qmf/Hash.cpp
@@ -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.
+ *
+ */
+
+#include "qmf/Hash.h"
+
+using namespace qmf;
+
+Hash::Hash()
+{
+ data[0] = 0x5A5A5A5A5A5A5A5ALL;
+ data[1] = 0x5A5A5A5A5A5A5A5ALL;
+}
+
+void Hash::update(const char* s, uint32_t len)
+{
+ uint64_t* first = &data[0];
+ uint64_t* second = &data[1];
+
+ for (uint32_t idx = 0; idx < len; idx++) {
+ uint64_t recycle = ((*second & 0xff00000000000000LL) >> 56);
+ *second = *second << 8;
+ *second |= ((*first & 0xFF00000000000000LL) >> 56);
+ *first = *first << 8;
+ *first = *first + (uint64_t) s[idx] + recycle;
+ }
+}
+
diff --git a/qpid/cpp/src/qmf/Hash.h b/qpid/cpp/src/qmf/Hash.h
new file mode 100644
index 0000000000..4bd76832aa
--- /dev/null
+++ b/qpid/cpp/src/qmf/Hash.h
@@ -0,0 +1,44 @@
+#ifndef QMF_HASH_H
+#define QMF_HASH_H
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/types/Uuid.h"
+#include <string>
+
+namespace qmf {
+ class Hash {
+ public:
+ Hash();
+ qpid::types::Uuid asUuid() const { return qpid::types::Uuid((const unsigned char*) data); }
+ void update(const char* s, uint32_t len);
+ void update(uint8_t v) { update((char*) &v, sizeof(v)); }
+ void update(uint32_t v) { update((char*) &v, sizeof(v)); }
+ void update(const std::string& v) { update(const_cast<char*>(v.c_str()), v.size()); }
+ void update(bool v) { update(uint8_t(v ? 1 : 0)); }
+
+ private:
+ uint64_t data[2];
+ };
+}
+
+#endif
diff --git a/qpid/cpp/src/qmf/PosixEventNotifier.cpp b/qpid/cpp/src/qmf/PosixEventNotifier.cpp
new file mode 100644
index 0000000000..a364cc155d
--- /dev/null
+++ b/qpid/cpp/src/qmf/PosixEventNotifier.cpp
@@ -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.
+ */
+
+#include "qmf/posix/EventNotifier.h"
+#include "qmf/PosixEventNotifierImpl.h"
+#include "qmf/PrivateImplRef.h"
+
+using namespace qmf;
+using namespace std;
+
+typedef qmf::PrivateImplRef<posix::EventNotifier> PI;
+
+posix::EventNotifier::EventNotifier(PosixEventNotifierImpl* impl) { PI::ctor(*this, impl); }
+
+posix::EventNotifier::EventNotifier(AgentSession& agentSession)
+{
+ PI::ctor(*this, new PosixEventNotifierImpl(agentSession));
+}
+
+
+posix::EventNotifier::EventNotifier(ConsoleSession& consoleSession)
+{
+ PI::ctor(*this, new PosixEventNotifierImpl(consoleSession));
+}
+
+
+posix::EventNotifier::EventNotifier(const posix::EventNotifier& that)
+ : Handle<PosixEventNotifierImpl>()
+{
+ PI::copy(*this, that);
+}
+
+
+posix::EventNotifier::~EventNotifier()
+{
+ PI::dtor(*this);
+}
+
+posix::EventNotifier& posix::EventNotifier::operator=(const posix::EventNotifier& that)
+{
+ return PI::assign(*this, that);
+}
+
+
+int posix::EventNotifier::getHandle() const
+{
+ return impl->getHandle();
+}
+
diff --git a/qpid/cpp/src/qmf/PosixEventNotifierImpl.cpp b/qpid/cpp/src/qmf/PosixEventNotifierImpl.cpp
new file mode 100644
index 0000000000..011dbcc214
--- /dev/null
+++ b/qpid/cpp/src/qmf/PosixEventNotifierImpl.cpp
@@ -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.
+ */
+
+#include "PosixEventNotifierImpl.h"
+#include "qpid/log/Statement.h"
+
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#define BUFFER_SIZE 10
+
+using namespace qmf;
+
+PosixEventNotifierImpl::PosixEventNotifierImpl(AgentSession& agentSession)
+ : EventNotifierImpl(agentSession)
+{
+ openHandle();
+}
+
+
+PosixEventNotifierImpl::PosixEventNotifierImpl(ConsoleSession& consoleSession)
+ : EventNotifierImpl(consoleSession)
+{
+ openHandle();
+}
+
+
+PosixEventNotifierImpl::~PosixEventNotifierImpl()
+{
+ closeHandle();
+}
+
+
+void PosixEventNotifierImpl::update(bool readable)
+{
+ char buffer[BUFFER_SIZE];
+
+ if(readable && !this->isReadable()) {
+ if (::write(myHandle, "1", 1) == -1)
+ QPID_LOG(error, "PosixEventNotifierImpl::update write failed: " << errno);
+ }
+ else if(!readable && this->isReadable()) {
+ if (::read(yourHandle, buffer, BUFFER_SIZE) == -1)
+ QPID_LOG(error, "PosixEventNotifierImpl::update read failed: " << errno);
+ }
+}
+
+
+void PosixEventNotifierImpl::openHandle()
+{
+ int pair[2];
+
+ if(::pipe(pair) == -1)
+ throw QmfException("Unable to open event notifier handle.");
+
+ yourHandle = pair[0];
+ myHandle = pair[1];
+
+ int flags;
+
+ flags = ::fcntl(yourHandle, F_GETFL);
+ if((::fcntl(yourHandle, F_SETFL, flags | O_NONBLOCK)) == -1)
+ throw QmfException("Unable to make remote handle non-blocking.");
+
+ flags = ::fcntl(myHandle, F_GETFL);
+ if((::fcntl(myHandle, F_SETFL, flags | O_NONBLOCK)) == -1)
+ throw QmfException("Unable to make local handle non-blocking.");
+}
+
+
+void PosixEventNotifierImpl::closeHandle()
+{
+ if(myHandle > 0) {
+ ::close(myHandle);
+ myHandle = -1;
+ }
+
+ if(yourHandle > 0) {
+ ::close(yourHandle);
+ yourHandle = -1;
+ }
+}
+
+
+PosixEventNotifierImpl& PosixEventNotifierImplAccess::get(posix::EventNotifier& notifier)
+{
+ return *notifier.impl;
+}
+
+
+const PosixEventNotifierImpl& PosixEventNotifierImplAccess::get(const posix::EventNotifier& notifier)
+{
+ return *notifier.impl;
+}
+
diff --git a/qpid/cpp/src/qmf/PosixEventNotifierImpl.h b/qpid/cpp/src/qmf/PosixEventNotifierImpl.h
new file mode 100644
index 0000000000..c8a7446bd5
--- /dev/null
+++ b/qpid/cpp/src/qmf/PosixEventNotifierImpl.h
@@ -0,0 +1,61 @@
+#ifndef __QMF_POSIX_EVENT_NOTIFIER_IMPL_H
+#define __QMF_POSIX_EVENT_NOTIFIER_IMPL_H
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "qmf/posix/EventNotifier.h"
+#include "qmf/EventNotifierImpl.h"
+#include "qpid/RefCounted.h"
+
+namespace qmf
+{
+ class AgentSession;
+ class ConsoleSession;
+
+ class PosixEventNotifierImpl : public EventNotifierImpl, public virtual qpid::RefCounted
+ {
+ public:
+ PosixEventNotifierImpl(AgentSession& agentSession);
+ PosixEventNotifierImpl(ConsoleSession& consoleSession);
+ virtual ~PosixEventNotifierImpl();
+
+ int getHandle() const { return yourHandle; }
+
+ private:
+ int myHandle;
+ int yourHandle;
+
+ void openHandle();
+ void closeHandle();
+
+ protected:
+ void update(bool readable);
+ };
+
+ struct PosixEventNotifierImplAccess
+ {
+ static PosixEventNotifierImpl& get(posix::EventNotifier& notifier);
+ static const PosixEventNotifierImpl& get(const posix::EventNotifier& notifier);
+ };
+
+}
+
+#endif
+
diff --git a/qpid/cpp/src/qmf/PrivateImplRef.h b/qpid/cpp/src/qmf/PrivateImplRef.h
new file mode 100644
index 0000000000..c0c07d7e1b
--- /dev/null
+++ b/qpid/cpp/src/qmf/PrivateImplRef.h
@@ -0,0 +1,93 @@
+#ifndef QMF_PRIVATEIMPL_H
+#define QMF_PRIVATEIMPL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qmf/ImportExport.h"
+#include "qpid/RefCounted.h"
+#include <boost/intrusive_ptr.hpp>
+
+namespace qmf {
+
+/**
+ * Helper class to implement a class with a private, reference counted
+ * implementation and reference semantics.
+ *
+ * Such classes are used in the public API to hide implementation, they
+ * should. Example of use:
+ *
+ * === Foo.h
+ *
+ * template <class T> PrivateImplRef;
+ * class FooImpl;
+ *
+ * Foo : public Handle<FooImpl> {
+ * public:
+ * Foo(FooImpl* = 0);
+ * Foo(const Foo&);
+ * ~Foo();
+ * Foo& operator=(const Foo&);
+ *
+ * int fooDo(); // and other Foo functions...
+ *
+ * private:
+ * typedef FooImpl Impl;
+ * Impl* impl;
+ * friend class PrivateImplRef<Foo>;
+ *
+ * === Foo.cpp
+ *
+ * typedef PrivateImplRef<Foo> PI;
+ * Foo::Foo(FooImpl* p) { PI::ctor(*this, p); }
+ * Foo::Foo(const Foo& c) : Handle<FooImpl>() { PI::copy(*this, c); }
+ * Foo::~Foo() { PI::dtor(*this); }
+ * Foo& Foo::operator=(const Foo& c) { return PI::assign(*this, c); }
+ *
+ * int foo::fooDo() { return impl->fooDo(); }
+ *
+ */
+template <class T> class PrivateImplRef {
+ public:
+ typedef typename T::Impl Impl;
+ typedef boost::intrusive_ptr<Impl> intrusive_ptr;
+
+ /** Get the implementation pointer from a handle */
+ static intrusive_ptr get(const T& t) { return intrusive_ptr(t.impl); }
+
+ /** Set the implementation pointer in a handle */
+ static void set(T& t, const intrusive_ptr& p) {
+ if (t.impl == p) return;
+ if (t.impl) intrusive_ptr_release(t.impl);
+ t.impl = p.get();
+ if (t.impl) intrusive_ptr_add_ref(t.impl);
+ }
+
+ // Helper functions to implement the ctor, dtor, copy, assign
+ static void ctor(T& t, Impl* p) { t.impl = p; if (p) intrusive_ptr_add_ref(p); }
+ static void copy(T& t, const T& x) { if (&t == &x) return; t.impl = 0; assign(t, x); }
+ static void dtor(T& t) { if(t.impl) intrusive_ptr_release(t.impl); }
+ static T& assign(T& t, const T& x) { set(t, get(x)); return t;}
+};
+
+} // namespace qmf
+
+#endif /*!QMF_PRIVATEIMPL_H*/
diff --git a/qpid/cpp/src/qmf/Query.cpp b/qpid/cpp/src/qmf/Query.cpp
new file mode 100644
index 0000000000..ee8ca38e59
--- /dev/null
+++ b/qpid/cpp/src/qmf/Query.cpp
@@ -0,0 +1,153 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qmf/PrivateImplRef.h"
+#include "qmf/exceptions.h"
+#include "qmf/QueryImpl.h"
+#include "qmf/DataAddrImpl.h"
+#include "qmf/SchemaIdImpl.h"
+#include "qpid/messaging/AddressParser.h"
+
+using namespace std;
+using namespace qmf;
+using qpid::types::Variant;
+
+typedef PrivateImplRef<Query> PI;
+
+Query::Query(QueryImpl* impl) { PI::ctor(*this, impl); }
+Query::Query(const Query& s) : qmf::Handle<QueryImpl>() { PI::copy(*this, s); }
+Query::~Query() { PI::dtor(*this); }
+Query& Query::operator=(const Query& s) { return PI::assign(*this, s); }
+
+Query::Query(QueryTarget t, const string& pr) { PI::ctor(*this, new QueryImpl(t, pr)); }
+Query::Query(QueryTarget t, const string& c, const string& p, const string& pr) { PI::ctor(*this, new QueryImpl(t, c, p, pr)); }
+Query::Query(QueryTarget t, const SchemaId& s, const string& pr) { PI::ctor(*this, new QueryImpl(t, s, pr)); }
+Query::Query(const DataAddr& a) { PI::ctor(*this, new QueryImpl(a)); }
+
+QueryTarget Query::getTarget() const { return impl->getTarget(); }
+const DataAddr& Query::getDataAddr() const { return impl->getDataAddr(); }
+const SchemaId& Query::getSchemaId() const { return impl->getSchemaId(); }
+void Query::setPredicate(const Variant::List& pr) { impl->setPredicate(pr); }
+const Variant::List& Query::getPredicate() const { return impl->getPredicate(); }
+bool Query::matchesPredicate(const qpid::types::Variant::Map& map) const { return impl->matchesPredicate(map); }
+
+
+QueryImpl::QueryImpl(const Variant::Map& map) : predicateCompiled(false)
+{
+ Variant::Map::const_iterator iter;
+
+ iter = map.find("_what");
+ if (iter == map.end())
+ throw QmfException("Query missing _what element");
+
+ const string& targetString(iter->second.asString());
+ if (targetString == "OBJECT") target = QUERY_OBJECT;
+ else if (targetString == "OBJECT_ID") target = QUERY_OBJECT_ID;
+ else if (targetString == "SCHEMA") target = QUERY_SCHEMA;
+ else if (targetString == "SCHEMA_ID") target = QUERY_SCHEMA_ID;
+ else
+ throw QmfException("Query with invalid _what value: " + targetString);
+
+ iter = map.find("_object_id");
+ if (iter != map.end()) {
+ auto_ptr<DataAddrImpl> addrImpl(new DataAddrImpl(iter->second.asMap()));
+ dataAddr = DataAddr(addrImpl.release());
+ }
+
+ iter = map.find("_schema_id");
+ if (iter != map.end()) {
+ auto_ptr<SchemaIdImpl> sidImpl(new SchemaIdImpl(iter->second.asMap()));
+ schemaId = SchemaId(sidImpl.release());
+ }
+
+ iter = map.find("_where");
+ if (iter != map.end())
+ predicate = iter->second.asList();
+}
+
+
+Variant::Map QueryImpl::asMap() const
+{
+ Variant::Map map;
+ string targetString;
+
+ switch (target) {
+ case QUERY_OBJECT : targetString = "OBJECT"; break;
+ case QUERY_OBJECT_ID : targetString = "OBJECT_ID"; break;
+ case QUERY_SCHEMA : targetString = "SCHEMA"; break;
+ case QUERY_SCHEMA_ID : targetString = "SCHEMA_ID"; break;
+ }
+
+ map["_what"] = targetString;
+
+ if (dataAddr.isValid())
+ map["_object_id"] = DataAddrImplAccess::get(dataAddr).asMap();
+
+ if (schemaId.isValid())
+ map["_schema_id"] = SchemaIdImplAccess::get(schemaId).asMap();
+
+ if (!predicate.empty())
+ map["_where"] = predicate;
+
+ return map;
+}
+
+
+bool QueryImpl::matchesPredicate(const qpid::types::Variant::Map& data) const
+{
+ if (predicate.empty())
+ return true;
+
+ if (!predicateCompiled) {
+ expression.reset(new Expression(predicate));
+ predicateCompiled = true;
+ }
+
+ return expression->evaluate(data);
+}
+
+
+void QueryImpl::parsePredicate(const string& pred)
+{
+ if (pred.empty())
+ return;
+
+ if (pred[0] == '[') {
+ //
+ // Parse this as an AddressParser list.
+ //
+ qpid::messaging::AddressParser parser(pred);
+ parser.parseList(predicate);
+ } else
+ throw QmfException("Invalid predicate format");
+}
+
+
+QueryImpl& QueryImplAccess::get(Query& item)
+{
+ return *item.impl;
+}
+
+
+const QueryImpl& QueryImplAccess::get(const Query& item)
+{
+ return *item.impl;
+}
diff --git a/qpid/cpp/src/qmf/QueryImpl.h b/qpid/cpp/src/qmf/QueryImpl.h
new file mode 100644
index 0000000000..27ec427684
--- /dev/null
+++ b/qpid/cpp/src/qmf/QueryImpl.h
@@ -0,0 +1,77 @@
+#ifndef _QMF_QUERY_IMPL_H_
+#define _QMF_QUERY_IMPL_H_
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/RefCounted.h"
+#include "qmf/Query.h"
+#include "qmf/DataAddr.h"
+#include "qmf/SchemaId.h"
+#include "qmf/Expression.h"
+#include "qpid/types/Variant.h"
+#include <boost/shared_ptr.hpp>
+
+namespace qmf {
+ class QueryImpl : public virtual qpid::RefCounted {
+ public:
+ //
+ // Public impl-only methods
+ //
+ QueryImpl(const qpid::types::Variant::Map&);
+ qpid::types::Variant::Map asMap() const;
+
+ //
+ // Methods from API handle
+ //
+ QueryImpl(QueryTarget t, const std::string& pr) : target(t), predicateCompiled(false) { parsePredicate(pr); }
+ QueryImpl(QueryTarget t, const std::string& c, const std::string& p, const std::string& pr) :
+ target(t), schemaId(SCHEMA_TYPE_DATA, p, c), predicateCompiled(false) { parsePredicate(pr); }
+ QueryImpl(QueryTarget t, const SchemaId& s, const std::string& pr) :
+ target(t), schemaId(s), predicateCompiled(false) { parsePredicate(pr); }
+ QueryImpl(const DataAddr& a) : target(QUERY_OBJECT), dataAddr(a), predicateCompiled(false) {}
+
+ QueryTarget getTarget() const { return target; }
+ const DataAddr& getDataAddr() const { return dataAddr; }
+ const SchemaId& getSchemaId() const { return schemaId; }
+ void setPredicate(const qpid::types::Variant::List& pr) { predicate = pr; }
+ const qpid::types::Variant::List& getPredicate() const { return predicate; }
+ bool matchesPredicate(const qpid::types::Variant::Map& map) const;
+
+ private:
+ QueryTarget target;
+ SchemaId schemaId;
+ DataAddr dataAddr;
+ qpid::types::Variant::List predicate;
+ mutable bool predicateCompiled;
+ mutable boost::shared_ptr<Expression> expression;
+
+ void parsePredicate(const std::string& s);
+ };
+
+ struct QueryImplAccess
+ {
+ static QueryImpl& get(Query&);
+ static const QueryImpl& get(const Query&);
+ };
+}
+
+#endif
+
diff --git a/qpid/cpp/src/qmf/Schema.cpp b/qpid/cpp/src/qmf/Schema.cpp
new file mode 100644
index 0000000000..872aad724c
--- /dev/null
+++ b/qpid/cpp/src/qmf/Schema.cpp
@@ -0,0 +1,358 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qmf/SchemaImpl.h"
+#include "qmf/PrivateImplRef.h"
+#include "qmf/exceptions.h"
+#include "qmf/SchemaTypes.h"
+#include "qmf/SchemaIdImpl.h"
+#include "qmf/SchemaPropertyImpl.h"
+#include "qmf/SchemaMethodImpl.h"
+#include "qmf/Hash.h"
+#include "qpid/log/Statement.h"
+#include "qpid/management/Buffer.h"
+#include <list>
+
+using namespace std;
+using qpid::types::Variant;
+using namespace qmf;
+
+typedef PrivateImplRef<Schema> PI;
+
+Schema::Schema(SchemaImpl* impl) { PI::ctor(*this, impl); }
+Schema::Schema(const Schema& s) : qmf::Handle<SchemaImpl>() { PI::copy(*this, s); }
+Schema::~Schema() { PI::dtor(*this); }
+Schema& Schema::operator=(const Schema& s) { return PI::assign(*this, s); }
+
+Schema::Schema(int t, const string& p, const string& c) { PI::ctor(*this, new SchemaImpl(t, p, c)); }
+const SchemaId& Schema::getSchemaId() const { return impl->getSchemaId(); }
+void Schema::finalize() { impl->finalize(); }
+bool Schema::isFinalized() const { return impl->isFinalized(); }
+void Schema::addProperty(const SchemaProperty& p) { impl->addProperty(p); }
+void Schema::addMethod(const SchemaMethod& m) { impl->addMethod(m); }
+void Schema::setDesc(const string& d) { impl->setDesc(d); }
+const string& Schema::getDesc() const { return impl->getDesc(); }
+void Schema::setDefaultSeverity(int s) { impl->setDefaultSeverity(s); }
+int Schema::getDefaultSeverity() const { return impl->getDefaultSeverity(); }
+uint32_t Schema::getPropertyCount() const { return impl->getPropertyCount(); }
+SchemaProperty Schema::getProperty(uint32_t i) const { return impl->getProperty(i); }
+uint32_t Schema::getMethodCount() const { return impl->getMethodCount(); }
+SchemaMethod Schema::getMethod(uint32_t i) const { return impl->getMethod(i); }
+
+//========================================================================================
+// Impl Method Bodies
+//========================================================================================
+
+SchemaImpl::SchemaImpl(const Variant::Map& map) : finalized(false)
+{
+ Variant::Map::const_iterator iter;
+ Variant::List::const_iterator lIter;
+
+ iter = map.find("_schema_id");
+ if (iter == map.end())
+ throw QmfException("Schema map missing _schema_id element");
+ schemaId = SchemaId(new SchemaIdImpl(iter->second.asMap()));
+
+ iter = map.find("_desc");
+ if (iter != map.end())
+ description = iter->second.asString();
+
+ iter = map.find("_default_severity");
+ if (iter != map.end())
+ defaultSeverity = int(iter->second.asUint32());
+
+ iter = map.find("_properties");
+ if (iter != map.end()) {
+ const Variant::List& props(iter->second.asList());
+ for (lIter = props.begin(); lIter != props.end(); lIter++)
+ addProperty(SchemaProperty(new SchemaPropertyImpl(lIter->asMap())));
+ }
+
+ iter = map.find("_methods");
+ if (iter != map.end()) {
+ const Variant::List& meths(iter->second.asList());
+ for (lIter = meths.begin(); lIter != meths.end(); lIter++)
+ addMethod(SchemaMethod(new SchemaMethodImpl(lIter->asMap())));
+ }
+
+ finalized = true;
+}
+
+
+Variant::Map SchemaImpl::asMap() const
+{
+ Variant::Map map;
+ Variant::List propList;
+ Variant::List methList;
+
+ checkNotFinal();
+
+ map["_schema_id"] = SchemaIdImplAccess::get(schemaId).asMap();
+ if (!description.empty())
+ map["_desc"] = description;
+ if (schemaId.getType() == SCHEMA_TYPE_EVENT)
+ map["_default_severity"] = uint32_t(defaultSeverity);
+
+ for (list<SchemaProperty>::const_iterator pIter = properties.begin(); pIter != properties.end(); pIter++)
+ propList.push_back(SchemaPropertyImplAccess::get(*pIter).asMap());
+
+ for (list<SchemaMethod>::const_iterator mIter = methods.begin(); mIter != methods.end(); mIter++)
+ methList.push_back(SchemaMethodImplAccess::get(*mIter).asMap());
+
+ map["_properties"] = propList;
+ map["_methods"] = methList;
+ return map;
+}
+
+
+SchemaImpl::SchemaImpl(qpid::management::Buffer& buffer) : finalized(false)
+{
+ int schemaType;
+ string packageName;
+ string className;
+ uint8_t hash[16];
+
+ schemaType = int(buffer.getOctet());
+ buffer.getShortString(packageName);
+ buffer.getShortString(className);
+ buffer.getBin128(hash);
+ schemaId = SchemaId(schemaType, packageName, className);
+ schemaId.setHash(qpid::types::Uuid(hash));
+
+ if (schemaType == SCHEMA_TYPE_DATA) {
+ uint16_t propCount(buffer.getShort());
+ uint16_t statCount(buffer.getShort());
+ uint16_t methCount(buffer.getShort());
+ for (uint16_t idx = 0; idx < propCount + statCount; idx++)
+ addProperty(new SchemaPropertyImpl(buffer));
+ for (uint16_t idx = 0; idx < methCount; idx++)
+ addMethod(new SchemaMethodImpl(buffer));
+ }
+
+ finalized = true;
+}
+
+
+string SchemaImpl::asV1Content(uint32_t sequence) const
+{
+#define RAW_BUF_SIZE 65536
+ char rawBuf[RAW_BUF_SIZE];
+ qpid::management::Buffer buffer(rawBuf, RAW_BUF_SIZE);
+
+ //
+ // Encode the QMFv1 Header
+ //
+ buffer.putOctet('A');
+ buffer.putOctet('M');
+ buffer.putOctet('2');
+ buffer.putOctet('s');
+ buffer.putLong(sequence);
+
+ //
+ // Encode the common schema information
+ //
+ buffer.putOctet(uint8_t(schemaId.getType()));
+ buffer.putShortString(schemaId.getPackageName());
+ buffer.putShortString(schemaId.getName());
+ buffer.putBin128(schemaId.getHash().data());
+
+ if (schemaId.getType() == SCHEMA_TYPE_DATA) {
+ buffer.putShort(properties.size());
+ buffer.putShort(0);
+ buffer.putShort(methods.size());
+ for (list<SchemaProperty>::const_iterator pIter = properties.begin(); pIter != properties.end(); pIter++)
+ SchemaPropertyImplAccess::get(*pIter).encodeV1(buffer, false, false);
+ for (list<SchemaMethod>::const_iterator mIter = methods.begin(); mIter != methods.end(); mIter++)
+ SchemaMethodImplAccess::get(*mIter).encodeV1(buffer);
+ } else {
+ buffer.putShort(properties.size());
+ for (list<SchemaProperty>::const_iterator pIter = properties.begin(); pIter != properties.end(); pIter++)
+ SchemaPropertyImplAccess::get(*pIter).encodeV1(buffer, true, false);
+ }
+
+ return string(rawBuf, buffer.getPosition());
+}
+
+
+bool SchemaImpl::isValidProperty(const std::string& k, const Variant& v) const
+{
+ for (list<SchemaProperty>::const_iterator iter = properties.begin(); iter != properties.end(); iter++)
+ if (iter->getName() == k)
+ return (isCompatibleType(iter->getType(), v.getType()));
+ return false;
+}
+
+
+bool SchemaImpl::isValidMethodInArg(const std::string& m, const std::string& k, const Variant& v) const
+{
+ for (list<SchemaMethod>::const_iterator mIter = methods.begin(); mIter != methods.end(); mIter++) {
+ if (mIter->getName() == m) {
+ uint32_t count(mIter->getArgumentCount());
+ for (uint32_t i = 0; i < count; i++) {
+ const SchemaProperty prop(mIter->getArgument(i));
+ if (prop.getName() == k) {
+ if (prop.getDirection() == DIR_IN || prop.getDirection() == DIR_IN_OUT)
+ return (isCompatibleType(prop.getType(), v.getType()));
+ else
+ return false;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+
+bool SchemaImpl::isValidMethodOutArg(const std::string& m, const std::string& k, const Variant& v) const
+{
+ for (list<SchemaMethod>::const_iterator mIter = methods.begin(); mIter != methods.end(); mIter++) {
+ if (mIter->getName() == m) {
+ uint32_t count(mIter->getArgumentCount());
+ for (uint32_t i = 0; i < count; i++) {
+ const SchemaProperty prop(mIter->getArgument(i));
+ if (prop.getName() == k) {
+ if (prop.getDirection() == DIR_OUT || prop.getDirection() == DIR_IN_OUT)
+ return (isCompatibleType(prop.getType(), v.getType()));
+ else
+ return false;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+
+void SchemaImpl::finalize()
+{
+ Hash hash;
+
+ hash.update((uint8_t) schemaId.getType());
+ hash.update(schemaId.getPackageName());
+ hash.update(schemaId.getName());
+
+ for (list<SchemaProperty>::const_iterator pIter = properties.begin(); pIter != properties.end(); pIter++)
+ SchemaPropertyImplAccess::get(*pIter).updateHash(hash);
+ for (list<SchemaMethod>::const_iterator mIter = methods.begin(); mIter != methods.end(); mIter++)
+ SchemaMethodImplAccess::get(*mIter).updateHash(hash);
+
+ schemaId.setHash(hash.asUuid());
+ QPID_LOG(debug, "Schema Finalized: " << schemaId.getPackageName() << ":" << schemaId.getName() << ":" <<
+ schemaId.getHash());
+
+ finalized = true;
+}
+
+
+SchemaProperty SchemaImpl::getProperty(uint32_t i) const
+{
+ uint32_t count = 0;
+ for (list<SchemaProperty>::const_iterator iter = properties.begin(); iter != properties.end(); iter++)
+ if (count++ == i)
+ return *iter;
+ throw IndexOutOfRange();
+}
+
+
+SchemaMethod SchemaImpl::getMethod(uint32_t i) const
+{
+ uint32_t count = 0;
+ for (list<SchemaMethod>::const_iterator iter = methods.begin(); iter != methods.end(); iter++)
+ if (count++ == i)
+ return *iter;
+ throw IndexOutOfRange();
+}
+
+void SchemaImpl::checkFinal() const
+{
+ if (finalized)
+ throw QmfException("Modification of a finalized schema is forbidden");
+}
+
+
+void SchemaImpl::checkNotFinal() const
+{
+ if (!finalized)
+ throw QmfException("Schema is not yet finalized/registered");
+}
+
+
+bool SchemaImpl::isCompatibleType(int qmfType, qpid::types::VariantType qpidType) const
+{
+ bool typeValid(false);
+
+ switch (qpidType) {
+ case qpid::types::VAR_VOID:
+ if (qmfType == SCHEMA_DATA_VOID)
+ typeValid = true;
+ break;
+ case qpid::types::VAR_BOOL:
+ if (qmfType == SCHEMA_DATA_BOOL)
+ typeValid = true;
+ break;
+ case qpid::types::VAR_UINT8:
+ case qpid::types::VAR_UINT16:
+ case qpid::types::VAR_UINT32:
+ case qpid::types::VAR_UINT64:
+ case qpid::types::VAR_INT8:
+ case qpid::types::VAR_INT16:
+ case qpid::types::VAR_INT32:
+ case qpid::types::VAR_INT64:
+ if (qmfType == SCHEMA_DATA_INT)
+ typeValid = true;
+ break;
+ case qpid::types::VAR_FLOAT:
+ case qpid::types::VAR_DOUBLE:
+ if (qmfType == SCHEMA_DATA_FLOAT)
+ typeValid = true;
+ break;
+ case qpid::types::VAR_STRING:
+ if (qmfType == SCHEMA_DATA_STRING)
+ typeValid = true;
+ break;
+ case qpid::types::VAR_MAP:
+ if (qmfType == SCHEMA_DATA_MAP)
+ typeValid = true;
+ break;
+ case qpid::types::VAR_LIST:
+ if (qmfType == SCHEMA_DATA_LIST)
+ typeValid = true;
+ break;
+ case qpid::types::VAR_UUID:
+ if (qmfType == SCHEMA_DATA_UUID)
+ typeValid = true;
+ break;
+ }
+
+ return typeValid;
+}
+
+
+SchemaImpl& SchemaImplAccess::get(Schema& item)
+{
+ return *item.impl;
+}
+
+
+const SchemaImpl& SchemaImplAccess::get(const Schema& item)
+{
+ return *item.impl;
+}
diff --git a/qpid/cpp/src/qmf/SchemaCache.cpp b/qpid/cpp/src/qmf/SchemaCache.cpp
new file mode 100644
index 0000000000..74ca4044fd
--- /dev/null
+++ b/qpid/cpp/src/qmf/SchemaCache.cpp
@@ -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.
+ *
+ */
+
+#include "qmf/SchemaCache.h"
+#include "qmf/exceptions.h"
+
+using namespace std;
+using namespace qmf;
+
+bool SchemaCache::declareSchemaId(const SchemaId& id)
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ SchemaMap::const_iterator iter = schemata.find(id);
+ if (iter == schemata.end()) {
+ schemata[id] = Schema();
+ return false;
+ }
+ return true;
+}
+
+
+void SchemaCache::declareSchema(const Schema& schema)
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ SchemaMap::const_iterator iter = schemata.find(schema.getSchemaId());
+ if (iter == schemata.end() || !iter->second.isValid()) {
+ schemata[schema.getSchemaId()] = schema;
+
+ //
+ // If there are any threads blocking in SchemaCache::getSchema waiting for
+ // this schema, unblock them all now.
+ //
+ CondMap::iterator cIter = conditions.find(schema.getSchemaId());
+ if (cIter != conditions.end())
+ cIter->second->notifyAll();
+ }
+}
+
+
+bool SchemaCache::haveSchema(const SchemaId& id) const
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ SchemaMap::const_iterator iter = schemata.find(id);
+ return iter != schemata.end() && iter->second.isValid();
+}
+
+
+const Schema& SchemaCache::getSchema(const SchemaId& id, qpid::messaging::Duration timeout) const
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ SchemaMap::const_iterator iter = schemata.find(id);
+ if (iter != schemata.end() && iter->second.isValid())
+ return iter->second;
+
+ //
+ // The desired schema is not in the cache. Assume that the caller knows this and has
+ // sent a schema request to the remote agent and now wishes to wait until the schema
+ // information arrives.
+ //
+ CondMap::iterator cIter = conditions.find(id);
+ if (cIter == conditions.end())
+ conditions[id] = boost::shared_ptr<qpid::sys::Condition>(new qpid::sys::Condition());
+
+ uint64_t milliseconds = timeout.getMilliseconds();
+ conditions[id]->wait(lock, qpid::sys::AbsTime(qpid::sys::now(),
+ qpid::sys::Duration(milliseconds * qpid::sys::TIME_MSEC)));
+ iter = schemata.find(id);
+ if (iter != schemata.end() && iter->second.isValid())
+ return iter->second;
+
+ throw QmfException("Schema lookup timed out");
+}
+
diff --git a/qpid/cpp/src/qmf/SchemaCache.h b/qpid/cpp/src/qmf/SchemaCache.h
new file mode 100644
index 0000000000..a1f104233f
--- /dev/null
+++ b/qpid/cpp/src/qmf/SchemaCache.h
@@ -0,0 +1,56 @@
+#ifndef QMF_SCHEMA_CACHE_H
+#define QMF_SCHEMA_CACHE_H
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qmf/SchemaIdImpl.h"
+#include "qmf/Schema.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/Condition.h"
+#include "qpid/messaging/Duration.h"
+#include <string>
+#include <map>
+#include <boost/shared_ptr.hpp>
+
+namespace qmf {
+
+ class SchemaCache {
+ public:
+ SchemaCache() {}
+ ~SchemaCache() {}
+
+ bool declareSchemaId(const SchemaId&);
+ void declareSchema(const Schema&);
+ bool haveSchema(const SchemaId&) const;
+ const Schema& getSchema(const SchemaId&, qpid::messaging::Duration) const;
+
+ private:
+ mutable qpid::sys::Mutex lock;
+ typedef std::map<SchemaId, Schema, SchemaIdCompare> SchemaMap;
+ typedef std::map<SchemaId, boost::shared_ptr<qpid::sys::Condition>, SchemaIdCompare> CondMap;
+ SchemaMap schemata;
+ mutable CondMap conditions;
+ };
+
+}
+
+#endif
+
diff --git a/qpid/cpp/src/qmf/SchemaId.cpp b/qpid/cpp/src/qmf/SchemaId.cpp
new file mode 100644
index 0000000000..25fa9915ae
--- /dev/null
+++ b/qpid/cpp/src/qmf/SchemaId.cpp
@@ -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.
+ *
+ */
+
+#include "qmf/SchemaIdImpl.h"
+#include "qmf/PrivateImplRef.h"
+
+using namespace std;
+using namespace qmf;
+using qpid::types::Variant;
+
+typedef PrivateImplRef<SchemaId> PI;
+
+SchemaId::SchemaId(SchemaIdImpl* impl) { PI::ctor(*this, impl); }
+SchemaId::SchemaId(const SchemaId& s) : qmf::Handle<SchemaIdImpl>() { PI::copy(*this, s); }
+SchemaId::~SchemaId() { PI::dtor(*this); }
+SchemaId& SchemaId::operator=(const SchemaId& s) { return PI::assign(*this, s); }
+
+SchemaId::SchemaId(int t, const string& p, const string& n) { PI::ctor(*this, new SchemaIdImpl(t, p, n)); }
+void SchemaId::setHash(const qpid::types::Uuid& h) { impl->setHash(h); }
+int SchemaId::getType() const { return impl->getType(); }
+const string& SchemaId::getPackageName() const { return impl->getPackageName(); }
+const string& SchemaId::getName() const { return impl->getName(); }
+const qpid::types::Uuid& SchemaId::getHash() const { return impl->getHash(); }
+
+
+SchemaIdImpl::SchemaIdImpl(const Variant::Map& map)
+{
+ Variant::Map::const_iterator iter;
+
+ iter = map.find("_package_name");
+ if (iter != map.end())
+ package = iter->second.asString();
+
+ iter = map.find("_class_name");
+ if (iter != map.end())
+ name = iter->second.asString();
+
+ iter = map.find("_type");
+ if (iter != map.end()) {
+ const string& stype = iter->second.asString();
+ if (stype == "_data")
+ sType = SCHEMA_TYPE_DATA;
+ else if (stype == "_event")
+ sType = SCHEMA_TYPE_EVENT;
+ }
+
+ iter = map.find("_hash");
+ if (iter != map.end())
+ hash = iter->second.asUuid();
+}
+
+
+Variant::Map SchemaIdImpl::asMap() const
+{
+ Variant::Map result;
+
+ result["_package_name"] = package;
+ result["_class_name"] = name;
+ if (sType == SCHEMA_TYPE_DATA)
+ result["_type"] = "_data";
+ else
+ result["_type"] = "_event";
+ if (!hash.isNull())
+ result["_hash"] = hash;
+ return result;
+}
+
+
+SchemaIdImpl& SchemaIdImplAccess::get(SchemaId& item)
+{
+ return *item.impl;
+}
+
+
+const SchemaIdImpl& SchemaIdImplAccess::get(const SchemaId& item)
+{
+ return *item.impl;
+}
diff --git a/qpid/cpp/src/qmf/SchemaIdImpl.h b/qpid/cpp/src/qmf/SchemaIdImpl.h
new file mode 100644
index 0000000000..ae1a3d8d3b
--- /dev/null
+++ b/qpid/cpp/src/qmf/SchemaIdImpl.h
@@ -0,0 +1,83 @@
+#ifndef _QMF_SCHEMA_ID_IMPL_H_
+#define _QMF_SCHEMA_ID_IMPL_H_
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/RefCounted.h"
+#include "qmf/SchemaId.h"
+#include "qpid/types/Variant.h"
+#include "qpid/types/Uuid.h"
+#include <string>
+
+namespace qmf {
+ class SchemaIdImpl : public virtual qpid::RefCounted {
+ public:
+ //
+ // Public impl-only methods
+ //
+ SchemaIdImpl(const qpid::types::Variant::Map&);
+ qpid::types::Variant::Map asMap() const;
+
+ //
+ // Methods from API handle
+ //
+ SchemaIdImpl(int t, const std::string& p, const std::string& n) : sType(t), package(p), name(n) {}
+ void setHash(const qpid::types::Uuid& h) { hash = h; }
+ int getType() const { return sType; }
+ const std::string& getPackageName() const { return package; }
+ const std::string& getName() const { return name; }
+ const qpid::types::Uuid& getHash() const { return hash; }
+
+ private:
+ int sType;
+ std::string package;
+ std::string name;
+ qpid::types::Uuid hash;
+ };
+
+ struct SchemaIdImplAccess
+ {
+ static SchemaIdImpl& get(SchemaId&);
+ static const SchemaIdImpl& get(const SchemaId&);
+ };
+
+ struct SchemaIdCompare {
+ bool operator() (const SchemaId& lhs, const SchemaId& rhs) const
+ {
+ if (lhs.getName() != rhs.getName())
+ return lhs.getName() < rhs.getName();
+ if (lhs.getPackageName() != rhs.getPackageName())
+ return lhs.getPackageName() < rhs.getPackageName();
+ return lhs.getHash() < rhs.getHash();
+ }
+ };
+
+ struct SchemaIdCompareNoHash {
+ bool operator() (const SchemaId& lhs, const SchemaId& rhs) const
+ {
+ if (lhs.getName() != rhs.getName())
+ return lhs.getName() < rhs.getName();
+ return lhs.getPackageName() < rhs.getPackageName();
+ }
+ };
+}
+
+#endif
diff --git a/qpid/cpp/src/qmf/SchemaImpl.h b/qpid/cpp/src/qmf/SchemaImpl.h
new file mode 100644
index 0000000000..1c88f87808
--- /dev/null
+++ b/qpid/cpp/src/qmf/SchemaImpl.h
@@ -0,0 +1,95 @@
+#ifndef _QMF_SCHEMAIMPL_H_
+#define _QMF_SCHEMAIMPL_H_
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/RefCounted.h"
+#include "qmf/PrivateImplRef.h"
+#include "qmf/exceptions.h"
+#include "qmf/SchemaTypes.h"
+#include "qmf/SchemaId.h"
+#include "qmf/Schema.h"
+#include "qmf/SchemaProperty.h"
+#include "qmf/SchemaMethod.h"
+#include <list>
+
+namespace qpid {
+namespace management {
+ class Buffer;
+}}
+
+namespace qmf {
+ class SchemaImpl : public virtual qpid::RefCounted {
+ public:
+ //
+ // Impl-only public methods
+ //
+ SchemaImpl(const qpid::types::Variant::Map& m);
+ qpid::types::Variant::Map asMap() const;
+ SchemaImpl(qpid::management::Buffer& v1Buffer);
+ std::string asV1Content(uint32_t sequence) const;
+ bool isValidProperty(const std::string& k, const qpid::types::Variant& v) const;
+ bool isValidMethodInArg(const std::string& m, const std::string& k, const qpid::types::Variant& v) const;
+ bool isValidMethodOutArg(const std::string& m, const std::string& k, const qpid::types::Variant& v) const;
+
+ //
+ // Methods from API handle
+ //
+ SchemaImpl(int t, const std::string& p, const std::string& c) : schemaId(t, p, c), finalized(false) {}
+ const SchemaId& getSchemaId() const { checkNotFinal(); return schemaId; }
+
+ void finalize();
+ bool isFinalized() const { return finalized; }
+ void addProperty(const SchemaProperty& p) { checkFinal(); properties.push_back(p); }
+ void addMethod(const SchemaMethod& m) { checkFinal(); methods.push_back(m); }
+
+ void setDesc(const std::string& d) { description = d; }
+ const std::string& getDesc() const { return description; }
+
+ void setDefaultSeverity(int s) { checkFinal(); defaultSeverity = s; }
+ int getDefaultSeverity() const { return defaultSeverity; }
+
+ uint32_t getPropertyCount() const { return properties.size(); }
+ SchemaProperty getProperty(uint32_t i) const;
+
+ uint32_t getMethodCount() const { return methods.size(); }
+ SchemaMethod getMethod(uint32_t i) const;
+ private:
+ SchemaId schemaId;
+ int defaultSeverity;
+ std::string description;
+ bool finalized;
+ std::list<SchemaProperty> properties;
+ std::list<SchemaMethod> methods;
+
+ void checkFinal() const;
+ void checkNotFinal() const;
+ bool isCompatibleType(int qmfType, qpid::types::VariantType qpidType) const;
+ };
+
+ struct SchemaImplAccess
+ {
+ static SchemaImpl& get(Schema&);
+ static const SchemaImpl& get(const Schema&);
+ };
+}
+
+#endif
diff --git a/qpid/cpp/src/qmf/SchemaMethod.cpp b/qpid/cpp/src/qmf/SchemaMethod.cpp
new file mode 100644
index 0000000000..e267878238
--- /dev/null
+++ b/qpid/cpp/src/qmf/SchemaMethod.cpp
@@ -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.
+ *
+ */
+
+#include "qmf/SchemaMethodImpl.h"
+#include "qmf/PrivateImplRef.h"
+#include "qmf/exceptions.h"
+#include "qmf/Hash.h"
+#include "qpid/messaging/AddressParser.h"
+#include "qpid/management/Buffer.h"
+
+using namespace std;
+using qpid::types::Variant;
+using namespace qmf;
+
+typedef PrivateImplRef<SchemaMethod> PI;
+
+SchemaMethod::SchemaMethod(SchemaMethodImpl* impl) { PI::ctor(*this, impl); }
+SchemaMethod::SchemaMethod(const SchemaMethod& s) : qmf::Handle<SchemaMethodImpl>() { PI::copy(*this, s); }
+SchemaMethod::~SchemaMethod() { PI::dtor(*this); }
+SchemaMethod& SchemaMethod::operator=(const SchemaMethod& s) { return PI::assign(*this, s); }
+
+SchemaMethod::SchemaMethod(const string& n, const string& o) { PI::ctor(*this, new SchemaMethodImpl(n, o)); }
+void SchemaMethod::setDesc(const string& d) { impl->setDesc(d); }
+void SchemaMethod::addArgument(const SchemaProperty& p) { impl->addArgument(p); }
+const string& SchemaMethod::getName() const { return impl->getName(); }
+const string& SchemaMethod::getDesc() const { return impl->getDesc(); }
+uint32_t SchemaMethod::getArgumentCount() const { return impl->getArgumentCount(); }
+SchemaProperty SchemaMethod::getArgument(uint32_t i) const { return impl->getArgument(i); }
+
+//========================================================================================
+// Impl Method Bodies
+//========================================================================================
+
+SchemaMethodImpl::SchemaMethodImpl(const string& n, const string& options) : name(n)
+{
+ if (!options.empty()) {
+ qpid::messaging::AddressParser parser = qpid::messaging::AddressParser(options);
+ Variant::Map optMap;
+ Variant::Map::iterator iter;
+
+ parser.parseMap(optMap);
+ iter = optMap.find("desc");
+ if (iter != optMap.end()) {
+ desc = iter->second.asString();
+ optMap.erase(iter);
+ }
+
+ if (!optMap.empty())
+ throw QmfException("Unrecognized option: " + optMap.begin()->first);
+ }
+}
+
+
+SchemaMethodImpl::SchemaMethodImpl(const qpid::types::Variant::Map& map)
+{
+ Variant::Map::const_iterator iter;
+ Variant::List::const_iterator lIter;
+
+ iter = map.find("_name");
+ if (iter == map.end())
+ throw QmfException("SchemaMethod without a _name element");
+ name = iter->second.asString();
+
+ iter = map.find("_desc");
+ if (iter != map.end())
+ desc = iter->second.asString();
+
+ iter = map.find("_arguments");
+ if (iter != map.end()) {
+ const Variant::List& argList(iter->second.asList());
+ for (lIter = argList.begin(); lIter != argList.end(); lIter++)
+ addArgument(SchemaProperty(new SchemaPropertyImpl(lIter->asMap())));
+ }
+}
+
+
+Variant::Map SchemaMethodImpl::asMap() const
+{
+ Variant::Map map;
+ Variant::List argList;
+
+ map["_name"] = name;
+
+ if (!desc.empty())
+ map["_desc"] = desc;
+
+ for (list<SchemaProperty>::const_iterator iter = arguments.begin(); iter != arguments.end(); iter++)
+ argList.push_back(SchemaPropertyImplAccess::get(*iter).asMap());
+ map["_arguments"] = argList;
+
+ return map;
+}
+
+
+SchemaMethodImpl::SchemaMethodImpl(qpid::management::Buffer& buffer)
+{
+ Variant::Map::const_iterator iter;
+ Variant::Map argMap;
+
+ buffer.getMap(argMap);
+
+ iter = argMap.find("name");
+ if (iter == argMap.end())
+ throw QmfException("Received V1 Method without a name");
+ name = iter->second.asString();
+
+ iter = argMap.find("desc");
+ if (iter != argMap.end())
+ desc = iter->second.asString();
+
+ iter = argMap.find("argCount");
+ if (iter == argMap.end())
+ throw QmfException("Received V1 Method without argCount");
+
+ int64_t count = iter->second.asInt64();
+ for (int idx = 0; idx < count; idx++) {
+ SchemaProperty arg(new SchemaPropertyImpl(buffer));
+ addArgument(arg);
+ }
+}
+
+
+SchemaProperty SchemaMethodImpl::getArgument(uint32_t i) const
+{
+ uint32_t count = 0;
+ for (list<SchemaProperty>::const_iterator iter = arguments.begin(); iter != arguments.end(); iter++)
+ if (count++ == i)
+ return *iter;
+
+ throw IndexOutOfRange();
+}
+
+
+void SchemaMethodImpl::updateHash(Hash& hash) const
+{
+ hash.update(name);
+ hash.update(desc);
+ for (list<SchemaProperty>::const_iterator iter = arguments.begin(); iter != arguments.end(); iter++)
+ SchemaPropertyImplAccess::get(*iter).updateHash(hash);
+}
+
+
+void SchemaMethodImpl::encodeV1(qpid::management::Buffer& buffer) const
+{
+ Variant::Map map;
+
+ map["name"] = name;
+ map["argCount"] = (uint64_t) arguments.size();
+ if (!desc.empty())
+ map["desc"] = desc;
+
+ buffer.putMap(map);
+
+ for (list<SchemaProperty>::const_iterator iter = arguments.begin(); iter != arguments.end(); iter++)
+ SchemaPropertyImplAccess::get(*iter).encodeV1(buffer, true, true);
+}
+
+
+SchemaMethodImpl& SchemaMethodImplAccess::get(SchemaMethod& item)
+{
+ return *item.impl;
+}
+
+
+const SchemaMethodImpl& SchemaMethodImplAccess::get(const SchemaMethod& item)
+{
+ return *item.impl;
+}
diff --git a/qpid/cpp/src/qmf/SchemaMethodImpl.h b/qpid/cpp/src/qmf/SchemaMethodImpl.h
new file mode 100644
index 0000000000..930d48509c
--- /dev/null
+++ b/qpid/cpp/src/qmf/SchemaMethodImpl.h
@@ -0,0 +1,75 @@
+#ifndef _QMF_SCHEMA_METHOD_IMPL_H_
+#define _QMF_SCHEMA_METHOD_IMPL_H_
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/RefCounted.h"
+#include "qmf/SchemaTypes.h"
+#include "qmf/SchemaMethod.h"
+#include "qmf/SchemaPropertyImpl.h"
+#include "qpid/management/Buffer.h"
+#include <list>
+#include <string>
+
+namespace qpid {
+namespace management {
+ class Buffer;
+}}
+
+namespace qmf {
+ class Hash;
+ class SchemaMethodImpl : public virtual qpid::RefCounted {
+ public:
+ //
+ // Public impl-only methods
+ //
+ SchemaMethodImpl(const qpid::types::Variant::Map& m);
+ SchemaMethodImpl(qpid::management::Buffer& v1Buffer);
+ qpid::types::Variant::Map asMap() const;
+ void updateHash(Hash&) const;
+ void encodeV1(qpid::management::Buffer&) const;
+
+ //
+ // Methods from API handle
+ //
+ SchemaMethodImpl(const std::string& n, const std::string& options);
+
+ void setDesc(const std::string& d) { desc = d; }
+ void addArgument(const SchemaProperty& p) { arguments.push_back(p); }
+ const std::string& getName() const { return name; }
+ const std::string& getDesc() const { return desc; }
+ uint32_t getArgumentCount() const { return arguments.size(); }
+ SchemaProperty getArgument(uint32_t i) const;
+
+ private:
+ std::string name;
+ std::string desc;
+ std::list<SchemaProperty> arguments;
+ };
+
+ struct SchemaMethodImplAccess
+ {
+ static SchemaMethodImpl& get(SchemaMethod&);
+ static const SchemaMethodImpl& get(const SchemaMethod&);
+ };
+}
+
+#endif
diff --git a/qpid/cpp/src/qmf/SchemaProperty.cpp b/qpid/cpp/src/qmf/SchemaProperty.cpp
new file mode 100644
index 0000000000..106127261b
--- /dev/null
+++ b/qpid/cpp/src/qmf/SchemaProperty.cpp
@@ -0,0 +1,434 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qmf/SchemaPropertyImpl.h"
+#include "qmf/PrivateImplRef.h"
+#include "qmf/exceptions.h"
+#include "qmf/SchemaTypes.h"
+#include "qmf/SchemaProperty.h"
+#include "qmf/Hash.h"
+#include "qpid/messaging/AddressParser.h"
+#include <list>
+#include <iostream>
+
+using namespace std;
+using qpid::types::Variant;
+using namespace qmf;
+
+typedef PrivateImplRef<SchemaProperty> PI;
+
+SchemaProperty::SchemaProperty(SchemaPropertyImpl* impl) { PI::ctor(*this, impl); }
+SchemaProperty::SchemaProperty(const SchemaProperty& s) : qmf::Handle<SchemaPropertyImpl>() { PI::copy(*this, s); }
+SchemaProperty::~SchemaProperty() { PI::dtor(*this); }
+SchemaProperty& SchemaProperty::operator=(const SchemaProperty& s) { return PI::assign(*this, s); }
+
+SchemaProperty::SchemaProperty(const string& n, int t, const string& o) { PI::ctor(*this, new SchemaPropertyImpl(n, t, o)); }
+
+void SchemaProperty::setAccess(int a) { impl->setAccess(a); }
+void SchemaProperty::setIndex(bool i) { impl->setIndex(i); }
+void SchemaProperty::setOptional(bool o) { impl->setOptional(o); }
+void SchemaProperty::setUnit(const string& u) { impl->setUnit(u); }
+void SchemaProperty::setDesc(const string& d) { impl->setDesc(d); }
+void SchemaProperty::setSubtype(const string& s) { impl->setSubtype(s); }
+void SchemaProperty::setDirection(int d) { impl->setDirection(d); }
+
+const string& SchemaProperty::getName() const { return impl->getName(); }
+int SchemaProperty::getType() const { return impl->getType(); }
+int SchemaProperty::getAccess() const { return impl->getAccess(); }
+bool SchemaProperty::isIndex() const { return impl->isIndex(); }
+bool SchemaProperty::isOptional() const { return impl->isOptional(); }
+const string& SchemaProperty::getUnit() const { return impl->getUnit(); }
+const string& SchemaProperty::getDesc() const { return impl->getDesc(); }
+const string& SchemaProperty::getSubtype() const { return impl->getSubtype(); }
+int SchemaProperty::getDirection() const { return impl->getDirection(); }
+
+//========================================================================================
+// Impl Method Bodies
+//========================================================================================
+
+SchemaPropertyImpl::SchemaPropertyImpl(const string& n, int t, const string options) :
+ name(n), dataType(t), access(ACCESS_READ_ONLY), index(false),
+ optional(false), direction(DIR_IN)
+{
+ if (!options.empty()) {
+ qpid::messaging::AddressParser parser = qpid::messaging::AddressParser(options);
+ Variant::Map optMap;
+ Variant::Map::iterator iter;
+
+ parser.parseMap(optMap);
+
+ iter = optMap.find("access");
+ if (iter != optMap.end()) {
+ const string& v(iter->second.asString());
+ if (v == "RC") access = ACCESS_READ_CREATE;
+ else if (v == "RO") access = ACCESS_READ_ONLY;
+ else if (v == "RW") access = ACCESS_READ_WRITE;
+ else
+ throw QmfException("Invalid value for 'access' option. Expected RC, RO, or RW");
+ optMap.erase(iter);
+ }
+
+ iter = optMap.find("index");
+ if (iter != optMap.end()) {
+ index = iter->second.asBool();
+ optMap.erase(iter);
+ }
+
+ iter = optMap.find("optional");
+ if (iter != optMap.end()) {
+ optional = iter->second.asBool();
+ optMap.erase(iter);
+ }
+
+ iter = optMap.find("unit");
+ if (iter != optMap.end()) {
+ unit = iter->second.asString();
+ optMap.erase(iter);
+ }
+
+ iter = optMap.find("desc");
+ if (iter != optMap.end()) {
+ desc = iter->second.asString();
+ optMap.erase(iter);
+ }
+
+ iter = optMap.find("subtype");
+ if (iter != optMap.end()) {
+ subtype = iter->second.asString();
+ optMap.erase(iter);
+ }
+
+ iter = optMap.find("dir");
+ if (iter != optMap.end()) {
+ const string& v(iter->second.asString());
+ if (v == "IN") direction = DIR_IN;
+ else if (v == "OUT") direction = DIR_OUT;
+ else if (v == "INOUT") direction = DIR_IN_OUT;
+ else
+ throw QmfException("Invalid value for 'dir' option. Expected IN, OUT, or INOUT");
+ optMap.erase(iter);
+ }
+
+ if (!optMap.empty())
+ throw QmfException("Unexpected option: " + optMap.begin()->first);
+ }
+}
+
+
+SchemaPropertyImpl::SchemaPropertyImpl(const Variant::Map& map) :
+ access(ACCESS_READ_ONLY), index(false), optional(false), direction(DIR_IN)
+{
+ Variant::Map::const_iterator iter;
+
+ iter = map.find("_name");
+ if (iter == map.end())
+ throw QmfException("SchemaProperty without a _name element");
+ name = iter->second.asString();
+
+ iter = map.find("_type");
+ if (iter == map.end())
+ throw QmfException("SchemaProperty without a _type element");
+ const string& ts(iter->second.asString());
+ if (ts == "TYPE_VOID") dataType = SCHEMA_DATA_VOID;
+ else if (ts == "TYPE_BOOL") dataType = SCHEMA_DATA_BOOL;
+ else if (ts == "TYPE_INT") dataType = SCHEMA_DATA_INT;
+ else if (ts == "TYPE_FLOAT") dataType = SCHEMA_DATA_FLOAT;
+ else if (ts == "TYPE_STRING") dataType = SCHEMA_DATA_STRING;
+ else if (ts == "TYPE_MAP") dataType = SCHEMA_DATA_MAP;
+ else if (ts == "TYPE_LIST") dataType = SCHEMA_DATA_LIST;
+ else if (ts == "TYPE_UUID") dataType = SCHEMA_DATA_UUID;
+ else
+ throw QmfException("SchemaProperty with an invalid type code: " + ts);
+
+ iter = map.find("_access");
+ if (iter != map.end()) {
+ const string& as(iter->second.asString());
+ if (as == "RO") access = ACCESS_READ_ONLY;
+ else if (as == "RC") access = ACCESS_READ_CREATE;
+ else if (as == "RW") access = ACCESS_READ_WRITE;
+ else
+ throw QmfException("SchemaProperty with an invalid access code: " + as);
+ }
+
+ iter = map.find("_unit");
+ if (iter != map.end())
+ unit = iter->second.asString();
+
+ iter = map.find("_dir");
+ if (iter != map.end()) {
+ const string& ds(iter->second.asString());
+ if (ds == "I") direction = DIR_IN;
+ else if (ds == "O") direction = DIR_OUT;
+ else if (ds == "IO") direction = DIR_IN_OUT;
+ else
+ throw QmfException("SchemaProperty with an invalid direction code: " + ds);
+ }
+
+ iter = map.find("_desc");
+ if (iter != map.end())
+ desc = iter->second.asString();
+
+ iter = map.find("_index");
+ if (iter != map.end())
+ index = iter->second.asBool();
+
+ iter = map.find("_subtype");
+ if (iter != map.end())
+ subtype = iter->second.asString();
+}
+
+
+Variant::Map SchemaPropertyImpl::asMap() const
+{
+ Variant::Map map;
+ string ts;
+
+ map["_name"] = name;
+
+ switch (dataType) {
+ case SCHEMA_DATA_VOID: ts = "TYPE_VOID"; break;
+ case SCHEMA_DATA_BOOL: ts = "TYPE_BOOL"; break;
+ case SCHEMA_DATA_INT: ts = "TYPE_INT"; break;
+ case SCHEMA_DATA_FLOAT: ts = "TYPE_FLOAT"; break;
+ case SCHEMA_DATA_STRING: ts = "TYPE_STRING"; break;
+ case SCHEMA_DATA_MAP: ts = "TYPE_MAP"; break;
+ case SCHEMA_DATA_LIST: ts = "TYPE_LIST"; break;
+ case SCHEMA_DATA_UUID: ts = "TYPE_UUID"; break;
+ }
+ map["_type"] = ts;
+
+ switch (access) {
+ case ACCESS_READ_ONLY: ts = "RO"; break;
+ case ACCESS_READ_CREATE: ts = "RC"; break;
+ case ACCESS_READ_WRITE: ts = "RW"; break;
+ }
+ map["_access"] = ts;
+
+ if (!unit.empty())
+ map["_unit"] = unit;
+
+ switch (direction) {
+ case DIR_IN: ts = "I"; break;
+ case DIR_OUT: ts = "O"; break;
+ case DIR_IN_OUT: ts = "IO"; break;
+ }
+ map["_dir"] = ts;
+
+ if (!desc.empty())
+ map["_desc"] = desc;
+
+ if (index)
+ map["_index"] = true;
+
+ if (!subtype.empty())
+ map["_subtype"] = subtype;
+
+ return map;
+}
+
+
+SchemaPropertyImpl::SchemaPropertyImpl(qpid::management::Buffer& buffer) :
+ access(ACCESS_READ_ONLY), index(false), optional(false), direction(DIR_IN)
+{
+ Variant::Map::const_iterator iter;
+ Variant::Map pmap;
+
+ buffer.getMap(pmap);
+ iter = pmap.find("name");
+ if (iter == pmap.end())
+ throw QmfException("Received V1 Schema property without a name");
+ name = iter->second.asString();
+
+ iter = pmap.find("type");
+ if (iter == pmap.end())
+ throw QmfException("Received V1 Schema property without a type");
+ fromV1TypeCode(iter->second.asInt8());
+
+ iter = pmap.find("unit");
+ if (iter != pmap.end())
+ unit = iter->second.asString();
+
+ iter = pmap.find("desc");
+ if (iter != pmap.end())
+ desc = iter->second.asString();
+
+ iter = pmap.find("access");
+ if (iter != pmap.end()) {
+ int8_t val = iter->second.asInt8();
+ if (val < 1 || val > 3)
+ throw QmfException("Received V1 Schema property with invalid 'access' code");
+ access = val;
+ }
+
+ iter = pmap.find("index");
+ if (iter != pmap.end())
+ index = iter->second.asInt64() != 0;
+
+ iter = pmap.find("optional");
+ if (iter != pmap.end())
+ optional = iter->second.asInt64() != 0;
+
+ iter = pmap.find("dir");
+ if (iter != pmap.end()) {
+ string dirStr(iter->second.asString());
+ if (dirStr == "I") direction = DIR_IN;
+ else if (dirStr == "O") direction = DIR_OUT;
+ else if (dirStr == "IO") direction = DIR_IN_OUT;
+ else
+ throw QmfException("Received V1 Schema property with invalid 'dir' code");
+ }
+}
+
+
+void SchemaPropertyImpl::updateHash(Hash& hash) const
+{
+ hash.update(name);
+ hash.update((uint8_t) dataType);
+ hash.update(subtype);
+ hash.update((uint8_t) access);
+ hash.update(index);
+ hash.update(optional);
+ hash.update(unit);
+ hash.update(desc);
+ hash.update((uint8_t) direction);
+}
+
+
+void SchemaPropertyImpl::encodeV1(qpid::management::Buffer& buffer, bool isArg, bool isMethodArg) const
+{
+ Variant::Map pmap;
+
+ pmap["name"] = name;
+ pmap["type"] = v1TypeCode();
+ if (!unit.empty())
+ pmap["unit"] = unit;
+ if (!desc.empty())
+ pmap["desc"] = desc;
+ if (!isArg) {
+ pmap["access"] = access;
+ pmap["index"] = index ? 1 : 0;
+ pmap["optional"] = optional ? 1 : 0;
+ } else {
+ if (isMethodArg) {
+ string dirStr;
+ switch (direction) {
+ case DIR_IN : dirStr = "I"; break;
+ case DIR_OUT : dirStr = "O"; break;
+ case DIR_IN_OUT : dirStr = "IO"; break;
+ }
+ pmap["dir"] = dirStr;
+ }
+ }
+
+ buffer.putMap(pmap);
+}
+
+
+uint8_t SchemaPropertyImpl::v1TypeCode() const
+{
+ switch (dataType) {
+ case SCHEMA_DATA_VOID: return 1;
+ case SCHEMA_DATA_BOOL: return 11;
+ case SCHEMA_DATA_INT:
+ if (subtype == "timestamp") return 8;
+ if (subtype == "duration") return 9;
+ return 19;
+ case SCHEMA_DATA_FLOAT: return 13;
+ case SCHEMA_DATA_STRING: return 7;
+ case SCHEMA_DATA_LIST: return 21;
+ case SCHEMA_DATA_UUID: return 14;
+ case SCHEMA_DATA_MAP:
+ if (subtype == "reference") return 10;
+ if (subtype == "data") return 20;
+ return 15;
+ }
+
+ return 1;
+}
+
+void SchemaPropertyImpl::fromV1TypeCode(int8_t code)
+{
+ switch (code) {
+ case 1: // U8
+ case 2: // U16
+ case 3: // U32
+ case 4: // U64
+ dataType = SCHEMA_DATA_INT;
+ break;
+ case 6: // SSTR
+ case 7: // LSTR
+ dataType = SCHEMA_DATA_STRING;
+ break;
+ case 8: // ABSTIME
+ dataType = SCHEMA_DATA_INT;
+ subtype = "timestamp";
+ break;
+ case 9: // DELTATIME
+ dataType = SCHEMA_DATA_INT;
+ subtype = "duration";
+ break;
+ case 10: // REF
+ dataType = SCHEMA_DATA_MAP;
+ subtype = "reference";
+ break;
+ case 11: // BOOL
+ dataType = SCHEMA_DATA_BOOL;
+ break;
+ case 12: // FLOAT
+ case 13: // DOUBLE
+ dataType = SCHEMA_DATA_FLOAT;
+ break;
+ case 14: // UUID
+ dataType = SCHEMA_DATA_UUID;
+ break;
+ case 15: // FTABLE
+ dataType = SCHEMA_DATA_MAP;
+ break;
+ case 16: // S8
+ case 17: // S16
+ case 18: // S32
+ case 19: // S64
+ dataType = SCHEMA_DATA_INT;
+ break;
+ case 20: // OBJECT
+ dataType = SCHEMA_DATA_MAP;
+ subtype = "data";
+ break;
+ case 21: // LIST
+ case 22: // ARRAY
+ dataType = SCHEMA_DATA_LIST;
+ break;
+ default:
+ throw QmfException("Received V1 schema with an unknown data type");
+ }
+}
+
+
+SchemaPropertyImpl& SchemaPropertyImplAccess::get(SchemaProperty& item)
+{
+ return *item.impl;
+}
+
+
+const SchemaPropertyImpl& SchemaPropertyImplAccess::get(const SchemaProperty& item)
+{
+ return *item.impl;
+}
diff --git a/qpid/cpp/src/qmf/SchemaPropertyImpl.h b/qpid/cpp/src/qmf/SchemaPropertyImpl.h
new file mode 100644
index 0000000000..cdfc29066f
--- /dev/null
+++ b/qpid/cpp/src/qmf/SchemaPropertyImpl.h
@@ -0,0 +1,93 @@
+#ifndef _QMF_SCHEMA_PROPERTY_IMPL_H_
+#define _QMF_SCHEMA_PROPERTY_IMPL_H_
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/RefCounted.h"
+#include "qmf/SchemaTypes.h"
+#include "qmf/SchemaProperty.h"
+#include "qpid/types/Variant.h"
+#include "qpid/management/Buffer.h"
+
+namespace qpid {
+namespace management {
+ class Buffer;
+}}
+
+namespace qmf {
+ class Hash;
+ class SchemaPropertyImpl : public virtual qpid::RefCounted {
+ public:
+ //
+ // Public impl-only methods
+ //
+ SchemaPropertyImpl(const qpid::types::Variant::Map& m);
+ SchemaPropertyImpl(qpid::management::Buffer& v1Buffer);
+ qpid::types::Variant::Map asMap() const;
+ void updateHash(Hash&) const;
+ void encodeV1(qpid::management::Buffer&, bool isArg, bool isMethodArg) const;
+
+ //
+ // Methods from API handle
+ //
+ SchemaPropertyImpl(const std::string& n, int t, const std::string o);
+ void setAccess(int a) { access = a; }
+ void setIndex(bool i) { index = i; }
+ void setOptional(bool o) { optional = o; }
+ void setUnit(const std::string& u) { unit = u; }
+ void setDesc(const std::string& d) { desc = d; }
+ void setSubtype(const std::string& s) { subtype = s; }
+ void setDirection(int d) { direction = d; }
+
+ const std::string& getName() const { return name; }
+ int getType() const { return dataType; }
+ int getAccess() const { return access; }
+ bool isIndex() const { return index; }
+ bool isOptional() const { return optional; }
+ const std::string& getUnit() const { return unit; }
+ const std::string& getDesc() const { return desc; }
+ const std::string& getSubtype() const { return subtype; }
+ int getDirection() const { return direction; }
+
+ private:
+ std::string name;
+ int dataType;
+ std::string subtype;
+ int access;
+ bool index;
+ bool optional;
+ std::string unit;
+ std::string desc;
+ int direction;
+
+ uint8_t v1TypeCode() const;
+ void fromV1TypeCode(int8_t);
+ };
+
+ struct SchemaPropertyImplAccess
+ {
+ static SchemaPropertyImpl& get(SchemaProperty&);
+ static const SchemaPropertyImpl& get(const SchemaProperty&);
+ };
+}
+
+#endif
diff --git a/qpid/cpp/src/qmf/Subscription.cpp b/qpid/cpp/src/qmf/Subscription.cpp
new file mode 100644
index 0000000000..73afc8c79d
--- /dev/null
+++ b/qpid/cpp/src/qmf/Subscription.cpp
@@ -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.
+ *
+ */
+
+#include "qmf/PrivateImplRef.h"
+#include "qmf/exceptions.h"
+#include "qmf/SubscriptionImpl.h"
+#include "qmf/DataImpl.h"
+
+using namespace std;
+using namespace qmf;
+using qpid::types::Variant;
+
+typedef PrivateImplRef<Subscription> PI;
+
+Subscription::Subscription(SubscriptionImpl* impl) { PI::ctor(*this, impl); }
+Subscription::Subscription(const Subscription& s) : qmf::Handle<SubscriptionImpl>() { PI::copy(*this, s); }
+Subscription::~Subscription() { PI::dtor(*this); }
+Subscription& Subscription::operator=(const Subscription& s) { return PI::assign(*this, s); }
+
+void Subscription::cancel() { impl->cancel(); }
+bool Subscription::isActive() const { return impl->isActive(); }
+void Subscription::lock() { impl->lock(); }
+void Subscription::unlock() { impl->unlock(); }
+uint32_t Subscription::getDataCount() const { return impl->getDataCount(); }
+Data Subscription::getData(uint32_t i) const { return impl->getData(i); }
+
+
+void SubscriptionImpl::cancel()
+{
+}
+
+
+bool SubscriptionImpl::isActive() const
+{
+ return false;
+}
+
+
+void SubscriptionImpl::lock()
+{
+}
+
+
+void SubscriptionImpl::unlock()
+{
+}
+
+
+uint32_t SubscriptionImpl::getDataCount() const
+{
+ return 0;
+}
+
+
+Data SubscriptionImpl::getData(uint32_t) const
+{
+ return Data();
+}
+
+
+SubscriptionImpl& SubscriptionImplAccess::get(Subscription& item)
+{
+ return *item.impl;
+}
+
+
+const SubscriptionImpl& SubscriptionImplAccess::get(const Subscription& item)
+{
+ return *item.impl;
+}
diff --git a/qpid/cpp/src/qmf/SubscriptionImpl.h b/qpid/cpp/src/qmf/SubscriptionImpl.h
new file mode 100644
index 0000000000..053e3cd00e
--- /dev/null
+++ b/qpid/cpp/src/qmf/SubscriptionImpl.h
@@ -0,0 +1,57 @@
+#ifndef _QMF_SUBSCRIPTION_IMPL_H_
+#define _QMF_SUBSCRIPTION_IMPL_H_
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/RefCounted.h"
+#include "qmf/Subscription.h"
+
+namespace qmf {
+ class SubscriptionImpl : public virtual qpid::RefCounted {
+ public:
+ //
+ // Public impl-only methods
+ //
+ SubscriptionImpl(int p) : placeholder(p) {}
+ ~SubscriptionImpl();
+
+ //
+ // Methods from API handle
+ //
+ void cancel();
+ bool isActive() const;
+ void lock();
+ void unlock();
+ uint32_t getDataCount() const;
+ Data getData(uint32_t) const;
+
+ private:
+ int placeholder;
+ };
+
+ struct SubscriptionImplAccess
+ {
+ static SubscriptionImpl& get(Subscription&);
+ static const SubscriptionImpl& get(const Subscription&);
+ };
+}
+
+#endif
diff --git a/qpid/cpp/src/qmf/agentCapability.h b/qpid/cpp/src/qmf/agentCapability.h
new file mode 100644
index 0000000000..6a3f6f8534
--- /dev/null
+++ b/qpid/cpp/src/qmf/agentCapability.h
@@ -0,0 +1,39 @@
+#ifndef QMF_AGENT_CAPABILITY_H
+#define QMF_AGENT_CAPABILITY_H
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+namespace qmf {
+
+ /**
+ * Legacy (Qpid 0.7 C++ Agent, 0.7 Broker Agent) capabilities
+ */
+ const uint32_t AGENT_CAPABILITY_LEGACY = 0;
+
+ /**
+ * Qpid 0.8 QMFv2 capabilities
+ */
+ const uint32_t AGENT_CAPABILITY_0_8 = 1;
+ const uint32_t AGENT_CAPABILITY_V2_SCHEMA = 1;
+ const uint32_t AGENT_CAPABILITY_AGENT_PREDICATE = 1;
+}
+
+#endif
diff --git a/qpid/cpp/src/qmf/constants.cpp b/qpid/cpp/src/qmf/constants.cpp
new file mode 100644
index 0000000000..6e2fd935a9
--- /dev/null
+++ b/qpid/cpp/src/qmf/constants.cpp
@@ -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.
+ *
+ */
+
+#include "constants.h"
+
+using namespace std;
+using namespace qmf;
+
+/**
+ * Header key strings
+ */
+const string protocol::HEADER_KEY_APP_ID = "x-amqp-0-10.app-id";
+const string protocol::HEADER_KEY_METHOD = "method";
+const string protocol::HEADER_KEY_OPCODE = "qmf.opcode";
+const string protocol::HEADER_KEY_AGENT = "qmf.agent";
+const string protocol::HEADER_KEY_CONTENT = "qmf.content";
+const string protocol::HEADER_KEY_PARTIAL = "partial";
+
+/**
+ * Header values per-key
+ */
+const string protocol::HEADER_APP_ID_QMF = "qmf2";
+
+const string protocol::HEADER_METHOD_REQUEST = "request";
+const string protocol::HEADER_METHOD_RESPONSE = "response";
+const string protocol::HEADER_METHOD_INDICATION = "indication";
+
+const string protocol::HEADER_OPCODE_EXCEPTION = "_exception";
+const string protocol::HEADER_OPCODE_AGENT_LOCATE_REQUEST = "_agent_locate_request";
+const string protocol::HEADER_OPCODE_AGENT_LOCATE_RESPONSE = "_agent_locate_response";
+const string protocol::HEADER_OPCODE_AGENT_HEARTBEAT_INDICATION = "_agent_heartbeat_indication";
+const string protocol::HEADER_OPCODE_QUERY_REQUEST = "_query_request";
+const string protocol::HEADER_OPCODE_QUERY_RESPONSE = "_query_response";
+const string protocol::HEADER_OPCODE_SUBSCRIBE_REQUEST = "_subscribe_request";
+const string protocol::HEADER_OPCODE_SUBSCRIBE_RESPONSE = "_subscribe_response";
+const string protocol::HEADER_OPCODE_SUBSCRIBE_CANCEL_INDICATION = "_subscribe_cancel_indication";
+const string protocol::HEADER_OPCODE_SUBSCRIBE_REFRESH_INDICATION = "_subscribe_refresh_indication";
+const string protocol::HEADER_OPCODE_DATA_INDICATION = "_data_indication";
+const string protocol::HEADER_OPCODE_METHOD_REQUEST = "_method_request";
+const string protocol::HEADER_OPCODE_METHOD_RESPONSE = "_method_response";
+
+const string protocol::HEADER_CONTENT_SCHEMA_ID = "_schema_id";
+const string protocol::HEADER_CONTENT_SCHEMA_CLASS = "_schema_class";
+const string protocol::HEADER_CONTENT_OBJECT_ID = "_object_id";
+const string protocol::HEADER_CONTENT_DATA = "_data";
+const string protocol::HEADER_CONTENT_EVENT = "_event";
+const string protocol::HEADER_CONTENT_QUERY = "_query";
+
+/**
+ * Keywords for Agent attributes
+ */
+const string protocol::AGENT_ATTR_VENDOR = "_vendor";
+const string protocol::AGENT_ATTR_PRODUCT = "_product";
+const string protocol::AGENT_ATTR_INSTANCE = "_instance";
+const string protocol::AGENT_ATTR_NAME = "_name";
+const string protocol::AGENT_ATTR_TIMESTAMP = "_timestamp";
+const string protocol::AGENT_ATTR_HEARTBEAT_INTERVAL = "_heartbeat_interval";
+const string protocol::AGENT_ATTR_EPOCH = "_epoch";
+const string protocol::AGENT_ATTR_SCHEMA_UPDATED_TIMESTAMP = "_schema_updated";
diff --git a/qpid/cpp/src/qmf/constants.h b/qpid/cpp/src/qmf/constants.h
new file mode 100644
index 0000000000..79beaaf1ca
--- /dev/null
+++ b/qpid/cpp/src/qmf/constants.h
@@ -0,0 +1,83 @@
+#ifndef QMF_CONSTANTS_H
+#define QMF_CONSTANTS_H
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <string>
+
+namespace qmf {
+
+ struct protocol {
+ /**
+ * Header key strings
+ */
+ static const std::string HEADER_KEY_APP_ID;
+ static const std::string HEADER_KEY_METHOD;
+ static const std::string HEADER_KEY_OPCODE;
+ static const std::string HEADER_KEY_AGENT;
+ static const std::string HEADER_KEY_CONTENT;
+ static const std::string HEADER_KEY_PARTIAL;
+
+ /**
+ * Header values per-key
+ */
+ static const std::string HEADER_APP_ID_QMF;
+
+ static const std::string HEADER_METHOD_REQUEST;
+ static const std::string HEADER_METHOD_RESPONSE;
+ static const std::string HEADER_METHOD_INDICATION;
+
+ static const std::string HEADER_OPCODE_EXCEPTION;
+ static const std::string HEADER_OPCODE_AGENT_LOCATE_REQUEST;
+ static const std::string HEADER_OPCODE_AGENT_LOCATE_RESPONSE;
+ static const std::string HEADER_OPCODE_AGENT_HEARTBEAT_INDICATION;
+ static const std::string HEADER_OPCODE_QUERY_REQUEST;
+ static const std::string HEADER_OPCODE_QUERY_RESPONSE;
+ static const std::string HEADER_OPCODE_SUBSCRIBE_REQUEST;
+ static const std::string HEADER_OPCODE_SUBSCRIBE_RESPONSE;
+ static const std::string HEADER_OPCODE_SUBSCRIBE_CANCEL_INDICATION;
+ static const std::string HEADER_OPCODE_SUBSCRIBE_REFRESH_INDICATION;
+ static const std::string HEADER_OPCODE_DATA_INDICATION;
+ static const std::string HEADER_OPCODE_METHOD_REQUEST;
+ static const std::string HEADER_OPCODE_METHOD_RESPONSE;
+
+ static const std::string HEADER_CONTENT_SCHEMA_ID;
+ static const std::string HEADER_CONTENT_SCHEMA_CLASS;
+ static const std::string HEADER_CONTENT_OBJECT_ID;
+ static const std::string HEADER_CONTENT_DATA;
+ static const std::string HEADER_CONTENT_EVENT;
+ static const std::string HEADER_CONTENT_QUERY;
+
+ /**
+ * Keywords for Agent attributes
+ */
+ static const std::string AGENT_ATTR_VENDOR;
+ static const std::string AGENT_ATTR_PRODUCT;
+ static const std::string AGENT_ATTR_INSTANCE;
+ static const std::string AGENT_ATTR_NAME;
+ static const std::string AGENT_ATTR_TIMESTAMP;
+ static const std::string AGENT_ATTR_HEARTBEAT_INTERVAL;
+ static const std::string AGENT_ATTR_EPOCH;
+ static const std::string AGENT_ATTR_SCHEMA_UPDATED_TIMESTAMP;
+ };
+}
+
+#endif
diff --git a/qpid/cpp/src/qmf/exceptions.cpp b/qpid/cpp/src/qmf/exceptions.cpp
new file mode 100644
index 0000000000..be212f62f7
--- /dev/null
+++ b/qpid/cpp/src/qmf/exceptions.cpp
@@ -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.
+ *
+ */
+#include "qmf/exceptions.h"
+
+namespace qmf {
+
+ QmfException::QmfException(const std::string& msg) : qpid::types::Exception(msg) {}
+ QmfException::~QmfException() throw() {}
+
+ KeyNotFound::KeyNotFound(const std::string& msg) : QmfException("Key Not Found: " + msg) {}
+ KeyNotFound::~KeyNotFound() throw() {}
+
+ IndexOutOfRange::IndexOutOfRange() : QmfException("Index out-of-range") {}
+ IndexOutOfRange::~IndexOutOfRange() throw() {}
+
+ OperationTimedOut::OperationTimedOut() : QmfException("Timeout Expired") {}
+ OperationTimedOut::~OperationTimedOut() throw() {}
+}
+
diff --git a/qpid/cpp/src/qmf2.pc.in b/qpid/cpp/src/qmf2.pc.in
new file mode 100644
index 0000000000..4c7e6f9763
--- /dev/null
+++ b/qpid/cpp/src/qmf2.pc.in
@@ -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.
+#
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: qmf2
+Version: @VERSION@
+Description: Qpid Management Framework
+Requires: qpid
+Libs: -L@libdir@ -lqmf2 @LIBS@
+Cflags: -I@includedir@
diff --git a/qpid/cpp/src/qpid.linkmap b/qpid/cpp/src/qpid.linkmap
new file mode 100644
index 0000000000..264e6bac57
--- /dev/null
+++ b/qpid/cpp/src/qpid.linkmap
@@ -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.
+ *
+ */
+
+{
+ global:
+ extern "C++" {
+ typeinfo*qpid::*;
+ vtable*qpid::*;
+ qpid::*;
+ qpid::*operator*qpid::*;
+ };
+ local: *;
+};
diff --git a/qpid/cpp/src/qpid.pc.in b/qpid/cpp/src/qpid.pc.in
new file mode 100644
index 0000000000..c44a157347
--- /dev/null
+++ b/qpid/cpp/src/qpid.pc.in
@@ -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.
+#
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: qpid
+Version: @VERSION@
+Description: Qpid C++ client library
+Requires:
+Libs: -L@libdir@ -lqpidmessaging -lqpidtypes @LIBS@
+Cflags: -I@includedir@
diff --git a/qpid/cpp/src/qpid/AclHost.cpp b/qpid/cpp/src/qpid/AclHost.cpp
new file mode 100644
index 0000000000..1581d3b46a
--- /dev/null
+++ b/qpid/cpp/src/qpid/AclHost.cpp
@@ -0,0 +1,151 @@
+/*
+ *
+ * Copyright (c) 2014 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/AclHost.h"
+#include "qpid/Exception.h"
+#include "qpid/Msg.h"
+#include "qpid/StringUtils.h"
+#include "qpid/log/Logger.h"
+#include "qpid/sys/SocketAddress.h"
+
+#include <vector>
+#include <string>
+
+using namespace std;
+
+namespace qpid {
+
+AclHost::Invalid::Invalid(const string& s) : Exception(s) {}
+
+string AclHost::str() const {
+ if (cache.empty()) {
+ ostringstream os;
+ os << *this;
+ cache = os.str();
+ }
+ return cache;
+}
+
+std::string undecorateIPv6Name(std::string& host) {
+ std::string s(host);
+ if (host.length() >= 3 && host.find("[") == 0 && host.rfind("]") == host.length()-1)
+ s = host.substr(1, host.length()-2);
+ return s;
+}
+
+ostream& operator<<(ostream& os, const AclHost& aclhost) {
+ os << aclhost.comparisonDetails();
+ return os;
+}
+
+class AclHostParser {
+ public:
+ AclHostParser(AclHost& ah, const std::string& hSpec) :
+ aclhost(ah), hostSpec(hSpec) {}
+
+ bool parse() {
+ // Convert given host spec into vector of host names
+ // Blank host name means "all addresses. Create AclHost
+ // with no SocketAddress objects
+ if (hostSpec.compare("") == 0) {
+ aclhost.allAddresses = true;
+ return true;
+ }
+ std::vector<string> hostList;
+ split(hostList, hostSpec, ",");
+ if (hostList.size() == 0 || hostList.size() > 2) {
+ throw AclHost::Invalid(
+ QPID_MSG("Invalid AclHost: hostlist must be one name or "
+ "two names separated with a comma : " << hostSpec));
+ }
+ // Create pairs of SocketAddress objects representing the host range
+ if (hostList.size() == 1) {
+ hostList[0] = undecorateIPv6Name(hostList[0]);
+ aclhost.loSAptr = AclHost::SAptr(new sys::SocketAddress(hostList[0], ""));
+ aclhost.hiSAptr = AclHost::SAptr(new sys::SocketAddress(hostList[0], ""));
+ } else {
+ hostList[0] = undecorateIPv6Name(hostList[0]);
+ hostList[1] = undecorateIPv6Name(hostList[1]);
+ aclhost.loSAptr = AclHost::SAptr(new sys::SocketAddress(hostList[0], ""));
+ aclhost.hiSAptr = AclHost::SAptr(new sys::SocketAddress(hostList[1], ""));
+ }
+ // Make sure that this pair will work for run-time comparisons
+ if (!aclhost.loSAptr->isComparable(*aclhost.hiSAptr)) {
+ throw AclHost::Invalid(
+ QPID_MSG("AclHost specifies hosts that cannot be compared : " << hostSpec));
+ }
+
+ return true;
+ }
+
+ AclHost& aclhost;
+ const std::string& hostSpec;
+};
+
+void AclHost::parse(const std::string& hostSpec) {
+ parseNoThrow(hostSpec);
+ if (isEmpty() && !allAddresses)
+ throw AclHost::Invalid(QPID_MSG("Invalid AclHost : " << hostSpec));
+}
+
+void AclHost::parseNoThrow(const std::string& hostSpec) {
+ clear();
+ try {
+ if (!AclHostParser(*this, hostSpec).parse())
+ clear();
+ } catch (...) {
+ clear();
+ }
+}
+
+std::istream& operator>>(std::istream& is, AclHost& aclhost) {
+ std::string s;
+ is >> s;
+ aclhost.parse(s);
+ return is;
+}
+
+/**
+ * Given a connecting host's numeric IP address as a string
+ * Return true if the host is in the range of any of our kept
+ * SocketAddress's binary address ranges.
+ */
+bool AclHost::match(const std::string& hostIp) const {
+ try {
+ sys::SocketAddress sa1(hostIp, "");
+ return match(sa1);
+ } catch (...) {
+ return false;
+ }
+}
+
+/**
+ * Given a connecting host's SocketAddress
+ * Return true if the host is in the range of any of our kept
+ * SocketAddress's binary address ranges.
+ */
+bool AclHost::match(const sys::SocketAddress& peer) const {
+ if (!loSAptr.get()) {
+ // No kept socket address means "all addresses"
+ return true;
+ }
+ bool result = peer.inRange(*loSAptr, *hiSAptr);
+ return result;
+}
+
+} // namespace qpid
diff --git a/qpid/cpp/src/qpid/AclHost.h b/qpid/cpp/src/qpid/AclHost.h
new file mode 100644
index 0000000000..3c4bbe1ce3
--- /dev/null
+++ b/qpid/cpp/src/qpid/AclHost.h
@@ -0,0 +1,95 @@
+#ifndef QPID_ACLHOST_H
+#define QPID_ACLHOST_H
+
+/*
+ *
+ * Copyright (c) 2014 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/sys/SocketAddress.h"
+#include "qpid/Exception.h"
+#include <utility>
+#include <string>
+#include <vector>
+#include <new>
+#include <ostream>
+#include <boost/shared_ptr.hpp>
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+
+/** An AclHost contains shared_ptrs to two SocketAddresses
+ * representing a low-high pair of addresses.
+ */
+class AclHost {
+ public:
+ typedef boost::shared_ptr<sys::SocketAddress> SAptr;
+
+ struct Invalid : public Exception { QPID_COMMON_EXTERN Invalid(const std::string& s); };
+
+ /** Convert to string form. */
+ QPID_COMMON_EXTERN std::string str() const;
+
+ /** Empty AclHost. */
+ AclHost() : allAddresses(false) {}
+
+ explicit AclHost(const std::string& hostSpec) : allAddresses(false) { parse(hostSpec); }
+
+ QPID_COMMON_EXTERN std::string comparisonDetails() const {
+ if (loSAptr.get()) {
+ return loSAptr->comparisonDetails(*hiSAptr);
+ } else {
+ return "(all)";
+ }
+ }
+
+ QPID_COMMON_EXTERN bool match(const std::string& hostIp) const;
+
+ QPID_COMMON_EXTERN bool match(const sys::SocketAddress& sa) const;
+
+ QPID_COMMON_EXTERN void parse( const std::string& hostSpec);
+ QPID_COMMON_EXTERN void parseNoThrow(const std::string& hostSpec);
+
+ QPID_COMMON_EXTERN void clear() {
+ cache.clear();
+ loSAptr.reset();
+ hiSAptr.reset();
+ }
+
+ QPID_COMMON_EXTERN bool isEmpty() const {
+ return !loSAptr.get() && !hiSAptr.get(); }
+
+ QPID_COMMON_EXTERN bool isAllAddresses() const { return allAddresses; }
+
+ private:
+ mutable std::string cache; // cache string form for efficiency.
+
+ bool allAddresses;
+ SAptr loSAptr;
+ SAptr hiSAptr;
+
+ friend class AclHostParser;
+};
+
+inline bool operator==(const AclHost& a, const AclHost& b) { return a.str()==b.str(); }
+inline bool operator!=(const AclHost& a, const AclHost& b) { return a.str()!=b.str(); }
+
+QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream& os, const AclHost& aclhost);
+QPID_COMMON_EXTERN std::istream& operator>>(std::istream& is, AclHost& aclhost);
+
+} // namespace qpid
+
+#endif /*!QPID_ACLHOST_H*/
diff --git a/qpid/cpp/src/qpid/Address.cpp b/qpid/cpp/src/qpid/Address.cpp
new file mode 100644
index 0000000000..01770524c8
--- /dev/null
+++ b/qpid/cpp/src/qpid/Address.cpp
@@ -0,0 +1,44 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/Address.h"
+#include "qpid/client/ConnectionSettings.h"
+
+#include <ostream>
+
+using namespace std;
+
+namespace qpid {
+
+const string Address::TCP("tcp");
+
+ostream& operator<<(ostream& os, const Address& a) {
+ // If the host is an IPv6 literal we need to print "[]" around it
+ // (we detect IPv6 literals because they contain ":" which is otherwise illegal)
+ if (a.host.find(':') != string::npos) {
+ return os << a.protocol << ":[" << a.host << "]:" << a.port;
+ } else {
+ return os << a.protocol << ":" << a.host << ":" << a.port;
+ }
+}
+
+bool operator==(const Address& x, const Address& y) {
+ return y.protocol==x.protocol && y.host==x.host && y.port == x.port;
+}
+bool operator!=(const Address& x, const Address& y) { return !(x == y); }
+} // namespace qpid
diff --git a/qpid/cpp/src/qpid/Address.h b/qpid/cpp/src/qpid/Address.h
new file mode 100755
index 0000000000..fe43f3847d
--- /dev/null
+++ b/qpid/cpp/src/qpid/Address.h
@@ -0,0 +1,56 @@
+#ifndef QPID_ADDRESS_H
+#define QPID_ADDRESS_H
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/CommonImportExport.h"
+#include <iosfwd>
+#include <string>
+
+namespace qpid {
+namespace client { struct ConnectionSettings; }
+
+
+/**
+ * Contains the protocol address of an AMQP broker.
+ */
+struct Address {
+public:
+ QPID_COMMON_EXTERN static const std::string TCP; // Default TCP protocol tag.
+ QPID_COMMON_EXTERN static const uint16_t AMQP_PORT=5672; // Default AMQP port.
+
+ QPID_COMMON_INLINE_EXTERN explicit Address(
+ const std::string& protocol_=std::string(),
+ const std::string& host_=std::string(),
+ uint16_t port_=0
+ ) : protocol(protocol_), host(host_), port(port_) {}
+
+ std::string protocol;
+ std::string host;
+ uint16_t port;
+};
+
+QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream& os, const Address& addr);
+QPID_COMMON_EXTERN bool operator==(const Address& x, const Address& y);
+QPID_COMMON_EXTERN bool operator!=(const Address& x, const Address& y);
+
+} // namespace qpid
+
+#endif /*!QPID_ADDRESS_H*/
diff --git a/qpid/cpp/src/qpid/BufferRef.h b/qpid/cpp/src/qpid/BufferRef.h
new file mode 100644
index 0000000000..bfe1f9ebaa
--- /dev/null
+++ b/qpid/cpp/src/qpid/BufferRef.h
@@ -0,0 +1,70 @@
+#ifndef QPID_BUFFERREF_H
+#define QPID_BUFFERREF_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/RefCounted.h"
+#include <boost/intrusive_ptr.hpp>
+
+namespace qpid {
+
+/** Template for mutable or const buffer references */
+template <class T> class BufferRefT {
+ public:
+ BufferRefT() : begin_(0), end_(0) {}
+
+ BufferRefT(boost::intrusive_ptr<RefCounted> c, T* begin, T* end) :
+ counter(c), begin_(begin), end_(end) {}
+
+ template <class U> BufferRefT(const BufferRefT<U>& other) :
+ counter(other.counter), begin_(other.begin_), end_(other.end_) {}
+
+ T* begin() const { return begin_; }
+ T* end() const { return end_; }
+
+ /** Return a sub-buffer of the current buffer */
+ BufferRefT sub_buffer(T* begin, T* end) {
+ assert(begin_ <= begin && begin <= end_);
+ assert(begin_ <= end && end <= end_);
+ assert(begin <= end);
+ return BufferRefT(counter, begin, end);
+ }
+
+ private:
+ boost::intrusive_ptr<RefCounted> counter;
+ T* begin_;
+ T* end_;
+};
+
+/**
+ * Reference to a mutable ref-counted buffer.
+ */
+typedef BufferRefT<char> BufferRef;
+
+/**
+ * Reference to a const ref-counted buffer.
+ */
+typedef BufferRefT<const char> ConstBufferRef;
+
+} // namespace qpid
+
+#endif /*!QPID_BUFFERREF_H*/
diff --git a/qpid/cpp/src/qpid/CommonImportExport.h b/qpid/cpp/src/qpid/CommonImportExport.h
new file mode 100644
index 0000000000..dd2b900b73
--- /dev/null
+++ b/qpid/cpp/src/qpid/CommonImportExport.h
@@ -0,0 +1,35 @@
+#ifndef QPID_COMMON_IMPORT_EXPORT_H
+#define QPID_COMMON_IMPORT_EXPORT_H
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "qpid/ImportExport.h"
+
+#if defined(COMMON_EXPORT) || defined (qpidcommon_EXPORTS)
+# define QPID_COMMON_EXTERN QPID_EXPORT
+# define QPID_COMMON_CLASS_EXTERN QPID_CLASS_EXPORT
+# define QPID_COMMON_INLINE_EXTERN QPID_INLINE_EXPORT
+#else
+# define QPID_COMMON_EXTERN QPID_IMPORT
+# define QPID_COMMON_CLASS_EXTERN QPID_CLASS_IMPORT
+# define QPID_COMMON_INLINE_EXTERN QPID_INLINE_IMPORT
+#endif
+
+#endif
diff --git a/qpid/cpp/src/qpid/DataDir.cpp b/qpid/cpp/src/qpid/DataDir.cpp
new file mode 100644
index 0000000000..546df3dabd
--- /dev/null
+++ b/qpid/cpp/src/qpid/DataDir.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/Exception.h"
+#include "qpid/DataDir.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/FileSysDir.h"
+#include "qpid/sys/LockFile.h"
+
+namespace qpid {
+
+DataDir::DataDir (const std::string& path) :
+ enabled (!path.empty ()),
+ dirPath (path)
+{
+ if (enabled)
+ {
+ sys::FileSysDir dir(dirPath);
+ if (!dir.exists())
+ dir.mkdir();
+ std::string lockFileName(path);
+ lockFileName += "/lock";
+ lockFile = std::auto_ptr<sys::LockFile>(new sys::LockFile(lockFileName, true));
+ }
+}
+
+DataDir::~DataDir () {}
+
+} // namespace qpid
+
diff --git a/qpid/cpp/src/qpid/DataDir.h b/qpid/cpp/src/qpid/DataDir.h
new file mode 100644
index 0000000000..ec73d28796
--- /dev/null
+++ b/qpid/cpp/src/qpid/DataDir.h
@@ -0,0 +1,54 @@
+#ifndef QPID_DATADIR_H
+#define QPID_DATADIR_H
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <string>
+#include <memory>
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+
+ namespace sys {
+ class LockFile;
+ }
+
+/**
+ * DataDir class.
+ */
+class DataDir
+{
+ const bool enabled;
+ const std::string dirPath;
+ std::auto_ptr<qpid::sys::LockFile> lockFile;
+
+ public:
+
+ QPID_COMMON_EXTERN DataDir (const std::string& path);
+ QPID_COMMON_EXTERN ~DataDir ();
+
+ bool isEnabled() const { return enabled; }
+ const std::string& getPath() const { return dirPath; }
+};
+
+} // namespace qpid
+
+#endif /*!QPID_DATADIR_H*/
diff --git a/qpid/cpp/src/qpid/DisableExceptionLogging.h b/qpid/cpp/src/qpid/DisableExceptionLogging.h
new file mode 100644
index 0000000000..04a9240513
--- /dev/null
+++ b/qpid/cpp/src/qpid/DisableExceptionLogging.h
@@ -0,0 +1,39 @@
+#ifndef QPID_DISABLEEXCEPTIONLOGGING_H
+#define QPID_DISABLEEXCEPTIONLOGGING_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+
+/**
+ * Temporarily disable logging in qpid::Exception constructor.
+ * Used by log::Logger to avoid logging exceptions during Logger construction.
+ */
+struct DisableExceptionLogging
+{
+ QPID_COMMON_EXTERN DisableExceptionLogging();
+ QPID_COMMON_EXTERN ~DisableExceptionLogging();
+};
+} // namespace qpid
+
+#endif /*!QPID_DISABLEEXCEPTIONLOGGING_H*/
diff --git a/qpid/cpp/src/qpid/Exception.cpp b/qpid/cpp/src/qpid/Exception.cpp
new file mode 100644
index 0000000000..999c2aeb52
--- /dev/null
+++ b/qpid/cpp/src/qpid/Exception.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/log/Statement.h"
+#include "qpid/Exception.h"
+#include "qpid/DisableExceptionLogging.h"
+#include <typeinfo>
+#include <assert.h>
+#include <string.h>
+
+namespace qpid {
+
+// Note on static initialization order: if an exception is constructed
+// in a static constructor before disableExceptionLogging has been
+// initialized, the worst that can happen is we lose an exception log
+// message. Since we shouldn't be throwing a lot of exceptions during
+// static construction this seems safe.
+static bool disableExceptionLogging = false;
+
+DisableExceptionLogging::DisableExceptionLogging() { disableExceptionLogging = true; }
+DisableExceptionLogging::~DisableExceptionLogging() { disableExceptionLogging = false; }
+
+Exception::Exception(const std::string& msg) throw() : message(msg) {
+ if (disableExceptionLogging) return;
+ QPID_LOG_IF(debug, !msg.empty(), "Exception constructed: " << message);
+}
+
+Exception::~Exception() throw() {}
+
+std::string Exception::getPrefix() const { return std::string(); }
+
+std::string Exception::getMessage() const { return message; }
+
+namespace { const std::string COLON(": "); }
+
+const char* Exception::what() const throw() {
+ // Construct the what string the first time it is needed.
+ if (whatStr.empty()) {
+ if (message.compare(0, getPrefix().size(), getPrefix()) == 0 || // Already has prefix
+ getPrefix().empty()) // No prefix
+ whatStr = message;
+ else
+ whatStr = getPrefix() + COLON + message;
+ }
+ return whatStr.c_str();
+}
+
+ClosedException::ClosedException(const std::string& msg)
+ : Exception(msg) {}
+
+std::string ClosedException::getPrefix() const { return "Closed"; }
+
+} // namespace qpid
diff --git a/qpid/cpp/src/qpid/Exception.h b/qpid/cpp/src/qpid/Exception.h
new file mode 100644
index 0000000000..6df012ba28
--- /dev/null
+++ b/qpid/cpp/src/qpid/Exception.h
@@ -0,0 +1,95 @@
+#ifndef _Exception_
+#define _Exception_
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/framing/amqp_types.h"
+#include "qpid/framing/constants.h"
+#include "qpid/framing/enum.h"
+#include "qpid/sys/StrError.h"
+#include "qpid/CommonImportExport.h"
+#include <string>
+#include <errno.h>
+
+namespace qpid
+{
+
+/**
+ * Base class for Qpid runtime exceptions.
+ */
+class QPID_COMMON_CLASS_EXTERN Exception : public std::exception
+{
+ public:
+ QPID_COMMON_EXTERN explicit Exception(const std::string& message=std::string()) throw();
+ QPID_COMMON_EXTERN virtual ~Exception() throw();
+ QPID_COMMON_EXTERN virtual const char* what() const throw(); // prefix: message
+ QPID_COMMON_EXTERN virtual std::string getMessage() const; // Unprefixed message
+ QPID_COMMON_EXTERN virtual std::string getPrefix() const; // Prefix
+
+ private:
+ std::string message;
+ mutable std::string whatStr;
+};
+
+/** Exception that includes an errno message. */
+struct QPID_COMMON_CLASS_EXTERN ErrnoException : public Exception {
+ ErrnoException(const std::string& msg, int err) : Exception(msg+": "+qpid::sys::strError(err)) {}
+ ErrnoException(const std::string& msg) : Exception(msg+": "+qpid::sys::strError(errno)) {}
+};
+
+struct QPID_COMMON_CLASS_EXTERN SessionException : public Exception {
+ const framing::execution::ErrorCode code;
+ SessionException(framing::execution::ErrorCode code_, const std::string& message)
+ : Exception(message), code(code_) {}
+};
+
+struct QPID_COMMON_CLASS_EXTERN ChannelException : public Exception {
+ const framing::session::DetachCode code;
+ ChannelException(framing::session::DetachCode _code, const std::string& message)
+ : Exception(message), code(_code) {}
+};
+
+struct QPID_COMMON_CLASS_EXTERN ConnectionException : public Exception {
+ const framing::connection::CloseCode code;
+ ConnectionException(framing::connection::CloseCode _code, const std::string& message)
+ : Exception(message), code(_code) {}
+};
+
+struct QPID_COMMON_CLASS_EXTERN ClosedException : public Exception {
+ QPID_COMMON_EXTERN ClosedException(const std::string& msg=std::string());
+ QPID_COMMON_EXTERN std::string getPrefix() const;
+};
+
+/**
+ * Exception representing transport failure
+ */
+struct TransportFailure : public Exception {
+ TransportFailure(const std::string& msg=std::string()) : Exception(msg) {}
+};
+
+struct ProtocolVersionError : public TransportFailure {
+ ProtocolVersionError(const std::string& msg=std::string()) : TransportFailure(msg) {}
+};
+
+} // namespace qpid
+
+#endif /*!_Exception_*/
diff --git a/qpid/cpp/src/qpid/InlineAllocator.h b/qpid/cpp/src/qpid/InlineAllocator.h
new file mode 100644
index 0000000000..28ea73ec12
--- /dev/null
+++ b/qpid/cpp/src/qpid/InlineAllocator.h
@@ -0,0 +1,101 @@
+#ifndef QPID_INLINEALLOCATOR_H
+#define QPID_INLINEALLOCATOR_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <memory>
+#include <assert.h>
+#include <boost/type_traits/type_with_alignment.hpp>
+#include <boost/type_traits/alignment_of.hpp>
+
+namespace qpid {
+
+template <typename RequestedType, typename InlineType, typename BaseAllocator, size_t Max>
+struct InlineRebind;
+
+
+/**
+ * An allocator that has inline storage for up to Max objects
+ * of type BaseAllocator::value_type.
+ */
+template <class BaseAllocator, size_t Max>
+class InlineAllocator : public BaseAllocator {
+ public:
+ typedef typename BaseAllocator::pointer pointer;
+ typedef typename BaseAllocator::size_type size_type;
+ typedef typename BaseAllocator::value_type value_type;
+
+ InlineAllocator() : allocated(false) {}
+ InlineAllocator(const InlineAllocator& x) : BaseAllocator(x), allocated(false) {}
+
+ pointer allocate(size_type n, std::allocator<void>::const_pointer = 0) {
+ if (n <= Max && !allocated) {
+ allocated=true;
+ return reinterpret_cast<value_type*>(address());
+ }
+ else
+ return BaseAllocator::allocate(n, 0);
+ }
+
+ void deallocate(pointer p, size_type n) {
+ if (p == address()) {
+ assert(allocated);
+ allocated=false;
+ }
+ else
+ BaseAllocator::deallocate(p, n);
+ }
+
+ template<typename T1>
+ struct rebind {
+ typedef typename InlineRebind<T1, value_type, BaseAllocator, Max>::other other;
+ };
+
+ private:
+ // POD object with alignment and size to hold Max value_types.
+ static const size_t ALIGNMENT=boost::alignment_of<value_type>::value;
+ typedef typename boost::type_with_alignment<ALIGNMENT>::type Aligner;
+ union Store {
+ Aligner aligner_;
+ char sizer_[sizeof(value_type)*Max];
+ } store;
+ value_type* address() { return reinterpret_cast<value_type*>(&store); }
+ bool allocated;
+};
+
+
+// Rebind: if RequestedType == InlineType, use the InlineAllocator,
+// otherwise, use the BaseAllocator without any inlining.
+
+template <typename RequestedType, typename InlineType, typename BaseAllocator, size_t Max>
+struct InlineRebind {
+ typedef typename BaseAllocator::template rebind<RequestedType>::other other;
+};
+
+template <typename T, typename BaseAllocator, size_t Max>
+struct InlineRebind<T, T, BaseAllocator, Max> {
+ typedef typename qpid::InlineAllocator<BaseAllocator, Max> other;
+};
+
+} // namespace qpid
+
+#endif /*!QPID_INLINEALLOCATOR_H*/
diff --git a/qpid/cpp/src/qpid/InlineVector.h b/qpid/cpp/src/qpid/InlineVector.h
new file mode 100644
index 0000000000..c55db295f3
--- /dev/null
+++ b/qpid/cpp/src/qpid/InlineVector.h
@@ -0,0 +1,68 @@
+#ifndef QPID_INLINEVECTOR_H
+#define QPID_INLINEVECTOR_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/InlineAllocator.h"
+#include <vector>
+
+namespace qpid {
+
+/**
+ * A vector that stores up to Max elements in inline storage,
+ * otherwise uses normal vector allocation.
+ *
+ * NOTE: depends on some non-standard but highly probably assumptions
+ * about how std::vector uses its allocator, they are true for g++.
+ * - default constructor does not allocate.
+ * - reserve(N) does not allocate more than N elements.
+ * - vector never re-allocates when size() < capacity()
+ */
+template <class T, size_t Max, class Alloc=std::allocator<T> >
+class InlineVector : public std::vector<T, InlineAllocator<Alloc, Max> >
+{
+ typedef std::vector<T, InlineAllocator<Alloc, Max> > Base;
+ public:
+ typedef typename Base::allocator_type allocator_type;
+ typedef typename Base::value_type value_type;
+ typedef typename Base::size_type size_type;
+
+ explicit InlineVector(const allocator_type& a=allocator_type()) : Base(a) {
+ this->reserve(Max);
+ }
+
+ explicit InlineVector(size_type n, const value_type& x = value_type(),
+ const allocator_type& a=allocator_type()) : Base(a)
+ {
+ this->reserve(std::max(n, Max));
+ this->insert(this->end(), n, x);
+ }
+
+ InlineVector(const InlineVector& x) : Base() {
+ this->reserve(std::max(x.size(), Max));
+ *this = x;
+ }
+};
+
+} // namespace qpid
+
+#endif /*!QPID_INLINEVECTOR_H*/
diff --git a/qpid/cpp/src/qpid/Modules.cpp b/qpid/cpp/src/qpid/Modules.cpp
new file mode 100644
index 0000000000..049ededaa7
--- /dev/null
+++ b/qpid/cpp/src/qpid/Modules.cpp
@@ -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.
+ *
+ */
+
+#include "config.h"
+#include "qpid/Modules.h"
+#include "qpid/Exception.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/Shlib.h"
+#include "qpid/sys/FileSysDir.h"
+
+namespace {
+
+// CMake sets QPID_MODULE_SUFFIX; Autoconf doesn't, so assume Linux .so
+#ifndef QPID_MODULE_SUFFIX
+#define QPID_MODULE_SUFFIX ".so"
+#endif
+
+inline std::string& suffix() {
+ static std::string s(QPID_MODULE_SUFFIX);
+ return s;
+}
+
+bool isShlibName(const std::string& name) {
+ return name.substr(name.size()-suffix().size()) == suffix();
+}
+
+}
+
+namespace qpid {
+
+ModuleOptions::ModuleOptions(const std::string& defaultModuleDir)
+ : qpid::Options("Module options"), loadDir(defaultModuleDir), noLoad(false)
+{
+ addOptions()
+ ("module-dir", optValue(loadDir, "DIR"), "Load all shareable modules in this directory")
+ ("load-module", optValue(load, "FILE"), "Specifies additional module(s) to be loaded")
+ ("no-module-dir", optValue(noLoad), "Don't load modules from module directory");
+}
+
+void tryShlib(const std::string& libname) {
+ sys::Shlib shlib( isShlibName(libname) ? libname : (libname + suffix()));
+}
+
+namespace {
+
+void tryOnlyShlib(const std::string& libname) throw() {
+ try {
+ if (isShlibName(libname)) sys::Shlib shlib( libname );
+ }
+ catch (const std::exception& /*e*/) {
+ }
+}
+
+}
+
+void loadModuleDir (std::string dirname, bool isDefault)
+{
+
+ sys::FileSysDir dirPath (dirname);
+
+ bool exists;
+ try
+ {
+ exists = dirPath.exists();
+ } catch (Exception& e) {
+ throw Exception ("Invalid value for module-dir: " + e.getMessage());
+ }
+ if (!exists) {
+ if (isDefault) return;
+ throw Exception ("Directory not found: " + dirname);
+ }
+
+ dirPath.forEachFile(&tryOnlyShlib);
+}
+
+} // namespace qpid
diff --git a/qpid/cpp/src/qpid/Modules.h b/qpid/cpp/src/qpid/Modules.h
new file mode 100644
index 0000000000..9fb91d60eb
--- /dev/null
+++ b/qpid/cpp/src/qpid/Modules.h
@@ -0,0 +1,44 @@
+#ifndef QPID_MODULES_H
+#define QPID_MODULES_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/Options.h"
+#include <string>
+#include <vector>
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+
+struct ModuleOptions : public qpid::Options {
+ std::string loadDir;
+ std::vector<std::string> load;
+ bool noLoad;
+ QPID_COMMON_EXTERN ModuleOptions(const std::string& defaultModuleDir);
+};
+
+QPID_COMMON_EXTERN void tryShlib(const std::string& libname);
+QPID_COMMON_EXTERN void loadModuleDir (std::string dirname, bool isDefault);
+
+} // namespace qpid
+
+#endif /*!QPID_MODULES_H*/
diff --git a/qpid/cpp/src/qpid/Msg.h b/qpid/cpp/src/qpid/Msg.h
new file mode 100644
index 0000000000..5f0b11bc60
--- /dev/null
+++ b/qpid/cpp/src/qpid/Msg.h
@@ -0,0 +1,79 @@
+#ifndef QPID_MSG_H
+#define QPID_MSG_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <sstream>
+#include <iostream>
+#include "qpid/types/ImportExport.h"
+
+namespace qpid {
+
+/** A simple wrapper for std::ostringstream that allows
+ * in place construction of a message and automatic conversion
+ * to string.
+ * E.g.
+ *@code
+ * void foo(const std::string&);
+ * foo(Msg() << "hello " << 32);
+ *@endcode
+ * Will construct the string "hello 32" and pass it to foo()
+ */
+struct Msg {
+ std::ostringstream os;
+ Msg() {}
+ Msg(const Msg& m) : os(m.str()) {}
+ std::string str() const { return os.str(); }
+ operator std::string() const { return str(); }
+
+ Msg& operator<<(long n) { os << n; return *this; }
+ Msg& operator<<(unsigned long n) { os << n; return *this; }
+ Msg& operator<<(bool n) { os << n; return *this; }
+ Msg& operator<<(short n) { os << n; return *this; }
+ Msg& operator<<(unsigned short n) { os << n; return *this; }
+ Msg& operator<<(int n) { os << n; return *this; }
+ Msg& operator<<(unsigned int n) { os << n; return *this; }
+#ifdef _GLIBCXX_USE_LONG_LONG
+ Msg& operator<<(long long n) { os << n; return *this; }
+ Msg& operator<<(unsigned long long n) { os << n; return *this; }
+#endif
+ Msg& operator<<(double n) { os << n; return *this; }
+ Msg& operator<<(float n) { os << n; return *this; }
+ Msg& operator<<(long double n) { os << n; return *this; }
+
+ template <class T> Msg& operator<<(const T& t) { os <<t; return *this; }
+};
+
+
+
+inline std::ostream& operator<<(std::ostream& o, const Msg& m) {
+ return o << m.str();
+}
+
+/** Construct a message using operator << and append (file:line) */
+#define QUOTE_(x) #x
+#define QUOTE(x) QUOTE_(x)
+#define QPID_MSG(message) (::qpid::Msg() << message << " (" __FILE__ ":" QUOTE(__LINE__) ")")
+
+} // namespace qpid
+
+#endif /*!QPID_MSG_H*/
diff --git a/qpid/cpp/src/qpid/NullSaslClient.cpp b/qpid/cpp/src/qpid/NullSaslClient.cpp
new file mode 100644
index 0000000000..30c2330553
--- /dev/null
+++ b/qpid/cpp/src/qpid/NullSaslClient.cpp
@@ -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.
+ *
+ */
+#include "NullSaslClient.h"
+#include "qpid/sys/SecurityLayer.h"
+#include "Exception.h"
+
+namespace qpid {
+namespace {
+const std::string ANONYMOUS("ANONYMOUS");
+const std::string PLAIN("PLAIN");
+}
+
+NullSaslClient::NullSaslClient(const std::string& u, const std::string& p) : username(u), password(p) {}
+
+bool NullSaslClient::start(const std::string& mechanisms, std::string& response,
+ const qpid::sys::SecuritySettings*)
+{
+ if (!username.empty() && !password.empty() && mechanisms.find(PLAIN) != std::string::npos) {
+ mechanism = PLAIN;
+ response = ((char)0) + username + ((char)0) + password;
+ } else if (mechanisms.find(ANONYMOUS) != std::string::npos) {
+ mechanism = ANONYMOUS;
+ const char* u = username.empty() ? ANONYMOUS.c_str() : username.c_str();
+ response = ((char)0) + u;
+ } else {
+ throw qpid::Exception("No suitable mechanism!");
+ }
+ return true;
+}
+std::string NullSaslClient::step(const std::string&)
+{
+ return std::string();
+}
+std::string NullSaslClient::getMechanism()
+{
+ return mechanism;
+}
+std::string NullSaslClient::getUserId()
+{
+ return username.empty() ? ANONYMOUS : username;
+}
+std::auto_ptr<qpid::sys::SecurityLayer> NullSaslClient::getSecurityLayer(uint16_t)
+{
+ return std::auto_ptr<qpid::sys::SecurityLayer>();
+}
+} // namespace qpid
diff --git a/qpid/cpp/src/qpid/NullSaslClient.h b/qpid/cpp/src/qpid/NullSaslClient.h
new file mode 100644
index 0000000000..26882c6c20
--- /dev/null
+++ b/qpid/cpp/src/qpid/NullSaslClient.h
@@ -0,0 +1,45 @@
+#ifndef QPID_NULLSASLCLIENT_H
+#define QPID_NULLSASLCLIENT_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "Sasl.h"
+
+namespace qpid {
+
+class NullSaslClient : public Sasl
+{
+ public:
+ NullSaslClient(const std::string& username, const std::string& password);
+ bool start(const std::string& mechanisms, std::string& response,
+ const qpid::sys::SecuritySettings* externalSecuritySettings = 0);
+ std::string step(const std::string& challenge);
+ std::string getMechanism();
+ std::string getUserId();
+ std::auto_ptr<qpid::sys::SecurityLayer> getSecurityLayer(uint16_t maxFrameSize);
+ private:
+ const std::string username;
+ const std::string password;
+ std::string mechanism;
+};
+} // namespace qpid
+
+#endif /*!QPID_NULLSASLCLIENT_H*/
diff --git a/qpid/cpp/src/qpid/NullSaslServer.cpp b/qpid/cpp/src/qpid/NullSaslServer.cpp
new file mode 100644
index 0000000000..9d560c8e68
--- /dev/null
+++ b/qpid/cpp/src/qpid/NullSaslServer.cpp
@@ -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.
+ *
+ */
+#include "NullSaslServer.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/SecurityLayer.h"
+#include <assert.h>
+#include <boost/format.hpp>
+
+namespace qpid {
+NullSaslServer::NullSaslServer(const std::string& r) : realm(r) {}
+NullSaslServer::Status NullSaslServer::start(const std::string& mechanism, const std::string* response, std::string& /*challenge*/)
+{
+ if (mechanism == "PLAIN") {
+ if (response) {
+ std::string uid;
+ std::string::size_type i = response->find((char)0);
+ if (i == 0 && response->size() > 1) {
+ //no authorization id; use authentication id
+ i = response->find((char)0, 1);
+ if (i != std::string::npos) uid = response->substr(1, i-1);
+ } else if (i != std::string::npos) {
+ //authorization id is first null delimited field
+ uid = response->substr(0, i);
+ } else {
+ QPID_LOG(error, "Invalid PLAIN request, null delimiter not found in response data");
+ return FAIL;
+ }
+ if (!uid.empty()) {
+ //append realm if it has not already been added
+ i = uid.find(realm);
+ if (i == std::string::npos || realm.size() + i < uid.size()) {
+ uid = boost::str(boost::format("%1%@%2%") % uid % realm);
+ }
+ userid = uid;
+ }
+ return OK;
+ } else {
+ QPID_LOG(error, "Invalid PLAIN request, expected response containing user credentials");
+ return FAIL;
+ }
+ } else if (mechanism == "ANONYMOUS") {
+ userid = "anonymous";
+ return OK;
+ } else {
+ return FAIL;
+ }
+}
+
+NullSaslServer::Status NullSaslServer::step(const std::string* /*response*/, std::string& /*challenge*/)
+{
+ return FAIL;
+}
+std::string NullSaslServer::getMechanisms()
+{
+ return std::string("ANONYMOUS PLAIN");
+}
+std::string NullSaslServer::getUserid()
+{
+ return userid;
+}
+
+std::auto_ptr<qpid::sys::SecurityLayer> NullSaslServer::getSecurityLayer(size_t)
+{
+ return std::auto_ptr<qpid::sys::SecurityLayer>();
+}
+
+} // namespace qpid
diff --git a/qpid/cpp/src/qpid/NullSaslServer.h b/qpid/cpp/src/qpid/NullSaslServer.h
new file mode 100644
index 0000000000..22a1b293a3
--- /dev/null
+++ b/qpid/cpp/src/qpid/NullSaslServer.h
@@ -0,0 +1,50 @@
+#ifndef QPID_NULLSASLSERVER_H
+#define QPID_NULLSASLSERVER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/CommonImportExport.h"
+#include "qpid/SaslServer.h"
+
+namespace qpid {
+
+/**
+ * Dummy implementation of the SASL server role. This will advertise
+ * ANONYMOUS and PLAIN, and parse the reponse data for those
+ * accordingly, but will make no attempt to actually authenticate
+ * users.
+ */
+class NullSaslServer : public SaslServer
+{
+ public:
+ QPID_COMMON_EXTERN NullSaslServer(const std::string& realm);
+ Status start(const std::string& mechanism, const std::string* response, std::string& challenge);
+ Status step(const std::string* response, std::string& challenge);
+ std::string getMechanisms();
+ std::string getUserid();
+ std::auto_ptr<qpid::sys::SecurityLayer> getSecurityLayer(size_t);
+ private:
+ std::string realm;
+ std::string userid;
+};
+} // namespace qpid
+
+#endif /*!QPID_NULLSASLSERVER_H*/
diff --git a/qpid/cpp/src/qpid/Options.cpp b/qpid/cpp/src/qpid/Options.cpp
new file mode 100644
index 0000000000..0021afc574
--- /dev/null
+++ b/qpid/cpp/src/qpid/Options.cpp
@@ -0,0 +1,335 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "config.h"
+#include "qpid/Options.h"
+#include "qpid/OptionsTemplates.h"
+#include "qpid/Exception.h"
+#include "qpid/log/Logger.h"
+#include "qpid/sys/Time.h"
+
+#include <boost/bind.hpp>
+
+#include <fstream>
+#include <algorithm>
+#include <iostream>
+
+namespace qpid {
+
+using namespace std;
+
+
+namespace {
+
+struct EnvOptMapper {
+ static bool matchChar(char env, char opt) {
+ return (env==toupper(opt)) || (strchr("-.", opt) && env=='_');
+ }
+
+ static bool matchStr(const string& env, boost::shared_ptr<po::option_description> desc) {
+ return desc->long_name().size() == env.size() &&
+ std::equal(env.begin(), env.end(), desc->long_name().begin(), &matchChar);
+ }
+
+ static bool matchCase(const string& env, boost::shared_ptr<po::option_description> desc) {
+ return env == desc->long_name();
+ }
+
+ EnvOptMapper(const Options& o) : opts(o) {}
+
+ string operator()(const string& envVar) {
+ static const std::string prefix("QPID_");
+ if (envVar.substr(0, prefix.size()) == prefix) {
+ string env = envVar.substr(prefix.size());
+ typedef const std::vector< boost::shared_ptr<po::option_description> > OptDescs;
+ OptDescs::const_iterator i =
+ find_if(opts.options().begin(), opts.options().end(), boost::bind(matchStr, env, _1));
+ if (i != opts.options().end())
+ return (*i)->long_name();
+ }
+ return string();
+ }
+
+
+ bool
+ isComment ( string const & str )
+ {
+ size_t i = str.find_first_not_of ( " \t" );
+
+ if ( i == string::npos )
+ return true;
+
+ return str[i] == '#';
+ }
+
+
+ void badArg ( string& line ) {
+ ostringstream msg;
+ msg << "Bad argument: |" << line << "|\n";
+ throw Exception(msg.str());
+ }
+
+
+ string configFileLine (string& line, bool allowUnknowns=true) {
+
+ if ( isComment ( line ) ) {
+ return string();
+ }
+
+ size_t pos = line.find ('=');
+ if (pos == string::npos) {
+ if ( allowUnknowns ) {
+ return string();
+ }
+ else {
+ badArg ( line );
+ }
+ }
+ string key = line.substr (0, pos);
+#if (BOOST_VERSION >= 103300)
+ typedef const std::vector< boost::shared_ptr<po::option_description> > OptDescs;
+ OptDescs::const_iterator i =
+ find_if(opts.options().begin(), opts.options().end(), boost::bind(matchCase, key, _1));
+ if (i != opts.options().end())
+ return string (line) + "\n";
+ else {
+ if ( allowUnknowns ) {
+ return string();
+ }
+ else {
+ badArg ( line );
+ }
+ }
+#else
+ // Use 'count' to see if this option exists. Using 'find' will
+ // SEGV or hang if the option has not been defined yet.
+ if ( opts.count(key.c_str()) > 0 )
+ return string ( line ) + "\n";
+ else {
+ if ( allowUnknowns ) {
+ return string ( );
+ }
+ else {
+ badArg ( line );
+ }
+ }
+#endif
+ // Control will not arrive here, but the compiler things it could.
+ // Calls to badArg(), that I used above, throw.
+ return string();
+ }
+
+ const Options& opts;
+};
+
+}
+
+template QPID_COMMON_EXTERN po::value_semantic* create_value(bool& val, const std::string& arg);
+template QPID_COMMON_EXTERN po::value_semantic* create_value(int16_t& val, const std::string& arg);
+template QPID_COMMON_EXTERN po::value_semantic* create_value(int32_t& val, const std::string& arg);
+template QPID_COMMON_EXTERN po::value_semantic* create_value(int64_t& val, const std::string& arg);
+template QPID_COMMON_EXTERN po::value_semantic* create_value(uint16_t& val, const std::string& arg);
+template QPID_COMMON_EXTERN po::value_semantic* create_value(uint32_t& val, const std::string& arg);
+template QPID_COMMON_EXTERN po::value_semantic* create_value(uint64_t& val, const std::string& arg);
+#ifdef QPID_SIZE_T_DISTINCT
+template QPID_COMMON_EXTERN po::value_semantic* create_value(size_t& val, const std::string& arg);
+#endif
+template QPID_COMMON_EXTERN po::value_semantic* create_value(double& val, const std::string& arg);
+
+template QPID_COMMON_EXTERN po::value_semantic* create_value(string& val, const std::string& arg);
+template QPID_COMMON_EXTERN po::value_semantic* create_value(vector<string>& val, const std::string& arg);
+template QPID_COMMON_EXTERN po::value_semantic* create_value(vector<int>& val, const std::string& arg);
+
+template QPID_COMMON_EXTERN po::value_semantic* create_value(sys::Duration& val, const std::string& arg);
+
+
+po::value_semantic* optValue(bool& value) {
+#if (BOOST_VERSION >= 103500)
+ return create_value(value, "", true);
+#else
+ return po::bool_switch(&value);
+#endif
+}
+
+po::value_semantic* pure_switch(bool& value) {
+ return po::bool_switch(&value);
+}
+
+std::string prettyArg(const std::string& name, const std::string& value) {
+ return value.empty() ? name+" " : name+" ("+value+") ";
+}
+
+Options::Options(const string& name) :
+ poOptions(new po::options_description(name))
+{
+}
+
+void Options::parse(int argc, char const* const* argv, const std::string& configFile, bool allowUnknown)
+{
+ string defaultConfigFile = configFile; // May be changed by env/cmdline
+ string parsing;
+ try {
+ po::variables_map vm;
+ parsing="command line options";
+ if (argc > 0 && argv != 0) {
+ if (allowUnknown) {
+ // This hideous workaround is required because boost 1.33 has a bug
+ // that causes 'allow_unregistered' to not work.
+ po::command_line_parser clp = po::command_line_parser(argc, const_cast<char**>(argv)).
+ options(*poOptions).allow_unregistered();
+ po::parsed_options opts = clp.run();
+ po::parsed_options filtopts = clp.run();
+ filtopts.options.clear ();
+ for (std::vector< po::basic_option<char> >::iterator i = opts.options.begin();
+ i != opts.options.end(); i++)
+ if (!i->unregistered)
+ filtopts.options.push_back (*i);
+ po::store(filtopts, vm);
+
+ }
+ else
+ po::store(po::parse_command_line(argc, const_cast<char**>(argv), *poOptions), vm);
+ }
+ parsing="environment variables";
+ po::store(po::parse_environment(*poOptions, EnvOptMapper(*this)), vm);
+ po::notify(vm); // configFile may be updated from arg/env options.
+ if (!configFile.empty()) {
+ parsing="configuration file "+configFile;
+ ifstream conf(configFile.c_str());
+ conf.peek();
+ if (conf.good()) {
+ // Remove this hack when we get a stable version of boost that
+ // can allow unregistered options in config files.
+ EnvOptMapper mapper(*this);
+ stringstream filtered;
+
+ while (!conf.eof()) {
+ string line;
+ getline (conf, line);
+ filtered << mapper.configFileLine (line, allowUnknown);
+ }
+
+ po::store(po::parse_config_file(filtered, *poOptions), vm);
+ // End of hack
+ }
+ else {
+ // log the inability to read the configuration file
+ QPID_LOG(debug, "Config file not read: " << configFile);
+ // No error if default configfile is missing/unreadable
+ // but complain for non-default config file.
+ if (configFile != defaultConfigFile)
+ throw Exception("cannot read configuration file "
+ +configFile);
+ }
+ }
+ po::notify(vm);
+ }
+ catch (const std::exception& e) {
+ ostringstream msg;
+ msg << "Error in " << parsing << ": " << e.what() << endl;
+#if (BOOST_VERSION >= 103300)
+ if (find_nothrow("help", false))
+ msg << "Use --help to see valid options" << endl;
+#endif
+ throw Exception(msg.str());
+ }
+}
+
+options_description_easy_init::options_description_easy_init(po::options_description* o) :
+ owner(o)
+{}
+
+options_description_easy_init Options::addOptions()
+{
+ return options_description_easy_init(poOptions.get());
+}
+
+void Options::add(Options& o)
+{
+ poOptions->add(*o.poOptions);
+}
+
+const std::vector< boost::shared_ptr<po::option_description> >& Options::options() const
+{
+ return poOptions->options();
+}
+
+bool Options::find_nothrow(const std::string& s, bool b)
+{
+ return poOptions->find_nothrow(s, b);
+}
+
+bool Options::findArg(int argc, char const* const* argv, const std::string& theArg)
+{
+ const string parsing("command line options");
+ bool result(false);
+ try {
+ if (argc > 0 && argv != 0) {
+ po::command_line_parser clp = po::command_line_parser(argc, const_cast<char**>(argv)).
+ options(*poOptions).allow_unregistered();
+ po::parsed_options opts = clp.run();
+
+ for (std::vector< po::basic_option<char> >::iterator
+ i = opts.options.begin(); i != opts.options.end(); i++) {
+ if (theArg.compare(i->string_key) == 0) {
+ result = true;
+ break;
+ }
+ }
+ }
+ return result;
+ }
+ catch (const std::exception& e) {
+ ostringstream msg;
+ msg << "Error in " << parsing << ": " << e.what() << endl;
+ throw Exception(msg.str());
+ }
+}
+
+void Options::print(ostream& os)
+{
+ poOptions->print(os);
+}
+
+std::ostream& operator<<(std::ostream& os, const Options& options)
+{
+ return os << *(options.poOptions);
+}
+
+options_description_easy_init&
+options_description_easy_init::operator()(const char* name,
+ const po::value_semantic* s,
+ const char* description)
+{
+ owner->add(boost::shared_ptr<po::option_description>(new po::option_description(name, s, description)));
+ return *this;
+}
+
+
+CommonOptions::CommonOptions(const string& name, const string& configfile, const string& clientfile)
+: Options(name), config(configfile), clientConfig(clientfile)
+{
+ addOptions()
+ ("help,h", optValue(help), "Displays the help message")
+ ("version,v", optValue(version), "Displays version information")
+ ("config", optValue(config, "FILE"), "Reads configuration from FILE")
+ ("client-config", optValue(clientConfig, "FILE"), "Reads client configuration from FILE (for cluster interconnect)");
+}
+
+} // namespace qpid
+
diff --git a/qpid/cpp/src/qpid/Options.h b/qpid/cpp/src/qpid/Options.h
new file mode 100644
index 0000000000..ed4221fc90
--- /dev/null
+++ b/qpid/cpp/src/qpid/Options.h
@@ -0,0 +1,203 @@
+#ifndef QPID_COMMONOPTIONS_H
+#define QPID_COMMONOPTIONS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/Exception.h"
+
+// Disable warnings triggered by boost.
+#ifdef _MSC_VER
+# pragma warning(push)
+# pragma warning(disable : 4251 4275)
+#endif
+
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
+
+#include <sstream>
+#include <iterator>
+#include <algorithm>
+#include <string>
+#include <vector>
+#include <iosfwd>
+
+#include "qpid/CommonImportExport.h"
+
+namespace boost {
+namespace program_options {
+class value_semantic;
+class option_description;
+class options_description;
+}}
+
+namespace qpid {
+namespace po=boost::program_options;
+
+
+
+///@internal
+QPID_COMMON_EXTERN std::string prettyArg(const std::string&, const std::string&);
+
+///@internal
+template <class T>
+po::value_semantic* create_value(T& val, const std::string& arg);
+
+/** Create an option value.
+ * name, value appear after the option name in help like this:
+ * <name> (=<value>)
+ * T must support operator <<.
+ *@see Options for example of use.
+ */
+template<class T>
+po::value_semantic* optValue(T& value, const char* name) {
+ std::string valstr(boost::lexical_cast<std::string>(value));
+ return create_value(value, prettyArg(name, valstr));
+}
+
+/** Create a vector value. Multiple occurences of the option are
+ * accumulated into the vector
+ */
+template <class T>
+po::value_semantic* optValue(std::vector<T>& value, const char* name) {
+ std::ostringstream os;
+ std::copy(value.begin(), value.end(), std::ostream_iterator<T>(os, " "));
+ std::string val=os.str();
+ if (!val.empty())
+ val.erase(val.end()-1); // Remove trailing " "
+ return create_value(value, prettyArg(name, val));
+}
+
+/** Create a boolean switch value. Presence of the option sets the value. */
+QPID_COMMON_EXTERN po::value_semantic* optValue(bool& value);
+QPID_COMMON_EXTERN po::value_semantic* pure_switch(bool& value);
+
+/**
+ * Base class for options.
+ * Example of use:
+ @code
+ struct MySubOptions : public Options {
+ int x;
+ string y;
+ MySubOptions() : Options("Sub options") {
+ addOptions()
+ ("x", optValue(x,"XUNIT"), "Option X")
+ ("y", optValue(y, "YUNIT"), "Option Y");
+ }
+ };
+
+ struct MyOptions : public Options {
+ bool z;
+ vector<string> foo;
+ MySubOptions subOptions;
+ MyOptions() : Options("My Options") {
+ addOptions()
+ ("z", boolSwitch(z), "Option Z")
+ ("foo", optValue(foo), "Multiple option foo");
+ add(subOptions);
+ }
+
+ main(int argc, char** argv) {
+ Options opts;
+ opts.parse(argc, char** argv);
+ // Use values
+ dosomething(opts.subOptions.x);
+ if (error)
+ cout << opts << end; // Help message.
+ }
+
+ @endcode
+ */
+
+
+class options_description_easy_init {
+public:
+ QPID_COMMON_EXTERN options_description_easy_init(po::options_description* o);
+
+ QPID_COMMON_EXTERN options_description_easy_init&
+ operator()(const char* name,
+ const po::value_semantic* s,
+ const char* description);
+
+private:
+ po::options_description* owner;
+};
+
+
+struct Options {
+ friend QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream& os, const Options&);
+
+ struct Exception : public qpid::Exception {
+ Exception(const std::string& msg) : qpid::Exception(msg) {}
+ };
+
+ QPID_COMMON_EXTERN explicit Options(const std::string& name=std::string());
+
+ /**
+ * Parses options from argc/argv, environment variables and config file.
+ * Note the filename argument can reference an options variable that
+ * is updated by argc/argv or environment variable parsing.
+ */
+ QPID_COMMON_EXTERN void parse(int argc, char const* const* argv,
+ const std::string& configfile=std::string(),
+ bool allowUnknown = false);
+
+ /**
+ * Tests for presence of argc/argv switch
+ */
+ QPID_COMMON_EXTERN bool findArg(int argc, char const* const* argv,
+ const std::string& theArg);
+
+ QPID_COMMON_EXTERN options_description_easy_init addOptions();
+ QPID_COMMON_EXTERN void add(Options&);
+ QPID_COMMON_EXTERN const std::vector< boost::shared_ptr<po::option_description> >& options() const;
+ QPID_COMMON_EXTERN bool find_nothrow(const std::string&, bool);
+ QPID_COMMON_EXTERN void print(std::ostream& os);
+
+private:
+ boost::shared_ptr<po::options_description> poOptions;
+};
+
+QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream& os, const Options&);
+
+/**
+ * Standard options for configuration
+ */
+struct CommonOptions : public Options {
+ QPID_COMMON_EXTERN CommonOptions(const std::string& name=std::string(),
+ const std::string& configfile=std::string(),
+ const std::string& clientConfigFile=std::string());
+ bool help;
+ bool version;
+ std::string config;
+ std::string clientConfig;
+};
+
+
+
+
+} // namespace qpid
+
+#endif /*!QPID_COMMONOPTIONS_H*/
diff --git a/qpid/cpp/src/qpid/OptionsTemplates.h b/qpid/cpp/src/qpid/OptionsTemplates.h
new file mode 100644
index 0000000000..5e7f7b5e1d
--- /dev/null
+++ b/qpid/cpp/src/qpid/OptionsTemplates.h
@@ -0,0 +1,52 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <boost/program_options.hpp>
+
+#include <string>
+
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+
+namespace po=boost::program_options;
+
+template <class T>
+class OptValue : public po::typed_value<T> {
+public:
+ OptValue(T& val, const std::string& arg) :
+ po::typed_value<T>(&val),
+ argName(arg)
+ {}
+ std::string name() const { return argName; }
+
+private:
+ std::string argName;
+};
+
+template <class T>
+po::value_semantic* create_value(T& val, const std::string& arg) {
+ return new OptValue<T>(val, arg);
+}
+
+template <class T>
+po::value_semantic* create_value(T& val, const std::string& arg, const T& implicit_val) {
+ return (new OptValue<T>(val, arg))->implicit_value(implicit_val);
+}
+
+}
diff --git a/qpid/cpp/src/qpid/Plugin.cpp b/qpid/cpp/src/qpid/Plugin.cpp
new file mode 100644
index 0000000000..196b5c2333
--- /dev/null
+++ b/qpid/cpp/src/qpid/Plugin.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/Plugin.h"
+#include "qpid/Options.h"
+#include <boost/bind.hpp>
+#include <algorithm>
+
+namespace qpid {
+
+namespace {
+
+Plugin::Plugins& thePlugins() {
+ // This is a single threaded singleton implementation so
+ // it is important to be sure that the first use of this
+ // singleton is when the program is still single threaded
+ static Plugin::Plugins plugins;
+ return plugins;
+}
+
+void invoke(boost::function<void()> f) { f(); }
+
+} // namespace
+
+Plugin::Target::~Target() { finalize(); }
+
+void Plugin::Target::finalize() {
+ std::for_each(finalizers.begin(), finalizers.end(), invoke);
+ finalizers.clear();
+}
+
+void Plugin::Target::addFinalizer(const boost::function<void()>& f) {
+ finalizers.push_back(f);
+}
+
+namespace {
+bool initBefore(const Plugin* a, const Plugin* b) {
+ return a->initOrder() < b->initOrder();
+}
+}
+
+Plugin::Plugin() {
+ // Register myself.
+ thePlugins().push_back(this);
+ std::sort(thePlugins().begin(), thePlugins().end(), &initBefore);
+}
+
+Plugin::~Plugin() {}
+
+Options* Plugin::getOptions() { return 0; }
+
+const Plugin::Plugins& Plugin::getPlugins() { return thePlugins(); }
+
+namespace {
+template <class F> void each_plugin(const F& f) {
+ std::for_each(Plugin::getPlugins().begin(), Plugin::getPlugins().end(), f);
+}
+}
+
+void Plugin::addOptions(Options& opts) {
+ for (Plugins::const_iterator i = getPlugins().begin(); i != getPlugins().end(); ++i) {
+ if ((*i)->getOptions())
+ opts.add(*(*i)->getOptions());
+ }
+}
+
+int Plugin::initOrder() const { return DEFAULT_INIT_ORDER; }
+
+void Plugin::earlyInitAll(Target& t) {
+ each_plugin(boost::bind(&Plugin::earlyInitialize, _1, boost::ref(t)));
+}
+
+void Plugin::initializeAll(Target& t) {
+ each_plugin(boost::bind(&Plugin::initialize, _1, boost::ref(t)));
+}
+
+} // namespace qpid
diff --git a/qpid/cpp/src/qpid/Plugin.h b/qpid/cpp/src/qpid/Plugin.h
new file mode 100644
index 0000000000..4e057872b9
--- /dev/null
+++ b/qpid/cpp/src/qpid/Plugin.h
@@ -0,0 +1,129 @@
+#ifndef QPID_PLUGIN_H
+#define QPID_PLUGIN_H
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <boost/noncopyable.hpp>
+#include <boost/function.hpp>
+#include <vector>
+#include "qpid/CommonImportExport.h"
+
+/**@file Generic plug-in framework. */
+
+namespace qpid {
+struct Options;
+
+/**
+ * Plug-in base class.
+ */
+class Plugin : private boost::noncopyable {
+ public:
+ typedef std::vector<Plugin*> Plugins;
+ /** Default value returned by initOrder() */
+ static const int DEFAULT_INIT_ORDER=1000;
+
+ /**
+ * Base interface for targets that can receive plug-ins.
+ * Also allows plug-ins to attach a a function to be called
+ * when the target is 'finalized'.
+ */
+ class Target : private boost::noncopyable
+ {
+ public:
+ /** Calls finalize() if not already called. */
+ QPID_COMMON_EXTERN virtual ~Target();
+
+ /** Run all the finalizers */
+ QPID_COMMON_EXTERN void finalize();
+
+ /** Add a function to run when finalize() is called */
+ QPID_COMMON_EXTERN void addFinalizer(const boost::function<void()>&);
+
+ private:
+ std::vector<boost::function<void()> > finalizers;
+ };
+
+ /**
+ * Constructor registers the plug-in to appear in getPlugins().
+ *
+ * A concrete Plugin is instantiated as a global or static
+ * member variable in a library so it is registered during
+ * initialization when the library is loaded.
+ */
+ QPID_COMMON_EXTERN Plugin();
+
+ QPID_COMMON_EXTERN virtual ~Plugin();
+
+ /**
+ * Configuration options for the plugin.
+ * Then will be updated during option parsing by the host program.
+ *
+ * @return An options group or 0 for no options. Default returns 0.
+ * Plugin retains ownership of return value.
+ */
+ QPID_COMMON_EXTERN virtual Options* getOptions();
+
+ /**
+ * Initialize Plugin functionality on a Target, called before
+ * initializing the target.
+ *
+ * Plugins should ignore targets they don't recognize.
+ *
+ * Called before the target itself is initialized.
+ */
+ virtual void earlyInitialize(Target&) = 0;
+
+ /**
+ * Initialize Plugin functionality on a Target. Called after
+ * initializing the target.
+ *
+ * Plugins should ignore targets they don't recognize.
+ *
+ * Called after the target is fully initialized.
+ */
+ virtual void initialize(Target&) = 0;
+
+ /**
+ * Initialization order. If a plugin does not override this, it
+ * returns DEFAULT_INIT_ORDER. Plugins that need to be initialized
+ * earlier/later than normal can override initOrder to return
+ * a lower/higher value than DEFAULT_INIT_ORDER.
+ */
+ QPID_COMMON_EXTERN virtual int initOrder() const;
+
+ /** List of registered Plugin objects.
+ * Caller must not delete plugin pointers.
+ */
+ QPID_COMMON_EXTERN static const Plugins& getPlugins();
+
+ /** Call earlyInitialize() on all registered plugins */
+ QPID_COMMON_EXTERN static void earlyInitAll(Target&);
+
+ /** Call initialize() on all registered plugins */
+ QPID_COMMON_EXTERN static void initializeAll(Target&);
+
+ /** For each registered plugin, add plugin.getOptions() to opts. */
+ QPID_COMMON_EXTERN static void addOptions(Options& opts);
+};
+
+} // namespace qpid
+
+#endif /*!QPID_PLUGIN_H*/
diff --git a/qpid/cpp/src/qpid/RangeSet.h b/qpid/cpp/src/qpid/RangeSet.h
new file mode 100644
index 0000000000..20ee722fcb
--- /dev/null
+++ b/qpid/cpp/src/qpid/RangeSet.h
@@ -0,0 +1,330 @@
+#ifndef QPID_RANGESET_H
+#define QPID_RANGESET_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/InlineVector.h"
+#include <boost/iterator/iterator_facade.hpp>
+#include <boost/operators.hpp>
+#include <boost/bind.hpp>
+
+#include <algorithm>
+#include <iostream>
+#include <numeric>
+
+namespace qpid {
+
+/** A range of values, used in RangeSet.
+ * Range(begin, end) includes begin but excludes end.
+ * Range::makeClosed(first,last) includes both first and last.
+ */
+template <class T>
+class Range {
+ public:
+ static Range makeClosed(const T& first, T last) { return Range(first, ++last); }
+
+ Range() : begin_(), end_() {}
+ explicit Range(const T& t) : begin_(t), end_(t) { ++end_; }
+ Range(const T& b, const T& e) : begin_(b), end_(e) { assert(b <= e); }
+
+ T begin() const { return begin_; }
+ /** End of _open_ range, i.e. !contains(end()) */
+ T end() const { return end_; }
+
+ T first() const { assert(!empty()); return begin_; }
+ /** Last in closed range, i.e. contains(end()) */
+ T last() const { assert(!empty()); T ret=end_; return --ret; }
+
+ void begin(const T& t) { begin_ = t; }
+ void end(const T& t) { end_ = t; }
+ size_t size() const { return end_ - begin_; }
+ bool empty() const { return begin_ == end_; }
+
+ bool contains(const T& x) const { return begin_ <= x && x < end_; }
+ bool contains(const Range& r) const { return begin_ <= r.begin_ && r.end_ <= end_; }
+ bool strictContains(const Range& r) const { return begin_ < r.begin_ && r.end_ < end_; }
+
+ bool operator==(const Range& x) { return begin_ == x.begin_ && end_== x.end_; }
+
+ bool operator<(const T& t) const { return end_ < t; }
+ bool operator<(const Range<T>& r) const { return end_ < r.begin_; }
+
+ /** touching ranges can be merged into a single range. */
+ bool touching(const Range& r) const {
+ return std::max(begin_, r.begin_) <= std::min(end_, r.end_);
+ }
+
+ /** @pre touching */
+ void merge(const Range& r) {
+ assert(touching(r));
+ begin_ = std::min(begin_, r.begin_);
+ end_ = std::max(end_, r.end_);
+ }
+
+ operator bool() const { return !empty(); }
+
+ template <class S> void serialize(S& s) { s(begin_)(end_); }
+
+ private:
+ T begin_, end_;
+};
+
+
+/**
+ * A set implemented as a list of [begin, end) ranges.
+ * T must be LessThanComparable and Incrementable.
+ * RangeSet only provides const iterators.
+ */
+template <class T>
+class RangeSet
+ : private boost::additive1<RangeSet<T>,
+ boost::additive2<RangeSet<T>, Range<T>,
+ boost::additive2<RangeSet<T>, T> > >
+{
+ typedef InlineVector<Range<T>, 3> Ranges; // TODO aconway 2008-04-21: what's the optimial inlined value?
+
+ public:
+
+ class iterator : public boost::iterator_facade<
+ iterator,
+ const T,
+ boost::forward_traversal_tag>
+ {
+ public:
+ iterator() : ranges(), iter(), value() {}
+
+ private:
+ typedef typename Ranges::const_iterator RangesIter;
+ iterator(const Ranges& r, const RangesIter& i, const T& t)
+ : ranges(&r), iter(i), value(t) {}
+
+ void increment();
+ bool equal(const iterator& i) const;
+ const T& dereference() const { return value; }
+
+ const Ranges* ranges;
+ RangesIter iter;
+ T value;
+
+ friend class RangeSet<T>;
+ friend class boost::iterator_core_access;
+ };
+
+ typedef iterator const_iterator;
+
+ RangeSet() {}
+ explicit RangeSet(const Range<T>& r) { *this += r; }
+ RangeSet(const T& a, const T& b) { *this += Range<T>(a,b); }
+
+ bool contiguous() const { return ranges.size() <= 1; }
+
+ bool contains(const T& t) const;
+ bool contains(const Range<T>&) const;
+
+ /**@pre contiguous() */
+ Range<T> toRange() const;
+
+ bool operator==(const RangeSet<T>&) const;
+
+ void addRange (const Range<T>&);
+ void addSet (const RangeSet<T>&);
+
+ RangeSet<T>& operator+=(const T& t) { return *this += Range<T>(t); }
+ RangeSet<T>& operator+=(const Range<T>& r) { addRange(r); return *this; }
+ RangeSet<T>& operator+=(const RangeSet<T>& s) { addSet(s); return *this; }
+
+ void removeRange (const Range<T>&);
+ void removeSet (const RangeSet<T>&);
+
+ RangeSet<T>& operator-=(const T& t) { return *this -= Range<T>(t); }
+ RangeSet<T>& operator-=(const Range<T>& r) { removeRange(r); return *this; }
+ RangeSet<T>& operator-=(const RangeSet<T>& s) { removeSet(s); return *this; }
+
+ T front() const { return ranges.front().begin(); }
+ T back() const { return ranges.back().end(); }
+
+ // Iterate over elements in the set.
+ iterator begin() const;
+ iterator end() const;
+
+ // Iterate over ranges in the set.
+ typedef typename Ranges::const_iterator RangeIterator;
+ RangeIterator rangesBegin() const { return ranges.begin(); }
+ RangeIterator rangesEnd() const { return ranges.end(); }
+ size_t rangesSize() const { return ranges.size(); }
+
+ // The difference between the start and end of this range set
+ uint32_t span() const;
+
+ size_t size() const;
+ bool empty() const { return ranges.empty(); }
+ void clear() { ranges.clear(); }
+
+ /** Return the largest contiguous range containing x.
+ * Returns the empty range [x,x) if x is not in the set.
+ */
+ Range<T> rangeContaining(const T&) const;
+
+ template <class S> void serialize(S& s) { s.split(*this); s(ranges.begin(), ranges.end()); }
+ template <class S> void encode(S& s) const { s(uint16_t(ranges.size()*sizeof(Range<T>))); }
+ template <class S> void decode(S& s) { uint16_t sz; s(sz); ranges.resize(sz/sizeof(Range<T>)); }
+
+ private:
+ static size_t accumulateSize(size_t s, const Range<T>& r) { return s+r.size(); }
+ Ranges ranges;
+
+ template <class U> friend std::ostream& operator<<(std::ostream& o, const RangeSet<U>& r);
+
+ friend class iterator;
+};
+
+template <class T>
+std::ostream& operator<<(std::ostream& o, const Range<T>& r) {
+ return o << "[" << r.begin() << "," << r.end() << ")";
+}
+
+template <class T>
+std::ostream& operator<<(std::ostream& o, const RangeSet<T>& rs) {
+ std::ostream_iterator<Range<T> > i(o, " ");
+ o << "{ ";
+ std::copy(rs.ranges.begin(), rs.ranges.end(), i);
+ return o << "}";
+}
+
+template <class T>
+bool RangeSet<T>::contains(const T& t) const {
+ typename Ranges::const_iterator i =
+ std::lower_bound(ranges.begin(), ranges.end(), Range<T>(t));
+ return i != ranges.end() && i->contains(t);
+}
+
+template <class T>
+bool RangeSet<T>::contains(const Range<T>& r) const {
+ typename Ranges::const_iterator i =
+ std::lower_bound(ranges.begin(), ranges.end(), r);
+ return i != ranges.end() && i->contains(r);
+}
+
+template <class T> void RangeSet<T>::addRange(const Range<T>& r) {
+ if (r.empty()) return;
+ typename Ranges::iterator i = std::lower_bound(ranges.begin(), ranges.end(), r);
+ if (i == ranges.end() || !i->touching(r))
+ ranges.insert(i, r); // No overlap
+ else {
+ i->merge(r);
+ typename Ranges::iterator j = i;
+ while (++j != ranges.end() && i->touching(*j))
+ i->merge(*j);
+ ranges.erase(i+1,j);
+ }
+}
+
+
+template <class T> void RangeSet<T>::addSet(const RangeSet<T>& s) {
+ typedef RangeSet<T>& (RangeSet<T>::*RangeSetRangeOp)(const Range<T>&);
+ std::for_each(s.ranges.begin(), s.ranges.end(),
+ boost::bind((RangeSetRangeOp)&RangeSet<T>::operator+=, this, _1));
+}
+
+template <class T> void RangeSet<T>::removeRange(const Range<T>& r) {
+ if (r.empty()) return;
+ typename Ranges::iterator i,j;
+ i = std::lower_bound(ranges.begin(), ranges.end(), r);
+ if (i == ranges.end() || i->begin() >= r.end())
+ return; // Outside of set
+ if (*i == r) // Erase i
+ ranges.erase(i);
+ else if (i->strictContains(r)) { // Split i
+ Range<T> i1(i->begin(), r.begin());
+ Range<T> i2(r.end(), i->end());
+ *i = i2;
+ ranges.insert(i, i1);
+ } else {
+ if (i->begin() < r.begin()) { // Truncate i
+ i->end(r.begin());
+ ++i;
+ }
+ for (j = i; j != ranges.end() && r.contains(*j); ++j)
+ ; // Ranges to erase.
+ if (j != ranges.end() && r.end() > j->begin())
+ j->begin(r.end()); // Truncate j
+ ranges.erase(i,j);
+ }
+}
+
+template <class T> void RangeSet<T>::removeSet(const RangeSet<T>& r) {
+ std::for_each(
+ r.ranges.begin(), r.ranges.end(),
+ boost::bind(&RangeSet<T>::removeRange, this, _1));
+}
+
+template <class T> Range<T> RangeSet<T>::toRange() const {
+ assert(contiguous());
+ return empty() ? Range<T>() : ranges.front();
+}
+
+template <class T> void RangeSet<T>::iterator::increment() {
+ assert(ranges && iter != ranges->end());
+ if (!iter->contains(++value)) {
+ ++iter;
+ if (iter == ranges->end())
+ *this=iterator(); // end() iterator
+ else
+ value=iter->begin();
+ }
+}
+
+template <class T> bool RangeSet<T>::operator==(const RangeSet<T>& r) const {
+ return ranges.size() == r.ranges.size() && std::equal(ranges.begin(), ranges.end(), r.ranges.begin());
+}
+
+template <class T> typename RangeSet<T>::iterator RangeSet<T>::begin() const {
+ return empty() ? end() : iterator(ranges, ranges.begin(), front());
+}
+
+template <class T> typename RangeSet<T>::iterator RangeSet<T>::end() const {
+ return iterator();
+}
+
+template <class T> bool RangeSet<T>::iterator::equal(const iterator& i) const {
+ return ranges==i.ranges && (ranges==0 || value==i.value);
+}
+
+template <class T> Range<T> RangeSet<T>::rangeContaining(const T& t) const {
+ typename Ranges::const_iterator i =
+ std::lower_bound(ranges.begin(), ranges.end(), Range<T>(t));
+ return (i != ranges.end() && i->contains(t)) ? *i : Range<T>(t,t);
+}
+
+template <class T> uint32_t RangeSet<T>::span() const {
+ if (ranges.empty()) return 0;
+ return ranges.back().last() - ranges.front().first();
+}
+
+template <class T> size_t RangeSet<T>::size() const {
+ return std::accumulate(rangesBegin(), rangesEnd(), 0, &RangeSet<T>::accumulateSize);
+}
+
+} // namespace qpid
+
+
+#endif /*!QPID_RANGESET_H*/
diff --git a/qpid/cpp/src/qpid/RefCounted.h b/qpid/cpp/src/qpid/RefCounted.h
new file mode 100644
index 0000000000..c2ec367658
--- /dev/null
+++ b/qpid/cpp/src/qpid/RefCounted.h
@@ -0,0 +1,59 @@
+#ifndef QPID_REFCOUNTED_H
+#define QPID_REFCOUNTED_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <boost/utility.hpp>
+#include <boost/detail/atomic_count.hpp>
+
+namespace qpid {
+
+/**
+ * Reference-counted base class.
+ * Note: this class isn't copyable - you must copy the intrusive_ptr that points
+ * to the class that has mixed this in not the class itself (as that would sidestep
+ * the reference counting)
+ */
+class RefCounted : private boost::noncopyable {
+ mutable boost::detail::atomic_count count;
+
+public:
+ RefCounted() : count(0) {}
+ void addRef() const { ++count; }
+ void release() const { if (--count==0) released(); }
+ long refCount() { return count; }
+
+protected:
+ virtual ~RefCounted() {};
+ // Allow subclasses to over-ride behavior when refcount reaches 0.
+ virtual void released() const { delete this; }
+};
+
+
+// intrusive_ptr support.
+inline void intrusive_ptr_add_ref(const RefCounted* p) { p->addRef(); }
+inline void intrusive_ptr_release(const RefCounted* p) { p->release(); }
+
+} // namespace qpid
+
+
+#endif /*!QPID_REFCOUNTED_H*/
diff --git a/qpid/cpp/src/qpid/RefCountedBuffer.cpp b/qpid/cpp/src/qpid/RefCountedBuffer.cpp
new file mode 100644
index 0000000000..a82e1a02ab
--- /dev/null
+++ b/qpid/cpp/src/qpid/RefCountedBuffer.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/RefCountedBuffer.h"
+#include <stdlib.h>
+#include <new>
+
+namespace qpid {
+
+void RefCountedBuffer::released() const {
+ this->~RefCountedBuffer();
+ ::free (reinterpret_cast<void *>(const_cast<RefCountedBuffer *>(this)));
+}
+
+BufferRef RefCountedBuffer::create(size_t n) {
+ void* store=::malloc (n + sizeof(RefCountedBuffer));
+ if (NULL == store)
+ throw std::bad_alloc();
+ new(store) RefCountedBuffer;
+ char* start = reinterpret_cast<char *>(store) + sizeof(RefCountedBuffer);
+ return BufferRef(
+ boost::intrusive_ptr<RefCounted>(reinterpret_cast<RefCountedBuffer*>(store)),
+ start, start+n);
+}
+
+} // namespace qpid
+
+
diff --git a/qpid/cpp/src/qpid/RefCountedBuffer.h b/qpid/cpp/src/qpid/RefCountedBuffer.h
new file mode 100644
index 0000000000..f0ea86130b
--- /dev/null
+++ b/qpid/cpp/src/qpid/RefCountedBuffer.h
@@ -0,0 +1,44 @@
+#ifndef QPID_REFCOUNTEDBUFFER_H
+#define QPID_REFCOUNTEDBUFFER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <qpid/RefCounted.h>
+#include <qpid/BufferRef.h>
+
+namespace qpid {
+
+/**
+ * Reference-counted byte buffer. No alignment guarantees.
+ */
+class RefCountedBuffer : public RefCounted {
+ public:
+ /** Create a reference counted buffer of size n */
+ static BufferRef create(size_t n);
+
+ protected:
+ void released() const;
+};
+
+} // namespace qpid
+
+#endif /*!QPID_REFCOUNTEDBUFFER_H*/
diff --git a/qpid/cpp/src/qpid/Sasl.h b/qpid/cpp/src/qpid/Sasl.h
new file mode 100644
index 0000000000..1164fb5ec3
--- /dev/null
+++ b/qpid/cpp/src/qpid/Sasl.h
@@ -0,0 +1,61 @@
+#ifndef QPID_SASL_H
+#define QPID_SASL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <memory>
+#include <string>
+#include "qpid/CommonImportExport.h"
+#include "qpid/sys/IntegerTypes.h"
+
+namespace qpid {
+
+namespace sys {
+class SecurityLayer;
+struct SecuritySettings;
+}
+
+/**
+ * Interface to support for the SASL client role. This class is implemented by platform-specific
+ * SASL providers.
+ */
+class Sasl
+{
+ public:
+ /**
+ * Start SASL negotiation with the broker.
+ *
+ * @param mechanisms Comma-separated list of the SASL mechanism the
+ * client supports.
+ * @param externalSecuritySettings security related details from the underlying transport
+ */
+ virtual bool start(const std::string& mechanisms, std::string& response,
+ const qpid::sys::SecuritySettings* externalSecuritySettings = 0) = 0;
+ virtual std::string step(const std::string& challenge) = 0;
+ virtual std::string getMechanism() = 0;
+ virtual std::string getUserId() = 0;
+ virtual std::auto_ptr<qpid::sys::SecurityLayer> getSecurityLayer(uint16_t maxFrameSize) = 0;
+ QPID_COMMON_EXTERN virtual ~Sasl() {}
+};
+} // namespace qpid
+
+#endif /*!QPID_SASL_H*/
diff --git a/qpid/cpp/src/qpid/SaslFactory.cpp b/qpid/cpp/src/qpid/SaslFactory.cpp
new file mode 100644
index 0000000000..7bfc510936
--- /dev/null
+++ b/qpid/cpp/src/qpid/SaslFactory.cpp
@@ -0,0 +1,646 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/SaslFactory.h"
+#include "qpid/SaslServer.h"
+#include "qpid/NullSaslClient.h"
+#include "qpid/NullSaslServer.h"
+#include <map>
+#include <string.h>
+
+#include "config.h"
+
+#ifndef HAVE_SASL
+
+namespace qpid {
+
+//Null implementation
+
+
+SaslFactory::SaslFactory() {}
+
+SaslFactory::~SaslFactory() {}
+
+SaslFactory& SaslFactory::getInstance()
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ if (!instance.get()) {
+ instance = std::auto_ptr<SaslFactory>(new SaslFactory());
+ }
+ return *instance;
+}
+
+std::auto_ptr<Sasl> SaslFactory::create(const std::string& username, const std::string& password, const std::string&, const std::string&, int, int, bool)
+{
+ std::auto_ptr<Sasl> client(new NullSaslClient(username, password));
+ return client;
+}
+
+std::auto_ptr<SaslServer> SaslFactory::createServer(const std::string& realm, const std::string& /*service*/, bool /*encryptionRequired*/, const qpid::sys::SecuritySettings&)
+{
+ std::auto_ptr<SaslServer> server(new NullSaslServer(realm));
+ return server;
+}
+
+qpid::sys::Mutex SaslFactory::lock;
+std::auto_ptr<SaslFactory> SaslFactory::instance;
+
+} // namespace qpid
+
+#else
+
+#include "qpid/Exception.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/sys/SecurityLayer.h"
+#include "qpid/sys/SecuritySettings.h"
+#include "qpid/sys/cyrus/CyrusSecurityLayer.h"
+#include "qpid/log/Statement.h"
+#include <sasl/sasl.h>
+#include <strings.h>
+
+namespace qpid {
+
+using qpid::sys::SecurityLayer;
+using qpid::sys::SecuritySettings;
+using qpid::sys::cyrus::CyrusSecurityLayer;
+using qpid::framing::InternalErrorException;
+
+const size_t MAX_LOGIN_LENGTH = 50;
+
+struct CyrusSaslSettings
+{
+ CyrusSaslSettings ( ) :
+ username ( std::string(0) ),
+ password ( std::string(0) ),
+ service ( std::string(0) ),
+ host ( std::string(0) ),
+ minSsf ( 0 ),
+ maxSsf ( 0 )
+ {
+ }
+
+ CyrusSaslSettings ( const std::string & user, const std::string & password, const std::string & service, const std::string & host, int minSsf, int maxSsf ) :
+ username(user),
+ password(password),
+ service(service),
+ host(host),
+ minSsf(minSsf),
+ maxSsf(maxSsf)
+ {
+ }
+
+ std::string username,
+ password,
+ service,
+ host;
+
+ int minSsf,
+ maxSsf;
+};
+
+
+class CyrusSasl : public Sasl
+{
+ public:
+ CyrusSasl(const std::string & username, const std::string & password, const std::string & serviceName, const std::string & hostName, int minSsf, int maxSsf, bool allowInteraction);
+ ~CyrusSasl();
+ bool start(const std::string& mechanisms, std::string& response, const SecuritySettings* externalSettings);
+ std::string step(const std::string& challenge);
+ std::string getMechanism();
+ std::string getUserId();
+ std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize);
+ private:
+ sasl_conn_t* conn;
+ sasl_callback_t callbacks[5];//realm, user, authname, password, end-of-list
+ CyrusSaslSettings settings;
+ std::string input;
+ std::string mechanism;
+ char login[MAX_LOGIN_LENGTH];
+
+ /* In some contexts, like running in the broker or as a daemon, console
+ * interaction is impossible. In those cases, we will treat the attempt
+ * to interact as an error. */
+ bool allowInteraction;
+ void interact(sasl_interact_t* client_interact);
+};
+
+//sasl callback functions
+int getUserFromSettings(void *context, int id, const char **result, unsigned *len);
+int getPasswordFromSettings(sasl_conn_t *conn, void *context, int id, sasl_secret_t **psecret);
+typedef int CallbackProc();
+
+qpid::sys::Mutex SaslFactory::lock;
+std::auto_ptr<SaslFactory> SaslFactory::instance;
+
+class CyrusSaslServer : public SaslServer
+{
+ public:
+ CyrusSaslServer(const std::string& realm, const std::string& service, bool encryptionRequired, const qpid::sys::SecuritySettings& external);
+ ~CyrusSaslServer();
+ Status start(const std::string& mechanism, const std::string* response, std::string& challenge);
+ Status step(const std::string* response, std::string& challenge);
+ std::string getMechanisms();
+ std::string getUserid();
+ std::auto_ptr<qpid::sys::SecurityLayer> getSecurityLayer(size_t);
+ private:
+ std::string realm;
+ std::string service;
+ std::string userid;
+ sasl_conn_t *sasl_conn;
+};
+
+SaslFactory::SaslFactory()
+{
+ sasl_callback_t* callbacks = 0;
+ int result = sasl_client_init(callbacks);
+ if (result != SASL_OK) {
+ throw InternalErrorException(QPID_MSG("Sasl error: " << sasl_errstring(result, 0, 0)));
+ }
+}
+
+SaslFactory::~SaslFactory()
+{
+ sasl_done();
+}
+
+SaslFactory& SaslFactory::getInstance()
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ if (!instance.get()) {
+ instance = std::auto_ptr<SaslFactory>(new SaslFactory());
+ }
+ return *instance;
+}
+
+std::auto_ptr<Sasl> SaslFactory::create(const std::string & username, const std::string & password, const std::string & serviceName, const std::string & hostName, int minSsf, int maxSsf, bool allowInteraction)
+{
+ std::auto_ptr<Sasl> sasl(new CyrusSasl(username, password, serviceName, hostName, minSsf, maxSsf, allowInteraction));
+ return sasl;
+}
+
+std::auto_ptr<SaslServer> SaslFactory::createServer(const std::string& realm, const std::string& service, bool encryptionRequired, const qpid::sys::SecuritySettings& external)
+{
+ std::auto_ptr<SaslServer> server(new CyrusSaslServer(realm, service, encryptionRequired, external));
+ return server;
+}
+
+CyrusSasl::CyrusSasl(const std::string & username, const std::string & password, const std::string & serviceName, const std::string & hostName, int minSsf, int maxSsf, bool allowInteraction)
+ : conn(0), settings(username, password, serviceName, hostName, minSsf, maxSsf), allowInteraction(allowInteraction)
+{
+ size_t i = 0;
+
+ callbacks[i].id = SASL_CB_GETREALM;
+ callbacks[i].proc = 0;
+ callbacks[i++].context = 0;
+
+ if (!settings.username.empty()) {
+ callbacks[i].id = SASL_CB_AUTHNAME;
+ callbacks[i].proc = (CallbackProc*) &getUserFromSettings;
+ callbacks[i++].context = &settings;
+
+ callbacks[i].id = SASL_CB_PASS;
+ if (settings.password.empty()) {
+ callbacks[i].proc = 0;
+ callbacks[i++].context = 0;
+ } else {
+ callbacks[i].proc = (CallbackProc*) &getPasswordFromSettings;
+ callbacks[i++].context = &settings;
+ }
+ }
+
+
+ callbacks[i].id = SASL_CB_LIST_END;
+ callbacks[i].proc = 0;
+ callbacks[i++].context = 0;
+}
+
+CyrusSasl::~CyrusSasl()
+{
+ if (conn) {
+ sasl_dispose(&conn);
+ }
+}
+
+namespace {
+ const std::string SSL("ssl");
+}
+
+bool CyrusSasl::start(const std::string& mechanisms, std::string& response, const SecuritySettings* externalSettings)
+{
+ QPID_LOG(debug, "CyrusSasl::start(" << mechanisms << ")");
+ int result = sasl_client_new(settings.service.c_str(),
+ settings.host.c_str(),
+ 0, 0, /* Local and remote IP address strings */
+ callbacks,
+ 0, /* security flags */
+ &conn);
+
+ if (result != SASL_OK) throw InternalErrorException(QPID_MSG("Sasl error: " << sasl_errdetail(conn)));
+
+ sasl_security_properties_t secprops;
+
+ if (externalSettings) {
+ sasl_ssf_t external_ssf = (sasl_ssf_t) externalSettings->ssf;
+ if (external_ssf) {
+ int result = sasl_setprop(conn, SASL_SSF_EXTERNAL, &external_ssf);
+ if (result != SASL_OK) {
+ throw framing::InternalErrorException(QPID_MSG("SASL error: unable to set external SSF: " << result));
+ }
+ QPID_LOG(debug, "external SSF detected and set to " << external_ssf);
+ }
+ if (externalSettings->authid.size()) {
+ const char* external_authid = externalSettings->authid.c_str();
+ result = sasl_setprop(conn, SASL_AUTH_EXTERNAL, external_authid);
+ if (result != SASL_OK) {
+ throw framing::InternalErrorException(QPID_MSG("SASL error: unable to set external auth: " << result));
+ }
+ QPID_LOG(debug, "external auth detected and set to " << external_authid);
+ }
+ }
+
+ secprops.min_ssf = settings.minSsf;
+ secprops.max_ssf = settings.maxSsf;
+ secprops.maxbufsize = 65535;
+
+ QPID_LOG(debug, "min_ssf: " << secprops.min_ssf << ", max_ssf: " << secprops.max_ssf);
+
+ secprops.property_names = 0;
+ secprops.property_values = 0;
+ secprops.security_flags = 0;//TODO: provide means for application to configure these
+
+ result = sasl_setprop(conn, SASL_SEC_PROPS, &secprops);
+ if (result != SASL_OK) {
+ throw framing::InternalErrorException(QPID_MSG("SASL error: " << sasl_errdetail(conn)));
+ }
+
+ sasl_interact_t* client_interact = 0;
+ const char *out = 0;
+ unsigned outlen = 0;
+ const char *chosenMechanism = 0;
+
+ do {
+ result = sasl_client_start(conn,
+ mechanisms.c_str(),
+ &client_interact,
+ &out,
+ &outlen,
+ &chosenMechanism);
+
+ if (result == SASL_INTERACT) {
+ interact(client_interact);
+ }
+ } while (result == SASL_INTERACT);
+
+ if (result == SASL_NOMECH) {
+ if (mechanisms.size()) {
+ throw qpid::Exception(std::string("Can't authenticate using ") + mechanisms);
+ } else {
+ throw qpid::Exception("No mutually acceptable authentication mechanism");
+ }
+ } else if (result != SASL_CONTINUE && result != SASL_OK) {
+ throw InternalErrorException(QPID_MSG("Sasl error: " << sasl_errdetail(conn)));
+ }
+
+ mechanism = std::string(chosenMechanism);
+ QPID_LOG(debug, "CyrusSasl::start(" << mechanisms << "): selected "
+ << mechanism << " response: '" << std::string(out, outlen) << "'");
+ if (out) {
+ response = std::string(out, outlen);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+std::string CyrusSasl::step(const std::string& challenge)
+{
+ sasl_interact_t* client_interact = 0;
+ const char *out = 0;
+ unsigned outlen = 0;
+ int result = 0;
+ do {
+ result = sasl_client_step(conn, /* our context */
+ challenge.data(), /* the data from the server */
+ challenge.size(), /* it's length */
+ &client_interact, /* this should be
+ unallocated and NULL */
+ &out, /* filled in on success */
+ &outlen); /* filled in on success */
+
+ if (result == SASL_INTERACT) {
+ interact(client_interact);
+ }
+ } while (result == SASL_INTERACT);
+
+ std::string response;
+ if (result == SASL_CONTINUE || result == SASL_OK) response = std::string(out, outlen);
+ else if (result != SASL_OK) {
+ throw InternalErrorException(QPID_MSG("Sasl error: " << sasl_errdetail(conn)));
+ }
+ QPID_LOG(debug, "CyrusSasl::step(" << challenge << "): " << response);
+ return response;
+}
+
+std::string CyrusSasl::getMechanism()
+{
+ return mechanism;
+}
+
+std::string CyrusSasl::getUserId()
+{
+ int propResult;
+ const void* operName;
+
+ propResult = sasl_getprop(conn, SASL_USERNAME, &operName);
+ if (propResult == SASL_OK)
+ return std::string((const char*) operName);
+
+ return std::string();
+}
+
+void CyrusSasl::interact(sasl_interact_t* client_interact)
+{
+
+ /*
+ In some context console interaction cannot be allowed, such
+ as when this code run as part of a broker, or as a some other
+ daemon. In those cases we will treat the attempt to
+ */
+ if ( ! allowInteraction ) {
+ throw InternalErrorException("interaction disallowed");
+ }
+
+ if (client_interact->id == SASL_CB_PASS) {
+ char* password = getpass(client_interact->prompt);
+ input = std::string(password);
+ client_interact->result = input.data();
+ client_interact->len = input.size();
+ } else {
+ std::cout << client_interact->prompt;
+ if (client_interact->defresult) std::cout << " (" << client_interact->defresult << ")";
+ std::cout << ": ";
+ if (std::cin >> input) {
+ client_interact->result = input.data();
+ client_interact->len = input.size();
+ }
+ }
+
+}
+
+std::auto_ptr<SecurityLayer> CyrusSasl::getSecurityLayer(uint16_t maxFrameSize)
+{
+ const void* value(0);
+ int result = sasl_getprop(conn, SASL_SSF, &value);
+ if (result != SASL_OK) {
+ throw framing::InternalErrorException(QPID_MSG("SASL error: " << sasl_errdetail(conn)));
+ }
+ uint ssf = *(reinterpret_cast<const unsigned*>(value));
+ std::auto_ptr<SecurityLayer> securityLayer;
+ if (ssf) {
+ QPID_LOG(info, "Installing security layer, SSF: "<< ssf);
+ securityLayer = std::auto_ptr<SecurityLayer>(new CyrusSecurityLayer(conn, maxFrameSize, ssf));
+ }
+ return securityLayer;
+}
+
+CyrusSaslServer::CyrusSaslServer(const std::string& r, const std::string& s, bool encryptionRequired, const qpid::sys::SecuritySettings& external) : realm(r), service(s), sasl_conn(0)
+{
+ int code = sasl_server_new(service.c_str(), /* Service name */
+ NULL, /* Server FQDN, gethostname() */
+ realm.c_str(), /* Authentication realm */
+ NULL, /* Local IP, needed for some mechanism */
+ NULL, /* Remote IP, needed for some mechanism */
+ NULL, /* Callbacks */
+ 0, /* Connection flags */
+ &sasl_conn);
+
+ if (SASL_OK != code) {
+ QPID_LOG(error, "SASL: Connection creation failed: [" << code << "] " << sasl_errdetail(sasl_conn));
+
+ // TODO: Change this to an exception signaling
+ // server error, when one is available
+ throw qpid::framing::ConnectionForcedException("Unable to perform authentication");
+ }
+
+ sasl_security_properties_t secprops;
+
+ //TODO: should the actual SSF values be configurable here?
+ secprops.min_ssf = encryptionRequired ? 10: 0;
+ secprops.max_ssf = 256;
+
+ // If the transport provides encryption, notify the SASL library of
+ // the key length and set the ssf range to prevent double encryption.
+ QPID_LOG(debug, "External ssf=" << external.ssf << " and auth=" << external.authid);
+ sasl_ssf_t external_ssf = (sasl_ssf_t) external.ssf;
+ if (external_ssf) {
+ int result = sasl_setprop(sasl_conn, SASL_SSF_EXTERNAL, &external_ssf);
+ if (result != SASL_OK) {
+ throw framing::InternalErrorException(QPID_MSG("SASL error: unable to set external SSF: " << result));
+ }
+
+ secprops.max_ssf = secprops.min_ssf = 0;
+ }
+
+ QPID_LOG(debug, "min_ssf: " << secprops.min_ssf <<
+ ", max_ssf: " << secprops.max_ssf <<
+ ", external_ssf: " << external_ssf );
+
+ if (!external.authid.empty()) {
+ const char* external_authid = external.authid.c_str();
+ int result = sasl_setprop(sasl_conn, SASL_AUTH_EXTERNAL, external_authid);
+ if (result != SASL_OK) {
+ throw framing::InternalErrorException(QPID_MSG("SASL error: unable to set external auth: " << result));
+ }
+
+ QPID_LOG(debug, "external auth detected and set to " << external_authid);
+ }
+ secprops.maxbufsize = 65535;
+ secprops.property_names = 0;
+ secprops.property_values = 0;
+ secprops.security_flags = 0; /* or SASL_SEC_NOANONYMOUS etc as appropriate */
+ /*
+ * The nodict flag restricts SASL authentication mechanisms
+ * to those that are not susceptible to dictionary attacks.
+ * They are:
+ * SRP
+ * PASSDSS-3DES-1
+ * EXTERNAL
+ */
+ if (external.nodict) secprops.security_flags |= SASL_SEC_NODICTIONARY;
+ int result = sasl_setprop(sasl_conn, SASL_SEC_PROPS, &secprops);
+ if (result != SASL_OK) {
+ throw framing::InternalErrorException(QPID_MSG("SASL error: " << result));
+ }
+}
+
+CyrusSaslServer::~CyrusSaslServer()
+{
+ if (sasl_conn) {
+ sasl_dispose(&sasl_conn);
+ sasl_conn = 0;
+ }
+}
+
+CyrusSaslServer::Status CyrusSaslServer::start(const std::string& mechanism, const std::string* response, std::string& chllng)
+{
+ const char *challenge;
+ unsigned int challenge_len;
+
+ // This should be at same debug level as mech list in getMechanisms().
+ QPID_LOG(info, "SASL: Starting authentication with mechanism: " << mechanism);
+ int code = sasl_server_start(sasl_conn,
+ mechanism.c_str(),
+ (response ? response->c_str() : 0), (response ? response->size() : 0),
+ &challenge, &challenge_len);
+ switch (code) {
+ case SASL_OK:
+ return SaslServer::OK;
+ case SASL_CONTINUE:
+ chllng = std::string(challenge, challenge_len);
+ return SaslServer::CHALLENGE;
+ case SASL_NOMECH:
+ QPID_LOG(info, "Unsupported mechanism: " << mechanism);
+ default:
+ return SaslServer::FAIL;
+ }
+}
+
+CyrusSaslServer::Status CyrusSaslServer::step(const std::string* response, std::string& chllng)
+{
+ const char *challenge;
+ unsigned int challenge_len;
+
+ int code = sasl_server_step(sasl_conn,
+ (response ? response->c_str() : 0), (response ? response->size() : 0),
+ &challenge, &challenge_len);
+
+ switch (code) {
+ case SASL_OK:
+ return SaslServer::OK;
+ case SASL_CONTINUE:
+ chllng = std::string(challenge, challenge_len);
+ return SaslServer::CHALLENGE;
+ default:
+ return SaslServer::FAIL;
+ }
+
+}
+std::string CyrusSaslServer::getMechanisms()
+{
+ const char *separator = " ";
+ const char *list;
+ unsigned int list_len;
+ int count;
+
+ int code = sasl_listmech(sasl_conn, NULL,
+ "", separator, "",
+ &list, &list_len,
+ &count);
+
+ if (SASL_OK != code) {
+ QPID_LOG(info, "SASL: Mechanism listing failed: " << sasl_errdetail(sasl_conn));
+
+ // TODO: Change this to an exception signaling
+ // server error, when one is available
+ throw qpid::framing::ConnectionForcedException("Mechanism listing failed");
+ } else {
+ std::string mechanisms(list, list_len);
+ QPID_LOG(info, "SASL: Mechanism list: " << mechanisms);
+ return mechanisms;
+ }
+}
+std::string CyrusSaslServer::getUserid()
+{
+ const void* ptr;
+ int code = sasl_getprop(sasl_conn, SASL_USERNAME, &ptr);
+ if (SASL_OK == code) {
+ userid = static_cast<const char*>(ptr);
+ } else {
+ QPID_LOG(warning, "Failed to retrieve sasl username");
+ }
+ return userid;
+}
+
+std::auto_ptr<SecurityLayer> CyrusSaslServer::getSecurityLayer(size_t maxFrameSize)
+{
+ const void* value(0);
+ int result = sasl_getprop(sasl_conn, SASL_SSF, &value);
+ if (result != SASL_OK) {
+ throw framing::InternalErrorException(QPID_MSG("SASL error: " << sasl_errdetail(sasl_conn)));
+ }
+ uint ssf = *(reinterpret_cast<const unsigned*>(value));
+ std::auto_ptr<SecurityLayer> securityLayer;
+ if (ssf) {
+ securityLayer = std::auto_ptr<SecurityLayer>(new CyrusSecurityLayer(sasl_conn, maxFrameSize, ssf));
+ }
+ return securityLayer;
+}
+
+int getUserFromSettings(void* context, int /*id*/, const char** result, unsigned* /*len*/)
+{
+ if (context) {
+ *result = ((CyrusSaslSettings*) context)->username.c_str();
+ QPID_LOG(debug, "getUserFromSettings(): " << (*result));
+ return SASL_OK;
+ } else {
+ return SASL_FAIL;
+ }
+}
+
+namespace {
+// Global map of secrets allocated for SASL connections via callback
+// to getPasswordFromSettings. Ensures secrets are freed.
+class SecretsMap {
+ typedef std::map<sasl_conn_t*, void*> Map;
+ Map map;
+ sys::Mutex lock;
+ public:
+ void keep(sasl_conn_t* conn, void* secret) {
+ sys::Mutex::ScopedLock l(lock);
+ Map::iterator i = map.find(conn);
+ if (i != map.end()) free(i->second);
+ map[conn] = secret;
+ }
+
+ ~SecretsMap() {
+ for (Map::iterator i = map.begin(); i != map.end(); ++i)
+ free(i->second);
+ }
+};
+SecretsMap getPasswordFromSettingsSecrets;
+}
+
+int getPasswordFromSettings(sasl_conn_t* conn, void* context, int /*id*/, sasl_secret_t** psecret)
+{
+ if (context) {
+ size_t length = ((CyrusSaslSettings*) context)->password.size();
+ sasl_secret_t* secret = (sasl_secret_t*) malloc(sizeof(sasl_secret_t) + length);
+ getPasswordFromSettingsSecrets.keep(conn, secret);
+ secret->len = length;
+ memcpy(secret->data, ((CyrusSaslSettings*) context)->password.data(), length);
+ *psecret = secret;
+ return SASL_OK;
+ } else {
+ return SASL_FAIL;
+ }
+}
+} // namespace qpid
+
+#endif
diff --git a/qpid/cpp/src/qpid/SaslFactory.h b/qpid/cpp/src/qpid/SaslFactory.h
new file mode 100644
index 0000000000..39a9d74fd0
--- /dev/null
+++ b/qpid/cpp/src/qpid/SaslFactory.h
@@ -0,0 +1,49 @@
+#ifndef QPID_SASLFACTORY_H
+#define QPID_SASLFACTORY_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/CommonImportExport.h"
+#include "qpid/Sasl.h"
+#include "qpid/sys/Mutex.h"
+#include <memory>
+
+namespace qpid {
+class SaslServer;
+/**
+ * Factory for instances of the Sasl interface through which Sasl
+ * support is provided to a ConnectionHandler.
+ */
+class SaslFactory
+{
+ public:
+ QPID_COMMON_EXTERN std::auto_ptr<Sasl> create(const std::string & userName, const std::string & password, const std::string & serviceName, const std::string & hostName, int minSsf, int maxSsf, bool allowInteraction=true );
+ QPID_COMMON_EXTERN std::auto_ptr<SaslServer> createServer(const std::string& realm, const std::string& service, bool encryptionRequired, const qpid::sys::SecuritySettings&);
+ QPID_COMMON_EXTERN static SaslFactory& getInstance();
+ QPID_COMMON_EXTERN ~SaslFactory();
+ private:
+ SaslFactory();
+ static qpid::sys::Mutex lock;
+ static std::auto_ptr<SaslFactory> instance;
+};
+} // namespace qpid
+
+#endif /*!QPID_SASLFACTORY_H*/
diff --git a/qpid/cpp/src/qpid/SaslServer.h b/qpid/cpp/src/qpid/SaslServer.h
new file mode 100644
index 0000000000..88909c69a9
--- /dev/null
+++ b/qpid/cpp/src/qpid/SaslServer.h
@@ -0,0 +1,48 @@
+#ifndef QPID_SASLSERVER_H
+#define QPID_SASLSERVER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <string>
+#include <memory>
+
+namespace qpid {
+namespace sys {
+class SecurityLayer;
+}
+/**
+ *
+ */
+class SaslServer
+{
+ public:
+ typedef enum {OK, FAIL, CHALLENGE} Status;
+ QPID_COMMON_EXTERN virtual ~SaslServer() {}
+ virtual Status start(const std::string& mechanism, const std::string* response, std::string& challenge) = 0;
+ virtual Status step(const std::string* response, std::string& challenge) = 0;
+ virtual std::string getMechanisms() = 0;
+ virtual std::string getUserid() = 0;
+ virtual std::auto_ptr<qpid::sys::SecurityLayer> getSecurityLayer(size_t) = 0;
+ private:
+};
+} // namespace qpid
+
+#endif /*!QPID_SASLSERVER_H*/
diff --git a/qpid/cpp/src/qpid/Serializer.h b/qpid/cpp/src/qpid/Serializer.h
new file mode 100644
index 0000000000..a8ded9f5e0
--- /dev/null
+++ b/qpid/cpp/src/qpid/Serializer.h
@@ -0,0 +1,197 @@
+#ifndef QPID_SERIALIZER_H
+#define QPID_SERIALIZER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <limits>
+#include <algorithm>
+#include "qpid/Exception.h" // FIXME aconway 2008-04-03: proper exception class.
+
+namespace qpid {
+
+/**
+ * Overload for types that do not provide a serialize() member.
+ * It should retrun a wrapper holding a reference to t that implements
+ * serialize()
+ */
+template <class T> T& serializable(T& t) { return t; }
+
+/** Serialize std::pair */
+template <class T, class U> struct SerializablePair {
+ std::pair<T,U>& value;
+ SerializablePair(std::pair<T,U>& x) : value(x) {}
+ template <class S> void serialize(S& s) { s(value.first)(value.second); }
+};
+
+template <class T, class U>
+SerializablePair<T,U> serializable(std::pair<T,U>& p) {
+ return SerializablePair<T,U>(p);
+}
+
+/**
+ * Base class for all serializers.
+ * Derived serializers inherit from either Encoder or Decoder.
+ * Serializers can be used as functors or static_visitors.
+ */
+template <class Derived> class Serializer {
+ public:
+ /** Temporarily set a lower relative limit on the serializer */
+ class ScopedLimit {
+ public:
+ ScopedLimit(Serializer& s, size_t l)
+ : serializer(s), save(serializer.setLimit(l)) {}
+
+ ~ScopedLimit() { serializer.setAbsLimit(save); }
+
+ private:
+ Serializer& serializer;
+ size_t save;
+ };
+
+ static size_t maxLimit() { return std::numeric_limits<size_t>::max(); }
+
+ Serializer() : bytes(0), limit(maxLimit()) {}
+
+ typedef Derived& result_type; // unary functor requirement.
+
+ /** Wrapper functor to pass serializer functors by reference. */
+ template <class S> struct Ref {
+ typedef typename S::result_type result_type;
+ S& s;
+ Ref(S& ss) : s(ss) {}
+ template <class T> result_type operator()(T& x) { return s(x); }
+ template <class T> result_type operator()(const T& x) { return s(x); }
+ };
+
+ /** Reference wrapper to pass serializers by reference,
+ * e.g. to std:: functions that take functors.
+ */
+ template <class S> static Ref<S> ref(S& s) { return Ref<S>(s); }
+
+ /** Generic rule to serialize an iterator range */
+ template <class Iter> Derived& operator()(Iter begin, Iter end) {
+ std::for_each(begin, end, ref(this->self()));
+ return self();
+ }
+
+ /** Set limit relative to current position.
+ * @return old absolute limit.
+ */
+ size_t setLimit(size_t n) {
+ size_t l=limit;
+ limit = bytes+n;
+ return l;
+ }
+
+ /** Get the max number of bytes that can be processed under the
+ * current limit.
+ */
+ size_t bytesRemaining() const {
+ return limit - bytes;
+ }
+ /** Set absolute limit. */
+ void setAbsLimit(size_t n) {
+ limit = n;
+ if (bytes > limit)
+ throw Exception("Framing error: data overrun"); // FIXME aconway 2008-04-03: proper exception.
+ }
+
+ protected:
+ Derived& self() { return *static_cast<Derived*>(this); }
+ void addBytes(size_t n) {
+ size_t newBytes=bytes+n;
+ if (newBytes > limit)
+ throw Exception("Framing error: data overrun"); // FIXME aconway 2008-04-03: proper exception.
+ bytes = newBytes;
+ }
+
+ private:
+ void checkLimit() {
+ }
+
+ size_t bytes; // how many bytes serialized.
+ size_t limit; // bytes may not exceed this limit.
+};
+
+/**
+ * Base class for encoders, provides generic encode functions.
+ *
+ * A derived encoder must provide operator(const T&) to encode all
+ * primitive types T.
+ */
+template <class Derived> class EncoderBase : public Serializer<Derived> {
+ public:
+ using Serializer<Derived>::operator();
+ using Serializer<Derived>::self;
+
+ /** Default op() for non-primitive types. */
+ template <class T> Derived& operator()(const T& t) {
+ serializable(const_cast<T&>(t)).serialize(self()); return self();
+ }
+
+ /** Split serialize() into encode()/decode() */
+ template <class T> Derived& split(const T& t) {
+ t.encode(self()); return self();
+ }
+};
+
+/**
+ * Base class for decoders, provides generic decode functions.
+ *
+ * A derived encoder must provide operator(T&) to encode all
+ * primitive types T.
+ */
+template <class Derived> class DecoderBase : public Serializer<Derived> {
+ public:
+ using Serializer<Derived>::operator();
+ using Serializer<Derived>::self;
+
+ /** Default op() for non-primitive types. */
+ template <class T> Derived& operator()(T& t) {
+
+ serializable(t).serialize(self()); return self();
+ }
+
+ /** Split serialize() into encode()/decode() */
+ template <class T> Derived& split(T& t) {
+ t.decode(self()); return self();
+ }
+};
+
+/** Serialize a type by converting it to/from another type.
+ * To serialize type Foo by converting to/from type Bar create
+ * a serializable() overload like this:
+ *
+ * SerializeAs<Foo,Bar> serializable(Foo& t) { return SerializeAs<Foo,Bar>(t); }
+ */
+template <class Type, class AsType>
+struct SerializeAs {
+ Type& value;
+ SerializeAs(Type & t) : value(t) {}
+ template <class S> void serialize(S& s) { s.split(*this); }
+ template <class S> void encode(S& s) const { s(AsType(value)); }
+ template <class S> void decode(S& s) { AsType x; s(x); value=Type(x); }
+};
+
+} // namespace qpid
+
+#endif /*!QPID_SERIALIZER_H*/
diff --git a/qpid/cpp/src/qpid/SessionId.cpp b/qpid/cpp/src/qpid/SessionId.cpp
new file mode 100644
index 0000000000..c7e83f83d7
--- /dev/null
+++ b/qpid/cpp/src/qpid/SessionId.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/SessionId.h"
+#include <sstream>
+
+namespace qpid {
+
+SessionId::SessionId(const std::string& u, const std::string& n) : userId(u), name(n) {}
+
+bool SessionId::operator<(const SessionId& id) const {
+ return userId < id.userId || (userId == id.userId && name < id.name);
+}
+
+bool SessionId::operator==(const SessionId& id) const {
+ return id.name == name && id.userId == userId;
+}
+
+std::ostream& operator<<(std::ostream& o, const SessionId& id) {
+ return o << id.getUserId() << "." << id.getName();
+}
+
+std::string SessionId::str() const {
+ std::ostringstream o;
+ o << *this;
+ return o.str();
+}
+
+} // namespace qpid
diff --git a/qpid/cpp/src/qpid/SessionId.h b/qpid/cpp/src/qpid/SessionId.h
new file mode 100644
index 0000000000..d950ad9d1a
--- /dev/null
+++ b/qpid/cpp/src/qpid/SessionId.h
@@ -0,0 +1,60 @@
+#ifndef QPID_SESSIONID_H
+#define QPID_SESSIONID_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <boost/operators.hpp>
+#include <string>
+#include <qpid/CommonImportExport.h>
+
+namespace qpid {
+
+/** Identifier for a session.
+ * There are two parts to a session identifier:
+ *
+ * getUserId() returns the authentication principal associated with
+ * the session's connection.
+ *
+ * getName() returns the session name.
+ *
+ * The name must be unique among sessions with the same authentication
+ * principal.
+ */
+class SessionId : private boost::totally_ordered1<SessionId> {
+ std::string userId;
+ std::string name;
+ public:
+ QPID_COMMON_EXTERN SessionId(const std::string& userId=std::string(), const std::string& name=std::string());
+ std::string getUserId() const { return userId; }
+ std::string getName() const { return name; }
+ QPID_COMMON_EXTERN bool operator<(const SessionId&) const ;
+ QPID_COMMON_EXTERN bool operator==(const SessionId& id) const;
+ // Convert to a string
+ QPID_COMMON_EXTERN std::string str() const;
+};
+
+QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream&, const SessionId&);
+
+
+} // namespace qpid
+
+#endif /*!QPID_SESSIONID_H*/
diff --git a/qpid/cpp/src/qpid/SessionState.cpp b/qpid/cpp/src/qpid/SessionState.cpp
new file mode 100644
index 0000000000..e5019604d2
--- /dev/null
+++ b/qpid/cpp/src/qpid/SessionState.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/SessionState.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/framing/AMQMethodBody.h"
+#include "qpid/framing/enum.h"
+#include "qpid/log/Statement.h"
+#include <boost/bind.hpp>
+#include <numeric>
+
+namespace qpid {
+using framing::AMQFrame;
+using framing::NotImplementedException;
+using framing::InvalidArgumentException;
+using framing::IllegalStateException;
+using framing::ResourceLimitExceededException;
+using framing::InternalErrorException;
+using framing::FramingErrorException;
+
+namespace {
+bool isControl(const AMQFrame& f) {
+ return f.getMethod() && f.getMethod()->type() == framing::SEGMENT_TYPE_CONTROL;
+}
+bool isCommand(const AMQFrame& f) {
+ return f.getMethod() && f.getMethod()->type() == framing::SEGMENT_TYPE_COMMAND;
+}
+} // namespace
+
+SessionPoint::SessionPoint(SequenceNumber c, uint64_t o) : command(c), offset(o) {}
+
+// TODO aconway 2008-05-22: Do complete frame sequence validity check here,
+// currently duplicated betwen broker and client session impl.
+//
+void SessionPoint::advance(const AMQFrame& f) {
+ if (isControl(f)) return; // Ignore controls.
+ if (f.isFirstSegment() && f.isFirstFrame()) {
+ if (offset != 0)
+ throw FramingErrorException(QPID_MSG("Unexpected command start frame."));
+ if (!isCommand(f))
+ throw FramingErrorException(
+ QPID_MSG("Command start frame has invalid type" << f.getBody()->type()));
+ if (f.isLastSegment() && f.isLastFrame())
+ ++command; // Single-frame command.
+ else
+ offset += f.encodedSize();
+ }
+ else { // continuation frame for partial command
+ if (offset == 0)
+ throw FramingErrorException(QPID_MSG("Unexpected command continuation frame."));
+ if (f.isLastSegment() && f.isLastFrame()) {
+ ++command;
+ offset = 0;
+ }
+ else {
+ // TODO aconway 2008-04-24: if we go to support for partial
+ // command replay, then it may be better to record the unframed
+ // data size in a command point rather than the framed size so
+ // that the relationship of fragment offsets to the replay
+ // list can be computed more easily.
+ //
+ offset += f.encodedSize();
+ }
+ }
+}
+
+bool SessionPoint::operator<(const SessionPoint& x) const {
+ return command < x.command || (command == x.command && offset < x.offset);
+}
+
+bool SessionPoint::operator==(const SessionPoint& x) const {
+ return command == x.command && offset == x.offset;
+}
+
+
+SessionState::SendState::SendState() : unflushedSize(), replaySize(), bytesSinceKnownCompleted() {}
+
+SessionState::ReceiveState::ReceiveState() : bytesSinceKnownCompleted() {}
+
+uint32_t SessionState::getTimeout() const { return timeout; }
+void SessionState::setTimeout(uint32_t seconds) { timeout = seconds; }
+
+SessionPoint SessionState::senderGetCommandPoint() { return sender.sendPoint; }
+SequenceSet SessionState::senderGetIncomplete() const { return sender.incomplete; }
+SessionPoint SessionState::senderGetReplayPoint() const { return sender.replayPoint; }
+
+SessionState::ReplayRange SessionState::senderExpected(const SessionPoint& expect) {
+ if (expect < sender.replayPoint || sender.sendPoint < expect)
+ throw InvalidArgumentException(QPID_MSG(getId() << ": expected command-point out of range."));
+ QPID_LOG(debug, getId() << ": sender expected point moved to " << expect);
+ ReplayList::iterator i = sender.replayList.begin();
+ SessionPoint p = sender.replayPoint;
+ while (i != sender.replayList.end() && p.command < expect.command)
+ p.advance(*i++);
+ assert(p.command == expect.command);
+ return boost::make_iterator_range(i, sender.replayList.end());
+}
+
+void SessionState::senderRecord(const AMQFrame& f) {
+ if (isControl(f)) return; // Ignore control frames.
+ QPID_LOG(trace, getId() << ": sent cmd " << sender.sendPoint.command << ": " << *f.getBody());
+
+ stateful = true;
+ if (timeout) sender.replayList.push_back(f);
+ sender.unflushedSize += f.encodedSize();
+ sender.bytesSinceKnownCompleted += f.encodedSize();
+ sender.replaySize += f.encodedSize();
+ sender.incomplete += sender.sendPoint.command;
+ sender.sendPoint.advance(f);
+ if (config.replayHardLimit && config.replayHardLimit < sender.replaySize)
+ throw ResourceLimitExceededException("Replay buffer exceeeded hard limit");
+}
+
+static const uint32_t SPONTANEOUS_REQUEST_INTERVAL = 65536;
+
+bool SessionState::senderNeedFlush() const {
+ return (sender.sendPoint.command % SPONTANEOUS_REQUEST_INTERVAL == 0) ||
+ (config.replayFlushLimit && sender.unflushedSize >= config.replayFlushLimit);
+}
+
+void SessionState::senderRecordFlush() {
+ sender.flushPoint = sender.sendPoint;
+ sender.unflushedSize = 0;
+}
+
+bool SessionState::senderNeedKnownCompleted() const {
+ return config.replayFlushLimit && sender.bytesSinceKnownCompleted >= config.replayFlushLimit;
+}
+
+void SessionState::senderRecordKnownCompleted() {
+ sender.bytesSinceKnownCompleted = 0;
+}
+
+void SessionState::senderConfirmed(const SessionPoint& confirmed) {
+ if (confirmed > sender.sendPoint)
+ throw InvalidArgumentException(QPID_MSG(getId() << ": confirmed < " << confirmed << " but only sent < " << sender.sendPoint));
+ QPID_LOG(debug, getId() << ": sender confirmed point moved to " << confirmed);
+ ReplayList::iterator i = sender.replayList.begin();
+ while (i != sender.replayList.end() && sender.replayPoint.command < confirmed.command) {
+ sender.replayPoint.advance(*i);
+ assert(sender.replayPoint <= sender.sendPoint);
+ sender.replaySize -= i->encodedSize();
+ if (sender.replayPoint > sender.flushPoint)
+ sender.unflushedSize -= i->encodedSize();
+ ++i;
+ }
+ if (sender.replayPoint > sender.flushPoint)
+ sender.flushPoint = sender.replayPoint;
+ sender.replayList.erase(sender.replayList.begin(), i);
+ assert(sender.replayPoint.offset == 0);
+}
+
+void SessionState::senderCompleted(const SequenceSet& commands) {
+ if (commands.empty()) return;
+ QPID_LOG(debug, getId() << ": sender marked completed: " << commands);
+ sender.incomplete -= commands;
+ // Completion implies confirmation but we don't handle out-of-order
+ // confirmation, so confirm up to the end of the first contiguous range of commands.
+ senderConfirmed(SessionPoint(commands.rangesBegin()->end()));
+}
+
+void SessionState::receiverSetCommandPoint(const SessionPoint& point) {
+ if (hasState() && point > receiver.received)
+ throw InvalidArgumentException(QPID_MSG(getId() << ": Command-point out of range."));
+ QPID_LOG(debug, getId() << ": receiver command-point set to: " << point);
+ receiver.expected = point;
+ if (receiver.expected > receiver.received)
+ receiver.received = receiver.expected;
+}
+
+bool SessionState::receiverRecord(const AMQFrame& f) {
+ if (receiverTrackingDisabled) return true; //Very nasty hack for push bridges
+ if (isControl(f)) return true; // Ignore control frames.
+ stateful = true;
+ receiver.expected.advance(f);
+ receiver.bytesSinceKnownCompleted += f.encodedSize();
+ bool firstTime = receiver.expected > receiver.received;
+ if (firstTime) {
+ receiver.received = receiver.expected;
+ receiver.incomplete += receiverGetCurrent();
+ }
+ QPID_LOG(trace, getId() << ": recv cmd " << receiverGetCurrent() << ": " << *f.getBody());
+ if (!firstTime) QPID_LOG(trace, "Ignoring duplicate frame.");
+ return firstTime;
+}
+
+void SessionState::receiverCompleted(SequenceNumber command, bool cumulative) {
+ if (receiverTrackingDisabled) return; //Very nasty hack for push bridges
+ assert(receiver.incomplete.contains(command)); // Internal error to complete command twice.
+ SequenceNumber first =cumulative ? receiver.incomplete.front() : command;
+ SequenceNumber last = command;
+ receiver.unknownCompleted.add(first, last);
+ receiver.incomplete.remove(first, last);
+ QPID_LOG(debug, getId() << ": receiver marked completed: " << command
+ << " incomplete: " << receiver.incomplete
+ << " unknown-completed: " << receiver.unknownCompleted);
+}
+
+void SessionState::receiverKnownCompleted(const SequenceSet& commands) {
+ if (!commands.empty() && commands.back() > receiver.received.command)
+ throw InvalidArgumentException(QPID_MSG(getId() << ": Known-completed has invalid commands."));
+ receiver.bytesSinceKnownCompleted=0;
+ receiver.unknownCompleted -= commands;
+ QPID_LOG(debug, getId() << ": receiver known completed: " << commands << " unknown: " << receiver.unknownCompleted);
+}
+
+bool SessionState::receiverNeedKnownCompleted() const {
+ return (receiver.expected.command % SPONTANEOUS_REQUEST_INTERVAL == 0) ||
+ (config.replayFlushLimit && receiver.bytesSinceKnownCompleted >= config.replayFlushLimit);
+}
+
+const SessionPoint& SessionState::receiverGetExpected() const { return receiver.expected; }
+const SessionPoint& SessionState::receiverGetReceived() const { return receiver.received; }
+const SequenceSet& SessionState::receiverGetUnknownComplete() const { return receiver.unknownCompleted; }
+const SequenceSet& SessionState::receiverGetIncomplete() const { return receiver.incomplete; }
+
+SequenceNumber SessionState::receiverGetCurrent() const {
+ SequenceNumber current = receiver.expected.command;
+ if (receiver.expected.offset == 0)
+ --current;
+ return current;
+}
+
+SessionState::Configuration::Configuration(size_t flush, size_t hard) :
+ replayFlushLimit(flush), replayHardLimit(hard) {}
+
+SessionState::SessionState(const SessionId& i, const Configuration& c)
+ : id(i), timeout(0), config(c), stateful(false), receiverTrackingDisabled(false)
+{
+ QPID_LOG(debug, "SessionState::SessionState " << id << ": " << this);
+}
+
+bool SessionState::hasState() const { return stateful; }
+
+SessionState::~SessionState() {}
+
+std::ostream& operator<<(std::ostream& o, const SessionPoint& p) {
+ return o << "(" << p.command.getValue() << "+" << p.offset << ")";
+}
+
+void SessionState::setState(
+ const SequenceNumber& replayStart,
+ const SequenceNumber& sendCommandPoint,
+ const SequenceSet& sentIncomplete,
+ const SequenceNumber& expected,
+ const SequenceNumber& received,
+ const SequenceSet& unknownCompleted,
+ const SequenceSet& receivedIncomplete
+)
+{
+ sender.replayPoint = replayStart;
+ sender.flushPoint = sendCommandPoint;
+ sender.sendPoint = sendCommandPoint;
+ sender.unflushedSize = 0;
+ sender.replaySize = 0; // Replay list will be updated separately.
+ sender.incomplete = sentIncomplete;
+ sender.bytesSinceKnownCompleted = 0;
+
+ receiver.expected = expected;
+ receiver.received = received;
+ receiver.unknownCompleted = unknownCompleted;
+ receiver.incomplete = receivedIncomplete;
+ receiver.bytesSinceKnownCompleted = 0;
+}
+
+void SessionState::disableReceiverTracking() { receiverTrackingDisabled = true; }
+void SessionState::enableReceiverTracking() { receiverTrackingDisabled = false; }
+
+} // namespace qpid
diff --git a/qpid/cpp/src/qpid/SessionState.h b/qpid/cpp/src/qpid/SessionState.h
new file mode 100644
index 0000000000..02853b1143
--- /dev/null
+++ b/qpid/cpp/src/qpid/SessionState.h
@@ -0,0 +1,235 @@
+#ifndef QPID_SESSIONSTATE_H
+#define QPID_SESSIONSTATE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <qpid/SessionId.h>
+#include <qpid/framing/SequenceNumber.h>
+#include <qpid/framing/SequenceSet.h>
+#include <qpid/framing/AMQFrame.h>
+#include <qpid/framing/FrameHandler.h>
+#include <boost/operators.hpp>
+#include <boost/range/iterator_range.hpp>
+#include <vector>
+#include <iosfwd>
+#include <qpid/CommonImportExport.h>
+
+namespace qpid {
+using framing::SequenceNumber;
+using framing::SequenceSet;
+
+/** A point in the session. Points to command id + offset */
+struct SessionPoint : boost::totally_ordered1<SessionPoint> {
+ QPID_COMMON_EXTERN SessionPoint(SequenceNumber command = 0, uint64_t offset = 0);
+
+ SequenceNumber command;
+ uint64_t offset;
+
+ /** Advance past frame f */
+ QPID_COMMON_EXTERN void advance(const framing::AMQFrame& f);
+
+ QPID_COMMON_EXTERN bool operator<(const SessionPoint&) const;
+ QPID_COMMON_EXTERN bool operator==(const SessionPoint&) const;
+};
+
+QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream&, const SessionPoint&);
+
+/**
+ * Support for session idempotence barrier and resume as defined in
+ * AMQP 0-10.
+ *
+ * We only issue/use contiguous confirmations, out-of-order confirmation
+ * is ignored. Out of order completion is fully supported.
+ *
+ * Raises NotImplemented if the command point is set greater than the
+ * max currently received command data, either explicitly via
+ * session.command-point or implicitly via session.gap.
+ *
+ * Partial replay is not supported, replay always begins on a command
+ * boundary, and we never confirm partial commands.
+ *
+ * The SessionPoint data structure does store offsets so this class
+ * could be extended to support partial replay without
+ * source-incompatbile API changes.
+ */
+class SessionState {
+ typedef std::vector<framing::AMQFrame> ReplayList;
+
+ public:
+
+ typedef boost::iterator_range<ReplayList::iterator> ReplayRange;
+
+ struct Configuration {
+ QPID_COMMON_EXTERN Configuration(size_t flush=1024*1024, size_t hard=0);
+ size_t replayFlushLimit; // Flush when the replay list >= N bytes. 0 disables.
+ size_t replayHardLimit; // Kill session if replay list > N bytes. 0 disables.
+ };
+
+ QPID_COMMON_EXTERN SessionState(const SessionId& =SessionId(), const Configuration& =Configuration());
+
+ QPID_COMMON_EXTERN virtual ~SessionState();
+
+ bool hasState() const;
+
+ const SessionId& getId() const { return id; }
+
+ QPID_COMMON_EXTERN virtual uint32_t getTimeout() const;
+ QPID_COMMON_EXTERN virtual void setTimeout(uint32_t seconds);
+
+ bool operator==(const SessionId& other) const { return id == other; }
+ bool operator==(const SessionState& other) const { return id == other.id; }
+
+ // ==== Functions for sender state.
+
+ /** Record frame f for replay. Should not be called during replay. */
+ QPID_COMMON_EXTERN virtual void senderRecord(const framing::AMQFrame& f);
+
+ /** @return true if we should send flush for confirmed and completed commands. */
+ QPID_COMMON_EXTERN virtual bool senderNeedFlush() const;
+
+ /** Called when flush for confirmed and completed commands is sent to peer. */
+ QPID_COMMON_EXTERN virtual void senderRecordFlush();
+
+ /** True if we should reply to the next incoming completed command */
+ QPID_COMMON_EXTERN virtual bool senderNeedKnownCompleted() const;
+
+ /** Called when knownCompleted is sent to peer. */
+ QPID_COMMON_EXTERN virtual void senderRecordKnownCompleted();
+
+ /** Called when the peer confirms up to comfirmed. */
+ QPID_COMMON_EXTERN virtual void senderConfirmed(const SessionPoint& confirmed);
+
+ /** Called when the peer indicates commands completed */
+ QPID_COMMON_EXTERN virtual void senderCompleted(const SequenceSet& commands);
+
+ /** Point from which the next new (not replayed) data will be sent. */
+ QPID_COMMON_EXTERN virtual SessionPoint senderGetCommandPoint();
+
+ /** Set of outstanding incomplete commands */
+ QPID_COMMON_EXTERN virtual SequenceSet senderGetIncomplete() const;
+
+ /** Point from which we can replay. */
+ QPID_COMMON_EXTERN virtual SessionPoint senderGetReplayPoint() const;
+
+ /** Peer expecting commands from this point.
+ *@return Range of frames to be replayed.
+ */
+ QPID_COMMON_EXTERN virtual ReplayRange senderExpected(const SessionPoint& expected);
+
+ // ==== Functions for receiver state
+
+ /** Set the command point. */
+ QPID_COMMON_EXTERN virtual void receiverSetCommandPoint(const SessionPoint& point);
+
+ /** Returns true if frame should be be processed, false if it is a duplicate. */
+ QPID_COMMON_EXTERN virtual bool receiverRecord(const framing::AMQFrame& f);
+
+ /** Command completed locally */
+ QPID_COMMON_EXTERN virtual void receiverCompleted(SequenceNumber command, bool cumulative=false);
+
+ /** Peer has indicated commands are known completed */
+ QPID_COMMON_EXTERN virtual void receiverKnownCompleted(const SequenceSet& commands);
+
+ /** True if the next completed control should set the timely-reply argument
+ * to request a knonw-completed response.
+ */
+ QPID_COMMON_EXTERN virtual bool receiverNeedKnownCompleted() const;
+
+ /** Get the incoming command point */
+ QPID_COMMON_EXTERN virtual const SessionPoint& receiverGetExpected() const;
+
+ /** Get the received high-water-mark, may be > getExpected() during replay */
+ QPID_COMMON_EXTERN virtual const SessionPoint& receiverGetReceived() const;
+
+ /** Completed received commands that the peer may not know about. */
+ QPID_COMMON_EXTERN virtual const SequenceSet& receiverGetUnknownComplete() const;
+
+ /** Incomplete received commands. */
+ QPID_COMMON_EXTERN virtual const SequenceSet& receiverGetIncomplete() const;
+
+ /** ID of the command currently being handled. */
+ QPID_COMMON_EXTERN virtual SequenceNumber receiverGetCurrent() const;
+
+ /** Set the state variables, used to create a session that will resume
+ * from some previously established point.
+ */
+ QPID_COMMON_EXTERN virtual void setState(
+ const SequenceNumber& replayStart,
+ const SequenceNumber& sendCommandPoint,
+ const SequenceSet& sentIncomplete,
+ const SequenceNumber& expected,
+ const SequenceNumber& received,
+ const SequenceSet& unknownCompleted,
+ const SequenceSet& receivedIncomplete
+ );
+
+ /**
+ * So called 'push' bridges work by faking a subscribe request
+ * (and the accompanying flows etc) to the local broker to initiate
+ * the outflow of messages for the bridge.
+ *
+ * As the peer doesn't send these it cannot include them in its
+ * session state. To keep the session state on either side of the
+ * bridge in sync, this hack allows the tracking of state for
+ * received messages to be disabled for the faked commands and
+ * subsequently re-enabled.
+ */
+ QPID_COMMON_EXTERN void disableReceiverTracking();
+ QPID_COMMON_EXTERN void enableReceiverTracking();
+
+ private:
+
+ struct SendState {
+ SendState();
+ // invariant: replayPoint <= flushPoint <= sendPoint
+ SessionPoint replayPoint; // Can replay from this point
+ SessionPoint flushPoint; // Point of last flush
+ SessionPoint sendPoint; // Send from this point
+ ReplayList replayList; // Starts from replayPoint.
+ size_t unflushedSize; // Un-flushed bytes in replay list.
+ size_t replaySize; // Total bytes in replay list.
+ SequenceSet incomplete; // Commands sent and not yet completed.
+ size_t bytesSinceKnownCompleted; // Bytes sent since we last issued a knownCompleted.
+ } sender;
+
+ struct ReceiveState {
+ ReceiveState();
+ SessionPoint expected; // Expected from here
+ SessionPoint received; // Received to here. Invariant: expected <= received.
+ SequenceSet unknownCompleted; // Received & completed, may not not known-complete by peer.
+ SequenceSet incomplete; // Incomplete received commands.
+ size_t bytesSinceKnownCompleted; // Bytes sent since we last issued a knownCompleted.
+ } receiver;
+
+ SessionId id;
+ uint32_t timeout;
+ Configuration config;
+ bool stateful;
+ bool receiverTrackingDisabled;//very nasty hack for 'push' bridges
+};
+
+inline bool operator==(const SessionId& id, const SessionState& s) { return s == id; }
+
+} // namespace qpid
+
+
+#endif /*!QPID_SESSIONSTATE_H*/
diff --git a/qpid/cpp/src/qpid/SharedObject.h b/qpid/cpp/src/qpid/SharedObject.h
new file mode 100644
index 0000000000..852a036ab9
--- /dev/null
+++ b/qpid/cpp/src/qpid/SharedObject.h
@@ -0,0 +1,55 @@
+#ifndef _SharedObject_
+#define _SharedObject_
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <boost/shared_ptr.hpp>
+#include <boost/noncopyable.hpp>
+
+namespace qpid {
+ /**
+ * Template to enforce shared object conventions.
+ * Shared object classes should inherit : public qpid::SharedObject
+ * That ensures Foo:
+ * - has typedef boost::shared_ptr<T> shared_ptr
+ * - has virtual destructor
+ * - is boost::noncopyable (no default copy or assign)
+ * - has a protected default constructor.
+ *
+ * Shared objects should not have public constructors.
+ * Make constructors protected and provide public statc create()
+ * functions that return a shared_ptr.
+ */
+ template <class T>
+ class SharedObject : private boost::noncopyable
+ {
+ public:
+ typedef boost::shared_ptr<T> shared_ptr;
+
+ virtual ~SharedObject() {};
+
+ protected:
+ SharedObject() {}
+ };
+}
+
+#endif /*!_SharedObject_*/
diff --git a/qpid/cpp/src/qpid/StringUtils.cpp b/qpid/cpp/src/qpid/StringUtils.cpp
new file mode 100644
index 0000000000..c436441c56
--- /dev/null
+++ b/qpid/cpp/src/qpid/StringUtils.cpp
@@ -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.
+ *
+ */
+#include "qpid/StringUtils.h"
+
+namespace qpid {
+
+using std::string;
+using std::vector;
+
+void split(vector<string>& out, const string& in, const string& delims)
+{
+ string::size_type start = in.find_first_not_of(delims);
+ if (start == string::npos) return;
+
+ string::size_type end = in.find_first_of(delims, start);
+ while (end != string::npos) {
+ out.push_back(in.substr(start, end - start));
+ start = in.find_first_not_of(delims, end);
+ if (start == string::npos) return;
+ end = in.find_first_of(delims, start);
+ }
+ out.push_back(in.substr(start));
+}
+
+vector<string> split(const string& in, const string& delims)
+{
+ vector<string> out;
+ split(out, in, delims);
+ return out;
+}
+
+} // namespace qpid
diff --git a/qpid/cpp/src/qpid/StringUtils.h b/qpid/cpp/src/qpid/StringUtils.h
new file mode 100644
index 0000000000..4130fae017
--- /dev/null
+++ b/qpid/cpp/src/qpid/StringUtils.h
@@ -0,0 +1,45 @@
+#ifndef QPID_STRINGUTILS_H
+#define QPID_STRINGUTILS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/CommonImportExport.h"
+
+#include <string>
+#include <vector>
+
+namespace qpid {
+
+/**
+ * Split 'in' into words using delimiters in 'delims' and put
+ * resulting strings into 'out' vector.
+ */
+QPID_COMMON_EXTERN void split(std::vector<std::string>& out, const std::string& in, const std::string& delims);
+/**
+ * Split 'in' into words using delimiters in 'delims' and return the
+ * resulting strings in a vector.
+ */
+QPID_COMMON_EXTERN std::vector<std::string> split(const std::string& in, const std::string& delims);
+
+} // namespace qpid
+
+#endif /*!QPID_STRINGUTILS_H*/
diff --git a/qpid/cpp/src/qpid/Url.cpp b/qpid/cpp/src/qpid/Url.cpp
new file mode 100644
index 0000000000..1780a07f92
--- /dev/null
+++ b/qpid/cpp/src/qpid/Url.cpp
@@ -0,0 +1,281 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/Url.h"
+#include "qpid/Exception.h"
+#include "qpid/Msg.h"
+#include "qpid/sys/SystemInfo.h"
+#include "qpid/sys/StrError.h"
+#include "qpid/client/Connector.h"
+#include "qpid/sys/Mutex.h"
+#include <boost/lexical_cast.hpp>
+
+#include <algorithm>
+#include <vector>
+#include <string>
+
+#include <string.h>
+
+using namespace std;
+using boost::lexical_cast;
+
+namespace qpid {
+
+class ProtocolTags {
+ public:
+ bool find(const string& tag) {
+ sys::Mutex::ScopedLock l(lock);
+ return std::find(tags.begin(), tags.end(), tag) != tags.end();
+ }
+
+ void add(const string& tag) {
+ sys::Mutex::ScopedLock l(lock);
+ if (std::find(tags.begin(), tags.end(), tag) == tags.end())
+ tags.push_back(tag);
+ }
+
+ static ProtocolTags& instance() {
+ /** First call must be made while program is still single threaded.
+ * This will be the case since tags are registered in static initializers.
+ */
+ static ProtocolTags tags;
+ return tags;
+ }
+
+ private:
+ sys::Mutex lock;
+ vector<string> tags;
+};
+
+Url::Invalid::Invalid(const string& s) : Exception(s) {}
+
+string Url::str() const {
+ if (cache.empty() && !this->empty()) {
+ ostringstream os;
+ os << *this;
+ cache = os.str();
+ }
+ return cache;
+}
+
+ostream& operator<<(ostream& os, const Url& url) {
+ os << "amqp:";
+ if (!url.getUser().empty()) os << url.getUser();
+ if (!url.getPass().empty()) os << "/" << url.getPass();
+ if (!(url.getUser().empty() && url.getPass().empty())) os << "@";
+ Url::const_iterator i = url.begin();
+ if (i!=url.end()) {
+ os << *i++;
+ while (i != url.end())
+ os << "," << *i++;
+ }
+ return os;
+}
+
+static const std::string TCP = "tcp";
+
+/** Simple recursive-descent parser for this grammar:
+url = ["amqp:"][ user ["/" password] "@" ] protocol_addr *("," protocol_addr)
+protocol_addr = tcp_addr / rmda_addr / ssl_addr / .. others plug-in
+tcp_addr = ["tcp:"] host [":" port]
+rdma_addr = "rdma:" host [":" port]
+ssl_addr = "ssl:" host [":" port]
+*/
+class UrlParser {
+ public:
+ UrlParser(Url& u, const char* s, const std::string& defaultProtocol_=Address::TCP) : url(u), text(s), end(s+strlen(s)), i(s),
+ defaultProtocol(defaultProtocol_) {}
+ bool parse() {
+ literal("amqp:"); // Optional
+ userPass(); // Optional
+ return list(&UrlParser::protocolAddr, &UrlParser::comma) && i == end;
+ }
+
+ private:
+ typedef bool (UrlParser::*Rule)();
+
+ bool userPass() {
+ const char* at = std::find(i, end, '@');
+ if (at == end) return false;
+ const char* slash = std::find(i, at, '/');
+ const char* colon = std::find(i, at, ':');
+ const char* sep = std::min(slash, colon);
+ url.setUser(string(i, sep));
+ const char* pass = (sep == at) ? sep : sep+1;
+ url.setPass(string(pass, at));
+ i = at+1;
+ return true;
+ }
+
+ bool comma() { return literal(","); }
+
+ bool protocolAddr() {
+ Address addr(defaultProtocol, "", Address::AMQP_PORT); // Set up defaults
+ protocolTag(addr.protocol); // Optional
+ bool ok = (host(addr.host) &&
+ (literal(":") ? port(addr.port) : true));
+ if (ok) url.push_back(addr);
+ return ok;
+ }
+
+ bool protocolTag(string& result) {
+ const char* j = std::find(i,end,':');
+ if (j != end) {
+ string tag(i,j);
+ if (ProtocolTags::instance().find(tag)) {
+ i = j+1;
+ result = tag;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ // A liberal interpretation of http://www.ietf.org/rfc/rfc3986.txt.
+ // Works for DNS names and and ipv4 and ipv6 literals
+ //
+ bool host(string& h) {
+ if (ip6literal(h)) return true;
+
+ const char* start=i;
+ while (unreserved() || pctEncoded())
+ ;
+ if (start == i) return false;//host is required
+ else h.assign(start, i);
+ return true;
+ }
+
+ // This is a bit too liberal for IPv6 literal addresses, but probably good enough
+ bool ip6literal(string& h) {
+ if (literal("[")) {
+ const char* start = i;
+ while (hexDigit() || literal(":") || literal("."))
+ ;
+ const char* end = i;
+ if ( end-start < 2 ) return false; // Smallest valid address is "::"
+ if (literal("]")) {
+ h.assign(start, end);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool unreserved() { return (::isalnum(*i) || ::strchr("-._~", *i)) && advance(); }
+
+ bool pctEncoded() { return literal("%") && hexDigit() && hexDigit(); }
+
+ bool hexDigit() { return i < end && ::strchr("01234567890abcdefABCDEF", *i) && advance(); }
+
+ bool port(uint16_t& p) { return decimalInt(p); }
+
+ template <class IntType> bool decimalInt(IntType& n) {
+ const char* start = i;
+ while (decDigit())
+ ;
+ try {
+ n = lexical_cast<IntType>(string(start, i));
+ return true;
+ } catch(...) { return false; }
+ }
+
+ bool decDigit() { return i < end && ::isdigit(*i) && advance(); }
+
+ bool literal(const char* s) {
+ int n = ::strlen(s);
+ if (n <= end-i && equal(s, s+n, i)) return advance(n);
+ return false;
+ };
+
+ bool noop() { return true; }
+
+ /** List of item, separated by separator, with min & max bounds. */
+ bool list(Rule item, Rule separator, size_t min=0, size_t max=UNLIMITED) {
+ assert(max > 0);
+ assert(max >= min);
+ if (!(this->*item)()) return min == 0; // Empty list.
+ size_t n = 1;
+ while (n < max && i < end) {
+ if (!(this->*separator)()) break;
+ if (i == end || !(this->*item)()) return false; // Separator with no item.
+ ++n;
+ }
+ return n >= min;
+ }
+
+ /** List of items with no separator */
+ bool list(Rule item, size_t min=0, size_t max=UNLIMITED) { return list(item, &UrlParser::noop, min, max); }
+
+ bool advance(size_t n=1) {
+ if (i+n > end) return false;
+ i += n;
+ return true;
+ }
+
+ static const size_t UNLIMITED = size_t(~1);
+ static const std::string LOCALHOST;
+
+ Url& url;
+ const char* text;
+ const char* end;
+ const char* i;
+ const std::string defaultProtocol;
+};
+
+const string UrlParser::LOCALHOST("127.0.0.1");
+
+void Url::parse(const char* url) {
+ parse(url, Address::TCP);
+}
+void Url::parse(const char* url, const std::string& defaultProtocol) {
+ parseNoThrow(url, defaultProtocol);
+ if (empty())
+ throw Url::Invalid(QPID_MSG("Invalid URL: " << url));
+}
+
+void Url::parseNoThrow(const char* url) {
+ parseNoThrow(url, Address::TCP);
+}
+
+void Url::parseNoThrow(const char* url, const std::string& defaultProtocol) {
+ clear();
+ cache.clear();
+ if (!UrlParser(*this, url, defaultProtocol).parse())
+ clear();
+}
+
+void Url::throwIfEmpty() const {
+ if (empty())
+ throw Url::Invalid("URL contains no addresses");
+}
+
+std::string Url::getUser() const { return user; }
+std::string Url::getPass() const { return pass; }
+void Url::setUser(const std::string& s) { user = s; }
+void Url::setPass(const std::string& s) { pass = s; }
+
+std::istream& operator>>(std::istream& is, Url& url) {
+ std::string s;
+ is >> s;
+ url.parse(s);
+ return is;
+}
+
+void Url::addProtocol(const std::string& tag) { ProtocolTags::instance().add(tag); }
+
+} // namespace qpid
diff --git a/qpid/cpp/src/qpid/Url.h b/qpid/cpp/src/qpid/Url.h
new file mode 100644
index 0000000000..f9ed87c24b
--- /dev/null
+++ b/qpid/cpp/src/qpid/Url.h
@@ -0,0 +1,96 @@
+#ifndef QPID_URL_H
+#define QPID_URL_H
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/Address.h"
+#include "qpid/Exception.h"
+#include <string>
+#include <vector>
+#include <new>
+#include <ostream>
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+
+/** An AMQP URL contains a list of addresses */
+struct Url : public std::vector<Address> {
+
+ struct Invalid : public Exception { QPID_COMMON_EXTERN Invalid(const std::string& s); };
+
+ /** Convert to string form. */
+ QPID_COMMON_EXTERN std::string str() const;
+
+ /** Empty URL. */
+ Url() {}
+
+ /** URL containing a single address */
+ explicit Url(const Address& addr) { push_back(addr); }
+
+ /** Parse url, throw Invalid if invalid. */
+ explicit Url(const std::string& url) { parse(url.c_str()); }
+ /** Parse url, throw Invalid if invalid. */
+ explicit Url(const std::string& url, const std::string& defaultProtocol) { parse(url.c_str(), defaultProtocol); }
+
+ /** Parse url, throw Invalid if invalid. */
+ explicit Url(const char* url) { parse(url); }
+
+ Url& operator=(const char* s) { parse(s); return *this; }
+ Url& operator=(const std::string& s) { parse(s); return *this; }
+
+ /** Throw Invalid if the URL does not contain any addresses. */
+ QPID_COMMON_EXTERN void throwIfEmpty() const;
+
+ /** Replace contents with parsed url
+ *@exception Invalid if the url is invalid.
+ */
+ QPID_COMMON_EXTERN void parse(const char* url);
+ QPID_COMMON_EXTERN void parse(const char* url, const std::string& defaultProtocol);
+ QPID_COMMON_INLINE_EXTERN void parse(const std::string& url) { parse(url.c_str()); }
+
+ /** Replace contesnts with parsed URL. Replace with empty URL if invalid. */
+ QPID_COMMON_EXTERN void parseNoThrow(const char* url);
+ QPID_COMMON_EXTERN void parseNoThrow(const char* url, const std::string& defaultProtocol);
+
+ /** Add a protocol tag to be recognzed in URLs.
+ * Only for use by protcol plug-in initializers.
+ */
+ QPID_COMMON_EXTERN static void addProtocol(const std::string& tag);
+
+ QPID_COMMON_EXTERN void setUser(const std::string&);
+ QPID_COMMON_EXTERN void setPass(const std::string&);
+ QPID_COMMON_EXTERN std::string getUser() const;
+ QPID_COMMON_EXTERN std::string getPass() const;
+
+ private:
+ mutable std::string cache; // cache string form for efficiency.
+ std::string user, pass;
+
+ friend class UrlParser;
+};
+
+inline bool operator==(const Url& a, const Url& b) { return a.str()==b.str(); }
+inline bool operator!=(const Url& a, const Url& b) { return a.str()!=b.str(); }
+
+QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream& os, const Url& url);
+QPID_COMMON_EXTERN std::istream& operator>>(std::istream& is, Url& url);
+
+} // namespace qpid
+
+#endif /*!QPID_URL_H*/
diff --git a/qpid/cpp/src/qpid/UrlArray.cpp b/qpid/cpp/src/qpid/UrlArray.cpp
new file mode 100644
index 0000000000..9ebacbd945
--- /dev/null
+++ b/qpid/cpp/src/qpid/UrlArray.cpp
@@ -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.
+ *
+ */
+#include "UrlArray.h"
+
+#include <qpid/framing/FieldValue.h>
+
+namespace qpid {
+
+std::vector<Url> urlArrayToVector(const framing::Array& array) {
+ std::vector<Url> urls;
+ for (framing::Array::ValueVector::const_iterator i = array.begin();
+ i != array.end();
+ ++i )
+ urls.push_back(Url((*i)->get<std::string>()));
+ return urls;
+}
+
+framing::Array vectorToUrlArray(const std::vector<Url>& urls) {
+ framing::Array array(0x95);
+ for (std::vector<Url>::const_iterator i = urls.begin(); i != urls.end(); ++i)
+ array.add(boost::shared_ptr<framing::Str16Value>(new framing::Str16Value(i->str())));
+ return array;
+}
+
+} // namespace qpid
diff --git a/qpid/cpp/src/qpid/UrlArray.h b/qpid/cpp/src/qpid/UrlArray.h
new file mode 100644
index 0000000000..f0065f0f0c
--- /dev/null
+++ b/qpid/cpp/src/qpid/UrlArray.h
@@ -0,0 +1,36 @@
+#ifndef QPID_URLARRAY_H
+#define QPID_URLARRAY_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/framing/Array.h"
+#include "qpid/Url.h"
+#include <vector>
+
+namespace qpid {
+
+/** @file Functions to encode/decode an array of URLs. */
+QPID_COMMON_EXTERN std::vector<Url> urlArrayToVector(const framing::Array& array);
+QPID_COMMON_EXTERN framing::Array vectorToUrlArray(const std::vector<Url>& urls);
+} // namespace qpid
+
+#endif /* !QPID_URLARRAY_H */
diff --git a/qpid/cpp/src/qpid/Version.h b/qpid/cpp/src/qpid/Version.h
new file mode 100755
index 0000000000..6a06566907
--- /dev/null
+++ b/qpid/cpp/src/qpid/Version.h
@@ -0,0 +1,36 @@
+#ifndef QPID_VERSION_H
+#define QPID_VERSION_H
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <string>
+
+#include "config.h"
+
+namespace qpid {
+ const std::string product = PACKAGE_NAME;
+ const std::string version = PACKAGE_VERSION;
+# if HAVE_SASL
+ const std::string saslName = BROKER_SASL_NAME;
+# else
+ const std::string saslName = "qpidd-no-sasl";
+# endif
+}
+
+#endif /*!QPID_VERSION_H*/
diff --git a/qpid/cpp/src/qpid/acl/Acl.cpp b/qpid/cpp/src/qpid/acl/Acl.cpp
new file mode 100644
index 0000000000..bd9482ef41
--- /dev/null
+++ b/qpid/cpp/src/qpid/acl/Acl.cpp
@@ -0,0 +1,450 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/acl/Acl.h"
+#include "qpid/acl/AclConnectionCounter.h"
+#include "qpid/acl/AclResourceCounter.h"
+#include "qpid/acl/AclData.h"
+#include "qpid/acl/AclValidator.h"
+#include "qpid/sys/Mutex.h"
+
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/Connection.h"
+#include "qpid/Plugin.h"
+#include "qpid/Options.h"
+#include "qpid/log/Logger.h"
+#include "qpid/types/Variant.h"
+#include "qmf/org/apache/qpid/acl/ArgsAclLookup.h"
+#include "qmf/org/apache/qpid/acl/ArgsAclLookupPublish.h"
+#include "qmf/org/apache/qpid/acl/Package.h"
+#include "qmf/org/apache/qpid/acl/EventAllow.h"
+#include "qmf/org/apache/qpid/acl/EventConnectionDeny.h"
+#include "qmf/org/apache/qpid/acl/EventQueueQuotaDeny.h"
+#include "qmf/org/apache/qpid/acl/EventDeny.h"
+#include "qmf/org/apache/qpid/acl/EventFileLoaded.h"
+#include "qmf/org/apache/qpid/acl/EventFileLoadFailed.h"
+
+#include <map>
+
+#include <boost/shared_ptr.hpp>
+
+using namespace std;
+using namespace qpid::acl;
+using qpid::broker::Broker;
+using namespace qpid::sys;
+using qpid::management::ManagementAgent;
+using qpid::management::ManagementObject;
+using qpid::management::Manageable;
+using qpid::management::Args;
+namespace _qmf = qmf::org::apache::qpid::acl;
+
+Acl::Acl (AclValues& av, Broker& b): aclValues(av), broker(&b), transferAcl(false),
+ connectionCounter(new ConnectionCounter(*this, aclValues.aclMaxConnectPerUser, aclValues.aclMaxConnectPerIp, aclValues.aclMaxConnectTotal)),
+ resourceCounter(new ResourceCounter(*this, aclValues.aclMaxQueuesPerUser)),userRules(false)
+{
+
+ if (aclValues.aclMaxConnectPerUser > AclData::getConnectMaxSpec())
+ throw Exception("--connection-limit-per-user switch cannot be larger than " + AclData::getMaxConnectSpecStr());
+ if (aclValues.aclMaxConnectPerIp > AclData::getConnectMaxSpec())
+ throw Exception("--connection-limit-per-ip switch cannot be larger than " + AclData::getMaxConnectSpecStr());
+ if (aclValues.aclMaxConnectTotal > AclData::getConnectMaxSpec())
+ throw Exception("--max-connections switch cannot be larger than " + AclData::getMaxConnectSpecStr());
+ if (aclValues.aclMaxQueuesPerUser > AclData::getConnectMaxSpec())
+ throw Exception("--max-queues-per-user switch cannot be larger than " + AclData::getMaxQueueSpecStr());
+
+ agent = broker->getManagementAgent();
+
+ if (agent != 0) {
+ _qmf::Package packageInit(agent);
+ mgmtObject = _qmf::Acl::shared_ptr(new _qmf::Acl (agent, this, broker));
+ agent->addObject (mgmtObject);
+ mgmtObject->set_maxConnections(aclValues.aclMaxConnectTotal);
+ mgmtObject->set_maxConnectionsPerIp(aclValues.aclMaxConnectPerIp);
+ mgmtObject->set_maxConnectionsPerUser(aclValues.aclMaxConnectPerUser);
+ mgmtObject->set_maxQueuesPerUser(aclValues.aclMaxQueuesPerUser);
+ }
+
+ if (!aclValues.aclFile.empty()) {
+ std::string errorString;
+ if (!readAclFile(errorString)){
+ if (mgmtObject!=0) mgmtObject->set_enforcingAcl(0);
+ throw Exception("Could not read ACL file " + errorString);
+ }
+ } else {
+ loadEmptyAclRuleset();
+ QPID_LOG(debug, "ACL loaded empty rule set");
+ }
+ broker->getConnectionObservers().add(connectionCounter);
+ QPID_LOG(info, "ACL Plugin loaded");
+ if (mgmtObject!=0) mgmtObject->set_enforcingAcl(1);
+}
+
+
+void Acl::reportConnectLimit(const std::string user, const std::string addr)
+{
+ if (mgmtObject!=0)
+ mgmtObject->inc_connectionDenyCount();
+
+ if (agent != 0) {
+ agent->raiseEvent(_qmf::EventConnectionDeny(user, addr));
+ }
+}
+
+
+void Acl::reportQueueLimit(const std::string user, const std::string queueName)
+{
+ if (mgmtObject!=0)
+ mgmtObject->inc_queueQuotaDenyCount();
+
+ if (agent != 0) {
+ agent->raiseEvent(_qmf::EventQueueQuotaDeny(user, queueName));
+ }
+}
+
+
+bool Acl::authorise(
+ const std::string& id,
+ const Action& action,
+ const ObjectType& objType,
+ const std::string& name,
+ std::map<Property, std::string>* params)
+{
+ boost::shared_ptr<AclData> dataLocal;
+ {
+ Mutex::ScopedLock locker(dataLock);
+ dataLocal = data; //rcu copy
+ }
+
+ // add real ACL check here...
+ AclResult aclreslt = dataLocal->lookup(id,action,objType,name,params);
+
+
+ return result(aclreslt, id, action, objType, name);
+}
+
+bool Acl::authorise(
+ const std::string& id,
+ const Action& action,
+ const ObjectType& objType,
+ const std::string& ExchangeName,
+ const std::string& RoutingKey)
+{
+ boost::shared_ptr<AclData> dataLocal;
+ {
+ Mutex::ScopedLock locker(dataLock);
+ dataLocal = data; //rcu copy
+ }
+
+ // only use dataLocal here...
+ AclResult aclreslt = dataLocal->lookup(id,action,objType,ExchangeName,RoutingKey);
+
+ return result(aclreslt, id, action, objType, ExchangeName);
+}
+
+
+bool Acl::approveConnection(const qpid::broker::Connection& conn)
+{
+ const std::string& userName(conn.getUserId());
+ uint16_t connectionLimit(0);
+
+ boost::shared_ptr<AclData> dataLocal;
+ {
+ Mutex::ScopedLock locker(dataLock);
+ dataLocal = data; //rcu copy
+ }
+
+ (void) dataLocal->getConnQuotaForUser(userName, &connectionLimit);
+
+ return connectionCounter->approveConnection(
+ conn,
+ userName,
+ dataLocal->enforcingConnectionQuotas(),
+ connectionLimit,
+ dataLocal);
+}
+
+bool Acl::approveCreateQueue(const std::string& userId, const std::string& queueName)
+{
+// return resourceCounter->approveCreateQueue(userId, queueName);
+ uint16_t queueLimit(0);
+
+ boost::shared_ptr<AclData> dataLocal;
+ {
+ Mutex::ScopedLock locker(dataLock);
+ dataLocal = data; //rcu copy
+ }
+
+ (void) dataLocal->getQueueQuotaForUser(userId, &queueLimit);
+
+ return resourceCounter->approveCreateQueue(userId, queueName, dataLocal->enforcingQueueQuotas(), queueLimit);
+}
+
+
+void Acl::recordDestroyQueue(const std::string& queueName)
+{
+ resourceCounter->recordDestroyQueue(queueName);
+}
+
+
+bool Acl::result(
+ const AclResult& aclreslt,
+ const std::string& id,
+ const Action& action,
+ const ObjectType& objType,
+ const std::string& name)
+{
+ bool result(false);
+
+ switch (aclreslt)
+ {
+ case ALLOWLOG:
+ QPID_LOG(info, "ACL Allow id:" << id
+ << " action:" << AclHelper::getActionStr(action)
+ << " ObjectType:" << AclHelper::getObjectTypeStr(objType)
+ << " Name:" << name );
+ if (agent != 0) {
+ agent->raiseEvent(_qmf::EventAllow(id, AclHelper::getActionStr(action),
+ AclHelper::getObjectTypeStr(objType),
+ name, types::Variant::Map()));
+ }
+ // FALLTHROUGH
+ case ALLOW:
+ result = true;
+ break;
+
+ case DENYLOG:
+ QPID_LOG(info, "ACL Deny id:" << id
+ << " action:" << AclHelper::getActionStr(action)
+ << " ObjectType:" << AclHelper::getObjectTypeStr(objType)
+ << " Name:" << name);
+ if (agent != 0) {
+ agent->raiseEvent(_qmf::EventDeny(id, AclHelper::getActionStr(action),
+ AclHelper::getObjectTypeStr(objType),
+ name, types::Variant::Map()));
+ }
+ // FALLTHROUGH
+ case DENY:
+ if (mgmtObject!=0)
+ mgmtObject->inc_aclDenyCount();
+ result = false;
+ break;
+
+ default:
+ assert (false);
+ }
+
+ return result;
+}
+
+bool Acl::readAclFile(std::string& errorText)
+{
+ // only set transferAcl = true if a rule implies the use of ACL on transfer, else keep false for performance reasons.
+ return readAclFile(aclValues.aclFile, errorText);
+}
+
+bool Acl::readAclFile(std::string& aclFile, std::string& errorText) {
+ boost::shared_ptr<AclData> d(new AclData);
+ AclReader ar(aclValues.aclMaxConnectPerUser, aclValues.aclMaxQueuesPerUser);
+ if (ar.read(aclFile, d)){
+ if (agent != 0) {
+ agent->raiseEvent(_qmf::EventFileLoadFailed("", ar.getError()));
+ }
+ errorText = ar.getError();
+ QPID_LOG(error,ar.getError());
+ return false;
+ }
+
+ AclValidator validator;
+ validator.validate(d);
+
+ {
+ Mutex::ScopedLock locker(dataLock);
+ data = d;
+ }
+ transferAcl = data->transferAcl; // any transfer ACL
+ userRules = true; // rules in force came from an ACL file
+
+ if (data->transferAcl){
+ QPID_LOG(debug,"ACL: Transfer ACL is Enabled!");
+ }
+
+ if (data->enforcingConnectionQuotas()){
+ QPID_LOG(debug, "ACL: Connection quotas are Enabled.");
+ }
+
+ if (data->enforcingQueueQuotas()){
+ QPID_LOG(debug, "ACL: Queue quotas are Enabled.");
+ }
+
+ QPID_LOG(debug, "ACL: Default connection mode : "
+ << AclHelper::getAclResultStr(d->connectionMode()));
+
+ data->aclSource = aclFile;
+ if (mgmtObject!=0){
+ mgmtObject->set_transferAcl(transferAcl?1:0);
+ mgmtObject->set_policyFile(aclFile);
+ mgmtObject->set_lastAclLoad(Duration::FromEpoch());
+ if (agent != 0) {
+ agent->raiseEvent(_qmf::EventFileLoaded(""));
+ }
+ }
+ return true;
+}
+
+//
+// loadEmptyAclRuleset()
+//
+// No ACL file is specified but ACL should run.
+// Create a ruleset as if only "ACL ALLOW ALL ALL" was in a file
+//
+void Acl::loadEmptyAclRuleset() {
+ boost::shared_ptr<AclData> d(new AclData);
+ d->decisionMode = ALLOW;
+ d->aclSource = "";
+ d->connectionDecisionMode = ALLOW;
+ {
+ Mutex::ScopedLock locker(dataLock);
+ data = d;
+ }
+ if (mgmtObject!=0){
+ mgmtObject->set_transferAcl(transferAcl?1:0);
+ mgmtObject->set_policyFile("");
+ mgmtObject->set_lastAclLoad(Duration::FromEpoch());
+ if (agent != 0) {
+ agent->raiseEvent(_qmf::EventFileLoaded(""));
+ }
+ }
+}
+
+//
+// management lookup function performs general query on acl engine
+//
+Manageable::status_t Acl::lookup(qpid::management::Args& args, std::string& text)
+{
+ _qmf::ArgsAclLookup& ioArgs = (_qmf::ArgsAclLookup&) args;
+ Manageable::status_t result(STATUS_USER);
+
+ try {
+ ObjectType objType = AclHelper::getObjectType(ioArgs.i_object);
+ Action action = AclHelper::getAction( ioArgs.i_action);
+ std::map<Property, std::string> propertyMap;
+ for (::qpid::types::Variant::Map::const_iterator
+ iMapIter = ioArgs.i_propertyMap.begin();
+ iMapIter != ioArgs.i_propertyMap.end();
+ iMapIter++)
+ {
+ Property property = AclHelper::getProperty(iMapIter->first);
+ propertyMap.insert(make_pair(property, iMapIter->second));
+ }
+
+ boost::shared_ptr<AclData> dataLocal;
+ {
+ Mutex::ScopedLock locker(dataLock);
+ dataLocal = data; //rcu copy
+ }
+ AclResult aclResult;
+ // CREATE CONNECTION does not use lookup()
+ if (action == ACT_CREATE && objType == OBJ_CONNECTION) {
+ std::string host = propertyMap[acl::PROP_HOST];
+ std::string logString;
+ aclResult = dataLocal->isAllowedConnection(
+ ioArgs.i_userId,
+ host,
+ logString);
+ } else {
+ aclResult = dataLocal->lookup(
+ ioArgs.i_userId,
+ action,
+ objType,
+ ioArgs.i_objectName,
+ &propertyMap);
+ }
+ ioArgs.o_result = AclHelper::getAclResultStr(aclResult);
+ result = STATUS_OK;
+
+ } catch (const std::exception& e) {
+ std::ostringstream oss;
+ oss << "AclLookup invalid name : " << e.what();
+ ioArgs.o_result = oss.str();
+ text = oss.str();
+ }
+
+ return result;
+}
+
+
+//
+// management lookupPublish function performs fastpath
+// PUBLISH EXCHANGE query on acl engine
+//
+Manageable::status_t Acl::lookupPublish(qpid::management::Args& args, std::string& /*text*/)
+{
+ _qmf::ArgsAclLookupPublish& ioArgs = (_qmf::ArgsAclLookupPublish&) args;
+ boost::shared_ptr<AclData> dataLocal;
+ {
+ Mutex::ScopedLock locker(dataLock);
+ dataLocal = data; //rcu copy
+ }
+ AclResult aclResult = dataLocal->lookup(
+ ioArgs.i_userId,
+ ACT_PUBLISH,
+ OBJ_EXCHANGE,
+ ioArgs.i_exchangeName,
+ ioArgs.i_routingKey);
+
+ ioArgs.o_result = AclHelper::getAclResultStr(aclResult);
+
+ return STATUS_OK;
+}
+
+
+Acl::~Acl(){
+ broker->getConnectionObservers().remove(connectionCounter);
+}
+
+ManagementObject::shared_ptr Acl::GetManagementObject(void) const
+{
+ return mgmtObject;
+}
+
+Manageable::status_t Acl::ManagementMethod (uint32_t methodId, Args& args, string& text)
+{
+ Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD;
+ QPID_LOG (debug, "ACL: Queue::ManagementMethod [id=" << methodId << "]");
+
+ switch (methodId)
+ {
+ case _qmf::Acl::METHOD_RELOADACLFILE :
+ readAclFile(text);
+ if (text.empty())
+ status = Manageable::STATUS_OK;
+ else
+ status = Manageable::STATUS_USER;
+ break;
+
+ case _qmf::Acl::METHOD_LOOKUP :
+ status = lookup(args, text);
+ break;
+
+ case _qmf::Acl::METHOD_LOOKUPPUBLISH :
+ status = lookupPublish(args, text);
+ break;
+ }
+
+ return status;
+}
diff --git a/qpid/cpp/src/qpid/acl/Acl.h b/qpid/cpp/src/qpid/acl/Acl.h
new file mode 100644
index 0000000000..df2fb66c82
--- /dev/null
+++ b/qpid/cpp/src/qpid/acl/Acl.h
@@ -0,0 +1,134 @@
+#ifndef QPID_ACL_ACL_H
+#define QPID_ACL_ACL_H
+
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+
+
+#include "qpid/acl/AclReader.h"
+#include "qpid/AclHost.h"
+#include "qpid/RefCounted.h"
+#include "qpid/broker/AclModule.h"
+#include "qpid/management/Manageable.h"
+#include "qpid/management/ManagementAgent.h"
+#include "qmf/org/apache/qpid/acl/Acl.h"
+#include "qpid/sys/Mutex.h"
+
+#include <boost/shared_ptr.hpp>
+#include <map>
+#include <string>
+
+
+namespace qpid {
+namespace broker {
+class Broker;
+class Connection;
+}
+
+namespace acl {
+class ConnectionCounter;
+class ResourceCounter;
+
+struct AclValues {
+ std::string aclFile;
+ uint16_t aclMaxConnectPerUser;
+ uint16_t aclMaxConnectPerIp;
+ uint16_t aclMaxConnectTotal;
+ uint16_t aclMaxQueuesPerUser;
+};
+
+
+class Acl : public broker::AclModule, public RefCounted, public management::Manageable
+{
+
+private:
+ acl::AclValues aclValues;
+ broker::Broker* broker;
+ bool transferAcl;
+ boost::shared_ptr<AclData> data;
+ qmf::org::apache::qpid::acl::Acl::shared_ptr mgmtObject;
+ qpid::management::ManagementAgent* agent;
+ mutable qpid::sys::Mutex dataLock;
+ boost::shared_ptr<ConnectionCounter> connectionCounter;
+ boost::shared_ptr<ResourceCounter> resourceCounter;
+ bool userRules;
+
+public:
+ Acl (AclValues& av, broker::Broker& b);
+
+ /** reportConnectLimit
+ * issue management counts and alerts for denied connections
+ */
+ void reportConnectLimit(const std::string user, const std::string addr);
+ void reportQueueLimit(const std::string user, const std::string queueName);
+
+ inline virtual bool doTransferAcl() {
+ return transferAcl;
+ };
+
+ inline virtual uint16_t getMaxConnectTotal() {
+ return aclValues.aclMaxConnectTotal;
+ };
+
+ inline virtual bool userAclRules() {
+ return userRules;
+ };
+
+// create specilied authorise methods for cases that need faster matching as needed.
+ virtual bool authorise(
+ const std::string& id,
+ const Action& action,
+ const ObjectType& objType,
+ const std::string& name,
+ std::map<Property, std::string>* params=0);
+
+ virtual bool authorise(
+ const std::string& id,
+ const Action& action,
+ const ObjectType& objType,
+ const std::string& ExchangeName,
+ const std::string& RoutingKey);
+
+ // Resource quota tracking
+ virtual bool approveConnection(const broker::Connection& connection);
+ virtual bool approveCreateQueue(const std::string& userId, const std::string& queueName);
+ virtual void recordDestroyQueue(const std::string& queueName);
+
+ virtual ~Acl();
+private:
+ bool result(
+ const AclResult& aclreslt,
+ const std::string& id,
+ const Action& action,
+ const ObjectType& objType,
+ const std::string& name);
+ bool readAclFile(std::string& errorText);
+ bool readAclFile(std::string& aclFile, std::string& errorText);
+ void loadEmptyAclRuleset();
+ Manageable::status_t lookup (management::Args& args, std::string& text);
+ Manageable::status_t lookupPublish(management::Args& args, std::string& text);
+ virtual qpid::management::ManagementObject::shared_ptr GetManagementObject(void) const;
+ virtual management::Manageable::status_t ManagementMethod (uint32_t methodId, management::Args& args, std::string& text);
+
+};
+
+}} // namespace qpid::acl
+
+#endif // QPID_ACL_ACL_H
diff --git a/qpid/cpp/src/qpid/acl/AclConnectionCounter.cpp b/qpid/cpp/src/qpid/acl/AclConnectionCounter.cpp
new file mode 100644
index 0000000000..ca3da50088
--- /dev/null
+++ b/qpid/cpp/src/qpid/acl/AclConnectionCounter.cpp
@@ -0,0 +1,320 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "AclConnectionCounter.h"
+#include "Acl.h"
+#include "qpid/broker/Connection.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/SocketAddress.h"
+#include <assert.h>
+#include <sstream>
+
+using namespace qpid::sys;
+
+namespace qpid {
+namespace acl {
+
+//
+// This module instantiates a broker::ConnectionObserver and limits client
+// connections by counting connections per user name, per client IP address
+// and per total connection count.
+//
+
+
+//
+//
+//
+ConnectionCounter::ConnectionCounter(Acl& a, uint16_t nl, uint16_t hl, uint16_t tl) :
+ acl(a), nameLimit(nl), hostLimit(hl), totalLimit(tl), totalCurrentConnections(0) {}
+
+ConnectionCounter::~ConnectionCounter() {}
+
+
+//
+// limitApproveLH
+//
+// Connection creation approver. Return true only if user is under limit.
+// Called with lock held.
+//
+bool ConnectionCounter::limitApproveLH(
+ connectCountsMap_t& theMap,
+ const std::string& theName,
+ uint16_t theLimit,
+ bool emitLog) {
+
+ bool result(true);
+ if (theLimit > 0) {
+ uint16_t count;
+ connectCountsMap_t::iterator eRef = theMap.find(theName);
+ if (eRef != theMap.end()) {
+ count = (uint16_t)(*eRef).second;
+ result = count <= theLimit;
+ } else {
+ // Not found
+ count = 0;
+ }
+ if (emitLog) {
+ QPID_LOG(trace, "ACL ConnectionApprover IP=" << theName
+ << " limit=" << theLimit
+ << " curValue=" << count
+ << " result=" << (result ? "allow" : "deny"));
+ }
+ }
+ return result;
+}
+
+
+//
+// countConnectionLH
+//
+// Increment the name's count in map and return an optional comparison
+// against a connection limit.
+// Called with dataLock already taken.
+//
+bool ConnectionCounter::countConnectionLH(
+ connectCountsMap_t& theMap,
+ const std::string& theName,
+ uint16_t theLimit,
+ bool emitLog,
+ bool enforceLimit) {
+
+ bool result(true);
+ uint16_t count(0);
+ connectCountsMap_t::iterator eRef = theMap.find(theName);
+ if (eRef != theMap.end()) {
+ count = (uint16_t)(*eRef).second + 1;
+ (*eRef).second = count;
+ } else {
+ theMap[theName] = count = 1;
+ }
+ if (enforceLimit) {
+ result = count <= theLimit;
+ }
+ if (emitLog) {
+ QPID_LOG(trace, "ACL ConnectionApprover user=" << theName
+ << " limit=" << theLimit
+ << " curValue=" << count
+ << " result=" << (result ? "allow" : "deny"));
+ }
+ return result;
+}
+
+
+//
+// releaseLH
+//
+// Decrement the name's count in map.
+// called with dataLock already taken
+//
+void ConnectionCounter::releaseLH(
+ connectCountsMap_t& theMap, const std::string& theName) {
+
+ connectCountsMap_t::iterator eRef = theMap.find(theName);
+ if (eRef != theMap.end()) {
+ uint16_t count = (uint16_t) (*eRef).second;
+ assert (count > 0);
+ if (1 == count) {
+ theMap.erase (eRef);
+ } else {
+ (*eRef).second = count - 1;
+ }
+ } else {
+ // User had no connections.
+ // Connections denied by ACL never get users added
+ //QPID_LOG(notice, "ACL ConnectionCounter Connection for '" << theName
+ // << "' not found in connection count pool");
+ }
+}
+
+
+//
+// connection - called during Connection's constructor
+//
+void ConnectionCounter::connection(broker::Connection& connection) {
+ QPID_LOG(trace, "ACL ConnectionCounter new connection: " << connection.getMgmtId());
+
+ const std::string& hostName(getClientHost(connection.getMgmtId()));
+
+ Mutex::ScopedLock locker(dataLock);
+
+ // Total connections goes up
+ totalCurrentConnections += 1;
+
+ // Record the fact that this connection exists
+ connectProgressMap[connection.getMgmtId()] = C_CREATED;
+
+ // Count the connection from this host.
+ (void) countConnectionLH(connectByHostMap, hostName, hostLimit, false, false);
+}
+
+
+//
+// closed - called during Connection's destructor
+//
+void ConnectionCounter::closed(broker::Connection& connection) {
+ QPID_LOG(trace, "ACL ConnectionCounter closed: " << connection.getMgmtId()
+ << ", userId:" << connection.getUserId());
+
+ Mutex::ScopedLock locker(dataLock);
+
+ connectCountsMap_t::iterator eRef = connectProgressMap.find(connection.getMgmtId());
+ if (eRef != connectProgressMap.end()) {
+ if ((*eRef).second == C_OPENED){
+ // Normal case: connection was created and opened.
+ // Decrement user in-use counts
+ releaseLH(connectByNameMap,
+ connection.getUserId());
+ } else {
+ // Connection was created but not opened.
+ // Don't decrement user count.
+ }
+
+ // Decrement host in-use count.
+ releaseLH(connectByHostMap,
+ getClientHost(connection.getMgmtId()));
+
+ // destroy connection progress indicator
+ connectProgressMap.erase(eRef);
+
+ } else {
+ // connection not found in progress map
+ QPID_LOG(notice, "ACL ConnectionCounter closed info for '" << connection.getMgmtId()
+ << "' not found in connection state pool");
+ }
+
+ // total connections
+ totalCurrentConnections -= 1;
+}
+
+
+//
+// approveConnection
+// check total connections, connections from IP, connections by user and
+// disallow if over any limit
+//
+bool ConnectionCounter::approveConnection(
+ const broker::Connection& connection,
+ const std::string& userName,
+ bool enforcingConnectionQuotas,
+ uint16_t connectionUserQuota,
+ boost::shared_ptr<AclData> localdata)
+{
+ const std::string& hostName(getClientHost(connection.getMgmtId()));
+
+ Mutex::ScopedLock locker(dataLock);
+
+ // Bump state from CREATED to OPENED
+ (void) countConnectionLH(connectProgressMap, connection.getMgmtId(),
+ C_OPENED, false, false);
+
+ // Run global black/white list check
+ sys::SocketAddress sa(hostName, "");
+ bool okByHostList(true);
+ std::string hostLimitText;
+ if (sa.isIp()) {
+ AclResult result = localdata->isAllowedConnection(userName, hostName, hostLimitText);
+ okByHostList = AclHelper::resultAllows(result);
+ if (okByHostList) {
+ QPID_LOG(trace, "ACL: ConnectionApprover host list " << hostLimitText);
+ }
+ }
+
+ // Approve total connections
+ bool okTotal = true;
+ if (totalLimit > 0) {
+ okTotal = totalCurrentConnections <= totalLimit;
+ QPID_LOG(trace, "ACL ConnectionApprover totalLimit=" << totalLimit
+ << " curValue=" << totalCurrentConnections
+ << " result=" << (okTotal ? "allow" : "deny"));
+ }
+
+ // Approve by IP host connections
+ bool okByIP = limitApproveLH(connectByHostMap, hostName, hostLimit, true);
+
+ // Count and Approve the connection by the user
+ bool okByUser = countConnectionLH(connectByNameMap, userName,
+ connectionUserQuota, true,
+ enforcingConnectionQuotas);
+
+ // Emit separate log for each disapproval
+ if (!okByHostList) {
+ QPID_LOG(error, "ACL: ConnectionApprover host list " << hostLimitText
+ << " Connection refused.");
+ }
+ if (!okTotal) {
+ QPID_LOG(error, "Client max total connection count limit of " << totalLimit
+ << " exceeded by '"
+ << connection.getMgmtId() << "', user: '"
+ << userName << "'. Connection refused");
+ }
+ if (!okByIP) {
+ QPID_LOG(error, "Client max per-host connection count limit of "
+ << hostLimit << " exceeded by '"
+ << connection.getMgmtId() << "', user: '"
+ << userName << "'. Connection refused.");
+ }
+ if (!okByUser) {
+ QPID_LOG(error, "Client max per-user connection count limit of "
+ << connectionUserQuota << " exceeded by '"
+ << connection.getMgmtId() << "', user: '"
+ << userName << "'. Connection refused.");
+ }
+
+ // Count/Event once for each disapproval
+ bool result = okByHostList && okTotal && okByIP && okByUser;
+ if (!result) {
+ acl.reportConnectLimit(userName, hostName);
+ }
+
+ return result;
+}
+
+//
+// getClientIp - given a connection's mgmtId return the client host part.
+//
+// TODO: Ideally this would be a method of the connection itself.
+// TODO: Verify it works with rdma connection names.
+//
+std::string ConnectionCounter::getClientHost(const std::string mgmtId)
+{
+ size_t hyphen = mgmtId.find('-');
+ if (std::string::npos != hyphen) {
+ size_t colon = mgmtId.find_last_of(':');
+ if (std::string::npos != colon) {
+ // trailing colon found
+ std::string tmp = mgmtId.substr(hyphen+1, colon - hyphen - 1);
+ // undecorate ipv6
+ if (tmp.length() >= 3 && tmp.find("[") == 0 && tmp.rfind("]") == tmp.length()-1)
+ tmp = tmp.substr(1, tmp.length()-2);
+ return tmp;
+
+ } else {
+ // colon not found - use everything after hyphen
+ return mgmtId.substr(hyphen+1);
+ }
+ }
+
+ // no hyphen found - use whole string
+ return mgmtId;
+}
+
+}} // namespace qpid::ha
diff --git a/qpid/cpp/src/qpid/acl/AclConnectionCounter.h b/qpid/cpp/src/qpid/acl/AclConnectionCounter.h
new file mode 100644
index 0000000000..3683b573ff
--- /dev/null
+++ b/qpid/cpp/src/qpid/acl/AclConnectionCounter.h
@@ -0,0 +1,106 @@
+#ifndef QPID_ACL_CONNECTIONCOUNTER_H
+#define QPID_ACL_CONNECTIONCOUNTER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/ConnectionObserver.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/acl/AclData.h"
+
+#include <map>
+
+namespace qpid {
+
+namespace broker {
+class Connection;
+}
+
+namespace acl {
+class Acl;
+
+ /**
+ * Terminate client connections when a user tries to create 'too many'.
+ * Terminate hostIp connections when an IP host tries to create 'too many'.
+ */
+class ConnectionCounter : public broker::ConnectionObserver
+{
+private:
+ typedef std::map<std::string, uint32_t> connectCountsMap_t;
+ enum CONNECTION_PROGRESS { C_CREATED=1, C_OPENED=2 };
+
+ Acl& acl;
+ uint16_t nameLimit;
+ uint16_t hostLimit;
+ uint16_t totalLimit;
+ uint16_t totalCurrentConnections;
+ qpid::sys::Mutex dataLock;
+
+ /** Records per-connection state */
+ connectCountsMap_t connectProgressMap;
+
+ /** Records per-username counts */
+ connectCountsMap_t connectByNameMap;
+
+ /** Records per-host counts */
+ connectCountsMap_t connectByHostMap;
+
+ /** Given a connection's management ID, return the client host name */
+ std::string getClientHost(const std::string mgmtId);
+
+ /** Return approval for proposed connection */
+ bool limitApproveLH(connectCountsMap_t& theMap,
+ const std::string& theName,
+ uint16_t theLimit,
+ bool emitLog);
+
+ /** Record a connection.
+ * @return indication if user/host is over its limit */
+ bool countConnectionLH(connectCountsMap_t& theMap,
+ const std::string& theName,
+ uint16_t theLimit,
+ bool emitLog,
+ bool enforceLimit);
+
+ /** Release a connection */
+ void releaseLH(connectCountsMap_t& theMap,
+ const std::string& theName);
+
+public:
+ ConnectionCounter(Acl& acl, uint16_t nl, uint16_t hl, uint16_t tl);
+ ~ConnectionCounter();
+
+ // ConnectionObserver interface
+ void connection(broker::Connection& connection);
+ void closed(broker::Connection& connection);
+
+ // Connection counting
+ bool approveConnection(const broker::Connection& conn,
+ const std::string& userName,
+ bool enforcingConnectionQuotas,
+ uint16_t connectionLimit,
+ boost::shared_ptr<AclData> localdata
+ );
+};
+
+}} // namespace qpid::ha
+
+#endif /*!QPID_ACL_CONNECTIONCOUNTER_H*/
diff --git a/qpid/cpp/src/qpid/acl/AclData.cpp b/qpid/cpp/src/qpid/acl/AclData.cpp
new file mode 100644
index 0000000000..a629e44d60
--- /dev/null
+++ b/qpid/cpp/src/qpid/acl/AclData.cpp
@@ -0,0 +1,890 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/acl/AclData.h"
+#include "qpid/acl/AclValidator.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/IntegerTypes.h"
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+#include <sstream>
+#include <iomanip>
+
+namespace qpid {
+namespace acl {
+
+//
+// Instantiate the keyword strings
+//
+const std::string AclData::ACL_KEYWORD_USER_SUBST = "${user}";
+const std::string AclData::ACL_KEYWORD_DOMAIN_SUBST = "${domain}";
+const std::string AclData::ACL_KEYWORD_USERDOMAIN_SUBST = "${userdomain}";
+const std::string AclData::ACL_KEYWORD_ALL = "all";
+const std::string AclData::ACL_KEYWORD_ACL = "acl";
+const std::string AclData::ACL_KEYWORD_GROUP = "group";
+const std::string AclData::ACL_KEYWORD_QUOTA = "quota";
+const std::string AclData::ACL_KEYWORD_QUOTA_CONNECTIONS = "connections";
+const std::string AclData::ACL_KEYWORD_QUOTA_QUEUES = "queues";
+const char AclData::ACL_SYMBOL_WILDCARD = '*';
+const std::string AclData::ACL_KEYWORD_WILDCARD = "*";
+const char AclData::ACL_SYMBOL_LINE_CONTINUATION = '\\';
+const std::string AclData::ACL_KEYWORD_DEFAULT_EXCHANGE = "amq.default";
+
+//
+// constructor
+//
+AclData::AclData():
+ decisionMode(qpid::acl::DENY),
+ transferAcl(false),
+ aclSource("UNKNOWN"),
+ connectionDecisionMode(qpid::acl::ALLOW),
+ connQuotaRuleSettings(new quotaRuleSet),
+ queueQuotaRuleSettings(new quotaRuleSet),
+ connBWHostsGlobalRules(new bwHostRuleSet),
+ connBWHostsUserRules(new bwHostUserRuleMap)
+{
+ for (unsigned int cnt=0; cnt< qpid::acl::ACTIONSIZE; cnt++) {
+ actionList[cnt]=0;
+ }
+}
+
+
+//
+// clear
+//
+void AclData::clear ()
+{
+ for (unsigned int cnt=0; cnt< qpid::acl::ACTIONSIZE; cnt++) {
+ if (actionList[cnt]) {
+ for (unsigned int cnt1=0; cnt1< qpid::acl::OBJECTSIZE; cnt1++) {
+ delete actionList[cnt][cnt1];
+ }
+ }
+ delete[] actionList[cnt];
+ }
+ transferAcl = false;
+ connectionDecisionMode = qpid::acl::ALLOW;
+ connQuotaRuleSettings->clear();
+ queueQuotaRuleSettings->clear();
+ connBWHostsGlobalRules->clear();
+ connBWHostsUserRules->clear();
+}
+
+void AclData::printDecisionRules(int userFieldWidth) {
+ AclValidator validator;
+ QPID_LOG(trace, "ACL: Decision rule cross reference");
+ for (int act=0; act<acl::ACTIONSIZE; act++) {
+ acl::Action action = acl::Action(act);
+ for (int obj=0; obj<acl::OBJECTSIZE; obj++) {
+ acl::ObjectType object = acl::ObjectType(obj);
+ if (actionList[act] != NULL && actionList[act][obj] != NULL) {
+ for (actObjItr aoitr = actionList[act][obj]->begin();
+ aoitr != actionList[act][obj]->end();
+ aoitr++) {
+ std::string user = (*aoitr).first;
+ ruleSetItr rsitr = (*aoitr).second.end();
+ for (size_t rCnt=0; rCnt < (*aoitr).second.size(); rCnt++) {
+ rsitr--;
+ std::vector<int> candidates;
+ validator.findPossibleLookupMatch(
+ action, object, rsitr->props, candidates);
+ std::stringstream ss;
+ std::string sep("");
+ for (std::vector<int>::const_iterator
+ itr = candidates.begin(); itr != candidates.end(); itr++) {
+ ss << sep << *itr;
+ sep = ",";
+ }
+ QPID_LOG(trace, "ACL: User: "
+ << std::setfill(' ') << std::setw(userFieldWidth +1) << std::left
+ << user << " "
+ << std::setfill(' ') << std::setw(acl::ACTION_STR_WIDTH +1) << std::left
+ << AclHelper::getActionStr(action)
+ << std::setfill(' ') << std::setw(acl::OBJECTTYPE_STR_WIDTH) << std::left
+ << AclHelper::getObjectTypeStr(object)
+ << " Rule: "
+ << rsitr->toString() << " may match Lookups : ("
+ << ss.str() << ")");
+ }
+ }
+ } else {
+ // no rules for action/object
+ }
+ }
+ }
+}
+
+//
+// matchProp
+//
+// Compare a rule's property name with a lookup name,
+// The rule's name may contain a trailing '*' to specify a wildcard match.
+//
+bool AclData::matchProp(const std::string& ruleStr,
+ const std::string& lookupStr)
+{
+ // allow wildcard on the end of rule strings...
+ if (ruleStr.data()[ruleStr.size()-1]==ACL_SYMBOL_WILDCARD) {
+ return ruleStr.compare(0,
+ ruleStr.size()-1,
+ lookupStr,
+ 0,
+ ruleStr.size()-1 ) == 0;
+ } else {
+ return ruleStr.compare(lookupStr) == 0;
+ }
+}
+
+
+//
+// lookupMatchRule
+//
+// Check a single rule and if it's a match return the decision
+//
+bool AclData::lookupMatchRule(
+ const ruleSetItr& rsItr,
+ const std::string& id,
+ const std::string& name,
+ const std::map<Property, std::string>* params,
+ AclResult& aclresult)
+{
+ QPID_LOG(debug, "ACL: checking rule " << rsItr->toString());
+
+ bool match = true;
+ bool limitChecked = true;
+
+ // Iterate this rule's properties. A 'match' is true when
+ // all of the rule's properties are found to be satisfied
+ // in the lookup param list. The lookup may specify things
+ // (they usually do) that are not in the rule properties but
+ // these things don't interfere with the rule match.
+
+ for (specPropertyMapItr rulePropMapItr = rsItr->props.begin();
+ (rulePropMapItr != rsItr->props.end()) && match;
+ rulePropMapItr++) {
+ // The rule property map's NAME property is given in
+ // the calling args and not in the param map.
+ if (rulePropMapItr->first == acl::SPECPROP_NAME)
+ {
+ // substitute user name into object name
+ bool result;
+ if (rsItr->ruleHasUserSub[PROP_NAME]) {
+ std::string sName(rulePropMapItr->second);
+ substituteUserId(sName, id);
+ result = matchProp(sName, name);
+ } else {
+ result = matchProp(rulePropMapItr->second, name);
+ }
+
+ if (result) {
+ QPID_LOG(debug, "ACL: lookup name '" << name
+ << "' matched with rule name '"
+ << rulePropMapItr->second << "'");
+ } else {
+ match = false;
+ QPID_LOG(debug, "ACL: lookup name '" << name
+ << "' didn't match with rule name '"
+ << rulePropMapItr->second << "'");
+ }
+ } else {
+ if (params) {
+ // The rule's property map non-NAME properties
+ // found in the lookup's params list.
+ // In some cases the param's index is not the same
+ // as rule's index.
+ propertyMapItr lookupParamItr;
+ switch (rulePropMapItr->first) {
+ case acl::SPECPROP_MAXPAGESLOWERLIMIT:
+ case acl::SPECPROP_MAXPAGESUPPERLIMIT:
+ lookupParamItr = params->find(PROP_MAXPAGES);
+ break;
+
+ case acl::SPECPROP_MAXPAGEFACTORLOWERLIMIT:
+ case acl::SPECPROP_MAXPAGEFACTORUPPERLIMIT:
+ lookupParamItr = params->find(PROP_MAXPAGEFACTOR);
+ break;
+
+ case acl::SPECPROP_MAXQUEUECOUNTUPPERLIMIT:
+ case acl::SPECPROP_MAXQUEUECOUNTLOWERLIMIT:
+ lookupParamItr = params->find(PROP_MAXQUEUECOUNT);
+ break;
+
+ case acl::SPECPROP_MAXQUEUESIZEUPPERLIMIT:
+ case acl::SPECPROP_MAXQUEUESIZELOWERLIMIT:
+ lookupParamItr = params->find(PROP_MAXQUEUESIZE);
+ break;
+
+ case acl::SPECPROP_MAXFILECOUNTUPPERLIMIT:
+ case acl::SPECPROP_MAXFILECOUNTLOWERLIMIT:
+ lookupParamItr = params->find(PROP_MAXFILECOUNT);
+ break;
+
+ case acl::SPECPROP_MAXFILESIZEUPPERLIMIT:
+ case acl::SPECPROP_MAXFILESIZELOWERLIMIT:
+ lookupParamItr = params->find(PROP_MAXFILESIZE);
+ break;
+
+ default:
+ lookupParamItr = params->find((Property)rulePropMapItr->first);
+ break;
+ };
+
+ if (lookupParamItr == params->end()) {
+ // Now the rule has a specified property
+ // that does not exist in the caller's
+ // lookup params list.
+ // This rule does not match.
+ match = false;
+ QPID_LOG(debug, "ACL: lookup parameter map doesn't contain the rule property '"
+ << AclHelper::getPropertyStr(rulePropMapItr->first) << "'");
+ } else {
+ // Now account for the business of rules
+ // whose property indexes are mismatched.
+ switch (rulePropMapItr->first) {
+ case acl::SPECPROP_MAXQUEUECOUNTUPPERLIMIT:
+ case acl::SPECPROP_MAXQUEUESIZEUPPERLIMIT:
+ case acl::SPECPROP_MAXFILECOUNTUPPERLIMIT:
+ case acl::SPECPROP_MAXFILESIZEUPPERLIMIT:
+ case acl::SPECPROP_MAXPAGESUPPERLIMIT:
+ case acl::SPECPROP_MAXPAGEFACTORUPPERLIMIT:
+ limitChecked &=
+ compareInt(
+ rulePropMapItr->first,
+ boost::lexical_cast<std::string>(rulePropMapItr->second),
+ boost::lexical_cast<std::string>(lookupParamItr->second),
+ true);
+ break;
+
+ case acl::SPECPROP_MAXQUEUECOUNTLOWERLIMIT:
+ case acl::SPECPROP_MAXQUEUESIZELOWERLIMIT:
+ case acl::SPECPROP_MAXFILECOUNTLOWERLIMIT:
+ case acl::SPECPROP_MAXFILESIZELOWERLIMIT:
+ case acl::SPECPROP_MAXPAGESLOWERLIMIT:
+ case acl::SPECPROP_MAXPAGEFACTORLOWERLIMIT:
+ limitChecked &=
+ compareInt(
+ rulePropMapItr->first,
+ boost::lexical_cast<std::string>(rulePropMapItr->second),
+ boost::lexical_cast<std::string>(lookupParamItr->second),
+ false);
+ break;
+
+ default:
+ bool result;
+ if ((SPECPROP_ALTERNATE == rulePropMapItr->first && rsItr->ruleHasUserSub[PROP_ALTERNATE]) ||
+ (SPECPROP_QUEUENAME == rulePropMapItr->first && rsItr->ruleHasUserSub[PROP_QUEUENAME])) {
+ // These properties are allowed to have username substitution
+ std::string sName(rulePropMapItr->second);
+ substituteUserId(sName, id);
+ result = matchProp(sName, lookupParamItr->second);
+ } else if (SPECPROP_ROUTINGKEY == rulePropMapItr->first) {
+ // Routing key is allowed to have username substitution
+ // and it gets topic exchange matching
+ if (rsItr->ruleHasUserSub[PROP_ROUTINGKEY]) {
+ std::string sKey(lookupParamItr->second);
+ substituteKeywords(sKey, id);
+ result = rsItr->matchRoutingKey(sKey);
+ } else {
+ result = rsItr->matchRoutingKey(lookupParamItr->second);
+ }
+ } else {
+ // Rules without substitution
+ result = matchProp(rulePropMapItr->second, lookupParamItr->second);
+ }
+
+ if (result) {
+ QPID_LOG(debug, "ACL: the pair("
+ << AclHelper::getPropertyStr(lookupParamItr->first)
+ << "," << lookupParamItr->second
+ << ") given in lookup matched the pair("
+ << AclHelper::getPropertyStr(rulePropMapItr->first) << ","
+ << rulePropMapItr->second
+ << ") given in the rule");
+ } else {
+ match = false;
+ QPID_LOG(debug, "ACL: the pair("
+ << AclHelper::getPropertyStr(lookupParamItr->first)
+ << "," << lookupParamItr->second
+ << ") given in lookup doesn't match the pair("
+ << AclHelper::getPropertyStr(rulePropMapItr->first)
+ << "," << rulePropMapItr->second
+ << ") given in the rule");
+ }
+ break;
+ };
+ }
+ } else {
+ // params don't exist.
+ }
+ }
+ }
+ if (match) {
+ aclresult = rsItr->ruleMode;
+ if (!limitChecked) {
+ // Now a lookup matched all rule properties but one
+ // of the numeric limit checks has failed.
+ // Demote allow rules to corresponding deny rules.
+ switch (aclresult) {
+ case acl::ALLOW:
+ aclresult = acl::DENY;
+ break;
+ case acl::ALLOWLOG:
+ aclresult = acl::DENYLOG;
+ break;
+ default:
+ break;
+ };
+ }
+ QPID_LOG(debug,"ACL: Successful match, the decision is:"
+ << AclHelper::getAclResultStr(aclresult));
+ } else {
+ // This rule did not match the requested lookup and
+ // does not contribute to an ACL decision.
+ }
+ return match;
+}
+
+//
+// lookup - general ACL lookup
+//
+// The ACL main business logic function of matching rules and declaring
+// an allow or deny result.
+//
+AclResult AclData::lookup(
+ const std::string& id,
+ const Action& action,
+ const ObjectType& objType,
+ const std::string& name,
+ std::map<Property, std::string>* params)
+{
+ QPID_LOG(debug, "ACL: Lookup for id:" << id
+ << " action:" << AclHelper::getActionStr((Action) action)
+ << " objectType:" << AclHelper::getObjectTypeStr((ObjectType) objType)
+ << " name:" << name
+ << " with params " << AclHelper::propertyMapToString(params));
+
+ // A typical log looks like:
+ // ACL: Lookup for id:bob@QPID action:create objectType:queue name:q2
+ // with params { durable=false passive=false autodelete=false
+ // exclusive=false alternate= policytype= maxqueuesize=0
+ // maxqueuecount=0 }
+
+ // Default result is blanket decision mode for the entire ACL list.
+ AclResult aclresult = decisionMode;
+
+ // Test for lists of rules at the intersection of the Action & Object
+ if (actionList[action] && actionList[action][objType]) {
+ // Find the list of rules for this actorId
+ AclData::actObjItr itrRule = actionList[action][objType]->find(id);
+
+ // If individual actorId not found then find a rule set for '*'.
+ if (itrRule == actionList[action][objType]->end()) {
+ itrRule = actionList[action][objType]->find(ACL_KEYWORD_WILDCARD);
+ }
+ if (itrRule != actionList[action][objType]->end()) {
+ // A list of rules exists for this actor/action/object tuple.
+ // Iterate the rule set to search for a matching rule.
+ ruleSetItr rsItr = itrRule->second.end();
+ for (int cnt = itrRule->second.size(); cnt != 0; cnt--) {
+ rsItr--;
+ if (lookupMatchRule(rsItr, id, name, params, aclresult)) {
+ return aclresult;
+ }
+ }
+ } else {
+ // The Action-Object list has entries but not for this actorId
+ // nor for *.
+ }
+ } else {
+ // The Action-Object list has no entries.
+ }
+
+ QPID_LOG(debug,"ACL: No successful match, defaulting to the decision mode "
+ << AclHelper::getAclResultStr(aclresult));
+ return aclresult;
+}
+
+
+//
+// lookupMatchPublishExchangeRule
+//
+// check a single publish exchange rule
+//
+bool AclData::lookupMatchPublishExchangeRule(
+ const ruleSetItr& rsItr,
+ const std::string& id,
+ const std::string& name,
+ const std::string& routingKey,
+ AclResult& aclresult)
+{
+ QPID_LOG(debug, "ACL: checking rule " << rsItr->toString());
+
+ // Search on exchange name and routing key only if specfied in rule.
+ bool match =true;
+ if (rsItr->pubExchNameInRule) {
+ // substitute user name into object name
+ bool result;
+
+ if (rsItr->ruleHasUserSub[PROP_NAME]) {
+ std::string sName(rsItr->pubExchName);
+ substituteUserId(sName, id);
+ result = matchProp(sName, name);
+ }
+ else if (rsItr->pubExchNameMatchesBlank) {
+ result = name.empty();
+ } else {
+ result = matchProp(rsItr->pubExchName, name);
+ }
+
+ if (result) {
+ QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum << " lookup exchange name '"
+ << name << "' matched with rule name '"
+ << rsItr->pubExchName << "'");
+ } else {
+ match= false;
+ QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum << " lookup exchange name '"
+ << name << "' did not match with rule name '"
+ << rsItr->pubExchName << "'");
+ }
+ }
+
+ if (match && rsItr->pubRoutingKeyInRule) {
+ if ((routingKey.find(ACL_KEYWORD_USER_SUBST, 0) != std::string::npos) ||
+ (routingKey.find(ACL_KEYWORD_DOMAIN_SUBST, 0) != std::string::npos) ||
+ (routingKey.find(ACL_KEYWORD_USERDOMAIN_SUBST, 0) != std::string::npos)) {
+ // The user is not allowed to present a routing key with the substitution key in it
+ QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum <<
+ " User-specified routing key has substitution wildcard:" << routingKey
+ << ". Rule match prohibited.");
+ match = false;
+ } else {
+ bool result;
+ if (rsItr->ruleHasUserSub[PROP_ROUTINGKEY]) {
+ std::string sKey(routingKey);
+ substituteKeywords(sKey, id);
+ result = rsItr->matchRoutingKey(sKey);
+ } else {
+ result = rsItr->matchRoutingKey(routingKey);
+ }
+
+ if (result) {
+ QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum << " lookup key name '"
+ << routingKey << "' matched with rule routing key '"
+ << rsItr->pubRoutingKey << "'");
+ } else {
+ QPID_LOG(debug, "ACL: Rule: " << rsItr->rawRuleNum << " lookup key name '"
+ << routingKey << "' did not match with rule routing key '"
+ << rsItr->pubRoutingKey << "'");
+ match = false;
+ }
+ }
+ }
+
+ if (match) {
+ aclresult = rsItr->ruleMode;
+ QPID_LOG(debug,"ACL: Rule: " << rsItr->rawRuleNum << " Successful match, the decision is:"
+ << AclHelper::getAclResultStr(aclresult));
+ }
+ return match;
+}
+
+//
+// lookup - special PUBLISH EXCHANGE lookup
+//
+// The ACL main business logic function of matching rules and declaring
+// an allow or deny result. This lookup is the fastpath per-message
+// lookup to verify if a user is allowed to publish to an exchange with
+// a given key.
+//
+AclResult AclData::lookup(
+ const std::string& id,
+ const Action& action,
+ const ObjectType& objType,
+ const std::string& /*Exchange*/ name,
+ const std::string& routingKey)
+{
+
+ QPID_LOG(debug, "ACL: Lookup for id:" << id
+ << " action:" << AclHelper::getActionStr((Action) action)
+ << " objectType:" << AclHelper::getObjectTypeStr((ObjectType) objType)
+ << " exchange name:" << name
+ << " with routing key " << routingKey);
+
+ AclResult aclresult = decisionMode;
+
+ if (actionList[action] && actionList[action][objType]){
+ AclData::actObjItr itrRule = actionList[action][objType]->find(id);
+
+ if (itrRule == actionList[action][objType]->end()) {
+ itrRule = actionList[action][objType]->find(ACL_KEYWORD_WILDCARD);
+ }
+ if (itrRule != actionList[action][objType]->end() ) {
+ // Found a rule list for this user-action-object set.
+ // Search the rule list for a matching rule.
+ ruleSetItr rsItr = itrRule->second.end();
+ for (int cnt = itrRule->second.size(); cnt != 0; cnt--) {
+ rsItr--;
+
+ if (lookupMatchPublishExchangeRule(rsItr, id, name, routingKey, aclresult)) {
+ return aclresult;
+ }
+ }
+ }
+ }
+ QPID_LOG(debug,"ACL: No successful match, defaulting to the decision mode "
+ << AclHelper::getAclResultStr(aclresult));
+ return aclresult;
+
+}
+
+
+
+//
+//
+//
+void AclData::setConnQuotaRuleSettings (
+ boost::shared_ptr<quotaRuleSet> quotaPtr)
+{
+ connQuotaRuleSettings = quotaPtr;
+}
+
+
+//
+// getConnQuotaForUser
+//
+// Return the true or false value of connQuotaRulesExist,
+// indicating whether any kind of lookup was done or not.
+//
+// When lookups are performed return the result value of
+// 1. The user's setting else
+// 2. The 'all' user setting else
+// 3. Zero
+// When lookups are not performed then return a result value of Zero.
+//
+bool AclData::getConnQuotaForUser(const std::string& theUserName,
+ uint16_t* theResult) const {
+ if (this->enforcingConnectionQuotas()) {
+ // look for this user explicitly
+ quotaRuleSetItr nameItr = (*connQuotaRuleSettings).find(theUserName);
+ if (nameItr != (*connQuotaRuleSettings).end()) {
+ QPID_LOG(trace, "ACL: Connection quota for user " << theUserName
+ << " explicitly set to : " << (*nameItr).second);
+ *theResult = (*nameItr).second;
+ } else {
+ // Look for the 'all' user
+ nameItr = (*connQuotaRuleSettings).find(ACL_KEYWORD_ALL);
+ if (nameItr != (*connQuotaRuleSettings).end()) {
+ QPID_LOG(trace, "ACL: Connection quota for user " << theUserName
+ << " chosen through value for 'all' : " << (*nameItr).second);
+ *theResult = (*nameItr).second;
+ } else {
+ // Neither userName nor "all" found.
+ QPID_LOG(trace, "ACL: Connection quota for user " << theUserName
+ << " absent in quota settings. Return value : 0");
+ *theResult = 0;
+ }
+ }
+ } else {
+ // Rules do not exist
+ QPID_LOG(trace, "ACL: Connection quota for user " << theUserName
+ << " unavailable; quota settings are not specified. Return value : 0");
+ *theResult = 0;
+ }
+ return this->enforcingConnectionQuotas();
+}
+
+//
+//
+//
+void AclData::setQueueQuotaRuleSettings (
+ boost::shared_ptr<quotaRuleSet> quotaPtr)
+{
+ queueQuotaRuleSettings = quotaPtr;
+}
+
+
+//
+// getQueueQuotaForUser
+//
+// Return the true or false value of queueQuotaRulesExist,
+// indicating whether any kind of lookup was done or not.
+//
+// When lookups are performed return the result value of
+// 1. The user's setting else
+// 2. The 'all' user setting else
+// 3. Zero
+// When lookups are not performed then return a result value of Zero.
+//
+bool AclData::getQueueQuotaForUser(const std::string& theUserName,
+ uint16_t* theResult) const {
+ if (this->enforcingQueueQuotas()) {
+ // look for this user explicitly
+ quotaRuleSetItr nameItr = (*queueQuotaRuleSettings).find(theUserName);
+ if (nameItr != (*queueQuotaRuleSettings).end()) {
+ QPID_LOG(trace, "ACL: Queue quota for user " << theUserName
+ << " explicitly set to : " << (*nameItr).second);
+ *theResult = (*nameItr).second;
+ } else {
+ // Look for the 'all' user
+ nameItr = (*queueQuotaRuleSettings).find(ACL_KEYWORD_ALL);
+ if (nameItr != (*queueQuotaRuleSettings).end()) {
+ QPID_LOG(trace, "ACL: Queue quota for user " << theUserName
+ << " chosen through value for 'all' : " << (*nameItr).second);
+ *theResult = (*nameItr).second;
+ } else {
+ // Neither userName nor "all" found.
+ QPID_LOG(trace, "ACL: Queue quota for user " << theUserName
+ << " absent in quota settings. Return value : 0");
+ *theResult = 0;
+ }
+ }
+ } else {
+ // Rules do not exist
+ QPID_LOG(trace, "ACL: Queue quota for user " << theUserName
+ << " unavailable; quota settings are not specified. Return value : 0");
+ *theResult = 0;
+ }
+ return this->enforcingQueueQuotas();
+}
+
+void AclData::setConnGlobalRules (boost::shared_ptr<bwHostRuleSet> cgr) {
+ connBWHostsGlobalRules = cgr;
+}
+
+void AclData::setConnUserRules (boost::shared_ptr<bwHostUserRuleMap> hurm) {
+ connBWHostsUserRules = hurm;
+}
+
+AclResult AclData::isAllowedConnection(const std::string& userName,
+ const std::string& hostName,
+ std::string& logText) {
+ bool decisionMade(false);
+ AclResult result(ALLOW);
+ for (bwHostRuleSetItr it=connBWHostsGlobalRules->begin();
+ it!=connBWHostsGlobalRules->end(); it++) {
+ if (it->getAclHost().match(hostName)) {
+ // This host matches a global spec and controls the
+ // allow/deny decision for this connection.
+ result = it->getAclResult();
+ logText = QPID_MSG("global rule " << it->toString()
+ << (AclHelper::resultAllows(result) ? " allows" : " denies")
+ << " connection for host " << hostName << ", user "
+ << userName);
+ decisionMade = true;
+ break;
+ } else {
+ // This rule in the global spec doesn't match and
+ // does not control the allow/deny decision.
+ }
+ }
+
+ // Run user black/white list check
+ if (!decisionMade) {
+ bwHostUserRuleMapItr itrRule = connBWHostsUserRules->find(userName);
+ if (itrRule != connBWHostsUserRules->end()) {
+ for (bwHostRuleSetItr it=(*itrRule).second.begin();
+ it!=(*itrRule).second.end(); it++) {
+ if (it->getAclHost().match(hostName)) {
+ // This host matches a user spec and controls the
+ // allow/deny decision for this connection.
+ result = it->getAclResult();
+ logText = QPID_MSG("global rule " << it->toString()
+ << (AclHelper::resultAllows(result) ? " allows" : " denies")
+ << " connection for host " << hostName << ", user "
+ << userName);
+ decisionMade = true;
+ break;
+ } else {
+ // This rule in the user's spec doesn't match and
+ // does not control the allow/deny decision.
+ }
+ }
+ }
+ }
+
+ // Apply global connection mode
+ if (!decisionMade) {
+ result = connectionDecisionMode;
+ logText = QPID_MSG("default connection policy "
+ << (AclHelper::resultAllows(result) ? "allows" : "denies")
+ << " connection for host " << hostName << ", user "
+ << userName);
+ }
+ return result;
+}
+
+//
+//
+//
+AclData::~AclData()
+{
+ clear();
+}
+
+
+//
+// Limit check an int limit
+//
+bool AclData::compareInt(const qpid::acl::SpecProperty theProperty,
+ const std::string theAclValue,
+ const std::string theLookupValue,
+ bool theMaxFlag)
+{
+ uint64_t aclRuleValue (0);
+ uint64_t lookupValue (0);
+
+ QPID_LOG(debug, "ACL: "
+ << (theMaxFlag ? "Upper" : "Lower") << "-limit comparison for property "
+ << AclHelper::getPropertyStr(theProperty)
+ << ". Success if lookup(" << theLookupValue
+ << ") "
+ << (theMaxFlag ? "<=" : ">=") << " rule(" << theAclValue << ")");
+
+ try {
+ aclRuleValue = boost::lexical_cast<uint64_t>(theAclValue);
+ }
+ catch(const boost::bad_lexical_cast&) {
+ assert (false);
+ return false;
+ }
+
+ if (aclRuleValue == 0) {
+ QPID_LOG(debug, "ACL: Comparison is always true when ACL rule value is zero");
+ return true;
+ }
+
+ try {
+ lookupValue = boost::lexical_cast<uint64_t>(theLookupValue);
+ }
+ catch(const boost::bad_lexical_cast&) {
+ QPID_LOG(error,"ACL: Illegal value given in lookup for property '"
+ << AclHelper::getPropertyStr(theProperty)
+ << "' : " << theLookupValue);
+ return false;
+ }
+
+ bool result =
+ (theMaxFlag ? lookupValue > aclRuleValue : lookupValue < aclRuleValue);
+ if ( result ) {
+ QPID_LOG(debug, "ACL: Limit exceeded for property '"
+ << AclHelper::getPropertyStr(theProperty) << "'");
+ return false;
+ }
+
+ return true;
+}
+
+const std::string DOMAIN_SEPARATOR("@");
+const std::string PERIOD(".");
+const std::string UNDERSCORE("_");
+//
+// substituteString
+// Given a name string from an Acl rule, substitute the replacement into it
+// wherever the placeholder directs.
+//
+void AclData::substituteString(std::string& targetString,
+ const std::string& placeholder,
+ const std::string& replacement)
+{
+ assert (!placeholder.empty());
+ if (placeholder.empty()) {
+ return;
+ }
+ size_t start_pos(0);
+ while((start_pos = targetString.find(placeholder, start_pos)) != std::string::npos) {
+ targetString.replace(start_pos, placeholder.length(), replacement);
+ start_pos += replacement.length();
+ }
+}
+
+
+//
+// normalizeUserId
+// Given a name string return it in a form usable as topic keys:
+// change "@" and "." to "_".
+//
+std::string AclData::normalizeUserId(const std::string& userId)
+{
+ std::string normalId(userId);
+ substituteString(normalId, DOMAIN_SEPARATOR, UNDERSCORE);
+ substituteString(normalId, PERIOD, UNDERSCORE);
+ return normalId;
+}
+
+
+//
+// substituteUserId
+// Given an Acl rule and an authenticated userId
+// do the keyword substitutions on the rule.
+//
+void AclData::substituteUserId(std::string& ruleString,
+ const std::string& userId)
+{
+ size_t locDomSeparator(0);
+ std::string user("");
+ std::string domain("");
+ std::string userdomain = normalizeUserId(userId);
+
+ locDomSeparator = userId.find(DOMAIN_SEPARATOR);
+ if (std::string::npos == locDomSeparator) {
+ // "@" not found. There's just a user name
+ user = normalizeUserId(userId);
+ } else {
+ // "@" found, split the names. Domain may be blank.
+ user = normalizeUserId(userId.substr(0,locDomSeparator));
+ domain = normalizeUserId(userId.substr(locDomSeparator+1));
+ }
+
+ substituteString(ruleString, ACL_KEYWORD_USER_SUBST, user);
+ substituteString(ruleString, ACL_KEYWORD_DOMAIN_SUBST, domain);
+ substituteString(ruleString, ACL_KEYWORD_USERDOMAIN_SUBST, userdomain);
+}
+
+
+//
+// substituteKeywords
+// Given an Acl rule and an authenticated userId
+// do reverse keyword substitutions on the rule.
+// That is, replace the normalized name in the rule string with
+// the keyword that represents it. This stragegy is used for
+// topic key lookups where the keyword string proper is in the
+// topic key search tree.
+//
+void AclData::substituteKeywords(std::string& ruleString,
+ const std::string& userId)
+{
+ size_t locDomSeparator(0);
+ std::string user("");
+ std::string domain("");
+ std::string userdomain = normalizeUserId(userId);
+
+ locDomSeparator = userId.find(DOMAIN_SEPARATOR);
+ if (std::string::npos == locDomSeparator) {
+ // "@" not found. There's just a user name
+ user = normalizeUserId(userId);
+ } else {
+ // "@" found, split the names
+ user = normalizeUserId(userId.substr(0,locDomSeparator));
+ domain = normalizeUserId(userId.substr(locDomSeparator+1));
+ }
+ std::string oRule(ruleString);
+ substituteString(ruleString, userdomain, ACL_KEYWORD_USERDOMAIN_SUBST);
+ substituteString(ruleString, user, ACL_KEYWORD_USER_SUBST);
+ substituteString(ruleString, domain, ACL_KEYWORD_DOMAIN_SUBST);
+}
+}}
diff --git a/qpid/cpp/src/qpid/acl/AclData.h b/qpid/cpp/src/qpid/acl/AclData.h
new file mode 100644
index 0000000000..105a5d9c67
--- /dev/null
+++ b/qpid/cpp/src/qpid/acl/AclData.h
@@ -0,0 +1,323 @@
+#ifndef QPID_ACL_ACLDATA_H
+#define QPID_ACL_ACLDATA_H
+
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/broker/AclModule.h"
+#include "qpid/AclHost.h"
+#include "AclTopicMatch.h"
+#include "qpid/log/Statement.h"
+#include "boost/shared_ptr.hpp"
+#include <vector>
+#include <sstream>
+
+namespace qpid {
+namespace acl {
+
+/** A rule for tracking black/white host connection settings.
+ * When a connection is attempted, the remote host is verified
+ * against lists of these rules. When the remote host is in
+ * the range specified by this aclHost then the AclResult is
+ * applied as allow/deny.
+ */
+class AclBWHostRule {
+public:
+ AclBWHostRule(AclResult r, std::string h) :
+ aclResult(r), aclHost(h) {}
+
+ std::string toString () const {
+ std::ostringstream ruleStr;
+ ruleStr << "[ruleMode = " << AclHelper::getAclResultStr(aclResult)
+ << " {" << aclHost.str() << "}";
+ return ruleStr.str();
+ }
+ const AclHost& getAclHost() const { return aclHost; }
+ const AclResult& getAclResult() const { return aclResult; }
+
+private:
+ AclResult aclResult;
+ AclHost aclHost;
+};
+
+
+class AclData {
+
+
+public:
+
+ typedef std::map<qpid::acl::Property, std::string> propertyMap;
+ typedef propertyMap::const_iterator propertyMapItr;
+
+ typedef std::map<qpid::acl::SpecProperty, std::string> specPropertyMap;
+ typedef specPropertyMap::const_iterator specPropertyMapItr;
+
+ //
+ // Rule
+ //
+ // Created by AclReader and stored in a ruleSet vector for subsequent
+ // run-time lookup matching and allow/deny decisions.
+ // RuleSet vectors are indexed by Action-Object-actorId so these
+ // attributes are not part of a rule.
+ // A single ACL file entry may create many rule entries in
+ // many ruleset vectors.
+ //
+ struct Rule {
+ typedef broker::TopicExchange::TopicExchangeTester topicTester;
+
+ int rawRuleNum; // rule number in ACL file
+ qpid::acl::AclResult ruleMode; // combined allow/deny log/nolog
+ specPropertyMap props; // properties to be matched
+ // pubXxx for publish exchange fastpath
+ bool pubRoutingKeyInRule;
+ std::string pubRoutingKey;
+ boost::shared_ptr<topicTester> pTTest;
+ bool pubExchNameInRule;
+ bool pubExchNameMatchesBlank;
+ std::string pubExchName;
+ std::vector<bool> ruleHasUserSub;
+ std::string lookupSource;
+ std::string lookupHelp;
+
+ Rule (int ruleNum, qpid::acl::AclResult res, specPropertyMap& p) :
+ rawRuleNum(ruleNum),
+ ruleMode(res),
+ props(p),
+ pubRoutingKeyInRule(false),
+ pubRoutingKey(),
+ pTTest(boost::shared_ptr<topicTester>(new topicTester())),
+ pubExchNameInRule(false),
+ pubExchNameMatchesBlank(false),
+ pubExchName(),
+ ruleHasUserSub(PROPERTYSIZE, false)
+ {}
+
+ // Variation of Rule for tracking PropertyDefs
+ // for AclValidation.
+ Rule (int ruleNum, qpid::acl::AclResult res, specPropertyMap& p,
+ const std::string& ls, const std::string& lh
+ ) :
+ rawRuleNum(ruleNum),
+ ruleMode(res),
+ props(p),
+ pubRoutingKeyInRule(false),
+ pubRoutingKey(),
+ pubExchNameInRule(false),
+ pubExchNameMatchesBlank(false),
+ pubExchName(),
+ ruleHasUserSub(PROPERTYSIZE, false),
+ lookupSource(ls),
+ lookupHelp(lh)
+ {}
+
+
+ std::string toString () const {
+ std::ostringstream ruleStr;
+ ruleStr << "[rule " << rawRuleNum
+ << " ruleMode = " << AclHelper::getAclResultStr(ruleMode)
+ << " props{";
+ for (specPropertyMapItr pMItr = props.begin();
+ pMItr != props.end();
+ pMItr++) {
+ ruleStr << " "
+ << AclHelper::getPropertyStr((SpecProperty) pMItr-> first)
+ << "=" << pMItr->second;
+ }
+ ruleStr << " }]";
+ return ruleStr.str();
+ }
+
+ void addTopicTest(const std::string& pattern) {
+ pTTest->addBindingKey(broker::TopicExchange::normalize(pattern));
+ }
+
+ // Topic Exchange tester
+ // return true if any bindings match 'pattern'
+ bool matchRoutingKey(const std::string& pattern) const
+ {
+ topicTester::BindingVec bv;
+ return pTTest->findMatches(pattern, bv);
+ }
+ };
+
+ typedef std::vector<Rule> ruleSet;
+ typedef ruleSet::const_iterator ruleSetItr;
+ typedef std::map<std::string, ruleSet > actionObject; // user
+ typedef actionObject::iterator actObjItr;
+ typedef actionObject* aclAction;
+ typedef std::map<std::string, uint16_t> quotaRuleSet; // <username, N>
+ typedef quotaRuleSet::const_iterator quotaRuleSetItr;
+ typedef std::vector<AclBWHostRule> bwHostRuleSet; // allow/deny hosts-vector
+ typedef bwHostRuleSet::const_iterator bwHostRuleSetItr;
+ typedef std::map<std::string, bwHostRuleSet> bwHostUserRuleMap; //<username, hosts-vector>
+ typedef bwHostUserRuleMap::const_iterator bwHostUserRuleMapItr;
+
+ // Action*[] -> Object*[] -> map<user, set<Rule> >
+ aclAction* actionList[qpid::acl::ACTIONSIZE];
+ qpid::acl::AclResult decisionMode; // allow/deny[-log] if no matching rule found
+ bool transferAcl;
+ std::string aclSource;
+ qpid::acl::AclResult connectionDecisionMode;
+
+ AclResult lookup(
+ const std::string& id, // actor id
+ const Action& action,
+ const ObjectType& objType,
+ const std::string& name, // object name
+ std::map<Property, std::string>* params=0);
+
+ AclResult lookup(
+ const std::string& id, // actor id
+ const Action& action,
+ const ObjectType& objType,
+ const std::string& ExchangeName,
+ const std::string& RoutingKey);
+
+ boost::shared_ptr<const bwHostRuleSet> getGlobalConnectionRules() {
+ return connBWHostsGlobalRules;
+ }
+
+ boost::shared_ptr<const bwHostUserRuleMap> getUserConnectionRules() {
+ return connBWHostsUserRules;
+ }
+
+ bool matchProp(const std::string & src, const std::string& src1);
+ void clear ();
+ void printDecisionRules(int userFieldWidth);
+
+ static const std::string ACL_KEYWORD_USER_SUBST;
+ static const std::string ACL_KEYWORD_DOMAIN_SUBST;
+ static const std::string ACL_KEYWORD_USERDOMAIN_SUBST;
+ static const std::string ACL_KEYWORD_ALL;
+ static const std::string ACL_KEYWORD_ACL;
+ static const std::string ACL_KEYWORD_GROUP;
+ static const std::string ACL_KEYWORD_QUOTA;
+ static const std::string ACL_KEYWORD_QUOTA_CONNECTIONS;
+ static const std::string ACL_KEYWORD_QUOTA_QUEUES;
+ static const char ACL_SYMBOL_WILDCARD;
+ static const std::string ACL_KEYWORD_WILDCARD;
+ static const char ACL_SYMBOL_LINE_CONTINUATION;
+ static const std::string ACL_KEYWORD_DEFAULT_EXCHANGE;
+
+ void substituteString(std::string& targetString,
+ const std::string& placeholder,
+ const std::string& replacement);
+ std::string normalizeUserId(const std::string& userId);
+ void substituteUserId(std::string& ruleString,
+ const std::string& userId);
+ void substituteKeywords(std::string& ruleString,
+ const std::string& userId);
+
+ // Per user connection quotas extracted from acl rule file
+ // Set by reader
+ void setConnQuotaRuleSettings (boost::shared_ptr<quotaRuleSet>);
+ // Get by connection approvers
+ bool enforcingConnectionQuotas() const { return connQuotaRuleSettings->size() > 0; }
+ bool getConnQuotaForUser(const std::string&, uint16_t*) const;
+
+ // Per user queue quotas extracted from acl rule file
+ // Set by reader
+ void setQueueQuotaRuleSettings (boost::shared_ptr<quotaRuleSet>);
+ // Get by queue approvers
+ bool enforcingQueueQuotas() const { return queueQuotaRuleSettings->size() > 0; }
+ bool getQueueQuotaForUser(const std::string&, uint16_t*) const;
+
+ // Global connection Black/White list rules
+ void setConnGlobalRules (boost::shared_ptr<bwHostRuleSet>);
+
+ // Per-user connection Black/White list rules map
+ void setConnUserRules (boost::shared_ptr<bwHostUserRuleMap>);
+
+ /** getConnectMaxSpec
+ * Connection quotas are held in uint16_t variables.
+ * This function specifies the largest value that a user is allowed
+ * to declare for a connection quota. The upper limit serves two
+ * purposes: 1. It leaves room for magic numbers that may be declared
+ * by keyword names in Acl files and not have those numbers conflict
+ * with innocent user declared values, and 2. It makes the unsigned
+ * math very close to _MAX work reliably with no risk of accidental
+ * wrapping back to zero.
+ */
+ static uint16_t getConnectMaxSpec() {
+ return 65530;
+ }
+ static std::string getMaxConnectSpecStr() {
+ return "65530";
+ }
+
+ static uint16_t getQueueMaxSpec() {
+ return 65530;
+ }
+ static std::string getMaxQueueSpecStr() {
+ return "65530";
+ }
+
+ /**
+ * isAllowedConnection
+ * Return true if this user is allowed to connect to this host.
+ * Return log text describing both success and failure.
+ */
+ AclResult isAllowedConnection(const std::string& userName,
+ const std::string& hostName,
+ std::string& logText);
+
+ AclResult connectionMode() const {
+ return connectionDecisionMode;
+ }
+
+ AclData();
+ virtual ~AclData();
+
+private:
+
+ inline bool lookupMatchRule(
+ const ruleSetItr& rsItr,
+ const std::string& id,
+ const std::string& name,
+ const std::map<Property, std::string>* params,
+ AclResult& aclresult);
+
+ inline bool lookupMatchPublishExchangeRule(
+ const ruleSetItr& rsItr,
+ const std::string& id,
+ const std::string& name,
+ const std::string& routingKey,
+ AclResult& aclresult);
+
+ bool compareInt(const qpid::acl::SpecProperty theProperty,
+ const std::string theAclValue,
+ const std::string theLookupValue,
+ bool theMaxFlag);
+
+ // Per-user connection quota
+ boost::shared_ptr<quotaRuleSet> connQuotaRuleSettings;
+
+ // Per-user queue quota
+ boost::shared_ptr<quotaRuleSet> queueQuotaRuleSettings;
+
+ // Global host connection black/white rule set
+ boost::shared_ptr<bwHostRuleSet> connBWHostsGlobalRules;
+
+ // Per-user host connection black/white rule set map
+ boost::shared_ptr<bwHostUserRuleMap> connBWHostsUserRules;
+};
+
+}} // namespace qpid::acl
+
+#endif // QPID_ACL_ACLDATA_H
diff --git a/qpid/cpp/src/qpid/acl/AclLexer.cpp b/qpid/cpp/src/qpid/acl/AclLexer.cpp
new file mode 100644
index 0000000000..4006e5271f
--- /dev/null
+++ b/qpid/cpp/src/qpid/acl/AclLexer.cpp
@@ -0,0 +1,141 @@
+/*
+ *
+ * Copyright (c) 2014 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/acl/AclLexer.h"
+#include "qpid/RefCounted.h"
+#include "qpid/Exception.h"
+#include <boost/shared_ptr.hpp>
+#include <boost/concept_check.hpp>
+#include <iostream>
+#include <map>
+#include <set>
+#include <string>
+#include <sstream>
+
+namespace qpid {
+namespace acl {
+
+// ObjectType
+const std::string objectNames[OBJECTSIZE] = {
+ "broker", "connection", "exchange", "link", "method", "query", "queue" };
+
+ObjectType AclHelper::getObjectType(const std::string& str) {
+ for (int i=0; i< OBJECTSIZE; ++i) {
+ if (str.compare(objectNames[i]) == 0)
+ return ObjectType(i);
+ }
+ throw qpid::Exception("Acl illegal object name: " + str);
+}
+
+const std::string& AclHelper::getObjectTypeStr(const ObjectType o) {
+ return objectNames[o];
+}
+
+// Action
+const std::string actionNames[ACTIONSIZE] = {
+ "access", "bind", "consume", "create", "delete",
+ "move", "publish", "purge", "redirect", "reroute",
+ "unbind", "update" };
+
+Action AclHelper::getAction(const std::string& str) {
+ for (int i=0; i< ACTIONSIZE; ++i) {
+ if (str.compare(actionNames[i]) == 0)
+ return Action(i);
+ }
+ throw qpid::Exception("Acl illegal action name: " + str);
+}
+
+const std::string& AclHelper::getActionStr(const Action a) {
+ return actionNames[a];
+}
+
+// Property
+// These are shared between broker and acl using code enums.
+const std::string propertyNames[PROPERTYSIZE] = {
+ "name", "durable", "owner", "routingkey", "autodelete", "exclusive", "type",
+ "alternate", "queuename", "exchangename", "schemapackage",
+ "schemaclass", "policytype", "paging", "host",
+
+ "maxpages", "maxpagefactor",
+ "maxqueuesize", "maxqueuecount", "maxfilesize", "maxfilecount"};
+
+Property AclHelper::getProperty(const std::string& str) {
+ for (int i=0; i< PROPERTYSIZE; ++i) {
+ if (str.compare(propertyNames[i]) == 0)
+ return Property(i);
+ }
+ throw qpid::Exception("Acl illegal property name: " + str);
+}
+
+const std::string& AclHelper::getPropertyStr(const Property p) {
+ return propertyNames[p];
+}
+
+// SpecProperty
+// These are shared between user acl files and acl using text.
+const std::string specPropertyNames[SPECPROPSIZE] = {
+ "name", "durable", "owner", "routingkey", "autodelete", "exclusive", "type",
+ "alternate", "queuename", "exchangename", "schemapackage",
+ "schemaclass", "policytype", "paging", "host",
+
+ "queuemaxsizelowerlimit", "queuemaxsizeupperlimit",
+ "queuemaxcountlowerlimit", "queuemaxcountupperlimit",
+ "filemaxsizelowerlimit", "filemaxsizeupperlimit",
+ "filemaxcountlowerlimit", "filemaxcountupperlimit",
+ "pageslowerlimit", "pagesupperlimit",
+ "pagefactorlowerlimit", "pagefactorupperlimit" };
+
+SpecProperty AclHelper::getSpecProperty(const std::string& str) {
+ for (int i=0; i< SPECPROPSIZE; ++i) {
+ if (str.compare(specPropertyNames[i]) == 0)
+ return SpecProperty(i);
+ }
+ // Allow old names in ACL file as aliases for newly-named properties
+ if (str.compare("maxqueuesize") == 0)
+ return SPECPROP_MAXQUEUESIZEUPPERLIMIT;
+ if (str.compare("maxqueuecount") == 0)
+ return SPECPROP_MAXQUEUECOUNTUPPERLIMIT;
+ throw qpid::Exception("Acl illegal spec property name: " + str);
+}
+
+const std::string& AclHelper::getPropertyStr(const SpecProperty p) {
+ return specPropertyNames[p];
+}
+
+// AclResult
+const std::string resultNames[RESULTSIZE] = {
+ "allow", "allow-log", "deny", "deny-log" };
+
+AclResult AclHelper::getAclResult(const std::string& str) {
+ for (int i=0; i< RESULTSIZE; ++i) {
+ if (str.compare(resultNames[i]) == 0)
+ return AclResult(i);
+ }
+ throw qpid::Exception("Acl illegal result name: " + str);
+}
+
+const std::string& AclHelper::getAclResultStr(const AclResult r) {
+ return resultNames[r];
+}
+
+bool AclHelper::resultAllows(const AclResult r) {
+ bool answer = r == ALLOW || r == ALLOWLOG;
+ return answer;
+}
+
+}} // namespace qpid::acl
diff --git a/qpid/cpp/src/qpid/acl/AclLexer.h b/qpid/cpp/src/qpid/acl/AclLexer.h
new file mode 100644
index 0000000000..d3df411afd
--- /dev/null
+++ b/qpid/cpp/src/qpid/acl/AclLexer.h
@@ -0,0 +1,200 @@
+#ifndef QPID_ACL_ACLLEXER_H
+#define QPID_ACL_ACLLEXER_H
+
+/*
+ *
+ * Copyright (c) 2014 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+
+#include "qpid/Exception.h"
+#include "qpid/broker/BrokerImportExport.h"
+#include <boost/shared_ptr.hpp>
+#include <iostream>
+#include <map>
+#include <set>
+#include <string>
+#include <sstream>
+
+namespace qpid {
+
+namespace acl {
+
+ // Interface enumerations.
+ // These enumerations define enum lists and implied text strings
+ // to match. They are used in two areas:
+ // 1. In the ACL specifications in the ACL file, file parsing, and
+ // internal rule storage.
+ // 2. In the authorize interface in the rest of the broker where
+ // code requests the ACL module to authorize an action.
+
+ // ObjectType shared between ACL spec and ACL authorise interface
+ enum ObjectType {
+ OBJ_BROKER,
+ OBJ_CONNECTION,
+ OBJ_EXCHANGE,
+ OBJ_LINK,
+ OBJ_METHOD,
+ OBJ_QUERY,
+ OBJ_QUEUE,
+ OBJECTSIZE }; // OBJECTSIZE must be last in list
+
+ const int OBJECTTYPE_STR_WIDTH = 10;
+
+ // Action shared between ACL spec and ACL authorise interface
+ enum Action {
+ ACT_ACCESS,
+ ACT_BIND,
+ ACT_CONSUME,
+ ACT_CREATE,
+ ACT_DELETE,
+ ACT_MOVE,
+ ACT_PUBLISH,
+ ACT_PURGE,
+ ACT_REDIRECT,
+ ACT_REROUTE,
+ ACT_UNBIND,
+ ACT_UPDATE,
+ ACTIONSIZE }; // ACTIONSIZE must be last in list
+
+ const int ACTION_STR_WIDTH = 8;
+
+ // Property used in ACL authorize interface
+ enum Property {
+ PROP_NAME,
+ PROP_DURABLE,
+ PROP_OWNER,
+ PROP_ROUTINGKEY,
+ PROP_AUTODELETE,
+ PROP_EXCLUSIVE,
+ PROP_TYPE,
+ PROP_ALTERNATE,
+ PROP_QUEUENAME,
+ PROP_EXCHANGENAME,
+ PROP_SCHEMAPACKAGE,
+ PROP_SCHEMACLASS,
+ PROP_POLICYTYPE,
+ PROP_PAGING,
+ PROP_HOST,
+ PROP_MAXPAGES,
+ PROP_MAXPAGEFACTOR,
+ PROP_MAXQUEUESIZE,
+ PROP_MAXQUEUECOUNT,
+ PROP_MAXFILESIZE,
+ PROP_MAXFILECOUNT,
+ PROPERTYSIZE // PROPERTYSIZE must be last in list
+ };
+
+ // Property used in ACL spec file
+ // Note for properties common to file processing/rule storage and to
+ // broker rule lookups the identical enum values are used.
+ enum SpecProperty {
+ SPECPROP_NAME = PROP_NAME,
+ SPECPROP_DURABLE = PROP_DURABLE,
+ SPECPROP_OWNER = PROP_OWNER,
+ SPECPROP_ROUTINGKEY = PROP_ROUTINGKEY,
+ SPECPROP_AUTODELETE = PROP_AUTODELETE,
+ SPECPROP_EXCLUSIVE = PROP_EXCLUSIVE,
+ SPECPROP_TYPE = PROP_TYPE,
+ SPECPROP_ALTERNATE = PROP_ALTERNATE,
+ SPECPROP_QUEUENAME = PROP_QUEUENAME,
+ SPECPROP_EXCHANGENAME = PROP_EXCHANGENAME,
+ SPECPROP_SCHEMAPACKAGE = PROP_SCHEMAPACKAGE,
+ SPECPROP_SCHEMACLASS = PROP_SCHEMACLASS,
+ SPECPROP_POLICYTYPE = PROP_POLICYTYPE,
+ SPECPROP_PAGING = PROP_PAGING,
+ SPECPROP_HOST = PROP_HOST,
+
+ SPECPROP_MAXQUEUESIZELOWERLIMIT,
+ SPECPROP_MAXQUEUESIZEUPPERLIMIT,
+ SPECPROP_MAXQUEUECOUNTLOWERLIMIT,
+ SPECPROP_MAXQUEUECOUNTUPPERLIMIT,
+ SPECPROP_MAXFILESIZELOWERLIMIT,
+ SPECPROP_MAXFILESIZEUPPERLIMIT,
+ SPECPROP_MAXFILECOUNTLOWERLIMIT,
+ SPECPROP_MAXFILECOUNTUPPERLIMIT,
+ SPECPROP_MAXPAGESLOWERLIMIT,
+ SPECPROP_MAXPAGESUPPERLIMIT,
+ SPECPROP_MAXPAGEFACTORLOWERLIMIT,
+ SPECPROP_MAXPAGEFACTORUPPERLIMIT,
+ SPECPROPSIZE // SPECPROPSIZE must be last
+ };
+
+// AclResult shared between ACL spec and ACL authorise interface
+ enum AclResult {
+ ALLOW,
+ ALLOWLOG,
+ DENY,
+ DENYLOG,
+ RESULTSIZE
+ };
+
+
+ QPID_BROKER_CLASS_EXTERN class AclHelper {
+ private:
+ AclHelper(){}
+ public:
+ static QPID_BROKER_EXTERN ObjectType getObjectType(const std::string& str);
+ static QPID_BROKER_EXTERN const std::string& getObjectTypeStr(const ObjectType o);
+ static QPID_BROKER_EXTERN Action getAction(const std::string& str);
+ static QPID_BROKER_EXTERN const std::string& getActionStr(const Action a);
+ static QPID_BROKER_EXTERN Property getProperty(const std::string& str);
+ static QPID_BROKER_EXTERN const std::string& getPropertyStr(const Property p);
+ static QPID_BROKER_EXTERN SpecProperty getSpecProperty(const std::string& str);
+ static QPID_BROKER_EXTERN const std::string& getPropertyStr(const SpecProperty p);
+ static QPID_BROKER_EXTERN AclResult getAclResult(const std::string& str);
+ static QPID_BROKER_EXTERN const std::string& getAclResultStr(const AclResult r);
+ static QPID_BROKER_EXTERN bool resultAllows(const AclResult r);
+
+ typedef std::set<Property> propSet;
+ typedef boost::shared_ptr<propSet> propSetPtr;
+ typedef std::pair<Action, propSetPtr> actionPair;
+ typedef std::map<Action, propSetPtr> actionMap;
+ typedef boost::shared_ptr<actionMap> actionMapPtr;
+ typedef std::pair<ObjectType, actionMapPtr> objectPair;
+ typedef std::map<Property, std::string> propMap;
+ typedef propMap::const_iterator propMapItr;
+ typedef std::map<SpecProperty, std::string> specPropMap;
+ typedef specPropMap::const_iterator specPropMapItr;
+
+ //
+ // properyMapToString
+ //
+ template <typename T>
+ static std::string propertyMapToString(
+ const std::map<T, std::string>* params)
+ {
+ std::ostringstream ss;
+ ss << "{";
+ if (params)
+ {
+ for (typename std::map<T, std::string>::const_iterator
+ pMItr = params->begin(); pMItr != params->end(); pMItr++)
+ {
+ ss << " " << getPropertyStr((T) pMItr-> first)
+ << "=" << pMItr->second;
+ }
+ }
+ ss << " }";
+ return ss.str();
+ }
+
+ };
+
+
+}} // namespace qpid::acl
+
+#endif // QPID_ACL_ACLLEXER_H
diff --git a/qpid/cpp/src/qpid/acl/AclPlugin.cpp b/qpid/cpp/src/qpid/acl/AclPlugin.cpp
new file mode 100644
index 0000000000..77580ba531
--- /dev/null
+++ b/qpid/cpp/src/qpid/acl/AclPlugin.cpp
@@ -0,0 +1,98 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <sstream>
+#include "qpid/acl/Acl.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/Plugin.h"
+#include "qpid/Options.h"
+#include "qpid/sys/Path.h"
+#include "qpid/log/Statement.h"
+
+#include <boost/shared_ptr.hpp>
+#include <boost/utility/in_place_factory.hpp>
+
+namespace qpid {
+namespace acl {
+
+using namespace std;
+
+/** Note separating options from values to work around boost version differences.
+ * Old boost takes a reference to options objects, but new boost makes a copy.
+ * New boost allows a shared_ptr but that's not compatible with old boost.
+ */
+struct AclOptions : public Options {
+ AclValues& values;
+
+ AclOptions(AclValues& v) : Options("ACL Options"), values(v) {
+ values.aclMaxConnectTotal = 500;
+ addOptions()
+ ("acl-file", optValue(values.aclFile, "FILE"), "The policy file to load from, loaded from data dir")
+ ("connection-limit-per-user", optValue(values.aclMaxConnectPerUser, "N"), "The maximum number of connections allowed per user. 0 implies no limit.")
+ ("max-connections" , optValue(values.aclMaxConnectTotal, "N"), "The maximum combined number of connections allowed. 0 implies no limit.")
+ ("connection-limit-per-ip" , optValue(values.aclMaxConnectPerIp, "N"), "The maximum number of connections allowed per host IP address. 0 implies no limit.")
+ ("max-queues-per-user", optValue(values.aclMaxQueuesPerUser, "N"), "The maximum number of queues allowed per user. 0 implies no limit.")
+ ;
+ }
+};
+
+struct AclPlugin : public Plugin {
+
+ AclValues values;
+ AclOptions options;
+ boost::intrusive_ptr<Acl> acl;
+
+ AclPlugin() : options(values) {}
+
+ Options* getOptions() { return &options; }
+
+ void init(broker::Broker& b) {
+ if (acl) throw Exception("ACL plugin cannot be initialized twice in one process.");
+
+ if (!values.aclFile.empty()){
+ sys::Path aclFile(values.aclFile);
+ sys::Path dataDir(b.getDataDir().getPath());
+ if (!aclFile.isAbsolute() && !dataDir.empty())
+ values.aclFile = (dataDir + aclFile).str();
+ }
+ acl = new Acl(values, b);
+ b.setAcl(acl.get());
+ b.addFinalizer(boost::bind(&AclPlugin::shutdown, this));
+ }
+
+ template <class T> bool init(Plugin::Target& target) {
+ T* t = dynamic_cast<T*>(&target);
+ if (t) init(*t);
+ return t;
+ }
+
+ void earlyInitialize(Plugin::Target&) {}
+
+ void initialize(Plugin::Target& target) {
+ init<broker::Broker>(target);
+ }
+
+ void shutdown() { acl = 0; }
+};
+
+static AclPlugin instance; // Static initialization.
+
+// For test purposes.
+boost::intrusive_ptr<Acl> getGlobalAcl() { return instance.acl; }
+
+}} // namespace qpid::acl
diff --git a/qpid/cpp/src/qpid/acl/AclReader.cpp b/qpid/cpp/src/qpid/acl/AclReader.cpp
new file mode 100644
index 0000000000..e8223c3570
--- /dev/null
+++ b/qpid/cpp/src/qpid/acl/AclReader.cpp
@@ -0,0 +1,827 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/acl/AclReader.h"
+#include "qpid/acl/AclData.h"
+
+#include <cctype>
+#include <cstring>
+#include <fstream>
+#include <sstream>
+#include "qpid/log/Statement.h"
+#include "qpid/Exception.h"
+#include <boost/lexical_cast.hpp>
+#include <algorithm>
+
+#include <iomanip> // degug
+#include <iostream> // debug
+
+#define ACL_FORMAT_ERR_LOG_PREFIX "ACL format error: " << fileName << ":" << lineNumber << ": "
+
+namespace qpid {
+namespace acl {
+
+ AclReader::aclRule::aclRule(const AclResult r, const std::string n, const groupMap& groups) : res(r), actionAll(true), objStatus(NONE) {
+ processName(n, groups);
+ }
+ AclReader::aclRule::aclRule(const AclResult r, const std::string n, const groupMap& groups, const Action a) : res(r), actionAll(false), action(a), objStatus(NONE) {
+ processName(n, groups);
+ }
+
+ void AclReader::aclRule::setObjectType(const ObjectType o) {
+ objStatus = VALUE;
+ object = o;
+ }
+
+ void AclReader::aclRule::setObjectTypeAll() {
+ objStatus = ALL;
+ }
+
+ bool AclReader::aclRule::addProperty(const SpecProperty p, const std::string v) {
+ return props.insert(propNvPair(p, v)).second;
+ }
+
+ // Debug aid
+ std::string AclReader::aclRule::toString() {
+ std::ostringstream oss;
+ oss << AclHelper::getAclResultStr(res) << " [";
+ for (nsCitr itr = names.begin(); itr != names.end(); itr++) {
+ if (itr != names.begin()) oss << ", ";
+ oss << *itr;
+ }
+ oss << "]";
+ if (actionAll) {
+ oss << " *";
+ } else {
+ oss << " " << AclHelper::getActionStr(action);
+ }
+ if (objStatus == ALL) {
+ oss << " *";
+ } else if (objStatus == VALUE) {
+ oss << " " << AclHelper::getObjectTypeStr(object);
+ }
+ for (pmCitr i=props.begin(); i!=props.end(); i++) {
+ oss << " " << AclHelper::getPropertyStr(i->first) << "=" << i->second;
+ }
+ return oss.str();
+ }
+
+ void AclReader::loadDecisionData(boost::shared_ptr<AclData> d) {
+ d->clear();
+ QPID_LOG(debug, "ACL: Load Rules");
+ bool foundmode = false;
+ bool foundConnectionMode = false;
+
+ rlCitr i = rules.end();
+ for (int cnt = rules.size(); cnt; cnt--) {
+ i--;
+ QPID_LOG(debug, "ACL: Processing " << std::setfill(' ') << std::setw(2)
+ << cnt << " " << (*i)->toString());
+
+ if (!(*i)->actionAll && (*i)->objStatus == aclRule::VALUE &&
+ !validator.validateAllowedProperties(
+ (*i)->action, (*i)->object, (*i)->props, false)) {
+ // specific object/action has bad property
+ // this rule gets ignored
+ continue;
+ } else {
+ // action=all or object=none/all means the rule gets propagated
+ // possibly to many places.
+ // Invalid rule combinations are not propagated.
+ }
+
+ if (!foundmode && (*i)->actionAll && (*i)->names.size() == 1
+ && (*((*i)->names.begin())).compare(AclData::ACL_KEYWORD_WILDCARD) == 0) {
+ d->decisionMode = (*i)->res;
+ QPID_LOG(debug, "ACL: FoundMode "
+ << AclHelper::getAclResultStr(d->decisionMode));
+ foundmode = true;
+ } else if ((*i)->action == acl::ACT_CREATE && (*i)->object == acl::OBJ_CONNECTION) {
+ // Intercept CREATE CONNECTION rules process them into separate lists to
+ // be consumed in the connection approval code path.
+ propMap::const_iterator pName = (*i)->props.find(SPECPROP_NAME);
+ if (pName != (*i)->props.end()) {
+ throw Exception(QPID_MSG("ACL: CREATE CONNECTION rule " << cnt << " must not have a 'name' property"));
+ }
+ propMap::const_iterator pHost = (*i)->props.find(SPECPROP_HOST);
+ if (pHost == (*i)->props.end()) {
+ throw Exception(QPID_MSG("ACL: CREATE CONNECTION rule " << cnt << " has no 'host' property"));
+ }
+ // create the connection rule
+ bool allUsers = (*(*i)->names.begin()).compare(AclData::ACL_KEYWORD_WILDCARD) == 0;
+ bool allHosts = pHost->second.compare(AclData::ACL_KEYWORD_ALL) == 0;
+ AclBWHostRule bwRule((*i)->res, (allHosts ? "" : pHost->second));
+
+ // apply the rule globally or to user list
+ if (allUsers) {
+ if (allHosts) {
+ // allow one specification of allUsers,allHosts
+ if (foundConnectionMode) {
+ throw Exception(QPID_MSG("ACL: only one CREATE CONNECTION rule for user=all and host=all allowed"));
+ }
+ foundConnectionMode = true;
+ d->connectionDecisionMode = (*i)->res;
+ QPID_LOG(trace, "ACL: Found connection mode: " << AclHelper::getAclResultStr( (*i)->res ));
+ } else {
+ // Rules for allUsers but not allHosts go into the global list
+ globalHostRules->insert( globalHostRules->begin(), bwRule );
+ }
+ } else {
+ // other rules go into binned rule sets for each user
+ for (nsCitr itr = (*i)->names.begin();
+ itr != (*i)->names.end();
+ itr++) {
+ (*userHostRules)[(*itr)].insert( (*userHostRules)[(*itr)].begin(), bwRule);
+ }
+ }
+ } else {
+ AclData::Rule rule(cnt, (*i)->res, (*i)->props);
+ // Record which properties have the user substitution string
+ for (pmCitr pItr=rule.props.begin(); pItr!=rule.props.end(); pItr++) {
+ if ((pItr->second.find(AclData::ACL_KEYWORD_USER_SUBST, 0) != std::string::npos) ||
+ (pItr->second.find(AclData::ACL_KEYWORD_DOMAIN_SUBST, 0) != std::string::npos) ||
+ (pItr->second.find(AclData::ACL_KEYWORD_USERDOMAIN_SUBST, 0) != std::string::npos)) {
+ rule.ruleHasUserSub[pItr->first] = true;
+ }
+ }
+
+ // Find possible routingkey property and cache its pattern
+ for (pmCitr pItr=rule.props.begin(); pItr!=rule.props.end(); pItr++) {
+ if (acl::SPECPROP_ROUTINGKEY == pItr->first)
+ {
+ rule.pubRoutingKeyInRule = true;
+ rule.pubRoutingKey = (std::string)pItr->second;
+ rule.addTopicTest(rule.pubRoutingKey);
+ }
+ }
+
+ // Action -> Object -> map<user -> set<Rule> >
+ std::ostringstream actionstr;
+ for (int acnt = ((*i)->actionAll ? 0 : (*i)->action);
+ acnt < acl::ACTIONSIZE;
+ (*i)->actionAll ? acnt++ : acnt = acl::ACTIONSIZE) {
+
+ if (acnt == acl::ACT_PUBLISH)
+ {
+ d->transferAcl = true; // we have transfer ACL
+ // For Publish the only object should be Exchange
+ // and the only property should be routingkey.
+ // Go through the rule properties and find the name and the key.
+ // If found then place them specially for the lookup engine.
+ for (pmCitr pItr=(*i)->props.begin(); pItr!=(*i)->props.end(); pItr++) {
+ if (acl::SPECPROP_NAME == pItr->first)
+ {
+ rule.pubExchNameInRule = true;
+ rule.pubExchName = pItr->second;
+ rule.pubExchNameMatchesBlank = rule.pubExchName.compare(AclData::ACL_KEYWORD_DEFAULT_EXCHANGE) == 0;
+ }
+ }
+ }
+ actionstr << AclHelper::getActionStr((Action) acnt) << ",";
+
+ //find the Action, create if not exist
+ if (d->actionList[acnt] == NULL) {
+ d->actionList[acnt] =
+ new AclData::aclAction[qpid::acl::OBJECTSIZE];
+ for (int j = 0; j < qpid::acl::OBJECTSIZE; j++)
+ d->actionList[acnt][j] = NULL;
+ }
+
+ for (int ocnt = ((*i)->objStatus != aclRule::VALUE ? 0
+ : (*i)->object);
+ ocnt < acl::OBJECTSIZE;
+ (*i)->objStatus != aclRule::VALUE ? ocnt++ : ocnt = acl::OBJECTSIZE) {
+
+ //find the Object, create if not exist
+ if (d->actionList[acnt][ocnt] == NULL)
+ d->actionList[acnt][ocnt] =
+ new AclData::actionObject;
+
+ // add users and Rule to object set
+ bool allNames = false;
+ // check to see if names.begin is '*'
+ if ((*(*i)->names.begin()).compare(AclData::ACL_KEYWORD_WILDCARD) == 0)
+ allNames = true;
+
+ for (nsCitr itr = (allNames ? names.begin() : (*i)->names.begin());
+ itr != (allNames ? names.end() : (*i)->names.end());
+ itr++) {
+ if (validator.validateAllowedProperties(acl::Action(acnt),
+ acl::ObjectType(ocnt),
+ (*i)->props,
+ false)) {
+ AclData::actObjItr itrRule =
+ d->actionList[acnt][ocnt]->find(*itr);
+
+ if (itrRule == d->actionList[acnt][ocnt]->end()) {
+ AclData::ruleSet rSet;
+ rSet.push_back(rule);
+ d->actionList[acnt][ocnt]->insert
+ (make_pair(std::string(*itr), rSet));
+ } else {
+ itrRule->second.push_back(rule);
+ }
+ } else {
+ // Skip propagating this rule as it will never match.
+ }
+ }
+ }
+ }
+
+ std::ostringstream objstr;
+ for (int ocnt = ((*i)->objStatus != aclRule::VALUE ? 0 : (*i)->object);
+ ocnt < acl::OBJECTSIZE;
+ (*i)->objStatus != aclRule::VALUE ? ocnt++ : ocnt = acl::OBJECTSIZE) {
+ objstr << AclHelper::getObjectTypeStr((ObjectType) ocnt) << ",";
+ }
+
+ bool allNames = ((*(*i)->names.begin()).compare(AclData::ACL_KEYWORD_WILDCARD) == 0);
+ std::ostringstream userstr;
+ for (nsCitr itr = (allNames ? names.begin() : (*i)->names.begin());
+ itr != (allNames ? names.end() : (*i)->names.end());
+ itr++) {
+ userstr << *itr << ",";
+ }
+
+ QPID_LOG(debug, "ACL: Adding actions {" <<
+ actionstr.str().substr(0,actionstr.str().length()-1)
+ << "} to objects {" <<
+ objstr.str().substr(0,objstr.str().length()-1)
+ << "} with props " <<
+ AclHelper::propertyMapToString(&rule.props)
+ << " for users {" <<
+ userstr.str().substr(0,userstr.str().length()-1)
+ << "}");
+ }
+ }
+
+ // connection quota
+ d->setConnQuotaRuleSettings(connQuota);
+ // queue quota
+ d->setQueueQuotaRuleSettings(queueQuota);
+ // global B/W connection rules
+ d->setConnGlobalRules(globalHostRules);
+ // user B/W connection rules
+ d->setConnUserRules(userHostRules);
+ }
+
+
+ void AclReader::aclRule::processName(const std::string& name, const groupMap& groups) {
+ if (name.compare(AclData::ACL_KEYWORD_ALL) == 0) {
+ names.insert(AclData::ACL_KEYWORD_WILDCARD);
+ } else {
+ gmCitr itr = groups.find(name);
+ if (itr == groups.end()) {
+ names.insert(name);
+ } else {
+ names.insert(itr->second->begin(), itr->second->end());
+ }
+ }
+ }
+
+ AclReader::AclReader(uint16_t theCliMaxConnPerUser, uint16_t theCliMaxQueuesPerUser) :
+ lineNumber(0), contFlag(false),
+ cliMaxConnPerUser (theCliMaxConnPerUser),
+ connQuotaRulesExist(false),
+ connQuota(new AclData::quotaRuleSet),
+ cliMaxQueuesPerUser (theCliMaxQueuesPerUser),
+ queueQuotaRulesExist(false),
+ queueQuota(new AclData::quotaRuleSet),
+ globalHostRules(new AclData::bwHostRuleSet),
+ userHostRules(new AclData::bwHostUserRuleMap) {
+ names.insert(AclData::ACL_KEYWORD_WILDCARD);
+ }
+
+ AclReader::~AclReader() {}
+
+ std::string AclReader::getError() {
+ return errorStream.str();
+ }
+
+ int AclReader::read(const std::string& fn, boost::shared_ptr<AclData> d) {
+ fileName = fn;
+ lineNumber = 0;
+ char buff[1024];
+ std::ifstream ifs(fn.c_str(), std::ios_base::in);
+ if (!ifs.good()) {
+ errorStream << "Unable to open ACL file \"" << fn << "\": eof=" << (ifs.eof()?"T":"F") << "; fail=" << (ifs.fail()?"T":"F") << "; bad=" << (ifs.bad()?"T":"F");
+ return -1;
+ }
+ // Propagate nonzero per-user max connection setting from CLI
+ if (cliMaxConnPerUser > 0) {
+ connQuotaRulesExist = true;
+ (*connQuota)[AclData::ACL_KEYWORD_ALL] = cliMaxConnPerUser;
+ }
+ // Propagate nonzero per-user max queue setting from CLI
+ if (cliMaxQueuesPerUser > 0) {
+ queueQuotaRulesExist = true;
+ (*queueQuota)[AclData::ACL_KEYWORD_ALL] = cliMaxQueuesPerUser;
+ }
+ // Loop to process the Acl file
+ try {
+ bool err = false;
+ while (ifs.good()) {
+ ifs.getline(buff, 1024);
+ lineNumber++;
+ if (std::strlen(buff) > 0 && buff[0] != '#') // Ignore blank lines and comments
+ err |= !processLine(buff);
+ }
+ if (!ifs.eof())
+ {
+ errorStream << "Unable to read ACL file \"" << fn << "\": eof=" << (ifs.eof()?"T":"F") << "; fail=" << (ifs.fail()?"T":"F") << "; bad=" << (ifs.bad()?"T":"F");
+ ifs.close();
+ return -2;
+ }
+ ifs.close();
+ if (err) return -3;
+ QPID_LOG(notice, "ACL: Read file \"" << fn << "\"");
+ } catch (const std::exception& e) {
+ errorStream << "Unable to read ACL file \"" << fn << "\": " << e.what();
+ ifs.close();
+ return -4;
+ } catch (...) {
+ errorStream << "Unable to read ACL file \"" << fn << "\": Unknown exception";
+ ifs.close();
+ return -5;
+ }
+ printNames();
+ printRules();
+ printQuotas(AclData::ACL_KEYWORD_QUOTA_CONNECTIONS, connQuota);
+ printQuotas(AclData::ACL_KEYWORD_QUOTA_QUEUES, queueQuota);
+ try {
+ loadDecisionData(d);
+ } catch (const std::exception& e) {
+ errorStream << "Error loading decision data : " << e.what();
+ return -6;
+ }
+ printGlobalConnectRules();
+ printUserConnectRules();
+ validator.tracePropertyDefs();
+ d->printDecisionRules( printNamesFieldWidth() );
+
+ return 0;
+ }
+
+ bool AclReader::processLine(char* line) {
+ bool ret = false;
+ std::vector<std::string> toks;
+
+ // Check for continuation
+ char* contCharPtr = std::strrchr(line, AclData::ACL_SYMBOL_LINE_CONTINUATION);
+ bool cont = contCharPtr != 0;
+ if (cont) *contCharPtr = 0;
+
+ int numToks = tokenize(line, toks);
+
+ if (cont && numToks == 0){
+ errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line \"" << lineNumber << "\" contains an illegal extension.";
+ return false;
+ }
+
+ if (numToks && (toks[0].compare(AclData::ACL_KEYWORD_GROUP) == 0 || contFlag)) {
+ ret = processGroupLine(toks, cont);
+ } else if (numToks && toks[0].compare(AclData::ACL_KEYWORD_ACL) == 0) {
+ ret = processAclLine(toks);
+ } else if (numToks && toks[0].compare(AclData::ACL_KEYWORD_QUOTA) == 0) {
+ ret = processQuotaLine(toks);
+ } else {
+ // Check for whitespace only line, ignore these
+ bool ws = true;
+ for (unsigned i=0; i<std::strlen(line) && ws; i++) {
+ if (!std::isspace(line[i])) ws = false;
+ }
+ if (ws) {
+ ret = true;
+ } else {
+ errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber
+ << ", Non-continuation line must start with \""
+ << AclData::ACL_KEYWORD_GROUP << "\", \""
+ << AclData::ACL_KEYWORD_ACL << "\". or \""
+ << AclData::ACL_KEYWORD_QUOTA << "\".";
+ ret = false;
+ }
+ }
+ contFlag = cont;
+ return ret;
+ }
+
+ int AclReader::tokenize(char* line, std::vector<std::string>& toks) {
+ const char* tokChars = " \t\n\f\v\r";
+ int cnt = 0;
+ char* cp = std::strtok(line, tokChars);
+ while (cp != 0) {
+ toks.push_back(std::string(cp));
+ cnt++;
+ cp = std::strtok(0, tokChars);
+ }
+ return cnt;
+ }
+
+
+ // Process 'quota' rule lines
+ // Return true if the line is successfully processed without errors
+ bool AclReader::processQuotaLine(tokList& toks) {
+ const unsigned toksSize = toks.size();
+ const unsigned minimumSize = 3;
+ if (toksSize < minimumSize) {
+ errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber
+ << ", Insufficient tokens for quota definition.";
+ return false;
+ }
+
+ if (toks[1].compare(AclData::ACL_KEYWORD_QUOTA_CONNECTIONS) == 0) {
+ if (processQuotaLine(toks, AclData::ACL_KEYWORD_QUOTA_CONNECTIONS, AclData::getConnectMaxSpec(), connQuota)) {
+ // We have processed a connection quota rule
+ connQuotaRulesExist = true;
+ return true;
+ }
+ } else if (toks[1].compare(AclData::ACL_KEYWORD_QUOTA_QUEUES) == 0) {
+ if (processQuotaLine(toks, AclData::ACL_KEYWORD_QUOTA_QUEUES, AclData::getConnectMaxSpec(), queueQuota)) {
+ // We have processed a queue quota rule
+ queueQuotaRulesExist = true;
+ return true;
+ }
+ } else {
+ errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber
+ << ", Quota type \"" << toks[1] << "\" unrecognized.";
+ return false;
+ }
+ return false;
+ }
+
+
+ // Process quota rule lines
+ // Return true if the line is successfully processed without errors
+ bool AclReader::processQuotaLine(tokList& toks, const std::string theNoun, uint16_t maxSpec, aclQuotaRuleSet theRules) {
+ const unsigned toksSize = toks.size();
+
+ uint16_t nEntities(0);
+ try {
+ nEntities = boost::lexical_cast<uint16_t>(toks[2]);
+ } catch(const boost::bad_lexical_cast&) {
+ errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber
+ << ", " << theNoun << " quota value \"" << toks[2]
+ << "\" cannot be converted to a 16-bit unsigned integer.";
+ return false;
+ }
+
+ // limit check the setting
+ if (nEntities > maxSpec)
+ {
+ errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber
+ << ", " << theNoun << " quota value \"" << toks[2]
+ << "\" exceeds maximum configuration setting of "
+ << maxSpec;
+ return false;
+ }
+
+ // Apply the ount to all names in rule
+ for (unsigned idx = 3; idx < toksSize; idx++) {
+ if (groups.find(toks[idx]) == groups.end()) {
+ // This is the name of an individual, not a group
+ (*theRules)[toks[idx]] = nEntities;
+ } else {
+ if (!processQuotaGroup(toks[idx], nEntities, theRules))
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+
+ // Process quota group expansion
+ // Return true if the quota is applied to all members of the group
+ bool AclReader::processQuotaGroup(const std::string& theGroup, uint16_t theQuota, aclQuotaRuleSet theRules) {
+ gmCitr citr = groups.find(theGroup);
+
+ if (citr == groups.end()) {
+ errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber
+ << ", Failed to expand group \"" << theGroup << "\".";
+ return false;
+ }
+
+ for (nsCitr gni=citr->second->begin(); gni!=citr->second->end(); gni++) {
+ if (groups.find(*gni) == groups.end()) {
+ (*theRules)[*gni] = theQuota;
+ } else {
+ if (!processQuotaGroup(*gni, theQuota, theRules))
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ void AclReader::printQuotas(const std::string theNoun, aclQuotaRuleSet theRules) const {
+ QPID_LOG(debug, "ACL: " << theNoun << " quota: " << (*theRules).size() << " rules found:");
+ int cnt = 1;
+ for (AclData::quotaRuleSetItr itr=(*theRules).begin();
+ itr != (*theRules).end();
+ ++itr,++cnt) {
+ QPID_LOG(debug, "ACL: quota " << cnt << " : " << (*itr).second
+ << " " << theNoun << " for " << (*itr).first)
+ }
+ }
+
+
+ // Return true if the line is successfully processed without errors
+ // If cont is true, then groupName must be set to the continuation group name
+ bool AclReader::processGroupLine(tokList& toks, const bool cont) {
+ const unsigned toksSize = toks.size();
+
+ if (contFlag) {
+ gmCitr citr = groups.find(groupName);
+ for (unsigned i = 0; i < toksSize; i++) {
+ if (isValidGroupName(toks[i])) {
+ if (toks[i] == groupName) {
+ QPID_LOG(debug, "ACL: Line: " << lineNumber
+ << ", Ignoring recursive sub-group \"" << toks[i] << "\".");
+ continue;
+ } else if (groups.find(toks[i]) == groups.end()) {
+ errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber
+ << ", Sub-group \"" << toks[i] << "\" not defined yet.";
+ return false;
+ }
+ } else if (!isValidUserName(toks[i])) return false;
+ addName(toks[i], citr->second);
+ }
+ } else {
+ const unsigned minimumSize = (cont ? 2 : 3);
+ if (toksSize < minimumSize) {
+ errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber
+ << ", Insufficient tokens for group definition.";
+ return false;
+ }
+ if (!isValidGroupName(toks[1])) {
+ errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber
+ << ", Group name \"" << toks[1] << "\" contains illegal characters.";
+ return false;
+ }
+ gmCitr citr = addGroup(toks[1]);
+ if (citr == groups.end()) return false;
+ for (unsigned i = 2; i < toksSize; i++) {
+ if (isValidGroupName(toks[i])) {
+ if (toks[i] == groupName) {
+ QPID_LOG(debug, "ACL: Line: " << lineNumber
+ << ", Ignoring recursive sub-group \"" << toks[i] << "\".");
+ continue;
+ } else if (groups.find(toks[i]) == groups.end()) {
+ errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber
+ << ", Sub-group \"" << toks[i] << "\" not defined yet.";
+ return false;
+ }
+ } else if (!isValidUserName(toks[i])) return false;
+ addName(toks[i], citr->second);
+ }
+ }
+ return true;
+ }
+
+ // Return true if sucessfully added group
+ AclReader::gmCitr AclReader::addGroup(const std::string& newGroupName) {
+ gmCitr citr = groups.find(newGroupName);
+ if (citr != groups.end()) {
+ errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber
+ << ", Duplicate group name \"" << newGroupName << "\".";
+ return groups.end();
+ }
+ groupPair p(newGroupName, nameSetPtr(new nameSet));
+ gmRes res = groups.insert(p);
+ assert(res.second);
+ groupName = newGroupName;
+ return res.first;
+ }
+
+ void AclReader::addName(const std::string& name, nameSetPtr groupNameSet) {
+ gmCitr citr = groups.find(name);
+ if (citr != groups.end()) {
+ // This is a previously defined group: add all the names in that group to this group
+ groupNameSet->insert(citr->second->begin(), citr->second->end());
+ } else {
+ // Not a known group name
+ groupNameSet->insert(name);
+ addName(name);
+ }
+ }
+
+ void AclReader::addName(const std::string& name) {
+ names.insert(name);
+ }
+
+ /**
+ * Emit debug logs exposing the name lists
+ */
+ void AclReader::printNames() const {
+ QPID_LOG(debug, "ACL: Group list: " << groups.size() << " groups found:" );
+ std::string tmp("ACL: ");
+ for (gmCitr i=groups.begin(); i!= groups.end(); i++) {
+ tmp += " \"";
+ tmp += i->first;
+ tmp += "\":";
+ for (nsCitr j=i->second->begin(); j!=i->second->end(); j++) {
+ tmp += " ";
+ tmp += *j;
+ }
+ QPID_LOG(debug, tmp);
+ tmp = "ACL: ";
+ }
+ QPID_LOG(debug, "ACL: name list: " << names.size() << " names found:" );
+ tmp = "ACL: ";
+ for (nsCitr k=names.begin(); k!=names.end(); k++) {
+ tmp += " ";
+ tmp += *k;
+ }
+ QPID_LOG(debug, tmp);
+ }
+
+ /**
+ * compute the width of longest user name
+ */
+ int AclReader::printNamesFieldWidth() const {
+ std::string::size_type max = 0;
+ for (nsCitr k=names.begin(); k!=names.end(); k++) {
+ max = std::max(max, (*k).length());
+ }
+ return max;
+ }
+
+ bool AclReader::processAclLine(tokList& toks) {
+ const unsigned toksSize = toks.size();
+ if (toksSize < 4) {
+ errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber
+ << ", Insufficient tokens for acl definition.";
+ return false;
+ }
+
+ AclResult res;
+ try {
+ res = AclHelper::getAclResult(toks[1]);
+ } catch (...) {
+ errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber
+ << ", Unknown ACL permission \"" << toks[1] << "\".";
+ return false;
+ }
+
+ bool actionAllFlag = toks[3].compare(AclData::ACL_KEYWORD_ALL) == 0;
+ bool userAllFlag = toks[2].compare(AclData::ACL_KEYWORD_ALL) == 0;
+ Action action;
+ if (actionAllFlag) {
+
+ if (userAllFlag && toksSize > 4) {
+ errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber
+ << ", Tokens found after action \"all\".";
+ return false;
+ }
+ action = ACT_CONSUME; // dummy; compiler must initialize action for this code path
+ } else {
+ try {
+ action = AclHelper::getAction(toks[3]);
+ } catch (...) {
+ errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber
+ << ", Unknown action \"" << toks[3] << "\".";
+ return false;
+ }
+ }
+
+ // Create rule obj; then add object (if any) and properties (if any)
+ aclRulePtr rule;
+ if (actionAllFlag) {
+ rule.reset(new aclRule(res, toks[2], groups));
+ } else {
+ rule.reset(new aclRule(res, toks[2], groups, action));
+ }
+
+ if (toksSize >= 5) { // object name-value pair
+ if (toks[4].compare(AclData::ACL_KEYWORD_ALL) == 0) {
+ rule->setObjectTypeAll();
+ } else {
+ try {
+ rule->setObjectType(AclHelper::getObjectType(toks[4]));
+ } catch (...) {
+ errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber
+ << ", Unknown object \"" << toks[4] << "\".";
+ return false;
+ }
+ }
+ }
+
+ if (toksSize >= 6) { // property name-value pair(s)
+ for (unsigned i=5; i<toksSize; i++) {
+ nvPair propNvp = splitNameValuePair(toks[i]);
+ if (propNvp.second.size() == 0) {
+ errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber
+ <<", Badly formed property name-value pair \""
+ << propNvp.first << "\". (Must be name=value)";
+ return false;
+ }
+ SpecProperty prop;
+ try {
+ prop = AclHelper::getSpecProperty(propNvp.first);
+ } catch (...) {
+ errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber
+ << ", Unknown property \"" << propNvp.first << "\".";
+ return false;
+ }
+ rule->addProperty(prop, propNvp.second);
+ }
+ }
+ // Check if name (toks[2]) is group; if not, add as name of individual
+ if (toks[2].compare(AclData::ACL_KEYWORD_ALL) != 0) {
+ if (groups.find(toks[2]) == groups.end()) {
+ addName(toks[2]);
+ }
+ }
+
+ rules.push_back(rule);
+
+ return true;
+ }
+
+ // Debug aid
+ void AclReader::printRules() const {
+ QPID_LOG(debug, "ACL: Rule list: " << rules.size() << " ACL rules found:");
+ int cnt = 1;
+ for (rlCitr i=rules.begin(); i<rules.end(); i++,cnt++) {
+ QPID_LOG(debug, "ACL: " << std::setfill(' ') << std::setw(2) << cnt << " " << (*i)->toString());
+ if (!(*i)->actionAll && (*i)->objStatus == aclRule::VALUE) {
+ (void)validator.validateAllowedProperties((*i)->action, (*i)->object, (*i)->props, true);
+ }
+ }
+ }
+
+ void AclReader::printConnectionRules(const std::string name, const AclData::bwHostRuleSet& rules) const {
+ QPID_LOG(debug, "ACL: " << name << " Connection Rule list : " << rules.size() << " rules found :");
+ int cnt = 1;
+ for (AclData::bwHostRuleSetItr i=rules.begin(); i<rules.end(); i++,cnt++) {
+ QPID_LOG(debug, "ACL: " << std::setfill(' ') << std::setw(2) << cnt << " " << i->toString());
+ }
+ }
+
+ void AclReader::printGlobalConnectRules() const {
+ printConnectionRules("global", *globalHostRules);
+ }
+
+ void AclReader::printUserConnectRules() const {
+ QPID_LOG(debug, "ACL: User Connection Rule lists : " << userHostRules->size() << " user lists found :");
+ int cnt = 1;
+ for (AclData::bwHostUserRuleMapItr i=userHostRules->begin(); i!=userHostRules->end(); i++,cnt++) {
+ printConnectionRules(std::string((*i).first), (*i).second);
+ }
+ }
+
+ // Static function
+ // Return true if the name is well-formed (ie contains legal characters)
+ bool AclReader::isValidGroupName(const std::string& name) {
+ for (unsigned i=0; i<name.size(); i++) {
+ const char ch = name.at(i);
+ if (!std::isalnum(ch) && ch != '-' && ch != '_') return false;
+ }
+ return true;
+ }
+
+ // Static function
+ // Split name-value pair around '=' char of the form "name=value"
+ AclReader::nvPair AclReader::splitNameValuePair(const std::string& nvpString) {
+ std::size_t pos = nvpString.find("=");
+ if (pos == std::string::npos || pos == nvpString.size() - 1) {
+ return nvPair(nvpString, "");
+ }
+ return nvPair(nvpString.substr(0, pos), nvpString.substr(pos+1));
+ }
+
+ // Returns true if a username has the name@realm format
+ bool AclReader::isValidUserName(const std::string& name){
+ size_t pos = name.find('@');
+ if ( pos == std::string::npos || pos == name.length() -1){
+ errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber
+ << ", Username '" << name << "' must contain a realm";
+ return false;
+ }
+ for (unsigned i=0; i<name.size(); i++) {
+ const char ch = name.at(i);
+ if (!std::isalnum(ch) && ch != '-' && ch != '_' && ch != '@' && ch != '.' && ch != '/'){
+ errorStream << ACL_FORMAT_ERR_LOG_PREFIX << "Line : " << lineNumber
+ << ", Username \"" << name << "\" contains illegal characters.";
+ return false;
+ }
+ }
+ return true;
+ }
+
+}} // namespace qpid::acl
diff --git a/qpid/cpp/src/qpid/acl/AclReader.h b/qpid/cpp/src/qpid/acl/AclReader.h
new file mode 100644
index 0000000000..24237a82ce
--- /dev/null
+++ b/qpid/cpp/src/qpid/acl/AclReader.h
@@ -0,0 +1,150 @@
+#ifndef QPID_ACL_ACLREADER_H
+#define QPID_ACL_ACLREADER_H
+
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <boost/shared_ptr.hpp>
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+#include <sstream>
+#include <memory>
+#include "qpid/acl/AclData.h"
+#include "qpid/acl/Acl.h"
+#include "qpid/broker/AclModule.h"
+#include "qpid/acl/AclValidator.h"
+
+namespace qpid {
+namespace acl {
+
+class AclReader {
+ typedef std::set<std::string> nameSet;
+ typedef nameSet::const_iterator nsCitr;
+ typedef boost::shared_ptr<nameSet> nameSetPtr;
+
+ typedef std::pair<std::string, nameSetPtr> groupPair;
+ typedef std::map<std::string, nameSetPtr> groupMap;
+ typedef groupMap::const_iterator gmCitr;
+ typedef std::pair<gmCitr, bool> gmRes;
+
+ typedef std::pair<SpecProperty, std::string> propNvPair;
+ typedef std::map<SpecProperty, std::string> propMap;
+ typedef propMap::const_iterator pmCitr;
+
+ //
+ // aclRule
+ //
+ // A temporary rule created during ACL file processing.
+ //
+ class aclRule {
+ public:
+ enum objectStatus {NONE, VALUE, ALL};
+
+ AclResult res;
+ nameSet names;
+ bool actionAll; // True if action is set to keyword "all"
+ Action action; // Ignored if action is set to keyword "all"
+ objectStatus objStatus;
+ ObjectType object; // Ignored for all status values except VALUE
+ propMap props;
+ public:
+ aclRule(const AclResult r, const std::string n, const groupMap& groups); // action = "all"
+ aclRule(const AclResult r, const std::string n, const groupMap& groups, const Action a);
+ void setObjectType(const ObjectType o);
+ void setObjectTypeAll();
+ bool addProperty(const SpecProperty p, const std::string v);
+ std::string toString(); // debug aid
+ private:
+ void processName(const std::string& name, const groupMap& groups);
+ };
+ typedef boost::shared_ptr<AclData::quotaRuleSet> aclQuotaRuleSet;
+ typedef boost::shared_ptr<aclRule> aclRulePtr;
+ typedef std::vector<aclRulePtr> ruleList;
+ typedef ruleList::const_iterator rlCitr;
+
+ typedef std::vector<std::string> tokList;
+ typedef tokList::const_iterator tlCitr;
+
+ typedef std::set<std::string> keywordSet;
+ typedef keywordSet::const_iterator ksCitr;
+ typedef std::pair<std::string, std::string> nvPair; // Name-Value pair
+
+ typedef boost::shared_ptr<std::vector<acl::AclBWHostRule> > aclGlobalHostRuleSet;
+ typedef boost::shared_ptr<std::map<std::string, std::vector<acl::AclBWHostRule> > > aclUserHostRuleSet;
+
+ std::string fileName;
+ int lineNumber;
+ bool contFlag;
+ std::string groupName;
+ nameSet names;
+ groupMap groups;
+ ruleList rules;
+ AclValidator validator;
+ std::ostringstream errorStream;
+
+ public:
+ AclReader(uint16_t cliMaxConnPerUser, uint16_t cliMaxQueuesPerUser);
+ virtual ~AclReader();
+ int read(const std::string& fn, boost::shared_ptr<AclData> d); // return=0 for success
+ std::string getError();
+
+ private:
+ bool processLine(char* line);
+ void loadDecisionData(boost::shared_ptr<AclData> d);
+ int tokenize(char* line, tokList& toks);
+
+ bool processGroupLine(tokList& toks, const bool cont);
+ gmCitr addGroup(const std::string& groupName);
+ void addName(const std::string& name, nameSetPtr groupNameSet);
+ void addName(const std::string& name);
+ void printNames() const; // debug aid
+ int printNamesFieldWidth() const;
+
+ bool processAclLine(tokList& toks);
+ void printRules() const; // debug aid
+ void printConnectionRules(const std::string name, const AclData::bwHostRuleSet& rules) const;
+ void printGlobalConnectRules() const;
+ void printUserConnectRules() const;
+ bool isValidUserName(const std::string& name);
+
+ bool processQuotaLine(tokList& toks);
+ bool processQuotaLine(tokList& toks, const std::string theNoun, uint16_t maxSpec, aclQuotaRuleSet theRules);
+ bool processQuotaGroup(const std::string&, uint16_t, aclQuotaRuleSet theRules);
+ void printQuotas(const std::string theNoun, aclQuotaRuleSet theRules) const;
+
+ static bool isValidGroupName(const std::string& name);
+ static nvPair splitNameValuePair(const std::string& nvpString);
+
+ const uint16_t cliMaxConnPerUser;
+ bool connQuotaRulesExist;
+ aclQuotaRuleSet connQuota;
+
+ const uint16_t cliMaxQueuesPerUser;
+ bool queueQuotaRulesExist;
+ aclQuotaRuleSet queueQuota;
+
+ aclGlobalHostRuleSet globalHostRules;
+ aclUserHostRuleSet userHostRules;
+};
+
+}} // namespace qpid::acl
+
+#endif // QPID_ACL_ACLREADER_H
diff --git a/qpid/cpp/src/qpid/acl/AclResourceCounter.cpp b/qpid/cpp/src/qpid/acl/AclResourceCounter.cpp
new file mode 100644
index 0000000000..2527af6375
--- /dev/null
+++ b/qpid/cpp/src/qpid/acl/AclResourceCounter.cpp
@@ -0,0 +1,174 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "AclResourceCounter.h"
+#include "Acl.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/Mutex.h"
+#include <assert.h>
+#include <sstream>
+
+using namespace qpid::sys;
+
+namespace qpid {
+namespace acl {
+
+//
+// This module approves various resource creation requests:
+// Queues
+//
+
+
+//
+//
+//
+ResourceCounter::ResourceCounter(Acl& a, uint16_t ql) :
+ acl(a), queueLimit(ql) {}
+
+ResourceCounter::~ResourceCounter() {}
+
+
+//
+// limitApproveLH
+//
+// Resource creation approver.
+// If user is under limit increment count and return true.
+// Called with lock held.
+//
+bool ResourceCounter::limitApproveLH(
+ countsMap_t& theMap,
+ const std::string& theName,
+ uint16_t theLimit,
+ bool emitLog,
+ bool enforceLimit) {
+
+ bool result(true);
+ uint16_t count;
+ countsMap_t::iterator eRef = theMap.find(theName);
+ if (eRef != theMap.end()) {
+ count = (uint16_t)(*eRef).second;
+ result = (enforceLimit ? count < theLimit : true);
+ if (result) {
+ count += 1;
+ (*eRef).second = count;
+ }
+ } else {
+ // user not found in map
+ if (enforceLimit) {
+ if (theLimit > 0) {
+ theMap[theName] = count = 1;
+ } else {
+ count = 0;
+ result = false;
+ }
+ }
+ else {
+ // not enforcing the limit
+ theMap[theName] = count = 1;
+ }
+ }
+ if (emitLog) {
+ QPID_LOG(trace, "ACL QueueApprover user=" << theName
+ << " limit=" << theLimit
+ << " curValue=" << count
+ << " result=" << (result ? "allow" : "deny"));
+ }
+ return result;
+}
+
+
+//
+// releaseLH
+//
+// Decrement the name's count in map.
+// called with dataLock already taken
+//
+void ResourceCounter::releaseLH(countsMap_t& theMap, const std::string& theName) {
+
+ countsMap_t::iterator eRef = theMap.find(theName);
+ if (eRef != theMap.end()) {
+ uint16_t count = (uint16_t) (*eRef).second;
+ assert (count > 0);
+ if (1 == count) {
+ theMap.erase (eRef);
+ } else {
+ (*eRef).second = count - 1;
+ }
+ } else {
+ // User had no connections.
+ QPID_LOG(notice, "ACL resource counter: Queue owner for queue '" << theName
+ << "' not found in resource count pool");
+ }
+}
+
+
+//
+// approveCreateQueue
+// Count an attempted queue creation by this user.
+// Disapprove if over limit.
+//
+bool ResourceCounter::approveCreateQueue(const std::string& userId,
+ const std::string& queueName,
+ bool enforcingQueueQuotas,
+ uint16_t queueUserQuota )
+{
+ Mutex::ScopedLock locker(dataLock);
+
+ bool okByQ = limitApproveLH(queuePerUserMap, userId, queueUserQuota, true, enforcingQueueQuotas);
+
+ if (okByQ) {
+ // Queue is owned by this userId
+ queueOwnerMap[queueName] = userId;
+
+ QPID_LOG(trace, "ACL create queue approved for user '" << userId
+ << "' queue '" << queueName << "'");
+ } else {
+
+ QPID_LOG(error, "Client max queue count limit of " << queueUserQuota
+ << " exceeded by '" << userId << "' creating queue '"
+ << queueName << "'. Queue creation denied.");
+
+ acl.reportQueueLimit(userId, queueName);
+ }
+ return okByQ;
+}
+
+
+//
+// recordDestroyQueue
+// Return a destroyed queue to a user's quota
+//
+void ResourceCounter::recordDestroyQueue(const std::string& queueName)
+{
+ Mutex::ScopedLock locker(dataLock);
+
+ queueOwnerMap_t::iterator eRef = queueOwnerMap.find(queueName);
+ if (eRef != queueOwnerMap.end()) {
+ releaseLH(queuePerUserMap, (*eRef).second);
+
+ queueOwnerMap.erase(eRef);
+ } else {
+ QPID_LOG(notice, "ACL resource counter: Queue '" << queueName
+ << "' not found in queue owner map");
+ }
+}
+
+}} // namespace qpid::acl
diff --git a/qpid/cpp/src/qpid/acl/AclResourceCounter.h b/qpid/cpp/src/qpid/acl/AclResourceCounter.h
new file mode 100644
index 0000000000..8809f73b18
--- /dev/null
+++ b/qpid/cpp/src/qpid/acl/AclResourceCounter.h
@@ -0,0 +1,78 @@
+#ifndef QPID_ACL_RESOURCECOUNTER_H
+#define QPID_ACL_RESOURCECOUNTER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/Mutex.h"
+
+#include <map>
+
+namespace qpid {
+
+namespace acl {
+class Acl;
+
+ /**
+ * Approve or disapprove resource creation requests
+ */
+class ResourceCounter
+{
+private:
+ typedef std::map<std::string, uint32_t> countsMap_t;
+ typedef std::map<std::string, std::string> queueOwnerMap_t;
+
+ Acl& acl;
+ uint16_t queueLimit;
+ qpid::sys::Mutex dataLock;
+
+ /** Records queueName-queueUserId */
+ queueOwnerMap_t queueOwnerMap;
+
+ /** Records queue-by-owner counts */
+ countsMap_t queuePerUserMap;
+
+ /** Return approval for proposed resource creation */
+ bool limitApproveLH(countsMap_t& theMap,
+ const std::string& theName,
+ uint16_t theLimit,
+ bool emitLog,
+ bool enforceLimit);
+
+ /** Release a connection */
+ void releaseLH(countsMap_t& theMap,
+ const std::string& theName);
+
+public:
+ ResourceCounter(Acl& acl, uint16_t ql);
+ ~ResourceCounter();
+
+ // Queue counting
+ bool approveCreateQueue(const std::string& userId,
+ const std::string& queueName,
+ bool enforcingQueueQuotas,
+ uint16_t queueUserQuota );
+ void recordDestroyQueue(const std::string& queueName);
+};
+
+}} // namespace qpid::acl
+
+#endif /*!QPID_ACL_RESOURCECOUNTER_H*/
diff --git a/qpid/cpp/src/qpid/acl/AclTopicMatch.h b/qpid/cpp/src/qpid/acl/AclTopicMatch.h
new file mode 100644
index 0000000000..654d1d63d4
--- /dev/null
+++ b/qpid/cpp/src/qpid/acl/AclTopicMatch.h
@@ -0,0 +1,89 @@
+#ifndef QPID_ACL_TOPIC_MATCH_H
+#define QPID_ACL_TOPIC_MATCH_H
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/broker/TopicKeyNode.h"
+#include "qpid/broker/TopicExchange.h"
+#include "qpid/log/Statement.h"
+#include "boost/shared_ptr.hpp"
+#include <vector>
+#include <sstream>
+
+namespace qpid {
+namespace broker {
+
+// Class for executing topic exchange routing key matching rules in
+// Acl code. Allows or denies users publishing to an exchange.
+class TopicExchange::TopicExchangeTester {
+
+class boundNode;
+
+public:
+ typedef std::vector<bool> BindingVec;
+ typedef TopicKeyNode<boundNode> TestBindingNode;
+
+private:
+ // Target class to be bound into topic key tree
+ class boundNode {
+ public:
+ BindingVec bindingVector;
+ };
+
+ // Acl binding trees contain only one node each.
+ // When the iterator sees it then the node matches the caller's spec.
+ class TestFinder : public TestBindingNode::TreeIterator {
+ public:
+ TestFinder(BindingVec& m) : bv(m), found(false) {};
+ ~TestFinder() {};
+ bool visit(TestBindingNode& /*node*/) {
+ assert(!found);
+ found = true;
+ return true;
+ }
+ BindingVec& bv;
+ bool found;
+ };
+
+public:
+ TopicExchangeTester() {};
+ ~TopicExchangeTester() {};
+ bool addBindingKey(const std::string& bKey) {
+ std::string routingPattern = normalize(bKey);
+ boundNode *mbn = bindingTree.add(routingPattern);
+ if (mbn) {
+ // push a dummy binding to mark this node as "non-leaf"
+ mbn->bindingVector.push_back(true);
+ return true;
+ }
+ return false;
+ }
+
+ bool findMatches(const std::string& rKey, BindingVec& matches) {
+ TestFinder testFinder(matches);
+ bindingTree.iterateMatch( rKey, testFinder );
+ return testFinder.found;
+ }
+
+private:
+ TestBindingNode bindingTree;
+};
+}} // namespace qpid::broker
+
+#endif // QPID_ACL_TOPIC_MATCH_H
diff --git a/qpid/cpp/src/qpid/acl/AclValidator.cpp b/qpid/cpp/src/qpid/acl/AclValidator.cpp
new file mode 100644
index 0000000000..f905b4aca5
--- /dev/null
+++ b/qpid/cpp/src/qpid/acl/AclValidator.cpp
@@ -0,0 +1,536 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/acl/AclValidator.h"
+#include "qpid/acl/AclData.h"
+#include "qpid/acl/AclLexer.h"
+#include "qpid/Exception.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/StringUtils.h"
+#include <boost/lexical_cast.hpp>
+#include <boost/bind.hpp>
+#include <numeric>
+#include <sstream>
+#include <iomanip>
+
+namespace qpid {
+namespace acl {
+
+ AclValidator::IntPropertyType::IntPropertyType(int64_t i,int64_t j) : min(i), max(j){
+ }
+
+ bool AclValidator::IntPropertyType::validate(const std::string& val) {
+ int64_t v;
+ try
+ {
+ v = boost::lexical_cast<int64_t>(val);
+ }catch(const boost::bad_lexical_cast&){
+ return 0;
+ }
+
+ if (v < min || v >= max){
+ return 0;
+ }else{
+ return 1;
+ }
+ }
+
+ std::string AclValidator::IntPropertyType::allowedValues() {
+ return "values should be between " +
+ boost::lexical_cast<std::string>(min) + " and " +
+ boost::lexical_cast<std::string>(max);
+ }
+
+ AclValidator::EnumPropertyType::EnumPropertyType(std::vector<std::string>& allowed): values(allowed){
+ }
+
+ bool AclValidator::EnumPropertyType::validate(const std::string& val) {
+ for (std::vector<std::string>::iterator itr = values.begin(); itr != values.end(); ++itr ){
+ if (val.compare(*itr) == 0){
+ return 1;
+ }
+ }
+
+ return 0;
+ }
+
+ std::string AclValidator::EnumPropertyType::allowedValues() {
+ std::ostringstream oss;
+ oss << "possible values are one of { ";
+ for (std::vector<std::string>::iterator itr = values.begin(); itr != values.end(); itr++ ){
+ oss << "'" << *itr << "' ";
+ }
+ oss << "}";
+ return oss.str();
+ }
+
+ AclValidator::AclValidator() : propertyIndex(1) {
+ validators.insert(Validator(acl::SPECPROP_MAXQUEUESIZELOWERLIMIT,
+ boost::shared_ptr<PropertyType>(
+ new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));
+
+ validators.insert(Validator(acl::SPECPROP_MAXQUEUESIZEUPPERLIMIT,
+ boost::shared_ptr<PropertyType>(
+ new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));
+
+ validators.insert(Validator(acl::SPECPROP_MAXQUEUECOUNTLOWERLIMIT,
+ boost::shared_ptr<PropertyType>(
+ new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));
+
+ validators.insert(Validator(acl::SPECPROP_MAXQUEUECOUNTUPPERLIMIT,
+ boost::shared_ptr<PropertyType>(
+ new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));
+
+ validators.insert(Validator(acl::SPECPROP_MAXFILESIZELOWERLIMIT,
+ boost::shared_ptr<PropertyType>(
+ new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));
+
+ validators.insert(Validator(acl::SPECPROP_MAXFILESIZEUPPERLIMIT,
+ boost::shared_ptr<PropertyType>(
+ new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));
+
+ validators.insert(Validator(acl::SPECPROP_MAXFILECOUNTLOWERLIMIT,
+ boost::shared_ptr<PropertyType>(
+ new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));
+
+ validators.insert(Validator(acl::SPECPROP_MAXFILECOUNTUPPERLIMIT,
+ boost::shared_ptr<PropertyType>(
+ new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));
+
+ validators.insert(Validator(acl::SPECPROP_MAXPAGESLOWERLIMIT,
+ boost::shared_ptr<PropertyType>(
+ new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));
+
+ validators.insert(Validator(acl::SPECPROP_MAXPAGESUPPERLIMIT,
+ boost::shared_ptr<PropertyType>(
+ new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));
+
+ validators.insert(Validator(acl::SPECPROP_MAXPAGEFACTORLOWERLIMIT,
+ boost::shared_ptr<PropertyType>(
+ new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));
+
+ validators.insert(Validator(acl::SPECPROP_MAXPAGEFACTORUPPERLIMIT,
+ boost::shared_ptr<PropertyType>(
+ new IntPropertyType(0,std::numeric_limits<int64_t>::max()))));
+
+ std::string policyTypes[] = {"ring", "self-destruct", "reject"};
+ std::vector<std::string> v(policyTypes, policyTypes + sizeof(policyTypes) / sizeof(std::string));
+ validators.insert(Validator(acl::SPECPROP_POLICYTYPE,
+ boost::shared_ptr<PropertyType>(
+ new EnumPropertyType(v))));
+
+ // Insert allowed action/object/property sets (generated manually 20140712)
+#define RP registerProperties
+ RP( "Broker::getTimestampConfig",
+ "User querying message timestamp setting ",
+ ACT_ACCESS, OBJ_BROKER);
+ RP( "ExchangeHandlerImpl::query",
+ "AMQP 0-10 protocol received 'query' ",
+ ACT_ACCESS, OBJ_EXCHANGE, "name");
+ RP( "ExchangeHandlerImpl::bound",
+ "AMQP 0-10 query binding ",
+ ACT_ACCESS, OBJ_EXCHANGE, "name queuename routingkey");
+ RP( "ExchangeHandlerImpl::declare",
+ "AMQP 0-10 exchange declare ",
+ ACT_ACCESS, OBJ_EXCHANGE, "name type alternate durable autodelete");
+ RP( "Authorise::access",
+ "AMQP 1.0 exchange access ",
+ ACT_ACCESS, OBJ_EXCHANGE, "name type durable");
+ RP( "Authorise::access",
+ "AMQP 1.0 node resolution ",
+ ACT_ACCESS, OBJ_EXCHANGE, "name");
+ RP( "ManagementAgent::handleMethodRequest",
+ "Management method request ",
+ ACT_ACCESS, OBJ_METHOD, "name schemapackage schemaclass");
+ RP( "ManagementAgent::authorizeAgentMessage",
+ "Management agent method request ",
+ ACT_ACCESS, OBJ_METHOD, "name schemapackage schemaclass");
+ RP( "ManagementAgent::handleGetQuery",
+ "Management agent query ",
+ ACT_ACCESS, OBJ_QUERY, "name schemaclass");
+ RP( "Broker::queryQueue",
+ "QMF 'query queue' method ",
+ ACT_ACCESS, OBJ_QUEUE, "name");
+ RP( "QueueHandlerImpl::query",
+ "AMQP 0-10 query ",
+ ACT_ACCESS, OBJ_QUEUE, "name");
+ RP( "QueueHandlerImpl::declare",
+ "AMQP 0-10 queue declare ",
+ ACT_ACCESS, OBJ_QUEUE, "name alternate durable exclusive autodelete policytype maxqueuecount maxqueuesize");
+ RP( "Authorise::access",
+ "AMQP 1.0 queue access ",
+ ACT_ACCESS, OBJ_QUEUE, "name alternate durable exclusive autodelete policytype maxqueuecount maxqueuesize");
+ RP( "Authorise::access",
+ "AMQP 1.0 node resolution ",
+ ACT_ACCESS, OBJ_QUEUE, "name");
+ RP( "Broker::bind",
+ "AMQP 0-10 or QMF bind request ",
+ ACT_BIND, OBJ_EXCHANGE, "name queuename routingkey");
+ RP( "Authorise::outgoing",
+ "AMQP 1.0 new outgoing link from exchange",
+ ACT_BIND, OBJ_EXCHANGE, "name queuename routingkey");
+ RP( "MessageHandlerImpl::subscribe",
+ "AMQP 0-10 subscribe request ",
+ ACT_CONSUME, OBJ_QUEUE, "name");
+ RP( "Authorise::outgoing",
+ "AMQP 1.0 new outgoing link from queue ",
+ ACT_CONSUME, OBJ_QUEUE, "name");
+ RP( "ConnectionHandler",
+ "TCP/IP connection creation ",
+ ACT_CREATE, OBJ_CONNECTION, "host");
+ RP( "Broker::createExchange",
+ "Create exchange ",
+ ACT_CREATE, OBJ_EXCHANGE, "name type alternate durable autodelete");
+ RP( "ConnectionHandler::Handler::open",
+ "Interbroker link creation ",
+ ACT_CREATE, OBJ_LINK);
+ RP( "Authorise::interlink",
+ "Interbroker link creation ",
+ ACT_CREATE, OBJ_LINK);
+ RP( "Broker::createQueue",
+ "Create queue ",
+ ACT_CREATE, OBJ_QUEUE, "name alternate durable exclusive autodelete policytype paging maxpages maxpagefactor maxqueuecount maxqueuesize maxfilecount maxfilesize");
+ RP( "Broker::deleteExchange",
+ "Delete exchange ",
+ ACT_DELETE, OBJ_EXCHANGE, "name type alternate durable");
+ RP( "Broker::deleteQueue",
+ "Delete queue ",
+ ACT_DELETE, OBJ_QUEUE, "name alternate durable exclusive autodelete policytype");
+ RP( "Broker::queueMoveMessages",
+ "Management 'move queue' request ",
+ ACT_MOVE, OBJ_QUEUE, "name queuename");
+ RP( "SemanticState::route",
+ "AMQP 0-10 received message processing ",
+ ACT_PUBLISH, OBJ_EXCHANGE, "name routingkey");
+ RP( "Authorise::incoming",
+ "AMQP 1.0 establish sender link to queue ",
+ ACT_PUBLISH, OBJ_EXCHANGE, "routingkey");
+ RP( "Authorise::route",
+ "AMQP 1.0 received message processing ",
+ ACT_PUBLISH, OBJ_EXCHANGE, "name routingkey");
+ RP( "Queue::ManagementMethod",
+ "Management 'purge queue' request ",
+ ACT_PURGE, OBJ_QUEUE, "name");
+ RP( "QueueHandlerImpl::purge",
+ "Management 'purge queue' request ",
+ ACT_PURGE, OBJ_QUEUE, "name");
+ RP( "Broker::queueRedirect",
+ "Management 'redirect queue' request ",
+ ACT_REDIRECT,OBJ_QUEUE, "name queuename");
+ RP( "Queue::ManagementMethod",
+ "Management 'reroute queue' request ",
+ ACT_REROUTE, OBJ_QUEUE, "name exchangename");
+ RP( "Broker::unbind",
+ "Management 'unbind exchange' request ",
+ ACT_UNBIND, OBJ_EXCHANGE, "name queuename routingkey");
+ RP( "Broker::setTimestampConfig",
+ "User modifying message timestamp setting",
+ ACT_UPDATE, OBJ_BROKER);
+ }
+
+ AclValidator::~AclValidator(){
+ }
+
+ /* Iterate through the data model and validate the parameters. */
+ void AclValidator::validate(boost::shared_ptr<AclData> d) {
+
+ for (unsigned int cnt=0; cnt< qpid::acl::ACTIONSIZE; cnt++){
+
+ if (d->actionList[cnt]){
+
+ for (unsigned int cnt1=0; cnt1< qpid::acl::OBJECTSIZE; cnt1++){
+
+ if (d->actionList[cnt][cnt1]){
+
+ std::for_each(d->actionList[cnt][cnt1]->begin(),
+ d->actionList[cnt][cnt1]->end(),
+ boost::bind(&AclValidator::validateRuleSet, this, _1));
+ }
+ }
+ }
+ }
+ }
+
+ void AclValidator::validateRuleSet(std::pair<const std::string, qpid::acl::AclData::ruleSet>& rules){
+ std::for_each(rules.second.begin(),
+ rules.second.end(),
+ boost::bind(&AclValidator::validateRule, this, _1));
+ }
+
+ void AclValidator::validateRule(qpid::acl::AclData::Rule& rule){
+ std::for_each(rule.props.begin(),
+ rule.props.end(),
+ boost::bind(&AclValidator::validateProperty, this, _1));
+ }
+
+ void AclValidator::validateProperty(std::pair<const qpid::acl::SpecProperty, std::string>& prop){
+ ValidatorItr itr = validators.find(prop.first);
+ if (itr != validators.end()){
+ QPID_LOG(debug,"ACL: Found validator for property '" << acl::AclHelper::getPropertyStr(itr->first)
+ << "'. " << itr->second->allowedValues());
+
+ if (!itr->second->validate(prop.second)){
+ QPID_LOG(debug, "ACL: Property failed validation. '" << prop.second << "' is not a valid value for '"
+ << AclHelper::getPropertyStr(prop.first) << "'");
+
+ throw Exception( prop.second + " is not a valid value for '" +
+ AclHelper::getPropertyStr(prop.first) + "', " +
+ itr->second->allowedValues());
+ }
+ }
+ }
+
+ /**
+ * validateAllowedProperties
+ * verify that at least one lookup definition can satisfy this
+ * action/object/props tuple.
+ * Return false and conditionally emit a warning log entry if the
+ * incoming definition can not be matched.
+ */
+ bool AclValidator::validateAllowedProperties(qpid::acl::Action action,
+ qpid::acl::ObjectType object,
+ const AclData::specPropertyMap& props,
+ bool emitLog) const {
+ // No rules defined means no match
+ if (!allowedSpecProperties[action][object].get()) {
+ if (emitLog) {
+ QPID_LOG(warning, "ACL rule ignored: Broker never checks for rules with action: '"
+ << AclHelper::getActionStr(action) << "' and object: '"
+ << AclHelper::getObjectTypeStr(object) << "'");
+ }
+ return false;
+ }
+ // two empty property sets is a match
+ if (allowedSpecProperties[action][object]->size() == 0) {
+ if ((props.size() == 0) ||
+ (props.size() == 1 && props.find(acl::SPECPROP_NAME) != props.end())) {
+ return true;
+ }
+ }
+ // Scan vector of rules looking for one that matches all properties
+ bool validRuleFound = false;
+ for (std::vector<AclData::Rule>::const_iterator
+ ruleItr = allowedSpecProperties[action][object]->begin();
+ ruleItr != allowedSpecProperties[action][object]->end() && !validRuleFound;
+ ruleItr++) {
+ // Scan one rule
+ validRuleFound = true;
+ for(AclData::specPropertyMapItr itr = props.begin();
+ itr != props.end();
+ itr++) {
+ if ((*itr).first != acl::SPECPROP_NAME &&
+ ruleItr->props.find((*itr).first) ==
+ ruleItr->props.end()) {
+ // Test property not found in this rule
+ validRuleFound = false;
+ break;
+ }
+ }
+ }
+ if (!validRuleFound) {
+ if (emitLog) {
+ QPID_LOG(warning, "ACL rule ignored: Broker checks for rules with action: '"
+ << AclHelper::getActionStr(action) << "' and object: '"
+ << AclHelper::getObjectTypeStr(object)
+ << "' but will never match with property set: "
+ << AclHelper::propertyMapToString(&props));
+ }
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Return a list of indexes of definitions that this lookup might match
+ */
+ void AclValidator::findPossibleLookupMatch(qpid::acl::Action action,
+ qpid::acl::ObjectType object,
+ const AclData::specPropertyMap& props,
+ std::vector<int>& result) const {
+ if (!allowedSpecProperties[action][object].get()) {
+ return;
+ } else {
+ // Scan vector of rules returning the indexes of all that match
+ bool validRuleFound;
+ for (std::vector<AclData::Rule>::const_iterator
+ ruleItr = allowedSpecProperties[action][object]->begin();
+ ruleItr != allowedSpecProperties[action][object]->end();
+ ruleItr++) {
+ // Scan one rule
+ validRuleFound = true;
+ for(AclData::specPropertyMapItr
+ itr = props.begin(); itr != props.end(); itr++) {
+ if ((*itr).first != acl::SPECPROP_NAME &&
+ ruleItr->props.find((*itr).first) ==
+ ruleItr->props.end()) {
+ // Test property not found in this rule
+ validRuleFound = false;
+ break;
+ }
+ }
+ if (validRuleFound) {
+ result.push_back(ruleItr->rawRuleNum);
+ }
+ }
+ }
+ return;
+ }
+
+ /**
+ * Emit trace log of original property definitions
+ */
+ void AclValidator::tracePropertyDefs() {
+ QPID_LOG(trace, "ACL: Definitions of action, object, (allowed properties) lookups");
+ for (int iA=0; iA<acl::ACTIONSIZE; iA++) {
+ for (int iO=0; iO<acl::OBJECTSIZE; iO++) {
+ if (allowedSpecProperties[iA][iO].get()) {
+ for (std::vector<AclData::Rule>::const_iterator
+ ruleItr = allowedSpecProperties[iA][iO]->begin();
+ ruleItr != allowedSpecProperties[iA][iO]->end();
+ ruleItr++) {
+ std::string pstr;
+ for (AclData::specPropertyMapItr pMItr = ruleItr->props.begin();
+ pMItr != ruleItr->props.end();
+ pMItr++) {
+ pstr += AclHelper::getPropertyStr((SpecProperty) pMItr-> first);
+ pstr += ",";
+ }
+ QPID_LOG(trace, "ACL: Lookup "
+ << std::setfill(' ') << std::setw(2)
+ << ruleItr->rawRuleNum << ": "
+ << ruleItr->lookupHelp << " "
+ << std::setfill(' ') << std::setw(acl::ACTION_STR_WIDTH +1) << std::left
+ << AclHelper::getActionStr(acl::Action(iA))
+ << std::setfill(' ') << std::setw(acl::OBJECTTYPE_STR_WIDTH) << std::left
+ << AclHelper::getObjectTypeStr(acl::ObjectType(iO))
+ << " (" << pstr.substr(0, pstr.length()-1) << ")");
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Construct a record of all the calls that the broker will
+ * make to acl::authorize and the properties for each call.
+ * From that create the list of all the spec properties that
+ * users are then allowed to specify in acl rule files.
+ */
+ void AclValidator::registerProperties(
+ const std::string& source,
+ const std::string& description,
+ Action action,
+ ObjectType object,
+ const std::string& properties) {
+ if (!allowedProperties[action][object].get()) {
+ boost::shared_ptr<std::set<Property> > t1(new std::set<Property>());
+ allowedProperties[action][object] = t1;
+ boost::shared_ptr<std::vector<AclData::Rule> > t2(new std::vector<AclData::Rule>());
+ allowedSpecProperties[action][object] = t2;
+ }
+ std::vector<std::string> props = split(properties, " ");
+ AclData::specPropertyMap spm;
+ for (size_t i=0; i<props.size(); i++) {
+ Property prop = AclHelper::getProperty(props[i]);
+ allowedProperties[action][object]->insert(prop);
+ // Given that the broker will be calling with this property,
+ // determine what user rule settings are allowed.
+ switch (prop) {
+ // Cases where broker and Acl file share property name and meaning
+ case PROP_NAME:
+ spm[SPECPROP_NAME]="";
+ break;
+ case PROP_DURABLE:
+ spm[SPECPROP_DURABLE]="";
+ break;
+ case PROP_OWNER:
+ spm[SPECPROP_OWNER]="";
+ break;
+ case PROP_ROUTINGKEY:
+ spm[SPECPROP_ROUTINGKEY]="";
+ break;
+ case PROP_AUTODELETE:
+ spm[SPECPROP_AUTODELETE]="";
+ break;
+ case PROP_EXCLUSIVE:
+ spm[SPECPROP_EXCLUSIVE]="";
+ break;
+ case PROP_TYPE:
+ spm[SPECPROP_TYPE]="";
+ break;
+ case PROP_ALTERNATE:
+ spm[SPECPROP_ALTERNATE]="";
+ break;
+ case PROP_QUEUENAME:
+ spm[SPECPROP_QUEUENAME]="";
+ break;
+ case PROP_EXCHANGENAME:
+ spm[SPECPROP_EXCHANGENAME]="";
+ break;
+ case PROP_SCHEMAPACKAGE:
+ spm[SPECPROP_SCHEMAPACKAGE]="";
+ break;
+ case PROP_SCHEMACLASS:
+ spm[SPECPROP_SCHEMACLASS]="";
+ break;
+ case PROP_POLICYTYPE:
+ spm[SPECPROP_POLICYTYPE]="";
+ break;
+ case PROP_PAGING:
+ spm[SPECPROP_PAGING]="";
+ break;
+ case PROP_HOST:
+ spm[SPECPROP_HOST]="";
+ break;
+ // Cases where broker supplies a property but Acl has upper/lower limit for it
+ case PROP_MAXPAGES:
+ spm[SPECPROP_MAXPAGESLOWERLIMIT]="";
+ spm[SPECPROP_MAXPAGESUPPERLIMIT]="";
+ break;
+ case PROP_MAXPAGEFACTOR:
+ spm[SPECPROP_MAXPAGEFACTORLOWERLIMIT]="";
+ spm[SPECPROP_MAXPAGEFACTORUPPERLIMIT]="";
+ break;
+ case PROP_MAXQUEUESIZE:
+ spm[SPECPROP_MAXQUEUESIZELOWERLIMIT]="";
+ spm[SPECPROP_MAXQUEUESIZEUPPERLIMIT]="";
+ break;
+ case PROP_MAXQUEUECOUNT:
+ spm[SPECPROP_MAXQUEUECOUNTLOWERLIMIT]="";
+ spm[SPECPROP_MAXQUEUECOUNTUPPERLIMIT]="";
+ break;
+ case PROP_MAXFILESIZE:
+ spm[SPECPROP_MAXFILESIZELOWERLIMIT]="";
+ spm[SPECPROP_MAXFILESIZEUPPERLIMIT]="";
+ break;
+ case PROP_MAXFILECOUNT:
+ spm[SPECPROP_MAXFILECOUNTLOWERLIMIT]="";
+ spm[SPECPROP_MAXFILECOUNTUPPERLIMIT]="";
+ break;
+ default:
+ throw Exception( "acl::RegisterProperties no case for property: " +
+ AclHelper::getPropertyStr(prop) );
+ }
+ }
+ AclData::Rule someProps(propertyIndex, acl::ALLOW, spm, source, description);
+ propertyIndex++;
+ allowedSpecProperties[action][object]->push_back(someProps);
+ }
+
+}}
diff --git a/qpid/cpp/src/qpid/acl/AclValidator.h b/qpid/cpp/src/qpid/acl/AclValidator.h
new file mode 100644
index 0000000000..8f555797c2
--- /dev/null
+++ b/qpid/cpp/src/qpid/acl/AclValidator.h
@@ -0,0 +1,106 @@
+#ifndef QPID_ACL_ACLVALIDATOR_H
+#define QPID_ACL_ACLVALIDATOR_H
+
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/broker/AclModule.h"
+#include "qpid/acl/AclData.h"
+#include "qpid/sys/IntegerTypes.h"
+#include <boost/shared_ptr.hpp>
+#include <boost/concept_check.hpp>
+#include <vector>
+#include <sstream>
+
+namespace qpid {
+namespace acl {
+
+class AclValidator {
+
+ /* Base Property */
+ class PropertyType{
+
+ public:
+ virtual ~PropertyType(){};
+ virtual bool validate(const std::string& val)=0;
+ virtual std::string allowedValues()=0;
+ };
+
+ class IntPropertyType : public PropertyType{
+ int64_t min;
+ int64_t max;
+
+ public:
+ IntPropertyType(int64_t min,int64_t max);
+ virtual ~IntPropertyType (){};
+ virtual bool validate(const std::string& val);
+ virtual std::string allowedValues();
+ };
+
+ class EnumPropertyType : public PropertyType{
+ std::vector<std::string> values;
+
+ public:
+ EnumPropertyType(std::vector<std::string>& allowed);
+ virtual ~EnumPropertyType (){};
+ virtual bool validate(const std::string& val);
+ virtual std::string allowedValues();
+ };
+
+ typedef std::pair<acl::SpecProperty,boost::shared_ptr<PropertyType> > Validator;
+ typedef std::map<acl::SpecProperty,boost::shared_ptr<PropertyType> > ValidatorMap;
+ typedef ValidatorMap::iterator ValidatorItr;
+ typedef boost::shared_ptr<std::set<Property> > AllowedProperties [ACTIONSIZE][OBJECTSIZE];
+ typedef boost::shared_ptr<std::vector<AclData::Rule> > AllowedSpecProperties[ACTIONSIZE][OBJECTSIZE];
+
+ ValidatorMap validators;
+ AllowedProperties allowedProperties;
+ AllowedSpecProperties allowedSpecProperties;
+
+public:
+
+ void validateRuleSet(std::pair<const std::string, qpid::acl::AclData::ruleSet>& rules);
+ void validateRule(qpid::acl::AclData::Rule& rule);
+ void validateProperty(std::pair<const qpid::acl::SpecProperty, std::string>& prop);
+ void validate(boost::shared_ptr<AclData> d);
+ bool validateAllowedProperties(qpid::acl::Action action,
+ qpid::acl::ObjectType object,
+ const AclData::specPropertyMap& props,
+ bool emitLog) const;
+ void findPossibleLookupMatch(qpid::acl::Action action,
+ qpid::acl::ObjectType object,
+ const AclData::specPropertyMap& props,
+ std::vector<int>& result) const;
+ void tracePropertyDefs();
+
+ AclValidator();
+ ~AclValidator();
+
+private:
+ void registerProperties(const std::string& source,
+ const std::string& description,
+ Action action,
+ ObjectType object,
+ const std::string& properties = "");
+ int propertyIndex;
+};
+
+}} // namespace qpid::acl
+
+#endif // QPID_ACL_ACLVALIDATOR_H
diff --git a/qpid/cpp/src/qpid/acl/management-schema.xml b/qpid/cpp/src/qpid/acl/management-schema.xml
new file mode 100644
index 0000000000..2ac20bb324
--- /dev/null
+++ b/qpid/cpp/src/qpid/acl/management-schema.xml
@@ -0,0 +1,85 @@
+<schema package="org.apache.qpid.acl">
+
+<!--
+ * Copyright (c) 2008 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+ <class name="Acl">
+ <property name="brokerRef" type="objId" references="org.apache.qpid.broker:Broker" access="RO" index="y" parentRef="y"/>
+ <property name="policyFile" type="lstr" access="RO" desc="Name of the policy file"/>
+ <property name="enforcingAcl" type="bool" access="RO" desc="Currently Enforcing ACL"/>
+ <property name="transferAcl" type="bool" access="RO" desc="Any transfer ACL rules in force"/>
+ <property name="lastAclLoad" type="absTime" access="RO" desc="Timestamp of last successful load of ACL"/>
+ <property name="maxConnections" type="uint16" access="RO" desc="Maximum allowed connections"/>
+ <property name="maxConnectionsPerIp" type="uint16" access="RO" desc="Maximum allowed connections"/>
+ <property name="maxConnectionsPerUser" type="uint16" access="RO" desc="Maximum allowed connections"/>
+ <property name="maxQueuesPerUser" type="uint16" access="RO" desc="Maximum allowed queues"/>
+ <statistic name="aclDenyCount" type="count64" unit="request" desc="Number of ACL requests denied"/>
+ <statistic name="connectionDenyCount" type="count64" unit="connection" desc="Number of connections denied"/>
+ <statistic name="queueQuotaDenyCount" type="count64" unit="queue" desc="Number of queue creations denied"/>
+
+ <method name="reloadACLFile" desc="Reload the ACL file"/>
+
+ <!--
+ Lookup is a general object lookup
+ User Name
+ Action
+ Object
+ Object Name
+ Property Map consisting of <"name" "value"> string pairs.
+ -->
+ <method name="Lookup" desc="Lookup: user action object [objectName [propertyMap]]">
+ <arg name="userId" dir="I" type="lstr"/>
+ <arg name="action" dir="I" type="lstr"/>
+ <arg name="object" dir="I" type="lstr"/>
+ <arg name="objectName" dir="I" type="lstr"/>
+ <arg name="propertyMap" dir="I" type="map"/>
+ <arg name="result" dir="O" type="lstr"/>
+ </method>
+
+ <!--
+ LookupPublish is a specific lookup for a PUBLISH EXCHANGE fastpath
+ User Name
+ Exchange Name
+ Routing Key
+ -->
+ <method name="LookupPublish" desc="Lookup PUBLISH EXCHANGE: user exchangeName routingKey">
+ <arg name="userId" dir="I" type="lstr"/>
+ <arg name="exchangeName" dir="I" type="lstr"/>
+ <arg name="routingKey" dir="I" type="lstr"/>
+ <arg name="result" dir="O" type="lstr"/>
+ </method>
+
+ </class>
+
+ <eventArguments>
+ <arg name="action" type="sstr"/>
+ <arg name="arguments" type="map"/>
+ <arg name="objectName" type="sstr"/>
+ <arg name="objectType" type="sstr"/>
+ <arg name="reason" type="lstr"/>
+ <arg name="userId" type="sstr"/>
+ <arg name="clientAddr" type="sstr"/>
+ <arg name="queueName" type="sstr"/>
+ </eventArguments>
+
+ <event name="allow" sev="inform" args="userId, action, objectType, objectName, arguments"/>
+ <event name="deny" sev="notice" args="userId, action, objectType, objectName, arguments"/>
+ <event name="connectionDeny" sev="notice" args="userId, clientAddr"/>
+ <event name="queueQuotaDeny" sev="notice" args="userId, queueName"/>
+ <event name="fileLoaded" sev="inform" args="userId"/>
+ <event name="fileLoadFailed" sev="error" args="userId, reason"/>
+
+</schema>
diff --git a/qpid/cpp/src/qpid/amqp/CharSequence.cpp b/qpid/cpp/src/qpid/amqp/CharSequence.cpp
new file mode 100644
index 0000000000..ad5b0ec84c
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/CharSequence.cpp
@@ -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.
+ *
+ */
+#include "CharSequence.h"
+
+namespace qpid {
+namespace amqp {
+
+void CharSequence::init()
+{
+ data = 0;
+ size = 0;
+}
+
+CharSequence::operator bool() const
+{
+ return data && size;
+}
+std::string CharSequence::str() const
+{
+ return (data && size) ? std::string(data, size) : std::string();
+}
+
+CharSequence CharSequence::create()
+{
+ CharSequence c = {0, 0};
+ return c;
+}
+
+CharSequence CharSequence::create(const std::string& str)
+{
+ CharSequence c = {str.data(), str.size()};
+ return c;
+}
+
+CharSequence CharSequence::create(const char* data, size_t size)
+{
+ CharSequence c = {data, size};
+ return c;
+}
+
+CharSequence CharSequence::create(const unsigned char* data, size_t size)
+{
+ CharSequence c = {reinterpret_cast<const char*>(data), size};
+ return c;
+}
+
+}} // namespace qpid::amqp
diff --git a/qpid/cpp/src/qpid/amqp/CharSequence.h b/qpid/cpp/src/qpid/amqp/CharSequence.h
new file mode 100644
index 0000000000..752097c913
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/CharSequence.h
@@ -0,0 +1,52 @@
+#ifndef QPID_AMQP_CHARSEQUENCE_H
+#define QPID_AMQP_CHARSEQUENCE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <stddef.h>
+#include <string>
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+namespace amqp {
+
+/**
+ * Simple record of a particular sequence of chars/bytes. The memroy
+ * referenced is assumed to be owned by some other entity, this is
+ * merely a pointer into a (segment of) it.
+ */
+struct CharSequence
+{
+ const char* data;
+ size_t size;
+
+ QPID_COMMON_EXTERN operator bool() const;
+ QPID_COMMON_EXTERN std::string str() const;
+ QPID_COMMON_EXTERN void init();
+
+ QPID_COMMON_EXTERN static CharSequence create();
+ QPID_COMMON_EXTERN static CharSequence create(const std::string& str);
+ QPID_COMMON_EXTERN static CharSequence create(const char* data, size_t size);
+ QPID_COMMON_EXTERN static CharSequence create(const unsigned char* data, size_t size);
+};
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_CHARSEQUENCE_H*/
diff --git a/qpid/cpp/src/qpid/amqp/Codec.h b/qpid/cpp/src/qpid/amqp/Codec.h
new file mode 100644
index 0000000000..c91cd0a96b
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/Codec.h
@@ -0,0 +1,83 @@
+#ifndef QPID_AMQP_CODEC_H
+#define QPID_AMQP_CODEC_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+namespace qpid {
+namespace amqp {
+
+/**
+ *
+ */
+class Codec
+{
+ public:
+
+
+
+ private:
+
+ struct Constructor
+ {
+ uint8_t code;
+ Descriptor descriptor;
+ bool isDescribed;
+ };
+
+ Constructor readConstructor(Decoder decoder, Reader reader)
+ {
+ Constructor result;
+ result.code = decoder.readCode();
+ if (code == DESCRIPTOR) {
+ result.isDescribed = true;
+ result.descriptor = decoder.readDescriptor();
+ result.code = decoder.readCode();
+ } else {
+ result.isDescribed = false;
+ }
+ return result;
+ }
+};
+
+Codec::Descriptor Codec::Decoder::readDescriptor()
+{
+ uint8_t code = decoder.readCode();
+ switch(code) {
+ case SYMBOL8:
+ return Descriptor(readSequence8());
+ case SYMBOL32:
+ return Descriptor(readSequence32());
+ case ULONG:
+ return Descriptor(readULong());
+ case ULONG_SMALL:
+ return Descriptor((uint64_t) readUByte());
+ case ULONG_ZERO:
+ return Descriptor((uint64_t) 0);
+ default:
+ throw qpid::Exception("Expected descriptor of type ulong or symbol; found " << code);
+ }
+}
+
+Codec::Descriptor::Descriptor(uint64_t id) : value.id(id), type(NUMERIC) {}
+Codec::Descriptor::Descriptor(const CharSequence& symbol) : value.symbol(symbol), type(SYMBOLIC) {}
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_CODEC_H*/
diff --git a/qpid/cpp/src/qpid/amqp/Constructor.h b/qpid/cpp/src/qpid/amqp/Constructor.h
new file mode 100644
index 0000000000..444e455670
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/Constructor.h
@@ -0,0 +1,42 @@
+#ifndef QPID_AMQP_CONSTRUCTOR_H
+#define QPID_AMQP_CONSTRUCTOR_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/amqp/Descriptor.h"
+namespace qpid {
+namespace amqp {
+
+/**
+ * Representation of an AMQP 1.0 type 'constructor' (i.e. a type code
+ * with an optional descriptor)
+ */
+struct Constructor
+{
+ uint8_t code;
+ Descriptor descriptor;
+ bool isDescribed;
+
+ Constructor(uint8_t c) : code(c), descriptor(0), isDescribed(false) {}
+};
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_CONSTRUCTOR_H*/
diff --git a/qpid/cpp/src/qpid/amqp/DataBuilder.cpp b/qpid/cpp/src/qpid/amqp/DataBuilder.cpp
new file mode 100644
index 0000000000..aeb9b9c612
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/DataBuilder.cpp
@@ -0,0 +1,196 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "DataBuilder.h"
+#include "CharSequence.h"
+#include "qpid/log/Statement.h"
+#include "qpid/types/encodings.h"
+
+namespace qpid {
+namespace amqp {
+
+void DataBuilder::onNull(const Descriptor*)
+{
+ handle(qpid::types::Variant());
+}
+void DataBuilder::onBoolean(bool v, const Descriptor*)
+{
+ handle(v);
+}
+void DataBuilder::onUByte(uint8_t v, const Descriptor*)
+{
+ handle(v);
+}
+void DataBuilder::onUShort(uint16_t v, const Descriptor*)
+{
+ handle(v);
+}
+void DataBuilder::onUInt(uint32_t v, const Descriptor*)
+{
+ handle(v);
+}
+void DataBuilder::onULong(uint64_t v, const Descriptor*)
+{
+ handle(v);
+}
+void DataBuilder::onByte(int8_t v, const Descriptor*)
+{
+ handle(v);
+}
+void DataBuilder::onShort(int16_t v, const Descriptor*)
+{
+ handle(v);
+}
+void DataBuilder::onInt(int32_t v, const Descriptor*)
+{
+ handle(v);
+}
+void DataBuilder::onLong(int64_t v, const Descriptor*)
+{
+ handle(v);
+}
+void DataBuilder::onFloat(float v, const Descriptor*)
+{
+ handle(v);
+}
+void DataBuilder::onDouble(double v, const Descriptor*)
+{
+ handle(v);
+}
+void DataBuilder::onUuid(const CharSequence& v, const Descriptor*)
+{
+ if (v.size == qpid::types::Uuid::SIZE) {
+ handle(qpid::types::Uuid(v.data));
+ }
+}
+void DataBuilder::onTimestamp(int64_t v, const Descriptor*)
+{
+ handle(v);
+}
+
+void DataBuilder::handle(const qpid::types::Variant& v)
+{
+ switch (nested.top()->getType()) {
+ case qpid::types::VAR_MAP:
+ nested.push(&nested.top()->asMap()[v.asString()]);
+ break;
+ case qpid::types::VAR_LIST:
+ nested.top()->asList().push_back(v);
+ break;
+ default:
+ *(nested.top()) = v;
+ nested.pop();
+ break;
+ }
+}
+
+void DataBuilder::onBinary(const CharSequence& v, const Descriptor*)
+{
+ onString(std::string(v.data, v.size), qpid::types::encodings::BINARY);
+}
+void DataBuilder::onString(const CharSequence& v, const Descriptor*)
+{
+ onString(std::string(v.data, v.size), qpid::types::encodings::UTF8);
+}
+void DataBuilder::onSymbol(const CharSequence& v, const Descriptor*)
+{
+ onString(std::string(v.data, v.size), qpid::types::encodings::ASCII);
+}
+
+void DataBuilder::onString(const std::string& value, const std::string& encoding)
+{
+ switch (nested.top()->getType()) {
+ case qpid::types::VAR_MAP:
+ nested.push(&nested.top()->asMap()[value]);
+ break;
+ case qpid::types::VAR_LIST:
+ nested.top()->asList().push_back(qpid::types::Variant(value));
+ nested.top()->asList().back().setEncoding(encoding);
+ break;
+ default:
+ qpid::types::Variant& v = *(nested.top());
+ v = value;
+ v.setEncoding(encoding);
+ nested.pop();
+ break;
+ }
+}
+
+bool DataBuilder::proceed()
+{
+ return !nested.empty();
+}
+
+bool DataBuilder::nest(const qpid::types::Variant& n)
+{
+ switch (nested.top()->getType()) {
+ case qpid::types::VAR_MAP:
+ if (nested.size() > 1 || nested.top()->asMap().size() > 0) {
+ QPID_LOG(error, QPID_MSG("Expecting map key; got " << n << " " << *(nested.top())));
+ }
+ break;
+ case qpid::types::VAR_LIST:
+ nested.top()->asList().push_back(n);
+ nested.push(&nested.top()->asList().back());
+ break;
+ default:
+ qpid::types::Variant& value = *(nested.top());
+ value = n;
+ nested.pop();
+ nested.push(&value);
+ break;
+ }
+ return true;
+}
+
+bool DataBuilder::onStartList(uint32_t, const CharSequence&, const CharSequence&, const Descriptor*)
+{
+ return nest(qpid::types::Variant::List());
+}
+void DataBuilder::onEndList(uint32_t /*count*/, const Descriptor*)
+{
+ nested.pop();
+}
+bool DataBuilder::onStartMap(uint32_t /*count*/, const CharSequence&, const CharSequence&, const Descriptor*)
+{
+ return nest(qpid::types::Variant::Map());
+}
+void DataBuilder::onEndMap(uint32_t /*count*/, const Descriptor*)
+{
+ nested.pop();
+}
+bool DataBuilder::onStartArray(uint32_t count, const CharSequence&, const Constructor&, const Descriptor*)
+{
+ return onStartList(count, CharSequence::create(), CharSequence::create(), 0);
+}
+void DataBuilder::onEndArray(uint32_t count, const Descriptor*)
+{
+ onEndList(count, 0);
+}
+qpid::types::Variant& DataBuilder::getValue()
+{
+ return base;
+}
+DataBuilder::DataBuilder(qpid::types::Variant v) : base(v)
+{
+ nested.push(&base);
+}
+DataBuilder::~DataBuilder() {}
+}} // namespace qpid::amqp
diff --git a/qpid/cpp/src/qpid/amqp/DataBuilder.h b/qpid/cpp/src/qpid/amqp/DataBuilder.h
new file mode 100644
index 0000000000..51ee3da5f8
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/DataBuilder.h
@@ -0,0 +1,79 @@
+#ifndef QPID_AMQP_DATABUILDER_H
+#define QPID_AMQP_DATABUILDER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "Reader.h"
+#include "qpid/types/Variant.h"
+#include "qpid/CommonImportExport.h"
+#include <stack>
+
+namespace qpid {
+namespace amqp {
+
+/**
+ * Utility to build a Variant based structure (or value) from a data stream
+ */
+class DataBuilder : public Reader
+{
+ public:
+ QPID_COMMON_EXTERN DataBuilder(qpid::types::Variant);
+ QPID_COMMON_EXTERN virtual ~DataBuilder();
+ QPID_COMMON_EXTERN void onNull(const Descriptor*);
+ QPID_COMMON_EXTERN void onBoolean(bool, const Descriptor*);
+ QPID_COMMON_EXTERN void onUByte(uint8_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onUShort(uint16_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onUInt(uint32_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onULong(uint64_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onByte(int8_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onShort(int16_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onInt(int32_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onLong(int64_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onFloat(float, const Descriptor*);
+ QPID_COMMON_EXTERN void onDouble(double, const Descriptor*);
+ QPID_COMMON_EXTERN void onUuid(const CharSequence&, const Descriptor*);
+ QPID_COMMON_EXTERN void onTimestamp(int64_t, const Descriptor*);
+
+ QPID_COMMON_EXTERN void onBinary(const CharSequence&, const Descriptor*);
+ QPID_COMMON_EXTERN void onString(const CharSequence&, const Descriptor*);
+ QPID_COMMON_EXTERN void onSymbol(const CharSequence&, const Descriptor*);
+
+ QPID_COMMON_EXTERN bool onStartList(uint32_t /*count*/, const CharSequence&, const CharSequence&, const Descriptor*);
+ QPID_COMMON_EXTERN bool onStartMap(uint32_t /*count*/, const CharSequence&, const CharSequence&, const Descriptor*);
+ QPID_COMMON_EXTERN bool onStartArray(uint32_t /*count*/, const CharSequence&, const Constructor&, const Descriptor*);
+ QPID_COMMON_EXTERN void onEndList(uint32_t /*count*/, const Descriptor*);
+ QPID_COMMON_EXTERN void onEndMap(uint32_t /*count*/, const Descriptor*);
+ QPID_COMMON_EXTERN void onEndArray(uint32_t /*count*/, const Descriptor*);
+
+ QPID_COMMON_EXTERN bool proceed();
+ QPID_COMMON_EXTERN qpid::types::Variant& getValue();
+ private:
+ qpid::types::Variant base;
+ std::stack<qpid::types::Variant*> nested;
+ std::string key;
+
+ void handle(const qpid::types::Variant& v);
+ bool nest(const qpid::types::Variant& v);
+ void onString(const std::string&, const std::string&);
+};
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_DATABUILDER_H*/
diff --git a/qpid/cpp/src/qpid/amqp/Decoder.cpp b/qpid/cpp/src/qpid/amqp/Decoder.cpp
new file mode 100644
index 0000000000..53cd367c25
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/Decoder.cpp
@@ -0,0 +1,448 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/amqp/Decoder.h"
+#include "qpid/amqp/CharSequence.h"
+#include "qpid/amqp/Constructor.h"
+#include "qpid/amqp/Descriptor.h"
+#include "qpid/amqp/MapBuilder.h"
+#include "qpid/amqp/Reader.h"
+#include "qpid/amqp/typecodes.h"
+#include "qpid/types/Uuid.h"
+#include "qpid/types/Variant.h"
+#include "qpid/log/Statement.h"
+#include "qpid/Exception.h"
+
+namespace qpid {
+namespace amqp {
+
+using namespace qpid::amqp::typecodes;
+
+Decoder::Decoder(const char* d, size_t s) : start(d), size(s), position(0), current(0) {}
+
+void Decoder::readMap(qpid::types::Variant::Map& map)
+{
+ MapBuilder builder;
+ read(builder);
+ map = builder.getMap();
+}
+
+qpid::types::Variant::Map Decoder::readMap()
+{
+ qpid::types::Variant::Map map;
+ readMap(map);
+ return map;
+}
+
+void Decoder::read(Reader& reader)
+{
+ while (available() && reader.proceed()) {
+ readOne(reader);
+ }
+}
+
+void Decoder::readOne(Reader& reader)
+{
+ const char* temp = start + position;
+ current = position;
+ Constructor c = readConstructor();
+ if (c.isDescribed) reader.onDescriptor(c.descriptor, temp);
+ readValue(reader, c.code, c.isDescribed ? &c.descriptor : 0);
+}
+
+void Decoder::readValue(Reader& reader, uint8_t code, const Descriptor* descriptor)
+{
+ switch(code) {
+ case NULL_VALUE:
+ reader.onNull(descriptor);
+ break;
+ case BOOLEAN:
+ reader.onBoolean(readBoolean(), descriptor);
+ break;
+ case BOOLEAN_TRUE:
+ reader.onBoolean(true, descriptor);
+ break;
+ case BOOLEAN_FALSE:
+ reader.onBoolean(false, descriptor);
+ break;
+ case UBYTE:
+ reader.onUByte(readUByte(), descriptor);
+ break;
+ case USHORT:
+ reader.onUShort(readUShort(), descriptor);
+ break;
+ case UINT:
+ reader.onUInt(readUInt(), descriptor);
+ break;
+ case UINT_SMALL:
+ reader.onUInt(readUByte(), descriptor);
+ break;
+ case UINT_ZERO:
+ reader.onUInt(0, descriptor);
+ break;
+ case ULONG:
+ reader.onULong(readULong(), descriptor);
+ break;
+ case ULONG_SMALL:
+ reader.onULong(readUByte(), descriptor);
+ break;
+ case ULONG_ZERO:
+ reader.onULong(0, descriptor);
+ break;
+ case BYTE:
+ reader.onByte(readByte(), descriptor);
+ break;
+ case SHORT:
+ reader.onShort(readShort(), descriptor);
+ break;
+ case INT:
+ reader.onInt(readInt(), descriptor);
+ break;
+ case INT_SMALL:
+ reader.onInt(readByte(), descriptor);
+ break;
+ case LONG:
+ reader.onLong(readLong(), descriptor);
+ break;
+ case LONG_SMALL:
+ reader.onLong(readByte(), descriptor);
+ break;
+ case FLOAT:
+ reader.onFloat(readFloat(), descriptor);
+ break;
+ case DOUBLE:
+ reader.onDouble(readDouble(), descriptor);
+ break;
+ case UUID:
+ reader.onUuid(readRawUuid(), descriptor);
+ break;
+ case TIMESTAMP:
+ reader.onTimestamp(readLong(), descriptor);
+ break;
+
+ case BINARY8:
+ reader.onBinary(readSequence8(), descriptor);
+ break;
+ case BINARY32:
+ reader.onBinary(readSequence32(), descriptor);
+ break;
+ case STRING8:
+ reader.onString(readSequence8(), descriptor);
+ break;
+ case STRING32:
+ reader.onString(readSequence32(), descriptor);
+ break;
+ case SYMBOL8:
+ reader.onSymbol(readSequence8(), descriptor);
+ break;
+ case SYMBOL32:
+ reader.onSymbol(readSequence32(), descriptor);
+ break;
+
+ case LIST0:
+ reader.onStartList(0, CharSequence::create(), getCurrent(0), descriptor);
+ reader.onEndList(0, descriptor);
+ break;
+ case LIST8:
+ readList8(reader, descriptor);
+ break;
+ case LIST32:
+ readList32(reader, descriptor);
+ break;
+ case MAP8:
+ readMap8(reader, descriptor);
+ break;
+ case MAP32:
+ readMap32(reader, descriptor);
+ break;
+ case ARRAY8:
+ readArray8(reader, descriptor);
+ break;
+ case ARRAY32:
+ readArray32(reader, descriptor);
+ break;
+ default:
+ break;
+ }
+}
+
+void Decoder::readList8(Reader& reader, const Descriptor* descriptor)
+{
+ uint8_t size = readUByte();
+ uint8_t count = readUByte();
+ readList(reader, size-sizeof(size), count, descriptor);
+}
+
+void Decoder::readList32(Reader& reader, const Descriptor* descriptor)
+{
+ uint32_t size = readUInt();
+ uint32_t count = readUInt();
+ readList(reader, size-sizeof(size), count, descriptor);
+}
+
+void Decoder::readMap8(Reader& reader, const Descriptor* descriptor)
+{
+ uint8_t size = readUByte();
+ uint8_t count = readUByte();
+ readMap(reader, size-sizeof(size), count, descriptor);
+}
+
+void Decoder::readMap32(Reader& reader, const Descriptor* descriptor)
+{
+ uint32_t size = readUInt();
+ uint32_t count = readUInt();
+ readMap(reader, size-sizeof(size), count, descriptor);
+}
+
+void Decoder::readArray8(Reader& reader, const Descriptor* descriptor)
+{
+ uint8_t size = readUByte();
+ uint8_t count = readUByte();
+ readArray(reader, size-sizeof(size), count, descriptor);
+}
+
+void Decoder::readArray32(Reader& reader, const Descriptor* descriptor)
+{
+ uint32_t size = readUInt();
+ uint32_t count = readUInt();
+ readArray(reader, size-sizeof(size), count, descriptor);
+}
+
+void Decoder::readList(Reader& reader, uint32_t size, uint32_t count, const Descriptor* descriptor)
+{
+ if (reader.onStartList(count, CharSequence::create(data(), size), getCurrent(size), descriptor)) {
+ for (uint32_t i = 0; i < count; ++i) {
+ readOne(reader);
+ }
+ reader.onEndList(count, descriptor);
+ } else {
+ //skip
+ advance(size);
+ }
+}
+void Decoder::readMap(Reader& reader, uint32_t size, uint32_t count, const Descriptor* descriptor)
+{
+ if (reader.onStartMap(count, CharSequence::create(data(), size), getCurrent(size), descriptor)) {
+ for (uint32_t i = 0; i < count; ++i) {
+ readOne(reader);
+ }
+ reader.onEndMap(count, descriptor);
+ } else {
+ //skip
+ advance(size);
+ }
+}
+
+void Decoder::readArray(Reader& reader, uint32_t size, uint32_t count, const Descriptor* descriptor)
+{
+ size_t temp = position;
+ Constructor constructor = readConstructor();
+ CharSequence raw = CharSequence::create(data(), size-(position-temp));
+ if (reader.onStartArray(count, raw, constructor, descriptor)) {
+ for (uint32_t i = 0; i < count; ++i) {
+ readValue(reader, constructor.code, constructor.isDescribed ? &constructor.descriptor : 0);
+ }
+ reader.onEndArray(count, descriptor);
+ } else {
+ //skip
+ advance(raw.size);
+ }
+}
+
+
+Constructor Decoder::readConstructor()
+{
+ Constructor result(readCode());
+ if (result.code == DESCRIPTOR) {
+ result.isDescribed = true;
+ result.descriptor = readDescriptor();
+ result.code = readCode();
+ for (Descriptor* d = &result.descriptor; result.code == DESCRIPTOR; result.code = readCode()) {
+ d = d->nest(readDescriptor());
+ }
+ } else {
+ result.isDescribed = false;
+ }
+ return result;
+}
+
+Descriptor Decoder::readDescriptor()
+{
+ uint8_t code = readCode();
+ switch(code) {
+ case SYMBOL8:
+ return Descriptor(readSequence8());
+ case SYMBOL32:
+ return Descriptor(readSequence32());
+ case ULONG:
+ return Descriptor(readULong());
+ case ULONG_SMALL:
+ return Descriptor((uint64_t) readUByte());
+ case ULONG_ZERO:
+ return Descriptor((uint64_t) 0);
+ default:
+ throw qpid::Exception(QPID_MSG("Expected descriptor of type ulong or symbol; found " << (int)code));
+ }
+}
+
+void Decoder::advance(size_t n)
+{
+ if (n > available()) throw qpid::Exception(QPID_MSG("Out of Bounds: requested advance of " << n << " at " << position << " but only " << available() << " available"));
+ position += n;
+}
+
+const char* Decoder::data()
+{
+ return start + position;
+}
+
+size_t Decoder::available()
+{
+ return size - position;
+}
+
+uint8_t Decoder::readCode()
+{
+ return readUByte();
+}
+
+bool Decoder::readBoolean()
+{
+ return readUByte();
+}
+
+uint8_t Decoder::readUByte()
+{
+ return static_cast<uint8_t>(start[position++]);
+}
+
+uint16_t Decoder::readUShort()
+{
+ uint16_t hi = (unsigned char) start[position++];
+ hi = hi << 8;
+ hi |= (unsigned char) start[position++];
+ return hi;
+}
+
+uint32_t Decoder::readUInt()
+{
+ uint32_t a = (unsigned char) start[position++];
+ uint32_t b = (unsigned char) start[position++];
+ uint32_t c = (unsigned char) start[position++];
+ uint32_t d = (unsigned char) start[position++];
+ a = a << 24;
+ a |= b << 16;
+ a |= c << 8;
+ a |= d;
+ return a;
+}
+
+uint64_t Decoder::readULong()
+{
+ uint64_t hi =readUInt();
+ uint64_t lo = readUInt();
+ hi = hi << 32;
+ return hi | lo;
+}
+
+int8_t Decoder::readByte()
+{
+ return (int8_t) readUByte();
+}
+
+int16_t Decoder::readShort()
+{
+ return (int16_t) readUShort();
+}
+
+int32_t Decoder::readInt()
+{
+ return (int32_t) readUInt();
+}
+
+int64_t Decoder::readLong()
+{
+ return (int64_t) readULong();
+}
+
+float Decoder::readFloat()
+{
+ union {
+ uint32_t i;
+ float f;
+ } val;
+ val.i = readUInt();
+ return val.f;
+}
+
+double Decoder::readDouble()
+{
+ union {
+ uint64_t i;
+ double f;
+ } val;
+ val.i = readULong();
+ return val.f;
+}
+
+CharSequence Decoder::readSequence8()
+{
+ CharSequence s;
+ s.size = readUByte();
+ s.data = start + position;
+ advance(s.size);
+ return s;
+}
+
+CharSequence Decoder::readSequence32()
+{
+ CharSequence s;
+ s.size = readUInt();
+ s.data = start + position;
+ advance(s.size);
+ return s;
+}
+
+qpid::types::Uuid Decoder::readUuid()
+{
+ qpid::types::Uuid uuid(start + position);
+ advance(16);
+ return uuid;
+}
+
+CharSequence Decoder::readRawUuid()
+{
+ CharSequence s;
+ s.data = start + position;
+ s.size = 16;
+ advance(s.size);
+ return s;
+}
+
+size_t Decoder::getPosition() const { return position; }
+size_t Decoder::getSize() const { return size; }
+void Decoder::resetSize(size_t s) { size = s; }
+
+CharSequence Decoder::getCurrent(size_t remaining) const
+{
+ return CharSequence::create(start + current, (position-current)+remaining);
+}
+
+}} // namespace qpid::amqp
diff --git a/qpid/cpp/src/qpid/amqp/Decoder.h b/qpid/cpp/src/qpid/amqp/Decoder.h
new file mode 100644
index 0000000000..a78518be2b
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/Decoder.h
@@ -0,0 +1,100 @@
+#ifndef QPID_AMQP_DECODER_H
+#define QPID_AMQP_DECODER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/CommonImportExport.h"
+#include <map>
+#include <string>
+#include <stddef.h>
+
+namespace qpid {
+namespace types {
+class Uuid;
+class Variant;
+}
+namespace amqp {
+struct CharSequence;
+struct Constructor;
+struct Descriptor;
+class Reader;
+
+/**
+ * Class to assist in decoding an AMQP encoded data-stream.
+ */
+class Decoder
+{
+ public:
+ QPID_COMMON_EXTERN Decoder(const char*, size_t);
+
+ QPID_COMMON_EXTERN size_t available();
+ QPID_COMMON_EXTERN uint8_t readCode();
+
+ QPID_COMMON_EXTERN bool readBoolean();
+ QPID_COMMON_EXTERN uint8_t readUByte();
+ QPID_COMMON_EXTERN uint16_t readUShort();
+ QPID_COMMON_EXTERN uint32_t readUInt();
+ QPID_COMMON_EXTERN uint64_t readULong();
+ QPID_COMMON_EXTERN int8_t readByte();
+ QPID_COMMON_EXTERN int16_t readShort();
+ QPID_COMMON_EXTERN int32_t readInt();
+ QPID_COMMON_EXTERN int64_t readLong();
+ QPID_COMMON_EXTERN float readFloat();
+ QPID_COMMON_EXTERN double readDouble();
+ QPID_COMMON_EXTERN qpid::types::Uuid readUuid();
+ QPID_COMMON_EXTERN CharSequence readSequence8();
+ QPID_COMMON_EXTERN CharSequence readSequence32();
+ QPID_COMMON_EXTERN Descriptor readDescriptor();
+ QPID_COMMON_EXTERN void read(Reader& reader);
+
+ QPID_COMMON_EXTERN void readMap(std::map<std::string, qpid::types::Variant>&);
+ QPID_COMMON_EXTERN std::map<std::string, qpid::types::Variant> readMap();
+ QPID_COMMON_EXTERN void advance(size_t);
+ QPID_COMMON_EXTERN size_t getPosition() const;
+ QPID_COMMON_EXTERN void resetSize(size_t size);
+ QPID_COMMON_EXTERN size_t getSize() const;
+
+ private:
+ const char* const start;
+ size_t size;
+ size_t position;
+ size_t current;
+
+ void readOne(Reader& reader);
+ void readValue(Reader& reader, uint8_t code, const Descriptor* descriptor);
+ void readList(Reader& reader, uint32_t size, uint32_t count, const Descriptor* descriptor);
+ void readMap(Reader& reader, uint32_t size, uint32_t count, const Descriptor* descriptor);
+ void readArray(Reader& reader, uint32_t size, uint32_t count, const Descriptor* descriptor);
+ void readList8(Reader& reader, const Descriptor* descriptor);
+ void readList32(Reader& reader, const Descriptor* descriptor);
+ void readMap8(Reader& reader, const Descriptor* descriptor);
+ void readMap32(Reader& reader, const Descriptor* descriptor);
+ void readArray8(Reader& reader, const Descriptor* descriptor);
+ void readArray32(Reader& reader, const Descriptor* descriptor);
+ CharSequence readRawUuid();
+ Constructor readConstructor();
+ const char* data();
+ CharSequence getCurrent(size_t remaining) const;
+};
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_DECODER_H*/
diff --git a/qpid/cpp/src/qpid/amqp/Descriptor.cpp b/qpid/cpp/src/qpid/amqp/Descriptor.cpp
new file mode 100644
index 0000000000..43d388ee76
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/Descriptor.cpp
@@ -0,0 +1,148 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "Descriptor.h"
+#include "descriptors.h"
+#include <qpid/framing/reply_exceptions.h>
+#include <map>
+
+namespace qpid {
+namespace amqp {
+
+Descriptor::Descriptor(uint64_t code) : type(NUMERIC) { value.code = code; }
+
+Descriptor::Descriptor(const CharSequence& symbol) : type(SYMBOLIC) { value.symbol = symbol; }
+
+bool Descriptor::match(const std::string& symbol, uint64_t code) const
+{
+ switch (type) {
+ case SYMBOLIC:
+ return symbol.compare(0, symbol.size(), value.symbol.data, value.symbol.size) == 0;
+ case NUMERIC:
+ return code == value.code;
+ }
+ return false;
+}
+
+size_t Descriptor::getSize() const
+{
+ size_t size = 1/*descriptor indicator*/ + 1/*type code*/;
+ switch (type) {
+ case Descriptor::NUMERIC:
+ if (value.code > 0) size += value.code < 256 ? 1/*encode as byte*/ : 8/*encode as long*/;
+ //else value will be indicated through ULONG_ZERO typecode
+ break;
+ case Descriptor::SYMBOLIC:
+ size += value.symbol.size < 256? 1/*size field is a byte*/ : 4/*size field is an int*/;
+ size += value.symbol.size;
+ break;
+ }
+ return size;
+}
+
+Descriptor* Descriptor::nest(const Descriptor& d)
+{
+ nested = boost::shared_ptr<Descriptor>(new Descriptor(0));
+ *nested = d;
+ return nested.get();
+}
+
+namespace {
+
+class DescriptorMap {
+ typedef std::map<uint64_t, std::string> SymbolMap;
+ typedef std::map<std::string, uint64_t> CodeMap;
+
+ SymbolMap symbols;
+ CodeMap codes;
+
+ public:
+ DescriptorMap() {
+ symbols[message::HEADER_CODE] = message::HEADER_SYMBOL;
+ symbols[message::DELIVERY_ANNOTATIONS_CODE] = message::DELIVERY_ANNOTATIONS_SYMBOL;
+ symbols[message::MESSAGE_ANNOTATIONS_CODE] = message::MESSAGE_ANNOTATIONS_SYMBOL;
+ symbols[message::PROPERTIES_CODE] = message::PROPERTIES_SYMBOL;
+ symbols[message::APPLICATION_PROPERTIES_CODE] = message::APPLICATION_PROPERTIES_SYMBOL;
+ symbols[message::DATA_CODE] = message::DATA_SYMBOL;
+ symbols[message::AMQP_SEQUENCE_CODE] = message::AMQP_SEQUENCE_SYMBOL;
+ symbols[message::AMQP_VALUE_CODE] = message::AMQP_VALUE_SYMBOL;
+ symbols[message::FOOTER_CODE] = message::FOOTER_SYMBOL;
+ symbols[message::ACCEPTED_CODE] = message::ACCEPTED_SYMBOL;
+ symbols[sasl::SASL_MECHANISMS_CODE] = sasl::SASL_MECHANISMS_SYMBOL;
+ symbols[sasl::SASL_INIT_CODE] = sasl::SASL_INIT_SYMBOL;
+ symbols[sasl::SASL_CHALLENGE_CODE] = sasl::SASL_CHALLENGE_SYMBOL;
+ symbols[sasl::SASL_RESPONSE_CODE] = sasl::SASL_RESPONSE_SYMBOL;
+ symbols[sasl::SASL_OUTCOME_CODE] = sasl::SASL_OUTCOME_SYMBOL;
+ symbols[filters::LEGACY_DIRECT_FILTER_CODE] = filters::LEGACY_DIRECT_FILTER_SYMBOL;
+ symbols[filters::LEGACY_TOPIC_FILTER_CODE] = filters::LEGACY_TOPIC_FILTER_SYMBOL;
+ symbols[filters::LEGACY_HEADERS_FILTER_CODE] = filters::LEGACY_HEADERS_FILTER_SYMBOL;
+ symbols[filters::SELECTOR_FILTER_CODE] = filters::SELECTOR_FILTER_SYMBOL;
+ symbols[filters::XQUERY_FILTER_CODE] = filters::XQUERY_FILTER_SYMBOL;
+ symbols[lifetime_policy::DELETE_ON_CLOSE_CODE] = lifetime_policy::DELETE_ON_CLOSE_SYMBOL;
+ symbols[lifetime_policy::DELETE_ON_NO_LINKS_CODE] = lifetime_policy::DELETE_ON_NO_LINKS_SYMBOL;
+ symbols[lifetime_policy::DELETE_ON_NO_MESSAGES_CODE] = lifetime_policy::DELETE_ON_NO_MESSAGES_SYMBOL;
+ symbols[lifetime_policy::DELETE_ON_NO_LINKS_OR_MESSAGES_CODE] = lifetime_policy::DELETE_ON_NO_LINKS_OR_MESSAGES_SYMBOL;
+ symbols[transaction::DECLARE_CODE] = transaction::DECLARE_SYMBOL;
+ symbols[transaction::DISCHARGE_CODE] = transaction::DISCHARGE_SYMBOL;
+ symbols[transaction::DECLARED_CODE] = transaction::DECLARED_SYMBOL;
+ symbols[transaction::TRANSACTIONAL_STATE_CODE] = transaction::TRANSACTIONAL_STATE_SYMBOL;
+ symbols[0] = "unknown-descriptor";
+
+ for (SymbolMap::const_iterator i = symbols.begin(); i != symbols.end(); ++i)
+ codes[i->second] = i->first;
+ }
+
+ std::string operator[](uint64_t code) const {
+ SymbolMap::const_iterator i = symbols.find(code);
+ return (i == symbols.end()) ? "unknown-descriptor" : i->second;
+ }
+
+ uint64_t operator[](const std::string& symbol) const {
+ CodeMap::const_iterator i = codes.find(symbol);
+ return (i == codes.end()) ? 0 : i->second;
+ }
+};
+
+DescriptorMap DESCRIPTOR_MAP;
+}
+
+std::string Descriptor::symbol() const {
+ switch (type) {
+ case Descriptor::NUMERIC: return DESCRIPTOR_MAP[value.code];
+ case Descriptor::SYMBOLIC: return value.symbol.str();
+ }
+ assert(0);
+ return std::string();
+}
+
+uint64_t Descriptor::code() const {
+ switch (type) {
+ case Descriptor::NUMERIC: return value.code;
+ case Descriptor::SYMBOLIC: return DESCRIPTOR_MAP[value.symbol.str()];
+ }
+ assert(0);
+ return 0;
+}
+
+std::ostream& operator<<(std::ostream& os, const Descriptor& d) {
+ return os << d.symbol() << "(" << "0x" << std::hex << d.code() << ")";
+}
+
+}} // namespace qpid::amqp
diff --git a/qpid/cpp/src/qpid/amqp/Descriptor.h b/qpid/cpp/src/qpid/amqp/Descriptor.h
new file mode 100644
index 0000000000..3726114769
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/Descriptor.h
@@ -0,0 +1,60 @@
+#ifndef QPID_AMQP_DESCRIPTOR_H
+#define QPID_AMQP_DESCRIPTOR_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/amqp/CharSequence.h"
+#include "qpid/sys/IntegerTypes.h"
+#include <ostream>
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace amqp {
+
+/**
+ * Representation of an AMQP 1.0 type descriptor.
+ */
+struct Descriptor
+{
+ union {
+ CharSequence symbol;
+ uint64_t code;
+ } value;
+ enum {
+ NUMERIC,
+ SYMBOLIC
+ } type;
+ boost::shared_ptr<Descriptor> nested;
+
+ QPID_COMMON_EXTERN Descriptor(uint64_t code);
+ QPID_COMMON_EXTERN Descriptor(const CharSequence& symbol);
+ QPID_COMMON_EXTERN bool match(const std::string&, uint64_t) const;
+ QPID_COMMON_EXTERN size_t getSize() const;
+ QPID_COMMON_EXTERN Descriptor* nest(const Descriptor& d);
+ QPID_COMMON_EXTERN std::string symbol() const;
+ QPID_COMMON_EXTERN uint64_t code() const;
+};
+
+QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream& os, const Descriptor& d);
+
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_DESCRIPTOR_H*/
diff --git a/qpid/cpp/src/qpid/amqp/Encoder.cpp b/qpid/cpp/src/qpid/amqp/Encoder.cpp
new file mode 100644
index 0000000000..86b59fb1a2
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/Encoder.cpp
@@ -0,0 +1,523 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/amqp/Encoder.h"
+#include "qpid/amqp/CharSequence.h"
+#include "qpid/amqp/Descriptor.h"
+#include "qpid/amqp/typecodes.h"
+#include "qpid/types/Uuid.h"
+#include "qpid/types/Variant.h"
+#include "qpid/types/encodings.h"
+#include "qpid/log/Statement.h"
+#include "qpid/Exception.h"
+#include <assert.h>
+#include <string.h>
+
+using namespace qpid::types::encodings;
+using qpid::types::Variant;
+
+namespace qpid {
+namespace amqp {
+
+Encoder::Overflow::Overflow() : Exception("Buffer overflow in encoder!") {}
+
+Encoder::Encoder(char* d, size_t s) : data(d), size(s), position(0), grow(false) {}
+
+Encoder::Encoder() : data(0), size(0), position(0), grow(true) {}
+
+namespace {
+template <typename T> size_t encode(char* data, T i);
+template <> size_t encode<uint8_t>(char* data, uint8_t i)
+{
+ *data = i;
+ return 1;
+}
+template <> size_t encode<uint16_t>(char* data, uint16_t i)
+{
+ uint16_t b = i;
+ size_t position(0);
+ data[position++] = (uint8_t) (0xFF & (b >> 8));
+ data[position++] = (uint8_t) (0xFF & b);
+ return position;
+}
+template <> size_t encode<uint32_t>(char* data, uint32_t i)
+{
+ uint32_t b = i;
+ size_t position(0);
+ data[position++] = (uint8_t) (0xFF & (b >> 24));
+ data[position++] = (uint8_t) (0xFF & (b >> 16));
+ data[position++] = (uint8_t) (0xFF & (b >> 8));
+ data[position++] = (uint8_t) (0xFF & b);
+ return position;
+}
+template <> size_t encode<uint64_t>(char* data, uint64_t i)
+{
+ uint32_t hi = i >> 32;
+ uint32_t lo = i;
+ size_t r(0);
+ r += encode(data, hi);
+ r += encode(data + r, lo);
+ return r;
+}
+template<typename T> struct Backfill
+{
+ T size;
+ T count;
+ char* location;
+};
+
+template<typename T> void end(T count, void* token, char* current)
+{
+ Backfill<T> b;
+ b.location = (char*) token;
+ b.size = (T) (current - b.location) - sizeof(b.size);
+ b.count = count;
+ b.location += encode<T>(b.location, b.size);
+ encode<T>(b.location, b.count);
+}
+}
+char* Encoder::skip(size_t n)
+{
+ char* current = data + position;
+ check(n);
+ position += n;
+ return current;
+}
+
+void Encoder::write(bool b)
+{
+ check(sizeof(b));
+ position += encode<uint8_t>(data+position, b ? 1u : 0u);
+}
+void Encoder::write(uint8_t i)
+{
+ check(sizeof(i));
+ position += encode<uint8_t>(data+position, i);
+}
+void Encoder::write(uint16_t i)
+{
+ check(sizeof(i));
+ position += encode<uint16_t>(data+position, i);
+}
+void Encoder::write(uint32_t i)
+{
+ check(sizeof(i));
+ position += encode<uint32_t>(data+position, i);
+}
+void Encoder::write(uint64_t i)
+{
+ check(sizeof(i));
+ position += encode<uint64_t>(data+position, i);
+}
+void Encoder::write(int8_t i)
+{
+ check(sizeof(i));
+ position += encode(data+position, (uint8_t) i);
+}
+void Encoder::write(int16_t i)
+{
+ check(sizeof(i));
+ position += encode(data+position, (uint16_t) i);
+}
+void Encoder::write(int32_t i)
+{
+ check(sizeof(i));
+ position += encode(data+position, (uint32_t) i);
+}
+void Encoder::write(int64_t i)
+{
+ check(sizeof(i));
+ position += encode(data+position, (uint64_t) i);
+}
+void Encoder::write(float f)
+{
+ check(sizeof(f));
+ union {
+ uint32_t i;
+ float f;
+ } val;
+
+ val.f = f;
+ write(val.i);
+}
+void Encoder::write(double d)
+{
+ check(sizeof(d));
+ union {
+ uint64_t i;
+ double d;
+ } val;
+
+ val.d = d;
+ write(val.i);
+}
+void Encoder::write(const qpid::types::Uuid& uuid)
+{
+ writeBytes((const char*) uuid.data(), uuid.size());
+}
+
+void Encoder::writeBytes(const char* bytes, size_t count)
+{
+ check(count);
+ ::memcpy(data + position, bytes, count);
+ position += count;
+}
+
+void Encoder::writeCode(uint8_t code)
+{
+ write(code);
+}
+
+void Encoder::writeNull(const Descriptor* d)
+{
+ if (d) writeDescriptor(*d);
+ writeCode(typecodes::NULL_VALUE);
+}
+void Encoder::writeBoolean(bool b, const Descriptor* d)
+{
+ if (d) writeDescriptor(*d);
+ writeCode(b ? typecodes::BOOLEAN_TRUE : typecodes::BOOLEAN_FALSE);
+}
+void Encoder::writeUByte(uint8_t i, const Descriptor* d)
+{
+ write(i, typecodes::UBYTE, d);
+}
+
+void Encoder::writeUShort(uint16_t i, const Descriptor* d)
+{
+ write(i, typecodes::USHORT, d);
+}
+
+void Encoder::writeUInt(uint32_t i, const Descriptor* d)
+{
+ if (i == 0) {
+ if (d) writeDescriptor(*d);
+ writeCode(typecodes::UINT_ZERO);
+ } else {
+ if (i < 256) {
+ write((uint8_t) i, typecodes::UINT_SMALL, d);
+ } else {
+ write(i, typecodes::UINT, d);
+ }
+ }
+}
+
+void Encoder::writeULong(uint64_t i, const Descriptor* d)
+{
+ if (i == 0) {
+ if (d) writeDescriptor(*d);
+ writeCode(typecodes::ULONG_ZERO);
+ } else {
+ if (i < 256) {
+ write((uint8_t) i, typecodes::ULONG_SMALL, d);
+ } else {
+ write(i, typecodes::ULONG, d);
+ }
+ }
+}
+
+void Encoder::writeByte(int8_t i, const Descriptor* d)
+{
+ write((uint8_t) i, typecodes::LONG, d);
+}
+
+void Encoder::writeShort(int16_t i, const Descriptor* d)
+{
+ write((uint16_t) i, typecodes::SHORT, d);
+}
+
+void Encoder::writeInt(int32_t i, const Descriptor* d)
+{
+ write((uint32_t) i, typecodes::INT, d);
+}
+
+void Encoder::writeLong(int64_t i, const Descriptor* d)
+{
+ write((uint64_t) i, typecodes::LONG, d);
+}
+
+void Encoder::writeTimestamp(int64_t t, const Descriptor* d)
+{
+ write((uint64_t) t, typecodes::TIMESTAMP, d);
+}
+
+
+void Encoder::writeFloat(float f, const Descriptor* d)
+{
+ write(f, typecodes::FLOAT, d);
+}
+
+void Encoder::writeDouble(double f, const Descriptor* d)
+{
+ write(f, typecodes::DOUBLE, d);
+}
+
+void Encoder::writeUuid(const qpid::types::Uuid& uuid, const Descriptor* d)
+{
+ write(uuid, typecodes::UUID, d);
+}
+
+void Encoder::write(const CharSequence& v, std::pair<uint8_t, uint8_t> codes, const Descriptor* d)
+{
+ if (d) writeDescriptor(*d);
+ if (v.size < 256) {
+ writeCode(codes.first);
+ write((uint8_t) v.size);
+ } else {
+ writeCode(codes.second);
+ write((uint32_t) v.size);
+ }
+ writeBytes(v.data, v.size);
+}
+
+void Encoder::write(const std::string& v, std::pair<uint8_t, uint8_t> codes, const Descriptor* d)
+{
+ if (d) writeDescriptor(*d);
+ if (v.size() < 256) {
+ writeCode(codes.first);
+ write((uint8_t) v.size());
+ } else {
+ writeCode(codes.second);
+ write((uint32_t) v.size());
+ }
+ writeBytes(v.data(), v.size());
+}
+
+void Encoder::writeSymbol(const CharSequence& v, const Descriptor* d)
+{
+ write(v, typecodes::SYMBOL, d);
+}
+
+void Encoder::writeSymbol(const std::string& v, const Descriptor* d)
+{
+ write(v, typecodes::SYMBOL, d);
+}
+
+void Encoder::writeString(const CharSequence& v, const Descriptor* d)
+{
+ write(v, typecodes::STRING, d);
+}
+
+void Encoder::writeString(const std::string& v, const Descriptor* d)
+{
+ write(v, typecodes::STRING, d);
+}
+
+void Encoder::writeBinary(const CharSequence& v, const Descriptor* d)
+{
+ write(v, typecodes::BINARY, d);
+}
+
+void Encoder::writeBinary(const std::string& v, const Descriptor* d)
+{
+ write(v, typecodes::BINARY, d);
+}
+
+void* Encoder::startList8(const Descriptor* d)
+{
+ return start<uint8_t>(typecodes::LIST8, d);
+}
+
+void* Encoder::startList32(const Descriptor* d)
+{
+ return start<uint32_t>(typecodes::LIST32, d);
+}
+
+void Encoder::endList8(uint8_t count, void* token)
+{
+ end<uint8_t>(count, token, data+position);
+}
+
+void Encoder::endList32(uint32_t count, void* token)
+{
+ end<uint32_t>(count, token, data+position);
+}
+
+void* Encoder::startMap8(const Descriptor* d)
+{
+ return start<uint8_t>(typecodes::MAP8, d);
+}
+
+void* Encoder::startMap32(const Descriptor* d)
+{
+ return start<uint32_t>(typecodes::MAP32, d);
+}
+
+void Encoder::endMap8(uint8_t count, void* token)
+{
+ end<uint8_t>(count, token, data+position);
+}
+
+void Encoder::endMap32(uint32_t count, void* token)
+{
+ end<uint32_t>(count, token, data+position);
+}
+
+void* Encoder::startArray8(const Constructor& c, const Descriptor* d)
+{
+ return startArray<uint8_t>(typecodes::ARRAY8, d, c);
+}
+
+void* Encoder::startArray32(const Constructor& c, const Descriptor* d)
+{
+ return startArray<uint8_t>(typecodes::ARRAY32, d, c);
+}
+
+void Encoder::endArray8(size_t count, void* token)
+{
+ end<uint8_t>(count, token, data+position);
+}
+
+void Encoder::endArray32(size_t count, void* token)
+{
+ end<uint32_t>(count, token, data+position);
+}
+
+void Encoder::writeMap(const std::map<std::string, qpid::types::Variant>& value, const Descriptor* d, bool large)
+{
+ void* token = large ? startMap32(d) : startMap8(d);
+ for (qpid::types::Variant::Map::const_iterator i = value.begin(); i != value.end(); ++i) {
+ writeString(i->first);
+ writeValue(i->second);
+ }
+ if (large) endMap32(value.size()*2, token);
+ else endMap8(value.size()*2, token);
+}
+
+void Encoder::writeList(const std::list<qpid::types::Variant>& value, const Descriptor* d, bool large)
+{
+ void* token = large ? startList32(d) : startList8(d);
+ for (qpid::types::Variant::List::const_iterator i = value.begin(); i != value.end(); ++i) {
+ writeValue(*i);
+ }
+ if (large) endList32(value.size(), token);
+ else endList8(value.size(), token);
+}
+
+void Encoder::writeValue(const qpid::types::Variant& value, const Descriptor* d)
+{
+ if (d) {
+ writeDescriptor(*d); // Write this descriptor before any in the value.
+ d = 0;
+ }
+ // Write any descriptors attached to the value.
+ const Variant::List& descriptors = value.getDescriptors();
+ for (Variant::List::const_iterator i = descriptors.begin(); i != descriptors.end(); ++i) {
+ if (i->getType() == types::VAR_STRING)
+ writeDescriptor(Descriptor(CharSequence::create(i->asString())));
+ else
+ writeDescriptor(Descriptor(i->asUint64()));
+ }
+ switch (value.getType()) {
+ case qpid::types::VAR_VOID:
+ writeNull(d);
+ break;
+ case qpid::types::VAR_BOOL:
+ writeBoolean(value.asBool(), d);
+ break;
+ case qpid::types::VAR_UINT8:
+ writeUByte(value.asUint8(), d);
+ break;
+ case qpid::types::VAR_UINT16:
+ writeUShort(value.asUint16(), d);
+ break;
+ case qpid::types::VAR_UINT32:
+ writeUInt(value.asUint32(), d);
+ break;
+ case qpid::types::VAR_UINT64:
+ writeULong(value.asUint64(), d);
+ break;
+ case qpid::types::VAR_INT8:
+ writeByte(value.asInt8(), d);
+ break;
+ case qpid::types::VAR_INT16:
+ writeShort(value.asInt16(), d);
+ break;
+ case qpid::types::VAR_INT32:
+ writeInt(value.asInt32(), d);
+ break;
+ case qpid::types::VAR_INT64:
+ writeLong(value.asInt64(), d);
+ break;
+ case qpid::types::VAR_FLOAT:
+ writeFloat(value.asFloat(), d);
+ break;
+ case qpid::types::VAR_DOUBLE:
+ writeDouble(value.asDouble(), d);
+ break;
+ case qpid::types::VAR_STRING:
+ if (value.getEncoding() == UTF8) {
+ writeString(value.getString(), d);
+ } else if (value.getEncoding() == ASCII) {
+ writeSymbol(value.getString(), d);
+ } else {
+ writeBinary(value.getString(), d);
+ }
+ break;
+ case qpid::types::VAR_MAP:
+ writeMap(value.asMap(), d);
+ break;
+ case qpid::types::VAR_LIST:
+ writeList(value.asList(), d);
+ break;
+ case qpid::types::VAR_UUID:
+ writeUuid(value.asUuid(), d);
+ break;
+ }
+
+}
+
+void Encoder::writeDescriptor(const Descriptor& d)
+{
+ writeCode(typecodes::DESCRIPTOR);
+ switch (d.type) {
+ case Descriptor::NUMERIC:
+ writeULong(d.value.code, 0);
+ break;
+ case Descriptor::SYMBOLIC:
+ writeSymbol(d.value.symbol, 0);
+ break;
+ }
+}
+
+void Encoder::check(size_t s)
+{
+ if (position + s > size) {
+ if (grow) {
+ buffer.resize(buffer.size() + s);
+ data = const_cast<char*>(buffer.data());
+ size = buffer.size();
+ }
+ else {
+ QPID_LOG(notice, "Buffer overflow for write of size " << s
+ << " to buffer of size " << size << " at position " << position);
+ assert(false);
+ throw Overflow();
+ }
+ }
+}
+
+size_t Encoder::getPosition() { return position; }
+size_t Encoder::getSize() const { return size; }
+char* Encoder::getData() { return data + position; }
+std::string Encoder::getBuffer() { return buffer; }
+void Encoder::resetPosition(size_t p) { assert(p <= size); position = p; }
+
+}} // namespace qpid::amqp
diff --git a/qpid/cpp/src/qpid/amqp/Encoder.h b/qpid/cpp/src/qpid/amqp/Encoder.h
new file mode 100644
index 0000000000..8729f29b94
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/Encoder.h
@@ -0,0 +1,181 @@
+#ifndef QPID_AMQP_ENCODER_H
+#define QPID_AMQP_ENCODER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/amqp/Constructor.h"
+#include "qpid/Exception.h"
+#include <list>
+#include <map>
+#include <stddef.h>
+#include <string>
+
+namespace qpid {
+namespace types {
+class Uuid;
+class Variant;
+}
+namespace amqp {
+struct CharSequence;
+struct Descriptor;
+
+/**
+ * Class to help create AMQP encoded data.
+ */
+class Encoder
+{
+ public:
+ struct Overflow : public Exception { Overflow(); };
+
+ /** Create an encoder that writes into the buffer at data up to size bytes.
+ * Write operations throw Overflow if encoding exceeds size bytes.
+ */
+ QPID_COMMON_EXTERN Encoder(char* data, size_t size);
+
+ /** Create an encoder that manages its own buffer. Buffer grows to accomodate
+ * all encoded data. Call getBuffer() to get the buffer.
+ */
+ QPID_COMMON_EXTERN Encoder();
+
+ void writeCode(uint8_t);
+
+ void write(bool);
+ void write(uint8_t);
+ void write(uint16_t);
+ void write(uint32_t);
+ void write(uint64_t);
+ void write(int8_t);
+ void write(int16_t);
+ void write(int32_t);
+ void write(int64_t);
+ void write(float);
+ void write(double);
+ void write(const qpid::types::Uuid&);
+
+ void writeNull(const Descriptor* d=0);
+ void writeBoolean(bool, const Descriptor* d=0);
+ void writeUByte(uint8_t, const Descriptor* d=0);
+ void writeUShort(uint16_t, const Descriptor* d=0);
+ void writeUInt(uint32_t, const Descriptor* d=0);
+ void writeULong(uint64_t, const Descriptor* d=0);
+ void writeByte(int8_t, const Descriptor* d=0);
+ void writeShort(int16_t, const Descriptor* d=0);
+ void writeInt(int32_t, const Descriptor* d=0);
+ void writeLong(int64_t, const Descriptor* d=0);
+ void writeFloat(float, const Descriptor* d=0);
+ void writeDouble(double, const Descriptor* d=0);
+ void writeUuid(const qpid::types::Uuid&, const Descriptor* d=0);
+ void writeTimestamp(int64_t, const Descriptor* d=0);
+
+ void writeSymbol(const CharSequence&, const Descriptor* d=0);
+ void writeSymbol(const std::string&, const Descriptor* d=0);
+ void writeString(const CharSequence&, const Descriptor* d=0);
+ void writeString(const std::string&, const Descriptor* d=0);
+ void writeBinary(const CharSequence&, const Descriptor* d=0);
+ QPID_COMMON_EXTERN void writeBinary(const std::string&, const Descriptor* d=0);
+
+ void* startList8(const Descriptor* d=0);
+ void* startList32(const Descriptor* d=0);
+ void endList8(uint8_t count, void*);
+ void endList32(uint32_t count, void*);
+
+ void* startMap8(const Descriptor* d=0);
+ void* startMap32(const Descriptor* d=0);
+ void endMap8(uint8_t count, void*);
+ void endMap32(uint32_t count, void*);
+
+ void* startArray8(const Constructor&, const Descriptor* d=0);
+ void* startArray32(const Constructor&, const Descriptor* d=0);
+ void endArray8(size_t count, void*);
+ void endArray32(size_t count, void*);
+
+ QPID_COMMON_EXTERN void writeValue(const qpid::types::Variant&, const Descriptor* d=0);
+ QPID_COMMON_EXTERN void writeMap(const std::map<std::string, qpid::types::Variant>& value, const Descriptor* d=0, bool large=true);
+ QPID_COMMON_EXTERN void writeList(const std::list<qpid::types::Variant>& value, const Descriptor* d=0, bool large=true);
+
+ void writeDescriptor(const Descriptor&);
+ QPID_COMMON_EXTERN size_t getPosition();
+ void resetPosition(size_t p);
+ char* skip(size_t);
+ void writeBytes(const char* bytes, size_t count);
+ virtual ~Encoder() {}
+
+ /** Return the total size of the buffer. */
+ size_t getSize() const;
+
+ /** Return the growable buffer. */
+ std::string getBuffer();
+
+ /** Return the unused portion of the buffer. */
+ char* getData();
+
+ private:
+ char* data;
+ size_t size;
+ size_t position;
+ bool grow;
+ std::string buffer;
+
+ void write(const CharSequence& v, std::pair<uint8_t, uint8_t> codes, const Descriptor* d);
+ void write(const std::string& v, std::pair<uint8_t, uint8_t> codes, const Descriptor* d);
+ void check(size_t);
+
+ template<typename T> void write(T value, uint8_t code, const Descriptor* d)
+ {
+ if (d) writeDescriptor(*d);
+ writeCode(code);
+ write(value);
+ }
+
+ template<typename T> void write(T value, std::pair<uint8_t, uint8_t> codes, const Descriptor* d)
+ {
+ if (value < 256) {
+ write((uint8_t) value, codes.first, d);
+ } else {
+ write(value, codes.second, d);
+ }
+ }
+
+ template<typename T> void* start(uint8_t code, const Descriptor* d)
+ {
+ if (d) writeDescriptor(*d);
+ writeCode(code);
+ //skip size and count, will backfill on end
+ return skip(sizeof(T)/*size*/ + sizeof(T)/*count*/);
+ }
+
+ template<typename T> void* startArray(uint8_t code, const Descriptor* d, const Constructor& c)
+ {
+ void* token = start<T>(code, d);
+ if (c.isDescribed) {
+ writeDescriptor(c.descriptor);
+ }
+ check(1);
+ writeCode(c.code);
+ return token;
+ }
+
+};
+
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_ENCODER_H*/
diff --git a/qpid/cpp/src/qpid/amqp/ListBuilder.cpp b/qpid/cpp/src/qpid/amqp/ListBuilder.cpp
new file mode 100644
index 0000000000..f2ca8e8805
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/ListBuilder.cpp
@@ -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.
+ *
+ */
+#include "ListBuilder.h"
+
+namespace qpid {
+namespace amqp {
+
+ListBuilder::ListBuilder() : DataBuilder(qpid::types::Variant::List()) {}
+
+qpid::types::Variant::List& ListBuilder::getList()
+{
+ return getValue().asList();
+}
+
+}} // namespace qpid::amqp
diff --git a/qpid/cpp/src/qpid/amqp/ListBuilder.h b/qpid/cpp/src/qpid/amqp/ListBuilder.h
new file mode 100644
index 0000000000..825f384f56
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/ListBuilder.h
@@ -0,0 +1,41 @@
+#ifndef QPID_AMQP_LISTBUILDER_H
+#define QPID_AMQP_LISTBUILDER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "DataBuilder.h"
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+namespace amqp {
+
+/**
+ * Utility to build a Variant::List from a data stream
+ */
+class ListBuilder : public DataBuilder
+{
+ public:
+ QPID_COMMON_EXTERN ListBuilder();
+ QPID_COMMON_EXTERN qpid::types::Variant::List& getList();
+};
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_LISTBUILDER_H*/
diff --git a/qpid/cpp/src/qpid/amqp/ListReader.h b/qpid/cpp/src/qpid/amqp/ListReader.h
new file mode 100644
index 0000000000..fafe2a1f9c
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/ListReader.h
@@ -0,0 +1,103 @@
+#ifndef QPID_AMQP_LISTREADER_H
+#define QPID_AMQP_LISTREADER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "Reader.h"
+
+namespace qpid {
+namespace amqp {
+
+/**
+ * Utility to assist in reading AMQP encoded lists
+ */
+class ListReader : public Reader
+{
+ public:
+ ListReader() : index(0), level(0) {}
+ virtual ~ListReader() {}
+ virtual void onNull(const Descriptor* descriptor) { getReader().onNull(descriptor); }
+ virtual void onBoolean(bool v, const Descriptor* descriptor) { getReader().onBoolean(v, descriptor); }
+ virtual void onUByte(uint8_t v, const Descriptor* descriptor) { getReader().onUByte(v, descriptor); }
+ virtual void onUShort(uint16_t v, const Descriptor* descriptor) { getReader().onUShort(v, descriptor); }
+ virtual void onUInt(uint32_t v, const Descriptor* descriptor) { getReader().onUInt(v, descriptor); }
+ virtual void onULong(uint64_t v, const Descriptor* descriptor) { getReader().onULong(v, descriptor); }
+ virtual void onByte(int8_t v, const Descriptor* descriptor) { getReader().onByte(v, descriptor); }
+ virtual void onShort(int16_t v, const Descriptor* descriptor) { getReader().onShort(v, descriptor); }
+ virtual void onInt(int32_t v, const Descriptor* descriptor) { getReader().onInt(v, descriptor); }
+ virtual void onLong(int64_t v, const Descriptor* descriptor) { getReader().onLong(v, descriptor); }
+ virtual void onFloat(float v, const Descriptor* descriptor) { getReader().onFloat(v, descriptor); }
+ virtual void onDouble(double v, const Descriptor* descriptor) { getReader().onDouble(v, descriptor); }
+ virtual void onUuid(const CharSequence& v, const Descriptor* descriptor) { getReader().onUuid(v, descriptor); }
+ virtual void onTimestamp(int64_t v, const Descriptor* descriptor) { getReader().onTimestamp(v, descriptor); }
+
+ virtual void onBinary(const CharSequence& v, const Descriptor* descriptor) { getReader().onBinary(v, descriptor); }
+ virtual void onString(const CharSequence& v, const Descriptor* descriptor) { getReader().onString(v, descriptor); }
+ virtual void onSymbol(const CharSequence& v, const Descriptor* descriptor) { getReader().onSymbol(v, descriptor); }
+
+ virtual bool onStartList(uint32_t count, const CharSequence& elements, const CharSequence& all, const Descriptor* descriptor)
+ {
+ ++level;
+ getReader().onStartList(count, elements, all, descriptor);
+ return false;
+ }
+ virtual void onEndList(uint32_t count, const Descriptor* descriptor)
+ {
+ --level;
+ getReader().onEndList(count, descriptor);
+ }
+ virtual bool onStartMap(uint32_t count, const CharSequence& elements, const CharSequence& all, const Descriptor* descriptor)
+ {
+ ++level;
+ getReader().onStartMap(count, elements, all, descriptor);
+ return false;
+ }
+ virtual void onEndMap(uint32_t count, const Descriptor* descriptor)
+ {
+ --level;
+ getReader().onEndList(count, descriptor);
+ }
+ virtual bool onStartArray(uint32_t count, const CharSequence& v, const Constructor& c, const Descriptor* descriptor)
+ {
+ ++level;
+ getReader().onStartArray(count, v, c, descriptor);
+ return false;
+ }
+ virtual void onEndArray(uint32_t count, const Descriptor* descriptor)
+ {
+ --level;
+ getReader().onEndList(count, descriptor);
+ }
+ private:
+ size_t index;
+ size_t level;
+ Reader& getReader()
+ {
+ Reader& r = getReader(index);
+ if (level == 0) ++index;
+ return r;
+ }
+ protected:
+ virtual Reader& getReader(size_t i) = 0;
+};
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_LISTREADER_H*/
diff --git a/qpid/cpp/src/qpid/amqp/LoggingReader.h b/qpid/cpp/src/qpid/amqp/LoggingReader.h
new file mode 100644
index 0000000000..ed5cab1cbd
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/LoggingReader.h
@@ -0,0 +1,64 @@
+#ifndef QPID_AMQP_LOGGINGREADER_H
+#define QPID_AMQP_LOGGINGREADER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "Reader.h"
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace amqp {
+
+class LoggingReader : public Reader
+{
+ public:
+ virtual ~LoggingReader() {}
+ virtual void onNull(const Descriptor*) { if (!ignoreNull()) QPID_LOG(warning, prefix() << "null" << suffix()); }
+ virtual void onBoolean(bool, const Descriptor*) { QPID_LOG(warning, prefix() << "boolean" << suffix()); }
+ virtual void onUByte(uint8_t, const Descriptor*) { QPID_LOG(warning, prefix() << "ubyte" << suffix()); }
+ virtual void onUShort(uint16_t, const Descriptor*) { QPID_LOG(warning, prefix() << "ushort" << suffix()); }
+ virtual void onUInt(uint32_t, const Descriptor*) { QPID_LOG(warning, prefix() << "uint" << suffix()); }
+ virtual void onULong(uint64_t, const Descriptor*) { QPID_LOG(warning, prefix() << "ulong" << suffix()); }
+ virtual void onByte(int8_t, const Descriptor*) { QPID_LOG(warning, prefix() << "byte" << suffix()); }
+ virtual void onShort(int16_t, const Descriptor*) { QPID_LOG(warning, prefix() << "short" << suffix()); }
+ virtual void onInt(int32_t, const Descriptor*) { QPID_LOG(warning, prefix() << "int" << suffix()); }
+ virtual void onLong(int64_t, const Descriptor*) { QPID_LOG(warning, prefix() << "long" << suffix()); }
+ virtual void onFloat(float, const Descriptor*) { QPID_LOG(warning, prefix() << "float" << suffix()); }
+ virtual void onDouble(double, const Descriptor*) { QPID_LOG(warning, prefix() << "double" << suffix()); }
+ virtual void onUuid(const CharSequence&, const Descriptor*) { QPID_LOG(warning, prefix() << "uuid" << suffix()); }
+ virtual void onTimestamp(int64_t, const Descriptor*) { QPID_LOG(warning, prefix() << "timestamp" << suffix()); }
+
+ virtual void onBinary(const CharSequence&, const Descriptor*) { QPID_LOG(warning, prefix() << "binary" << suffix()); }
+ virtual void onString(const CharSequence&, const Descriptor*) { QPID_LOG(warning, prefix() << "string" << suffix()); }
+ virtual void onSymbol(const CharSequence&, const Descriptor*) { QPID_LOG(warning, prefix() << "symbol" << suffix()); }
+
+ virtual bool onStartList(uint32_t, const CharSequence&, const Descriptor*) { QPID_LOG(warning, prefix() << "list" << suffix()); return recursive; }
+ virtual bool onStartMap(uint32_t, const CharSequence&, const Descriptor*) { QPID_LOG(warning, prefix() << "map" << suffix()); return recursive; }
+ virtual bool onStartArray(uint32_t, const CharSequence&, const Constructor&, const Descriptor*) { QPID_LOG(warning, prefix() << "array" << suffix()); return recursive; }
+ protected:
+ virtual bool recursive() { return true; }
+ virtual bool ignoreNull() { return true; }
+ virtual std::string prefix() = 0;
+ virtual std::string suffix() = 0;
+};
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_LOGGINGREADER_H*/
diff --git a/qpid/cpp/src/qpid/amqp/MapBuilder.cpp b/qpid/cpp/src/qpid/amqp/MapBuilder.cpp
new file mode 100644
index 0000000000..ce8eea038e
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/MapBuilder.cpp
@@ -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.
+ *
+ */
+#include "MapBuilder.h"
+
+namespace qpid {
+namespace amqp {
+MapBuilder::MapBuilder() : DataBuilder(qpid::types::Variant::Map()) {}
+qpid::types::Variant::Map MapBuilder::getMap()
+{
+ return getValue().asMap();
+}
+}} // namespace qpid::amqp
diff --git a/qpid/cpp/src/qpid/amqp/MapBuilder.h b/qpid/cpp/src/qpid/amqp/MapBuilder.h
new file mode 100644
index 0000000000..fd94ae04af
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/MapBuilder.h
@@ -0,0 +1,41 @@
+#ifndef QPID_AMQP_MAPBUILDER_H
+#define QPID_AMQP_MAPBUILDER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "DataBuilder.h"
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+namespace amqp {
+
+/**
+ * Utility to build a Variant::Map from a data stream
+ */
+class MapBuilder : public DataBuilder
+{
+ public:
+ QPID_COMMON_EXTERN MapBuilder();
+ QPID_COMMON_EXTERN qpid::types::Variant::Map getMap();
+};
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_MAPBUILDER_H*/
diff --git a/qpid/cpp/src/qpid/amqp/MapEncoder.cpp b/qpid/cpp/src/qpid/amqp/MapEncoder.cpp
new file mode 100644
index 0000000000..cf8ef4ecb5
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/MapEncoder.cpp
@@ -0,0 +1,142 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "MapEncoder.h"
+#include "CharSequence.h"
+#include "qpid/amqp/typecodes.h"
+#include <string.h>
+
+namespace qpid {
+namespace amqp {
+
+MapEncoder::MapEncoder(char* data, size_t size) : Encoder(data, size) {}
+
+void MapEncoder::handleVoid(const CharSequence& key)
+{
+ writeString(key);
+ writeNull();
+}
+
+void MapEncoder::handleBool(const CharSequence& key, bool value)
+{
+ writeString(key);
+ writeBoolean(value);
+}
+
+void MapEncoder::handleUint8(const CharSequence& key, uint8_t value)
+{
+ writeString(key);
+ writeUByte(value);
+}
+
+void MapEncoder::handleUint16(const CharSequence& key, uint16_t value)
+{
+ writeString(key);
+ writeUShort(value);
+}
+
+void MapEncoder::handleUint32(const CharSequence& key, uint32_t value)
+{
+ writeString(key);
+ writeUInt(value);
+}
+
+void MapEncoder::handleUint64(const CharSequence& key, uint64_t value)
+{
+ writeString(key);
+ writeULong(value);
+}
+
+void MapEncoder::handleInt8(const CharSequence& key, int8_t value)
+{
+ writeString(key);
+ writeByte(value);
+}
+
+void MapEncoder::handleInt16(const CharSequence& key, int16_t value)
+{
+ writeString(key);
+ writeShort(value);
+}
+
+void MapEncoder::handleInt32(const CharSequence& key, int32_t value)
+{
+ writeString(key);
+ writeInt(value);
+}
+
+void MapEncoder::handleInt64(const CharSequence& key, int64_t value)
+{
+ writeString(key);
+ writeLong(value);
+}
+
+void MapEncoder::handleFloat(const CharSequence& key, float value)
+{
+ writeString(key);
+ writeFloat(value);
+}
+
+void MapEncoder::handleDouble(const CharSequence& key, double value)
+{
+ writeString(key);
+ writeDouble(value);
+}
+
+namespace {
+const std::string BINARY("binary");
+}
+
+void MapEncoder::handleString(const CharSequence& key, const CharSequence& value, const CharSequence& encoding)
+{
+ writeString(key);
+ if (encoding.size == BINARY.size() && ::strncmp(encoding.data, BINARY.data(), encoding.size)) {
+ writeBinary(value);
+ } else {
+ writeString(value);
+ }
+}
+
+void MapEncoder::writeMetaData(size_t size, size_t count, const Descriptor* d)
+{
+ if (count > 255 || size > 255) {
+ writeMap32MetaData((uint32_t) size, (uint32_t) count, d);
+ } else {
+ writeMap8MetaData((uint8_t) size, (uint8_t) count, d);
+ }
+}
+
+void MapEncoder::writeMap8MetaData(uint8_t size, uint8_t count, const Descriptor* d)
+{
+ if (d) writeDescriptor(*d);
+ writeCode(typecodes::MAP8);
+ write((uint8_t) (size+1)/*size includes count field*/);
+ write(count);
+}
+
+void MapEncoder::writeMap32MetaData(uint32_t size, uint32_t count, const Descriptor* d)
+{
+ if (d) writeDescriptor(*d);
+ writeCode(typecodes::MAP32);
+ write((uint32_t) (size+4)/*size includes count field*/);
+ write(count);
+}
+
+}} // namespace qpid::amqp
diff --git a/qpid/cpp/src/qpid/amqp/MapEncoder.h b/qpid/cpp/src/qpid/amqp/MapEncoder.h
new file mode 100644
index 0000000000..1481f9125a
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/MapEncoder.h
@@ -0,0 +1,58 @@
+#ifndef QPID_AMQP_MAPENCODER_H
+#define QPID_AMQP_MAPENCODER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "MapHandler.h"
+#include "Encoder.h"
+
+namespace qpid {
+namespace amqp {
+struct Descriptor;
+
+/**
+ * Encode map like data
+ */
+class MapEncoder : public MapHandler, private Encoder
+{
+ public:
+ MapEncoder(char* data, size_t size);
+ void handleVoid(const CharSequence& key);
+ void handleBool(const CharSequence& key, bool value);
+ void handleUint8(const CharSequence& key, uint8_t value);
+ void handleUint16(const CharSequence& key, uint16_t value);
+ void handleUint32(const CharSequence& key, uint32_t value);
+ void handleUint64(const CharSequence& key, uint64_t value);
+ void handleInt8(const CharSequence& key, int8_t value);
+ void handleInt16(const CharSequence& key, int16_t value);
+ void handleInt32(const CharSequence& key, int32_t value);
+ void handleInt64(const CharSequence& key, int64_t value);
+ void handleFloat(const CharSequence& key, float value);
+ void handleDouble(const CharSequence& key, double value);
+ void handleString(const CharSequence& key, const CharSequence& value, const CharSequence& encoding);
+ void writeMetaData(size_t size, size_t count, const Descriptor* descriptor=0);
+ void writeMap8MetaData(uint8_t size, uint8_t count, const Descriptor* d=0);
+ void writeMap32MetaData(uint32_t size, uint32_t count, const Descriptor* d=0);
+ private:
+};
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_MAPENCODER_H*/
diff --git a/qpid/cpp/src/qpid/amqp/MapHandler.h b/qpid/cpp/src/qpid/amqp/MapHandler.h
new file mode 100644
index 0000000000..14994ccac7
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/MapHandler.h
@@ -0,0 +1,53 @@
+#ifndef QPID_AMQP_MAPHANDLER_H
+#define QPID_AMQP_MAPHANDLER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/sys/IntegerTypes.h"
+
+namespace qpid {
+namespace amqp {
+struct CharSequence;
+/**
+ * Interface for processing entries in some map-like object
+ */
+class MapHandler
+{
+ public:
+ virtual ~MapHandler() {}
+ virtual void handleVoid(const CharSequence& key) = 0;
+ virtual void handleBool(const CharSequence& key, bool value) = 0;
+ virtual void handleUint8(const CharSequence& key, uint8_t value) = 0;
+ virtual void handleUint16(const CharSequence& key, uint16_t value) = 0;
+ virtual void handleUint32(const CharSequence& key, uint32_t value) = 0;
+ virtual void handleUint64(const CharSequence& key, uint64_t value) = 0;
+ virtual void handleInt8(const CharSequence& key, int8_t value) = 0;
+ virtual void handleInt16(const CharSequence& key, int16_t value) = 0;
+ virtual void handleInt32(const CharSequence& key, int32_t value) = 0;
+ virtual void handleInt64(const CharSequence& key, int64_t value) = 0;
+ virtual void handleFloat(const CharSequence& key, float value) = 0;
+ virtual void handleDouble(const CharSequence& key, double value) = 0;
+ virtual void handleString(const CharSequence& key, const CharSequence& value, const CharSequence& encoding) = 0;
+ private:
+};
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_MAPHANDLER_H*/
diff --git a/qpid/cpp/src/qpid/amqp/MapReader.cpp b/qpid/cpp/src/qpid/amqp/MapReader.cpp
new file mode 100644
index 0000000000..b6c31849f0
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/MapReader.cpp
@@ -0,0 +1,309 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/amqp/MapReader.h"
+#include "qpid/Exception.h"
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace amqp {
+
+void MapReader::onNull(const Descriptor* d)
+{
+ if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum"));
+ if (key) {
+ onNullValue(key, d);
+ clearKey();
+ } else {
+ throw qpid::Exception(QPID_MSG("Expecting symbol as key"));
+ }
+}
+void MapReader::onBoolean(bool v, const Descriptor* d)
+{
+ if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum"));
+ if (key) {
+ onBooleanValue(key, v, d);
+ clearKey();
+ } else {
+ throw qpid::Exception(QPID_MSG("Expecting symbol as key"));
+ }
+}
+
+void MapReader::onUByte(uint8_t v, const Descriptor* d)
+{
+ if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum"));
+ if (key) {
+ onUByteValue(key, v, d);
+ clearKey();
+ } else {
+ throw qpid::Exception(QPID_MSG("Expecting symbol as key"));
+ }
+}
+
+void MapReader::onUShort(uint16_t v, const Descriptor* d)
+{
+ if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum"));
+ if (key) {
+ onUShortValue(key, v, d);
+ clearKey();
+ } else {
+ throw qpid::Exception(QPID_MSG("Expecting symbol as key"));
+ }
+}
+
+void MapReader::onUInt(uint32_t v, const Descriptor* d)
+{
+ if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum"));
+ if (key) {
+ onUIntValue(key, v, d);
+ clearKey();
+ } else {
+ throw qpid::Exception(QPID_MSG("Expecting symbol as key"));
+ }
+}
+
+void MapReader::onULong(uint64_t v, const Descriptor* d)
+{
+ if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum"));
+ if (key) {
+ onULongValue(key, v, d);
+ clearKey();
+ } else {
+ throw qpid::Exception(QPID_MSG("Expecting symbol as key"));
+ }
+}
+
+void MapReader::onByte(int8_t v, const Descriptor* d)
+{
+ if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum"));
+ if (key) {
+ onByteValue(key, v, d);
+ clearKey();
+ } else {
+ throw qpid::Exception(QPID_MSG("Expecting symbol as key"));
+ }
+}
+
+void MapReader::onShort(int16_t v, const Descriptor* d)
+{
+ if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum"));
+ if (key) {
+ onShortValue(key, v, d);
+ clearKey();
+ } else {
+ throw qpid::Exception(QPID_MSG("Expecting symbol as key"));
+ }
+}
+
+void MapReader::onInt(int32_t v, const Descriptor* d)
+{
+ if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum"));
+ if (key) {
+ onIntValue(key, v, d);
+ clearKey();
+ } else {
+ throw qpid::Exception(QPID_MSG("Expecting symbol as key"));
+ }
+}
+
+void MapReader::onLong(int64_t v, const Descriptor* d)
+{
+ if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum"));
+ if (key) {
+ onLongValue(key, v, d);
+ clearKey();
+ } else {
+ throw qpid::Exception(QPID_MSG("Expecting symbol as key"));
+ }
+}
+
+void MapReader::onFloat(float v, const Descriptor* d)
+{
+ if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum"));
+ if (key) {
+ onFloatValue(key, v, d);
+ clearKey();
+ } else {
+ throw qpid::Exception(QPID_MSG("Expecting symbol as key"));
+ }
+}
+
+void MapReader::onDouble(double v, const Descriptor* d)
+{
+ if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum"));
+ if (key) {
+ onDoubleValue(key, v, d);
+ clearKey();
+ } else {
+ throw qpid::Exception(QPID_MSG("Expecting symbol as key"));
+ }
+}
+
+void MapReader::onUuid(const CharSequence& v, const Descriptor* d)
+{
+ if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum"));
+ if (key) {
+ onUuidValue(key, v, d);
+ clearKey();
+ } else {
+ throw qpid::Exception(QPID_MSG("Expecting symbol as key"));
+ }
+}
+
+void MapReader::onTimestamp(int64_t v, const Descriptor* d)
+{
+ if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum"));
+ if (key) {
+ onTimestampValue(key, v, d);
+ clearKey();
+ } else {
+ throw qpid::Exception(QPID_MSG("Expecting symbol as key"));
+ }
+}
+
+void MapReader::onBinary(const CharSequence& v, const Descriptor* d)
+{
+ if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum"));
+ if (key) {
+ onBinaryValue(key, v, d);
+ clearKey();
+ } else {
+ throw qpid::Exception(QPID_MSG("Expecting symbol as key"));
+ }
+}
+
+void MapReader::onString(const CharSequence& v, const Descriptor* d)
+{
+ if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum"));
+ if (key) {
+ onStringValue(key, v, d);
+ clearKey();
+ } else {
+ if (keyType & STRING_KEY) {
+ key = v;
+ } else {
+ throw qpid::Exception(QPID_MSG("Expecting symbol as key, got string " << v.str()));
+ }
+ }
+}
+
+void MapReader::onSymbol(const CharSequence& v, const Descriptor* d)
+{
+ if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum"));
+ if (key) {
+ onSymbolValue(key, v, d);
+ clearKey();
+ } else {
+ if (keyType & SYMBOL_KEY) {
+ key = v;
+ } else {
+ throw qpid::Exception(QPID_MSG("Expecting string as key, got symbol " << v.str()));
+ }
+ }
+}
+
+bool MapReader::onStartList(uint32_t count, const CharSequence&, const CharSequence&, const Descriptor* d)
+{
+ if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum"));
+ if (key) {
+ bool step = onStartListValue(key, count, d);
+ clearKey();
+ return step;
+ } else {
+ throw qpid::Exception(QPID_MSG("Expecting symbol as key"));
+ }
+ return true;
+}
+
+bool MapReader::onStartMap(uint32_t count, const CharSequence&, const CharSequence&, const Descriptor* d)
+{
+ if (level++) {
+ if (key) {
+ bool step = onStartMapValue(key, count, d);
+ clearKey();
+ return step;
+ } else {
+ throw qpid::Exception(QPID_MSG("Expecting symbol as key"));
+ }
+ }
+ return true;
+}
+
+bool MapReader::onStartArray(uint32_t count, const CharSequence&, const Constructor& c, const Descriptor* d)
+{
+ if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum"));
+ if (key) {
+ bool step = onStartArrayValue(key, count, c, d);
+ clearKey();
+ return step;
+ } else {
+ throw qpid::Exception(QPID_MSG("Expecting symbol as key"));
+ }
+ return true;
+}
+
+void MapReader::onEndList(uint32_t count, const Descriptor* d)
+{
+ if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum"));
+ if (key) {
+ onEndListValue(key, count, d);
+ clearKey();
+ } else {
+ throw qpid::Exception(QPID_MSG("Expecting symbol as key"));
+ }
+}
+
+void MapReader::onEndMap(uint32_t count, const Descriptor* d)
+{
+ if (--level) {
+ onEndMapValue(key, count, d);
+ clearKey();
+ }
+}
+
+void MapReader::onEndArray(uint32_t count, const Descriptor* d)
+{
+ if (!level) throw qpid::Exception(QPID_MSG("Expecting map as top level datum"));
+ if (key) {
+ onEndArrayValue(key, count, d);
+ clearKey();
+ } else {
+ throw qpid::Exception(QPID_MSG("Expecting symbol as key"));
+ }
+}
+
+MapReader::MapReader() : level(0), keyType(SYMBOL_KEY)
+{
+ clearKey();
+}
+
+void MapReader::setAllowedKeyType(int t)
+{
+ keyType = t;
+}
+
+void MapReader::clearKey()
+{
+ key.data = 0; key.size = 0;
+}
+
+const int MapReader::SYMBOL_KEY(1);
+const int MapReader::STRING_KEY(2);
+}} // namespace qpid::amqp
diff --git a/qpid/cpp/src/qpid/amqp/MapReader.h b/qpid/cpp/src/qpid/amqp/MapReader.h
new file mode 100644
index 0000000000..875f919d63
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/MapReader.h
@@ -0,0 +1,110 @@
+#ifndef QPID_AMQP_MAPREADER_H
+#define QPID_AMQP_MAPREADER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "Reader.h"
+#include "CharSequence.h"
+#include <string>
+
+namespace qpid {
+namespace amqp {
+
+/**
+ * Reading AMQP 1.0 encoded data which is constrained to be a symbol
+ * keyeed map. The keys are assumed never to be described, the values
+ * may be.
+ */
+class MapReader : public Reader
+{
+ public:
+ virtual void onNullValue(const CharSequence& /*key*/, const Descriptor*) {}
+ virtual void onBooleanValue(const CharSequence& /*key*/, bool, const Descriptor*) {}
+ virtual void onUByteValue(const CharSequence& /*key*/, uint8_t, const Descriptor*) {}
+ virtual void onUShortValue(const CharSequence& /*key*/, uint16_t, const Descriptor*) {}
+ virtual void onUIntValue(const CharSequence& /*key*/, uint32_t, const Descriptor*) {}
+ virtual void onULongValue(const CharSequence& /*key*/, uint64_t, const Descriptor*) {}
+ virtual void onByteValue(const CharSequence& /*key*/, int8_t, const Descriptor*) {}
+ virtual void onShortValue(const CharSequence& /*key*/, int16_t, const Descriptor*) {}
+ virtual void onIntValue(const CharSequence& /*key*/, int32_t, const Descriptor*) {}
+ virtual void onLongValue(const CharSequence& /*key*/, int64_t, const Descriptor*) {}
+ virtual void onFloatValue(const CharSequence& /*key*/, float, const Descriptor*) {}
+ virtual void onDoubleValue(const CharSequence& /*key*/, double, const Descriptor*) {}
+ virtual void onUuidValue(const CharSequence& /*key*/, const CharSequence&, const Descriptor*) {}
+ virtual void onTimestampValue(const CharSequence& /*key*/, int64_t, const Descriptor*) {}
+
+ virtual void onBinaryValue(const CharSequence& /*key*/, const CharSequence&, const Descriptor*) {}
+ virtual void onStringValue(const CharSequence& /*key*/, const CharSequence&, const Descriptor*) {}
+ virtual void onSymbolValue(const CharSequence& /*key*/, const CharSequence&, const Descriptor*) {}
+
+ /**
+ * @return true to step into elements of the compound value, false
+ * to skip over it
+ */
+ virtual bool onStartListValue(const CharSequence& /*key*/, uint32_t /*count*/, const Descriptor*) { return true; }
+ virtual bool onStartMapValue(const CharSequence& /*key*/, uint32_t /*count*/, const Descriptor*) { return true; }
+ virtual bool onStartArrayValue(const CharSequence& /*key*/, uint32_t /*count*/, const Constructor&, const Descriptor*) { return true; }
+ virtual void onEndListValue(const CharSequence& /*key*/, uint32_t /*count*/, const Descriptor*) {}
+ virtual void onEndMapValue(const CharSequence& /*key*/, uint32_t /*count*/, const Descriptor*) {}
+ virtual void onEndArrayValue(const CharSequence& /*key*/, uint32_t /*count*/, const Descriptor*) {}
+
+
+ //this class implements the Reader interface, thus acting as a transformer into a more map oriented scheme
+ QPID_COMMON_EXTERN void onNull(const Descriptor*);
+ QPID_COMMON_EXTERN void onBoolean(bool, const Descriptor*);
+ QPID_COMMON_EXTERN void onUByte(uint8_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onUShort(uint16_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onUInt(uint32_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onULong(uint64_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onByte(int8_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onShort(int16_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onInt(int32_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onLong(int64_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onFloat(float, const Descriptor*);
+ QPID_COMMON_EXTERN void onDouble(double, const Descriptor*);
+ QPID_COMMON_EXTERN void onUuid(const CharSequence&, const Descriptor*);
+ QPID_COMMON_EXTERN void onTimestamp(int64_t, const Descriptor*);
+
+ QPID_COMMON_EXTERN void onBinary(const CharSequence&, const Descriptor*);
+ QPID_COMMON_EXTERN void onString(const CharSequence&, const Descriptor*);
+ QPID_COMMON_EXTERN void onSymbol(const CharSequence&, const Descriptor*);
+
+ QPID_COMMON_EXTERN bool onStartList(uint32_t /*count*/, const CharSequence&, const CharSequence&, const Descriptor*);
+ QPID_COMMON_EXTERN bool onStartMap(uint32_t /*count*/, const CharSequence&, const CharSequence&, const Descriptor*);
+ QPID_COMMON_EXTERN bool onStartArray(uint32_t /*count*/, const CharSequence&, const Constructor&, const Descriptor*);
+ QPID_COMMON_EXTERN void onEndList(uint32_t /*count*/, const Descriptor*);
+ QPID_COMMON_EXTERN void onEndMap(uint32_t /*count*/, const Descriptor*);
+ QPID_COMMON_EXTERN void onEndArray(uint32_t /*count*/, const Descriptor*);
+
+ QPID_COMMON_EXTERN MapReader();
+ QPID_COMMON_EXTERN static const int SYMBOL_KEY;
+ QPID_COMMON_EXTERN static const int STRING_KEY;
+ QPID_COMMON_EXTERN void setAllowedKeyType(int);
+ private:
+ CharSequence key;
+ size_t level;
+ int keyType;
+
+ void clearKey();
+};
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_MAPREADER_H*/
diff --git a/qpid/cpp/src/qpid/amqp/MapSizeCalculator.cpp b/qpid/cpp/src/qpid/amqp/MapSizeCalculator.cpp
new file mode 100644
index 0000000000..2da152108f
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/MapSizeCalculator.cpp
@@ -0,0 +1,151 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "MapSizeCalculator.h"
+#include "CharSequence.h"
+#include "Descriptor.h"
+
+namespace qpid {
+namespace amqp {
+
+MapSizeCalculator::MapSizeCalculator() : size(0), count(0) {}
+
+void MapSizeCalculator::handleKey(const CharSequence& key)
+{
+ ++count;
+ size += getEncodedSize(key);
+}
+
+size_t MapSizeCalculator::getEncodedSize(const CharSequence& s)
+{
+ return 1/*typecode*/ + (s.size < 256 ? 1 : 4)/*size field*/ + s.size;
+}
+
+void MapSizeCalculator::handleVoid(const CharSequence& key)
+{
+ handleKey(key);
+ size += 1;/*typecode*/;
+}
+
+void MapSizeCalculator::handleBool(const CharSequence& key, bool /*value*/)
+{
+ handleKey(key);
+ size += 1;/*typecode*/;
+}
+
+void MapSizeCalculator::handleUint8(const CharSequence& key, uint8_t /*value*/)
+{
+ handleKey(key);
+ size += 1/*typecode*/ + 1/*value*/;
+}
+
+void MapSizeCalculator::handleUint16(const CharSequence& key, uint16_t /*value*/)
+{
+ handleKey(key);
+ size += 1/*typecode*/ + 2/*value*/;
+}
+
+void MapSizeCalculator::handleUint32(const CharSequence& key, uint32_t value)
+{
+ handleKey(key);
+ size += 1;/*typecode*/;
+ if (value > 0) {
+ if (value < 256) size += 1/*UINT_SMALL*/;
+ else size += 4/*UINT*/;
+ }//else UINT_ZERO
+}
+
+void MapSizeCalculator::handleUint64(const CharSequence& key, uint64_t value)
+{
+ handleKey(key);
+ size += 1;/*typecode*/;
+ if (value > 0) {
+ if (value < 256) size += 1/*ULONG_SMALL*/;
+ else size += 8/*ULONG*/;
+ }//else ULONG_ZERO
+}
+
+void MapSizeCalculator::handleInt8(const CharSequence& key, int8_t /*value*/)
+{
+ handleKey(key);
+ size += 1/*typecode*/ + 1/*value*/;
+}
+
+void MapSizeCalculator::handleInt16(const CharSequence& key, int16_t /*value*/)
+{
+ handleKey(key);
+ size += 1/*typecode*/ + 2/*value*/;
+}
+
+void MapSizeCalculator::handleInt32(const CharSequence& key, int32_t /*value*/)
+{
+ handleKey(key);
+ size += 1/*typecode*/ + 4/*value*/;
+}
+
+void MapSizeCalculator::handleInt64(const CharSequence& key, int64_t /*value*/)
+{
+ handleKey(key);
+ size += 1/*typecode*/ + 8/*value*/;
+}
+
+void MapSizeCalculator::handleFloat(const CharSequence& key, float /*value*/)
+{
+ handleKey(key);
+ size += 1/*typecode*/ + 4/*value*/;
+}
+
+void MapSizeCalculator::handleDouble(const CharSequence& key, double /*value*/)
+{
+ handleKey(key);
+ size += 1/*typecode*/ + 8/*value*/;
+}
+
+void MapSizeCalculator::handleString(const CharSequence& key, const CharSequence& value, const CharSequence& /*encoding*/)
+{
+ handleKey(key);
+ size += getEncodedSize(value);
+}
+
+size_t MapSizeCalculator::getSize() const
+{
+ return size;
+}
+
+size_t MapSizeCalculator::getCount() const
+{
+ return count;
+}
+
+size_t MapSizeCalculator::getTotalSizeRequired(const Descriptor* d) const
+{
+ size_t result(size);
+ if (d) result += d->getSize();
+ result += 1/*typecode*/;
+ if (count * 2 > 255 || size > 255) {
+ result += 4/*size*/ + 4/*count*/;
+ } else {
+ result += 1/*size*/ + 1/*count*/;
+ }
+ return result;
+}
+
+
+}} // namespace qpid::amqp
diff --git a/qpid/cpp/src/qpid/amqp/MapSizeCalculator.h b/qpid/cpp/src/qpid/amqp/MapSizeCalculator.h
new file mode 100644
index 0000000000..35c9ad732d
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/MapSizeCalculator.h
@@ -0,0 +1,73 @@
+#ifndef QPID_AMQP_MAPSIZECALCULATOR_H
+#define QPID_AMQP_MAPSIZECALCULATOR_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "MapHandler.h"
+#include <cstring>
+
+namespace qpid {
+namespace amqp {
+struct Descriptor;
+
+/**
+ * Utility to calculate the encoded size for map data
+ */
+class MapSizeCalculator : public MapHandler
+{
+ public:
+ MapSizeCalculator();
+ void handleVoid(const CharSequence& key);
+ void handleBool(const CharSequence& key, bool value);
+ void handleUint8(const CharSequence& key, uint8_t value);
+ void handleUint16(const CharSequence& key, uint16_t value);
+ void handleUint32(const CharSequence& key, uint32_t value);
+ void handleUint64(const CharSequence& key, uint64_t value);
+ void handleInt8(const CharSequence& key, int8_t value);
+ void handleInt16(const CharSequence& key, int16_t value);
+ void handleInt32(const CharSequence& key, int32_t value);
+ void handleInt64(const CharSequence& key, int64_t value);
+ void handleFloat(const CharSequence& key, float value);
+ void handleDouble(const CharSequence& key, double value);
+ void handleString(const CharSequence& key, const CharSequence& value, const CharSequence& encoding);
+ /**
+ * @returns the encoded size of the map entries (i.e. does not
+ * include the count field, typecode or any other metadata for the
+ * map as a whole)
+ */
+ size_t getSize() const;
+ size_t getCount() const;
+ /**
+ * @returns the total encoded size for a map containing the
+ * handled values (i.e. including the metadata for the map as a
+ * whole)
+ */
+ size_t getTotalSizeRequired(const Descriptor* d=0) const;
+ private:
+ size_t size;
+ size_t count;
+
+ void handleKey(const CharSequence& key);
+ static size_t getEncodedSize(const CharSequence&);
+};
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_MAPSIZECALCULATOR_H*/
diff --git a/qpid/cpp/src/qpid/amqp/MessageEncoder.cpp b/qpid/cpp/src/qpid/amqp/MessageEncoder.cpp
new file mode 100644
index 0000000000..71e7e75111
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/MessageEncoder.cpp
@@ -0,0 +1,312 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/amqp/MessageEncoder.h"
+#include "qpid/amqp/MapEncoder.h"
+#include "qpid/amqp/MapSizeCalculator.h"
+#include "qpid/amqp/descriptors.h"
+#include "qpid/log/Statement.h"
+#include <assert.h>
+
+namespace qpid {
+namespace amqp {
+
+namespace {
+size_t optimisable(const MessageEncoder::Header& msg)
+{
+ if (msg.getDeliveryCount()) return 5;
+ else if (msg.isFirstAcquirer()) return 4;
+ else if (msg.hasTtl()) return 3;
+ else if (msg.getPriority() != 4) return 2;
+ else if (msg.isDurable()) return 1;
+ else return 0;
+}
+
+size_t optimisable(const MessageEncoder::Properties& msg)
+{
+ if (msg.hasReplyToGroupId()) return 13;
+ else if (msg.hasGroupSequence()) return 12;
+ else if (msg.hasGroupId()) return 11;
+ else if (msg.hasCreationTime()) return 10;
+ else if (msg.hasAbsoluteExpiryTime()) return 9;
+ else if (msg.hasContentEncoding()) return 8;
+ else if (msg.hasContentType()) return 7;
+ else if (msg.hasCorrelationId()) return 6;
+ else if (msg.hasReplyTo()) return 5;
+ else if (msg.hasSubject()) return 4;
+ else if (msg.hasTo()) return 3;
+ else if (msg.hasUserId()) return 2;
+ else if (msg.hasMessageId()) return 1;
+ else return 0;
+}
+size_t encodedSize(const std::string& s)
+{
+ size_t total = s.size();
+ if (total > 255) total += 4;
+ else total += 1;
+ return total;
+}
+const std::string BINARY("binary");
+}
+
+void MessageEncoder::writeHeader(const Header& msg)
+{
+ size_t fields(optimise ? optimisable(msg) : 5);
+ if (fields) {
+ void* token = startList8(&qpid::amqp::message::HEADER);
+ writeBoolean(msg.isDurable());
+ if (fields > 1) writeUByte(msg.getPriority());
+
+ if (msg.getTtl()) writeUInt(msg.getTtl());
+ else if (fields > 2) writeNull();
+
+ if (msg.isFirstAcquirer()) writeBoolean(true);
+ else if (fields > 3) writeNull();
+
+ if (msg.getDeliveryCount()) writeUInt(msg.getDeliveryCount());
+ else if (fields > 4) writeNull();
+ endList8(fields, token);
+ }
+}
+
+
+void MessageEncoder::writeProperties(const Properties& msg)
+{
+ size_t fields(optimise ? optimisable(msg) : 13);
+ if (fields) {
+ void* token = startList32(&qpid::amqp::message::PROPERTIES);
+ if (msg.hasMessageId()) writeString(msg.getMessageId());
+ else writeNull();
+
+ if (msg.hasUserId()) writeBinary(msg.getUserId());
+ else if (fields > 1) writeNull();
+
+ if (msg.hasTo()) writeString(msg.getTo());
+ else if (fields > 2) writeNull();
+
+ if (msg.hasSubject()) writeString(msg.getSubject());
+ else if (fields > 3) writeNull();
+
+ if (msg.hasReplyTo()) writeString(msg.getReplyTo());
+ else if (fields > 4) writeNull();
+
+ if (msg.hasCorrelationId()) writeString(msg.getCorrelationId());
+ else if (fields > 5) writeNull();
+
+ if (msg.hasContentType()) writeSymbol(msg.getContentType());
+ else if (fields > 6) writeNull();
+
+ if (msg.hasContentEncoding()) writeSymbol(msg.getContentEncoding());
+ else if (fields > 7) writeNull();
+
+ if (msg.hasAbsoluteExpiryTime()) writeTimestamp(msg.getAbsoluteExpiryTime());
+ else if (fields > 8) writeNull();
+
+ if (msg.hasCreationTime()) writeTimestamp(msg.getCreationTime());
+ else if (fields > 9) writeNull();
+
+ if (msg.hasGroupId()) writeString(msg.getGroupId());
+ else if (fields > 10) writeNull();
+
+ if (msg.hasGroupSequence()) writeUInt(msg.getGroupSequence());
+ else if (fields > 11) writeNull();
+
+ if (msg.hasReplyToGroupId()) writeString(msg.getReplyToGroupId());
+ else if (fields > 12) writeNull();
+
+ endList32(fields, token);
+ }
+}
+
+void MessageEncoder::writeApplicationProperties(const ApplicationProperties& properties)
+{
+ MapSizeCalculator calc;
+ properties.handle(calc);
+ size_t required = calc.getTotalSizeRequired(&qpid::amqp::message::APPLICATION_PROPERTIES);
+ assert(required <= getSize() - getPosition());
+ MapEncoder encoder(skip(required), required);
+ encoder.writeMetaData(calc.getSize(), calc.getCount()*2, &qpid::amqp::message::APPLICATION_PROPERTIES);
+ properties.handle(encoder);
+}
+
+void MessageEncoder::writeApplicationProperties(const qpid::types::Variant::Map& properties)
+{
+ writeApplicationProperties(properties, !optimise || properties.size()*2 > 255 || getEncodedSizeForElements(properties) > 255);
+}
+
+void MessageEncoder::writeApplicationProperties(const qpid::types::Variant::Map& properties, bool large)
+{
+ writeMap(properties, &qpid::amqp::message::APPLICATION_PROPERTIES, large);
+}
+
+size_t MessageEncoder::getEncodedSize(const Header& h, const Properties& p, const qpid::types::Variant::Map& ap, const std::string& d)
+{
+ return getEncodedSize(h) + getEncodedSize(p, ap, d);
+}
+
+size_t MessageEncoder::getEncodedSize(const Header& h, const Properties& p, const ApplicationProperties& ap, const std::string& d)
+{
+ return getEncodedSize(h) + getEncodedSize(p) + getEncodedSize(ap) + getEncodedSizeForContent(d);
+}
+
+size_t MessageEncoder::getEncodedSize(const Properties& p, const qpid::types::Variant::Map& ap, const std::string& d)
+{
+ size_t total(getEncodedSize(p));
+ //application-properties:
+ total += 3/*descriptor*/ + getEncodedSize(ap, true);
+ //body:
+ if (d.size()) total += 3/*descriptor*/ + 1/*code*/ + encodedSize(d);
+
+ return total;
+}
+
+size_t MessageEncoder::getEncodedSizeForContent(const std::string& d)
+{
+ if (d.size()) return 3/*descriptor*/ + 1/*code*/ + encodedSize(d);
+ else return 0;
+}
+
+size_t MessageEncoder::getEncodedSize(const Header& h)
+{
+ //NOTE: this does not take optional optimisation into account,
+ //i.e. it is a 'worst case' estimate for required buffer space
+ size_t total(3/*descriptor*/ + 1/*code*/ + 1/*size*/ + 1/*count*/ + 5/*codes for each field*/);
+ if (h.getPriority() != 4) total += 1;
+ if (h.getDeliveryCount()) total += 4;
+ if (h.hasTtl()) total += 4;
+ return total;
+}
+
+size_t MessageEncoder::getEncodedSize(const Properties& p)
+{
+ //NOTE: this does not take optional optimisation into account,
+ //i.e. it is a 'worst case' estimate for required buffer space
+ size_t total(3/*descriptor*/ + 1/*code*/ + 4/*size*/ + 4/*count*/ + 13/*codes for each field*/);
+ if (p.hasMessageId()) total += encodedSize(p.getMessageId());
+ if (p.hasUserId()) total += encodedSize(p.getUserId());
+ if (p.hasTo()) total += encodedSize(p.getTo());
+ if (p.hasSubject()) total += encodedSize(p.getSubject());
+ if (p.hasReplyTo()) total += encodedSize(p.getReplyTo());
+ if (p.hasCorrelationId()) total += encodedSize(p.getCorrelationId());
+ if (p.hasContentType()) total += encodedSize(p.getContentType());
+ if (p.hasContentEncoding()) total += encodedSize(p.getContentEncoding());
+ if (p.hasAbsoluteExpiryTime()) total += 8;
+ if (p.hasCreationTime()) total += 8;
+ if (p.hasGroupId()) total += encodedSize(p.getGroupId());
+ if (p.hasGroupSequence()) total += 4;
+ if (p.hasReplyToGroupId()) total += encodedSize(p.getReplyToGroupId());
+ return total;
+}
+
+size_t MessageEncoder::getEncodedSize(const ApplicationProperties& p)
+{
+ MapSizeCalculator calc;
+ p.handle(calc);
+ return calc.getTotalSizeRequired(&qpid::amqp::message::APPLICATION_PROPERTIES);
+}
+
+size_t MessageEncoder::getEncodedSizeForElements(const qpid::types::Variant::Map& map)
+{
+ size_t total = 0;
+ for (qpid::types::Variant::Map::const_iterator i = map.begin(); i != map.end(); ++i) {
+ total += 1/*code*/ + encodedSize(i->first) + getEncodedSizeForValue(i->second);
+ }
+ return total;
+}
+
+size_t MessageEncoder::getEncodedSizeForValue(const qpid::types::Variant& value)
+{
+ size_t total = 0;
+ switch (value.getType()) {
+ case qpid::types::VAR_MAP:
+ total += getEncodedSize(value.asMap(), true);
+ break;
+ case qpid::types::VAR_LIST:
+ total += getEncodedSize(value.asList(), true);
+ break;
+
+ case qpid::types::VAR_VOID:
+ case qpid::types::VAR_BOOL:
+ total += 1;
+ break;
+
+ case qpid::types::VAR_UINT8:
+ case qpid::types::VAR_INT8:
+ total += 2;
+ break;
+
+ case qpid::types::VAR_UINT16:
+ case qpid::types::VAR_INT16:
+ total += 3;
+ break;
+
+ case qpid::types::VAR_UINT32:
+ case qpid::types::VAR_INT32:
+ case qpid::types::VAR_FLOAT:
+ total += 5;
+ break;
+
+ case qpid::types::VAR_UINT64:
+ case qpid::types::VAR_INT64:
+ case qpid::types::VAR_DOUBLE:
+ total += 9;
+ break;
+
+ case qpid::types::VAR_UUID:
+ total += 17;
+ break;
+
+ case qpid::types::VAR_STRING:
+ total += 1/*code*/ + encodedSize(value.getString());
+ break;
+ }
+ return total;
+}
+
+
+size_t MessageEncoder::getEncodedSize(const qpid::types::Variant::Map& map, bool alwaysUseLargeMap)
+{
+ size_t total = getEncodedSizeForElements(map);
+
+ //its not just the count that determines whether we can use a small map, but the aggregate size:
+ if (alwaysUseLargeMap || map.size()*2 > 255 || total > 255) total += 4/*size*/ + 4/*count*/;
+ else total += 1/*size*/ + 1/*count*/;
+
+ total += 1 /*code for map itself*/;
+
+ return total;
+}
+
+size_t MessageEncoder::getEncodedSize(const qpid::types::Variant::List& list, bool alwaysUseLargeList)
+{
+ size_t total(0);
+ for (qpid::types::Variant::List::const_iterator i = list.begin(); i != list.end(); ++i) {
+ total += getEncodedSizeForValue(*i);
+ }
+
+ //its not just the count that determines whether we can use a small list, but the aggregate size:
+ if (alwaysUseLargeList || list.size()*2 > 255 || total > 255) total += 4/*size*/ + 4/*count*/;
+ else total += 1/*size*/ + 1/*count*/;
+
+ total += 1 /*code for list itself*/;
+
+ return total;
+}
+}} // namespace qpid::amqp
diff --git a/qpid/cpp/src/qpid/amqp/MessageEncoder.h b/qpid/cpp/src/qpid/amqp/MessageEncoder.h
new file mode 100644
index 0000000000..05e1714004
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/MessageEncoder.h
@@ -0,0 +1,117 @@
+#ifndef QPID_AMQP_MESSAGEENCODER_H
+#define QPID_AMQP_MESSAGEENCODER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/amqp/Encoder.h"
+#include "qpid/types/Variant.h"
+
+namespace qpid {
+namespace amqp {
+class MapHandler;
+/**
+ *
+ */
+class MessageEncoder : public Encoder
+{
+ public:
+ class Header
+ {
+ public:
+ virtual ~Header() {}
+ virtual bool isDurable() const = 0;
+ virtual uint8_t getPriority() const = 0;
+ QPID_COMMON_EXTERN virtual bool hasTtl() const = 0;
+ virtual uint32_t getTtl() const = 0;
+ virtual bool isFirstAcquirer() const = 0;
+ virtual uint32_t getDeliveryCount() const = 0;
+ };
+
+ class Properties
+ {
+ public:
+ virtual ~Properties() {}
+ virtual bool hasMessageId() const = 0;
+ virtual std::string getMessageId() const = 0;
+ virtual bool hasUserId() const = 0;
+ virtual std::string getUserId() const = 0;
+ virtual bool hasTo() const = 0;
+ virtual std::string getTo() const = 0;
+ virtual bool hasSubject() const = 0;
+ virtual std::string getSubject() const = 0;
+ virtual bool hasReplyTo() const = 0;
+ virtual std::string getReplyTo() const = 0;
+ virtual bool hasCorrelationId() const = 0;
+ virtual std::string getCorrelationId() const = 0;
+ virtual bool hasContentType() const = 0;
+ virtual std::string getContentType() const = 0;
+ virtual bool hasContentEncoding() const = 0;
+ virtual std::string getContentEncoding() const = 0;
+ virtual bool hasAbsoluteExpiryTime() const = 0;
+ virtual int64_t getAbsoluteExpiryTime() const = 0;
+ virtual bool hasCreationTime() const = 0;
+ virtual int64_t getCreationTime() const = 0;
+ virtual bool hasGroupId() const = 0;
+ virtual std::string getGroupId() const = 0;
+ virtual bool hasGroupSequence() const = 0;
+ virtual uint32_t getGroupSequence() const = 0;
+ virtual bool hasReplyToGroupId() const = 0;
+ virtual std::string getReplyToGroupId() const = 0;
+ };
+
+ class ApplicationProperties
+ {
+ public:
+ virtual ~ApplicationProperties() {}
+ virtual void handle(MapHandler&) const = 0;
+ };
+
+ QPID_COMMON_EXTERN MessageEncoder(char* d, size_t s) : Encoder(d, s), optimise(true) {}
+ QPID_COMMON_EXTERN void writeHeader(const Header&);
+ QPID_COMMON_EXTERN void writeProperties(const Properties&);
+ QPID_COMMON_EXTERN void writeApplicationProperties(const ApplicationProperties&);
+ QPID_COMMON_EXTERN void writeApplicationProperties(const qpid::types::Variant::Map& properties);
+ QPID_COMMON_EXTERN void writeApplicationProperties(const qpid::types::Variant::Map& properties, bool useLargeMap);
+
+ QPID_COMMON_EXTERN static size_t getEncodedSize(const Header&);
+ QPID_COMMON_EXTERN static size_t getEncodedSize(const Properties&);
+ QPID_COMMON_EXTERN static size_t getEncodedSize(const ApplicationProperties&);
+
+ QPID_COMMON_EXTERN static size_t getEncodedSize(const qpid::types::Variant::List&, bool useLargeList);
+ QPID_COMMON_EXTERN static size_t getEncodedSize(const qpid::types::Variant::Map&, bool useLargeMap);
+
+ QPID_COMMON_EXTERN static size_t getEncodedSizeForValue(const qpid::types::Variant& value);
+ QPID_COMMON_EXTERN static size_t getEncodedSizeForContent(const std::string&);
+
+ //used in translating 0-10 content to 1.0, to determine buffer space needed
+ QPID_COMMON_EXTERN static size_t getEncodedSize(const Properties&, const qpid::types::Variant::Map&, const std::string&);
+
+ private:
+ bool optimise;
+
+ static size_t getEncodedSize(const Header&, const Properties&, const ApplicationProperties&, const std::string&);
+ static size_t getEncodedSize(const Header&, const Properties&, const qpid::types::Variant::Map&, const std::string&);
+
+ static size_t getEncodedSizeForElements(const qpid::types::Variant::Map&);
+};
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_MESSAGEENCODER_H*/
diff --git a/qpid/cpp/src/qpid/amqp/MessageId.cpp b/qpid/cpp/src/qpid/amqp/MessageId.cpp
new file mode 100644
index 0000000000..69ec4ce7f5
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/MessageId.cpp
@@ -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.
+ *
+ */
+#include "qpid/amqp/MessageId.h"
+#include <assert.h>
+#include <boost/lexical_cast.hpp>
+
+namespace qpid {
+namespace amqp {
+
+MessageId::MessageId() : type(NONE)
+{
+}
+void MessageId::assign(std::string& s) const
+{
+ switch (type) {
+ case NONE:
+ s = std::string();
+ break;
+ case BYTES:
+ if (value.bytes) s.assign(value.bytes.data, value.bytes.size);
+ break;
+ case UUID:
+ s = qpid::types::Uuid(value.bytes).str();
+ break;
+ case ULONG:
+ s = boost::lexical_cast<std::string>(value.ulong);
+ break;
+ }
+}
+
+MessageId::operator bool() const
+{
+ return type!=NONE;
+}
+
+std::string MessageId::str() const
+{
+ std::string s;
+ assign(s);
+ return s;
+}
+
+void MessageId::set(qpid::amqp::CharSequence bytes, qpid::types::VariantType t)
+{
+ switch (t) {
+ case qpid::types::VAR_STRING:
+ type = BYTES;
+ break;
+ case qpid::types::VAR_UUID:
+ type = UUID;
+ assert(bytes.size == 16);
+ break;
+ default:
+ assert(false);
+ }
+ value.bytes = bytes;
+}
+void MessageId::set(uint64_t ulong)
+{
+ type = ULONG;
+ value.ulong = ulong;
+}
+
+}} // namespace qpid::amqp
diff --git a/qpid/cpp/src/qpid/amqp/MessageId.h b/qpid/cpp/src/qpid/amqp/MessageId.h
new file mode 100644
index 0000000000..4505469148
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/MessageId.h
@@ -0,0 +1,57 @@
+#ifndef QPID_AMQP_MESSAGEID_H
+#define QPID_AMQP_MESSAGEID_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/amqp/CharSequence.h"
+#include "qpid/types/Variant.h"
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+namespace amqp {
+
+struct MessageId
+{
+ union
+ {
+ qpid::amqp::CharSequence bytes;
+ uint64_t ulong;
+ } value;
+ enum
+ {
+ NONE,
+ BYTES,
+ UUID,
+ ULONG
+ } type;
+
+ QPID_COMMON_EXTERN MessageId();
+ QPID_COMMON_EXTERN operator bool() const;
+ QPID_COMMON_EXTERN std::string str() const;
+ QPID_COMMON_EXTERN void assign(std::string&) const;
+ QPID_COMMON_EXTERN void set(qpid::amqp::CharSequence bytes, qpid::types::VariantType t);
+ QPID_COMMON_EXTERN void set(uint64_t ulong);
+
+};
+
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_MESSAGEID_H*/
diff --git a/qpid/cpp/src/qpid/amqp/MessageReader.cpp b/qpid/cpp/src/qpid/amqp/MessageReader.cpp
new file mode 100644
index 0000000000..ab90472067
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/MessageReader.cpp
@@ -0,0 +1,685 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/amqp/MessageReader.h"
+#include "qpid/amqp/Descriptor.h"
+#include "qpid/amqp/descriptors.h"
+#include "qpid/amqp/typecodes.h"
+#include "qpid/types/Uuid.h"
+#include "qpid/types/Variant.h"
+#include "qpid/log/Statement.h"
+
+using namespace qpid::amqp::message;
+
+namespace qpid {
+namespace amqp {
+namespace {
+
+//header fields:
+const size_t DURABLE(0);
+const size_t PRIORITY(1);
+const size_t TTL(2);
+const size_t FIRST_ACQUIRER(3);
+const size_t DELIVERY_COUNT(4);
+
+//properties fields:
+const size_t MESSAGE_ID(0);
+const size_t USER_ID(1);
+const size_t TO(2);
+const size_t SUBJECT(3);
+const size_t REPLY_TO(4);
+const size_t CORRELATION_ID(5);
+const size_t CONTENT_TYPE(6);
+const size_t CONTENT_ENCODING(7);
+const size_t ABSOLUTE_EXPIRY_TIME(8);
+const size_t CREATION_TIME(9);
+const size_t GROUP_ID(10);
+const size_t GROUP_SEQUENCE(11);
+const size_t REPLY_TO_GROUP_ID(12);
+
+
+const Descriptor* nested(const Descriptor* d)
+{
+ if (d && d->nested) return d->nested.get();
+ else return 0;
+}
+}
+
+MessageReader::HeaderReader::HeaderReader(MessageReader& p) : parent(p), index(0) {}
+void MessageReader::HeaderReader::onBoolean(bool v, const Descriptor*) // durable, first-acquirer
+{
+ if (index == DURABLE) {
+ parent.onDurable(v);
+ } else if (index == FIRST_ACQUIRER) {
+ parent.onFirstAcquirer(v);
+ } else {
+ QPID_LOG(warning, "Unexpected message format, got boolean at index " << index << " of headers");
+ }
+ ++index;
+}
+void MessageReader::HeaderReader::onUByte(uint8_t v, const Descriptor*) // priority
+{
+ if (index == PRIORITY) {
+ parent.onPriority(v);
+ } else {
+ QPID_LOG(warning, "Unexpected message format, got ubyte at index " << index << " of headers");
+ }
+ ++index;
+}
+void MessageReader::HeaderReader::onUInt(uint32_t v, const Descriptor*) // ttl, delivery-count
+{
+ if (index == TTL) {
+ parent.onTtl(v);
+ } else if (index == DELIVERY_COUNT) {
+ parent.onDeliveryCount(v);
+ } else {
+ QPID_LOG(warning, "Unexpected message format, got uint at index " << index << " of headers");
+ }
+ ++index;
+}
+void MessageReader::HeaderReader::onNull(const Descriptor*)
+{
+ ++index;
+}
+
+MessageReader::PropertiesReader::PropertiesReader(MessageReader& p) : parent(p), index(0) {}
+void MessageReader::PropertiesReader::onUuid(const CharSequence& v, const Descriptor*) // message-id, correlation-id
+{
+ if (index == MESSAGE_ID) {
+ parent.onMessageId(v, qpid::types::VAR_UUID);
+ } else if (index == CORRELATION_ID) {
+ parent.onCorrelationId(v, qpid::types::VAR_UUID);
+ } else {
+ QPID_LOG(warning, "Unexpected message format, got uuid at index " << index << " of properties");
+ }
+ ++index;
+}
+void MessageReader::PropertiesReader::onULong(uint64_t v, const Descriptor*) // message-id, correlation-id
+{
+ if (index == MESSAGE_ID) {
+ parent.onMessageId(v);
+ } else if (index == CORRELATION_ID) {
+ parent.onCorrelationId(v);
+ } else {
+ QPID_LOG(warning, "Unexpected message format, got long at index " << index << " of properties");
+ }
+ ++index;
+}
+void MessageReader::PropertiesReader::onBinary(const CharSequence& v, const Descriptor*) // message-id, correlation-id, user-id
+{
+ if (index == MESSAGE_ID) {
+ parent.onMessageId(v, qpid::types::VAR_STRING);
+ } else if (index == CORRELATION_ID) {
+ parent.onCorrelationId(v, qpid::types::VAR_STRING);
+ } else if (index == USER_ID) {
+ parent.onUserId(v);
+ } else {
+ QPID_LOG(warning, "Unexpected message format, got binary at index " << index << " of properties");
+ }
+ ++index;
+}
+void MessageReader::PropertiesReader::onString(const CharSequence& v, const Descriptor*) // message-id, correlation-id, group-id, reply-to-group-id, subject, to, reply-to
+{
+ if (index == MESSAGE_ID) {
+ parent.onMessageId(v, qpid::types::VAR_STRING);
+ } else if (index == CORRELATION_ID) {
+ parent.onCorrelationId(v, qpid::types::VAR_STRING);
+ } else if (index == GROUP_ID) {
+ parent.onGroupId(v);
+ } else if (index == REPLY_TO_GROUP_ID) {
+ parent.onReplyToGroupId(v);
+ } else if (index == SUBJECT) {
+ parent.onSubject(v);
+ } else if (index == TO) {
+ parent.onTo(v);
+ } else if (index == REPLY_TO) {
+ parent.onReplyTo(v);
+ } else {
+ QPID_LOG(warning, "Unexpected message format, got string at index " << index << " of properties");
+ }
+ ++index;
+}
+void MessageReader::PropertiesReader::onSymbol(const CharSequence& v, const Descriptor*) // content-type, content-encoding
+{
+ if (index == CONTENT_TYPE) {
+ parent.onContentType(v);
+ } else if (index == CONTENT_ENCODING) {
+ parent.onContentEncoding(v);
+ } else {
+ QPID_LOG(warning, "Unexpected message format, got symbol at index " << index << " of properties");
+ }
+ ++index;
+}
+void MessageReader::PropertiesReader::onTimestamp(int64_t v, const Descriptor*) // absolute-expiry-time, creation-time
+{
+ if (index == ABSOLUTE_EXPIRY_TIME) {
+ parent.onAbsoluteExpiryTime(v);
+ } else if (index == CREATION_TIME) {
+ parent.onCreationTime(v);
+ } else {
+ QPID_LOG(warning, "Unexpected message format, got timestamp at index " << index << " of properties");
+ }
+ ++index;
+}
+void MessageReader::PropertiesReader::onUInt(uint32_t v, const Descriptor*) // group-sequence
+{
+ if (index == GROUP_SEQUENCE) {
+ parent.onGroupSequence(v);
+ } else {
+ QPID_LOG(warning, "Unexpected message format, got uint at index " << index << " of properties");
+ }
+ ++index;
+}
+void MessageReader::PropertiesReader::onNull(const Descriptor*)
+{
+ ++index;
+}
+void MessageReader::PropertiesReader::onBoolean(bool, const Descriptor*)
+{
+ QPID_LOG(info, "skipping message property at index " << index << " unexpected type (boolean)");
+ ++index;
+}
+void MessageReader::PropertiesReader::onUByte(uint8_t, const Descriptor*)
+{
+ QPID_LOG(info, "skipping message property at index " << index << " unexpected type (ubyte)");
+ ++index;
+}
+void MessageReader::PropertiesReader::onUShort(uint16_t, const Descriptor*)
+{
+ QPID_LOG(info, "skipping message property at index " << index << " unexpected type (ushort)");
+ ++index;
+}
+void MessageReader::PropertiesReader::onByte(int8_t, const Descriptor*)
+{
+ QPID_LOG(info, "skipping message property at index " << index << " unexpected type (byte)");
+ ++index;
+}
+void MessageReader::PropertiesReader::onShort(int16_t, const Descriptor*)
+{
+ QPID_LOG(info, "skipping message property at index " << index << " unexpected type (short)");
+ ++index;
+}
+void MessageReader::PropertiesReader::onInt(int32_t, const Descriptor*)
+{
+ QPID_LOG(info, "skipping message property at index " << index << " unexpected type (int)");
+ ++index;
+}
+void MessageReader::PropertiesReader::onLong(int64_t, const Descriptor*)
+{
+ QPID_LOG(info, "skipping message property at index " << index << " unexpected type (long)");
+ ++index;
+}
+void MessageReader::PropertiesReader::onFloat(float, const Descriptor*)
+{
+ QPID_LOG(info, "skipping message property at index " << index << " unexpected type (float)");
+ ++index;
+}
+void MessageReader::PropertiesReader::onDouble(double, const Descriptor*)
+{
+ QPID_LOG(info, "skipping message property at index " << index << " unexpected type (double)");
+ ++index;
+}
+bool MessageReader::PropertiesReader::onStartList(uint32_t /*count*/, const CharSequence& /*elements*/, const CharSequence& /*complete*/, const Descriptor*)
+{
+ QPID_LOG(info, "skipping message property at index " << index << " unexpected type (list)");
+ ++index;
+ return false;
+}
+bool MessageReader::PropertiesReader::onStartMap(uint32_t /*count*/, const CharSequence& /*elements*/, const CharSequence& /*complete*/, const Descriptor*)
+{
+ QPID_LOG(info, "skipping message property at index " << index << " unexpected type (map)");
+ ++index;
+ return false;
+}
+bool MessageReader::PropertiesReader::onStartArray(uint32_t /*count*/, const CharSequence&, const Constructor&, const Descriptor*)
+{
+ QPID_LOG(info, "skipping message property at index " << index << " unexpected type (array)");
+ ++index;
+ return false;
+}
+
+
+//header, properties, amqp-sequence, amqp-value
+bool MessageReader::onStartList(uint32_t count, const CharSequence& elements, const CharSequence& raw, const Descriptor* descriptor)
+{
+ if (delegate) {
+ return delegate->onStartList(count, elements, raw, descriptor);
+ } else {
+ if (!descriptor) {
+ QPID_LOG(warning, "Expected described type but got no descriptor for list.");
+ return false;
+ } else if (descriptor->match(HEADER_SYMBOL, HEADER_CODE)) {
+ delegate = &headerReader;
+ return true;
+ } else if (descriptor->match(PROPERTIES_SYMBOL, PROPERTIES_CODE)) {
+ delegate = &propertiesReader;
+ return true;
+ } else if (descriptor->match(AMQP_SEQUENCE_SYMBOL, AMQP_SEQUENCE_CODE)) {
+ onAmqpSequence(raw);
+ return false;
+ } else if (descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) {
+ onAmqpValue(elements, qpid::amqp::typecodes::LIST_NAME, nested(descriptor));
+ return false;
+ } else {
+ QPID_LOG(warning, "Unexpected described list: " << *descriptor);
+ return false;
+ }
+ }
+}
+void MessageReader::onEndList(uint32_t count, const Descriptor* descriptor)
+{
+ if (delegate) {
+ if (descriptor && (descriptor->match(HEADER_SYMBOL, HEADER_CODE) || descriptor->match(PROPERTIES_SYMBOL, PROPERTIES_CODE))) {
+ delegate = 0;
+ } else {
+ delegate->onEndList(count, descriptor);
+ }
+ }
+}
+
+//delivery-annotations, message-annotations, application-properties, amqp-value
+bool MessageReader::onStartMap(uint32_t count, const CharSequence& elements, const CharSequence& raw, const Descriptor* descriptor)
+{
+ if (delegate) {
+ return delegate->onStartMap(count, elements, raw, descriptor);
+ } else {
+ if (!descriptor) {
+ QPID_LOG(warning, "Expected described type but got no descriptor for map.");
+ return false;
+ } else if (descriptor->match(DELIVERY_ANNOTATIONS_SYMBOL, DELIVERY_ANNOTATIONS_CODE)) {
+ onDeliveryAnnotations(elements, raw);
+ return false;
+ } else if (descriptor->match(MESSAGE_ANNOTATIONS_SYMBOL, MESSAGE_ANNOTATIONS_CODE)) {
+ onMessageAnnotations(elements, raw);
+ return false;
+ } else if (descriptor->match(FOOTER_SYMBOL, FOOTER_CODE)) {
+ onFooter(elements, raw);
+ return false;
+ } else if (descriptor->match(APPLICATION_PROPERTIES_SYMBOL, APPLICATION_PROPERTIES_CODE)) {
+ onApplicationProperties(elements, raw);
+ return false;
+ } else if (descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) {
+ onAmqpValue(elements, qpid::amqp::typecodes::MAP_NAME, nested(descriptor));
+ return false;
+ } else {
+ QPID_LOG(warning, "Unexpected described map: " << *descriptor);
+ return false;
+ }
+ }
+}
+
+void MessageReader::onEndMap(uint32_t count, const Descriptor* descriptor)
+{
+ if (delegate) {
+ delegate->onEndMap(count, descriptor);
+ }
+}
+
+//data, amqp-value
+void MessageReader::onBinary(const CharSequence& bytes, const Descriptor* descriptor)
+{
+ if (delegate) {
+ delegate->onBinary(bytes, descriptor);
+ } else {
+ if (!descriptor) {
+ QPID_LOG(warning, "Expected described type but got binary value with no descriptor.");
+ } else if (descriptor->match(DATA_SYMBOL, DATA_CODE)) {
+ onData(bytes);
+ } else if (descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) {
+ onAmqpValue(bytes, qpid::amqp::typecodes::BINARY_NAME, nested(descriptor));
+ } else {
+ QPID_LOG(warning, "Unexpected binary value with descriptor: " << *descriptor);
+ }
+ }
+
+}
+
+//amqp-value
+void MessageReader::onNull(const Descriptor* descriptor)
+{
+ if (delegate) {
+ delegate->onNull(descriptor);
+ } else {
+ if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) {
+ qpid::types::Variant v;
+ onAmqpValue(v, nested(descriptor));
+ } else {
+ if (!descriptor) {
+ QPID_LOG(warning, "Expected described type but got null value with no descriptor.");
+ } else {
+ QPID_LOG(warning, "Unexpected null value with descriptor: " << *descriptor);
+ }
+ }
+ }
+}
+void MessageReader::onString(const CharSequence& v, const Descriptor* descriptor)
+{
+ if (delegate) {
+ delegate->onString(v, descriptor);
+ } else {
+ if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) {
+ onAmqpValue(v, qpid::amqp::typecodes::STRING_NAME, nested(descriptor));
+ } else {
+ if (!descriptor) {
+ QPID_LOG(warning, "Expected described type but got string value with no descriptor.");
+ } else {
+ QPID_LOG(warning, "Unexpected string value with descriptor: " << *descriptor);
+ }
+ }
+ }
+}
+void MessageReader::onSymbol(const CharSequence& v, const Descriptor* descriptor)
+{
+ if (delegate) {
+ delegate->onSymbol(v, descriptor);
+ } else {
+ if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) {
+ onAmqpValue(v, qpid::amqp::typecodes::SYMBOL_NAME, nested(descriptor));
+ } else {
+ if (!descriptor) {
+ QPID_LOG(warning, "Expected described type but got symbol value with no descriptor.");
+ } else {
+ QPID_LOG(warning, "Unexpected symbol value with descriptor: " << *descriptor);
+ }
+ }
+ }
+}
+
+void MessageReader::onBoolean(bool v, const Descriptor* descriptor)
+{
+ if (delegate) {
+ delegate->onBoolean(v, descriptor);
+ } else {
+ if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) {
+ qpid::types::Variant body = v;
+ onAmqpValue(body, nested(descriptor));
+ } else {
+ if (!descriptor) {
+ QPID_LOG(warning, "Expected described type but got boolean value with no descriptor.");
+ } else {
+ QPID_LOG(warning, "Unexpected boolean value with descriptor: " << *descriptor);
+ }
+ }
+ }
+}
+
+void MessageReader::onUByte(uint8_t v, const Descriptor* descriptor)
+{
+ if (delegate) {
+ delegate->onUByte(v, descriptor);
+ } else {
+ if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) {
+ qpid::types::Variant body = v;
+ onAmqpValue(body, nested(descriptor));
+ } else {
+ if (!descriptor) {
+ QPID_LOG(warning, "Expected described type but got ubyte value with no descriptor.");
+ } else {
+ QPID_LOG(warning, "Unexpected ubyte value with descriptor: " << *descriptor);
+ }
+ }
+ }
+}
+
+void MessageReader::onUShort(uint16_t v, const Descriptor* descriptor)
+{
+ if (delegate) {
+ delegate->onUShort(v, descriptor);
+ } else {
+ if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) {
+ qpid::types::Variant body = v;
+ onAmqpValue(body, nested(descriptor));
+ } else {
+ if (!descriptor) {
+ QPID_LOG(warning, "Expected described type but got ushort value with no descriptor.");
+ } else {
+ QPID_LOG(warning, "Unexpected ushort value with descriptor: " << *descriptor);
+ }
+ }
+ }
+}
+
+void MessageReader::onUInt(uint32_t v, const Descriptor* descriptor)
+{
+ if (delegate) {
+ delegate->onUInt(v, descriptor);
+ } else {
+ if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) {
+ qpid::types::Variant body = v;
+ onAmqpValue(body, nested(descriptor));
+ } else {
+ if (!descriptor) {
+ QPID_LOG(warning, "Expected described type but got uint value with no descriptor.");
+ } else {
+ QPID_LOG(warning, "Unexpected uint value with descriptor: " << *descriptor);
+ }
+ }
+ }
+}
+
+void MessageReader::onULong(uint64_t v, const Descriptor* descriptor)
+{
+ if (delegate) {
+ delegate->onULong(v, descriptor);
+ } else {
+ if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) {
+ qpid::types::Variant body = v;
+ onAmqpValue(body, nested(descriptor));
+ } else {
+ if (!descriptor) {
+ QPID_LOG(warning, "Expected described type but got ulong value with no descriptor.");
+ } else {
+ QPID_LOG(warning, "Unexpected ulong value with descriptor: " << *descriptor);
+ }
+ }
+ }
+}
+
+void MessageReader::onByte(int8_t v, const Descriptor* descriptor)
+{
+ if (delegate) {
+ delegate->onByte(v, descriptor);
+ } else {
+ if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) {
+ qpid::types::Variant body = v;
+ onAmqpValue(body, nested(descriptor));
+ } else {
+ if (!descriptor) {
+ QPID_LOG(warning, "Expected described type but got byte value with no descriptor.");
+ } else {
+ QPID_LOG(warning, "Unexpected byte value with descriptor: " << *descriptor);
+ }
+ }
+ }
+}
+
+void MessageReader::onShort(int16_t v, const Descriptor* descriptor)
+{
+ if (delegate) {
+ delegate->onShort(v, descriptor);
+ } else {
+ if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) {
+ qpid::types::Variant body = v;
+ onAmqpValue(body, nested(descriptor));
+ } else {
+ if (!descriptor) {
+ QPID_LOG(warning, "Expected described type but got short value with no descriptor.");
+ } else {
+ QPID_LOG(warning, "Unexpected short value with descriptor: " << *descriptor);
+ }
+ }
+ }
+}
+
+void MessageReader::onInt(int32_t v, const Descriptor* descriptor)
+{
+ if (delegate) {
+ delegate->onInt(v, descriptor);
+ } else {
+ if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) {
+ qpid::types::Variant body = v;
+ onAmqpValue(body, nested(descriptor));
+ } else {
+ if (!descriptor) {
+ QPID_LOG(warning, "Expected described type but got int value with no descriptor.");
+ } else {
+ QPID_LOG(warning, "Unexpected int value with descriptor: " << *descriptor);
+ }
+ }
+ }
+}
+
+void MessageReader::onLong(int64_t v, const Descriptor* descriptor)
+{
+ if (delegate) {
+ delegate->onLong(v, descriptor);
+ } else {
+ if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) {
+ qpid::types::Variant body = v;
+ onAmqpValue(body, nested(descriptor));
+ } else {
+ if (!descriptor) {
+ QPID_LOG(warning, "Expected described type but got long value with no descriptor.");
+ } else {
+ QPID_LOG(warning, "Unexpected long value with descriptor: " << *descriptor);
+ }
+ }
+ }
+}
+
+void MessageReader::onFloat(float v, const Descriptor* descriptor)
+{
+ if (delegate) {
+ delegate->onFloat(v, descriptor);
+ } else {
+ if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) {
+ qpid::types::Variant body = v;
+ onAmqpValue(body, nested(descriptor));
+ } else {
+ if (!descriptor) {
+ QPID_LOG(warning, "Expected described type but got float value with no descriptor.");
+ } else {
+ QPID_LOG(warning, "Unexpected float value with descriptor: " << *descriptor);
+ }
+ }
+ }
+}
+
+void MessageReader::onDouble(double v, const Descriptor* descriptor)
+{
+ if (delegate) {
+ delegate->onDouble(v, descriptor);
+ } else {
+ if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) {
+ qpid::types::Variant body = v;
+ onAmqpValue(body, nested(descriptor));
+ } else {
+ if (!descriptor) {
+ QPID_LOG(warning, "Expected described type but got double value with no descriptor.");
+ } else {
+ QPID_LOG(warning, "Unexpected double value with descriptor: " << *descriptor);
+ }
+ }
+ }
+}
+
+void MessageReader::onUuid(const CharSequence& v, const Descriptor* descriptor)
+{
+ if (delegate) {
+ delegate->onUuid(v, descriptor);
+ } else {
+ if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) {
+ onAmqpValue(v, qpid::amqp::typecodes::UUID_NAME, nested(descriptor));
+ } else {
+ if (!descriptor) {
+ QPID_LOG(warning, "Expected described type but got uuid value with no descriptor.");
+ } else {
+ QPID_LOG(warning, "Unexpected uuid value with descriptor: " << *descriptor);
+ }
+ }
+ }
+}
+
+void MessageReader::onTimestamp(int64_t v, const Descriptor* descriptor)
+{
+ if (delegate) {
+ delegate->onTimestamp(v, descriptor);
+ } else {
+ if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) {
+ qpid::types::Variant body = v;
+ onAmqpValue(body, nested(descriptor));
+ } else {
+ if (!descriptor) {
+ QPID_LOG(warning, "Expected described type but got timestamp value with no descriptor.");
+ } else {
+ QPID_LOG(warning, "Unexpected timestamp value with descriptor: " << *descriptor);
+ }
+ }
+ }
+}
+
+bool MessageReader::onStartArray(uint32_t count, const CharSequence& raw, const Constructor& constructor, const Descriptor* descriptor)
+{
+ if (delegate) {
+ return delegate->onStartArray(count, raw, constructor, descriptor);
+ } else {
+ if (descriptor && descriptor->match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE)) {
+ //TODO: might be better to decode this here
+ onAmqpValue(raw, qpid::amqp::typecodes::ARRAY_NAME, nested(descriptor));
+ } else {
+ if (!descriptor) {
+ QPID_LOG(warning, "Expected described type but got array with no descriptor.");
+ } else {
+ QPID_LOG(warning, "Unexpected array with descriptor: " << *descriptor);
+ }
+ }
+ return false;
+ }
+}
+
+void MessageReader::onEndArray(uint32_t v, const Descriptor* descriptor)
+{
+ if (delegate) {
+ delegate->onEndArray(v, descriptor);
+ }
+}
+
+MessageReader::MessageReader() : headerReader(*this), propertiesReader(*this), delegate(0)
+{
+ bare.init();
+}
+
+void MessageReader::onDescriptor(const Descriptor& descriptor, const char* position)
+{
+ if (bare.data) {
+ if (descriptor.match(FOOTER_SYMBOL, FOOTER_CODE)) {
+ bare.size = position - bare.data;
+ }
+ } else {
+ if (descriptor.match(PROPERTIES_SYMBOL, PROPERTIES_CODE) || descriptor.match(APPLICATION_PROPERTIES_SYMBOL, APPLICATION_PROPERTIES_CODE)
+ || descriptor.match(AMQP_SEQUENCE_SYMBOL, AMQP_SEQUENCE_CODE) || descriptor.match(AMQP_VALUE_SYMBOL, AMQP_VALUE_CODE) || descriptor.match(DATA_SYMBOL, DATA_CODE)) {
+ bare.data = position;
+ }
+ }
+}
+
+CharSequence MessageReader::getBareMessage() const { return bare; }
+
+}} // namespace qpid::amqp
diff --git a/qpid/cpp/src/qpid/amqp/MessageReader.h b/qpid/cpp/src/qpid/amqp/MessageReader.h
new file mode 100644
index 0000000000..2fb588546f
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/MessageReader.h
@@ -0,0 +1,161 @@
+#ifndef QPID_AMQP_MESSAGEREADER_H
+#define QPID_AMQP_MESSAGEREADER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/amqp/CharSequence.h"
+#include "qpid/amqp/Reader.h"
+#include "qpid/types/Variant.h"
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+namespace amqp {
+
+/**
+ * Reader for an AMQP 1.0 message
+ */
+class MessageReader : public Reader
+{
+ public:
+ QPID_COMMON_EXTERN MessageReader();
+
+ //header, properties, amqp-sequence, amqp-value
+ QPID_COMMON_EXTERN bool onStartList(uint32_t, const CharSequence&, const CharSequence&, const Descriptor*);
+ QPID_COMMON_EXTERN void onEndList(uint32_t, const Descriptor*);
+
+ //delivery-annotations, message-annotations, application-headers, amqp-value
+ QPID_COMMON_EXTERN bool onStartMap(uint32_t, const CharSequence&, const CharSequence&, const Descriptor*);
+ QPID_COMMON_EXTERN void onEndMap(uint32_t, const Descriptor*);
+
+ //data, amqp-value
+ QPID_COMMON_EXTERN void onBinary(const CharSequence&, const Descriptor*);
+
+ //amqp-value
+ QPID_COMMON_EXTERN void onNull(const Descriptor*);
+ QPID_COMMON_EXTERN void onString(const CharSequence&, const Descriptor*);
+ QPID_COMMON_EXTERN void onSymbol(const CharSequence&, const Descriptor*);
+ QPID_COMMON_EXTERN void onBoolean(bool, const Descriptor*);
+ QPID_COMMON_EXTERN void onUByte(uint8_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onUShort(uint16_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onUInt(uint32_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onULong(uint64_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onByte(int8_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onShort(int16_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onInt(int32_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onLong(int64_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onFloat(float, const Descriptor*);
+ QPID_COMMON_EXTERN void onDouble(double, const Descriptor*);
+ QPID_COMMON_EXTERN void onUuid(const CharSequence&, const Descriptor*);
+ QPID_COMMON_EXTERN void onTimestamp(int64_t, const Descriptor*);
+ QPID_COMMON_EXTERN bool onStartArray(uint32_t, const CharSequence&, const Constructor&, const Descriptor*);
+ QPID_COMMON_EXTERN void onEndArray(uint32_t, const Descriptor*);
+ QPID_COMMON_EXTERN void onDescriptor(const Descriptor&, const char*);
+
+ //header:
+ virtual void onDurable(bool) = 0;
+ virtual void onPriority(uint8_t) = 0;
+ virtual void onTtl(uint32_t) = 0;
+ virtual void onFirstAcquirer(bool) = 0;
+ virtual void onDeliveryCount(uint32_t) = 0;
+
+ //properties:
+ virtual void onMessageId(uint64_t) = 0;
+ virtual void onMessageId(const CharSequence&, qpid::types::VariantType) = 0;
+ virtual void onUserId(const CharSequence&) = 0;
+ virtual void onTo(const CharSequence&) = 0;
+ virtual void onSubject(const CharSequence&) = 0;
+ virtual void onReplyTo(const CharSequence&) = 0;
+ virtual void onCorrelationId(uint64_t) = 0;
+ virtual void onCorrelationId(const CharSequence&, qpid::types::VariantType) = 0;
+ virtual void onContentType(const CharSequence&) = 0;
+ virtual void onContentEncoding(const CharSequence&) = 0;
+ virtual void onAbsoluteExpiryTime(int64_t) = 0;
+ virtual void onCreationTime(int64_t) = 0;
+ virtual void onGroupId(const CharSequence&) = 0;
+ virtual void onGroupSequence(uint32_t) = 0;
+ virtual void onReplyToGroupId(const CharSequence&) = 0;
+
+ virtual void onApplicationProperties(const CharSequence& /*values*/, const CharSequence& /*full*/) = 0;
+ virtual void onDeliveryAnnotations(const CharSequence& /*values*/, const CharSequence& /*full*/) = 0;
+ virtual void onMessageAnnotations(const CharSequence& /*values*/, const CharSequence& /*full*/) = 0;
+
+ virtual void onData(const CharSequence&) = 0;
+ virtual void onAmqpSequence(const CharSequence&) = 0;
+ virtual void onAmqpValue(const CharSequence&, const std::string& type, const Descriptor*) = 0;
+ virtual void onAmqpValue(const qpid::types::Variant&, const Descriptor*) = 0;
+
+ virtual void onFooter(const CharSequence& /*values*/, const CharSequence& /*full*/) = 0;
+
+ QPID_COMMON_EXTERN CharSequence getBareMessage() const;
+
+ private:
+
+ class HeaderReader : public Reader
+ {
+ public:
+ HeaderReader(MessageReader&);
+ void onBoolean(bool v, const Descriptor*); // durable, first-acquirer
+ void onUByte(uint8_t v, const Descriptor*); // priority
+ void onUInt(uint32_t v, const Descriptor*); // ttl, delivery-count
+ void onNull(const Descriptor*);
+ private:
+ MessageReader& parent;
+ size_t index;
+ };
+ class PropertiesReader : public Reader
+ {
+ public:
+ PropertiesReader(MessageReader&);
+ void onUuid(const CharSequence& v, const Descriptor*); // message-id, correlation-id
+ void onULong(uint64_t v, const Descriptor*); // message-id, correlation-id
+ void onBinary(const CharSequence& v, const Descriptor*); // message-id, correlation-id, user-id
+ void onString(const CharSequence& v, const Descriptor*); // message-id, correlation-id, group-id, reply-to-group-id, subject, to, reply-to
+ void onSymbol(const CharSequence& v, const Descriptor*); // content-type, content-encoding
+ void onTimestamp(int64_t v, const Descriptor*); // absolute-expiry-time, creation-time
+ void onUInt(uint32_t v, const Descriptor*); // group-sequence
+ void onNull(const Descriptor*);
+
+ void onBoolean(bool, const Descriptor*);
+ void onUByte(uint8_t, const Descriptor*);
+ void onUShort(uint16_t, const Descriptor*);
+ void onByte(int8_t, const Descriptor*);
+ void onShort(int16_t, const Descriptor*);
+ void onInt(int32_t, const Descriptor*);
+ void onLong(int64_t, const Descriptor*);
+ void onFloat(float, const Descriptor*);
+ void onDouble(double, const Descriptor*);
+ bool onStartList(uint32_t /*count*/, const CharSequence& /*elements*/, const CharSequence& /*complete*/, const Descriptor*);
+ bool onStartMap(uint32_t /*count*/, const CharSequence& /*elements*/, const CharSequence& /*complete*/, const Descriptor*);
+ bool onStartArray(uint32_t /*count*/, const CharSequence&, const Constructor&, const Descriptor*);
+
+ private:
+ MessageReader& parent;
+ size_t index;
+ };
+ HeaderReader headerReader;
+ PropertiesReader propertiesReader;
+ Reader* delegate;
+ CharSequence bare;
+};
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_MESSAGEREADER_H*/
diff --git a/qpid/cpp/src/qpid/amqp/Reader.h b/qpid/cpp/src/qpid/amqp/Reader.h
new file mode 100644
index 0000000000..32f33dc33f
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/Reader.h
@@ -0,0 +1,80 @@
+#ifndef QPID_AMQP_READER_H
+#define QPID_AMQP_READER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/sys/IntegerTypes.h"
+#include <stddef.h>
+
+namespace qpid {
+namespace amqp {
+struct CharSequence;
+struct Constructor;
+struct Descriptor;
+
+/**
+ * Allows an event-driven, callback-based approach to processing an
+ * AMQP encoded data stream. By sublassing and implementing the
+ * methods of interest, readers can be constructed for different
+ * contexts.
+ */
+class Reader
+{
+ public:
+ virtual ~Reader() {}
+ virtual void onNull(const Descriptor*) {}
+ virtual void onBoolean(bool, const Descriptor*) {}
+ virtual void onUByte(uint8_t, const Descriptor*) {}
+ virtual void onUShort(uint16_t, const Descriptor*) {}
+ virtual void onUInt(uint32_t, const Descriptor*) {}
+ virtual void onULong(uint64_t, const Descriptor*) {}
+ virtual void onByte(int8_t, const Descriptor*) {}
+ virtual void onShort(int16_t, const Descriptor*) {}
+ virtual void onInt(int32_t, const Descriptor*) {}
+ virtual void onLong(int64_t, const Descriptor*) {}
+ virtual void onFloat(float, const Descriptor*) {}
+ virtual void onDouble(double, const Descriptor*) {}
+ virtual void onUuid(const CharSequence&, const Descriptor*) {}
+ virtual void onTimestamp(int64_t, const Descriptor*) {}
+
+ virtual void onBinary(const CharSequence&, const Descriptor*) {}
+ virtual void onString(const CharSequence&, const Descriptor*) {}
+ virtual void onSymbol(const CharSequence&, const Descriptor*) {}
+
+ /**
+ * @return true to get elements of the compound value, false
+ * to skip over it
+ */
+ virtual bool onStartList(uint32_t /*count*/, const CharSequence& /*elements*/, const CharSequence& /*complete*/, const Descriptor*) { return true; }
+ virtual bool onStartMap(uint32_t /*count*/, const CharSequence& /*elements*/, const CharSequence& /*complete*/, const Descriptor*) { return true; }
+ virtual bool onStartArray(uint32_t /*count*/, const CharSequence&, const Constructor&, const Descriptor*) { return true; }
+ virtual void onEndList(uint32_t /*count*/, const Descriptor*) {}
+ virtual void onEndMap(uint32_t /*count*/, const Descriptor*) {}
+ virtual void onEndArray(uint32_t /*count*/, const Descriptor*) {}
+
+ virtual void onDescriptor(const Descriptor&, const char*) {}
+
+ virtual bool proceed() { return true; }
+ private:
+};
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_READER_H*/
diff --git a/qpid/cpp/src/qpid/amqp/Sasl.cpp b/qpid/cpp/src/qpid/amqp/Sasl.cpp
new file mode 100644
index 0000000000..a7c2eea35b
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/Sasl.cpp
@@ -0,0 +1,141 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/amqp/Sasl.h"
+#include "qpid/amqp/Decoder.h"
+#include "qpid/amqp/Descriptor.h"
+#include "qpid/amqp/Encoder.h"
+#include "qpid/log/Statement.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/framing/ProtocolVersion.h"
+#include "qpid/framing/ProtocolInitiation.h"
+#include <string.h>
+
+namespace qpid {
+namespace amqp {
+
+Sasl::Sasl(const std::string& i) : id(i), buffer(2*512/*AMQP 1.0's MAX_MIN_FRAME_SIZE - is this enough though?*/), encoder(&buffer[0], buffer.size()) {}
+Sasl::~Sasl() {}
+
+void* Sasl::startFrame()
+{
+ //write sasl frame header, leaving 4 bytes for total size
+ char* start = encoder.skip(4);
+ encoder.write((uint8_t) 0x02);//data offset
+ encoder.write((uint8_t) 0x01);//frame type
+ encoder.write((uint16_t) 0x0000);//ignored
+ return start;
+}
+
+void Sasl::endFrame(void* frame)
+{
+ //now backfill the frame size
+ char* start = (char*) frame;
+ char* current = &buffer[encoder.getPosition()];
+ uint32_t frameSize = current - start;
+ Encoder backfill(start, 4);
+ backfill.write(frameSize);
+ QPID_LOG(trace, "Completed encoding of frame of " << frameSize << " bytes");
+}
+
+
+std::size_t Sasl::read(const char* data, size_t available)
+{
+ size_t consumed = 0;
+ while (!stopReading() && available - consumed > 4/*framesize*/) {
+ Decoder decoder(data+consumed, available-consumed);
+ //read frame-header
+ uint32_t frameSize = decoder.readUInt();
+ if (frameSize > decoder.getSize()) break;//don't have all the data for this frame yet
+
+ QPID_LOG(trace, "Reading SASL frame of size " << frameSize);
+ decoder.resetSize(frameSize);
+ uint8_t dataOffset = decoder.readUByte();
+ uint8_t frameType = decoder.readUByte();
+ if (frameType != 0x01) {
+ QPID_LOG(error, "Expected SASL frame; got type " << frameType);
+ }
+ uint16_t ignored = decoder.readUShort();
+ if (ignored) {
+ QPID_LOG(info, "Got non null bytes at end of SASL frame header");
+ }
+
+ //body is at offset 4*dataOffset from the start
+ size_t skip = dataOffset*4 - 8;
+ if (skip) {
+ QPID_LOG(info, "Offset for sasl frame was not as expected");
+ decoder.advance(skip);
+ }
+ decoder.read(*this);
+ consumed += decoder.getPosition();
+ }
+ return consumed;
+}
+
+std::size_t Sasl::write(char* data, size_t size)
+{
+ size_t available = encoder.getPosition();
+ if (available) {
+ size_t encoded = available > size ? size : available;
+ ::memcpy(data, &buffer[0], encoded);
+ size_t remainder = encoder.getPosition() - encoded;
+ if (remainder) {
+ //shuffle
+ ::memcpy(&buffer[0], &buffer[size], remainder);
+ }
+ encoder.resetPosition(remainder);
+ return encoded;
+ } else {
+ return 0;
+ }
+}
+
+std::size_t Sasl::readProtocolHeader(const char* buffer, std::size_t size)
+{
+ framing::ProtocolInitiation pi(qpid::framing::ProtocolVersion(1,0,qpid::framing::ProtocolVersion::SASL));
+ if (size >= pi.encodedSize()) {
+ qpid::framing::Buffer out(const_cast<char*>(buffer), size);
+ pi.decode(out);
+ QPID_LOG_CAT(debug, protocol, id << " read protocol header: " << pi);
+ return pi.encodedSize();
+ } else {
+ return 0;
+ }
+}
+std::size_t Sasl::writeProtocolHeader(char* buffer, std::size_t size)
+{
+ framing::ProtocolInitiation pi(qpid::framing::ProtocolVersion(1,0,qpid::framing::ProtocolVersion::SASL));
+ if (size >= pi.encodedSize()) {
+ QPID_LOG_CAT(debug, protocol, id << " writing protocol header: " << pi);
+ qpid::framing::Buffer out(buffer, size);
+ pi.encode(out);
+ return pi.encodedSize();
+ } else {
+ QPID_LOG_CAT(warning, protocol, id << " insufficient buffer for protocol header: " << size)
+ return 0;
+ }
+}
+
+bool Sasl::stopReading()
+{
+ return false;
+}
+
+}} // namespace qpid::amqp
diff --git a/qpid/cpp/src/qpid/amqp/Sasl.h b/qpid/cpp/src/qpid/amqp/Sasl.h
new file mode 100644
index 0000000000..24a8de7dc4
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/Sasl.h
@@ -0,0 +1,55 @@
+#ifndef QPID_AMQP_SASL_H
+#define QPID_AMQP_SASL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/amqp/Encoder.h"
+#include "qpid/amqp/Reader.h"
+#include <string>
+#include <vector>
+
+namespace qpid {
+namespace amqp {
+
+/**
+ * Base for SASL client and server utilities
+ */
+class Sasl : protected Reader
+{
+ public:
+ QPID_COMMON_EXTERN Sasl(const std::string& id);
+ QPID_COMMON_EXTERN virtual ~Sasl();
+ QPID_COMMON_EXTERN std::size_t read(const char* data, size_t available);
+ QPID_COMMON_EXTERN std::size_t write(char* data, size_t available);
+ QPID_COMMON_EXTERN std::size_t readProtocolHeader(const char* buffer, std::size_t size);
+ QPID_COMMON_EXTERN std::size_t writeProtocolHeader(char* buffer, std::size_t size);
+ protected:
+ const std::string id;
+ std::vector<char> buffer;
+ Encoder encoder;
+
+ void* startFrame();
+ void endFrame(void*);
+ QPID_COMMON_EXTERN virtual bool stopReading();
+};
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_SASL_H*/
diff --git a/qpid/cpp/src/qpid/amqp/SaslClient.cpp b/qpid/cpp/src/qpid/amqp/SaslClient.cpp
new file mode 100644
index 0000000000..d8a38750c5
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/SaslClient.cpp
@@ -0,0 +1,154 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/amqp/SaslClient.h"
+#include "qpid/amqp/Decoder.h"
+#include "qpid/amqp/Descriptor.h"
+#include "qpid/amqp/descriptors.h"
+#include "qpid/amqp/Encoder.h"
+#include "qpid/log/Statement.h"
+
+using namespace qpid::amqp::sasl;
+
+namespace qpid {
+namespace amqp {
+
+SaslClient::SaslClient(const std::string& id) : Sasl(id) {}
+SaslClient::~SaslClient() {}
+void SaslClient::init(const std::string& mechanism, const std::string* response, const std::string* hostname)
+{
+ void* frame = startFrame();
+
+ void* token = encoder.startList32(&SASL_INIT);
+ encoder.writeSymbol(mechanism);
+ if (response) encoder.writeBinary(*response);
+ else encoder.writeNull();
+ if (hostname) encoder.writeString(*hostname);
+ else encoder.writeNull();
+ encoder.endList32(3, token);
+
+ endFrame(frame);
+ QPID_LOG_CAT(debug, protocol, id << " Sent SASL-INIT(" << mechanism << ", " << (response ? *response : "null") << ", " << (hostname ? *hostname : "null") << ")");
+}
+void SaslClient::response(const std::string* r)
+{
+ void* frame = startFrame();
+
+ void* token = encoder.startList32(&SASL_RESPONSE);
+ if (r) encoder.writeBinary(*r);
+ else encoder.writeNull();
+ encoder.endList32(1, token);
+
+ endFrame(frame);
+ QPID_LOG_CAT(debug, protocol, id << " Sent SASL-RESPONSE(" << (r ? *r : "null") << ")");
+}
+
+
+namespace {
+const std::string SPACE(" ");
+class SaslMechanismsReader : public Reader
+{
+ public:
+ SaslMechanismsReader(SaslClient& c) : client(c), expected(0) {}
+ void onSymbol(const CharSequence& mechanism, const Descriptor*)
+ {
+ if (expected) {
+ mechanisms << mechanism.str() << SPACE;
+ } else {
+ client.mechanisms(mechanism.str());
+ }
+ }
+ bool onStartArray(uint32_t count, const CharSequence&, const Constructor&, const Descriptor*)
+ {
+ expected = count;
+ return true;
+ }
+ void onEndArray(uint32_t, const Descriptor*)
+ {
+ client.mechanisms(mechanisms.str());
+ }
+ private:
+ SaslClient& client;
+ uint32_t expected;
+ std::stringstream mechanisms;
+};
+class SaslChallengeReader : public Reader
+{
+ public:
+ SaslChallengeReader(SaslClient& c) : client(c) {}
+ void onNull(const Descriptor*) { client.challenge(); }
+ void onBinary(const CharSequence& c, const Descriptor*) { client.challenge(c.str()); }
+ private:
+ SaslClient& client;
+};
+class SaslOutcomeReader : public Reader
+{
+ public:
+ SaslOutcomeReader(SaslClient& c, bool e) : client(c), expectExtraData(e) {}
+ void onUByte(uint8_t c, const Descriptor*)
+ {
+ if (expectExtraData) code = c;
+ else client.outcome(c);
+ }
+ void onBinary(const CharSequence& extra, const Descriptor*) { client.outcome(code, extra.str()); }
+ void onNull(const Descriptor*) { client.outcome(code); }
+ private:
+ SaslClient& client;
+ bool expectExtraData;
+ uint8_t code;
+};
+}
+
+bool SaslClient::onStartList(uint32_t count, const CharSequence& arguments, const CharSequence& /*full raw data*/, const Descriptor* descriptor)
+{
+ if (!descriptor) {
+ QPID_LOG(error, "Expected described type in SASL negotiation but got no descriptor");
+ } else if (descriptor->match(SASL_MECHANISMS_SYMBOL, SASL_MECHANISMS_CODE)) {
+ QPID_LOG(trace, "Reading SASL-MECHANISMS");
+ Decoder decoder(arguments.data, arguments.size);
+ if (count != 1) QPID_LOG(error, "Invalid SASL-MECHANISMS frame; exactly one field expected, got " << count);
+ SaslMechanismsReader reader(*this);
+ decoder.read(reader);
+ } else if (descriptor->match(SASL_CHALLENGE_SYMBOL, SASL_CHALLENGE_CODE)) {
+ QPID_LOG(trace, "Reading SASL-CHALLENGE");
+ Decoder decoder(arguments.data, arguments.size);
+ if (count != 1) QPID_LOG(error, "Invalid SASL-CHALLENGE frame; exactly one field expected, got " << count);
+ SaslChallengeReader reader(*this);
+ decoder.read(reader);
+ } else if (descriptor->match(SASL_OUTCOME_SYMBOL, SASL_OUTCOME_CODE)) {
+ QPID_LOG(trace, "Reading SASL-OUTCOME");
+ Decoder decoder(arguments.data, arguments.size);
+ if (count == 1) {
+ SaslOutcomeReader reader(*this, false);
+ decoder.read(reader);
+ } else if (count == 2) {
+ SaslOutcomeReader reader(*this, true);
+ decoder.read(reader);
+ } else {
+ QPID_LOG(error, "Invalid SASL-OUTCOME frame; got " << count << " fields");
+ }
+ } else {
+ QPID_LOG(error, "Unexpected descriptor in SASL negotiation: " << *descriptor);
+ }
+ return false;
+}
+
+
+}} // namespace qpid::amqp
diff --git a/qpid/cpp/src/qpid/amqp/SaslClient.h b/qpid/cpp/src/qpid/amqp/SaslClient.h
new file mode 100644
index 0000000000..d22887de1a
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/SaslClient.h
@@ -0,0 +1,55 @@
+#ifndef QPID_AMQP_SASLCLIENT_H
+#define QPID_AMQP_SASLCLIENT_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <qpid/CommonImportExport.h>
+#include "qpid/amqp/Sasl.h"
+
+namespace qpid {
+namespace amqp {
+
+/**
+ * Utility for decoding and encoding SASL frames by the peer acting as
+ * the SASL client.
+ */
+class SaslClient : public Sasl
+{
+ public:
+ QPID_COMMON_EXTERN SaslClient(const std::string& id);
+ QPID_COMMON_EXTERN virtual ~SaslClient();
+ QPID_COMMON_EXTERN virtual void mechanisms(const std::string&) = 0;
+ QPID_COMMON_EXTERN virtual void challenge(const std::string&) = 0;
+ QPID_COMMON_EXTERN virtual void challenge() = 0; //null != empty string
+ QPID_COMMON_EXTERN virtual void outcome(uint8_t result, const std::string&) = 0;
+ QPID_COMMON_EXTERN virtual void outcome(uint8_t result) = 0;
+
+ QPID_COMMON_EXTERN void init(const std::string& mechanism, const std::string* response, const std::string* hostname);
+ QPID_COMMON_EXTERN void response(const std::string*);
+
+ private:
+ QPID_COMMON_EXTERN bool onStartList(uint32_t count, const CharSequence& arguments, const CharSequence&, const Descriptor* descriptor);
+
+};
+
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_SASLCLIENT_H*/
diff --git a/qpid/cpp/src/qpid/amqp/SaslServer.cpp b/qpid/cpp/src/qpid/amqp/SaslServer.cpp
new file mode 100644
index 0000000000..250858bda0
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/SaslServer.cpp
@@ -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.
+ *
+ */
+#include "qpid/amqp/SaslServer.h"
+#include "qpid/amqp/Constructor.h"
+#include "qpid/amqp/Decoder.h"
+#include "qpid/amqp/Descriptor.h"
+#include "qpid/amqp/descriptors.h"
+#include "qpid/amqp/Encoder.h"
+#include "qpid/amqp/typecodes.h"
+#include "qpid/log/Statement.h"
+#include "qpid/StringUtils.h"
+#include <limits>
+#include <vector>
+
+using namespace qpid::amqp::sasl;
+using namespace qpid::amqp::typecodes;
+
+namespace qpid {
+namespace amqp {
+namespace {
+const std::string SPACE(" ");
+const std::string NULL_("NULL");
+}
+
+SaslServer::SaslServer(const std::string& id) : Sasl(id) {}
+SaslServer::~SaslServer() {}
+
+void SaslServer::mechanisms(const std::string& mechanisms)
+{
+ void* frameToken = startFrame();
+
+ std::vector<std::string> parts = split(mechanisms, SPACE);
+ void* listToken = encoder.startList32(&SASL_MECHANISMS);
+ if (parts.size() > 1) {
+ void* arrayToken = encoder.startArray8(Constructor(SYMBOL8));
+ for (std::vector<std::string>::const_iterator i = parts.begin();i != parts.end(); ++i) {
+ uint8_t size = i->size() > std::numeric_limits<uint8_t>::max() ? std::numeric_limits<uint8_t>::max() : i->size();
+ encoder.write(size);
+ encoder.writeBytes(i->data(), size);
+ }
+ encoder.endArray8(parts.size(), arrayToken);
+ } else {
+ encoder.writeSymbol(mechanisms);
+ }
+ encoder.endList32(1, listToken);
+
+ endFrame(frameToken);
+ QPID_LOG_CAT(debug, protocol, id << " Sent SASL-MECHANISMS(" << mechanisms << ") " << encoder.getPosition());
+}
+void SaslServer::challenge(const std::string* c)
+{
+ void* frameToken = startFrame();
+
+ void* listToken = encoder.startList32(&SASL_CHALLENGE);
+ if (c) encoder.writeBinary(*c);
+ else encoder.writeNull();
+ encoder.endList32(1, listToken);
+
+ endFrame(frameToken);
+ QPID_LOG_CAT(debug, protocol, id << " Sent SASL-CHALLENGE(" << (c ? *c : NULL_) << ") " << encoder.getPosition());
+}
+void SaslServer::completed(bool succeeded)
+{
+ void* frameToken = startFrame();
+
+ void* listToken = encoder.startList8(&SASL_OUTCOME);
+ encoder.writeUByte(succeeded ? 0 : 1);
+ encoder.endList8(1, listToken);
+
+ endFrame(frameToken);
+ QPID_LOG_CAT(debug, protocol, id << " Sent SASL-OUTCOME(" << (succeeded ? 0 : 1) << ") " << encoder.getPosition());
+}
+
+namespace {
+class SaslInitReader : public Reader
+{
+ public:
+ SaslInitReader(SaslServer& s, uint32_t e) : server(s), expected(e), hasResponse(false), index(0) {}
+ void onNull(const Descriptor*)
+ {
+ ++index;
+ if (index == 2) {
+ if (--expected == 0) {
+ server.init(mechanism, 0, 0);
+ }
+ } else if (index == 3) {
+ server.init(mechanism, hasResponse ? &response : 0, 0);
+ } else {
+ QPID_LOG(warning, "Unexpected sequence of fields for SASL-INIT: got null for field " << index);
+ }
+ }
+ void onBinary(const CharSequence& r, const Descriptor*)
+ {
+ if (++index != 2) QPID_LOG(warning, "Unexpected sequence of fields for SASL-INIT: got binary for field " << index);
+ response = r.str();
+ hasResponse = true;
+ if (--expected == 0) {
+ server.init(mechanism, &response, 0);
+ }
+ }
+ void onString(const CharSequence& h, const Descriptor*)
+ {
+ if (--expected || ++index != 3) {
+ QPID_LOG(warning, "Unexpected sequence of fields for SASL-INIT: got string for field " << index);
+ } else {
+ std::string hostname = h.str();
+ server.init(mechanism, hasResponse ? &response : 0, &hostname);
+ }
+ }
+ void onSymbol(const CharSequence& m, const Descriptor*)
+ {
+ if (++index != 1) QPID_LOG(warning, "Unexpected sequence of fields for SASL-INIT: got symbol for field " << index);
+ if (--expected) {
+ mechanism = m.str();
+ } else {
+ server.init(m.str(), 0, 0);
+ }
+ }
+ private:
+ SaslServer& server;
+ uint32_t expected;
+ std::string mechanism;
+ std::string response;
+ bool hasResponse;
+ uint32_t index;
+};
+
+class SaslResponseReader : public Reader
+{
+ public:
+ SaslResponseReader(SaslServer& s) : server(s) {}
+ void onNull(const Descriptor*) { server.response(0); }
+ void onBinary(const CharSequence& r, const Descriptor*)
+ {
+ std::string s = r.str();
+ server.response(&s);
+ }
+ private:
+ SaslServer& server;
+};
+}
+
+bool SaslServer::onStartList(uint32_t count, const CharSequence& arguments, const CharSequence& /*full raw data*/, const Descriptor* descriptor)
+{
+ if (!descriptor) {
+ QPID_LOG(error, "Expected described type in SASL negotiation but got no descriptor");
+ } else if (descriptor->match(SASL_INIT_SYMBOL, SASL_INIT_CODE)) {
+ QPID_LOG(trace, "Reading SASL-INIT");
+ Decoder decoder(arguments.data, arguments.size);
+ if (count < 1 || count > 3) QPID_LOG(error, "Invalid SASL-INIT frame; got " << count << " fields");
+ SaslInitReader reader(*this, count);
+ decoder.read(reader);
+ } else if (descriptor->match(SASL_RESPONSE_SYMBOL, SASL_RESPONSE_CODE)) {
+ QPID_LOG(trace, "Reading SASL-RESPONSE (" << std::string(arguments.data, arguments.size) << ") " << count << " elements");
+ Decoder decoder(arguments.data, arguments.size);
+ if (count != 1) QPID_LOG(error, "Invalid SASL-RESPONSE frame; exactly one field expected, got " << count);
+ SaslResponseReader reader(*this);
+ decoder.read(reader);
+ } else {
+ QPID_LOG(error, "Unexpected descriptor in SASL negotiation: " << *descriptor);
+ }
+ return false;
+}
+
+}} // namespace qpid::amqp
diff --git a/qpid/cpp/src/qpid/amqp/SaslServer.h b/qpid/cpp/src/qpid/amqp/SaslServer.h
new file mode 100644
index 0000000000..68d0854488
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/SaslServer.h
@@ -0,0 +1,50 @@
+#ifndef QPID_AMQP_SASLSERVER_H
+#define QPID_AMQP_SASLSERVER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/amqp/Sasl.h"
+
+namespace qpid {
+namespace amqp {
+
+/**
+ * Utility for decoding and encoding SASL frames by the peer acting as
+ * the SASL server.
+ */
+class SaslServer : public Sasl
+{
+ public:
+ QPID_COMMON_EXTERN SaslServer(const std::string& id);
+ QPID_COMMON_EXTERN virtual ~SaslServer();
+ virtual void init(const std::string& mechanism, const std::string* response, const std::string* hostname) = 0;
+ virtual void response(const std::string*) = 0;
+
+ QPID_COMMON_EXTERN void mechanisms(const std::string& mechanisms);
+ QPID_COMMON_EXTERN void challenge(const std::string*);
+ QPID_COMMON_EXTERN void completed(bool succeeded);
+
+ private:
+ QPID_COMMON_EXTERN bool onStartList(uint32_t count, const CharSequence& arguments, const CharSequence&, const Descriptor* descriptor);
+};
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_SASLSERVER_H*/
diff --git a/qpid/cpp/src/qpid/amqp/descriptors.h b/qpid/cpp/src/qpid/amqp/descriptors.h
new file mode 100644
index 0000000000..29c626edc2
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/descriptors.h
@@ -0,0 +1,140 @@
+#ifndef QPID_AMQP_DESCRIPTORS_H
+#define QPID_AMQP_DESCRIPTORS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "Descriptor.h"
+
+namespace qpid {
+namespace amqp {
+
+// NOTE: If you add descriptor symbols and codes here, you must also update the DescriptorMap
+// constructor in Descriptor.cpp.
+
+namespace message {
+const std::string HEADER_SYMBOL("amqp:header:list");
+const std::string PROPERTIES_SYMBOL("amqp:properties:list");
+const std::string DELIVERY_ANNOTATIONS_SYMBOL("amqp:delivery-annotations:map");
+const std::string MESSAGE_ANNOTATIONS_SYMBOL("amqp:message-annotations:map");
+const std::string APPLICATION_PROPERTIES_SYMBOL("amqp:application-properties:map");
+const std::string AMQP_SEQUENCE_SYMBOL("amqp:amqp-sequence:list");
+const std::string AMQP_VALUE_SYMBOL("amqp:amqp-value:*");
+const std::string DATA_SYMBOL("amqp:data:binary");
+const std::string FOOTER_SYMBOL("amqp:footer:map");
+const std::string ACCEPTED_SYMBOL("amqp:accepted:list");
+
+const uint64_t HEADER_CODE(0x70);
+const uint64_t DELIVERY_ANNOTATIONS_CODE(0x71);
+const uint64_t MESSAGE_ANNOTATIONS_CODE(0x72);
+const uint64_t PROPERTIES_CODE(0x73);
+const uint64_t APPLICATION_PROPERTIES_CODE(0x74);
+const uint64_t DATA_CODE(0x75);
+const uint64_t AMQP_SEQUENCE_CODE(0x76);
+const uint64_t AMQP_VALUE_CODE(0x77);
+const uint64_t FOOTER_CODE(0x78);
+const uint64_t ACCEPTED_CODE(0x24);
+
+const Descriptor HEADER(HEADER_CODE);
+const Descriptor DELIVERY_ANNOTATIONS(DELIVERY_ANNOTATIONS_CODE);
+const Descriptor MESSAGE_ANNOTATIONS(MESSAGE_ANNOTATIONS_CODE);
+const Descriptor PROPERTIES(PROPERTIES_CODE);
+const Descriptor APPLICATION_PROPERTIES(APPLICATION_PROPERTIES_CODE);
+const Descriptor AMQP_VALUE(AMQP_VALUE_CODE);
+const Descriptor DATA(DATA_CODE);
+}
+
+namespace sasl {
+const std::string SASL_MECHANISMS_SYMBOL("amqp:sasl-mechanisms:list");
+const std::string SASL_INIT_SYMBOL("amqp:sasl-init:list");
+const std::string SASL_CHALLENGE_SYMBOL("amqp:sasl-challenge:list");
+const std::string SASL_RESPONSE_SYMBOL("amqp:sasl-response:list");
+const std::string SASL_OUTCOME_SYMBOL("amqp:sasl-outcome:list");
+
+const uint64_t SASL_MECHANISMS_CODE(0x40);
+const uint64_t SASL_INIT_CODE(0x41);
+const uint64_t SASL_CHALLENGE_CODE(0x42);
+const uint64_t SASL_RESPONSE_CODE(0x43);
+const uint64_t SASL_OUTCOME_CODE(0x44);
+
+const Descriptor SASL_MECHANISMS(SASL_MECHANISMS_CODE);
+const Descriptor SASL_INIT(SASL_INIT_CODE);
+const Descriptor SASL_CHALLENGE(SASL_CHALLENGE_CODE);
+const Descriptor SASL_RESPONSE(SASL_RESPONSE_CODE);
+const Descriptor SASL_OUTCOME(SASL_OUTCOME_CODE);
+}
+
+namespace filters {
+const std::string LEGACY_DIRECT_FILTER_SYMBOL("apache.org:legacy-amqp-direct-binding:string");
+const std::string LEGACY_TOPIC_FILTER_SYMBOL("apache.org:legacy-amqp-topic-binding:string");
+const std::string LEGACY_HEADERS_FILTER_SYMBOL("apache.org:legacy-amqp-headers-binding:map");
+const std::string SELECTOR_FILTER_SYMBOL("apache.org:selector-filter:string");
+const std::string XQUERY_FILTER_SYMBOL("apache.org:xquery-filter:string");
+
+const uint64_t LEGACY_DIRECT_FILTER_CODE(0x0000468C00000000ULL);
+const uint64_t LEGACY_TOPIC_FILTER_CODE(0x0000468C00000001ULL);
+const uint64_t LEGACY_HEADERS_FILTER_CODE(0x0000468C00000002ULL);
+const uint64_t SELECTOR_FILTER_CODE(0x0000468C00000004ULL);
+const uint64_t XQUERY_FILTER_CODE(0x0000468C00000005ULL);
+}
+
+namespace lifetime_policy {
+const std::string DELETE_ON_CLOSE_SYMBOL("amqp:delete-on-close:list");
+const std::string DELETE_ON_NO_LINKS_SYMBOL("amqp:delete-on-no-links:list");
+const std::string DELETE_ON_NO_MESSAGES_SYMBOL("amqp:delete-on-no-messages:list");
+const std::string DELETE_ON_NO_LINKS_OR_MESSAGES_SYMBOL("amqp:delete-on-no-links-or-messages:list");
+
+const uint64_t DELETE_ON_CLOSE_CODE(0x2B);
+const uint64_t DELETE_ON_NO_LINKS_CODE(0x2C);
+const uint64_t DELETE_ON_NO_MESSAGES_CODE(0x2D);
+const uint64_t DELETE_ON_NO_LINKS_OR_MESSAGES_CODE(0x2E);
+}
+
+namespace transaction {
+const std::string DECLARE_SYMBOL("amqp:declare:list");
+const std::string DISCHARGE_SYMBOL("amqp:discharge:list");
+const std::string DECLARED_SYMBOL("amqp:declared:list");
+const std::string TRANSACTIONAL_STATE_SYMBOL("amqp:transactional-state:list");
+
+const uint64_t DECLARE_CODE(0x31);
+const uint64_t DISCHARGE_CODE(0x32);
+const uint64_t DECLARED_CODE(0x33);
+const uint64_t TRANSACTIONAL_STATE_CODE(0x34);
+}
+
+namespace error_conditions {
+//note these are not actually descriptors
+const std::string INTERNAL_ERROR("amqp:internal-error");
+const std::string NOT_FOUND("amqp:not-found");
+const std::string UNAUTHORIZED_ACCESS("amqp:unauthorized-access");
+const std::string DECODE_ERROR("amqp:decode-error");
+const std::string NOT_ALLOWED("amqp:not-allowed");
+const std::string NOT_IMPLEMENTED("amqp:not-implemented");
+const std::string RESOURCE_LIMIT_EXCEEDED("amqp:resource-limit-exceeded");
+const std::string RESOURCE_DELETED("amqp:resource-deleted");
+const std::string PRECONDITION_FAILED("amqp:precondition-failed");
+namespace transaction {
+const std::string UNKNOWN_ID("amqp:transaction:unknown-id");
+const std::string ROLLBACK("amqp:transaction:rollback");
+}
+}
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_DESCRIPTORS_H*/
diff --git a/qpid/cpp/src/qpid/amqp/typecodes.h b/qpid/cpp/src/qpid/amqp/typecodes.h
new file mode 100644
index 0000000000..915b75ca3f
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp/typecodes.h
@@ -0,0 +1,115 @@
+#ifndef QPID_AMQP_TYPECODES_H
+#define QPID_AMQP_TYPECODES_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+namespace qpid {
+namespace amqp {
+
+namespace typecodes
+{
+const uint8_t DESCRIPTOR(0x0);
+
+const uint8_t NULL_VALUE(0x40);
+
+const uint8_t BOOLEAN(0x56);
+const uint8_t BOOLEAN_TRUE(0x41);
+const uint8_t BOOLEAN_FALSE(0x42);
+
+const uint8_t UBYTE(0x50);
+const uint8_t USHORT(0x60);
+const uint8_t UINT(0x70);
+const uint8_t UINT_SMALL(0x52);
+const uint8_t UINT_ZERO(0x43);
+const uint8_t ULONG(0x80);
+const uint8_t ULONG_SMALL(0x53);
+const uint8_t ULONG_ZERO(0x44);
+
+const uint8_t BYTE(0x51);
+const uint8_t SHORT(0x61);
+const uint8_t INT(0x71);
+const uint8_t INT_SMALL(0x54);
+const uint8_t LONG(0x81);
+const uint8_t LONG_SMALL(0x55);
+
+const uint8_t FLOAT(0x72);
+const uint8_t DOUBLE(0x82);
+
+const uint8_t DECIMAL32(0x74);
+const uint8_t DECIMAL64(0x84);
+const uint8_t DECIMAL128(0x94);
+
+const uint8_t CHAR_UTF32(0x73);
+const uint8_t TIMESTAMP(0x83);
+const uint8_t UUID(0x98);
+
+const uint8_t BINARY8(0xa0);
+const uint8_t BINARY32(0xb0);
+const uint8_t STRING8(0xa1);
+const uint8_t STRING32(0xb1);
+const uint8_t SYMBOL8(0xa3);
+const uint8_t SYMBOL32(0xb3);
+
+typedef std::pair<uint8_t, uint8_t> CodePair;
+const CodePair SYMBOL(SYMBOL8, SYMBOL32);
+const CodePair STRING(STRING8, STRING32);
+const CodePair BINARY(BINARY8, BINARY32);
+
+const uint8_t LIST0(0x45);
+const uint8_t LIST8(0xc0);
+const uint8_t LIST32(0xd0);
+const uint8_t MAP8(0xc1);
+const uint8_t MAP32(0xd1);
+const uint8_t ARRAY8(0xe0);
+const uint8_t ARRAY32(0xf0);
+
+
+const std::string NULL_NAME("null");
+const std::string BOOLEAN_NAME("bool");
+
+const std::string UBYTE_NAME("ubyte");
+const std::string USHORT_NAME("ushort");
+const std::string UINT_NAME("uint");
+const std::string ULONG_NAME("ulong");
+
+const std::string BYTE_NAME("byte");
+const std::string SHORT_NAME("short");
+const std::string INT_NAME("int");
+const std::string LONG_NAME("long");
+
+const std::string FLOAT_NAME("float");
+const std::string DOUBLE_NAME("double");
+
+const std::string TIMESTAMP_NAME("timestamp");
+const std::string UUID_NAME("uuid");
+
+const std::string BINARY_NAME("binary");
+const std::string STRING_NAME("string");
+const std::string SYMBOL_NAME("symbol");
+
+const std::string LIST_NAME("list");
+const std::string MAP_NAME("map");
+const std::string ARRAY_NAME("array");
+}
+
+}} // namespace qpid::amqp
+
+#endif /*!QPID_AMQP_TYPECODES_H*/
diff --git a/qpid/cpp/src/qpid/amqp_0_10/Codecs.cpp b/qpid/cpp/src/qpid/amqp_0_10/Codecs.cpp
new file mode 100644
index 0000000000..49d152cc05
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp_0_10/Codecs.cpp
@@ -0,0 +1,586 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/amqp_0_10/Codecs.h"
+#include "qpid/amqp_0_10/CodecsInternal.h"
+#include "qpid/framing/Array.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/framing/List.h"
+#include "qpid/log/Statement.h"
+#include <algorithm>
+#include <functional>
+#include <limits>
+
+using namespace qpid::framing;
+using namespace qpid::types;
+
+namespace qpid {
+namespace amqp_0_10 {
+
+namespace {
+const std::string iso885915("iso-8859-15");
+const std::string utf8("utf8");
+const std::string utf16("utf16");
+const std::string binary("binary");
+const std::string amqp0_10_binary("amqp0-10:binary");
+const std::string amqp0_10_bit("amqp0-10:bit");
+const std::string amqp0_10_datetime("amqp0-10:datetime");
+const std::string amqp0_10_struct("amqp0-10:struct");
+}
+
+template <class T, class U, class F> void convert(const T& from, U& to, F f)
+{
+ std::transform(from.begin(), from.end(), std::inserter(to, to.begin()), f);
+}
+
+Variant::Map::value_type toVariantMapEntry(const FieldTable::value_type& in);
+
+template <class T, class U, class F> void translate(boost::shared_ptr<FieldValue> in, U& u, F f)
+{
+ T t;
+ getEncodedValue<T>(in, t);
+ convert(t, u, f);
+}
+
+void setEncodingFor(Variant& out, uint8_t code)
+{
+ switch(code){
+ case 0x80:
+ case 0x90:
+ case 0xa0:
+ out.setEncoding(amqp0_10_binary);
+ break;
+ case 0x84:
+ case 0x94:
+ out.setEncoding(iso885915);
+ break;
+ case 0x85:
+ case 0x95:
+ out.setEncoding(utf8);
+ break;
+ case 0x86:
+ case 0x96:
+ out.setEncoding(utf16);
+ break;
+ case 0xab:
+ out.setEncoding(amqp0_10_struct);
+ break;
+ default:
+ //do nothing
+ break;
+ }
+}
+
+qpid::types::Uuid getUuid(FieldValue& value)
+{
+ unsigned char data[16];
+ value.getFixedWidthValue<16>(data);
+ return qpid::types::Uuid(data);
+}
+
+Variant toVariant(boost::shared_ptr<FieldValue> in)
+{
+ Variant out;
+ //based on AMQP 0-10 typecode, pick most appropriate variant type
+ switch (in->getType()) {
+ //Fixed Width types:
+ case 0x00: out.setEncoding(amqp0_10_binary);
+ case 0x01: out = in->getIntegerValue<int8_t>(); break;
+ case 0x02: out = in->getIntegerValue<uint8_t>(); break;
+ case 0x04: break; //TODO: iso-8859-15 char
+ case 0x08: out = static_cast<bool>(in->getIntegerValue<uint8_t>()); break;
+ case 0x10: out.setEncoding(amqp0_10_binary);
+ case 0x11: out = in->getIntegerValue<int16_t, 2>(); break;
+ case 0x12: out = in->getIntegerValue<uint16_t, 2>(); break;
+ case 0x20: out.setEncoding(amqp0_10_binary);
+ case 0x21: out = in->getIntegerValue<int32_t, 4>(); break;
+ case 0x22: out = in->getIntegerValue<uint32_t, 4>(); break;
+ case 0x23: out = in->get<float>(); break;
+ case 0x27: break; //TODO: utf-32 char
+ case 0x30: out.setEncoding(amqp0_10_binary);
+ case 0x31: out = in->getIntegerValue<int64_t, 8>(); break;
+ case 0x38: out.setEncoding(amqp0_10_datetime); //treat datetime as uint64_t, but set encoding
+ case 0x32: out = in->getIntegerValue<uint64_t, 8>(); break;
+ case 0x33: out = in->get<double>(); break;
+
+ case 0x48: out = getUuid(*in); break;
+
+ //TODO: figure out whether and how to map values with codes 0x40-0xd8
+
+ case 0xf0: break;//void, which is the default value for Variant
+ case 0xf1: out.setEncoding(amqp0_10_bit); break;//treat 'bit' as void, which is the default value for Variant
+
+ //Variable Width types:
+ //strings:
+ case 0x80:
+ case 0x84:
+ case 0x85:
+ case 0x86:
+ case 0x90:
+ case 0x94:
+ case 0x95:
+ case 0x96:
+ case 0xa0:
+ case 0xab:
+ out = in->get<std::string>();
+ setEncodingFor(out, in->getType());
+ break;
+
+ case 0xa8:
+ out = Variant::Map();
+ translate<FieldTable>(in, out.asMap(), &toVariantMapEntry);
+ break;
+
+ case 0xa9:
+ out = Variant::List();
+ translate<List>(in, out.asList(), &toVariant);
+ break;
+ case 0xaa: //convert amqp0-10 array into variant list
+ out = Variant::List();
+ translate<Array>(in, out.asList(), &toVariant);
+ break;
+
+ default:
+ //error?
+ break;
+ }
+ return out;
+}
+
+Variant::Map::value_type toVariantMapEntry(const FieldTable::value_type& in)
+{
+ return Variant::Map::value_type(in.first, toVariant(in.second));
+}
+
+struct DecodeBuffer
+{
+ Buffer buffer;
+
+ DecodeBuffer(const std::string& s) : buffer(const_cast<char*>(s.data()), s.size()) {}
+
+ template <class T> void decode(T& t) { t.decode(buffer); }
+
+};
+
+template <class T, class U, class F> void _decode(const std::string& data, U& value, F f)
+{
+ T t;
+ DecodeBuffer buffer(data);
+ buffer.decode(t);
+ convert(t, value, f);
+}
+
+uint32_t encodedSize(const Variant& value)
+{
+ switch (value.getType()) {
+ case VAR_VOID:
+ return 0;
+ case VAR_BOOL:
+ case VAR_UINT8:
+ case VAR_INT8:
+ return 1;
+ case VAR_UINT16:
+ case VAR_INT16:
+ return 2;
+ break;
+ case VAR_UINT32:
+ case VAR_INT32:
+ case VAR_FLOAT:
+ return 4;
+ case VAR_UINT64:
+ case VAR_INT64:
+ case VAR_DOUBLE:
+ return 8;
+ case VAR_UUID:
+ return 16;
+ case VAR_MAP:
+ return encodedSize(value.asMap());
+ case VAR_LIST:
+ return encodedSize(value.asList());
+ case VAR_STRING:
+ return encodedSize(value.getString());
+ default:
+ throw Exception("Couldn't encode Variant: Illegal type code");
+ }
+}
+
+uint32_t encodedSize(const Variant::Map& values)
+{
+ uint32_t size = 4/*size field*/ + 4/*count field*/;
+ for(Variant::Map::const_iterator i = values.begin(); i != values.end(); ++i) {
+ size += 1/*size of key*/ + (i->first).size() + 1/*typecode*/ + encodedSize(i->second);
+ }
+ return size;
+}
+
+uint32_t encodedSize(const Variant::Map& values, const std::string& efield, const Variant& evalue)
+{
+ uint32_t size = 4/*size field*/ + 4/*count field*/;
+ for(Variant::Map::const_iterator i = values.begin(); i != values.end(); ++i) {
+ size += 1/*size of key*/ + (i->first).size() + 1/*typecode*/ + encodedSize(i->second);
+ }
+ size += 1/*size of key*/ + efield.size() + 1/*typecode*/ + encodedSize(evalue);
+ return size;
+}
+
+uint32_t encodedSize(const Variant::List& values)
+{
+ uint32_t size = 4/*size field*/ + 4/*count field*/;
+ for(Variant::List::const_iterator i = values.begin(); i != values.end(); ++i) {
+ size += 1/*typecode*/ + encodedSize(*i);
+ }
+ return size;
+}
+
+uint32_t encodedSize(const std::string& value)
+{
+ uint32_t size = value.size();
+ if (size > std::numeric_limits<uint16_t>::max()) {
+ return size + 4; /*Long size*/
+ } else {
+ return size + 2; /*Short size*/
+ }
+}
+
+void encode(const std::string& value, const std::string& encoding, qpid::framing::Buffer& buffer)
+{
+ uint32_t size = value.size();
+ if (size > std::numeric_limits<uint16_t>::max()) {
+ if (encoding == utf8 || encoding == utf16 || encoding == iso885915) {
+ throw Exception(QPID_MSG("Could not encode " << encoding << " character string - too long (" << size << " bytes)"));
+ } else {
+ buffer.putOctet(0xa0);
+ buffer.putLong(size);
+ buffer.putRawData(value);
+ }
+ } else {
+ if (encoding == utf8) {
+ buffer.putOctet(0x95);
+ } else if (encoding == utf16) {
+ buffer.putOctet(0x96);
+ } else if (encoding == iso885915) {
+ buffer.putOctet(0x94);
+ } else {
+ buffer.putOctet(0x90);
+ }
+ buffer.putShort(size);
+ buffer.putRawData(value);
+ }
+}
+
+void encode(const Variant& value, qpid::framing::Buffer& buffer)
+{
+ switch (value.getType()) {
+ case VAR_VOID:
+ buffer.putOctet(0xf0);
+ break;
+ case VAR_BOOL:
+ buffer.putOctet(0x08);
+ buffer.putOctet(value.asBool());
+ break;
+ case VAR_INT8:
+ buffer.putOctet(0x01);
+ buffer.putInt8(value.asInt8());
+ break;
+ case VAR_UINT8:
+ buffer.putOctet(0x02);
+ buffer.putOctet(value.asUint8());
+ break;
+ case VAR_INT16:
+ buffer.putOctet(0x11);
+ buffer.putInt16(value.asInt16());
+ break;
+ case VAR_UINT16:
+ buffer.putOctet(0x12);
+ buffer.putShort(value.asUint16());
+ break;
+ case VAR_INT32:
+ buffer.putOctet(0x21);
+ buffer.putInt32(value.asInt32());
+ break;
+ case VAR_UINT32:
+ buffer.putOctet(0x22);
+ buffer.putLong(value.asUint32());
+ break;
+ case VAR_FLOAT:
+ buffer.putOctet(0x23);
+ buffer.putFloat(value.asFloat());
+ break;
+ case VAR_INT64:
+ buffer.putOctet(0x31);
+ buffer.putInt64(value.asInt64());
+ break;
+ case VAR_UINT64:
+ buffer.putOctet(0x32);
+ buffer.putLongLong(value.asUint64());
+ break;
+ case VAR_DOUBLE:
+ buffer.putOctet(0x33);
+ buffer.putDouble(value.asDouble());
+ break;
+ case VAR_UUID:
+ buffer.putOctet(0x48);
+ buffer.putBin128(value.asUuid().data());
+ break;
+ case VAR_MAP:
+ buffer.putOctet(0xa8);
+ encode(value.asMap(), encodedSize(value.asMap()), buffer);
+ break;
+ case VAR_LIST:
+ buffer.putOctet(0xa9);
+ encode(value.asList(), encodedSize(value.asList()), buffer);
+ break;
+ case VAR_STRING:
+ encode(value.getString(), value.getEncoding(), buffer);
+ break;
+ }
+}
+
+void encode(const Variant::Map& map, uint32_t len, qpid::framing::Buffer& buffer)
+{
+ uint32_t s = buffer.getPosition();
+ buffer.putLong(len - 4);//exclusive of the size field itself
+ buffer.putLong(map.size());
+ for (Variant::Map::const_iterator i = map.begin(); i != map.end(); ++i) {
+ buffer.putShortString(i->first);
+ encode(i->second, buffer);
+ }
+ (void) s; assert(s + len == buffer.getPosition());
+}
+
+void encode(const Variant::Map& map, const std::string& efield, const Variant& evalue, uint32_t len, qpid::framing::Buffer& buffer)
+{
+ uint32_t s = buffer.getPosition();
+ buffer.putLong(len - 4);//exclusive of the size field itself
+ buffer.putLong(map.size() + 1 /* The extra field */ );
+ for (Variant::Map::const_iterator i = map.begin(); i != map.end(); ++i) {
+ buffer.putShortString(i->first);
+ encode(i->second, buffer);
+ }
+ buffer.putShortString(efield);
+ encode(evalue, buffer);
+
+ (void) s; assert(s + len == buffer.getPosition());
+}
+
+void encode(const Variant::List& list, uint32_t len, qpid::framing::Buffer& buffer)
+{
+ uint32_t s = buffer.getPosition();
+ buffer.putLong(len - 4);//exclusive of the size field itself
+ buffer.putLong(list.size());
+ for (Variant::List::const_iterator i = list.begin(); i != list.end(); ++i) {
+ encode(*i, buffer);
+ }
+ (void) s; assert(s + len == buffer.getPosition());
+}
+
+void decode(qpid::framing::Buffer&, Variant::Map&)
+{
+}
+
+
+void MapCodec::encode(const Variant::Map& value, std::string& data)
+{
+ uint32_t len = qpid::amqp_0_10::encodedSize(value);
+ std::vector<char> space(len);
+ qpid::framing::Buffer buff(&space[0], len);
+
+ qpid::amqp_0_10::encode(value, len, buff);
+ assert( len == buff.getPosition() );
+ data.assign(&space[0], len);
+}
+
+void MapCodec::decode(const std::string& data, Variant::Map& value)
+{
+ _decode<FieldTable>(data, value, &toVariantMapEntry);
+}
+
+size_t MapCodec::encodedSize(const Variant::Map& value)
+{
+ return qpid::amqp_0_10::encodedSize(value);
+}
+
+void ListCodec::encode(const Variant::List& value, std::string& data)
+{
+ uint32_t len = qpid::amqp_0_10::encodedSize(value);
+ std::vector<char> space(len);
+ qpid::framing::Buffer buff(&space[0], len);
+
+ qpid::amqp_0_10::encode(value, len, buff);
+ assert( len == buff.getPosition() );
+ data.assign(&space[0], len);
+}
+
+void ListCodec::decode(const std::string& data, Variant::List& value)
+{
+ _decode<List>(data, value, &toVariant);
+}
+
+size_t ListCodec::encodedSize(const Variant::List& value)
+{
+ return qpid::amqp_0_10::encodedSize(value);
+}
+
+void translate(const Variant::Map& from, FieldTable& to)
+{
+ // Create buffer of correct size to encode Variant::Map
+ uint32_t len = encodedSize(from);
+ std::vector<char> space(len);
+ qpid::framing::Buffer buff(&space[0], len);
+
+ // Encode Variant::Map into buffer directly -
+ // We pass the already calculated length in to avoid
+ // recalculating it.
+ encode(from, len, buff);
+
+ // Give buffer to FieldTable
+ // Could speed this up a bit by avoiding copying
+ // the buffer we just created into the FieldTable
+ assert( len == buff.getPosition() );
+ buff.reset();
+ to.decode(buff);
+}
+
+void translate(const Variant::Map& from, const std::string& efield, const Variant& evalue, FieldTable& to)
+{
+ // Create buffer of correct size to encode Variant::Map
+ uint32_t len = encodedSize(from, efield, evalue);
+ std::vector<char> space(len);
+ qpid::framing::Buffer buff(&space[0], len);
+
+ // Encode Variant::Map into buffer directly -
+ // We pass the already calculated length in to avoid
+ // recalculating it.
+ encode(from, efield, evalue, len, buff);
+
+ // Give buffer to FieldTable
+ // Could speed this up a bit by avoiding copying
+ // the buffer we just created into the FieldTable
+ assert( len == buff.getPosition() );
+ buff.reset();
+ to.decode(buff);
+}
+
+void translate(const FieldTable& from, Variant::Map& to)
+{
+ convert(from, to, &toVariantMapEntry);
+}
+
+namespace {
+boost::shared_ptr<FieldValue> convertString(const std::string& value, const std::string& encoding);
+FieldTableValue* toFieldTableValue(const Variant::Map& map);
+ListValue* toListValue(const Variant::List& list);
+
+boost::shared_ptr<FieldValue> toFieldValue(const Variant& in)
+{
+ boost::shared_ptr<FieldValue> out;
+ switch (in.getType()) {
+ case VAR_VOID: out = boost::shared_ptr<FieldValue>(new VoidValue()); break;
+ case VAR_BOOL: out = boost::shared_ptr<FieldValue>(new BoolValue(in.asBool())); break;
+ case VAR_UINT8: out = boost::shared_ptr<FieldValue>(new Unsigned8Value(in.asUint8())); break;
+ case VAR_UINT16: out = boost::shared_ptr<FieldValue>(new Unsigned16Value(in.asUint16())); break;
+ case VAR_UINT32: out = boost::shared_ptr<FieldValue>(new Unsigned32Value(in.asUint32())); break;
+ case VAR_UINT64: out = boost::shared_ptr<FieldValue>(new Unsigned64Value(in.asUint64())); break;
+ case VAR_INT8: out = boost::shared_ptr<FieldValue>(new Integer8Value(in.asInt8())); break;
+ case VAR_INT16: out = boost::shared_ptr<FieldValue>(new Integer16Value(in.asInt16())); break;
+ case VAR_INT32: out = boost::shared_ptr<FieldValue>(new Integer32Value(in.asInt32())); break;
+ case VAR_INT64: out = boost::shared_ptr<FieldValue>(new Integer64Value(in.asInt64())); break;
+ case VAR_FLOAT: out = boost::shared_ptr<FieldValue>(new FloatValue(in.asFloat())); break;
+ case VAR_DOUBLE: out = boost::shared_ptr<FieldValue>(new DoubleValue(in.asDouble())); break;
+ case VAR_STRING: out = convertString(in.asString(), in.getEncoding()); break;
+ case VAR_UUID: out = boost::shared_ptr<FieldValue>(new UuidValue(in.asUuid().data())); break;
+ case VAR_MAP:
+ out = boost::shared_ptr<FieldValue>(toFieldTableValue(in.asMap()));
+ break;
+ case VAR_LIST:
+ out = boost::shared_ptr<FieldValue>(toListValue(in.asList()));
+ }
+ return out;
+}
+
+boost::shared_ptr<FieldValue> convertString(const std::string& value, const std::string& encoding)
+{
+ bool large = value.size() > std::numeric_limits<uint16_t>::max();
+ if (encoding.empty() || encoding == amqp0_10_binary || encoding == binary) {
+ if (large) {
+ return boost::shared_ptr<FieldValue>(new Var32Value(value, 0xa0));
+ } else {
+ return boost::shared_ptr<FieldValue>(new Var16Value(value, 0x90));
+ }
+ } else if (encoding == utf8) {
+ if (!large)
+ return boost::shared_ptr<FieldValue>(new Str16Value(value));
+ throw Exception(QPID_MSG("Could not encode utf8 character string - too long (" << value.size() << " bytes)"));
+ } else if (encoding == utf16) {
+ if (!large)
+ return boost::shared_ptr<FieldValue>(new Var16Value(value, 0x96));
+ throw Exception(QPID_MSG("Could not encode utf16 character string - too long (" << value.size() << " bytes)"));
+ } else if (encoding == iso885915) {
+ if (!large)
+ return boost::shared_ptr<FieldValue>(new Var16Value(value, 0x94));
+ throw Exception(QPID_MSG("Could not encode iso-8859-15 character string - too long (" << value.size() << " bytes)"));
+ } else {
+ // the encoding was not recognised
+ QPID_LOG(warning, "Unknown byte encoding: [" << encoding << "], encoding as vbin32.");
+ return boost::shared_ptr<FieldValue>(new Var32Value(value, 0xa0));
+ }
+}
+
+FieldTable::value_type toFieldTableEntry(const Variant::Map::value_type& in)
+{
+ return FieldTable::value_type(in.first, toFieldValue(in.second));
+}
+
+FieldTableValue* toFieldTableValue(const Variant::Map& map)
+{
+ FieldTable ft;
+ convert(map, ft, &toFieldTableEntry);
+ return new FieldTableValue(ft);
+}
+
+ListValue* toListValue(const Variant::List& list)
+{
+ List l;
+ convert(list, l, &toFieldValue);
+ return new ListValue(l);
+}
+}
+
+void translate(const types::Variant& from, boost::shared_ptr<framing::FieldValue> to)
+{
+ to = toFieldValue(from);
+}
+
+void translate(const boost::shared_ptr<FieldValue> from, Variant& to)
+{
+ to = toVariant(from);
+}
+
+boost::shared_ptr<framing::FieldValue> translate(const types::Variant& from)
+{
+ return toFieldValue(from);
+}
+
+const std::string ListCodec::contentType("amqp/list");
+const std::string MapCodec::contentType("amqp/map");
+
+}} // namespace qpid::amqp_0_10
diff --git a/qpid/cpp/src/qpid/amqp_0_10/Codecs.h b/qpid/cpp/src/qpid/amqp_0_10/Codecs.h
new file mode 100644
index 0000000000..79d76bcc4b
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp_0_10/Codecs.h
@@ -0,0 +1,87 @@
+#ifndef QPID_AMQP_0_10_CODECS_H
+#define QPID_AMQP_0_10_CODECS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/CommonImportExport.h"
+#include "qpid/types/Variant.h"
+#include "boost/shared_ptr.hpp"
+
+namespace qpid {
+namespace framing {
+class FieldTable;
+class FieldValue;
+}
+namespace amqp_0_10 {
+/**
+ * Codec for encoding/decoding a map of Variants using the AMQP 0-10
+ * map encoding.
+ */
+class QPID_COMMON_CLASS_EXTERN MapCodec
+{
+ public:
+ typedef qpid::types::Variant::Map ObjectType;
+ static void QPID_COMMON_EXTERN encode(const ObjectType&, std::string&);
+ static void QPID_COMMON_EXTERN decode(const std::string&, ObjectType&);
+ static size_t QPID_COMMON_EXTERN encodedSize(const ObjectType&);
+ static const QPID_COMMON_EXTERN std::string contentType;
+ private:
+};
+
+/**
+ * Codec for encoding/decoding a list of Variants using the AMQP 0-10
+ * list encoding.
+ */
+class QPID_COMMON_CLASS_EXTERN ListCodec
+{
+ public:
+ typedef qpid::types::Variant::List ObjectType;
+ static void QPID_COMMON_EXTERN encode(const ObjectType&, std::string&);
+ static void QPID_COMMON_EXTERN decode(const std::string&, ObjectType&);
+ static size_t QPID_COMMON_EXTERN encodedSize(const ObjectType&);
+ static const QPID_COMMON_EXTERN std::string contentType;
+ private:
+};
+
+/**
+ * @internal
+ *
+ * Conversion functions between qpid::types:Variant::Map and the
+ * deprecated qpid::framing::FieldTable.
+ *
+ */
+QPID_COMMON_EXTERN void translate(const qpid::types::Variant::Map& from,
+ qpid::framing::FieldTable& to);
+QPID_COMMON_EXTERN void translate(const qpid::types::Variant::Map& from, const std::string& efield, const qpid::types::Variant& evalue,
+ qpid::framing::FieldTable& to);
+QPID_COMMON_EXTERN void translate(const qpid::framing::FieldTable& from,
+ qpid::types::Variant::Map& to);
+
+QPID_COMMON_EXTERN void translate(const boost::shared_ptr<qpid::framing::FieldValue> from,
+ qpid::types::Variant& to);
+QPID_COMMON_EXTERN void translate(const types::Variant& from,
+ boost::shared_ptr<qpid::framing::FieldValue> to);
+QPID_COMMON_EXTERN boost::shared_ptr<qpid::framing::FieldValue> translate(const types::Variant& from);
+
+}} // namespace qpid::amqp_0_10
+
+#endif /*!QPID_AMQP_0_10_CODECS_H*/
diff --git a/qpid/cpp/src/qpid/amqp_0_10/CodecsInternal.h b/qpid/cpp/src/qpid/amqp_0_10/CodecsInternal.h
new file mode 100644
index 0000000000..cf9a7d0447
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp_0_10/CodecsInternal.h
@@ -0,0 +1,42 @@
+#ifndef QPID_AMQP_0_10_CODECSINTERNAL_H
+#define QPID_AMQP_0_10_CODECSINTERNAL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/CommonImportExport.h"
+#include "qpid/types/Variant.h"
+
+namespace qpid {
+namespace framing {
+class Buffer;
+}
+namespace amqp_0_10 {
+QPID_COMMON_EXTERN void encode(const qpid::types::Variant::Map& map, uint32_t len, qpid::framing::Buffer& buffer);
+QPID_COMMON_EXTERN void encode(const qpid::types::Variant::List& list, uint32_t len, qpid::framing::Buffer& buffer);
+QPID_COMMON_EXTERN void encode(const qpid::types::Variant& value, qpid::framing::Buffer& buffer);
+QPID_COMMON_EXTERN void encode(const std::string& value, const std::string& encoding, qpid::framing::Buffer& buffer);
+QPID_COMMON_EXTERN uint32_t encodedSize(const qpid::types::Variant::Map& values);
+QPID_COMMON_EXTERN uint32_t encodedSize(const qpid::types::Variant::List& values);
+QPID_COMMON_EXTERN uint32_t encodedSize(const std::string& value);
+
+}} // namespace qpid::amqp_0_10
+
+#endif /*!QPID_AMQP_0_10_CODECSINTERNAL_H*/
diff --git a/qpid/cpp/src/qpid/amqp_0_10/Connection.cpp b/qpid/cpp/src/qpid/amqp_0_10/Connection.cpp
new file mode 100644
index 0000000000..866f98071b
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp_0_10/Connection.cpp
@@ -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.
+ *
+ */
+#include "qpid/amqp_0_10/Connection.h"
+#include "qpid/log/Statement.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/framing/ProtocolInitiation.h"
+
+namespace qpid {
+namespace amqp_0_10 {
+
+using framing::InternalErrorException;
+using sys::Mutex;
+
+Connection::Connection(sys::OutputControl& o, const std::string& id, bool _isClient)
+ : pushClosed(false), popClosed(false), output(o), identifier(id), initialized(false),
+ isClient(_isClient), buffered(0), version(0,10)
+{}
+
+void Connection::setInputHandler(std::auto_ptr<sys::ConnectionInputHandler> c) {
+ connection = c;
+}
+
+size_t Connection::decode(const char* buffer, size_t size) {
+ framing::Buffer in(const_cast<char*>(buffer), size);
+ if (isClient && !initialized) {
+ //read in protocol header
+ framing::ProtocolInitiation pi;
+ if (pi.decode(in)) {
+ if(!(pi==version))
+ throw Exception(QPID_MSG("Unsupported version: " << pi
+ << " supported version " << version));
+ QPID_LOG(trace, "RECV [" << identifier << "]: INIT(" << pi << ")");
+ initialized = true;
+ }
+ }
+ framing::AMQFrame frame;
+ while(!pushClosed && frame.decode(in)) {
+ QPID_LOG(trace, "RECV [" << identifier << "]: " << frame);
+ connection->received(frame);
+ }
+ return in.getPosition();
+}
+
+bool Connection::canEncode() {
+ Mutex::ScopedLock l(frameQueueLock);
+ if (!popClosed) {
+ Mutex::ScopedUnlock u(frameQueueLock);
+ connection->doOutput();
+ }
+ return !popClosed && ((!isClient && !initialized) || !frameQueue.empty());
+}
+
+bool Connection::isClosed() const {
+ Mutex::ScopedLock l(frameQueueLock);
+ return pushClosed && popClosed;
+}
+
+size_t Connection::encode(char* buffer, size_t size) {
+ { // Swap frameQueue data into workQueue to avoid holding lock while we encode.
+ Mutex::ScopedLock l(frameQueueLock);
+ if (popClosed) return 0; // Can't pop any more frames.
+ assert(workQueue.empty());
+ workQueue.swap(frameQueue);
+ }
+ framing::Buffer out(buffer, size);
+ if (!isClient && !initialized) {
+ framing::ProtocolInitiation pi(getVersion());
+ pi.encode(out);
+ initialized = true;
+ QPID_LOG(trace, "SENT [" << identifier << "]: INIT(" << pi << ")");
+ }
+ size_t frameSize=0;
+ size_t encoded=0;
+ while (!workQueue.empty() && ((frameSize=workQueue.front().encodedSize()) <= out.available())) {
+ workQueue.front().encode(out);
+ QPID_LOG(trace, "SENT [" << identifier << "]: " << workQueue.front());
+ workQueue.pop_front();
+ encoded += frameSize;
+ if (workQueue.empty() && out.available() > 0) {
+ // try to get more output
+ connection->doOutput();
+ Mutex::ScopedLock l(frameQueueLock);
+ workQueue.swap(frameQueue); // Need to get any new frames into the work queue
+ }
+ }
+ assert(workQueue.empty() || workQueue.front().encodedSize() <= size);
+ if (!workQueue.empty() && workQueue.front().encodedSize() > size)
+ throw InternalErrorException(QPID_MSG("Frame too large for buffer."));
+ {
+ Mutex::ScopedLock l(frameQueueLock);
+ buffered -= encoded;
+ // Put back any frames we did not encode.
+ frameQueue.insert(frameQueue.begin(), workQueue.begin(), workQueue.end());
+ workQueue.clear();
+ if (frameQueue.empty() && pushClosed)
+ popClosed = true;
+ }
+ return out.getPosition();
+}
+
+void Connection::abort() { output.abort(); }
+void Connection::connectionEstablished() { output.connectionEstablished(); }
+void Connection::activateOutput() { output.activateOutput(); }
+
+void Connection::close() {
+ // No more frames can be pushed onto the queue.
+ // Frames aleady on the queue can be popped.
+ Mutex::ScopedLock l(frameQueueLock);
+ pushClosed = true;
+}
+
+void Connection::closed() {
+ connection->closed();
+}
+
+void Connection::handle(framing::AMQFrame& f) {
+ {
+ Mutex::ScopedLock l(frameQueueLock);
+ if (!pushClosed)
+ frameQueue.push_back(f);
+ buffered += f.encodedSize();
+ }
+ activateOutput();
+}
+
+framing::ProtocolVersion Connection::getVersion() const {
+ return version;
+}
+
+}} // namespace qpid::amqp_0_10
diff --git a/qpid/cpp/src/qpid/amqp_0_10/Connection.h b/qpid/cpp/src/qpid/amqp_0_10/Connection.h
new file mode 100644
index 0000000000..5f58df307d
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp_0_10/Connection.h
@@ -0,0 +1,78 @@
+#ifndef QPID_AMQP_0_10_CONNECTION_H
+#define QPID_AMQP_0_10_CONNECTION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/sys/ConnectionCodec.h"
+#include "qpid/sys/ConnectionInputHandler.h"
+#include "qpid/sys/ConnectionOutputHandler.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/broker/BrokerImportExport.h"
+#include <boost/intrusive_ptr.hpp>
+#include <memory>
+#include <deque>
+
+namespace qpid {
+
+namespace sys {
+class ConnectionInputHandlerFactory;
+}
+
+namespace amqp_0_10 {
+
+class Connection : public sys::ConnectionCodec,
+ public sys::ConnectionOutputHandler
+{
+ typedef std::deque<framing::AMQFrame> FrameQueue;
+
+ FrameQueue frameQueue;
+ FrameQueue workQueue;
+ bool pushClosed, popClosed;
+ mutable sys::Mutex frameQueueLock;
+ sys::OutputControl& output;
+ std::auto_ptr<sys::ConnectionInputHandler> connection;
+ std::string identifier;
+ bool initialized;
+ bool isClient;
+ size_t buffered;
+ framing::ProtocolVersion version;
+
+ public:
+ QPID_BROKER_EXTERN Connection(sys::OutputControl&, const std::string& id, bool isClient);
+ QPID_BROKER_EXTERN void setInputHandler(std::auto_ptr<sys::ConnectionInputHandler> c);
+ size_t decode(const char* buffer, size_t size);
+ size_t encode(char* buffer, size_t size);
+ bool isClosed() const;
+ bool canEncode();
+ void abort();
+ void connectionEstablished();
+ void activateOutput();
+ void closed(); // connection closed by peer.
+ void close(); // closing from this end.
+ void handle(framing::AMQFrame&);
+ framing::ProtocolVersion getVersion() const;
+};
+
+}} // namespace qpid::amqp_0_10
+
+#endif /*!QPID_AMQP_0_10_CONNECTION_H*/
diff --git a/qpid/cpp/src/qpid/amqp_0_10/SessionHandler.cpp b/qpid/cpp/src/qpid/amqp_0_10/SessionHandler.cpp
new file mode 100644
index 0000000000..bd0dcbfc85
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp_0_10/SessionHandler.cpp
@@ -0,0 +1,333 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+
+#include "qpid/amqp_0_10/SessionHandler.h"
+#include "qpid/SessionState.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/framing/AllInvoker.h"
+#include "qpid/framing/enum.h"
+#include "qpid/log/Statement.h"
+
+
+#include <boost/bind.hpp>
+
+namespace qpid {
+namespace amqp_0_10 {
+using namespace framing;
+using namespace std;
+
+void SessionHandler::checkAttached() {
+ if (!getState())
+ throw NotAttachedException(QPID_MSG("Channel " << channel.get() << " is not attached"));
+}
+
+SessionHandler::SessionHandler(FrameHandler* out, ChannelId ch)
+ : channel(ch, out), peer(channel),
+ awaitingDetached(false),
+ sendReady(), receiveReady() {}
+
+SessionHandler::~SessionHandler() {}
+
+namespace {
+bool isSessionControl(AMQMethodBody* m) {
+ return m && m->amqpClassId() == SESSION_CLASS_ID;
+}
+
+session::DetachCode convert(uint8_t code) {
+ switch(code) {
+ case 0: return session::DETACH_CODE_NORMAL;
+ case 1: return session::DETACH_CODE_SESSION_BUSY;
+ case 2: return session::DETACH_CODE_TRANSPORT_BUSY;
+ case 3: return session::DETACH_CODE_NOT_ATTACHED;
+ case 4: default: return session::DETACH_CODE_UNKNOWN_IDS;
+ }
+}
+
+} // namespace
+
+void SessionHandler::invoke(const AMQMethodBody& m) {
+ framing::invoke(*this, m);
+}
+
+void SessionHandler::handleIn(AMQFrame& f) {
+ // Note on channel states: a channel is attached if session != 0
+ AMQMethodBody* m = f.getBody()->getMethod();
+ try {
+ // Ignore all but detach controls while awaiting detach
+ if (awaitingDetached) {
+ if (!isSessionControl(m)) return;
+ if (m->amqpMethodId() != SESSION_DETACH_METHOD_ID &&
+ m->amqpMethodId() != SESSION_DETACHED_METHOD_ID)
+ return;
+ }
+ if (isSessionControl(m)) {
+ invoke(*m);
+ }
+ else {
+ // Drop frames if we are detached.
+ if (!getState()) return;
+ if (!receiveReady)
+ throw IllegalStateException(QPID_MSG(getState()->getId() << ": Not ready to receive data"));
+ if (!getState()->receiverRecord(f))
+ return; // Ignore duplicates.
+ if (getState()->receiverNeedKnownCompleted())
+ sendCompletion();
+ getInHandler()->handle(f);
+ }
+ }
+ catch(const SessionException& e) {
+ executionException(e.code, e.what());
+ framing::AMQP_AllProxy::Execution execution(channel);
+ AMQMethodBody* m = f.getMethod();
+ SequenceNumber commandId;
+ if (getState()) commandId = getState()->receiverGetCurrent();
+ execution.exception(e.code, commandId, m ? m->amqpClassId() : 0, m ? m->amqpMethodId() : 0, 0, e.what(), FieldTable());
+ detaching();
+ sendDetach();
+ }
+ catch(const ChannelException& e){
+ channelException(e.code, e.what());
+ peer.detached(name, e.code);
+ }
+ catch(const ConnectionException& e) {
+ connectionException(e.code, e.getMessage());
+ }
+ catch(const std::exception& e) {
+ connectionException(connection::CLOSE_CODE_FRAMING_ERROR, e.what());
+ }
+}
+
+void SessionHandler::handleException(const qpid::SessionException& e)
+{
+ QPID_LOG(error, "Execution exception (during output): " << e.what());
+ executionException(e.code, e.what()); // Let subclass handle this first.
+ framing::AMQP_AllProxy::Execution execution(channel);
+ execution.exception(e.code, 0, 0, 0, 0, e.what(), FieldTable());
+ detaching();
+ sendDetach();
+}
+
+namespace {
+bool isCommand(const AMQFrame& f) {
+ return f.getMethod() && f.getMethod()->type() == framing::SEGMENT_TYPE_COMMAND;
+}
+} // namespace
+
+void SessionHandler::handleOut(AMQFrame& f) {
+ checkAttached();
+ if (!sendReady)
+ throw IllegalStateException(QPID_MSG(getState()->getId() << ": Not ready to send data"));
+ getState()->senderRecord(f);
+ if (isCommand(f) && getState()->senderNeedFlush()) {
+ peer.flush(false, false, true);
+ getState()->senderRecordFlush();
+ }
+ channel.handle(f);
+}
+
+void SessionHandler::attach(const std::string& name_, bool force) {
+ // Save the name for possible session-busy exception. Session-busy
+ // can be thrown before we have attached the handler to a valid
+ // SessionState, and in that case we need the name to send peer.detached
+ name = name_;
+ if (getState() && name == getState()->getId().getName())
+ return; // Idempotent
+ if (getState())
+ throw TransportBusyException(
+ QPID_MSG("Channel " << channel.get() << " already attached to " << getState()->getId()));
+ setState(name, force);
+ QPID_LOG(debug, "Attached channel " << channel.get() << " to " << getState()->getId());
+ peer.attached(name);
+ if (getState()->hasState())
+ peer.flush(true, true, true);
+ else
+ sendCommandPoint(getState()->senderGetCommandPoint());
+}
+
+#define CHECK_NAME(NAME, MSG) do { \
+ checkAttached(); \
+ if (NAME != getState()->getId().getName()) \
+ throw InvalidArgumentException( \
+ QPID_MSG(MSG << ": incorrect session name: " << NAME \
+ << ", expecting: " << getState()->getId().getName())); \
+ } while(0)
+
+
+void SessionHandler::attached(const std::string& name) {
+ CHECK_NAME(name, "session.attached");
+}
+
+void SessionHandler::detach(const std::string& name) {
+ CHECK_NAME(name, "session.detach");
+ peer.detached(name, session::DETACH_CODE_NORMAL);
+ handleDetach();
+}
+
+void SessionHandler::detached(const std::string& /*name*/, uint8_t code) {
+ awaitingDetached = false;
+ // Special case for detached: Don't throw if we are not attached. Doing so
+ // can lead to an endless game of "detached tennis" on federated brokers.
+ if (!getState()) return; // Already detached.
+ if (code != session::DETACH_CODE_NORMAL) {
+ sendReady = receiveReady = false;
+ channelException(convert(code), Msg() << "Channel " << channel.get()
+ << " received session.detached from peer");
+ } else {
+ handleDetach();
+ }
+}
+
+void SessionHandler::handleDetach() {
+ sendReady = receiveReady = false;
+}
+
+void SessionHandler::requestTimeout(uint32_t t) {
+ checkAttached();
+ getState()->setTimeout(t);
+ peer.timeout(getState()->getTimeout());
+}
+
+void SessionHandler::timeout(uint32_t t) {
+ checkAttached();
+ getState()->setTimeout(t);
+}
+
+void SessionHandler::commandPoint(const SequenceNumber& id, uint64_t offset) {
+ checkAttached();
+ getState()->receiverSetCommandPoint(SessionPoint(id, offset));
+ if (!receiveReady) {
+ receiveReady = true;
+ readyToReceive();
+ }
+}
+
+void SessionHandler::expected(const SequenceSet& commands, const Array& /*fragments*/) {
+ checkAttached();
+ if (getState()->hasState()) { // Replay
+ if (commands.empty()) throw IllegalStateException(
+ QPID_MSG(getState()->getId() << ": has state but client is attaching as new session."));
+ // TODO aconway 2008-05-12: support replay of partial commands.
+ // Here we always round down to the last command boundary.
+ SessionPoint expectedPoint = commands.empty() ? SequenceNumber(0) : SessionPoint(commands.front(),0);
+ SessionState::ReplayRange replay = getState()->senderExpected(expectedPoint);
+ sendCommandPoint(expectedPoint);
+ std::for_each(replay.begin(), replay.end(), out); // replay
+ }
+ else
+ sendCommandPoint(getState()->senderGetCommandPoint());
+}
+
+void SessionHandler::confirmed(const SequenceSet& commands, const Array& /*fragments*/) {
+ checkAttached();
+ // Ignore non-contiguous confirmations.
+ if (!commands.empty() && commands.front() >= getState()->senderGetReplayPoint())
+ getState()->senderConfirmed(commands.rangesBegin()->last());
+}
+
+void SessionHandler::completed(const SequenceSet& commands, bool timelyReply) {
+ checkAttached();
+ getState()->senderCompleted(commands);
+ if (getState()->senderNeedKnownCompleted() || timelyReply) {
+ peer.knownCompleted(commands);
+ getState()->senderRecordKnownCompleted();
+ }
+}
+
+void SessionHandler::knownCompleted(const SequenceSet& commands) {
+ checkAttached();
+ getState()->receiverKnownCompleted(commands);
+}
+
+void SessionHandler::flush(bool expected, bool confirmed, bool completed) {
+ checkAttached();
+ if (expected) {
+ SequenceSet expectSet;
+ if (getState()->hasState())
+ expectSet.add(getState()->receiverGetExpected().command);
+ peer.expected(expectSet, Array());
+ }
+ if (confirmed) {
+ SequenceSet confirmSet;
+ if (!getState()->receiverGetUnknownComplete().empty())
+ confirmSet.add(getState()->receiverGetUnknownComplete().front(),
+ getState()->receiverGetReceived().command);
+ peer.confirmed(confirmSet, Array());
+ }
+ if (completed)
+ peer.completed(getState()->receiverGetUnknownComplete(), true);
+}
+
+void SessionHandler::gap(const SequenceSet& /*commands*/) {
+ checkAttached();
+ throw NotImplementedException("session.gap not supported");
+}
+
+void SessionHandler::sendDetach()
+{
+ checkAttached();
+ awaitingDetached = true;
+ peer.detach(getState()->getId().getName());
+}
+
+void SessionHandler::sendCompletion() {
+ checkAttached();
+ const SequenceSet& c = getState()->receiverGetUnknownComplete();
+ peer.completed(c, getState()->receiverNeedKnownCompleted());
+}
+
+void SessionHandler::sendAttach(bool force) {
+ QPID_LOG(debug, "SessionHandler::sendAttach attach id=" << getState()->getId());
+ peer.attach(getState()->getId().getName(), force);
+ if (getState()->hasState())
+ peer.flush(true, true, true);
+ else
+ sendCommandPoint(getState()->senderGetCommandPoint());
+}
+
+void SessionHandler::sendCommandPoint(const SessionPoint& point) {
+ peer.commandPoint(point.command, point.offset);
+ if (!sendReady) {
+ sendReady = true;
+ readyToSend();
+ }
+}
+
+void SessionHandler::markReadyToSend() {
+ if (!sendReady) {
+ sendReady = true;
+ }
+}
+
+void SessionHandler::sendTimeout(uint32_t t) {
+ checkAttached();
+ peer.requestTimeout(t);
+}
+
+void SessionHandler::sendFlush() {
+ peer.flush(false, true, true);
+}
+
+bool SessionHandler::ready() const {
+ return sendReady && receiveReady;
+}
+
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/amqp_0_10/SessionHandler.h b/qpid/cpp/src/qpid/amqp_0_10/SessionHandler.h
new file mode 100644
index 0000000000..8b072fa05c
--- /dev/null
+++ b/qpid/cpp/src/qpid/amqp_0_10/SessionHandler.h
@@ -0,0 +1,118 @@
+#ifndef QPID_AMQP_0_10_SESSIONHANDLER_H
+#define QPID_AMQP_0_10_SESSIONHANDLER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/framing/ChannelHandler.h"
+#include "qpid/framing/AMQP_AllProxy.h"
+#include "qpid/framing/AMQP_AllOperations.h"
+#include "qpid/SessionState.h"
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+
+struct SessionException;
+
+namespace amqp_0_10 {
+
+/**
+ * Base SessionHandler with logic common to both client and broker.
+ *
+ * A SessionHandler is associated with a channel and can be attached
+ * to a session state.
+ */
+
+class QPID_COMMON_CLASS_EXTERN SessionHandler : public framing::AMQP_AllOperations::SessionHandler,
+ public framing::FrameHandler::InOutHandler
+{
+ public:
+ QPID_COMMON_EXTERN SessionHandler(framing::FrameHandler* out=0, uint16_t channel=0);
+ QPID_COMMON_EXTERN ~SessionHandler();
+
+ void setChannel(uint16_t ch) { channel = ch; }
+ uint16_t getChannel() const { return channel.get(); }
+
+ void setOutHandler(framing::FrameHandler& h) { channel.next = &h; }
+
+ virtual SessionState* getState() = 0;
+ virtual framing::FrameHandler* getInHandler() = 0;
+
+ // Non-protocol methods, called locally to initiate some action.
+ QPID_COMMON_EXTERN void sendDetach();
+ QPID_COMMON_EXTERN void sendCompletion();
+ QPID_COMMON_EXTERN void sendAttach(bool force);
+ QPID_COMMON_EXTERN void sendTimeout(uint32_t t);
+ QPID_COMMON_EXTERN void sendFlush();
+ QPID_COMMON_EXTERN void markReadyToSend();//TODO: only needed for inter-broker bridge; cleanup
+ QPID_COMMON_EXTERN void handleException(const qpid::SessionException& e);
+
+ /** True if the handler is ready to send and receive */
+ QPID_COMMON_EXTERN bool ready() const;
+
+ // Protocol methods
+ QPID_COMMON_EXTERN void attach(const std::string& name, bool force);
+ QPID_COMMON_EXTERN void attached(const std::string& name);
+ QPID_COMMON_EXTERN void detach(const std::string& name);
+ QPID_COMMON_EXTERN void detached(const std::string& name, uint8_t code);
+
+ QPID_COMMON_EXTERN void requestTimeout(uint32_t t);
+ QPID_COMMON_EXTERN void timeout(uint32_t t);
+
+ QPID_COMMON_EXTERN void commandPoint(const framing::SequenceNumber& id, uint64_t offset);
+ QPID_COMMON_EXTERN void expected(const framing::SequenceSet& commands, const framing::Array& fragments);
+ QPID_COMMON_EXTERN void confirmed(const framing::SequenceSet& commands,const framing::Array& fragments);
+ QPID_COMMON_EXTERN void completed(const framing::SequenceSet& commands, bool timelyReply);
+ QPID_COMMON_EXTERN void knownCompleted(const framing::SequenceSet& commands);
+ QPID_COMMON_EXTERN void flush(bool expected, bool confirmed, bool completed);
+ QPID_COMMON_EXTERN void gap(const framing::SequenceSet& commands);
+
+ protected:
+ QPID_COMMON_EXTERN virtual void invoke(const framing::AMQMethodBody& m);
+
+ virtual void setState(const std::string& sessionName, bool force) = 0;
+ virtual void connectionException(framing::connection::CloseCode code, const std::string& msg) = 0;
+ virtual void channelException(framing::session::DetachCode, const std::string& msg) = 0;
+ virtual void executionException(framing::execution::ErrorCode, const std::string& msg) = 0;
+ virtual void detaching() = 0;
+
+ // Notification of events
+ virtual void readyToSend() {}
+ virtual void readyToReceive() {}
+
+ QPID_COMMON_EXTERN virtual void handleDetach();
+ QPID_COMMON_EXTERN virtual void handleIn(framing::AMQFrame&);
+ QPID_COMMON_EXTERN virtual void handleOut(framing::AMQFrame&);
+
+ framing::ChannelHandler channel;
+
+ private:
+ void checkAttached();
+ void sendCommandPoint(const SessionPoint&);
+
+ framing::AMQP_AllProxy::Session peer;
+ std::string name;
+ bool awaitingDetached;
+ bool sendReady, receiveReady;
+};
+}} // namespace qpid::amqp_0_10
+
+#endif /*!QPID_AMQP_0_10_SESSIONHANDLER_H*/
diff --git a/qpid/cpp/src/qpid/assert.cpp b/qpid/cpp/src/qpid/assert.cpp
new file mode 100644
index 0000000000..801bfa6ae5
--- /dev/null
+++ b/qpid/cpp/src/qpid/assert.cpp
@@ -0,0 +1,47 @@
+#ifndef QPID_ASSERT_CPP
+#define QPID_ASSERT_CPP
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <sstream>
+#include <iostream>
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/log/Statement.h"
+#include <stdlib.h>
+
+namespace qpid {
+
+void assert_fail(char const * expr, char const * function, char const * file, long line) {
+ std::ostringstream msg;
+ msg << "Assertion failed: " << expr << " in function " << function
+ << "(" << file << ":" << line << ")";
+ QPID_LOG(critical, msg.str());
+#ifdef NDEBUG
+ throw framing::InternalErrorException(msg.str());
+#else
+ std::cerr << msg.str() << std::endl;
+ abort();
+#endif
+}
+
+} // namespace qpid
+
+#endif /*!QPID_ASSERT_CPP*/
diff --git a/qpid/cpp/src/qpid/assert.h b/qpid/cpp/src/qpid/assert.h
new file mode 100644
index 0000000000..49e7c5355d
--- /dev/null
+++ b/qpid/cpp/src/qpid/assert.h
@@ -0,0 +1,38 @@
+#ifndef QPID_ASSERT_H
+#define QPID_ASSERT_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <boost/current_function.hpp>
+
+/**
+ * Abort if !expr in debug mode, throw an exception if NDEBUG is set.
+ */
+#define QPID_ASSERT(expr) ((expr) ? static_cast<void>(0) : ::qpid::assert_fail(#expr, BOOST_CURRENT_FUNCTION, __FILE__, __LINE__))
+
+namespace qpid {
+
+void assert_fail(char const * expr, char const * function, char const * file, long line);
+
+} // namespace qpid
+
+#endif /*!QPID_ASSERT_H*/
diff --git a/qpid/cpp/src/qpid/broker/AclModule.h b/qpid/cpp/src/qpid/broker/AclModule.h
new file mode 100644
index 0000000000..6f3b69390b
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/AclModule.h
@@ -0,0 +1,75 @@
+#ifndef QPID_ACLMODULE_ACL_H
+#define QPID_ACLMODULE_ACL_H
+
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/acl/AclLexer.h"
+#include <map>
+#include <string>
+
+namespace qpid {
+namespace broker {
+
+ class Connection;
+
+ class AclModule
+ {
+
+ public:
+
+ // Some ACLs are invoked on every message transfer.
+ // doTransferAcl prevents time consuming ACL calls on a per-message basis.
+ virtual bool doTransferAcl()=0;
+
+ virtual uint16_t getMaxConnectTotal()=0;
+
+ virtual bool userAclRules()=0;
+
+ virtual bool authorise(
+ const std::string& id,
+ const acl::Action& action,
+ const acl::ObjectType& objType,
+ const std::string& name,
+ std::map<acl::Property, std::string>* params=0)=0;
+
+ virtual bool authorise(
+ const std::string& id,
+ const acl::Action& action,
+ const acl::ObjectType& objType,
+ const std::string& ExchangeName,
+ const std::string& RoutingKey)=0;
+
+ // Add specialized authorise() methods as required.
+
+ /** Approve connection by counting connections total, per-IP, and
+ * per-user.
+ */
+ virtual bool approveConnection (const Connection& connection)=0;
+
+ /** Approve queue creation by counting per-user.
+ */
+ virtual bool approveCreateQueue(const std::string& userId, const std::string& queueName)=0;
+ virtual void recordDestroyQueue(const std::string& queueName)=0;
+
+ virtual ~AclModule() {};
+ };
+}} // namespace qpid::broker
+
+#endif // QPID_ACLMODULE_ACL_H
diff --git a/qpid/cpp/src/qpid/broker/AsyncCommandCallback.cpp b/qpid/cpp/src/qpid/broker/AsyncCommandCallback.cpp
new file mode 100644
index 0000000000..0b52c6b5c5
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/AsyncCommandCallback.cpp
@@ -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.
+ *
+ */
+
+#include "AsyncCommandCallback.h"
+#include "SessionOutputException.h"
+
+
+namespace qpid {
+namespace broker {
+
+using namespace framing;
+
+AsyncCommandCallback::AsyncCommandCallback(SessionState& ss, Command f, bool sync) :
+ AsyncCommandContext(ss), command(f), channel(ss.getChannel()), syncPoint(sync)
+{}
+
+void AsyncCommandCallback::completed(bool sync) {
+ if (sync)
+ doCommand(); // In initiating thread, execute now.
+ else
+ completerContext->schedule(
+ boost::bind(&AsyncCommandCallback::complete,
+ boost::intrusive_ptr<AsyncCommandCallback>(this)));
+}
+
+boost::intrusive_ptr<AsyncCompletion::Callback> AsyncCommandCallback::clone() {
+ return new AsyncCommandCallback(*this);
+}
+
+void AsyncCommandCallback::complete() {
+ try{
+ doCommand();
+ } catch (const SessionException& e) {
+ throw SessionOutputException(e, channel);
+ } catch (const std::exception& e) {
+ throw SessionOutputException(InternalErrorException(e.what()), channel);
+ }
+}
+
+void AsyncCommandCallback::doCommand() {
+ SessionState* session = completerContext->getSession();
+ if (session && session->isAttached()) {
+ std::string result = command(); // Execute the command now.
+ // Send completion now unless this is a syncPoint and there are incomplete commands.
+ if (!(syncPoint && session->addPendingExecutionSync(id)))
+ session->completeCommand(id, false, requiresSync, result);
+ }
+ else
+ throw InternalErrorException("Cannot complete command, no session");
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/AsyncCommandCallback.h b/qpid/cpp/src/qpid/broker/AsyncCommandCallback.h
new file mode 100644
index 0000000000..15884ae8d9
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/AsyncCommandCallback.h
@@ -0,0 +1,68 @@
+#ifndef QPID_BROKER_ASYNCCOMMANDCALLBACK_H
+#define QPID_BROKER_ASYNCCOMMANDCALLBACK_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/SessionState.h"
+#include "qpid/broker/AsyncCompletion.h"
+
+namespace qpid {
+namespace broker {
+
+/**
+ * An AsyncCompletion::Callback that executes the final part of an
+ * async-completed command in the proper context:
+ *
+ * - Complete synchronously: Called in the initiating thread.
+ * - Complete asynchronously: Scheduled on the IO thread.
+ *
+ * Errors thrown by the command are returned to the correct session on the client
+ * even if we are executed via an IO callback.
+ */
+class AsyncCommandCallback : public SessionState::AsyncCommandContext {
+ public:
+ /** Command function returns a string containing the encoded result of the
+ * command, or empty for no result. It may raise an exception.
+ */
+ typedef boost::function<std::string ()> Command;
+
+ /**
+ * @param syncPoint: if true have this command complete only when all
+ * preceeding commands are complete, like execution.sync.
+ */
+ AsyncCommandCallback(SessionState& ss, Command f, bool syncPoint=false);
+
+ void completed(bool sync);
+
+ boost::intrusive_ptr<AsyncCompletion::Callback> clone();
+
+ private:
+ void complete();
+ void doCommand();
+
+ Command command;
+ uint16_t channel;
+ bool syncPoint;
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_ASYNCCOMMANDCALLBACK_H*/
diff --git a/qpid/cpp/src/qpid/broker/AsyncCompletion.h b/qpid/cpp/src/qpid/broker/AsyncCompletion.h
new file mode 100644
index 0000000000..cb5d58977b
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/AsyncCompletion.h
@@ -0,0 +1,209 @@
+#ifndef _AsyncCompletion_
+#define _AsyncCompletion_
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/RefCounted.h"
+#include <boost/intrusive_ptr.hpp>
+
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/sys/AtomicValue.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/Monitor.h"
+
+namespace qpid {
+namespace broker {
+
+/**
+ * Class to implement asynchronous notification of completion.
+ *
+ * Use-case: An "initiator" needs to wait for a set of "completers" to
+ * finish a unit of work before an action can occur. This object
+ * tracks the progress of the set of completers, and allows the action
+ * to occur once all completers have signalled that they are done.
+ *
+ * The initiator and completers may be running in separate threads.
+ *
+ * The initiating thread is the thread that initiates the action,
+ * i.e. the connection read thread.
+ *
+ * A completing thread is any thread that contributes to completion,
+ * e.g. a store thread that does an async write.
+ * There may be zero or more completers.
+ *
+ * When the work is complete, a callback is invoked. The callback
+ * may be invoked in the Initiator thread, or one of the Completer
+ * threads. The callback is passed a flag indicating whether or not
+ * the callback is running under the context of the Initiator thread.
+ *
+ * Use model:
+ * 1) Initiator thread invokes begin()
+ * 2) After begin() has been invoked, zero or more Completers invoke
+ * startCompleter(). Completers may be running in the same or
+ * different thread as the Initiator, as long as they guarantee that
+ * startCompleter() is invoked at least once before the Initiator invokes end().
+ * 3) Completers may invoke finishCompleter() at any time, even after the
+ * initiator has invoked end(). finishCompleter() may be called from any
+ * thread.
+ * 4) startCompleter()/finishCompleter() calls "nest": for each call to
+ * startCompleter(), a corresponding call to finishCompleter() must be made.
+ * Once the last finishCompleter() is called, the Completer must no longer
+ * reference the completion object.
+ * 5) The Initiator invokes end() at the point where it has finished
+ * dispatching work to the Completers, and is prepared for the callback
+ * handler to be invoked. Note: if there are no outstanding Completers
+ * pending when the Initiator invokes end(), the callback will be invoked
+ * directly, and the sync parameter will be set true. This indicates to the
+ * Initiator that the callback is executing in the context of the end() call,
+ * and the Initiator is free to optimize the handling of the completion,
+ * assuming no need for synchronization with Completer threads.
+ */
+
+class AsyncCompletion : public virtual RefCounted
+{
+ public:
+
+ /** Supplied by the Initiator to the end() method, allows for a callback
+ * when all outstanding completers are done. If the callback cannot be
+ * made during the end() call, the clone() method must supply a copy of
+ * this callback object that persists after end() returns. The cloned
+ * callback object will be used by the last completer thread, and
+ * released when the callback returns.
+ */
+ class Callback : public RefCounted
+ {
+ public:
+ // Normally RefCounted objects cannot be copied.
+ // Allow Callback objects to be copied (by subclasses implementing clone())
+ // The copy has an initial refcount of 0
+ Callback(const Callback&) : RefCounted() {}
+ Callback() {}
+
+ virtual void completed(bool) = 0;
+ virtual boost::intrusive_ptr<Callback> clone() = 0;
+ };
+
+ private:
+ mutable qpid::sys::AtomicValue<uint32_t> completionsNeeded;
+ mutable qpid::sys::Monitor callbackLock;
+ bool inCallback, active;
+
+ void invokeCallback(bool sync) {
+ qpid::sys::Mutex::ScopedLock l(callbackLock);
+ if (active) {
+ if (callback.get()) {
+ boost::intrusive_ptr<Callback> save = callback;
+ callback = boost::intrusive_ptr<Callback>(); // Nobody else can run callback.
+ inCallback = true;
+ {
+ qpid::sys::Mutex::ScopedUnlock ul(callbackLock);
+ save->completed(sync);
+ }
+ inCallback = false;
+ callbackLock.notifyAll();
+ }
+ active = false;
+ }
+ }
+
+ protected:
+ /** Invoked when all completers have signalled that they have completed
+ * (via calls to finishCompleter()). bool == true if called via end()
+ */
+ boost::intrusive_ptr<Callback> callback;
+
+ public:
+ AsyncCompletion() : completionsNeeded(0), inCallback(false), active(true) {};
+ virtual ~AsyncCompletion() { cancel(); }
+
+
+ /** True when all outstanding operations have compeleted
+ */
+ bool isDone()
+ {
+ return !active;
+ }
+
+ /** Called to signal the start of an asynchronous operation. The operation
+ * is considered pending until finishCompleter() is called.
+ * E.g. called when initiating an async store operation.
+ */
+ void startCompleter() { ++completionsNeeded; }
+
+ /** Called by completer to signal that it has finished the operation started
+ * when startCompleter() was invoked.
+ * e.g. called when async write complete.
+ */
+ void finishCompleter()
+ {
+ if (--completionsNeeded == 0) {
+ invokeCallback(false);
+ }
+ }
+
+ /** called by initiator before any calls to startCompleter can be done.
+ */
+ void begin()
+ {
+ ++completionsNeeded;
+ }
+
+ /** called by initiator after all potential completers have called
+ * startCompleter().
+ */
+ void end(Callback& cb)
+ {
+ assert(completionsNeeded.get() > 0); // ensure begin() has been called!
+ // the following only "decrements" the count if it is 1. This means
+ // there are no more outstanding completers and we are done.
+ if (completionsNeeded.boolCompareAndSwap(1, 0)) {
+ // done! Complete immediately
+ cb.completed(true);
+ return;
+ }
+
+ // the compare-and-swap did not succeed. This means there are
+ // outstanding completers pending (count > 1). Get a persistent
+ // Callback object to use when the last completer is done.
+ // Decrement after setting up the callback ensures that pending
+ // completers cannot touch the callback until it is ready.
+ callback = cb.clone();
+ if (--completionsNeeded == 0) {
+ // note that a completer may have completed during the
+ // callback setup or decrement:
+ invokeCallback(true);
+ }
+ }
+
+ /** may be called by Initiator to cancel the callback. Will wait for
+ * callback to complete if in progress.
+ */
+ virtual void cancel() {
+ qpid::sys::Mutex::ScopedLock l(callbackLock);
+ while (inCallback) callbackLock.wait();
+ callback = boost::intrusive_ptr<Callback>();
+ active = false;
+ }
+};
+
+}} // qpid::broker::
+#endif /*!_AsyncCompletion_*/
diff --git a/qpid/cpp/src/qpid/broker/Bridge.cpp b/qpid/cpp/src/qpid/broker/Bridge.cpp
new file mode 100644
index 0000000000..06d3a0dd52
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Bridge.cpp
@@ -0,0 +1,467 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/Bridge.h"
+
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/FedOps.h"
+#include "qpid/broker/amqp_0_10/Connection.h"
+#include "qpid/broker/Link.h"
+#include "qpid/broker/LinkRegistry.h"
+#include "qpid/broker/SessionState.h"
+
+#include "qpid/management/ManagementAgent.h"
+#include "qpid/types/Variant.h"
+#include "qpid/amqp_0_10/Codecs.h"
+#include "qpid/framing/Uuid.h"
+#include "qpid/framing/MessageProperties.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qpid/log/Statement.h"
+#include <iostream>
+
+using qpid::framing::FieldTable;
+using qpid::framing::Uuid;
+using qpid::framing::Buffer;
+using qpid::framing::AMQFrame;
+using qpid::framing::AMQContentBody;
+using qpid::framing::AMQHeaderBody;
+using qpid::framing::MessageProperties;
+using qpid::framing::MessageTransferBody;
+using qpid::types::Variant;
+using qpid::management::ManagementAgent;
+using std::string;
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+namespace {
+const std::string QPID_REPLICATE("qpid.replicate");
+const std::string NONE("none");
+const uint8_t EXPLICIT_ACK(0); // msg.accept required to be sent
+const uint8_t IMPLIED_ACK(1); // msg.accept assumed, not sent
+}
+
+namespace qpid {
+namespace broker {
+
+void Bridge::PushHandler::handle(framing::AMQFrame& frame)
+{
+ conn->received(frame);
+}
+
+Bridge::Bridge(const std::string& _name, Link* _link, framing::ChannelId _id,
+ CancellationListener l, const _qmf::ArgsLinkBridge& _args,
+ InitializeCallback init, const std::string& _queueName, const string& ae) :
+ link(_link), channel(_id), args(_args),
+ listener(l), name(_name),
+ queueName(_queueName.empty() ? "qpid.bridge_queue_" + name + "_" + link->getBroker()->getFederationTag()
+ : _queueName),
+ altEx(ae), persistenceId(0),
+ conn(0), initialize(init), detached(false),
+ useExistingQueue(!_queueName.empty()),
+ sessionName("qpid.bridge_session_" + name + "_" + link->getBroker()->getFederationTag())
+{
+ // If both acks (i_sync) and limited credit is configured, then we'd
+ // better be able to sync before running out of credit or we
+ // may stall (note: i_credit==0 means "unlimited")
+ if (args.i_credit && args.i_sync && args.i_sync > args.i_credit)
+ throw Exception("The credit value must be greater than configured sync (ack) interval.");
+
+ ManagementAgent* agent = link->getBroker()->getManagementAgent();
+ if (agent != 0) {
+ mgmtObject = _qmf::Bridge::shared_ptr(new _qmf::Bridge
+ (agent, this, link, name, args.i_durable, args.i_src, args.i_dest,
+ args.i_key, args.i_srcIsQueue, args.i_srcIsLocal,
+ args.i_tag, args.i_excludes, args.i_dynamic, args.i_sync,
+ args.i_credit));
+ mgmtObject->set_channelId(channel);
+ agent->addObject(mgmtObject);
+ }
+ QPID_LOG(debug, "Bridge " << name << " created from " << args.i_src << " to " << args.i_dest);
+}
+
+Bridge::~Bridge()
+{
+ mgmtObject->resourceDestroy();
+}
+
+void Bridge::create(amqp_0_10::Connection& c)
+{
+ detached = false; // Reset detached in case we are recovering.
+ conn = &c;
+
+ SessionHandler& sessionHandler = c.getChannel(channel);
+ sessionHandler.setErrorListener(shared_from_this());
+ if (args.i_srcIsLocal) {
+ if (args.i_dynamic)
+ throw Exception("Dynamic routing not supported for push routes");
+ // Point the bridging commands at the local connection handler
+ pushHandler.reset(new PushHandler(&c));
+ channelHandler.reset(new framing::ChannelHandler(channel, pushHandler.get()));
+
+ session.reset(new framing::AMQP_ServerProxy::Session(*channelHandler));
+ peer.reset(new framing::AMQP_ServerProxy(*channelHandler));
+
+ session->attach(sessionName, false);
+ session->commandPoint(0,0);
+ } else {
+ sessionHandler.attachAs(sessionName);
+ // Point the bridging commands at the remote peer broker
+ peer.reset(new framing::AMQP_ServerProxy(sessionHandler.out));
+ }
+
+ if (args.i_srcIsLocal) sessionHandler.getSession()->disableReceiverTracking();
+
+ if (initialize) {
+ initialize(*this, sessionHandler); // custom subscription initializer supplied
+ } else {
+ // will a temp queue be created for this bridge?
+ const bool temp_queue = !args.i_srcIsQueue && !useExistingQueue;
+ // UI convention: user specifies 0 for infinite credit
+ const uint32_t credit = (args.i_credit == 0) ? LinkRegistry::INFINITE_CREDIT : args.i_credit;
+ // use explicit acks only for non-temp queues, useless for temp queues since they are
+ // destroyed when the session drops (can't resend unacked msgs)
+ const uint8_t ack_mode = (args.i_sync && !temp_queue) ? EXPLICIT_ACK : IMPLIED_ACK;
+
+ // configure command.sync frequency
+ FieldTable options;
+ uint32_t freq = 0;
+ if (ack_mode == EXPLICIT_ACK) { // user explicitly configured syncs
+ freq = uint32_t(args.i_sync);
+ } else if (credit && credit != LinkRegistry::INFINITE_CREDIT) {
+ // force occasional sync to keep from stalling due to lack of credit
+ freq = (credit + 1)/2;
+ }
+ if (freq)
+ options.setInt("qpid.sync_frequency", freq);
+
+ // create a subscription on the remote
+ if (args.i_srcIsQueue) {
+ peer->getMessage().subscribe(args.i_src, args.i_dest, ack_mode, 0, false, "", 0, options);
+ peer->getMessage().flow(args.i_dest, 0, credit); // message credit
+ peer->getMessage().flow(args.i_dest, 1, LinkRegistry::INFINITE_CREDIT); // byte credit
+ QPID_LOG(debug, "Activated bridge " << name << " for route from queue " << args.i_src << " to " << args.i_dest);
+ } else {
+ if (!useExistingQueue) {
+ FieldTable queueSettings;
+
+ if (args.i_tag.size()) {
+ queueSettings.setString("qpid.trace.id", args.i_tag);
+ } else {
+ const string& peerTag = c.getFederationPeerTag();
+ if (peerTag.size())
+ queueSettings.setString("qpid.trace.id", peerTag);
+ }
+
+ if (args.i_excludes.size()) {
+ queueSettings.setString("qpid.trace.exclude", args.i_excludes);
+ } else {
+ const string& localTag = link->getBroker()->getFederationTag();
+ if (localTag.size())
+ queueSettings.setString("qpid.trace.exclude", localTag);
+ }
+
+ bool durable = false;//should this be an arg, or would we use srcIsQueue for durable queues?
+ bool exclusive = true; // only exclusive if the queue is owned by the bridge
+ bool autoDelete = exclusive && !durable;//auto delete transient queues?
+ peer->getQueue().declare(queueName, altEx, false, durable, exclusive, autoDelete, queueSettings);
+ }
+ if (!args.i_dynamic)
+ peer->getExchange().bind(queueName, args.i_src, args.i_key, FieldTable());
+ peer->getMessage().subscribe(queueName, args.i_dest, ack_mode, 0, false, "", 0, options);
+ peer->getMessage().flow(args.i_dest, 0, credit);
+ peer->getMessage().flow(args.i_dest, 1, LinkRegistry::INFINITE_CREDIT);
+ if (args.i_dynamic) {
+ Exchange::shared_ptr exchange = link->getBroker()->getExchanges().get(args.i_src);
+ if (exchange.get() == 0)
+ throw Exception("Exchange not found for dynamic route");
+ exchange->registerDynamicBridge(this);
+ QPID_LOG(debug, "Activated bridge " << name << " for dynamic route for exchange " << args.i_src);
+ } else {
+ QPID_LOG(debug, "Activated bridge " << name << " for static route from exchange " << args.i_src << " to " << args.i_dest);
+ }
+ }
+ }
+ if (args.i_srcIsLocal) sessionHandler.getSession()->enableReceiverTracking();
+}
+
+void Bridge::cancel(amqp_0_10::Connection& c)
+{
+ // If &c != conn then we have failed over so the old connection is closed.
+ if (&c == conn && resetProxy()) {
+ peer->getMessage().cancel(args.i_dest);
+ peer->getSession().detach(sessionName);
+ }
+ QPID_LOG(debug, "Cancelled bridge " << name);
+}
+
+/** Notify the bridge that the connection has closed */
+void Bridge::closed()
+{
+ if (args.i_dynamic) {
+ Exchange::shared_ptr exchange = link->getBroker()->getExchanges().find(args.i_src);
+ if (exchange.get()) exchange->removeDynamicBridge(this);
+ }
+ QPID_LOG(debug, "Closed bridge " << name);
+}
+
+/** Shut down the bridge */
+void Bridge::close()
+{
+ listener(this); // ask the LinkRegistry to destroy us
+}
+
+void Bridge::setPersistenceId(uint64_t pId) const
+{
+ persistenceId = pId;
+}
+
+
+const std::string Bridge::ENCODED_IDENTIFIER("bridge.v2");
+const std::string Bridge::ENCODED_IDENTIFIER_V1("bridge");
+
+bool Bridge::isEncodedBridge(const std::string& key)
+{
+ return key == ENCODED_IDENTIFIER || key == ENCODED_IDENTIFIER_V1;
+}
+
+
+Bridge::shared_ptr Bridge::decode(LinkRegistry& links, Buffer& buffer)
+{
+ string kind;
+ buffer.getShortString(kind);
+
+ string host;
+ uint16_t port;
+ string src;
+ string dest;
+ string key;
+ string id;
+ string excludes;
+ string name;
+
+ Link::shared_ptr link;
+ if (kind == ENCODED_IDENTIFIER_V1) {
+ /** previous versions identified the bridge by host:port, not by name, and
+ * transport wasn't provided. Try to find a link using those paramters.
+ */
+ buffer.getShortString(host);
+ port = buffer.getShort();
+
+ link = links.getLink(host, port);
+ if (!link) {
+ QPID_LOG(error, "Bridge::decode() failed: cannot find Link for host=" << host << ", port=" << port);
+ return Bridge::shared_ptr();
+ }
+ } else {
+ string linkName;
+
+ buffer.getShortString(name);
+ buffer.getShortString(linkName);
+ link = links.getLink(linkName);
+ if (!link) {
+ QPID_LOG(error, "Bridge::decode() failed: cannot find Link named='" << linkName << "'");
+ return Bridge::shared_ptr();
+ }
+ }
+
+ bool durable(buffer.getOctet());
+ buffer.getShortString(src);
+ buffer.getShortString(dest);
+ buffer.getShortString(key);
+ bool is_queue(buffer.getOctet());
+ bool is_local(buffer.getOctet());
+ buffer.getShortString(id);
+ buffer.getShortString(excludes);
+ bool dynamic(buffer.getOctet());
+ uint16_t sync = buffer.getShort();
+ uint32_t credit = buffer.getLong();
+
+ if (kind == ENCODED_IDENTIFIER_V1) {
+ /** previous versions did not provide a name for the bridge, so create one
+ */
+ name = createName(link->getName(), src, dest, key);
+ }
+
+ return links.declare(name, *link, durable, src, dest, key, is_queue,
+ is_local, id, excludes, dynamic, sync, credit).first;
+}
+
+void Bridge::encode(Buffer& buffer) const
+{
+ buffer.putShortString(ENCODED_IDENTIFIER);
+ buffer.putShortString(name);
+ buffer.putShortString(link->getName());
+ buffer.putOctet(args.i_durable ? 1 : 0);
+ buffer.putShortString(args.i_src);
+ buffer.putShortString(args.i_dest);
+ buffer.putShortString(args.i_key);
+ buffer.putOctet(args.i_srcIsQueue ? 1 : 0);
+ buffer.putOctet(args.i_srcIsLocal ? 1 : 0);
+ buffer.putShortString(args.i_tag);
+ buffer.putShortString(args.i_excludes);
+ buffer.putOctet(args.i_dynamic ? 1 : 0);
+ buffer.putShort(args.i_sync);
+ buffer.putLong(args.i_credit);
+}
+
+uint32_t Bridge::encodedSize() const
+{
+ return ENCODED_IDENTIFIER.size() + 1 // +1 byte length
+ + name.size() + 1
+ + link->getName().size() + 1
+ + 1 // durable
+ + args.i_src.size() + 1
+ + args.i_dest.size() + 1
+ + args.i_key.size() + 1
+ + 1 // srcIsQueue
+ + 1 // srcIsLocal
+ + args.i_tag.size() + 1
+ + args.i_excludes.size() + 1
+ + 1 // dynamic
+ + 2 // sync
+ + 4; // credit
+}
+
+management::ManagementObject::shared_ptr Bridge::GetManagementObject(void) const
+{
+ return mgmtObject;
+}
+
+management::Manageable::status_t Bridge::ManagementMethod(uint32_t methodId,
+ management::Args& /*args*/,
+ string&)
+{
+ if (methodId == _qmf::Bridge::METHOD_CLOSE) {
+ //notify that we are closed
+ QPID_LOG(debug, "Bridge::close() method called on bridge '" << name << "'");
+ close();
+ return management::Manageable::STATUS_OK;
+ } else {
+ return management::Manageable::STATUS_UNKNOWN_METHOD;
+ }
+}
+
+void Bridge::propagateBinding(const string& key, const string& tagList,
+ const string& op, const string& origin,
+ qpid::framing::FieldTable* extra_args)
+{
+ const string& localTag = link->getBroker()->getFederationTag();
+ const string& peerTag = conn->getFederationPeerTag();
+
+ if (tagList.find(peerTag) == tagList.npos) {
+ FieldTable bindArgs;
+ if (extra_args) {
+ for (qpid::framing::FieldTable::ValueMap::iterator i=extra_args->begin(); i != extra_args->end(); ++i) {
+ bindArgs.insert((*i));
+ }
+ }
+ string newTagList(tagList + string(tagList.empty() ? "" : ",") + localTag);
+
+ bindArgs.setString(QPID_REPLICATE, NONE);
+ bindArgs.setString(qpidFedOp, op);
+ bindArgs.setString(qpidFedTags, newTagList);
+ if (origin.empty())
+ bindArgs.setString(qpidFedOrigin, localTag);
+ else
+ bindArgs.setString(qpidFedOrigin, origin);
+
+ conn->requestIOProcessing(boost::bind(&Bridge::ioThreadPropagateBinding, this,
+ queueName, args.i_src, key, bindArgs));
+ }
+}
+
+void Bridge::sendReorigin()
+{
+ FieldTable bindArgs;
+
+ bindArgs.setString(qpidFedOp, fedOpReorigin);
+ bindArgs.setString(qpidFedTags, link->getBroker()->getFederationTag());
+
+ conn->requestIOProcessing(boost::bind(&Bridge::ioThreadPropagateBinding, this,
+ queueName, args.i_src, args.i_key, bindArgs));
+}
+bool Bridge::resetProxy()
+{
+ SessionHandler& sessionHandler = conn->getChannel(channel);
+ if (!sessionHandler.getSession()) peer.reset();
+ else peer.reset(new framing::AMQP_ServerProxy(sessionHandler.out));
+ return peer.get();
+}
+
+void Bridge::ioThreadPropagateBinding(const string& queue, const string& exchange, const string& key, FieldTable args)
+{
+ if (resetProxy()) {
+ peer->getExchange().bind(queue, exchange, key, args);
+ } else {
+ // link's periodic maintenance visit will attempt to recover
+ }
+}
+
+bool Bridge::containsLocalTag(const string& tagList) const
+{
+ const string& localTag = link->getBroker()->getFederationTag();
+ return (tagList.find(localTag) != tagList.npos);
+}
+
+const string& Bridge::getLocalTag() const
+{
+ return link->getBroker()->getFederationTag();
+}
+
+// SessionHandler::ErrorListener methods.
+void Bridge::connectionException(
+ framing::connection::CloseCode code, const std::string& msg)
+{
+ if (errorListener) errorListener->connectionException(code, msg);
+}
+
+void Bridge::channelException(
+ framing::session::DetachCode code, const std::string& msg)
+{
+ if (errorListener) errorListener->channelException(code, msg);
+}
+
+void Bridge::executionException(
+ framing::execution::ErrorCode code, const std::string& msg)
+{
+ if (errorListener) errorListener->executionException(code, msg);
+}
+
+void Bridge::incomingExecutionException(
+ framing::execution::ErrorCode code, const std::string& msg)
+{
+ if (errorListener) errorListener->incomingExecutionException(code, msg);
+}
+
+void Bridge::detach() {
+ detached = true;
+ if (errorListener) errorListener->detach();
+}
+
+std::string Bridge::createName(const std::string& linkName,
+ const std::string& src,
+ const std::string& dest,
+ const std::string& key)
+{
+ std::stringstream keystream;
+ keystream << linkName << "!" << src << "!" << dest << "!" << key;
+ return keystream.str();
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/broker/Bridge.h b/qpid/cpp/src/qpid/broker/Bridge.h
new file mode 100644
index 0000000000..3eac8f2af2
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Bridge.h
@@ -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.
+ *
+ */
+#ifndef _Bridge_
+#define _Bridge_
+
+#include "qpid/broker/PersistableConfig.h"
+#include "qpid/framing/AMQP_ServerProxy.h"
+#include "qpid/framing/ChannelHandler.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/framing/FrameHandler.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/management/Manageable.h"
+#include "qpid/broker/Exchange.h"
+#include "qpid/broker/SessionHandler.h"
+#include "qmf/org/apache/qpid/broker/ArgsLinkBridge.h"
+#include "qmf/org/apache/qpid/broker/Bridge.h"
+
+#include <boost/function.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include <memory>
+
+namespace qpid {
+namespace broker {
+namespace amqp_0_10 {
+class Connection;
+}
+class Link;
+class LinkRegistry;
+
+class Bridge : public PersistableConfig,
+ public management::Manageable,
+ public Exchange::DynamicBridge,
+ public SessionHandler::ErrorListener,
+ public boost::enable_shared_from_this<Bridge>
+{
+ public:
+ typedef boost::shared_ptr<Bridge> shared_ptr;
+ typedef boost::function<void(Bridge*)> CancellationListener;
+ typedef boost::function<void(Bridge&, SessionHandler&)> InitializeCallback;
+
+ Bridge(const std::string& name, Link* link, framing::ChannelId id, CancellationListener l,
+ const qmf::org::apache::qpid::broker::ArgsLinkBridge& args,
+ InitializeCallback init, const std::string& queueName="",
+ const std::string& altExchange=""
+ );
+ ~Bridge();
+
+ QPID_BROKER_EXTERN void close();
+ bool isDurable() { return args.i_durable; }
+ framing::ChannelId getChannel() const { return channel; }
+ Link *getLink() const { return link; }
+ const std::string getSrc() const { return args.i_src; }
+ const std::string getDest() const { return args.i_dest; }
+ const std::string getKey() const { return args.i_key; }
+
+ bool isDetached() const { return detached; }
+
+ management::ManagementObject::shared_ptr GetManagementObject() const;
+ management::Manageable::status_t ManagementMethod(uint32_t methodId,
+ management::Args& args,
+ std::string& text);
+
+ // PersistableConfig:
+ void setPersistenceId(uint64_t id) const;
+ uint64_t getPersistenceId() const { return persistenceId; }
+ uint32_t encodedSize() const;
+ void encode(framing::Buffer& buffer) const;
+ const std::string& getName() const { return name; }
+
+ static const std::string ENCODED_IDENTIFIER;
+ static const std::string ENCODED_IDENTIFIER_V1;
+ static Bridge::shared_ptr decode(LinkRegistry& links, framing::Buffer& buffer);
+ static bool isEncodedBridge(const std::string& key);
+
+ // Exchange::DynamicBridge methods
+ void propagateBinding(const std::string& key, const std::string& tagList, const std::string& op, const std::string& origin, qpid::framing::FieldTable* extra_args=0);
+ void sendReorigin();
+ void ioThreadPropagateBinding(const std::string& queue, const std::string& exchange, const std::string& key, framing::FieldTable args);
+ bool containsLocalTag(const std::string& tagList) const;
+ const std::string& getLocalTag() const;
+
+ // Methods needed by initialization functions
+ std::string getQueueName() const { return queueName; }
+ const qmf::org::apache::qpid::broker::ArgsLinkBridge& getArgs() { return args; }
+
+ /** create a name for a bridge (if none supplied by user config) */
+ static std::string createName(const std::string& linkName,
+ const std::string& src,
+ const std::string& dest,
+ const std::string& key);
+
+ // SessionHandler::ErrorListener methods.
+ void connectionException(framing::connection::CloseCode code, const std::string& msg);
+ void channelException(framing::session::DetachCode, const std::string& msg);
+ void executionException(framing::execution::ErrorCode, const std::string& msg);
+ void incomingExecutionException(framing::execution::ErrorCode, const std::string& msg);
+ void detach();
+
+ void setErrorListener(boost::shared_ptr<ErrorListener> e) { errorListener = e; }
+ private:
+ struct PushHandler : framing::FrameHandler {
+ PushHandler(amqp_0_10::Connection* c) { conn = c; }
+ void handle(framing::AMQFrame& frame);
+ amqp_0_10::Connection* conn;
+ };
+
+ std::auto_ptr<PushHandler> pushHandler;
+ std::auto_ptr<framing::ChannelHandler> channelHandler;
+ std::auto_ptr<framing::AMQP_ServerProxy::Session> session;
+ std::auto_ptr<framing::AMQP_ServerProxy> peer;
+
+ Link* const link;
+ const framing::ChannelId channel;
+ qmf::org::apache::qpid::broker::ArgsLinkBridge args;
+ qmf::org::apache::qpid::broker::Bridge::shared_ptr mgmtObject;
+ CancellationListener listener;
+ std::string name;
+ std::string queueName;
+ std::string altEx;
+ mutable uint64_t persistenceId;
+ amqp_0_10::Connection* conn;
+ InitializeCallback initialize;
+ bool detached; // Set when session is detached.
+ bool resetProxy();
+
+ // connection Management (called by owning Link)
+ void create(amqp_0_10::Connection& c);
+ void cancel(amqp_0_10::Connection& c);
+ void closed();
+ friend class Link; // to call create, cancel, closed()
+ boost::shared_ptr<ErrorListener> errorListener;
+
+ const bool useExistingQueue;
+ const std::string sessionName;
+};
+
+
+}}
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/Broker.cpp b/qpid/cpp/src/qpid/broker/Broker.cpp
new file mode 100644
index 0000000000..622681ab2a
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Broker.cpp
@@ -0,0 +1,1686 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/Broker.h"
+
+#include "qpid/broker/AclModule.h"
+#include "qpid/broker/BrokerOptions.h"
+#include "qpid/broker/Connection.h"
+#include "qpid/broker/DirectExchange.h"
+#include "qpid/broker/FanOutExchange.h"
+#include "qpid/broker/HeadersExchange.h"
+#include "qpid/broker/MessageStoreModule.h"
+#include "qpid/broker/NameGenerator.h"
+#include "qpid/broker/NullMessageStore.h"
+#include "qpid/broker/RecoveryManagerImpl.h"
+#include "qpid/broker/SaslAuthenticator.h"
+#include "qpid/broker/TopicExchange.h"
+#include "qpid/broker/Link.h"
+#include "qpid/broker/PersistableObject.h"
+#include "qpid/broker/QueueFlowLimit.h"
+#include "qpid/broker/QueueSettings.h"
+#include "qpid/broker/TransactionObserver.h"
+#include "qpid/broker/MessageGroupManager.h"
+
+#include "qmf/org/apache/qpid/broker/Package.h"
+#include "qmf/org/apache/qpid/broker/ArgsBrokerConnect.h"
+#include "qmf/org/apache/qpid/broker/ArgsBrokerCreate.h"
+#include "qmf/org/apache/qpid/broker/ArgsBrokerDelete.h"
+#include "qmf/org/apache/qpid/broker/ArgsBrokerQuery.h"
+#include "qmf/org/apache/qpid/broker/ArgsBrokerEcho.h"
+#include "qmf/org/apache/qpid/broker/ArgsBrokerGetLogLevel.h"
+#include "qmf/org/apache/qpid/broker/ArgsBrokerQueueMoveMessages.h"
+#include "qmf/org/apache/qpid/broker/ArgsBrokerSetLogLevel.h"
+#include "qmf/org/apache/qpid/broker/ArgsBrokerGetLogHiresTimestamp.h"
+#include "qmf/org/apache/qpid/broker/ArgsBrokerSetLogHiresTimestamp.h"
+#include "qmf/org/apache/qpid/broker/ArgsBrokerSetTimestampConfig.h"
+#include "qmf/org/apache/qpid/broker/ArgsBrokerGetTimestampConfig.h"
+#include "qmf/org/apache/qpid/broker/ArgsBrokerQueueRedirect.h"
+#include "qmf/org/apache/qpid/broker/EventExchangeDeclare.h"
+#include "qmf/org/apache/qpid/broker/EventExchangeDelete.h"
+#include "qmf/org/apache/qpid/broker/EventBind.h"
+#include "qmf/org/apache/qpid/broker/EventUnbind.h"
+#include "qmf/org/apache/qpid/broker/EventQueueRedirect.h"
+#include "qmf/org/apache/qpid/broker/EventQueueRedirectCancelled.h"
+#include "qpid/amqp_0_10/Codecs.h"
+#include "qpid/management/ManagementDirectExchange.h"
+#include "qpid/management/ManagementTopicExchange.h"
+#include "qpid/log/Logger.h"
+#include "qpid/log/Options.h"
+#include "qpid/log/Statement.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/ProtocolInitiation.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/framing/Uuid.h"
+#include "qpid/sys/TransportFactory.h"
+#include "qpid/sys/Poller.h"
+#include "qpid/sys/Dispatcher.h"
+#include "qpid/sys/Thread.h"
+#include "qpid/sys/Time.h"
+#include "qpid/sys/Timer.h"
+#include "qpid/sys/ConnectionInputHandlerFactory.h"
+#include "qpid/sys/SystemInfo.h"
+#include "qpid/Address.h"
+#include "qpid/StringUtils.h"
+#include "qpid/Url.h"
+#include "qpid/Version.h"
+#include "config.h"
+
+#include <boost/bind.hpp>
+#include <boost/format.hpp>
+
+#include <iostream>
+#include <memory>
+#include <set>
+
+using qpid::sys::TransportAcceptor;
+using qpid::sys::TransportConnector;
+using qpid::sys::Poller;
+using qpid::sys::Dispatcher;
+using qpid::sys::Thread;
+using qpid::framing::FrameHandler;
+using qpid::framing::ChannelId;
+using qpid::management::ManagementAgent;
+using qpid::management::ManagementObject;
+using qpid::management::Manageable;
+using qpid::management::Args;
+using qpid::management::getCurrentPublisher;
+using qpid::types::Variant;
+using std::string;
+using std::make_pair;
+
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+namespace qpid {
+namespace broker {
+
+const std::string empty;
+const std::string amq_direct("amq.direct");
+const std::string amq_topic("amq.topic");
+const std::string amq_fanout("amq.fanout");
+const std::string amq_match("amq.match");
+const std::string qpid_management("qpid.management");
+const std::string knownHostsNone("none");
+
+BrokerOptions::BrokerOptions(const std::string& name) :
+ qpid::Options(name),
+ noDataDir(0),
+ port(DEFAULT_PORT),
+ workerThreads(5),
+ connectionBacklog(10),
+ enableMgmt(1),
+ mgmtPublish(1),
+ mgmtPubInterval(10*sys::TIME_SEC),
+ queueCleanInterval(60*sys::TIME_SEC*10),//10 minutes
+ auth(SaslAuthenticator::available()),
+ realm("QPID"),
+ saslServiceName(BROKER_SASL_NAME),
+ replayFlushLimit(0),
+ replayHardLimit(0),
+ queueLimit(100*1048576/*100M default limit*/),
+ tcpNoDelay(true),
+ requireEncrypted(false),
+ knownHosts(knownHostsNone),
+ qmf2Support(true),
+ qmf1Support(false),
+ queueFlowStopRatio(80),
+ queueFlowResumeRatio(70),
+ queueThresholdEventRatio(80),
+ defaultMsgGroup("qpid.no-group"),
+ timestampRcvMsgs(false), // set the 0.10 timestamp delivery property
+ linkMaintenanceInterval(2*sys::TIME_SEC),
+ linkHeartbeatInterval(120*sys::TIME_SEC),
+ dtxDefaultTimeout(60), // 60s
+ dtxMaxTimeout(3600), // 3600s
+ maxNegotiateTime(10000) // 10s
+{
+ int c = sys::SystemInfo::concurrency();
+ workerThreads=c+1;
+ std::string home = getHome();
+
+ if (home.length() == 0)
+ dataDir += DEFAULT_DATA_DIR_LOCATION;
+ else
+ dataDir += home;
+ dataDir += DEFAULT_DATA_DIR_NAME;
+
+ addOptions()
+ ("data-dir", optValue(dataDir,"DIR"), "Directory to contain persistent data generated by the broker")
+ ("no-data-dir", optValue(noDataDir), "Don't use a data directory. No persistent configuration will be loaded or stored")
+ ("paging-dir", optValue(pagingDir,"DIR"), "Directory in which paging files will be created for paged queues")
+ ("port,p", optValue(port,"PORT"), "Tells the broker to listen on PORT")
+ ("interface", optValue(listenInterfaces, "<interface name>|<interface address>"), "Which network interfaces to use to listen for incoming connections")
+ ("listen-disable", optValue(listenDisabled, "<transport name>"), "Transports to disable listening")
+ ("protocols", optValue(protocols, "<protocol name+version>"), "Which protocol versions to allow")
+ ("worker-threads", optValue(workerThreads, "N"), "Sets the broker thread pool size")
+ ("connection-backlog", optValue(connectionBacklog, "N"), "Sets the connection backlog limit for the server socket")
+ ("mgmt-enable,m", optValue(enableMgmt,"yes|no"), "Enable Management")
+ ("mgmt-publish", optValue(mgmtPublish,"yes|no"), "Enable Publish of Management Data ('no' implies query-only)")
+ ("mgmt-qmf2", optValue(qmf2Support,"yes|no"), "Enable broadcast of management information over QMF v2")
+ ("mgmt-qmf1", optValue(qmf1Support,"yes|no"), "Enable broadcast of management information over QMF v1")
+ ("mgmt-pub-interval", optValue(mgmtPubInterval, "SECONDS"), "Management Publish Interval")
+ ("queue-purge-interval", optValue(queueCleanInterval, "SECONDS"),
+ "Interval between attempts to purge any expired messages from queues")
+ ("auth", optValue(auth, "yes|no"), "Enable authentication, if disabled all incoming connections will be trusted")
+ ("realm", optValue(realm, "REALM"), "Use the given realm when performing authentication")
+ ("sasl-service-name", optValue(saslServiceName, "NAME"), "The service name to specify for SASL")
+ ("default-queue-limit", optValue(queueLimit, "BYTES"), "Default maximum size for queues (in bytes)")
+ ("tcp-nodelay", optValue(tcpNoDelay), "Set TCP_NODELAY on TCP connections")
+ ("require-encryption", optValue(requireEncrypted), "Only accept connections that are encrypted")
+ ("known-hosts-url", optValue(knownHosts, "URL or 'none'"), "URL to send as 'known-hosts' to clients ('none' implies empty list)")
+ ("sasl-config", optValue(saslConfigPath, "DIR"), "Allows SASL config path, if supported by platform, to be overridden. For default location on Linux, see Cyrus SASL documentation. There is no SASL config dir on Windows.")
+ ("default-flow-stop-threshold", optValue(queueFlowStopRatio, "PERCENT"), "Percent of queue's maximum capacity at which flow control is activated.")
+ ("default-flow-resume-threshold", optValue(queueFlowResumeRatio, "PERCENT"), "Percent of queue's maximum capacity at which flow control is de-activated.")
+ ("default-event-threshold-ratio", optValue(queueThresholdEventRatio, "%age of limit"), "The ratio of any specified queue limit at which an event will be raised")
+ ("default-message-group", optValue(defaultMsgGroup, "GROUP-IDENTIFER"), "Group identifier to assign to messages delivered to a message group queue that do not contain an identifier.")
+ ("enable-timestamp", optValue(timestampRcvMsgs, "yes|no"), "Add current time to each received message.")
+ ("link-maintenance-interval", optValue(linkMaintenanceInterval, "SECONDS"),
+ "Interval to check federation link health and re-connect if need be")
+ ("link-heartbeat-interval", optValue(linkHeartbeatInterval, "SECONDS"),
+ "Heartbeat interval for a federation link")
+ ("dtx-default-timeout", optValue(dtxDefaultTimeout, "SECONDS"), "Default timeout for DTX transaction before aborting it")
+ ("dtx-max-timeout", optValue(dtxMaxTimeout, "SECONDS"), "Maximum allowed timeout for DTX transaction. A value of zero disables maximum timeout limit checks and allows arbitrarily large timeout settings.")
+ ("max-negotiate-time", optValue(maxNegotiateTime, "MILLISECONDS"), "Maximum time a connection can take to send the initial protocol negotiation")
+ ("federation-tag", optValue(fedTag, "NAME"), "Override the federation tag")
+ ;
+}
+
+namespace {
+// Arguments to declare a non-replicated exchange.
+framing::FieldTable noReplicateArgs() {
+ framing::FieldTable args;
+ args.setString("qpid.replicate", "none");
+ return args;
+}
+}
+
+Broker::LogPrefix::LogPrefix() :
+ std::string(Msg() << "Broker (pid=" << sys::SystemInfo::getProcessId() << ") ") {
+ QPID_LOG(notice, *this << "start-up");
+}
+
+Broker::LogPrefix::~LogPrefix() { QPID_LOG(notice, *this << "shut-down"); }
+
+Broker::Broker(const BrokerOptions& conf) :
+ poller(new Poller),
+ timer(new qpid::sys::Timer),
+ config(conf),
+ managementAgent(conf.enableMgmt ? new ManagementAgent(conf.qmf1Support,
+ conf.qmf2Support)
+ : 0),
+ disabledListeningTransports(conf.listenDisabled.begin(), conf.listenDisabled.end()),
+ store(new NullMessageStore),
+ acl(0),
+ dataDir(conf.noDataDir ? std::string() : conf.dataDir),
+ pagingDir(!conf.pagingDir.empty() ? conf.pagingDir :
+ dataDir.isEnabled() ? dataDir.getPath() + BrokerOptions::DEFAULT_PAGED_QUEUE_DIR :
+ std::string() ),
+ queues(this),
+ exchanges(this),
+ links(this),
+ dtxManager(*timer.get(), conf.dtxDefaultTimeout),
+ sessionManager(
+ qpid::SessionState::Configuration(
+ conf.replayFlushLimit*1024, // convert kb to bytes.
+ conf.replayHardLimit*1024),
+ *this),
+ queueCleaner(queues, poller, timer.get()),
+ recoveryInProgress(false),
+ protocolRegistry(std::set<std::string>(conf.protocols.begin(), conf.protocols.end()), this),
+ timestampRcvMsgs(conf.timestampRcvMsgs),
+ getKnownBrokers(boost::bind(&Broker::getKnownBrokersImpl, this))
+{
+ if (!dataDir.isEnabled()) {
+ QPID_LOG (info, "No data directory - Disabling persistent configuration");
+ }
+ try {
+ if (conf.enableMgmt) {
+ QPID_LOG(info, "Management enabled");
+ managementAgent->configure(dataDir.isEnabled() ? dataDir.getPath() : string(), conf.mgmtPublish,
+ conf.mgmtPubInterval/sys::TIME_SEC, this, conf.workerThreads + 3);
+ managementAgent->setName("apache.org", "qpidd");
+ _qmf::Package packageInitializer(managementAgent.get());
+
+ System* system = new System (dataDir.isEnabled() ? dataDir.getPath() : string(), this);
+ systemObject = System::shared_ptr(system);
+
+ mgmtObject = _qmf::Broker::shared_ptr(new _qmf::Broker(managementAgent.get(), this, system, "amqp-broker"));
+ mgmtObject->set_systemRef(system->GetManagementObject()->getObjectId());
+ mgmtObject->set_port(conf.port);
+ mgmtObject->set_workerThreads(conf.workerThreads);
+ mgmtObject->set_connBacklog(conf.connectionBacklog);
+ mgmtObject->set_mgmtPubInterval(conf.mgmtPubInterval/sys::TIME_SEC);
+ mgmtObject->set_mgmtPublish(conf.mgmtPublish);
+ mgmtObject->set_version(qpid::version);
+ if (dataDir.isEnabled())
+ mgmtObject->set_dataDir(dataDir.getPath());
+ else
+ mgmtObject->clr_dataDir();
+
+ managementAgent->addObject(mgmtObject, 0, true);
+
+ // Since there is currently no support for virtual hosts, a placeholder object
+ // representing the implied single virtual host is added here to keep the
+ // management schema correct.
+ Vhost* vhost = new Vhost(this, this);
+ vhostObject = Vhost::shared_ptr(vhost);
+ if (conf.fedTag.empty()) {
+ framing::Uuid uuid(managementAgent->getUuid());
+ federationTag = uuid.str();
+ } else
+ federationTag = conf.fedTag;
+ vhostObject->setFederationTag(federationTag);
+
+ queues.setParent(vhost);
+ exchanges.setParent(vhost);
+ links.setParent(vhost);
+ } else {
+ // Management is disabled so there is no broker management ID.
+ // Create a unique uuid to use as the federation tag.
+ if (conf.fedTag.empty()) {
+ framing::Uuid uuid(true);
+ federationTag = uuid.str();
+ } else
+ federationTag = conf.fedTag;
+ }
+
+ // Early-Initialize plugins
+ Plugin::earlyInitAll(*this);
+
+ QueueFlowLimit::setDefaults(conf.queueLimit, conf.queueFlowStopRatio, conf.queueFlowResumeRatio);
+ MessageGroupManager::setDefaults(conf.defaultMsgGroup);
+
+ // If no plugin store module registered itself, set up the null store.
+ if (NullMessageStore::isNullStore(store.get()))
+ setStore();
+
+ framing::FieldTable args;
+
+ // Default exchnge is not replicated.
+ exchanges.declare(empty, DirectExchange::typeName, false, false, noReplicateArgs());
+
+ RecoveredObjects objects;
+ if (store.get() != 0) {
+ RecoveryManagerImpl recoverer(
+ queues, exchanges, links, dtxManager, protocolRegistry, objects);
+ recoveryInProgress = true;
+ store->recover(recoverer);
+ recoveryInProgress = false;
+ }
+
+ //ensure standard exchanges exist (done after recovery from store)
+ declareStandardExchange(amq_direct, DirectExchange::typeName);
+ declareStandardExchange(amq_topic, TopicExchange::typeName);
+ declareStandardExchange(amq_fanout, FanOutExchange::typeName);
+ declareStandardExchange(amq_match, HeadersExchange::typeName);
+
+ if(conf.enableMgmt) {
+ exchanges.declare(qpid_management, ManagementTopicExchange::typeName, false, false, noReplicateArgs());
+ Exchange::shared_ptr mExchange = exchanges.get(qpid_management);
+ Exchange::shared_ptr dExchange = exchanges.get(amq_direct);
+ managementAgent->setExchange(mExchange, dExchange);
+ boost::dynamic_pointer_cast<ManagementTopicExchange>(mExchange)->setManagmentAgent(managementAgent.get(), 1);
+
+ std::string qmfTopic("qmf.default.topic");
+ std::string qmfDirect("qmf.default.direct");
+
+ std::pair<Exchange::shared_ptr, bool> topicPair(
+ exchanges.declare(qmfTopic, ManagementTopicExchange::typeName, false, false, noReplicateArgs()));
+ std::pair<Exchange::shared_ptr, bool> directPair(
+ exchanges.declare(qmfDirect, ManagementDirectExchange::typeName, false, false, noReplicateArgs()));
+
+ boost::dynamic_pointer_cast<ManagementDirectExchange>(directPair.first)->setManagmentAgent(managementAgent.get(), 2);
+ boost::dynamic_pointer_cast<ManagementTopicExchange>(topicPair.first)->setManagmentAgent(managementAgent.get(), 2);
+
+ managementAgent->setExchangeV2(topicPair.first, directPair.first);
+ }
+ else
+ QPID_LOG(info, "Management not enabled");
+
+ // this feature affects performance, so let's be sure that gets logged!
+ if (conf.timestampRcvMsgs) {
+ QPID_LOG(notice, "Receive message timestamping is ENABLED.");
+ }
+
+ /**
+ * SASL setup, can fail and terminate startup
+ */
+ if (conf.auth) {
+ SaslAuthenticator::init(qpid::saslName, conf.saslConfigPath);
+ QPID_LOG(info, "SASL enabled");
+ } else {
+ QPID_LOG(notice, "SASL disabled: No Authentication Performed");
+ }
+
+ // Initialize plugins
+ Plugin::initializeAll(*this);
+ //recover any objects via object factories
+ objects.restore(*this);
+
+ // Assign to queues their users who created them (can be done after ACL is loaded in Plugin::initializeAll above
+ if ((getAcl()) && (store.get())) {
+ queues.eachQueue(boost::bind(&qpid::broker::Queue::updateAclUserQueueCount, _1));
+ }
+
+ if(conf.enableMgmt) {
+ if (getAcl()) {
+ mgmtObject->set_maxConns(getAcl()->getMaxConnectTotal());
+ }
+ }
+
+ if (conf.queueCleanInterval) {
+ queueCleaner.start(conf.queueCleanInterval);
+ }
+
+ if (!conf.knownHosts.empty() && conf.knownHosts != knownHostsNone) {
+ knownBrokers.push_back(Url(conf.knownHosts));
+ }
+
+ } catch (const std::exception& e) {
+ QPID_LOG(critical, logPrefix << "start-up failed: " << e.what());
+ finalize();
+ throw;
+ }
+ QPID_LOG(info, logPrefix << "initialized");
+}
+
+void Broker::declareStandardExchange(const std::string& name, const std::string& type)
+{
+ bool storeEnabled = store.get() != NULL;
+ framing::FieldTable args;
+ // Standard exchanges are not replicated.
+ std::pair<Exchange::shared_ptr, bool> status =
+ exchanges.declare(name, type, storeEnabled, false, noReplicateArgs());
+ if (status.second && storeEnabled) {
+ store->create(*status.first, framing::FieldTable ());
+ }
+}
+
+bool Broker::isAuthenticating() const
+{
+ return config.auth;
+}
+
+bool Broker::requireEncrypted() const
+{
+ return config.requireEncrypted;
+}
+
+std::string Broker::getRealm() const
+{
+ return config.realm;
+}
+
+std::string Broker::getSaslServiceName() const
+{
+ return config.saslServiceName;
+}
+
+bool Broker::getTcpNoDelay() const
+{
+ return config.tcpNoDelay;
+}
+
+uint32_t Broker::getMaxNegotiateTime() const
+{
+ return config.maxNegotiateTime;
+}
+
+uint16_t Broker::getPortOption() const
+{
+ return config.port;
+}
+
+const std::vector<std::string>& Broker::getListenInterfaces() const
+{
+ return config.listenInterfaces;
+}
+
+int Broker::getConnectionBacklog() const
+{
+ return config.connectionBacklog;
+}
+
+sys::Duration Broker::getLinkMaintenanceInterval() const
+{
+ return config.linkMaintenanceInterval;
+}
+
+sys::Duration Broker::getLinkHeartbeatInterval() const
+{
+ return config.linkHeartbeatInterval;
+}
+
+uint32_t Broker::getDtxMaxTimeout() const
+{
+ return config.dtxMaxTimeout;
+}
+
+uint16_t Broker::getQueueThresholdEventRatio() const
+{
+ return config.queueThresholdEventRatio;
+}
+
+uint Broker::getQueueLimit() const
+{
+ return config.queueLimit;
+}
+
+boost::intrusive_ptr<Broker> Broker::create(int16_t port)
+{
+ BrokerOptions config;
+ config.port=port;
+ return create(config);
+}
+
+boost::intrusive_ptr<Broker> Broker::create(const BrokerOptions& opts)
+{
+ return boost::intrusive_ptr<Broker>(new Broker(opts));
+}
+
+void Broker::setStore (const boost::shared_ptr<MessageStore>& _store)
+{
+ // Exit now if multiple store plugins are attempting to load
+ if (!NullMessageStore::isNullStore(store.get())) {
+ QPID_LOG(error, "Multiple store plugins are not supported");
+ throw Exception(QPID_MSG("Failed to start broker: Multiple store plugins were loaded"));
+ }
+
+ store.reset(new MessageStoreModule (_store));
+ setStore();
+}
+
+void Broker::setStore () {
+ queues.setStore (store.get());
+ dtxManager.setStore (store.get());
+ links.setStore (store.get());
+}
+
+void Broker::run() {
+ if (config.workerThreads > 0) {
+ QPID_LOG(info, logPrefix << "running");
+ Dispatcher d(poller);
+ int numIOThreads = config.workerThreads;
+ std::vector<Thread> t(numIOThreads-1);
+
+ // Run n-1 io threads
+ for (int i=0; i<numIOThreads-1; ++i)
+ t[i] = Thread(d);
+
+ // Run final thread
+ d.run();
+
+ // Now wait for n-1 io threads to exit
+ for (int i=0; i<numIOThreads-1; ++i) {
+ t[i].join();
+ }
+ QPID_LOG(info, logPrefix << "stopped");
+ } else {
+ throw Exception((boost::format("Invalid value for worker-threads: %1%") % config.workerThreads).str());
+ }
+}
+
+void Broker::shutdown() {
+ // NB: this function must be async-signal safe, it must not
+ // call any function that is not async-signal safe.
+ // Any unsafe shutdown actions should be done in the destructor.
+ poller->shutdown();
+}
+
+Broker::~Broker() {
+ QPID_LOG(info, logPrefix << "shutting down");
+ if (mgmtObject != 0)
+ mgmtObject->debugStats("destroying");
+ shutdown();
+ finalize(); // Finalize any plugins.
+ if (config.auth)
+ SaslAuthenticator::fini();
+ timer->stop();
+ managementAgent.reset();
+}
+
+ManagementObject::shared_ptr Broker::GetManagementObject(void) const
+{
+ return mgmtObject;
+}
+
+Manageable* Broker::GetVhostObject(void) const
+{
+ return vhostObject.get();
+}
+
+Manageable::status_t Broker::ManagementMethod (uint32_t methodId,
+ Args& args,
+ string& text)
+{
+ Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD;
+
+ switch (methodId)
+ {
+ case _qmf::Broker::METHOD_ECHO :
+ QPID_LOG (debug, "Broker::echo("
+ << dynamic_cast<_qmf::ArgsBrokerEcho&>(args).io_sequence
+ << ", "
+ << dynamic_cast<_qmf::ArgsBrokerEcho&>(args).io_body
+ << ")");
+ status = Manageable::STATUS_OK;
+ break;
+ case _qmf::Broker::METHOD_CONNECT : {
+ /** Management is creating a Link to a remote broker using the host and port of
+ * the remote. This (old) interface does not allow management to specify a name
+ * for the link, nor does it allow multiple Links to the same remote. Use the
+ * "create()" broker method if these features are needed.
+ * TBD: deprecate this interface.
+ */
+ QPID_LOG(info, "The Broker::connect() method will be removed in a future release of QPID."
+ " Please use the Broker::create() method with type='link' instead.");
+ _qmf::ArgsBrokerConnect& hp=
+ dynamic_cast<_qmf::ArgsBrokerConnect&>(args);
+
+ string transport = hp.i_transport.empty() ? TCP_TRANSPORT : hp.i_transport;
+ QPID_LOG (debug, "Broker::connect() " << hp.i_host << ":" << hp.i_port << "; transport=" << transport <<
+ "; durable=" << (hp.i_durable?"T":"F") << "; authMech=\"" << hp.i_authMechanism << "\"");
+ if (!getTransportInfo(transport).connectorFactory) {
+ QPID_LOG(error, "Transport '" << transport << "' not supported");
+ text = "transport type not supported";
+ return Manageable::STATUS_NOT_IMPLEMENTED;
+ }
+
+ // Does a link to the remote already exist? If so, re-use the existing link
+ // - this behavior is backward compatible with previous releases.
+ if (!links.getLink(hp.i_host, hp.i_port, transport)) {
+ // new link, need to generate a unique name for it
+ std::pair<Link::shared_ptr, bool> response =
+ links.declare(Link::createName(transport, hp.i_host, hp.i_port),
+ hp.i_host, hp.i_port, transport,
+ hp.i_durable, hp.i_authMechanism, hp.i_username, hp.i_password);
+ if (!response.first) {
+ text = "Unable to create Link";
+ status = Manageable::STATUS_PARAMETER_INVALID;
+ break;
+ }
+ }
+ status = Manageable::STATUS_OK;
+ break;
+ }
+ case _qmf::Broker::METHOD_QUEUEMOVEMESSAGES : {
+ _qmf::ArgsBrokerQueueMoveMessages& moveArgs=
+ dynamic_cast<_qmf::ArgsBrokerQueueMoveMessages&>(args);
+ QPID_LOG (debug, "Broker::queueMoveMessages()");
+ if (queueMoveMessages(moveArgs.i_srcQueue, moveArgs.i_destQueue, moveArgs.i_qty,
+ moveArgs.i_filter, getCurrentPublisher()) >=0)
+ status = Manageable::STATUS_OK;
+ else
+ return Manageable::STATUS_PARAMETER_INVALID;
+ break;
+ }
+ case _qmf::Broker::METHOD_SETLOGLEVEL :
+ setLogLevel(dynamic_cast<_qmf::ArgsBrokerSetLogLevel&>(args).i_level);
+ QPID_LOG (debug, "Broker::setLogLevel()");
+ status = Manageable::STATUS_OK;
+ break;
+ case _qmf::Broker::METHOD_GETLOGLEVEL :
+ dynamic_cast<_qmf::ArgsBrokerGetLogLevel&>(args).o_level = getLogLevel();
+ QPID_LOG (debug, "Broker::getLogLevel()");
+ status = Manageable::STATUS_OK;
+ break;
+ case _qmf::Broker::METHOD_CREATE :
+ {
+ _qmf::ArgsBrokerCreate& a = dynamic_cast<_qmf::ArgsBrokerCreate&>(args);
+ createObject(a.i_type, a.i_name, a.i_properties, a.i_strict, getCurrentPublisher());
+ status = Manageable::STATUS_OK;
+ break;
+ }
+ case _qmf::Broker::METHOD_DELETE :
+ {
+ _qmf::ArgsBrokerDelete& a = dynamic_cast<_qmf::ArgsBrokerDelete&>(args);
+ deleteObject(a.i_type, a.i_name, a.i_options, getCurrentPublisher());
+ status = Manageable::STATUS_OK;
+ break;
+ }
+ case _qmf::Broker::METHOD_QUERY :
+ {
+ _qmf::ArgsBrokerQuery& a = dynamic_cast<_qmf::ArgsBrokerQuery&>(args);
+ status = queryObject(a.i_type, a.i_name, a.o_results, getCurrentPublisher());
+ break;
+ }
+ case _qmf::Broker::METHOD_GETTIMESTAMPCONFIG:
+ {
+ _qmf::ArgsBrokerGetTimestampConfig& a = dynamic_cast<_qmf::ArgsBrokerGetTimestampConfig&>(args);
+ status = getTimestampConfig(a.o_receive, getCurrentPublisher());
+ break;
+ }
+ case _qmf::Broker::METHOD_SETTIMESTAMPCONFIG:
+ {
+ _qmf::ArgsBrokerSetTimestampConfig& a = dynamic_cast<_qmf::ArgsBrokerSetTimestampConfig&>(args);
+ status = setTimestampConfig(a.i_receive, getCurrentPublisher());
+ break;
+ }
+
+ case _qmf::Broker::METHOD_GETLOGHIRESTIMESTAMP:
+ {
+ dynamic_cast<_qmf::ArgsBrokerGetLogHiresTimestamp&>(args).o_logHires = getLogHiresTimestamp();
+ QPID_LOG (debug, "Broker::getLogHiresTimestamp()");
+ status = Manageable::STATUS_OK;
+ break;
+ }
+ case _qmf::Broker::METHOD_SETLOGHIRESTIMESTAMP:
+ {
+ setLogHiresTimestamp(dynamic_cast<_qmf::ArgsBrokerSetLogHiresTimestamp&>(args).i_logHires);
+ QPID_LOG (debug, "Broker::setLogHiresTimestamp()");
+ status = Manageable::STATUS_OK;
+ break;
+ }
+ case _qmf::Broker::METHOD_QUEUEREDIRECT:
+ {
+ string srcQueue(dynamic_cast<_qmf::ArgsBrokerQueueRedirect&>(args).i_sourceQueue);
+ string tgtQueue(dynamic_cast<_qmf::ArgsBrokerQueueRedirect&>(args).i_targetQueue);
+ QPID_LOG (debug, "Broker::queueRedirect source queue:" << srcQueue << " to target queue " << tgtQueue);
+ status = queueRedirect(srcQueue, tgtQueue, getCurrentPublisher());
+ break;
+ }
+ case _qmf::Broker::METHOD_SHUTDOWN :
+ {
+ QPID_LOG (info, "Broker received shutdown command");
+ shutdown();
+ }
+ default:
+ QPID_LOG (debug, "Broker ManagementMethod not implemented: id=" << methodId << "]");
+ status = Manageable::STATUS_NOT_IMPLEMENTED;
+ break;
+ }
+
+ return status;
+}
+
+namespace
+{
+const std::string TYPE_QUEUE("queue");
+const std::string TYPE_EXCHANGE("exchange");
+const std::string TYPE_TOPIC("topic");
+const std::string TYPE_BINDING("binding");
+const std::string TYPE_LINK("link");
+const std::string TYPE_BRIDGE("bridge");
+const std::string DURABLE("durable");
+const std::string AUTO_DELETE("auto-delete");
+const std::string ALTERNATE_EXCHANGE("alternate-exchange");
+const std::string EXCHANGE_TYPE("exchange-type");
+const std::string QUEUE_NAME("queue");
+const std::string EXCHANGE_NAME("exchange");
+
+const std::string ATTRIBUTE_TIMESTAMP_0_10("timestamp-0.10");
+
+const std::string _TRUE("true");
+const std::string _FALSE("false");
+
+// parameters for creating a Link object, see mgmt schema
+const std::string HOST("host");
+const std::string PORT("port");
+const std::string TRANSPORT("transport");
+const std::string AUTH_MECHANISM("authMechanism");
+const std::string USERNAME("username");
+const std::string PASSWORD("password");
+
+// parameters for creating a Bridge object, see mgmt schema
+const std::string LINK("link");
+const std::string SRC("src");
+const std::string DEST("dest");
+const std::string KEY("key");
+const std::string TAG("tag");
+const std::string EXCLUDES("excludes");
+const std::string SRC_IS_QUEUE("srcIsQueue");
+const std::string SRC_IS_LOCAL("srcIsLocal");
+const std::string DYNAMIC("dynamic");
+const std::string SYNC("sync");
+const std::string CREDIT("credit");
+
+// parameters for deleting a Queue object
+const std::string IF_EMPTY("if_empty");
+const std::string IF_UNUSED("if_unused");
+}
+
+struct InvalidBindingIdentifier : public qpid::Exception
+{
+ InvalidBindingIdentifier(const std::string& name) : qpid::Exception(name) {}
+ std::string getPrefix() const { return "invalid binding"; }
+};
+
+struct BindingIdentifier
+{
+ std::string exchange;
+ std::string queue;
+ std::string key;
+
+ BindingIdentifier(const std::string& name)
+ {
+ std::vector<std::string> path;
+ split(path, name, "/");
+ switch (path.size()) {
+ case 1:
+ queue = path[0];
+ break;
+ case 2:
+ exchange = path[0];
+ queue = path[1];
+ break;
+ case 3:
+ exchange = path[0];
+ queue = path[1];
+ key = path[2];
+ break;
+ default:
+ throw InvalidBindingIdentifier(name);
+ }
+ }
+};
+
+struct ObjectAlreadyExists : public qpid::Exception
+{
+ ObjectAlreadyExists(const std::string& name) : qpid::Exception(name) {}
+ std::string getPrefix() const { return "object already exists"; }
+};
+
+struct UnknownObjectType : public qpid::Exception
+{
+ UnknownObjectType(const std::string& type) : qpid::Exception(type) {}
+ std::string getPrefix() const { return "unknown object type"; }
+};
+
+struct ReservedObjectName : public qpid::Exception
+{
+ ReservedObjectName(const std::string& type) : qpid::Exception(type) {}
+ std::string getPrefix() const { return std::string("names prefixed with '")
+ + QPID_NAME_PREFIX + std::string("' are reserved"); }
+};
+
+struct UnsupportedTransport : public qpid::Exception
+{
+ UnsupportedTransport(const std::string& type) : qpid::Exception(type) {}
+ std::string getPrefix() const { return "transport is not supported"; }
+};
+
+struct InvalidParameter : public qpid::Exception
+{
+ InvalidParameter(const std::string& type) : qpid::Exception(type) {}
+ std::string getPrefix() const { return "invalid parameter to method call"; }
+};
+
+void Broker::createObject(const std::string& type, const std::string& name,
+ const Variant::Map& properties, bool /*strict*/, const Connection* context)
+{
+ std::string userId;
+ std::string connectionId;
+ if (context) {
+ userId = context->getUserId();
+ connectionId = context->getMgmtId();
+ }
+ //TODO: implement 'strict' option (check there are no unrecognised properties)
+ QPID_LOG (debug, "Broker::create(" << type << ", " << name << "," << properties << ")");
+ if (objectFactory.createObject(*this, type, name, properties, userId, connectionId)) {
+ QPID_LOG (debug, "Broker::create(" << type << ", " << name << "," << properties << ") handled by registered factory");
+ } else if (type == TYPE_QUEUE) {
+ bool durable(false);
+ bool autodelete(false);
+ std::string alternateExchange;
+ Variant::Map extensions;
+ for (Variant::Map::const_iterator i = properties.begin(); i != properties.end(); ++i) {
+ // extract durable, auto-delete and alternate-exchange properties
+ if (i->first == DURABLE) durable = i->second;
+ else if (i->first == AUTO_DELETE) autodelete = i->second;
+ else if (i->first == ALTERNATE_EXCHANGE) alternateExchange = i->second.asString();
+ //treat everything else as extension properties
+ else extensions[i->first] = i->second;
+ }
+ QueueSettings settings(durable, autodelete);
+ Variant::Map unused;
+ settings.populate(extensions, unused);
+ qpid::amqp_0_10::translate(unused, settings.storeSettings);
+ //TODO: unused doesn't take store settings into account... so can't yet implement strict
+ QPID_LOG(debug, "Broker did not use the following settings (store module may): " << unused);
+
+ std::pair<boost::shared_ptr<Queue>, bool> result =
+ createQueue(name, settings, 0, alternateExchange, userId, connectionId);
+ if (!result.second) {
+ throw ObjectAlreadyExists(name);
+ }
+ } else if (type == TYPE_EXCHANGE || type == TYPE_TOPIC) {
+ bool durable(false);
+ bool autodelete(false);
+ std::string exchangeType("topic");
+ std::string alternateExchange;
+ Variant::Map extensions;
+ for (Variant::Map::const_iterator i = properties.begin(); i != properties.end(); ++i) {
+ // extract durable, auto-delete and alternate-exchange properties
+ if (i->first == DURABLE) durable = i->second;
+ else if (i->first == AUTO_DELETE) autodelete = i->second;
+ else if (i->first == EXCHANGE_TYPE) exchangeType = i->second.asString();
+ else if (i->first == ALTERNATE_EXCHANGE) alternateExchange = i->second.asString();
+ //treat everything else as extension properties
+ else extensions[i->first] = i->second;
+ }
+ framing::FieldTable arguments;
+ qpid::amqp_0_10::translate(extensions, arguments);
+
+ try {
+ std::pair<boost::shared_ptr<Exchange>, bool> result =
+ createExchange(name, exchangeType, durable, autodelete, alternateExchange, arguments, userId, connectionId);
+ if (!result.second) {
+ throw ObjectAlreadyExists(name);
+ }
+ } catch (const UnknownExchangeTypeException&) {
+ throw Exception(QPID_MSG("Invalid exchange type: " << exchangeType));
+ }
+ } else if (type == TYPE_BINDING) {
+ BindingIdentifier binding(name);
+ std::string exchangeType("topic");
+ Variant::Map extensions;
+ for (Variant::Map::const_iterator i = properties.begin(); i != properties.end(); ++i) {
+ // extract durable, auto-delete and alternate-exchange properties
+ if (i->first == EXCHANGE_TYPE) exchangeType = i->second.asString();
+ //treat everything else as extension properties
+ else extensions[i->first] = i->second;
+ }
+ framing::FieldTable arguments;
+ qpid::amqp_0_10::translate(extensions, arguments);
+
+ bind(binding.queue, binding.exchange, binding.key, arguments, 0, userId, connectionId);
+
+ } else if (type == TYPE_LINK) {
+
+ QPID_LOG (debug, "createObject: Link; name=" << name << "; args=" << properties );
+
+ if (name.compare(0, QPID_NAME_PREFIX.length(), QPID_NAME_PREFIX) == 0) {
+ QPID_LOG(error, "Link name='" << name << "' cannot use the reserved prefix '" << QPID_NAME_PREFIX << "'");
+ throw ReservedObjectName(name);
+ }
+
+ std::string host;
+ uint16_t port = 0;
+ std::string transport = TCP_TRANSPORT;
+ bool durable = false;
+ std::string authMech, username, password;
+
+ for (Variant::Map::const_iterator i = properties.begin(); i != properties.end(); ++i) {
+ if (i->first == HOST) host = i->second.asString();
+ else if (i->first == PORT) port = i->second.asUint16();
+ else if (i->first == TRANSPORT) transport = i->second.asString();
+ else if (i->first == DURABLE) durable = bool(i->second);
+ else if (i->first == AUTH_MECHANISM) authMech = i->second.asString();
+ else if (i->first == USERNAME) username = i->second.asString();
+ else if (i->first == PASSWORD) password = i->second.asString();
+ else {
+ // TODO: strict checking here
+ }
+ }
+
+ if (!getTransportInfo(transport).connectorFactory) {
+ QPID_LOG(error, "Transport '" << transport << "' not supported.");
+ throw UnsupportedTransport(transport);
+ }
+
+ std::pair<boost::shared_ptr<Link>, bool> rc;
+ rc = links.declare(name, host, port, transport, durable, authMech, username, password);
+ if (!rc.first) {
+ QPID_LOG (error, "Failed to create Link object, name=" << name << " remote=" << host << ":" << port <<
+ "; transport=" << transport << "; durable=" << (durable?"T":"F") << "; authMech=\"" << authMech << "\"");
+ throw InvalidParameter(name);
+ }
+ if (!rc.second) {
+ QPID_LOG (error, "Failed to create a new Link object, name=" << name << " already exists.");
+ throw ObjectAlreadyExists(name);
+ }
+
+ } else if (type == TYPE_BRIDGE) {
+
+ QPID_LOG (debug, "createObject: Bridge; name=" << name << "; args=" << properties );
+
+ if (name.compare(0, QPID_NAME_PREFIX.length(), QPID_NAME_PREFIX) == 0) {
+ QPID_LOG(error, "Bridge name='" << name << "' cannot use the reserved prefix '" << QPID_NAME_PREFIX << "'");
+ throw ReservedObjectName(name);
+ }
+
+ std::string linkName;
+ std::string src;
+ std::string dest;
+ std::string key;
+ std::string id;
+ std::string excludes;
+ std::string queueName;
+ bool durable = false;
+ bool srcIsQueue = false;
+ bool srcIsLocal = false;
+ bool dynamic = false;
+ uint16_t sync = 0;
+ uint32_t credit = LinkRegistry::INFINITE_CREDIT;
+
+ for (Variant::Map::const_iterator i = properties.begin(); i != properties.end(); ++i) {
+
+ if (i->first == LINK) linkName = i->second.asString();
+ else if (i->first == SRC) src = i->second.asString();
+ else if (i->first == DEST) dest = i->second.asString();
+ else if (i->first == KEY) key = i->second.asString();
+ else if (i->first == TAG) id = i->second.asString();
+ else if (i->first == EXCLUDES) excludes = i->second.asString();
+ else if (i->first == SRC_IS_QUEUE) srcIsQueue = bool(i->second);
+ else if (i->first == SRC_IS_LOCAL) srcIsLocal = bool(i->second);
+ else if (i->first == DYNAMIC) dynamic = bool(i->second);
+ else if (i->first == SYNC) sync = i->second.asUint16();
+ else if (i->first == CREDIT) credit = i->second.asUint32();
+ else if (i->first == DURABLE) durable = bool(i->second);
+ else if (i->first == QUEUE_NAME) queueName = i->second.asString();
+ else {
+ // TODO: strict checking here
+ }
+ }
+
+ boost::shared_ptr<Link> link;
+ if (linkName.empty() || !(link = links.getLink(linkName))) {
+ QPID_LOG(error, "Link '" << linkName << "' not found; bridge create failed.");
+ throw InvalidParameter(name);
+ }
+ std::pair<Bridge::shared_ptr, bool> rc =
+ links.declare(name, *link, durable, src, dest, key, srcIsQueue, srcIsLocal, id, excludes,
+ dynamic, sync, credit,
+ 0,
+ queueName);
+
+ if (!rc.first) {
+ QPID_LOG (error, "Failed to create Bridge object, name=" << name << " link=" << linkName <<
+ "; src=" << src << "; dest=" << dest << "; key=" << key);
+ throw InvalidParameter(name);
+ }
+ if (!rc.second) {
+ QPID_LOG (error, "Failed to create a new Bridge object, name=" << name << " already exists.");
+ throw ObjectAlreadyExists(name);
+ }
+ } else {
+ throw UnknownObjectType(type);
+ }
+}
+
+void Broker::deleteObject(const std::string& type, const std::string& name,
+ const Variant::Map& options, const Connection* context)
+{
+ std::string userId;
+ std::string connectionId;
+ if (context) {
+ userId = context->getUserId();
+ connectionId = context->getMgmtId();
+ }
+ QPID_LOG (debug, "Broker::delete(" << type << ", " << name << "," << options << ")");
+ if (objectFactory.deleteObject(*this, type, name, options, userId, connectionId)) {
+ QPID_LOG (debug, "Broker::delete(" << type << ", " << name << "," << options << ") handled by registered factory");
+ } else if (type == TYPE_QUEUE) {
+ // extract ifEmpty and ifUnused from options
+ bool ifUnused = false, ifEmpty = false;
+ for (Variant::Map::const_iterator i = options.begin(); i != options.end(); ++i) {
+ if (i->first == IF_UNUSED) ifUnused = i->second.asBool();
+ else if (i->first == IF_EMPTY) ifEmpty = i->second.asBool();
+ }
+ deleteQueue(name, userId, connectionId,
+ boost::bind(&Broker::checkDeleteQueue, this, _1, ifUnused, ifEmpty));
+ } else if (type == TYPE_EXCHANGE || type == TYPE_TOPIC) {
+ deleteExchange(name, userId, connectionId);
+ } else if (type == TYPE_BINDING) {
+ BindingIdentifier binding(name);
+ unbind(binding.queue, binding.exchange, binding.key, 0, userId, connectionId);
+ } else if (type == TYPE_LINK) {
+ boost::shared_ptr<Link> link = links.getLink(name);
+ if (link) {
+ link->close();
+ }
+ } else if (type == TYPE_BRIDGE) {
+ boost::shared_ptr<Bridge> bridge = links.getBridge(name);
+ if (bridge) {
+ bridge->close();
+ }
+ } else {
+ throw UnknownObjectType(type);
+ }
+}
+
+void Broker::checkDeleteQueue(Queue::shared_ptr queue, bool ifUnused, bool ifEmpty)
+{
+ if(ifEmpty && queue->getMessageCount() > 0) {
+ throw qpid::framing::PreconditionFailedException(QPID_MSG("Cannot delete queue "
+ << queue->getName() << "; queue not empty"));
+ } else if(ifUnused && queue->getConsumerCount() > 0) {
+ throw qpid::framing::PreconditionFailedException(QPID_MSG("Cannot delete queue "
+ << queue->getName() << "; queue in use"));
+ }
+}
+
+Manageable::status_t Broker::queryObject(const std::string& type,
+ const std::string& name,
+ Variant::Map& results,
+ const Connection* context)
+{
+ std::string userId;
+ std::string connectionId;
+ if (context) {
+ userId = context->getUserId();
+ connectionId = context->getMgmtId();
+ }
+ QPID_LOG (debug, "Broker::query(" << type << ", " << name << ")");
+
+ if (type == TYPE_QUEUE)
+ return queryQueue( name, userId, connectionId, results );
+
+ if (type == TYPE_EXCHANGE ||
+ type == TYPE_TOPIC ||
+ type == TYPE_BINDING)
+ return Manageable::STATUS_NOT_IMPLEMENTED;
+
+ throw UnknownObjectType(type);
+}
+
+Manageable::status_t Broker::queryQueue( const std::string& name,
+ const std::string& userId,
+ const std::string& /*connectionId*/,
+ Variant::Map& results )
+{
+ (void) results;
+ if (acl) {
+ if (!acl->authorise(userId, acl::ACT_ACCESS, acl::OBJ_QUEUE, name, NULL) )
+ throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied queue query request from " << userId));
+ }
+
+ boost::shared_ptr<Queue> q(queues.find(name));
+ if (!q) {
+ QPID_LOG(error, "Query failed: queue not found, name=" << name);
+ return Manageable::STATUS_UNKNOWN_OBJECT;
+ }
+ q->query( results );
+ return Manageable::STATUS_OK;;
+}
+
+Manageable::status_t Broker::getTimestampConfig(bool& receive,
+ const Connection* context)
+{
+ std::string name; // none needed for broker
+ std::string userId = context->getUserId();
+ if (acl && !acl->authorise(userId, acl::ACT_ACCESS, acl::OBJ_BROKER, name, NULL)) {
+ throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied broker timestamp get request from " << userId));
+ }
+ receive = timestampRcvMsgs;
+ return Manageable::STATUS_OK;
+}
+
+Manageable::status_t Broker::setTimestampConfig(const bool receive,
+ const Connection* context)
+{
+ std::string name; // none needed for broker
+ std::string userId = context->getUserId();
+ if (acl && !acl->authorise(userId, acl::ACT_UPDATE, acl::OBJ_BROKER, name, NULL)) {
+ throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied broker timestamp set request from " << userId));
+ }
+ timestampRcvMsgs = receive;
+ QPID_LOG(notice, "Receive message timestamping is " << ((timestampRcvMsgs) ? "ENABLED." : "DISABLED."));
+ return Manageable::STATUS_OK;
+}
+
+void Broker::setLogLevel(const std::string& level)
+{
+ QPID_LOG(notice, "Changing log level to " << level);
+ std::vector<std::string> selectors;
+ split(selectors, level, ", ");
+ qpid::log::Logger::instance().reconfigure(selectors);
+}
+
+std::string Broker::getLogLevel()
+{
+ std::string level;
+ std::string sep("");
+ const std::vector<std::string>& selectors = qpid::log::Logger::instance().getOptions().selectors;
+ for (std::vector<std::string>::const_iterator i = selectors.begin(); i != selectors.end(); ++i) {
+ level += sep + *i;
+ sep = ",";
+ }
+ const std::vector<std::string>& disselectors = qpid::log::Logger::instance().getOptions().deselectors;
+ for (std::vector<std::string>::const_iterator i = disselectors.begin(); i != disselectors.end(); ++i) {
+ level += sep + "!" + *i;
+ sep = ",";
+ }
+ return level;
+}
+
+void Broker::setLogHiresTimestamp(bool enabled)
+{
+ QPID_LOG(notice, "Changing log hires timestamp to " << enabled);
+ qpid::log::Logger::instance().setHiresTimestamp(enabled);
+}
+
+bool Broker::getLogHiresTimestamp()
+{
+ return qpid::log::Logger::instance().getHiresTimestamp();
+}
+
+
+Manageable::status_t Broker::queueRedirect(const std::string& srcQueue,
+ const std::string& tgtQueue,
+ const Connection* context)
+{
+ Queue::shared_ptr srcQ(queues.find(srcQueue));
+ if (!srcQ) {
+ QPID_LOG(error, "Queue redirect failed: source queue not found: "
+ << srcQueue);
+ return Manageable::STATUS_UNKNOWN_OBJECT;
+ }
+
+ if (!tgtQueue.empty()) {
+ // NonBlank target queue creates partnership
+ Queue::shared_ptr tgtQ(queues.find(tgtQueue));
+ if (!tgtQ) {
+ QPID_LOG(error, "Queue redirect failed: target queue not found: "
+ << tgtQueue);
+ return Manageable::STATUS_UNKNOWN_OBJECT;
+ }
+
+ if (srcQueue.compare(tgtQueue) == 0) {
+ QPID_LOG(error, "Queue redirect source queue: "
+ << tgtQueue << " cannot be its own target");
+ return Manageable::STATUS_USER;
+ }
+
+ if (srcQ->isAutoDelete()) {
+ QPID_LOG(error, "Queue redirect source queue: "
+ << srcQueue << " is autodelete and can not be part of redirect");
+ return Manageable::STATUS_USER;
+ }
+
+ if (tgtQ->isAutoDelete()) {
+ QPID_LOG(error, "Queue redirect target queue: "
+ << tgtQueue << " is autodelete and can not be part of redirect");
+ return Manageable::STATUS_USER;
+ }
+
+ if (srcQ->getRedirectPeer()) {
+ QPID_LOG(error, "Queue redirect source queue: "
+ << srcQueue << " is already redirected");
+ return Manageable::STATUS_USER;
+ }
+
+ if (tgtQ->getRedirectPeer()) {
+ QPID_LOG(error, "Queue redirect target queue: "
+ << tgtQueue << " is already redirected");
+ return Manageable::STATUS_USER;
+ }
+
+ if (acl) {
+ std::map<acl::Property, std::string> params;
+ params.insert(make_pair(acl::PROP_QUEUENAME, tgtQ->getName()));
+ if (!acl->authorise((context)?context->getUserId():"", acl::ACT_REDIRECT, acl::OBJ_QUEUE, srcQ->getName(), &params))
+ throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied redirect request from " << ((context)?context->getUserId():"(uknown)")));
+ }
+
+ // Start the backup overflow partnership
+ srcQ->setRedirectPeer(tgtQ, true);
+ tgtQ->setRedirectPeer(srcQ, false);
+
+ // Set management state
+ srcQ->setMgmtRedirectState(tgtQueue, true, true);
+ tgtQ->setMgmtRedirectState(srcQueue, true, false);
+
+ // Management event
+ if (managementAgent.get()) {
+ managementAgent->raiseEvent(_qmf::EventQueueRedirect(srcQueue, tgtQueue));
+ }
+
+ QPID_LOG(info, "Queue redirect complete. queue: "
+ << srcQueue << " target queue: " << tgtQueue);
+ return Manageable::STATUS_OK;
+ } else {
+ // Blank target queue destroys partnership
+ Queue::shared_ptr tgtQ(srcQ->getRedirectPeer());
+ if (!tgtQ) {
+ QPID_LOG(error, "Queue redirect source queue: "
+ << srcQueue << " is not in redirected");
+ return Manageable::STATUS_USER;
+ }
+
+ if (!srcQ->isRedirectSource()) {
+ QPID_LOG(error, "Queue redirect source queue: "
+ << srcQueue << " is not a redirect source");
+ return Manageable::STATUS_USER;
+ }
+
+ if (acl) {
+ std::map<acl::Property, std::string> params;
+ params.insert(make_pair(acl::PROP_QUEUENAME, tgtQ->getName()));
+ if (!acl->authorise((context)?context->getUserId():"", acl::ACT_REDIRECT, acl::OBJ_QUEUE, srcQ->getName(), &params))
+ throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied redirect request from " << ((context)?context->getUserId():"(uknown)")));
+ }
+
+ queueRedirectDestroy(srcQ, tgtQ, true);
+
+ return Manageable::STATUS_OK;
+ }
+}
+
+
+void Broker::queueRedirectDestroy(Queue::shared_ptr srcQ,
+ Queue::shared_ptr tgtQ,
+ bool moveMsgs) {
+ QPID_LOG(notice, "Queue redirect destroyed. queue: " << srcQ->getName()
+ << " target queue: " << tgtQ->getName());
+
+ tgtQ->setMgmtRedirectState(empty, false, false);
+ srcQ->setMgmtRedirectState(empty, false, false);
+
+ if (moveMsgs) {
+ // TODO: this 'move' works in the static case but has no
+ // actual locking that does what redirect needs when
+ // there is a lot of traffic in flight.
+ tgtQ->move(srcQ, 0);
+ }
+
+ Queue::shared_ptr np;
+
+ tgtQ->setRedirectPeer(np, false);
+ srcQ->setRedirectPeer(np, false);
+
+ if (managementAgent.get()) {
+ managementAgent->raiseEvent(_qmf::EventQueueRedirectCancelled(srcQ->getName(), tgtQ->getName()));
+ }
+}
+
+const Broker::TransportInfo& Broker::getTransportInfo(const std::string& name) const {
+ static TransportInfo nullTransportInfo;
+ TransportMap::const_iterator i
+ = name.empty() ? transportMap.begin() : transportMap.find(name);
+ if (i == transportMap.end()) return nullTransportInfo;
+ else return i->second;
+}
+
+uint16_t Broker::getPort(const std::string& name) const {
+ if (int p = getTransportInfo(name).port) {
+ return p;
+ } else {
+ throw NoSuchTransportException(QPID_MSG("No such transport: '" << name << "'"));
+ }
+}
+
+bool Broker::shouldListen(std::string transport) {
+ return disabledListeningTransports.count(transport)==0;
+}
+
+void Broker::disableListening(std::string transport) {
+ disabledListeningTransports.insert(transport);
+}
+
+void Broker::registerTransport(const std::string& name, boost::shared_ptr<TransportAcceptor> a, boost::shared_ptr<TransportConnector> c, uint16_t p) {
+ transportMap[name] = TransportInfo(a, c, p);
+ Url::addProtocol(name);
+}
+
+void Broker::accept() {
+ unsigned accepting = 0;
+ for (TransportMap::const_iterator i = transportMap.begin(); i != transportMap.end(); i++) {
+ if (i->second.acceptor) {
+ i->second.acceptor->accept(poller, &protocolRegistry);
+ ++accepting;
+ }
+ }
+ if ( accepting==0 ) {
+ throw Exception(QPID_MSG("Failed to start broker: No transports are listening for incoming connections"));
+ }
+}
+
+void Broker::connect(
+ const std::string& name,
+ const std::string& host, const std::string& port, const std::string& transport,
+ boost::function2<void, int, std::string> failed)
+{
+ connect(name, host, port, transport, &protocolRegistry, failed);
+}
+
+void Broker::connect(
+ const std::string& name,
+ const std::string& host, const std::string& port, const std::string& transport,
+ sys::ConnectionCodec::Factory* f, boost::function2<void, int, std::string> failed)
+{
+ boost::shared_ptr<TransportConnector> tcf = getTransportInfo(transport).connectorFactory;
+ if (tcf) tcf->connect(poller, name, host, port, f, failed);
+ else throw NoSuchTransportException(QPID_MSG("Unsupported transport type: " << transport));
+}
+
+int32_t Broker::queueMoveMessages(
+ const std::string& srcQueue,
+ const std::string& destQueue,
+ uint32_t qty,
+ const Variant::Map& filter,
+ const Connection* context)
+{
+ Queue::shared_ptr src_queue = queues.find(srcQueue);
+ if (!src_queue)
+ return -1;
+ Queue::shared_ptr dest_queue = queues.find(destQueue);
+ if (!dest_queue)
+ return -1;
+
+ if (acl) {
+ std::map<acl::Property, std::string> params;
+ params.insert(make_pair(acl::PROP_QUEUENAME, dest_queue->getName()));
+ if (!acl->authorise((context)?context->getUserId():"", acl::ACT_MOVE, acl::OBJ_QUEUE, src_queue->getName(), &params))
+ throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied move request from " << ((context)?context->getUserId():"(uknown)")));
+ }
+
+ return (int32_t) src_queue->move(dest_queue, qty, &filter);
+}
+
+
+boost::shared_ptr<sys::Poller> Broker::getPoller() { return poller; }
+
+std::vector<Url>
+Broker::getKnownBrokersImpl()
+{
+ return knownBrokers;
+}
+
+bool Broker::deferDeliveryImpl(const std::string&, const Message&)
+{ return false; }
+
+const std::string Broker::TCP_TRANSPORT("tcp");
+
+std::pair<boost::shared_ptr<Queue>, bool> Broker::createQueue(
+ const std::string& name,
+ const QueueSettings& constSettings,
+ const OwnershipToken* owner,
+ const std::string& alternateExchange,
+ const std::string& userId,
+ const std::string& connectionId)
+{
+ QueueSettings settings(constSettings); // So we can modify them
+ if (acl) {
+ std::map<acl::Property, std::string> params;
+ params.insert(make_pair(acl::PROP_ALTERNATE, alternateExchange));
+ params.insert(make_pair(acl::PROP_DURABLE, settings.durable ? _TRUE : _FALSE));
+ params.insert(make_pair(acl::PROP_EXCLUSIVE, owner ? _TRUE : _FALSE));
+ params.insert(make_pair(acl::PROP_AUTODELETE, settings.autodelete ? _TRUE : _FALSE));
+ params.insert(make_pair(acl::PROP_POLICYTYPE, settings.getLimitPolicy()));
+ params.insert(make_pair(acl::PROP_PAGING, settings.paging ? _TRUE : _FALSE));
+ if (settings.paging) {
+ params.insert(make_pair(acl::PROP_MAXPAGES, boost::lexical_cast<string>(settings.maxPages ? settings.maxPages : DEFAULT_MAX_PAGES)));
+ params.insert(make_pair(acl::PROP_MAXPAGEFACTOR, boost::lexical_cast<string>(settings.pageFactor ? settings.pageFactor : DEFAULT_PAGE_FACTOR)));
+ }
+ if (settings.maxDepth.hasCount())
+ params.insert(make_pair(acl::PROP_MAXQUEUECOUNT, boost::lexical_cast<string>(settings.maxDepth.getCount() ? settings.maxDepth.getCount() : std::numeric_limits<uint64_t>::max())));
+ if (settings.maxDepth.hasSize())
+ params.insert(make_pair(acl::PROP_MAXQUEUESIZE, boost::lexical_cast<string>(settings.maxDepth.getSize() ? settings.maxDepth.getSize() : std::numeric_limits<uint64_t>::max())));
+ else
+ params.insert(make_pair(acl::PROP_MAXQUEUESIZE, boost::lexical_cast<string>(config.queueLimit)));
+ if (settings.durable) {
+ params.insert(make_pair(acl::PROP_MAXFILECOUNT, boost::lexical_cast<string>(settings.maxFileCount ? settings.maxFileCount : 8)));
+ params.insert(make_pair(acl::PROP_MAXFILESIZE, boost::lexical_cast<string>(settings.maxFileSize ? settings.maxFileSize : 24)));
+ }
+
+ if (!acl->authorise(userId,acl::ACT_CREATE,acl::OBJ_QUEUE,name,&params) )
+ throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied queue create request from " << userId));
+
+ if (!queues.find(name))
+ if (!acl->approveCreateQueue(userId,name) )
+ throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied queue create request from " << userId));
+ }
+
+ Exchange::shared_ptr alternate;
+ if (!alternateExchange.empty()) {
+ alternate = exchanges.get(alternateExchange);
+ if (!alternate) throw framing::NotFoundException(QPID_MSG("Alternate exchange does not exist: " << alternateExchange));
+ }
+
+ // Identify queues that won't survive a failover: exclusive, auto-delete with no delay.
+ if (owner && settings.autodelete && !settings.autoDeleteDelay)
+ settings.isTemporary = true;
+
+ std::pair<Queue::shared_ptr, bool> result =
+ queues.declare(name, settings, alternate, false/*recovering*/,
+ owner, connectionId, userId);
+ if (result.second) {
+ //add default binding:
+ result.first->bind(exchanges.getDefault(), name);
+ QPID_LOG_CAT(debug, model, "Create queue. name:" << name
+ << " user:" << userId
+ << " rhost:" << connectionId
+ << " durable:" << (settings.durable ? "T" : "F")
+ << " owner:" << owner
+ << " autodelete:" << (settings.autodelete ? "T" : "F")
+ << " alternateExchange:" << alternateExchange );
+ }
+ return result;
+}
+
+void Broker::deleteQueue(const std::string& name, const std::string& userId,
+ const std::string& connectionId, QueueFunctor check)
+{
+ QPID_LOG_CAT(debug, model, "Deleting queue. name:" << name
+ << " user:" << userId
+ << " rhost:" << connectionId
+ );
+ Queue::shared_ptr queue = queues.find(name);
+ if (queue) {
+ if (acl) {
+ std::map<acl::Property, std::string> params;
+ boost::shared_ptr<Exchange> altEx = queue->getAlternateExchange();
+ params.insert(make_pair(acl::PROP_ALTERNATE, (altEx) ? altEx->getName() : "" ));
+ params.insert(make_pair(acl::PROP_DURABLE, queue->isDurable() ? _TRUE : _FALSE));
+ params.insert(make_pair(acl::PROP_EXCLUSIVE, queue->hasExclusiveOwner() ? _TRUE : _FALSE));
+ params.insert(make_pair(acl::PROP_AUTODELETE, queue->isAutoDelete() ? _TRUE : _FALSE));
+ params.insert(make_pair(acl::PROP_POLICYTYPE, queue->getSettings().getLimitPolicy()));
+
+ if (!acl->authorise(userId,acl::ACT_DELETE,acl::OBJ_QUEUE,name,&params) )
+ throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied queue delete request from " << userId));
+ }
+ if (check) check(queue);
+ if (acl)
+ acl->recordDestroyQueue(name);
+ Queue::shared_ptr peerQ(queue->getRedirectPeer());
+ if (peerQ)
+ queueRedirectDestroy(queue->isRedirectSource() ? queue : peerQ,
+ queue->isRedirectSource() ? peerQ : queue,
+ false);
+ queues.destroy(name, connectionId, userId);
+ queue->destroyed();
+ } else {
+ throw framing::NotFoundException(QPID_MSG("Delete failed. No such queue: " << name));
+ }
+}
+
+std::pair<Exchange::shared_ptr, bool> Broker::createExchange(
+ const std::string& name,
+ const std::string& type,
+ bool durable,
+ bool autodelete,
+ const std::string& alternateExchange,
+ const qpid::framing::FieldTable& arguments,
+ const std::string& userId,
+ const std::string& connectionId)
+{
+ if (acl) {
+ std::map<acl::Property, std::string> params;
+ params.insert(make_pair(acl::PROP_TYPE, type));
+ params.insert(make_pair(acl::PROP_ALTERNATE, alternateExchange));
+ params.insert(make_pair(acl::PROP_DURABLE, durable ? _TRUE : _FALSE));
+ params.insert(make_pair(acl::PROP_AUTODELETE, autodelete ? _TRUE : _FALSE));
+ if (!acl->authorise(userId,acl::ACT_CREATE,acl::OBJ_EXCHANGE,name,&params) )
+ throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied exchange create request from " << userId));
+ }
+
+ Exchange::shared_ptr alternate;
+ if (!alternateExchange.empty()) {
+ alternate = exchanges.get(alternateExchange);
+ if (!alternate) throw framing::NotFoundException(QPID_MSG("Alternate exchange does not exist: " << alternateExchange));
+ }
+
+ std::pair<Exchange::shared_ptr, bool> result;
+ result = exchanges.declare(
+ name, type, durable, autodelete, arguments, alternate, connectionId, userId);
+ if (result.second) {
+ if (durable) {
+ store->create(*result.first, arguments);
+ }
+ QPID_LOG_CAT(debug, model, "Create exchange. name:" << name
+ << " user:" << userId
+ << " rhost:" << connectionId
+ << " type:" << type
+ << " alternateExchange:" << alternateExchange
+ << " durable:" << (durable ? "T" : "F")
+ << " autodelete:" << (autodelete ? "T" : "F"));
+ }
+ return result;
+}
+
+void Broker::deleteExchange(const std::string& name, const std::string& userId,
+ const std::string& connectionId)
+{
+ QPID_LOG_CAT(debug, model, "Deleting exchange. name:" << name
+ << " user:" << userId
+ << " rhost:" << connectionId);
+ if (name.empty()) {
+ throw framing::InvalidArgumentException(QPID_MSG("Delete not allowed for default exchange"));
+ }
+ Exchange::shared_ptr exchange(exchanges.get(name));
+ if (!exchange) throw framing::NotFoundException(QPID_MSG("Delete failed. No such exchange: " << name));
+
+ if (acl) {
+ std::map<acl::Property, std::string> params;
+ Exchange::shared_ptr altEx = exchange->getAlternate();
+ params.insert(make_pair(acl::PROP_TYPE, exchange->getType()));
+ params.insert(make_pair(acl::PROP_ALTERNATE, (altEx) ? altEx->getName() : "" ));
+ params.insert(make_pair(acl::PROP_DURABLE, exchange->isDurable() ? _TRUE : _FALSE));
+
+ if (!acl->authorise(userId,acl::ACT_DELETE,acl::OBJ_EXCHANGE,name,&params) )
+ throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied exchange delete request from " << userId));
+ }
+
+ if (exchange->inUseAsAlternate()) throw framing::NotAllowedException(QPID_MSG("Cannot delete " << name <<", in use as alternate-exchange."));
+ if (exchange->isDurable()) store->destroy(*exchange);
+ if (exchange->getAlternate()) exchange->getAlternate()->decAlternateUsers();
+ exchanges.destroy(name, connectionId, userId);
+}
+
+void Broker::bind(const std::string& queueName,
+ const std::string& exchangeName,
+ const std::string& key,
+ const qpid::framing::FieldTable& arguments,
+ const OwnershipToken* owner,
+ const std::string& userId,
+ const std::string& connectionId)
+{
+ if (acl) {
+ std::map<acl::Property, std::string> params;
+ params.insert(make_pair(acl::PROP_QUEUENAME, queueName));
+ params.insert(make_pair(acl::PROP_ROUTINGKEY, key));
+
+ if (!acl->authorise(userId,acl::ACT_BIND,acl::OBJ_EXCHANGE,exchangeName,&params))
+ throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied exchange bind request from " << userId));
+ }
+ if (exchangeName.empty()) {
+ throw framing::InvalidArgumentException(QPID_MSG("Bind not allowed for default exchange"));
+ }
+
+ Queue::shared_ptr queue = queues.find(queueName);
+ Exchange::shared_ptr exchange = exchanges.get(exchangeName);
+ if (!queue) {
+ throw framing::NotFoundException(QPID_MSG("Bind failed. No such queue: " << queueName));
+ } else if (!exchange) {
+ throw framing::NotFoundException(QPID_MSG("Bind failed. No such exchange: " << exchangeName));
+ } else if (queue->hasExclusiveOwner() && !queue->isExclusiveOwner(owner)) {
+ throw framing::ResourceLockedException(QPID_MSG("Cannot bind queue "
+ << queue->getName() << "; it is exclusive to another session"));
+ } else {
+ if (queue->bind(exchange, key, arguments)) {
+ getBrokerObservers().bind(exchange, queue, key, arguments);
+ if (managementAgent.get()) {
+ managementAgent->raiseEvent(_qmf::EventBind(connectionId, userId, exchangeName,
+ queueName, key, ManagementAgent::toMap(arguments)));
+ }
+ QPID_LOG_CAT(debug, model, "Create binding. exchange:" << exchangeName
+ << " queue:" << queueName
+ << " key:" << key
+ << " arguments:" << arguments
+ << " user:" << userId
+ << " rhost:" << connectionId);
+ }
+ }
+}
+
+void Broker::unbind(const std::string& queueName,
+ const std::string& exchangeName,
+ const std::string& key,
+ const OwnershipToken* owner,
+ const std::string& userId,
+ const std::string& connectionId)
+{
+ if (acl) {
+ std::map<acl::Property, std::string> params;
+ params.insert(make_pair(acl::PROP_QUEUENAME, queueName));
+ params.insert(make_pair(acl::PROP_ROUTINGKEY, key));
+ if (!acl->authorise(userId,acl::ACT_UNBIND,acl::OBJ_EXCHANGE,exchangeName,&params) )
+ throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied exchange unbind request from " << userId));
+ }
+ if (exchangeName.empty()) {
+ throw framing::InvalidArgumentException(QPID_MSG("Unbind not allowed for default exchange"));
+ }
+ Queue::shared_ptr queue = queues.find(queueName);
+ Exchange::shared_ptr exchange = exchanges.get(exchangeName);
+ if (!queue) {
+ throw framing::NotFoundException(QPID_MSG("Unbind failed. No such queue: " << queueName));
+ } else if (!exchange) {
+ throw framing::NotFoundException(QPID_MSG("Unbind failed. No such exchange: " << exchangeName));
+ } else if (queue->hasExclusiveOwner() && !queue->isExclusiveOwner(owner)) {
+ throw framing::ResourceLockedException(QPID_MSG("Cannot unbind queue "
+ << queue->getName() << "; it is exclusive to another session"));
+ } else {
+ if (exchange->unbind(queue, key, 0)) {
+ if (exchange->isDurable() && queue->isDurable()) {
+ store->unbind(*exchange, *queue, key, qpid::framing::FieldTable());
+ }
+ getBrokerObservers().unbind(
+ exchange, queue, key, framing::FieldTable());
+ if (managementAgent.get()) {
+ managementAgent->raiseEvent(_qmf::EventUnbind(connectionId, userId, exchangeName, queueName, key));
+ }
+ QPID_LOG_CAT(debug, model, "Delete binding. exchange:" << exchangeName
+ << " queue:" << queueName
+ << " key:" << key
+ << " user:" << userId
+ << " rhost:" << connectionId);
+ }
+ }
+}
+
+// FIXME aconway 2012-04-27: access to linkClientProperties is
+// not properly thread safe, you could lose fields if 2 threads
+// attempt to add a field concurrently.
+
+framing::FieldTable Broker::getLinkClientProperties() const {
+ sys::Mutex::ScopedLock l(linkClientPropertiesLock);
+ return linkClientProperties;
+}
+
+void Broker::setLinkClientProperties(const framing::FieldTable& ft) {
+ sys::Mutex::ScopedLock l(linkClientPropertiesLock);
+ linkClientProperties = ft;
+}
+
+}} // namespace qpid::broker
+
diff --git a/qpid/cpp/src/qpid/broker/Broker.h b/qpid/cpp/src/qpid/broker/Broker.h
new file mode 100644
index 0000000000..af1144eeb0
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Broker.h
@@ -0,0 +1,350 @@
+#ifndef QPID_BROKER_BROKER_H
+#define QPID_BROKER_BROKER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/BrokerImportExport.h"
+
+#include "qpid/DataDir.h"
+#include "qpid/Options.h"
+#include "qpid/Plugin.h"
+#include "qpid/broker/DtxManager.h"
+#include "qpid/broker/ExchangeRegistry.h"
+#include "qpid/broker/ObjectFactory.h"
+#include "qpid/broker/Protocol.h"
+#include "qpid/broker/QueueRegistry.h"
+#include "qpid/broker/LinkRegistry.h"
+#include "qpid/broker/SessionManager.h"
+#include "qpid/broker/QueueCleaner.h"
+#include "qpid/broker/Vhost.h"
+#include "qpid/broker/System.h"
+#include "qpid/broker/ConsumerFactory.h"
+#include "qpid/broker/ConnectionObservers.h"
+#include "qpid/broker/SessionHandlerObserver.h"
+#include "qpid/broker/BrokerObservers.h"
+#include "qpid/management/Manageable.h"
+#include "qpid/sys/ConnectionCodec.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/Runnable.h"
+
+#include <boost/intrusive_ptr.hpp>
+
+#include <string>
+#include <vector>
+
+namespace qpid {
+namespace sys {
+class TransportAcceptor;
+class TransportConnector;
+class Poller;
+class Timer;
+}
+
+struct Url;
+
+namespace broker {
+
+class AclModule;
+struct BrokerOptions;
+class Message;
+struct QueueSettings;
+
+static const uint16_t DEFAULT_PORT=5672;
+
+struct NoSuchTransportException : qpid::Exception
+{
+ NoSuchTransportException(const std::string& s) : Exception(s) {}
+ virtual ~NoSuchTransportException() throw() {}
+};
+
+/**
+ * A broker instance.
+ */
+class Broker : public sys::Runnable, public Plugin::Target,
+ public management::Manageable,
+ public RefCounted
+{
+ struct TransportInfo {
+ boost::shared_ptr<sys::TransportAcceptor> acceptor;
+ boost::shared_ptr<sys::TransportConnector> connectorFactory;
+ uint16_t port;
+
+ TransportInfo() :
+ port(0)
+ {}
+
+ TransportInfo(boost::shared_ptr<sys::TransportAcceptor> a, boost::shared_ptr<sys::TransportConnector> c, uint16_t p) :
+ acceptor(a),
+ connectorFactory(c),
+ port(p)
+ {}
+ };
+ typedef std::map<std::string, TransportInfo > TransportMap;
+
+ void declareStandardExchange(const std::string& name, const std::string& type);
+ void setStore ();
+ void setLogLevel(const std::string& level);
+ std::string getLogLevel();
+ void setLogHiresTimestamp(bool enabled);
+ bool getLogHiresTimestamp();
+ void createObject(const std::string& type, const std::string& name,
+ const qpid::types::Variant::Map& properties, bool strict, const Connection* context);
+ void deleteObject(const std::string& type, const std::string& name,
+ const qpid::types::Variant::Map& options, const Connection* context);
+ void checkDeleteQueue(boost::shared_ptr<Queue> queue, bool ifUnused, bool ifEmpty);
+ Manageable::status_t queryObject(const std::string& type, const std::string& name,
+ qpid::types::Variant::Map& results, const Connection* context);
+ Manageable::status_t queryQueue( const std::string& name,
+ const std::string& userId,
+ const std::string& connectionId,
+ qpid::types::Variant::Map& results);
+ Manageable::status_t getTimestampConfig(bool& receive,
+ const Connection* context);
+ Manageable::status_t setTimestampConfig(const bool receive,
+ const Connection* context);
+ Manageable::status_t queueRedirect(const std::string& srcQueue, const std::string& tgtQueue, const Connection* context);
+ void queueRedirectDestroy(boost::shared_ptr<Queue> srcQ, boost::shared_ptr<Queue> tgtQ, bool moveMsgs);
+
+ // This must be the first member of Broker. It logs a start-up message
+ // at the start of Broker construction and a shut-down message at the
+ // end of destruction.
+ struct LogPrefix : public std::string {
+ LogPrefix();
+ ~LogPrefix();
+ } logPrefix;
+
+ boost::shared_ptr<sys::Poller> poller;
+ std::auto_ptr<sys::Timer> timer;
+ const BrokerOptions& config;
+ std::auto_ptr<management::ManagementAgent> managementAgent;
+ std::set<std::string> disabledListeningTransports;
+ TransportMap transportMap;
+ std::auto_ptr<MessageStore> store;
+ AclModule* acl;
+ DataDir dataDir;
+ DataDir pagingDir;
+ ConnectionObservers connectionObservers;
+ SessionHandlerObservers sessionHandlerObservers;
+ BrokerObservers brokerObservers;
+
+ QueueRegistry queues;
+ ExchangeRegistry exchanges;
+ LinkRegistry links;
+ DtxManager dtxManager;
+ SessionManager sessionManager;
+ qmf::org::apache::qpid::broker::Broker::shared_ptr mgmtObject;
+ Vhost::shared_ptr vhostObject;
+ System::shared_ptr systemObject;
+ QueueCleaner queueCleaner;
+ std::vector<Url> knownBrokers;
+ std::vector<Url> getKnownBrokersImpl();
+ bool deferDeliveryImpl(const std::string& queue,
+ const Message& msg);
+ std::string federationTag;
+ bool recoveryInProgress;
+ ConsumerFactories consumerFactories;
+ ProtocolRegistry protocolRegistry;
+ ObjectFactoryRegistry objectFactory;
+
+ mutable sys::Mutex linkClientPropertiesLock;
+ framing::FieldTable linkClientProperties;
+ bool timestampRcvMsgs;
+
+ public:
+ QPID_BROKER_EXTERN virtual ~Broker();
+
+ QPID_BROKER_EXTERN Broker(const BrokerOptions& configuration);
+ static QPID_BROKER_EXTERN boost::intrusive_ptr<Broker> create(const BrokerOptions& configuration);
+ static QPID_BROKER_EXTERN boost::intrusive_ptr<Broker> create(int16_t port = DEFAULT_PORT);
+
+ /**
+ * Return listening port. If called before bind this is
+ * the configured port. If called after it is the actual
+ * port, which will be different if the configured port is
+ * 0.
+ */
+ QPID_BROKER_EXTERN virtual uint16_t getPort(const std::string& name) const;
+
+ /**
+ * Run the broker. Implements Runnable::run() so the broker
+ * can be run in a separate thread.
+ */
+ QPID_BROKER_EXTERN virtual void run();
+
+ /** Shut down the broker */
+ QPID_BROKER_EXTERN virtual void shutdown();
+
+ QPID_BROKER_EXTERN void setStore (const boost::shared_ptr<MessageStore>& store);
+ bool hasStore() const { return store.get(); }
+ MessageStore& getStore() { return *store; }
+ void setAcl (AclModule* _acl) {acl = _acl;}
+ AclModule* getAcl() { return acl; }
+ QueueRegistry& getQueues() { return queues; }
+ ExchangeRegistry& getExchanges() { return exchanges; }
+ LinkRegistry& getLinks() { return links; }
+ DtxManager& getDtxManager() { return dtxManager; }
+ const DataDir& getDataDir() { return dataDir; }
+ const DataDir& getPagingDir() { return pagingDir; }
+ ProtocolRegistry& getProtocolRegistry() { return protocolRegistry; }
+ ObjectFactoryRegistry& getObjectFactoryRegistry() { return objectFactory; }
+
+ SessionManager& getSessionManager() { return sessionManager; }
+ const std::string& getFederationTag() const { return federationTag; }
+
+ QPID_BROKER_EXTERN management::ManagementObject::shared_ptr GetManagementObject() const;
+ QPID_BROKER_EXTERN management::Manageable* GetVhostObject() const;
+ QPID_BROKER_EXTERN management::Manageable::status_t ManagementMethod(
+ uint32_t methodId, management::Args& args, std::string& text);
+
+ // Should we listen using this protocol or not?
+ QPID_BROKER_EXTERN bool shouldListen(std::string transport);
+
+ // Turn off listening for a protocol
+ QPID_BROKER_EXTERN void disableListening(std::string transport);
+
+ /** Add to the broker's protocolFactorys */
+ QPID_BROKER_EXTERN void registerTransport(
+ const std::string& name,
+ boost::shared_ptr<sys::TransportAcceptor>, boost::shared_ptr<sys::TransportConnector>,
+ uint16_t port);
+
+ /** Accept connections */
+ QPID_BROKER_EXTERN void accept();
+
+ /** Create a connection to another broker. */
+ void connect(const std::string& name,
+ const std::string& host, const std::string& port,
+ const std::string& transport,
+ boost::function2<void, int, std::string> failed);
+ QPID_BROKER_EXTERN void connect(const std::string& name,
+ const std::string& host, const std::string& port,
+ const std::string& transport,
+ sys::ConnectionCodec::Factory*,
+ boost::function2<void, int, std::string> failed);
+
+
+ /** Move messages from one queue to another.
+ A zero quantity means to move all messages
+ Return -1 if one of the queues does not exist, otherwise
+ the number of messages moved.
+ */
+ QPID_BROKER_EXTERN int32_t queueMoveMessages(
+ const std::string& srcQueue,
+ const std::string& destQueue,
+ uint32_t qty,
+ const qpid::types::Variant::Map& filter,
+ const Connection* context);
+
+ QPID_BROKER_EXTERN const TransportInfo& getTransportInfo(
+ const std::string& name = TCP_TRANSPORT) const;
+
+ /** Expose poller so plugins can register their descriptors. */
+ QPID_BROKER_EXTERN boost::shared_ptr<sys::Poller> getPoller();
+
+ /** Timer for local tasks affecting only this broker */
+ sys::Timer& getTimer() { return *timer; }
+
+ boost::function<std::vector<Url> ()> getKnownBrokers;
+
+ static QPID_BROKER_EXTERN const std::string TCP_TRANSPORT;
+
+ management::ManagementAgent* getManagementAgent() { return managementAgent.get(); }
+
+ typedef boost::function1<void, boost::shared_ptr<Queue> > QueueFunctor;
+
+ QPID_BROKER_EXTERN std::pair<boost::shared_ptr<Queue>, bool> createQueue(
+ const std::string& name,
+ const QueueSettings& settings,
+ const OwnershipToken* owner,
+ const std::string& alternateExchange,
+ const std::string& userId,
+ const std::string& connectionId);
+
+ QPID_BROKER_EXTERN void deleteQueue(
+ const std::string& name,
+ const std::string& userId,
+ const std::string& connectionId,
+ QueueFunctor check = QueueFunctor());
+
+ QPID_BROKER_EXTERN std::pair<Exchange::shared_ptr, bool> createExchange(
+ const std::string& name,
+ const std::string& type,
+ bool durable,
+ bool autodelete,
+ const std::string& alternateExchange,
+ const qpid::framing::FieldTable& args,
+ const std::string& userId, const std::string& connectionId);
+
+ QPID_BROKER_EXTERN void deleteExchange(
+ const std::string& name, const std::string& userId,
+ const std::string& connectionId);
+
+ QPID_BROKER_EXTERN void bind(
+ const std::string& queue,
+ const std::string& exchange,
+ const std::string& key,
+ const qpid::framing::FieldTable& arguments,
+ const OwnershipToken* owner,
+ const std::string& userId,
+ const std::string& connectionId);
+
+ QPID_BROKER_EXTERN void unbind(
+ const std::string& queue,
+ const std::string& exchange,
+ const std::string& key,
+ const OwnershipToken* owner,
+ const std::string& userId,
+ const std::string& connectionId);
+
+ ConsumerFactories& getConsumerFactories() { return consumerFactories; }
+ ConnectionObservers& getConnectionObservers() { return connectionObservers; }
+ SessionHandlerObservers& getSessionHandlerObservers() { return sessionHandlerObservers; }
+ BrokerObservers& getBrokerObservers() { return brokerObservers; }
+
+ /** Properties to be set on outgoing link connections */
+ QPID_BROKER_EXTERN framing::FieldTable getLinkClientProperties() const;
+ QPID_BROKER_EXTERN void setLinkClientProperties(const framing::FieldTable&);
+
+ bool inRecovery() const { return recoveryInProgress; }
+ bool isTimestamping() const { return timestampRcvMsgs; }
+ QPID_BROKER_EXTERN bool isAuthenticating() const;
+ QPID_BROKER_EXTERN bool requireEncrypted() const;
+ QPID_BROKER_EXTERN std::string getRealm() const;
+ QPID_BROKER_EXTERN std::string getSaslServiceName() const;
+ QPID_BROKER_EXTERN bool getTcpNoDelay() const;
+ QPID_BROKER_EXTERN uint16_t getPortOption() const;
+ QPID_BROKER_EXTERN const std::vector<std::string>& getListenInterfaces() const;
+ QPID_BROKER_EXTERN int getConnectionBacklog() const;
+ uint32_t getMaxNegotiateTime() const;
+ sys::Duration getLinkMaintenanceInterval() const;
+ QPID_BROKER_EXTERN sys::Duration getLinkHeartbeatInterval() const;
+ uint32_t getDtxMaxTimeout() const;
+ uint16_t getQueueThresholdEventRatio() const;
+ uint getQueueLimit() const;
+
+ /** Information identifying this system */
+ boost::shared_ptr<const System> getSystem() const { return systemObject; }
+};
+
+}}
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/BrokerImportExport.h b/qpid/cpp/src/qpid/broker/BrokerImportExport.h
new file mode 100644
index 0000000000..ee05788063
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/BrokerImportExport.h
@@ -0,0 +1,42 @@
+#ifndef QPID_BROKER_IMPORT_EXPORT_H
+#define QPID_BROKER_IMPORT_EXPORT_H
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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 defined(WIN32) && !defined(QPID_DECLARE_STATIC)
+# if defined(BROKER_EXPORT) || defined (qpidbroker_EXPORTS)
+# define QPID_BROKER_EXTERN __declspec(dllexport)
+# else
+# define QPID_BROKER_EXTERN __declspec(dllimport)
+# endif
+# ifdef _MSC_VER
+# define QPID_BROKER_CLASS_EXTERN
+# define QPID_BROKER_INLINE_EXTERN QPID_BROKER_EXTERN
+# else
+# define QPID_BROKER_CLASS_EXTERN QPID_BROKER_EXTERN
+# define QPID_BROKER_INLINE_EXTERN
+# endif
+#else
+# define QPID_BROKER_EXTERN
+# define QPID_BROKER_CLASS_EXTERN
+# define QPID_BROKER_INLINE_EXTERN
+#endif
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/BrokerObserver.h b/qpid/cpp/src/qpid/broker/BrokerObserver.h
new file mode 100644
index 0000000000..a9573b9e12
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/BrokerObserver.h
@@ -0,0 +1,68 @@
+#ifndef QPID_BROKER_BROKEROBSERVER_H
+#define QPID_BROKER_BROKEROBSERVER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace qpid {
+
+namespace framing {
+class FieldTable;
+}
+
+namespace broker {
+class Queue;
+class Exchange;
+class TxBuffer;
+class DtxBuffer;
+
+/**
+ * Observer for changes to configuration (aka wiring)
+ *
+ * NOTE: create and destroy functions are called with
+ * the registry lock held. This is necessary to ensure
+ * they are called in the correct sequence.
+ */
+class BrokerObserver
+{
+ public:
+ virtual ~BrokerObserver() {}
+ virtual void queueCreate(const boost::shared_ptr<Queue>&) {}
+ virtual void queueDestroy(const boost::shared_ptr<Queue>&) {}
+ virtual void exchangeCreate(const boost::shared_ptr<Exchange>&) {}
+ virtual void exchangeDestroy(const boost::shared_ptr<Exchange>&) {}
+ virtual void bind(const boost::shared_ptr<Exchange>& ,
+ const boost::shared_ptr<Queue>& ,
+ const std::string& /*key*/,
+ const framing::FieldTable& /*args*/) {}
+ virtual void unbind(const boost::shared_ptr<Exchange>&,
+ const boost::shared_ptr<Queue>& ,
+ const std::string& /*key*/,
+ const framing::FieldTable& /*args*/) {}
+ virtual void startTx(const boost::intrusive_ptr<TxBuffer>&) {}
+ virtual void startDtx(const boost::intrusive_ptr<DtxBuffer>&) {}
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_BROKEROBSERVER_H*/
diff --git a/qpid/cpp/src/qpid/broker/BrokerObservers.h b/qpid/cpp/src/qpid/broker/BrokerObservers.h
new file mode 100644
index 0000000000..67427adcb1
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/BrokerObservers.h
@@ -0,0 +1,73 @@
+#ifndef QPID_BROKER_BROKEROBSERVERS_H
+#define QPID_BROKER_BROKEROBSERVERS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "BrokerObserver.h"
+#include "Observers.h"
+
+namespace qpid {
+namespace broker {
+
+/**
+ * Collection of BrokerObserver.
+ */
+class BrokerObservers : public Observers<BrokerObserver> {
+ public:
+ void queueCreate(const boost::shared_ptr<Queue>& q) {
+ each(boost::bind(&BrokerObserver::queueCreate, _1, q));
+ }
+ void queueDestroy(const boost::shared_ptr<Queue>& q) {
+ each(boost::bind(&BrokerObserver::queueDestroy, _1, q));
+ }
+ void exchangeCreate(const boost::shared_ptr<Exchange>& e) {
+ each(boost::bind(&BrokerObserver::exchangeCreate, _1, e));
+ }
+ void exchangeDestroy(const boost::shared_ptr<Exchange>& e) {
+ each(boost::bind(&BrokerObserver::exchangeDestroy, _1, e));
+ }
+ void bind(const boost::shared_ptr<Exchange>& exchange,
+ const boost::shared_ptr<Queue>& queue,
+ const std::string& key,
+ const framing::FieldTable& args) {
+ each(boost::bind(&BrokerObserver::bind, _1, exchange, queue, key, args));
+ }
+ void unbind(const boost::shared_ptr<Exchange>& exchange,
+ const boost::shared_ptr<Queue>& queue,
+ const std::string& key,
+ const framing::FieldTable& args) {
+ each(boost::bind(&BrokerObserver::unbind, _1, exchange, queue, key, args));
+ }
+ void startTx(const boost::intrusive_ptr<TxBuffer>& tx) {
+ each(boost::bind(&BrokerObserver::startTx, _1, tx));
+ }
+ void startDtx(const boost::intrusive_ptr<DtxBuffer>& dtx) {
+ each(boost::bind(&BrokerObserver::startDtx, _1, dtx));
+ }
+
+ private:
+ template <class F> void each(F f) { Observers<BrokerObserver>::each(f); }
+};
+
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_BROKEROBSERVERS_H*/
diff --git a/qpid/cpp/src/qpid/broker/BrokerOptions.h b/qpid/cpp/src/qpid/broker/BrokerOptions.h
new file mode 100644
index 0000000000..7207c17f91
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/BrokerOptions.h
@@ -0,0 +1,88 @@
+#ifndef QPID_BROKER_BROKEROPTIONS_H
+#define QPID_BROKER_BROKEROPTIONS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/Options.h"
+#include "qpid/sys/Time.h"
+#include "qpid/sys/IntegerTypes.h"
+
+#include "qpid/broker/BrokerImportExport.h"
+
+#include <string>
+#include <vector>
+
+namespace qpid {
+namespace broker {
+
+struct BrokerOptions : public qpid::Options
+{
+ static const std::string DEFAULT_DATA_DIR_LOCATION;
+ static const std::string DEFAULT_DATA_DIR_NAME;
+ static const std::string DEFAULT_PAGED_QUEUE_DIR;
+
+ QPID_BROKER_EXTERN BrokerOptions(const std::string& name="Broker Options");
+
+ bool noDataDir;
+ std::string dataDir;
+ std::string pagingDir;
+ uint16_t port;
+ std::vector<std::string> listenInterfaces;
+ std::vector<std::string> listenDisabled;
+ std::vector<std::string> protocols;
+ int workerThreads;
+ int connectionBacklog;
+ bool enableMgmt;
+ bool mgmtPublish;
+ sys::Duration mgmtPubInterval;
+ sys::Duration queueCleanInterval;
+ bool auth;
+ std::string realm;
+ std::string saslServiceName;
+ size_t replayFlushLimit;
+ size_t replayHardLimit;
+ uint queueLimit;
+ bool tcpNoDelay;
+ bool requireEncrypted;
+ std::string knownHosts;
+ std::string saslConfigPath;
+ bool qmf2Support;
+ bool qmf1Support;
+ uint queueFlowStopRatio; // producer flow control: on
+ uint queueFlowResumeRatio; // producer flow control: off
+ uint16_t queueThresholdEventRatio;
+ std::string defaultMsgGroup;
+ bool timestampRcvMsgs;
+ sys::Duration linkMaintenanceInterval;
+ sys::Duration linkHeartbeatInterval;
+ uint32_t dtxDefaultTimeout; // Default timeout of a DTX transaction
+ uint32_t dtxMaxTimeout; // Maximal timeout of a DTX transaction
+ uint32_t maxNegotiateTime; // Max time in ms for connection with no negotiation
+ std::string fedTag;
+
+private:
+ std::string getHome();
+};
+
+}}
+
+#endif // QPID_BROKER_BROKEROPTIONS_H
diff --git a/qpid/cpp/src/qpid/broker/Connection.h b/qpid/cpp/src/qpid/broker/Connection.h
new file mode 100644
index 0000000000..8cab18f37b
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Connection.h
@@ -0,0 +1,53 @@
+#ifndef QPID_BROKER_CONNECTION_H
+#define QPID_BROKER_CONNECTION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "OwnershipToken.h"
+#include <map>
+#include <string>
+
+namespace qpid {
+namespace management {
+class ObjectId;
+}
+namespace types {
+class Variant;
+}
+
+namespace broker {
+
+/**
+ * Protocol independent connection abstraction.
+ */
+class Connection : public OwnershipToken {
+public:
+ virtual ~Connection() {}
+ virtual const management::ObjectId getObjectId() const = 0;
+ virtual const std::string& getUserId() const = 0;
+ virtual const std::string& getMgmtId() const = 0;
+ virtual const std::map<std::string, types::Variant>& getClientProperties() const = 0;
+ virtual bool isLink() const = 0;
+ virtual void abort() = 0;
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_CONNECTION_H*/
diff --git a/qpid/cpp/src/qpid/broker/ConnectionHandler.cpp b/qpid/cpp/src/qpid/broker/ConnectionHandler.cpp
new file mode 100644
index 0000000000..49afd8d24d
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/ConnectionHandler.cpp
@@ -0,0 +1,469 @@
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/ConnectionHandler.h"
+
+#include "qpid/SaslFactory.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/amqp_0_10/Connection.h"
+#include "qpid/broker/SecureConnection.h"
+#include "qpid/Url.h"
+#include "qpid/framing/AllInvoker.h"
+#include "qpid/framing/ConnectionStartOkBody.h"
+#include "qpid/framing/enum.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/log/Statement.h"
+#include "qpid/management/ManagementAgent.h"
+#include "qpid/sys/ConnectionOutputHandler.h"
+#include "qpid/sys/SecurityLayer.h"
+#include "qpid/sys/Time.h"
+#include "qpid/broker/AclModule.h"
+#include "qpid/amqp_0_10/Codecs.h"
+#include "qmf/org/apache/qpid/broker/EventClientConnectFail.h"
+#include "qpid/Version.h"
+
+using namespace qpid;
+using namespace qpid::broker;
+
+using std::string;
+
+using namespace qpid::framing;
+using qpid::sys::SecurityLayer;
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+namespace
+{
+const std::string en_US = "en_US";
+const std::string QPID_FED_LINK = "qpid.fed_link";
+const std::string QPID_FED_TAG = "qpid.federation_tag";
+const std::string CLIENT_PROCESS_NAME("qpid.client_process");
+const std::string CLIENT_PID("qpid.client_pid");
+const std::string CLIENT_PPID("qpid.client_ppid");
+const std::string SPACE(" ");
+}
+
+void ConnectionHandler::close(connection::CloseCode code, const string& text)
+{
+ handler->proxy.close(code, text);
+}
+
+void ConnectionHandler::heartbeat()
+{
+ handler->proxy.heartbeat();
+}
+
+bool ConnectionHandler::handle(const framing::AMQMethodBody& method)
+{
+ //Need special handling for start-ok, in order to distinguish
+ //between null and empty response
+ if (method.isA<ConnectionStartOkBody>()) {
+ handler->startOk(dynamic_cast<const ConnectionStartOkBody&>(method));
+ return true;
+ } else {
+ return invoke(static_cast<AMQP_AllOperations::ConnectionHandler&>(*handler), method);
+ }
+}
+
+void ConnectionHandler::handle(framing::AMQFrame& frame)
+{
+ AMQMethodBody* method=frame.getBody()->getMethod();
+ try{
+ if (method && handle(*method)) {
+ // This is a connection control frame, nothing more to do.
+ } else if (isOpen()) {
+ handler->connection.getChannel(frame.getChannel()).in(frame);
+ } else {
+ handler->connection.close(
+ connection::CLOSE_CODE_FRAMING_ERROR,
+ "Connection not yet open, invalid frame received.");
+ }
+ }catch(ConnectionException& e){
+ handler->connection.close(e.code, e.what());
+ }catch(std::exception& e){
+ handler->connection.close(connection::CLOSE_CODE_CONNECTION_FORCED, e.what());
+ }
+}
+
+void ConnectionHandler::setSecureConnection(SecureConnection* secured)
+{
+ handler->secured = secured;
+}
+
+ConnectionHandler::ConnectionHandler(qpid::broker::amqp_0_10::Connection& connection, bool isClient) :
+ handler(new Handler(connection, isClient)) {}
+
+ConnectionHandler::Handler::Handler(qpid::broker::amqp_0_10::Connection& c, bool isClient) :
+ proxy(c.getOutput()),
+ connection(c), serverMode(!isClient), secured(0),
+ isOpen(false)
+{
+ if (serverMode) {
+ FieldTable properties;
+ Array mechanisms(0x95);
+ boost::shared_ptr<const System> sysInfo = connection.getBroker().getSystem();
+
+ properties.setString("product", qpid::product);
+ properties.setString("version", qpid::version);
+ properties.setString("platform", sysInfo->getOsName());
+ properties.setString("host", sysInfo->getNodeName());
+ properties.setString(QPID_FED_TAG, connection.getBroker().getFederationTag());
+
+ authenticator = SaslAuthenticator::createAuthenticator(c);
+ authenticator->getMechanisms(mechanisms);
+
+ Array locales(0x95);
+ boost::shared_ptr<FieldValue> l(new Str16Value(en_US));
+ locales.add(l);
+ proxy.start(properties, mechanisms, locales);
+ }
+
+ maxFrameSize = (64 * 1024) - 1;
+}
+
+
+ConnectionHandler::Handler::~Handler() {}
+
+
+void ConnectionHandler::Handler::startOk(const framing::FieldTable& /*clientProperties*/,
+ const string& /*mechanism*/,
+ const string& /*response*/,
+ const string& /*locale*/)
+{
+ //Need special handling for start-ok, in order to distinguish
+ //between null and empty response -> should never use this method
+ assert(false);
+}
+
+void ConnectionHandler::Handler::startOk(const ConnectionStartOkBody& body)
+{
+ const framing::FieldTable& clientProperties = body.getClientProperties();
+ qmf::org::apache::qpid::broker::Connection::shared_ptr mgmtObject = connection.getMgmtObject();
+ types::Variant::Map properties;
+ qpid::amqp_0_10::translate(clientProperties, properties);
+
+ if (mgmtObject != 0) {
+ string procName = clientProperties.getAsString(CLIENT_PROCESS_NAME);
+ uint32_t pid = clientProperties.getAsInt(CLIENT_PID);
+ uint32_t ppid = clientProperties.getAsInt(CLIENT_PPID);
+
+ mgmtObject->set_remoteProperties(properties);
+ if (!procName.empty())
+ mgmtObject->set_remoteProcessName(procName);
+ if (pid != 0)
+ mgmtObject->set_remotePid(pid);
+ if (ppid != 0)
+ mgmtObject->set_remoteParentPid(ppid);
+ }
+ try {
+ authenticator->start(body.getMechanism(), body.hasResponse() ? &body.getResponse() : 0);
+ } catch (std::exception& /*e*/) {
+ management::ManagementAgent* agent = connection.getAgent();
+ bool logEnabled;
+ QPID_LOG_TEST_CAT(debug, model, logEnabled);
+ if (logEnabled || agent)
+ {
+ string error;
+ string uid;
+ authenticator->getError(error);
+ authenticator->getUid(uid);
+ if (agent && mgmtObject) {
+ agent->raiseEvent(_qmf::EventClientConnectFail(connection.getMgmtId(), uid, error,
+ mgmtObject->get_remoteProperties()));
+ }
+ QPID_LOG_CAT(debug, model, "Failed connection. rhost:" << connection.getMgmtId()
+ << " user:" << uid
+ << " reason:" << error );
+ }
+ throw;
+ }
+
+ connection.setClientProperties(properties);
+ if (clientProperties.isSet(QPID_FED_TAG)) {
+ connection.setFederationPeerTag(clientProperties.getAsString(QPID_FED_TAG));
+ }
+}
+
+void ConnectionHandler::Handler::secureOk(const string& response)
+{
+ try {
+ authenticator->step(response);
+ } catch (std::exception& /*e*/) {
+ management::ManagementAgent* agent = connection.getAgent();
+ bool logEnabled;
+ QPID_LOG_TEST_CAT(debug, model, logEnabled);
+ if (logEnabled || agent)
+ {
+ string error;
+ string uid;
+ authenticator->getError(error);
+ authenticator->getUid(uid);
+ if (agent && connection.getMgmtObject()) {
+ agent->raiseEvent(_qmf::EventClientConnectFail(connection.getMgmtId(), uid, error,
+ connection.getMgmtObject()->get_remoteProperties()));
+ }
+ QPID_LOG_CAT(debug, model, "Failed connection. rhost:" << connection.getMgmtId()
+ << " user:" << uid
+ << " reason:" << error );
+ }
+ throw;
+ }
+}
+
+void ConnectionHandler::Handler::tuneOk(uint16_t /*channelmax*/,
+ uint16_t framemax, uint16_t heartbeat)
+{
+ if (framemax) connection.setFrameMax(framemax);
+ connection.setHeartbeatInterval(heartbeat);
+}
+
+void ConnectionHandler::Handler::open(const string& /*virtualHost*/,
+ const framing::Array& /*capabilities*/, bool /*insist*/)
+{
+ if (connection.getUserId().empty() && connection.getBroker().isAuthenticating()) {
+ throw ConnectionForcedException("Not authenticated!");
+ }
+
+ if (connection.isFederationLink()) {
+ AclModule* acl = connection.getBroker().getAcl();
+ if (acl && acl->userAclRules()) {
+ if (!acl->authorise(connection.getUserId(),acl::ACT_CREATE,acl::OBJ_LINK,"")){
+ connection.close(framing::connection::CLOSE_CODE_CONNECTION_FORCED,
+ QPID_MSG("ACL denied " << connection.getUserId()
+ << " creating a federation link"));
+ return;
+ }
+ } else {
+ if (connection.getBroker().isAuthenticating()) {
+ connection.close(framing::connection::CLOSE_CODE_CONNECTION_FORCED,
+ QPID_MSG("User " << connection.getUserId()
+ << " federation connection denied. Systems with authentication "
+ "enabled must specify ACL create link rules."));
+ return;
+ }
+ }
+ QPID_LOG(info, "Connection is a federation link");
+ }
+ std::vector<Url> urls = connection.getBroker().getKnownBrokers();
+ framing::Array array(0x95); // str16 array
+ for (std::vector<Url>::iterator i = urls.begin(); i < urls.end(); ++i)
+ array.add(boost::shared_ptr<Str16Value>(new Str16Value(i->str())));
+
+ //install security layer if one has been negotiated:
+ if (secured) {
+ std::auto_ptr<SecurityLayer> sl = authenticator->getSecurityLayer(connection.getFrameMax());
+ if (sl.get()) secured->activateSecurityLayer(sl);
+ }
+
+ isOpen = true;
+ proxy.openOk(array);
+}
+
+
+void ConnectionHandler::Handler::close(uint16_t replyCode, const string& replyText)
+{
+ if (replyCode != 200) {
+ QPID_LOG(warning, "Client closed connection with " << replyCode << ": " << replyText);
+ }
+
+ if (replyCode == framing::connection::CLOSE_CODE_CONNECTION_FORCED)
+ connection.notifyConnectionForced(replyText);
+
+ proxy.closeOk();
+ connection.getOutput().close();
+}
+
+void ConnectionHandler::Handler::closeOk(){
+ connection.getOutput().close();
+}
+
+void ConnectionHandler::Handler::heartbeat(){
+ // For general case, do nothing - the purpose of heartbeats is
+ // just to make sure that there is some traffic on the connection
+ // within the heart beat interval, we check for the traffic and
+ // don't need to do anything in response to heartbeats. The
+ // exception is when we are in fact the client to another broker
+ // (i.e. an inter-broker link), in which case we echo the
+ // heartbeat back to the peer
+ if (!serverMode) proxy.heartbeat();
+}
+
+void ConnectionHandler::Handler::start(const FieldTable& serverProperties,
+ const framing::Array& supportedMechanisms,
+ const framing::Array& /*locales*/)
+{
+ if (serverMode) {
+ throw ConnectionForcedException("Invalid protocol sequence.");
+ }
+
+
+ string requestedMechanism = connection.getAuthMechanism();
+
+ std::string username = connection.getUsername();
+
+ std::string password = connection.getPassword();
+ std::string host = connection.getHost();
+ std::string service("qpidd");
+
+ if ( connection.getBroker().isAuthenticating() ) {
+ sasl = SaslFactory::getInstance().create( username,
+ password,
+ service,
+ host,
+ 0, // TODO -- mgoulish Fri Sep 24 2010
+ 256,
+ false ); // disallow interaction
+ }
+ std::string supportedMechanismsList;
+ Array::const_iterator i;
+
+ /*
+ If no specific mechanism has been requested, just make
+ a list of all of them, and assert that the one the caller
+ requested is there. ( If *any* are supported! )
+ */
+ if ( requestedMechanism.empty() ) {
+ for ( i = supportedMechanisms.begin(); i != supportedMechanisms.end(); ++i) {
+ if (i != supportedMechanisms.begin())
+ supportedMechanismsList += SPACE;
+ supportedMechanismsList += (*i)->get<std::string>();
+ }
+ }
+ else {
+ /*
+ The caller has requested a mechanism. If it's available,
+ make sure it ends up at the head of the list.
+ */
+ for ( i = supportedMechanisms.begin(); i != supportedMechanisms.end(); ++i) {
+ string currentMechanism = (*i)->get<std::string>();
+
+ if ( requestedMechanism == currentMechanism ) {
+ supportedMechanismsList = currentMechanism + SPACE + supportedMechanismsList;
+ } else {
+ if (i != supportedMechanisms.begin())
+ supportedMechanismsList += SPACE;
+ supportedMechanismsList += currentMechanism;
+ }
+ }
+ }
+
+ if (serverProperties.isSet(QPID_FED_TAG)) {
+ connection.setFederationPeerTag(serverProperties.getAsString(QPID_FED_TAG));
+ }
+
+ FieldTable ft = connection.getBroker().getLinkClientProperties();
+ ft.setInt(QPID_FED_LINK,1);
+ ft.setString(QPID_FED_TAG, connection.getBroker().getFederationTag());
+
+ string response;
+ if (sasl.get()) {
+ const qpid::sys::SecuritySettings& ss = connection.getExternalSecuritySettings();
+ if (sasl->start ( requestedMechanism.empty()
+ ? supportedMechanismsList
+ : requestedMechanism,
+ response,
+ & ss )) {
+ proxy.startOk ( ft, sasl->getMechanism(), response, en_US );
+ } else {
+ //response was null
+ ConnectionStartOkBody body;
+ body.setClientProperties(ft);
+ body.setMechanism(sasl->getMechanism());
+ //Don't set response, as none was given
+ body.setLocale(en_US);
+ proxy.send(body);
+ }
+ }
+ else {
+ response = ((char)0) + username + ((char)0) + password;
+ proxy.startOk ( ft, requestedMechanism, response, en_US );
+ }
+
+}
+
+void ConnectionHandler::Handler::secure(const string& challenge )
+{
+ if (serverMode) {
+ throw ConnectionForcedException("Invalid protocol sequence.");
+ }
+
+ if (sasl.get()) {
+ string response = sasl->step(challenge);
+ proxy.secureOk(response);
+ }
+ else {
+ proxy.secureOk("");
+ }
+}
+
+void ConnectionHandler::Handler::tune(uint16_t channelMax,
+ uint16_t maxFrameSizeProposed,
+ uint16_t /*heartbeatMin*/,
+ uint16_t heartbeatMax)
+{
+ if (serverMode) {
+ throw ConnectionForcedException("Invalid protocol sequence.");
+ }
+
+ maxFrameSize = std::min(maxFrameSize, maxFrameSizeProposed);
+ connection.setFrameMax(maxFrameSize);
+
+ // this method is only ever called when this Connection
+ // is a federation link where this Broker is acting as
+ // a client to another Broker
+ sys::Duration interval = connection.getBroker().getLinkHeartbeatInterval();
+ uint16_t intervalSec = static_cast<uint16_t>(interval/sys::TIME_SEC);
+ uint16_t hb = std::min(intervalSec, heartbeatMax);
+ connection.setHeartbeat(hb);
+ connection.startLinkHeartbeatTimeoutTask();
+
+ proxy.tuneOk(channelMax, maxFrameSize, hb);
+ proxy.open("/", Array(), true);
+}
+
+void ConnectionHandler::Handler::openOk(const framing::Array& knownHosts)
+{
+ if (serverMode) {
+ throw ConnectionForcedException("Invalid protocol sequence.");
+ }
+
+ for (Array::ValueVector::const_iterator i = knownHosts.begin(); i != knownHosts.end(); ++i) {
+ Url url((*i)->get<std::string>());
+ connection.getKnownHosts().push_back(url);
+ }
+
+ if (sasl.get()) {
+ std::auto_ptr<qpid::sys::SecurityLayer> securityLayer = sasl->getSecurityLayer(maxFrameSize);
+
+ if ( securityLayer.get() ) {
+ secured->activateSecurityLayer(securityLayer, true);
+ }
+
+ saslUserId = sasl->getUserId();
+ }
+
+ isOpen = true;
+}
+
+void ConnectionHandler::Handler::redirect(const string& /*host*/, const framing::Array& /*knownHosts*/)
+{
+
+}
diff --git a/qpid/cpp/src/qpid/broker/ConnectionHandler.h b/qpid/cpp/src/qpid/broker/ConnectionHandler.h
new file mode 100644
index 0000000000..93bd62d5d0
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/ConnectionHandler.h
@@ -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.
+ *
+ */
+#ifndef _ConnectionAdapter_
+#define _ConnectionAdapter_
+
+#include <memory>
+#include "qpid/Sasl.h"
+#include "qpid/broker/SaslAuthenticator.h"
+#include "qpid/framing/amqp_types.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/AMQMethodBody.h"
+#include "qpid/framing/AMQP_AllOperations.h"
+#include "qpid/framing/AMQP_AllProxy.h"
+#include "qpid/framing/ConnectionStartOkBody.h"
+#include "qpid/framing/enum.h"
+#include "qpid/framing/FrameHandler.h"
+#include "qpid/framing/ProtocolInitiation.h"
+#include "qpid/framing/ProtocolVersion.h"
+#include "qpid/Exception.h"
+#include "qpid/sys/SecurityLayer.h"
+#include "qpid/broker/System.h"
+
+
+
+namespace qpid {
+
+namespace sys {
+struct SecuritySettings;
+}
+
+
+namespace broker {
+
+namespace amqp_0_10 {
+class Connection;
+}
+class SecureConnection;
+
+class ConnectionHandler : public framing::FrameHandler
+{
+ struct Handler : public framing::AMQP_AllOperations::ConnectionHandler
+ {
+ framing::AMQP_AllProxy::Connection proxy;
+ amqp_0_10::Connection& connection;
+ bool serverMode;
+ std::auto_ptr<SaslAuthenticator> authenticator;
+ SecureConnection* secured;
+ bool isOpen;
+
+ Handler(amqp_0_10::Connection& connection, bool isClient);
+ ~Handler();
+ void startOk(const qpid::framing::ConnectionStartOkBody& body);
+ void startOk(const qpid::framing::FieldTable& clientProperties,
+ const std::string& mechanism, const std::string& response,
+ const std::string& locale);
+ void secureOk(const std::string& response);
+ void tuneOk(uint16_t channelMax, uint16_t frameMax, uint16_t heartbeat);
+ void heartbeat();
+ void open(const std::string& virtualHost,
+ const framing::Array& capabilities, bool insist);
+ void close(uint16_t replyCode, const std::string& replyText);
+ void closeOk();
+
+ void start(const qpid::framing::FieldTable& serverProperties,
+ const framing::Array& mechanisms,
+ const framing::Array& locales);
+
+ void secure(const std::string& challenge);
+
+ void tune(uint16_t channelMax,
+ uint16_t frameMax,
+ uint16_t heartbeatMin,
+ uint16_t heartbeatMax);
+
+ void openOk(const framing::Array& knownHosts);
+
+ void redirect(const std::string& host, const framing::Array& knownHosts);
+
+ std::auto_ptr<Sasl> sasl;
+ typedef boost::function<const qpid::sys::SecuritySettings*()> GetSecuritySettings;
+ std::string saslUserId;
+ uint16_t maxFrameSize;
+ };
+ std::auto_ptr<Handler> handler;
+
+ bool handle(const qpid::framing::AMQMethodBody& method);
+ void close(framing::connection::CloseCode code, const std::string& text);
+ public:
+ ConnectionHandler(amqp_0_10::Connection& connection, bool isClient );
+ void heartbeat();
+ void handle(framing::AMQFrame& frame);
+ void setSecureConnection(SecureConnection* secured);
+ bool isOpen() { return handler->isOpen; }
+ friend class amqp_0_10::Connection;
+};
+
+
+}}
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/ConnectionObserver.h b/qpid/cpp/src/qpid/broker/ConnectionObserver.h
new file mode 100644
index 0000000000..eea2981185
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/ConnectionObserver.h
@@ -0,0 +1,59 @@
+#ifndef QPID_BROKER_CONNECTIONOBSERVER_H
+#define QPID_BROKER_CONNECTIONOBSERVER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <string>
+
+namespace qpid {
+namespace broker {
+
+class Connection;
+
+/**
+ * Observer that is informed of connection events. For use by
+ * plug-ins that want to be notified of, or influence, connection
+ * events.
+ */
+class ConnectionObserver
+{
+ public:
+ virtual ~ConnectionObserver() {}
+
+ /** Called when a connection is first established. */
+ virtual void connection(Connection&) {}
+
+ /** Called when the opening negotiation is done and the connection is authenticated.
+ * @exception Throwing an exception will abort the connection.
+ */
+ virtual void opened(Connection&) {}
+
+ /** Called when a connection is closed. */
+ virtual void closed(Connection&) {}
+
+ /** Called when a connection is forced closed. */
+ virtual void forced(Connection&, const std::string& /*message*/) {}
+};
+
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_CONNECTIONOBSERVER_H*/
diff --git a/qpid/cpp/src/qpid/broker/ConnectionObservers.h b/qpid/cpp/src/qpid/broker/ConnectionObservers.h
new file mode 100644
index 0000000000..8b9fb67aa5
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/ConnectionObservers.h
@@ -0,0 +1,56 @@
+#ifndef QPID_BROKER_CONNECTIONOBSERVERS_H
+#define QPID_BROKER_CONNECTIONOBSERVERS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "ConnectionObserver.h"
+#include "Observers.h"
+
+namespace qpid {
+namespace broker {
+
+/**
+ * A collection of connection observers.
+ */
+class ConnectionObservers : public Observers<ConnectionObserver>
+{
+ public:
+ void connection(Connection& c) {
+ each(boost::bind(&ConnectionObserver::connection, _1, boost::ref(c)));
+ }
+
+ void opened(Connection& c) {
+ each(boost::bind(&ConnectionObserver::opened, _1, boost::ref(c)));
+ }
+
+ void closed(Connection& c) {
+ each(boost::bind(&ConnectionObserver::closed, _1, boost::ref(c)));
+ }
+
+ void forced(Connection& c, const std::string& text) {
+ each(boost::bind(&ConnectionObserver::forced, _1, boost::ref(c), text));
+ }
+};
+
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_CONNECTIONOBSERVERS_H*/
diff --git a/qpid/cpp/src/qpid/broker/Consumer.h b/qpid/cpp/src/qpid/broker/Consumer.h
new file mode 100644
index 0000000000..4a0621243c
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Consumer.h
@@ -0,0 +1,108 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#ifndef _Consumer_
+#define _Consumer_
+
+#include "qpid/broker/QueueCursor.h"
+#include "qpid/broker/OwnershipToken.h"
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace qpid {
+namespace broker {
+
+class DeliveryRecord;
+class Message;
+class Queue;
+class QueueListeners;
+
+/**
+ * Base class for consumers which represent a subscription to a queue.
+ */
+class Consumer : public QueueCursor {
+ const bool acquires;
+ // inListeners allows QueueListeners to efficiently track if this
+ // instance is registered for notifications without having to
+ // search its containers
+ bool inListeners;
+ // the name is generated by broker and is unique within broker scope. It is not
+ // provided or known by the remote Consumer.
+ const std::string name;
+ public:
+ typedef boost::shared_ptr<Consumer> shared_ptr;
+
+ Consumer(const std::string& _name, SubscriptionType type, const std::string& _tag)
+ : QueueCursor(type), acquires(type == CONSUMER), inListeners(false), name(_name), tag(_tag) {}
+ virtual ~Consumer(){}
+
+ bool preAcquires() const { return acquires; }
+ const std::string& getName() const { return name; }
+
+ virtual bool deliver(const QueueCursor& cursor, const Message& msg) = 0;
+ virtual void notify() = 0;
+ virtual bool filter(const Message&) { return true; }
+ virtual bool accept(const Message&) { return true; }
+ virtual OwnershipToken* getSession() = 0;
+ virtual void cancel() = 0;
+
+ /** Returns true if the browser wants acquired as well as
+ * available messages.
+ */
+ virtual bool browseAcquired() const { return false; };
+
+ /** Called when the peer has acknowledged receipt of the message.
+ * Not to be confused with accept() above, which is asking if
+ * this consumer will consume/browse the message.
+ */
+ virtual void acknowledged(const DeliveryRecord&) = 0;
+
+ /** Called if queue has been deleted, if true suppress the error message.
+ * Used by HA ReplicatingSubscriptions where such errors are normal.
+ */
+ virtual bool hideDeletedError() { return false; }
+
+ /** If false, the consumer is not counted for purposes of auto-deletion or
+ * immediate messages. This is used for "system" consumers that are created
+ * by the broker for internal purposes as opposed to consumers that are
+ * created by normal clients.
+ */
+ virtual bool isCounted() { return true; }
+
+ QueueCursor getCursor() const { return *this; }
+ void setCursor(const QueueCursor& qc) { static_cast<QueueCursor&>(*this) = qc; }
+
+ const std::string& getTag() const { return tag; }
+
+ /** Called when there are no more messages immediately available for this consumer on the queue */
+ virtual void stopped() {}
+
+ protected:
+ //framing::SequenceNumber position;
+ const std::string tag; // <destination> from AMQP 0-10 Message.subscribe command
+
+ private:
+ friend class QueueListeners;
+};
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/ConsumerFactory.h b/qpid/cpp/src/qpid/broker/ConsumerFactory.h
new file mode 100644
index 0000000000..1c0f2571e2
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/ConsumerFactory.h
@@ -0,0 +1,73 @@
+#ifndef QPID_BROKER_CONSUMERFACTORY_H
+#define QPID_BROKER_CONSUMERFACTORY_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+// TODO aconway 2011-11-25: it's ugly exposing SemanticState::ConsumerImpl in public.
+// Refactor to use a more abstract interface.
+
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace broker {
+
+class SemanticState;
+class SemanticStateConsumerImpl;
+
+/**
+ * Base class for consumer factoires. Plugins can register a
+ * ConsumerFactory via Broker:: getConsumerFactories() Each time a
+ * conumer is created, each factory is tried in turn till one returns
+ * non-0.
+ */
+class ConsumerFactory
+{
+ public:
+ virtual ~ConsumerFactory() {}
+
+ virtual boost::shared_ptr<SemanticStateConsumerImpl> create(
+ SemanticState* parent,
+ const std::string& name, boost::shared_ptr<Queue> queue,
+ bool ack, bool acquire, bool exclusive, const std::string& tag,
+ const std::string& resumeId, uint64_t resumeTtl, const framing::FieldTable& arguments) = 0;
+};
+
+/** A set of factories held by the broker
+ * THREAD UNSAFE: see notes on member functions.
+ */
+class ConsumerFactories {
+ public:
+ typedef std::vector<boost::shared_ptr<ConsumerFactory> > Factories;
+
+ /** Thread safety: May only be called during plug-in initialization. */
+ void add(const boost::shared_ptr<ConsumerFactory>& cf) { factories.push_back(cf); }
+
+ /** Thread safety: May only be called after plug-in initialization. */
+ const Factories& get() const { return factories; }
+
+ private:
+ Factories factories;
+};
+
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_CONSUMERFACTORY_H*/
diff --git a/qpid/cpp/src/qpid/broker/Credit.cpp b/qpid/cpp/src/qpid/broker/Credit.cpp
new file mode 100644
index 0000000000..c0e0b3b3d3
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Credit.cpp
@@ -0,0 +1,151 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/Credit.h"
+
+namespace qpid {
+namespace broker {
+
+const uint32_t CreditBalance::INFINITE_CREDIT(0xFFFFFFFF);
+CreditBalance::CreditBalance() : balance(0) {}
+CreditBalance::~CreditBalance() {}
+void CreditBalance::clear() { balance = 0; }
+void CreditBalance::grant(uint32_t value)
+{
+ if (balance != INFINITE_CREDIT) {
+ if (value == INFINITE_CREDIT) {
+ balance = INFINITE_CREDIT;
+ } else if (INFINITE_CREDIT - balance > value) {
+ balance += value;
+ } else {
+ balance = INFINITE_CREDIT - 1;
+ }
+ }
+}
+void CreditBalance::consume(uint32_t value) { if (!unlimited()) balance -= value; }
+bool CreditBalance::check(uint32_t required) const { return balance >= required; }
+uint32_t CreditBalance::remaining() const { return balance; }
+uint32_t CreditBalance::allocated() const { return balance; }
+bool CreditBalance::unlimited() const { return balance == INFINITE_CREDIT; }
+
+CreditWindow::CreditWindow() : used(0) {}
+bool CreditWindow::check(uint32_t required) const { return CreditBalance::check(used + required); }
+void CreditWindow::consume(uint32_t value) { if (!unlimited()) used += value; }
+void CreditWindow::move(uint32_t value) { if (!unlimited()) used -= value; }
+uint32_t CreditWindow::remaining() const { return allocated() - used; }
+uint32_t CreditWindow::consumed() const { return used; }
+
+Credit::Credit() : windowing(true) {}
+void Credit::setWindowMode(bool b) { windowing = b; }
+bool Credit::isWindowMode() const { return windowing; }
+void Credit::addByteCredit(uint32_t value)
+{
+ bytes().grant(value);
+}
+void Credit::addMessageCredit(uint32_t value)
+{
+ messages().grant(value);
+}
+void Credit::cancel()
+{
+ messages().clear();
+ bytes().clear();
+}
+void Credit::moveWindow(uint32_t m, uint32_t b)
+{
+ if (windowing) {
+ window.messages.move(m);
+ window.bytes.move(b);
+ }
+}
+void Credit::consume(uint32_t m, uint32_t b)
+{
+ messages().consume(m);
+ bytes().consume(b);
+}
+bool Credit::check(uint32_t m, uint32_t b) const
+{
+ return messages().check(m) && bytes().check(b);
+}
+CreditPair<uint32_t> Credit::used() const
+{
+ CreditPair<uint32_t> result;
+ if (windowing) {
+ result.messages = window.messages.consumed();
+ result.bytes = window.bytes.consumed();
+ } else {
+ result.messages = 0;
+ result.bytes = 0;
+ }
+ return result;
+}
+CreditPair<uint32_t> Credit::allocated() const
+{
+ CreditPair<uint32_t> result;
+ result.messages = messages().allocated();
+ result.bytes = bytes().allocated();
+ return result;
+}
+Credit::operator bool() const
+{
+ return check(1,1);
+}
+CreditBalance& Credit::messages()
+{
+ if (windowing) return window.messages;
+ else return balance.messages;
+}
+CreditBalance& Credit::bytes()
+{
+ if (windowing) return window.bytes;
+ else return balance.bytes;
+}
+const CreditBalance& Credit::messages() const
+{
+ if (windowing) return window.messages;
+ else return balance.messages;
+}
+const CreditBalance& Credit::bytes() const
+{
+ if (windowing) return window.bytes;
+ else return balance.bytes;
+}
+std::ostream& operator<<(std::ostream& out, const CreditBalance& b)
+{
+ if (b.unlimited()) return out << "unlimited";
+ else return out << b.balance;
+}
+std::ostream& operator<<(std::ostream& out, const CreditWindow& w)
+{
+ if (w.unlimited()) return out << ((CreditBalance) w);
+ else return out << w.remaining() << " (from window of " << w.allocated() << ")";
+}
+template <class T>
+std::ostream& operator<<(std::ostream& out, const CreditPair<T>& pair)
+{
+ return out << "messages: " << pair.messages << " bytes: " << pair.bytes;
+}
+std::ostream& operator<<(std::ostream& out, const Credit& c)
+{
+ if (c.windowing) return out << c.window;
+ else return out << c.balance;
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/Credit.h b/qpid/cpp/src/qpid/broker/Credit.h
new file mode 100644
index 0000000000..7f98c8d071
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Credit.h
@@ -0,0 +1,96 @@
+#ifndef QPID_BROKER_CREDIT_H
+#define QPID_BROKER_CREDIT_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/sys/IntegerTypes.h"
+#include <memory>
+#include <ostream>
+
+namespace qpid {
+namespace broker {
+
+class CreditBalance {
+ public:
+ CreditBalance();
+ virtual ~CreditBalance();
+ void clear();
+ void grant(uint32_t value);
+ virtual void consume(uint32_t value);
+ virtual bool check(uint32_t required) const;
+ virtual uint32_t remaining() const;
+ uint32_t allocated() const;
+ bool unlimited() const;
+ static const uint32_t INFINITE_CREDIT;
+ friend std::ostream& operator<<(std::ostream&, const CreditBalance&);
+ private:
+ uint32_t balance;
+};
+
+class CreditWindow : public CreditBalance {
+ public:
+ CreditWindow();
+ bool check(uint32_t required) const;
+ void consume(uint32_t value);
+ void move(uint32_t value);
+ uint32_t remaining() const;
+ uint32_t consumed() const;
+ friend std::ostream& operator<<(std::ostream&, const CreditWindow&);
+ private:
+ uint32_t used;
+};
+
+template<class T> struct CreditPair
+{
+ T messages;
+ T bytes;
+};
+
+class Credit {
+ public:
+ Credit();
+ void setWindowMode(bool);
+ bool isWindowMode() const;
+ void addByteCredit(uint32_t);
+ void addMessageCredit(uint32_t);
+ void consume(uint32_t messages, uint32_t bytes);
+ void moveWindow(uint32_t messages, uint32_t bytes);
+ bool check(uint32_t messages, uint32_t bytes) const;
+ void cancel();
+ operator bool() const;
+ CreditPair<uint32_t> allocated() const;
+ CreditPair<uint32_t> used() const;
+ friend std::ostream& operator<<(std::ostream&, const Credit&);
+ private:
+ CreditPair<CreditBalance> balance;
+ CreditPair<CreditWindow> window;
+ bool windowing;
+ CreditBalance& bytes();
+ CreditBalance& messages();
+ const CreditBalance& bytes() const;
+ const CreditBalance& messages() const;
+};
+
+std::ostream& operator<<(std::ostream&, const Credit&);
+
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_CREDIT_H*/
diff --git a/qpid/cpp/src/qpid/broker/Daemon.cpp b/qpid/cpp/src/qpid/broker/Daemon.cpp
new file mode 100644
index 0000000000..9f7a5b3f2d
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Daemon.cpp
@@ -0,0 +1,217 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/*
+ * TODO: Note this is really a Posix specific implementation and so should be
+ * refactored together with windows/QpiddBroker into a more coherent daemon driver/
+ * platform specific split
+ */
+#include "qpid/broker/Daemon.h"
+#include "qpid/log/Statement.h"
+#include "qpid/Exception.h"
+#include "qpid/sys/posix/PidFile.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+namespace qpid {
+namespace broker {
+
+using namespace std;
+using qpid::sys::PidFile;
+
+Daemon::Daemon(std::string _pidDir) : pidDir(_pidDir) {
+ struct stat s;
+ pid = -1;
+ pipeFds[0] = pipeFds[1] = -1;
+
+ if (::stat(pidDir.c_str(), &s)) {
+ if (errno == ENOENT) {
+ if (::mkdir(pidDir.c_str(), 0755))
+ throw Exception ("Can't create PID directory: " + pidDir);
+ }
+ else
+ throw Exception ("PID directory not found: " + pidDir);
+ }
+}
+
+string Daemon::pidFile(string pidDir, uint16_t port) {
+ ostringstream path;
+ path << pidDir << "/qpidd." << port << ".pid";
+ return path.str();
+}
+
+/*
+ * Rewritten using low-level IO, for compatibility
+ * with earlier Boost versions, i.e. 103200.
+ */
+void Daemon::fork()
+{
+ if(::pipe(pipeFds) < 0) throw ErrnoException("Can't create pipe");
+ if ((pid = ::fork()) < 0) throw ErrnoException("Daemon fork failed");
+ if (pid == 0) { // Child
+ try {
+ QPID_LOG(debug, "Forked daemon child process");
+
+ // File descriptors
+ if(::close(pipeFds[0])<0) throw ErrnoException("Cannot close read pipe");
+ if(::close(0)<0) throw ErrnoException("Cannot close stdin");
+ if(::close(1)<0) throw ErrnoException("Cannot close stdout");
+ if(::close(2)<0) throw ErrnoException("Cannot close stderr");
+ int fd=::open("/dev/null",O_RDWR); // stdin
+ if(fd != 0) throw ErrnoException("Cannot re-open stdin");
+ if(::dup(fd)<0) throw ErrnoException("Cannot re-open stdout");
+ if(::dup(fd)<0) throw ErrnoException("Cannot re-open stderror");
+
+ // Misc
+ if(setsid()<0) throw ErrnoException("Cannot set session ID");
+ if(chdir(pidDir.c_str()) < 0) throw ErrnoException("Cannot change directory to "+pidDir);
+ umask(027);
+
+ // Child behavior
+ child();
+ }
+ catch (const exception& e) {
+ uint16_t port = 0;
+ if (write(pipeFds[1], &port, sizeof(uint16_t))) {};
+
+ std::string pipeFailureMessage = e.what();
+ if (write(pipeFds[1],
+ pipeFailureMessage.c_str(),
+ strlen(pipeFailureMessage.c_str())
+ )) {};
+ }
+ }
+ else { // Parent
+ close(pipeFds[1]); // Write side.
+ parent();
+ }
+}
+
+Daemon::~Daemon() {
+ if (!lockFile.empty())
+ unlink(lockFile.c_str());
+}
+
+uint16_t Daemon::wait(int timeout) { // parent waits for child.
+ try {
+ errno = 0;
+ struct timeval tv;
+ tv.tv_sec = timeout;
+ tv.tv_usec = 0;
+
+ /*
+ * Rewritten using low-level IO, for compatibility
+ * with earlier Boost versions, i.e. 103200.
+ */
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(pipeFds[0], &fds);
+ int n=select(FD_SETSIZE, &fds, 0, 0, &tv);
+ if(n==0) throw Exception("Timed out waiting for daemon (If store recovery is in progress, use longer wait time)");
+ if(n<0) throw ErrnoException("Error waiting for daemon");
+ uint16_t port = 0;
+ /*
+ * Read the child's port number from the pipe.
+ */
+ int desired_read = sizeof(uint16_t);
+ if ( desired_read > ::read(pipeFds[0], & port, desired_read) )
+ throw Exception("Cannot read from child process.");
+
+ /*
+ * If the port number is 0, the child has put an error message
+ * on the pipe. Get it and throw it.
+ */
+ if ( 0 == port ) {
+ // Skip whitespace
+ char c = ' ';
+ while ( isspace(c) ) {
+ if ( 1 > ::read(pipeFds[0], &c, 1) )
+ throw Exception("Child port == 0, and no error message on pipe.");
+ }
+
+ // Get Message
+ string errmsg;
+ do {
+ errmsg += c;
+ } while (::read(pipeFds[0], &c, 1));
+ throw Exception("Daemon startup failed"+
+ (errmsg.empty() ? string(".") : ": " + errmsg));
+ }
+ return port;
+ }
+ catch (const std::exception& e) {
+ // Print directly to cerr. The caller will catch and log the
+ // exception, but in the case of a daemon parent process we
+ // also need to be sure the error goes to stderr. A
+ // dameon's logging configuration normally does not log to
+ // stderr.
+ std::cerr << e.what() << endl;
+ throw;
+ }
+}
+
+
+/*
+ * When the child is ready, it writes its pid to the
+ * lockfile and its port number on the pipe back to
+ * its parent process. This indicates that the
+ * child has successfully daemonized. When the parent
+ * hears the good news, it ill exit.
+ */
+void Daemon::ready(uint16_t port) { // child
+ lockFile = pidFile(pidDir, port);
+ PidFile lf(lockFile, true);
+
+ /*
+ * Write the PID to the lockfile.
+ */
+ lf.writePid();
+
+ /*
+ * Write the port number to the parent.
+ */
+ int desired_write = sizeof(uint16_t);
+ if ( desired_write > ::write(pipeFds[1], & port, desired_write) ) {
+ throw ErrnoException("Error writing to parent" );
+ }
+
+ QPID_LOG(debug, "Daemon ready on port: " << port);
+}
+
+/*
+ * The parent process reads the child's pid
+ * from the lockfile.
+ */
+pid_t Daemon::getPid(string _pidDir, uint16_t port) {
+ string name = pidFile(_pidDir, port);
+ PidFile lf(name, false);
+ pid_t pid = lf.readPid();
+ if (kill(pid, 0) < 0 && errno != EPERM) {
+ unlink(name.c_str());
+ throw Exception("Removing stale lock file "+name);
+ }
+ return pid;
+}
+
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/Daemon.h b/qpid/cpp/src/qpid/broker/Daemon.h
new file mode 100644
index 0000000000..2bb9fc5577
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Daemon.h
@@ -0,0 +1,83 @@
+#ifndef _broker_Daemon_h
+#define _broker_Daemon_h
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/sys/IntegerTypes.h"
+#include <boost/scoped_ptr.hpp>
+#include <boost/function.hpp>
+#include <boost/noncopyable.hpp>
+#include <string>
+
+
+namespace qpid {
+namespace broker {
+
+/**
+ * Tools for forking and managing a daemon process.
+ * NB: Only one Daemon instance is allowed in a process.
+ */
+class Daemon : private boost::noncopyable
+{
+ public:
+ /** Check daemon is running on port, throw exception if not */
+ static pid_t getPid(std::string pidDir, uint16_t port);
+
+ Daemon(std::string pidDir);
+
+ virtual ~Daemon();
+
+ /**
+ * Fork a daemon process.
+ * Call parent() in the parent process, child() in the child.
+ */
+ void fork();
+
+ protected:
+
+ /** Called in parent process */
+ virtual void parent() = 0;
+
+ /** Called in child process */
+ virtual void child() = 0;
+
+ /** Call from parent(): wait for child to indicate it is ready.
+ * @timeout in seconds to wait for response.
+ * @return port passed by child to ready().
+ */
+ uint16_t wait(int timeout);
+
+ /** Call from child(): Notify the parent we are ready and write the
+ * PID file.
+ *@param port returned by parent call to wait().
+ */
+ void ready(uint16_t port);
+
+ private:
+ static std::string pidFile(std::string pidDir, uint16_t port);
+
+ pid_t pid;
+ int pipeFds[2];
+ std::string lockFile;
+ std::string pidDir;
+};
+
+}} // namespace qpid::broker
+
+#endif /*!_broker_Daemon_h*/
diff --git a/qpid/cpp/src/qpid/broker/Deliverable.h b/qpid/cpp/src/qpid/broker/Deliverable.h
new file mode 100644
index 0000000000..4dc67fdcfc
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Deliverable.h
@@ -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.
+ *
+ */
+#ifndef _Deliverable_
+#define _Deliverable_
+
+#include "qpid/broker/AsyncCompletion.h"
+#include "qpid/sys/IntegerTypes.h"
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+ namespace broker {
+ class Message;
+ class Queue;
+
+ class Deliverable : public AsyncCompletion {
+ public:
+ bool delivered;
+ Deliverable() : delivered(false) {}
+
+ virtual Message& getMessage() = 0;
+
+ virtual void deliverTo(const boost::shared_ptr<Queue>& queue) = 0;
+ virtual ~Deliverable(){}
+ };
+ }
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/DeliverableMessage.cpp b/qpid/cpp/src/qpid/broker/DeliverableMessage.cpp
new file mode 100644
index 0000000000..31823709ce
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/DeliverableMessage.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/broker/DeliverableMessage.h"
+#include "qpid/broker/Queue.h"
+
+using namespace qpid::broker;
+
+DeliverableMessage::DeliverableMessage(const Message& _msg, TxBuffer* _txn) : msg(_msg), txn(_txn) {}
+
+void DeliverableMessage::deliverTo(const boost::shared_ptr<Queue>& queue)
+{
+ queue->deliver(msg, txn);
+ delivered = true;
+}
+
+Message& DeliverableMessage::getMessage()
+{
+ return msg;
+}
diff --git a/qpid/cpp/src/qpid/broker/DeliverableMessage.h b/qpid/cpp/src/qpid/broker/DeliverableMessage.h
new file mode 100644
index 0000000000..6e8275770d
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/DeliverableMessage.h
@@ -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.
+ *
+ */
+#ifndef _DeliverableMessage_
+#define _DeliverableMessage_
+
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/broker/Deliverable.h"
+#include "qpid/broker/Message.h"
+
+namespace qpid {
+ namespace broker {
+ class TxBuffer;
+ class QPID_BROKER_CLASS_EXTERN DeliverableMessage : public Deliverable
+ {
+ Message msg;
+ TxBuffer* txn;
+ public:
+ QPID_BROKER_EXTERN DeliverableMessage(const Message& msg, TxBuffer* txn);
+ QPID_BROKER_EXTERN virtual void deliverTo(const boost::shared_ptr<Queue>& queue);
+ QPID_BROKER_EXTERN Message& getMessage();
+ virtual ~DeliverableMessage(){}
+ };
+ }
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/DeliveryId.h b/qpid/cpp/src/qpid/broker/DeliveryId.h
new file mode 100644
index 0000000000..05b19f032e
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/DeliveryId.h
@@ -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.
+ *
+ */
+#ifndef _DeliveryId_
+#define _DeliveryId_
+
+#include "qpid/framing/SequenceNumber.h"
+#include "qpid/framing/SequenceNumberSet.h"
+
+namespace qpid {
+namespace broker {
+
+ typedef framing::SequenceNumber DeliveryId;
+ typedef framing::SequenceNumberSet DeliveryIds;
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/DeliveryRecord.cpp b/qpid/cpp/src/qpid/broker/DeliveryRecord.cpp
new file mode 100644
index 0000000000..06ecf62ed4
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/DeliveryRecord.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/DeliveryRecord.h"
+#include "qpid/broker/DeliverableMessage.h"
+#include "qpid/broker/SemanticState.h"
+#include "qpid/broker/Consumer.h"
+#include "qpid/broker/Exchange.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/amqp_0_10/MessageTransfer.h"
+#include "qpid/log/Statement.h"
+#include "qpid/framing/FrameHandler.h"
+#include "qpid/framing/MessageTransferBody.h"
+
+using namespace qpid;
+using namespace qpid::broker;
+using std::string;
+
+DeliveryRecord::DeliveryRecord(const QueueCursor& _msg,
+ framing::SequenceNumber _msgId,
+ framing::SequenceNumber _replicationId,
+ const Queue::shared_ptr& _queue,
+ const std::string& _tag,
+ const boost::shared_ptr<Consumer>& _consumer,
+ bool _acquired,
+ bool accepted,
+ bool _windowing,
+ uint32_t _credit) : msg(_msg),
+ queue(_queue),
+ tag(_tag),
+ consumer(_consumer),
+ acquired(_acquired),
+ acceptExpected(!accepted),
+ cancelled(false),
+ completed(false),
+ ended(accepted && acquired),
+ windowing(_windowing),
+ credit(_credit),
+ msgId(_msgId),
+ replicationId(_replicationId)
+{}
+
+bool DeliveryRecord::setEnded()
+{
+ ended = true;
+ QPID_LOG(debug, "DeliveryRecord::setEnded() id=" << id);
+ return isRedundant();
+}
+
+void DeliveryRecord::requeue()
+{
+ if (acquired && !ended) {
+ queue->release(msg);
+ }
+}
+
+void DeliveryRecord::release(bool setRedelivered)
+{
+ if (acquired && !ended) {
+ queue->release(msg, setRedelivered);
+ acquired = false;
+ setEnded();
+ } else {
+ QPID_LOG(debug, "Ignoring release for " << id << " acquired=" << acquired << ", ended =" << ended);
+ }
+}
+
+void DeliveryRecord::complete()
+{
+ completed = true;
+}
+
+bool DeliveryRecord::accept(TransactionContext* ctxt) {
+ if (!ended) {
+ if (consumer) consumer->acknowledged(*this);
+ if (acquired) queue->dequeue(ctxt, msg);
+ setEnded();
+ QPID_LOG(debug, "Accepted " << id);
+ }
+ return isRedundant();
+}
+
+void DeliveryRecord::dequeue(TransactionContext* ctxt) const
+{
+ if (acquired && !ended) {
+ queue->dequeue(ctxt, msg);
+ }
+}
+
+void DeliveryRecord::committed() const
+{
+ if (acquired && !ended) {
+ queue->dequeueCommitted(msg);
+ }
+}
+
+void DeliveryRecord::reject()
+{
+ if (acquired && !ended) {
+ queue->reject(msg);
+ setEnded();
+ }
+}
+
+uint32_t DeliveryRecord::getCredit() const
+{
+ return credit;
+}
+
+void DeliveryRecord::acquire(DeliveryIds& results) {
+ if (queue->acquire(msg, tag)) {
+ acquired = true;
+ results.push_back(id);
+ if (!acceptExpected) {
+ if (ended) { QPID_LOG(error, "Can't dequeue ended message"); }
+ else { queue->dequeue(0, msg); setEnded(); }
+ }
+ } else {
+ QPID_LOG(info, "Message already acquired " << id.getValue());
+ }
+}
+
+void DeliveryRecord::cancel(const std::string& cancelledTag)
+{
+ if (tag == cancelledTag)
+ cancelled = true;
+}
+
+AckRange DeliveryRecord::findRange(DeliveryRecords& records, DeliveryId first, DeliveryId last)
+{
+ DeliveryRecords::iterator start = lower_bound(records.begin(), records.end(), first);
+ // Find end - position it just after the last record in range
+ DeliveryRecords::iterator end = lower_bound(records.begin(), records.end(), last);
+ if (end != records.end() && end->getId() == last) ++end;
+ return AckRange(start, end);
+}
+
+
+namespace qpid {
+namespace broker {
+
+std::ostream& operator<<(std::ostream& out, const DeliveryRecord& r)
+{
+ out << "{" << "id=" << r.id.getValue();
+ out << ", tag=" << r.tag << "}";
+ out << ", queue=" << r.queue->getName() << "}";
+ return out;
+}
+
+
+}}
diff --git a/qpid/cpp/src/qpid/broker/DeliveryRecord.h b/qpid/cpp/src/qpid/broker/DeliveryRecord.h
new file mode 100644
index 0000000000..37ce8dc709
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/DeliveryRecord.h
@@ -0,0 +1,152 @@
+#ifndef QPID_BROKER_DELIVERYRECORD_H
+#define QPID_BROKER_DELIVERYRECORD_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <algorithm>
+#include <deque>
+#include <vector>
+#include <ostream>
+#include "qpid/framing/FrameHandler.h"
+#include "qpid/framing/SequenceSet.h"
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/broker/QueueCursor.h"
+#include "qpid/broker/DeliveryId.h"
+#include "qpid/broker/Message.h"
+
+namespace qpid {
+namespace broker {
+
+class Queue;
+class TransactionContext;
+class SemanticState;
+struct AckRange;
+class Consumer;
+
+/**
+ * Record of a delivery for which an ack is outstanding.
+ */
+class DeliveryRecord
+{
+ QueueCursor msg;
+ mutable boost::shared_ptr<Queue> queue;
+ std::string tag; // name of consumer
+ boost::shared_ptr<Consumer> consumer;
+ DeliveryId id;
+ bool acquired : 1;
+ bool acceptExpected : 1;
+ bool cancelled : 1;
+ bool completed : 1;
+ bool ended : 1;
+ bool windowing : 1;
+
+ /**
+ * Record required credit on construction as the pointer to the
+ * message may be reset once we no longer need to deliver it
+ * (e.g. when it is accepted), but we will still need to be able
+ * to reallocate credit when it is completed (which could happen
+ * after that).
+ */
+ uint32_t credit;
+ framing::SequenceNumber msgId;
+ framing::SequenceNumber replicationId;
+
+ public:
+ QPID_BROKER_EXTERN DeliveryRecord(const QueueCursor& msgCursor,
+ framing::SequenceNumber msgId,
+ framing::SequenceNumber replicationId,
+ const boost::shared_ptr<Queue>& queue,
+ const std::string& tag,
+ const boost::shared_ptr<Consumer>& consumer,
+ bool acquired,
+ bool accepted,
+ bool windowing,
+ uint32_t credit=0 // Only used if msg is empty.
+ );
+
+ bool coveredBy(const framing::SequenceSet* const range) const { return range->contains(id); }
+
+ void dequeue(TransactionContext* ctxt = 0) const;
+ void requeue();
+ void release(bool setRedelivered);
+ void reject();
+ void cancel(const std::string& tag);
+ void acquire(DeliveryIds& results);
+ void complete();
+ bool accept(TransactionContext* ctxt); // Returns isRedundant()
+ bool setEnded(); // Returns isRedundant()
+ void committed() const;
+
+ bool isAcquired() const { return acquired; }
+ bool isComplete() const { return completed; }
+ bool isRedundant() const { return ended && (!windowing || completed || cancelled); }
+ bool isCancelled() const { return cancelled; }
+ bool isAccepted() const { return !acceptExpected; }
+ bool isEnded() const { return ended; }
+ bool isWindowing() const { return windowing; }
+
+ uint32_t getCredit() const;
+ const std::string& getTag() const { return tag; }
+
+ void setId(DeliveryId _id) { id = _id; }
+
+ typedef std::deque<DeliveryRecord> DeliveryRecords;
+ static AckRange findRange(DeliveryRecords& records, DeliveryId first, DeliveryId last);
+ const QueueCursor& getMessage() const { return msg; }
+ framing::SequenceNumber getId() const { return id; }
+ framing::SequenceNumber getMessageId() const { return msgId; }
+ framing::SequenceNumber getReplicationId() const { return replicationId; }
+ boost::shared_ptr<Queue> getQueue() const { return queue; }
+
+ friend std::ostream& operator<<(std::ostream&, const DeliveryRecord&);
+};
+
+inline bool operator<(const DeliveryRecord& a, const DeliveryRecord& b) { return a.getId() < b.getId(); }
+inline bool operator<(const framing::SequenceNumber& a, const DeliveryRecord& b) { return a < b.getId(); }
+inline bool operator<(const DeliveryRecord& a, const framing::SequenceNumber& b) { return a.getId() < b; }
+
+struct AcquireFunctor
+{
+ DeliveryIds& results;
+
+ AcquireFunctor(DeliveryIds& _results) : results(_results) {}
+
+ void operator()(DeliveryRecord& record)
+ {
+ record.acquire(results);
+ }
+};
+
+typedef DeliveryRecord::DeliveryRecords DeliveryRecords;
+
+struct AckRange
+{
+ DeliveryRecords::iterator start;
+ DeliveryRecords::iterator end;
+ AckRange(DeliveryRecords::iterator _start, DeliveryRecords::iterator _end) : start(_start), end(_end) {}
+};
+
+}
+}
+
+
+#endif /*!QPID_BROKER_DELIVERYRECORD_H*/
diff --git a/qpid/cpp/src/qpid/broker/DirectExchange.cpp b/qpid/cpp/src/qpid/broker/DirectExchange.cpp
new file mode 100644
index 0000000000..be66bc2e5f
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/DirectExchange.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/log/Statement.h"
+#include "qpid/broker/FedOps.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/DirectExchange.h"
+#include <iostream>
+
+using namespace qpid::broker;
+
+using std::string;
+
+using namespace qpid::framing;
+using namespace qpid::sys;
+using qpid::management::Manageable;
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+namespace
+{
+ const std::string qpidExclusiveBinding("qpid.exclusive-binding");
+}
+
+DirectExchange::DirectExchange(const string& _name, Manageable* _parent, Broker* b) : Exchange(_name, _parent, b)
+{
+ if (mgmtExchange != 0)
+ mgmtExchange->set_type(typeName);
+}
+
+DirectExchange::DirectExchange(const string& _name, bool _durable, bool autodelete,
+ const FieldTable& _args, Manageable* _parent, Broker* b) :
+ Exchange(_name, _durable, autodelete, _args, _parent, b)
+{
+ if (mgmtExchange != 0)
+ mgmtExchange->set_type(typeName);
+}
+
+bool DirectExchange::bind(Queue::shared_ptr queue, const string& routingKey, const FieldTable* args)
+{
+ string fedOp(fedOpBind);
+ string fedTags;
+ string fedOrigin;
+ bool exclusiveBinding = false;
+ if (args) {
+ fedOp = args->getAsString(qpidFedOp);
+ fedTags = args->getAsString(qpidFedTags);
+ fedOrigin = args->getAsString(qpidFedOrigin);
+ exclusiveBinding = !!args->get(qpidExclusiveBinding); // only direct exchanges take exclusive bindings
+ }
+
+ bool propagate = false;
+
+ if (args == 0 || fedOp.empty() || fedOp == fedOpBind) {
+ Mutex::ScopedLock l(lock);
+ Binding::shared_ptr b(new Binding(routingKey, queue, this, args ? *args : FieldTable(), fedOrigin));
+ BoundKey& bk = bindings[routingKey];
+ if (exclusiveBinding) bk.queues.clear();
+
+ QPID_LOG(debug, "Bind key [" << routingKey << "] to queue " << queue->getName()
+ << " (origin=" << fedOrigin << ")");
+
+ if (bk.queues.add_unless(b, MatchQueue(queue))) {
+ b->startManagement();
+ propagate = bk.fedBinding.addOrigin(queue->getName(), fedOrigin);
+ if (mgmtExchange != 0) {
+ mgmtExchange->inc_bindingCount();
+ }
+ } else {
+ // queue already present - still need to track fedOrigin
+ bk.fedBinding.addOrigin(queue->getName(), fedOrigin);
+ return false;
+ }
+ } else if (fedOp == fedOpUnbind) {
+ Mutex::ScopedLock l(lock);
+ BoundKey& bk = bindings[routingKey];
+
+ QPID_LOG(debug, "Bind - fedOpUnbind key [" << routingKey << "] queue " << queue->getName()
+ << " (origin=" << fedOrigin << ")" << " (count=" << bk.fedBinding.count() << ")");
+
+ propagate = bk.fedBinding.delOrigin(queue->getName(), fedOrigin);
+ if (bk.fedBinding.countFedBindings(queue->getName()) == 0)
+ unbind(queue, routingKey, args);
+
+ } else if (fedOp == fedOpReorigin) {
+ /** gather up all the keys that need rebinding in a local vector
+ * while holding the lock. Then propagate once the lock is
+ * released
+ */
+ std::vector<std::string> keys2prop;
+ {
+ Mutex::ScopedLock l(lock);
+ for (Bindings::iterator iter = bindings.begin();
+ iter != bindings.end(); iter++) {
+ const BoundKey& bk = iter->second;
+ if (bk.fedBinding.hasLocal()) {
+ keys2prop.push_back(iter->first);
+ }
+ }
+ } /* lock dropped */
+ for (std::vector<std::string>::const_iterator key = keys2prop.begin();
+ key != keys2prop.end(); key++) {
+ propagateFedOp( *key, string(), fedOpBind, string());
+ }
+ }
+
+ routeIVE();
+ if (propagate)
+ propagateFedOp(routingKey, fedTags, fedOp, fedOrigin);
+ return true;
+}
+
+bool DirectExchange::unbind(Queue::shared_ptr queue, const string& routingKey, const FieldTable* args)
+{
+ string fedOrigin(args ? args->getAsString(qpidFedOrigin) : "");
+ bool propagate = false;
+ bool empty = false;
+
+ QPID_LOG(debug, "Unbinding key [" << routingKey << "] from queue " << queue->getName()
+ << " on exchange " << getName() << " origin=" << fedOrigin << ")" );
+ {
+ Mutex::ScopedLock l(lock);
+ BoundKey& bk = bindings[routingKey];
+ if (bk.queues.remove_if(MatchQueue(queue))) {
+ propagate = bk.fedBinding.delOrigin(queue->getName(), fedOrigin);
+ if (mgmtExchange != 0) {
+ mgmtExchange->dec_bindingCount();
+ }
+ if (bk.queues.empty()) {
+ bindings.erase(routingKey);
+ if (bindings.empty()) empty = true;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ // If I delete my local binding, propagate this unbind to any upstream brokers
+ if (propagate)
+ propagateFedOp(routingKey, string(), fedOpUnbind, string());
+ if (empty) checkAutodelete();
+ return true;
+}
+
+void DirectExchange::route(Deliverable& msg)
+{
+ const string& routingKey = msg.getMessage().getRoutingKey();
+ PreRoute pr(msg, this);
+ ConstBindingList b;
+ {
+ Mutex::ScopedLock l(lock);
+ Bindings::iterator i = bindings.find(routingKey);
+ if (i != bindings.end()) b = i->second.queues.snapshot();
+ }
+ doRoute(msg, b);
+}
+
+
+bool DirectExchange::isBound(Queue::shared_ptr queue, const string* const routingKey, const FieldTable* const)
+{
+ Mutex::ScopedLock l(lock);
+ if (routingKey) {
+ Bindings::iterator i = bindings.find(*routingKey);
+
+ if (i == bindings.end())
+ return false;
+ if (!queue)
+ return true;
+
+ Queues::ConstPtr p = i->second.queues.snapshot();
+ return p && std::find_if(p->begin(), p->end(), MatchQueue(queue)) != p->end();
+ } else if (!queue) {
+ //if no queue or routing key is specified, just report whether any bindings exist
+ return bindings.size() > 0;
+ } else {
+ for (Bindings::iterator i = bindings.begin(); i != bindings.end(); i++) {
+ Queues::ConstPtr p = i->second.queues.snapshot();
+ if (p && std::find_if(p->begin(), p->end(), MatchQueue(queue)) != p->end()) return true;
+ }
+ return false;
+ }
+
+ return false;
+}
+
+DirectExchange::~DirectExchange() {
+ if (mgmtExchange != 0)
+ mgmtExchange->debugStats("destroying");
+}
+
+const std::string DirectExchange::typeName("direct");
+
+bool DirectExchange::hasBindings()
+{
+ Mutex::ScopedLock l(lock);
+ return !bindings.empty();
+}
diff --git a/qpid/cpp/src/qpid/broker/DirectExchange.h b/qpid/cpp/src/qpid/broker/DirectExchange.h
new file mode 100644
index 0000000000..cbf9aa5975
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/DirectExchange.h
@@ -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.
+ *
+ */
+#ifndef _DirectExchange_
+#define _DirectExchange_
+
+#include <map>
+#include <vector>
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/broker/Exchange.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/sys/CopyOnWriteArray.h"
+#include "qpid/sys/Mutex.h"
+
+namespace qpid {
+namespace broker {
+class DirectExchange : public virtual Exchange {
+ typedef qpid::sys::CopyOnWriteArray<Binding::shared_ptr> Queues;
+ struct BoundKey {
+ Queues queues;
+ FedBinding fedBinding;
+ };
+ typedef std::map<std::string, BoundKey> Bindings;
+ Bindings bindings;
+ qpid::sys::Mutex lock;
+
+public:
+ QPID_BROKER_EXTERN static const std::string typeName;
+
+ QPID_BROKER_EXTERN DirectExchange(const std::string& name,
+ management::Manageable* parent = 0, Broker* broker = 0);
+ QPID_BROKER_EXTERN DirectExchange(const std::string& _name,
+ bool _durable, bool autodelete,
+ const qpid::framing::FieldTable& _args,
+ management::Manageable* parent = 0, Broker* broker = 0);
+
+ virtual std::string getType() const { return typeName; }
+
+ QPID_BROKER_EXTERN virtual bool bind(boost::shared_ptr<Queue> queue,
+ const std::string& routingKey,
+ const qpid::framing::FieldTable* args);
+ virtual bool unbind(boost::shared_ptr<Queue> queue, const std::string& routingKey, const qpid::framing::FieldTable* args);
+ QPID_BROKER_EXTERN virtual void route(Deliverable& msg);
+ QPID_BROKER_EXTERN virtual bool isBound(boost::shared_ptr<Queue> queue,
+ const std::string* const routingKey,
+ const qpid::framing::FieldTable* const args);
+
+ QPID_BROKER_EXTERN virtual ~DirectExchange();
+
+ virtual bool supportsDynamicBinding() { return true; }
+ protected:
+ bool hasBindings();
+};
+
+}}
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/DtxAck.cpp b/qpid/cpp/src/qpid/broker/DtxAck.cpp
new file mode 100644
index 0000000000..c558681d62
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/DtxAck.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/DtxAck.h"
+#include "qpid/log/Statement.h"
+
+using std::bind1st;
+using std::bind2nd;
+using std::mem_fun_ref;
+using namespace qpid::broker;
+
+DtxAck::DtxAck(const qpid::framing::SequenceSet& acked, DeliveryRecords& unacked)
+{
+ remove_copy_if(unacked.begin(), unacked.end(), inserter(pending, pending.end()),
+ not1(bind2nd(mem_fun_ref(&DeliveryRecord::coveredBy), &acked)));
+}
+
+DtxAck::DtxAck(DeliveryRecords& unacked) {
+ pending = unacked;
+}
+
+bool DtxAck::prepare(TransactionContext* ctxt) throw()
+{
+ try{
+ //record dequeue in the store
+ for (DeliveryRecords::iterator i = pending.begin(); i != pending.end(); i++) {
+ i->dequeue(ctxt);
+ }
+ return true;
+ }catch(...){
+ QPID_LOG(error, "Failed to prepare");
+ return false;
+ }
+}
+
+void DtxAck::commit() throw()
+{
+ try {
+ for_each(pending.begin(), pending.end(), mem_fun_ref(&DeliveryRecord::committed));
+ pending.clear();
+ } catch (const std::exception& e) {
+ QPID_LOG(error, "Failed to commit: " << e.what());
+ } catch(...) {
+ QPID_LOG(error, "Failed to commit (unknown error)");
+ }
+
+}
+
+void DtxAck::rollback() throw()
+{
+ try {
+ for_each(pending.begin(), pending.end(), mem_fun_ref(&DeliveryRecord::requeue));
+ pending.clear();
+ } catch (const std::exception& e) {
+ QPID_LOG(error, "Failed to complete rollback: " << e.what());
+ } catch(...) {
+ QPID_LOG(error, "Failed to complete rollback (unknown error)");
+ }
+
+}
diff --git a/qpid/cpp/src/qpid/broker/DtxAck.h b/qpid/cpp/src/qpid/broker/DtxAck.h
new file mode 100644
index 0000000000..5775edf5de
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/DtxAck.h
@@ -0,0 +1,51 @@
+#ifndef QPID_BROKER_DTXACK_H
+#define QPID_BROKER_DTXACK_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <algorithm>
+#include <functional>
+#include <list>
+#include "qpid/framing/SequenceSet.h"
+#include "qpid/broker/DeliveryRecord.h"
+#include "qpid/broker/TxOp.h"
+
+namespace qpid {
+namespace broker {
+class DtxAck : public TxOp{
+ DeliveryRecords pending;
+
+ public:
+ DtxAck(const framing::SequenceSet& acked, DeliveryRecords& unacked);
+ DtxAck(DeliveryRecords& unacked);
+ virtual bool prepare(TransactionContext* ctxt) throw();
+ virtual void commit() throw();
+ virtual void rollback() throw();
+ // TODO aconway 2013-07-08:
+ virtual void callObserver(const boost::shared_ptr<TransactionObserver>&) {}
+
+ virtual ~DtxAck(){}
+ const DeliveryRecords& getPending() const { return pending; }
+};
+
+}} // qpid::broker
+
+#endif /*!QPID_BROKER_DTXACK_H*/
diff --git a/qpid/cpp/src/qpid/broker/DtxBuffer.cpp b/qpid/cpp/src/qpid/broker/DtxBuffer.cpp
new file mode 100644
index 0000000000..13177d3b72
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/DtxBuffer.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/DtxBuffer.h"
+
+using namespace qpid::broker;
+using qpid::sys::Mutex;
+
+DtxBuffer::DtxBuffer(
+ const std::string& _xid,
+ bool ended_, bool suspended_, bool failed_, bool expired_)
+ : xid(_xid), ended(ended_), suspended(suspended_), failed(failed_), expired(expired_)
+{}
+
+DtxBuffer::~DtxBuffer() {}
+
+void DtxBuffer::markEnded()
+{
+ Mutex::ScopedLock locker(lock);
+ ended = true;
+}
+
+bool DtxBuffer::isEnded() const
+{
+ Mutex::ScopedLock locker(lock);
+ return ended;
+}
+
+void DtxBuffer::setSuspended(bool isSuspended)
+{
+ suspended = isSuspended;
+}
+
+bool DtxBuffer::isSuspended() const
+{
+ return suspended;
+}
+
+void DtxBuffer::fail()
+{
+ Mutex::ScopedLock locker(lock);
+ rollback();
+ failed = true;
+ ended = true;
+}
+
+bool DtxBuffer::isRollbackOnly() const
+{
+ Mutex::ScopedLock locker(lock);
+ return failed;
+}
+
+std::string DtxBuffer::getXid() const
+{
+ return xid;
+}
+
+void DtxBuffer::timedout()
+{
+ Mutex::ScopedLock locker(lock);
+ expired = true;
+ fail();
+}
+
+bool DtxBuffer::isExpired() const
+{
+ Mutex::ScopedLock locker(lock);
+ return expired;
+}
+
+bool DtxBuffer::isFailed() const
+{
+ return failed;
+}
diff --git a/qpid/cpp/src/qpid/broker/DtxBuffer.h b/qpid/cpp/src/qpid/broker/DtxBuffer.h
new file mode 100644
index 0000000000..21ca76c8f4
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/DtxBuffer.h
@@ -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.
+ *
+ */
+#ifndef _DtxBuffer_
+#define _DtxBuffer_
+
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/broker/TxBuffer.h"
+#include "qpid/sys/Mutex.h"
+
+namespace qpid {
+namespace broker {
+class DtxBuffer : public TxBuffer {
+
+ mutable sys::Mutex lock;
+ const std::string xid;
+ bool ended;
+ bool suspended;
+ bool failed;
+ bool expired;
+
+ public:
+ QPID_BROKER_EXTERN DtxBuffer(
+ const std::string& xid = "",
+ bool ended=false, bool suspended=false, bool failed=false, bool expired=false);
+ QPID_BROKER_EXTERN ~DtxBuffer();
+ QPID_BROKER_EXTERN void markEnded();
+ bool isEnded() const;
+ void setSuspended(bool suspended);
+ bool isSuspended() const;
+ void fail();
+ bool isRollbackOnly() const;
+ void timedout();
+ bool isExpired() const;
+ bool isFailed() const;
+ std::string getXid() const;
+};
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/DtxManager.cpp b/qpid/cpp/src/qpid/broker/DtxManager.cpp
new file mode 100644
index 0000000000..4fb82bb41b
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/DtxManager.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/DtxManager.h"
+#include "qpid/broker/DtxTimeout.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/framing/StructHelper.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/Timer.h"
+#include "qpid/ptr_map.h"
+
+#include <boost/format.hpp>
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+
+#include <iostream>
+
+using boost::intrusive_ptr;
+using qpid::sys::Mutex;
+using qpid::ptr_map_ptr;
+using namespace qpid::broker;
+using namespace qpid::framing;
+
+namespace {
+ typedef boost::function0<void> FireFunction;
+ struct DtxCleanup : public qpid::sys::TimerTask
+ {
+ FireFunction fireFunction;
+
+ DtxCleanup(uint32_t timeout, FireFunction f);
+ void fire();
+ };
+
+ DtxCleanup::DtxCleanup(uint32_t _timeout, FireFunction f)
+ : TimerTask(qpid::sys::Duration(_timeout * qpid::sys::TIME_SEC),"DtxCleanup"), fireFunction(f){}
+
+ void DtxCleanup::fire()
+ {
+ try {
+ fireFunction();
+ } catch (qpid::ConnectionException& /*e*/) {
+ //assume it was explicitly cleaned up after a call to prepare, commit or rollback
+ }
+ }
+
+}
+
+DtxManager::DtxManager(qpid::sys::Timer& t, uint32_t _dtxDefaultTimeout) :
+ store(0),
+ timer(&t),
+ dtxDefaultTimeout(_dtxDefaultTimeout)
+{
+}
+
+DtxManager::~DtxManager() {}
+
+void DtxManager::start(const std::string& xid, boost::intrusive_ptr<DtxBuffer> ops)
+{
+ createWork(xid)->add(ops);
+}
+
+void DtxManager::join(const std::string& xid, boost::intrusive_ptr<DtxBuffer> ops)
+{
+ getWork(xid)->add(ops);
+}
+
+void DtxManager::recover(const std::string& xid, std::auto_ptr<TPCTransactionContext> txn, boost::intrusive_ptr<DtxBuffer> ops)
+{
+ createWork(xid)->recover(txn, ops);
+}
+
+bool DtxManager::prepare(const std::string& xid)
+{
+ QPID_LOG(debug, "preparing: " << convert(xid));
+ try {
+ return getWork(xid)->prepare();
+ } catch (DtxTimeoutException& e) {
+ remove(xid);
+ throw e;
+ }
+}
+
+bool DtxManager::commit(const std::string& xid, bool onePhase)
+{
+ QPID_LOG(debug, "committing: " << convert(xid));
+ try {
+ bool result = getWork(xid)->commit(onePhase);
+ remove(xid);
+ return result;
+ } catch (DtxTimeoutException& e) {
+ remove(xid);
+ throw e;
+ }
+}
+
+void DtxManager::rollback(const std::string& xid)
+{
+ QPID_LOG(debug, "rolling back: " << convert(xid));
+ try {
+ getWork(xid)->rollback();
+ remove(xid);
+ } catch (DtxTimeoutException& e) {
+ remove(xid);
+ throw e;
+ }
+}
+
+DtxWorkRecord* DtxManager::getWork(const std::string& xid)
+{
+ Mutex::ScopedLock locker(lock);
+ WorkMap::iterator i = work.find(xid);
+ if (i == work.end()) {
+ throw NotFoundException(QPID_MSG("Unrecognised xid " << convert(xid)));
+ }
+ return ptr_map_ptr(i);
+}
+
+bool DtxManager::exists(const std::string& xid) {
+ Mutex::ScopedLock locker(lock);
+ return work.find(xid) != work.end();
+}
+
+void DtxManager::remove(const std::string& xid)
+{
+ Mutex::ScopedLock locker(lock);
+ WorkMap::iterator i = work.find(xid);
+ if (i == work.end()) {
+ throw NotFoundException(QPID_MSG("Unrecognised xid " << convert(xid)));
+ } else {
+ work.erase(i);
+ }
+}
+
+DtxWorkRecord* DtxManager::createWork(const std::string& xid)
+{
+ Mutex::ScopedLock locker(lock);
+ WorkMap::iterator i = work.find(xid);
+ if (i != work.end()) {
+ throw NotAllowedException(QPID_MSG("Xid " << convert(xid) << " is already known (use 'join' to add work to an existing xid)"));
+ } else {
+ std::string ncxid = xid; // Work around const correctness problems with work.insert
+ DtxWorkRecord* dtxWorkRecord = new DtxWorkRecord(xid, store);
+ work.insert(ncxid, dtxWorkRecord);
+ if (dtxDefaultTimeout>0)
+ setTimeout(xid, dtxDefaultTimeout);
+ return dtxWorkRecord;
+ }
+}
+
+void DtxManager::setTimeout(const std::string& xid, uint32_t secs)
+{
+ DtxWorkRecord* record = getWork(xid);
+ intrusive_ptr<DtxTimeout> timeout = record->getTimeout();
+ if (timeout.get()) {
+ if (timeout->timeout == secs) return;//no need to do anything further if timeout hasn't changed
+ timeout->cancel();
+ }
+ timeout = intrusive_ptr<DtxTimeout>(new DtxTimeout(secs, *this, xid));
+ record->setTimeout(timeout);
+ timer->add(timeout);
+}
+
+uint32_t DtxManager::getTimeout(const std::string& xid)
+{
+ intrusive_ptr<DtxTimeout> timeout = getWork(xid)->getTimeout();
+ return !timeout ? 0 : timeout->timeout;
+}
+
+void DtxManager::timedout(const std::string& xid)
+{
+ Mutex::ScopedLock locker(lock);
+ WorkMap::iterator i = work.find(xid);
+ if (i == work.end()) {
+ QPID_LOG(warning, "Transaction timeout failed: no record for xid");
+ } else {
+ ptr_map_ptr(i)->timedout();
+ //TODO: do we want to have a timed task to cleanup, or can we rely on an explicit completion?
+ //timer->add(new DtxCleanup(60*30/*30 mins*/, boost::bind(&DtxManager::remove, this, xid)));
+ }
+}
+
+void DtxManager::setStore (TransactionalStore* _store)
+{
+ store = _store;
+}
+
+std::string DtxManager::convert(const qpid::framing::Xid& xid)
+{
+ qpid::framing::StructHelper helper;
+ std::string encoded;
+ helper.encode(xid, encoded);
+ return encoded;
+}
+
+qpid::framing::Xid DtxManager::convert(const std::string& xid)
+{
+ qpid::framing::StructHelper helper;
+ qpid::framing::Xid decoded;
+ helper.decode(decoded, xid);
+ return decoded;
+}
diff --git a/qpid/cpp/src/qpid/broker/DtxManager.h b/qpid/cpp/src/qpid/broker/DtxManager.h
new file mode 100644
index 0000000000..e64c97c7dd
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/DtxManager.h
@@ -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.
+ *
+ */
+#ifndef _DtxManager_
+#define _DtxManager_
+
+#include "qpid/broker/DtxBuffer.h"
+#include "qpid/broker/DtxWorkRecord.h"
+#include "qpid/broker/TransactionalStore.h"
+#include "qpid/framing/amqp_types.h"
+#include "qpid/framing/Xid.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/ptr_map.h"
+
+namespace qpid {
+namespace sys {
+class Timer;
+}
+
+namespace broker {
+
+class DtxManager{
+ typedef boost::ptr_map<std::string, DtxWorkRecord> WorkMap;
+
+ WorkMap work;
+ TransactionalStore* store;
+ qpid::sys::Mutex lock;
+ qpid::sys::Timer* timer;
+ uint32_t dtxDefaultTimeout;
+
+ void remove(const std::string& xid);
+ DtxWorkRecord* createWork(const std::string& xid);
+
+public:
+ DtxManager(sys::Timer&, uint32_t _dtxDefaultTimeout=0);
+ ~DtxManager();
+ void start(const std::string& xid, boost::intrusive_ptr<DtxBuffer> work);
+ void join(const std::string& xid, boost::intrusive_ptr<DtxBuffer> work);
+ void recover(const std::string& xid, std::auto_ptr<TPCTransactionContext> txn, boost::intrusive_ptr<DtxBuffer> work);
+ bool prepare(const std::string& xid);
+ bool commit(const std::string& xid, bool onePhase);
+ void rollback(const std::string& xid);
+ void setTimeout(const std::string& xid, uint32_t secs);
+ uint32_t getTimeout(const std::string& xid);
+ void timedout(const std::string& xid);
+ void setStore(TransactionalStore* store);
+ void setTimer(sys::Timer& t) { timer = &t; }
+
+ DtxWorkRecord* getWork(const std::string& xid);
+ bool exists(const std::string& xid);
+ static std::string convert(const framing::Xid& xid);
+ static framing::Xid convert(const std::string& xid);
+};
+
+}
+}
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/DtxTimeout.cpp b/qpid/cpp/src/qpid/broker/DtxTimeout.cpp
new file mode 100644
index 0000000000..f317df5713
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/DtxTimeout.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/DtxTimeout.h"
+#include "qpid/broker/DtxManager.h"
+#include "qpid/sys/Time.h"
+#include "qpid/log/Statement.h"
+
+using namespace qpid::broker;
+
+DtxTimeout::DtxTimeout(uint32_t _timeout, DtxManager& _mgr, const std::string& _xid)
+ : TimerTask(qpid::sys::Duration(_timeout * qpid::sys::TIME_SEC),"DtxTimeout-"+_xid), timeout(_timeout), mgr(_mgr), xid(_xid)
+{
+}
+
+void DtxTimeout::fire()
+{
+ QPID_LOG(debug, "DTX transaction timeouted, XID=" << xid << ", timeout=" << timeout);
+ mgr.timedout(xid);
+}
diff --git a/qpid/cpp/src/qpid/broker/DtxTimeout.h b/qpid/cpp/src/qpid/broker/DtxTimeout.h
new file mode 100644
index 0000000000..1fcb4cee2a
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/DtxTimeout.h
@@ -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.
+ *
+ */
+#ifndef _DtxTimeout_
+#define _DtxTimeout_
+
+#include "qpid/Exception.h"
+#include "qpid/sys/Timer.h"
+
+namespace qpid {
+namespace broker {
+
+class DtxManager;
+
+struct DtxTimeoutException : public Exception {
+ DtxTimeoutException(const std::string& msg=std::string()) : Exception(msg) {}
+};
+
+struct DtxTimeout : public sys::TimerTask
+{
+ const uint32_t timeout;
+ DtxManager& mgr;
+ const std::string xid;
+
+ DtxTimeout(uint32_t timeout, DtxManager& mgr, const std::string& xid);
+ void fire();
+};
+
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/DtxWorkRecord.cpp b/qpid/cpp/src/qpid/broker/DtxWorkRecord.cpp
new file mode 100644
index 0000000000..04d19dc43e
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/DtxWorkRecord.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/DtxWorkRecord.h"
+#include "qpid/broker/DtxManager.h"
+#include "qpid/broker/DtxTimeout.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/sys/Timer.h"
+
+#include <boost/format.hpp>
+#include <boost/mem_fn.hpp>
+using boost::mem_fn;
+using qpid::sys::Mutex;
+
+using namespace qpid::broker;
+using namespace qpid::framing;
+
+DtxWorkRecord::DtxWorkRecord(const std::string& _xid, TransactionalStore* const _store) :
+ xid(_xid), store(_store), completed(false), rolledback(false), prepared(false), expired(false) {}
+
+DtxWorkRecord::~DtxWorkRecord()
+{
+ if (timeout.get()) {
+ timeout->cancel();
+ }
+}
+
+void DtxWorkRecord::setTimeout(boost::intrusive_ptr<DtxTimeout> t)
+{ timeout = t; }
+
+boost::intrusive_ptr<DtxTimeout> DtxWorkRecord::getTimeout()
+{ return timeout; }
+
+bool DtxWorkRecord::prepare()
+{
+ Mutex::ScopedLock locker(lock);
+ if (check()) {
+ txn = store->begin(xid);
+ if (prepare(txn.get())) {
+ store->prepare(*txn);
+ prepared = true;
+ } else {
+ abort();
+ //TODO: this should probably be flagged as internal error
+ }
+ } else {
+ //some part of the work has been marked rollback only
+ abort();
+ }
+ return prepared;
+}
+
+bool DtxWorkRecord::prepare(TransactionContext* _txn)
+{
+ bool succeeded(true);
+ for (Work::iterator i = work.begin(); succeeded && i != work.end(); i++) {
+ succeeded = (*i)->prepare(_txn);
+ }
+ return succeeded;
+}
+
+bool DtxWorkRecord::commit(bool onePhase)
+{
+ Mutex::ScopedLock locker(lock);
+ if (check()) {
+ if (prepared) {
+ //already prepared i.e. 2pc
+ if (onePhase) {
+ throw IllegalStateException(QPID_MSG("Branch with xid " << DtxManager::convert(xid) << " has been prepared, one-phase option not valid!"));
+ }
+
+ store->commit(*txn);
+ txn.reset();
+
+ std::for_each(work.begin(), work.end(), mem_fn(&TxBuffer::commit));
+ return true;
+ } else {
+ //1pc commit optimisation, don't need a 2pc transaction context:
+ if (!onePhase) {
+ throw IllegalStateException(QPID_MSG("Branch with xid " << DtxManager::convert(xid) << " has not been prepared, one-phase option required!"));
+ }
+ std::auto_ptr<TransactionContext> localtxn = store->begin();
+ if (prepare(localtxn.get())) {
+ store->commit(*localtxn);
+ std::for_each(work.begin(), work.end(), mem_fn(&TxBuffer::commit));
+ return true;
+ } else {
+ store->abort(*localtxn);
+ abort();
+ //TODO: this should probably be flagged as internal error
+ return false;
+ }
+ }
+ } else {
+ //some part of the work has been marked rollback only
+ abort();
+ return false;
+ }
+}
+
+void DtxWorkRecord::rollback()
+{
+ Mutex::ScopedLock locker(lock);
+ check();
+ abort();
+}
+
+void DtxWorkRecord::add(boost::intrusive_ptr<DtxBuffer> ops)
+{
+ Mutex::ScopedLock locker(lock);
+ if (expired) {
+ throw DtxTimeoutException(QPID_MSG("Branch with xid " << DtxManager::convert(xid) << " has timed out."));
+ }
+ if (completed) {
+ throw CommandInvalidException(QPID_MSG("Branch with xid " << DtxManager::convert(xid) << " has been completed!"));
+ }
+ work.push_back(ops);
+}
+
+bool DtxWorkRecord::check()
+{
+ if (expired) {
+ throw DtxTimeoutException();
+ }
+ if (!completed) {
+ //iterate through all DtxBuffers and ensure they are all ended
+ for (Work::iterator i = work.begin(); i != work.end(); i++) {
+ if (!(*i)->isEnded()) {
+ throw IllegalStateException(QPID_MSG("Branch with xid " << DtxManager::convert(xid) << " not completed!"));
+ } else if ((*i)->isRollbackOnly()) {
+ rolledback = true;
+ }
+ }
+ completed = true;
+ }
+ return !rolledback;
+}
+
+void DtxWorkRecord::abort()
+{
+ if (txn.get()) {
+ store->abort(*txn);
+ txn.reset();
+ }
+ std::for_each(work.begin(), work.end(), mem_fn(&TxBuffer::rollback));
+}
+
+void DtxWorkRecord::recover(std::auto_ptr<TPCTransactionContext> _txn, boost::intrusive_ptr<DtxBuffer> ops)
+{
+ add(ops);
+ txn = _txn;
+ ops->markEnded();
+ completed = true;
+ prepared = true;
+}
+
+void DtxWorkRecord::timedout()
+{
+ Mutex::ScopedLock locker(lock);
+ expired = true;
+ rolledback = true;
+ if (!completed) {
+ for (Work::iterator i = work.begin(); i != work.end(); i++) {
+ if (!(*i)->isEnded()) {
+ (*i)->timedout();
+ }
+ }
+ }
+ abort();
+}
diff --git a/qpid/cpp/src/qpid/broker/DtxWorkRecord.h b/qpid/cpp/src/qpid/broker/DtxWorkRecord.h
new file mode 100644
index 0000000000..91fda797d1
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/DtxWorkRecord.h
@@ -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.
+ *
+ */
+#ifndef _DtxWorkRecord_
+#define _DtxWorkRecord_
+
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/broker/DtxBuffer.h"
+#include "qpid/broker/TransactionalStore.h"
+
+#include "qpid/framing/amqp_types.h"
+#include "qpid/sys/Mutex.h"
+
+#include <algorithm>
+#include <functional>
+#include <vector>
+
+#include <boost/intrusive_ptr.hpp>
+
+namespace qpid {
+namespace broker {
+
+struct DtxTimeout;
+
+/**
+ * Represents the work done under a particular distributed transaction
+ * across potentially multiple channels. Identified by a xid. Allows
+ * that work to be prepared, committed and rolled-back.
+ */
+class DtxWorkRecord
+{
+ typedef std::vector<boost::intrusive_ptr<DtxBuffer> >Work;
+
+ const std::string xid;
+ TransactionalStore* const store;
+ bool completed;
+ bool rolledback;
+ bool prepared;
+ bool expired;
+ boost::intrusive_ptr<DtxTimeout> timeout;
+ Work work;
+ std::auto_ptr<TPCTransactionContext> txn;
+ qpid::sys::Mutex lock;
+
+ bool check();
+ void abort();
+ bool prepare(TransactionContext* txn);
+public:
+ QPID_BROKER_EXTERN DtxWorkRecord(const std::string& xid,
+ TransactionalStore* const store);
+ QPID_BROKER_EXTERN ~DtxWorkRecord();
+ QPID_BROKER_EXTERN bool prepare();
+ QPID_BROKER_EXTERN bool commit(bool onePhase);
+ QPID_BROKER_EXTERN void rollback();
+ QPID_BROKER_EXTERN void add(boost::intrusive_ptr<DtxBuffer> ops);
+ void recover(std::auto_ptr<TPCTransactionContext> txn, boost::intrusive_ptr<DtxBuffer> ops);
+ void timedout();
+ void setTimeout(boost::intrusive_ptr<DtxTimeout> t);
+ boost::intrusive_ptr<DtxTimeout> getTimeout();
+ std::string getXid() const { return xid; }
+ bool isCompleted() const { return completed; }
+ bool isRolledback() const { return rolledback; }
+ bool isPrepared() const { return prepared; }
+ bool isExpired() const { return expired; }
+};
+
+}} // qpid::broker
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/Exchange.cpp b/qpid/cpp/src/qpid/broker/Exchange.cpp
new file mode 100644
index 0000000000..c9ed648735
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Exchange.cpp
@@ -0,0 +1,504 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/DeliverableMessage.h"
+#include "qpid/broker/Exchange.h"
+#include "qpid/broker/ExchangeRegistry.h"
+#include "qpid/broker/FedOps.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/amqp_0_10/MessageTransfer.h"
+#include "qpid/framing/MessageProperties.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/log/Statement.h"
+#include "qpid/management/ManagementAgent.h"
+#include "qpid/sys/ExceptionHolder.h"
+#include <stdexcept>
+
+namespace qpid {
+namespace broker {
+
+using std::string;
+
+using namespace qpid::framing;
+using qpid::framing::Buffer;
+using qpid::framing::FieldTable;
+using qpid::sys::Mutex;
+using qpid::management::ManagementAgent;
+using qpid::management::ManagementObject;
+using qpid::management::Manageable;
+using qpid::management::Args;
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+namespace
+{
+ const std::string qpidMsgSequence("qpid.msg_sequence");
+ const std::string qpidSequenceCounter("qpid.sequence_counter");
+ const std::string qpidIVE("qpid.ive");
+ const std::string QPID_MANAGEMENT("qpid.management");
+}
+
+
+Exchange::PreRoute::PreRoute(Deliverable& msg, Exchange* _p):parent(_p) {
+ if (parent){
+ if (parent->sequence || parent->ive) parent->sequenceLock.lock();
+
+ if (parent->sequence){
+ parent->sequenceNo++;
+ msg.getMessage().addAnnotation(qpidMsgSequence,parent->sequenceNo);
+ }
+ if (parent->ive) {
+ parent->lastMsg = msg.getMessage();
+ }
+ }
+}
+
+Exchange::PreRoute::~PreRoute(){
+ if (parent && (parent->sequence || parent->ive)){
+ parent->sequenceLock.unlock();
+ }
+}
+
+namespace {
+/** Store information about an exception to be thrown later.
+ * If multiple exceptions are stored, save the first of the "most severe"
+ * exceptions, SESSION is les sever than CONNECTION etc.
+ */
+class ExInfo {
+ public:
+ enum Type { NONE, SESSION, CONNECTION, OTHER };
+
+ ExInfo(string exchange) : type(NONE), exchange(exchange) {}
+ void store(Type type_, const qpid::sys::ExceptionHolder& exception_, const boost::shared_ptr<Queue>& queue) {
+ QPID_LOG(warning, "Exchange " << exchange << " cannot deliver to queue "
+ << queue->getName() << ": " << exception_.what());
+ if (type < type_) { // Replace less severe exception
+ type = type_;
+ exception = exception_;
+ }
+ }
+
+ void raise() {
+ exception.raise();
+ }
+
+ private:
+ Type type;
+ string exchange;
+ qpid::sys::ExceptionHolder exception;
+};
+}
+
+void Exchange::doRoute(Deliverable& msg, ConstBindingList b)
+{
+ int count = 0;
+
+ if (b.get()) {
+ ExInfo error(getName()); // Save exception to throw at the end.
+ for(std::vector<Binding::shared_ptr>::const_iterator i = b->begin(); i != b->end(); i++, count++) {
+ try {
+ msg.deliverTo((*i)->queue);
+ if ((*i)->mgmtBinding != 0)
+ (*i)->mgmtBinding->inc_msgMatched();
+ }
+ catch (const SessionException& e) {
+ error.store(ExInfo::SESSION, framing::createSessionException(e.code, e.what()),(*i)->queue);
+ }
+ catch (const ConnectionException& e) {
+ error.store(ExInfo::CONNECTION, framing::createConnectionException(e.code, e.what()), (*i)->queue);
+ }
+ catch (const std::exception& e) {
+ error.store(ExInfo::OTHER, qpid::sys::ExceptionHolder(new Exception(e.what())), (*i)->queue);
+ }
+ }
+ error.raise();
+ }
+
+ if (mgmtExchange != 0)
+ {
+ qmf::org::apache::qpid::broker::Exchange::PerThreadStats *eStats = mgmtExchange->getStatistics();
+ uint64_t contentSize = msg.getMessage().getMessageSize();
+
+ eStats->msgReceives += 1;
+ eStats->byteReceives += contentSize;
+ if (count == 0)
+ {
+ //QPID_LOG(warning, "Exchange " << getName() << " could not route message; no matching binding found");
+ eStats->msgDrops += 1;
+ eStats->byteDrops += contentSize;
+ if (brokerMgmtObject)
+ brokerMgmtObject->inc_discardsNoRoute();
+ }
+ else
+ {
+ eStats->msgRoutes += count;
+ eStats->byteRoutes += count * contentSize;
+ }
+
+ mgmtExchange->statisticsUpdated();
+ }
+}
+
+void Exchange::routeIVE(){
+ if (ive && lastMsg){
+ DeliverableMessage dmsg(lastMsg, 0);
+ route(dmsg);
+ }
+}
+
+
+Exchange::Exchange (const string& _name, Manageable* parent, Broker* b) :
+ name(_name), durable(false), autodelete(false), alternateUsers(0), otherUsers(0), persistenceId(0), sequence(false),
+ sequenceNo(0), ive(false), broker(b), destroyed(false)
+{
+ if (parent != 0 && broker != 0)
+ {
+ ManagementAgent* agent = broker->getManagementAgent();
+ if (agent != 0)
+ {
+ mgmtExchange = _qmf::Exchange::shared_ptr(new _qmf::Exchange (agent, this, parent, _name));
+ mgmtExchange->set_durable(durable);
+ mgmtExchange->set_autoDelete(autodelete);
+ agent->addObject(mgmtExchange, 0, durable);
+ if (broker)
+ brokerMgmtObject = boost::dynamic_pointer_cast<qmf::org::apache::qpid::broker::Broker>(broker->GetManagementObject());
+ }
+ }
+}
+
+Exchange::Exchange(const string& _name, bool _durable, bool _autodelete, const qpid::framing::FieldTable& _args,
+ Manageable* parent, Broker* b)
+ : name(_name), durable(_durable), autodelete(_autodelete), alternateUsers(0), otherUsers(0), persistenceId(0),
+ args(_args), sequence(false), sequenceNo(0), ive(false), broker(b), destroyed(false)
+{
+ if (parent != 0 && broker != 0)
+ {
+ ManagementAgent* agent = broker->getManagementAgent();
+ if (agent != 0)
+ {
+ mgmtExchange = _qmf::Exchange::shared_ptr(new _qmf::Exchange (agent, this, parent, _name));
+ mgmtExchange->set_durable(durable);
+ mgmtExchange->set_autoDelete(autodelete);
+ mgmtExchange->set_arguments(ManagementAgent::toMap(args));
+ agent->addObject(mgmtExchange, 0, durable);
+ if (broker)
+ brokerMgmtObject = boost::dynamic_pointer_cast<qmf::org::apache::qpid::broker::Broker>(broker->GetManagementObject());
+ }
+ }
+
+ sequence = !!_args.get(qpidMsgSequence);
+ if (sequence) {
+ QPID_LOG(debug, "Configured exchange " << _name << " with Msg sequencing");
+ args.setInt64(std::string(qpidSequenceCounter), sequenceNo);
+ }
+
+ ive = !!_args.get(qpidIVE);
+ if (ive) {
+ QPID_LOG(debug, "Configured exchange " << _name << " with Initial Value");
+ }
+}
+
+Exchange::~Exchange ()
+{
+ if (mgmtExchange != 0)
+ mgmtExchange->resourceDestroy ();
+}
+
+void Exchange::setAlternate(Exchange::shared_ptr _alternate)
+{
+ alternate = _alternate;
+ alternate->incAlternateUsers();
+ if (mgmtExchange != 0) {
+ if (alternate.get() != 0)
+ mgmtExchange->set_altExchange(alternate->GetManagementObject()->getObjectId());
+ else
+ mgmtExchange->clr_altExchange();
+ }
+}
+
+void Exchange::setPersistenceId(uint64_t id) const
+{
+ persistenceId = id;
+}
+
+Exchange::shared_ptr Exchange::decode(ExchangeRegistry& exchanges, Buffer& buffer)
+{
+ string name;
+ string type;
+ string altName;
+ FieldTable args;
+
+ buffer.getShortString(name);
+ bool durable(buffer.getOctet());
+ buffer.getShortString(type);
+ buffer.get(args);
+ // For backwards compatibility on restoring exchanges from before the alt-exchange update, perform check
+ if (buffer.available())
+ buffer.getShortString(altName);
+ // Check autodelete bool; for backwards compatibility if the bool isn't present, assume false
+ bool _autodelete = ((buffer.available()) && (buffer.getInt8()));
+
+ try {
+ Exchange::shared_ptr exch = exchanges.declare(name, type, durable, _autodelete, args).first;
+ exch->sequenceNo = args.getAsInt64(qpidSequenceCounter);
+ exch->alternateName.assign(altName);
+ return exch;
+ } catch (const UnknownExchangeTypeException&) {
+ QPID_LOG(warning, "Could not create exchange " << name << "; type " << type << " is not recognised");
+ return Exchange::shared_ptr();
+ }
+}
+
+void Exchange::encode(Buffer& buffer) const
+{
+ buffer.putShortString(name);
+ buffer.putOctet(durable);
+ buffer.putShortString(getType());
+ if (args.isSet(qpidSequenceCounter))
+ args.setInt64(std::string(qpidSequenceCounter),sequenceNo);
+ buffer.put(args);
+ buffer.putShortString(alternate.get() ? alternate->getName() : string(""));
+ buffer.putInt8(isAutoDelete());
+}
+
+uint32_t Exchange::encodedSize() const
+{
+ return name.size() + 1/*short string size*/
+ + 1 /*durable*/
+ + getType().size() + 1/*short string size*/
+ + (alternate.get() ? alternate->getName().size() : 0) + 1/*short string size*/
+ + args.encodedSize()
+ + 1 /* autodelete bool as int_8 */;
+}
+
+void Exchange::recoveryComplete(ExchangeRegistry& exchanges)
+{
+ if (!alternateName.empty()) {
+ Exchange::shared_ptr ae = exchanges.find(alternateName);
+ if (ae) setAlternate(ae);
+ else QPID_LOG(warning, "Could not set alternate exchange \""
+ << alternateName << "\": does not exist.");
+ }
+}
+
+ManagementObject::shared_ptr Exchange::GetManagementObject (void) const
+{
+ return mgmtExchange;
+}
+
+void Exchange::registerDynamicBridge(DynamicBridge* db)
+{
+ if (!supportsDynamicBinding())
+ throw Exception("Exchange type does not support dynamic binding");
+
+ {
+ Mutex::ScopedLock l(bridgeLock);
+ for (std::vector<DynamicBridge*>::iterator iter = bridgeVector.begin();
+ iter != bridgeVector.end(); iter++)
+ (*iter)->sendReorigin();
+
+ bridgeVector.push_back(db);
+ }
+
+ FieldTable args;
+ args.setString(qpidFedOp, fedOpReorigin);
+ bind(Queue::shared_ptr(), string(), &args);
+}
+
+void Exchange::removeDynamicBridge(DynamicBridge* db)
+{
+ Mutex::ScopedLock l(bridgeLock);
+ for (std::vector<DynamicBridge*>::iterator iter = bridgeVector.begin();
+ iter != bridgeVector.end(); iter++)
+ if (*iter == db) {
+ bridgeVector.erase(iter);
+ break;
+ }
+}
+
+void Exchange::handleHelloRequest()
+{
+}
+
+void Exchange::propagateFedOp(const string& routingKey, const string& tags, const string& op, const string& origin, qpid::framing::FieldTable* extra_args)
+{
+ Mutex::ScopedLock l(bridgeLock);
+ string myOp(op.empty() ? fedOpBind : op);
+
+ for (std::vector<DynamicBridge*>::iterator iter = bridgeVector.begin();
+ iter != bridgeVector.end(); iter++)
+ (*iter)->propagateBinding(routingKey, tags, op, origin, extra_args);
+}
+
+Exchange::Binding::Binding(const string& _key, Queue::shared_ptr _queue, Exchange* _parent,
+ FieldTable _args, const string& _origin)
+ : parent(_parent), queue(_queue), key(_key), args(_args), origin(_origin)
+{
+}
+
+Exchange::Binding::~Binding ()
+{
+ if (mgmtBinding != 0) {
+ mgmtBinding->debugStats("destroying");
+ _qmf::Queue::shared_ptr mo = boost::dynamic_pointer_cast<_qmf::Queue>(queue->GetManagementObject());
+ if (mo != 0)
+ mo->dec_bindingCount();
+ mgmtBinding->resourceDestroy ();
+ }
+}
+
+void Exchange::Binding::startManagement()
+{
+ if (parent != 0)
+ {
+ Broker* broker = parent->getBroker();
+ if (broker != 0) {
+ ManagementAgent* agent = broker->getManagementAgent();
+ if (agent != 0) {
+ _qmf::Queue::shared_ptr mo = boost::dynamic_pointer_cast<_qmf::Queue>(queue->GetManagementObject());
+ if (mo != 0) {
+ management::ObjectId queueId = mo->getObjectId();
+
+ mgmtBinding = _qmf::Binding::shared_ptr(new _qmf::Binding
+ (agent, this, (Manageable*) parent, queueId, key, ManagementAgent::toMap(args)));
+ if (!origin.empty())
+ mgmtBinding->set_origin(origin);
+ agent->addObject(mgmtBinding);
+ mo->inc_bindingCount();
+ }
+ }
+ }
+ }
+}
+
+ManagementObject::shared_ptr Exchange::Binding::GetManagementObject () const
+{
+ return mgmtBinding;
+}
+
+Exchange::MatchQueue::MatchQueue(Queue::shared_ptr q) : queue(q) {}
+
+bool Exchange::MatchQueue::operator()(Exchange::Binding::shared_ptr b)
+{
+ return b->queue == queue;
+}
+
+//void Exchange::setProperties(Message& msg) {
+// qpid::broker::amqp_0_10::MessageTransfer::setExchange(msg, getName());
+//}
+
+bool Exchange::routeWithAlternate(Deliverable& msg)
+{
+ route(msg);
+ if (!msg.delivered && alternate) {
+ alternate->route(msg);
+ }
+ return msg.delivered;
+}
+
+void Exchange::setArgs(const framing::FieldTable& newArgs) {
+ args = newArgs;
+ if (mgmtExchange) mgmtExchange->set_arguments(ManagementAgent::toMap(args));
+}
+
+void Exchange::checkAutodelete()
+{
+ if (autodelete && !inUse() && broker) {
+ broker->getExchanges().destroy(name);
+ }
+}
+void Exchange::incAlternateUsers()
+{
+ Mutex::ScopedLock l(usersLock);
+ alternateUsers++;
+}
+
+void Exchange::decAlternateUsers()
+{
+ Mutex::ScopedLock l(usersLock);
+ alternateUsers--;
+}
+
+bool Exchange::inUseAsAlternate()
+{
+ Mutex::ScopedLock l(usersLock);
+ return alternateUsers > 0;
+}
+
+void Exchange::incOtherUsers()
+{
+ Mutex::ScopedLock l(usersLock);
+ otherUsers++;
+}
+void Exchange::decOtherUsers(bool isControllingLink=false)
+{
+ Mutex::ScopedLock l(usersLock);
+ assert(otherUsers);
+ if (otherUsers) otherUsers--;
+ if (autodelete) {
+ if (isControllingLink) {
+ if (broker) broker->getExchanges().destroy(name);
+ } else if (!inUse() && !hasBindings()) {
+ checkAutodelete();
+ }
+ }
+}
+bool Exchange::inUse() const
+{
+ Mutex::ScopedLock l(usersLock);
+ return alternateUsers > 0 || otherUsers > 0;
+}
+void Exchange::setDeletionListener(const std::string& key, boost::function0<void> listener)
+{
+ Mutex::ScopedLock l(usersLock);
+ if (listener) deletionListeners[key] = listener;
+}
+void Exchange::unsetDeletionListener(const std::string& key)
+{
+ Mutex::ScopedLock l(usersLock);
+ deletionListeners.erase(key);
+}
+
+void Exchange::destroy()
+{
+ std::map<std::string, boost::function0<void> > copy;
+ {
+ Mutex::ScopedLock l(usersLock);
+ destroyed = true;
+ deletionListeners.swap(copy);
+ }
+ for (std::map<std::string, boost::function0<void> >::iterator i = copy.begin(); i != copy.end(); ++i) {
+ QPID_LOG(debug, "Exchange::destroy() notifying " << i->first);
+ if (i->second) i->second();
+ }
+}
+bool Exchange::isDestroyed() const
+{
+ Mutex::ScopedLock l(usersLock);
+ return destroyed;
+}
+bool Exchange::isAutoDelete() const
+{
+ return autodelete;
+}
+
+}}
+
diff --git a/qpid/cpp/src/qpid/broker/Exchange.h b/qpid/cpp/src/qpid/broker/Exchange.h
new file mode 100644
index 0000000000..3308d54e1b
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Exchange.h
@@ -0,0 +1,268 @@
+#ifndef _broker_Exchange_h
+#define _broker_Exchange_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <boost/shared_ptr.hpp>
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/broker/Deliverable.h"
+#include "qpid/broker/Message.h"
+#include "qpid/broker/MessageStore.h"
+#include "qpid/broker/PersistableExchange.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/management/Manageable.h"
+#include "qmf/org/apache/qpid/broker/Exchange.h"
+#include "qmf/org/apache/qpid/broker/Binding.h"
+#include "qmf/org/apache/qpid/broker/Broker.h"
+#include <map>
+#include <boost/function.hpp>
+
+namespace qpid {
+namespace broker {
+
+class Broker;
+class ExchangeRegistry;
+
+class QPID_BROKER_CLASS_EXTERN Exchange : public PersistableExchange, public management::Manageable {
+public:
+ struct Binding : public management::Manageable {
+ typedef boost::shared_ptr<Binding> shared_ptr;
+ typedef std::vector<Binding::shared_ptr> vector;
+
+ Exchange* parent;
+ boost::shared_ptr<Queue> queue;
+ const std::string key;
+ const framing::FieldTable args;
+ std::string origin;
+ qmf::org::apache::qpid::broker::Binding::shared_ptr mgmtBinding;
+
+ Binding(const std::string& key, boost::shared_ptr<Queue> queue, Exchange* parent = 0,
+ framing::FieldTable args = framing::FieldTable(), const std::string& origin = std::string());
+ ~Binding();
+ void startManagement();
+ management::ManagementObject::shared_ptr GetManagementObject() const;
+ };
+
+private:
+ const std::string name;
+ const bool durable;
+ const bool autodelete;
+ std::string alternateName;
+ boost::shared_ptr<Exchange> alternate;
+ mutable qpid::sys::Mutex usersLock;
+ uint32_t alternateUsers;
+ uint32_t otherUsers;
+ std::map<std::string, boost::function0<void> > deletionListeners;
+ mutable uint64_t persistenceId;
+
+protected:
+ mutable qpid::framing::FieldTable args;
+ bool sequence;
+ mutable qpid::sys::Mutex sequenceLock;
+ int64_t sequenceNo;
+ bool ive;
+ Message lastMsg;
+
+ class PreRoute{
+ public:
+ PreRoute(Deliverable& msg, Exchange* _p);
+ ~PreRoute();
+ private:
+ Exchange* parent;
+ };
+
+ typedef boost::shared_ptr<const std::vector<boost::shared_ptr<qpid::broker::Exchange::Binding> > > ConstBindingList;
+ typedef boost::shared_ptr< std::vector<boost::shared_ptr<qpid::broker::Exchange::Binding> > > BindingList;
+ void doRoute(Deliverable& msg, ConstBindingList b);
+ void routeIVE();
+ void checkAutodelete();
+ virtual bool hasBindings() = 0;
+
+ struct MatchQueue {
+ const boost::shared_ptr<Queue> queue;
+ MatchQueue(boost::shared_ptr<Queue> q);
+ bool operator()(Exchange::Binding::shared_ptr b);
+ };
+
+ /** A FedBinding keeps track of information that Federation needs
+ to know when to propagate changes.
+
+ Dynamic federation needs to know which exchanges have at least
+ one local binding. The bindings on these exchanges need to be
+ propagated.
+
+ Federated binds and unbinds need to know which federation
+ origins are associated with the bindings for each queue. When
+ origins are added or deleted, the corresponding bindings need
+ to be propagated.
+
+ fedBindings[queueName] contains the origins associated with
+ the given queue.
+ */
+
+ class FedBinding {
+ uint32_t localBindings;
+
+ typedef std::set<std::string> originSet;
+ std::map<std::string, originSet> fedBindings;
+
+ public:
+ FedBinding() : localBindings(0) {}
+ bool hasLocal() const { return localBindings != 0; }
+
+ /** Returns true if propagation is needed. */
+ bool addOrigin(const std::string& queueName, const std::string& origin) {
+ if (origin.empty()) {
+ localBindings++;
+ return localBindings == 1;
+ }
+ fedBindings[queueName].insert(origin);
+ return true;
+ }
+
+ /** Returns true if propagation is needed. */
+ bool delOrigin(const std::string& queueName, const std::string& origin){
+ if (origin.empty()) { // no remote == local binding
+ if (localBindings > 0)
+ localBindings--;
+ return localBindings == 0;
+ }
+ size_t match = fedBindings[queueName].erase(origin);
+ if (fedBindings[queueName].empty())
+ fedBindings.erase(queueName);
+ return match != 0;
+ }
+
+ uint32_t count() {
+ return localBindings + fedBindings.size();
+ }
+
+ uint32_t countFedBindings(const std::string& queueName) {
+ // don't use '[]' - it may increase size of fedBindings!
+ std::map<std::string, originSet>::iterator i;
+ if ((i = fedBindings.find(queueName)) != fedBindings.end())
+ return i->second.size();
+ return 0;
+ }
+ };
+
+ qmf::org::apache::qpid::broker::Exchange::shared_ptr mgmtExchange;
+ qmf::org::apache::qpid::broker::Broker::shared_ptr brokerMgmtObject;
+
+public:
+ typedef boost::shared_ptr<Exchange> shared_ptr;
+
+ QPID_BROKER_EXTERN explicit Exchange(const std::string& name, management::Manageable* parent = 0,
+ Broker* broker = 0);
+ QPID_BROKER_EXTERN Exchange(const std::string& _name, bool _durable, bool autodelete, const qpid::framing::FieldTable& _args,
+ management::Manageable* parent = 0, Broker* broker = 0);
+ QPID_BROKER_INLINE_EXTERN virtual ~Exchange();
+
+ const std::string& getName() const { return name; }
+ bool isDurable() { return durable; }
+ QPID_BROKER_EXTERN bool isAutoDelete() const;
+ QPID_BROKER_EXTERN const qpid::framing::FieldTable& getArgs() const { return args; }
+ QPID_BROKER_EXTERN void setArgs(const framing::FieldTable&);
+
+ QPID_BROKER_EXTERN Exchange::shared_ptr getAlternate() { return alternate; }
+ QPID_BROKER_EXTERN void setAlternate(Exchange::shared_ptr _alternate);
+
+ QPID_BROKER_EXTERN void incAlternateUsers();
+ QPID_BROKER_EXTERN void decAlternateUsers();
+ QPID_BROKER_EXTERN bool inUseAsAlternate();
+
+ QPID_BROKER_EXTERN void incOtherUsers();
+ QPID_BROKER_EXTERN void decOtherUsers(bool isControllingLink);
+ QPID_BROKER_EXTERN bool inUse() const;
+
+ virtual std::string getType() const = 0;
+
+ /**
+ * bind() is used for two distinct purposes:
+ *
+ * 1. To create a binding, in the conventional sense
+ *
+ * 2. As a vehicle for any FedOp, currently including federated
+ * binding, federated unbinding, federated reorigin.
+ *
+ */
+
+ virtual bool bind(boost::shared_ptr<Queue> queue, const std::string& routingKey, const qpid::framing::FieldTable* args) = 0;
+ virtual bool unbind(boost::shared_ptr<Queue> queue, const std::string& routingKey, const qpid::framing::FieldTable* args) = 0;
+ virtual bool isBound(boost::shared_ptr<Queue> queue, const std::string* const routingKey, const qpid::framing::FieldTable* const args) = 0;
+ //QPID_BROKER_EXTERN virtual void setProperties(Message&);
+ virtual void route(Deliverable& msg) = 0;
+
+ //PersistableExchange:
+ QPID_BROKER_EXTERN void setPersistenceId(uint64_t id) const;
+ uint64_t getPersistenceId() const { return persistenceId; }
+ QPID_BROKER_EXTERN uint32_t encodedSize() const;
+ QPID_BROKER_EXTERN virtual void encode(framing::Buffer& buffer) const;
+
+ static QPID_BROKER_EXTERN Exchange::shared_ptr decode(ExchangeRegistry& exchanges, framing::Buffer& buffer);
+
+ // Manageable entry points
+ QPID_BROKER_EXTERN management::ManagementObject::shared_ptr GetManagementObject(void) const;
+
+ // Federation hooks
+ class DynamicBridge {
+ public:
+ virtual ~DynamicBridge() {}
+ virtual void propagateBinding(const std::string& key, const std::string& tagList, const std::string& op, const std::string& origin, qpid::framing::FieldTable* extra_args=0) = 0;
+ virtual void sendReorigin() = 0;
+ virtual bool containsLocalTag(const std::string& tagList) const = 0;
+ virtual const std::string& getLocalTag() const = 0;
+ };
+
+ void registerDynamicBridge(DynamicBridge* db);
+ void removeDynamicBridge(DynamicBridge* db);
+ virtual bool supportsDynamicBinding() { return false; }
+ Broker* getBroker() const { return broker; }
+ /**
+ * Notify exchange that recovery has completed.
+ */
+ void recoveryComplete(ExchangeRegistry& exchanges);
+
+ bool routeWithAlternate(Deliverable& message);
+
+ QPID_BROKER_EXTERN void destroy();
+ QPID_BROKER_EXTERN bool isDestroyed() const;
+
+ QPID_BROKER_EXTERN void setDeletionListener(const std::string& key, boost::function0<void> listener);
+ QPID_BROKER_EXTERN void unsetDeletionListener(const std::string& key);
+
+protected:
+ qpid::sys::Mutex bridgeLock;
+ std::vector<DynamicBridge*> bridgeVector;
+ Broker* broker;
+ bool destroyed;
+
+ QPID_BROKER_EXTERN virtual void handleHelloRequest();
+ void propagateFedOp(const std::string& routingKey, const std::string& tags,
+ const std::string& op, const std::string& origin,
+ qpid::framing::FieldTable* extra_args=0);
+};
+
+}}
+
+#endif /*!_broker_Exchange.cpp_h*/
diff --git a/qpid/cpp/src/qpid/broker/ExchangeRegistry.cpp b/qpid/cpp/src/qpid/broker/ExchangeRegistry.cpp
new file mode 100644
index 0000000000..73f1c80f48
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/ExchangeRegistry.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/ExchangeRegistry.h"
+#include "qpid/broker/DirectExchange.h"
+#include "qpid/broker/FanOutExchange.h"
+#include "qpid/broker/HeadersExchange.h"
+#include "qpid/broker/TopicExchange.h"
+#include "qpid/broker/Link.h"
+#include "qpid/management/ManagementDirectExchange.h"
+#include "qpid/management/ManagementTopicExchange.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qmf/org/apache/qpid/broker/EventExchangeDeclare.h"
+#include "qmf/org/apache/qpid/broker/EventExchangeDelete.h"
+
+using namespace qpid::broker;
+using namespace qpid::sys;
+using std::pair;
+using std::string;
+using qpid::framing::FieldTable;
+using qpid::management::ManagementAgent;
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+pair<Exchange::shared_ptr, bool> ExchangeRegistry::declare(const string& name, const string& type){
+
+ return declare(name, type, false, false, FieldTable());
+}
+
+pair<Exchange::shared_ptr, bool> ExchangeRegistry::declare(
+ const string& name, const string& type, bool durable, bool autodelete, const FieldTable& args,
+ Exchange::shared_ptr alternate, const string& connectionId, const string& userId)
+{
+ Exchange::shared_ptr exchange;
+ std::pair<Exchange::shared_ptr, bool> result;
+ {
+ RWlock::ScopedWlock locker(lock);
+ ExchangeMap::iterator i = exchanges.find(name);
+ if (i == exchanges.end()) {
+ if (type == TopicExchange::typeName){
+ exchange = Exchange::shared_ptr(new TopicExchange(name, durable, autodelete, args, parent, broker));
+ }else if(type == DirectExchange::typeName){
+ exchange = Exchange::shared_ptr(new DirectExchange(name, durable, autodelete, args, parent, broker));
+ }else if(type == FanOutExchange::typeName){
+ exchange = Exchange::shared_ptr(new FanOutExchange(name, durable, autodelete, args, parent, broker));
+ }else if (type == HeadersExchange::typeName) {
+ exchange = Exchange::shared_ptr(new HeadersExchange(name, durable, autodelete, args, parent, broker));
+ }else if (type == ManagementDirectExchange::typeName) {
+ exchange = Exchange::shared_ptr(new ManagementDirectExchange(name, durable, args, parent, broker));
+ }else if (type == ManagementTopicExchange::typeName) {
+ exchange = Exchange::shared_ptr(new ManagementTopicExchange(name, durable, args, parent, broker));
+ }else if (type == Link::exchangeTypeName) {
+ exchange = Link::linkExchangeFactory(name);
+ }else{
+ FunctionMap::iterator i = factory.find(type);
+ if (i == factory.end()) {
+ throw UnknownExchangeTypeException(type);
+ } else {
+ exchange = i->second(name, durable, autodelete, args, parent, broker);
+ }
+ }
+ exchanges[name] = exchange;
+ result = std::pair<Exchange::shared_ptr, bool>(exchange, true);
+ if (alternate) exchange->setAlternate(alternate);
+ // Call exchangeCreate inside the lock to ensure correct ordering.
+ if (broker) broker->getBrokerObservers().exchangeCreate(exchange);
+ } else {
+ result = std::pair<Exchange::shared_ptr, bool>(i->second, false);
+ }
+ if (broker && broker->getManagementAgent()) {
+ // Call raiseEvent inside the lock to ensure correct ordering.
+ broker->getManagementAgent()->raiseEvent(
+ _qmf::EventExchangeDeclare(
+ connectionId,
+ userId,
+ name,
+ type,
+ alternate ? alternate->getName() : string(),
+ durable,
+ false,
+ ManagementAgent::toMap(result.first->getArgs()),
+ result.second ? "created" : "existing"));
+ }
+ }
+ return result;
+}
+
+void ExchangeRegistry::destroy(
+ const string& name, const string& connectionId, const string& userId)
+{
+ if (name.empty() ||
+ (name.find("amq.") == 0 &&
+ (name == "amq.direct" || name == "amq.fanout" || name == "amq.topic" || name == "amq.match")) ||
+ name == "qpid.management")
+ throw framing::NotAllowedException(QPID_MSG("Cannot delete default exchange: '" << name << "'"));
+ {
+ RWlock::ScopedWlock locker(lock);
+ ExchangeMap::iterator i = exchanges.find(name);
+ if (i != exchanges.end()) {
+ if (broker) {
+ // Call exchangeDestroy and raiseEvent inside the lock to ensure
+ // correct ordering.
+ broker->getBrokerObservers().exchangeDestroy(i->second);
+ if (broker->getManagementAgent())
+ broker->getManagementAgent()->raiseEvent(
+ _qmf::EventExchangeDelete(connectionId, userId, name));
+ }
+ i->second->destroy();
+ exchanges.erase(i);
+
+ }
+ }
+}
+
+Exchange::shared_ptr ExchangeRegistry::find(const string& name){
+ RWlock::ScopedRlock locker(lock);
+ ExchangeMap::iterator i = exchanges.find(name);
+ if (i == exchanges.end())
+ return Exchange::shared_ptr();
+ else
+ return i->second;
+}
+
+Exchange::shared_ptr ExchangeRegistry::get(const string& name) {
+ Exchange::shared_ptr ex = find(name);
+ if (!ex) throw framing::NotFoundException(QPID_MSG("Exchange not found: "<<name));
+ return ex;
+}
+
+bool ExchangeRegistry::registerExchange(const Exchange::shared_ptr& ex) {
+ RWlock::ScopedWlock locker(lock);
+ return exchanges.insert(ExchangeMap::value_type(ex->getName(), ex)).second;
+}
+
+void ExchangeRegistry::registerType(const std::string& type, FactoryFunction f)
+{
+ factory[type] = f;
+}
+
+void ExchangeRegistry::checkType(const std::string& type)
+{
+ if (type != TopicExchange::typeName && type != DirectExchange::typeName && type != FanOutExchange::typeName
+ && type != HeadersExchange::typeName && type != ManagementDirectExchange::typeName
+ && type != ManagementTopicExchange::typeName && type != Link::exchangeTypeName
+ && factory.find(type) == factory.end()) {
+ throw UnknownExchangeTypeException(type);
+ }
+}
+
+
+namespace
+{
+const std::string EMPTY;
+}
+
+Exchange::shared_ptr ExchangeRegistry::getDefault()
+{
+ return get(EMPTY);
+}
diff --git a/qpid/cpp/src/qpid/broker/ExchangeRegistry.h b/qpid/cpp/src/qpid/broker/ExchangeRegistry.h
new file mode 100644
index 0000000000..fc741dce27
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/ExchangeRegistry.h
@@ -0,0 +1,127 @@
+#ifndef _broker_ExchangeRegistry_h
+#define _broker_ExchangeRegistry_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/broker/Exchange.h"
+#include "qpid/broker/MessageStore.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/sys/Monitor.h"
+#include "qpid/management/Manageable.h"
+
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+
+#include <algorithm>
+#include <map>
+
+namespace qpid {
+namespace broker {
+namespace {
+const std::string UNKNOWN_EXCHANGE_TYPE("Unknown exchange type: ");
+}
+
+struct UnknownExchangeTypeException : std::exception
+{
+ const std::string message;
+ UnknownExchangeTypeException(const std::string& type) throw() : message(UNKNOWN_EXCHANGE_TYPE + type) {}
+ ~UnknownExchangeTypeException() throw() {}
+ const char* what() const throw()
+ {
+ return message.c_str();
+ }
+};
+
+class ExchangeRegistry{
+ public:
+ typedef boost::function6<Exchange::shared_ptr, const std::string&,
+ bool, bool, const qpid::framing::FieldTable&, qpid::management::Manageable*, qpid::broker::Broker*> FactoryFunction;
+
+ ExchangeRegistry (Broker* b = 0) : parent(0), broker(b) {}
+ QPID_BROKER_EXTERN std::pair<Exchange::shared_ptr, bool> declare(
+ const std::string& name, const std::string& type);
+
+ QPID_BROKER_EXTERN std::pair<Exchange::shared_ptr, bool> declare(
+ const std::string& name,
+ const std::string& type,
+ bool durable,
+ bool autodelete,
+ const qpid::framing::FieldTable& args = framing::FieldTable(),
+ Exchange::shared_ptr alternate = Exchange::shared_ptr(),
+ const std::string& connectionId = std::string(),
+ const std::string& userId = std::string());
+
+ QPID_BROKER_EXTERN void destroy(
+ const std::string& name,
+ const std::string& connectionId = std::string(),
+ const std::string& userId = std::string());
+
+ QPID_BROKER_EXTERN Exchange::shared_ptr getDefault();
+
+ /**
+ * Find the named exchange. Return 0 if not found.
+ */
+ QPID_BROKER_EXTERN boost::shared_ptr<Exchange> find(const std::string& name);
+
+ /**
+ * Get the named exchange. Throw exception if not found.
+ */
+ QPID_BROKER_EXTERN boost::shared_ptr<Exchange> get(const std::string& name);
+
+
+ /**
+ * Register the manageable parent for declared exchanges
+ */
+ void setParent (management::Manageable* _parent) { parent = _parent; }
+
+ /** Register an exchange instance.
+ *@return true if registered, false if exchange with same name is already registered.
+ */
+ QPID_BROKER_EXTERN bool registerExchange(const Exchange::shared_ptr&);
+
+ QPID_BROKER_EXTERN void registerType(const std::string& type, FactoryFunction);
+
+ QPID_BROKER_EXTERN void checkType(const std::string& type);
+
+ /** Call f for each exchange in the registry. */
+ template <class F> void eachExchange(F f) const {
+ qpid::sys::RWlock::ScopedRlock l(lock);
+ for (ExchangeMap::const_iterator i = exchanges.begin(); i != exchanges.end(); ++i)
+ f(i->second);
+ }
+
+ private:
+ typedef std::map<std::string, Exchange::shared_ptr> ExchangeMap;
+ typedef std::map<std::string, FactoryFunction > FunctionMap;
+
+ ExchangeMap exchanges;
+ FunctionMap factory;
+ mutable qpid::sys::RWlock lock;
+ management::Manageable* parent;
+ Broker* broker;
+};
+
+}} // namespace qpid::broker
+
+
+#endif /*!_broker_ExchangeRegistry_h*/
diff --git a/qpid/cpp/src/qpid/broker/Fairshare.cpp b/qpid/cpp/src/qpid/broker/Fairshare.cpp
new file mode 100644
index 0000000000..ec8ae9a037
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Fairshare.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/Fairshare.h"
+#include "qpid/broker/Message.h"
+#include "qpid/broker/QueueSettings.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/log/Statement.h"
+#include <boost/format.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/assign/list_of.hpp>
+
+namespace qpid {
+namespace broker {
+
+Fairshare::Fairshare(size_t levels, uint limit) :
+ PriorityQueue(levels),
+ limits(levels, limit), priority(levels-1), count(0) {}
+
+
+void Fairshare::setLimit(size_t level, uint limit)
+{
+ limits[level] = limit;
+}
+
+bool Fairshare::limitReached()
+{
+ uint l = limits[priority];
+ return l && ++count > l;
+}
+
+uint Fairshare::currentLevel()
+{
+ if (limitReached()) {
+ return nextLevel();
+ } else {
+ return priority;
+ }
+}
+
+uint Fairshare::nextLevel()
+{
+ count = 1;
+ if (priority) --priority;
+ else priority = levels-1;
+ return priority;
+}
+
+bool Fairshare::isNull()
+{
+ for (int i = 0; i < levels; i++) if (limits[i]) return false;
+ return true;
+}
+
+bool Fairshare::getState(uint& p, uint& c) const
+{
+ p = priority;
+ c = count;
+ return true;
+}
+
+bool Fairshare::setState(uint p, uint c)
+{
+ priority = p;
+ count = c;
+ return true;
+}
+
+bool Fairshare::getState(const Messages& m, uint& priority, uint& count)
+{
+ const Fairshare* fairshare = dynamic_cast<const Fairshare*>(&m);
+ return fairshare && fairshare->getState(priority, count);
+}
+
+bool Fairshare::setState(Messages& m, uint priority, uint count)
+{
+ Fairshare* fairshare = dynamic_cast<Fairshare*>(&m);
+ return fairshare && fairshare->setState(priority, count);
+}
+
+PriorityQueue::Priority Fairshare::firstLevel()
+{
+ return Priority(currentLevel());
+}
+
+bool Fairshare::nextLevel(Priority& p)
+{
+ int next = nextLevel();
+ if (next == p.start) {
+ return false;
+ } else {
+ p.current = next;
+ return true;
+ }
+}
+
+std::auto_ptr<Messages> Fairshare::create(const QueueSettings& settings)
+{
+ std::auto_ptr<Fairshare> fairshare(new Fairshare(settings.priorities, settings.defaultFairshare));
+ for (uint i = 0; i < settings.priorities; i++) {
+ std::map<uint32_t,uint32_t>::const_iterator l = settings.fairshare.find(i);
+ if (l != settings.fairshare.end()) fairshare->setLimit(i, l->second);
+ }
+ return std::auto_ptr<Messages>(fairshare.release());
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/Fairshare.h b/qpid/cpp/src/qpid/broker/Fairshare.h
new file mode 100644
index 0000000000..e7c8b04ecf
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Fairshare.h
@@ -0,0 +1,60 @@
+#ifndef QPID_BROKER_FAIRSHARE_H
+#define QPID_BROKER_FAIRSHARE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/PriorityQueue.h"
+
+namespace qpid {
+namespace broker {
+struct QueueSettings;
+
+/**
+ * Modifies a basic priority queue by limiting the number of messages
+ * from each priority level that are dispatched before allowing
+ * dispatch from the next level.
+ */
+class Fairshare : public PriorityQueue
+{
+ public:
+ Fairshare(size_t levels, uint limit);
+ bool getState(uint& priority, uint& count) const;
+ bool setState(uint priority, uint count);
+ void setLimit(size_t level, uint limit);
+ bool isNull();
+ static std::auto_ptr<Messages> create(const QueueSettings& settings);
+ static bool getState(const Messages&, uint& priority, uint& count);
+ static bool setState(Messages&, uint priority, uint count);
+ private:
+ std::vector<uint> limits;
+
+ uint priority;
+ uint count;
+
+ uint currentLevel();
+ uint nextLevel();
+ bool limitReached();
+ Priority firstLevel();
+ bool nextLevel(Priority& );
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_FAIRSHARE_H*/
diff --git a/qpid/cpp/src/qpid/broker/FanOutExchange.cpp b/qpid/cpp/src/qpid/broker/FanOutExchange.cpp
new file mode 100644
index 0000000000..8c1d3f4954
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/FanOutExchange.cpp
@@ -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.
+ *
+ */
+#include "qpid/log/Statement.h"
+#include "qpid/broker/FanOutExchange.h"
+#include "qpid/broker/FedOps.h"
+#include <algorithm>
+
+using namespace qpid::broker;
+
+using std::string;
+
+using namespace qpid::framing;
+using namespace qpid::sys;
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+FanOutExchange::FanOutExchange(const std::string& _name, Manageable* _parent, Broker* b) :
+ Exchange(_name, _parent, b)
+{
+ if (mgmtExchange != 0)
+ mgmtExchange->set_type (typeName);
+}
+
+FanOutExchange::FanOutExchange(const std::string& _name, bool _durable, bool autodelete,
+ const FieldTable& _args, Manageable* _parent, Broker* b) :
+ Exchange(_name, _durable, autodelete, _args, _parent, b)
+{
+ if (mgmtExchange != 0)
+ mgmtExchange->set_type (typeName);
+}
+
+bool FanOutExchange::bind(Queue::shared_ptr queue, const string& /*key*/, const FieldTable* args)
+{
+ string fedOp(args ? args->getAsString(qpidFedOp) : fedOpBind);
+ string fedTags(args ? args->getAsString(qpidFedTags) : "");
+ string fedOrigin(args ? args->getAsString(qpidFedOrigin) : "");
+ bool propagate = false;
+
+ if (args == 0 || fedOp.empty() || fedOp == fedOpBind) {
+ Binding::shared_ptr binding (new Binding ("", queue, this, args ? *args : FieldTable(), fedOrigin));
+ if (bindings.add_unless(binding, MatchQueue(queue))) {
+ binding->startManagement();
+ propagate = fedBinding.addOrigin(queue->getName(), fedOrigin);
+ if (mgmtExchange != 0) {
+ mgmtExchange->inc_bindingCount();
+ }
+ } else {
+ // queue already present - still need to track fedOrigin
+ fedBinding.addOrigin(queue->getName(), fedOrigin);
+ return false;
+ }
+ } else if (fedOp == fedOpUnbind) {
+ propagate = fedBinding.delOrigin(queue->getName(), fedOrigin);
+ if (fedBinding.countFedBindings(queue->getName()) == 0)
+ unbind(queue, "", args);
+ } else if (fedOp == fedOpReorigin) {
+ if (fedBinding.hasLocal()) {
+ propagateFedOp(string(), string(), fedOpBind, string());
+ }
+ }
+
+ routeIVE();
+ if (propagate)
+ propagateFedOp(string(), fedTags, fedOp, fedOrigin);
+ return true;
+}
+
+bool FanOutExchange::unbind(Queue::shared_ptr queue, const string& /*key*/, const FieldTable* args)
+{
+ string fedOrigin(args ? args->getAsString(qpidFedOrigin) : "");
+ bool propagate = false;
+
+ QPID_LOG(debug, "Unbinding queue " << queue->getName()
+ << " from exchange " << getName() << " origin=" << fedOrigin << ")" );
+
+ if (bindings.remove_if(MatchQueue(queue))) {
+ propagate = fedBinding.delOrigin(queue->getName(), fedOrigin);
+ if (mgmtExchange != 0) {
+ mgmtExchange->dec_bindingCount();
+ }
+ } else {
+ return false;
+ }
+
+ if (propagate)
+ propagateFedOp(string(), string(), fedOpUnbind, string());
+ if (bindings.empty()) checkAutodelete();
+ return true;
+}
+
+void FanOutExchange::route(Deliverable& msg)
+{
+ PreRoute pr(msg, this);
+ doRoute(msg, bindings.snapshot());
+}
+
+bool FanOutExchange::isBound(Queue::shared_ptr queue, const string* const, const FieldTable* const)
+{
+ BindingsArray::ConstPtr ptr = bindings.snapshot();
+ return ptr && std::find_if(ptr->begin(), ptr->end(), MatchQueue(queue)) != ptr->end();
+}
+
+
+FanOutExchange::~FanOutExchange() {
+ if (mgmtExchange != 0)
+ mgmtExchange->debugStats("destroying");
+}
+
+const std::string FanOutExchange::typeName("fanout");
+
+bool FanOutExchange::hasBindings()
+{
+ BindingsArray::ConstPtr ptr = bindings.snapshot();
+ return ptr && !ptr->empty();
+}
diff --git a/qpid/cpp/src/qpid/broker/FanOutExchange.h b/qpid/cpp/src/qpid/broker/FanOutExchange.h
new file mode 100644
index 0000000000..a92ff7ce97
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/FanOutExchange.h
@@ -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.
+ *
+ */
+#ifndef _FanOutExchange_
+#define _FanOutExchange_
+
+#include <map>
+#include <vector>
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/broker/Exchange.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/sys/CopyOnWriteArray.h"
+#include "qpid/broker/Queue.h"
+
+namespace qpid {
+namespace broker {
+
+class FanOutExchange : public virtual Exchange {
+ typedef qpid::sys::CopyOnWriteArray<Binding::shared_ptr> BindingsArray;
+ BindingsArray bindings;
+ FedBinding fedBinding;
+ public:
+ static const std::string typeName;
+
+ QPID_BROKER_EXTERN FanOutExchange(const std::string& name,
+ management::Manageable* parent = 0, Broker* broker = 0);
+ QPID_BROKER_EXTERN FanOutExchange(const std::string& _name,
+ bool _durable, bool autodelete,
+ const qpid::framing::FieldTable& _args,
+ management::Manageable* parent = 0, Broker* broker = 0);
+
+ virtual std::string getType() const { return typeName; }
+
+ QPID_BROKER_EXTERN virtual bool bind(Queue::shared_ptr queue,
+ const std::string& routingKey,
+ const qpid::framing::FieldTable* args);
+
+ virtual bool unbind(Queue::shared_ptr queue, const std::string& routingKey, const qpid::framing::FieldTable* args);
+
+ QPID_BROKER_EXTERN virtual void route(Deliverable& msg);
+
+ QPID_BROKER_EXTERN virtual bool isBound(Queue::shared_ptr queue,
+ const std::string* const routingKey,
+ const qpid::framing::FieldTable* const args);
+
+ QPID_BROKER_EXTERN virtual ~FanOutExchange();
+ virtual bool supportsDynamicBinding() { return true; }
+ protected:
+ bool hasBindings();
+};
+
+}
+}
+
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/FedOps.h b/qpid/cpp/src/qpid/broker/FedOps.h
new file mode 100644
index 0000000000..dc4a38e244
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/FedOps.h
@@ -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.
+ *
+ */
+
+/*
+ * Strings used to identify federated operations and operands.
+ */
+
+namespace
+{
+ const std::string qpidFedOp("qpid.fed.op"); // a federation primitive
+ const std::string qpidFedTags("qpid.fed.tags"); // a unique id for a broker
+ const std::string qpidFedOrigin("qpid.fed.origin"); // the tag of the broker on which a propagated binding originated
+
+ // Operands for qpidFedOp - each identifies a federation primitive
+
+ const std::string fedOpBind("B");
+ const std::string fedOpUnbind("U");
+ const std::string fedOpReorigin("R");
+ const std::string fedOpHello("H");
+}
diff --git a/qpid/cpp/src/qpid/broker/FifoDistributor.cpp b/qpid/cpp/src/qpid/broker/FifoDistributor.cpp
new file mode 100644
index 0000000000..e1c0d268ce
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/FifoDistributor.cpp
@@ -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.
+ *
+ */
+
+
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/FifoDistributor.h"
+
+using namespace qpid::broker;
+
+FifoDistributor::FifoDistributor(Messages& container)
+ : messages(container) {}
+
+bool FifoDistributor::acquire(const std::string&, Message& msg)
+{
+ if (msg.getState() == AVAILABLE) {
+ msg.setState(ACQUIRED);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void FifoDistributor::query(qpid::types::Variant::Map&) const
+{
+ // nothing to see here....
+}
+
diff --git a/qpid/cpp/src/qpid/broker/FifoDistributor.h b/qpid/cpp/src/qpid/broker/FifoDistributor.h
new file mode 100644
index 0000000000..aa5ebe28c5
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/FifoDistributor.h
@@ -0,0 +1,50 @@
+#ifndef _broker_FifoDistributor_h
+#define _broker_FifoDistributor_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/** Simple MessageDistributor for FIFO Queues - the HEAD message is always the next
+ * available message for consumption.
+ */
+
+#include "qpid/broker/MessageDistributor.h"
+
+namespace qpid {
+namespace broker {
+
+class Messages;
+
+class FifoDistributor : public MessageDistributor
+{
+ public:
+ FifoDistributor(Messages& container);
+
+ bool acquire(const std::string& consumer, Message& target);
+ void query(qpid::types::Variant::Map&) const;
+
+ private:
+ Messages& messages;
+};
+
+}}
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/HandlerImpl.h b/qpid/cpp/src/qpid/broker/HandlerImpl.h
new file mode 100644
index 0000000000..c41438fd90
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/HandlerImpl.h
@@ -0,0 +1,53 @@
+#ifndef _broker_HandlerImpl_h
+#define _broker_HandlerImpl_h
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/broker/SemanticState.h"
+#include "qpid/broker/SessionContext.h"
+#include "qpid/broker/amqp_0_10/Connection.h"
+
+namespace qpid {
+namespace broker {
+
+class Broker;
+
+/**
+ * Base template for protocol handler implementations.
+ * Provides convenience methods for getting common session objects.
+ */
+class HandlerImpl {
+ protected:
+ SemanticState& state;
+ SessionContext& session;
+
+ HandlerImpl(SemanticState& s) : state(s), session(s.getSession()) {}
+
+ framing::AMQP_ClientProxy& getProxy() { return session.getProxy(); }
+ amqp_0_10::Connection& getConnection() { return session.getConnection(); }
+ Broker& getBroker() { return session.getConnection().getBroker(); }
+};
+
+}} // namespace qpid::broker
+
+
+
+#endif /*!_broker_HandlerImpl_h*/
+
+
diff --git a/qpid/cpp/src/qpid/broker/HeadersExchange.cpp b/qpid/cpp/src/qpid/broker/HeadersExchange.cpp
new file mode 100644
index 0000000000..585c7ba764
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/HeadersExchange.cpp
@@ -0,0 +1,417 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/HeadersExchange.h"
+
+#include "qpid/amqp/CharSequence.h"
+#include "qpid/amqp/MapHandler.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/log/Statement.h"
+#include <algorithm>
+
+
+using namespace qpid::broker;
+
+using std::string;
+using qpid::amqp::MapHandler;
+
+using namespace qpid::framing;
+using namespace qpid::sys;
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+// TODO aconway 2006-09-20: More efficient matching algorithm.
+// The current search algorithm really sucks.
+// Fieldtables are heavy, maybe use shared_ptr to do handle-body.
+
+using namespace qpid::broker;
+
+namespace {
+ const std::string x_match("x-match");
+ // possible values for x-match
+ const std::string all("all");
+ const std::string any("any");
+ const std::string empty;
+
+ // federation related args and values
+ const std::string QPID_RESERVED("qpid.");
+ const std::string qpidFedOp("qpid.fed.op");
+ const std::string qpidFedTags("qpid.fed.tags");
+ const std::string qpidFedOrigin("qpid.fed.origin");
+
+ const std::string fedOpBind("B");
+ const std::string fedOpUnbind("U");
+ const std::string fedOpReorigin("R");
+ const std::string fedOpHello("H");
+
+std::string getMatch(const FieldTable* args)
+{
+ if (!args) {
+ throw InternalErrorException(QPID_MSG("No arguments given."));
+ }
+ FieldTable::ValuePtr what = args->get(x_match);
+ if (!what) {
+ return empty;
+ }
+ if (!what->convertsTo<std::string>()) {
+ throw InternalErrorException(QPID_MSG("Invalid x-match binding format to headers exchange. Must be a string [\"all\" or \"any\"]"));
+ }
+ return what->get<std::string>();
+}
+class Matcher : public MapHandler
+{
+ public:
+ Matcher(const FieldTable& b) : binding(b), matched(0) {}
+ void handleBool(const qpid::amqp::CharSequence& key, bool value) { processUint(std::string(key.data, key.size), value); }
+ void handleUint8(const qpid::amqp::CharSequence& key, uint8_t value) { processUint(std::string(key.data, key.size), value); }
+ void handleUint16(const qpid::amqp::CharSequence& key, uint16_t value) { processUint(std::string(key.data, key.size), value); }
+ void handleUint32(const qpid::amqp::CharSequence& key, uint32_t value) { processUint(std::string(key.data, key.size), value); }
+ void handleUint64(const qpid::amqp::CharSequence& key, uint64_t value) { processUint(std::string(key.data, key.size), value); }
+ void handleInt8(const qpid::amqp::CharSequence& key, int8_t value) { processInt(std::string(key.data, key.size), value); }
+ void handleInt16(const qpid::amqp::CharSequence& key, int16_t value) { processInt(std::string(key.data, key.size), value); }
+ void handleInt32(const qpid::amqp::CharSequence& key, int32_t value) { processInt(std::string(key.data, key.size), value); }
+ void handleInt64(const qpid::amqp::CharSequence& key, int64_t value) { processInt(std::string(key.data, key.size), value); }
+ void handleFloat(const qpid::amqp::CharSequence& key, float value) { processFloat(std::string(key.data, key.size), value); }
+ void handleDouble(const qpid::amqp::CharSequence& key, double value) { processFloat(std::string(key.data, key.size), value); }
+ void handleString(const qpid::amqp::CharSequence& key, const qpid::amqp::CharSequence& value, const qpid::amqp::CharSequence& /*encoding*/)
+ {
+ processString(std::string(key.data, key.size), std::string(value.data, value.size));
+ }
+ void handleVoid(const qpid::amqp::CharSequence& key)
+ {
+ valueCheckRequired(std::string(key.data, key.size));
+ }
+ bool matches()
+ {
+ std::string what = getMatch(&binding);
+ if (what == all) {
+ //must match all entries in the binding, except the match mode indicator
+ return matched == binding.size() - 1;
+ } else if (what == any) {
+ //match any of the entries in the binding
+ return matched > 0;
+ } else {
+ return false;
+ }
+ }
+ private:
+ bool valueCheckRequired(const std::string& key)
+ {
+ FieldTable::ValuePtr v = binding.get(key);
+ if (v) {
+ if (v->getType() == 0xf0/*VOID*/) {
+ ++matched;
+ return false;
+ } else {
+ return true;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ void processString(const std::string& key, const std::string& actual)
+ {
+ if (valueCheckRequired(key) && binding.getAsString(key) == actual) {
+ ++matched;
+ }
+ }
+ void processFloat(const std::string& key, double actual)
+ {
+ double bound;
+ if (valueCheckRequired(key) && binding.getDouble(key, bound) && bound == actual) {
+ ++matched;
+ }
+ }
+ void processInt(const std::string& key, int64_t actual)
+ {
+ if (valueCheckRequired(key) && binding.getAsInt64(key) == actual) {
+ ++matched;
+ }
+ }
+ void processUint(const std::string& key, uint64_t actual)
+ {
+ if (valueCheckRequired(key) && binding.getAsUInt64(key) == actual) {
+ ++matched;
+ }
+ }
+ const FieldTable& binding;
+ size_t matched;
+};
+}
+
+HeadersExchange::HeadersExchange(const string& _name, Manageable* _parent, Broker* b) :
+ Exchange(_name, _parent, b)
+{
+ if (mgmtExchange != 0)
+ mgmtExchange->set_type (typeName);
+}
+
+HeadersExchange::HeadersExchange(const std::string& _name, bool _durable, bool autodelete,
+ const FieldTable& _args, Manageable* _parent, Broker* b) :
+ Exchange(_name, _durable, autodelete, _args, _parent, b)
+{
+ if (mgmtExchange != 0)
+ mgmtExchange->set_type (typeName);
+}
+
+bool HeadersExchange::bind(Queue::shared_ptr queue, const string& bindingKey, const FieldTable* args)
+{
+ string fedOp(fedOpBind);
+ string fedTags;
+ string fedOrigin;
+ if (args) {
+ fedOp = args->getAsString(qpidFedOp);
+ fedTags = args->getAsString(qpidFedTags);
+ fedOrigin = args->getAsString(qpidFedOrigin);
+ }
+
+ bool propagate = false;
+
+ // The federation args get propagated directly, so we need to identify
+ // the non federation args in case a federated propagate is needed
+ FieldTable extra_args;
+ getNonFedArgs(args, extra_args);
+
+ if (fedOp.empty() || fedOp == fedOpBind) {
+ // x-match arg MUST be present for a bind call
+ std::string x_match_value = getMatch(args);
+
+ if (x_match_value != all && x_match_value != any) {
+ throw InternalErrorException(QPID_MSG("Invalid or missing x-match value binding to headers exchange. Must be a string [\"all\" or \"any\"]"));
+ }
+
+ Bindings::ConstPtr p = bindings.snapshot();
+ if (p.get()) {
+ MatchArgs matchArgs(queue, &extra_args);
+ MatchKey matchKey(queue, bindingKey);
+ for (std::vector<BoundKey>::const_iterator i = p->begin(); i != p->end(); ++i) {
+ if (matchKey(*i) && !matchArgs(*i)) {
+ throw InternalErrorException(QPID_MSG("Exchange: " << getName()
+ << ", binding key: " << bindingKey
+ << " Duplicate binding key not allowed." ));
+ }
+ }
+ }
+
+ {
+ Mutex::ScopedLock l(lock);
+ //NOTE: do not include the fed op/tags/origin in the
+ //arguments as when x-match is 'all' these would prevent
+ //matching (they are internally added properties
+ //controlling binding propagation but not relevant to
+ //actual routing)
+ Binding::shared_ptr binding (new Binding (bindingKey, queue, this, args ? *args : FieldTable()));
+ BoundKey bk(binding, extra_args);
+ if (bindings.add_unless(bk, MatchArgs(queue, &extra_args))) {
+ binding->startManagement();
+ propagate = bk.fedBinding.addOrigin(queue->getName(), fedOrigin);
+ if (mgmtExchange != 0) {
+ mgmtExchange->inc_bindingCount();
+ }
+ } else {
+ bk.fedBinding.addOrigin(queue->getName(), fedOrigin);
+ return false;
+ }
+ } // lock dropped
+
+ } else if (fedOp == fedOpUnbind) {
+ Mutex::ScopedLock l(lock);
+
+ FedUnbindModifier modifier(queue->getName(), fedOrigin);
+ bindings.modify_if(MatchKey(queue, bindingKey), modifier);
+ propagate = modifier.shouldPropagate;
+ if (modifier.shouldUnbind) {
+ unbind(queue, bindingKey, args);
+ }
+
+ } else if (fedOp == fedOpReorigin) {
+ Bindings::ConstPtr p = bindings.snapshot();
+ if (p.get())
+ {
+ Mutex::ScopedLock l(lock);
+ for (std::vector<BoundKey>::const_iterator i = p->begin(); i != p->end(); ++i)
+ {
+ if ((*i).fedBinding.hasLocal()) {
+ propagateFedOp( (*i).binding->key, string(), fedOpBind, string());
+ }
+ }
+ }
+ }
+ routeIVE();
+ if (propagate) {
+ FieldTable * prop_args = (extra_args.count() != 0 ? &extra_args : 0);
+ propagateFedOp(bindingKey, fedTags, fedOp, fedOrigin, prop_args);
+ }
+
+ return true;
+}
+
+bool HeadersExchange::unbind(Queue::shared_ptr queue, const string& bindingKey, const FieldTable *args){
+ bool propagate = false;
+ string fedOrigin(args ? args->getAsString(qpidFedOrigin) : "");
+ {
+ Mutex::ScopedLock l(lock);
+
+ FedUnbindModifier modifier(queue->getName(), fedOrigin);
+ MatchKey match_key(queue, bindingKey);
+ bindings.modify_if(match_key, modifier);
+ propagate = modifier.shouldPropagate;
+ if (modifier.shouldUnbind) {
+ if (bindings.remove_if(match_key)) {
+ if (mgmtExchange != 0) {
+ mgmtExchange->dec_bindingCount();
+ }
+ } else {
+ return false;
+ }
+ }
+ }
+
+ if (propagate) {
+ propagateFedOp(bindingKey, string(), fedOpUnbind, string());
+ }
+ if (bindings.empty()) checkAutodelete();
+ return true;
+}
+
+
+void HeadersExchange::route(Deliverable& msg)
+{
+ PreRoute pr(msg, this);
+
+ BindingList b(new std::vector<boost::shared_ptr<qpid::broker::Exchange::Binding> >);
+ Bindings::ConstPtr p = bindings.snapshot();
+ if (p.get()) {
+ for (std::vector<BoundKey>::const_iterator i = p->begin(); i != p->end(); ++i) {
+ if (match(i->args, msg.getMessage())) {
+ /* check if a binding tothe same queue has not been already added to b */
+ std::vector<boost::shared_ptr<qpid::broker::Exchange::Binding> >::iterator bi = b->begin();
+ while ((bi != b->end()) && ((*bi)->queue != i->binding->queue))
+ ++bi;
+ if (bi == b->end())
+ b->push_back(i->binding);
+ }
+ }
+ }
+ doRoute(msg, b);
+}
+
+
+bool HeadersExchange::isBound(Queue::shared_ptr queue, const string* const, const FieldTable* const args)
+{
+ Bindings::ConstPtr p = bindings.snapshot();
+ if (p.get()){
+ for (std::vector<BoundKey>::const_iterator i = p->begin(); i != p->end(); ++i) {
+ if ( (!args || equal((*i).args, *args)) && (!queue || (*i).binding->queue == queue)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void HeadersExchange::getNonFedArgs(const FieldTable* args, FieldTable& nonFedArgs)
+{
+ if (!args)
+ {
+ return;
+ }
+
+ for (qpid::framing::FieldTable::ValueMap::const_iterator i=args->begin(); i != args->end(); ++i)
+ {
+ if (i->first.find(QPID_RESERVED) == 0)
+ {
+ continue;
+ }
+ nonFedArgs.insert((*i));
+ }
+}
+
+HeadersExchange::~HeadersExchange() {
+ if (mgmtExchange != 0)
+ mgmtExchange->debugStats("destroying");
+}
+
+const std::string HeadersExchange::typeName("headers");
+
+namespace
+{
+
+ bool match_values(const FieldValue& bind, const FieldValue& msg) {
+ return bind.getType() == 0xf0 || bind == msg;
+ }
+
+}
+
+bool HeadersExchange::match(const FieldTable& bindArgs, const Message& msg) {
+ Matcher matcher(bindArgs);
+ msg.processProperties(matcher);
+ return matcher.matches();
+}
+
+bool HeadersExchange::equal(const FieldTable& a, const FieldTable& b) {
+ typedef FieldTable::ValueMap Map;
+ for (Map::const_iterator i = a.begin();
+ i != a.end();
+ ++i)
+ {
+ Map::const_iterator j = b.find(i->first);
+ if (j == b.end()) return false;
+ if (!match_values(*(i->second), *(j->second))) return false;
+ }
+ return true;
+}
+
+//---------
+HeadersExchange::MatchArgs::MatchArgs(Queue::shared_ptr q, const qpid::framing::FieldTable* a) : queue(q), args(a) {}
+
+bool HeadersExchange::MatchArgs::operator()(const BoundKey & bk)
+{
+ return bk.binding->queue == queue && bk.binding->args == *args;
+}
+
+//---------
+HeadersExchange::MatchKey::MatchKey(Queue::shared_ptr q, const std::string& k) : queue(q), key(k) {}
+
+bool HeadersExchange::MatchKey::operator()(const BoundKey & bk)
+{
+ return bk.binding->queue == queue && bk.binding->key == key;
+}
+
+//----------
+HeadersExchange::FedUnbindModifier::FedUnbindModifier(const string& queueName, const string& origin) : queueName(queueName), fedOrigin(origin), shouldUnbind(false), shouldPropagate(false) {}
+HeadersExchange::FedUnbindModifier::FedUnbindModifier() : shouldUnbind(false), shouldPropagate(false) {}
+
+bool HeadersExchange::FedUnbindModifier::operator()(BoundKey & bk)
+{
+ shouldPropagate = bk.fedBinding.delOrigin(queueName, fedOrigin);
+ if (bk.fedBinding.countFedBindings(queueName) == 0)
+ {
+ shouldUnbind = true;
+ }
+ return true;
+}
+
+bool HeadersExchange::hasBindings()
+{
+ Bindings::ConstPtr ptr = bindings.snapshot();
+ return ptr && !ptr->empty();
+}
diff --git a/qpid/cpp/src/qpid/broker/HeadersExchange.h b/qpid/cpp/src/qpid/broker/HeadersExchange.h
new file mode 100644
index 0000000000..54c69491e9
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/HeadersExchange.h
@@ -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.
+ *
+ */
+#ifndef _HeadersExchange_
+#define _HeadersExchange_
+
+#include <vector>
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/broker/Exchange.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/sys/CopyOnWriteArray.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/broker/Queue.h"
+
+namespace qpid {
+namespace broker {
+
+
+class HeadersExchange : public virtual Exchange {
+
+ struct BoundKey
+ {
+ Binding::shared_ptr binding;
+ qpid::framing::FieldTable args;
+ FedBinding fedBinding;
+ BoundKey(Binding::shared_ptr binding_, const qpid::framing::FieldTable& args_) : binding(binding_), args(args_) {}
+ };
+
+ struct MatchArgs
+ {
+ const Queue::shared_ptr queue;
+ const qpid::framing::FieldTable* args;
+ MatchArgs(Queue::shared_ptr q, const qpid::framing::FieldTable* a);
+ bool operator()(const BoundKey & bk);
+ };
+
+ struct MatchKey
+ {
+ const Queue::shared_ptr queue;
+ const std::string& key;
+ MatchKey(Queue::shared_ptr q, const std::string& k);
+ bool operator()(const BoundKey & bk);
+ };
+
+ struct FedUnbindModifier
+ {
+ std::string queueName;
+ std::string fedOrigin;
+ bool shouldUnbind;
+ bool shouldPropagate;
+ FedUnbindModifier();
+ FedUnbindModifier(const std::string& queueName, const std::string& origin);
+ bool operator()(BoundKey & bk);
+ };
+
+ typedef qpid::sys::CopyOnWriteArray<BoundKey> Bindings;
+
+ Bindings bindings;
+ qpid::sys::Mutex lock;
+ protected:
+ void getNonFedArgs(const framing::FieldTable* args,
+ framing::FieldTable& nonFedArgs);
+ bool hasBindings();
+
+ public:
+ QPID_BROKER_EXTERN static const std::string typeName;
+
+ QPID_BROKER_EXTERN HeadersExchange(const std::string& name,
+ management::Manageable* parent = 0, Broker* broker = 0);
+ QPID_BROKER_EXTERN HeadersExchange(const std::string& _name,
+ bool _durable, bool autodelete,
+ const qpid::framing::FieldTable& _args,
+ management::Manageable* parent = 0, Broker* broker = 0);
+
+ virtual std::string getType() const { return typeName; }
+
+ QPID_BROKER_EXTERN virtual bool bind(Queue::shared_ptr queue,
+ const std::string& routingKey,
+ const qpid::framing::FieldTable* args);
+
+ virtual bool unbind(Queue::shared_ptr queue, const std::string& routingKey, const qpid::framing::FieldTable* args);
+
+ QPID_BROKER_EXTERN virtual void route(Deliverable& msg);
+
+ QPID_BROKER_EXTERN virtual bool isBound(Queue::shared_ptr queue,
+ const std::string* const routingKey,
+ const qpid::framing::FieldTable* const args);
+
+ QPID_BROKER_EXTERN virtual ~HeadersExchange();
+
+ virtual bool supportsDynamicBinding() { return true; }
+
+ static QPID_BROKER_EXTERN bool match(const qpid::framing::FieldTable& bindArgs, const qpid::broker::Message& msg);
+ static bool equal(const qpid::framing::FieldTable& bindArgs, const qpid::framing::FieldTable& msgArgs);
+};
+
+
+
+}
+}
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/IndexedDeque.h b/qpid/cpp/src/qpid/broker/IndexedDeque.h
new file mode 100644
index 0000000000..229b4e3009
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/IndexedDeque.h
@@ -0,0 +1,226 @@
+#ifndef QPID_BROKER_INDEXEDDEQUE_H
+#define QPID_BROKER_INDEXEDDEQUE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/framing/SequenceNumber.h"
+#include "qpid/broker/Message.h"
+#include "qpid/broker/Messages.h"
+#include "qpid/broker/QueueCursor.h"
+#include "qpid/log/Statement.h"
+#include <deque>
+
+namespace qpid {
+namespace broker {
+
+/**
+ * Template for a deque whose contents can be refered to by
+ * QueueCursor
+ */
+template <typename T> class IndexedDeque
+{
+ public:
+ typedef boost::function1<T, qpid::framing::SequenceNumber> Padding;
+ IndexedDeque(Padding p) : head(0), version(0), padding(p) {}
+
+ bool index(const QueueCursor& cursor, size_t& result)
+ {
+ return cursor.valid && index(qpid::framing::SequenceNumber(cursor.position + 1), result);
+ }
+
+ /**
+ * Finds the index for the message with the specified sequence number.
+ *
+ * @returns true if a message was found with the specified sequence,
+ * in which case the second parameter will be set to the index of that
+ * message; false if no message with that sequence exists, in which
+ * case the second parameter will be 0 if the sequence is less than
+ * that of the first message and non-zero if it is greater than that
+ * of the last message
+ */
+ bool index(const qpid::framing::SequenceNumber& position, size_t& i)
+ {
+ //assuming a monotonic sequence, with no messages removed except
+ //from the ends of the deque, we can use the position to determine
+ //an index into the deque
+ if (messages.size()) {
+ qpid::framing::SequenceNumber front(messages.front().getSequence());
+ if (position < front) {
+ i = 0;
+ } else {
+ i = position - front;
+ return i < messages.size();
+ }
+ }
+ return false;
+ }
+
+ bool deleted(const QueueCursor& cursor)
+ {
+ size_t i;
+ if (cursor.valid && index(cursor.position, i)) {
+ messages[i].setState(DELETED);
+ clean();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ T& publish(const T& added)
+ {
+ // QPID-4046: let producer help clean the backlog of deleted messages
+ clean();
+ //for ha replication, the queue can sometimes be reset by
+ //removing some of the more recent messages, in this case we
+ //need to ensure the DELETED records at the tail do not interfere with indexing
+ while (messages.size() && added.getSequence() <= messages.back().getSequence() && messages.back().getState() == DELETED)
+ messages.pop_back();
+ if (messages.size() && added.getSequence() <= messages.back().getSequence()) throw qpid::Exception(QPID_MSG("Index out of sequence!"));
+
+ //add padding to prevent gaps in sequence, which break the index
+ //calculation (needed for queue replication)
+ while (messages.size() && (added.getSequence() - messages.back().getSequence()) > 1)
+ messages.push_back(padding(messages.back().getSequence() + 1));
+
+ messages.push_back(added);
+ T& m = messages.back();
+ m.setState(AVAILABLE);
+ if (head >= messages.size()) head = messages.size() - 1;
+ QPID_LOG(debug, "Message " << &m << " published, state is " << m.getState() << " (head is now " << head << ")");
+ return m;
+ }
+
+ T* release(const QueueCursor& cursor)
+ {
+ size_t i;
+ if (cursor.valid && index(cursor.position, i)) {
+ messages[i].setState(AVAILABLE);
+ ++version;
+ QPID_LOG(debug, "Released message at position " << cursor.position << ", index " << i);
+ return &messages[i];
+ } else {
+ if (!cursor.valid) { QPID_LOG(debug, "Could not release message; cursor was invalid");}
+ else { QPID_LOG(debug, "Could not release message at position " << cursor.position); }
+ return 0;
+ }
+ }
+
+ bool reset(const QueueCursor& cursor)
+ {
+ return !cursor.valid || (cursor.type == CONSUMER && cursor.version != version);
+ }
+
+ T* next(QueueCursor& cursor)
+ {
+ size_t i = 0;
+ if (reset(cursor)) i = head; //start from head
+ else index(cursor, i); //get first message that is greater than position
+
+ if (cursor.valid) {
+ QPID_LOG(debug, "next() called for cursor at " << cursor.position << ", index set to " << i << " (of " << messages.size() << ")");
+ } else {
+ QPID_LOG(debug, "next() called for invalid cursor, index started at " << i << " (of " << messages.size() << ")");
+ }
+ while (i < messages.size()) {
+ T& m = messages[i++];
+ if (m.getState() == DELETED) continue;
+ cursor.setPosition(m.getSequence(), version);
+ QPID_LOG(debug, "in next(), cursor set to " << cursor.position);
+
+ if (cursor.check(m)) {
+ QPID_LOG(debug, "in next(), returning message at " << cursor.position);
+ return &m;
+ }
+ }
+ QPID_LOG(debug, "no message to return from next");
+ return 0;
+ }
+
+ size_t size()
+ {
+ size_t count(0);
+ for (size_t i = head; i < messages.size(); ++i) {
+ if (messages[i].getState() == AVAILABLE) ++count;
+ }
+ return count;
+ }
+
+ T* find(const qpid::framing::SequenceNumber& position, QueueCursor* cursor)
+ {
+ size_t i = 0;
+ if (index(position, i)){
+ T& m = messages[i];
+ if (cursor) cursor->setPosition(position, version);
+ if (m.getState() == AVAILABLE || m.getState() == ACQUIRED) {
+ return &m;
+ }
+ } else if (cursor) {
+ if (i >= messages.size()) cursor->setPosition(position, version);//haven't yet got a message with that seq no
+ else if (i == 0) cursor->valid = false;//reset
+ }
+ return 0;
+ }
+
+ T* find(const QueueCursor& cursor)
+ {
+ if (cursor.valid) return find(cursor.position, 0);
+ else return 0;
+ }
+
+ void clean()
+ {
+ // QPID-4046: If a queue has multiple consumers, then it is possible for a large
+ // collection of deleted messages to build up. Limit the number of messages cleaned
+ // up on each call to clean().
+ size_t count = 0;
+ while (messages.size() && messages.front().getState() == DELETED && count < 10) {
+ messages.pop_front();
+ count += 1;
+ }
+ head = (head > count) ? head - count : 0;
+ QPID_LOG(debug, "clean(): " << messages.size() << " messages remain; head is now " << head);
+ }
+
+ void foreach(Messages::Functor f)
+ {
+ for (typename Deque::iterator i = messages.begin(); i != messages.end(); ++i) {
+ if (i->getState() == AVAILABLE) {
+ f(*i);
+ }
+ }
+ clean();
+ }
+
+ void resetCursors()
+ {
+ ++version;
+ }
+
+ typedef std::deque<T> Deque;
+ Deque messages;
+ size_t head;
+ int32_t version;
+ Padding padding;
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_INDEXEDDEQUE_H*/
diff --git a/qpid/cpp/src/qpid/broker/IngressCompletion.cpp b/qpid/cpp/src/qpid/broker/IngressCompletion.cpp
new file mode 100644
index 0000000000..51eafb8d86
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/IngressCompletion.cpp
@@ -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.
+ *
+ */
+#include "IngressCompletion.h"
+#include "Queue.h"
+
+namespace qpid {
+namespace broker {
+IngressCompletion::~IngressCompletion() {}
+
+void IngressCompletion::enqueueAsync(boost::shared_ptr<Queue> q)
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ queues.push_back(q);
+}
+
+void IngressCompletion::flush()
+{
+ Queues copy;
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ queues.swap(copy);
+ }
+ for (Queues::const_iterator i = copy.begin(); i != copy.end(); ++i) {
+ boost::shared_ptr<Queue> q(i->lock());
+ if (q) {
+ q->flush();
+ }
+ }
+}
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/IngressCompletion.h b/qpid/cpp/src/qpid/broker/IngressCompletion.h
new file mode 100644
index 0000000000..ca21ec9837
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/IngressCompletion.h
@@ -0,0 +1,52 @@
+#ifndef QPID_BROKER_INGRESSCOMPLETION_H
+#define QPID_BROKER_INGRESSCOMPLETION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "AsyncCompletion.h"
+#include "qpid/sys/Mutex.h"
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+#include <vector>
+
+namespace qpid {
+namespace broker {
+
+class Queue;
+/**
+ * An AsyncCompletion object for async enqueues, that can be flushed
+ * when needed
+ */
+class IngressCompletion : public AsyncCompletion
+{
+ public:
+ QPID_BROKER_EXTERN virtual ~IngressCompletion();
+
+ void enqueueAsync(boost::shared_ptr<Queue>);
+ void flush();
+ private:
+ typedef std::vector<boost::weak_ptr<Queue> > Queues;
+ Queues queues;
+ qpid::sys::Mutex lock;
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_INGRESSCOMPLETION_H*/
diff --git a/qpid/cpp/src/qpid/broker/Link.cpp b/qpid/cpp/src/qpid/broker/Link.cpp
new file mode 100644
index 0000000000..9b85917251
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Link.cpp
@@ -0,0 +1,795 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/Link.h"
+#include "qpid/broker/LinkRegistry.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/amqp_0_10/Connection.h"
+#include "qpid/sys/Timer.h"
+#include "qmf/org/apache/qpid/broker/EventBrokerLinkUp.h"
+#include "qmf/org/apache/qpid/broker/EventBrokerLinkDown.h"
+#include "boost/bind.hpp"
+#include "qpid/log/Statement.h"
+#include "qpid/framing/enum.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/framing/amqp_types.h"
+#include "qpid/broker/AclModule.h"
+#include "qpid/broker/Exchange.h"
+#include "qpid/broker/NameGenerator.h"
+#include "qpid/UrlArray.h"
+
+namespace qpid {
+namespace broker {
+
+using framing::Buffer;
+using framing::FieldTable;
+using framing::UnauthorizedAccessException;
+using framing::connection::CLOSE_CODE_CONNECTION_FORCED;
+using management::ManagementAgent;
+using management::ManagementObject;
+using management::Manageable;
+using management::Args;
+using sys::Mutex;
+using std::stringstream;
+using std::string;
+namespace _qmf = ::qmf::org::apache::qpid::broker;
+
+
+namespace {
+ const std::string FAILOVER_EXCHANGE("amq.failover");
+ const std::string FAILOVER_HEADER_KEY("amq.failover");
+}
+
+
+struct LinkTimerTask : public sys::TimerTask {
+ LinkTimerTask(Link& l, sys::Timer& t)
+ : TimerTask(l.getBroker()->getLinkMaintenanceInterval(),
+ "Link retry timer"),
+ link(l), timer(t) {}
+
+ void fire() {
+ link.maintenanceVisit();
+ setupNextFire();
+ timer.add(this);
+ }
+
+ Link& link;
+ sys::Timer& timer;
+};
+
+
+
+/** LinkExchange is used by the link to subscribe to the remote broker's amq.failover exchange.
+ */
+class LinkExchange : public broker::Exchange
+{
+public:
+ LinkExchange(const std::string& name) : Exchange(name), link(0) {}
+ ~LinkExchange() {};
+ std::string getType() const { return Link::exchangeTypeName; }
+
+ // Exchange methods - set up to prevent binding/unbinding etc from clients!
+ bool bind(boost::shared_ptr<broker::Queue>, const std::string&, const framing::FieldTable*) { return false; }
+ bool unbind(boost::shared_ptr<broker::Queue>, const std::string&, const framing::FieldTable*) { return false; }
+ bool isBound(boost::shared_ptr<broker::Queue>, const std::string* const, const framing::FieldTable* const) {return false;}
+ bool hasBindings() { return false; }
+ // Process messages sent from the remote's amq.failover exchange by extracting the failover URLs
+ // and saving them should the Link need to reconnect.
+ void route(broker::Deliverable& /*msg*/)
+ {
+ if (!link) return;
+ const framing::FieldTable* headers = 0;//TODO: msg.getMessage().getApplicationHeaders();
+ framing::Array addresses;
+ if (headers && headers->getArray(FAILOVER_HEADER_KEY, addresses)) {
+ // convert the Array of addresses to a single Url container for used with setUrl():
+ std::vector<Url> urlVec;
+ Url urls;
+ urlVec = urlArrayToVector(addresses);
+ for(size_t i = 0; i < urlVec.size(); ++i)
+ urls.insert(urls.end(), urlVec[i].begin(), urlVec[i].end());
+ QPID_LOG(debug, "Remote broker has provided these failover addresses= " << urls);
+ link->setUrl(urls);
+ }
+ }
+
+ void setLink(Link *_link)
+ {
+ assert(!link);
+ link = _link;
+ }
+
+private:
+ Link *link;
+};
+
+
+boost::shared_ptr<Exchange> Link::linkExchangeFactory( const std::string& _name )
+{
+ return Exchange::shared_ptr(new LinkExchange(_name));
+}
+
+Link::Link(const string& _name,
+ LinkRegistry* _links,
+ const string& _host,
+ uint16_t _port,
+ const string& _transport,
+ DestroyedListener l,
+ bool _durable,
+ const string& _authMechanism,
+ const string& _username,
+ const string& _password,
+ Broker* _broker,
+ Manageable* parent,
+ bool failover_)
+ : name(_name), links(_links),
+ configuredTransport(_transport), configuredHost(_host), configuredPort(_port),
+ host(_host), port(_port), transport(_transport),
+ durable(_durable),
+ authMechanism(_authMechanism), username(_username), password(_password),
+ persistenceId(0), broker(_broker), state(0),
+ visitCount(0),
+ currentInterval(1),
+ reconnectNext(0), // Index of next address for reconnecting in url.
+ nextFreeChannel(1),
+ freeChannels(1, framing::CHANNEL_MAX),
+ connection(0),
+ agent(0),
+ listener(l),
+ timerTask(new LinkTimerTask(*this, broker->getTimer())),
+ failover(failover_),
+ failoverChannel(0)
+{
+ if (parent != 0 && broker != 0)
+ {
+ agent = broker->getManagementAgent();
+ if (agent != 0)
+ {
+ mgmtObject = _qmf::Link::shared_ptr(new _qmf::Link(agent, this, parent, name, durable));
+ mgmtObject->set_host(host);
+ mgmtObject->set_port(port);
+ mgmtObject->set_transport(transport);
+ agent->addObject(mgmtObject, 0, durable);
+ }
+ }
+ setStateLH(STATE_WAITING);
+ startConnectionLH();
+ broker->getTimer().add(timerTask);
+
+ if (failover) {
+ stringstream exchangeName;
+ exchangeName << "qpid.link." << name;
+ std::pair<Exchange::shared_ptr, bool> rc =
+ broker->getExchanges().declare(exchangeName.str(), exchangeTypeName);
+ failoverExchange = boost::static_pointer_cast<LinkExchange>(rc.first);
+ assert(failoverExchange);
+ failoverExchange->setLink(this);
+ }
+}
+
+Link::~Link ()
+{
+ if (state == STATE_OPERATIONAL && connection != 0) {
+ closeConnection("closed by management");
+ }
+
+ if (mgmtObject != 0)
+ mgmtObject->resourceDestroy ();
+
+ if (failover)
+ broker->getExchanges().destroy(failoverExchange->getName());
+}
+
+void Link::setStateLH (int newState)
+{
+ if (newState == state)
+ return;
+
+ state = newState;
+
+ switch (state)
+ {
+ case STATE_WAITING : mgmtObject->set_state("Waiting"); break;
+ case STATE_CONNECTING : mgmtObject->set_state("Connecting"); break;
+ case STATE_OPERATIONAL : mgmtObject->set_state("Operational"); break;
+ case STATE_FAILED : mgmtObject->set_state("Failed"); break;
+ case STATE_CLOSED : mgmtObject->set_state("Closed"); break;
+ case STATE_CLOSING : mgmtObject->set_state("Closing"); break;
+ }
+}
+
+void Link::startConnectionLH ()
+{
+ assert(state == STATE_WAITING);
+ try {
+ // Set the state before calling connect. It is possible that connect
+ // will fail synchronously and call Link::closed before returning.
+ setStateLH(STATE_CONNECTING);
+ broker->connect (name, host, boost::lexical_cast<std::string>(port), transport,
+ boost::bind (&Link::closed, this, _1, _2));
+ QPID_LOG (info, "Inter-broker link connecting to " << host << ":" << port);
+ } catch(const std::exception& e) {
+ QPID_LOG(error, "Link connection to " << host << ":" << port << " failed: "
+ << e.what());
+ setStateLH(STATE_WAITING);
+ mgmtObject->set_lastError (e.what());
+ }
+}
+
+void Link::established(qpid::broker::amqp_0_10::Connection* c)
+{
+ stringstream addr;
+ addr << host << ":" << port;
+ QPID_LOG (info, "Inter-broker link established to " << addr.str());
+
+ if (agent)
+ agent->raiseEvent(_qmf::EventBrokerLinkUp(addr.str()));
+ bool isClosing = true;
+ {
+ Mutex::ScopedLock mutex(lock);
+ if (state != STATE_CLOSING) {
+ isClosing = false;
+ setStateLH(STATE_OPERATIONAL);
+ currentInterval = 1;
+ visitCount = 0;
+ connection = c;
+ c->requestIOProcessing (boost::bind(&Link::ioThreadProcessing, this));
+ }
+ }
+ if (isClosing)
+ destroy();
+}
+
+
+void Link::setUrl(const Url& u) {
+ QPID_LOG(info, "Setting remote broker failover addresses for link '" << getName() << "' to these urls: " << u);
+ Mutex::ScopedLock mutex(lock);
+ url = u;
+ reconnectNext = 0;
+}
+
+
+namespace {
+class DetachedCallback : public SessionHandler::ErrorListener {
+ public:
+ DetachedCallback(const Link& link) : name(link.getName()) {}
+ void connectionException(framing::connection::CloseCode, const std::string&) {}
+ void channelException(framing::session::DetachCode, const std::string&) {}
+ void executionException(framing::execution::ErrorCode, const std::string&) {}
+ void incomingExecutionException(framing::execution::ErrorCode, const std::string& ) {}
+ void detach() {}
+ private:
+ const std::string name;
+};
+}
+
+void Link::opened()
+{
+ Mutex::ScopedLock mutex(lock);
+ if (!connection || state != STATE_OPERATIONAL) return;
+
+ if (connection->GetManagementObject()) {
+ mgmtObject->set_connectionRef(connection->GetManagementObject()->getObjectId());
+ }
+
+ // Get default URL from known-hosts if not already set
+ if (url.empty()) {
+ const std::vector<Url>& known = connection->getKnownHosts();
+ // Flatten vector of URLs into a single URL listing all addresses.
+ url.clear();
+ for(size_t i = 0; i < known.size(); ++i)
+ url.insert(url.end(), known[i].begin(), known[i].end());
+ reconnectNext = 0;
+ QPID_LOG(debug, "Known hosts for peer of inter-broker link: " << url);
+ }
+
+ if (failover) {
+ //
+ // attempt to subscribe to failover exchange for updates from remote
+ //
+
+ const std::string queueName = "qpid.link." + framing::Uuid(true).str();
+ failoverChannel = nextChannel();
+
+ SessionHandler& sessionHandler = connection->getChannel(failoverChannel);
+ sessionHandler.setErrorListener(
+ boost::shared_ptr<SessionHandler::ErrorListener>(new DetachedCallback(*this)));
+ failoverSession = queueName;
+ sessionHandler.attachAs(failoverSession);
+
+ framing::AMQP_ServerProxy remoteBroker(sessionHandler.out);
+
+ remoteBroker.getQueue().declare(queueName,
+ "", // alt-exchange
+ false, // passive
+ false, // durable
+ true, // exclusive
+ true, // auto-delete
+ FieldTable());
+ remoteBroker.getExchange().bind(queueName,
+ FAILOVER_EXCHANGE,
+ "", // no key
+ FieldTable());
+ remoteBroker.getMessage().subscribe(queueName,
+ failoverExchange->getName(),
+ 1, // implied-accept mode
+ 0, // pre-acquire mode
+ false, // exclusive
+ "", // resume-id
+ 0, // resume-ttl
+ FieldTable());
+ remoteBroker.getMessage().flow(failoverExchange->getName(), 0, 0xFFFFFFFF);
+ remoteBroker.getMessage().flow(failoverExchange->getName(), 1, 0xFFFFFFFF);
+ }
+}
+
+
+// called when connection attempt fails (see startConnectionLH)
+void Link::closed(int, std::string text)
+{
+ QPID_LOG (info, "Inter-broker link disconnected from " << host << ":" << port << " " << text);
+
+ bool isClosing = false;
+ {
+ Mutex::ScopedLock mutex(lock);
+
+ connection = 0;
+
+ mgmtObject->set_connectionRef(qpid::management::ObjectId());
+ if (state == STATE_OPERATIONAL && agent) {
+ stringstream addr;
+ addr << host << ":" << port;
+ agent->raiseEvent(_qmf::EventBrokerLinkDown(addr.str()));
+ }
+
+ for (Bridges::iterator i = active.begin(); i != active.end(); i++) {
+ (*i)->closed();
+ created.push_back(*i);
+ }
+ active.clear();
+
+ if (state == STATE_CLOSING) {
+ isClosing = true;
+ } else if (state != STATE_FAILED) {
+ setStateLH(STATE_WAITING);
+ mgmtObject->set_lastError (text);
+ }
+ }
+ if (isClosing) destroy();
+}
+
+// Cleans up the connection before destroying Link. Must be called in connection thread
+// if the connection is active. Caller Note well: may call "delete this"!
+void Link::destroy ()
+{
+ Bridges toDelete;
+
+ timerTask->cancel(); // call prior to locking so maintenance visit can finish
+ {
+ Mutex::ScopedLock mutex(lock);
+
+ QPID_LOG (info, "Inter-broker link to " << configuredHost << ":" << configuredPort << " removed by management");
+ closeConnection("closed by management");
+ setStateLH(STATE_CLOSED);
+
+ // Move the bridges to be deleted into a local vector so there is no
+ // corruption of the iterator caused by bridge deletion.
+ for (Bridges::iterator i = active.begin(); i != active.end(); i++) {
+ (*i)->closed();
+ toDelete.push_back(*i);
+ }
+ active.clear();
+
+ for (Bridges::iterator i = created.begin(); i != created.end(); i++)
+ toDelete.push_back(*i);
+ created.clear();
+ }
+
+ // Now delete all bridges on this link (don't hold the lock for this).
+ for (Bridges::iterator i = toDelete.begin(); i != toDelete.end(); i++)
+ (*i)->close();
+ toDelete.clear();
+ // notify LinkRegistry that this Link has been destroyed. Will result in "delete
+ // this" if LinkRegistry is holding the last shared pointer to *this
+ listener(this);
+}
+
+void Link::add(Bridge::shared_ptr bridge)
+{
+ Mutex::ScopedLock mutex(lock);
+ created.push_back (bridge);
+ if (connection)
+ connection->requestIOProcessing (boost::bind(&Link::ioThreadProcessing, this));
+
+}
+
+void Link::cancel(Bridge::shared_ptr bridge)
+{
+ bool needIOProcessing = false;
+ {
+ Mutex::ScopedLock mutex(lock);
+
+ for (Bridges::iterator i = created.begin(); i != created.end(); i++) {
+ if ((*i).get() == bridge.get()) {
+ created.erase(i);
+ break;
+ }
+ }
+ for (Bridges::iterator i = active.begin(); i != active.end(); i++) {
+ if ((*i).get() == bridge.get()) {
+ cancellations.push_back(bridge);
+ bridge->closed();
+ active.erase(i);
+ break;
+ }
+ }
+ needIOProcessing = !cancellations.empty();
+ }
+ if (needIOProcessing && connection)
+ connection->requestIOProcessing (boost::bind(&Link::ioThreadProcessing, this));
+}
+
+void Link::ioThreadProcessing()
+{
+ Mutex::ScopedLock mutex(lock);
+
+ if (state != STATE_OPERATIONAL)
+ return;
+
+ // check for bridge session errors and recover
+ if (!active.empty()) {
+ Bridges::iterator removed = std::remove_if(
+ active.begin(), active.end(), boost::bind(&Bridge::isDetached, _1));
+ for (Bridges::iterator i = removed; i != active.end(); ++i) {
+ Bridge::shared_ptr bridge = *i;
+ bridge->closed();
+ bridge->cancel(*connection);
+ created.push_back(bridge);
+ }
+ active.erase(removed, active.end());
+ }
+
+ //process any pending creates and/or cancellations (do
+ //cancellations first in case any of the creates represent
+ //recreation of cancelled subscriptions
+ if (!cancellations.empty()) {
+ for (Bridges::iterator i = cancellations.begin(); i != cancellations.end(); ++i) {
+ (*i)->cancel(*connection);
+ }
+ cancellations.clear();
+ }
+ if (!created.empty()) {
+ for (Bridges::iterator i = created.begin(); i != created.end(); ++i) {
+ active.push_back(*i);
+ (*i)->create(*connection);
+ }
+ created.clear();
+ }
+}
+
+void Link::maintenanceVisit ()
+{
+ Mutex::ScopedLock mutex(lock);
+
+ switch (state) {
+ case STATE_WAITING:
+ visitCount++;
+ if (visitCount >= currentInterval)
+ {
+ visitCount = 0;
+ //switch host and port to next in url list if possible
+ if (!tryFailoverLH()) {
+ currentInterval *= 2;
+ if (currentInterval > MAX_INTERVAL)
+ currentInterval = MAX_INTERVAL;
+ startConnectionLH();
+ }
+ }
+ break;
+
+ case STATE_OPERATIONAL:
+ if ((!active.empty() || !created.empty() || !cancellations.empty()) &&
+ connection && connection->isOpen())
+ connection->requestIOProcessing (boost::bind(&Link::ioThreadProcessing, this));
+ break;
+
+ default: // no-op for all other states
+ break;
+ }
+}
+
+void Link::reconnectLH(const Address& a)
+{
+ host = a.host;
+ port = a.port;
+ transport = a.protocol;
+
+ stringstream errorString;
+ errorString << "Failing over to " << a;
+ mgmtObject->set_lastError(errorString.str());
+ mgmtObject->set_host(host);
+ mgmtObject->set_port(port);
+ mgmtObject->set_transport(transport);
+
+ startConnectionLH();
+}
+
+bool Link::tryFailoverLH() {
+ assert(state == STATE_WAITING);
+ if (reconnectNext >= url.size()) reconnectNext = 0;
+ if (url.empty()) return false;
+ Address next = url[reconnectNext++];
+ if (next.host != host || next.port != port || next.protocol != transport) {
+ QPID_LOG(info, "Inter-broker link '" << name << "' failing over to " << next);
+ reconnectLH(next);
+ return true;
+ }
+ return false;
+}
+
+// Allocate channel from link free pool
+framing::ChannelId Link::nextChannel()
+{
+ Mutex::ScopedLock mutex(lock);
+ if (!freeChannels.empty()) {
+ // A free channel exists.
+ for (framing::ChannelId i = 1; i <= framing::CHANNEL_MAX; i++)
+ {
+ // extract proposed free channel
+ framing::ChannelId c = nextFreeChannel;
+ // calculate next free channel
+ if (framing::CHANNEL_MAX == nextFreeChannel)
+ nextFreeChannel = 1;
+ else
+ nextFreeChannel += 1;
+ // if proposed channel is free, use it
+ if (freeChannels.contains(c))
+ {
+ freeChannels -= c;
+ QPID_LOG(debug, "Link " << name << " allocates channel: " << c);
+ return c;
+ }
+ }
+ assert (false);
+ }
+
+ throw Exception(Msg() << "Link " << name << " channel pool is empty");
+}
+
+// Return channel to link free pool
+void Link::returnChannel(framing::ChannelId c)
+{
+ Mutex::ScopedLock mutex(lock);
+ QPID_LOG(debug, "Link " << name << " frees channel: " << c);
+ freeChannels += c;
+}
+
+void Link::notifyConnectionForced(const string text)
+{
+ bool isClosing = false;
+ {
+ Mutex::ScopedLock mutex(lock);
+ if (state == STATE_CLOSING) {
+ isClosing = true;
+ } else {
+ setStateLH(STATE_FAILED);
+ mgmtObject->set_lastError(text);
+ }
+ }
+ if (isClosing) destroy();
+}
+
+void Link::setPersistenceId(uint64_t id) const
+{
+ persistenceId = id;
+}
+
+const string& Link::getName() const
+{
+ return name;
+}
+
+const std::string Link::ENCODED_IDENTIFIER("link.v2");
+const std::string Link::ENCODED_IDENTIFIER_V1("link");
+
+bool Link::isEncodedLink(const std::string& key)
+{
+ return key == ENCODED_IDENTIFIER || key == ENCODED_IDENTIFIER_V1;
+}
+
+Link::shared_ptr Link::decode(LinkRegistry& links, Buffer& buffer)
+{
+ string kind;
+ buffer.getShortString(kind);
+
+ string host;
+ uint16_t port;
+ string transport;
+ string authMechanism;
+ string username;
+ string password;
+ string name;
+
+ if (kind == ENCODED_IDENTIFIER) {
+ // newer version provides a link name.
+ buffer.getShortString(name);
+ }
+ buffer.getShortString(host);
+ port = buffer.getShort();
+ buffer.getShortString(transport);
+ bool durable(buffer.getOctet());
+ buffer.getShortString(authMechanism);
+ buffer.getShortString(username);
+ buffer.getShortString(password);
+
+ if (kind == ENCODED_IDENTIFIER_V1) {
+ /** previous versions identified the Link by host:port, there was no name
+ * assigned. So create a name for the new Link.
+ */
+ name = createName(transport, host, port);
+ }
+
+ return links.declare(name, host, port, transport, durable, authMechanism,
+ username, password).first;
+}
+
+void Link::encode(Buffer& buffer) const
+{
+ buffer.putShortString(ENCODED_IDENTIFIER);
+ buffer.putShortString(name);
+ buffer.putShortString(configuredHost);
+ buffer.putShort(configuredPort);
+ buffer.putShortString(configuredTransport);
+ buffer.putOctet(durable ? 1 : 0);
+ buffer.putShortString(authMechanism);
+ buffer.putShortString(username);
+ buffer.putShortString(password);
+}
+
+uint32_t Link::encodedSize() const
+{
+ return ENCODED_IDENTIFIER.size() + 1 // +1 byte length
+ + name.size() + 1
+ + configuredHost.size() + 1 // short-string (host)
+ + 2 // port
+ + configuredTransport.size() + 1 // short-string(transport)
+ + 1 // durable
+ + authMechanism.size() + 1
+ + username.size() + 1
+ + password.size() + 1;
+}
+
+ManagementObject::shared_ptr Link::GetManagementObject(void) const
+{
+ return mgmtObject;
+}
+
+void Link::close() {
+ QPID_LOG(debug, "Link::close(), link=" << name );
+ bool destroy_now = false;
+ {
+ Mutex::ScopedLock mutex(lock);
+ if (state != STATE_CLOSING) {
+ int old_state = state;
+ setStateLH(STATE_CLOSING);
+ if (connection) {
+ //connection can only be closed on the connections own IO processing thread
+ connection->requestIOProcessing(boost::bind(&Link::destroy, this));
+ } else if (old_state == STATE_CONNECTING) {
+ // cannot destroy Link now since a connection request is outstanding.
+ // destroy the link after we get a response (see Link::established,
+ // Link::closed, Link::notifyConnectionForced, etc).
+ } else {
+ destroy_now = true;
+ }
+ }
+ }
+ if (destroy_now) destroy();
+}
+
+
+Manageable::status_t Link::ManagementMethod (uint32_t op, Args& args, string& text)
+{
+ switch (op)
+ {
+ case _qmf::Link::METHOD_CLOSE :
+ close();
+ return Manageable::STATUS_OK;
+
+ case _qmf::Link::METHOD_BRIDGE :
+ /* TBD: deprecate this interface in favor of the Broker::create() method. The
+ * Broker::create() method allows the user to assign a name to the bridge.
+ */
+ QPID_LOG(info, "The Link::bridge() method will be removed in a future release of QPID."
+ " Please use the Broker::create() method with type='bridge' instead.");
+ _qmf::ArgsLinkBridge& iargs = (_qmf::ArgsLinkBridge&) args;
+ QPID_LOG(debug, "Link::bridge() request received; src=" << iargs.i_src <<
+ "; dest=" << iargs.i_dest << "; key=" << iargs.i_key);
+
+ // Does a bridge already exist that has the src/dest/key? If so, re-use the
+ // existing bridge - this behavior is backward compatible with previous releases.
+ Bridge::shared_ptr bridge = links->getBridge(*this, iargs.i_src, iargs.i_dest, iargs.i_key);
+ if (!bridge) {
+ // need to create a new bridge on this link.
+ std::pair<Bridge::shared_ptr, bool> rc =
+ links->declare( Bridge::createName(name, iargs.i_src, iargs.i_dest, iargs.i_key),
+ *this, iargs.i_durable,
+ iargs.i_src, iargs.i_dest, iargs.i_key, iargs.i_srcIsQueue,
+ iargs.i_srcIsLocal, iargs.i_tag, iargs.i_excludes,
+ iargs.i_dynamic, iargs.i_sync, iargs.i_credit);
+ if (!rc.first) {
+ text = "invalid parameters";
+ return Manageable::STATUS_PARAMETER_INVALID;
+ }
+ }
+ return Manageable::STATUS_OK;
+ }
+
+ return Manageable::STATUS_UNKNOWN_METHOD;
+}
+
+/** utility to clean up connection resources correctly */
+void Link::closeConnection( const std::string& reason)
+{
+ if (connection != 0) {
+ // cancel our subscription to the failover exchange
+ if (failover) {
+ SessionHandler& sessionHandler = connection->getChannel(failoverChannel);
+ if (sessionHandler.getSession()) {
+ framing::AMQP_ServerProxy remoteBroker(sessionHandler.out);
+ remoteBroker.getMessage().cancel(failoverExchange->getName());
+ remoteBroker.getSession().detach(failoverSession);
+ }
+ }
+ connection->close(CLOSE_CODE_CONNECTION_FORCED, reason);
+ connection = 0;
+ }
+}
+
+/** returns the current remote's address, and connection state */
+bool Link::getRemoteAddress(qpid::Address& addr) const
+{
+ addr.protocol = transport;
+ addr.host = host;
+ addr.port = port;
+
+ return state == STATE_OPERATIONAL;
+}
+
+
+// FieldTable keys for internal state data
+namespace {
+ const std::string FAILOVER_ADDRESSES("failover-addresses");
+ const std::string FAILOVER_INDEX("failover-index");
+}
+
+std::string Link::createName(const std::string& transport,
+ const std::string& host,
+ uint16_t port)
+{
+ stringstream linkName;
+ linkName << QPID_NAME_PREFIX << transport << std::string(":")
+ << host << std::string(":") << port;
+ return linkName.str();
+}
+
+const std::string Link::exchangeTypeName("qpid.LinkExchange");
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/Link.h b/qpid/cpp/src/qpid/broker/Link.h
new file mode 100644
index 0000000000..ceee0186f3
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Link.h
@@ -0,0 +1,205 @@
+#ifndef _broker_Link_h
+#define _broker_Link_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <boost/shared_ptr.hpp>
+#include "qpid/Url.h"
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/broker/PersistableConfig.h"
+#include "qpid/broker/Bridge.h"
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/management/Manageable.h"
+#include "qpid/management/ManagementAgent.h"
+#include "qmf/org/apache/qpid/broker/Link.h"
+#include <boost/ptr_container/ptr_vector.hpp>
+
+namespace qpid {
+
+namespace sys {
+class TimerTask;
+}
+
+namespace broker {
+
+class LinkRegistry;
+class Broker;
+class LinkExchange;
+namespace amqp_0_10 {
+class Connection;
+}
+
+class Link : public PersistableConfig, public management::Manageable {
+ private:
+ mutable sys::Mutex lock;
+ const std::string name;
+ LinkRegistry* links;
+
+ // these remain constant across failover - used to identify this link
+ const std::string configuredTransport;
+ const std::string configuredHost;
+ const uint16_t configuredPort;
+ // these reflect the current address of remote - will change during failover
+ std::string host;
+ uint16_t port;
+ std::string transport;
+
+ bool durable;
+
+ std::string authMechanism;
+ std::string username;
+ std::string password;
+ mutable uint64_t persistenceId;
+ qmf::org::apache::qpid::broker::Link::shared_ptr mgmtObject;
+ Broker* broker;
+ int state;
+ uint32_t visitCount;
+ uint32_t currentInterval;
+ Url url; // URL can contain many addresses.
+ size_t reconnectNext; // Index for next re-connect attempt
+
+ typedef std::vector<Bridge::shared_ptr> Bridges;
+ Bridges created; // Bridges pending creation
+ Bridges active; // Bridges active
+ Bridges cancellations; // Bridges pending cancellation
+ framing::ChannelId nextFreeChannel;
+ RangeSet<framing::ChannelId> freeChannels;
+ amqp_0_10::Connection* connection;
+ management::ManagementAgent* agent;
+ boost::function<void(Link*)> listener;
+ boost::intrusive_ptr<sys::TimerTask> timerTask;
+ boost::shared_ptr<broker::LinkExchange> failoverExchange; // subscribed to remote's amq.failover exchange
+ bool failover; // Do we subscribe to a failover exchange?
+ uint failoverChannel;
+ std::string failoverSession;
+
+ static const int STATE_WAITING = 1;
+ static const int STATE_CONNECTING = 2;
+ static const int STATE_OPERATIONAL = 3;
+ static const int STATE_FAILED = 4;
+ static const int STATE_CLOSED = 5;
+ static const int STATE_CLOSING = 6; // Waiting for outstanding connect to complete first
+
+ static const uint32_t MAX_INTERVAL = 32;
+
+ void setStateLH (int newState);
+ void startConnectionLH(); // Start the IO Connection
+ void destroy(); // Cleanup connection before link goes away
+ void ioThreadProcessing(); // Called on connection's IO thread by request
+ bool tryFailoverLH(); // Called during maintenance visit
+ void reconnectLH(const Address&); //called by LinkRegistry
+
+ // connection management (called by LinkRegistry)
+ void established(amqp_0_10::Connection*); // Called when connection is created
+ void opened(); // Called when connection is open (after create)
+ void closed(int, std::string); // Called when connection goes away
+ void notifyConnectionForced(const std::string text);
+ void closeConnection(const std::string& reason);
+
+ friend class LinkRegistry; // to call established, opened, closed
+
+ public:
+ typedef boost::shared_ptr<Link> shared_ptr;
+ typedef boost::function<void(Link*)> DestroyedListener;
+
+ Link(const std::string& name,
+ LinkRegistry* links,
+ const std::string& host,
+ uint16_t port,
+ const std::string& transport,
+ DestroyedListener l,
+ bool durable,
+ const std::string& authMechanism,
+ const std::string& username,
+ const std::string& password,
+ Broker* broker,
+ management::Manageable* parent = 0,
+ bool failover=true);
+ virtual ~Link();
+
+ /** these return the *configured* transport/host/port, which does not change over the
+ lifetime of the Link */
+ std::string getHost() const { return configuredHost; }
+ uint16_t getPort() const { return configuredPort; }
+ std::string getTransport() const { return configuredTransport; }
+
+ /** returns the current address of the remote, which may be different from the
+ configured transport/host/port due to failover. Returns true if connection is
+ active */
+ QPID_BROKER_EXTERN bool getRemoteAddress(qpid::Address& addr) const;
+
+ bool isDurable() { return durable; }
+ void maintenanceVisit ();
+ QPID_BROKER_EXTERN framing::ChannelId nextChannel(); // allocate channel from link free pool
+ QPID_BROKER_EXTERN void returnChannel(framing::ChannelId); // return channel to link free pool
+ void add(Bridge::shared_ptr);
+ void cancel(Bridge::shared_ptr);
+
+ QPID_BROKER_EXTERN void setUrl(const Url&); // Set URL for reconnection.
+
+ // Close the link.
+ QPID_BROKER_EXTERN void close();
+
+ std::string getAuthMechanism() { return authMechanism; }
+ std::string getUsername() { return username; }
+ std::string getPassword() { return password; }
+ Broker* getBroker() { return broker; }
+
+ bool isConnecting() const { return state == STATE_CONNECTING; }
+
+ // PersistableConfig:
+ void setPersistenceId(uint64_t id) const;
+ uint64_t getPersistenceId() const { return persistenceId; }
+ uint32_t encodedSize() const;
+ void encode(framing::Buffer& buffer) const;
+ const std::string& getName() const;
+
+ static const std::string ENCODED_IDENTIFIER;
+ static const std::string ENCODED_IDENTIFIER_V1;
+ static Link::shared_ptr decode(LinkRegistry& links, framing::Buffer& buffer);
+ static bool isEncodedLink(const std::string& key);
+
+ // Manageable entry points
+ management::ManagementObject::shared_ptr GetManagementObject(void) const;
+ management::Manageable::status_t ManagementMethod(uint32_t, management::Args&, std::string&);
+
+ // manage the exchange owned by this link
+ static const std::string exchangeTypeName;
+ static boost::shared_ptr<Exchange> linkExchangeFactory(const std::string& name);
+
+ /** create a name for a link (if none supplied by user config) */
+ static std::string createName(const std::string& transport,
+ const std::string& host,
+ uint16_t port);
+
+ /** The current connction for this link. Note returns 0 if the link is not
+ * presently connected.
+ */
+ amqp_0_10::Connection* getConnection() { return connection; }
+};
+}
+}
+
+
+#endif /*!_broker_Link.cpp_h*/
diff --git a/qpid/cpp/src/qpid/broker/LinkRegistry.cpp b/qpid/cpp/src/qpid/broker/LinkRegistry.cpp
new file mode 100644
index 0000000000..b3a78b98ca
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/LinkRegistry.cpp
@@ -0,0 +1,435 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/LinkRegistry.h"
+
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/amqp_0_10/Connection.h"
+#include "qpid/broker/Link.h"
+#include "qpid/log/Statement.h"
+#include <iostream>
+#include <boost/format.hpp>
+
+namespace qpid {
+namespace broker {
+
+using namespace qpid::sys;
+using std::string;
+using std::pair;
+using std::stringstream;
+using boost::intrusive_ptr;
+using boost::format;
+using boost::str;
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+// TODO: This constructor is only used by the store unit tests -
+// That probably indicates that LinkRegistry isn't correctly
+// factored: The persistence element should be factored separately
+LinkRegistry::LinkRegistry () :
+ broker(0),
+ parent(0), store(0),
+ realm("")
+{
+}
+
+class LinkRegistryConnectionObserver : public ConnectionObserver {
+ LinkRegistry& links;
+ public:
+ LinkRegistryConnectionObserver(LinkRegistry& l) : links(l) {}
+ void connection(Connection& in)
+ {
+ amqp_0_10::Connection* c = dynamic_cast<amqp_0_10::Connection*>(&in);
+ if (c) links.notifyConnection(c->getMgmtId(), c);
+ }
+ void opened(Connection& in)
+ {
+ amqp_0_10::Connection* c = dynamic_cast<amqp_0_10::Connection*>(&in);
+ if (c) links.notifyOpened(c->getMgmtId());
+ }
+ void closed(Connection& in)
+ {
+ amqp_0_10::Connection* c = dynamic_cast<amqp_0_10::Connection*>(&in);
+ if (c) links.notifyClosed(c->getMgmtId());
+ }
+ void forced(Connection& in, const string& text)
+ {
+ amqp_0_10::Connection* c = dynamic_cast<amqp_0_10::Connection*>(&in);
+ if (c) links.notifyConnectionForced(c->getMgmtId(), text);
+ }
+};
+
+LinkRegistry::LinkRegistry (Broker* _broker) :
+ broker(_broker),
+ parent(0), store(0),
+ realm(broker->getRealm())
+{
+ broker->getConnectionObservers().add(
+ boost::shared_ptr<ConnectionObserver>(new LinkRegistryConnectionObserver(*this)));
+}
+
+LinkRegistry::~LinkRegistry() {}
+
+/** find link by the *configured* remote address */
+boost::shared_ptr<Link> LinkRegistry::getLink(const std::string& host,
+ uint16_t port,
+ const std::string& transport)
+{
+ Mutex::ScopedLock locker(lock);
+ for (LinkMap::iterator i = links.begin(); i != links.end(); ++i) {
+ Link::shared_ptr& link = i->second;
+ if (link->getHost() == host &&
+ link->getPort() == port &&
+ (transport.empty() || link->getTransport() == transport))
+ return link;
+ }
+ return boost::shared_ptr<Link>();
+}
+
+/** find link by name */
+boost::shared_ptr<Link> LinkRegistry::getLink(const std::string& name)
+{
+ Mutex::ScopedLock locker(lock);
+ LinkMap::iterator l = links.find(name);
+ if (l != links.end())
+ return l->second;
+ return boost::shared_ptr<Link>();
+}
+
+pair<Link::shared_ptr, bool> LinkRegistry::declare(const string& name,
+ const string& host,
+ uint16_t port,
+ const string& transport,
+ bool durable,
+ const string& authMechanism,
+ const string& username,
+ const string& password,
+ bool failover)
+
+{
+ Mutex::ScopedLock locker(lock);
+
+ LinkMap::iterator i = links.find(name);
+ if (i == links.end())
+ {
+ Link::shared_ptr link;
+
+ link = Link::shared_ptr (
+ new Link (name, this, host, port, transport,
+ boost::bind(&LinkRegistry::linkDestroyed, this, _1),
+ durable, authMechanism, username, password, broker,
+ parent, failover));
+ if (durable && store && !broker->inRecovery())
+ store->create(*link);
+ links[name] = link;
+ pendingLinks[name] = link;
+ QPID_LOG(debug, "Creating new link; name=" << name );
+ return std::pair<Link::shared_ptr, bool>(link, true);
+ }
+ return std::pair<Link::shared_ptr, bool>(i->second, false);
+}
+
+/** find bridge by link & route info */
+Bridge::shared_ptr LinkRegistry::getBridge(const Link& link,
+ const std::string& src,
+ const std::string& dest,
+ const std::string& key)
+{
+ Mutex::ScopedLock locker(lock);
+ for (BridgeMap::iterator i = bridges.begin(); i != bridges.end(); ++i) {
+ if (i->second->getSrc() == src && i->second->getDest() == dest &&
+ i->second->getKey() == key && i->second->getLink() &&
+ i->second->getLink()->getName() == link.getName()) {
+ return i->second;
+ }
+ }
+ return Bridge::shared_ptr();
+}
+
+/** find bridge by name */
+Bridge::shared_ptr LinkRegistry::getBridge(const std::string& name)
+{
+ Mutex::ScopedLock locker(lock);
+ BridgeMap::iterator b = bridges.find(name);
+ if (b != bridges.end())
+ return b->second;
+ return Bridge::shared_ptr();
+}
+
+pair<Bridge::shared_ptr, bool> LinkRegistry::declare(const std::string& name,
+ Link& link,
+ bool durable,
+ const std::string& src,
+ const std::string& dest,
+ const std::string& key,
+ bool isQueue,
+ bool isLocal,
+ const std::string& tag,
+ const std::string& excludes,
+ bool dynamic,
+ uint16_t sync,
+ uint32_t credit,
+ Bridge::InitializeCallback init,
+ const std::string& queueName,
+ const std::string& altExchange
+)
+{
+ Mutex::ScopedLock locker(lock);
+
+ // Durable bridges are only valid on durable links
+ if (durable && !link.isDurable()) {
+ QPID_LOG(error, "Can't create a durable route '" << name << "' on a non-durable link '" << link.getName());
+ return pair<Bridge::shared_ptr, bool>(Bridge::shared_ptr(), false);
+ }
+
+ if (dynamic) {
+ Exchange::shared_ptr exchange = broker->getExchanges().get(src);
+ if (exchange.get() == 0) {
+ QPID_LOG(error, "Exchange not found, name='" << src << "'" );
+ return pair<Bridge::shared_ptr, bool>(Bridge::shared_ptr(), false);
+ }
+ if (!exchange->supportsDynamicBinding()) {
+ QPID_LOG(error, "Exchange type does not support dynamic routing, name='" << src << "'");
+ return pair<Bridge::shared_ptr, bool>(Bridge::shared_ptr(), false);
+ }
+ }
+
+ BridgeMap::iterator b = bridges.find(name);
+ if (b == bridges.end())
+ {
+ _qmf::ArgsLinkBridge args;
+ Bridge::shared_ptr bridge;
+
+ args.i_durable = durable;
+ args.i_src = src;
+ args.i_dest = dest;
+ args.i_key = key;
+ args.i_srcIsQueue = isQueue;
+ args.i_srcIsLocal = isLocal;
+ args.i_tag = tag;
+ args.i_excludes = excludes;
+ args.i_dynamic = dynamic;
+ args.i_sync = sync;
+ args.i_credit = credit;
+
+ bridge = Bridge::shared_ptr
+ (new Bridge (name, &link, link.nextChannel(),
+ boost::bind(&LinkRegistry::destroyBridge, this, _1),
+ args, init, queueName, altExchange));
+ bridges[name] = bridge;
+ link.add(bridge);
+ if (durable && store && !broker->inRecovery())
+ store->create(*bridge);
+
+ QPID_LOG(debug, "Bridge '" << name <<"' declared on link '" << link.getName() <<
+ "' from " << src << " to " << dest << " (" << key << ")");
+
+ return std::pair<Bridge::shared_ptr, bool>(bridge, true);
+ }
+ return std::pair<Bridge::shared_ptr, bool>(b->second, false);
+}
+
+/** called back by the link when it has completed its cleanup and can be removed. */
+void LinkRegistry::linkDestroyed(Link *link)
+{
+ QPID_LOG(debug, "LinkRegistry::destroy(); link= " << link->getName());
+ Mutex::ScopedLock locker(lock);
+
+ pendingLinks.erase(link->getName());
+ LinkMap::iterator i = links.find(link->getName());
+ if (i != links.end())
+ {
+ if (i->second->isDurable() && store)
+ store->destroy(*(i->second));
+ links.erase(i);
+ }
+}
+
+/** called back by bridge when its destruction has been requested */
+void LinkRegistry::destroyBridge(Bridge *bridge)
+{
+ QPID_LOG(debug, "LinkRegistry::destroy(); bridge= " << bridge->getName());
+ Mutex::ScopedLock locker(lock);
+
+ BridgeMap::iterator b = bridges.find(bridge->getName());
+ if (b == bridges.end())
+ return;
+
+ Link *link = b->second->getLink();
+ if (link) {
+ link->cancel(b->second);
+ link->returnChannel( bridge->getChannel() );
+ }
+ if (b->second->isDurable())
+ store->destroy(*(b->second));
+ bridges.erase(b);
+}
+
+void LinkRegistry::setStore (MessageStore* _store)
+{
+ store = _store;
+}
+
+MessageStore* LinkRegistry::getStore() const {
+ return store;
+}
+
+/** find the Link that corresponds to the given connection */
+Link::shared_ptr LinkRegistry::findLink(const std::string& connId)
+{
+ Mutex::ScopedLock locker(lock);
+ ConnectionMap::iterator c = connections.find(connId);
+ if (c != connections.end()) {
+ LinkMap::iterator l = links.find(c->second);
+ if (l != links.end())
+ return l->second;
+ }
+ return Link::shared_ptr();
+}
+
+void LinkRegistry::notifyConnection(const std::string& key, amqp_0_10::Connection* c)
+{
+ // find a link that is attempting to connect to the remote, and
+ // create a mapping from connection id to link
+ QPID_LOG(debug, "LinkRegistry::notifyConnection(); key=" << key );
+ std::string host;
+ Link::shared_ptr link;
+ {
+ Mutex::ScopedLock locker(lock);
+ LinkMap::iterator l = pendingLinks.find(key);
+ if (l != pendingLinks.end()) {
+ link = l->second;
+ pendingLinks.erase(l);
+ connections[key] = link->getName();
+ QPID_LOG(debug, "LinkRegistry:: found pending =" << link->getName());
+ }
+ }
+
+ if (link) {
+ link->established(c);
+ c->setUserId(str(format("%1%@%2%") % link->getUsername() % realm));
+ }
+}
+
+void LinkRegistry::notifyOpened(const std::string& key)
+{
+ Link::shared_ptr link = findLink(key);
+ if (link) link->opened();
+}
+
+void LinkRegistry::notifyClosed(const std::string& key)
+{
+ Link::shared_ptr link = findLink(key);
+ if (link) {
+ {
+ Mutex::ScopedLock locker(lock);
+ pendingLinks[link->getName()] = link;
+ }
+ link->closed(0, "Closed by peer");
+ }
+}
+
+void LinkRegistry::notifyConnectionForced(const std::string& key, const std::string& text)
+{
+ Link::shared_ptr link = findLink(key);
+ if (link) {
+ {
+ Mutex::ScopedLock locker(lock);
+ pendingLinks[link->getName()] = link;
+ }
+ link->notifyConnectionForced(text);
+ }
+}
+
+std::string LinkRegistry::getAuthMechanism(const std::string& key)
+{
+ Link::shared_ptr link = findLink(key);
+ if (link)
+ return link->getAuthMechanism();
+ return string("ANONYMOUS");
+}
+
+std::string LinkRegistry::getAuthCredentials(const std::string& key)
+{
+ Link::shared_ptr link = findLink(key);
+ if (!link)
+ return string();
+
+ string result;
+ result += '\0';
+ result += link->getUsername();
+ result += '\0';
+ result += link->getPassword();
+
+ return result;
+}
+
+std::string LinkRegistry::getUsername(const std::string& key)
+{
+ Link::shared_ptr link = findLink(key);
+ if (!link)
+ return string();
+
+ return link->getUsername();
+}
+
+/** note: returns the current remote host (may be different from the host originally
+ configured for the Link due to failover) */
+std::string LinkRegistry::getHost(const std::string& key)
+{
+ Link::shared_ptr link = findLink(key);
+ if (!link)
+ return string();
+
+ qpid::Address addr;
+ link->getRemoteAddress(addr);
+ return addr.host;
+}
+
+/** returns the current remote port (ditto above) */
+uint16_t LinkRegistry::getPort(const std::string& key)
+{
+ Link::shared_ptr link = findLink(key);
+ if (!link)
+ return 0;
+
+ qpid::Address addr;
+ link->getRemoteAddress(addr);
+ return addr.port;
+}
+
+std::string LinkRegistry::getPassword(const std::string& key)
+{
+ Link::shared_ptr link = findLink(key);
+ if (!link)
+ return string();
+
+ return link->getPassword();
+}
+
+std::string LinkRegistry::getAuthIdentity(const std::string& key)
+{
+ Link::shared_ptr link = findLink(key);
+ if (!link)
+ return string();
+
+ return link->getUsername();
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/LinkRegistry.h b/qpid/cpp/src/qpid/broker/LinkRegistry.h
new file mode 100644
index 0000000000..a156a53624
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/LinkRegistry.h
@@ -0,0 +1,155 @@
+#ifndef _broker_LinkRegistry_h
+#define _broker_LinkRegistry_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <map>
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/broker/Bridge.h"
+#include "qpid/broker/MessageStore.h"
+#include "qpid/Address.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/management/Manageable.h"
+#include <boost/shared_ptr.hpp>
+#include <boost/intrusive_ptr.hpp>
+#include <boost/function.hpp>
+
+namespace qpid {
+namespace broker {
+namespace amqp_0_10 {
+ class Connection;
+}
+ class Link;
+ class Broker;
+ class LinkRegistry {
+ typedef std::map<std::string, boost::shared_ptr<Link> > LinkMap;
+ typedef std::map<std::string, Bridge::shared_ptr> BridgeMap;
+ typedef std::map<std::string, std::string> ConnectionMap;
+
+ LinkMap links; /** indexed by name of Link */
+ BridgeMap bridges; /** indexed by name of Bridge */
+ ConnectionMap connections; /** indexed by connection identifier, gives link name */
+ LinkMap pendingLinks; /** pending connection, indexed by name of Link */
+
+ qpid::sys::Mutex lock;
+ Broker* broker;
+ management::Manageable* parent;
+ MessageStore* store;
+ std::string realm;
+
+ boost::shared_ptr<Link> findLink(const std::string& key);
+
+ // Methods called by the connection observer, key is connection identifier
+ void notifyConnection (const std::string& key, amqp_0_10::Connection* c);
+ void notifyOpened (const std::string& key);
+ void notifyClosed (const std::string& key);
+ void notifyConnectionForced (const std::string& key, const std::string& text);
+ friend class LinkRegistryConnectionObserver;
+
+ /** Notify the registry that a Link has been destroyed */
+ void linkDestroyed(Link*);
+ /** Request to destroy a Bridge */
+ void destroyBridge(Bridge*);
+
+ public:
+ QPID_BROKER_EXTERN LinkRegistry (); // Only used in store tests
+ QPID_BROKER_EXTERN LinkRegistry (Broker* _broker);
+ QPID_BROKER_EXTERN ~LinkRegistry();
+
+ QPID_BROKER_EXTERN std::pair<boost::shared_ptr<Link>, bool>
+ declare(const std::string& name,
+ const std::string& host,
+ uint16_t port,
+ const std::string& transport,
+ bool durable,
+ const std::string& authMechanism,
+ const std::string& username,
+ const std::string& password,
+ bool failover=true);
+
+ /** determine if Link exists */
+ QPID_BROKER_EXTERN boost::shared_ptr<Link>
+ getLink(const std::string& name);
+ /** host,port,transport will be matched against the configured values, which may
+ be different from the current values due to failover */
+ QPID_BROKER_EXTERN boost::shared_ptr<Link>
+ getLink(const std::string& configHost,
+ uint16_t configPort,
+ const std::string& configTransport = std::string());
+
+ QPID_BROKER_EXTERN std::pair<Bridge::shared_ptr, bool>
+ declare(const std::string& name,
+ Link& link,
+ bool durable,
+ const std::string& src,
+ const std::string& dest,
+ const std::string& key,
+ bool isQueue,
+ bool isLocal,
+ const std::string& id,
+ const std::string& excludes,
+ bool dynamic,
+ uint16_t sync,
+ uint32_t credit,
+ Bridge::InitializeCallback=0,
+ const std::string& queueName="",
+ const std::string& altExchange=""
+ );
+ QPID_BROKER_EXTERN static const uint32_t INFINITE_CREDIT = 0xFFFFFFFF;
+
+ /** determine if Bridge exists */
+ QPID_BROKER_EXTERN Bridge::shared_ptr
+ getBridge(const std::string& name);
+ QPID_BROKER_EXTERN Bridge::shared_ptr
+ getBridge(const Link& link,
+ const std::string& src,
+ const std::string& dest,
+ const std::string& key);
+
+ /**
+ * Register the manageable parent for declared queues
+ */
+ void setParent (management::Manageable* _parent) { parent = _parent; }
+
+ /**
+ * Set the store to use. May only be called once.
+ */
+ QPID_BROKER_EXTERN void setStore (MessageStore*);
+
+ /**
+ * Return the message store used.
+ */
+ QPID_BROKER_EXTERN MessageStore* getStore() const;
+
+ QPID_BROKER_EXTERN std::string getAuthMechanism (const std::string& key);
+ QPID_BROKER_EXTERN std::string getAuthCredentials (const std::string& key);
+ QPID_BROKER_EXTERN std::string getAuthIdentity (const std::string& key);
+ QPID_BROKER_EXTERN std::string getUsername (const std::string& key);
+ QPID_BROKER_EXTERN std::string getPassword (const std::string& key);
+ QPID_BROKER_EXTERN std::string getHost (const std::string& key);
+ QPID_BROKER_EXTERN uint16_t getPort (const std::string& key);
+ };
+}
+}
+
+
+#endif /*!_broker_LinkRegistry_h*/
diff --git a/qpid/cpp/src/qpid/broker/LossyLvq.cpp b/qpid/cpp/src/qpid/broker/LossyLvq.cpp
new file mode 100644
index 0000000000..f59ecc1925
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/LossyLvq.cpp
@@ -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.
+ *
+ */
+#include "LossyLvq.h"
+#include "MessageMap.h"
+
+namespace qpid {
+namespace broker {
+
+LossyLvq::LossyLvq(const std::string& n, std::auto_ptr<MessageMap> m, const QueueSettings& s, MessageStore* const ms, management::Manageable* p, Broker* b)
+ : Queue(n, s, ms, p, b), Lvq(n, m, s, ms, p, b), LossyQueue(n, s, ms, p, b) {}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/LossyLvq.h b/qpid/cpp/src/qpid/broker/LossyLvq.h
new file mode 100644
index 0000000000..2665ebe49f
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/LossyLvq.h
@@ -0,0 +1,52 @@
+#ifndef QPID_BROKER_LOSSYLVQ_H
+#define QPID_BROKER_LOSSYLVQ_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/Lvq.h"
+#include "qpid/broker/LossyQueue.h"
+
+namespace qpid {
+namespace broker {
+class MessageMap;
+
+// Disable inherited-by-dominance warning on MSVC. We know. It's ok.
+#ifdef _MSC_VER
+# pragma warning(push)
+# pragma warning(disable : 4250)
+#endif
+
+/**
+ * Combination of LossyQueue and Lvq behaviours.
+ */
+class LossyLvq : public Lvq, public LossyQueue
+{
+ public:
+ LossyLvq(const std::string&, std::auto_ptr<MessageMap>, const QueueSettings&, MessageStore* const, management::Manageable*, Broker*);
+};
+
+#ifdef _MSC_VER
+# pragma warning(pop)
+#endif
+
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_LOSSYLVQ_H*/
diff --git a/qpid/cpp/src/qpid/broker/LossyQueue.cpp b/qpid/cpp/src/qpid/broker/LossyQueue.cpp
new file mode 100644
index 0000000000..ee13d7733a
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/LossyQueue.cpp
@@ -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.
+ *
+ */
+#include "LossyQueue.h"
+#include "QueueDepth.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace broker {
+
+namespace {
+bool isLowerPriorityThan(uint8_t priority, const Message& m)
+{
+ return m.getPriority() <= priority;
+}
+}
+
+LossyQueue::LossyQueue(const std::string& n, const QueueSettings& s, MessageStore* const ms, management::Manageable* p, Broker* b)
+ : Queue(n, s, ms, p, b) {}
+
+bool LossyQueue::checkDepth(const QueueDepth& increment, const Message& message)
+{
+ if (settings.maxDepth.hasSize() && increment.getSize() > settings.maxDepth.getSize()) {
+ if (mgmtObject) {
+ mgmtObject->inc_discardsOverflow();
+ if (brokerMgmtObject)
+ brokerMgmtObject->inc_discardsOverflow();
+ }
+ throw qpid::framing::ResourceLimitExceededException(QPID_MSG("Message larger than configured maximum depth on "
+ << name << ": size=" << increment.getSize() << ", max-size=" << settings.maxDepth.getSize()));
+ }
+
+ while (settings.maxDepth && (settings.maxDepth - current < increment)) {
+ QPID_LOG(debug, "purging " << name << ": current depth is [" << current << "], max depth is [" << settings.maxDepth << "], new message has size " << increment.getSize());
+ qpid::sys::Mutex::ScopedUnlock u(messageLock);
+ //TODO: arguably we should try and purge expired messages first but that
+ //is potentially expensive
+
+ // Note: in the case of a priority queue we are only comparing the new mesage
+ // with single lowest-priority message, hence the final parameter maxTests
+ // is 1 in this case, so we only test one message for removal.
+ if (remove(1,
+ settings.priorities ?
+ boost::bind(&isLowerPriorityThan, message.getPriority(), _1) :
+ MessagePredicate(), boost::bind(&reroute, alternateExchange, _1),
+ PURGE, false,
+ settings.priorities ? 1 : 0))
+ {
+ if (mgmtObject) {
+ mgmtObject->inc_discardsRing(1);
+ if (brokerMgmtObject)
+ brokerMgmtObject->inc_discardsRing(1);
+ }
+ } else {
+ //should only be the case for a non-empty queue if we are
+ //testing priority and there was no lower (or equal)
+ //priority message available to purge
+ break;
+ }
+ }
+ if (settings.maxDepth && (settings.maxDepth - current < increment)) {
+ //will only be the case where we were unable to purge another
+ //message, which should only be the case if we are purging
+ //based on priority and there was no message with a lower (or
+ //equal) priority than this one, meaning that we drop this
+ //current message
+ if (mgmtObject) {
+ mgmtObject->inc_discardsRing(1);
+ if (brokerMgmtObject)
+ brokerMgmtObject->inc_discardsRing(1);
+ }
+ return false;
+ } else {
+ //have sufficient space for this message
+ current += increment;
+ return true;
+ }
+}
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/LossyQueue.h b/qpid/cpp/src/qpid/broker/LossyQueue.h
new file mode 100644
index 0000000000..705865f449
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/LossyQueue.h
@@ -0,0 +1,41 @@
+#ifndef QPID_BROKER_LOSSYQUEUE_H
+#define QPID_BROKER_LOSSYQUEUE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/Queue.h"
+
+namespace qpid {
+namespace broker {
+
+/**
+ * Drops messages to prevent a breach of any configured maximum depth.
+ */
+class LossyQueue : public virtual Queue
+{
+ public:
+ LossyQueue(const std::string&, const QueueSettings&, MessageStore* const, management::Manageable*, Broker*);
+ bool checkDepth(const QueueDepth& increment, const Message&);
+ private:
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_LOSSYQUEUE_H*/
diff --git a/qpid/cpp/src/qpid/broker/Lvq.cpp b/qpid/cpp/src/qpid/broker/Lvq.cpp
new file mode 100644
index 0000000000..34f080c57f
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Lvq.cpp
@@ -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.
+ *
+ */
+#include "Lvq.h"
+#include "MessageMap.h"
+#include "qpid/sys/Monitor.h"
+
+namespace qpid {
+namespace broker {
+Lvq::Lvq(const std::string& n, std::auto_ptr<MessageMap> m, const QueueSettings& s, MessageStore* const ms, management::Manageable* p, Broker* b)
+ : Queue(n, s, ms, p, b), messageMap(*m)
+{
+ messages = m;
+}
+
+void Lvq::push(Message& message, bool isRecovery)
+{
+ QueueListeners::NotificationSet copy;
+ Message old;
+ bool removed;
+ {
+ qpid::sys::Mutex::ScopedLock locker(messageLock);
+ message.setSequence(++sequence);
+ interceptors.publish(message);
+ removed = messageMap.update(message, old);
+ listeners.populate(copy);
+ observeEnqueue(message, locker);
+ if (removed) {
+ if (mgmtObject) {
+ mgmtObject->inc_acquires();
+ mgmtObject->inc_discardsLvq();
+ if (brokerMgmtObject) {
+ brokerMgmtObject->inc_acquires();
+ brokerMgmtObject->inc_discardsLvq();
+ }
+ }
+ observeDequeue(old, locker, 0/*can't be empty, so no need to check autodelete*/);
+ }
+ }
+ copy.notify();
+ if (removed) {
+ if (isRecovery) pendingDequeues.push_back(old);
+ else if (old.isPersistent())
+ dequeueFromStore(old.getPersistentContext());//do outside of lock
+ }
+}
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/Lvq.h b/qpid/cpp/src/qpid/broker/Lvq.h
new file mode 100644
index 0000000000..26ba2b4914
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Lvq.h
@@ -0,0 +1,45 @@
+#ifndef QPID_BROKER_LVQ_H
+#define QPID_BROKER_LVQ_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/Queue.h"
+
+namespace qpid {
+namespace broker {
+class MessageMap;
+
+/**
+ * Subclass of queue that handles last-value-queue semantics in
+ * conjunction with the MessageMap class. This requires an existing
+ * message to be 'replaced' by a newer message with the same key.
+ */
+class Lvq : public virtual Queue
+{
+ public:
+ Lvq(const std::string&, std::auto_ptr<MessageMap>, const QueueSettings&, MessageStore* const, management::Manageable*, Broker*);
+ void push(Message& msg, bool isRecovery=false);
+ private:
+ MessageMap& messageMap;
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_LVQ_H*/
diff --git a/qpid/cpp/src/qpid/broker/Message.cpp b/qpid/cpp/src/qpid/broker/Message.cpp
new file mode 100644
index 0000000000..250acf6b4e
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Message.cpp
@@ -0,0 +1,368 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/Message.h"
+
+#include "qpid/amqp/CharSequence.h"
+#include "qpid/amqp/MapHandler.h"
+#include "qpid/broker/Connection.h"
+#include "qpid/broker/OwnershipToken.h"
+#include "qpid/management/ManagementObject.h"
+#include "qpid/management/Manageable.h"
+#include "qpid/StringUtils.h"
+#include "qpid/log/Statement.h"
+#include "qpid/assert.h"
+
+#include <algorithm>
+#include <string.h>
+#include <time.h>
+
+using boost::intrusive_ptr;
+using qpid::sys::AbsTime;
+using qpid::sys::Duration;
+using qpid::sys::TIME_MSEC;
+using qpid::sys::FAR_FUTURE;
+using qpid::amqp::CharSequence;
+using qpid::amqp::MapHandler;
+using std::string;
+
+namespace qpid {
+namespace broker {
+
+Message::Message() : deliveryCount(-1), alreadyAcquired(false), replicationId(0), isReplicationIdSet(false)
+{}
+
+Message::Message(boost::intrusive_ptr<SharedState> e, boost::intrusive_ptr<PersistableMessage> p)
+ : sharedState(e), persistentContext(p), deliveryCount(-1), alreadyAcquired(false), replicationId(0), isReplicationIdSet(false)
+{
+ if (persistentContext) persistentContext->setIngressCompletion(e);
+}
+
+Message::~Message() {}
+
+
+std::string Message::getRoutingKey() const
+{
+ return getEncoding().getRoutingKey();
+}
+
+bool Message::isPersistent() const
+{
+ return getEncoding().isPersistent();
+}
+
+uint64_t Message::getMessageSize() const
+{
+ return getEncoding().getMessageSize();
+}
+
+boost::intrusive_ptr<AsyncCompletion> Message::getIngressCompletion() const
+{
+ return sharedState;
+}
+
+namespace
+{
+const std::string X_QPID_TRACE("x-qpid.trace");
+}
+
+bool Message::isExcluded(const std::vector<std::string>& excludes) const
+{
+ std::string traceStr = getEncoding().getAnnotationAsString(X_QPID_TRACE);
+ if (traceStr.size()) {
+ std::vector<std::string> trace = split(traceStr, ", ");
+ for (std::vector<std::string>::const_iterator i = excludes.begin(); i != excludes.end(); i++) {
+ for (std::vector<std::string>::const_iterator j = trace.begin(); j != trace.end(); j++) {
+ if (*i == *j) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+void Message::addTraceId(const std::string& id)
+{
+ std::string trace = getEncoding().getAnnotationAsString(X_QPID_TRACE);
+ if (trace.empty()) {
+ addAnnotation(X_QPID_TRACE, id);
+ } else if (trace.find(id) == std::string::npos) {
+ trace += ",";
+ trace += id;
+ addAnnotation(X_QPID_TRACE, trace);
+ }
+}
+
+void Message::clearTrace()
+{
+ addAnnotation(X_QPID_TRACE, std::string());
+}
+
+uint64_t Message::getTimestamp() const
+{
+ return sharedState ? sharedState->getTimestamp() : 0;
+}
+
+uint64_t Message::getTtl() const
+{
+ uint64_t ttl;
+ if (getTtl(ttl, 1)/*set to 1 if expired*/) {
+ return ttl;
+ } else {
+ return 0;
+ }
+}
+
+bool Message::getTtl(uint64_t& ttl) const
+{
+ return getTtl(ttl, 0); //set to 0 if expired
+}
+
+bool Message::getTtl(uint64_t& ttl, uint64_t expiredValue) const
+{
+ if (sharedState->getTtl(ttl) && sharedState->getExpiration() < FAR_FUTURE) {
+ sys::Duration remaining = sharedState->getTimeToExpiration();
+ // convert from ns to ms
+ ttl = (int64_t(remaining) >= 1000000 ? int64_t(remaining)/1000000 : expiredValue);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void Message::addAnnotation(const std::string& key, const qpid::types::Variant& value)
+{
+ annotations.get()[key] = value;
+ annotationsChanged();
+}
+
+void Message::annotationsChanged()
+{
+ if (persistentContext) {
+ uint64_t id = persistentContext->getPersistenceId();
+ persistentContext = persistentContext->merge(getAnnotations());
+ persistentContext->setIngressCompletion(sharedState);
+ persistentContext->setPersistenceId(id);
+ }
+}
+
+uint8_t Message::getPriority() const
+{
+ return getEncoding().getPriority();
+}
+
+bool Message::getIsManagementMessage() const { return sharedState->getIsManagementMessage(); }
+
+const Connection* Message::getPublisher() const { return sharedState->getPublisher(); }
+bool Message::isLocalTo(const OwnershipToken* token) const {
+ return token && sharedState->getPublisher() && token->isLocal(sharedState->getPublisher());
+}
+
+
+qpid::framing::SequenceNumber Message::getSequence() const
+{
+ return sequence;
+}
+void Message::setSequence(const qpid::framing::SequenceNumber& s)
+{
+ sequence = s;
+}
+
+MessageState Message::getState() const
+{
+ return state;
+}
+void Message::setState(MessageState s)
+{
+ state = s;
+}
+namespace {
+const qpid::types::Variant::Map EMPTY_MAP;
+}
+
+const qpid::types::Variant::Map& Message::getAnnotations() const
+{
+ return annotations ? *annotations : EMPTY_MAP;
+}
+
+qpid::types::Variant Message::getAnnotation(const std::string& key) const
+{
+ const qpid::types::Variant::Map& a = getAnnotations();
+ qpid::types::Variant::Map::const_iterator i = a.find(key);
+ if (i != a.end()) return i->second;
+ //FIXME: modify Encoding interface to allow retrieval of
+ //annotations of different types from the message data as received
+ //off the wire
+ return qpid::types::Variant(getEncoding().getAnnotationAsString(key));
+}
+
+std::string Message::getUserId() const
+{
+ return sharedState->getUserId();
+}
+
+Message::SharedState& Message::getSharedState()
+{
+ return *sharedState;
+}
+const Message::Encoding& Message::getEncoding() const
+{
+ return *sharedState;
+}
+Message::operator bool() const
+{
+ return !!sharedState;
+}
+
+std::string Message::getContent() const
+{
+ return sharedState->getContent();
+}
+
+std::string Message::getPropertyAsString(const std::string& key) const
+{
+ return sharedState->getPropertyAsString(key);
+}
+namespace {
+class PropertyRetriever : public MapHandler
+{
+ public:
+ PropertyRetriever(const std::string& key) : name(key) {}
+ void handleVoid(const CharSequence&) {}
+ void handleBool(const CharSequence& key, bool value) { handle(key, value); }
+ void handleUint8(const CharSequence& key, uint8_t value) { handle(key, value); }
+ void handleUint16(const CharSequence& key, uint16_t value) { handle(key, value); }
+ void handleUint32(const CharSequence& key, uint32_t value) { handle(key, value); }
+ void handleUint64(const CharSequence& key, uint64_t value) { handle(key, value); }
+ void handleInt8(const CharSequence& key, int8_t value) { handle(key, value); }
+ void handleInt16(const CharSequence& key, int16_t value) { handle(key, value); }
+ void handleInt32(const CharSequence& key, int32_t value) { handle(key, value); }
+ void handleInt64(const CharSequence& key, int64_t value) { handle(key, value); }
+ void handleFloat(const CharSequence& key, float value) { handle(key, value); }
+ void handleDouble(const CharSequence& key, double value) { handle(key, value); }
+ void handleString(const CharSequence& key, const CharSequence& value, const CharSequence& /*encoding*/)
+ {
+ if (matches(key)) result = std::string(value.data, value.size);
+ }
+ qpid::types::Variant getResult() { return result; }
+
+ private:
+ std::string name;
+ qpid::types::Variant result;
+
+ bool matches(const CharSequence& key)
+ {
+ return name.size()==key.size &&
+ ::strncmp(key.data, name.data(), key.size) == 0;
+ }
+
+ template <typename T> void handle(const CharSequence& key, T value)
+ {
+ if (matches(key)) result = value;
+ }
+};
+}
+qpid::types::Variant Message::getProperty(const std::string& key) const
+{
+ PropertyRetriever r(key);
+ sharedState->processProperties(r);
+ return r.getResult();
+}
+
+boost::intrusive_ptr<PersistableMessage> Message::getPersistentContext() const
+{
+ return persistentContext;
+}
+
+void Message::processProperties(MapHandler& handler) const
+{
+ sharedState->processProperties(handler);
+}
+
+bool Message::hasReplicationId() const {
+ return isReplicationIdSet;
+}
+
+uint64_t Message::getReplicationId() const {
+ return replicationId;
+}
+
+void Message::setReplicationId(framing::SequenceNumber id) {
+ replicationId = id;
+ isReplicationIdSet = true;
+}
+
+sys::AbsTime Message::getExpiration() const
+{
+ return sharedState->getExpiration();
+}
+
+Message::SharedStateImpl::SharedStateImpl() : publisher(0), expiration(qpid::sys::FAR_FUTURE), isManagementMessage(false) {}
+
+const Connection* Message::SharedStateImpl::getPublisher() const
+{
+ return publisher;
+}
+
+void Message::SharedStateImpl::setPublisher(const Connection* p)
+{
+ publisher = p;
+}
+
+sys::AbsTime Message::SharedStateImpl::getExpiration() const
+{
+ return expiration;
+}
+
+void Message::SharedStateImpl::setExpiration(sys::AbsTime e)
+{
+ expiration = e;
+}
+
+sys::Duration Message::SharedStateImpl::getTimeToExpiration() const
+{
+ return sys::Duration(sys::AbsTime::now(), expiration);
+}
+
+void Message::SharedStateImpl::computeExpiration()
+{
+ //TODO: this is still quite 0-10 specific...
+ uint64_t ttl;
+ if (getTtl(ttl)) {
+ // Use higher resolution time for the internal expiry calculation.
+ // Prevent overflow as a signed int64_t
+ Duration duration(std::min(ttl * TIME_MSEC,
+ (uint64_t) std::numeric_limits<int64_t>::max()));
+ expiration = AbsTime(sys::AbsTime::now(), duration);
+ }
+}
+
+bool Message::SharedStateImpl::getIsManagementMessage() const
+{
+ return isManagementMessage;
+}
+void Message::SharedStateImpl::setIsManagementMessage(bool b)
+{
+ isManagementMessage = b;
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/Message.h b/qpid/cpp/src/qpid/broker/Message.h
new file mode 100644
index 0000000000..9843bc6220
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Message.h
@@ -0,0 +1,227 @@
+#ifndef _broker_Message_h
+#define _broker_Message_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/RefCounted.h"
+#include "qpid/broker/PersistableMessage.h"
+//TODO: move the following out of framing or replace it
+#include "qpid/framing/SequenceNumber.h"
+#include "qpid/sys/Time.h"
+#include "qpid/types/Variant.h"
+
+#include "qpid/broker/BrokerImportExport.h"
+
+#include <string>
+#include <vector>
+#include <boost/intrusive_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+
+namespace qpid {
+namespace amqp {
+class MapHandler;
+struct MessageId;
+}
+
+namespace management {
+class ObjectId;
+class Manageable;
+}
+
+namespace broker {
+class OwnershipToken;
+class Connection;
+
+enum MessageState
+{
+ AVAILABLE=1,
+ ACQUIRED=2,
+ DELETED=4,
+ UNAVAILABLE=8
+};
+
+class Message {
+public:
+ class Encoding : public IngressCompletion
+ {
+ public:
+ virtual ~Encoding() {}
+ virtual std::string getRoutingKey() const = 0;
+ virtual bool isPersistent() const = 0;
+ virtual uint8_t getPriority() const = 0;
+ virtual uint64_t getMessageSize() const = 0;
+ virtual qpid::amqp::MessageId getMessageId() const = 0;
+ virtual qpid::amqp::MessageId getCorrelationId() const = 0;
+ virtual std::string getPropertyAsString(const std::string& key) const = 0;
+ virtual std::string getAnnotationAsString(const std::string& key) const = 0;
+ virtual bool getTtl(uint64_t&) const = 0;
+ virtual std::string getContent() const = 0;
+ virtual void processProperties(qpid::amqp::MapHandler&) const = 0;
+ virtual std::string getUserId() const = 0;
+ virtual uint64_t getTimestamp() const = 0;
+ };
+
+ class SharedState : public Encoding
+ {
+ public:
+ virtual ~SharedState() {}
+ virtual const Connection* getPublisher() const = 0;
+ virtual void setPublisher(const Connection* p) = 0;
+
+ virtual void setExpiration(sys::AbsTime e) = 0;
+ virtual sys::AbsTime getExpiration() const = 0;
+ virtual sys::Duration getTimeToExpiration() const = 0;
+ virtual void computeExpiration() = 0;
+
+ virtual bool getIsManagementMessage() const = 0;
+ virtual void setIsManagementMessage(bool b) = 0;
+ };
+
+ class SharedStateImpl : public SharedState
+ {
+ const Connection* publisher;
+ qpid::sys::AbsTime expiration;
+ bool isManagementMessage;
+ public:
+ QPID_BROKER_EXTERN SharedStateImpl();
+ virtual ~SharedStateImpl() {}
+ QPID_BROKER_EXTERN const Connection* getPublisher() const;
+ QPID_BROKER_EXTERN void setPublisher(const Connection* p);
+ QPID_BROKER_EXTERN void setExpiration(sys::AbsTime e);
+ QPID_BROKER_EXTERN sys::AbsTime getExpiration() const;
+ QPID_BROKER_EXTERN sys::Duration getTimeToExpiration() const;
+ QPID_BROKER_EXTERN void computeExpiration();
+ QPID_BROKER_EXTERN bool getIsManagementMessage() const;
+ QPID_BROKER_EXTERN void setIsManagementMessage(bool b);
+ };
+
+ QPID_BROKER_EXTERN Message(boost::intrusive_ptr<SharedState>, boost::intrusive_ptr<PersistableMessage>);
+ QPID_BROKER_EXTERN Message();
+ QPID_BROKER_EXTERN ~Message();
+
+ bool isRedelivered() const { return deliveryCount > 0; }
+ bool hasBeenAcquired() const { return alreadyAcquired; }
+ void deliver() { ++deliveryCount; alreadyAcquired |= (deliveryCount>0); }
+ void undeliver() { --deliveryCount; }
+ int getDeliveryCount() const { return deliveryCount; }
+ void resetDeliveryCount() { deliveryCount = -1; alreadyAcquired = false; }
+
+ const Connection* getPublisher() const;
+ bool isLocalTo(const OwnershipToken*) const;
+
+ QPID_BROKER_EXTERN std::string getRoutingKey() const;
+ QPID_BROKER_EXTERN bool isPersistent() const;
+
+ QPID_BROKER_EXTERN sys::AbsTime getExpiration() const;
+ uint64_t getTtl() const;
+ QPID_BROKER_EXTERN bool getTtl(uint64_t&) const;
+
+ QPID_BROKER_EXTERN uint64_t getTimestamp() const;
+
+ QPID_BROKER_EXTERN void addAnnotation(const std::string& key, const qpid::types::Variant& value);
+ QPID_BROKER_EXTERN bool isExcluded(const std::vector<std::string>& excludes) const;
+ QPID_BROKER_EXTERN void addTraceId(const std::string& id);
+ QPID_BROKER_EXTERN void clearTrace();
+ QPID_BROKER_EXTERN uint8_t getPriority() const;
+ QPID_BROKER_EXTERN std::string getPropertyAsString(const std::string& key) const;
+ QPID_BROKER_EXTERN qpid::types::Variant getProperty(const std::string& key) const;
+ void processProperties(qpid::amqp::MapHandler&) const;
+
+ QPID_BROKER_EXTERN uint64_t getMessageSize() const;
+
+ QPID_BROKER_EXTERN const Encoding& getEncoding() const;
+ QPID_BROKER_EXTERN operator bool() const;
+ QPID_BROKER_EXTERN SharedState& getSharedState();
+
+ bool getIsManagementMessage() const;
+
+ QPID_BROKER_EXTERN qpid::framing::SequenceNumber getSequence() const;
+ QPID_BROKER_EXTERN void setSequence(const qpid::framing::SequenceNumber&);
+
+ MessageState getState() const;
+ void setState(MessageState);
+
+ QPID_BROKER_EXTERN qpid::types::Variant getAnnotation(const std::string& key) const;
+ QPID_BROKER_EXTERN const qpid::types::Variant::Map& getAnnotations() const;
+ QPID_BROKER_EXTERN std::string getUserId() const;
+
+ QPID_BROKER_EXTERN std::string getContent() const;//Used for ha, management, when content needs to be decoded
+
+ QPID_BROKER_EXTERN boost::intrusive_ptr<AsyncCompletion> getIngressCompletion() const;
+ QPID_BROKER_EXTERN boost::intrusive_ptr<PersistableMessage> getPersistentContext() const;
+ QPID_BROKER_EXTERN bool hasReplicationId() const;
+ QPID_BROKER_EXTERN uint64_t getReplicationId() const;
+ QPID_BROKER_EXTERN void setReplicationId(framing::SequenceNumber id);
+
+ private:
+ /**
+ * Template for optional members that are only constructed when
+ * if/when needed, to conserve memory. (Boost::optional doesn't
+ * help here).
+ */
+ template <typename T> class Optional
+ {
+ boost::scoped_ptr<T> value;
+ public:
+ Optional() : value(0) {}
+ Optional(const Optional<T>& o) : value(o.value ? new T(*o.value) : 0) {}
+ T& get()
+ {
+ if (!value) value.reset(new T);
+ return *value;
+ }
+ const T& operator*() const
+ {
+ return *value;
+ }
+ Optional<T>& operator=(const Optional<T>& o)
+ {
+ if (o.value) {
+ if (!value) value.reset(new T(*o.value));
+ }
+ return *this;
+ }
+ operator bool() const
+ {
+ return !!value;
+ }
+ };
+
+
+ boost::intrusive_ptr<SharedState> sharedState;
+ boost::intrusive_ptr<PersistableMessage> persistentContext;
+ int deliveryCount;
+ bool alreadyAcquired;
+ Optional<qpid::types::Variant::Map> annotations;
+ MessageState state;
+ qpid::framing::SequenceNumber sequence;
+ framing::SequenceNumber replicationId;
+ bool isReplicationIdSet:1;
+
+ void annotationsChanged();
+ bool getTtl(uint64_t&, uint64_t expiredValue) const;
+};
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/MessageAdapter.cpp b/qpid/cpp/src/qpid/broker/MessageAdapter.cpp
new file mode 100644
index 0000000000..489386f82e
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/MessageAdapter.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/broker/MessageAdapter.h"
+
+#include "qpid/framing/DeliveryProperties.h"
+#include "qpid/framing/MessageProperties.h"
+#include "qpid/framing/MessageTransferBody.h"
+
+namespace {
+ const std::string EMPTY;
+}
+
+namespace qpid {
+namespace broker{
+
+ std::string TransferAdapter::getRoutingKey(const framing::FrameSet& f)
+ {
+ const framing::DeliveryProperties* p = f.getHeaders()->get<framing::DeliveryProperties>();
+ return p ? p->getRoutingKey() : EMPTY;
+ }
+
+ std::string TransferAdapter::getExchange(const framing::FrameSet& f)
+ {
+ return f.as<framing::MessageTransferBody>()->getDestination();
+ }
+
+ bool TransferAdapter::isImmediate(const framing::FrameSet&)
+ {
+ //TODO: delete this, immediate is no longer part of the spec
+ return false;
+ }
+
+ const framing::FieldTable* TransferAdapter::getApplicationHeaders(const framing::FrameSet& f)
+ {
+ const framing::MessageProperties* p = f.getHeaders()->get<framing::MessageProperties>();
+ return p ? &(p->getApplicationHeaders()) : 0;
+ }
+
+ bool TransferAdapter::isPersistent(const framing::FrameSet& f)
+ {
+ const framing::DeliveryProperties* p = f.getHeaders()->get<framing::DeliveryProperties>();
+ return p && p->getDeliveryMode() == 2;
+ }
+
+ bool TransferAdapter::requiresAccept(const framing::FrameSet& f)
+ {
+ const framing::MessageTransferBody* b = f.as<framing::MessageTransferBody>();
+ return b && b->getAcceptMode() == 0/*EXPLICIT == 0*/;
+ }
+
+ uint8_t TransferAdapter::getPriority(const framing::FrameSet& f)
+ {
+ const framing::DeliveryProperties* p = f.getHeaders()->get<framing::DeliveryProperties>();
+ return p ? p->getPriority() : 0;
+ }
+
+ std::string TransferAdapter::getAppId(const framing::FrameSet& f)
+ {
+ const framing::MessageProperties* p = f.getHeaders()->get<framing::MessageProperties>();
+ return p ? p->getAppId() : EMPTY;
+ }
+}}
diff --git a/qpid/cpp/src/qpid/broker/MessageAdapter.h b/qpid/cpp/src/qpid/broker/MessageAdapter.h
new file mode 100644
index 0000000000..df50db4063
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/MessageAdapter.h
@@ -0,0 +1,62 @@
+#ifndef _broker_MessageAdapter_h
+#define _broker_MessageAdapter_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <string>
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/FrameSet.h"
+
+namespace qpid {
+namespace broker {
+
+// TODO aconway 2007-11-09: No longer needed, we only have one type of message.
+struct MessageAdapter
+{
+ virtual ~MessageAdapter() {}
+
+ virtual std::string getRoutingKey(const framing::FrameSet& f) = 0;
+ virtual std::string getExchange(const framing::FrameSet& f) = 0;
+ virtual bool isImmediate(const framing::FrameSet& f) = 0;
+ virtual const framing::FieldTable* getApplicationHeaders(const framing::FrameSet& f) = 0;
+ virtual bool isPersistent(const framing::FrameSet& f) = 0;
+ virtual bool requiresAccept(const framing::FrameSet& f) = 0;
+ virtual uint8_t getPriority(const framing::FrameSet& f) = 0;
+ virtual std::string getAppId(const framing::FrameSet& f) = 0;
+};
+
+struct TransferAdapter : MessageAdapter
+{
+ virtual std::string getRoutingKey(const framing::FrameSet& f);
+ virtual std::string getExchange(const framing::FrameSet& f);
+ virtual const framing::FieldTable* getApplicationHeaders(const framing::FrameSet& f);
+ virtual bool isPersistent(const framing::FrameSet& f);
+ bool isImmediate(const framing::FrameSet&);
+ bool requiresAccept(const framing::FrameSet& f);
+ uint8_t getPriority(const framing::FrameSet& f);
+ virtual std::string getAppId(const framing::FrameSet& f);
+};
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/MessageBuilder.cpp b/qpid/cpp/src/qpid/broker/MessageBuilder.cpp
new file mode 100644
index 0000000000..f5e9332052
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/MessageBuilder.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/MessageBuilder.h"
+
+#include "qpid/broker/Message.h"
+#include "qpid/broker/amqp_0_10/MessageTransfer.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/log/Statement.h"
+
+using boost::intrusive_ptr;
+using namespace qpid::broker;
+using namespace qpid::framing;
+
+namespace
+{
+ std::string type_str(uint8_t type);
+ const std::string QPID_MANAGEMENT("qpid.management");
+}
+
+MessageBuilder::MessageBuilder() : state(DORMANT) {}
+
+void MessageBuilder::handle(AMQFrame& frame)
+{
+ uint8_t type = frame.getBody()->type();
+ switch(state) {
+ case METHOD:
+ checkType(METHOD_BODY, type);
+ if (!frame.getMethod()->isA<qpid::framing::MessageTransferBody>())
+ throw NotImplementedException(QPID_MSG("Unexpected method: " << *(frame.getMethod())));
+
+ exchange = frame.castBody<qpid::framing::MessageTransferBody>()->getDestination();
+ state = HEADER;
+ break;
+ case HEADER:
+ if (type == CONTENT_BODY) {
+ //TODO: rethink how to handle non-existent headers(?)...
+ //didn't get a header: add in a dummy
+ AMQFrame header((AMQHeaderBody()));
+ header.setBof(false);
+ header.setEof(false);
+ message->getFrames().append(header);
+ } else if (type == HEADER_BODY) {
+ frame.castBody<AMQHeaderBody>()->get<DeliveryProperties>(true)->setExchange(exchange);
+ } else {
+ throw CommandInvalidException(
+ QPID_MSG("Invalid frame sequence for message, expected header or content got "
+ << type_str(type) << ")"));
+ }
+ state = CONTENT;
+ break;
+ case CONTENT:
+ checkType(CONTENT_BODY, type);
+ break;
+ default:
+ throw CommandInvalidException(QPID_MSG("Invalid frame sequence for message (state=" << state << ")"));
+ }
+ message->getFrames().append(frame);
+}
+
+void MessageBuilder::end()
+{
+ message->computeRequiredCredit();
+ message = 0;
+ state = DORMANT;
+}
+
+void MessageBuilder::start(const SequenceNumber& id)
+{
+ message = intrusive_ptr<qpid::broker::amqp_0_10::MessageTransfer>(new qpid::broker::amqp_0_10::MessageTransfer(id));
+ state = METHOD;
+}
+
+namespace {
+
+const std::string HEADER_BODY_S = "HEADER";
+const std::string METHOD_BODY_S = "METHOD";
+const std::string CONTENT_BODY_S = "CONTENT";
+const std::string HEARTBEAT_BODY_S = "HEARTBEAT";
+const std::string UNKNOWN = "unknown";
+
+std::string type_str(uint8_t type)
+{
+ switch(type) {
+ case METHOD_BODY: return METHOD_BODY_S;
+ case HEADER_BODY: return HEADER_BODY_S;
+ case CONTENT_BODY: return CONTENT_BODY_S;
+ case HEARTBEAT_BODY: return HEARTBEAT_BODY_S;
+ }
+ return UNKNOWN;
+}
+
+}
+
+void MessageBuilder::checkType(uint8_t expected, uint8_t actual)
+{
+ if (expected != actual) {
+ throw CommandInvalidException(QPID_MSG("Invalid frame sequence for message (expected "
+ << type_str(expected) << " got " << type_str(actual) << ")"));
+ }
+}
+
+boost::intrusive_ptr<qpid::broker::amqp_0_10::MessageTransfer> MessageBuilder::getMessage() { return message; }
diff --git a/qpid/cpp/src/qpid/broker/MessageBuilder.h b/qpid/cpp/src/qpid/broker/MessageBuilder.h
new file mode 100644
index 0000000000..5673ed3b7f
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/MessageBuilder.h
@@ -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.
+ *
+ */
+#ifndef _MessageBuilder_
+#define _MessageBuilder_
+
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/framing/FrameHandler.h"
+#include "qpid/framing/SequenceNumber.h"
+#include "qpid/RefCounted.h"
+
+#include <string>
+
+#include <boost/intrusive_ptr.hpp>
+
+namespace qpid {
+ namespace broker {
+ namespace amqp_0_10 {
+ class MessageTransfer;
+ }
+
+ class QPID_BROKER_CLASS_EXTERN MessageBuilder : public framing::FrameHandler{
+ public:
+ QPID_BROKER_EXTERN MessageBuilder();
+ QPID_BROKER_EXTERN void handle(framing::AMQFrame& frame);
+ boost::intrusive_ptr<qpid::broker::amqp_0_10::MessageTransfer> getMessage();
+ QPID_BROKER_EXTERN void start(const framing::SequenceNumber& id);
+ void end();
+ private:
+ enum State {DORMANT, METHOD, HEADER, CONTENT};
+ State state;
+ boost::intrusive_ptr<qpid::broker::amqp_0_10::MessageTransfer> message;
+ std::string exchange;
+
+ void checkType(uint8_t expected, uint8_t actual);
+ };
+ }
+}
+
+
+#endif
+
diff --git a/qpid/cpp/src/qpid/broker/MessageDeque.cpp b/qpid/cpp/src/qpid/broker/MessageDeque.cpp
new file mode 100644
index 0000000000..1529d4ac94
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/MessageDeque.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/MessageDeque.h"
+#include "assert.h"
+#include "qpid/broker/Message.h"
+#include "qpid/broker/QueueCursor.h"
+#include "qpid/framing/SequenceNumber.h"
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace broker {
+namespace {
+Message padding(qpid::framing::SequenceNumber id) {
+ Message m;
+ m.setState(DELETED);
+ m.setSequence(id);
+ return m;
+}
+}
+
+using qpid::framing::SequenceNumber;
+
+MessageDeque::MessageDeque() : messages(&padding) {}
+
+
+bool MessageDeque::deleted(const QueueCursor& cursor)
+{
+ return messages.deleted(cursor);
+}
+
+void MessageDeque::publish(const Message& added)
+{
+ messages.publish(added);
+}
+
+Message* MessageDeque::release(const QueueCursor& cursor)
+{
+ return messages.release(cursor);
+}
+
+Message* MessageDeque::next(QueueCursor& cursor)
+{
+ return messages.next(cursor);
+}
+
+size_t MessageDeque::size()
+{
+ return messages.size();
+}
+
+Message* MessageDeque::find(const framing::SequenceNumber& position, QueueCursor* cursor)
+{
+ return messages.find(position, cursor);
+}
+
+Message* MessageDeque::find(const QueueCursor& cursor)
+{
+ return messages.find(cursor);
+}
+
+void MessageDeque::foreach(Functor f)
+{
+ messages.foreach(f);
+}
+
+void MessageDeque::resetCursors()
+{
+ messages.resetCursors();
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/MessageDeque.h b/qpid/cpp/src/qpid/broker/MessageDeque.h
new file mode 100644
index 0000000000..ec67476926
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/MessageDeque.h
@@ -0,0 +1,55 @@
+#ifndef QPID_BROKER_MESSAGEDEQUE_H
+#define QPID_BROKER_MESSAGEDEQUE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/Messages.h"
+#include "qpid/broker/IndexedDeque.h"
+
+namespace qpid {
+namespace broker {
+
+/**
+ * Provides the standard FIFO queue behaviour.
+ */
+class MessageDeque : public Messages
+{
+ public:
+ MessageDeque();
+ size_t size();
+ bool deleted(const QueueCursor&);
+ void publish(const Message& added);
+ Message* next(QueueCursor&);
+ Message* release(const QueueCursor& cursor);
+ Message* find(const QueueCursor&);
+ Message* find(const framing::SequenceNumber&, QueueCursor*);
+
+ void foreach(Functor);
+
+ void resetCursors();
+
+ private:
+ typedef IndexedDeque<Message> Deque;
+ Deque messages;
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_MESSAGEDEQUE_H*/
diff --git a/qpid/cpp/src/qpid/broker/MessageDistributor.h b/qpid/cpp/src/qpid/broker/MessageDistributor.h
new file mode 100644
index 0000000000..c11e4495a1
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/MessageDistributor.h
@@ -0,0 +1,53 @@
+#ifndef _broker_MessageDistributor_h
+#define _broker_MessageDistributor_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/types/Variant.h"
+/** Abstraction used by Queue to determine the next "most desirable" message to provide to
+ * a particular consuming client
+ */
+
+namespace qpid {
+namespace broker {
+
+class Message;
+
+class MessageDistributor
+{
+ public:
+ virtual ~MessageDistributor() {};
+
+ /**
+ * Determine whether the named consumer can take ownership of the specified message.
+ * @param consumer the name of the consumer that is attempting to acquire the message
+ * @param target the message to be acquired
+ * @return true if ownership is permitted, false if ownership cannot be assigned.
+ */
+ virtual bool acquire(const std::string& consumer, Message& target) = 0;
+
+ /** hook to add any interesting management state to the status map */
+ virtual void query(qpid::types::Variant::Map&) const = 0;
+};
+
+}}
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/MessageGroupManager.cpp b/qpid/cpp/src/qpid/broker/MessageGroupManager.cpp
new file mode 100644
index 0000000000..c083e4ee0f
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/MessageGroupManager.cpp
@@ -0,0 +1,313 @@
+ /*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/MessageGroupManager.h"
+#include "qpid/broker/Message.h"
+#include "qpid/broker/Messages.h"
+#include "qpid/broker/MessageDeque.h"
+#include "qpid/broker/QueueSettings.h"
+#include "qpid/framing/Array.h"
+#include "qpid/framing/DeliveryProperties.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/framing/TypeCode.h"
+#include "qpid/types/Variant.h"
+#include "qpid/log/Statement.h"
+#include "qpid/types/Variant.h"
+
+using namespace qpid::broker;
+
+namespace {
+ const std::string GROUP_QUERY_KEY("qpid.message_group_queue");
+ const std::string GROUP_HEADER_KEY("group_header_key");
+ const std::string GROUP_STATE_KEY("group_state");
+ const std::string GROUP_ID_KEY("group_id");
+ const std::string GROUP_MSG_COUNT("msg_count");
+ const std::string GROUP_TIMESTAMP("timestamp");
+ const std::string GROUP_CONSUMER("consumer");
+}
+
+
+const std::string MessageGroupManager::qpidMessageGroupKey("qpid.group_header_key");
+const std::string MessageGroupManager::qpidSharedGroup("qpid.shared_msg_group");
+const std::string MessageGroupManager::qpidMessageGroupTimestamp("qpid.group_timestamp");
+
+
+/** return an iterator to the message at position, or members.end() if not found */
+MessageGroupManager::GroupState::MessageFifo::iterator
+MessageGroupManager::GroupState::findMsg(const qpid::framing::SequenceNumber &position)
+{
+ MessageState mState(position);
+ MessageFifo::iterator found = std::lower_bound(members.begin(), members.end(), mState);
+ return (found->position == position) ? found : members.end();
+}
+
+void MessageGroupManager::unFree( const GroupState& state )
+{
+ GroupFifo::iterator pos = freeGroups.find( state.members.front().position );
+ assert( pos != freeGroups.end() && pos->second == &state );
+ freeGroups.erase( pos );
+}
+
+void MessageGroupManager::own( GroupState& state, const std::string& owner )
+{
+ state.owner = owner;
+ unFree( state );
+}
+
+void MessageGroupManager::disown( GroupState& state )
+{
+ state.owner.clear();
+ assert(state.members.size());
+ assert(freeGroups.find(state.members.front().position) == freeGroups.end());
+ freeGroups[state.members.front().position] = &state;
+}
+
+MessageGroupManager::GroupState& MessageGroupManager::findGroup( const Message& m )
+{
+ uint32_t thisMsg = m.getSequence().getValue();
+ if (cachedGroup && lastMsg == thisMsg) {
+ hits++;
+ return *cachedGroup;
+ }
+
+ std::string group = m.getPropertyAsString(groupIdHeader);
+ if (group.empty()) group = defaultGroupId; //empty group is reserved
+
+ if (cachedGroup && group == lastGroup) {
+ hits++;
+ lastMsg = thisMsg;
+ return *cachedGroup;
+ }
+
+ misses++;
+
+ GroupState& found = messageGroups[group];
+ if (found.group.empty())
+ found.group = group; // new group, assign name
+ lastMsg = thisMsg;
+ lastGroup = group;
+ cachedGroup = &found;
+ return found;
+}
+
+
+void MessageGroupManager::enqueued( const Message& m )
+{
+ // @todo KAG optimization - store reference to group state in QueuedMessage
+ // issue: const-ness??
+ GroupState& state = findGroup(m);
+ GroupState::MessageState mState(m.getSequence());
+ state.members.push_back(mState);
+ uint32_t total = state.members.size();
+ QPID_LOG( trace, "group queue " << qName <<
+ ": added message to group id=" << state.group << " total=" << total );
+ if (total == 1) {
+ // newly created group, no owner
+ assert(freeGroups.find(m.getSequence()) == freeGroups.end());
+ freeGroups[m.getSequence()] = &state;
+ }
+}
+
+
+void MessageGroupManager::acquired( const Message& m )
+{
+ // @todo KAG avoid lookup: retrieve direct reference to group state from QueuedMessage
+ // issue: const-ness??
+ GroupState& state = findGroup(m);
+ GroupState::MessageFifo::iterator gm = state.findMsg(m.getSequence());
+ assert(gm != state.members.end());
+ gm->acquired = true;
+ state.acquired += 1;
+ QPID_LOG( trace, "group queue " << qName <<
+ ": acquired message in group id=" << state.group << " acquired=" << state.acquired );
+}
+
+
+void MessageGroupManager::requeued( const Message& m )
+{
+ // @todo KAG avoid lookup: retrieve direct reference to group state from QueuedMessage
+ // issue: const-ness??
+ GroupState& state = findGroup(m);
+ assert( state.acquired != 0 );
+ state.acquired -= 1;
+ GroupState::MessageFifo::iterator i = state.findMsg(m.getSequence());
+ assert(i != state.members.end());
+ i->acquired = false;
+ if (state.acquired == 0 && state.owned()) {
+ QPID_LOG( trace, "group queue " << qName <<
+ ": consumer name=" << state.owner << " released group id=" << state.group);
+ disown(state);
+ }
+ QPID_LOG( trace, "group queue " << qName <<
+ ": requeued message to group id=" << state.group << " acquired=" << state.acquired );
+}
+
+
+void MessageGroupManager::dequeued( const Message& m )
+{
+ // @todo KAG avoid lookup: retrieve direct reference to group state from QueuedMessage
+ // issue: const-ness??
+ GroupState& state = findGroup(m);
+ GroupState::MessageFifo::iterator i = state.findMsg(m.getSequence());
+ assert(i != state.members.end());
+ if (i->acquired) {
+ assert( state.acquired != 0 );
+ state.acquired -= 1;
+ }
+
+ // special case if qm is first (oldest) message in the group:
+ // may need to re-insert it back on the freeGroups list, as the index will change
+ bool reFreeNeeded = false;
+ if (i == state.members.begin()) {
+ if (!state.owned()) {
+ // will be on the freeGroups list if mgmt is dequeueing rather than a consumer!
+ // if on freelist, it is indexed by first member, which is about to be removed!
+ unFree(state);
+ reFreeNeeded = true;
+ }
+ state.members.pop_front();
+ } else {
+ state.members.erase(i);
+ }
+
+ uint32_t total = state.members.size();
+ QPID_LOG( trace, "group queue " << qName <<
+ ": dequeued message from group id=" << state.group << " total=" << total );
+
+ if (total == 0) {
+ QPID_LOG( trace, "group queue " << qName << ": deleting group id=" << state.group);
+ if (cachedGroup == &state) {
+ cachedGroup = 0;
+ }
+ std::string key(state.group);
+ messageGroups.erase( key );
+ } else if (state.acquired == 0 && state.owned()) {
+ QPID_LOG( trace, "group queue " << qName <<
+ ": consumer name=" << state.owner << " released group id=" << state.group);
+ disown(state);
+ MessageDeque* md = dynamic_cast<MessageDeque*>(&messages);
+ if (md) {
+ md->resetCursors();
+ } else {
+ QPID_LOG(warning, "Could not reset cursors for message group, unexpected container type");
+ }
+ } else if (reFreeNeeded) {
+ disown(state);
+ }
+}
+
+MessageGroupManager::~MessageGroupManager()
+{
+ QPID_LOG( debug, "group queue " << qName << " cache results: hits=" << hits << " misses=" << misses );
+}
+
+bool MessageGroupManager::acquire(const std::string& consumer, Message& m)
+{
+ if (m.getState() == AVAILABLE) {
+ // @todo KAG avoid lookup: retrieve direct reference to group state from QueuedMessage
+ GroupState& state = findGroup(m);
+
+ if (!state.owned()) {
+ own( state, consumer );
+ QPID_LOG( trace, "group queue " << qName <<
+ ": consumer name=" << consumer << " has acquired group id=" << state.group);
+ }
+ if (state.owner == consumer) {
+ m.setState(ACQUIRED);
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+}
+
+void MessageGroupManager::query(qpid::types::Variant::Map& status) const
+{
+ /** Add a description of the current state of the message groups for this queue.
+ FORMAT:
+ { "qpid.message_group_queue":
+ { "group_header_key" : "<KEY>",
+ "group_state" :
+ [ { "group_id" : "<name>",
+ "msg_count" : <int>,
+ "timestamp" : <absTime>,
+ "consumer" : <consumer name> },
+ {...} // one for each known group
+ ]
+ }
+ }
+ **/
+
+ assert(status.find(GROUP_QUERY_KEY) == status.end());
+ qpid::types::Variant::Map state;
+ qpid::types::Variant::List groups;
+
+ state[GROUP_HEADER_KEY] = groupIdHeader;
+ for (GroupMap::const_iterator g = messageGroups.begin();
+ g != messageGroups.end(); ++g) {
+ qpid::types::Variant::Map info;
+ info[GROUP_ID_KEY] = g->first;
+ info[GROUP_MSG_COUNT] = (uint64_t) g->second.members.size();
+ // set the timestamp to the arrival timestamp of the oldest (HEAD) message, if present
+ info[GROUP_TIMESTAMP] = 0;
+ if (g->second.members.size() != 0) {
+ Message* m = messages.find(g->second.members.front().position, 0);
+ if (m && m->getTimestamp()) {
+ info[GROUP_TIMESTAMP] = m->getTimestamp();
+ }
+ }
+ info[GROUP_CONSUMER] = g->second.owner;
+ groups.push_back(info);
+ }
+ state[GROUP_STATE_KEY] = groups;
+ status[GROUP_QUERY_KEY] = state;
+}
+
+
+boost::shared_ptr<MessageGroupManager> MessageGroupManager::create( const std::string& qName,
+ Messages& messages,
+ const QueueSettings& settings )
+{
+ boost::shared_ptr<MessageGroupManager> manager( new MessageGroupManager( settings.groupKey, qName, messages, settings.addTimestamp ) );
+ QPID_LOG( debug, "Configured Queue '" << qName <<
+ "' for message grouping using header key '" << settings.groupKey << "'" <<
+ " (timestamp=" << settings.addTimestamp << ")");
+ return manager;
+}
+
+std::string MessageGroupManager::defaultGroupId;
+void MessageGroupManager::setDefaults(const std::string& groupId) // static
+{
+ defaultGroupId = groupId;
+}
+
+namespace {
+ const std::string GROUP_NAME("name");
+ const std::string GROUP_OWNER("owner");
+ const std::string GROUP_ACQUIRED_CT("acquired-ct");
+ const std::string GROUP_POSITIONS("positions");
+ const std::string GROUP_ACQUIRED_MSGS("acquired-msgs");
+ const std::string GROUP_STATE("group-state");
+}
+
diff --git a/qpid/cpp/src/qpid/broker/MessageGroupManager.h b/qpid/cpp/src/qpid/broker/MessageGroupManager.h
new file mode 100644
index 0000000000..bf45e776c8
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/MessageGroupManager.h
@@ -0,0 +1,129 @@
+#ifndef _broker_MessageGroupManager_h
+#define _broker_MessageGroupManager_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/* for managing message grouping on Queues */
+
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/broker/QueueObserver.h"
+#include "qpid/broker/MessageDistributor.h"
+#include "qpid/framing/SequenceNumber.h"
+#include "qpid/sys/unordered_map.h"
+
+#include "boost/shared_ptr.hpp"
+#include <deque>
+
+namespace qpid {
+namespace broker {
+
+class QueueObserver;
+struct QueueSettings;
+class MessageDistributor;
+class Messages;
+class Consumer;
+
+class MessageGroupManager : public QueueObserver, public MessageDistributor
+{
+ static std::string defaultGroupId; // assigned if no group id header present
+
+ const std::string groupIdHeader; // msg header holding group identifier
+ const unsigned int timestamp; // mark messages with timestamp if set
+ Messages& messages; // parent Queue's in memory message container
+ const std::string qName; // name of parent queue (for logs)
+
+ struct GroupState {
+ // note: update getState()/setState() when changing this object's state implementation
+
+ // track which messages are in this group, and if they have been acquired
+ struct MessageState {
+ qpid::framing::SequenceNumber position;
+ bool acquired;
+ MessageState() : acquired(false) {}
+ MessageState(const qpid::framing::SequenceNumber& p) : position(p), acquired(false) {}
+ bool operator<(const MessageState& b) const { return position < b.position; }
+ };
+ typedef std::deque<MessageState> MessageFifo;
+
+ std::string group; // group identifier
+ std::string owner; // consumer with outstanding acquired messages
+ uint32_t acquired; // count of outstanding acquired messages
+ MessageFifo members; // msgs belonging to this group, in enqueue order
+
+ GroupState() : acquired(0) {}
+ bool owned() const {return !owner.empty();}
+ MessageFifo::iterator findMsg(const qpid::framing::SequenceNumber &);
+ };
+
+ typedef sys::unordered_map<std::string, struct GroupState> GroupMap;
+ typedef std::map<qpid::framing::SequenceNumber, struct GroupState *> GroupFifo;
+
+ GroupMap messageGroups; // index: group name
+ GroupFifo freeGroups; // ordered by oldest free msg
+ //Consumers consumers; // index: consumer name
+
+ GroupState& findGroup( const Message& m );
+ unsigned long hits, misses; // for debug
+ uint32_t lastMsg;
+ std::string lastGroup;
+ GroupState *cachedGroup;
+
+ void unFree( const GroupState& state );
+ void own( GroupState& state, const std::string& owner );
+ void disown( GroupState& state );
+
+ public:
+ static const std::string qpidMessageGroupKey;
+ static const std::string qpidSharedGroup; // if specified, one group can be consumed by multiple receivers
+ static const std::string qpidMessageGroupTimestamp;
+
+ static QPID_BROKER_EXTERN void setDefaults(const std::string& groupId);
+ static boost::shared_ptr<MessageGroupManager> create( const std::string& qName,
+ Messages& messages,
+ const QueueSettings& settings );
+
+ MessageGroupManager(const std::string& header, const std::string& _qName,
+ Messages& container, unsigned int _timestamp=0 )
+ : groupIdHeader( header ), timestamp(_timestamp), messages(container),
+ qName(_qName),
+ hits(0), misses(0),
+ lastMsg(0), cachedGroup(0) {}
+ virtual ~MessageGroupManager();
+
+ // QueueObserver iface
+ void enqueued( const Message& qm );
+ void acquired( const Message& qm );
+ void requeued( const Message& qm );
+ void dequeued( const Message& qm );
+ void consumerAdded( const Consumer& ) {};
+ void consumerRemoved( const Consumer& ) {};
+
+ // MessageDistributor iface
+ bool acquire(const std::string& c, Message& );
+ void query(qpid::types::Variant::Map&) const;
+
+ bool match(const qpid::types::Variant::Map*, const Message&) const;
+};
+
+}}
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/MessageInterceptor.h b/qpid/cpp/src/qpid/broker/MessageInterceptor.h
new file mode 100644
index 0000000000..3ada86b6f7
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/MessageInterceptor.h
@@ -0,0 +1,58 @@
+#ifndef QPID_BROKER_MESSAGEINTERCEPTOR_H
+#define QPID_BROKER_MESSAGEINTERCEPTOR_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "Observers.h"
+
+namespace qpid {
+namespace broker {
+
+class Message;
+
+/**
+ * Interface for classes that want to modify a message as it is processed by the queue.
+ */
+class MessageInterceptor
+{
+ public:
+ virtual ~MessageInterceptor() {}
+
+ /** Modify a message before it is recorded in durable store */
+ virtual void record(Message&) {}
+ /** Modify a message as it is being published onto the queue. */
+ virtual void publish(Message&) {}
+};
+
+class MessageInterceptors : public Observers<MessageInterceptor> {
+ public:
+ void record(Message& m) {
+ each(boost::bind(&MessageInterceptor::record, _1, boost::ref(m)));
+ }
+ void publish(Message& m) {
+ each(boost::bind(&MessageInterceptor::publish, _1, boost::ref(m)));
+ }
+};
+
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_MESSAGEINTERCEPTOR_H*/
diff --git a/qpid/cpp/src/qpid/broker/MessageMap.cpp b/qpid/cpp/src/qpid/broker/MessageMap.cpp
new file mode 100644
index 0000000000..4cdd83c9aa
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/MessageMap.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/MessageMap.h"
+#include "qpid/broker/Message.h"
+#include "qpid/broker/QueueCursor.h"
+#include "qpid/log/Statement.h"
+#include <algorithm>
+
+namespace qpid {
+namespace broker {
+namespace {
+const std::string EMPTY;
+}
+
+
+std::string MessageMap::getKey(const Message& message)
+{
+ return message.getPropertyAsString(key);
+}
+
+size_t MessageMap::size()
+{
+ size_t count(0);
+ for (Ordering::iterator i = messages.begin(); i != messages.end(); ++i) {
+ if (i->second.getState() == AVAILABLE) ++count;
+ }
+ return count;
+}
+
+bool MessageMap::empty()
+{
+ return size() == 0;//TODO: more efficient implementation
+}
+
+bool MessageMap::deleted(const QueueCursor& cursor)
+{
+ Ordering::iterator i = messages.find(cursor.position);
+ if (i != messages.end()) {
+ erase(i);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+Message* MessageMap::find(const QueueCursor& cursor)
+{
+ if (cursor.valid) return find(cursor.position, 0);
+ else return 0;
+}
+
+Message* MessageMap::find(const framing::SequenceNumber& position, QueueCursor* cursor)
+{
+ Ordering::iterator i = messages.lower_bound(position);
+ if (i != messages.end()) {
+ if (cursor) cursor->setPosition(i->first, version);
+ if (i->first == position) return &(i->second);
+ else return 0;
+ } else {
+ //there is no message whose sequence is greater than position,
+ //i.e. haven't got there yet
+ if (cursor) cursor->setPosition(position, version);
+ return 0;
+ }
+}
+
+Message* MessageMap::next(QueueCursor& cursor)
+{
+ Ordering::iterator i;
+ if (!cursor.valid) i = messages.begin(); //start with oldest message
+ else i = messages.upper_bound(cursor.position); //get first message that is greater than position
+
+ while (i != messages.end()) {
+ Message& m = i->second;
+ cursor.setPosition(m.getSequence(), version);
+ if (cursor.check(m)) {
+ return &m;
+ } else {
+ ++i;
+ }
+ }
+ return 0;
+}
+
+const Message& MessageMap::replace(const Message& original, const Message& update)
+{
+ messages.erase(original.getSequence());
+ std::pair<Ordering::iterator, bool> i = messages.insert(Ordering::value_type(update.getSequence(), update));
+ i.first->second.setState(AVAILABLE);
+ return i.first->second;
+}
+
+void MessageMap::publish(const Message& added)
+{
+ Message dummy;
+ update(added, dummy);
+}
+
+bool MessageMap::update(const Message& added, Message& removed)
+{
+ std::pair<Index::iterator, bool> result = index.insert(Index::value_type(getKey(added), added));
+ if (result.second) {
+ //there was no previous message for this key; nothing needs to
+ //be removed, just add the message into its correct position
+ messages.insert(Ordering::value_type(added.getSequence(), added)).first->second.setState(AVAILABLE);
+ return false;
+ } else {
+ //there is already a message with that key which needs to be replaced
+ removed = result.first->second;
+ result.first->second = replace(result.first->second, added);
+ result.first->second.setState(AVAILABLE);
+ QPID_LOG(debug, "Displaced message at " << removed.getSequence() << " with " << result.first->second.getSequence() << ": " << result.first->first);
+ return true;
+ }
+}
+
+Message* MessageMap::release(const QueueCursor& cursor)
+{
+ Ordering::iterator i = messages.find(cursor.position);
+ if (i != messages.end()) {
+ i->second.setState(AVAILABLE);
+ return &i->second;
+ } else {
+ return 0;
+ }
+}
+
+void MessageMap::foreach(Functor f)
+{
+ for (Ordering::iterator i = messages.begin(); i != messages.end(); ++i) {
+ if (i->second.getState() == AVAILABLE) f(i->second);
+ }
+}
+
+void MessageMap::erase(Ordering::iterator i)
+{
+ index.erase(getKey(i->second));
+ messages.erase(i);
+}
+
+MessageMap::MessageMap(const std::string& k) : key(k), version(0) {}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/MessageMap.h b/qpid/cpp/src/qpid/broker/MessageMap.h
new file mode 100644
index 0000000000..c30606d0ff
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/MessageMap.h
@@ -0,0 +1,71 @@
+#ifndef QPID_BROKER_MESSAGEMAP_H
+#define QPID_BROKER_MESSAGEMAP_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/Messages.h"
+#include "qpid/broker/Message.h"
+#include "qpid/framing/SequenceNumber.h"
+#include <map>
+#include <string>
+
+namespace qpid {
+namespace broker {
+
+/**
+ * Provides a last value queue behaviour, whereby a messages replace
+ * any previous message with the same value for a defined property
+ * (i.e. the key).
+ */
+class MessageMap : public Messages
+{
+ public:
+ MessageMap(const std::string& key);
+
+ size_t size();
+ bool empty();
+
+ bool deleted(const QueueCursor&);
+ void publish(const Message& added);//use update instead to get replaced message
+ Message* next(QueueCursor&);
+ Message* release(const QueueCursor& cursor);
+ Message* find(const QueueCursor&);
+ Message* find(const framing::SequenceNumber&, QueueCursor*);
+
+ void foreach(Functor);
+
+ bool update(const Message& added, Message& removed);
+
+ protected:
+ typedef std::map<std::string, Message> Index;
+ typedef std::map<framing::SequenceNumber, Message> Ordering;
+ const std::string key;
+ Index index;
+ Ordering messages;
+ int32_t version;
+
+ std::string getKey(const Message&);
+ virtual const Message& replace(const Message&, const Message&);
+ void erase(Ordering::iterator);
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_MESSAGEMAP_H*/
diff --git a/qpid/cpp/src/qpid/broker/MessageStore.h b/qpid/cpp/src/qpid/broker/MessageStore.h
new file mode 100644
index 0000000000..4f7e9f3d5f
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/MessageStore.h
@@ -0,0 +1,189 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#ifndef _MessageStore_
+#define _MessageStore_
+
+#include "qpid/broker/PersistableExchange.h"
+#include "qpid/broker/PersistableMessage.h"
+#include "qpid/broker/PersistableQueue.h"
+#include "qpid/broker/PersistableConfig.h"
+#include "qpid/broker/RecoveryManager.h"
+#include "qpid/broker/TransactionalStore.h"
+#include "qpid/framing/FieldTable.h"
+
+#include <boost/shared_ptr.hpp>
+#include <boost/intrusive_ptr.hpp>
+
+namespace qpid {
+namespace broker {
+
+/**
+ * An abstraction of the persistent storage for messages. (In
+ * all methods, any pointers/references to queues or messages
+ * are valid only for the duration of the call).
+ */
+class MessageStore : public TransactionalStore, public Recoverable {
+ public:
+
+ /**
+ * Record the existence of a durable queue
+ */
+ virtual void create(PersistableQueue& queue,
+ const framing::FieldTable& args) = 0;
+ /**
+ * Destroy a durable queue
+ */
+ virtual void destroy(PersistableQueue& queue) = 0;
+
+ /**
+ * Record the existence of a durable exchange
+ */
+ virtual void create(const PersistableExchange& exchange,
+ const framing::FieldTable& args) = 0;
+ /**
+ * Destroy a durable exchange
+ */
+ virtual void destroy(const PersistableExchange& exchange) = 0;
+
+ /**
+ * Record a binding
+ */
+ virtual void bind(const PersistableExchange& exchange, const PersistableQueue& queue,
+ const std::string& key, const framing::FieldTable& args) = 0;
+
+ /**
+ * Forget a binding
+ */
+ virtual void unbind(const PersistableExchange& exchange, const PersistableQueue& queue,
+ const std::string& key, const framing::FieldTable& args) = 0;
+
+ /**
+ * Record generic durable configuration
+ */
+ virtual void create(const PersistableConfig& config) = 0;
+
+ /**
+ * Destroy generic durable configuration
+ */
+ virtual void destroy(const PersistableConfig& config) = 0;
+
+ /**
+ * Stores a messages before it has been enqueued
+ * (enqueueing automatically stores the message so this is
+ * only required if storage is required prior to that
+ * point). If the message has not yet been stored it will
+ * store the headers as well as any content passed in. A
+ * persistence id will be set on the message which can be
+ * used to load the content or to append to it.
+ */
+ virtual void stage(const boost::intrusive_ptr<PersistableMessage>& msg) = 0;
+
+ /**
+ * Destroys a previously staged message. This only needs
+ * to be called if the message is never enqueued. (Once
+ * enqueued, deletion will be automatic when the message
+ * is dequeued from all queues it was enqueued onto).
+ */
+ virtual void destroy(PersistableMessage& msg) = 0;
+
+ /**
+ * Appends content to a previously staged message
+ */
+ virtual void appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg,
+ const std::string& data) = 0;
+
+ /**
+ * Loads (a section) of content data for the specified
+ * message (previously stored through a call to stage or
+ * enqueue) into data. The offset refers to the content
+ * only (i.e. an offset of 0 implies that the start of the
+ * content should be loaded, not the headers or related
+ * meta-data).
+ */
+ virtual void loadContent(const qpid::broker::PersistableQueue& queue,
+ const boost::intrusive_ptr<const PersistableMessage>& msg,
+ std::string& data, uint64_t offset, uint32_t length) = 0;
+
+ /**
+ * Enqueues a message, storing the message if it has not
+ * been previously stored and recording that the given
+ * message is on the given queue.
+ *
+ * Note: that this is async so the return of the function does
+ * not mean the opperation is complete.
+ *
+ * @param msg the message to enqueue
+ * @param queue the name of the queue onto which it is to be enqueued
+ * @param xid (a pointer to) an identifier of the
+ * distributed transaction in which the operation takes
+ * place or null for 'local' transactions
+ */
+ virtual void enqueue(TransactionContext* ctxt,
+ const boost::intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue& queue) = 0;
+
+ /**
+ * Dequeues a message, recording that the given message is
+ * no longer on the given queue and deleting the message
+ * if it is no longer on any other queue.
+ *
+ * Note: that this is async so the return of the function does
+ * not mean the opperation is complete.
+ *
+ * @param msg the message to dequeue
+ * @param queue the name of the queue from which it is to be dequeued
+ * @param xid (a pointer to) an identifier of the
+ * distributed transaction in which the operation takes
+ * place or null for 'local' transactions
+ */
+ virtual void dequeue(TransactionContext* ctxt,
+ const boost::intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue& queue) = 0;
+
+ /**
+ * Flushes all async messages to disk for the specified queue
+ *
+ * Note: that this is async so the return of the function does
+ * not mean the opperation is complete.
+ *
+ * @param queue the name of the queue from which it is to be dequeued
+ */
+ virtual void flush(const qpid::broker::PersistableQueue& queue)=0;
+
+ /**
+ * Returns the number of outstanding AIO's for a given queue
+ *
+ * If 0, than all the enqueue / dequeues have been stored
+ * to disk
+ *
+ * @param queue the name of the queue to check for outstanding AIO
+ */
+ virtual uint32_t outstandingQueueAIO(const PersistableQueue& queue) = 0;
+
+
+ virtual ~MessageStore(){}
+};
+
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/MessageStoreModule.cpp b/qpid/cpp/src/qpid/broker/MessageStoreModule.cpp
new file mode 100644
index 0000000000..556ecc3084
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/MessageStoreModule.cpp
@@ -0,0 +1,173 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/MessageStoreModule.h"
+#include "qpid/broker/NullMessageStore.h"
+#include <iostream>
+
+// This transfer protects against the unloading of the store lib prior to the handling of the exception
+#define TRANSFER_EXCEPTION(fn) try { fn; } catch (std::exception& e) { throw Exception(e.what()); }
+
+using boost::intrusive_ptr;
+using qpid::framing::FieldTable;
+using std::string;
+
+namespace qpid {
+namespace broker {
+
+MessageStoreModule::MessageStoreModule(const boost::shared_ptr<MessageStore>& _store)
+ : store(_store) {}
+
+MessageStoreModule::~MessageStoreModule()
+{
+}
+
+void MessageStoreModule::create(PersistableQueue& queue, const FieldTable& args)
+{
+ TRANSFER_EXCEPTION(store->create(queue, args));
+}
+
+void MessageStoreModule::destroy(PersistableQueue& queue)
+{
+ TRANSFER_EXCEPTION(store->destroy(queue));
+}
+
+void MessageStoreModule::create(const PersistableExchange& exchange, const FieldTable& args)
+{
+ TRANSFER_EXCEPTION(store->create(exchange, args));
+}
+
+void MessageStoreModule::destroy(const PersistableExchange& exchange)
+{
+ TRANSFER_EXCEPTION(store->destroy(exchange));
+}
+
+void MessageStoreModule::bind(const PersistableExchange& e, const PersistableQueue& q,
+ const std::string& k, const framing::FieldTable& a)
+{
+ TRANSFER_EXCEPTION(store->bind(e, q, k, a));
+}
+
+void MessageStoreModule::unbind(const PersistableExchange& e, const PersistableQueue& q,
+ const std::string& k, const framing::FieldTable& a)
+{
+ TRANSFER_EXCEPTION(store->unbind(e, q, k, a));
+}
+
+void MessageStoreModule::create(const PersistableConfig& config)
+{
+ TRANSFER_EXCEPTION(store->create(config));
+}
+
+void MessageStoreModule::destroy(const PersistableConfig& config)
+{
+ TRANSFER_EXCEPTION(store->destroy(config));
+}
+
+void MessageStoreModule::recover(RecoveryManager& registry)
+{
+ TRANSFER_EXCEPTION(store->recover(registry));
+}
+
+void MessageStoreModule::stage(const intrusive_ptr<PersistableMessage>& msg)
+{
+ TRANSFER_EXCEPTION(store->stage(msg));
+}
+
+void MessageStoreModule::destroy(PersistableMessage& msg)
+{
+ TRANSFER_EXCEPTION(store->destroy(msg));
+}
+
+void MessageStoreModule::appendContent(const intrusive_ptr<const PersistableMessage>& msg,
+ const std::string& data)
+{
+ TRANSFER_EXCEPTION(store->appendContent(msg, data));
+}
+
+void MessageStoreModule::loadContent(
+ const qpid::broker::PersistableQueue& queue,
+ const intrusive_ptr<const PersistableMessage>& msg,
+ string& data, uint64_t offset, uint32_t length)
+{
+ TRANSFER_EXCEPTION(store->loadContent(queue, msg, data, offset, length));
+}
+
+void MessageStoreModule::enqueue(TransactionContext* ctxt,
+ const intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue& queue)
+{
+ TRANSFER_EXCEPTION(store->enqueue(ctxt, msg, queue));
+}
+
+void MessageStoreModule::dequeue(TransactionContext* ctxt,
+ const intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue& queue)
+{
+ TRANSFER_EXCEPTION(store->dequeue(ctxt, msg, queue));
+}
+
+void MessageStoreModule::flush(const qpid::broker::PersistableQueue& queue)
+{
+ TRANSFER_EXCEPTION(store->flush(queue));
+}
+
+uint32_t MessageStoreModule::outstandingQueueAIO(const PersistableQueue& queue)
+{
+ TRANSFER_EXCEPTION(return store->outstandingQueueAIO(queue));
+}
+
+std::auto_ptr<TransactionContext> MessageStoreModule::begin()
+{
+ TRANSFER_EXCEPTION(return store->begin());
+}
+
+std::auto_ptr<TPCTransactionContext> MessageStoreModule::begin(const std::string& xid)
+{
+ TRANSFER_EXCEPTION(return store->begin(xid));
+}
+
+void MessageStoreModule::prepare(TPCTransactionContext& txn)
+{
+ TRANSFER_EXCEPTION(store->prepare(txn));
+}
+
+void MessageStoreModule::commit(TransactionContext& ctxt)
+{
+ TRANSFER_EXCEPTION(store->commit(ctxt));
+}
+
+void MessageStoreModule::abort(TransactionContext& ctxt)
+{
+ TRANSFER_EXCEPTION(store->abort(ctxt));
+}
+
+void MessageStoreModule::collectPreparedXids(std::set<std::string>& xids)
+{
+ TRANSFER_EXCEPTION(store->collectPreparedXids(xids));
+}
+
+bool MessageStoreModule::isNull() const
+{
+ return NullMessageStore::isNullStore(store.get());
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/MessageStoreModule.h b/qpid/cpp/src/qpid/broker/MessageStoreModule.h
new file mode 100644
index 0000000000..ca7de3d318
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/MessageStoreModule.h
@@ -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.
+ *
+ */
+#ifndef _MessageStoreModule_
+#define _MessageStoreModule_
+
+#include "qpid/broker/MessageStore.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/RecoveryManager.h"
+
+#include <boost/intrusive_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace broker {
+
+/**
+ * A null implementation of the MessageStore interface
+ */
+class MessageStoreModule : public MessageStore
+{
+ boost::shared_ptr<MessageStore> store;
+ public:
+ MessageStoreModule(const boost::shared_ptr<MessageStore>& store);
+
+ std::auto_ptr<TransactionContext> begin();
+ std::auto_ptr<TPCTransactionContext> begin(const std::string& xid);
+ void prepare(TPCTransactionContext& txn);
+ void commit(TransactionContext& txn);
+ void abort(TransactionContext& txn);
+ void collectPreparedXids(std::set<std::string>& xids);
+
+ void create(PersistableQueue& queue, const framing::FieldTable& args);
+ void destroy(PersistableQueue& queue);
+ void create(const PersistableExchange& exchange, const framing::FieldTable& args);
+ void destroy(const PersistableExchange& exchange);
+ void bind(const PersistableExchange& exchange, const PersistableQueue& queue,
+ const std::string& key, const framing::FieldTable& args);
+ void unbind(const PersistableExchange& exchange, const PersistableQueue& queue,
+ const std::string& key, const framing::FieldTable& args);
+ void create(const PersistableConfig& config);
+ void destroy(const PersistableConfig& config);
+ void recover(RecoveryManager& queues);
+ void stage(const boost::intrusive_ptr<PersistableMessage>& msg);
+ void destroy(PersistableMessage& msg);
+ void appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg, const std::string& data);
+ void loadContent(const qpid::broker::PersistableQueue& queue,
+ const boost::intrusive_ptr<const PersistableMessage>& msg, std::string& data,
+ uint64_t offset, uint32_t length);
+
+ void enqueue(TransactionContext* ctxt,
+ const boost::intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue& queue);
+ void dequeue(TransactionContext* ctxt,
+ const boost::intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue& queue);
+ uint32_t outstandingQueueAIO(const PersistableQueue& queue);
+ void flush(const qpid::broker::PersistableQueue& queue);
+ bool isNull() const;
+
+ ~MessageStoreModule();
+};
+
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/Messages.h b/qpid/cpp/src/qpid/broker/Messages.h
new file mode 100644
index 0000000000..cd846a4973
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Messages.h
@@ -0,0 +1,101 @@
+#ifndef QPID_BROKER_MESSAGES_H
+#define QPID_BROKER_MESSAGES_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/framing/SequenceNumber.h"
+#include <boost/function.hpp>
+
+namespace qpid {
+namespace framing {
+class SequenceNumber;
+}
+namespace broker {
+class Message;
+class QueueCursor;
+
+/**
+ * This interface abstracts out the access to the messages held for
+ * delivery by a Queue instance. Note the the assumption at present is
+ * that all locking is done in the Queue itself.
+ */
+class Messages
+{
+ public:
+ typedef boost::function1<void, Message&> Functor;
+
+ virtual ~Messages() {}
+ /**
+ * @return the number of messages available for delivery.
+ */
+ virtual size_t size() = 0;
+
+ /**
+ * Called when a message is deleted from the queue.
+ */
+ virtual bool deleted(const QueueCursor&) = 0;
+ /**
+ * Makes a message available.
+ */
+ virtual void publish(const Message& added) = 0;
+ /**
+ * Retrieve the next message for the given cursor. A reference to
+ * the message is passed back via the second parameter.
+ *
+ * @return a pointer to the message if there is one, in which case
+ * the cursor that points to it is assigned to cursor; null
+ * otherwise.
+ */
+ virtual Message* next(QueueCursor& cursor) = 0;
+
+ /**
+ * Release the message i.e. return it to the available state
+ * unless it has already been deleted.
+ *
+ * @return a pointer to the Message if it is still in acquired state and
+ * hence can be released; null if it has already been deleted
+ */
+ virtual Message* release(const QueueCursor& cursor) = 0;
+ /**
+ * Find the message with the specified sequence number, returning
+ * a pointer if found, null otherwise. A cursor to the matched
+ * message can be passed back via the second parameter, regardless
+ * of whether the message is found, using this cursor to call
+ * next() will give the next message greater than position if one
+ * exists.
+ */
+ virtual Message* find(const framing::SequenceNumber&, QueueCursor*) = 0;
+
+ /**
+ * Find the message at the specified position, returning a pointer if
+ * found, null otherwise.
+ */
+ virtual Message* find(const QueueCursor&) = 0;
+
+ /**
+ * Apply, the functor to each message held
+ */
+ virtual void foreach(Functor) = 0;
+ private:
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_MESSAGES_H*/
diff --git a/qpid/cpp/src/qpid/broker/NameGenerator.cpp b/qpid/cpp/src/qpid/broker/NameGenerator.cpp
new file mode 100644
index 0000000000..e7f193d546
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/NameGenerator.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/NameGenerator.h"
+#include <sstream>
+
+using namespace qpid::broker;
+
+NameGenerator::NameGenerator(const std::string& _base) : base(_base), counter(1) {}
+
+std::string NameGenerator::generate(){
+ std::stringstream ss;
+ ss << base << counter++;
+ return ss.str();
+}
diff --git a/qpid/cpp/src/qpid/broker/NameGenerator.h b/qpid/cpp/src/qpid/broker/NameGenerator.h
new file mode 100644
index 0000000000..2e9f7febe2
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/NameGenerator.h
@@ -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.
+ *
+ */
+#ifndef _NameGenerator_
+#define _NameGenerator_
+
+#include <string>
+
+namespace qpid {
+ namespace broker {
+ class NameGenerator{
+ const std::string base;
+ unsigned int counter;
+ public:
+ NameGenerator(const std::string& base);
+ std::string generate();
+ };
+ const std::string QPID_NAME_PREFIX("qpid."); // reserved for private names
+ }
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/NullMessageStore.cpp b/qpid/cpp/src/qpid/broker/NullMessageStore.cpp
new file mode 100644
index 0000000000..f2ff7d0e6e
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/NullMessageStore.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/broker/NullMessageStore.h"
+#include "qpid/broker/MessageStoreModule.h"
+#include "qpid/broker/RecoveryManager.h"
+#include "qpid/log/Statement.h"
+#include "qpid/framing/reply_exceptions.h"
+
+#include <iostream>
+
+using boost::intrusive_ptr;
+
+namespace qpid{
+namespace broker{
+
+const std::string nullxid = "";
+
+class SimpleDummyCtxt : public TransactionContext {};
+
+class DummyCtxt : public TPCTransactionContext
+{
+ const std::string xid;
+public:
+ DummyCtxt(const std::string& _xid) : xid(_xid) {}
+ static std::string getXid(TransactionContext& ctxt)
+ {
+ DummyCtxt* c(dynamic_cast<DummyCtxt*>(&ctxt));
+ return c ? c->xid : nullxid;
+ }
+};
+
+NullMessageStore::NullMessageStore() : nextPersistenceId(1) {}
+
+void NullMessageStore::create(PersistableQueue& queue, const framing::FieldTable& /*args*/)
+{
+ queue.setPersistenceId(nextPersistenceId++);
+}
+
+void NullMessageStore::destroy(PersistableQueue&) {}
+
+void NullMessageStore::create(const PersistableExchange& exchange, const framing::FieldTable& /*args*/)
+{
+ exchange.setPersistenceId(nextPersistenceId++);
+}
+
+void NullMessageStore::destroy(const PersistableExchange& ) {}
+
+void NullMessageStore::bind(const PersistableExchange&, const PersistableQueue&, const std::string&, const framing::FieldTable&){}
+
+void NullMessageStore::unbind(const PersistableExchange&, const PersistableQueue&, const std::string&, const framing::FieldTable&){}
+
+void NullMessageStore::create(const PersistableConfig& config)
+{
+ config.setPersistenceId(nextPersistenceId++);
+}
+
+void NullMessageStore::destroy(const PersistableConfig&) {}
+
+void NullMessageStore::recover(RecoveryManager&) {}
+
+void NullMessageStore::stage(const intrusive_ptr<PersistableMessage>&) {}
+
+void NullMessageStore::destroy(PersistableMessage&) {}
+
+void NullMessageStore::appendContent(const intrusive_ptr<const PersistableMessage>&, const std::string&) {}
+
+void NullMessageStore::loadContent(const qpid::broker::PersistableQueue&,
+ const intrusive_ptr<const PersistableMessage>&,
+ std::string&, uint64_t, uint32_t)
+{
+ throw qpid::framing::InternalErrorException("Can't load content; persistence not enabled");
+}
+
+void NullMessageStore::enqueue(TransactionContext*,
+ const intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue&)
+{
+ msg->enqueueComplete();
+}
+
+void NullMessageStore::dequeue(TransactionContext*,
+ const intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue&)
+{
+ msg->dequeueComplete();
+}
+
+void NullMessageStore::flush(const qpid::broker::PersistableQueue&) {}
+
+uint32_t NullMessageStore::outstandingQueueAIO(const PersistableQueue& ) {
+ return 0;
+}
+
+std::auto_ptr<TransactionContext> NullMessageStore::begin()
+{
+ return std::auto_ptr<TransactionContext>(new SimpleDummyCtxt());
+}
+
+std::auto_ptr<TPCTransactionContext> NullMessageStore::begin(const std::string& xid)
+{
+ return std::auto_ptr<TPCTransactionContext>(new DummyCtxt(xid));
+}
+
+void NullMessageStore::prepare(TPCTransactionContext& ctxt)
+{
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock);
+ prepared.insert(DummyCtxt::getXid(ctxt));
+}
+
+void NullMessageStore::commit(TransactionContext& ctxt)
+{
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock);
+ prepared.erase(DummyCtxt::getXid(ctxt));
+}
+
+void NullMessageStore::abort(TransactionContext& ctxt)
+{
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock);
+ prepared.erase(DummyCtxt::getXid(ctxt));
+}
+
+void NullMessageStore::collectPreparedXids(std::set<std::string>& out)
+{
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock);
+ out.insert(prepared.begin(), prepared.end());
+}
+
+bool NullMessageStore::isNull() const
+{
+ return true;
+}
+
+bool NullMessageStore::isNullStore(const MessageStore* store)
+{
+ const MessageStoreModule* wrapper = dynamic_cast<const MessageStoreModule*>(store);
+ if (wrapper) {
+ return wrapper->isNull();
+ } else {
+ const NullMessageStore* test = dynamic_cast<const NullMessageStore*>(store);
+ return test && test->isNull();
+ }
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/NullMessageStore.h b/qpid/cpp/src/qpid/broker/NullMessageStore.h
new file mode 100644
index 0000000000..9f394d1fde
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/NullMessageStore.h
@@ -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.
+ *
+ */
+#ifndef _NullMessageStore_
+#define _NullMessageStore_
+
+#include <set>
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/broker/MessageStore.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/sys/Mutex.h"
+
+#include <boost/intrusive_ptr.hpp>
+
+namespace qpid {
+namespace broker {
+
+/**
+ * A null implementation of the MessageStore interface
+ */
+class QPID_BROKER_CLASS_EXTERN NullMessageStore : public MessageStore
+{
+ std::set<std::string> prepared;
+ uint64_t nextPersistenceId;
+ qpid::sys::Mutex lock;
+ public:
+ QPID_BROKER_EXTERN NullMessageStore();
+
+ QPID_BROKER_EXTERN virtual std::auto_ptr<TransactionContext> begin();
+ QPID_BROKER_EXTERN virtual std::auto_ptr<TPCTransactionContext> begin(const std::string& xid);
+ QPID_BROKER_EXTERN virtual void prepare(TPCTransactionContext& txn);
+ QPID_BROKER_EXTERN virtual void commit(TransactionContext& txn);
+ QPID_BROKER_EXTERN virtual void abort(TransactionContext& txn);
+ QPID_BROKER_EXTERN virtual void collectPreparedXids(std::set<std::string>& xids);
+
+ QPID_BROKER_EXTERN virtual void create(PersistableQueue& queue,
+ const framing::FieldTable& args);
+ QPID_BROKER_EXTERN virtual void destroy(PersistableQueue& queue);
+ QPID_BROKER_EXTERN virtual void create(const PersistableExchange& exchange,
+ const framing::FieldTable& args);
+ QPID_BROKER_EXTERN virtual void destroy(const PersistableExchange& exchange);
+
+ QPID_BROKER_EXTERN virtual void bind(const PersistableExchange& exchange,
+ const PersistableQueue& queue,
+ const std::string& key,
+ const framing::FieldTable& args);
+ QPID_BROKER_EXTERN virtual void unbind(const PersistableExchange& exchange,
+ const PersistableQueue& queue,
+ const std::string& key,
+ const framing::FieldTable& args);
+ QPID_BROKER_EXTERN virtual void create(const PersistableConfig& config);
+ QPID_BROKER_EXTERN virtual void destroy(const PersistableConfig& config);
+ QPID_BROKER_EXTERN virtual void recover(RecoveryManager& queues);
+ QPID_BROKER_EXTERN virtual void stage(const boost::intrusive_ptr<PersistableMessage>& msg);
+ QPID_BROKER_EXTERN virtual void destroy(PersistableMessage& msg);
+ QPID_BROKER_EXTERN virtual void appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg,
+ const std::string& data);
+ QPID_BROKER_EXTERN virtual void loadContent(const qpid::broker::PersistableQueue& queue,
+ const boost::intrusive_ptr<const PersistableMessage>& msg,
+ std::string& data,
+ uint64_t offset,
+ uint32_t length);
+ QPID_BROKER_EXTERN virtual void enqueue(TransactionContext* ctxt,
+ const boost::intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue& queue);
+ QPID_BROKER_EXTERN virtual void dequeue(TransactionContext* ctxt,
+ const boost::intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue& queue);
+ QPID_BROKER_EXTERN virtual uint32_t outstandingQueueAIO(const PersistableQueue& queue);
+ QPID_BROKER_EXTERN virtual void flush(const qpid::broker::PersistableQueue& queue);
+ ~NullMessageStore(){}
+
+ QPID_BROKER_EXTERN virtual bool isNull() const;
+ static bool isNullStore(const MessageStore*);
+};
+
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/ObjectFactory.cpp b/qpid/cpp/src/qpid/broker/ObjectFactory.cpp
new file mode 100644
index 0000000000..39edaa9fab
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/ObjectFactory.cpp
@@ -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.
+ *
+ */
+#include "ObjectFactory.h"
+#include "Broker.h"
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace broker {
+
+bool ObjectFactoryRegistry::createObject(Broker& broker, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties,
+ const std::string& userId, const std::string& connectionId)
+{
+ for (Factories::iterator i = factories.begin(); i != factories.end(); ++i)
+ {
+ if ((*i)->createObject(broker, type, name, properties, userId, connectionId)) return true;
+ }
+ return false;
+}
+
+bool ObjectFactoryRegistry::deleteObject(Broker& broker, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties,
+ const std::string& userId, const std::string& connectionId)
+{
+ for (Factories::iterator i = factories.begin(); i != factories.end(); ++i)
+ {
+ if ((*i)->deleteObject(broker, type, name, properties, userId, connectionId)) return true;
+ }
+ return false;
+}
+
+bool ObjectFactoryRegistry::recoverObject(Broker& broker, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties,
+ uint64_t persistenceId)
+{
+ for (Factories::iterator i = factories.begin(); i != factories.end(); ++i)
+ {
+ try {
+ if ((*i)->recoverObject(broker, type, name, properties, persistenceId)) return true;
+ } catch (const std::exception& e) {
+ QPID_LOG(warning, "Error while recovering object " << name << " of type " << type << ": " << e.what());
+ }
+ }
+ return false;
+}
+
+ObjectFactoryRegistry::~ObjectFactoryRegistry()
+{
+ for (Factories::iterator i = factories.begin(); i != factories.end(); ++i)
+ {
+ delete *i;
+ }
+ factories.clear();
+}
+void ObjectFactoryRegistry::add(ObjectFactory* factory)
+{
+ factories.push_back(factory);
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/ObjectFactory.h b/qpid/cpp/src/qpid/broker/ObjectFactory.h
new file mode 100644
index 0000000000..d1b570da2f
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/ObjectFactory.h
@@ -0,0 +1,68 @@
+#ifndef QPID_BROKER_OBJECTFACTORY_H
+#define QPID_BROKER_OBJECTFACTORY_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/types/Variant.h"
+#include <vector>
+
+namespace qpid {
+namespace broker {
+
+class Broker;
+
+/**
+ * An extension point through which plugins can register functionality
+ * for creating (and deleting) particular types of objects via the
+ * Broker::createObject() method (or deleteObject()), invoked via management.
+ */
+class ObjectFactory
+{
+ public:
+ virtual bool createObject(Broker&, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties,
+ const std::string& userId, const std::string& connectionId) = 0;
+ virtual bool deleteObject(Broker&, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties,
+ const std::string& userId, const std::string& connectionId) = 0;
+ virtual bool recoverObject(Broker&, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties, uint64_t persistenceId) = 0;
+ virtual ~ObjectFactory() {}
+ private:
+};
+
+class ObjectFactoryRegistry : public ObjectFactory
+{
+ public:
+ bool createObject(Broker&, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties,
+ const std::string& userId, const std::string& connectionId);
+ bool deleteObject(Broker&, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties,
+ const std::string& userId, const std::string& connectionId);
+ bool recoverObject(Broker&, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties, uint64_t persistenceId);
+
+ ~ObjectFactoryRegistry();
+ QPID_BROKER_EXTERN void add(ObjectFactory*);
+ private:
+ typedef std::vector<ObjectFactory*> Factories;
+ Factories factories;
+};
+
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_OBJECTFACTORY_H*/
diff --git a/qpid/cpp/src/qpid/broker/Observers.h b/qpid/cpp/src/qpid/broker/Observers.h
new file mode 100644
index 0000000000..efed377b33
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Observers.h
@@ -0,0 +1,93 @@
+#ifndef QPID_BROKER_OBSERVERS_H
+#define QPID_BROKER_OBSERVERS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/Mutex.h"
+#include <boost/shared_ptr.hpp>
+#include <set>
+#include <algorithm>
+
+namespace qpid {
+namespace broker {
+
+/**
+ * Base class for collections of observers with thread-safe add/remove and traversal.
+ */
+template <class Observer>
+class Observers
+{
+ public:
+ typedef boost::shared_ptr<Observer> ObserverPtr;
+
+ void add(const ObserverPtr& observer) {
+ sys::Mutex::ScopedLock l(lock);
+ observers.insert(observer);
+ }
+
+ void remove(const ObserverPtr& observer) {
+ sys::Mutex::ScopedLock l(lock);
+ observers.erase(observer) ;
+ }
+
+ /** Iterate over the observers. */
+ template <class F> void each(F f) {
+ Set copy; // Make a copy and iterate outside the lock.
+ {
+ sys::Mutex::ScopedLock l(lock);
+ copy = observers;
+ }
+ std::for_each(copy.begin(), copy.end(), f);
+ }
+
+ template <class T> boost::shared_ptr<T> findType() const {
+ sys::Mutex::ScopedLock l(lock);
+ typename Set::const_iterator i =
+ std::find_if(observers.begin(), observers.end(), &isA<T>);
+ return i == observers.end() ?
+ boost::shared_ptr<T>() : boost::dynamic_pointer_cast<T>(*i);
+ }
+
+ protected:
+ typedef std::set<ObserverPtr> Set;
+ Observers() : lock(myLock) {}
+
+ /** Specify a lock for the Observers to use */
+ Observers(sys::Mutex& l) : lock(l) {}
+
+ /** Iterate over the observers without taking the lock, caller must hold the lock */
+ template <class F> void each(F f, const sys::Mutex::ScopedLock&) {
+ std::for_each(observers.begin(), observers.end(), f);
+ }
+
+ template <class T> static bool isA(const ObserverPtr&o) {
+ return !!boost::dynamic_pointer_cast<T>(o);
+ }
+
+ mutable sys::Mutex myLock;
+ sys::Mutex& lock;
+ Set observers;
+};
+
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_OBSERVERS_H*/
diff --git a/qpid/cpp/src/qpid/broker/OwnershipToken.h b/qpid/cpp/src/qpid/broker/OwnershipToken.h
new file mode 100644
index 0000000000..2fc51408b6
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/OwnershipToken.h
@@ -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.
+ *
+ */
+#ifndef _OwnershipToken_
+#define _OwnershipToken_
+
+namespace qpid {
+namespace broker {
+
+class OwnershipToken{
+public:
+ virtual bool isLocal(const OwnershipToken* t) const { return this==t; };
+ virtual ~OwnershipToken(){}
+};
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/PagedQueue.cpp b/qpid/cpp/src/qpid/broker/PagedQueue.cpp
new file mode 100644
index 0000000000..b5edfb89c0
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/PagedQueue.cpp
@@ -0,0 +1,460 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/PagedQueue.h"
+#include "qpid/broker/Protocol.h"
+#include "qpid/broker/QueueCursor.h"
+#include "qpid/broker/Message.h"
+#include "qpid/log/Statement.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/sys/Time.h"
+#include <string.h>
+
+namespace qpid {
+namespace broker {
+namespace {
+using qpid::sys::AbsTime;
+using qpid::sys::Duration;
+using qpid::sys::ZERO;
+using qpid::sys::FAR_FUTURE;
+using qpid::sys::MemoryMappedFile;
+const uint32_t OVERHEAD(4/*content-size*/ + 4/*sequence-number*/ + 8/*persistence-id*/ + 8/*expiration*/);
+
+size_t encodedSize(const Message& msg)
+{
+ return msg.getPersistentContext()->encodedSize() + OVERHEAD;
+}
+
+size_t encode(const Message& msg, char* data, size_t size)
+{
+ uint32_t encoded = msg.getPersistentContext()->encodedSize();
+ uint32_t required = encoded + OVERHEAD;
+ if (required > size) return 0;
+ qpid::framing::Buffer buffer(data, required);
+ buffer.putLong(encoded);
+ buffer.putLong(msg.getSequence());
+ buffer.putLongLong(msg.getPersistentContext()->getPersistenceId());
+ sys::AbsTime expiration = msg.getExpiration();
+ int64_t t(0);
+ if (expiration < FAR_FUTURE) {
+ // Just need an integer that will round trip
+ t = Duration(ZERO, expiration);
+ }
+ buffer.putLongLong(t);
+ msg.getPersistentContext()->encode(buffer);
+ assert(buffer.getPosition() == required);
+ return required;
+}
+
+size_t decode(ProtocolRegistry& protocols, Message& msg, const char* data, size_t size)
+{
+ qpid::framing::Buffer metadata(const_cast<char*>(data), size);
+ uint32_t encoded = metadata.getLong();
+ uint32_t sequence = metadata.getLong();
+ uint64_t persistenceId = metadata.getLongLong();
+ int64_t t = metadata.getLongLong();
+ assert(metadata.available() >= encoded);
+ qpid::framing::Buffer buffer(const_cast<char*>(data) + metadata.getPosition(), encoded);
+ msg = protocols.decode(buffer);
+ assert(buffer.getPosition() == encoded);
+ msg.setSequence(qpid::framing::SequenceNumber(sequence));
+ msg.getPersistentContext()->setPersistenceId(persistenceId);
+ if (t) {
+ sys::AbsTime expiration(ZERO, t);
+ msg.getSharedState().setExpiration(expiration);
+ }
+ return encoded + metadata.getPosition();
+}
+
+}
+
+PagedQueue::PagedQueue(const std::string& name_, const std::string& directory, uint m, uint factor, ProtocolRegistry& p)
+ : name(name_), pageSize(file.getPageSize()*factor), maxLoaded(m), protocols(p), offset(0), loaded(0), version(0)
+{
+ if (directory.empty()) {
+ throw qpid::Exception(QPID_MSG("Cannot create paged queue: No paged queue directory specified"));
+ }
+ file.open(name, directory);
+ QPID_LOG(debug, "PagedQueue[" << name << "]");
+}
+
+PagedQueue::~PagedQueue()
+{
+ file.close();
+}
+
+size_t PagedQueue::size()
+{
+ size_t total(0);
+ for (Used::const_iterator i = used.begin(); i != used.end(); ++i) {
+ total += i->second.available();
+ }
+ return total;
+}
+
+bool PagedQueue::deleted(const QueueCursor& cursor)
+{
+ if (cursor.valid) {
+ Used::iterator page = findPage(cursor.position, false);
+ if (page == used.end()) {
+ return false;
+ }
+ page->second.deleted(cursor.position);
+ if (page->second.empty()) {
+ //move page to free list
+ --loaded;
+ page->second.clear(file);
+ free.push_back(page->second);
+ used.erase(page);
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void PagedQueue::publish(const Message& added)
+{
+ if (encodedSize(added) > pageSize) {
+ QPID_LOG(error, "Message is larger than page size for queue " << name);
+ throw qpid::framing::PreconditionFailedException(QPID_MSG("Message is larger than page size for queue " << name));
+ }
+ Used::reverse_iterator i = used.rbegin();
+ if (i != used.rend()) {
+ if (!i->second.isLoaded()) load(i->second);
+ if (i->second.add(added)) return;
+ }
+ //used is empty or last page is full, need to add a new page
+ if (!newPage(added.getSequence()).add(added)) {
+ QPID_LOG(error, "Could not add message to paged queue " << name);
+ throw qpid::Exception(QPID_MSG("Could not add message to paged queue " << name));
+ }
+}
+
+Message* PagedQueue::next(QueueCursor& cursor)
+{
+ Used::iterator i = used.begin();
+ if (cursor.valid) {
+ qpid::framing::SequenceNumber position(cursor.position);
+ ++position;
+ i = findPage(position, true);
+ if (i == used.end() && !used.empty() && used.begin()->first > position) i = used.begin();
+ }
+ while (i != used.end()) {
+ if (!i->second.isLoaded()) load(i->second);
+ Message* m = i->second.next(version, cursor);
+ QPID_LOG(debug, "PagedQueue::next(" << cursor.valid << ":" << cursor.position << "): " << m);
+ if (m) return m;
+ ++i;
+ }
+ QPID_LOG(debug, "PagedQueue::next(" << cursor.valid << ":" << cursor.position << ") returning 0 ");
+ return 0;
+}
+
+Message* PagedQueue::release(const QueueCursor& cursor)
+{
+ if (cursor.valid) {
+ Used::iterator i = findPage(cursor.position, true);
+ if (i == used.end()) return 0;
+ return i->second.release(cursor.position);
+ } else {
+ return 0;
+ }
+}
+
+Message* PagedQueue::find(const framing::SequenceNumber& position, QueueCursor* cursor)
+{
+ Used::iterator i = findPage(position, true);
+ if (i != used.end()) {
+ Message* m = i->second.find(position);
+ if (cursor) {
+ cursor->setPosition(version, m ? m->getSequence() : position);
+ }
+ return m;
+ } else {
+ return 0;
+ }
+}
+
+void PagedQueue::foreach(Functor)
+{
+ //TODO:
+}
+
+Message* PagedQueue::find(const QueueCursor& cursor)
+{
+ if (cursor.valid) return find(cursor.position, 0);
+ else return 0;
+}
+
+PagedQueue::Page::Page(size_t s, size_t o) : size(s), offset(o), region(0), used(0)
+{
+ QPID_LOG(debug, "Created Page[" << offset << "], size=" << size);
+}
+
+void PagedQueue::Page::deleted(qpid::framing::SequenceNumber s)
+{
+ if (isLoaded()) {
+ Message* message = find(s);
+ assert(message);//could this ever legitimately be 0?
+ message->setState(DELETED);
+ }
+ contents.remove(s);
+ acquired.remove(s);
+}
+
+Message* PagedQueue::Page::release(qpid::framing::SequenceNumber s)
+{
+ Message* m = find(s);
+ if (m) {
+ m->setState(AVAILABLE);
+ }
+ acquired.remove(s);
+ return m;
+}
+
+bool PagedQueue::Page::add(const Message& message)
+{
+ assert(region);
+ assert (size >= used);
+ size_t encoded = encode(message, region + used, size - used);
+ QPID_LOG(debug, "Calling Page[" << offset << "]::add() used=" << used << ", size=" << size << ", encoded=" << encoded << ")");
+ if (encoded) {
+ used += encoded;
+ messages.push_back(message);
+ messages.back().setState(AVAILABLE);
+ contents.add(message.getSequence());
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool PagedQueue::Page::empty() const
+{
+ return contents.empty();
+}
+
+bool PagedQueue::Page::isLoaded() const
+{
+ return region;
+}
+
+Message* PagedQueue::Page::next(uint32_t version, QueueCursor& cursor)
+{
+ if (messages.empty()) return 0;
+
+ qpid::framing::SequenceNumber position;
+ if (cursor.valid) {
+ position = cursor.position + 1;
+ if (position < messages.front().getSequence()) {
+ position = messages.front().getSequence();
+ cursor.setPosition(position, version);
+ }
+ } else {
+ position = messages.front().getSequence();
+ cursor.setPosition(position, version);
+ }
+
+ Message* m;
+ do {
+ m = find(position);
+ if (m) cursor.setPosition(position, version);
+ ++position;
+ } while (m != 0 && !cursor.check(*m));
+ return m;
+ //if it is the first in the page, increment the hint count of the page
+ //if it is the last in the page, decrement the hint count of the page
+}
+
+/**
+ * Called before adding to the free list
+ */
+void PagedQueue::Page::clear(MemoryMappedFile& file)
+{
+ if (region) file.unmap(region, size);
+ region = 0;
+ used = 0;
+ contents.clear();
+ messages.clear();
+}
+
+size_t PagedQueue::Page::available() const
+{
+ return contents.size() - acquired.size();
+}
+
+Message* PagedQueue::Page::find(qpid::framing::SequenceNumber position)
+{
+ if (messages.size()) {
+ assert(position >= messages.front().getSequence());
+
+ size_t index = position - messages.front().getSequence();
+ if (index < messages.size()) return &(messages[index]);
+ else return 0;
+ } else {
+ //page is empty, is this an error?
+ QPID_LOG(warning, "Could not find message at " << position << "; empty page.");
+ return 0;
+ }
+
+ //if it is the first in the page, increment the hint count of the page
+ //if it is the last in the page, decrement the hint count of the page
+}
+
+void PagedQueue::Page::load(MemoryMappedFile& file, ProtocolRegistry& protocols)
+{
+ QPID_LOG(debug, "Page[" << offset << "]::load" << " used=" << used << ", size=" << size);
+ assert(region == 0);
+ region = file.map(offset, size);
+ assert(region != 0);
+ bool haveData = used > 0;
+ used = 4;//first 4 bytes are the count
+ if (haveData) {
+ qpid::framing::Buffer buffer(region, sizeof(uint32_t));
+ uint32_t count = buffer.getLong();
+ //decode messages into Page::messages
+ for (size_t i = 0; i < count; ++i) {
+ Message message;
+ used += decode(protocols, message, region + used, size - used);
+ if (!contents.contains(message.getSequence())) {
+ message.setState(DELETED);
+ QPID_LOG(debug, "Setting state to deleted for message loaded at " << message.getSequence());
+ } else if (acquired.contains(message.getSequence())) {
+ message.setState(ACQUIRED);
+ } else {
+ message.setState(AVAILABLE);
+ }
+ messages.push_back(message);
+ }
+ if (messages.size()) {
+ QPID_LOG(debug, "Page[" << offset << "]::load " << messages.size() << " messages loaded from "
+ << messages.front().getSequence() << " to " << messages.back().getSequence());
+ } else {
+ QPID_LOG(debug, "Page[" << offset << "]::load no messages loaded");
+ }
+ }//else there is nothing we need to explicitly load, just needed to map region
+}
+
+void PagedQueue::Page::unload(MemoryMappedFile& file)
+{
+ if (messages.size()) {
+ QPID_LOG(debug, "Page[" << offset << "]::unload " << messages.size() << " messages to unload from "
+ << messages.front().getSequence() << " to " << messages.back().getSequence());
+ } else {
+ QPID_LOG(debug, "Page[" << offset << "]::unload no messages to unload");
+ }
+ for (std::deque<Message>::iterator i = messages.begin(); i != messages.end(); ++i) {
+ if (i->getState() == ACQUIRED) acquired.add(i->getSequence());
+ }
+ uint32_t count = messages.size();
+ qpid::framing::Buffer buffer(region, sizeof(uint32_t));
+ buffer.putLong(count);
+ file.flush(region, size);
+ file.unmap(region, size);
+ //remove messages from memory
+ messages.clear();
+ region = 0;
+}
+
+void PagedQueue::load(Page& page)
+{
+ //if needed, release another page
+ if (loaded == maxLoaded) {
+ //which page to select?
+ Used::reverse_iterator i = used.rbegin();
+ while (i != used.rend() && !i->second.isLoaded()) {
+ ++i;
+ }
+ assert(i != used.rend());
+ unload(i->second);
+ }
+ page.load(file, protocols);
+ ++loaded;
+ QPID_LOG(debug, "PagedQueue[" << name << "] loaded page, " << loaded << " pages now loaded");
+}
+
+void PagedQueue::unload(Page& page)
+{
+ page.unload(file);
+ --loaded;
+ QPID_LOG(debug, "PagedQueue[" << name << "] unloaded page, " << loaded << " pages now loaded");
+}
+
+
+PagedQueue::Page& PagedQueue::newPage(qpid::framing::SequenceNumber id)
+{
+ if (loaded == maxLoaded) {
+ //need to release a page from memory to make way for a new one
+
+ //choose last one?
+ Used::reverse_iterator i = used.rbegin();
+ while (!i->second.isLoaded() && i != used.rend()) {
+ ++i;
+ }
+ assert(i != used.rend());
+ unload(i->second);
+ }
+ if (free.empty()) {
+ //need to extend file and add some pages to the free list
+ addPages(4/*arbitrary number, should this be config item?*/);
+ }
+ std::pair<Used::iterator, bool> result = used.insert(Used::value_type(id, free.front()));
+ QPID_LOG(debug, "Added page for sequence starting from " << id);
+ assert(result.second);
+ free.pop_front();
+ load(result.first->second);
+ return result.first->second;
+}
+
+void PagedQueue::addPages(size_t count)
+{
+ for (size_t i = 0; i < count; ++i) {
+ free.push_back(Page(pageSize, offset));
+ offset += pageSize;
+ file.expand(offset);
+ }
+ QPID_LOG(debug, "Added " << count << " pages to free list; now have " << used.size() << " used, and " << free.size() << " free");
+}
+
+PagedQueue::Used::iterator PagedQueue::findPage(const QueueCursor& cursor)
+{
+ Used::iterator i = used.begin();
+ if (cursor.valid) {
+ i = findPage(cursor.position, true);
+ } else if (i != used.end() && !i->second.isLoaded()) {
+ load(i->second);
+ }
+ return i;
+}
+
+PagedQueue::Used::iterator PagedQueue::findPage(qpid::framing::SequenceNumber n, bool loadIfRequired)
+{
+ Used::iterator i = used.end();
+ for (Used::iterator j = used.begin(); j != used.end() && j->first <= n; ++j) {
+ i = j;
+ }
+ if (loadIfRequired && i != used.end() && !i->second.isLoaded()) {
+ load(i->second);
+ }
+ return i;
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/PagedQueue.h b/qpid/cpp/src/qpid/broker/PagedQueue.h
new file mode 100644
index 0000000000..c8a9f13fc7
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/PagedQueue.h
@@ -0,0 +1,100 @@
+#ifndef QPID_BROKER_PAGEDQUEUE_H
+#define QPID_BROKER_PAGEDQUEUE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/Messages.h"
+#include "qpid/broker/Message.h"
+#include "qpid/framing/SequenceSet.h"
+#include "qpid/sys/MemoryMappedFile.h"
+#include <deque>
+#include <list>
+#include <map>
+
+namespace qpid {
+namespace broker {
+class ProtocolRegistry;
+/**
+ *
+ */
+class PagedQueue : public Messages {
+ public:
+ PagedQueue(const std::string& name, const std::string& directory, uint maxLoaded, uint pageFactor, ProtocolRegistry& protocols);
+ ~PagedQueue();
+ size_t size();
+ bool deleted(const QueueCursor&);
+ void publish(const Message& added);
+ Message* next(QueueCursor& cursor);
+ Message* release(const QueueCursor& cursor);
+ Message* find(const framing::SequenceNumber&, QueueCursor*);
+ Message* find(const QueueCursor&);
+ void foreach(Functor);
+ private:
+ class Page {
+ public:
+ Page(size_t size, size_t offset);
+ void read();//decode messages from memory mapped file
+ void write();//encode messages into memory mapped file
+ bool isLoaded() const;
+ bool empty() const;
+ void deleted(qpid::framing::SequenceNumber);
+ Message* release(qpid::framing::SequenceNumber);
+ bool add(const Message&);
+ Message* next(uint32_t version, QueueCursor&);
+ Message* find(qpid::framing::SequenceNumber);
+ void load(qpid::sys::MemoryMappedFile&,ProtocolRegistry&);
+ void unload(qpid::sys::MemoryMappedFile&);
+ void clear(qpid::sys::MemoryMappedFile&);
+ size_t available() const;
+ private:
+ size_t size;
+ size_t offset;
+
+ char* region;//0 implies not mapped
+ qpid::framing::SequenceSet contents;
+ qpid::framing::SequenceSet acquired;
+ std::deque<Message> messages;//decoded representation
+ size_t used;//amount of data used to encode current set of messages held
+ };
+
+ qpid::sys::MemoryMappedFile file;
+ std::string name;
+ const size_t pageSize;
+ const uint maxLoaded;
+ ProtocolRegistry& protocols;
+ size_t offset;
+ typedef std::map<qpid::framing::SequenceNumber, Page> Used;
+ Used used;
+ std::list<Page> free;
+ uint loaded;
+ uint32_t version;
+
+ void addPages(size_t count);
+ Page& newPage(qpid::framing::SequenceNumber);
+ Used::iterator findPage(const QueueCursor& cursor);
+ Used::iterator findPage(qpid::framing::SequenceNumber n, bool loadIfRequired);
+ void load(Page&);
+ void unload(Page&);
+ bool deleted(qpid::framing::SequenceNumber);
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_PAGEDQUEUE_H*/
diff --git a/qpid/cpp/src/qpid/broker/Persistable.h b/qpid/cpp/src/qpid/broker/Persistable.h
new file mode 100644
index 0000000000..444aca3295
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Persistable.h
@@ -0,0 +1,63 @@
+#ifndef _broker_Persistable_h
+#define _broker_Persistable_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/framing/amqp_types.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/RefCounted.h"
+
+namespace qpid {
+namespace broker {
+
+/**
+ * Base class for all persistable objects
+ */
+class Persistable : public virtual RefCounted
+{
+public:
+ /**
+ * Allows the store to attach its own identifier to this object
+ */
+ virtual void setPersistenceId(uint64_t id) const = 0;
+ /**
+ * Returns any identifier the store may have attached to this
+ * object
+ */
+ virtual uint64_t getPersistenceId() const = 0;
+ /**
+ * Encodes the persistable state of this object into the supplied
+ * buffer
+ */
+ virtual void encode(framing::Buffer& buffer) const = 0;
+ /**
+ * @returns the size of the buffer needed to encode this object
+ */
+ virtual uint32_t encodedSize() const = 0;
+
+ virtual ~Persistable() {};
+};
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/PersistableConfig.h b/qpid/cpp/src/qpid/broker/PersistableConfig.h
new file mode 100644
index 0000000000..8ddb84d129
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/PersistableConfig.h
@@ -0,0 +1,45 @@
+#ifndef _broker_PersistableConfig_h
+#define _broker_PersistableConfig_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <string>
+#include "qpid/broker/Persistable.h"
+
+namespace qpid {
+namespace broker {
+
+/**
+ * The interface used by general-purpose persistable configuration for
+ * the message store.
+ */
+class PersistableConfig : public Persistable
+{
+public:
+ virtual const std::string& getName() const = 0;
+ virtual ~PersistableConfig() {};
+};
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/PersistableExchange.h b/qpid/cpp/src/qpid/broker/PersistableExchange.h
new file mode 100644
index 0000000000..e1a0853247
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/PersistableExchange.h
@@ -0,0 +1,45 @@
+#ifndef _broker_PersistableExchange_h
+#define _broker_PersistableExchange_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <string>
+#include "qpid/broker/Persistable.h"
+
+namespace qpid {
+namespace broker {
+
+/**
+ * The interface exchanges must expose to the MessageStore in order to be
+ * persistable.
+ */
+class PersistableExchange : public Persistable
+{
+public:
+ virtual const std::string& getName() const = 0;
+ virtual ~PersistableExchange() {};
+};
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/PersistableMessage.cpp b/qpid/cpp/src/qpid/broker/PersistableMessage.cpp
new file mode 100644
index 0000000000..afc0976d61
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/PersistableMessage.cpp
@@ -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.
+ *
+ */
+
+
+#include "qpid/broker/PersistableMessage.h"
+#include "qpid/broker/Queue.h"
+#include <iostream>
+
+using namespace qpid::broker;
+
+namespace qpid {
+namespace broker {
+
+PersistableMessage::~PersistableMessage() {}
+PersistableMessage::PersistableMessage() : ingressCompletion(0), persistenceId(0) {}
+
+void PersistableMessage::setIngressCompletion(boost::intrusive_ptr<IngressCompletion> i)
+{
+ ingressCompletion = i.get();
+ /**
+ * What follows is a hack to account for the fact that the
+ * AsyncCompletion to use may be, but is not always, this same
+ * object.
+ *
+ * This is hopefully temporary, and allows the store interface to
+ * remain unchanged without requiring another object to be allocated
+ * for every message.
+ *
+ * The case in question is where a message previously passed to
+ * the store is modified by some other queue onto which it is
+ * pushed, and then again persisted to the store. These will be
+ * two separate PersistableMessage instances (since the latter now
+ * has different content), but need to share the same
+ * AsyncCompletion (since they refer to the same incoming transfer
+ * command).
+ */
+ if (static_cast<RefCounted*>(ingressCompletion) != static_cast<RefCounted*>(this)) {
+ holder = i;
+ }
+}
+
+void PersistableMessage::enqueueAsync(boost::shared_ptr<Queue> q)
+{
+ enqueueStart();
+ ingressCompletion->enqueueAsync(q);
+}
+
+void PersistableMessage::dequeueComplete() {}
+
+}}
+
+
diff --git a/qpid/cpp/src/qpid/broker/PersistableMessage.h b/qpid/cpp/src/qpid/broker/PersistableMessage.h
new file mode 100644
index 0000000000..cfcf8f0afc
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/PersistableMessage.h
@@ -0,0 +1,95 @@
+#ifndef _broker_PersistableMessage_h
+#define _broker_PersistableMessage_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <string>
+#include <list>
+#include <map>
+#include <boost/intrusive_ptr.hpp>
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/broker/Persistable.h"
+#include "qpid/framing/amqp_types.h"
+#include "qpid/framing/amqp_framing.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/broker/IngressCompletion.h"
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace types {
+class Variant;
+}
+namespace broker {
+
+class MessageStore;
+class Queue;
+
+/**
+ * Base class for persistable messages.
+ */
+class PersistableMessage : public Persistable
+{
+ /**
+ * "Ingress" messages == messages sent _to_ the broker.
+ * Tracks the number of outstanding asynchronous operations that must
+ * complete before an inbound message can be considered fully received by the
+ * broker. E.g. all enqueues have completed, the message has been written
+ * to store, credit has been replenished, etc. Once all outstanding
+ * operations have completed, the transfer of this message from the client
+ * may be considered complete.
+ */
+ IngressCompletion* ingressCompletion;
+ boost::intrusive_ptr<IngressCompletion> holder;
+ mutable uint64_t persistenceId;
+
+ public:
+ QPID_BROKER_EXTERN virtual ~PersistableMessage();
+ QPID_BROKER_EXTERN PersistableMessage();
+
+ virtual QPID_BROKER_EXTERN bool isPersistent() const = 0;
+
+ /** track the progress of a message received by the broker - see ingressCompletion above */
+ QPID_BROKER_INLINE_EXTERN bool isIngressComplete() { return ingressCompletion->isDone(); }
+ QPID_BROKER_INLINE_EXTERN IngressCompletion& getIngressCompletion() { return *ingressCompletion; }
+ QPID_BROKER_EXTERN void setIngressCompletion(boost::intrusive_ptr<IngressCompletion> i);
+
+ QPID_BROKER_INLINE_EXTERN void enqueueStart() { ingressCompletion->startCompleter(); }
+ QPID_BROKER_INLINE_EXTERN void enqueueComplete() { ingressCompletion->finishCompleter(); }
+
+ QPID_BROKER_EXTERN void enqueueAsync(boost::shared_ptr<Queue> queue);
+
+ QPID_BROKER_EXTERN void dequeueComplete();
+
+ uint64_t getPersistenceId() const { return persistenceId; }
+ void setPersistenceId(uint64_t _persistenceId) const { persistenceId = _persistenceId; }
+
+
+ virtual void decodeHeader(framing::Buffer& buffer) = 0;
+ virtual void decodeContent(framing::Buffer& buffer) = 0;
+ virtual uint32_t encodedHeaderSize() const = 0;
+ virtual boost::intrusive_ptr<PersistableMessage> merge(const std::map<std::string, qpid::types::Variant>& annotations) const = 0;
+};
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/PersistableObject.cpp b/qpid/cpp/src/qpid/broker/PersistableObject.cpp
new file mode 100644
index 0000000000..575ef09270
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/PersistableObject.cpp
@@ -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.
+ *
+ */
+#include "PersistableObject.h"
+#include "Broker.h"
+#include "qpid/amqp_0_10/Codecs.h"
+#include "qpid/amqp_0_10/CodecsInternal.h"
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace broker {
+namespace {
+const std::string UTF8("utf8");
+}
+PersistableObject::PersistableObject(const std::string& n, const std::string& t, const qpid::types::Variant::Map p) : name(n), type(t), properties(p), id(0) {}
+PersistableObject::PersistableObject() : id(0) {}
+PersistableObject::~PersistableObject() {}
+const std::string& PersistableObject::getName() const { return name; }
+const std::string& PersistableObject::getType() const { return type; }
+void PersistableObject::setPersistenceId(uint64_t i) const { id = i; }
+uint64_t PersistableObject::getPersistenceId() const { return id; }
+void PersistableObject::encode(framing::Buffer& buffer) const
+{
+ buffer.putShortString(type);
+ buffer.putMediumString(name);
+ qpid::amqp_0_10::encode(properties, qpid::amqp_0_10::encodedSize(properties), buffer);
+}
+uint32_t PersistableObject::encodedSize() const
+{
+ return type.size()+1 + name.size()+2 + qpid::amqp_0_10::encodedSize(properties);
+}
+void PersistableObject::decode(framing::Buffer& buffer)
+{
+ buffer.getShortString(type);
+ buffer.getMediumString(name);
+ qpid::framing::FieldTable ft;
+ buffer.get(ft);
+ qpid::amqp_0_10::translate(ft, properties);
+}
+bool PersistableObject::recover(Broker& broker)
+{
+ return broker.getObjectFactoryRegistry().recoverObject(broker, type, name, properties, id);
+}
+
+namespace {
+class RecoverableObject : public RecoverableConfig
+{
+ public:
+ RecoverableObject(boost::shared_ptr<PersistableObject> o) : object(o) {}
+ void setPersistenceId(uint64_t id) { object->setPersistenceId(id); }
+ private:
+ boost::shared_ptr<PersistableObject> object;
+};
+}
+boost::shared_ptr<RecoverableConfig> RecoveredObjects::recover(framing::Buffer& buffer)
+{
+ boost::shared_ptr<PersistableObject> object(new PersistableObject());
+ object->decode(buffer);
+ objects.push_back(object);
+ return boost::shared_ptr<RecoverableConfig>(new RecoverableObject(object));
+}
+void RecoveredObjects::restore(Broker& broker)
+{
+ //recover objects created through ObjectFactory
+ for (Objects::iterator i = objects.begin(); i != objects.end(); ++i) {
+ if (!(*i)->recover(broker)) {
+ QPID_LOG(warning, "Failed to recover object " << (*i)->name << " of type " << (*i)->type);
+ }
+ }
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/PersistableObject.h b/qpid/cpp/src/qpid/broker/PersistableObject.h
new file mode 100644
index 0000000000..da4bd44601
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/PersistableObject.h
@@ -0,0 +1,73 @@
+#ifndef QPID_BROKER_PERSISTABLEOBJECT_H
+#define QPID_BROKER_PERSISTABLEOBJECT_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/BrokerImportExport.h"
+#include "PersistableConfig.h"
+#include "qpid/types/Variant.h"
+#include <vector>
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace broker {
+class Broker;
+class RecoverableConfig;
+/**
+ * Generic persistence support for objects created through the brokers
+ * create method.
+ */
+class PersistableObject : public PersistableConfig
+{
+ public:
+ QPID_BROKER_EXTERN PersistableObject(const std::string& name, const std::string& type, const qpid::types::Variant::Map properties);
+ QPID_BROKER_EXTERN virtual ~PersistableObject();
+ QPID_BROKER_EXTERN const std::string& getName() const;
+ QPID_BROKER_EXTERN const std::string& getType() const;
+ QPID_BROKER_EXTERN void setPersistenceId(uint64_t id) const;
+ QPID_BROKER_EXTERN uint64_t getPersistenceId() const;
+ QPID_BROKER_EXTERN void encode(framing::Buffer& buffer) const;
+ QPID_BROKER_EXTERN uint32_t encodedSize() const;
+ friend class RecoveredObjects;
+ private:
+ std::string name;
+ std::string type;
+ qpid::types::Variant::Map properties;
+ mutable uint64_t id;
+
+ PersistableObject();
+ void decode(framing::Buffer& buffer);
+ bool recover(Broker&);
+};
+
+class RecoveredObjects
+{
+ public:
+ boost::shared_ptr<RecoverableConfig> recover(framing::Buffer&);
+ void restore(Broker&);
+ private:
+ typedef std::vector<boost::shared_ptr<PersistableObject> > Objects;
+ Objects objects;
+};
+
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_PERSISTABLEOBJECT_H*/
diff --git a/qpid/cpp/src/qpid/broker/PersistableQueue.h b/qpid/cpp/src/qpid/broker/PersistableQueue.h
new file mode 100644
index 0000000000..6733163084
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/PersistableQueue.h
@@ -0,0 +1,77 @@
+#ifndef _broker_PersistableQueue_h
+#define _broker_PersistableQueue_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <string>
+#include "qpid/broker/Persistable.h"
+#include "qpid/management/Manageable.h"
+
+namespace qpid {
+namespace broker {
+
+
+/**
+* Empty class to be used by any module that wanted to set an external per queue store into
+* persistableQueue
+*/
+
+class ExternalQueueStore : public management::Manageable
+{
+public:
+ virtual ~ExternalQueueStore() {};
+
+};
+
+
+/**
+ * The interface queues must expose to the MessageStore in order to be
+ * persistable.
+ */
+class PersistableQueue : public Persistable
+{
+public:
+ virtual const std::string& getName() const = 0;
+ virtual ~PersistableQueue() {
+ if (externalQueueStore) {
+ delete externalQueueStore;
+ externalQueueStore = 0;
+ }
+ };
+
+ virtual void setExternalQueueStore(ExternalQueueStore* inst) = 0;
+ virtual void flush() = 0;
+
+ inline ExternalQueueStore* getExternalQueueStore() const {return externalQueueStore;};
+
+ PersistableQueue():externalQueueStore(NULL){
+ };
+
+protected:
+ ExternalQueueStore* externalQueueStore;
+
+};
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/PriorityQueue.cpp b/qpid/cpp/src/qpid/broker/PriorityQueue.cpp
new file mode 100644
index 0000000000..5e60fe5cce
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/PriorityQueue.cpp
@@ -0,0 +1,234 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownersip. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/PriorityQueue.h"
+#include "qpid/broker/Message.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/QueueCursor.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/log/Statement.h"
+#include <algorithm>
+#include <cmath>
+#include <boost/bind.hpp>
+
+namespace qpid {
+namespace broker {
+namespace {
+class PriorityContext : public CursorContext {
+ public:
+ std::vector<QueueCursor> position;
+ PriorityContext(size_t levels, SubscriptionType type) : position(levels, QueueCursor(type)) {}
+};
+}
+
+
+PriorityQueue::PriorityQueue(int l) :
+ levels(l),
+ messages(levels, Deque(boost::bind(&PriorityQueue::priorityPadding, this, _1))),
+ counters(levels, framing::SequenceNumber()),
+ fifo(boost::bind(&PriorityQueue::fifoPadding, this, _1)),
+ frontLevel(0), haveFront(false), cached(false)
+{
+}
+
+bool PriorityQueue::deleted(const QueueCursor& c)
+{
+ MessagePointer* ptr = fifo.find(c);
+ if (ptr && ptr->holder) {
+ //mark the message as deleted
+ ptr->holder->message.setState(DELETED);
+ //clean the deque for the relevant priority level
+ boost::shared_ptr<PriorityContext> ctxt = boost::dynamic_pointer_cast<PriorityContext>(c.context);
+ messages[ptr->holder->priority].clean();
+ //stop referencing that message holder (it may now have been
+ //deleted)
+ ptr->holder = 0;
+ //clean fifo index
+ fifo.clean();
+ return true;
+ } else {
+ return false;
+ }
+}
+
+size_t PriorityQueue::size()
+{
+ return fifo.size();
+}
+
+Message* PriorityQueue::next(QueueCursor& cursor)
+{
+ boost::shared_ptr<PriorityContext> ctxt = boost::dynamic_pointer_cast<PriorityContext>(cursor.context);
+ if (!ctxt) {
+ ctxt = boost::shared_ptr<PriorityContext>(new PriorityContext(levels, CONSUMER));
+ cursor.context = ctxt;
+ }
+ if (cursor.type == REPLICATOR) {
+ //browse in fifo order
+ MessagePointer* ptr = fifo.next(cursor);
+ return ptr ? &(ptr->holder->message) : 0;
+ } else if (cursor.type == PURGE) {
+ //iterate over message in reverse priority order (i.e. purge lowest priority message first)
+ //ignore any fairshare configuration here as well
+ for (int p = 0; p < levels; ++p) {
+ MessageHolder* holder = messages[p].next(ctxt->position[p]);
+ if (holder) {
+ cursor.setPosition(holder->message.getSequence(), 0);
+ return &(holder->message);
+ }
+ }
+ return 0;
+ } else {
+ //check each level in turn, in priority order, for any more messages
+ Priority p = firstLevel();
+ do {
+ MessageHolder* holder = messages[p.current].next(ctxt->position[p.current]);
+ if (holder) {
+ cursor.setPosition(holder->message.getSequence(), 0);
+ return &(holder->message);
+ }
+ } while (nextLevel(p));
+ return 0;
+ }
+}
+
+Message* PriorityQueue::find(const QueueCursor& cursor)
+{
+ return find(cursor.position, 0);
+}
+
+Message* PriorityQueue::find(const framing::SequenceNumber& position, QueueCursor* cursor)
+{
+ MessagePointer* ptr = fifo.find(position, cursor);
+ return ptr ? &(ptr->holder->message) : 0;
+}
+
+void PriorityQueue::publish(const Message& published)
+{
+ MessageHolder holder;
+ holder.message = published;
+ holder.priority = getPriorityLevel(published);
+ holder.id = ++(counters[holder.priority]);
+ MessagePointer pointer;
+ pointer.holder = &(messages[holder.priority].publish(holder));
+ pointer.id = published.getSequence();
+ fifo.publish(pointer);
+}
+
+Message* PriorityQueue::release(const QueueCursor& cursor)
+{
+ MessagePointer* ptr = fifo.release(cursor);
+ if (ptr) {
+ messages[ptr->holder->priority].resetCursors();
+ return &(ptr->holder->message);
+ } else {
+ return 0;
+ }
+}
+
+void PriorityQueue::foreach(Functor f)
+{
+ fifo.foreach(f);
+}
+
+uint PriorityQueue::getPriorityLevel(const Message& m) const
+{
+ uint priority = m.getPriority();
+ //Use AMQP 0-10 approach to mapping priorities to a fixed level
+ //(see rule priority-level-implementation)
+ const uint firstLevel = 5 - uint(std::min(5.0, std::ceil((double) levels/2.0)));
+ if (priority <= firstLevel) return 0;
+ return std::min(priority - firstLevel, (uint)levels-1);
+}
+PriorityQueue::MessagePointer PriorityQueue::fifoPadding(qpid::framing::SequenceNumber id)
+{
+ PriorityQueue::MessagePointer pointer;
+ pointer.holder = 0;
+ pointer.id = id;
+ return pointer;
+}
+
+PriorityQueue::MessageHolder PriorityQueue::priorityPadding(qpid::framing::SequenceNumber id)
+{
+ PriorityQueue::MessageHolder holder;
+ holder.id = id;
+ holder.message.setState(DELETED);
+ return holder;
+}
+
+PriorityQueue::Priority PriorityQueue::firstLevel()
+{
+ return Priority(levels - 1);
+}
+bool PriorityQueue::nextLevel(Priority& p)
+{
+ if (p.current > 0) {
+ --(p.current);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+framing::SequenceNumber PriorityQueue::MessageHolder::getSequence() const
+{
+ return id;
+}
+void PriorityQueue::MessageHolder::setState(MessageState s)
+{
+ message.setState(s);
+}
+MessageState PriorityQueue::MessageHolder::getState() const
+{
+ return message.getState();
+}
+PriorityQueue::MessageHolder::operator Message&()
+{
+ return message;
+}
+framing::SequenceNumber PriorityQueue::MessagePointer::getSequence() const
+{
+ if (holder) {
+ return holder->message.getSequence();
+ } else {
+ //this is used when the instance is merely acting as padding
+ return id;
+ }
+}
+void PriorityQueue::MessagePointer::setState(MessageState s)
+{
+ if (holder) {
+ holder->message.setState(s);
+ }
+}
+MessageState PriorityQueue::MessagePointer::getState() const
+{
+ if (holder) {
+ return holder->message.getState();
+ } else {
+ return DELETED;
+ }
+}
+PriorityQueue::MessagePointer::operator Message&()
+{
+ assert(holder);
+ return holder->message;
+}
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/PriorityQueue.h b/qpid/cpp/src/qpid/broker/PriorityQueue.h
new file mode 100644
index 0000000000..16432bfb54
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/PriorityQueue.h
@@ -0,0 +1,109 @@
+#ifndef QPID_BROKER_PRIORITYQUEUE_H
+#define QPID_BROKER_PRIORITYQUEUE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/MessageDeque.h"
+#include "qpid/broker/IndexedDeque.h"
+#include "qpid/sys/IntegerTypes.h"
+#include <deque>
+#include <vector>
+
+namespace qpid {
+namespace broker {
+
+/**
+ * Basic priority queue with a configurable number of recognised
+ * priority levels. This is implemented as a separate deque per
+ * priority level.
+ *
+ * Browsing is FIFO not priority order. There is a MessageDeque
+ * for fast browsing.
+ */
+class PriorityQueue : public Messages
+{
+ public:
+ PriorityQueue(int levels);
+ virtual ~PriorityQueue() {}
+ size_t size();
+
+ bool deleted(const QueueCursor&);
+ void publish(const Message& added);
+ Message* next(QueueCursor&);
+ Message* release(const QueueCursor& cursor);
+ Message* find(const QueueCursor&);
+ Message* find(const framing::SequenceNumber&, QueueCursor*);
+
+ void foreach(Functor);
+ static uint getPriority(const Message&);
+ protected:
+ const int levels;
+ struct Priority
+ {
+ const int start;
+ int current;
+ Priority(int s) : start(s), current(start) {}
+ };
+ virtual Priority firstLevel();
+ virtual bool nextLevel(Priority& );
+
+ private:
+ struct MessageHolder
+ {
+ Message message;
+ int priority;
+ framing::SequenceNumber id;
+ framing::SequenceNumber getSequence() const;
+ void setState(MessageState);
+ MessageState getState() const;
+ operator Message&();
+ };
+ struct MessagePointer
+ {
+ MessageHolder* holder;
+ framing::SequenceNumber id;//used only for padding
+ framing::SequenceNumber getSequence() const;
+ void setState(MessageState);
+ MessageState getState() const;
+ operator Message&();
+ };
+ typedef IndexedDeque<MessageHolder> Deque;
+ typedef std::vector<Deque> PriorityLevels;
+ typedef std::vector<framing::SequenceNumber> Counters;
+
+ /** Holds pointers to messages (stored in the fifo index) separated by priority.
+ */
+ PriorityLevels messages;
+ Counters counters;
+ /** FIFO index of messages for fast browsing and indexing */
+ IndexedDeque<MessagePointer> fifo;
+ uint frontLevel;
+ bool haveFront;
+ bool cached;
+
+ uint getPriorityLevel(const Message&) const;
+ MessageHolder priorityPadding(qpid::framing::SequenceNumber);
+ MessagePointer fifoPadding(qpid::framing::SequenceNumber);
+};
+
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_PRIORITYQUEUE_H*/
diff --git a/qpid/cpp/src/qpid/broker/Protocol.cpp b/qpid/cpp/src/qpid/broker/Protocol.cpp
new file mode 100644
index 0000000000..e9e7892499
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Protocol.cpp
@@ -0,0 +1,141 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "Protocol.h"
+#include "qpid/broker/RecoverableMessageImpl.h"
+#include "qpid/amqp_0_10/Connection.h"
+#include "qpid/broker/amqp_0_10/Connection.h"
+#include "qpid/broker/amqp_0_10/MessageTransfer.h"
+#include "qpid/broker/SecureConnection.h"
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace broker {
+namespace {
+const std::string AMQP_0_10("amqp0-10");
+}
+ProtocolRegistry::ProtocolRegistry(const std::set<std::string>& e, Broker* b) : enabled(e), broker(b) {}
+
+qpid::sys::ConnectionCodec* ProtocolRegistry::create(const qpid::framing::ProtocolVersion& v, qpid::sys::OutputControl& o, const std::string& id, const qpid::sys::SecuritySettings& s)
+{
+ if (v == qpid::framing::ProtocolVersion(0, 10)) {
+ if (isEnabled(AMQP_0_10)) {
+ return create_0_10(o, id, s, false);
+ }
+ }
+ qpid::sys::ConnectionCodec* codec = 0;
+ for (Protocols::const_iterator i = protocols.begin(); !codec && i != protocols.end(); ++i) {
+ if (isEnabled(i->first)) {
+ codec = i->second->create(v, o, id, s);
+ }
+ }
+ return codec;
+}
+qpid::sys::ConnectionCodec* ProtocolRegistry::create(qpid::sys::OutputControl& o, const std::string& id, const qpid::sys::SecuritySettings& s)
+{
+ return create_0_10(o, id, s, true);
+}
+
+qpid::framing::ProtocolVersion ProtocolRegistry::supportedVersion() const
+{
+ if (isEnabled(AMQP_0_10)) {
+ return qpid::framing::ProtocolVersion(0,10);
+ } else {
+ for (Protocols::const_iterator i = protocols.begin(); i != protocols.end(); ++i) {
+ if (isEnabled(i->first)) {
+ return i->second->supportedVersion();
+ }
+ }
+ }
+ QPID_LOG(error, "No enabled protocols!");
+ return qpid::framing::ProtocolVersion(0,0);
+}
+
+bool ProtocolRegistry::isEnabled(const std::string& name) const
+{
+ return enabled.empty()/*if nothing is explicitly enabled, assume everything is*/ || enabled.find(name) != enabled.end();
+}
+
+
+typedef std::auto_ptr<qpid::amqp_0_10::Connection> CodecPtr;
+typedef std::auto_ptr<SecureConnection> SecureConnectionPtr;
+typedef std::auto_ptr<qpid::broker::amqp_0_10::Connection> ConnectionPtr;
+typedef std::auto_ptr<sys::ConnectionInputHandler> InputPtr;
+
+sys::ConnectionCodec*
+ProtocolRegistry::create_0_10(qpid::sys::OutputControl& out, const std::string& id,
+ const qpid::sys::SecuritySettings& external, bool brokerActsAsClient)
+{
+ assert(broker);
+ SecureConnectionPtr sc(new SecureConnection());
+ CodecPtr c(new qpid::amqp_0_10::Connection(out, id, brokerActsAsClient));
+ ConnectionPtr i(new broker::amqp_0_10::Connection(c.get(), *broker, id, external, brokerActsAsClient));
+ i->setSecureConnection(sc.get());
+ c->setInputHandler(InputPtr(i.release()));
+ sc->setCodec(std::auto_ptr<sys::ConnectionCodec>(c));
+ return sc.release();
+}
+
+boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer> ProtocolRegistry::translate(const Message& m)
+{
+ boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer> transfer;
+ const qpid::broker::amqp_0_10::MessageTransfer* ptr = dynamic_cast<const qpid::broker::amqp_0_10::MessageTransfer*>(&m.getEncoding());
+ if (ptr) transfer = boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer>(ptr);
+ for (Protocols::const_iterator i = protocols.begin(); !transfer && i != protocols.end(); ++i) {
+ transfer = i->second->translate(m);
+ }
+ if (!transfer) throw new Exception("Could not convert message into 0-10");
+ return transfer;
+}
+boost::shared_ptr<RecoverableMessage> ProtocolRegistry::recover(qpid::framing::Buffer& b)
+{
+ uint32_t position = b.getPosition();
+ for (Protocols::const_iterator i = protocols.begin(); i != protocols.end(); ++i) {
+ boost::shared_ptr<RecoverableMessage> msg = i->second->recover(b);
+ if (msg) return msg;
+ else b.setPosition(position);
+ }
+ boost::intrusive_ptr<qpid::broker::amqp_0_10::MessageTransfer> transfer(new qpid::broker::amqp_0_10::MessageTransfer());
+ transfer->decodeHeader(b);
+ boost::shared_ptr<RecoverableMessage> msg(new RecoverableMessageImpl(Message(transfer, transfer)));
+ return msg;
+}
+
+Message ProtocolRegistry::decode(qpid::framing::Buffer& buffer)
+{
+ boost::shared_ptr<RecoverableMessage> r = recover(buffer);
+ r->decodeContent(buffer);
+ return r->getMessage();
+}
+
+ProtocolRegistry::~ProtocolRegistry()
+{
+ for (Protocols::const_iterator i = protocols.begin(); i != protocols.end(); ++i) {
+ delete i->second;
+ }
+ protocols.clear();
+}
+void ProtocolRegistry::add(const std::string& key, Protocol* protocol)
+{
+ protocols[key] = protocol;
+ QPID_LOG(info, "Loaded protocol " << key);
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/Protocol.h b/qpid/cpp/src/qpid/broker/Protocol.h
new file mode 100644
index 0000000000..a7dbc98fff
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Protocol.h
@@ -0,0 +1,96 @@
+#ifndef QPID_BROKER_PROTOCOL_H
+#define QPID_BROKER_PROTOCOL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <map>
+#include <string>
+#include <set>
+#include <boost/shared_ptr.hpp>
+#include <boost/intrusive_ptr.hpp>
+#include "qpid/sys/ConnectionCodec.h"
+#include "qpid/broker/BrokerImportExport.h"
+
+namespace qpid {
+namespace sys {
+class OutputControl;
+struct SecuritySettings;
+}
+namespace framing {
+class Buffer;
+class ProtocolVersion;
+}
+namespace broker {
+class Broker;
+class Message;
+class RecoverableMessage;
+namespace amqp_0_10 {
+class MessageTransfer;
+}
+
+/**
+ * A simple abstraction allowing pluggable protocol(s)
+ * (versions). AMQP 0-10 is considered the default. Alternatives must
+ * provide a ConnectionCodec for encoding/decoding the protocol in
+ * full, a means of translating the native message format of that
+ * protocol into AMQP 0-10 and a means of recovering durable messages
+ * from disk.
+ */
+class Protocol
+{
+ public:
+ virtual ~Protocol() {}
+ virtual qpid::sys::ConnectionCodec* create(const qpid::framing::ProtocolVersion&, qpid::sys::OutputControl&, const std::string&, const qpid::sys::SecuritySettings&) = 0;
+ virtual boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer> translate(const Message&) = 0;
+ virtual boost::shared_ptr<RecoverableMessage> recover(qpid::framing::Buffer&) = 0;
+ virtual qpid::framing::ProtocolVersion supportedVersion() const = 0;
+
+ private:
+};
+
+class ProtocolRegistry : public Protocol, public qpid::sys::ConnectionCodec::Factory
+{
+ public:
+ QPID_BROKER_EXTERN qpid::sys::ConnectionCodec* create(const qpid::framing::ProtocolVersion&, qpid::sys::OutputControl&, const std::string&, const qpid::sys::SecuritySettings&);
+ QPID_BROKER_EXTERN qpid::sys::ConnectionCodec* create(qpid::sys::OutputControl&, const std::string&, const qpid::sys::SecuritySettings&);
+ QPID_BROKER_EXTERN qpid::framing::ProtocolVersion supportedVersion() const;
+ QPID_BROKER_EXTERN boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer> translate(const Message&);
+ QPID_BROKER_EXTERN boost::shared_ptr<RecoverableMessage> recover(qpid::framing::Buffer&);
+ QPID_BROKER_EXTERN Message decode(qpid::framing::Buffer&);
+
+ QPID_BROKER_EXTERN ProtocolRegistry(const std::set<std::string>& enabled, Broker* b);
+ QPID_BROKER_EXTERN ~ProtocolRegistry();
+ QPID_BROKER_EXTERN void add(const std::string&, Protocol*);
+ private:
+ //name may be useful for descriptive purposes or even for some
+ //limited manipulation of ordering
+ typedef std::map<std::string, Protocol*> Protocols;
+ Protocols protocols;
+ const std::set<std::string> enabled;
+ Broker* broker;
+
+ qpid::sys::ConnectionCodec* create_0_10(qpid::sys::OutputControl&, const std::string&, const qpid::sys::SecuritySettings&, bool);
+ bool isEnabled(const std::string&) const;
+
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_PROTOCOL_H*/
diff --git a/qpid/cpp/src/qpid/broker/Queue.cpp b/qpid/cpp/src/qpid/broker/Queue.cpp
new file mode 100644
index 0000000000..97b5a75e28
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Queue.cpp
@@ -0,0 +1,1764 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/Connection.h"
+#include "qpid/broker/AclModule.h"
+#include "qpid/broker/QueueCursor.h"
+#include "qpid/broker/QueueDepth.h"
+#include "qpid/broker/QueueSettings.h"
+#include "qpid/broker/Exchange.h"
+#include "qpid/broker/DeliverableMessage.h"
+#include "qpid/broker/MessageStore.h"
+#include "qpid/broker/MessageDeque.h"
+#include "qpid/broker/MessageDistributor.h"
+#include "qpid/broker/FifoDistributor.h"
+#include "qpid/broker/NullMessageStore.h"
+#include "qpid/broker/QueueRegistry.h"
+#include "qpid/broker/Selector.h"
+#include "qpid/broker/TransactionObserver.h"
+#include "qpid/broker/TxDequeue.h"
+
+//TODO: get rid of this
+#include "qpid/broker/amqp_0_10/MessageTransfer.h"
+
+#include "qpid/amqp_0_10/Codecs.h"
+#include "qpid/StringUtils.h"
+#include "qpid/log/Statement.h"
+#include "qpid/management/ManagementAgent.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/sys/Monitor.h"
+#include "qpid/sys/Time.h"
+#include "qpid/sys/Timer.h"
+#include "qpid/types/Variant.h"
+#include "qmf/org/apache/qpid/broker/ArgsQueuePurge.h"
+#include "qmf/org/apache/qpid/broker/ArgsQueueReroute.h"
+#include "qmf/org/apache/qpid/broker/EventQueueDelete.h"
+#include "qmf/org/apache/qpid/broker/EventSubscribe.h"
+#include "qmf/org/apache/qpid/broker/EventUnsubscribe.h"
+
+#include <iostream>
+#include <algorithm>
+#include <functional>
+
+#include <boost/bind.hpp>
+#include <boost/intrusive_ptr.hpp>
+
+
+namespace qpid {
+namespace broker {
+
+using namespace qpid::sys;
+using namespace qpid::framing;
+using qpid::management::ManagementAgent;
+using qpid::management::ManagementObject;
+using qpid::management::Manageable;
+using qpid::management::Args;
+using qpid::management::getCurrentPublisher;
+using std::string;
+using std::for_each;
+using std::mem_fun;
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+
+namespace
+{
+
+inline void mgntEnqStats(const Message& msg,
+ _qmf::Queue::shared_ptr mgmtObject,
+ _qmf::Broker::shared_ptr brokerMgmtObject)
+{
+ if (mgmtObject != 0) {
+ _qmf::Queue::PerThreadStats *qStats = mgmtObject->getStatistics();
+ _qmf::Broker::PerThreadStats *bStats = brokerMgmtObject->getStatistics();
+
+ uint64_t contentSize = msg.getMessageSize();
+ qStats->msgTotalEnqueues +=1;
+ bStats->msgTotalEnqueues += 1;
+ qStats->byteTotalEnqueues += contentSize;
+ bStats->byteTotalEnqueues += contentSize;
+ if (msg.isPersistent ()) {
+ qStats->msgPersistEnqueues += 1;
+ bStats->msgPersistEnqueues += 1;
+ qStats->bytePersistEnqueues += contentSize;
+ bStats->bytePersistEnqueues += contentSize;
+ }
+ mgmtObject->statisticsUpdated();
+ brokerMgmtObject->statisticsUpdated();
+ }
+}
+
+inline void mgntDeqStats(const Message& msg,
+ _qmf::Queue::shared_ptr mgmtObject,
+ _qmf::Broker::shared_ptr brokerMgmtObject)
+{
+ if (mgmtObject != 0){
+ _qmf::Queue::PerThreadStats *qStats = mgmtObject->getStatistics();
+ _qmf::Broker::PerThreadStats *bStats = brokerMgmtObject->getStatistics();
+ uint64_t contentSize = msg.getMessageSize();
+
+ qStats->msgTotalDequeues += 1;
+ bStats->msgTotalDequeues += 1;
+ qStats->byteTotalDequeues += contentSize;
+ bStats->byteTotalDequeues += contentSize;
+ if (msg.isPersistent ()){
+ qStats->msgPersistDequeues += 1;
+ bStats->msgPersistDequeues += 1;
+ qStats->bytePersistDequeues += contentSize;
+ bStats->bytePersistDequeues += contentSize;
+ }
+ mgmtObject->statisticsUpdated();
+ brokerMgmtObject->statisticsUpdated();
+ }
+}
+
+QueueSettings merge(const QueueSettings& inputs, const Broker& broker)
+{
+ QueueSettings settings(inputs);
+ settings.maxDepth = QueueDepth();
+ if (inputs.maxDepth.hasCount() && inputs.maxDepth.getCount()) {
+ settings.maxDepth.setCount(inputs.maxDepth.getCount());
+ }
+ if (inputs.maxDepth.hasSize()) {
+ if (inputs.maxDepth.getSize()) {
+ settings.maxDepth.setSize(inputs.maxDepth.getSize());
+ }
+ } else if (broker.getQueueLimit()) {
+ settings.maxDepth.setSize(broker.getQueueLimit());
+ }
+ return settings;
+}
+
+}
+
+
+Queue::TxPublish::TxPublish(const Message& m, boost::shared_ptr<Queue> q) : message(m), queue(q), prepared(false) {}
+bool Queue::TxPublish::prepare(TransactionContext* ctxt) throw()
+{
+ try {
+ prepared = queue->enqueue(ctxt, message);
+ return true;
+ } catch (const std::exception& e) {
+ QPID_LOG(error, "Failed to prepare: " << e.what());
+ return false;
+ }
+}
+void Queue::TxPublish::commit() throw()
+{
+ try {
+ if (prepared) queue->process(message);
+ } catch (const std::exception& e) {
+ QPID_LOG(error, "Failed to commit: " << e.what());
+ }
+}
+void Queue::TxPublish::rollback() throw()
+{
+ try {
+ if (prepared) queue->enqueueAborted(message);
+ } catch (const std::exception& e) {
+ QPID_LOG(error, "Failed to rollback: " << e.what());
+ }
+}
+
+void Queue::TxPublish::callObserver(
+ const boost::shared_ptr<TransactionObserver>& observer)
+{
+ observer->enqueue(queue, message);
+}
+
+Queue::Queue(const string& _name, const QueueSettings& _settings,
+ MessageStore* const _store,
+ Manageable* parent,
+ Broker* b) :
+
+ name(_name),
+ store(_store),
+ owner(0),
+ exclusive(0),
+ messages(new MessageDeque()),
+ persistenceId(0),
+ settings(b ? merge(_settings, *b) : _settings),
+ eventMode(0),
+ observers(name, messageLock),
+ broker(b),
+ deleted(false),
+ barrier(*this),
+ allocator(new FifoDistributor( *messages )),
+ redirectSource(false)
+{
+ current.setCount(0);//always track depth in messages
+ if (settings.maxDepth.getSize()) current.setSize(0);//track depth in bytes only if policy requires it
+ if (settings.traceExcludes.size()) {
+ split(traceExclude, settings.traceExcludes, ", ");
+ }
+ qpid::amqp_0_10::translate(settings.asMap(), encodableSettings);
+ if (parent != 0 && broker != 0) {
+ ManagementAgent* agent = broker->getManagementAgent();
+ if (agent != 0) {
+ mgmtObject = _qmf::Queue::shared_ptr(
+ new _qmf::Queue(agent, this, parent, _name, _store != 0, settings.autodelete));
+ mgmtObject->set_arguments(settings.asMap());
+ agent->addObject(mgmtObject, 0, store != 0);
+ brokerMgmtObject = boost::dynamic_pointer_cast<_qmf::Broker>(broker->GetManagementObject());
+ if (brokerMgmtObject)
+ brokerMgmtObject->inc_queueCount();
+ }
+ }
+
+ if ( settings.isBrowseOnly ) {
+ QPID_LOG ( info, "Queue " << name << " is browse-only." );
+ }
+ if (settings.filter.size()) {
+ selector.reset(new Selector(settings.filter));
+ QPID_LOG (info, "Queue " << name << " using filter: " << settings.filter);
+ }
+}
+
+Queue::~Queue()
+{
+ if (mgmtObject != 0)
+ mgmtObject->debugStats("destroying");
+}
+
+bool Queue::isLocal(const Message& msg)
+{
+ //message is considered local if it was published on the same
+ //connection as that of the session which declared this queue
+ //exclusive (owner) or which has an exclusive subscription
+ //(exclusive)
+ return settings.noLocal && (msg.isLocalTo(owner) || msg.isLocalTo(exclusive));
+}
+
+bool Queue::isExcluded(const Message& msg)
+{
+ return traceExclude.size() && msg.isExcluded(traceExclude);
+}
+
+bool Queue::accept(const Message& msg)
+{
+ //TODO: move some of this out of the queue and into the publishing
+ //'link' for whatever protocol is used; that would let protocol
+ //specific stuff be kept out the queue
+ if (broker::amqp_0_10::MessageTransfer::isImmediateDeliveryRequired(msg) && getConsumerCount() == 0) {
+ if (alternateExchange) {
+ DeliverableMessage deliverable(msg, 0);
+ alternateExchange->route(deliverable);
+ }
+ return false;
+ } else if (isLocal(msg)) {
+ //drop message
+ QPID_LOG(info, "Dropping 'local' message from " << getName());
+ return false;
+ } else if (isExcluded(msg)) {
+ //drop message
+ QPID_LOG(info, "Dropping excluded message from " << getName());
+ return false;
+ } else if (selector) {
+ return selector->filter(msg);
+ } else {
+ return true;
+ }
+}
+
+void Queue::deliver(Message msg, TxBuffer* txn)
+{
+ if (redirectPeer) {
+ redirectPeer->deliverTo(msg, txn);
+ } else {
+ deliverTo(msg, txn);
+ }
+}
+
+void Queue::deliverTo(Message msg, TxBuffer* txn)
+{
+ if (accept(msg)) {
+ interceptors.record(msg);
+ if (txn) {
+ TxOp::shared_ptr op(new TxPublish(msg, shared_from_this()));
+ txn->enlist(op);
+ QPID_LOG(debug, "Message " << msg.getSequence() << " enqueue on " << name
+ << " enlisted in " << txn);
+ } else {
+ if (enqueue(0, msg)) {
+ push(msg);
+ QPID_LOG(debug, "Message " << msg.getSequence() << " enqueued on " << name);
+ } else {
+ QPID_LOG(debug, "Message " << msg.getSequence() << " dropped from " << name);
+ }
+ }
+ }
+}
+
+void Queue::recoverPrepared(const Message& msg)
+{
+ Mutex::ScopedLock locker(messageLock);
+ current += QueueDepth(1, msg.getMessageSize());
+}
+
+void Queue::recover(Message& msg)
+{
+ recoverPrepared(msg);
+ push(msg, true);
+}
+
+void Queue::process(Message& msg)
+{
+ push(msg);
+ if (mgmtObject != 0){
+ _qmf::Queue::PerThreadStats *qStats = mgmtObject->getStatistics();
+ const uint64_t contentSize = msg.getMessageSize();
+ qStats->msgTxnEnqueues += 1;
+ qStats->byteTxnEnqueues += contentSize;
+ mgmtObject->statisticsUpdated();
+ if (brokerMgmtObject) {
+ _qmf::Broker::PerThreadStats *bStats = brokerMgmtObject->getStatistics();
+ bStats->msgTxnEnqueues += 1;
+ bStats->byteTxnEnqueues += contentSize;
+ brokerMgmtObject->statisticsUpdated();
+ }
+ }
+}
+
+void Queue::release(const QueueCursor& position, bool markRedelivered)
+{
+ QueueListeners::NotificationSet copy;
+ {
+ Mutex::ScopedLock locker(messageLock);
+ if (!deleted) {
+ Message* message = messages->release(position);
+ if (message) {
+ if (!markRedelivered) message->undeliver();
+ listeners.populate(copy);
+ observeRequeue(*message, locker);
+ if (mgmtObject) {
+ mgmtObject->inc_releases();
+ if (brokerMgmtObject)
+ brokerMgmtObject->inc_releases();
+ }
+ }
+ }
+ }
+ copy.notify();
+}
+
+bool Queue::dequeueMessageAt(const SequenceNumber& position)
+{
+ ScopedAutoDelete autodelete(*this);
+ boost::intrusive_ptr<PersistableMessage> pmsg;
+ {
+ Mutex::ScopedLock locker(messageLock);
+ QPID_LOG(debug, "Attempting to dequeue message at " << position);
+ QueueCursor cursor;
+ Message* msg = messages->find(position, &cursor);
+ if (msg) {
+ if (msg->isPersistent()) pmsg = msg->getPersistentContext();
+ observeDequeue(*msg, locker, settings.autodelete ? &autodelete : 0);
+ messages->deleted(cursor);
+ } else {
+ QPID_LOG(debug, "Could not dequeue message at " << position << "; no such message");
+ return false;
+ }
+ }
+ dequeueFromStore(pmsg);
+ return true;
+}
+
+bool Queue::acquire(const QueueCursor& position, const std::string& consumer)
+{
+ Mutex::ScopedLock locker(messageLock);
+ Message* msg;
+
+ msg = messages->find(position);
+ if (msg) {
+ QPID_LOG(debug, consumer << " attempting to acquire message at " << msg->getSequence());
+ if (!allocator->acquire(consumer, *msg)) {
+ QPID_LOG(debug, "Not permitted to acquire msg at " << msg->getSequence() << " from '" << name);
+ return false;
+ } else {
+ observeAcquire(*msg, locker);
+ QPID_LOG(debug, "Acquired message at " << msg->getSequence() << " from " << name);
+ return true;
+ }
+ } else {
+ QPID_LOG(debug, "Failed to acquire message which no longer exists on " << name);
+ return false;
+ }
+}
+
+bool Queue::getNextMessage(Message& m, Consumer::shared_ptr& c)
+{
+ if (!checkNotDeleted(c)) return false;
+ QueueListeners::NotificationSet set;
+ ScopedAutoDelete autodelete(*this);
+ bool messageFound(false);
+ while (true) {
+ //TODO: reduce lock scope
+ Mutex::ScopedLock locker(messageLock);
+ QueueCursor cursor = c->getCursor(); // Save current position.
+ Message* msg = messages->next(*c); // Advances c.
+ if (msg) {
+ if (msg->getExpiration() < sys::AbsTime::now()) {
+ QPID_LOG(debug, "Message expired from queue '" << name << "'");
+ observeDequeue(*msg, locker, settings.autodelete ? &autodelete : 0);
+ //ERROR: don't hold lock across call to store!!
+ if (msg->isPersistent()) dequeueFromStore(msg->getPersistentContext());
+ if (mgmtObject) {
+ mgmtObject->inc_discardsTtl();
+ if (brokerMgmtObject)
+ brokerMgmtObject->inc_discardsTtl();
+ }
+ messages->deleted(*c);
+ continue;
+ }
+
+ if (c->filter(*msg)) {
+ if (c->accept(*msg)) {
+ if (c->preAcquires()) {
+ QPID_LOG(debug, "Attempting to acquire message " << msg->getSequence()
+ << " from '" << name << "' with state " << msg->getState());
+ if (allocator->acquire(c->getName(), *msg)) {
+ if (mgmtObject) {
+ mgmtObject->inc_acquires();
+ if (brokerMgmtObject)
+ brokerMgmtObject->inc_acquires();
+ }
+ observeAcquire(*msg, locker);
+ msg->deliver();
+ } else {
+ QPID_LOG(debug, "Could not acquire message from '" << name << "'");
+ continue; //try another message
+ }
+ }
+ QPID_LOG(debug, "Message " << msg->getSequence() << " retrieved from '"
+ << name << "'");
+ m = *msg;
+ messageFound = true;
+ break;
+ } else {
+ //message(s) are available but consumer hasn't got enough credit
+ QPID_LOG(debug, "Consumer can't currently accept message from '" << name << "'");
+ c->setCursor(cursor); // Restore cursor, will try again with credit
+ if (c->preAcquires()) {
+ //let someone else try
+ listeners.populate(set);
+ }
+ break;
+ }
+ } else {
+ //consumer will never want this message, try another one
+ QPID_LOG(debug, "Consumer doesn't want message from '" << name << "'");
+ if (c->preAcquires()) {
+ //let someone else try to take this one
+ listeners.populate(set);
+ }
+ }
+ } else {
+ QPID_LOG(debug, "No messages to dispatch on queue '" << name << "'");
+ c->stopped();
+ listeners.addListener(c);
+ break;
+ }
+
+ }
+ set.notify();
+ return messageFound;
+}
+
+void Queue::removeListener(Consumer::shared_ptr c)
+{
+ QueueListeners::NotificationSet set;
+ {
+ Mutex::ScopedLock locker(messageLock);
+ listeners.removeListener(c);
+ if (messages->size()) {
+ listeners.populate(set);
+ }
+ }
+ set.notify();
+}
+
+bool Queue::dispatch(Consumer::shared_ptr c)
+{
+ Message msg;
+ if (getNextMessage(msg, c)) {
+ c->deliver(*c, msg);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool Queue::find(SequenceNumber pos, Message& msg) const
+{
+ Mutex::ScopedLock locker(messageLock);
+ Message* ptr = messages->find(pos, 0);
+ if (ptr) {
+ msg = *ptr;
+ return true;
+ }
+ return false;
+}
+
+void Queue::markInUse(bool controlling)
+{
+ Mutex::ScopedLock locker(messageLock);
+ if (controlling) users.addLifecycleController();
+ else users.addOther();
+}
+
+void Queue::releaseFromUse(bool controlling, bool doDelete)
+{
+ bool trydelete;
+ if (controlling) {
+ Mutex::ScopedLock locker(messageLock);
+ users.removeLifecycleController();
+ trydelete = true;
+ } else {
+ Mutex::ScopedLock locker(messageLock);
+ users.removeOther();
+ trydelete = isUnused(locker);
+ }
+ if (trydelete && doDelete) scheduleAutoDelete();
+}
+
+void Queue::consume(Consumer::shared_ptr c, bool requestExclusive,
+ const framing::FieldTable& arguments,
+ const std::string& connectionId, const std::string& userId)
+{
+ boost::intrusive_ptr<qpid::sys::TimerTask> t;
+ {
+ Mutex::ScopedLock locker(messageLock);
+ if (c->preAcquires()) {
+ if(settings.isBrowseOnly) {
+ throw NotAllowedException(
+ QPID_MSG("Queue " << name << " is browse only. Refusing acquiring consumer."));
+ }
+
+ if(exclusive) {
+ throw ResourceLockedException(
+ QPID_MSG("Queue " << getName()
+ << " has an exclusive consumer. No more consumers allowed."));
+ } else if(requestExclusive) {
+ if(users.hasConsumers()) {
+ throw ResourceLockedException(
+ QPID_MSG("Queue " << getName()
+ << " already has consumers. Exclusive access denied."));
+ } else {
+ exclusive = c->getSession();
+ }
+ }
+ users.addConsumer();
+ } else if(c->isCounted()) {
+ users.addBrowser();
+ }
+ if(c->isCounted()) {
+ //reset auto deletion timer if necessary
+ if (settings.autoDeleteDelay && autoDeleteTask) {
+ t = autoDeleteTask;
+ }
+
+ observeConsumerAdd(*c, locker);
+ }
+ }
+ if (t) t->cancel();
+ if (mgmtObject != 0 && c->isCounted()) {
+ mgmtObject->inc_consumerCount();
+ }
+ if (broker) {
+ ManagementAgent* agent = broker->getManagementAgent();
+ if (agent) {
+ agent->raiseEvent(
+ _qmf::EventSubscribe(connectionId, userId, name,
+ c->getTag(), requestExclusive, ManagementAgent::toMap(arguments)));
+ }
+ }
+}
+
+void Queue::cancel(Consumer::shared_ptr c, const std::string& connectionId, const std::string& userId)
+{
+ removeListener(c);
+ if(c->isCounted())
+
+ {
+ bool unused;
+ {
+ Mutex::ScopedLock locker(messageLock);
+ if (c->preAcquires()) {
+ users.removeConsumer();
+ if (exclusive) exclusive = 0;
+ } else {
+ users.removeBrowser();
+ }
+ observeConsumerRemove(*c, locker);
+ unused = !users.isUsed();
+ }
+ if (mgmtObject != 0) {
+ mgmtObject->dec_consumerCount();
+ }
+ if (unused && settings.autodelete) scheduleAutoDelete();
+ }
+ if (broker) {
+ ManagementAgent* agent = broker->getManagementAgent();
+ if (agent) agent->raiseEvent(_qmf::EventUnsubscribe(connectionId, userId, c->getTag()));
+ }
+}
+
+namespace{
+bool hasExpired(const Message& m, AbsTime now)
+{
+ return m.getExpiration() < now;
+}
+}
+
+/**
+ *@param lapse: time since the last purgeExpired
+ */
+void Queue::purgeExpired(sys::Duration lapse) {
+ //As expired messages are discarded during dequeue also, only
+ //bother explicitly expiring if the rate of dequeues since last
+ //attempt is less than one per second.
+ int count = dequeueSincePurge.get();
+ dequeueSincePurge -= count;
+ int seconds = int64_t(lapse)/qpid::sys::TIME_SEC;
+ if (seconds == 0 || count / seconds < 1) {
+ sys::AbsTime time = sys::AbsTime::now();
+ uint32_t count = remove(0, boost::bind(&hasExpired, _1, time), 0, CONSUMER, settings.autodelete);
+ QPID_LOG(debug, "Purged " << count << " expired messages from " << getName());
+ //
+ // Report the count of discarded-by-ttl messages
+ //
+ if (mgmtObject && count) {
+ mgmtObject->inc_discardsTtl(count);
+ if (brokerMgmtObject) {
+ brokerMgmtObject->inc_discardsTtl(count);
+ }
+ }
+ }
+}
+
+namespace {
+ // for use with purge/move below - collect messages that match a given filter
+ //
+ class MessageFilter
+ {
+ public:
+ static const std::string typeKey;
+ static const std::string paramsKey;
+ static MessageFilter *create( const ::qpid::types::Variant::Map *filter );
+ virtual bool match( const Message& ) const { return true; }
+ virtual ~MessageFilter() {}
+ protected:
+ MessageFilter() {};
+ };
+ const std::string MessageFilter::typeKey("filter_type");
+ const std::string MessageFilter::paramsKey("filter_params");
+
+ // filter by message header string value exact match
+ class HeaderMatchFilter : public MessageFilter
+ {
+ public:
+ /* Config:
+ { 'filter_type' : 'header_match_str',
+ 'filter_params' : { 'header_key' : "<header name>",
+ 'header_value' : "<value to match>"
+ }
+ }
+ */
+ static const std::string typeKey;
+ static const std::string headerKey;
+ static const std::string valueKey;
+ HeaderMatchFilter( const std::string& _header, const std::string& _value )
+ : MessageFilter (), header(_header), value(_value) {}
+ bool match( const Message& msg ) const
+ {
+ return msg.getPropertyAsString(header) == value;
+ }
+ private:
+ const std::string header;
+ const std::string value;
+ };
+ const std::string HeaderMatchFilter::typeKey("header_match_str");
+ const std::string HeaderMatchFilter::headerKey("header_key");
+ const std::string HeaderMatchFilter::valueKey("header_value");
+
+ // factory to create correct filter based on map
+ MessageFilter* MessageFilter::create( const ::qpid::types::Variant::Map *filter )
+ {
+ using namespace qpid::types;
+ if (filter && !filter->empty()) {
+ Variant::Map::const_iterator i = filter->find(MessageFilter::typeKey);
+ if (i != filter->end()) {
+
+ if (i->second.asString() == HeaderMatchFilter::typeKey) {
+ Variant::Map::const_iterator p = filter->find(MessageFilter::paramsKey);
+ if (p != filter->end() && p->second.getType() == VAR_MAP) {
+ Variant::Map::const_iterator k = p->second.asMap().find(HeaderMatchFilter::headerKey);
+ Variant::Map::const_iterator v = p->second.asMap().find(HeaderMatchFilter::valueKey);
+ if (k != p->second.asMap().end() && v != p->second.asMap().end()) {
+ std::string headerKey(k->second.asString());
+ std::string value(v->second.asString());
+ QPID_LOG(debug, "Message filtering by header value configured. key: " << headerKey << " value: " << value );
+ return new HeaderMatchFilter( headerKey, value );
+ }
+ }
+ }
+ }
+ QPID_LOG(error, "Unrecognized message filter: '" << *filter << "'");
+ throw qpid::Exception(QPID_MSG("Unrecognized message filter: '" << *filter << "'"));
+ }
+ return new MessageFilter();
+ }
+
+ void moveTo(boost::shared_ptr<Queue> q, Message& m)
+ {
+ if (q) {
+ q->deliver(m);
+ }
+ }
+} // end namespace
+
+uint32_t Queue::remove(const uint32_t maxCount, MessagePredicate p, MessageFunctor f,
+ SubscriptionType type, bool triggerAutoDelete, uint32_t maxTests)
+{
+ ScopedAutoDelete autodelete(*this);
+ std::deque<Message> removed;
+ {
+ QueueCursor c(type);
+ uint32_t count(0), tests(0);
+ Mutex::ScopedLock locker(messageLock);
+ Message* m = messages->next(c);
+ while (m){
+ if (maxTests && tests++ >= maxTests) break;
+ if (!p || p(*m)) {
+ if (maxCount && count++ >= maxCount) break;
+ if (m->getState() == AVAILABLE) {
+ //don't actually acquire, just act as if we did
+ observeAcquire(*m, locker);
+ }
+ observeDequeue(*m, locker, triggerAutoDelete ? &autodelete : 0);
+ removed.push_back(*m);//takes a copy of the message
+ if (!messages->deleted(c)) {
+ QPID_LOG(warning, "Failed to correctly remove message from " << name << "; state is not consistent!");
+ assert(false);
+ }
+ }
+ m = messages->next(c);
+ }
+ }
+ for (std::deque<Message>::iterator i = removed.begin(); i != removed.end(); ++i) {
+ if (f) f(*i);//ERROR? need to clear old persistent context?
+ if (i->isPersistent()) dequeueFromStore(i->getPersistentContext());//do this outside of lock and after any re-routing
+ }
+ return removed.size();
+}
+
+
+/**
+ * purge - for purging all or some messages on a queue
+ * depending on the purge_request
+ *
+ * qty == 0 then purge all messages
+ * == N then purge N messages from queue
+ * Sometimes qty == 1 to unblock the top of queue
+ *
+ * The dest exchange may be supplied to re-route messages through the exchange.
+ * It is safe to re-route messages such that they arrive back on the same queue,
+ * even if the queue is ordered by priority.
+ *
+ * An optional filter can be supplied that will be applied against each message. The
+ * message is purged only if the filter matches. See MessageDistributor for more detail.
+ */
+uint32_t Queue::purge(const uint32_t qty, boost::shared_ptr<Exchange> dest,
+ const qpid::types::Variant::Map *filter)
+{
+ std::auto_ptr<MessageFilter> mf(MessageFilter::create(filter));
+ uint32_t count = remove(qty, boost::bind(&MessageFilter::match, mf.get(), _1), boost::bind(&reroute, dest, _1), CONSUMER/*?*/, settings.autodelete);
+
+ if (mgmtObject && count) {
+ mgmtObject->inc_acquires(count);
+ if (dest.get()) {
+ mgmtObject->inc_reroutes(count);
+ if (brokerMgmtObject) {
+ brokerMgmtObject->inc_acquires(count);
+ brokerMgmtObject->inc_reroutes(count);
+ }
+ } else {
+ mgmtObject->inc_discardsPurge(count);
+ if (brokerMgmtObject) {
+ brokerMgmtObject->inc_acquires(count);
+ brokerMgmtObject->inc_discardsPurge(count);
+ }
+ }
+ }
+
+ return count;
+}
+
+uint32_t Queue::move(const Queue::shared_ptr destq, uint32_t qty,
+ const qpid::types::Variant::Map *filter)
+{
+ std::auto_ptr<MessageFilter> mf(MessageFilter::create(filter));
+ return remove(qty, boost::bind(&MessageFilter::match, mf.get(), _1), boost::bind(&moveTo, destq, _1), CONSUMER/*?*/, settings.autodelete);
+}
+
+void Queue::push(Message& message, bool /*isRecovery*/)
+{
+ QueueListeners::NotificationSet copy;
+ {
+ Mutex::ScopedLock locker(messageLock);
+ message.setSequence(++sequence);
+ if (settings.sequencing) message.addAnnotation(settings.sequenceKey, (uint32_t)sequence);
+ interceptors.publish(message);
+ messages->publish(message);
+ listeners.populate(copy);
+ observeEnqueue(message, locker);
+ }
+ copy.notify();
+}
+
+uint32_t Queue::getMessageCount() const
+{
+ Mutex::ScopedLock locker(messageLock);
+ return messages->size();
+}
+
+uint32_t Queue::getConsumerCount() const
+{
+ Mutex::ScopedLock locker(messageLock);
+ return users.getSubscriberCount();
+}
+
+bool Queue::canAutoDelete() const
+{
+ Mutex::ScopedLock locker(messageLock);
+ return !deleted && checkAutoDelete(locker);
+}
+
+bool Queue::checkAutoDelete(const Mutex::ScopedLock& lock) const
+{
+ if (settings.autodelete) {
+ switch (settings.lifetime) {
+ case QueueSettings::DELETE_IF_UNUSED:
+ return isUnused(lock);
+ case QueueSettings::DELETE_IF_EMPTY:
+ return !users.isInUseByController() && isEmpty(lock);
+ case QueueSettings::DELETE_IF_UNUSED_AND_EMPTY:
+ return isUnused(lock) && isEmpty(lock);
+ case QueueSettings::DELETE_ON_CLOSE:
+ return !users.isInUseByController();
+ }
+ }
+ return false;
+}
+
+bool Queue::isUnused(const Mutex::ScopedLock&) const
+{
+ return !owner && !users.isUsed();;
+}
+
+bool Queue::isEmpty(const Mutex::ScopedLock&) const
+{
+ return current.getCount() == 0;
+}
+/*
+ * return true if enqueue succeeded and message should be made
+ * available; returning false will result in the message being dropped
+ */
+bool Queue::enqueue(TransactionContext* ctxt, Message& msg)
+{
+ ScopedUse u(barrier);
+ if (!u.acquired) return false;
+
+ {
+ Mutex::ScopedLock locker(messageLock);
+ if (!checkDepth(QueueDepth(1, msg.getMessageSize()), msg)) {
+ return false;
+ }
+ }
+
+ if (settings.traceId.size()) {
+ msg.addTraceId(settings.traceId);
+ }
+
+ if (msg.isPersistent() && store) {
+ // mark the message as being enqueued - the store MUST CALL msg->enqueueComplete()
+ // when it considers the message stored.
+ boost::intrusive_ptr<PersistableMessage> pmsg = msg.getPersistentContext();
+ assert(pmsg);
+ pmsg->enqueueAsync(shared_from_this());
+ try {
+ store->enqueue(ctxt, pmsg, *this);
+ } catch (...) {
+ enqueueAborted(msg);
+ throw;
+ }
+ }
+ return true;
+}
+
+void Queue::enqueueAborted(const Message& msg)
+{
+ //Called when any transactional enqueue is aborted (including but
+ //not limited to a recovered dtx transaction)
+ Mutex::ScopedLock locker(messageLock);
+ current -= QueueDepth(1, msg.getMessageSize());
+}
+
+void Queue::enqueueCommited(Message& msg)
+{
+ //called when a recovered dtx enqueue operation is committed; the
+ //message is already on disk and space has been reserved in policy
+ //but it should now be made available
+ process(msg);
+}
+void Queue::dequeueAborted(Message& msg)
+{
+ //called when a recovered dtx dequeue operation is aborted; the
+ //message should be added back to the queue
+ push(msg);
+}
+void Queue::dequeueCommited(const Message& msg)
+{
+ //called when a recovered dtx dequeue operation is committed; the
+ //message will at this point have already been removed from the
+ //store and will not be available for delivery. The only action
+ //required is to ensure the observers are notified and the
+ //management stats are correctly decremented
+ ScopedAutoDelete autodelete(*this);
+ Mutex::ScopedLock locker(messageLock);
+ observeDequeue(msg, locker, settings.autodelete ? &autodelete : 0);
+ if (mgmtObject != 0) {
+ mgmtObject->inc_msgTxnDequeues();
+ mgmtObject->inc_byteTxnDequeues(msg.getMessageSize());
+ }
+}
+
+
+void Queue::dequeueFromStore(boost::intrusive_ptr<PersistableMessage> msg)
+{
+ ScopedUse u(barrier);
+ if (u.acquired && msg && store) {
+ store->dequeue(0, msg, *this);
+ }
+}
+
+void Queue::dequeue(const QueueCursor& cursor, TxBuffer* txn)
+{
+ if (txn) {
+ TxOp::shared_ptr op;
+ {
+ Mutex::ScopedLock locker(messageLock);
+ Message* msg = messages->find(cursor);
+ if (msg) {
+ op = TxOp::shared_ptr(new TxDequeue(cursor, shared_from_this(), msg->getSequence(), msg->getReplicationId()));
+ }
+ }
+ if (op) txn->enlist(op);
+ } else {
+ dequeue(0, cursor);
+ }
+}
+
+
+void Queue::dequeue(TransactionContext* ctxt, const QueueCursor& cursor)
+{
+ ScopedUse u(barrier);
+ if (!u.acquired) return;
+ ScopedAutoDelete autodelete(*this);
+ boost::intrusive_ptr<PersistableMessage> pmsg;
+ {
+ Mutex::ScopedLock locker(messageLock);
+ Message* msg = messages->find(cursor);
+ if (msg) {
+ if (msg->isPersistent()) pmsg = msg->getPersistentContext();
+ if (!ctxt) {
+ observeDequeue(*msg, locker, settings.autodelete ? &autodelete : 0);
+ messages->deleted(cursor);//message pointer not valid after this
+ }
+ } else {
+ return;
+ }
+ }
+ if (store && pmsg) {
+ store->dequeue(ctxt, pmsg, *this);
+ }
+}
+
+void Queue::dequeueCommitted(const QueueCursor& cursor)
+{
+ ScopedAutoDelete autodelete(*this);
+ Mutex::ScopedLock locker(messageLock);
+ Message* msg = messages->find(cursor);
+ if (msg) {
+ const uint64_t contentSize = msg->getMessageSize();
+ observeDequeue(*msg, locker, settings.autodelete ? &autodelete : 0);
+ if (mgmtObject != 0) {
+ mgmtObject->inc_msgTxnDequeues();
+ mgmtObject->inc_byteTxnDequeues(contentSize);
+ }
+ if (brokerMgmtObject) {
+ _qmf::Broker::PerThreadStats *bStats = brokerMgmtObject->getStatistics();
+ bStats->msgTxnDequeues += 1;
+ bStats->byteTxnDequeues += contentSize;
+ brokerMgmtObject->statisticsUpdated();
+ }
+ messages->deleted(cursor);
+ } else {
+ QPID_LOG(error, "Could not find dequeued message on commit");
+ }
+}
+
+/**
+ * Updates policy and management when a message has been dequeued,
+ * Requires messageLock be held by caller.
+ */
+void Queue::observeDequeue(const Message& msg, const Mutex::ScopedLock& lock, ScopedAutoDelete* autodelete)
+{
+ current -= QueueDepth(1, msg.getMessageSize());
+ mgntDeqStats(msg, mgmtObject, brokerMgmtObject);
+ observers.dequeued(msg, lock);
+ if (autodelete && isEmpty(lock)) autodelete->check(lock);
+}
+
+/** updates queue observers when a message has become unavailable for transfer.
+ * Requires messageLock be held by caller.
+ */
+void Queue::observeAcquire(const Message& msg, const Mutex::ScopedLock& l)
+{
+ observers.acquired(msg, l);
+}
+
+/** updates queue observers when a message has become re-available for transfer
+ * Requires messageLock be held by caller.
+ */
+void Queue::observeRequeue(const Message& msg, const Mutex::ScopedLock& l)
+{
+ observers.requeued(msg, l);
+}
+
+/** updates queue observers when a new consumer has subscribed to this queue.
+ */
+void Queue::observeConsumerAdd( const Consumer& c, const qpid::sys::Mutex::ScopedLock& l)
+{
+ observers.consumerAdded(c, l);
+}
+
+/** updates queue observers when a consumer has unsubscribed from this queue.
+ */
+void Queue::observeConsumerRemove( const Consumer& c, const qpid::sys::Mutex::ScopedLock& l)
+{
+ observers.consumerRemoved(c, l);
+}
+
+
+void Queue::create()
+{
+ if (store) {
+ store->create(*this, settings.storeSettings);
+ }
+}
+
+
+int getIntegerSetting(const qpid::framing::FieldTable& settings, const std::string& key)
+{
+ qpid::framing::FieldTable::ValuePtr v = settings.get(key);
+ if (!v) {
+ return 0;
+ } else if (v->convertsTo<int>()) {
+ return v->get<int>();
+ } else if (v->convertsTo<std::string>()){
+ std::string s = v->get<std::string>();
+ try {
+ return boost::lexical_cast<int>(s);
+ } catch(const boost::bad_lexical_cast&) {
+ QPID_LOG(warning, "Ignoring invalid integer value for " << key << ": " << s);
+ return 0;
+ }
+ } else {
+ QPID_LOG(warning, "Ignoring invalid integer value for " << key << ": " << *v);
+ return 0;
+ }
+}
+
+bool getBoolSetting(const qpid::framing::FieldTable& settings, const std::string& key)
+{
+ qpid::framing::FieldTable::ValuePtr v = settings.get(key);
+ if (!v) {
+ return false;
+ } else if (v->convertsTo<int>()) {
+ return v->get<int>() != 0;
+ } else if (v->convertsTo<std::string>()){
+ std::string s = v->get<std::string>();
+ if (s == "True") return true;
+ if (s == "true") return true;
+ if (s == "False") return false;
+ if (s == "false") return false;
+ try {
+ return boost::lexical_cast<bool>(s);
+ } catch(const boost::bad_lexical_cast&) {
+ QPID_LOG(warning, "Ignoring invalid boolean value for " << key << ": " << s);
+ return false;
+ }
+ } else {
+ QPID_LOG(warning, "Ignoring invalid boolean value for " << key << ": " << *v);
+ return false;
+ }
+}
+
+void Queue::abandoned(const Message& message)
+{
+ if (reroute(alternateExchange, message) && brokerMgmtObject)
+ brokerMgmtObject->inc_abandonedViaAlt();
+ else if (brokerMgmtObject)
+ brokerMgmtObject->inc_abandoned();
+}
+
+void Queue::destroyed()
+{
+ if (mgmtObject != 0)
+ mgmtObject->debugStats("destroying");
+ unbind(broker->getExchanges());
+ remove(0, 0, boost::bind(&Queue::abandoned, this, _1), REPLICATOR/*even acquired message are treated as abandoned*/, false);
+ if (alternateExchange.get()) {
+ alternateExchange->decAlternateUsers();
+ alternateExchange.reset();
+ }
+
+ if (store) {
+ barrier.destroy();
+ store->flush(*this);
+ store->destroy(*this);
+ store = 0;//ensure we make no more calls to the store for this queue
+ }
+ notifyDeleted();
+ {
+ Mutex::ScopedLock l(messageLock);
+ if (autoDeleteTask) autoDeleteTask = boost::intrusive_ptr<TimerTask>();
+ observers.destroy(l);
+ }
+ if (mgmtObject != 0) {
+ mgmtObject->resourceDestroy();
+ if (brokerMgmtObject)
+ brokerMgmtObject->dec_queueCount();
+ mgmtObject = _qmf::Queue::shared_ptr(); // dont print debugStats in Queue::~Queue
+ }
+}
+
+void Queue::notifyDeleted()
+{
+ QueueListeners::ListenerSet set;
+ {
+ Mutex::ScopedLock locker(messageLock);
+ deleted = true;
+ listeners.snapshot(set);
+ }
+ set.notifyAll();
+}
+
+void Queue::bound(const string& exchange, const string& key,
+ const FieldTable& args)
+{
+ bindings.add(exchange, key, args);
+}
+
+void Queue::unbind(ExchangeRegistry& exchanges)
+{
+ bindings.unbind(exchanges, shared_from_this());
+}
+
+
+uint64_t Queue::getPersistenceId() const
+{
+ return persistenceId;
+}
+
+void Queue::setPersistenceId(uint64_t _persistenceId) const
+{
+ if (mgmtObject != 0 && persistenceId == 0 && externalQueueStore)
+ {
+ ManagementObject::shared_ptr childObj = externalQueueStore->GetManagementObject();
+ if (childObj != 0)
+ childObj->setReference(mgmtObject->getObjectId());
+ }
+ persistenceId = _persistenceId;
+}
+
+void Queue::encode(Buffer& buffer) const
+{
+ buffer.putShortString(name);
+ buffer.put(encodableSettings);
+ buffer.putShortString(alternateExchange.get() ? alternateExchange->getName() : std::string(""));
+ buffer.putShortString(userId);
+ buffer.putInt8(isAutoDelete());
+}
+
+uint32_t Queue::encodedSize() const
+{
+ return name.size() + 1/*short string size octet*/
+ + (alternateExchange.get() ? alternateExchange->getName().size() : 0) + 1 /* short string */
+ + userId.size() + 1 /* short string */
+ + 1 /* autodelete flag */
+ + encodableSettings.encodedSize();
+}
+
+void Queue::updateAclUserQueueCount()
+{
+ if (broker->getAcl())
+ broker->getAcl()->approveCreateQueue(userId, name);
+}
+
+Queue::shared_ptr Queue::restore( QueueRegistry& queues, Buffer& buffer )
+{
+ string name;
+ string _userId;
+ FieldTable ft;
+ boost::shared_ptr<Exchange> alternate;
+ QueueSettings settings(true, false); // settings.autodelete might be overwritten
+ string altExch;
+ bool has_userId = false;
+ bool has_altExch = false;
+
+ buffer.getShortString(name);
+ buffer.get(ft);
+ settings.populate(ft, settings.storeSettings);
+ //get alternate exchange
+ if (buffer.available()) {
+ buffer.getShortString(altExch);
+ has_altExch = true;
+ }
+ //get userId of queue's creator; ACL counters for userId are done after ACL plugin is initialized
+ if (buffer.available()) {
+ buffer.getShortString(_userId);
+ has_userId = true;
+ }
+ //get autodelete flag
+ if (buffer.available()) {
+ settings.autodelete = buffer.getInt8();
+ }
+
+ std::pair<Queue::shared_ptr, bool> result = queues.declare(name, settings, alternate, true);
+ if (has_altExch)
+ result.first->alternateExchangeName.assign(altExch);
+ if (has_userId)
+ result.first->setOwningUser(_userId);
+
+ if (result.first->getSettings().autoDeleteDelay) {
+ result.first->scheduleAutoDelete();
+ }
+
+ return result.first;
+}
+
+
+void Queue::setAlternateExchange(boost::shared_ptr<Exchange> exchange)
+{
+ alternateExchange = exchange;
+ alternateExchange->incAlternateUsers();
+ if (mgmtObject) {
+ if (exchange.get() != 0)
+ mgmtObject->set_altExchange(exchange->GetManagementObject()->getObjectId());
+ else
+ mgmtObject->clr_altExchange();
+ }
+}
+
+boost::shared_ptr<Exchange> Queue::getAlternateExchange()
+{
+ return alternateExchange;
+}
+
+struct AutoDeleteTask : qpid::sys::TimerTask
+{
+ Queue::shared_ptr queue;
+
+ AutoDeleteTask(Queue::shared_ptr q, AbsTime fireTime)
+ : qpid::sys::TimerTask(fireTime, "DelayedAutoDeletion:"+q->getName()), queue(q) {}
+
+ void fire()
+ {
+ //need to detect case where queue was used after the task was
+ //created, but then became unused again before the task fired;
+ //in this case ignore this request as there will have already
+ //been a later task added
+ queue->tryAutoDelete();
+ }
+};
+
+void Queue::scheduleAutoDelete(bool immediate)
+{
+ if (canAutoDelete()) {
+ if (!immediate && settings.autoDeleteDelay) {
+ AbsTime time(now(), Duration(settings.autoDeleteDelay * TIME_SEC));
+ autoDeleteTask = boost::intrusive_ptr<qpid::sys::TimerTask>(new AutoDeleteTask(shared_from_this(), time));
+ broker->getTimer().add(autoDeleteTask);
+ QPID_LOG(debug, "Timed auto-delete for " << getName() << " initiated");
+ } else {
+ tryAutoDelete();
+ }
+ }
+}
+
+void Queue::tryAutoDelete()
+{
+ bool proceed(false);
+ {
+ Mutex::ScopedLock locker(messageLock);
+ if (!deleted && checkAutoDelete(locker)) {
+ proceed = true;
+ deleted = true;
+ }
+ }
+
+ if (proceed) {
+ broker->getQueues().destroy(name);
+ if (broker->getAcl())
+ broker->getAcl()->recordDestroyQueue(name);
+
+ QPID_LOG_CAT(debug, model, "Auto-delete queue deleted: " << name << " (" << deleted << ")");
+ destroyed();
+ } else {
+ QPID_LOG_CAT(debug, model, "Auto-delete queue could not be deleted: " << name);
+ }
+}
+
+bool Queue::isExclusiveOwner(const OwnershipToken* const o) const
+{
+ Mutex::ScopedLock locker(messageLock);
+ return o == owner;
+}
+
+void Queue::releaseExclusiveOwnership(bool immediateExpiry)
+{
+ bool unused;
+ {
+ Mutex::ScopedLock locker(messageLock);
+ owner = 0;
+ if (mgmtObject) {
+ mgmtObject->set_exclusive(false);
+ }
+ unused = !users.isUsed();
+ }
+ if (unused && settings.autodelete) {
+ scheduleAutoDelete(immediateExpiry);
+ }
+}
+
+bool Queue::setExclusiveOwner(const OwnershipToken* const o)
+{
+ //reset auto deletion timer if necessary
+ if (settings.autoDeleteDelay && autoDeleteTask) {
+ autoDeleteTask->cancel();
+ }
+ Mutex::ScopedLock locker(messageLock);
+ if (owner || users.hasConsumers()) {
+ return false;
+ } else {
+ owner = o;
+ if (mgmtObject) {
+ mgmtObject->set_exclusive(true);
+ }
+ return true;
+ }
+}
+
+bool Queue::hasExclusiveOwner() const
+{
+ Mutex::ScopedLock locker(messageLock);
+ return owner != 0;
+}
+
+bool Queue::hasExclusiveConsumer() const
+{
+ return exclusive;
+}
+
+void Queue::setExternalQueueStore(ExternalQueueStore* inst) {
+ if (externalQueueStore!=inst && externalQueueStore)
+ delete externalQueueStore;
+ externalQueueStore = inst;
+
+ if (inst) {
+ ManagementObject::shared_ptr childObj = inst->GetManagementObject();
+ if (childObj != 0 && mgmtObject != 0)
+ childObj->setReference(mgmtObject->getObjectId());
+ }
+}
+
+void Queue::countRejected() const
+{
+ if (mgmtObject) {
+ mgmtObject->inc_discardsSubscriber();
+ if (brokerMgmtObject)
+ brokerMgmtObject->inc_discardsSubscriber();
+ }
+}
+
+ManagementObject::shared_ptr Queue::GetManagementObject(void) const
+{
+ return mgmtObject;
+}
+
+Manageable::status_t Queue::ManagementMethod (uint32_t methodId, Args& args, string& etext)
+{
+ Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD;
+ AclModule* acl = broker->getAcl();
+ std::string _userId = (getCurrentPublisher()?getCurrentPublisher()->getUserId():"");
+
+ QPID_LOG (debug, "Queue::ManagementMethod [id=" << methodId << "]");
+
+ switch (methodId) {
+ case _qmf::Queue::METHOD_PURGE :
+ {
+ if ((acl)&&(!(acl->authorise(_userId, acl::ACT_PURGE, acl::OBJ_QUEUE, name, NULL)))) {
+ throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied purge request from " << _userId));
+ }
+ _qmf::ArgsQueuePurge& purgeArgs = (_qmf::ArgsQueuePurge&) args;
+ purge(purgeArgs.i_request, boost::shared_ptr<Exchange>(), &purgeArgs.i_filter);
+ status = Manageable::STATUS_OK;
+ }
+ break;
+
+ case _qmf::Queue::METHOD_REROUTE :
+ {
+ _qmf::ArgsQueueReroute& rerouteArgs = (_qmf::ArgsQueueReroute&) args;
+ boost::shared_ptr<Exchange> dest;
+ if (rerouteArgs.i_useAltExchange) {
+ if (!alternateExchange) {
+ status = Manageable::STATUS_PARAMETER_INVALID;
+ etext = "No alternate-exchange defined";
+ break;
+ }
+ dest = alternateExchange;
+ } else {
+ try {
+ dest = broker->getExchanges().get(rerouteArgs.i_exchange);
+ } catch(const std::exception&) {
+ status = Manageable::STATUS_PARAMETER_INVALID;
+ etext = "Exchange not found";
+ break;
+ }
+ }
+
+ if (acl) {
+ std::map<acl::Property, std::string> params;
+ params.insert(make_pair(acl::PROP_EXCHANGENAME, dest->getName()));
+ if (!acl->authorise(_userId, acl::ACT_REROUTE, acl::OBJ_QUEUE, name, &params)) {
+ throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied reroute request from " << _userId));
+ }
+ }
+
+ purge(rerouteArgs.i_request, dest, &rerouteArgs.i_filter);
+ status = Manageable::STATUS_OK;
+ }
+ break;
+ }
+
+ return status;
+}
+
+
+void Queue::query(qpid::types::Variant::Map& results) const
+{
+ Mutex::ScopedLock locker(messageLock);
+ /** @todo add any interesting queue state into results */
+ if (allocator) allocator->query(results);
+}
+
+namespace {
+struct After {
+ framing::SequenceNumber seq;
+ After(framing::SequenceNumber s) : seq(s) {}
+ bool operator()(const Message& m) { return m.getSequence() > seq; }
+};
+} // namespace
+
+
+void Queue::setPosition(SequenceNumber n) {
+ Mutex::ScopedLock locker(messageLock);
+ if (n < sequence) {
+ remove(0, After(n), MessagePredicate(), BROWSER, false);
+ }
+ sequence = n;
+ QPID_LOG(debug, "Set position to " << sequence << " on " << getName());
+}
+
+SequenceNumber Queue::getPosition() {
+ Mutex::ScopedLock locker(messageLock);
+ return sequence;
+}
+
+void Queue::getRange(framing::SequenceNumber& front, framing::SequenceNumber& back,
+ SubscriptionType type)
+{
+ Mutex::ScopedLock locker(messageLock);
+ QueueCursor cursor(type);
+ back = sequence;
+ Message* message = messages->next(cursor);
+ front = message ? message->getSequence() : back+1;
+}
+
+int Queue::getEventMode() { return eventMode; }
+
+void Queue::recoveryComplete(ExchangeRegistry& exchanges)
+{
+ // set the alternate exchange
+ if (!alternateExchangeName.empty()) {
+ Exchange::shared_ptr ae = exchanges.find(alternateExchangeName);
+ if (ae) setAlternateExchange(ae);
+ else QPID_LOG(warning, "Could not set alternate exchange \""
+ << alternateExchangeName << "\" on queue \"" << name
+ << "\": exchange does not exist.");
+ }
+ //process any pending dequeues
+ for (std::vector<Message>::iterator i = pendingDequeues.begin(); i != pendingDequeues.end(); ++i) {
+ dequeueFromStore(i->getPersistentContext());
+ }
+ pendingDequeues.clear();
+}
+
+/** updates queue observers and state when a message has become available for transfer
+ * Requires messageLock be held by caller.
+ */
+void Queue::observeEnqueue(const Message& m, const Mutex::ScopedLock& l)
+{
+ observers.enqueued(m, l);
+ mgntEnqStats(m, mgmtObject, brokerMgmtObject);
+}
+
+bool Queue::checkNotDeleted(const Consumer::shared_ptr& c)
+{
+ if (deleted && !c->hideDeletedError())
+ throw ResourceDeletedException(QPID_MSG("Queue " << getName() << " has been deleted."));
+ return !deleted;
+}
+
+bool Queue::isDeleted() const
+{
+ Mutex::ScopedLock lock(messageLock);
+ return deleted;
+}
+
+void Queue::flush()
+{
+ ScopedUse u(barrier);
+ if (u.acquired && store) store->flush(*this);
+}
+
+
+bool Queue::bind(boost::shared_ptr<Exchange> exchange, const std::string& key,
+ const qpid::framing::FieldTable& arguments)
+{
+ if (!isDeleted() && exchange->bind(shared_from_this(), key, &arguments)) {
+ bound(exchange->getName(), key, arguments);
+ if (exchange->isDurable() && isDurable()) {
+ store->bind(*exchange, *this, key, arguments);
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+Broker* Queue::getBroker()
+{
+ return broker;
+}
+
+void Queue::setDequeueSincePurge(uint32_t value) {
+ dequeueSincePurge = value;
+}
+
+void Queue::reject(const QueueCursor& cursor)
+{
+ ScopedAutoDelete autodelete(*this);
+ Exchange::shared_ptr alternate = getAlternateExchange();
+ Message copy;
+ boost::intrusive_ptr<PersistableMessage> pmsg;
+ {
+ Mutex::ScopedLock locker(messageLock);
+ Message* message = messages->find(cursor);
+ if (message) {
+ if (alternate) copy = *message;
+ if (message->isPersistent()) pmsg = message->getPersistentContext();
+ countRejected();
+ observeDequeue(*message, locker, settings.autodelete ? &autodelete : 0);
+ messages->deleted(cursor);
+ } else {
+ return;
+ }
+ }
+ if (alternate) {
+ copy.resetDeliveryCount();
+ DeliverableMessage delivery(copy, 0);
+ alternate->routeWithAlternate(delivery);
+ QPID_LOG(info, "Routed rejected message from " << getName() << " to "
+ << alternate->getName());
+ } else {
+ //just drop it
+ QPID_LOG(info, "Dropping rejected message from " << getName());
+ }
+ dequeueFromStore(pmsg);
+}
+
+bool Queue::checkDepth(const QueueDepth& increment, const Message&)
+{
+ if (settings.maxDepth && (settings.maxDepth - current < increment)) {
+ if (mgmtObject) {
+ mgmtObject->inc_discardsOverflow();
+ if (brokerMgmtObject)
+ brokerMgmtObject->inc_discardsOverflow();
+ }
+ throw ResourceLimitExceededException(QPID_MSG("Maximum depth exceeded on " << name << ": current=[" << current << "], max=[" << settings.maxDepth << "]"));
+ } else {
+ current += increment;
+ return true;
+ }
+}
+
+bool Queue::seek(QueueCursor& cursor, MessagePredicate predicate)
+{
+ Mutex::ScopedLock locker(messageLock);
+ //hold lock across calls to predicate, or take copy of message?
+ //currently hold lock, may want to revise depending on any new use
+ //cases
+ Message* message = messages->next(cursor);
+ while (message && (predicate && !predicate(*message))) {
+ message = messages->next(cursor);
+ }
+ return message != 0;
+}
+
+bool Queue::seek(QueueCursor& cursor, MessagePredicate predicate, qpid::framing::SequenceNumber start)
+{
+ Mutex::ScopedLock locker(messageLock);
+ //hold lock across calls to predicate, or take copy of message?
+ //currently hold lock, may want to revise depending on any new use
+ //cases
+ Message* message;
+ message = messages->find(start, &cursor);
+ if (message && (!predicate || predicate(*message))) return true;
+
+ return seek(cursor, predicate);
+}
+
+bool Queue::seek(QueueCursor& cursor, qpid::framing::SequenceNumber start)
+{
+ Mutex::ScopedLock locker(messageLock);
+ return messages->find(start, &cursor);
+}
+
+Queue::UsageBarrier::UsageBarrier(Queue& q) : parent(q), count(0) {}
+
+bool Queue::UsageBarrier::acquire()
+{
+ Monitor::ScopedLock l(usageLock);
+ if (parent.deleted) {
+ return false;
+ } else {
+ ++count;
+ return true;
+ }
+}
+
+void Queue::UsageBarrier::release()
+{
+ Monitor::ScopedLock l(usageLock);
+ if (--count == 0) usageLock.notifyAll();
+}
+
+void Queue::UsageBarrier::destroy()
+{
+ Monitor::ScopedLock l(usageLock);
+ parent.deleted = true;
+ while (count) usageLock.wait();
+}
+
+void Queue::addArgument(const string& key, const types::Variant& value) {
+ settings.original[key] = value;
+ qpid::amqp_0_10::translate(settings.asMap(), encodableSettings);
+ boost::shared_ptr<qpid::framing::FieldValue> v;
+ qpid::amqp_0_10::translate(value, v);
+ settings.storeSettings.set(key, v);
+ if (mgmtObject != 0) mgmtObject->set_arguments(settings.asMap());
+}
+
+
+void Queue::setRedirectPeer ( Queue::shared_ptr peer, bool isSrc) {
+ Mutex::ScopedLock locker(messageLock);
+ redirectPeer = peer;
+ redirectSource = isSrc;
+}
+
+void Queue::setMgmtRedirectState( std::string peer, bool enabled, bool isSrc ) {
+ if (mgmtObject != 0) {
+ mgmtObject->set_redirectPeer(enabled ? peer : "");
+ mgmtObject->set_redirectSource(isSrc);
+ }
+}
+
+void Queue::setOwningUser(std::string& _userId) {
+ userId = _userId;
+ if (mgmtObject != 0)
+ mgmtObject->set_creator(userId);
+}
+
+bool Queue::reroute(boost::shared_ptr<Exchange> e, const Message& m)
+{
+ if (e) {
+ DeliverableMessage d(m, 0);
+ d.getMessage().clearTrace();
+ e->routeWithAlternate(d);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+Queue::QueueUsers::QueueUsers() : consumers(0), browsers(0), others(0), controller(false) {}
+void Queue::QueueUsers::addConsumer() { ++consumers; }
+void Queue::QueueUsers::addBrowser() { ++browsers; }
+void Queue::QueueUsers::addLifecycleController() { assert(!controller); controller = true; }
+void Queue::QueueUsers::addOther(){ ++others; }
+void Queue::QueueUsers::removeConsumer() { assert(consumers > 0); --consumers; }
+void Queue::QueueUsers::removeBrowser() { assert(browsers > 0); --browsers; }
+void Queue::QueueUsers::removeLifecycleController() { assert(controller); controller = false; }
+void Queue::QueueUsers::removeOther() { assert(others > 0); --others; }
+bool Queue::QueueUsers::isInUseByController() const { return controller; }
+bool Queue::QueueUsers::isUsed() const { return controller || consumers || browsers || others; }
+uint32_t Queue::QueueUsers::getSubscriberCount() const { return consumers + browsers; }
+bool Queue::QueueUsers::hasConsumers() const { return consumers; }
+
+Queue::ScopedAutoDelete::ScopedAutoDelete(Queue& q) : queue(q), eligible(false) {}
+void Queue::ScopedAutoDelete::check(const sys::Mutex::ScopedLock& lock)
+{
+ eligible = queue.checkAutoDelete(lock);
+}
+Queue::ScopedAutoDelete::~ScopedAutoDelete()
+{
+ if (eligible) queue.scheduleAutoDelete();
+}
+
+}}
+
diff --git a/qpid/cpp/src/qpid/broker/Queue.h b/qpid/cpp/src/qpid/broker/Queue.h
new file mode 100644
index 0000000000..efca9b9d40
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Queue.h
@@ -0,0 +1,540 @@
+#ifndef _broker_Queue_h
+#define _broker_Queue_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/broker/OwnershipToken.h"
+#include "qpid/broker/Consumer.h"
+#include "qpid/broker/Message.h"
+#include "qpid/broker/Messages.h"
+#include "qpid/broker/MessageInterceptor.h"
+#include "qpid/broker/PersistableQueue.h"
+#include "qpid/broker/QueueBindings.h"
+#include "qpid/broker/QueueListeners.h"
+#include "qpid/broker/QueueObservers.h"
+#include "qpid/broker/QueueSettings.h"
+#include "qpid/broker/TxOp.h"
+
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/SequenceNumber.h"
+#include "qpid/sys/AtomicValue.h"
+#include "qpid/sys/Monitor.h"
+#include "qpid/management/Manageable.h"
+#include "qmf/org/apache/qpid/broker/Queue.h"
+#include "qmf/org/apache/qpid/broker/Broker.h"
+#include "qpid/framing/amqp_types.h"
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/intrusive_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+
+#include <list>
+#include <vector>
+#include <memory>
+#include <deque>
+#include <set>
+#include <algorithm>
+
+namespace qpid {
+namespace sys {
+class TimerTask;
+}
+namespace broker {
+class Broker;
+class Exchange;
+class MessageStore;
+class QueueDepth;
+class QueueEvents;
+class QueueRegistry;
+class QueueFactory;
+class Selector;
+class TransactionContext;
+class TxBuffer;
+class MessageDistributor;
+
+/**
+ * The brokers representation of an amqp queue. Messages are
+ * delivered to a queue from where they can be dispatched to
+ * registered consumers or be stored until dequeued or until one
+ * or more consumers registers.
+ */
+class Queue : public boost::enable_shared_from_this<Queue>,
+ public PersistableQueue, public management::Manageable {
+ public:
+ typedef boost::function1<bool, const Message&> MessagePredicate;
+
+ typedef boost::shared_ptr<Queue> shared_ptr;
+
+ protected:
+ friend struct AutoDeleteTask;
+ struct UsageBarrier
+ {
+ Queue& parent;
+ uint count;
+ qpid::sys::Monitor usageLock;
+
+ UsageBarrier(Queue&);
+ bool acquire();
+ void release();
+ void destroy();
+ };
+
+ struct ScopedUse
+ {
+ UsageBarrier& barrier;
+ const bool acquired;
+ ScopedUse(UsageBarrier& b) : barrier(b), acquired(barrier.acquire()) {}
+ ~ScopedUse() { if (acquired) barrier.release(); }
+ };
+
+ class TxPublish : public TxOp
+ {
+ Message message;
+ boost::shared_ptr<Queue> queue;
+ bool prepared;
+ public:
+ TxPublish(const Message&,boost::shared_ptr<Queue>);
+ bool prepare(TransactionContext* ctxt) throw();
+ void commit() throw();
+ void rollback() throw();
+ void callObserver(const boost::shared_ptr<TransactionObserver>&);
+ };
+
+ /**
+ * This class tracks whether a queue is in use and how it is being
+ * used.
+ */
+ class QueueUsers
+ {
+ public:
+ QueueUsers();
+ void addConsumer();
+ void addBrowser();
+ void addOther();
+ void removeConsumer();
+ void removeBrowser();
+ void addLifecycleController();
+ void removeLifecycleController();
+ void removeOther();
+ bool isUsed() const;
+ uint32_t getSubscriberCount() const;
+ bool hasConsumers() const;
+ bool isInUseByController() const;
+ private:
+ uint32_t consumers;
+ uint32_t browsers;
+ uint32_t others;
+ bool controller;
+ };
+
+ /**
+ * This class is used to check - and if necessary trigger -
+ * autodeletion when removing messages, as this could cause the
+ * queue to become empty (which is one possible trigger for
+ * autodeletion).
+ *
+ * The constructor and descructor should be called outside the
+ * message lock. The check method should be called while holding
+ * the message lock.
+ */
+ class ScopedAutoDelete
+ {
+ public:
+ ScopedAutoDelete(Queue& q);
+ void check(const sys::Mutex::ScopedLock& lock);
+ ~ScopedAutoDelete();
+ private:
+ Queue& queue;
+ bool eligible;
+ };
+
+ enum ConsumeCode {NO_MESSAGES=0, CANT_CONSUME=1, CONSUMED=2};
+ typedef boost::function1<void, Message&> MessageFunctor;
+
+ const std::string name;
+ MessageStore* store;
+ const OwnershipToken* owner;
+ QueueUsers users;
+ OwnershipToken* exclusive;
+ std::vector<std::string> traceExclude;
+ QueueListeners listeners;
+ std::auto_ptr<Messages> messages;
+ std::vector<Message> pendingDequeues;
+ /** messageLock is used to keep the Queue's state consistent while processing message
+ * events, such as message dispatch, enqueue, acquire, and dequeue. It must be held
+ * while updating certain members in order to keep these members consistent with
+ * each other:
+ * o messages
+ * o sequence
+ * o listeners
+ * o allocator
+ * o observeXXX() methods
+ * o observers
+ * o pendingDequeues (TBD: move under separate lock)
+ * o exclusive OwnershipToken (TBD: move under separate lock)
+ * o consumerCount (TBD: move under separate lock)
+ * o Queue::UsageBarrier (TBD: move under separate lock)
+ */
+ mutable qpid::sys::Mutex messageLock;
+ mutable uint64_t persistenceId;
+ QueueSettings settings;
+ qpid::framing::FieldTable encodableSettings;
+ QueueDepth current;
+ QueueBindings bindings;
+ std::string alternateExchangeName;
+ std::string userId; // queue owner for ACL quota purposes
+ boost::shared_ptr<Exchange> alternateExchange;
+ framing::SequenceNumber sequence;
+ qmf::org::apache::qpid::broker::Queue::shared_ptr mgmtObject;
+ qmf::org::apache::qpid::broker::Broker::shared_ptr brokerMgmtObject;
+ sys::AtomicValue<uint32_t> dequeueSincePurge; // Count dequeues since last purge.
+ int eventMode;
+ QueueObservers observers;
+ MessageInterceptors interceptors;
+ std::string seqNoKey;
+ Broker* broker;
+ bool deleted;
+ UsageBarrier barrier;
+ boost::intrusive_ptr<qpid::sys::TimerTask> autoDeleteTask;
+ boost::shared_ptr<MessageDistributor> allocator;
+ boost::scoped_ptr<Selector> selector;
+
+ // Redirect source and target refer to each other. Only one is source.
+ Queue::shared_ptr redirectPeer;
+ bool redirectSource;
+
+ bool checkAutoDelete(const qpid::sys::Mutex::ScopedLock&) const;
+ bool isUnused(const qpid::sys::Mutex::ScopedLock&) const;
+ bool isEmpty(const qpid::sys::Mutex::ScopedLock&) const;
+ virtual void push(Message& msg, bool isRecovery=false);
+ bool accept(const Message&);
+ void process(Message& msg);
+ bool enqueue(TransactionContext* ctxt, Message& msg);
+ bool getNextMessage(Message& msg, Consumer::shared_ptr& c);
+
+ void removeListener(Consumer::shared_ptr);
+
+ bool isExcluded(const Message& msg);
+
+ /** update queue observers, stats, policy, etc when the messages' state changes. Lock
+ * must be held by caller */
+ void observeEnqueue(const Message& msg, const sys::Mutex::ScopedLock& lock);
+ void observeAcquire(const Message& msg, const sys::Mutex::ScopedLock& lock);
+ void observeRequeue(const Message& msg, const sys::Mutex::ScopedLock& lock);
+ void observeDequeue(const Message& msg, const sys::Mutex::ScopedLock& lock, ScopedAutoDelete*);
+ void observeConsumerAdd( const Consumer&, const sys::Mutex::ScopedLock& lock);
+ void observeConsumerRemove( const Consumer&, const sys::Mutex::ScopedLock& lock);
+
+ bool acquire(const qpid::framing::SequenceNumber& position, Message& msg,
+ const qpid::sys::Mutex::ScopedLock& locker);
+
+ int getEventMode();
+ void dequeueFromStore(boost::intrusive_ptr<PersistableMessage>);
+ void abandoned(const Message& message);
+ bool checkNotDeleted(const Consumer::shared_ptr&);
+ void notifyDeleted();
+
+ /** Remove messages from the queue:
+ *@param maxCount Maximum number of messages to remove, 0 means unlimited.
+ *@param p Only remove messages for which p(msg) is true.
+ *@param f Call f on each message that is removed.
+ *@param st Use a cursor of this SubscriptionType to iterate messages to remove.
+ *@param triggerAutoDelete If true removing messages may trigger aut-delete.
+ *@param maxTests Max number of messages to test for removal, 0 means unlimited.
+ *@return Number of messages removed.
+ */
+ uint32_t remove(uint32_t maxCount,
+ MessagePredicate p, MessageFunctor f,
+ SubscriptionType st,
+ bool triggerAutoDelete,
+ uint32_t maxTests=0);
+
+ virtual bool checkDepth(const QueueDepth& increment, const Message&);
+ void tryAutoDelete();
+ public:
+
+ typedef std::vector<shared_ptr> vector;
+
+ QPID_BROKER_EXTERN Queue(const std::string& name,
+ const QueueSettings& settings = QueueSettings(),
+ MessageStore* const store = 0,
+ management::Manageable* parent = 0,
+ Broker* broker = 0);
+ QPID_BROKER_EXTERN virtual ~Queue();
+
+ /** allow the Consumer to consume or browse the next available message */
+ QPID_BROKER_EXTERN bool dispatch(Consumer::shared_ptr);
+
+ /** allow the Consumer to acquire a message that it has browsed.
+ * @param msg - message to be acquired.
+ * @return false if message is no longer available for acquire.
+ */
+ QPID_BROKER_EXTERN bool acquire(const QueueCursor& msg, const std::string& consumer);
+
+ /**
+ * Used to create a persistent record for the queue in store if required.
+ */
+ QPID_BROKER_EXTERN void create();
+
+ void destroyed();
+ QPID_BROKER_EXTERN void bound(const std::string& exchange,
+ const std::string& key,
+ const qpid::framing::FieldTable& args);
+ //TODO: get unbind out of the public interface; only there for purposes of one unit test
+ QPID_BROKER_EXTERN void unbind(ExchangeRegistry& exchanges);
+ /**
+ * Bind self to specified exchange, and record that binding for unbinding on delete.
+ */
+ QPID_BROKER_EXTERN bool bind(
+ boost::shared_ptr<Exchange> exchange, const std::string& key,
+ const qpid::framing::FieldTable& arguments=qpid::framing::FieldTable());
+
+ /**
+ * Removes (and dequeues) a message by its sequence number (used
+ * for some broker features, e.g. queue replication)
+ *
+ * @param position the sequence number of the message to be dequeued.
+ * @return true if the message is dequeued.
+ */
+ QPID_BROKER_EXTERN bool dequeueMessageAt(const qpid::framing::SequenceNumber& position);
+
+ /**
+ * Delivers a message to the queue or to overflow partner.
+ */
+ QPID_BROKER_EXTERN void deliver(Message, TxBuffer* = 0);
+ /**
+ * Delivers a message to the queue. Will record it as
+ * enqueued if persistent then process it.
+ */
+ private:
+ QPID_BROKER_EXTERN void deliverTo(Message, TxBuffer* = 0);
+ public:
+ /**
+ * Returns a message to the in-memory queue (due to lack
+ * of acknowledegement from a receiver). If a consumer is
+ * available it will be dispatched immediately, else it
+ * will be returned to the front of the queue.
+ */
+ QPID_BROKER_EXTERN void release(const QueueCursor& msg, bool markRedelivered=true);
+ QPID_BROKER_EXTERN void reject(const QueueCursor& msg);
+
+ QPID_BROKER_EXTERN bool seek(QueueCursor&, MessagePredicate);
+ QPID_BROKER_EXTERN bool seek(QueueCursor&, MessagePredicate, qpid::framing::SequenceNumber start);
+ QPID_BROKER_EXTERN bool seek(QueueCursor&, qpid::framing::SequenceNumber start);
+ /**
+ * Used during recovery to add stored messages back to the queue
+ */
+ QPID_BROKER_EXTERN void recover(Message& msg);
+
+ QPID_BROKER_EXTERN void consume(Consumer::shared_ptr c,
+ bool exclusive = false,
+ const framing::FieldTable& arguments = framing::FieldTable(),
+ const std::string& connectionId=std::string(),
+ const std::string& userId=std::string());
+
+ QPID_BROKER_EXTERN void cancel(Consumer::shared_ptr c,
+ const std::string& connectionId=std::string(),
+ const std::string& userId=std::string());
+ /**
+ * Used to indicate that the queue is being used in some other
+ * context than by a subscriber. The controlling flag should only
+ * be set if the mode of use is the one that caused the queue to
+ * be created.
+ */
+ QPID_BROKER_EXTERN void markInUse(bool controlling=false);
+ QPID_BROKER_EXTERN void releaseFromUse(bool controlling=false, bool doDelete=true);
+
+ QPID_BROKER_EXTERN uint32_t purge(const uint32_t purge_request=0, //defaults to all messages
+ boost::shared_ptr<Exchange> dest=boost::shared_ptr<Exchange>(),
+ const ::qpid::types::Variant::Map *filter=0);
+ QPID_BROKER_EXTERN void purgeExpired(sys::Duration);
+
+ //move qty # of messages to destination Queue destq
+ QPID_BROKER_EXTERN uint32_t move(
+ const Queue::shared_ptr destq, uint32_t qty,
+ const qpid::types::Variant::Map *filter=0);
+
+ QPID_BROKER_EXTERN uint32_t getMessageCount() const;
+ QPID_BROKER_EXTERN uint32_t getConsumerCount() const;
+ inline const std::string& getName() const { return name; }
+ QPID_BROKER_EXTERN bool isExclusiveOwner(const OwnershipToken* const o) const;
+ QPID_BROKER_EXTERN void releaseExclusiveOwnership(bool immediateExpiry=false);
+ QPID_BROKER_EXTERN bool setExclusiveOwner(const OwnershipToken* const o);
+ QPID_BROKER_EXTERN bool hasExclusiveConsumer() const;
+ QPID_BROKER_EXTERN bool hasExclusiveOwner() const;
+ inline bool isDurable() const { return store != 0; }
+ inline const QueueSettings& getSettings() const { return settings; }
+ inline const qpid::framing::FieldTable& getEncodableSettings() const { return encodableSettings; }
+ inline bool isAutoDelete() const { return settings.autodelete; }
+ inline bool isBrowseOnly() const { return settings.isBrowseOnly; }
+ QPID_BROKER_EXTERN bool canAutoDelete() const;
+ QPID_BROKER_EXTERN void scheduleAutoDelete(bool immediate=false);
+ QPID_BROKER_EXTERN bool isDeleted() const;
+ const QueueBindings& getBindings() const { return bindings; }
+
+ /**
+ * Dequeue message referenced by cursor. If txn is specified, this will
+ * occur only when txn is committed.
+ */
+ QPID_BROKER_EXTERN void dequeue(const QueueCursor& cursor, TxBuffer* txn);
+
+ /**
+ * dequeue from store (only done once messages is acknowledged)
+ */
+ QPID_BROKER_EXTERN void dequeue(TransactionContext* ctxt, const QueueCursor&);
+
+ /**
+ * Inform the queue that a previous transactional dequeue
+ * committed.
+ */
+ void dequeueCommitted(const QueueCursor& msg);
+
+ /** Get the message at position pos, returns true if found and sets msg */
+ QPID_BROKER_EXTERN bool find(framing::SequenceNumber pos, Message& msg ) const;
+
+ // Remember the queue's owner so acl quotas can be restored after restart
+ void setOwningUser(std::string& _userId);
+ void updateAclUserQueueCount();
+
+ QPID_BROKER_EXTERN void setAlternateExchange(boost::shared_ptr<Exchange> exchange);
+ QPID_BROKER_EXTERN boost::shared_ptr<Exchange> getAlternateExchange();
+ QPID_BROKER_EXTERN bool isLocal(const Message& msg);
+
+ //PersistableQueue support:
+ QPID_BROKER_EXTERN uint64_t getPersistenceId() const;
+ QPID_BROKER_EXTERN void setPersistenceId(uint64_t persistenceId) const;
+ QPID_BROKER_EXTERN void encode(framing::Buffer& buffer) const;
+ QPID_BROKER_EXTERN uint32_t encodedSize() const;
+
+ /**
+ * Restores a queue from encoded data (used in recovery)
+ *
+ * Note: restored queue will be neither auto-deleted or have an
+ * exclusive owner
+ */
+ static Queue::shared_ptr restore(QueueRegistry& queues, framing::Buffer& buffer);
+
+ virtual void setExternalQueueStore(ExternalQueueStore* inst);
+
+ // Increment the rejected-by-consumer counter.
+ QPID_BROKER_EXTERN void countRejected() const;
+
+ // Manageable entry points
+ QPID_BROKER_EXTERN management::ManagementObject::shared_ptr GetManagementObject(void) const;
+ management::Manageable::status_t
+ QPID_BROKER_EXTERN ManagementMethod (uint32_t methodId, management::Args& args, std::string& text);
+ QPID_BROKER_EXTERN void query(::qpid::types::Variant::Map&) const;
+
+ /** Apply f to each Message on the queue. */
+ template <class F> void eachMessage(F f) {
+ sys::Mutex::ScopedLock l(messageLock);
+ messages->foreach(f);
+ }
+
+ /** Apply f to each QueueBinding on the queue */
+ template <class F> void eachBinding(F f) {
+ bindings.eachBinding(f);
+ }
+
+ /**
+ * Set the sequence number for the back of the queue, the
+ * next message enqueued will be pos+1.
+ * If pos > getPosition() this creates a gap in the sequence numbers.
+ * if pos < getPosition() the back of the queue is reset to pos,
+ *
+ * The _caller_ must ensure that any messages after pos have been dequeued.
+ *
+ * Used by HA code for queue replication.
+ */
+ QPID_BROKER_EXTERN void setPosition(framing::SequenceNumber pos);
+
+ /**
+ *@return sequence number for the back of the queue. The next message pushed
+ * will be at getPosition()+1
+ */
+ QPID_BROKER_EXTERN framing::SequenceNumber getPosition();
+
+ /**
+ * Set front and back.
+ * If the queue is empty then front = back+1 (the first message to
+ * consume will be the next message pushed.)
+ *
+ *@param front = Position of first message to consume.
+ *@param back = getPosition(), next message pushed will be getPosition()+1
+ *@param type Subscription type to use to determine the front.
+ */
+ QPID_BROKER_EXTERN void getRange(
+ framing::SequenceNumber& front, framing::SequenceNumber& back,
+ SubscriptionType type=CONSUMER
+ );
+
+
+ QPID_BROKER_EXTERN void insertSequenceNumbers(const std::string& key);
+
+ QPID_BROKER_EXTERN MessageInterceptors& getMessageInterceptors() { return interceptors; }
+ QPID_BROKER_EXTERN QueueObservers& getObservers() { return observers; }
+
+ /**
+ * Notify queue that recovery has completed.
+ */
+ QPID_BROKER_EXTERN void recoveryComplete(ExchangeRegistry& exchanges);
+
+ /**
+ * Reserve space in policy for an enqueued message that
+ * has been recovered in the prepared state (dtx only)
+ */
+ QPID_BROKER_EXTERN void recoverPrepared(const Message& msg);
+ void enqueueAborted(const Message& msg);
+ void enqueueCommited(Message& msg);
+ void dequeueAborted(Message& msg);
+ void dequeueCommited(const Message& msg);
+
+ QPID_BROKER_EXTERN void flush();
+
+ QPID_BROKER_EXTERN Broker* getBroker();
+
+ uint32_t getDequeueSincePurge() { return dequeueSincePurge.get(); }
+ QPID_BROKER_EXTERN void setDequeueSincePurge(uint32_t value);
+
+ /** Add an argument to be included in management messages about this queue. */
+ QPID_BROKER_EXTERN void addArgument(const std::string& key, const types::Variant& value);
+
+ /**
+ * Atomic Redirect
+ */
+ QPID_BROKER_EXTERN void setRedirectPeer ( Queue::shared_ptr peer, bool isSrc );
+ QPID_BROKER_EXTERN Queue::shared_ptr getRedirectPeer() { return redirectPeer; }
+ QPID_BROKER_EXTERN bool isRedirectSource() const { return redirectSource; }
+ QPID_BROKER_EXTERN void setMgmtRedirectState( std::string peer, bool enabled, bool isSrc );
+
+ //utility function
+ static bool reroute(boost::shared_ptr<Exchange> e, const Message& m);
+
+ friend class QueueFactory;
+};
+}
+}
+
+
+#endif /*!_broker_Queue_h*/
diff --git a/qpid/cpp/src/qpid/broker/QueueBindings.cpp b/qpid/cpp/src/qpid/broker/QueueBindings.cpp
new file mode 100644
index 0000000000..1cc3486d9a
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/QueueBindings.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/QueueBindings.h"
+#include "qpid/broker/ExchangeRegistry.h"
+#include "qpid/framing/reply_exceptions.h"
+
+using qpid::framing::FieldTable;
+using qpid::framing::NotFoundException;
+using std::string;
+using namespace qpid::broker;
+
+void QueueBindings::add(const string& exchange, const string& key, const FieldTable& args)
+{
+ sys::Mutex::ScopedLock l(lock);
+ bindings.push_back(QueueBinding(exchange, key, args));
+}
+
+void QueueBindings::unbind(ExchangeRegistry& exchanges, Queue::shared_ptr queue)
+{
+ Bindings local;
+ {
+ sys::Mutex::ScopedLock l(lock);
+ local = bindings;
+ }
+
+ for (Bindings::iterator i = local.begin(); i != local.end(); i++) {
+ Exchange::shared_ptr ex = exchanges.find(i->exchange);
+ if (ex) ex->unbind(queue, i->key, &(i->args));
+ }
+}
+
+QueueBinding::QueueBinding(const string& _exchange, const string& _key, const FieldTable& _args)
+ : exchange(_exchange), key(_key), args(_args)
+{}
diff --git a/qpid/cpp/src/qpid/broker/QueueBindings.h b/qpid/cpp/src/qpid/broker/QueueBindings.h
new file mode 100644
index 0000000000..f9b07e7431
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/QueueBindings.h
@@ -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.
+ *
+ */
+#ifndef _QueueBindings_
+#define _QueueBindings_
+
+#include "qpid/framing/FieldTable.h"
+#include "qpid/sys/Mutex.h"
+#include <boost/ptr_container/ptr_list.hpp>
+#include <boost/shared_ptr.hpp>
+#include <algorithm>
+
+namespace qpid {
+namespace broker {
+
+class ExchangeRegistry;
+class Queue;
+
+struct QueueBinding{
+ std::string exchange;
+ std::string key;
+ qpid::framing::FieldTable args;
+ QueueBinding(const std::string& exchange, const std::string& key, const qpid::framing::FieldTable& args);
+};
+
+class QueueBindings
+{
+ public:
+
+ /** Apply f to each QueueBinding. */
+ template <class F> void eachBinding(F f) const {
+ Bindings local;
+ {
+ sys::Mutex::ScopedLock l(lock);
+ local = bindings;
+ }
+ std::for_each(local.begin(), local.end(), f);
+ }
+
+ void add(const std::string& exchange, const std::string& key, const qpid::framing::FieldTable& args);
+ void unbind(ExchangeRegistry& exchanges, boost::shared_ptr<Queue> queue);
+
+ private:
+ mutable sys::Mutex lock;
+ typedef std::vector<QueueBinding> Bindings;
+ Bindings bindings;
+};
+
+
+}} // namespace qpid::broker
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/QueueCleaner.cpp b/qpid/cpp/src/qpid/broker/QueueCleaner.cpp
new file mode 100644
index 0000000000..bf4b62c849
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/QueueCleaner.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/QueueCleaner.h"
+
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/sys/Timer.h"
+#include "qpid/sys/Time.h"
+
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+
+namespace qpid {
+namespace broker {
+
+namespace {
+ typedef boost::function0<void> FireFunction;
+ class Task : public sys::TimerTask
+ {
+ public:
+ Task(FireFunction f, sys::Duration duration);
+ void fire();
+ private:
+ FireFunction fireFunction;
+ };
+
+ Task::Task(FireFunction f, qpid::sys::Duration d) : sys::TimerTask(d,"QueueCleaner"), fireFunction(f) {}
+
+ void Task::fire()
+ {
+ fireFunction();
+ }
+}
+QueueCleaner::QueueCleaner(QueueRegistry& q, boost::shared_ptr<sys::Poller> p, sys::Timer* t)
+ : queues(q), timer(t), purging(boost::bind(&QueueCleaner::purge, this, _1), p)
+{
+ purging.start();
+}
+
+QueueCleaner::~QueueCleaner()
+{
+ purging.stop();
+ if (task) task->cancel();
+}
+
+void QueueCleaner::start(qpid::sys::Duration p)
+{
+ period = p;
+ task = new Task(boost::bind(&QueueCleaner::fired, this), p);
+ timer->add(task);
+}
+
+void QueueCleaner::setTimer(qpid::sys::Timer* timer) {
+ this->timer = timer;
+}
+
+void QueueCleaner::fired()
+{
+ QPID_LOG(debug, "QueueCleaner::fired: requesting purge");
+ queues.eachQueue(boost::bind(&PurgeSet::push, &purging, _1));
+ task->restart(); // Update task restart time to now()+interval
+ timer->add(task);
+}
+
+QueueCleaner::QueuePtrs::const_iterator QueueCleaner::purge(const QueueCleaner::QueuePtrs& batch)
+{
+ const sys::AbsTime tmoTime = sys::AbsTime(sys::AbsTime::now(), 1 * sys::TIME_SEC);
+ int nPurged = 0;
+ QueuePtrs::const_iterator batchItr = batch.begin();
+ for ( ; batchItr != batch.end() && sys::AbsTime::now() < tmoTime; ++batchItr) {
+ task->restart(); // Update task restart time to now()+interval
+ (*batchItr)->purgeExpired(period);
+ nPurged++;
+ }
+ QPID_LOG(debug, "QueueCleaner::purge: purged " << nPurged << " of " << batch.size() << " queues");
+ task->restart(); // Update task restart time to now()+interval
+ return batchItr;
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/QueueCleaner.h b/qpid/cpp/src/qpid/broker/QueueCleaner.h
new file mode 100644
index 0000000000..499326460f
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/QueueCleaner.h
@@ -0,0 +1,70 @@
+#ifndef QPID_BROKER_QUEUECLEANER_H
+#define QPID_BROKER_QUEUECLEANER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/sys/PollableQueue.h"
+#include "qpid/sys/Time.h"
+
+#include <boost/intrusive_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+
+namespace sys {
+ class Timer;
+ class TimerTask;
+}
+
+namespace broker {
+
+class Queue;
+class QueueRegistry;
+/**
+ * TimerTask to purge expired messages from queues
+ */
+class QueueCleaner
+{
+ public:
+ QPID_BROKER_EXTERN QueueCleaner(QueueRegistry& queues, boost::shared_ptr<sys::Poller>, sys::Timer* timer);
+ QPID_BROKER_EXTERN ~QueueCleaner();
+ QPID_BROKER_EXTERN void start(sys::Duration period);
+ QPID_BROKER_EXTERN void setTimer(sys::Timer* timer);
+
+ private:
+ typedef boost::shared_ptr<Queue> QueuePtr;
+ typedef std::deque< QueuePtr > QueuePtrs;
+ typedef qpid::sys::PollableQueue< QueuePtr > PurgeSet;
+ boost::intrusive_ptr<sys::TimerTask> task;
+ QueueRegistry& queues;
+ sys::Timer* timer;
+ sys::Duration period;
+ PurgeSet purging;
+
+ void fired();
+ QueuePtrs::const_iterator purge(const QueuePtrs&);
+
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_QUEUECLEANER_H*/
diff --git a/qpid/cpp/src/qpid/broker/QueueCursor.cpp b/qpid/cpp/src/qpid/broker/QueueCursor.cpp
new file mode 100644
index 0000000000..e48b18b748
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/QueueCursor.cpp
@@ -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.
+ *
+ */
+#include "QueueCursor.h"
+#include "qpid/broker/Message.h"
+
+namespace qpid {
+namespace broker {
+QueueCursor::QueueCursor(SubscriptionType t) : type(t), position(0), version(0), valid(false) {}
+
+void QueueCursor::setPosition(int32_t p, int32_t v)
+{
+ position = p;
+ version = v;
+ valid = true;
+}
+
+bool QueueCursor::check(const Message& m)
+{
+ return (m.getState() == AVAILABLE || ((type == REPLICATOR || type == PURGE) && m.getState() == ACQUIRED));
+}
+
+bool QueueCursor::isValid(int32_t v)
+{
+ return valid && (valid = (v == version));
+}
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/QueueCursor.h b/qpid/cpp/src/qpid/broker/QueueCursor.h
new file mode 100644
index 0000000000..c7368177e9
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/QueueCursor.h
@@ -0,0 +1,72 @@
+#ifndef QPID_BROKER_QUEUECURSOR_H
+#define QPID_BROKER_QUEUECURSOR_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/sys/IntegerTypes.h"
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace broker {
+
+class Message;
+
+enum SubscriptionType
+{
+ CONSUMER,
+ BROWSER,
+ PURGE,
+ REPLICATOR
+};
+
+class CursorContext {
+ public:
+ virtual ~CursorContext() {}
+};
+/**
+ *
+ */
+class QueueCursor
+{
+ public:
+ QPID_BROKER_EXTERN QueueCursor(SubscriptionType type = CONSUMER);
+
+ private:
+ SubscriptionType type;
+ int32_t position;
+ int32_t version;
+ bool valid;
+ boost::shared_ptr<CursorContext> context;
+
+ void setPosition(int32_t p, int32_t v);
+ bool check(const Message& m);
+ bool isValid(int32_t v);
+
+ friend class MessageDeque;
+ friend class MessageMap;
+ friend class PriorityQueue;
+ friend class PagedQueue;
+ template <typename T> friend class IndexedDeque;
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_QUEUECURSOR_H*/
diff --git a/qpid/cpp/src/qpid/broker/QueueDepth.cpp b/qpid/cpp/src/qpid/broker/QueueDepth.cpp
new file mode 100644
index 0000000000..69ec0ab4ac
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/QueueDepth.cpp
@@ -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.
+ *
+ */
+#include "QueueDepth.h"
+
+namespace qpid {
+namespace broker {
+
+QueueDepth::QueueDepth() {}
+QueueDepth::QueueDepth(uint32_t c, uint64_t s) : count(c), size(s) {}
+QueueDepth& QueueDepth::operator+=(const QueueDepth& other)
+{
+ if (count.valid) count.value += other.count.value;
+ if (size.valid) size.value += other.size.value;
+ return *this;
+}
+QueueDepth& QueueDepth::operator-=(const QueueDepth& other)
+{
+ if (count.valid) count.value -= other.count.value;
+ if (size.valid) size.value -= other.size.value;
+ return *this;
+}
+bool QueueDepth::operator==(const QueueDepth& other) const
+{
+ //only compare values, not validity an invalid value is always 0;
+ //this means that an invalid value will match an empty queue
+ //depth, which is fine
+ return (count.value == other.count.value)
+ && (size.value == other.size.value);
+}
+bool QueueDepth::operator!=(const QueueDepth& other) const
+{
+ return !(*this == other);
+}
+bool QueueDepth::operator<(const QueueDepth& other) const
+{
+ if (count.valid && size.valid)
+ return count.value < other.count.value || size.value < other.size.value;
+ else if (count.valid)
+ return count.value < other.count.value;
+ else
+ return size.value < other.size.value;
+}
+bool QueueDepth::operator>(const QueueDepth& other) const
+{
+ if (count.valid && size.valid)
+ return count.value > other.count.value || size.value > other.size.value;
+ else if (count.valid)
+ return count.value > other.count.value;
+ else
+ return size.value > other.size.value;
+}
+bool QueueDepth::hasCount() const { return count.valid; }
+uint32_t QueueDepth::getCount() const { return count.value; }
+void QueueDepth::setCount(uint32_t c) { count.value = c; count.valid = true; }
+bool QueueDepth::hasSize() const { return size.valid; }
+uint64_t QueueDepth::getSize() const { return size.value; }
+void QueueDepth::setSize(uint64_t c) { size.value = c; size.valid = true; }
+
+namespace{
+ template <typename T> QueueDepth::Optional<T> add(const QueueDepth::Optional<T>& a, const QueueDepth::Optional<T>& b)
+ {
+ QueueDepth::Optional<T> result;
+ if (a.valid && b.valid) {
+ result.valid = true;
+ result.value = a.value + b.value;
+ }
+ return result;
+ }
+ template <typename T> QueueDepth::Optional<T> subtract(const QueueDepth::Optional<T>& a, const QueueDepth::Optional<T>& b)
+ {
+ QueueDepth::Optional<T> result;
+ if (a.valid && b.valid) {
+ result.valid = true;
+ result.value = a.value - b.value;
+ }
+ return result;
+ }
+}
+QueueDepth operator-(const QueueDepth& a, const QueueDepth& b)
+{
+ QueueDepth result;
+ result.count = subtract(a.count, b.count);
+ result.size = subtract(a.size, b.size);
+ return result;
+}
+
+QueueDepth operator+(const QueueDepth& a, const QueueDepth& b)
+{
+ QueueDepth result;
+ result.count = add(a.count, b.count);
+ result.size = add(a.size, b.size);
+ return result;
+
+}
+
+std::ostream& operator<<(std::ostream& o, const QueueDepth& d)
+{
+ if (d.hasCount()) o << "count: " << d.getCount();
+ if (d.hasSize()) {
+ if (d.hasCount()) o << ", ";
+ o << "size: " << d.getSize();
+ }
+ return o;
+}
+
+QueueDepth::operator bool() const { return hasCount() || hasSize(); }
+
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/QueueDepth.h b/qpid/cpp/src/qpid/broker/QueueDepth.h
new file mode 100644
index 0000000000..d93acb2a7a
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/QueueDepth.h
@@ -0,0 +1,74 @@
+#ifndef QPID_BROKER_QUEUEDEPTH_H
+#define QPID_BROKER_QUEUEDEPTH_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/sys/IntegerTypes.h"
+#include <ostream>
+
+namespace qpid {
+namespace broker {
+
+/**
+ * Represents a queue depth in message count and/or aggregate message
+ * size.
+ */
+class QueueDepth
+{
+ public:
+ QPID_BROKER_EXTERN QueueDepth();
+ QPID_BROKER_EXTERN QueueDepth(uint32_t count, uint64_t size);
+ QPID_BROKER_EXTERN QueueDepth& operator+=(const QueueDepth&);
+ QPID_BROKER_EXTERN QueueDepth& operator-=(const QueueDepth&);
+ QPID_BROKER_EXTERN bool operator==(const QueueDepth&) const;
+ QPID_BROKER_EXTERN bool operator!=(const QueueDepth&) const;
+ QPID_BROKER_EXTERN bool operator<(const QueueDepth& other) const;
+ QPID_BROKER_EXTERN bool operator>(const QueueDepth& other) const;
+ QPID_BROKER_EXTERN operator bool() const;
+ QPID_BROKER_EXTERN bool hasCount() const;
+ QPID_BROKER_EXTERN uint32_t getCount() const;
+ QPID_BROKER_EXTERN void setCount(uint32_t);
+ QPID_BROKER_EXTERN bool hasSize() const;
+ QPID_BROKER_EXTERN uint64_t getSize() const;
+ QPID_BROKER_EXTERN void setSize(uint64_t);
+ friend QPID_BROKER_EXTERN QueueDepth operator-(const QueueDepth&, const QueueDepth&);
+ friend QPID_BROKER_EXTERN QueueDepth operator+(const QueueDepth&, const QueueDepth&);
+ template <typename T> struct Optional
+ {
+ T value;
+ bool valid;
+
+ Optional(T v) : value(v), valid(true) {}
+ Optional() : value(0), valid(false) {}
+ };
+ private:
+ Optional<uint32_t> count;
+ Optional<uint64_t> size;
+};
+
+QPID_BROKER_EXTERN QueueDepth operator-(const QueueDepth&, const QueueDepth&);
+QPID_BROKER_EXTERN QueueDepth operator+(const QueueDepth&, const QueueDepth&);
+std::ostream& operator<<(std::ostream&, const QueueDepth&);
+
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_QUEUEDEPTH_H*/
diff --git a/qpid/cpp/src/qpid/broker/QueueFactory.cpp b/qpid/cpp/src/qpid/broker/QueueFactory.cpp
new file mode 100644
index 0000000000..16cdea3b0a
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/QueueFactory.cpp
@@ -0,0 +1,142 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/QueueFactory.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/QueueSettings.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/LossyLvq.h"
+#include "qpid/broker/LossyQueue.h"
+#include "qpid/broker/Lvq.h"
+#include "qpid/broker/Messages.h"
+#include "qpid/broker/MessageDistributor.h"
+#include "qpid/broker/MessageGroupManager.h"
+#include "qpid/broker/Fairshare.h"
+#include "qpid/broker/MessageDeque.h"
+#include "qpid/broker/MessageMap.h"
+#include "qpid/broker/PagedQueue.h"
+#include "qpid/broker/PriorityQueue.h"
+#include "qpid/broker/QueueFlowLimit.h"
+#include "qpid/broker/SelfDestructQueue.h"
+#include "qpid/broker/ThresholdAlerts.h"
+#include "qpid/broker/FifoDistributor.h"
+#include "qpid/log/Statement.h"
+#include <map>
+#include <memory>
+
+namespace qpid {
+namespace broker {
+
+QueueFactory::QueueFactory() : broker(0), store(0), parent(0) {}
+
+boost::shared_ptr<Queue> QueueFactory::create(const std::string& name, const QueueSettings& settings)
+{
+ settings.validate();
+ boost::shared_ptr<QueueFlowLimit> flow_ptr(QueueFlowLimit::createLimit(name, settings));
+
+ //1. determine Queue type (i.e. whether we are subclassing Queue)
+ boost::shared_ptr<Queue> queue;
+ if (settings.dropMessagesAtLimit) {
+ // -> if 'ring' policy is in use then subclass
+ if (settings.lvqKey.size()) {
+ //combination of ring and lvq:
+ std::auto_ptr<MessageMap> map(new MessageMap(settings.lvqKey));
+ queue = boost::shared_ptr<Queue>(new LossyLvq(name, map, settings, settings.durable ? store : 0, parent, broker));
+ } else {
+ //simple ring:
+ queue = boost::shared_ptr<Queue>(new LossyQueue(name, settings, settings.durable ? store : 0, parent, broker));
+ }
+ } else if (settings.selfDestructAtLimit) {
+ queue = boost::shared_ptr<Queue>(new SelfDestructQueue(name, settings, settings.durable ? store : 0, parent, broker));
+ } else if (settings.lvqKey.size()) {
+ std::auto_ptr<MessageMap> map(new MessageMap(settings.lvqKey));
+ queue = boost::shared_ptr<Queue>(new Lvq(name, map, settings, settings.durable ? store : 0, parent, broker));
+ } else {
+ queue = boost::shared_ptr<Queue>(new Queue(name, settings, settings.durable ? store : 0, parent, broker));
+ }
+
+ //2. determine Messages type (i.e. structure)
+ if (settings.priorities) {
+ if (settings.defaultFairshare || settings.fairshare.size()) {
+ queue->messages = Fairshare::create(settings);
+ } else {
+ queue->messages = std::auto_ptr<Messages>(new PriorityQueue(settings.priorities));
+ }
+ } else if (settings.paging) {
+ if (!broker) {
+ QPID_LOG(warning, "Cannot create paged queue without broker context");
+ } else if (!qpid::sys::MemoryMappedFile::isSupported()) {
+ QPID_LOG(warning, "Cannot create paged queue; memory mapped file support not available on this platform");
+ } else if ( !broker->getPagingDir().isEnabled() ) {
+ QPID_LOG(warning, "Cannot create paged queue; no paging directory enabled");
+ } else {
+ queue->messages = std::auto_ptr<Messages>(new PagedQueue(name, broker->getPagingDir().getPath(),
+ settings.maxPages ? settings.maxPages : DEFAULT_MAX_PAGES,
+ settings.pageFactor ? settings.pageFactor : DEFAULT_PAGE_FACTOR,
+ broker->getProtocolRegistry()));
+ }
+ } else if (settings.lvqKey.empty()) {//LVQ already handled above
+ queue->messages = std::auto_ptr<Messages>(new MessageDeque());
+ }
+
+ //3. determine MessageDistributor type
+ if (settings.groupKey.size()) {
+ boost::shared_ptr<MessageGroupManager> mgm(MessageGroupManager::create( name, *(queue->messages), settings));
+ queue->allocator = mgm;
+ queue->getObservers().add(mgm);
+ } else {
+ queue->allocator = boost::shared_ptr<MessageDistributor>(new FifoDistributor( *(queue->messages) ));
+ }
+
+
+ //4. threshold event config
+ if (broker && broker->getManagementAgent()) {
+ ThresholdAlerts::observe(*queue, *(broker->getManagementAgent()), settings, broker->getQueueThresholdEventRatio());
+ }
+ //5. flow control config
+ if (flow_ptr) {
+ flow_ptr->observe(*queue);
+ }
+
+ return queue;
+}
+
+void QueueFactory::setBroker(Broker* b)
+{
+ broker = b;
+}
+Broker* QueueFactory::getBroker()
+{
+ return broker;
+}
+void QueueFactory::setStore (MessageStore* s)
+{
+ store = s;
+}
+MessageStore* QueueFactory::getStore() const
+{
+ return store;
+}
+void QueueFactory::setParent(management::Manageable* p)
+{
+ parent = p;
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/QueueFactory.h b/qpid/cpp/src/qpid/broker/QueueFactory.h
new file mode 100644
index 0000000000..585983ba25
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/QueueFactory.h
@@ -0,0 +1,76 @@
+#ifndef QPID_BROKER_QUEUEFACTORY_H
+#define QPID_BROKER_QUEUEFACTORY_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/types/Variant.h"
+#include <boost/shared_ptr.hpp>
+
+#define DEFAULT_MAX_PAGES 4
+#define DEFAULT_PAGE_FACTOR 1
+
+namespace qpid {
+namespace management {
+class Manageable;
+}
+namespace broker {
+class Broker;
+class MessageStore;
+class Queue;
+struct QueueSettings;
+
+/**
+ * Handles the creation and configuration of a Queue instance in order
+ * to meet the required settings
+ */
+class QueueFactory
+{
+ public:
+ QPID_BROKER_EXTERN QueueFactory();
+
+ QPID_BROKER_EXTERN boost::shared_ptr<Queue> create(const std::string& name, const QueueSettings& settings);
+
+ void setBroker(Broker*);
+ Broker* getBroker();
+
+ /**
+ * Set the store to use. May only be called once.
+ */
+ void setStore (MessageStore*);
+
+ /**
+ * Return the message store used.
+ */
+ MessageStore* getStore() const;
+
+ /**
+ * Register the manageable parent for declared queues
+ */
+ void setParent(management::Manageable*);
+ private:
+ Broker* broker;
+ MessageStore* store;
+ management::Manageable* parent;
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_QUEUEFACTORY_H*/
diff --git a/qpid/cpp/src/qpid/broker/QueueFlowLimit.cpp b/qpid/cpp/src/qpid/broker/QueueFlowLimit.cpp
new file mode 100644
index 0000000000..9c215d197f
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/QueueFlowLimit.cpp
@@ -0,0 +1,307 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/QueueFlowLimit.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/Message.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/QueueSettings.h"
+#include "qpid/Exception.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/broker/SessionState.h"
+
+#include "qmf/org/apache/qpid/broker/Queue.h"
+
+#include <sstream>
+
+#include <boost/enable_shared_from_this.hpp>
+
+using namespace qpid::broker;
+using namespace qpid::framing;
+
+namespace {
+ /** ensure that the configured flow control stop and resume values are
+ * valid with respect to the maximum queue capacity, and each other
+ */
+ template <typename T>
+ void validateFlowConfig(T max, T& stop, T& resume, const std::string& type, const std::string& queue)
+ {
+ if (stop) {
+ if (resume > stop) {
+ throw InvalidArgumentException(QPID_MSG("Queue \"" << queue << "\": qpid.flow_resume_" << type
+ << "=" << resume
+ << " must be less or equal to qpid.flow_stop_" << type
+ << "=" << stop));
+ }
+ if (resume == 0) resume = stop;
+ if (max != 0 && (max < stop)) {
+ throw InvalidArgumentException(QPID_MSG("Queue \"" << queue << "\": qpid.flow_stop_" << type
+ << "=" << stop
+ << " must be less than qpid.max_" << type
+ << "=" << max));
+ }
+ }
+ }
+}
+
+
+
+QueueFlowLimit::QueueFlowLimit(const std::string& _queueName,
+ uint32_t _flowStopCount, uint32_t _flowResumeCount,
+ uint64_t _flowStopSize, uint64_t _flowResumeSize)
+ : queue(0), queueName(_queueName),
+ flowStopCount(_flowStopCount), flowResumeCount(_flowResumeCount),
+ flowStopSize(_flowStopSize), flowResumeSize(_flowResumeSize),
+ flowStopped(false), count(0), size(0), broker(0)
+{
+ QPID_LOG(info, "Queue \"" << queueName << "\": Flow limit created: flowStopCount=" << flowStopCount
+ << ", flowResumeCount=" << flowResumeCount
+ << ", flowStopSize=" << flowStopSize << ", flowResumeSize=" << flowResumeSize );
+}
+
+
+QueueFlowLimit::~QueueFlowLimit()
+{
+ sys::Mutex::ScopedLock l(indexLock);
+ if (!index.empty()) {
+ // we're gone - release all pending msgs
+ for (std::map<framing::SequenceNumber, Message >::iterator itr = index.begin();
+ itr != index.end(); ++itr)
+ if (itr->second)
+ try {
+ itr->second.getPersistentContext()->getIngressCompletion().finishCompleter();
+ } catch (...) {} // ignore - not safe for a destructor to throw.
+ index.clear();
+ }
+}
+
+
+void QueueFlowLimit::enqueued(const Message& msg)
+{
+ sys::Mutex::ScopedLock l(indexLock);
+
+ ++count;
+ size += msg.getMessageSize();
+
+ if (!flowStopped) {
+ if (flowStopCount && count > flowStopCount) {
+ flowStopped = true;
+ QPID_LOG(info, "Queue \"" << queueName << "\": has reached " << flowStopCount << " enqueued messages. Producer flow control activated." );
+ } else if (flowStopSize && size > flowStopSize) {
+ flowStopped = true;
+ QPID_LOG(info, "Queue \"" << queueName << "\": has reached " << flowStopSize << " enqueued bytes. Producer flow control activated." );
+ }
+ if (flowStopped && queueMgmtObj) {
+ queueMgmtObj->set_flowStopped(true);
+ queueMgmtObj->inc_flowStoppedCount();
+ }
+ }
+
+ if (flowStopped || !index.empty()) {
+ QPID_LOG(trace, "Queue \"" << queueName << "\": setting flow control for msg pos=" << msg.getSequence());
+ msg.getPersistentContext()->getIngressCompletion().startCompleter(); // don't complete until flow resumes
+ bool unique;
+ unique = index.insert(std::pair<framing::SequenceNumber, Message >(msg.getSequence(), msg)).second;
+ // Like this to avoid tripping up unused variable warning when NDEBUG set
+ if (!unique) assert(unique);
+ }
+}
+
+
+
+void QueueFlowLimit::dequeued(const Message& msg)
+{
+ sys::Mutex::ScopedLock l(indexLock);
+
+ if (count > 0) {
+ --count;
+ } else {
+ throw Exception(QPID_MSG("Flow limit count underflow on dequeue. Queue=" << queueName));
+ }
+
+ uint64_t _size = msg.getMessageSize();
+ if (_size <= size) {
+ size -= _size;
+ } else {
+ throw Exception(QPID_MSG("Flow limit size underflow on dequeue. Queue=" << queueName));
+ }
+
+ if (flowStopped &&
+ (flowResumeSize == 0 || size < flowResumeSize) &&
+ (flowResumeCount == 0 || count < flowResumeCount)) {
+ flowStopped = false;
+ if (queueMgmtObj)
+ queueMgmtObj->set_flowStopped(false);
+ QPID_LOG(info, "Queue \"" << queueName << "\": has drained below the flow control resume level. Producer flow control deactivated." );
+ }
+
+ if (!index.empty()) {
+ if (!flowStopped) {
+ // flow enabled - release all pending msgs
+ for (std::map<framing::SequenceNumber, Message >::iterator itr = index.begin();
+ itr != index.end(); ++itr)
+ if (itr->second)
+ itr->second.getPersistentContext()->getIngressCompletion().finishCompleter();
+ index.clear();
+ } else {
+ // even if flow controlled, we must release this msg as it is being dequeued
+ std::map<framing::SequenceNumber, Message >::iterator itr = index.find(msg.getSequence());
+ if (itr != index.end()) { // this msg is flow controlled, release it:
+ msg.getPersistentContext()->getIngressCompletion().finishCompleter();
+ index.erase(itr);
+ }
+ }
+ }
+}
+
+
+void QueueFlowLimit::encode(Buffer& buffer) const
+{
+ buffer.putLong(flowStopCount);
+ buffer.putLong(flowResumeCount);
+ buffer.putLongLong(flowStopSize);
+ buffer.putLongLong(flowResumeSize);
+ buffer.putLong(count);
+ buffer.putLongLong(size);
+}
+
+
+void QueueFlowLimit::decode ( Buffer& buffer )
+{
+ flowStopCount = buffer.getLong();
+ flowResumeCount = buffer.getLong();
+ flowStopSize = buffer.getLongLong();
+ flowResumeSize = buffer.getLongLong();
+ count = buffer.getLong();
+ size = buffer.getLongLong();
+}
+
+
+uint32_t QueueFlowLimit::encodedSize() const {
+ return sizeof(uint32_t) + // flowStopCount
+ sizeof(uint32_t) + // flowResumecount
+ sizeof(uint64_t) + // flowStopSize
+ sizeof(uint64_t) + // flowResumeSize
+ sizeof(uint32_t) + // count
+ sizeof(uint64_t); // size
+}
+
+
+const std::string QueueFlowLimit::flowStopCountKey("qpid.flow_stop_count");
+const std::string QueueFlowLimit::flowResumeCountKey("qpid.flow_resume_count");
+const std::string QueueFlowLimit::flowStopSizeKey("qpid.flow_stop_size");
+const std::string QueueFlowLimit::flowResumeSizeKey("qpid.flow_resume_size");
+uint64_t QueueFlowLimit::defaultMaxSize;
+uint QueueFlowLimit::defaultFlowStopRatio;
+uint QueueFlowLimit::defaultFlowResumeRatio;
+
+
+void QueueFlowLimit::setDefaults(uint64_t maxQueueSize, uint flowStopRatio, uint flowResumeRatio)
+{
+ defaultMaxSize = maxQueueSize;
+ defaultFlowStopRatio = flowStopRatio;
+ defaultFlowResumeRatio = flowResumeRatio;
+
+ /** @todo KAG: Verify valid range on Broker::Options instead of here */
+ if (flowStopRatio > 100 || flowResumeRatio > 100)
+ throw InvalidArgumentException(QPID_MSG("Default queue flow ratios must be between 0 and 100, inclusive:"
+ << " flowStopRatio=" << flowStopRatio
+ << " flowResumeRatio=" << flowResumeRatio));
+ if (flowResumeRatio > flowStopRatio)
+ throw InvalidArgumentException(QPID_MSG("Default queue flow stop ratio must be >= flow resume ratio:"
+ << " flowStopRatio=" << flowStopRatio
+ << " flowResumeRatio=" << flowResumeRatio));
+}
+
+
+void QueueFlowLimit::observe(Queue& queue)
+{
+ /* set up management stuff */
+ broker = queue.getBroker();
+ queueMgmtObj = boost::dynamic_pointer_cast<_qmfBroker::Queue> (queue.GetManagementObject());
+ if (queueMgmtObj) {
+ queueMgmtObj->set_flowStopped(isFlowControlActive());
+ }
+
+ /* set up the observer */
+ queue.getObservers().add(shared_from_this());
+}
+
+/** returns ptr to a QueueFlowLimit, else 0 if no limit */
+boost::shared_ptr<QueueFlowLimit> QueueFlowLimit::createLimit(const std::string& queueName, const QueueSettings& settings)
+{
+ if (settings.dropMessagesAtLimit) {
+ // The size of a RING queue is limited by design - no need for flow control.
+ return boost::shared_ptr<QueueFlowLimit>();
+ }
+ if ((!settings.flowStop.hasCount()) && (!settings.flowStop.hasSize()) && (settings.flowResume.hasCount() || settings.flowResume.hasSize()))
+ QPID_LOG(warning, "queue " << queueName << ": user-configured flow limits are ignored as no stop limits provided");
+
+ uint32_t flowStopCount(0), flowResumeCount(0), maxMsgCount(settings.maxDepth.hasCount() ? settings.maxDepth.getCount() : 0);
+ uint64_t flowStopSize(0), flowResumeSize(0), maxByteCount(settings.maxDepth.hasSize() ? settings.maxDepth.getSize() : defaultMaxSize);
+
+ // pre-fill by defaults, if exist
+ if (defaultFlowStopRatio) { // broker has a default ratio setup...
+ flowStopSize = (uint64_t)(maxByteCount * (defaultFlowStopRatio/100.0) + 0.5);
+ flowStopCount = (uint32_t)(maxMsgCount * (defaultFlowStopRatio/100.0) + 0.5);
+ }
+
+ if (defaultFlowResumeRatio) { // broker has a default ratio setup...
+ flowResumeSize = (uint64_t)(maxByteCount * (defaultFlowResumeRatio/100.0));
+ flowResumeCount = (uint32_t)(maxMsgCount * (defaultFlowResumeRatio/100.0));
+ }
+
+ // update by user-specified thresholds
+ if (settings.flowStop.hasCount())
+ flowStopCount = settings.flowStop.getCount();
+ if (settings.flowStop.hasSize())
+ flowStopSize = settings.flowStop.getSize();
+ if (settings.flowResume.hasCount())
+ flowResumeCount = settings.flowResume.getCount();
+ if (settings.flowResume.hasSize())
+ flowResumeSize = settings.flowResume.getSize();
+
+ if (flowStopCount || flowStopSize) {
+ validateFlowConfig(maxMsgCount, flowStopCount, flowResumeCount, "count", queueName );
+ validateFlowConfig(maxByteCount, flowStopSize, flowResumeSize, "size", queueName );
+ return boost::shared_ptr<QueueFlowLimit>(new QueueFlowLimit(queueName, flowStopCount, flowResumeCount, flowStopSize, flowResumeSize));
+ }
+ else
+ //don't have a non-zero value for either the count or the
+ //size to stop at, so no flow limit applicable
+ return boost::shared_ptr<QueueFlowLimit>();
+}
+
+namespace qpid {
+namespace broker {
+
+std::ostream& operator<<(std::ostream& out, const QueueFlowLimit& f)
+{
+ out << "; flowStopCount=" << f.flowStopCount << ", flowResumeCount=" << f.flowResumeCount;
+ out << "; flowStopSize=" << f.flowStopSize << ", flowResumeSize=" << f.flowResumeSize;
+ return out;
+}
+
+}
+}
+
diff --git a/qpid/cpp/src/qpid/broker/QueueFlowLimit.h b/qpid/cpp/src/qpid/broker/QueueFlowLimit.h
new file mode 100644
index 0000000000..92bd7f76a0
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/QueueFlowLimit.h
@@ -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.
+ *
+ */
+#ifndef _QueueFlowLimit_
+#define _QueueFlowLimit_
+
+#include <list>
+#include <set>
+#include <iostream>
+#include <memory>
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/broker/QueueObserver.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/SequenceNumber.h"
+#include "qpid/sys/AtomicValue.h"
+#include "qpid/sys/Mutex.h"
+#include "qmf/org/apache/qpid/broker/Queue.h"
+
+#include <boost/enable_shared_from_this.hpp>
+
+namespace _qmfBroker = qmf::org::apache::qpid::broker;
+
+namespace qpid {
+namespace broker {
+
+class Broker;
+class Queue;
+class Message;
+struct QueueSettings;
+
+/**
+ * Producer flow control: when level is > flowStop*, flow control is ON.
+ * then level is < flowResume*, flow control is OFF. If == 0, flow control
+ * is not used. If both byte and msg count thresholds are set, then
+ * passing _either_ level may turn flow control ON, but _both_ must be
+ * below level before flow control will be turned OFF.
+ */
+ class QueueFlowLimit : public QueueObserver, public boost::enable_shared_from_this<QueueFlowLimit>
+{
+ static uint64_t defaultMaxSize;
+ static uint defaultFlowStopRatio;
+ static uint defaultFlowResumeRatio;
+
+ Queue *queue;
+ std::string queueName;
+
+ uint32_t flowStopCount;
+ uint32_t flowResumeCount;
+ uint64_t flowStopSize;
+ uint64_t flowResumeSize;
+ bool flowStopped; // true = producers held in flow control
+
+ // current queue utilization
+ uint32_t count;
+ uint64_t size;
+
+ public:
+ static QPID_BROKER_EXTERN const std::string flowStopCountKey;
+ static QPID_BROKER_EXTERN const std::string flowResumeCountKey;
+ static QPID_BROKER_EXTERN const std::string flowStopSizeKey;
+ static QPID_BROKER_EXTERN const std::string flowResumeSizeKey;
+
+ QPID_BROKER_EXTERN virtual ~QueueFlowLimit();
+
+ /** the queue has added QueuedMessage */
+ QPID_BROKER_EXTERN void enqueued(const Message&);
+ /** the queue has removed QueuedMessage */
+ QPID_BROKER_EXTERN void dequeued(const Message&);
+ /** ignored */
+ QPID_BROKER_EXTERN void acquired(const Message&) {};
+ QPID_BROKER_EXTERN void requeued(const Message&) {};
+
+ uint32_t getFlowStopCount() const { return flowStopCount; }
+ uint32_t getFlowResumeCount() const { return flowResumeCount; }
+ uint64_t getFlowStopSize() const { return flowStopSize; }
+ uint64_t getFlowResumeSize() const { return flowResumeSize; }
+
+ uint32_t getFlowCount() const { return count; }
+ uint64_t getFlowSize() const { return size; }
+ bool isFlowControlActive() const { return flowStopped; }
+ bool monitorFlowControl() const { return flowStopCount || flowStopSize; }
+
+ void encode(framing::Buffer& buffer) const;
+ void decode(framing::Buffer& buffer);
+ uint32_t encodedSize() const;
+
+ QPID_BROKER_EXTERN void observe(Queue& queue);
+ static QPID_BROKER_EXTERN boost::shared_ptr<QueueFlowLimit> createLimit(const std::string& queueName, const QueueSettings& settings);
+ static QPID_BROKER_EXTERN void setDefaults(uint64_t defaultMaxSize, uint defaultFlowStopRatio, uint defaultFlowResumeRatio);
+
+ friend QPID_BROKER_EXTERN std::ostream& operator<<(std::ostream&, const QueueFlowLimit&);
+
+ protected:
+ // msgs waiting for flow to become available.
+ std::map<framing::SequenceNumber, Message > index;
+ mutable qpid::sys::Mutex indexLock;
+
+ _qmfBroker::Queue::shared_ptr queueMgmtObj;
+
+ const Broker *broker;
+
+ QPID_BROKER_EXTERN QueueFlowLimit(const std::string& _queueName,
+ uint32_t _flowStopCount, uint32_t _flowResumeCount,
+ uint64_t _flowStopSize, uint64_t _flowResumeSize);
+};
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/QueueListeners.cpp b/qpid/cpp/src/qpid/broker/QueueListeners.cpp
new file mode 100644
index 0000000000..0338a674cf
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/QueueListeners.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/QueueListeners.h"
+#include <boost/bind.hpp>
+
+namespace qpid {
+namespace broker {
+
+void QueueListeners::addListener(Consumer::shared_ptr c)
+{
+ if (!c->inListeners) {
+ if (c->acquires) {
+ add(consumers, c);
+ } else {
+ add(browsers, c);
+ }
+ c->inListeners = true;
+ }
+}
+
+void QueueListeners::removeListener(Consumer::shared_ptr c)
+{
+ if (c->inListeners) {
+ if (c->acquires) {
+ remove(consumers, c);
+ } else {
+ remove(browsers, c);
+ }
+ c->inListeners = false;
+ }
+}
+
+void QueueListeners::populate(NotificationSet& set)
+{
+ if (consumers.size()) {
+ set.consumer = consumers.front();
+ consumers.pop_front();
+ set.consumer->inListeners = false;
+ }
+ // Don't swap the deques, hang on to the memory allocated.
+ set.browsers = browsers;
+ browsers.clear();
+ for (Listeners::iterator i = set.browsers.begin(); i != set.browsers.end(); i++)
+ (*i)->inListeners = false;
+}
+
+void QueueListeners::add(Listeners& listeners, Consumer::shared_ptr c)
+{
+ listeners.push_back(c);
+}
+
+void QueueListeners::remove(Listeners& listeners, Consumer::shared_ptr c)
+{
+ Listeners::iterator i = std::find(listeners.begin(), listeners.end(), c);
+ if (i != listeners.end()) listeners.erase(i);
+}
+
+void QueueListeners::NotificationSet::notify()
+{
+ if (consumer) consumer->notify();
+ std::for_each(browsers.begin(), browsers.end(), boost::mem_fn(&Consumer::notify));
+}
+
+void QueueListeners::ListenerSet::notifyAll()
+{
+ std::for_each(listeners.begin(), listeners.end(), boost::mem_fn(&Consumer::notify));
+}
+
+void QueueListeners::snapshot(ListenerSet& set)
+{
+ set.listeners.insert(set.listeners.end(), consumers.begin(), consumers.end());
+ set.listeners.insert(set.listeners.end(), browsers.begin(), browsers.end());
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/QueueListeners.h b/qpid/cpp/src/qpid/broker/QueueListeners.h
new file mode 100644
index 0000000000..ca844fd47e
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/QueueListeners.h
@@ -0,0 +1,85 @@
+#ifndef QPID_BROKER_QUEUELISTENERS_H
+#define QPID_BROKER_QUEUELISTENERS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/Consumer.h"
+#include <deque>
+
+namespace qpid {
+namespace broker {
+
+/**
+ * Track and notify components that wish to be notified of messages
+ * that become available on a queue.
+ *
+ * None of the methods defined here are protected by locking. However
+ * the populate method allows a 'snapshot' to be taken of the
+ * listeners to be notified. NotificationSet::notify() may then be
+ * called outside of any lock that protects the QueueListeners
+ * instance from concurrent access.
+ */
+class QueueListeners
+{
+ public:
+ typedef std::deque<Consumer::shared_ptr> Listeners;
+
+ class NotificationSet
+ {
+ public:
+ void notify();
+ private:
+ Listeners browsers;
+ Consumer::shared_ptr consumer;
+ friend class QueueListeners;
+ };
+
+ class ListenerSet
+ {
+ public:
+ void notifyAll();
+ private:
+ Listeners listeners;
+ friend class QueueListeners;
+ };
+
+ void addListener(Consumer::shared_ptr);
+ void removeListener(Consumer::shared_ptr);
+ void populate(NotificationSet&);
+ void snapshot(ListenerSet&);
+ void notifyAll();
+
+ template <class F> void eachListener(F f) {
+ std::for_each(browsers.begin(), browsers.end(), f);
+ std::for_each(consumers.begin(), consumers.end(), f);
+ }
+
+ private:
+ Listeners consumers;
+ Listeners browsers;
+
+ void add(Listeners&, Consumer::shared_ptr);
+ void remove(Listeners&, Consumer::shared_ptr);
+
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_QUEUELISTENERS_H*/
diff --git a/qpid/cpp/src/qpid/broker/QueueObserver.h b/qpid/cpp/src/qpid/broker/QueueObserver.h
new file mode 100644
index 0000000000..6d29f83721
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/QueueObserver.h
@@ -0,0 +1,76 @@
+#ifndef QPID_BROKER_QUEUEOBSERVER_H
+#define QPID_BROKER_QUEUEOBSERVER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+namespace qpid {
+namespace broker {
+
+class Consumer;
+class Message;
+
+/**
+ * Interface for notifying classes who want to act as 'observers' of a queue of particular
+ * events.
+ *
+ * The events that are monitored reflect the relationship between a particular message and
+ * the queue it has been delivered to. A message can be considered in one of three states
+ * with respect to the queue:
+ *
+ * 1) "Available" - available for transfer to consumers (i.e. for browse or acquire),
+ *
+ * 2) "Acquired" - owned by a particular consumer, no longer available to other consumers
+ * (by either browse or acquire), but still considered on the queue.
+ *
+ * 3) "Dequeued" - removed from the queue and no longer available to any consumer.
+ *
+ * The queue events that are observable are:
+ *
+ * "Enqueued" - the message is "Available" - on the queue for transfer to any consumer
+ * (e.g. browse or acquire)
+ *
+ * "Acquired" - - a consumer has claimed exclusive access to it. It is no longer available
+ * for other consumers to browse or acquire, but it is not yet considered dequeued as it
+ * may be requeued by the consumer.
+ *
+ * "Requeued" - a previously-acquired message is released by its owner: it is put back on
+ * the queue at its original position and returns to the "Available" state.
+ *
+ * "Dequeued" - a message is no longer queued. At this point, the queue no longer tracks
+ * the message, and the broker considers the consumer's transaction complete.
+ */
+class QueueObserver
+{
+ public:
+ virtual ~QueueObserver() {}
+
+ // note: the Queue will hold the messageLock while calling these methods!
+ virtual void enqueued(const Message&) = 0;
+ virtual void dequeued(const Message&) = 0;
+ virtual void acquired(const Message&) = 0;
+ virtual void requeued(const Message&) = 0;
+ virtual void consumerAdded( const Consumer& ) {};
+ virtual void consumerRemoved( const Consumer& ) {};
+ virtual void destroy() {};
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_QUEUEOBSERVER_H*/
diff --git a/qpid/cpp/src/qpid/broker/QueueObservers.h b/qpid/cpp/src/qpid/broker/QueueObservers.h
new file mode 100644
index 0000000000..1bf5f1f696
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/QueueObservers.h
@@ -0,0 +1,77 @@
+#ifndef QPID_BROKER_QUEUEOBSERVERS_H
+#define QPID_BROKER_QUEUEOBSERVERS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "Observers.h"
+#include "QueueObserver.h"
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace broker {
+
+/**
+ * A collection of queue observers.
+ */
+class QueueObservers : public Observers<QueueObserver> {
+ public:
+ typedef Observers<QueueObserver> Base;
+
+ // The only public functions are inherited from Observers<QueueObserver>
+ using Base::each; // Avoid function hiding.
+
+ friend class Queue;
+
+ typedef const sys::Mutex::ScopedLock& Lock;
+
+ QueueObservers(const std::string& q, sys::Mutex& lock) : Base(lock), qname(q) {}
+
+ template <class T> void each(void (QueueObserver::*f)(const T&), const T& arg, const char* fname, Lock l) {
+ Base::each(boost::bind(&QueueObservers::wrap<T>, this, f, boost::cref(arg), fname, _1), l);
+ }
+
+ template <class T> void wrap(void (QueueObserver::*f)(const T&), const T& arg, const char* fname, const ObserverPtr& o) {
+ try { (o.get()->*f)(arg); }
+ catch (const std::exception& e) {
+ QPID_LOG(warning, "Exception on " << fname << " for queue " << qname << ": " << e.what());
+ }
+ }
+
+
+ // Calls are locked externally by caller.
+ void enqueued(const Message& m, Lock l) { each(&QueueObserver::enqueued, m, "enqueue", l); }
+ void dequeued(const Message& m, Lock l) { each(&QueueObserver::dequeued, m, "dequeue", l); }
+ void acquired(const Message& m, Lock l) { each(&QueueObserver::acquired, m, "acquire", l); }
+ void requeued(const Message& m, Lock l) { each(&QueueObserver::requeued, m, "requeue", l); }
+ void consumerAdded(const Consumer& c, Lock l) { each(&QueueObserver::consumerAdded, c, "consumer added", l); }
+ void consumerRemoved(const Consumer& c, Lock l) { each(&QueueObserver::consumerRemoved, c, "consumer removed", l); }
+ void destroy(Lock l) {
+ Base::each(boost::bind(&QueueObserver::destroy, _1), l);
+ observers.clear();
+ }
+
+ std::string qname;
+};
+
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_QUEUEOBSERVERS_H*/
diff --git a/qpid/cpp/src/qpid/broker/QueueRegistry.cpp b/qpid/cpp/src/qpid/broker/QueueRegistry.cpp
new file mode 100644
index 0000000000..1283a42e6d
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/QueueRegistry.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/QueueRegistry.h"
+#include "qpid/broker/Exchange.h"
+#include "qpid/log/Statement.h"
+#include "qpid/management/ManagementAgent.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qmf/org/apache/qpid/broker/EventQueueDeclare.h"
+#include "qmf/org/apache/qpid/broker/EventQueueDelete.h"
+#include <sstream>
+#include <assert.h>
+
+namespace _qmf = qmf::org::apache::qpid::broker;
+using namespace qpid::broker;
+using namespace qpid::sys;
+using std::string;
+
+QueueRegistry::QueueRegistry(Broker* b)
+{
+ setBroker(b);
+}
+
+QueueRegistry::~QueueRegistry(){}
+
+std::pair<Queue::shared_ptr, bool>
+QueueRegistry::declare(const string& name, const QueueSettings& settings,
+ boost::shared_ptr<Exchange> alternate,
+ bool recovering/*true if this declare is a
+ result of recovering queue
+ definition from persistent
+ record*/,
+ const OwnershipToken* owner,
+ std::string connectionId,
+ std::string userId)
+{
+ std::pair<Queue::shared_ptr, bool> result;
+ {
+ RWlock::ScopedWlock locker(lock);
+ QueueMap::iterator i = queues.find(name);
+ if (i == queues.end()) {
+ Queue::shared_ptr queue = create(name, settings);
+ // Allow BrokerObserver to modify settings before storing the message.
+ if (getBroker()) getBroker()->getBrokerObservers().queueCreate(queue);
+ //Move this to factory also?
+ if (alternate)
+ queue->setAlternateExchange(alternate);//need to do this *before* create
+ queue->setOwningUser(userId);
+
+ if (!recovering) {
+ //create persistent record if required
+ queue->create();
+ }
+ queues[name] = queue;
+ result = std::pair<Queue::shared_ptr, bool>(queue, true);
+ } else {
+ result = std::pair<Queue::shared_ptr, bool>(i->second, false);
+ }
+ if (getBroker() && getBroker()->getManagementAgent()) {
+ getBroker()->getManagementAgent()->raiseEvent(
+ _qmf::EventQueueDeclare(
+ connectionId, userId, name,
+ settings.durable, owner, settings.autodelete,
+ alternate ? alternate->getName() : string(),
+ result.first->getSettings().asMap(),
+ result.second ? "created" : "existing"));
+ }
+ }
+ return result;
+}
+
+void QueueRegistry::destroy(
+ const string& name, const string& connectionId, const string& userId)
+{
+ Queue::shared_ptr q;
+ {
+ qpid::sys::RWlock::ScopedWlock locker(lock);
+ QueueMap::iterator i = queues.find(name);
+ if (i != queues.end()) {
+ q = i->second;
+ queues.erase(i);
+ if (getBroker()) {
+ // NOTE: queueDestroy and raiseEvent must be called with the
+ // lock held in order to ensure events are generated
+ // in the correct order.
+ getBroker()->getBrokerObservers().queueDestroy(q);
+ if (getBroker()->getManagementAgent())
+ getBroker()->getManagementAgent()->raiseEvent(
+ _qmf::EventQueueDelete(connectionId, userId, name));
+ }
+ }
+ }
+}
+
+Queue::shared_ptr QueueRegistry::find(const string& name){
+ RWlock::ScopedRlock locker(lock);
+ QueueMap::iterator i = queues.find(name);
+ if (i == queues.end()) {
+ return Queue::shared_ptr();
+ } else {
+ return i->second;
+ }
+}
+
+Queue::shared_ptr QueueRegistry::get(const string& name) {
+ Queue::shared_ptr q = find(name);
+ if (!q) {
+ throw framing::NotFoundException(QPID_MSG("Queue not found: "<<name));
+ }
+ return q;
+}
+
+void QueueRegistry::setStore (MessageStore* _store)
+{
+ QueueFactory::setStore(_store);
+}
+
+MessageStore* QueueRegistry::getStore() const
+{
+ return QueueFactory::getStore();
+}
+
+void QueueRegistry::setParent(qpid::management::Manageable* _parent)
+{
+ QueueFactory::setParent(_parent);
+}
diff --git a/qpid/cpp/src/qpid/broker/QueueRegistry.h b/qpid/cpp/src/qpid/broker/QueueRegistry.h
new file mode 100644
index 0000000000..af4e8e50fb
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/QueueRegistry.h
@@ -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.
+ *
+ */
+#ifndef _QueueRegistry_
+#define _QueueRegistry_
+
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/broker/QueueFactory.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/framing/FieldTable.h"
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+#include <algorithm>
+#include <map>
+
+namespace qpid {
+namespace broker {
+
+class Queue;
+class Exchange;
+class OwnershipToken;
+
+/**
+ * A registry of queues indexed by queue name.
+ *
+ * Queues are reference counted using shared_ptr to ensure that they
+ * are deleted when and only when they are no longer in use.
+ *
+ */
+class QueueRegistry : private QueueFactory {
+ public:
+ QPID_BROKER_EXTERN QueueRegistry(Broker* b = 0);
+ QPID_BROKER_EXTERN ~QueueRegistry();
+
+ /**
+ * Declare a queue.
+ *
+ * @return The queue and a boolean flag which is true if the queue
+ * was created by this declare call false if it already existed.
+ */
+ QPID_BROKER_EXTERN std::pair<boost::shared_ptr<Queue>, bool> declare(
+ const std::string& name,
+ const QueueSettings& settings,
+ boost::shared_ptr<Exchange> alternateExchange = boost::shared_ptr<Exchange>(),
+ bool recovering = false,
+ const OwnershipToken* owner = 0,
+ std::string connectionId=std::string(), std::string userId=std::string());
+
+ /**
+ * Destroy the named queue.
+ *
+ * Note: if the queue is in use it is not actually destroyed until
+ * all shared_ptrs to it are destroyed. During that time it is
+ * possible that a new queue with the same name may be
+ * created. This should not create any problems as the new and
+ * old queues exist independently. The registry has
+ * forgotten the old queue so there can be no confusion for
+ * subsequent calls to find or declare with the same name.
+ *
+ */
+ QPID_BROKER_EXTERN void destroy(
+ const std::string& name,
+ const std::string& connectionId=std::string(),
+ const std::string& userId=std::string());
+
+ template <class Test> bool destroyIf(const std::string& name, Test test)
+ {
+ if (test()) {
+ destroy(name);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Find the named queue. Return 0 if not found.
+ */
+ QPID_BROKER_EXTERN boost::shared_ptr<Queue> find(const std::string& name);
+
+ /**
+ * Get the named queue. Throw exception if not found.
+ */
+ QPID_BROKER_EXTERN boost::shared_ptr<Queue> get(const std::string& name);
+
+ /**
+ * Set the store to use. May only be called once.
+ */
+ void setStore (MessageStore*);
+
+ /**
+ * Return the message store used.
+ */
+ MessageStore* getStore() const;
+
+ /**
+ * Register the manageable parent for declared queues
+ */
+ void setParent (management::Manageable*);
+
+ /** Call f for each queue in the registry. */
+ template <class F> void eachQueue(F f) const {
+ qpid::sys::RWlock::ScopedRlock l(lock);
+ for (QueueMap::const_iterator i = queues.begin(); i != queues.end(); ++i)
+ f(i->second);
+ }
+
+private:
+ typedef std::map<std::string, boost::shared_ptr<Queue> > QueueMap;
+ QueueMap queues;
+ mutable qpid::sys::RWlock lock;
+};
+
+
+}} // namespace qpid::broker
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/QueueSettings.cpp b/qpid/cpp/src/qpid/broker/QueueSettings.cpp
new file mode 100644
index 0000000000..8de8539579
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/QueueSettings.cpp
@@ -0,0 +1,328 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "QueueSettings.h"
+#include "QueueFlowLimit.h"
+#include "MessageGroupManager.h"
+#include "qpid/types/Variant.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/log/Statement.h"
+#include "qpid/amqp_0_10/Codecs.h"
+
+
+namespace qpid {
+namespace broker {
+
+namespace {
+const std::string MAX_COUNT("qpid.max_count");
+const std::string MAX_SIZE("qpid.max_size");
+const std::string MAX_FILE_COUNT("qpid.file_count");
+const std::string MAX_FILE_SIZE("qpid.file_size");
+const std::string POLICY_TYPE("qpid.policy_type");
+const std::string POLICY_TYPE_REJECT("reject");
+const std::string POLICY_TYPE_RING("ring");
+const std::string POLICY_TYPE_SELF_DESTRUCT("self-destruct");
+const std::string NO_LOCAL("no-local");
+const std::string BROWSE_ONLY("qpid.browse-only");
+const std::string TRACE_ID("qpid.trace.id");
+const std::string TRACE_EXCLUDES("qpid.trace.exclude");
+const std::string LVQ_KEY("qpid.last_value_queue_key");
+const std::string AUTO_DELETE_TIMEOUT("qpid.auto_delete_timeout");
+const std::string ALERT_REPEAT_GAP("qpid.alert_repeat_gap");
+const std::string ALERT_COUNT("qpid.alert_count");
+const std::string ALERT_SIZE("qpid.alert_size");
+const std::string ALERT_COUNT_UP("qpid.alert_count_up");
+const std::string ALERT_SIZE_UP("qpid.alert_size_up");
+const std::string ALERT_COUNT_DOWN("qpid.alert_count_down");
+const std::string ALERT_SIZE_DOWN("qpid.alert_size_down");
+const std::string PRIORITIES("qpid.priorities");
+const std::string FAIRSHARE("qpid.fairshare");
+const std::string FAIRSHARE_ALIAS("x-qpid-fairshare");
+const std::string PAGING("qpid.paging");
+const std::string MAX_PAGES("qpid.max_pages_loaded");
+const std::string PAGE_FACTOR("qpid.page_factor");
+const std::string FILTER("qpid.filter");
+const std::string LIFETIME_POLICY("qpid.lifetime-policy");
+const std::string DELETE_IF_UNUSED_KEY("delete-if-unused");
+const std::string DELETE_IF_UNUSED_AND_EMPTY_KEY("delete-if-unused-and-empty");
+const std::string MANUAL("manual");
+
+const std::string LVQ_LEGACY("qpid.last_value_queue");
+const std::string LVQ_LEGACY_KEY("qpid.LVQ_key");
+const std::string LVQ_LEGACY_NOBROWSE("qpid.last_value_queue_no_browse");
+
+const std::string SEQUENCING("qpid.queue_msg_sequence");
+
+bool handleFairshareSetting(const std::string& basename, const std::string& key, const qpid::types::Variant& value, QueueSettings& settings)
+{
+ if (key.find(basename) == 0) {
+ qpid::types::Variant index(key.substr(basename.size()+1));
+ settings.fairshare[index] = value;
+ return true;
+ } else {
+ return false;
+ }
+}
+bool isFairshareSetting(const std::string& key, const qpid::types::Variant& value, QueueSettings& settings)
+{
+ return handleFairshareSetting(FAIRSHARE, key, value, settings) || handleFairshareSetting(FAIRSHARE_ALIAS, key, value, settings);
+}
+}
+
+const QueueSettings::Aliases QueueSettings::aliases;
+
+QueueSettings::QueueSettings(bool d, bool a) :
+ durable(d),
+ autodelete(a),
+ lifetime(DELETE_IF_UNUSED),
+ isTemporary(false),
+ priorities(0),
+ defaultFairshare(0),
+ shareGroups(false),
+ addTimestamp(false),
+ dropMessagesAtLimit(false),
+ selfDestructAtLimit(false),
+ paging(false),
+ maxPages(0),
+ pageFactor(0),
+ noLocal(false),
+ isBrowseOnly(false),
+ autoDeleteDelay(0),
+ alertRepeatInterval(60),
+ maxFileSize(0),
+ maxFileCount(0),
+ sequencing(false)
+{}
+
+bool QueueSettings::handle(const std::string& key, const qpid::types::Variant& value)
+{
+ if (key == MAX_COUNT) {
+ maxDepth.setCount(value);
+ return true;
+ } else if (key == MAX_SIZE) {
+ maxDepth.setSize(value);
+ return true;
+ } else if (key == POLICY_TYPE) {
+ if (value.getString() == POLICY_TYPE_RING) {
+ dropMessagesAtLimit = true;
+ return true;
+ } else if (value.getString() == POLICY_TYPE_SELF_DESTRUCT) {
+ selfDestructAtLimit = true;
+ return true;
+ } else if (value.getString() == POLICY_TYPE_REJECT) {
+ //do nothing, thats the default
+ return true;
+ } else {
+ QPID_LOG(warning, "Unrecognised policy option: " << value);
+ return false;
+ }
+ } else if (key == NO_LOCAL) {
+ noLocal = value;
+ return true;
+ } else if (key == BROWSE_ONLY) {
+ isBrowseOnly = value;
+ return true;
+ } else if (key == TRACE_ID) {
+ traceId = value.asString();
+ return true;
+ } else if (key == TRACE_EXCLUDES) {
+ traceExcludes = value.asString();
+ return true;
+ } else if (key == PRIORITIES) {
+ priorities = value;
+ return true;
+ } else if (key == FAIRSHARE) {
+ defaultFairshare = value;
+ return true;
+ } else if (isFairshareSetting(key, value, *this)) {
+ return true;
+ } else if (key == MessageGroupManager::qpidMessageGroupKey) {
+ groupKey = value.asString();
+ return true;
+ } else if (key == MessageGroupManager::qpidSharedGroup) {
+ shareGroups = value;
+ return true;
+ } else if (key == MessageGroupManager::qpidMessageGroupTimestamp) {
+ addTimestamp = value;
+ return true;
+ } else if (key == LVQ_KEY) {
+ lvqKey = value.asString();
+ return true;
+ } else if (key == LVQ_LEGACY) {
+ if (lvqKey.empty()) lvqKey = LVQ_LEGACY_KEY;
+ return true;
+ } else if (key == LVQ_LEGACY_NOBROWSE) {
+ QPID_LOG(warning, "Ignoring 'no-browse' directive for LVQ; it is no longer necessary");
+ if (lvqKey.empty()) lvqKey = LVQ_LEGACY_KEY;
+ return true;
+ } else if (key == AUTO_DELETE_TIMEOUT) {
+ autoDeleteDelay = value;
+ if (autoDeleteDelay) autodelete = true;
+ return true;
+ } else if (key == QueueFlowLimit::flowStopCountKey) {
+ flowStop.setCount(value);
+ return true;
+ } else if (key == QueueFlowLimit::flowResumeCountKey) {
+ flowResume.setCount(value);
+ return true;
+ } else if (key == QueueFlowLimit::flowStopSizeKey) {
+ flowStop.setSize(value);
+ return true;
+ } else if (key == QueueFlowLimit::flowResumeSizeKey) {
+ flowResume.setSize(value);
+ return true;
+ } else if (key == ALERT_REPEAT_GAP) {
+ alertRepeatInterval = value;
+ return true;
+ } else if ((key == ALERT_COUNT) || (key == ALERT_COUNT_UP)) {
+ alertThreshold.setCount(value);
+ return true;
+ } else if ((key == ALERT_SIZE) || (key == ALERT_SIZE_UP)) {
+ alertThreshold.setSize(value);
+ return true;
+ } else if (key == ALERT_COUNT_DOWN) {
+ alertThresholdDown.setCount(value);
+ return true;
+ } else if (key == ALERT_SIZE_DOWN) {
+ alertThresholdDown.setSize(value);
+ return true;
+ } else if (key == MAX_FILE_COUNT && value.asUint64() > 0) {
+ maxFileCount = value.asUint64();
+ return false; // 'handle' here and also pass to store
+ } else if (key == MAX_FILE_SIZE && value.asUint64() > 0) {
+ maxFileSize = value.asUint64();
+ return false; // 'handle' here and also pass to store
+ } else if (key == PAGING) {
+ paging = value;
+ return true;
+ } else if (key == MAX_PAGES) {
+ maxPages = value;
+ return true;
+ } else if (key == PAGE_FACTOR) {
+ pageFactor = value;
+ return true;
+ } else if (key == SEQUENCING) {
+ sequenceKey = value.getString();
+ sequencing = !sequenceKey.empty();
+ return true;
+ } else if (key == FILTER) {
+ filter = value.asString();
+ return true;
+ } else if (key == LIFETIME_POLICY) {
+ if (value.asString() == DELETE_IF_UNUSED_KEY) {
+ lifetime = DELETE_IF_UNUSED;
+ autodelete = true;
+ } else if (value.asString() == DELETE_IF_UNUSED_AND_EMPTY_KEY) {
+ lifetime = DELETE_IF_UNUSED_AND_EMPTY;
+ autodelete = true;
+ } else if (value.asString() == MANUAL) {
+ autodelete = false;
+ } else {
+ QPID_LOG(warning, "Invalid value for " << LIFETIME_POLICY << ": " << value);
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void QueueSettings::validate() const
+{
+ if (lvqKey.size() && priorities > 0)
+ throw qpid::framing::InvalidArgumentException(QPID_MSG("Cannot specify " << LVQ_KEY << " and " << PRIORITIES << " for the same queue"));
+ if ((fairshare.size() || defaultFairshare) && priorities == 0)
+ throw qpid::framing::InvalidArgumentException(QPID_MSG("Cannot specify fairshare settings when queue is not enabled for priorities"));
+ if (fairshare.size() > priorities)
+ throw qpid::framing::InvalidArgumentException(QPID_MSG("Cannot have fairshare set for priority levels greater than " << priorities));
+ if (groupKey.size() && lvqKey.size())
+ throw qpid::framing::InvalidArgumentException(QPID_MSG("Cannot specify " << LVQ_KEY << " and " << MessageGroupManager::qpidMessageGroupKey << " for the same queue"));
+ if (groupKey.size() && priorities)
+ throw qpid::framing::InvalidArgumentException(QPID_MSG("Cannot specify " << PRIORITIES << " and " << MessageGroupManager::qpidMessageGroupKey << " for the same queue"));
+ if (shareGroups && groupKey.empty()) {
+ throw qpid::framing::InvalidArgumentException(QPID_MSG("Can only specify " << MessageGroupManager::qpidSharedGroup
+ << " if " << MessageGroupManager::qpidMessageGroupKey << " is set"));
+ }
+ if (addTimestamp && groupKey.empty()) {
+ throw qpid::framing::InvalidArgumentException(QPID_MSG("Can only specify " << MessageGroupManager::qpidMessageGroupTimestamp
+ << " if " << MessageGroupManager::qpidMessageGroupKey << " is set"));
+ }
+
+ // @todo: remove once "sticky" consumers are supported - see QPID-3347
+ if (!shareGroups && groupKey.size()) {
+ throw qpid::framing::InvalidArgumentException(QPID_MSG("Only shared groups are supported at present; " << MessageGroupManager::qpidSharedGroup
+ << " is required if " << MessageGroupManager::qpidMessageGroupKey << " is set"));
+ }
+
+ if (paging) {
+ if(lvqKey.size()) {
+ throw qpid::framing::InvalidArgumentException(QPID_MSG("Cannot specify " << LVQ_KEY << " and " << PAGING << " for the same queue"));
+ }
+ if(priorities) {
+ throw qpid::framing::InvalidArgumentException(QPID_MSG("Cannot specify " << PRIORITIES << " and " << PAGING << " for the same queue"));
+ }
+ if(groupKey.size()) {
+ throw qpid::framing::InvalidArgumentException(QPID_MSG("Cannot specify " << MessageGroupManager::qpidMessageGroupKey << " and " << PAGING << " for the same queue"));
+ }
+ } else {
+ if (maxPages) {
+ throw qpid::framing::InvalidArgumentException(QPID_MSG("Can only specify " << MAX_PAGES << " if " << PAGING << " is set"));
+ }
+ if (pageFactor) {
+ throw qpid::framing::InvalidArgumentException(QPID_MSG("Can only specify " << PAGE_FACTOR << " if " << PAGING << " is set"));
+ }
+ }
+}
+
+void QueueSettings::populate(const std::map<std::string, qpid::types::Variant>& inputs, std::map<std::string, qpid::types::Variant>& unused)
+{
+ original = inputs;
+ for (qpid::types::Variant::Map::const_iterator i = inputs.begin(); i != inputs.end(); ++i) {
+ Aliases::const_iterator a = aliases.find(i->first);
+ if (!handle((a != aliases.end() ? a->second : i->first), i->second)) unused.insert(*i);
+ }
+}
+void QueueSettings::populate(const qpid::framing::FieldTable& inputs, qpid::framing::FieldTable& unused)
+{
+ qpid::types::Variant::Map o;
+ qpid::amqp_0_10::translate(inputs, original);
+ populate(original, o);
+ qpid::amqp_0_10::translate(o, unused);
+}
+std::map<std::string, qpid::types::Variant> QueueSettings::asMap() const
+{
+ return original;
+}
+
+QueueSettings::Aliases::Aliases()
+{
+ insert(value_type("x-qpid-priorities", "qpid.priorities"));
+ insert(value_type("x-qpid-fairshare", "qpid.fairshare"));
+ insert(value_type("x-qpid-minimum-alert-repeat-gap", "qpid.alert_repeat_gap"));
+ insert(value_type("x-qpid-maximum-message-count", "qpid.alert_count"));
+ insert(value_type("x-qpid-maximum-message-size", "qpid.alert_size"));
+}
+
+std::string QueueSettings::getLimitPolicy() const
+{
+ if (dropMessagesAtLimit) return POLICY_TYPE_RING;
+ else if (selfDestructAtLimit) return POLICY_TYPE_SELF_DESTRUCT;
+ else return POLICY_TYPE_REJECT;
+}
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/QueueSettings.h b/qpid/cpp/src/qpid/broker/QueueSettings.h
new file mode 100644
index 0000000000..9fda51e17a
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/QueueSettings.h
@@ -0,0 +1,124 @@
+#ifndef QPID_BROKER_QUEUESETTINGS_H
+#define QPID_BROKER_QUEUESETTINGS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/broker/QueueDepth.h"
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/framing/FieldTable.h"
+#include <string>
+#include <map>
+
+namespace qpid {
+namespace types {
+class Variant;
+}
+namespace broker {
+
+/**
+ * Defines the various queue configuration settings that can be specified
+ */
+struct QueueSettings
+{
+ QPID_BROKER_EXTERN QueueSettings(bool durable=false, bool autodelete=false);
+ /**
+ * The lifetime policy dictates when an autodelete queue is
+ * eligible for delete.
+ */
+ enum LifetimePolicy
+ {
+ DELETE_IF_UNUSED = 0,
+ DELETE_IF_EMPTY,
+ DELETE_IF_UNUSED_AND_EMPTY,
+ DELETE_ON_CLOSE
+ };
+
+ bool durable;
+ bool autodelete;
+ LifetimePolicy lifetime;
+ bool isTemporary;
+
+ //basic queue types:
+ std::string lvqKey;
+ uint32_t priorities;
+ uint32_t defaultFairshare;
+ std::map<uint32_t,uint32_t> fairshare;
+
+ //message groups:
+ std::string groupKey;
+ bool shareGroups;
+ bool addTimestamp;//not actually used; always on at present?
+
+ QueueDepth maxDepth;
+ bool dropMessagesAtLimit;//aka ring queue policy
+ bool selfDestructAtLimit;
+
+ //PagedQueue:
+ bool paging;
+ uint maxPages;
+ uint pageFactor;
+
+ bool noLocal;
+ bool isBrowseOnly;
+ std::string traceId;
+ std::string traceExcludes;
+ uint64_t autoDeleteDelay;//queueTtl?
+
+ //flow control:
+ QueueDepth flowStop;
+ QueueDepth flowResume;
+
+ //threshold events:
+ QueueDepth alertThreshold;
+ QueueDepth alertThresholdDown;
+ int64_t alertRepeatInterval;
+
+ //file limits checked by Acl and shared with storeSettings
+ uint64_t maxFileSize;
+ uint64_t maxFileCount;
+
+ std::string sequenceKey;
+ // store bool to avoid testing string value
+ bool sequencing;
+
+ std::string filter;
+
+ //yuck, yuck
+ qpid::framing::FieldTable storeSettings;
+ std::map<std::string, qpid::types::Variant> original;
+
+ bool handle(const std::string& key, const qpid::types::Variant& value);
+ void validate() const;
+ QPID_BROKER_EXTERN void populate(const std::map<std::string, qpid::types::Variant>& inputs, std::map<std::string, qpid::types::Variant>& unused);
+ QPID_BROKER_EXTERN void populate(const qpid::framing::FieldTable& inputs, qpid::framing::FieldTable& unused);
+ QPID_BROKER_EXTERN std::map<std::string, qpid::types::Variant> asMap() const;
+ std::string getLimitPolicy() const;
+
+ struct Aliases : std::map<std::string, std::string>
+ {
+ Aliases();
+ };
+ static const Aliases aliases;
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_QUEUESETTINGS_H*/
diff --git a/qpid/cpp/src/qpid/broker/QueuedMessage.cpp b/qpid/cpp/src/qpid/broker/QueuedMessage.cpp
new file mode 100644
index 0000000000..d40cc901ff
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/QueuedMessage.cpp
@@ -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.
+ *
+ */
+#include "QueuedMessage.h"
+#include "Queue.h"
+#include <iostream>
+
+namespace qpid {
+namespace broker {
+
+std::ostream& operator<<(std::ostream& o, const QueuedMessage& qm) {
+ o << (qm.queue ? qm.queue->getName() : std::string()) << "[" << qm.position <<"]";
+ return o;
+}
+
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/QueuedMessage.h b/qpid/cpp/src/qpid/broker/QueuedMessage.h
new file mode 100644
index 0000000000..c80fff900a
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/QueuedMessage.h
@@ -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.
+ *
+ */
+#ifndef _QueuedMessage_
+#define _QueuedMessage_
+
+#include "qpid/broker/Message.h"
+#include "qpid/framing/SequenceNumber.h"
+#include "qpid/broker/BrokerImportExport.h"
+
+namespace qpid {
+namespace broker {
+
+class Queue;
+
+struct QueuedMessage
+{
+ Message message;
+ framing::SequenceNumber position;
+ enum {AVAILABLE, ACQUIRED, DELETED, REMOVED} status;
+ Queue* queue;
+
+ QueuedMessage() : queue(0) {}
+ QueuedMessage(Queue* q, Message msg, framing::SequenceNumber sn) :
+ message(msg), position(sn), queue(q) {}
+ QueuedMessage(Queue* q) : queue(q) {}
+};
+
+inline bool operator<(const QueuedMessage& a, const QueuedMessage& b)
+{
+ return a.position < b.position;
+}
+
+QPID_BROKER_EXTERN std::ostream& operator<<(std::ostream&, const QueuedMessage&);
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/RecoverableConfig.h b/qpid/cpp/src/qpid/broker/RecoverableConfig.h
new file mode 100644
index 0000000000..838a8582dc
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/RecoverableConfig.h
@@ -0,0 +1,45 @@
+#ifndef _broker_RecoverableConfig_h
+#define _broker_RecoverableConfig_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace broker {
+
+/**
+ * The interface through which configurations are recovered.
+ */
+class RecoverableConfig
+{
+public:
+ typedef boost::shared_ptr<RecoverableConfig> shared_ptr;
+
+ virtual void setPersistenceId(uint64_t id) = 0;
+ virtual ~RecoverableConfig() {};
+};
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/RecoverableExchange.h b/qpid/cpp/src/qpid/broker/RecoverableExchange.h
new file mode 100644
index 0000000000..f8c08b2989
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/RecoverableExchange.h
@@ -0,0 +1,55 @@
+#ifndef _broker_RecoverableExchange_h
+#define _broker_RecoverableExchange_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <boost/shared_ptr.hpp>
+#include "qpid/framing/FieldTable.h"
+
+namespace qpid {
+namespace broker {
+
+/**
+ * The interface through which bindings are recovered.
+ */
+class RecoverableExchange
+{
+public:
+ typedef boost::shared_ptr<RecoverableExchange> shared_ptr;
+
+ virtual void setPersistenceId(uint64_t id) = 0;
+ /**
+ * Recover binding. Nb: queue must have been recovered earlier.
+ */
+ virtual void bind(const std::string& queue,
+ const std::string& routingKey,
+ qpid::framing::FieldTable& args) = 0;
+
+ virtual std::string getName() const = 0;
+
+ virtual ~RecoverableExchange() {};
+};
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/RecoverableMessage.h b/qpid/cpp/src/qpid/broker/RecoverableMessage.h
new file mode 100644
index 0000000000..3c82a69883
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/RecoverableMessage.h
@@ -0,0 +1,62 @@
+#ifndef _broker_RecoverableMessage_h
+#define _broker_RecoverableMessage_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <boost/intrusive_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+#include "qpid/framing/amqp_types.h"
+#include "qpid/framing/Buffer.h"
+
+namespace qpid {
+namespace broker {
+class Message;
+/**
+ * The interface through which messages are reloaded on recovery.
+ */
+class RecoverableMessage
+{
+public:
+ typedef boost::shared_ptr<RecoverableMessage> shared_ptr;
+ virtual void setPersistenceId(uint64_t id) = 0;
+ virtual void setRedelivered() = 0;
+ virtual void computeExpiration() = 0;
+ /**
+ * Used by store to determine whether to load content on recovery
+ * or let message load its own content as and when it requires it.
+ *
+ * @returns true if the content of the message should be loaded
+ */
+ virtual bool loadContent(uint64_t available) = 0;
+ /**
+ * Loads the content held in the supplied buffer (may do checking
+ * of length as necessary)
+ */
+ virtual void decodeContent(framing::Buffer& buffer) = 0;
+ virtual Message getMessage() = 0;
+ virtual ~RecoverableMessage() {};
+};
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/RecoverableMessageImpl.h b/qpid/cpp/src/qpid/broker/RecoverableMessageImpl.h
new file mode 100644
index 0000000000..c257c2057b
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/RecoverableMessageImpl.h
@@ -0,0 +1,50 @@
+#ifndef QPID_BROKER_RECOVERABLEMESSAGEIMPL_H
+#define QPID_BROKER_RECOVERABLEMESSAGEIMPL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "RecoverableMessage.h"
+#include "Message.h"
+
+namespace qpid {
+namespace broker {
+class DtxBuffer;
+class Queue;
+
+class RecoverableMessageImpl : public RecoverableMessage
+{
+ Message msg;
+public:
+ QPID_BROKER_EXTERN RecoverableMessageImpl(const Message& _msg);
+ ~RecoverableMessageImpl() {};
+ void setPersistenceId(uint64_t id);
+ void setRedelivered();
+ void computeExpiration();
+ bool loadContent(uint64_t available);
+ void decodeContent(framing::Buffer& buffer);
+ void recover(boost::shared_ptr<Queue> queue);
+ void enqueue(boost::intrusive_ptr<DtxBuffer> buffer, boost::shared_ptr<Queue> queue);
+ void dequeue(boost::intrusive_ptr<DtxBuffer> buffer, boost::shared_ptr<Queue> queue);
+ Message getMessage();
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_RECOVERABLEMESSAGEIMPL_H*/
diff --git a/qpid/cpp/src/qpid/broker/RecoverableQueue.h b/qpid/cpp/src/qpid/broker/RecoverableQueue.h
new file mode 100644
index 0000000000..ab300dbce9
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/RecoverableQueue.h
@@ -0,0 +1,63 @@
+#ifndef _broker_RecoverableQueue_h
+#define _broker_RecoverableQueue_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/RecoverableMessage.h"
+#include "qpid/types/Variant.h"
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace broker {
+
+class ExternalQueueStore;
+struct QueueSettings;
+
+/**
+ * The interface through which messages are added back to queues on
+ * recovery.
+ */
+class RecoverableQueue
+{
+public:
+ typedef boost::shared_ptr<RecoverableQueue> shared_ptr;
+
+ virtual void setPersistenceId(uint64_t id) = 0;
+ virtual uint64_t getPersistenceId() const = 0;
+ /**
+ * Used during recovery to add stored messages back to the queue
+ */
+ virtual void recover(RecoverableMessage::shared_ptr msg) = 0;
+ virtual ~RecoverableQueue() {};
+
+ virtual const std::string& getName() const = 0;
+ virtual void setExternalQueueStore(ExternalQueueStore* inst) = 0;
+ virtual ExternalQueueStore* getExternalQueueStore() const = 0;
+ virtual const QueueSettings& getSettings() const = 0;
+ virtual void addArgument(const std::string& key, const qpid::types::Variant& value) = 0;
+
+};
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/RecoverableTransaction.h b/qpid/cpp/src/qpid/broker/RecoverableTransaction.h
new file mode 100644
index 0000000000..1b7d94bd1a
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/RecoverableTransaction.h
@@ -0,0 +1,49 @@
+#ifndef _broker_RecoverableTransaction_h
+#define _broker_RecoverableTransaction_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <boost/shared_ptr.hpp>
+
+#include "qpid/broker/RecoverableMessage.h"
+#include "qpid/broker/RecoverableQueue.h"
+
+namespace qpid {
+namespace broker {
+
+/**
+ * The interface through which prepared 2pc transactions are
+ * recovered.
+ */
+class RecoverableTransaction
+{
+public:
+ typedef boost::shared_ptr<RecoverableTransaction> shared_ptr;
+ virtual void enqueue(RecoverableQueue::shared_ptr queue, RecoverableMessage::shared_ptr message) = 0;
+ virtual void dequeue(RecoverableQueue::shared_ptr queue, RecoverableMessage::shared_ptr message) = 0;
+ virtual ~RecoverableTransaction() {};
+};
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/RecoveredDequeue.cpp b/qpid/cpp/src/qpid/broker/RecoveredDequeue.cpp
new file mode 100644
index 0000000000..6e21a5bc21
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/RecoveredDequeue.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/RecoveredDequeue.h"
+
+using namespace qpid::broker;
+
+RecoveredDequeue::RecoveredDequeue(Queue::shared_ptr _queue, Message _msg) : queue(_queue), msg(_msg)
+{
+ queue->recoverPrepared(msg);
+}
+
+bool RecoveredDequeue::prepare(TransactionContext*) throw()
+{
+ //should never be called; transaction has already prepared if an enqueue is recovered
+ return false;
+}
+
+void RecoveredDequeue::commit() throw()
+{
+ queue->dequeueCommited(msg);
+}
+
+void RecoveredDequeue::rollback() throw()
+{
+ queue->dequeueAborted(msg);
+}
+
diff --git a/qpid/cpp/src/qpid/broker/RecoveredDequeue.h b/qpid/cpp/src/qpid/broker/RecoveredDequeue.h
new file mode 100644
index 0000000000..b85919975c
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/RecoveredDequeue.h
@@ -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.
+ *
+ */
+#ifndef _RecoveredDequeue_
+#define _RecoveredDequeue_
+
+#include "qpid/broker/Deliverable.h"
+#include "qpid/broker/Message.h"
+#include "qpid/broker/MessageStore.h"
+#include "qpid/broker/TxOp.h"
+
+#include <algorithm>
+#include <functional>
+#include <list>
+
+namespace qpid {
+ namespace broker {
+ class RecoveredDequeue : public TxOp{
+ boost::shared_ptr<Queue> queue;
+ Message msg;
+
+ public:
+ RecoveredDequeue(boost::shared_ptr<Queue> queue, Message msg);
+ virtual bool prepare(TransactionContext* ctxt) throw();
+ virtual void commit() throw();
+ virtual void rollback() throw();
+ // TODO aconway 2013-07-08: revisit
+ virtual void callObserver(const boost::shared_ptr<TransactionObserver>&) {}
+ virtual ~RecoveredDequeue(){}
+
+ boost::shared_ptr<Queue> getQueue() const { return queue; }
+ Message getMessage() const { return msg; }
+ };
+ }
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/RecoveredEnqueue.cpp b/qpid/cpp/src/qpid/broker/RecoveredEnqueue.cpp
new file mode 100644
index 0000000000..296d5194c0
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/RecoveredEnqueue.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/RecoveredEnqueue.h"
+
+using namespace qpid::broker;
+
+RecoveredEnqueue::RecoveredEnqueue(Queue::shared_ptr _queue, Message _msg) : queue(_queue), msg(_msg)
+{
+ queue->recoverPrepared(msg);
+}
+
+bool RecoveredEnqueue::prepare(TransactionContext*) throw(){
+ //should never be called; transaction has already prepared if an enqueue is recovered
+ return false;
+}
+
+void RecoveredEnqueue::commit() throw(){
+ queue->enqueueCommited(msg);
+}
+
+void RecoveredEnqueue::rollback() throw(){
+ queue->enqueueAborted(msg);
+}
+
diff --git a/qpid/cpp/src/qpid/broker/RecoveredEnqueue.h b/qpid/cpp/src/qpid/broker/RecoveredEnqueue.h
new file mode 100644
index 0000000000..01c350af92
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/RecoveredEnqueue.h
@@ -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.
+ *
+ */
+#ifndef _RecoveredEnqueue_
+#define _RecoveredEnqueue_
+
+#include "qpid/broker/Deliverable.h"
+#include "qpid/broker/Message.h"
+#include "qpid/broker/MessageStore.h"
+#include "qpid/broker/TxOp.h"
+
+#include <algorithm>
+#include <functional>
+#include <list>
+
+namespace qpid {
+namespace broker {
+class RecoveredEnqueue : public TxOp{
+ boost::shared_ptr<Queue> queue;
+ Message msg;
+
+ public:
+ RecoveredEnqueue(boost::shared_ptr<Queue> queue, Message msg);
+ virtual bool prepare(TransactionContext* ctxt) throw();
+ virtual void commit() throw();
+ virtual void rollback() throw();
+ // TODO aconway 2013-07-08: revisit
+ virtual void callObserver(const boost::shared_ptr<TransactionObserver>&) {}
+ virtual ~RecoveredEnqueue(){}
+
+ boost::shared_ptr<Queue> getQueue() const { return queue; }
+ Message getMessage() const { return msg; }
+};
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/RecoveryManager.h b/qpid/cpp/src/qpid/broker/RecoveryManager.h
new file mode 100644
index 0000000000..2929e92250
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/RecoveryManager.h
@@ -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.
+ *
+ */
+#ifndef _RecoveryManager_
+#define _RecoveryManager_
+
+#include "qpid/broker/RecoverableExchange.h"
+#include "qpid/broker/RecoverableQueue.h"
+#include "qpid/broker/RecoverableMessage.h"
+#include "qpid/broker/RecoverableTransaction.h"
+#include "qpid/broker/RecoverableConfig.h"
+#include "qpid/broker/TransactionalStore.h"
+#include "qpid/framing/Buffer.h"
+
+namespace qpid {
+namespace broker {
+
+class RecoveryManager{
+ public:
+ virtual ~RecoveryManager(){}
+ virtual RecoverableExchange::shared_ptr recoverExchange(framing::Buffer& buffer) = 0;
+ virtual RecoverableQueue::shared_ptr recoverQueue(framing::Buffer& buffer) = 0;
+ virtual RecoverableMessage::shared_ptr recoverMessage(framing::Buffer& buffer) = 0;
+ virtual RecoverableTransaction::shared_ptr recoverTransaction(const std::string& xid,
+ std::auto_ptr<TPCTransactionContext> txn) = 0;
+ virtual RecoverableConfig::shared_ptr recoverConfig(framing::Buffer& buffer) = 0;
+
+ virtual void recoveryComplete() = 0;
+};
+
+class Recoverable {
+ public:
+ virtual ~Recoverable() {}
+
+ /**
+ * Request recovery of queue and message state.
+ */
+ virtual void recover(RecoveryManager& recoverer) = 0;
+};
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/RecoveryManagerImpl.cpp b/qpid/cpp/src/qpid/broker/RecoveryManagerImpl.cpp
new file mode 100644
index 0000000000..dd9bfc57f6
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/RecoveryManagerImpl.cpp
@@ -0,0 +1,288 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/RecoveryManagerImpl.h"
+
+#include "qpid/broker/Message.h"
+#include "qpid/broker/PersistableMessage.h"
+#include "qpid/broker/PersistableObject.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/Link.h"
+#include "qpid/broker/Bridge.h"
+#include "qpid/broker/Protocol.h"
+#include "qpid/broker/RecoverableMessageImpl.h"
+#include "qpid/broker/RecoveredEnqueue.h"
+#include "qpid/broker/RecoveredDequeue.h"
+#include "qpid/broker/amqp_0_10/MessageTransfer.h"
+#include "qpid/framing/reply_exceptions.h"
+
+using boost::dynamic_pointer_cast;
+using boost::intrusive_ptr;
+using std::string;
+
+namespace qpid {
+namespace broker {
+
+RecoveryManagerImpl::RecoveryManagerImpl(QueueRegistry& _queues, ExchangeRegistry& _exchanges, LinkRegistry& _links,
+ DtxManager& _dtxMgr, ProtocolRegistry& p, RecoveredObjects& o)
+ : queues(_queues), exchanges(_exchanges), links(_links), dtxMgr(_dtxMgr), protocols(p), objects(o) {}
+
+RecoveryManagerImpl::~RecoveryManagerImpl() {}
+
+class RecoverableQueueImpl : public RecoverableQueue
+{
+ Queue::shared_ptr queue;
+public:
+ RecoverableQueueImpl(const boost::shared_ptr<Queue>& _queue) : queue(_queue) {}
+ ~RecoverableQueueImpl() {};
+ void setPersistenceId(uint64_t id);
+ uint64_t getPersistenceId() const;
+ const std::string& getName() const;
+ void setExternalQueueStore(ExternalQueueStore* inst);
+ ExternalQueueStore* getExternalQueueStore() const;
+ const QueueSettings& getSettings() const;
+ void addArgument(const std::string& key, const types::Variant& value);
+ void recover(RecoverableMessage::shared_ptr msg);
+ void enqueue(boost::intrusive_ptr<DtxBuffer> buffer, RecoverableMessage::shared_ptr msg);
+ void dequeue(boost::intrusive_ptr<DtxBuffer> buffer, RecoverableMessage::shared_ptr msg);
+
+};
+
+class RecoverableExchangeImpl : public RecoverableExchange
+{
+ Exchange::shared_ptr exchange;
+ QueueRegistry& queues;
+public:
+ RecoverableExchangeImpl(Exchange::shared_ptr _exchange, QueueRegistry& _queues) : exchange(_exchange), queues(_queues) {}
+ void setPersistenceId(uint64_t id);
+ void bind(const std::string& queue, const std::string& routingKey, qpid::framing::FieldTable& args);
+ string getName() const { return exchange->getName(); }
+};
+
+class RecoverableConfigImpl : public RecoverableConfig
+{
+ Link::shared_ptr link;
+ Bridge::shared_ptr bridge;
+public:
+ RecoverableConfigImpl(Link::shared_ptr _link) : link(_link) {}
+ RecoverableConfigImpl(Bridge::shared_ptr _bridge) : bridge(_bridge) {}
+ void setPersistenceId(uint64_t id);
+};
+
+class RecoverableTransactionImpl : public RecoverableTransaction
+{
+ boost::intrusive_ptr<DtxBuffer> buffer;
+public:
+ RecoverableTransactionImpl(boost::intrusive_ptr<DtxBuffer> _buffer) : buffer(_buffer) {}
+ void enqueue(RecoverableQueue::shared_ptr queue, RecoverableMessage::shared_ptr message);
+ void dequeue(RecoverableQueue::shared_ptr queue, RecoverableMessage::shared_ptr message);
+};
+
+RecoverableExchange::shared_ptr RecoveryManagerImpl::recoverExchange(framing::Buffer& buffer)
+{
+ Exchange::shared_ptr e = Exchange::decode(exchanges, buffer);
+ if (e) {
+ return RecoverableExchange::shared_ptr(new RecoverableExchangeImpl(e, queues));
+ } else {
+ return RecoverableExchange::shared_ptr();
+ }
+}
+
+RecoverableQueue::shared_ptr RecoveryManagerImpl::recoverQueue(framing::Buffer& buffer)
+{
+ Queue::shared_ptr queue = Queue::restore(queues, buffer);
+ try {
+ Exchange::shared_ptr exchange = exchanges.getDefault();
+ if (exchange) {
+ exchange->bind(queue, queue->getName(), 0);
+ queue->bound(exchange->getName(), queue->getName(), framing::FieldTable());
+ }
+ } catch (const framing::NotFoundException& /*e*/) {
+ //assume no default exchange has been declared
+ }
+ return RecoverableQueue::shared_ptr(new RecoverableQueueImpl(queue));
+}
+
+RecoverableMessage::shared_ptr RecoveryManagerImpl::recoverMessage(framing::Buffer& buffer)
+{
+ RecoverableMessage::shared_ptr m = protocols.recover(buffer);
+ return m;
+}
+
+RecoverableTransaction::shared_ptr RecoveryManagerImpl::recoverTransaction(const std::string& xid,
+ std::auto_ptr<TPCTransactionContext> txn)
+{
+ boost::intrusive_ptr<DtxBuffer> buffer(new DtxBuffer());
+ dtxMgr.recover(xid, txn, buffer);
+ return RecoverableTransaction::shared_ptr(new RecoverableTransactionImpl(buffer));
+}
+
+RecoverableConfig::shared_ptr RecoveryManagerImpl::recoverConfig(framing::Buffer& buffer)
+{
+ string kind;
+ uint32_t p = buffer.getPosition();
+ buffer.getShortString (kind);
+ buffer.setPosition(p);
+
+ if (Link::isEncodedLink(kind))
+ return RecoverableConfig::shared_ptr(new RecoverableConfigImpl(Link::decode (links, buffer)));
+ else if (Bridge::isEncodedBridge(kind))
+ return RecoverableConfig::shared_ptr(new RecoverableConfigImpl(Bridge::decode (links, buffer)));
+
+ return objects.recover(buffer);
+}
+
+void RecoveryManagerImpl::recoveryComplete()
+{
+ //notify all queues and exchanges
+ queues.eachQueue(boost::bind(&Queue::recoveryComplete, _1, boost::ref(exchanges)));
+ exchanges.eachExchange(boost::bind(&Exchange::recoveryComplete, _1, boost::ref(exchanges)));
+}
+
+RecoverableMessageImpl:: RecoverableMessageImpl(const Message& _msg) : msg(_msg) {}
+
+bool RecoverableMessageImpl::loadContent(uint64_t /*available*/)
+{
+ return true;
+}
+
+void RecoverableMessageImpl::decodeContent(framing::Buffer& buffer)
+{
+ msg.getPersistentContext()->decodeContent(buffer);
+}
+
+void RecoverableMessageImpl::recover(Queue::shared_ptr queue)
+{
+ queue->recover(msg);
+}
+
+void RecoverableMessageImpl::setPersistenceId(uint64_t id)
+{
+ msg.getPersistentContext()->setPersistenceId(id);
+}
+
+void RecoverableMessageImpl::setRedelivered()
+{
+ msg.deliver();//increment delivery count (but at present that isn't recorded durably)
+}
+
+void RecoverableMessageImpl::computeExpiration()
+{
+ msg.getSharedState().computeExpiration();
+}
+
+Message RecoverableMessageImpl::getMessage()
+{
+ return msg;
+}
+
+void RecoverableQueueImpl::recover(RecoverableMessage::shared_ptr msg)
+{
+ dynamic_pointer_cast<RecoverableMessageImpl>(msg)->recover(queue);
+}
+
+void RecoverableQueueImpl::setPersistenceId(uint64_t id)
+{
+ queue->setPersistenceId(id);
+}
+
+uint64_t RecoverableQueueImpl::getPersistenceId() const
+{
+ return queue->getPersistenceId();
+}
+
+const std::string& RecoverableQueueImpl::getName() const
+{
+ return queue->getName();
+}
+
+void RecoverableQueueImpl::setExternalQueueStore(ExternalQueueStore* inst)
+{
+ queue->setExternalQueueStore(inst);
+}
+
+ExternalQueueStore* RecoverableQueueImpl::getExternalQueueStore() const
+{
+ return queue->getExternalQueueStore();
+}
+
+const QueueSettings& RecoverableQueueImpl::getSettings() const
+{
+ return queue->getSettings();
+}
+
+void RecoverableQueueImpl::addArgument(const std::string& key, const types::Variant& value)
+{
+ queue->addArgument(key, value);
+}
+
+void RecoverableExchangeImpl::setPersistenceId(uint64_t id)
+{
+ exchange->setPersistenceId(id);
+}
+
+void RecoverableConfigImpl::setPersistenceId(uint64_t id)
+{
+ if (link.get())
+ link->setPersistenceId(id);
+ else if (bridge.get())
+ bridge->setPersistenceId(id);
+}
+
+void RecoverableExchangeImpl::bind(const string& queueName,
+ const string& key,
+ framing::FieldTable& args)
+{
+ Queue::shared_ptr queue = queues.find(queueName);
+ exchange->bind(queue, key, &args);
+ queue->bound(exchange->getName(), key, args);
+}
+
+void RecoverableMessageImpl::dequeue(boost::intrusive_ptr<DtxBuffer> buffer, Queue::shared_ptr queue)
+{
+ buffer->enlist(TxOp::shared_ptr(new RecoveredDequeue(queue, msg)));
+}
+
+void RecoverableMessageImpl::enqueue(boost::intrusive_ptr<DtxBuffer> buffer, Queue::shared_ptr queue)
+{
+ buffer->enlist(TxOp::shared_ptr(new RecoveredEnqueue(queue, msg)));
+}
+
+void RecoverableQueueImpl::dequeue(boost::intrusive_ptr<DtxBuffer> buffer, RecoverableMessage::shared_ptr message)
+{
+ dynamic_pointer_cast<RecoverableMessageImpl>(message)->dequeue(buffer, queue);
+}
+
+void RecoverableQueueImpl::enqueue(boost::intrusive_ptr<DtxBuffer> buffer, RecoverableMessage::shared_ptr message)
+{
+ dynamic_pointer_cast<RecoverableMessageImpl>(message)->enqueue(buffer, queue);
+}
+
+void RecoverableTransactionImpl::dequeue(RecoverableQueue::shared_ptr queue, RecoverableMessage::shared_ptr message)
+{
+ dynamic_pointer_cast<RecoverableQueueImpl>(queue)->dequeue(buffer, message);
+}
+
+void RecoverableTransactionImpl::enqueue(RecoverableQueue::shared_ptr queue, RecoverableMessage::shared_ptr message)
+{
+ dynamic_pointer_cast<RecoverableQueueImpl>(queue)->enqueue(buffer, message);
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/broker/RecoveryManagerImpl.h b/qpid/cpp/src/qpid/broker/RecoveryManagerImpl.h
new file mode 100644
index 0000000000..22ea6f8d04
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/RecoveryManagerImpl.h
@@ -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.
+ *
+ */
+#ifndef _RecoveryManagerImpl_
+#define _RecoveryManagerImpl_
+
+#include <list>
+#include <vector>
+#include "qpid/broker/DtxManager.h"
+#include "qpid/broker/ExchangeRegistry.h"
+#include "qpid/broker/QueueRegistry.h"
+#include "qpid/broker/LinkRegistry.h"
+#include "qpid/broker/RecoveryManager.h"
+
+namespace qpid {
+namespace broker {
+class Broker;
+class PersistableObject;
+class ProtocolRegistry;
+class RecoveredObjects;
+
+ class RecoveryManagerImpl : public RecoveryManager {
+ QueueRegistry& queues;
+ ExchangeRegistry& exchanges;
+ LinkRegistry& links;
+ DtxManager& dtxMgr;
+ ProtocolRegistry& protocols;
+ RecoveredObjects& objects;
+ public:
+ RecoveryManagerImpl(QueueRegistry& queues, ExchangeRegistry& exchanges, LinkRegistry& links,
+ DtxManager& dtxMgr, ProtocolRegistry&, RecoveredObjects&);
+ ~RecoveryManagerImpl();
+
+ RecoverableExchange::shared_ptr recoverExchange(framing::Buffer& buffer);
+ RecoverableQueue::shared_ptr recoverQueue(framing::Buffer& buffer);
+ RecoverableMessage::shared_ptr recoverMessage(framing::Buffer& buffer);
+ RecoverableTransaction::shared_ptr recoverTransaction(const std::string& xid,
+ std::auto_ptr<TPCTransactionContext> txn);
+ RecoverableConfig::shared_ptr recoverConfig(framing::Buffer& buffer);
+ void recoveryComplete();
+ };
+
+
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/RetryList.cpp b/qpid/cpp/src/qpid/broker/RetryList.cpp
new file mode 100644
index 0000000000..b0477dd0f7
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/RetryList.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/RetryList.h"
+
+namespace qpid {
+namespace broker {
+
+RetryList::RetryList() : urlIndex(0), addressIndex(0) {}
+
+void RetryList::reset(const std::vector<Url>& u)
+{
+ urls = u;
+ urlIndex = addressIndex = 0;//reset indices
+}
+
+bool RetryList::next(Address& address)
+{
+ while (urlIndex < urls.size()) {
+ if (addressIndex < urls[urlIndex].size()) {
+ address = urls[urlIndex][addressIndex++];
+ return true;
+ }
+ urlIndex++;
+ addressIndex = 0;
+ }
+ urlIndex = addressIndex = 0;//reset indices
+ return false;
+}
+
+std::ostream& operator<<(std::ostream& os, const RetryList& l)
+{
+ for (size_t i = 0; i < l.urls.size(); i++) {
+ os << l.urls[i] << " ";
+ }
+ return os;
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/RetryList.h b/qpid/cpp/src/qpid/broker/RetryList.h
new file mode 100644
index 0000000000..9c4b779bcb
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/RetryList.h
@@ -0,0 +1,53 @@
+#ifndef QPID_BROKER_RETRYLIST_H
+#define QPID_BROKER_RETRYLIST_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/Url.h"
+
+namespace qpid {
+namespace broker {
+
+/**
+ * Simple utility for managing a list of urls to try on reconnecting a
+ * link. Currently only supports TCP urls.
+ */
+class RetryList
+{
+ public:
+ QPID_BROKER_EXTERN RetryList();
+ QPID_BROKER_EXTERN void reset(const std::vector<Url>& urls);
+ QPID_BROKER_EXTERN bool next(Address& address);
+ private:
+ std::vector<Url> urls;
+ size_t urlIndex;
+ size_t addressIndex;
+
+ friend std::ostream& operator<<(std::ostream& os, const RetryList& l);
+};
+
+std::ostream& operator<<(std::ostream& os, const RetryList& l);
+
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_RETRYLIST_H*/
diff --git a/qpid/cpp/src/qpid/broker/SaslAuthenticator.cpp b/qpid/cpp/src/qpid/broker/SaslAuthenticator.cpp
new file mode 100644
index 0000000000..d5ada6f3a5
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SaslAuthenticator.cpp
@@ -0,0 +1,567 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/AclModule.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/amqp_0_10/Connection.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/ConnectionOutputHandler.h"
+#include "qpid/sys/SecuritySettings.h"
+
+#include <boost/format.hpp>
+
+#include "config.h"
+
+#if HAVE_SASL
+#include <sys/stat.h>
+#include <sasl/sasl.h>
+#include <sasl/saslplug.h>
+#include "qpid/sys/cyrus/CyrusSecurityLayer.h"
+using qpid::sys::cyrus::CyrusSecurityLayer;
+#endif
+
+using std::string;
+using namespace qpid::framing;
+using qpid::sys::SecurityLayer;
+using qpid::sys::SecuritySettings;
+using boost::format;
+using boost::str;
+
+
+namespace qpid {
+namespace broker {
+
+
+
+class NullAuthenticator : public SaslAuthenticator
+{
+ amqp_0_10::Connection& connection;
+ framing::AMQP_ClientProxy::Connection client;
+ std::string realm;
+ const bool encrypt;
+public:
+ NullAuthenticator(amqp_0_10::Connection& connection, bool encrypt);
+ ~NullAuthenticator();
+ void getMechanisms(framing::Array& mechanisms);
+ void start(const std::string& mechanism, const std::string* response);
+ void step(const std::string&) {}
+ std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize);
+};
+
+#if HAVE_SASL
+
+
+
+class CyrusAuthenticator : public SaslAuthenticator
+{
+ sasl_conn_t *sasl_conn;
+ amqp_0_10::Connection& connection;
+ framing::AMQP_ClientProxy::Connection client;
+ const bool encrypt;
+
+ void processAuthenticationStep(int code, const char *challenge, unsigned int challenge_len);
+ bool getUsername(std::string& uid);
+
+public:
+ CyrusAuthenticator(amqp_0_10::Connection& connection, bool encrypt);
+ ~CyrusAuthenticator();
+ void init();
+ void getMechanisms(framing::Array& mechanisms);
+ void start(const std::string& mechanism, const std::string* response);
+ void step(const std::string& response);
+ void getError(std::string& error);
+ void getUid(std::string& uid) { getUsername(uid); }
+ std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize);
+};
+
+bool SaslAuthenticator::available(void) {
+ return true;
+}
+
+// Called by sasl_server_init() when config file name is constructed to allow clients to verify file
+// Returning SASL_FAIL here will cause sasl_server_init() to fail and an exception will be thrown
+int _sasl_verifyfile_callback(void *, const char *configFileName, sasl_verify_type_t type)
+{
+ if (type == SASL_VRFY_CONF) {
+ struct stat st;
+ // verify the file exists
+ if ( ::stat ( configFileName, & st) ) {
+ QPID_LOG(error, "SASL: config file doesn't exist: " << configFileName);
+ return SASL_FAIL;
+ }
+ // verify the file can be read by the broker
+ if ( ::access ( configFileName, R_OK ) ) {
+ QPID_LOG(error, "SASL: broker unable to read the config file. Check file permissions: " << configFileName);
+ return SASL_FAIL;
+ }
+ }
+ return SASL_OK;
+}
+
+#ifndef sasl_callback_ft
+typedef int (*sasl_callback_ft)(void);
+#endif
+
+// passed to sasl_server_init()
+static sasl_callback_t _callbacks[] =
+{
+ { SASL_CB_VERIFYFILE, (sasl_callback_ft)&_sasl_verifyfile_callback, NULL },
+ { SASL_CB_LIST_END, NULL, NULL }
+};
+sasl_callback_t *callbacks = _callbacks;
+
+// Initialize the SASL mechanism; throw if it fails.
+void SaslAuthenticator::init(const std::string& saslName, std::string const & saslConfigPath )
+{
+ // Check if we have a version of SASL that supports sasl_set_path()
+#if (SASL_VERSION_FULL >= ((2<<16)|(1<<8)|22))
+ // If we are not given a sasl path, do nothing and allow the default to be used.
+ if ( saslConfigPath.empty() ) {
+ // don't pass callbacks if there is no config path
+ callbacks = NULL;
+ QPID_LOG ( info, "SASL: no config path set - using default." );
+ }
+ else {
+ struct stat st;
+
+ // Make sure the directory exists and we can read up to it.
+ if ( ::stat ( saslConfigPath.c_str(), & st) ) {
+ // Note: not using strerror() here because I think its messages are a little too hazy.
+ if ( errno == ENOENT )
+ throw Exception ( QPID_MSG ( "SASL: sasl_set_path failed: no such directory: " << saslConfigPath ) );
+ if ( errno == EACCES )
+ throw Exception ( QPID_MSG ( "SASL: sasl_set_path failed: cannot read parent of: " << saslConfigPath ) );
+ // catch-all stat failure
+ throw Exception ( QPID_MSG ( "SASL: sasl_set_path failed: cannot stat: " << saslConfigPath ) );
+ }
+
+ // Make sure that saslConfigPath is a directory.
+ if (!S_ISDIR(st.st_mode)) {
+ throw Exception ( QPID_MSG ( "SASL: not a directory: " << saslConfigPath ) );
+ }
+
+ // Make sure the directory is readable.
+ if ( ::access ( saslConfigPath.c_str(), R_OK ) ) {
+ throw Exception ( QPID_MSG ( "SASL: sasl_set_path failed: directory not readable:" << saslConfigPath ) );
+ }
+
+ // This shouldn't fail now, but check anyway.
+ int code = sasl_set_path(SASL_PATH_TYPE_CONFIG, const_cast<char *>(saslConfigPath.c_str()));
+ if(SASL_OK != code)
+ throw Exception(QPID_MSG("SASL: sasl_set_path failed [" << code << "] " ));
+
+ QPID_LOG(info, "SASL: config path set to " << saslConfigPath );
+ }
+#endif
+
+ int code = sasl_server_init(callbacks, saslName.c_str());
+ if (code != SASL_OK) {
+ // TODO: Figure out who owns the char* returned by
+ // sasl_errstring, though it probably does not matter much
+ throw Exception(QPID_MSG("SASL: failed to parse SASL configuration file in (" << saslConfigPath << "), error: " << sasl_errstring(code, NULL, NULL)));
+ }
+}
+
+void SaslAuthenticator::fini(void)
+{
+ sasl_done();
+}
+
+#else
+
+typedef NullAuthenticator CyrusAuthenticator;
+
+bool SaslAuthenticator::available(void) {
+ return false;
+}
+
+void SaslAuthenticator::init(const std::string& /*saslName*/, std::string const & /*saslConfigPath*/ )
+{
+ throw Exception("Requested authentication but SASL unavailable");
+}
+
+void SaslAuthenticator::fini(void)
+{
+ return;
+}
+
+#endif
+
+std::auto_ptr<SaslAuthenticator> SaslAuthenticator::createAuthenticator(amqp_0_10::Connection& c )
+{
+ if (c.getBroker().isAuthenticating()) {
+ return std::auto_ptr<SaslAuthenticator>(
+ new CyrusAuthenticator(c, c.getBroker().requireEncrypted()));
+ } else {
+ QPID_LOG(debug, "SASL: No Authentication Performed");
+ return std::auto_ptr<SaslAuthenticator>(new NullAuthenticator(c, c.getBroker().requireEncrypted()));
+ }
+}
+
+
+NullAuthenticator::NullAuthenticator(amqp_0_10::Connection& c, bool e) : connection(c), client(c.getOutput()),
+ realm(c.getBroker().getRealm()), encrypt(e) {}
+NullAuthenticator::~NullAuthenticator() {}
+
+void NullAuthenticator::getMechanisms(Array& mechanisms)
+{
+ mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value("ANONYMOUS")));
+ mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value("PLAIN")));//useful for testing
+}
+
+void NullAuthenticator::start(const string& mechanism, const string* response)
+{
+ if (encrypt) {
+ // encryption required - check to see if we are running over an
+ // encrypted SSL connection.
+ SecuritySettings external = connection.getExternalSecuritySettings();
+ if (external.ssf < 1) // < 1 == unencrypted
+ {
+ QPID_LOG(error, "Rejected un-encrypted connection.");
+ throw ConnectionForcedException("Connection must be encrypted.");
+ }
+ }
+ if (mechanism == "PLAIN") { // Old behavior
+ if (response && response->size() > 0) {
+ string uid;
+ string::size_type i = response->find((char)0);
+ if (i == 0 && response->size() > 1) {
+ //no authorization id; use authentication id
+ i = response->find((char)0, 1);
+ if (i != string::npos) uid = response->substr(1, i-1);
+ } else if (i != string::npos) {
+ //authorization id is first null delimited field
+ uid = response->substr(0, i);
+ }//else not a valid SASL PLAIN response, throw error?
+ if (!uid.empty()) {
+ //append realm if it has not already been added
+ i = uid.find(realm);
+ if (i == string::npos || realm.size() + i < uid.size()) {
+ uid = str(format("%1%@%2%") % uid % realm);
+ }
+ connection.setUserId(uid);
+ }
+ }
+ } else {
+ connection.setUserId("anonymous");
+ }
+ AclModule* acl = connection.getBroker().getAcl();
+ if (acl && !acl->approveConnection(connection))
+ {
+ throw ConnectionForcedException("User connection denied by configured limit");
+ }
+ qmf::org::apache::qpid::broker::Connection::shared_ptr cnxMgmt = connection.getMgmtObject();
+ if ( cnxMgmt )
+ cnxMgmt->set_saslMechanism(mechanism);
+
+ client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, connection.getHeartbeatMax());
+}
+
+
+std::auto_ptr<SecurityLayer> NullAuthenticator::getSecurityLayer(uint16_t)
+{
+ std::auto_ptr<SecurityLayer> securityLayer;
+ return securityLayer;
+}
+
+
+#if HAVE_SASL
+
+CyrusAuthenticator::CyrusAuthenticator(amqp_0_10::Connection& c, bool _encrypt) :
+ sasl_conn(0), connection(c), client(c.getOutput()), encrypt(_encrypt)
+{
+ init();
+}
+
+void CyrusAuthenticator::init()
+{
+ /* Next to the service name, which specifies the
+ * /etc/sasl2/<service name>.conf file to read, the realm is
+ * currently the most important argument below. When
+ * performing authentication the user that is authenticating
+ * will be looked up in a specific realm. If none is given
+ * then the realm defaults to the hostname, which can cause
+ * confusion when the daemon is run on different hosts that
+ * may be logically sharing a realm (aka a user domain). This
+ * is especially important for SASL PLAIN authentication,
+ * which cannot specify a realm for the user that is
+ * authenticating.
+ */
+ int code;
+
+ std::string realm = connection.getBroker().getRealm();
+ std::string service = connection.getBroker().getSaslServiceName();
+ code = sasl_server_new(service.c_str(), /* Service name */
+ NULL, /* Server FQDN, gethostname() */
+ realm.c_str(), /* Authentication realm */
+ NULL, /* Local IP, needed for some mechanism */
+ NULL, /* Remote IP, needed for some mechanism */
+ NULL, /* Callbacks */
+ 0, /* Connection flags */
+ &sasl_conn);
+
+ if (SASL_OK != code) {
+ QPID_LOG(error, "SASL: Connection creation failed: [" << code << "] " << sasl_errdetail(sasl_conn));
+
+ // TODO: Change this to an exception signaling
+ // server error, when one is available
+ throw ConnectionForcedException("Unable to perform authentication");
+ }
+
+ sasl_security_properties_t secprops;
+
+ //TODO: should the actual SSF values be configurable here?
+ secprops.min_ssf = encrypt ? 10: 0;
+ secprops.max_ssf = 256;
+
+ // If the transport provides encryption, notify the SASL library of
+ // the key length and set the ssf range to prevent double encryption.
+ SecuritySettings external = connection.getExternalSecuritySettings();
+ QPID_LOG(debug, "External ssf=" << external.ssf << " and auth=" << external.authid);
+ sasl_ssf_t external_ssf = (sasl_ssf_t) external.ssf;
+
+ if ((external_ssf) && (external.authid.empty())) {
+ QPID_LOG(warning, "SASL error: unable to offer EXTERNAL mechanism as authid cannot be determined");
+ }
+
+ if (external_ssf) {
+ int result = sasl_setprop(sasl_conn, SASL_SSF_EXTERNAL, &external_ssf);
+ if (result != SASL_OK) {
+ throw framing::InternalErrorException(QPID_MSG("SASL error: unable to set external SSF: " << result));
+ }
+
+ secprops.max_ssf = secprops.min_ssf = 0;
+ }
+
+ QPID_LOG(debug, "min_ssf: " << secprops.min_ssf <<
+ ", max_ssf: " << secprops.max_ssf <<
+ ", external_ssf: " << external_ssf );
+
+ if (!external.authid.empty()) {
+ const char* external_authid = external.authid.c_str();
+ int result = sasl_setprop(sasl_conn, SASL_AUTH_EXTERNAL, external_authid);
+ if (result != SASL_OK) {
+ throw framing::InternalErrorException(QPID_MSG("SASL error: unable to set external auth: " << result));
+ }
+
+ QPID_LOG(debug, "external auth detected and set to " << external_authid);
+ }
+
+ secprops.maxbufsize = 65535;
+ secprops.property_names = 0;
+ secprops.property_values = 0;
+ secprops.security_flags = 0; /* or SASL_SEC_NOANONYMOUS etc as appropriate */
+ /*
+ * The nodict flag restricts SASL authentication mechanisms
+ * to those that are not susceptible to dictionary attacks.
+ * They are:
+ * SRP
+ * PASSDSS-3DES-1
+ * EXTERNAL
+ */
+ if (external.nodict) secprops.security_flags |= SASL_SEC_NODICTIONARY;
+ int result = sasl_setprop(sasl_conn, SASL_SEC_PROPS, &secprops);
+ if (result != SASL_OK) {
+ throw framing::InternalErrorException(QPID_MSG("SASL error: " << result));
+ }
+}
+
+CyrusAuthenticator::~CyrusAuthenticator()
+{
+ if (sasl_conn) {
+ sasl_dispose(&sasl_conn);
+ sasl_conn = 0;
+ }
+}
+
+void CyrusAuthenticator::getError(string& error)
+{
+ error = string(sasl_errdetail(sasl_conn));
+}
+
+bool CyrusAuthenticator::getUsername(string& uid)
+{
+ const void* ptr;
+
+ int code = sasl_getprop(sasl_conn, SASL_USERNAME, &ptr);
+ if (SASL_OK == code) {
+ uid = string(const_cast<char*>(static_cast<const char*>(ptr)));
+ return true;
+ } else {
+ QPID_LOG(warning, "Failed to retrieve sasl username");
+ return false;
+ }
+}
+
+void CyrusAuthenticator::getMechanisms(Array& mechanisms)
+{
+ const char *separator = " ";
+ const char *list;
+ unsigned int list_len;
+ int count;
+
+ int code = sasl_listmech(sasl_conn, NULL,
+ "", separator, "",
+ &list, &list_len,
+ &count);
+
+ if (SASL_OK != code) {
+ QPID_LOG(info, "SASL: Mechanism listing failed: " << sasl_errdetail(sasl_conn));
+
+ // TODO: Change this to an exception signaling
+ // server error, when one is available
+ throw ConnectionForcedException("Mechanism listing failed");
+ } else {
+ string mechanism;
+ unsigned int start;
+ unsigned int end;
+
+ QPID_LOG(info, "SASL: Mechanism list: " << list);
+
+ end = 0;
+ do {
+ start = end;
+
+ // Seek to end of next mechanism
+ while (end < list_len && separator[0] != list[end])
+ end++;
+
+ // Record the mechanism
+ mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value(string(list, start, end - start))));
+ end++;
+ } while (end < list_len);
+ }
+}
+
+void CyrusAuthenticator::start(const string& mechanism, const string* response)
+{
+ const char *challenge;
+ unsigned int challenge_len;
+
+ // This should be at same debug level as mech list in getMechanisms().
+ QPID_LOG(info, "SASL: Starting authentication with mechanism: " << mechanism);
+ int code = sasl_server_start(sasl_conn,
+ mechanism.c_str(),
+ (response ? response->c_str() : 0), (response ? response->size() : 0),
+ &challenge, &challenge_len);
+
+ processAuthenticationStep(code, challenge, challenge_len);
+ qmf::org::apache::qpid::broker::Connection::shared_ptr cnxMgmt = connection.getMgmtObject();
+ if ( cnxMgmt )
+ cnxMgmt->set_saslMechanism(mechanism);
+}
+
+void CyrusAuthenticator::step(const string& response)
+{
+ const char *challenge;
+ unsigned int challenge_len;
+
+ int code = sasl_server_step(sasl_conn,
+ response.c_str(), response.length(),
+ &challenge, &challenge_len);
+
+ processAuthenticationStep(code, challenge, challenge_len);
+}
+
+void CyrusAuthenticator::processAuthenticationStep(int code, const char *challenge, unsigned int challenge_len)
+{
+ if (SASL_OK == code) {
+ std::string uid;
+ if (!getUsername(uid)) {
+ // TODO: Change this to an exception signaling
+ // authentication failure, when one is available
+ throw ConnectionForcedException("Authenticated username unavailable");
+ }
+
+ connection.setUserId(uid);
+
+ AclModule* acl = connection.getBroker().getAcl();
+ if (acl && !acl->approveConnection(connection))
+ {
+ throw ConnectionForcedException("User connection denied by configured limit");
+ }
+
+ QPID_LOG(info, connection.getMgmtId() << " SASL: Authentication succeeded for: " << uid);
+
+ client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, connection.getHeartbeatMax());
+ } else if (SASL_CONTINUE == code) {
+ string challenge_str(challenge, challenge_len);
+
+ QPID_LOG(debug, "SASL: sending challenge to client");
+
+ client.secure(challenge_str);
+ } else {
+ std::string uid;
+ //save error detail before trying to retrieve username as error in doing so will overwrite it
+ std::string errordetail = sasl_errdetail(sasl_conn);
+ if (!getUsername(uid)) {
+ QPID_LOG(info, "SASL: Authentication failed (no username available yet):" << errordetail);
+ } else if (SASL_NOUSER == code) {
+ // SASL_NOUSER is returned when either:
+ // - the user name supplied was not in the sasl db or
+ // - the sasl db could not be read
+ // - because of file permissions or
+ // - because the file was not found
+ QPID_LOG(info, "SASL: Authentication failed. User not found or sasldb not accessible.(" << code << ") for " << uid);
+ } else {
+ QPID_LOG(info, "SASL: Authentication failed for " << uid << ":" << errordetail);
+ }
+
+ // TODO: Change to more specific exceptions, when they are
+ // available
+ switch (code) {
+ case SASL_NOMECH:
+ throw ConnectionForcedException("Unsupported mechanism");
+ break;
+ case SASL_TRYAGAIN:
+ throw ConnectionForcedException("Transient failure, try again");
+ break;
+ default:
+ throw ConnectionForcedException("Authentication failed");
+ break;
+ }
+ }
+}
+
+std::auto_ptr<SecurityLayer> CyrusAuthenticator::getSecurityLayer(uint16_t maxFrameSize)
+{
+
+ const void* value(0);
+ int result = sasl_getprop(sasl_conn, SASL_SSF, &value);
+ if (result != SASL_OK) {
+ throw framing::InternalErrorException(QPID_MSG("SASL error: " << sasl_errdetail(sasl_conn)));
+ }
+ uint ssf = *(reinterpret_cast<const unsigned*>(value));
+ std::auto_ptr<SecurityLayer> securityLayer;
+ if (ssf) {
+ securityLayer = std::auto_ptr<SecurityLayer>(new CyrusSecurityLayer(sasl_conn, maxFrameSize, ssf));
+ }
+ qmf::org::apache::qpid::broker::Connection::shared_ptr cnxMgmt = connection.getMgmtObject();
+ if ( cnxMgmt )
+ cnxMgmt->set_saslSsf(ssf);
+ return securityLayer;
+}
+
+#endif
+
+}}
diff --git a/qpid/cpp/src/qpid/broker/SaslAuthenticator.h b/qpid/cpp/src/qpid/broker/SaslAuthenticator.h
new file mode 100644
index 0000000000..97434d6ffe
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SaslAuthenticator.h
@@ -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.
+ *
+ */
+#ifndef _SaslAuthenticator_
+#define _SaslAuthenticator_
+
+
+#include "qpid/framing/amqp_types.h"
+#include "qpid/framing/AMQP_ClientProxy.h"
+#include "qpid/Exception.h"
+#include "qpid/sys/SecurityLayer.h"
+#include <memory>
+#include <vector>
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+
+namespace qpid {
+namespace broker {
+
+namespace amqp_0_10 {
+class Connection;
+}
+
+class SaslAuthenticator
+{
+public:
+ virtual ~SaslAuthenticator() {}
+ virtual void getMechanisms(framing::Array& mechanisms) = 0;
+ virtual void start(const std::string& mechanism, const std::string* response) = 0;
+ virtual void step(const std::string& response) = 0;
+ virtual void getUid(std::string&) {}
+ virtual bool getUsername(std::string&) { return false; };
+ virtual void getError(std::string&) {}
+ virtual std::auto_ptr<qpid::sys::SecurityLayer> getSecurityLayer(uint16_t maxFrameSize) = 0;
+
+ static bool available(void);
+
+ // Initialize the SASL mechanism; throw if it fails.
+ static void init(const std::string& saslName, std::string const & saslConfigPath );
+ static void fini(void);
+
+ static std::auto_ptr<SaslAuthenticator> createAuthenticator(amqp_0_10::Connection& connection);
+
+ virtual void callUserIdCallbacks() { }
+};
+
+}}
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/SecureConnection.cpp b/qpid/cpp/src/qpid/broker/SecureConnection.cpp
new file mode 100644
index 0000000000..59ac9ef132
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SecureConnection.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/SecureConnection.h"
+#include "qpid/sys/SecurityLayer.h"
+#include "qpid/framing/reply_exceptions.h"
+
+namespace qpid {
+namespace broker {
+
+using qpid::sys::SecurityLayer;
+
+SecureConnection::SecureConnection() : secured(false) {}
+
+size_t SecureConnection::decode(const char* buffer, size_t size)
+{
+ if (!secured && securityLayer.get()) {
+ //security layer comes into effect on first read after its
+ //activated
+ secured = true;
+ }
+ if (secured) {
+ return securityLayer->decode(buffer, size);
+ } else {
+ return codec->decode(buffer, size);
+ }
+}
+
+size_t SecureConnection::encode(char* buffer, size_t size)
+{
+ if (secured) {
+ return securityLayer->encode(buffer, size);
+ } else {
+ return codec->encode(buffer, size);
+ }
+}
+
+bool SecureConnection::canEncode()
+{
+ if (secured) return securityLayer->canEncode();
+ else return codec->canEncode();
+}
+
+void SecureConnection::closed()
+{
+ codec->closed();
+}
+
+bool SecureConnection::isClosed() const
+{
+ return codec->isClosed();
+}
+
+framing::ProtocolVersion SecureConnection::getVersion() const
+{
+ return codec->getVersion();
+}
+
+void SecureConnection:: setCodec(std::auto_ptr<ConnectionCodec> c)
+{
+ codec = c;
+}
+
+void SecureConnection::activateSecurityLayer(std::auto_ptr<SecurityLayer> sl, bool secureImmediately)
+{
+ securityLayer = sl;
+ securityLayer->init(codec.get());
+
+ if ( secureImmediately )
+ secured = true;
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/SecureConnection.h b/qpid/cpp/src/qpid/broker/SecureConnection.h
new file mode 100644
index 0000000000..632087350e
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SecureConnection.h
@@ -0,0 +1,60 @@
+#ifndef QPID_BROKER_SECURECONNECTION_H
+#define QPID_BROKER_SECURECONNECTION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/ConnectionCodec.h"
+#include <memory>
+
+namespace qpid {
+
+namespace sys {
+class SecurityLayer;
+}
+
+namespace broker {
+
+/**
+ * A ConnectionCodec 'wrapper' that allows a connection to be
+ * 'secured' e.g. encrypted based on settings negotiatiated at the
+ * time of establishment.
+ */
+class SecureConnection : public qpid::sys::ConnectionCodec
+{
+ public:
+ SecureConnection();
+ size_t decode(const char* buffer, size_t size);
+ size_t encode(char* buffer, size_t size);
+ bool canEncode();
+ void closed();
+ bool isClosed() const;
+ framing::ProtocolVersion getVersion() const;
+ void setCodec(std::auto_ptr<ConnectionCodec>);
+ void activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer>, bool secureImmediately=false);
+ private:
+ std::auto_ptr<ConnectionCodec> codec;
+ std::auto_ptr<qpid::sys::SecurityLayer> securityLayer;
+ bool secured;
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_SECURECONNECTION_H*/
diff --git a/qpid/cpp/src/qpid/broker/Selector.cpp b/qpid/cpp/src/qpid/broker/Selector.cpp
new file mode 100644
index 0000000000..21247e125c
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Selector.cpp
@@ -0,0 +1,237 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/Selector.h"
+
+#include "qpid/amqp/CharSequence.h"
+#include "qpid/amqp/MapHandler.h"
+#include "qpid/amqp/MessageId.h"
+#include "qpid/broker/Message.h"
+#include "qpid/broker/SelectorExpression.h"
+#include "qpid/broker/SelectorValue.h"
+#include "qpid/log/Statement.h"
+#include "qpid/types/Variant.h"
+
+#include <stdexcept>
+#include <string>
+#include <sstream>
+#include "qpid/sys/unordered_map.h"
+
+#include <boost/lexical_cast.hpp>
+#include <boost/ptr_container/ptr_vector.hpp>
+
+namespace qpid {
+namespace broker {
+
+using std::string;
+using qpid::sys::unordered_map;
+using qpid::amqp::CharSequence;
+using qpid::amqp::MapHandler;
+using qpid::amqp::MessageId;
+
+/**
+ * Identifier (amqp.) | JMS... | amqp 1.0 equivalent
+ * durable | | durable header section
+ * delivery_mode | DeliveryMode | [durable ? 'PERSISTENT' : 'NON_PERSISTENT'] (computed value)
+ * priority | Priority | priority header section
+ * delivery_count | | delivery-count header section
+ * redelivered |[Redelivered] | (delivery_count>0) (computed value)
+ * correlation_id | CorrelationID| correlation-id properties section
+ * to |[Destination] | to properties section
+ * absolute_expiry_time |[Expiration] | absolute-expiry-time properties section
+ * message_id | MessageID | message-id properties section
+ * reply_to |[ReplyTo] | reply-to properties section
+ * creation_time | Timestamp | creation-time properties section
+ * jms_type | Type | jms-type message-annotations section
+ */
+const string EMPTY;
+const string PERSISTENT("PERSISTENT");
+const string NON_PERSISTENT("NON_PERSISTENT");
+
+class MessageSelectorEnv : public SelectorEnv {
+ const Message& msg;
+ mutable boost::ptr_vector<string> returnedStrings;
+ mutable unordered_map<string, Value> returnedValues;
+ mutable bool valuesLookedup;
+
+ const Value& value(const string&) const;
+ const Value specialValue(const string&) const;
+
+public:
+ MessageSelectorEnv(const Message&);
+};
+
+MessageSelectorEnv::MessageSelectorEnv(const Message& m) :
+ msg(m),
+ valuesLookedup(false)
+{
+}
+
+const Value MessageSelectorEnv::specialValue(const string& id) const
+{
+ Value v;
+ // TODO: Just use a simple if chain for now - improve this later
+ if ( id=="delivery_mode" ) {
+ v = msg.getEncoding().isPersistent() ? PERSISTENT : NON_PERSISTENT;
+ } else if ( id=="redelivered" ) {
+ // Although redelivered is defined to be true delivery-count>0 if it is 0 now
+ // it will be 1 by the time the message is delivered
+ v = msg.getDeliveryCount()>=0 ? true : false;
+ } else if ( id=="priority" ) {
+ v = int64_t(msg.getPriority());
+ } else if ( id=="correlation_id" ) {
+ MessageId cId = msg.getEncoding().getCorrelationId();
+ if (cId) {
+ returnedStrings.push_back(new string(cId.str()));
+ v = returnedStrings[returnedStrings.size()-1];
+ }
+ } else if ( id=="message_id" ) {
+ MessageId mId = msg.getEncoding().getMessageId();
+ if (mId) {
+ returnedStrings.push_back(new string(mId.str()));
+ v = returnedStrings[returnedStrings.size()-1];
+ }
+ } else if ( id=="to" ) {
+ v = EMPTY; // Hard to get this correct for both 1.0 and 0-10
+ } else if ( id=="reply_to" ) {
+ v = EMPTY; // Hard to get this correct for both 1.0 and 0-10
+ } else if ( id=="absolute_expiry_time" ) {
+ qpid::sys::AbsTime expiry = msg.getExpiration();
+ // Java property has value of 0 for no expiry
+ v = (expiry==qpid::sys::FAR_FUTURE) ? 0
+ : qpid::sys::Duration(qpid::sys::AbsTime::epoch(), expiry) / qpid::sys::TIME_MSEC;
+ } else if ( id=="creation_time" ) {
+ // Use the time put on queue (if it is enabled) as 0-10 has no standard way to get message
+ // creation time and we're not paying attention to the 1.0 creation time yet.
+ v = int64_t(msg.getTimestamp() * 1000); // getTimestamp() returns time in seconds we need milliseconds
+ } else if ( id=="jms_type" ) {
+ // Currently we can't distinguish between an empty JMSType and no JMSType
+ // We'll assume for now that setting an empty JMSType doesn't make a lot of sense
+ const string jmsType = msg.getAnnotation("jms-type").asString();
+ if ( !jmsType.empty() ) {
+ returnedStrings.push_back(new string(jmsType));
+ v = returnedStrings[returnedStrings.size()-1];
+ }
+ } else {
+ v = Value();
+ }
+ return v;
+}
+
+struct ValueHandler : public broker::MapHandler {
+ unordered_map<string, Value>& values;
+ boost::ptr_vector<string>& strings;
+
+ ValueHandler(unordered_map<string, Value>& v, boost::ptr_vector<string>& s) :
+ values(v),
+ strings(s)
+ {}
+
+ template <typename T>
+ void handle(const CharSequence& key, const T& value)
+ {
+ values[string(key.data, key.size)] = value;
+ }
+
+ void handleVoid(const CharSequence&) {}
+ void handleBool(const CharSequence& key, bool value) { handle<bool>(key, value); }
+ void handleUint8(const CharSequence& key, uint8_t value) { handle<int64_t>(key, value); }
+ void handleUint16(const CharSequence& key, uint16_t value) { handle<int64_t>(key, value); }
+ void handleUint32(const CharSequence& key, uint32_t value) { handle<int64_t>(key, value); }
+ void handleUint64(const CharSequence& key, uint64_t value) {
+ if ( value>uint64_t(std::numeric_limits<int64_t>::max()) ) {
+ handle<double>(key, value);
+ } else {
+ handle<int64_t>(key, value);
+ }
+ }
+ void handleInt8(const CharSequence& key, int8_t value) { handle<int64_t>(key, value); }
+ void handleInt16(const CharSequence& key, int16_t value) { handle<int64_t>(key, value); }
+ void handleInt32(const CharSequence& key, int32_t value) { handle<int64_t>(key, value); }
+ void handleInt64(const CharSequence& key, int64_t value) { handle<int64_t>(key, value); }
+ void handleFloat(const CharSequence& key, float value) { handle<double>(key, value); }
+ void handleDouble(const CharSequence& key, double value) { handle<double>(key, value); }
+ void handleString(const CharSequence& key, const CharSequence& value, const CharSequence&) {
+ strings.push_back(new string(value.data, value.size));
+ handle(key, strings[strings.size()-1]);
+ }
+};
+
+const Value& MessageSelectorEnv::value(const string& identifier) const
+{
+ // Check for amqp prefix and strip it if present
+ if ( identifier.substr(0, 5) == "amqp." ) {
+ if ( returnedValues.count(identifier)==0 ) {
+ QPID_LOG(debug, "Selector lookup special identifier: " << identifier);
+ returnedValues[identifier] = specialValue(identifier.substr(5));
+ }
+ } else if (!valuesLookedup) {
+ QPID_LOG(debug, "Selector lookup triggered by: " << identifier);
+ // Iterate over all the message properties
+ ValueHandler handler(returnedValues, returnedStrings);
+ msg.getEncoding().processProperties(handler);
+ valuesLookedup = true;
+ // Anything that wasn't found will have a void value now
+ }
+ const Value& v = returnedValues[identifier];
+ QPID_LOG(debug, "Selector identifier: " << identifier << "->" << v);
+ return v;
+}
+
+Selector::Selector(const string& e)
+try :
+ parse(TopExpression::parse(e)),
+ expression(e)
+{
+ bool debugOut;
+ QPID_LOG_TEST(debug, debugOut);
+ if (debugOut) {
+ std::stringstream ss;
+ parse->repr(ss);
+ QPID_LOG(debug, "Selector parsed[" << e << "] into: " << ss.str());
+ }
+}
+catch (std::range_error& ex) {
+ QPID_LOG(debug, "Selector failed[" << e << "] -> " << ex.what());
+ throw;
+}
+
+Selector::~Selector()
+{
+}
+
+bool Selector::eval(const SelectorEnv& env)
+{
+ return parse->eval(env);
+}
+
+bool Selector::filter(const Message& msg)
+{
+ const MessageSelectorEnv env(msg);
+ return eval(env);
+}
+
+boost::shared_ptr<Selector> returnSelector(const string& e)
+{
+ return boost::shared_ptr<Selector>(new Selector(e));
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/broker/Selector.h b/qpid/cpp/src/qpid/broker/Selector.h
new file mode 100644
index 0000000000..0e151ecd0e
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Selector.h
@@ -0,0 +1,80 @@
+#ifndef QPID_BROKER_SELECTOR_H
+#define QPID_BROKER_SELECTOR_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/BrokerImportExport.h"
+
+#include <string>
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace broker {
+
+class Message;
+class Value;
+class TopExpression;
+
+/**
+ * Interface to provide values to a Selector evaluation
+ */
+class SelectorEnv {
+public:
+ virtual ~SelectorEnv() {};
+
+ virtual const Value& value(const std::string&) const = 0;
+};
+
+class Selector {
+ boost::scoped_ptr<TopExpression> parse;
+ const std::string expression;
+
+public:
+ QPID_BROKER_EXTERN Selector(const std::string&);
+ QPID_BROKER_EXTERN ~Selector();
+
+ /**
+ * Evaluate parsed expression with a given environment
+ */
+ QPID_BROKER_EXTERN bool eval(const SelectorEnv& env);
+
+ /**
+ * Apply selector to message
+ * @param msg message to filter against selector
+ * @return true if msg meets the selector specification
+ */
+ QPID_BROKER_EXTERN bool filter(const Message& msg);
+};
+
+/**
+ * Return a Selector as specified by the string:
+ * - Structured like this so that we can move to caching Selectors with the same
+ * specifications and just returning an existing one
+ */
+boost::shared_ptr<Selector> returnSelector(const std::string&);
+
+}}
+
+#endif
+
diff --git a/qpid/cpp/src/qpid/broker/SelectorExpression.cpp b/qpid/cpp/src/qpid/broker/SelectorExpression.cpp
new file mode 100644
index 0000000000..1e8a90ed4c
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SelectorExpression.cpp
@@ -0,0 +1,1016 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/SelectorExpression.h"
+
+#include "qpid/broker/Selector.h"
+#include "qpid/broker/SelectorToken.h"
+#include "qpid/broker/SelectorValue.h"
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/sys/regex.h"
+
+#include <string>
+#include <memory>
+#include <ostream>
+
+#include <boost/lexical_cast.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/ptr_container/ptr_vector.hpp>
+
+/*
+ * Syntax for JMS style selector expressions (informal):
+ * This is a mixture of regular expression and EBNF formalism
+ *
+ * The top level term is SelectExpression
+ *
+ * // Lexical elements
+ *
+ * Alpha ::= [a-zA-Z]
+ * Digit ::= [0-9]
+ * IdentifierInitial ::= Alpha | "_" | "$"
+ * IdentifierPart ::= IdentifierInitial | Digit | "."
+ * Identifier ::= IdentifierInitial IdentifierPart*
+ * Constraint : Identifier NOT IN ("NULL", "TRUE", "FALSE", "NOT", "AND", "OR", "BETWEEN", "LIKE", "IN", "IS") // Case insensitive
+ *
+ * LiteralString ::= ("'" [^']* "'")+ // Repeats to cope with embedded single quote
+ *
+ * LiteralExactNumeric ::= Digit+
+ * Exponent ::= ('+'|'-')? LiteralExactNumeric
+ * LiteralApproxNumeric ::= ( Digit "." Digit* ( "E" Exponent )? ) |
+ * ( "." Digit+ ( "E" Exponent )? ) |
+ * ( Digit+ "E" Exponent )
+ * LiteralBool ::= "TRUE" | "FALSE"
+ *
+ * Literal ::= LiteralBool | LiteralString | LiteralApproxNumeric | LiteralExactNumeric
+ *
+ * EqOps ::= "=" | "<>"
+ * ComparisonOps ::= EqOps | ">" | ">=" | "<" | "<="
+ * AddOps ::= "+" | "-"
+ * MultiplyOps ::= "*" | "/"
+ *
+ * // Expression Syntax
+ *
+ * SelectExpression ::= OrExpression? // An empty expression is equivalent to "true"
+ *
+ * OrExpression ::= AndExpression ( "OR" AndExpression )*
+ *
+ * AndExpression :: = ComparisonExpression ( "AND" ComparisonExpression )*
+ *
+ * ComparisonExpression ::= AddExpression "IS" "NOT"? "NULL" |
+ * AddExpression "NOT"? "LIKE" LiteralString [ "ESCAPE" LiteralString ] |
+ * AddExpression "NOT"? "BETWEEN" AddExpression "AND" AddExpression |
+ * AddExpression "NOT"? "IN" "(" PrimaryExpression ("," PrimaryExpression)* ")" |
+ * AddExpression ComparisonOps AddExpression |
+ * "NOT" ComparisonExpression |
+ * AddExpression
+ *
+ * AddExpression :: = MultiplyExpression ( AddOps MultiplyExpression )*
+ *
+ * MultiplyExpression :: = UnaryArithExpression ( MultiplyOps UnaryArithExpression )*
+ *
+ * UnaryArithExpression ::= AddOps AddExpression |
+ * "(" OrExpression ")" |
+ * PrimaryExpression
+ *
+ * PrimaryExpression :: = Identifier |
+ * Literal
+ */
+
+
+using std::string;
+using std::ostream;
+
+namespace qpid {
+namespace broker {
+
+class Expression {
+public:
+ virtual ~Expression() {}
+ virtual void repr(std::ostream&) const = 0;
+ virtual Value eval(const SelectorEnv&) const = 0;
+
+ virtual BoolOrNone eval_bool(const SelectorEnv& env) const {
+ Value v = eval(env);
+ if (v.type==Value::T_BOOL) return BoolOrNone(v.b);
+ else return BN_UNKNOWN;
+ }
+};
+
+class BoolExpression : public Expression {
+public:
+ virtual ~BoolExpression() {}
+ virtual void repr(std::ostream&) const = 0;
+ virtual BoolOrNone eval_bool(const SelectorEnv&) const = 0;
+
+ Value eval(const SelectorEnv& env) const {
+ return eval_bool(env);
+ }
+};
+
+// Operators
+
+class ComparisonOperator {
+public:
+ virtual ~ComparisonOperator() {}
+ virtual void repr(ostream&) const = 0;
+ virtual BoolOrNone eval(Expression&, Expression&, const SelectorEnv&) const = 0;
+};
+
+class UnaryBooleanOperator {
+public:
+ virtual ~UnaryBooleanOperator() {}
+ virtual void repr(ostream&) const = 0;
+ virtual BoolOrNone eval(Expression&, const SelectorEnv&) const = 0;
+};
+
+class ArithmeticOperator {
+public:
+ virtual ~ArithmeticOperator() {}
+ virtual void repr(ostream&) const = 0;
+ virtual Value eval(Expression&, Expression&, const SelectorEnv&) const = 0;
+};
+
+class UnaryArithmeticOperator {
+public:
+ virtual ~UnaryArithmeticOperator() {}
+ virtual void repr(ostream&) const = 0;
+ virtual Value eval(Expression&, const SelectorEnv&) const = 0;
+};
+
+////////////////////////////////////////////////////
+
+// Convenience outputters
+
+ostream& operator<<(ostream& os, const Expression& e)
+{
+ e.repr(os);
+ return os;
+}
+
+ostream& operator<<(ostream& os, const ComparisonOperator& e)
+{
+ e.repr(os);
+ return os;
+}
+
+ostream& operator<<(ostream& os, const UnaryBooleanOperator& e)
+{
+ e.repr(os);
+ return os;
+}
+
+ostream& operator<<(ostream& os, const ArithmeticOperator& e)
+{
+ e.repr(os);
+ return os;
+}
+
+ostream& operator<<(ostream& os, const UnaryArithmeticOperator& e)
+{
+ e.repr(os);
+ return os;
+}
+
+// Boolean Expression types...
+
+class ComparisonExpression : public BoolExpression {
+ ComparisonOperator* op;
+ boost::scoped_ptr<Expression> e1;
+ boost::scoped_ptr<Expression> e2;
+
+public:
+ ComparisonExpression(ComparisonOperator* o, Expression* e, Expression* e_):
+ op(o),
+ e1(e),
+ e2(e_)
+ {}
+
+ void repr(ostream& os) const {
+ os << "(" << *e1 << *op << *e2 << ")";
+ }
+
+ BoolOrNone eval_bool(const SelectorEnv& env) const {
+ return op->eval(*e1, *e2, env);
+ }
+};
+
+class OrExpression : public BoolExpression {
+ boost::scoped_ptr<Expression> e1;
+ boost::scoped_ptr<Expression> e2;
+
+public:
+ OrExpression(Expression* e, Expression* e_):
+ e1(e),
+ e2(e_)
+ {}
+
+ void repr(ostream& os) const {
+ os << "(" << *e1 << " OR " << *e2 << ")";
+ }
+
+ BoolOrNone eval_bool(const SelectorEnv& env) const {
+ BoolOrNone bn1(e1->eval_bool(env));
+ if (bn1==BN_TRUE) return BN_TRUE;
+ BoolOrNone bn2(e2->eval_bool(env));
+ if (bn2==BN_TRUE) return BN_TRUE;
+ if (bn1==BN_FALSE && bn2==BN_FALSE) return BN_FALSE;
+ else return BN_UNKNOWN;
+ }
+};
+
+class AndExpression : public BoolExpression {
+ boost::scoped_ptr<Expression> e1;
+ boost::scoped_ptr<Expression> e2;
+
+public:
+ AndExpression(Expression* e, Expression* e_):
+ e1(e),
+ e2(e_)
+ {}
+
+ void repr(ostream& os) const {
+ os << "(" << *e1 << " AND " << *e2 << ")";
+ }
+
+ BoolOrNone eval_bool(const SelectorEnv& env) const {
+ BoolOrNone bn1(e1->eval_bool(env));
+ if (bn1==BN_FALSE) return BN_FALSE;
+ BoolOrNone bn2(e2->eval_bool(env));
+ if (bn2==BN_FALSE) return BN_FALSE;
+ if (bn1==BN_TRUE && bn2==BN_TRUE) return BN_TRUE;
+ else return BN_UNKNOWN;
+ }
+};
+
+class UnaryBooleanExpression : public BoolExpression {
+ UnaryBooleanOperator* op;
+ boost::scoped_ptr<Expression> e1;
+
+public:
+ UnaryBooleanExpression(UnaryBooleanOperator* o, Expression* e) :
+ op(o),
+ e1(e)
+ {}
+
+ void repr(ostream& os) const {
+ os << *op << "(" << *e1 << ")";
+ }
+
+ BoolOrNone eval_bool(const SelectorEnv& env) const {
+ return op->eval(*e1, env);
+ }
+};
+
+class LikeExpression : public BoolExpression {
+ boost::scoped_ptr<Expression> e;
+ string reString;
+ qpid::sys::regex regexBuffer;
+
+ static string toRegex(const string& s, const string& escape) {
+ string regex("^");
+ if (escape.size()>1) throw std::logic_error("Internal error");
+ char e = 0;
+ if (escape.size()==1) {
+ e = escape[0];
+ }
+ // Translate % -> .*, _ -> ., . ->\. *->\*
+ bool doEscape = false;
+ for (string::const_iterator i = s.begin(); i!=s.end(); ++i) {
+ if ( e!=0 && *i==e ) {
+ doEscape = true;
+ continue;
+ }
+ switch(*i) {
+ case '%':
+ if (doEscape) regex += *i;
+ else regex += ".*";
+ break;
+ case '_':
+ if (doEscape) regex += *i;
+ else regex += ".";
+ break;
+ case ']':
+ regex += "[]]";
+ break;
+ case '-':
+ regex += "[-]";
+ break;
+ // Don't add any more cases here: these are sufficient,
+ // adding more might turn on inadvertent matching
+ case '\\':
+ case '^':
+ case '$':
+ case '.':
+ case '*':
+ case '[':
+ regex += "\\";
+ // Fallthrough
+ default:
+ regex += *i;
+ break;
+ }
+ doEscape = false;
+ }
+ regex += "$";
+ return regex;
+ }
+
+public:
+ LikeExpression(Expression* e_, const string& like, const string& escape="") :
+ e(e_),
+ reString(toRegex(like, escape)),
+ regexBuffer(reString)
+ {}
+
+ void repr(ostream& os) const {
+ os << *e << " REGEX_MATCH '" << reString << "'";
+ }
+
+ BoolOrNone eval_bool(const SelectorEnv& env) const {
+ Value v(e->eval(env));
+ if ( v.type!=Value::T_STRING ) return BN_UNKNOWN;
+ return BoolOrNone(qpid::sys::regex_match(*v.s, regexBuffer));
+ }
+};
+
+class BetweenExpression : public BoolExpression {
+ boost::scoped_ptr<Expression> e;
+ boost::scoped_ptr<Expression> l;
+ boost::scoped_ptr<Expression> u;
+
+public:
+ BetweenExpression(Expression* e_, Expression* l_, Expression* u_) :
+ e(e_),
+ l(l_),
+ u(u_)
+ {}
+
+ void repr(ostream& os) const {
+ os << *e << " BETWEEN " << *l << " AND " << *u;
+ }
+
+ BoolOrNone eval_bool(const SelectorEnv& env) const {
+ Value ve(e->eval(env));
+ if (unknown(ve)) return BN_UNKNOWN;
+ Value vl(l->eval(env));
+ if (!unknown(vl) && ve<vl) return BN_FALSE;
+ Value vu(u->eval(env));
+ if (!unknown(vu) && ve>vu) return BN_FALSE;
+ if (unknown(vl) || unknown(vu)) return BN_UNKNOWN;
+ return BN_TRUE;
+ }
+};
+
+class InExpression : public BoolExpression {
+ boost::scoped_ptr<Expression> e;
+ boost::ptr_vector<Expression> l;
+
+public:
+ InExpression(Expression* e_, boost::ptr_vector<Expression>& l_) :
+ e(e_)
+ {
+ l.swap(l_);
+ }
+
+ void repr(ostream& os) const {
+ os << *e << " IN (";
+ for (std::size_t i = 0; i<l.size(); ++i){
+ os << l[i] << (i<l.size()-1 ? ", " : ")");
+ }
+ }
+
+ BoolOrNone eval_bool(const SelectorEnv& env) const {
+ Value ve(e->eval(env));
+ if (unknown(ve)) return BN_UNKNOWN;
+ BoolOrNone r = BN_FALSE;
+ for (std::size_t i = 0; i<l.size(); ++i){
+ Value li(l[i].eval(env));
+ if (unknown(li)) {
+ r = BN_UNKNOWN;
+ continue;
+ }
+ if (ve==li) return BN_TRUE;
+ }
+ return r;
+ }
+};
+
+// Arithmetic Expression types
+
+class ArithmeticExpression : public Expression {
+ ArithmeticOperator* op;
+ boost::scoped_ptr<Expression> e1;
+ boost::scoped_ptr<Expression> e2;
+
+public:
+ ArithmeticExpression(ArithmeticOperator* o, Expression* e, Expression* e_):
+ op(o),
+ e1(e),
+ e2(e_)
+ {}
+
+ void repr(ostream& os) const {
+ os << "(" << *e1 << *op << *e2 << ")";
+ }
+
+ Value eval(const SelectorEnv& env) const {
+ return op->eval(*e1, *e2, env);
+ }
+};
+
+class UnaryArithExpression : public Expression {
+ UnaryArithmeticOperator* op;
+ boost::scoped_ptr<Expression> e1;
+
+public:
+ UnaryArithExpression(UnaryArithmeticOperator* o, Expression* e) :
+ op(o),
+ e1(e)
+ {}
+
+ void repr(ostream& os) const {
+ os << *op << "(" << *e1 << ")";
+ }
+
+ Value eval(const SelectorEnv& env) const {
+ return op->eval(*e1, env);
+ }
+};
+
+// Expression types...
+
+class Literal : public Expression {
+ const Value value;
+
+public:
+ template <typename T>
+ Literal(const T& v) :
+ value(v)
+ {}
+
+ void repr(ostream& os) const {
+ os << value;
+ }
+
+ Value eval(const SelectorEnv&) const {
+ return value;
+ }
+};
+
+class StringLiteral : public Expression {
+ const string value;
+
+public:
+ StringLiteral(const string& v) :
+ value(v)
+ {}
+
+ void repr(ostream& os) const {
+ os << "'" << value << "'";
+ }
+
+ Value eval(const SelectorEnv&) const {
+ return value;
+ }
+};
+
+class Identifier : public Expression {
+ string identifier;
+
+public:
+ Identifier(const string& i) :
+ identifier(i)
+ {}
+
+ void repr(ostream& os) const {
+ os << "I:" << identifier;
+ }
+
+ Value eval(const SelectorEnv& env) const {
+ return env.value(identifier);
+ }
+};
+
+////////////////////////////////////////////////////
+
+// Some operators...
+
+typedef bool BoolOp(const Value&, const Value&);
+
+BoolOrNone booleval(BoolOp* op, Expression& e1, Expression& e2, const SelectorEnv& env) {
+ const Value v1(e1.eval(env));
+ if (!unknown(v1)) {
+ const Value v2(e2.eval(env));
+ if (!unknown(v2)) {
+ return BoolOrNone(op(v1, v2));
+ }
+ }
+ return BN_UNKNOWN;
+}
+
+// "="
+class Eq : public ComparisonOperator {
+ void repr(ostream& os) const {
+ os << "=";
+ }
+
+ BoolOrNone eval(Expression& e1, Expression& e2, const SelectorEnv& env) const {
+ return booleval(&operator==, e1, e2, env);
+ }
+};
+
+// "<>"
+class Neq : public ComparisonOperator {
+ void repr(ostream& os) const {
+ os << "<>";
+ }
+
+ BoolOrNone eval(Expression& e1, Expression& e2, const SelectorEnv& env) const {
+ return booleval(&operator!=, e1, e2, env);
+ }
+};
+
+// "<"
+class Ls : public ComparisonOperator {
+ void repr(ostream& os) const {
+ os << "<";
+ }
+
+ BoolOrNone eval(Expression& e1, Expression& e2, const SelectorEnv& env) const {
+ return booleval(&operator<, e1, e2, env);
+ }
+};
+
+// ">"
+class Gr : public ComparisonOperator {
+ void repr(ostream& os) const {
+ os << ">";
+ }
+
+ BoolOrNone eval(Expression& e1, Expression& e2, const SelectorEnv& env) const {
+ return booleval(&operator>, e1, e2, env);
+ }
+};
+
+// "<="
+class Lseq : public ComparisonOperator {
+ void repr(ostream& os) const {
+ os << "<=";
+ }
+
+ BoolOrNone eval(Expression& e1, Expression& e2, const SelectorEnv& env) const {
+ return booleval(&operator<=, e1, e2, env);
+ }
+};
+
+// ">="
+class Greq : public ComparisonOperator {
+ void repr(ostream& os) const {
+ os << ">=";
+ }
+
+ BoolOrNone eval(Expression& e1, Expression& e2, const SelectorEnv& env) const {
+ return booleval(&operator>=, e1, e2, env);
+ }
+};
+
+// "IS NULL"
+class IsNull : public UnaryBooleanOperator {
+ void repr(ostream& os) const {
+ os << "IsNull";
+ }
+
+ BoolOrNone eval(Expression& e, const SelectorEnv& env) const {
+ return BoolOrNone(unknown(e.eval(env)));
+ }
+};
+
+// "IS NOT NULL"
+class IsNonNull : public UnaryBooleanOperator {
+ void repr(ostream& os) const {
+ os << "IsNonNull";
+ }
+
+ BoolOrNone eval(Expression& e, const SelectorEnv& env) const {
+ return BoolOrNone(!unknown(e.eval(env)));
+ }
+};
+
+// "NOT"
+class Not : public UnaryBooleanOperator {
+ void repr(ostream& os) const {
+ os << "NOT";
+ }
+
+ BoolOrNone eval(Expression& e, const SelectorEnv& env) const {
+ BoolOrNone bn = e.eval_bool(env);
+ if (bn==BN_UNKNOWN) return bn;
+ else return BoolOrNone(!bn);
+ }
+};
+
+class Negate : public UnaryArithmeticOperator {
+ void repr(ostream& os) const {
+ os << "-";
+ }
+
+ Value eval(Expression& e, const SelectorEnv& env) const {
+ return -e.eval(env);
+ }
+};
+
+class Add : public ArithmeticOperator {
+ void repr(ostream& os) const {
+ os << "+";
+ }
+
+ Value eval(Expression& e1, Expression& e2, const SelectorEnv& env) const {
+ return e1.eval(env)+e2.eval(env);
+ }
+};
+
+class Sub : public ArithmeticOperator {
+ void repr(ostream& os) const {
+ os << "-";
+ }
+
+ Value eval(Expression& e1, Expression& e2, const SelectorEnv& env) const {
+ return e1.eval(env)-e2.eval(env);
+ }
+};
+
+class Mult : public ArithmeticOperator {
+ void repr(ostream& os) const {
+ os << "*";
+ }
+
+ Value eval(Expression& e1, Expression& e2, const SelectorEnv& env) const {
+ return e1.eval(env)*e2.eval(env);
+ }
+};
+
+class Div : public ArithmeticOperator {
+ void repr(ostream& os) const {
+ os << "/";
+ }
+
+ Value eval(Expression& e1, Expression& e2, const SelectorEnv& env) const {
+ return e1.eval(env)/e2.eval(env);
+ }
+};
+
+Eq eqOp;
+Neq neqOp;
+Ls lsOp;
+Gr grOp;
+Lseq lseqOp;
+Greq greqOp;
+IsNull isNullOp;
+IsNonNull isNonNullOp;
+Not notOp;
+
+Negate negate;
+Add add;
+Sub sub;
+Mult mult;
+Div div;
+
+////////////////////////////////////////////////////
+
+// Top level parser
+class TopBoolExpression : public TopExpression {
+ boost::scoped_ptr<Expression> expression;
+
+ void repr(ostream& os) const {
+ expression->repr(os);
+ }
+
+ bool eval(const SelectorEnv& env) const {
+ BoolOrNone bn = expression->eval_bool(env);
+ if (bn==BN_TRUE) return true;
+ else return false;
+ }
+
+public:
+ TopBoolExpression(Expression* be) :
+ expression(be)
+ {}
+};
+
+void throwParseError(Tokeniser& tokeniser, const string& msg) {
+ tokeniser.returnTokens();
+ string error("Illegal selector: '");
+ error += tokeniser.nextToken().val;
+ error += "': ";
+ error += msg;
+ throw std::range_error(error);
+}
+
+class Parse {
+
+friend TopExpression* TopExpression::parse(const string&);
+
+string error;
+
+Expression* selectorExpression(Tokeniser& tokeniser)
+{
+ if ( tokeniser.nextToken().type==T_EOS ) {
+ return (new Literal(true));
+ }
+ tokeniser.returnTokens();
+ std::auto_ptr<Expression> e(orExpression(tokeniser));
+ return e.release();
+}
+
+Expression* orExpression(Tokeniser& tokeniser)
+{
+ std::auto_ptr<Expression> e(andExpression(tokeniser));
+ if (!e.get()) return 0;
+ while ( tokeniser.nextToken().type==T_OR ) {
+ std::auto_ptr<Expression> e1(e);
+ std::auto_ptr<Expression> e2(andExpression(tokeniser));
+ if (!e2.get()) return 0;
+ e.reset(new OrExpression(e1.release(), e2.release()));
+ }
+ tokeniser.returnTokens();
+ return e.release();
+}
+
+Expression* andExpression(Tokeniser& tokeniser)
+{
+ std::auto_ptr<Expression> e(comparisonExpression(tokeniser));
+ if (!e.get()) return 0;
+ while ( tokeniser.nextToken().type==T_AND ) {
+ std::auto_ptr<Expression> e1(e);
+ std::auto_ptr<Expression> e2(comparisonExpression(tokeniser));
+ if (!e2.get()) return 0;
+ e.reset(new AndExpression(e1.release(), e2.release()));
+ }
+ tokeniser.returnTokens();
+ return e.release();
+}
+
+BoolExpression* specialComparisons(Tokeniser& tokeniser, std::auto_ptr<Expression> e1) {
+ switch (tokeniser.nextToken().type) {
+ case T_LIKE: {
+ const Token t = tokeniser.nextToken();
+ if ( t.type!=T_STRING ) {
+ error = "expected string after LIKE";
+ return 0;
+ }
+ // Check for "ESCAPE"
+ if ( tokeniser.nextToken().type==T_ESCAPE ) {
+ const Token e = tokeniser.nextToken();
+ if ( e.type!=T_STRING ) {
+ error = "expected string after ESCAPE";
+ return 0;
+ }
+ if (e.val.size()>1) {
+ throwParseError(tokeniser, "single character string required after ESCAPE");
+ }
+ if (e.val=="%" || e.val=="_") {
+ throwParseError(tokeniser, "'%' and '_' are not allowed as ESCAPE characters");
+ }
+ return new LikeExpression(e1.release(), t.val, e.val);
+ } else {
+ tokeniser.returnTokens();
+ return new LikeExpression(e1.release(), t.val);
+ }
+ }
+ case T_BETWEEN: {
+ std::auto_ptr<Expression> lower(addExpression(tokeniser));
+ if ( !lower.get() ) return 0;
+ if ( tokeniser.nextToken().type!=T_AND ) {
+ error = "expected AND after BETWEEN";
+ return 0;
+ }
+ std::auto_ptr<Expression> upper(addExpression(tokeniser));
+ if ( !upper.get() ) return 0;
+ return new BetweenExpression(e1.release(), lower.release(), upper.release());
+ }
+ case T_IN: {
+ if ( tokeniser.nextToken().type!=T_LPAREN ) {
+ error = "missing '(' after IN";
+ return 0;
+ }
+ boost::ptr_vector<Expression> list;
+ do {
+ std::auto_ptr<Expression> e(addExpression(tokeniser));
+ if (!e.get()) return 0;
+ list.push_back(e.release());
+ } while (tokeniser.nextToken().type==T_COMMA);
+ tokeniser.returnTokens();
+ if ( tokeniser.nextToken().type!=T_RPAREN ) {
+ error = "missing ',' or ')' after IN";
+ return 0;
+ }
+ return new InExpression(e1.release(), list);
+ }
+ default:
+ error = "expected LIKE, IN or BETWEEN";
+ return 0;
+ }
+}
+
+Expression* comparisonExpression(Tokeniser& tokeniser)
+{
+ const Token t = tokeniser.nextToken();
+ if ( t.type==T_NOT ) {
+ std::auto_ptr<Expression> e(comparisonExpression(tokeniser));
+ if (!e.get()) return 0;
+ return new UnaryBooleanExpression(&notOp, e.release());
+ }
+
+ tokeniser.returnTokens();
+ std::auto_ptr<Expression> e1(addExpression(tokeniser));
+ if (!e1.get()) return 0;
+
+ switch (tokeniser.nextToken().type) {
+ // Check for "IS NULL" and "IS NOT NULL"
+ case T_IS:
+ // The rest must be T_NULL or T_NOT, T_NULL
+ switch (tokeniser.nextToken().type) {
+ case T_NULL:
+ return new UnaryBooleanExpression(&isNullOp, e1.release());
+ case T_NOT:
+ if ( tokeniser.nextToken().type == T_NULL)
+ return new UnaryBooleanExpression(&isNonNullOp, e1.release());
+ default:
+ error = "expected NULL or NOT NULL after IS";
+ return 0;
+ }
+ case T_NOT: {
+ std::auto_ptr<BoolExpression> e(specialComparisons(tokeniser, e1));
+ if (!e.get()) return 0;
+ return new UnaryBooleanExpression(&notOp, e.release());
+ }
+ case T_BETWEEN:
+ case T_LIKE:
+ case T_IN: {
+ tokeniser.returnTokens();
+ return specialComparisons(tokeniser, e1);
+ }
+ default:
+ break;
+ }
+ tokeniser.returnTokens();
+
+ ComparisonOperator* op;
+ switch (tokeniser.nextToken().type) {
+ case T_EQUAL: op = &eqOp; break;
+ case T_NEQ: op = &neqOp; break;
+ case T_LESS: op = &lsOp; break;
+ case T_GRT: op = &grOp; break;
+ case T_LSEQ: op = &lseqOp; break;
+ case T_GREQ: op = &greqOp; break;
+ default:
+ tokeniser.returnTokens();
+ return e1.release();
+ }
+
+ std::auto_ptr<Expression> e2(addExpression(tokeniser));
+ if (!e2.get()) return 0;
+
+ return new ComparisonExpression(op, e1.release(), e2.release());
+}
+
+Expression* addExpression(Tokeniser& tokeniser)
+{
+ std::auto_ptr<Expression> e(multiplyExpression(tokeniser));
+ if (!e.get()) return 0;
+
+ Token t = tokeniser.nextToken();
+ while (t.type==T_PLUS || t.type==T_MINUS ) {
+ ArithmeticOperator* op;
+ switch (t.type) {
+ case T_PLUS: op = &add; break;
+ case T_MINUS: op = &sub; break;
+ default:
+ error = "internal error processing binary + or -";
+ return 0;
+ }
+ std::auto_ptr<Expression> e1(e);
+ std::auto_ptr<Expression> e2(multiplyExpression(tokeniser));
+ if (!e2.get()) return 0;
+ e.reset(new ArithmeticExpression(op, e1.release(), e2.release()));
+ t = tokeniser.nextToken();
+ }
+
+ tokeniser.returnTokens();
+ return e.release();
+}
+
+Expression* multiplyExpression(Tokeniser& tokeniser)
+{
+ std::auto_ptr<Expression> e(unaryArithExpression(tokeniser));
+ if (!e.get()) return 0;
+
+ Token t = tokeniser.nextToken();
+ while (t.type==T_MULT || t.type==T_DIV ) {
+ ArithmeticOperator* op;
+ switch (t.type) {
+ case T_MULT: op = &mult; break;
+ case T_DIV: op = &div; break;
+ default:
+ error = "internal error processing * or /";
+ return 0;
+ }
+ std::auto_ptr<Expression> e1(e);
+ std::auto_ptr<Expression> e2(unaryArithExpression(tokeniser));
+ if (!e2.get()) return 0;
+ e.reset(new ArithmeticExpression(op, e1.release(), e2.release()));
+ t = tokeniser.nextToken();
+ }
+
+ tokeniser.returnTokens();
+ return e.release();
+}
+
+Expression* unaryArithExpression(Tokeniser& tokeniser)
+{
+ const Token t = tokeniser.nextToken();
+ switch (t.type) {
+ case T_LPAREN: {
+ std::auto_ptr<Expression> e(orExpression(tokeniser));
+ if (!e.get()) return 0;
+ if ( tokeniser.nextToken().type!=T_RPAREN ) {
+ error = "missing ')' after '('";
+ return 0;
+ }
+ return e.release();
+ }
+ case T_PLUS:
+ break; // Unary + is no op
+ case T_MINUS: {
+ std::auto_ptr<Expression> e(unaryArithExpression(tokeniser));
+ if (!e.get()) return 0;
+ return new UnaryArithExpression(&negate, e.release());
+ }
+ default:
+ tokeniser.returnTokens();
+ break;
+ }
+
+ std::auto_ptr<Expression> e(primaryExpression(tokeniser));
+ return e.release();
+}
+
+Expression* primaryExpression(Tokeniser& tokeniser)
+{
+ const Token& t = tokeniser.nextToken();
+ switch (t.type) {
+ case T_IDENTIFIER:
+ return new Identifier(t.val);
+ case T_STRING:
+ return new StringLiteral(t.val);
+ case T_FALSE:
+ return new Literal(false);
+ case T_TRUE:
+ return new Literal(true);
+ case T_NUMERIC_EXACT:
+ return new Literal(boost::lexical_cast<int64_t>(t.val));
+ case T_NUMERIC_APPROX:
+ return new Literal(boost::lexical_cast<double>(t.val));
+ default:
+ error = "expected literal or identifier";
+ return 0;
+ }
+}
+
+};
+
+TopExpression* TopExpression::parse(const string& exp)
+{
+ string::const_iterator s = exp.begin();
+ string::const_iterator e = exp.end();
+ Tokeniser tokeniser(s,e);
+ Parse parse;
+ std::auto_ptr<Expression> b(parse.selectorExpression(tokeniser));
+ if (!b.get()) {
+ throwParseError(tokeniser, parse.error);
+ }
+ if (tokeniser.nextToken().type != T_EOS) {
+ throwParseError(tokeniser, "extra input");
+ }
+ return new TopBoolExpression(b.release());
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/broker/SelectorExpression.h b/qpid/cpp/src/qpid/broker/SelectorExpression.h
new file mode 100644
index 0000000000..158f086287
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SelectorExpression.h
@@ -0,0 +1,44 @@
+#ifndef QPID_BROKER_SELECTOREXPRESSION_H
+#define QPID_BROKER_SELECTOREXPRESSION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <iosfwd>
+#include <string>
+
+namespace qpid {
+namespace broker {
+
+class SelectorEnv;
+
+class TopExpression {
+public:
+ virtual ~TopExpression() {};
+ virtual void repr(std::ostream&) const = 0;
+ virtual bool eval(const SelectorEnv&) const = 0;
+
+ static TopExpression* parse(const std::string& exp);
+};
+
+}}
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/SelectorToken.cpp b/qpid/cpp/src/qpid/broker/SelectorToken.cpp
new file mode 100644
index 0000000000..d69267b2e5
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SelectorToken.cpp
@@ -0,0 +1,298 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/SelectorToken.h"
+
+#include <string>
+#include <algorithm>
+#include <iostream>
+#include <cassert>
+#include <cctype>
+
+namespace qpid {
+namespace broker {
+
+// Tokeniserss always take string const_iterators to mark the beginning and end of the string being tokenised
+// if the tokenise is successful then the start iterator is advanced, if the tokenise fails then the start
+// iterator is unchanged.
+
+std::ostream& operator<<(std::ostream& os, const Token& t)
+{
+ os << "T<" << t.type << ", " << t.val << ">";
+ return os;
+}
+
+TokenException::TokenException(const std::string& msg) :
+ range_error(msg)
+{}
+
+// Lexically, reserved words are a subset of identifiers
+// so we parse an identifier first then check if it is a reserved word and
+// convert it if it is a reserved word
+namespace {
+
+struct RWEntry {
+ const char* word;
+ TokenType type;
+};
+
+inline bool caseless(const char* s1, const char* s2)
+{
+ do {
+ char ls1 = std::tolower(*s1);
+ char ls2 = std::tolower(*s2);
+ if (ls1<ls2)
+ return true;
+ else if (ls1>ls2)
+ return false;
+ } while ( *s1++ && *s2++ );
+ // Equal
+ return false;
+}
+
+inline bool operator<(const RWEntry& lhs, const RWEntry& rhs) {
+ return caseless(lhs.word, rhs.word);
+}
+
+}
+
+bool tokeniseReservedWord(Token& tok)
+{
+ // This must be sorted!!
+ static const RWEntry reserved[] = {
+ {"and", T_AND},
+ {"between", T_BETWEEN},
+ {"escape", T_ESCAPE},
+ {"false", T_FALSE},
+ {"in", T_IN},
+ {"is", T_IS},
+ {"like", T_LIKE},
+ {"not", T_NOT},
+ {"null", T_NULL},
+ {"or", T_OR},
+ {"true", T_TRUE}
+ };
+
+ const int reserved_size = sizeof(reserved)/sizeof(RWEntry);
+
+ if ( tok.type != T_IDENTIFIER ) return false;
+
+ RWEntry rw;
+ rw.word = tok.val.c_str();
+ std::pair<const RWEntry*, const RWEntry*> entry = std::equal_range(&reserved[0], &reserved[reserved_size], rw);
+
+ if ( entry.first==entry.second ) return false;
+
+ tok.type = entry.first->type;
+ return true;
+}
+
+// parsing strings is complicated by the need to allow embedded quotes by doubling the quote character
+bool processString(std::string::const_iterator& s, std::string::const_iterator& e, char quoteChar, TokenType type, Token& tok)
+{
+ // We only get here once the tokeniser recognises the initial quote for a string
+ // so we don't need to check for it again.
+ std::string::const_iterator q = std::find(s+1, e, quoteChar);
+ if ( q==e ) return false;
+
+ std::string content(s+1, q);
+ ++q;
+
+ while ( q!=e && *q==quoteChar ) {
+ std::string::const_iterator p = q;
+ q = std::find(p+1, e, quoteChar);
+ if ( q==e ) return false;
+ content += std::string(p, q);
+ ++q;
+ }
+
+ tok = Token(type, s, content);
+ s = q;
+ return true;
+}
+
+inline bool isIdentifierStart(char c)
+{
+ return std::isalpha(c) || c=='_' || c=='$';
+}
+
+inline bool isIdentifierPart(char c)
+{
+ return std::isalnum(c) || c=='_' || c=='$' || c=='.';
+}
+
+static const std::string END("<END>");
+bool tokenise(std::string::const_iterator& s, std::string::const_iterator& e, Token& tok)
+{
+ std::string::const_iterator t = s;
+
+ // Hand constructed state machine recogniser
+ enum {
+ START,
+ REJECT,
+ IDENTIFIER,
+ DIGIT,
+ DECIMAL_START,
+ DECIMAL,
+ EXPONENT_SIGN,
+ EXPONENT_START,
+ EXPONENT,
+ ACCEPT_IDENTIFIER,
+ ACCEPT_INC,
+ ACCEPT_NOINC
+ } state = START;
+
+ TokenType tokType = T_EOS;
+ while (true)
+ switch (state) {
+ case START:
+ if (t==e) {tok = Token(T_EOS, s, END); return true;}
+ else if (std::isspace(*t)) {++t; ++s; continue;}
+ else switch (*t) {
+ case '(': tokType = T_LPAREN; state = ACCEPT_INC; continue;
+ case ')': tokType = T_RPAREN; state = ACCEPT_INC; continue;
+ case ',': tokType = T_COMMA; state = ACCEPT_INC; continue;
+ case '+': tokType = T_PLUS; state = ACCEPT_INC; continue;
+ case '-': tokType = T_MINUS; state = ACCEPT_INC; continue;
+ case '*': tokType = T_MULT; state = ACCEPT_INC; continue;
+ case '/': tokType = T_DIV; state = ACCEPT_INC; continue;
+ case '=': tokType = T_EQUAL; state = ACCEPT_INC; continue;
+ case '<':
+ ++t;
+ if (t==e || (*t!='>' && *t!='='))
+ {tokType = T_LESS; state = ACCEPT_NOINC; continue; }
+ else
+ {tokType = (*t=='>') ? T_NEQ : T_LSEQ; state = ACCEPT_INC; continue; }
+ case '>':
+ ++t;
+ if (t==e || *t!='=')
+ {tokType = T_GRT; state = ACCEPT_NOINC; continue;}
+ else
+ {tokType = T_GREQ; state = ACCEPT_INC; continue;}
+ default:
+ break;
+ }
+ if (isIdentifierStart(*t)) {++t; state = IDENTIFIER;}
+ else if (*t=='\'') {return processString(s, e, '\'', T_STRING, tok);}
+ else if (*t=='\"') {return processString(s, e, '\"', T_IDENTIFIER, tok);}
+ else if (std::isdigit(*t)) {++t; state = DIGIT;}
+ else if (*t=='.') {++t; state = DECIMAL_START;}
+ else state = REJECT;
+ continue;
+ case IDENTIFIER:
+ if (t==e) {state = ACCEPT_IDENTIFIER;}
+ else if (isIdentifierPart(*t)) {++t; state = IDENTIFIER;}
+ else state = ACCEPT_IDENTIFIER;
+ continue;
+ case DECIMAL_START:
+ if (t==e) {state = REJECT;}
+ else if (std::isdigit(*t)) {++t; state = DECIMAL;}
+ else state = REJECT;
+ continue;
+ case EXPONENT_SIGN:
+ if (t==e) {state = REJECT;}
+ else if (*t=='-' || *t=='+') {++t; state = EXPONENT_START;}
+ else if (std::isdigit(*t)) {++t; state = EXPONENT;}
+ else state = REJECT;
+ continue;
+ case EXPONENT_START:
+ if (t==e) {state = REJECT;}
+ else if (std::isdigit(*t)) {++t; state = EXPONENT;}
+ else state = REJECT;
+ continue;
+ case DIGIT:
+ if (t==e) {tokType = T_NUMERIC_EXACT; state = ACCEPT_NOINC;}
+ else if (std::isdigit(*t)) {++t; state = DIGIT;}
+ else if (*t=='.') {++t; state = DECIMAL;}
+ else if (*t=='e' || *t=='E') {++t; state = EXPONENT_SIGN;}
+ else {tokType = T_NUMERIC_EXACT; state = ACCEPT_NOINC;}
+ continue;
+ case DECIMAL:
+ if (t==e) {tokType = T_NUMERIC_APPROX; state = ACCEPT_NOINC;}
+ else if (std::isdigit(*t)) {++t; state = DECIMAL;}
+ else if (*t=='e' || *t=='E') {++t; state = EXPONENT_SIGN;}
+ else {tokType = T_NUMERIC_APPROX; state = ACCEPT_NOINC;}
+ continue;
+ case EXPONENT:
+ if (t==e) {tokType = T_NUMERIC_APPROX; state = ACCEPT_NOINC;}
+ else if (std::isdigit(*t)) {++t; state = EXPONENT;}
+ else {tokType = T_NUMERIC_APPROX; state = ACCEPT_NOINC;}
+ continue;
+ case ACCEPT_INC:
+ ++t;
+ case ACCEPT_NOINC:
+ tok = Token(tokType, s, t);
+ s = t;
+ return true;
+ case ACCEPT_IDENTIFIER:
+ tok = Token(T_IDENTIFIER, s, t);
+ s = t;
+ tokeniseReservedWord(tok);
+ return true;
+ case REJECT:
+ return false;
+ };
+}
+
+Tokeniser::Tokeniser(const std::string::const_iterator& s, const std::string::const_iterator& e) :
+ tokp(0),
+ inStart(s),
+ inp(s),
+ inEnd(e)
+{
+}
+
+/**
+ * Skip any whitespace then look for a token, throwing an exception if no valid token
+ * is found.
+ *
+ * Advance the string iterator past the parsed token on success. On failure the string iterator is
+ * in an undefined location.
+ */
+const Token& Tokeniser::nextToken()
+{
+ if ( tokens.size()>tokp ) return tokens[tokp++];
+
+ // Don't extend stream of tokens further than the end of stream;
+ if ( tokp>0 && tokens[tokp-1].type==T_EOS ) return tokens[tokp-1];
+
+ tokens.push_back(Token());
+ Token& tok = tokens[tokp++];
+
+ if (tokenise(inp, inEnd, tok)) return tok;
+
+ throw TokenException("Found illegal character");
+}
+
+void Tokeniser::returnTokens(unsigned int n)
+{
+ assert( n<=tokp );
+ tokp-=n;
+}
+
+std::string Tokeniser::remaining()
+{
+ Token& currentTok = tokens[tokp];
+ return std::string(currentTok.tokenStart, inEnd);
+}
+
+
+}}
diff --git a/qpid/cpp/src/qpid/broker/SelectorToken.h b/qpid/cpp/src/qpid/broker/SelectorToken.h
new file mode 100644
index 0000000000..bd60b69dce
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SelectorToken.h
@@ -0,0 +1,126 @@
+#ifndef QPID_BROKER_SELECTORTOKEN_H
+#define QPID_BROKER_SELECTORTOKEN_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/BrokerImportExport.h"
+
+#include <iosfwd>
+#include <string>
+#include <stdexcept>
+#include <vector>
+
+namespace qpid {
+namespace broker {
+
+typedef enum {
+ T_EOS,
+ T_NULL,
+ T_TRUE,
+ T_FALSE,
+ T_NOT,
+ T_AND,
+ T_OR,
+ T_IN,
+ T_IS,
+ T_BETWEEN,
+ T_LIKE,
+ T_ESCAPE,
+ T_IDENTIFIER,
+ T_STRING,
+ T_NUMERIC_EXACT,
+ T_NUMERIC_APPROX,
+ T_LPAREN,
+ T_RPAREN,
+ T_COMMA,
+ T_PLUS,
+ T_MINUS,
+ T_MULT,
+ T_DIV,
+ T_EQUAL,
+ T_NEQ,
+ T_LESS,
+ T_GRT,
+ T_LSEQ,
+ T_GREQ
+} TokenType;
+
+struct Token {
+ TokenType type;
+ std::string val;
+ std::string::const_iterator tokenStart;
+
+ Token()
+ {}
+
+ Token(TokenType t, const std::string& v) :
+ type(t),
+ val(v)
+ {}
+
+ Token(TokenType t, const std::string::const_iterator& s, const std::string& v) :
+ type(t),
+ val(v),
+ tokenStart(s)
+ {}
+
+ Token(TokenType t, const std::string::const_iterator& s, const std::string::const_iterator& e) :
+ type(t),
+ val(std::string(s,e)),
+ tokenStart(s)
+ {}
+
+ bool operator==(const Token& r) const
+ {
+ return
+ (type == T_EOS && r.type == T_EOS) ||
+ (type == r.type && val == r.val);
+ }
+};
+
+QPID_BROKER_EXTERN std::ostream& operator<<(std::ostream& os, const Token& t);
+
+class TokenException : public std::range_error {
+public:
+ TokenException(const std::string&);
+};
+
+QPID_BROKER_EXTERN bool tokenise(std::string::const_iterator& s, std::string::const_iterator& e, Token& tok);
+
+class Tokeniser {
+ std::vector<Token> tokens;
+ unsigned int tokp;
+
+ std::string::const_iterator inStart;
+ std::string::const_iterator inp;
+ std::string::const_iterator inEnd;
+
+public:
+ QPID_BROKER_EXTERN Tokeniser(const std::string::const_iterator& s, const std::string::const_iterator& e);
+ QPID_BROKER_EXTERN void returnTokens(unsigned int n = 1);
+ QPID_BROKER_EXTERN const Token& nextToken();
+ QPID_BROKER_EXTERN std::string remaining();
+};
+
+}}
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/SelectorValue.cpp b/qpid/cpp/src/qpid/broker/SelectorValue.cpp
new file mode 100644
index 0000000000..0855fa91d0
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SelectorValue.cpp
@@ -0,0 +1,212 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/SelectorValue.h"
+
+#include <ostream>
+#include <boost/scoped_ptr.hpp>
+
+using std::ostream;
+
+namespace qpid {
+namespace broker {
+
+ostream& operator<<(ostream& os, const Value& v)
+{
+ switch (v.type) {
+ case Value::T_UNKNOWN: os << "UNKNOWN"; break;
+ case Value::T_BOOL: os << "BOOL:" << std::boolalpha << v.b; break;
+ case Value::T_EXACT: os << "EXACT:" << v.i; break;
+ case Value::T_INEXACT: os << "APPROX:" << v.x; break;
+ case Value::T_STRING: os << "STRING:'" << *v.s << "'"; break;
+ };
+ return os;
+}
+
+class NumericPairBase {
+public:
+ virtual ~NumericPairBase() {}
+ virtual Value add() = 0;
+ virtual Value sub() = 0;
+ virtual Value mul() = 0;
+ virtual Value div() = 0;
+
+ virtual bool eq() = 0;
+ virtual bool ne() = 0;
+ virtual bool ls() = 0;
+ virtual bool gr() = 0;
+ virtual bool le() = 0;
+ virtual bool ge() = 0;
+};
+
+template <typename T>
+class NumericPair : public NumericPairBase {
+ const T n1;
+ const T n2;
+
+ Value add() { return n1+n2; }
+ Value sub() { return n1-n2; }
+ Value mul() { return n1*n2; }
+ Value div() { return n1/n2; }
+
+ bool eq() { return n1==n2; }
+ bool ne() { return n1!=n2; }
+ bool ls() { return n1<n2; }
+ bool gr() { return n1>n2; }
+ bool le() { return n1<=n2; }
+ bool ge() { return n1>=n2; }
+
+public:
+ NumericPair(T x, T y) :
+ n1(x),
+ n2(y)
+ {}
+};
+
+NumericPairBase* promoteNumeric(const Value& v1, const Value& v2)
+{
+ if (!numeric(v1) || !numeric(v2)) return 0;
+
+ if (v1.type != v2.type) {
+ switch (v1.type) {
+ case Value::T_INEXACT: return new NumericPair<double>(v1.x, v2.i);
+ case Value::T_EXACT: return new NumericPair<double>(v1.i, v2.x);
+ default:
+ assert(false);
+ }
+ } else {
+ switch (v1.type) {
+ case Value::T_INEXACT: return new NumericPair<double>(v1.x, v2.x);
+ case Value::T_EXACT: return new NumericPair<int64_t>(v1.i, v2.i);
+ default:
+ assert(false);
+ }
+ }
+ // Can never get here - but this stops a warning
+ return 0;
+}
+
+bool operator==(const Value& v1, const Value& v2)
+{
+ boost::scoped_ptr<NumericPairBase> nbp(promoteNumeric(v1, v2));
+ if (nbp) return nbp->eq();
+
+ if (v1.type != v2.type) return false;
+ switch (v1.type) {
+ case Value::T_BOOL: return v1.b == v2.b;
+ case Value::T_STRING: return *v1.s == *v2.s;
+ default: // Cannot ever get here
+ return false;
+ }
+}
+
+bool operator!=(const Value& v1, const Value& v2)
+{
+ boost::scoped_ptr<NumericPairBase> nbp(promoteNumeric(v1, v2));
+ if (nbp) return nbp->ne();
+
+ if (v1.type != v2.type) return false;
+ switch (v1.type) {
+ case Value::T_BOOL: return v1.b != v2.b;
+ case Value::T_STRING: return *v1.s != *v2.s;
+ default: // Cannot ever get here
+ return false;
+ }
+}
+
+bool operator<(const Value& v1, const Value& v2)
+{
+ boost::scoped_ptr<NumericPairBase> nbp(promoteNumeric(v1, v2));
+ if (nbp) return nbp->ls();
+
+ return false;
+}
+
+bool operator>(const Value& v1, const Value& v2)
+{
+ boost::scoped_ptr<NumericPairBase> nbp(promoteNumeric(v1, v2));
+ if (nbp) return nbp->gr();
+
+ return false;
+}
+
+bool operator<=(const Value& v1, const Value& v2)
+{
+ boost::scoped_ptr<NumericPairBase> nbp(promoteNumeric(v1, v2));
+ if (nbp) return nbp->le();
+
+ return false;
+}
+
+bool operator>=(const Value& v1, const Value& v2)
+{
+ boost::scoped_ptr<NumericPairBase> nbp(promoteNumeric(v1, v2));
+ if (nbp) return nbp->ge();
+
+ return false;
+}
+
+Value operator+(const Value& v1, const Value& v2)
+{
+ boost::scoped_ptr<NumericPairBase> nbp(promoteNumeric(v1, v2));
+ if (nbp) return nbp->add();
+
+ return Value();
+}
+
+Value operator-(const Value& v1, const Value& v2)
+{
+ boost::scoped_ptr<NumericPairBase> nbp(promoteNumeric(v1, v2));
+ if (nbp) return nbp->sub();
+
+ return Value();
+}
+
+Value operator*(const Value& v1, const Value& v2)
+{
+ boost::scoped_ptr<NumericPairBase> nbp(promoteNumeric(v1, v2));
+ if (nbp) return nbp->mul();
+
+ return Value();
+}
+
+Value operator/(const Value& v1, const Value& v2)
+{
+ boost::scoped_ptr<NumericPairBase> nbp(promoteNumeric(v1, v2));
+ if (nbp) return nbp->div();
+
+ return Value();
+}
+
+Value operator-(const Value& v)
+{
+ switch (v.type) {
+ case Value::T_EXACT:
+ return -v.i;
+ case Value::T_INEXACT:
+ return -v.x;
+ default:
+ break;
+ }
+ return Value();
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/broker/SelectorValue.h b/qpid/cpp/src/qpid/broker/SelectorValue.h
new file mode 100644
index 0000000000..3a731fd000
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SelectorValue.h
@@ -0,0 +1,121 @@
+#ifndef QPID_BROKER_SELECTORVALUE_H
+#define QPID_BROKER_SELECTORVALUE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/IntegerTypes.h"
+
+#include <iosfwd>
+#include <string>
+
+namespace qpid {
+namespace broker {
+
+enum BoolOrNone {
+ BN_FALSE = false,
+ BN_TRUE = true,
+ BN_UNKNOWN
+};
+
+// The user of the Value class for strings must ensure that
+// the string has a lifetime longer than the string used and
+// is responsible for managing its lifetime.
+class Value {
+public:
+ union {
+ bool b;
+ int64_t i;
+ double x;
+ const std::string* s;
+ };
+ enum {
+ T_UNKNOWN,
+ T_BOOL,
+ T_STRING,
+ T_EXACT,
+ T_INEXACT
+ } type;
+
+ // Default copy contructor
+ // Default assignment operator
+ // Default destructor
+ Value() :
+ type(T_UNKNOWN)
+ {}
+
+ Value(const std::string& s0) :
+ s(&s0),
+ type(T_STRING)
+ {}
+
+ Value(const int64_t i0) :
+ i(i0),
+ type(T_EXACT)
+ {}
+
+ Value(const int32_t i0) :
+ i(i0),
+ type(T_EXACT)
+ {}
+
+ Value(const double x0) :
+ x(x0),
+ type(T_INEXACT)
+ {}
+
+ Value(bool b0) :
+ b(b0),
+ type(T_BOOL)
+ {}
+
+ Value(BoolOrNone bn) :
+ b(bn),
+ type(bn==BN_UNKNOWN ? T_UNKNOWN : T_BOOL)
+ {}
+};
+
+inline bool unknown(const Value& v) {
+ return v.type == Value::T_UNKNOWN;
+}
+
+inline bool numeric(const Value& v) {
+ return v.type == Value::T_EXACT || v.type == Value::T_INEXACT;
+}
+
+std::ostream& operator<<(std::ostream& os, const Value& v);
+
+bool operator==(const Value&, const Value&);
+bool operator!=(const Value&, const Value&);
+bool operator<(const Value&, const Value&);
+bool operator>(const Value&, const Value&);
+bool operator<=(const Value&, const Value&);
+bool operator>=(const Value&, const Value&);
+
+Value operator+(const Value&, const Value&);
+Value operator-(const Value&, const Value&);
+Value operator*(const Value&, const Value&);
+Value operator/(const Value&, const Value&);
+Value operator-(const Value&);
+
+}}
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/SelfDestructQueue.cpp b/qpid/cpp/src/qpid/broker/SelfDestructQueue.cpp
new file mode 100644
index 0000000000..f8b26a62ad
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SelfDestructQueue.cpp
@@ -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.
+ *
+ */
+#include "SelfDestructQueue.h"
+#include "AclModule.h"
+#include "Broker.h"
+#include "QueueDepth.h"
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace broker {
+SelfDestructQueue::SelfDestructQueue(const std::string& n, const QueueSettings& s, MessageStore* const ms, management::Manageable* p, Broker* b) : Queue(n, s, ms, p, b)
+{
+ QPID_LOG_CAT(debug, model, "Self-destruct queue created: " << name);
+}
+bool SelfDestructQueue::checkDepth(const QueueDepth& increment, const Message&)
+{
+ if (settings.maxDepth && (settings.maxDepth - current < increment)) {
+ broker->getQueues().destroy(name);
+ if (broker->getAcl())
+ broker->getAcl()->recordDestroyQueue(name);
+ QPID_LOG_CAT(debug, model, "Queue " << name << " deleted itself due to reaching limit: " << current << " (policy is " << settings.maxDepth << ")");
+ destroyed();
+ }
+ current += increment;
+ return true;
+}
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/SelfDestructQueue.h b/qpid/cpp/src/qpid/broker/SelfDestructQueue.h
new file mode 100644
index 0000000000..110eb1dcf7
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SelfDestructQueue.h
@@ -0,0 +1,45 @@
+#ifndef QPID_BROKER_SELFDESTRUCTQUEUE_H
+#define QPID_BROKER_SELFDESTRUCTQUEUE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/Queue.h"
+
+namespace qpid {
+namespace broker {
+
+/**
+ * Deletes itself when breaching specified maximum depth (useful as
+ * subscription queue for consumers that should be ejecetd from topic
+ * when they can't keep up).
+ */
+class SelfDestructQueue : public Queue
+{
+ public:
+ public:
+ SelfDestructQueue(const std::string&, const QueueSettings&, MessageStore* const, management::Manageable*, Broker*);
+ bool checkDepth(const QueueDepth& increment, const Message&);
+ private:
+ private:
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_SELFDESTRUCTQUEUE_H*/
diff --git a/qpid/cpp/src/qpid/broker/SemanticState.cpp b/qpid/cpp/src/qpid/broker/SemanticState.cpp
new file mode 100644
index 0000000000..1b3823c845
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SemanticState.cpp
@@ -0,0 +1,896 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/SessionState.h"
+
+#include "qpid/broker/AsyncCommandCallback.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/amqp_0_10/Connection.h"
+#include "qpid/broker/DeliverableMessage.h"
+#include "qpid/broker/DtxAck.h"
+#include "qpid/broker/DtxTimeout.h"
+#include "qpid/broker/Message.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/Selector.h"
+#include "qpid/broker/SessionContext.h"
+#include "qpid/broker/SessionOutputException.h"
+#include "qpid/broker/TransactionObserver.h"
+#include "qpid/broker/TxAccept.h"
+#include "qpid/broker/amqp_0_10/MessageTransfer.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qpid/framing/SequenceSet.h"
+#include "qpid/framing/IsInSequenceSet.h"
+#include "qpid/log/Statement.h"
+#include "qpid/management/ManagementAgent.h"
+#include "qpid/ptr_map.h"
+#include "qpid/broker/AclModule.h"
+#include "qpid/broker/FedOps.h"
+#include "qpid/sys/ConnectionOutputHandler.h"
+
+#include <boost/bind.hpp>
+#include <boost/format.hpp>
+#include <boost/tuple/tuple_comparison.hpp>
+
+#include <iostream>
+#include <sstream>
+#include <algorithm>
+#include <functional>
+
+#include <assert.h>
+
+namespace {
+const std::string X_SCOPE("x-scope");
+const std::string SESSION("session");
+}
+
+namespace qpid {
+namespace broker {
+
+using namespace std;
+using boost::intrusive_ptr;
+using boost::shared_ptr;
+using boost::bind;
+using namespace qpid::broker;
+using namespace qpid::framing;
+using namespace qpid::sys;
+using qpid::ptr_map_ptr;
+using qpid::management::ManagementAgent;
+using qpid::management::ManagementObject;
+using qpid::management::Manageable;
+using qpid::management::Args;
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+SemanticState::SemanticState(SessionState& ss)
+ : session(ss),
+ tagGenerator("sgen"),
+ dtxSelected(false),
+ authMsg(getSession().getBroker().isAuthenticating() && !getSession().getConnection().isFederationLink()),
+ userID(getSession().getConnection().getUserId()),
+ closeComplete(false),
+ connectionId(getSession().getConnection().getMgmtId())
+{}
+
+SemanticState::~SemanticState() {
+ closed();
+}
+
+void SemanticState::closed() {
+ if (!closeComplete) {
+ //prevent requeued messages being redelivered to consumers
+ for (ConsumerImplMap::iterator i = consumers.begin(); i != consumers.end(); i++) {
+ disable(i->second);
+ }
+ if (dtxBuffer.get()) {
+ dtxBuffer->fail();
+ }
+ unbindSessionBindings();
+ requeue();
+
+ //now unsubscribe, which may trigger queue deletion and thus
+ //needs to occur after the requeueing of unacked messages
+ for (ConsumerImplMap::iterator i = consumers.begin(); i != consumers.end(); i++) {
+ cancel(i->second);
+ }
+ closeComplete = true;
+ if (txBuffer) txBuffer->rollback();
+ }
+}
+
+bool SemanticState::exists(const string& consumerTag){
+ return consumers.find(consumerTag) != consumers.end();
+}
+
+namespace {
+ const std::string SEPARATOR("::");
+}
+
+void SemanticState::consume(const string& tag,
+ Queue::shared_ptr queue, bool ackRequired, bool acquire,
+ bool exclusive, const string& resumeId, uint64_t resumeTtl,
+ const FieldTable& arguments)
+{
+ // "tag" is only guaranteed to be unique to this session (see AMQP 0-10 Message.subscribe, destination).
+ // Create a globally unique name so the broker can identify individual consumers
+ std::string name = session.getSessionId().str() + SEPARATOR + tag;
+ const ConsumerFactories::Factories& cf(
+ session.getBroker().getConsumerFactories().get());
+ ConsumerImpl::shared_ptr c;
+ for (ConsumerFactories::Factories::const_iterator i = cf.begin(); i != cf.end() && !c; ++i)
+ c = (*i)->create(this, name, queue, ackRequired, acquire, exclusive, tag,
+ resumeId, resumeTtl, arguments);
+ if (!c) // Create plain consumer
+ c = ConsumerImpl::shared_ptr(
+ new ConsumerImpl(this, name, queue, ackRequired, acquire ? CONSUMER : BROWSER, exclusive, tag,
+ resumeId, resumeTtl, arguments));
+ queue->consume(c, exclusive, arguments, connectionId, userID);//may throw exception
+ consumers[tag] = c;
+}
+
+bool SemanticState::cancel(const string& tag)
+{
+ ConsumerImplMap::iterator i = consumers.find(tag);
+ if (i != consumers.end()) {
+ cancel(i->second);
+ consumers.erase(i);
+ //should cancel all unacked messages for this consumer so that
+ //they are not redelivered on recovery
+ for_each(unacked.begin(), unacked.end(), boost::bind(&DeliveryRecord::cancel, _1, tag));
+ //can also remove any records that are now redundant
+ DeliveryRecords::iterator removed =
+ remove_if(unacked.begin(), unacked.end(), bind(&DeliveryRecord::isRedundant, _1));
+ unacked.erase(removed, unacked.end());
+ getSession().setUnackedCount(unacked.size());
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+void SemanticState::startTx()
+{
+ accumulatedAck.clear();
+ txBuffer = boost::intrusive_ptr<TxBuffer>(new TxBuffer());
+ session.getBroker().getBrokerObservers().startTx(txBuffer);
+ session.startTx(); //just to update statistics
+}
+
+namespace {
+struct StartTxOnExit {
+ SemanticState& session;
+ StartTxOnExit(SemanticState& ss) : session(ss) {}
+ ~StartTxOnExit() { session.startTx(); }
+};
+} // namespace
+
+void SemanticState::commit(MessageStore* const store)
+{
+ if (!txBuffer) throw
+ CommandInvalidException(
+ QPID_MSG("Session has not been selected for use with transactions"));
+ // Start a new TX regardless of outcome of this one.
+ StartTxOnExit e(*this);
+ session.getCurrentCommand().setCompleteSync(false); // Async completion
+ txBuffer->begin(); // Begin async completion.
+ session.commitTx(); //just to update statistics
+ TxOp::shared_ptr txAck(static_cast<TxOp*>(new TxAccept(accumulatedAck, unacked)));
+ txBuffer->enlist(txAck);
+ // In a HA cluster, tx.commit may complete asynchronously.
+ txBuffer->startCommit(store);
+ AsyncCommandCallback callback(
+ session,
+ boost::bind(&TxBuffer::endCommit, txBuffer, store),
+ true // This is a sync point
+ );
+ txBuffer->end(callback);
+}
+
+void SemanticState::rollback()
+{
+ if (!txBuffer)
+ throw CommandInvalidException(QPID_MSG("Session has not been selected for use with transactions"));
+ StartTxOnExit e(*this);
+ session.rollbackTx(); // Just to update statistics
+ txBuffer->rollback();
+}
+
+void SemanticState::selectDtx()
+{
+ dtxSelected = true;
+}
+
+void SemanticState::startDtx(const std::string& xid, DtxManager& mgr, bool join)
+{
+ if (!dtxSelected) {
+ throw CommandInvalidException(QPID_MSG("Session has not been selected for use with dtx"));
+ }
+ dtxBuffer = new DtxBuffer(xid);
+ txBuffer = dtxBuffer;
+ session.getBroker().getBrokerObservers().startDtx(dtxBuffer);
+ if (join) {
+ mgr.join(xid, dtxBuffer);
+ } else {
+ mgr.start(xid, dtxBuffer);
+ }
+}
+
+void SemanticState::endDtx(const std::string& xid, bool fail)
+{
+ if (!dtxBuffer) {
+ throw IllegalStateException(QPID_MSG("xid " << xid << " not associated with this session"));
+ }
+ if (dtxBuffer->getXid() != xid) {
+ throw CommandInvalidException(
+ QPID_MSG("xid specified on start was " << dtxBuffer->getXid() << ", but " << xid << " specified on end"));
+
+ }
+
+ txBuffer = 0;//ops on this session no longer transactional
+
+ checkDtxTimeout();
+ if (fail) {
+ dtxBuffer->fail();
+ } else {
+ dtxBuffer->markEnded();
+ }
+ dtxBuffer = 0;
+}
+
+void SemanticState::suspendDtx(const std::string& xid)
+{
+ if (dtxBuffer->getXid() != xid) {
+ throw CommandInvalidException(
+ QPID_MSG("xid specified on start was " << dtxBuffer->getXid() << ", but " << xid << " specified on suspend"));
+ }
+ txBuffer = 0;//ops on this session no longer transactional
+
+ checkDtxTimeout();
+ dtxBuffer->setSuspended(true);
+ suspendedXids[xid] = dtxBuffer;
+ dtxBuffer = 0;
+}
+
+void SemanticState::resumeDtx(const std::string& xid)
+{
+ if (!dtxSelected) {
+ throw CommandInvalidException(QPID_MSG("Session has not been selected for use with dtx"));
+ }
+
+ dtxBuffer = suspendedXids[xid];
+ if (!dtxBuffer) {
+ throw CommandInvalidException(QPID_MSG("xid " << xid << " not attached"));
+ } else {
+ suspendedXids.erase(xid);
+ }
+
+ if (dtxBuffer->getXid() != xid) {
+ throw CommandInvalidException(
+ QPID_MSG("xid specified on start was " << dtxBuffer->getXid() << ", but " << xid << " specified on resume"));
+
+ }
+ if (!dtxBuffer->isSuspended()) {
+ throw CommandInvalidException(QPID_MSG("xid " << xid << " not suspended"));
+ }
+
+ checkDtxTimeout();
+ dtxBuffer->setSuspended(false);
+ txBuffer = dtxBuffer;
+}
+
+void SemanticState::checkDtxTimeout()
+{
+ if (dtxBuffer->isExpired()) {
+ dtxBuffer = 0;
+ throw DtxTimeoutException();
+ }
+}
+
+void SemanticState::record(const DeliveryRecord& delivery)
+{
+ unacked.push_back(delivery);
+ getSession().setUnackedCount(unacked.size());
+}
+
+const std::string QPID_SYNC_FREQUENCY("qpid.sync_frequency");
+const std::string APACHE_SELECTOR("x-apache-selector");
+
+SemanticStateConsumerImpl::SemanticStateConsumerImpl(SemanticState* _parent,
+ const string& _name,
+ Queue::shared_ptr _queue,
+ bool ack,
+ SubscriptionType type,
+ bool _exclusive,
+ const string& _tag,
+ const string& _resumeId,
+ uint64_t _resumeTtl,
+ const framing::FieldTable& _arguments
+
+) :
+ Consumer(_name, type, _tag),
+ parent(_parent),
+ queue(_queue),
+ ackExpected(ack),
+ acquire(type == CONSUMER),
+ blocked(true),
+ exclusive(_exclusive),
+ resumeId(_resumeId),
+ selector(returnSelector(_arguments.getAsString(APACHE_SELECTOR))),
+ resumeTtl(_resumeTtl),
+ arguments(_arguments),
+ notifyEnabled(true),
+ syncFrequency(_arguments.getAsInt(QPID_SYNC_FREQUENCY)),
+ deliveryCount(0),
+ protocols(parent->getSession().getBroker().getProtocolRegistry())
+{
+ if (parent != 0 && queue.get() != 0 && queue->GetManagementObject() !=0)
+ {
+ ManagementAgent* agent = parent->session.getBroker().getManagementAgent();
+ qpid::management::Manageable* ms = dynamic_cast<qpid::management::Manageable*> (&(parent->session));
+
+ if (agent != 0)
+ {
+ mgmtObject = _qmf::Subscription::shared_ptr(new _qmf::Subscription(agent, this, ms , queue->GetManagementObject()->getObjectId(), getTag(),
+ !acquire, ackExpected, exclusive, ManagementAgent::toMap(arguments)));
+ agent->addObject (mgmtObject);
+ mgmtObject->set_creditMode("WINDOW");
+ }
+ }
+}
+
+ManagementObject::shared_ptr SemanticStateConsumerImpl::GetManagementObject (void) const
+{
+ return mgmtObject;
+}
+
+Manageable::status_t SemanticStateConsumerImpl::ManagementMethod (uint32_t methodId, Args&, string&)
+{
+ Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD;
+
+ QPID_LOG (debug, "Queue::ManagementMethod [id=" << methodId << "]");
+
+ return status;
+}
+
+
+OwnershipToken* SemanticStateConsumerImpl::getSession()
+{
+ return &(parent->session);
+}
+
+bool SemanticStateConsumerImpl::deliver(const QueueCursor& cursor, const Message& msg)
+{
+ return deliver(cursor, msg, shared_from_this());
+}
+bool SemanticStateConsumerImpl::deliver(const QueueCursor& cursor, const Message& msg, boost::shared_ptr<Consumer> consumer)
+{
+ allocateCredit(msg);
+ boost::intrusive_ptr<const amqp_0_10::MessageTransfer> transfer = protocols.translate(msg);
+ DeliveryRecord record(cursor, msg.getSequence(), msg.getReplicationId(), queue, getTag(),
+ consumer, acquire, !ackExpected, credit.isWindowMode(), transfer->getRequiredCredit());
+ bool sync = syncFrequency && ++deliveryCount >= syncFrequency;
+ if (sync) deliveryCount = 0;//reset
+
+ record.setId(parent->session.deliver(*transfer, getTag(), msg.isRedelivered(), msg.getTtl(),
+ ackExpected ? message::ACCEPT_MODE_EXPLICIT : message::ACCEPT_MODE_NONE,
+ acquire ? message::ACQUIRE_MODE_PRE_ACQUIRED : message::ACQUIRE_MODE_NOT_ACQUIRED,
+ msg.getAnnotations(),
+ sync));
+ if (credit.isWindowMode() || ackExpected || !acquire) {
+ parent->record(record);
+ }
+ if (acquire && !ackExpected) { // auto acquire && auto accept
+ queue->dequeue(0 /*ctxt*/, cursor);
+ }
+ if (mgmtObject) { mgmtObject->inc_delivered(); }
+ return true;
+}
+
+bool SemanticStateConsumerImpl::filter(const Message& msg)
+{
+ return !selector || selector->filter(msg);
+}
+
+bool SemanticStateConsumerImpl::accept(const Message& msg)
+{
+ // TODO aconway 2009-06-08: if we have byte & message credit but
+ // checkCredit fails because the message is to big, we should
+ // remain on queue's listener list for possible smaller messages
+ // in future.
+ //
+ blocked = !checkCredit(msg);
+ return !blocked;
+}
+
+namespace {
+struct ConsumerName {
+ const SemanticStateConsumerImpl& consumer;
+ ConsumerName(const SemanticStateConsumerImpl& ci) : consumer(ci) {}
+};
+
+ostream& operator<<(ostream& o, const ConsumerName& pc) {
+ return o << pc.consumer.getTag() << " on "
+ << pc.consumer.getParent().getSession().getSessionId();
+}
+}
+
+void SemanticStateConsumerImpl::allocateCredit(const Message& msg)
+{
+ Credit original = credit;
+ boost::intrusive_ptr<const amqp_0_10::MessageTransfer> transfer = protocols.translate(msg);
+ credit.consume(1, transfer->getRequiredCredit());
+ QPID_LOG(debug, "Credit allocated for " << ConsumerName(*this)
+ << ", was " << original << " now " << credit);
+
+}
+
+bool SemanticStateConsumerImpl::checkCredit(const Message& msg)
+{
+ boost::intrusive_ptr<const amqp_0_10::MessageTransfer> transfer = protocols.translate(msg);
+ bool enoughCredit = credit.check(1, transfer->getRequiredCredit());
+ QPID_LOG(debug, "Subscription " << ConsumerName(*this) << " has " << (enoughCredit ? "sufficient " : "insufficient")
+ << " credit for message of " << transfer->getRequiredCredit() << " bytes: "
+ << credit);
+ return enoughCredit;
+}
+
+SemanticStateConsumerImpl::~SemanticStateConsumerImpl()
+{
+ if (mgmtObject != 0) {
+ mgmtObject->debugStats("destroying");
+ mgmtObject->resourceDestroy ();
+ }
+}
+
+void SemanticState::disable(ConsumerImpl::shared_ptr c)
+{
+ c->disableNotify();
+ if (session.isAttached())
+ session.getConnection().removeOutputTask(c.get());
+}
+
+void SemanticState::cancel(ConsumerImpl::shared_ptr c)
+{
+ disable(c);
+ Queue::shared_ptr queue = c->getQueue();
+ if(queue) {
+ queue->cancel(c, connectionId, userID);
+ }
+ c->cancel();
+}
+
+TxBuffer* SemanticState::getTxBuffer()
+{
+ return txBuffer.get();
+}
+
+void SemanticState::route(Message& msg, Deliverable& strategy) {
+ std::string exchangeName = qpid::broker::amqp_0_10::MessageTransfer::get(msg).getExchangeName();
+ if (!cacheExchange || cacheExchange->getName() != exchangeName || cacheExchange->isDestroyed())
+ cacheExchange = session.getBroker().getExchanges().get(exchangeName);
+
+ /* verify the userid if specified: */
+ std::string id = msg.getUserId();
+ if (authMsg && !id.empty() && !session.getConnection().isAuthenticatedUser(id))
+ {
+ QPID_LOG(debug, "authorised user id : " << userID << " but user id in message declared as " << id);
+ throw UnauthorizedAccessException(QPID_MSG("authorised user id : " << userID << " but user id in message declared as " << id));
+ }
+
+ AclModule* acl = getSession().getBroker().getAcl();
+ if (acl && acl->doTransferAcl())
+ {
+ if (!acl->authorise(getSession().getConnection().getUserId(),acl::ACT_PUBLISH,acl::OBJ_EXCHANGE,exchangeName, msg.getRoutingKey() ))
+ throw UnauthorizedAccessException(QPID_MSG(userID << " cannot publish to " <<
+ exchangeName << " with routing-key " << msg.getRoutingKey()));
+ }
+
+ cacheExchange->route(strategy);
+
+ if (!strategy.delivered) {
+ //TODO:if discard-unroutable, just drop it
+ //TODO:else if accept-mode is explicit, reject it
+ //else route it to alternate exchange
+ if (cacheExchange->getAlternate()) {
+ cacheExchange->getAlternate()->route(strategy);
+ }
+ }
+
+}
+
+void SemanticState::requestDispatch()
+{
+ for (ConsumerImplMap::iterator i = consumers.begin(); i != consumers.end(); i++)
+ i->second->requestDispatch();
+}
+
+void SemanticStateConsumerImpl::requestDispatch()
+{
+ if (blocked) {
+ parent->session.getConnection().addOutputTask(this);
+ parent->session.getConnection().activateOutput();
+ blocked = false;
+ }
+}
+
+bool SemanticState::complete(DeliveryRecord& delivery)
+{
+ ConsumerImplMap::iterator i = consumers.find(delivery.getTag());
+ if (i != consumers.end()) {
+ i->second->complete(delivery);
+ }
+ return delivery.isRedundant();
+}
+
+void SemanticStateConsumerImpl::complete(DeliveryRecord& delivery)
+{
+ if (!delivery.isComplete()) {
+ delivery.complete();
+ if (credit.isWindowMode()) {
+ credit.moveWindow(1, delivery.getCredit());
+ }
+ }
+}
+
+void SemanticState::requeue()
+{
+ //take copy and clear unacked as requeue may result in redelivery to this session
+ //which will in turn result in additions to unacked
+ DeliveryRecords copy = unacked;
+ unacked.clear();
+ for_each(copy.rbegin(), copy.rend(), mem_fun_ref(&DeliveryRecord::requeue));
+ getSession().setUnackedCount(unacked.size());
+}
+
+
+SessionContext& SemanticState::getSession() { return session; }
+const SessionContext& SemanticState::getSession() const { return session; }
+
+
+const SemanticStateConsumerImpl::shared_ptr SemanticState::find(const std::string& destination) const
+{
+ ConsumerImpl::shared_ptr consumer;
+ if (!find(destination, consumer)) {
+ throw NotFoundException(QPID_MSG("Unknown destination " << destination << " session=" << session.getSessionId()));
+ } else {
+ return consumer;
+ }
+}
+
+bool SemanticState::find(const std::string& destination, ConsumerImpl::shared_ptr& consumer) const
+{
+ // @todo KAG gsim: shouldn't the consumers map be locked????
+ ConsumerImplMap::const_iterator i = consumers.find(destination);
+ if (i == consumers.end()) {
+ return false;
+ }
+ consumer = i->second;
+ return true;
+}
+
+void SemanticState::setWindowMode(const std::string& destination)
+{
+ find(destination)->setWindowMode();
+}
+
+void SemanticState::setCreditMode(const std::string& destination)
+{
+ find(destination)->setCreditMode();
+}
+
+void SemanticState::addByteCredit(const std::string& destination, uint32_t value)
+{
+ ConsumerImpl::shared_ptr c = find(destination);
+ c->addByteCredit(value);
+ c->requestDispatch();
+}
+
+
+void SemanticState::addMessageCredit(const std::string& destination, uint32_t value)
+{
+ ConsumerImpl::shared_ptr c = find(destination);
+ c->addMessageCredit(value);
+ c->requestDispatch();
+}
+
+void SemanticState::flush(const std::string& destination)
+{
+ find(destination)->flush();
+}
+
+
+void SemanticState::stop(const std::string& destination)
+{
+ find(destination)->stop();
+}
+
+void SemanticStateConsumerImpl::setWindowMode()
+{
+ credit.setWindowMode(true);
+ if (mgmtObject){
+ mgmtObject->set_creditMode("WINDOW");
+ }
+}
+
+void SemanticStateConsumerImpl::setCreditMode()
+{
+ credit.setWindowMode(false);
+ if (mgmtObject){
+ mgmtObject->set_creditMode("CREDIT");
+ }
+}
+
+void SemanticStateConsumerImpl::addByteCredit(uint32_t value)
+{
+ credit.addByteCredit(value);
+}
+
+void SemanticStateConsumerImpl::addMessageCredit(uint32_t value)
+{
+ credit.addMessageCredit(value);
+}
+
+bool SemanticStateConsumerImpl::haveCredit()
+{
+ if (credit) {
+ return true;
+ } else {
+ blocked = true;
+ return false;
+ }
+}
+
+bool SemanticStateConsumerImpl::doDispatch()
+{
+ return queue->dispatch(shared_from_this());
+}
+
+void SemanticStateConsumerImpl::flush()
+{
+ while(haveCredit() && doDispatch())
+ ;
+ credit.cancel();
+}
+
+void SemanticStateConsumerImpl::stop()
+{
+ credit.cancel();
+}
+
+Queue::shared_ptr SemanticState::getQueue(const string& name) const {
+ Queue::shared_ptr queue;
+ if (name.empty()) {
+ throw NotAllowedException(QPID_MSG("No queue name specified."));
+ } else {
+ queue = session.getBroker().getQueues().get(name);
+ }
+ return queue;
+}
+
+AckRange SemanticState::findRange(DeliveryId first, DeliveryId last)
+{
+ return DeliveryRecord::findRange(unacked, first, last);
+}
+
+void SemanticState::acquire(DeliveryId first, DeliveryId last, DeliveryIds& acquired)
+{
+ AckRange range = findRange(first, last);
+ for_each(range.start, range.end, AcquireFunctor(acquired));
+}
+
+void SemanticState::release(DeliveryId first, DeliveryId last, bool setRedelivered)
+{
+ AckRange range = findRange(first, last);
+ //release results in the message being added to the head so want
+ //to release in reverse order to keep the original transfer order
+ DeliveryRecords::reverse_iterator start(range.end);
+ DeliveryRecords::reverse_iterator end(range.start);
+ for_each(start, end, boost::bind(&DeliveryRecord::release, _1, setRedelivered));
+
+ DeliveryRecords::iterator removed =
+ remove_if(range.start, range.end, bind(&DeliveryRecord::isRedundant, _1));
+ unacked.erase(removed, range.end);
+ getSession().setUnackedCount(unacked.size());
+}
+
+void SemanticState::reject(DeliveryId first, DeliveryId last)
+{
+ AckRange range = findRange(first, last);
+ for_each(range.start, range.end, mem_fun_ref(&DeliveryRecord::reject));
+ //may need to remove the delivery records as well
+ for (DeliveryRecords::iterator i = range.start; i != unacked.end() && i->getId() <= last; ) {
+ if (i->isRedundant()) i = unacked.erase(i);
+ else i++;
+ }
+ getSession().setUnackedCount(unacked.size());
+}
+
+bool SemanticStateConsumerImpl::doOutput()
+{
+ try {
+ return haveCredit() && doDispatch();
+ } catch (const SessionException& e) {
+ throw SessionOutputException(e, parent->session.getChannel());
+ }
+}
+
+void SemanticStateConsumerImpl::enableNotify()
+{
+ Mutex::ScopedLock l(lock);
+ notifyEnabled = true;
+}
+
+void SemanticStateConsumerImpl::disableNotify()
+{
+ Mutex::ScopedLock l(lock);
+ notifyEnabled = false;
+}
+
+bool SemanticStateConsumerImpl::isNotifyEnabled() const {
+ Mutex::ScopedLock l(lock);
+ return notifyEnabled;
+}
+
+void SemanticStateConsumerImpl::notify()
+{
+ Mutex::ScopedLock l(lock);
+ if (notifyEnabled) {
+ parent->session.getConnection().addOutputTask(this);
+ parent->session.getConnection().activateOutput();
+ }
+}
+
+
+// Test that a DeliveryRecord's ID is in a sequence set and some other
+// predicate on DeliveryRecord holds.
+template <class Predicate> struct IsInSequenceSetAnd {
+ IsInSequenceSet isInSet;
+ Predicate predicate;
+ IsInSequenceSetAnd(const SequenceSet& s, Predicate p) : isInSet(s), predicate(p) {}
+ bool operator()(DeliveryRecord& dr) {
+ return isInSet(dr.getId()) && predicate(dr);
+ }
+};
+
+template<class Predicate> IsInSequenceSetAnd<Predicate>
+isInSequenceSetAnd(const SequenceSet& s, Predicate p) {
+ return IsInSequenceSetAnd<Predicate>(s,p);
+}
+
+void SemanticState::accepted(const SequenceSet& commands) {
+ if (txBuffer.get()) {
+ //in transactional mode, don't dequeue or remove, just
+ //maintain set of acknowledged messages:
+ accumulatedAck.add(commands);
+
+ if (dtxBuffer.get()) {
+ //if enlisted in a dtx, copy the relevant slice from
+ //unacked and record it against that transaction
+ TxOp::shared_ptr txAck(new DtxAck(accumulatedAck, unacked));
+ accumulatedAck.clear();
+ dtxBuffer->enlist(txAck);
+ //mark the relevant messages as 'ended' in unacked
+ //if the messages are already completed, they can be
+ //removed from the record
+ DeliveryRecords::iterator removed =
+ remove_if(unacked.begin(), unacked.end(),
+ isInSequenceSetAnd(commands,
+ bind(&DeliveryRecord::setEnded, _1)));
+ unacked.erase(removed, unacked.end());
+ }
+ } else {
+ DeliveryRecords::iterator removed =
+ remove_if(unacked.begin(), unacked.end(),
+ isInSequenceSetAnd(commands,
+ bind(&DeliveryRecord::accept, _1,
+ (TransactionContext*) 0)));
+ unacked.erase(removed, unacked.end());
+ }
+ getSession().setUnackedCount(unacked.size());
+}
+
+void SemanticState::completed(const SequenceSet& commands) {
+ DeliveryRecords::iterator removed =
+ remove_if(unacked.begin(), unacked.end(),
+ isInSequenceSetAnd(commands,
+ bind(&SemanticState::complete, this, _1)));
+ unacked.erase(removed, unacked.end());
+ requestDispatch();
+ getSession().setUnackedCount(unacked.size());
+}
+
+void SemanticState::attached()
+{
+ for (ConsumerImplMap::iterator i = consumers.begin(); i != consumers.end(); i++) {
+ i->second->enableNotify();
+ session.getConnection().addOutputTask(i->second.get());
+ }
+ session.getConnection().activateOutput();
+}
+
+void SemanticState::detached()
+{
+ for (ConsumerImplMap::iterator i = consumers.begin(); i != consumers.end(); i++) {
+ i->second->disableNotify();
+ session.getConnection().removeOutputTask(i->second.get());
+ }
+}
+
+void SemanticState::addBinding(const string& queueName, const string& exchangeName,
+ const string& routingKey, const framing::FieldTable& arguments)
+{
+ QPID_LOG (debug, "SemanticState::addBinding ["
+ << "queue=" << queueName << ", "
+ << "exchange=" << exchangeName << ", "
+ << "key=" << routingKey << ", "
+ << "args=" << arguments << "]");
+ std::string fedOp = arguments.getAsString(qpidFedOp);
+ if ((arguments.isSet(qpidFedOp)) && (fedOp.empty())) {
+ fedOp = fedOpBind;
+ }
+ std::string fedOrigin = arguments.getAsString(qpidFedOrigin);
+ if ((arguments.getAsString(X_SCOPE) == SESSION) || (fedOp == fedOpBind)) {
+ bindings.insert(boost::make_tuple(queueName, exchangeName, routingKey, fedOrigin));
+ }
+ else if (fedOp == fedOpUnbind) {
+ bindings.erase(boost::make_tuple(queueName, exchangeName, routingKey, fedOrigin));
+ }
+}
+
+void SemanticState::removeBinding(const string& queueName, const string& exchangeName,
+ const string& routingKey)
+{
+ QPID_LOG (debug, "SemanticState::removeBinding ["
+ << "queue=" << queueName << ", "
+ << "exchange=" << exchangeName << ", "
+ << "key=" << routingKey)
+ bindings.erase(boost::make_tuple(queueName, exchangeName, routingKey, ""));
+}
+
+void SemanticState::unbindSessionBindings()
+{
+ //unbind session-scoped bindings
+ for (Bindings::iterator i = bindings.begin(); i != bindings.end(); i++) {
+ QPID_LOG (debug, "SemanticState::unbindSessionBindings ["
+ << "queue=" << i->get<0>() << ", "
+ << "exchange=" << i->get<1>()<< ", "
+ << "key=" << i->get<2>() << ", "
+ << "fedOrigin=" << i->get<3>() << "]");
+ try {
+ std::string fedOrigin = i->get<3>();
+ if (!fedOrigin.empty()) {
+ framing::FieldTable fedArguments;
+ fedArguments.setString(qpidFedOp, fedOpUnbind);
+ fedArguments.setString(qpidFedOrigin, fedOrigin);
+ session.getBroker().bind(i->get<0>(), i->get<1>(), i->get<2>(), fedArguments,
+ &session, userID, connectionId);
+ } else {
+ session.getBroker().unbind(i->get<0>(), i->get<1>(), i->get<2>(),
+ &session, userID, connectionId);
+ }
+ }
+ catch (...) {
+ }
+ }
+ bindings.clear();
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/SemanticState.h b/qpid/cpp/src/qpid/broker/SemanticState.h
new file mode 100644
index 0000000000..e5456067a8
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SemanticState.h
@@ -0,0 +1,295 @@
+#ifndef QPID_BROKER_SEMANTICSTATE_H
+#define QPID_BROKER_SEMANTICSTATE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/broker/Consumer.h"
+#include "qpid/broker/Credit.h"
+#include "qpid/broker/Deliverable.h"
+#include "qpid/broker/DeliveryRecord.h"
+#include "qpid/broker/DtxBuffer.h"
+#include "qpid/broker/DtxManager.h"
+#include "qpid/broker/NameGenerator.h"
+#include "qpid/broker/QueueObserver.h"
+#include "qpid/broker/TxBuffer.h"
+
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/FrameHandler.h"
+#include "qpid/framing/SequenceSet.h"
+#include "qpid/framing/Uuid.h"
+#include "qpid/sys/AggregateOutput.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/AtomicValue.h"
+#include "qpid/broker/AclModule.h"
+#include "qpid/management/Manageable.h"
+#include "qmf/org/apache/qpid/broker/Subscription.h"
+
+#include <list>
+#include <map>
+#include <set>
+#include <vector>
+
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/cast.hpp>
+#include <boost/tuple/tuple.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace broker {
+
+class Exchange;
+class MessageStore;
+class ProtocolRegistry;
+class Selector;
+class SessionContext;
+class SessionState;
+
+/**
+ *
+ * SemanticState implements the behavior of a Session, especially the
+ * state of consumers subscribed to queues. The code for ConsumerImpl
+ * is also in SemanticState.cpp
+ *
+ * SemanticState holds the AMQP Execution and Model state of an open
+ * session, whether attached to a channel or suspended. It is not
+ * dependent on any specific AMQP version.
+ *
+ * Message delivery is driven by ConsumerImpl::doOutput(), which is
+ * called when a client's socket is ready to write data.
+ *
+ */
+class SemanticStateConsumerImpl;
+class SemanticState : private boost::noncopyable {
+ friend class SemanticStateConsumerImpl;
+
+ public:
+ typedef SemanticStateConsumerImpl ConsumerImpl;
+ typedef std::map<std::string, boost::intrusive_ptr<DtxBuffer> > DtxBufferMap;
+
+ private:
+ typedef std::map<std::string, boost::shared_ptr<ConsumerImpl> > ConsumerImplMap;
+ typedef boost::tuple<std::string, std::string, std::string, std::string> Binding;
+ typedef std::set<Binding> Bindings;
+
+ SessionState& session;
+ ConsumerImplMap consumers;
+ NameGenerator tagGenerator;
+ DeliveryRecords unacked;
+ boost::intrusive_ptr<TxBuffer> txBuffer;
+ boost::intrusive_ptr<DtxBuffer> dtxBuffer;
+ bool dtxSelected;
+ DtxBufferMap suspendedXids;
+ framing::SequenceSet accumulatedAck;
+ boost::shared_ptr<Exchange> cacheExchange;
+ const bool authMsg;
+ const std::string userID;
+ bool closeComplete;
+ //needed for queue delete events in auto-delete:
+ const std::string connectionId;
+
+ Bindings bindings;
+
+ void checkDtxTimeout();
+
+ bool complete(DeliveryRecord&);
+ AckRange findRange(DeliveryId first, DeliveryId last);
+ void requestDispatch();
+ void cancel(boost::shared_ptr<ConsumerImpl>);
+ void disable(boost::shared_ptr<ConsumerImpl>);
+ void unbindSessionBindings();
+
+ public:
+
+ SemanticState(SessionState&);
+ ~SemanticState();
+
+ SessionContext& getSession();
+ const SessionContext& getSession() const;
+ SessionState& getSessionState() { return session; }
+
+ const boost::shared_ptr<ConsumerImpl> find(const std::string& destination) const;
+ bool find(const std::string& destination, boost::shared_ptr<ConsumerImpl>&) const;
+
+ /**
+ * Get named queue, never returns 0.
+ * @return: named queue
+ * @exception: ChannelException if no queue of that name is found.
+ * @exception: ConnectionException if name="" and session has no default.
+ */
+ boost::shared_ptr<Queue> getQueue(const std::string& name) const;
+
+ bool exists(const std::string& consumerTag);
+
+ void consume(const std::string& destination,
+ boost::shared_ptr<Queue> queue,
+ bool ackRequired, bool acquire, bool exclusive,
+ const std::string& resumeId=std::string(), uint64_t resumeTtl=0,
+ const framing::FieldTable& = framing::FieldTable());
+
+ bool cancel(const std::string& tag);
+
+ void setWindowMode(const std::string& destination);
+ void setCreditMode(const std::string& destination);
+ void addByteCredit(const std::string& destination, uint32_t value);
+ void addMessageCredit(const std::string& destination, uint32_t value);
+ void flush(const std::string& destination);
+ void stop(const std::string& destination);
+
+ void startTx();
+ void commit(MessageStore* const store);
+ void rollback();
+ void selectDtx();
+ bool getDtxSelected() const { return dtxSelected; }
+ void startDtx(const std::string& xid, DtxManager& mgr, bool join);
+ void endDtx(const std::string& xid, bool fail);
+ void suspendDtx(const std::string& xid);
+ void resumeDtx(const std::string& xid);
+ TxBuffer* getTxBuffer();
+ void requeue();
+ void acquire(DeliveryId first, DeliveryId last, DeliveryIds& acquired);
+ void release(DeliveryId first, DeliveryId last, bool setRedelivered);
+ void reject(DeliveryId first, DeliveryId last);
+ void route(Message& msg, Deliverable& strategy);
+
+ void completed(const framing::SequenceSet& commands);
+ void accepted(const framing::SequenceSet& commands);
+
+ void attached();
+ void detached();
+ void closed();
+
+ DeliveryRecords& getUnacked() { return unacked; }
+ framing::SequenceSet getAccumulatedAck() const { return accumulatedAck; }
+ boost::intrusive_ptr<TxBuffer> getTxBuffer() const { return txBuffer; }
+ boost::intrusive_ptr<DtxBuffer> getDtxBuffer() const { return dtxBuffer; }
+ void setTxBuffer(const boost::intrusive_ptr<TxBuffer>& txb) { txBuffer = txb; }
+ void setDtxBuffer(const boost::intrusive_ptr<DtxBuffer>& dtxb) { dtxBuffer = dtxb; txBuffer = dtxb; }
+ void setAccumulatedAck(const framing::SequenceSet& s) { accumulatedAck = s; }
+ void record(const DeliveryRecord& delivery);
+ DtxBufferMap& getSuspendedXids() { return suspendedXids; }
+
+ void addBinding(const std::string& queueName, const std::string& exchangeName,
+ const std::string& routingKey, const framing::FieldTable& arguments);
+ void removeBinding(const std::string& queueName, const std::string& exchangeName,
+ const std::string& routingKey);
+};
+
+class SemanticStateConsumerImpl : public Consumer, public sys::OutputTask,
+ public boost::enable_shared_from_this<SemanticStateConsumerImpl>,
+ public management::Manageable
+{
+ protected:
+ mutable qpid::sys::Mutex lock;
+ SemanticState* const parent;
+ const boost::shared_ptr<Queue> queue;
+
+ private:
+ const bool ackExpected;
+ const bool acquire;
+ bool blocked;
+ bool exclusive;
+ std::string resumeId;
+ boost::shared_ptr<Selector> selector;
+ uint64_t resumeTtl;
+ framing::FieldTable arguments;
+ Credit credit;
+ bool notifyEnabled;
+ const int syncFrequency;
+ int deliveryCount;
+ qmf::org::apache::qpid::broker::Subscription::shared_ptr mgmtObject;
+ ProtocolRegistry& protocols;
+
+ bool checkCredit(const Message& msg);
+ void allocateCredit(const Message& msg);
+ bool haveCredit();
+
+ protected:
+ QPID_BROKER_EXTERN virtual bool doDispatch();
+ size_t unacked() { return parent->unacked.size(); }
+ QPID_BROKER_EXTERN bool deliver(const QueueCursor&, const Message&, boost::shared_ptr<Consumer>);
+
+ public:
+ typedef boost::shared_ptr<SemanticStateConsumerImpl> shared_ptr;
+
+ QPID_BROKER_EXTERN SemanticStateConsumerImpl(SemanticState* parent,
+ const std::string& name, boost::shared_ptr<Queue> queue,
+ bool ack, SubscriptionType type, bool exclusive,
+ const std::string& tag, const std::string& resumeId,
+ uint64_t resumeTtl, const framing::FieldTable& arguments);
+ QPID_BROKER_EXTERN ~SemanticStateConsumerImpl();
+ QPID_BROKER_EXTERN OwnershipToken* getSession();
+ QPID_BROKER_EXTERN bool deliver(const QueueCursor&, const Message&);
+ QPID_BROKER_EXTERN bool filter(const Message&);
+ QPID_BROKER_EXTERN bool accept(const Message&);
+ QPID_BROKER_EXTERN void cancel() {}
+
+ QPID_BROKER_EXTERN void disableNotify();
+ QPID_BROKER_EXTERN void enableNotify();
+ QPID_BROKER_EXTERN void notify();
+ QPID_BROKER_EXTERN bool isNotifyEnabled() const;
+
+ QPID_BROKER_EXTERN void requestDispatch();
+
+ QPID_BROKER_EXTERN void setWindowMode();
+ QPID_BROKER_EXTERN void setCreditMode();
+ QPID_BROKER_EXTERN void addByteCredit(uint32_t value);
+ QPID_BROKER_EXTERN void addMessageCredit(uint32_t value);
+ QPID_BROKER_EXTERN void flush();
+ QPID_BROKER_EXTERN void stop();
+ QPID_BROKER_EXTERN void complete(DeliveryRecord&);
+ boost::shared_ptr<Queue> getQueue() const { return queue; }
+ bool isBlocked() const { return blocked; }
+ bool setBlocked(bool set) { std::swap(set, blocked); return set; }
+
+ QPID_BROKER_EXTERN bool doOutput();
+
+ Credit& getCredit() { return credit; }
+ const Credit& getCredit() const { return credit; }
+ bool isAckExpected() const { return ackExpected; }
+ bool isAcquire() const { return acquire; }
+ bool isExclusive() const { return exclusive; }
+ std::string getResumeId() const { return resumeId; };
+ uint64_t getResumeTtl() const { return resumeTtl; }
+ uint32_t getDeliveryCount() const { return deliveryCount; }
+ void setDeliveryCount(uint32_t _deliveryCount) { deliveryCount = _deliveryCount; }
+ const framing::FieldTable& getArguments() const { return arguments; }
+
+ SemanticState& getParent() { return *parent; }
+ const SemanticState& getParent() const { return *parent; }
+
+ void acknowledged(const DeliveryRecord&) {}
+
+ // manageable entry points
+ QPID_BROKER_EXTERN management::ManagementObject::shared_ptr
+ GetManagementObject(void) const;
+
+ QPID_BROKER_EXTERN management::Manageable::status_t
+ ManagementMethod(uint32_t methodId, management::Args& args, std::string& text);
+};
+
+}} // namespace qpid::broker
+
+
+
+
+#endif /*!QPID_BROKER_SEMANTICSTATE_H*/
diff --git a/qpid/cpp/src/qpid/broker/SessionAdapter.cpp b/qpid/cpp/src/qpid/broker/SessionAdapter.cpp
new file mode 100644
index 0000000000..c4a0d9f008
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SessionAdapter.cpp
@@ -0,0 +1,680 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#include "qpid/broker/SessionAdapter.h"
+
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/amqp_0_10/Connection.h"
+#include "qpid/broker/DtxTimeout.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/Exception.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/framing/enum.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/framing/SequenceSet.h"
+#include "qpid/log/Statement.h"
+#include "qpid/management/ManagementAgent.h"
+#include "qpid/broker/SessionState.h"
+ #include <boost/format.hpp>
+#include <boost/cast.hpp>
+#include <boost/bind.hpp>
+
+namespace qpid {
+namespace broker {
+
+using std::string;
+
+using namespace qpid;
+using namespace qpid::framing;
+using namespace qpid::framing::dtx;
+using namespace qpid::management;
+
+typedef std::vector<Queue::shared_ptr> QueueVector;
+
+SessionAdapter::SessionAdapter(SemanticState& s) :
+ HandlerImpl(s),
+ exchangeImpl(s),
+ queueImpl(s),
+ messageImpl(s),
+ executionImpl(s),
+ txImpl(s),
+ dtxImpl(s)
+{}
+
+static const std::string _TRUE("true");
+static const std::string _FALSE("false");
+
+void SessionAdapter::ExchangeHandlerImpl::declare(const string& exchange, const string& type,
+ const string& alternateExchange,
+ bool passive, bool durable, bool autodelete, const FieldTable& args){
+
+ Exchange::shared_ptr alternate;
+ if (!alternateExchange.empty()) {
+ alternate = getBroker().getExchanges().get(alternateExchange);
+ }
+ if(passive){
+ AclModule* acl = getBroker().getAcl();
+ if (acl) {
+ std::map<acl::Property, std::string> params;
+ params.insert(make_pair(acl::PROP_TYPE, type));
+ params.insert(make_pair(acl::PROP_ALTERNATE, alternateExchange));
+ params.insert(make_pair(acl::PROP_DURABLE, durable ? _TRUE : _FALSE));
+ params.insert(make_pair(acl::PROP_AUTODELETE, autodelete ? _TRUE : _FALSE));
+ if (!acl->authorise(getConnection().getUserId(),acl::ACT_ACCESS,acl::OBJ_EXCHANGE,exchange,&params) )
+ throw framing::UnauthorizedAccessException(QPID_MSG("ACL denied exchange access request from " << getConnection().getUserId()));
+ }
+ Exchange::shared_ptr actual(getBroker().getExchanges().get(exchange));
+ checkType(actual, type);
+ checkAlternate(actual, alternate);
+ }else{
+ if(exchange.find("amq.") == 0 || exchange.find("qpid.") == 0) {
+ throw framing::NotAllowedException(QPID_MSG("Exchange names beginning with \"amq.\" or \"qpid.\" are reserved. (exchange=\"" << exchange << "\")"));
+ }
+ try{
+ std::pair<Exchange::shared_ptr, bool> response =
+ getBroker().createExchange(exchange, type, durable, autodelete, alternateExchange, args,
+ getConnection().getUserId(), getConnection().getMgmtId());
+ if (!response.second) {
+ //exchange already there, not created
+ checkType(response.first, type);
+ checkAlternate(response.first, alternate);
+ QPID_LOG_CAT(debug, model, "Create exchange. name:" << exchange
+ << " user:" << getConnection().getUserId()
+ << " rhost:" << getConnection().getMgmtId()
+ << " type:" << type
+ << " alternateExchange:" << alternateExchange
+ << " durable:" << (durable ? "T" : "F")
+ << " autodelete:" << (autodelete ? "T" : "F"));
+ }
+ }catch(UnknownExchangeTypeException& /*e*/){
+ throw NotFoundException(QPID_MSG("Exchange type not implemented: " << type));
+ }
+ }
+}
+
+void SessionAdapter::ExchangeHandlerImpl::checkType(Exchange::shared_ptr exchange, const std::string& type)
+{
+ if (!type.empty() && exchange->getType() != type) {
+ throw NotAllowedException(QPID_MSG("Exchange declared to be of type " << exchange->getType() << ", requested " << type));
+ }
+}
+
+void SessionAdapter::ExchangeHandlerImpl::checkAlternate(Exchange::shared_ptr exchange, Exchange::shared_ptr alternate)
+{
+ if (alternate && ((exchange->getAlternate() && alternate != exchange->getAlternate())
+ || !exchange->getAlternate()))
+ throw NotAllowedException(QPID_MSG("Exchange declared with alternate-exchange "
+ << (exchange->getAlternate() ? exchange->getAlternate()->getName() : "<nonexistent>")
+ << ", requested "
+ << alternate->getName()));
+}
+
+void SessionAdapter::ExchangeHandlerImpl::delete_(const string& name, bool /*ifUnused*/)
+{
+ //TODO: implement if-unused
+ getBroker().deleteExchange(name, getConnection().getUserId(), getConnection().getMgmtId());
+}
+
+ExchangeQueryResult SessionAdapter::ExchangeHandlerImpl::query(const string& name)
+{
+ AclModule* acl = getBroker().getAcl();
+ if (acl) {
+ if (!acl->authorise(getConnection().getUserId(),acl::ACT_ACCESS,acl::OBJ_EXCHANGE,name,NULL) )
+ throw UnauthorizedAccessException(QPID_MSG("ACL denied exchange query request from " << getConnection().getUserId()));
+ }
+ Exchange::shared_ptr exchange(getBroker().getExchanges().find(name));
+ if (exchange)
+ return ExchangeQueryResult(exchange->getType(), exchange->isDurable(), false, exchange->getArgs());
+ else
+ return ExchangeQueryResult("", false, true, FieldTable());
+}
+
+void SessionAdapter::ExchangeHandlerImpl::bind(const string& queueName,
+ const string& exchangeName, const string& routingKey,
+ const FieldTable& arguments)
+{
+ getBroker().bind(queueName, exchangeName, routingKey, arguments, &session,
+ getConnection().getUserId(), getConnection().getMgmtId());
+ state.addBinding(queueName, exchangeName, routingKey, arguments);
+}
+
+void SessionAdapter::ExchangeHandlerImpl::unbind(const string& queueName,
+ const string& exchangeName,
+ const string& routingKey)
+{
+ state.removeBinding(queueName, exchangeName, routingKey);
+ getBroker().unbind(queueName, exchangeName, routingKey, &session,
+ getConnection().getUserId(), getConnection().getMgmtId());
+}
+
+ExchangeBoundResult SessionAdapter::ExchangeHandlerImpl::bound(const std::string& exchangeName,
+ const std::string& queueName,
+ const std::string& key,
+ const framing::FieldTable& args)
+{
+ AclModule* acl = getBroker().getAcl();
+ if (acl) {
+ std::map<acl::Property, std::string> params;
+ params.insert(make_pair(acl::PROP_QUEUENAME, queueName));
+ params.insert(make_pair(acl::PROP_ROUTINGKEY, key));
+ if (!acl->authorise(getConnection().getUserId(),acl::ACT_ACCESS,acl::OBJ_EXCHANGE,exchangeName,&params) )
+ throw UnauthorizedAccessException(QPID_MSG("ACL denied exchange bound request from " << getConnection().getUserId()));
+ }
+
+ Exchange::shared_ptr exchange = getBroker().getExchanges().find(exchangeName);
+
+ Queue::shared_ptr queue;
+ if (!queueName.empty()) {
+ queue = getBroker().getQueues().find(queueName);
+ }
+
+ if (!exchange) {
+ return ExchangeBoundResult(true, (!queueName.empty() && !queue), false, false, false);
+ } else if (!queueName.empty() && !queue) {
+ return ExchangeBoundResult(false, true, false, false, false);
+ } else if (exchange->isBound(queue, key.empty() ? 0 : &key, args.count() > 0 ? &args : &args)) {
+ return ExchangeBoundResult(false, false, false, false, false);
+ } else {
+ //need to test each specified option individually
+ bool queueMatched = queueName.empty() || exchange->isBound(queue, 0, 0);
+ bool keyMatched = key.empty() || exchange->isBound(Queue::shared_ptr(), &key, 0);
+ bool argsMatched = args.count() == 0 || exchange->isBound(Queue::shared_ptr(), 0, &args);
+
+ return ExchangeBoundResult(false, false, !queueMatched, !keyMatched, !argsMatched);
+ }
+}
+
+SessionAdapter::QueueHandlerImpl::QueueHandlerImpl(SemanticState& session)
+ : HandlerHelper(session), broker(getBroker()),
+ //record connection id and userid for deleting exclsuive queues after session has ended:
+ connectionId(getConnection().getMgmtId()), userId(getConnection().getUserId())
+{}
+
+
+SessionAdapter::QueueHandlerImpl::~QueueHandlerImpl()
+{
+ try {
+ destroyExclusiveQueues();
+ } catch (std::exception& e) {
+ QPID_LOG(error, e.what());
+ }
+}
+
+void SessionAdapter::QueueHandlerImpl::destroyExclusiveQueues()
+{
+ while (!exclusiveQueues.empty()) {
+ Queue::shared_ptr q(exclusiveQueues.front());
+ q->releaseExclusiveOwnership();
+ exclusiveQueues.erase(exclusiveQueues.begin());
+ }
+}
+
+bool SessionAdapter::QueueHandlerImpl::isLocal(const OwnershipToken* t) const
+{
+ return session.isLocal(t);
+}
+
+
+QueueQueryResult SessionAdapter::QueueHandlerImpl::query(const string& name)
+{
+ AclModule* acl = getBroker().getAcl();
+ if (acl) {
+ if (!acl->authorise(getConnection().getUserId(),acl::ACT_ACCESS,acl::OBJ_QUEUE,name,NULL) )
+ throw UnauthorizedAccessException(QPID_MSG("ACL denied queue query request from " << getConnection().getUserId()));
+ }
+
+ Queue::shared_ptr queue = session.getBroker().getQueues().find(name);
+ if (queue) {
+
+ Exchange::shared_ptr alternateExchange = queue->getAlternateExchange();
+
+ return QueueQueryResult(queue->getName(),
+ alternateExchange ? alternateExchange->getName() : "",
+ queue->isDurable(),
+ queue->hasExclusiveOwner(),
+ queue->isAutoDelete(),
+ queue->getEncodableSettings(),
+ queue->getMessageCount(),
+ queue->getConsumerCount());
+ } else {
+ return QueueQueryResult();
+ }
+}
+
+void SessionAdapter::QueueHandlerImpl::declare(const string& name, const string& alternateExchange,
+ bool passive, bool durable, bool exclusive,
+ bool autoDelete, const qpid::framing::FieldTable& arguments)
+{
+ Queue::shared_ptr queue;
+ if (passive && !name.empty()) {
+ AclModule* acl = getBroker().getAcl();
+ if (acl) {
+ std::map<acl::Property, std::string> params;
+ params.insert(make_pair(acl::PROP_ALTERNATE, alternateExchange));
+ params.insert(make_pair(acl::PROP_DURABLE, std::string(durable ? _TRUE : _FALSE)));
+ params.insert(make_pair(acl::PROP_EXCLUSIVE, std::string(exclusive ? _TRUE : _FALSE)));
+ params.insert(make_pair(acl::PROP_AUTODELETE, std::string(autoDelete ? _TRUE : _FALSE)));
+ params.insert(make_pair(acl::PROP_POLICYTYPE, arguments.getAsString("qpid.policy_type")));
+ params.insert(make_pair(acl::PROP_MAXQUEUECOUNT, boost::lexical_cast<string>(arguments.getAsInt("qpid.max_count"))));
+ params.insert(make_pair(acl::PROP_MAXQUEUESIZE, boost::lexical_cast<string>(arguments.getAsInt64("qpid.max_size"))));
+ if (!acl->authorise(getConnection().getUserId(),acl::ACT_ACCESS,acl::OBJ_QUEUE,name,&params) )
+ throw UnauthorizedAccessException(QPID_MSG("ACL denied queue access request from " << getConnection().getUserId()));
+ }
+ queue = getQueue(name);
+ //TODO: check alternate-exchange is as expected
+ } else {
+ QueueSettings settings(durable, autoDelete);
+ try {
+ settings.populate(arguments, settings.storeSettings);
+ } catch (const qpid::types::Exception& e) {
+ throw InvalidArgumentException(e.what());
+ }
+
+ std::pair<Queue::shared_ptr, bool> queue_created =
+ getBroker().createQueue(name, settings,
+ exclusive ? &session : 0,
+ alternateExchange,
+ getConnection().getUserId(),
+ getConnection().getMgmtId());
+ queue = queue_created.first;
+ assert(queue);
+ if (queue_created.second) { // This is a new queue
+ //handle automatic cleanup:
+ if (exclusive && queue->setExclusiveOwner(&session)) {
+ exclusiveQueues.push_back(queue);
+ }
+ } else {
+ if (exclusive && queue->setExclusiveOwner(&session)) {
+ exclusiveQueues.push_back(queue);
+ }
+ QPID_LOG_CAT(debug, model, "Create queue. name:" << name
+ << " user:" << getConnection().getUserId()
+ << " rhost:" << getConnection().getMgmtId()
+ << " durable:" << (durable ? "T" : "F")
+ << " exclusive:" << (exclusive ? "T" : "F")
+ << " autodelete:" << (autoDelete ? "T" : "F")
+ << " alternateExchange:" << alternateExchange
+ );
+ }
+
+ }
+
+ if (exclusive && !queue->isExclusiveOwner(&session))
+ throw ResourceLockedException(QPID_MSG("Cannot grant exclusive access to queue "
+ << queue->getName()));
+}
+
+void SessionAdapter::QueueHandlerImpl::purge(const string& queue){
+ AclModule* acl = getBroker().getAcl();
+ if (acl)
+ {
+ if (!acl->authorise(getConnection().getUserId(),acl::ACT_PURGE,acl::OBJ_QUEUE,queue,NULL) )
+ throw UnauthorizedAccessException(QPID_MSG("ACL denied queue purge request from " << getConnection().getUserId()));
+ }
+ getQueue(queue)->purge();
+}
+
+void SessionAdapter::QueueHandlerImpl::checkDelete(Queue::shared_ptr queue, bool ifUnused, bool ifEmpty)
+{
+ if (queue->hasExclusiveOwner() && !queue->isExclusiveOwner(&session)) {
+ throw ResourceLockedException(QPID_MSG("Cannot delete queue "
+ << queue->getName() << "; it is exclusive to another session"));
+ } else if(ifEmpty && queue->getMessageCount() > 0) {
+ throw PreconditionFailedException(QPID_MSG("Cannot delete queue "
+ << queue->getName() << "; queue not empty"));
+ } else if(ifUnused && queue->getConsumerCount() > 0) {
+ throw PreconditionFailedException(QPID_MSG("Cannot delete queue "
+ << queue->getName() << "; queue in use"));
+ } else if (queue->isExclusiveOwner(&session)) {
+ //remove the queue from the list of exclusive queues if necessary
+ QueueVector::iterator i = std::find(exclusiveQueues.begin(),
+ exclusiveQueues.end(),
+ queue);
+ if (i < exclusiveQueues.end()) exclusiveQueues.erase(i);
+ }
+}
+
+void SessionAdapter::QueueHandlerImpl::delete_(const string& queue, bool ifUnused, bool ifEmpty)
+{
+ getBroker().deleteQueue(queue, getConnection().getUserId(), getConnection().getMgmtId(),
+ boost::bind(&SessionAdapter::QueueHandlerImpl::checkDelete, this, _1, ifUnused, ifEmpty));
+}
+
+SessionAdapter::MessageHandlerImpl::MessageHandlerImpl(SemanticState& s) :
+ HandlerHelper(s),
+ releaseRedeliveredOp(boost::bind(&SemanticState::release, &state, _1, _2, true)),
+ releaseOp(boost::bind(&SemanticState::release, &state, _1, _2, false)),
+ rejectOp(boost::bind(&SemanticState::reject, &state, _1, _2))
+ {}
+
+//
+// Message class method handlers
+//
+
+void SessionAdapter::MessageHandlerImpl::transfer(const string& /*destination*/,
+ uint8_t /*acceptMode*/,
+ uint8_t /*acquireMode*/)
+{
+ //not yet used (content containing assemblies treated differently at present
+ std::cout << "SessionAdapter::MessageHandlerImpl::transfer() called" << std::endl;
+}
+
+void SessionAdapter::MessageHandlerImpl::release(const SequenceSet& transfers, bool setRedelivered)
+{
+ transfers.for_each(setRedelivered ? releaseRedeliveredOp : releaseOp);
+}
+
+void
+SessionAdapter::MessageHandlerImpl::subscribe(const string& queueName,
+ const string& destination,
+ uint8_t acceptMode,
+ uint8_t acquireMode,
+ bool exclusive,
+ const string& resumeId,
+ uint64_t resumeTtl,
+ const FieldTable& arguments)
+{
+
+ AclModule* acl = getBroker().getAcl();
+ if (acl)
+ {
+ if (!acl->authorise(getConnection().getUserId(),acl::ACT_CONSUME,acl::OBJ_QUEUE,queueName,NULL) )
+ throw UnauthorizedAccessException(QPID_MSG("ACL denied Queue subscribe request from " << getConnection().getUserId()));
+ }
+
+ Queue::shared_ptr queue = getQueue(queueName);
+ if(!destination.empty() && state.exists(destination))
+ throw NotAllowedException(QPID_MSG("Consumer tags must be unique"));
+
+ if (queue->getSettings().isBrowseOnly && acquireMode == 0) {
+ QPID_LOG(info, "Overriding request to consume from browse-only queue " << queue->getName());
+ acquireMode = 1;
+ }
+
+ // We allow browsing (acquireMode == 1) of exclusive queues, this is required by HA.
+ if (queue->hasExclusiveOwner() && !queue->isExclusiveOwner(&session) && acquireMode == 0)
+ throw ResourceLockedException(QPID_MSG("Cannot subscribe to exclusive queue "
+ << queue->getName()));
+
+ state.consume(destination, queue,
+ acceptMode == 0, acquireMode == 0, exclusive,
+ resumeId, resumeTtl, arguments);
+
+ QPID_LOG_CAT(debug, model, "Create subscription. queue:" << queueName
+ << " destination:" << destination
+ << " user:" << getConnection().getUserId()
+ << " rhost:" << getConnection().getMgmtId()
+ << " exclusive:" << (exclusive ? "T" : "F")
+ );
+}
+
+void
+SessionAdapter::MessageHandlerImpl::cancel(const string& destination )
+{
+ if (!state.cancel(destination)) {
+ throw NotFoundException(QPID_MSG("No such subscription: " << destination));
+ }
+ QPID_LOG_CAT(debug, model, "Delete subscription. destination:" << destination
+ << " user:" << getConnection().getUserId()
+ << " rhost:" << getConnection().getMgmtId() );
+}
+
+void
+SessionAdapter::MessageHandlerImpl::reject(const SequenceSet& transfers, uint16_t /*code*/, const string& /*text*/ )
+{
+ transfers.for_each(rejectOp);
+}
+
+void SessionAdapter::MessageHandlerImpl::flow(const std::string& destination, uint8_t unit, uint32_t value)
+{
+ if (unit == 0) {
+ //message
+ state.addMessageCredit(destination, value);
+ } else if (unit == 1) {
+ //bytes
+ state.addByteCredit(destination, value);
+ } else {
+ //unknown
+ throw InvalidArgumentException(QPID_MSG("Invalid value for unit " << unit));
+ }
+
+}
+
+void SessionAdapter::MessageHandlerImpl::setFlowMode(const std::string& destination, uint8_t mode)
+{
+ if (mode == 0) {
+ //credit
+ state.setCreditMode(destination);
+ } else if (mode == 1) {
+ //window
+ state.setWindowMode(destination);
+ } else{
+ throw InvalidArgumentException(QPID_MSG("Invalid value for mode " << mode));
+ }
+}
+
+void SessionAdapter::MessageHandlerImpl::flush(const std::string& destination)
+{
+ state.flush(destination);
+}
+
+void SessionAdapter::MessageHandlerImpl::stop(const std::string& destination)
+{
+ state.stop(destination);
+}
+
+void SessionAdapter::MessageHandlerImpl::accept(const framing::SequenceSet& commands)
+{
+ state.accepted(commands);
+}
+
+framing::MessageAcquireResult SessionAdapter::MessageHandlerImpl::acquire(const framing::SequenceSet& transfers)
+{
+ // FIXME aconway 2008-05-12: create SequenceSet directly, no need for intermediate results vector.
+ SequenceNumberSet results;
+ RangedOperation f = boost::bind(&SemanticState::acquire, &state, _1, _2, boost::ref(results));
+ transfers.for_each(f);
+
+ results = results.condense();
+ SequenceSet acquisitions;
+ RangedOperation g = boost::bind(&SequenceSet::add, &acquisitions, _1, _2);
+ results.processRanges(g);
+
+ return MessageAcquireResult(acquisitions);
+}
+
+framing::MessageResumeResult SessionAdapter::MessageHandlerImpl::resume(const std::string& /*destination*/,
+ const std::string& /*resumeId*/)
+{
+ throw NotImplementedException("resuming transfers not yet supported");
+}
+
+
+
+void SessionAdapter::ExecutionHandlerImpl::sync()
+{
+ session.addPendingExecutionSync();
+ /** @todo KAG - need a generic mechanism to allow a command to returning "not completed" status back to SessionState */
+
+}
+
+void SessionAdapter::ExecutionHandlerImpl::result(const SequenceNumber& /*commandId*/, const string& /*value*/)
+{
+ //TODO: but currently never used client->server
+}
+
+void SessionAdapter::ExecutionHandlerImpl::exception(uint16_t errorCode,
+ const SequenceNumber& /*commandId*/,
+ uint8_t /*classCode*/,
+ uint8_t /*commandCode*/,
+ uint8_t /*fieldIndex*/,
+ const std::string& description,
+ const framing::FieldTable& /*errorInfo*/)
+{
+ broker::SessionHandler* s = state.getSessionState().getHandler();
+ if (s) s->incomingExecutionException(
+ framing::execution::ErrorCode(errorCode), description);
+}
+
+
+
+void SessionAdapter::TxHandlerImpl::select()
+{
+ state.startTx();
+}
+
+void SessionAdapter::TxHandlerImpl::commit()
+{
+ state.commit(&getBroker().getStore());
+}
+
+void SessionAdapter::TxHandlerImpl::rollback()
+{
+ state.rollback();
+}
+
+void SessionAdapter::DtxHandlerImpl::select()
+{
+ state.selectDtx();
+}
+
+XaResult SessionAdapter::DtxHandlerImpl::end(const Xid& xid,
+ bool fail,
+ bool suspend)
+{
+ try {
+ if (fail) {
+ state.endDtx(DtxManager::convert(xid), true);
+ if (suspend) {
+ throw CommandInvalidException(QPID_MSG("End and suspend cannot both be set."));
+ } else {
+ return XaResult(XA_STATUS_XA_RBROLLBACK);
+ }
+ } else {
+ if (suspend) {
+ state.suspendDtx(DtxManager::convert(xid));
+ } else {
+ state.endDtx(DtxManager::convert(xid), false);
+ }
+ return XaResult(XA_STATUS_XA_OK);
+ }
+ } catch (const DtxTimeoutException& /*e*/) {
+ return XaResult(XA_STATUS_XA_RBTIMEOUT);
+ }
+}
+
+XaResult SessionAdapter::DtxHandlerImpl::start(const Xid& xid,
+ bool join,
+ bool resume)
+{
+ if (join && resume) {
+ throw CommandInvalidException(QPID_MSG("Join and resume cannot both be set."));
+ }
+ try {
+ if (resume) {
+ state.resumeDtx(DtxManager::convert(xid));
+ } else {
+ state.startDtx(DtxManager::convert(xid), getBroker().getDtxManager(), join);
+ }
+ return XaResult(XA_STATUS_XA_OK);
+ } catch (const DtxTimeoutException& /*e*/) {
+ return XaResult(XA_STATUS_XA_RBTIMEOUT);
+ }
+}
+
+XaResult SessionAdapter::DtxHandlerImpl::prepare(const Xid& xid)
+{
+ try {
+ bool ok = getBroker().getDtxManager().prepare(DtxManager::convert(xid));
+ return XaResult(ok ? XA_STATUS_XA_OK : XA_STATUS_XA_RBROLLBACK);
+ } catch (const DtxTimeoutException& /*e*/) {
+ return XaResult(XA_STATUS_XA_RBTIMEOUT);
+ }
+}
+
+XaResult SessionAdapter::DtxHandlerImpl::commit(const Xid& xid,
+ bool onePhase)
+{
+ try {
+ bool ok = getBroker().getDtxManager().commit(DtxManager::convert(xid), onePhase);
+ return XaResult(ok ? XA_STATUS_XA_OK : XA_STATUS_XA_RBROLLBACK);
+ } catch (const DtxTimeoutException& /*e*/) {
+ return XaResult(XA_STATUS_XA_RBTIMEOUT);
+ }
+}
+
+
+XaResult SessionAdapter::DtxHandlerImpl::rollback(const Xid& xid)
+{
+ try {
+ getBroker().getDtxManager().rollback(DtxManager::convert(xid));
+ return XaResult(XA_STATUS_XA_OK);
+ } catch (const DtxTimeoutException& /*e*/) {
+ return XaResult(XA_STATUS_XA_RBTIMEOUT);
+ }
+}
+
+DtxRecoverResult SessionAdapter::DtxHandlerImpl::recover()
+{
+ std::set<std::string> xids;
+ getBroker().getStore().collectPreparedXids(xids);
+ /*
+ * create array of long structs
+ */
+ Array indoubt(0xAB);
+ for (std::set<std::string>::iterator i = xids.begin(); i != xids.end(); i++) {
+ boost::shared_ptr<FieldValue> xid(new Struct32Value(*i));
+ indoubt.add(xid);
+ }
+ return DtxRecoverResult(indoubt);
+}
+
+void SessionAdapter::DtxHandlerImpl::forget(const Xid& xid)
+{
+ //Currently no heuristic completion is supported, so this should never be used.
+ throw NotImplementedException(QPID_MSG("Forget not implemented. Branch with xid " << xid << " not heuristically completed!"));
+}
+
+DtxGetTimeoutResult SessionAdapter::DtxHandlerImpl::getTimeout(const Xid& xid)
+{
+ uint32_t timeout = getBroker().getDtxManager().getTimeout(DtxManager::convert(xid));
+ return DtxGetTimeoutResult(timeout);
+}
+
+
+void SessionAdapter::DtxHandlerImpl::setTimeout(const Xid& xid,
+ uint32_t timeout)
+{
+ if ((timeout > getBroker().getDtxMaxTimeout()) && (getBroker().getDtxMaxTimeout() > 0))
+ throw InvalidArgumentException(QPID_MSG("xid " << xid << " has timeout " << timeout << " bigger than maximum allowed " << getBroker().getDtxMaxTimeout()));
+ getBroker().getDtxManager().setTimeout(DtxManager::convert(xid), timeout);
+}
+
+
+Queue::shared_ptr SessionAdapter::HandlerHelper::getQueue(const string& name) const {
+ Queue::shared_ptr queue;
+ if (name.empty()) {
+ throw framing::IllegalArgumentException(QPID_MSG("No queue name specified."));
+ } else {
+ queue = session.getBroker().getQueues().get(name);
+ }
+ return queue;
+}
+
+}} // namespace qpid::broker
+
+
diff --git a/qpid/cpp/src/qpid/broker/SessionAdapter.h b/qpid/cpp/src/qpid/broker/SessionAdapter.h
new file mode 100644
index 0000000000..6c09d68254
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SessionAdapter.h
@@ -0,0 +1,272 @@
+#ifndef _broker_SessionAdapter_h
+#define _broker_SessionAdapter_h
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/broker/HandlerImpl.h"
+
+#include "qpid/Exception.h"
+#include "qpid/framing/AMQP_ServerOperations.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/framing/StructHelper.h"
+
+#include <algorithm>
+#include <vector>
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace broker {
+
+class Channel;
+class Connection;
+class Broker;
+class Queue;
+
+/**
+ * SessionAdapter translates protocol-specific AMQP commands for one
+ * specific version of AMQP into calls on the core broker objects. It
+ * is a container for a collection of adapters.
+ *
+ * Each adapter class provides a client proxy to send methods to the
+ * peer broker or client.
+ *
+ */
+
+ class SessionAdapter : public HandlerImpl, public framing::AMQP_ServerOperations
+{
+ public:
+ SessionAdapter(SemanticState& session);
+
+ framing::ProtocolVersion getVersion() const { return session.getConnection().getVersion();}
+
+ MessageHandler* getMessageHandler(){ return &messageImpl; }
+ ExchangeHandler* getExchangeHandler(){ return &exchangeImpl; }
+ QueueHandler* getQueueHandler(){ return &queueImpl; }
+ ExecutionHandler* getExecutionHandler(){ return &executionImpl; }
+ TxHandler* getTxHandler(){ return &txImpl; }
+ DtxHandler* getDtxHandler(){ return &dtxImpl; }
+
+ ConnectionHandler* getConnectionHandler() { throw framing::NotImplementedException("Class not implemented"); }
+ SessionHandler* getSessionHandler() { throw framing::NotImplementedException("Class not implemented"); }
+ FileHandler* getFileHandler() { throw framing::NotImplementedException("Class not implemented"); }
+ StreamHandler* getStreamHandler() { throw framing::NotImplementedException("Class not implemented"); }
+
+ template <class F> void eachExclusiveQueue(F f)
+ {
+ queueImpl.eachExclusiveQueue(f);
+ }
+
+
+ private:
+ //common base for utility methods etc that are specific to this adapter
+ struct HandlerHelper : public HandlerImpl
+ {
+ HandlerHelper(SemanticState& s) : HandlerImpl(s) {}
+
+ boost::shared_ptr<Queue> getQueue(const std::string& name) const;
+ };
+
+
+ class ExchangeHandlerImpl :
+ public ExchangeHandler,
+ public HandlerHelper
+ {
+ public:
+ ExchangeHandlerImpl(SemanticState& session) : HandlerHelper(session) {}
+
+ void declare(const std::string& exchange, const std::string& type,
+ const std::string& alternateExchange,
+ bool passive, bool durable, bool autoDelete,
+ const qpid::framing::FieldTable& arguments);
+ void delete_(const std::string& exchange, bool ifUnused);
+ framing::ExchangeQueryResult query(const std::string& name);
+ void bind(const std::string& queue,
+ const std::string& exchange, const std::string& routingKey,
+ const qpid::framing::FieldTable& arguments);
+ void unbind(const std::string& queue,
+ const std::string& exchange,
+ const std::string& routingKey);
+ framing::ExchangeBoundResult bound(const std::string& exchange,
+ const std::string& queue,
+ const std::string& routingKey,
+ const framing::FieldTable& arguments);
+ private:
+ void checkType(boost::shared_ptr<Exchange> exchange, const std::string& type);
+
+ void checkAlternate(boost::shared_ptr<Exchange> exchange,
+ boost::shared_ptr<Exchange> alternate);
+ };
+
+ class QueueHandlerImpl : public QueueHandler,
+ public HandlerHelper
+ {
+ Broker& broker;
+ std::vector< boost::shared_ptr<Queue> > exclusiveQueues;
+ //connectionId and userId are needed for queue-delete events for auto deleted, exclusive queues
+ std::string connectionId;
+ std::string userId;
+
+ public:
+ QueueHandlerImpl(SemanticState& session);
+ ~QueueHandlerImpl();
+
+ void declare(const std::string& queue,
+ const std::string& alternateExchange,
+ bool passive, bool durable, bool exclusive,
+ bool autoDelete,
+ const qpid::framing::FieldTable& arguments);
+ void delete_(const std::string& queue,
+ bool ifUnused, bool ifEmpty);
+ void purge(const std::string& queue);
+ framing::QueueQueryResult query(const std::string& queue);
+ bool isLocal(const OwnershipToken* t) const;
+
+ void destroyExclusiveQueues();
+ void checkDelete(boost::shared_ptr<Queue> queue, bool ifUnused, bool ifEmpty);
+ template <class F> void eachExclusiveQueue(F f)
+ {
+ std::for_each(exclusiveQueues.begin(), exclusiveQueues.end(), f);
+ }
+ };
+
+ class MessageHandlerImpl :
+ public MessageHandler,
+ public HandlerHelper
+ {
+ typedef boost::function<void(DeliveryId, DeliveryId)> RangedOperation;
+ RangedOperation releaseRedeliveredOp;
+ RangedOperation releaseOp;
+ RangedOperation rejectOp;
+ RangedOperation acceptOp;
+
+ public:
+ MessageHandlerImpl(SemanticState& session);
+ void transfer(const std::string& destination,
+ uint8_t acceptMode,
+ uint8_t acquireMode);
+
+ void accept(const framing::SequenceSet& commands);
+
+ void reject(const framing::SequenceSet& commands,
+ uint16_t code,
+ const std::string& text);
+
+ void release(const framing::SequenceSet& commands,
+ bool setRedelivered);
+
+ framing::MessageAcquireResult acquire(const framing::SequenceSet&);
+
+ void subscribe(const std::string& queue,
+ const std::string& destination,
+ uint8_t acceptMode,
+ uint8_t acquireMode,
+ bool exclusive,
+ const std::string& resumeId,
+ uint64_t resumeTtl,
+ const framing::FieldTable& arguments);
+
+ void cancel(const std::string& destination);
+
+ void setFlowMode(const std::string& destination,
+ uint8_t flowMode);
+
+ void flow(const std::string& destination,
+ uint8_t unit,
+ uint32_t value);
+
+ void flush(const std::string& destination);
+
+ void stop(const std::string& destination);
+
+ framing::MessageResumeResult resume(const std::string& destination,
+ const std::string& resumeId);
+
+ };
+
+ class ExecutionHandlerImpl : public ExecutionHandler, public HandlerHelper
+ {
+ public:
+ ExecutionHandlerImpl(SemanticState& session) : HandlerHelper(session) {}
+
+ void sync();
+ void result(const framing::SequenceNumber& commandId, const std::string& value);
+ void exception(uint16_t errorCode,
+ const framing::SequenceNumber& commandId,
+ uint8_t classCode,
+ uint8_t commandCode,
+ uint8_t fieldIndex,
+ const std::string& description,
+ const framing::FieldTable& errorInfo);
+
+ };
+
+ class TxHandlerImpl : public TxHandler, public HandlerHelper
+ {
+ public:
+ TxHandlerImpl(SemanticState& session) : HandlerHelper(session) {}
+
+ void select();
+ void commit();
+ void rollback();
+ };
+
+ class DtxHandlerImpl : public DtxHandler, public HandlerHelper
+ {
+ public:
+ DtxHandlerImpl(SemanticState& session) : HandlerHelper(session) {}
+
+ void select();
+
+ framing::XaResult start(const framing::Xid& xid,
+ bool join,
+ bool resume);
+
+ framing::XaResult end(const framing::Xid& xid,
+ bool fail,
+ bool suspend);
+
+ framing::XaResult commit(const framing::Xid& xid,
+ bool onePhase);
+
+ void forget(const framing::Xid& xid);
+
+ framing::DtxGetTimeoutResult getTimeout(const framing::Xid& xid);
+
+ framing::XaResult prepare(const framing::Xid& xid);
+
+ framing::DtxRecoverResult recover();
+
+ framing::XaResult rollback(const framing::Xid& xid);
+
+ void setTimeout(const framing::Xid& xid, uint32_t timeout);
+ };
+
+ ExchangeHandlerImpl exchangeImpl;
+ QueueHandlerImpl queueImpl;
+ MessageHandlerImpl messageImpl;
+ ExecutionHandlerImpl executionImpl;
+ TxHandlerImpl txImpl;
+ DtxHandlerImpl dtxImpl;
+};
+}} // namespace qpid::broker
+
+
+
+#endif /*!_broker_SessionAdapter_h*/
diff --git a/qpid/cpp/src/qpid/broker/SessionContext.h b/qpid/cpp/src/qpid/broker/SessionContext.h
new file mode 100644
index 0000000000..53f3e1bab3
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SessionContext.h
@@ -0,0 +1,61 @@
+#ifndef QPID_BROKER_SESSIONCONTEXT_H
+#define QPID_BROKER_SESSIONCONTEXT_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/OwnershipToken.h"
+
+#include <boost/noncopyable.hpp>
+
+namespace qpid {
+
+class SessionId;
+
+namespace framing {
+class AMQP_ClientProxy;
+}
+namespace broker {
+
+class Broker;
+namespace amqp_0_10 {
+class Connection;
+}
+
+class SessionContext : public OwnershipToken
+{
+ public:
+ virtual ~SessionContext(){}
+ virtual bool isAttached() const = 0;
+ virtual amqp_0_10::Connection& getConnection() = 0;
+ virtual framing::AMQP_ClientProxy& getProxy() = 0;
+ virtual Broker& getBroker() = 0;
+ virtual uint16_t getChannel() const = 0;
+ virtual const SessionId& getSessionId() const = 0;
+ virtual bool addPendingExecutionSync() = 0;
+ virtual void setUnackedCount(uint64_t) {}
+};
+
+}} // namespace qpid::broker
+
+
+
+#endif /*!QPID_BROKER_SESSIONCONTEXT_H*/
diff --git a/qpid/cpp/src/qpid/broker/SessionHandler.cpp b/qpid/cpp/src/qpid/broker/SessionHandler.cpp
new file mode 100644
index 0000000000..3d5faf2dab
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SessionHandler.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/broker/SessionHandler.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/amqp_0_10/Connection.h"
+#include "qpid/broker/SessionState.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/ConnectionOutputHandler.h"
+
+#include <boost/bind.hpp>
+
+namespace qpid {
+namespace broker {
+using namespace framing;
+using namespace std;
+using namespace qpid::sys;
+
+namespace {
+class DefaultErrorListener : public SessionHandler::ErrorListener {
+ public:
+ void connectionException(framing::connection::CloseCode code, const std::string& msg) {
+ QPID_LOG(error, "Connection exception: " << framing::createConnectionException(code, msg).what());
+ }
+ void channelException(framing::session::DetachCode code, const std::string& msg) {
+ QPID_LOG(error, "Channel exception: " << framing::createChannelException(code, msg).what());
+ }
+ void executionException(framing::execution::ErrorCode code, const std::string& msg) {
+ QPID_LOG(error, "Execution exception: " << framing::createSessionException(code, msg).what());
+ }
+ void incomingExecutionException(framing::execution::ErrorCode code, const std::string& msg) {
+ QPID_LOG(debug, "Incoming execution exception: " << framing::createSessionException(code, msg).what());
+ }
+ void detach() {}
+
+ private:
+};
+}
+
+SessionHandler::SessionHandler(amqp_0_10::Connection& c, ChannelId ch)
+ : qpid::amqp_0_10::SessionHandler(&c.getOutput(), ch),
+ connection(c),
+ proxy(out),
+ errorListener(boost::shared_ptr<ErrorListener>(new DefaultErrorListener()))
+{
+ c.getBroker().getSessionHandlerObservers().newSessionHandler(*this);
+}
+
+SessionHandler::~SessionHandler()
+{
+ if (session.get())
+ connection.getBroker().getSessionManager().forget(session->getId());
+}
+
+void SessionHandler::connectionException(
+ framing::connection::CloseCode code, const std::string& msg)
+{
+ // NOTE: must tell the error listener _before_ calling connection.close()
+ if (errorListener)
+ errorListener->connectionException(code, msg);
+ connection.close(code, msg);
+}
+
+void SessionHandler::channelException(
+ framing::session::DetachCode code, const std::string& msg)
+{
+ if (errorListener)
+ errorListener->channelException(code, msg);
+}
+
+void SessionHandler::executionException(
+ framing::execution::ErrorCode code, const std::string& msg)
+{
+ if (errorListener)
+ errorListener->executionException(code, msg);
+}
+
+void SessionHandler::incomingExecutionException(
+ framing::execution::ErrorCode code, const std::string& msg)
+{
+ if (errorListener)
+ errorListener->incomingExecutionException(code, msg);
+}
+
+amqp_0_10::Connection& SessionHandler::getConnection() { return connection; }
+
+const amqp_0_10::Connection& SessionHandler::getConnection() const { return connection; }
+
+void SessionHandler::handleDetach() {
+ qpid::amqp_0_10::SessionHandler::handleDetach();
+ assert(&connection.getChannel(channel.get()) == this);
+ if (session.get())
+ connection.getBroker().getSessionManager().detach(session);
+ assert(!session.get());
+ if (errorListener) errorListener->detach();
+ connection.closeChannel(channel.get());
+}
+
+void SessionHandler::setState(const std::string& name, bool force) {
+ assert(!session.get());
+ SessionId id(connection.getUserId(), name);
+ session = connection.getBroker().getSessionManager().attach(*this, id, force);
+}
+
+void SessionHandler::detaching()
+{
+ assert(session.get());
+ session->disableOutput();
+}
+
+FrameHandler* SessionHandler::getInHandler() { return session.get() ? &session->in : 0; }
+qpid::SessionState* SessionHandler::getState() { return session.get(); }
+
+void SessionHandler::readyToSend() {
+ if (session.get()) session->readyToSend();
+}
+
+/**
+ * Used by inter-broker bridges to set up session id and attach
+ */
+void SessionHandler::attachAs(const std::string& name)
+{
+ SessionId id(connection.getUserId(), name);
+ SessionState::Configuration config = connection.getBroker().getSessionManager().getSessionConfig();
+ session.reset(new SessionState(connection.getBroker(), *this, id, config));
+ sendAttach(false);
+}
+
+/**
+ * TODO: this is a little ugly, fix it; its currently still relied on
+ * for 'push' bridges
+ */
+void SessionHandler::attached(const std::string& name)
+{
+ if (session.get()) {
+ session->addManagementObject(); // Delayed from attachAs()
+ qpid::amqp_0_10::SessionHandler::attached(name);
+ } else {
+ SessionId id(connection.getUserId(), name);
+ SessionState::Configuration config = connection.getBroker().getSessionManager().getSessionConfig();
+ session.reset(new SessionState(connection.getBroker(), *this, id, config));
+ markReadyToSend();
+ }
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/SessionHandler.h b/qpid/cpp/src/qpid/broker/SessionHandler.h
new file mode 100644
index 0000000000..ee6baed0b6
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SessionHandler.h
@@ -0,0 +1,121 @@
+#ifndef QPID_BROKER_SESSIONHANDLER_H
+#define QPID_BROKER_SESSIONHANDLER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/amqp_0_10/SessionHandler.h"
+#include "qpid/broker/SessionHandler.h"
+#include "qpid/framing/AMQP_ClientProxy.h"
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+class SessionState;
+
+namespace broker {
+namespace amqp_0_10 {
+class Connection;
+}
+class SessionState;
+
+/**
+ * A SessionHandler is associated with each active channel. It
+ * receives incoming frames, handles session controls and manages the
+ * association between the channel and a session.
+ */
+class SessionHandler : public qpid::amqp_0_10::SessionHandler {
+ public:
+ class ErrorListener {
+ public:
+ virtual ~ErrorListener() {}
+
+ /** Called when there is an outgoing connection-exception */
+ virtual void connectionException(
+ framing::connection::CloseCode code, const std::string& msg) = 0;
+ /** Called when there is an outgoing channel-exception */
+ virtual void channelException(
+ framing::session::DetachCode, const std::string& msg) = 0;
+ /** Called when there is an outgoing execution-exception */
+ virtual void executionException(
+ framing::execution::ErrorCode, const std::string& msg) = 0;
+
+ /** Called when there is an incoming execution-exception.
+ * Useful for inter-broker bridges.
+ */
+ virtual void incomingExecutionException(
+ framing::execution::ErrorCode, const std::string& msg) = 0;
+
+ /** Called when it is safe to delete the ErrorListener. */
+ virtual void detach() = 0;
+ };
+
+ /**
+ *@param e must not be deleted until ErrorListener::detach has been called */
+ SessionHandler(amqp_0_10::Connection&, framing::ChannelId);
+ ~SessionHandler();
+
+ /** Get broker::SessionState */
+ SessionState* getSession() { return session.get(); }
+ const SessionState* getSession() const { return session.get(); }
+
+ QPID_BROKER_EXTERN amqp_0_10::Connection& getConnection();
+ QPID_BROKER_EXTERN const amqp_0_10::Connection& getConnection() const;
+
+ framing::AMQP_ClientProxy& getProxy() { return proxy; }
+ const framing::AMQP_ClientProxy& getProxy() const { return proxy; }
+
+ virtual void handleDetach();
+ void attached(const std::string& name);//used by 'pushing' inter-broker bridges
+ void attachAs(const std::string& name);//used by 'pulling' inter-broker bridges
+
+ QPID_BROKER_EXTERN void setErrorListener(boost::shared_ptr<ErrorListener> e) { errorListener = e; }
+
+ // Called by SessionAdapter
+ void incomingExecutionException(framing::execution::ErrorCode, const std::string& msg);
+
+ protected:
+ void setState(const std::string& sessionName, bool force);
+ qpid::SessionState* getState();
+ framing::FrameHandler* getInHandler();
+ void connectionException(framing::connection::CloseCode code, const std::string& msg);
+ void channelException(framing::session::DetachCode, const std::string& msg);
+ void executionException(framing::execution::ErrorCode, const std::string& msg);
+ void detaching();
+ void readyToSend();
+
+ private:
+ struct SetChannelProxy : public framing::AMQP_ClientProxy { // Proxy that sets the channel.
+ framing::ChannelHandler setChannel;
+ SetChannelProxy(uint16_t ch, framing::FrameHandler* out)
+ : framing::AMQP_ClientProxy(setChannel), setChannel(ch, out) {}
+ };
+
+ amqp_0_10::Connection& connection;
+ framing::AMQP_ClientProxy proxy;
+ std::auto_ptr<SessionState> session;
+ boost::shared_ptr<ErrorListener> errorListener;
+};
+
+}} // namespace qpid::broker
+
+
+
+#endif /*!QPID_BROKER_SESSIONHANDLER_H*/
diff --git a/qpid/cpp/src/qpid/broker/SessionHandlerObserver.h b/qpid/cpp/src/qpid/broker/SessionHandlerObserver.h
new file mode 100644
index 0000000000..6d0ea16254
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SessionHandlerObserver.h
@@ -0,0 +1,51 @@
+#ifndef QPID_BROKER_SESSIONHANDLEROBSERVER_H
+#define QPID_BROKER_SESSIONHANDLEROBSERVER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "Observers.h"
+
+namespace qpid {
+namespace broker {
+class SessionHandler;
+
+/**
+ * Observer of session handler events.
+ */
+class SessionHandlerObserver
+{
+ public:
+ virtual ~SessionHandlerObserver() {}
+ virtual void newSessionHandler(SessionHandler&) {}
+};
+
+
+class SessionHandlerObservers : public Observers<SessionHandlerObserver> {
+ public:
+ void newSessionHandler(SessionHandler& sh) {
+ each(boost::bind(&SessionHandlerObserver::newSessionHandler, _1, boost::ref(sh)));
+ }
+};
+
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_SESSIONHANDLEROBSERVER_H*/
diff --git a/qpid/cpp/src/qpid/broker/SessionManager.cpp b/qpid/cpp/src/qpid/broker/SessionManager.cpp
new file mode 100644
index 0000000000..a39cbab88a
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SessionManager.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/broker/SessionManager.h"
+#include "qpid/broker/SessionState.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/log/Statement.h"
+#include "qpid/log/Helpers.h"
+#include "qpid/memory.h"
+
+#include <boost/bind.hpp>
+#include <boost/range.hpp>
+
+#include <algorithm>
+#include <functional>
+#include <ostream>
+
+namespace qpid {
+namespace broker {
+
+using boost::intrusive_ptr;
+using namespace sys;
+using namespace framing;
+
+SessionManager::SessionManager(const SessionState::Configuration& c, Broker& b)
+ : config(c), broker(b) {}
+
+SessionManager::~SessionManager() {
+ detached.clear(); // Must clear before destructor as session dtor will call forget()
+}
+
+std::auto_ptr<SessionState> SessionManager::attach(SessionHandler& h, const SessionId& id, bool force) {
+ Mutex::ScopedLock l(lock);
+ eraseExpired(); // Clean up expired table
+ std::pair<Attached::iterator, bool> insert = attached.insert(id);
+ if (!insert.second && !force)
+ throw SessionBusyException(QPID_MSG("Session already attached: " << id));
+ Detached::iterator i = std::find(detached.begin(), detached.end(), id);
+ std::auto_ptr<SessionState> state;
+ if (i == detached.end())
+ state.reset(new SessionState(broker, h, id, config));
+ else {
+ state.reset(detached.release(i).release());
+ state->attach(h);
+ }
+ return state;
+}
+
+void SessionManager::detach(std::auto_ptr<SessionState> session) {
+ Mutex::ScopedLock l(lock);
+ attached.erase(session->getId());
+ session->detach();
+ if (session->getTimeout() > 0) {
+ session->expiry = AbsTime(now(), session->getTimeout()*TIME_SEC);
+ if (session->mgmtObject != 0)
+ session->mgmtObject->set_expireTime (Duration::FromEpoch()+session->getTimeout()*TIME_SEC);
+ detached.push_back(session.release()); // In expiry order
+ eraseExpired();
+}
+}
+
+void SessionManager::forget(const SessionId& id) {
+ Mutex::ScopedLock l(lock);
+ attached.erase(id);
+}
+
+void SessionManager::eraseExpired() {
+ // Called with lock held.
+ if (!detached.empty()) {
+ // This used to use a more elegant invocation of std::lower_bound
+ // but violated the strict weak ordering rule which Visual Studio
+ // enforced. See QPID-1424 for more info should you be tempted to
+ // replace the loop with something more elegant.
+ AbsTime now = AbsTime::now();
+ Detached::iterator keep = detached.begin();
+ while ((keep != detached.end()) && ((*keep).expiry < now))
+ keep++;
+ if (detached.begin() != keep) {
+ QPID_LOG(debug, "Expiring sessions: " << log::formatList(detached.begin(), keep));
+ detached.erase(detached.begin(), keep);
+ }
+ }
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/SessionManager.h b/qpid/cpp/src/qpid/broker/SessionManager.h
new file mode 100644
index 0000000000..db88e7ec10
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SessionManager.h
@@ -0,0 +1,87 @@
+#ifndef QPID_BROKER_SESSIONMANAGER_H
+#define QPID_BROKER_SESSIONMANAGER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <qpid/SessionState.h>
+#include <qpid/sys/Time.h>
+#include <qpid/sys/Mutex.h>
+#include <qpid/RefCounted.h>
+
+#include <set>
+#include <vector>
+#include <memory>
+
+#include <boost/noncopyable.hpp>
+#include <boost/ptr_container/ptr_vector.hpp>
+#include <boost/intrusive_ptr.hpp>
+
+namespace qpid {
+namespace broker {
+class Broker;
+class SessionState;
+class SessionHandler;
+
+/**
+ * Create and manage SessionState objects.
+ */
+class SessionManager : private boost::noncopyable {
+ public:
+ SessionManager(const qpid::SessionState::Configuration&, Broker&);
+
+ ~SessionManager();
+
+ /** Open a new active session, caller takes ownership */
+ std::auto_ptr<SessionState> attach(SessionHandler& h, const SessionId& id, bool/*force*/);
+
+ /** Return a detached session to the manager, start the timeout counter. */
+ void detach(std::auto_ptr<SessionState>);
+
+ /** Forget about an attached session. Called by SessionState destructor. */
+ void forget(const SessionId&);
+
+ Broker& getBroker() const { return broker; }
+
+ const qpid::SessionState::Configuration& getSessionConfig() const { return config; }
+
+ private:
+ typedef boost::ptr_vector<SessionState> Detached; // Sorted in expiry order.
+ typedef std::set<SessionId> Attached;
+
+ void eraseExpired();
+
+ sys::Mutex lock;
+ Detached detached;
+ Attached attached;
+ qpid::SessionState::Configuration config;
+ Broker& broker;
+};
+
+
+
+}} // namespace qpid::broker
+
+
+
+
+
+#endif /*!QPID_BROKER_SESSIONMANAGER_H*/
diff --git a/qpid/cpp/src/qpid/broker/SessionOutputException.h b/qpid/cpp/src/qpid/broker/SessionOutputException.h
new file mode 100644
index 0000000000..7c1c5de926
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SessionOutputException.h
@@ -0,0 +1,47 @@
+#ifndef QPID_BROKER_SESSIONOUTPUTEXCEPTION_H
+#define QPID_BROKER_SESSIONOUTPUTEXCEPTION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/Exception.h"
+
+namespace qpid {
+namespace broker {
+
+/**
+ * This exception is used to signal 'session' exceptions (aka
+ * execution exceptions in AMQP 0-10 terms) that occur during output
+ * processing. It simply allows the channel of the session to be
+ * specified in addition to the other details. Special treatment is
+ * required at present because the output processing chain is
+ * different from that which handles incoming commands (specifically
+ * AggregateOutput cannot reasonably handle exceptions as it has no
+ * context).
+ */
+struct SessionOutputException : qpid::SessionException
+{
+ const uint16_t channel;
+ SessionOutputException(const qpid::SessionException& e, uint16_t c) : qpid::SessionException(e.code, e.getMessage()), channel(c) {}
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_SESSIONOUTPUTEXCEPTION_H*/
diff --git a/qpid/cpp/src/qpid/broker/SessionState.cpp b/qpid/cpp/src/qpid/broker/SessionState.cpp
new file mode 100644
index 0000000000..6836794622
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SessionState.cpp
@@ -0,0 +1,527 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/SessionState.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/DeliverableMessage.h"
+#include "qpid/broker/DeliveryRecord.h"
+#include "qpid/broker/SessionManager.h"
+#include "qpid/broker/SessionHandler.h"
+#include "qpid/framing/AMQContentBody.h"
+#include "qpid/framing/AMQHeaderBody.h"
+#include "qpid/framing/AMQMethodBody.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/framing/ServerInvoker.h"
+#include "qpid/log/Statement.h"
+#include "qpid/management/ManagementAgent.h"
+
+#include <boost/bind.hpp>
+#include <boost/lexical_cast.hpp>
+
+namespace qpid {
+namespace broker {
+
+using namespace framing;
+using sys::Mutex;
+using boost::intrusive_ptr;
+using qpid::management::ManagementAgent;
+using qpid::management::ManagementObject;
+using qpid::management::Manageable;
+using qpid::management::Args;
+using qpid::sys::AbsTime;
+//using qpid::sys::Timer;
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+SessionState::SessionState(
+ Broker& b, SessionHandler& h, const SessionId& id,
+ const SessionState::Configuration& config)
+ : qpid::SessionState(id, config),
+ broker(b), handler(&h),
+ semanticState(*this),
+ adapter(semanticState),
+ asyncCommandCompleter(new AsyncCommandCompleter(this))
+{
+ addManagementObject();
+ attach(h);
+}
+
+void SessionState::addManagementObject() {
+ if (GetManagementObject()) return; // Already added.
+ Manageable* parent = broker.GetVhostObject ();
+ if (parent != 0) {
+ ManagementAgent* agent = getBroker().getManagementAgent();
+ if (agent != 0) {
+ std::string name(getId().str());
+ std::string fullName(name);
+ if (name.length() >= std::numeric_limits<uint8_t>::max())
+ name.resize(std::numeric_limits<uint8_t>::max()-1);
+ mgmtObject = _qmf::Session::shared_ptr(new _qmf::Session (agent, this, parent, name));
+ mgmtObject->set_fullName (fullName);
+ mgmtObject->set_attached (0);
+ mgmtObject->clr_expireTime();
+ agent->addObject(mgmtObject);
+ }
+ }
+}
+
+void SessionState::startTx() {
+ if (mgmtObject) { mgmtObject->inc_TxnStarts(); }
+}
+
+void SessionState::commitTx() {
+ if (mgmtObject) {
+ mgmtObject->inc_TxnCommits();
+ mgmtObject->inc_TxnCount();
+ }
+}
+
+void SessionState::rollbackTx() {
+ if (mgmtObject) {
+ mgmtObject->inc_TxnRejects();
+ mgmtObject->inc_TxnCount();
+ }
+}
+
+SessionState::~SessionState() {
+ if (mgmtObject != 0)
+ mgmtObject->debugStats("destroying");
+ asyncCommandCompleter->cancel();
+ semanticState.closed();
+ if (mgmtObject != 0)
+ mgmtObject->resourceDestroy ();
+}
+
+AMQP_ClientProxy& SessionState::getProxy() {
+ assert(isAttached());
+ return handler->getProxy();
+}
+
+uint16_t SessionState::getChannel() const {
+ assert(isAttached());
+ return handler->getChannel();
+}
+
+amqp_0_10::Connection& SessionState::getConnection() {
+ assert(isAttached());
+ return handler->getConnection();
+}
+
+bool SessionState::isLocal(const OwnershipToken* t) const
+{
+ return isAttached() && &(handler->getConnection()) == t;
+}
+
+void SessionState::detach() {
+ QPID_LOG(debug, getId() << ": detached on broker.");
+ asyncCommandCompleter->detached();
+ disableOutput();
+ handler = 0;
+ if (mgmtObject != 0)
+ mgmtObject->set_attached (0);
+}
+
+void SessionState::disableOutput()
+{
+ semanticState.detached(); //prevents further activateOutput calls until reattached
+}
+
+void SessionState::attach(SessionHandler& h) {
+ QPID_LOG(debug, getId() << ": attached on broker.");
+ handler = &h;
+ if (mgmtObject != 0)
+ {
+ mgmtObject->set_attached (1);
+ mgmtObject->set_connectionRef (h.getConnection().GetManagementObject()->getObjectId());
+ mgmtObject->set_channelId (h.getChannel());
+ }
+ asyncCommandCompleter->attached();
+}
+
+ManagementObject::shared_ptr SessionState::GetManagementObject(void) const
+{
+ return mgmtObject;
+}
+
+Manageable::status_t SessionState::ManagementMethod (uint32_t methodId,
+ Args& /*args*/,
+ std::string& /*text*/)
+{
+ Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD;
+
+ switch (methodId)
+ {
+ case _qmf::Session::METHOD_DETACH :
+ if (handler != 0) {
+ handler->sendDetach();
+ }
+ status = Manageable::STATUS_OK;
+ break;
+
+ case _qmf::Session::METHOD_CLOSE :
+ /*
+ if (handler != 0)
+ {
+ handler->getConnection().closeChannel(handler->getChannel());
+ }
+ status = Manageable::STATUS_OK;
+ break;
+ */
+
+ case _qmf::Session::METHOD_SOLICITACK :
+ case _qmf::Session::METHOD_RESETLIFESPAN :
+ status = Manageable::STATUS_NOT_IMPLEMENTED;
+ break;
+ }
+
+ return status;
+}
+
+void SessionState::handleCommand(framing::AMQMethodBody* method) {
+ Invoker::Result result = invoke(adapter, *method);
+ if (!result.wasHandled())
+ throw NotImplementedException(QPID_MSG("Not implemented: " << *method));
+ if (currentCommand.isCompleteSync())
+ completeCommand(
+ currentCommand.getId(), false/*needAccept*/, currentCommand.isSyncRequired(),
+ result.getResult());
+}
+
+
+void SessionState::handleContent(AMQFrame& frame)
+{
+ if (frame.getBof() && frame.getBos()) //start of frameset
+ msgBuilder.start(currentCommand.getId());
+ intrusive_ptr<qpid::broker::amqp_0_10::MessageTransfer> msg(msgBuilder.getMessage());
+ msgBuilder.handle(frame);
+ if (frame.getEof() && frame.getEos()) {//end of frameset
+ if (frame.getBof()) {
+ //i.e this is a just a command frame, add a dummy header
+ AMQFrame header((AMQHeaderBody()));
+ header.setBof(false);
+ header.setEof(false);
+ msg->getFrames().append(header);
+ }
+ DeliverableMessage deliverable(Message(msg, msg), semanticState.getTxBuffer());
+ if (broker.isTimestamping())
+ msg->setTimestamp();
+ msg->setPublisher(&(getConnection()));
+ msg->computeExpiration();
+
+
+ IncompleteIngressMsgXfer xfer(this, msg);
+ msg->getIngressCompletion().begin();
+ // This call should come before routing, because it calcs required credit.
+ msgBuilder.end();
+ semanticState.route(deliverable.getMessage(), deliverable);
+ msg->getIngressCompletion().end(xfer); // allows msg to complete xfer
+ }
+}
+
+void SessionState::sendAcceptAndCompletion()
+{
+ if (!accepted.empty()) {
+ getProxy().getMessage().accept(accepted);
+ accepted.clear();
+ }
+ sendCompletion();
+}
+
+/** Invoked when the given command is finished being processed by all interested
+ * parties (eg. it is done being enqueued to all queues, its credit has been
+ * accounted for, etc). At this point the command is considered by this
+ * receiver as 'completed' (as defined by AMQP 0_10)
+ */
+void SessionState::completeCommand(SequenceNumber id,
+ bool requiresAccept,
+ bool requiresSync,
+ const std::string& result=std::string())
+{
+ bool callSendCompletion = false;
+ receiverCompleted(id);
+ if (requiresAccept)
+ // will cause cmd's seq to appear in the next message.accept we send.
+ accepted.add(id);
+
+ if (!result.empty())
+ getProxy().getExecution().result(id, result);
+
+ // Are there any outstanding Execution.Sync commands pending the
+ // completion of this cmd? If so, complete them.
+ while (!pendingExecutionSyncs.empty() &&
+ (receiverGetIncomplete().empty() ||
+ receiverGetIncomplete().front() >= pendingExecutionSyncs.front()))
+ {
+ const SequenceNumber syncId = pendingExecutionSyncs.front();
+ pendingExecutionSyncs.pop();
+ QPID_LOG(debug, getId() << ": delayed execution.sync " << syncId << " is completed.");
+ if (receiverGetIncomplete().contains(syncId))
+ receiverCompleted(syncId);
+ callSendCompletion = true; // likely peer is pending for this completion.
+ }
+
+ // if the sender has requested immediate notification of the completion...
+ if (requiresSync || callSendCompletion) {
+ sendAcceptAndCompletion();
+ }
+}
+
+void SessionState::handleIn(AMQFrame& frame) {
+ //TODO: make command handling more uniform, regardless of whether
+ //commands carry content.
+ AMQMethodBody* m = frame.getMethod();
+ currentCommand = CurrentCommand(receiverGetCurrent(), m && m->isSync());
+
+ if (m == 0 || m->isContentBearing()) {
+ handleContent(frame);
+ } else if (frame.getBof() && frame.getEof()) {
+ handleCommand(frame.getMethod());
+ } else {
+ throw InternalErrorException("Cannot handle multi-frame command segments yet");
+ }
+}
+
+void SessionState::handleOut(AMQFrame& frame) {
+ assert(handler);
+ handler->out(frame);
+}
+
+DeliveryId SessionState::deliver(const qpid::broker::amqp_0_10::MessageTransfer& message,
+ const std::string& destination, bool isRedelivered, uint64_t ttl,
+ qpid::framing::message::AcceptMode acceptMode, qpid::framing::message::AcquireMode acquireMode,
+ const qpid::types::Variant::Map& annotations, bool sync)
+{
+ uint32_t maxFrameSize = getConnection().getFrameMax();
+ assert(senderGetCommandPoint().offset == 0);
+ SequenceNumber commandId = senderGetCommandPoint().command;
+
+ framing::AMQFrame method((framing::MessageTransferBody(framing::ProtocolVersion(), destination, acceptMode, acquireMode)));
+ method.setEof(false);
+ getProxy().getHandler().handle(method);
+ message.sendHeader(getProxy().getHandler(), maxFrameSize, isRedelivered, ttl, annotations);
+ message.sendContent(getProxy().getHandler(), maxFrameSize);
+
+ assert(senderGetCommandPoint() == SessionPoint(commandId+1, 0)); // Delivery has moved sendPoint.
+ if (sync) {
+ AMQP_ClientProxy::Execution& p(getProxy().getExecution());
+ Proxy::ScopedSync s(p);
+ p.sync();
+ }
+ return commandId;
+}
+
+void SessionState::sendCompletion() {
+ handler->sendCompletion();
+}
+
+void SessionState::senderCompleted(const SequenceSet& commands) {
+ qpid::SessionState::senderCompleted(commands);
+ semanticState.completed(commands);
+}
+
+void SessionState::readyToSend() {
+ QPID_LOG(debug, getId() << ": ready to send, activating output.");
+ assert(handler);
+ semanticState.attached();
+}
+
+Broker& SessionState::getBroker() { return broker; }
+
+// Session resume is not fully implemented so it is useless to set a
+// non-0 timeout.
+void SessionState::setTimeout(uint32_t) { }
+
+// Current received command is an execution.sync command.
+// Complete this command only when all preceding commands have completed.
+// (called via the invoker() in handleCommand() above)
+bool SessionState::addPendingExecutionSync() {
+ SequenceNumber id = currentCommand.getId();
+ if (addPendingExecutionSync(id)) {
+ currentCommand.setCompleteSync(false);
+ QPID_LOG(debug, getId() << ": delaying completion of execution.sync " << id);
+ return true;
+ }
+ return false;
+}
+
+bool SessionState::addPendingExecutionSync(SequenceNumber id)
+{
+ if (receiverGetIncomplete().front() < id) {
+ pendingExecutionSyncs.push(id);
+ asyncCommandCompleter->flushPendingMessages();
+ return true;
+ }
+ return false;
+}
+
+/** factory for creating a reference-counted IncompleteIngressMsgXfer object
+ * which will be attached to a message that will be completed asynchronously.
+ */
+boost::intrusive_ptr<AsyncCompletion::Callback>
+SessionState::IncompleteIngressMsgXfer::clone()
+{
+ // Optimization: this routine is *only* invoked when the message needs to be asynchronously completed.
+ // If the client is pending the message.transfer completion, flush now to force immediate write to journal.
+ if (requiresSync)
+ msg->flush();
+ else {
+ // otherwise, we need to track this message in order to flush it if an execution.sync arrives
+ // before it has been completed (see flushPendingMessages())
+ pending = true;
+ completerContext->addPendingMessage(msg);
+ }
+
+ return boost::intrusive_ptr<SessionState::IncompleteIngressMsgXfer>(new SessionState::IncompleteIngressMsgXfer(*this));
+}
+
+
+/** Invoked by the asynchronous completer associated with a received
+ * msg that is pending Completion. May be invoked by the IO thread
+ * (sync == true), or some external thread (!sync).
+ */
+void SessionState::IncompleteIngressMsgXfer::completed(bool sync)
+{
+ if (pending) completerContext->deletePendingMessage(id);
+ if (!sync) {
+ /** note well: this path may execute in any thread. It is safe to access
+ * the scheduledCompleterContext, since *this has a shared pointer to it.
+ * but not session!
+ */
+ session = 0;
+ QPID_LOG(debug, ": async completion callback scheduled for msg seq=" << id);
+ completerContext->scheduleCommandCompletion(id, requiresAccept, requiresSync);
+ } else {
+ // this path runs directly from the ac->end() call in handleContent() above,
+ // so *session is definately valid.
+ if (session->isAttached()) {
+ QPID_LOG(debug, ": receive completed for msg seq=" << id);
+ session->completeCommand(id, requiresAccept, requiresSync);
+ }
+ }
+ completerContext = 0;
+}
+
+
+/** Track an ingress message that is pending completion */
+void SessionState::AsyncCommandCompleter::addPendingMessage(boost::intrusive_ptr<qpid::broker::amqp_0_10::MessageTransfer> msg)
+{
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock);
+ std::pair<SequenceNumber, boost::intrusive_ptr<qpid::broker::amqp_0_10::MessageTransfer> > item(msg->getCommandId(), msg);
+ bool unique = pendingMsgs.insert(item).second;
+ if (!unique) {
+ assert(false);
+ }
+}
+
+
+/** pending message has completed */
+void SessionState::AsyncCommandCompleter::deletePendingMessage(SequenceNumber id)
+{
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock);
+ pendingMsgs.erase(id);
+}
+
+
+/** done when an execution.sync arrives */
+void SessionState::AsyncCommandCompleter::flushPendingMessages()
+{
+ std::map<SequenceNumber, boost::intrusive_ptr<qpid::broker::amqp_0_10::MessageTransfer> > copy;
+ {
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock);
+ pendingMsgs.swap(copy); // we've only tracked these in case a flush is needed, so nuke 'em now.
+ }
+ // drop lock, so it is safe to call "flush()"
+ for (std::map<SequenceNumber, boost::intrusive_ptr<qpid::broker::amqp_0_10::MessageTransfer> >::iterator i = copy.begin();
+ i != copy.end(); ++i) {
+ i->second->flush();
+ }
+}
+
+
+/** mark an ingress Message.Transfer command as completed.
+ * This method must be thread safe - it may run on any thread.
+ */
+void SessionState::AsyncCommandCompleter::scheduleCommandCompletion(
+ SequenceNumber cmd,
+ bool requiresAccept,
+ bool requiresSync)
+{
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock);
+
+ if (session && isAttached) {
+ CommandInfo info(cmd, requiresAccept, requiresSync);
+ completedCmds.push_back(info);
+ if (completedCmds.size() == 1) {
+ session->getConnection().requestIOProcessing(
+ boost::bind(&AsyncCommandCompleter::completeCommands,
+ session->asyncCommandCompleter));
+ }
+ }
+}
+
+void SessionState::AsyncCommandCompleter::schedule(boost::function<void()> f) {
+ if (session && isAttached) session->getConnection().requestIOProcessing(f);
+}
+
+/** Cause the session to complete all completed commands.
+ * Executes on the IO thread.
+ */
+void SessionState::AsyncCommandCompleter::completeCommands()
+{
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock);
+
+ // when session is destroyed, it clears the session pointer via cancel().
+ if (session && session->isAttached()) {
+ for (std::vector<CommandInfo>::iterator cmd = completedCmds.begin();
+ cmd != completedCmds.end(); ++cmd) {
+ session->completeCommand(
+ cmd->cmd, cmd->requiresAccept, cmd->requiresSync);
+ }
+ }
+ completedCmds.clear();
+}
+
+
+/** cancel any pending calls to scheduleComplete */
+void SessionState::AsyncCommandCompleter::cancel()
+{
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock);
+ session = 0;
+}
+
+
+/** inform the completer that the session has attached,
+ * allows command completion scheduling from any thread */
+void SessionState::AsyncCommandCompleter::attached()
+{
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock);
+ isAttached = true;
+}
+
+
+/** inform the completer that the session has detached,
+ * disables command completion scheduling from any thread */
+void SessionState::AsyncCommandCompleter::detached()
+{
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(completerLock);
+ isAttached = false;
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/SessionState.h b/qpid/cpp/src/qpid/broker/SessionState.h
new file mode 100644
index 0000000000..c71c520f9c
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SessionState.h
@@ -0,0 +1,329 @@
+#ifndef QPID_BROKER_SESSION_H
+#define QPID_BROKER_SESSION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/SessionState.h"
+#include "qpid/framing/enum.h"
+#include "qpid/framing/FrameHandler.h"
+#include "qpid/framing/SequenceSet.h"
+#include "qpid/sys/Time.h"
+#include "qpid/management/Manageable.h"
+#include "qmf/org/apache/qpid/broker/Session.h"
+#include "qpid/broker/SessionAdapter.h"
+#include "qpid/broker/AsyncCompletion.h"
+#include "qpid/broker/MessageBuilder.h"
+#include "qpid/broker/SessionContext.h"
+#include "qpid/broker/SemanticState.h"
+#include "qpid/broker/amqp_0_10/MessageTransfer.h"
+#include "qpid/sys/Monitor.h"
+
+#include <boost/noncopyable.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/intrusive_ptr.hpp>
+
+#include <queue>
+#include <set>
+#include <vector>
+#include <ostream>
+
+namespace qpid {
+
+namespace framing {
+class AMQP_ClientProxy;
+}
+
+namespace sys {
+class TimerTask;
+}
+
+namespace broker {
+
+class Broker;
+class ConnectionState;
+class SessionHandler;
+class SessionManager;
+
+/**
+ * Broker-side session state includes session's handler chains, which
+ * may themselves have state.
+ */
+class SessionState : public qpid::SessionState,
+ public SessionContext,
+ public management::Manageable,
+ public framing::FrameHandler::InOutHandler
+{
+ public:
+ SessionState(Broker&, SessionHandler&, const SessionId&,
+ const SessionState::Configuration&);
+ ~SessionState();
+ bool isAttached() const { return handler; }
+
+ void detach();
+ void attach(SessionHandler& handler);
+ void disableOutput();
+
+ SessionHandler* getHandler() { return handler; }
+
+ /** @pre isAttached() */
+ framing::AMQP_ClientProxy& getProxy();
+
+ /** @pre isAttached() */
+ uint16_t getChannel() const;
+
+ /** @pre isAttached() */
+ amqp_0_10::Connection& getConnection();
+ bool isLocal(const OwnershipToken* t) const;
+
+ Broker& getBroker();
+
+ void setTimeout(uint32_t seconds);
+
+ void senderCompleted(const framing::SequenceSet& ranges);
+
+ void sendCompletion();
+
+ DeliveryId deliver(const qpid::broker::amqp_0_10::MessageTransfer& message,
+ const std::string& destination, bool isRedelivered, uint64_t ttl,
+ qpid::framing::message::AcceptMode, qpid::framing::message::AcquireMode,
+ const qpid::types::Variant::Map& annotations, bool sync);
+
+ // Manageable entry points
+ management::ManagementObject::shared_ptr GetManagementObject(void) const;
+ management::Manageable::status_t
+ ManagementMethod (uint32_t methodId, management::Args& args, std::string&);
+
+ void readyToSend();
+
+ const SessionId& getSessionId() const { return getId(); }
+
+ /**
+ * Used by ExecutionHandler sync command processing. Notifies
+ * the SessionState of a received Execution.Sync command.
+ * Return true if there are incomplete commands before the execution sync.
+ */
+ bool addPendingExecutionSync();
+
+ /**
+ * Mark commannd ID as an execution sync point, completions will be sent
+ * when all commands up to that point are completed.
+ */
+ bool addPendingExecutionSync(SequenceNumber id);
+
+
+ void setUnackedCount(uint64_t count) {
+ if (mgmtObject)
+ mgmtObject->set_unackedMessages(count);
+ }
+
+ // Used to delay creation of management object for sessions
+ // belonging to inter-broker bridges
+ void addManagementObject();
+
+ // transaction-related methods just to update statistics
+ void startTx();
+ void commitTx();
+ void rollbackTx();
+
+ /** Send result and completion for a given command to the client. */
+ void completeCommand(SequenceNumber id, bool requiresAccept, bool requiresSync,
+ const std::string& result);
+ private:
+ void handleCommand(framing::AMQMethodBody* method);
+ void handleContent(framing::AMQFrame& frame);
+
+ void handleIn(framing::AMQFrame& frame);
+ void handleOut(framing::AMQFrame& frame);
+
+ // End of the input & output chains.
+ void handleInLast(framing::AMQFrame& frame);
+ void handleOutLast(framing::AMQFrame& frame);
+
+ void sendAcceptAndCompletion();
+
+ Broker& broker;
+ SessionHandler* handler;
+ sys::AbsTime expiry; // Used by SessionManager.
+ SemanticState semanticState;
+ SessionAdapter adapter;
+ MessageBuilder msgBuilder;
+ qmf::org::apache::qpid::broker::Session::shared_ptr mgmtObject;
+ qpid::framing::SequenceSet accepted;
+
+ // sequence numbers for pending received Execution.Sync commands
+ std::queue<SequenceNumber> pendingExecutionSyncs;
+
+ public:
+
+ /** Information about the currently executing command.
+ * Can only be used in the IO thread during command execution.
+ */
+ class CurrentCommand {
+ public:
+ CurrentCommand(
+ SequenceNumber id_=0, bool syncRequired_=false, bool completeSync_=true ) :
+ id(id_), syncRequired(syncRequired_), completeSync(completeSync_)
+ {}
+
+ SequenceNumber getId() const { return id; }
+
+ /**@return true if the sync flag was set for the command. */
+ bool isSyncRequired() const { return syncRequired; }
+
+ /**@return true if the command should be completed synchronously
+ * in the handling thread.
+ */
+ bool isCompleteSync() const { return completeSync; }
+ void setCompleteSync(bool b) { completeSync = b; }
+
+ private:
+ SequenceNumber id; ///< Command identifier.
+ bool syncRequired; ///< True if sync flag set for the command.
+ bool completeSync; ///< Will be completed by handCommand.
+ };
+
+ CurrentCommand& getCurrentCommand() { return currentCommand; }
+
+ /** This class provides a context for completing asynchronous commands in a thread
+ * safe manner. Asynchronous commands save their completion state in this class.
+ * This class then schedules the completeCommands() method in the IO thread.
+ * While running in the IO thread, completeCommands() may safely complete all
+ * saved commands without the risk of colliding with other operations on this
+ * SessionState.
+ */
+ class AsyncCommandCompleter : public RefCounted {
+ private:
+ SessionState *session;
+ bool isAttached;
+ qpid::sys::Mutex completerLock;
+
+ struct CommandInfo {
+ SequenceNumber cmd; // message.transfer command id
+ bool requiresAccept;
+ bool requiresSync;
+
+ CommandInfo(
+ SequenceNumber c, bool a, bool s)
+ : cmd(c), requiresAccept(a), requiresSync(s) {}
+ };
+
+ std::vector<CommandInfo> completedCmds;
+ // If an ingress message does not require a Sync, we need to
+ // hold a reference to it in case an Execution.Sync command is received and we
+ // have to manually flush the message.
+ std::map<SequenceNumber, boost::intrusive_ptr<qpid::broker::amqp_0_10::MessageTransfer> > pendingMsgs;
+
+ /** complete all pending commands, runs in IO thread */
+ void completeCommands();
+
+ public:
+ AsyncCommandCompleter(SessionState *s) : session(s), isAttached(s->isAttached()) {};
+ ~AsyncCommandCompleter() {};
+
+ /** track a message pending ingress completion */
+ void addPendingMessage(boost::intrusive_ptr<qpid::broker::amqp_0_10::MessageTransfer> m);
+ void deletePendingMessage(SequenceNumber id);
+ void flushPendingMessages();
+ /** schedule the processing of command completion. */
+ void scheduleCommandCompletion(SequenceNumber cmd,
+ bool requiresAccept,
+ bool requiresSync);
+ void schedule(boost::function<void()>);
+ void cancel(); // called by SessionState destructor.
+ void attached(); // called by SessionState on attach()
+ void detached(); // called by SessionState on detach()
+
+ SessionState* getSession() const { return session; }
+ };
+
+ boost::intrusive_ptr<AsyncCommandCompleter> getAsyncCommandCompleter() {
+ return asyncCommandCompleter;
+ }
+
+ /** Abstract class that represents a single asynchronous command that is
+ * pending completion.
+ */
+ class AsyncCommandContext : public AsyncCompletion::Callback
+ {
+ public:
+ AsyncCommandContext(SessionState& ss )
+ : id(ss.getCurrentCommand().getId()),
+ requiresSync(ss.getCurrentCommand().isSyncRequired()),
+ completerContext(ss.getAsyncCommandCompleter())
+ {}
+
+ virtual ~AsyncCommandContext() {}
+
+ protected:
+ SequenceNumber id;
+ bool requiresSync;
+ boost::intrusive_ptr<AsyncCommandCompleter> completerContext;
+ };
+
+
+ private:
+ boost::intrusive_ptr<AsyncCommandCompleter> asyncCommandCompleter;
+ CurrentCommand currentCommand;
+
+ /** incomplete Message.transfer commands - inbound to broker from client
+ */
+ class IncompleteIngressMsgXfer : public SessionState::AsyncCommandContext
+ {
+ public:
+ IncompleteIngressMsgXfer( SessionState *ss,
+ boost::intrusive_ptr<qpid::broker::amqp_0_10::MessageTransfer> m)
+ : AsyncCommandContext(*ss),
+ session(ss),
+ msg(m),
+ requiresAccept(m->requiresAccept()),
+ requiresSync(m->getFrames().getMethod()->isSync()),
+ pending(false)
+ {
+ assert(id == m->getCommandId());
+ }
+
+ virtual ~IncompleteIngressMsgXfer() {}
+
+ virtual void completed(bool);
+ virtual boost::intrusive_ptr<AsyncCompletion::Callback> clone();
+
+ private:
+ SessionState *session; // only valid if sync flag in callback is true
+ boost::intrusive_ptr<qpid::broker::amqp_0_10::MessageTransfer> msg;
+ bool requiresAccept;
+ bool requiresSync;
+ bool pending; // true if msg saved on pending list...
+ };
+
+ friend class SessionManager;
+};
+
+
+inline std::ostream& operator<<(std::ostream& out, const SessionState& session) {
+ return out << session.getId();
+}
+
+}} // namespace qpid::broker
+
+
+
+#endif /*!QPID_BROKER_SESSION_H*/
diff --git a/qpid/cpp/src/qpid/broker/SignalHandler.cpp b/qpid/cpp/src/qpid/broker/SignalHandler.cpp
new file mode 100644
index 0000000000..16c141f21c
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SignalHandler.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/SignalHandler.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/sys/Mutex.h"
+#include <signal.h>
+
+namespace qpid {
+namespace broker {
+
+// Lock is to ensure that broker is not concurrently set to 0 and
+// deleted while we are in a call to broker->shutdown()
+
+sys::Mutex brokerLock;
+Broker* SignalHandler::broker;
+
+void SignalHandler::setBroker(Broker* b) {
+ sys::Mutex::ScopedLock l(brokerLock);
+ broker = b;
+ signal(SIGINT,shutdownHandler);
+ signal(SIGTERM, shutdownHandler);
+ signal(SIGHUP,SIG_IGN);
+ signal(SIGCHLD,SIG_IGN);
+}
+
+void SignalHandler::shutdown() { shutdownHandler(0); }
+
+void SignalHandler::shutdownHandler(int) {
+ sys::Mutex::ScopedLock l(brokerLock);
+ if (broker) {
+ broker->shutdown();
+ broker = 0;
+ }
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/SignalHandler.h b/qpid/cpp/src/qpid/broker/SignalHandler.h
new file mode 100644
index 0000000000..7bfa9ea630
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/SignalHandler.h
@@ -0,0 +1,50 @@
+#ifndef QPID_BROKER_SIGNALHANDLER_H
+#define QPID_BROKER_SIGNALHANDLER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+namespace qpid {
+namespace broker {
+
+class Broker;
+
+/**
+ * Handle signals e.g. to shut-down a broker.
+ */
+class SignalHandler
+{
+ public:
+ /** Set the broker to be shutdown on signals.
+ * Must be reset by calling setBroker(0) before the broker is deleted.
+ */
+ static void setBroker(Broker* broker);
+
+ /** Initiate shut-down of broker */
+ static void shutdown();
+
+ private:
+ static void shutdownHandler(int);
+ static Broker* broker;
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_SIGNALHANDLER_H*/
diff --git a/qpid/cpp/src/qpid/broker/System.cpp b/qpid/cpp/src/qpid/broker/System.cpp
new file mode 100644
index 0000000000..f3535b0eec
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/System.cpp
@@ -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.
+//
+
+#include "qpid/broker/System.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/management/ManagementAgent.h"
+#include "qpid/framing/Uuid.h"
+#include "qpid/sys/SystemInfo.h"
+#include "qpid/types/Uuid.h"
+#include <iostream>
+#include <fstream>
+
+using qpid::management::ManagementAgent;
+using namespace qpid::broker;
+using namespace std;
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+System::System (string _dataDir, Broker* broker)
+{
+ ManagementAgent* agent = broker ? broker->getManagementAgent() : 0;
+
+ if (agent != 0)
+ {
+
+ if (_dataDir.empty ())
+ {
+ systemId.generate ();
+ }
+ else
+ {
+ string filename (_dataDir + "/systemId");
+ ifstream inFile (filename.c_str ());
+
+ if (inFile.good ())
+ {
+ inFile >> systemId;
+ inFile.close ();
+ }
+ else
+ {
+ systemId.generate ();
+ ofstream outFile (filename.c_str ());
+ if (outFile.good ())
+ {
+ outFile << systemId << endl;
+ outFile.close ();
+ }
+ }
+ }
+
+ mgmtObject = _qmf::System::shared_ptr(new _qmf::System(agent, this, systemId));
+ qpid::sys::SystemInfo::getSystemId (osName,
+ nodeName,
+ release,
+ version,
+ machine);
+ mgmtObject->set_osName (osName);
+ mgmtObject->set_nodeName (nodeName);
+ mgmtObject->set_release (release);
+ mgmtObject->set_version (version);
+ mgmtObject->set_machine (machine);
+
+ agent->addObject(mgmtObject, 0, true);
+ }
+}
+
+System::~System ()
+{
+ if (mgmtObject != 0)
+ mgmtObject->debugStats("destroying");
+}
+
diff --git a/qpid/cpp/src/qpid/broker/System.h b/qpid/cpp/src/qpid/broker/System.h
new file mode 100644
index 0000000000..4a4af275c4
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/System.h
@@ -0,0 +1,70 @@
+#ifndef _BrokerSystem_
+#define _BrokerSystem_
+
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+#include "qpid/management/Manageable.h"
+#include "qpid/framing/Uuid.h"
+#include "qmf/org/apache/qpid/broker/System.h"
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace qpid {
+namespace broker {
+
+class Broker;
+
+class System : public management::Manageable
+{
+ private:
+
+ qmf::org::apache::qpid::broker::System::shared_ptr mgmtObject;
+ framing::Uuid systemId;
+ std::string osName, nodeName, release, version, machine;
+
+ public:
+
+ typedef boost::shared_ptr<System> shared_ptr;
+
+ System (std::string _dataDir, Broker* broker = 0);
+
+ ~System ();
+
+ management::ManagementObject::shared_ptr GetManagementObject(void) const
+ { return mgmtObject; }
+
+
+ /** Persistent UUID assigned by the management system to this broker. */
+ framing::Uuid getSystemId() const { return systemId; }
+ /** Returns the OS name; e.g., GNU/Linux or Windows */
+ std::string getOsName() const { return osName; }
+ /** Returns the node name. Usually the same as the host name. */
+ std::string getNodeName() const { return nodeName; }
+ /** Returns the OS release identifier. */
+ std::string getRelease() const { return release; }
+ /** Returns the OS release version (kernel, build, sp, etc.) */
+ std::string getVersion() const { return version; }
+ /** Returns the hardware type. */
+ std::string getMachine() const { return machine; }
+};
+
+}}
+
+#endif /*!_BrokerSystem_*/
diff --git a/qpid/cpp/src/qpid/broker/ThresholdAlerts.cpp b/qpid/cpp/src/qpid/broker/ThresholdAlerts.cpp
new file mode 100644
index 0000000000..41d20342c8
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/ThresholdAlerts.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/ThresholdAlerts.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/Message.h"
+#include "qpid/log/Statement.h"
+#include "qpid/management/ManagementAgent.h"
+#include "qmf/org/apache/qpid/broker/EventQueueThresholdCrossedUpward.h"
+#include "qmf/org/apache/qpid/broker/EventQueueThresholdCrossedDownward.h"
+#include "qmf/org/apache/qpid/broker/EventQueueThresholdExceeded.h"
+
+namespace qpid {
+namespace broker {
+
+ThresholdAlerts::ThresholdAlerts(const std::string& n,
+ qpid::management::ManagementAgent& a,
+ const uint32_t ctu,
+ const uint32_t ctd,
+ const uint64_t stu,
+ const uint64_t std,
+ const bool bw)
+ : name(n), agent(a),
+ countThreshold(ctu), countThresholdDown(ctd),
+ sizeThreshold(stu), sizeThresholdDown(std),
+ count(0), size(0), countGoingUp(true), sizeGoingUp(true), backwardCompat(bw) {}
+
+void ThresholdAlerts::enqueued(const Message& m)
+{
+ size += m.getMessageSize();
+ ++count;
+
+ if (sizeGoingUp && sizeThreshold && size >= sizeThreshold) {
+ sizeGoingUp = false;
+ agent.raiseEvent(qmf::org::apache::qpid::broker::EventQueueThresholdCrossedUpward(name, count, size));
+ if (backwardCompat)
+ agent.raiseEvent(qmf::org::apache::qpid::broker::EventQueueThresholdExceeded(name, count, size));
+ }
+
+ if (countGoingUp && countThreshold && count >= countThreshold) {
+ countGoingUp = false;
+ agent.raiseEvent(qmf::org::apache::qpid::broker::EventQueueThresholdCrossedUpward(name, count, size));
+ if (backwardCompat)
+ agent.raiseEvent(qmf::org::apache::qpid::broker::EventQueueThresholdExceeded(name, count, size));
+ }
+}
+
+void ThresholdAlerts::dequeued(const Message& m)
+{
+ size -= m.getMessageSize();
+ --count;
+
+ if (!sizeGoingUp && sizeThreshold && size <= sizeThresholdDown) {
+ sizeGoingUp = true;
+ agent.raiseEvent(qmf::org::apache::qpid::broker::EventQueueThresholdCrossedDownward(name, count, size));
+ }
+
+ if (!countGoingUp && countThreshold && count <= countThresholdDown) {
+ countGoingUp = true;
+ agent.raiseEvent(qmf::org::apache::qpid::broker::EventQueueThresholdCrossedDownward(name, count, size));
+ }
+}
+
+
+
+void ThresholdAlerts::observe(Queue& queue, qpid::management::ManagementAgent& agent,
+ const uint64_t ctu,
+ const uint64_t _ctd,
+ const uint64_t stu,
+ const uint64_t _std)
+{
+ if (ctu || stu) {
+ uint64_t ctd = (_ctd == 0 || _ctd >= ctu) ? ctu >> 1 : _ctd;
+ uint64_t std = (_std == 0 || _std >= stu) ? stu >> 1 : _std;
+
+ boost::shared_ptr<QueueObserver> observer(
+ new ThresholdAlerts(queue.getName(), agent, ctu, ctd, stu, std, (_ctd == 0 && _std == 0))
+ );
+ queue.getObservers().add(observer);
+ }
+}
+
+void ThresholdAlerts::observe(Queue& queue, qpid::management::ManagementAgent& agent,
+ const QueueSettings& settings, uint16_t limitRatio)
+{
+ //If no explicit threshold settings were given use specified
+ //percentage of any limit from the policy.
+ uint32_t countThreshold = settings.alertThreshold.hasCount() ? settings.alertThreshold.getCount() : (settings.maxDepth.getCount()*limitRatio/100);
+ uint32_t sizeThreshold = settings.alertThreshold.hasSize() ? settings.alertThreshold.getSize() : (settings.maxDepth.getSize()*limitRatio/100);
+ uint32_t countThresholdDown = settings.alertThresholdDown.hasCount() ? settings.alertThresholdDown.getCount() : 0;
+ uint32_t sizeThresholdDown = settings.alertThresholdDown.hasSize() ? settings.alertThresholdDown.getSize() : 0;
+
+ observe(queue, agent, countThreshold, countThresholdDown , sizeThreshold, sizeThresholdDown);
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/broker/ThresholdAlerts.h b/qpid/cpp/src/qpid/broker/ThresholdAlerts.h
new file mode 100644
index 0000000000..a8ff5270f3
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/ThresholdAlerts.h
@@ -0,0 +1,77 @@
+#ifndef QPID_BROKER_THRESHOLDALERTS_H
+#define QPID_BROKER_THRESHOLDALERTS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/QueueObserver.h"
+#include "qpid/types/Variant.h"
+#include <string>
+
+namespace qpid {
+namespace management {
+class ManagementAgent;
+}
+namespace broker {
+
+class Queue;
+struct QueueSettings;
+/**
+ * Class to manage generation of QMF alerts when particular thresholds
+ * are breached on a queue.
+ */
+class ThresholdAlerts : public QueueObserver
+{
+ public:
+ ThresholdAlerts(const std::string& name,
+ qpid::management::ManagementAgent& agent,
+ const uint32_t countThreshold,
+ const uint32_t countThresholdDown,
+ const uint64_t sizeThreshold,
+ const uint64_t sizeThresholdDown,
+ const bool backwardCompat);
+ void enqueued(const Message&);
+ void dequeued(const Message&);
+ void acquired(const Message&) {};
+ void requeued(const Message&) {};
+
+ static void observe(Queue& queue, qpid::management::ManagementAgent& agent,
+ const uint64_t countThreshold,
+ const uint64_t countThresholdDown,
+ const uint64_t sizeThreshold,
+ const uint64_t sizeThresholdDown);
+ static void observe(Queue& queue, qpid::management::ManagementAgent& agent,
+ const QueueSettings& settings, uint16_t limitRatio);
+ private:
+ const std::string name;
+ qpid::management::ManagementAgent& agent;
+ const uint32_t countThreshold;
+ const uint32_t countThresholdDown;
+ const uint64_t sizeThreshold;
+ const uint64_t sizeThresholdDown;
+ uint64_t count;
+ uint64_t size;
+ bool countGoingUp;
+ bool sizeGoingUp;
+ bool backwardCompat;
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_THRESHOLDALERTS_H*/
diff --git a/qpid/cpp/src/qpid/broker/TopicExchange.cpp b/qpid/cpp/src/qpid/broker/TopicExchange.cpp
new file mode 100644
index 0000000000..558c900a4f
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/TopicExchange.cpp
@@ -0,0 +1,350 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/TopicExchange.h"
+#include "qpid/broker/FedOps.h"
+#include "qpid/log/Statement.h"
+#include <algorithm>
+
+
+namespace qpid {
+namespace broker {
+
+using namespace qpid::framing;
+using namespace qpid::sys;
+using namespace std;
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+// iterator for federation ReOrigin bind operation
+class TopicExchange::ReOriginIter : public BindingNode::TreeIterator {
+public:
+ ReOriginIter() {};
+ ~ReOriginIter() {};
+ bool visit(BindingNode& node) {
+ if (node.bindings.fedBinding.hasLocal()) {
+ keys2prop.push_back(node.routePattern);
+ }
+ return true;
+ }
+ std::vector<std::string> keys2prop;
+};
+
+
+// match iterator used by route(): builds BindingList of all unique queues
+// that match the routing key.
+class TopicExchange::BindingsFinderIter : public BindingNode::TreeIterator {
+public:
+ BindingsFinderIter(BindingList &bl) : b(bl) {};
+ ~BindingsFinderIter() {};
+
+ BindingList& b;
+ std::set<std::string> qSet;
+
+ bool visit(BindingNode& node) {
+
+ Binding::vector& qv(node.bindings.bindingVector);
+ for (Binding::vector::iterator j = qv.begin(); j != qv.end(); j++) {
+ // do not duplicate queues on the binding list
+ if (qSet.insert(j->get()->queue->getName()).second) {
+ b->push_back(*j);
+ }
+ }
+ return true;
+ }
+};
+
+
+
+// Iterator to visit all bindings until a given queue is found
+class TopicExchange::QueueFinderIter : public BindingNode::TreeIterator {
+public:
+ QueueFinderIter(Queue::shared_ptr queue) : queue(queue), found(false) {};
+ ~QueueFinderIter() {};
+ bool visit(BindingNode& node) {
+
+ Binding::vector& qv(node.bindings.bindingVector);
+ Binding::vector::iterator q;
+ for (q = qv.begin(); q != qv.end(); q++) {
+ if ((*q)->queue == queue) {
+ found = true;
+ return false; // search done
+ }
+ }
+ return true; // continue search
+ }
+
+ Queue::shared_ptr queue;
+ bool found;
+};
+
+
+class TopicExchange::Normalizer : public TokenIterator {
+ public:
+ Normalizer(string& p)
+ : TokenIterator(&p[0], &p[0]+p.size()), pattern(p)
+ { normalize(); }
+
+ private:
+ // Apply 2 transformations: #.* -> *.# and #.# -> #
+ void normalize() {
+ while (!finished()) {
+ if (match1('#')) {
+ const char* hash1=token.first;
+ next();
+ if (!finished()) {
+ if (match1('#')) { // Erase #.# -> #
+ pattern.erase(hash1-pattern.data(), 2);
+ token.first -= 2;
+ token.second -= 2;
+ end -= 2;
+ }
+ else if (match1('*')) { // Swap #.* -> *.#
+ swap(*const_cast<char*>(hash1),
+ *const_cast<char*>(token.first));
+ }
+ }
+ }
+ else
+ next();
+ }
+ }
+
+ string& pattern;
+};
+
+
+
+// Convert sequences of * and # to a sequence of * followed by a single #
+string TopicExchange::normalize(const string& pattern) {
+ string normal(pattern);
+ Normalizer n(normal);
+ return normal;
+}
+
+
+TopicExchange::TopicExchange(const string& _name, Manageable* _parent, Broker* b)
+ : Exchange(_name, _parent, b),
+ nBindings(0)
+{
+ if (mgmtExchange != 0)
+ mgmtExchange->set_type (typeName);
+}
+
+TopicExchange::TopicExchange(const std::string& _name, bool _durable, bool autodelete,
+ const FieldTable& _args, Manageable* _parent, Broker* b) :
+ Exchange(_name, _durable, autodelete, _args, _parent, b),
+ nBindings(0)
+{
+ if (mgmtExchange != 0)
+ mgmtExchange->set_type (typeName);
+}
+
+bool TopicExchange::bind(Queue::shared_ptr queue, const string& routingKey, const FieldTable* args)
+{
+ ClearCache cc(&cacheLock,&bindingCache); // clear the cache on function exit.
+ string fedOp(args ? args->getAsString(qpidFedOp) : fedOpBind);
+ string fedTags(args ? args->getAsString(qpidFedTags) : "");
+ string fedOrigin(args ? args->getAsString(qpidFedOrigin) : "");
+ bool propagate = false;
+ string routingPattern = normalize(routingKey);
+
+ if (args == 0 || fedOp.empty() || fedOp == fedOpBind) {
+ RWlock::ScopedWlock l(lock);
+ BindingKey *bk = bindingTree.add(routingPattern);
+ if (bk) {
+ Binding::vector& qv(bk->bindingVector);
+ Binding::vector::iterator q;
+ for (q = qv.begin(); q != qv.end(); q++) {
+ if ((*q)->queue == queue) {
+ // already bound, but may be from a different fedOrigin
+ bk->fedBinding.addOrigin(queue->getName(), fedOrigin);
+ return false;
+ }
+ }
+
+ Binding::shared_ptr binding (new Binding (routingPattern, queue, this, args ? *args : FieldTable(), fedOrigin));
+ binding->startManagement();
+ bk->bindingVector.push_back(binding);
+ nBindings++;
+ propagate = bk->fedBinding.addOrigin(queue->getName(), fedOrigin);
+ if (mgmtExchange != 0) {
+ mgmtExchange->inc_bindingCount();
+ }
+ QPID_LOG(debug, "Binding key [" << routingPattern << "] to queue " << queue->getName()
+ << " on exchange " << getName() << " (origin=" << fedOrigin << ")");
+ }
+ } else if (fedOp == fedOpUnbind) {
+ RWlock::ScopedWlock l(lock);
+ BindingKey* bk = getQueueBinding(queue, routingPattern);
+ if (bk) {
+ QPID_LOG(debug, "FedOpUnbind [" << routingPattern << "] from exchange " << getName()
+ << " on queue=" << queue->getName() << " origin=" << fedOrigin);
+ propagate = bk->fedBinding.delOrigin(queue->getName(), fedOrigin);
+ // if this was the last binding for the queue, delete the binding
+ if (bk->fedBinding.countFedBindings(queue->getName()) == 0) {
+ deleteBinding(queue, routingPattern, bk);
+ }
+ }
+ } else if (fedOp == fedOpReorigin) {
+ /** gather up all the keys that need rebinding in a local vector
+ * while holding the lock. Then propagate once the lock is
+ * released
+ */
+ ReOriginIter reOriginIter;
+ {
+ RWlock::ScopedRlock l(lock);
+ bindingTree.iterateAll( reOriginIter );
+ } /* lock dropped */
+
+ for (std::vector<std::string>::const_iterator key = reOriginIter.keys2prop.begin();
+ key != reOriginIter.keys2prop.end(); key++) {
+ propagateFedOp( *key, string(), fedOpBind, string());
+ }
+ }
+
+ cc.clearCache(); // clear the cache before we IVE route.
+ routeIVE();
+ if (propagate)
+ propagateFedOp(routingKey, fedTags, fedOp, fedOrigin);
+ return true;
+}
+
+bool TopicExchange::unbind(Queue::shared_ptr queue, const string& constRoutingKey, const FieldTable* args)
+{
+ string fedOrigin(args ? args->getAsString(qpidFedOrigin) : "");
+ QPID_LOG(debug, "Unbinding key [" << constRoutingKey << "] from queue " << queue->getName()
+ << " on exchange " << getName() << " origin=" << fedOrigin << ")" );
+
+ ClearCache cc(&cacheLock,&bindingCache); // clear the cache on function exit.
+ RWlock::ScopedWlock l(lock);
+ string routingKey = normalize(constRoutingKey);
+ BindingKey* bk = getQueueBinding(queue, routingKey);
+ if (!bk) return false;
+ bool propagate = bk->fedBinding.delOrigin(queue->getName(), fedOrigin);
+ deleteBinding(queue, routingKey, bk);
+ if (propagate)
+ propagateFedOp(routingKey, string(), fedOpUnbind, string());
+ if (nBindings == 0) checkAutodelete();
+ return true;
+}
+
+
+bool TopicExchange::deleteBinding(Queue::shared_ptr queue,
+ const std::string& routingKey,
+ BindingKey *bk)
+{
+ // Note well: write lock held by caller
+ Binding::vector& qv(bk->bindingVector);
+ Binding::vector::iterator q;
+ for (q = qv.begin(); q != qv.end(); q++)
+ if ((*q)->queue == queue)
+ break;
+ if(q == qv.end()) return false;
+ qv.erase(q);
+ assert(nBindings > 0);
+ nBindings--;
+
+ if(qv.empty()) {
+ bindingTree.remove(routingKey);
+ }
+ if (mgmtExchange != 0) {
+ mgmtExchange->dec_bindingCount();
+ }
+ QPID_LOG(debug, "Unbound key [" << routingKey << "] from queue " << queue->getName()
+ << " on exchange " << getName());
+ return true;
+}
+
+/** returns a pointer to the BindingKey if the given queue is bound to this
+ * exchange using the routing pattern. 0 if queue binding does not exist.
+ */
+TopicExchange::BindingKey *TopicExchange::getQueueBinding(Queue::shared_ptr queue, const string& pattern)
+{
+ // Note well: lock held by caller....
+ BindingKey *bk = bindingTree.get(pattern); // Exact match against binding pattern
+ if (!bk) return 0;
+ Binding::vector& qv(bk->bindingVector);
+ Binding::vector::iterator q;
+ for (q = qv.begin(); q != qv.end(); q++)
+ if ((*q)->queue == queue)
+ break;
+ return (q != qv.end()) ? bk : 0;
+}
+
+void TopicExchange::route(Deliverable& msg)
+{
+ const string& routingKey = msg.getMessage().getRoutingKey();
+ // Note: PERFORMANCE CRITICAL!!!
+ BindingList b;
+ std::map<std::string, BindingList>::iterator it;
+ { // only lock the cache for read
+ RWlock::ScopedRlock cl(cacheLock);
+ it = bindingCache.find(routingKey);
+ if (it != bindingCache.end()) {
+ b = it->second;
+ }
+ }
+ PreRoute pr(msg, this);
+ if (!b.get()) // no cache hit
+ {
+ RWlock::ScopedRlock l(lock);
+ b = BindingList(new std::vector<boost::shared_ptr<qpid::broker::Exchange::Binding> >);
+ BindingsFinderIter bindingsFinder(b);
+ bindingTree.iterateMatch(routingKey, bindingsFinder);
+ RWlock::ScopedWlock cwl(cacheLock);
+ bindingCache[routingKey] = b; // update cache
+ }
+ doRoute(msg, b);
+}
+
+bool TopicExchange::isBound(Queue::shared_ptr queue, const string* const routingKey, const FieldTable* const)
+{
+ RWlock::ScopedRlock l(lock);
+ if (routingKey && queue) {
+ string key(normalize(*routingKey));
+ return getQueueBinding(queue, key) != 0;
+ } else if (!routingKey && !queue) {
+ return nBindings > 0;
+ } else if (routingKey) {
+ if (bindingTree.get(*routingKey)) {
+ return true;
+ }
+ } else {
+ QueueFinderIter queueFinder(queue);
+ bindingTree.iterateAll( queueFinder );
+ return queueFinder.found;
+ }
+ return false;
+}
+
+TopicExchange::~TopicExchange() {
+ if (mgmtExchange != 0)
+ mgmtExchange->debugStats("destroying");
+}
+
+const std::string TopicExchange::typeName("topic");
+
+bool TopicExchange::hasBindings()
+{
+ RWlock::ScopedRlock l(lock);
+ return nBindings > 0;
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/TopicExchange.h b/qpid/cpp/src/qpid/broker/TopicExchange.h
new file mode 100644
index 0000000000..d54f23a70d
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/TopicExchange.h
@@ -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.
+ *
+ */
+#ifndef _TopicExchange_
+#define _TopicExchange_
+
+#include <map>
+#include <vector>
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/broker/Exchange.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/sys/Monitor.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/TopicKeyNode.h"
+
+
+namespace qpid {
+namespace broker {
+
+class TopicExchange : public virtual Exchange {
+
+ class Normalizer;
+
+ struct BindingKey { // binding for this node
+ Binding::vector bindingVector;
+ FedBinding fedBinding;
+ };
+
+ typedef TopicKeyNode<BindingKey> BindingNode;
+
+ BindingKey *getQueueBinding(Queue::shared_ptr queue, const std::string& pattern);
+ bool deleteBinding(Queue::shared_ptr queue,
+ const std::string& routingKey,
+ BindingKey *bk);
+
+ class ReOriginIter;
+ class BindingsFinderIter;
+ class QueueFinderIter;
+
+ BindingNode bindingTree;
+ unsigned long nBindings;
+ qpid::sys::RWlock lock; // protects bindingTree and nBindings
+ qpid::sys::RWlock cacheLock; // protects cache
+ std::map<std::string, BindingList> bindingCache; // cache of matched routes.
+
+ class ClearCache {
+ private:
+ qpid::sys::RWlock* cacheLock;
+ std::map<std::string, BindingList>* bindingCache;
+ bool cleared;
+ public:
+ ClearCache(qpid::sys::RWlock* l, std::map<std::string, BindingList>* bc) :
+ cacheLock(l), bindingCache(bc),cleared(false) {};
+ void clearCache() {
+ qpid::sys::RWlock::ScopedWlock l(*cacheLock);
+ if (!cleared) {
+ bindingCache->clear();
+ cleared =true;
+ }
+ };
+ ~ClearCache(){
+ clearCache();
+ };
+ };
+
+public:
+ QPID_BROKER_EXTERN static const std::string typeName;
+
+ static QPID_BROKER_EXTERN std::string normalize(const std::string& pattern);
+
+ QPID_BROKER_EXTERN TopicExchange(const std::string& name,
+ management::Manageable* parent = 0, Broker* broker = 0);
+ QPID_BROKER_EXTERN TopicExchange(const std::string& _name,
+ bool _durable, bool autodelete,
+ const qpid::framing::FieldTable& _args,
+ management::Manageable* parent = 0, Broker* broker = 0);
+
+ virtual std::string getType() const { return typeName; }
+
+ QPID_BROKER_EXTERN virtual bool bind(Queue::shared_ptr queue,
+ const std::string& routingKey,
+ const qpid::framing::FieldTable* args);
+
+ virtual bool unbind(Queue::shared_ptr queue, const std::string& routingKey, const qpid::framing::FieldTable* args);
+
+ QPID_BROKER_EXTERN virtual void route(Deliverable& msg);
+
+ QPID_BROKER_EXTERN virtual bool isBound(Queue::shared_ptr queue,
+ const std::string* const routingKey,
+ const qpid::framing::FieldTable* const args);
+
+ QPID_BROKER_EXTERN virtual ~TopicExchange();
+ virtual bool supportsDynamicBinding() { return true; }
+
+ class TopicExchangeTester;
+ friend class TopicExchangeTester;
+ protected:
+ bool hasBindings();
+};
+
+
+}
+}
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/TopicKeyNode.h b/qpid/cpp/src/qpid/broker/TopicKeyNode.h
new file mode 100644
index 0000000000..ac760b198e
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/TopicKeyNode.h
@@ -0,0 +1,371 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#ifndef _QPID_BROKER_TOPIC_KEY_NODE_
+#define _QPID_BROKER_TOPIC_KEY_NODE_
+
+#include "qpid/broker/BrokerImportExport.h"
+#include <boost/shared_ptr.hpp>
+#include <map>
+#include <string>
+#include <string.h>
+
+
+namespace qpid {
+namespace broker {
+
+static const std::string STAR("*");
+static const std::string HASH("#");
+
+
+// Iterate over a string of '.'-separated tokens.
+struct TokenIterator {
+ typedef std::pair<const char*,const char*> Token;
+
+ TokenIterator(const char* b, const char* e) : end(e), token(std::make_pair(b, std::find(b,e,'.'))) {}
+
+ TokenIterator(const std::string& key) : end(&key[0]+key.size()), token(std::make_pair(&key[0], std::find(&key[0],end,'.'))) {}
+
+ bool finished() const { return !token.first; }
+
+ void next() {
+ if (token.second == end)
+ token.first = token.second = 0;
+ else {
+ token.first=token.second+1;
+ token.second=(std::find(token.first, end, '.'));
+ }
+ }
+
+ void pop(std::string &top) {
+ ptrdiff_t l = len();
+ if (l) {
+ top.assign(token.first, l);
+ } else top.clear();
+ next();
+ }
+
+ bool match1(char c) const {
+ return token.second==token.first+1 && *token.first == c;
+ }
+
+ bool match(const Token& token2) const {
+ ptrdiff_t l=len();
+ return l == token2.second-token2.first &&
+ strncmp(token.first, token2.first, l) == 0;
+ }
+
+ bool match(const std::string& str) const {
+ ptrdiff_t l=len();
+ return l == ptrdiff_t(str.size()) &&
+ str.compare(0, l, token.first, l) == 0;
+ }
+
+ ptrdiff_t len() const { return token.second - token.first; }
+
+
+ const char* end;
+ Token token;
+};
+
+
+// Binding database:
+// The dotted form of a binding key is broken up and stored in a directed tree graph.
+// Common binding prefix are merged. This allows the route match alogrithm to quickly
+// isolate those sub-trees that match a given routingKey.
+// For example, given the routes:
+// a.b.c.<...>
+// a.b.d.<...>
+// a.x.y.<...>
+// The resulting tree would be:
+// a-->b-->c-->...
+// | +-->d-->...
+// +-->x-->y-->...
+//
+template <class T>
+class QPID_BROKER_CLASS_EXTERN TopicKeyNode {
+
+ public:
+
+ typedef boost::shared_ptr<TopicKeyNode> shared_ptr;
+
+ // for database transversal (visit a node).
+ class TreeIterator {
+ public:
+ TreeIterator() {};
+ virtual ~TreeIterator() {};
+ virtual bool visit(TopicKeyNode& node) = 0;
+ };
+
+ TopicKeyNode() : isStar(false), isHash(false) {}
+ TopicKeyNode(const std::string& _t) : token(_t), isStar(_t == STAR), isHash(_t == HASH) {}
+ QPID_BROKER_EXTERN virtual ~TopicKeyNode() {
+ childTokens.clear();
+ }
+
+ // add normalizedRoute to tree, return associated T
+ QPID_BROKER_EXTERN T* add(const std::string& normalizedRoute) {
+ TokenIterator bKey(normalizedRoute);
+ return add(bKey, normalizedRoute);
+ }
+
+ // return T associated with normalizedRoute
+ QPID_BROKER_EXTERN T* get(const std::string& normalizedRoute) {
+ TokenIterator bKey(normalizedRoute);
+ return get(bKey);
+ }
+
+ // remove T associated with normalizedRoute
+ QPID_BROKER_EXTERN void remove(const std::string& normalizedRoute) {
+ TokenIterator bKey2(normalizedRoute);
+ remove(bKey2, normalizedRoute);
+ }
+
+ // applies iter against each node in tree until iter returns false
+ QPID_BROKER_EXTERN bool iterateAll(TreeIterator& iter) {
+ if (!iter.visit(*this)) return false;
+ if (starChild && !starChild->iterateAll(iter)) return false;
+ if (hashChild && !hashChild->iterateAll(iter)) return false;
+ for (typename ChildMap::iterator ptr = childTokens.begin();
+ ptr != childTokens.end(); ptr++) {
+ if (!ptr->second->iterateAll(iter)) return false;
+ }
+ return true;
+ }
+
+ // applies iter against only matching nodes until iter returns false
+ QPID_BROKER_EXTERN bool iterateMatch(const std::string& routingKey, TreeIterator& iter) {
+ TokenIterator rKey(routingKey);
+ return iterateMatch( rKey, iter );
+ }
+
+ std::string routePattern; // normalized binding that matches this node
+ T bindings; // for matches against this node
+
+ private:
+
+ std::string token; // portion of pattern represented by this node
+ bool isStar;
+ bool isHash;
+
+ // children
+ typedef std::map<std::string, typename TopicKeyNode::shared_ptr> ChildMap;
+ ChildMap childTokens;
+ typename TopicKeyNode::shared_ptr starChild; // "*" subtree
+ typename TopicKeyNode::shared_ptr hashChild; // "#" subtree
+
+ unsigned int getChildCount() { return childTokens.size() +
+ (starChild ? 1 : 0) + (hashChild ? 1 : 0); }
+
+ T* add(TokenIterator& bKey, const std::string& fullPattern){
+ if (bKey.finished()) {
+ // this node's binding
+ if (routePattern.empty()) {
+ routePattern = fullPattern;
+ } else assert(routePattern == fullPattern);
+
+ return &bindings;
+
+ } else {
+ // pop the topmost token & recurse...
+
+ if (bKey.match(STAR)) {
+ if (!starChild) {
+ starChild.reset(new TopicKeyNode<T>(STAR));
+ }
+ bKey.next();
+ return starChild->add(bKey, fullPattern);
+
+ } else if (bKey.match(HASH)) {
+ if (!hashChild) {
+ hashChild.reset(new TopicKeyNode<T>(HASH));
+ }
+ bKey.next();
+ return hashChild->add(bKey, fullPattern);
+
+ } else {
+ typename ChildMap::iterator ptr;
+ std::string next_token;
+ bKey.pop(next_token);
+ ptr = childTokens.find(next_token);
+ if (ptr != childTokens.end()) {
+ return ptr->second->add(bKey, fullPattern);
+ } else {
+ typename TopicKeyNode::shared_ptr child(new TopicKeyNode<T>(next_token));
+ childTokens[next_token] = child;
+ return child->add(bKey, fullPattern);
+ }
+ }
+ }
+ }
+
+
+ bool remove(TokenIterator& bKey, const std::string& fullPattern) {
+ bool remove;
+ if (!bKey.finished()) {
+ if (bKey.match(STAR)) {
+ bKey.next();
+ if (starChild) {
+ remove = starChild->remove(bKey, fullPattern);
+ if (remove) {
+ starChild.reset();
+ }
+ }
+ } else if (bKey.match(HASH)) {
+ bKey.next();
+ if (hashChild) {
+ remove = hashChild->remove(bKey, fullPattern);
+ if (remove) {
+ hashChild.reset();
+ }
+ }
+ } else {
+ typename ChildMap::iterator ptr;
+ std::string next_token;
+ bKey.pop(next_token);
+ ptr = childTokens.find(next_token);
+ if (ptr != childTokens.end()) {
+ remove = ptr->second->remove(bKey, fullPattern);
+ if (remove) {
+ childTokens.erase(ptr);
+ }
+ }
+ }
+ }
+
+ // no bindings and no children == parent can delete this node.
+ return getChildCount() == 0 && bindings.bindingVector.empty();
+ }
+
+
+ T* get(TokenIterator& bKey) {
+ if (bKey.finished()) {
+ return &bindings;
+ }
+
+ std::string next_token;
+ bKey.pop(next_token);
+
+ if (next_token == STAR) {
+ if (starChild)
+ return starChild->get(bKey);
+ } else if (next_token == HASH) {
+ if (hashChild)
+ return hashChild->get(bKey);
+ } else {
+ typename ChildMap::iterator ptr;
+ ptr = childTokens.find(next_token);
+ if (ptr != childTokens.end()) {
+ return ptr->second->get(bKey);
+ }
+ }
+
+ return 0;
+ }
+
+
+ bool iterateMatch(TokenIterator& rKey, TreeIterator& iter) {
+ if (isStar) return iterateMatchStar(rKey, iter);
+ if (isHash) return iterateMatchHash(rKey, iter);
+ return iterateMatchString(rKey, iter);
+ }
+
+
+ bool iterateMatchString(TokenIterator& rKey, TreeIterator& iter){
+ // invariant: key has matched all previous tokens up to this node.
+ if (rKey.finished()) {
+ // exact match this node: visit if bound
+ if (!bindings.bindingVector.empty())
+ if (!iter.visit(*this)) return false;
+ }
+
+ // check remaining key against children, even if empty.
+ return iterateMatchChildren(rKey, iter);
+ }
+
+
+ bool iterateMatchStar(TokenIterator& rKey, TreeIterator& iter) {
+ // must match one token:
+ if (rKey.finished())
+ return true; // match failed, but continue iteration on siblings
+
+ // pop the topmost token
+ rKey.next();
+
+ if (rKey.finished()) {
+ // exact match this node: visit if bound
+ if (!bindings.bindingVector.empty())
+ if (!iter.visit(*this)) return false;
+ }
+
+ return iterateMatchChildren(rKey, iter);
+ }
+
+
+ bool iterateMatchHash(TokenIterator& rKey, TreeIterator& iter) {
+ // consume each token and look for a match on the
+ // remaining key.
+ while (!rKey.finished()) {
+ if (!iterateMatchChildren(rKey, iter)) return false;
+ rKey.next();
+ }
+
+ if (!bindings.bindingVector.empty())
+ return iter.visit(*this);
+
+ return true;
+ }
+
+
+ bool iterateMatchChildren(const TokenIterator& key, TreeIterator& iter) {
+ // always try glob - it can match empty keys
+ if (hashChild) {
+ TokenIterator tmp(key);
+ if (!hashChild->iterateMatch(tmp, iter))
+ return false;
+ }
+
+ if (!key.finished()) {
+ if (starChild) {
+ TokenIterator tmp(key);
+ if (!starChild->iterateMatch(tmp, iter))
+ return false;
+ }
+
+ if (!childTokens.empty()) {
+ TokenIterator newKey(key);
+ std::string next_token;
+ newKey.pop(next_token);
+
+ typename ChildMap::iterator ptr = childTokens.find(next_token);
+ if (ptr != childTokens.end()) {
+ return ptr->second->iterateMatch(newKey, iter);
+ }
+ }
+ }
+
+ return true;
+ }
+};
+
+}
+}
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/TransactionObserver.h b/qpid/cpp/src/qpid/broker/TransactionObserver.h
new file mode 100644
index 0000000000..5333d7b8d6
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/TransactionObserver.h
@@ -0,0 +1,82 @@
+#ifndef QPID_BROKER_TRANSACTIONOBSERVER_H
+#define QPID_BROKER_TRANSACTIONOBSERVER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+
+#include "DeliveryRecord.h"
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+
+namespace framing {
+class SequenceSet;
+}
+
+namespace broker {
+class Queue;
+class Message;
+class TxBuffer;
+class DtxBuffer;
+
+/**
+ * Interface for intercepting events in a transaction.
+ */
+class TransactionObserver {
+ public:
+ typedef boost::shared_ptr<Queue> QueuePtr;
+ typedef framing::SequenceNumber SequenceNumber;
+
+ virtual ~TransactionObserver() {}
+
+ /** Message enqueued in the transaction. */
+ virtual void enqueue(const QueuePtr&, const Message&) = 0;
+
+ /**
+ * Message is dequeued in the transaction (it was accepted by a consumer.)
+ *@param queuePosition: Sequence number of message on queue.
+ *@param replicationId: Replication sequence number, may be different.
+ */
+ virtual void dequeue(const QueuePtr& queue,
+ SequenceNumber queueSeq,
+ SequenceNumber replicationSeq) = 0;
+
+ virtual bool prepare() = 0;
+ virtual void commit() = 0;
+ virtual void rollback() = 0;
+};
+
+/**
+ * No-op TransactionObserver.
+ */
+class NullTransactionObserver : public TransactionObserver {
+ public:
+ void enqueue(const QueuePtr&, const Message&) {}
+ void dequeue(const QueuePtr&, SequenceNumber, SequenceNumber) {}
+ bool prepare() { return true; }
+ void commit() {}
+ void rollback() {}
+};
+
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_TRANSACTIONOBSERVER_H*/
diff --git a/qpid/cpp/src/qpid/broker/TransactionalStore.h b/qpid/cpp/src/qpid/broker/TransactionalStore.h
new file mode 100644
index 0000000000..2a2bac0c51
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/TransactionalStore.h
@@ -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.
+ *
+ */
+#ifndef _TransactionalStore_
+#define _TransactionalStore_
+
+#include <memory>
+#include <string>
+#include <set>
+
+namespace qpid {
+namespace broker {
+
+struct InvalidTransactionContextException : public std::exception {};
+
+class TransactionContext {
+public:
+ virtual ~TransactionContext(){}
+};
+
+class TPCTransactionContext : public TransactionContext {
+public:
+ virtual ~TPCTransactionContext(){}
+};
+
+class TransactionalStore {
+public:
+ virtual std::auto_ptr<TransactionContext> begin() = 0;
+ virtual std::auto_ptr<TPCTransactionContext> begin(const std::string& xid) = 0;
+ virtual void prepare(TPCTransactionContext& txn) = 0;
+ virtual void commit(TransactionContext& txn) = 0;
+ virtual void abort(TransactionContext& txn) = 0;
+
+ virtual void collectPreparedXids(std::set<std::string>& xids) = 0;
+
+ virtual ~TransactionalStore(){}
+};
+
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/TxAccept.cpp b/qpid/cpp/src/qpid/broker/TxAccept.cpp
new file mode 100644
index 0000000000..496f8a2a42
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/TxAccept.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/TxAccept.h"
+#include "qpid/broker/TransactionObserver.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/log/Statement.h"
+
+using std::bind1st;
+using std::bind2nd;
+using std::mem_fun_ref;
+using namespace qpid::broker;
+using qpid::framing::SequenceSet;
+using qpid::framing::SequenceNumber;
+
+
+TxAccept::TxAccept(const SequenceSet& _acked, DeliveryRecords& _unacked) :
+ acked(_acked), unacked(_unacked)
+{}
+
+void TxAccept::each(boost::function<void(DeliveryRecord&)> f) {
+ DeliveryRecords::iterator dr = unacked.begin();
+ SequenceSet::iterator seq = acked.begin();
+ while(dr != unacked.end() && seq != acked.end()) {
+ if (dr->getId() == *seq) {
+ f(*dr);
+ ++dr;
+ ++seq;
+ }
+ else if (dr->getId() < *seq) ++dr;
+ else if (dr->getId() > *seq) ++seq;
+ }
+}
+
+bool TxAccept::prepare(TransactionContext* ctxt) throw()
+{
+ try{
+ each(bind(&DeliveryRecord::dequeue, _1, ctxt));
+ return true;
+ }catch(const std::exception& e){
+ QPID_LOG(error, "Failed to prepare: " << e.what());
+ return false;
+ }catch(...){
+ QPID_LOG(error, "Failed to prepare");
+ return false;
+ }
+}
+
+void TxAccept::commit() throw()
+{
+ try {
+ each(bind(&DeliveryRecord::committed, _1));
+ each(bind(&DeliveryRecord::setEnded, _1));
+ //now remove if isRedundant():
+ if (!acked.empty()) {
+ AckRange r = DeliveryRecord::findRange(unacked, acked.front(), acked.back());
+ DeliveryRecords::iterator removed =
+ remove_if(r.start, r.end, mem_fun_ref(&DeliveryRecord::isRedundant));
+ unacked.erase(removed, r.end);
+ }
+ } catch (const std::exception& e) {
+ QPID_LOG(error, "Failed to commit: " << e.what());
+ } catch(...) {
+ QPID_LOG(error, "Failed to commit (unknown error)");
+ }
+}
+
+void TxAccept::rollback() throw() {}
+
+namespace {
+void callObserverDR(boost::shared_ptr<TransactionObserver> observer, DeliveryRecord& dr) {
+ observer->dequeue(dr.getQueue(), dr.getMessageId(), dr.getReplicationId());
+}
+} // namespace
+
+void TxAccept::callObserver(const ObserverPtr& observer) {
+ each(boost::bind(&callObserverDR, observer, _1));
+}
diff --git a/qpid/cpp/src/qpid/broker/TxAccept.h b/qpid/cpp/src/qpid/broker/TxAccept.h
new file mode 100644
index 0000000000..97e82ffa3f
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/TxAccept.h
@@ -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.
+ *
+ */
+#ifndef _TxAccept_
+#define _TxAccept_
+
+#include "qpid/framing/SequenceSet.h"
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/broker/DeliveryRecord.h"
+#include "qpid/broker/TxOp.h"
+#include <boost/function.hpp>
+#include <algorithm>
+#include <functional>
+#include <list>
+
+namespace qpid {
+namespace broker {
+/**
+ * Defines the transactional behaviour for accepts received by
+ * a transactional channel.
+ */
+class TxAccept : public TxOp {
+ typedef boost::shared_ptr<TransactionObserver> ObserverPtr;
+
+ void each(boost::function<void(DeliveryRecord&)>);
+
+ framing::SequenceSet acked;
+ DeliveryRecords& unacked;
+
+ public:
+ /**
+ * @param acked a representation of the accumulation of
+ * acks received
+ * @param unacked the record of delivered messages
+ */
+ QPID_BROKER_EXTERN TxAccept(const framing::SequenceSet& acked, DeliveryRecords& unacked);
+ virtual bool prepare(TransactionContext* ctxt) throw();
+ virtual void commit() throw();
+ virtual void rollback() throw();
+ virtual void callObserver(const ObserverPtr&);
+ virtual ~TxAccept(){}
+};
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/TxBuffer.cpp b/qpid/cpp/src/qpid/broker/TxBuffer.cpp
new file mode 100644
index 0000000000..f7552f16a3
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/TxBuffer.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/TxBuffer.h"
+#include "qpid/broker/TransactionObserver.h"
+#include "qpid/log/Statement.h"
+#include "qpid/framing/reply_exceptions.h"
+
+#include <boost/mem_fn.hpp>
+#include <boost/bind.hpp>
+
+namespace qpid {
+namespace broker{
+
+using boost::mem_fn;
+using framing::InternalErrorException;
+
+TxBuffer::TxBuffer() : observer(new NullTransactionObserver) {}
+
+bool TxBuffer::prepare(TransactionContext* const ctxt)
+{
+ // The observer may call startCompleter to delay completion.
+ if (!observer->prepare()) return false;
+ for(op_iterator i = ops.begin(); i != ops.end(); i++){
+ if(!(*i)->prepare(ctxt)) return false;
+ }
+ // At this point prepare has succeeded locally but if completion is delayed,
+ // then completing threads may call setError to indicate an error.
+ return true;
+}
+
+void TxBuffer::commit()
+{
+ observer->commit();
+ std::for_each(ops.begin(), ops.end(), mem_fn(&TxOp::commit));
+ ops.clear();
+}
+
+void TxBuffer::rollback()
+{
+ observer->rollback();
+ std::for_each(ops.begin(), ops.end(), mem_fn(&TxOp::rollback));
+ ops.clear();
+}
+
+void TxBuffer::enlist(TxOp::shared_ptr op)
+{
+ op->callObserver(observer);
+ ops.push_back(op);
+}
+
+void TxBuffer::startCommit(TransactionalStore* const store)
+{
+ if (!store) throw Exception("Can't commit transaction, no store.");
+ txContext.reset(store->begin().release());
+ if (!prepare(txContext.get()))
+ setError("Transaction prepare failed.");
+}
+
+// Called when async completion is complete.
+std::string TxBuffer::endCommit(TransactionalStore* const store) {
+ std::string e;
+ {
+ sys::Mutex::ScopedLock l(errorLock);
+ e = error;
+ }
+ if (!e.empty()) {
+ store->abort(*txContext);
+ rollback();
+ throw InternalErrorException(e);
+ }
+ else {
+ store->commit(*txContext);
+ commit();
+ }
+ return std::string(); // There is no result from tx.commit
+}
+
+void TxBuffer::setError(const std::string& e) {
+ QPID_LOG(error, "Asynchronous transaction error: " << e);
+ sys::Mutex::ScopedLock l(errorLock);
+ if (!error.empty()) error += " ";
+ error += e;
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/TxBuffer.h b/qpid/cpp/src/qpid/broker/TxBuffer.h
new file mode 100644
index 0000000000..2478b78138
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/TxBuffer.h
@@ -0,0 +1,148 @@
+#ifndef QPID_BROKER_TXBUFFER_H
+#define QPID_BROKER_TXBUFFER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/broker/TransactionalStore.h"
+#include "qpid/broker/TxOp.h"
+#include "qpid/broker/AsyncCompletion.h"
+#include "qpid/sys/Mutex.h"
+#include <algorithm>
+#include <functional>
+#include <vector>
+
+
+namespace qpid {
+namespace broker {
+class TransactionObserver;
+
+/**
+ * Represents a single transaction. As such, an instance of this class
+ * will hold a list of operations representing the workload of the
+ * transaction. This work can be committed or rolled back. Committing
+ * is a two-stage process: first all the operations should be
+ * prepared, then if that succeeds they can be committed.
+ *
+ * In the 2pc case, a successful prepare may be followed by either a
+ * commit or a rollback.
+ *
+ * Atomicity of prepare is ensured by using a lower level
+ * transactional facility. This saves explicitly rolling back all the
+ * successfully prepared ops when one of them fails. i.e. we do not
+ * use 2pc internally, we instead ensure that prepare is atomic at a
+ * lower level. This makes individual prepare operations easier to
+ * code.
+ *
+ * Transactions on a messaging broker effect three types of 'action':
+ * (1) updates to persistent storage (2) updates to transient storage
+ * or cached data (3) network writes.
+ *
+ * Of these, (1) should always occur atomically during prepare to
+ * ensure that if the broker crashes while a transaction is being
+ * completed the persistent state (which is all that then remains) is
+ * consistent. (3) can only be done on commit, after a successful
+ * prepare. There is a little more flexibility with (2) but any
+ * changes made during prepare should be subject to the control of the
+ * TransactionalStore in use.
+ *
+ * TxBuffer inherits AsyncCompletion because transactions can be completed
+ * asynchronously if the broker is part of a HA cluster.
+ */
+class TxBuffer : public AsyncCompletion {
+ private:
+ typedef std::vector<TxOp::shared_ptr>::iterator op_iterator;
+ std::vector<TxOp::shared_ptr> ops;
+ boost::shared_ptr<TransactionObserver> observer;
+ std::auto_ptr<TransactionContext> txContext;
+ std::string error;
+ sys::Mutex errorLock;
+
+ public:
+ QPID_BROKER_EXTERN TxBuffer();
+
+ /**
+ * Adds an operation to the transaction.
+ */
+ QPID_BROKER_EXTERN void enlist(TxOp::shared_ptr op);
+
+ /**
+ * Requests that all ops are prepared. This should
+ * primarily involve making sure that a persistent record
+ * of the operations is stored where necessary.
+ *
+ * Once prepared, a transaction can be committed (or in
+ * the 2pc case, rolled back).
+ *
+ * @returns true if all the operations prepared
+ * successfully, false if not.
+ */
+ QPID_BROKER_EXTERN bool prepare(TransactionContext* const ctxt);
+
+ /**
+ * Signals that the ops all prepared successfully and can
+ * now commit, i.e. the operation can now be fully carried
+ * out.
+ *
+ * Should only be called after a call to prepare() returns
+ * true.
+ */
+ QPID_BROKER_EXTERN void commit();
+
+ /**
+ * Signals that all ops can be rolled back.
+ *
+ * Should only be called either after a call to prepare()
+ * returns true (2pc) or instead of a prepare call
+ * ('server-local')
+ */
+ QPID_BROKER_EXTERN void rollback();
+
+ /**
+ * Start a local commit - may complete asynchronously.
+ */
+ QPID_BROKER_EXTERN void startCommit(TransactionalStore* const store);
+
+ /** End a commit, called via async completion.
+ *@return encoded result, not used here.
+ */
+ QPID_BROKER_EXTERN std::string endCommit(TransactionalStore* const store);
+
+ QPID_BROKER_EXTERN void setObserver(boost::shared_ptr<TransactionObserver> o) {
+ observer = o;
+ }
+
+ QPID_BROKER_EXTERN boost::shared_ptr<TransactionObserver> getObserver() const {
+ return observer;
+ }
+
+ /** Set an error to be raised from endCommit when the commit completes.
+ * Called from completer threads if we are doing async completion.
+ * This is the only TxBuffer function called outside the IO thread.
+ */
+ QPID_BROKER_EXTERN void setError(const std::string& message);
+};
+
+}} // namespace qpid::broker
+
+
+#endif /*!QPID_BROKER_TXBUFFER_H*/
diff --git a/qpid/cpp/src/qpid/broker/TxDequeue.cpp b/qpid/cpp/src/qpid/broker/TxDequeue.cpp
new file mode 100644
index 0000000000..e9a2e0ca98
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/TxDequeue.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/TxDequeue.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/TransactionObserver.h"
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace broker {
+
+TxDequeue::TxDequeue(QueueCursor m, boost::shared_ptr<Queue> q,
+ qpid::framing::SequenceNumber mId, qpid::framing::SequenceNumber rId)
+ : message(m), queue(q), messageId(mId), replicationId(rId), releaseOnAbort(true), redeliveredOnAbort(true) {}
+
+bool TxDequeue::prepare(TransactionContext* ctxt) throw()
+{
+ try{
+ queue->dequeue(ctxt, message);
+ return true;
+ }catch(const std::exception& e){
+ QPID_LOG(error, "Failed to prepare: " << e.what());
+ return false;
+ }catch(...){
+ QPID_LOG(error, "Failed to prepare");
+ return false;
+ }
+}
+
+void TxDequeue::commit() throw()
+{
+ try {
+ queue->dequeueCommitted(message);
+ } catch (const std::exception& e) {
+ QPID_LOG(error, "Failed to commit: " << e.what());
+ } catch(...) {
+ QPID_LOG(error, "Failed to commit (unknown error)");
+ }
+}
+
+void TxDequeue::rollback() throw()
+{
+ if (releaseOnAbort) queue->release(message, redeliveredOnAbort);
+}
+
+void TxDequeue::callObserver(const boost::shared_ptr<TransactionObserver>& observer)
+{
+ observer->dequeue(queue, messageId, replicationId);
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/TxDequeue.h b/qpid/cpp/src/qpid/broker/TxDequeue.h
new file mode 100644
index 0000000000..e861bcaa77
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/TxDequeue.h
@@ -0,0 +1,54 @@
+#ifndef QPID_BROKER_TXDEQUEUE_H
+#define QPID_BROKER_TXDEQUEUE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/QueueCursor.h"
+#include "qpid/broker/TxOp.h"
+#include "qpid/framing/SequenceNumber.h"
+
+namespace qpid {
+namespace broker {
+class Queue;
+
+/**
+ * Transaction dequeue operation
+ */
+class TxDequeue: public TxOp
+{
+ public:
+ TxDequeue(QueueCursor message, boost::shared_ptr<Queue> queue,
+ qpid::framing::SequenceNumber messageId, qpid::framing::SequenceNumber replicationId);
+ bool prepare(TransactionContext* ctxt) throw();
+ void commit() throw();
+ void rollback() throw();
+ void callObserver(const boost::shared_ptr<TransactionObserver>&);
+ private:
+ QueueCursor message;
+ boost::shared_ptr<Queue> queue;
+ qpid::framing::SequenceNumber messageId;
+ qpid::framing::SequenceNumber replicationId;
+ bool releaseOnAbort;
+ bool redeliveredOnAbort;
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_TXDEQUEUE_H*/
diff --git a/qpid/cpp/src/qpid/broker/TxOp.h b/qpid/cpp/src/qpid/broker/TxOp.h
new file mode 100644
index 0000000000..00303eb27c
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/TxOp.h
@@ -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.
+ *
+ */
+#ifndef _TxOp_
+#define _TxOp_
+
+#include "qpid/broker/TransactionalStore.h"
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace broker {
+class TransactionObserver;
+
+class TxOp{
+ public:
+ typedef boost::shared_ptr<TxOp> shared_ptr;
+
+ virtual bool prepare(TransactionContext*) throw() = 0;
+ virtual void commit() throw() = 0;
+ virtual void rollback() throw() = 0;
+ virtual void callObserver(const boost::shared_ptr<TransactionObserver>&) = 0;
+ virtual ~TxOp(){}
+};
+
+}} // namespace qpid::broker
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/broker/Vhost.cpp b/qpid/cpp/src/qpid/broker/Vhost.cpp
new file mode 100644
index 0000000000..8fd88601f5
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Vhost.cpp
@@ -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.
+//
+
+#include "qpid/broker/Vhost.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/management/ManagementAgent.h"
+
+using namespace qpid::broker;
+using qpid::management::ManagementAgent;
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+namespace qpid { namespace management {
+class Manageable;
+}}
+
+Vhost::Vhost (qpid::management::Manageable* parentBroker, Broker* broker)
+{
+ if (parentBroker != 0 && broker != 0)
+ {
+ ManagementAgent* agent = broker->getManagementAgent();
+
+ if (agent != 0)
+ {
+ mgmtObject = _qmf::Vhost::shared_ptr(new _qmf::Vhost(agent, this, parentBroker, "/"));
+ agent->addObject(mgmtObject, 0, true);
+ }
+ }
+}
+
+Vhost::~Vhost () {
+ if (mgmtObject != 0)
+ mgmtObject->debugStats("destroying");
+}
+
+void Vhost::setFederationTag(const std::string& tag)
+{
+ mgmtObject->set_federationTag(tag);
+}
diff --git a/qpid/cpp/src/qpid/broker/Vhost.h b/qpid/cpp/src/qpid/broker/Vhost.h
new file mode 100644
index 0000000000..06a11db8ea
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/Vhost.h
@@ -0,0 +1,52 @@
+#ifndef _Vhost_
+#define _Vhost_
+
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+#include "qpid/management/Manageable.h"
+#include "qmf/org/apache/qpid/broker/Vhost.h"
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace broker {
+
+class Broker;
+class Vhost : public management::Manageable
+{
+ private:
+
+ qmf::org::apache::qpid::broker::Vhost::shared_ptr mgmtObject;
+
+ public:
+
+ typedef boost::shared_ptr<Vhost> shared_ptr;
+
+ Vhost (management::Manageable* parentBroker, Broker* broker = 0);
+
+ ~Vhost ();
+
+ management::ManagementObject::shared_ptr GetManagementObject (void) const
+ { return mgmtObject; }
+ void setFederationTag(const std::string& tag);
+};
+
+}}
+
+#endif /*!_Vhost_*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/Authorise.cpp b/qpid/cpp/src/qpid/broker/amqp/Authorise.cpp
new file mode 100644
index 0000000000..3c88414567
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Authorise.cpp
@@ -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.
+ *
+ */
+#include "Authorise.h"
+#include "Exception.h"
+#include "Filter.h"
+#include "qpid/amqp/descriptors.h"
+#include "qpid/broker/AclModule.h"
+#include "qpid/broker/Exchange.h"
+#include "qpid/broker/Message.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/types/Variant.h"
+#include <map>
+#include <boost/lexical_cast.hpp>
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+namespace {
+const std::string B_TRUE("true");
+const std::string B_FALSE("false");
+const std::string POLICY_TYPE("qpid.policy_type");
+}
+
+Authorise::Authorise(const std::string& u, AclModule* a) : user(u), acl(a) {}
+void Authorise::access(boost::shared_ptr<Exchange> exchange)
+{
+ if (acl) {
+ std::map<acl::Property, std::string> params;
+ params.insert(std::make_pair(acl::PROP_TYPE, exchange->getType()));
+ params.insert(std::make_pair(acl::PROP_DURABLE, exchange->isDurable() ? B_TRUE : B_FALSE));
+ if (!acl->authorise(user, acl::ACT_ACCESS, acl::OBJ_EXCHANGE, exchange->getName(), &params))
+ throw Exception(qpid::amqp::error_conditions::UNAUTHORIZED_ACCESS, QPID_MSG("ACL denied exchange access request from " << user));
+ }
+}
+void Authorise::access(boost::shared_ptr<Queue> queue)
+{
+ if (acl) {
+ const QueueSettings& settings = queue->getSettings();
+ std::map<acl::Property, std::string> params;
+ boost::shared_ptr<Exchange> altex = queue->getAlternateExchange();
+ if (altex)
+ params.insert(std::make_pair(acl::PROP_ALTERNATE, altex->getName()));
+ params.insert(std::make_pair(acl::PROP_DURABLE, settings.durable ? B_TRUE : B_FALSE));
+ params.insert(std::make_pair(acl::PROP_EXCLUSIVE, queue->hasExclusiveOwner() ? B_TRUE : B_FALSE));
+ params.insert(std::make_pair(acl::PROP_AUTODELETE, settings.autodelete ? B_TRUE : B_FALSE));
+ qpid::types::Variant::Map::const_iterator i = settings.original.find(POLICY_TYPE);
+ if (i != settings.original.end())
+ params.insert(std::make_pair(acl::PROP_POLICYTYPE, i->second.asString()));
+ if (settings.maxDepth.hasCount())
+ params.insert(std::make_pair(acl::PROP_MAXQUEUECOUNT, boost::lexical_cast<std::string>(settings.maxDepth.getCount())));
+ if (settings.maxDepth.hasCount())
+ params.insert(std::make_pair(acl::PROP_MAXQUEUESIZE, boost::lexical_cast<std::string>(settings.maxDepth.getSize())));
+ if (!acl->authorise(user, acl::ACT_ACCESS, acl::OBJ_QUEUE, queue->getName(), &params) )
+ throw Exception(qpid::amqp::error_conditions::UNAUTHORIZED_ACCESS, QPID_MSG("ACL denied queue access request from " << user));
+ }
+}
+
+void Authorise::incoming(boost::shared_ptr<Exchange> exchange)
+{
+ access(exchange);
+ //can't check publish permission here as do not yet know routing key
+}
+void Authorise::incoming(boost::shared_ptr<Queue> queue)
+{
+ access(queue);
+ if (acl) {
+ if (!acl->authorise(user, acl::ACT_PUBLISH, acl::OBJ_EXCHANGE, std::string()/*default exchange*/, queue->getName()))
+ throw Exception(qpid::amqp::error_conditions::UNAUTHORIZED_ACCESS, QPID_MSG(user << " cannot publish to queue " << queue->getName()));
+ }
+}
+void Authorise::outgoing(boost::shared_ptr<Exchange> exchange, boost::shared_ptr<Queue> queue, const Filter& filter)
+{
+ access(exchange);
+ if (acl) {
+ std::map<qpid::acl::Property, std::string> params;
+ params.insert(std::make_pair(acl::PROP_QUEUENAME, queue->getName()));
+ params.insert(std::make_pair(acl::PROP_ROUTINGKEY, filter.getBindingKey(exchange)));
+
+ if (!acl->authorise(user, acl::ACT_BIND, acl::OBJ_EXCHANGE, exchange->getName(), &params))
+ throw Exception(qpid::amqp::error_conditions::UNAUTHORIZED_ACCESS, QPID_MSG("ACL denied exchange bind request from " << user));
+
+ if (!acl->authorise(user, acl::ACT_CONSUME, acl::OBJ_QUEUE, queue->getName(), NULL))
+ throw Exception(qpid::amqp::error_conditions::UNAUTHORIZED_ACCESS, QPID_MSG("ACL denied queue subscribe request from " << user));
+ }
+}
+
+void Authorise::outgoing(boost::shared_ptr<Queue> queue)
+{
+ access(queue);
+ if (acl) {
+ if (!acl->authorise(user, acl::ACT_CONSUME, acl::OBJ_QUEUE, queue->getName(), NULL))
+ throw Exception(qpid::amqp::error_conditions::UNAUTHORIZED_ACCESS, QPID_MSG("ACL denied queue subscribe request from " << user));
+ }
+}
+
+void Authorise::route(boost::shared_ptr<Exchange> exchange, const Message& msg)
+{
+ if (acl && acl->doTransferAcl()) {
+ if (!acl->authorise(user, acl::ACT_PUBLISH, acl::OBJ_EXCHANGE, exchange->getName(), msg.getRoutingKey()))
+ throw Exception(qpid::amqp::error_conditions::UNAUTHORIZED_ACCESS, QPID_MSG(user << " cannot publish to " << exchange->getName() << " with routing-key " << msg.getRoutingKey()));
+ }
+}
+
+void Authorise::interlink()
+{
+ if (acl && acl->userAclRules()) {
+ if (!acl->authorise(user, acl::ACT_CREATE, acl::OBJ_LINK, "")){
+ throw Exception(qpid::amqp::error_conditions::UNAUTHORIZED_ACCESS, QPID_MSG("ACL denied " << user << " a AMQP 1.0 link"));
+ }
+ }
+}
+
+void Authorise::access(const std::string& node, bool queueRequested, bool exchangeRequested)
+{
+ if (acl) {
+ std::map<acl::Property, std::string> params;
+ bool checkExchange = true;
+ bool checkQueue = true;
+ if (exchangeRequested) checkQueue = false;
+ else if (queueRequested) checkExchange = false;
+
+ bool allowExchange = !checkExchange || acl->authorise(user, acl::ACT_ACCESS, acl::OBJ_EXCHANGE, node, &params);
+ bool allowQueue = !checkQueue || acl->authorise(user, acl::ACT_ACCESS, acl::OBJ_QUEUE, node, &params);
+
+ if (!allowQueue || !allowExchange) {
+ throw Exception(qpid::amqp::error_conditions::UNAUTHORIZED_ACCESS, QPID_MSG("ACL denied access request to " << node << " from " << user));
+ }
+ }
+}
+
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/Authorise.h b/qpid/cpp/src/qpid/broker/amqp/Authorise.h
new file mode 100644
index 0000000000..3714511177
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Authorise.h
@@ -0,0 +1,65 @@
+#ifndef QPID_BROKER_AMQP_AUTHORISE_H
+#define QPID_BROKER_AMQP_AUTHORISE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <string>
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace broker {
+class AclModule;
+class Exchange;
+class Message;
+class Queue;
+namespace amqp {
+class Filter;
+
+/**
+ * Class to handle authorisation requests (and hide the ACL mess behind)
+ */
+class Authorise
+{
+ public:
+ Authorise(const std::string& user, AclModule*);
+ void access(boost::shared_ptr<Exchange>);
+ void access(boost::shared_ptr<Queue>);
+ void incoming(boost::shared_ptr<Exchange>);
+ void incoming(boost::shared_ptr<Queue>);
+ void outgoing(boost::shared_ptr<Exchange>, boost::shared_ptr<Queue>, const Filter&);
+ void outgoing(boost::shared_ptr<Queue>);
+ void route(boost::shared_ptr<Exchange>, const Message&);
+ void interlink();
+ /**
+ * Used to determine whether the user has access permission for a
+ * given node name. If a specific type of node was requested, only
+ * acces to that type is checked. Otherwise access to either queue
+ * or exchange is required.
+ */
+ void access(const std::string& name, bool queueRequested, bool exchangeRequested);
+ private:
+ const std::string user;
+ AclModule* const acl;
+
+};
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP_AUTHORISE_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/BrokerContext.cpp b/qpid/cpp/src/qpid/broker/amqp/BrokerContext.cpp
new file mode 100644
index 0000000000..9f7ae17293
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/BrokerContext.cpp
@@ -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.
+ *
+ */
+#include "BrokerContext.h"
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+BrokerContext::BrokerContext(Broker& b, Interconnects& i, TopicRegistry& t, NodePolicyRegistry& np, const std::string& d) : broker(b), interconnects(i), topics(t), nodePolicies(np), domain(d) {}
+BrokerContext::BrokerContext(BrokerContext& c) : broker(c.broker), interconnects(c.interconnects), topics(c.topics), nodePolicies(c.nodePolicies), domain(c.domain) {}
+Broker& BrokerContext::getBroker() { return broker; }
+Interconnects& BrokerContext::getInterconnects() { return interconnects; }
+TopicRegistry& BrokerContext::getTopics() { return topics; }
+NodePolicyRegistry& BrokerContext::getNodePolicies() { return nodePolicies; }
+std::string BrokerContext::getDomain() { return domain; }
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/BrokerContext.h b/qpid/cpp/src/qpid/broker/amqp/BrokerContext.h
new file mode 100644
index 0000000000..feb35e39c4
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/BrokerContext.h
@@ -0,0 +1,55 @@
+#ifndef QPID_BROKER_AMQP_BROKERCONTEXT_H
+#define QPID_BROKER_AMQP_BROKERCONTEXT_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <string>
+
+namespace qpid {
+namespace broker {
+class Broker;
+namespace amqp {
+class Interconnects;
+class TopicRegistry;
+class NodePolicyRegistry;
+/**
+ * Context providing access to broker scoped entities.
+ */
+class BrokerContext
+{
+ public:
+ BrokerContext(Broker&, Interconnects&, TopicRegistry&, NodePolicyRegistry&, const std::string&);
+ BrokerContext(BrokerContext&);
+ Broker& getBroker();
+ Interconnects& getInterconnects();
+ TopicRegistry& getTopics();
+ NodePolicyRegistry& getNodePolicies();
+ std::string getDomain();
+ private:
+ Broker& broker;
+ Interconnects& interconnects;
+ TopicRegistry& topics;
+ NodePolicyRegistry& nodePolicies;
+ std::string domain;
+};
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP_BROKERCONTEXT_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/Connection.cpp b/qpid/cpp/src/qpid/broker/amqp/Connection.cpp
new file mode 100644
index 0000000000..b4dab83594
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Connection.cpp
@@ -0,0 +1,678 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "Connection.h"
+#include "DataReader.h"
+#include "Session.h"
+#include "Exception.h"
+#include "qpid/broker/AclModule.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/amqp/descriptors.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/framing/ProtocolInitiation.h"
+#include "qpid/framing/ProtocolVersion.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/Time.h"
+#include "qpid/sys/Timer.h"
+#include "qpid/sys/OutputControl.h"
+#include "qpid/Version.h"
+#include "config.h"
+#include <sstream>
+extern "C" {
+#include <proton/engine.h>
+#include <proton/error.h>
+#ifdef HAVE_PROTON_EVENTS
+#include <proton/event.h>
+#endif
+}
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+namespace {
+
+void do_trace(pn_transport_t* transport, const char* message)
+{
+ Connection* c = reinterpret_cast<Connection*>(pn_transport_get_context(transport));
+ if (c) c->trace(message);
+}
+
+void set_tracer(pn_transport_t* transport, void* context)
+{
+ pn_transport_set_context(transport, context);
+ pn_transport_set_tracer(transport, &do_trace);
+}
+
+#ifdef USE_PROTON_TRANSPORT_CONDITION
+std::string get_error(pn_connection_t* connection, pn_transport_t* transport)
+{
+ std::stringstream text;
+ pn_error_t* cerror = pn_connection_error(connection);
+ if (cerror) text << "connection error " << pn_error_text(cerror) << " [" << cerror << "]";
+ pn_condition_t* tcondition = pn_transport_condition(transport);
+ if (pn_condition_is_set(tcondition)) text << "transport error: " << pn_condition_get_name(tcondition) << ", " << pn_condition_get_description(tcondition);
+ return text.str();
+}
+#else
+std::string get_error(pn_connection_t* connection, pn_transport_t* transport)
+{
+ std::stringstream text;
+ pn_error_t* cerror = pn_connection_error(connection);
+ if (cerror) text << "connection error " << pn_error_text(cerror) << " [" << cerror << "]";
+ pn_error_t* terror = pn_transport_error(transport);
+ if (terror) text << "transport error " << pn_error_text(terror) << " [" << terror << "]";
+ return text.str();
+}
+#endif
+
+}
+
+void Connection::trace(const char* message) const
+{
+ QPID_LOG_CAT(trace, protocol, "[" << id << "]: " << message);
+}
+
+namespace {
+struct ConnectionTickerTask : public qpid::sys::TimerTask
+{
+ qpid::sys::Timer& timer;
+ Connection& connection;
+ ConnectionTickerTask(uint64_t interval, qpid::sys::Timer& t, Connection& c) :
+ TimerTask(qpid::sys::Duration(interval*qpid::sys::TIME_MSEC), "ConnectionTicker"),
+ timer(t),
+ connection(c)
+ {}
+
+ void fire() {
+ // Setup next firing
+ setupNextFire();
+ timer.add(this);
+
+ // Send Ticker
+ connection.requestIO();
+ }
+};
+}
+
+Connection::Connection(qpid::sys::OutputControl& o, const std::string& i, BrokerContext& b, bool saslInUse, bool brokerInitiated)
+ : BrokerContext(b), ManagedConnection(getBroker(), i, brokerInitiated),
+ connection(pn_connection()),
+ transport(pn_transport()),
+ collector(0),
+ out(o), id(i), haveOutput(true), closeInitiated(false), closeRequested(false), ioRequested(false)
+{
+#ifdef HAVE_PROTON_EVENTS
+ collector = pn_collector();
+ pn_connection_collect(connection, collector);
+#endif
+
+ if (pn_transport_bind(transport, connection)) {
+ //error
+ QPID_LOG(error, "Failed to bind transport to connection: " << getError());
+ }
+ out.activateOutput();
+ bool enableTrace(false);
+ QPID_LOG_TEST_CAT(trace, protocol, enableTrace);
+ if (enableTrace) {
+ pn_transport_trace(transport, PN_TRACE_FRM);
+ set_tracer(transport, this);
+ }
+
+ getBroker().getConnectionObservers().connection(*this);
+ if (!saslInUse) {
+ //feed in a dummy AMQP 1.0 header as engine expects one, but
+ //we already read it (if sasl is in use we read the sasl
+ //header,not the AMQP 1.0 header).
+ std::vector<char> protocolHeader(8);
+ qpid::framing::ProtocolInitiation pi(getVersion());
+ qpid::framing::Buffer buffer(&protocolHeader[0], protocolHeader.size());
+ pi.encode(buffer);
+ pn_transport_input(transport, &protocolHeader[0], protocolHeader.size());
+
+ setUserId("none");
+ }
+}
+
+void Connection::requestIO()
+{
+ ioRequested = true;
+ out.activateOutput();
+}
+
+Connection::~Connection()
+{
+ if (ticker) ticker->cancel();
+ getBroker().getConnectionObservers().closed(*this);
+ pn_connection_free(connection);
+ pn_transport_free(transport);
+#ifdef HAVE_PROTON_EVENTS
+ pn_collector_free(collector);
+#endif
+}
+
+pn_transport_t* Connection::getTransport()
+{
+ return transport;
+}
+size_t Connection::decode(const char* buffer, size_t size)
+{
+ QPID_LOG(trace, id << " decode(" << size << ")");
+ if (size == 0) return 0;
+
+ ssize_t n = pn_transport_input(transport, const_cast<char*>(buffer), size);
+ if (n > 0 || n == PN_EOS) {
+ // PN_EOS either means we received a Close (which also means we've
+ // consumed all the input), OR some Very Bad Thing happened and this
+ // connection is toast.
+ if (n == PN_EOS)
+ {
+ std::string error;
+ if (checkTransportError(error)) {
+ // "He's dead, Jim."
+ QPID_LOG_CAT(error, network, id << " connection failed: " << error);
+ out.abort();
+ return 0;
+ } else {
+ n = size; // assume all consumed
+ }
+ }
+ QPID_LOG_CAT(debug, network, id << " decoded " << n << " bytes from " << size);
+ try {
+ process();
+ } catch (const Exception& e) {
+ QPID_LOG(error, id << ": " << e.what());
+ pn_condition_t* error = pn_connection_condition(connection);
+ pn_condition_set_name(error, e.symbol());
+ pn_condition_set_description(error, e.what());
+ close();
+ } catch (const std::exception& e) {
+ QPID_LOG(error, id << ": " << e.what());
+ pn_condition_t* error = pn_connection_condition(connection);
+ pn_condition_set_name(error, qpid::amqp::error_conditions::INTERNAL_ERROR.c_str());
+ pn_condition_set_description(error, e.what());
+ close();
+ }
+ pn_transport_tick(transport, qpid::sys::Duration::FromEpoch() / qpid::sys::TIME_MSEC);
+ if (!haveOutput) {
+ haveOutput = true;
+ out.activateOutput();
+ }
+ return n;
+ } else if (n == PN_ERR) {
+ std::string error;
+ checkTransportError(error);
+ QPID_LOG_CAT(error, network, id << " connection error: " << error);
+ out.abort();
+ return 0;
+ } else {
+ return 0;
+ }
+}
+
+size_t Connection::encode(char* buffer, size_t size)
+{
+ QPID_LOG(trace, "encode(" << size << ")");
+ doOutput(size);
+ ssize_t n = pn_transport_output(transport, buffer, size);
+ if (n > 0) {
+ QPID_LOG_CAT(debug, network, id << " encoded " << n << " bytes from " << size)
+ haveOutput = true;
+ return n;
+ } else if (n == PN_EOS) {
+ haveOutput = false;
+ // Normal close, or error?
+ std::string error;
+ if (checkTransportError(error)) {
+ QPID_LOG_CAT(error, network, id << " connection failed: " << error);
+ out.abort();
+ }
+ return 0;
+ } else if (n == PN_ERR) {
+ std::string error;
+ checkTransportError(error);
+ QPID_LOG_CAT(error, network, id << " connection error: " << error);
+ out.abort();
+ return 0;
+ } else {
+ haveOutput = false;
+ return 0;
+ }
+}
+
+void Connection::doOutput(size_t capacity)
+{
+ ssize_t n = 0;
+ do {
+ if (dispatch()) {
+ processDeliveries();
+ ssize_t next = pn_transport_pending(transport);
+ if (n == next) break;
+ n = next;
+ } else break;
+ } while (n > 0 && n < (ssize_t) capacity);
+}
+
+bool Connection::dispatch()
+{
+ bool result = false;
+ for (Sessions::iterator i = sessions.begin();i != sessions.end();) {
+ if (i->second->endedByManagement()) {
+ pn_session_close(i->first);
+ i->second->close();
+ sessions.erase(i++);
+ result = true;
+ QPID_LOG_CAT(debug, model, id << " session ended by management");
+ } else {
+ if (i->second->dispatch()) result = true;
+ ++i;
+ }
+ }
+ return result;
+}
+
+bool Connection::canEncode()
+{
+ if (!closeInitiated) {
+ if (closeRequested) {
+ close();
+ return true;
+ }
+ try {
+ if (dispatch()) haveOutput = true;
+ process();
+ } catch (const Exception& e) {
+ QPID_LOG(error, id << ": " << e.what());
+ pn_condition_t* error = pn_connection_condition(connection);
+ pn_condition_set_name(error, e.symbol());
+ pn_condition_set_description(error, e.what());
+ close();
+ haveOutput = true;
+ } catch (const std::exception& e) {
+ QPID_LOG(error, id << ": " << e.what());
+ pn_condition_t* error = pn_connection_condition(connection);
+ pn_condition_set_name(error, qpid::amqp::error_conditions::INTERNAL_ERROR.c_str());
+ pn_condition_set_description(error, e.what());
+ close();
+ haveOutput = true;
+ }
+ } else {
+ QPID_LOG(info, "Connection " << id << " has been closed locally");
+ }
+ if (ioRequested.valueCompareAndSwap(true, false)) haveOutput = true;
+ pn_transport_tick(transport, qpid::sys::Duration::FromEpoch() / qpid::sys::TIME_MSEC);
+ QPID_LOG_CAT(trace, network, id << " canEncode(): " << haveOutput)
+ return haveOutput;
+}
+
+void Connection::open()
+{
+ readPeerProperties();
+
+ pn_connection_set_container(connection, getBroker().getFederationTag().c_str());
+ uint32_t timeout = pn_transport_get_remote_idle_timeout(transport);
+ if (timeout) {
+ // if idle generate empty frames at 1/2 the timeout interval as keepalives:
+ ticker = boost::intrusive_ptr<qpid::sys::TimerTask>(new ConnectionTickerTask((timeout+1)/2,
+ getBroker().getTimer(),
+ *this));
+ getBroker().getTimer().add(ticker);
+
+ // Note: in version 0-10 of the protocol, idle timeout applies to both
+ // ends. AMQP 1.0 changes that - it's now asymmetric: each end can
+ // configure/disable it independently. For backward compatibility, by
+ // default mimic the old behavior and set our local timeout.
+ // Use 2x the remote's timeout, as per the spec the remote should
+ // advertise 1/2 its actual timeout threshold
+ pn_transport_set_idle_timeout(transport, timeout * 2);
+ QPID_LOG_CAT(debug, network, id << " AMQP 1.0 idle-timeout set:"
+ << " local=" << pn_transport_get_idle_timeout(transport)
+ << " remote=" << pn_transport_get_remote_idle_timeout(transport));
+ }
+
+ // QPID-6592: put self-identifying information into the connection
+ // properties. Use keys defined by the 0-10 spec, as AMQP 1.0 has yet to
+ // define any.
+ //
+ pn_data_t *props = pn_connection_properties(connection);
+ if (props) {
+ boost::shared_ptr<const System> sysInfo = getBroker().getSystem();
+ std::string osName(sysInfo->getOsName());
+ std::string nodeName(sysInfo->getNodeName());
+
+ pn_data_clear(props);
+ pn_data_put_map(props);
+ pn_data_enter(props);
+ pn_data_put_symbol(props, pn_bytes(7, "product"));
+ pn_data_put_string(props, pn_bytes(qpid::product.size(), qpid::product.c_str()));
+ pn_data_put_symbol(props, pn_bytes(7, "version"));
+ pn_data_put_string(props, pn_bytes(qpid::version.size(), qpid::version.c_str()));
+ pn_data_put_symbol(props, pn_bytes(8, "platform"));
+ pn_data_put_string(props, pn_bytes(osName.size(), osName.c_str()));
+ pn_data_put_symbol(props, pn_bytes(4, "host"));
+ pn_data_put_string(props, pn_bytes(nodeName.size(), nodeName.c_str()));
+ pn_data_exit(props);
+ pn_data_rewind(props);
+ }
+
+ pn_connection_open(connection);
+ out.connectionEstablished();
+ opened();
+ getBroker().getConnectionObservers().opened(*this);
+}
+
+void Connection::readPeerProperties()
+{
+ qpid::types::Variant::Map properties;
+ DataReader::read(pn_connection_remote_properties(connection), properties);
+ setPeerProperties(properties);
+}
+
+void Connection::closed()
+{
+ if (ticker) ticker->cancel();
+ for (Sessions::iterator i = sessions.begin(); i != sessions.end(); ++i) {
+ i->second->close();
+ }
+}
+void Connection::close()
+{
+ if (!closeInitiated) {
+ closeInitiated = true;
+ closed();
+ QPID_LOG_CAT(debug, model, id << " connection closed");
+ pn_connection_close(connection);
+ }
+}
+bool Connection::isClosed() const
+{
+ return pn_connection_state(connection) & PN_REMOTE_CLOSED;
+}
+framing::ProtocolVersion Connection::getVersion() const
+{
+ return qpid::framing::ProtocolVersion(1,0);
+}
+
+void Connection::process()
+{
+ QPID_LOG(trace, id << " process()");
+#ifdef HAVE_PROTON_EVENTS
+ pn_event_t *event = pn_collector_peek(collector);
+ while (event) {
+ switch (pn_event_type(event)) {
+ case PN_CONNECTION_REMOTE_OPEN:
+ doConnectionRemoteOpen();
+ break;
+ case PN_CONNECTION_REMOTE_CLOSE:
+ doConnectionRemoteClose();
+ break;
+ case PN_SESSION_REMOTE_OPEN:
+ doSessionRemoteOpen(pn_event_session(event));
+ break;
+ case PN_SESSION_REMOTE_CLOSE:
+ doSessionRemoteClose(pn_event_session(event));
+ break;
+ case PN_LINK_REMOTE_OPEN:
+ doLinkRemoteOpen(pn_event_link(event));
+ break;
+ case PN_LINK_REMOTE_DETACH:
+ doLinkRemoteDetach(pn_event_link(event), false);
+ break;
+ case PN_LINK_REMOTE_CLOSE:
+ doLinkRemoteClose(pn_event_link(event));
+ break;
+ case PN_DELIVERY:
+ doDeliveryUpdated(pn_event_delivery(event));
+ break;
+ default:
+ break;
+ }
+ pn_collector_pop(collector);
+ event = pn_collector_peek(collector);
+ }
+
+#else // !HAVE_PROTON_EVENTS
+
+ const pn_state_t REQUIRES_OPEN = PN_LOCAL_UNINIT | PN_REMOTE_ACTIVE;
+ const pn_state_t REQUIRES_CLOSE = PN_LOCAL_ACTIVE | PN_REMOTE_CLOSED;
+
+ if ((pn_connection_state(connection) & REQUIRES_OPEN) == REQUIRES_OPEN) {
+ doConnectionRemoteOpen();
+ }
+
+ for (pn_session_t* s = pn_session_head(connection, REQUIRES_OPEN); s; s = pn_session_next(s, REQUIRES_OPEN)) {
+ doSessionRemoteOpen(s);
+ }
+ for (pn_link_t* l = pn_link_head(connection, REQUIRES_OPEN); l; l = pn_link_next(l, REQUIRES_OPEN)) {
+ doLinkRemoteOpen(l);
+ }
+
+ processDeliveries();
+
+ for (pn_link_t* l = pn_link_head(connection, REQUIRES_CLOSE), *next = 0;
+ l; l = next) {
+ next = pn_link_next(l, REQUIRES_CLOSE);
+ doLinkRemoteClose(l);
+ }
+ for (pn_session_t* s = pn_session_head(connection, REQUIRES_CLOSE), *next = 0;
+ s; s = next) {
+ next = pn_session_next(s, REQUIRES_CLOSE);
+ doSessionRemoteClose(s);
+ }
+ if ((pn_connection_state(connection) & REQUIRES_CLOSE) == REQUIRES_CLOSE) {
+ doConnectionRemoteClose();
+ }
+#endif // !HAVE_PROTON_EVENTS
+}
+void Connection::processDeliveries()
+{
+#ifdef HAVE_PROTON_EVENTS
+ // with the event API, there's no way to selectively process only
+ // the delivery-related events. We have to process all events:
+ process();
+#else
+ for (pn_delivery_t* delivery = pn_work_head(connection); delivery; delivery = pn_work_next(delivery)) {
+ doDeliveryUpdated(delivery);
+ }
+#endif
+}
+
+std::string Connection::getError()
+{
+ return get_error(connection, transport);
+}
+
+void Connection::abort()
+{
+ out.abort();
+}
+
+void Connection::setUserId(const std::string& user)
+{
+ ManagedConnection::setUserId(user);
+ AclModule* acl = getBroker().getAcl();
+ if (acl && !acl->approveConnection(*this))
+ {
+ throw Exception(qpid::amqp::error_conditions::RESOURCE_LIMIT_EXCEEDED, "User connection denied by configured limit");
+ }
+}
+
+void Connection::closedByManagement()
+{
+ closeRequested = true;
+ out.activateOutput();
+}
+
+// the peer has issued an Open performative
+void Connection::doConnectionRemoteOpen()
+{
+ // respond in kind if we haven't yet
+ if ((pn_connection_state(connection) & PN_LOCAL_UNINIT) == PN_LOCAL_UNINIT) {
+ QPID_LOG_CAT(debug, model, id << " connection opened");
+ open();
+ setContainerId(pn_connection_remote_container(connection));
+ }
+}
+
+// the peer has issued a Close performative
+void Connection::doConnectionRemoteClose()
+{
+ if ((pn_connection_state(connection) & PN_LOCAL_CLOSED) == 0) {
+ QPID_LOG_CAT(debug, model, id << " connection closed");
+ pn_connection_close(connection);
+ }
+}
+
+// the peer has issued a Begin performative
+void Connection::doSessionRemoteOpen(pn_session_t *session)
+{
+ if ((pn_session_state(session) & PN_LOCAL_UNINIT) == PN_LOCAL_UNINIT) {
+ QPID_LOG_CAT(debug, model, id << " session begun");
+ pn_session_open(session);
+ boost::shared_ptr<Session> ssn(new Session(session, *this, out));
+ sessions[session] = ssn;
+ }
+}
+
+// the peer has issued an End performative
+void Connection::doSessionRemoteClose(pn_session_t *session)
+{
+ if ((pn_session_state(session) & PN_LOCAL_CLOSED) == 0) {
+ pn_session_close(session);
+ Sessions::iterator i = sessions.find(session);
+ if (i != sessions.end()) {
+ i->second->close();
+ sessions.erase(i);
+ QPID_LOG_CAT(debug, model, id << " session ended");
+ } else {
+ QPID_LOG(error, id << " peer attempted to close unrecognised session");
+ }
+ }
+ pn_session_free(session);
+}
+
+// the peer has issued an Attach performative
+void Connection::doLinkRemoteOpen(pn_link_t *link)
+{
+ if ((pn_link_state(link) & PN_LOCAL_UNINIT) == PN_LOCAL_UNINIT) {
+ pn_link_open(link);
+ Sessions::iterator session = sessions.find(pn_link_session(link));
+ if (session == sessions.end()) {
+ QPID_LOG(error, id << " Link attached on unknown session!");
+ } else {
+ try {
+ session->second->attach(link);
+ QPID_LOG_CAT(debug, protocol, id << " link " << link << " attached on " << pn_link_session(link));
+ } catch (const Exception& e) {
+ QPID_LOG_CAT(error, protocol, "Error on attach: " << e.what());
+ pn_condition_t* error = pn_link_condition(link);
+ pn_condition_set_name(error, e.symbol());
+ pn_condition_set_description(error, e.what());
+ pn_link_close(link);
+ } catch (const qpid::framing::UnauthorizedAccessException& e) {
+ QPID_LOG_CAT(error, protocol, "Error on attach: " << e.what());
+ pn_condition_t* error = pn_link_condition(link);
+ pn_condition_set_name(error, qpid::amqp::error_conditions::UNAUTHORIZED_ACCESS.c_str());
+ pn_condition_set_description(error, e.what());
+ pn_link_close(link);
+ } catch (const std::exception& e) {
+ QPID_LOG_CAT(error, protocol, "Error on attach: " << e.what());
+ pn_condition_t* error = pn_link_condition(link);
+ pn_condition_set_name(error, qpid::amqp::error_conditions::INTERNAL_ERROR.c_str());
+ pn_condition_set_description(error, e.what());
+ pn_link_close(link);
+ }
+ }
+ }
+}
+
+// the peer has issued a Detach performative with closed=true
+void Connection::doLinkRemoteClose(pn_link_t *link)
+{
+ doLinkRemoteDetach(link, true);
+}
+// the peer has issued a Detach performative
+void Connection::doLinkRemoteDetach(pn_link_t *link, bool closed)
+{
+ if ((pn_link_state(link) & PN_LOCAL_CLOSED) == 0) {
+ if (closed) pn_link_close(link);
+ //pn_link_detach was only introduced after 0.7, as was the event interface:
+#ifdef HAVE_PROTON_EVENTS
+ else pn_link_detach(link);
+#endif
+ Sessions::iterator session = sessions.find(pn_link_session(link));
+ if (session == sessions.end()) {
+ QPID_LOG(error, id << " peer attempted to detach link on unknown session!");
+ } else {
+ session->second->detach(link, closed);
+ QPID_LOG_CAT(debug, model, id << " link detached");
+ }
+ }
+ pn_link_free(link);
+}
+
+// the status of the delivery has changed
+void Connection::doDeliveryUpdated(pn_delivery_t *delivery)
+{
+ pn_link_t* link = pn_delivery_link(delivery);
+ try {
+ if (pn_link_is_receiver(link)) {
+ Sessions::iterator i = sessions.find(pn_link_session(link));
+ if (i != sessions.end()) {
+ i->second->readable(link, delivery);
+ } else {
+ pn_delivery_update(delivery, PN_REJECTED);
+ }
+ } else { //i.e. SENDER
+ Sessions::iterator i = sessions.find(pn_link_session(link));
+ if (i != sessions.end()) {
+ QPID_LOG(trace, id << " handling outgoing delivery for " << link << " on session " << pn_link_session(link));
+ i->second->writable(link, delivery);
+ } else {
+ QPID_LOG(error, id << " Got delivery for non-existent session: " << pn_link_session(link) << ", link: " << link);
+ }
+ }
+ } catch (const Exception& e) {
+ QPID_LOG_CAT(error, protocol, "Error processing deliveries: " << e.what());
+ pn_condition_t* error = pn_link_condition(link);
+ pn_condition_set_name(error, e.symbol());
+ pn_condition_set_description(error, e.what());
+ pn_link_close(link);
+ }
+}
+
+// check for failures of the transport:
+bool Connection::checkTransportError(std::string& text)
+{
+ std::stringstream info;
+
+#ifdef USE_PROTON_TRANSPORT_CONDITION
+ pn_condition_t* tcondition = pn_transport_condition(transport);
+ if (pn_condition_is_set(tcondition))
+ info << "transport error: " << pn_condition_get_name(tcondition) << ", " << pn_condition_get_description(tcondition);
+#else
+ pn_error_t* terror = pn_transport_error(transport);
+ if (terror) info << "transport error " << pn_error_text(terror) << " [" << terror << "]";
+#endif
+
+ text = info.str();
+ return !text.empty();
+}
+
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/Connection.h b/qpid/cpp/src/qpid/broker/amqp/Connection.h
new file mode 100644
index 0000000000..0d06f18924
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Connection.h
@@ -0,0 +1,111 @@
+#ifndef QPID_BROKER_AMQP1_CONNECTION_H
+#define QPID_BROKER_AMQP1_CONNECTION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/sys/ConnectionCodec.h"
+#include "qpid/broker/amqp/BrokerContext.h"
+#include "qpid/broker/amqp/ManagedConnection.h"
+#include "qpid/sys/AtomicValue.h"
+#include <map>
+#include <boost/intrusive_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+
+struct pn_connection_t;
+struct pn_session_t;
+struct pn_transport_t;
+struct pn_collector_t;
+struct pn_link_t;
+struct pn_delivery_t;
+
+namespace qpid {
+namespace sys {
+class TimerTask;
+}
+namespace broker {
+
+class Broker;
+
+namespace amqp {
+
+class Interconnects;
+class Session;
+/**
+ * AMQP 1.0 protocol support for broker
+ */
+class Connection : public BrokerContext, public sys::ConnectionCodec, public ManagedConnection
+{
+ public:
+ Connection(qpid::sys::OutputControl& out, const std::string& id, BrokerContext& context, bool saslInUse, bool brokerInitiated);
+ virtual ~Connection();
+ size_t decode(const char* buffer, size_t size);
+ virtual size_t encode(char* buffer, size_t size);
+ bool canEncode();
+
+ void closed();
+ bool isClosed() const;
+
+ framing::ProtocolVersion getVersion() const;
+ pn_transport_t* getTransport();
+ void setUserId(const std::string&);
+ void abort();
+ void trace(const char*) const;
+ void requestIO();
+ protected:
+ typedef std::map<pn_session_t*, boost::shared_ptr<Session> > Sessions;
+ pn_connection_t* connection;
+ pn_transport_t* transport;
+ pn_collector_t* collector;
+ qpid::sys::OutputControl& out;
+ const std::string id;
+ bool haveOutput;
+ Sessions sessions;
+ bool closeInitiated;
+ bool closeRequested;
+ boost::intrusive_ptr<sys::TimerTask> ticker;
+ qpid::sys::AtomicValue<bool> ioRequested;
+
+ virtual void process();
+ void doOutput(size_t);
+ bool dispatch();
+ void processDeliveries();
+ std::string getError();
+ void close();
+ void open();
+ void readPeerProperties();
+ void closedByManagement();
+
+ private:
+ bool checkTransportError(std::string&);
+
+ // handle Proton engine events
+ void doConnectionRemoteOpen();
+ void doConnectionRemoteClose();
+ void doSessionRemoteOpen(pn_session_t *session);
+ void doSessionRemoteClose(pn_session_t *session);
+ void doLinkRemoteOpen(pn_link_t *link);
+ void doLinkRemoteClose(pn_link_t *link);
+ void doLinkRemoteDetach(pn_link_t *link, bool closed);
+ void doDeliveryUpdated(pn_delivery_t *delivery);
+};
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP1_CONNECTION_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/DataReader.cpp b/qpid/cpp/src/qpid/broker/amqp/DataReader.cpp
new file mode 100644
index 0000000000..ca3e6daabd
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/DataReader.cpp
@@ -0,0 +1,193 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "DataReader.h"
+#include "qpid/amqp/CharSequence.h"
+#include "qpid/amqp/Descriptor.h"
+#include "qpid/amqp/MapBuilder.h"
+#include "qpid/log/Statement.h"
+#include <string>
+extern "C" {
+#include <proton/engine.h>
+}
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+namespace {
+qpid::amqp::CharSequence convert(pn_bytes_t in)
+{
+ qpid::amqp::CharSequence out;
+ out.data = in.start;
+ out.size = in.size;
+ return out;
+}
+
+qpid::amqp::CharSequence convert(pn_uuid_t in)
+{
+ qpid::amqp::CharSequence out;
+ out.data = in.bytes;
+ out.size = 16;
+ return out;
+}
+}
+
+DataReader::DataReader(qpid::amqp::Reader& r) : reader(r) {}
+
+void DataReader::read(pn_data_t* data)
+{
+ do {
+ readOne(data);
+ } while (pn_data_next(data));
+}
+void DataReader::readOne(pn_data_t* data)
+{
+ qpid::amqp::Descriptor descriptor(0);
+ bool described = pn_data_is_described(data);
+ if (described) {
+ pn_data_enter(data);
+ pn_data_next(data);
+ if (pn_data_type(data) == PN_ULONG) {
+ descriptor = qpid::amqp::Descriptor(pn_data_get_ulong(data));
+ } else if (pn_data_type(data) == PN_SYMBOL) {
+ descriptor = qpid::amqp::Descriptor(convert(pn_data_get_symbol(data)));
+ } else {
+ QPID_LOG(notice, "Ignoring descriptor of type " << pn_data_type(data));
+ }
+ pn_data_next(data);
+ }
+ switch (pn_data_type(data)) {
+ case PN_NULL:
+ reader.onNull(described ? &descriptor : 0);
+ break;
+ case PN_BOOL:
+ reader.onBoolean(pn_data_get_bool(data), described ? &descriptor : 0);
+ break;
+ case PN_UBYTE:
+ reader.onUByte(pn_data_get_ubyte(data), described ? &descriptor : 0);
+ break;
+ case PN_BYTE:
+ reader.onByte(pn_data_get_byte(data), described ? &descriptor : 0);
+ break;
+ case PN_USHORT:
+ reader.onUShort(pn_data_get_ushort(data), described ? &descriptor : 0);
+ break;
+ case PN_SHORT:
+ reader.onShort(pn_data_get_short(data), described ? &descriptor : 0);
+ break;
+ case PN_UINT:
+ reader.onUInt(pn_data_get_uint(data), described ? &descriptor : 0);
+ break;
+ case PN_INT:
+ reader.onInt(pn_data_get_int(data), described ? &descriptor : 0);
+ break;
+ case PN_CHAR:
+ pn_data_get_char(data);
+ break;
+ case PN_ULONG:
+ reader.onULong(pn_data_get_ulong(data), described ? &descriptor : 0);
+ break;
+ case PN_LONG:
+ reader.onLong(pn_data_get_long(data), described ? &descriptor : 0);
+ break;
+ case PN_TIMESTAMP:
+ reader.onTimestamp(pn_data_get_timestamp(data), described ? &descriptor : 0);
+ break;
+ case PN_FLOAT:
+ reader.onFloat(pn_data_get_float(data), described ? &descriptor : 0);
+ break;
+ case PN_DOUBLE:
+ reader.onDouble(pn_data_get_double(data), described ? &descriptor : 0);
+ break;
+ case PN_DECIMAL32:
+ pn_data_get_decimal32(data);
+ break;
+ case PN_DECIMAL64:
+ pn_data_get_decimal64(data);
+ break;
+ case PN_DECIMAL128:
+ pn_data_get_decimal128(data);
+ break;
+ case PN_UUID:
+ reader.onUuid(convert(pn_data_get_uuid(data)), described ? &descriptor : 0);
+ break;
+ case PN_BINARY:
+ reader.onBinary(convert(pn_data_get_binary(data)), described ? &descriptor : 0);
+ break;
+ case PN_STRING:
+ reader.onString(convert(pn_data_get_string(data)), described ? &descriptor : 0);
+ break;
+ case PN_SYMBOL:
+ reader.onSymbol(convert(pn_data_get_symbol(data)), described ? &descriptor : 0);
+ break;
+ case PN_DESCRIBED:
+ break;
+ case PN_ARRAY:
+ readArray(data, described ? &descriptor : 0);
+ break;
+ case PN_LIST:
+ readList(data, described ? &descriptor : 0);
+ break;
+ case PN_MAP:
+ readMap(data, described ? &descriptor : 0);
+ break;
+ }
+ if (described) pn_data_exit(data);
+}
+
+void DataReader::readArray(pn_data_t* /*data*/, const qpid::amqp::Descriptor* /*descriptor*/)
+{
+ //not yet implemented
+}
+
+void DataReader::readList(pn_data_t* data, const qpid::amqp::Descriptor* descriptor)
+{
+ size_t count = pn_data_get_list(data);
+ bool skip = reader.onStartList(count, qpid::amqp::CharSequence(), qpid::amqp::CharSequence(), descriptor);
+ if (!skip) {
+ pn_data_enter(data);
+ for (size_t i = 0; i < count && pn_data_next(data); ++i) {
+ read(data);
+ }
+ pn_data_exit(data);
+ reader.onEndList(count, descriptor);
+ }
+}
+
+void DataReader::readMap(pn_data_t* data, const qpid::amqp::Descriptor* descriptor)
+{
+ size_t count = pn_data_get_map(data);
+ reader.onStartMap(count, qpid::amqp::CharSequence(), qpid::amqp::CharSequence(), descriptor);
+ pn_data_enter(data);
+ for (size_t i = 0; i < count && pn_data_next(data); ++i) {
+ read(data);
+ }
+ pn_data_exit(data);
+ reader.onEndMap(count, descriptor);
+}
+
+void DataReader::read(pn_data_t* data, std::map<std::string, qpid::types::Variant>& out)
+{
+ qpid::amqp::MapBuilder builder;
+ DataReader reader(builder);
+ reader.read(data);
+ out = builder.getMap();
+}
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/DataReader.h b/qpid/cpp/src/qpid/broker/amqp/DataReader.h
new file mode 100644
index 0000000000..99ff77b3dd
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/DataReader.h
@@ -0,0 +1,59 @@
+#ifndef QPID_BROKER_AMQP_DATAREADER_H
+#define QPID_BROKER_AMQP_DATAREADER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/amqp/Reader.h"
+#include <map>
+#include <string>
+
+struct pn_data_t;
+
+namespace qpid {
+namespace types {
+class Variant;
+}
+namespace amqp {
+struct Descriptor;
+}
+namespace broker {
+namespace amqp {
+
+/**
+ * Allows use of Reader interface to read pn_data_t* data.
+ */
+class DataReader
+{
+ public:
+ DataReader(qpid::amqp::Reader& reader);
+ void read(pn_data_t*);
+ static void read(pn_data_t*, std::map<std::string, qpid::types::Variant>&);
+ private:
+ qpid::amqp::Reader& reader;
+
+ void readOne(pn_data_t*);
+ void readMap(pn_data_t*, const qpid::amqp::Descriptor*);
+ void readList(pn_data_t*, const qpid::amqp::Descriptor*);
+ void readArray(pn_data_t*, const qpid::amqp::Descriptor*);
+};
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP_DATAREADER_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/Domain.cpp b/qpid/cpp/src/qpid/broker/amqp/Domain.cpp
new file mode 100644
index 0000000000..c2d4782fc4
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Domain.cpp
@@ -0,0 +1,303 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "Domain.h"
+#include "Interconnect.h"
+#include "Interconnects.h"
+#include "SaslClient.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/Exception.h"
+#include "qpid/SaslFactory.h"
+#include "qpid/sys/ConnectionCodec.h"
+#include "qpid/sys/OutputControl.h"
+#include "qpid/log/Statement.h"
+#include "qpid/management/ManagementAgent.h"
+#include <boost/shared_ptr.hpp>
+#include <boost/lexical_cast.hpp>
+#include <sstream>
+
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+
+namespace {
+const std::string NONE("NONE");
+const std::string SOURCE("source");
+const std::string TARGET("target");
+const std::string URL("url");
+const std::string USERNAME("username");
+const std::string PASSWORD("password");
+const std::string SASL_MECHANISMS("sasl_mechanisms");
+const std::string SASL_SERVICE("sasl_service");
+const std::string MIN_SSF("min_ssf");
+const std::string MAX_SSF("max_ssf");
+const std::string DURABLE("durable");
+class Wrapper : public qpid::sys::ConnectionCodec
+{
+ public:
+ Wrapper(boost::shared_ptr<Interconnect> c) : connection(c) {}
+ ~Wrapper()
+ {
+ QPID_LOG(debug, "Wrapper for non-SASL based interconnect has been deleted");
+ connection->transportDeleted();
+ }
+
+ std::size_t decode(const char* buffer, std::size_t size)
+ {
+ return connection->decode(buffer, size);
+ }
+ std::size_t encode(char* buffer, std::size_t size)
+ {
+ return connection->encode(buffer, size);
+ }
+ bool canEncode()
+ {
+ return connection->canEncode();
+ }
+ void closed()
+ {
+ connection->closed();
+ }
+ bool isClosed() const
+ {
+ QPID_LOG(debug, "Wrapper for non_SASL based interconnect " << (connection->isClosed() ? " IS " : " IS NOT ") << " closed");
+ return connection->isClosed();
+ }
+ qpid::framing::ProtocolVersion getVersion() const
+ {
+ return connection->getVersion();
+ }
+ private:
+ boost::shared_ptr<Interconnect> connection;
+};
+
+bool get(std::string& result, const std::string& key, const qpid::types::Variant::Map& map)
+{
+ qpid::types::Variant::Map::const_iterator i = map.find(key);
+ if (i != map.end()) {
+ result = i->second.asString();
+ return true;
+ } else {
+ return false;
+ }
+}
+bool get(int& result, const std::string& key, const qpid::types::Variant::Map& map)
+{
+ qpid::types::Variant::Map::const_iterator i = map.find(key);
+ if (i != map.end()) {
+ result = i->second;
+ return true;
+ } else {
+ return false;
+ }
+}
+bool get(qpid::Url& url, const std::string& key, const qpid::types::Variant::Map& map)
+{
+ qpid::types::Variant::Map::const_iterator i = map.find(key);
+ if (i != map.end()) {
+ url = qpid::Url(i->second.asString());
+ return true;
+ } else {
+ return false;
+ }
+}
+bool get(const std::string& key, const qpid::types::Variant::Map& map)
+{
+ qpid::types::Variant::Map::const_iterator i = map.find(key);
+ if (i != map.end()) {
+ return i->second.asBool();
+ } else {
+ return false;
+ }
+}
+}
+
+class InterconnectFactory : public BrokerContext, public qpid::sys::ConnectionCodec::Factory, public boost::enable_shared_from_this<InterconnectFactory>
+{
+ public:
+ InterconnectFactory(bool incoming, const std::string& name, const qpid::types::Variant::Map& properties,
+ boost::shared_ptr<Domain>, BrokerContext&);
+ InterconnectFactory(bool incoming, const std::string& name, const std::string& source, const std::string& target,
+ boost::shared_ptr<Domain>, BrokerContext&, boost::shared_ptr<Relay>);
+ qpid::sys::ConnectionCodec* create(const framing::ProtocolVersion&, qpid::sys::OutputControl&, const std::string&, const qpid::sys::SecuritySettings&);
+ qpid::sys::ConnectionCodec* create(qpid::sys::OutputControl&, const std::string&, const qpid::sys::SecuritySettings&);
+ qpid::framing::ProtocolVersion supportedVersion() const
+ {
+ return qpid::framing::ProtocolVersion(1, 0);
+ }
+ bool connect();
+ void failed(int, std::string);
+ private:
+ bool incoming;
+ std::string name;
+ std::string source;
+ std::string target;
+ qpid::Url url;
+ qpid::Url::iterator next;
+ std::string hostname;
+ boost::shared_ptr<Domain> domain;
+ qpid::Address address;
+ boost::shared_ptr<Relay> relay;
+};
+
+InterconnectFactory::InterconnectFactory(bool i, const std::string& n, const qpid::types::Variant::Map& properties, boost::shared_ptr<Domain> d, BrokerContext& c)
+ : BrokerContext(c), incoming(i), name(n), url(d->getUrl()), domain(d)
+{
+ get(source, SOURCE, properties);
+ get(target, TARGET, properties);
+ next = url.begin();
+}
+
+InterconnectFactory::InterconnectFactory(bool i, const std::string& n, const std::string& source_, const std::string& target_,
+ boost::shared_ptr<Domain> d, BrokerContext& c, boost::shared_ptr<Relay> relay_)
+ : BrokerContext(c), incoming(i), name(n), source(source_), target(target_), url(d->getUrl()), domain(d), relay(relay_)
+{
+ next = url.begin();
+}
+
+qpid::sys::ConnectionCodec* InterconnectFactory::create(const qpid::framing::ProtocolVersion&, qpid::sys::OutputControl&, const std::string&, const qpid::sys::SecuritySettings&)
+{
+ throw qpid::Exception("Not implemented!");
+}
+qpid::sys::ConnectionCodec* InterconnectFactory::create(qpid::sys::OutputControl& out, const std::string& id, const qpid::sys::SecuritySettings& t)
+{
+ bool useSasl = domain->getMechanisms() != NONE;
+ boost::shared_ptr<Interconnect> connection(new Interconnect(out, id, *this, useSasl, incoming, name, source, target, domain->getName()));
+ if (!relay) getInterconnects().add(name, connection);
+ else connection->setRelay(relay);
+
+ std::auto_ptr<qpid::sys::ConnectionCodec> codec;
+ if (useSasl) {
+ QPID_LOG(info, "Using AMQP 1.0 (with SASL layer) on connect");
+ codec = std::auto_ptr<qpid::sys::ConnectionCodec>(new qpid::broker::amqp::SaslClient(out, id, connection, domain->sasl(hostname), hostname, domain->getMechanisms(), t));
+ } else {
+ QPID_LOG(info, "Using AMQP 1.0 (no SASL layer) on connect");
+ codec = std::auto_ptr<qpid::sys::ConnectionCodec>(new Wrapper(connection));
+ }
+ domain->removePending(shared_from_this());//(TODO: add support for retry on connection failure)
+ return codec.release();
+}
+
+bool InterconnectFactory::connect()
+{
+ if (next == url.end()) return false;
+ address = *next;
+ next++;
+ hostname = address.host;
+ QPID_LOG (info, "Inter-broker connection initiated (" << address << ")");
+ std::stringstream identifier;
+ identifier << name << "@" << domain->getName();
+ getBroker().connect(identifier.str(), address.host, boost::lexical_cast<std::string>(address.port), address.protocol, this, boost::bind(&InterconnectFactory::failed, this, _1, _2));
+ return true;
+}
+
+void InterconnectFactory::failed(int, std::string text)
+{
+ QPID_LOG (info, "Inter-broker connection failed (" << address << "): " << text);
+ if (!connect()) {
+ domain->removePending(shared_from_this());//give up (TODO: add support for periodic retry)
+ }
+}
+
+Domain::Domain(const std::string& n, const qpid::types::Variant::Map& properties, Broker& b)
+ : PersistableObject(n, "domain", properties), name(n), durable(get(DURABLE, properties)), broker(b), mechanisms("ANONYMOUS"), service(qpid::saslName), minSsf(0), maxSsf(0), agent(b.getManagementAgent())
+{
+ if (!get(url, URL, properties)) {
+ QPID_LOG(error, "No URL specified for domain " << name << "!");
+ throw qpid::Exception("A url is required for a domain!");
+ } else {
+ QPID_LOG(notice, "Created domain " << name << " with url " << url << " from " << properties);
+ }
+ get(username, USERNAME, properties);
+ get(password, PASSWORD, properties);
+ get(mechanisms, SASL_MECHANISMS, properties);
+ get(service, SASL_SERVICE, properties);
+ get(minSsf, MIN_SSF, properties);
+ get(maxSsf, MAX_SSF, properties);
+ if (agent != 0) {
+ domain = _qmf::Domain::shared_ptr(new _qmf::Domain(agent, this, name, durable));
+ domain->set_url(url.str());
+ domain->set_username(username);
+ domain->set_password(password);
+ domain->set_mechanisms(mechanisms);
+ agent->addObject(domain);
+ }
+}
+
+Domain::~Domain()
+{
+ if (domain != 0) domain->resourceDestroy();
+}
+
+boost::shared_ptr<qpid::management::ManagementObject> Domain::GetManagementObject() const
+{
+ return domain;
+}
+
+const std::string& Domain::getMechanisms() const
+{
+ return mechanisms;
+}
+
+qpid::Url Domain::getUrl() const
+{
+ return url;
+}
+
+bool Domain::isDurable() const
+{
+ return durable;
+}
+
+std::auto_ptr<qpid::Sasl> Domain::sasl(const std::string& hostname)
+{
+ return qpid::SaslFactory::getInstance().create(username, password, service, hostname, minSsf, maxSsf, false);
+}
+
+void Domain::connect(bool incoming, const std::string& name, const qpid::types::Variant::Map& properties, BrokerContext& context)
+{
+ boost::shared_ptr<InterconnectFactory> factory(new InterconnectFactory(incoming, name, properties, shared_from_this(), context));
+ factory->connect();
+ addPending(factory);
+}
+
+void Domain::connect(bool incoming, const std::string& name, const std::string& source, const std::string& target, BrokerContext& context, boost::shared_ptr<Relay> relay)
+{
+ boost::shared_ptr<InterconnectFactory> factory(new InterconnectFactory(incoming, name, source, target, shared_from_this(), context, relay));
+ factory->connect();
+ addPending(factory);
+}
+
+void Domain::addPending(boost::shared_ptr<InterconnectFactory> f)
+{
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock);
+ pending.insert(f);
+}
+
+void Domain::removePending(boost::shared_ptr<InterconnectFactory> f)
+{
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock);
+ pending.erase(f);
+}
+
+
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/Domain.h b/qpid/cpp/src/qpid/broker/amqp/Domain.h
new file mode 100644
index 0000000000..7ee8572c8a
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Domain.h
@@ -0,0 +1,82 @@
+#ifndef QPID_BROKER_AMQP_DOMAIN_H
+#define QPID_BROKER_AMQP_DOMAIN_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/sys/ConnectionCodec.h"
+#include "qpid/types/Variant.h"
+#include "qpid/Url.h"
+#include "qpid/Version.h"
+#include "qpid/broker/PersistableObject.h"
+#include "qpid/management/Manageable.h"
+#include "qpid/sys/Mutex.h"
+#include "qmf/org/apache/qpid/broker/Domain.h"
+#include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include <memory>
+#include <set>
+
+namespace qpid {
+class Sasl;
+namespace management {
+class ManagementAgent;
+class ManagementObject;
+}
+namespace broker {
+class Broker;
+namespace amqp {
+class InterconnectFactory;
+class BrokerContext;
+class Relay;
+
+class Domain : public PersistableObject, public qpid::management::Manageable, public boost::enable_shared_from_this<Domain>
+{
+ public:
+ Domain(const std::string& name, const qpid::types::Variant::Map& properties, Broker&);
+ ~Domain();
+ void connect(bool incoming, const std::string& name, const qpid::types::Variant::Map& properties, BrokerContext&);
+ void connect(bool incoming, const std::string& name, const std::string& source, const std::string& target, BrokerContext&, boost::shared_ptr<Relay>);
+ std::auto_ptr<qpid::Sasl> sasl(const std::string& hostname);
+ const std::string& getMechanisms() const;
+ qpid::Url getUrl() const;
+ bool isDurable() const;
+ void addPending(boost::shared_ptr<InterconnectFactory>);
+ void removePending(boost::shared_ptr<InterconnectFactory>);
+ boost::shared_ptr<qpid::management::ManagementObject> GetManagementObject() const;
+ private:
+ std::string name;
+ bool durable;
+ Broker& broker;
+ qpid::Url url;
+ std::string username;
+ std::string password;
+ std::string mechanisms;
+ std::string service;
+ int minSsf;
+ int maxSsf;
+ boost::shared_ptr<qmf::org::apache::qpid::broker::Domain> domain;
+ qpid::management::ManagementAgent* agent;
+ std::set< boost::shared_ptr<InterconnectFactory> > pending;
+ qpid::sys::Mutex lock;
+};
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP_DOMAIN_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/Exception.cpp b/qpid/cpp/src/qpid/broker/amqp/Exception.cpp
new file mode 100644
index 0000000000..6b874aa272
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Exception.cpp
@@ -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.
+ *
+ */
+#include "Exception.h"
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+Exception::Exception(const std::string& n, const std::string& d) : name(n), description(d) {}
+Exception::~Exception() throw() {}
+const char* Exception::what() const throw() { return description.c_str(); }
+const char* Exception::symbol() const throw() { return name.c_str(); }
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/Exception.h b/qpid/cpp/src/qpid/broker/amqp/Exception.h
new file mode 100644
index 0000000000..a129dffe1f
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Exception.h
@@ -0,0 +1,46 @@
+#ifndef QPID_BROKER_AMQP_EXCEPTION_H
+#define QPID_BROKER_AMQP_EXCEPTION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <string>
+#include <qpid/Exception.h>
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+/**
+ * Exception to signal various AMQP 1.0 defined conditions
+ */
+class Exception : public qpid::Exception
+{
+ public:
+ Exception(const std::string& name, const std::string& description);
+ virtual ~Exception() throw();
+ const char* what() const throw();
+ const char* symbol() const throw();
+ private:
+ std::string name;
+ std::string description;
+};
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP_EXCEPTION_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/Filter.cpp b/qpid/cpp/src/qpid/broker/amqp/Filter.cpp
new file mode 100644
index 0000000000..b7b29e004d
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Filter.cpp
@@ -0,0 +1,402 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/amqp/Filter.h"
+#include "qpid/broker/amqp/Authorise.h"
+#include "qpid/broker/amqp/DataReader.h"
+#include "qpid/broker/amqp/Outgoing.h"
+#include "qpid/broker/DirectExchange.h"
+#include "qpid/broker/HeadersExchange.h"
+#include "qpid/broker/TopicExchange.h"
+#include "qpid/amqp/descriptors.h"
+#include "qpid/amqp_0_10/Codecs.h"
+#include "qpid/log/Statement.h"
+extern "C" {
+#include <proton/engine.h>
+}
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+namespace {
+const std::string XQUERY("xquery");
+const std::string XML("xml");
+const std::string DEFAULT_SUBJECT_FILTER("default-subject-filter");
+const std::string DEFAULT_HEADERS_FILTER("default-headers-filter");
+const std::string XMATCH("x-match");
+const std::string ALL("all");
+const std::string DEFAULT_XQUERY_FILTER("default-xquery-filter");
+const std::string DEFAULT_XQUERY_VALUE("true()");
+const std::string WILDCARD("#");
+}
+Filter::Filter() : inHeadersMap(false) {}
+
+void Filter::read(pn_data_t* data)
+{
+ try {
+ DataReader reader(*this);
+ reader.read(data);
+ } catch (const std::exception& e) {
+ QPID_LOG(warning, "Error parsing filter: " << e.what());
+ }
+}
+
+void Filter::write(pn_data_t* data)
+{
+ if (!active.empty()) {
+ pn_data_put_map(data);
+ pn_data_enter(data);
+ for (std::vector<FilterBase*>::const_iterator i = active.begin(); i != active.end(); ++i) {
+ (*i)->write(data);
+ }
+ pn_data_exit(data);
+ }
+}
+
+void Filter::onStringValue(const qpid::amqp::CharSequence& key, const qpid::amqp::CharSequence& value, const qpid::amqp::Descriptor* descriptor)
+{
+ if (inHeadersMap) {
+ headersFilter.value[std::string(key.data, key.size)] = std::string(value.data, value.size);
+ } else {
+ StringFilter filter;
+ filter.key = std::string(key.data, key.size);
+ filter.value = std::string(value.data, value.size);
+ if (descriptor) {
+ filter.described = true;
+ filter.descriptor = *descriptor;
+ if (descriptor->match(qpid::amqp::filters::LEGACY_TOPIC_FILTER_SYMBOL, qpid::amqp::filters::LEGACY_TOPIC_FILTER_CODE)
+ || descriptor->match(qpid::amqp::filters::LEGACY_DIRECT_FILTER_SYMBOL, qpid::amqp::filters::LEGACY_DIRECT_FILTER_CODE)) {
+ setFilter(subjectFilter, filter);
+ } else if (descriptor->match(qpid::amqp::filters::SELECTOR_FILTER_SYMBOL, qpid::amqp::filters::SELECTOR_FILTER_CODE)) {
+ setFilter(selectorFilter, filter);
+ } else if (descriptor->match(qpid::amqp::filters::XQUERY_FILTER_SYMBOL, qpid::amqp::filters::XQUERY_FILTER_CODE)) {
+ setFilter(xqueryFilter, filter);
+ } else {
+ QPID_LOG(notice, "Skipping unrecognised string filter with key " << filter.key << " and descriptor " << filter.descriptor);
+ }
+ } else {
+ setFilter(subjectFilter, filter);
+ }
+ }
+}
+void Filter::onNullValue(const qpid::amqp::CharSequence& key, const qpid::amqp::Descriptor*)
+{
+ headersFilter.value[std::string(key.data, key.size)] = qpid::types::Variant();
+}
+void Filter::onBooleanValue(const qpid::amqp::CharSequence& key, bool value, const qpid::amqp::Descriptor*)
+{
+ headersFilter.value[std::string(key.data, key.size)] = value;
+}
+
+void Filter::onUByteValue(const qpid::amqp::CharSequence& key, uint8_t value, const qpid::amqp::Descriptor*)
+{
+ headersFilter.value[std::string(key.data, key.size)] = value;
+}
+
+void Filter::onUShortValue(const qpid::amqp::CharSequence& key, uint16_t value, const qpid::amqp::Descriptor*)
+{
+ headersFilter.value[std::string(key.data, key.size)] = value;
+}
+
+void Filter::onUIntValue(const qpid::amqp::CharSequence& key, uint32_t value, const qpid::amqp::Descriptor*)
+{
+ headersFilter.value[std::string(key.data, key.size)] = value;
+}
+
+void Filter::onULongValue(const qpid::amqp::CharSequence& key, uint64_t value, const qpid::amqp::Descriptor*)
+{
+ headersFilter.value[std::string(key.data, key.size)] = value;
+}
+
+void Filter::onByteValue(const qpid::amqp::CharSequence& key, int8_t value, const qpid::amqp::Descriptor*)
+{
+ headersFilter.value[std::string(key.data, key.size)] = value;
+}
+
+void Filter::onShortValue(const qpid::amqp::CharSequence& key, int16_t value, const qpid::amqp::Descriptor*)
+{
+ headersFilter.value[std::string(key.data, key.size)] = value;
+}
+
+void Filter::onIntValue(const qpid::amqp::CharSequence& key, int32_t value, const qpid::amqp::Descriptor*)
+{
+ headersFilter.value[std::string(key.data, key.size)] = value;
+}
+
+void Filter::onLongValue(const qpid::amqp::CharSequence& key, int64_t value, const qpid::amqp::Descriptor*)
+{
+ headersFilter.value[std::string(key.data, key.size)] = value;
+}
+
+void Filter::onFloatValue(const qpid::amqp::CharSequence& key, float value, const qpid::amqp::Descriptor*)
+{
+ headersFilter.value[std::string(key.data, key.size)] = value;
+}
+
+void Filter::onDoubleValue(const qpid::amqp::CharSequence& key, double value, const qpid::amqp::Descriptor*)
+{
+ headersFilter.value[std::string(key.data, key.size)] = value;
+}
+
+bool Filter::onStartMapValue(const qpid::amqp::CharSequence& key, uint32_t /*count*/, const qpid::amqp::Descriptor* descriptor)
+{
+ if (inHeadersMap) {
+ QPID_LOG(warning, "Skipping illegal nested map data in headers filter");
+ return false;
+ } else if (descriptor && descriptor->match(qpid::amqp::filters::LEGACY_HEADERS_FILTER_SYMBOL, qpid::amqp::filters::LEGACY_HEADERS_FILTER_CODE)) {
+ inHeadersMap = true;
+ setAllowedKeyType(STRING_KEY);
+ headersFilter.requested = true;
+ headersFilter.described = true;
+ headersFilter.descriptor = *descriptor;
+ headersFilter.key = std::string(key.data, key.size);
+ return true;
+ } else {
+ if (descriptor) {
+ QPID_LOG(info, "Skipping unrecognised map data in filter: " << *descriptor);
+ } else {
+ QPID_LOG(info, "Skipping undescribed map data in filter");
+ }
+ return false;
+ }
+}
+void Filter::onEndMapValue(const qpid::amqp::CharSequence&, uint32_t, const qpid::amqp::Descriptor*)
+{
+ if (inHeadersMap) {
+ inHeadersMap = false;
+ setAllowedKeyType(SYMBOL_KEY);
+ }
+}
+
+bool Filter::hasSubjectFilter() const
+{
+ return !subjectFilter.value.empty();
+}
+
+std::string Filter::getSubjectFilter() const
+{
+ return subjectFilter.value;
+}
+
+
+bool Filter::hasSelectorFilter() const
+{
+ return !selectorFilter.value.empty();
+}
+
+std::string Filter::getSelectorFilter() const
+{
+ return selectorFilter.value;
+}
+
+void Filter::setFilter(StringFilter& lhs, const StringFilter& rhs)
+{
+ if (!lhs.value.empty()) {
+ QPID_LOG(notice, "Skipping filter with key " << rhs.key << "; value provided for " << lhs.key << " already");
+ } else {
+ lhs = rhs;
+ lhs.requested = true;
+ }
+}
+
+void Filter::apply(boost::shared_ptr<Outgoing> queue)
+{
+ if (hasSubjectFilter()) {
+ queue->setSubjectFilter(getSubjectFilter());
+ active.push_back(&subjectFilter);
+ }
+ if (hasSelectorFilter()) {
+ queue->setSelectorFilter(getSelectorFilter());
+ active.push_back(&selectorFilter);
+ }
+}
+
+void Filter::configure(QueueSettings& settings)
+{
+ if (hasSelectorFilter()) {
+ settings.filter = getSelectorFilter();
+ active.push_back(&selectorFilter);
+ }
+}
+
+std::string Filter::getBindingKey(boost::shared_ptr<Exchange> exchange) const
+{
+ if (subjectFilter.value.empty() && exchange->getType() == TopicExchange::typeName) {
+ return WILDCARD;
+ } else {
+ return subjectFilter.value;
+ }
+}
+
+void Filter::bind(boost::shared_ptr<Exchange> exchange, boost::shared_ptr<Queue> queue)
+{
+ qpid::framing::FieldTable bindingArgs;
+ if (exchange->getType() == TopicExchange::typeName) {
+ setDefaultSubjectFilter(true);
+ active.push_back(&subjectFilter);
+ } else if (exchange->getType() == DirectExchange::typeName) {
+ if (!setDefaultSubjectFilter() && adjustDirectFilter()) {
+ QPID_LOG(info, "Using legacy topic filter as a direct matching filter for " << exchange->getName());
+ }
+ active.push_back(&subjectFilter);
+ } else if (exchange->getType() == HeadersExchange::typeName) {
+ setDefaultHeadersFilter();
+ qpid::amqp_0_10::translate(headersFilter.value, bindingArgs);
+ active.push_back(&headersFilter);
+ } else if (exchange->getType() == XML) {
+ setDefaultXQueryFilter();
+ if (!setDefaultSubjectFilter() && adjustDirectFilter()) {
+ QPID_LOG(info, "Using legacy topic filter as a direct matching filter for " << exchange->getName());
+ }
+ bindingArgs.setString(XQUERY, xqueryFilter.value);
+ active.push_back(&subjectFilter);
+ active.push_back(&xqueryFilter);
+ }
+ queue->bind(exchange, subjectFilter.value, bindingArgs);
+}
+
+void Filter::setDefaultXQueryFilter()
+{
+ if (!xqueryFilter.requested) {
+ xqueryFilter.key = DEFAULT_XQUERY_FILTER;
+ xqueryFilter.value = DEFAULT_XQUERY_VALUE;
+ xqueryFilter.setDescriptor(qpid::amqp::Descriptor(qpid::amqp::filters::XQUERY_FILTER_CODE));
+ }
+}
+void Filter::setDefaultHeadersFilter()
+{
+ if (!headersFilter.requested) {
+ headersFilter.key = DEFAULT_HEADERS_FILTER;
+ headersFilter.value[XMATCH] = ALL;
+ headersFilter.setDescriptor(qpid::amqp::Descriptor(qpid::amqp::filters::LEGACY_HEADERS_FILTER_CODE));
+ }
+}
+
+bool Filter::setDefaultSubjectFilter(const qpid::amqp::Descriptor& d, const std::string& value)
+{
+ if (!subjectFilter.requested) {
+ subjectFilter.key = DEFAULT_SUBJECT_FILTER;
+ subjectFilter.value = value;
+ subjectFilter.setDescriptor(d);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool Filter::setDefaultSubjectFilter(bool wildcards)
+{
+ if (wildcards) {
+ return setDefaultSubjectFilter(qpid::amqp::Descriptor(qpid::amqp::filters::LEGACY_TOPIC_FILTER_CODE), WILDCARD);
+ } else {
+ return setDefaultSubjectFilter(qpid::amqp::Descriptor(qpid::amqp::filters::LEGACY_DIRECT_FILTER_CODE));
+ }
+}
+
+Filter::FilterBase::FilterBase() : described(false), requested(false), descriptor(0) {}
+Filter::FilterBase::~FilterBase() {}
+void Filter::FilterBase::setDescriptor(const qpid::amqp::Descriptor& d)
+{
+ described = true;
+ descriptor = d;
+}
+namespace {
+pn_bytes_t convert(const std::string& in)
+{
+ pn_bytes_t out;
+ out.start = const_cast<char*>(in.data());
+ out.size = in.size();
+ return out;
+}
+pn_bytes_t convert(const qpid::amqp::CharSequence& in)
+{
+ pn_bytes_t out;
+ out.start = const_cast<char*>(in.data);
+ out.size = in.size;
+ return out;
+}
+qpid::amqp::Descriptor symbolicDescriptor(const std::string& symbol)
+{
+ qpid::amqp::CharSequence cs;
+ cs.data = symbol.data();
+ cs.size = symbol.size();
+ return qpid::amqp::Descriptor(cs);
+}
+}
+
+bool Filter::adjustDirectFilter()
+{
+ if (subjectFilter.descriptor.match(qpid::amqp::filters::LEGACY_TOPIC_FILTER_SYMBOL, qpid::amqp::filters::LEGACY_TOPIC_FILTER_CODE)) {
+ if (subjectFilter.descriptor.type == qpid::amqp::Descriptor::NUMERIC) {
+ subjectFilter.descriptor = qpid::amqp::Descriptor(qpid::amqp::filters::LEGACY_DIRECT_FILTER_CODE);
+ } else {
+ subjectFilter.descriptor = symbolicDescriptor(qpid::amqp::filters::LEGACY_DIRECT_FILTER_SYMBOL);
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void Filter::FilterBase::write(pn_data_t* data)
+{
+ pn_data_put_symbol(data, convert(key));
+ if (described) {
+ pn_data_put_described(data);
+ pn_data_enter(data);
+ switch (descriptor.type) {
+ case qpid::amqp::Descriptor::NUMERIC:
+ pn_data_put_ulong(data, descriptor.value.code);
+ break;
+ case qpid::amqp::Descriptor::SYMBOLIC:
+ pn_data_put_symbol(data, convert(descriptor.value.symbol));
+ break;
+ }
+ writeValue(data);
+ pn_data_exit(data);
+ } else {
+ writeValue(data);
+ }
+}
+void Filter::StringFilter::writeValue(pn_data_t* data)
+{
+ pn_data_put_string(data, convert(value));
+}
+
+void Filter::MapFilter::writeValue(pn_data_t* data)
+{
+ pn_data_put_map(data);
+ pn_data_enter(data);
+ for (ValueType::const_iterator i = value.begin(); i != value.end(); ++i) {
+ pn_data_put_string(data, convert(i->first));
+ pn_data_put_string(data, convert(i->second));//TODO: other types?
+ }
+ pn_data_exit(data);
+}
+
+void Filter::write(std::map<std::string, qpid::types::Variant> source, pn_data_t* target)
+{
+ MapFilter dummy;
+ dummy.value = source;
+ dummy.writeValue(target);
+}
+
+
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/Filter.h b/qpid/cpp/src/qpid/broker/amqp/Filter.h
new file mode 100644
index 0000000000..c246fb9ede
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Filter.h
@@ -0,0 +1,126 @@
+#ifndef QPID_BROKER_AMQP_FILTER_H
+#define QPID_BROKER_AMQP_FILTER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/amqp/MapReader.h"
+#include "qpid/amqp/Descriptor.h"
+#include "qpid/types/Variant.h"
+#include <map>
+#include <vector>
+#include <boost/shared_ptr.hpp>
+
+struct pn_data_t;
+namespace qpid {
+namespace broker {
+class Exchange;
+class Queue;
+struct QueueSettings;
+namespace amqp {
+class Outgoing;
+
+class Filter : private qpid::amqp::MapReader
+{
+ public:
+ Filter();
+ void read(pn_data_t*);
+ void write(pn_data_t*);
+ std::string getBindingKey(boost::shared_ptr<Exchange> exchange) const;
+
+ /**
+ * Apply filters where source is a queue
+ */
+ void apply(boost::shared_ptr<Outgoing> queue);
+
+ /**
+ * Configure subscription queue for case where source is an exchange
+ */
+ void configure(QueueSettings&);
+ /**
+ * Bind subscription queue for case where source is an exchange
+ */
+ void bind(boost::shared_ptr<Exchange> exchange, boost::shared_ptr<Queue> queue);
+
+ /**
+ * Not really the ideal place for this, but the logic is already implemented here...
+ */
+ static void write(std::map<std::string, qpid::types::Variant> source, pn_data_t* target);
+ private:
+ struct FilterBase
+ {
+ bool described;
+ bool requested;
+ qpid::amqp::Descriptor descriptor;
+ std::string key;
+ FilterBase();
+ virtual ~FilterBase();
+ void write(pn_data_t*);
+ virtual void writeValue(pn_data_t*) = 0;
+ void setDescriptor(const qpid::amqp::Descriptor&);
+ };
+ struct StringFilter : FilterBase
+ {
+ std::string value;
+ void writeValue(pn_data_t*);
+ };
+ struct MapFilter : FilterBase
+ {
+ typedef std::map<std::string, qpid::types::Variant> ValueType;
+ ValueType value;
+ void writeValue(pn_data_t*);
+ };
+
+ void onStringValue(const qpid::amqp::CharSequence& key, const qpid::amqp::CharSequence& value, const qpid::amqp::Descriptor* descriptor);
+ void onNullValue(const qpid::amqp::CharSequence&, const qpid::amqp::Descriptor*);
+ void onBooleanValue(const qpid::amqp::CharSequence&, bool, const qpid::amqp::Descriptor*);
+ void onUByteValue(const qpid::amqp::CharSequence&, uint8_t, const qpid::amqp::Descriptor*);
+ void onUShortValue(const qpid::amqp::CharSequence&, uint16_t, const qpid::amqp::Descriptor*);
+ void onUIntValue(const qpid::amqp::CharSequence&, uint32_t, const qpid::amqp::Descriptor*);
+ void onULongValue(const qpid::amqp::CharSequence&, uint64_t, const qpid::amqp::Descriptor*);
+ void onByteValue(const qpid::amqp::CharSequence&, int8_t, const qpid::amqp::Descriptor*);
+ void onShortValue(const qpid::amqp::CharSequence&, int16_t, const qpid::amqp::Descriptor*);
+ void onIntValue(const qpid::amqp::CharSequence&, int32_t, const qpid::amqp::Descriptor*);
+ void onLongValue(const qpid::amqp::CharSequence&, int64_t, const qpid::amqp::Descriptor*);
+ void onFloatValue(const qpid::amqp::CharSequence&, float, const qpid::amqp::Descriptor*);
+ void onDoubleValue(const qpid::amqp::CharSequence&, double, const qpid::amqp::Descriptor*);
+ bool onStartMapValue(const qpid::amqp::CharSequence& key, uint32_t count, const qpid::amqp::Descriptor* descriptor);
+ void onEndMapValue(const qpid::amqp::CharSequence& key, uint32_t count, const qpid::amqp::Descriptor* descriptor);
+ void setFilter(StringFilter&, const StringFilter&);
+ bool hasSubjectFilter() const;
+ std::string getSubjectFilter() const;
+ bool hasSelectorFilter() const;
+ std::string getSelectorFilter() const;
+ bool setDefaultSubjectFilter(bool wildcards=false);
+ bool setDefaultSubjectFilter(const qpid::amqp::Descriptor& descriptor, const std::string& value=std::string());
+ bool adjustDirectFilter();
+ void setDefaultHeadersFilter();
+ void setDefaultXQueryFilter();
+
+ StringFilter subjectFilter;
+ StringFilter selectorFilter;
+ StringFilter xqueryFilter;
+ MapFilter headersFilter;
+ std::vector<FilterBase*> active;
+ bool inHeadersMap;
+};
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP_FILTER_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/Header.cpp b/qpid/cpp/src/qpid/broker/amqp/Header.cpp
new file mode 100644
index 0000000000..e1b3d22553
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Header.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/amqp/Header.h"
+#include "qpid/broker/Message.h"
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+
+bool Header::isDurable() const
+{
+ return message.isPersistent();
+}
+
+uint8_t Header::getPriority() const
+{
+ return message.getPriority();
+}
+
+bool Header::hasTtl() const
+{
+ uint64_t dummy(0);
+ return message.getTtl(dummy);
+}
+
+uint32_t Header::getTtl() const
+{
+ uint64_t ttl(0);
+ message.getTtl(ttl);
+ if (ttl > std::numeric_limits<uint32_t>::max()) return std::numeric_limits<uint32_t>::max();
+ else return (uint32_t) ttl;
+}
+
+bool Header::isFirstAcquirer() const
+{
+ return (!message.hasBeenAcquired());
+}
+
+uint32_t Header::getDeliveryCount() const
+{
+ return message.isRedelivered() ? message.getDeliveryCount() : 0;
+}
+
+Header::Header(const qpid::broker::Message& m) : message(m) {}
+
+
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/Header.h b/qpid/cpp/src/qpid/broker/amqp/Header.h
new file mode 100644
index 0000000000..6e4f763028
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Header.h
@@ -0,0 +1,50 @@
+#ifndef QPID_BROKER_AMQP_HEADER_H
+#define QPID_BROKER_AMQP_HEADER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/amqp/MessageEncoder.h"
+
+namespace qpid {
+namespace broker {
+class Message;
+namespace amqp {
+
+/**
+ * Adapts the broker current message abstraction to provide that
+ * required by the AMQP 1.0 message encoder.
+ */
+class Header : public qpid::amqp::MessageEncoder::Header
+{
+ public:
+ Header(const qpid::broker::Message&);
+ bool isDurable() const;
+ uint8_t getPriority() const;
+ bool hasTtl() const;
+ uint32_t getTtl() const;
+ bool isFirstAcquirer() const;
+ uint32_t getDeliveryCount() const;
+ private:
+ const qpid::broker::Message& message;
+};
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP_HEADER_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/Incoming.cpp b/qpid/cpp/src/qpid/broker/amqp/Incoming.cpp
new file mode 100644
index 0000000000..3986818846
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Incoming.cpp
@@ -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.
+ *
+ */
+#include "Incoming.h"
+#include "Exception.h"
+#include "ManagedConnection.h"
+#include "Message.h"
+#include "Session.h"
+#include "qpid/amqp/descriptors.h"
+#include "qpid/broker/AsyncCompletion.h"
+#include "qpid/broker/Message.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+Incoming::Incoming(pn_link_t* l, Broker& broker, Session& parent, const std::string& source, const std::string& target, const std::string& name)
+ : ManagedIncomingLink(broker, parent, source, target, name), credit(500), window(0), link(l), session(parent) {}
+
+
+Incoming::~Incoming() {}
+bool Incoming::doWork()
+{
+ uint32_t c = getCredit();
+ bool issue = window < c;
+ if (issue) {
+ pn_link_flow(link, c - window);
+ window = c;
+ }
+ return issue;
+}
+bool Incoming::haveWork()
+{
+ return window <= (getCredit()/2);
+}
+
+uint32_t Incoming::getCredit()
+{
+ return credit;//TODO: proper flow control
+}
+
+void Incoming::detached(bool /*closed*/)
+{
+}
+
+void Incoming::wakeup()
+{
+ session.wakeup();
+}
+
+void Incoming::verify(const std::string& u, const std::string& r)
+{
+ userid.init(u, r);
+}
+
+Incoming::UserId::UserId() : inDefaultRealm(false) {}
+void Incoming::UserId::init(const std::string& u, const std::string& defaultRealm)
+{
+ userid = u;
+ size_t at = userid.find('@');
+ if (at != std::string::npos) {
+ unqualified = userid.substr(0, at);
+ inDefaultRealm = defaultRealm == userid.substr(at+1);
+ }
+}
+void Incoming::UserId::verify(const std::string& claimed)
+{
+ if(!userid.empty() && !claimed.empty() && userid != claimed && !(inDefaultRealm && claimed == unqualified)) {
+ throw Exception(qpid::amqp::error_conditions::NOT_ALLOWED, QPID_MSG("Authenticated user id is " << userid << " but user id in message declared as " << claimed));
+ }
+}
+
+
+namespace {
+ class Transfer : public qpid::broker::AsyncCompletion::Callback
+ {
+ public:
+ Transfer(pn_delivery_t* d, boost::shared_ptr<Session> s) : delivery(d), session(s) {}
+ void completed(bool sync) { session->accepted(delivery, sync); }
+ boost::intrusive_ptr<qpid::broker::AsyncCompletion::Callback> clone()
+ {
+ boost::intrusive_ptr<qpid::broker::AsyncCompletion::Callback> copy(new Transfer(delivery, session));
+ return copy;
+ }
+
+ private:
+ pn_delivery_t* delivery;
+ boost::shared_ptr<Session> session;
+ };
+}
+
+DecodingIncoming::DecodingIncoming(pn_link_t* link, Broker& broker, Session& parent, const std::string& source, const std::string& target, const std::string& name)
+ : Incoming(link, broker, parent, source, target, name), sessionPtr(parent.shared_from_this()) {}
+DecodingIncoming::~DecodingIncoming() {}
+
+void DecodingIncoming::readable(pn_delivery_t* delivery)
+{
+ size_t pending = pn_delivery_pending(delivery);
+ size_t offset = partial ? partial->getSize() : 0;
+ boost::intrusive_ptr<Message> received(new Message(offset + pending));
+ if (partial) {
+ ::memcpy(received->getData(), partial->getData(), offset);
+ partial = boost::intrusive_ptr<Message>();
+ }
+ assert(received->getSize() == pending + offset);
+ pn_link_recv(link, received->getData() + offset, pending);
+
+ if (pn_delivery_partial(delivery)) {
+ QPID_LOG(debug, "Message incomplete: received " << pending << " bytes, now have " << received->getSize());
+ partial = received;
+ } else {
+ incomingMessageReceived();
+ if (offset) {
+ QPID_LOG(debug, "Message complete: received " << pending << " bytes, " << received->getSize() << " in total");
+ } else {
+ QPID_LOG(debug, "Message received: " << received->getSize() << " bytes");
+ }
+
+ received->scan();
+ pn_link_advance(link);
+ received->setPublisher(&session.getParent());
+ received->computeExpiration();
+ --window;
+ deliver(received, delivery);
+ }
+}
+
+void DecodingIncoming::deliver(boost::intrusive_ptr<qpid::broker::amqp::Message> received, pn_delivery_t* delivery)
+{
+ qpid::broker::Message message(received, received);
+ userid.verify(message.getUserId());
+ received->begin();
+ handle(message, session.getTransaction(delivery));
+ Transfer t(delivery, sessionPtr);
+ received->end(t);
+}
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/Incoming.h b/qpid/cpp/src/qpid/broker/amqp/Incoming.h
new file mode 100644
index 0000000000..ccf999a256
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Incoming.h
@@ -0,0 +1,87 @@
+#ifndef QPID_BROKER_AMQP_INCOMING_H
+#define QPID_BROKER_AMQP_INCOMING_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "Message.h"
+#include "ManagedIncomingLink.h"
+extern "C" {
+#include <proton/engine.h>
+}
+#include <boost/intrusive_ptr.hpp>
+namespace qpid {
+namespace broker {
+class Broker;
+class Message;
+class TxBuffer;
+namespace amqp {
+class Session;
+
+class Incoming : public ManagedIncomingLink
+{
+ public:
+ Incoming(pn_link_t*, Broker& broker, Session& parent, const std::string& source, const std::string& target, const std::string& name);
+ virtual ~Incoming();
+ virtual bool doWork();//do anything that requires output
+ virtual bool haveWork();//called when handling input to see whether any output work is needed
+ virtual void detached(bool closed);
+ virtual void readable(pn_delivery_t* delivery) = 0;
+ void verify(const std::string& userid, const std::string& defaultRealm);
+ void wakeup();
+ protected:
+ class UserId
+ {
+ public:
+ UserId();
+ void init(const std::string& userid, const std::string& defaultRealm);
+ void verify(const std::string& claimed);
+ private:
+ std::string userid;
+ bool inDefaultRealm;
+ std::string unqualified;
+ };
+
+ const uint32_t credit;
+ uint32_t window;
+
+
+ pn_link_t* link;
+ Session& session;
+ UserId userid;
+ virtual uint32_t getCredit();
+};
+
+class DecodingIncoming : public Incoming
+{
+ public:
+ DecodingIncoming(pn_link_t*, Broker& broker, Session& parent, const std::string& source, const std::string& target, const std::string& name);
+ virtual ~DecodingIncoming();
+ void readable(pn_delivery_t* delivery);
+ virtual void deliver(boost::intrusive_ptr<qpid::broker::amqp::Message> received, pn_delivery_t* delivery);
+ virtual void handle(qpid::broker::Message&, qpid::broker::TxBuffer*) = 0;
+ private:
+ boost::shared_ptr<Session> sessionPtr;
+ boost::intrusive_ptr<Message> partial;
+};
+
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP_INCOMING_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/Interconnect.cpp b/qpid/cpp/src/qpid/broker/amqp/Interconnect.cpp
new file mode 100644
index 0000000000..41d22d39cc
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Interconnect.cpp
@@ -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.
+ *
+ */
+#include "Interconnect.h"
+#include "Interconnects.h"
+#include "Connection.h"
+#include "SaslClient.h"
+#include "Session.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/Exception.h"
+#include "qpid/SaslFactory.h"
+#include "qpid/sys/ConnectionCodec.h"
+#include "qpid/sys/OutputControl.h"
+#include "qpid/sys/SystemInfo.h"
+#include "qpid/log/Statement.h"
+#include <boost/shared_ptr.hpp>
+extern "C" {
+#include <proton/engine.h>
+#include <proton/error.h>
+}
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+
+Interconnect::Interconnect(qpid::sys::OutputControl& out, const std::string& id, BrokerContext& broker, bool saslInUse,
+ bool i, const std::string& n, const std::string& s, const std::string& t, const std::string& d)
+ : Connection(out, id, broker, true, true), incoming(i), name(n), source(s), target(t), domain(d), headerDiscarded(saslInUse),
+ isOpened(false), closeRequested(false), isTransportDeleted(false)
+{}
+
+Interconnect::~Interconnect()
+{
+ QPID_LOG(notice, "Interconnect deleted");
+}
+
+namespace {
+const pn_state_t UNINIT = PN_LOCAL_UNINIT | PN_REMOTE_UNINIT;
+const size_t PROTOCOL_HEADER_LENGTH(8);
+}
+
+size_t Interconnect::encode(char* buffer, size_t size)
+{
+ if (headerDiscarded) {
+ return Connection::encode(buffer, size);
+ } else {
+ //The IO 'layer' will write in a protocol header when an
+ //'outgoing' connection is established. However the proton
+ //protocol engine will also emit one. One needs to be
+ //discarded, here we discard the one the engine emits for
+ //interconnects.
+ headerDiscarded = true;
+ size_t encoded = Connection::encode(buffer, size);
+ assert(encoded >= PROTOCOL_HEADER_LENGTH);//we never encode part of protocol header
+ //discard first eight bytes
+ ::memmove(buffer, buffer + PROTOCOL_HEADER_LENGTH, encoded - PROTOCOL_HEADER_LENGTH);
+ return encoded - PROTOCOL_HEADER_LENGTH;
+ }
+}
+
+namespace {
+const std::string CLIENT_PROCESS_NAME("qpid.client_process");
+const std::string CLIENT_PID("qpid.client_pid");
+pn_bytes_t convert(const std::string& s)
+{
+ pn_bytes_t result;
+ result.start = const_cast<char*>(s.data());
+ result.size = s.size();
+ return result;
+}
+void setProperties(pn_connection_t* connection)
+{
+ pn_data_t* data = pn_connection_properties(connection);
+ pn_data_put_map(data);
+ pn_data_enter(data);
+
+ pn_data_put_symbol(data, convert(CLIENT_PROCESS_NAME));
+ std::string processName = sys::SystemInfo::getProcessName();
+ pn_data_put_string(data, convert(processName));
+
+ pn_data_put_symbol(data, convert(CLIENT_PID));
+ pn_data_put_int(data, sys::SystemInfo::getProcessId());
+ pn_data_exit(data);
+}
+}
+
+void Interconnect::process()
+{
+ QPID_LOG(trace, id << " processing interconnect");
+ if (closeRequested) {
+ close();
+ } else {
+ if ((pn_connection_state(connection) & UNINIT) == UNINIT) {
+ QPID_LOG_CAT(debug, model, id << " interconnect open initiated");
+ pn_connection_set_container(connection, getBroker().getFederationTag().c_str());
+ setProperties(connection);
+ pn_connection_open(connection);
+ out.connectionEstablished();
+ setInterconnectDomain(domain);
+ }
+ if (!isOpened && (pn_connection_state(connection) & PN_REMOTE_ACTIVE)) {
+ QPID_LOG_CAT(debug, model, id << " interconnect open completed, attaching link");
+ isOpened = true;
+ readPeerProperties();
+ const char* containerid(pn_connection_remote_container(connection));
+ if (containerid) {
+ setContainerId(std::string(containerid));
+ }
+ opened();
+ getBroker().getConnectionObservers().opened(*this);
+ pn_session_t* s = pn_session(connection);
+ pn_session_open(s);
+ boost::shared_ptr<Session> ssn(new Session(s, *this, out));
+ sessions[s] = ssn;
+
+ pn_link_t* l = incoming ? pn_receiver(s, name.c_str()) : pn_sender(s, name.c_str());
+ pn_link_open(l);
+ ssn->attach(l, source, target, relay);
+ }
+ Connection::process();
+ }
+}
+
+void Interconnect::setRelay(boost::shared_ptr<Relay> r)
+{
+ relay = r;
+}
+
+void Interconnect::deletedFromRegistry()
+{
+ closeRequested = true;
+ if (!isTransportDeleted) out.activateOutput();
+}
+
+void Interconnect::transportDeleted()
+{
+ isTransportDeleted = true;
+ getInterconnects().remove(name);
+}
+
+bool Interconnect::isLink() const
+{
+ return true;
+}
+
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/Interconnect.h b/qpid/cpp/src/qpid/broker/amqp/Interconnect.h
new file mode 100644
index 0000000000..d8eb457be2
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Interconnect.h
@@ -0,0 +1,65 @@
+#ifndef QPID_BROKER_AMQP_INTERCONNECT_H
+#define QPID_BROKER_AMQP_INTERCONNECT_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "Connection.h"
+
+namespace qpid {
+struct Address;
+namespace broker {
+namespace amqp {
+class Domain;
+class Interconnects;
+class Relay;
+
+/**
+ *
+ */
+class Interconnect : public Connection
+{
+ public:
+ Interconnect(qpid::sys::OutputControl& out, const std::string& id, BrokerContext& broker, bool saslInUse,
+ bool incoming, const std::string& name,
+ const std::string& source, const std::string& target, const std::string& domain);
+ void setRelay(boost::shared_ptr<Relay>);
+ ~Interconnect();
+ size_t encode(char* buffer, size_t size);
+ void deletedFromRegistry();
+ void transportDeleted();
+ bool isLink() const;
+ private:
+ bool incoming;
+ std::string name;
+ std::string source;
+ std::string target;
+ std::string domain;
+ bool headerDiscarded;
+ boost::shared_ptr<Relay> relay;
+ bool isOpened;
+ bool closeRequested;
+ bool isTransportDeleted;
+
+ void process();
+};
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP_INTERCONNECT_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/Interconnects.cpp b/qpid/cpp/src/qpid/broker/amqp/Interconnects.cpp
new file mode 100644
index 0000000000..912b244bf9
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Interconnects.cpp
@@ -0,0 +1,176 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "Interconnects.h"
+#include "Interconnect.h"
+#include "Connection.h"
+#include "Domain.h"
+#include "SaslClient.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/Exception.h"
+#include "qpid/SaslFactory.h"
+#include "qpid/sys/ConnectionCodec.h"
+#include "qpid/sys/OutputControl.h"
+#include "qpid/log/Statement.h"
+#include <boost/shared_ptr.hpp>
+#include <assert.h>
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+
+namespace {
+const std::string INCOMING_TYPE("incoming");
+const std::string OUTGOING_TYPE("outgoing");
+const std::string DOMAIN_TYPE("domain");
+}
+
+bool Interconnects::createObject(Broker& broker, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties,
+ const std::string& /*userId*/, const std::string& /*connectionId*/)
+{
+ if (type == DOMAIN_TYPE) {
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock);
+ DomainMap::iterator i = domains.find(name);
+ if (i == domains.end()) {
+ boost::shared_ptr<Domain> domain(new Domain(name, properties, broker));
+ domains[name] = domain;
+ if (domain->isDurable()) broker.getStore().create(*domain);
+ return true;
+ } else {
+ throw qpid::Exception(QPID_MSG("A domain named " << name << " already exists"));
+ }
+ } else if (type == INCOMING_TYPE || type == OUTGOING_TYPE) {
+ QPID_LOG(notice, "Creating interconnect " << name << ", " << properties);
+ boost::shared_ptr<Domain> domain;
+ {
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock);
+ qpid::types::Variant::Map::const_iterator p = properties.find(DOMAIN_TYPE);
+ if (p != properties.end()) {
+ std::string domainName = p->second;
+ DomainMap::iterator i = domains.find(domainName);
+ if (i != domains.end()) {
+ domain = i->second;
+ } else {
+ throw qpid::Exception(QPID_MSG("No such domain: " << domainName));
+ }
+ } else {
+ throw qpid::Exception(QPID_MSG("Domain must be specified"));
+ }
+ }
+ domain->connect(type == INCOMING_TYPE, name, properties, *context);
+ return true;
+ } else {
+ return false;
+ }
+}
+bool Interconnects::deleteObject(Broker& broker, const std::string& type, const std::string& name, const qpid::types::Variant::Map& /*properties*/,
+ const std::string& /*userId*/, const std::string& /*connectionId*/)
+{
+ if (type == DOMAIN_TYPE) {
+ boost::shared_ptr<Domain> domain;
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock);
+ DomainMap::iterator i = domains.find(name);
+ if (i != domains.end()) {
+ domain = i->second;
+ domains.erase(i);
+ if (domain->isDurable()) broker.getStore().destroy(*domain);
+ return true;
+ } else {
+ throw qpid::Exception(QPID_MSG("No such domain: " << name));
+ }
+ } else if (type == INCOMING_TYPE || type == OUTGOING_TYPE) {
+ boost::shared_ptr<Interconnect> interconnect;
+ {
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock);
+ InterconnectMap::iterator i = interconnects.find(name);
+ if (i != interconnects.end()) {
+ interconnect = i->second;
+ interconnects.erase(i);
+ } else {
+ throw qpid::Exception(QPID_MSG("No such interconnection: " << name));
+ }
+ }
+ if (interconnect) interconnect->deletedFromRegistry();
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool Interconnects::recoverObject(Broker& broker, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties,
+ uint64_t persistenceId)
+{
+ if (type == DOMAIN_TYPE) {
+ boost::shared_ptr<Domain> domain(new Domain(name, properties, broker));
+ domain->setPersistenceId(persistenceId);
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock);
+ domains[name] = domain;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+bool Interconnects::add(const std::string& name, boost::shared_ptr<Interconnect> connection)
+{
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock);
+ InterconnectMap::iterator i = interconnects.find(name);
+ if (i == interconnects.end()) {
+ interconnects[name] = connection;
+ return true;
+ } else return false;
+}
+boost::shared_ptr<Interconnect> Interconnects::get(const std::string& name)
+{
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock);
+ InterconnectMap::const_iterator i = interconnects.find(name);
+ if (i != interconnects.end()) return i->second;
+ else return boost::shared_ptr<Interconnect>();
+}
+bool Interconnects::remove(const std::string& name)
+{
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock);
+ InterconnectMap::iterator i = interconnects.find(name);
+ if (i != interconnects.end()) {
+ interconnects.erase(i);
+ return true;
+ } else return false;
+}
+
+boost::shared_ptr<Domain> Interconnects::findDomain(const std::string& name)
+{
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock);
+ DomainMap::iterator i = domains.find(name);
+ if (i == domains.end()) {
+ return boost::shared_ptr<Domain>();
+ } else {
+ return i->second;
+ }
+}
+void Interconnects::setContext(BrokerContext& c)
+{
+ context = &c;
+ assert(&(context->getInterconnects()) == this);
+}
+
+Interconnects::Interconnects() : context(0) {}
+
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/Interconnects.h b/qpid/cpp/src/qpid/broker/amqp/Interconnects.h
new file mode 100644
index 0000000000..030d6d6446
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Interconnects.h
@@ -0,0 +1,66 @@
+#ifndef QPID_BROKER_AMQP_INTERCONNECTS_H
+#define QPID_BROKER_AMQP_INTERCONNECTS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/ObjectFactory.h"
+#include "qpid/sys/Mutex.h"
+#include <string>
+#include <map>
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+class BrokerContext;
+class Domain;
+class Interconnect;
+/**
+ *
+ */
+class Interconnects : public ObjectFactory
+{
+ public:
+ bool createObject(Broker&, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties,
+ const std::string& userId, const std::string& connectionId);
+ bool deleteObject(Broker&, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties,
+ const std::string& userId, const std::string& connectionId);
+ bool recoverObject(Broker&, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties,
+ uint64_t persistenceId);
+
+ bool add(const std::string&, boost::shared_ptr<Interconnect>);
+ boost::shared_ptr<Interconnect> get(const std::string&);
+ bool remove(const std::string&);
+
+ boost::shared_ptr<Domain> findDomain(const std::string&);
+ void setContext(BrokerContext&);
+ Interconnects();
+ private:
+ typedef std::map<std::string, boost::shared_ptr<Interconnect> > InterconnectMap;
+ typedef std::map<std::string, boost::shared_ptr<Domain> > DomainMap;
+ InterconnectMap interconnects;
+ DomainMap domains;
+ qpid::sys::Mutex lock;
+ BrokerContext* context;
+};
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP_INTERCONNECTS_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/ManagedConnection.cpp b/qpid/cpp/src/qpid/broker/amqp/ManagedConnection.cpp
new file mode 100644
index 0000000000..2c4824bc51
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/ManagedConnection.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/amqp/ManagedConnection.h"
+#include "qpid/broker/amqp/Exception.h"
+#include "qpid/amqp/descriptors.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/management/ManagementAgent.h"
+#include "qpid/log/Statement.h"
+#include "qpid/types/Variant.h"
+#include "qmf/org/apache/qpid/broker/EventClientConnect.h"
+#include "qmf/org/apache/qpid/broker/EventClientDisconnect.h"
+
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+namespace {
+const std::string CLIENT_PROCESS_NAME("qpid.client_process");
+const std::string CLIENT_PID("qpid.client_pid");
+const std::string CLIENT_PPID("qpid.client_ppid");
+template <typename T> T getProperty(const std::string& key, const qpid::types::Variant::Map& props, T defaultValue)
+{
+ qpid::types::Variant::Map::const_iterator i = props.find(key);
+ if (i != props.end()) {
+ return i->second;
+ } else {
+ return defaultValue;
+ }
+}
+}
+ManagedConnection::ManagedConnection(Broker& broker, const std::string i, bool brokerInitiated) : id(i), agent(0)
+{
+ //management integration:
+ agent = broker.getManagementAgent();
+ if (agent != 0) {
+ qpid::management::Manageable* parent = broker.GetVhostObject();
+ connection = _qmf::Connection::shared_ptr(new _qmf::Connection(agent, this, parent, id, !brokerInitiated, false, "AMQP 1.0"));
+ agent->addObject(connection);
+ }
+}
+
+ManagedConnection::~ManagedConnection()
+{
+ if (agent && connection) {
+ agent->raiseEvent(_qmf::EventClientDisconnect(id, userid, connection->get_remoteProperties()));
+ connection->resourceDestroy();
+ }
+ QPID_LOG_CAT(debug, model, "Delete connection. user:" << userid << " rhost:" << id);
+}
+
+void ManagedConnection::setUserId(const std::string& uid)
+{
+ userid = uid;
+ if (connection) {
+ connection->set_authIdentity(userid);
+ }
+}
+
+void ManagedConnection::opened()
+{
+ if (agent) {
+ agent->raiseEvent(_qmf::EventClientConnect(id, userid, connection->get_remoteProperties()));
+ }
+ QPID_LOG_CAT(debug, model, "Create connection. user:" << userid << " rhost:" << id );
+}
+
+void ManagedConnection::setSaslMechanism(const std::string& mechanism)
+{
+ if (connection) {
+ connection->set_saslMechanism(mechanism);
+ }
+}
+
+void ManagedConnection::setSaslSsf(int ssf)
+{
+ if (connection) {
+ connection->set_saslSsf(ssf);
+ }
+}
+
+void ManagedConnection::setPeerProperties(std::map<std::string, types::Variant>& p)
+{
+ peerProperties = p;
+ if (connection) {
+ connection->set_remoteProperties(peerProperties);
+
+ std::string procName = getProperty(CLIENT_PROCESS_NAME, peerProperties, std::string());
+ uint32_t pid = getProperty(CLIENT_PID, peerProperties, 0);
+ uint32_t ppid = getProperty(CLIENT_PPID, peerProperties, 0);
+
+ if (!procName.empty())
+ connection->set_remoteProcessName(procName);
+ if (pid != 0)
+ connection->set_remotePid(pid);
+ if (ppid != 0)
+ connection->set_remoteParentPid(ppid);
+
+ }
+}
+
+void ManagedConnection::setContainerId(const std::string& container)
+{
+ containerid = container;
+ peerProperties["container-id"] = containerid;
+ if (connection) {
+ connection->set_remoteProperties(peerProperties);
+ }
+}
+const std::string& ManagedConnection::getContainerId() const
+{
+ return containerid;
+}
+
+void ManagedConnection::setInterconnectDomain(const std::string& d)
+{
+ domain = d;
+}
+const std::string& ManagedConnection::getInterconnectDomain() const
+{
+ return domain;
+}
+
+qpid::management::ManagementObject::shared_ptr ManagedConnection::GetManagementObject() const
+{
+ return connection;
+}
+
+std::string ManagedConnection::getId() const { return id; }
+
+const management::ObjectId ManagedConnection::getObjectId() const
+{
+ return GetManagementObject()->getObjectId();
+}
+const std::string& ManagedConnection::getUserId() const
+{
+ return userid;
+}
+const std::string& ManagedConnection::getMgmtId() const
+{
+ return id;
+}
+const std::map<std::string, types::Variant>& ManagedConnection::getClientProperties() const
+{
+ return connection->get_remoteProperties();
+}
+bool ManagedConnection::isLink() const
+{
+ return false;
+}
+
+bool ManagedConnection::isLocal(const OwnershipToken* t) const
+{
+ return this == t;
+}
+void ManagedConnection::outgoingMessageSent()
+{
+ if (connection) connection->inc_msgsToClient();
+}
+
+void ManagedConnection::incomingMessageReceived()
+{
+ if (connection) connection->inc_msgsFromClient();
+}
+
+void ManagedConnection::closedByManagement()
+{
+ throw Exception(qpid::amqp::error_conditions::NOT_IMPLEMENTED, QPID_MSG(id << "Connection close requested, but not implemented"));
+}
+
+qpid::management::Manageable::status_t ManagedConnection::ManagementMethod(uint32_t methodId, qpid::management::Args&, std::string& error)
+{
+ qpid::management::Manageable::status_t status = qpid::management::Manageable::STATUS_UNKNOWN_METHOD;
+
+ try {
+ switch (methodId)
+ {
+ case _qmf::Connection::METHOD_CLOSE :
+ closedByManagement();
+ if (connection) connection->set_closing(true);
+ status = qpid::management::Manageable::STATUS_OK;
+ break;
+ }
+ } catch (const Exception& e) {
+ if (e.symbol() == qpid::amqp::error_conditions::NOT_IMPLEMENTED) {
+ status = qpid::management::Manageable::STATUS_NOT_IMPLEMENTED;
+ } else {
+ error = e.what();
+ status = qpid::management::Manageable::STATUS_EXCEPTION;
+ }
+ }
+
+ return status;
+}
+
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/ManagedConnection.h b/qpid/cpp/src/qpid/broker/amqp/ManagedConnection.h
new file mode 100644
index 0000000000..77483b5630
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/ManagedConnection.h
@@ -0,0 +1,80 @@
+#ifndef QPID_BROKER_AMQP_MANAGEDCONNECTION_H
+#define QPID_BROKER_AMQP_MANAGEDCONNECTION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/management/Manageable.h"
+#include "qpid/broker/Connection.h"
+#include "qpid/types/Variant.h"
+#include "qmf/org/apache/qpid/broker/Connection.h"
+
+namespace qpid {
+namespace management {
+class ManagementAgent;
+class ManagementObject;
+}
+namespace broker {
+class Broker;
+namespace amqp {
+
+class ManagedConnection : public qpid::management::Manageable, public qpid::broker::Connection
+{
+ public:
+ ManagedConnection(Broker& broker, const std::string id, bool brokerInitiated);
+ virtual ~ManagedConnection();
+ virtual void setUserId(const std::string&);
+ std::string getId() const;
+ void setSaslMechanism(const std::string&);
+ void setSaslSsf(int);
+ void setContainerId(const std::string&);
+ const std::string& getContainerId() const;
+ void setInterconnectDomain(const std::string&);
+ const std::string& getInterconnectDomain() const;
+ void setPeerProperties(std::map<std::string, types::Variant>&);
+ qpid::management::ManagementObject::shared_ptr GetManagementObject() const;
+ bool isLocal(const OwnershipToken* t) const;
+ void incomingMessageReceived();
+ void outgoingMessageSent();
+
+ //ConnectionIdentity
+ const management::ObjectId getObjectId() const;
+ const std::string& getUserId() const;
+ const std::string& getMgmtId() const;
+ const std::map<std::string, types::Variant>& getClientProperties() const;
+ virtual bool isLink() const;
+ void opened();
+
+ qpid::management::Manageable::status_t ManagementMethod(uint32_t methodId, qpid::management::Args&, std::string&);
+
+ protected:
+ virtual void closedByManagement();
+ private:
+ const std::string id;
+ std::string userid;
+ std::string containerid;
+ std::string domain;
+ qmf::org::apache::qpid::broker::Connection::shared_ptr connection;
+ qpid::management::ManagementAgent* agent;
+ std::map<std::string, types::Variant> peerProperties;
+};
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP_MANAGEDCONNECTION_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/ManagedIncomingLink.cpp b/qpid/cpp/src/qpid/broker/amqp/ManagedIncomingLink.cpp
new file mode 100644
index 0000000000..a30349d50e
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/ManagedIncomingLink.cpp
@@ -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.
+ *
+ */
+#include "ManagedIncomingLink.h"
+#include "qpid/broker/amqp/ManagedConnection.h"
+#include "qpid/broker/amqp/ManagedSession.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/management/ManagementAgent.h"
+#include "qpid/log/Statement.h"
+
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+
+ManagedIncomingLink::ManagedIncomingLink(Broker& broker, ManagedSession& p, const std::string& source, const std::string& target, const std::string& _name)
+ : parent(p), name(_name)
+{
+ qpid::management::ManagementAgent* agent = broker.getManagementAgent();
+ if (agent) {
+ incoming = _qmf::Incoming::shared_ptr(new _qmf::Incoming(agent, this, &parent, parent.getParent().getContainerId(), _name, source, target,
+ parent.getParent().getInterconnectDomain()));
+ agent->addObject(incoming);
+ }
+}
+ManagedIncomingLink::~ManagedIncomingLink()
+{
+ if (incoming != 0) incoming->resourceDestroy();
+}
+
+qpid::management::ManagementObject::shared_ptr ManagedIncomingLink::GetManagementObject() const
+{
+ return incoming;
+}
+
+void ManagedIncomingLink::incomingMessageReceived()
+{
+ if (incoming) { incoming->inc_transfers(); }
+}
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/ManagedIncomingLink.h b/qpid/cpp/src/qpid/broker/amqp/ManagedIncomingLink.h
new file mode 100644
index 0000000000..5d4b1409d4
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/ManagedIncomingLink.h
@@ -0,0 +1,50 @@
+#ifndef QPID_BROKER_AMQP_MANAGEDINCOMINGLINK_H
+#define QPID_BROKER_AMQP_MANAGEDINCOMINGLINK_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/management/Manageable.h"
+#include "qmf/org/apache/qpid/broker/Incoming.h"
+
+namespace qpid {
+namespace management {
+class ManagementObject;
+}
+namespace broker {
+class Broker;
+namespace amqp {
+class ManagedSession;
+
+class ManagedIncomingLink : public qpid::management::Manageable
+{
+ public:
+ ManagedIncomingLink(Broker& broker, ManagedSession& parent, const std::string& source, const std::string& target, const std::string& name);
+ virtual ~ManagedIncomingLink();
+ qpid::management::ManagementObject::shared_ptr GetManagementObject() const;
+ void incomingMessageReceived();
+ private:
+ ManagedSession& parent;
+ const std::string name;
+ qmf::org::apache::qpid::broker::Incoming::shared_ptr incoming;
+};
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP_MANAGEDINCOMINGLINK_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/ManagedOutgoingLink.cpp b/qpid/cpp/src/qpid/broker/amqp/ManagedOutgoingLink.cpp
new file mode 100644
index 0000000000..9c538f864c
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/ManagedOutgoingLink.cpp
@@ -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.
+ *
+ */
+#include "ManagedOutgoingLink.h"
+#include "qpid/broker/amqp/ManagedConnection.h"
+#include "qpid/broker/amqp/ManagedSession.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/management/ManagementAgent.h"
+#include "qpid/log/Statement.h"
+
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+
+ManagedOutgoingLink::ManagedOutgoingLink(Broker& broker, ManagedSession& p, const std::string& source, const std::string& target, const std::string& _name)
+ : parent(p), name(_name)
+{
+ qpid::management::ManagementAgent* agent = broker.getManagementAgent();
+ if (agent) {
+ outgoing = _qmf::Outgoing::shared_ptr(new _qmf::Outgoing(agent, this, &parent, parent.getParent().getContainerId(), _name, source, target,
+ parent.getParent().getInterconnectDomain()));
+ agent->addObject(outgoing);
+ }
+}
+ManagedOutgoingLink::~ManagedOutgoingLink()
+{
+ if (outgoing != 0) outgoing->resourceDestroy();
+}
+
+qpid::management::ManagementObject::shared_ptr ManagedOutgoingLink::GetManagementObject() const
+{
+ return outgoing;
+}
+
+void ManagedOutgoingLink::outgoingMessageSent()
+{
+ if (outgoing) { outgoing->inc_transfers(); }
+ parent.outgoingMessageSent();
+}
+void ManagedOutgoingLink::outgoingMessageAccepted()
+{
+ parent.outgoingMessageAccepted();
+}
+void ManagedOutgoingLink::outgoingMessageRejected()
+{
+ parent.outgoingMessageRejected();
+}
+
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/ManagedOutgoingLink.h b/qpid/cpp/src/qpid/broker/amqp/ManagedOutgoingLink.h
new file mode 100644
index 0000000000..f6415bff38
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/ManagedOutgoingLink.h
@@ -0,0 +1,52 @@
+#ifndef QPID_BROKER_AMQP_MANAGEDOUTGOINGLINK_H
+#define QPID_BROKER_AMQP_MANAGEDOUTGOINGLINK_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/management/Manageable.h"
+#include "qmf/org/apache/qpid/broker/Outgoing.h"
+
+namespace qpid {
+namespace management {
+class ManagementObject;
+}
+namespace broker {
+class Broker;
+namespace amqp {
+class ManagedSession;
+
+class ManagedOutgoingLink : public qpid::management::Manageable
+{
+ public:
+ ManagedOutgoingLink(Broker& broker, ManagedSession& parent, const std::string& source, const std::string& target, const std::string& name);
+ virtual ~ManagedOutgoingLink();
+ qpid::management::ManagementObject::shared_ptr GetManagementObject() const;
+ void outgoingMessageSent();
+ void outgoingMessageAccepted();
+ void outgoingMessageRejected();
+ private:
+ ManagedSession& parent;
+ const std::string name;
+ qmf::org::apache::qpid::broker::Outgoing::shared_ptr outgoing;
+};
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP_MANAGEDOUTGOINGLINK_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/ManagedSession.cpp b/qpid/cpp/src/qpid/broker/amqp/ManagedSession.cpp
new file mode 100644
index 0000000000..3bf9cf90db
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/ManagedSession.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/amqp/ManagedSession.h"
+#include "qpid/broker/amqp/ManagedConnection.h"
+#include "qpid/broker/amqp/Exception.h"
+#include "qpid/amqp/descriptors.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/management/ManagementAgent.h"
+#include "qpid/log/Statement.h"
+
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+
+ManagedSession::ManagedSession(Broker& broker, ManagedConnection& p, const std::string i) : parent(p), id(i), unacked(0)
+{
+ qpid::management::ManagementAgent* agent = broker.getManagementAgent();
+ if (agent != 0) {
+ std::string name(id);
+ std::string fullName(name);
+ if (name.length() >= std::numeric_limits<uint8_t>::max())
+ name.resize(std::numeric_limits<uint8_t>::max()-1);
+ session = _qmf::Session::shared_ptr(new _qmf::Session(agent, this, broker.GetVhostObject(), name));
+ session->set_fullName(fullName);
+ session->set_attached(true);
+ session->clr_expireTime();
+ session->set_connectionRef(parent.GetManagementObject()->getObjectId());
+ agent->addObject(session);
+ }
+}
+
+ManagedSession::~ManagedSession()
+{
+ if (session) session->resourceDestroy();
+}
+
+qpid::management::ManagementObject::shared_ptr ManagedSession::GetManagementObject() const
+{
+ return session;
+}
+
+bool ManagedSession::isLocal(const OwnershipToken* t) const
+{
+ return &parent == t;
+}
+
+void ManagedSession::outgoingMessageSent()
+{
+ if (session) session->set_unackedMessages(++unacked);
+ parent.outgoingMessageSent();
+}
+void ManagedSession::outgoingMessageAccepted()
+{
+ if (session) session->set_unackedMessages(--unacked);
+}
+void ManagedSession::outgoingMessageRejected()
+{
+ if (session) session->set_unackedMessages(--unacked);
+}
+
+void ManagedSession::incomingMessageReceived()
+{
+ parent.incomingMessageReceived();
+}
+void ManagedSession::incomingMessageAccepted()
+{
+
+}
+void ManagedSession::incomingMessageRejected()
+{
+
+}
+void ManagedSession::txStarted()
+{
+ if (session) {
+ session->inc_TxnStarts();
+ }
+}
+
+void ManagedSession::txCommitted()
+{
+ if (session) {
+ session->inc_TxnCommits();
+ session->inc_TxnCount();
+ }
+}
+
+void ManagedSession::txAborted()
+{
+ if (session) {
+ session->inc_TxnRejects();
+ session->inc_TxnCount();
+ }
+}
+
+ManagedConnection& ManagedSession::getParent()
+{
+ return parent;
+}
+
+void ManagedSession::detachedByManagement()
+{
+ throw Exception(qpid::amqp::error_conditions::NOT_IMPLEMENTED, QPID_MSG(id << "Session detach requested, but not implemented"));
+}
+
+qpid::management::Manageable::status_t ManagedSession::ManagementMethod (uint32_t methodId,
+ qpid::management::Args& /*args*/,
+ std::string& error)
+{
+ qpid::management::Manageable::status_t status = qpid::management::Manageable::STATUS_UNKNOWN_METHOD;
+
+ try {
+ switch (methodId)
+ {
+ case _qmf::Session::METHOD_DETACH :
+ detachedByManagement();
+ status = qpid::management::Manageable::STATUS_OK;
+ break;
+
+ case _qmf::Session::METHOD_CLOSE :
+ case _qmf::Session::METHOD_SOLICITACK :
+ case _qmf::Session::METHOD_RESETLIFESPAN :
+ status = Manageable::STATUS_NOT_IMPLEMENTED;
+ break;
+ }
+ } catch (const Exception& e) {
+ if (e.symbol() == qpid::amqp::error_conditions::NOT_IMPLEMENTED) {
+ status = qpid::management::Manageable::STATUS_NOT_IMPLEMENTED;
+ } else {
+ error = e.what();
+ status = qpid::management::Manageable::STATUS_EXCEPTION;
+ }
+ }
+
+ return status;
+}
+
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/ManagedSession.h b/qpid/cpp/src/qpid/broker/amqp/ManagedSession.h
new file mode 100644
index 0000000000..c35e304cfb
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/ManagedSession.h
@@ -0,0 +1,66 @@
+#ifndef QPID_BROKER_AMQP_MANAGEDSESSION_H
+#define QPID_BROKER_AMQP_MANAGEDSESSION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/management/Manageable.h"
+#include "qmf/org/apache/qpid/broker/Session.h"
+#include "qpid/broker/OwnershipToken.h"
+
+namespace qpid {
+namespace management {
+class ManagementObject;
+}
+namespace broker {
+class Broker;
+namespace amqp {
+class ManagedConnection;
+
+class ManagedSession : public qpid::management::Manageable, public OwnershipToken
+{
+ public:
+ ManagedSession(Broker& broker, ManagedConnection& parent, const std::string id);
+ virtual ~ManagedSession();
+ qpid::management::ManagementObject::shared_ptr GetManagementObject() const;
+ bool isLocal(const OwnershipToken* t) const;
+ void incomingMessageReceived();
+ void incomingMessageAccepted();
+ void incomingMessageRejected();
+ void outgoingMessageSent();
+ void outgoingMessageAccepted();
+ void outgoingMessageRejected();
+ void txStarted();
+ void txCommitted();
+ void txAborted();
+ ManagedConnection& getParent();
+
+ qpid::management::Manageable::status_t ManagementMethod (uint32_t, qpid::management::Args&, std::string&);
+ protected:
+ virtual void detachedByManagement();
+ private:
+ ManagedConnection& parent;
+ const std::string id;
+ qmf::org::apache::qpid::broker::Session::shared_ptr session;
+ size_t unacked;
+};
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP_MANAGEDSESSION_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/Message.cpp b/qpid/cpp/src/qpid/broker/amqp/Message.cpp
new file mode 100644
index 0000000000..54741e6436
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Message.cpp
@@ -0,0 +1,472 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "Message.h"
+#include "qpid/amqp/Decoder.h"
+#include "qpid/amqp/descriptors.h"
+#include "qpid/amqp/ListBuilder.h"
+#include "qpid/amqp/MapBuilder.h"
+#include "qpid/amqp/MapHandler.h"
+#include "qpid/amqp/MessageEncoder.h"
+#include "qpid/amqp/Reader.h"
+#include "qpid/amqp/typecodes.h"
+#include "qpid/types/encodings.h"
+#include "qpid/log/Statement.h"
+#include "qpid/framing/Buffer.h"
+#include <string.h>
+#include <boost/lexical_cast.hpp>
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+
+using qpid::amqp::CharSequence;
+using qpid::amqp::Constructor;
+using qpid::amqp::Descriptor;
+using qpid::amqp::MapHandler;
+using qpid::amqp::Reader;
+
+namespace {
+std::string empty;
+}
+
+std::string Message::getRoutingKey() const
+{
+ std::string v;
+ v.assign(subject.data, subject.size);
+ return v;
+}
+std::string Message::getUserId() const
+{
+ std::string v;
+ v.assign(userId.data, userId.size);
+ return v;
+}
+
+uint64_t Message::getTimestamp() const
+{
+ //AMQP 1.0 message doesn't have the equivalent of the 0-10 timestamp field
+ //TODO: define an annotation for that
+ return 0;
+}
+
+bool Message::isPersistent() const
+{
+ return durable && durable.get();
+}
+bool Message::getTtl(uint64_t& t) const
+{
+ if (!ttl) {
+ return false;
+ } else {
+ t = ttl.get();
+ return true;
+ }
+}
+
+uint8_t Message::getPriority() const
+{
+ if (!priority) return 4;
+ else return priority.get();
+}
+
+namespace {
+class StringRetriever : public MapHandler
+{
+ public:
+ StringRetriever(const std::string& k) : key(k) {}
+ void handleBool(const qpid::amqp::CharSequence& actualKey, bool actualValue) { process(actualKey, actualValue); }
+ void handleUint8(const qpid::amqp::CharSequence& actualKey, uint8_t actualValue) { process(actualKey, actualValue); }
+ void handleUint16(const qpid::amqp::CharSequence& actualKey, uint16_t actualValue) { process(actualKey, actualValue); }
+ void handleUint32(const qpid::amqp::CharSequence& actualKey, uint32_t actualValue) { process(actualKey, actualValue); }
+ void handleUint64(const qpid::amqp::CharSequence& actualKey, uint64_t actualValue) { process(actualKey, actualValue); }
+ void handleInt8(const qpid::amqp::CharSequence& actualKey, int8_t actualValue) { process(actualKey, actualValue); }
+ void handleInt16(const qpid::amqp::CharSequence& actualKey, int16_t actualValue) { process(actualKey, actualValue); }
+ void handleInt32(const qpid::amqp::CharSequence& actualKey, int32_t actualValue) { process(actualKey, actualValue); }
+ void handleInt64(const qpid::amqp::CharSequence& actualKey, int64_t actualValue) { process(actualKey, actualValue); }
+ void handleFloat(const qpid::amqp::CharSequence& actualKey, float actualValue) { process(actualKey, actualValue); }
+ void handleDouble(const qpid::amqp::CharSequence& actualKey, double actualValue) { process(actualKey, actualValue); }
+ void handleVoid(const qpid::amqp::CharSequence&) { /*nothing to do*/ }
+ void handleString(const qpid::amqp::CharSequence& actualKey, const qpid::amqp::CharSequence& actualValue, const qpid::amqp::CharSequence& /*encoding*/)
+ {
+ if (isRequestedKey(actualKey)) value = std::string(actualValue.data, actualValue.size);
+ }
+ std::string getValue() const { return value; }
+ private:
+ const std::string key;
+ std::string value;
+
+ template <typename T> void process(const qpid::amqp::CharSequence& actualKey, T actualValue)
+ {
+ if (isRequestedKey(actualKey)) value = boost::lexical_cast<std::string>(actualValue);
+ }
+
+ bool isRequestedKey(const qpid::amqp::CharSequence& actualKey)
+ {
+ //TODO: avoid allocating new string by just iterating over chars
+ return key == std::string(actualKey.data, actualKey.size);
+ }
+};
+}
+
+std::string Message::getPropertyAsString(const std::string& key) const
+{
+ StringRetriever sr(key);
+ processProperties(sr);
+ return sr.getValue();
+}
+
+namespace {
+ class PropertyAdapter : public Reader {
+ MapHandler& handler;
+ CharSequence key;
+ enum {
+ KEY,
+ VALUE
+ } state;
+
+ void checkValue() {
+ if ( state==VALUE ) state = KEY;
+ else {
+ // TODO: Would throwing an exception make more sense here?
+ QPID_LOG(error, "Received non string property key");
+ key = CharSequence();
+ state = KEY;
+ }
+ }
+
+ void onNull(const Descriptor*) {checkValue(); handler.handleVoid(key);}
+ void onBoolean(bool b, const Descriptor*) {checkValue(); handler.handleBool(key, b);}
+ void onUByte(uint8_t i, const Descriptor*) {checkValue(); handler.handleUint8(key, i);}
+ void onUShort(uint16_t i, const Descriptor*) {checkValue(); handler.handleUint16(key, i);}
+ void onUInt(uint32_t i, const Descriptor*) {checkValue(); handler.handleUint32(key, i);}
+ void onULong(uint64_t i, const Descriptor*) {checkValue(); handler.handleUint64(key, i);}
+ void onByte(int8_t i, const Descriptor*) {checkValue(); handler.handleInt8(key, i);}
+ void onShort(int16_t i, const Descriptor*) {checkValue(); handler.handleInt16(key, i);}
+ void onInt(int32_t i, const Descriptor*) {checkValue(); handler.handleInt32(key, i);}
+ void onLong(int64_t i, const Descriptor*) {checkValue(); handler.handleInt64(key, i);}
+ void onFloat(float x, const Descriptor*) {checkValue(); handler.handleFloat(key, x);}
+ void onDouble(double x, const Descriptor*) {checkValue(); handler.handleDouble(key, x);}
+ void onTimestamp(int64_t i, const Descriptor*) {checkValue(); handler.handleInt64(key, i);}
+
+ void onString(const CharSequence& s, const Descriptor*) {
+ if ( state==KEY ) {
+ state = VALUE;
+ key = s;
+ } else {
+ state = KEY;
+ handler.handleString(key, s, CharSequence());
+ }
+ }
+
+ bool onStartList(uint32_t, const CharSequence&, const CharSequence&, const Descriptor*) { return false; }
+ bool onStartMap(uint32_t, const CharSequence&, const CharSequence&, const Descriptor*) { return false; }
+ bool onStartArray(uint32_t, const CharSequence&, const Constructor&, const Descriptor*) { return false; }
+
+ public:
+ PropertyAdapter(MapHandler& mh) :
+ handler(mh),
+ state(KEY)
+ {}
+ };
+
+void processMapData(const CharSequence& source, MapHandler& handler)
+{
+ qpid::amqp::Decoder d(source.data, source.size);
+ PropertyAdapter adapter(handler);
+ d.read(adapter);
+
+}
+}
+
+void Message::processProperties(MapHandler& mh) const {
+ processMapData(applicationProperties, mh);
+}
+
+std::string Message::getAnnotationAsString(const std::string& key) const
+{
+ StringRetriever sr(key);
+ processMapData(messageAnnotations, sr);
+ if (sr.getValue().empty()) processMapData(deliveryAnnotations, sr);
+ return sr.getValue();
+
+}
+
+uint64_t Message::getMessageSize() const { return data.size(); }
+//getContent() is used primarily for decoding qmf messages in
+//management and ha, but also by the xml exchange
+std::string Message::getContent() const
+{
+ return std::string(body.data, body.size);
+}
+
+Message::Message(size_t size) : data(size), bodyDescriptor(0)
+{
+ deliveryAnnotations.init();
+ messageAnnotations.init();
+ bareMessage.init();
+
+ userId.init();
+ to.init();
+ subject.init();
+ replyTo.init();
+ contentType.init();
+ contentEncoding.init();
+
+ applicationProperties.init();
+ body.init();
+ footer.init();
+}
+char* Message::getData() { return &data[0]; }
+const char* Message::getData() const { return &data[0]; }
+size_t Message::getSize() const { return data.size(); }
+
+qpid::amqp::MessageId Message::getMessageId() const
+{
+ return messageId;
+}
+qpid::amqp::CharSequence Message::getReplyTo() const
+{
+ return replyTo;
+}
+qpid::amqp::MessageId Message::getCorrelationId() const
+{
+ return correlationId;
+}
+qpid::amqp::CharSequence Message::getContentType() const
+{
+ return contentType;
+}
+qpid::amqp::CharSequence Message::getContentEncoding() const
+{
+ return contentEncoding;
+}
+
+qpid::amqp::CharSequence Message::getDeliveryAnnotations() const
+{
+ return deliveryAnnotations;
+}
+qpid::amqp::CharSequence Message::getMessageAnnotations() const
+{
+ return messageAnnotations;
+}
+qpid::amqp::CharSequence Message::getApplicationProperties() const
+{
+ return applicationProperties;
+}
+qpid::amqp::CharSequence Message::getBareMessage() const
+{
+ return bareMessage;
+}
+qpid::amqp::CharSequence Message::getBody() const
+{
+ return body;
+}
+qpid::amqp::CharSequence Message::getFooter() const
+{
+ return footer;
+}
+
+void Message::scan()
+{
+ qpid::amqp::Decoder decoder(getData(), getSize());
+ decoder.read(*this);
+ bareMessage = qpid::amqp::MessageReader::getBareMessage();
+ if (bareMessage.data && !bareMessage.size) {
+ bareMessage.size = getSize() - (bareMessage.data - getData());
+ }
+}
+
+const Message& Message::get(const qpid::broker::Message& message)
+{
+ const Message* m = dynamic_cast<const Message*>(&message.getEncoding());
+ if (!m) throw qpid::Exception("Translation not yet implemented!!");
+ return *m;
+}
+
+void Message::onDurable(bool b) { durable = b; }
+void Message::onPriority(uint8_t i) { priority = i; }
+void Message::onTtl(uint32_t i) { ttl = i; }
+void Message::onFirstAcquirer(bool b) { firstAcquirer = b; }
+void Message::onDeliveryCount(uint32_t i) { deliveryCount = i; }
+
+void Message::onMessageId(uint64_t v) { messageId.set(v); }
+void Message::onMessageId(const qpid::amqp::CharSequence& v, qpid::types::VariantType t) { messageId.set(v, t); }
+void Message::onUserId(const qpid::amqp::CharSequence& v) { userId = v; }
+void Message::onTo(const qpid::amqp::CharSequence& v) { to = v; }
+void Message::onSubject(const qpid::amqp::CharSequence& v) { subject = v; }
+void Message::onReplyTo(const qpid::amqp::CharSequence& v) { replyTo = v; }
+void Message::onCorrelationId(uint64_t v) { correlationId.set(v); }
+void Message::onCorrelationId(const qpid::amqp::CharSequence& v, qpid::types::VariantType t) { correlationId.set(v, t);}
+void Message::onContentType(const qpid::amqp::CharSequence& v) { contentType = v; }
+void Message::onContentEncoding(const qpid::amqp::CharSequence& v) { contentEncoding = v; }
+void Message::onAbsoluteExpiryTime(int64_t) {}
+void Message::onCreationTime(int64_t) {}
+void Message::onGroupId(const qpid::amqp::CharSequence&) {}
+void Message::onGroupSequence(uint32_t) {}
+void Message::onReplyToGroupId(const qpid::amqp::CharSequence&) {}
+
+void Message::onApplicationProperties(const qpid::amqp::CharSequence& v, const qpid::amqp::CharSequence&) { applicationProperties = v; }
+void Message::onDeliveryAnnotations(const qpid::amqp::CharSequence&, const qpid::amqp::CharSequence& v) { deliveryAnnotations = v; }
+void Message::onMessageAnnotations(const qpid::amqp::CharSequence&, const qpid::amqp::CharSequence& v) { messageAnnotations = v; }
+
+void Message::onData(const qpid::amqp::CharSequence& v) { body = v; }
+void Message::onAmqpSequence(const qpid::amqp::CharSequence& v) { body = v; bodyType = qpid::amqp::typecodes::LIST_NAME; }
+void Message::onAmqpValue(const qpid::amqp::CharSequence& v, const std::string& t, const qpid::amqp::Descriptor* d)
+{
+ body = v;
+ if (t == qpid::amqp::typecodes::STRING_NAME) {
+ bodyType = qpid::types::encodings::UTF8;
+ } else if (t == qpid::amqp::typecodes::SYMBOL_NAME) {
+ bodyType = qpid::types::encodings::ASCII;
+ } else if (t == qpid::amqp::typecodes::BINARY_NAME) {
+ bodyType = qpid::types::encodings::BINARY;
+ } else {
+ bodyType = t;
+ }
+ if (d) {
+ bodyDescriptor = *d;
+ }
+}
+void Message::onAmqpValue(const qpid::types::Variant& v, const qpid::amqp::Descriptor* d)
+{
+ typedBody = v;
+ if (d) {
+ bodyDescriptor = *d;
+ }
+}
+
+void Message::onFooter(const qpid::amqp::CharSequence&, const qpid::amqp::CharSequence& v) { footer = v; }
+
+bool Message::isTypedBody() const
+{
+ return !typedBody.isVoid() || !bodyType.empty();
+}
+
+qpid::types::Variant Message::getTypedBody() const
+{
+ if (bodyType == qpid::amqp::typecodes::LIST_NAME) {
+ qpid::amqp::ListBuilder builder;
+ qpid::amqp::Decoder decoder(body.data, body.size);
+ decoder.read(builder);
+ return builder.getList();
+ } else if (bodyType == qpid::amqp::typecodes::MAP_NAME) {
+ qpid::amqp::MapBuilder builder;
+ qpid::amqp::Decoder decoder(body.data, body.size);
+ decoder.read(builder);
+ return builder.getMap();
+ } else if (!bodyType.empty()) {
+ qpid::types::Variant value(std::string(body.data, body.size));
+ value.setEncoding(bodyType);
+ return value;
+ } else {
+ return typedBody;
+ }
+}
+
+const qpid::amqp::Descriptor& Message::getBodyDescriptor() const
+{
+ return bodyDescriptor;
+}
+
+//PersistableMessage interface:
+void Message::encode(framing::Buffer& buffer) const
+{
+ buffer.putLong(0);//4-byte format indicator
+ buffer.putRawData((const uint8_t*) getData(), getSize());
+ QPID_LOG(debug, "Encoded 1.0 message of " << getSize() << " bytes, including " << bareMessage.size << " bytes of 'bare message'");
+}
+uint32_t Message::encodedSize() const
+{
+ return 4/*format indicator*/ + data.size();
+}
+//in 1.0 the binary header/content makes less sense and in any case
+//the functionality that split originally supported (i.e. lazy-loaded
+//messages) is no longer in use; for 1.0 we therefore treat the whole
+//content as 'header' and load it in the first stage.
+uint32_t Message::encodedHeaderSize() const
+{
+ return encodedSize();
+}
+void Message::decodeHeader(framing::Buffer& buffer)
+{
+ if (buffer.available() != getSize()) {
+ QPID_LOG(warning, "1.0 Message buffer was " << data.size() << " bytes, but " << buffer.available() << " bytes are available. Resizing.");
+ data.resize(buffer.available());
+ }
+ buffer.getRawData((uint8_t*) getData(), getSize());
+ scan();
+ QPID_LOG(debug, "Decoded 1.0 message of " << getSize() << " bytes, including " << bareMessage.size << " bytes of 'bare message'");
+}
+void Message::decodeContent(framing::Buffer& /*buffer*/) {}
+
+boost::intrusive_ptr<PersistableMessage> Message::merge(const std::map<std::string, qpid::types::Variant>& added) const
+{
+ //message- or delivery- annotations? would have to determine that from the name, for now assume always message-annotations
+ std::map<std::string, qpid::types::Variant> combined;
+ const std::map<std::string, qpid::types::Variant>* annotations(0);
+ if (messageAnnotations) {
+ //combine existing and added annotations (TODO: this could be
+ //optimised by avoiding the decode and simply 'editing' the
+ //size and count in the raw data, then appending the new
+ //elements).
+ qpid::amqp::MapBuilder builder;
+ qpid::amqp::Decoder decoder(messageAnnotations.data, messageAnnotations.size);
+ decoder.read(builder);
+ combined = builder.getMap();
+ for (std::map<std::string, qpid::types::Variant>::const_iterator i = added.begin(); i != added.end(); ++i) {
+ combined[i->first] = i->second;
+ }
+ annotations = &combined;
+ } else {
+ //additions form a whole new section
+ annotations = &added;
+ }
+ size_t annotationsSize = qpid::amqp::MessageEncoder::getEncodedSize(*annotations, true) + 3/*descriptor*/;
+
+ boost::intrusive_ptr<Message> copy(new Message(bareMessage.size+footer.size+deliveryAnnotations.size+annotationsSize));
+ size_t position(0);
+ if (deliveryAnnotations.size) {
+ ::memcpy(&copy->data[position], deliveryAnnotations.data, deliveryAnnotations.size);
+ position += deliveryAnnotations.size;
+ }
+
+ qpid::amqp::Encoder encoder(&copy->data[position], annotationsSize);
+ encoder.writeMap(*annotations, &qpid::amqp::message::MESSAGE_ANNOTATIONS, true);
+ position += encoder.getPosition();
+
+ if (bareMessage) {
+ ::memcpy(&copy->data[position], bareMessage.data, bareMessage.size);
+ position += bareMessage.size;
+ }
+ if (footer) {
+ ::memcpy(&copy->data[position], footer.data, footer.size);
+ position += footer.size;
+ }
+ copy->data.resize(position);//annotationsSize may be slightly bigger than needed if optimisations are used (e.g. smallint)
+ copy->scan();
+ assert(copy->messageAnnotations);
+ assert(copy->bareMessage.size == bareMessage.size);
+ assert(copy->footer.size == footer.size);
+ assert(copy->deliveryAnnotations.size == deliveryAnnotations.size);
+ return copy;
+}
+
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/Message.h b/qpid/cpp/src/qpid/broker/amqp/Message.h
new file mode 100644
index 0000000000..20310aa977
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Message.h
@@ -0,0 +1,160 @@
+#ifndef QPID_BROKER_AMQP_MESSAGE_H
+#define QPID_BROKER_AMQP_MESSAGE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/Message.h"
+#include "qpid/amqp/CharSequence.h"
+#include "qpid/amqp/Descriptor.h"
+#include "qpid/amqp/MessageId.h"
+#include "qpid/amqp/MessageReader.h"
+#include <boost/optional.hpp>
+
+namespace qpid {
+namespace framing {
+class Buffer;
+}
+namespace broker {
+namespace amqp {
+
+/**
+ * Represents an AMQP 1.0 format message
+ */
+class Message : public qpid::broker::Message::SharedStateImpl, private qpid::amqp::MessageReader, public qpid::broker::PersistableMessage
+{
+ public:
+ //Encoding interface:
+ std::string getRoutingKey() const;
+ bool isPersistent() const;
+ uint8_t getPriority() const;
+ uint64_t getMessageSize() const;
+ std::string getPropertyAsString(const std::string& key) const;
+ std::string getAnnotationAsString(const std::string& key) const;
+ bool getTtl(uint64_t&) const;
+ std::string getContent() const;
+ void processProperties(qpid::amqp::MapHandler&) const;
+ std::string getUserId() const;
+ uint64_t getTimestamp() const;
+ qpid::amqp::MessageId getMessageId() const;
+ qpid::amqp::MessageId getCorrelationId() const;
+
+ qpid::amqp::CharSequence getReplyTo() const;
+ qpid::amqp::CharSequence getContentType() const;
+ qpid::amqp::CharSequence getContentEncoding() const;
+
+ qpid::amqp::CharSequence getDeliveryAnnotations() const;
+ qpid::amqp::CharSequence getMessageAnnotations() const;
+ qpid::amqp::CharSequence getApplicationProperties() const;
+ qpid::amqp::CharSequence getBareMessage() const;
+ qpid::amqp::CharSequence getBody() const;
+ qpid::amqp::CharSequence getFooter() const;
+ bool isTypedBody() const;
+ qpid::types::Variant getTypedBody() const;
+ const qpid::amqp::Descriptor& getBodyDescriptor() const;
+
+ Message(size_t size);
+ char* getData();
+ const char* getData() const;
+ size_t getSize() const;
+ void scan();
+
+ //PersistableMessage interface:
+ void encode(framing::Buffer& buffer) const;
+ uint32_t encodedSize() const;
+ void decodeHeader(framing::Buffer& buffer);
+ void decodeContent(framing::Buffer& buffer);
+ uint32_t encodedHeaderSize() const;
+ boost::intrusive_ptr<PersistableMessage> merge(const std::map<std::string, qpid::types::Variant>& annotations) const;
+
+ static const Message& get(const qpid::broker::Message&);
+ private:
+ std::vector<char> data;
+
+ //header:
+ boost::optional<bool> durable;
+ boost::optional<uint8_t> priority;
+ boost::optional<uint32_t> ttl;
+ boost::optional<bool> firstAcquirer;
+ boost::optional<uint32_t> deliveryCount;
+ //annotations:
+ qpid::amqp::CharSequence deliveryAnnotations;
+ qpid::amqp::CharSequence messageAnnotations;
+
+ qpid::amqp::CharSequence bareMessage;//properties, application-properties and content
+ //properties:
+ qpid::amqp::MessageId messageId;
+ qpid::amqp::CharSequence userId;
+ qpid::amqp::CharSequence to;
+ qpid::amqp::CharSequence subject;
+ qpid::amqp::CharSequence replyTo;
+ qpid::amqp::MessageId correlationId;
+ qpid::amqp::CharSequence contentType;
+ qpid::amqp::CharSequence contentEncoding;
+
+ //application-properties:
+ qpid::amqp::CharSequence applicationProperties;
+
+ //body:
+ qpid::amqp::CharSequence body;
+ qpid::types::Variant typedBody;
+ std::string bodyType;
+ qpid::amqp::Descriptor bodyDescriptor;
+
+ //footer:
+ qpid::amqp::CharSequence footer;
+
+ //header:
+ void onDurable(bool b);
+ void onPriority(uint8_t i);
+ void onTtl(uint32_t i);
+ void onFirstAcquirer(bool b);
+ void onDeliveryCount(uint32_t i);
+ //properties:
+ void onMessageId(uint64_t);
+ void onMessageId(const qpid::amqp::CharSequence&, qpid::types::VariantType);
+ void onUserId(const qpid::amqp::CharSequence& v);
+ void onTo(const qpid::amqp::CharSequence& v);
+ void onSubject(const qpid::amqp::CharSequence& v);
+ void onReplyTo(const qpid::amqp::CharSequence& v);
+ void onCorrelationId(uint64_t);
+ void onCorrelationId(const qpid::amqp::CharSequence&, qpid::types::VariantType);
+ void onContentType(const qpid::amqp::CharSequence& v);
+ void onContentEncoding(const qpid::amqp::CharSequence& v);
+ void onAbsoluteExpiryTime(int64_t i);
+ void onCreationTime(int64_t);
+ void onGroupId(const qpid::amqp::CharSequence&);
+ void onGroupSequence(uint32_t);
+ void onReplyToGroupId(const qpid::amqp::CharSequence&);
+
+ void onApplicationProperties(const qpid::amqp::CharSequence&, const qpid::amqp::CharSequence&);
+ void onDeliveryAnnotations(const qpid::amqp::CharSequence&, const qpid::amqp::CharSequence&);
+ void onMessageAnnotations(const qpid::amqp::CharSequence&, const qpid::amqp::CharSequence&);
+
+ void onData(const qpid::amqp::CharSequence&);
+ void onAmqpSequence(const qpid::amqp::CharSequence&);
+ void onAmqpValue(const qpid::amqp::CharSequence&, const std::string& type, const qpid::amqp::Descriptor*);
+ void onAmqpValue(const qpid::types::Variant&, const qpid::amqp::Descriptor*);
+
+ void onFooter(const qpid::amqp::CharSequence&, const qpid::amqp::CharSequence&);
+};
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP_MESSAGE_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/NodePolicy.cpp b/qpid/cpp/src/qpid/broker/amqp/NodePolicy.cpp
new file mode 100644
index 0000000000..69b41dafa2
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/NodePolicy.cpp
@@ -0,0 +1,326 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/amqp/NodePolicy.h"
+#include "qpid/broker/amqp/Connection.h"
+#include "qpid/broker/amqp/Topic.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/Exchange.h"
+#include "qpid/types/Exception.h"
+#include "qpid/amqp_0_10/Codecs.h"
+#include "qpid/log/Statement.h"
+#include "qpid/management/ManagementAgent.h"
+
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+namespace {
+const std::string DURABLE("durable");
+const std::string AUTO_DELETE("auto-delete");
+const std::string LIFETIME_POLICY("qpid.lifetime-policy");
+const std::string MANUAL("manual");
+const std::string UNUSED("delete-if-unused");
+const std::string UNUSED_AND_EMPTY("delete-if-unused-and-empty");
+const std::string QUEUE_POLICY("QueuePolicy");
+const std::string TOPIC_POLICY("TopicPolicy");
+const std::string QUEUE("queue");
+const std::string TOPIC("topic");
+const std::string ALTERNATE_EXCHANGE("alternate-exchange");
+const std::string EXCHANGE_TYPE("exchange-type");
+const std::string QPID_MSG_SEQUENCE("qpid.msg_sequence");
+const std::string QPID_IVE("qpid.ive");
+const std::string EMPTY;
+
+template <typename T>
+T get(const std::string& k, const qpid::types::Variant::Map& m, T defaultValue)
+{
+ qpid::types::Variant::Map::const_iterator i = m.find(k);
+ if (i == m.end()) return defaultValue;
+ else return i->second;
+}
+
+std::string getProperty(const std::string& k, const qpid::types::Variant::Map& m)
+{
+ return get(k, m, EMPTY);
+}
+
+bool testProperty(const std::string& k, const qpid::types::Variant::Map& m)
+{
+ return get(k, m, false);
+}
+
+qpid::types::Variant::Map filterForQueue(const qpid::types::Variant::Map& properties)
+{
+ qpid::types::Variant::Map filtered = properties;
+ filtered.erase(DURABLE);
+ filtered.erase(AUTO_DELETE);
+ filtered.erase(ALTERNATE_EXCHANGE);
+ return filtered;
+}
+qpid::types::Variant::Map filterForTopic(const qpid::types::Variant::Map& properties)
+{
+ qpid::types::Variant::Map filtered = properties;
+ filtered.erase(DURABLE);
+ filtered.erase(EXCHANGE_TYPE);
+ filtered.erase(AUTO_DELETE);
+ filtered.erase(QPID_IVE);
+ filtered.erase(QPID_MSG_SEQUENCE);
+ return filtered;
+}
+void copy(const std::string& key, const qpid::types::Variant::Map& from, qpid::types::Variant::Map& to)
+{
+ qpid::types::Variant::Map::const_iterator i = from.find(key);
+ if (i != from.end()) to.insert(*i);
+}
+
+}
+NodePolicy::NodePolicy(const std::string& type, const std::string& ptrn, const qpid::types::Variant::Map& props)
+ : PersistableObject(ptrn, type, props), pattern(ptrn),
+ durable(testProperty(DURABLE, props)),
+ alternateExchange(getProperty(ALTERNATE_EXCHANGE, props)),
+ compiled(pattern) {}
+
+NodePolicy::~NodePolicy() {}
+
+const std::string& NodePolicy::getPattern() const
+{
+ return pattern;
+}
+
+bool NodePolicy::isDurable() const
+{
+ return durable;
+}
+
+bool NodePolicy::match(const std::string& name) const
+{
+ return qpid::sys::regex_match(name, compiled);
+}
+
+QueuePolicy::QueuePolicy(Broker& broker, const std::string& pattern, const qpid::types::Variant::Map& props)
+ : NodePolicy(QUEUE_POLICY, pattern, props),
+ queueSettings(durable, testProperty(AUTO_DELETE, props))
+{
+ qpid::types::Variant::Map unused;
+ qpid::types::Variant::Map filtered = filterForQueue(props);
+ //if queue is not durable and neither lifetime policy nor
+ //autodelete were explicitly specified, clean it up when not
+ //needed by default:
+ if (!queueSettings.durable && props.find(LIFETIME_POLICY) == props.end() && props.find(AUTO_DELETE) == props.end()) {
+ filtered[LIFETIME_POLICY] = UNUSED_AND_EMPTY;
+ }
+ queueSettings.populate(filtered, unused);
+ qpid::amqp_0_10::translate(filtered, queueSettings.storeSettings);
+
+ qpid::management::ManagementAgent* agent = broker.getManagementAgent();
+ if (agent != 0) {
+ policy = _qmf::QueuePolicy::shared_ptr(new _qmf::QueuePolicy(agent, this, pattern));
+ policy->set_properties(props);
+ agent->addObject(policy);
+ }
+}
+QueuePolicy::~QueuePolicy()
+{
+ if (policy != 0) policy->resourceDestroy();
+}
+
+
+std::pair<boost::shared_ptr<Queue>, boost::shared_ptr<Topic> > QueuePolicy::create(const std::string& name, Connection& connection)
+{
+ std::pair<boost::shared_ptr<Queue>, boost::shared_ptr<Topic> > result;
+ result.first = connection.getBroker().createQueue(name, queueSettings, 0/*not exclusive*/, alternateExchange, connection.getUserId(), connection.getId()).first;
+ return result;
+}
+
+boost::shared_ptr<qpid::management::ManagementObject> QueuePolicy::GetManagementObject() const
+{
+ return policy;
+}
+
+TopicPolicy::TopicPolicy(Broker& broker, const std::string& pattern, const qpid::types::Variant::Map& props)
+ : NodePolicy(TOPIC_POLICY, pattern, props), exchangeType(getProperty(EXCHANGE_TYPE, props)),
+ autodelete(get(AUTO_DELETE, props, !durable))
+{
+ if (exchangeType.empty()) exchangeType = TOPIC;
+ broker.getExchanges().checkType(exchangeType);
+ qpid::types::Variant::Map::const_iterator i = props.find(LIFETIME_POLICY);
+ if (i != props.end()) {
+ if (i->second == MANUAL) {
+ autodelete = false;
+ } else if (i->second == UNUSED || i->second == UNUSED_AND_EMPTY/*though empty doesn't mean much for an exchange*/) {
+ autodelete = true;
+ } else {
+ QPID_LOG(warning, "Did not recognise lifetime policy " << i->second << " in topic policy for " << pattern);
+ }
+ }
+ topicSettings = filterForTopic(props);
+ copy(QPID_IVE, props, exchangeSettings);
+ copy(QPID_MSG_SEQUENCE, props, exchangeSettings);
+
+ qpid::management::ManagementAgent* agent = broker.getManagementAgent();
+ if (agent != 0) {
+ policy = _qmf::TopicPolicy::shared_ptr(new _qmf::TopicPolicy(agent, this, pattern));
+ policy->set_properties(props);
+ agent->addObject(policy);
+ }
+}
+
+TopicPolicy::~TopicPolicy()
+{
+ if (policy != 0) policy->resourceDestroy();
+}
+
+std::pair<boost::shared_ptr<Queue>, boost::shared_ptr<Topic> > TopicPolicy::create(const std::string& name, Connection& connection)
+{
+ std::pair<boost::shared_ptr<Queue>, boost::shared_ptr<Topic> > result;
+ qpid::framing::FieldTable args;
+ qpid::amqp_0_10::translate(exchangeSettings, args);
+ boost::shared_ptr<Exchange> exchange = connection.getBroker().createExchange(name, exchangeType, isDurable(), autodelete, alternateExchange,
+ args, connection.getUserId(), connection.getId()).first;
+ result.second = connection.getTopics().declare(connection.getBroker(), name, exchange, topicSettings);
+ return result;
+}
+
+boost::shared_ptr<qpid::management::ManagementObject> TopicPolicy::GetManagementObject() const
+{
+ return policy;
+}
+
+boost::shared_ptr<NodePolicy> NodePolicyRegistry::createQueuePolicy(Broker& broker, const std::string& name, const qpid::types::Variant::Map& properties)
+{
+ boost::shared_ptr<NodePolicy> nodePolicy(new QueuePolicy(broker, name, properties));
+ add(nodePolicy);
+ return nodePolicy;
+}
+
+boost::shared_ptr<NodePolicy> NodePolicyRegistry::createTopicPolicy(Broker& broker, const std::string& name, const qpid::types::Variant::Map& properties)
+{
+ boost::shared_ptr<NodePolicy> nodePolicy(new TopicPolicy(broker, name, properties));
+ add(nodePolicy);
+ return nodePolicy;
+}
+
+boost::shared_ptr<NodePolicy> NodePolicyRegistry::createNodePolicy(Broker& broker, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties)
+{
+ if (type == QUEUE_POLICY) {
+ return createQueuePolicy(broker, name, properties);
+ } else if (type == TOPIC_POLICY) {
+ return createTopicPolicy(broker, name, properties);
+ } else {
+ return boost::shared_ptr<NodePolicy>();
+ }
+}
+
+bool NodePolicyRegistry::createObject(Broker& broker, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties,
+ const std::string& /*userId*/, const std::string& /*connectionId*/)
+{
+ boost::shared_ptr<NodePolicy> nodePolicy = createNodePolicy(broker, type, name, properties);
+ if (nodePolicy) {
+ if (nodePolicy->isDurable()) broker.getStore().create(*nodePolicy);
+ return true;
+ } else {
+ return false;
+ }
+}
+bool NodePolicyRegistry::deleteObject(Broker& broker, const std::string& type, const std::string& name, const qpid::types::Variant::Map&,
+ const std::string& /*userId*/, const std::string& /*connectionId*/)
+{
+ if (type == QUEUE_POLICY || type == TOPIC_POLICY) {
+ boost::shared_ptr<NodePolicy> nodePolicy = remove(name, type);
+ if (nodePolicy) {
+ if (nodePolicy->isDurable()) broker.getStore().destroy(*nodePolicy);
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+}
+bool NodePolicyRegistry::recoverObject(Broker& broker, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties,
+ uint64_t persistenceId)
+{
+
+ boost::shared_ptr<NodePolicy> nodePolicy = createNodePolicy(broker, type, name, properties);
+ if (nodePolicy) {
+ nodePolicy->setPersistenceId(persistenceId);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void NodePolicyRegistry::add(boost::shared_ptr<NodePolicy> nodePolicy)
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ NodePolicies::const_iterator i = nodePolicies.find(nodePolicy->getName());
+ if (i == nodePolicies.end()) {
+ nodePolicies.insert(NodePolicies::value_type(nodePolicy->getName(), nodePolicy));
+ } else {
+ if (i->second->getType() != nodePolicy->getType()) {
+ throw qpid::types::Exception(QPID_MSG("Cannot create object of type " << nodePolicy->getType() << " with key "
+ << nodePolicy->getName() << " as an object of type " << i->second->getType() << " already exists with the same key"));
+ } else {
+ throw qpid::types::Exception(QPID_MSG("An object of type " << nodePolicy->getType() << " with key " << nodePolicy->getName() << " already exists"));
+ }
+ }
+}
+boost::shared_ptr<NodePolicy> NodePolicyRegistry::remove(const std::string& pattern, const std::string& type)
+{
+ boost::shared_ptr<NodePolicy> result;
+ qpid::sys::Mutex::ScopedLock l(lock);
+ NodePolicies::iterator i = nodePolicies.find(pattern);
+ if (i != nodePolicies.end()) {
+ if (i->second->getType() != type) {
+ throw qpid::types::Exception(QPID_MSG("Object with key " << i->first << " is of type " << i->second->getType() << " not " << type));
+ }
+ result = i->second;
+ nodePolicies.erase(i);
+ }
+ return result;
+}
+boost::shared_ptr<NodePolicy> NodePolicyRegistry::get(const std::string& pattern)
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ NodePolicies::const_iterator i = nodePolicies.find(pattern);
+ if (i == nodePolicies.end()) {
+ return boost::shared_ptr<NodePolicy>();
+ } else {
+ return i->second;
+ }
+}
+
+boost::shared_ptr<NodePolicy> NodePolicyRegistry::match(const std::string& name)
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ boost::shared_ptr<NodePolicy> best;
+ for (NodePolicies::const_iterator i = nodePolicies.begin(); i != nodePolicies.end(); ++i) {
+ //where multiple policies match, pick the one with the longest
+ //pattern as a crude guesstimate of the more specific one
+ if (i->second->match(name) && (!best || i->first.size() > best->getPattern().size())) {
+ best = i->second;
+ }
+ }
+ return best;
+}
+
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/NodePolicy.h b/qpid/cpp/src/qpid/broker/amqp/NodePolicy.h
new file mode 100644
index 0000000000..d6e987d85f
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/NodePolicy.h
@@ -0,0 +1,117 @@
+#ifndef QPID_BROKER_AMQP_NODEPOLICY_H
+#define QPID_BROKER_AMQP_NODEPOLICY_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/ObjectFactory.h"
+#include "qpid/broker/PersistableObject.h"
+#include "qpid/broker/QueueSettings.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/regex.h"
+#include "qpid/types/Variant.h"
+#include "qpid/management/Manageable.h"
+#include "qmf/org/apache/qpid/broker/QueuePolicy.h"
+#include "qmf/org/apache/qpid/broker/TopicPolicy.h"
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace broker {
+class Broker;
+class Queue;
+namespace amqp {
+class Connection;
+class Topic;
+
+/**
+ * Policy for creation of nodes 'on-demand'
+ */
+class NodePolicy : public PersistableObject, public management::Manageable
+{
+ public:
+ NodePolicy(const std::string& type, const std::string& ptrn, const qpid::types::Variant::Map& props);
+ virtual ~NodePolicy();
+ const std::string& getPattern() const;
+ bool match(const std::string&) const;
+ bool isDurable() const;
+ virtual std::pair<boost::shared_ptr<Queue>, boost::shared_ptr<Topic> > create(const std::string&, Connection&) = 0;
+ virtual boost::shared_ptr<qpid::management::ManagementObject> GetManagementObject() const = 0;
+ protected:
+ NodePolicy(Broker&, const std::string& type, const std::string& pattern, const qpid::types::Variant::Map& properties);
+ const std::string pattern;
+ bool durable;
+ std::string alternateExchange;
+ qpid::sys::regex compiled;
+};
+
+class QueuePolicy : public NodePolicy
+{
+ public:
+ QueuePolicy(Broker&, const std::string& pattern, const qpid::types::Variant::Map& properties);
+ ~QueuePolicy();
+ std::pair<boost::shared_ptr<Queue>, boost::shared_ptr<Topic> > create(const std::string&, Connection&);
+ boost::shared_ptr<qpid::management::ManagementObject> GetManagementObject() const;
+ private:
+ qpid::broker::QueueSettings queueSettings;
+ qmf::org::apache::qpid::broker::QueuePolicy::shared_ptr policy;
+};
+
+class TopicPolicy : public NodePolicy
+{
+ public:
+ TopicPolicy(Broker&, const std::string& pattern, const qpid::types::Variant::Map& properties);
+ ~TopicPolicy();
+ std::pair<boost::shared_ptr<Queue>, boost::shared_ptr<Topic> > create(const std::string&, Connection&);
+ boost::shared_ptr<qpid::management::ManagementObject> GetManagementObject() const;
+ private:
+ qpid::types::Variant::Map topicSettings;
+ std::string exchangeType;
+ bool autodelete;
+ qpid::types::Variant::Map exchangeSettings;
+ qmf::org::apache::qpid::broker::TopicPolicy::shared_ptr policy;
+};
+
+class NodePolicyRegistry : public ObjectFactory
+{
+ public:
+ bool createObject(Broker&, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties,
+ const std::string& userId, const std::string& connectionId);
+ bool deleteObject(Broker&, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties,
+ const std::string& userId, const std::string& connectionId);
+ bool recoverObject(Broker&, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties,
+ uint64_t persistenceId);
+
+ boost::shared_ptr<NodePolicy> match(const std::string& name);
+ boost::shared_ptr<NodePolicy> createQueuePolicy(Broker&, const std::string& name, const qpid::types::Variant::Map& properties);
+ boost::shared_ptr<NodePolicy> createTopicPolicy(Broker&, const std::string& name, const qpid::types::Variant::Map& properties);
+ private:
+ typedef std::map<std::string, boost::shared_ptr<NodePolicy> > NodePolicies;
+ qpid::sys::Mutex lock;
+ NodePolicies nodePolicies;
+
+ boost::shared_ptr<NodePolicy> createNodePolicy(Broker&, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties);
+ void add(boost::shared_ptr<NodePolicy> nodePolicy);
+ boost::shared_ptr<NodePolicy> remove(const std::string& pattern, const std::string& type);
+ boost::shared_ptr<NodePolicy> get(const std::string& pattern);
+};
+
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP_NODEPOLICY_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/NodeProperties.cpp b/qpid/cpp/src/qpid/broker/amqp/NodeProperties.cpp
new file mode 100644
index 0000000000..500a0c042d
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/NodeProperties.cpp
@@ -0,0 +1,388 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/amqp/NodeProperties.h"
+#include "qpid/broker/amqp/DataReader.h"
+#include "qpid/broker/Exchange.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/QueueSettings.h"
+#include "qpid/amqp/CharSequence.h"
+#include "qpid/amqp/Descriptor.h"
+#include "qpid/amqp/descriptors.h"
+#include "qpid/amqp_0_10/Codecs.h"
+#include "qpid/types/encodings.h"
+#include "qpid/types/Variant.h"
+#include "qpid/broker/QueueSettings.h"
+#include "qpid/log/Statement.h"
+extern "C" {
+#include <proton/engine.h>
+}
+
+using qpid::amqp::CharSequence;
+using qpid::amqp::Descriptor;
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+namespace {
+//distribution modes:
+const std::string MOVE("move");
+const std::string COPY("copy");
+const std::string SUPPORTED_DIST_MODES("supported-dist-modes");
+const std::string LIFETIME_POLICY("lifetime-policy");
+
+//AMQP 0-10 standard parameters:
+const std::string DURABLE("durable");
+const std::string EXCLUSIVE("exclusive");
+const std::string AUTO_DELETE("auto-delete");
+const std::string ALTERNATE_EXCHANGE("alternate-exchange");
+const std::string EXCHANGE_TYPE("exchange-type");
+const std::string EMPTY;
+
+pn_bytes_t convert(const std::string& s)
+{
+ pn_bytes_t result;
+ result.start = const_cast<char*>(s.data());
+ result.size = s.size();
+ return result;
+}
+
+bool getLifetimePolicy(const Descriptor& d, QueueSettings::LifetimePolicy& policy)
+{
+ if (d.match(qpid::amqp::lifetime_policy::DELETE_ON_CLOSE_SYMBOL, qpid::amqp::lifetime_policy::DELETE_ON_CLOSE_CODE)) {
+ policy = QueueSettings::DELETE_ON_CLOSE;
+ return true;
+ } else if (d.match(qpid::amqp::lifetime_policy::DELETE_ON_NO_LINKS_SYMBOL, qpid::amqp::lifetime_policy::DELETE_ON_NO_LINKS_CODE)) {
+ policy = QueueSettings::DELETE_IF_UNUSED;
+ return true;
+ } else if (d.match(qpid::amqp::lifetime_policy::DELETE_ON_NO_MESSAGES_SYMBOL, qpid::amqp::lifetime_policy::DELETE_ON_NO_MESSAGES_CODE)) {
+ policy = QueueSettings::DELETE_IF_EMPTY;
+ return true;
+ } else if (d.match(qpid::amqp::lifetime_policy::DELETE_ON_NO_LINKS_OR_MESSAGES_SYMBOL, qpid::amqp::lifetime_policy::DELETE_ON_NO_LINKS_OR_MESSAGES_CODE)) {
+ policy = QueueSettings::DELETE_IF_UNUSED_AND_EMPTY;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool getLifetimeDescriptorSymbol(QueueSettings::LifetimePolicy policy, pn_bytes_t& out)
+{
+ switch (policy) {
+ case QueueSettings::DELETE_ON_CLOSE:
+ out = convert(qpid::amqp::lifetime_policy::DELETE_ON_CLOSE_SYMBOL);
+ return true;
+ case QueueSettings::DELETE_IF_UNUSED:
+ out = convert(qpid::amqp::lifetime_policy::DELETE_ON_NO_LINKS_SYMBOL);
+ return true;
+ case QueueSettings::DELETE_IF_EMPTY:
+ out = convert(qpid::amqp::lifetime_policy::DELETE_ON_NO_MESSAGES_SYMBOL);
+ return true;
+ case QueueSettings::DELETE_IF_UNUSED_AND_EMPTY:
+ out = convert(qpid::amqp::lifetime_policy::DELETE_ON_NO_LINKS_OR_MESSAGES_SYMBOL);
+ return true;
+ default:
+ return false;
+ }
+}
+
+}
+
+NodeProperties::NodeProperties(bool isDynamic) : received(false), queue(true), durable(false), autoDelete(false), exclusive(false),
+ dynamic(isDynamic), exchangeType("topic"), lifetime(QueueSettings::DELETE_IF_UNUSED) {}
+
+void NodeProperties::read(pn_data_t* data)
+{
+ DataReader reader(*this);
+ reader.read(data);
+
+}
+
+bool NodeProperties::wasSpecified(const std::string& key) const
+{
+ return specified.find(key) != specified.end();
+}
+
+void NodeProperties::write(pn_data_t* data, boost::shared_ptr<Queue> node)
+{
+ if (received) {
+ pn_data_put_map(data);
+ pn_data_enter(data);
+ pn_data_put_symbol(data, convert(SUPPORTED_DIST_MODES));
+ pn_data_put_string(data, convert(MOVE));//TODO: should really add COPY as well, since queues can be browsed
+ pn_bytes_t symbol;
+ if ((wasSpecified(AUTO_DELETE) || wasSpecified(LIFETIME_POLICY)) && node->isAutoDelete() && getLifetimeDescriptorSymbol(node->getSettings().lifetime, symbol)) {
+ pn_data_put_symbol(data, convert(LIFETIME_POLICY));
+ pn_data_put_described(data);
+ pn_data_enter(data);
+ pn_data_put_symbol(data, symbol);
+ pn_data_put_list(data);
+ pn_data_exit(data);
+ }
+ if (wasSpecified(DURABLE) && node->isDurable()) {
+ pn_data_put_symbol(data, convert(DURABLE));
+ pn_data_put_bool(data, true);
+ }
+ if (wasSpecified(EXCLUSIVE) && node->hasExclusiveOwner()) {
+ pn_data_put_symbol(data, convert(EXCLUSIVE));
+ pn_data_put_bool(data, true);
+ }
+ if (!alternateExchange.empty() && node->getAlternateExchange()) {
+ pn_data_put_symbol(data, convert(ALTERNATE_EXCHANGE));
+ pn_data_put_string(data, convert(node->getAlternateExchange()->getName()));
+ }
+
+ qpid::types::Variant::Map actual = node->getSettings().asMap();
+ qpid::types::Variant::Map unrecognised;
+ QueueSettings dummy;
+ dummy.populate(actual, unrecognised);
+ for (qpid::types::Variant::Map::const_iterator i = unrecognised.begin(); i != unrecognised.end(); ++i) {
+ actual.erase(i->first);
+ }
+ for (qpid::types::Variant::Map::const_iterator i = properties.begin(); i != properties.end(); ++i) {
+ qpid::types::Variant::Map::const_iterator j = actual.find(i->first);
+ if (j != actual.end()) {
+ pn_data_put_symbol(data, convert(j->first));
+ pn_data_put_string(data, convert(j->second.asString()));
+ }
+ }
+
+ pn_data_exit(data);
+ }
+}
+namespace {
+const std::string QPID_MSG_SEQUENCE("qpid.msg_sequence");
+const std::string QPID_IVE("qpid.ive");
+}
+void NodeProperties::write(pn_data_t* data, boost::shared_ptr<Exchange> node)
+{
+ if (received) {
+ pn_data_put_map(data);
+ pn_data_enter(data);
+ pn_data_put_symbol(data, convert(SUPPORTED_DIST_MODES));
+ pn_data_put_string(data, convert(COPY));
+ if (wasSpecified(DURABLE) && node->isDurable()) {
+ pn_data_put_symbol(data, convert(DURABLE));
+ pn_data_put_bool(data, true);
+ }
+ if (!exchangeType.empty()) {
+ pn_data_put_symbol(data, convert(EXCHANGE_TYPE));
+ pn_data_put_string(data, convert(node->getType()));
+ }
+ if (!alternateExchange.empty() && node->getAlternate()) {
+ pn_data_put_symbol(data, convert(ALTERNATE_EXCHANGE));
+ pn_data_put_string(data, convert(node->getAlternate()->getName()));
+ }
+ if (wasSpecified(AUTO_DELETE)) {
+ pn_data_put_symbol(data, convert(AUTO_DELETE));
+ pn_data_put_bool(data, node->isAutoDelete());
+ }
+
+ for (qpid::types::Variant::Map::const_iterator i = properties.begin(); i != properties.end(); ++i) {
+ if ((i->first == QPID_MSG_SEQUENCE || i->first == QPID_IVE) && node->getArgs().isSet(i->first)) {
+ pn_data_put_symbol(data, convert(i->first));
+ pn_data_put_bool(data, true);
+ }
+ }
+
+ pn_data_exit(data);
+ }
+}
+
+
+void NodeProperties::process(const std::string& key, const qpid::types::Variant& value, const Descriptor* d)
+{
+ received = true;
+ QPID_LOG(debug, "Processing node property " << key << " = " << value);
+ specified.insert(key);
+ if (key == SUPPORTED_DIST_MODES) {
+ if (value == MOVE) queue = true;
+ else if (value == COPY) queue = false;
+ } else if (key == LIFETIME_POLICY) {
+ if (d) {
+ if (getLifetimePolicy(*d, lifetime)) {
+ autoDelete = true;
+ } else {
+ QPID_LOG(warning, "Unrecognised lifetime policy: " << *d);
+ }
+ }
+ } else if (key == DURABLE) {
+ durable = value;
+ } else if (key == EXCLUSIVE) {
+ exclusive = value;
+ } else if (key == AUTO_DELETE) {
+ autoDelete = value;
+ } else if (key == ALTERNATE_EXCHANGE) {
+ alternateExchange = value.asString();
+ } else if (key == EXCHANGE_TYPE) {
+ exchangeType = value.asString();
+ } else {
+ properties[key] = value;
+ }
+}
+
+bool NodeProperties::onStartListValue(const qpid::amqp::CharSequence& key, uint32_t count, const qpid::amqp::Descriptor* d)
+{
+ QPID_LOG(debug, "NodeProperties::onStartListValue(" << std::string(key.data, key.size) << ", " << count << ", " << d);
+ process(key.str(), qpid::types::Variant(), d);
+ return true;
+}
+
+void NodeProperties::onNullValue(const CharSequence& key, const Descriptor* d)
+{
+ process(key.str(), qpid::types::Variant(), d);
+}
+
+void NodeProperties::onBooleanValue(const CharSequence& key, bool value, const Descriptor* d)
+{
+ process(key.str(), value, d);
+}
+
+void NodeProperties::onUByteValue(const CharSequence& key, uint8_t value, const Descriptor* d)
+{
+ process(key.str(), value, d);
+}
+
+void NodeProperties::onUShortValue(const CharSequence& key, uint16_t value, const Descriptor* d)
+{
+ process(key.str(), value, d);
+}
+
+void NodeProperties::onUIntValue(const CharSequence& key, uint32_t value, const Descriptor* d)
+{
+ process(key.str(), value, d);
+}
+
+void NodeProperties::onULongValue(const CharSequence& key, uint64_t value, const Descriptor* d)
+{
+ process(key.str(), value, d);
+}
+
+void NodeProperties::onByteValue(const CharSequence& key, int8_t value, const Descriptor* d)
+{
+ process(key.str(), value, d);
+}
+
+void NodeProperties::onShortValue(const CharSequence& key, int16_t value, const Descriptor* d)
+{
+ process(key.str(), value, d);
+}
+
+void NodeProperties::onIntValue(const CharSequence& key, int32_t value, const Descriptor* d)
+{
+ process(key.str(), value, d);
+}
+
+void NodeProperties::onLongValue(const CharSequence& key, int64_t value, const Descriptor* d)
+{
+ process(key.str(), value, d);
+}
+
+void NodeProperties::onFloatValue(const CharSequence& key, float value, const Descriptor* d)
+{
+ process(key.str(), value, d);
+}
+
+void NodeProperties::onDoubleValue(const CharSequence& key, double value, const Descriptor* d)
+{
+ process(key.str(), value, d);
+}
+
+void NodeProperties::onUuidValue(const CharSequence& key, const CharSequence& value, const Descriptor* d)
+{
+ process(key.str(), value.str(), d);
+}
+
+void NodeProperties::onTimestampValue(const CharSequence& key, int64_t value, const Descriptor* d)
+{
+ process(key.str(), value, d);
+}
+
+namespace {
+qpid::types::Variant utf8(const std::string& s)
+{
+ qpid::types::Variant v(s);
+ v.setEncoding(qpid::types::encodings::UTF8);
+ return v;
+}
+}
+
+void NodeProperties::onStringValue(const CharSequence& key, const CharSequence& value, const Descriptor* d)
+{
+ process(key.str(), utf8(value.str()), d);
+}
+
+void NodeProperties::onSymbolValue(const CharSequence& key, const CharSequence& value, const Descriptor* d)
+{
+ process(key.str(), utf8(value.str()), d);
+}
+
+QueueSettings NodeProperties::getQueueSettings()
+{
+ //assume autodelete for dynamic nodes unless explicitly requested
+ //otherwise or unless durability is requested
+ QueueSettings settings(durable, autoDelete || (dynamic && !wasSpecified(AUTO_DELETE) && !durable));
+ qpid::types::Variant::Map unused;
+ settings.populate(properties, unused);
+ settings.lifetime = lifetime;
+ qpid::amqp_0_10::translate(unused, settings.storeSettings);
+ return settings;
+}
+
+bool NodeProperties::isQueue() const
+{
+ return queue;
+}
+bool NodeProperties::isDurable() const
+{
+ return durable;
+}
+bool NodeProperties::isExclusive() const
+{
+ return exclusive;
+}
+bool NodeProperties::isAutodelete() const
+{
+ return autoDelete;
+}
+std::string NodeProperties::getExchangeType() const
+{
+ return exchangeType;
+}
+std::string NodeProperties::getSpecifiedExchangeType() const
+{
+ return wasSpecified(EXCHANGE_TYPE) ? exchangeType : EMPTY;
+}
+std::string NodeProperties::getAlternateExchange() const
+{
+ return alternateExchange;
+}
+
+bool NodeProperties::trackControllingLink() const
+{
+ return lifetime == QueueSettings::DELETE_ON_CLOSE || lifetime == QueueSettings::DELETE_IF_EMPTY;
+}
+
+const qpid::types::Variant::Map& NodeProperties::getProperties() const
+{
+ return properties;
+}
+
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/NodeProperties.h b/qpid/cpp/src/qpid/broker/amqp/NodeProperties.h
new file mode 100644
index 0000000000..45a3533cf4
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/NodeProperties.h
@@ -0,0 +1,90 @@
+#ifndef QPID_BROKER_AMQP_NODEPROPERTIES_H
+#define QPID_BROKER_AMQP_NODEPROPERTIES_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/amqp/MapReader.h"
+#include "qpid/types/Variant.h"
+#include "qpid/broker/QueueSettings.h"
+#include <set>
+#include <boost/shared_ptr.hpp>
+
+struct pn_data_t;
+namespace qpid {
+namespace broker {
+class Exchange;
+class Queue;
+struct QueueSettings;
+namespace amqp {
+
+class NodeProperties : public qpid::amqp::MapReader
+{
+ public:
+ NodeProperties(bool isDynamic);
+ void read(pn_data_t*);
+ void write(pn_data_t*,boost::shared_ptr<Queue>);
+ void write(pn_data_t*,boost::shared_ptr<Exchange>);
+ void onNullValue(const qpid::amqp::CharSequence&, const qpid::amqp::Descriptor*);
+ void onBooleanValue(const qpid::amqp::CharSequence&, bool, const qpid::amqp::Descriptor*);
+ void onUByteValue(const qpid::amqp::CharSequence&, uint8_t, const qpid::amqp::Descriptor*);
+ void onUShortValue(const qpid::amqp::CharSequence&, uint16_t, const qpid::amqp::Descriptor*);
+ void onUIntValue(const qpid::amqp::CharSequence&, uint32_t, const qpid::amqp::Descriptor*);
+ void onULongValue(const qpid::amqp::CharSequence&, uint64_t, const qpid::amqp::Descriptor*);
+ void onByteValue(const qpid::amqp::CharSequence&, int8_t, const qpid::amqp::Descriptor*);
+ void onShortValue(const qpid::amqp::CharSequence&, int16_t, const qpid::amqp::Descriptor*);
+ void onIntValue(const qpid::amqp::CharSequence&, int32_t, const qpid::amqp::Descriptor*);
+ void onLongValue(const qpid::amqp::CharSequence&, int64_t, const qpid::amqp::Descriptor*);
+ void onFloatValue(const qpid::amqp::CharSequence&, float, const qpid::amqp::Descriptor*);
+ void onDoubleValue(const qpid::amqp::CharSequence&, double, const qpid::amqp::Descriptor*);
+ void onUuidValue(const qpid::amqp::CharSequence&, const qpid::amqp::CharSequence&, const qpid::amqp::Descriptor*);
+ void onTimestampValue(const qpid::amqp::CharSequence&, int64_t, const qpid::amqp::Descriptor*);
+ void onStringValue(const qpid::amqp::CharSequence&, const qpid::amqp::CharSequence&, const qpid::amqp::Descriptor*);
+ void onSymbolValue(const qpid::amqp::CharSequence&, const qpid::amqp::CharSequence&, const qpid::amqp::Descriptor*);
+ bool onStartListValue(const qpid::amqp::CharSequence&, uint32_t count, const qpid::amqp::Descriptor*);
+ bool isQueue() const;
+ QueueSettings getQueueSettings();
+ bool isDurable() const;
+ bool isExclusive() const;
+ bool isAutodelete() const;
+ std::string getExchangeType() const;
+ std::string getSpecifiedExchangeType() const;
+ std::string getAlternateExchange() const;
+ bool trackControllingLink() const;
+ const qpid::types::Variant::Map& getProperties() const;
+ private:
+ bool received;
+ bool queue;
+ bool durable;
+ bool autoDelete;
+ bool exclusive;
+ bool dynamic;
+ std::string exchangeType;
+ std::string alternateExchange;
+ qpid::types::Variant::Map properties;
+ QueueSettings::LifetimePolicy lifetime;
+ std::set<std::string> specified;
+
+ void process(const std::string&, const qpid::types::Variant&, const qpid::amqp::Descriptor*);
+ bool wasSpecified(const std::string& key) const;
+};
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP_NODEPROPERTIES_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/Outgoing.cpp b/qpid/cpp/src/qpid/broker/amqp/Outgoing.cpp
new file mode 100644
index 0000000000..f2949c5879
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Outgoing.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/amqp/Outgoing.h"
+#include "qpid/broker/amqp/Exception.h"
+#include "qpid/broker/amqp/Header.h"
+#include "qpid/broker/amqp/Session.h"
+#include "qpid/broker/amqp/Translation.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/Selector.h"
+#include "qpid/broker/TopicKeyNode.h"
+#include "qpid/sys/OutputControl.h"
+#include "qpid/amqp/descriptors.h"
+#include "qpid/amqp/Descriptor.h"
+#include "qpid/amqp/MessageEncoder.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/log/Statement.h"
+#include "config.h"
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+
+Outgoing::Outgoing(Broker& broker, Session& parent, const std::string& source, const std::string& target, const std::string& name)
+ : ManagedOutgoingLink(broker, parent, source, target, name), session(parent) {}
+
+void Outgoing::wakeup()
+{
+ session.wakeup();
+}
+
+namespace {
+bool requested_reliable(pn_link_t* link)
+{
+ return pn_link_remote_snd_settle_mode(link) == PN_SND_UNSETTLED;
+}
+bool requested_unreliable(pn_link_t* link)
+{
+ return pn_link_remote_snd_settle_mode(link) == PN_SND_SETTLED;
+}
+}
+
+OutgoingFromQueue::OutgoingFromQueue(Broker& broker, const std::string& source, const std::string& target, boost::shared_ptr<Queue> q, pn_link_t* l, Session& session,
+ qpid::sys::OutputControl& o, SubscriptionType type, bool e, bool p)
+ : Outgoing(broker, session, source, target, pn_link_name(l)),
+ Consumer(pn_link_name(l), type, target),
+ exclusive(e),
+ isControllingUser(p),
+ queue(q), deliveries(5000), link(l), out(o),
+ current(0),
+ buffer(1024)/*used only for header at present*/,
+ //for exclusive queues, assume unreliable unless reliable is explicitly requested; otherwise assume reliable unless unreliable requested
+ unreliable(exclusive ? !requested_reliable(link) : requested_unreliable(link)),
+ cancelled(false)
+{
+ for (size_t i = 0 ; i < deliveries.capacity(); ++i) {
+ deliveries[i].init(i);
+ }
+ if (isControllingUser) queue->markInUse(true);
+}
+
+void OutgoingFromQueue::init()
+{
+ queue->consume(shared_from_this(), exclusive);//may throw exception
+}
+
+bool OutgoingFromQueue::doWork()
+{
+ QPID_LOG(trace, "Dispatching to " << getName() << ": " << pn_link_credit(link));
+ if (canDeliver()) {
+ try{
+ if (queue->dispatch(shared_from_this())) {
+ return true;
+ } else {
+ pn_link_drained(link);
+ QPID_LOG(trace, "No message available on " << queue->getName());
+ }
+ } catch (const qpid::framing::ResourceDeletedException& e) {
+ throw Exception(qpid::amqp::error_conditions::RESOURCE_DELETED, e.what());
+ }
+ } else {
+ QPID_LOG(trace, "Can't deliver to " << getName() << " from " << queue->getName() << ": " << pn_link_credit(link));
+ }
+ return false;
+}
+
+void OutgoingFromQueue::write(const char* data, size_t size)
+{
+ pn_link_send(link, data, size);
+}
+
+void OutgoingFromQueue::handle(pn_delivery_t* delivery)
+{
+ size_t i = Record::getIndex(pn_delivery_tag(delivery));
+ Record& r = deliveries[i];
+ if (pn_delivery_updated(delivery)) {
+ assert(r.delivery == delivery);
+ r.disposition = pn_delivery_remote_state(delivery);
+
+ std::pair<TxBuffer*,uint64_t> txn = session.getTransactionalState(delivery);
+ if (txn.first) {
+ r.disposition = txn.second;
+ }
+
+ if (!r.disposition && pn_delivery_settled(delivery)) {
+ //if peer has settled without setting state, assume accepted
+ r.disposition = PN_ACCEPTED;
+ }
+ if (r.disposition) {
+ switch (r.disposition) {
+ case PN_ACCEPTED:
+ if (preAcquires()) queue->dequeue(r.cursor, txn.first);
+ outgoingMessageAccepted();
+ break;
+ case PN_REJECTED:
+ if (preAcquires()) queue->reject(r.cursor);
+ outgoingMessageRejected();
+ break;
+ case PN_RELEASED:
+ if (preAcquires()) queue->release(r.cursor, false);//for PN_RELEASED, delivery count should not be incremented
+ outgoingMessageRejected();//TODO: not quite true...
+ break;
+ case PN_MODIFIED:
+ if (preAcquires()) queue->release(r.cursor, pn_disposition_is_failed(pn_delivery_remote(delivery)));
+ //TODO: handle undeliverable-here and message-annotations
+ outgoingMessageRejected();//TODO: not quite true...
+ break;
+ default:
+ QPID_LOG(warning, "Unhandled disposition: " << r.disposition);
+ }
+ //TODO: only settle once any dequeue on store has completed
+ pn_delivery_settle(delivery);
+ r.reset();
+ }
+ }
+}
+
+bool OutgoingFromQueue::canDeliver()
+{
+ return deliveries[current].delivery == 0 && pn_link_credit(link);
+}
+
+void OutgoingFromQueue::detached(bool closed)
+{
+ QPID_LOG(debug, "Detaching outgoing link " << getName() << " from " << queue->getName());
+ queue->cancel(shared_from_this());
+ //TODO: release in a clearer order?
+ for (size_t i = 0 ; i < deliveries.capacity(); ++i) {
+ if (deliveries[i].msg) queue->release(deliveries[i].cursor, true);
+ }
+ if (exclusive) {
+ queue->releaseExclusiveOwnership(closed);
+ } else if (isControllingUser) {
+ queue->releaseFromUse(true);
+ }
+ cancelled = true;
+}
+
+OutgoingFromQueue::~OutgoingFromQueue()
+{
+ if (!cancelled && isControllingUser) queue->releaseFromUse(true);
+}
+
+//Consumer interface:
+bool OutgoingFromQueue::deliver(const QueueCursor& cursor, const qpid::broker::Message& msg)
+{
+ Record& r = deliveries[current++];
+ if (current >= deliveries.capacity()) current = 0;
+ r.cursor = cursor;
+ r.msg = msg;
+ r.delivery = pn_delivery(link, r.tag);
+ //write header
+ qpid::amqp::MessageEncoder encoder(&buffer[0], buffer.size());
+ encoder.writeHeader(Header(r.msg));
+ write(&buffer[0], encoder.getPosition());
+ Translation t(r.msg);
+ t.write(*this);
+ if (pn_link_advance(link)) {
+ if (unreliable) pn_delivery_settle(r.delivery);
+ outgoingMessageSent();
+ QPID_LOG(debug, "Sent message " << r.msg.getSequence() << " from " << queue->getName() << ", index=" << r.index);
+ } else {
+ QPID_LOG(error, "Failed to send message " << r.msg.getSequence() << " from " << queue->getName() << ", index=" << r.index);
+ }
+ if (unreliable) {
+ if (preAcquires()) queue->dequeue(0, r.cursor);
+ r.reset();
+ }
+ QPID_LOG(debug, "Requested delivery of " << r.msg.getSequence() << " from " << queue->getName() << ", index=" << r.index);
+ return true;
+}
+
+void OutgoingFromQueue::notify()
+{
+ QPID_LOG(trace, "Notification received for " << queue->getName());
+ out.activateOutput();
+}
+
+bool OutgoingFromQueue::accept(const qpid::broker::Message&)
+{
+ return true;
+}
+
+void OutgoingFromQueue::setSubjectFilter(const std::string& f)
+{
+ subjectFilter = f;
+}
+
+void OutgoingFromQueue::setSelectorFilter(const std::string& f)
+{
+ selector.reset(new Selector(f));
+}
+
+namespace {
+
+bool match(TokenIterator& filter, TokenIterator& target)
+{
+ bool wild = false;
+ while (!filter.finished())
+ {
+ if (filter.match1('*')) {
+ if (target.finished()) return false;
+ //else move to next word in filter target
+ filter.next();
+ target.next();
+ } else if (filter.match1('#')) {
+ // i.e. filter word is '#' which can match a variable number of words in the target
+ filter.next();
+ if (filter.finished()) return true;
+ else if (target.finished()) return false;
+ wild = true;
+ } else {
+ //filter word needs to match target exactly
+ if (target.finished()) return false;
+ std::string word;
+ target.pop(word);
+ if (filter.match(word)) {
+ wild = false;
+ filter.next();
+ } else if (!wild) {
+ return false;
+ }
+ }
+ }
+ return target.finished();
+}
+bool match(const std::string& filter, const std::string& target)
+{
+ TokenIterator lhs(filter);
+ TokenIterator rhs(target);
+ return match(lhs, rhs);
+}
+}
+
+bool OutgoingFromQueue::filter(const qpid::broker::Message& m)
+{
+ return (subjectFilter.empty() || subjectFilter == m.getRoutingKey() || match(subjectFilter, m.getRoutingKey()))
+ && (!selector || selector->filter(m));
+}
+
+void OutgoingFromQueue::cancel() {}
+
+void OutgoingFromQueue::acknowledged(const qpid::broker::DeliveryRecord&) {}
+
+qpid::broker::OwnershipToken* OutgoingFromQueue::getSession()
+{
+ return 0;
+}
+
+OutgoingFromQueue::Record::Record() : delivery(0), disposition(0), index(0)
+{
+#ifdef NO_PROTON_DELIVERY_TAG_T
+ tag.start = tagData;
+#else
+ tag.bytes = tagData;
+#endif
+ tag.size = TAG_WIDTH;
+}
+void OutgoingFromQueue::Record::init(size_t i)
+{
+ index = i;
+ qpid::framing::Buffer buffer(tagData, tag.size);
+ assert(index <= std::numeric_limits<uint32_t>::max());
+ buffer.putLong(index);
+}
+void OutgoingFromQueue::Record::reset()
+{
+ cursor = QueueCursor();
+ msg = qpid::broker::Message();
+ delivery = 0;
+ disposition = 0;
+}
+
+size_t OutgoingFromQueue::Record::getIndex(pn_delivery_tag_t t)
+{
+ assert(t.size == TAG_WIDTH);
+#ifdef NO_PROTON_DELIVERY_TAG_T
+ qpid::framing::Buffer buffer(const_cast<char*>(t.start)/*won't ever be written to*/, t.size);
+#else
+ qpid::framing::Buffer buffer(const_cast<char*>(t.bytes)/*won't ever be written to*/, t.size);
+#endif
+ return (size_t) buffer.getLong();
+}
+
+boost::shared_ptr<Queue> OutgoingFromQueue::getExclusiveSubscriptionQueue(Outgoing* o)
+{
+ OutgoingFromQueue* s = dynamic_cast<OutgoingFromQueue*>(o);
+ if (s && s->exclusive) return s->queue;
+ else return boost::shared_ptr<Queue>();
+}
+
+
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/Outgoing.h b/qpid/cpp/src/qpid/broker/amqp/Outgoing.h
new file mode 100644
index 0000000000..d3825d0894
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Outgoing.h
@@ -0,0 +1,151 @@
+#ifndef QPID_BROKER_AMQP1_OUTGOING_H
+#define QPID_BROKER_AMQP1_OUTGOING_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/amqp/Message.h"
+#include "qpid/broker/amqp/ManagedOutgoingLink.h"
+#include "qpid/broker/Consumer.h"
+
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+extern "C" {
+#include <proton/engine.h>
+}
+
+namespace qpid {
+namespace sys {
+class OutputControl;
+}
+namespace broker {
+class Broker;
+class Queue;
+class Selector;
+namespace amqp {
+class Session;
+template <class T>
+class CircularArray
+{
+ public:
+ CircularArray(size_t l) : limit(l), data(new T[limit]) {}
+ T& operator[](size_t i) { return data[i]; }
+ size_t capacity() { return limit; }
+ ~CircularArray() { delete [] data; }
+ private:
+ const size_t limit;
+ T* const data;
+ size_t next;
+};
+
+class Outgoing : public ManagedOutgoingLink
+{
+ public:
+ Outgoing(Broker& broker, Session& parent, const std::string& source, const std::string& target, const std::string& name);
+ virtual void setSubjectFilter(const std::string&) = 0;
+ virtual void setSelectorFilter(const std::string&) = 0;
+ virtual void init() = 0;
+ /**
+ * Allows the link to initiate any outgoing transfers
+ */
+ virtual bool doWork() = 0;
+ /**
+ * Signals that this link has been detached
+ */
+ virtual void detached(bool closed) = 0;
+ /**
+ * Called when a delivery is writable
+ */
+ virtual void handle(pn_delivery_t* delivery) = 0;
+ void wakeup();
+ virtual ~Outgoing() {}
+ protected:
+ Session& session;
+};
+
+/**
+ * Logic for handling an outgoing link from a queue (even if it is a
+ * subscription pseduo-queue created by the broker)
+ */
+class OutgoingFromQueue : public Outgoing, public qpid::broker::Consumer, public boost::enable_shared_from_this<OutgoingFromQueue>
+{
+ public:
+ OutgoingFromQueue(Broker&, const std::string& source, const std::string& target, boost::shared_ptr<Queue> q, pn_link_t* l, Session&,
+ qpid::sys::OutputControl& o, SubscriptionType type, bool exclusive, bool isControllingUser);
+ ~OutgoingFromQueue();
+ void setSubjectFilter(const std::string&);
+ void setSelectorFilter(const std::string&);
+ void init();
+ bool doWork();
+ void write(const char* data, size_t size);
+ void handle(pn_delivery_t* delivery);
+ bool canDeliver();
+ void detached(bool closed);
+
+ //Consumer interface:
+ bool deliver(const QueueCursor& cursor, const qpid::broker::Message& msg);
+ void notify();
+ bool accept(const qpid::broker::Message&);
+ bool filter(const qpid::broker::Message&);
+ void cancel();
+ void acknowledged(const qpid::broker::DeliveryRecord&);
+ qpid::broker::OwnershipToken* getSession();
+ static boost::shared_ptr<Queue> getExclusiveSubscriptionQueue(Outgoing*);
+
+ private:
+
+ struct Record
+ {
+ QueueCursor cursor;
+ qpid::broker::Message msg;
+ pn_delivery_t* delivery;
+ int disposition;
+ size_t index;
+ pn_delivery_tag_t tag;
+ //The delivery tag is a 4 byte value representing the
+ //index. It is encoded separately to avoid alignment issues.
+ //The number of deliveries held here is always strictly
+ //bounded, so 4 bytes is more than enough.
+ static const size_t TAG_WIDTH = sizeof(uint32_t);
+ char tagData[TAG_WIDTH];
+
+ Record();
+ void init(size_t i);
+ void reset();
+ static size_t getIndex(pn_delivery_tag_t);
+ };
+
+ const bool exclusive;
+ const bool isControllingUser;
+ boost::shared_ptr<Queue> queue;
+ CircularArray<Record> deliveries;
+ pn_link_t* link;
+ qpid::sys::OutputControl& out;
+ size_t current;
+ std::vector<char> buffer;
+ std::string subjectFilter;
+ boost::scoped_ptr<Selector> selector;
+ bool unreliable;
+ bool cancelled;
+};
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP1_OUTGOING_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/ProtocolPlugin.cpp b/qpid/cpp/src/qpid/broker/amqp/ProtocolPlugin.cpp
new file mode 100644
index 0000000000..abc26876b5
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/ProtocolPlugin.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/Plugin.h"
+
+#include "qpid/Options.h"
+#include "qpid/SaslFactory.h"
+#include "qpid/NullSaslServer.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/Message.h"
+#include "qpid/broker/Protocol.h"
+#include "qpid/broker/RecoverableMessage.h"
+#include "qpid/broker/RecoverableMessageImpl.h"
+#include "qpid/broker/amqp/Connection.h"
+#include "qpid/broker/amqp/Interconnects.h"
+#include "qpid/broker/amqp/Message.h"
+#include "qpid/broker/amqp/NodePolicy.h"
+#include "qpid/broker/amqp/Sasl.h"
+#include "qpid/broker/amqp/Topic.h"
+#include "qpid/broker/amqp/Translation.h"
+#include "qpid/broker/amqp_0_10/MessageTransfer.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/framing/ProtocolVersion.h"
+#include "qpid/sys/ConnectionCodec.h"
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+
+struct Options : public qpid::Options {
+ std::string domain;
+ std::vector<std::string> queuePatterns;
+ std::vector<std::string> topicPatterns;
+
+ Options() : qpid::Options("AMQP 1.0 Options") {
+ addOptions()
+ ("domain", optValue(domain, "DOMAIN"), "Domain of this broker")
+ ("queue-patterns", optValue(queuePatterns, "PATTERN"), "Pattern for on-demand queues")
+ ("topic-patterns", optValue(topicPatterns, "PATTERN"), "Pattern for on-demand topics");
+ }
+};
+
+class ProtocolImpl : public BrokerContext, public Protocol
+{
+ public:
+ ProtocolImpl(Interconnects* interconnects, TopicRegistry* topics, NodePolicyRegistry* policies, Broker& broker, const std::string& domain)
+ : BrokerContext(broker, *interconnects, *topics, *policies, domain)
+ {
+ interconnects->setContext(*this);
+ broker.getObjectFactoryRegistry().add(interconnects);//registry deletes on shutdown
+ broker.getObjectFactoryRegistry().add(topics);//registry deletes on shutdown
+ broker.getObjectFactoryRegistry().add(policies);//registry deletes on shutdown
+ }
+ qpid::sys::ConnectionCodec* create(const qpid::framing::ProtocolVersion&, qpid::sys::OutputControl&, const std::string&, const qpid::sys::SecuritySettings&);
+ boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer> translate(const qpid::broker::Message&);
+ boost::shared_ptr<RecoverableMessage> recover(qpid::framing::Buffer&);
+ qpid::framing::ProtocolVersion supportedVersion() const;
+ private:
+};
+
+struct ProtocolPlugin : public Plugin
+{
+ Options options;
+ Options* getOptions() { return &options; }
+ NodePolicyRegistry* policies;
+
+ ProtocolPlugin() : policies(0) {}
+
+ void earlyInitialize(Plugin::Target& target)
+ {
+ //need to register protocol before recovery from store
+ broker::Broker* broker = dynamic_cast<qpid::broker::Broker*>(&target);
+ if (broker) {
+ policies = new NodePolicyRegistry();
+ ProtocolImpl* impl = new ProtocolImpl(new Interconnects(), new TopicRegistry(), policies, *broker, options.domain);
+ broker->getProtocolRegistry().add("amqp1.0", impl);//registry deletes on shutdown
+ }
+ }
+
+ void initialize(Plugin::Target& target)
+ {
+ broker::Broker* broker = dynamic_cast<qpid::broker::Broker*>(&target);
+ if (broker) {
+ for (std::vector<std::string>::const_iterator i = options.queuePatterns.begin(); i != options.queuePatterns.end(); ++i) {
+ policies->createQueuePolicy(*broker, *i, qpid::types::Variant::Map());
+ }
+ for (std::vector<std::string>::const_iterator i = options.topicPatterns.begin(); i != options.topicPatterns.end(); ++i) {
+ policies->createTopicPolicy(*broker, *i, qpid::types::Variant::Map());
+ }
+ }
+ }
+};
+
+ProtocolPlugin instance; // Static initialization
+
+qpid::sys::ConnectionCodec* ProtocolImpl::create(const qpid::framing::ProtocolVersion& v, qpid::sys::OutputControl& out, const std::string& id, const qpid::sys::SecuritySettings& external)
+{
+ if (v == qpid::framing::ProtocolVersion(1, 0)) {
+ if (v.getProtocol() == qpid::framing::ProtocolVersion::SASL) {
+ if (getBroker().isAuthenticating()) {
+ QPID_LOG(info, "Using AMQP 1.0 (with SASL layer)");
+ return new qpid::broker::amqp::Sasl(out, id, *this,
+ qpid::SaslFactory::getInstance().createServer(getBroker().getRealm(),getBroker().getSaslServiceName(),getBroker().requireEncrypted(), external));
+ } else {
+ std::auto_ptr<SaslServer> authenticator(new qpid::NullSaslServer(getBroker().getRealm()));
+ QPID_LOG(info, "Using AMQP 1.0 (with dummy SASL layer)");
+ return new qpid::broker::amqp::Sasl(out, id, *this, authenticator);
+ }
+ } else {
+ if (getBroker().isAuthenticating()) {
+ throw qpid::Exception("SASL layer required!");
+ } else {
+ QPID_LOG(info, "Using AMQP 1.0 (no SASL layer)");
+ return new qpid::broker::amqp::Connection(out, id, *this, false, false);
+ }
+ }
+ }
+ return 0;
+}
+
+boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer> ProtocolImpl::translate(const qpid::broker::Message& m)
+{
+ qpid::broker::amqp::Translation t(m, &getBroker());
+ return t.getTransfer();
+}
+
+boost::shared_ptr<RecoverableMessage> ProtocolImpl::recover(qpid::framing::Buffer& buffer)
+{
+ QPID_LOG(debug, "Recovering, checking for 1.0 message format indicator...");
+ uint32_t format = buffer.getLong();
+ if (format == 0) {
+ QPID_LOG(debug, "Recovered message IS in 1.0 format");
+ //this is a 1.0 format message
+ boost::intrusive_ptr<qpid::broker::amqp::Message> m(new qpid::broker::amqp::Message(buffer.available()));
+ m->decodeHeader(buffer);
+ return RecoverableMessage::shared_ptr(new RecoverableMessageImpl(qpid::broker::Message(m, m)));
+ } else {
+ QPID_LOG(debug, "Recovered message is NOT in 1.0 format");
+ return RecoverableMessage::shared_ptr();
+ }
+}
+
+qpid::framing::ProtocolVersion ProtocolImpl::supportedVersion() const
+{
+ return qpid::framing::ProtocolVersion(1,0);
+}
+
+
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/Relay.cpp b/qpid/cpp/src/qpid/broker/amqp/Relay.cpp
new file mode 100644
index 0000000000..587a11466a
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Relay.cpp
@@ -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.
+ *
+ */
+#include "Relay.h"
+#include "qpid/Exception.h"
+#include "qpid/log/Statement.h"
+#include <algorithm>
+#include <string.h>
+#include "config.h"
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+
+Relay::Relay(size_t max_) : credit(0), max(max_), head(0), tail(0), isDetached(false), out(0), in(0) {}
+void Relay::check()
+{
+ if (isDetached) throw qpid::Exception("other end of relay has been detached");
+}
+bool Relay::send(pn_link_t* link)
+{
+ BufferedTransfer* c(0);
+ {
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock);
+ if (head < tail) {
+ c = &buffer[head++];
+ } else {
+ return false;
+ }
+ }
+ c->initOut(link);
+ return true;
+}
+
+BufferedTransfer& Relay::push()
+{
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock);
+ buffer.push_back(BufferedTransfer());
+ return buffer.back();
+}
+
+void Relay::received(pn_link_t* link, pn_delivery_t* delivery)
+{
+ BufferedTransfer& received = push();
+ received.initIn(link, delivery);
+ {
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock);
+ ++tail;
+ }
+ if (out) out->wakeup();
+}
+size_t Relay::size() const
+{
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock);
+ return buffer.size();
+}
+BufferedTransfer& Relay::front()
+{
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock);
+ return buffer.front();
+}
+void Relay::pop()
+{
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock);
+ buffer.pop_front();
+ if (head) --head;
+ if (tail) --tail;
+}
+void Relay::setCredit(int c)
+{
+ credit = c;
+ if (in) in->wakeup();
+}
+
+int Relay::getCredit() const
+{
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(lock);
+ return std::min(credit - size(), max);
+}
+void Relay::attached(Outgoing* o)
+{
+ out = o;
+}
+void Relay::attached(Incoming* i)
+{
+ in = i;
+}
+void Relay::detached(Outgoing*)
+{
+ out = 0;
+ isDetached = true;
+ QPID_LOG(info, "Outgoing link detached from relay [" << this << "]");
+ if (in) in->wakeup();
+}
+void Relay::detached(Incoming*)
+{
+ in = 0;
+ isDetached = true;
+ QPID_LOG(info, "Incoming link detached from relay [" << this << "]");
+ if (out) out->wakeup();
+}
+
+OutgoingFromRelay::OutgoingFromRelay(pn_link_t* l, Broker& broker, Session& parent, const std::string& source,
+ const std::string& target, const std::string& name_, boost::shared_ptr<Relay> r)
+ : Outgoing(broker, parent, source, target, name_), name(name_), link(l), relay(r) {}
+/**
+ * Allows the link to initiate any outgoing transfers
+ */
+bool OutgoingFromRelay::doWork()
+{
+ relay->check();
+ relay->setCredit(pn_link_credit(link));
+ bool worked = relay->send(link);
+ pn_delivery_t *d = pn_link_current(link);
+ if (d && pn_delivery_writable(d)) {
+ handle(d);
+ return true;
+ }
+ return worked;
+}
+/**
+ * Called when a delivery is writable
+ */
+void OutgoingFromRelay::handle(pn_delivery_t* delivery)
+{
+ void* context = pn_delivery_get_context(delivery);
+ BufferedTransfer* transfer = reinterpret_cast<BufferedTransfer*>(context);
+ assert(transfer);
+ if (pn_delivery_writable(delivery)) {
+ if (transfer->write(link)) {
+ outgoingMessageSent();
+ QPID_LOG(debug, "Sent relayed message " << name << " [" << relay.get() << "]");
+ } else {
+ QPID_LOG(error, "Failed to send relayed message " << name << " [" << relay.get() << "]");
+ }
+ }
+ if (pn_delivery_updated(delivery)) {
+ uint64_t d = transfer->updated();
+ switch (d) {
+ case PN_ACCEPTED:
+ outgoingMessageAccepted();
+ break;
+ case PN_REJECTED:
+ case PN_RELEASED://TODO: not quite true...
+ case PN_MODIFIED://TODO: not quite true...
+ outgoingMessageRejected();
+ break;
+ default:
+ QPID_LOG(warning, "Unhandled disposition: " << d);
+ }
+ }
+}
+/**
+ * Signals that this link has been detached
+ */
+void OutgoingFromRelay::detached(bool /*closed*/)
+{
+ relay->detached(this);
+}
+void OutgoingFromRelay::init()
+{
+ relay->attached(this);
+}
+void OutgoingFromRelay::setSubjectFilter(const std::string&)
+{
+ //TODO
+}
+void OutgoingFromRelay::setSelectorFilter(const std::string&)
+{
+ //TODO
+}
+
+IncomingToRelay::IncomingToRelay(pn_link_t* link, Broker& broker, Session& parent, const std::string& source,
+ const std::string& target, const std::string& name, boost::shared_ptr<Relay> r)
+ : Incoming(link, broker, parent, source, target, name), relay(r)
+{
+ relay->attached(this);
+}
+bool IncomingToRelay::settle()
+{
+ bool result(false);
+ while (relay->size() && relay->front().settle()) {
+ result = true;
+ relay->pop();
+ }
+ return result;
+}
+bool IncomingToRelay::doWork()
+{
+ relay->check();
+ bool work(false);
+ if (settle()) work = true;
+ if (Incoming::doWork()) work = true;
+ return work;
+}
+bool IncomingToRelay::haveWork()
+{
+ bool work(false);
+ if (settle()) work = true;
+ if (Incoming::haveWork()) work = true;
+ return work;
+}
+void IncomingToRelay::readable(pn_delivery_t* delivery)
+{
+ relay->received(link, delivery);
+ --window;
+}
+
+uint32_t IncomingToRelay::getCredit()
+{
+ return relay->getCredit();
+}
+
+void IncomingToRelay::detached(bool /*closed*/)
+{
+ relay->detached(this);
+}
+
+BufferedTransfer::BufferedTransfer() : disposition(0) {}
+void BufferedTransfer::initIn(pn_link_t* link, pn_delivery_t* d)
+{
+ in.handle = d;
+ //read in data
+ data.resize(pn_delivery_pending(d));
+ /*ssize_t read = */pn_link_recv(link, &data[0], data.size());
+ pn_link_advance(link);
+
+ //copy delivery tag
+ pn_delivery_tag_t dt = pn_delivery_tag(d);
+ tag.resize(dt.size);
+#ifdef NO_PROTON_DELIVERY_TAG_T
+ ::memmove(&tag[0], dt.start, dt.size);
+#else
+ ::memmove(&tag[0], dt.bytes, dt.size);
+#endif
+
+ //set context
+ pn_delivery_set_context(d, this);
+
+}
+
+bool BufferedTransfer::settle()
+{
+ if (out.settled && !in.settled) {
+ pn_delivery_update(in.handle, disposition);
+ pn_delivery_settle(in.handle);
+ in.settled = true;
+ }
+ return out.settled && in.settled;
+}
+
+void BufferedTransfer::initOut(pn_link_t* link)
+{
+ pn_delivery_tag_t dt;
+#ifdef NO_PROTON_DELIVERY_TAG_T
+ dt.start = &tag[0];
+#else
+ dt.bytes = &tag[0];
+#endif
+ dt.size = tag.size();
+ out.handle = pn_delivery(link, dt);
+ //set context
+ pn_delivery_set_context(out.handle, this);
+}
+
+uint64_t BufferedTransfer::updated()
+{
+ disposition = pn_delivery_remote_state(out.handle);
+ if (disposition) {
+ pn_delivery_settle(out.handle);
+ out.settled = true;
+ }
+ return disposition;
+}
+
+bool BufferedTransfer::write(pn_link_t* link)
+{
+ pn_link_send(link, &data[0], data.size());
+ return pn_link_advance(link);
+}
+Delivery::Delivery() : settled(false), handle(0) {}
+Delivery::Delivery(pn_delivery_t* d) : settled(false), handle(d) {}
+
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/Relay.h b/qpid/cpp/src/qpid/broker/amqp/Relay.h
new file mode 100644
index 0000000000..32f317bfe1
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Relay.h
@@ -0,0 +1,130 @@
+#ifndef QPID_BROKER_AMQP_RELAY_H
+#define QPID_BROKER_AMQP_RELAY_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "Incoming.h"
+#include "Outgoing.h"
+#include "qpid/sys/Mutex.h"
+extern "C" {
+#include <proton/engine.h>
+}
+#include <deque>
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+
+struct Delivery
+{
+ bool settled;
+ pn_delivery_t* handle;
+
+ Delivery();
+ Delivery(pn_delivery_t* d);
+};
+
+class BufferedTransfer
+{
+ public:
+ BufferedTransfer();
+ void initIn(pn_link_t* link, pn_delivery_t* d);
+ bool settle();
+ void initOut(pn_link_t* link);
+ uint64_t updated();
+ bool write(pn_link_t*);
+ private:
+ std::vector<char> data;
+ Delivery in;
+ Delivery out;
+ pn_delivery_tag_t dt;
+ std::vector<char> tag;
+ uint64_t disposition;
+};
+
+/**
+ *
+ */
+class Relay
+{
+ public:
+ Relay(size_t max);
+ void check();
+ size_t size() const;
+ BufferedTransfer& front();
+ void pop();
+ bool send(pn_link_t*);
+ void received(pn_link_t* link, pn_delivery_t* delivery);
+ int getCredit() const;
+ void setCredit(int);
+ void attached(Outgoing*);
+ void attached(Incoming*);
+ void detached(Outgoing*);
+ void detached(Incoming*);
+ private:
+ std::deque<BufferedTransfer> buffer;//TODO: optimise by replacing with simple circular array
+ int credit;//issued by outgoing peer, decremented everytime we send a message on outgoing link
+ size_t max;
+ size_t head;
+ size_t tail;
+ bool isDetached;
+ Outgoing* out;
+ Incoming* in;
+ mutable qpid::sys::Mutex lock;
+
+ BufferedTransfer& push();
+};
+
+class OutgoingFromRelay : public Outgoing
+{
+ public:
+ OutgoingFromRelay(pn_link_t*, Broker&, Session&, const std::string& source,
+ const std::string& target, const std::string& name, boost::shared_ptr<Relay>);
+ bool doWork();
+ void handle(pn_delivery_t* delivery);
+ void detached(bool closed);
+ void init();
+ void setSubjectFilter(const std::string&);
+ void setSelectorFilter(const std::string&);
+ private:
+ const std::string name;
+ pn_link_t* link;
+ boost::shared_ptr<Relay> relay;
+};
+
+class IncomingToRelay : public Incoming
+{
+ public:
+ IncomingToRelay(pn_link_t*, Broker&, Session&, const std::string& source,
+ const std::string& target, const std::string& name, boost::shared_ptr<Relay> r);
+ bool settle();
+ bool doWork();
+ bool haveWork();
+ void detached(bool closed);
+ void readable(pn_delivery_t* delivery);
+ uint32_t getCredit();
+ private:
+ boost::shared_ptr<Relay> relay;
+};
+
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP_RELAY_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/Sasl.cpp b/qpid/cpp/src/qpid/broker/amqp/Sasl.cpp
new file mode 100644
index 0000000000..907e04f5ed
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Sasl.cpp
@@ -0,0 +1,167 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/amqp/Sasl.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/log/Statement.h"
+#include "qpid/framing/ProtocolInitiation.h"
+#include "qpid/sys/OutputControl.h"
+#include "qpid/sys/SecurityLayer.h"
+#include <boost/format.hpp>
+#include <vector>
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+
+Sasl::Sasl(qpid::sys::OutputControl& o, const std::string& id, BrokerContext& context, std::auto_ptr<qpid::SaslServer> auth)
+ : qpid::amqp::SaslServer(id), out(o), connection(out, id, context, true, false),
+ authenticator(auth),
+ state(INCOMPLETE), writeHeader(true), haveOutput(true)
+{
+ out.activateOutput();
+ mechanisms(authenticator->getMechanisms());
+}
+
+Sasl::~Sasl() {}
+
+size_t Sasl::decode(const char* buffer, size_t size)
+{
+ if (state == AUTHENTICATED) {
+ if (securityLayer.get()) return securityLayer->decode(buffer, size);
+ else return connection.decode(buffer, size);
+ } else if (state == INCOMPLETE && size) {
+ size_t decoded = read(buffer, size);
+ QPID_LOG(trace, id << " Sasl::decode(" << size << "): " << decoded);
+ return decoded;
+ } else {
+ return 0;
+ }
+}
+
+size_t Sasl::encode(char* buffer, size_t size)
+{
+ if (state == AUTHENTICATED) {
+ if (securityLayer.get()) return securityLayer->encode(buffer, size);
+ else return connection.encode(buffer, size);
+ } else {
+ size_t encoded = 0;
+ if (writeHeader) {
+ encoded += writeProtocolHeader(buffer, size);
+ if (!encoded) return 0;
+ writeHeader = false;
+ }
+ if (encoded < size) {
+ encoded += write(buffer + encoded, size - encoded);
+ }
+ if (state == SUCCESS_PENDING) {
+ state = AUTHENTICATED;
+ } else if (state == FAILURE_PENDING) {
+ state = FAILED;
+ } else {
+ haveOutput = (encoded == size);
+ }
+ QPID_LOG(trace, id << " Sasl::encode(" << size << "): " << encoded);
+ return encoded;
+ }
+}
+
+bool Sasl::canEncode()
+{
+ if (state == AUTHENTICATED) {
+ if (securityLayer.get()) return securityLayer->canEncode();
+ else return connection.canEncode();
+ } else {
+ return haveOutput;
+ }
+}
+
+void Sasl::closed()
+{
+ if (state == AUTHENTICATED) {
+ connection.closed();
+ } else {
+ QPID_LOG(info, id << " Connection closed prior to authentication completing");
+ state = FAILED;
+ }
+}
+bool Sasl::isClosed() const
+{
+ if (state == AUTHENTICATED) {
+ return connection.isClosed();
+ } else {
+ return state == FAILED;
+ }
+}
+
+framing::ProtocolVersion Sasl::getVersion() const
+{
+ return connection.getVersion();
+}
+namespace {
+const std::string EMPTY;
+}
+
+void Sasl::init(const std::string& mechanism, const std::string* response, const std::string* /*hostname*/)
+{
+ QPID_LOG_CAT(debug, protocol, id << " Received SASL-INIT(" << mechanism << ", " << (response ? *response : EMPTY) << ")");
+ //TODO: what should we do with hostname here?
+ std::string c;
+ respond(authenticator->start(mechanism, response, c), c);
+ connection.setSaslMechanism(mechanism);
+}
+
+void Sasl::response(const std::string* r)
+{
+ QPID_LOG_CAT(debug, protocol, id << " Received SASL-RESPONSE(" << (r ? *r : EMPTY) << ")");
+ std::string c;
+ respond(authenticator->step(r, c), c);
+}
+
+void Sasl::respond(qpid::SaslServer::Status status, const std::string& chllnge)
+{
+ switch (status) {
+ case qpid::SaslServer::OK:
+ connection.setUserId(authenticator->getUserid());
+ completed(true);
+ //can't set authenticated & failed until we have actually sent the outcome
+ state = SUCCESS_PENDING;
+ securityLayer = authenticator->getSecurityLayer(65535);
+ if (securityLayer.get()) {
+ QPID_LOG_CAT(info, security, id << " Security layer installed");
+ securityLayer->init(&connection);
+ connection.setSaslSsf(securityLayer->getSsf());
+ }
+ QPID_LOG_CAT(info, security, id << " Authenticated as " << authenticator->getUserid());
+ break;
+ case qpid::SaslServer::FAIL:
+ completed(false);
+ state = FAILURE_PENDING;
+ QPID_LOG_CAT(info, security, id << " Failed to authenticate");
+ break;
+ case qpid::SaslServer::CHALLENGE:
+ challenge(&chllnge);
+ QPID_LOG_CAT(info, security, id << " Challenge issued");
+ break;
+ }
+ haveOutput = true;
+ out.activateOutput();
+}
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/Sasl.h b/qpid/cpp/src/qpid/broker/amqp/Sasl.h
new file mode 100644
index 0000000000..859cfb2d34
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Sasl.h
@@ -0,0 +1,71 @@
+#ifndef QPID_BROKER_AMQP_SASL_H
+#define QPID_BROKER_AMQP_SASL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/amqp/Connection.h"
+#include "qpid/SaslServer.h"
+#include "qpid/amqp/SaslServer.h"
+#include "qpid/sys/ConnectionCodec.h"
+#include <memory>
+namespace qpid {
+namespace sys {
+class SecurityLayer;
+}
+namespace broker {
+namespace amqp {
+/**
+ * An AMQP 1.0 SASL Security Layer for authentication and optionally
+ * encryption.
+ */
+class Sasl : public sys::ConnectionCodec, qpid::amqp::SaslServer
+{
+ public:
+ Sasl(qpid::sys::OutputControl& out, const std::string& id, BrokerContext& context, std::auto_ptr<qpid::SaslServer> authenticator);
+ ~Sasl();
+
+ size_t decode(const char* buffer, size_t size);
+ size_t encode(char* buffer, size_t size);
+ bool canEncode();
+
+ void closed();
+ bool isClosed() const;
+
+ framing::ProtocolVersion getVersion() const;
+ private:
+ qpid::sys::OutputControl& out;
+ Connection connection;
+ std::auto_ptr<qpid::sys::SecurityLayer> securityLayer;
+ std::auto_ptr<qpid::SaslServer> authenticator;
+ enum {
+ INCOMPLETE, SUCCESS_PENDING, FAILURE_PENDING, AUTHENTICATED, FAILED
+ } state;
+
+ bool writeHeader;
+ bool haveOutput;
+
+ void init(const std::string& mechanism, const std::string* response, const std::string* hostname);
+ void response(const std::string*);
+ void respond(qpid::SaslServer::Status status, const std::string& challenge);
+};
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP_SASL_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/SaslClient.cpp b/qpid/cpp/src/qpid/broker/amqp/SaslClient.cpp
new file mode 100644
index 0000000000..4317d18525
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/SaslClient.cpp
@@ -0,0 +1,181 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "SaslClient.h"
+#include "Interconnect.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/sys/OutputControl.h"
+#include "qpid/sys/SecurityLayer.h"
+#include "qpid/log/Statement.h"
+#include "qpid/Sasl.h"
+#include "qpid/SaslFactory.h"
+#include "qpid/StringUtils.h"
+#include <sstream>
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+
+SaslClient::SaslClient(qpid::sys::OutputControl& out_, const std::string& id, boost::shared_ptr<Interconnect> c, std::auto_ptr<qpid::Sasl> s,
+ const std::string& hostname_, const std::string& mechs, const qpid::sys::SecuritySettings& t)
+ : qpid::amqp::SaslClient(id), out(out_), connection(c), sasl(s),
+ hostname(hostname_), allowedMechanisms(mechs), transport(t), readHeader(true), writeHeader(false), haveOutput(false), initialised(false), state(NONE) {}
+
+SaslClient::~SaslClient()
+{
+ connection->transportDeleted();
+}
+
+std::size_t SaslClient::decode(const char* buffer, std::size_t size)
+{
+ size_t decoded = 0;
+ if (readHeader) {
+ decoded += readProtocolHeader(buffer, size);
+ readHeader = !decoded;
+ }
+ if (state == NONE && decoded < size) {
+ decoded += read(buffer + decoded, size - decoded);
+ } else if (state == SUCCEEDED) {
+ if (securityLayer.get()) decoded += securityLayer->decode(buffer + decoded, size - decoded);
+ else decoded += connection->decode(buffer + decoded, size - decoded);
+ }
+ QPID_LOG(trace, id << " SaslClient::decode(" << size << "): " << decoded);
+ return decoded;
+}
+
+std::size_t SaslClient::encode(char* buffer, std::size_t size)
+{
+ size_t encoded = 0;
+ if (writeHeader) {
+ encoded += writeProtocolHeader(buffer, size);
+ writeHeader = !encoded;
+ }
+ if ((!initialised || state == NONE) && encoded < size) {
+ size_t extra = write(buffer + encoded, size - encoded);
+ encoded += extra;
+ initialised = extra > 0;
+ } else if (state == SUCCEEDED) {
+ if (securityLayer.get()) encoded += securityLayer->encode(buffer + encoded, size - encoded);
+ else encoded += connection->encode(buffer + encoded, size - encoded);
+ }
+ haveOutput = (encoded == size);
+ QPID_LOG(trace, id << " SaslClient::encode(" << size << "): " << encoded);
+ return encoded;
+}
+
+bool SaslClient::canEncode()
+{
+ if (state == NONE) {
+ QPID_LOG(trace, id << " SaslClient::canEncode(): " << writeHeader << " || " << haveOutput);
+ return writeHeader || haveOutput;
+ } else if (state == SUCCEEDED) {
+ if (securityLayer.get()) return securityLayer->canEncode();
+ else return connection->canEncode();
+ } else {
+ return false;
+ }
+}
+
+void SaslClient::mechanisms(const std::string& offered)
+{
+ QPID_LOG_CAT(debug, protocol, id << " Received SASL-MECHANISMS(" << offered << ")");
+ std::string response;
+
+ std::string mechanisms;
+ if (allowedMechanisms.size()) {
+ std::vector<std::string> allowed = split(allowedMechanisms, " ");
+ std::vector<std::string> supported = split(offered, " ");
+ std::stringstream intersection;
+ for (std::vector<std::string>::const_iterator i = allowed.begin(); i != allowed.end(); ++i) {
+ if (std::find(supported.begin(), supported.end(), *i) != supported.end()) {
+ if (!intersection.str().empty()) intersection << " ";
+ intersection << *i;
+ }
+ }
+ mechanisms = intersection.str();
+ } else {
+ mechanisms = offered;
+ }
+
+ if (sasl->start(mechanisms, response, &transport)) {
+ init(sasl->getMechanism(), &response, hostname.size() ? &hostname : 0);
+ } else {
+ init(sasl->getMechanism(), 0, hostname.size() ? &hostname : 0);
+ }
+ haveOutput = true;
+ out.activateOutput();
+}
+void SaslClient::challenge(const std::string& challenge)
+{
+ QPID_LOG_CAT(debug, protocol, id << " Received SASL-CHALLENGE(" << challenge.size() << " bytes)");
+ std::string r = sasl->step(challenge);
+ response(&r);
+ haveOutput = true;
+ out.activateOutput();
+}
+namespace {
+const std::string EMPTY;
+}
+void SaslClient::challenge()
+{
+ QPID_LOG_CAT(debug, protocol, id << " Received SASL-CHALLENGE(null)");
+ std::string r = sasl->step(EMPTY);
+ response(&r);
+}
+void SaslClient::outcome(uint8_t result, const std::string& extra)
+{
+ QPID_LOG_CAT(debug, protocol, id << " Received SASL-OUTCOME(" << result << ", " << extra << ")");
+ outcome(result);
+}
+void SaslClient::outcome(uint8_t result)
+{
+ QPID_LOG_CAT(debug, protocol, id << " Received SASL-OUTCOME(" << result << ")");
+ if (result) state = FAILED;
+ else state = SUCCEEDED;
+
+ securityLayer = sasl->getSecurityLayer(65535);
+ if (securityLayer.get()) {
+ securityLayer->init(connection.get());
+ }
+ out.activateOutput();
+}
+
+void SaslClient::closed()
+{
+ if (state == SUCCEEDED) {
+ connection->closed();
+ } else {
+ QPID_LOG(info, id << " Connection closed prior to authentication completing");
+ state = FAILED;
+ }
+}
+
+bool SaslClient::isClosed() const
+{
+ if (state == FAILED) return true;
+ else if (state == SUCCEEDED) return connection->isClosed();
+ else return false;
+}
+qpid::framing::ProtocolVersion SaslClient::getVersion() const
+{
+ return qpid::framing::ProtocolVersion(1,0,qpid::framing::ProtocolVersion::SASL);
+}
+
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/SaslClient.h b/qpid/cpp/src/qpid/broker/amqp/SaslClient.h
new file mode 100644
index 0000000000..fca293879e
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/SaslClient.h
@@ -0,0 +1,81 @@
+#ifndef QPID_BROKER_AMQP_SASLCLIENT_H
+#define QPID_BROKER_AMQP_SASLCLIENT_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/sys/ConnectionCodec.h"
+#include "qpid/sys/SecuritySettings.h"
+#include "qpid/amqp/SaslClient.h"
+#include <memory>
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+class Sasl;
+namespace sys {
+class OutputControl;
+class SecurityLayer;
+}
+namespace broker {
+class Broker;
+namespace amqp {
+class Interconnect;
+
+/**
+ * Implementation of SASL client role for when broker connects to
+ * external peers.
+ */
+class SaslClient : public qpid::sys::ConnectionCodec, qpid::amqp::SaslClient
+{
+ public:
+ SaslClient(qpid::sys::OutputControl& out, const std::string& id, boost::shared_ptr<Interconnect>, std::auto_ptr<qpid::Sasl>,
+ const std::string& hostname, const std::string& allowedMechanisms, const qpid::sys::SecuritySettings&);
+ ~SaslClient();
+ std::size_t decode(const char* buffer, std::size_t size);
+ std::size_t encode(char* buffer, std::size_t size);
+ bool canEncode();
+ void closed();
+ bool isClosed() const;
+ qpid::framing::ProtocolVersion getVersion() const;
+ private:
+ qpid::sys::OutputControl& out;
+ boost::shared_ptr<Interconnect> connection;
+ std::auto_ptr<qpid::Sasl> sasl;
+ std::string hostname;
+ std::string allowedMechanisms;
+ qpid::sys::SecuritySettings transport;
+ bool readHeader;
+ bool writeHeader;
+ bool haveOutput;
+ bool initialised;
+ enum {
+ NONE, FAILED, SUCCEEDED
+ } state;
+ std::auto_ptr<qpid::sys::SecurityLayer> securityLayer;
+
+ void mechanisms(const std::string&);
+ void challenge(const std::string&);
+ void challenge(); //null != empty string
+ void outcome(uint8_t result, const std::string&);
+ void outcome(uint8_t result);
+};
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP_SASLCLIENT_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/Session.cpp b/qpid/cpp/src/qpid/broker/amqp/Session.cpp
new file mode 100644
index 0000000000..aa4ba03dfd
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Session.cpp
@@ -0,0 +1,952 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "Session.h"
+#include "Incoming.h"
+#include "Outgoing.h"
+#include "Message.h"
+#include "Connection.h"
+#include "DataReader.h"
+#include "Domain.h"
+#include "Exception.h"
+#include "Interconnects.h"
+#include "NodePolicy.h"
+#include "Relay.h"
+#include "Topic.h"
+#include "qpid/amqp/Descriptor.h"
+#include "qpid/amqp/descriptors.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/DeliverableMessage.h"
+#include "qpid/broker/Exchange.h"
+#include "qpid/broker/DirectExchange.h"
+#include "qpid/broker/TopicExchange.h"
+#include "qpid/broker/FanOutExchange.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/QueueCursor.h"
+#include "qpid/broker/Selector.h"
+#include "qpid/broker/TopicExchange.h"
+#include "qpid/broker/amqp/Filter.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qpid/log/Statement.h"
+#include "qpid/amqp_0_10/Codecs.h"
+#include "config.h"
+#include <boost/intrusive_ptr.hpp>
+#include <boost/format.hpp>
+#include <map>
+#include <sstream>
+extern "C" {
+#include <proton/engine.h>
+}
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+
+using namespace qpid::amqp::transaction;
+
+namespace {
+pn_bytes_t convert(const std::string& s)
+{
+ pn_bytes_t result;
+ result.start = const_cast<char*>(s.data());
+ result.size = s.size();
+ return result;
+}
+std::string convert(pn_bytes_t in)
+{
+ return std::string(in.start, in.size);
+}
+//capabilities
+const std::string CREATE_ON_DEMAND("create-on-demand");
+const std::string DURABLE("durable");
+const std::string QUEUE("queue");
+const std::string TOPIC("topic");
+const std::string DIRECT_FILTER("legacy-amqp-direct-binding");
+const std::string TOPIC_FILTER("legacy-amqp-topic-binding");
+const std::string SHARED("shared");
+
+void writeCapabilities(pn_data_t* out, const std::vector<std::string>& supported)
+{
+ if (supported.size() == 1) {
+ pn_data_put_symbol(out, convert(supported.front()));
+ } else if (supported.size() > 1) {
+ pn_data_put_array(out, false, PN_SYMBOL);
+ pn_data_enter(out);
+ for (std::vector<std::string>::const_iterator i = supported.begin(); i != supported.end(); ++i) {
+ pn_data_put_symbol(out, convert(*i));
+ }
+ pn_data_exit(out);
+ }
+}
+
+template <class F>
+void readCapabilities(pn_data_t* data, F f)
+{
+ pn_data_rewind(data);
+ if (pn_data_next(data)) {
+ pn_type_t type = pn_data_type(data);
+ if (type == PN_ARRAY) {
+ pn_data_enter(data);
+ while (pn_data_next(data)) {
+ std::string s = convert(pn_data_get_symbol(data));
+ f(s);
+ }
+ pn_data_exit(data);
+ } else if (type == PN_SYMBOL) {
+ std::string s = convert(pn_data_get_symbol(data));
+ f(s);
+ } else {
+ QPID_LOG(error, "Skipping capabilities field of type " << pn_type_name(type));
+ }
+ }
+}
+
+void matchCapability(const std::string& name, bool* result, const std::string& s)
+{
+ if (s == name) *result = true;
+}
+
+bool is_capability_requested(const std::string& name, pn_data_t* capabilities)
+{
+ bool result(false);
+ readCapabilities(capabilities, boost::bind(&matchCapability, name, &result, _1));
+ return result;
+}
+
+void collectQueueCapabilities(boost::shared_ptr<Queue> node, std::vector<std::string>* supported, const std::string& s)
+{
+ if (s == DURABLE) {
+ if (node->isDurable()) supported->push_back(s);
+ } else if (s == CREATE_ON_DEMAND || s == QUEUE || s == DIRECT_FILTER || s == TOPIC_FILTER) {
+ supported->push_back(s);
+ }
+}
+
+void collectExchangeCapabilities(boost::shared_ptr<Exchange> node, std::vector<std::string>* supported, const std::string& s)
+{
+ if (s == DURABLE) {
+ if (node->isDurable()) supported->push_back(s);
+ } else if (s == SHARED) {
+ supported->push_back(s);
+ } else if (s == CREATE_ON_DEMAND || s == TOPIC) {
+ supported->push_back(s);
+ } else if (s == DIRECT_FILTER) {
+ if (node->getType() == DirectExchange::typeName) supported->push_back(s);
+ } else if (s == TOPIC_FILTER) {
+ if (node->getType() == TopicExchange::typeName) supported->push_back(s);
+ }
+}
+
+void setCapabilities(pn_data_t* in, pn_data_t* out, boost::shared_ptr<Queue> node)
+{
+ std::vector<std::string> supported;
+ readCapabilities(in, boost::bind(&collectQueueCapabilities, node, &supported, _1));
+ writeCapabilities(out, supported);
+}
+
+void setCapabilities(pn_data_t* in, pn_data_t* out, boost::shared_ptr<Exchange> node)
+{
+ std::vector<std::string> supported;
+ readCapabilities(in, boost::bind(&collectExchangeCapabilities, node, &supported, _1));
+ writeCapabilities(out, supported);
+}
+
+}
+
+class IncomingToQueue : public DecodingIncoming
+{
+ public:
+ IncomingToQueue(Broker& b, Session& p, boost::shared_ptr<qpid::broker::Queue> q, pn_link_t* l, const std::string& source, bool icl)
+ : DecodingIncoming(l, b, p, source, q->getName(), pn_link_name(l)), queue(q), isControllingLink(icl)
+ {
+ queue->markInUse(isControllingLink);
+ }
+ ~IncomingToQueue() { queue->releaseFromUse(isControllingLink); }
+ void handle(qpid::broker::Message& m, qpid::broker::TxBuffer*);
+ private:
+ boost::shared_ptr<qpid::broker::Queue> queue;
+ bool isControllingLink;
+};
+
+class IncomingToExchange : public DecodingIncoming
+{
+ public:
+ IncomingToExchange(Broker& b, Session& p, boost::shared_ptr<qpid::broker::Exchange> e, pn_link_t* l, const std::string& source, bool icl)
+ : DecodingIncoming(l, b, p, source, e->getName(), pn_link_name(l)), exchange(e), authorise(p.getAuthorise()), isControllingLink(icl)
+ {
+ exchange->incOtherUsers();
+ }
+ ~IncomingToExchange()
+ {
+ exchange->decOtherUsers(isControllingLink);
+ }
+ void handle(qpid::broker::Message& m, qpid::broker::TxBuffer*);
+ private:
+ boost::shared_ptr<qpid::broker::Exchange> exchange;
+ Authorise& authorise;
+ bool isControllingLink;
+};
+
+class IncomingToCoordinator : public DecodingIncoming
+{
+ public:
+ IncomingToCoordinator(pn_link_t* link, Broker& broker, Session& parent)
+ : DecodingIncoming(link, broker, parent, std::string(), "txn-ctrl", pn_link_name(link)) {}
+
+ ~IncomingToCoordinator() { session.abort(); }
+ void deliver(boost::intrusive_ptr<qpid::broker::amqp::Message>, pn_delivery_t*);
+ void handle(qpid::broker::Message&, qpid::broker::TxBuffer*) {}
+ private:
+};
+
+Session::Session(pn_session_t* s, Connection& c, qpid::sys::OutputControl& o)
+ : ManagedSession(c.getBroker(), c, (boost::format("%1%") % s).str()), session(s), connection(c), out(o), deleted(false),
+ authorise(connection.getUserId(), connection.getBroker().getAcl()),
+ detachRequested(),
+ tx(*this)
+{}
+
+
+Session::ResolvedNode Session::resolve(const std::string name, pn_terminus_t* terminus, bool incoming)
+{
+ bool isQueueRequested = is_capability_requested(QUEUE, pn_terminus_capabilities(terminus));
+ bool isTopicRequested = is_capability_requested(TOPIC, pn_terminus_capabilities(terminus));
+ if (isTopicRequested && isQueueRequested) {
+ //requesting both renders each request meaningless
+ isQueueRequested = false;
+ isTopicRequested = false;
+ }
+ //check whether user is even allowed access to queues/topics before resolving
+ authorise.access(name, isQueueRequested, isTopicRequested);
+ ResolvedNode node(pn_terminus_is_dynamic(terminus));
+ if (isTopicRequested || !isQueueRequested) {
+ node.topic = connection.getTopics().get(name);
+ if (node.topic) node.exchange = node.topic->getExchange();
+ else node.exchange = connection.getBroker().getExchanges().find(name);
+ }
+ if (isQueueRequested || !isTopicRequested) {
+ node.queue = connection.getBroker().getQueues().find(name);
+ }
+ bool createOnDemand = is_capability_requested(CREATE_ON_DEMAND, pn_terminus_capabilities(terminus));
+ //Strictly speaking, properties should only be specified when the
+ //terminus is dynamic. However we will not enforce that here. If
+ //properties are set on the attach request, we will set them on
+ //our reply. This allows the 'create' and 'assert' options in the
+ //qpid messaging API to be implemented over 1.0.
+ node.properties.read(pn_terminus_properties(terminus));
+
+ if (node.exchange && createOnDemand && isTopicRequested) {
+ if (!node.properties.getSpecifiedExchangeType().empty() && node.properties.getExchangeType() != node.exchange->getType()) {
+ //emulate 0-10 exchange-declare behaviour
+ throw Exception(qpid::amqp::error_conditions::PRECONDITION_FAILED, "Exchange of different type already exists");
+ }
+ }
+ bool isCreateRequested = pn_terminus_is_dynamic(terminus) || createOnDemand;
+ bool isCreateQueueRequested = isCreateRequested && isQueueRequested;
+ bool isCreateTopicRequested = isCreateRequested && isTopicRequested;
+ if ((!node.queue && !node.exchange) || (!node.queue && isCreateQueueRequested) || (!node.exchange && isCreateTopicRequested)) {
+ if (isCreateRequested) {
+ //is it a queue or an exchange?
+ if (isTopicRequested) {
+ if (node.queue) {
+ QPID_LOG_CAT(warning, model, "Node name will be ambiguous, creation of exchange named " << name << " requested when queue of the same name already exists");
+ }
+ qpid::framing::FieldTable args;
+ qpid::amqp_0_10::translate(node.properties.getProperties(), args);
+ std::pair<boost::shared_ptr<Exchange>, bool> result
+ = connection.getBroker().createExchange(name, node.properties.getExchangeType(), node.properties.isDurable(), node.properties.isAutodelete(),
+ node.properties.getAlternateExchange(),
+ args, connection.getUserId(), connection.getId());
+ node.exchange = result.first;
+ node.created = result.second;
+ } else {
+ if (node.exchange) {
+ QPID_LOG_CAT(warning, model, "Node name will be ambiguous, creation of queue named " << name << " requested when exchange of the same name already exists");
+ }
+ std::pair<boost::shared_ptr<Queue>, bool> result
+ = connection.getBroker().createQueue(name, node.properties.getQueueSettings(), node.properties.isExclusive() ? this:0, node.properties.getAlternateExchange(), connection.getUserId(), connection.getId());
+ node.queue = result.first;
+ node.created = result.second;
+ }
+ } else {
+ boost::shared_ptr<NodePolicy> nodePolicy = connection.getNodePolicies().match(name);
+ if (nodePolicy) {
+ std::pair<boost::shared_ptr<Queue>, boost::shared_ptr<Topic> > result = nodePolicy->create(name, connection);
+ node.queue = result.first;
+ node.topic = result.second;
+ if (node.topic) node.exchange = node.topic->getExchange();
+
+ if (node.queue) {
+ QPID_LOG(info, "Created queue " << name << " from policy with pattern " << nodePolicy->getPattern());
+ } else if (node.topic) {
+ QPID_LOG(info, "Created topic " << name << " from policy with pattern " << nodePolicy->getPattern());
+ } else {
+ QPID_LOG(debug, "Created neither a topic nor a queue for " << name << " from policy with pattern " << nodePolicy->getPattern());
+ }
+
+ } else {
+ size_t i = name.find('@');
+ if (i != std::string::npos && (i+1) < name.length()) {
+ std::string domain = name.substr(i+1);
+ std::string local = name.substr(0, i);
+ std::string id = (boost::format("%1%-%2%") % name % qpid::types::Uuid(true).str()).str();
+ //does this domain exist?
+ boost::shared_ptr<Domain> d = connection.getInterconnects().findDomain(domain);
+ if (d) {
+ node.relay = boost::shared_ptr<Relay>(new Relay(1000));
+ if (incoming) {
+ d->connect(false, id, name, local, connection, node.relay);
+ } else {
+ d->connect(true, id, local, name, connection, node.relay);
+ }
+ }
+ }
+ }
+ }
+ } else if (node.queue && node.topic) {
+ if (isTopicRequested) {
+ QPID_LOG_CAT(info, protocol, "Ambiguous node name; " << name << " could be queue or topic, topic requested");
+ node.queue.reset();
+ } else if (isQueueRequested) {
+ QPID_LOG_CAT(info, protocol, "Ambiguous node name; " << name << " could be queue or topic, queue requested");
+ node.exchange.reset();
+ node.topic.reset();
+ } else {
+ QPID_LOG_CAT(warning, protocol, "Ambiguous node name; " << name << " could be queue or topic, assuming topic");
+ node.queue.reset();
+ }
+ } else if (node.queue && node.exchange) {
+ if (isTopicRequested) {
+ QPID_LOG_CAT(info, protocol, "Ambiguous node name; " << name << " could be queue or topic, topic requested");
+ node.queue.reset();
+ } else if (isQueueRequested) {
+ QPID_LOG_CAT(info, protocol, "Ambiguous node name; " << name << " could be queue or topic, queue requested");
+ node.exchange.reset();
+ node.topic.reset();
+ } else {
+ QPID_LOG_CAT(warning, protocol, "Ambiguous node name; " << name << " could be queue or exchange, assuming queue");
+ node.exchange.reset();
+ }
+ }
+
+ if (node.properties.isExclusive() && node.queue) {
+ if (node.queue->setExclusiveOwner(this)) {
+ exclusiveQueues.insert(node.queue);
+ } else {
+ throw Exception(qpid::amqp::error_conditions::PRECONDITION_FAILED, std::string("Cannot grant exclusive access to ") + node.queue->getName());
+ }
+ }
+ return node;
+}
+
+std::string Session::generateName(pn_link_t* link)
+{
+ std::stringstream s;
+ if (connection.getContainerId().empty()) {
+ s << qpid::types::Uuid(true);
+ } else {
+ s << connection.getContainerId();
+ }
+ s << "_" << pn_link_name(link);
+ return s.str();
+}
+
+std::string Session::qualifyName(const std::string& name)
+{
+ if (connection.getDomain().empty()) {
+ return name;
+ } else {
+ std::stringstream s;
+ s << name << "@" << connection.getDomain();
+ return s.str();
+ }
+}
+
+void Session::attach(pn_link_t* link)
+{
+ if (pn_link_is_sender(link)) {
+ pn_terminus_t* source = pn_link_remote_source(link);
+ //i.e a subscription
+ std::string name;
+ if (pn_terminus_get_type(source) == PN_UNSPECIFIED) {
+ pn_terminus_set_type(pn_link_source(link), PN_UNSPECIFIED);
+ throw Exception(qpid::amqp::error_conditions::PRECONDITION_FAILED, "No source specified!");
+ } else if (pn_terminus_is_dynamic(source)) {
+ name = generateName(link);
+ QPID_LOG(debug, "Received attach request for outgoing link from " << name);
+ pn_terminus_set_address(pn_link_source(link), qualifyName(name).c_str());
+ } else {
+ name = pn_terminus_get_address(source);
+ QPID_LOG(debug, "Received attach request for outgoing link from " << name);
+ pn_terminus_set_address(pn_link_source(link), name.c_str());
+ }
+
+ try {
+ setupOutgoing(link, source, name);
+ } catch (const std::exception&) {
+ pn_terminus_set_type(pn_link_source(link), PN_UNSPECIFIED);
+ throw;
+ }
+ } else {
+ pn_terminus_t* target = pn_link_remote_target(link);
+ std::string name;
+ if (pn_terminus_get_type(target) == PN_UNSPECIFIED) {
+ pn_terminus_set_type(pn_link_target(link), PN_UNSPECIFIED);
+ throw Exception(qpid::amqp::error_conditions::PRECONDITION_FAILED, "No target specified!");
+ } else if (pn_terminus_get_type(target) == PN_COORDINATOR) {
+ QPID_LOG(debug, "Received attach request for incoming link to transaction coordinator on " << this);
+ boost::shared_ptr<Incoming> i(new IncomingToCoordinator(link, connection.getBroker(), *this));
+ incoming[link] = i;
+ return;
+ } else if (pn_terminus_is_dynamic(target)) {
+ name = generateName(link);
+ QPID_LOG(debug, "Received attach request for incoming link to " << name);
+ pn_terminus_set_address(pn_link_target(link), qualifyName(name).c_str());
+ } else {
+ name = pn_terminus_get_address(target);
+ QPID_LOG(debug, "Received attach request for incoming link to " << name);
+ pn_terminus_set_address(pn_link_target(link), name.c_str());
+ }
+
+ try {
+ setupIncoming(link, target, name);
+ } catch (const std::exception&) {
+ pn_terminus_set_type(pn_link_target(link), PN_UNSPECIFIED);
+ throw;
+ }
+ }
+}
+
+void Session::setupIncoming(pn_link_t* link, pn_terminus_t* target, const std::string& name)
+{
+ ResolvedNode node = resolve(name, target, true);
+ //set capabilities
+ if (node.queue) {
+ setCapabilities(pn_terminus_capabilities(target), pn_terminus_capabilities(pn_link_target(link)), node.queue);
+ authorise.incoming(node.queue);
+ node.properties.write(pn_terminus_properties(pn_link_target(link)), node.queue);
+ } else if (node.exchange) {
+ setCapabilities(pn_terminus_capabilities(target), pn_terminus_capabilities(pn_link_target(link)), node.exchange);
+ authorise.incoming(node.exchange);
+ node.properties.write(pn_terminus_properties(pn_link_target(link)), node.exchange);
+ }
+
+ const char* sourceAddress = pn_terminus_get_address(pn_link_remote_source(link));
+ if (!sourceAddress) {
+ sourceAddress = pn_terminus_get_address(pn_link_source(link));
+ }
+ std::string source;
+ if (sourceAddress) {
+ source = sourceAddress;
+ }
+ if (node.queue) {
+ boost::shared_ptr<Incoming> q(new IncomingToQueue(connection.getBroker(), *this, node.queue, link, source, node.created && node.properties.trackControllingLink()));
+ incoming[link] = q;
+ } else if (node.exchange) {
+ boost::shared_ptr<Incoming> e(new IncomingToExchange(connection.getBroker(), *this, node.exchange, link, source, node.created && node.properties.trackControllingLink()));
+ incoming[link] = e;
+ } else if (node.relay) {
+ boost::shared_ptr<Incoming> in(new IncomingToRelay(link, connection.getBroker(), *this, source, name, pn_link_name(link), node.relay));
+ incoming[link] = in;
+ } else {
+ pn_terminus_set_type(pn_link_target(link), PN_UNSPECIFIED);
+ throw Exception(qpid::amqp::error_conditions::NOT_FOUND, std::string("Node not found: ") + name);
+ }
+ if (connection.getBroker().isAuthenticating() && !connection.isLink())
+ incoming[link]->verify(connection.getUserId(), connection.getBroker().getRealm());
+ QPID_LOG(debug, "Incoming link attached");
+}
+
+void Session::setupOutgoing(pn_link_t* link, pn_terminus_t* source, const std::string& name)
+{
+ ResolvedNode node = resolve(name, source, false);
+ if (node.queue) {
+ setCapabilities(pn_terminus_capabilities(source), pn_terminus_capabilities(pn_link_source(link)), node.queue);
+ node.properties.write(pn_terminus_properties(pn_link_source(link)), node.queue);
+ } else if (node.exchange) {
+ setCapabilities(pn_terminus_capabilities(source), pn_terminus_capabilities(pn_link_source(link)), node.exchange);
+ node.properties.write(pn_terminus_properties(pn_link_source(link)), node.exchange);
+ }
+
+ Filter filter;
+ filter.read(pn_terminus_filter(source));
+ const char* targetAddress = pn_terminus_get_address(pn_link_remote_target(link));
+ if (!targetAddress) {
+ targetAddress = pn_terminus_get_address(pn_link_target(link));
+ }
+ std::string target;
+ if (targetAddress) {
+ target = targetAddress;
+ }
+
+ if (node.queue) {
+ authorise.outgoing(node.queue);
+ SubscriptionType type = (pn_terminus_get_distribution_mode(source) == PN_DIST_MODE_COPY) || (node.queue->isBrowseOnly()) ? BROWSER : CONSUMER;
+ if (type == CONSUMER && node.queue->hasExclusiveOwner() && !node.queue->isExclusiveOwner(this)) {
+ throw Exception(qpid::amqp::error_conditions::PRECONDITION_FAILED, std::string("Cannot consume from exclusive queue ") + node.queue->getName());
+ }
+ boost::shared_ptr<Outgoing> q(new OutgoingFromQueue(connection.getBroker(), name, target, node.queue, link, *this, out, type, false, node.created && node.properties.trackControllingLink()));
+ q->init();
+ filter.apply(q);
+ outgoing[link] = q;
+ pn_terminus_set_distribution_mode(pn_link_source(link), type == BROWSER ? PN_DIST_MODE_COPY : PN_DIST_MODE_MOVE);
+ } else if (node.exchange) {
+ authorise.access(node.exchange);//do separate access check before trying to create the queue
+ bool shared = is_capability_requested(SHARED, pn_terminus_capabilities(source));
+ bool durable = pn_terminus_get_durability(source);
+ bool autodelete = !durable && pn_link_remote_snd_settle_mode(link) != PN_SND_UNSETTLED;
+ QueueSettings settings(durable, autodelete);
+ std::string altExchange;
+ if (node.topic) {
+ settings = node.topic->getPolicy();
+ settings.durable = durable;
+ //only determine autodelete from link details if the policy did not imply autodeletion
+ if (!settings.autodelete) settings.autodelete = autodelete;
+ altExchange = node.topic->getAlternateExchange();
+ }
+ if (settings.original.find("qpid.auto_delete_timeout") == settings.original.end()) {
+ //only use delay from link if policy didn't specify one
+ settings.autoDeleteDelay = pn_terminus_get_timeout(source);
+ if (settings.autoDeleteDelay)
+ settings.original["qpid.auto_delete_timeout"] = settings.autoDeleteDelay;
+ }
+ if (settings.autoDeleteDelay) {
+ settings.autodelete = true;
+ }
+ filter.configure(settings);
+ std::stringstream queueName;
+ if (shared) {
+ //just use link name (TODO: could allow this to be
+ //overridden when access to link properties is provided
+ //(PROTON-335))
+ queueName << pn_link_name(link);
+ } else {
+ //combination of container id and link name is unique
+ queueName << connection.getContainerId() << "_" << pn_link_name(link);
+ }
+ boost::shared_ptr<qpid::broker::Queue> queue
+ = connection.getBroker().createQueue(queueName.str(), settings, this, altExchange, connection.getUserId(), connection.getId()).first;
+ if (!shared) queue->setExclusiveOwner(this);
+ authorise.outgoing(node.exchange, queue, filter);
+ filter.bind(node.exchange, queue);
+ boost::shared_ptr<Outgoing> q(new OutgoingFromQueue(connection.getBroker(), name, target, queue, link, *this, out, CONSUMER, !shared, false));
+ q->init();
+ outgoing[link] = q;
+ } else if (node.relay) {
+ boost::shared_ptr<Outgoing> out(new OutgoingFromRelay(link, connection.getBroker(), *this, name, target, pn_link_name(link), node.relay));
+ outgoing[link] = out;
+ out->init();
+ } else {
+ pn_terminus_set_type(pn_link_source(link), PN_UNSPECIFIED);
+ throw Exception(qpid::amqp::error_conditions::NOT_FOUND, std::string("Node not found: ") + name);/*not-found*/
+ }
+ filter.write(pn_terminus_filter(pn_link_source(link)));
+ QPID_LOG(debug, "Outgoing link attached");
+}
+
+/**
+ * Called for links initiated by the broker
+ */
+void Session::attach(pn_link_t* link, const std::string& src, const std::string& tgt, boost::shared_ptr<Relay> relay)
+{
+ pn_terminus_t* source = pn_link_source(link);
+ pn_terminus_t* target = pn_link_target(link);
+ pn_terminus_set_address(source, src.c_str());
+ pn_terminus_set_address(target, tgt.c_str());
+
+ if (relay) {
+ if (pn_link_is_sender(link)) {
+ boost::shared_ptr<Outgoing> out(new OutgoingFromRelay(link, connection.getBroker(), *this, src, tgt, pn_link_name(link), relay));
+ outgoing[link] = out;
+ out->init();
+ } else {
+ boost::shared_ptr<Incoming> in(new IncomingToRelay(link, connection.getBroker(), *this, src, tgt, pn_link_name(link), relay));
+ incoming[link] = in;
+ }
+ } else {
+ if (pn_link_is_sender(link)) {
+ setupOutgoing(link, source, src);
+ } else {
+ setupIncoming(link, target, tgt);
+ }
+ }
+}
+
+void Session::detach(pn_link_t* link, bool closed)
+{
+ if (pn_link_is_sender(link)) {
+ OutgoingLinks::iterator i = outgoing.find(link);
+ if (i != outgoing.end()) {
+ i->second->detached(closed);
+ boost::shared_ptr<Queue> q = OutgoingFromQueue::getExclusiveSubscriptionQueue(i->second.get());
+ if (q && !q->isAutoDelete() && !q->isDeleted()) {
+ connection.getBroker().deleteQueue(q->getName(), connection.getUserId(), connection.getMgmtId());
+ }
+ outgoing.erase(i);
+ QPID_LOG(debug, "Outgoing link detached");
+ }
+ } else {
+ IncomingLinks::iterator i = incoming.find(link);
+ if (i != incoming.end()) {
+ i->second->detached(closed);
+ incoming.erase(i);
+ QPID_LOG(debug, "Incoming link detached");
+ }
+ }
+}
+
+void Session::accepted(pn_delivery_t* delivery, bool sync)
+{
+ if (sync) {
+ //this is on IO thread
+ pn_delivery_update(delivery, PN_ACCEPTED);
+ pn_delivery_settle(delivery);//do we need to check settlement modes/orders?
+ incomingMessageAccepted();
+ } else {
+ //this is not on IO thread, need to delay processing until on IO thread
+ qpid::sys::Mutex::ScopedLock l(lock);
+ if (!deleted) {
+ completed.push_back(delivery);
+ out.activateOutput();
+ }
+ }
+}
+
+void Session::readable(pn_link_t* link, pn_delivery_t* delivery)
+{
+ pn_delivery_tag_t tag = pn_delivery_tag(delivery);
+#ifdef NO_PROTON_DELIVERY_TAG_T
+ QPID_LOG(debug, "received delivery: " << std::string(tag.start, tag.size));
+#else
+ QPID_LOG(debug, "received delivery: " << std::string(tag.bytes, tag.size));
+#endif
+ incomingMessageReceived();
+ IncomingLinks::iterator target = incoming.find(link);
+ if (target == incoming.end()) {
+ QPID_LOG(error, "Received message on unknown link");
+ pn_delivery_update(delivery, PN_REJECTED);
+ pn_delivery_settle(delivery);//do we need to check settlement modes/orders?
+ incomingMessageRejected();
+ } else {
+ target->second->readable(delivery);
+ if (target->second->haveWork()) out.activateOutput();
+ }
+}
+
+void Session::writable(pn_link_t* link, pn_delivery_t* delivery)
+{
+ OutgoingLinks::iterator sender = outgoing.find(link);
+ if (sender == outgoing.end()) {
+ QPID_LOG(error, "Delivery returned for unknown link " << pn_link_name(link));
+ } else {
+ sender->second->handle(delivery);
+ }
+}
+
+bool Session::dispatch()
+{
+ bool output(false);
+ if (tx.commitPending.boolCompareAndSwap(true, false)) {
+ committed(true);
+ }
+ for (OutgoingLinks::iterator s = outgoing.begin(); s != outgoing.end();) {
+ try {
+ if (s->second->doWork()) output = true;
+ ++s;
+ } catch (const Exception& e) {
+ pn_condition_t* error = pn_link_condition(s->first);
+ pn_condition_set_name(error, e.symbol());
+ pn_condition_set_description(error, e.what());
+ pn_link_close(s->first);
+ s->second->detached(true);
+ outgoing.erase(s++);
+ output = true;
+ }
+ }
+ if (completed.size()) {
+ output = true;
+ std::deque<pn_delivery_t*> copy;
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ completed.swap(copy);
+ }
+ for (std::deque<pn_delivery_t*>::iterator i = copy.begin(); i != copy.end(); ++i) {
+ accepted(*i, true);
+ }
+ }
+ for (IncomingLinks::iterator i = incoming.begin(); i != incoming.end();) {
+ try {
+ if (i->second->doWork()) output = true;
+ ++i;
+ } catch (const Exception& e) {
+ pn_condition_t* error = pn_link_condition(i->first);
+ pn_condition_set_name(error, e.symbol());
+ pn_condition_set_description(error, e.what());
+ pn_link_close(i->first);
+ i->second->detached(true);
+ incoming.erase(i++);
+ output = true;
+ }
+ }
+
+ return output;
+}
+
+void Session::close()
+{
+ for (OutgoingLinks::iterator i = outgoing.begin(); i != outgoing.end(); ++i) {
+ i->second->detached(false);
+ }
+ for (IncomingLinks::iterator i = incoming.begin(); i != incoming.end(); ++i) {
+ i->second->detached(false);
+ }
+ outgoing.clear();
+ incoming.clear();
+ QPID_LOG(debug, "Session " << session << " closed, all links detached.");
+ for (std::set< boost::shared_ptr<Queue> >::const_iterator i = exclusiveQueues.begin(); i != exclusiveQueues.end(); ++i) {
+ (*i)->releaseExclusiveOwnership();
+ }
+ exclusiveQueues.clear();
+ qpid::sys::Mutex::ScopedLock l(lock);
+ deleted = true;
+}
+
+void Session::wakeup()
+{
+ out.activateOutput();
+}
+
+Authorise& Session::getAuthorise()
+{
+ return authorise;
+}
+
+bool Session::endedByManagement() const
+{
+ return detachRequested;
+}
+
+void Session::detachedByManagement()
+{
+ detachRequested = true;
+ wakeup();
+}
+
+TxBuffer* Session::getTransaction(const std::string& id)
+{
+ return (tx.buffer.get() && id == tx.id) ? tx.buffer.get() : 0;
+}
+
+TxBuffer* Session::getTransaction(pn_delivery_t* delivery)
+{
+ return getTransactionalState(delivery).first;
+}
+
+std::pair<TxBuffer*,uint64_t> Session::getTransactionalState(pn_delivery_t* delivery)
+{
+ std::pair<TxBuffer*,uint64_t> result((TxBuffer*)0, 0);
+ if (pn_delivery_remote_state(delivery) == TRANSACTIONAL_STATE_CODE) {
+ pn_data_t* data = pn_disposition_data(pn_delivery_remote(delivery));
+ pn_data_rewind(data);
+ size_t count = 0;
+ if (data && pn_data_next(data) && (count = pn_data_get_list(data)) > 0) {
+ pn_data_enter(data);
+ pn_data_next(data);
+ std::string id = convert(pn_data_get_binary(data));
+ result.first = getTransaction(id);
+ if (!result.first) {
+ QPID_LOG(error, "Transaction not found for id: " << id);
+ }
+ if (count > 1 && pn_data_next(data)) {
+ pn_data_enter(data);
+ pn_data_next(data);
+ result.second = pn_data_get_ulong(data);
+ }
+ }
+ else
+ QPID_LOG(error, "Transactional delivery " << delivery << " appears to have no data");
+ }
+ return result;
+}
+
+std::string Session::declare()
+{
+ if (tx.buffer.get()) {
+ //not sure what the error code should be; none in spec really fit well.
+ throw Exception(qpid::amqp::error_conditions::transaction::ROLLBACK,
+ "Session only supports one transaction active at a time");
+ }
+ tx.buffer = boost::intrusive_ptr<TxBuffer>(new TxBuffer());
+ connection.getBroker().getBrokerObservers().startTx(tx.buffer);
+ txStarted();
+ return tx.id;
+}
+
+namespace {
+ class AsyncCommit : public qpid::broker::AsyncCompletion::Callback
+ {
+ public:
+ AsyncCommit(boost::shared_ptr<Session> s) : session(s) {}
+ void completed(bool sync) { session->committed(sync); }
+ boost::intrusive_ptr<qpid::broker::AsyncCompletion::Callback> clone()
+ {
+ boost::intrusive_ptr<qpid::broker::AsyncCompletion::Callback> copy(new AsyncCommit(session));
+ return copy;
+ }
+
+ private:
+ boost::shared_ptr<Session> session;
+ };
+}
+
+void Session::discharge(const std::string& id, bool failed, pn_delivery_t* delivery)
+{
+ QPID_LOG(debug, "Coordinator " << (failed ? " rollback" : " commit")
+ << " transaction " << id);
+ if (!tx.buffer.get() || id != tx.id) {
+ throw Exception(qpid::amqp::error_conditions::transaction::UNKNOWN_ID,
+ Msg() << "Cannot discharge transaction " << id
+ << (tx.buffer.get() ? Msg() << ", current transaction is " << tx.id :
+ Msg() << ", no current transaction"));
+ }
+ tx.discharge = delivery;
+ if (failed) {
+ abort();
+ } else {
+ tx.buffer->begin();
+ tx.buffer->startCommit(&connection.getBroker().getStore());
+ AsyncCommit callback(shared_from_this());
+ tx.buffer->end(callback);
+ }
+}
+
+void Session::abort()
+{
+ if (tx.buffer) {
+ tx.dischargeComplete();
+ tx.buffer->rollback();
+ txAborted();
+ tx.buffer = boost::intrusive_ptr<TxBuffer>();
+ QPID_LOG(debug, "Transaction " << tx.id << " rolled back");
+ }
+}
+
+void Session::committed(bool sync)
+{
+ if (sync) {
+ //this is on IO thread
+ tx.dischargeComplete();
+ if (tx.buffer.get()) {
+ tx.buffer->endCommit(&connection.getBroker().getStore());
+ txCommitted();
+ tx.buffer = boost::intrusive_ptr<TxBuffer>();
+ QPID_LOG(debug, "Transaction " << tx.id << " comitted");
+ } else {
+ throw Exception(qpid::amqp::error_conditions::transaction::ROLLBACK, "tranaction vanished during async commit");
+ }
+ } else {
+ //this is not on IO thread, need to delay processing until on IO thread
+ if (tx.commitPending.boolCompareAndSwap(false, true)) {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ if (!deleted) {
+ out.activateOutput();
+ }
+ }
+ }
+}
+
+void IncomingToQueue::handle(qpid::broker::Message& message, qpid::broker::TxBuffer* transaction)
+{
+ if (queue->isDeleted()) {
+ std::stringstream msg;
+ msg << " Queue " << queue->getName() << " has been deleted";
+ throw Exception(qpid::amqp::error_conditions::RESOURCE_DELETED, msg.str());
+ }
+ try {
+ queue->deliver(message, transaction);
+ } catch (const qpid::SessionException& e) {
+ throw Exception(qpid::amqp::error_conditions::PRECONDITION_FAILED, e.what());
+ }
+}
+
+void IncomingToExchange::handle(qpid::broker::Message& message, qpid::broker::TxBuffer* transaction)
+{
+ if (exchange->isDestroyed())
+ throw qpid::framing::ResourceDeletedException(QPID_MSG("Exchange " << exchange->getName() << " has been deleted."));
+ authorise.route(exchange, message);
+ DeliverableMessage deliverable(message, transaction);
+ exchange->route(deliverable);
+ if (!deliverable.delivered) {
+ if (exchange->getAlternate()) {
+ exchange->getAlternate()->route(deliverable);
+ }
+ }
+}
+
+void IncomingToCoordinator::deliver(boost::intrusive_ptr<qpid::broker::amqp::Message> message, pn_delivery_t* delivery)
+{
+ if (message && message->isTypedBody()) {
+ QPID_LOG(debug, "Coordinator got message: @" << message->getBodyDescriptor() << " " << message->getTypedBody());
+ if (message->getBodyDescriptor().match(DECLARE_SYMBOL, DECLARE_CODE)) {
+ std::string id = session.declare();
+ //encode the txn id in a 'declared' list on the disposition
+ pn_data_t* data = pn_disposition_data(pn_delivery_local(delivery));
+ pn_data_put_list(data);
+ pn_data_enter(data);
+ pn_data_put_binary(data, convert(id));
+ pn_data_exit(data);
+ pn_data_exit(data);
+ pn_delivery_update(delivery, DECLARED_CODE);
+ pn_delivery_settle(delivery);
+ session.incomingMessageAccepted();
+ QPID_LOG(debug, "Coordinator declared transaction " << id);
+ } else if (message->getBodyDescriptor().match(DISCHARGE_SYMBOL, DISCHARGE_CODE)) {
+ if (message->getTypedBody().getType() == qpid::types::VAR_LIST) {
+ qpid::types::Variant::List args = message->getTypedBody().asList();
+ qpid::types::Variant::List::const_iterator i = args.begin();
+ if (i != args.end()) {
+ std::string id = *i;
+ bool failed = ++i != args.end() ? i->asBool() : false;
+ session.discharge(id, failed, delivery);
+ }
+
+ } else {
+ throw framing::IllegalArgumentException(
+ Msg() << "Coordinator unknown message: @" <<
+ message->getBodyDescriptor() << " " << message->getTypedBody());
+ }
+ }
+ }
+}
+
+Session::Transaction::Transaction(Session& s) :
+ session(s), id((boost::format("%1%") % &s).str()), discharge(0) {}
+
+// Called in IO thread to signal completion of dischage by settling discharge message.
+void Session::Transaction::dischargeComplete() {
+ if (buffer.get() && discharge) {
+ session.accepted(discharge, false); // Queue up accept and activate output.
+ discharge = 0;
+ }
+}
+
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/Session.h b/qpid/cpp/src/qpid/broker/amqp/Session.h
new file mode 100644
index 0000000000..2537d6ca27
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Session.h
@@ -0,0 +1,144 @@
+#ifndef QPID_BROKER_AMQP1_SESSION_H
+#define QPID_BROKER_AMQP1_SESSION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/sys/AtomicValue.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/OutputControl.h"
+#include "qpid/broker/amqp/Authorise.h"
+#include "qpid/broker/amqp/ManagedSession.h"
+#include "qpid/broker/amqp/NodeProperties.h"
+#include <deque>
+#include <map>
+#include <set>
+#include <boost/intrusive_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+
+struct pn_delivery_t;
+struct pn_link_t;
+struct pn_session_t;
+struct pn_terminus_t;
+
+namespace qpid {
+namespace broker {
+
+class Broker;
+class Exchange;
+class Queue;
+class TxBuffer;
+
+namespace amqp {
+
+class Connection;
+class Incoming;
+class Outgoing;
+class Relay;
+class Topic;
+/**
+ *
+ */
+class Session : public ManagedSession, public boost::enable_shared_from_this<Session>
+{
+ public:
+ Session(pn_session_t*, Connection&, qpid::sys::OutputControl&);
+ /**
+ * called for links initiated by the peer
+ */
+ void attach(pn_link_t*);
+ void detach(pn_link_t*, bool closed);
+ void readable(pn_link_t*, pn_delivery_t*);
+ void writable(pn_link_t*, pn_delivery_t*);
+ bool dispatch();
+ bool endedByManagement() const;
+ void close();
+
+ /**
+ * called for links initiated by the broker
+ */
+ void attach(pn_link_t* link, const std::string& src, const std::string& tgt, boost::shared_ptr<Relay>);
+
+ //called when a transfer is completly processed (e.g.including stored on disk)
+ void accepted(pn_delivery_t*, bool sync);
+ //called when async transaction completes
+ void committed(bool sync);
+
+ void wakeup();
+
+ Authorise& getAuthorise();
+
+ TxBuffer* getTransaction(const std::string&);
+ TxBuffer* getTransaction(pn_delivery_t*);
+ std::pair<TxBuffer*,uint64_t> getTransactionalState(pn_delivery_t*);
+ //transaction coordination:
+ std::string declare();
+ void discharge(const std::string& id, bool failed, pn_delivery_t*);
+ void abort();
+ protected:
+ void detachedByManagement();
+ private:
+ typedef std::map<pn_link_t*, boost::shared_ptr<Outgoing> > OutgoingLinks;
+ typedef std::map<pn_link_t*, boost::shared_ptr<Incoming> > IncomingLinks;
+ pn_session_t* session;
+ Connection& connection;
+ qpid::sys::OutputControl& out;
+ IncomingLinks incoming;
+ OutgoingLinks outgoing;
+ std::deque<pn_delivery_t*> completed;
+ bool deleted;
+ qpid::sys::Mutex lock;
+ std::set< boost::shared_ptr<Queue> > exclusiveQueues;
+ Authorise authorise;
+ bool detachRequested;
+
+ struct Transaction {
+ Transaction(Session&);
+ void dischargeComplete();
+
+ Session& session;
+ boost::intrusive_ptr<TxBuffer> buffer;
+ std::string id;
+ qpid::sys::AtomicValue<bool> commitPending;
+ pn_delivery_t* discharge;
+ };
+ Transaction tx;
+
+ struct ResolvedNode
+ {
+ boost::shared_ptr<qpid::broker::Exchange> exchange;
+ boost::shared_ptr<qpid::broker::Queue> queue;
+ boost::shared_ptr<qpid::broker::amqp::Topic> topic;
+ boost::shared_ptr<Relay> relay;
+ NodeProperties properties;
+ bool created;
+ ResolvedNode(bool isDynamic) : properties(isDynamic), created(false) {}
+ };
+
+ ResolvedNode resolve(const std::string name, pn_terminus_t* terminus, bool incoming);
+ void setupOutgoing(pn_link_t* link, pn_terminus_t* source, const std::string& name);
+ void setupIncoming(pn_link_t* link, pn_terminus_t* target, const std::string& name);
+ std::string generateName(pn_link_t*);
+ std::string qualifyName(const std::string&);
+};
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP1_SESSION_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/Topic.cpp b/qpid/cpp/src/qpid/broker/amqp/Topic.cpp
new file mode 100644
index 0000000000..93f4b83f08
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Topic.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/amqp/Topic.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/management/ManagementAgent.h"
+
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+namespace {
+const std::string TOPIC("topic");
+const std::string EXCHANGE("exchange");
+const std::string DURABLE("durable");
+const std::string ALTERNATE_EXCHANGE("alternate-exchange");
+const std::string EMPTY;
+
+std::string getProperty(const std::string& k, const qpid::types::Variant::Map& m)
+{
+ qpid::types::Variant::Map::const_iterator i = m.find(k);
+ if (i == m.end()) return EMPTY;
+ else return i->second;
+}
+
+bool testProperty(const std::string& k, const qpid::types::Variant::Map& m)
+{
+ qpid::types::Variant::Map::const_iterator i = m.find(k);
+ if (i == m.end()) return false;
+ else return i->second;
+}
+
+qpid::types::Variant::Map filter(const qpid::types::Variant::Map& properties, bool queue)
+{
+ qpid::types::Variant::Map filtered = properties;
+ filtered.erase(DURABLE);
+ filtered.erase(EXCHANGE);
+ if (queue) filtered.erase(ALTERNATE_EXCHANGE);
+ return filtered;
+}
+}
+
+Topic::Topic(Broker& broker, const std::string& n, boost::shared_ptr<Exchange> e, const qpid::types::Variant::Map& properties)
+ : PersistableObject(n, TOPIC, properties), name(n), durable(testProperty(DURABLE, properties)), exchange(e),
+ alternateExchange(getProperty(ALTERNATE_EXCHANGE, properties))
+{
+ if (exchange->getName().empty()) throw qpid::Exception("Exchange must be specified.");
+ if (durable && !exchange->isDurable()) throw qpid::Exception("Durable topic must be backed by durable exchange");
+
+ qpid::types::Variant::Map unused;
+ qpid::types::Variant::Map filtered = filter(properties, true);
+ policy.populate(filtered, unused);
+
+ qpid::management::ManagementAgent* agent = broker.getManagementAgent();
+ if (agent != 0) {
+ topic = _qmf::Topic::shared_ptr(new _qmf::Topic(agent, this, name, exchange->GetManagementObject()->getObjectId(), durable));
+ topic->set_properties(filter(properties, false));
+ agent->addObject(topic);
+ }
+}
+
+bool Topic::isDurable() const
+{
+ return durable;
+}
+
+Topic::~Topic()
+{
+ if (topic != 0) topic->resourceDestroy();
+}
+
+boost::shared_ptr<qpid::management::ManagementObject> Topic::GetManagementObject() const
+{
+ return topic;
+}
+
+const QueueSettings& Topic::getPolicy() const
+{
+ return policy;
+}
+boost::shared_ptr<Exchange> Topic::getExchange()
+{
+ return exchange;
+}
+const std::string& Topic::getName() const
+{
+ return name;
+}
+const std::string& Topic::getAlternateExchange() const
+{
+ return alternateExchange;
+}
+boost::shared_ptr<Topic> TopicRegistry::createTopic(Broker& broker, const std::string& name, boost::shared_ptr<Exchange> exchange, const qpid::types::Variant::Map& properties)
+{
+ boost::shared_ptr<Topic> topic(new Topic(broker, name, exchange, properties));
+ add(topic);
+ topic->getExchange()->setDeletionListener(name, boost::bind(&TopicRegistry::remove, this, name));
+ return topic;
+}
+
+boost::shared_ptr<Topic> TopicRegistry::declare(Broker& broker, const std::string& name, boost::shared_ptr<Exchange> exchange, const qpid::types::Variant::Map& properties)
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ Topics::const_iterator i = topics.find(name);
+ if (i == topics.end()) {
+ boost::shared_ptr<Topic> topic(new Topic(broker, name, exchange, properties));
+ topics.insert(Topics::value_type(name, topic));
+ topic->getExchange()->setDeletionListener(name, boost::bind(&TopicRegistry::remove, this, name));
+ return topic;
+ } else {
+ return i->second;
+ }
+}
+
+bool TopicRegistry::createObject(Broker& broker, const std::string& type, const std::string& name, const qpid::types::Variant::Map& props,
+ const std::string& /*userId*/, const std::string& /*connectionId*/)
+{
+ if (type == TOPIC) {
+ boost::shared_ptr<Topic> topic = createTopic(broker, name, broker.getExchanges().get(getProperty(EXCHANGE, props)), props);
+ if (topic->isDurable()) broker.getStore().create(*topic);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool TopicRegistry::deleteObject(Broker& broker, const std::string& type, const std::string& name, const qpid::types::Variant::Map& /*properties*/,
+ const std::string& /*userId*/, const std::string& /*connectionId*/)
+{
+ if (type == TOPIC) {
+ boost::shared_ptr<Topic> topic = remove(name);
+ if (topic) {
+ if (topic->isDurable()) broker.getStore().destroy(*topic);
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+}
+
+bool TopicRegistry::recoverObject(Broker& broker, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties,
+ uint64_t persistenceId)
+{
+ if (type == TOPIC) {
+ boost::shared_ptr<Topic> topic = createTopic(broker, name, broker.getExchanges().get(getProperty(EXCHANGE, properties)), properties);
+ topic->setPersistenceId(persistenceId);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool TopicRegistry::add(boost::shared_ptr<Topic> topic)
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ Topics::const_iterator i = topics.find(topic->getName());
+ if (i == topics.end()) {
+ topics.insert(Topics::value_type(topic->getName(), topic));
+ return true;
+ } else {
+ throw qpid::types::Exception(QPID_MSG("A topic named " << topic->getName() << " already exists"));
+ }
+
+}
+boost::shared_ptr<Topic> TopicRegistry::remove(const std::string& name)
+{
+ boost::shared_ptr<Topic> result;
+ qpid::sys::Mutex::ScopedLock l(lock);
+ Topics::iterator i = topics.find(name);
+ if (i != topics.end()) {
+ result = i->second;
+ topics.erase(i);
+ result->getExchange()->unsetDeletionListener(name);
+ }
+ return result;
+}
+
+boost::shared_ptr<Topic> TopicRegistry::get(const std::string& name)
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ Topics::const_iterator i = topics.find(name);
+ if (i == topics.end()) {
+ return boost::shared_ptr<Topic>();
+ } else {
+ return i->second;
+ }
+}
+
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/Topic.h b/qpid/cpp/src/qpid/broker/amqp/Topic.h
new file mode 100644
index 0000000000..457b8cfced
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Topic.h
@@ -0,0 +1,91 @@
+#ifndef QPID_BROKER_AMQP_TOPIC_H
+#define QPID_BROKER_AMQP_TOPIC_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/ObjectFactory.h"
+#include "qpid/broker/PersistableObject.h"
+#include "qpid/broker/QueueSettings.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/types/Variant.h"
+#include "qpid/management/Manageable.h"
+#include "qmf/org/apache/qpid/broker/Exchange.h"
+#include "qmf/org/apache/qpid/broker/Topic.h"
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace broker {
+class Broker;
+class Exchange;
+class QueueDepth;
+
+namespace amqp {
+
+/**
+ * A topic is a node supporting a pub-sub style. It is at present
+ * implemented by an exchange with an additional policy for handling
+ * subscription queues.
+ */
+class Topic : public PersistableObject, public management::Manageable
+{
+ public:
+ Topic(Broker&, const std::string& name, boost::shared_ptr<Exchange>, const qpid::types::Variant::Map& properties);
+ ~Topic();
+ const std::string& getName() const;
+ const QueueSettings& getPolicy() const;
+ const std::string& getAlternateExchange() const;
+ boost::shared_ptr<Exchange> getExchange();
+ bool isDurable() const;
+ boost::shared_ptr<qpid::management::ManagementObject> GetManagementObject() const;
+ private:
+ std::string name;
+ bool durable;
+ boost::shared_ptr<Exchange> exchange;
+ QueueSettings policy;
+ std::string alternateExchange;
+ qmf::org::apache::qpid::broker::Topic::shared_ptr topic;
+};
+
+class TopicRegistry : public ObjectFactory
+{
+ public:
+ bool createObject(Broker&, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties,
+ const std::string& userId, const std::string& connectionId);
+ bool deleteObject(Broker&, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties,
+ const std::string& userId, const std::string& connectionId);
+ bool recoverObject(Broker&, const std::string& type, const std::string& name, const qpid::types::Variant::Map& properties,
+ uint64_t persistenceId);
+
+ bool add(boost::shared_ptr<Topic> topic);
+ boost::shared_ptr<Topic> remove(const std::string& name);
+ boost::shared_ptr<Topic> get(const std::string& name);
+ boost::shared_ptr<Topic> createTopic(Broker&, const std::string& name, boost::shared_ptr<Exchange> exchange, const qpid::types::Variant::Map& properties);
+ boost::shared_ptr<Topic> declare(Broker&, const std::string& name, boost::shared_ptr<Exchange> exchange, const qpid::types::Variant::Map& properties);
+ private:
+ typedef std::map<std::string, boost::shared_ptr<Topic> > Topics;
+ qpid::sys::Mutex lock;
+ Topics topics;
+
+};
+
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP_TOPIC_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp/Translation.cpp b/qpid/cpp/src/qpid/broker/amqp/Translation.cpp
new file mode 100644
index 0000000000..2196c8ff3d
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Translation.cpp
@@ -0,0 +1,347 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/amqp/Translation.h"
+#include "qpid/broker/amqp/Outgoing.h"
+#include "qpid/broker/amqp_0_10/MessageTransfer.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/amqp/Decoder.h"
+#include "qpid/amqp/descriptors.h"
+#include "qpid/amqp/MessageEncoder.h"
+#include "qpid/amqp_0_10/Codecs.h"
+#include "qpid/types/Variant.h"
+#include "qpid/types/encodings.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qpid/log/Statement.h"
+#include <boost/lexical_cast.hpp>
+
+namespace qpid {
+namespace broker {
+namespace amqp {
+namespace {
+
+const std::string EMPTY;
+const std::string FORWARD_SLASH("/");
+const std::string TEXT_PLAIN("text/plain");
+const std::string SUBJECT_KEY("qpid.subject");
+const std::string APP_ID("x-amqp-0-10.app-id");
+
+qpid::framing::ReplyTo translate(const std::string address, Broker* broker)
+{
+ size_t i = address.find(FORWARD_SLASH);
+ if (i == std::string::npos) {
+ //is it a queue or an exchange?
+ if (broker && broker->getQueues().find(address)) {
+ return qpid::framing::ReplyTo(EMPTY, address);
+ } else if (broker && broker->getExchanges().find(address)) {
+ return qpid::framing::ReplyTo(address, EMPTY);
+ } else {
+ return qpid::framing::ReplyTo();
+ }
+ } else {
+ return qpid::framing::ReplyTo(i > 0 ? address.substr(0, i) : EMPTY, (i+1) < address.size() ? address.substr(i+1) : EMPTY);
+ }
+}
+qpid::framing::ReplyTo translate(const qpid::amqp::CharSequence& address, Broker* broker)
+{
+ return translate(std::string(address.data, address.size), broker);
+}
+std::string translate(const qpid::framing::ReplyTo r)
+{
+ if (r.getExchange().size()) {
+ if (r.getRoutingKey().size()) return r.getExchange() + FORWARD_SLASH + r.getRoutingKey();
+ else return r.getExchange();
+ } else return r.getRoutingKey();
+}
+std::string translate(const qpid::amqp::CharSequence& chars)
+{
+ if (chars.data && chars.size) return std::string(chars.data, chars.size);
+ else return EMPTY;
+}
+bool setMessageId(qpid::framing::MessageProperties& m, const qpid::amqp::CharSequence& chars)
+{
+ if (chars.data && chars.size) {
+ if (chars.size == 16) {
+ m.setMessageId(qpid::framing::Uuid(chars.data));
+ return true;
+ } else {
+ std::istringstream in(translate(chars));
+ qpid::framing::Uuid uuid;
+ in >> uuid;
+ if (!in.fail()) {
+ m.setMessageId(uuid);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+class Properties_0_10 : public qpid::amqp::MessageEncoder::Properties
+{
+ public:
+ bool hasMessageId() const { return messageProperties && messageProperties->hasMessageId(); }
+ std::string getMessageId() const { return messageProperties ? messageProperties->getMessageId().str() : EMPTY; }
+ bool hasUserId() const { return messageProperties && messageProperties->hasUserId(); }
+ std::string getUserId() const { return messageProperties ? messageProperties->getUserId() : EMPTY; }
+ bool hasTo() const { return getDestination().size() || hasSubject(); }
+ std::string getTo() const { return getDestination().size() ? getDestination() : getSubject(); }
+ bool hasSubject() const
+ {
+ if (getDestination().empty()) {
+ return getApplicationProperties().isSet(SUBJECT_KEY);
+ } else {
+ return deliveryProperties && deliveryProperties->hasRoutingKey();
+ }
+ }
+ std::string getSubject() const
+ {
+ if (getDestination().empty()) {
+ //message was sent to default exchange, routing key is the queue name
+ return getApplicationProperties().getAsString(SUBJECT_KEY);
+ } else if (deliveryProperties) {
+ return deliveryProperties->getRoutingKey();
+ } else {
+ return EMPTY;
+ }
+ }
+ bool hasReplyTo() const { return messageProperties && messageProperties->hasReplyTo(); }
+ std::string getReplyTo() const { return messageProperties ? translate(messageProperties->getReplyTo()) : EMPTY; }
+ bool hasCorrelationId() const { return messageProperties && messageProperties->hasCorrelationId(); }
+ std::string getCorrelationId() const { return messageProperties ? messageProperties->getCorrelationId() : EMPTY; }
+ bool hasContentType() const { return messageProperties && messageProperties->hasContentType(); }
+ std::string getContentType() const { return messageProperties ? messageProperties->getContentType() : EMPTY; }
+ bool hasContentEncoding() const { return messageProperties && messageProperties->hasContentEncoding(); }
+ std::string getContentEncoding() const { return messageProperties ? messageProperties->getContentEncoding() : EMPTY; }
+ bool hasAbsoluteExpiryTime() const { return deliveryProperties && deliveryProperties->hasExpiration(); }
+ int64_t getAbsoluteExpiryTime() const { return deliveryProperties ? deliveryProperties->getExpiration() : 0; }
+ bool hasCreationTime() const { return false; }
+ int64_t getCreationTime() const { return 0; }
+ bool hasGroupId() const {return false; }
+ std::string getGroupId() const { return EMPTY; }
+ bool hasGroupSequence() const { return false; }
+ uint32_t getGroupSequence() const { return 0; }
+ bool hasReplyToGroupId() const { return false; }
+ std::string getReplyToGroupId() const { return EMPTY; }
+
+ const qpid::framing::FieldTable& getApplicationProperties() const { return messageProperties->getApplicationHeaders(); }
+ Properties_0_10(const qpid::broker::amqp_0_10::MessageTransfer& t) : transfer(t),
+ messageProperties(transfer.getProperties<qpid::framing::MessageProperties>()),
+ deliveryProperties(transfer.getProperties<qpid::framing::DeliveryProperties>())
+ {}
+ private:
+ const qpid::broker::amqp_0_10::MessageTransfer& transfer;
+ const qpid::framing::MessageProperties* messageProperties;
+ const qpid::framing::DeliveryProperties* deliveryProperties;
+
+ std::string getDestination() const
+ {
+ return transfer.getMethod<qpid::framing::MessageTransferBody>()->getDestination();
+ }
+};
+}
+
+Translation::Translation(const qpid::broker::Message& m, Broker* b) : original(m), broker(b) {}
+
+boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer> Translation::getTransfer()
+{
+ boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer> t =
+ boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer>(dynamic_cast<const qpid::broker::amqp_0_10::MessageTransfer*>(&original.getEncoding()));
+ if (t) {
+ return t;//no translation required
+ } else {
+ const Message* message = dynamic_cast<const Message*>(&original.getEncoding());
+ if (message) {
+ //translate 1.0 message into 0-10
+ boost::intrusive_ptr<qpid::broker::amqp_0_10::MessageTransfer> transfer(new qpid::broker::amqp_0_10::MessageTransfer());
+ qpid::framing::AMQFrame method((qpid::framing::MessageTransferBody(qpid::framing::ProtocolVersion(), EMPTY, 0, 0)));
+ qpid::framing::AMQFrame header((qpid::framing::AMQHeaderBody()));
+ qpid::framing::AMQFrame content((qpid::framing::AMQContentBody()));
+ method.setEof(false);
+ header.setBof(false);
+ header.setEof(false);
+ content.setBof(false);
+
+ transfer->getFrames().append(method);
+ transfer->getFrames().append(header);
+
+ qpid::framing::MessageProperties* props =
+ transfer->getFrames().getHeaders()->get<qpid::framing::MessageProperties>(true);
+
+ if (message->isTypedBody()) {
+ qpid::types::Variant body = message->getTypedBody();
+ std::string& data = content.castBody<qpid::framing::AMQContentBody>()->getData();
+ if (body.getType() == qpid::types::VAR_MAP) {
+ qpid::amqp_0_10::MapCodec::encode(body.asMap(), data);
+ props->setContentType(qpid::amqp_0_10::MapCodec::contentType);
+ } else if (body.getType() == qpid::types::VAR_LIST) {
+ qpid::amqp_0_10::ListCodec::encode(body.asList(), data);
+ props->setContentType(qpid::amqp_0_10::ListCodec::contentType);
+ } else if (body.getType() == qpid::types::VAR_STRING) {
+ data = body.getString();
+ if (body.getEncoding() == qpid::types::encodings::UTF8 || body.getEncoding() == qpid::types::encodings::ASCII) {
+ props->setContentType(TEXT_PLAIN);
+ }
+ } else {
+ qpid::types::Variant::List container;
+ container.push_back(body);
+ qpid::amqp_0_10::ListCodec::encode(container, data);
+ props->setContentType(qpid::amqp_0_10::ListCodec::contentType);
+ }
+ transfer->getFrames().append(content);
+ props->setContentLength(data.size());
+ } else {
+ qpid::amqp::CharSequence body = message->getBody();
+ content.castBody<qpid::framing::AMQContentBody>()->getData().assign(body.data, body.size);
+ transfer->getFrames().append(content);
+
+ props->setContentLength(body.size);
+ }
+
+ qpid::amqp::MessageId mid = message->getMessageId();
+ qpid::framing::Uuid uuid;
+ switch (mid.type) {
+ case qpid::amqp::MessageId::NONE:
+ break;
+ case qpid::amqp::MessageId::UUID:
+ case qpid::amqp::MessageId::BYTES:
+ if (mid.value.bytes.size == 0) break;
+ if (setMessageId(*props, mid.value.bytes)) break;
+ case qpid::amqp::MessageId::ULONG:
+ QPID_LOG(info, "Skipping message id in translation from 1.0 to 0-10 as it is not a UUID");
+ break;
+ }
+
+ qpid::amqp::MessageId cid = message->getCorrelationId();
+ switch (cid.type) {
+ case qpid::amqp::MessageId::NONE:
+ break;
+ case qpid::amqp::MessageId::UUID:
+ assert(cid.value.bytes.size = 16);
+ props->setCorrelationId(qpid::framing::Uuid(cid.value.bytes.data).str());
+ break;
+ case qpid::amqp::MessageId::BYTES:
+ if (cid.value.bytes.size) {
+ props->setCorrelationId(translate(cid.value.bytes));
+ }
+ break;
+ case qpid::amqp::MessageId::ULONG:
+ props->setCorrelationId(boost::lexical_cast<std::string>(cid.value.ulong));
+ break;
+ }
+ if (message->getReplyTo()) props->setReplyTo(translate(message->getReplyTo(), broker));
+ if (message->getContentType()) props->setContentType(translate(message->getContentType()));
+ if (message->getContentEncoding()) props->setContentEncoding(translate(message->getContentEncoding()));
+ props->setUserId(message->getUserId());
+ // TODO: FieldTable applicationHeaders;
+ qpid::amqp::CharSequence ap = message->getApplicationProperties();
+ if (ap) {
+ qpid::amqp::Decoder d(ap.data, ap.size);
+ qpid::amqp_0_10::translate(d.readMap(), props->getApplicationHeaders());
+ std::string appid = props->getApplicationHeaders().getAsString(APP_ID);
+ if (!appid.empty()) {
+ props->setAppId(appid);
+ }
+ }
+
+ qpid::framing::DeliveryProperties* dp =
+ transfer->getFrames().getHeaders()->get<qpid::framing::DeliveryProperties>(true);
+ dp->setPriority(message->getPriority());
+ if (message->isPersistent()) dp->setDeliveryMode(2);
+ if (message->getRoutingKey().size()) {
+ if (message->getRoutingKey().size() > std::numeric_limits<uint8_t>::max()) {
+ //have to truncate routing key as it is specified to be a str8
+ dp->setRoutingKey(message->getRoutingKey().substr(0,std::numeric_limits<uint8_t>::max()));
+ } else {
+ dp->setRoutingKey(message->getRoutingKey());
+ }
+ props->getApplicationHeaders().setString(SUBJECT_KEY, message->getRoutingKey());
+ }
+
+ return transfer.get();
+ } else {
+ throw qpid::Exception("Could not write message data in AMQP 0-10 format");
+ }
+ }
+}
+
+void Translation::write(OutgoingFromQueue& out)
+{
+ const Message* message = dynamic_cast<const Message*>(original.getPersistentContext().get());
+ //persistent context will contain any newly added annotations
+ if (!message) message = dynamic_cast<const Message*>(&original.getEncoding());
+ if (message) {
+ //write annotations
+ qpid::amqp::CharSequence deliveryAnnotations = message->getDeliveryAnnotations();
+ qpid::amqp::CharSequence messageAnnotations = message->getMessageAnnotations();
+ if (deliveryAnnotations.size) out.write(deliveryAnnotations.data, deliveryAnnotations.size);
+ if (messageAnnotations.size) out.write(messageAnnotations.data, messageAnnotations.size);
+ //write bare message
+ qpid::amqp::CharSequence bareMessage = message->getBareMessage();
+ if (bareMessage.size) out.write(bareMessage.data, bareMessage.size);
+ //write footer:
+ qpid::amqp::CharSequence footer = message->getFooter();
+ if (footer.size) out.write(footer.data, footer.size);
+ } else {
+ const qpid::broker::amqp_0_10::MessageTransfer* transfer = dynamic_cast<const qpid::broker::amqp_0_10::MessageTransfer*>(&original.getEncoding());
+ if (transfer) {
+ Properties_0_10 properties(*transfer);
+ qpid::types::Variant::Map applicationProperties;
+ qpid::amqp_0_10::translate(properties.getApplicationProperties(), applicationProperties);
+ if (properties.getContentType() == qpid::amqp_0_10::MapCodec::contentType) {
+ qpid::types::Variant::Map content;
+ qpid::amqp_0_10::MapCodec::decode(transfer->getContent(), content);
+ size_t size = qpid::amqp::MessageEncoder::getEncodedSize(properties);
+ size += qpid::amqp::MessageEncoder::getEncodedSize(applicationProperties, true) + 3;/*descriptor*/
+ size += qpid::amqp::MessageEncoder::getEncodedSize(content, true) + 3/*descriptor*/;
+ std::vector<char> buffer(size);
+ qpid::amqp::MessageEncoder encoder(&buffer[0], buffer.size());
+ encoder.writeProperties(properties);
+ encoder.writeApplicationProperties(applicationProperties);
+ encoder.writeMap(content, &qpid::amqp::message::AMQP_VALUE);
+ out.write(&buffer[0], encoder.getPosition());
+ } else if (properties.getContentType() == qpid::amqp_0_10::ListCodec::contentType) {
+ qpid::types::Variant::List content;
+ qpid::amqp_0_10::ListCodec::decode(transfer->getContent(), content);
+ size_t size = qpid::amqp::MessageEncoder::getEncodedSize(properties);
+ size += qpid::amqp::MessageEncoder::getEncodedSize(applicationProperties, true) + 3;/*descriptor*/
+ size += qpid::amqp::MessageEncoder::getEncodedSize(content, true) + 3/*descriptor*/;
+ std::vector<char> buffer(size);
+ qpid::amqp::MessageEncoder encoder(&buffer[0], buffer.size());
+ encoder.writeProperties(properties);
+ encoder.writeApplicationProperties(applicationProperties);
+ encoder.writeList(content, &qpid::amqp::message::AMQP_VALUE);
+ out.write(&buffer[0], encoder.getPosition());
+ } else {
+ std::string content = transfer->getContent();
+ size_t size = qpid::amqp::MessageEncoder::getEncodedSize(properties, applicationProperties, content);
+ std::vector<char> buffer(size);
+ qpid::amqp::MessageEncoder encoder(&buffer[0], buffer.size());
+ encoder.writeProperties(properties);
+ encoder.writeApplicationProperties(applicationProperties);
+ if (content.size()) encoder.writeBinary(content, &qpid::amqp::message::DATA);
+ out.write(&buffer[0], encoder.getPosition());
+ }
+ } else {
+ QPID_LOG(error, "Could not write message data in AMQP 1.0 format");
+ }
+ }
+}
+
+}}} // namespace qpid::broker::amqp
diff --git a/qpid/cpp/src/qpid/broker/amqp/Translation.h b/qpid/cpp/src/qpid/broker/amqp/Translation.h
new file mode 100644
index 0000000000..47cb69d0ba
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp/Translation.h
@@ -0,0 +1,60 @@
+#ifndef QPID_BROKER_AMQP_TRANSLATION_H
+#define QPID_BROKER_AMQP_TRANSLATION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <boost/intrusive_ptr.hpp>
+
+namespace qpid {
+namespace broker {
+class Broker;
+class Message;
+namespace amqp_0_10 {
+class MessageTransfer;
+}
+namespace amqp {
+
+class OutgoingFromQueue;
+/**
+ *
+ */
+class Translation
+{
+ public:
+ Translation(const qpid::broker::Message& message, Broker* broker = 0);
+
+ /**
+ * @returns a pointer to an AMQP 0-10 message transfer suitable
+ * for sending on an 0-10 session, translating from 1.0 as
+ * necessary
+ */
+ boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer> getTransfer();
+ /**
+ * Writes the AMQP 1.0 bare message and any annotations, translating from 0-10 if necessary
+ */
+ void write(OutgoingFromQueue&);
+ private:
+ const qpid::broker::Message& original;
+ Broker* broker;
+};
+}}} // namespace qpid::broker::amqp
+
+#endif /*!QPID_BROKER_AMQP_TRANSLATION_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp_0_10/Connection.cpp b/qpid/cpp/src/qpid/broker/amqp_0_10/Connection.cpp
new file mode 100644
index 0000000000..518c2fa9d0
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp_0_10/Connection.cpp
@@ -0,0 +1,549 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/amqp_0_10/Connection.h"
+
+#include "qpid/broker/ConnectionObserver.h"
+#include "qpid/broker/SessionOutputException.h"
+#include "qpid/broker/SessionState.h"
+#include "qpid/broker/Bridge.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/management/ManagementAgent.h"
+#include "qpid/sys/ConnectionOutputHandler.h"
+#include "qpid/sys/SecuritySettings.h"
+#include "qpid/sys/Timer.h"
+
+#include "qpid/log/Statement.h"
+#include "qpid/ptr_map.h"
+#include "qpid/framing/AMQP_ClientProxy.h"
+#include "qpid/framing/enum.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qmf/org/apache/qpid/broker/EventClientConnect.h"
+#include "qmf/org/apache/qpid/broker/EventClientDisconnect.h"
+
+#include <boost/bind.hpp>
+#include <boost/ptr_container/ptr_vector.hpp>
+
+#include <algorithm>
+#include <iostream>
+#include <assert.h>
+
+using std::string;
+
+using namespace qpid::sys;
+using namespace qpid::framing;
+using qpid::ptr_map_ptr;
+using qpid::management::ManagementAgent;
+using qpid::management::ManagementObject;
+using qpid::management::Manageable;
+using qpid::management::Args;
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+namespace qpid {
+namespace broker {
+namespace amqp_0_10 {
+
+struct ConnectionTimeoutTask : public sys::TimerTask {
+ sys::Timer& timer;
+ Connection& connection;
+
+ ConnectionTimeoutTask(uint16_t hb, sys::Timer& t, Connection& c) :
+ TimerTask(Duration(hb*2*TIME_SEC),"ConnectionTimeout"),
+ timer(t),
+ connection(c)
+ {}
+
+ void touch() {
+ restart();
+ }
+
+ void fire() {
+ // If we get here then we've not received any traffic in the timeout period
+ // Schedule closing the connection for the io thread
+ QPID_LOG(error, "Connection " << connection.getMgmtId()
+ << " timed out: closing");
+ connection.abort();
+ }
+};
+/**
+ * A ConnectionOutputHandler that delegates to another
+ * ConnectionOutputHandler. Allows you to inspect outputting frames
+ */
+class FrameInspector : public sys::ConnectionOutputHandler
+{
+public:
+ FrameInspector(ConnectionOutputHandler* p, framing::FrameHandler* i) :
+ next(p),
+ intercepter(i)
+ {
+ assert(next);
+ assert(intercepter);
+ }
+
+ void close() { next->close(); }
+ void abort() { next->abort(); }
+ void connectionEstablished() { next->connectionEstablished(); }
+ void activateOutput() { next->activateOutput(); }
+ void handle(framing::AMQFrame& f) { intercepter->handle(f); next->handle(f); }
+
+private:
+ ConnectionOutputHandler* next;
+ framing::FrameHandler* intercepter;
+};
+
+/**
+ * Chained ConnectionOutputHandler that allows outgoing frames to be
+ * tracked (for updating mgmt stats).
+ */
+class OutboundFrameTracker : public framing::FrameHandler
+{
+public:
+ OutboundFrameTracker(Connection& _con) : con(_con) {}
+ void handle(framing::AMQFrame& f)
+ {
+ con.sent(f);
+ }
+private:
+ Connection& con;
+};
+
+Connection::Connection(ConnectionOutputHandler* out_,
+ Broker& broker_, const
+ std::string& mgmtId_,
+ const qpid::sys::SecuritySettings& external,
+ bool link_,
+ uint64_t objectId_
+) :
+ outboundTracker(new OutboundFrameTracker(*this)),
+ out(new FrameInspector(out_, outboundTracker.get())),
+ broker(broker_),
+ framemax(65535),
+ heartbeat(0),
+ heartbeatmax(120),
+ isDefaultRealm(false),
+ securitySettings(external),
+ link(link_),
+ adapter(*this, link),
+ mgmtClosing(false),
+ mgmtId(mgmtId_),
+ links(broker_.getLinks()),
+ agent(0),
+ timer(broker_.getTimer()),
+ objectId(objectId_)
+{
+ broker.getConnectionObservers().connection(*this);
+ assert(agent == 0);
+ assert(mgmtObject == 0);
+ Manageable* parent = broker.GetVhostObject();
+ if (parent != 0) {
+ agent = broker.getManagementAgent();
+ if (agent != 0) {
+ // TODO set last bool true if system connection
+ mgmtObject = _qmf::Connection::shared_ptr(new _qmf::Connection(agent, this, parent, mgmtId, !link, false, "AMQP 0-10"));
+ agent->addObject(mgmtObject, objectId);
+ }
+ }
+}
+
+void Connection::requestIOProcessing(boost::function0<void> callback)
+{
+ ScopedLock<Mutex> l(ioCallbackLock);
+ ioCallbacks.push(callback);
+ if (isOpen()) out->activateOutput();
+}
+
+Connection::~Connection()
+{
+ if (mgmtObject != 0) {
+ mgmtObject->debugStats("destroying");
+ if (!link)
+ agent->raiseEvent(_qmf::EventClientDisconnect(mgmtId, getUserId(), mgmtObject->get_remoteProperties()));
+ QPID_LOG_CAT(debug, model, "Delete connection. user:" << getUserId()
+ << " rhost:" << mgmtId );
+ mgmtObject->resourceDestroy();
+ }
+ broker.getConnectionObservers().closed(*this);
+
+ if (heartbeatTimer)
+ heartbeatTimer->cancel();
+ if (timeoutTimer)
+ timeoutTimer->cancel();
+ if (linkHeartbeatTimer) {
+ linkHeartbeatTimer->cancel();
+ }
+}
+
+void Connection::received(framing::AMQFrame& frame) {
+ // Received frame on connection so delay timeout
+ restartTimeout();
+ bool wasOpen = isOpen();
+ adapter.handle(frame);
+ if (link) //i.e. we are acting as the client to another broker
+ recordFromServer(frame);
+ else
+ recordFromClient(frame);
+ if (!wasOpen && isOpen()) {
+ doIoCallbacks(); // Do any callbacks registered before we opened.
+ broker.getConnectionObservers().opened(*this);
+ }
+}
+
+void Connection::sent(const framing::AMQFrame& frame)
+{
+ if (link) //i.e. we are acting as the client to another broker
+ recordFromClient(frame);
+ else
+ recordFromServer(frame);
+}
+
+bool isMessage(const AMQMethodBody* method)
+{
+ return method && method->isA<qpid::framing::MessageTransferBody>();
+}
+
+void Connection::recordFromServer(const framing::AMQFrame& frame)
+{
+ if (mgmtObject != 0)
+ {
+ qmf::org::apache::qpid::broker::Connection::PerThreadStats *cStats = mgmtObject->getStatistics();
+ cStats->framesToClient += 1;
+ cStats->bytesToClient += frame.encodedSize();
+ if (isMessage(frame.getMethod())) {
+ cStats->msgsToClient += 1;
+ }
+ mgmtObject->statisticsUpdated();
+ }
+}
+
+void Connection::recordFromClient(const framing::AMQFrame& frame)
+{
+ if (mgmtObject != 0)
+ {
+ qmf::org::apache::qpid::broker::Connection::PerThreadStats *cStats = mgmtObject->getStatistics();
+ cStats->framesFromClient += 1;
+ cStats->bytesFromClient += frame.encodedSize();
+ if (isMessage(frame.getMethod())) {
+ cStats->msgsFromClient += 1;
+ }
+ mgmtObject->statisticsUpdated();
+ }
+}
+
+string Connection::getAuthMechanism()
+{
+ if (!link)
+ return string("ANONYMOUS");
+
+ return links.getAuthMechanism(mgmtId);
+}
+
+string Connection::getUsername ( )
+{
+ if (!link)
+ return string("anonymous");
+
+ return links.getUsername(mgmtId);
+}
+
+string Connection::getPassword ( )
+{
+ if (!link)
+ return string("");
+
+ return links.getPassword(mgmtId);
+}
+
+string Connection::getHost ( )
+{
+ if (!link)
+ return string("");
+
+ return links.getHost(mgmtId);
+}
+
+uint16_t Connection::getPort ( )
+{
+ if (!link)
+ return 0;
+
+ return links.getPort(mgmtId);
+}
+
+string Connection::getAuthCredentials()
+{
+ if (!link)
+ return string();
+
+ if (mgmtObject != 0)
+ {
+ if (links.getAuthMechanism(mgmtId) == "ANONYMOUS")
+ mgmtObject->set_authIdentity("anonymous");
+ else
+ mgmtObject->set_authIdentity(links.getAuthIdentity(mgmtId));
+ }
+
+ return links.getAuthCredentials(mgmtId);
+}
+
+void Connection::notifyConnectionForced(const string& text)
+{
+ broker.getConnectionObservers().forced(*this, text);
+}
+
+void Connection::setUserId(const string& uid)
+{
+ userId = uid;
+ size_t at = userId.find('@');
+ userName = userId.substr(0, at);
+ isDefaultRealm = (
+ at!= std::string::npos &&
+ getBroker().getRealm() == userId.substr(at+1,userId.size()));
+ raiseConnectEvent();
+}
+
+void Connection::raiseConnectEvent() {
+ if (mgmtObject != 0) {
+ mgmtObject->set_authIdentity(userId);
+ agent->raiseEvent(_qmf::EventClientConnect(mgmtId, userId, mgmtObject->get_remoteProperties()));
+ }
+
+ QPID_LOG_CAT(debug, model, "Create connection. user:" << userId
+ << " rhost:" << mgmtId );
+}
+
+void Connection::close(connection::CloseCode code, const string& text)
+{
+ QPID_LOG_IF(error, code != connection::CLOSE_CODE_NORMAL, "Connection " << mgmtId << " closed by error: " << text << "(" << code << ")");
+ if (heartbeatTimer)
+ heartbeatTimer->cancel();
+ if (timeoutTimer)
+ timeoutTimer->cancel();
+ if (linkHeartbeatTimer) {
+ linkHeartbeatTimer->cancel();
+ }
+ adapter.close(code, text);
+ //make sure we delete dangling pointers from outputTasks before deleting sessions
+ outputTasks.removeAll();
+ channels.clear();
+ out->close();
+}
+
+void Connection::activateOutput()
+{
+ out->activateOutput();
+}
+
+void Connection::addOutputTask(OutputTask* t)
+{
+ outputTasks.addOutputTask(t);
+}
+
+void Connection::removeOutputTask(OutputTask* t)
+{
+ outputTasks.removeOutputTask(t);
+}
+
+void Connection::closed(){ // Physically closed, suspend open sessions.
+ if (heartbeatTimer)
+ heartbeatTimer->cancel();
+ if (timeoutTimer)
+ timeoutTimer->cancel();
+ if (linkHeartbeatTimer) {
+ linkHeartbeatTimer->cancel();
+ }
+ try {
+ while (!channels.empty())
+ ptr_map_ptr(channels.begin())->handleDetach();
+ } catch(std::exception& e) {
+ QPID_LOG(error, QPID_MSG("While closing connection: " << e.what()));
+ assert(0);
+ }
+}
+
+void Connection::doIoCallbacks() {
+ if (!isOpen()) return; // Don't process IO callbacks until we are open.
+ ScopedLock<Mutex> l(ioCallbackLock);
+ while (!ioCallbacks.empty()) {
+ boost::function0<void> cb = ioCallbacks.front();
+ ioCallbacks.pop();
+ ScopedUnlock<Mutex> ul(ioCallbackLock);
+ cb(); // Lend the IO thread for management processing
+ }
+}
+
+bool Connection::doOutput() {
+ try {
+ doIoCallbacks();
+ if (mgmtClosing) {
+ closed();
+ close(connection::CLOSE_CODE_CONNECTION_FORCED, "Closed by Management Request");
+ } else {
+ //then do other output as needed:
+ return outputTasks.doOutput();
+ }
+ }catch(const SessionOutputException& e){
+ getChannel(e.channel).handleException(e);
+ return true;
+ }catch(ConnectionException& e){
+ close(e.code, e.getMessage());
+ }catch(std::exception& e){
+ close(connection::CLOSE_CODE_CONNECTION_FORCED, e.what());
+ }
+ return false;
+}
+
+void Connection::sendHeartbeat() {
+ adapter.heartbeat();
+}
+
+void Connection::closeChannel(uint16_t id) {
+ ChannelMap::iterator i = channels.find(id);
+ if (i != channels.end()) channels.erase(i);
+}
+
+SessionHandler& Connection::getChannel(ChannelId id) {
+ ChannelMap::iterator i=channels.find(id);
+ if (i == channels.end()) {
+ i = channels.insert(id, new SessionHandler(*this, id)).first;
+ }
+ return *ptr_map_ptr(i);
+}
+
+ManagementObject::shared_ptr Connection::GetManagementObject(void) const
+{
+ return mgmtObject;
+}
+
+Manageable::status_t Connection::ManagementMethod(uint32_t methodId, Args&, string&)
+{
+ Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD;
+
+ QPID_LOG(debug, "Connection::ManagementMethod [id=" << methodId << "]");
+
+ switch (methodId)
+ {
+ case _qmf::Connection::METHOD_CLOSE :
+ mgmtClosing = true;
+ if (mgmtObject != 0) mgmtObject->set_closing(1);
+ out->activateOutput();
+ status = Manageable::STATUS_OK;
+ break;
+ }
+
+ return status;
+}
+
+void Connection::setSecureConnection(SecureConnection* s)
+{
+ adapter.setSecureConnection(s);
+}
+
+struct ConnectionHeartbeatTask : public sys::TimerTask {
+ sys::Timer& timer;
+ Connection& connection;
+ ConnectionHeartbeatTask(uint16_t hb, sys::Timer& t, Connection& c) :
+ TimerTask(Duration(hb*TIME_SEC), "ConnectionHeartbeat"),
+ timer(t),
+ connection(c)
+ {}
+
+ void fire() {
+ // Setup next firing
+ setupNextFire();
+ timer.add(this);
+
+ // Send Heartbeat
+ connection.sendHeartbeat();
+ }
+};
+
+class LinkHeartbeatTask : public qpid::sys::TimerTask {
+ sys::Timer& timer;
+ Connection& connection;
+ bool heartbeatSeen;
+
+ void fire() {
+ if (!heartbeatSeen) {
+ QPID_LOG(error, "Federation link connection " << connection.getMgmtId() << " missed 2 heartbeats - closing connection");
+ connection.abort();
+ } else {
+ heartbeatSeen = false;
+ // Setup next firing
+ setupNextFire();
+ timer.add(this);
+ }
+ }
+
+public:
+ LinkHeartbeatTask(sys::Timer& t, qpid::sys::Duration period, Connection& c) :
+ TimerTask(period, "LinkHeartbeatTask"), timer(t), connection(c), heartbeatSeen(false) {}
+
+ void heartbeatReceived() { heartbeatSeen = true; }
+};
+
+
+void Connection::abort()
+{
+ // Make sure that we don't try to send a heartbeat as we're
+ // aborting the connection
+ if (heartbeatTimer)
+ heartbeatTimer->cancel();
+
+ out->abort();
+}
+
+void Connection::setHeartbeatInterval(uint16_t heartbeat)
+{
+ setHeartbeat(heartbeat);
+ if (heartbeat > 0) {
+ if (!heartbeatTimer) {
+ heartbeatTimer = new ConnectionHeartbeatTask(heartbeat, timer, *this);
+ timer.add(heartbeatTimer);
+ }
+ if (!timeoutTimer) {
+ timeoutTimer = new ConnectionTimeoutTask(heartbeat, timer, *this);
+ timer.add(timeoutTimer);
+ }
+ }
+ out->connectionEstablished();
+}
+
+void Connection::startLinkHeartbeatTimeoutTask() {
+ if (!linkHeartbeatTimer && heartbeat > 0) {
+ linkHeartbeatTimer = new LinkHeartbeatTask(timer, 2 * heartbeat * TIME_SEC, *this);
+ timer.add(linkHeartbeatTimer);
+ }
+ out->connectionEstablished();
+}
+
+void Connection::restartTimeout()
+{
+ if (timeoutTimer)
+ timeoutTimer->touch();
+
+ if (linkHeartbeatTimer) {
+ static_cast<LinkHeartbeatTask*>(linkHeartbeatTimer.get())->heartbeatReceived();
+ }
+}
+
+bool Connection::isOpen() { return adapter.isOpen(); }
+
+}}}
diff --git a/qpid/cpp/src/qpid/broker/amqp_0_10/Connection.h b/qpid/cpp/src/qpid/broker/amqp_0_10/Connection.h
new file mode 100644
index 0000000000..1bf6b2cee3
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp_0_10/Connection.h
@@ -0,0 +1,232 @@
+#ifndef QPID_BROKER_AMQP_0_10_CONNECTION_H
+#define QPID_BROKER_AMQP_0_10_CONNECTION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <memory>
+#include <sstream>
+#include <vector>
+#include <queue>
+
+#include "qpid/broker/BrokerImportExport.h"
+
+#include "qpid/broker/ConnectionHandler.h"
+#include "qpid/broker/Connection.h"
+#include "qpid/broker/OwnershipToken.h"
+#include "qpid/management/Manageable.h"
+#include "qpid/sys/AggregateOutput.h"
+#include "qpid/sys/ConnectionInputHandler.h"
+#include "qpid/sys/SecuritySettings.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/types/Variant.h"
+#include "qpid/RefCounted.h"
+#include "qpid/Url.h"
+#include "qpid/ptr_map.h"
+
+#include "qmf/org/apache/qpid/broker/Connection.h"
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/bind.hpp>
+
+#include <algorithm>
+
+namespace qpid {
+namespace sys {
+class ConnectionOutputHandler;
+class Timer;
+class TimerTask;
+}
+namespace broker {
+
+class Broker;
+class LinkRegistry;
+class Queue;
+class SecureConnection;
+class SessionHandler;
+
+namespace amqp_0_10 {
+struct ConnectionTimeoutTask;
+
+class Connection : public sys::ConnectionInputHandler, public qpid::broker::Connection,
+ public management::Manageable,
+ public RefCounted
+{
+ public:
+ uint32_t getFrameMax() const { return framemax; }
+ uint16_t getHeartbeat() const { return heartbeat; }
+ uint16_t getHeartbeatMax() const { return heartbeatmax; }
+
+ void setFrameMax(uint32_t fm) { framemax = std::max(fm, (uint32_t) 4096); }
+ void setHeartbeat(uint16_t hb) { heartbeat = hb; }
+ void setHeartbeatMax(uint16_t hbm) { heartbeatmax = hbm; }
+
+
+ const management::ObjectId getObjectId() const { return GetManagementObject()->getObjectId(); };
+ const std::string& getUserId() const { return userId; }
+
+ bool isFederationLink() const { return federationPeerTag.size() > 0; }
+ void setFederationPeerTag(const std::string& tag) { federationPeerTag = std::string(tag); }
+ const std::string& getFederationPeerTag() const { return federationPeerTag; }
+ std::vector<Url>& getKnownHosts() { return knownHosts; }
+
+ /**@return true if user is the authenticated user on this connection.
+ * If id has the default realm will also compare plain username.
+ */
+ bool isAuthenticatedUser(const std::string& id) const {
+ return (id == userId || (isDefaultRealm && id == userName));
+ }
+
+ Broker& getBroker() { return broker; }
+
+ sys::ConnectionOutputHandler& getOutput() { return *out; }
+ void activateOutput();
+ void addOutputTask(OutputTask*);
+ void removeOutputTask(OutputTask*);
+ framing::ProtocolVersion getVersion() const { return version; }
+
+ Connection(sys::ConnectionOutputHandler* out,
+ Broker& broker,
+ const std::string& mgmtId,
+ const qpid::sys::SecuritySettings&,
+ bool isLink = false,
+ uint64_t objectId = 0);
+
+ ~Connection ();
+
+ /** Get the SessionHandler for channel. Create if it does not already exist */
+ SessionHandler& getChannel(framing::ChannelId channel);
+
+ /** Close the connection. Waits for the client to respond with close-ok
+ * before actually destroying the connection.
+ */
+ QPID_BROKER_EXTERN void close(
+ framing::connection::CloseCode code, const std::string& text);
+
+ /** Abort the connection. Close abruptly and immediately. */
+ QPID_BROKER_EXTERN void abort();
+
+ // ConnectionInputHandler methods
+ void received(framing::AMQFrame& frame);
+ bool doOutput();
+ void closed();
+
+ void closeChannel(framing::ChannelId channel);
+
+ // Manageable entry points
+ management::ManagementObject::shared_ptr GetManagementObject(void) const;
+ management::Manageable::status_t
+ ManagementMethod (uint32_t methodId, management::Args& args, std::string&);
+
+ void requestIOProcessing (boost::function0<void>);
+ void recordFromServer (const framing::AMQFrame& frame);
+ void recordFromClient (const framing::AMQFrame& frame);
+
+ // gets for configured federation links
+ std::string getAuthMechanism();
+ std::string getAuthCredentials();
+ std::string getUsername();
+ std::string getPassword();
+ std::string getHost();
+ uint16_t getPort();
+
+ void notifyConnectionForced(const std::string& text);
+ void setUserId(const std::string& uid);
+
+ // credentials for connected client
+ const std::string& getMgmtId() const { return mgmtId; }
+ management::ManagementAgent* getAgent() const { return agent; }
+
+ void setHeartbeatInterval(uint16_t heartbeat);
+ void sendHeartbeat();
+ void restartTimeout();
+
+ void setSecureConnection(SecureConnection* secured);
+
+ const qpid::sys::SecuritySettings& getExternalSecuritySettings() const
+ {
+ return securitySettings;
+ }
+
+ /** @return true if the initial connection negotiation is complete. */
+ bool isOpen();
+
+ bool isLink() const { return link; }
+ void startLinkHeartbeatTimeoutTask();
+
+ void setClientProperties(const types::Variant::Map& cp) { clientProperties = cp; }
+ const types::Variant::Map& getClientProperties() const { return clientProperties; }
+
+ private:
+ // Management object is used in the constructor so must be early
+ qmf::org::apache::qpid::broker::Connection::shared_ptr mgmtObject;
+
+ //contained output tasks
+ sys::AggregateOutput outputTasks;
+
+ boost::scoped_ptr<framing::FrameHandler> outboundTracker;
+ boost::scoped_ptr<sys::ConnectionOutputHandler> out;
+
+ Broker& broker;
+
+ framing::ProtocolVersion version;
+ uint32_t framemax;
+ uint16_t heartbeat;
+ uint16_t heartbeatmax;
+ std::string userId;
+ std::string federationPeerTag;
+ std::vector<Url> knownHosts;
+ std::string userName;
+ bool isDefaultRealm;
+
+ typedef boost::ptr_map<framing::ChannelId, SessionHandler> ChannelMap;
+
+ ChannelMap channels;
+ qpid::sys::SecuritySettings securitySettings;
+ const bool link;
+ ConnectionHandler adapter;
+ bool mgmtClosing;
+ const std::string mgmtId;
+ sys::Mutex ioCallbackLock;
+ std::queue<boost::function0<void> > ioCallbacks;
+ LinkRegistry& links;
+ management::ManagementAgent* agent;
+ sys::Timer& timer;
+ boost::intrusive_ptr<sys::TimerTask> heartbeatTimer, linkHeartbeatTimer;
+ boost::intrusive_ptr<ConnectionTimeoutTask> timeoutTimer;
+ uint64_t objectId;
+ types::Variant::Map clientProperties;
+
+ void raiseConnectEvent();
+
+friend class OutboundFrameTracker;
+
+ void sent(const framing::AMQFrame& f);
+ void doIoCallbacks();
+
+ public:
+
+ qmf::org::apache::qpid::broker::Connection::shared_ptr getMgmtObject() { return mgmtObject; }
+};
+
+}}}
+
+#endif /*!QPID_BROKER_AMQP_0_10_CONNECTION_H*/
diff --git a/qpid/cpp/src/qpid/broker/amqp_0_10/MessageTransfer.cpp b/qpid/cpp/src/qpid/broker/amqp_0_10/MessageTransfer.cpp
new file mode 100644
index 0000000000..a43bf8efa6
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp_0_10/MessageTransfer.cpp
@@ -0,0 +1,430 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "MessageTransfer.h"
+
+#include "qpid/amqp/CharSequence.h"
+#include "qpid/amqp_0_10/Codecs.h"
+#include "qpid/amqp/MapHandler.h"
+#include "qpid/amqp/MessageId.h"
+#include "qpid/broker/Message.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qpid/framing/MessageProperties.h"
+#include "qpid/framing/DeliveryProperties.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/framing/frame_functors.h"
+#include "qpid/framing/TypeFilter.h"
+#include "qpid/framing/SendContent.h"
+#include "qpid/log/Statement.h"
+#include "boost/lexical_cast.hpp"
+
+using namespace qpid::framing;
+
+namespace qpid {
+namespace broker {
+namespace amqp_0_10 {
+namespace {
+const std::string QMF2("qmf2");
+const std::string PARTIAL("partial");
+}
+MessageTransfer::MessageTransfer() : frames(framing::SequenceNumber()), requiredCredit(0), cachedRequiredCredit(false) {}
+MessageTransfer::MessageTransfer(const framing::SequenceNumber& id) : frames(id), requiredCredit(0), cachedRequiredCredit(false) {}
+
+uint64_t MessageTransfer::getContentSize() const
+{
+ return frames.getContentSize();
+}
+
+uint64_t MessageTransfer::getMessageSize() const
+{
+ return getRequiredCredit();
+}
+
+std::string MessageTransfer::getAnnotationAsString(const std::string& key) const
+{
+ const qpid::framing::MessageProperties* mp = getProperties<qpid::framing::MessageProperties>();
+ if (mp && mp->hasApplicationHeaders()) {
+ FieldTable::ValuePtr value = mp->getApplicationHeaders().get(key);
+ if (value) {
+ if (value->convertsTo<std::string>()) return value->get<std::string>();
+ else if (value->convertsTo<int>()) return boost::lexical_cast<std::string>(value->get<int>());
+ }
+ return std::string();
+ } else {
+ return std::string();
+ }
+}
+std::string MessageTransfer::getPropertyAsString(const std::string& key) const { return getAnnotationAsString(key); }
+
+amqp::MessageId MessageTransfer::getMessageId() const
+{
+ const qpid::framing::MessageProperties* mp = getProperties<qpid::framing::MessageProperties>();
+
+ amqp::MessageId r;
+ if (mp->hasMessageId()) {
+ r.set(amqp::CharSequence::create(mp->getMessageId().data(),16), types::VAR_UUID);
+ }
+ return r;
+}
+
+amqp::MessageId MessageTransfer::getCorrelationId() const
+{
+ const qpid::framing::MessageProperties* mp = getProperties<qpid::framing::MessageProperties>();
+
+ amqp::MessageId r;
+ if (mp->hasCorrelationId()) {
+ r.set(amqp::CharSequence::create(mp->getCorrelationId()), types::VAR_STRING);
+ }
+ return r;
+}
+
+bool MessageTransfer::getTtl(uint64_t& result) const
+{
+ const qpid::framing::DeliveryProperties* dp = getProperties<qpid::framing::DeliveryProperties>();
+ if (dp && dp->hasTtl()) {
+ result = dp->getTtl();
+ return true;
+ } else {
+ return false;
+ }
+}
+bool MessageTransfer::hasExpiration() const
+{
+ const qpid::framing::DeliveryProperties* dp = getProperties<qpid::framing::DeliveryProperties>();
+ if (dp && dp->hasExpiration()) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+uint8_t MessageTransfer::getPriority() const
+{
+ const qpid::framing::DeliveryProperties* dp = getProperties<qpid::framing::DeliveryProperties>();
+ if (dp && dp->hasPriority()) {
+ return dp->getPriority();
+ } else {
+ return 0;
+ }
+}
+
+std::string MessageTransfer::getExchangeName() const
+{
+ return getFrames().as<framing::MessageTransferBody>()->getDestination();
+}
+
+void MessageTransfer::setTimestamp()
+{
+ DeliveryProperties* props = getFrames().getHeaders()->get<DeliveryProperties>(true);
+ time_t now = ::time(0);
+ props->setTimestamp(now);
+}
+
+uint64_t MessageTransfer::getTimestamp() const
+{
+ const DeliveryProperties* props = getProperties<DeliveryProperties>();
+ return props ? props->getTimestamp() : 0;
+}
+
+bool MessageTransfer::requiresAccept() const
+{
+ const framing::MessageTransferBody* b = getFrames().as<framing::MessageTransferBody>();
+ return b && b->getAcceptMode() == 0/*EXPLICIT == 0*/;
+}
+uint32_t MessageTransfer::getRequiredCredit() const
+{
+ if (cachedRequiredCredit) {
+ return requiredCredit;
+ } else {
+ // TODO -- remove this code and replace it with a QPID_ASSERT(cachedRequiredCredit),
+ // then fix whatever breaks. compute should always be called before get.
+ uint32_t sum = 0;
+ for(FrameSet::Frames::const_iterator i = frames.begin(); i != frames.end(); ++i ) {
+ uint8_t type = (*i).getBody()->type();
+ if ((type == qpid::framing::HEADER_BODY ) || (type == qpid::framing::CONTENT_BODY ))
+ sum += (*i).getBody()->encodedSize();
+ }
+ return sum;
+ }
+}
+void MessageTransfer::computeRequiredCredit()
+{
+ //add up payload for all header and content frames in the frameset
+ uint32_t sum = 0;
+ for(FrameSet::Frames::const_iterator i = frames.begin(); i != frames.end(); ++i ) {
+ uint8_t type = (*i).getBody()->type();
+ if ((type == qpid::framing::HEADER_BODY ) || (type == qpid::framing::CONTENT_BODY ))
+ sum += (*i).getBody()->encodedSize();
+ }
+ requiredCredit = sum;
+ cachedRequiredCredit = true;
+}
+
+qpid::framing::FrameSet& MessageTransfer::getFrames()
+{
+ return frames;
+}
+const qpid::framing::FrameSet& MessageTransfer::getFrames() const
+{
+ return frames;
+}
+void MessageTransfer::sendContent(framing::FrameHandler& out, uint16_t maxFrameSize) const
+{
+ qpid::framing::Count c;
+ frames.map_if(c, qpid::framing::TypeFilter<qpid::framing::CONTENT_BODY>());
+
+ qpid::framing::SendContent f(out, maxFrameSize, c.getCount());
+ frames.map_if(f, qpid::framing::TypeFilter<qpid::framing::CONTENT_BODY>());
+}
+
+class SendHeader
+{
+ public:
+ SendHeader(FrameHandler& h, bool r, uint64_t t, const qpid::types::Variant::Map& a) : handler(h), redelivered(r), ttl(t), annotations(a) {}
+ void operator()(const AMQFrame& f)
+ {
+ AMQFrame copy = f;
+ if (redelivered || ttl || annotations.size()) {
+ copy.cloneBody();
+ if (annotations.size()) {
+ MessageProperties* props =
+ copy.castBody<AMQHeaderBody>()->get<MessageProperties>(true);
+ for (qpid::types::Variant::Map::const_iterator i = annotations.begin();
+ i != annotations.end(); ++i) {
+ props->getApplicationHeaders().set(i->first, qpid::amqp_0_10::translate(i->second));
+ }
+ }
+ if (redelivered || ttl) {
+ DeliveryProperties* dp =
+ copy.castBody<AMQHeaderBody>()->get<DeliveryProperties>(true);
+ if (ttl) dp->setTtl(ttl);
+ if (redelivered) dp->setRedelivered(redelivered);
+ }
+ }
+ handler.handle(copy);
+ }
+ private:
+ FrameHandler& handler;
+ bool redelivered;
+ uint64_t ttl;
+ const qpid::types::Variant::Map& annotations;
+};
+
+void MessageTransfer::sendHeader(framing::FrameHandler& out, uint16_t /*maxFrameSize*/,
+ bool redelivered, uint64_t ttl,
+ const qpid::types::Variant::Map& annotations) const
+{
+ SendHeader f(out, redelivered, ttl, annotations);
+ frames.map_if(f, TypeFilter<HEADER_BODY>());
+}
+bool MessageTransfer::isImmediateDeliveryRequired(const qpid::broker::Message& /*message*/)
+{
+ return false;//TODO
+}
+
+const framing::SequenceNumber& MessageTransfer::getCommandId() const { return frames.getId(); }
+
+std::string MessageTransfer::getRoutingKey() const
+{
+ const qpid::framing::DeliveryProperties* dp = getProperties<qpid::framing::DeliveryProperties>();
+ if (dp && dp->hasRoutingKey()) {
+ return dp->getRoutingKey();
+ } else {
+ return std::string();
+ }
+}
+bool MessageTransfer::isPersistent() const
+{
+ const qpid::framing::DeliveryProperties* dp = getProperties<qpid::framing::DeliveryProperties>();
+ if (dp && dp->hasDeliveryMode()) {
+ return dp->getDeliveryMode() == 2;
+ } else {
+ return false;
+ }
+}
+
+std::string MessageTransfer::getContent() const
+{
+ return frames.getContent();
+}
+
+void MessageTransfer::decodeHeader(framing::Buffer& buffer)
+{
+ AMQFrame method;
+ method.decode(buffer);
+ frames.append(method);
+
+ AMQFrame header;
+ header.decode(buffer);
+ frames.append(header);
+}
+void MessageTransfer::decodeContent(framing::Buffer& buffer)
+{
+ decodeContent(buffer, buffer.available());
+}
+
+void MessageTransfer::decodeContent(framing::Buffer& buffer, size_t size)
+{
+ if (size) {
+ //get the data as a string and set that as the content
+ //body on a frame then add that frame to the frameset
+ AMQFrame frame((AMQContentBody()));
+ frame.castBody<AMQContentBody>()->decode(buffer, size);
+ frame.setFirstSegment(false);
+ frames.append(frame);
+ } else {
+ //adjust header flags
+ MarkLastSegment f;
+ frames.map_if(f, TypeFilter<HEADER_BODY>());
+ }
+}
+
+void MessageTransfer::encode(framing::Buffer& buffer) const
+{
+ //encode method and header frames
+ EncodeFrame f1(buffer);
+ frames.map_if(f1, TypeFilter2<METHOD_BODY, HEADER_BODY>());
+
+ //then encode the payload of each content frame
+ framing::EncodeBody f2(buffer);
+ frames.map_if(f2, TypeFilter<CONTENT_BODY>());
+}
+
+void MessageTransfer::encodeContent(framing::Buffer& buffer) const
+{
+ //encode the payload of each content frame
+ EncodeBody f2(buffer);
+ frames.map_if(f2, TypeFilter<CONTENT_BODY>());
+}
+
+uint32_t MessageTransfer::encodedSize() const
+{
+ return encodedHeaderSize() + encodedContentSize();
+}
+
+uint32_t MessageTransfer::encodedContentSize() const
+{
+ return frames.getContentSize();
+}
+
+uint32_t MessageTransfer::encodedHeaderSize() const
+{
+ //add up the size for all method and header frames in the frameset
+ SumFrameSize sum;
+ frames.map_if(sum, TypeFilter2<METHOD_BODY, HEADER_BODY>());
+ return sum.getSize();
+}
+
+bool MessageTransfer::isQMFv2() const
+{
+ const framing::MessageProperties* props = getProperties<framing::MessageProperties>();
+ return props && props->getAppId() == QMF2 && props->hasApplicationHeaders();
+}
+
+bool MessageTransfer::isQMFv2(const qpid::broker::Message& message)
+{
+ const MessageTransfer* transfer = dynamic_cast<const MessageTransfer*>(&message.getEncoding());
+ return transfer && transfer->isQMFv2();
+}
+
+bool MessageTransfer::isLastQMFResponse(const std::string correlation) const
+{
+ const framing::MessageProperties* props = getProperties<framing::MessageProperties>();
+ return props && props->getCorrelationId() == correlation
+ && props->hasApplicationHeaders() && !props->getApplicationHeaders().isSet(PARTIAL);
+}
+
+bool MessageTransfer::isLastQMFResponse(const qpid::broker::Message& message, const std::string correlation)
+{
+ const MessageTransfer* transfer = dynamic_cast<const MessageTransfer*>(&message.getEncoding());
+ return transfer && transfer->isLastQMFResponse(correlation);
+}
+
+
+void MessageTransfer::processProperties(qpid::amqp::MapHandler& handler) const
+{
+ const qpid::framing::MessageProperties* mp = getProperties<qpid::framing::MessageProperties>();
+ if (mp && mp->hasApplicationHeaders()) {
+ const FieldTable ft = mp->getApplicationHeaders();
+ for (FieldTable::const_iterator i = ft.begin(); i != ft.end(); ++i) {
+ qpid::types::Variant v;
+ qpid::amqp_0_10::translate(i->second, v);
+ qpid::amqp::CharSequence key = {i->first.data(), i->first.size()};
+ switch (v.getType()) {
+ case qpid::types::VAR_VOID:
+ handler.handleVoid(key); break;
+ case qpid::types::VAR_BOOL:
+ handler.handleBool(key, v); break;
+ case qpid::types::VAR_UINT8:
+ handler.handleUint8(key, v); break;
+ case qpid::types::VAR_UINT16:
+ handler.handleUint8(key, v); break;
+ case qpid::types::VAR_UINT32:
+ handler.handleUint32(key, v); break;
+ case qpid::types::VAR_UINT64:
+ handler.handleUint64(key, v); break;
+ case qpid::types::VAR_INT8:
+ handler.handleInt8(key, v); break;
+ case qpid::types::VAR_INT16:
+ handler.handleInt16(key, v); break;
+ case qpid::types::VAR_INT32:
+ handler.handleInt32(key, v); break;
+ case qpid::types::VAR_INT64:
+ handler.handleInt64(key, v); break;
+ case qpid::types::VAR_FLOAT:
+ handler.handleFloat(key, v); break;
+ case qpid::types::VAR_DOUBLE:
+ handler.handleDouble(key, v); break;
+ case qpid::types::VAR_STRING: {
+ std::string s(v);
+ qpid::amqp::CharSequence value = {s.data(), s.size()};
+ qpid::amqp::CharSequence encoding = {0, 0};
+ handler.handleString(key, value, encoding);
+ break;
+ }
+ case qpid::types::VAR_MAP:
+ case qpid::types::VAR_LIST:
+ case qpid::types::VAR_UUID:
+ QPID_LOG(debug, "Unhandled key!" << v);
+ break;
+ }
+ }
+ }
+}
+
+std::string MessageTransfer::getUserId() const
+{
+ const qpid::framing::MessageProperties* mp = getProperties<qpid::framing::MessageProperties>();
+ if (mp && mp->hasUserId()) return mp->getUserId();
+ else return std::string();
+
+}
+MessageTransfer::MessageTransfer(const qpid::framing::FrameSet& f) : frames(f), requiredCredit(0) {}
+
+boost::intrusive_ptr<PersistableMessage> MessageTransfer::merge(const std::map<std::string, qpid::types::Variant>& annotations) const
+{
+ boost::intrusive_ptr<MessageTransfer> clone(new MessageTransfer(this->frames));
+ qpid::framing::MessageProperties* mp = clone->frames.getHeaders()->get<qpid::framing::MessageProperties>(true);
+ for (qpid::types::Variant::Map::const_iterator i = annotations.begin(); i != annotations.end(); ++i) {
+ mp->getApplicationHeaders().set(i->first, qpid::amqp_0_10::translate(i->second));
+ }
+ return clone;
+}
+}}} // namespace qpid::broker::amqp_0_10
diff --git a/qpid/cpp/src/qpid/broker/amqp_0_10/MessageTransfer.h b/qpid/cpp/src/qpid/broker/amqp_0_10/MessageTransfer.h
new file mode 100644
index 0000000000..513bbe1bfb
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/amqp_0_10/MessageTransfer.h
@@ -0,0 +1,138 @@
+#ifndef QPID_BROKER_AMQP_0_10_MESSAGETRANSFER_H
+#define QPID_BROKER_AMQP_0_10_MESSAGETRANSFER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/framing/FrameSet.h"
+#include "qpid/broker/Message.h"
+#include "qpid/broker/PersistableMessage.h"
+#include "qpid/types/Variant.h"
+
+namespace qpid {
+namespace broker {
+class Queue;
+namespace amqp_0_10 {
+
+/**
+ *
+ */
+class MessageTransfer : public qpid::broker::Message::SharedStateImpl, public qpid::broker::PersistableMessage
+{
+ public:
+ QPID_BROKER_EXTERN MessageTransfer();
+ QPID_BROKER_EXTERN MessageTransfer(const qpid::framing::SequenceNumber&);
+
+ std::string getRoutingKey() const;
+ bool isPersistent() const;
+ uint8_t getPriority() const;
+ uint64_t getContentSize() const;
+ uint64_t getMessageSize() const;
+ qpid::amqp::MessageId getMessageId() const;
+ qpid::amqp::MessageId getCorrelationId() const;
+ std::string getPropertyAsString(const std::string& key) const;
+ std::string getAnnotationAsString(const std::string& key) const;
+ bool getTtl(uint64_t&) const;
+ bool hasExpiration() const;
+ std::string getExchangeName() const;
+ void processProperties(qpid::amqp::MapHandler&) const;
+ std::string getUserId() const;
+ void setTimestamp();
+ uint64_t getTimestamp() const;
+
+ bool requiresAccept() const;
+ const qpid::framing::SequenceNumber& getCommandId() const;
+ QPID_BROKER_EXTERN qpid::framing::FrameSet& getFrames();
+ QPID_BROKER_EXTERN const qpid::framing::FrameSet& getFrames() const;
+
+ template <class T> const T* getProperties() const {
+ const qpid::framing::AMQHeaderBody* p = frames.getHeaders();
+ return p->get<T>();
+ }
+
+ template <class T> const T* hasProperties() const {
+ const qpid::framing::AMQHeaderBody* p = frames.getHeaders();
+ return p->get<T>();
+ }
+ template <class T> const T* getMethod() const {
+ return frames.as<T>();
+ }
+
+ template <class T> T* getMethod() {
+ return frames.as<T>();
+ }
+
+ template <class T> bool isA() const {
+ return frames.isA<T>();
+ }
+
+ template <class T> void eraseProperties() {
+ qpid::framing::AMQHeaderBody* p = frames.getHeaders();
+ p->erase<T>();
+ }
+ std::string getContent() const;
+ uint32_t getRequiredCredit() const;
+ void computeRequiredCredit();
+
+ void clearApplicationHeadersFlag();
+ void sendContent(framing::FrameHandler& out, uint16_t maxFrameSize) const;
+ void sendHeader(framing::FrameHandler& out, uint16_t maxFrameSize, bool redelivered, uint64_t ttl, const qpid::types::Variant::Map& annotations) const;
+
+ void decodeHeader(framing::Buffer& buffer);
+ void decodeContent(framing::Buffer& buffer);
+ void decodeContent(framing::Buffer& buffer, size_t size);
+
+ void encode(framing::Buffer& buffer) const;
+ uint32_t encodedSize() const;
+
+ /**
+ * @returns the size of the buffer needed to encode the
+ * 'header' of this message (not just the header frame,
+ * but other meta data e.g.routing key and exchange)
+ */
+ uint32_t encodedHeaderSize() const;
+ boost::intrusive_ptr<PersistableMessage> merge(const std::map<std::string, qpid::types::Variant>& annotations) const;
+
+ QPID_BROKER_EXTERN bool isQMFv2() const;
+ QPID_BROKER_EXTERN bool isLastQMFResponse(const std::string correlation) const;
+
+ static bool isImmediateDeliveryRequired(const qpid::broker::Message& message);
+ static MessageTransfer& get(qpid::broker::Message& message) {
+ return *dynamic_cast<MessageTransfer*>(&message.getSharedState());
+ }
+ static const MessageTransfer& get(const qpid::broker::Message& message) {
+ return *dynamic_cast<const MessageTransfer*>(&message.getEncoding());
+ }
+ QPID_BROKER_EXTERN static bool isQMFv2(const qpid::broker::Message& message);
+ QPID_BROKER_EXTERN static bool isLastQMFResponse(const qpid::broker::Message& message, const std::string correlation);
+ private:
+ qpid::framing::FrameSet frames;
+ uint32_t requiredCredit;
+ bool cachedRequiredCredit;
+
+ MessageTransfer(const qpid::framing::FrameSet&);
+ void encodeHeader(framing::Buffer& buffer) const;
+ uint32_t encodedContentSize() const;
+ void encodeContent(framing::Buffer& buffer) const;
+};
+}}} // namespace qpid::broker::amqp_0_10
+
+#endif /*!QPID_BROKER_AMQP_0_10_MESSAGETRANSFER_H*/
diff --git a/qpid/cpp/src/qpid/broker/management-schema.xml b/qpid/cpp/src/qpid/broker/management-schema.xml
new file mode 100644
index 0000000000..debc1a4af2
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/management-schema.xml
@@ -0,0 +1,624 @@
+<schema package="org.apache.qpid.broker">
+
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+
+ <!-- Type information:
+
+ Numeric types with "_wm" suffix are watermarked numbers. These are compound
+ values containing a current value, and a low and high water mark for the reporting
+ interval. The low and high water marks are set to the current value at the
+ beginning of each interval and track the minimum and maximum values of the statistic
+ over the interval respectively.
+
+ Access rights for configuration elements:
+
+ RO => Read Only
+ RC => Read/Create, can be set at create time only, read-only thereafter
+ RW => Read/Write
+
+ If access rights are omitted for a property, they are assumed to be RO.
+
+ -->
+
+ <!-- Questions: Does C++ broker round-robin dests on queues? -->
+
+ <!--
+ ===============================================================
+ System
+ ===============================================================
+ -->
+ <class name="System">
+ <property name="systemId" index="y" type="uuid" access="RC"/>
+
+ <property name="osName" type="sstr" access="RO" desc="Operating System Name"/>
+ <property name="nodeName" type="sstr" access="RO" desc="Node Name"/>
+ <property name="release" type="sstr" access="RO"/>
+ <property name="version" type="sstr" access="RO"/>
+ <property name="machine" type="sstr" access="RO"/>
+
+ </class>
+
+ <!--
+ ===============================================================
+ Memory
+ ===============================================================
+ -->
+ <class name="Memory">
+ <property name="name" type="sstr" access="RC" index="y" desc="Index for the broker at this agent"/>
+ <property name="malloc_arena" type="uint64" access="RO" optional="y" desc="Total size of memory allocated with `sbrk' by `malloc', in bytes"/>
+ <property name="malloc_ordblks" type="uint64" access="RO" optional="y" desc="The number of chunks not in use"/>
+ <property name="malloc_hblks" type="uint64" access="RO" optional="y" desc="Total number of chunks allocated with `mmap'"/>
+ <property name="malloc_hblkhd" type="uint64" access="RO" optional="y" desc="Total size of memory allocated with `mmap', in bytes"/>
+ <property name="malloc_uordblks" type="uint64" access="RO" optional="y" desc="Total size of memory occupied by chunks handed out by `malloc'"/>
+ <property name="malloc_fordblks" type="uint64" access="RO" optional="y" desc="Total size of memory occupied by free (not in use) chunks"/>
+ <property name="malloc_keepcost" type="uint64" access="RO" optional="y" desc="The size of the top-most releasable chunk that normally borders the end of the heap"/>
+ </class>
+
+ <!--
+ ===============================================================
+ Broker
+ ===============================================================
+ -->
+ <class name="Broker">
+ <property name="name" type="sstr" access="RC" index="y" desc="Index for the broker at this agent"/>
+ <property name="systemRef" type="objId" references="System" access="RO" desc="System ID" parentRef="y"/>
+ <property name="port" type="uint16" access="RO" desc="TCP Port for AMQP Service"/>
+ <property name="workerThreads" type="uint16" access="RO" desc="Thread pool size"/>
+ <property name="maxConns" type="uint16" access="RO" desc="Maximum allowed connections"/>
+ <property name="connBacklog" type="uint16" access="RO" desc="Connection backlog limit for listening socket"/>
+ <property name="stagingThreshold" type="uint32" access="RO" desc="Deprecated"/>
+ <property name="mgmtPublish" type="bool" access="RO" desc="Broker's management agent sends unsolicited data on the publish interval"/>
+ <property name="mgmtPubInterval" type="uint16" access="RW" unit="second" min="1" desc="Interval for management broadcasts"/>
+ <property name="version" type="sstr" access="RO" desc="Running software version"/>
+ <property name="dataDir" type="lstr" access="RO" optional="y" desc="Persistent configuration storage location"/>
+ <statistic name="uptime" type="deltaTime"/>
+
+ <statistic name="queueCount" type="count64" unit="queue" desc="Number of queues in the broker"/>
+ <statistic name="msgTotalEnqueues" type="count64" unit="message" desc="Total messages enqueued to broker"/>
+ <statistic name="msgTotalDequeues" type="count64" unit="message" desc="Total messages dequeued from broker"/>
+ <statistic name="byteTotalEnqueues" type="count64" unit="octet" desc="Total bytes enqueued to broker"/>
+ <statistic name="byteTotalDequeues" type="count64" unit="octet" desc="Total bytes dequeued from broker"/>
+ <statistic name="msgDepth" type="count64" unit="message" desc="Current number of messages on queues in broker" assign="msgTotalEnqueues - msgTotalDequeues"/>
+ <statistic name="byteDepth" type="count64" unit="octet" desc="Current number of bytes on queues in broker" assign="byteTotalEnqueues - byteTotalDequeues"/>
+ <statistic name="msgPersistEnqueues" type="count64" unit="message" desc="Total persistent messages enqueued to broker"/>
+ <statistic name="msgPersistDequeues" type="count64" unit="message" desc="Total persistent messages dequeued from broker"/>
+ <statistic name="bytePersistEnqueues" type="count64" unit="octet" desc="Total persistent bytes enqueued to broker"/>
+ <statistic name="bytePersistDequeues" type="count64" unit="octet" desc="Total persistent bytes dequeued from broker"/>
+ <statistic name="msgTxnEnqueues" type="count64" unit="message" desc="Total transactional messages enqueued to broker"/>
+ <statistic name="msgTxnDequeues" type="count64" unit="message" desc="Total transactional messages dequeued from broker"/>
+ <statistic name="byteTxnEnqueues" type="count64" unit="octet" desc="Total transactional bytes enqueued to broker"/>
+ <statistic name="byteTxnDequeues" type="count64" unit="octet" desc="Total transactional bytes dequeued from broker"/>
+ <statistic name="msgFtdEnqueues" type="count64" unit="message" desc="Deprecated"/>
+ <statistic name="msgFtdDequeues" type="count64" unit="message" desc="Deprecated"/>
+ <statistic name="byteFtdEnqueues" type="count64" unit="octet" desc="Deprecated"/>
+ <statistic name="byteFtdDequeues" type="count64" unit="octet" desc="Deprecated"/>
+ <statistic name="msgFtdDepth" type="count64" unit="message" desc="Deprecated"/>
+ <statistic name="byteFtdDepth" type="count64" unit="octet" desc="Deprecated"/>
+ <statistic name="releases" type="count64" unit="message" desc="Acquired messages reinserted into the queue"/>
+ <statistic name="acquires" type="count64" unit="message" desc="Messages acquired from the queue"/>
+ <statistic name="discardsNoRoute" type="count64" unit="message" desc="Messages discarded due to no-route from exchange"/>
+ <statistic name="discardsTtl" type="count64" unit="message" desc="Messages discarded due to TTL expiration"/>
+ <statistic name="discardsRing" type="count64" unit="message" desc="Messages discarded due to ring-queue overflow"/>
+ <statistic name="discardsLvq" type="count64" unit="message" desc="Messages discarded due to LVQ insert"/>
+ <statistic name="discardsOverflow" type="count64" unit="message" desc="Messages discarded due to reject-policy overflow"/>
+ <statistic name="discardsSubscriber" type="count64" unit="message" desc="Messages discarded due to subscriber reject"/>
+ <statistic name="discardsPurge" type="count64" unit="message" desc="Messages discarded due to management purge"/>
+ <statistic name="reroutes" type="count64" unit="message" desc="Messages dequeued to management re-route"/>
+ <statistic name="abandoned" type="count64" unit="message" desc="Messages left in a deleted queue"/>
+ <statistic name="abandonedViaAlt" type="count64" unit="message" desc="Messages routed to alternate exchange from a deleted queue"/>
+
+ <method name="echo" desc="Request a response to test the path to the management broker">
+ <arg name="sequence" dir="IO" type="uint32"/>
+ <arg name="body" dir="IO" type="lstr"/>
+ </method>
+
+ <method name="connect" desc="Establish a connection to another broker">
+ <arg name="host" dir="I" type="sstr"/>
+ <arg name="port" dir="I" type="uint32"/>
+ <arg name="durable" dir="I" type="bool"/>
+ <arg name="authMechanism" dir="I" type="sstr"/>
+ <arg name="username" dir="I" type="sstr"/>
+ <arg name="password" dir="I" type="sstr"/>
+ <arg name="transport" dir="I" type="sstr"/>
+ </method>
+
+ <method name="queueMoveMessages" desc="Move messages from one queue to another">
+ <arg name="srcQueue" dir="I" type="sstr" desc="Source queue"/>
+ <arg name="destQueue" dir="I" type="sstr" desc="Destination queue"/>
+ <arg name="qty" dir="I" type="uint32" desc="# of messages to move. 0 means all messages"/>
+ <arg name="filter" dir="I" type="map" desc="if specified, move only those messages matching this filter"/>
+ </method>
+
+ <method name="setLogLevel" desc="Set the log level">
+ <arg name="level" dir="I" type="sstr"/>
+ </method>
+
+ <method name="getLogLevel" desc="Get the current log level">
+ <arg name="level" dir="O" type="sstr"/>
+ </method>
+
+ <method name="getTimestampConfig" desc="Get the message timestamping configuration">
+ <arg name="receive" dir="O" type="bool" desc="True if received messages are timestamped."/>
+ </method>
+
+ <method name="setTimestampConfig" desc="Set the message timestamping configuration">
+ <arg name="receive" dir="I" type="bool" desc="Set true to enable timestamping received messages."/>
+ </method>
+
+ <method name="create" desc="Create an object of the specified type">
+ <arg name="type" dir="I" type="sstr" desc="The type of object to create"/>
+ <arg name="name" dir="I" type="sstr" desc="The name of the object to create"/>
+ <arg name="properties" dir="I" type="map" desc="Type specific object properties"/>
+ <arg name="strict" dir="I" type="bool" desc="If specified, treat unrecognised object properties as an error"/>
+ </method>
+
+ <method name="delete" desc="Delete an object of the specified type">
+ <arg name="type" dir="I" type="sstr" desc="The type of object to delete"/>
+ <arg name="name" dir="I" type="sstr" desc="The name of the object to delete"/>
+ <arg name="options" dir="I" type="map" desc="Type specific object options for deletion"/>
+ </method>
+
+ <method name="query" desc="Query the current state of an object.">
+ <arg name="type" dir="I" type="sstr" desc="The type of object to query."/>
+ <arg name="name" dir="I" type="sstr" desc="The name of the object to query"/>
+ <arg name="results" dir="O" type="map" desc="A snapshot of the object's state."/>
+ </method>
+
+ <method name="getLogHiresTimestamp" desc="Get the high resolution timestamp in logs">
+ <arg name="logHires" dir="O" type="bool" desc="True if high resolution timestamp in logs is enabled."/>
+ </method>
+
+ <method name="setLogHiresTimestamp" desc="Set the high resolution timestamp in logs">
+ <arg name="logHires" dir="I" type="bool" desc="True to enable enable high resolution timestamp in logs."/>
+ </method>
+
+ <method name="queueRedirect" desc="Enable/disable delivery redirect for indicated queues">
+ <arg name="sourceQueue" dir="I" type="sstr" desc="Source queue."/>
+ <arg name="targetQueue" dir="I" type="sstr" desc="Redirect target queue. Blank disables redirect."/>
+ </method>
+
+ <method name="shutdown" desc="Shutdown the broker">
+ </method>
+
+ </class>
+
+ <!--
+ ===============================================================
+ Management Agent
+ ===============================================================
+ -->
+ <class name="Agent">
+ <property name="connectionRef" type="objId" references="Connection" access="RO" index="y"/>
+ <property name="label" type="sstr" access="RO" desc="Label for agent"/>
+ <property name="registeredTo" type="objId" references="Broker" access="RO" desc="Broker agent is registered to"/>
+ <property name="systemId" type="uuid" access="RO" desc="Identifier of system where agent resides"/>
+ <property name="brokerBank" type="uint32" access="RO" desc="Assigned object-id broker bank"/>
+ <property name="agentBank" type="uint32" access="RO" desc="Assigned object-id agent bank"/>
+ </class>
+
+ <!--
+ ===============================================================
+ Virtual Host
+ ===============================================================
+ -->
+ <class name="Vhost">
+ <property name="brokerRef" type="objId" references="Broker" access="RC" index="y" parentRef="y"/>
+ <property name="name" type="sstr" access="RC" index="y"/>
+ <property name="federationTag" type="sstr" access="RO"/>
+ </class>
+
+ <!--
+ ===============================================================
+ Queue
+ ===============================================================
+ -->
+ <class name="Queue">
+ <property name="vhostRef" type="objId" references="Vhost" access="RC" index="y" parentRef="y"/>
+ <property name="name" type="sstr" access="RC" index="y"/>
+
+ <property name="durable" type="bool" access="RC"/>
+ <property name="autoDelete" type="bool" access="RC"/>
+ <property name="exclusive" type="bool" access="RO"/>
+ <property name="arguments" type="map" access="RO" desc="Arguments supplied in queue.declare"/>
+ <property name="altExchange" type="objId" references="Exchange" access="RO" optional="y"/>
+
+ <statistic name="msgTotalEnqueues" type="count64" unit="message" desc="Total messages enqueued"/>
+ <statistic name="msgTotalDequeues" type="count64" unit="message" desc="Total messages dequeued"/>
+ <statistic name="msgTxnEnqueues" type="count64" unit="message" desc="Transactional messages enqueued"/>
+ <statistic name="msgTxnDequeues" type="count64" unit="message" desc="Transactional messages dequeued"/>
+ <statistic name="msgPersistEnqueues" type="count64" unit="message" desc="Persistent messages enqueued"/>
+ <statistic name="msgPersistDequeues" type="count64" unit="message" desc="Persistent messages dequeued"/>
+ <statistic name="msgDepth" type="count64" unit="message" desc="Current size of queue in messages" assign="msgTotalEnqueues - msgTotalDequeues"/>
+ <statistic name="byteDepth" type="count64" unit="octet" desc="Current size of queue in bytes" assign="byteTotalEnqueues - byteTotalDequeues"/>
+ <statistic name="byteTotalEnqueues" type="count64" unit="octet" desc="Total messages enqueued"/>
+ <statistic name="byteTotalDequeues" type="count64" unit="octet" desc="Total messages dequeued"/>
+ <statistic name="byteTxnEnqueues" type="count64" unit="octet" desc="Transactional messages enqueued"/>
+ <statistic name="byteTxnDequeues" type="count64" unit="octet" desc="Transactional messages dequeued"/>
+ <statistic name="bytePersistEnqueues" type="count64" unit="octet" desc="Persistent messages enqueued"/>
+ <statistic name="bytePersistDequeues" type="count64" unit="octet" desc="Persistent messages dequeued"/>
+
+ <!-- Flow-to-disk Statistics, deprecated -->
+
+ <statistic name="msgFtdEnqueues" type="count64" unit="message" desc="Deprecated"/>
+ <statistic name="msgFtdDequeues" type="count64" unit="message" desc="Deprecated"/>
+ <statistic name="byteFtdEnqueues" type="count64" unit="octet" desc="Deprecated"/>
+ <statistic name="byteFtdDequeues" type="count64" unit="octet" desc="Deprecated"/>
+ <statistic name="msgFtdDepth" type="count64" unit="message" desc="Deprecated"/>
+ <statistic name="byteFtdDepth" type="count64" unit="octet" desc="Deprecated"/>
+
+ <!-- Acquire and Release Statistics - These do not affect msgDepth since msgDepth includes acquired-but-not-completed messages. -->
+
+ <statistic name="releases" type="count64" unit="message" desc="Acquired messages reinserted into the queue"/>
+ <statistic name="acquires" type="count64" unit="message" desc="Messages acquired from the queue"/>
+
+ <!-- Dequeue Details - all of these are included in msgTotalDequeues -->
+
+ <statistic name="discardsTtl" type="count64" unit="message" desc="Messages discarded due to TTL expiration"/>
+ <statistic name="discardsRing" type="count64" unit="message" desc="Messages discarded due to ring-queue overflow"/>
+ <statistic name="discardsLvq" type="count64" unit="message" desc="Messages discarded due to LVQ insert"/>
+ <statistic name="discardsOverflow" type="count64" unit="message" desc="Messages discarded due to reject-policy overflow"/>
+ <statistic name="discardsSubscriber" type="count64" unit="message" desc="Messages discarded due to subscriber reject"/>
+ <statistic name="discardsPurge" type="count64" unit="message" desc="Messages discarded due to management purge"/>
+ <statistic name="reroutes" type="count64" unit="message" desc="Messages dequeued to management re-route"/>
+
+ <statistic name="consumerCount" type="hilo32" unit="consumer" desc="Current consumers on queue"/>
+ <statistic name="bindingCount" type="hilo32" unit="binding" desc="Current bindings"/>
+ <statistic name="unackedMessages" type="hilo32" unit="message" desc="Deprecated"/>
+ <statistic name="messageLatency" type="mmaTime" unit="nanosecond" desc="Deprecated"/>
+ <statistic name="flowStopped" type="bool" desc="Flow control active."/>
+ <statistic name="flowStoppedCount" type="count32" desc="Number of times flow control was activated for this queue"/>
+
+ <statistic name="redirectPeer" type="sstr" desc="Partner queue for redirected pair"/>
+ <statistic name="redirectSource" type="bool" desc="This queue is the redirect source"/>
+ <statistic name="creator" type="sstr" desc="userId of creator of the queue"/>
+
+ <method name="purge" desc="Discard all or some messages on a queue">
+ <arg name="request" dir="I" type="uint32" desc="0 for all messages or n>0 for n messages"/>
+ <arg name="filter" dir="I" type="map" desc="if specified, purge only those messages matching this filter"/>
+ </method>
+
+ <method name="reroute" desc="Remove all or some messages on this queue and route them to an exchange">
+ <arg name="request" dir="I" type="uint32" desc="0 for all messages or n>0 for n messages"/>
+ <arg name="useAltExchange" dir="I" type="bool" desc="Iff true, use the queue's configured alternate exchange; iff false, use exchange named in the 'exchange' argument"/>
+ <arg name="exchange" dir="I" type="sstr" desc="Name of the exchange to route the messages through"/>
+ <arg name="filter" dir="I" type="map" desc="if specified, reroute only those messages matching this filter"/>
+ </method>
+ </class>
+
+ <!--
+ ===============================================================
+ Exchange
+ ===============================================================
+ -->
+ <class name="Exchange">
+ <property name="vhostRef" type="objId" references="Vhost" access="RC" index="y" parentRef="y"/>
+ <property name="name" type="sstr" access="RC" index="y"/>
+ <property name="type" type="sstr" access="RO"/>
+ <property name="durable" type="bool" access="RO"/>
+ <property name="autoDelete" type="bool" access="RO"/>
+ <property name="altExchange" type="objId" references="Exchange" access="RO" optional="y"/>
+ <property name="arguments" type="map" access="RO" desc="Arguments supplied in exchange.declare"/>
+
+ <statistic name="producerCount" type="hilo32" desc="Unused"/>
+ <statistic name="bindingCount" type="hilo32" desc="Current bindings"/>
+ <statistic name="msgReceives" type="count64" desc="Total messages received"/>
+ <statistic name="msgDrops" type="count64" desc="Total messages dropped (no matching key)"/>
+ <statistic name="msgRoutes" type="count64" desc="Total routed messages"/>
+ <statistic name="byteReceives" type="count64" desc="Total bytes received"/>
+ <statistic name="byteDrops" type="count64" desc="Total bytes dropped (no matching key)"/>
+ <statistic name="byteRoutes" type="count64" desc="Total routed bytes"/>
+ </class>
+
+ <!--
+ ===============================================================
+ Binding
+ ===============================================================
+ -->
+ <class name="Binding">
+ <property name="exchangeRef" type="objId" references="Exchange" access="RC" index="y" parentRef="y"/>
+ <property name="queueRef" type="objId" references="Queue" access="RC" index="y"/>
+ <property name="bindingKey" type="lstr" access="RC" index="y"/>
+ <property name="arguments" type="map" access="RC"/>
+ <property name="origin" type="sstr" access="RO" optional="y"/>
+
+ <statistic name="msgMatched" type="count64"/>
+ </class>
+
+ <!--
+ ===============================================================
+ Subscription
+ ===============================================================
+ -->
+ <class name="Subscription">
+ <property name="sessionRef" type="objId" references="Session" access="RC" index="y" parentRef="y"/>
+ <property name="queueRef" type="objId" references="Queue" access="RC" index="y"/>
+ <property name="name" type="sstr" access="RC" index="y"/>
+ <property name="browsing" type="bool" access="RC"/>
+ <property name="acknowledged" type="bool" access="RC"/>
+ <property name="exclusive" type="bool" access="RC"/>
+ <property name="creditMode" type="sstr" access="RO" desc="WINDOW or CREDIT"/>
+ <property name="arguments" type="map" access="RC"/>
+ <statistic name="delivered" type="count64" unit="message" desc="Messages delivered"/>
+ </class>
+
+ <!--
+ ===============================================================
+ Connection
+ ===============================================================
+ -->
+ <class name="Connection">
+ <property name="vhostRef" type="objId" references="Vhost" access="RC" index="y" parentRef="y"/>
+ <property name="address" type="sstr" access="RC" index="y"/>
+ <property name="incoming" type="bool" access="RC"/>
+ <property name="SystemConnection" type="bool" access="RC" desc="Deprecated"/>
+ <property name="userProxyAuth" type="bool" access="RO" desc="Deprecated"/>
+ <property name="federationLink" type="bool" access="RO" desc="Deprecated"/>
+ <property name="authIdentity" type="sstr" access="RO" desc="authId of connection if authentication enabled"/>
+ <property name="remoteProcessName" type="lstr" access="RO" optional="y" desc="Name of executable running as remote client"/>
+ <property name="remotePid" type="uint32" access="RO" optional="y" desc="Process ID of remote client"/>
+ <property name="remoteParentPid" type="uint32" access="RO" optional="y" desc="Parent Process ID of remote client"/>
+ <property name="shadow" type="bool" access="RO" desc="Deprecated"/>
+ <property name="saslMechanism" type="sstr" access="RO" desc="SASL mechanism"/>
+ <property name="saslSsf" type="uint16" access="RO" desc="SASL security strength factor"/>
+ <property name="remoteProperties" type="map" access="RO" desc="optional map of identifying information sent by the remote"/>
+ <property name="protocol" type="sstr" access="RC" desc="protocol in use"/>
+ <statistic name="closing" type="bool" desc="This client is closing by management request"/>
+ <statistic name="framesFromClient" type="count64"/>
+ <statistic name="framesToClient" type="count64"/>
+ <statistic name="bytesFromClient" type="count64"/>
+ <statistic name="bytesToClient" type="count64"/>
+ <statistic name="msgsFromClient" type="count64"/>
+ <statistic name="msgsToClient" type="count64"/>
+
+ <method name="close"/>
+ </class>
+
+
+ <!--
+ ===============================================================
+ AMQP 1.0 link for incoming transfers
+ ===============================================================
+ -->
+ <class name="Incoming">
+ <property name="sessionRef" type="objId" references="Session" access="RC" parentRef="y"/>
+ <property name="containerid" type="sstr" access="RC" index="y"/>
+ <property name="name" type="sstr" access="RC" index="y"/>
+ <property name="source" type="sstr" access="RC"/>
+ <property name="target" type="sstr" access="RC"/>
+ <property name="domain" type="sstr" access="RC"/>
+ <statistic name="transfers" type="count64" unit="message" desc="Messages transfered"/>
+ </class>
+ <!--
+ ===============================================================
+ AMQP 1.0 link for outgoing transfers
+ ===============================================================
+ -->
+ <class name="Outgoing">
+ <property name="sessionRef" type="objId" references="Session" access="RC" parentRef="y"/>
+ <property name="containerid" type="sstr" access="RC" index="y"/>
+ <property name="name" type="sstr" access="RC" index="y"/>
+ <property name="source" type="sstr" access="RC"/>
+ <property name="target" type="sstr" access="RC"/>
+ <property name="domain" type="sstr" access="RC"/>
+ <statistic name="transfers" type="count64" unit="message" desc="Messages transfered"/>
+ </class>
+ <!--
+ ===============================================================
+ AMQP 1.0 Domain
+ ===============================================================
+ -->
+ <class name="Domain">
+ <property name="name" type="sstr" access="RC" index="y"/>
+ <property name="durable" type="bool" access="RC"/>
+ <property name="url" type="sstr" access="RO"/>
+ <property name="mechanisms" type="sstr" access="RO"/>
+ <property name="username" type="sstr" access="RO"/>
+ <property name="password" type="sstr" access="RO"/>
+ </class>
+ <!--
+ ===============================================================
+ AMQP 1.0 Topic
+ ===============================================================
+ -->
+ <class name="Topic">
+ <property name="name" type="sstr" access="RC" index="y"/>
+ <property name="exchangeRef" type="objId" references="Exchange" access="RC"/>
+ <property name="durable" type="bool" access="RC"/>
+ <property name="properties" type="map" access="RO"/>
+ </class>
+ <!--
+ ===============================================================
+ AMQP 1.0 QueuePolicy
+ ===============================================================
+ -->
+ <class name="QueuePolicy">
+ <property name="name" type="sstr" access="RC" index="y"/>
+ <property name="properties" type="map" access="RO"/>
+ </class>
+ <!--
+ ===============================================================
+ AMQP 1.0 TopicPolicy
+ ===============================================================
+ -->
+ <class name="TopicPolicy">
+ <property name="name" type="sstr" access="RC" index="y"/>
+ <property name="properties" type="map" access="RO"/>
+ </class>
+
+
+ <!--
+ ===============================================================
+ Link
+ ===============================================================
+ -->
+ <class name="Link">
+
+ This class represents an inter-broker connection.
+
+ <property name="vhostRef" type="objId" references="Vhost" access="RC" index="y" parentRef="y"/>
+ <property name="name" type="sstr" access="RC" index="y"/>
+ <property name="host" type="sstr" access="RO"/>
+ <property name="port" type="uint16" access="RO"/>
+ <property name="transport" type="sstr" access="RO"/>
+ <property name="durable" type="bool" access="RC"/>
+ <property name="connectionRef" type="objId" references="Connection" access="RO"/>
+
+ <statistic name="state" type="sstr" desc="Operational state of the link"/>
+ <statistic name="lastError" type="lstr" desc="Reason link is not operational"/>
+
+ <method name="close"/>
+
+ <method name="bridge" desc="Bridge messages over the link">
+ <arg name="durable" dir="I" type="bool"/>
+ <arg name="src" dir="I" type="sstr"/>
+ <arg name="dest" dir="I" type="sstr"/>
+ <arg name="key" dir="I" type="lstr"/>
+ <arg name="tag" dir="I" type="sstr"/>
+ <arg name="excludes" dir="I" type="sstr"/>
+ <arg name="srcIsQueue" dir="I" type="bool"/>
+ <arg name="srcIsLocal" dir="I" type="bool"/>
+ <arg name="dynamic" dir="I" type="bool"/>
+ <arg name="sync" dir="I" type="uint16"/>
+ <arg name="credit" dir="I" type="uint32" default="0xFFFFFFFF" desc="granted to peer, 0 = infinite"/>
+ </method>
+ </class>
+
+
+ <!--
+ ===============================================================
+ Bridge
+ ===============================================================
+ -->
+ <class name="Bridge">
+ <property name="linkRef" type="objId" references="Link" access="RC" index="y" parentRef="y"/>
+ <property name="name" type="sstr" access="RC" index="y"/>
+ <property name="channelId" type="uint16" access="RO"/>
+ <property name="durable" type="bool" access="RC"/>
+ <property name="src" type="sstr" access="RC"/>
+ <property name="dest" type="sstr" access="RC"/>
+ <property name="key" type="lstr" access="RC"/>
+ <property name="srcIsQueue" type="bool" access="RC"/>
+ <property name="srcIsLocal" type="bool" access="RC"/>
+ <property name="tag" type="sstr" access="RC"/>
+ <property name="excludes" type="sstr" access="RC"/>
+ <property name="dynamic" type="bool" access="RC"/>
+ <property name="sync" type="uint16" access="RC"/>
+ <property name="credit" type="uint32" access="RC"/>
+ <method name="close"/>
+ </class>
+
+
+ <!--
+ ===============================================================
+ Session
+ ===============================================================
+ -->
+ <class name="Session">
+ <property name="vhostRef" type="objId" references="Vhost" access="RC" index="y" parentRef="y"/>
+ <property name="name" type="sstr" access="RC" index="y"/>
+ <property name="fullName" type="lstr" access="RO" optional="y"/>
+ <property name="channelId" type="uint16" access="RO"/>
+ <property name="connectionRef" type="objId" references="Connection" access="RO"/>
+ <property name="detachedLifespan" type="uint32" access="RO" unit="second" desc="Deprecated"/>
+ <property name="attached" type="bool" access="RO"/>
+ <property name="expireTime" type="absTime" access="RO" optional="y"/>
+ <property name="maxClientRate" type="uint32" access="RO" unit="msgs/sec" optional="y" desc="Deprecated"/>
+
+ <statistic name="unackedMessages" type="uint64" unit="message" desc="Unacknowledged messages in the session"/>
+
+ <statistic name="TxnStarts" type="count64" unit="transaction" desc="Total transactions started "/>
+ <statistic name="TxnCommits" type="count64" unit="transaction" desc="Total transactions committed"/>
+ <statistic name="TxnRejects" type="count64" unit="transaction" desc="Total transactions rejected"/>
+ <statistic name="TxnCount" type="count32" unit="transaction" desc="Current pending transactions"/>
+
+ <statistic name="clientCredit" type="count32" unit="message" desc="Deprecated"/>
+
+ <statistic name="framesOutstanding" type="count32" desc="Deprecated"/>
+
+ <method name="solicitAck"/>
+ <method name="detach"/>
+ <method name="resetLifespan"/>
+ <method name="close"/>
+ </class>
+
+ <!--
+ ===============================================================
+ ManagementSetupState
+ ===============================================================
+
+ This thing is used during cluster recovery operations (and maybe
+ eventually elsewhere) to transmit assorted state from one broker to
+ another. At present, the two data propagated are the object number
+ counter and boot sequence, both of which are used for creating
+ object ids for newly-created objects.
+
+ -->
+ <class name="ManagementSetupState">
+ <!-- for reasons that aren't clear (to me, anyhow) you have to say
+ access="RO" to get accessor methods defined. RC or RW don't do
+ it. Probably this is documented someplace, but I couldn't find
+ it. -jrd -->
+ <property name="objectNum" type="uint64" access="RO" desc="Deprecated"/>
+ <property name="bootSequence" type="uint16" access="RO" desc="Deprecated"/>
+ </class>
+
+ <eventArguments>
+ <arg name="altEx" type="sstr" desc="Name of the alternate exchange"/>
+ <arg name="args" type="map" desc="Supplemental arguments or parameters supplied"/>
+ <arg name="autoDel" type="bool" desc="Created object is automatically deleted when no longer in use"/>
+ <arg name="dest" type="sstr" desc="Destination tag for a subscription"/>
+ <arg name="disp" type="sstr" desc="Disposition of a declaration: 'created' if object was created, 'existing' if object already existed"/>
+ <arg name="durable" type="bool" desc="Created object is durable"/>
+ <arg name="exName" type="sstr" desc="Name of an exchange"/>
+ <arg name="exType" type="sstr" desc="Type of an exchange"/>
+ <arg name="excl" type="bool" desc="Created object is exclusive for the use of the owner only"/>
+ <arg name="key" type="lstr" desc="Key text used for routing or binding"/>
+ <arg name="qName" type="sstr" desc="Name of a queue"/>
+ <arg name="reason" type="lstr" desc="Reason for a failure"/>
+ <arg name="rhost" type="sstr" desc="Address (i.e. DNS name, IP address, etc.) of a remotely connected host"/>
+ <arg name="user" type="sstr" desc="Authentication identity"/>
+ <arg name="qTarget" type="sstr" desc="Redirect target queue"/>
+ <arg name="msgDepth" type="count64" desc="Current size of queue in messages"/>
+ <arg name="byteDepth" type="count64" desc="Current size of queue in bytes"/>
+ <arg name="properties" type="map" desc="optional identifying information sent by the remote"/>
+ </eventArguments>
+
+ <event name="clientConnect" sev="inform" args="rhost, user, properties"/>
+ <event name="clientConnectFail" sev="warn" args="rhost, user, reason, properties"/>
+ <event name="clientDisconnect" sev="inform" args="rhost, user, properties"/>
+ <event name="brokerLinkUp" sev="inform" args="rhost"/>
+ <event name="brokerLinkDown" sev="warn" args="rhost"/>
+ <event name="queueDeclare" sev="inform" args="rhost, user, qName, durable, excl, autoDel, altEx, args, disp"/>
+ <event name="queueDelete" sev="inform" args="rhost, user, qName"/>
+ <event name="exchangeDeclare" sev="inform" args="rhost, user, exName, exType, altEx, durable, autoDel, args, disp"/>
+ <event name="exchangeDelete" sev="inform" args="rhost, user, exName"/>
+ <event name="bind" sev="inform" args="rhost, user, exName, qName, key, args"/>
+ <event name="unbind" sev="inform" args="rhost, user, exName, qName, key"/>
+ <event name="subscribe" sev="inform" args="rhost, user, qName, dest, excl, args"/>
+ <event name="unsubscribe" sev="inform" args="rhost, user, dest"/>
+ <event name="queueThresholdCrossedUpward" sev="inform" args="qName, msgDepth, byteDepth"/>
+ <event name="queueThresholdCrossedDownward" sev="inform" args="qName, msgDepth, byteDepth"/>
+ <event name="queueRedirect" sev="inform" args="qName, qTarget"/>
+ <event name="queueRedirectCancelled" sev="inform" args="qName, qTarget"/>
+
+ <!-- The following are deprecated -->
+ <event name="queueThresholdExceeded" sev="warn" args="qName, msgDepth, byteDepth"/>
+</schema>
+
diff --git a/qpid/cpp/src/qpid/broker/posix/BrokerDefaults.cpp b/qpid/cpp/src/qpid/broker/posix/BrokerDefaults.cpp
new file mode 100644
index 0000000000..e3cfa66b02
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/posix/BrokerDefaults.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/BrokerOptions.h"
+#include <stdlib.h>
+
+namespace qpid {
+namespace broker {
+
+const std::string BrokerOptions::DEFAULT_DATA_DIR_LOCATION("/tmp");
+const std::string BrokerOptions::DEFAULT_DATA_DIR_NAME("/.qpidd");
+const std::string BrokerOptions::DEFAULT_PAGED_QUEUE_DIR("/pq");
+
+std::string
+BrokerOptions::getHome() {
+ std::string home;
+ char *home_c = ::getenv("HOME");
+ if (home_c != 0)
+ home += home_c;
+ return home;
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/posix/SocketFDPlugin.cpp b/qpid/cpp/src/qpid/broker/posix/SocketFDPlugin.cpp
new file mode 100644
index 0000000000..d73c5713ea
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/posix/SocketFDPlugin.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/TransportFactory.h"
+
+#include "qpid/Options.h"
+#include "qpid/Plugin.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/AsynchIO.h"
+#include "qpid/sys/posix/BSDSocket.h"
+#include "qpid/sys/SocketTransport.h"
+
+#include <vector>
+
+#include <sys/stat.h>
+
+namespace qpid {
+namespace sys {
+
+struct SocketOptions : public Options {
+ std::vector<int> socketFds;
+
+ SocketOptions() :
+ socketFds(0)
+ {
+ addOptions()
+ ("socket-fd", optValue(socketFds, "FD"), "File descriptor for tcp listening socket");
+ }
+};
+
+bool isSocket(int fd)
+{
+ if (fd < 0 ) return false;
+
+ struct ::stat st_fd;
+ if (::fstat(fd, &st_fd) < 0) return false;
+
+ return S_ISSOCK(st_fd.st_mode);
+}
+
+// Static instance to initialise plugin
+static class SocketFDPlugin : public Plugin {
+ SocketOptions options;
+
+ Options* getOptions() { return &options; }
+
+ void earlyInitialize(Target&) {
+ }
+
+ void initialize(Target& target) {
+ broker::Broker* broker = dynamic_cast<broker::Broker*>(&target);
+ // Only provide to a Broker
+ if (broker) {
+ if (!options.socketFds.empty()) {
+ SocketAcceptor* sa = new SocketAcceptor(broker->getTcpNoDelay(), false, broker->getMaxNegotiateTime(), broker->getTimer());
+ for (unsigned i = 0; i<options.socketFds.size(); ++i) {
+ int fd = options.socketFds[i];
+ if (!isSocket(fd)) {
+ QPID_LOG(error, "Imported socket fd " << fd << ": isn't a socket");
+ continue;
+ }
+ Socket* s = new BSDSocket(fd);
+ sa->addListener(s);
+ QPID_LOG(notice, "Listening on imported socket: " << s->getLocalAddress());
+ }
+ broker->registerTransport("socket", TransportAcceptor::shared_ptr(sa), TransportConnector::shared_ptr(), 0);
+ } else {
+ QPID_LOG(debug, "No Socket fd specified");
+ }
+ }
+ }
+} socketFdPlugin;
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/broker/windows/BrokerDefaults.cpp b/qpid/cpp/src/qpid/broker/windows/BrokerDefaults.cpp
new file mode 100644
index 0000000000..6184e0450f
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/windows/BrokerDefaults.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/broker/BrokerOptions.h"
+#include <stdlib.h>
+#include <windows.h>
+
+namespace qpid {
+namespace broker {
+
+const std::string BrokerOptions::DEFAULT_DATA_DIR_LOCATION("\\TEMP");
+const std::string BrokerOptions::DEFAULT_DATA_DIR_NAME("\\QPIDD.DATA");
+const std::string BrokerOptions::DEFAULT_PAGED_QUEUE_DIR("\\PQ");
+
+std::string
+BrokerOptions::getHome() {
+ std::string home;
+#ifdef _MSC_VER
+ char home_c[MAX_PATH+1];
+ size_t unused;
+ if (0 == getenv_s (&unused, home_c, sizeof(home_c), "HOME"))
+ home += home_c;
+#else
+ char *home_c = getenv("HOME");
+ if (home_c)
+ home += home_c;
+#endif
+ return home;
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp b/qpid/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp
new file mode 100644
index 0000000000..19941c7909
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/windows/SaslAuthenticator.cpp
@@ -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.
+ *
+ */
+
+// This source is only used on Windows; SSPI is the Windows mechanism for
+// accessing authentication mechanisms, analogous to Cyrus SASL.
+
+#include "qpid/broker/amqp_0_10/Connection.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/log/Statement.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/sys/ConnectionOutputHandler.h"
+
+#include <windows.h>
+
+using namespace qpid::framing;
+using qpid::sys::SecurityLayer;
+
+using std::string;
+
+namespace qpid {
+namespace broker {
+
+class NullAuthenticator : public SaslAuthenticator
+{
+ qpid::broker::amqp_0_10::Connection& connection;
+ framing::AMQP_ClientProxy::Connection client;
+ string realm;
+public:
+ NullAuthenticator(qpid::broker::amqp_0_10::Connection& connection);
+ ~NullAuthenticator();
+ void getMechanisms(framing::Array& mechanisms);
+ void start(const std::string& mechanism, const std::string* response);
+ void step(const std::string&) {}
+ std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize);
+};
+
+class SspiAuthenticator : public SaslAuthenticator
+{
+ HANDLE userToken;
+ qpid::broker::amqp_0_10::Connection& connection;
+ framing::AMQP_ClientProxy::Connection client;
+
+public:
+ SspiAuthenticator(qpid::broker::amqp_0_10::Connection& connection);
+ ~SspiAuthenticator();
+ void getMechanisms(framing::Array& mechanisms);
+ void start(const std::string& mechanism, const std::string* response);
+ void step(const std::string& response);
+ std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize);
+};
+
+bool SaslAuthenticator::available(void)
+{
+ return true;
+}
+
+// Initialize the SASL mechanism; throw if it fails.
+void SaslAuthenticator::init(const std::string& /*saslName*/, const std::string& /*saslConfig*/)
+{
+ return;
+}
+
+void SaslAuthenticator::fini(void)
+{
+ return;
+}
+
+std::auto_ptr<SaslAuthenticator> SaslAuthenticator::createAuthenticator(qpid::broker::amqp_0_10::Connection& c)
+{
+ if (c.getBroker().isAuthenticating()) {
+ return std::auto_ptr<SaslAuthenticator>(new SspiAuthenticator(c));
+ } else {
+ return std::auto_ptr<SaslAuthenticator>(new NullAuthenticator(c));
+ }
+}
+
+NullAuthenticator::NullAuthenticator(qpid::broker::amqp_0_10::Connection& c) :
+ connection(c), client(c.getOutput()), realm("@"+c.getBroker().getRealm()) {}
+NullAuthenticator::~NullAuthenticator() {}
+
+void NullAuthenticator::getMechanisms(Array& mechanisms)
+{
+ mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value("ANONYMOUS")));
+ mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value("PLAIN")));
+}
+
+namespace {
+bool endsWith(const std::string& str, const std::string& ending) {
+ return (ending.size() <= str.size()) &&
+ (str.compare(str.size() - ending.size(), ending.size(), ending) == 0);
+}
+}
+
+void NullAuthenticator::start(const string& mechanism, const string* response)
+{
+ QPID_LOG(warning, "SASL: No Authentication Performed");
+ if (mechanism == "PLAIN") { // Old behavior
+ if (response && response->size() > 0 && (*response).c_str()[0] == (char) 0) {
+ string temp = response->substr(1);
+ string::size_type i = temp.find((char)0);
+ string uid = temp.substr(0, i);
+ string pwd = temp.substr(i + 1);
+ if (!endsWith(uid, realm)) uid += realm;
+ connection.setUserId(uid);
+ }
+ } else {
+ connection.setUserId("anonymous");
+ }
+ client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, 0);
+}
+
+std::auto_ptr<SecurityLayer> NullAuthenticator::getSecurityLayer(uint16_t)
+{
+ std::auto_ptr<SecurityLayer> securityLayer;
+ return securityLayer;
+}
+
+
+SspiAuthenticator::SspiAuthenticator(qpid::broker::amqp_0_10::Connection& c) : userToken(INVALID_HANDLE_VALUE), connection(c), client(c.getOutput())
+{
+}
+
+SspiAuthenticator::~SspiAuthenticator()
+{
+ if (INVALID_HANDLE_VALUE != userToken) {
+ CloseHandle(userToken);
+ userToken = INVALID_HANDLE_VALUE;
+ }
+}
+
+void SspiAuthenticator::getMechanisms(Array& mechanisms)
+{
+ mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value(string("ANONYMOUS"))));
+ mechanisms.add(boost::shared_ptr<FieldValue>(new Str16Value(string("PLAIN"))));
+ QPID_LOG(info, "SASL: Mechanism list: ANONYMOUS PLAIN");
+}
+
+void SspiAuthenticator::start(const string& mechanism, const string* response)
+{
+ QPID_LOG(info, "SASL: Starting authentication with mechanism: " << mechanism);
+ if (mechanism == "ANONYMOUS") {
+ connection.setUserId("anonymous");
+ client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, 0);
+ return;
+ }
+ if (mechanism != "PLAIN")
+ throw ConnectionForcedException("Unsupported mechanism");
+
+ // PLAIN's response is composed of 3 strings separated by 0 bytes:
+ // authorization id, authentication id (user), clear-text password.
+ if (!response || response->size() == 0)
+ throw ConnectionForcedException("Authentication failed");
+
+ string::size_type i = response->find((char)0);
+ string auth = response->substr(0, i);
+ string::size_type j = response->find((char)0, i+1);
+ string uid = response->substr(i+1, j-1);
+ string pwd = response->substr(j+1);
+ string dot(".");
+ int error = 0;
+ if (!LogonUser(const_cast<char*>(uid.c_str()),
+ const_cast<char*>(dot.c_str()),
+ const_cast<char*>(pwd.c_str()),
+ LOGON32_LOGON_NETWORK,
+ LOGON32_PROVIDER_DEFAULT,
+ &userToken))
+ error = GetLastError();
+ pwd.replace(0, string::npos, 1, (char)0);
+ if (error != 0) {
+ QPID_LOG(info,
+ "SASL: Auth failed [" << error << "]: " << qpid::sys::strError(error));
+ throw ConnectionForcedException("Authentication failed");
+ }
+
+ connection.setUserId(uid);
+ client.tune(framing::CHANNEL_MAX, connection.getFrameMax(), 0, 0);
+}
+
+void SspiAuthenticator::step(const string& /*response*/)
+{
+ QPID_LOG(info, "SASL: Need another step!!!");
+}
+
+std::auto_ptr<SecurityLayer> SspiAuthenticator::getSecurityLayer(uint16_t)
+{
+ std::auto_ptr<SecurityLayer> securityLayer;
+ return securityLayer;
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/broker/windows/SslProtocolFactory.cpp b/qpid/cpp/src/qpid/broker/windows/SslProtocolFactory.cpp
new file mode 100644
index 0000000000..6ff624ef75
--- /dev/null
+++ b/qpid/cpp/src/qpid/broker/windows/SslProtocolFactory.cpp
@@ -0,0 +1,313 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/TransportFactory.h"
+#include "qpid/sys/SocketTransport.h"
+
+#include "qpid/Plugin.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/AsynchIOHandler.h"
+#include "qpid/sys/ConnectionCodec.h"
+#include "qpid/sys/Socket.h"
+#include "qpid/sys/SocketAddress.h"
+#include "qpid/sys/SystemInfo.h"
+#include "qpid/sys/windows/SslAsynchIO.h"
+
+#include <boost/bind.hpp>
+#include <boost/ptr_container/ptr_vector.hpp>
+#include <memory>
+
+// security.h needs to see this to distinguish from kernel use.
+#define SECURITY_WIN32
+#include <security.h>
+#include <Schnlsp.h>
+#undef SECURITY_WIN32
+
+
+namespace qpid {
+namespace sys {
+
+class Timer;
+
+namespace windows {
+
+struct SslServerOptions : qpid::Options
+{
+ std::string certStore;
+ std::string certStoreLocation;
+ std::string certName;
+ uint16_t port;
+ bool clientAuth;
+
+ SslServerOptions() : qpid::Options("SSL Options"),
+ certStore("My"),
+ certStoreLocation("CurrentUser"),
+ certName("localhost"),
+ port(5671),
+ clientAuth(false)
+ {
+ qpid::Address me;
+ if (qpid::sys::SystemInfo::getLocalHostname(me))
+ certName = me.host;
+
+ addOptions()
+ ("ssl-cert-store", optValue(certStore, "NAME"), "Local store name from which to obtain certificate")
+ ("ssl-cert-store-location", optValue(certStoreLocation, "NAME"),
+ "Local store name location for certificates ( CurrentUser | LocalMachine | CurrentService )")
+ ("ssl-cert-name", optValue(certName, "NAME"), "Name of the certificate to use")
+ ("ssl-port", optValue(port, "PORT"), "Port on which to listen for SSL connections")
+ ("ssl-require-client-authentication", optValue(clientAuth),
+ "Forces clients to authenticate in order to establish an SSL connection");
+ }
+};
+
+class SslProtocolFactory : public qpid::sys::SocketAcceptor, public qpid::sys::TransportConnector {
+ Timer& brokerTimer;
+ uint32_t maxNegotiateTime;
+ const bool tcpNoDelay;
+ std::string brokerHost;
+ const bool clientAuthSelected;
+ std::auto_ptr<qpid::sys::AsynchAcceptor> acceptor;
+ ConnectFailedCallback connectFailedCallback;
+ CredHandle credHandle;
+
+ public:
+ SslProtocolFactory(const qpid::broker::Broker& broker, const SslServerOptions&, Timer& timer);
+ ~SslProtocolFactory();
+
+ void connect(sys::Poller::shared_ptr, const std::string& name, const std::string& host, const std::string& port,
+ sys::ConnectionCodec::Factory*,
+ ConnectFailedCallback failed);
+
+ private:
+ void connectFailed(const qpid::sys::Socket&,
+ int err,
+ const std::string& msg);
+ void establishedIncoming(sys::Poller::shared_ptr, const qpid::sys::Socket&, sys::ConnectionCodec::Factory*);
+ void establishedOutgoing(sys::Poller::shared_ptr, const qpid::sys::Socket&, sys::ConnectionCodec::Factory*, std::string& );
+ void establishedCommon(sys::Poller::shared_ptr, sys::AsynchIOHandler*, sys::AsynchIO*, const qpid::sys::Socket&);
+};
+
+// Static instance to initialise plugin
+static struct SslPlugin : public Plugin {
+ SslServerOptions options;
+
+ Options* getOptions() { return &options; }
+
+ void earlyInitialize(Target&) {
+ }
+
+ void initialize(Target& target) {
+ broker::Broker* broker = dynamic_cast<broker::Broker*>(&target);
+ // Only provide to a Broker
+ if (broker) {
+ try {
+ boost::shared_ptr<SslProtocolFactory> protocol(new SslProtocolFactory(*broker, options, broker->getTimer()));
+ uint16_t port =
+ protocol->listen(broker->getListenInterfaces(),
+ options.port, broker->getConnectionBacklog(),
+ &createSocket);
+ QPID_LOG(notice, "Listening for SSL connections on TCP port " << port);
+ broker->registerTransport("ssl", protocol, protocol, port);
+ } catch (const std::exception& e) {
+ QPID_LOG(error, "Failed to initialise SSL listener: " << e.what());
+ }
+ }
+ }
+} sslPlugin;
+
+SslProtocolFactory::SslProtocolFactory(const qpid::broker::Broker& broker, const SslServerOptions& options, Timer& timer)
+ : SocketAcceptor(broker.getTcpNoDelay(), false, broker.getMaxNegotiateTime(), timer,
+ boost::bind(&SslProtocolFactory::establishedIncoming, this, _1, _2, _3)),
+ brokerTimer(timer),
+ maxNegotiateTime(broker.getMaxNegotiateTime()),
+ tcpNoDelay(broker.getTcpNoDelay()),
+ clientAuthSelected(options.clientAuth) {
+
+ // Make sure that certificate store is good before listening to sockets
+ // to avoid having open and listening sockets when there is no cert store
+ SecInvalidateHandle(&credHandle);
+
+ // Get the certificate for this server.
+ DWORD flags = 0;
+ std::string certStoreLocation = options.certStoreLocation;
+ std::transform(certStoreLocation.begin(), certStoreLocation.end(), certStoreLocation.begin(), ::tolower);
+ if (certStoreLocation == "currentuser") {
+ flags = CERT_SYSTEM_STORE_CURRENT_USER;
+ } else if (certStoreLocation == "localmachine") {
+ flags = CERT_SYSTEM_STORE_LOCAL_MACHINE;
+ } else if (certStoreLocation == "currentservice") {
+ flags = CERT_SYSTEM_STORE_CURRENT_SERVICE;
+ } else {
+ QPID_LOG(error, "Unrecognised SSL certificate store location: " << options.certStoreLocation
+ << " - Using default location");
+ }
+ HCERTSTORE certStoreHandle;
+ certStoreHandle = ::CertOpenStore(CERT_STORE_PROV_SYSTEM_A,
+ X509_ASN_ENCODING,
+ 0,
+ flags |
+ CERT_STORE_READONLY_FLAG,
+ options.certStore.c_str());
+ if (!certStoreHandle)
+ throw qpid::Exception(QPID_MSG("Opening store " << options.certStore << " " << qpid::sys::strError(GetLastError())));
+
+ PCCERT_CONTEXT certContext;
+ certContext = ::CertFindCertificateInStore(certStoreHandle,
+ X509_ASN_ENCODING,
+ 0,
+ CERT_FIND_SUBJECT_STR_A,
+ options.certName.c_str(),
+ NULL);
+ if (certContext == NULL) {
+ int err = ::GetLastError();
+ ::CertCloseStore(certStoreHandle, 0);
+ throw qpid::Exception(QPID_MSG("Locating certificate " << options.certName << " in store " << options.certStore << " " << qpid::sys::strError(GetLastError())));
+ throw QPID_WINDOWS_ERROR(err);
+ }
+
+ SCHANNEL_CRED cred;
+ memset(&cred, 0, sizeof(cred));
+ cred.dwVersion = SCHANNEL_CRED_VERSION;
+ cred.cCreds = 1;
+ cred.paCred = &certContext;
+ SECURITY_STATUS status = ::AcquireCredentialsHandle(NULL,
+ UNISP_NAME,
+ SECPKG_CRED_INBOUND,
+ NULL,
+ &cred,
+ NULL,
+ NULL,
+ &credHandle,
+ NULL);
+ if (status != SEC_E_OK)
+ throw QPID_WINDOWS_ERROR(status);
+ ::CertFreeCertificateContext(certContext);
+ ::CertCloseStore(certStoreHandle, 0);
+}
+
+SslProtocolFactory::~SslProtocolFactory() {
+ ::FreeCredentialsHandle(&credHandle);
+}
+
+void SslProtocolFactory::connectFailed(const qpid::sys::Socket&,
+ int err,
+ const std::string& msg) {
+ if (connectFailedCallback)
+ connectFailedCallback(err, msg);
+}
+
+void SslProtocolFactory::establishedIncoming(sys::Poller::shared_ptr poller,
+ const qpid::sys::Socket& s,
+ sys::ConnectionCodec::Factory* f) {
+ sys::AsynchIOHandler* async = new sys::AsynchIOHandler(s.getFullAddress(), f, false, false);
+
+ sys::AsynchIO *aio =
+ new qpid::sys::windows::ServerSslAsynchIO(
+ clientAuthSelected,
+ s,
+ credHandle,
+ boost::bind(&AsynchIOHandler::readbuff, async, _1, _2),
+ boost::bind(&AsynchIOHandler::eof, async, _1),
+ boost::bind(&AsynchIOHandler::disconnect, async, _1),
+ boost::bind(&AsynchIOHandler::closedSocket, async, _1, _2),
+ boost::bind(&AsynchIOHandler::nobuffs, async, _1),
+ boost::bind(&AsynchIOHandler::idle, async, _1));
+
+ establishedCommon(poller, async, aio, s);
+}
+
+void SslProtocolFactory::establishedOutgoing(sys::Poller::shared_ptr poller,
+ const qpid::sys::Socket& s,
+ sys::ConnectionCodec::Factory* f,
+ std::string& name) {
+ sys::AsynchIOHandler* async = new sys::AsynchIOHandler(name, f, true, false);
+
+ sys::AsynchIO *aio =
+ new qpid::sys::windows::ClientSslAsynchIO(
+ brokerHost,
+ s,
+ credHandle,
+ boost::bind(&AsynchIOHandler::readbuff, async, _1, _2),
+ boost::bind(&AsynchIOHandler::eof, async, _1),
+ boost::bind(&AsynchIOHandler::disconnect, async, _1),
+ boost::bind(&AsynchIOHandler::closedSocket, async, _1, _2),
+ boost::bind(&AsynchIOHandler::nobuffs, async, _1),
+ boost::bind(&AsynchIOHandler::idle, async, _1));
+
+ establishedCommon(poller, async, aio, s);
+}
+
+void SslProtocolFactory::establishedCommon(sys::Poller::shared_ptr poller,
+ sys::AsynchIOHandler* async,
+ sys::AsynchIO* aio,
+ const qpid::sys::Socket& s) {
+ if (tcpNoDelay) {
+ s.setTcpNoDelay();
+ QPID_LOG(info,
+ "Set TCP_NODELAY on connection to " << s.getPeerAddress());
+ }
+
+ async->init(aio, brokerTimer, maxNegotiateTime);
+ aio->start(poller);
+}
+
+void SslProtocolFactory::connect(sys::Poller::shared_ptr poller,
+ const std::string& name,
+ const std::string& host,
+ const std::string& port,
+ sys::ConnectionCodec::Factory* fact,
+ ConnectFailedCallback failed)
+{
+ SCHANNEL_CRED cred;
+ memset(&cred, 0, sizeof(cred));
+ cred.dwVersion = SCHANNEL_CRED_VERSION;
+ SECURITY_STATUS status = ::AcquireCredentialsHandle(NULL,
+ UNISP_NAME,
+ SECPKG_CRED_OUTBOUND,
+ NULL,
+ &cred,
+ NULL,
+ NULL,
+ &credHandle,
+ NULL);
+ if (status != SEC_E_OK)
+ throw QPID_WINDOWS_ERROR(status);
+
+ brokerHost = host;
+ // Note that the following logic does not cause a memory leak.
+ // The allocated Socket is freed either by the AsynchConnector
+ // upon connection failure or by the AsynchIO upon connection
+ // shutdown. The allocated AsynchConnector frees itself when it
+ // is no longer needed.
+ qpid::sys::Socket* socket = createSocket();
+ connectFailedCallback = failed;
+ AsynchConnector::create(*socket,
+ host,
+ port,
+ boost::bind(&SslProtocolFactory::establishedOutgoing,
+ this, poller, _1, fact, name),
+ boost::bind(&SslProtocolFactory::connectFailed,
+ this, _1, _2, _3));
+}
+
+}}}
diff --git a/qpid/cpp/src/qpid/client/AsyncSession.h b/qpid/cpp/src/qpid/client/AsyncSession.h
new file mode 100644
index 0000000000..d91efeb4f1
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/AsyncSession.h
@@ -0,0 +1,38 @@
+#ifndef QPID_CLIENT_ASYNCSESSION_H
+#define QPID_CLIENT_ASYNCSESSION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/client/AsyncSession_0_10.h"
+
+namespace qpid {
+namespace client {
+
+/**
+ * AsyncSession is an alias for Session_0_10
+ *
+ * \ingroup clientapi
+ */
+typedef AsyncSession_0_10 AsyncSession;
+
+}} // namespace qpid::client
+
+#endif /*!QPID_CLIENT_ASYNCSESSION_H*/
diff --git a/qpid/cpp/src/qpid/client/Bounds.cpp b/qpid/cpp/src/qpid/client/Bounds.cpp
new file mode 100644
index 0000000000..cc2577d5fc
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/Bounds.cpp
@@ -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.
+ *
+ */
+#include "qpid/client/Bounds.h"
+
+#include "qpid/log/Statement.h"
+#include "qpid/sys/Waitable.h"
+
+namespace qpid {
+namespace client {
+
+using sys::Waitable;
+
+Bounds::Bounds(size_t maxSize) : max(maxSize), current(0) {}
+
+bool Bounds::expand(size_t sizeRequired, bool block) {
+ if (!max) return true;
+ Waitable::ScopedLock l(lock);
+ if (block) {
+ Waitable::ScopedWait w(lock);
+ while (current + sizeRequired > max)
+ lock.wait();
+ }
+ current += sizeRequired;
+ return current <= max;
+}
+
+void Bounds::reduce(size_t size) {
+ if (!max || size == 0) return;
+ Waitable::ScopedLock l(lock);
+ assert(current >= size);
+ current -= std::min(size, current);
+ if (current < max && lock.hasWaiters()) {
+ lock.notifyAll();
+ }
+}
+
+size_t Bounds::getCurrentSize() {
+ Waitable::ScopedLock l(lock);
+ return current;
+}
+
+std::ostream& operator<<(std::ostream& out, const Bounds& bounds) {
+ out << "current=" << bounds.current << ", max=" << bounds.max << " [" << &bounds << "]";
+ return out;
+}
+
+void Bounds::setException(const sys::ExceptionHolder& e) {
+ Waitable::ScopedLock l(lock);
+ lock.setException(e);
+ lock.waitWaiters(); // Wait for waiting threads to exit.
+}
+
+}} // namespace qpid::client
diff --git a/qpid/cpp/src/qpid/client/Bounds.h b/qpid/cpp/src/qpid/client/Bounds.h
new file mode 100644
index 0000000000..838fcb8368
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/Bounds.h
@@ -0,0 +1,49 @@
+#ifndef QPID_CLIENT_BOUNDSCHECKING_H
+#define QPID_CLIENT_BOUNDSCHECKING_H
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/sys/Waitable.h"
+
+namespace qpid{
+namespace client{
+
+class Bounds
+{
+ public:
+ Bounds(size_t maxSize);
+ bool expand(size_t, bool block);
+ void reduce(size_t);
+ size_t getCurrentSize();
+ void setException(const sys::ExceptionHolder&);
+
+ private:
+ friend std::ostream& operator<<(std::ostream&, const Bounds&);
+ sys::Waitable lock;
+ const size_t max;
+ size_t current;
+};
+
+std::ostream& operator<<(std::ostream&, const Bounds&);
+
+
+}}
+
+#endif
diff --git a/qpid/cpp/src/qpid/client/ChainableFrameHandler.h b/qpid/cpp/src/qpid/client/ChainableFrameHandler.h
new file mode 100644
index 0000000000..29e16d53dc
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/ChainableFrameHandler.h
@@ -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.
+ *
+ */
+
+#ifndef _ChainableFrameHandler_
+#define _ChainableFrameHandler_
+
+#include <boost/function.hpp>
+#include "qpid/framing/AMQFrame.h"
+
+namespace qpid {
+namespace client {
+
+struct ChainableFrameHandler
+{
+ typedef boost::function<void(framing::AMQFrame&)> FrameDelegate;
+
+ FrameDelegate in;
+ FrameDelegate out;
+
+ ChainableFrameHandler() {}
+ ChainableFrameHandler(FrameDelegate i, FrameDelegate o): in(i), out(o) {}
+ virtual ~ChainableFrameHandler() {}
+};
+
+}}
+
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/client/ClientImportExport.h b/qpid/cpp/src/qpid/client/ClientImportExport.h
new file mode 100644
index 0000000000..2a3a5a52e9
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/ClientImportExport.h
@@ -0,0 +1,35 @@
+#ifndef QPID_CLIENT_IMPORT_EXPORT_H
+#define QPID_CLIENT_IMPORT_EXPORT_H
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "qpid/ImportExport.h"
+
+#if defined(CLIENT_EXPORT) || defined (qpidclient_EXPORTS)
+# define QPID_CLIENT_EXTERN QPID_EXPORT
+# define QPID_CLIENT_CLASS_EXTERN QPID_CLASS_EXPORT
+# define QPID_CLIENT_INLINE_EXTERN QPID_INLINE_EXPORT
+#else
+# define QPID_CLIENT_EXTERN QPID_IMPORT
+# define QPID_CLIENT_CLASS_EXTERN QPID_CLASS_IMPORT
+# define QPID_CLIENT_INLINE_EXTERN QPID_INLINE_IMPORT
+#endif
+
+#endif
diff --git a/qpid/cpp/src/qpid/client/Completion.cpp b/qpid/cpp/src/qpid/client/Completion.cpp
new file mode 100644
index 0000000000..fc6737d96f
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/Completion.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/client/Completion.h"
+#include "qpid/client/CompletionImpl.h"
+#include "qpid/client/PrivateImplRef.h"
+
+namespace qpid {
+namespace client {
+
+// Explicitly instantiate Handle superclass
+template class Handle<CompletionImpl>;
+
+typedef PrivateImplRef<Completion> PI;
+Completion::Completion(CompletionImpl* p) { PI::ctor(*this, p); }
+Completion::Completion(const Completion& c) : Handle<CompletionImpl>() { PI::copy(*this, c); }
+Completion::~Completion() { PI::dtor(*this); }
+Completion& Completion::operator=(const Completion& c) { return PI::assign(*this, c); }
+
+
+void Completion::wait() { impl->wait(); }
+bool Completion::isComplete() { return impl->isComplete(); }
+std::string Completion::getResult() { return impl->getResult(); }
+
+}} // namespace qpid::client
diff --git a/qpid/cpp/src/qpid/client/Completion.h b/qpid/cpp/src/qpid/client/Completion.h
new file mode 100644
index 0000000000..9546db9258
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/Completion.h
@@ -0,0 +1,71 @@
+#ifndef QPID_CLIENT_COMPLETION_H
+#define QPID_CLIENT_COMPLETION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/client/Handle.h"
+#include "qpid/client/ClientImportExport.h"
+#include <string>
+
+namespace qpid {
+namespace client {
+
+class CompletionImpl;
+template <class T> class PrivateImplRef;
+
+/**
+ * Asynchronous commands that do not return a result will return a
+ * Completion. You can use the completion to wait for that specific
+ * command to complete.
+ *
+ *@see TypedResult
+ *
+ *\ingroup clientapi
+ */
+class QPID_CLIENT_CLASS_EXTERN Completion : public Handle<CompletionImpl>
+{
+public:
+ QPID_CLIENT_EXTERN Completion(CompletionImpl* = 0);
+ QPID_CLIENT_EXTERN Completion(const Completion&);
+ QPID_CLIENT_EXTERN ~Completion();
+ QPID_CLIENT_EXTERN Completion& operator=(const Completion&);
+
+ /** Wait for the asynchronous command that returned this
+ *Completion to complete.
+ *
+ *@exception If the command returns an error.
+ */
+ QPID_CLIENT_EXTERN void wait();
+ QPID_CLIENT_EXTERN bool isComplete();
+
+ protected:
+ QPID_CLIENT_EXTERN std::string getResult();
+
+ private:
+ typedef CompletionImpl Impl;
+ friend class PrivateImplRef<Completion>;
+};
+
+}}
+
+
+#endif /*!QPID_CLIENT_COMPLETION_H*/
diff --git a/qpid/cpp/src/qpid/client/CompletionImpl.cpp b/qpid/cpp/src/qpid/client/CompletionImpl.cpp
new file mode 100644
index 0000000000..cf2cfc6138
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/CompletionImpl.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/client/CompletionImpl.h"
+#include "qpid/client/Handle.h"
+#include "qpid/client/PrivateImplRef.h"
+
+namespace qpid {
+namespace client {
+
+CompletionImpl::CompletionImpl() {}
+
+CompletionImpl::CompletionImpl(Future f, boost::shared_ptr<SessionImpl> s) :
+ future(f), session(s) {}
+
+}} // namespace qpid::client
diff --git a/qpid/cpp/src/qpid/client/CompletionImpl.h b/qpid/cpp/src/qpid/client/CompletionImpl.h
new file mode 100644
index 0000000000..b8243511ef
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/CompletionImpl.h
@@ -0,0 +1,52 @@
+#ifndef QPID_CLIENT_COMPLETIONIMPL_H
+#define QPID_CLIENT_COMPLETIONIMPL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/RefCounted.h"
+#include "qpid/client/Future.h"
+#include "qpid/client/ClientImportExport.h"
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace client {
+
+///@internal
+class QPID_CLIENT_CLASS_EXTERN CompletionImpl : public RefCounted
+{
+public:
+ QPID_CLIENT_EXTERN CompletionImpl();
+ QPID_CLIENT_EXTERN CompletionImpl(Future f, boost::shared_ptr<SessionImpl> s);
+
+ QPID_CLIENT_EXTERN bool isComplete() { return future.isComplete(*session); }
+ QPID_CLIENT_EXTERN void wait() { future.wait(*session); }
+ QPID_CLIENT_EXTERN std::string getResult() { return future.getResult(*session); }
+
+protected:
+ Future future;
+ boost::shared_ptr<SessionImpl> session;
+};
+
+}} // namespace qpid::client
+
+
+#endif /*!QPID_CLIENT_COMPLETIONIMPL_H*/
diff --git a/qpid/cpp/src/qpid/client/Connection.cpp b/qpid/cpp/src/qpid/client/Connection.cpp
new file mode 100644
index 0000000000..f7d6341128
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/Connection.cpp
@@ -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.
+ *
+ */
+#include "qpid/client/Connection.h"
+#include "qpid/client/ConnectionImpl.h"
+#include "qpid/client/ConnectionSettings.h"
+#include "qpid/client/Message.h"
+#include "qpid/client/SessionImpl.h"
+#include "qpid/client/SessionBase_0_10Access.h"
+#include "qpid/Url.h"
+#include "qpid/log/Logger.h"
+#include "qpid/log/Options.h"
+#include "qpid/log/Statement.h"
+#include "qpid/framing/AMQP_HighestVersion.h"
+
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+#include <functional>
+#include <boost/format.hpp>
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+
+using namespace qpid::framing;
+using namespace qpid::sys;
+
+
+namespace qpid {
+namespace client {
+
+Connection::Connection() : version(framing::ProtocolVersion(0, 10))
+{
+ ConnectionImpl::init();
+}
+
+Connection::~Connection() {}
+
+void Connection::open(
+ const Url& url,
+ const std::string& uid, const std::string& pwd,
+ const std::string& vhost,
+ uint16_t maxFrameSize)
+{
+ ConnectionSettings settings;
+ settings.username = uid;
+ settings.password = pwd;
+ settings.virtualhost = vhost;
+ settings.maxFrameSize = maxFrameSize;
+ open(url, settings);
+}
+
+void Connection::open(const Url& url, const ConnectionSettings& settings) {
+ if (url.empty())
+ throw Exception(QPID_MSG("Attempt to open URL with no addresses."));
+ Url::const_iterator i = url.begin();
+ do {
+ const Address& addr = *i;
+ i++;
+ try {
+ ConnectionSettings cs(settings);
+ if (addr.protocol.size()) cs.protocol = addr.protocol;
+ cs.host = addr.host;
+ cs.port = addr.port;
+ open(cs);
+ break;
+ }
+ catch (const Exception& /*e*/) {
+ if (i == url.end()) throw;
+ }
+ } while (i != url.end());
+}
+
+void Connection::open(
+ const std::string& host, int port,
+ const std::string& uid, const std::string& pwd,
+ const std::string& vhost,
+ uint16_t maxFrameSize)
+{
+ ConnectionSettings settings;
+ settings.host = host;
+ settings.port = port;
+ settings.username = uid;
+ settings.password = pwd;
+ settings.virtualhost = vhost;
+ settings.maxFrameSize = maxFrameSize;
+ open(settings);
+}
+
+bool Connection::isOpen() const {
+ return impl && impl->isOpen();
+}
+
+void
+Connection::registerFailureCallback ( boost::function<void ()> fn ) {
+ failureCallback = fn;
+ if ( impl )
+ impl->registerFailureCallback ( fn );
+}
+
+
+
+void Connection::open(const ConnectionSettings& settings)
+{
+ if (isOpen())
+ throw Exception(QPID_MSG("Connection::open() was already called"));
+
+ impl = ConnectionImpl::create(version, settings);
+ impl->open();
+ if ( failureCallback )
+ impl->registerFailureCallback ( failureCallback );
+}
+
+const ConnectionSettings& Connection::getNegotiatedSettings() const
+{
+ if (!isOpen())
+ throw Exception(QPID_MSG("Connection is not open."));
+ return impl->getNegotiatedSettings();
+}
+
+Session Connection::newSession(const std::string& name, uint32_t timeout) {
+ if (!isOpen())
+ throw TransportFailure("Can't create session, connection is not open");
+ Session s;
+ SessionBase_0_10Access(s).set(impl->newSession(name, timeout));
+ return s;
+}
+
+void Connection::resume(Session& session) {
+ if (!isOpen())
+ throw Exception(QPID_MSG("Connection is not open."));
+ impl->addSession(session.impl);
+ session.impl->resume(impl);
+}
+
+void Connection::close() {
+ if ( impl )
+ impl->close();
+}
+
+std::vector<Url> Connection::getInitialBrokers() {
+ return impl ? impl->getInitialBrokers() : std::vector<Url>();
+}
+
+}} // namespace qpid::client
diff --git a/qpid/cpp/src/qpid/client/Connection.h b/qpid/cpp/src/qpid/client/Connection.h
new file mode 100644
index 0000000000..8c8a106c36
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/Connection.h
@@ -0,0 +1,227 @@
+#ifndef QPID_CLIENT_CONNECTION_H
+#define QPID_CLIENT_CONNECTION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <map>
+#include <string>
+#include "qpid/client/Session.h"
+#include "qpid/client/ClientImportExport.h"
+#include "qpid/client/ConnectionSettings.h"
+#include "qpid/framing/ProtocolVersion.h"
+
+#include "boost/function.hpp"
+
+namespace qpid {
+
+struct Url;
+
+namespace client {
+
+class ConnectionImpl;
+
+/**
+ * Represents a connection to an AMQP broker. All communication is
+ * initiated by establishing a connection, then creating one or more
+ * Session objects using the connection. @see newSession()
+ *
+ * \ingroup clientapi
+ *
+ * Some methods use an AMQP 0-10 URL to specify connection parameters.
+ * This is defined in the AMQP 0-10 specification (http://jira.amqp.org/confluence/display/AMQP/AMQP+Specification).
+ *
+ * amqp_url = "amqp:" prot_addr_list
+ * prot_addr_list = [prot_addr ","]* prot_addr
+ * prot_addr = tcp_prot_addr | tls_prot_addr
+ *
+ * tcp_prot_addr = tcp_id tcp_addr
+ * tcp_id = "tcp:" | ""
+ * tcp_addr = [host [":" port] ]
+ * host = <as per http://www.ietf.org/rfc/rfc3986.txt>
+ * port = number]]>
+ *
+ */
+
+class QPID_CLIENT_CLASS_EXTERN Connection
+{
+ framing::ProtocolVersion version;
+
+ boost::function<void ()> failureCallback;
+
+
+ protected:
+ boost::shared_ptr<ConnectionImpl> impl;
+
+
+ public:
+ /**
+ * Creates a Connection object, but does not open the connection.
+ * @see open()
+ */
+ QPID_CLIENT_EXTERN Connection();
+
+ /**
+ * Destroys a Connection object but does not close the connection if it
+ * was open. @see close()
+ */
+ QPID_CLIENT_EXTERN ~Connection();
+
+ /**
+ * Opens a connection to a broker.
+ *
+ * @param host the host on which the broker is running.
+ *
+ * @param port the port on the which the broker is listening.
+ *
+ * @param uid the userid to connect with.
+ *
+ * @param pwd the password to connect with (currently SASL
+ * PLAIN is the only authentication method supported so this
+ * is sent in clear text).
+ *
+ * @param virtualhost the AMQP virtual host to use (virtual
+ * hosts, where implemented(!), provide namespace partitioning
+ * within a single broker).
+ */
+ QPID_CLIENT_EXTERN void open(const std::string& host, int port = 5672,
+ const std::string& uid = "",
+ const std::string& pwd = "",
+ const std::string& virtualhost = "/", uint16_t maxFrameSize=65535);
+ /**
+ * Opens a connection to a broker using a URL.
+ * If the URL contains multiple addresses, try each in turn
+ * till connection is successful.
+ *
+ * @url address of the broker to connect to.
+ *
+ * @param uid the userid to connect with.
+ *
+ * @param pwd the password to connect with (currently SASL
+ * PLAIN is the only authentication method supported so this
+ * is sent in clear text).
+ *
+ * @param virtualhost the AMQP virtual host to use (virtual
+ * hosts, where implemented(!), provide namespace partitioning
+ * within a single broker).
+ */
+ QPID_CLIENT_EXTERN void open(const Url& url,
+ const std::string& uid = "",
+ const std::string& pwd = "",
+ const std::string& virtualhost = "/", uint16_t maxFrameSize=65535);
+
+ /**
+ * Opens a connection to a broker using a URL.
+ * If the URL contains multiple addresses, try each in turn
+ * till connection is successful.
+ *
+ * @url address of the broker to connect to.
+ *
+ * @param settings used for any settings not provided by the URL.
+ * Settings provided by the url (e.g. host, port) are ignored.
+ */
+ QPID_CLIENT_EXTERN void open(const Url& url, const ConnectionSettings& settings);
+
+ /**
+ * Opens a connection to a broker.
+ *
+ * @param the settings to use (host, port etc). @see ConnectionSettings.
+ */
+ QPID_CLIENT_EXTERN void open(const ConnectionSettings& settings);
+
+ /**
+ * Close the connection.
+ *
+ * Any further use of this connection (without reopening it) will
+ * not succeed.
+ */
+ QPID_CLIENT_EXTERN void close();
+
+ /**
+ * Create a new session on this connection. Sessions allow
+ * multiple streams of work to be multiplexed over the same
+ * connection. The returned Session provides functions to send
+ * commands to the broker.
+ *
+ * Session functions are synchronous. In other words, a Session
+ * function will send a command to the broker and does not return
+ * until it receives the broker's response confirming the command
+ * was executed.
+ *
+ * AsyncSession provides asynchronous versions of the same
+ * functions. These functions send a command to the broker but do
+ * not wait for a response.
+ *
+ * You can convert a Session s into an AsyncSession as follows:
+ * @code
+ * #include <qpid/client/AsyncSession.h>
+ * AsyncSession as = async(s);
+ * @endcode
+ *
+ * You can execute a single command asynchronously will a Session s
+ * like ths:
+ * @code
+ * async(s).messageTransfer(...);
+ * @endcode
+ *
+ * Using an AsyncSession is faster for sending large numbers of
+ * commands, since each command is sent as soon as possible
+ * without waiting for the previous command to be confirmed.
+ *
+ * However with AsyncSession you cannot assume that a command has
+ * completed until you explicitly synchronize. The simplest way to
+ * do this is to call Session::sync() or AsyncSession::sync().
+ * Both of these functions wait for the broker to confirm all
+ * commands issued so far on the session.
+ *
+ *@param name: A name to identify the session. @see qpid::SessionId
+ * If the name is empty (the default) then a unique name will be
+ * chosen using a Universally-unique identifier (UUID) algorithm.
+ */
+ QPID_CLIENT_EXTERN Session newSession(const std::string& name=std::string(), uint32_t timeoutSeconds = 0);
+
+ /**
+ * Resume a suspended session. A session may be resumed
+ * on a different connection to the one that created it.
+ */
+ QPID_CLIENT_EXTERN void resume(Session& session);
+
+ QPID_CLIENT_EXTERN bool isOpen() const;
+
+ /** In a cluster, returns the initial set of known broker URLs
+ * at the time of connection.
+ */
+ QPID_CLIENT_EXTERN std::vector<Url> getInitialBrokers();
+
+ QPID_CLIENT_EXTERN void registerFailureCallback ( boost::function<void ()> fn );
+
+ /**
+ * Return the set of client negotiated settings
+ */
+ QPID_CLIENT_EXTERN const ConnectionSettings& getNegotiatedSettings() const;
+
+ friend struct ConnectionAccess; ///<@internal
+ friend class SessionBase_0_10; ///<@internal
+};
+
+}} // namespace qpid::client
+
+
+#endif /*!QPID_CLIENT_CONNECTION_H*/
diff --git a/qpid/cpp/src/qpid/client/ConnectionAccess.h b/qpid/cpp/src/qpid/client/ConnectionAccess.h
new file mode 100644
index 0000000000..3a763f692f
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/ConnectionAccess.h
@@ -0,0 +1,42 @@
+#ifndef QPID_CLIENT_CONNECTIONACCESS_H
+#define QPID_CLIENT_CONNECTIONACCESS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/client/Connection.h"
+
+/**@file @internal Internal use only */
+
+namespace qpid {
+namespace client {
+
+
+
+struct ConnectionAccess {
+ static void setVersion(Connection& c, const framing::ProtocolVersion& v) { c.version = v; }
+ static boost::shared_ptr<ConnectionImpl> getImpl(Connection& c) { return c.impl; }
+ static void setImpl(Connection& c, boost::shared_ptr<ConnectionImpl> i) { c.impl = i; }
+};
+
+}} // namespace qpid::client
+
+#endif /*!QPID_CLIENT_CONNECTIONACCESS_H*/
diff --git a/qpid/cpp/src/qpid/client/ConnectionHandler.cpp b/qpid/cpp/src/qpid/client/ConnectionHandler.cpp
new file mode 100644
index 0000000000..77d43f191d
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/ConnectionHandler.cpp
@@ -0,0 +1,392 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/client/ConnectionHandler.h"
+
+#include "qpid/SaslFactory.h"
+#include "qpid/StringUtils.h"
+#include "qpid/client/Bounds.h"
+#include "qpid/framing/amqp_framing.h"
+#include "qpid/framing/all_method_bodies.h"
+#include "qpid/framing/ClientInvoker.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/log/Helpers.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/SystemInfo.h"
+
+#include <algorithm>
+
+using namespace qpid::client;
+using namespace qpid::framing;
+using namespace qpid::framing::connection;
+using qpid::sys::SecurityLayer;
+using qpid::sys::Duration;
+using qpid::sys::TimerTask;
+using qpid::sys::Timer;
+using qpid::sys::AbsTime;
+using qpid::sys::TIME_SEC;
+using qpid::sys::ScopedLock;
+using qpid::sys::Mutex;
+
+namespace {
+const std::string OK("OK");
+const std::string PLAIN("PLAIN");
+const std::string ANONYMOUS("ANONYMOUS");
+const std::string en_US("en_US");
+
+const std::string INVALID_STATE_START("start received in invalid state");
+const std::string INVALID_STATE_TUNE("tune received in invalid state");
+const std::string INVALID_STATE_OPEN_OK("open-ok received in invalid state");
+const std::string INVALID_STATE_CLOSE_OK("close-ok received in invalid state");
+
+const std::string SESSION_FLOW_CONTROL("qpid.session_flow");
+const std::string CLIENT_PROCESS_NAME("qpid.client_process");
+const std::string CLIENT_PID("qpid.client_pid");
+const std::string CLIENT_PPID("qpid.client_ppid");
+const int SESSION_FLOW_CONTROL_VER = 1;
+}
+
+CloseCode ConnectionHandler::convert(uint16_t replyCode)
+{
+ switch (replyCode) {
+ case 200: return CLOSE_CODE_NORMAL;
+ case 320: return CLOSE_CODE_CONNECTION_FORCED;
+ case 402: return CLOSE_CODE_INVALID_PATH;
+ case 501: default:
+ return CLOSE_CODE_FRAMING_ERROR;
+ }
+}
+
+ConnectionHandler::Adapter::Adapter(ConnectionHandler& h, Bounds& b) : handler(h), bounds(b) {}
+void ConnectionHandler::Adapter::handle(qpid::framing::AMQFrame& f)
+{
+ bounds.expand(f.encodedSize(), false);
+ handler.out(f);
+}
+
+ConnectionHandler::ConnectionHandler(
+ const ConnectionSettings& s, ProtocolVersion& v, Bounds& b)
+ : StateManager(NOT_STARTED), ConnectionSettings(s),
+ outHandler(*this, b), proxy(outHandler), errorCode(CLOSE_CODE_NORMAL), version(v),
+ properties(s.clientProperties)
+{
+ insist = true;
+
+ ESTABLISHED.insert(FAILED);
+ ESTABLISHED.insert(CLOSED);
+ ESTABLISHED.insert(OPEN);
+
+ FINISHED.insert(FAILED);
+ FINISHED.insert(CLOSED);
+
+ properties.setInt(SESSION_FLOW_CONTROL, SESSION_FLOW_CONTROL_VER);
+ properties.setString(CLIENT_PROCESS_NAME, sys::SystemInfo::getProcessName());
+ properties.setInt(CLIENT_PID, sys::SystemInfo::getProcessId());
+ properties.setInt(CLIENT_PPID, sys::SystemInfo::getParentProcessId());
+}
+
+void ConnectionHandler::incoming(AMQFrame& frame)
+{
+ if (getState() == CLOSED) {
+ throw Exception("Received frame on closed connection");
+ }
+
+ if (rcvTimeoutTask) {
+ // Received frame on connection so delay timeout
+ rcvTimeoutTask->restart();
+ }
+
+ AMQBody* body = frame.getBody();
+ try {
+ if (frame.getChannel() != 0 || !invoke(static_cast<ConnectionOperations&>(*this), *body)) {
+ switch(getState()) {
+ case OPEN:
+ in(frame);
+ break;
+ case CLOSING:
+ QPID_LOG(warning, "Ignoring frame while closing connection: " << frame);
+ break;
+ default:
+ throw Exception("Cannot receive frames on non-zero channel until connection is established.");
+ }
+ }
+ }catch(std::exception& e){
+ QPID_LOG(warning, "Closing connection due to " << e.what());
+ setState(CLOSING);
+ errorCode = CLOSE_CODE_FRAMING_ERROR;
+ errorText = e.what();
+ proxy.close(501, e.what());
+ }
+}
+
+void ConnectionHandler::outgoing(AMQFrame& frame)
+{
+ if (getState() == OPEN)
+ out(frame);
+ else
+ throw TransportFailure(errorText.empty() ? "Connection is not open." : errorText);
+}
+
+void ConnectionHandler::waitForOpen()
+{
+ waitFor(ESTABLISHED);
+ if (getState() == FAILED) {
+ throw TransportFailure(errorText);
+ } else if (getState() == CLOSED) {
+ throw ConnectionException(errorCode, errorText);
+ }
+}
+
+void ConnectionHandler::close()
+{
+ switch (getState()) {
+ case NEGOTIATING:
+ case OPENING:
+ fail("Connection closed before it was established");
+ break;
+ case OPEN:
+ if (setState(CLOSING, OPEN)) {
+ proxy.close(200, OK);
+ if (ConnectionSettings::heartbeat) {
+ //heartbeat timer is turned off at this stage, so don't wait indefinately
+ if (!waitFor(FINISHED, qpid::sys::Duration(ConnectionSettings::heartbeat * qpid::sys::TIME_SEC))) {
+ QPID_LOG(warning, "Connection close timed out");
+ }
+ } else {
+ waitFor(FINISHED);//FINISHED = CLOSED or FAILED
+ }
+ }
+ //else, state was changed from open after we checked, can only
+ //change to failed or closed, so nothing to do
+ break;
+
+ // Nothing to do if already CLOSING, CLOSED, FAILED or if NOT_STARTED
+ }
+}
+
+void ConnectionHandler::heartbeat()
+{
+ // Do nothing - the purpose of heartbeats is just to make sure that there is some
+ // traffic on the connection within the heart beat interval, we check for the
+ // traffic and don't need to do anything in response to heartbeats
+
+ // Although the above is still true we're now using a received heartbeat as a trigger
+ // to send out our own heartbeat
+ proxy.heartbeat();
+}
+
+void ConnectionHandler::checkState(STATES s, const std::string& msg)
+{
+ if (getState() != s) {
+ throw CommandInvalidException(msg);
+ }
+}
+
+void ConnectionHandler::fail(const std::string& message)
+{
+ errorCode = CLOSE_CODE_FRAMING_ERROR;
+ errorText = message;
+ QPID_LOG(debug, message);
+ setState(FAILED);
+}
+
+namespace {
+std::string SPACE(" ");
+
+std::string join(const std::vector<std::string>& in)
+{
+ std::string result;
+ for (std::vector<std::string>::const_iterator i = in.begin(); i != in.end(); ++i) {
+ if (result.size()) result += SPACE;
+ result += *i;
+ }
+ return result;
+}
+
+void intersection(const std::vector<std::string>& a, const std::vector<std::string>& b, std::vector<std::string>& results)
+{
+ for (std::vector<std::string>::const_iterator i = a.begin(); i != a.end(); ++i) {
+ if (std::find(b.begin(), b.end(), *i) != b.end()) results.push_back(*i);
+ }
+}
+
+}
+
+void ConnectionHandler::start(const FieldTable& /*serverProps*/, const Array& mechanisms, const Array& /*locales*/)
+{
+ checkState(NOT_STARTED, INVALID_STATE_START);
+ setState(NEGOTIATING);
+ sasl = SaslFactory::getInstance().create( username,
+ password,
+ service,
+ host,
+ minSsf,
+ maxSsf
+ );
+
+ std::vector<std::string> mechlist;
+ mechlist.reserve(mechanisms.size());
+
+ if (mechanism.empty()) {
+ //mechlist is simply what the server offers
+ std::transform(mechanisms.begin(), mechanisms.end(), std::back_inserter(mechlist), Array::get<std::string, Array::ValuePtr>);
+ } else {
+ //mechlist is the intersection of those indicated by user and
+ //those supported by server, in the order listed by user
+ std::vector<std::string> allowed = split(mechanism, " ");
+ std::vector<std::string> supported(mechanisms.size());
+ std::transform(mechanisms.begin(), mechanisms.end(), std::back_inserter(supported), Array::get<std::string, Array::ValuePtr>);
+ intersection(allowed, supported, mechlist);
+ if (mechlist.empty()) {
+ throw Exception(QPID_MSG("Desired mechanism(s) not valid: " << mechanism << " (supported: " << join(supported) << ")"));
+ }
+ }
+
+ if (sasl.get()) {
+ std::string response;
+ if (sasl->start(join(mechlist), response, getSecuritySettings ? getSecuritySettings() : 0)) {
+ proxy.startOk(properties, sasl->getMechanism(), response, locale);
+ } else {
+ //response was null
+ ConnectionStartOkBody body;
+ body.setClientProperties(properties);
+ body.setMechanism(sasl->getMechanism());
+ //Don't set response, as none was given
+ body.setLocale(locale);
+ proxy.send(body);
+ }
+ } else {
+ bool haveAnonymous(false);
+ bool havePlain(false);
+ for (std::vector<std::string>::const_iterator i = mechlist.begin(); i != mechlist.end(); ++i) {
+ if (*i == ANONYMOUS) {
+ haveAnonymous = true;
+ break;
+ } else if (*i == PLAIN) {
+ havePlain = true;
+ }
+ }
+ if (haveAnonymous && (mechanism.empty() || mechanism.find(ANONYMOUS) != std::string::npos)) {
+ proxy.startOk(properties, ANONYMOUS, username, locale);
+ } else if (havePlain && (mechanism.empty() || mechanism.find(PLAIN) !=std::string::npos)) {
+ std::string response = ((char)0) + username + ((char)0) + password;
+ proxy.startOk(properties, PLAIN, response, locale);
+ } else {
+ if (!mechanism.empty()) throw Exception(QPID_MSG("Desired mechanism(s) not valid: " << mechanism << "; client supports PLAIN or ANONYMOUS, broker supports: " << join(mechlist)));
+ throw Exception(QPID_MSG("No valid mechanism; client supports PLAIN or ANONYMOUS, broker supports: " << join(mechlist)));
+ }
+ }
+}
+
+void ConnectionHandler::secure(const std::string& challenge)
+{
+ if (sasl.get()) {
+ std::string response = sasl->step(challenge);
+ proxy.secureOk(response);
+ } else {
+ throw NotImplementedException("Challenge-response cycle not yet implemented in client");
+ }
+}
+
+void ConnectionHandler::tune(uint16_t maxChannelsProposed, uint16_t maxFrameSizeProposed,
+ uint16_t heartbeatMin, uint16_t heartbeatMax)
+{
+ checkState(NEGOTIATING, INVALID_STATE_TUNE);
+ maxChannels = std::min(maxChannels, maxChannelsProposed);
+ maxFrameSize = std::min(maxFrameSize, maxFrameSizeProposed);
+ // Clip the requested heartbeat to the maximum/minimum offered
+ uint16_t heartbeat = ConnectionSettings::heartbeat;
+ heartbeat = heartbeat < heartbeatMin ? heartbeatMin :
+ heartbeat > heartbeatMax ? heartbeatMax :
+ heartbeat;
+ ConnectionSettings::heartbeat = heartbeat;
+ proxy.tuneOk(maxChannels, maxFrameSize, heartbeat);
+ setState(OPENING);
+ proxy.open(virtualhost, capabilities, insist);
+}
+
+void ConnectionHandler::openOk ( const Array& knownBrokers )
+{
+ checkState(OPENING, INVALID_STATE_OPEN_OK);
+ knownBrokersUrls.clear();
+ framing::Array::ValueVector::const_iterator i;
+ for ( i = knownBrokers.begin(); i != knownBrokers.end(); ++i )
+ knownBrokersUrls.push_back(Url((*i)->get<std::string>()));
+ if (sasl.get()) {
+ securityLayer = sasl->getSecurityLayer(maxFrameSize);
+ operUserId = sasl->getUserId();
+ }
+ setState(OPEN);
+ QPID_LOG(debug, "Known-brokers for connection: " << log::formatList(knownBrokersUrls));
+}
+
+
+void ConnectionHandler::redirect(const std::string& /*host*/, const Array& /*knownHosts*/)
+{
+ throw NotImplementedException("Redirection received from broker; not yet implemented in client");
+}
+
+void ConnectionHandler::close(uint16_t replyCode, const std::string& replyText)
+{
+ proxy.closeOk();
+ errorCode = convert(replyCode);
+ errorText = replyText;
+ setState(CLOSED);
+ QPID_LOG(warning, "Broker closed connection: " << replyCode << ", " << replyText);
+ if (onError) {
+ onError(replyCode, replyText);
+ }
+}
+
+void ConnectionHandler::closeOk()
+{
+ checkState(CLOSING, INVALID_STATE_CLOSE_OK);
+ if (onError && errorCode != CLOSE_CODE_NORMAL) {
+ onError(errorCode, errorText);
+ } else if (onClose) {
+ onClose();
+ }
+ setState(CLOSED);
+}
+
+bool ConnectionHandler::isOpen() const
+{
+ return getState() == OPEN;
+}
+
+bool ConnectionHandler::isClosed() const
+{
+ int s = getState();
+ return s == CLOSED || s == FAILED;
+}
+
+bool ConnectionHandler::isClosing() const { return getState() == CLOSING; }
+
+std::auto_ptr<qpid::sys::SecurityLayer> ConnectionHandler::getSecurityLayer()
+{
+ return securityLayer;
+}
+
+void ConnectionHandler::setRcvTimeoutTask(boost::intrusive_ptr<qpid::sys::TimerTask> t)
+{
+ rcvTimeoutTask = t;
+}
diff --git a/qpid/cpp/src/qpid/client/ConnectionHandler.h b/qpid/cpp/src/qpid/client/ConnectionHandler.h
new file mode 100644
index 0000000000..6af2e987fb
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/ConnectionHandler.h
@@ -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.
+ *
+ */
+#ifndef _ConnectionHandler_
+#define _ConnectionHandler_
+
+#include "qpid/client/ChainableFrameHandler.h"
+#include "qpid/client/ConnectionSettings.h"
+#include "qpid/Sasl.h"
+#include "qpid/client/StateManager.h"
+#include "qpid/framing/AMQMethodBody.h"
+#include "qpid/framing/AMQP_HighestVersion.h"
+#include "qpid/framing/AMQP_ClientOperations.h"
+#include "qpid/framing/AMQP_ServerProxy.h"
+#include "qpid/framing/Array.h"
+#include "qpid/framing/enum.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/FrameHandler.h"
+#include "qpid/framing/InputHandler.h"
+#include "qpid/sys/SecurityLayer.h"
+#include "qpid/sys/Timer.h"
+#include "qpid/Url.h"
+#include <memory>
+
+namespace qpid {
+
+namespace sys {
+struct SecuritySettings;
+}
+
+namespace client {
+
+class Bounds;
+
+class ConnectionHandler : private StateManager,
+ public ConnectionSettings,
+ public ChainableFrameHandler,
+ public framing::InputHandler,
+ private framing::AMQP_ClientOperations::ConnectionHandler
+{
+ typedef framing::AMQP_ClientOperations::ConnectionHandler ConnectionOperations;
+ enum STATES {NOT_STARTED, NEGOTIATING, OPENING, OPEN, CLOSING, CLOSED, FAILED};
+ std::set<int> ESTABLISHED, FINISHED;
+
+ class Adapter : public framing::FrameHandler
+ {
+ ConnectionHandler& handler;
+ Bounds& bounds;
+ public:
+ Adapter(ConnectionHandler& h, Bounds& bounds);
+ void handle(framing::AMQFrame& f);
+ };
+
+ Adapter outHandler;
+ framing::AMQP_ServerProxy::Connection proxy;
+ framing::connection::CloseCode errorCode;
+ std::string errorText;
+ bool insist;
+ framing::ProtocolVersion version;
+ framing::Array capabilities;
+ framing::FieldTable properties;
+ std::auto_ptr<Sasl> sasl;
+ std::auto_ptr<qpid::sys::SecurityLayer> securityLayer;
+ boost::intrusive_ptr<qpid::sys::TimerTask> rcvTimeoutTask;
+ std::string operUserId;
+
+ void checkState(STATES s, const std::string& msg);
+
+ //methods corresponding to connection controls:
+ void start(const framing::FieldTable& serverProperties,
+ const framing::Array& mechanisms,
+ const framing::Array& locales);
+ void secure(const std::string& challenge);
+ void tune(uint16_t channelMax,
+ uint16_t frameMax,
+ uint16_t heartbeatMin,
+ uint16_t heartbeatMax);
+ void openOk(const framing::Array& knownHosts);
+ void redirect(const std::string& host,
+ const framing::Array& knownHosts);
+ void close(uint16_t replyCode, const std::string& replyText);
+ void closeOk();
+ void heartbeat();
+
+public:
+ using InputHandler::handle;
+ typedef boost::function<void()> CloseListener;
+ typedef boost::function<void(uint16_t, const std::string&)> ErrorListener;
+ typedef boost::function<const qpid::sys::SecuritySettings*()> GetSecuritySettings;
+
+ ConnectionHandler(const ConnectionSettings&, framing::ProtocolVersion&, Bounds&);
+
+ void received(framing::AMQFrame& f) { incoming(f); }
+
+ void incoming(framing::AMQFrame& frame);
+ void outgoing(framing::AMQFrame& frame);
+
+ void waitForOpen();
+ void close();
+ void fail(const std::string& message);
+
+ // Note that open and closed aren't related by open = !closed
+ bool isOpen() const;
+ bool isClosed() const;
+ bool isClosing() const;
+
+ std::auto_ptr<qpid::sys::SecurityLayer> getSecurityLayer();
+ void setRcvTimeoutTask(boost::intrusive_ptr<qpid::sys::TimerTask>);
+
+ CloseListener onClose;
+ ErrorListener onError;
+
+ std::vector<Url> knownBrokersUrls;
+
+ static framing::connection::CloseCode convert(uint16_t replyCode);
+ const std::string& getUserId() const { return operUserId; }
+ GetSecuritySettings getSecuritySettings; /** query the transport for its security details */
+};
+
+}}
+
+#endif
diff --git a/qpid/cpp/src/qpid/client/ConnectionImpl.cpp b/qpid/cpp/src/qpid/client/ConnectionImpl.cpp
new file mode 100644
index 0000000000..98d04d8d66
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/ConnectionImpl.cpp
@@ -0,0 +1,452 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/client/ConnectionImpl.h"
+
+#include "qpid/client/LoadPlugins.h"
+#include "qpid/client/Connector.h"
+#include "qpid/client/ConnectionSettings.h"
+#include "qpid/client/SessionImpl.h"
+
+#include "qpid/log/Statement.h"
+#include "qpid/Url.h"
+#include "qpid/framing/enum.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/sys/Poller.h"
+#include "qpid/sys/SystemInfo.h"
+#include "qpid/Options.h"
+
+#include <boost/bind.hpp>
+#include <boost/format.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <limits>
+#include <vector>
+
+#include "config.h"
+
+namespace qpid {
+namespace client {
+
+using namespace qpid::framing;
+using namespace qpid::framing::connection;
+using namespace qpid::sys;
+using namespace qpid::framing::connection;//for connection error codes
+
+namespace {
+// Maybe should amalgamate the singletons into a single client singleton
+
+// Get timer singleton
+Timer& theTimer() {
+ static Mutex timerInitLock;
+ ScopedLock<Mutex> l(timerInitLock);
+
+ static qpid::sys::Timer t;
+ return t;
+}
+
+struct IOThreadOptions : public qpid::Options {
+ int maxIOThreads;
+
+ IOThreadOptions(int c) :
+ Options("IO threading options"),
+ maxIOThreads(c)
+ {
+ addOptions()
+ ("max-iothreads", optValue(maxIOThreads, "N"), "Maximum number of io threads to use");
+ }
+};
+
+// IO threads
+class IOThread {
+ int maxIOThreads;
+ int ioThreads;
+ int connections;
+ Mutex threadLock;
+ std::vector<Thread> t;
+ Poller::shared_ptr poller_;
+
+public:
+ void add() {
+ ScopedLock<Mutex> l(threadLock);
+ ++connections;
+ if (!poller_)
+ poller_.reset(new Poller);
+ if (ioThreads < connections && ioThreads < maxIOThreads) {
+ QPID_LOG(debug, "Created IO thread: " << ioThreads);
+ ++ioThreads;
+ t.push_back( Thread(poller_.get()) );
+ }
+ }
+
+ void sub() {
+ ScopedLock<Mutex> l(threadLock);
+ --connections;
+ }
+
+ Poller::shared_ptr poller() const {
+ assert(poller_);
+ return poller_;
+ }
+
+ // Here is where the maximum number of threads is set
+ IOThread(int c) :
+ ioThreads(0),
+ connections(0)
+ {
+ CommonOptions common("", "", QPIDC_CONF_FILE);
+ IOThreadOptions options(c);
+ common.parse(0, 0, common.clientConfig, true);
+ options.parse(0, 0, common.clientConfig, true);
+ maxIOThreads = (options.maxIOThreads != -1) ?
+ options.maxIOThreads : 1;
+ }
+
+ // We can't destroy threads one-by-one as the only
+ // control we have is to shutdown the whole lot
+ // and we can't do that before we're unloaded as we can't
+ // restart the Poller after shutting it down
+ ~IOThread() {
+ if (SystemInfo::threadSafeShutdown()) {
+ std::vector<Thread> threads;
+ {
+ ScopedLock<Mutex> l(threadLock);
+ if (poller_)
+ poller_->shutdown();
+ t.swap(threads);
+ }
+ for (std::vector<Thread>::iterator i = threads.begin(); i != threads.end(); ++i) {
+ i->join();
+ }
+ }
+ }
+};
+
+IOThread& theIO() {
+ static IOThread io(SystemInfo::concurrency());
+ return io;
+}
+
+class HeartbeatTask : public TimerTask {
+ ConnectionImpl& timeout;
+
+ void fire() {
+ // If we ever get here then we have timed out
+ QPID_LOG(debug, "Traffic timeout");
+ timeout.timeout();
+ }
+
+public:
+ HeartbeatTask(Duration p, ConnectionImpl& t) :
+ TimerTask(p,"Heartbeat"),
+ timeout(t)
+ {}
+};
+
+}
+
+void ConnectionImpl::init() {
+ // Ensure that the plugin modules have been loaded
+ // This will make sure that any plugin protocols are available
+ theModuleLoader();
+
+ // Ensure the IO threads exist:
+ // This needs to be called in the Connection constructor
+ // so that they will still exist at last connection destruction
+ (void) theIO();
+}
+
+boost::shared_ptr<ConnectionImpl> ConnectionImpl::create(framing::ProtocolVersion version, const ConnectionSettings& settings)
+{
+ boost::shared_ptr<ConnectionImpl> instance(new ConnectionImpl(version, settings), boost::bind(&ConnectionImpl::release, _1));
+ return instance;
+}
+
+ConnectionImpl::ConnectionImpl(framing::ProtocolVersion v, const ConnectionSettings& settings)
+ : Bounds(settings.maxFrameSize * settings.bounds),
+ handler(settings, v, *this),
+ version(v),
+ nextChannel(1),
+ shutdownComplete(false),
+ released(false)
+{
+ handler.in = boost::bind(&ConnectionImpl::incoming, this, _1);
+ handler.out = boost::bind(&Connector::handle, boost::ref(connector), _1);
+ handler.onClose = boost::bind(&ConnectionImpl::closed, this,
+ CLOSE_CODE_NORMAL, std::string());
+ //only set error handler once open
+ handler.onError = boost::bind(&ConnectionImpl::closed, this, _1, _2);
+ handler.getSecuritySettings = boost::bind(&Connector::getSecuritySettings, boost::ref(connector));
+}
+
+const uint16_t ConnectionImpl::NEXT_CHANNEL = std::numeric_limits<uint16_t>::max();
+
+ConnectionImpl::~ConnectionImpl() {
+ if (heartbeatTask) heartbeatTask->cancel();
+ theIO().sub();
+}
+
+void ConnectionImpl::addSession(const boost::shared_ptr<SessionImpl>& session, uint16_t channel)
+{
+ Mutex::ScopedLock l(lock);
+ for (uint16_t i = 0; i < NEXT_CHANNEL; i++) { //will at most search through channels once
+ uint16_t c = channel == NEXT_CHANNEL ? nextChannel++ : channel;
+ boost::weak_ptr<SessionImpl>& s = sessions[c];
+ boost::shared_ptr<SessionImpl> ss = s.lock();
+ if (!ss) {
+ //channel is free, we can assign it to this session
+ session->setChannel(c);
+ s = session;
+ return;
+ } else if (channel != NEXT_CHANNEL) {
+ //channel is taken and was requested explicitly so don't look for another
+ throw SessionBusyException(QPID_MSG("Channel " << ss->getChannel() << " attached to " << ss->getId()));
+ } //else channel is busy, but we can keep looking for a free one
+ }
+ // If we get here, we didn't find any available channel.
+ throw ResourceLimitExceededException("There are no channels available");
+}
+
+void ConnectionImpl::handle(framing::AMQFrame& frame)
+{
+ handler.outgoing(frame);
+}
+
+void ConnectionImpl::incoming(framing::AMQFrame& frame)
+{
+ boost::shared_ptr<SessionImpl> s;
+ {
+ Mutex::ScopedLock l(lock);
+ s = sessions[frame.getChannel()].lock();
+ }
+ if (!s) {
+ QPID_LOG(info, *this << " dropping frame received on invalid channel: " << frame);
+ } else {
+ s->in(frame);
+ }
+}
+
+bool ConnectionImpl::isOpen() const
+{
+ return handler.isOpen();
+}
+
+void ConnectionImpl::open()
+{
+ const std::string& protocol = handler.protocol;
+ const std::string& host = handler.host;
+ int port = handler.port;
+
+ theIO().add();
+ connector.reset(Connector::create(protocol, theIO().poller(), version, handler, this));
+ connector->setInputHandler(&handler);
+ connector->setShutdownHandler(this);
+ try {
+ std::string p = boost::lexical_cast<std::string>(port);
+ connector->connect(host, p);
+
+ } catch (const std::exception& e) {
+ QPID_LOG(debug, "Failed to connect to " << protocol << ":" << host << ":" << port << " " << e.what());
+ connector.reset();
+ throw TransportFailure(e.what());
+ }
+ connector->init();
+
+ // Enable heartbeat if requested
+ uint16_t heartbeat = static_cast<ConnectionSettings&>(handler).heartbeat;
+ if (heartbeat) {
+ // Set connection timeout to be 2x heart beat interval and setup timer
+ heartbeatTask = new HeartbeatTask(heartbeat * 2 * TIME_SEC, *this);
+ handler.setRcvTimeoutTask(heartbeatTask);
+ theTimer().add(heartbeatTask);
+ }
+
+ // If the connect fails then the connector is cleaned up either when we try to connect again
+ // - in that case in connector.reset() above;
+ // - or when we are deleted
+ try {
+ handler.waitForOpen();
+ QPID_LOG(info, *this << " connected to " << protocol << ":" << host << ":" << port);
+ } catch (const Exception& e) {
+ connector->checkVersion(version);
+ throw;
+ }
+
+ // If the SASL layer has provided an "operational" userId for the connection,
+ // put it in the negotiated settings.
+ const std::string& userId(handler.getUserId());
+ if (!userId.empty())
+ handler.username = userId;
+
+ //enable security layer if one has been negotiated:
+ std::auto_ptr<SecurityLayer> securityLayer = handler.getSecurityLayer();
+ if (securityLayer.get()) {
+ QPID_LOG(debug, *this << " activating security layer");
+ connector->activateSecurityLayer(securityLayer);
+ } else {
+ QPID_LOG(debug, *this << " no security layer in place");
+ }
+}
+
+void ConnectionImpl::timeout()
+{
+ connector->abort();
+}
+
+void ConnectionImpl::close()
+{
+ if (heartbeatTask)
+ heartbeatTask->cancel();
+ // close() must be idempotent and no-throw as it will often be called in destructors.
+ if (handler.isOpen()) {
+ try {
+ handler.close();
+ closed(CLOSE_CODE_NORMAL, "Closed by client");
+ } catch (...) {}
+ }
+ assert(!handler.isOpen());
+}
+
+
+template <class F> void ConnectionImpl::closeInternal(const F& f) {
+ if (heartbeatTask) {
+ heartbeatTask->cancel();
+ }
+ {
+ Mutex::ScopedUnlock u(lock);
+ connector->close();
+ }
+ //notifying sessions of failure can result in those session being
+ //deleted which in turn results in a call to erase(); this can
+ //even happen on this thread, when 's' goes out of scope
+ //below. Using a copy prevents the map being modified as we
+ //iterate through.
+ SessionMap copy;
+ sessions.swap(copy);
+ for (SessionMap::iterator i = copy.begin(); i != copy.end(); ++i) {
+ boost::shared_ptr<SessionImpl> s = i->second.lock();
+ if (s) f(s);
+ }
+}
+
+void ConnectionImpl::closed(uint16_t code, const std::string& text) {
+ Mutex::ScopedLock l(lock);
+ setException(new ConnectionException(ConnectionHandler::convert(code), text));
+ closeInternal(boost::bind(&SessionImpl::connectionClosed, _1, code, text));
+}
+
+void ConnectionImpl::shutdown() {
+ if (!handler.isClosed()) {
+ failedConnection();
+ }
+ bool canDelete;
+ {
+ Mutex::ScopedLock l(lock);
+ //association with IO thread is now ended
+ shutdownComplete = true;
+ //If we have already been released, we can now delete ourselves
+ canDelete = released;
+ }
+ if (canDelete) delete this;
+}
+
+void ConnectionImpl::release() {
+ bool isActive;
+ {
+ Mutex::ScopedLock l(lock);
+ isActive = connector && !shutdownComplete;
+ }
+ //If we are still active - i.e. associated with an IO thread -
+ //then we cannot delete ourselves yet, but must wait for the
+ //shutdown callback which we can trigger by calling
+ //connector.close()
+ if (isActive) {
+ connector->close();
+ bool canDelete;
+ {
+ Mutex::ScopedLock l(lock);
+ released = true;
+ canDelete = shutdownComplete;
+ }
+ if (canDelete) delete this;
+ } else {
+ delete this;
+ }
+}
+
+static const std::string CONN_CLOSED("Connection closed");
+
+void ConnectionImpl::failedConnection() {
+ if ( failureCallback )
+ failureCallback();
+
+ if (handler.isClosed()) return;
+
+ bool isClosing = handler.isClosing();
+ bool isOpen = handler.isOpen();
+
+ std::ostringstream msg;
+ msg << *this << " closed";
+
+ // FIXME aconway 2008-06-06: exception use, amqp0-10 does not seem to have
+ // an appropriate close-code. connection-forced is not right.
+ handler.fail(msg.str());//ensure connection is marked as failed before notifying sessions
+
+ // At this point if the object isn't open and isn't closing it must have failed to open
+ // so we can't do the rest of the cleanup
+ if (!isClosing && !isOpen) return;
+
+ Mutex::ScopedLock l(lock);
+ closeInternal(boost::bind(&SessionImpl::connectionBroke, _1, msg.str()));
+ setException(new TransportFailure(msg.str()));
+}
+
+void ConnectionImpl::erase(uint16_t ch) {
+ Mutex::ScopedLock l(lock);
+ sessions.erase(ch);
+}
+
+const ConnectionSettings& ConnectionImpl::getNegotiatedSettings()
+{
+ return handler;
+}
+
+std::vector<qpid::Url> ConnectionImpl::getInitialBrokers() {
+ return handler.knownBrokersUrls;
+}
+
+boost::shared_ptr<SessionImpl> ConnectionImpl::newSession(const std::string& name, uint32_t timeout, uint16_t channel) {
+ boost::shared_ptr<SessionImpl> simpl(new SessionImpl(name, shared_from_this()));
+ addSession(simpl, channel);
+ simpl->open(timeout);
+ return simpl;
+}
+
+std::ostream& operator<<(std::ostream& o, const ConnectionImpl& c) {
+ if (c.connector)
+ return o << "Connection " << c.connector->getIdentifier();
+ else
+ return o << "Connection <not connected>";
+}
+
+
+}} // namespace qpid::client
diff --git a/qpid/cpp/src/qpid/client/ConnectionImpl.h b/qpid/cpp/src/qpid/client/ConnectionImpl.h
new file mode 100644
index 0000000000..b07ce142fb
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/ConnectionImpl.h
@@ -0,0 +1,104 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#ifndef _ConnectionImpl_
+#define _ConnectionImpl_
+
+#include "qpid/client/Bounds.h"
+#include "qpid/client/ConnectionHandler.h"
+
+#include "qpid/framing/FrameHandler.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/ShutdownHandler.h"
+
+#include <map>
+#include <iosfwd>
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+
+namespace qpid {
+namespace client {
+
+class Connector;
+struct ConnectionSettings;
+class SessionImpl;
+
+class ConnectionImpl : public Bounds,
+ public framing::FrameHandler,
+ public sys::ShutdownHandler,
+ public boost::enable_shared_from_this<ConnectionImpl>
+{
+ typedef std::map<uint16_t, boost::weak_ptr<SessionImpl> > SessionMap;
+
+ static const uint16_t NEXT_CHANNEL;
+
+ SessionMap sessions;
+ ConnectionHandler handler;
+ boost::scoped_ptr<Connector> connector;
+ framing::ProtocolVersion version;
+ uint16_t nextChannel;
+ sys::Mutex lock;
+ bool shutdownComplete;
+ bool released;
+
+ boost::intrusive_ptr<qpid::sys::TimerTask> heartbeatTask;
+
+ template <class F> void closeInternal(const F&);
+
+ void incoming(framing::AMQFrame& frame);
+ void closed(uint16_t, const std::string&);
+ void shutdown();
+ void failedConnection();
+ void release();
+ ConnectionImpl(framing::ProtocolVersion version, const ConnectionSettings& settings);
+
+ boost::function<void ()> failureCallback;
+
+ public:
+ static void init();
+ static boost::shared_ptr<ConnectionImpl> create(framing::ProtocolVersion version, const ConnectionSettings& settings);
+ ~ConnectionImpl();
+
+ void open();
+ bool isOpen() const;
+
+ boost::shared_ptr<SessionImpl> newSession(const std::string& name, uint32_t timeout, uint16_t channel=NEXT_CHANNEL);
+ void addSession(const boost::shared_ptr<SessionImpl>&, uint16_t channel=NEXT_CHANNEL);
+
+ void close();
+ void timeout();
+ void handle(framing::AMQFrame& frame);
+ void erase(uint16_t channel);
+ const ConnectionSettings& getNegotiatedSettings();
+
+ std::vector<Url> getInitialBrokers();
+ void registerFailureCallback ( boost::function<void ()> fn ) { failureCallback = fn; }
+ framing::ProtocolVersion getVersion() { return version; }
+
+ friend std::ostream& operator<<(std::ostream&, const ConnectionImpl&);
+};
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/client/ConnectionSettings.cpp b/qpid/cpp/src/qpid/client/ConnectionSettings.cpp
new file mode 100644
index 0000000000..7d41b48abd
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/ConnectionSettings.cpp
@@ -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.
+ *
+ */
+#include "qpid/client/ConnectionSettings.h"
+
+#include "qpid/log/Logger.h"
+#include "qpid/sys/Socket.h"
+#include "qpid/Url.h"
+#include "qpid/Version.h"
+
+namespace qpid {
+namespace client {
+
+ConnectionSettings::ConnectionSettings() :
+ protocol("tcp"),
+ host("localhost"),
+ port(5672),
+ locale("en_US"),
+ heartbeat(0),
+ maxChannels(32767),
+ maxFrameSize(65535),
+ bounds(2),
+ tcpNoDelay(true),
+ service(qpid::saslName),
+ minSsf(0),
+ maxSsf(256),
+ sslCertName(""),
+ sslIgnoreHostnameVerificationFailure(false)
+{}
+
+ConnectionSettings::~ConnectionSettings() {}
+
+void ConnectionSettings::configureSocket(qpid::sys::Socket& socket) const
+{
+ if (tcpNoDelay) {
+ socket.setTcpNoDelay();
+ QPID_LOG(info, "Set TCP_NODELAY");
+ }
+}
+
+}} // namespace qpid::client
diff --git a/qpid/cpp/src/qpid/client/ConnectionSettings.h b/qpid/cpp/src/qpid/client/ConnectionSettings.h
new file mode 100644
index 0000000000..a4b2b59ff7
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/ConnectionSettings.h
@@ -0,0 +1,145 @@
+#ifndef QPID_CLIENT_CONNECTIONSETTINGS_H
+#define QPID_CLIENT_CONNECTIONSETTINGS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/client/ClientImportExport.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/sys/IntegerTypes.h"
+#include <string>
+
+namespace qpid {
+
+namespace sys {
+class Socket;
+}
+
+namespace client {
+
+/**
+ * Settings for a Connection.
+ */
+struct QPID_CLIENT_CLASS_EXTERN ConnectionSettings {
+
+ QPID_CLIENT_EXTERN ConnectionSettings();
+ QPID_CLIENT_EXTERN virtual ~ConnectionSettings();
+
+ /**
+ * Allows socket to be configured; default only sets tcp-nodelay
+ * based on the flag set. Can be overridden.
+ */
+ QPID_CLIENT_EXTERN virtual void configureSocket(qpid::sys::Socket&) const;
+
+ /**
+ * The protocol used for the connection (defaults to 'tcp')
+ */
+ std::string protocol;
+
+ /**
+ * The host (or ip address) to connect to (defaults to 'localhost').
+ */
+ std::string host;
+ /**
+ * The port to connect to (defaults to 5672).
+ */
+ uint16_t port;
+ /**
+ * Allows an AMQP 'virtual host' to be specified for the
+ * connection.
+ */
+ std::string virtualhost;
+
+ /**
+ * The username to use when authenticating the connection. If not
+ * specified the current users login is used if available.
+ */
+ std::string username;
+ /**
+ * The password to use when authenticating the connection.
+ */
+ std::string password;
+ /**
+ * The SASL mechanism to use when authenticating the connection;
+ * the options are currently PLAIN or ANONYMOUS.
+ */
+ std::string mechanism;
+ /**
+ * Allows a locale to be specified for the connection.
+ */
+ std::string locale;
+ /**
+ * Allows a heartbeat frequency to be specified
+ */
+ uint16_t heartbeat;
+ /**
+ * The maximum number of channels that the client will request for
+ * use on this connection.
+ */
+ uint16_t maxChannels;
+ /**
+ * The maximum frame size that the client will request for this
+ * connection.
+ */
+ uint16_t maxFrameSize;
+ /**
+ * Limit the size of the connections send buffer . The buffer
+ * is limited to bounds * maxFrameSize.
+ */
+ unsigned int bounds;
+ /**
+ * If true, TCP_NODELAY will be set for the connection.
+ */
+ bool tcpNoDelay;
+ /**
+ * SASL service name
+ */
+ std::string service;
+ /**
+ * Minimum acceptable strength of any SASL negotiated security
+ * layer. 0 means no security layer required.
+ */
+ unsigned int minSsf;
+ /**
+ * Maximum acceptable strength of any SASL negotiated security
+ * layer. 0 means no security layer allowed.
+ */
+ unsigned int maxSsf;
+ /**
+ * SSL cert-name for the connection. Overrides global SSL
+ * settings. Used only when a client connects to the broker.
+ */
+ std::string sslCertName;
+
+ /**
+ * Passed as client-propreties on opening the connecction.
+ */
+ framing::FieldTable clientProperties;
+
+ /**
+ * If using SSL, connect regardless of hostname verification failure.
+ */
+ bool sslIgnoreHostnameVerificationFailure;
+};
+
+}} // namespace qpid::client
+
+#endif /*!QPID_CLIENT_CONNECTIONSETTINGS_H*/
diff --git a/qpid/cpp/src/qpid/client/Connector.cpp b/qpid/cpp/src/qpid/client/Connector.cpp
new file mode 100644
index 0000000000..afb3c8a597
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/Connector.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/client/Connector.h"
+#include "qpid/Url.h"
+#include "qpid/Exception.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/SecurityLayer.h"
+#include "qpid/framing/ProtocolInitiation.h"
+
+#include <map>
+
+namespace qpid {
+namespace client {
+
+using namespace qpid::sys;
+using namespace qpid::framing;
+
+namespace {
+ typedef std::map<std::string, Connector::Factory*> ProtocolRegistry;
+
+ ProtocolRegistry& theProtocolRegistry() {
+ static ProtocolRegistry protocolRegistry;
+
+ return protocolRegistry;
+ }
+}
+
+Connector* Connector::create(const std::string& proto,
+ boost::shared_ptr<Poller> p,
+ framing::ProtocolVersion v, const ConnectionSettings& s, ConnectionImpl* c)
+{
+ ProtocolRegistry::const_iterator i = theProtocolRegistry().find(proto);
+ if (i==theProtocolRegistry().end()) {
+ throw Exception(QPID_MSG("Unknown protocol: " << proto));
+ }
+ return (i->second)(p, v, s, c);
+}
+
+void Connector::registerFactory(const std::string& proto, Factory* connectorFactory)
+{
+ ProtocolRegistry::const_iterator i = theProtocolRegistry().find(proto);
+ if (i!=theProtocolRegistry().end()) {
+ QPID_LOG(error, "Tried to register protocol: " << proto << " more than once");
+ }
+ theProtocolRegistry()[proto] = connectorFactory;
+ Url::addProtocol(proto);
+}
+
+void Connector::activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer>)
+{
+}
+
+bool Connector::checkProtocolHeader(framing::Buffer& in, const framing::ProtocolVersion& version)
+{
+ if (!header) {
+ boost::shared_ptr<framing::ProtocolInitiation> protocolInit(new framing::ProtocolInitiation);
+ if (protocolInit->decode(in)) {
+ header = protocolInit;
+ QPID_LOG(debug, "RECV [" << getIdentifier() << "]: INIT(" << *protocolInit << ")");
+ checkVersion(version);
+ }
+ }
+ return header.get();
+}
+
+void Connector::checkVersion(const framing::ProtocolVersion& version)
+{
+ if (header && !header->matches(version)){
+ throw ProtocolVersionError(QPID_MSG("Incorrect version: " << *header
+ << "; expected " << version));
+ }
+}
+
+
+}} // namespace qpid::client
diff --git a/qpid/cpp/src/qpid/client/Connector.h b/qpid/cpp/src/qpid/client/Connector.h
new file mode 100644
index 0000000000..49fb48bdf6
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/Connector.h
@@ -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.
+ *
+ */
+#ifndef _Connector_
+#define _Connector_
+
+
+#include "qpid/framing/FrameHandler.h"
+#include "qpid/framing/ProtocolVersion.h"
+
+#include <boost/shared_ptr.hpp>
+
+#include <string>
+
+namespace qpid {
+
+namespace sys {
+class ShutdownHandler;
+class SecurityLayer;
+class Poller;
+struct SecuritySettings;
+}
+
+namespace framing {
+class InputHandler;
+class AMQFrame;
+class Buffer;
+class ProtocolInitiation;
+}
+
+namespace client {
+
+struct ConnectionSettings;
+class ConnectionImpl;
+
+///@internal
+class Connector : public framing::FrameHandler
+{
+ public:
+ // Protocol connector factory related stuff (it might be better to separate this code from the TCP Connector in the future)
+ typedef Connector* Factory(boost::shared_ptr<qpid::sys::Poller>,
+ framing::ProtocolVersion, const ConnectionSettings&, ConnectionImpl*);
+ static Connector* create(const std::string& proto,
+ boost::shared_ptr<qpid::sys::Poller>,
+ framing::ProtocolVersion, const ConnectionSettings&, ConnectionImpl*);
+ static void registerFactory(const std::string& proto, Factory* connectorFactory);
+
+ virtual ~Connector() {};
+ virtual void connect(const std::string& host, const std::string& port) = 0;
+ virtual void init() {};
+ virtual void close() = 0;
+ virtual void handle(framing::AMQFrame& frame) = 0;
+ virtual void abort() = 0;
+
+ virtual void setInputHandler(framing::InputHandler* handler) = 0;
+ virtual void setShutdownHandler(sys::ShutdownHandler* handler) = 0;
+ virtual const std::string& getIdentifier() const = 0;
+
+ virtual void activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer>);
+
+ virtual const qpid::sys::SecuritySettings* getSecuritySettings() = 0;
+ void checkVersion(const framing::ProtocolVersion& version);
+ protected:
+ boost::shared_ptr<framing::ProtocolInitiation> header;
+
+ bool checkProtocolHeader(framing::Buffer&, const framing::ProtocolVersion& version);
+};
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/client/Demux.cpp b/qpid/cpp/src/qpid/client/Demux.cpp
new file mode 100644
index 0000000000..abc23c75df
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/Demux.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/client/Demux.h"
+#include "qpid/Exception.h"
+#include "qpid/framing/MessageTransferBody.h"
+
+#include <iostream>
+
+namespace qpid {
+namespace client {
+
+ByTransferDest::ByTransferDest(const std::string& d) : dest(d) {}
+bool ByTransferDest::operator()(const framing::FrameSet& frameset) const
+{
+ return frameset.isA<framing::MessageTransferBody>() &&
+ frameset.as<framing::MessageTransferBody>()->getDestination() == dest;
+}
+
+ScopedDivert::ScopedDivert(const std::string& _dest, Demux& _demuxer) : dest(_dest), demuxer(_demuxer)
+{
+ queue = demuxer.add(dest, ByTransferDest(dest));
+}
+
+ScopedDivert::~ScopedDivert()
+{
+ demuxer.remove(dest);
+}
+
+Demux::Demux() : defaultQueue(new Queue()) {}
+
+Demux::~Demux() { close(sys::ExceptionHolder(new ClosedException())); }
+
+Demux::QueuePtr ScopedDivert::getQueue()
+{
+ return queue;
+}
+
+void Demux::handle(framing::FrameSet::shared_ptr frameset)
+{
+ sys::Mutex::ScopedLock l(lock);
+ bool matched = false;
+ for (iterator i = records.begin(); i != records.end() && !matched; i++) {
+ if (i->condition && i->condition(*frameset)) {
+ matched = true;
+ i->queue->push(frameset);
+ }
+ }
+ if (!matched) {
+ defaultQueue->push(frameset);
+ }
+}
+
+void Demux::close(const sys::ExceptionHolder& ex)
+{
+ sys::Mutex::ScopedLock l(lock);
+ for (iterator i = records.begin(); i != records.end(); i++) {
+ i->queue->close(ex);
+ }
+ defaultQueue->close(ex);
+}
+
+void Demux::open()
+{
+ sys::Mutex::ScopedLock l(lock);
+ for (iterator i = records.begin(); i != records.end(); i++) {
+ i->queue->open();
+ }
+ defaultQueue->open();
+}
+
+Demux::QueuePtr Demux::add(const std::string& name, Condition condition)
+{
+ sys::Mutex::ScopedLock l(lock);
+ iterator i = std::find_if(records.begin(), records.end(), Find(name));
+ if (i == records.end()) {
+ Record r(name, condition);
+ records.push_back(r);
+ return r.queue;
+ } else {
+ throw Exception("Queue already exists for " + name);
+ }
+}
+
+void Demux::remove(const std::string& name)
+{
+ sys::Mutex::ScopedLock l(lock);
+ records.remove_if(Find(name));
+}
+
+Demux::QueuePtr Demux::get(const std::string& name)
+{
+ sys::Mutex::ScopedLock l(lock);
+ iterator i = std::find_if(records.begin(), records.end(), Find(name));
+ if (i == records.end()) {
+ throw Exception("No queue for " + name);
+ }
+ return i->queue;
+}
+
+Demux::QueuePtr Demux::getDefault()
+{
+ return defaultQueue;
+}
+
+Demux::Find::Find(const std::string& n) : name(n) {}
+
+bool Demux::Find::operator()(const Record& record) const
+{
+ return record.name == name;
+}
+
+}}
+
diff --git a/qpid/cpp/src/qpid/client/Demux.h b/qpid/cpp/src/qpid/client/Demux.h
new file mode 100644
index 0000000000..31dc3f9c06
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/Demux.h
@@ -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.
+ *
+ */
+
+#include <list>
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+#include "qpid/framing/FrameSet.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/BlockingQueue.h"
+#include "qpid/client/ClientImportExport.h"
+
+#ifndef _Demux_
+#define _Demux_
+
+namespace qpid {
+namespace client {
+
+///@internal
+class ByTransferDest
+{
+ const std::string dest;
+public:
+ ByTransferDest(const std::string& dest);
+ bool operator()(const framing::FrameSet& frameset) const;
+};
+
+///@internal
+class Demux
+{
+public:
+ typedef boost::function<bool(const framing::FrameSet&)> Condition;
+ typedef sys::BlockingQueue<framing::FrameSet::shared_ptr> Queue;
+ typedef boost::shared_ptr<Queue> QueuePtr;
+
+ QPID_CLIENT_EXTERN Demux();
+ QPID_CLIENT_EXTERN ~Demux();
+
+ QPID_CLIENT_EXTERN void handle(framing::FrameSet::shared_ptr);
+ QPID_CLIENT_EXTERN void close(const sys::ExceptionHolder& ex);
+ QPID_CLIENT_EXTERN void open();
+
+ QPID_CLIENT_EXTERN QueuePtr add(const std::string& name, Condition);
+ QPID_CLIENT_EXTERN void remove(const std::string& name);
+ QPID_CLIENT_EXTERN QueuePtr get(const std::string& name);
+ QPID_CLIENT_EXTERN QueuePtr getDefault();
+
+private:
+ struct Record
+ {
+ const std::string name;
+ Condition condition;
+ QueuePtr queue;
+
+ Record(const std::string& n, Condition c) : name(n), condition(c), queue(new Queue()) {}
+ };
+
+ sys::Mutex lock;
+ std::list<Record> records;
+ QueuePtr defaultQueue;
+
+ typedef std::list<Record>::iterator iterator;
+
+ struct Find
+ {
+ const std::string name;
+ Find(const std::string& name);
+ bool operator()(const Record& record) const;
+ };
+};
+
+class ScopedDivert
+{
+ const std::string dest;
+ Demux& demuxer;
+ Demux::QueuePtr queue;
+public:
+ ScopedDivert(const std::string& dest, Demux& demuxer);
+ ~ScopedDivert();
+ Demux::QueuePtr getQueue();
+};
+
+}} // namespace qpid::client
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/client/Dispatcher.cpp b/qpid/cpp/src/qpid/client/Dispatcher.cpp
new file mode 100644
index 0000000000..a715c623bf
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/Dispatcher.cpp
@@ -0,0 +1,151 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/client/Dispatcher.h"
+#include "qpid/client/SubscriptionImpl.h"
+#include "qpid/client/SessionImpl.h"
+
+#include "qpid/framing/FrameSet.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/BlockingQueue.h"
+#include "qpid/client/Message.h"
+#include "qpid/client/MessageImpl.h"
+
+#include <boost/version.hpp>
+#if (BOOST_VERSION >= 104000)
+# include <boost/serialization/state_saver.hpp>
+ using boost::serialization::state_saver;
+#else
+# include <boost/state_saver.hpp>
+ using boost::state_saver;
+#endif /* BOOST_VERSION */
+
+using qpid::framing::FrameSet;
+using qpid::framing::MessageTransferBody;
+using qpid::sys::Mutex;
+using qpid::sys::ScopedLock;
+using qpid::sys::Thread;
+
+namespace qpid {
+namespace client {
+
+Dispatcher::Dispatcher(const Session& s, const std::string& q)
+ : session(s),
+ running(false),
+ autoStop(true),
+ failoverHandler(0)
+{
+ Demux& demux = SessionBase_0_10Access(session).get()->getDemux();
+ queue = q.empty() ? demux.getDefault() : demux.get(q);
+}
+
+void Dispatcher::start()
+{
+ worker = Thread(this);
+}
+
+void Dispatcher::wait()
+{
+ worker.join();
+}
+
+void Dispatcher::run()
+{
+ Mutex::ScopedLock l(lock);
+ if (running)
+ throw Exception("Dispatcher is already running.");
+ state_saver<bool> reset(running); // Reset to false on exit.
+ running = true;
+ try {
+ while (!queue->isClosed()) {
+ Mutex::ScopedUnlock u(lock);
+ FrameSet::shared_ptr content = queue->pop();
+ if (content->isA<MessageTransferBody>()) {
+ Message msg(new MessageImpl(*content));
+ boost::intrusive_ptr<SubscriptionImpl> listener = find(msg.getDestination());
+ if (!listener) {
+ QPID_LOG(error, "No listener found for destination " << msg.getDestination());
+ } else {
+ assert(listener);
+ listener->received(msg);
+ }
+ } else {
+ if (handler.get()) {
+ handler->handle(*content);
+ } else {
+ QPID_LOG(warning, "No handler found for " << *(content->getMethod()));
+ }
+ }
+ }
+ session.sync(); // Make sure all our acks are received before returning.
+ }
+ catch (const ClosedException&) {
+ QPID_LOG(debug, QPID_MSG(session.getId() << ": closed by peer"));
+ }
+ catch (const TransportFailure&) {
+ QPID_LOG(info, QPID_MSG(session.getId() << ": transport failure"));
+ throw;
+ }
+ catch (const std::exception& e) {
+ if ( failoverHandler ) {
+ QPID_LOG(debug, QPID_MSG(session.getId() << " failover: " << e.what()));
+ failoverHandler();
+ } else {
+ QPID_LOG(error, session.getId() << " error: " << e.what());
+ throw;
+ }
+ }
+}
+
+void Dispatcher::stop()
+{
+ ScopedLock<Mutex> l(lock);
+ queue->close(); // Will interrupt thread blocked in pop()
+}
+
+void Dispatcher::setAutoStop(bool b)
+{
+ ScopedLock<Mutex> l(lock);
+ autoStop = b;
+}
+
+boost::intrusive_ptr<SubscriptionImpl> Dispatcher::find(const std::string& name)
+{
+ ScopedLock<Mutex> l(lock);
+ Listeners::iterator i = listeners.find(name);
+ if (i == listeners.end()) {
+ return defaultListener;
+ }
+ return i->second;
+}
+
+void Dispatcher::listen(const boost::intrusive_ptr<SubscriptionImpl>& subscription) {
+ ScopedLock<Mutex> l(lock);
+ listeners[subscription->getName()] = subscription;
+}
+
+void Dispatcher::cancel(const std::string& destination) {
+ ScopedLock<Mutex> l(lock);
+ if (listeners.erase(destination) && running && autoStop && listeners.empty())
+ queue->close();
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/client/Dispatcher.h b/qpid/cpp/src/qpid/client/Dispatcher.h
new file mode 100644
index 0000000000..74fdb90103
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/Dispatcher.h
@@ -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.
+ *
+ */
+#ifndef _Dispatcher_
+#define _Dispatcher_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <boost/shared_ptr.hpp>
+#include "qpid/client/Session.h"
+#include "qpid/client/SessionBase_0_10Access.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/Runnable.h"
+#include "qpid/sys/Thread.h"
+#include "qpid/client/ClientImportExport.h"
+#include "qpid/client/MessageListener.h"
+#include "qpid/client/SubscriptionImpl.h"
+
+namespace qpid {
+namespace client {
+
+class SubscriptionImpl;
+
+///@internal
+typedef framing::Handler<framing::FrameSet> FrameSetHandler;
+
+///@internal
+class Dispatcher : public sys::Runnable
+{
+ typedef std::map<std::string, boost::intrusive_ptr<SubscriptionImpl> >Listeners;
+ sys::Mutex lock;
+ sys::Thread worker;
+ Session session;
+ Demux::QueuePtr queue;
+ bool running;
+ bool autoStop;
+ Listeners listeners;
+ boost::intrusive_ptr<SubscriptionImpl> defaultListener;
+ std::auto_ptr<FrameSetHandler> handler;
+
+ boost::intrusive_ptr<SubscriptionImpl> find(const std::string& name);
+ bool isStopped();
+
+ boost::function<void ()> failoverHandler;
+
+public:
+ Dispatcher(const Session& session, const std::string& queue = "");
+ ~Dispatcher() {}
+
+ void start();
+ void wait();
+ // As this class is marked 'internal', no extern should be made here;
+ // however, some test programs rely on it.
+ QPID_CLIENT_EXTERN void run();
+ void stop();
+ void setAutoStop(bool b);
+
+ void registerFailoverHandler ( boost::function<void ()> fh )
+ {
+ failoverHandler = fh;
+ }
+
+ void listen(const boost::intrusive_ptr<SubscriptionImpl>& subscription);
+ void cancel(const std::string& destination);
+};
+
+}}
+
+#endif
diff --git a/qpid/cpp/src/qpid/client/Execution.h b/qpid/cpp/src/qpid/client/Execution.h
new file mode 100644
index 0000000000..ad622af9c1
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/Execution.h
@@ -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.
+ *
+ */
+#ifndef _Execution_
+#define _Execution_
+
+#include "qpid/framing/SequenceNumber.h"
+#include "qpid/client/Demux.h"
+
+namespace qpid {
+namespace client {
+
+/**@internal
+ *
+ * Provides access to more detailed aspects of the session
+ * implementation.
+ */
+class Execution
+{
+public:
+ virtual ~Execution() {}
+ /**
+ * Provides access to the demultiplexing function within the
+ * session implementation
+ */
+ virtual Demux& getDemux() = 0;
+ /**
+ * Wait until notification has been received of completion of the
+ * outgoing command with the specified id.
+ */
+ void waitForCompletion(const framing::SequenceNumber& id);
+};
+
+}}
+
+#endif
diff --git a/qpid/cpp/src/qpid/client/FailoverListener.cpp b/qpid/cpp/src/qpid/client/FailoverListener.cpp
new file mode 100644
index 0000000000..1a69182c90
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/FailoverListener.cpp
@@ -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.
+ *
+ */
+#include "qpid/client/FailoverListener.h"
+#include "qpid/client/Session.h"
+#include "qpid/framing/Uuid.h"
+#include "qpid/log/Statement.h"
+#include "qpid/log/Helpers.h"
+#include "qpid/UrlArray.h"
+
+namespace qpid {
+namespace client {
+
+const std::string FailoverListener::AMQ_FAILOVER("amq.failover");
+
+FailoverListener::FailoverListener(Connection c) :
+ connection(c),
+ session(c.newSession(AMQ_FAILOVER+"."+framing::Uuid(true).str())),
+ subscriptions(session)
+{ init(true); }
+
+FailoverListener::FailoverListener(Connection c, bool useInitial) :
+ connection(c),
+ session(c.newSession(AMQ_FAILOVER+"."+framing::Uuid(true).str())),
+ subscriptions(session)
+{ init(useInitial); }
+
+void FailoverListener::init(bool useInitial) {
+ if (useInitial) knownBrokers = connection.getInitialBrokers();
+ if (session.exchangeQuery(arg::name=AMQ_FAILOVER).getNotFound()) {
+ session.close();
+ return;
+ }
+ std::string qname=session.getId().getName();
+ session.queueDeclare(arg::queue=qname, arg::exclusive=true, arg::autoDelete=true);
+ session.exchangeBind(arg::queue=qname, arg::exchange=AMQ_FAILOVER);
+ subscriptions.subscribe(*this, qname, SubscriptionSettings(FlowControl::unlimited(),
+ ACCEPT_MODE_NONE));
+ thread = sys::Thread(*this);
+}
+
+void FailoverListener::run() {
+ try {
+ subscriptions.run();
+ } catch(...) {}
+}
+
+FailoverListener::~FailoverListener() {
+ try {
+ subscriptions.stop();
+ thread.join();
+ if (connection.isOpen()) {
+ session.sync();
+ session.close();
+ }
+ } catch (...) {}
+}
+
+void FailoverListener::received(Message& msg) {
+ sys::Mutex::ScopedLock l(lock);
+ knownBrokers = getKnownBrokers(msg);
+}
+
+std::vector<Url> FailoverListener::getKnownBrokers() const {
+ sys::Mutex::ScopedLock l(lock);
+ return knownBrokers;
+}
+
+std::vector<Url> FailoverListener::getKnownBrokers(const Message& msg) {
+ framing::Array urlArray;
+ msg.getHeaders().getArray("amq.failover", urlArray);
+ return urlArrayToVector(urlArray);
+}
+
+
+}} // namespace qpid::client
diff --git a/qpid/cpp/src/qpid/client/FailoverListener.h b/qpid/cpp/src/qpid/client/FailoverListener.h
new file mode 100644
index 0000000000..53c7c26211
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/FailoverListener.h
@@ -0,0 +1,88 @@
+#ifndef QPID_CLIENT_FAILOVERLISTENER_H
+#define QPID_CLIENT_FAILOVERLISTENER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/client/ClientImportExport.h"
+#include "qpid/client/MessageListener.h"
+#include "qpid/client/Connection.h"
+#include "qpid/client/Session.h"
+#include "qpid/client/SubscriptionManager.h"
+#include "qpid/Url.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/Runnable.h"
+#include "qpid/sys/Thread.h"
+#include <vector>
+
+namespace qpid {
+namespace client {
+
+
+/**
+ * Listen for updates from the amq.failover exchange.
+ *
+ * In a cluster, the amq.failover exchange provides updates whenever
+ * the cluster membership changes. This class subscribes to the
+ * failover exchange and providees the latest list of known brokers.
+ *
+ * You can also subscribe to amq.failover yourself and use
+ * FailoverListener::decode to extract a list of broker URLs from a
+ * failover exchange message.
+ */
+class QPID_CLIENT_CLASS_EXTERN FailoverListener : private MessageListener, private qpid::sys::Runnable
+{
+ public:
+ /** The name of the standard failover exchange amq.failover */
+ static QPID_CLIENT_EXTERN const std::string AMQ_FAILOVER;
+
+ /** Extract the broker list from a failover exchange message */
+ static QPID_CLIENT_EXTERN std::vector<Url> getKnownBrokers(const Message& m);
+
+ /** Subscribe to amq.failover exchange. */
+ QPID_CLIENT_EXTERN FailoverListener(Connection);
+
+ /** Subscribe to amq.failover exchange.
+ *@param useInitial If true use the connection's initial brokers as
+ * the initial value of getKnownBrokers
+ */
+ QPID_CLIENT_EXTERN FailoverListener(Connection, bool useInitial);
+
+ QPID_CLIENT_EXTERN ~FailoverListener();
+
+ /** Returns the latest list of known broker URLs. */
+ QPID_CLIENT_EXTERN std::vector<Url> getKnownBrokers() const;
+
+ private:
+ void received(Message& msg);
+ void run();
+ void init(bool);
+
+ mutable sys::Mutex lock;
+ Connection connection;
+ Session session;
+ SubscriptionManager subscriptions;
+ sys::Thread thread;
+ std::vector<Url> knownBrokers;
+};
+}} // namespace qpid::client
+
+#endif /*!QPID_CLIENT_FAILOVERLISTENER_H*/
diff --git a/qpid/cpp/src/qpid/client/FailoverManager.cpp b/qpid/cpp/src/qpid/client/FailoverManager.cpp
new file mode 100644
index 0000000000..f27aeb5b52
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/FailoverManager.cpp
@@ -0,0 +1,133 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/client/FailoverManager.h"
+#include "qpid/Exception.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/Time.h"
+
+
+namespace qpid {
+namespace client {
+
+using qpid::sys::Monitor;
+using qpid::sys::AbsTime;
+using qpid::sys::Duration;
+
+FailoverManager::FailoverManager(const ConnectionSettings& s,
+ ReconnectionStrategy* rs) : settings(s), strategy(rs), state(IDLE) {}
+
+FailoverManager::~FailoverManager()
+{}
+
+void FailoverManager::execute(Command& c)
+{
+ bool retry = false;
+ bool completed = false;
+ AbsTime failed;
+ while (!completed) {
+ try {
+ AsyncSession session = connect().newSession();
+ if (retry) {
+ Duration failoverTime(failed, AbsTime::now());
+ QPID_LOG(info, "Failed over for " << &c << " in " << (failoverTime/qpid::sys::TIME_MSEC) << " milliseconds");
+ }
+ c.execute(session, retry);
+ session.sync();//TODO: shouldn't be required
+ session.close();
+ completed = true;
+ } catch(const TransportFailure&) {
+ retry = true;
+ failed = AbsTime::now();
+ }
+ }
+}
+
+void FailoverManager::close()
+{
+ Monitor::ScopedLock l(lock);
+ connection.close();
+}
+
+Connection& FailoverManager::connect(std::vector<Url> brokers)
+{
+ Monitor::ScopedLock l(lock);
+ if (state == CANT_CONNECT) {
+ state = IDLE;//retry
+ }
+ while (!connection.isOpen()) {
+ if (state == CONNECTING) {
+ lock.wait();
+ } else if (state == CANT_CONNECT) {
+ throw CannotConnectException("Cannot establish a connection");
+ } else {
+ state = CONNECTING;
+ Connection c;
+ if (brokers.empty() && failoverListener.get())
+ brokers = failoverListener->getKnownBrokers();
+ attempt(c, settings, brokers);
+ if (c.isOpen()) state = IDLE;
+ else state = CANT_CONNECT;
+ connection = c;
+ lock.notifyAll();
+ }
+ }
+ return connection;
+}
+
+Connection& FailoverManager::getConnection()
+{
+ Monitor::ScopedLock l(lock);
+ return connection;
+}
+
+void FailoverManager::attempt(Connection& c, ConnectionSettings s, std::vector<Url> urls)
+{
+ Monitor::ScopedUnlock u(lock);
+ if (strategy) strategy->editUrlList(urls);
+ if (urls.empty()) {
+ attempt(c, s);
+ } else {
+ for (std::vector<Url>::const_iterator i = urls.begin(); i != urls.end() && !c.isOpen(); ++i) {
+ for (Url::const_iterator j = i->begin(); j != i->end() && !c.isOpen(); ++j) {
+ const Address& addr = *j;
+ s.protocol = addr.protocol;
+ s.host = addr.host;
+ s.port = addr.port;
+ attempt(c, s);
+ }
+ }
+ }
+}
+
+void FailoverManager::attempt(Connection& c, ConnectionSettings s)
+{
+ try {
+ QPID_LOG(info, "Attempting to connect to " << s.host << " on " << s.port << "...");
+ c.open(s);
+ failoverListener.reset(new FailoverListener(c));
+ QPID_LOG(info, "Connected to " << s.host << " on " << s.port);
+ } catch (const Exception& e) {
+ QPID_LOG(info, "Could not connect to " << s.host << " on " << s.port << ": " << e.what());
+ }
+}
+
+
+}} // namespace qpid::client
diff --git a/qpid/cpp/src/qpid/client/FailoverManager.h b/qpid/cpp/src/qpid/client/FailoverManager.h
new file mode 100644
index 0000000000..bc739fd0f5
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/FailoverManager.h
@@ -0,0 +1,138 @@
+#ifndef QPID_CLIENT_FAILOVERMANAGER_H
+#define QPID_CLIENT_FAILOVERMANAGER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/Exception.h"
+#include "qpid/client/AsyncSession.h"
+#include "qpid/client/ClientImportExport.h"
+#include "qpid/client/Connection.h"
+#include "qpid/client/ConnectionSettings.h"
+#include "qpid/client/FailoverListener.h"
+#include "qpid/sys/Monitor.h"
+#include <vector>
+
+namespace qpid {
+namespace client {
+
+struct CannotConnectException : qpid::Exception
+{
+ CannotConnectException(const std::string& m) : qpid::Exception(m) {}
+};
+
+/**
+ * Utility to manage failover.
+ */
+class QPID_CLIENT_CLASS_EXTERN FailoverManager
+{
+ public:
+ /**
+ * Interface to implement for doing work that can be resumed on
+ * failover
+ */
+ struct Command
+ {
+ /**
+ * This method will be called with isRetry=false when the
+ * command is first executed. The session to use for the work
+ * will be passed to the implementing class. If the connection
+ * fails while the execute call is in progress, the
+ * FailoverManager controlling the execution will re-establish
+ * a connection, open a new session and call back to the
+ * Command implementations execute method with the new session
+ * and isRetry=true.
+ */
+ virtual void execute(AsyncSession& session, bool isRetry) = 0;
+ virtual ~Command() {}
+ };
+
+ struct ReconnectionStrategy
+ {
+ /**
+ * This method is called by the FailoverManager prior to
+ * establishing a connection (or re-connection) and can be
+ * used if the application wishes to edit or re-order the list
+ * which will default to the list of known brokers for the
+ * last connection.
+ */
+ virtual void editUrlList(std::vector<Url>& urls) = 0;
+ virtual ~ReconnectionStrategy() {}
+ };
+
+ /**
+ * Create a manager to control failover for a logical connection.
+ *
+ * @param settings the initial connection settings
+ * @param strategy optional stratgey callback allowing application
+ * to edit or reorder the list of urls to which reconnection is
+ * attempted
+ */
+ QPID_CLIENT_EXTERN FailoverManager(const ConnectionSettings& settings, ReconnectionStrategy* strategy = 0);
+ QPID_CLIENT_EXTERN ~FailoverManager();
+ /**
+ * Return the current connection if open or attept to reconnect to
+ * the specified list of urls. If no list is specified the list of
+ * known brokers from the last connection will be used. If no list
+ * is specified and this is the first connect attempt, the host
+ * and port from the initial settings will be used.
+ *
+ * If the full list is tried and all attempts fail,
+ * CannotConnectException is thrown.
+ */
+ QPID_CLIENT_EXTERN Connection& connect(std::vector<Url> brokers = std::vector<Url>());
+ /**
+ * Return the current connection whether open or not
+ */
+ QPID_CLIENT_EXTERN Connection& getConnection();
+ /**
+ * Close the current connection
+ */
+ QPID_CLIENT_EXTERN void close();
+ /**
+ * Reliably execute the specified command. This involves creating
+ * a session on which to carry out the work of the command,
+ * handling failover occuring while exeuting that command and
+ * re-starting the work.
+ *
+ * Multiple concurrent threads can call execute with different
+ * commands; each thread will be allocated its own
+ * session. FailoverManager will coordinate the different threads
+ * on failover to ensure they continue to use the same logical
+ * connection.
+ */
+ QPID_CLIENT_EXTERN void execute(Command&);
+ private:
+ enum State {IDLE, CONNECTING, CANT_CONNECT};
+
+ qpid::sys::Monitor lock;
+ Connection connection;
+ std::auto_ptr<FailoverListener> failoverListener;
+ ConnectionSettings settings;
+ ReconnectionStrategy* strategy;
+ State state;
+
+ void attempt(Connection&, ConnectionSettings settings, std::vector<Url> urls);
+ void attempt(Connection&, ConnectionSettings settings);
+};
+}} // namespace qpid::client
+
+#endif /*!QPID_CLIENT_FAILOVERMANAGER_H*/
diff --git a/qpid/cpp/src/qpid/client/FlowControl.h b/qpid/cpp/src/qpid/client/FlowControl.h
new file mode 100644
index 0000000000..bff7071b3b
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/FlowControl.h
@@ -0,0 +1,75 @@
+#ifndef QPID_CLIENT_FLOWCONTROL_H
+#define QPID_CLIENT_FLOWCONTROL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <qpid/sys/IntegerTypes.h>
+
+namespace qpid {
+namespace client {
+
+/**
+ * Flow control works by associating a finite amount of "credit"
+ * with a subscription.
+ *
+ * Credit includes a message count and a byte count. Each message
+ * received decreases the message count by one, and the byte count by
+ * the size of the message. Either count can have the special value
+ * UNLIMITED which is never decreased.
+ *
+ * A subscription's credit is exhausted when the message count is 0 or
+ * the byte count is too small for the next available message. The
+ * subscription will not receive any further messages until is credit
+ * is renewed.
+ *
+ * In "window mode" credit is automatically renewed when a message is
+ * completed (which by default happens when it is accepted). In
+ * non-window mode credit is not automatically renewed, it must be
+ * explicitly re-set (@see Subscription)
+ */
+struct FlowControl {
+ static const uint32_t UNLIMITED=0xFFFFFFFF;
+ FlowControl(uint32_t messages_=0, uint32_t bytes_=0, bool window_=false)
+ : messages(messages_), bytes(bytes_), window(window_) {}
+
+ static FlowControl messageCredit(uint32_t messages_) { return FlowControl(messages_,UNLIMITED,false); }
+ static FlowControl messageWindow(uint32_t messages_) { return FlowControl(messages_,UNLIMITED,true); }
+ static FlowControl byteCredit(uint32_t bytes_) { return FlowControl(UNLIMITED,bytes_,false); }
+ static FlowControl byteWindow(uint32_t bytes_) { return FlowControl(UNLIMITED,bytes_,true); }
+ static FlowControl unlimited() { return FlowControl(UNLIMITED, UNLIMITED, false); }
+ static FlowControl zero() { return FlowControl(0, 0, false); }
+
+ /** Message credit: subscription can accept up to this many messages. */
+ uint32_t messages;
+ /** Byte credit: subscription can accept up to this many bytes of message content. */
+ uint32_t bytes;
+ /** Window mode. If true credit is automatically renewed as messages are acknowledged. */
+ bool window;
+
+ bool operator==(const FlowControl& x) {
+ return messages == x.messages && bytes == x.bytes && window == x.window;
+ };
+};
+
+}} // namespace qpid::client
+
+#endif /*!QPID_CLIENT_FLOWCONTROL_H*/
diff --git a/qpid/cpp/src/qpid/client/Future.cpp b/qpid/cpp/src/qpid/client/Future.cpp
new file mode 100644
index 0000000000..740cd3df59
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/Future.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/client/Future.h"
+#include "qpid/client/SessionImpl.h"
+
+namespace qpid {
+namespace client {
+
+void Future::wait(SessionImpl& session)
+{
+ if (!complete) {
+ session.waitForCompletion(command);
+ }
+ complete = true;
+}
+
+bool Future::isComplete(SessionImpl& session)
+{
+ return complete || session.isComplete(command);
+}
+
+void Future::setFutureResult(boost::shared_ptr<FutureResult> r)
+{
+ result = r;
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/client/Future.h b/qpid/cpp/src/qpid/client/Future.h
new file mode 100644
index 0000000000..630a7e03c0
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/Future.h
@@ -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.
+ *
+ */
+
+#ifndef _Future_
+#define _Future_
+
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+#include "qpid/Exception.h"
+#include "qpid/framing/SequenceNumber.h"
+#include "qpid/client/FutureCompletion.h"
+#include "qpid/client/FutureResult.h"
+#include "qpid/client/ClientImportExport.h"
+
+namespace qpid {
+namespace client {
+
+/**@internal */
+class QPID_CLIENT_CLASS_EXTERN Future
+{
+ framing::SequenceNumber command;
+ boost::shared_ptr<FutureResult> result;
+ bool complete;
+
+public:
+ Future() : complete(false) {}
+ Future(const framing::SequenceNumber& id) : command(id), complete(false) {}
+
+ std::string getResult(SessionImpl& session) {
+ if (result) return result->getResult(session);
+ else throw Exception("Result not expected");
+ }
+
+ QPID_CLIENT_EXTERN void wait(SessionImpl& session);
+ QPID_CLIENT_EXTERN bool isComplete(SessionImpl& session);
+ QPID_CLIENT_EXTERN void setFutureResult(boost::shared_ptr<FutureResult> r);
+};
+
+}}
+
+#endif
diff --git a/qpid/cpp/src/qpid/client/FutureCompletion.cpp b/qpid/cpp/src/qpid/client/FutureCompletion.cpp
new file mode 100644
index 0000000000..ccfb073855
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/FutureCompletion.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/client/FutureCompletion.h"
+
+using namespace qpid::client;
+using namespace qpid::sys;
+
+FutureCompletion::FutureCompletion() : complete(false) {}
+
+bool FutureCompletion::isComplete() const
+{
+ Monitor::ScopedLock l(lock);
+ return complete;
+}
+
+void FutureCompletion::completed()
+{
+ Monitor::ScopedLock l(lock);
+ complete = true;
+ lock.notifyAll();
+}
+
+void FutureCompletion::waitForCompletion() const
+{
+ Monitor::ScopedLock l(lock);
+ while (!complete) {
+ lock.wait();
+ }
+}
diff --git a/qpid/cpp/src/qpid/client/FutureCompletion.h b/qpid/cpp/src/qpid/client/FutureCompletion.h
new file mode 100644
index 0000000000..0970f494b7
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/FutureCompletion.h
@@ -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.
+ *
+ */
+
+#ifndef _FutureCompletion_
+#define _FutureCompletion_
+
+#include "qpid/framing/amqp_framing.h"
+#include "qpid/sys/Monitor.h"
+
+namespace qpid {
+namespace client {
+
+///@internal
+class FutureCompletion
+{
+protected:
+ mutable sys::Monitor lock;
+ bool complete;
+
+public:
+ FutureCompletion();
+ virtual ~FutureCompletion(){}
+ bool isComplete() const;
+ void waitForCompletion() const;
+ void completed();
+};
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/client/FutureResult.cpp b/qpid/cpp/src/qpid/client/FutureResult.cpp
new file mode 100644
index 0000000000..0237eb1464
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/FutureResult.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/client/FutureResult.h"
+
+#include "qpid/client/SessionImpl.h"
+
+using namespace qpid::client;
+using namespace qpid::framing;
+using namespace qpid::sys;
+
+const std::string& FutureResult::getResult(SessionImpl& session) const
+{
+ waitForCompletion();
+ session.assertOpen();
+ return result;
+}
+
+void FutureResult::received(const std::string& r)
+{
+ Monitor::ScopedLock l(lock);
+ result = r;
+ complete = true;
+ lock.notifyAll();
+}
diff --git a/qpid/cpp/src/qpid/client/FutureResult.h b/qpid/cpp/src/qpid/client/FutureResult.h
new file mode 100644
index 0000000000..ead4929571
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/FutureResult.h
@@ -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.
+ *
+ */
+
+#ifndef _FutureResult_
+#define _FutureResult_
+
+#include <string>
+
+#include "qpid/client/ClientImportExport.h"
+#include "qpid/framing/amqp_framing.h"
+#include "qpid/client/FutureCompletion.h"
+
+namespace qpid {
+namespace client {
+
+class SessionImpl;
+
+///@internal
+class QPID_CLIENT_CLASS_EXTERN FutureResult : public FutureCompletion
+{
+ std::string result;
+public:
+ QPID_CLIENT_EXTERN const std::string& getResult(SessionImpl& session) const;
+ void received(const std::string& result);
+};
+
+}}
+
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/client/Handle.h b/qpid/cpp/src/qpid/client/Handle.h
new file mode 100644
index 0000000000..859dca4029
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/Handle.h
@@ -0,0 +1,72 @@
+#ifndef QPID_CLIENT_HANDLE_H
+#define QPID_CLIENT_HANDLE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/client/ClientImportExport.h"
+
+namespace qpid {
+namespace client {
+
+template <class> class PrivateImplRef;
+
+/**
+ * A handle is like a pointer: refers to an underlying implementation object.
+ * Copying the handle does not copy the object.
+ *
+ * Handles can be null, like a 0 pointer. Use isValid(), isNull() or the
+ * conversion to bool to test for a null handle.
+ */
+template <class T> class Handle {
+ public:
+
+ /**@return true if handle is valid, i.e. not null. */
+ QPID_CLIENT_INLINE_EXTERN bool isValid() const { return impl; }
+
+ /**@return true if handle is null. It is an error to call any function on a null handle. */
+ QPID_CLIENT_INLINE_EXTERN bool isNull() const { return !impl; }
+
+ /** Conversion to bool supports idiom if (handle) { handle->... } */
+ QPID_CLIENT_INLINE_EXTERN operator bool() const { return impl; }
+
+ /** Operator ! supports idiom if (!handle) { do_if_handle_is_null(); } */
+ QPID_CLIENT_INLINE_EXTERN bool operator !() const { return !impl; }
+
+ void swap(Handle<T>& h) { T* t = h.impl; h.impl = impl; impl = t; }
+
+ private:
+ // Not implemented,subclasses must implement.
+ Handle(const Handle&);
+ Handle& operator=(const Handle&);
+
+ protected:
+ typedef T Impl;
+ QPID_CLIENT_INLINE_EXTERN Handle() :impl() {}
+
+ Impl* impl;
+
+ friend class PrivateImplRef<T>; // FIXME aconway 2009-04-30: Specify
+};
+
+}} // namespace qpid::client
+
+#endif /*!QPID_CLIENT_HANDLE_H*/
diff --git a/qpid/cpp/src/qpid/client/LoadPlugins.cpp b/qpid/cpp/src/qpid/client/LoadPlugins.cpp
new file mode 100644
index 0000000000..65a43c4012
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/LoadPlugins.cpp
@@ -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.
+ *
+ */
+
+#include "LoadPlugins.h"
+
+#include "qpid/Modules.h"
+#include "qpid/sys/Shlib.h"
+
+#include <string>
+#include <vector>
+
+#include "config.h"
+
+using std::vector;
+using std::string;
+
+namespace qpid {
+namespace client {
+
+namespace {
+
+struct LoadtimeInitialise {
+ LoadtimeInitialise() {
+ CommonOptions common("", "", QPIDC_CONF_FILE);
+ qpid::ModuleOptions moduleOptions(QPIDC_MODULE_DIR);
+ string defaultPath (moduleOptions.loadDir);
+ common.parse(0, 0, common.clientConfig, true);
+ moduleOptions.parse (0, 0, common.clientConfig, true);
+
+ for (vector<string>::iterator iter = moduleOptions.load.begin();
+ iter != moduleOptions.load.end();
+ iter++)
+ qpid::tryShlib (*iter);
+
+ if (!moduleOptions.noLoad) {
+ bool isDefault = defaultPath == moduleOptions.loadDir;
+ qpid::loadModuleDir (moduleOptions.loadDir, isDefault);
+ }
+ }
+};
+
+} // namespace
+
+void theModuleLoader() {
+ static LoadtimeInitialise l;
+}
+
+}} // namespace qpid::client
diff --git a/qpid/cpp/src/qpid/client/LoadPlugins.h b/qpid/cpp/src/qpid/client/LoadPlugins.h
new file mode 100644
index 0000000000..0b398f6831
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/LoadPlugins.h
@@ -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.
+ *
+ */
+
+#ifndef _LoadPlugins_
+#define _LoadPlugins_
+
+#include "qpid/client/ClientImportExport.h"
+
+namespace qpid {
+namespace client {
+
+QPID_CLIENT_EXTERN void theModuleLoader();
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/client/LocalQueue.cpp b/qpid/cpp/src/qpid/client/LocalQueue.cpp
new file mode 100644
index 0000000000..0019adabaf
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/LocalQueue.cpp
@@ -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.
+ *
+ */
+#include "qpid/client/LocalQueue.h"
+#include "qpid/client/LocalQueueImpl.h"
+#include "qpid/client/MessageImpl.h"
+#include "qpid/Exception.h"
+#include "qpid/framing/FrameSet.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/client/PrivateImplRef.h"
+#include "qpid/client/SubscriptionImpl.h"
+
+namespace qpid {
+namespace client {
+
+using namespace framing;
+
+typedef PrivateImplRef<LocalQueue> PI;
+
+LocalQueue::LocalQueue() { PI::ctor(*this, new LocalQueueImpl()); }
+LocalQueue::LocalQueue(const LocalQueue& x) : Handle<LocalQueueImpl>() { PI::copy(*this, x); }
+LocalQueue::~LocalQueue() { PI::dtor(*this); }
+LocalQueue& LocalQueue::operator=(const LocalQueue& x) { return PI::assign(*this, x); }
+
+Message LocalQueue::pop(sys::Duration timeout) { return impl->pop(timeout); }
+
+Message LocalQueue::get(sys::Duration timeout) { return impl->get(timeout); }
+
+bool LocalQueue::get(Message& result, sys::Duration timeout) { return impl->get(result, timeout); }
+
+bool LocalQueue::empty() const { return impl->empty(); }
+size_t LocalQueue::size() const { return impl->size(); }
+
+}} // namespace qpid::client
diff --git a/qpid/cpp/src/qpid/client/LocalQueue.h b/qpid/cpp/src/qpid/client/LocalQueue.h
new file mode 100644
index 0000000000..1a19a8499d
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/LocalQueue.h
@@ -0,0 +1,120 @@
+#ifndef QPID_CLIENT_LOCALQUEUE_H
+#define QPID_CLIENT_LOCALQUEUE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/client/ClientImportExport.h"
+#include "qpid/client/Handle.h"
+#include "qpid/client/Message.h"
+#include "qpid/sys/Time.h"
+
+namespace qpid {
+namespace client {
+
+class LocalQueueImpl;
+template <class T> class PrivateImplRef;
+
+/**
+ * A local queue to collect messages retrieved from a remote broker
+ * queue. Create a queue and subscribe it using the SubscriptionManager.
+ * Messages from the remote queue on the broker will be stored in the
+ * local queue until you retrieve them.
+ *
+ * \ingroup clientapi
+ *
+ * \details Using a Local Queue
+ *
+ * <pre>
+ * LocalQueue local_queue;
+ * subscriptions.subscribe(local_queue, string("message_queue"));
+ * for (int i=0; i&lt;10; i++) {
+ * Message message = local_queue.get();
+ * std::cout &lt;&lt; message.getData() &lt;&lt; std::endl;
+ * }
+ * </pre>
+ *
+ * <h2>Getting Messages</h2>
+ *
+ * <ul><li>
+ * <p>get()</p>
+ * <pre>Message message = local_queue.get();</pre>
+ * <pre>// Specifying timeouts (TIME_SEC, TIME_MSEC, TIME_USEC, TIME_NSEC)
+ *#include <qpid/sys/Time.h>
+ *Message message;
+ *local_queue.get(message, 5*sys::TIME_SEC);</pre></li></ul>
+ *
+ * <h2>Checking size</h2>
+ * <ul><li>
+ * <p>empty()</p>
+ * <pre>if (local_queue.empty()) { ... }</pre></li>
+ * <li><p>size()</p>
+ * <pre>std::cout &lt;&lt; local_queue.size();</pre></li>
+ * </ul>
+ */
+
+class QPID_CLIENT_CLASS_EXTERN LocalQueue : public Handle<LocalQueueImpl> {
+ public:
+ /** Create a local queue. Subscribe the local queue to a remote broker
+ * queue with a SubscriptionManager.
+ *
+ * LocalQueue is an alternative to implementing a MessageListener.
+ */
+ QPID_CLIENT_EXTERN LocalQueue();
+ QPID_CLIENT_EXTERN LocalQueue(const LocalQueue&);
+ QPID_CLIENT_EXTERN ~LocalQueue();
+ QPID_CLIENT_EXTERN LocalQueue& operator=(const LocalQueue&);
+
+ /** Wait up to timeout for the next message from the local queue.
+ *@param result Set to the message from the queue.
+ *@param timeout wait up this timeout for a message to appear.
+ *@return true if result was set, false if queue was empty after timeout.
+ */
+ QPID_CLIENT_EXTERN bool get(Message& result, sys::Duration timeout=0);
+
+ /** Get the next message off the local queue, or wait up to the timeout
+ * for message from the broker queue.
+ *@param timeout wait up this timeout for a message to appear.
+ *@return message from the queue.
+ *@throw ClosedException if subscription is closed or timeout exceeded.
+ */
+ QPID_CLIENT_EXTERN Message get(sys::Duration timeout=sys::TIME_INFINITE);
+
+ /** Synonym for get() */
+ QPID_CLIENT_EXTERN Message pop(sys::Duration timeout=sys::TIME_INFINITE);
+
+ /** Return true if local queue is empty. */
+ QPID_CLIENT_EXTERN bool empty() const;
+
+ /** Number of messages on the local queue */
+ QPID_CLIENT_EXTERN size_t size() const;
+
+ LocalQueue(LocalQueueImpl*); ///<@internal
+
+
+ private:
+ typedef LocalQueueImpl Impl;
+ friend class PrivateImplRef<LocalQueue>;
+};
+
+}} // namespace qpid::client
+
+#endif /*!QPID_CLIENT_LOCALQUEUE_H*/
diff --git a/qpid/cpp/src/qpid/client/LocalQueueImpl.cpp b/qpid/cpp/src/qpid/client/LocalQueueImpl.cpp
new file mode 100644
index 0000000000..8b191728f4
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/LocalQueueImpl.cpp
@@ -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.
+ *
+ */
+#include "qpid/client/LocalQueueImpl.h"
+#include "qpid/client/MessageImpl.h"
+#include "qpid/Exception.h"
+#include "qpid/framing/FrameSet.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/client/PrivateImplRef.h"
+#include "qpid/client/SubscriptionImpl.h"
+#include "qpid/client/CompletionImpl.h"
+
+namespace qpid {
+namespace client {
+
+using namespace framing;
+
+Message LocalQueueImpl::pop(sys::Duration timeout) { return get(timeout); }
+
+Message LocalQueueImpl::get(sys::Duration timeout) {
+ Message result;
+ bool ok = get(result, timeout);
+ if (!ok) throw Exception("Timed out waiting for a message");
+ return result;
+}
+
+bool LocalQueueImpl::get(Message& result, sys::Duration timeout) {
+ if (!queue)
+ throw ClosedException();
+ FrameSet::shared_ptr content;
+ bool ok = queue->pop(content, timeout);
+ if (!ok) return false;
+ if (content->isA<MessageTransferBody>()) {
+
+ *MessageImpl::get(result) = MessageImpl(*content);
+ boost::intrusive_ptr<SubscriptionImpl> si = PrivateImplRef<Subscription>::get(subscription);
+ assert(si);
+ if (si) si->received(result);
+ return true;
+ }
+ else
+ throw CommandInvalidException(
+ QPID_MSG("Unexpected method: " << content->getMethod()));
+}
+
+bool LocalQueueImpl::empty() const
+{
+ if (!queue)
+ throw ClosedException();
+ return queue->empty();
+}
+
+size_t LocalQueueImpl::size() const
+{
+ if (!queue)
+ throw ClosedException();
+ return queue->size();
+}
+
+}} // namespace qpid::client
diff --git a/qpid/cpp/src/qpid/client/LocalQueueImpl.h b/qpid/cpp/src/qpid/client/LocalQueueImpl.h
new file mode 100644
index 0000000000..75b62cf203
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/LocalQueueImpl.h
@@ -0,0 +1,108 @@
+#ifndef QPID_CLIENT_LOCALQUEUEIMPL_H
+#define QPID_CLIENT_LOCALQUEUEIMPL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/client/ClientImportExport.h"
+#include "qpid/client/Handle.h"
+#include "qpid/client/Message.h"
+#include "qpid/client/Subscription.h"
+#include "qpid/client/Demux.h"
+#include "qpid/sys/Time.h"
+#include "qpid/RefCounted.h"
+
+namespace qpid {
+namespace client {
+
+/**
+ * A local queue to collect messages retrieved from a remote broker
+ * queue. Create a queue and subscribe it using the SubscriptionManager.
+ * Messages from the remote queue on the broker will be stored in the
+ * local queue until you retrieve them.
+ *
+ * \ingroup clientapi
+ *
+ * \details Using a Local Queue
+ *
+ * <pre>
+ * LocalQueue local_queue;
+ * subscriptions.subscribe(local_queue, string("message_queue"));
+ * for (int i=0; i&lt;10; i++) {
+ * Message message = local_queue.get();
+ * std::cout &lt;&lt; message.getData() &lt;&lt; std::endl;
+ * }
+ * </pre>
+ *
+ * <h2>Getting Messages</h2>
+ *
+ * <ul><li>
+ * <p>get()</p>
+ * <pre>Message message = local_queue.get();</pre>
+ * <pre>// Specifying timeouts (TIME_SEC, TIME_MSEC, TIME_USEC, TIME_NSEC)
+ *#include <qpid/sys/Time.h>
+ *Message message;
+ *local_queue.get(message, 5*sys::TIME_SEC);</pre></li></ul>
+ *
+ * <h2>Checking size</h2>
+ * <ul><li>
+ * <p>empty()</p>
+ * <pre>if (local_queue.empty()) { ... }</pre></li>
+ * <li><p>size()</p>
+ * <pre>std::cout &lt;&lt; local_queue.size();</pre></li>
+ * </ul>
+ */
+
+class LocalQueueImpl : public RefCounted {
+ public:
+ /** Wait up to timeout for the next message from the local queue.
+ *@param result Set to the message from the queue.
+ *@param timeout wait up this timeout for a message to appear.
+ *@return true if result was set, false if queue was empty after timeout.
+ */
+ bool get(Message& result, sys::Duration timeout=0);
+
+ /** Get the next message off the local queue, or wait up to the timeout
+ * for message from the broker queue.
+ *@param timeout wait up this timeout for a message to appear.
+ *@return message from the queue.
+ *@throw ClosedException if subscription is closed or timeout exceeded.
+ */
+ Message get(sys::Duration timeout=sys::TIME_INFINITE);
+
+ /** Synonym for get() */
+ Message pop(sys::Duration timeout=sys::TIME_INFINITE);
+
+ /** Return true if local queue is empty. */
+ bool empty() const;
+
+ /** Number of messages on the local queue */
+ size_t size() const;
+
+ private:
+ Demux::QueuePtr queue;
+ Subscription subscription;
+ friend class SubscriptionManagerImpl;
+};
+
+}} // namespace qpid::client
+
+#endif /*!QPID_CLIENT_LOCALQUEUEIMPL_H*/
diff --git a/qpid/cpp/src/qpid/client/Message.cpp b/qpid/cpp/src/qpid/client/Message.cpp
new file mode 100644
index 0000000000..00f911c57e
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/Message.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/client/Message.h"
+#include "qpid/client/MessageImpl.h"
+
+namespace qpid {
+namespace client {
+
+Message::Message(MessageImpl* mi) : impl(mi) {}
+
+Message::Message(const std::string& data, const std::string& routingKey)
+ : impl(new MessageImpl(data, routingKey)) {}
+
+Message::Message(const Message& m) : impl(new MessageImpl(*m.impl)) {}
+
+Message::~Message() { delete impl; }
+
+Message& Message::operator=(const Message& m) { *impl = *m.impl; return *this; }
+
+void Message::swap(Message& m) { std::swap(impl, m.impl); }
+
+std::string Message::getDestination() const { return impl->getDestination(); }
+bool Message::isRedelivered() const { return impl->isRedelivered(); }
+void Message::setRedelivered(bool redelivered) { impl->setRedelivered(redelivered); }
+framing::FieldTable& Message::getHeaders() { return impl->getHeaders(); }
+const framing::FieldTable& Message::getHeaders() const { return impl->getHeaders(); }
+const framing::SequenceNumber& Message::getId() const { return impl->getId(); }
+
+void Message::setData(const std::string& s) { impl->setData(s); }
+const std::string& Message::getData() const { return impl->getData(); }
+std::string& Message::getData() { return impl->getData(); }
+
+void Message::appendData(const std::string& s) { impl->appendData(s); }
+
+bool Message::hasMessageProperties() const { return impl->hasMessageProperties(); }
+framing::MessageProperties& Message::getMessageProperties() { return impl->getMessageProperties(); }
+const framing::MessageProperties& Message::getMessageProperties() const { return impl->getMessageProperties(); }
+
+bool Message::hasDeliveryProperties() const { return impl->hasDeliveryProperties(); }
+framing::DeliveryProperties& Message::getDeliveryProperties() { return impl->getDeliveryProperties(); }
+const framing::DeliveryProperties& Message::getDeliveryProperties() const { return impl->getDeliveryProperties(); }
+
+}}
diff --git a/qpid/cpp/src/qpid/client/Message.h b/qpid/cpp/src/qpid/client/Message.h
new file mode 100644
index 0000000000..ba50dda9ba
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/Message.h
@@ -0,0 +1,175 @@
+#ifndef QPID_CLIENT_MESSAGE_H
+#define QPID_CLIENT_MESSAGE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/client/ClientImportExport.h"
+#include "qpid/framing/MessageProperties.h"
+#include "qpid/framing/DeliveryProperties.h"
+#include <string>
+
+namespace qpid {
+
+namespace framing {
+class FieldTable;
+class SequenceNumber; // FIXME aconway 2009-04-17: remove with getID?
+}
+
+namespace client {
+
+class MessageImpl;
+
+/**
+ * A message sent to or received from the broker.
+ *
+ * \ingroup clientapi
+ * \details
+ *
+ * <h2>Getting and setting message contents</h2>
+ *
+ * <ul>
+ * <li>
+ * <p>getData()</p>
+ * <pre>std::cout &lt;&lt; "Response: " &lt;&lt; message.getData() &lt;&lt; std::endl;</pre>
+ * </li>
+ * <li>
+ * <p>setData()</p>
+ * <pre>message.setData("That's all, folks!");</pre></li>
+ * <li>
+ * <p>appendData()</p>
+ * <pre>message.appendData(" ... let's add a bit more ...");</pre></li>
+ * </ul>
+ *
+ * <h2>Getting and Setting Delivery Properties</h2>
+ *
+ * <ul>
+ * <li>
+ * <p>getDeliveryProperties()</p>
+ * <pre>message.getDeliveryProperties().setRoutingKey("control");</pre>
+ * <pre>message.getDeliveryProperties().setDeliveryMode(PERSISTENT);</pre>
+ * <pre>message.getDeliveryProperties().setPriority(9);</pre>
+ * <pre>message.getDeliveryProperties().setTtl(100);</pre></li>
+ *
+ * <li>
+ * <p>hasDeliveryProperties()</p>
+ * <pre>if (! message.hasDeliveryProperties()) {
+ * ...
+ *}</pre></li>
+ * </ul>
+ *
+ * <h2>Getting and Setting Message Properties</h2>
+ *
+ * <ul>
+ * <li>
+ * <p>getMessageProperties()</p>
+ * <pre>
+ *request.getMessageProperties().setReplyTo(ReplyTo("amq.direct", response_queue.str()));
+ * </pre>
+ * <pre>
+ *routingKey = request.getMessageProperties().getReplyTo().getRoutingKey();
+ *exchange = request.getMessageProperties().getReplyTo().getExchange();
+ * </pre>
+ * <pre>message.getMessageProperties().setContentType("text/plain");</pre>
+ * <pre>message.getMessageProperties().setContentEncoding("text/plain");</pre>
+ * </li>
+ * <li>
+ * <p>hasMessageProperties()</p>
+ * <pre>request.getMessageProperties().hasReplyTo();</pre>
+ * </li>
+ * </ul>
+ *
+ * <h2>Getting and Setting Application Headers</h2>
+ *
+ * <ul>
+ * <li>
+ * <p>getHeaders()</p>
+ * <pre>
+ *message.getHeaders().getString("control");
+ * </pre>
+ * <pre>
+ *message.getHeaders().setString("control","continue");
+ * </pre></li>
+ * </ul>
+ *
+ *
+ */
+class QPID_CLIENT_CLASS_EXTERN Message
+{
+public:
+ /** Create a Message.
+ *@param data Data for the message body.
+ *@param routingKey Passed to the exchange that routes the message.
+ */
+ QPID_CLIENT_EXTERN Message(
+ const std::string& data=std::string(),
+ const std::string& routingKey=std::string());
+ Message(MessageImpl*); ///< @internal
+ QPID_CLIENT_EXTERN Message(const Message&);
+ QPID_CLIENT_EXTERN ~Message();
+ QPID_CLIENT_EXTERN Message& operator=(const Message&);
+ QPID_CLIENT_EXTERN void swap(Message&);
+
+ QPID_CLIENT_EXTERN void setData(const std::string&);
+ QPID_CLIENT_EXTERN const std::string& getData() const;
+ QPID_CLIENT_EXTERN std::string& getData();
+
+ QPID_CLIENT_EXTERN void appendData(const std::string&);
+
+ QPID_CLIENT_EXTERN bool hasMessageProperties() const;
+ QPID_CLIENT_EXTERN framing::MessageProperties& getMessageProperties();
+ QPID_CLIENT_EXTERN const framing::MessageProperties& getMessageProperties() const;
+
+ QPID_CLIENT_EXTERN bool hasDeliveryProperties() const;
+ QPID_CLIENT_EXTERN framing::DeliveryProperties& getDeliveryProperties();
+ QPID_CLIENT_EXTERN const framing::DeliveryProperties& getDeliveryProperties() const;
+
+
+ /** The destination of messages sent to the broker is the exchange
+ * name. The destination of messages received from the broker is
+ * the delivery tag identifyig the local subscription (often this
+ * is the name of the subscribed queue.)
+ */
+ QPID_CLIENT_EXTERN std::string getDestination() const;
+
+ /** Check the redelivered flag. */
+ QPID_CLIENT_EXTERN bool isRedelivered() const;
+ /** Set the redelivered flag. */
+ QPID_CLIENT_EXTERN void setRedelivered(bool redelivered);
+
+ /** Get a modifyable reference to the message headers. */
+ QPID_CLIENT_EXTERN framing::FieldTable& getHeaders();
+
+ /** Get a non-modifyable reference to the message headers. */
+ QPID_CLIENT_EXTERN const framing::FieldTable& getHeaders() const;
+
+ // FIXME aconway 2009-04-17: does this need to be in public API?
+ ///@internal
+ QPID_CLIENT_EXTERN const framing::SequenceNumber& getId() const;
+
+ private:
+ MessageImpl* impl;
+ friend class MessageImpl; // Helper template for implementation
+};
+
+}}
+
+#endif /*!QPID_CLIENT_MESSAGE_H*/
diff --git a/qpid/cpp/src/qpid/client/MessageImpl.cpp b/qpid/cpp/src/qpid/client/MessageImpl.cpp
new file mode 100644
index 0000000000..865c462b15
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/MessageImpl.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/client/MessageImpl.h"
+
+namespace qpid {
+namespace client {
+
+MessageImpl::MessageImpl(const std::string& data, const std::string& routingKey) : TransferContent(data, routingKey) {}
+
+std::string MessageImpl::getDestination() const
+{
+ return method.getDestination();
+}
+
+bool MessageImpl::isRedelivered() const
+{
+ return hasDeliveryProperties() && getDeliveryProperties().getRedelivered();
+}
+
+void MessageImpl::setRedelivered(bool redelivered)
+{
+ getDeliveryProperties().setRedelivered(redelivered);
+}
+
+framing::FieldTable& MessageImpl::getHeaders()
+{
+ return getMessageProperties().getApplicationHeaders();
+}
+
+const framing::FieldTable& MessageImpl::getHeaders() const
+{
+ return getMessageProperties().getApplicationHeaders();
+}
+
+const framing::MessageTransferBody& MessageImpl::getMethod() const
+{
+ return method;
+}
+
+const framing::SequenceNumber& MessageImpl::getId() const
+{
+ return id;
+}
+
+/**@internal for incoming messages */
+MessageImpl::MessageImpl(const framing::FrameSet& frameset) :
+ method(*frameset.as<framing::MessageTransferBody>()), id(frameset.getId())
+{
+ populate(frameset);
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/client/MessageImpl.h b/qpid/cpp/src/qpid/client/MessageImpl.h
new file mode 100644
index 0000000000..a64ddd20d8
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/MessageImpl.h
@@ -0,0 +1,80 @@
+#ifndef QPID_CLIENT_MESSAGEIMPL_H
+#define QPID_CLIENT_MESSAGEIMPL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/client/Message.h"
+#include "qpid/client/Session.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qpid/framing/TransferContent.h"
+#include <string>
+
+namespace qpid {
+namespace client {
+
+class MessageImpl : public framing::TransferContent
+{
+public:
+ /** Create a Message.
+ *@param data Data for the message body.
+ *@param routingKey Passed to the exchange that routes the message.
+ */
+ MessageImpl(const std::string& data=std::string(),
+ const std::string& routingKey=std::string());
+
+ /** The destination of messages sent to the broker is the exchange
+ * name. The destination of messages received from the broker is
+ * the delivery tag identifyig the local subscription (often this
+ * is the name of the subscribed queue.)
+ */
+ std::string getDestination() const;
+
+ /** Check the redelivered flag. */
+ bool isRedelivered() const;
+ /** Set the redelivered flag. */
+ void setRedelivered(bool redelivered);
+
+ /** Get a modifyable reference to the message headers. */
+ framing::FieldTable& getHeaders();
+
+ /** Get a non-modifyable reference to the message headers. */
+ const framing::FieldTable& getHeaders() const;
+
+ ///@internal
+ const framing::MessageTransferBody& getMethod() const;
+ ///@internal
+ const framing::SequenceNumber& getId() const;
+
+ /**@internal for incoming messages */
+ MessageImpl(const framing::FrameSet& frameset);
+
+ static MessageImpl* get(Message& m) { return m.impl; }
+ static const MessageImpl* get(const Message& m) { return m.impl; }
+
+private:
+ //method and id are only set for received messages:
+ framing::MessageTransferBody method;
+ framing::SequenceNumber id;
+};
+
+}}
+
+#endif /*!QPID_CLIENT_MESSAGEIMPL_H*/
diff --git a/qpid/cpp/src/qpid/client/MessageListener.cpp b/qpid/cpp/src/qpid/client/MessageListener.cpp
new file mode 100644
index 0000000000..0f2a71287c
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/MessageListener.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/client/MessageListener.h"
+
+qpid::client::MessageListener::~MessageListener() {}
diff --git a/qpid/cpp/src/qpid/client/MessageListener.h b/qpid/cpp/src/qpid/client/MessageListener.h
new file mode 100644
index 0000000000..3ca2fa964a
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/MessageListener.h
@@ -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.
+ *
+ */
+#include <string>
+#include "qpid/client/ClientImportExport.h"
+
+#ifndef _MessageListener_
+#define _MessageListener_
+
+#include "qpid/client/Message.h"
+
+namespace qpid {
+namespace client {
+
+ /**
+ * Implement a subclass of MessageListener and subscribe it using
+ * the SubscriptionManager to receive messages.
+ *
+ * Another way to receive messages is by using a LocalQueue.
+ *
+ * \ingroup clientapi
+ * \details
+ *
+ * <h2>Using a MessageListener</h2>
+ *
+ * <ul>
+ * <li>
+ * <p>The received() function is called when a message arrives:</p>
+ * <pre>virtual void received(Message&amp; message)=0;</pre>
+ * </li>
+ * <li>
+ * <p>Derive your own listener, implement the received() function:</p>
+ * <pre>
+ * class Listener : public MessageListener {
+ * private:
+ * SubscriptionManager&amp; subscriptions;
+ * public:
+ * Listener(SubscriptionManager&amp; subscriptions);
+ * virtual void received(Message&amp; message);
+ * };
+ *
+ * Listener::Listener(SubscriptionManager&amp; subs) : subscriptions(subs)
+ * {}
+ *
+ * void Listener::received(Message&amp; message) {
+ * std::cout &lt;&lt; "Message: " &lt;&lt; message.getData() &lt;&lt; std::endl;
+ * if (message.getData() == "That's all, folks!") {
+ * std::cout &lt;&lt; "Shutting down listener for " &lt;&lt; message.getDestination()
+ * &lt;&lt; std::endl;
+ * subscriptions.cancel(message.getDestination());
+ * }
+ * }
+ *</pre>
+ * <pre>
+ * SubscriptionManager subscriptions(session);
+ *
+ * // Create a listener and subscribe it to the queue named "message_queue"
+ * Listener listener(subscriptions);
+ * subscriptions.subscribe(listener, "message_queue");
+ *
+ * // Receive messages until the subscription is cancelled
+ * // by Listener::received()
+ * subscriptions.run();
+ * </pre>
+ * </li>
+ * </ul>
+ *
+ */
+
+ class QPID_CLIENT_CLASS_EXTERN MessageListener{
+ public:
+ QPID_CLIENT_EXTERN virtual ~MessageListener();
+
+ /** Called for each message arriving from the broker. Override
+ * in your own subclass to process messages.
+ */
+ virtual void received(Message& msg) = 0;
+ };
+
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/client/MessageReplayTracker.cpp b/qpid/cpp/src/qpid/client/MessageReplayTracker.cpp
new file mode 100644
index 0000000000..3afaae74e8
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/MessageReplayTracker.cpp
@@ -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.
+ *
+ */
+#include "qpid/client/MessageReplayTracker.h"
+#include <boost/bind.hpp>
+
+namespace qpid {
+namespace client {
+
+MessageReplayTracker::MessageReplayTracker(uint f) : flushInterval(f), count(0) {}
+
+void MessageReplayTracker::send(const Message& message, const std::string& destination)
+{
+ buffer.push_back(ReplayRecord(message, destination));
+ buffer.back().send(*this);
+ if (flushInterval && (++count % flushInterval == 0)) {
+ checkCompletion();
+ if (!buffer.empty()) session.flush();
+ }
+}
+void MessageReplayTracker::init(AsyncSession s)
+{
+ session = s;
+}
+
+void MessageReplayTracker::replay(AsyncSession s)
+{
+ session = s;
+ std::for_each(buffer.begin(), buffer.end(), boost::bind(&ReplayRecord::send, _1, boost::ref(*this)));
+ session.flush();
+ count = 0;
+}
+
+void MessageReplayTracker::setFlushInterval(uint f)
+{
+ flushInterval = f;
+}
+
+uint MessageReplayTracker::getFlushInterval()
+{
+ return flushInterval;
+}
+
+void MessageReplayTracker::checkCompletion()
+{
+ buffer.remove_if(boost::bind(&ReplayRecord::isComplete, _1));
+}
+
+MessageReplayTracker::ReplayRecord::ReplayRecord(const Message& m, const std::string& d) : message(m), destination(d) {}
+
+void MessageReplayTracker::ReplayRecord::send(MessageReplayTracker& tracker)
+{
+ status = tracker.session.messageTransfer(arg::destination=destination, arg::content=message);
+}
+
+bool MessageReplayTracker::ReplayRecord::isComplete()
+{
+ return status.isComplete();
+}
+
+}} // namespace qpid::client
diff --git a/qpid/cpp/src/qpid/client/MessageReplayTracker.h b/qpid/cpp/src/qpid/client/MessageReplayTracker.h
new file mode 100644
index 0000000000..06a3f29c7d
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/MessageReplayTracker.h
@@ -0,0 +1,73 @@
+#ifndef QPID_CLIENT_MESSAGEREPLAYTRACKER_H
+#define QPID_CLIENT_MESSAGEREPLAYTRACKER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/client/AsyncSession.h"
+#include "qpid/client/Message.h"
+#include "qpid/client/ClientImportExport.h"
+#include <list>
+#include <string>
+
+namespace qpid {
+namespace client {
+
+/**
+ * Utility to track messages sent asynchronously, allowing those that
+ * are indoubt to be replayed over a new session.
+ */
+class QPID_CLIENT_CLASS_EXTERN MessageReplayTracker
+{
+ public:
+ QPID_CLIENT_EXTERN MessageReplayTracker(uint flushInterval);
+ QPID_CLIENT_EXTERN void send(const Message& message, const std::string& destination = "");
+ QPID_CLIENT_EXTERN void init(AsyncSession session);
+ QPID_CLIENT_EXTERN void replay(AsyncSession session);
+ QPID_CLIENT_EXTERN void setFlushInterval(uint interval);
+ QPID_CLIENT_EXTERN uint getFlushInterval();
+ QPID_CLIENT_EXTERN void checkCompletion();
+
+ template <class F> void foreach(F& f) {
+ for (std::list<ReplayRecord>::const_iterator i = buffer.begin(); i != buffer.end(); i++) {
+ f(i->message);
+ }
+ }
+
+ private:
+ struct ReplayRecord
+ {
+ Completion status;
+ Message message;
+ std::string destination;
+
+ ReplayRecord(const Message& message, const std::string& destination);
+ void send(MessageReplayTracker&);
+ bool isComplete();
+ };
+
+ AsyncSession session;
+ uint flushInterval;
+ uint count;
+ std::list<ReplayRecord> buffer;
+};
+}} // namespace qpid::client
+
+#endif /*!QPID_CLIENT_MESSAGEREPLAYTRACKER_H*/
diff --git a/qpid/cpp/src/qpid/client/PrivateImplRef.h b/qpid/cpp/src/qpid/client/PrivateImplRef.h
new file mode 100644
index 0000000000..fa89b1bfa0
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/PrivateImplRef.h
@@ -0,0 +1,94 @@
+#ifndef QPID_CLIENT_PRIVATEIMPL_H
+#define QPID_CLIENT_PRIVATEIMPL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/client/ClientImportExport.h"
+#include <boost/intrusive_ptr.hpp>
+#include "qpid/RefCounted.h"
+
+namespace qpid {
+namespace client {
+
+// FIXME aconway 2009-04-24: details!
+/** @file
+ *
+ * Helper class to implement a class with a private, reference counted
+ * implementation and reference semantics.
+ *
+ * Such classes are used in the public API to hide implementation, they
+ * should. Example of use:
+ *
+ * === Foo.h
+ *
+ * template <class T> PrivateImplRef;
+ * class FooImpl;
+ *
+ * Foo : public Handle<FooImpl> {
+ * public:
+ * Foo(FooImpl* = 0);
+ * Foo(const Foo&);
+ * ~Foo();
+ * Foo& operator=(const Foo&);
+ *
+ * int fooDo(); // and other Foo functions...
+ *
+ * private:
+ * typedef FooImpl Impl;
+ * Impl* impl;
+ * friend class PrivateImplRef<Foo>;
+ *
+ * === Foo.cpp
+ *
+ * typedef PrivateImplRef<Foo> PI;
+ * Foo::Foo(FooImpl* p) { PI::ctor(*this, p); }
+ * Foo::Foo(const Foo& c) : Handle<FooImpl>() { PI::copy(*this, c); }
+ * Foo::~Foo() { PI::dtor(*this); }
+ * Foo& Foo::operator=(const Foo& c) { return PI::assign(*this, c); }
+ *
+ * int foo::fooDo() { return impl->fooDo(); }
+ *
+ */
+template <class T> class PrivateImplRef {
+ public:
+ typedef typename T::Impl Impl;
+ typedef boost::intrusive_ptr<Impl> intrusive_ptr;
+
+ static intrusive_ptr get(const T& t) { return intrusive_ptr(t.impl); }
+
+ static void set(T& t, const intrusive_ptr& p) {
+ if (t.impl == p) return;
+ if (t.impl) intrusive_ptr_release(t.impl);
+ t.impl = p.get();
+ if (t.impl) intrusive_ptr_add_ref(t.impl);
+ }
+
+ // Helper functions to implement the ctor, dtor, copy, assign
+ static void ctor(T& t, Impl* p) { t.impl = p; if (p) intrusive_ptr_add_ref(p); }
+ static void copy(T& t, const T& x) { if (&t == &x) return; t.impl = 0; assign(t, x); }
+ static void dtor(T& t) { if(t.impl) intrusive_ptr_release(t.impl); }
+ static T& assign(T& t, const T& x) { set(t, get(x)); return t;}
+};
+
+}} // namespace qpid::client
+
+#endif /*!QPID_CLIENT_PRIVATEIMPL_H*/
diff --git a/qpid/cpp/src/qpid/client/QueueOptions.cpp b/qpid/cpp/src/qpid/client/QueueOptions.cpp
new file mode 100644
index 0000000000..f7705afeb0
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/QueueOptions.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/client/QueueOptions.h"
+
+namespace qpid {
+namespace client {
+
+enum QueueEventGeneration {ENQUEUE_ONLY=1, ENQUEUE_AND_DEQUEUE=2};
+
+
+QueueOptions::QueueOptions()
+{}
+
+const std::string QueueOptions::strMaxCountKey("qpid.max_count");
+const std::string QueueOptions::strMaxSizeKey("qpid.max_size");
+const std::string QueueOptions::strTypeKey("qpid.policy_type");
+const std::string QueueOptions::strREJECT("reject");
+const std::string QueueOptions::strFLOW_TO_DISK("flow_to_disk");
+const std::string QueueOptions::strRING("ring");
+const std::string QueueOptions::strRING_STRICT("ring_strict");
+const std::string QueueOptions::strLastValueQueue("qpid.last_value_queue");
+const std::string QueueOptions::strLVQMatchProperty("qpid.LVQ_key");
+const std::string QueueOptions::strLastValueQueueNoBrowse("qpid.last_value_queue_no_browse");
+
+QueueOptions::~QueueOptions()
+{}
+
+void QueueOptions::setSizePolicy(QueueSizePolicy sp, uint64_t maxSize, uint32_t maxCount)
+{
+ if (maxCount) setUInt64(strMaxCountKey, maxCount);
+ if (maxSize) setUInt64(strMaxSizeKey, maxSize);
+ if (maxSize || maxCount){
+ switch (sp)
+ {
+ case REJECT:
+ setString(strTypeKey, strREJECT);
+ break;
+ case FLOW_TO_DISK:
+ setString(strTypeKey, strFLOW_TO_DISK);
+ break;
+ case RING:
+ setString(strTypeKey, strRING);
+ break;
+ case RING_STRICT:
+ setString(strTypeKey, strRING_STRICT);
+ break;
+ case NONE:
+ clearSizePolicy();
+ break;
+ }
+ }
+}
+
+
+void QueueOptions::setOrdering(QueueOrderingPolicy op)
+{
+ if (op == LVQ){
+ setInt(strLastValueQueue, 1);
+ }else if (op == LVQ_NO_BROWSE){
+ setInt(strLastValueQueueNoBrowse, 1);
+ }else {
+ clearOrdering();
+ }
+}
+
+void QueueOptions::getLVQKey(std::string& key)
+{
+ key.assign(strLVQMatchProperty);
+}
+
+void QueueOptions::clearSizePolicy()
+{
+ erase(strMaxCountKey);
+ erase(strMaxSizeKey);
+ erase(strTypeKey);
+}
+
+void QueueOptions::clearOrdering()
+{
+ erase(strLastValueQueue);
+}
+
+}
+}
+
+
diff --git a/qpid/cpp/src/qpid/client/QueueOptions.h b/qpid/cpp/src/qpid/client/QueueOptions.h
new file mode 100644
index 0000000000..f7ace15ff9
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/QueueOptions.h
@@ -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.
+ *
+ */
+
+#include "qpid/client/ClientImportExport.h"
+#include "qpid/framing/FieldTable.h"
+
+#ifndef _QueueOptions_
+#define _QueueOptions_
+
+namespace qpid {
+namespace client {
+
+enum QueueSizePolicy {NONE, REJECT, FLOW_TO_DISK, RING, RING_STRICT};
+enum QueueOrderingPolicy {FIFO, LVQ, LVQ_NO_BROWSE};
+
+/**
+ * A help class to set options on the Queue. Create a configured args while
+ * still allowing any custom configuration via the FieldTable base class
+ */
+class QPID_CLIENT_CLASS_EXTERN QueueOptions: public framing::FieldTable
+{
+ public:
+ QPID_CLIENT_EXTERN QueueOptions();
+ QPID_CLIENT_EXTERN virtual ~QueueOptions();
+
+ /**
+ * Sets the queue sizing policy
+ *
+ * @param sp SizePolicy
+ * REJECT - reject if queue greater than size/count
+ * FLOW_TO_DISK - page messages to disk from this point is greater than size/count
+ * RING - limit the queue to size/count and over-write old messages round a ring
+ * RING_STRICT - limit the queue to size/count and reject is head == tail
+ * NONE - Use default broker sizing policy
+ * @param maxSize Set the max number of bytes for the sizing policies
+ * @param setMaxCount Set the max number of messages for the sizing policies
+ */
+ QPID_CLIENT_EXTERN void setSizePolicy(QueueSizePolicy sp, uint64_t maxSize, uint32_t maxCount );
+
+ /**
+ * Sets the odering policy on the Queue, default ordering is FIFO.
+ */
+ QPID_CLIENT_EXTERN void setOrdering(QueueOrderingPolicy op);
+
+ /**
+ * Use broker defualt sizing ploicy
+ */
+ QPID_CLIENT_EXTERN void clearSizePolicy();
+
+ /**
+ * get the key used match LVQ in args for message transfer
+ */
+ QPID_CLIENT_EXTERN void getLVQKey(std::string& key);
+
+ /**
+ * Use default odering policy
+ */
+ QPID_CLIENT_EXTERN void clearOrdering();
+
+ static QPID_CLIENT_EXTERN const std::string strMaxCountKey;
+ static QPID_CLIENT_EXTERN const std::string strMaxSizeKey;
+ static QPID_CLIENT_EXTERN const std::string strTypeKey;
+ static QPID_CLIENT_EXTERN const std::string strREJECT;
+ static QPID_CLIENT_EXTERN const std::string strFLOW_TO_DISK;
+ static QPID_CLIENT_EXTERN const std::string strRING;
+ static QPID_CLIENT_EXTERN const std::string strRING_STRICT;
+ static QPID_CLIENT_EXTERN const std::string strLastValueQueue;
+ static QPID_CLIENT_EXTERN const std::string strLVQMatchProperty;
+ static QPID_CLIENT_EXTERN const std::string strLastValueQueueNoBrowse;
+ static QPID_CLIENT_EXTERN const std::string strQueueEventMode;
+};
+
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/client/RdmaConnector.cpp b/qpid/cpp/src/qpid/client/RdmaConnector.cpp
new file mode 100644
index 0000000000..77762343e2
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/RdmaConnector.cpp
@@ -0,0 +1,420 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/client/Connector.h"
+
+#include "qpid/client/Bounds.h"
+#include "qpid/client/ConnectionImpl.h"
+#include "qpid/client/ConnectionSettings.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/Time.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/InitiationHandler.h"
+#include "qpid/sys/rdma/RdmaIO.h"
+#include "qpid/sys/rdma/rdma_exception.h"
+#include "qpid/sys/Dispatcher.h"
+#include "qpid/sys/Poller.h"
+#include "qpid/sys/SecurityLayer.h"
+#include "qpid/Msg.h"
+
+#include <iostream>
+#include <boost/bind.hpp>
+#include <boost/format.hpp>
+#include <boost/lexical_cast.hpp>
+
+// This stuff needs to abstracted out of here to a platform specific file
+#include <netdb.h>
+
+namespace qpid {
+namespace client {
+
+using namespace qpid::sys;
+using namespace qpid::framing;
+using boost::format;
+using boost::str;
+
+class RdmaConnector : public Connector, public sys::Codec
+{
+ typedef std::deque<framing::AMQFrame> Frames;
+
+ const uint16_t maxFrameSize;
+ sys::Mutex lock;
+ Frames frames;
+ size_t lastEof; // Position after last EOF in frames
+ uint64_t currentSize;
+ Bounds* bounds;
+
+ framing::ProtocolVersion version;
+ bool initiated;
+
+ sys::Mutex dataConnectedLock;
+ bool dataConnected;
+
+ sys::ShutdownHandler* shutdownHandler;
+ framing::InputHandler* input;
+ framing::InitiationHandler* initialiser;
+ framing::FrameHandler* output;
+
+ Rdma::AsynchIO* aio;
+ Rdma::Connector* acon;
+ sys::Poller::shared_ptr poller;
+ std::auto_ptr<qpid::sys::SecurityLayer> securityLayer;
+
+ ~RdmaConnector();
+
+ // Callbacks
+ void connected(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr, const Rdma::ConnectionParams&);
+ void connectionError(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr, Rdma::ErrorType);
+ void disconnected();
+ void rejected(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr, const Rdma::ConnectionParams&);
+
+ void readbuff(Rdma::AsynchIO&, Rdma::Buffer*);
+ void writebuff(Rdma::AsynchIO&);
+ void writeDataBlock(const framing::AMQDataBlock& data);
+ void dataError(Rdma::AsynchIO&);
+ void drained();
+ void connectionStopped(Rdma::Connector* acon, Rdma::AsynchIO* aio);
+ void dataStopped(Rdma::AsynchIO* aio);
+
+ std::string identifier;
+
+ void connect(const std::string& host, const std::string& port);
+ void close();
+ void handle(framing::AMQFrame& frame);
+ void abort() {} // TODO: need to fix this for heartbeat timeouts to work
+
+ void setInputHandler(framing::InputHandler* handler);
+ void setShutdownHandler(sys::ShutdownHandler* handler);
+ const std::string& getIdentifier() const;
+ void activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer>);
+ const qpid::sys::SecuritySettings* getSecuritySettings() { return 0; }
+
+ size_t decode(const char* buffer, size_t size);
+ size_t encode(char* buffer, size_t size);
+ bool canEncode();
+
+public:
+ RdmaConnector(Poller::shared_ptr,
+ framing::ProtocolVersion pVersion,
+ const ConnectionSettings&,
+ ConnectionImpl*);
+};
+
+// Static constructor which registers connector here
+namespace {
+ Connector* create(Poller::shared_ptr p, framing::ProtocolVersion v, const ConnectionSettings& s, ConnectionImpl* c) {
+ return new RdmaConnector(p, v, s, c);
+ }
+
+ struct StaticInit {
+ StaticInit() {
+ Connector::registerFactory("rdma", &create);
+ Connector::registerFactory("ib", &create);
+ };
+ } init;
+}
+
+
+RdmaConnector::RdmaConnector(Poller::shared_ptr p,
+ ProtocolVersion ver,
+ const ConnectionSettings& settings,
+ ConnectionImpl* cimpl)
+ : maxFrameSize(settings.maxFrameSize),
+ lastEof(0),
+ currentSize(0),
+ bounds(cimpl),
+ version(ver),
+ initiated(false),
+ dataConnected(false),
+ shutdownHandler(0),
+ aio(0),
+ acon(0),
+ poller(p)
+{
+ QPID_LOG(debug, "RdmaConnector created for " << version);
+}
+
+namespace {
+ void deleteAsynchIO(Rdma::AsynchIO& aio) {
+ delete &aio;
+ }
+
+ void deleteConnector(Rdma::ConnectionManager& con) {
+ delete &con;
+ }
+}
+
+RdmaConnector::~RdmaConnector() {
+ QPID_LOG(debug, "~RdmaConnector " << identifier);
+ if (aio) {
+ aio->stop(deleteAsynchIO);
+ }
+ if (acon) {
+ acon->stop(deleteConnector);
+ }
+}
+
+void RdmaConnector::connect(const std::string& host, const std::string& port){
+ Mutex::ScopedLock l(dataConnectedLock);
+ assert(!dataConnected);
+
+ acon = new Rdma::Connector(
+ Rdma::ConnectionParams(maxFrameSize, Rdma::DEFAULT_WR_ENTRIES),
+ boost::bind(&RdmaConnector::connected, this, poller, _1, _2),
+ boost::bind(&RdmaConnector::connectionError, this, poller, _1, _2),
+ boost::bind(&RdmaConnector::disconnected, this),
+ boost::bind(&RdmaConnector::rejected, this, poller, _1, _2));
+
+ SocketAddress sa(host, port);
+ acon->start(poller, sa);
+}
+
+// The following only gets run when connected
+void RdmaConnector::connected(Poller::shared_ptr poller, Rdma::Connection::intrusive_ptr ci, const Rdma::ConnectionParams& cp) {
+ try {
+ Mutex::ScopedLock l(dataConnectedLock);
+ assert(!dataConnected);
+ Rdma::QueuePair::intrusive_ptr q = ci->getQueuePair();
+
+ aio = new Rdma::AsynchIO(ci->getQueuePair(),
+ cp.rdmaProtocolVersion,
+ cp.maxRecvBufferSize, cp.initialXmitCredit , Rdma::DEFAULT_WR_ENTRIES,
+ boost::bind(&RdmaConnector::readbuff, this, _1, _2),
+ boost::bind(&RdmaConnector::writebuff, this, _1),
+ 0, // write buffers full
+ boost::bind(&RdmaConnector::dataError, this, _1));
+
+ identifier = str(format("[%1% %2%]") % ci->getLocalName() % ci->getPeerName());
+ ProtocolInitiation init(version);
+ writeDataBlock(init);
+
+ aio->start(poller);
+
+ dataConnected = true;
+
+ return;
+ } catch (const Rdma::Exception& e) {
+ QPID_LOG(error, "Rdma: Cannot create new connection (Rdma exception): " << e.what());
+ } catch (const std::exception& e) {
+ QPID_LOG(error, "Rdma: Cannot create new connection (unknown exception): " << e.what());
+ }
+ dataConnected = false;
+ connectionStopped(acon, aio);
+}
+
+void RdmaConnector::connectionError(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr, Rdma::ErrorType) {
+ QPID_LOG(debug, "Connection Error " << identifier);
+ connectionStopped(acon, aio);
+}
+
+// Bizarrely we seem to get rejected events *after* we've already got a connected event for some peer disconnects
+// so we need to check whether the data connection is started or not in here
+void RdmaConnector::rejected(sys::Poller::shared_ptr, Rdma::Connection::intrusive_ptr, const Rdma::ConnectionParams& cp) {
+ QPID_LOG(debug, "Connection Rejected " << identifier << ": " << cp.maxRecvBufferSize);
+ if (dataConnected) {
+ disconnected();
+ } else {
+ connectionStopped(acon, aio);
+ }
+}
+
+void RdmaConnector::disconnected() {
+ QPID_LOG(debug, "Connection disconnected " << identifier);
+ {
+ Mutex::ScopedLock l(dataConnectedLock);
+ // If we're closed already then we'll get to drained() anyway
+ if (!dataConnected) return;
+ dataConnected = false;
+ }
+ // Make sure that all the disconnected actions take place on the data "thread"
+ aio->requestCallback(boost::bind(&RdmaConnector::drained, this));
+}
+
+void RdmaConnector::dataError(Rdma::AsynchIO&) {
+ QPID_LOG(debug, "Data Error " << identifier);
+ {
+ Mutex::ScopedLock l(dataConnectedLock);
+ // If we're closed already then we'll get to drained() anyway
+ if (!dataConnected) return;
+ dataConnected = false;
+ }
+ drained();
+}
+
+void RdmaConnector::close() {
+ QPID_LOG(debug, "RdmaConnector::close " << identifier);
+ {
+ Mutex::ScopedLock l(dataConnectedLock);
+ if (!dataConnected) return;
+ dataConnected = false;
+ }
+ aio->drainWriteQueue(boost::bind(&RdmaConnector::drained, this));
+}
+
+void RdmaConnector::drained() {
+ QPID_LOG(debug, "RdmaConnector::drained " << identifier);
+ assert(!dataConnected);
+ assert(aio);
+ Rdma::AsynchIO* a = aio;
+ aio = 0;
+ a->stop(boost::bind(&RdmaConnector::dataStopped, this, a));
+}
+
+void RdmaConnector::dataStopped(Rdma::AsynchIO* a) {
+ QPID_LOG(debug, "RdmaConnector::dataStopped " << identifier);
+ assert(!dataConnected);
+ assert(acon);
+ Rdma::Connector* c = acon;
+ acon = 0;
+ c->stop(boost::bind(&RdmaConnector::connectionStopped, this, c, a));
+}
+
+void RdmaConnector::connectionStopped(Rdma::Connector* c, Rdma::AsynchIO* a) {
+ QPID_LOG(debug, "RdmaConnector::connectionStopped " << identifier);
+ assert(!dataConnected);
+ aio = 0;
+ acon = 0;
+ delete a;
+ delete c;
+ if (shutdownHandler) {
+ ShutdownHandler* s = shutdownHandler;
+ shutdownHandler = 0;
+ s->shutdown();
+ }
+}
+
+void RdmaConnector::setInputHandler(InputHandler* handler){
+ input = handler;
+}
+
+void RdmaConnector::setShutdownHandler(ShutdownHandler* handler){
+ shutdownHandler = handler;
+}
+
+const std::string& RdmaConnector::getIdentifier() const {
+ return identifier;
+}
+
+void RdmaConnector::handle(AMQFrame& frame) {
+ // It is possible that we are called to write after we are already shutting down
+ Mutex::ScopedLock l(dataConnectedLock);
+ if (!dataConnected) return;
+
+ bool notifyWrite = false;
+ {
+ Mutex::ScopedLock l(lock);
+ frames.push_back(frame);
+ //only ask to write if this is the end of a frameset or if we
+ //already have a buffers worth of data
+ currentSize += frame.encodedSize();
+ if (frame.getEof()) {
+ lastEof = frames.size();
+ notifyWrite = true;
+ } else {
+ notifyWrite = (currentSize >= maxFrameSize);
+ }
+ }
+ if (notifyWrite) aio->notifyPendingWrite();
+}
+
+// Called in IO thread. (write idle routine)
+// This is NOT only called in response to previously calling notifyPendingWrite
+void RdmaConnector::writebuff(Rdma::AsynchIO&) {
+ // It's possible to be disconnected and be writable
+ Mutex::ScopedLock l(dataConnectedLock);
+ if (!dataConnected) {
+ return;
+ }
+ Codec* codec = securityLayer.get() ? (Codec*) securityLayer.get() : (Codec*) this;
+ if (!codec->canEncode()) {
+ return;
+ }
+ Rdma::Buffer* buffer = aio->getSendBuffer();
+ if (buffer) {
+ size_t encoded = codec->encode(buffer->bytes(), buffer->byteCount());
+ buffer->dataCount(encoded);
+ aio->queueWrite(buffer);
+ }
+}
+
+bool RdmaConnector::canEncode()
+{
+ Mutex::ScopedLock l(lock);
+ //have at least one full frameset or a whole buffers worth of data
+ return aio->writable() && (lastEof || currentSize >= maxFrameSize);
+}
+
+size_t RdmaConnector::encode(char* buffer, size_t size)
+{
+ framing::Buffer out(buffer, size);
+ size_t bytesWritten(0);
+ {
+ Mutex::ScopedLock l(lock);
+ while (!frames.empty() && out.available() >= frames.front().encodedSize() ) {
+ frames.front().encode(out);
+ QPID_LOG(trace, "SENT [" << identifier << "]: " << frames.front());
+ frames.pop_front();
+ if (lastEof) --lastEof;
+ }
+ bytesWritten = size - out.available();
+ currentSize -= bytesWritten;
+ }
+ if (bounds) bounds->reduce(bytesWritten);
+ return bytesWritten;
+}
+
+void RdmaConnector::readbuff(Rdma::AsynchIO&, Rdma::Buffer* buff) {
+ Codec* codec = securityLayer.get() ? (Codec*) securityLayer.get() : (Codec*) this;
+ codec->decode(buff->bytes(), buff->dataCount());
+}
+
+size_t RdmaConnector::decode(const char* buffer, size_t size)
+{
+ framing::Buffer in(const_cast<char*>(buffer), size);
+ try {
+ if (checkProtocolHeader(in, version)) {
+ AMQFrame frame;
+ while(frame.decode(in)){
+ QPID_LOG(trace, "RECV [" << identifier << "]: " << frame);
+ input->received(frame);
+ }
+ }
+ } catch (const ProtocolVersionError& e) {
+ QPID_LOG(info, "Closing connection due to " << e.what());
+ close();
+ }
+ return size - in.available();
+}
+
+void RdmaConnector::writeDataBlock(const AMQDataBlock& data) {
+ Rdma::Buffer* buff = aio->getSendBuffer();
+ framing::Buffer out(buff->bytes(), buff->byteCount());
+ data.encode(out);
+ buff->dataCount(data.encodedSize());
+ aio->queueWrite(buff);
+}
+
+void RdmaConnector::activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer> sl)
+{
+ securityLayer = sl;
+ securityLayer->init(this);
+}
+
+}} // namespace qpid::client
diff --git a/qpid/cpp/src/qpid/client/Results.cpp b/qpid/cpp/src/qpid/client/Results.cpp
new file mode 100644
index 0000000000..0de3e8bd04
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/Results.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/client/Results.h"
+#include "qpid/client/FutureResult.h"
+#include "qpid/framing/SequenceSet.h"
+
+using namespace qpid::framing;
+
+namespace qpid {
+namespace client {
+
+Results::Results() {}
+
+Results::~Results() {
+ try { close(); } catch (const std::exception& /*e*/) { assert(0); }
+}
+
+void Results::close()
+{
+ for (Listeners::iterator i = listeners.begin(); i != listeners.end(); i++) {
+ i->second->completed();
+ }
+ listeners.clear();
+}
+
+void Results::completed(const SequenceSet& set)
+{
+ //call complete on those listeners whose ids fall within the set
+ Listeners::iterator i = listeners.begin();
+ while (i != listeners.end()) {
+ if (set.contains(i->first)) {
+ i->second->completed();
+ listeners.erase(i++);
+ } else {
+ i++;
+ }
+ }
+}
+
+void Results::received(const SequenceNumber& id, const std::string& result)
+{
+ Listeners::iterator i = listeners.find(id);
+ if (i != listeners.end()) {
+ i->second->received(result);
+ listeners.erase(i);
+ }
+}
+
+Results::FutureResultPtr Results::listenForResult(const SequenceNumber& id)
+{
+ FutureResultPtr l(new FutureResult());
+ listeners[id] = l;
+ return l;
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/client/Results.h b/qpid/cpp/src/qpid/client/Results.h
new file mode 100644
index 0000000000..4c49f6b05b
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/Results.h
@@ -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.
+ *
+ */
+
+#include "qpid/framing/SequenceNumber.h"
+#include <map>
+#include <boost/shared_ptr.hpp>
+
+#ifndef _Results_
+#define _Results_
+
+namespace qpid {
+namespace client {
+
+class FutureResult;
+
+///@internal
+class Results
+{
+public:
+ typedef boost::shared_ptr<FutureResult> FutureResultPtr;
+
+ Results();
+ ~Results();
+ void completed(const framing::SequenceSet& set);
+ void received(const framing::SequenceNumber& id, const std::string& result);
+ FutureResultPtr listenForResult(const framing::SequenceNumber& point);
+ void close();
+
+private:
+ typedef std::map<framing::SequenceNumber, FutureResultPtr> Listeners;
+ Listeners listeners;
+};
+
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/client/Session.h b/qpid/cpp/src/qpid/client/Session.h
new file mode 100644
index 0000000000..c40549bbc5
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/Session.h
@@ -0,0 +1,39 @@
+#ifndef QPID_CLIENT_SESSION_H
+#define QPID_CLIENT_SESSION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/client/Session_0_10.h"
+
+namespace qpid {
+namespace client {
+
+/**
+ * Session is an alias for Session_0_10
+ *
+ * \ingroup clientapi
+ */
+typedef Session_0_10 Session;
+
+
+}} // namespace qpid::client
+
+#endif /*!QPID_CLIENT_SESSION_H*/
diff --git a/qpid/cpp/src/qpid/client/SessionBase_0_10.cpp b/qpid/cpp/src/qpid/client/SessionBase_0_10.cpp
new file mode 100644
index 0000000000..2de6c601a1
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/SessionBase_0_10.cpp
@@ -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.
+ *
+ */
+#include "qpid/client/SessionBase_0_10.h"
+#include "qpid/client/Connection.h"
+#include "qpid/client/ConnectionAccess.h"
+#include "qpid/client/SessionImpl.h"
+#include "qpid/client/Future.h"
+#include "qpid/framing/all_method_bodies.h"
+
+namespace qpid {
+namespace client {
+
+using namespace framing;
+
+SessionBase_0_10::SessionBase_0_10() {}
+SessionBase_0_10::~SessionBase_0_10() {}
+
+void SessionBase_0_10::close()
+{
+ if (impl) impl->close();
+}
+
+void SessionBase_0_10::flush()
+{
+ impl->sendFlush();
+}
+
+void SessionBase_0_10::sync()
+{
+ ExecutionSyncBody b;
+ b.setSync(true);
+ impl->send(b).wait(*impl);
+}
+
+void SessionBase_0_10::markCompleted(const framing::SequenceSet& ids, bool notifyPeer)
+{
+ impl->markCompleted(ids, notifyPeer);
+}
+
+void SessionBase_0_10::markCompleted(const framing::SequenceNumber& id, bool cumulative, bool notifyPeer)
+{
+ impl->markCompleted(id, cumulative, notifyPeer);
+}
+
+void SessionBase_0_10::sendCompletion()
+{
+ impl->sendCompletion();
+}
+
+uint16_t SessionBase_0_10::getChannel() const { return impl->getChannel(); }
+
+void SessionBase_0_10::suspend() { impl->suspend(); }
+void SessionBase_0_10::resume(Connection c) { impl->resume(c.impl); }
+uint32_t SessionBase_0_10::timeout(uint32_t seconds) { return impl->setTimeout(seconds); }
+
+SessionId SessionBase_0_10::getId() const { return impl->getId(); }
+
+bool SessionBase_0_10::isValid() const { return !!impl; }
+
+Connection SessionBase_0_10::getConnection()
+{
+ Connection c;
+ ConnectionAccess::setImpl(c, impl->getConnection());
+ return c;
+}
+
+}} // namespace qpid::client
diff --git a/qpid/cpp/src/qpid/client/SessionBase_0_10.h b/qpid/cpp/src/qpid/client/SessionBase_0_10.h
new file mode 100644
index 0000000000..630987c11d
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/SessionBase_0_10.h
@@ -0,0 +1,109 @@
+#ifndef QPID_CLIENT_SESSIONBASE_H
+#define QPID_CLIENT_SESSIONBASE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/SessionId.h"
+#include "qpid/framing/amqp_structs.h"
+#include "qpid/client/Message.h"
+#include "qpid/client/Completion.h"
+#include "qpid/client/TypedResult.h"
+#include "qpid/client/ClientImportExport.h"
+#include <string>
+
+namespace qpid {
+namespace client {
+
+class Connection;
+class SessionImpl;
+
+using qpid::framing::Content;
+using qpid::framing::FieldTable;
+using qpid::framing::SequenceNumber;
+using qpid::framing::SequenceSet;
+using qpid::framing::SequenceNumberSet;
+using qpid::SessionId;
+using qpid::framing::Xid;
+
+/** Unit of message credit: messages or bytes */
+enum CreditUnit { MESSAGE_CREDIT=0, BYTE_CREDIT=1, UNLIMITED_CREDIT=0xFFFFFFFF };
+
+/**
+ * Base class for handles to an AMQP session.
+ *
+ * Subclasses provide the AMQP commands for a given
+ * version of the protocol.
+ */
+class QPID_CLIENT_CLASS_EXTERN SessionBase_0_10 {
+ public:
+
+ ///@internal
+ QPID_CLIENT_EXTERN SessionBase_0_10();
+ QPID_CLIENT_EXTERN ~SessionBase_0_10();
+
+ /** Get the session ID */
+ QPID_CLIENT_EXTERN SessionId getId() const;
+
+ /** Close the session.
+ * A session is automatically closed when all handles to it are destroyed.
+ */
+ QPID_CLIENT_EXTERN void close();
+
+ /**
+ * Synchronize the session: sync() waits until all commands issued
+ * on this session so far have been completed by the broker.
+ *
+ * Note sync() is always synchronous, even on an AsyncSession object
+ * because that's almost always what you want. You can call
+ * AsyncSession::executionSync() directly in the unusual event
+ * that you want to do an asynchronous sync.
+ */
+ QPID_CLIENT_EXTERN void sync();
+
+ /** Set the timeout for this session. */
+ QPID_CLIENT_EXTERN uint32_t timeout(uint32_t seconds);
+
+ /** Suspend the session - detach it from its connection */
+ QPID_CLIENT_EXTERN void suspend();
+
+ /** Resume a suspended session with a new connection */
+ QPID_CLIENT_EXTERN void resume(Connection);
+
+ /** Get the channel associated with this session */
+ QPID_CLIENT_EXTERN uint16_t getChannel() const;
+
+ QPID_CLIENT_EXTERN void flush();
+ QPID_CLIENT_EXTERN void markCompleted(const framing::SequenceSet& ids, bool notifyPeer);
+ QPID_CLIENT_EXTERN void markCompleted(const framing::SequenceNumber& id, bool cumulative, bool notifyPeer);
+ QPID_CLIENT_EXTERN void sendCompletion();
+
+ QPID_CLIENT_EXTERN bool isValid() const;
+
+ QPID_CLIENT_EXTERN Connection getConnection();
+ protected:
+ boost::shared_ptr<SessionImpl> impl;
+ friend class SessionBase_0_10Access;
+};
+
+}} // namespace qpid::client
+
+#endif /*!QPID_CLIENT_SESSIONBASE_H*/
diff --git a/qpid/cpp/src/qpid/client/SessionBase_0_10Access.h b/qpid/cpp/src/qpid/client/SessionBase_0_10Access.h
new file mode 100644
index 0000000000..4d08a7ceaf
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/SessionBase_0_10Access.h
@@ -0,0 +1,42 @@
+#ifndef QPID_CLIENT_SESSIONBASEACCESS_H
+#define QPID_CLIENT_SESSIONBASEACCESS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/client/SessionBase_0_10.h"
+
+/**@file @internal Internal use only */
+
+namespace qpid {
+namespace client {
+
+class SessionBase_0_10Access {
+ public:
+ SessionBase_0_10Access(SessionBase_0_10& sb_) : sb(sb_) {}
+ void set(const boost::shared_ptr<SessionImpl>& si) { sb.impl = si; }
+ boost::shared_ptr<SessionImpl> get() const { return sb.impl; }
+ private:
+ SessionBase_0_10& sb;
+};
+}} // namespace qpid::client
+
+#endif /*!QPID_CLIENT_SESSIONBASEACCESS_H*/
diff --git a/qpid/cpp/src/qpid/client/SessionImpl.cpp b/qpid/cpp/src/qpid/client/SessionImpl.cpp
new file mode 100644
index 0000000000..397067b37a
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/SessionImpl.cpp
@@ -0,0 +1,714 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/client/SessionImpl.h"
+
+#include "qpid/client/ConnectionImpl.h"
+#include "qpid/client/Future.h"
+
+#include "qpid/framing/all_method_bodies.h"
+#include "qpid/framing/ClientInvoker.h"
+#include "qpid/framing/enum.h"
+#include "qpid/framing/FrameSet.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/MethodContent.h"
+#include "qpid/framing/SequenceSet.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/framing/DeliveryProperties.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/IntegerTypes.h"
+
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace { const std::string EMPTY; }
+
+namespace qpid {
+namespace client {
+
+using namespace qpid::framing;
+using namespace qpid::framing::session; //for detach codes
+
+typedef sys::Monitor::ScopedLock Lock;
+typedef sys::Monitor::ScopedUnlock UnLock;
+typedef sys::ScopedLock<sys::Semaphore> Acquire;
+
+
+SessionImpl::SessionImpl(const std::string& name, boost::shared_ptr<ConnectionImpl> conn)
+ : state(INACTIVE),
+ detachedLifetime(0),
+ maxFrameSize(conn->getNegotiatedSettings().maxFrameSize),
+ id(conn->getNegotiatedSettings().username, name.empty() ? Uuid(true).str() : name),
+ connection(conn),
+ ioHandler(*this),
+ proxy(ioHandler),
+ nextIn(0),
+ nextOut(0)
+{
+ channel.next = connection.get();
+}
+
+SessionImpl::~SessionImpl() {
+ {
+ Lock l(state);
+ if (state != DETACHED && state != DETACHING) {
+ QPID_LOG(warning, "Session was not closed cleanly: " << id);
+ // Inform broker but don't wait for detached as that deadlocks.
+ // The detached will be ignored as the channel will be invalid.
+ try { detach(); } catch (...) {} // ignore errors.
+ setState(DETACHED);
+ handleClosed();
+ state.waitWaiters();
+ }
+ }
+ connection->erase(channel);
+}
+
+
+FrameSet::shared_ptr SessionImpl::get() // user thread
+{
+ // No lock here: pop does a blocking wait.
+ return demux.getDefault()->pop();
+}
+
+const SessionId SessionImpl::getId() const //user thread
+{
+ return id; //id is immutable
+}
+
+void SessionImpl::open(uint32_t timeout) // user thread
+{
+ Lock l(state);
+ if (state == INACTIVE) {
+ setState(ATTACHING);
+ proxy.attach(id.getName(), false);
+ waitFor(ATTACHED);
+ //TODO: timeout will not be set locally until get response to
+ //confirm, should we wait for that?
+ setTimeout(timeout);
+ proxy.commandPoint(nextOut, 0);
+ } else {
+ throw Exception("Open already called for this session");
+ }
+}
+
+void SessionImpl::close() //user thread
+{
+ Lock l(state);
+ // close() must be idempotent and no-throw as it will often be called in destructors.
+ if (state != DETACHED && state != DETACHING) {
+ try {
+ if (detachedLifetime) setTimeout(0);
+ detach();
+ waitFor(DETACHED);
+ } catch (...) {}
+ setState(DETACHED);
+ }
+}
+
+void SessionImpl::resume(boost::shared_ptr<ConnectionImpl>) // user thread
+{
+ throw NotImplementedException("Resume not yet implemented by client!");
+}
+
+void SessionImpl::suspend() //user thread
+{
+ Lock l(state);
+ detach();
+}
+
+void SessionImpl::detach() //call with lock held
+{
+ if (state == ATTACHED) {
+ setState(DETACHING);
+ proxy.detach(id.getName());
+ }
+}
+
+
+uint16_t SessionImpl::getChannel() const // user thread
+{
+ return channel;
+}
+
+void SessionImpl::setChannel(uint16_t c) // user thread
+{
+ //channel will only ever be set when session is detached (and
+ //about to be resumed)
+ channel = c;
+}
+
+Demux& SessionImpl::getDemux()
+{
+ return demux;
+}
+
+void SessionImpl::waitForCompletion(const SequenceNumber& id)
+{
+ Lock l(state);
+ sys::Waitable::ScopedWait w(state);
+ waitForCompletionImpl(id);
+}
+
+void SessionImpl::waitForCompletionImpl(const SequenceNumber& id) //call with lock held
+{
+ while (incompleteOut.contains(id)) {
+ checkOpen();
+ state.wait();
+ }
+}
+
+bool SessionImpl::isComplete(const SequenceNumber& id)
+{
+ Lock l(state);
+ return !incompleteOut.contains(id);
+}
+
+struct IsCompleteUpTo
+{
+ const SequenceNumber& id;
+ bool result;
+
+ IsCompleteUpTo(const SequenceNumber& _id) : id(_id), result(true) {}
+ void operator()(const SequenceNumber& start, const SequenceNumber&)
+ {
+ if (start <= id) result = false;
+ }
+
+};
+
+bool SessionImpl::isCompleteUpTo(const SequenceNumber& id)
+{
+ Lock l(state);
+ //return false if incompleteOut contains anything less than id,
+ //true otherwise
+ IsCompleteUpTo f(id);
+ incompleteIn.for_each(f);
+ return f.result;
+}
+
+framing::SequenceNumber SessionImpl::getCompleteUpTo()
+{
+ SequenceNumber firstIncomplete;
+ {
+ Lock l(state);
+ firstIncomplete = incompleteIn.front();
+ }
+ return --firstIncomplete;
+}
+
+struct MarkCompleted
+{
+ const SequenceNumber& id;
+ SequenceSet& completedIn;
+
+ MarkCompleted(const SequenceNumber& _id, SequenceSet& set) : id(_id), completedIn(set) {}
+
+ void operator()(const SequenceNumber& start, const SequenceNumber& end)
+ {
+ if (id >= end) {
+ completedIn.add(start, end);
+ } else if (id >= start) {
+ completedIn.add(start, id);
+ }
+ }
+
+};
+
+void SessionImpl::markCompleted(const SequenceSet& ids, bool notifyPeer)
+{
+ Lock l(state);
+ incompleteIn.remove(ids);
+ completedIn.add(ids);
+ if (notifyPeer) {
+ sendCompletion();
+ }
+}
+
+void SessionImpl::markCompleted(const SequenceNumber& id, bool cumulative, bool notifyPeer)
+{
+ Lock l(state);
+ if (cumulative) {
+ //everything in incompleteIn less than or equal to id is now complete
+ MarkCompleted f(id, completedIn);
+ incompleteIn.for_each(f);
+ //make sure id itself is in
+ completedIn.add(id);
+ //then remove anything thats completed from the incomplete set
+ incompleteIn.remove(completedIn);
+ } else if (incompleteIn.contains(id)) {
+ incompleteIn.remove(id);
+ completedIn.add(id);
+ }
+ if (notifyPeer) {
+ sendCompletion();
+ }
+}
+
+void SessionImpl::setException(const sys::ExceptionHolder& ex) {
+ Lock l(state);
+ setExceptionLH(ex);
+}
+
+void SessionImpl::setExceptionLH(const sys::ExceptionHolder& ex) { // Call with lock held.
+ exceptionHolder = ex;
+ setState(DETACHED);
+}
+
+/**
+ * Called by ConnectionImpl to notify active sessions when connection
+ * is explictly closed
+ */
+void SessionImpl::connectionClosed(uint16_t code, const std::string& text) {
+ setException(createConnectionException(code, text));
+ handleClosed();
+}
+
+/**
+ * Called by ConnectionImpl to notify active sessions when connection
+ * is disconnected
+ */
+void SessionImpl::connectionBroke(const std::string& _text) {
+ setException(sys::ExceptionHolder(new TransportFailure(_text)));
+ handleClosed();
+}
+
+Future SessionImpl::send(const AMQBody& command)
+{
+ return sendCommand(command);
+}
+
+Future SessionImpl::send(const AMQBody& command, const MethodContent& content)
+{
+ return sendCommand(command, &content);
+}
+
+namespace {
+// Functor for FrameSet::map to send header + content frames but, not method frames.
+struct SendContentFn {
+ FrameHandler& handler;
+ void operator()(const AMQFrame& f) {
+ if (!f.getMethod())
+ handler(const_cast<AMQFrame&>(f));
+ }
+ SendContentFn(FrameHandler& h) : handler(h) {}
+};
+
+}
+
+Future SessionImpl::send(const AMQBody& command, const FrameSet& content) {
+ Acquire a(sendLock);
+ SequenceNumber id = nextOut++;
+ {
+ Lock l(state);
+ checkOpen();
+ incompleteOut.add(id);
+ }
+ Future f(id);
+ if (command.getMethod()->resultExpected()) {
+ Lock l(state);
+ //result listener must be set before the command is sent
+ f.setFutureResult(results.listenForResult(id));
+ }
+ AMQFrame frame(command);
+ frame.setEof(false);
+ handleOut(frame);
+ SendContentFn send(out);
+ content.map(send);
+ return f;
+}
+
+void SessionImpl::sendRawFrame(AMQFrame& frame) {
+ Acquire a(sendLock);
+ handleOut(frame);
+}
+
+Future SessionImpl::sendCommand(const AMQBody& command, const MethodContent* content)
+{
+ Acquire a(sendLock);
+ SequenceNumber id = nextOut++;
+ {
+ Lock l(state);
+ checkOpen();
+ incompleteOut.add(id);
+ }
+ Future f(id);
+ if (command.getMethod()->resultExpected()) {
+ Lock l(state);
+ //result listener must be set before the command is sent
+ f.setFutureResult(results.listenForResult(id));
+ }
+ AMQFrame frame(command);
+ if (content) {
+ frame.setEof(false);
+ }
+ handleOut(frame);
+ if (content) {
+ sendContent(*content);
+ }
+ return f;
+}
+
+void SessionImpl::sendContent(const MethodContent& content)
+{
+ AMQFrame header(content.getHeader());
+
+ header.setFirstSegment(false);
+ uint64_t data_length = content.getData().length();
+ if(data_length > 0){
+ header.setLastSegment(false);
+ handleOut(header);
+ /*Note: end of frame marker included in overhead but not in size*/
+ const uint32_t frag_size = maxFrameSize - AMQFrame::frameOverhead();
+
+ if(data_length < frag_size){
+ AMQFrame frame((AMQContentBody(content.getData())));
+ frame.setFirstSegment(false);
+ handleOut(frame);
+ }else{
+ uint32_t offset = 0;
+ uint32_t remaining = data_length - offset;
+ while (remaining > 0) {
+ uint32_t length = remaining > frag_size ? frag_size : remaining;
+ std::string frag(content.getData().substr(offset, length));
+ AMQFrame frame((AMQContentBody(frag)));
+ frame.setFirstSegment(false);
+ frame.setLastSegment(true);
+ if (offset > 0) {
+ frame.setFirstFrame(false);
+ }
+ offset += length;
+ remaining = data_length - offset;
+ if (remaining) {
+ frame.setLastFrame(false);
+ }
+ handleOut(frame);
+ }
+ }
+ } else {
+ handleOut(header);
+ }
+}
+
+
+bool isMessageMethod(AMQMethodBody* method)
+{
+ return method->isA<MessageTransferBody>();
+}
+
+bool isMessageMethod(AMQBody* body)
+{
+ AMQMethodBody* method=body->getMethod();
+ return method && isMessageMethod(method);
+}
+
+bool isContentFrame(AMQFrame& frame)
+{
+ AMQBody* body = frame.getBody();
+ uint8_t type = body->type();
+ return type == HEADER_BODY || type == CONTENT_BODY || isMessageMethod(body);
+}
+
+void SessionImpl::handleIn(AMQFrame& frame) // network thread
+{
+ try {
+ if (invoke(static_cast<SessionHandler&>(*this), *frame.getBody())) {
+ ;
+ } else if (invoke(static_cast<ExecutionHandler&>(*this), *frame.getBody())) {
+ //make sure the command id sequence and completion
+ //tracking takes account of execution commands
+ Lock l(state);
+ completedIn.add(nextIn++);
+ } else {
+ //if not handled by this class, its for the application:
+ deliver(frame);
+ }
+ }
+ catch (const SessionException& e) {
+ setException(createSessionException(e.code, e.getMessage()));
+ }
+ catch (const ChannelException& e) {
+ setException(createChannelException(e.code, e.getMessage()));
+ }
+}
+
+void SessionImpl::handleOut(AMQFrame& frame) // user thread
+{
+ sendFrame(frame, true);
+}
+
+void SessionImpl::proxyOut(AMQFrame& frame) // network thread
+{
+ //Note: this case is treated slightly differently that command
+ //frames sent by application; session controls should not be
+ //blocked by bounds checking on the outgoing frame queue.
+ sendFrame(frame, false);
+}
+
+void SessionImpl::sendFrame(AMQFrame& frame, bool canBlock)
+{
+ connection->expand(frame.encodedSize(), canBlock);
+ channel.handle(frame);
+}
+
+void SessionImpl::deliver(AMQFrame& frame) // network thread
+{
+ if (!arriving) {
+ arriving = FrameSet::shared_ptr(new FrameSet(nextIn++));
+ }
+ arriving->append(frame);
+ if (arriving->isComplete()) {
+ //message.transfers will be marked completed only when 'acked'
+ //as completion affects flow control; other commands will be
+ //considered completed as soon as processed here
+ if (arriving->isA<MessageTransferBody>()) {
+ arriving->setReceived();
+ Lock l(state);
+ incompleteIn.add(arriving->getId());
+ } else {
+ Lock l(state);
+ completedIn.add(arriving->getId());
+ }
+ demux.handle(arriving);
+ arriving.reset();
+ }
+}
+
+//control handler methods (called by network thread when controls are
+//received from peer):
+
+void SessionImpl::attach(const std::string& /*name*/, bool /*force*/)
+{
+ throw NotImplementedException("Client does not support attach");
+}
+
+void SessionImpl::attached(const std::string& _name)
+{
+ Lock l(state);
+ if (id.getName() != _name) throw InternalErrorException("Incorrect session name");
+ setState(ATTACHED);
+}
+
+void SessionImpl::detach(const std::string& _name)
+{
+ Lock l(state);
+ if (id.getName() != _name) throw InternalErrorException("Incorrect session name");
+ setState(DETACHED);
+ QPID_LOG(info, "Session detached by peer: " << id);
+ proxy.detached(_name, DETACH_CODE_NORMAL);
+ handleClosed();
+}
+
+void SessionImpl::detached(const std::string& _name, uint8_t _code) {
+ Lock l(state);
+ if (id.getName() != _name) throw InternalErrorException("Incorrect session name");
+ setState(DETACHED);
+ if (_code) {
+ //TODO: make sure this works with execution.exception - don't
+ //want to overwrite the code from that
+ setExceptionLH(createChannelException(_code, "Session detached by peer"));
+ QPID_LOG(error, exceptionHolder.what());
+ }
+ if (detachedLifetime == 0) {
+ handleClosed();
+}
+}
+
+void SessionImpl::requestTimeout(uint32_t t)
+{
+ Lock l(state);
+ detachedLifetime = t;
+ proxy.timeout(t);
+}
+
+void SessionImpl::timeout(uint32_t t)
+{
+ Lock l(state);
+ detachedLifetime = t;
+}
+
+void SessionImpl::commandPoint(const framing::SequenceNumber& id, uint64_t offset)
+{
+ if (offset) throw NotImplementedException("Non-zero byte offset not yet supported for command-point");
+
+ Lock l(state);
+ nextIn = id;
+}
+
+void SessionImpl::expected(const framing::SequenceSet& commands, const framing::Array& fragments)
+{
+ if (!commands.empty() || fragments.encodedSize()) {
+ throw NotImplementedException("Session resumption not yet supported");
+ }
+}
+
+void SessionImpl::confirmed(const framing::SequenceSet& /*commands*/, const framing::Array& /*fragments*/)
+{
+ //don't really care too much about this yet
+}
+
+void SessionImpl::completed(const framing::SequenceSet& commands, bool timelyReply)
+{
+ Lock l(state);
+ incompleteOut.remove(commands);
+ state.notifyAll();//notify any waiters of completion
+ completedOut.add(commands);
+ //notify any waiting results of completion
+ results.completed(commands);
+
+ if (timelyReply) {
+ proxy.knownCompleted(completedOut);
+ completedOut.clear();
+ }
+}
+
+void SessionImpl::knownCompleted(const framing::SequenceSet& commands)
+{
+ Lock l(state);
+ completedIn.remove(commands);
+}
+
+void SessionImpl::flush(bool expected, bool confirmed, bool completed)
+{
+ Lock l(state);
+ if (expected) {
+ proxy.expected(SequenceSet(nextIn), Array());
+ }
+ if (confirmed) {
+ proxy.confirmed(completedIn, Array());
+ }
+ if (completed) {
+ proxy.completed(completedIn, true);
+ }
+}
+
+void SessionImpl::sendCompletion()
+{
+ Lock l(state);
+ sendCompletionImpl();
+}
+
+void SessionImpl::sendFlush()
+{
+ Lock l(state);
+ proxy.flush(false, false, true);
+}
+
+void SessionImpl::sendCompletionImpl()
+{
+ proxy.completed(completedIn, completedIn.span() > 1000);
+}
+
+void SessionImpl::gap(const framing::SequenceSet& /*commands*/)
+{
+ throw NotImplementedException("gap not yet supported");
+}
+
+void SessionImpl::sync() {}
+
+void SessionImpl::result(const framing::SequenceNumber& commandId, const std::string& value)
+{
+ Lock l(state);
+ results.received(commandId, value);
+}
+
+void SessionImpl::exception(uint16_t errorCode,
+ const framing::SequenceNumber& commandId,
+ uint8_t classCode,
+ uint8_t commandCode,
+ uint8_t /*fieldIndex*/,
+ const std::string& description,
+ const framing::FieldTable& /*errorInfo*/)
+{
+ Lock l(state);
+ setExceptionLH(createSessionException(errorCode, description));
+ QPID_LOG(warning, "Exception received from broker: " << exceptionHolder.what()
+ << " [caused by " << commandId << " " << classCode << ":" << commandCode << "]");
+
+ if (detachedLifetime)
+ setTimeout(0);
+}
+
+//private utility methods:
+
+inline void SessionImpl::setState(State s) //call with lock held
+{
+ state = s;
+}
+
+inline void SessionImpl::waitFor(State s) //call with lock held
+{
+ // We can be DETACHED at any time
+ if (s == DETACHED) state.waitFor(DETACHED);
+ else state.waitFor(States(s, DETACHED));
+ check();
+}
+
+void SessionImpl::check() const //call with lock held.
+{
+ exceptionHolder.raise();
+}
+
+void SessionImpl::checkOpen() const //call with lock held.
+{
+ check();
+ if (state != ATTACHED) {
+ throw NotAttachedException(QPID_MSG("Session " << getId() << " isn't attached"));
+ }
+}
+
+void SessionImpl::assertOpen() const
+{
+ Lock l(state);
+ checkOpen();
+}
+
+bool SessionImpl::hasError() const
+{
+ Lock l(state);
+ return !exceptionHolder.empty();
+}
+
+void SessionImpl::handleClosed()
+{
+ demux.close(exceptionHolder.empty() ?
+ sys::ExceptionHolder(new ClosedException()) : exceptionHolder);
+ results.close();
+}
+
+uint32_t SessionImpl::setTimeout(uint32_t seconds) {
+ proxy.requestTimeout(seconds);
+ // FIXME aconway 2008-10-07: wait for timeout response from broker
+ // and use value retured by broker.
+ detachedLifetime = seconds;
+ return detachedLifetime;
+}
+
+uint32_t SessionImpl::getTimeout() const {
+ return detachedLifetime;
+}
+
+boost::shared_ptr<ConnectionImpl> SessionImpl::getConnection()
+{
+ return connection;
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/client/SessionImpl.h b/qpid/cpp/src/qpid/client/SessionImpl.h
new file mode 100644
index 0000000000..752d587a39
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/SessionImpl.h
@@ -0,0 +1,220 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#ifndef _SessionImpl_
+#define _SessionImpl_
+
+#include "qpid/client/Demux.h"
+#include "qpid/client/Execution.h"
+#include "qpid/client/Results.h"
+#include "qpid/client/ClientImportExport.h"
+
+#include "qpid/SessionId.h"
+#include "qpid/SessionState.h"
+#include "qpid/framing/FrameHandler.h"
+#include "qpid/framing/ChannelHandler.h"
+#include "qpid/framing/SequenceNumber.h"
+#include "qpid/framing/AMQP_ClientOperations.h"
+#include "qpid/framing/AMQP_ServerProxy.h"
+#include "qpid/sys/Semaphore.h"
+#include "qpid/sys/StateMonitor.h"
+#include "qpid/sys/ExceptionHolder.h"
+
+#include <boost/weak_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/optional.hpp>
+
+namespace qpid {
+
+namespace framing {
+
+class FrameSet;
+class MethodContent;
+class SequenceSet;
+
+}
+
+namespace client {
+
+class Future;
+class ConnectionImpl;
+class SessionHandler;
+
+///@internal
+class SessionImpl : public framing::FrameHandler::InOutHandler,
+ public Execution,
+ private framing::AMQP_ClientOperations::SessionHandler,
+ private framing::AMQP_ClientOperations::ExecutionHandler
+{
+public:
+ SessionImpl(const std::string& name, boost::shared_ptr<ConnectionImpl>);
+ ~SessionImpl();
+
+
+ //NOTE: Public functions called in user thread.
+ framing::FrameSet::shared_ptr get();
+
+ const SessionId getId() const;
+
+ uint16_t getChannel() const;
+ void setChannel(uint16_t channel);
+
+ void open(uint32_t detachedLifetime);
+ void close();
+ void resume(boost::shared_ptr<ConnectionImpl>);
+ void suspend();
+
+ QPID_CLIENT_EXTERN void assertOpen() const;
+ QPID_CLIENT_EXTERN bool hasError() const;
+
+ Future send(const framing::AMQBody& command);
+ Future send(const framing::AMQBody& command, const framing::MethodContent& content);
+ QPID_CLIENT_EXTERN Future send(const framing::AMQBody& command, const framing::FrameSet& content);
+ void sendRawFrame(framing::AMQFrame& frame);
+
+ Demux& getDemux();
+ void markCompleted(const framing::SequenceNumber& id, bool cumulative, bool notifyPeer);
+ void markCompleted(const framing::SequenceSet& ids, bool notifyPeer);
+ bool isComplete(const framing::SequenceNumber& id);
+ bool isCompleteUpTo(const framing::SequenceNumber& id);
+ framing::SequenceNumber getCompleteUpTo();
+ void waitForCompletion(const framing::SequenceNumber& id);
+ void sendCompletion();
+ void sendFlush();
+
+ QPID_CLIENT_EXTERN void setException(const sys::ExceptionHolder&);
+
+ //NOTE: these are called by the network thread when the connection is closed or dies
+ void connectionClosed(uint16_t code, const std::string& text);
+ void connectionBroke(const std::string& text);
+
+ /** Set timeout in seconds, returns actual timeout allowed by broker */
+ uint32_t setTimeout(uint32_t requestedSeconds);
+
+ /** Get timeout in seconds. */
+ uint32_t getTimeout() const;
+
+ /**
+ * get the Connection associated with this connection
+ */
+ boost::shared_ptr<ConnectionImpl> getConnection();
+
+private:
+ enum State {
+ INACTIVE,
+ ATTACHING,
+ ATTACHED,
+ DETACHING,
+ DETACHED
+ };
+ typedef framing::AMQP_ClientOperations::SessionHandler SessionHandler;
+ typedef framing::AMQP_ClientOperations::ExecutionHandler ExecutionHandler;
+ typedef sys::StateMonitor<State, DETACHED> StateMonitor;
+ typedef StateMonitor::Set States;
+
+ inline void setState(State s);
+ inline void waitFor(State);
+
+ void setExceptionLH(const sys::ExceptionHolder&); // LH = lock held when called.
+ void detach();
+
+ void check() const;
+ void checkOpen() const;
+ void handleClosed();
+
+ void handleIn(framing::AMQFrame& frame);
+ void handleOut(framing::AMQFrame& frame);
+ /**
+ * Sends session controls. This case is treated slightly
+ * differently than command frames sent by the application via
+ * handleOut(); session controlsare not subject to bounds checking
+ * on the outgoing frame queue.
+ */
+ void proxyOut(framing::AMQFrame& frame);
+ void sendFrame(framing::AMQFrame& frame, bool canBlock);
+ void deliver(framing::AMQFrame& frame);
+
+ Future sendCommand(const framing::AMQBody&, const framing::MethodContent* = 0);
+ void sendContent(const framing::MethodContent&);
+ void waitForCompletionImpl(const framing::SequenceNumber& id);
+
+ void sendCompletionImpl();
+
+ // Note: Following methods are called by network thread in
+ // response to session controls from the broker
+ void attach(const std::string& name, bool force);
+ void attached(const std::string& name);
+ void detach(const std::string& name);
+ void detached(const std::string& name, uint8_t detachCode);
+ void requestTimeout(uint32_t timeout);
+ void timeout(uint32_t timeout);
+ void commandPoint(const framing::SequenceNumber& commandId, uint64_t commandOffset);
+ void expected(const framing::SequenceSet& commands, const framing::Array& fragments);
+ void confirmed(const framing::SequenceSet& commands, const framing::Array& fragments);
+ void completed(const framing::SequenceSet& commands, bool timelyReply);
+ void knownCompleted(const framing::SequenceSet& commands);
+ void flush(bool expected, bool confirmed, bool completed);
+ void gap(const framing::SequenceSet& commands);
+
+ // Note: Following methods are called by network thread in
+ // response to execution commands from the broker
+ void sync();
+ void result(const framing::SequenceNumber& commandId, const std::string& value);
+ void exception(uint16_t errorCode,
+ const framing::SequenceNumber& commandId,
+ uint8_t classCode,
+ uint8_t commandCode,
+ uint8_t fieldIndex,
+ const std::string& description,
+ const framing::FieldTable& errorInfo);
+
+ sys::ExceptionHolder exceptionHolder;
+ mutable StateMonitor state;
+ mutable sys::Semaphore sendLock;
+ uint32_t detachedLifetime;
+ const uint64_t maxFrameSize;
+ const SessionId id;
+
+ boost::shared_ptr<ConnectionImpl> connection;
+
+ framing::FrameHandler::MemFunRef<SessionImpl, &SessionImpl::proxyOut> ioHandler;
+ framing::ChannelHandler channel;
+ framing::AMQP_ServerProxy::Session proxy;
+
+ Results results;
+ Demux demux;
+ framing::FrameSet::shared_ptr arriving;
+
+ framing::SequenceSet incompleteIn;//incoming commands that are as yet incomplete
+ framing::SequenceSet completedIn;//incoming commands that are have completed
+ framing::SequenceSet incompleteOut;//outgoing commands not yet known to be complete
+ framing::SequenceSet completedOut;//outgoing commands that we know to be completed
+ framing::SequenceNumber nextIn;
+ framing::SequenceNumber nextOut;
+
+ SessionState sessionState;
+
+ friend class client::SessionHandler;
+};
+
+}} // namespace qpid::client
+
+#endif
diff --git a/qpid/cpp/src/qpid/client/SslConnector.cpp b/qpid/cpp/src/qpid/client/SslConnector.cpp
new file mode 100644
index 0000000000..d5d2433060
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/SslConnector.cpp
@@ -0,0 +1,428 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/client/Connector.h"
+
+#include "config.h"
+#include "qpid/client/Bounds.h"
+#include "qpid/client/ConnectionImpl.h"
+#include "qpid/client/ConnectionSettings.h"
+#include "qpid/Options.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/Time.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/InitiationHandler.h"
+#include "qpid/sys/ssl/util.h"
+#include "qpid/sys/AsynchIO.h"
+#include "qpid/sys/ssl/SslSocket.h"
+#include "qpid/sys/SocketAddress.h"
+#include "qpid/sys/Dispatcher.h"
+#include "qpid/sys/Poller.h"
+#include "qpid/sys/SecuritySettings.h"
+#include "qpid/Msg.h"
+#include "qpid/types/Exception.h"
+
+#include <iostream>
+#include <boost/bind.hpp>
+#include <boost/format.hpp>
+
+namespace qpid {
+namespace client {
+
+using namespace qpid::sys;
+using namespace qpid::sys::ssl;
+using namespace qpid::framing;
+using boost::format;
+using boost::str;
+
+
+class SslConnector : public Connector
+{
+ typedef std::deque<framing::AMQFrame> Frames;
+
+ const uint16_t maxFrameSize;
+
+ sys::Mutex lock;
+ Frames frames;
+ size_t lastEof; // Position after last EOF in frames
+ uint64_t currentSize;
+ Bounds* bounds;
+
+ framing::ProtocolVersion version;
+ bool initiated;
+ bool closed;
+
+ sys::ShutdownHandler* shutdownHandler;
+ framing::InputHandler* input;
+
+ sys::ssl::SslSocket socket;
+
+ sys::AsynchConnector* connector;
+ sys::AsynchIO* aio;
+ std::string identifier;
+ Poller::shared_ptr poller;
+ SecuritySettings securitySettings;
+
+ ~SslConnector();
+
+ void readbuff(AsynchIO&, AsynchIOBufferBase*);
+ void writebuff(AsynchIO&);
+ void writeDataBlock(const framing::AMQDataBlock& data);
+ void eof(AsynchIO&);
+ void disconnected(AsynchIO&);
+
+ void connect(const std::string& host, const std::string& port);
+ void connected(const sys::Socket&);
+ void connectFailed(const std::string& msg);
+
+ void close();
+ void handle(framing::AMQFrame& frame);
+ void abort();
+ void connectAborted();
+
+ void setInputHandler(framing::InputHandler* handler);
+ void setShutdownHandler(sys::ShutdownHandler* handler);
+ const std::string& getIdentifier() const;
+ const SecuritySettings* getSecuritySettings();
+ void socketClosed(AsynchIO&, const Socket&);
+
+ size_t decode(const char* buffer, size_t size);
+ size_t encode(char* buffer, size_t size);
+ bool canEncode();
+
+public:
+ SslConnector(Poller::shared_ptr p, framing::ProtocolVersion pVersion,
+ const ConnectionSettings&,
+ ConnectionImpl*);
+};
+
+// Static constructor which registers connector here
+namespace {
+ Connector* create(Poller::shared_ptr p, framing::ProtocolVersion v, const ConnectionSettings& s, ConnectionImpl* c);
+
+ struct StaticInit {
+ static bool initialised;
+
+ StaticInit() {
+ Connector::registerFactory("ssl", &create);
+ };
+ ~StaticInit() {
+ if (initialised) shutdownNSS();
+ }
+
+ void checkInitialised() {
+ static qpid::sys::Mutex lock;
+ qpid::sys::Mutex::ScopedLock l(lock);
+ if (!initialised) {
+ CommonOptions common("", "", QPIDC_CONF_FILE);
+ SslOptions options;
+ try {
+ common.parse(0, 0, common.clientConfig, true);
+ options.parse (0, 0, common.clientConfig, true);
+ } catch (const std::exception& e) {
+ throw qpid::types::Exception(QPID_MSG("Failed to parse options while initialising SSL connector: " << e.what()));
+ }
+ if (options.certDbPath.empty()) {
+ throw qpid::types::Exception(QPID_MSG("SSL connector not enabled, you must set QPID_SSL_CERT_DB to enable it."));
+ } else {
+ try {
+ initNSS(options);
+ initialised = true;
+ } catch (const std::exception& e) {
+ throw qpid::types::Exception(QPID_MSG("Failed to initialise SSL: " << e.what()));
+ }
+ }
+ }
+ }
+
+ } init;
+ bool StaticInit::initialised = false;
+
+ Connector* create(Poller::shared_ptr p, framing::ProtocolVersion v, const ConnectionSettings& s, ConnectionImpl* c) {
+ init.checkInitialised();
+ return new SslConnector(p, v, s, c);
+ }
+}
+
+void initialiseSSL()
+{
+ init.checkInitialised();
+}
+
+void shutdownSSL()
+{
+ if (StaticInit::initialised) shutdownNSS();
+}
+
+SslConnector::SslConnector(Poller::shared_ptr p,
+ ProtocolVersion ver,
+ const ConnectionSettings& settings,
+ ConnectionImpl* cimpl)
+ : maxFrameSize(settings.maxFrameSize),
+ lastEof(0),
+ currentSize(0),
+ bounds(cimpl),
+ version(ver),
+ initiated(false),
+ closed(true),
+ shutdownHandler(0),
+ input(0),
+ aio(0),
+ poller(p)
+{
+ QPID_LOG(debug, "SslConnector created for " << version.toString());
+
+ if (settings.sslCertName != "") {
+ QPID_LOG(debug, "ssl-cert-name = " << settings.sslCertName);
+ socket.setCertName(settings.sslCertName);
+ }
+ if (settings.sslIgnoreHostnameVerificationFailure) {
+ socket.ignoreHostnameVerificationFailure();
+ }
+}
+
+SslConnector::~SslConnector() {
+ close();
+}
+
+void SslConnector::connect(const std::string& host, const std::string& port) {
+ Mutex::ScopedLock l(lock);
+ assert(closed);
+ connector = AsynchConnector::create(
+ socket,
+ host, port,
+ boost::bind(&SslConnector::connected, this, _1),
+ boost::bind(&SslConnector::connectFailed, this, _3));
+ closed = false;
+
+ connector->start(poller);
+}
+
+void SslConnector::connected(const Socket&) {
+ connector = 0;
+ aio = AsynchIO::create(socket,
+ boost::bind(&SslConnector::readbuff, this, _1, _2),
+ boost::bind(&SslConnector::eof, this, _1),
+ boost::bind(&SslConnector::disconnected, this, _1),
+ boost::bind(&SslConnector::socketClosed, this, _1, _2),
+ 0, // nobuffs
+ boost::bind(&SslConnector::writebuff, this, _1));
+
+ aio->createBuffers(maxFrameSize);
+ identifier = str(format("[%1%]") % socket.getFullAddress());
+ ProtocolInitiation init(version);
+ writeDataBlock(init);
+ aio->start(poller);
+}
+
+void SslConnector::connectFailed(const std::string& msg) {
+ connector = 0;
+ QPID_LOG(warning, "Connect failed: " << msg);
+ socket.close();
+ if (!closed)
+ closed = true;
+ if (shutdownHandler)
+ shutdownHandler->shutdown();
+}
+
+void SslConnector::close() {
+ Mutex::ScopedLock l(lock);
+ if (!closed) {
+ closed = true;
+ if (aio)
+ aio->queueWriteClose();
+ }
+}
+
+void SslConnector::socketClosed(AsynchIO&, const Socket&) {
+ if (aio)
+ aio->queueForDeletion();
+ if (shutdownHandler)
+ shutdownHandler->shutdown();
+}
+
+void SslConnector::connectAborted() {
+ connector->stop();
+ connectFailed("Connection timedout");
+}
+
+void SslConnector::abort() {
+ // Can't abort a closed connection
+ if (!closed) {
+ if (aio) {
+ // Established connection
+ aio->requestCallback(boost::bind(&SslConnector::eof, this, _1));
+ } else if (connector) {
+ // We're still connecting
+ connector->requestCallback(boost::bind(&SslConnector::connectAborted, this));
+ }
+ }
+}
+
+void SslConnector::setInputHandler(InputHandler* handler){
+ input = handler;
+}
+
+void SslConnector::setShutdownHandler(ShutdownHandler* handler){
+ shutdownHandler = handler;
+}
+
+const std::string& SslConnector::getIdentifier() const {
+ return identifier;
+}
+
+void SslConnector::handle(AMQFrame& frame) {
+ bool notifyWrite = false;
+ {
+ Mutex::ScopedLock l(lock);
+ frames.push_back(frame);
+ //only ask to write if this is the end of a frameset or if we
+ //already have a buffers worth of data
+ currentSize += frame.encodedSize();
+ if (frame.getEof()) {
+ lastEof = frames.size();
+ notifyWrite = true;
+ } else {
+ notifyWrite = (currentSize >= maxFrameSize);
+ }
+ /*
+ NOTE: Moving the following line into this mutex block
+ is a workaround for BZ 570168, in which the test
+ testConcurrentSenders causes a hang about 1.5%
+ of the time. ( To see the hang much more frequently
+ leave this line out of the mutex block, and put a
+ small usleep just before it.)
+
+ TODO mgoulish - fix the underlying cause and then
+ move this call back outside the mutex.
+ */
+ if (notifyWrite && !closed) aio->notifyPendingWrite();
+ }
+}
+
+void SslConnector::writebuff(AsynchIO& /*aio*/)
+{
+ // It's possible to be disconnected and be writable
+ if (closed)
+ return;
+
+ if (!canEncode()) {
+ return;
+ }
+
+ AsynchIOBufferBase* buffer = aio->getQueuedBuffer();
+ if (buffer) {
+
+ size_t encoded = encode(buffer->bytes, buffer->byteCount);
+
+ buffer->dataStart = 0;
+ buffer->dataCount = encoded;
+ aio->queueWrite(buffer);
+ }
+}
+
+// Called in IO thread.
+bool SslConnector::canEncode()
+{
+ Mutex::ScopedLock l(lock);
+ //have at least one full frameset or a whole buffers worth of data
+ return lastEof || currentSize >= maxFrameSize;
+}
+
+// Called in IO thread.
+size_t SslConnector::encode(char* buffer, size_t size)
+{
+ framing::Buffer out(buffer, size);
+ size_t bytesWritten(0);
+ {
+ Mutex::ScopedLock l(lock);
+ while (!frames.empty() && out.available() >= frames.front().encodedSize() ) {
+ frames.front().encode(out);
+ QPID_LOG(trace, "SENT [" << identifier << "]: " << frames.front());
+ frames.pop_front();
+ if (lastEof) --lastEof;
+ }
+ bytesWritten = size - out.available();
+ currentSize -= bytesWritten;
+ }
+ if (bounds) bounds->reduce(bytesWritten);
+ return bytesWritten;
+}
+
+void SslConnector::readbuff(AsynchIO& aio, AsynchIOBufferBase* buff)
+{
+ int32_t decoded = decode(buff->bytes+buff->dataStart, buff->dataCount);
+ // TODO: unreading needs to go away, and when we can cope
+ // with multiple sub-buffers in the general buffer scheme, it will
+ if (decoded < buff->dataCount) {
+ // Adjust buffer for used bytes and then "unread them"
+ buff->dataStart += decoded;
+ buff->dataCount -= decoded;
+ aio.unread(buff);
+ } else {
+ // Give whole buffer back to aio subsystem
+ aio.queueReadBuffer(buff);
+ }
+}
+
+size_t SslConnector::decode(const char* buffer, size_t size)
+{
+ framing::Buffer in(const_cast<char*>(buffer), size);
+ try {
+ if (checkProtocolHeader(in, version)) {
+ AMQFrame frame;
+ while(frame.decode(in)){
+ QPID_LOG(trace, "RECV [" << identifier << "]: " << frame);
+ input->received(frame);
+ }
+ }
+ } catch (const ProtocolVersionError& e) {
+ QPID_LOG(info, "Closing connection due to " << e.what());
+ close();
+ }
+ return size - in.available();
+}
+
+void SslConnector::writeDataBlock(const AMQDataBlock& data) {
+ AsynchIOBufferBase* buff = aio->getQueuedBuffer();
+ assert(buff);
+ framing::Buffer out(buff->bytes, buff->byteCount);
+ data.encode(out);
+ buff->dataCount = data.encodedSize();
+ aio->queueWrite(buff);
+}
+
+void SslConnector::eof(AsynchIO&) {
+ close();
+}
+
+void SslConnector::disconnected(AsynchIO&) {
+ close();
+ socketClosed(*aio, socket);
+}
+
+const SecuritySettings* SslConnector::getSecuritySettings()
+{
+ securitySettings.ssf = socket.getKeyLen();
+ securitySettings.authid = "dummy";//set to non-empty string to enable external authentication
+ return &securitySettings;
+}
+
+}} // namespace qpid::client
diff --git a/qpid/cpp/src/qpid/client/StateManager.cpp b/qpid/cpp/src/qpid/client/StateManager.cpp
new file mode 100644
index 0000000000..839d92abdc
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/StateManager.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/client/StateManager.h"
+#include "qpid/framing/amqp_framing.h"
+
+using namespace qpid::client;
+using namespace qpid::framing;
+using namespace qpid::sys;
+
+StateManager::StateManager(int s) : state(s) {}
+
+void StateManager::waitForStateChange(int current)
+{
+ Monitor::ScopedLock l(stateLock);
+ while (state == current) {
+ stateLock.wait();
+ }
+}
+
+void StateManager::waitFor(int desired)
+{
+ Monitor::ScopedLock l(stateLock);
+ while (state != desired) {
+ stateLock.wait();
+ }
+}
+
+void StateManager::waitFor(std::set<int> desired)
+{
+ Monitor::ScopedLock l(stateLock);
+ while (desired.find(state) == desired.end()) {
+ stateLock.wait();
+ }
+}
+
+bool StateManager::waitFor(int desired, qpid::sys::Duration timeout)
+{
+ AbsTime end(now(), timeout);
+ Monitor::ScopedLock l(stateLock);
+ while (state != desired && now() < end) {
+ stateLock.wait(end);
+ }
+ return state == desired;
+}
+
+bool StateManager::waitFor(std::set<int> desired, qpid::sys::Duration timeout)
+{
+ AbsTime end(now(), timeout);
+ Monitor::ScopedLock l(stateLock);
+ while (desired.find(state) == desired.end() && now() < end) {
+ stateLock.wait(end);
+ }
+ return desired.find(state) != desired.end();
+}
+
+
+void StateManager::setState(int s)
+{
+ Monitor::ScopedLock l(stateLock);
+ state = s;
+ stateLock.notifyAll();
+}
+
+bool StateManager::setState(int s, int expected)
+{
+ Monitor::ScopedLock l(stateLock);
+ if (state == expected) {
+ state = s;
+ stateLock.notifyAll();
+ return true;
+ } else {
+ return false;
+ }
+}
+
+int StateManager::getState() const
+{
+ Monitor::ScopedLock l(stateLock);
+ return state;
+}
+
diff --git a/qpid/cpp/src/qpid/client/StateManager.h b/qpid/cpp/src/qpid/client/StateManager.h
new file mode 100644
index 0000000000..f06dbc493c
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/StateManager.h
@@ -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.
+ *
+ */
+#ifndef _StateManager_
+#define _StateManager_
+
+#include <set>
+#include "qpid/sys/Monitor.h"
+
+namespace qpid {
+namespace client {
+
+///@internal
+class StateManager
+{
+ int state;
+ mutable sys::Monitor stateLock;
+
+public:
+ StateManager(int initial);
+ void setState(int state);
+ bool setState(int state, int expected);
+ int getState() const ;
+ void waitForStateChange(int current);
+ void waitFor(std::set<int> states);
+ void waitFor(int state);
+ bool waitFor(std::set<int> states, qpid::sys::Duration);
+ bool waitFor(int state, qpid::sys::Duration);
+};
+
+}}
+
+#endif
diff --git a/qpid/cpp/src/qpid/client/Subscription.cpp b/qpid/cpp/src/qpid/client/Subscription.cpp
new file mode 100644
index 0000000000..988f372604
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/Subscription.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/client/Subscription.h"
+#include "qpid/client/SubscriptionImpl.h"
+#include "qpid/client/CompletionImpl.h"
+#include "qpid/client/PrivateImplRef.h"
+#include "qpid/framing/enum.h"
+
+namespace qpid {
+namespace client {
+
+typedef PrivateImplRef<Subscription> PI;
+Subscription::Subscription(SubscriptionImpl* p) { PI::ctor(*this, p); }
+Subscription::~Subscription() { PI::dtor(*this); }
+Subscription::Subscription(const Subscription& c) : Handle<SubscriptionImpl>() { PI::copy(*this, c); }
+Subscription& Subscription::operator=(const Subscription& c) { return PI::assign(*this, c); }
+
+
+std::string Subscription::getName() const { return impl->getName(); }
+std::string Subscription::getQueue() const { return impl->getQueue(); }
+const SubscriptionSettings& Subscription::getSettings() const { return impl->getSettings(); }
+void Subscription::setFlowControl(const FlowControl& f) { impl->setFlowControl(f); }
+void Subscription::setAutoAck(unsigned int n) { impl->setAutoAck(n); }
+SequenceSet Subscription::getUnacquired() const { return impl->getUnacquired(); }
+SequenceSet Subscription::getUnaccepted() const { return impl->getUnaccepted(); }
+void Subscription::acquire(const SequenceSet& messageIds) { impl->acquire(messageIds); }
+void Subscription::accept(const SequenceSet& messageIds) { impl->accept(messageIds); }
+void Subscription::release(const SequenceSet& messageIds) { impl->release(messageIds); }
+Session Subscription::getSession() const { return impl->getSession(); }
+SubscriptionManager Subscription::getSubscriptionManager() { return impl->getSubscriptionManager(); }
+void Subscription::cancel() { impl->cancel(); }
+void Subscription::grantMessageCredit(uint32_t value) { impl->grantCredit(framing::message::CREDIT_UNIT_MESSAGE, value); }
+void Subscription::grantByteCredit(uint32_t value) { impl->grantCredit(framing::message::CREDIT_UNIT_BYTE, value); }
+}} // namespace qpid::client
+
+
diff --git a/qpid/cpp/src/qpid/client/Subscription.h b/qpid/cpp/src/qpid/client/Subscription.h
new file mode 100644
index 0000000000..bb9b98e8ff
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/Subscription.h
@@ -0,0 +1,123 @@
+#ifndef QPID_CLIENT_SUBSCRIPTION_H
+#define QPID_CLIENT_SUBSCRIPTION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/client/Handle.h"
+#include "qpid/client/Session.h"
+#include "qpid/client/SubscriptionSettings.h"
+#include "qpid/client/Message.h"
+#include "qpid/client/ClientImportExport.h"
+
+namespace qpid {
+namespace client {
+
+template <class> class PrivateImplRef;
+class SubscriptionImpl;
+class SubscriptionManager;
+
+/**
+ * A handle to an active subscription. Provides methods to query the subscription status
+ * and control acknowledgement (acquire and accept) of messages.
+ */
+class QPID_CLIENT_CLASS_EXTERN Subscription : public Handle<SubscriptionImpl> {
+ public:
+ QPID_CLIENT_EXTERN Subscription(SubscriptionImpl* = 0);
+ QPID_CLIENT_EXTERN Subscription(const Subscription&);
+ QPID_CLIENT_EXTERN ~Subscription();
+ QPID_CLIENT_EXTERN Subscription& operator=(const Subscription&);
+
+
+ /** The name of the subscription, used as the "destination" for messages from the broker.
+ * Usually the same as the queue name but can be set differently.
+ */
+ QPID_CLIENT_EXTERN std::string getName() const;
+
+ /** Name of the queue this subscription subscribes to */
+ QPID_CLIENT_EXTERN std::string getQueue() const;
+
+ /** Get the flow control and acknowledgement settings for this subscription */
+ QPID_CLIENT_EXTERN const SubscriptionSettings& getSettings() const;
+
+ /** Set the flow control parameters */
+ QPID_CLIENT_EXTERN void setFlowControl(const FlowControl&);
+
+ /** Automatically acknowledge (acquire and accept) batches of n messages.
+ * You can disable auto-acknowledgement by setting n=0, and use acquire() and accept()
+ * to manually acquire and accept messages.
+ */
+ QPID_CLIENT_EXTERN void setAutoAck(unsigned int n);
+
+ /** Get the set of ID's for messages received by this subscription but not yet acquired.
+ * This will always be empty if getSettings().acquireMode=ACQUIRE_MODE_PRE_ACQUIRED
+ */
+ QPID_CLIENT_EXTERN SequenceSet getUnacquired() const;
+
+ /** Get the set of ID's for messages received by this subscription but not yet accepted. */
+ QPID_CLIENT_EXTERN SequenceSet getUnaccepted() const;
+
+ /** Acquire messageIds and remove them from the unacquired set.
+ * oAdd them to the unaccepted set if getSettings().acceptMode == ACCEPT_MODE_EXPLICIT.
+ */
+ QPID_CLIENT_EXTERN void acquire(const SequenceSet& messageIds);
+
+ /** Accept messageIds and remove them from the unaccepted set.
+ *@pre messageIds is a subset of getUnaccepted()
+ */
+ QPID_CLIENT_EXTERN void accept(const SequenceSet& messageIds);
+
+ /** Release messageIds and remove them from the unaccepted set.
+ *@pre messageIds is a subset of getUnaccepted()
+ */
+ QPID_CLIENT_EXTERN void release(const SequenceSet& messageIds);
+
+ /* Acquire a single message */
+ QPID_CLIENT_INLINE_EXTERN void acquire(const Message& m) { acquire(SequenceSet(m.getId())); }
+
+ /* Accept a single message */
+ QPID_CLIENT_INLINE_EXTERN void accept(const Message& m) { accept(SequenceSet(m.getId())); }
+
+ /* Release a single message */
+ QPID_CLIENT_INLINE_EXTERN void release(const Message& m) { release(SequenceSet(m.getId())); }
+
+ /** Get the session associated with this subscription */
+ QPID_CLIENT_EXTERN Session getSession() const;
+
+ /** Get the subscription manager associated with this subscription */
+ QPID_CLIENT_EXTERN SubscriptionManager getSubscriptionManager();
+
+ /** Cancel the subscription. */
+ QPID_CLIENT_EXTERN void cancel();
+
+ /** Grant the specified amount of message credit */
+ QPID_CLIENT_EXTERN void grantMessageCredit(uint32_t);
+
+ /** Grant the specified amount of byte credit */
+ QPID_CLIENT_EXTERN void grantByteCredit(uint32_t);
+
+ private:
+ friend class PrivateImplRef<Subscription>;
+ friend class SubscriptionManager;
+};
+}} // namespace qpid::client
+
+#endif /*!QPID_CLIENT_SUBSCRIPTION_H*/
diff --git a/qpid/cpp/src/qpid/client/SubscriptionImpl.cpp b/qpid/cpp/src/qpid/client/SubscriptionImpl.cpp
new file mode 100644
index 0000000000..a8a0b47d94
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/SubscriptionImpl.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/client/AsyncSession.h"
+#include "qpid/client/SubscriptionImpl.h"
+#include "qpid/client/SessionImpl.h"
+#include "qpid/client/SubscriptionManagerImpl.h"
+#include "qpid/client/MessageImpl.h"
+#include "qpid/client/CompletionImpl.h"
+#include "qpid/client/SubscriptionManager.h"
+#include "qpid/client/SubscriptionSettings.h"
+#include "qpid/client/SessionBase_0_10Access.h"
+#include "qpid/client/PrivateImplRef.h"
+
+namespace qpid {
+namespace client {
+
+using sys::Mutex;
+using framing::MessageAcquireResult;
+
+SubscriptionImpl::SubscriptionImpl(SubscriptionManager m, const std::string& q, const SubscriptionSettings& s, const std::string& n, MessageListener* l)
+ : manager(*PrivateImplRef<SubscriptionManager>::get(m)), name(n), queue(q), settings(s), listener(l)
+{}
+
+void SubscriptionImpl::subscribe()
+{
+ async(manager.getSession()).messageSubscribe(
+ arg::queue=queue,
+ arg::destination=name,
+ arg::acceptMode=settings.acceptMode,
+ arg::acquireMode=settings.acquireMode,
+ arg::exclusive=settings.exclusive);
+ setFlowControl(settings.flowControl);
+}
+
+std::string SubscriptionImpl::getName() const { return name; }
+
+std::string SubscriptionImpl::getQueue() const { return queue; }
+
+const SubscriptionSettings& SubscriptionImpl::getSettings() const {
+ Mutex::ScopedLock l(lock);
+ return settings;
+}
+
+void SubscriptionImpl::setFlowControl(const FlowControl& f) {
+ Mutex::ScopedLock l(lock);
+ AsyncSession s=manager.getSession();
+ if (&settings.flowControl != &f) settings.flowControl = f;
+ s.messageSetFlowMode(name, f.window);
+ s.messageFlow(name, CREDIT_UNIT_MESSAGE, f.messages);
+ s.messageFlow(name, CREDIT_UNIT_BYTE, f.bytes);
+ s.sync();
+}
+
+void SubscriptionImpl::grantCredit(framing::message::CreditUnit unit, uint32_t value) {
+ async(manager.getSession()).messageFlow(name, unit, value);
+}
+
+void SubscriptionImpl::setAutoAck(size_t n) {
+ Mutex::ScopedLock l(lock);
+ settings.autoAck = n;
+}
+
+SequenceSet SubscriptionImpl::getUnacquired() const { Mutex::ScopedLock l(lock); return unacquired; }
+SequenceSet SubscriptionImpl::getUnaccepted() const { Mutex::ScopedLock l(lock); return unaccepted; }
+
+void SubscriptionImpl::acquire(const SequenceSet& messageIds) {
+ Mutex::ScopedLock l(lock);
+ MessageAcquireResult result = manager.getSession().messageAcquire(messageIds);
+ unacquired.remove(result.getTransfers());
+ if (settings.acceptMode == ACCEPT_MODE_EXPLICIT)
+ unaccepted.add(result.getTransfers());
+}
+
+void SubscriptionImpl::accept(const SequenceSet& messageIds) {
+ Mutex::ScopedLock l(lock);
+ manager.getSession().messageAccept(messageIds);
+ unaccepted.remove(messageIds);
+ switch (settings.completionMode) {
+ case COMPLETE_ON_ACCEPT:
+ manager.getSession().markCompleted(messageIds, true);
+ break;
+ case COMPLETE_ON_DELIVERY:
+ manager.getSession().sendCompletion();
+ break;
+ default://do nothing
+ break;
+ }
+}
+
+void SubscriptionImpl::release(const SequenceSet& messageIds) {
+ Mutex::ScopedLock l(lock);
+ manager.getSession().messageRelease(messageIds);
+ if (settings.acceptMode == ACCEPT_MODE_EXPLICIT)
+ unaccepted.remove(messageIds);
+}
+
+Session SubscriptionImpl::getSession() const { return manager.getSession(); }
+
+SubscriptionManager SubscriptionImpl::getSubscriptionManager() { return SubscriptionManager(&manager); }
+
+void SubscriptionImpl::cancel() { manager.cancel(name); }
+
+void SubscriptionImpl::received(Message& m) {
+ Mutex::ScopedLock l(lock);
+ MessageImpl& mi = *MessageImpl::get(m);
+ if (mi.getMethod().getAcquireMode() == ACQUIRE_MODE_NOT_ACQUIRED)
+ unacquired.add(m.getId());
+ else if (mi.getMethod().getAcceptMode() == ACCEPT_MODE_EXPLICIT)
+ unaccepted.add(m.getId());
+
+ if (listener) {
+ Mutex::ScopedUnlock u(lock);
+ listener->received(m);
+ }
+
+ if (settings.completionMode == COMPLETE_ON_DELIVERY) {
+ manager.getSession().markCompleted(m.getId(), false, false);
+ }
+ if (settings.autoAck) {
+ if (unaccepted.size() >= settings.autoAck) {
+ async(manager.getSession()).messageAccept(unaccepted);
+ switch (settings.completionMode) {
+ case COMPLETE_ON_ACCEPT:
+ manager.getSession().markCompleted(unaccepted, true);
+ break;
+ case COMPLETE_ON_DELIVERY:
+ manager.getSession().sendCompletion();
+ break;
+ default://do nothing
+ break;
+ }
+ unaccepted.clear();
+ }
+ }
+}
+
+Demux::QueuePtr SubscriptionImpl::divert()
+{
+ Session session(manager.getSession());
+ Demux& demux = SessionBase_0_10Access(session).get()->getDemux();
+ demuxRule = std::auto_ptr<ScopedDivert>(new ScopedDivert(name, demux));
+ return demuxRule->getQueue();
+}
+
+void SubscriptionImpl::cancelDiversion() {
+ demuxRule.reset();
+}
+
+}} // namespace qpid::client
+
diff --git a/qpid/cpp/src/qpid/client/SubscriptionImpl.h b/qpid/cpp/src/qpid/client/SubscriptionImpl.h
new file mode 100644
index 0000000000..da77213423
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/SubscriptionImpl.h
@@ -0,0 +1,125 @@
+#ifndef QPID_CLIENT_SUBSCRIPTIONIMPL_H
+#define QPID_CLIENT_SUBSCRIPTIONIMPL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/client/SubscriptionSettings.h"
+#include "qpid/client/SubscriptionManager.h"
+#include "qpid/client/Session.h"
+#include "qpid/client/MessageListener.h"
+#include "qpid/client/Demux.h"
+#include "qpid/framing/enum.h"
+#include "qpid/framing/SequenceSet.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/RefCounted.h"
+#include "qpid/client/ClientImportExport.h"
+#include <memory>
+
+namespace qpid {
+namespace client {
+
+class SubscriptionManager;
+class SubscriptionManagerImpl;
+
+class SubscriptionImpl : public RefCounted, public MessageListener {
+ public:
+ QPID_CLIENT_EXTERN SubscriptionImpl(SubscriptionManager, const std::string& queue,
+ const SubscriptionSettings&, const std::string& name, MessageListener* =0);
+
+ /** The name of the subsctription, used as the "destination" for messages from the broker.
+ * Usually the same as the queue name but can be set differently.
+ */
+ QPID_CLIENT_EXTERN std::string getName() const;
+
+ /** Name of the queue this subscription subscribes to */
+ QPID_CLIENT_EXTERN std::string getQueue() const;
+
+ /** Get the flow control and acknowledgement settings for this subscription */
+ QPID_CLIENT_EXTERN const SubscriptionSettings& getSettings() const;
+
+ /** Set the flow control parameters */
+ QPID_CLIENT_EXTERN void setFlowControl(const FlowControl&);
+
+ /** Automatically acknowledge (acquire and accept) batches of n messages.
+ * You can disable auto-acknowledgement by setting n=0, and use acquire() and accept()
+ * to manually acquire and accept messages.
+ */
+ QPID_CLIENT_EXTERN void setAutoAck(size_t n);
+
+ /** Get the set of ID's for messages received by this subscription but not yet acquired.
+ * This will always be empty if acquireMode=ACQUIRE_MODE_PRE_ACQUIRED
+ */
+ QPID_CLIENT_EXTERN SequenceSet getUnacquired() const;
+
+ /** Get the set of ID's for messages acquired by this subscription but not yet accepted. */
+ QPID_CLIENT_EXTERN SequenceSet getUnaccepted() const;
+
+ /** Acquire messageIds and remove them from the un-acquired set for the session. */
+ QPID_CLIENT_EXTERN void acquire(const SequenceSet& messageIds);
+
+ /** Accept messageIds and remove them from the un-accepted set for the session. */
+ QPID_CLIENT_EXTERN void accept(const SequenceSet& messageIds);
+
+ /** Release messageIds and remove them from the un-accepted set for the session. */
+ QPID_CLIENT_EXTERN void release(const SequenceSet& messageIds);
+
+ /** Get the session associated with this subscription */
+ QPID_CLIENT_EXTERN Session getSession() const;
+
+ /** Get the subscription manager associated with this subscription */
+ QPID_CLIENT_EXTERN SubscriptionManager getSubscriptionManager();
+
+ /** Send subscription request and issue appropriate flow control commands. */
+ QPID_CLIENT_EXTERN void subscribe();
+
+ /** Cancel the subscription. */
+ QPID_CLIENT_EXTERN void cancel();
+
+ /** Grant specified credit for this subscription **/
+ QPID_CLIENT_EXTERN void grantCredit(framing::message::CreditUnit unit, uint32_t value);
+
+ QPID_CLIENT_EXTERN void received(Message&);
+
+ /**
+ * Set up demux diversion for messages sent to this subscription
+ */
+ Demux::QueuePtr divert();
+ /**
+ * Cancel any demux diversion that may have been setup for this
+ * subscription
+ */
+ QPID_CLIENT_EXTERN void cancelDiversion();
+
+ private:
+
+ mutable sys::Mutex lock;
+ SubscriptionManagerImpl& manager;
+ std::string name, queue;
+ SubscriptionSettings settings;
+ framing::SequenceSet unacquired, unaccepted;
+ MessageListener* listener;
+ std::auto_ptr<ScopedDivert> demuxRule;
+};
+
+}} // namespace qpid::client
+
+#endif /*!QPID_CLIENT_SUBSCRIPTIONIMPL_H*/
diff --git a/qpid/cpp/src/qpid/client/SubscriptionManager.cpp b/qpid/cpp/src/qpid/client/SubscriptionManager.cpp
new file mode 100644
index 0000000000..485361d577
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/SubscriptionManager.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/client/SubscriptionManager.h"
+#include "qpid/client/SubscriptionManagerImpl.h"
+#include "qpid/client/PrivateImplRef.h"
+
+
+namespace qpid {
+namespace client {
+
+typedef PrivateImplRef<SubscriptionManager> PI;
+
+SubscriptionManager::SubscriptionManager(const Session& s) { PI::ctor(*this, new SubscriptionManagerImpl(s)); }
+SubscriptionManager::SubscriptionManager(SubscriptionManagerImpl* i) { PI::ctor(*this, i); }
+SubscriptionManager::SubscriptionManager(const SubscriptionManager& x) : Runnable(), Handle<SubscriptionManagerImpl>() { PI::copy(*this, x); }
+SubscriptionManager::~SubscriptionManager() { PI::dtor(*this); }
+SubscriptionManager& SubscriptionManager::operator=(const SubscriptionManager& x) { return PI::assign(*this, x); }
+
+Subscription SubscriptionManager::subscribe(
+ MessageListener& listener, const std::string& q, const SubscriptionSettings& ss, const std::string& n)
+{ return impl->subscribe(listener, q, ss, n); }
+
+Subscription SubscriptionManager::subscribe(
+ LocalQueue& lq, const std::string& q, const SubscriptionSettings& ss, const std::string& n)
+{ return impl->subscribe(lq, q, ss, n); }
+
+
+Subscription SubscriptionManager::subscribe(
+ MessageListener& listener, const std::string& q, const std::string& n)
+{ return impl->subscribe(listener, q, n); }
+
+
+Subscription SubscriptionManager::subscribe(
+ LocalQueue& lq, const std::string& q, const std::string& n)
+{ return impl->subscribe(lq, q, n); }
+
+void SubscriptionManager::cancel(const std::string& dest) { return impl->cancel(dest); }
+
+void SubscriptionManager::setAutoStop(bool set) { impl->setAutoStop(set); }
+
+void SubscriptionManager::run() { impl->run(); }
+
+void SubscriptionManager::start() { impl->start(); }
+
+void SubscriptionManager::wait() { impl->wait(); }
+
+void SubscriptionManager::stop() { impl->stop(); }
+
+bool SubscriptionManager::get(Message& result, const std::string& queue, sys::Duration timeout) {
+ return impl->get(result, queue, timeout);
+}
+
+Message SubscriptionManager::get(const std::string& queue, sys::Duration timeout) {
+ return impl->get(queue, timeout);
+}
+
+Session SubscriptionManager::getSession() const { return impl->getSession(); }
+
+Subscription SubscriptionManager::getSubscription(const std::string& name) const {
+ return impl->getSubscription(name);
+}
+void SubscriptionManager::registerFailoverHandler (boost::function<void ()> fh) {
+ impl->registerFailoverHandler(fh);
+}
+
+void SubscriptionManager::setFlowControl(const std::string& name, const FlowControl& flow) {
+ impl->setFlowControl(name, flow);
+}
+
+void SubscriptionManager::setDefaultSettings(const SubscriptionSettings& s){
+ impl->setDefaultSettings(s);
+}
+
+void SubscriptionManager::setFlowControl(const std::string& name, uint32_t messages, uint32_t bytes, bool window) {
+ impl->setFlowControl(name, FlowControl(messages, bytes, window));
+}
+
+void SubscriptionManager::setFlowControl(uint32_t messages, uint32_t bytes, bool window) {
+ impl->setFlowControl(messages, bytes, window);
+}
+
+void SubscriptionManager::setAcceptMode(AcceptMode mode) { impl->setAcceptMode(mode); }
+void SubscriptionManager::setAcquireMode(AcquireMode mode) { impl->setAcquireMode(mode); }
+
+}} // namespace qpid::client
+
+
diff --git a/qpid/cpp/src/qpid/client/SubscriptionManager.h b/qpid/cpp/src/qpid/client/SubscriptionManager.h
new file mode 100644
index 0000000000..404a9c6eb9
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/SubscriptionManager.h
@@ -0,0 +1,292 @@
+#ifndef QPID_CLIENT_SUBSCRIPTIONMANAGER_H
+#define QPID_CLIENT_SUBSCRIPTIONMANAGER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/client/Session.h"
+#include "qpid/client/Subscription.h"
+#include "qpid/sys/Runnable.h"
+#include "qpid/sys/Thread.h"
+#include "qpid/client/ClientImportExport.h"
+#include "qpid/client/MessageListener.h"
+#include "qpid/client/LocalQueue.h"
+#include "qpid/client/Handle.h"
+#include <string>
+
+namespace qpid {
+namespace client {
+
+class SubscriptionManagerImpl;
+
+/**
+ * A class to help create and manage subscriptions.
+ *
+ * Set up your subscriptions, then call run() to have messages
+ * delivered.
+ *
+ * \ingroup clientapi
+ *
+ * \details
+ *
+ * <h2>Subscribing and canceling subscriptions</h2>
+ *
+ * <ul>
+ * <li>
+ * <p>subscribe()</p>
+ * <pre> SubscriptionManager subscriptions(session);
+ * Listener listener(subscriptions);
+ * subscriptions.subscribe(listener, myQueue);</pre>
+ * <pre> SubscriptionManager subscriptions(session);
+ * LocalQueue local_queue;
+ * subscriptions.subscribe(local_queue, string("message_queue"));</pre></li>
+ * <li>
+ * <p>cancel()</p>
+ * <pre>subscriptions.cancel();</pre></li>
+ * </ul>
+ *
+ * <h2>Waiting for messages (and returning)</h2>
+ *
+ * <ul>
+ * <li>
+ * <p>run()</p>
+ * <pre> // Give up control to receive messages
+ * subscriptions.run();</pre></li>
+ * <li>
+ * <p>stop()</p>
+ * <pre>.// Use this code in a listener to return from run()
+ * subscriptions.stop();</pre></li>
+ * <li>
+ * <p>setAutoStop()</p>
+ * <pre>.// Return from subscriptions.run() when last subscription is cancelled
+ *.subscriptions.setAutoStop(true);
+ *.subscriptons.run();
+ * </pre></li>
+ * <li>
+ * <p>Ending a subscription in a listener</p>
+ * <pre>
+ * void Listener::received(Message&amp; message) {
+ *
+ * if (message.getData() == "That's all, folks!") {
+ * subscriptions.cancel(message.getDestination());
+ * }
+ * }
+ * </pre>
+ * </li>
+ * </ul>
+ *
+ */
+class QPID_CLIENT_CLASS_EXTERN SubscriptionManager : public sys::Runnable, public Handle<SubscriptionManagerImpl>
+{
+ public:
+ /** Create a new SubscriptionManager associated with a session */
+ QPID_CLIENT_EXTERN SubscriptionManager(const Session& session);
+ QPID_CLIENT_EXTERN SubscriptionManager(const SubscriptionManager&);
+ QPID_CLIENT_EXTERN ~SubscriptionManager();
+ QPID_CLIENT_EXTERN SubscriptionManager& operator=(const SubscriptionManager&);
+
+ /**
+ * Subscribe a MessagesListener to receive messages from queue.
+ *
+ * Provide your own subclass of MessagesListener to process
+ * incoming messages. It will be called for each message received.
+ *
+ *@param listener Listener object to receive messages.
+ *@param queue Name of the queue to subscribe to.
+ *@param settings settings for the subscription.
+ *@param name unique destination name for the subscription, defaults to queue name.
+ */
+ QPID_CLIENT_EXTERN Subscription subscribe(MessageListener& listener,
+ const std::string& queue,
+ const SubscriptionSettings& settings,
+ const std::string& name=std::string());
+
+ /**
+ * Subscribe a LocalQueue to receive messages from queue.
+ *
+ * Incoming messages are stored in the queue for you to retrieve.
+ *
+ *@param queue Name of the queue to subscribe to.
+ *@param flow initial FlowControl for the subscription.
+ *@param name unique destination name for the subscription, defaults to queue name.
+ * If not specified, the queue name is used.
+ */
+ QPID_CLIENT_EXTERN Subscription subscribe(LocalQueue& localQueue,
+ const std::string& queue,
+ const SubscriptionSettings& settings,
+ const std::string& name=std::string());
+
+ /**
+ * Subscribe a MessagesListener to receive messages from queue.
+ *
+ * Provide your own subclass of MessagesListener to process
+ * incoming messages. It will be called for each message received.
+ *
+ *@param listener Listener object to receive messages.
+ *@param queue Name of the queue to subscribe to.
+ *@param name unique destination name for the subscription, defaults to queue name.
+ * If not specified, the queue name is used.
+ */
+ QPID_CLIENT_EXTERN Subscription subscribe(MessageListener& listener,
+ const std::string& queue,
+ const std::string& name=std::string());
+
+ /**
+ * Subscribe a LocalQueue to receive messages from queue.
+ *
+ * Incoming messages are stored in the queue for you to retrieve.
+ *
+ *@param queue Name of the queue to subscribe to.
+ *@param name unique destination name for the subscription, defaults to queue name.
+ * If not specified, the queue name is used.
+ */
+ QPID_CLIENT_EXTERN Subscription subscribe(LocalQueue& localQueue,
+ const std::string& queue,
+ const std::string& name=std::string());
+
+
+ /** Get a single message from a queue.
+ * (Note: this currently uses a subscription per invocation and is
+ * thus relatively expensive. The subscription is cancelled as
+ * part of each call which can trigger auto-deletion).
+ *@param result is set to the message from the queue.
+ *@param timeout wait up this timeout for a message to appear.
+ *@return true if result was set, false if no message available after timeout.
+ */
+ QPID_CLIENT_EXTERN bool get(Message& result, const std::string& queue, sys::Duration timeout=0);
+
+ /** Get a single message from a queue.
+ * (Note: this currently uses a subscription per invocation and is
+ * thus relatively expensive. The subscription is cancelled as
+ * part of each call which can trigger auto-deletion).
+ *@param timeout wait up this timeout for a message to appear.
+ *@return message from the queue.
+ *@throw Exception if the timeout is exceeded.
+ */
+ QPID_CLIENT_EXTERN Message get(const std::string& queue, sys::Duration timeout=sys::TIME_INFINITE);
+
+ /** Get a subscription by name.
+ *@throw Exception if not found.
+ */
+ QPID_CLIENT_EXTERN Subscription getSubscription(const std::string& name) const;
+
+ /** Cancel a subscription. See also: Subscription.cancel() */
+ QPID_CLIENT_EXTERN void cancel(const std::string& name);
+
+ /** Deliver messages in the current thread until stop() is called.
+ * Only one thread may be running in a SubscriptionManager at a time.
+ * @see run
+ */
+ QPID_CLIENT_EXTERN void run();
+
+ /** Start a new thread to deliver messages.
+ * Only one thread may be running in a SubscriptionManager at a time.
+ * @see start
+ */
+ QPID_CLIENT_EXTERN void start();
+
+ /**
+ * Wait for the thread started by a call to start() to complete.
+ */
+ QPID_CLIENT_EXTERN void wait();
+
+ /** If set true, run() will stop when all subscriptions
+ * are cancelled. If false, run will only stop when stop()
+ * is called. True by default.
+ */
+ QPID_CLIENT_EXTERN void setAutoStop(bool set=true);
+
+ /** Stop delivery. Causes run() to return, or the thread started with start() to exit. */
+ QPID_CLIENT_EXTERN void stop();
+
+ static const uint32_t UNLIMITED=0xFFFFFFFF;
+
+ /** Set the flow control for a subscription. */
+ QPID_CLIENT_EXTERN void setFlowControl(const std::string& name, const FlowControl& flow);
+
+ /** Set the flow control for a subscription.
+ *@param name: name of the subscription.
+ *@param messages: message credit.
+ *@param bytes: byte credit.
+ *@param window: if true use window-based flow control.
+ */
+ QPID_CLIENT_EXTERN void setFlowControl(const std::string& name, uint32_t messages, uint32_t bytes, bool window=true);
+
+ /** Set the default settings for subscribe() calls that don't
+ * include a SubscriptionSettings parameter.
+ */
+ QPID_CLIENT_EXTERN void setDefaultSettings(const SubscriptionSettings& s);
+
+ /** Get the default settings for subscribe() calls that don't
+ * include a SubscriptionSettings parameter.
+ */
+ QPID_CLIENT_EXTERN const SubscriptionSettings& getDefaultSettings() const;
+
+ /** Get the default settings for subscribe() calls that don't
+ * include a SubscriptionSettings parameter.
+ */
+ QPID_CLIENT_EXTERN SubscriptionSettings& getDefaultSettings();
+
+ /**
+ * Set the default flow control settings for subscribe() calls
+ * that don't include a SubscriptionSettings parameter.
+ *
+ *@param messages: message credit.
+ *@param bytes: byte credit.
+ *@param window: if true use window-based flow control.
+ */
+ QPID_CLIENT_EXTERN void setFlowControl(uint32_t messages, uint32_t bytes, bool window=true);
+
+ /**
+ *Set the default accept-mode for subscribe() calls that don't
+ *include a SubscriptionSettings parameter.
+ */
+ QPID_CLIENT_EXTERN void setAcceptMode(AcceptMode mode);
+
+ /**
+ * Set the default acquire-mode subscribe()s that don't specify SubscriptionSettings.
+ */
+ QPID_CLIENT_EXTERN void setAcquireMode(AcquireMode mode);
+
+ QPID_CLIENT_EXTERN void registerFailoverHandler ( boost::function<void ()> fh );
+
+ QPID_CLIENT_EXTERN Session getSession() const;
+
+ SubscriptionManager(SubscriptionManagerImpl*); ///<@internal
+
+ private:
+ typedef SubscriptionManagerImpl Impl;
+ friend class PrivateImplRef<SubscriptionManager>;
+};
+
+/** AutoCancel cancels a subscription in its destructor */
+class AutoCancel {
+ public:
+ AutoCancel(SubscriptionManager&, const std::string& tag);
+ ~AutoCancel();
+ private:
+ SubscriptionManager& sm;
+ std::string tag;
+};
+
+}} // namespace qpid::client
+
+#endif /*!QPID_CLIENT_SUBSCRIPTIONMANAGER_H*/
diff --git a/qpid/cpp/src/qpid/client/SubscriptionManagerImpl.cpp b/qpid/cpp/src/qpid/client/SubscriptionManagerImpl.cpp
new file mode 100644
index 0000000000..44f81776c0
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/SubscriptionManagerImpl.cpp
@@ -0,0 +1,182 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/client/SubscriptionManager.h"
+#include "qpid/client/SubscriptionManagerImpl.h"
+#include "qpid/client/SubscriptionImpl.h"
+#include "qpid/client/LocalQueueImpl.h"
+#include "qpid/client/PrivateImplRef.h"
+#include <qpid/client/Dispatcher.h>
+#include <qpid/client/Session.h>
+#include <qpid/client/MessageListener.h>
+#include <qpid/framing/Uuid.h>
+#include <qpid/log/Statement.h>
+#include <set>
+#include <sstream>
+
+
+namespace qpid {
+namespace client {
+
+SubscriptionManagerImpl::SubscriptionManagerImpl(const Session& s)
+ : dispatcher(s), session(s), autoStop(true)
+{}
+
+SubscriptionManagerImpl::~SubscriptionManagerImpl()
+{
+ sys::Mutex::ScopedLock l(lock);
+ for (std::map<std::string, Subscription>::iterator i = subscriptions.begin(); i != subscriptions.end(); ++i) {
+ boost::intrusive_ptr<SubscriptionImpl> s = PrivateImplRef<Subscription>::get(i->second);
+ if (s) s->cancelDiversion();
+ }
+ subscriptions.clear();
+}
+
+Subscription SubscriptionManagerImpl::subscribe(
+ MessageListener& listener, const std::string& q, const SubscriptionSettings& ss, const std::string& n)
+{
+ sys::Mutex::ScopedLock l(lock);
+ std::string name=n.empty() ? q:n;
+ boost::intrusive_ptr<SubscriptionImpl> si = new SubscriptionImpl(SubscriptionManager(this), q, ss, name, &listener);
+ dispatcher.listen(si);
+ //issue subscription request after listener is registered with dispatcher
+ si->subscribe();
+ return subscriptions[name] = Subscription(si.get());
+}
+
+Subscription SubscriptionManagerImpl::subscribe(
+ LocalQueue& lq, const std::string& q, const SubscriptionSettings& ss, const std::string& n)
+{
+ sys::Mutex::ScopedLock l(lock);
+ std::string name=n.empty() ? q:n;
+ boost::intrusive_ptr<SubscriptionImpl> si = new SubscriptionImpl(SubscriptionManager(this), q, ss, name, 0);
+ boost::intrusive_ptr<LocalQueueImpl> lqi = PrivateImplRef<LocalQueue>::get(lq);
+ lqi->queue=si->divert();
+ si->subscribe();
+ lqi->subscription = Subscription(si.get());
+ return subscriptions[name] = lqi->subscription;
+}
+
+Subscription SubscriptionManagerImpl::subscribe(
+ MessageListener& listener, const std::string& q, const std::string& n)
+{
+ return subscribe(listener, q, defaultSettings, n);
+}
+
+Subscription SubscriptionManagerImpl::subscribe(
+ LocalQueue& lq, const std::string& q, const std::string& n)
+{
+ return subscribe(lq, q, defaultSettings, n);
+}
+
+void SubscriptionManagerImpl::cancel(const std::string& dest)
+{
+ sys::Mutex::ScopedLock l(lock);
+ std::map<std::string, Subscription>::iterator i = subscriptions.find(dest);
+ if (i != subscriptions.end()) {
+ sync(session).messageCancel(dest);
+ dispatcher.cancel(dest);
+ Subscription s = i->second;
+ if (s.isValid())
+ PrivateImplRef<Subscription>::get(s)->cancelDiversion();
+ subscriptions.erase(i);
+ }
+}
+
+void SubscriptionManagerImpl::setAutoStop(bool set) { autoStop=set; }
+
+void SubscriptionManagerImpl::run()
+{
+ dispatcher.setAutoStop(autoStop);
+ dispatcher.run();
+}
+
+void SubscriptionManagerImpl::start()
+{
+ dispatcher.setAutoStop(autoStop);
+ dispatcher.start();
+}
+
+void SubscriptionManagerImpl::wait()
+{
+ dispatcher.wait();
+}
+
+void SubscriptionManagerImpl::stop()
+{
+ dispatcher.stop();
+}
+
+bool SubscriptionManagerImpl::get(Message& result, const std::string& queue, sys::Duration timeout) {
+ LocalQueue lq;
+ std::string unique = framing::Uuid(true).str();
+ subscribe(lq, queue, SubscriptionSettings(FlowControl::messageCredit(1)), unique);
+ SubscriptionManager sm(this);
+ AutoCancel ac(sm, unique);
+ //first wait for message to be delivered if a timeout has been specified
+ if (timeout && lq.get(result, timeout))
+ return true;
+ //make sure message is not on queue before final check
+ sync(session).messageFlush(unique);
+ return lq.get(result, 0);
+}
+
+Message SubscriptionManagerImpl::get(const std::string& queue, sys::Duration timeout) {
+ Message result;
+ if (!get(result, queue, timeout))
+ throw Exception("Timed out waiting for a message");
+ return result;
+}
+
+Session SubscriptionManagerImpl::getSession() const { return session; }
+
+Subscription SubscriptionManagerImpl::getSubscription(const std::string& name) const {
+ sys::Mutex::ScopedLock l(lock);
+ std::map<std::string, Subscription>::const_iterator i = subscriptions.find(name);
+ if (i == subscriptions.end())
+ throw Exception(QPID_MSG("Subscription not found: " << name));
+ return i->second;
+}
+
+void SubscriptionManagerImpl::registerFailoverHandler (boost::function<void ()> fh) {
+ dispatcher.registerFailoverHandler(fh);
+}
+
+void SubscriptionManagerImpl::setFlowControl(const std::string& name, const FlowControl& flow) {
+ getSubscription(name).setFlowControl(flow);
+}
+
+void SubscriptionManagerImpl::setFlowControl(const std::string& name, uint32_t messages, uint32_t bytes, bool window) {
+ setFlowControl(name, FlowControl(messages, bytes, window));
+}
+
+AutoCancel::AutoCancel(SubscriptionManager& sm_, const std::string& tag_) : sm(sm_), tag(tag_) {}
+AutoCancel::~AutoCancel() {
+ try {
+ sm.cancel(tag);
+ } catch (const qpid::Exception& e) {
+ QPID_LOG(info, "Exception in AutoCancel destructor: " << e.what());
+ }
+}
+
+}} // namespace qpid::client
+
+
diff --git a/qpid/cpp/src/qpid/client/SubscriptionManagerImpl.h b/qpid/cpp/src/qpid/client/SubscriptionManagerImpl.h
new file mode 100644
index 0000000000..64d922e387
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/SubscriptionManagerImpl.h
@@ -0,0 +1,279 @@
+#ifndef QPID_CLIENT_SUBSCRIPTIONMANAGERIMPL_H
+#define QPID_CLIENT_SUBSCRIPTIONMANAGERIMPL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/sys/Mutex.h"
+#include <qpid/client/Dispatcher.h>
+#include <qpid/client/Completion.h>
+#include <qpid/client/Session.h>
+#include <qpid/client/AsyncSession.h>
+#include <qpid/client/MessageListener.h>
+#include <qpid/client/LocalQueue.h>
+#include <qpid/client/Subscription.h>
+#include <qpid/sys/Runnable.h>
+#include <qpid/RefCounted.h>
+#include <set>
+#include <sstream>
+
+namespace qpid {
+namespace client {
+
+/**
+ * A class to help create and manage subscriptions.
+ *
+ * Set up your subscriptions, then call run() to have messages
+ * delivered.
+ *
+ * \ingroup clientapi
+ *
+ * \details
+ *
+ * <h2>Subscribing and canceling subscriptions</h2>
+ *
+ * <ul>
+ * <li>
+ * <p>subscribe()</p>
+ * <pre> SubscriptionManager subscriptions(session);
+ * Listener listener(subscriptions);
+ * subscriptions.subscribe(listener, myQueue);</pre>
+ * <pre> SubscriptionManager subscriptions(session);
+ * LocalQueue local_queue;
+ * subscriptions.subscribe(local_queue, string("message_queue"));</pre></li>
+ * <li>
+ * <p>cancel()</p>
+ * <pre>subscriptions.cancel();</pre></li>
+ * </ul>
+ *
+ * <h2>Waiting for messages (and returning)</h2>
+ *
+ * <ul>
+ * <li>
+ * <p>run()</p>
+ * <pre> // Give up control to receive messages
+ * subscriptions.run();</pre></li>
+ * <li>
+ * <p>stop()</p>
+ * <pre>.// Use this code in a listener to return from run()
+ * subscriptions.stop();</pre></li>
+ * <li>
+ * <p>setAutoStop()</p>
+ * <pre>.// Return from subscriptions.run() when last subscription is cancelled
+ *.subscriptions.setAutoStop(true);
+ *.subscriptons.run();
+ * </pre></li>
+ * <li>
+ * <p>Ending a subscription in a listener</p>
+ * <pre>
+ * void Listener::received(Message&amp; message) {
+ *
+ * if (message.getData() == "That's all, folks!") {
+ * subscriptions.cancel(message.getDestination());
+ * }
+ * }
+ * </pre>
+ * </li>
+ * </ul>
+ *
+ */
+class SubscriptionManagerImpl : public sys::Runnable, public RefCounted
+{
+ public:
+ /** Create a new SubscriptionManagerImpl associated with a session */
+ SubscriptionManagerImpl(const Session& session);
+ ~SubscriptionManagerImpl();
+
+ /**
+ * Subscribe a MessagesListener to receive messages from queue.
+ *
+ * Provide your own subclass of MessagesListener to process
+ * incoming messages. It will be called for each message received.
+ *
+ *@param listener Listener object to receive messages.
+ *@param queue Name of the queue to subscribe to.
+ *@param settings settings for the subscription.
+ *@param name unique destination name for the subscription, defaults to queue name.
+ */
+ Subscription subscribe(MessageListener& listener,
+ const std::string& queue,
+ const SubscriptionSettings& settings,
+ const std::string& name=std::string());
+
+ /**
+ * Subscribe a LocalQueue to receive messages from queue.
+ *
+ * Incoming messages are stored in the queue for you to retrieve.
+ *
+ *@param queue Name of the queue to subscribe to.
+ *@param flow initial FlowControl for the subscription.
+ *@param name unique destination name for the subscription, defaults to queue name.
+ * If not specified, the queue name is used.
+ */
+ Subscription subscribe(LocalQueue& localQueue,
+ const std::string& queue,
+ const SubscriptionSettings& settings,
+ const std::string& name=std::string());
+
+ /**
+ * Subscribe a MessagesListener to receive messages from queue.
+ *
+ * Provide your own subclass of MessagesListener to process
+ * incoming messages. It will be called for each message received.
+ *
+ *@param listener Listener object to receive messages.
+ *@param queue Name of the queue to subscribe to.
+ *@param name unique destination name for the subscription, defaults to queue name.
+ * If not specified, the queue name is used.
+ */
+ Subscription subscribe(MessageListener& listener,
+ const std::string& queue,
+ const std::string& name=std::string());
+
+ /**
+ * Subscribe a LocalQueue to receive messages from queue.
+ *
+ * Incoming messages are stored in the queue for you to retrieve.
+ *
+ *@param queue Name of the queue to subscribe to.
+ *@param name unique destination name for the subscription, defaults to queue name.
+ * If not specified, the queue name is used.
+ */
+ Subscription subscribe(LocalQueue& localQueue,
+ const std::string& queue,
+ const std::string& name=std::string());
+
+
+ /** Get a single message from a queue.
+ *@param result is set to the message from the queue.
+ *@param timeout wait up this timeout for a message to appear.
+ *@return true if result was set, false if no message available after timeout.
+ */
+ bool get(Message& result, const std::string& queue, sys::Duration timeout=0);
+
+ /** Get a single message from a queue.
+ *@param timeout wait up this timeout for a message to appear.
+ *@return message from the queue.
+ *@throw Exception if the timeout is exceeded.
+ */
+ Message get(const std::string& queue, sys::Duration timeout=sys::TIME_INFINITE);
+
+ /** Get a subscription by name.
+ *@throw Exception if not found.
+ */
+ Subscription getSubscription(const std::string& name) const;
+
+ /** Cancel a subscription. See also: Subscription.cancel() */
+ void cancel(const std::string& name);
+
+ /** Deliver messages in the current thread until stop() is called.
+ * Only one thread may be running in a SubscriptionManager at a time.
+ * @see run
+ */
+ void run();
+
+ /** Start a new thread to deliver messages.
+ * Only one thread may be running in a SubscriptionManager at a time.
+ * @see start
+ */
+ void start();
+
+ /**
+ * Wait for the thread started by a call to start() to complete.
+ */
+ void wait();
+
+ /** If set true, run() will stop when all subscriptions
+ * are cancelled. If false, run will only stop when stop()
+ * is called. True by default.
+ */
+ void setAutoStop(bool set=true);
+
+ /** Stop delivery. Causes run() to return, or the thread started with start() to exit. */
+ void stop();
+
+ static const uint32_t UNLIMITED=0xFFFFFFFF;
+
+ /** Set the flow control for a subscription. */
+ void setFlowControl(const std::string& name, const FlowControl& flow);
+
+ /** Set the flow control for a subscription.
+ *@param name: name of the subscription.
+ *@param messages: message credit.
+ *@param bytes: byte credit.
+ *@param window: if true use window-based flow control.
+ */
+ void setFlowControl(const std::string& name, uint32_t messages, uint32_t bytes, bool window=true);
+
+ /** Set the default settings for subscribe() calls that don't
+ * include a SubscriptionSettings parameter.
+ */
+ void setDefaultSettings(const SubscriptionSettings& s) { defaultSettings = s; }
+
+ /** Get the default settings for subscribe() calls that don't
+ * include a SubscriptionSettings parameter.
+ */
+ const SubscriptionSettings& getDefaultSettings() const { return defaultSettings; }
+
+ /** Get the default settings for subscribe() calls that don't
+ * include a SubscriptionSettings parameter.
+ */
+ SubscriptionSettings& getDefaultSettings() { return defaultSettings; }
+
+ /**
+ * Set the default flow control settings for subscribe() calls
+ * that don't include a SubscriptionSettings parameter.
+ *
+ *@param messages: message credit.
+ *@param bytes: byte credit.
+ *@param window: if true use window-based flow control.
+ */
+ void setFlowControl(uint32_t messages, uint32_t bytes, bool window=true) {
+ defaultSettings.flowControl = FlowControl(messages, bytes, window);
+ }
+
+ /**
+ *Set the default accept-mode for subscribe() calls that don't
+ *include a SubscriptionSettings parameter.
+ */
+ void setAcceptMode(AcceptMode mode) { defaultSettings.acceptMode = mode; }
+
+ /**
+ * Set the default acquire-mode subscribe()s that don't specify SubscriptionSettings.
+ */
+ void setAcquireMode(AcquireMode mode) { defaultSettings.acquireMode = mode; }
+
+ void registerFailoverHandler ( boost::function<void ()> fh );
+
+ Session getSession() const;
+
+ private:
+ mutable sys::Mutex lock;
+ qpid::client::Dispatcher dispatcher;
+ qpid::client::AsyncSession session;
+ bool autoStop;
+ SubscriptionSettings defaultSettings;
+ std::map<std::string, Subscription> subscriptions;
+};
+
+
+}} // namespace qpid::client
+
+#endif /*!QPID_CLIENT_SUBSCRIPTIONMANAGERIMPL_H*/
diff --git a/qpid/cpp/src/qpid/client/SubscriptionSettings.h b/qpid/cpp/src/qpid/client/SubscriptionSettings.h
new file mode 100644
index 0000000000..bee39f6816
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/SubscriptionSettings.h
@@ -0,0 +1,135 @@
+#ifndef QPID_CLIENT_SUBSCRIPTIONSETTINGS_H
+#define QPID_CLIENT_SUBSCRIPTIONSETTINGS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/client/FlowControl.h"
+#include "qpid/framing/enum.h"
+
+namespace qpid {
+namespace client {
+
+/** Bring AMQP enum definitions for message class into this namespace. */
+using qpid::framing::message::AcceptMode;
+using qpid::framing::message::AcquireMode;
+using qpid::framing::message::ACCEPT_MODE_EXPLICIT;
+using qpid::framing::message::ACCEPT_MODE_NONE;
+using qpid::framing::message::ACQUIRE_MODE_NOT_ACQUIRED;
+using qpid::framing::message::ACQUIRE_MODE_PRE_ACQUIRED;
+using qpid::framing::message::CREDIT_UNIT_BYTE;
+using qpid::framing::message::CREDIT_UNIT_MESSAGE;
+using qpid::framing::message::DELIVERY_MODE_NON_PERSISTENT;
+using qpid::framing::message::DELIVERY_MODE_PERSISTENT;
+using qpid::framing::message::FLOW_MODE_CREDIT;
+using qpid::framing::message::FLOW_MODE_WINDOW;
+
+
+enum CompletionMode {
+ MANUAL_COMPLETION = 0,
+ COMPLETE_ON_DELIVERY = 1,
+ COMPLETE_ON_ACCEPT = 2
+};
+/**
+ * Settings for a subscription.
+ */
+struct SubscriptionSettings
+{
+ SubscriptionSettings(
+ FlowControl flow=FlowControl::unlimited(),
+ AcceptMode accept=ACCEPT_MODE_EXPLICIT,
+ AcquireMode acquire=ACQUIRE_MODE_PRE_ACQUIRED,
+ unsigned int autoAck_=1,
+ CompletionMode completion=COMPLETE_ON_DELIVERY
+ ) : flowControl(flow), acceptMode(accept), acquireMode(acquire), autoAck(autoAck_), completionMode(completion), exclusive(false) {}
+
+ FlowControl flowControl; ///@< Flow control settings. @see FlowControl
+ /**
+ * The acceptMode determines whether the broker should expect
+ * delivery of messages to be acknowledged by the client
+ * indicating that it accepts them. A value of
+ * ACCEPT_MODE_EXPLICIT means that messages must be accepted
+ * (note: this may be done automatically by the library - see
+ * autoAck - or through an explicit call be the application - see
+ * Subscription::accept()) before they can be dequeued. A value of
+ * ACCEPT_MODE_NONE means that the broker can dequeue a message as
+ * soon as it is acquired.
+ */
+ AcceptMode acceptMode; ///@< ACCEPT_MODE_EXPLICIT or ACCEPT_MODE_NONE
+ /**
+ * The acquireMode determines whether messages are locked for the
+ * subscriber when delivered, and thus are not delivered to any
+ * other subscriber unless this subscriber releases them.
+ *
+ * The default is ACQUIRE_MODE_PRE_ACQUIRED meaning that the
+ * subscriber expects to have been given that message exclusively
+ * (i.e. the message will not be given to any other subscriber
+ * unless released explicitly or by this subscribers session
+ * failing without having accepted the message).
+ *
+ * Delivery of message in ACQUIRE_MODE_NOT_ACQUIRED mode means the
+ * message will still be available for other subscribers to
+ * receive. The application can if desired acquire a (set of)
+ * messages through an explicit acquire call - see
+ * Subscription::acquire().
+ */
+ AcquireMode acquireMode; ///@< ACQUIRE_MODE_PRE_ACQUIRED or ACQUIRE_MODE_NOT_ACQUIRED
+
+ /**
+ * Configures the frequency at which messages are automatically
+ * accepted (e.g. a value of 5 means that messages are accepted in
+ * batches of 5). A value of 0 means no automatic acknowledgement
+ * will occur and the application will itself be responsible for
+ * accepting messages.
+ */
+ unsigned int autoAck;
+ /**
+ * In windowing mode, completion of a message will cause the
+ * credit used up by that message to be reallocated. The
+ * subscriptions completion mode controls how completion is
+ * managed.
+ *
+ * If set to COMPLETE_ON_DELIVERY (which is the default), messages
+ * will be marked as completed once they have been received. The
+ * server will be explicitly notified of all completed messages
+ * for the session when the next accept is sent through the
+ * subscription (either explictly or through autAck). However the
+ * server may also periodically request information on the
+ * completed messages.
+ *
+ * If set to COMPLETE_ON_ACCEPT, messages will be marked as
+ * completed once they are accepted (via the Subscription class)
+ * and the server will also be notified of all completed messages
+ * for the session.
+ *
+ * If set to MANUAL_COMPLETION the application is responsible for
+ * completing messages (@see Session::markCompleted()).
+ */
+ CompletionMode completionMode;
+ /**
+ * If set, requests that no other subscriber be allowed to access
+ * the queue while this subscription is active.
+ */
+ bool exclusive;
+};
+
+}} // namespace qpid::client
+
+#endif /*!QPID_CLIENT_SUBSCRIPTIONSETTINGS_H*/
diff --git a/qpid/cpp/src/qpid/client/TCPConnector.cpp b/qpid/cpp/src/qpid/client/TCPConnector.cpp
new file mode 100644
index 0000000000..0a570fb1d9
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/TCPConnector.cpp
@@ -0,0 +1,323 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/client/TCPConnector.h"
+
+#include "qpid/client/ConnectionImpl.h"
+#include "qpid/client/ConnectionSettings.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/Codec.h"
+#include "qpid/sys/Time.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/InitiationHandler.h"
+#include "qpid/sys/AsynchIO.h"
+#include "qpid/sys/Dispatcher.h"
+#include "qpid/sys/Poller.h"
+#include "qpid/sys/SecurityLayer.h"
+#include "qpid/Msg.h"
+
+#include <iostream>
+#include <boost/bind.hpp>
+#include <boost/format.hpp>
+
+namespace qpid {
+namespace client {
+
+using namespace qpid::sys;
+using namespace qpid::framing;
+using boost::format;
+using boost::str;
+
+// Static constructor which registers connector here
+namespace {
+ Connector* create(Poller::shared_ptr p, framing::ProtocolVersion v, const ConnectionSettings& s, ConnectionImpl* c) {
+ return new TCPConnector(p, v, s, c);
+ }
+
+ struct StaticInit {
+ StaticInit() {
+ Connector::registerFactory("tcp", &create);
+ };
+ } init;
+}
+
+TCPConnector::TCPConnector(Poller::shared_ptr p,
+ ProtocolVersion ver,
+ const ConnectionSettings& settings,
+ ConnectionImpl* cimpl)
+ : maxFrameSize(settings.maxFrameSize),
+ lastEof(0),
+ currentSize(0),
+ bounds(cimpl),
+ version(ver),
+ initiated(false),
+ closed(true),
+ shutdownHandler(0),
+ input(0),
+ socket(createSocket()),
+ connector(0),
+ aio(0),
+ poller(p)
+{
+ QPID_LOG(debug, "TCPConnector created for " << version);
+ settings.configureSocket(*socket);
+}
+
+TCPConnector::~TCPConnector() {
+ close();
+}
+
+void TCPConnector::connect(const std::string& host, const std::string& port) {
+ Mutex::ScopedLock l(lock);
+ assert(closed);
+ connector = AsynchConnector::create(
+ *socket,
+ host, port,
+ boost::bind(&TCPConnector::connected, this, _1),
+ boost::bind(&TCPConnector::connectFailed, this, _3));
+ closed = false;
+
+ connector->start(poller);
+}
+
+void TCPConnector::connected(const Socket&) {
+ connector = 0;
+ aio = AsynchIO::create(*socket,
+ boost::bind(&TCPConnector::readbuff, this, _1, _2),
+ boost::bind(&TCPConnector::eof, this, _1),
+ boost::bind(&TCPConnector::disconnected, this, _1),
+ boost::bind(&TCPConnector::socketClosed, this, _1, _2),
+ 0, // nobuffs
+ boost::bind(&TCPConnector::writebuff, this, _1));
+ start(aio);
+ initAmqp();
+ aio->start(poller);
+}
+
+void TCPConnector::start(sys::AsynchIO* aio_) {
+ aio = aio_;
+
+ aio->createBuffers(maxFrameSize);
+
+ identifier = str(format("[%1%]") % socket->getFullAddress());
+}
+
+void TCPConnector::initAmqp() {
+ ProtocolInitiation init(version);
+ writeDataBlock(init);
+}
+
+void TCPConnector::connectFailed(const std::string& msg) {
+ connector = 0;
+ QPID_LOG(warning, "Connect failed: " << msg);
+ socket->close();
+ if (!closed)
+ closed = true;
+ if (shutdownHandler)
+ shutdownHandler->shutdown();
+}
+
+void TCPConnector::close() {
+ Mutex::ScopedLock l(lock);
+ if (!closed) {
+ closed = true;
+ if (aio)
+ aio->queueWriteClose();
+ }
+}
+
+void TCPConnector::socketClosed(AsynchIO&, const Socket&) {
+ if (aio)
+ aio->queueForDeletion();
+ if (shutdownHandler)
+ shutdownHandler->shutdown();
+}
+
+void TCPConnector::connectAborted() {
+ connector->stop();
+ connectFailed("Connection timedout");
+}
+
+void TCPConnector::abort() {
+ // Can't abort a closed connection
+ if (!closed) {
+ if (aio) {
+ // Established connection
+ aio->requestCallback(boost::bind(&TCPConnector::disconnected, this, _1));
+ } else if (connector) {
+ // We're still connecting
+ connector->requestCallback(boost::bind(&TCPConnector::connectAborted, this));
+ }
+ }
+}
+
+void TCPConnector::setInputHandler(InputHandler* handler){
+ input = handler;
+}
+
+void TCPConnector::setShutdownHandler(ShutdownHandler* handler){
+ shutdownHandler = handler;
+}
+
+const std::string& TCPConnector::getIdentifier() const {
+ return identifier;
+}
+
+void TCPConnector::handle(AMQFrame& frame) {
+ bool notifyWrite = false;
+ {
+ Mutex::ScopedLock l(lock);
+ frames.push_back(frame);
+ //only ask to write if this is the end of a frameset or if we
+ //already have a buffers worth of data
+ currentSize += frame.encodedSize();
+ if (frame.getEof()) {
+ lastEof = frames.size();
+ notifyWrite = true;
+ } else {
+ notifyWrite = (currentSize >= maxFrameSize);
+ }
+ /*
+ NOTE: Moving the following line into this mutex block
+ is a workaround for BZ 570168, in which the test
+ testConcurrentSenders causes a hang about 1.5%
+ of the time. ( To see the hang much more frequently
+ leave this line out of the mutex block, and put a
+ small usleep just before it.)
+
+ TODO mgoulish - fix the underlying cause and then
+ move this call back outside the mutex.
+ */
+ if (notifyWrite && !closed) aio->notifyPendingWrite();
+ }
+}
+
+void TCPConnector::writebuff(AsynchIO& /*aio*/)
+{
+ // It's possible to be disconnected and be writable
+ if (closed)
+ return;
+
+ Codec* codec = securityLayer.get() ? (Codec*) securityLayer.get() : (Codec*) this;
+
+ if (!codec->canEncode()) {
+ return;
+ }
+
+ AsynchIO::BufferBase* buffer = aio->getQueuedBuffer();
+ if (buffer) {
+
+ size_t encoded = codec->encode(buffer->bytes, buffer->byteCount);
+
+ buffer->dataStart = 0;
+ buffer->dataCount = encoded;
+ aio->queueWrite(buffer);
+ }
+}
+
+// Called in IO thread.
+bool TCPConnector::canEncode()
+{
+ Mutex::ScopedLock l(lock);
+ //have at least one full frameset or a whole buffers worth of data
+ return lastEof || currentSize >= maxFrameSize;
+}
+
+// Called in IO thread.
+size_t TCPConnector::encode(char* buffer, size_t size)
+{
+ framing::Buffer out(buffer, size);
+ size_t bytesWritten(0);
+ {
+ Mutex::ScopedLock l(lock);
+ while (!frames.empty() && out.available() >= frames.front().encodedSize() ) {
+ frames.front().encode(out);
+ QPID_LOG(trace, "SENT [" << identifier << "]: " << frames.front());
+ frames.pop_front();
+ if (lastEof) --lastEof;
+ }
+ bytesWritten = size - out.available();
+ currentSize -= bytesWritten;
+ }
+ if (bounds) bounds->reduce(bytesWritten);
+ return bytesWritten;
+}
+
+void TCPConnector::readbuff(AsynchIO& aio, AsynchIO::BufferBase* buff)
+{
+ Codec* codec = securityLayer.get() ? (Codec*) securityLayer.get() : (Codec*) this;
+ int32_t decoded = codec->decode(buff->bytes+buff->dataStart, buff->dataCount);
+ // TODO: unreading needs to go away, and when we can cope
+ // with multiple sub-buffers in the general buffer scheme, it will
+ if (decoded < buff->dataCount) {
+ // Adjust buffer for used bytes and then "unread them"
+ buff->dataStart += decoded;
+ buff->dataCount -= decoded;
+ aio.unread(buff);
+ } else {
+ // Give whole buffer back to aio subsystem
+ aio.queueReadBuffer(buff);
+ }
+}
+
+size_t TCPConnector::decode(const char* buffer, size_t size)
+{
+ framing::Buffer in(const_cast<char*>(buffer), size);
+ try {
+ if (checkProtocolHeader(in, version)) {
+ AMQFrame frame;
+ while(frame.decode(in)){
+ QPID_LOG(trace, "RECV [" << identifier << "]: " << frame);
+ input->received(frame);
+ }
+ }
+ } catch (const ProtocolVersionError& e) {
+ QPID_LOG(info, "Closing connection due to " << e.what());
+ close();
+ }
+ return size - in.available();
+}
+
+void TCPConnector::writeDataBlock(const AMQDataBlock& data) {
+ AsynchIO::BufferBase* buff = aio->getQueuedBuffer();
+ assert(buff);
+ framing::Buffer out(buff->bytes, buff->byteCount);
+ data.encode(out);
+ buff->dataCount = data.encodedSize();
+ aio->queueWrite(buff);
+}
+
+void TCPConnector::eof(AsynchIO&) {
+ close();
+}
+
+void TCPConnector::disconnected(AsynchIO&) {
+ close();
+ socketClosed(*aio, *socket);
+}
+
+void TCPConnector::activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer> sl)
+{
+ securityLayer = sl;
+ securityLayer->init(this);
+}
+
+}} // namespace qpid::client
diff --git a/qpid/cpp/src/qpid/client/TCPConnector.h b/qpid/cpp/src/qpid/client/TCPConnector.h
new file mode 100644
index 0000000000..20bd2fa5b8
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/TCPConnector.h
@@ -0,0 +1,116 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#ifndef _TCPConnector_
+#define _TCPConnector_
+
+#include "Connector.h"
+#include "qpid/client/Bounds.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/sys/AsynchIO.h"
+#include "qpid/sys/Codec.h"
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/Runnable.h"
+#include "qpid/sys/SecurityLayer.h"
+#include "qpid/sys/Socket.h"
+#include "qpid/sys/Thread.h"
+
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <deque>
+#include <string>
+
+namespace qpid {
+
+namespace framing {
+ class InitiationHandler;
+}
+
+namespace client {
+
+class TCPConnector : public Connector, public sys::Codec
+{
+ typedef std::deque<framing::AMQFrame> Frames;
+
+ const uint16_t maxFrameSize;
+
+ sys::Mutex lock;
+ Frames frames; // Outgoing frame queue
+ size_t lastEof; // Position after last EOF in frames
+ uint64_t currentSize;
+ Bounds* bounds;
+
+ framing::ProtocolVersion version;
+ bool initiated;
+ bool closed;
+
+ sys::ShutdownHandler* shutdownHandler;
+ framing::InputHandler* input;
+
+ boost::scoped_ptr<sys::Socket> socket;
+
+ sys::AsynchConnector* connector;
+ sys::AsynchIO* aio;
+ std::string identifier;
+ boost::shared_ptr<sys::Poller> poller;
+ std::auto_ptr<qpid::sys::SecurityLayer> securityLayer;
+
+ virtual void connected(const sys::Socket&);
+ void writeDataBlock(const framing::AMQDataBlock& data);
+
+ void close();
+ void handle(framing::AMQFrame& frame);
+ void abort();
+ void connectAborted();
+
+ void setInputHandler(framing::InputHandler* handler);
+ void setShutdownHandler(sys::ShutdownHandler* handler);
+ const std::string& getIdentifier() const;
+ void activateSecurityLayer(std::auto_ptr<qpid::sys::SecurityLayer>);
+ const qpid::sys::SecuritySettings* getSecuritySettings() { return 0; }
+
+ size_t decode(const char* buffer, size_t size);
+ size_t encode(char* buffer, size_t size);
+ bool canEncode();
+
+protected:
+ virtual ~TCPConnector();
+ void connect(const std::string& host, const std::string& port);
+ void start(sys::AsynchIO* aio_);
+ void initAmqp();
+ virtual void connectFailed(const std::string& msg);
+ void readbuff(qpid::sys::AsynchIO&, qpid::sys::AsynchIOBufferBase*);
+ void writebuff(qpid::sys::AsynchIO&);
+ void eof(qpid::sys::AsynchIO&);
+ void disconnected(qpid::sys::AsynchIO&);
+ void socketClosed(qpid::sys::AsynchIO&, const qpid::sys::Socket&);
+
+public:
+ TCPConnector(boost::shared_ptr<sys::Poller>,
+ framing::ProtocolVersion pVersion,
+ const ConnectionSettings&,
+ ConnectionImpl*);
+};
+
+}} // namespace qpid::client
+
+#endif /* _TCPConnector_ */
diff --git a/qpid/cpp/src/qpid/client/TypedResult.h b/qpid/cpp/src/qpid/client/TypedResult.h
new file mode 100644
index 0000000000..8e1a16580c
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/TypedResult.h
@@ -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.
+ *
+ */
+
+#ifndef _TypedResult_
+#define _TypedResult_
+
+#include "qpid/client/Completion.h"
+#include "qpid/framing/StructHelper.h"
+
+namespace qpid {
+namespace client {
+
+/**
+ * Returned by asynchronous commands that return a result.
+ * You can use get() to wait for completion and get the result value.
+ * \ingroup clientapi
+ */
+template <class T> class TypedResult : public Completion
+{
+ T result;
+ bool decoded;
+
+public:
+ ///@internal
+ TypedResult(const Completion& c) : Completion(c), decoded(false) {}
+
+ /**
+ * Wait for the asynchronous command that returned this TypedResult to complete
+ * and return its result.
+ *
+ *@return The result returned by the command.
+ *@exception If the command returns an error, get() throws an exception.
+ *
+ */
+ T& get() {
+ if (!decoded) {
+ framing::StructHelper helper;
+ helper.decode(result, getResult());
+ decoded = true;
+ }
+ return result;
+ }
+};
+
+}}
+
+#endif
diff --git a/qpid/cpp/src/qpid/client/amqp0_10/AcceptTracker.cpp b/qpid/cpp/src/qpid/client/amqp0_10/AcceptTracker.cpp
new file mode 100644
index 0000000000..d2accddcd0
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/amqp0_10/AcceptTracker.cpp
@@ -0,0 +1,153 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "AcceptTracker.h"
+
+namespace qpid {
+namespace client {
+namespace amqp0_10 {
+
+void AcceptTracker::State::accept()
+{
+ unconfirmed.add(unaccepted);
+ unaccepted.clear();
+}
+
+SequenceSet AcceptTracker::State::accept(qpid::framing::SequenceNumber id, bool cumulative)
+{
+ SequenceSet accepting;
+ if (cumulative) {
+ for (SequenceSet::iterator i = unaccepted.begin(); i != unaccepted.end() && *i <= id; ++i) {
+ accepting.add(*i);
+ }
+ unconfirmed.add(accepting);
+ unaccepted.remove(accepting);
+ } else {
+ if (unaccepted.contains(id)) {
+ unaccepted.remove(id);
+ unconfirmed.add(id);
+ accepting.add(id);
+ }
+ }
+ return accepting;
+}
+
+void AcceptTracker::State::release()
+{
+ unaccepted.clear();
+}
+
+uint32_t AcceptTracker::State::acceptsPending()
+{
+ return unconfirmed.size();
+}
+
+void AcceptTracker::State::completed(qpid::framing::SequenceSet& set)
+{
+ unconfirmed.remove(set);
+}
+
+void AcceptTracker::delivered(const std::string& destination, const qpid::framing::SequenceNumber& id)
+{
+ aggregateState.unaccepted.add(id);
+ destinationState[destination].unaccepted.add(id);
+}
+
+namespace
+{
+const size_t FLUSH_FREQUENCY = 1024;
+}
+
+void AcceptTracker::addToPending(qpid::client::AsyncSession& session, const Record& record)
+{
+ pending.push_back(record);
+ if (pending.size() % FLUSH_FREQUENCY == 0) session.flush();
+}
+
+
+void AcceptTracker::accept(qpid::client::AsyncSession& session)
+{
+ for (StateMap::iterator i = destinationState.begin(); i != destinationState.end(); ++i) {
+ i->second.accept();
+ }
+ Record record;
+ record.status = session.messageAccept(aggregateState.unaccepted);
+ record.accepted = aggregateState.unaccepted;
+ addToPending(session, record);
+ aggregateState.accept();
+}
+
+void AcceptTracker::accept(qpid::framing::SequenceNumber id, qpid::client::AsyncSession& session, bool cumulative)
+{
+ for (StateMap::iterator i = destinationState.begin(); i != destinationState.end(); ++i) {
+ i->second.accept(id, cumulative);
+ }
+ Record record;
+ record.accepted = aggregateState.accept(id, cumulative);
+ record.status = session.messageAccept(record.accepted);
+ addToPending(session, record);
+}
+
+void AcceptTracker::release(qpid::client::AsyncSession& session)
+{
+ session.messageRelease(aggregateState.unaccepted);
+ for (StateMap::iterator i = destinationState.begin(); i != destinationState.end(); ++i) {
+ i->second.release();
+ }
+ aggregateState.release();
+}
+
+uint32_t AcceptTracker::acceptsPending()
+{
+ checkPending();
+ return aggregateState.acceptsPending();
+}
+
+uint32_t AcceptTracker::acceptsPending(const std::string& destination)
+{
+ checkPending();
+ return destinationState[destination].acceptsPending();
+}
+
+void AcceptTracker::reset()
+{
+ destinationState.clear();
+ aggregateState.unaccepted.clear();
+ aggregateState.unconfirmed.clear();
+ pending.clear();
+}
+
+void AcceptTracker::checkPending()
+{
+ while (!pending.empty() && pending.front().status.isComplete()) {
+ completed(pending.front().accepted);
+ pending.pop_front();
+ }
+}
+
+void AcceptTracker::completed(qpid::framing::SequenceSet& set)
+{
+ for (StateMap::iterator i = destinationState.begin(); i != destinationState.end(); ++i) {
+ i->second.completed(set);
+ }
+ aggregateState.completed(set);
+}
+
+}}} // namespace qpid::client::amqp0_10
diff --git a/qpid/cpp/src/qpid/client/amqp0_10/AcceptTracker.h b/qpid/cpp/src/qpid/client/amqp0_10/AcceptTracker.h
new file mode 100644
index 0000000000..85209c3b87
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/amqp0_10/AcceptTracker.h
@@ -0,0 +1,88 @@
+#ifndef QPID_CLIENT_AMQP0_10_ACCEPTTRACKER_H
+#define QPID_CLIENT_AMQP0_10_ACCEPTTRACKER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/client/AsyncSession.h"
+#include "qpid/client/Completion.h"
+#include "qpid/framing/SequenceNumber.h"
+#include "qpid/framing/SequenceSet.h"
+#include <deque>
+#include <map>
+
+namespace qpid {
+namespace client {
+namespace amqp0_10 {
+
+/**
+ * Tracks the set of messages requiring acceptance, and those for
+ * which an accept has been issued but is yet to be confirmed
+ * complete.
+ */
+class AcceptTracker
+{
+ public:
+ void delivered(const std::string& destination, const qpid::framing::SequenceNumber& id);
+ void accept(qpid::client::AsyncSession&);
+ void accept(qpid::framing::SequenceNumber, qpid::client::AsyncSession&, bool cumulative);
+ void release(qpid::client::AsyncSession&);
+ uint32_t acceptsPending();
+ uint32_t acceptsPending(const std::string& destination);
+ void reset();
+ private:
+ struct State
+ {
+ /**
+ * ids of messages that have been delivered but not yet
+ * accepted
+ */
+ qpid::framing::SequenceSet unaccepted;
+ /**
+ * ids of messages for which an accept has been issued but not
+ * yet confirmed as completed
+ */
+ qpid::framing::SequenceSet unconfirmed;
+
+ void accept();
+ qpid::framing::SequenceSet accept(qpid::framing::SequenceNumber, bool cumulative);
+ void release();
+ uint32_t acceptsPending();
+ void completed(qpid::framing::SequenceSet&);
+ };
+ typedef std::map<std::string, State> StateMap;
+ struct Record
+ {
+ qpid::client::Completion status;
+ qpid::framing::SequenceSet accepted;
+ };
+ typedef std::deque<Record> Records;
+
+ State aggregateState;
+ StateMap destinationState;
+ Records pending;
+
+ void addToPending(qpid::client::AsyncSession&, const Record&);
+ void checkPending();
+ void completed(qpid::framing::SequenceSet&);
+};
+}}} // namespace qpid::client::amqp0_10
+
+#endif /*!QPID_CLIENT_AMQP0_10_ACCEPTTRACKER_H*/
diff --git a/qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp b/qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp
new file mode 100644
index 0000000000..ed931c90fb
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.cpp
@@ -0,0 +1,1058 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/client/amqp0_10/AddressResolution.h"
+#include "qpid/amqp_0_10/Codecs.h"
+#include "qpid/client/amqp0_10/MessageSource.h"
+#include "qpid/client/amqp0_10/MessageSink.h"
+#include "qpid/client/amqp0_10/OutgoingMessage.h"
+#include "qpid/messaging/Address.h"
+#include "qpid/messaging/AddressImpl.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/types/Variant.h"
+#include "qpid/messaging/exceptions.h"
+#include "qpid/log/Statement.h"
+#include "qpid/framing/enum.h"
+#include "qpid/framing/ExchangeBoundResult.h"
+#include "qpid/framing/ExchangeQueryResult.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/framing/QueueQueryResult.h"
+#include "qpid/framing/ReplyTo.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/framing/Uuid.h"
+#include <boost/assign.hpp>
+#include <boost/format.hpp>
+
+namespace qpid {
+namespace client {
+namespace amqp0_10 {
+
+using qpid::Exception;
+using qpid::messaging::Address;
+using qpid::messaging::AddressError;
+using qpid::messaging::MalformedAddress;
+using qpid::messaging::ResolutionError;
+using qpid::messaging::NotFound;
+using qpid::messaging::AssertionFailed;
+using qpid::framing::ExchangeBoundResult;
+using qpid::framing::ExchangeQueryResult;
+using qpid::framing::FieldTable;
+using qpid::framing::FieldValue;
+using qpid::framing::QueueQueryResult;
+using qpid::framing::ReplyTo;
+using qpid::framing::Uuid;
+using namespace qpid::types;
+using namespace qpid::framing::message;
+using namespace qpid::amqp_0_10;
+using namespace boost::assign;
+
+class Verifier
+{
+ public:
+ Verifier();
+ void verify(const Address& address) const;
+ private:
+ Variant::Map defined;
+ void verify(const Variant::Map& allowed, const Variant::Map& actual) const;
+};
+
+namespace{
+const Variant EMPTY_VARIANT;
+const FieldTable EMPTY_FIELD_TABLE;
+const Variant::List EMPTY_LIST;
+const std::string EMPTY_STRING;
+
+//policy types
+const std::string CREATE("create");
+const std::string ASSERT("assert");
+const std::string DELETE("delete");
+
+//option names
+const std::string NODE("node");
+const std::string LINK("link");
+const std::string MODE("mode");
+const std::string RELIABILITY("reliability");
+const std::string TIMEOUT("timeout");
+const std::string NAME("name");
+const std::string DURABLE("durable");
+const std::string X_DECLARE("x-declare");
+const std::string X_SUBSCRIBE("x-subscribe");
+const std::string X_BINDINGS("x-bindings");
+const std::string SELECTOR("selector");
+const std::string APACHE_SELECTOR("x-apache-selector");
+const std::string QPID_FILTER("qpid.filter");
+const std::string EXCHANGE("exchange");
+const std::string QUEUE("queue");
+const std::string KEY("key");
+const std::string ARGUMENTS("arguments");
+const std::string ALTERNATE_EXCHANGE("alternate-exchange");
+const std::string TYPE("type");
+const std::string EXCLUSIVE("exclusive");
+const std::string AUTO_DELETE("auto-delete");
+
+//policy values
+const std::string ALWAYS("always");
+const std::string NEVER("never");
+const std::string RECEIVER("receiver");
+const std::string SENDER("sender");
+
+//address types
+const std::string QUEUE_ADDRESS("queue");
+const std::string TOPIC_ADDRESS("topic");
+
+//reliability options:
+const std::string UNRELIABLE("unreliable");
+const std::string AT_MOST_ONCE("at-most-once");
+const std::string AT_LEAST_ONCE("at-least-once");
+const std::string EXACTLY_ONCE("exactly-once");
+
+//receiver modes:
+const std::string BROWSE("browse");
+const std::string CONSUME("consume");
+
+//0-10 exchange types:
+const std::string TOPIC_EXCHANGE("topic");
+const std::string FANOUT_EXCHANGE("fanout");
+const std::string DIRECT_EXCHANGE("direct");
+const std::string HEADERS_EXCHANGE("headers");
+const std::string XML_EXCHANGE("xml");
+const std::string WILDCARD_ANY("#");
+
+//exchange prefixes:
+const std::string PREFIX_AMQ("amq.");
+const std::string PREFIX_QPID("qpid.");
+
+const Verifier verifier;
+
+bool areEquivalent(const FieldValue& a, const FieldValue& b)
+{
+ return ((a == b) || (a.convertsTo<int64_t>() && b.convertsTo<int64_t>() && a.get<int64_t>() == b.get<int64_t>()));
+}
+}
+
+struct Binding
+{
+ Binding(const Variant::Map&);
+ Binding(const std::string& exchange, const std::string& queue, const std::string& key);
+
+ std::string exchange;
+ std::string queue;
+ std::string key;
+ FieldTable arguments;
+};
+
+struct Bindings : std::vector<Binding>
+{
+ void add(const Variant::List& bindings);
+ void setDefaultExchange(const std::string&);
+ void setDefaultQueue(const std::string&);
+ void bind(qpid::client::AsyncSession& session);
+ void unbind(qpid::client::AsyncSession& session);
+ void check(qpid::client::AsyncSession& session);
+};
+
+class Node
+{
+ protected:
+ enum CheckMode {FOR_RECEIVER, FOR_SENDER};
+
+ Node(const Address& address);
+
+ const std::string name;
+ Variant createPolicy;
+ Variant assertPolicy;
+ Variant deletePolicy;
+ Bindings nodeBindings;
+ Bindings linkBindings;
+
+ static bool enabled(const Variant& policy, CheckMode mode);
+ static bool createEnabled(const Address& address, CheckMode mode);
+ static void convert(const Variant& option, FieldTable& arguments);
+ static std::vector<std::string> RECEIVER_MODES;
+ static std::vector<std::string> SENDER_MODES;
+};
+
+
+class Queue : protected Node
+{
+ public:
+ Queue(const Address& address);
+ protected:
+ void checkCreate(qpid::client::AsyncSession&, CheckMode);
+ void checkAssert(qpid::client::AsyncSession&, CheckMode);
+ void checkDelete(qpid::client::AsyncSession&, CheckMode);
+ private:
+ const bool durable;
+ bool autoDelete;
+ bool exclusive;
+ const std::string alternateExchange;
+ FieldTable arguments;
+};
+
+class Exchange : protected Node
+{
+ public:
+ Exchange(const Address& address);
+ protected:
+ void checkCreate(qpid::client::AsyncSession&, CheckMode);
+ void checkAssert(qpid::client::AsyncSession&, CheckMode);
+ void checkDelete(qpid::client::AsyncSession&, CheckMode);
+ bool isReservedName();
+
+ protected:
+ const std::string specifiedType;
+ private:
+ const bool durable;
+ bool autoDelete;
+ const std::string alternateExchange;
+ FieldTable arguments;
+};
+
+class QueueSource : public Queue, public MessageSource
+{
+ public:
+ QueueSource(const Address& address);
+ void subscribe(qpid::client::AsyncSession& session, const std::string& destination);
+ void cancel(qpid::client::AsyncSession& session, const std::string& destination);
+ private:
+ const AcquireMode acquireMode;
+ const AcceptMode acceptMode;
+ bool exclusive;
+ FieldTable options;
+};
+
+class Subscription : public Exchange, public MessageSource
+{
+ public:
+ Subscription(const Address&, const std::string& actualType);
+ void subscribe(qpid::client::AsyncSession& session, const std::string& destination);
+ void cancel(qpid::client::AsyncSession& session, const std::string& destination);
+ private:
+ const std::string queue;
+ const bool durable;
+ const bool reliable;
+ const std::string actualType;
+ const bool exclusiveQueue;
+ const bool autoDeleteQueue;
+ const bool exclusiveSubscription;
+ const std::string alternateExchange;
+ FieldTable queueOptions;
+ FieldTable subscriptionOptions;
+ Bindings bindings;
+
+ void bindSubject(const std::string& subject);
+ void bindAll();
+ void add(const std::string& exchange, const std::string& key);
+ static std::string getSubscriptionName(const std::string& base, const std::string& name);
+};
+
+class ExchangeSink : public Exchange, public MessageSink
+{
+ public:
+ ExchangeSink(const Address& name);
+ void declare(qpid::client::AsyncSession& session, const std::string& name);
+ void send(qpid::client::AsyncSession& session, const std::string& name, OutgoingMessage& message);
+ void cancel(qpid::client::AsyncSession& session, const std::string& name);
+ private:
+};
+
+class QueueSink : public Queue, public MessageSink
+{
+ public:
+ QueueSink(const Address& name);
+ void declare(qpid::client::AsyncSession& session, const std::string& name);
+ void send(qpid::client::AsyncSession& session, const std::string& name, OutgoingMessage& message);
+ void cancel(qpid::client::AsyncSession& session, const std::string& name);
+ private:
+};
+bool isQueue(qpid::client::Session session, const qpid::messaging::Address& address);
+bool isTopic(qpid::client::Session session, const qpid::messaging::Address& address);
+
+bool in(const Variant& value, const std::vector<std::string>& choices)
+{
+ if (!value.isVoid()) {
+ for (std::vector<std::string>::const_iterator i = choices.begin(); i != choices.end(); ++i) {
+ if (value.asString() == *i) return true;
+ }
+ }
+ return false;
+}
+
+const Variant& getOption(const Variant::Map& options, const std::string& name)
+{
+ Variant::Map::const_iterator j = options.find(name);
+ if (j == options.end()) {
+ return EMPTY_VARIANT;
+ } else {
+ return j->second;
+ }
+}
+
+const Variant& getOption(const Address& address, const std::string& name)
+{
+ return getOption(address.getOptions(), name);
+}
+
+bool getReceiverPolicy(const Address& address, const std::string& key)
+{
+ return in(getOption(address, key), list_of<std::string>(ALWAYS)(RECEIVER));
+}
+
+bool getSenderPolicy(const Address& address, const std::string& key)
+{
+ return in(getOption(address, key), list_of<std::string>(ALWAYS)(SENDER));
+}
+
+struct Opt
+{
+ Opt(const Address& address);
+ Opt(const Variant::Map& base);
+ Opt& operator/(const std::string& name);
+ operator bool() const;
+ operator std::string() const;
+ std::string str() const;
+ bool asBool(bool defaultValue) const;
+ const Variant::List& asList() const;
+ void collect(qpid::framing::FieldTable& args) const;
+ bool hasKey(const std::string&) const;
+
+ const Variant::Map* options;
+ const Variant* value;
+};
+
+Opt::Opt(const Address& address) : options(&(address.getOptions())), value(0) {}
+Opt::Opt(const Variant::Map& base) : options(&base), value(0) {}
+Opt& Opt::operator/(const std::string& name)
+{
+ if (options) {
+ Variant::Map::const_iterator j = options->find(name);
+ if (j == options->end()) {
+ value = 0;
+ options = 0;
+ } else {
+ value = &(j->second);
+ if (value->getType() == VAR_MAP) options = &(value->asMap());
+ else options = 0;
+ }
+ }
+ return *this;
+}
+
+
+Opt::operator bool() const
+{
+ return value && !value->isVoid() && value->asBool();
+}
+
+Opt::operator std::string() const
+{
+ return str();
+}
+
+bool Opt::asBool(bool defaultValue) const
+{
+ if (value) return value->asBool();
+ else return defaultValue;
+}
+
+std::string Opt::str() const
+{
+ if (value) return value->asString();
+ else return EMPTY_STRING;
+}
+
+const Variant::List& Opt::asList() const
+{
+ if (value) return value->asList();
+ else return EMPTY_LIST;
+}
+
+void Opt::collect(qpid::framing::FieldTable& args) const
+{
+ if (value) {
+ translate(value->asMap(), args);
+ }
+}
+bool Opt::hasKey(const std::string& key) const
+{
+ if (value) {
+ Variant::Map::const_iterator i = value->asMap().find(key);
+ return i != value->asMap().end();
+ } else {
+ return false;
+ }
+}
+
+bool AddressResolution::is_unreliable(const Address& address)
+{
+
+ return in((Opt(address)/LINK/RELIABILITY).str(),
+ list_of<std::string>(UNRELIABLE)(AT_MOST_ONCE));
+}
+
+bool AddressResolution::is_reliable(const Address& address)
+{
+ return in((Opt(address)/LINK/RELIABILITY).str(),
+ list_of<std::string>(AT_LEAST_ONCE)(EXACTLY_ONCE));
+}
+
+std::string checkAddressType(qpid::client::Session session, const Address& address)
+{
+ verifier.verify(address);
+ if (address.getName().empty()) {
+ throw MalformedAddress("Name cannot be null");
+ }
+ std::string type = (Opt(address)/NODE/TYPE).str();
+ if (type.empty()) {
+ ExchangeBoundResult result = session.exchangeBound(arg::exchange=address.getName(), arg::queue=address.getName());
+ if (result.getQueueNotFound() && result.getExchangeNotFound()) {
+ //neither a queue nor an exchange exists with that name; treat it as a queue
+ type = QUEUE_ADDRESS;
+ } else if (result.getExchangeNotFound()) {
+ //name refers to a queue
+ type = QUEUE_ADDRESS;
+ } else if (result.getQueueNotFound()) {
+ //name refers to an exchange
+ type = TOPIC_ADDRESS;
+ } else {
+ //both a queue and exchange exist for that name
+ throw ResolutionError("Ambiguous address, please specify queue or topic as node type");
+ }
+ }
+ return type;
+}
+
+std::auto_ptr<MessageSource> AddressResolution::resolveSource(qpid::client::Session session,
+ const Address& address)
+{
+ std::string type = checkAddressType(session, address);
+ if (type == TOPIC_ADDRESS) {
+ std::string exchangeType = sync(session).exchangeQuery(address.getName()).getType();
+ std::auto_ptr<MessageSource> source(new Subscription(address, exchangeType));
+ QPID_LOG(debug, "treating source address as topic: " << address);
+ return source;
+ } else if (type == QUEUE_ADDRESS) {
+ std::auto_ptr<MessageSource> source(new QueueSource(address));
+ QPID_LOG(debug, "treating source address as queue: " << address);
+ return source;
+ } else {
+ throw ResolutionError("Unrecognised type: " + type);
+ }
+}
+
+
+std::auto_ptr<MessageSink> AddressResolution::resolveSink(qpid::client::Session session,
+ const qpid::messaging::Address& address)
+{
+ std::string type = checkAddressType(session, address);
+ if (type == TOPIC_ADDRESS) {
+ std::auto_ptr<MessageSink> sink(new ExchangeSink(address));
+ QPID_LOG(debug, "treating target address as topic: " << address);
+ return sink;
+ } else if (type == QUEUE_ADDRESS) {
+ std::auto_ptr<MessageSink> sink(new QueueSink(address));
+ QPID_LOG(debug, "treating target address as queue: " << address);
+ return sink;
+ } else {
+ throw ResolutionError("Unrecognised type: " + type);
+ }
+}
+
+bool isBrowse(const Address& address)
+{
+ const Variant& mode = getOption(address, MODE);
+ if (!mode.isVoid()) {
+ std::string value = mode.asString();
+ if (value == BROWSE) return true;
+ else if (value != CONSUME) throw ResolutionError("Invalid mode");
+ }
+ return false;
+}
+
+QueueSource::QueueSource(const Address& address) :
+ Queue(address),
+ acquireMode(isBrowse(address) ? ACQUIRE_MODE_NOT_ACQUIRED : ACQUIRE_MODE_PRE_ACQUIRED),
+ //since this client does not provide any means by which an
+ //unacquired message can be acquired, there is no value in an
+ //explicit accept
+ acceptMode(acquireMode == ACQUIRE_MODE_NOT_ACQUIRED || AddressResolution::is_unreliable(address) ? ACCEPT_MODE_NONE : ACCEPT_MODE_EXPLICIT),
+ exclusive(false)
+{
+ exclusive = Opt(address)/LINK/X_SUBSCRIBE/EXCLUSIVE;
+ (Opt(address)/LINK/X_SUBSCRIBE/ARGUMENTS).collect(options);
+ std::string selector = Opt(address)/LINK/SELECTOR;
+ if (!selector.empty()) options.setString(APACHE_SELECTOR, selector);
+}
+
+void QueueSource::subscribe(qpid::client::AsyncSession& session, const std::string& destination)
+{
+ checkCreate(session, FOR_RECEIVER);
+ checkAssert(session, FOR_RECEIVER);
+ linkBindings.bind(session);
+ session.messageSubscribe(arg::queue=name,
+ arg::destination=destination,
+ arg::acceptMode=acceptMode,
+ arg::acquireMode=acquireMode,
+ arg::exclusive=exclusive,
+ arg::arguments=options);
+}
+
+void QueueSource::cancel(qpid::client::AsyncSession& session, const std::string& destination)
+{
+ linkBindings.unbind(session);
+ session.messageCancel(destination);
+ checkDelete(session, FOR_RECEIVER);
+}
+
+std::string Subscription::getSubscriptionName(const std::string& base, const std::string& name)
+{
+ if (name.empty()) {
+ return (boost::format("%1%_%2%") % base % Uuid(true).str()).str();
+ } else {
+ return name;
+ }
+}
+
+Subscription::Subscription(const Address& address, const std::string& type)
+ : Exchange(address),
+ queue(getSubscriptionName(name, (Opt(address)/LINK/NAME).str())),
+ durable(Opt(address)/LINK/DURABLE),
+ //if the link is durable, then assume it is also reliable unless explicitly stated otherwise
+ //if not assume it is unreliable unless explicitly stated otherwise
+ reliable(durable ? !AddressResolution::is_unreliable(address) : AddressResolution::is_reliable(address)),
+ actualType(type.empty() ? (specifiedType.empty() ? TOPIC_EXCHANGE : specifiedType) : type),
+ exclusiveQueue((Opt(address)/LINK/X_DECLARE/EXCLUSIVE).asBool(true)),
+ autoDeleteQueue((Opt(address)/LINK/X_DECLARE/AUTO_DELETE).asBool(!(durable || reliable))),
+ exclusiveSubscription((Opt(address)/LINK/X_SUBSCRIBE/EXCLUSIVE).asBool(exclusiveQueue)),
+ alternateExchange((Opt(address)/LINK/X_DECLARE/ALTERNATE_EXCHANGE).str())
+{
+
+ if ((Opt(address)/LINK).hasKey(TIMEOUT)) {
+ const Variant* timeout = (Opt(address)/LINK/TIMEOUT).value;
+ if (timeout->asUint32()) queueOptions.setInt("qpid.auto_delete_timeout", timeout->asUint32());
+ } else if (durable && !AddressResolution::is_reliable(address) && !(Opt(address)/LINK/X_DECLARE).hasKey(AUTO_DELETE)) {
+ //if durable, not explicitly reliable, and auto-delete not
+ //explicitly set, then set a non-zero default for the
+ //autodelete timeout
+ queueOptions.setInt("qpid.auto_delete_timeout", 2*60);
+ }
+ (Opt(address)/LINK/X_DECLARE/ARGUMENTS).collect(queueOptions);
+ (Opt(address)/LINK/X_SUBSCRIBE/ARGUMENTS).collect(subscriptionOptions);
+ std::string selector = Opt(address)/LINK/SELECTOR;
+ if (!selector.empty()) queueOptions.setString(QPID_FILTER, selector);
+
+ if (!address.getSubject().empty()) bindSubject(address.getSubject());
+ else if (linkBindings.empty()) bindAll();
+}
+
+void Subscription::bindSubject(const std::string& subject)
+{
+ if (actualType == HEADERS_EXCHANGE) {
+ Binding b(name, queue, subject);
+ b.arguments.setString("qpid.subject", subject);
+ b.arguments.setString("x-match", "all");
+ bindings.push_back(b);
+ } else if (actualType == XML_EXCHANGE) {
+ Binding b(name, queue, subject);
+ std::string query = (boost::format("declare variable $qpid.subject external; $qpid.subject = '%1%'")
+ % subject).str();
+ b.arguments.setString("xquery", query);
+ bindings.push_back(b);
+ } else {
+ //Note: the fanout exchange doesn't support any filtering, so
+ //the subject is ignored in that case
+ add(name, subject);
+ }
+}
+
+void Subscription::bindAll()
+{
+ if (actualType == TOPIC_EXCHANGE) {
+ add(name, WILDCARD_ANY);
+ } else if (actualType == FANOUT_EXCHANGE) {
+ add(name, queue);
+ } else if (actualType == HEADERS_EXCHANGE) {
+ Binding b(name, queue, "match-all");
+ b.arguments.setString("x-match", "all");
+ bindings.push_back(b);
+ } else if (actualType == XML_EXCHANGE) {
+ Binding b(name, queue, EMPTY_STRING);
+ b.arguments.setString("xquery", "true()");
+ bindings.push_back(b);
+ } else {
+ add(name, EMPTY_STRING);
+ }
+}
+
+void Subscription::add(const std::string& exchange, const std::string& key)
+{
+ bindings.push_back(Binding(exchange, queue, key));
+}
+
+void Subscription::subscribe(qpid::client::AsyncSession& session, const std::string& destination)
+{
+ //create exchange if required and specified by policy:
+ checkCreate(session, FOR_RECEIVER);
+ checkAssert(session, FOR_RECEIVER);
+
+ //create subscription queue:
+ session.queueDeclare(arg::queue=queue, arg::exclusive=exclusiveQueue,
+ arg::autoDelete=autoDeleteQueue, arg::durable=durable,
+ arg::alternateExchange=alternateExchange,
+ arg::arguments=queueOptions);
+ //'default' binding:
+ bindings.bind(session);
+ //any explicit bindings:
+ linkBindings.setDefaultQueue(queue);
+ linkBindings.bind(session);
+ //subscribe to subscription queue:
+ AcceptMode accept = reliable ? ACCEPT_MODE_EXPLICIT : ACCEPT_MODE_NONE;
+ session.messageSubscribe(arg::queue=queue, arg::destination=destination,
+ arg::exclusive=exclusiveSubscription, arg::acceptMode=accept, arg::arguments=subscriptionOptions);
+}
+
+void Subscription::cancel(qpid::client::AsyncSession& session, const std::string& destination)
+{
+ linkBindings.unbind(session);
+ session.messageCancel(destination);
+ if (exclusiveQueue) session.queueDelete(arg::queue=queue, arg::ifUnused=true);
+ checkDelete(session, FOR_RECEIVER);
+}
+
+ExchangeSink::ExchangeSink(const Address& address) : Exchange(address) {}
+
+void ExchangeSink::declare(qpid::client::AsyncSession& session, const std::string&)
+{
+ checkCreate(session, FOR_SENDER);
+ checkAssert(session, FOR_SENDER);
+ linkBindings.bind(session);
+}
+
+void ExchangeSink::send(qpid::client::AsyncSession& session, const std::string&, OutgoingMessage& m)
+{
+ m.send(session, name, m.getSubject());
+}
+
+void ExchangeSink::cancel(qpid::client::AsyncSession& session, const std::string&)
+{
+ linkBindings.unbind(session);
+ checkDelete(session, FOR_SENDER);
+}
+
+QueueSink::QueueSink(const Address& address) : Queue(address) {}
+
+void QueueSink::declare(qpid::client::AsyncSession& session, const std::string&)
+{
+ checkCreate(session, FOR_SENDER);
+ checkAssert(session, FOR_SENDER);
+ linkBindings.bind(session);
+}
+void QueueSink::send(qpid::client::AsyncSession& session, const std::string&, OutgoingMessage& m)
+{
+ m.send(session, name);
+}
+
+void QueueSink::cancel(qpid::client::AsyncSession& session, const std::string&)
+{
+ linkBindings.unbind(session);
+ checkDelete(session, FOR_SENDER);
+}
+
+Address AddressResolution::convert(const qpid::framing::ReplyTo& rt)
+{
+ Address address;
+ if (rt.getExchange().empty()) {//if default exchange, treat as queue
+ if (!rt.getRoutingKey().empty()) {
+ address.setName(rt.getRoutingKey());
+ address.setType(QUEUE_ADDRESS);
+ }
+ } else {
+ address.setName(rt.getExchange());
+ address.setSubject(rt.getRoutingKey());
+ address.setType(TOPIC_ADDRESS);
+ }
+ return address;
+}
+
+qpid::framing::ReplyTo AddressResolution::convert(const Address& address)
+{
+ if (address.getType() == QUEUE_ADDRESS || address.getType().empty()) {
+ return ReplyTo(EMPTY_STRING, address.getName());
+ } else if (address.getType() == TOPIC_ADDRESS) {
+ return ReplyTo(address.getName(), address.getSubject());
+ } else {
+ QPID_LOG(notice, "Unrecognised type for reply-to: " << address.getType());
+ return ReplyTo(EMPTY_STRING, address.getName());//treat as queue
+ }
+}
+
+bool isQueue(qpid::client::Session session, const qpid::messaging::Address& address)
+{
+ return address.getType() == QUEUE_ADDRESS ||
+ (address.getType().empty() && session.queueQuery(address.getName()).getQueue() == address.getName());
+}
+
+bool isTopic(qpid::client::Session session, const qpid::messaging::Address& address)
+{
+ if (address.getType().empty()) {
+ return !session.exchangeQuery(address.getName()).getNotFound();
+ } else if (address.getType() == TOPIC_ADDRESS) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+Node::Node(const Address& address) : name(address.getName()),
+ createPolicy(getOption(address, CREATE)),
+ assertPolicy(getOption(address, ASSERT)),
+ deletePolicy(getOption(address, DELETE))
+{
+ nodeBindings.add((Opt(address)/NODE/X_BINDINGS).asList());
+ linkBindings.add((Opt(address)/LINK/X_BINDINGS).asList());
+}
+
+Queue::Queue(const Address& a) : Node(a),
+ durable(Opt(a)/NODE/DURABLE),
+ autoDelete(Opt(a)/NODE/X_DECLARE/AUTO_DELETE),
+ exclusive(Opt(a)/NODE/X_DECLARE/EXCLUSIVE),
+ alternateExchange((Opt(a)/NODE/X_DECLARE/ALTERNATE_EXCHANGE).str())
+{
+ (Opt(a)/NODE/X_DECLARE/ARGUMENTS).collect(arguments);
+ nodeBindings.setDefaultQueue(name);
+ linkBindings.setDefaultQueue(name);
+ if (qpid::messaging::AddressImpl::isTemporary(a) && createPolicy.isVoid()) {
+ createPolicy = "always";
+ Opt specified = Opt(a)/NODE/X_DECLARE;
+ if (!specified.hasKey(AUTO_DELETE)) autoDelete = true;
+ if (!specified.hasKey(EXCLUSIVE)) exclusive = true;
+ }
+}
+
+void Queue::checkCreate(qpid::client::AsyncSession& session, CheckMode mode)
+{
+ if (enabled(createPolicy, mode)) {
+ QPID_LOG(debug, "Auto-creating queue '" << name << "'");
+ try {
+ session.queueDeclare(arg::queue=name,
+ arg::durable=durable,
+ arg::autoDelete=autoDelete,
+ arg::exclusive=exclusive,
+ arg::alternateExchange=alternateExchange,
+ arg::arguments=arguments);
+ nodeBindings.bind(session);
+ session.sync();
+ } catch (const qpid::framing::ResourceLockedException& e) {
+ throw ResolutionError((boost::format("Creation failed for queue %1%; %2%") % name % e.what()).str());
+ } catch (const qpid::framing::NotAllowedException& e) {
+ throw ResolutionError((boost::format("Creation failed for queue %1%; %2%") % name % e.what()).str());
+ } catch (const qpid::framing::NotFoundException& e) {//may be thrown when creating bindings
+ throw ResolutionError((boost::format("Creation failed for queue %1%; %2%") % name % e.what()).str());
+ }
+ } else {
+ try {
+ sync(session).queueDeclare(arg::queue=name, arg::passive=true);
+ } catch (const qpid::framing::NotFoundException& /*e*/) {
+ throw NotFound((boost::format("Queue %1% does not exist") % name).str());
+ }
+ }
+}
+
+void Queue::checkDelete(qpid::client::AsyncSession& session, CheckMode mode)
+{
+ //Note: queue-delete will cause a session exception if the queue
+ //does not exist, the query here prevents obvious cases of this
+ //but there is a race whenever two deletions are made concurrently
+ //so careful use of the delete policy is recommended at present
+ if (enabled(deletePolicy, mode) && sync(session).queueQuery(name).getQueue() == name) {
+ QPID_LOG(debug, "Auto-deleting queue '" << name << "'");
+ sync(session).queueDelete(arg::queue=name);
+ }
+}
+
+void Queue::checkAssert(qpid::client::AsyncSession& session, CheckMode mode)
+{
+ if (enabled(assertPolicy, mode)) {
+ QueueQueryResult result = sync(session).queueQuery(name);
+ if (result.getQueue() != name) {
+ throw NotFound((boost::format("Queue not found: %1%") % name).str());
+ } else {
+ if (durable && !result.getDurable()) {
+ throw AssertionFailed((boost::format("Queue not durable: %1%") % name).str());
+ }
+ if (autoDelete && !result.getAutoDelete()) {
+ throw AssertionFailed((boost::format("Queue not set to auto-delete: %1%") % name).str());
+ }
+ if (exclusive && !result.getExclusive()) {
+ throw AssertionFailed((boost::format("Queue not exclusive: %1%") % name).str());
+ }
+ if (!alternateExchange.empty() && result.getAlternateExchange() != alternateExchange) {
+ throw AssertionFailed((boost::format("Alternate exchange does not match for %1%, expected %2%, got %3%")
+ % name % alternateExchange % result.getAlternateExchange()).str());
+ }
+ for (FieldTable::ValueMap::const_iterator i = arguments.begin(); i != arguments.end(); ++i) {
+ FieldTable::ValuePtr v = result.getArguments().get(i->first);
+ if (!v) {
+ throw AssertionFailed((boost::format("Option %1% not set for %2%") % i->first % name).str());
+ } else if (!areEquivalent(*i->second, *v)) {
+ throw AssertionFailed((boost::format("Option %1% does not match for %2%, expected %3%, got %4%")
+ % i->first % name % *(i->second) % *v).str());
+ }
+ }
+ nodeBindings.check(session);
+ }
+ }
+}
+
+Exchange::Exchange(const Address& a) : Node(a),
+ specifiedType((Opt(a)/NODE/X_DECLARE/TYPE).str()),
+ durable(Opt(a)/NODE/DURABLE),
+ autoDelete(Opt(a)/NODE/X_DECLARE/AUTO_DELETE),
+ alternateExchange((Opt(a)/NODE/X_DECLARE/ALTERNATE_EXCHANGE).str())
+{
+ (Opt(a)/NODE/X_DECLARE/ARGUMENTS).collect(arguments);
+ nodeBindings.setDefaultExchange(name);
+ linkBindings.setDefaultExchange(name);
+ if (qpid::messaging::AddressImpl::isTemporary(a) && createPolicy.isVoid()) {
+ createPolicy = "always";
+ if (!(Opt(a)/NODE/X_DECLARE).hasKey(AUTO_DELETE)) autoDelete = true;
+ }
+}
+
+bool Exchange::isReservedName()
+{
+ return name.find(PREFIX_AMQ) != std::string::npos || name.find(PREFIX_QPID) != std::string::npos;
+}
+
+void Exchange::checkCreate(qpid::client::AsyncSession& session, CheckMode mode)
+{
+ if (enabled(createPolicy, mode)) {
+ try {
+ if (isReservedName()) {
+ try {
+ sync(session).exchangeDeclare(arg::exchange=name, arg::passive=true);
+ } catch (const qpid::framing::NotFoundException& /*e*/) {
+ throw ResolutionError((boost::format("Cannot create exchange %1%; names beginning with \"amq.\" or \"qpid.\" are reserved.") % name).str());
+ }
+
+ } else {
+ std::string type = specifiedType;
+ if (type.empty()) type = TOPIC_EXCHANGE;
+ session.exchangeDeclare(arg::exchange=name,
+ arg::type=type,
+ arg::durable=durable,
+ arg::autoDelete=autoDelete,
+ arg::alternateExchange=alternateExchange,
+ arg::arguments=arguments);
+ }
+ nodeBindings.bind(session);
+ session.sync();
+ } catch (const qpid::framing::NotAllowedException& e) {
+ throw ResolutionError((boost::format("Create failed for exchange %1%; %2%") % name % e.what()).str());
+ } catch (const qpid::framing::NotFoundException& e) {//can be caused when creating bindings
+ throw ResolutionError((boost::format("Create failed for exchange %1%; %2%") % name % e.what()).str());
+ }
+ } else {
+ try {
+ sync(session).exchangeDeclare(arg::exchange=name, arg::passive=true);
+ } catch (const qpid::framing::NotFoundException& /*e*/) {
+ throw NotFound((boost::format("Exchange %1% does not exist") % name).str());
+ }
+ }
+}
+
+void Exchange::checkDelete(qpid::client::AsyncSession& session, CheckMode mode)
+{
+ //Note: exchange-delete will cause a session exception if the
+ //exchange does not exist, the query here prevents obvious cases
+ //of this but there is a race whenever two deletions are made
+ //concurrently so careful use of the delete policy is recommended
+ //at present
+ if (enabled(deletePolicy, mode) && !sync(session).exchangeQuery(name).getNotFound()) {
+ sync(session).exchangeDelete(arg::exchange=name);
+ }
+}
+
+void Exchange::checkAssert(qpid::client::AsyncSession& session, CheckMode mode)
+{
+ if (enabled(assertPolicy, mode)) {
+ ExchangeQueryResult result = sync(session).exchangeQuery(name);
+ if (result.getNotFound()) {
+ throw NotFound((boost::format("Exchange not found: %1%") % name).str());
+ } else {
+ if (specifiedType.size() && result.getType() != specifiedType) {
+ throw AssertionFailed((boost::format("Exchange %1% is of incorrect type, expected %2% but got %3%")
+ % name % specifiedType % result.getType()).str());
+ }
+ if (durable && !result.getDurable()) {
+ throw AssertionFailed((boost::format("Exchange not durable: %1%") % name).str());
+ }
+ //Note: Can't check auto-delete or alternate-exchange via
+ //exchange-query-result as these are not returned
+ //TODO: could use a passive declare to check alternate-exchange
+ for (FieldTable::ValueMap::const_iterator i = arguments.begin(); i != arguments.end(); ++i) {
+ FieldTable::ValuePtr v = result.getArguments().get(i->first);
+ if (!v) {
+ throw AssertionFailed((boost::format("Option %1% not set for %2%") % i->first % name).str());
+ } else if (!areEquivalent(*i->second, *v)) {
+ throw AssertionFailed((boost::format("Option %1% does not match for %2%, expected %3%, got %4%")
+ % i->first % name % *(i->second) % *v).str());
+ }
+ }
+ nodeBindings.check(session);
+ }
+ }
+}
+
+Binding::Binding(const Variant::Map& b) :
+ exchange((Opt(b)/EXCHANGE).str()),
+ queue((Opt(b)/QUEUE).str()),
+ key((Opt(b)/KEY).str())
+{
+ (Opt(b)/ARGUMENTS).collect(arguments);
+}
+
+Binding::Binding(const std::string& e, const std::string& q, const std::string& k) : exchange(e), queue(q), key(k) {}
+
+
+void Bindings::add(const Variant::List& list)
+{
+ for (Variant::List::const_iterator i = list.begin(); i != list.end(); ++i) {
+ push_back(Binding(i->asMap()));
+ }
+}
+
+void Bindings::setDefaultExchange(const std::string& exchange)
+{
+ for (Bindings::iterator i = begin(); i != end(); ++i) {
+ if (i->exchange.empty()) i->exchange = exchange;
+ }
+}
+
+void Bindings::setDefaultQueue(const std::string& queue)
+{
+ for (Bindings::iterator i = begin(); i != end(); ++i) {
+ if (i->queue.empty()) i->queue = queue;
+ }
+}
+
+void Bindings::bind(qpid::client::AsyncSession& session)
+{
+ for (Bindings::const_iterator i = begin(); i != end(); ++i) {
+ session.exchangeBind(arg::queue=i->queue,
+ arg::exchange=i->exchange,
+ arg::bindingKey=i->key,
+ arg::arguments=i->arguments);
+ }
+}
+
+void Bindings::unbind(qpid::client::AsyncSession& session)
+{
+ for (Bindings::const_iterator i = begin(); i != end(); ++i) {
+ session.exchangeUnbind(arg::queue=i->queue,
+ arg::exchange=i->exchange,
+ arg::bindingKey=i->key);
+ }
+}
+
+void Bindings::check(qpid::client::AsyncSession& session)
+{
+ for (Bindings::const_iterator i = begin(); i != end(); ++i) {
+ ExchangeBoundResult result = sync(session).exchangeBound(arg::queue=i->queue,
+ arg::exchange=i->exchange,
+ arg::bindingKey=i->key);
+ if (result.getQueueNotMatched() || result.getKeyNotMatched()) {
+ throw AssertionFailed((boost::format("No such binding [exchange=%1%, queue=%2%, key=%3%]")
+ % i->exchange % i->queue % i->key).str());
+ }
+ }
+}
+
+bool Node::enabled(const Variant& policy, CheckMode mode)
+{
+ bool result = false;
+ switch (mode) {
+ case FOR_RECEIVER:
+ result = in(policy, RECEIVER_MODES);
+ break;
+ case FOR_SENDER:
+ result = in(policy, SENDER_MODES);
+ break;
+ }
+ return result;
+}
+
+bool Node::createEnabled(const Address& address, CheckMode mode)
+{
+ const Variant& policy = getOption(address, CREATE);
+ return enabled(policy, mode);
+}
+
+void Node::convert(const Variant& options, FieldTable& arguments)
+{
+ if (!options.isVoid()) {
+ translate(options.asMap(), arguments);
+ }
+}
+std::vector<std::string> Node::RECEIVER_MODES = list_of<std::string>(ALWAYS) (RECEIVER);
+std::vector<std::string> Node::SENDER_MODES = list_of<std::string>(ALWAYS) (SENDER);
+
+Verifier::Verifier()
+{
+ defined[CREATE] = true;
+ defined[ASSERT] = true;
+ defined[DELETE] = true;
+ defined[MODE] = true;
+ Variant::Map node;
+ node[TYPE] = true;
+ node[DURABLE] = true;
+ node[X_DECLARE] = true;
+ node[X_BINDINGS] = true;
+ defined[NODE] = node;
+ Variant::Map link;
+ link[NAME] = true;
+ link[DURABLE] = true;
+ link[RELIABILITY] = true;
+ link[TIMEOUT] = true;
+ link[X_SUBSCRIBE] = true;
+ link[X_DECLARE] = true;
+ link[X_BINDINGS] = true;
+ link[SELECTOR] = true;
+ defined[LINK] = link;
+}
+void Verifier::verify(const Address& address) const
+{
+ verify(defined, address.getOptions());
+}
+
+void Verifier::verify(const Variant::Map& allowed, const Variant::Map& actual) const
+{
+ for (Variant::Map::const_iterator i = actual.begin(); i != actual.end(); ++i) {
+ Variant::Map::const_iterator option = allowed.find(i->first);
+ if (option == allowed.end()) {
+ throw AddressError((boost::format("Unrecognised option: %1%") % i->first).str());
+ } else if (option->second.getType() == qpid::types::VAR_MAP) {
+ verify(option->second.asMap(), i->second.asMap());
+ }
+ }
+}
+
+}}} // namespace qpid::client::amqp0_10
diff --git a/qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.h b/qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.h
new file mode 100644
index 0000000000..fc8f1a1d18
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/amqp0_10/AddressResolution.h
@@ -0,0 +1,64 @@
+#ifndef QPID_CLIENT_AMQP0_10_ADDRESSRESOLUTION_H
+#define QPID_CLIENT_AMQP0_10_ADDRESSRESOLUTION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/client/Session.h"
+
+namespace qpid {
+
+namespace framing{
+class ReplyTo;
+}
+
+namespace messaging {
+class Address;
+}
+
+namespace client {
+namespace amqp0_10 {
+
+class MessageSource;
+class MessageSink;
+
+/**
+ * Maps from a generic Address and optional Filter to an AMQP 0-10
+ * MessageSource which will then be used by a ReceiverImpl instance
+ * created for the address.
+ */
+class AddressResolution
+{
+ public:
+ std::auto_ptr<MessageSource> resolveSource(qpid::client::Session session,
+ const qpid::messaging::Address& address);
+
+ std::auto_ptr<MessageSink> resolveSink(qpid::client::Session session,
+ const qpid::messaging::Address& address);
+
+ static qpid::messaging::Address convert(const qpid::framing::ReplyTo&);
+ static qpid::framing::ReplyTo convert(const qpid::messaging::Address&);
+ static bool is_unreliable(const qpid::messaging::Address& address);
+ static bool is_reliable(const qpid::messaging::Address& address);
+ private:
+};
+}}} // namespace qpid::client::amqp0_10
+
+#endif /*!QPID_CLIENT_AMQP0_10_ADDRESSRESOLUTION_H*/
diff --git a/qpid/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp b/qpid/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp
new file mode 100644
index 0000000000..11ef06e517
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/amqp0_10/ConnectionImpl.cpp
@@ -0,0 +1,404 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "ConnectionImpl.h"
+#include "SessionImpl.h"
+#include "qpid/messaging/exceptions.h"
+#include "qpid/messaging/Session.h"
+#include "qpid/messaging/PrivateImplRef.h"
+#include "qpid/framing/Uuid.h"
+#include "qpid/log/Statement.h"
+#include "qpid/Url.h"
+#include "qpid/amqp_0_10/Codecs.h"
+#include <boost/intrusive_ptr.hpp>
+#include <vector>
+#include <sstream>
+#include <limits>
+
+namespace qpid {
+namespace client {
+namespace amqp0_10 {
+
+using qpid::types::Variant;
+using qpid::types::VAR_LIST;
+using qpid::framing::Uuid;
+
+namespace {
+
+const std::string TCP("tcp");
+const std::string COLON(":");
+double FOREVER(std::numeric_limits<double>::max());
+
+// Time values in seconds can be specified as integer or floating point values.
+double timeValue(const Variant& value) {
+ if (types::isIntegerType(value.getType()))
+ return double(value.asInt64());
+ return value.asDouble();
+}
+
+void merge(const std::string& value, std::vector<std::string>& list) {
+ if (std::find(list.begin(), list.end(), value) == list.end())
+ list.push_back(value);
+}
+
+void merge(const Variant::List& from, std::vector<std::string>& to)
+{
+ for (Variant::List::const_iterator i = from.begin(); i != from.end(); ++i)
+ merge(i->asString(), to);
+}
+
+std::string asString(const std::vector<std::string>& v) {
+ std::stringstream os;
+ os << "[";
+ for(std::vector<std::string>::const_iterator i = v.begin(); i != v.end(); ++i ) {
+ if (i != v.begin()) os << ", ";
+ os << *i;
+ }
+ os << "]";
+ return os.str();
+}
+
+bool expired(const sys::AbsTime& start, double timeout)
+{
+ if (timeout == 0) return true;
+ if (timeout == FOREVER) return false;
+ sys::Duration used(start, sys::now());
+ sys::Duration allowed((int64_t)(timeout*sys::TIME_SEC));
+ return allowed < used;
+}
+
+} // namespace
+
+ConnectionImpl::ConnectionImpl(const std::string& url, const Variant::Map& options) :
+ replaceUrls(false), autoReconnect(false), timeout(FOREVER), limit(-1),
+ minReconnectInterval(0.001), maxReconnectInterval(2),
+ retries(0), reconnectOnLimitExceeded(true), disableAutoDecode(false)
+{
+ setOptions(options);
+ urls.insert(urls.begin(), url);
+}
+
+void ConnectionImpl::setOptions(const Variant::Map& options)
+{
+ for (Variant::Map::const_iterator i = options.begin(); i != options.end(); ++i) {
+ setOption(i->first, i->second);
+ }
+}
+
+void ConnectionImpl::setOption(const std::string& name, const Variant& value)
+{
+ sys::Mutex::ScopedLock l(lock);
+ if (name == "reconnect") {
+ autoReconnect = value;
+ } else if (name == "reconnect-timeout" || name == "reconnect_timeout") {
+ timeout = timeValue(value);
+ } else if (name == "reconnect-limit" || name == "reconnect_limit") {
+ limit = value;
+ } else if (name == "reconnect-interval" || name == "reconnect_interval") {
+ maxReconnectInterval = minReconnectInterval = timeValue(value);
+ } else if (name == "reconnect-interval-min" || name == "reconnect_interval_min") {
+ minReconnectInterval = timeValue(value);
+ } else if (name == "reconnect-interval-max" || name == "reconnect_interval_max") {
+ maxReconnectInterval = timeValue(value);
+ } else if (name == "reconnect-urls-replace" || name == "reconnect_urls_replace") {
+ replaceUrls = value.asBool();
+ } else if (name == "reconnect-urls" || name == "reconnect_urls") {
+ if (replaceUrls) urls.clear();
+ if (value.getType() == VAR_LIST) {
+ merge(value.asList(), urls);
+ } else {
+ merge(value.asString(), urls);
+ }
+ } else if (name == "username") {
+ settings.username = value.asString();
+ } else if (name == "password") {
+ settings.password = value.asString();
+ } else if (name == "sasl-mechanism" || name == "sasl_mechanism" ||
+ name == "sasl-mechanisms" || name == "sasl_mechanisms") {
+ settings.mechanism = value.asString();
+ } else if (name == "sasl-service" || name == "sasl_service") {
+ settings.service = value.asString();
+ } else if (name == "sasl-min-ssf" || name == "sasl_min_ssf") {
+ settings.minSsf = value;
+ } else if (name == "sasl-max-ssf" || name == "sasl_max_ssf") {
+ settings.maxSsf = value;
+ } else if (name == "heartbeat") {
+ settings.heartbeat = value;
+ } else if (name == "tcp-nodelay" || name == "tcp_nodelay") {
+ settings.tcpNoDelay = value;
+ } else if (name == "locale") {
+ settings.locale = value.asString();
+ } else if (name == "max-channels" || name == "max_channels") {
+ settings.maxChannels = value;
+ } else if (name == "max-frame-size" || name == "max_frame_size") {
+ settings.maxFrameSize = value;
+ } else if (name == "bounds") {
+ settings.bounds = value;
+ } else if (name == "transport") {
+ settings.protocol = value.asString();
+ } else if (name == "ssl-cert-name" || name == "ssl_cert_name") {
+ settings.sslCertName = value.asString();
+ } else if (name == "ssl-ignore-hostname-verification-failure" || name == "ssl_ignore_hostname_verification_failure") {
+ settings.sslIgnoreHostnameVerificationFailure = value;
+ } else if (name == "x-reconnect-on-limit-exceeded" || name == "x_reconnect_on_limit_exceeded") {
+ reconnectOnLimitExceeded = value;
+ } else if (name == "client-properties" || name == "client_properties") {
+ amqp_0_10::translate(value.asMap(), settings.clientProperties);
+ } else if (name == "disable-auto-decode" || name == "disable_auto_decode") {
+ disableAutoDecode = value;
+ } else {
+ throw qpid::messaging::MessagingException(QPID_MSG("Invalid option: " << name << " not recognised"));
+ }
+}
+
+
+void ConnectionImpl::close()
+{
+ while(true) {
+ messaging::Session session;
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ if (sessions.empty()) break;
+ session = sessions.begin()->second;
+ }
+ session.close();
+ }
+ detach();
+}
+
+void ConnectionImpl::detach()
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ connection.close();
+}
+
+bool ConnectionImpl::isOpen() const
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ return connection.isOpen();
+}
+
+boost::intrusive_ptr<SessionImpl> getImplPtr(qpid::messaging::Session& session)
+{
+ return boost::dynamic_pointer_cast<SessionImpl>(
+ qpid::messaging::PrivateImplRef<qpid::messaging::Session>::get(session)
+ );
+}
+
+void ConnectionImpl::closed(SessionImpl& s)
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ for (Sessions::iterator i = sessions.begin(); i != sessions.end(); ++i) {
+ if (getImplPtr(i->second).get() == &s) {
+ sessions.erase(i);
+ break;
+ }
+ }
+}
+
+qpid::messaging::Session ConnectionImpl::getSession(const std::string& name) const
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ Sessions::const_iterator i = sessions.find(name);
+ if (i == sessions.end()) {
+ throw qpid::messaging::KeyError("No such session: " + name);
+ } else {
+ return i->second;
+ }
+}
+
+qpid::messaging::Session ConnectionImpl::newSession(bool transactional, const std::string& n)
+{
+ std::string name = n.empty() ? Uuid(true).str() : n;
+ qpid::messaging::Session impl(new SessionImpl(*this, transactional));
+ while (true) {
+ try {
+ getImplPtr(impl)->setSession(connection.newSession(name));
+ qpid::sys::Mutex::ScopedLock l(lock);
+ sessions[name] = impl;
+ break;
+ } catch (const qpid::TransportFailure&) {
+ reopen();
+ } catch (const qpid::SessionException& e) {
+ SessionImpl::rethrow(e);
+ } catch (const std::exception& e) {
+ throw qpid::messaging::MessagingException(e.what());
+ }
+ }
+ return impl;
+}
+
+void ConnectionImpl::open()
+{
+ qpid::sys::AbsTime start = qpid::sys::now();
+ qpid::sys::ScopedLock<qpid::sys::Semaphore> l(semaphore);
+ try {
+ if (!connection.isOpen()) connect(start);
+ }
+ catch (const types::Exception&) { throw; }
+ catch (const qpid::Exception& e) { throw messaging::ConnectionError(e.what()); }
+}
+
+void ConnectionImpl::reopen()
+{
+ if (!autoReconnect) {
+ throw qpid::messaging::TransportFailure("Failed to connect (reconnect disabled)");
+ }
+ open();
+}
+
+
+void ConnectionImpl::connect(const qpid::sys::AbsTime& started)
+{
+ QPID_LOG(debug, "Starting connection, urls=" << asString(urls));
+ for (double i = minReconnectInterval; !tryConnect(); i = std::min(i*2, maxReconnectInterval)) {
+ if (!autoReconnect) {
+ throw qpid::messaging::TransportFailure("Failed to connect (reconnect disabled)");
+ }
+ if (limit >= 0 && retries++ >= limit) {
+ throw qpid::messaging::TransportFailure("Failed to connect within reconnect limit");
+ }
+ if (expired(started, timeout)) {
+ throw qpid::messaging::TransportFailure("Failed to connect within reconnect timeout");
+ }
+ QPID_LOG(debug, "Connection retry in " << i*1000*1000 << " microseconds, urls="
+ << asString(urls));
+ qpid::sys::usleep(int64_t(i*1000*1000)); // Sleep in microseconds.
+ }
+ QPID_LOG(debug, "Connection successful, urls=" << asString(urls));
+ retries = 0;
+}
+
+void ConnectionImpl::mergeUrls(const std::vector<Url>& more, const sys::Mutex::ScopedLock&) {
+ for (std::vector<Url>::const_iterator i = more.begin(); i != more.end(); ++i)
+ merge(i->str(), urls);
+ QPID_LOG(debug, "Added known-hosts, reconnect-urls=" << asString(urls));
+}
+
+bool ConnectionImpl::tryConnect()
+{
+ sys::Mutex::ScopedLock l(lock);
+ for (std::vector<std::string>::const_iterator i = urls.begin(); i != urls.end(); ++i) {
+ try {
+ QPID_LOG(info, "Trying to connect to " << *i << "...");
+ Url url(*i, settings.protocol.size() ? settings.protocol : TCP);
+ if (url.getUser().size()) settings.username = url.getUser();
+ if (url.getPass().size()) settings.password = url.getPass();
+ connection.open(url, settings);
+ QPID_LOG(info, "Connected to " << *i);
+ mergeUrls(connection.getInitialBrokers(), l);
+ return resetSessions(l);
+ } catch (const qpid::ProtocolVersionError& e) {
+ throw qpid::messaging::ProtocolVersionError("AMQP 0-10 not supported");
+ } catch (const qpid::TransportFailure& e) {
+ QPID_LOG(info, "Failed to connect to " << *i << ": " << e.what());
+ }
+ }
+ return false;
+}
+
+bool ConnectionImpl::resetSessions(const sys::Mutex::ScopedLock& )
+{
+ try {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ for (Sessions::iterator i = sessions.begin(); i != sessions.end(); ++i) {
+ if (!getImplPtr(i->second)->isTransactional()) {
+ getImplPtr(i->second)->setSession(connection.newSession(i->first));
+ }
+ }
+ return true;
+ } catch (const qpid::TransportFailure& e) {
+ QPID_LOG(debug, "Connection Failed to re-initialize sessions: " << e.what());
+ return false;
+ } catch (const qpid::framing::ResourceLimitExceededException& e) {
+ if (reconnectOnLimitExceeded) {
+ QPID_LOG(debug, "Detaching and reconnecting due to: " << e.what());
+ detach();
+ return false;
+ } else {
+ throw qpid::messaging::TargetCapacityExceeded(e.what());
+ }
+ }
+}
+
+bool ConnectionImpl::backoff()
+{
+ if (reconnectOnLimitExceeded) {
+ detach();
+ open();
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void ConnectionImpl::reconnect(const std::string& u)
+{
+ sys::Mutex::ScopedLock l(lock);
+ try {
+ QPID_LOG(info, "Trying to connect to " << u << "...");
+ Url url(u, settings.protocol.size() ? settings.protocol : TCP);
+ if (url.getUser().size()) settings.username = url.getUser();
+ if (url.getPass().size()) settings.password = url.getPass();
+ connection.open(url, settings);
+ QPID_LOG(info, "Connected to " << u);
+ mergeUrls(connection.getInitialBrokers(), l);
+ if (!resetSessions(l)) throw qpid::messaging::TransportFailure("Could not re-establish sessions");
+ } catch (const qpid::TransportFailure& e) {
+ QPID_LOG(info, "Failed to connect to " << u << ": " << e.what());
+ throw qpid::messaging::TransportFailure(e.what());
+ } catch (const std::exception& e) {
+ QPID_LOG(info, "Error while connecting to " << u << ": " << e.what());
+ throw qpid::messaging::MessagingException(e.what());
+ }
+}
+
+void ConnectionImpl::reconnect()
+{
+ if (!tryConnect()) {
+ throw qpid::messaging::TransportFailure("Could not reconnect");
+ }
+}
+std::string ConnectionImpl::getUrl() const
+{
+ if (isOpen()) {
+ std::stringstream u;
+ u << connection.getNegotiatedSettings().protocol << COLON << connection.getNegotiatedSettings().host << COLON << connection.getNegotiatedSettings().port;
+ return u.str();
+ } else {
+ return std::string();
+ }
+}
+
+std::string ConnectionImpl::getAuthenticatedUsername()
+{
+ return connection.getNegotiatedSettings().username;
+}
+
+bool ConnectionImpl::getAutoDecode() const
+{
+ return !disableAutoDecode;
+}
+bool ConnectionImpl::getAutoReconnect() const
+{
+ return autoReconnect;
+}
+
+}}} // namespace qpid::client::amqp0_10
diff --git a/qpid/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h b/qpid/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h
new file mode 100644
index 0000000000..bf8a759107
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/amqp0_10/ConnectionImpl.h
@@ -0,0 +1,88 @@
+#ifndef QPID_CLIENT_AMQP0_10_CONNECTIONIMPL_H
+#define QPID_CLIENT_AMQP0_10_CONNECTIONIMPL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/messaging/ConnectionImpl.h"
+#include "qpid/types/Variant.h"
+#include "qpid/client/Connection.h"
+#include "qpid/client/ConnectionSettings.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/Semaphore.h"
+#include <map>
+#include <vector>
+
+namespace qpid {
+struct Url;
+
+namespace client {
+namespace amqp0_10 {
+
+class SessionImpl;
+
+class ConnectionImpl : public qpid::messaging::ConnectionImpl
+{
+ public:
+ ConnectionImpl(const std::string& url, const qpid::types::Variant::Map& options);
+ void open();
+ void reopen();
+ bool isOpen() const;
+ void close();
+ qpid::messaging::Session newSession(bool transactional, const std::string& name);
+ qpid::messaging::Session getSession(const std::string& name) const;
+ void closed(SessionImpl&);
+ void detach();
+ void setOption(const std::string& name, const qpid::types::Variant& value);
+ bool backoff();
+ std::string getAuthenticatedUsername();
+ void reconnect(const std::string& url);
+ void reconnect();
+ std::string getUrl() const;
+ bool getAutoDecode() const;
+ bool getAutoReconnect() const;
+ private:
+ typedef std::map<std::string, qpid::messaging::Session> Sessions;
+
+ mutable qpid::sys::Mutex lock;//used to protect data structures
+ qpid::sys::Semaphore semaphore;//used to coordinate reconnection
+ Sessions sessions;
+ qpid::client::Connection connection;
+ bool replaceUrls; // Replace rather than merging with reconnect-urls
+ std::vector<std::string> urls;
+ qpid::client::ConnectionSettings settings;
+ bool autoReconnect;
+ double timeout;
+ int32_t limit;
+ double minReconnectInterval;
+ double maxReconnectInterval;
+ int32_t retries;
+ bool reconnectOnLimitExceeded;
+ bool disableAutoDecode;
+
+ void setOptions(const qpid::types::Variant::Map& options);
+ void connect(const qpid::sys::AbsTime& started);
+ bool tryConnect();
+ bool resetSessions(const sys::Mutex::ScopedLock&); // dummy parameter indicates call with lock held.
+ void mergeUrls(const std::vector<Url>& more, const sys::Mutex::ScopedLock&);
+};
+}}} // namespace qpid::client::amqp0_10
+
+#endif /*!QPID_CLIENT_AMQP0_10_CONNECTIONIMPL_H*/
diff --git a/qpid/cpp/src/qpid/client/amqp0_10/IncomingMessages.cpp b/qpid/cpp/src/qpid/client/amqp0_10/IncomingMessages.cpp
new file mode 100644
index 0000000000..2ca2c85c64
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/amqp0_10/IncomingMessages.cpp
@@ -0,0 +1,466 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/client/amqp0_10/IncomingMessages.h"
+#include "qpid/client/amqp0_10/AddressResolution.h"
+#include "qpid/amqp_0_10/Codecs.h"
+#include "qpid/client/SessionImpl.h"
+#include "qpid/client/SessionBase_0_10Access.h"
+#include "qpid/log/Statement.h"
+#include "qpid/messaging/Address.h"
+#include "qpid/messaging/Duration.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/messaging/MessageImpl.h"
+#include "qpid/types/Variant.h"
+#include "qpid/framing/DeliveryProperties.h"
+#include "qpid/framing/FrameSet.h"
+#include "qpid/framing/MessageProperties.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qpid/framing/enum.h"
+#include <algorithm>
+
+namespace qpid {
+namespace client {
+namespace amqp0_10 {
+
+using namespace qpid::framing;
+using namespace qpid::framing::message;
+using namespace qpid::amqp_0_10;
+using qpid::sys::AbsTime;
+using qpid::sys::Duration;
+using qpid::messaging::MessageImplAccess;
+using qpid::types::Variant;
+
+namespace {
+const std::string EMPTY_STRING;
+
+
+struct GetNone : IncomingMessages::Handler
+{
+ bool accept(IncomingMessages::MessageTransfer&) { return false; }
+};
+
+struct GetAny : IncomingMessages::Handler
+{
+ bool accept(IncomingMessages::MessageTransfer& transfer)
+ {
+ transfer.retrieve(0);
+ return true;
+ }
+};
+
+struct MatchAndTrack
+{
+ const std::string destination;
+ SequenceSet ids;
+
+ MatchAndTrack(const std::string& d) : destination(d) {}
+
+ bool operator()(boost::shared_ptr<qpid::framing::FrameSet> command)
+ {
+ if (command->as<MessageTransferBody>()->getDestination() == destination) {
+ ids.add(command->getId());
+ return true;
+ } else {
+ return false;
+ }
+ }
+};
+
+struct Match
+{
+ const std::string destination;
+ uint32_t matched;
+
+ Match(const std::string& d) : destination(d), matched(0) {}
+
+ bool operator()(boost::shared_ptr<qpid::framing::FrameSet> command)
+ {
+ if (command->as<MessageTransferBody>()->getDestination() == destination) {
+ ++matched;
+ return true;
+ } else {
+ return false;
+ }
+ }
+};
+
+struct ScopedRelease
+{
+ bool& flag;
+ qpid::sys::Monitor& lock;
+
+ ScopedRelease(bool& f, qpid::sys::Monitor& l) : flag(f), lock(l) {}
+ ~ScopedRelease()
+ {
+ sys::Monitor::ScopedLock l(lock);
+ flag = false;
+ lock.notifyAll();
+ }
+};
+}
+
+IncomingMessages::IncomingMessages() : inUse(false) {}
+
+void IncomingMessages::setSession(qpid::client::AsyncSession s)
+{
+ sys::Mutex::ScopedLock l(lock);
+ session = s;
+ incoming = SessionBase_0_10Access(session).get()->getDemux().getDefault();
+ acceptTracker.reset();
+}
+
+namespace {
+qpid::sys::Duration get_duration(qpid::sys::Duration timeout, qpid::sys::AbsTime deadline)
+{
+ if (timeout == qpid::sys::TIME_INFINITE) {
+ return qpid::sys::TIME_INFINITE;
+ } else {
+ return std::max(qpid::sys::Duration(0), qpid::sys::Duration(AbsTime::now(), deadline));
+ }
+}
+}
+
+bool IncomingMessages::get(Handler& handler, qpid::sys::Duration timeout)
+{
+ sys::Mutex::ScopedLock l(lock);
+ AbsTime deadline(AbsTime::now(), timeout);
+ do {
+ //search through received list for any transfer of interest:
+ for (FrameSetQueue::iterator i = received.begin(); i != received.end();)
+ {
+ MessageTransfer transfer(*i, *this);
+ if (transfer.checkExpired()) {
+ i = received.erase(i);
+ } else if (handler.accept(transfer)) {
+ received.erase(i);
+ return true;
+ } else {
+ ++i;
+ }
+ }
+ if (inUse) {
+ //someone is already waiting on the incoming session queue, wait for them to finish
+ lock.wait(deadline);
+ } else {
+ inUse = true;
+ ScopedRelease release(inUse, lock);
+ sys::Mutex::ScopedUnlock l(lock);
+ //wait for suitable new message to arrive
+ switch (process(&handler, get_duration(timeout, deadline))) {
+ case OK:
+ return true;
+ case CLOSED:
+ return false;
+ case EMPTY:
+ break;
+ }
+ }
+ if (handler.isClosed()) throw qpid::messaging::ReceiverError("Receiver has been closed");
+ } while (AbsTime::now() < deadline);
+ return false;
+}
+namespace {
+struct Wakeup : public qpid::types::Exception {};
+}
+
+void IncomingMessages::wakeup()
+{
+ sys::Mutex::ScopedLock l(lock);
+ incoming->close(qpid::sys::ExceptionHolder(new Wakeup()));
+ lock.notifyAll();
+}
+
+bool IncomingMessages::getNextDestination(std::string& destination, qpid::sys::Duration timeout)
+{
+ sys::Mutex::ScopedLock l(lock);
+ AbsTime deadline(AbsTime::now(), timeout);
+ while (received.empty()) {
+ if (inUse) {
+ //someone is already waiting on the sessions incoming queue
+ lock.wait(deadline);
+ } else {
+ inUse = true;
+ ScopedRelease release(inUse, lock);
+ sys::Mutex::ScopedUnlock l(lock);
+ //wait for an incoming message
+ wait(get_duration(timeout, deadline));
+ }
+ if (!(AbsTime::now() < deadline)) break;
+ }
+ if (!received.empty()) {
+ destination = received.front()->as<MessageTransferBody>()->getDestination();
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void IncomingMessages::accept()
+{
+ sys::Mutex::ScopedLock l(lock);
+ acceptTracker.accept(session);
+}
+
+void IncomingMessages::accept(qpid::framing::SequenceNumber id, bool cumulative)
+{
+ sys::Mutex::ScopedLock l(lock);
+ acceptTracker.accept(id, session, cumulative);
+}
+
+
+void IncomingMessages::releaseAll()
+{
+ {
+ //first process any received messages...
+ sys::Mutex::ScopedLock l(lock);
+ while (!received.empty()) {
+ retrieve(received.front(), 0);
+ received.pop_front();
+ }
+ }
+ //then pump out any available messages from incoming queue...
+ GetAny handler;
+ while (process(&handler, 0) == OK) ;
+ //now release all messages
+ sys::Mutex::ScopedLock l(lock);
+ acceptTracker.release(session);
+}
+
+void IncomingMessages::releasePending(const std::string& destination)
+{
+ //first pump all available messages from incoming to received...
+ while (process(0, 0) == OK) ;
+
+ //now remove all messages for this destination from received list, recording their ids...
+ sys::Mutex::ScopedLock l(lock);
+ MatchAndTrack match(destination);
+ for (FrameSetQueue::iterator i = received.begin(); i != received.end(); i = match(*i) ? received.erase(i) : ++i) ;
+ //now release those messages
+ session.messageRelease(match.ids);
+}
+
+bool IncomingMessages::pop(FrameSet::shared_ptr& content, qpid::sys::Duration timeout)
+{
+ try {
+ return incoming->pop(content, timeout);
+ } catch (const Wakeup&) {
+ incoming->open();
+ return false;
+ }
+}
+
+/**
+ * Get a frameset that is accepted by the specified handler from
+ * session queue, waiting for up to the specified duration and
+ * returning true if this could be achieved, false otherwise. Messages
+ * that are not accepted by the handler are pushed onto received queue
+ * for later retrieval.
+ */
+IncomingMessages::ProcessState IncomingMessages::process(Handler* handler, qpid::sys::Duration duration)
+{
+ AbsTime deadline(AbsTime::now(), duration);
+ FrameSet::shared_ptr content;
+ try {
+ for (Duration timeout = duration; pop(content, timeout); timeout = Duration(AbsTime::now(), deadline)) {
+ if (content->isA<MessageTransferBody>()) {
+ MessageTransfer transfer(content, *this);
+ if (transfer.checkExpired()) {
+ QPID_LOG(debug, "Expired received transfer: " << *content->getMethod());
+ } else if (handler && handler->accept(transfer)) {
+ QPID_LOG(debug, "Delivered " << *content->getMethod() << " "
+ << *content->getHeaders());
+ return OK;
+ } else {
+ //received message for another destination, keep for later
+ QPID_LOG(debug, "Pushed " << *content->getMethod() << " to received queue");
+ sys::Mutex::ScopedLock l(lock);
+ received.push_back(content);
+ lock.notifyAll();
+ }
+ } else {
+ //TODO: handle other types of commands (e.g. message-accept, message-flow etc)
+ }
+ }
+ }
+ catch (const qpid::ClosedException&) { return CLOSED; }
+ return EMPTY;
+}
+
+bool IncomingMessages::wait(qpid::sys::Duration duration)
+{
+ AbsTime deadline(AbsTime::now(), duration);
+ FrameSet::shared_ptr content;
+ for (Duration timeout = duration; pop(content, timeout); timeout = Duration(AbsTime::now(), deadline)) {
+ if (content->isA<MessageTransferBody>()) {
+ QPID_LOG(debug, "Pushed " << *content->getMethod() << " to received queue");
+ sys::Mutex::ScopedLock l(lock);
+ received.push_back(content);
+ lock.notifyAll();
+ return true;
+ } else {
+ //TODO: handle other types of commands (e.g. message-accept, message-flow etc)
+ }
+ }
+ return false;
+}
+
+uint32_t IncomingMessages::pendingAccept()
+{
+ sys::Mutex::ScopedLock l(lock);
+ return acceptTracker.acceptsPending();
+}
+uint32_t IncomingMessages::pendingAccept(const std::string& destination)
+{
+ sys::Mutex::ScopedLock l(lock);
+ return acceptTracker.acceptsPending(destination);
+}
+
+uint32_t IncomingMessages::available()
+{
+ //first pump all available messages from incoming to received...
+ while (process(0, 0) == OK) {}
+ //return the count of received messages
+ sys::Mutex::ScopedLock l(lock);
+ return received.size();
+}
+
+uint32_t IncomingMessages::available(const std::string& destination)
+{
+ //first pump all available messages from incoming to received...
+ while (process(0, 0) == OK) {}
+
+ //count all messages for this destination from received list
+ sys::Mutex::ScopedLock l(lock);
+ return std::for_each(received.begin(), received.end(), Match(destination)).matched;
+}
+
+void populate(qpid::messaging::Message& message, FrameSet& command);
+
+/**
+ * Called when message is retrieved; records retrieval for subsequent
+ * acceptance, marks the command as completed and converts command to
+ * message if message is required
+ */
+void IncomingMessages::retrieve(FrameSetPtr command, qpid::messaging::Message* message)
+{
+ if (message) {
+ populate(*message, *command);
+ }
+ const MessageTransferBody* transfer = command->as<MessageTransferBody>();
+ if (transfer->getAcceptMode() == ACCEPT_MODE_EXPLICIT) {
+ sys::Mutex::ScopedLock l(lock);
+ acceptTracker.delivered(transfer->getDestination(), command->getId());
+ }
+ session.markCompleted(command->getId(), false, false);
+}
+
+IncomingMessages::MessageTransfer::MessageTransfer(FrameSetPtr c, IncomingMessages& p) : content(c), parent(p) {}
+
+const std::string& IncomingMessages::MessageTransfer::getDestination()
+{
+ return content->as<MessageTransferBody>()->getDestination();
+}
+void IncomingMessages::MessageTransfer::retrieve(qpid::messaging::Message* message)
+{
+ parent.retrieve(content, message);
+}
+
+bool IncomingMessages::MessageTransfer::checkExpired()
+{
+ if (content->hasExpired()) {
+ retrieve(0);
+ parent.accept(content->getId(), false);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+namespace {
+//TODO: unify conversion to and from 0-10 message that is currently
+//split between IncomingMessages and OutgoingMessage
+const std::string SUBJECT("qpid.subject");
+
+const std::string X_APP_ID("x-amqp-0-10.app-id");
+const std::string X_ROUTING_KEY("x-amqp-0-10.routing-key");
+const std::string X_CONTENT_ENCODING("x-amqp-0-10.content-encoding");
+const std::string X_TIMESTAMP("x-amqp-0-10.timestamp");
+}
+
+void populateHeaders(qpid::messaging::Message& message,
+ const DeliveryProperties* deliveryProperties,
+ const MessageProperties* messageProperties)
+{
+ if (deliveryProperties) {
+ message.setTtl(qpid::messaging::Duration(deliveryProperties->getTtl()));
+ message.setDurable(deliveryProperties->getDeliveryMode() == DELIVERY_MODE_PERSISTENT);
+ message.setPriority(deliveryProperties->getPriority());
+ message.setRedelivered(deliveryProperties->getRedelivered());
+ }
+ if (messageProperties) {
+ message.setContentType(messageProperties->getContentType());
+ if (messageProperties->hasReplyTo()) {
+ message.setReplyTo(AddressResolution::convert(messageProperties->getReplyTo()));
+ }
+ message.setSubject(messageProperties->getApplicationHeaders().getAsString(SUBJECT));
+ message.getProperties().clear();
+ translate(messageProperties->getApplicationHeaders(), message.getProperties());
+ message.setCorrelationId(messageProperties->getCorrelationId());
+ message.setUserId(messageProperties->getUserId());
+ if (messageProperties->hasMessageId()) {
+ message.setMessageId(messageProperties->getMessageId().str());
+ }
+ //expose 0-10 specific items through special properties:
+ // app-id, content-encoding
+ if (messageProperties->hasAppId()) {
+ message.getProperties()[X_APP_ID] = messageProperties->getAppId();
+ }
+ if (messageProperties->hasContentEncoding()) {
+ message.getProperties()[X_CONTENT_ENCODING] = messageProperties->getContentEncoding();
+ }
+ // routing-key, timestamp, others?
+ if (deliveryProperties && deliveryProperties->hasRoutingKey()) {
+ message.getProperties()[X_ROUTING_KEY] = deliveryProperties->getRoutingKey();
+ }
+ if (deliveryProperties && deliveryProperties->hasTimestamp()) {
+ message.getProperties()[X_TIMESTAMP] = deliveryProperties->getTimestamp();
+ }
+ }
+}
+
+void populateHeaders(qpid::messaging::Message& message, const AMQHeaderBody* headers)
+{
+ populateHeaders(message, headers->get<DeliveryProperties>(), headers->get<MessageProperties>());
+}
+
+void populate(qpid::messaging::Message& message, FrameSet& command)
+{
+ //need to be able to link the message back to the transfer it was delivered by
+ //e.g. for rejecting.
+ MessageImplAccess::get(message).setInternalId(command.getId());
+
+ message.setContent(command.getContent());
+
+ populateHeaders(message, command.getHeaders());
+}
+
+
+}}} // namespace qpid::client::amqp0_10
diff --git a/qpid/cpp/src/qpid/client/amqp0_10/IncomingMessages.h b/qpid/cpp/src/qpid/client/amqp0_10/IncomingMessages.h
new file mode 100644
index 0000000000..4c9ee68ece
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/amqp0_10/IncomingMessages.h
@@ -0,0 +1,108 @@
+#ifndef QPID_CLIENT_AMQP0_10_INCOMINGMESSAGES_H
+#define QPID_CLIENT_AMQP0_10_INCOMINGMESSAGES_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <string>
+#include <boost/shared_ptr.hpp>
+#include "qpid/client/AsyncSession.h"
+#include "qpid/framing/SequenceSet.h"
+#include "qpid/sys/BlockingQueue.h"
+#include "qpid/sys/Time.h"
+#include "qpid/client/amqp0_10/AcceptTracker.h"
+
+namespace qpid {
+
+namespace framing{
+class FrameSet;
+}
+
+namespace messaging {
+class Message;
+}
+
+namespace client {
+namespace amqp0_10 {
+
+/**
+ * Queue of incoming messages.
+ */
+class IncomingMessages
+{
+ public:
+ typedef boost::shared_ptr<qpid::framing::FrameSet> FrameSetPtr;
+ class MessageTransfer
+ {
+ public:
+ const std::string& getDestination();
+ void retrieve(qpid::messaging::Message* message);
+ private:
+ FrameSetPtr content;
+ IncomingMessages& parent;
+ bool checkExpired();
+
+ MessageTransfer(FrameSetPtr, IncomingMessages&);
+ friend class IncomingMessages;
+ };
+
+ struct Handler
+ {
+ virtual ~Handler() {}
+ virtual bool accept(MessageTransfer& transfer) = 0;
+ virtual bool isClosed() { return false; }
+ };
+
+ IncomingMessages();
+ void setSession(qpid::client::AsyncSession session);
+ bool get(Handler& handler, qpid::sys::Duration timeout);
+ void wakeup();
+ bool getNextDestination(std::string& destination, qpid::sys::Duration timeout);
+ void accept();
+ void accept(qpid::framing::SequenceNumber id, bool cumulative);
+ void releaseAll();
+ void releasePending(const std::string& destination);
+
+ uint32_t pendingAccept();
+ uint32_t pendingAccept(const std::string& destination);
+
+ uint32_t available();
+ uint32_t available(const std::string& destination);
+ private:
+ typedef std::deque<FrameSetPtr> FrameSetQueue;
+ enum ProcessState {EMPTY=0,OK=1,CLOSED=2};
+
+ sys::Monitor lock;
+ qpid::client::AsyncSession session;
+ boost::shared_ptr< sys::BlockingQueue<FrameSetPtr> > incoming;
+ bool inUse;
+ FrameSetQueue received;
+ AcceptTracker acceptTracker;
+
+ ProcessState process(Handler*, qpid::sys::Duration);
+ bool wait(qpid::sys::Duration);
+ bool pop(FrameSetPtr&, qpid::sys::Duration);
+
+ void retrieve(FrameSetPtr, qpid::messaging::Message*);
+
+};
+}}} // namespace qpid::client::amqp0_10
+
+#endif /*!QPID_CLIENT_AMQP0_10_INCOMINGMESSAGES_H*/
diff --git a/qpid/cpp/src/qpid/client/amqp0_10/MessageSink.h b/qpid/cpp/src/qpid/client/amqp0_10/MessageSink.h
new file mode 100644
index 0000000000..d66d2ecb3c
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/amqp0_10/MessageSink.h
@@ -0,0 +1,52 @@
+#ifndef QPID_CLIENT_AMQP0_10_MESSAGESINK_H
+#define QPID_CLIENT_AMQP0_10_MESSAGESINK_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <string>
+#include "qpid/client/AsyncSession.h"
+
+namespace qpid {
+
+namespace messaging {
+class Message;
+}
+
+namespace client {
+namespace amqp0_10 {
+
+class OutgoingMessage;
+
+/**
+ *
+ */
+class MessageSink
+{
+ public:
+ virtual ~MessageSink() {}
+ virtual void declare(qpid::client::AsyncSession& session, const std::string& name) = 0;
+ virtual void send(qpid::client::AsyncSession& session, const std::string& name, OutgoingMessage& message) = 0;
+ virtual void cancel(qpid::client::AsyncSession& session, const std::string& name) = 0;
+ private:
+};
+}}} // namespace qpid::client::amqp0_10
+
+#endif /*!QPID_CLIENT_AMQP0_10_MESSAGESINK_H*/
diff --git a/qpid/cpp/src/qpid/client/amqp0_10/MessageSource.h b/qpid/cpp/src/qpid/client/amqp0_10/MessageSource.h
new file mode 100644
index 0000000000..74f2732f59
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/amqp0_10/MessageSource.h
@@ -0,0 +1,47 @@
+#ifndef QPID_CLIENT_AMQP0_10_MESSAGESOURCE_H
+#define QPID_CLIENT_AMQP0_10_MESSAGESOURCE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <string>
+#include "qpid/client/AsyncSession.h"
+
+namespace qpid {
+namespace client {
+namespace amqp0_10 {
+
+/**
+ * Abstraction behind which the AMQP 0-10 commands required to
+ * establish (and tear down) an incoming stream of messages from a
+ * given address are hidden.
+ */
+class MessageSource
+{
+ public:
+ virtual ~MessageSource() {}
+ virtual void subscribe(qpid::client::AsyncSession& session, const std::string& destination) = 0;
+ virtual void cancel(qpid::client::AsyncSession& session, const std::string& destination) = 0;
+
+ private:
+};
+}}} // namespace qpid::client::amqp0_10
+
+#endif /*!QPID_CLIENT_AMQP0_10_MESSAGESOURCE_H*/
diff --git a/qpid/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp b/qpid/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp
new file mode 100644
index 0000000000..f2b205a78a
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/amqp0_10/OutgoingMessage.cpp
@@ -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.
+ *
+ */
+#include "qpid/client/amqp0_10/OutgoingMessage.h"
+#include "qpid/client/amqp0_10/AddressResolution.h"
+#include "qpid/amqp_0_10/Codecs.h"
+#include "qpid/types/encodings.h"
+#include "qpid/types/Variant.h"
+#include "qpid/messaging/Address.h"
+#include "qpid/messaging/Duration.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/messaging/MessageImpl.h"
+#include "qpid/framing/enum.h"
+#include "qpid/log/Statement.h"
+#include <sstream>
+
+namespace qpid {
+namespace client {
+namespace amqp0_10 {
+
+using qpid::messaging::Address;
+using qpid::messaging::MessageImplAccess;
+using qpid::types::Variant;
+using namespace qpid::framing::message;
+using namespace qpid::amqp_0_10;
+
+namespace {
+//TODO: unify conversion to and from 0-10 message that is currently
+//split between IncomingMessages and OutgoingMessage
+const std::string SUBJECT("qpid.subject");
+const std::string X_APP_ID("x-amqp-0-10.app-id");
+const std::string X_ROUTING_KEY("x-amqp-0-10.routing-key");
+const std::string X_CONTENT_ENCODING("x-amqp-0-10.content-encoding");
+const std::string TEXT_PLAIN("text/plain");
+}
+
+void OutgoingMessage::convert(const qpid::messaging::Message& from)
+{
+ //TODO: need to avoid copying as much as possible
+ if (from.getContentObject().getType() == qpid::types::VAR_MAP) {
+ std::string content;
+ qpid::amqp_0_10::MapCodec::encode(from.getContentObject().asMap(), content);
+ message.getMessageProperties().setContentType(qpid::amqp_0_10::MapCodec::contentType);
+ message.setData(content);
+ } else if (from.getContentObject().getType() == qpid::types::VAR_LIST) {
+ std::string content;
+ qpid::amqp_0_10::ListCodec::encode(from.getContentObject().asList(), content);
+ message.getMessageProperties().setContentType(qpid::amqp_0_10::ListCodec::contentType);
+ message.setData(content);
+ } else if (from.getContentObject().getType() == qpid::types::VAR_STRING &&
+ (from.getContentObject().getEncoding() == qpid::types::encodings::UTF8 || from.getContentObject().getEncoding() == qpid::types::encodings::ASCII)) {
+ message.getMessageProperties().setContentType(TEXT_PLAIN);
+ message.setData(from.getContent());
+ } else {
+ message.setData(from.getContent());
+ message.getMessageProperties().setContentType(from.getContentType());
+ }
+ if ( !from.getCorrelationId().empty() )
+ message.getMessageProperties().setCorrelationId(from.getCorrelationId());
+ message.getMessageProperties().setUserId(from.getUserId());
+ const Address& address = from.getReplyTo();
+ if (address) {
+ message.getMessageProperties().setReplyTo(AddressResolution::convert(address));
+ }
+ if (!subject.empty()) {
+ Variant v(subject); v.setEncoding("utf8");
+ translate(from.getProperties(), SUBJECT, v, message.getMessageProperties().getApplicationHeaders());
+ } else {
+ translate(from.getProperties(), message.getMessageProperties().getApplicationHeaders());
+ }
+ if (from.getTtl().getMilliseconds()) {
+ message.getDeliveryProperties().setTtl(from.getTtl().getMilliseconds());
+ }
+ if (from.getDurable()) {
+ message.getDeliveryProperties().setDeliveryMode(DELIVERY_MODE_PERSISTENT);
+ }
+ if (from.getRedelivered()) {
+ message.getDeliveryProperties().setRedelivered(true);
+ }
+ if (from.getPriority()) message.getDeliveryProperties().setPriority(from.getPriority());
+
+ //allow certain 0-10 specific items to be set through special properties:
+ // message-id, app-id, content-encoding
+ if (from.getMessageId().size()) {
+ qpid::framing::Uuid uuid;
+ std::istringstream data(from.getMessageId());
+ data >> uuid;
+ message.getMessageProperties().setMessageId(uuid);
+ }
+ Variant::Map::const_iterator i;
+ i = from.getProperties().find(X_APP_ID);
+ if (i != from.getProperties().end()) {
+ message.getMessageProperties().setAppId(i->second.asString());
+ }
+ i = from.getProperties().find(X_CONTENT_ENCODING);
+ if (i != from.getProperties().end()) {
+ message.getMessageProperties().setContentEncoding(i->second.asString());
+ }
+ base = qpid::sys::now();
+}
+
+void OutgoingMessage::setSubject(const std::string& s)
+{
+ subject = s;
+}
+
+std::string OutgoingMessage::getSubject() const
+{
+ return subject;
+}
+
+void OutgoingMessage::send(qpid::client::AsyncSession& session, const std::string& destination, const std::string& routingKey)
+{
+ if (!expired) {
+ message.getDeliveryProperties().setRoutingKey(routingKey);
+ status = session.messageTransfer(arg::destination=destination, arg::content=message);
+ if (destination.empty()) {
+ QPID_LOG(debug, "Sending to queue " << routingKey << " " << message.getMessageProperties() << " " << message.getDeliveryProperties());
+ } else {
+ QPID_LOG(debug, "Sending to exchange " << destination << " " << message.getMessageProperties() << " " << message.getDeliveryProperties());
+ }
+ }
+}
+void OutgoingMessage::send(qpid::client::AsyncSession& session, const std::string& routingKey)
+{
+ send(session, std::string(), routingKey);
+}
+
+bool OutgoingMessage::isComplete()
+{
+ return expired || (status.isValid() && status.isComplete());
+}
+void OutgoingMessage::markRedelivered()
+{
+ message.setRedelivered(true);
+ if (message.getDeliveryProperties().hasTtl()) {
+ uint64_t delta = qpid::sys::Duration(base, qpid::sys::now())/qpid::sys::TIME_MSEC;
+ uint64_t ttl = message.getDeliveryProperties().getTtl();
+ if (ttl <= delta) {
+ QPID_LOG(debug, "Expiring outgoing message (" << ttl << " < " << delta << ")");
+ expired = true;
+ message.getDeliveryProperties().setTtl(1);
+ } else {
+ QPID_LOG(debug, "Adjusting ttl on outgoing message from " << ttl << " by " << delta);
+ ttl = ttl - delta;
+ message.getDeliveryProperties().setTtl(ttl);
+ }
+ }
+}
+OutgoingMessage::OutgoingMessage() : expired (false) {}
+
+
+}}} // namespace qpid::client::amqp0_10
diff --git a/qpid/cpp/src/qpid/client/amqp0_10/OutgoingMessage.h b/qpid/cpp/src/qpid/client/amqp0_10/OutgoingMessage.h
new file mode 100644
index 0000000000..a17ef03e10
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/amqp0_10/OutgoingMessage.h
@@ -0,0 +1,60 @@
+#ifndef QPID_CLIENT_AMQP0_10_OUTGOINGMESSAGE_H
+#define QPID_CLIENT_AMQP0_10_OUTGOINGMESSAGE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/client/AsyncSession.h"
+#include "qpid/client/Completion.h"
+#include "qpid/client/Message.h"
+#include "qpid/sys/Time.h"
+
+namespace qpid {
+namespace messaging {
+class Message;
+}
+namespace client {
+namespace amqp0_10 {
+
+class OutgoingMessage
+{
+ private:
+ qpid::client::Message message;
+ qpid::client::Completion status;
+ std::string subject;
+ qpid::sys::AbsTime base;
+ bool expired;
+
+ public:
+ OutgoingMessage();
+ void convert(const qpid::messaging::Message&);
+ void setSubject(const std::string& subject);
+ std::string getSubject() const;
+ void send(qpid::client::AsyncSession& session, const std::string& destination, const std::string& routingKey);
+ void send(qpid::client::AsyncSession& session,const std::string& routingKey);
+ bool isComplete();
+ void markRedelivered();
+};
+
+
+
+}}} // namespace qpid::client::amqp0_10
+
+#endif /*!QPID_CLIENT_AMQP0_10_OUTGOINGMESSAGE_H*/
diff --git a/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.cpp b/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.cpp
new file mode 100644
index 0000000000..c356bc298b
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.cpp
@@ -0,0 +1,263 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "ReceiverImpl.h"
+#include "AddressResolution.h"
+#include "MessageSource.h"
+#include "SessionImpl.h"
+#include "qpid/messaging/exceptions.h"
+#include "qpid/messaging/Receiver.h"
+#include "qpid/messaging/Session.h"
+#include "qpid/amqp_0_10/Codecs.h"
+#include "qpid/types/encodings.h"
+
+namespace qpid {
+namespace client {
+namespace amqp0_10 {
+
+using qpid::messaging::NoMessageAvailable;
+using qpid::messaging::Receiver;
+using qpid::messaging::Duration;
+
+void ReceiverImpl::received(qpid::messaging::Message&)
+{
+ //TODO: should this be configurable
+ sys::Mutex::ScopedLock l(lock);
+ if (capacity && --window <= capacity/2) {
+ session.sendCompletion();
+ window = capacity;
+ }
+}
+
+qpid::messaging::Message ReceiverImpl::get(qpid::messaging::Duration timeout)
+{
+ qpid::messaging::Message result;
+ if (!get(result, timeout)) throw NoMessageAvailable();
+ return result;
+}
+
+qpid::messaging::Message ReceiverImpl::fetch(qpid::messaging::Duration timeout)
+{
+ qpid::messaging::Message result;
+ if (!fetch(result, timeout)) throw NoMessageAvailable();
+ return result;
+}
+
+bool ReceiverImpl::get(qpid::messaging::Message& message, qpid::messaging::Duration timeout)
+{
+ Get f(*this, message, timeout);
+ while (!parent->execute(f)) {}
+ return f.result;
+}
+
+bool ReceiverImpl::fetch(qpid::messaging::Message& message, qpid::messaging::Duration timeout)
+{
+ Fetch f(*this, message, timeout);
+ while (!parent->execute(f)) {}
+ return f.result;
+}
+
+void ReceiverImpl::close()
+{
+ execute<Close>();
+}
+
+void ReceiverImpl::start()
+{
+ sys::Mutex::ScopedLock l(lock);
+ if (state == STOPPED) {
+ state = STARTED;
+ startFlow(l);
+ session.sendCompletion();
+ }
+}
+
+void ReceiverImpl::stop()
+{
+ sys::Mutex::ScopedLock l(lock);
+ state = STOPPED;
+ session.messageStop(destination);
+}
+
+void ReceiverImpl::setCapacity(uint32_t c)
+{
+ execute1<SetCapacity>(c);
+}
+
+void ReceiverImpl::startFlow(const sys::Mutex::ScopedLock&)
+{
+ if (capacity > 0) {
+ session.messageSetFlowMode(destination, FLOW_MODE_WINDOW);
+ session.messageFlow(destination, CREDIT_UNIT_MESSAGE, capacity);
+ session.messageFlow(destination, CREDIT_UNIT_BYTE, byteCredit);
+ window = capacity;
+ }
+}
+
+void ReceiverImpl::init(qpid::client::AsyncSession s, AddressResolution& resolver)
+{
+ sys::Mutex::ScopedLock l(lock);
+ session = s;
+ if (state == CANCELLED) return;
+ if (state == UNRESOLVED) {
+ source = resolver.resolveSource(session, address);
+ assert(source.get());
+ state = STARTED;
+ }
+ source->subscribe(session, destination);
+ startFlow(l);
+}
+
+const std::string& ReceiverImpl::getName() const {
+ return destination;
+}
+
+uint32_t ReceiverImpl::getCapacity()
+{
+ sys::Mutex::ScopedLock l(lock);
+ return capacity;
+}
+
+uint32_t ReceiverImpl::getAvailable()
+{
+ return parent->getReceivable(destination);
+}
+
+uint32_t ReceiverImpl::getUnsettled()
+{
+ return parent->getUnsettledAcks(destination);
+}
+
+qpid::messaging::Address ReceiverImpl::getAddress() const
+{
+ return address;
+}
+
+ReceiverImpl::ReceiverImpl(SessionImpl& p, const std::string& name,
+ const qpid::messaging::Address& a, bool autoDecode_) :
+
+ parent(&p), destination(name), address(a), byteCredit(0xFFFFFFFF), autoDecode(autoDecode_),
+ state(UNRESOLVED), capacity(0), window(0) {}
+
+namespace {
+const std::string TEXT_PLAIN("text/plain");
+}
+
+bool ReceiverImpl::getImpl(qpid::messaging::Message& message, qpid::messaging::Duration timeout)
+{
+ {
+ sys::Mutex::ScopedLock l(lock);
+ if (state == CANCELLED) return false;
+ }
+ if (parent->get(*this, message, timeout)) {
+ if (autoDecode) {
+ if (message.getContentType() == qpid::amqp_0_10::MapCodec::contentType) {
+ message.getContentObject() = qpid::types::Variant::Map();
+ decode(message, message.getContentObject().asMap());
+ } else if (message.getContentType() == qpid::amqp_0_10::ListCodec::contentType) {
+ message.getContentObject() = qpid::types::Variant::List();
+ decode(message, message.getContentObject().asList());
+ } else if (!message.getContentBytes().empty()) {
+ message.getContentObject() = message.getContentBytes();
+ if (message.getContentType() == TEXT_PLAIN) {
+ message.getContentObject().setEncoding(qpid::types::encodings::UTF8);
+ } else {
+ message.getContentObject().setEncoding(qpid::types::encodings::BINARY);
+ }
+ }
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool ReceiverImpl::fetchImpl(qpid::messaging::Message& message, qpid::messaging::Duration timeout)
+{
+ {
+ sys::Mutex::ScopedLock l(lock);
+ if (state == CANCELLED) return false;
+
+ if (capacity == 0 || state != STARTED) {
+ session.messageSetFlowMode(destination, FLOW_MODE_CREDIT);
+ session.messageFlow(destination, CREDIT_UNIT_MESSAGE, 1);
+ session.messageFlow(destination, CREDIT_UNIT_BYTE, 0xFFFFFFFF);
+ }
+ }
+ if (getImpl(message, timeout)) {
+ return true;
+ } else {
+ qpid::client::Session s;
+ {
+ sys::Mutex::ScopedLock l(lock);
+ if (state == CANCELLED) return false; // Might have been closed during get.
+ s = sync(session);
+ }
+ s.messageFlush(destination);
+ {
+ sys::Mutex::ScopedLock l(lock);
+ startFlow(l); //reallocate credit
+ session.sendCompletion();//ensure previously received messages are signalled as completed
+ }
+ return getImpl(message, Duration::IMMEDIATE);
+ }
+}
+
+void ReceiverImpl::closeImpl()
+{
+ sys::Mutex::ScopedLock l(lock);
+ if (state != CANCELLED) {
+ state = CANCELLED;
+ sync(session).messageStop(destination);
+ {
+ sys::Mutex::ScopedUnlock l(lock);
+ parent->releasePending(destination);
+ }
+ source->cancel(session, destination);
+ {
+ sys::Mutex::ScopedUnlock l(lock);
+ parent->receiverCancelled(destination);
+ }
+ }
+}
+
+bool ReceiverImpl::isClosed() const {
+ sys::Mutex::ScopedLock l(lock);
+ return state == CANCELLED;
+}
+
+void ReceiverImpl::setCapacityImpl(uint32_t c)
+{
+ sys::Mutex::ScopedLock l(lock);
+ if (c != capacity) {
+ capacity = c;
+ if (state == STARTED) {
+ session.messageStop(destination);
+ startFlow(l);
+ }
+ }
+}
+
+qpid::messaging::Session ReceiverImpl::getSession() const
+{
+ return qpid::messaging::Session(parent.get());
+}
+
+}}} // namespace qpid::client::amqp0_10
diff --git a/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h b/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h
new file mode 100644
index 0000000000..0d3366907b
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/amqp0_10/ReceiverImpl.h
@@ -0,0 +1,152 @@
+#ifndef QPID_CLIENT_AMQP0_10_RECEIVERIMPL_H
+#define QPID_CLIENT_AMQP0_10_RECEIVERIMPL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/messaging/Address.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/messaging/ReceiverImpl.h"
+#include "qpid/client/AsyncSession.h"
+#include "qpid/client/amqp0_10/SessionImpl.h"
+#include "qpid/messaging/Duration.h"
+#include "qpid/sys/Mutex.h"
+#include <boost/intrusive_ptr.hpp>
+#include <memory>
+
+namespace qpid {
+namespace client {
+namespace amqp0_10 {
+
+class AddressResolution;
+class MessageSource;
+
+/**
+ * A receiver implementation based on an AMQP 0-10 subscription.
+ */
+class ReceiverImpl : public qpid::messaging::ReceiverImpl
+{
+ public:
+
+ enum State {UNRESOLVED, STOPPED, STARTED, CANCELLED};
+
+ ReceiverImpl(SessionImpl& parent, const std::string& name,
+ const qpid::messaging::Address& address, bool autoDecode);
+
+ void init(qpid::client::AsyncSession session, AddressResolution& resolver);
+ bool get(qpid::messaging::Message& message, qpid::messaging::Duration timeout);
+ qpid::messaging::Message get(qpid::messaging::Duration timeout);
+ bool fetch(qpid::messaging::Message& message, qpid::messaging::Duration timeout);
+ qpid::messaging::Message fetch(qpid::messaging::Duration timeout);
+ void close();
+ void start();
+ void stop();
+ const std::string& getName() const;
+ void setCapacity(uint32_t);
+ uint32_t getCapacity();
+ uint32_t getAvailable();
+ uint32_t getUnsettled();
+ void received(qpid::messaging::Message& message);
+ qpid::messaging::Session getSession() const;
+ bool isClosed() const;
+ qpid::messaging::Address getAddress() const;
+
+ private:
+ mutable sys::Mutex lock;
+ boost::intrusive_ptr<SessionImpl> parent;
+ const std::string destination;
+ const qpid::messaging::Address address;
+ const uint32_t byteCredit;
+ const bool autoDecode;
+ State state;
+
+ std::auto_ptr<MessageSource> source;
+ uint32_t capacity;
+ qpid::client::AsyncSession session;
+ uint32_t window;
+
+ void startFlow(const sys::Mutex::ScopedLock&); // Dummy param, call with lock held
+ //implementation of public facing methods
+ bool fetchImpl(qpid::messaging::Message& message, qpid::messaging::Duration timeout);
+ bool getImpl(qpid::messaging::Message& message, qpid::messaging::Duration timeout);
+ void closeImpl();
+ void setCapacityImpl(uint32_t);
+
+ //functors for public facing methods.
+ struct Command
+ {
+ ReceiverImpl& impl;
+
+ Command(ReceiverImpl& i) : impl(i) {}
+ };
+
+ struct Get : Command
+ {
+ qpid::messaging::Message& message;
+ qpid::messaging::Duration timeout;
+ bool result;
+
+ Get(ReceiverImpl& i, qpid::messaging::Message& m, qpid::messaging::Duration t) :
+ Command(i), message(m), timeout(t), result(false) {}
+ void operator()() { result = impl.getImpl(message, timeout); }
+ };
+
+ struct Fetch : Command
+ {
+ qpid::messaging::Message& message;
+ qpid::messaging::Duration timeout;
+ bool result;
+
+ Fetch(ReceiverImpl& i, qpid::messaging::Message& m, qpid::messaging::Duration t) :
+ Command(i), message(m), timeout(t), result(false) {}
+ void operator()() { result = impl.fetchImpl(message, timeout); }
+ };
+
+ struct Close : Command
+ {
+ Close(ReceiverImpl& i) : Command(i) {}
+ void operator()() { impl.closeImpl(); }
+ };
+
+ struct SetCapacity : Command
+ {
+ uint32_t capacity;
+
+ SetCapacity(ReceiverImpl& i, uint32_t c) : Command(i), capacity(c) {}
+ void operator()() { impl.setCapacityImpl(capacity); }
+ };
+
+ //helper templates for some common patterns
+ template <class F> void execute()
+ {
+ F f(*this);
+ parent->execute(f);
+ }
+
+ template <class F, class P> void execute1(P p)
+ {
+ F f(*this, p);
+ parent->execute(f);
+ }
+};
+
+}}} // namespace qpid::client::amqp0_10
+
+#endif /*!QPID_CLIENT_AMQP0_10_RECEIVERIMPL_H*/
diff --git a/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp b/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp
new file mode 100644
index 0000000000..7575aaa306
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.cpp
@@ -0,0 +1,206 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "SenderImpl.h"
+#include "MessageSink.h"
+#include "SessionImpl.h"
+#include "AddressResolution.h"
+#include "OutgoingMessage.h"
+#include "qpid/messaging/Session.h"
+
+namespace qpid {
+namespace client {
+namespace amqp0_10 {
+
+SenderImpl::SenderImpl(SessionImpl& _parent, const std::string& _name,
+ const qpid::messaging::Address& _address, bool _autoReconnect) :
+ parent(&_parent), autoReconnect(_autoReconnect), name(_name), address(_address), state(UNRESOLVED),
+ capacity(50), window(0), flushed(false), unreliable(AddressResolution::is_unreliable(address)) {}
+
+qpid::messaging::Address SenderImpl::getAddress() const
+{
+ return address;
+}
+
+void SenderImpl::send(const qpid::messaging::Message& message, bool sync)
+{
+ if (unreliable) { // immutable, don't need lock
+ UnreliableSend f(*this, message);
+ parent->execute(f);
+ } else {
+ Send f(*this, message);
+ while (f.repeat) parent->execute(f);
+ }
+ if (sync) parent->sync(true);
+}
+
+void SenderImpl::close()
+{
+ execute<Close>();
+}
+
+void SenderImpl::setCapacity(uint32_t c)
+{
+ bool flush;
+ {
+ sys::Mutex::ScopedLock l(lock);
+ flush = c < capacity;
+ capacity = c;
+ }
+ execute1<CheckPendingSends>(flush);
+}
+
+uint32_t SenderImpl::getCapacity() {
+ sys::Mutex::ScopedLock l(lock);
+ return capacity;
+}
+
+uint32_t SenderImpl::getUnsettled()
+{
+ CheckPendingSends f(*this, false);
+ parent->execute(f);
+ return f.pending;
+}
+
+void SenderImpl::init(qpid::client::AsyncSession s, AddressResolution& resolver)
+{
+ sys::Mutex::ScopedLock l(lock);
+ session = s;
+ if (state == UNRESOLVED) {
+ sink = resolver.resolveSink(session, address);
+ state = ACTIVE;
+ }
+ if (state == CANCELLED) {
+ sink->cancel(session, name);
+ sys::Mutex::ScopedUnlock u(lock);
+ parent->senderCancelled(name);
+ } else {
+ sink->declare(session, name);
+ replay(l);
+ }
+}
+
+void SenderImpl::waitForCapacity()
+{
+ sys::Mutex::ScopedLock l(lock);
+ try {
+ //TODO: add option to throw exception rather than blocking?
+ if (!unreliable && capacity <=
+ (flushed ? checkPendingSends(false, l) : outgoing.size()))
+ {
+ //Initial implementation is very basic. As outgoing is
+ //currently only reduced on receiving completions and we are
+ //blocking anyway we may as well sync(). If successful that
+ //should clear all outstanding sends.
+ session.sync();
+ checkPendingSends(false, l);
+ }
+ //flush periodically and check for conmpleted sends
+ if (++window > (capacity / 4)) {//TODO: make this configurable?
+ checkPendingSends(true, l);
+ window = 0;
+ }
+ } catch (const qpid::TransportFailure&) {
+ //Disconnection prevents flushing or syncing. If we have any
+ //capacity we will return anyway (the subsequent attempt to
+ //send will fail, but message will be on replay buffer).
+ if (capacity > outgoing.size()) return;
+ //If we are out of capacity, but autoreconnect is on, then
+ //rethrow the transport failure to trigger reconnect which
+ //will have the effect of blocking until connected and
+ //capacity is freed up
+ if (autoReconnect) throw;
+ //Otherwise, in order to clearly signal to the application
+ //that the message was not pushed to replay buffer, throw an
+ //out of capacity error
+ throw qpid::messaging::OutOfCapacity(name);
+ }
+}
+
+void SenderImpl::sendImpl(const qpid::messaging::Message& m)
+{
+ sys::Mutex::ScopedLock l(lock);
+ std::auto_ptr<OutgoingMessage> msg(new OutgoingMessage());
+ msg->setSubject(m.getSubject().empty() ? address.getSubject() : m.getSubject());
+ msg->convert(m);
+ outgoing.push_back(msg.release());
+ sink->send(session, name, outgoing.back());
+}
+
+void SenderImpl::sendUnreliable(const qpid::messaging::Message& m)
+{
+ sys::Mutex::ScopedLock l(lock);
+ OutgoingMessage msg;
+ msg.setSubject(m.getSubject().empty() ? address.getSubject() : m.getSubject());
+ msg.convert(m);
+ sink->send(session, name, msg);
+}
+
+void SenderImpl::replay(const sys::Mutex::ScopedLock& l)
+{
+ checkPendingSends(false, l);
+ for (OutgoingMessages::iterator i = outgoing.begin(); i != outgoing.end(); ++i) {
+ i->markRedelivered();
+ sink->send(session, name, *i);
+ }
+}
+
+uint32_t SenderImpl::checkPendingSends(bool flush) {
+ sys::Mutex::ScopedLock l(lock);
+ return checkPendingSends(flush, l);
+}
+
+uint32_t SenderImpl::checkPendingSends(bool flush, const sys::Mutex::ScopedLock&)
+{
+ if (flush) {
+ session.flush();
+ flushed = true;
+ } else {
+ flushed = false;
+ }
+ while (!outgoing.empty() && outgoing.front().isComplete()) {
+ outgoing.pop_front();
+ }
+ return outgoing.size();
+}
+
+void SenderImpl::closeImpl()
+{
+ {
+ sys::Mutex::ScopedLock l(lock);
+ state = CANCELLED;
+ sink->cancel(session, name);
+ }
+ parent->senderCancelled(name);
+}
+
+const std::string& SenderImpl::getName() const
+{
+ sys::Mutex::ScopedLock l(lock);
+ return name;
+}
+
+qpid::messaging::Session SenderImpl::getSession() const
+{
+ sys::Mutex::ScopedLock l(lock);
+ return qpid::messaging::Session(parent.get());
+}
+
+}}} // namespace qpid::client::amqp0_10
diff --git a/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.h b/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.h
new file mode 100644
index 0000000000..35ce82cf5d
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/amqp0_10/SenderImpl.h
@@ -0,0 +1,162 @@
+#ifndef QPID_CLIENT_AMQP0_10_SENDERIMPL_H
+#define QPID_CLIENT_AMQP0_10_SENDERIMPL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/messaging/Address.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/messaging/SenderImpl.h"
+#include "qpid/client/AsyncSession.h"
+#include "qpid/client/amqp0_10/SessionImpl.h"
+#include <memory>
+#include <boost/intrusive_ptr.hpp>
+#include <boost/ptr_container/ptr_deque.hpp>
+
+namespace qpid {
+namespace client {
+namespace amqp0_10 {
+
+class AddressResolution;
+class MessageSink;
+class OutgoingMessage;
+
+/**
+ *
+ */
+class SenderImpl : public qpid::messaging::SenderImpl
+{
+ public:
+ enum State {UNRESOLVED, ACTIVE, CANCELLED};
+
+ SenderImpl(SessionImpl& parent, const std::string& name,
+ const qpid::messaging::Address& address, bool autoReconnect);
+ void send(const qpid::messaging::Message&, bool sync);
+ void close();
+ void setCapacity(uint32_t);
+ uint32_t getCapacity();
+ uint32_t getUnsettled();
+ void init(qpid::client::AsyncSession, AddressResolution&);
+ const std::string& getName() const;
+ qpid::messaging::Session getSession() const;
+ qpid::messaging::Address getAddress() const;
+
+ private:
+ mutable sys::Mutex lock;
+ boost::intrusive_ptr<SessionImpl> parent;
+ const bool autoReconnect;
+ const std::string name;
+ const qpid::messaging::Address address;
+ State state;
+ std::auto_ptr<MessageSink> sink;
+
+ qpid::client::AsyncSession session;
+ std::string destination;
+ std::string routingKey;
+
+ typedef boost::ptr_deque<OutgoingMessage> OutgoingMessages;
+ OutgoingMessages outgoing;
+ uint32_t capacity;
+ uint32_t window;
+ bool flushed;
+ const bool unreliable;
+
+ uint32_t checkPendingSends(bool flush);
+ // Dummy ScopedLock parameter means call with lock held
+ uint32_t checkPendingSends(bool flush, const sys::Mutex::ScopedLock&);
+ void replay(const sys::Mutex::ScopedLock&);
+ void waitForCapacity();
+
+ //logic for application visible methods:
+ void sendImpl(const qpid::messaging::Message&);
+ void sendUnreliable(const qpid::messaging::Message&);
+ void closeImpl();
+
+
+ //functors for application visible methods (allowing locking and
+ //retry to be centralised):
+ struct Command
+ {
+ SenderImpl& impl;
+
+ Command(SenderImpl& i) : impl(i) {}
+ };
+
+ struct Send : Command
+ {
+ const qpid::messaging::Message& message;
+ bool repeat;
+
+ Send(SenderImpl& i, const qpid::messaging::Message& m) : Command(i), message(m), repeat(true) {}
+ void operator()()
+ {
+ impl.waitForCapacity();
+ //from this point message will be recorded if there is any
+ //failure (and replayed) so need not repeat the call
+ repeat = false;
+ impl.sendImpl(message);
+ }
+ };
+
+ struct UnreliableSend : Command
+ {
+ const qpid::messaging::Message& message;
+
+ UnreliableSend(SenderImpl& i, const qpid::messaging::Message& m) : Command(i), message(m) {}
+ void operator()()
+ {
+ //TODO: ideally want to put messages on the outbound
+ //queue and pull them off in io thread, but the old
+ //0-10 client doesn't support that option so for now
+ //we simply don't queue unreliable messages
+ impl.sendUnreliable(message);
+ }
+ };
+
+ struct Close : Command
+ {
+ Close(SenderImpl& i) : Command(i) {}
+ void operator()() { impl.closeImpl(); }
+ };
+
+ struct CheckPendingSends : Command
+ {
+ bool flush;
+ uint32_t pending;
+ CheckPendingSends(SenderImpl& i, bool f) : Command(i), flush(f), pending(0) {}
+ void operator()() { pending = impl.checkPendingSends(flush); }
+ };
+
+ //helper templates for some common patterns
+ template <class F> void execute()
+ {
+ F f(*this);
+ parent->execute(f);
+ }
+
+ template <class F, class P> bool execute1(P p)
+ {
+ F f(*this, p);
+ return parent->execute(f);
+ }
+};
+}}} // namespace qpid::client::amqp0_10
+
+#endif /*!QPID_CLIENT_AMQP0_10_SENDERIMPL_H*/
diff --git a/qpid/cpp/src/qpid/client/amqp0_10/SessionImpl.cpp b/qpid/cpp/src/qpid/client/amqp0_10/SessionImpl.cpp
new file mode 100644
index 0000000000..1e2b68b24e
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/amqp0_10/SessionImpl.cpp
@@ -0,0 +1,606 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/client/amqp0_10/SessionImpl.h"
+#include "qpid/client/amqp0_10/ConnectionImpl.h"
+#include "qpid/client/amqp0_10/ReceiverImpl.h"
+#include "qpid/client/amqp0_10/SenderImpl.h"
+#include "qpid/client/amqp0_10/MessageSource.h"
+#include "qpid/client/amqp0_10/MessageSink.h"
+#include "qpid/client/SessionBase_0_10Access.h"
+#include "qpid/client/SessionImpl.h"
+#include "qpid/messaging/PrivateImplRef.h"
+#include "qpid/Exception.h"
+#include "qpid/log/Statement.h"
+#include "qpid/messaging/Address.h"
+#include "qpid/messaging/Connection.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/messaging/MessageImpl.h"
+#include "qpid/messaging/Sender.h"
+#include "qpid/messaging/Receiver.h"
+#include "qpid/messaging/Session.h"
+#include "qpid/framing/enum.h"
+#include <boost/format.hpp>
+#include <boost/function.hpp>
+#include <boost/intrusive_ptr.hpp>
+
+using qpid::messaging::KeyError;
+using qpid::messaging::NoMessageAvailable;
+using qpid::messaging::MessagingException;
+using qpid::messaging::TransactionError;
+using qpid::messaging::TransactionAborted;
+using qpid::messaging::TransactionUnknown;
+using qpid::messaging::SessionError;
+using qpid::messaging::MessageImplAccess;
+using qpid::messaging::Sender;
+using qpid::messaging::Receiver;
+
+namespace qpid {
+namespace client {
+namespace amqp0_10 {
+
+typedef qpid::sys::Mutex::ScopedLock ScopedLock;
+typedef qpid::sys::Mutex::ScopedUnlock ScopedUnlock;
+
+SessionImpl::SessionImpl(ConnectionImpl& c, bool t) :
+ connection(&c), transactional(t), committing(false) {}
+
+bool SessionImpl::isTransactional() const
+{
+ return transactional;
+}
+
+void SessionImpl::checkError()
+{
+ ScopedLock l(lock);
+ txError.raise();
+ qpid::client::SessionBase_0_10Access s(session);
+ try {
+ s.get()->assertOpen();
+ } catch (const qpid::TransportFailure&) {
+ throw qpid::messaging::TransportFailure(std::string());
+ } catch (const qpid::framing::ResourceLimitExceededException& e) {
+ throw qpid::messaging::TargetCapacityExceeded(e.what());
+ } catch (const qpid::framing::UnauthorizedAccessException& e) {
+ throw qpid::messaging::UnauthorizedAccess(e.what());
+ } catch (const qpid::framing::NotFoundException& e) {
+ throw qpid::messaging::NotFound(e.what());
+ } catch (const qpid::framing::ResourceDeletedException& e) {
+ throw qpid::messaging::NotFound(e.what());
+ } catch (const qpid::SessionException& e) {
+ throw qpid::messaging::SessionError(e.what());
+ } catch (const qpid::ConnectionException& e) {
+ throw qpid::messaging::ConnectionError(e.what());
+ } catch (const qpid::Exception& e) {
+ throw qpid::messaging::MessagingException(e.what());
+ }
+}
+
+bool SessionImpl::hasError()
+{
+ ScopedLock l(lock);
+ qpid::client::SessionBase_0_10Access s(session);
+ return s.get()->hasError();
+}
+
+void SessionImpl::sync(bool block)
+{
+ if (block) retry<Sync>();
+ else execute<NonBlockingSync>();
+}
+
+namespace {
+struct ScopedSet {
+ bool& flag;
+ ScopedSet(bool& f) : flag(f) { flag = true; }
+ ~ScopedSet() { flag = false; }
+};
+}
+
+void SessionImpl::commit()
+{
+ try {
+ checkError();
+ ScopedSet s(committing);
+ execute<Commit>();
+ }
+ catch (const TransactionError&) {
+ assert(txError); // Must be set by thrower of TransactionError
+ }
+ catch (const std::exception& e) {
+ txError = new TransactionAborted(Msg() << "Transaction aborted: " << e.what());
+ }
+ checkError();
+}
+
+void SessionImpl::rollback()
+{
+ //If the session fails during this operation, the transaction will
+ //be rolled back anyway.
+ execute<Rollback>();
+}
+
+void SessionImpl::acknowledge(bool sync_)
+{
+ //Should probably throw an exception on failure here, or indicate
+ //it through a return type at least. Failure means that the
+ //message may be redelivered; i.e. the application cannot delete
+ //any state necessary for preventing reprocessing of the message
+ execute<Acknowledge>();
+ sync(sync_);
+}
+
+void SessionImpl::reject(qpid::messaging::Message& m)
+{
+ //Possibly want to somehow indicate failure here as well. Less
+ //clear need as compared to acknowledge however.
+ execute1<Reject>(m);
+}
+
+void SessionImpl::release(qpid::messaging::Message& m)
+{
+ execute1<Release>(m);
+}
+
+void SessionImpl::acknowledge(qpid::messaging::Message& m, bool cumulative)
+{
+ //Should probably throw an exception on failure here, or indicate
+ //it through a return type at least. Failure means that the
+ //message may be redelivered; i.e. the application cannot delete
+ //any state necessary for preventing reprocessing of the message
+ Acknowledge2 ack(*this, m, cumulative);
+ execute(ack);
+}
+
+void SessionImpl::close()
+{
+ if (hasError()) {
+ ScopedLock l(lock);
+ senders.clear();
+ receivers.clear();
+ } else {
+ Senders sCopy;
+ Receivers rCopy;
+ {
+ ScopedLock l(lock);
+ senders.swap(sCopy);
+ receivers.swap(rCopy);
+ }
+ for (Senders::iterator i = sCopy.begin(); i != sCopy.end(); ++i)
+ {
+ // outside the lock, will call senderCancelled
+ i->second.close();
+ }
+ for (Receivers::iterator i = rCopy.begin(); i != rCopy.end(); ++i)
+ {
+ // outside the lock, will call receiverCancelled
+ i->second.close();
+ }
+ }
+ connection->closed(*this);
+ if (!hasError()) {
+ ScopedLock l(lock);
+ session.close();
+ }
+}
+
+template <class T, class S> boost::intrusive_ptr<S> getImplPtr(T& t)
+{
+ return boost::dynamic_pointer_cast<S>(qpid::messaging::PrivateImplRef<T>::get(t));
+}
+
+template <class T> void getFreeKey(std::string& key, T& map)
+{
+ std::string name = key;
+ int count = 1;
+ for (typename T::const_iterator i = map.find(name); i != map.end(); i = map.find(name)) {
+ name = (boost::format("%1%_%2%") % key % ++count).str();
+ }
+ key = name;
+}
+
+void SessionImpl::setSession(qpid::client::Session s)
+{
+ session = s;
+ incoming.setSession(session);
+ if (transactional) {
+ session.txSelect();
+ }
+ for (Receivers::iterator i = receivers.begin(); i != receivers.end(); ++i) {
+ getImplPtr<Receiver, ReceiverImpl>(i->second)->init(session, resolver);
+ }
+ for (Senders::iterator i = senders.begin(); i != senders.end(); ++i) {
+ getImplPtr<Sender, SenderImpl>(i->second)->init(session, resolver);
+ }
+ session.sync();
+}
+
+struct SessionImpl::CreateReceiver : Command
+{
+ qpid::messaging::Receiver result;
+ const qpid::messaging::Address& address;
+
+ CreateReceiver(SessionImpl& i, const qpid::messaging::Address& a) :
+ Command(i), address(a) {}
+ void operator()() { result = impl.createReceiverImpl(address); }
+};
+
+Receiver SessionImpl::createReceiver(const qpid::messaging::Address& address)
+{
+ return get1<CreateReceiver, Receiver>(address);
+}
+
+Receiver SessionImpl::createReceiverImpl(const qpid::messaging::Address& address)
+{
+ ScopedLock l(lock);
+ std::string name = address.getName();
+ getFreeKey(name, receivers);
+ Receiver receiver(new ReceiverImpl(*this, name, address, connection->getAutoDecode()));
+ getImplPtr<Receiver, ReceiverImpl>(receiver)->init(session, resolver);
+ receivers[name] = receiver;
+ return receiver;
+}
+
+struct SessionImpl::CreateSender : Command
+{
+ qpid::messaging::Sender result;
+ const qpid::messaging::Address& address;
+
+ CreateSender(SessionImpl& i, const qpid::messaging::Address& a) :
+ Command(i), address(a) {}
+ void operator()() { result = impl.createSenderImpl(address); }
+};
+
+Sender SessionImpl::createSender(const qpid::messaging::Address& address)
+{
+ return get1<CreateSender, Sender>(address);
+}
+
+Sender SessionImpl::createSenderImpl(const qpid::messaging::Address& address)
+{
+ ScopedLock l(lock);
+ std::string name = address.getName();
+ getFreeKey(name, senders);
+ Sender sender(new SenderImpl(*this, name, address, connection->getAutoReconnect()));
+ getImplPtr<Sender, SenderImpl>(sender)->init(session, resolver);
+ senders[name] = sender;
+ return sender;
+}
+
+Sender SessionImpl::getSender(const std::string& name) const
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ Senders::const_iterator i = senders.find(name);
+ if (i == senders.end()) {
+ throw KeyError(name);
+ } else {
+ return i->second;
+ }
+}
+
+Receiver SessionImpl::getReceiver(const std::string& name) const
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ Receivers::const_iterator i = receivers.find(name);
+ if (i == receivers.end()) {
+ throw KeyError(name);
+ } else {
+ return i->second;
+ }
+}
+
+SessionImpl& SessionImpl::convert(qpid::messaging::Session& s)
+{
+ boost::intrusive_ptr<SessionImpl> impl = getImplPtr<qpid::messaging::Session, SessionImpl>(s);
+ if (!impl) {
+ throw SessionError(QPID_MSG("Configuration error; require qpid::client::amqp0_10::SessionImpl"));
+ }
+ return *impl;
+}
+
+namespace {
+
+struct IncomingMessageHandler : IncomingMessages::Handler
+{
+ typedef boost::function1<bool, IncomingMessages::MessageTransfer&> Callback;
+ Callback callback;
+ ReceiverImpl* receiver;
+
+ IncomingMessageHandler(Callback c) : callback(c), receiver(0) {}
+
+ bool accept(IncomingMessages::MessageTransfer& transfer)
+ {
+ return callback(transfer);
+ }
+
+ bool isClosed()
+ {
+ return receiver && receiver->isClosed();
+ }
+};
+
+}
+
+
+bool SessionImpl::getNextReceiver(Receiver* receiver, IncomingMessages::MessageTransfer& transfer)
+{
+ ScopedLock l(lock);
+ Receivers::const_iterator i = receivers.find(transfer.getDestination());
+ if (i == receivers.end()) {
+ QPID_LOG(error, "Received message for unknown destination " << transfer.getDestination());
+ return false;
+ } else {
+ *receiver = i->second;
+ return true;
+ }
+}
+
+bool SessionImpl::accept(ReceiverImpl* receiver,
+ qpid::messaging::Message* message,
+ IncomingMessages::MessageTransfer& transfer)
+{
+ if (receiver->getName() == transfer.getDestination()) {
+ transfer.retrieve(message);
+ receiver->received(*message);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+qpid::sys::Duration adjust(qpid::messaging::Duration timeout)
+{
+ uint64_t ms = timeout.getMilliseconds();
+ if (ms < (uint64_t) (qpid::sys::TIME_INFINITE/qpid::sys::TIME_MSEC)) {
+ return ms * qpid::sys::TIME_MSEC;
+ } else {
+ return qpid::sys::TIME_INFINITE;
+ }
+}
+
+bool SessionImpl::getIncoming(IncomingMessages::Handler& handler, qpid::messaging::Duration timeout)
+{
+ return incoming.get(handler, adjust(timeout));
+}
+
+bool SessionImpl::get(ReceiverImpl& receiver, qpid::messaging::Message& message, qpid::messaging::Duration timeout)
+{
+ IncomingMessageHandler handler(boost::bind(&SessionImpl::accept, this, &receiver, &message, _1));
+ handler.receiver = &receiver;
+ return getIncoming(handler, timeout);
+}
+
+bool SessionImpl::nextReceiver(qpid::messaging::Receiver& receiver, qpid::messaging::Duration timeout)
+{
+ while (true) {
+ txError.raise();
+ try {
+ std::string destination;
+ if (incoming.getNextDestination(destination, adjust(timeout))) {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ Receivers::const_iterator i = receivers.find(destination);
+ if (i == receivers.end()) {
+ throw qpid::messaging::ReceiverError(QPID_MSG("Received message for unknown destination " << destination));
+ } else {
+ receiver = i->second;
+ }
+ return true;
+ } else {
+ return false;
+ }
+ } catch (TransportFailure&) {
+ reconnect();
+ } catch (const qpid::framing::ResourceLimitExceededException& e) {
+ if (backoff()) return false;
+ else throw qpid::messaging::TargetCapacityExceeded(e.what());
+ } catch (const qpid::SessionException& e) {
+ rethrow(e);
+ } catch (const qpid::ClosedException&) {
+ throw qpid::messaging::SessionClosed();
+ } catch (const qpid::ConnectionException& e) {
+ throw qpid::messaging::ConnectionError(e.what());
+ } catch (const qpid::ChannelException& e) {
+ throw qpid::messaging::MessagingException(e.what());
+ }
+ }
+}
+
+qpid::messaging::Receiver SessionImpl::nextReceiver(qpid::messaging::Duration timeout)
+{
+ qpid::messaging::Receiver receiver;
+ if (!nextReceiver(receiver, timeout)) throw NoMessageAvailable();
+ if (!receiver) throw SessionError("Bad receiver returned!");
+ return receiver;
+}
+
+uint32_t SessionImpl::getReceivable()
+{
+ return get1<Receivable, uint32_t>((const std::string*) 0);
+}
+uint32_t SessionImpl::getReceivable(const std::string& destination)
+{
+ return get1<Receivable, uint32_t>(&destination);
+}
+
+struct SessionImpl::Receivable : Command
+{
+ const std::string* destination;
+ uint32_t result;
+
+ Receivable(SessionImpl& i, const std::string* d) : Command(i), destination(d), result(0) {}
+ void operator()() { result = impl.getReceivableImpl(destination); }
+};
+
+uint32_t SessionImpl::getReceivableImpl(const std::string* destination)
+{
+ ScopedLock l(lock);
+ if (destination) {
+ return incoming.available(*destination);
+ } else {
+ return incoming.available();
+ }
+}
+
+uint32_t SessionImpl::getUnsettledAcks()
+{
+ return get1<UnsettledAcks, uint32_t>((const std::string*) 0);
+}
+
+uint32_t SessionImpl::getUnsettledAcks(const std::string& destination)
+{
+ return get1<UnsettledAcks, uint32_t>(&destination);
+}
+
+struct SessionImpl::UnsettledAcks : Command
+{
+ const std::string* destination;
+ uint32_t result;
+
+ UnsettledAcks(SessionImpl& i, const std::string* d) : Command(i), destination(d), result(0) {}
+ void operator()() { result = impl.getUnsettledAcksImpl(destination); }
+};
+
+uint32_t SessionImpl::getUnsettledAcksImpl(const std::string* destination)
+{
+ ScopedLock l(lock);
+ if (destination) {
+ return incoming.pendingAccept(*destination);
+ } else {
+ return incoming.pendingAccept();
+ }
+}
+
+void SessionImpl::syncImpl(bool block)
+{
+ {
+ ScopedLock l(lock);
+ if (block) session.sync();
+ else session.flush();
+ }
+ //cleanup unconfirmed accept records:
+ incoming.pendingAccept();
+}
+
+void SessionImpl::commitImpl()
+{
+ ScopedLock l(lock);
+ incoming.accept();
+ session.txCommit();
+}
+
+void SessionImpl::rollbackImpl()
+{
+ ScopedLock l(lock);
+ for (Receivers::iterator i = receivers.begin(); i != receivers.end(); ++i) {
+ getImplPtr<Receiver, ReceiverImpl>(i->second)->stop();
+ }
+ //ensure that stop has been processed and all previously sent
+ //messages are available for release:
+ session.sync();
+ incoming.releaseAll();
+ session.txRollback();
+
+ for (Receivers::iterator i = receivers.begin(); i != receivers.end(); ++i) {
+ getImplPtr<Receiver, ReceiverImpl>(i->second)->start();
+ }
+}
+
+void SessionImpl::acknowledgeImpl()
+{
+ if (!transactional) incoming.accept();
+}
+
+void SessionImpl::acknowledgeImpl(qpid::messaging::Message& m, bool cumulative)
+{
+ if (!transactional) incoming.accept(MessageImplAccess::get(m).getInternalId(), cumulative);
+}
+
+void SessionImpl::rejectImpl(qpid::messaging::Message& m)
+{
+ SequenceSet set;
+ set.add(MessageImplAccess::get(m).getInternalId());
+ session.messageReject(set);
+}
+
+void SessionImpl::releaseImpl(qpid::messaging::Message& m)
+{
+ SequenceSet set;
+ set.add(MessageImplAccess::get(m).getInternalId());
+ session.messageRelease(set, true);
+}
+
+void SessionImpl::receiverCancelled(const std::string& name)
+{
+ {
+ ScopedLock l(lock);
+ receivers.erase(name);
+ session.sync();
+ incoming.releasePending(name);
+ }
+ incoming.wakeup();
+}
+
+void SessionImpl::releasePending(const std::string& name)
+{
+ ScopedLock l(lock);
+ incoming.releasePending(name);
+}
+
+void SessionImpl::senderCancelled(const std::string& name)
+{
+ ScopedLock l(lock);
+ senders.erase(name);
+}
+
+void SessionImpl::reconnect()
+{
+ if (transactional) {
+ if (committing)
+ txError = new TransactionUnknown("Transaction outcome unknown: transport failure");
+ else
+ txError = new TransactionAborted("Transaction aborted: transport failure");
+ txError.raise();
+ }
+ connection->reopen();
+}
+
+bool SessionImpl::backoff()
+{
+ return connection->backoff();
+}
+
+qpid::messaging::Connection SessionImpl::getConnection() const
+{
+ return qpid::messaging::Connection(connection.get());
+}
+
+void SessionImpl::rethrow(const qpid::SessionException& e) {
+ switch (e.code) {
+ case framing::execution::ERROR_CODE_NOT_ALLOWED:
+ case framing::execution::ERROR_CODE_UNAUTHORIZED_ACCESS: throw messaging::UnauthorizedAccess(e.what());
+
+ case framing::execution::ERROR_CODE_NOT_FOUND:
+ case framing::execution::ERROR_CODE_RESOURCE_DELETED: throw messaging::NotFound(e.what());
+
+ default: throw SessionError(e.what());
+ }
+}
+
+}}} // namespace qpid::client::amqp0_10
diff --git a/qpid/cpp/src/qpid/client/amqp0_10/SessionImpl.h b/qpid/cpp/src/qpid/client/amqp0_10/SessionImpl.h
new file mode 100644
index 0000000000..2bb72aa877
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/amqp0_10/SessionImpl.h
@@ -0,0 +1,259 @@
+#ifndef QPID_CLIENT_AMQP0_10_SESSIONIMPL_H
+#define QPID_CLIENT_AMQP0_10_SESSIONIMPL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/messaging/SessionImpl.h"
+#include "qpid/messaging/Duration.h"
+#include "qpid/messaging/exceptions.h"
+#include "qpid/client/Session.h"
+#include "qpid/client/SubscriptionManager.h"
+#include "qpid/client/amqp0_10/AddressResolution.h"
+#include "qpid/client/amqp0_10/IncomingMessages.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/sys/ExceptionHolder.h"
+#include <boost/intrusive_ptr.hpp>
+
+namespace qpid {
+
+namespace messaging {
+class Address;
+class Connection;
+class Message;
+class Receiver;
+class Sender;
+class Session;
+}
+
+namespace client {
+namespace amqp0_10 {
+
+class ConnectionImpl;
+class ReceiverImpl;
+class SenderImpl;
+
+/**
+ * Implementation of the protocol independent Session interface using
+ * AMQP 0-10.
+ */
+class SessionImpl : public qpid::messaging::SessionImpl
+{
+ public:
+ SessionImpl(ConnectionImpl&, bool transactional);
+ void commit();
+ void rollback();
+ void acknowledge(bool sync);
+ void reject(qpid::messaging::Message&);
+ void release(qpid::messaging::Message&);
+ void acknowledge(qpid::messaging::Message& msg, bool cumulative);
+ void close();
+ void sync(bool block);
+ qpid::messaging::Sender createSender(const qpid::messaging::Address& address);
+ qpid::messaging::Receiver createReceiver(const qpid::messaging::Address& address);
+
+ qpid::messaging::Sender getSender(const std::string& name) const;
+ qpid::messaging::Receiver getReceiver(const std::string& name) const;
+
+ bool nextReceiver(qpid::messaging::Receiver& receiver, qpid::messaging::Duration timeout);
+ qpid::messaging::Receiver nextReceiver(qpid::messaging::Duration timeout);
+
+ qpid::messaging::Connection getConnection() const;
+ void checkError();
+ bool hasError();
+ bool isTransactional() const;
+
+ bool get(ReceiverImpl& receiver, qpid::messaging::Message& message, qpid::messaging::Duration timeout);
+
+ void releasePending(const std::string& destination);
+ void receiverCancelled(const std::string& name);
+ void senderCancelled(const std::string& name);
+
+ uint32_t getReceivable();
+ uint32_t getReceivable(const std::string& destination);
+
+ uint32_t getUnsettledAcks();
+ uint32_t getUnsettledAcks(const std::string& destination);
+
+ void setSession(qpid::client::Session);
+
+ template <class T> bool execute(T& f)
+ {
+ try {
+ txError.raise();
+ f();
+ return true;
+ } catch (const qpid::TransportFailure&) {
+ reconnect();
+ return false;
+ } catch (const qpid::framing::ResourceLimitExceededException& e) {
+ if (backoff()) return false;
+ else throw qpid::messaging::TargetCapacityExceeded(e.what());
+ } catch (const qpid::framing::UnauthorizedAccessException& e) {
+ throw qpid::messaging::UnauthorizedAccess(e.what());
+ } catch (const qpid::framing::NotFoundException& e) {
+ throw qpid::messaging::NotFound(e.what());
+ } catch (const qpid::framing::ResourceDeletedException& e) {
+ throw qpid::messaging::NotFound(e.what());
+ } catch (const qpid::SessionException& e) {
+ rethrow(e);
+ return false; // Keep the compiler happy
+ } catch (const qpid::ConnectionException& e) {
+ throw qpid::messaging::ConnectionError(e.what());
+ } catch (const qpid::ChannelException& e) {
+ throw qpid::messaging::MessagingException(e.what());
+ }
+ }
+
+ static SessionImpl& convert(qpid::messaging::Session&);
+ static void rethrow(const qpid::SessionException&);
+
+ private:
+ typedef std::map<std::string, qpid::messaging::Receiver> Receivers;
+ typedef std::map<std::string, qpid::messaging::Sender> Senders;
+
+ mutable qpid::sys::Mutex lock;
+ boost::intrusive_ptr<ConnectionImpl> connection;
+ qpid::client::Session session;
+ AddressResolution resolver;
+ IncomingMessages incoming;
+ Receivers receivers;
+ Senders senders;
+ const bool transactional;
+ bool committing;
+ sys::ExceptionHolder txError;
+
+ bool accept(ReceiverImpl*, qpid::messaging::Message*, IncomingMessages::MessageTransfer&);
+ bool getIncoming(IncomingMessages::Handler& handler, qpid::messaging::Duration timeout);
+ bool getNextReceiver(qpid::messaging::Receiver* receiver, IncomingMessages::MessageTransfer& transfer);
+ void reconnect();
+ bool backoff();
+
+ void commitImpl();
+ void rollbackImpl();
+ void acknowledgeImpl();
+ void acknowledgeImpl(qpid::messaging::Message&, bool cumulative);
+ void rejectImpl(qpid::messaging::Message&);
+ void releaseImpl(qpid::messaging::Message&);
+ void closeImpl();
+ void syncImpl(bool block);
+ qpid::messaging::Sender createSenderImpl(const qpid::messaging::Address& address);
+ qpid::messaging::Receiver createReceiverImpl(const qpid::messaging::Address& address);
+ uint32_t getReceivableImpl(const std::string* destination);
+ uint32_t getUnsettledAcksImpl(const std::string* destination);
+
+ //functors for public facing methods (allows locking and retry
+ //logic to be centralised)
+ struct Command
+ {
+ SessionImpl& impl;
+
+ Command(SessionImpl& i) : impl(i) {}
+ };
+
+ struct Commit : Command
+ {
+ Commit(SessionImpl& i) : Command(i) {}
+ void operator()() { impl.commitImpl(); }
+ };
+
+ struct Rollback : Command
+ {
+ Rollback(SessionImpl& i) : Command(i) {}
+ void operator()() { impl.rollbackImpl(); }
+ };
+
+ struct Acknowledge : Command
+ {
+ Acknowledge(SessionImpl& i) : Command(i) {}
+ void operator()() { impl.acknowledgeImpl(); }
+ };
+
+ struct Sync : Command
+ {
+ Sync(SessionImpl& i) : Command(i) {}
+ void operator()() { impl.syncImpl(true); }
+ };
+
+ struct NonBlockingSync : Command
+ {
+ NonBlockingSync(SessionImpl& i) : Command(i) {}
+ void operator()() { impl.syncImpl(false); }
+ };
+
+ struct Reject : Command
+ {
+ qpid::messaging::Message& message;
+
+ Reject(SessionImpl& i, qpid::messaging::Message& m) : Command(i), message(m) {}
+ void operator()() { impl.rejectImpl(message); }
+ };
+
+ struct Release : Command
+ {
+ qpid::messaging::Message& message;
+
+ Release(SessionImpl& i, qpid::messaging::Message& m) : Command(i), message(m) {}
+ void operator()() { impl.releaseImpl(message); }
+ };
+
+ struct Acknowledge2 : Command
+ {
+ qpid::messaging::Message& message;
+ bool cumulative;
+
+ Acknowledge2(SessionImpl& i, qpid::messaging::Message& m, bool c) : Command(i), message(m), cumulative(c) {}
+ void operator()() { impl.acknowledgeImpl(message, cumulative); }
+ };
+
+ struct CreateSender;
+ struct CreateReceiver;
+ struct UnsettledAcks;
+ struct Receivable;
+
+ //helper templates for some common patterns
+ template <class F> bool execute()
+ {
+ F f(*this);
+ return execute(f);
+ }
+
+ template <class F> void retry()
+ {
+ while (!execute<F>()) {}
+ }
+
+ template <class F, class P> bool execute1(P p)
+ {
+ F f(*this, p);
+ return execute(f);
+ }
+
+ template <class F, class R, class P> R get1(P p)
+ {
+ F f(*this, p);
+ while (!execute(f)) {}
+ return f.result;
+ }
+};
+}}} // namespace qpid::client::amqp0_10
+
+#endif /*!QPID_CLIENT_AMQP0_10_SESSIONIMPL_H*/
diff --git a/qpid/cpp/src/qpid/client/ssl.h b/qpid/cpp/src/qpid/client/ssl.h
new file mode 100644
index 0000000000..0adef21f7e
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/ssl.h
@@ -0,0 +1,30 @@
+#ifndef QPID_CLIENT_SSL_H
+#define QPID_CLIENT_SSL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+namespace qpid {
+namespace client {
+void initialiseSSL();
+void shutdownSSL();
+}} // namespace qpid::client
+
+#endif /*!QPID_CLIENT_SSL_H*/
diff --git a/qpid/cpp/src/qpid/client/windows/ClientDllMain.cpp b/qpid/cpp/src/qpid/client/windows/ClientDllMain.cpp
new file mode 100644
index 0000000000..d636489908
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/windows/ClientDllMain.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/windows/QpidDllMain.h"
diff --git a/qpid/cpp/src/qpid/client/windows/SaslFactory.cpp b/qpid/cpp/src/qpid/client/windows/SaslFactory.cpp
new file mode 100644
index 0000000000..0f02c572ce
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/windows/SaslFactory.cpp
@@ -0,0 +1,202 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/SaslFactory.h"
+
+#include "qpid/Exception.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/sys/SecurityLayer.h"
+#include "qpid/sys/SecuritySettings.h"
+#include "qpid/log/Statement.h"
+#include "qpid/NullSaslServer.h"
+#include "qpid/sys/SystemInfo.h"
+
+#include "boost/tokenizer.hpp"
+
+namespace qpid {
+
+using qpid::sys::SecurityLayer;
+using qpid::sys::SecuritySettings;
+using qpid::framing::InternalErrorException;
+
+struct WindowsSaslSettings
+{
+ WindowsSaslSettings ( ) :
+ username ( std::string(0) ),
+ password ( std::string(0) ),
+ service ( std::string(0) ),
+ host ( std::string(0) ),
+ minSsf ( 0 ),
+ maxSsf ( 0 )
+ {
+ }
+
+ WindowsSaslSettings ( const std::string & user, const std::string & password, const std::string & service, const std::string & host, int minSsf, int maxSsf ) :
+ username(user),
+ password(password),
+ service(service),
+ host(host),
+ minSsf(minSsf),
+ maxSsf(maxSsf)
+ {
+ }
+
+ std::string username,
+ password,
+ service,
+ host;
+
+ int minSsf,
+ maxSsf;
+};
+
+class WindowsSasl : public Sasl
+{
+ public:
+ WindowsSasl( const std::string &, const std::string &, const std::string &, const std::string &, int, int );
+ ~WindowsSasl();
+ bool start(const std::string& mechanisms, std::string& response, const SecuritySettings* externalSettings);
+ std::string step(const std::string& challenge);
+ std::string getMechanism();
+ std::string getUserId();
+ std::auto_ptr<SecurityLayer> getSecurityLayer(uint16_t maxFrameSize);
+ private:
+ WindowsSaslSettings settings;
+ std::string mechanism;
+};
+
+qpid::sys::Mutex SaslFactory::lock;
+std::auto_ptr<SaslFactory> SaslFactory::instance;
+
+SaslFactory::SaslFactory()
+{
+}
+
+SaslFactory::~SaslFactory()
+{
+}
+
+SaslFactory& SaslFactory::getInstance()
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ if (!instance.get()) {
+ instance = std::auto_ptr<SaslFactory>(new SaslFactory());
+ }
+ return *instance;
+}
+
+std::auto_ptr<Sasl> SaslFactory::create( const std::string & username, const std::string & password, const std::string & serviceName, const std::string & hostName, int minSsf, int maxSsf, bool )
+{
+ std::auto_ptr<Sasl> sasl(new WindowsSasl( username, password, serviceName, hostName, minSsf, maxSsf ));
+ return sasl;
+}
+
+std::auto_ptr<SaslServer> SaslFactory::createServer( const std::string& realm, const std::string& /*service*/, bool /*encryptionRequired*/, const qpid::sys::SecuritySettings& )
+{
+ std::auto_ptr<SaslServer> server(new NullSaslServer(realm));
+ return server;
+}
+
+namespace {
+ const std::string ANONYMOUS = "ANONYMOUS";
+ const std::string PLAIN = "PLAIN";
+ const std::string EXTERNAL = "EXTERNAL";
+}
+
+WindowsSasl::WindowsSasl( const std::string & username, const std::string & password, const std::string & serviceName, const std::string & hostName, int minSsf, int maxSsf )
+ : settings(username, password, serviceName, hostName, minSsf, maxSsf)
+{
+}
+
+WindowsSasl::~WindowsSasl()
+{
+}
+
+bool WindowsSasl::start(const std::string& mechanisms, std::string& response,
+ const SecuritySettings* /*externalSettings*/)
+{
+ QPID_LOG(debug, "WindowsSasl::start(" << mechanisms << ")");
+
+ typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
+ boost::char_separator<char> sep(" ");
+ bool haveExt = false;
+ bool havePlain = false;
+ bool haveAnon = false;
+ tokenizer mechs(mechanisms, sep);
+ for (tokenizer::iterator mech = mechs.begin();
+ mech != mechs.end();
+ ++mech) {
+ if (*mech == EXTERNAL)
+ haveExt = true;
+ else if (*mech == ANONYMOUS)
+ haveAnon = true;
+ else if (*mech == PLAIN)
+ havePlain = true;
+ }
+ if (!haveAnon && !havePlain && !haveExt)
+ throw InternalErrorException(QPID_MSG("Sasl error: no common mechanism"));
+
+ if (haveExt && settings.username.size() > 0) {
+ mechanism = EXTERNAL;
+ response = ((char)0) + settings.username.c_str();
+ }
+ else if (havePlain && settings.username.size() > 0) {
+ mechanism = PLAIN;
+ response = ((char)0) + settings.username + ((char)0) + settings.password;
+ }
+ else if (haveAnon) {
+ std::string osName;
+ std::string nodeName;
+ std::string release;
+ std::string version;
+ std::string machine;
+ qpid::sys::SystemInfo::getSystemId(osName, nodeName, release, version, machine);
+
+ mechanism = ANONYMOUS;
+ response = "anonymous@" + nodeName;
+ } else {
+ throw InternalErrorException(QPID_MSG("Sasl error: no user name specified"));
+ }
+ return true;
+}
+
+std::string WindowsSasl::step(const std::string& /*challenge*/)
+{
+ // Shouldn't get this for PLAIN...
+ throw InternalErrorException(QPID_MSG("Sasl step error"));
+}
+
+std::string WindowsSasl::getMechanism()
+{
+ return mechanism;
+}
+
+std::string WindowsSasl::getUserId()
+{
+ return std::string(); // TODO - when GSSAPI is supported, return userId for connection.
+}
+
+std::auto_ptr<SecurityLayer> WindowsSasl::getSecurityLayer(uint16_t /*maxFrameSize*/)
+{
+ return std::auto_ptr<SecurityLayer>(0);
+}
+
+} // namespace qpid
diff --git a/qpid/cpp/src/qpid/client/windows/SslConnector.cpp b/qpid/cpp/src/qpid/client/windows/SslConnector.cpp
new file mode 100644
index 0000000000..dc82ece9d1
--- /dev/null
+++ b/qpid/cpp/src/qpid/client/windows/SslConnector.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/client/TCPConnector.h"
+
+#include "config.h"
+#include "qpid/Msg.h"
+#include "qpid/client/ConnectionImpl.h"
+#include "qpid/client/ConnectionSettings.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/Dispatcher.h"
+#include "qpid/sys/Poller.h"
+#include "qpid/sys/Time.h"
+#include "qpid/sys/windows/check.h"
+#include "qpid/sys/windows/util.h"
+#include "qpid/sys/windows/SslAsynchIO.h"
+#include "qpid/sys/windows/SslCredential.h"
+
+#include <boost/bind.hpp>
+
+#include <memory.h>
+#include <winsock2.h>
+
+
+
+namespace qpid {
+namespace client {
+namespace windows {
+
+using qpid::sys::Socket;
+
+class SslConnector : public qpid::client::TCPConnector
+{
+ qpid::sys::windows::ClientSslAsynchIO *shim;
+ boost::shared_ptr<qpid::sys::Poller> poller;
+ std::string brokerHost;
+ qpid::sys::windows::SslCredential sslCredential;
+ bool certLoaded;
+
+ void negotiationDone(SECURITY_STATUS status);
+
+ void connect(const std::string& host, const std::string& port);
+ void connected(const Socket&);
+
+public:
+ SslConnector(boost::shared_ptr<qpid::sys::Poller>,
+ framing::ProtocolVersion pVersion,
+ const ConnectionSettings&,
+ ConnectionImpl*);
+};
+
+// Static constructor which registers connector here
+namespace {
+ Connector* create(boost::shared_ptr<qpid::sys::Poller> p,
+ framing::ProtocolVersion v,
+ const ConnectionSettings& s,
+ ConnectionImpl* c) {
+ return new SslConnector(p, v, s, c);
+ }
+
+ struct StaticInit {
+ StaticInit() {
+ try {
+ CommonOptions common("", "", QPIDC_CONF_FILE);
+ qpid::sys::ssl::SslOptions options;
+ common.parse(0, 0, common.clientConfig, true);
+ options.parse (0, 0, common.clientConfig, true);
+ Connector::registerFactory("ssl", &create);
+ initWinSsl(options);
+ } catch (const std::exception& e) {
+ QPID_LOG(error, "Failed to initialise SSL connector: " << e.what());
+ }
+ };
+ ~StaticInit() { }
+ } init;
+
+}
+
+void SslConnector::negotiationDone(SECURITY_STATUS status)
+{
+ if (status == SEC_E_OK) {
+ initAmqp();
+ }
+ else {
+ if (status == SEC_E_INCOMPLETE_CREDENTIALS && !certLoaded) {
+ // Server requested a client cert but we supplied none for the following reason:
+ connectFailed(QPID_MSG(sslCredential.error()));
+ }
+ else
+ connectFailed(QPID_MSG(qpid::sys::strError(status)));
+ }
+}
+
+SslConnector::SslConnector(boost::shared_ptr<qpid::sys::Poller> p,
+ framing::ProtocolVersion ver,
+ const ConnectionSettings& settings,
+ ConnectionImpl* cimpl)
+ : TCPConnector(p, ver, settings, cimpl), shim(0), poller(p)
+{
+ if (settings.sslIgnoreHostnameVerificationFailure) {
+ sslCredential.ignoreHostnameVerificationFailure();
+ }
+ const std::string& name = (settings.sslCertName != "") ?
+ settings.sslCertName : qpid::sys::ssl::SslOptions::global.certName;
+ certLoaded = sslCredential.load(name);
+ QPID_LOG(debug, "SslConnector created for " << ver.toString());
+}
+
+void SslConnector::connect(const std::string& host, const std::string& port) {
+ brokerHost = host;
+ TCPConnector::connect(host, port);
+}
+
+void SslConnector::connected(const Socket& s) {
+ shim = new qpid::sys::windows::ClientSslAsynchIO(brokerHost,
+ s,
+ sslCredential.handle(),
+ boost::bind(&SslConnector::readbuff, this, _1, _2),
+ boost::bind(&SslConnector::eof, this, _1),
+ boost::bind(&SslConnector::disconnected, this, _1),
+ boost::bind(&SslConnector::socketClosed, this, _1, _2),
+ 0, // nobuffs
+ boost::bind(&SslConnector::writebuff, this, _1),
+ boost::bind(&SslConnector::negotiationDone, this, _1));
+ start(shim);
+ shim->start(poller);
+}
+
+}}} // namespace qpid::client::windows
diff --git a/qpid/cpp/src/qpid/framing/AMQBody.cpp b/qpid/cpp/src/qpid/framing/AMQBody.cpp
new file mode 100644
index 0000000000..b3eeae0615
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/AMQBody.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/framing/AMQBody.h"
+#include "qpid/framing/AMQMethodBody.h"
+#include "qpid/framing/AMQHeaderBody.h"
+#include "qpid/framing/AMQContentBody.h"
+#include "qpid/framing/AMQHeartbeatBody.h"
+#include <iostream>
+
+namespace qpid {
+namespace framing {
+
+std::ostream& operator<<(std::ostream& out, const AMQBody& body)
+{
+ body.print(out);
+ return out;
+}
+
+AMQBody::~AMQBody() {}
+
+namespace {
+struct MatchBodies : public AMQBodyConstVisitor {
+ const AMQBody& body;
+ bool match;
+
+ MatchBodies(const AMQBody& b) : body(b), match(false) {}
+ virtual ~MatchBodies() {}
+
+ virtual void visit(const AMQHeaderBody&) { match=dynamic_cast<const AMQHeaderBody*>(&body); }
+ virtual void visit(const AMQContentBody&) { match=dynamic_cast<const AMQContentBody*>(&body); }
+ virtual void visit(const AMQHeartbeatBody&) { match=dynamic_cast<const AMQHeartbeatBody*>(&body); }
+ virtual void visit(const AMQMethodBody& x) {
+ const AMQMethodBody* y=dynamic_cast<const AMQMethodBody*>(&body);
+ match = (y && y->amqpMethodId() == x.amqpMethodId() && y->amqpClassId() == x.amqpClassId());
+ }
+};
+
+}
+bool AMQBody::match(const AMQBody& a, const AMQBody& b) {
+ MatchBodies matcher(a);
+ b.accept(matcher);
+ return matcher.match;
+}
+
+}} // namespace
diff --git a/qpid/cpp/src/qpid/framing/AMQBody.h b/qpid/cpp/src/qpid/framing/AMQBody.h
new file mode 100644
index 0000000000..56d1d250c1
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/AMQBody.h
@@ -0,0 +1,86 @@
+#ifndef QPID_FRAMING_AMQBODY_H
+#define QPID_FRAMING_AMQBODY_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/framing/amqp_types.h"
+#include "qpid/RefCounted.h"
+#include "qpid/framing/BodyFactory.h"
+#include <boost/intrusive_ptr.hpp>
+#include <ostream>
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+namespace framing {
+
+class Buffer;
+
+class AMQMethodBody;
+class AMQHeaderBody;
+class AMQContentBody;
+class AMQHeartbeatBody;
+
+struct AMQBodyConstVisitor {
+ virtual ~AMQBodyConstVisitor() {}
+ virtual void visit(const AMQHeaderBody&) = 0;
+ virtual void visit(const AMQContentBody&) = 0;
+ virtual void visit(const AMQHeartbeatBody&) = 0;
+ virtual void visit(const AMQMethodBody&) = 0;
+};
+
+class QPID_COMMON_CLASS_EXTERN AMQBody : public RefCounted {
+ public:
+ AMQBody() {}
+ QPID_COMMON_EXTERN virtual ~AMQBody();
+
+ // Make AMQBody copyable even though RefCounted.
+ AMQBody(const AMQBody&) : RefCounted() {}
+ AMQBody& operator=(const AMQBody&) { return *this; }
+
+ virtual uint8_t type() const = 0;
+
+ virtual void encode(Buffer& buffer) const = 0;
+ virtual void decode(Buffer& buffer, uint32_t=0) = 0;
+ virtual uint32_t encodedSize() const = 0;
+
+ virtual void print(std::ostream& out) const = 0;
+ virtual void accept(AMQBodyConstVisitor&) const = 0;
+
+ virtual AMQMethodBody* getMethod() { return 0; }
+ virtual const AMQMethodBody* getMethod() const { return 0; }
+
+ /** Match if same type and same class/method ID for methods */
+ static bool match(const AMQBody& , const AMQBody& );
+ virtual boost::intrusive_ptr<AMQBody> clone() const = 0;
+};
+
+QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream& out, const AMQBody& body) ;
+
+enum BodyTypes {
+ METHOD_BODY = 1,
+ HEADER_BODY = 2,
+ CONTENT_BODY = 3,
+ HEARTBEAT_BODY = 8
+};
+
+}} // namespace qpid::framing
+
+#endif /*!QPID_FRAMING_AMQBODY_H*/
diff --git a/qpid/cpp/src/qpid/framing/AMQContentBody.cpp b/qpid/cpp/src/qpid/framing/AMQContentBody.cpp
new file mode 100644
index 0000000000..18f6994f8f
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/AMQContentBody.cpp
@@ -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.
+ *
+ */
+#include "qpid/framing/AMQContentBody.h"
+#include <iostream>
+
+qpid::framing::AMQContentBody::AMQContentBody(){
+}
+
+qpid::framing::AMQContentBody::AMQContentBody(const std::string& _data) : data(_data){
+}
+
+uint32_t qpid::framing::AMQContentBody::encodedSize() const{
+ return data.size();
+}
+void qpid::framing::AMQContentBody::encode(Buffer& buffer) const{
+ buffer.putRawData(data);
+}
+void qpid::framing::AMQContentBody::decode(Buffer& buffer, uint32_t _size){
+ buffer.getRawData(data, _size);
+}
+
+void qpid::framing::AMQContentBody::print(std::ostream& out) const
+{
+ out << "content (" << encodedSize() << " bytes)";
+ const size_t max = 32;
+ out << " " << data.substr(0, max);
+ if (data.size() > max) out << "...";
+}
diff --git a/qpid/cpp/src/qpid/framing/AMQContentBody.h b/qpid/cpp/src/qpid/framing/AMQContentBody.h
new file mode 100644
index 0000000000..148b293a2f
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/AMQContentBody.h
@@ -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.
+ *
+ */
+#include "qpid/framing/amqp_types.h"
+#include "qpid/framing/AMQBody.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/CommonImportExport.h"
+
+#ifndef _AMQContentBody_
+#define _AMQContentBody_
+
+namespace qpid {
+namespace framing {
+
+class QPID_COMMON_CLASS_EXTERN AMQContentBody : public AMQBody
+{
+ std::string data;
+
+public:
+ QPID_COMMON_EXTERN AMQContentBody();
+ QPID_COMMON_EXTERN AMQContentBody(const std::string& data);
+ inline virtual ~AMQContentBody(){}
+ inline uint8_t type() const { return CONTENT_BODY; };
+ inline const std::string& getData() const { return data; }
+ inline std::string& getData() { return data; }
+ QPID_COMMON_EXTERN uint32_t encodedSize() const;
+ QPID_COMMON_EXTERN void encode(Buffer& buffer) const;
+ QPID_COMMON_EXTERN void decode(Buffer& buffer, uint32_t size);
+ QPID_COMMON_EXTERN void print(std::ostream& out) const;
+ void accept(AMQBodyConstVisitor& v) const { v.visit(*this); }
+ boost::intrusive_ptr<AMQBody> clone() const { return BodyFactory::copy(*this); }
+};
+
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/framing/AMQDataBlock.h b/qpid/cpp/src/qpid/framing/AMQDataBlock.h
new file mode 100644
index 0000000000..7f0d0dc2b5
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/AMQDataBlock.h
@@ -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.
+ *
+ */
+#include "qpid/framing/Buffer.h"
+
+#ifndef _AMQDataBlock_
+#define _AMQDataBlock_
+
+namespace qpid {
+namespace framing {
+
+class AMQDataBlock
+{
+public:
+ virtual ~AMQDataBlock() {}
+ virtual void encode(Buffer& buffer) const = 0;
+ virtual bool decode(Buffer& buffer) = 0;
+ virtual uint32_t encodedSize() const = 0;
+};
+
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/framing/AMQFrame.cpp b/qpid/cpp/src/qpid/framing/AMQFrame.cpp
new file mode 100644
index 0000000000..5e065d598c
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/AMQFrame.cpp
@@ -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.
+ *
+ */
+#include "qpid/framing/AMQFrame.h"
+
+#include "qpid/framing/AMQMethodBody.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/framing/BodyFactory.h"
+#include "qpid/framing/MethodBodyFactory.h"
+#include "qpid/Msg.h"
+
+#include <boost/format.hpp>
+#include <iostream>
+
+namespace qpid {
+namespace framing {
+
+void AMQFrame::init() { bof = eof = bos = eos = true; subchannel=0; channel=0; }
+
+AMQFrame::AMQFrame(const boost::intrusive_ptr<AMQBody>& b) : body(b) { init(); }
+
+AMQFrame::AMQFrame(const AMQBody& b) : body(b.clone()) { init(); }
+
+AMQFrame::~AMQFrame() {}
+
+void AMQFrame::setMethod(ClassId c, MethodId m) { body = MethodBodyFactory::create(c,m); }
+
+uint32_t AMQFrame::encodedSize() const {
+ uint32_t size = frameOverhead() + body->encodedSize();
+ if (body->getMethod())
+ size += sizeof(ClassId)+sizeof(MethodId);
+ return size;
+}
+
+uint32_t AMQFrame::frameOverhead() {
+ return 12 /*frame header*/;
+}
+
+uint16_t AMQFrame::DECODE_SIZE_MIN=4;
+
+uint16_t AMQFrame::decodeSize(char* data) {
+ Buffer buf(data+2, DECODE_SIZE_MIN);
+ return buf.getShort();
+}
+
+void AMQFrame::encode(Buffer& buffer) const
+{
+ //set track first (controls on track 0, everything else on 1):
+ uint8_t track = getBody()->type() ? 1 : 0;
+
+ uint8_t flags = (bof ? 0x08 : 0) | (eof ? 0x04 : 0) | (bos ? 0x02 : 0) | (eos ? 0x01 : 0);
+ buffer.putOctet(flags);
+ buffer.putOctet(getBody()->type());
+ buffer.putShort(encodedSize());
+ buffer.putOctet(0);
+ buffer.putOctet(0x0f & track);
+ buffer.putShort(channel);
+ buffer.putLong(0);
+ const AMQMethodBody* method=getMethod();
+ if (method) {
+ buffer.putOctet(method->amqpClassId());
+ buffer.putOctet(method->amqpMethodId());
+ }
+ body->encode(buffer);
+}
+
+bool AMQFrame::decode(Buffer& buffer)
+{
+ if(buffer.available() < frameOverhead())
+ return false;
+ uint32_t pos = buffer.getPosition();
+
+ uint8_t flags = buffer.getOctet();
+ uint8_t framing_version = (flags & 0xc0) >> 6;
+ if (framing_version != 0)
+ throw FramingErrorException(QPID_MSG("Framing version unsupported"));
+ bof = flags & 0x08;
+ eof = flags & 0x04;
+ bos = flags & 0x02;
+ eos = flags & 0x01;
+ uint8_t type = buffer.getOctet();
+ uint16_t frame_size = buffer.getShort();
+ if (frame_size < frameOverhead())
+ throw FramingErrorException(QPID_MSG("Frame size too small " << frame_size));
+ uint8_t reserved1 = buffer.getOctet();
+ uint8_t field1 = buffer.getOctet();
+ subchannel = field1 & 0x0f;
+ channel = buffer.getShort();
+ (void) buffer.getLong(); // reserved2
+
+ // Verify that the protocol header meets current spec
+ // TODO: should we check reserved2 against zero as well? - the
+ // spec isn't clear
+ if ((flags & 0x30) != 0 || reserved1 != 0 || (field1 & 0xf0) != 0)
+ throw FramingErrorException(QPID_MSG("Reserved bits not zero"));
+
+ // TODO: should no longer care about body size and only pass up
+ // B,E,b,e flags
+ uint16_t body_size = frame_size - frameOverhead();
+ if (buffer.available() < body_size){
+ buffer.setPosition(pos);
+ return false;
+ }
+
+ switch(type)
+ {
+ case 0://CONTROL
+ case METHOD_BODY: {
+ ClassId c = buffer.getOctet();
+ MethodId m = buffer.getOctet();
+ body = MethodBodyFactory::create(c, m);
+ break;
+ }
+ case HEADER_BODY: body = BodyFactory::create<AMQHeaderBody>(); break;
+ case CONTENT_BODY: body = BodyFactory::create<AMQContentBody>(); break;
+ case HEARTBEAT_BODY: body = BodyFactory::create<AMQHeartbeatBody>(); break;
+ default:
+ throw IllegalArgumentException(QPID_MSG("Invalid frame type " << type));
+ }
+ body->decode(buffer, body_size);
+
+ return true;
+}
+
+void AMQFrame::cloneBody()
+{
+ body = body->clone();
+}
+
+std::ostream& operator<<(std::ostream& out, const AMQFrame& f)
+{
+ return
+ out << "Frame["
+ << (f.getBof() ? "B" : "") << (f.getEof() ? "E" : "")
+ << (f.getBos() ? "b" : "") << (f.getEos() ? "e" : "") << "; "
+ << "channel=" << f.getChannel() << "; " << *f.getBody()
+ << "]";
+}
+
+
+}} // namespace qpid::framing
diff --git a/qpid/cpp/src/qpid/framing/AMQFrame.h b/qpid/cpp/src/qpid/framing/AMQFrame.h
new file mode 100644
index 0000000000..19675ce6ff
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/AMQFrame.h
@@ -0,0 +1,116 @@
+#ifndef _AMQFrame_
+#define _AMQFrame_
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/framing/AMQDataBlock.h"
+#include "qpid/framing/AMQHeaderBody.h"
+#include "qpid/framing/AMQContentBody.h"
+#include "qpid/framing/AMQHeartbeatBody.h"
+#include "qpid/framing/ProtocolVersion.h"
+#include <boost/intrusive_ptr.hpp>
+#include <boost/cast.hpp>
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+namespace framing {
+
+class QPID_COMMON_CLASS_EXTERN AMQFrame : public AMQDataBlock
+{
+ public:
+ QPID_COMMON_EXTERN AMQFrame(const boost::intrusive_ptr<AMQBody>& b=0);
+ QPID_COMMON_EXTERN AMQFrame(const AMQBody& b);
+ QPID_COMMON_EXTERN ~AMQFrame();
+
+ ChannelId getChannel() const { return channel; }
+ void setChannel(ChannelId c) { channel = c; }
+
+ AMQBody* getBody() const { return body.get(); }
+
+ AMQMethodBody* getMethod() { return getBody() ? getBody()->getMethod() : 0; }
+ const AMQMethodBody* getMethod() const { return getBody() ? getBody()->getMethod() : 0; }
+
+ void setMethod(ClassId c, MethodId m);
+
+ template <class T> T* castBody() {
+ return boost::polymorphic_downcast<T*>(getBody());
+ }
+
+ template <class T> const T* castBody() const {
+ return boost::polymorphic_downcast<const T*>(getBody());
+ }
+
+ /**
+ * Take a deep copy of the body currently referenced
+ */
+ QPID_COMMON_EXTERN void cloneBody();
+
+ QPID_COMMON_EXTERN void encode(Buffer& buffer) const;
+ QPID_COMMON_EXTERN bool decode(Buffer& buffer);
+ QPID_COMMON_EXTERN uint32_t encodedSize() const;
+
+ // 0-10 terminology: first/last frame (in segment) first/last segment (in assembly)
+
+ bool isFirstSegment() const { return bof; }
+ bool isLastSegment() const { return eof; }
+ bool isFirstFrame() const { return bos; }
+ bool isLastFrame() const { return eos; }
+
+ void setFirstSegment(bool set=true) { bof = set; }
+ void setLastSegment(bool set=true) { eof = set; }
+ void setFirstFrame(bool set=true) { bos = set; }
+ void setLastFrame(bool set=true) { eos = set; }
+
+ // 0-9 terminology: beginning/end of frameset, beginning/end of segment.
+
+ bool getBof() const { return bof; }
+ void setBof(bool isBof) { bof = isBof; }
+ bool getEof() const { return eof; }
+ void setEof(bool isEof) { eof = isEof; }
+
+ bool getBos() const { return bos; }
+ void setBos(bool isBos) { bos = isBos; }
+ bool getEos() const { return eos; }
+ void setEos(bool isEos) { eos = isEos; }
+
+ static uint16_t DECODE_SIZE_MIN;
+ QPID_COMMON_EXTERN static uint32_t frameOverhead();
+ /** Must point to at least DECODE_SIZE_MIN bytes of data */
+ static uint16_t decodeSize(char* data);
+
+ private:
+ void init();
+
+ boost::intrusive_ptr<AMQBody> body;
+ uint16_t channel : 16;
+ uint8_t subchannel : 8;
+ bool bof : 1;
+ bool eof : 1;
+ bool bos : 1;
+ bool eos : 1;
+};
+
+QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream&, const AMQFrame&);
+
+}} // namespace qpid::framing
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/framing/AMQHeaderBody.cpp b/qpid/cpp/src/qpid/framing/AMQHeaderBody.cpp
new file mode 100644
index 0000000000..14218f1b45
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/AMQHeaderBody.cpp
@@ -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.
+ *
+ */
+#include "qpid/framing/AMQHeaderBody.h"
+#include "qpid/Exception.h"
+#include "qpid/log/Statement.h"
+
+uint32_t qpid::framing::AMQHeaderBody::encodedSize() const {
+ return properties.encodedSize();
+}
+
+void qpid::framing::AMQHeaderBody::encode(Buffer& buffer) const {
+ properties.encode(buffer);
+}
+
+void qpid::framing::AMQHeaderBody::decode(Buffer& buffer, uint32_t size) {
+ uint32_t limit = buffer.available() - size;
+ while (buffer.available() > limit + 2) {
+ uint32_t len = buffer.getLong();
+ uint16_t type = buffer.getShort();
+ if (!properties.decode(buffer, len, type)) {
+ // TODO: should just skip & keep for later dispatch.
+ throw Exception(QPID_MSG("Unexpected property type: " << type));
+ }
+ }
+}
+
+uint64_t qpid::framing::AMQHeaderBody::getContentLength() const
+{
+ const MessageProperties* mProps = get<MessageProperties>();
+ if (mProps)
+ return mProps->getContentLength();
+ return 0;
+}
+
+void qpid::framing::AMQHeaderBody::print(std::ostream& out) const
+{
+ out << "header (" << encodedSize() << " bytes)";
+ out << "; properties={";
+ properties.print(out);
+ out << "}";
+}
+
+void qpid::framing::AMQHeaderBody::accept(AMQBodyConstVisitor& v) const {
+ v.visit(*this);
+}
diff --git a/qpid/cpp/src/qpid/framing/AMQHeaderBody.h b/qpid/cpp/src/qpid/framing/AMQHeaderBody.h
new file mode 100644
index 0000000000..452154eb5c
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/AMQHeaderBody.h
@@ -0,0 +1,113 @@
+#ifndef QPID_FRAMING_AMQHEADERBODY_H
+#define QPID_FRAMING_AMQHEADERBODY_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/framing/amqp_types.h"
+#include "qpid/framing/AMQBody.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/framing/DeliveryProperties.h"
+#include "qpid/framing/MessageProperties.h"
+#include "qpid/CommonImportExport.h"
+#include <iostream>
+
+#include <boost/optional.hpp>
+
+
+namespace qpid {
+namespace framing {
+
+class QPID_COMMON_CLASS_EXTERN AMQHeaderBody : public AMQBody
+{
+ template <class T> struct OptProps { boost::optional<T> props; };
+ template <class Base, class T>
+ struct PropSet : public Base, public OptProps<T> {
+ uint32_t encodedSize() const {
+ const boost::optional<T>& p=this->OptProps<T>::props;
+ return (p ? p->encodedSize() : 0) + Base::encodedSize();
+ }
+ void encode(Buffer& buffer) const {
+ const boost::optional<T>& p=this->OptProps<T>::props;
+ if (p) p->encode(buffer);
+ Base::encode(buffer);
+ }
+ bool decode(Buffer& buffer, uint32_t size, uint16_t type) {
+ boost::optional<T>& p=this->OptProps<T>::props;
+ if (type == T::TYPE) {
+ p=T();
+ p->decodeStructBody(buffer, size);
+ return true;
+ }
+ else
+ return Base::decode(buffer, size, type);
+ }
+ void print(std::ostream& out) const {
+ const boost::optional<T>& p=this->OptProps<T>::props;
+ if (p) out << *p;
+ Base::print(out);
+ }
+ };
+
+ struct Empty {
+ uint32_t encodedSize() const { return 0; }
+ void encode(Buffer&) const {};
+ bool decode(Buffer&, uint32_t, uint16_t) const { return false; };
+ void print(std::ostream&) const {}
+ };
+
+ // Could use boost::mpl::fold to construct a larger set.
+ typedef PropSet<PropSet<Empty, DeliveryProperties>, MessageProperties> Properties;
+
+ Properties properties;
+
+public:
+
+ inline uint8_t type() const { return HEADER_BODY; }
+
+ QPID_COMMON_EXTERN uint32_t encodedSize() const;
+ QPID_COMMON_EXTERN void encode(Buffer& buffer) const;
+ QPID_COMMON_EXTERN void decode(Buffer& buffer, uint32_t size);
+ QPID_COMMON_EXTERN uint64_t getContentLength() const;
+ QPID_COMMON_EXTERN void print(std::ostream& out) const;
+ QPID_COMMON_EXTERN void accept(AMQBodyConstVisitor&) const;
+
+ template <class T> T* get(bool create) {
+ boost::optional<T>& p=properties.OptProps<T>::props;
+ if (create && !p) p=T();
+ return p.get_ptr();
+ }
+
+ template <class T> const T* get() const {
+ return properties.OptProps<T>::props.get_ptr();
+ }
+
+ template <class T> void erase() {
+ properties.OptProps<T>::props.reset();
+ }
+
+ boost::intrusive_ptr<AMQBody> clone() const { return BodyFactory::copy(*this); }
+};
+
+}}
+
+
+
+#endif /*!QPID_FRAMING_AMQHEADERBODY_H*/
diff --git a/qpid/cpp/src/qpid/framing/AMQHeartbeatBody.cpp b/qpid/cpp/src/qpid/framing/AMQHeartbeatBody.cpp
new file mode 100644
index 0000000000..477616221c
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/AMQHeartbeatBody.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/framing/AMQHeartbeatBody.h"
+#include <iostream>
+
+qpid::framing::AMQHeartbeatBody::~AMQHeartbeatBody() {}
+
+void qpid::framing::AMQHeartbeatBody::print(std::ostream& out) const {
+ out << "heartbeat";
+}
diff --git a/qpid/cpp/src/qpid/framing/AMQHeartbeatBody.h b/qpid/cpp/src/qpid/framing/AMQHeartbeatBody.h
new file mode 100644
index 0000000000..19ac2be013
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/AMQHeartbeatBody.h
@@ -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.
+ *
+ */
+#include "qpid/framing/amqp_types.h"
+#include "qpid/framing/AMQBody.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/CommonImportExport.h"
+
+#ifndef _AMQHeartbeatBody_
+#define _AMQHeartbeatBody_
+
+namespace qpid {
+namespace framing {
+
+class QPID_COMMON_CLASS_EXTERN AMQHeartbeatBody : public AMQBody
+{
+public:
+ QPID_COMMON_EXTERN virtual ~AMQHeartbeatBody();
+ inline uint32_t encodedSize() const { return 0; }
+ inline uint8_t type() const { return HEARTBEAT_BODY; }
+ inline void encode(Buffer& ) const {}
+ inline void decode(Buffer& , uint32_t /*size*/) {}
+ QPID_COMMON_EXTERN virtual void print(std::ostream& out) const;
+ void accept(AMQBodyConstVisitor& v) const { v.visit(*this); }
+ boost::intrusive_ptr<AMQBody> clone() const { return BodyFactory::copy(*this); }
+};
+
+}
+}
+
+#endif
diff --git a/qpid/cpp/src/qpid/framing/AMQMethodBody.cpp b/qpid/cpp/src/qpid/framing/AMQMethodBody.cpp
new file mode 100644
index 0000000000..594af4c6dc
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/AMQMethodBody.cpp
@@ -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.
+ *
+ */
+#include "qpid/framing/AMQMethodBody.h"
+
+namespace qpid {
+namespace framing {
+
+AMQMethodBody::~AMQMethodBody() {}
+
+}} // namespace qpid::framing
diff --git a/qpid/cpp/src/qpid/framing/AMQMethodBody.h b/qpid/cpp/src/qpid/framing/AMQMethodBody.h
new file mode 100644
index 0000000000..c634180712
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/AMQMethodBody.h
@@ -0,0 +1,72 @@
+#ifndef _AMQMethodBody_
+#define _AMQMethodBody_
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/framing/amqp_types.h"
+#include "qpid/framing/AMQBody.h"
+#include "qpid/framing/ProtocolVersion.h"
+#include "qpid/CommonImportExport.h"
+
+#include <boost/shared_ptr.hpp>
+#include <ostream>
+#include <assert.h>
+
+namespace qpid {
+namespace framing {
+
+class Buffer;
+class AMQP_ServerOperations;
+class MethodBodyConstVisitor;
+
+class AMQMethodBody : public AMQBody {
+ public:
+ AMQMethodBody() {}
+ QPID_COMMON_EXTERN virtual ~AMQMethodBody();
+
+ virtual void accept(MethodBodyConstVisitor&) const = 0;
+
+ virtual MethodId amqpMethodId() const = 0;
+ virtual ClassId amqpClassId() const = 0;
+ virtual bool isContentBearing() const = 0;
+ virtual bool resultExpected() const = 0;
+ virtual bool responseExpected() const = 0;
+
+ template <class T> bool isA() const {
+ return amqpClassId()==T::CLASS_ID && amqpMethodId()==T::METHOD_ID;
+ }
+
+ virtual uint32_t encodedSize() const = 0;
+ virtual uint8_t type() const { return METHOD_BODY; }
+
+ virtual bool isSync() const { return false; /*only ModelMethods can have the sync flag set*/ }
+ virtual void setSync(bool) const { /*only ModelMethods can have the sync flag set*/ }
+
+ AMQMethodBody* getMethod() { return this; }
+ const AMQMethodBody* getMethod() const { return this; }
+ void accept(AMQBodyConstVisitor& v) const { v.visit(*this); }
+};
+
+
+}} // namespace qpid::framing
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/framing/AMQP_HighestVersion.h b/qpid/cpp/src/qpid/framing/AMQP_HighestVersion.h
new file mode 100644
index 0000000000..42139c7937
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/AMQP_HighestVersion.h
@@ -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.
+ *
+ */
+
+/*
+ * This file used to be auto-generated by Qpid Gentools v.0.1
+ * its here temporarily until we get a full solution to multi-version support
+ */
+#ifndef qpid_framing_highestProtocolVersion__
+#define qpid_framing_highestProtocolVersion__
+
+#include "qpid/framing/ProtocolVersion.h"
+
+
+namespace qpid {
+namespace framing {
+
+static ProtocolVersion highestProtocolVersion(0, 10);
+
+} /* namespace framing */
+} /* namespace qpid */
+
+#endif
diff --git a/qpid/cpp/src/qpid/framing/AccumulatedAck.cpp b/qpid/cpp/src/qpid/framing/AccumulatedAck.cpp
new file mode 100644
index 0000000000..2e6433a82f
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/AccumulatedAck.cpp
@@ -0,0 +1,164 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/framing/AccumulatedAck.h"
+
+#include <assert.h>
+#include <iostream>
+#include <boost/bind.hpp>
+
+using std::list;
+using std::max;
+using std::min;
+using namespace qpid::framing;
+
+AccumulatedAck::AccumulatedAck(SequenceNumber r) : mark(r) {}
+
+void AccumulatedAck::update(SequenceNumber first, SequenceNumber last){
+ assert(first <= last);
+ if (last < mark) return;
+
+
+ Range r(first, last);
+ bool handled = false;
+ bool markMerged = false;
+ list<Range>::iterator merged = ranges.end();
+ if (r.mergeable(mark)) {
+ mark = r.end;
+ markMerged = true;
+ handled = true;
+ } else {
+ for (list<Range>::iterator i = ranges.begin(); i != ranges.end() && !handled; i++) {
+ if (i->merge(r)) {
+ merged = i;
+ handled = true;
+ } else if (r.start < i->start) {
+ ranges.insert(i, r);
+ handled = true;
+ }
+ }
+ }
+ if (!handled) {
+ ranges.push_back(r);
+ } else {
+ while (!ranges.empty() && ranges.front().end <= mark) {
+ ranges.pop_front();
+ }
+ if (markMerged) {
+ //new range is incorporated, but may be possible to consolidate
+ merged = ranges.begin();
+ while (merged != ranges.end() && merged->mergeable(mark)) {
+ mark = merged->end;
+ merged = ranges.erase(merged);
+ }
+ }
+ if (merged != ranges.end()) {
+ //consolidate ranges
+ list<Range>::iterator i = merged;
+ list<Range>::iterator j = i++;
+ while (i != ranges.end() && j->merge(*i)) {
+ j = i++;
+ }
+ }
+ }
+}
+
+void AccumulatedAck::consolidate(){}
+
+void AccumulatedAck::clear(){
+ mark = SequenceNumber(0);//not sure that this is valid when wraparound is a possibility
+ ranges.clear();
+}
+
+bool AccumulatedAck::covers(SequenceNumber tag) const{
+ if (tag <= mark) return true;
+ for (list<Range>::const_iterator i = ranges.begin(); i != ranges.end(); i++) {
+ if (i->contains(tag)) return true;
+ }
+ return false;
+}
+
+void AccumulatedAck::collectRanges(SequenceNumberSet& set) const
+{
+ for (list<Range>::const_iterator i = ranges.begin(); i != ranges.end(); i++) {
+ set.push_back(i->start);
+ set.push_back(i->end);
+ }
+}
+
+void AccumulatedAck::update(const SequenceNumber cumulative, const SequenceNumberSet& range)
+{
+ update(mark, cumulative);
+ range.processRanges(*this);
+}
+
+
+bool Range::contains(SequenceNumber i) const
+{
+ return i >= start && i <= end;
+}
+
+bool Range::intersect(const Range& r) const
+{
+ return r.contains(start) || r.contains(end) || contains(r.start) || contains(r.end);
+}
+
+bool Range::merge(const Range& r)
+{
+ if (intersect(r) || mergeable(r.end) || r.mergeable(end)) {
+ start = min(start, r.start);
+ end = max(end, r.end);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool Range::mergeable(const SequenceNumber& s) const
+{
+ if (contains(s) || start - s == 1) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+Range::Range(SequenceNumber s, SequenceNumber e) : start(s), end(e) {}
+
+
+namespace qpid{
+namespace framing{
+ std::ostream& operator<<(std::ostream& out, const Range& r)
+ {
+ out << "[" << r.start.getValue() << "-" << r.end.getValue() << "]";
+ return out;
+ }
+
+ std::ostream& operator<<(std::ostream& out, const AccumulatedAck& a)
+ {
+ out << "{mark: " << a.mark.getValue() << ", ranges: (";
+ for (list<Range>::const_iterator i = a.ranges.begin(); i != a.ranges.end(); i++) {
+ if (i != a.ranges.begin()) out << ", ";
+ out << *i;
+ }
+ out << ")]";
+ return out;
+ }
+}}
diff --git a/qpid/cpp/src/qpid/framing/AccumulatedAck.h b/qpid/cpp/src/qpid/framing/AccumulatedAck.h
new file mode 100644
index 0000000000..8e241b4ba1
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/AccumulatedAck.h
@@ -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.
+ *
+ */
+#ifndef _AccumulatedAck_
+#define _AccumulatedAck_
+
+#include <algorithm>
+#include <functional>
+#include <list>
+#include <ostream>
+#include "qpid/framing/SequenceNumber.h"
+#include "qpid/framing/SequenceNumberSet.h"
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+ namespace framing {
+
+ struct Range
+ {
+ SequenceNumber start;
+ SequenceNumber end;
+
+ Range(SequenceNumber s, SequenceNumber e);
+ bool contains(SequenceNumber i) const;
+ bool intersect(const Range& r) const;
+ bool merge(const Range& r);
+ bool mergeable(const SequenceNumber& r) const;
+ };
+ /**
+ * Keeps an accumulated record of acknowledged messages (by delivery
+ * tag).
+ */
+ class AccumulatedAck {
+ public:
+ /**
+ * Everything up to this value has been acknowledged.
+ */
+ SequenceNumber mark;
+ /**
+ * List of individually acknowledged messages greater than the
+ * 'mark'.
+ */
+ std::list<Range> ranges;
+
+ QPID_COMMON_EXTERN explicit AccumulatedAck(SequenceNumber r = SequenceNumber());
+ QPID_COMMON_EXTERN void update(SequenceNumber firstTag, SequenceNumber lastTag);
+ QPID_COMMON_EXTERN void consolidate();
+ QPID_COMMON_EXTERN void clear();
+ QPID_COMMON_EXTERN bool covers(SequenceNumber tag) const;
+ void collectRanges(SequenceNumberSet& set) const;
+ QPID_COMMON_EXTERN void update(const SequenceNumber cumulative, const SequenceNumberSet& range);
+ void operator()(SequenceNumber first, SequenceNumber last) { update(first, last); }
+ };
+ QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream&, const Range&);
+ QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream&, const AccumulatedAck&);
+ }
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/framing/Array.cpp b/qpid/cpp/src/qpid/framing/Array.cpp
new file mode 100644
index 0000000000..4b4338f931
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/Array.cpp
@@ -0,0 +1,137 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/framing/Array.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/Exception.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/Msg.h"
+#include <assert.h>
+
+namespace qpid {
+namespace framing {
+
+Array::Array() : type(TYPE_CODE_VOID) {}
+
+Array::Array(TypeCode t) : type(t) {}
+
+Array::Array(uint8_t t) : type(typeCode(t)) {}
+
+Array::Array(const std::vector<std::string>& in)
+{
+ type = TYPE_CODE_STR16;
+ for (std::vector<std::string>::const_iterator i = in.begin(); i != in.end(); ++i) {
+ ValuePtr value(new Str16Value(*i));
+ values.push_back(value);
+ }
+}
+
+uint32_t Array::encodedSize() const {
+ //note: size is only included when used as a 'top level' type
+ uint32_t len(4/*size*/ + 1/*type*/ + 4/*count*/);
+ for(ValueVector::const_iterator i = values.begin(); i != values.end(); ++i) {
+ len += (*i)->getData().encodedSize();
+ }
+ return len;
+}
+
+int Array::count() const {
+ return values.size();
+}
+
+std::ostream& operator<<(std::ostream& out, const Array& a) {
+ out << typeName(a.getType()) << "{";
+ for(Array::ValueVector::const_iterator i = a.values.begin(); i != a.values.end(); ++i) {
+ if (i != a.values.begin()) out << ", ";
+ (*i)->print(out);
+ }
+ return out << "}";
+}
+
+void Array::encode(Buffer& buffer) const{
+ buffer.putLong(encodedSize() - 4);//size added only when array is a top-level type
+ buffer.putOctet(type);
+ buffer.putLong(count());
+ for (ValueVector::const_iterator i = values.begin(); i!=values.end(); ++i) {
+ (*i)->getData().encode(buffer);
+ }
+}
+
+void Array::decode(Buffer& buffer){
+ values.clear();
+ uint32_t size = buffer.getLong();//size added only when array is a top-level type
+ uint32_t available = buffer.available();
+ if (available < size) {
+ throw IllegalArgumentException(QPID_MSG("Not enough data for array, expected "
+ << size << " bytes but only " << available << " available"));
+ }
+ if (size) {
+ type = TypeCode(buffer.getOctet());
+ uint32_t count = buffer.getLong();
+
+ FieldValue dummy;
+ dummy.setType(type);
+ available = buffer.available();
+ uint32_t elementSize = dummy.getData().encodedSize();
+ if (available < count * elementSize) {
+ throw IllegalArgumentException(QPID_MSG("Not enough data for array, expected "
+ << count << " items of " << elementSize
+ << " bytes each but only " << available << " bytes available"));
+ }
+ // Special check to avoid ridiculously long arrays of zero length elements (they must all be the same
+ // value, but consume broker resources without consuming any on the wire)
+ if (elementSize == 0 && count > 256) {
+ throw IllegalArgumentException(QPID_MSG("Too many zero length elements in array: " << count));
+ }
+
+ for (uint32_t i = 0; i < count; i++) {
+ ValuePtr value(new FieldValue);
+ value->setType(type);
+ value->getData().decode(buffer);
+ values.push_back(ValuePtr(value));
+ }
+ }
+}
+
+
+bool Array::operator==(const Array& x) const {
+ if (type != x.type) return false;
+ if (values.size() != x.values.size()) return false;
+
+ for (ValueVector::const_iterator i = values.begin(), j = x.values.begin(); i != values.end(); ++i, ++j) {
+ if (*(i->get()) != *(j->get())) return false;
+ }
+
+ return true;
+}
+
+void Array::insert(iterator i, ValuePtr value) {
+ if (type != value->getType()) {
+ // FIXME aconway 2008-10-31: put meaningful strings in this message.
+ throw Exception(QPID_MSG("Wrong type of value in Array, expected " << type
+ << " but found " << TypeCode(value->getType())));
+ }
+ values.insert(i, value);
+}
+
+
+}
+}
diff --git a/qpid/cpp/src/qpid/framing/Array.h b/qpid/cpp/src/qpid/framing/Array.h
new file mode 100644
index 0000000000..6254f6271a
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/Array.h
@@ -0,0 +1,99 @@
+#ifndef QPID_FRAMING_ARRAY_H
+#define QPID_FRAMING_ARRAY_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/framing/amqp_types.h"
+#include "qpid/framing/TypeCode.h"
+
+#include <boost/shared_ptr.hpp>
+
+#include <iostream>
+#include <vector>
+
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+namespace framing {
+
+class Buffer;
+class FieldValue;
+
+class QPID_COMMON_CLASS_EXTERN Array
+{
+ public:
+ typedef boost::shared_ptr<FieldValue> ValuePtr;
+ typedef std::vector<ValuePtr> ValueVector;
+ typedef ValueVector::const_iterator const_iterator;
+ typedef ValueVector::iterator iterator;
+
+ QPID_COMMON_EXTERN uint32_t encodedSize() const;
+ QPID_COMMON_EXTERN void encode(Buffer& buffer) const;
+ QPID_COMMON_EXTERN void decode(Buffer& buffer);
+
+ QPID_COMMON_EXTERN int count() const;
+ QPID_COMMON_EXTERN bool operator==(const Array& other) const;
+
+ QPID_COMMON_EXTERN Array();
+ QPID_COMMON_EXTERN Array(TypeCode type);
+ QPID_COMMON_EXTERN Array(uint8_t type);
+ //creates a longstr array
+ QPID_COMMON_EXTERN Array(const std::vector<std::string>& in);
+
+ QPID_COMMON_INLINE_EXTERN TypeCode getType() const { return type; }
+
+ // std collection interface.
+ QPID_COMMON_INLINE_EXTERN const_iterator begin() const { return values.begin(); }
+ QPID_COMMON_INLINE_EXTERN const_iterator end() const { return values.end(); }
+ QPID_COMMON_INLINE_EXTERN iterator begin() { return values.begin(); }
+ QPID_COMMON_INLINE_EXTERN iterator end(){ return values.end(); }
+
+ QPID_COMMON_INLINE_EXTERN ValuePtr front() const { return values.front(); }
+ QPID_COMMON_INLINE_EXTERN ValuePtr back() const { return values.back(); }
+ QPID_COMMON_INLINE_EXTERN size_t size() const { return values.size(); }
+
+ QPID_COMMON_EXTERN void insert(iterator i, ValuePtr value);
+ QPID_COMMON_INLINE_EXTERN void erase(iterator i) { values.erase(i); }
+ QPID_COMMON_INLINE_EXTERN void push_back(ValuePtr value) { values.insert(end(), value); }
+ QPID_COMMON_INLINE_EXTERN void pop_back() { values.pop_back(); }
+
+ // Non-std interface
+ QPID_COMMON_INLINE_EXTERN void add(ValuePtr value) { push_back(value); }
+
+ // For use in standard algorithms
+ template <typename R, typename V>
+ static R get(const V& v) {
+ return v->template get<R>();
+ }
+
+ private:
+ TypeCode type;
+ ValueVector values;
+
+ friend QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream& out, const Array& body);
+};
+
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/framing/Blob.cpp b/qpid/cpp/src/qpid/framing/Blob.cpp
new file mode 100644
index 0000000000..0c8316f3d2
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/Blob.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/framing/Blob.h"
+
+
+namespace qpid {
+namespace framing {
+
+void BlobHelper<void>::destroy(void*) {}
+
+void BlobHelper<void>::copy(void*, const void*) {}
+
+}} // namespace qpid::framing
diff --git a/qpid/cpp/src/qpid/framing/Blob.h b/qpid/cpp/src/qpid/framing/Blob.h
new file mode 100644
index 0000000000..9878d92fe4
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/Blob.h
@@ -0,0 +1,21 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
diff --git a/qpid/cpp/src/qpid/framing/BodyFactory.h b/qpid/cpp/src/qpid/framing/BodyFactory.h
new file mode 100644
index 0000000000..6a8d9b1988
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/BodyFactory.h
@@ -0,0 +1,47 @@
+#ifndef QPID_FRAMING_BODYFACTORY_H
+#define QPID_FRAMING_BODYFACTORY_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <boost/intrusive_ptr.hpp>
+
+namespace qpid {
+namespace framing {
+
+/**
+ * Indirect creation of body types to allow centralized changes to
+ * memory management strategy.
+ */
+class BodyFactory {
+ public:
+ template <class BodyType> static boost::intrusive_ptr<BodyType> create() {
+ return new BodyType;
+ }
+
+ template <class BodyType> static boost::intrusive_ptr<BodyType> copy(const BodyType& body) {
+ return new BodyType(body);
+ }
+};
+
+}} // namespace qpid::framing
+
+#endif /*!QPID_FRAMING_BODYFACTORY_H*/
diff --git a/qpid/cpp/src/qpid/framing/Buffer.cpp b/qpid/cpp/src/qpid/framing/Buffer.cpp
new file mode 100644
index 0000000000..1c4caef046
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/Buffer.cpp
@@ -0,0 +1,342 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/framing/Buffer.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/Msg.h"
+#include <string.h>
+#include <boost/format.hpp>
+
+namespace qpid {
+
+namespace framing {
+
+using std::string;
+
+Buffer::Buffer(char* _data, uint32_t _size)
+ : size(_size), data(_data), position(0) {
+}
+
+void Buffer::reset(){
+ position = 0;
+}
+
+///////////////////////////////////////////////////
+
+void Buffer::putOctet(uint8_t i){
+ checkAvailable(1);
+ data[position++] = i;
+}
+
+void Buffer::putShort(uint16_t i){
+ checkAvailable(2);
+ uint16_t b = i;
+ data[position++] = (uint8_t) (0xFF & (b >> 8));
+ data[position++] = (uint8_t) (0xFF & b);
+}
+
+void Buffer::putLong(uint32_t i){
+ checkAvailable(4);
+ uint32_t b = i;
+ data[position++] = (uint8_t) (0xFF & (b >> 24));
+ data[position++] = (uint8_t) (0xFF & (b >> 16));
+ data[position++] = (uint8_t) (0xFF & (b >> 8));
+ data[position++] = (uint8_t) (0xFF & b);
+}
+
+void Buffer::putLongLong(uint64_t i){
+ uint32_t hi = i >> 32;
+ uint32_t lo = i;
+ putLong(hi);
+ putLong(lo);
+}
+
+void Buffer::putInt8(int8_t i){
+ checkAvailable(1);
+ data[position++] = (uint8_t) i;
+}
+
+void Buffer::putInt16(int16_t i){
+ putShort((uint16_t) i);
+}
+
+void Buffer::putInt32(int32_t i){
+ putLong((uint32_t) i);
+}
+
+void Buffer::putInt64(int64_t i){
+ putLongLong((uint64_t) i);
+}
+
+void Buffer::putFloat(float f){
+ union {
+ uint32_t i;
+ float f;
+ } val;
+
+ val.f = f;
+ putLong (val.i);
+}
+
+void Buffer::putDouble(double f){
+ union {
+ uint64_t i;
+ double f;
+ } val;
+
+ val.f = f;
+ putLongLong (val.i);
+}
+
+void Buffer::putBin128(const uint8_t* b){
+ checkAvailable(16);
+ memcpy (data + position, b, 16);
+ position += 16;
+}
+
+uint8_t Buffer::getOctet(){
+ checkAvailable(1);
+ uint8_t octet = static_cast<uint8_t>(data[position++]);
+ return octet;
+}
+
+uint16_t Buffer::getShort(){
+ checkAvailable(2);
+ uint16_t hi = (unsigned char) data[position++];
+ hi = hi << 8;
+ hi |= (unsigned char) data[position++];
+ return hi;
+}
+
+uint32_t Buffer::getLong(){
+ checkAvailable(4);
+ uint32_t a = (unsigned char) data[position++];
+ uint32_t b = (unsigned char) data[position++];
+ uint32_t c = (unsigned char) data[position++];
+ uint32_t d = (unsigned char) data[position++];
+ a = a << 24;
+ a |= b << 16;
+ a |= c << 8;
+ a |= d;
+ return a;
+}
+
+uint64_t Buffer::getLongLong(){
+ uint64_t hi = getLong();
+ uint64_t lo = getLong();
+ hi = hi << 32;
+ return hi | lo;
+}
+
+int8_t Buffer::getInt8(){
+ checkAvailable(1);
+ int8_t i = static_cast<int8_t>(data[position++]);
+ return i;
+}
+
+int16_t Buffer::getInt16(){
+ return (int16_t) getShort();
+}
+
+int32_t Buffer::getInt32(){
+ return (int32_t) getLong();
+}
+
+int64_t Buffer::getInt64(){
+ return (int64_t) getLongLong();
+}
+
+float Buffer::getFloat(){
+ union {
+ uint32_t i;
+ float f;
+ } val;
+ val.i = getLong();
+ return val.f;
+}
+
+double Buffer::getDouble(){
+ union {
+ uint64_t i;
+ double f;
+ } val;
+ val.i = getLongLong();
+ return val.f;
+}
+
+template <>
+QPID_COMMON_EXTERN uint64_t Buffer::getUInt<1>() {
+ return getOctet();
+}
+
+template <>
+QPID_COMMON_EXTERN uint64_t Buffer::getUInt<2>() {
+ return getShort();
+}
+
+template <>
+QPID_COMMON_EXTERN uint64_t Buffer::getUInt<4>() {
+ return getLong();
+}
+
+template <>
+QPID_COMMON_EXTERN uint64_t Buffer::getUInt<8>() {
+ return getLongLong();
+}
+
+template <>
+QPID_COMMON_EXTERN void Buffer::putUInt<1>(uint64_t i) {
+ if (std::numeric_limits<uint8_t>::min() <= i && i <= std::numeric_limits<uint8_t>::max()) {
+ putOctet(i);
+ return;
+ }
+ throw Exception(QPID_MSG("Could not encode (" << i << ") as uint8_t."));
+}
+
+template <>
+QPID_COMMON_EXTERN void Buffer::putUInt<2>(uint64_t i) {
+ if (std::numeric_limits<uint16_t>::min() <= i && i <= std::numeric_limits<uint16_t>::max()) {
+ putShort(i);
+ return;
+ }
+ throw Exception(QPID_MSG("Could not encode (" << i << ") as uint16_t."));
+}
+
+template <>
+QPID_COMMON_EXTERN void Buffer::putUInt<4>(uint64_t i) {
+ if (std::numeric_limits<uint32_t>::min() <= i && i <= std::numeric_limits<uint32_t>::max()) {
+ putLong(i);
+ return;
+ }
+ throw Exception(QPID_MSG("Could not encode (" << i << ") as uint32_t."));
+}
+
+template <>
+QPID_COMMON_EXTERN void Buffer::putUInt<8>(uint64_t i) {
+ putLongLong(i);
+}
+
+void Buffer::putShortString(const string& s){
+ size_t slen = s.length();
+ if (slen <= std::numeric_limits<uint8_t>::max()) {
+ uint8_t len = (uint8_t) slen;
+ putOctet(len);
+ checkAvailable(slen);
+ s.copy(data + position, len);
+ position += len;
+ return;
+ }
+ throw Exception(QPID_MSG("Could not encode string of " << slen << " bytes as uint8_t string."));
+}
+
+void Buffer::putMediumString(const string& s){
+ size_t slen = s.length();
+ if (slen <= std::numeric_limits<uint16_t>::max()) {
+ uint16_t len = (uint16_t) slen;
+ putShort(len);
+ checkAvailable(slen);
+ s.copy(data + position, len);
+ position += len;
+ return;
+ }
+ throw Exception(QPID_MSG("Could not encode string of " << slen << " bytes as uint16_t string."));
+}
+
+void Buffer::putLongString(const string& s){
+ size_t slen = s.length();
+ if (slen <= std::numeric_limits<uint32_t>::max()) {
+ uint32_t len = (uint32_t) slen;
+ putLong(len);
+ checkAvailable(slen);
+ s.copy(data + position, len);
+ position += len;
+ return;
+ }
+ throw Exception(QPID_MSG("Could not encode string of " << slen << " bytes as uint32_t string."));
+}
+
+void Buffer::getShortString(string& s){
+ uint8_t len = getOctet();
+ checkAvailable(len);
+ s.assign(data + position, len);
+ position += len;
+}
+
+void Buffer::getMediumString(string& s){
+ uint16_t len = getShort();
+ checkAvailable(len);
+ s.assign(data + position, len);
+ position += len;
+}
+
+void Buffer::getLongString(string& s){
+ uint32_t len = getLong();
+ checkAvailable(len);
+ s.assign(data + position, len);
+ position += len;
+}
+
+void Buffer::getBin128(uint8_t* b){
+ checkAvailable(16);
+ memcpy (b, data + position, 16);
+ position += 16;
+}
+
+void Buffer::putRawData(const string& s){
+ size_t len = s.length();
+ checkAvailable(len);
+ s.copy(data + position, len);
+ position += len;
+}
+
+void Buffer::getRawData(string& s, uint32_t len){
+ checkAvailable(len);
+ s.assign(data + position, len);
+ position += len;
+}
+
+void Buffer::putRawData(const uint8_t* s, size_t len){
+ checkAvailable(len);
+ memcpy(data + position, s, len);
+ position += len;
+}
+
+void Buffer::getRawData(uint8_t* s, size_t len){
+ checkAvailable(len);
+ memcpy(s, data + position, len);
+ position += len;
+}
+
+void Buffer::dump(std::ostream& out) const {
+ for (uint32_t i = position; i < size; i++)
+ {
+ if (i != position)
+ out << " ";
+ out << boost::format("%02x") % ((unsigned) (uint8_t) data[i]);
+ }
+}
+
+std::ostream& operator<<(std::ostream& out, const Buffer& b){
+ out << "Buffer[";
+ b.dump(out);
+ return out << "]";
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/framing/Buffer.h b/qpid/cpp/src/qpid/framing/Buffer.h
new file mode 100644
index 0000000000..166b524e3c
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/Buffer.h
@@ -0,0 +1,115 @@
+#ifndef QPID_FRAMING_BUFFER_H
+#define QPID_FRAMING_BUFFER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/Exception.h"
+#include "qpid/CommonImportExport.h"
+#include "qpid/sys/IntegerTypes.h"
+
+#include <string>
+
+namespace qpid {
+namespace framing {
+
+struct QPID_COMMON_CLASS_EXTERN OutOfBounds : qpid::Exception {
+ OutOfBounds() : qpid::Exception(std::string("Out of Bounds")) {}
+};
+
+class Content;
+class FieldTable;
+
+class QPID_COMMON_CLASS_EXTERN Buffer
+{
+ uint32_t size;
+ char* data;
+ uint32_t position;
+
+ public:
+ void checkAvailable(size_t count) { if (count > size - position) throw OutOfBounds(); }
+
+ QPID_COMMON_EXTERN Buffer(char* data=0, uint32_t size=0);
+
+ QPID_COMMON_EXTERN void reset();
+
+ QPID_COMMON_INLINE_EXTERN uint32_t available() const{ return size - position; }
+ QPID_COMMON_INLINE_EXTERN uint32_t getSize() const { return size; }
+ QPID_COMMON_INLINE_EXTERN uint32_t getPosition() const { return position; }
+ QPID_COMMON_INLINE_EXTERN void setPosition(uint32_t p) { position = p; }
+ QPID_COMMON_INLINE_EXTERN const char * getPointer() const { return data; }
+ QPID_COMMON_INLINE_EXTERN char* getPointer() { return data; }
+
+ QPID_COMMON_EXTERN void putOctet(uint8_t i);
+ QPID_COMMON_EXTERN void putShort(uint16_t i);
+ QPID_COMMON_EXTERN void putLong(uint32_t i);
+ QPID_COMMON_EXTERN void putLongLong(uint64_t i);
+ QPID_COMMON_EXTERN void putInt8(int8_t i);
+ QPID_COMMON_EXTERN void putInt16(int16_t i);
+ QPID_COMMON_EXTERN void putInt32(int32_t i);
+ QPID_COMMON_EXTERN void putInt64(int64_t i);
+ QPID_COMMON_EXTERN void putFloat(float f);
+ QPID_COMMON_EXTERN void putDouble(double f);
+ QPID_COMMON_EXTERN void putBin128(const uint8_t* b);
+
+ QPID_COMMON_EXTERN uint8_t getOctet();
+ QPID_COMMON_EXTERN uint16_t getShort();
+ QPID_COMMON_EXTERN uint32_t getLong();
+ QPID_COMMON_EXTERN uint64_t getLongLong();
+ QPID_COMMON_EXTERN int8_t getInt8();
+ QPID_COMMON_EXTERN int16_t getInt16();
+ QPID_COMMON_EXTERN int32_t getInt32();
+ QPID_COMMON_EXTERN int64_t getInt64();
+ QPID_COMMON_EXTERN float getFloat();
+ QPID_COMMON_EXTERN double getDouble();
+
+ template <int n>
+ QPID_COMMON_EXTERN uint64_t getUInt();
+
+ template <int n>
+ QPID_COMMON_EXTERN void putUInt(uint64_t);
+
+ QPID_COMMON_EXTERN void putShortString(const std::string& s);
+ QPID_COMMON_EXTERN void putMediumString(const std::string& s);
+ QPID_COMMON_EXTERN void putLongString(const std::string& s);
+ QPID_COMMON_EXTERN void getShortString(std::string& s);
+ QPID_COMMON_EXTERN void getMediumString(std::string& s);
+ QPID_COMMON_EXTERN void getLongString(std::string& s);
+ QPID_COMMON_EXTERN void getBin128(uint8_t* b);
+
+ QPID_COMMON_EXTERN void putRawData(const std::string& s);
+ QPID_COMMON_EXTERN void getRawData(std::string& s, uint32_t size);
+
+ QPID_COMMON_EXTERN void putRawData(const uint8_t* data, size_t size);
+ QPID_COMMON_EXTERN void getRawData(uint8_t* data, size_t size);
+
+ template <class T> void put(const T& data) { data.encode(*this); }
+ template <class T> void get(T& data) { data.decode(*this); }
+
+ QPID_COMMON_EXTERN void dump(std::ostream&) const;
+};
+
+std::ostream& operator<<(std::ostream&, const Buffer&);
+
+}} // namespace qpid::framing
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/framing/BufferTypes.h b/qpid/cpp/src/qpid/framing/BufferTypes.h
new file mode 100644
index 0000000000..4199755852
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/BufferTypes.h
@@ -0,0 +1,106 @@
+#ifndef QPID_FRAMING_BUFFERTYPES_H
+#define QPID_FRAMING_BUFFERTYPES_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**@file
+ * Using templates with framing::Buffer is difficultg becase of the many
+ * different put/get function names. Here we define a set of types
+ * corresponding the basic types of Buffer but presenting a uniform
+ * encode/decode/encodedSize interface.
+ *
+ * It also provides some convenience templates for the common case of
+ * encoding a single encodable value as a string, e.g.
+ *
+ * LongString ls("hello");
+ * std::string encoded = encodeStr(ls);
+ * LongString ls2 = decodeStr<LongString>(encoded);
+ * LongString ls3;
+ * decodeStr(encoded, ls3);
+ */
+
+namespace qpid {
+namespace framing {
+
+// Templates to help define types
+template <class ValueType> struct BufferTypeTraits {
+ typedef void (Buffer::*Put)(const ValueType&);
+ typedef void (Buffer::*Get)(ValueType&);
+};
+
+template <class ValueType,
+ typename BufferTypeTraits<ValueType>::Put PutFn,
+ typename BufferTypeTraits<ValueType>::Get GetFn>
+struct EncodeDecodeTemplate {
+ EncodeDecodeTemplate(const ValueType& s) : value(s) {}
+ operator ValueType() const { return value; }
+
+ ValueType value;
+ void encode(framing::Buffer& buf) const { (buf.*PutFn)(value); }
+ void decode(framing::Buffer& buf) { (buf.*GetFn)(value); }
+};
+
+template <size_t Size,
+ typename BufferTypeTraits<std::string>::Put PutFn,
+ typename BufferTypeTraits<std::string>::Get GetFn
+ >
+struct StringTypeTemplate : public EncodeDecodeTemplate<std::string, PutFn, GetFn> {
+ typedef EncodeDecodeTemplate<std::string, PutFn, GetFn> Base;
+ StringTypeTemplate(const std::string& s) : Base(s) {}
+ size_t encodedSize() const { return Size + Base::value.size(); }
+};
+
+
+// Convenience tempates for encoding/decoding values to/from a std::string.
+
+/** Encode value as a string. */
+template <class T> std::string encodeStr(const T& value) {
+ std::string encoded(value.encodedSize(), '\0');
+ framing::Buffer buffer(&encoded[0], encoded.size());
+ value.encode(buffer);
+ return encoded;
+}
+
+/** Decode value from a string. */
+template <class T> void decodeStr(const std::string& encoded, T& value) {
+ framing::Buffer b(const_cast<char*>(&encoded[0]), encoded.size());
+ value.decode(b);
+}
+
+/** Decode value from a string. */
+template <class T> T decodeStr(const std::string& encoded) {
+ T value;
+ decodeStr(encoded, value);
+ return value;
+}
+
+// The types
+
+typedef StringTypeTemplate<4, &Buffer::putLongString, &Buffer::getLongString> LongString;
+typedef StringTypeTemplate<2, &Buffer::putMediumString, &Buffer::getMediumString> MediumString;
+typedef StringTypeTemplate<1, &Buffer::putShortString, &Buffer::getShortString> ShortString;
+
+// TODO aconway 2013-07-26: Add integer types.
+
+}} // namespace qpid::framing
+
+#endif /*!QPID_FRAMING_BUFFERTYPES_H*/
diff --git a/qpid/cpp/src/qpid/framing/ChannelHandler.h b/qpid/cpp/src/qpid/framing/ChannelHandler.h
new file mode 100644
index 0000000000..ddab204578
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/ChannelHandler.h
@@ -0,0 +1,53 @@
+#ifndef QPID_FRAMING_CHANNELHANDLER_H
+#define QPID_FRAMING_CHANNELHANDLER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/framing/FrameHandler.h"
+#include "qpid/framing/AMQFrame.h"
+
+namespace qpid {
+namespace framing {
+
+/**
+ * Sets the channel number on outgoing frames.
+ */
+class ChannelHandler : public FrameHandler
+{
+ public:
+ ChannelHandler(uint16_t channelId=0, FrameHandler* next=0)
+ : FrameHandler(next), channel(channelId) {}
+ void handle(AMQFrame& frame) {
+ frame.setChannel(channel);
+ next->handle(frame);
+ }
+ uint16_t get() const { return channel; }
+ ChannelHandler& set(uint16_t ch) { channel=ch; return *this; }
+ operator uint16_t() const { return get(); }
+ ChannelHandler& operator=(uint16_t ch) { return set(ch); }
+
+ private:
+ uint16_t channel;
+};
+
+}} // namespace qpid::framing
+
+#endif /*!QPID_FRAMING_CHANNELHANDLER_H*/
diff --git a/qpid/cpp/src/qpid/framing/Endian.h b/qpid/cpp/src/qpid/framing/Endian.h
new file mode 100644
index 0000000000..faf553fed5
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/Endian.h
@@ -0,0 +1,78 @@
+#ifndef QPID_FRAMING_ENDIAN_H
+#define QPID_FRAMING_ENDIAN_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/IntegerTypes.h"
+
+namespace qpid {
+namespace framing {
+namespace endian {
+
+/** Decode integer from network byte order buffer to type T, buffer must be at least sizeof(T). */
+template <class T> T decodeInt(const uint8_t* buffer) {
+ T v = buffer[0];
+ for (size_t i = 1; i < sizeof(T); ++i) {
+ v <<= 8;
+ v |= buffer[i];
+ }
+ return v;
+}
+
+/** Encode integer value to network byte order in buffer, buffer must be at least sizeof(T). */
+template <class T> void encodeInt(uint8_t* buffer, T value) {
+ for (size_t i = sizeof(T); i > 0; --i) {
+ buffer[i-1] = value & 0XFF;
+ value >>= 8;
+ }
+}
+
+// Compute the int type that can hold a float type.
+template <class T> struct IntBox { typedef T Type; };
+template <> struct IntBox<float> { typedef uint32_t Type; };
+template <> struct IntBox<double> { typedef uint64_t Type; };
+
+/** Decode floating from network byte order buffer to type T, buffer must be at least sizeof(T). */
+template <class T> T decodeFloat(const uint8_t* buffer) {
+ typedef typename IntBox<T>::Type Box;
+ union { T f; Box i; } u;
+ u.i = decodeInt<Box>(buffer);
+ return u.f;
+}
+
+/** Encode floating value to network byte order in buffer, buffer must be at least sizeof(T). */
+template <class T> void encodeFloat(uint8_t* buffer, T value) {
+ typedef typename IntBox<T>::Type Box;
+ union { T f; Box i; } u;
+ u.f = value;
+ encodeInt(buffer, u.i);
+}
+
+}}} // namespace qpid::framing::endian
+
+#endif /*!QPID_FRAMING_ENDIAN_H*/
+
+
+
+
+
+
diff --git a/qpid/cpp/src/qpid/framing/FieldTable.cpp b/qpid/cpp/src/qpid/framing/FieldTable.cpp
new file mode 100644
index 0000000000..0b677a6ccb
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/FieldTable.cpp
@@ -0,0 +1,433 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/Array.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/Exception.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/Msg.h"
+#include <assert.h>
+
+// The locking rationale in the FieldTable seems a little odd, but it
+// maintains the concurrent guarantees and requirements that were in
+// place before the cachedBytes/cachedSize were added:
+//
+// The FieldTable client code needs to make sure that they call no write
+// operation in parallel with any other operation on the FieldTable.
+// However multiple parallel read operations are safe.
+//
+// To this end the only code that is locked is code that can transparently
+// change the state of the FieldTable during a read only operation.
+// (In other words the code that required the mutable members in the class
+// definition!)
+//
+namespace qpid {
+
+using sys::Mutex;
+using sys::ScopedLock;
+
+namespace framing {
+
+FieldTable::FieldTable() :
+ cachedSize(0),
+ newBytes(false)
+{
+}
+
+FieldTable::FieldTable(const FieldTable& ft)
+{
+ ScopedLock<Mutex> l(ft.lock); // lock _source_ FieldTable
+
+ cachedBytes = ft.cachedBytes;
+ cachedSize = ft.cachedSize;
+ newBytes = ft.newBytes;
+
+ // Only copy the values if we have no raw data
+ // - copying the map is expensive and we can
+ // reconstruct it if necessary from the raw data
+ if (cachedBytes) {
+ newBytes = true;
+ return;
+ }
+ // In practice Encoding the source field table and only copying
+ // the encoded bytes is faster than copying the whole value map.
+ // (Because we nearly always copy a field table internally before
+ // encoding it to send, but don't change it after the copy)
+ if (!ft.values.empty()) {
+ // Side effect of getting encoded size will cache it in ft.cachedSize
+ ft.cachedBytes = boost::shared_array<uint8_t>(new uint8_t[ft.encodedSize()]);
+
+ Buffer buffer((char*)&ft.cachedBytes[0], ft.cachedSize);
+
+ // Cut and paste ahead...
+ buffer.putLong(ft.encodedSize() - 4);
+ buffer.putLong(ft.values.size());
+ for (ValueMap::const_iterator i = ft.values.begin(); i!=ft.values.end(); ++i) {
+ buffer.putShortString(i->first);
+ i->second->encode(buffer);
+ }
+
+ cachedBytes = ft.cachedBytes;
+ cachedSize = ft.cachedSize;
+ newBytes = true;
+ }
+}
+
+FieldTable& FieldTable::operator=(const FieldTable& ft)
+{
+ FieldTable nft(ft);
+ values.swap(nft.values);
+ cachedBytes.swap(nft.cachedBytes);
+ cachedSize = nft.cachedSize;
+ newBytes = nft.newBytes;
+ return (*this);
+}
+
+uint32_t FieldTable::encodedSize() const {
+ ScopedLock<Mutex> l(lock);
+
+ if (cachedSize != 0) {
+ return cachedSize;
+ }
+ uint32_t len(4/*size field*/ + 4/*count field*/);
+ for(ValueMap::const_iterator i = values.begin(); i != values.end(); ++i) {
+ // shortstr_len_byte + key size + value size
+ len += 1 + (i->first).size() + (i->second)->encodedSize();
+ }
+ cachedSize = len;
+ return len;
+}
+
+int FieldTable::count() const {
+ return values.size();
+}
+
+namespace
+{
+std::ostream& operator<<(std::ostream& out, const FieldTable::ValueMap::value_type& i) {
+ return out << i.first << ":" << *i.second;
+}
+}
+
+std::ostream& operator<<(std::ostream& out, const FieldTable& t) {
+ t.realDecode();
+ out << "{";
+ FieldTable::ValueMap::const_iterator i = t.begin();
+ if (i != t.end()) out << *i++;
+ while (i != t.end())
+ {
+ out << "," << *i++;
+ }
+ return out << "}";
+}
+
+void FieldTable::set(const std::string& name, const ValuePtr& value){
+ realDecode();
+ values[name] = value;
+ flushRawCache();
+}
+
+void FieldTable::setString(const std::string& name, const std::string& value){
+ realDecode();
+ values[name] = ValuePtr(new Str16Value(value));
+ flushRawCache();
+}
+
+void FieldTable::setInt(const std::string& name, const int value){
+ realDecode();
+ values[name] = ValuePtr(new IntegerValue(value));
+ flushRawCache();
+}
+
+void FieldTable::setInt64(const std::string& name, const int64_t value){
+ realDecode();
+ values[name] = ValuePtr(new Integer64Value(value));
+ flushRawCache();
+}
+
+void FieldTable::setTimestamp(const std::string& name, const uint64_t value){
+ realDecode();
+ values[name] = ValuePtr(new TimeValue(value));
+ flushRawCache();
+}
+
+void FieldTable::setUInt64(const std::string& name, const uint64_t value){
+ realDecode();
+ values[name] = ValuePtr(new Unsigned64Value(value));
+ flushRawCache();
+}
+
+void FieldTable::setTable(const std::string& name, const FieldTable& value)
+{
+ realDecode();
+ values[name] = ValuePtr(new FieldTableValue(value));
+ flushRawCache();
+}
+void FieldTable::setArray(const std::string& name, const Array& value)
+{
+ realDecode();
+ values[name] = ValuePtr(new ArrayValue(value));
+ flushRawCache();
+}
+
+void FieldTable::setFloat(const std::string& name, const float value){
+ realDecode();
+ values[name] = ValuePtr(new FloatValue(value));
+ flushRawCache();
+}
+
+void FieldTable::setDouble(const std::string& name, const double value){
+ realDecode();
+ values[name] = ValuePtr(new DoubleValue(value));
+ flushRawCache();
+}
+
+FieldTable::ValuePtr FieldTable::get(const std::string& name) const
+{
+ // Ensure we have any values we're trying to read
+ realDecode();
+ ValuePtr value;
+ ValueMap::const_iterator i = values.find(name);
+ if ( i!=values.end() )
+ value = i->second;
+ return value;
+}
+
+namespace {
+ template <class T> T default_value() { return T(); }
+ template <> int default_value<int>() { return 0; }
+ //template <> uint64_t default_value<uint64_t>() { return 0; }
+}
+
+template <class T>
+T getValue(const FieldTable::ValuePtr value)
+{
+ if (!value || !value->convertsTo<T>())
+ return default_value<T>();
+
+ return value->get<T>();
+}
+
+std::string FieldTable::getAsString(const std::string& name) const {
+ return getValue<std::string>(get(name));
+}
+
+int FieldTable::getAsInt(const std::string& name) const {
+ return getValue<int>(get(name));
+}
+
+uint64_t FieldTable::getAsUInt64(const std::string& name) const {
+ return static_cast<uint64_t>( getValue<int64_t>(get(name)));
+}
+
+int64_t FieldTable::getAsInt64(const std::string& name) const {
+ return getValue<int64_t>(get(name));
+}
+
+bool FieldTable::getTable(const std::string& name, FieldTable& value) const {
+ return getEncodedValue<FieldTable>(get(name), value);
+}
+
+bool FieldTable::getArray(const std::string& name, Array& value) const {
+ return getEncodedValue<Array>(get(name), value);
+}
+
+template <class T, int width, uint8_t typecode>
+bool getRawFixedWidthValue(FieldTable::ValuePtr vptr, T& value)
+{
+ if (vptr && vptr->getType() == typecode) {
+ value = vptr->get<T>();
+ return true;
+ }
+ return false;
+}
+
+bool FieldTable::getFloat(const std::string& name, float& value) const {
+ return getRawFixedWidthValue<float, 4, 0x23>(get(name), value);
+}
+
+bool FieldTable::getDouble(const std::string& name, double& value) const {
+ return getRawFixedWidthValue<double, 8, 0x33>(get(name), value);
+}
+
+//uint64_t FieldTable::getTimestamp(const std::string& name) const {
+// return getValue<uint64_t>(name);
+//}
+
+void FieldTable::encode(Buffer& buffer) const {
+ // If we've still got the input field table
+ // we can just copy it directly to the output
+ if (cachedBytes) {
+ ScopedLock<Mutex> l(lock);
+ buffer.putRawData(&cachedBytes[0], cachedSize);
+ } else {
+ buffer.putLong(encodedSize() - 4);
+ buffer.putLong(values.size());
+ for (ValueMap::const_iterator i = values.begin(); i!=values.end(); ++i) {
+ buffer.putShortString(i->first);
+ i->second->encode(buffer);
+ }
+ }
+}
+
+// Decode lazily - just record the raw bytes until we need them
+void FieldTable::decode(Buffer& buffer){
+ if (buffer.available() < 4)
+ throw IllegalArgumentException(QPID_MSG("Not enough data for field table."));
+ uint32_t p = buffer.getPosition();
+ uint32_t len = buffer.getLong();
+ if (len) {
+ uint32_t available = buffer.available();
+ if ((available < len) || (available < 4))
+ throw IllegalArgumentException(QPID_MSG("Not enough data for field table."));
+ }
+ ScopedLock<Mutex> l(lock);
+ // Throw away previous stored values
+ values.clear();
+ // Copy data into our buffer
+ cachedBytes = boost::shared_array<uint8_t>(new uint8_t[len + 4]);
+ cachedSize = len + 4;
+ newBytes = true;
+ buffer.setPosition(p);
+ buffer.getRawData(&cachedBytes[0], cachedSize);
+}
+
+void FieldTable::realDecode() const
+{
+ ScopedLock<Mutex> l(lock);
+
+ // If we've got no raw data stored up then nothing to do
+ if (!newBytes)
+ return;
+
+ Buffer buffer((char*)&cachedBytes[0], cachedSize);
+ uint32_t len = buffer.getLong();
+ if (len) {
+ uint32_t available = buffer.available();
+ uint32_t count = buffer.getLong();
+ uint32_t leftover = available - len;
+ while(buffer.available() > leftover && count--){
+ std::string name;
+ ValuePtr value(new FieldValue);
+
+ buffer.getShortString(name);
+ value->decode(buffer);
+ values[name] = ValuePtr(value);
+ }
+ }
+ newBytes = false;
+}
+
+void FieldTable::flushRawCache()
+{
+ ScopedLock<Mutex> l(lock);
+ // We can only flush the cache if there are no cached bytes to decode
+ assert(newBytes==false);
+ // Avoid recreating shared array unless we actually have one.
+ if (cachedBytes) cachedBytes.reset();
+ cachedSize = 0;
+}
+
+bool FieldTable::operator==(const FieldTable& x) const {
+ realDecode();
+ x.realDecode();
+ if (values.size() != x.values.size()) return false;
+ for (ValueMap::const_iterator i = values.begin(); i != values.end(); ++i) {
+ ValueMap::const_iterator j = x.values.find(i->first);
+ if (j == x.values.end()) return false;
+ if (*(i->second) != *(j->second)) return false;
+ }
+ return true;
+}
+
+void FieldTable::erase(const std::string& name)
+{
+ realDecode();
+ if (values.find(name) != values.end()) {
+ values.erase(name);
+ flushRawCache();
+ }
+}
+
+void FieldTable::clear()
+{
+ values.clear();
+ newBytes = false;
+ flushRawCache();
+}
+
+// Map-like interface.
+FieldTable::ValueMap::const_iterator FieldTable::begin() const
+{
+ realDecode();
+ return values.begin();
+}
+
+FieldTable::ValueMap::const_iterator FieldTable::end() const
+{
+ realDecode();
+ return values.end();
+}
+
+FieldTable::ValueMap::const_iterator FieldTable::find(const std::string& s) const
+{
+ realDecode();
+ return values.find(s);
+}
+
+FieldTable::ValueMap::iterator FieldTable::begin()
+{
+ realDecode();
+ flushRawCache();
+ return values.begin();
+}
+
+FieldTable::ValueMap::iterator FieldTable::end()
+{
+ realDecode();
+ flushRawCache();
+ return values.end();
+}
+
+FieldTable::ValueMap::iterator FieldTable::find(const std::string& s)
+{
+ realDecode();
+ flushRawCache();
+ return values.find(s);
+}
+
+std::pair<FieldTable::ValueMap::iterator, bool> FieldTable::insert(const ValueMap::value_type& value)
+{
+ realDecode();
+ flushRawCache();
+ return values.insert(value);
+}
+
+FieldTable::ValueMap::iterator FieldTable::insert(ValueMap::iterator position, const ValueMap::value_type& value)
+{
+ realDecode();
+ flushRawCache();
+ return values.insert(position, value);
+}
+
+}
+}
diff --git a/qpid/cpp/src/qpid/framing/FieldTable.h b/qpid/cpp/src/qpid/framing/FieldTable.h
new file mode 100644
index 0000000000..1986a72d10
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/FieldTable.h
@@ -0,0 +1,139 @@
+#ifndef _FieldTable_
+#define _FieldTable_
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/framing/amqp_types.h"
+#include "qpid/sys/Mutex.h"
+
+#include <boost/shared_ptr.hpp>
+#include <boost/shared_array.hpp>
+
+#include <iosfwd>
+#include <map>
+
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+ /**
+ * The framing namespace contains classes that are used to create,
+ * send and receive the basic packets from which AMQP is built.
+ */
+namespace framing {
+
+class Array;
+class FieldValue;
+class Buffer;
+
+/**
+ * A set of name-value pairs. (See the AMQP spec for more details on
+ * AMQP field tables).
+ *
+ * \ingroup clientapi
+ */
+class FieldTable
+{
+ public:
+ typedef boost::shared_ptr<FieldValue> ValuePtr;
+ typedef std::map<std::string, ValuePtr> ValueMap;
+ typedef ValueMap::iterator iterator;
+ typedef ValueMap::const_iterator const_iterator;
+ typedef ValueMap::const_reference const_reference;
+ typedef ValueMap::reference reference;
+ typedef ValueMap::value_type value_type;
+
+ QPID_COMMON_EXTERN FieldTable();
+ QPID_COMMON_EXTERN FieldTable(const FieldTable&);
+ QPID_COMMON_EXTERN FieldTable& operator=(const FieldTable&);
+ // Compiler default destructor fine
+ QPID_COMMON_EXTERN uint32_t encodedSize() const;
+ QPID_COMMON_EXTERN void encode(Buffer& buffer) const;
+ QPID_COMMON_EXTERN void decode(Buffer& buffer);
+
+ QPID_COMMON_EXTERN int count() const;
+ QPID_COMMON_INLINE_EXTERN size_t size() const { return values.size(); }
+ QPID_COMMON_INLINE_EXTERN bool empty() { return size() == 0; }
+ QPID_COMMON_EXTERN void set(const std::string& name, const ValuePtr& value);
+ QPID_COMMON_EXTERN ValuePtr get(const std::string& name) const;
+ QPID_COMMON_INLINE_EXTERN bool isSet(const std::string& name) const { return get(name).get() != 0; }
+
+ QPID_COMMON_EXTERN void setString(const std::string& name, const std::string& value);
+ QPID_COMMON_EXTERN void setInt(const std::string& name, const int value);
+ QPID_COMMON_EXTERN void setInt64(const std::string& name, const int64_t value);
+ QPID_COMMON_EXTERN void setTimestamp(const std::string& name, const uint64_t value);
+ QPID_COMMON_EXTERN void setUInt64(const std::string& name, const uint64_t value);
+ QPID_COMMON_EXTERN void setTable(const std::string& name, const FieldTable& value);
+ QPID_COMMON_EXTERN void setArray(const std::string& name, const Array& value);
+ QPID_COMMON_EXTERN void setFloat(const std::string& name, const float value);
+ QPID_COMMON_EXTERN void setDouble(const std::string& name, const double value);
+ //void setDecimal(string& name, xxx& value);
+
+ QPID_COMMON_EXTERN int getAsInt(const std::string& name) const;
+ QPID_COMMON_EXTERN uint64_t getAsUInt64(const std::string& name) const;
+ QPID_COMMON_EXTERN int64_t getAsInt64(const std::string& name) const;
+ QPID_COMMON_EXTERN std::string getAsString(const std::string& name) const;
+
+ QPID_COMMON_EXTERN bool getTable(const std::string& name, FieldTable& value) const;
+ QPID_COMMON_EXTERN bool getArray(const std::string& name, Array& value) const;
+ QPID_COMMON_EXTERN bool getFloat(const std::string& name, float& value) const;
+ QPID_COMMON_EXTERN bool getDouble(const std::string& name, double& value) const;
+ //QPID_COMMON_EXTERN bool getTimestamp(const std::string& name, uint64_t& value) const;
+ //QPID_COMMON_EXTERN bool getDecimal(string& name, xxx& value);
+ QPID_COMMON_EXTERN void erase(const std::string& name);
+
+
+ QPID_COMMON_EXTERN bool operator==(const FieldTable& other) const;
+
+ // Map-like interface.
+ QPID_COMMON_EXTERN ValueMap::const_iterator begin() const;
+ QPID_COMMON_EXTERN ValueMap::const_iterator end() const;
+ QPID_COMMON_EXTERN ValueMap::const_iterator find(const std::string& s) const;
+
+ QPID_COMMON_EXTERN ValueMap::iterator begin();
+ QPID_COMMON_EXTERN ValueMap::iterator end();
+ QPID_COMMON_EXTERN ValueMap::iterator find(const std::string& s);
+
+ QPID_COMMON_EXTERN std::pair <ValueMap::iterator, bool> insert(const ValueMap::value_type&);
+ QPID_COMMON_EXTERN ValueMap::iterator insert(ValueMap::iterator, const ValueMap::value_type&);
+ QPID_COMMON_EXTERN void clear();
+
+ private:
+ void realDecode() const;
+ void flushRawCache();
+
+ mutable qpid::sys::Mutex lock;
+ mutable ValueMap values;
+ mutable boost::shared_array<uint8_t> cachedBytes;
+ mutable uint32_t cachedSize; // if = 0 then non cached size as 0 is not a legal size
+ mutable bool newBytes;
+
+ QPID_COMMON_EXTERN friend std::ostream& operator<<(std::ostream& out, const FieldTable& body);
+};
+
+//class FieldNotFoundException{};
+//class UnknownFieldName : public FieldNotFoundException{};
+//class IncorrectFieldType : public FieldNotFoundException{};
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/framing/FieldValue.cpp b/qpid/cpp/src/qpid/framing/FieldValue.cpp
new file mode 100644
index 0000000000..227c12e44e
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/FieldValue.cpp
@@ -0,0 +1,267 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/framing/FieldValue.h"
+#include "qpid/framing/Array.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/framing/List.h"
+#include "qpid/framing/Uuid.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/framing/Endian.h"
+#include "qpid/Msg.h"
+
+namespace qpid {
+namespace framing {
+
+// Some template magic for computing types from sizes.
+template<int W> struct IntType{};
+template<> struct IntType<1> { typedef int8_t Type; };
+template<> struct IntType<2> { typedef int16_t Type; };
+template<> struct IntType<4> { typedef int32_t Type; };
+template<> struct IntType<8> { typedef int64_t Type; };
+
+template<int W> struct UintType{};
+template<> struct UintType<1> { typedef uint8_t Type; };
+template<> struct UintType<2> { typedef uint16_t Type; };
+template<> struct UintType<4> { typedef uint32_t Type; };
+template<> struct UintType<8> { typedef uint64_t Type; };
+
+template<int W> struct FloatType{};
+template<> struct FloatType<1> { typedef int8_t Type; }; // Dummy, never used.
+template<> struct FloatType<2> { typedef int16_t Type; }; // Dummy, never used.
+template<> struct FloatType<4> { typedef float Type; };
+template<> struct FloatType<8> { typedef double Type; };
+
+// Construct the right subclass of FixedWidthValue for numeric types using width and kind.
+// Kind 1=int, 2=unsigned int, 3=float
+template<int W> FixedWidthValue<W>* numericFixedWidthValue(uint8_t kind) {
+ switch (kind) {
+ case 1: return new FixedWidthIntValue<typename IntType<W>::Type>();
+ case 2: return new FixedWidthIntValue<typename UintType<W>::Type>();
+ case 3: return new FixedWidthFloatValue<typename FloatType<W>::Type>();
+ default: return new FixedWidthValue<W>();
+ }
+}
+
+uint8_t FieldValue::getType() const
+{
+ return typeOctet;
+}
+
+void FieldValue::setType(uint8_t type)
+{
+ typeOctet = type;
+ if (typeOctet == 0xA8) {
+ data.reset(new EncodedValue<FieldTable>());
+ } else if (typeOctet == 0xA9) {
+ data.reset(new EncodedValue<List>());
+ } else if (typeOctet == 0xAA) {
+ data.reset(new EncodedValue<Array>());
+ } else if (typeOctet == 0x48) {
+ data.reset(new UuidData());
+ } else {
+ uint8_t kind = typeOctet & 0xF;
+ uint8_t lenType = typeOctet >> 4;
+ switch(lenType){
+ case 0:
+ data.reset(numericFixedWidthValue<1>(kind));
+ break;
+ case 1:
+ data.reset(numericFixedWidthValue<2>(kind));
+ break;
+ case 2:
+ data.reset(numericFixedWidthValue<4>(kind));
+ break;
+ case 3:
+ data.reset(numericFixedWidthValue<8>(kind));
+ break;
+ // None of the remaining widths can be numeric types so just use new FixedWidthValue
+ case 4:
+ data.reset(new FixedWidthValue<16>());
+ break;
+ case 5:
+ data.reset(new FixedWidthValue<32>());
+ break;
+ case 6:
+ data.reset(new FixedWidthValue<64>());
+ break;
+ case 7:
+ data.reset(new FixedWidthValue<128>());
+ break;
+ case 8:
+ data.reset(new VariableWidthValue<1>());
+ break;
+ case 9:
+ data.reset(new VariableWidthValue<2>());
+ break;
+ case 0xA:
+ data.reset(new VariableWidthValue<4>());
+ break;
+ case 0xC:
+ data.reset(new FixedWidthValue<5>());
+ break;
+ case 0xD:
+ data.reset(new FixedWidthValue<9>());
+ break;
+ case 0xF:
+ data.reset(new FixedWidthValue<0>());
+ break;
+ default:
+ throw IllegalArgumentException(QPID_MSG("Unknown field table value type: " << (int)typeOctet));
+ }
+ }
+}
+
+void FieldValue::decode(Buffer& buffer)
+{
+ setType(buffer.getOctet());
+ data->decode(buffer);
+}
+
+void FieldValue::encode(Buffer& buffer)
+{
+ buffer.putOctet(typeOctet);
+ data->encode(buffer);
+}
+
+bool FieldValue::operator==(const FieldValue& v) const
+{
+ return
+ typeOctet == v.typeOctet &&
+ *data == *v.data;
+}
+
+Str8Value::Str8Value(const std::string& v) :
+ FieldValue(
+ TYPE_CODE_STR8,
+ new VariableWidthValue<1>(
+ reinterpret_cast<const uint8_t*>(v.data()),
+ reinterpret_cast<const uint8_t*>(v.data()+v.size())))
+{
+}
+
+Str16Value::Str16Value(const std::string& v) :
+ FieldValue(
+ 0x95,
+ new VariableWidthValue<2>(
+ reinterpret_cast<const uint8_t*>(v.data()),
+ reinterpret_cast<const uint8_t*>(v.data()+v.size())))
+{}
+
+Var16Value::Var16Value(const std::string& v, uint8_t code) :
+ FieldValue(
+ code,
+ new VariableWidthValue<2>(
+ reinterpret_cast<const uint8_t*>(v.data()),
+ reinterpret_cast<const uint8_t*>(v.data()+v.size())))
+{}
+Var32Value::Var32Value(const std::string& v, uint8_t code) :
+ FieldValue(
+ code,
+ new VariableWidthValue<4>(
+ reinterpret_cast<const uint8_t*>(v.data()),
+ reinterpret_cast<const uint8_t*>(v.data()+v.size())))
+{}
+
+Struct32Value::Struct32Value(const std::string& v) :
+ FieldValue(
+ 0xAB,
+ new VariableWidthValue<4>(
+ reinterpret_cast<const uint8_t*>(v.data()),
+ reinterpret_cast<const uint8_t*>(v.data()+v.size())))
+{}
+
+IntegerValue::IntegerValue(int v) :
+ FieldValue(0x21, new FixedWidthIntValue<int32_t>(v))
+{}
+
+FloatValue::FloatValue(float v) :
+ FieldValue(0x23, new FixedWidthFloatValue<float>(v))
+{}
+
+DoubleValue::DoubleValue(double v) :
+ FieldValue(0x33, new FixedWidthFloatValue<double>(v))
+{}
+
+Integer64Value::Integer64Value(int64_t v) :
+ FieldValue(0x31, new FixedWidthIntValue<int64_t>(v))
+{}
+
+Unsigned64Value::Unsigned64Value(uint64_t v) :
+ FieldValue(0x32, new FixedWidthIntValue<uint64_t>(v))
+{}
+
+
+TimeValue::TimeValue(uint64_t v) :
+ FieldValue(0x38, new FixedWidthIntValue<uint64_t>(v))
+{
+}
+
+FieldTableValue::FieldTableValue(const FieldTable& f) : FieldValue(0xa8, new EncodedValue<FieldTable>(f))
+{
+}
+
+ListValue::ListValue(const List& l) : FieldValue(0xa9, new EncodedValue<List>(l))
+{
+}
+
+ArrayValue::ArrayValue(const Array& a) : FieldValue(0xaa, new EncodedValue<Array>(a))
+{
+}
+
+VoidValue::VoidValue() : FieldValue(0xf0, new FixedWidthValue<0>()) {}
+
+BoolValue::BoolValue(bool b) :
+ FieldValue(0x08, new FixedWidthIntValue<bool>(b))
+{}
+
+Unsigned8Value::Unsigned8Value(uint8_t v) :
+ FieldValue(0x02, new FixedWidthIntValue<uint8_t>(v))
+{}
+Unsigned16Value::Unsigned16Value(uint16_t v) :
+ FieldValue(0x12, new FixedWidthIntValue<uint16_t>(v))
+{}
+Unsigned32Value::Unsigned32Value(uint32_t v) :
+ FieldValue(0x22, new FixedWidthIntValue<uint32_t>(v))
+{}
+
+Integer8Value::Integer8Value(int8_t v) :
+ FieldValue(0x01, new FixedWidthIntValue<int8_t>(v))
+{}
+Integer16Value::Integer16Value(int16_t v) :
+ FieldValue(0x11, new FixedWidthIntValue<int16_t>(v))
+{}
+
+UuidData::UuidData() {}
+UuidData::UuidData(const unsigned char* bytes) : FixedWidthValue<16>(bytes) {}
+bool UuidData::convertsToString() const { return true; }
+std::string UuidData::getString() const { return Uuid(rawOctets()).str(); }
+UuidValue::UuidValue(const unsigned char* v) : FieldValue(0x48, new UuidData(v)) {}
+
+void FieldValue::print(std::ostream& out) const {
+ data->print(out);
+ out << TypeCode(typeOctet) << '(';
+ if (data->convertsToString()) out << data->getString();
+ else if (data->convertsToInt()) out << data->getInt();
+ else data->print(out);
+ out << ')';
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/framing/FieldValue.h b/qpid/cpp/src/qpid/framing/FieldValue.h
new file mode 100644
index 0000000000..e20244e7c9
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/FieldValue.h
@@ -0,0 +1,482 @@
+#ifndef _framing_FieldValue_h
+#define _framing_FieldValue_h
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/Exception.h"
+#include "qpid/framing/amqp_types.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/Endian.h"
+#include "qpid/CommonImportExport.h"
+
+#include <iostream>
+#include <memory>
+#include <vector>
+
+#include <assert.h>
+
+namespace qpid {
+namespace framing {
+
+/**
+ * Exception that is the base exception for all field table errors.
+ *
+ * \ingroup clientapi
+ */
+class QPID_COMMON_CLASS_EXTERN FieldValueException : public qpid::Exception {};
+
+/**
+ * Exception thrown when we can't perform requested conversion
+ *
+ * \ingroup clientapi
+ */
+struct QPID_COMMON_CLASS_EXTERN InvalidConversionException : public FieldValueException {
+ InvalidConversionException() {}
+};
+
+class List;
+
+/**
+ * Value that can appear in an AMQP field table
+ *
+ * \ingroup clientapi
+ */
+class QPID_COMMON_CLASS_EXTERN FieldValue {
+ public:
+ /*
+ * Abstract type for content of different types
+ */
+ class Data {
+ public:
+ virtual ~Data() {}
+ virtual uint32_t encodedSize() const = 0;
+ virtual void encode(Buffer& buffer) = 0;
+ virtual void decode(Buffer& buffer) = 0;
+ virtual bool operator==(const Data&) const = 0;
+
+ virtual bool convertsToInt() const { return false; }
+ virtual bool convertsToFloat() const { return false; }
+ virtual bool convertsToString() const { return false; }
+ virtual int64_t getInt() const { throw InvalidConversionException();}
+ virtual double getFloat() const { throw InvalidConversionException();}
+ virtual std::string getString() const { throw InvalidConversionException(); }
+
+ virtual void print(std::ostream& out) const = 0;
+ };
+
+ FieldValue(): data(0) {};
+ // Default assignment operator is fine
+ void setType(uint8_t type);
+ QPID_COMMON_EXTERN uint8_t getType() const;
+ Data& getData() { return *data; }
+ uint32_t encodedSize() const { return 1 + data->encodedSize(); };
+ bool empty() const { return data.get() == 0; }
+ void encode(Buffer& buffer);
+ void decode(Buffer& buffer);
+ QPID_COMMON_EXTERN bool operator==(const FieldValue&) const;
+ QPID_COMMON_INLINE_EXTERN bool operator!=(const FieldValue& v) const { return !(*this == v); }
+
+ QPID_COMMON_EXTERN void print(std::ostream& out) const;
+
+ template <typename T> bool convertsTo() const { return false; }
+ template <typename T> T get() const { throw InvalidConversionException(); }
+
+ template <class T, int W> T getIntegerValue() const;
+ template <class T> T getIntegerValue() const;
+ template <class T, int W> T getFloatingPointValue() const;
+ template <int W> void getFixedWidthValue(unsigned char*) const;
+ template <class T> bool get(T&) const;
+
+ protected:
+ FieldValue(uint8_t t, Data* d): typeOctet(t), data(d) {}
+
+ private:
+ uint8_t typeOctet;
+ std::auto_ptr<Data> data;
+};
+
+template <>
+inline bool FieldValue::convertsTo<int>() const { return data->convertsToInt(); }
+
+template <>
+inline bool FieldValue::convertsTo<int64_t>() const { return data->convertsToInt(); }
+
+template <>
+inline bool FieldValue::convertsTo<std::string>() const { return data->convertsToString(); }
+
+template <>
+inline bool FieldValue::convertsTo<float>() const { return data->convertsToFloat(); }
+
+template <>
+inline bool FieldValue::convertsTo<double>() const { return data->convertsToFloat(); }
+
+template <>
+inline int FieldValue::get<int>() const { return static_cast<int>(data->getInt()); }
+
+template <>
+inline int64_t FieldValue::get<int64_t>() const { return data->getInt(); }
+
+template <>
+inline float FieldValue::get<float>() const { return data->getFloat(); }
+
+template <>
+inline double FieldValue::get<double>() const { return data->getFloat(); }
+
+template <>
+inline std::string FieldValue::get<std::string>() const { return data->getString(); }
+
+inline std::ostream& operator<<(std::ostream& out, const FieldValue& v) {
+ v.print(out);
+ return out;
+}
+
+template <int width>
+class FixedWidthValue : public FieldValue::Data {
+ protected:
+ uint8_t octets[width];
+
+ public:
+ FixedWidthValue() {}
+ FixedWidthValue(const uint8_t (&data)[width]) : octets(data) {}
+ FixedWidthValue(const uint8_t* const data) { std::copy(data, data + width, octets); }
+
+ uint32_t encodedSize() const { return width; }
+ void encode(Buffer& buffer) { buffer.putRawData(octets, width); }
+ void decode(Buffer& buffer) { buffer.getRawData(octets, width); }
+ bool operator==(const Data& d) const {
+ const FixedWidthValue<width>* rhs = dynamic_cast< const FixedWidthValue<width>* >(&d);
+ if (rhs == 0) return false;
+ else return std::equal(&octets[0], &octets[width], &rhs->octets[0]);
+ }
+ uint8_t* rawOctets() { return octets; }
+ const uint8_t* rawOctets() const { return octets; }
+
+ void print(std::ostream& o) const { o << "F" << width << ":"; };
+};
+
+template <class T> class FixedWidthIntValue : public FixedWidthValue<sizeof(T)> {
+ public:
+ FixedWidthIntValue(T v = 0) { endian::encodeInt(this->octets, v); }
+ bool convertsToInt() const { return true; }
+ int64_t getInt() const { return endian::decodeInt<T>(this->octets); }
+ bool convertsToFloat() const { return true; }
+ double getFloat() const { return getInt(); }
+};
+
+template <class T> class FixedWidthFloatValue : public FixedWidthValue<sizeof(T)> {
+ public:
+ FixedWidthFloatValue(T v = 0) { endian::encodeFloat(this->octets, v); }
+ bool convertsToFloat() const { return true; }
+ double getFloat() const { return endian::decodeFloat<T>(this->octets); }
+};
+
+// Dummy implementations that are never used but needed to avoid compile errors.
+template <> class FixedWidthFloatValue<uint8_t> : public FixedWidthValue<1> {
+ FixedWidthFloatValue() { assert(0); }
+};
+template <> class FixedWidthFloatValue<uint16_t> : public FixedWidthValue<2> {
+ FixedWidthFloatValue() { assert(0); }
+};
+
+
+class UuidData : public FixedWidthValue<16> {
+ public:
+ UuidData();
+ UuidData(const unsigned char* bytes);
+ bool convertsToString() const;
+ std::string getString() const;
+};
+
+template <class T, int W>
+inline T FieldValue::getIntegerValue() const
+{
+ FixedWidthValue<W>* const fwv = dynamic_cast< FixedWidthValue<W>* const>(data.get());
+ if (fwv) {
+ return endian::decodeInt<T>(fwv->rawOctets());
+ } else {
+ throw InvalidConversionException();
+ }
+}
+
+template <class T>
+inline T FieldValue::getIntegerValue() const
+{
+ FixedWidthValue<1>* const fwv = dynamic_cast< FixedWidthValue<1>* const>(data.get());
+ if (fwv) {
+ uint8_t* octets = fwv->rawOctets();
+ return octets[0];
+ } else {
+ throw InvalidConversionException();
+ }
+}
+
+template <class T, int W>
+inline T FieldValue::getFloatingPointValue() const {
+ const FixedWidthFloatValue<T>* fv = dynamic_cast<FixedWidthFloatValue<T>*>(data.get());
+ if (fv) {
+ return endian::decodeFloat<T>(fv->rawOctets());
+ } else {
+ throw InvalidConversionException();
+ }
+}
+
+template <int W> void FieldValue::getFixedWidthValue(unsigned char* value) const
+{
+ FixedWidthValue<W>* const fwv = dynamic_cast< FixedWidthValue<W>* const>(data.get());
+ if (fwv) {
+ std::copy(fwv->rawOctets(), fwv->rawOctets() + W, value);
+ } else {
+ throw InvalidConversionException();
+ }
+}
+
+template <>
+class FixedWidthValue<0> : public FieldValue::Data {
+ public:
+ // Implicit default constructor is fine
+ uint32_t encodedSize() const { return 0; }
+ void encode(Buffer&) {};
+ void decode(Buffer&) {};
+ bool operator==(const Data& d) const {
+ const FixedWidthValue<0>* rhs = dynamic_cast< const FixedWidthValue<0>* >(&d);
+ return rhs != 0;
+ }
+ void print(std::ostream& o) const { o << "F0"; };
+};
+
+template <int lenwidth>
+class VariableWidthValue : public FieldValue::Data {
+ std::vector<uint8_t> octets;
+
+ public:
+ VariableWidthValue() {}
+ VariableWidthValue(const std::vector<uint8_t>& data) : octets(data) {}
+ VariableWidthValue(const uint8_t* start, const uint8_t* end) : octets(start, end) {}
+ uint32_t encodedSize() const { return lenwidth + octets.size(); }
+ void encode(Buffer& buffer) {
+ buffer.putUInt<lenwidth>(octets.size());
+ if (octets.size() > 0)
+ buffer.putRawData(&octets[0], octets.size());
+ };
+ void decode(Buffer& buffer) {
+ uint32_t len = buffer.getUInt<lenwidth>();
+ buffer.checkAvailable(len);
+ octets.resize(len);
+ if (len > 0)
+ buffer.getRawData(&octets[0], len);
+ }
+ bool operator==(const Data& d) const {
+ const VariableWidthValue<lenwidth>* rhs = dynamic_cast< const VariableWidthValue<lenwidth>* >(&d);
+ if (rhs == 0) return false;
+ else return octets==rhs->octets;
+ }
+
+ bool convertsToString() const { return true; }
+ std::string getString() const { return std::string(octets.begin(), octets.end()); }
+
+ void print(std::ostream& o) const { o << "V" << lenwidth << ":" << octets.size() << ":"; };
+};
+
+template <class T>
+class EncodedValue : public FieldValue::Data {
+ T value;
+ public:
+
+ EncodedValue() {}
+ EncodedValue(const T& v) : value(v) {}
+
+ T& getValue() { return value; }
+ const T& getValue() const { return value; }
+
+ uint32_t encodedSize() const { return value.encodedSize(); }
+
+ void encode(Buffer& buffer) {
+ value.encode(buffer);
+ };
+ void decode(Buffer& buffer) {
+ value.decode(buffer);
+ }
+ bool operator==(const Data& d) const {
+ const EncodedValue<T>* rhs = dynamic_cast< const EncodedValue<T>* >(&d);
+ if (rhs == 0) return false;
+ else return value==rhs->value;
+ }
+
+ void print(std::ostream& o) const { o << "[" << value << "]"; };
+};
+
+/**
+ * Accessor that can be used to get values of type FieldTable, Array
+ * and List.
+ */
+template <class T>
+inline bool FieldValue::get(T& t) const
+{
+ const EncodedValue<T>* v = dynamic_cast< EncodedValue<T>* >(data.get());
+ if (v != 0) {
+ t = v->getValue();
+ return true;
+ } else {
+ try {
+ t = get<T>();
+ return true;
+ } catch (const InvalidConversionException&) {
+ return false;
+ }
+ }
+}
+
+class Str8Value : public FieldValue {
+ public:
+ QPID_COMMON_EXTERN Str8Value(const std::string& v);
+};
+
+class Str16Value : public FieldValue {
+ public:
+ QPID_COMMON_EXTERN Str16Value(const std::string& v);
+};
+
+class Var16Value : public FieldValue {
+ public:
+ QPID_COMMON_EXTERN Var16Value(const std::string& v, uint8_t code);
+};
+
+class Var32Value : public FieldValue {
+ public:
+ QPID_COMMON_EXTERN Var32Value(const std::string& v, uint8_t code);
+ };
+
+class Struct32Value : public FieldValue {
+ public:
+ QPID_COMMON_EXTERN Struct32Value(const std::string& v);
+};
+
+class FloatValue : public FieldValue
+{
+ public:
+ QPID_COMMON_EXTERN FloatValue(float f);
+};
+class DoubleValue : public FieldValue
+{
+ public:
+ QPID_COMMON_EXTERN DoubleValue(double f);
+};
+
+/*
+ * Basic integer value encodes as signed 32 bit
+ */
+class IntegerValue : public FieldValue {
+ public:
+ QPID_COMMON_EXTERN IntegerValue(int v);
+};
+
+class TimeValue : public FieldValue {
+ public:
+ QPID_COMMON_EXTERN TimeValue(uint64_t v);
+};
+
+class Integer64Value : public FieldValue {
+ public:
+ QPID_COMMON_EXTERN Integer64Value(int64_t v);
+};
+
+class Unsigned64Value : public FieldValue {
+ public:
+ QPID_COMMON_EXTERN Unsigned64Value(uint64_t v);
+};
+
+class FieldTableValue : public FieldValue {
+ public:
+ typedef FieldTable ValueType;
+ QPID_COMMON_EXTERN FieldTableValue(const FieldTable&);
+};
+
+class ArrayValue : public FieldValue {
+ public:
+ QPID_COMMON_EXTERN ArrayValue(const Array&);
+};
+
+class VoidValue : public FieldValue {
+ public:
+ QPID_COMMON_EXTERN VoidValue();
+};
+
+class BoolValue : public FieldValue {
+ public:
+ QPID_COMMON_EXTERN BoolValue(bool);
+};
+
+class Unsigned8Value : public FieldValue {
+ public:
+ QPID_COMMON_EXTERN Unsigned8Value(uint8_t);
+};
+
+class Unsigned16Value : public FieldValue {
+ public:
+ QPID_COMMON_EXTERN Unsigned16Value(uint16_t);
+};
+
+class Unsigned32Value : public FieldValue {
+ public:
+ QPID_COMMON_EXTERN Unsigned32Value(uint32_t);
+};
+
+class Integer8Value : public FieldValue {
+ public:
+ QPID_COMMON_EXTERN Integer8Value(int8_t);
+};
+
+class Integer16Value : public FieldValue {
+ public:
+ QPID_COMMON_EXTERN Integer16Value(int16_t);
+};
+
+typedef IntegerValue Integer32Value;
+
+class ListValue : public FieldValue {
+ public:
+ typedef List ValueType;
+ QPID_COMMON_EXTERN ListValue(const List&);
+};
+
+class UuidValue : public FieldValue {
+ public:
+ QPID_COMMON_EXTERN UuidValue();
+ QPID_COMMON_EXTERN UuidValue(const unsigned char*);
+};
+
+template <class T>
+bool getEncodedValue(FieldTable::ValuePtr vptr, T& value)
+{
+ if (vptr) {
+ const EncodedValue<T>* ev = dynamic_cast< EncodedValue<T>* >(&(vptr->getData()));
+ if (ev != 0) {
+ value = ev->getValue();
+ return true;
+ }
+ }
+ return false;
+}
+
+}} // qpid::framing
+
+#endif
diff --git a/qpid/cpp/src/qpid/framing/FrameDecoder.cpp b/qpid/cpp/src/qpid/framing/FrameDecoder.cpp
new file mode 100644
index 0000000000..90cbbd84a1
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/FrameDecoder.cpp
@@ -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.
+ *
+ */
+#include "qpid/framing/FrameDecoder.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/log/Statement.h"
+#include "qpid/framing/reply_exceptions.h"
+#include <algorithm>
+#include <string.h>
+
+namespace qpid {
+namespace framing {
+
+namespace {
+/** Append up to n bytes from start of buf to end of bytes. */
+void append(std::vector<char>& bytes, Buffer& buffer, size_t n) {
+ size_t oldSize = bytes.size();
+ if ((n = std::min(n, size_t(buffer.available()))) == 0)
+ return;
+ bytes.resize(oldSize+n);
+ char* p = &bytes[oldSize];
+ buffer.getRawData(reinterpret_cast<uint8_t*>(p), n);
+}
+}
+
+bool FrameDecoder::decode(Buffer& buffer) {
+ if (buffer.available() == 0) return false;
+ if (fragment.empty()) {
+ if (frame.decode(buffer)) // Decode from buffer
+ return true;
+ else // Store fragment
+ append(fragment, buffer, buffer.available());
+ }
+ else { // Already have a fragment
+ // Get enough data to decode the frame size.
+ if (fragment.size() < AMQFrame::DECODE_SIZE_MIN) {
+ append(fragment, buffer, AMQFrame::DECODE_SIZE_MIN - fragment.size());
+ }
+ if (fragment.size() >= AMQFrame::DECODE_SIZE_MIN) {
+ uint16_t size = AMQFrame::decodeSize(&fragment[0]);
+ if (size <= fragment.size())
+ throw FramingErrorException(QPID_MSG("Frame size " << size << " is too small."));
+ append(fragment, buffer, size-fragment.size());
+ Buffer b(&fragment[0], fragment.size());
+ if (frame.decode(b)) {
+ assert(b.available() == 0);
+ fragment.clear();
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void FrameDecoder::setFragment(const char* data, size_t size) {
+ fragment.resize(size);
+ ::memcpy(&fragment[0], data, size);
+}
+
+std::pair<const char*, size_t> FrameDecoder::getFragment() const {
+ return std::pair<const char*, size_t>(&fragment[0], fragment.size());
+}
+
+}} // namespace qpid::framing
diff --git a/qpid/cpp/src/qpid/framing/FrameDecoder.h b/qpid/cpp/src/qpid/framing/FrameDecoder.h
new file mode 100644
index 0000000000..26bed6c447
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/FrameDecoder.h
@@ -0,0 +1,52 @@
+#ifndef QPID_FRAMING_FRAMEDECODER_H
+#define QPID_FRAMING_FRAMEDECODER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+namespace framing {
+
+/**
+ * Decode a frame from buffer. If buffer does not contain a complete
+ * frame, caches the fragment for the next call to decode.
+ */
+class FrameDecoder
+{
+ public:
+ QPID_COMMON_EXTERN bool decode(Buffer& buffer);
+ const AMQFrame& getFrame() const { return frame; }
+ AMQFrame& getFrame() { return frame; }
+
+ void setFragment(const char*, size_t);
+ std::pair<const char*, size_t> getFragment() const;
+
+ private:
+ std::vector<char> fragment;
+ AMQFrame frame;
+
+};
+}} // namespace qpid::framing
+
+#endif /*!QPID_FRAMING_FRAMEDECODER_H*/
diff --git a/qpid/cpp/src/qpid/framing/FrameDefaultVisitor.h b/qpid/cpp/src/qpid/framing/FrameDefaultVisitor.h
new file mode 100644
index 0000000000..bd676960bf
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/FrameDefaultVisitor.h
@@ -0,0 +1,60 @@
+#ifndef QPID_FRAMING_FRAMEVISITOR_H
+#define QPID_FRAMING_FRAMEVISITOR_H
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/framing/MethodBodyDefaultVisitor.h"
+#include "qpid/framing/AMQBody.h"
+#include "qpid/framing/AMQMethodBody.h"
+#include "qpid/framing/AMQHeaderBody.h"
+#include "qpid/framing/AMQContentBody.h"
+#include "qpid/framing/AMQHeartbeatBody.h"
+
+namespace qpid {
+namespace framing {
+/**
+ * Visitor for all concrete frame body types, which combines
+ * AMQBodyConstVisitor and MethodBodyDefaultVisitor.
+ *
+ * Derived classes can override visit methods to specify actions.
+ * Derived classes must override defaultVisit(), which is called
+ * for any non-overridden visit functions.
+ *
+ */
+struct FrameDefaultVisitor : public AMQBodyConstVisitor,
+ protected MethodBodyDefaultVisitor
+{
+ virtual void defaultVisit(const AMQBody&) = 0;
+ void defaultVisit(const AMQMethodBody& method) { defaultVisit(static_cast<const AMQBody&>(method)); }
+
+ void visit(const AMQHeaderBody& b) { defaultVisit(b); }
+ void visit(const AMQContentBody& b) { defaultVisit(b); }
+ void visit(const AMQHeartbeatBody& b) { defaultVisit(b); }
+ void visit(const AMQMethodBody& b) { b.accept(static_cast<MethodBodyDefaultVisitor&>(*this)); }
+
+ using AMQBodyConstVisitor::visit;
+ using MethodBodyDefaultVisitor::visit;
+};
+
+}} // namespace qpid::framing
+
+
+#endif /*!QPID_FRAMING_FRAMEVISITOR_H*/
diff --git a/qpid/cpp/src/qpid/framing/FrameHandler.h b/qpid/cpp/src/qpid/framing/FrameHandler.h
new file mode 100644
index 0000000000..fa1fb535ef
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/FrameHandler.h
@@ -0,0 +1,33 @@
+#ifndef QPID_FRAMING_FRAMEHANDLER_H
+#define QPID_FRAMING_FRAMEHANDLER_H
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/framing/Handler.h"
+
+namespace qpid {
+namespace framing {
+
+class AMQFrame;
+typedef Handler<AMQFrame&> FrameHandler;
+
+
+}}
+#endif /*!QPID_FRAMING_FRAMEHANDLER_H*/
diff --git a/qpid/cpp/src/qpid/framing/FrameSet.cpp b/qpid/cpp/src/qpid/framing/FrameSet.cpp
new file mode 100644
index 0000000000..4089475c7a
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/FrameSet.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/framing/FrameSet.h"
+#include "qpid/framing/all_method_bodies.h"
+#include "qpid/framing/frame_functors.h"
+#include "qpid/framing/MessageProperties.h"
+#include "qpid/framing/TypeFilter.h"
+
+using namespace qpid::framing;
+using qpid::sys::AbsTime;
+using qpid::sys::TIME_MSEC;
+
+FrameSet::FrameSet(const SequenceNumber& _id) : id(_id),contentSize(0),recalculateSize(true),received(AbsTime::FarFuture()) { }
+FrameSet::FrameSet(const FrameSet& original) : id(original.id), contentSize(0), recalculateSize(true), received(AbsTime::FarFuture())
+{
+ for (Frames::const_iterator i = original.begin(); i != original.end(); ++i) {
+ parts.push_back(AMQFrame(*(i->getBody())));
+ parts.back().setFirstSegment(i->isFirstSegment());
+ parts.back().setLastSegment(i->isLastSegment());
+ parts.back().setFirstFrame(i->isFirstFrame());
+ parts.back().setLastFrame(i->isLastFrame());
+ }
+}
+
+void FrameSet::append(const AMQFrame& part)
+{
+ parts.push_back(part);
+ recalculateSize = true;
+}
+
+bool FrameSet::isComplete() const
+{
+ return !parts.empty() && parts.back().getEof() && parts.back().getEos();
+}
+
+bool FrameSet::isContentBearing() const
+{
+ const AMQMethodBody* method = getMethod();
+ return method && method->isContentBearing();
+}
+
+const AMQMethodBody* FrameSet::getMethod() const
+{
+ return parts.empty() ? 0 : parts[0].getMethod();
+}
+
+AMQMethodBody* FrameSet::getMethod()
+{
+ return parts.empty() ? 0 : parts[0].getMethod();
+}
+
+const AMQHeaderBody* FrameSet::getHeaders() const
+{
+ return parts.size() < 2 ? 0 : parts[1].castBody<AMQHeaderBody>();
+}
+
+AMQHeaderBody* FrameSet::getHeaders()
+{
+ return parts.size() < 2 ? 0 : parts[1].castBody<AMQHeaderBody>();
+}
+
+uint64_t FrameSet::getContentSize() const
+{
+ if (recalculateSize)
+ {
+ SumBodySize sum;
+ map_if(sum, TypeFilter<CONTENT_BODY>());
+ contentSize = sum.getSize();
+ recalculateSize = false;
+ }
+ return contentSize;
+}
+
+void FrameSet::getContent(std::string& out) const {
+ out.clear();
+ out.reserve(getContentSize());
+ for(Frames::const_iterator i = parts.begin(); i != parts.end(); i++) {
+ if (i->getBody()->type() == CONTENT_BODY)
+ out += i->castBody<AMQContentBody>()->getData();
+ }
+}
+
+std::string FrameSet::getContent() const {
+ std::string out;
+ getContent(out);
+ return out;
+}
+
+bool FrameSet::hasContent() const {
+ return parts.size() >= 3;
+}
+
+void FrameSet::setReceived()
+{
+ received = AbsTime::now();
+}
+namespace {
+uint64_t MAX_TTL = std::numeric_limits<int64_t>::max()/TIME_MSEC;
+}
+
+bool FrameSet::hasExpired() const
+{
+ const DeliveryProperties* props = getHeaderProperties<DeliveryProperties>();
+ if (props && props->hasTtl() && props->getTtl() < MAX_TTL) {
+ AbsTime expiration(received, props->getTtl()*TIME_MSEC);
+ return expiration < AbsTime::now();
+ }
+ return false;
+}
diff --git a/qpid/cpp/src/qpid/framing/FrameSet.h b/qpid/cpp/src/qpid/framing/FrameSet.h
new file mode 100644
index 0000000000..e234864dfd
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/FrameSet.h
@@ -0,0 +1,130 @@
+#ifndef QPID_FRAMING_FRAMESET_H
+#define QPID_FRAMING_FRAMESET_H
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <string>
+#include "qpid/InlineVector.h"
+#include "qpid/framing/amqp_framing.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/SequenceNumber.h"
+#include "qpid/sys/Time.h"
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+namespace framing {
+
+/**
+ * Collects the frames representing a message.
+ */
+class FrameSet
+{
+public:
+ typedef InlineVector<AMQFrame, 4> Frames;
+
+private:
+ const SequenceNumber id;
+ Frames parts;
+ mutable uint64_t contentSize;
+ mutable bool recalculateSize;
+ qpid::sys::AbsTime received;
+
+public:
+ typedef boost::shared_ptr<FrameSet> shared_ptr;
+ typedef Frames::iterator iterator;
+ typedef Frames::const_iterator const_iterator;
+
+ QPID_COMMON_EXTERN FrameSet(const SequenceNumber& id);
+ QPID_COMMON_EXTERN FrameSet(const FrameSet&);
+ QPID_COMMON_EXTERN void append(const AMQFrame& part);
+ QPID_COMMON_EXTERN bool isComplete() const;
+
+ QPID_COMMON_EXTERN uint64_t getContentSize() const;
+
+ QPID_COMMON_EXTERN void getContent(std::string&) const;
+ QPID_COMMON_EXTERN std::string getContent() const;
+ QPID_COMMON_EXTERN bool hasContent() const;
+
+ QPID_COMMON_EXTERN void setReceived();
+ QPID_COMMON_EXTERN bool hasExpired() const;
+
+ bool isContentBearing() const;
+
+ QPID_COMMON_EXTERN const AMQMethodBody* getMethod() const;
+ QPID_COMMON_EXTERN AMQMethodBody* getMethod();
+ QPID_COMMON_EXTERN const AMQHeaderBody* getHeaders() const;
+ QPID_COMMON_EXTERN AMQHeaderBody* getHeaders();
+
+ template <class T> bool isA() const {
+ const AMQMethodBody* method = getMethod();
+ return method && method->isA<T>();
+ }
+
+ template <class T> const T* as() const {
+ const AMQMethodBody* method = getMethod();
+ return (method && method->isA<T>()) ? dynamic_cast<const T*>(method) : 0;
+ }
+
+ template <class T> T* as() {
+ AMQMethodBody* method = getMethod();
+ return (method && method->isA<T>()) ? dynamic_cast<T*>(method) : 0;
+ }
+
+ template <class T> const T* getHeaderProperties() const {
+ const AMQHeaderBody* header = getHeaders();
+ return header ? header->get<T>() : 0;
+ }
+
+ Frames::const_iterator begin() const { return parts.begin(); }
+ Frames::const_iterator end() const { return parts.end(); }
+
+ const SequenceNumber& getId() const { return id; }
+
+ template <class P> void remove(P predicate) {
+ parts.erase(std::remove_if(parts.begin(), parts.end(), predicate), parts.end());
+ }
+
+ template <class F> void map(F& functor) {
+ std::for_each(parts.begin(), parts.end(), functor);
+ }
+
+ template <class F> void map(F& functor) const {
+ std::for_each(parts.begin(), parts.end(), functor);
+ }
+
+ template <class F, class P> void map_if(F& functor, P predicate) {
+ for(Frames::iterator i = parts.begin(); i != parts.end(); i++) {
+ if (predicate(*i)) functor(*i);
+ }
+ }
+
+ template <class F, class P> void map_if(F& functor, P predicate) const {
+ for(Frames::const_iterator i = parts.begin(); i != parts.end(); i++) {
+ if (predicate(*i)) functor(*i);
+ }
+ }
+};
+
+}
+}
+
+
+#endif /*!QPID_FRAMING_FRAMESET_H*/
diff --git a/qpid/cpp/src/qpid/framing/Handler.h b/qpid/cpp/src/qpid/framing/Handler.h
new file mode 100644
index 0000000000..f2a9fccde0
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/Handler.h
@@ -0,0 +1,103 @@
+#ifndef QPID_FRAMING_HANDLER_H
+#define QPID_FRAMING_HANDLER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+namespace qpid {
+namespace framing {
+
+template <class T>
+struct Handler {
+ typedef T HandledType;
+ typedef void handleFptr(T);
+ typedef void result_type; // Compatible with std/boost functors.
+
+ Handler(Handler<T>* next_=0) : next(next_) {}
+ virtual ~Handler() {}
+ virtual void handle(T) = 0;
+
+ /** Allow functor syntax for calling handle */
+ void operator()(T t) { handle(t); }
+
+
+ /** Pointer to next handler in a linked list. */
+ Handler<T>* next;
+
+ /** Adapt any void(T) functor as a Handler.
+ * Functor<F>(f) will copy f.
+ * Functor<F&>(f) will only take a reference to x.
+ */
+ template <class F> class Functor;
+
+ /** Adapt a member function of X as a Handler.
+ * Only holds a reference to its target, not a copy.
+ */
+ template <class X, void (X::*F)(T)> class MemFunRef;
+
+ /** Interface for a handler that implements a
+ * pair of in/out handle operations.
+ * @see InOutHandler
+ */
+ class InOutHandlerInterface {
+ public:
+ virtual ~InOutHandlerInterface() {}
+ virtual void handleIn(T) = 0;
+ virtual void handleOut(T) = 0;
+ };
+
+ /** Support for implementing an in-out handler pair as a single class.
+ * Overrides handleIn, handleOut functions in a single class.
+ */
+ struct InOutHandler : protected InOutHandlerInterface {
+ InOutHandler(Handler<T>* nextIn=0, Handler<T>* nextOut=0) : in(*this, nextIn), out(*this, nextOut) {}
+ MemFunRef<InOutHandlerInterface, &InOutHandlerInterface::handleIn> in;
+ MemFunRef<InOutHandlerInterface, &InOutHandlerInterface::handleOut> out;
+ };
+};
+
+template <class T>
+template <class F>
+class Handler<T>::Functor : public Handler<T> {
+ public:
+ Functor(F f, Handler<T>* next=0) : Handler<T>(next), functor(f) {}
+ void handle(T t) { functor(t); }
+ private:
+ F functor;
+};
+
+template <class T>
+template <class X, void (X::*F)(T)>
+class Handler<T>::MemFunRef : public Handler<T> {
+ public:
+ MemFunRef(X& x, Handler<T>* next=0) : Handler(next), target(&x) {}
+ void handle(T t) { (target->*F)(t); }
+
+ /** Allow calling with -> syntax */
+ MemFunRef* operator->() { return this; }
+
+ private:
+ X* target;
+};
+
+}}
+#endif /*!QPID_FRAMING_HANDLER_H*/
+//
diff --git a/qpid/cpp/src/qpid/framing/HeaderProperties.h b/qpid/cpp/src/qpid/framing/HeaderProperties.h
new file mode 100644
index 0000000000..8b1828daec
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/HeaderProperties.h
@@ -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.
+ *
+ */
+#include "qpid/framing/amqp_types.h"
+#include "qpid/framing/Buffer.h"
+
+#ifndef _HeaderProperties_
+#define _HeaderProperties_
+
+namespace qpid {
+namespace framing {
+
+ class HeaderProperties
+ {
+
+ public:
+ inline virtual ~HeaderProperties(){}
+ virtual uint8_t classId() const = 0;
+ virtual uint32_t encodedSize() const = 0;
+ virtual void encode(Buffer& buffer) const = 0;
+ virtual void decode(Buffer& buffer, uint32_t size) = 0;
+ };
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/framing/InitiationHandler.cpp b/qpid/cpp/src/qpid/framing/InitiationHandler.cpp
new file mode 100644
index 0000000000..7ded505a47
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/InitiationHandler.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/framing/InitiationHandler.h"
+
+qpid::framing::InitiationHandler::~InitiationHandler() {}
diff --git a/qpid/cpp/src/qpid/framing/InitiationHandler.h b/qpid/cpp/src/qpid/framing/InitiationHandler.h
new file mode 100644
index 0000000000..5dfcc6b468
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/InitiationHandler.h
@@ -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.
+ *
+ */
+#include <string>
+
+#ifndef _InitiationHandler_
+#define _InitiationHandler_
+
+#include "qpid/framing/ProtocolInitiation.h"
+
+namespace qpid {
+namespace framing {
+
+ class InitiationHandler{
+ public:
+ virtual ~InitiationHandler();
+ virtual void initiated(const ProtocolInitiation&) = 0;
+ };
+
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/framing/InputHandler.h b/qpid/cpp/src/qpid/framing/InputHandler.h
new file mode 100644
index 0000000000..3efb23632a
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/InputHandler.h
@@ -0,0 +1,41 @@
+#ifndef _InputHandler_
+#define _InputHandler_
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/framing/FrameHandler.h"
+#include <boost/noncopyable.hpp>
+
+namespace qpid {
+namespace framing {
+
+// TODO aconway 2007-08-29: Eliminate, replace with FrameHandler.
+class InputHandler : public FrameHandler {
+ public:
+ virtual ~InputHandler() {}
+ virtual void received(AMQFrame&) = 0;
+ void handle(AMQFrame& f) { received(f); }
+};
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/framing/Invoker.h b/qpid/cpp/src/qpid/framing/Invoker.h
new file mode 100644
index 0000000000..4f1cf7c331
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/Invoker.h
@@ -0,0 +1,86 @@
+#ifndef QPID_FRAMING_INVOKER_H
+#define QPID_FRAMING_INVOKER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/framing/AMQMethodBody.h"
+#include "qpid/framing/MethodBodyDefaultVisitor.h"
+#include "qpid/framing/StructHelper.h"
+
+#include <boost/optional.hpp>
+
+namespace qpid {
+namespace framing {
+
+class AMQMethodBody;
+
+/**
+ * Base class for invoker visitors.
+ */
+class Invoker: public MethodBodyDefaultVisitor, protected StructHelper
+{
+ public:
+ struct Result {
+ public:
+ Result() : handled(false) {}
+ const std::string& getResult() const { return result; }
+ bool hasResult() const { return !result.empty(); }
+ bool wasHandled() const { return handled; }
+ operator bool() const { return handled; }
+
+ std::string result;
+ bool handled;
+ };
+
+ void defaultVisit(const AMQMethodBody&) {}
+ Result getResult() const { return result; }
+
+ protected:
+ Result result;
+};
+
+/**
+ * Invoke an invocable object.
+ * Invocable classes must provide a nested type Invoker.
+ */
+template <class Invocable>
+Invoker::Result invoke(Invocable& target, const AMQMethodBody& body) {
+ typename Invocable::Invoker invoker(target);
+ body.accept(invoker);
+ return invoker.getResult();
+}
+
+/**
+ * Invoke an invocable object.
+ * Invocable classes must provide a nested type Invoker.
+ */
+template <class Invocable>
+Invoker::Result invoke(Invocable& target, const AMQBody& body) {
+ typename Invocable::Invoker invoker(target);
+ const AMQMethodBody* method = body.getMethod();
+ if (method)
+ method->accept(invoker);
+ return invoker.getResult();
+}
+
+}} // namespace qpid::framing
+
+#endif /*!QPID_FRAMING_INVOKER_H*/
diff --git a/qpid/cpp/src/qpid/framing/IsInSequenceSet.h b/qpid/cpp/src/qpid/framing/IsInSequenceSet.h
new file mode 100644
index 0000000000..fe10c1b9fa
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/IsInSequenceSet.h
@@ -0,0 +1,51 @@
+#ifndef QPID_FRAMING_ISINSEQUENCESET_H
+#define QPID_FRAMING_ISINSEQUENCESET_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/framing/SequenceSet.h"
+
+namespace qpid {
+namespace framing {
+/**
+ * Functor to test whether values are in a sequence set. This is a
+ * stateful functor that requires the values to be supplied in order
+ * and takes advantage of that ordering to avoid multiple scans.
+ */
+class IsInSequenceSet
+{
+ public:
+ IsInSequenceSet(const SequenceSet& s) : set(s), i(set.rangesBegin()) {}
+
+ bool operator()(const SequenceNumber& n) {
+ while (i != set.rangesEnd() && i->end() <= n) ++i;
+ return i != set.rangesEnd() && i->begin() <= n;
+ }
+
+ private:
+ const SequenceSet& set;
+ SequenceSet::RangeIterator i;
+};
+
+}} // namespace qpid::framing
+
+#endif /*!QPID_FRAMING_ISINSEQUENCESET_H*/
diff --git a/qpid/cpp/src/qpid/framing/List.cpp b/qpid/cpp/src/qpid/framing/List.cpp
new file mode 100644
index 0000000000..d7ea172bac
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/List.cpp
@@ -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.
+ *
+ */
+#include "qpid/framing/List.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/Exception.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/Msg.h"
+
+namespace qpid {
+namespace framing {
+
+uint32_t List::encodedSize() const
+{
+ uint32_t len(4/*size*/ + 4/*count*/);
+ for(Values::const_iterator i = values.begin(); i != values.end(); ++i) {
+ len += (*i)->encodedSize();
+ }
+ return len;
+}
+
+void List::encode(Buffer& buffer) const
+{
+ buffer.putLong(encodedSize() - 4);
+ buffer.putLong(size());
+ for (Values::const_iterator i = values.begin(); i!=values.end(); ++i) {
+ (*i)->encode(buffer);
+ }
+}
+
+void List::decode(Buffer& buffer)
+{
+ values.clear();
+ if (buffer.available() < 4)
+ throw IllegalArgumentException(QPID_MSG("Not enough data for list, expected at least "
+ " 4 bytes but only " << buffer.available() << " available"));
+ uint32_t size = buffer.getLong();
+ uint32_t available = buffer.available();
+ if (available < size) {
+ throw IllegalArgumentException(QPID_MSG("Not enough data for list, expected "
+ << size << " bytes but only " << available << " available"));
+ }
+ if (size) {
+ if (buffer.available() < 4)
+ throw IllegalArgumentException(QPID_MSG("Not enough data for list, expected at least "
+ " 4 bytes but only " << buffer.available() << " available"));
+ uint32_t count = buffer.getLong();
+ for (uint32_t i = 0; i < count; i++) {
+ ValuePtr value(new FieldValue);
+ value->decode(buffer);
+ values.push_back(value);
+ }
+ }
+}
+
+
+bool List::operator==(const List& other) const {
+ return values.size() == other.values.size() &&
+ std::equal(values.begin(), values.end(), other.values.begin());
+}
+
+std::ostream& operator<<(std::ostream& out, const List& l)
+{
+ out << "{";
+ for(List::Values::const_iterator i = l.values.begin(); i != l.values.end(); ++i) {
+ if (i != l.values.begin()) out << ", ";
+ (*i)->print(out);
+ }
+ return out << "}";
+}
+
+}} // namespace qpid::framing
diff --git a/qpid/cpp/src/qpid/framing/List.h b/qpid/cpp/src/qpid/framing/List.h
new file mode 100644
index 0000000000..681445947c
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/List.h
@@ -0,0 +1,78 @@
+#ifndef QPID_FRAMING_LIST_H
+#define QPID_FRAMING_LIST_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/CommonImportExport.h"
+#include "qpid/framing/amqp_types.h"
+#include <iostream>
+#include <list>
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace framing {
+
+class Buffer;
+class FieldValue;
+
+/**
+ * Representation of an AMQP 0-10 list
+ */
+class QPID_COMMON_CLASS_EXTERN List
+{
+ public:
+ typedef boost::shared_ptr<FieldValue> ValuePtr;
+ typedef ValuePtr value_type;
+ typedef std::list<ValuePtr> Values;
+ typedef Values::const_iterator const_iterator;
+ typedef Values::iterator iterator;
+ typedef Values::const_reference const_reference;
+ typedef Values::reference reference;
+
+ QPID_COMMON_EXTERN uint32_t encodedSize() const;
+ QPID_COMMON_EXTERN void encode(Buffer& buffer) const;
+ QPID_COMMON_EXTERN void decode(Buffer& buffer);
+
+ QPID_COMMON_EXTERN bool operator==(const List& other) const;
+
+ // std collection interface.
+ QPID_COMMON_INLINE_EXTERN const_iterator begin() const { return values.begin(); }
+ QPID_COMMON_INLINE_EXTERN const_iterator end() const { return values.end(); }
+ QPID_COMMON_INLINE_EXTERN iterator begin() { return values.begin(); }
+ QPID_COMMON_INLINE_EXTERN iterator end(){ return values.end(); }
+
+ QPID_COMMON_INLINE_EXTERN ValuePtr front() const { return values.front(); }
+ QPID_COMMON_INLINE_EXTERN ValuePtr back() const { return values.back(); }
+ QPID_COMMON_INLINE_EXTERN size_t size() const { return values.size(); }
+
+ QPID_COMMON_INLINE_EXTERN iterator insert(iterator i, ValuePtr value) { return values.insert(i, value); }
+ QPID_COMMON_INLINE_EXTERN void erase(iterator i) { values.erase(i); }
+ QPID_COMMON_INLINE_EXTERN void push_back(ValuePtr value) { values.insert(end(), value); }
+ QPID_COMMON_INLINE_EXTERN void pop_back() { values.pop_back(); }
+
+ private:
+ Values values;
+
+ friend QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream& out, const List& list);
+};
+}} // namespace qpid::framing
+
+#endif /*!QPID_FRAMING_LIST_H*/
diff --git a/qpid/cpp/src/qpid/framing/MethodBodyFactory.h b/qpid/cpp/src/qpid/framing/MethodBodyFactory.h
new file mode 100644
index 0000000000..88bc444795
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/MethodBodyFactory.h
@@ -0,0 +1,45 @@
+#ifndef QPID_FRAMING_METHODBODYFACTORY_H
+#define QPID_FRAMING_METHODBODYFACTORY_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/framing/amqp_types.h"
+#include "qpid/framing/AMQBody.h"
+#include <boost/intrusive_ptr.hpp>
+
+namespace qpid {
+namespace framing {
+
+class AMQMethodBody;
+
+/**
+ * Functions to create instances of AMQMethodBody sub-classes.
+ * Note: MethodBodyFactory.cpp file is generated by rubygen.
+ */
+class MethodBodyFactory
+{
+ public:
+ static boost::intrusive_ptr<AMQMethodBody> create(ClassId c, MethodId m);
+};
+
+}} // namespace qpid::framing
+
+#endif /*!QPID_FRAMING_METHODBODYFACTORY_H*/
diff --git a/qpid/cpp/src/qpid/framing/MethodContent.h b/qpid/cpp/src/qpid/framing/MethodContent.h
new file mode 100644
index 0000000000..58c9143cfa
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/MethodContent.h
@@ -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.
+ *
+ */
+#ifndef _MethodContent_
+#define _MethodContent_
+
+#include <string>
+#include "qpid/framing/AMQHeaderBody.h"
+
+namespace qpid {
+namespace framing {
+
+class MethodContent
+{
+public:
+ virtual ~MethodContent() {}
+ //TODO: rethink this interface
+ virtual const AMQHeaderBody& getHeader() const = 0;
+ virtual const std::string& getData() const = 0;
+};
+
+}}
+#endif
diff --git a/qpid/cpp/src/qpid/framing/ModelMethod.h b/qpid/cpp/src/qpid/framing/ModelMethod.h
new file mode 100644
index 0000000000..d99bd06cfa
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/ModelMethod.h
@@ -0,0 +1,49 @@
+#ifndef _ModelMethod_
+#define _ModelMethod_
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/framing/AMQMethodBody.h"
+#include "qpid/framing/Header.h"
+
+namespace qpid {
+namespace framing {
+
+
+class ModelMethod : public AMQMethodBody
+{
+ mutable Header header;
+public:
+ virtual ~ModelMethod() {}
+ virtual void encodeHeader(Buffer& buffer) const { header.encode(buffer); }
+ virtual void decodeHeader(Buffer& buffer, uint32_t size=0) { header.decode(buffer, size); }
+ virtual uint32_t headerSize() const { return header.encodedSize(); }
+ virtual bool isSync() const { return header.getSync(); }
+ virtual void setSync(bool on) const { header.setSync(on); }
+ Header& getHeader() { return header; }
+ const Header& getHeader() const { return header; }
+};
+
+
+}} // namespace qpid::framing
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/framing/ProtocolInitiation.cpp b/qpid/cpp/src/qpid/framing/ProtocolInitiation.cpp
new file mode 100644
index 0000000000..19cb3f0e3d
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/ProtocolInitiation.cpp
@@ -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.
+ *
+ */
+#include "qpid/framing/ProtocolInitiation.h"
+
+#include <iostream>
+
+namespace qpid {
+namespace framing {
+
+ProtocolInitiation::ProtocolInitiation(){}
+
+ProtocolInitiation::ProtocolInitiation(uint8_t _major, uint8_t _minor) : version(_major, _minor) {}
+
+ProtocolInitiation::ProtocolInitiation(ProtocolVersion p) : version(p) {}
+
+ProtocolInitiation::~ProtocolInitiation(){}
+
+void ProtocolInitiation::encode(Buffer& buffer) const {
+ buffer.putOctet('A');
+ buffer.putOctet('M');
+ buffer.putOctet('Q');
+ buffer.putOctet('P');
+ if (version.getMajor() == 1) {
+ buffer.putOctet(version.getProtocol());
+ buffer.putOctet(version.getMajor());
+ buffer.putOctet(version.getMinor());
+ buffer.putOctet(0);//revision
+ } else {
+ buffer.putOctet(1);//class
+ buffer.putOctet(1);//instance
+ buffer.putOctet(version.getMajor());
+ buffer.putOctet(version.getMinor());
+ }
+}
+
+bool ProtocolInitiation::decode(Buffer& buffer){
+ if(buffer.available() >= 8){
+ buffer.getOctet();//A
+ buffer.getOctet();//M
+ buffer.getOctet();//Q
+ buffer.getOctet();//P
+ uint8_t protocolClass = buffer.getOctet();//class
+ version.setProtocol(protocolClass);
+ if (protocolClass == 1) {
+ //old (pre-1.0) style
+ buffer.getOctet();//instance
+ version.setMajor(buffer.getOctet());
+ version.setMinor(buffer.getOctet());
+ } else {
+ version.setMajor(buffer.getOctet());
+ version.setMinor(buffer.getOctet());
+ buffer.getOctet();//revision
+ }
+ return true;
+ }else{
+ return false;
+ }
+}
+
+
+std::ostream& operator<<(std::ostream& o, const framing::ProtocolInitiation& pi) {
+ return o << int(pi.getMajor()) << "-" << int(pi.getMinor());
+}
+
+}} // namespace qpid::framing
diff --git a/qpid/cpp/src/qpid/framing/ProtocolInitiation.h b/qpid/cpp/src/qpid/framing/ProtocolInitiation.h
new file mode 100644
index 0000000000..fe6410af55
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/ProtocolInitiation.h
@@ -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.
+ *
+ */
+#include "qpid/framing/amqp_types.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/framing/AMQDataBlock.h"
+#include "qpid/framing/ProtocolVersion.h"
+#include "qpid/CommonImportExport.h"
+
+#ifndef _ProtocolInitiation_
+#define _ProtocolInitiation_
+
+namespace qpid {
+namespace framing {
+
+class ProtocolInitiation : public AMQDataBlock
+{
+private:
+ ProtocolVersion version;
+
+public:
+ QPID_COMMON_EXTERN ProtocolInitiation();
+ QPID_COMMON_EXTERN ProtocolInitiation(uint8_t major, uint8_t minor);
+ QPID_COMMON_EXTERN ProtocolInitiation(ProtocolVersion p);
+ QPID_COMMON_EXTERN virtual ~ProtocolInitiation();
+ QPID_COMMON_EXTERN virtual void encode(Buffer& buffer) const;
+ QPID_COMMON_EXTERN virtual bool decode(Buffer& buffer);
+ inline virtual uint32_t encodedSize() const { return 8; }
+ inline uint8_t getMajor() const { return version.getMajor(); }
+ inline uint8_t getMinor() const { return version.getMinor(); }
+ inline ProtocolVersion getVersion() const { return version; }
+ bool operator==(ProtocolVersion v) const { return v == getVersion(); }
+ bool matches(ProtocolVersion v) const { return v == getVersion(); }
+};
+
+QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream& o, const framing::ProtocolInitiation& pi);
+
+
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/framing/ProtocolVersion.cpp b/qpid/cpp/src/qpid/framing/ProtocolVersion.cpp
new file mode 100644
index 0000000000..269b861191
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/ProtocolVersion.cpp
@@ -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.
+ *
+ */
+#include "qpid/framing/ProtocolVersion.h"
+#include <sstream>
+
+using namespace qpid::framing;
+
+const std::string ProtocolVersion::toString() const
+{
+ std::stringstream ss;
+ ss << unsigned(major_) << "-" << unsigned(minor_);
+ if (major_ == 1) {
+ if (protocol_ == SASL) ss << " (SASL)";
+ else if (protocol_ == TLS) ss << " (TLS)";
+ }
+ return ss.str();
+}
+
+ProtocolVersion& ProtocolVersion::operator=(ProtocolVersion p)
+{
+ major_ = p.major_;
+ minor_ = p.minor_;
+ return *this;
+}
+
+bool ProtocolVersion::operator==(ProtocolVersion p) const
+{
+ return major_ == p.major_ && minor_ == p.minor_;
+}
+
+const uint8_t ProtocolVersion::AMQP(0);
+const uint8_t ProtocolVersion::LEGACY_AMQP(1);
+const uint8_t ProtocolVersion::TLS(2);
+const uint8_t ProtocolVersion::SASL(3);
diff --git a/qpid/cpp/src/qpid/framing/ProtocolVersion.h b/qpid/cpp/src/qpid/framing/ProtocolVersion.h
new file mode 100644
index 0000000000..92580baf1a
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/ProtocolVersion.h
@@ -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.
+ *
+ */
+#ifndef _ProtocolVersion_
+#define _ProtocolVersion_
+
+#include "qpid/framing/amqp_types.h"
+#include "qpid/CommonImportExport.h"
+
+#include <string>
+
+namespace qpid
+{
+namespace framing
+{
+
+class QPID_COMMON_CLASS_EXTERN ProtocolVersion
+{
+private:
+ uint8_t major_;
+ uint8_t minor_;
+ uint8_t protocol_;
+
+public:
+ explicit ProtocolVersion(uint8_t _major=0, uint8_t _minor=0, uint8_t _protocol=0)
+ : major_(_major), minor_(_minor), protocol_(_protocol) {}
+
+ QPID_COMMON_INLINE_EXTERN uint8_t getMajor() const { return major_; }
+ QPID_COMMON_INLINE_EXTERN void setMajor(uint8_t major) { major_ = major; }
+ QPID_COMMON_INLINE_EXTERN uint8_t getMinor() const { return minor_; }
+ QPID_COMMON_INLINE_EXTERN void setMinor(uint8_t minor) { minor_ = minor; }
+ QPID_COMMON_INLINE_EXTERN uint8_t getProtocol() const { return protocol_; }
+ QPID_COMMON_INLINE_EXTERN void setProtocol(uint8_t protocol) { protocol_ = protocol; }
+ QPID_COMMON_EXTERN const std::string toString() const;
+
+ QPID_COMMON_EXTERN ProtocolVersion& operator=(ProtocolVersion p);
+
+ QPID_COMMON_EXTERN bool operator==(ProtocolVersion p) const;
+ QPID_COMMON_INLINE_EXTERN bool operator!=(ProtocolVersion p) const { return ! (*this == p); }
+ QPID_COMMON_EXTERN static const uint8_t AMQP;
+ QPID_COMMON_EXTERN static const uint8_t LEGACY_AMQP;
+ QPID_COMMON_EXTERN static const uint8_t TLS;
+ QPID_COMMON_EXTERN static const uint8_t SASL;
+};
+
+} // namespace framing
+} // namespace qpid
+
+
+#endif // ifndef _ProtocolVersion_
diff --git a/qpid/cpp/src/qpid/framing/Proxy.cpp b/qpid/cpp/src/qpid/framing/Proxy.cpp
new file mode 100644
index 0000000000..452fb13b01
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/Proxy.cpp
@@ -0,0 +1,51 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/framing/Proxy.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/AMQMethodBody.h"
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace framing {
+
+Proxy::Proxy(FrameHandler& h) : out(&h), sync(false) {}
+
+Proxy::~Proxy() {}
+
+void Proxy::send(const AMQBody& b) {
+ if (sync) {
+ const AMQMethodBody* m = dynamic_cast<const AMQMethodBody*>(&b);
+ if (m) m->setSync(sync);
+ }
+ AMQFrame f(b);
+ out->handle(f);
+}
+
+ProtocolVersion Proxy::getVersion() const {
+ return ProtocolVersion();
+}
+
+FrameHandler& Proxy::getHandler() { return *out; }
+
+void Proxy::setHandler(FrameHandler& f) { out=&f; }
+
+Proxy::ScopedSync::ScopedSync(Proxy& p) : proxy(p) { proxy.sync = true; }
+Proxy::ScopedSync::~ScopedSync() { proxy.sync = false; }
+
+}} // namespace qpid::framing
diff --git a/qpid/cpp/src/qpid/framing/Proxy.h b/qpid/cpp/src/qpid/framing/Proxy.h
new file mode 100644
index 0000000000..0884e9cbd2
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/Proxy.h
@@ -0,0 +1,64 @@
+#ifndef _framing_Proxy_h
+#define _framing_Proxy_h
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/framing/FrameHandler.h"
+#include "qpid/framing/ProtocolVersion.h"
+
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+namespace framing {
+
+class AMQBody;
+
+/**
+ * Base class for proxies.
+ */
+class Proxy
+{
+ public:
+ class ScopedSync
+ {
+ Proxy& proxy;
+ public:
+ QPID_COMMON_EXTERN ScopedSync(Proxy& p);
+ QPID_COMMON_EXTERN ~ScopedSync();
+ };
+
+ QPID_COMMON_EXTERN Proxy(FrameHandler& h);
+ QPID_COMMON_EXTERN virtual ~Proxy();
+
+ QPID_COMMON_EXTERN void send(const AMQBody&);
+
+ QPID_COMMON_EXTERN ProtocolVersion getVersion() const;
+
+ QPID_COMMON_EXTERN FrameHandler& getHandler();
+ QPID_COMMON_EXTERN void setHandler(FrameHandler&);
+ private:
+ FrameHandler* out;
+ bool sync;
+};
+
+}} // namespace qpid::framing
+
+
+
+#endif /*!_framing_Proxy_h*/
diff --git a/qpid/cpp/src/qpid/framing/ResizableBuffer.h b/qpid/cpp/src/qpid/framing/ResizableBuffer.h
new file mode 100644
index 0000000000..0abc5ba7f4
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/ResizableBuffer.h
@@ -0,0 +1,60 @@
+#ifndef QPID_FRAMING_RESIZABLEBUFFER_H
+#define QPID_FRAMING_RESIZABLEBUFFER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/framing/Buffer.h"
+#include <vector>
+
+namespace qpid {
+namespace framing {
+
+/**
+ * A buffer that maintains its own storage and can be resized,
+ * keeping any data already written to the buffer.
+ */
+class ResizableBuffer : public Buffer
+{
+ public:
+ ResizableBuffer(size_t initialSize) : store(initialSize) {
+ static_cast<Buffer&>(*this) = Buffer(&store[0], store.size());
+ }
+
+ void resize(size_t newSize) {
+ size_t oldPos = getPosition();
+ store.resize(newSize);
+ static_cast<Buffer&>(*this) = Buffer(&store[0], store.size());
+ setPosition(oldPos);
+ }
+
+ /** Make sure at least n bytes are available */
+ void makeAvailable(size_t n) {
+ if (n > available())
+ resize(getSize() + n - available());
+ }
+
+ private:
+ std::vector<char> store;
+};
+}} // namespace qpid::framing
+
+#endif /*!QPID_FRAMING_RESIZABLEBUFFER_H*/
diff --git a/qpid/cpp/src/qpid/framing/SendContent.cpp b/qpid/cpp/src/qpid/framing/SendContent.cpp
new file mode 100644
index 0000000000..04b60396da
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/SendContent.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/framing/SendContent.h"
+
+qpid::framing::SendContent::SendContent(FrameHandler& h, uint16_t mfs, uint efc) : handler(h),
+ maxFrameSize(mfs),
+ expectedFrameCount(efc), frameCount(0) {}
+
+void qpid::framing::SendContent::operator()(const AMQFrame& f)
+{
+ bool first = frameCount == 0;
+ bool last = ++frameCount == expectedFrameCount;
+
+ uint16_t maxContentSize = maxFrameSize - AMQFrame::frameOverhead();
+ const AMQContentBody* body(f.castBody<AMQContentBody>());
+ if (body->encodedSize() > maxContentSize) {
+ uint32_t offset = 0;
+ for (int chunk = body->encodedSize() / maxContentSize; chunk > 0; chunk--) {
+ sendFragment(*body, offset, maxContentSize, first && offset == 0, last && offset + maxContentSize == body->encodedSize());
+ offset += maxContentSize;
+ }
+ uint32_t remainder = body->encodedSize() % maxContentSize;
+ if (remainder) {
+ sendFragment(*body, offset, remainder, first && offset == 0, last);
+ }
+ } else {
+ AMQFrame copy(f);
+ setFlags(copy, first, last);
+ handler.handle(copy);
+ }
+}
+
+void qpid::framing::SendContent::sendFragment(const AMQContentBody& body, uint32_t offset, uint16_t size, bool first, bool last) const
+{
+ AMQFrame fragment((AMQContentBody(body.getData().substr(offset, size))));
+ setFlags(fragment, first, last);
+ handler.handle(fragment);
+}
+
+void qpid::framing::SendContent::setFlags(AMQFrame& f, bool first, bool last) const
+{
+ f.setBof(false);
+ f.setBos(first);
+ f.setEof(true);//content is always the last segment
+ f.setEos(last);
+}
+
diff --git a/qpid/cpp/src/qpid/framing/SendContent.h b/qpid/cpp/src/qpid/framing/SendContent.h
new file mode 100644
index 0000000000..1c464b9c8b
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/SendContent.h
@@ -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.
+ *
+ */
+#include <string>
+#include "qpid/framing/amqp_framing.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/FrameHandler.h"
+#include "qpid/CommonImportExport.h"
+
+#ifndef _SendContent_
+#define _SendContent_
+
+namespace qpid {
+namespace framing {
+
+/**
+ * Functor that sends frame to handler, refragmenting if
+ * necessary. Currently only works on content frames but this could be
+ * changed once we support multi-frame segments in general.
+ */
+class SendContent
+{
+ FrameHandler& handler;
+ const uint16_t maxFrameSize;
+ uint expectedFrameCount;
+ uint frameCount;
+
+ void sendFragment(const AMQContentBody& body, uint32_t offset, uint16_t size, bool first, bool last) const;
+ void setFlags(AMQFrame& f, bool first, bool last) const;
+public:
+ QPID_COMMON_EXTERN SendContent(FrameHandler& _handler, uint16_t _maxFrameSize, uint frameCount);
+ QPID_COMMON_EXTERN void operator()(const AMQFrame& f);
+};
+
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/framing/SequenceNumber.cpp b/qpid/cpp/src/qpid/framing/SequenceNumber.cpp
new file mode 100644
index 0000000000..41cb236629
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/SequenceNumber.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/framing/SequenceNumber.h"
+#include "qpid/framing/Buffer.h"
+#include <ostream>
+
+using qpid::framing::SequenceNumber;
+using qpid::framing::Buffer;
+
+void SequenceNumber::encode(Buffer& buffer) const
+{
+ buffer.putLong(value);
+}
+
+void SequenceNumber::decode(Buffer& buffer)
+{
+ value = buffer.getLong();
+}
+
+uint32_t SequenceNumber::encodedSize() const {
+ return 4;
+}
+
+namespace qpid {
+namespace framing {
+
+std::ostream& operator<<(std::ostream& o, const SequenceNumber& n) {
+ return o << n.getValue();
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/framing/SequenceNumber.h b/qpid/cpp/src/qpid/framing/SequenceNumber.h
new file mode 100644
index 0000000000..00fa2469c8
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/SequenceNumber.h
@@ -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.
+ *
+ */
+#ifndef _framing_SequenceNumber_h
+#define _framing_SequenceNumber_h
+
+#include "qpid/framing/amqp_types.h"
+#include <boost/operators.hpp>
+#include <iosfwd>
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+namespace framing {
+
+class Buffer;
+
+/**
+ * 4-byte sequence number that 'wraps around'.
+ */
+class QPID_COMMON_CLASS_EXTERN SequenceNumber : public
+boost::equality_comparable<
+ SequenceNumber, boost::less_than_comparable<
+ SequenceNumber, boost::incrementable<
+ SequenceNumber, boost::decrementable<SequenceNumber> > > >
+{
+ int32_t value;
+
+ public:
+ SequenceNumber(uint32_t v=0) : value(v) {}
+
+ SequenceNumber& operator++() { ++value; return *this; }
+ SequenceNumber& operator--() { --value; return *this; }
+ bool operator==(const SequenceNumber& other) const { return value == other.value; }
+ bool operator<(const SequenceNumber& other) const { return (value - other.value) < 0; }
+ uint32_t getValue() const { return uint32_t(value); }
+ operator uint32_t() const { return uint32_t(value); }
+
+ QPID_COMMON_EXTERN void encode(Buffer& buffer) const;
+ QPID_COMMON_EXTERN void decode(Buffer& buffer);
+ QPID_COMMON_EXTERN uint32_t encodedSize() const;
+
+ template <class S> void serialize(S& s) { s(value); }
+};
+
+inline int32_t operator-(const SequenceNumber& a, const SequenceNumber& b) {
+ return int32_t(a.getValue() - b.getValue());
+}
+
+inline SequenceNumber operator+(const SequenceNumber& a, int32_t n) {
+ return SequenceNumber(a.getValue() + n);
+}
+
+inline SequenceNumber operator-(const SequenceNumber& a, int32_t n) {
+ return SequenceNumber(a.getValue() - n);
+}
+
+struct Window
+{
+ SequenceNumber hwm;
+ SequenceNumber lwm;
+};
+
+QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream& o, const SequenceNumber& n);
+
+}} // namespace qpid::framing
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/framing/SequenceNumberSet.cpp b/qpid/cpp/src/qpid/framing/SequenceNumberSet.cpp
new file mode 100644
index 0000000000..e9d78f3c17
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/SequenceNumberSet.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/framing/SequenceNumberSet.h"
+
+using namespace qpid::framing;
+
+void SequenceNumberSet::encode(Buffer& buffer) const
+{
+ buffer.putShort(size() * 4);
+ for (const_iterator i = begin(); i != end(); i++) {
+ buffer.putLong(i->getValue());
+ }
+}
+
+void SequenceNumberSet::decode(Buffer& buffer)
+{
+ clear();
+ uint16_t count = (buffer.getShort() / 4);
+ for (uint16_t i = 0; i < count; i++) {
+ push_back(SequenceNumber(buffer.getLong()));
+ }
+}
+
+uint32_t SequenceNumberSet::encodedSize() const
+{
+ return 2 /*count*/ + (size() * 4);
+}
+
+SequenceNumberSet SequenceNumberSet::condense() const
+{
+ SequenceNumberSet result;
+ const_iterator last = end();
+ const_iterator start = end();
+ for (const_iterator i = begin(); i != end(); i++) {
+ if (start == end()) {
+ start = i;
+ } else if (*i - *last > 1) {
+ result.push_back(*start);
+ result.push_back(*last);
+ start = i;
+ }
+ last = i;
+ }
+ if (start != end()) {
+ result.push_back(*start);
+ result.push_back(*last);
+ }
+ return result;
+}
+
+void SequenceNumberSet::addRange(const SequenceNumber& start, const SequenceNumber& end)
+{
+ push_back(start);
+ push_back(end);
+}
+
+namespace qpid{
+namespace framing{
+
+std::ostream& operator<<(std::ostream& out, const SequenceNumberSet& set) {
+ out << "{";
+ for (SequenceNumberSet::const_iterator i = set.begin(); i != set.end(); i++) {
+ if (i != set.begin()) out << ", ";
+ out << (i->getValue());
+ }
+ out << "}";
+ return out;
+}
+
+}
+}
diff --git a/qpid/cpp/src/qpid/framing/SequenceNumberSet.h b/qpid/cpp/src/qpid/framing/SequenceNumberSet.h
new file mode 100644
index 0000000000..c8356c8163
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/SequenceNumberSet.h
@@ -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.
+ *
+ */
+#ifndef _framing_SequenceNumberSet_h
+#define _framing_SequenceNumberSet_h
+
+#include <ostream>
+#include "qpid/framing/amqp_types.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/framing/SequenceNumber.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/InlineVector.h"
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+namespace framing {
+
+class SequenceNumberSet : public InlineVector<SequenceNumber, 2>
+{
+ typedef InlineVector<SequenceNumber, 2> Base;
+public:
+ typedef Base::const_iterator const_iterator;
+ typedef Base::iterator iterator;
+
+ void encode(Buffer& buffer) const;
+ void decode(Buffer& buffer);
+ uint32_t encodedSize() const;
+ QPID_COMMON_EXTERN SequenceNumberSet condense() const;
+ QPID_COMMON_EXTERN void addRange(const SequenceNumber& start, const SequenceNumber& end);
+
+ template <class T>
+ void processRanges(T& t) const
+ {
+ if (size() % 2) { //must be even number
+ throw InvalidArgumentException("SequenceNumberSet contains odd number of elements");
+ }
+
+ for (SequenceNumberSet::const_iterator i = begin(); i != end(); i++) {
+ SequenceNumber first = *(i);
+ SequenceNumber last = *(++i);
+ t(first, last);
+ }
+ }
+
+ friend QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream&, const SequenceNumberSet&);
+};
+
+
+}} // namespace qpid::framing
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/framing/SequenceSet.cpp b/qpid/cpp/src/qpid/framing/SequenceSet.cpp
new file mode 100644
index 0000000000..6510842c58
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/SequenceSet.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/framing/SequenceSet.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/Msg.h"
+
+using namespace qpid::framing;
+using std::max;
+using std::min;
+
+namespace qpid {
+namespace framing {
+
+namespace {
+//each range contains 2 numbers, 4 bytes each
+uint16_t RANGE_SIZE = 2 * 4;
+int32_t MAX_RANGE = 2147483647;//2^31-1
+
+int32_t gap(const SequenceNumber& a, const SequenceNumber& b)
+{
+ return a < b ? b - a : a - b;
+}
+
+bool is_max_range(const SequenceNumber& a, const SequenceNumber& b)
+{
+ return gap(a, b) == MAX_RANGE;
+}
+}
+
+void SequenceSet::encode(Buffer& buffer) const
+{
+ buffer.putShort(rangesSize() * RANGE_SIZE);
+ for (RangeIterator i = rangesBegin(); i != rangesEnd(); i++) {
+ buffer.putLong(i->first().getValue());
+ buffer.putLong(i->last().getValue());
+ }
+}
+
+void SequenceSet::decode(Buffer& buffer)
+{
+ clear();
+ uint16_t size = buffer.getShort();
+ uint16_t count = size / RANGE_SIZE;//number of ranges
+ if (size % RANGE_SIZE)
+ throw IllegalArgumentException(QPID_MSG("Invalid size for sequence set: " << size));
+
+ for (uint16_t i = 0; i < count; i++) {
+ SequenceNumber a(buffer.getLong());
+ SequenceNumber b(buffer.getLong());
+ if (b < a)
+ throw IllegalArgumentException(QPID_MSG("Invalid range in sequence set: " << a << " -> " << b));
+ if (is_max_range(a, b)) {
+ //RangeSet holds 'half-closed' ranges, where the end is
+ //one past the 'highest' value in the range. So if the
+ //range is already the maximum expressable with a 32bit
+ //sequence number, we can't represent it as a
+ //'half-closed' range, so we represent it as two ranges.
+ add(a, b-1);
+ add(b);
+ } else {
+ add(a, b);
+ }
+ }
+}
+
+uint32_t SequenceSet::encodedSize() const {
+ return 2 /*size field*/ + (rangesSize() * RANGE_SIZE);
+}
+
+bool SequenceSet::contains(const SequenceNumber& s) const {
+ return RangeSet<SequenceNumber>::contains(s);
+}
+
+void SequenceSet::add(const SequenceNumber& s) { *this += s; }
+
+void SequenceSet::add(const SequenceNumber& start, const SequenceNumber& finish) {
+ *this += Range<SequenceNumber>::makeClosed(std::min(start,finish), std::max(start, finish));
+}
+
+void SequenceSet::add(const SequenceSet& set) { *this += set; }
+
+void SequenceSet::remove(const SequenceSet& set) { *this -= set; }
+
+void SequenceSet::remove(const SequenceNumber& start, const SequenceNumber& finish) {
+ *this -= Range<SequenceNumber>::makeClosed(std::min(start,finish), std::max(start, finish));
+}
+
+void SequenceSet::remove(const SequenceNumber& s) { *this -= s; }
+
+
+struct RangePrinter {
+ std::ostream& out;
+ RangePrinter(std::ostream& o) : out(o) {}
+ void operator()(SequenceNumber i, SequenceNumber j) const {
+ out << "[" << i.getValue() << "," << j.getValue() << "] ";
+ }
+};
+
+std::ostream& operator<<(std::ostream& o, const SequenceSet& s) {
+ RangePrinter print(o);
+ o << "{ ";
+ s.for_each(print);
+ return o << "}";
+}
+
+}} // namespace qpid::framing
+
diff --git a/qpid/cpp/src/qpid/framing/SequenceSet.h b/qpid/cpp/src/qpid/framing/SequenceSet.h
new file mode 100644
index 0000000000..827c8999b3
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/SequenceSet.h
@@ -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.
+ *
+ */
+#ifndef _framing_SequenceSet_h
+#define _framing_SequenceSet_h
+
+#include "qpid/framing/SequenceNumber.h"
+#include "qpid/RangeSet.h"
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+namespace framing {
+class Buffer;
+
+class QPID_COMMON_CLASS_EXTERN SequenceSet : public RangeSet<SequenceNumber> {
+ public:
+ SequenceSet() {}
+ SequenceSet(const RangeSet<SequenceNumber>& r)
+ : RangeSet<SequenceNumber>(r) {}
+ SequenceSet(const SequenceNumber& s) { add(s); }
+ SequenceSet(const SequenceNumber& start, const SequenceNumber finish) { add(start,finish); }
+
+
+ QPID_COMMON_EXTERN void encode(Buffer& buffer) const;
+ QPID_COMMON_EXTERN void decode(Buffer& buffer);
+ QPID_COMMON_EXTERN uint32_t encodedSize() const;
+
+ QPID_COMMON_EXTERN bool contains(const SequenceNumber& s) const;
+ QPID_COMMON_EXTERN void add(const SequenceNumber& s);
+ QPID_COMMON_EXTERN void add(const SequenceNumber& start, const SequenceNumber& finish); // Closed range
+ QPID_COMMON_EXTERN void add(const SequenceSet& set);
+ QPID_COMMON_EXTERN void remove(const SequenceNumber& s);
+ QPID_COMMON_EXTERN void remove(const SequenceNumber& start, const SequenceNumber& finish); // Closed range
+ QPID_COMMON_EXTERN void remove(const SequenceSet& set);
+
+ template <class T> void for_each(T& t) const {
+ for (RangeIterator i = rangesBegin(); i != rangesEnd(); i++)
+ t(i->first(), i->last());
+ }
+
+ template <class T> void for_each(const T& t) const {
+ for (RangeIterator i = rangesBegin(); i != rangesEnd(); i++)
+ t(i->first(), i->last());
+ }
+
+ friend QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream&, const SequenceSet&);
+};
+
+}} // namespace qpid::framing
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/framing/StructHelper.h b/qpid/cpp/src/qpid/framing/StructHelper.h
new file mode 100644
index 0000000000..fe2fa64ce7
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/StructHelper.h
@@ -0,0 +1,57 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#ifndef _StructHelper_
+#define _StructHelper_
+
+#include "qpid/Exception.h"
+#include "qpid/CommonImportExport.h"
+#include "qpid/framing/Buffer.h"
+
+#include <stdlib.h> // For alloca
+
+namespace qpid {
+namespace framing {
+
+class QPID_COMMON_CLASS_EXTERN StructHelper
+{
+public:
+
+ template <class T> void encode(const T& t, std::string& data) {
+ uint32_t size = t.bodySize() + 2/*type*/;
+ data.resize(size);
+ Buffer wbuffer(const_cast<char*>(data.data()), size);
+ wbuffer.putShort(T::TYPE);
+ t.encodeStructBody(wbuffer);
+ }
+
+ template <class T> void decode(T& t, const std::string& data) {
+ Buffer rbuffer(const_cast<char*>(data.data()), data.length());
+ uint16_t type = rbuffer.getShort();
+ if (type == T::TYPE) {
+ t.decodeStructBody(rbuffer);
+ } else {
+ throw Exception("Type code does not match");
+ }
+ }
+};
+
+}}
+#endif
diff --git a/qpid/cpp/src/qpid/framing/TransferContent.cpp b/qpid/cpp/src/qpid/framing/TransferContent.cpp
new file mode 100644
index 0000000000..d997b24304
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/TransferContent.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/framing/TransferContent.h"
+
+namespace qpid {
+namespace framing {
+
+TransferContent::TransferContent(const std::string& data, const std::string& key) {
+ setData(data);
+ if (!key.empty()) getDeliveryProperties().setRoutingKey(key);
+}
+
+
+const AMQHeaderBody& TransferContent::getHeader() const
+{
+ return header;
+}
+
+const std::string& TransferContent::getData() const {
+ return data;
+}
+
+std::string& TransferContent::getData() {
+ return data;
+}
+
+void TransferContent::setData(const std::string& _data)
+{
+ data = _data;
+ header.get<MessageProperties>(true)->setContentLength(data.size());
+}
+
+void TransferContent::appendData(const std::string& _data)
+{
+ data += _data;
+ header.get<MessageProperties>(true)->setContentLength(data.size());
+}
+
+MessageProperties& TransferContent::getMessageProperties()
+{
+ return *header.get<MessageProperties>(true);
+}
+
+DeliveryProperties& TransferContent::getDeliveryProperties()
+{
+ return *header.get<DeliveryProperties>(true);
+}
+
+void TransferContent::populate(const FrameSet& frameset)
+{
+ const AMQHeaderBody* h = frameset.getHeaders();
+ if (h) {
+ header = *h;
+ }
+ frameset.getContent(data);
+}
+
+const MessageProperties& TransferContent::getMessageProperties() const
+{
+ const MessageProperties* props = header.get<MessageProperties>();
+ if (!props) throw Exception("No message properties.");
+ return *props;
+}
+
+const DeliveryProperties& TransferContent::getDeliveryProperties() const
+{
+ const DeliveryProperties* props = header.get<DeliveryProperties>();
+ if (!props) throw Exception("No message properties.");
+ return *props;
+}
+
+bool TransferContent::hasMessageProperties() const
+{
+ return header.get<MessageProperties>();
+}
+
+bool TransferContent::hasDeliveryProperties() const
+{
+ return header.get<DeliveryProperties>();
+}
+
+
+}}
diff --git a/qpid/cpp/src/qpid/framing/TransferContent.h b/qpid/cpp/src/qpid/framing/TransferContent.h
new file mode 100644
index 0000000000..32663d7020
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/TransferContent.h
@@ -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.
+ *
+ */
+#ifndef _TransferContent_
+#define _TransferContent_
+
+#include "qpid/framing/FrameSet.h"
+#include "qpid/framing/MethodContent.h"
+#include "qpid/Exception.h"
+#include "qpid/framing/MessageProperties.h"
+#include "qpid/framing/DeliveryProperties.h"
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+namespace framing {
+
+/** Message content */
+class QPID_COMMON_CLASS_EXTERN TransferContent : public MethodContent
+{
+ AMQHeaderBody header;
+ std::string data;
+public:
+ QPID_COMMON_EXTERN TransferContent(const std::string& data = std::string(), const std::string& key=std::string());
+
+ ///@internal
+ QPID_COMMON_EXTERN const AMQHeaderBody& getHeader() const;
+
+ QPID_COMMON_EXTERN void setData(const std::string&);
+ QPID_COMMON_EXTERN const std::string& getData() const;
+ QPID_COMMON_EXTERN std::string& getData();
+
+ QPID_COMMON_EXTERN void appendData(const std::string&);
+
+ QPID_COMMON_EXTERN bool hasMessageProperties() const;
+ QPID_COMMON_EXTERN MessageProperties& getMessageProperties();
+ QPID_COMMON_EXTERN const MessageProperties& getMessageProperties() const;
+
+ QPID_COMMON_EXTERN bool hasDeliveryProperties() const;
+ QPID_COMMON_EXTERN DeliveryProperties& getDeliveryProperties();
+ QPID_COMMON_EXTERN const DeliveryProperties& getDeliveryProperties() const;
+
+ ///@internal
+ QPID_COMMON_EXTERN void populate(const FrameSet& frameset);
+};
+
+}}
+#endif
diff --git a/qpid/cpp/src/qpid/framing/TypeFilter.h b/qpid/cpp/src/qpid/framing/TypeFilter.h
new file mode 100644
index 0000000000..d1c42de583
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/TypeFilter.h
@@ -0,0 +1,51 @@
+#ifndef QPID_FRAMING_TYPEFILTER_H
+#define QPID_FRAMING_TYPEFILTER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <string>
+#include "qpid/framing/amqp_framing.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/FrameHandler.h"
+
+namespace qpid {
+namespace framing {
+
+/**
+ * Predicate that selects frames by type
+ */
+template <uint8_t Type>
+struct TypeFilter {
+ bool operator()(const AMQFrame& f) const {
+ return f.getBody()->type() == Type;
+ }
+};
+
+template <uint8_t T1, uint8_t T2>
+struct TypeFilter2 {
+ bool operator()(const AMQFrame& f) const {
+ return f.getBody()->type() == T1 || f.getBody()->type() == T2;
+ }
+};
+
+}} // namespace qpid::framing
+
+#endif /*!QPID_FRAMING_TYPEFILTER_H*/
diff --git a/qpid/cpp/src/qpid/framing/Uuid.cpp b/qpid/cpp/src/qpid/framing/Uuid.cpp
new file mode 100644
index 0000000000..eb4c33be75
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/Uuid.cpp
@@ -0,0 +1,52 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/framing/Uuid.h"
+
+#include "qpid/sys/uuid.h"
+#include "qpid/Exception.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/Msg.h"
+
+namespace qpid {
+namespace framing {
+
+using namespace std;
+
+Uuid::Uuid(bool unique):
+ qpid::types::Uuid(unique)
+{}
+
+Uuid::Uuid(const uint8_t* data):
+ qpid::types::Uuid(data)
+{}
+
+void Uuid::encode(Buffer& buf) const {
+ buf.putRawData(data(), size());
+}
+
+void Uuid::decode(Buffer& buf) {
+ if (buf.available() < size())
+ throw IllegalArgumentException(QPID_MSG("Not enough data for UUID."));
+
+ // Break qpid::types::Uuid encapsulation - Nasty, but efficient
+ buf.getRawData(const_cast<uint8_t*>(data()), size());
+}
+
+}} // namespace qpid::framing
diff --git a/qpid/cpp/src/qpid/framing/Uuid.h b/qpid/cpp/src/qpid/framing/Uuid.h
new file mode 100644
index 0000000000..906e20951f
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/Uuid.h
@@ -0,0 +1,57 @@
+#ifndef QPID_FRAMING_UUID_H
+#define QPID_FRAMING_UUID_H
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/CommonImportExport.h"
+#include "qpid/sys/IntegerTypes.h"
+
+#include "qpid/types/Uuid.h"
+
+#include <ostream>
+#include <istream>
+
+namespace qpid {
+namespace framing {
+
+class Buffer;
+
+/**
+ * Framing UUID is now a thine wrapper around qpid::types::Uuid
+ */
+struct Uuid : public qpid::types::Uuid {
+ /** If unique is true, generate a unique ID else a null ID. */
+ QPID_COMMON_EXTERN Uuid(bool unique=false);
+
+ /** Copy from 16 bytes of data. */
+ QPID_COMMON_EXTERN Uuid(const uint8_t* data);
+
+ // We get most of our operations directly from qpid::types::Uuid
+ QPID_COMMON_INLINE_EXTERN static size_t size()
+ { return SIZE; }
+
+ QPID_COMMON_EXTERN void encode(framing::Buffer& buf) const;
+ QPID_COMMON_EXTERN void decode(framing::Buffer& buf);
+ QPID_COMMON_INLINE_EXTERN uint32_t encodedSize() const
+ { return size(); }
+};
+
+}} // namespace qpid::framing
+
+#endif /*!QPID_FRAMING_UUID_H*/
diff --git a/qpid/cpp/src/qpid/framing/amqp_framing.h b/qpid/cpp/src/qpid/framing/amqp_framing.h
new file mode 100644
index 0000000000..bad1c08a46
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/amqp_framing.h
@@ -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.
+ *
+ */
+#include "qpid/framing/amqp_types.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/AMQBody.h"
+#include "qpid/framing/AMQMethodBody.h"
+#include "qpid/framing/AMQHeaderBody.h"
+#include "qpid/framing/AMQContentBody.h"
+#include "qpid/framing/AMQHeartbeatBody.h"
+#include "qpid/framing/InputHandler.h"
+#include "qpid/framing/ProtocolInitiation.h"
+#include "qpid/framing/ProtocolVersion.h"
diff --git a/qpid/cpp/src/qpid/framing/amqp_types.h b/qpid/cpp/src/qpid/framing/amqp_types.h
new file mode 100644
index 0000000000..3fe8b68dcd
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/amqp_types.h
@@ -0,0 +1,63 @@
+#ifndef AMQP_TYPES_H
+#define AMQP_TYPES_H
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/** \file
+ * Definitions and forward declarations of all types used
+ * in AMQP messages.
+ */
+
+#include "qpid/sys/IntegerTypes.h"
+
+namespace qpid {
+namespace framing {
+
+typedef uint8_t FrameType;
+typedef uint16_t ChannelId;
+typedef uint32_t BatchOffset;
+typedef uint8_t ClassId;
+typedef uint8_t MethodId;
+typedef uint16_t ReplyCode;
+
+// Types represented by classes.
+class Content;
+class FieldTable;
+class SequenceNumberSet;
+struct Uuid;
+
+// Useful constants
+
+/** Maximum channel ID used by broker. */
+const ChannelId CHANNEL_MAX=(ChannelId(~1));
+
+// Forward declare class types
+class FramingContent;
+class FieldTable;
+class SequenceNumberSet;
+class SequenceSet;
+struct Uuid;
+
+// Enum types
+enum DeliveryMode { TRANSIENT = 1, PERSISTENT = 2};
+
+}} // namespace qpid::framing
+#endif
diff --git a/qpid/cpp/src/qpid/framing/amqp_types_full.h b/qpid/cpp/src/qpid/framing/amqp_types_full.h
new file mode 100644
index 0000000000..c5d84dedea
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/amqp_types_full.h
@@ -0,0 +1,38 @@
+#ifndef _framing_amqp_types_decl_h
+#define _framing_amqp_types_decl_h
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/** \file
+ * Definitions and full declarations of all types used
+ * in AMQP messages.
+ *
+ * It's better to include amqp_types.h in another header instead of this file
+ * unless the header actually needs the full declarations. Including
+ * full declarations when forward declarations would increase compile
+ * times.
+ */
+
+#include "qpid/framing/amqp_types.h"
+#include "qpid/framing/Array.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/SequenceSet.h"
+#include "qpid/framing/Uuid.h"
+
+#endif /*!_framing_amqp_types_decl_h*/
diff --git a/qpid/cpp/src/qpid/framing/frame_functors.h b/qpid/cpp/src/qpid/framing/frame_functors.h
new file mode 100644
index 0000000000..d2064d6a57
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/frame_functors.h
@@ -0,0 +1,116 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <string>
+#include <ostream>
+#include <iostream>
+#include "qpid/framing/amqp_framing.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/Buffer.h"
+
+#ifndef _frame_functors_
+#define _frame_functors_
+
+namespace qpid {
+namespace framing {
+
+class SumFrameSize
+{
+ uint64_t size;
+public:
+ SumFrameSize() : size(0) {}
+ void operator()(const AMQFrame& f) { size += f.encodedSize(); }
+ uint64_t getSize() { return size; }
+};
+
+class SumBodySize
+{
+ uint64_t size;
+public:
+ SumBodySize() : size(0) {}
+ void operator()(const AMQFrame& f) { size += f.getBody()->encodedSize(); }
+ uint64_t getSize() { return size; }
+};
+
+class Count
+{
+ uint count;
+public:
+ Count() : count(0) {}
+ void operator()(const AMQFrame&) { count++; }
+ uint getCount() { return count; }
+};
+
+class EncodeFrame
+{
+ Buffer& buffer;
+public:
+ EncodeFrame(Buffer& b) : buffer(b) {}
+ void operator()(const AMQFrame& f) { f.encode(buffer); }
+};
+
+class EncodeBody
+{
+ Buffer& buffer;
+public:
+ EncodeBody(Buffer& b) : buffer(b) {}
+ void operator()(const AMQFrame& f) { f.getBody()->encode(buffer); }
+};
+
+/**
+ * Sends to the specified handler a copy of the frame it is applied to.
+ */
+class Relay
+{
+ FrameHandler& handler;
+
+public:
+ Relay(FrameHandler& h) : handler(h) {}
+
+ void operator()(const AMQFrame& f)
+ {
+ AMQFrame copy(f);
+ handler.handle(copy);
+ }
+};
+
+class Print
+{
+ std::ostream& out;
+public:
+ Print(std::ostream& o) : out(o) {}
+
+ void operator()(const AMQFrame& f)
+ {
+ out << f << std::endl;
+ }
+};
+
+class MarkLastSegment
+{
+public:
+ void operator()(AMQFrame& f) const { f.setEof(true); }
+};
+
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/framing/variant.h b/qpid/cpp/src/qpid/framing/variant.h
new file mode 100644
index 0000000000..8e13063385
--- /dev/null
+++ b/qpid/cpp/src/qpid/framing/variant.h
@@ -0,0 +1,91 @@
+#ifndef QPID_FRAMING_VARIANT_H
+#define QPID_FRAMING_VARIANT_H
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**@file Tools for using boost::variant. */
+
+
+#include <boost/variant.hpp>
+
+namespace qpid {
+namespace framing {
+class Buffer;
+
+/** boost::static_visitor that throws an exception if variant contains a blank.
+ * Subclasses need to have a using() declaration, which can be generated
+ * with QPID_USING_NOBLANK(R)
+ */
+template <class R=void>
+struct NoBlankVisitor : public boost::static_visitor<R> {
+ R foundBlank() const {
+ assert(0);
+ throw Exception(QPID_MSG("Invalid variant value."));
+ }
+ R operator()(const boost::blank&) const { return foundBlank(); }
+ R operator()(boost::blank&) const { return foundBlank(); }
+};
+
+
+}} // qpid::framing
+
+
+/** Generate a using statement, needed in visitors inheriting NoBlankVisitor
+ * @param R return type.
+ */
+#define QPID_USING_NOBLANK(R) using ::qpid::framing::NoBlankVisitor<R>::operator()
+
+namespace qpid {
+namespace framing {
+
+/** Convert the variant value to type R. */
+template <class R> struct ConvertVisitor : public NoBlankVisitor<R> {
+ QPID_USING_NOBLANK(R);
+ template <class T> R operator()(T& t) const { return t; }
+};
+
+/** Convert the address of variant value to type R. */
+template <class R> struct AddressVisitor : public NoBlankVisitor<R> {
+ QPID_USING_NOBLANK(R);
+ template <class T> R operator()(T& t) const { return &t; }
+};
+
+/** Apply a visitor to the nested variant.*/
+template<class V>
+struct ApplyVisitor : public NoBlankVisitor<typename V::result_type> {
+ QPID_USING_NOBLANK(typename V::result_type);
+ const V& visitor;
+ ApplyVisitor(const V& v) : visitor(v) {}
+ template <class T> typename V::result_type operator()(T& t) const {
+ return boost::apply_visitor(visitor, t);
+ }
+};
+
+/** Convenience function to construct and apply an ApplyVisitor */
+template <class Visitor, class Visitable>
+typename Visitor::result_type applyApplyVisitor(const Visitor& visitor, Visitable& visitable) {
+ return boost::apply_visitor(ApplyVisitor<Visitor>(visitor), visitable);
+}
+
+}} // namespace qpid::framing
+
+
+#endif /*!QPID_FRAMING_VARIANT_H*/
diff --git a/qpid/cpp/src/qpid/ha/AlternateExchangeSetter.h b/qpid/cpp/src/qpid/ha/AlternateExchangeSetter.h
new file mode 100644
index 0000000000..fbf4755e7d
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/AlternateExchangeSetter.h
@@ -0,0 +1,74 @@
+#ifndef QPID_HA_ALTERNATEEXCHANGESETTER_H
+#define QPID_HA_ALTERNATEEXCHANGESETTER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/log/Statement.h"
+#include "qpid/broker/Exchange.h"
+#include "qpid/broker/ExchangeRegistry.h"
+#include <map>
+
+namespace qpid {
+namespace ha {
+
+/**
+ * Sets the alternate exchange on queues and exchanges.
+ * Holds onto queues/exchanges if necessary till the alternate exchange is available.
+ * THREAD UNSAFE
+ */
+class AlternateExchangeSetter
+{
+ public:
+ typedef boost::function<void(boost::shared_ptr<broker::Exchange>)> SetFunction;
+
+ AlternateExchangeSetter(broker::ExchangeRegistry& er) : exchanges(er) {}
+
+ /** If altEx is already known, call setter(altEx) now else save for later */
+ void setAlternate(const std::string& altEx, const SetFunction& setter) {
+ boost::shared_ptr<broker::Exchange> ex = exchanges.find(altEx);
+ if (ex) setter(ex); // Set immediately.
+ else setters.insert(Setters::value_type(altEx, setter)); // Save for later.
+ }
+
+ /** Add an exchange and call any setters that are waiting for it. */
+ void addExchange(boost::shared_ptr<broker::Exchange> exchange) {
+ // Update the setters for this exchange
+ std::pair<Setters::iterator, Setters::iterator> range = setters.equal_range(exchange->getName());
+ for (Setters::iterator i = range.first; i != range.second; ++i)
+ i->second(exchange);
+ setters.erase(range.first, range.second);
+ }
+
+ void clear() {
+ if (!setters.empty())
+ QPID_LOG(warning, "Some alternate exchanges were not resolved.");
+ setters.clear();
+ }
+
+ private:
+ typedef std::multimap<std::string, SetFunction> Setters;
+ broker::ExchangeRegistry& exchanges;
+ Setters setters;
+};
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_ALTERNATEEXCHANGESETTER_H*/
diff --git a/qpid/cpp/src/qpid/ha/Backup.cpp b/qpid/cpp/src/qpid/ha/Backup.cpp
new file mode 100644
index 0000000000..f1b6eadd75
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/Backup.cpp
@@ -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.
+ *
+ */
+#include "Backup.h"
+#include "BrokerReplicator.h"
+#include "ConnectionObserver.h"
+#include "HaBroker.h"
+#include "Primary.h"
+#include "ReplicatingSubscription.h"
+#include "Settings.h"
+#include "StatusCheck.h"
+#include "qpid/Url.h"
+#include "qpid/amqp_0_10/Codecs.h"
+#include "qpid/broker/Bridge.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/Link.h"
+#include "qpid/framing/AMQP_ServerProxy.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qpid/sys/SystemInfo.h"
+#include "qpid/types/Variant.h"
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace ha {
+
+using namespace framing;
+using namespace broker;
+using types::Variant;
+using std::string;
+using sys::Mutex;
+
+Backup::Backup(HaBroker& hb, const Settings& s) :
+ logPrefix(hb.logPrefix), membership(hb.getMembership()), stopped(false),
+ haBroker(hb), broker(hb.getBroker()), settings(s),
+ statusCheck(new StatusCheck(hb))
+{}
+
+void Backup::setBrokerUrl(const Url& brokers) {
+ if (brokers.empty()) return;
+ Mutex::ScopedLock l(lock);
+ if (stopped) return;
+ if (haBroker.getStatus() == JOINING) statusCheck->setUrl(brokers);
+ if (!link) { // Not yet initialized
+ QPID_LOG(info, logPrefix << "Connecting to cluster: " << brokers);
+ string protocol = brokers[0].protocol.empty() ? "tcp" : brokers[0].protocol;
+ types::Uuid uuid(true);
+ link = broker.getLinks().declare(
+ broker::QPID_NAME_PREFIX + string("ha.link.") + uuid.str(),
+ brokers[0].host, brokers[0].port, protocol,
+ false, // durable
+ settings.mechanism, settings.username, settings.password,
+ false).first; // no amq.failover - don't want to use client URL.
+ replicator = BrokerReplicator::create(haBroker, link);
+ broker.getExchanges().registerExchange(replicator);
+ }
+ link->setUrl(brokers);
+}
+
+void Backup::stop(Mutex::ScopedLock&) {
+ if (stopped) return;
+ stopped = true;
+ if (link) link->close();
+ if (replicator.get()) {
+ replicator->shutdown();
+ replicator.reset();
+ }
+}
+
+Role* Backup::recover(Mutex::ScopedLock&) {
+ BrokerInfo::Set backups;
+ {
+ Mutex::ScopedLock l(lock);
+ if (stopped) return 0;
+ stop(l); // Stop backup activity before starting primary.
+ // Reset membership before allowing backups to connect.
+ backups = membership.otherBackups();
+ membership.clear();
+ }
+ return new Primary(haBroker, backups);
+}
+
+Role* Backup::promote() {
+ Mutex::ScopedLock l(lock);
+ if (stopped) return 0;
+ switch (haBroker.getStatus()) {
+ case JOINING:
+ if (statusCheck->canPromote()) return recover(l);
+ else {
+ QPID_LOG(error, logPrefix << "Joining active cluster, cannot be promoted.");
+ throw Exception("Joining active cluster, cannot be promoted.");
+ }
+ break;
+ case CATCHUP:
+ QPID_LOG(error, logPrefix << "Still catching up, cannot be promoted.");
+ throw Exception("Still catching up, cannot be promoted.");
+ break;
+ case READY: return recover(l); break;
+ default:
+ assert(0); // Not a valid state for the Backup role..
+ }
+ return 0; // Keep compiler happy
+}
+
+Backup::~Backup() {
+ Mutex::ScopedLock l(lock);
+ stop(l);
+}
+
+}} // namespace qpid::ha
diff --git a/qpid/cpp/src/qpid/ha/Backup.h b/qpid/cpp/src/qpid/ha/Backup.h
new file mode 100644
index 0000000000..47c44aa59c
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/Backup.h
@@ -0,0 +1,82 @@
+#ifndef QPID_HA_BACKUP_H
+#define QPID_HA_BACKUP_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "LogPrefix.h"
+#include "Role.h"
+#include "Settings.h"
+#include "qpid/Url.h"
+#include "qpid/sys/Mutex.h"
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+
+namespace broker {
+class Broker;
+class Link;
+}
+
+namespace ha {
+class Settings;
+class BrokerReplicator;
+class HaBroker;
+class StatusCheck;
+class Membership;
+
+/**
+ * Backup role: Manages connections to primary, replicates management events and queue contents.
+ *
+ * THREAD SAFE
+ */
+class Backup : public Role
+{
+ public:
+ Backup(HaBroker&, const Settings&);
+ ~Backup();
+
+ void setBrokerUrl(const Url&);
+
+ Role* promote();
+
+ boost::shared_ptr<BrokerReplicator> getBrokerReplicator() { return replicator; }
+
+ private:
+ void stop(sys::Mutex::ScopedLock&);
+ Role* recover(sys::Mutex::ScopedLock&);
+
+ const LogPrefix& logPrefix;
+ Membership& membership;
+
+ sys::Mutex lock;
+ bool stopped;
+ HaBroker& haBroker;
+ broker::Broker& broker;
+ Settings settings;
+ boost::shared_ptr<broker::Link> link;
+ boost::shared_ptr<BrokerReplicator> replicator;
+ std::auto_ptr<StatusCheck> statusCheck;
+};
+
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_BACKUP_H*/
diff --git a/qpid/cpp/src/qpid/ha/BackupConnectionExcluder.h b/qpid/cpp/src/qpid/ha/BackupConnectionExcluder.h
new file mode 100644
index 0000000000..a58e666fa7
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/BackupConnectionExcluder.h
@@ -0,0 +1,54 @@
+#ifndef QPID_HA_BACKUPCONNECTIONEXCLUDER_H
+#define QPID_HA_BACKUPCONNECTIONEXCLUDER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "LogPrefix.h"
+#include "qpid/broker/ConnectionObserver.h"
+#include "qpid/broker/Connection.h"
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace ha {
+
+/**
+ * Exclude connections to a backup broker.
+ */
+class BackupConnectionExcluder : public broker::ConnectionObserver
+{
+ public:
+ BackupConnectionExcluder(const LogPrefix& lp) : logPrefix(lp) {}
+
+ void opened(broker::Connection& connection) {
+ QPID_LOG(trace, logPrefix << "Rejected connection "+connection.getMgmtId());
+ connection.abort();
+ }
+
+ void closed(broker::Connection&) {}
+
+ private:
+ const LogPrefix& logPrefix;
+};
+
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_BACKUPCONNECTIONEXCLUDER_H*/
diff --git a/qpid/cpp/src/qpid/ha/BrokerInfo.cpp b/qpid/cpp/src/qpid/ha/BrokerInfo.cpp
new file mode 100644
index 0000000000..c8a652a7ab
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/BrokerInfo.cpp
@@ -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.
+ *
+ */
+
+#include "BrokerInfo.h"
+#include "qpid/amqp_0_10/Codecs.h"
+#include "qpid/Exception.h"
+#include "qpid/log/Statement.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/FieldValue.h"
+#include <iostream>
+#include <iterator>
+#include <sstream>
+
+namespace qpid {
+namespace ha {
+
+namespace {
+const std::string SYSTEM_ID="system-id";
+const std::string PROTOCOL="protocol";
+const std::string HOST_NAME="host-name";
+const std::string PORT="port";
+const std::string STATUS="status";
+}
+
+using types::Uuid;
+using types::Variant;
+using framing::FieldTable;
+
+BrokerInfo::BrokerInfo() : status(JOINING) {}
+
+BrokerInfo::BrokerInfo(const types::Uuid& id, BrokerStatus s, const Address& a)
+ : address(a), systemId(id), status(s)
+{}
+
+FieldTable BrokerInfo::asFieldTable() const {
+ Variant::Map m = asMap();
+ FieldTable ft;
+ amqp_0_10::translate(m, ft);
+ return ft;
+}
+
+Variant::Map BrokerInfo::asMap() const {
+ Variant::Map m;
+ m[SYSTEM_ID] = systemId;
+ m[PROTOCOL] = address.protocol;
+ m[HOST_NAME] = address.host;
+ m[PORT] = address.port;
+ m[STATUS] = status;
+ return m;
+}
+
+void BrokerInfo::assign(const FieldTable& ft) {
+ Variant::Map m;
+ amqp_0_10::translate(ft, m);
+ assign(m);
+}
+
+namespace {
+const Variant& get(const Variant::Map& m, const std::string& k) {
+ Variant::Map::const_iterator i = m.find(k);
+ if (i == m.end()) throw Exception(
+ QPID_MSG("Missing field '" << k << "' in broker information"));
+ return i->second;
+}
+const Address empty;
+}
+
+void BrokerInfo::assign(const Variant::Map& m) {
+ systemId = get(m, SYSTEM_ID).asUuid();
+ address = Address(get(m, PROTOCOL).asString(),
+ get(m, HOST_NAME).asString(),
+ get(m, PORT).asUint16());
+ status = BrokerStatus(get(m, STATUS).asUint8());
+}
+
+std::ostream& BrokerInfo::printId(std::ostream& o) const {
+ o << shortStr(getSystemId());
+ if (getAddress() != empty) o << "@" << getAddress();
+ return o;
+}
+
+std::ostream& operator<<(std::ostream& o, const BrokerInfo& b) {
+ return b.printId(o) << "(" << printable(b.getStatus()) << ")";
+}
+
+std::ostream& operator<<(std::ostream& o, const BrokerInfo::Set& infos) {
+ std::ostream_iterator<BrokerInfo> out(o, " ");
+ copy(infos.begin(), infos.end(), out);
+ return o;
+}
+
+std::ostream& operator<<(std::ostream& o, const BrokerInfo::Map::value_type& v) {
+ return o << v.second;
+}
+
+std::ostream& operator<<(std::ostream& o, const BrokerInfo::Map& infos) {
+ std::ostream_iterator<BrokerInfo::Map::value_type> out(o, " ");
+ copy(infos.begin(), infos.end(), out);
+ return o;
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/ha/BrokerInfo.h b/qpid/cpp/src/qpid/ha/BrokerInfo.h
new file mode 100644
index 0000000000..92556a5c4b
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/BrokerInfo.h
@@ -0,0 +1,89 @@
+#ifndef QPID_HA_BROKERINFO_H
+#define QPID_HA_BROKERINFO_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "types.h"
+#include "hash.h"
+#include "qpid/Url.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/types/Uuid.h"
+#include "qpid/types/Variant.h"
+#include "qpid/sys/unordered_map.h"
+#include <string>
+#include <iosfwd>
+#include <vector>
+
+namespace qpid {
+namespace ha {
+
+/**
+ * Information about a cluster broker, maintained by the cluster primary.
+ */
+class BrokerInfo
+{
+ public:
+ typedef std::set<BrokerInfo> Set;
+ typedef qpid::sys::unordered_map<types::Uuid, BrokerInfo, Hasher<types::Uuid> > Map;
+
+ BrokerInfo();
+ BrokerInfo(const types::Uuid& id, BrokerStatus, const Address& = Address());
+ BrokerInfo(const framing::FieldTable& ft) { assign(ft); }
+ BrokerInfo(const types::Variant::Map& m) { assign(m); }
+
+ types::Uuid getSystemId() const { return systemId; }
+ BrokerStatus getStatus() const { return status; }
+ void setStatus(BrokerStatus s) { status = s; }
+ Address getAddress() const { return address; }
+ void setAddress(const Address& a) { address = a; }
+
+ framing::FieldTable asFieldTable() const;
+ types::Variant::Map asMap() const;
+
+ void assign(const framing::FieldTable&);
+ void assign(const types::Variant::Map&);
+
+ // So it can be put in a set.
+ bool operator<(const BrokerInfo& x) const { return systemId < x.systemId; }
+
+ bool operator==(const BrokerInfo& x) const
+ { return address == x.address && systemId == x.systemId && status == x.status; }
+
+ bool operator!=(const BrokerInfo& x) const { return !(*this == x); }
+
+ // Print just the identifying information (shortId@address), not the status.
+ std::ostream& printId(std::ostream& o) const;
+
+ private:
+ Address address;
+ types::Uuid systemId;
+ BrokerStatus status;
+};
+
+std::ostream& operator<<(std::ostream&, const BrokerInfo&);
+std::ostream& operator<<(std::ostream&, const BrokerInfo::Set&);
+std::ostream& operator<<(std::ostream&, const BrokerInfo::Map::value_type&);
+std::ostream& operator<<(std::ostream&, const BrokerInfo::Map&);
+
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_BROKERINFO_H*/
diff --git a/qpid/cpp/src/qpid/ha/BrokerReplicator.cpp b/qpid/cpp/src/qpid/ha/BrokerReplicator.cpp
new file mode 100644
index 0000000000..a62080932d
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/BrokerReplicator.cpp
@@ -0,0 +1,908 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "BrokerReplicator.h"
+#include "HaBroker.h"
+#include "QueueReplicator.h"
+#include "TxReplicator.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/amqp_0_10/Connection.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/QueueSettings.h"
+#include "qpid/broker/Link.h"
+#include "qpid/broker/amqp_0_10/MessageTransfer.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/log/Statement.h"
+#include "qpid/amqp_0_10/Codecs.h"
+#include "qpid/broker/SessionHandler.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qmf/org/apache/qpid/broker/EventBind.h"
+#include "qmf/org/apache/qpid/broker/EventUnbind.h"
+#include "qmf/org/apache/qpid/broker/EventExchangeDeclare.h"
+#include "qmf/org/apache/qpid/broker/EventExchangeDelete.h"
+#include "qmf/org/apache/qpid/broker/EventQueueDeclare.h"
+#include "qmf/org/apache/qpid/broker/EventQueueDelete.h"
+#include "qmf/org/apache/qpid/broker/EventSubscribe.h"
+#include "qmf/org/apache/qpid/ha/EventMembersUpdate.h"
+#include <boost/bind.hpp>
+#include <algorithm>
+#include <sstream>
+#include <iostream>
+#include <assert.h>
+
+namespace qpid {
+namespace ha {
+
+using qmf::org::apache::qpid::broker::EventBind;
+using qmf::org::apache::qpid::broker::EventUnbind;
+using qmf::org::apache::qpid::broker::EventExchangeDeclare;
+using qmf::org::apache::qpid::broker::EventExchangeDelete;
+using qmf::org::apache::qpid::broker::EventQueueDeclare;
+using qmf::org::apache::qpid::broker::EventQueueDelete;
+using qmf::org::apache::qpid::broker::EventSubscribe;
+using qmf::org::apache::qpid::ha::EventMembersUpdate;
+using qpid::broker::amqp_0_10::MessageTransfer;
+using namespace framing;
+using namespace std;
+using std::ostream;
+using types::Variant;
+using namespace broker;
+
+namespace {
+
+const string QPID_CONFIGURATION_REPLICATOR("qpid.broker-replicator");
+const string CLASS_NAME("_class_name");
+const string EVENT("_event");
+const string OBJECT_NAME("_object_name");
+const string PACKAGE_NAME("_package_name");
+const string QUERY_RESPONSE("_query_response");
+const string VALUES("_values");
+const string SCHEMA_ID("_schema_id");
+const string WHAT("_what");
+
+const string ALTEX("altEx");
+const string ALTEXCHANGE("altExchange");
+const string ARGS("args");
+const string ARGUMENTS("arguments");
+const string AUTODEL("autoDel");
+const string AUTODELETE("autoDelete");
+const string BIND("bind");
+const string BINDING("binding");
+const string BINDING_KEY("bindingKey");
+const string CREATED("created");
+const string DISP("disp");
+const string DEST("dest");
+const string DURABLE("durable");
+const string EXCHANGE("exchange");
+const string EXCL("excl");
+const string EXCLUSIVE("exclusive");
+const string EXNAME("exName");
+const string EXTYPE("exType");
+const string HA_BROKER("habroker");
+const string KEY("key");
+const string NAME("name");
+const string PARTIAL("partial");
+const string QNAME("qName");
+const string QUEUE("queue");
+const string TYPE("type");
+const string UNBIND("unbind");
+const string CONSUMER_COUNT("consumerCount");
+
+const string AGENT_EVENT_BROKER("agent.ind.event.org_apache_qpid_broker.#");
+const string AGENT_EVENT_HA("agent.ind.event.org_apache_qpid_ha.#");
+const string QMF2("qmf2");
+const string QMF_CONTENT("qmf.content");
+const string QMF_DEFAULT_TOPIC("qmf.default.topic");
+const string QMF_OPCODE("qmf.opcode");
+
+const string OBJECT("OBJECT");
+const string ORG_APACHE_QPID_BROKER("org.apache.qpid.broker");
+const string ORG_APACHE_QPID_HA("org.apache.qpid.ha");
+const string QMF_DEFAULT_DIRECT("qmf.default.direct");
+const string _QUERY_REQUEST("_query_request");
+const string BROKER("broker");
+const string MEMBERS("members");
+const string AUTO_DELETE_TIMEOUT("qpid.auto_delete_timeout");
+
+const string COLON(":");
+
+void sendQuery(const string& packageName, const string& className, const string& queueName,
+ SessionHandler& sessionHandler)
+{
+ Variant::Map request;
+ request[WHAT] = OBJECT;
+ Variant::Map schema;
+ schema[CLASS_NAME] = className;
+ schema[PACKAGE_NAME] = packageName;
+ request[SCHEMA_ID] = schema;
+
+ AMQFrame method((MessageTransferBody(ProtocolVersion(), QMF_DEFAULT_DIRECT, 0, 0)));
+ method.setBof(true);
+ method.setEof(false);
+ method.setBos(true);
+ method.setEos(true);
+ AMQHeaderBody headerBody;
+ MessageProperties* props = headerBody.get<MessageProperties>(true);
+ props->setReplyTo(qpid::framing::ReplyTo("", queueName));
+ props->setAppId(QMF2);
+ props->getApplicationHeaders().setString(QMF_OPCODE, _QUERY_REQUEST);
+ headerBody.get<qpid::framing::DeliveryProperties>(true)->setRoutingKey(BROKER);
+ headerBody.get<qpid::framing::MessageProperties>(true)->setCorrelationId(className);
+ AMQFrame header(headerBody);
+ header.setBof(false);
+ header.setEof(false);
+ header.setBos(true);
+ header.setEos(true);
+ AMQContentBody data;
+ qpid::amqp_0_10::MapCodec::encode(request, data.getData());
+ AMQFrame content(data);
+ content.setBof(false);
+ content.setEof(true);
+ content.setBos(true);
+ content.setEos(true);
+ sessionHandler.out->handle(method);
+ sessionHandler.out->handle(header);
+ sessionHandler.out->handle(content);
+}
+
+// Like Variant::asMap but treat void value as an empty map.
+Variant::Map asMapVoid(const Variant& value) {
+ if (!value.isVoid()) return value.asMap();
+ else return Variant::Map();
+}
+} // namespace
+
+// Report errors on the broker replication session.
+class BrokerReplicator::ErrorListener : public broker::SessionHandler::ErrorListener {
+ public:
+ ErrorListener(const LogPrefix& lp) : logPrefix(lp) {}
+
+ void connectionException(framing::connection::CloseCode code, const std::string& msg) {
+ QPID_LOG(error, logPrefix << framing::createConnectionException(code, msg).what());
+ }
+ void channelException(framing::session::DetachCode code, const std::string& msg) {
+ QPID_LOG(error, logPrefix << framing::createChannelException(code, msg).what());
+ }
+ void executionException(framing::execution::ErrorCode code, const std::string& msg) {
+ QPID_LOG(error, logPrefix << framing::createSessionException(code, msg).what());
+ }
+ void incomingExecutionException(framing::execution::ErrorCode code, const std::string& msg) {
+ QPID_LOG(error, logPrefix << "Incoming " << framing::createSessionException(code, msg).what());
+ }
+ void detach() {}
+
+ private:
+ const LogPrefix& logPrefix;
+};
+
+/** Keep track of queues or exchanges during the update process to solve 2
+ * problems.
+ *
+ * 1. Once all responses are processed, remove any queues/exchanges
+ * that were not mentioned as they no longer exist on the primary.
+ *
+ * 2. During the update if we see an event for an object we should
+ * ignore any subsequent responses for that object as they are out
+ * of date.
+ */
+class BrokerReplicator::UpdateTracker {
+ public:
+ typedef std::set<std::string> Names;
+ typedef boost::function<void (const std::string&)> CleanFn;
+
+ UpdateTracker(const std::string& type_, // "queue" or "exchange"
+ CleanFn f,
+ const LogPrefix& lp)
+ : type(type_), cleanFn(f), logPrefix(lp) {}
+
+ /** Destructor cleans up remaining initial queues. */
+ ~UpdateTracker() {
+ // Don't throw in a destructor.
+ try {
+ for_each(initial.begin(), initial.end(),
+ boost::bind(&UpdateTracker::clean, this, _1));
+ }
+ catch (const std::exception& e) {
+ QPID_LOG(error, logPrefix << "Error in cleanup of lost objects: " << e.what());
+ }
+ }
+
+ /** Add an exchange name */
+ void addExchange(Exchange::shared_ptr ex) { initial.insert(ex->getName()); }
+
+ /** Add a queue name. */
+ void addQueue(Queue::shared_ptr q) { initial.insert(q->getName()); }
+
+ /** Received an event for name */
+ void event(const std::string& name) {
+ initial.erase(name); // no longer a candidate for deleting
+ events.insert(name); // we have seen an event for this name
+ }
+
+ /** Received a response for name.
+ *@return true if this response should be processed, false if we have
+ *already seen an event for this object.
+ */
+ bool response(const std::string& name) {
+ initial.erase(name); // no longer a candidate for deleting
+ return events.find(name) == events.end(); // true if no event seen yet.
+ }
+
+ private:
+ void clean(const std::string& name) {
+ QPID_LOG(debug, logPrefix << "Deleted " << type << " " << name <<
+ ": no longer exists on primary");
+ try { cleanFn(name); }
+ catch (const framing::NotFoundException&) {}
+ }
+
+ std::string type;
+ Names initial, events;
+ CleanFn cleanFn;
+ const LogPrefix& logPrefix;
+};
+
+namespace {
+template <class EventType> std::string key() {
+ pair<string,string> name = EventType::getFullName();
+ return name.first + COLON + name.second;
+}
+}
+
+boost::shared_ptr<BrokerReplicator> BrokerReplicator::create(
+ HaBroker& hb, const boost::shared_ptr<broker::Link>& l)
+{
+ boost::shared_ptr<BrokerReplicator> br(new BrokerReplicator(hb, l));
+ br->initialize();
+ return br;
+}
+
+BrokerReplicator::BrokerReplicator(HaBroker& hb, const boost::shared_ptr<Link>& l)
+ : Exchange(QPID_CONFIGURATION_REPLICATOR),
+ logPrefix(hb.logPrefix), replicationTest(NONE),
+ haBroker(hb), broker(hb.getBroker()),
+ exchanges(broker.getExchanges()), queues(broker.getQueues()),
+ link(l),
+ initialized(false),
+ alternates(hb.getBroker().getExchanges()),
+ connect(0)
+{
+ framing::FieldTable args = getArgs();
+ args.setString(QPID_REPLICATE, printable(NONE).str());
+ setArgs(args);
+
+ dispatch[key<EventQueueDeclare>()] = &BrokerReplicator::doEventQueueDeclare;
+ dispatch[key<EventQueueDelete>()] = &BrokerReplicator::doEventQueueDelete;
+ dispatch[key<EventExchangeDeclare>()] = &BrokerReplicator::doEventExchangeDeclare;
+ dispatch[key<EventExchangeDelete>()] = &BrokerReplicator::doEventExchangeDelete;
+ dispatch[key<EventBind>()] = &BrokerReplicator::doEventBind;
+ dispatch[key<EventUnbind>()] = &BrokerReplicator::doEventUnbind;
+ dispatch[key<EventMembersUpdate>()] = &BrokerReplicator::doEventMembersUpdate;
+ dispatch[key<EventSubscribe>()] = &BrokerReplicator::doEventSubscribe;
+}
+
+void BrokerReplicator::initialize() {
+ // Can't do this in the constructor because we need a shared_ptr to this.
+ types::Uuid uuid(true);
+ const std::string name(QPID_CONFIGURATION_REPLICATOR + ".bridge." + uuid.str());
+ std::pair<Bridge::shared_ptr, bool> result = broker.getLinks().declare(
+ name, // name for bridge
+ *link, // parent
+ false, // durable
+ QPID_CONFIGURATION_REPLICATOR, // src
+ QPID_CONFIGURATION_REPLICATOR, // dest
+ "", // key
+ false, // isQueue
+ false, // isLocal
+ "", // id/tag
+ "", // excludes
+ false, // dynamic
+ 0, // sync?
+ LinkRegistry::INFINITE_CREDIT,
+ // shared_ptr keeps this in memory until outstanding connected
+ // calls are run.
+ boost::bind(&BrokerReplicator::connected, shared_from_this(), _1, _2)
+ );
+ assert(result.second);
+ result.first->setErrorListener(boost::shared_ptr<ErrorListener>(new ErrorListener(logPrefix)));
+ broker.getConnectionObservers().add(shared_from_this());
+}
+
+BrokerReplicator::~BrokerReplicator() {}
+
+namespace {
+struct QueueReplicators : public std::deque<boost::shared_ptr<QueueReplicator> > {
+ QueueReplicators(const ExchangeRegistry& er) { addAll(er); }
+
+ /** Add the exchange if it is a QueueReplicator. */
+ void add(const boost::shared_ptr<Exchange>& ex) {
+ boost::shared_ptr<QueueReplicator> qr =
+ boost::dynamic_pointer_cast<QueueReplicator>(ex);
+ if (qr) push_back(qr);
+ }
+ /** Add all QueueReplicator in the ExchangeRegistry. */
+ void addAll(const ExchangeRegistry& er) {
+ // Make copy of exchanges so we can work outside the registry lock.
+ er.eachExchange(boost::bind(&QueueReplicators::add, this, _1));
+ }
+};
+} // namespace
+
+void BrokerReplicator::shutdown() {
+ // NOTE: this is called in a QMF dispatch thread, not the Link's connection
+ // thread. It's OK to be unlocked because it doesn't use any mutable state,
+ // it only calls thread safe functions objects belonging to the Broker.
+
+ // Unregister with broker objects:
+ broker.getConnectionObservers().remove(shared_from_this());
+ broker.getExchanges().destroy(getName());
+}
+
+// This is called in the connection IO thread when the bridge is started.
+void BrokerReplicator::connected(Bridge& bridge, SessionHandler& sessionHandler) {
+ // Use the credentials of the outgoing Link connection for creating queues,
+ // exchanges etc. We know link->getConnection() is non-zero because we are
+ // being called in the connections thread context.
+ //
+ connect = link->getConnection();
+ assert(connect);
+ userId = link->getConnection()->getUserId();
+ remoteHost = link->getConnection()->getMgmtId();
+
+ link->getRemoteAddress(primary);
+ string queueName = bridge.getQueueName();
+
+ QPID_LOG(info, logPrefix << (initialized ? "Failing over" : "Connecting")
+ << " to primary " << primary);
+ initialized = true;
+
+ exchangeTracker.reset(
+ new UpdateTracker("exchange",
+ boost::bind(&BrokerReplicator::deleteExchange, this, _1),
+ logPrefix));
+ exchanges.eachExchange(boost::bind(&BrokerReplicator::existingExchange, this, _1));
+
+ queueTracker.reset(
+ new UpdateTracker("queue",
+ boost::bind(&BrokerReplicator::deleteQueue, this, _1, true),
+ logPrefix));
+ queues.eachQueue(boost::bind(&BrokerReplicator::existingQueue, this, _1));
+
+ framing::AMQP_ServerProxy peer(sessionHandler.out);
+ const qmf::org::apache::qpid::broker::ArgsLinkBridge& args(bridge.getArgs());
+
+ //declare and bind an event queue
+ FieldTable declareArgs;
+ declareArgs.setString(QPID_REPLICATE, printable(NONE).str());
+ peer.getQueue().declare(queueName, "", false, false, true, true, declareArgs);
+ peer.getExchange().bind(queueName, QMF_DEFAULT_TOPIC, AGENT_EVENT_BROKER, FieldTable());
+ peer.getExchange().bind(queueName, QMF_DEFAULT_TOPIC, AGENT_EVENT_HA, FieldTable());
+ //subscribe to the queue
+ FieldTable arguments;
+ arguments.setInt(QueueReplicator::QPID_SYNC_FREQUENCY, 1); // TODO aconway 2012-05-22: optimize?
+ peer.getMessage().subscribe(
+ queueName, args.i_dest, 1/*accept-none*/, 0/*pre-acquired*/,
+ false/*exclusive*/, "", 0, arguments);
+ peer.getMessage().setFlowMode(args.i_dest, 1); // Window
+ peer.getMessage().flow(args.i_dest, 0, haBroker.getSettings().getFlowMessages());
+ peer.getMessage().flow(args.i_dest, 1, haBroker.getSettings().getFlowBytes());
+
+ // Issue a query request for queues, exchanges, bindings and the habroker
+ // using event queue as the reply-to address
+ sendQuery(ORG_APACHE_QPID_HA, HA_BROKER, queueName, sessionHandler);
+ sendQuery(ORG_APACHE_QPID_BROKER, QUEUE, queueName, sessionHandler);
+ sendQuery(ORG_APACHE_QPID_BROKER, EXCHANGE, queueName, sessionHandler);
+ sendQuery(ORG_APACHE_QPID_BROKER, BINDING, queueName, sessionHandler);
+}
+
+// Called for each queue in existence when the backup connects to a primary.
+void BrokerReplicator::existingQueue(const boost::shared_ptr<Queue>& q) {
+ if (replicationTest.getLevel(*q)) {
+ QPID_LOG(debug, logPrefix << "Existing queue: " << q->getName());
+ queueTracker->addQueue(q);
+ }
+}
+
+void BrokerReplicator::existingExchange(const boost::shared_ptr<Exchange>& ex) {
+ if (replicationTest.getLevel(*ex)) {
+ QPID_LOG(debug, logPrefix << "Existing exchange: " << ex->getName());
+ exchangeTracker->addExchange(ex);
+ }
+}
+
+void BrokerReplicator::route(Deliverable& msg) {
+ // We transition from JOINING->CATCHUP on the first message received from the primary.
+ // Until now we couldn't be sure if we had a good connection to the primary.
+ if (haBroker.getStatus() == JOINING) {
+ haBroker.getMembership().setStatus(CATCHUP);
+ QPID_LOG(notice, logPrefix << "Connected to primary " << primary);
+ }
+ Variant::List list;
+ try {
+ if (!MessageTransfer::isQMFv2(msg.getMessage()))
+ throw Exception("Unexpected message, not QMF2 event or query response.");
+ // decode as list
+ string content = msg.getMessage().getContent();
+ qpid::amqp_0_10::ListCodec::decode(content, list);
+
+ if (msg.getMessage().getPropertyAsString(QMF_CONTENT) == EVENT) {
+ for (Variant::List::iterator i = list.begin(); i != list.end(); ++i) {
+ Variant::Map& map = i->asMap();
+ QPID_LOG(trace, logPrefix << "Broker replicator event: " << map);
+ Variant::Map& schema = map[SCHEMA_ID].asMap();
+ Variant::Map& values = map[VALUES].asMap();
+ std::string key = (schema[PACKAGE_NAME].asString() +
+ COLON +
+ schema[CLASS_NAME].asString());
+ EventDispatchMap::iterator j = dispatch.find(key);
+ if (j != dispatch.end()) (this->*(j->second))(values);
+ }
+ } else if (msg.getMessage().getPropertyAsString(QMF_OPCODE) == QUERY_RESPONSE) {
+ for (Variant::List::iterator i = list.begin(); i != list.end(); ++i) {
+ Variant::Map& map = i->asMap();
+ QPID_LOG(trace, logPrefix << "Broker replicator response: " << map);
+ string type = map[SCHEMA_ID].asMap()[CLASS_NAME].asString();
+ Variant::Map& values = map[VALUES].asMap();
+ framing::FieldTable args;
+ qpid::amqp_0_10::translate(asMapVoid(values[ARGUMENTS]), args);
+ if (type == QUEUE) doResponseQueue(values);
+ else if (type == EXCHANGE) doResponseExchange(values);
+ else if (type == BINDING) doResponseBind(values);
+ else if (type == HA_BROKER) doResponseHaBroker(values);
+ }
+ if (MessageTransfer::isLastQMFResponse(msg.getMessage(), EXCHANGE)) {
+ QPID_LOG(debug, logPrefix << "All exchange responses received.")
+ exchangeTracker.reset(); // Clean up exchanges that no longer exist in the primary
+ alternates.clear();
+ }
+ if (MessageTransfer::isLastQMFResponse(msg.getMessage(), QUEUE)) {
+ QPID_LOG(debug, logPrefix << "All queue responses received.");
+ queueTracker.reset(); // Clean up queues that no longer exist in the primary
+ }
+ }
+ } catch (const std::exception& e) {
+;
+ haBroker.shutdown(
+ QPID_MSG(logPrefix << "Configuration replication failed: "
+ << e.what() << ": while handling: " << list));
+ throw;
+ }
+}
+
+
+void BrokerReplicator::doEventQueueDeclare(Variant::Map& values) {
+ Variant::Map argsMap = asMapVoid(values[ARGS]);
+ if (values[DISP] == CREATED && replicationTest.getLevel(argsMap)) {
+ string name = values[QNAME].asString();
+ QueueSettings settings(values[DURABLE].asBool(), values[AUTODEL].asBool());
+ QPID_LOG(debug, logPrefix << "Queue declare event: " << name);
+ if (queueTracker.get()) queueTracker->event(name);
+ framing::FieldTable args;
+ qpid::amqp_0_10::translate(argsMap, args);
+ // If we already have a queue with this name, replace it.
+ // The queue was definitely created on the primary.
+ if (queues.find(name)) {
+ QPID_LOG(warning, logPrefix << "Declare event, replacing exsiting queue: "
+ << name);
+ deleteQueue(name);
+ }
+ replicateQueue(name, values[DURABLE].asBool(), values[AUTODEL].asBool(), args,
+ values[ALTEX].asString());
+ }
+}
+
+boost::shared_ptr<QueueReplicator> BrokerReplicator::findQueueReplicator(
+ const std::string& qname)
+{
+ string rname = QueueReplicator::replicatorName(qname);
+ boost::shared_ptr<broker::Exchange> ex = exchanges.find(rname);
+ return boost::dynamic_pointer_cast<QueueReplicator>(ex);
+}
+
+void BrokerReplicator::doEventQueueDelete(Variant::Map& values) {
+ // The remote queue has already been deleted so replicator
+ // sessions may be closed by a "queue deleted" exception.
+ string name = values[QNAME].asString();
+ boost::shared_ptr<Queue> queue = queues.find(name);
+ if (queue && replicationTest.getLevel(*queue)) {
+ QPID_LOG(debug, logPrefix << "Queue delete event: " << name);
+ if (queueTracker.get()) queueTracker->event(name);
+ deleteQueue(name);
+ }
+}
+
+void BrokerReplicator::doEventExchangeDeclare(Variant::Map& values) {
+ Variant::Map argsMap(asMapVoid(values[ARGS]));
+ if (values[DISP] == CREATED && replicationTest.getLevel(argsMap)) {
+ string name = values[EXNAME].asString();
+ QPID_LOG(debug, logPrefix << "Exchange declare event: " << name);
+ if (exchangeTracker.get()) exchangeTracker->event(name);
+ framing::FieldTable args;
+ qpid::amqp_0_10::translate(argsMap, args);
+ // If we already have a exchange with this name, replace it.
+ // The exchange was definitely created on the primary.
+ if (exchanges.find(name)) {
+ deleteExchange(name);
+ QPID_LOG(warning, logPrefix << "Declare event, replacing existing exchange: "
+ << name);
+ }
+ //Note: unlike qieth queues, autodeleted exchanges have no
+ //messages, so need no special handling for autodelete in ha
+ CreateExchangeResult result = createExchange(
+ name, values[EXTYPE].asString(), values[DURABLE].asBool(), values[AUTODEL].asBool(), args,
+ values[ALTEX].asString());
+ assert(result.second);
+ }
+}
+
+void BrokerReplicator::doEventExchangeDelete(Variant::Map& values) {
+ string name = values[EXNAME].asString();
+ boost::shared_ptr<Exchange> exchange = exchanges.find(name);
+ if (exchange && replicationTest.getLevel(*exchange)) {
+ QPID_LOG(debug, logPrefix << "Exchange delete event:" << name);
+ if (exchangeTracker.get()) exchangeTracker->event(name);
+ deleteExchange(name);
+ }
+}
+
+void BrokerReplicator::doEventBind(Variant::Map& values) {
+ boost::shared_ptr<Exchange> exchange =
+ exchanges.find(values[EXNAME].asString());
+ boost::shared_ptr<Queue> queue =
+ queues.find(values[QNAME].asString());
+ framing::FieldTable args;
+ qpid::amqp_0_10::translate(asMapVoid(values[ARGS]), args);
+ // We only replicate binds for a replicated queue to replicated exchange
+ // that both exist locally. Respect the replication level set in the
+ // bind arguments, but replicate by default.
+ if (exchange && replicationTest.getLevel(*exchange) &&
+ queue && replicationTest.getLevel(*queue) &&
+ ReplicationTest(ALL).getLevel(args))
+ {
+ string key = values[KEY].asString();
+ QPID_LOG(debug, logPrefix << "Bind event: exchange=" << exchange->getName()
+ << " queue=" << queue->getName()
+ << " key=" << key
+ << " args=" << args);
+ queue->bind(exchange, key, args);
+ }
+}
+
+void BrokerReplicator::doEventUnbind(Variant::Map& values) {
+ boost::shared_ptr<Exchange> exchange =
+ exchanges.find(values[EXNAME].asString());
+ boost::shared_ptr<Queue> queue =
+ queues.find(values[QNAME].asString());
+ // We only replicate unbinds for a replicated queue to replicated
+ // exchange that both exist locally.
+ if (exchange && replicationTest.getLevel(*exchange) &&
+ queue && replicationTest.getLevel(*queue))
+ {
+ string key = values[KEY].asString();
+ QPID_LOG(debug, logPrefix << "Unbind event: exchange=" << exchange->getName()
+ << " queue=" << queue->getName()
+ << " key=" << key);
+ exchange->unbind(queue, key, 0);
+ }
+}
+
+void BrokerReplicator::doEventMembersUpdate(Variant::Map& values) {
+ Variant::List members = values[MEMBERS].asList();
+ setMembership(members);
+}
+
+void BrokerReplicator::doEventSubscribe(Variant::Map& values) {
+ // Ignore queue replicator subscriptions.
+ if (QueueReplicator::isReplicatorName(values[DEST].asString())) return;
+ boost::shared_ptr<QueueReplicator> qr = findQueueReplicator(values[QNAME]);
+ if (qr) {
+ qr->setSubscribed();
+ QPID_LOG(debug, logPrefix << "Subscribe event: " << values[QNAME]);
+ }
+}
+
+namespace {
+
+// Get the alternate exchange from the exchange field of a queue or exchange response.
+static const string EXCHANGE_KEY_PREFIX("org.apache.qpid.broker:exchange:");
+
+string getAltExchange(const types::Variant& var) {
+ if (!var.isVoid()) {
+ management::ObjectId oid(var);
+ string key = oid.getV2Key();
+ if (key.find(EXCHANGE_KEY_PREFIX) != 0) throw Exception("Invalid exchange reference: "+key);
+ return key.substr(EXCHANGE_KEY_PREFIX.size());
+ }
+ else return string();
+}
+
+Variant getHaUuid(const Variant::Map& map) {
+ Variant::Map::const_iterator i = map.find(QPID_HA_UUID);
+ return i == map.end() ? Variant() : i->second;
+}
+
+} // namespace
+
+
+void BrokerReplicator::doResponseQueue(Variant::Map& values) {
+ Variant::Map argsMap(asMapVoid(values[ARGUMENTS]));
+ if (!replicationTest.getLevel(argsMap)) return;
+ string name(values[NAME].asString());
+ if (!queueTracker.get())
+ throw Exception(QPID_MSG("Unexpected queue response: " << values));
+ if (!queueTracker->response(name)) return; // Response is out-of-date
+
+ QPID_LOG(debug, logPrefix << "Queue response: " << name);
+ boost::shared_ptr<Queue> queue = queues.find(name);
+
+ if (queue) { // Already exists
+ bool uuidOk = (getHaUuid(queue->getSettings().original) == getHaUuid(argsMap));
+ if (!uuidOk) QPID_LOG(debug, logPrefix << "UUID mismatch for queue: " << name);
+ if (uuidOk && findQueueReplicator(name)) return; // already replicated, UUID OK.
+ QPID_LOG(debug, logPrefix << "Queue response replacing queue: " << name);
+ deleteQueue(name);
+ }
+
+ framing::FieldTable args;
+ qpid::amqp_0_10::translate(argsMap, args);
+ boost::shared_ptr<QueueReplicator> qr = replicateQueue(
+ name, values[DURABLE].asBool(), values[AUTODELETE].asBool(), args,
+ getAltExchange(values[ALTEXCHANGE]));
+ if (qr) {
+ Variant::Map::const_iterator i = values.find(CONSUMER_COUNT);
+ if (i != values.end() && isIntegerType(i->second.getType())) {
+ if (i->second.asInt64()) qr->setSubscribed();
+ }
+ }
+}
+
+void BrokerReplicator::doResponseExchange(Variant::Map& values) {
+ Variant::Map argsMap(asMapVoid(values[ARGUMENTS]));
+ if (!replicationTest.getLevel(argsMap)) return;
+ string name = values[NAME].asString();
+ if (!exchangeTracker.get())
+ throw Exception(QPID_MSG("Unexpected exchange response: " << values));
+ if (!exchangeTracker->response(name)) return; // Response is out of date.
+ QPID_LOG(debug, logPrefix << "Exchange response: " << name);
+ framing::FieldTable args;
+ qpid::amqp_0_10::translate(argsMap, args);
+ // If we see an exchange with the same name as one we have, but a different UUID,
+ // then replace the one we have.
+ boost::shared_ptr<Exchange> exchange = exchanges.find(name);
+ if (exchange &&
+ exchange->getArgs().getAsString(QPID_HA_UUID) != args.getAsString(QPID_HA_UUID))
+ {
+ QPID_LOG(warning, logPrefix << "Exchange response replacing (UUID mismatch): " << name);
+ deleteExchange(name);
+ }
+ CreateExchangeResult result = createExchange(
+ name, values[TYPE].asString(), values[DURABLE].asBool(), values[AUTODELETE].asBool(), args,
+ getAltExchange(values[ALTEXCHANGE]));
+}
+
+namespace {
+const std::string QUEUE_REF_PREFIX("org.apache.qpid.broker:queue:");
+const std::string EXCHANGE_REF_PREFIX("org.apache.qpid.broker:exchange:");
+
+std::string getRefName(const std::string& prefix, const Variant& ref) {
+ Variant::Map map(ref.asMap());
+ Variant::Map::const_iterator i = map.find(OBJECT_NAME);
+ if (i == map.end())
+ throw Exception(QPID_MSG("Replicator: invalid object reference: " << ref));
+ const std::string name = i->second.asString();
+ if (name.compare(0, prefix.size(), prefix) != 0)
+ throw Exception(QPID_MSG("Replicator: unexpected reference prefix: " << name));
+ std::string ret = name.substr(prefix.size());
+ return ret;
+}
+
+const std::string EXCHANGE_REF("exchangeRef");
+const std::string QUEUE_REF("queueRef");
+
+} // namespace
+
+void BrokerReplicator::doResponseBind(Variant::Map& values) {
+ std::string exName = getRefName(EXCHANGE_REF_PREFIX, values[EXCHANGE_REF]);
+ std::string qName = getRefName(QUEUE_REF_PREFIX, values[QUEUE_REF]);
+ boost::shared_ptr<Exchange> exchange = exchanges.find(exName);
+ boost::shared_ptr<Queue> queue = queues.find(qName);
+
+ framing::FieldTable args;
+ qpid::amqp_0_10::translate(asMapVoid(values[ARGUMENTS]), args);
+
+ // Automatically replicate binding if queue and exchange exist and are replicated.
+ // Respect replicate setting in binding args but default to replicated.
+ if (exchange && replicationTest.getLevel(*exchange) &&
+ queue && replicationTest.getLevel(*queue) &&
+ ReplicationTest(ALL).getLevel(args))
+ {
+ string key = values[BINDING_KEY].asString();
+ QPID_LOG(debug, logPrefix << "Bind response: exchange:" << exName
+ << " queue:" << qName
+ << " key:" << key
+ << " args:" << args);
+ queue->bind(exchange, key, args);
+ }
+}
+
+namespace {
+const string REPLICATE_DEFAULT="replicateDefault";
+}
+
+// Received the ha-broker configuration object for the primary broker.
+void BrokerReplicator::doResponseHaBroker(Variant::Map& values) {
+ try {
+ QPID_LOG(debug, logPrefix << "HA Broker response: " << values);
+ ReplicateLevel mine = haBroker.getSettings().replicateDefault.get();
+ ReplicateLevel primary = replicationTest.getLevel(values[REPLICATE_DEFAULT].asString());
+ if (mine != primary)
+ throw Exception(QPID_MSG("Replicate default on backup (" << mine
+ << ") does not match primary (" << primary << ")"));
+ setMembership(values[MEMBERS].asList());
+ } catch (const std::exception& e) {
+ haBroker.shutdown(
+ QPID_MSG(logPrefix << "Invalid HA Broker response: " << e.what()
+ << ": " << values));
+
+ throw;
+ }
+}
+
+boost::shared_ptr<QueueReplicator> BrokerReplicator::startQueueReplicator(
+ const boost::shared_ptr<Queue>& queue)
+{
+ if (replicationTest.getLevel(*queue) == ALL) {
+ if (TxReplicator::isTxQueue(queue->getName()))
+ return TxReplicator::create(haBroker, queue, link);
+ else
+ return QueueReplicator::create(haBroker, queue, link);
+ }
+ return boost::shared_ptr<QueueReplicator>();
+}
+
+void BrokerReplicator::deleteQueue(const std::string& name, bool purge) {
+ Queue::shared_ptr queue = queues.find(name);
+ if (queue) {
+ // Purge before deleting to ensure that we don't reroute any
+ // messages. Any reroutes will be done at the primary and
+ // replicated as normal.
+ if (purge) queue->purge(0, boost::shared_ptr<Exchange>());
+ haBroker.getBroker().deleteQueue(name, userId, remoteHost);
+ QPID_LOG(debug, logPrefix << "Queue deleted: " << name);
+ }
+}
+
+void BrokerReplicator::deleteExchange(const std::string& name) {
+ boost::shared_ptr<broker::Exchange> exchange = exchanges.find(name);
+ if (!exchange) {
+ QPID_LOG(warning, logPrefix << "Cannot delete exchange, not found: " << name);
+ return;
+ }
+ if (exchange->inUseAsAlternate()) {
+ QPID_LOG(warning, logPrefix << "Cannot delete exchange, in use as alternate: " << name);
+ return;
+ }
+ broker.deleteExchange(name, userId, remoteHost);
+ QPID_LOG(debug, logPrefix << "Exchange deleted: " << name);
+}
+
+boost::shared_ptr<QueueReplicator> BrokerReplicator::replicateQueue(
+ const std::string& name,
+ bool durable,
+ bool autodelete,
+ const qpid::framing::FieldTable& arguments,
+ const std::string& alternateExchange)
+{
+ QueueSettings settings(durable, autodelete);
+ settings.populate(arguments, settings.storeSettings);
+ CreateQueueResult result =
+ broker.createQueue(
+ name,
+ settings,
+ 0,// no owner regardless of exclusivity on primary
+ string(), // Set alternate exchange below
+ userId,
+ remoteHost);
+ boost::shared_ptr<QueueReplicator> qr;
+ if (!findQueueReplicator(name)) qr = startQueueReplicator(result.first);
+ if (result.second && !alternateExchange.empty()) {
+ alternates.setAlternate(
+ alternateExchange, boost::bind(&Queue::setAlternateExchange, result.first, _1));
+ }
+ return qr;
+}
+
+BrokerReplicator::CreateExchangeResult BrokerReplicator::createExchange(
+ const std::string& name,
+ const std::string& type,
+ bool durable,
+ bool autodelete,
+ const qpid::framing::FieldTable& args,
+ const std::string& alternateExchange)
+{
+ CreateExchangeResult result =
+ broker.createExchange(
+ name,
+ type,
+ durable,
+ autodelete,
+ string(), // Set alternate exchange below
+ args,
+ userId,
+ remoteHost);
+ alternates.addExchange(result.first);
+ if (!alternateExchange.empty()) {
+ alternates.setAlternate(
+ alternateExchange, boost::bind(&Exchange::setAlternate, result.first, _1));
+ }
+ return result;
+}
+
+bool BrokerReplicator::bind(boost::shared_ptr<Queue>, const string&, const framing::FieldTable*) { return false; }
+bool BrokerReplicator::unbind(boost::shared_ptr<Queue>, const string&, const framing::FieldTable*) { return false; }
+bool BrokerReplicator::isBound(boost::shared_ptr<Queue>, const string* const, const framing::FieldTable* const) { return false; }
+bool BrokerReplicator::hasBindings() { return false; }
+
+// ConnectionObserver methods
+void BrokerReplicator::connection(broker::Connection&) {}
+void BrokerReplicator::opened(broker::Connection&) {}
+
+void BrokerReplicator::closed(broker::Connection& c) {
+ if (link && &c == connect) disconnected();
+}
+
+void BrokerReplicator::forced(broker::Connection& c, const std::string& message) {
+ if (link && &c == link->getConnection()) {
+ haBroker.shutdown(
+ QPID_MSG(logPrefix << "Connection forced, cluster may be misconfigured: "
+ << message));
+ }
+ closed(c);
+}
+
+string BrokerReplicator::getType() const { return QPID_CONFIGURATION_REPLICATOR; }
+
+void BrokerReplicator::disconnectedQueueReplicator(
+ const boost::shared_ptr<QueueReplicator>& qr)
+{
+ qr->disconnect();
+ if (TxReplicator::isTxQueue(qr->getQueue()->getName())) {
+ // Transactions are aborted on failover so clean up tx-queues
+ deleteQueue(qr->getQueue()->getName());
+ }
+}
+
+// Called by ConnectionObserver::disconnected, disconnected from the network side.
+void BrokerReplicator::disconnected() {
+ QPID_LOG(info, logPrefix << "Disconnected from primary " << primary);
+ connect = 0;
+ QueueReplicators qrs(broker.getExchanges());
+ for_each(qrs.begin(), qrs.end(),
+ boost::bind(&BrokerReplicator::disconnectedQueueReplicator, this, _1));
+}
+
+void BrokerReplicator::setMembership(const Variant::List& brokers) {
+ haBroker.getMembership().assign(brokers);
+}
+
+}} // namespace broker
diff --git a/qpid/cpp/src/qpid/ha/BrokerReplicator.h b/qpid/cpp/src/qpid/ha/BrokerReplicator.h
new file mode 100644
index 0000000000..44e80263de
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/BrokerReplicator.h
@@ -0,0 +1,177 @@
+#ifndef QPID_HA_REPLICATOR_H
+#define QPID_HA_REPLICATOR_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "types.h"
+#include "ReplicationTest.h"
+#include "AlternateExchangeSetter.h"
+#include "qpid/Address.h"
+#include "qpid/broker/Exchange.h"
+#include "qpid/broker/ConnectionObserver.h"
+#include "qpid/types/Variant.h"
+#include "qpid/management/ManagementObject.h"
+#include "qpid/sys/unordered_map.h"
+#include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+#include <set>
+
+namespace qpid {
+
+namespace broker {
+class Broker;
+class Link;
+class Bridge;
+class SessionHandler;
+class Connection;
+class QueueRegistry;
+class ExchangeRegistry;
+}
+
+namespace framing {
+class FieldTable;
+}
+
+namespace ha {
+class LogPrefix;
+class HaBroker;
+class QueueReplicator;
+
+/**
+ * Replicate configuration on a backup broker.
+ *
+ * Implemented as an exchange that subscribes to receive QMF
+ * configuration events from the primary. It configures local queues
+ * exchanges and bindings to replicate the primary.
+ * It also creates QueueReplicators for newly replicated queues.
+ *
+ * THREAD UNSAFE:
+ * All members except shutdown are only called in the Link's connection thread context.
+ * shutdown() does not use any mutable state.
+ *
+ */
+class BrokerReplicator : public broker::Exchange,
+ public boost::enable_shared_from_this<BrokerReplicator>,
+ public broker::ConnectionObserver
+{
+ public:
+ typedef boost::shared_ptr<QueueReplicator> QueueReplicatorPtr;
+
+ static boost::shared_ptr<BrokerReplicator> create(
+ HaBroker&, const boost::shared_ptr<broker::Link>&);
+
+ ~BrokerReplicator();
+
+ void shutdown();
+
+ // Exchange methods
+ std::string getType() const;
+ bool bind(boost::shared_ptr<broker::Queue>, const std::string&, const framing::FieldTable*);
+ bool unbind(boost::shared_ptr<broker::Queue>, const std::string&, const framing::FieldTable*);
+ void route(broker::Deliverable&);
+ bool isBound(boost::shared_ptr<broker::Queue>, const std::string* const, const framing::FieldTable* const);
+ bool hasBindings();
+
+ // ConnectionObserver methods
+ void connection(broker::Connection&);
+ void opened(broker::Connection&);
+ void closed(broker::Connection&);
+ void forced(broker::Connection&, const std::string& /*message*/);
+
+ QueueReplicatorPtr findQueueReplicator(const std::string& qname);
+
+ private:
+ BrokerReplicator(HaBroker&, const boost::shared_ptr<broker::Link>&);
+ void initialize(); // Called in create()
+
+ typedef std::pair<boost::shared_ptr<broker::Queue>, bool> CreateQueueResult;
+ typedef std::pair<boost::shared_ptr<broker::Exchange>, bool> CreateExchangeResult;
+
+ typedef void (BrokerReplicator::*DispatchFunction)(types::Variant::Map&);
+ typedef qpid::sys::unordered_map<std::string, DispatchFunction> EventDispatchMap;
+
+ class UpdateTracker;
+ class ErrorListener;
+
+ void connected(broker::Bridge&, broker::SessionHandler&);
+ void existingQueue(const boost::shared_ptr<broker::Queue>&);
+ void existingExchange(const boost::shared_ptr<broker::Exchange>&);
+
+ void doEventQueueDeclare(types::Variant::Map& values);
+ void doEventQueueDelete(types::Variant::Map& values);
+ void doEventExchangeDeclare(types::Variant::Map& values);
+ void doEventExchangeDelete(types::Variant::Map& values);
+ void doEventBind(types::Variant::Map&);
+ void doEventUnbind(types::Variant::Map&);
+ void doEventMembersUpdate(types::Variant::Map&);
+ void doEventSubscribe(types::Variant::Map&);
+
+ void doResponseQueue(types::Variant::Map& values);
+ void doResponseExchange(types::Variant::Map& values);
+ void doResponseBind(types::Variant::Map& values);
+ void doResponseHaBroker(types::Variant::Map& values);
+
+ QueueReplicatorPtr startQueueReplicator(const boost::shared_ptr<broker::Queue>&);
+
+ QueueReplicatorPtr replicateQueue(
+ const std::string& name,
+ bool durable,
+ bool autodelete,
+ const qpid::framing::FieldTable& arguments,
+ const std::string& alternateExchange);
+
+ CreateExchangeResult createExchange(
+ const std::string& name,
+ const std::string& type,
+ bool durable,
+ bool autodelete,
+ const qpid::framing::FieldTable& args,
+ const std::string& alternateExchange);
+
+ bool deactivate(boost::shared_ptr<broker::Exchange> ex, bool destroy);
+ void deleteQueue(const std::string& name, bool purge=true);
+ void deleteExchange(const std::string& name);
+
+ void disconnectedQueueReplicator(const boost::shared_ptr<QueueReplicator>&);
+ void disconnected();
+
+ void setMembership(const types::Variant::List&); // Set membership from list.
+
+ const LogPrefix& logPrefix;
+ ReplicationTest replicationTest;
+ std::string userId, remoteHost;
+ HaBroker& haBroker;
+ broker::Broker& broker;
+ broker::ExchangeRegistry& exchanges;
+ broker::QueueRegistry& queues;
+ boost::shared_ptr<broker::Link> link;
+ bool initialized;
+ AlternateExchangeSetter alternates;
+ qpid::Address primary;
+ broker::Connection* connect;
+ EventDispatchMap dispatch;
+ std::auto_ptr<UpdateTracker> queueTracker;
+ std::auto_ptr<UpdateTracker> exchangeTracker;
+};
+}} // namespace qpid::broker
+
+#endif /*!QPID_HA_REPLICATOR_H*/
diff --git a/qpid/cpp/src/qpid/ha/ConnectionObserver.cpp b/qpid/cpp/src/qpid/ha/ConnectionObserver.cpp
new file mode 100644
index 0000000000..a824adb871
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/ConnectionObserver.cpp
@@ -0,0 +1,121 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "ConnectionObserver.h"
+#include "BrokerInfo.h"
+#include "HaBroker.h"
+#include "qpid/Url.h"
+#include "qpid/types/Variant.h"
+#include "qpid/broker/Connection.h"
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace ha {
+
+ConnectionObserver::ConnectionObserver(HaBroker& hb, const types::Uuid& uuid)
+ : haBroker(hb), logPrefix(hb.logPrefix), self(uuid) {}
+
+bool ConnectionObserver::getBrokerInfo(const broker::Connection& connection, BrokerInfo& info) {
+ qpid::types::Variant::Map::const_iterator i = connection.getClientProperties().find(ConnectionObserver::BACKUP_TAG);
+ if (i != connection.getClientProperties().end() && i->second.getType() == qpid::types::VAR_MAP) {
+ info = BrokerInfo(i->second.asMap());
+ return true;
+ }
+ return false;
+}
+
+bool ConnectionObserver::getAddress(const broker::Connection& connection, Address& addr) {
+ qpid::types::Variant::Map::const_iterator i = connection.getClientProperties().find(ConnectionObserver::ADDRESS_TAG);
+ if (i != connection.getClientProperties().end()) {
+ Url url;
+ url.parseNoThrow(i->second.asString().c_str());
+ if (!url.empty()) {
+ addr = url[0];
+ return true;
+ }
+ }
+ return false;
+}
+
+void ConnectionObserver::setObserver(const ObserverPtr& o)
+{
+ sys::Mutex::ScopedLock l(lock);
+ observer = o;
+}
+
+ConnectionObserver::ObserverPtr ConnectionObserver::getObserver() {
+ sys::Mutex::ScopedLock l(lock);
+ return observer;
+}
+
+void ConnectionObserver::reset() {
+ sys::Mutex::ScopedLock l(lock);
+ observer.reset();
+}
+
+bool ConnectionObserver::isSelf(const broker::Connection& connection) {
+ BrokerInfo info;
+ return getBrokerInfo(connection, info) && info.getSystemId() == self;
+}
+
+void ConnectionObserver::opened(broker::Connection& connection) {
+ try {
+ if (isSelf(connection)) { // Reject self connections
+ // Set my own address if there is an address header.
+ Address addr;
+ if (getAddress(connection, addr)) haBroker.setAddress(addr);
+ QPID_LOG(trace, logPrefix << "Rejected self connection "+connection.getMgmtId());
+ connection.abort();
+ return;
+ }
+ if (connection.isLink()) return; // Allow outgoing links.
+ if (connection.getClientProperties().find(ADMIN_TAG) != connection.getClientProperties().end()) {
+ QPID_LOG(trace, logPrefix << "Accepted admin connection: " << connection.getMgmtId());
+ return; // No need to call observer, always allow admins.
+ }
+ ObserverPtr o(getObserver());
+ if (o) o->opened(connection);
+ }
+ catch (const std::exception& e) {
+ QPID_LOG(error, logPrefix << "Error on incoming connection " << connection.getMgmtId()
+ << ": " << e.what());
+ throw;
+ }
+}
+
+void ConnectionObserver::closed(broker::Connection& connection) {
+ if (isSelf(connection)) return; // Ignore closing of self connections.
+ try {
+ ObserverPtr o(getObserver());
+ if (o) o->closed(connection);
+ }
+ catch (const std::exception& e) {
+ QPID_LOG(error, logPrefix << "Error closing incoming connection " << connection.getMgmtId()
+ << ": " << e.what());
+ throw;
+ }
+}
+
+const std::string ConnectionObserver::ADMIN_TAG="qpid.ha-admin";
+const std::string ConnectionObserver::BACKUP_TAG="qpid.ha-backup";
+const std::string ConnectionObserver::ADDRESS_TAG="qpid.ha-address";
+
+}} // namespace qpid::ha
diff --git a/qpid/cpp/src/qpid/ha/ConnectionObserver.h b/qpid/cpp/src/qpid/ha/ConnectionObserver.h
new file mode 100644
index 0000000000..f447d479f0
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/ConnectionObserver.h
@@ -0,0 +1,83 @@
+#ifndef QPID_HA_CONNECTIONOBSERVER_H
+#define QPID_HA_CONNECTIONOBSERVER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "types.h"
+#include "qpid/broker/ConnectionObserver.h"
+#include "qpid/types/Uuid.h"
+#include "qpid/sys/Mutex.h"
+#include "boost/shared_ptr.hpp"
+
+namespace qpid {
+struct Address;
+
+namespace ha {
+class BrokerInfo;
+class HaBroker;
+class LogPrefix;
+
+/**
+ * Observes connections, delegates to another ConnectionObserver for
+ * actions specific to primary or backup.
+ *
+ * THREAD SAFE: called in arbitrary connection threads.
+ *
+ * Main role of this class is to provide a continuous observer object
+ * on the connection so we can't lose observations between removing
+ * one observer and adding another.
+ */
+class ConnectionObserver : public broker::ConnectionObserver
+{
+ public:
+ typedef boost::shared_ptr<broker::ConnectionObserver> ObserverPtr;
+
+ static const std::string ADMIN_TAG;
+ static const std::string BACKUP_TAG;
+ static const std::string ADDRESS_TAG;
+
+ static bool getBrokerInfo(const broker::Connection& connection, BrokerInfo&);
+ static bool getAddress(const broker::Connection& connection, Address&);
+
+ ConnectionObserver(HaBroker& haBroker, const types::Uuid& self);
+
+ void setObserver(const ObserverPtr&);
+ ObserverPtr getObserver();
+
+ void reset();
+
+ void opened(broker::Connection& connection);
+ void closed(broker::Connection& connection);
+
+ private:
+ bool isSelf(const broker::Connection&);
+
+ sys::Mutex lock;
+ HaBroker& haBroker;
+ const LogPrefix& logPrefix;
+ ObserverPtr observer;
+ types::Uuid self;
+};
+
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_CONNECTIONOBSERVER_H*/
diff --git a/qpid/cpp/src/qpid/ha/Event.cpp b/qpid/cpp/src/qpid/ha/Event.cpp
new file mode 100644
index 0000000000..ff336d0b2b
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/Event.cpp
@@ -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.
+ *
+ */
+#include "Event.h"
+#include "qpid/broker/amqp_0_10/MessageTransfer.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/DeliveryProperties.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace ha {
+
+using namespace std;
+using namespace framing;
+using namespace broker::amqp_0_10;
+
+namespace {
+const string QPID_HA(QPID_HA_PREFIX);
+}
+
+bool isEventKey(const std::string& key) {
+ const std::string& prefix = QPID_HA;
+ bool ret = key.size() > prefix.size() && key.compare(0, prefix.size(), prefix) == 0;
+ return ret;
+}
+
+const string DequeueEvent::KEY(QPID_HA+"de");
+const string IdEvent::KEY(QPID_HA+"id");
+const string TxEnqueueEvent::KEY(QPID_HA+"txenq");
+const string TxDequeueEvent::KEY(QPID_HA+"txdeq");
+const string TxPrepareEvent::KEY(QPID_HA+"txpre");
+const string TxCommitEvent::KEY(QPID_HA+"txcom");
+const string TxRollbackEvent::KEY(QPID_HA+"txrb");
+const string TxPrepareOkEvent::KEY(QPID_HA+"txok");
+const string TxPrepareFailEvent::KEY(QPID_HA+"txno");
+const string TxBackupsEvent::KEY(QPID_HA+"txmem");
+
+broker::Message makeMessage(
+ const string& data, const string& destination, const string& routingKey)
+{
+ boost::intrusive_ptr<MessageTransfer> transfer(new MessageTransfer());
+ AMQFrame method((MessageTransferBody(ProtocolVersion(), destination, 0, 0)));
+ method.setBof(true);
+ method.setEof(false);
+ method.setBos(true);
+ method.setEos(true);
+ AMQFrame header((AMQHeaderBody()));
+ header.setBof(false);
+ header.setEof(false);
+ header.setBos(true);
+ header.setEos(true);
+ AMQFrame content((AMQContentBody()));
+ content.setBof(false);
+ content.setEof(true);
+ content.setBos(true);
+ content.setEos(true);
+ Buffer buffer(const_cast<char*>(&data[0]), data.size());
+ content.castBody<AMQContentBody>()->decode(
+ const_cast<Buffer&>(buffer), buffer.getSize());
+ transfer->getFrames().append(method);
+ transfer->getFrames().append(header);
+ transfer->getFrames().append(content);
+ transfer->getFrames().getHeaders()->
+ get<DeliveryProperties>(true)->setRoutingKey(routingKey);
+ return broker::Message(transfer, 0);
+}
+
+}} // namespace qpid::ha
diff --git a/qpid/cpp/src/qpid/ha/Event.h b/qpid/cpp/src/qpid/ha/Event.h
new file mode 100644
index 0000000000..7b96e36f64
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/Event.h
@@ -0,0 +1,193 @@
+#ifndef QPID_HA_EVENT_H
+#define QPID_HA_EVENT_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "types.h"
+#include "qpid/broker/Message.h"
+#include "qpid/framing/BufferTypes.h"
+
+/**@file Defines event messages used to pass transaction information from
+ * primary observers to backup replicators.
+ */
+
+namespace qpid {
+namespace ha {
+
+broker::Message makeMessage(
+ const std::string& content,
+ const std::string& destination,
+ const std::string& routingKey);
+
+
+/** Test if a string is an event key */
+bool isEventKey(const std::string& key);
+
+/** Base class for encodable events */
+class Event {
+ public:
+ virtual ~Event() {}
+ virtual void encode(framing::Buffer& buffer) const = 0;
+ virtual void decode(framing::Buffer& buffer) = 0;
+ virtual size_t encodedSize() const = 0;
+ virtual std::string key() const = 0; // Routing key
+ virtual void print(std::ostream& o) const = 0;
+ broker::Message message(const std::string& destination=std::string()) const {
+ return makeMessage(framing::encodeStr(*this), destination, key()); }
+};
+
+
+inline std::ostream& operator<<(std::ostream& o, const Event& e) {
+ o << "<" << e.key() << ":";
+ e.print(o);
+ return o << ">";
+}
+
+/** Event base template */
+template <class Derived> class EventBase : public Event {
+ public:
+ std::string key() const { return Derived::KEY; }
+};
+
+//////////////// Specific event type
+
+//// QueueReplicator events
+
+struct DequeueEvent : public EventBase<DequeueEvent> {
+ static const std::string KEY;
+ ReplicationIdSet ids;
+
+ DequeueEvent(ReplicationIdSet ids_=ReplicationIdSet()) : ids(ids_) {}
+ void encode(framing::Buffer& b) const { b.put(ids); }
+ void decode(framing::Buffer& b) { b.get(ids); }
+ virtual size_t encodedSize() const { return ids.encodedSize(); }
+ void print(std::ostream& o) const { o << ids; }
+};
+
+struct IdEvent : public EventBase<IdEvent> {
+ static const std::string KEY;
+ ReplicationId id;
+
+ IdEvent(ReplicationId id_=0) : id(id_) {}
+ void encode(framing::Buffer& b) const { b.put(id); }
+ void decode(framing::Buffer& b) { b.get(id); }
+ virtual size_t encodedSize() const { return id.encodedSize(); }
+ void print(std::ostream& o) const { o << id; }
+};
+
+//// Transaction events
+
+struct TxEnqueueEvent : public EventBase<TxEnqueueEvent> {
+ static const std::string KEY;
+ framing::LongString queue;
+ ReplicationId id;
+
+ TxEnqueueEvent(std::string q=std::string(), ReplicationId i=ReplicationId())
+ : queue(q), id(i) {}
+ void encode(framing::Buffer& b) const { b.put(queue); b.put(id); }
+ void decode(framing::Buffer& b) { b.get(queue); b.get(id); }
+ virtual size_t encodedSize() const { return queue.encodedSize()+id.encodedSize(); }
+ void print(std::ostream& o) const { o << queue.value << " " << id; }
+};
+
+struct TxDequeueEvent : public EventBase<TxDequeueEvent> {
+ static const std::string KEY;
+ framing::LongString queue;
+ ReplicationId id;
+
+ TxDequeueEvent(std::string q=std::string(), ReplicationId r=0) :
+ queue(q), id(r) {}
+ void encode(framing::Buffer& b) const { b.put(queue);b.put(id); }
+ void decode(framing::Buffer& b) { b.get(queue);b.get(id); }
+ virtual size_t encodedSize() const { return queue.encodedSize()+id.encodedSize(); }
+ void print(std::ostream& o) const { o << queue.value << " " << id; }
+};
+
+struct TxPrepareEvent : public EventBase<TxPrepareEvent> {
+ static const std::string KEY;
+ void encode(framing::Buffer&) const {}
+ void decode(framing::Buffer&) {}
+ virtual size_t encodedSize() const { return 0; }
+ void print(std::ostream&) const {}
+};
+
+struct TxCommitEvent : public EventBase<TxCommitEvent> {
+ static const std::string KEY;
+ void encode(framing::Buffer&) const {}
+ void decode(framing::Buffer&) {}
+ virtual size_t encodedSize() const { return 0; }
+ void print(std::ostream&) const {}
+};
+
+struct TxRollbackEvent : public EventBase<TxRollbackEvent> {
+ static const std::string KEY;
+ void encode(framing::Buffer&) const {}
+ void decode(framing::Buffer&) {}
+ virtual size_t encodedSize() const { return 0; }
+ void print(std::ostream&) const {}
+};
+
+struct TxPrepareOkEvent : public EventBase<TxPrepareOkEvent> {
+ static const std::string KEY;
+ types::Uuid broker;
+ TxPrepareOkEvent(const types::Uuid& b=types::Uuid()) : broker(b) {}
+
+ void encode(framing::Buffer& b) const {
+ b.putRawData(broker.data(), broker.size());
+ }
+
+ void decode(framing::Buffer& b) {
+ std::string s;
+ b.getRawData(s, broker.size());
+ broker = types::Uuid(&s[0]);
+ }
+ virtual size_t encodedSize() const { return broker.size(); }
+ void print(std::ostream& o) const { o << broker; }
+};
+
+struct TxPrepareFailEvent : public EventBase<TxPrepareFailEvent> {
+ static const std::string KEY;
+ types::Uuid broker;
+ TxPrepareFailEvent(const types::Uuid& b=types::Uuid()) : broker(b) {}
+ void encode(framing::Buffer& b) const { b.putRawData(broker.data(), broker.size()); }
+ void decode(framing::Buffer& b) {
+ std::string s;
+ b.getRawData(s, broker.size());
+ broker = types::Uuid(&s[0]);
+ }
+ virtual size_t encodedSize() const { return broker.size(); }
+ void print(std::ostream& o) const { o << broker; }
+};
+
+struct TxBackupsEvent : public EventBase<TxBackupsEvent> {
+ static const std::string KEY;
+ UuidSet backups;
+ TxBackupsEvent(const UuidSet& s=UuidSet()) : backups(s) {}
+ void encode(framing::Buffer& b) const { b.put(backups); }
+ void decode(framing::Buffer& b) { b.get(backups); }
+ size_t encodedSize() const { return backups.encodedSize(); }
+ void print(std::ostream& o) const { o << backups; }
+};
+
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_EVENT_H*/
diff --git a/qpid/cpp/src/qpid/ha/FailoverExchange.cpp b/qpid/cpp/src/qpid/ha/FailoverExchange.cpp
new file mode 100644
index 0000000000..9bda5ea5bf
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/FailoverExchange.cpp
@@ -0,0 +1,133 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "FailoverExchange.h"
+#include "Event.h"
+#include "qpid/broker/amqp_0_10/MessageTransfer.h"
+#include "qpid/broker/Message.h"
+#include "qpid/broker/DeliverableMessage.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/framing/MessageProperties.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/AMQHeaderBody.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qpid/log/Statement.h"
+#include "qpid/framing/Array.h"
+#include "qpid/RefCounted.h"
+#include "qpid/UrlArray.h"
+#include <boost/bind.hpp>
+#include <algorithm>
+
+
+namespace qpid {
+namespace ha {
+
+using namespace std;
+
+using namespace broker;
+using namespace framing;
+using broker::amqp_0_10::MessageTransfer;
+
+const string FailoverExchange::typeName("amq.failover");
+
+namespace {
+struct OstreamUrls {
+ OstreamUrls(const FailoverExchange::Urls& u) : urls(u) {}
+ FailoverExchange::Urls urls;
+};
+
+ostream& operator<<(ostream& o, const OstreamUrls& urls) {
+ ostream_iterator<qpid::Url> out(o, " ");
+ copy(urls.urls.begin(), urls.urls.end(), out);
+ return o;
+}
+}
+
+FailoverExchange::FailoverExchange(management::Manageable& parent, Broker& b)
+ : Exchange(typeName, &parent, &b)
+{
+ if (mgmtExchange != 0)
+ mgmtExchange->set_type(typeName);
+}
+
+void FailoverExchange::setUrls(const vector<Url>& u) {
+ QPID_LOG(debug, typeName << " URLs set to " << OstreamUrls(u));
+ Lock l(lock);
+ urls = u;
+}
+
+void FailoverExchange::updateUrls(const vector<Url>& u) {
+ QPID_LOG(debug, typeName << " Updating URLs " << OstreamUrls(u) << " to "
+ << queues.size() << " subscribers.");
+ Lock l(lock);
+ urls=u;
+ if (!urls.empty() && !queues.empty()) {
+ for (Queues::const_iterator i = queues.begin(); i != queues.end(); ++i)
+ sendUpdate(*i, l);
+ }
+}
+
+string FailoverExchange::getType() const { return typeName; }
+
+bool FailoverExchange::bind(Queue::shared_ptr queue, const string&,
+ const framing::FieldTable*) {
+ QPID_LOG(debug, typeName << " binding " << queue->getName());
+ Lock l(lock);
+ sendUpdate(queue, l);
+ return queues.insert(queue).second;
+}
+
+bool FailoverExchange::unbind(Queue::shared_ptr queue, const string&,
+ const framing::FieldTable*) {
+ QPID_LOG(debug, typeName << " un-binding " << queue->getName());
+ Lock l(lock);
+ return queues.erase(queue);
+}
+
+bool FailoverExchange::isBound(Queue::shared_ptr queue, const string* const,
+ const framing::FieldTable*) {
+ Lock l(lock);
+ return queues.find(queue) != queues.end();
+}
+
+bool FailoverExchange::hasBindings() {
+ Lock l(lock);
+ return !queues.empty();
+}
+
+void FailoverExchange::route(Deliverable&) {
+ QPID_LOG(warning, typeName << " unexpected message, ignored.");
+}
+
+void FailoverExchange::sendUpdate(const Queue::shared_ptr& queue, sys::Mutex::ScopedLock&) {
+ QPID_LOG(debug, typeName << " sending " << OstreamUrls(urls) << " to " << queue->getName());
+ if (urls.empty()) return;
+ framing::Array array = vectorToUrlArray(urls);
+ const ProtocolVersion v;
+ broker::Message message(makeMessage(std::string(), typeName, typeName));
+ MessageTransfer& transfer = MessageTransfer::get(message);
+ MessageProperties* props =
+ transfer.getFrames().getHeaders()->get<framing::MessageProperties>(true);
+ props->setContentLength(0);
+ props->getApplicationHeaders().setArray(typeName, array);
+ DeliverableMessage(message, 0).deliverTo(queue);
+}
+
+}} // namespace ha
diff --git a/qpid/cpp/src/qpid/ha/FailoverExchange.h b/qpid/cpp/src/qpid/ha/FailoverExchange.h
new file mode 100644
index 0000000000..5263bdfb03
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/FailoverExchange.h
@@ -0,0 +1,72 @@
+#ifndef QPID_HA_FAILOVEREXCHANGE_H
+#define QPID_HA_FAILOVEREXCHANGE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *ls
+ */
+
+#include "qpid/broker/Exchange.h"
+#include "qpid/broker/DeliverableMessage.h"
+#include "qpid/Url.h"
+
+#include <vector>
+#include <set>
+
+namespace qpid {
+namespace ha {
+
+/**
+ * Failover exchange provides failover host list, as specified in AMQP 0-10.
+ */
+class FailoverExchange : public broker::Exchange
+{
+ public:
+ typedef std::vector<Url> Urls;
+
+ static const std::string typeName;
+
+ FailoverExchange(management::Manageable& parent, broker::Broker& b);
+
+ /** Set the URLs but don't send an update.*/
+ void setUrls(const Urls&);
+ /** Set the URLs and send an update.*/
+ void updateUrls(const Urls&);
+
+ // Exchange overrides
+ std::string getType() const;
+ bool bind(boost::shared_ptr<broker::Queue> queue, const std::string& routingKey, const framing::FieldTable* args);
+ bool unbind(boost::shared_ptr<broker::Queue> queue, const std::string& routingKey, const framing::FieldTable* args);
+ bool isBound(boost::shared_ptr<broker::Queue> queue, const std::string* const routingKey, const framing::FieldTable* const args);
+ bool hasBindings();
+ void route(broker::Deliverable& msg);
+
+ private:
+ void sendUpdate(const boost::shared_ptr<broker::Queue>&, sys::Mutex::ScopedLock&);
+
+ typedef sys::Mutex::ScopedLock Lock;
+ typedef std::set<boost::shared_ptr<broker::Queue> > Queues;
+
+ sys::Mutex lock;
+ Urls urls;
+ Queues queues;
+};
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_FAILOVEREXCHANGE_H*/
diff --git a/qpid/cpp/src/qpid/ha/HaBroker.cpp b/qpid/cpp/src/qpid/ha/HaBroker.cpp
new file mode 100644
index 0000000000..7699b0e1d2
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/HaBroker.cpp
@@ -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.
+ *
+ */
+#include "Backup.h"
+#include "BackupConnectionExcluder.h"
+#include "ConnectionObserver.h"
+#include "HaBroker.h"
+#include "IdSetter.h"
+#include "Primary.h"
+#include "QueueReplicator.h"
+#include "ReplicatingSubscription.h"
+#include "Settings.h"
+#include "StandAlone.h"
+#include "QueueSnapshot.h"
+#include "qpid/amqp_0_10/Codecs.h"
+#include "qpid/assert.h"
+#include "qpid/Exception.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/BrokerObserver.h"
+#include "qpid/broker/Link.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/SignalHandler.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/management/ManagementAgent.h"
+#include "qpid/sys/SystemInfo.h"
+#include "qpid/types/Uuid.h"
+#include "qpid/framing/Uuid.h"
+#include "qmf/org/apache/qpid/ha/Package.h"
+#include "qmf/org/apache/qpid/ha/ArgsHaBrokerReplicate.h"
+#include "qmf/org/apache/qpid/ha/ArgsHaBrokerSetBrokersUrl.h"
+#include "qmf/org/apache/qpid/ha/ArgsHaBrokerSetPublicUrl.h"
+#include "qpid/log/Statement.h"
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace ha {
+
+namespace _qmf = ::qmf::org::apache::qpid::ha;
+using namespace management;
+using namespace std;
+using types::Variant;
+using types::Uuid;
+using sys::Mutex;
+using boost::shared_ptr;
+using boost::dynamic_pointer_cast;
+
+// In a HaBroker we always need to add QueueSnapshot and IdSetter to each queue
+// because we don't know in advance which queues might be used for stand-alone
+// replication.
+//
+// TODO aconway 2013-12-13: Can we restrict this to queues identified as replicated?
+//
+class HaBroker::BrokerObserver : public broker::BrokerObserver {
+ public:
+ BrokerObserver(const LogPrefix& lp) : logPrefix(lp) {}
+
+ void queueCreate(const boost::shared_ptr<broker::Queue>& q) {
+ q->getObservers().add(boost::shared_ptr<QueueSnapshot>(new QueueSnapshot));
+ q->getMessageInterceptors().add(
+ boost::shared_ptr<IdSetter>(new IdSetter(logPrefix, q->getName())));
+ }
+
+ private:
+ const LogPrefix& logPrefix;
+};
+
+// Called in Plugin::earlyInitialize
+HaBroker::HaBroker(broker::Broker& b, const Settings& s)
+ : systemId(b.getSystem()->getSystemId().data()),
+ settings(s),
+ userId(s.username+"@"+b.getRealm()),
+ broker(b),
+ observer(new ConnectionObserver(*this, systemId)),
+ role(new StandAlone),
+ membership(BrokerInfo(systemId, STANDALONE), *this), // Sets logPrefix
+ failoverExchange(new FailoverExchange(*b.GetVhostObject(), b))
+{
+ // If we are joining a cluster we must start excluding clients now,
+ // otherwise there's a window for a client to connect before we get to
+ // initialize()
+ if (settings.cluster) {
+ shared_ptr<broker::ConnectionObserver> excluder(new BackupConnectionExcluder(logPrefix));
+ observer->setObserver(excluder);
+ broker.getConnectionObservers().add(observer);
+ broker.getExchanges().registerExchange(failoverExchange);
+ }
+ broker.getBrokerObservers().add(boost::shared_ptr<BrokerObserver>(new BrokerObserver(logPrefix)));
+}
+
+namespace {
+const std::string NONE("none");
+bool isNone(const std::string& x) { return x.empty() || x == NONE; }
+}
+
+// Called in Plugin::initialize
+void HaBroker::initialize() {
+ if (settings.cluster) {
+ QPID_LOG(info, logPrefix << "Starting HA broker");
+ membership.setStatus(JOINING);
+ }
+
+ // Set up the management object.
+ ManagementAgent* ma = broker.getManagementAgent();
+ if (settings.cluster && !ma)
+ throw Exception("Cannot start HA: management is disabled");
+ _qmf::Package packageInit(ma);
+ mgmtObject = _qmf::HaBroker::shared_ptr(new _qmf::HaBroker(ma, this, "ha-broker"));
+ mgmtObject->set_replicateDefault(settings.replicateDefault.str());
+ mgmtObject->set_systemId(systemId);
+ ma->addObject(mgmtObject);
+ membership.setMgmtObject(mgmtObject);
+
+ // Register a factory for replicating subscriptions.
+ broker.getConsumerFactories().add(
+ shared_ptr<ReplicatingSubscription::Factory>(
+ new ReplicatingSubscription::Factory(*this)));
+
+ // If we are in a cluster, start as backup in joining state.
+ if (settings.cluster) {
+ assert(membership.getStatus() == JOINING);
+ role.reset(new Backup(*this, settings));
+ broker.getKnownBrokers = boost::bind(&HaBroker::getKnownBrokers, this);
+ if (!isNone(settings.publicUrl)) setPublicUrl(Url(settings.publicUrl));
+ if (!isNone(settings.brokerUrl)) setBrokerUrl(Url(settings.brokerUrl));
+ }
+}
+
+HaBroker::~HaBroker() {
+ broker.getConnectionObservers().remove(observer);
+}
+
+Manageable::status_t HaBroker::ManagementMethod (uint32_t methodId, Args& args, string&) {
+ switch (methodId) {
+ case _qmf::HaBroker::METHOD_PROMOTE: {
+ Role* r = role->promote();
+ if (r) role.reset(r);
+ break;
+ }
+ case _qmf::HaBroker::METHOD_SETBROKERSURL: {
+ setBrokerUrl(Url(dynamic_cast<_qmf::ArgsHaBrokerSetBrokersUrl&>(args).i_url));
+ break;
+ }
+ case _qmf::HaBroker::METHOD_SETPUBLICURL: {
+ setPublicUrl(Url(dynamic_cast<_qmf::ArgsHaBrokerSetPublicUrl&>(args).i_url));
+ break;
+ }
+ case _qmf::HaBroker::METHOD_REPLICATE: {
+ _qmf::ArgsHaBrokerReplicate& bq_args =
+ dynamic_cast<_qmf::ArgsHaBrokerReplicate&>(args);
+ QPID_LOG(debug, logPrefix << "Replicate individual queue "
+ << bq_args.i_queue << " from " << bq_args.i_broker);
+
+ shared_ptr<broker::Queue> queue = broker.getQueues().get(bq_args.i_queue);
+ Url url(bq_args.i_broker);
+ string protocol = url[0].protocol.empty() ? "tcp" : url[0].protocol;
+ Uuid uuid(true);
+ std::pair<broker::Link::shared_ptr, bool> result = broker.getLinks().declare(
+ broker::QPID_NAME_PREFIX + string("ha.link.") + uuid.str(),
+ url[0].host, url[0].port, protocol,
+ false, // durable
+ settings.mechanism, settings.username, settings.password,
+ false); // no amq.failover - don't want to use client URL.
+ shared_ptr<broker::Link> link = result.first;
+ link->setUrl(url);
+ // Create a queue replicator
+ shared_ptr<QueueReplicator> qr(QueueReplicator::create(*this, queue, link));
+ broker.getExchanges().registerExchange(qr);
+ break;
+ }
+
+ default:
+ return Manageable::STATUS_UNKNOWN_METHOD;
+ }
+ return Manageable::STATUS_OK;
+}
+
+void HaBroker::setPublicUrl(const Url& url) {
+ Mutex::ScopedLock l(lock);
+ publicUrl = url;
+ mgmtObject->set_publicUrl(url.str());
+ knownBrokers.clear();
+ knownBrokers.push_back(url);
+ vector<Url> urls(1, url);
+ failoverExchange->updateUrls(urls);
+ QPID_LOG(debug, logPrefix << "Public URL set to: " << url);
+}
+
+void HaBroker::setBrokerUrl(const Url& url) {
+ {
+ Mutex::ScopedLock l(lock);
+ brokerUrl = url;
+ mgmtObject->set_brokersUrl(brokerUrl.str());
+ QPID_LOG(info, logPrefix << "Brokers URL set to: " << url);
+ }
+ role->setBrokerUrl(url); // Oustside lock
+}
+
+std::vector<Url> HaBroker::getKnownBrokers() const {
+ Mutex::ScopedLock l(lock);
+ return knownBrokers;
+}
+
+void HaBroker::shutdown(const std::string& message) {
+ QPID_LOG(critical, logPrefix << "Shutting down: " << message);
+ broker.shutdown();
+ throw Exception(message);
+}
+
+BrokerStatus HaBroker::getStatus() const {
+ return membership.getStatus();
+}
+
+void HaBroker::setAddress(const Address& a) {
+ QPID_LOG(info, logPrefix << "Set self address to: " << a);
+ membership.setSelfAddress(a);
+}
+
+boost::shared_ptr<QueueReplicator> HaBroker::findQueueReplicator(const std::string& queueName) {
+ return boost::dynamic_pointer_cast<QueueReplicator>(
+ broker.getExchanges().find(QueueReplicator::replicatorName(queueName)));
+}
+
+}} // namespace qpid::ha
diff --git a/qpid/cpp/src/qpid/ha/HaBroker.h b/qpid/cpp/src/qpid/ha/HaBroker.h
new file mode 100644
index 0000000000..023706e7e3
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/HaBroker.h
@@ -0,0 +1,137 @@
+#ifndef QPID_HA_BROKER_H
+#define QPID_HA_BROKER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "BrokerInfo.h"
+#include "Membership.h"
+#include "types.h"
+#include "LogPrefix.h"
+#include "Settings.h"
+#include "qpid/Url.h"
+#include "FailoverExchange.h"
+#include "qpid/sys/Mutex.h"
+#include "qmf/org/apache/qpid/ha/HaBroker.h"
+#include "qpid/management/Manageable.h"
+#include "qpid/types/Variant.h"
+#include <set>
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+
+namespace types {
+class Variant;
+}
+
+namespace broker {
+class Broker;
+class Queue;
+}
+namespace framing {
+class FieldTable;
+}
+
+namespace ha {
+class Backup;
+class ConnectionObserver;
+class Primary;
+class Role;
+class QueueReplicator;
+
+/**
+ * HA state and actions associated with a HA broker. Holds all the management info.
+ *
+ * THREAD SAFE: may be called in arbitrary broker IO or timer threads.
+
+ * NOTE: HaBroker and Role subclasses follow this lock hierarchy:
+ * - HaBroker MUST NOT hold its own lock across calls Role subclasses.
+ * - Role subclasses MAY hold their locks accross calls to HaBroker.
+ */
+class HaBroker : public management::Manageable
+{
+ public:
+ /** HaBroker is constructed during earlyInitialize */
+ HaBroker(broker::Broker&, const Settings&);
+ ~HaBroker();
+
+ /** Called during plugin initialization */
+ void initialize();
+
+ // Implement Manageable.
+ qpid::management::ManagementObject::shared_ptr GetManagementObject() const { return mgmtObject; }
+ management::Manageable::status_t ManagementMethod (
+ uint32_t methodId, management::Args& args, std::string& text);
+
+ broker::Broker& getBroker() { return broker; }
+ const Settings& getSettings() const { return settings; }
+ boost::shared_ptr<Role> getRole() const {return role; }
+
+ /** Shut down the broker because of a critical error. */
+ void shutdown(const std::string& message);
+
+ BrokerStatus getStatus() const;
+ boost::shared_ptr<ConnectionObserver> getObserver() { return observer; }
+
+ BrokerInfo getBrokerInfo() const { return membership.getSelf(); }
+ Membership& getMembership() { return membership; }
+ types::Uuid getSystemId() const { return systemId; }
+
+ void setAddress(const Address&); // set self address from a self-connection
+
+ boost::shared_ptr<QueueReplicator> findQueueReplicator(const std::string& queueName);
+
+ /** Authenticated user ID for queue create/delete */
+ std::string getUserId() const { return userId; }
+
+ /** logPrefix is thread safe and used by other classes (Membership) */
+ LogPrefix logPrefix;
+
+ private:
+ class BrokerObserver;
+
+ void setPublicUrl(const Url&);
+ void setBrokerUrl(const Url&);
+ void updateClientUrl(sys::Mutex::ScopedLock&);
+
+ std::vector<Url> getKnownBrokers() const;
+
+ // Immutable members
+ const types::Uuid systemId;
+ const Settings settings;
+ const std::string userId;
+
+ // Member variables protected by lock
+ mutable sys::Mutex lock;
+ Url publicUrl, brokerUrl;
+ std::vector<Url> knownBrokers;
+
+ // Independently thread-safe member variables
+ broker::Broker& broker;
+ qmf::org::apache::qpid::ha::HaBroker::shared_ptr mgmtObject;
+ boost::shared_ptr<ConnectionObserver> observer; // Used by Backup and Primary
+ boost::shared_ptr<Role> role;
+ Membership membership;
+ boost::shared_ptr<FailoverExchange> failoverExchange;
+};
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_BROKER_H*/
diff --git a/qpid/cpp/src/qpid/ha/HaPlugin.cpp b/qpid/cpp/src/qpid/ha/HaPlugin.cpp
new file mode 100644
index 0000000000..913a9b5084
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/HaPlugin.cpp
@@ -0,0 +1,103 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#include "HaBroker.h"
+#include "Settings.h"
+#include "qpid/Plugin.h"
+#include "qpid/Options.h"
+#include "qpid/OptionsTemplates.h"
+#include "qpid/broker/Broker.h"
+#include <boost/bind.hpp>
+
+namespace qpid {
+
+template po::value_semantic* create_value(ha::Enum<ha::ReplicateLevel>& val, const std::string& arg);
+
+namespace ha {
+
+using namespace std;
+
+struct Options : public qpid::Options {
+ Settings& settings;
+ Options(Settings& s) : qpid::Options("HA Options"), settings(s) {
+ addOptions()
+ ("ha-cluster", optValue(settings.cluster, "yes|no"),
+ "Join a HA active/passive cluster.")
+ ("ha-queue-replication", optValue(settings.queueReplication, "yes|no"),
+ "Enable replication of specific queues without joining a cluster")
+ ("ha-brokers-url", optValue(settings.brokerUrl,"URL"),
+ "URL with address of each broker in the cluster.")
+ ("ha-public-url", optValue(settings.publicUrl,"URL"),
+ "URL advertized to clients to connect to the cluster.")
+ ("ha-replicate",
+ optValue(settings.replicateDefault, "LEVEL"),
+ "Replication level for creating queues and exchanges if there is no qpid.replicate argument supplied. LEVEL is 'none', 'configuration' or 'all'")
+ ("ha-username", optValue(settings.username, "USER"),
+ "Username for connections between HA brokers")
+ ("ha-password", optValue(settings.password, "PASS"),
+ "Password for connections between HA brokers")
+ ("ha-mechanism", optValue(settings.mechanism, "MECH"),
+ "Authentication mechanism for connections between HA brokers")
+ ("ha-backup-timeout", optValue(settings.backupTimeout, "SECONDS"),
+ "Maximum time to wait for an expected backup to connect and become ready.")
+ ("ha-flow-messages", optValue(settings.flowMessages, "N"),
+ "Flow control message count limit for replication, 0 means no limit")
+ ("ha-flow-bytes", optValue(settings.flowBytes, "N"),
+ "Flow control byte limit for replication, 0 means no limit")
+ ;
+ }
+};
+
+struct HaPlugin : public Plugin {
+
+ Settings settings;
+ Options options;
+ auto_ptr<HaBroker> haBroker;
+
+ HaPlugin() : options(settings) {}
+
+ Options* getOptions() { return &options; }
+
+ void earlyInitialize(Plugin::Target& target) {
+ broker::Broker* broker = dynamic_cast<broker::Broker*>(&target);
+ if (broker && (settings.cluster || settings.queueReplication)) {
+ if (!broker->getManagementAgent()) {
+ QPID_LOG(warning, "Cannot start HA: management is disabled");
+ if (settings.cluster)
+ throw Exception("Cannot start HA: management is disabled");
+ } else {
+ // Must create the HaBroker in earlyInitialize so it can set up its
+ // connection observer before clients start connecting.
+ haBroker.reset(new ha::HaBroker(*broker, settings));
+ broker->addFinalizer(boost::bind(&HaPlugin::finalize, this));
+ }
+ }
+ }
+
+ void initialize(Plugin::Target& target) {
+ broker::Broker* broker = dynamic_cast<broker::Broker*>(&target);
+ if (broker && haBroker.get()) haBroker->initialize();
+ }
+
+ void finalize() {
+ haBroker.reset();
+ }
+};
+
+HaPlugin instance; // Static initialization.
+
+}} // namespace qpid::ha
diff --git a/qpid/cpp/src/qpid/ha/IdSetter.h b/qpid/cpp/src/qpid/ha/IdSetter.h
new file mode 100644
index 0000000000..94bc74668e
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/IdSetter.h
@@ -0,0 +1,76 @@
+#ifndef QPID_HA_IDSETTER_H
+#define QPID_HA_IDSETTER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "types.h"
+
+#include "qpid/broker/Message.h"
+#include "qpid/broker/MessageInterceptor.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/AtomicValue.h"
+
+
+namespace qpid {
+namespace ha {
+class LogPrefix;
+
+/**
+ * A MessageInterceptor that sets the ReplicationId on each message as it is
+ * enqueued on a primary queue.
+ *
+ * THREAD UNSAFE: Called sequentially under the queue lock.
+ */
+class IdSetter : public broker::MessageInterceptor
+{
+ public:
+ IdSetter(const LogPrefix& lp, const std::string& q, ReplicationId firstId=1) :
+ logPrefix(lp), queue(q), nextId(firstId)
+ {}
+
+ void record(broker::Message& m) {
+ // Record is called when a message is first delivered to a queue, before it has
+ // been enqueued or saved in a transaction buffer. This is when we normally want
+ // to assign a replication-id.
+ m.setReplicationId(nextId++);
+ }
+
+ void publish(broker::Message& m) {
+ // Publish is called when a message is assigned a position on the queue,
+ // after any transaction has comitted. Normally this is too late to
+ // assign a replication-id but during broker start-up and recovery from
+ // store record() is not called, so set the ID now if not already set.
+ if (!m.hasReplicationId()) {
+ m.setReplicationId(nextId++);
+ }
+ }
+
+ private:
+ const LogPrefix& logPrefix;
+ std::string queue;
+ sys::AtomicValue<uint32_t> nextId;
+};
+
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_IDSETTER_H*/
diff --git a/qpid/cpp/src/qpid/ha/LogPrefix.cpp b/qpid/cpp/src/qpid/ha/LogPrefix.cpp
new file mode 100644
index 0000000000..c1ccf050c1
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/LogPrefix.cpp
@@ -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.
+ *
+ */
+#include "LogPrefix.h"
+#include <iostream>
+
+namespace qpid {
+namespace ha {
+
+std::ostream& operator<<(std::ostream& o, const LogPrefix& lp) {
+ return o << lp.get();
+}
+
+std::ostream& operator<<(std::ostream& o, const LogPrefix2& lp) {
+ return o << lp.prePrefix.get() << lp.get();
+}
+
+}} // namespace qpid::ha
diff --git a/qpid/cpp/src/qpid/ha/LogPrefix.h b/qpid/cpp/src/qpid/ha/LogPrefix.h
new file mode 100644
index 0000000000..3b6bb17d99
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/LogPrefix.h
@@ -0,0 +1,75 @@
+#ifndef QPID_HA_LOGPREFIX_H
+#define QPID_HA_LOGPREFIX_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <qpid/sys/Mutex.h>
+#include <string>
+#include <iosfwd>
+
+namespace qpid {
+namespace ha {
+
+/**
+ * Thread safe string holder to hold a string that may be read and modified concurrently.
+ */
+class LogPrefix
+{
+ public:
+ explicit LogPrefix(const std::string& s=std::string()) : prefix(s) {}
+ void set(const std::string& s) { sys::RWlock::ScopedWlock l(lock); prefix = s; }
+ std::string get() const { sys::RWlock::ScopedRlock l(lock); return prefix; }
+
+ LogPrefix& operator=(const std::string& s) { set(s); return *this; }
+ operator std::string() const { return get(); }
+
+ private:
+ // Undefined, not copyable.
+ LogPrefix(const LogPrefix& lp);
+ LogPrefix& operator=(const LogPrefix&);
+
+ mutable sys::RWlock lock;
+ std::string prefix;
+};
+std::ostream& operator<<(std::ostream& o, const LogPrefix& lp);
+
+/**
+ * A two-part log prefix with a reference to a pre-prefix and a post-prefix.
+ * Operator << will print both parts, get/set just manage the post-prefix.
+ */
+class LogPrefix2 : public LogPrefix {
+ public:
+ const LogPrefix& prePrefix;
+ explicit LogPrefix2(const LogPrefix& lp, const std::string& s=std::string()) : LogPrefix(s), prePrefix(lp) {}
+ LogPrefix2& operator=(const std::string& s) { set(s); return *this; }
+
+ private:
+ // Undefined, not copyable.
+ LogPrefix2(const LogPrefix2& lp);
+ LogPrefix2& operator=(const LogPrefix2&);
+};
+std::ostream& operator<<(std::ostream& o, const LogPrefix2& lp);
+
+
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_LOGPREFIX_H*/
diff --git a/qpid/cpp/src/qpid/ha/Membership.cpp b/qpid/cpp/src/qpid/ha/Membership.cpp
new file mode 100644
index 0000000000..92a0b7db70
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/Membership.cpp
@@ -0,0 +1,228 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "ConnectionObserver.h"
+#include "HaBroker.h"
+#include "Membership.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/management/ManagementAgent.h"
+#include "qpid/types/Variant.h"
+#include "qmf/org/apache/qpid/ha/EventMembersUpdate.h"
+#include "qmf/org/apache/qpid/ha/HaBroker.h"
+#include <boost/bind.hpp>
+#include <iostream>
+#include <iterator>
+
+namespace qpid {
+namespace ha {
+
+namespace _qmf = ::qmf::org::apache::qpid::ha;
+
+using sys::Mutex;
+using types::Variant;
+
+Membership::Membership(const BrokerInfo& info, HaBroker& b)
+ : haBroker(b), self(info.getSystemId())
+{
+ brokers[self] = info;
+ setPrefix();
+ oldStatus = info.getStatus();
+}
+
+void Membership::setPrefix() {
+ haBroker.logPrefix = Msg() << shortStr(brokers[self].getSystemId())
+ << "(" << printable(brokers[self].getStatus()) << ") ";
+}
+void Membership::clear() {
+ Mutex::ScopedLock l(lock);
+ BrokerInfo me = brokers[self];
+ brokers.clear();
+ brokers[self] = me;
+}
+
+void Membership::add(const BrokerInfo& b) {
+ Mutex::ScopedLock l(lock);
+ assert(b.getSystemId() != self);
+ brokers[b.getSystemId()] = b;
+ update(true, l);
+}
+
+
+void Membership::remove(const types::Uuid& id) {
+ Mutex::ScopedLock l(lock);
+ if (id == self) return; // Never remove myself
+ BrokerInfo::Map::iterator i = brokers.find(id);
+ if (i != brokers.end()) {
+ brokers.erase(i);
+ update(true, l);
+ }
+}
+
+bool Membership::contains(const types::Uuid& id) {
+ Mutex::ScopedLock l(lock);
+ return brokers.find(id) != brokers.end();
+}
+
+void Membership::assign(const types::Variant::List& list) {
+ Mutex::ScopedLock l(lock);
+ clear();
+ for (types::Variant::List::const_iterator i = list.begin(); i != list.end(); ++i) {
+ BrokerInfo b(i->asMap());
+ brokers[b.getSystemId()] = b;
+ }
+ update(true, l);
+}
+
+types::Variant::List Membership::asList() const {
+ Mutex::ScopedLock l(lock);
+ return asList(l);
+}
+
+types::Variant::List Membership::asList(sys::Mutex::ScopedLock&) const {
+ types::Variant::List list;
+ for (BrokerInfo::Map::const_iterator i = brokers.begin(); i != brokers.end(); ++i)
+ list.push_back(i->second.asMap());
+ return list;
+}
+
+BrokerInfo::Set Membership::otherBackups() const {
+ Mutex::ScopedLock l(lock);
+ BrokerInfo::Set result;
+ for (BrokerInfo::Map::const_iterator i = brokers.begin(); i != brokers.end(); ++i)
+ if (i->second.getStatus() == READY && i->second.getSystemId() != self)
+ result.insert(i->second);
+ return result;
+}
+
+BrokerInfo::Set Membership::getBrokers() const {
+ Mutex::ScopedLock l(lock);
+ BrokerInfo::Set result;
+ transform(brokers.begin(), brokers.end(), inserter(result, result.begin()),
+ boost::bind(&BrokerInfo::Map::value_type::second, _1));
+ return result;
+}
+
+bool Membership::get(const types::Uuid& id, BrokerInfo& result) const {
+ Mutex::ScopedLock l(lock);
+ BrokerInfo::Map::const_iterator i = brokers.find(id);
+ if (i == brokers.end()) return false;
+ result = i->second;
+ return true;
+}
+
+namespace {
+bool checkTransition(BrokerStatus from, BrokerStatus to) {
+ // Legal state transitions. Initial state is JOINING, ACTIVE is terminal.
+ static const BrokerStatus TRANSITIONS[][2] = {
+ { STANDALONE, JOINING }, // Initialization of backup broker
+ { JOINING, CATCHUP }, // Connected to primary
+ { JOINING, RECOVERING }, // Chosen as initial primary.
+ { CATCHUP, READY }, // Caught up all queues, ready to take over.
+ { READY, RECOVERING }, // Chosen as new primary
+ { READY, CATCHUP }, // Timed out failing over, demoted to catch-up.
+ { RECOVERING, ACTIVE } // All expected backups are ready
+ };
+ static const size_t N = sizeof(TRANSITIONS)/sizeof(TRANSITIONS[0]);
+ for (size_t i = 0; i < N; ++i) {
+ if (TRANSITIONS[i][0] == from && TRANSITIONS[i][1] == to)
+ return true;
+ }
+ return false;
+}
+} // namespace
+
+void Membership::update(bool log, Mutex::ScopedLock& l) {
+ // Update managment and send update event.
+ BrokerStatus newStatus = getStatus(l);
+ Variant::List brokerList = asList(l);
+ if (mgmtObject) {
+ mgmtObject->set_status(printable(newStatus).str());
+ mgmtObject->set_members(brokerList);
+ }
+ haBroker.getBroker().getManagementAgent()->raiseEvent(
+ _qmf::EventMembersUpdate(brokerList));
+
+ // Update link client properties
+ framing::FieldTable linkProperties = haBroker.getBroker().getLinkClientProperties();
+ if (isBackup(newStatus)) {
+ // Set backup tag on outgoing link properties.
+ linkProperties.setTable(
+ ConnectionObserver::BACKUP_TAG, brokers[types::Uuid(self)].asFieldTable());
+ haBroker.getBroker().setLinkClientProperties(linkProperties);
+ } else {
+ // Remove backup tag property from outgoing link properties.
+ linkProperties.erase(ConnectionObserver::BACKUP_TAG);
+ haBroker.getBroker().setLinkClientProperties(linkProperties);
+ }
+
+ // Check status transitions
+ if (oldStatus != newStatus) {
+ QPID_LOG(info, haBroker.logPrefix << "Status change: "
+ << printable(oldStatus) << " -> " << printable(newStatus));
+ if (!checkTransition(oldStatus, newStatus)) {
+ haBroker.shutdown(QPID_MSG("Illegal state transition: " << printable(oldStatus)
+ << " -> " << printable(newStatus)));
+ }
+ oldStatus = newStatus;
+ setPrefix();
+ if (newStatus == READY) QPID_LOG(notice, haBroker.logPrefix << "Backup is ready");
+ }
+ if (log) QPID_LOG(info, haBroker.logPrefix << "Membership update: " << brokers);
+}
+
+void Membership::setMgmtObject(boost::shared_ptr<_qmf::HaBroker> mo) {
+ Mutex::ScopedLock l(lock);
+ mgmtObject = mo;
+ update(false, l);
+}
+
+
+void Membership::setStatus(BrokerStatus newStatus) {
+ Mutex::ScopedLock l(lock);
+ brokers[self].setStatus(newStatus);
+ update(false, l);
+}
+
+BrokerStatus Membership::getStatus() const {
+ Mutex::ScopedLock l(lock);
+ return getStatus(l);
+}
+
+BrokerStatus Membership::getStatus(sys::Mutex::ScopedLock&) const {
+ BrokerInfo::Map::const_iterator i = brokers.find(self);
+ assert(i != brokers.end());
+ return i->second.getStatus();
+}
+
+BrokerInfo Membership::getSelf() const {
+ Mutex::ScopedLock l(lock);
+ BrokerInfo::Map::const_iterator i = brokers.find(self);
+ assert(i != brokers.end());
+ return i->second;
+}
+
+void Membership::setSelfAddress(const Address& a) {
+ Mutex::ScopedLock l(lock);
+ brokers[self].setAddress(a);
+ update(false, l);
+}
+
+}} // namespace qpid::ha
diff --git a/qpid/cpp/src/qpid/ha/Membership.h b/qpid/cpp/src/qpid/ha/Membership.h
new file mode 100644
index 0000000000..5590d255fa
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/Membership.h
@@ -0,0 +1,103 @@
+#ifndef QPID_HA_MEMBERSHIP_H
+#define QPID_HA_MEMBERSHIP_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "BrokerInfo.h"
+#include "types.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/Time.h"
+#include "qpid/types/Variant.h"
+#include <boost/function.hpp>
+#include <set>
+#include <vector>
+#include <iosfwd>
+
+namespace qmf { namespace org { namespace apache { namespace qpid { namespace ha {
+class HaBroker;
+}}}}}
+
+namespace qpid {
+
+namespace broker {
+class Broker;
+}
+
+namespace types {
+class Uuid;
+}
+
+namespace ha {
+class HaBroker;
+
+/**
+ * Keep track of the brokers in the membership.
+ * Send management when events on membership changes.
+ * THREAD SAFE
+ */
+class Membership
+{
+ public:
+ Membership(const BrokerInfo& info, HaBroker&);
+
+ void setMgmtObject(boost::shared_ptr<qmf::org::apache::qpid::ha::HaBroker>);
+
+ void clear(); ///< Clear all but self.
+ void add(const BrokerInfo& b);
+ void remove(const types::Uuid& id);
+ bool contains(const types::Uuid& id);
+
+ /** Return IDs of all READY backups other than self */
+ BrokerInfo::Set otherBackups() const;
+
+ /** Return IDs of all brokers */
+ BrokerInfo::Set getBrokers() const;
+
+ void assign(const types::Variant::List&);
+ types::Variant::List asList() const;
+
+ bool get(const types::Uuid& id, BrokerInfo& result) const;
+
+ BrokerInfo getSelf() const;
+ BrokerStatus getStatus() const;
+ void setStatus(BrokerStatus s);
+
+ void setSelfAddress(const Address&);
+
+ private:
+ void setPrefix();
+ void update(bool log, sys::Mutex::ScopedLock&);
+ BrokerStatus getStatus(sys::Mutex::ScopedLock&) const;
+ types::Variant::List asList(sys::Mutex::ScopedLock&) const;
+
+ mutable sys::Mutex lock;
+ HaBroker& haBroker;
+ boost::shared_ptr<qmf::org::apache::qpid::ha::HaBroker> mgmtObject;
+ const types::Uuid self;
+ BrokerInfo::Map brokers;
+ BrokerStatus oldStatus;
+};
+
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_MEMBERSHIP_H*/
diff --git a/qpid/cpp/src/qpid/ha/Primary.cpp b/qpid/cpp/src/qpid/ha/Primary.cpp
new file mode 100644
index 0000000000..870e4723b2
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/Primary.cpp
@@ -0,0 +1,498 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "Backup.h"
+#include "HaBroker.h"
+#include "Primary.h"
+#include "ReplicationTest.h"
+#include "IdSetter.h"
+#include "ReplicatingSubscription.h"
+#include "RemoteBackup.h"
+#include "ConnectionObserver.h"
+#include "QueueReplicator.h"
+#include "PrimaryTxObserver.h"
+#include "qpid/assert.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/BrokerObserver.h"
+#include "qpid/broker/Connection.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/SessionHandlerObserver.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/log/Statement.h"
+#include "qpid/types/Uuid.h"
+#include "qpid/types/Variant.h"
+#include "qpid/sys/Timer.h"
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/intrusive_ptr.hpp>
+
+namespace qpid {
+namespace ha {
+
+using sys::Mutex;
+using boost::shared_ptr;
+using boost::intrusive_ptr;
+using namespace std;
+using namespace framing;
+
+namespace {
+
+const std::string CLIENT_PROCESS_NAME("qpid.client_process");
+const std::string CLIENT_PID("qpid.client_pid");
+const std::string CLIENT_PPID("qpid.client_ppid");
+
+class PrimaryConnectionObserver : public broker::ConnectionObserver
+{
+ public:
+ PrimaryConnectionObserver(Primary& p) : primary(p) {}
+ void opened(broker::Connection& c) { primary.opened(c); }
+ void closed(broker::Connection& c) { primary.closed(c); }
+ private:
+ Primary& primary;
+};
+
+class PrimaryBrokerObserver : public broker::BrokerObserver
+{
+ public:
+ PrimaryBrokerObserver(Primary& p) : primary(p) {}
+ void queueCreate(const Primary::QueuePtr& q) { primary.queueCreate(q); }
+ void queueDestroy(const Primary::QueuePtr& q) { primary.queueDestroy(q); }
+ void exchangeCreate(const Primary::ExchangePtr& q) { primary.exchangeCreate(q); }
+ void exchangeDestroy(const Primary::ExchangePtr& q) { primary.exchangeDestroy(q); }
+ void startTx(const intrusive_ptr<broker::TxBuffer>& tx) { primary.startTx(tx); }
+ void startDtx(const intrusive_ptr<broker::DtxBuffer>& dtx) { primary.startDtx(dtx); }
+
+ private:
+ Primary& primary;
+};
+
+class ExpectedBackupTimerTask : public sys::TimerTask {
+ public:
+ ExpectedBackupTimerTask(Primary& p, sys::AbsTime deadline)
+ : TimerTask(deadline, "ExpectedBackupTimerTask"), primary(p) {}
+ void fire() { primary.timeoutExpectedBackups(); }
+ private:
+ Primary& primary;
+};
+
+class PrimaryErrorListener : public broker::SessionHandler::ErrorListener {
+ public:
+ PrimaryErrorListener(const LogPrefix& lp) : logPrefix(lp) {}
+
+ void connectionException(framing::connection::CloseCode code, const std::string& msg) {
+ QPID_LOG(debug, logPrefix << framing::createConnectionException(code, msg).what());
+ }
+ void channelException(framing::session::DetachCode code, const std::string& msg) {
+ QPID_LOG(debug, logPrefix << framing::createChannelException(code, msg).what());
+ }
+ void executionException(framing::execution::ErrorCode code, const std::string& msg) {
+ QPID_LOG(debug, logPrefix << framing::createSessionException(code, msg).what());
+ }
+ void incomingExecutionException(framing::execution::ErrorCode code, const std::string& msg) {
+ QPID_LOG(debug, logPrefix << "Incoming " << framing::createSessionException(code, msg).what());
+ }
+ void detach() {}
+
+ private:
+ const LogPrefix& logPrefix;
+};
+
+class PrimarySessionHandlerObserver : public broker::SessionHandlerObserver {
+ public:
+ PrimarySessionHandlerObserver(const LogPrefix& logPrefix)
+ : errorListener(new PrimaryErrorListener(logPrefix)) {}
+ void newSessionHandler(broker::SessionHandler& sh) {
+ BrokerInfo info;
+ // Suppress error logging for backup connections
+ // TODO aconway 2014-01-31: Be more selective, suppress only expected errors?
+ if (ha::ConnectionObserver::getBrokerInfo(sh.getConnection(), info)) {
+ sh.setErrorListener(errorListener);
+ }
+ }
+ private:
+ boost::shared_ptr<PrimaryErrorListener> errorListener;
+};
+
+
+} // namespace
+
+Primary::Primary(HaBroker& hb, const BrokerInfo::Set& expect) :
+ haBroker(hb), membership(hb.getMembership()),
+ logPrefix(hb.logPrefix), active(false),
+ replicationTest(hb.getSettings().replicateDefault.get()),
+ sessionHandlerObserver(new PrimarySessionHandlerObserver(logPrefix)),
+ queueLimits(logPrefix, hb.getBroker().getQueues(), replicationTest)
+{
+ // Note that at this point, we are still rejecting client connections.
+ // So we are safe from client interference while we set up the primary.
+
+ hb.getMembership().setStatus(RECOVERING);
+ QPID_LOG(notice, logPrefix << "Promoted to primary");
+
+ // Process all QueueReplicators, handles auto-delete queues.
+ QueueReplicator::Vector qrs;
+ QueueReplicator::copy(hb.getBroker().getExchanges(), qrs);
+ std::for_each(qrs.begin(), qrs.end(), boost::bind(&QueueReplicator::promoted, _1));
+
+ if (!expect.empty()) {
+ // NOTE: RemoteBackups must be created before we set the BrokerObserver
+ // or ConnectionObserver so that there is no client activity while
+ // the QueueGuards are created.
+ QPID_LOG(notice, logPrefix << "Recovering backups: " << expect);
+ for (BrokerInfo::Set::const_iterator i = expect.begin(); i != expect.end(); ++i) {
+ boost::shared_ptr<RemoteBackup> backup(new RemoteBackup(*i, 0, haBroker.logPrefix));
+ backups[i->getSystemId()] = backup;
+ if (!backup->isReady()) expectedBackups.insert(backup);
+ setCatchupQueues(backup, true); // Create guards
+ }
+ // Set timeout for expected brokers to connect and become ready.
+ sys::AbsTime deadline(sys::now(), hb.getSettings().backupTimeout);
+ timerTask = new ExpectedBackupTimerTask(*this, deadline);
+ hb.getBroker().getTimer().add(timerTask);
+ }
+ brokerObserver.reset(new PrimaryBrokerObserver(*this));
+ haBroker.getBroker().getBrokerObservers().add(brokerObserver);
+ haBroker.getBroker().getSessionHandlerObservers().add(sessionHandlerObserver);
+
+ checkReady(); // Outside lock
+
+ // Allow client connections
+ connectionObserver.reset(new PrimaryConnectionObserver(*this));
+ haBroker.getObserver()->setObserver(connectionObserver);
+}
+
+Primary::~Primary() {
+ if (timerTask) timerTask->cancel();
+ haBroker.getBroker().getBrokerObservers().remove(brokerObserver);
+ haBroker.getBroker().getSessionHandlerObservers().remove(sessionHandlerObserver);
+ haBroker.getObserver()->reset();
+}
+
+void Primary::checkReady() {
+ bool activate = false;
+ {
+ Mutex::ScopedLock l(lock);
+ if (!active && expectedBackups.empty())
+ activate = active = true;
+ }
+ if (activate) {
+ membership.setStatus(ACTIVE); // Outside of lock.
+ QPID_LOG(notice, logPrefix << "All backups recovered.");
+ }
+}
+
+void Primary::checkReady(boost::shared_ptr<RemoteBackup> backup) {
+ bool ready = false;
+ {
+ Mutex::ScopedLock l(lock);
+ if (backup->reportReady()) {
+ BrokerInfo info = backup->getBrokerInfo();
+ info.setStatus(READY);
+ membership.add(info);
+ if (expectedBackups.erase(backup)) {
+ QPID_LOG(info, logPrefix << "Recovering backup is ready: " << info);
+ ready = true;
+ }
+ else
+ QPID_LOG(info, logPrefix << "New backup is ready: " << info);
+ }
+ }
+ if (ready) checkReady(); // Outside lock
+}
+
+void Primary::timeoutExpectedBackups() {
+ try {
+ sys::Mutex::ScopedLock l(lock);
+ if (active) return; // Already activated
+ // Remove records for any expectedBackups that are not yet connected
+ // Allow backups that are connected to continue becoming ready.
+ for (BackupSet::iterator i = expectedBackups.begin(); i != expectedBackups.end();)
+ {
+ // This loop erases elements of backups in backupDisconnect, so
+ // save and increment the iterator.
+ BackupSet::iterator j = i++;
+ boost::shared_ptr<RemoteBackup> backup = *j;
+ if (!backup->getConnection()) {
+ BrokerInfo info = backup->getBrokerInfo();
+ QPID_LOG(error, logPrefix << "Recovering backup timed out: " << info);
+ backupDisconnect(backup, l); // Calls erase(j)
+ // Keep broker in membership but downgrade status to CATCHUP.
+ // The broker will get this status change when it eventually connects.
+ info.setStatus(CATCHUP);
+ membership.add(info);
+ }
+ }
+ }
+ catch(const std::exception& e) {
+ QPID_LOG(error, logPrefix << "Error timing out backups: " << e.what());
+ // No-where for this exception to go.
+ }
+ checkReady();
+}
+
+void Primary::readyReplica(const ReplicatingSubscription& rs) {
+ shared_ptr<RemoteBackup> backup;
+ {
+ sys::Mutex::ScopedLock l(lock);
+ BackupMap::iterator i = backups.find(rs.getBrokerInfo().getSystemId());
+ if (i != backups.end()) {
+ backup = i->second;
+ backup->ready(rs.getQueue());
+ }
+ }
+ if (backup) checkReady(backup);
+}
+
+void Primary::addReplica(ReplicatingSubscription& rs) {
+ // Note this is called before the ReplicatingSubscription has been activated
+ // on the queue.
+ sys::Mutex::ScopedLock l(lock);
+ replicas[make_pair(rs.getBrokerInfo().getSystemId(), rs.getQueue())] = &rs;
+}
+
+void Primary::skipEnqueues(
+ const types::Uuid& backup,
+ const boost::shared_ptr<broker::Queue>& queue,
+ const ReplicationIdSet& ids)
+{
+ sys::Mutex::ScopedLock l(lock);
+ ReplicaMap::const_iterator i = replicas.find(make_pair(backup, queue));
+ if (i != replicas.end()) i->second->skipEnqueues(ids);
+}
+
+void Primary::skipDequeues(
+ const types::Uuid& backup,
+ const boost::shared_ptr<broker::Queue>& queue,
+ const ReplicationIdSet& ids)
+{
+ sys::Mutex::ScopedLock l(lock);
+ ReplicaMap::const_iterator i = replicas.find(make_pair(backup, queue));
+ if (i != replicas.end()) i->second->skipDequeues(ids);
+}
+
+// Called from ReplicatingSubscription::cancel
+void Primary::removeReplica(const ReplicatingSubscription& rs) {
+ boost::shared_ptr<PrimaryTxObserver> tx;
+ {
+ sys::Mutex::ScopedLock l(lock);
+ replicas.erase(make_pair(rs.getBrokerInfo().getSystemId(), rs.getQueue()));
+ TxMap::const_iterator i = txMap.find(rs.getQueue()->getName());
+ if (i != txMap.end()) tx = i->second.lock();
+ }
+ if (tx) tx->cancel(rs); // Outside of lock.
+}
+
+// NOTE: Called with queue registry lock held.
+void Primary::queueCreate(const QueuePtr& q) {
+ // Set replication argument.
+ ReplicateLevel level = replicationTest.useLevel(*q);
+ q->addArgument(QPID_REPLICATE, printable(level).str());
+ if (level) {
+ QPID_LOG(debug, logPrefix << "Created queue " << q->getName()
+ << " replication: " << printable(level));
+ // Give each queue a unique id. Used by backups to avoid confusion of
+ // same-named queues.
+ q->addArgument(QPID_HA_UUID, types::Variant(Uuid(true)));
+ {
+ Mutex::ScopedLock l(lock);
+ queueLimits.addQueue(q); // Throws if limit exceeded
+ for (BackupMap::iterator i = backups.begin(); i != backups.end(); ++i)
+ i->second->queueCreate(q);
+ }
+ checkReady(); // Outside lock
+ }
+}
+
+// NOTE: Called with queue registry lock held.
+void Primary::queueDestroy(const QueuePtr& q) {
+ if (replicationTest.useLevel(*q)) {
+ QPID_LOG(debug, logPrefix << "Destroyed queue " << q->getName());
+ {
+ Mutex::ScopedLock l(lock);
+ queueLimits.removeQueue(q);
+ for (BackupMap::iterator i = backups.begin(); i != backups.end(); ++i)
+ i->second->queueDestroy(q);
+ }
+ checkReady(); // Outside lock
+ }
+}
+
+// NOTE: Called with exchange registry lock held.
+void Primary::exchangeCreate(const ExchangePtr& ex) {
+ ReplicateLevel level = replicationTest.useLevel(*ex);
+ FieldTable args = ex->getArgs();
+ args.setString(QPID_REPLICATE, printable(level).str()); // Set replication arg.
+ if (level) {
+ QPID_LOG(debug, logPrefix << "Created exchange " << ex->getName()
+ << " replication: " << printable(level));
+ // Give each exchange a unique id to avoid confusion of same-named exchanges.
+ args.set(QPID_HA_UUID, FieldTable::ValuePtr(new UuidValue(Uuid(true).data())));
+ }
+ ex->setArgs(args);
+}
+
+// NOTE: Called with exchange registry lock held.
+void Primary::exchangeDestroy(const ExchangePtr& ex) {
+ if (replicationTest.useLevel(*ex)) {
+ QPID_LOG(debug, logPrefix << "Destroyed exchange " << ex->getName());
+ // Do nothing
+ }
+ }
+
+// New backup connected
+shared_ptr<RemoteBackup> Primary::backupConnect(
+ const BrokerInfo& info, broker::Connection& connection, Mutex::ScopedLock&)
+{
+ shared_ptr<RemoteBackup> backup(new RemoteBackup(info, &connection, haBroker.logPrefix));
+ queueLimits.addBackup(backup);
+ backups[info.getSystemId()] = backup;
+ return backup;
+}
+
+// Remove a backup. Caller should not release the shared pointer returend till
+// outside the lock.
+void Primary::backupDisconnect(shared_ptr<RemoteBackup> backup, Mutex::ScopedLock&) {
+ queueLimits.addBackup(backup);
+ types::Uuid id = backup->getBrokerInfo().getSystemId();
+ backup->cancel();
+ expectedBackups.erase(backup);
+ backups.erase(id);
+ membership.remove(id);
+}
+
+
+void Primary::opened(broker::Connection& connection) {
+ BrokerInfo info;
+ shared_ptr<RemoteBackup> backup;
+ if (ha::ConnectionObserver::getBrokerInfo(connection, info)) {
+ Mutex::ScopedLock l(lock);
+ BackupMap::iterator i = backups.find(info.getSystemId());
+ if (info.getStatus() == JOINING) {
+ info.setStatus(CATCHUP);
+ membership.add(info);
+ }
+ if (i == backups.end()) {
+ if (info.getStatus() == JOINING) {
+ info.setStatus(CATCHUP);
+ membership.add(info);
+ }
+ QPID_LOG(info, logPrefix << "New backup connection: " << info);
+ backup = backupConnect(info, connection, l);
+ }
+ else if (i->second->getConnection()) {
+ // The backup is failing over before we recieved the closed() call
+ // for its previous connection. Remove the old entry and create a new one.
+ QPID_LOG(error, logPrefix << "Known backup reconnect before disconnection: " << info);
+ backupDisconnect(i->second, l);
+ backup = backupConnect(info, connection, l);
+ } else {
+ QPID_LOG(info, logPrefix << "Known backup reconnection: " << info);
+ i->second->setConnection(&connection);
+ backup = i->second;
+ }
+ }
+ else {
+ const types::Variant::Map& properties = connection.getClientProperties();
+ std::ostringstream pinfo;
+ types::Variant::Map::const_iterator i = properties.find(CLIENT_PROCESS_NAME);
+ // FIXME aconway 2014-08-13: Conditional on logging.
+ if (i != properties.end()) {
+ pinfo << " " << i->second;
+ i = properties.find(CLIENT_PID);
+ if (i != properties.end())
+ pinfo << "(" << i->second << ")";
+ }
+ QPID_LOG(info, logPrefix << "Accepted client connection " << connection.getMgmtId() << pinfo.str());
+ }
+
+ // Outside lock
+ if (backup) {
+ setCatchupQueues(backup, false);
+ checkReady(backup);
+ }
+ checkReady();
+}
+
+void Primary::closed(broker::Connection& connection) {
+ BrokerInfo info;
+ shared_ptr<RemoteBackup> backup;
+ if (ha::ConnectionObserver::getBrokerInfo(connection, info)) {
+ Mutex::ScopedLock l(lock);
+ BackupMap::iterator i = backups.find(info.getSystemId());
+ // NOTE: It is possible for a backup connection to be rejected while we
+ // are a backup, but closed() is called after we have become primary.
+ // Checking isConnected() lets us ignore such spurious closes.
+ if (i == backups.end()) {
+ QPID_LOG(info, logPrefix << "Disconnect from unknown backup " << info);
+ }
+ else if (i->second->getConnection() != &connection) {
+ QPID_LOG(info, logPrefix << "Late disconnect from backup " << info);
+ }
+ else {
+ QPID_LOG(info, logPrefix << "Disconnect from "
+ << (i->second->getConnection() ? "" : "disconnected ")
+ << "backup " << info);
+ // Assign to shared_ptr so it will be deleted after we release the lock.
+ backup = i->second;
+ backupDisconnect(backup, l);
+ }
+ }
+ checkReady();
+}
+
+boost::shared_ptr<QueueGuard> Primary::getGuard(const QueuePtr& q, const BrokerInfo& info)
+{
+ Mutex::ScopedLock l(lock);
+ BackupMap::iterator i = backups.find(info.getSystemId());
+ return i == backups.end() ? boost::shared_ptr<QueueGuard>() : i->second->guard(q);
+}
+
+Role* Primary::promote() {
+ QPID_LOG(info, logPrefix << "Ignoring promotion, already primary");
+ return 0;
+}
+
+void Primary::setCatchupQueues(const RemoteBackupPtr& backup, bool createGuards) {
+ // Do queue iteration outside the lock to avoid deadlocks with QueueRegistry.
+ haBroker.getBroker().getQueues().eachQueue(
+ boost::bind(&RemoteBackup::catchupQueue, backup, _1, createGuards));
+ backup->startCatchup();
+}
+
+shared_ptr<PrimaryTxObserver> Primary::makeTxObserver(
+ const boost::intrusive_ptr<broker::TxBuffer>& txBuffer)
+{
+ shared_ptr<PrimaryTxObserver> observer =
+ PrimaryTxObserver::create(*this, haBroker, txBuffer);
+ sys::Mutex::ScopedLock l(lock);
+ txMap[observer->getTxQueue()->getName()] = observer;
+ return observer;
+}
+
+void Primary::startTx(const boost::intrusive_ptr<broker::TxBuffer>& txBuffer) {
+ txBuffer->setObserver(makeTxObserver(txBuffer));
+}
+
+void Primary::startDtx(const boost::intrusive_ptr<broker::DtxBuffer>& ) {
+ QPID_LOG(warning, "DTX transactions in a HA cluster are not yet atomic");
+}
+
+}} // namespace qpid::ha
diff --git a/qpid/cpp/src/qpid/ha/Primary.h b/qpid/cpp/src/qpid/ha/Primary.h
new file mode 100644
index 0000000000..84d714fc01
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/Primary.h
@@ -0,0 +1,169 @@
+#ifndef QPID_HA_PRIMARY_H
+#define QPID_HA_PRIMARY_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "types.h"
+#include "hash.h"
+#include "BrokerInfo.h"
+#include "LogPrefix.h"
+#include "PrimaryQueueLimits.h"
+#include "ReplicationTest.h"
+#include "Role.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/unordered_map.h"
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+#include <boost/intrusive_ptr.hpp>
+#include <string>
+
+namespace qpid {
+
+namespace broker {
+class Queue;
+class Connection;
+class ConnectionObserver;
+class BrokerObserver;
+class SessionHandlerObserver;
+class TxBuffer;
+class DtxBuffer;
+}
+
+namespace sys {
+class TimerTask;
+}
+
+namespace ha {
+class HaBroker;
+class ReplicatingSubscription;
+class RemoteBackup;
+class QueueGuard;
+class Membership;
+class PrimaryTxObserver;
+
+/**
+ * State associated with a primary broker:
+ * - sets queue guards and tracks readiness of initial backups till active.
+ * - sets queue guards on new queues for each backup.
+ *
+ * THREAD SAFE: called concurrently in arbitrary connection threads.
+ *
+ * Locking rules: BrokerObserver create/destroy functions are called with
+ * the QueueRegistry lock held. Functions holding Primary::lock *must not*
+ * directly or indirectly call on the queue registry.
+ */
+class Primary : public Role
+{
+ public:
+ typedef boost::shared_ptr<broker::Queue> QueuePtr;
+ typedef boost::shared_ptr<broker::Exchange> ExchangePtr;
+ typedef boost::shared_ptr<RemoteBackup> RemoteBackupPtr;
+
+ Primary(HaBroker& hb, const BrokerInfo::Set& expectedBackups);
+ ~Primary();
+
+ // Role implementation
+ Role* promote();
+ void setBrokerUrl(const Url&) {}
+
+ void readyReplica(const ReplicatingSubscription&);
+ void addReplica(ReplicatingSubscription&);
+ void removeReplica(const ReplicatingSubscription&);
+
+ /** Skip replication of ids to queue on backup. */
+ void skipEnqueues(const types::Uuid& backup,
+ const boost::shared_ptr<broker::Queue>& queue,
+ const ReplicationIdSet& ids);
+
+ /** Skip replication of dequeue of ids to queue on backup. */
+ void skipDequeues(const types::Uuid& backup,
+ const boost::shared_ptr<broker::Queue>& queue,
+ const ReplicationIdSet& ids);
+
+ // Called via BrokerObserver
+ void queueCreate(const QueuePtr&);
+ void queueDestroy(const QueuePtr&);
+ void exchangeCreate(const ExchangePtr&);
+ void exchangeDestroy(const ExchangePtr&);
+ void startTx(const boost::intrusive_ptr<broker::TxBuffer>&);
+ void startDtx(const boost::intrusive_ptr<broker::DtxBuffer>&);
+
+ // Called via ConnectionObserver
+ void opened(broker::Connection& connection);
+ void closed(broker::Connection& connection);
+
+ boost::shared_ptr<QueueGuard> getGuard(const QueuePtr& q, const BrokerInfo&);
+
+ // Called in timer thread when the deadline for expected backups expires.
+ void timeoutExpectedBackups();
+
+ private:
+ typedef sys::unordered_map<
+ types::Uuid, RemoteBackupPtr, Hasher<types::Uuid> > BackupMap;
+
+ typedef std::set<RemoteBackupPtr > BackupSet;
+
+ typedef std::pair<types::Uuid, boost::shared_ptr<broker::Queue> > UuidQueue;
+ typedef sys::unordered_map<UuidQueue, ReplicatingSubscription*,
+ Hasher<UuidQueue> > ReplicaMap;
+
+ // Map of PrimaryTxObservers by tx-queue name
+ typedef sys::unordered_map<std::string, boost::weak_ptr<PrimaryTxObserver> > TxMap;
+
+ RemoteBackupPtr backupConnect(const BrokerInfo&, broker::Connection&, sys::Mutex::ScopedLock&);
+ void backupDisconnect(RemoteBackupPtr, sys::Mutex::ScopedLock&);
+
+ void checkReady();
+ void checkReady(RemoteBackupPtr);
+ void setCatchupQueues(const RemoteBackupPtr&, bool createGuards);
+ void deduplicate();
+ boost::shared_ptr<PrimaryTxObserver> makeTxObserver(
+ const boost::intrusive_ptr<broker::TxBuffer>&);
+
+ mutable sys::Mutex lock;
+ HaBroker& haBroker;
+ Membership& membership;
+ const LogPrefix& logPrefix;
+ bool active;
+ ReplicationTest replicationTest;
+
+ /**
+ * Set of expected backups that must be ready before we declare ourselves
+ * active. These are backups that were known and ready before the primary
+ * crashed. As new primary we expect them to re-connect.
+ */
+ BackupSet expectedBackups;
+ /**
+ * Map of all the expected backups plus all connected backups.
+ */
+ BackupMap backups;
+ boost::shared_ptr<broker::ConnectionObserver> connectionObserver;
+ boost::shared_ptr<broker::BrokerObserver> brokerObserver;
+ boost::shared_ptr<broker::SessionHandlerObserver> sessionHandlerObserver;
+ boost::intrusive_ptr<sys::TimerTask> timerTask;
+ ReplicaMap replicas;
+ TxMap txMap;
+ PrimaryQueueLimits queueLimits;
+};
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_PRIMARY_H*/
diff --git a/qpid/cpp/src/qpid/ha/PrimaryQueueLimits.h b/qpid/cpp/src/qpid/ha/PrimaryQueueLimits.h
new file mode 100644
index 0000000000..6d0c55736a
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/PrimaryQueueLimits.h
@@ -0,0 +1,108 @@
+#ifndef QPID_HA_PRIMARYQUEUELIMITS_H
+#define QPID_HA_PRIMARYQUEUELIMITS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "ReplicationTest.h"
+#include <qpid/broker/Queue.h>
+#include <qpid/broker/QueueRegistry.h>
+#include <qpid/framing/amqp_types.h>
+#include <boost/shared_ptr.hpp>
+#include <boost/bind.hpp>
+#include <string>
+
+namespace qpid {
+namespace broker {
+class Queue;
+}
+
+namespace ha {
+class LogPrefix;
+class RemoteBackup;
+
+/**
+ * Track queue limits on the primary, ensure the primary does not attempt to
+ * replicate more queues than the backups can handle.
+ *
+ * THREAD UNSAFE: Protected by Primary::lock
+ */
+class PrimaryQueueLimits
+{
+ public:
+ // FIXME aconway 2014-01-24: hardcoded maxQueues, use negotiated channel-max
+ PrimaryQueueLimits(const LogPrefix& lp,
+ broker::QueueRegistry& qr,
+ const ReplicationTest& rt
+ ) :
+ logPrefix(lp), maxQueues(framing::CHANNEL_MAX-100), queues(0)
+ {
+ // Get initial count of replicated queues
+ qr.eachQueue(boost::bind(&PrimaryQueueLimits::addQueueIfReplicated, this, _1, rt));
+ }
+
+ /** Add a replicated queue
+ *@exception ResourceLimitExceededException if this would exceed the limit.
+ */
+ void addQueue(const boost::shared_ptr<broker::Queue>& q) {
+ if (queues >= maxQueues) {
+ QPID_LOG(error, logPrefix << "Cannot create replicated queue " << q->getName()
+ << " exceeds limit of " << maxQueues
+ << " replicated queues.");
+ throw framing::ResourceLimitExceededException(
+ Msg() << "Exceeded replicated queue limit " << queues << " >= " << maxQueues);
+ }
+ else ++queues;
+ }
+
+ void addQueueIfReplicated(const boost::shared_ptr<broker::Queue>& q, const ReplicationTest& rt) {
+ if(rt.useLevel(*q)) addQueue(q);
+ }
+
+ /** Remove a replicated queue.
+ * @pre Was previously added with addQueue
+ */
+ void removeQueue(const boost::shared_ptr<broker::Queue>&) {
+ assert(queues != 0);
+ --queues;
+ }
+
+ // TODO aconway 2014-01-24: Currently replication links always use the
+ // hard-coded framing::CHANNEL_MAX. In future (e.g. when we support AMQP1.0
+ // on replication links) we may need to check the actual channel max on each
+ // link and update maxQueues to the smallest value. addBackup and removeBackup
+ // are placeholders for that.
+
+ /** Add a backup */
+ void addBackup(const boost::shared_ptr<RemoteBackup>&) {}
+
+ /** Remove a backup */
+ void removeBackup(const boost::shared_ptr<RemoteBackup>&) {}
+
+ private:
+ const LogPrefix& logPrefix;
+ uint64_t maxQueues;
+ uint64_t queues;
+};
+
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_PRIMARYQUEUELIMITS_H*/
diff --git a/qpid/cpp/src/qpid/ha/PrimaryTxObserver.cpp b/qpid/cpp/src/qpid/ha/PrimaryTxObserver.cpp
new file mode 100644
index 0000000000..56815ef89d
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/PrimaryTxObserver.cpp
@@ -0,0 +1,307 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "Event.h"
+#include "HaBroker.h"
+#include "Primary.h"
+#include "PrimaryTxObserver.h"
+#include "QueueGuard.h"
+#include "RemoteBackup.h"
+#include "ReplicatingSubscription.h"
+
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/framing/reply_exceptions.h"
+#include <boost/lexical_cast.hpp>
+#include <algorithm>
+
+namespace qpid {
+namespace framing {
+class FieldTable;
+}
+namespace ha {
+
+using namespace std;
+using namespace sys;
+using namespace broker;
+using namespace framing;
+using types::Uuid;
+
+// Exchange to receive prepare OK events.
+class PrimaryTxObserver::Exchange : public broker::Exchange {
+ public:
+ Exchange(const boost::shared_ptr<PrimaryTxObserver>& tx_) :
+ broker::Exchange(tx_->getExchangeName()),
+ tx(tx_)
+ {
+ args.setString(QPID_REPLICATE, printable(NONE).str()); // Set replication arg.
+ dispatch[TxPrepareOkEvent::KEY] =
+ boost::bind(&PrimaryTxObserver::txPrepareOkEvent, tx, _1);
+ dispatch[TxPrepareFailEvent::KEY] =
+ boost::bind(&PrimaryTxObserver::txPrepareFailEvent, tx, _1);
+ }
+
+ void route(Deliverable& deliverable) {
+ const broker::Message& message(deliverable.getMessage());
+ DispatchMap::iterator i = dispatch.find(message.getRoutingKey());
+ if (i != dispatch.end()) i->second(message.getContent());
+ }
+
+ bool bind(boost::shared_ptr<Queue>, const string&, const FieldTable*) { return false; }
+ bool unbind(boost::shared_ptr<Queue>, const string&, const FieldTable*) { return false; }
+ bool isBound(boost::shared_ptr<Queue>, const string* const, const FieldTable* const) { return false; }
+ bool hasBindings() { return false; }
+ string getType() const { return TYPE_NAME; }
+
+ private:
+ static const string TYPE_NAME;
+ typedef boost::function<void(const std::string&)> DispatchFn;
+ typedef unordered_map<std::string, DispatchFn> DispatchMap;
+
+ DispatchMap dispatch;
+ boost::shared_ptr<PrimaryTxObserver> tx;
+};
+
+const string PrimaryTxObserver::Exchange::TYPE_NAME(string(QPID_HA_PREFIX)+"primary-tx-observer");
+
+boost::shared_ptr<PrimaryTxObserver> PrimaryTxObserver::create(
+ Primary& p, HaBroker& hb, const boost::intrusive_ptr<broker::TxBuffer>& tx) {
+ boost::shared_ptr<PrimaryTxObserver> pto(new PrimaryTxObserver(p, hb, tx));
+ pto->initialize();
+ return pto;
+}
+
+
+PrimaryTxObserver::PrimaryTxObserver(
+ Primary& p, HaBroker& hb, const boost::intrusive_ptr<broker::TxBuffer>& tx
+) :
+ state(SENDING),
+ logPrefix(hb.logPrefix),
+ primary(p), haBroker(hb), broker(hb.getBroker()),
+ replicationTest(hb.getSettings().replicateDefault.get()),
+ txBuffer(tx),
+ id(true),
+ exchangeName(TRANSACTION_REPLICATOR_PREFIX+id.str()),
+ empty(true)
+{
+ logPrefix = "Primary TX "+shortStr(id)+": ";
+
+ // The brokers known at this point are the ones that will be included
+ // in the transaction. Brokers that join later are not included.
+ //
+ BrokerInfo::Set backups_(haBroker.getMembership().otherBackups());
+ std::transform(backups_.begin(), backups_.end(), inserter(backups, backups.begin()),
+ boost::bind(&BrokerInfo::getSystemId, _1));
+
+ // Delay completion of TX untill all backups have responded to prepare.
+ incomplete = backups;
+ for (size_t i = 0; i < incomplete.size(); ++i)
+ txBuffer->startCompleter();
+
+ QPID_LOG(debug, logPrefix << "Started, backups " << backups);
+}
+
+void PrimaryTxObserver::initialize() {
+ boost::shared_ptr<Exchange> ex(new Exchange(shared_from_this()));
+ broker.getExchanges().registerExchange(ex);
+ pair<QueuePtr, bool> result =
+ broker.createQueue(
+ exchangeName,
+ QueueSettings(/*durable*/false, /*autodelete*/true),
+ 0, // no owner regardless of exclusivity on primary
+ string(), // No alternate exchange
+ haBroker.getUserId(),
+ string()); // Remote host.
+ if (!result.second)
+ throw InvalidArgumentException(
+ QPID_MSG(logPrefix << "TX replication queue already exists."));
+ txQueue = result.first;
+ txQueue->markInUse(); // Prevent auto-delete till we are done.
+ txQueue->deliver(TxBackupsEvent(backups).message());
+}
+
+
+PrimaryTxObserver::~PrimaryTxObserver() {}
+
+void PrimaryTxObserver::checkState(State expect, const std::string& msg) {
+ if (state != expect)
+ throw IllegalStateException(QPID_MSG(logPrefix << "Illegal state: " << msg));
+}
+
+void PrimaryTxObserver::enqueue(const QueuePtr& q, const broker::Message& m)
+{
+ Mutex::ScopedLock l(lock);
+ if (replicationTest.useLevel(*q) == ALL) { // Ignore unreplicated queues.
+ QPID_LOG(trace, logPrefix << "Enqueue: " << logMessageId(*q, m.getReplicationId()));
+ checkState(SENDING, "Too late for enqueue");
+ empty = false;
+ enqueues[q] += m.getReplicationId();
+ txQueue->deliver(TxEnqueueEvent(q->getName(), m.getReplicationId()).message());
+ txQueue->deliver(m);
+ }
+}
+
+void PrimaryTxObserver::dequeue(
+ const QueuePtr& q, QueuePosition pos, ReplicationId id)
+{
+ Mutex::ScopedLock l(lock);
+ checkState(SENDING, "Too late for dequeue");
+ if (replicationTest.useLevel(*q) == ALL) { // Ignore unreplicated queues.
+ QPID_LOG(trace, logPrefix << "Dequeue: " << logMessageId(*q, pos, id));
+ empty = false;
+ dequeues[q] += id;
+ txQueue->deliver(TxDequeueEvent(q->getName(), id).message());
+ }
+}
+
+namespace {
+struct Skip {
+ Uuid backup;
+ boost::shared_ptr<broker::Queue> queue;
+ ReplicationIdSet ids;
+
+ Skip(const Uuid& backup_,
+ const boost::shared_ptr<broker::Queue>& queue_,
+ const ReplicationIdSet& ids_) :
+ backup(backup_), queue(queue_), ids(ids_) {}
+
+ void skipEnqueues(Primary& p) const { p.skipEnqueues(backup, queue, ids); }
+ void skipDequeues(Primary& p) const { p.skipDequeues(backup, queue, ids); }
+};
+} // namespace
+
+void PrimaryTxObserver::skip(Mutex::ScopedLock&) {
+ // Tell replicating subscriptions to skip IDs in the transaction.
+ vector<Skip> skipEnq, skipDeq;
+ for (UuidSet::iterator b = backups.begin(); b != backups.end(); ++b) {
+ for (QueueIdsMap::iterator q = enqueues.begin(); q != enqueues.end(); ++q)
+ skipEnq.push_back(Skip(*b, q->first, q->second));
+ for (QueueIdsMap::iterator q = dequeues.begin(); q != dequeues.end(); ++q)
+ skipDeq.push_back(Skip(*b, q->first, q->second));
+ }
+ Mutex::ScopedUnlock u(lock); // Outside lock
+ for_each(skipEnq.begin(), skipEnq.end(), boost::bind(&Skip::skipEnqueues, _1, boost::ref(primary)));
+ for_each(skipDeq.begin(), skipDeq.end(), boost::bind(&Skip::skipDequeues, _1, boost::ref(primary)));
+}
+
+bool PrimaryTxObserver::prepare() {
+ QPID_LOG(debug, logPrefix << "Prepare " << backups);
+ Mutex::ScopedLock l(lock);
+ checkState(SENDING, "Too late for prepare");
+ state = PREPARING;
+ skip(l); // Tell local replicating subscriptions to skip tx enqueue/dequeue.
+ txQueue->deliver(TxPrepareEvent().message());
+ return true;
+}
+
+void PrimaryTxObserver::commit() {
+ QPID_LOG(debug, logPrefix << "Commit");
+ Mutex::ScopedLock l(lock);
+ checkState(PREPARING, "Cannot commit, not preparing");
+ if (incomplete.size() == 0) {
+ txQueue->deliver(TxCommitEvent().message());
+ end(l);
+ } else {
+ txQueue->deliver(TxRollbackEvent().message());
+ end(l);
+ throw PreconditionFailedException(
+ QPID_MSG(logPrefix << "Cannot commit, " << incomplete.size()
+ << " incomplete backups"));
+ }
+}
+
+void PrimaryTxObserver::rollback() {
+ Mutex::ScopedLock l(lock);
+ // Don't bleat about rolling back empty transactions, this happens all the time
+ // when a session closes and rolls back its outstanding transaction.
+ if (!empty) QPID_LOG(debug, logPrefix << "Rollback");
+ if (state != ENDED) {
+ txQueue->deliver(TxRollbackEvent().message());
+ end(l);
+ }
+}
+
+void PrimaryTxObserver::end(Mutex::ScopedLock&) {
+ if (state == ENDED) return;
+ state = ENDED;
+ // If there are no outstanding completions, break pointer cycle here.
+ // Otherwise break it in cancel() when the remaining completions are done.
+ if (incomplete.empty()) txBuffer = 0;
+ txQueue->releaseFromUse(); // txQueue will auto-delete
+ txQueue->scheduleAutoDelete();
+ txQueue.reset();
+ try {
+ broker.getExchanges().destroy(getExchangeName());
+ } catch (const std::exception& e) {
+ QPID_LOG(error, logPrefix << "Deleting TX exchange: " << e.what());
+ }
+}
+
+bool PrimaryTxObserver::completed(const Uuid& id, Mutex::ScopedLock&) {
+ if (incomplete.erase(id)) {
+ txBuffer->finishCompleter();
+ return true;
+ }
+ return false;
+}
+
+bool PrimaryTxObserver::error(const Uuid& id, const std::string& msg, Mutex::ScopedLock& l)
+{
+ if (incomplete.find(id) != incomplete.end()) {
+ // Note: setError before completed since completed may trigger completion.
+ // Only use the TX part of the log prefix.
+ txBuffer->setError(Msg() << logPrefix.get() << msg << shortStr(id) << ".");
+ completed(id, l);
+ return true;
+ }
+ return false;
+}
+
+void PrimaryTxObserver::txPrepareOkEvent(const string& data) {
+ Mutex::ScopedLock l(lock);
+ types::Uuid backup = decodeStr<TxPrepareOkEvent>(data).broker;
+ if (completed(backup, l)) {
+ QPID_LOG(debug, logPrefix << "Backup prepared ok: " << backup);
+ } else {
+ QPID_LOG(error, logPrefix << "Unexpected prepare-ok response from " << backup);
+ }
+}
+
+void PrimaryTxObserver::txPrepareFailEvent(const string& data) {
+ Mutex::ScopedLock l(lock);
+ types::Uuid backup = decodeStr<TxPrepareFailEvent>(data).broker;
+ if (error(backup, "Prepare failed on backup ", l)) {
+ QPID_LOG(error, logPrefix << "Prepare failed on backup " << backup);
+ } else {
+ QPID_LOG(error, logPrefix << "Unexpected prepare-fail response from " << backup);
+ }
+}
+
+void PrimaryTxObserver::cancel(const ReplicatingSubscription& rs) {
+ Mutex::ScopedLock l(lock);
+ types::Uuid backup = rs.getBrokerInfo().getSystemId();
+ // Normally the backup should be completed before it is cancelled.
+ if (completed(backup, l)) error(backup, "Unexpected disconnect:", l);
+ // Break the pointer cycle if backups have completed and we are done with txBuffer.
+ if (state == ENDED && incomplete.empty()) txBuffer = 0;
+}
+
+}} // namespace qpid::ha
diff --git a/qpid/cpp/src/qpid/ha/PrimaryTxObserver.h b/qpid/cpp/src/qpid/ha/PrimaryTxObserver.h
new file mode 100644
index 0000000000..6f445ee212
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/PrimaryTxObserver.h
@@ -0,0 +1,133 @@
+#ifndef QPID_HA_PRIMARYTXOBSERVER_H
+#define QPID_HA_PRIMARYTXOBSERVER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "types.h"
+#include "ReplicationTest.h"
+#include "LogPrefix.h"
+#include "qpid/broker/SessionState.h"
+#include "qpid/broker/TransactionObserver.h"
+#include "qpid/log/Statement.h"
+#include "qpid/types/Uuid.h"
+#include "qpid/sys/unordered_map.h"
+#include "qpid/sys/Monitor.h"
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/intrusive_ptr.hpp>
+
+namespace qpid {
+
+namespace broker {
+class Broker;
+class Message;
+class Consumer;
+class AsyncCompletion;
+}
+
+namespace ha {
+class HaBroker;
+class ReplicatingSubscription;
+class Primary;
+
+/**
+ * Observe events in the lifecycle of a transaction.
+ *
+ * The observer is called by TxBuffer for each transactional event.
+ * It puts the events on a special tx-queue.
+ * A TxReplicator on the backup replicates the tx-queue and creates
+ * a TxBuffer on the backup equivalent to the one on the primary.
+ *
+ * Creates an exchange to receive prepare-ok/prepare-fail messages from backups.
+ *
+ * Monitors for tx-queue subscription cancellations.
+ *
+ * THREAD SAFE: called in user connection thread for TX events,
+ * and in backup connection threads for prepare-completed events
+ * and unsubscriptions.
+ */
+class PrimaryTxObserver : public broker::TransactionObserver,
+ public boost::enable_shared_from_this<PrimaryTxObserver>
+{
+ public:
+ static boost::shared_ptr<PrimaryTxObserver> create(
+ Primary&, HaBroker&, const boost::intrusive_ptr<broker::TxBuffer>&);
+
+ ~PrimaryTxObserver();
+
+ void enqueue(const QueuePtr&, const broker::Message&);
+ void dequeue(const QueuePtr& queue, QueuePosition, ReplicationId);
+ bool prepare();
+ void commit();
+ void rollback();
+
+ types::Uuid getId() const { return id; }
+ QueuePtr getTxQueue() const { return txQueue; }
+ std::string getExchangeName() const { return exchangeName; }
+
+ // Notify that a backup subscription has been cancelled.
+ void cancel(const ReplicatingSubscription&);
+
+ private:
+ class Exchange;
+ typedef qpid::sys::unordered_map<
+ QueuePtr, ReplicationIdSet, Hasher<QueuePtr> > QueueIdsMap;
+
+ enum State {
+ SENDING, ///< Sending TX messages and acks
+ PREPARING, ///< Prepare sent, waiting for response
+ ENDED ///< Commit or rollback sent, local transaction ended.
+ };
+
+ PrimaryTxObserver(Primary&, HaBroker&, const boost::intrusive_ptr<broker::TxBuffer>&);
+ void initialize();
+
+ void skip(sys::Mutex::ScopedLock&);
+ void checkState(State expect, const std::string& msg);
+ void end(sys::Mutex::ScopedLock&);
+ void txPrepareOkEvent(const std::string& data);
+ void txPrepareFailEvent(const std::string& data);
+ bool completed(const types::Uuid& id, sys::Mutex::ScopedLock&);
+ bool error(const types::Uuid& id, const std::string& msg, sys::Mutex::ScopedLock& l);
+
+ sys::Monitor lock;
+ State state;
+ LogPrefix2 logPrefix;
+ Primary& primary;
+ HaBroker& haBroker;
+ broker::Broker& broker;
+ ReplicationTest replicationTest;
+ // NOTE: There is an intrusive_ptr cycle between PrimaryTxObserver
+ // and TxBuffer. The cycle is broken in PrimaryTxObserver::end()
+ boost::intrusive_ptr<broker::TxBuffer> txBuffer;
+
+ types::Uuid id;
+ std::string exchangeName;
+ QueuePtr txQueue;
+ QueueIdsMap enqueues, dequeues;
+ UuidSet backups; // All backups of transaction.
+ UuidSet incomplete; // Incomplete backups (not yet responded to prepare)
+ bool empty; // True if the transaction is empty - no enqueues/dequeues.
+};
+
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_PRIMARYTXOBSERVER_H*/
diff --git a/qpid/cpp/src/qpid/ha/QueueGuard.cpp b/qpid/cpp/src/qpid/ha/QueueGuard.cpp
new file mode 100644
index 0000000000..b6b6037b6f
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/QueueGuard.cpp
@@ -0,0 +1,123 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "QueueGuard.h"
+#include "BrokerInfo.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/QueuedMessage.h"
+#include "qpid/broker/QueueObserver.h"
+#include "qpid/log/Statement.h"
+#include <boost/bind.hpp>
+#include <sstream>
+
+namespace qpid {
+namespace ha {
+
+using namespace broker;
+using sys::Mutex;
+
+class QueueGuard::QueueObserver : public broker::QueueObserver
+{
+ public:
+ QueueObserver(QueueGuard& g) : guard(g) {}
+ void enqueued(const broker::Message& m) { guard.enqueued(m); }
+ void dequeued(const broker::Message& m) { guard.dequeued(m); }
+ void acquired(const broker::Message&) {}
+ void requeued(const broker::Message&) {}
+ private:
+ QueueGuard& guard;
+};
+
+
+
+QueueGuard::QueueGuard(broker::Queue& q, const BrokerInfo& info, const LogPrefix& lp)
+ : cancelled(false), logPrefix(lp), queue(q)
+{
+ std::ostringstream os;
+ os << "Guard of " << queue.getName() << " at ";
+ info.printId(os) << ": ";
+ logPrefix = os.str();
+ observer.reset(new QueueObserver(*this));
+ queue.getObservers().add(observer);
+ // Set first after adding the observer so we know that the back of the
+ // queue+1 is (or will be) a guarded position.
+ QueuePosition front, back;
+ q.getRange(front, back, broker::REPLICATOR);
+ first = back + 1;
+ QPID_LOG(debug, logPrefix << "Guarded: front " << front
+ << ", back " << back
+ << ", guarded " << first);
+}
+
+QueueGuard::~QueueGuard() { cancel(); }
+
+// NOTE: Called with message lock held.
+void QueueGuard::enqueued(const Message& m) {
+ // Delay completion
+ ReplicationId id = m.getReplicationId();
+ Mutex::ScopedLock l(lock);
+ if (cancelled) return; // Don't record enqueues after we are cancelled.
+ QPID_LOG(trace, logPrefix << "Delayed completion of " << logMessageId(queue, m));
+ delayed[id] = m.getIngressCompletion();
+ m.getIngressCompletion()->startCompleter();
+}
+
+// NOTE: Called with message lock held.
+void QueueGuard::dequeued(const Message& m) {
+ ReplicationId id = m.getReplicationId();
+ QPID_LOG(trace, logPrefix << "Dequeued " << logMessageId(queue, m));
+ Mutex::ScopedLock l(lock);
+ complete(id, l);
+}
+
+void QueueGuard::cancel() {
+ queue.getObservers().remove(observer);
+ Mutex::ScopedLock l(lock);
+ if (cancelled) return;
+ QPID_LOG(debug, logPrefix << "Cancelled");
+ cancelled = true;
+ while (!delayed.empty()) complete(delayed.begin(), l);
+}
+
+bool QueueGuard::complete(ReplicationId id) {
+ Mutex::ScopedLock l(lock);
+ return complete(id, l);
+}
+
+bool QueueGuard::complete(ReplicationId id, Mutex::ScopedLock& l) {
+ // The same message can be completed twice, by
+ // ReplicatingSubscription::acknowledged and dequeued. Remove it
+ // from the map so we only call finishCompleter() once
+ Delayed::iterator i = delayed.find(id);
+ if (i != delayed.end()) {
+ complete(i, l);
+ return true;
+ }
+ return false;
+}
+
+void QueueGuard::complete(Delayed::iterator i, Mutex::ScopedLock&) {
+ QPID_LOG(trace, logPrefix << "Completed " << queue.getName() << " =" << i->first);
+ i->second->finishCompleter();
+ delayed.erase(i);
+}
+
+
+}} // namespaces qpid::ha
diff --git a/qpid/cpp/src/qpid/ha/QueueGuard.h b/qpid/cpp/src/qpid/ha/QueueGuard.h
new file mode 100644
index 0000000000..9bf61f31da
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/QueueGuard.h
@@ -0,0 +1,108 @@
+#ifndef QPID_HA_QUEUEGUARD_H
+#define QPID_HA_QUEUEGUARD_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "types.h"
+#include "hash.h"
+#include "LogPrefix.h"
+#include "qpid/types/Uuid.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/unordered_map.h"
+#include <boost/shared_ptr.hpp>
+#include <boost/intrusive_ptr.hpp>
+#include <deque>
+#include <set>
+
+namespace qpid {
+namespace broker {
+class Queue;
+struct QueuedMessage;
+class Message;
+class AsyncCompletion;
+}
+
+namespace ha {
+class BrokerInfo;
+class ReplicatingSubscription;
+
+/**
+ * A queue guard is a QueueObserver that delays completion of new messages
+ * arriving on a queue. It works as part of a ReplicatingSubscription to ensure
+ * messages are not acknowledged till they have been replicated.
+ *
+ * The guard can be created before the ReplicatingSubscription to protect
+ * messages arriving before the creation of the subscription.
+ *
+ * THREAD SAFE: Concurrent calls:
+ * - enqueued() via QueueObserver in arbitrary connection threads.
+ * - cancel(), complete() from ReplicatingSubscription in subscription thread.
+ *
+ */
+class QueueGuard {
+ public:
+ QueueGuard(broker::Queue& q, const BrokerInfo&, const LogPrefix&);
+ ~QueueGuard();
+
+ /** QueueObserver override. Delay completion of the message.
+ * NOTE: Called under the queues message lock.
+ */
+ void enqueued(const broker::Message&);
+
+ /** QueueObserver override: Complete a delayed message.
+ * NOTE: Called under the queues message lock.
+ */
+ void dequeued(const broker::Message&);
+
+ /** Complete a delayed message.
+ *@return true if the ID was delayed
+ */
+ bool complete(ReplicationId);
+
+ /** Complete all delayed messages. */
+ void cancel();
+
+ /** Return the first known guarded position on the queue. It is possible
+ * that the guard has seen a few messages before this point.
+ */
+ QueuePosition getFirst() const { return first; } // Thread safe: Immutable.
+
+ private:
+ class QueueObserver;
+ typedef qpid::sys::unordered_map<ReplicationId,
+ boost::intrusive_ptr<broker::AsyncCompletion>,
+ Hasher<ReplicationId> > Delayed;
+
+ bool complete(ReplicationId, sys::Mutex::ScopedLock &);
+ void complete(Delayed::iterator, sys::Mutex::ScopedLock &);
+
+ sys::Mutex lock;
+ QueuePosition first;
+ bool cancelled;
+ LogPrefix2 logPrefix;
+ broker::Queue& queue;
+ Delayed delayed;
+ boost::shared_ptr<QueueObserver> observer;
+};
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_QUEUEGUARD_H*/
diff --git a/qpid/cpp/src/qpid/ha/QueueReplicator.cpp b/qpid/cpp/src/qpid/ha/QueueReplicator.cpp
new file mode 100644
index 0000000000..7997bc6aa9
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/QueueReplicator.cpp
@@ -0,0 +1,410 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "Event.h"
+#include "HaBroker.h"
+#include "IdSetter.h"
+#include "LogPrefix.h"
+#include "QueueReplicator.h"
+#include "QueueSnapshot.h"
+#include "ReplicatingSubscription.h"
+#include "Settings.h"
+#include "types.h"
+#include "qpid/broker/Bridge.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/Link.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/QueueObserver.h"
+#include "qpid/broker/QueueRegistry.h"
+#include "qpid/broker/SessionHandler.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/log/Statement.h"
+#include "qpid/Msg.h"
+#include "qpid/assert.h"
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+#include <boost/bind.hpp>
+
+
+namespace qpid {
+namespace ha {
+using namespace broker;
+using namespace framing;
+using namespace framing::execution;
+using namespace std;
+using std::exception;
+using sys::Mutex;
+using boost::shared_ptr;
+
+const std::string QueueReplicator::QPID_SYNC_FREQUENCY("qpid.sync_frequency");
+
+std::string QueueReplicator::replicatorName(const std::string& queueName) {
+ return QUEUE_REPLICATOR_PREFIX + queueName;
+}
+
+bool QueueReplicator::isReplicatorName(const std::string& name) {
+ return startsWith(name, QUEUE_REPLICATOR_PREFIX);
+}
+
+namespace {
+void pushIfQr(QueueReplicator::Vector& v, const shared_ptr<Exchange>& ex) {
+ shared_ptr<QueueReplicator> qr = boost::dynamic_pointer_cast<QueueReplicator>(ex);
+ if (qr) v.push_back(qr);
+}
+}
+
+void QueueReplicator::copy(ExchangeRegistry& registry, Vector& result) {
+ registry.eachExchange(boost::bind(&pushIfQr, boost::ref(result), _1));
+}
+
+// Debug log expected exceptions on queue replicator, check incoming execution
+// exceptions for "deleted on primary" conditions.
+class QueueReplicator::ErrorListener : public SessionHandler::ErrorListener {
+ public:
+ ErrorListener(const boost::shared_ptr<QueueReplicator>& qr)
+ : queueReplicator(qr), logPrefix(qr->logPrefix) {}
+
+ void connectionException(framing::connection::CloseCode code, const std::string& msg) {
+ QPID_LOG(debug, logPrefix << framing::createConnectionException(code, msg).what());
+ }
+ void channelException(framing::session::DetachCode code, const std::string& msg) {
+ QPID_LOG(debug, logPrefix << framing::createChannelException(code, msg).what());
+ }
+ void executionException(framing::execution::ErrorCode code, const std::string& msg) {
+ QPID_LOG(debug, logPrefix << framing::createSessionException(code, msg).what());
+ }
+ void incomingExecutionException(ErrorCode code, const std::string& msg) {
+ boost::shared_ptr<QueueReplicator> qr = queueReplicator.lock();
+ if (qr && !qr->deletedOnPrimary(code, msg))
+ QPID_LOG(error, logPrefix << "Incoming "
+ << framing::createSessionException(code, msg).what());
+ }
+ void detach() {}
+
+ private:
+ boost::weak_ptr<QueueReplicator> queueReplicator;
+ const LogPrefix& logPrefix;
+};
+
+class QueueReplicator::QueueObserver : public broker::QueueObserver {
+ public:
+ typedef boost::shared_ptr<QueueReplicator> Ptr;
+ QueueObserver(Ptr qr) : queueReplicator(qr) {}
+
+ void enqueued(const Message& m) {
+ Ptr qr = queueReplicator.lock();
+ if (qr) qr->enqueued(m);
+ }
+
+ void dequeued(const Message& m) {
+ Ptr qr = queueReplicator.lock();
+ if (qr) qr->dequeued(m);
+ }
+
+ void acquired(const Message&) {}
+ void requeued(const Message&) {}
+ void consumerAdded( const Consumer& ) {}
+ void consumerRemoved( const Consumer& ) {}
+ // Queue observer is destroyed when the queue is.
+ void destroy() {
+ Ptr qr = queueReplicator.lock();
+ if (qr) qr->destroy();
+ }
+
+ private:
+ boost::weak_ptr<QueueReplicator> queueReplicator;
+};
+
+
+boost::shared_ptr<QueueReplicator> QueueReplicator::create(
+ HaBroker& hb, boost::shared_ptr<broker::Queue> q, boost::shared_ptr<broker::Link> l)
+{
+ boost::shared_ptr<QueueReplicator> qr(new QueueReplicator(hb, q, l));
+ qr->initialize();
+ return qr;
+}
+
+QueueReplicator::QueueReplicator(HaBroker& hb,
+ boost::shared_ptr<Queue> q,
+ boost::shared_ptr<Link> l)
+ : Exchange(replicatorName(q->getName()), 0, q->getBroker()),
+ haBroker(hb),
+ brokerInfo(hb.getBrokerInfo()),
+ link(l),
+ queue(q),
+ sessionHandler(0),
+ logPrefix(hb.logPrefix, "Backup of "+q->getName()+": "),
+ subscribed(false),
+ settings(hb.getSettings()),
+ nextId(0), maxId(0)
+{
+ QPID_LOG(debug, logPrefix << "Created");
+ // The QueueReplicator will take over setting replication IDs.
+ boost::shared_ptr<IdSetter> setter =
+ q->getMessageInterceptors().findType<IdSetter>();
+ if (setter) q->getMessageInterceptors().remove(setter);
+
+ args.setString(QPID_REPLICATE, printable(NONE).str());
+ Uuid uuid(true);
+ bridgeName = replicatorName(q->getName()) + std::string(".") + uuid.str();
+ framing::FieldTable args = getArgs();
+ args.setString(QPID_REPLICATE, printable(NONE).str());
+ setArgs(args);
+ // Don't allow backup queues to auto-delete, primary decides when to delete.
+ if (q->isAutoDelete()) q->markInUse(false);
+
+ dispatch[DequeueEvent::KEY] =
+ boost::bind(&QueueReplicator::dequeueEvent, this, _1, _2);
+ dispatch[IdEvent::KEY] =
+ boost::bind(&QueueReplicator::idEvent, this, _1, _2);
+}
+
+QueueReplicator::~QueueReplicator() {}
+
+void QueueReplicator::initialize() {
+ Mutex::ScopedLock l(lock);
+ if (!queue) return; // Already destroyed
+
+ // Enable callback to route()
+ if (!getBroker()->getExchanges().registerExchange(shared_from_this()))
+ throw Exception(QPID_MSG("Duplicate queue replicator " << getName()));
+
+ // Enable callback to initializeBridge
+ boost::shared_ptr<Bridge> b = queue->getBroker()->getLinks().declare(
+ bridgeName,
+ *link,
+ false, // durable
+ queue->getName(), // src
+ getName(), // dest
+ "", // key
+ false, // isQueue
+ false, // isLocal
+ "", // id/tag
+ "", // excludes
+ false, // dynamic
+ 0, // sync?
+ LinkRegistry::INFINITE_CREDIT,
+ // Include shared_ptr to self to ensure we are not deleted
+ // before initializeBridge is called.
+ boost::bind(&QueueReplicator::initializeBridge, shared_from_this(), _1, _2)
+ ).first;
+ b->setErrorListener(
+ boost::shared_ptr<ErrorListener>(new ErrorListener(shared_from_this())));
+ bridge = b; // bridge is a weak_ptr to avoid a cycle.
+
+ // Enable callback to destroy()
+ queue->getObservers().add(
+ boost::shared_ptr<QueueObserver>(new QueueObserver(shared_from_this())));
+}
+
+void QueueReplicator::disconnect() {
+ Mutex::ScopedLock l(lock);
+ sessionHandler = 0;
+}
+
+// Called from Queue::destroyed()
+void QueueReplicator::destroy() {
+ QPID_LOG(debug, logPrefix << "Destroyed");
+ boost::shared_ptr<Bridge> bridge2; // To call outside of lock
+ {
+ Mutex::ScopedLock l(lock);
+ if (!queue) return; // Already destroyed
+ bridge2 = bridge.lock(); // !call close outside the lock.
+ destroy(l);
+ }
+ if (bridge2) bridge2->close(); // Outside of lock, avoid deadlock.
+}
+
+void QueueReplicator::destroy(Mutex::ScopedLock&) {
+ // Need to drop shared pointers to avoid pointer cycles keeping this in memory.
+ queue.reset();
+ bridge.reset();
+ getBroker()->getExchanges().destroy(getName());
+}
+
+
+// Called in a broker connection thread when the bridge is created.
+// Note: called with the Link lock held.
+void QueueReplicator::initializeBridge(Bridge& bridge, SessionHandler& sessionHandler_) {
+ Mutex::ScopedLock l(lock);
+ if (!queue) return; // Already destroyed
+ sessionHandler = &sessionHandler_;
+ AMQP_ServerProxy peer(sessionHandler->out);
+ const qmf::org::apache::qpid::broker::ArgsLinkBridge& args(bridge.getArgs());
+ FieldTable arguments;
+ arguments.setString(ReplicatingSubscription::QPID_REPLICATING_SUBSCRIPTION, getType());
+ arguments.setInt(QPID_SYNC_FREQUENCY, 1); // TODO aconway 2012-05-22: optimize?
+ arguments.setTable(ReplicatingSubscription::QPID_BROKER_INFO, brokerInfo.asFieldTable());
+ boost::shared_ptr<QueueSnapshot> qs = queue->getObservers().findType<QueueSnapshot>();
+ ReplicationIdSet snapshot;
+ if (qs) {
+ snapshot = qs->getSnapshot();
+ arguments.set(
+ ReplicatingSubscription::QPID_ID_SET,
+ FieldTable::ValuePtr(new Var32Value(encodeStr(snapshot), TYPE_CODE_VBIN32)));
+ }
+ try {
+ peer.getMessage().subscribe(
+ args.i_src, args.i_dest, 0/*accept-explicit*/, 1/*not-acquired*/,
+ false/*exclusive*/, "", 0, arguments);
+ peer.getMessage().setFlowMode(getName(), 1); // Window
+ peer.getMessage().flow(getName(), 0, settings.getFlowMessages());
+ peer.getMessage().flow(getName(), 1, settings.getFlowBytes());
+ }
+ catch(const exception& e) {
+ QPID_LOG(error, logPrefix << "Cannot connect to primary: " << e.what());
+ throw;
+ }
+ qpid::Address primary;
+ link->getRemoteAddress(primary);
+ QPID_LOG(debug, logPrefix << "Connected to " << primary << " snapshot=" << snapshot << " bridge=" << bridgeName);
+ QPID_LOG(trace, logPrefix << "Subscription arguments: " << arguments);
+}
+
+namespace {
+template <class T> T decodeContent(Message& m) {
+ std::string content = m.getContent();
+ Buffer buffer(const_cast<char*>(content.c_str()), content.size());
+ T result;
+ result.decode(buffer);
+ return result;
+}
+}
+
+void QueueReplicator::dequeueEvent(const string& data, Mutex::ScopedLock&) {
+ DequeueEvent e;
+ decodeStr(data, e);
+ QPID_LOG(trace, logPrefix << "Dequeue " << e.ids);
+ //TODO: should be able to optimise the following
+ for (ReplicationIdSet::iterator i = e.ids.begin(); i != e.ids.end(); ++i) {
+ QueuePosition position;
+ {
+ Mutex::ScopedLock l(lock);
+ PositionMap::iterator j = positions.find(*i);
+ if (j == positions.end()) continue;
+ position = j->second;
+ }
+ queue->dequeueMessageAt(position); // Outside lock, will call dequeued().
+ // positions will be cleaned up in dequeued()
+ }
+}
+
+// Called in connection thread of the queues bridge to primary.
+void QueueReplicator::route(Deliverable& deliverable)
+{
+ try {
+ broker::Message& message(deliverable.getMessage());
+ {
+ Mutex::ScopedLock l(lock);
+ if (!queue) return; // Already destroyed
+ string key(message.getRoutingKey());
+ if (isEventKey(key)) {
+ DispatchMap::iterator i = dispatch.find(key);
+ if (i == dispatch.end()) {
+ QPID_LOG(info, logPrefix << "Ignoring unknown event: " << key);
+ } else {
+ (i->second)(message.getContent(), l);
+ }
+ return;
+ }
+ ReplicationId id = nextId++;
+ message.setReplicationId(id);
+ PositionMap::iterator i = positions.find(id);
+ if (i != positions.end()) {
+ QPID_LOG(trace, logPrefix << "Already on queue: " << logMessageId(*queue, message));
+ return;
+ }
+ QPID_LOG(trace, logPrefix << "Received: " << logMessageId(*queue, message));
+ }
+ deliver(message); // Outside lock, will call enqueued()
+ }
+ catch (const std::exception& e) {
+ haBroker.shutdown(QPID_MSG(logPrefix << "Replication failed: " << e.what()));
+ }
+
+}
+
+void QueueReplicator::deliver(const broker::Message& m) {
+ queue->deliver(m);
+}
+
+// Called via QueueObserver when message is enqueued. Could be as part of deliver()
+// or in a different thread if a message is enqueued via a transaction.
+//
+void QueueReplicator::enqueued(const broker::Message& m) {
+ Mutex::ScopedLock l(lock);
+ maxId = std::max(maxId, ReplicationId(m.getReplicationId()));
+ positions[m.getReplicationId()] = m.getSequence();
+ QPID_LOG(trace, logPrefix << "Enqueued " << logMessageId(*queue, m));
+}
+
+// Called via QueueObserver
+void QueueReplicator::dequeued(const broker::Message& m) {
+ Mutex::ScopedLock l(lock);
+ positions.erase(m.getReplicationId());
+}
+
+void QueueReplicator::idEvent(const string& data, Mutex::ScopedLock&) {
+ nextId = decodeStr<IdEvent>(data).id;
+}
+
+bool QueueReplicator::deletedOnPrimary(ErrorCode e, const std::string& msg) {
+ if (e == ERROR_CODE_NOT_FOUND || e == ERROR_CODE_RESOURCE_DELETED) {
+ // If the queue is destroyed at the same time we are subscribing, we may
+ // get a not-found or resource-deleted exception before the
+ // BrokerReplicator gets the queue-delete event. Shut down the bridge by
+ // calling destroy(), we can let the BrokerReplicator delete the queue
+ // when the queue-delete arrives.
+ QPID_LOG(debug, logPrefix << "Deleted on primary: "
+ << framing::createSessionException(e, msg).what());
+ destroy();
+ return true;
+ }
+ return false;
+}
+
+// Unused Exchange methods.
+bool QueueReplicator::bind(boost::shared_ptr<Queue>, const std::string&, const FieldTable*) { return false; }
+bool QueueReplicator::unbind(boost::shared_ptr<Queue>, const std::string&, const FieldTable*) { return false; }
+bool QueueReplicator::isBound(boost::shared_ptr<Queue>, const std::string* const, const FieldTable* const) { return false; }
+bool QueueReplicator::hasBindings() { return false; }
+std::string QueueReplicator::getType() const { return ReplicatingSubscription::QPID_QUEUE_REPLICATOR; }
+
+void QueueReplicator::promoted() {
+ if (queue) {
+ // On primary QueueReplicator no longer sets IDs, start an IdSetter.
+ QPID_LOG(debug, logPrefix << "Promoted, first replication-id " << maxId+1)
+ queue->getMessageInterceptors().add(
+ boost::shared_ptr<IdSetter>(new IdSetter(logPrefix, queue->getName(), maxId+1)));
+ // Process auto-deletes
+ if (queue->isAutoDelete()) {
+ // Make a temporary shared_ptr to prevent premature deletion of queue.
+ // Otherwise scheduleAutoDelete can call this->destroy, which resets this->queue
+ // which could delete the queue while it's still running it's destroyed logic.
+ boost::shared_ptr<Queue> q(queue);
+ // See markInUse in ctor: release but don't delete if never used.
+ q->releaseFromUse(false/*controller*/, subscribed/*doDelete*/);
+ }
+ }
+}
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/ha/QueueReplicator.h b/qpid/cpp/src/qpid/ha/QueueReplicator.h
new file mode 100644
index 0000000000..a4b31b6c9a
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/QueueReplicator.h
@@ -0,0 +1,156 @@
+#ifndef QPID_HA_QUEUEREPLICATOR_H
+#define QPID_HA_QUEUEREPLICATOR_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+
+
+#include "BrokerInfo.h"
+#include "LogPrefix.h"
+#include "hash.h"
+#include "qpid/broker/Exchange.h"
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/function.hpp>
+#include <iosfwd>
+
+namespace qpid {
+
+namespace broker {
+class Bridge;
+class Link;
+class Queue;
+class QueueRegistry;
+class SessionHandler;
+class Deliverable;
+class ExchangeRegistry;
+}
+
+namespace ha {
+class HaBroker;
+class Settings;
+
+/**
+ * Exchange created on a backup broker to receive replicated messages and
+ * replication events from a queue on the primary. It subscribes to the primary
+ * queue via a ReplicatingSubscription on the primary by passing special
+ * arguments to the subscribe command.
+ *
+ * It puts replicated messages on the local replica queue and handles dequeue
+ * events by removing local messages.
+ *
+ * THREAD SAFE: Called in different connection threads.
+ */
+class QueueReplicator : public broker::Exchange,
+ public boost::enable_shared_from_this<QueueReplicator>
+{
+ public:
+ static const std::string QPID_SYNC_FREQUENCY;
+ static const std::string REPLICATOR_PREFIX;
+ typedef std::vector<boost::shared_ptr<QueueReplicator> > Vector;
+
+ static std::string replicatorName(const std::string& queueName);
+ static bool isReplicatorName(const std::string&);
+ /*** Copy QueueReplicators from the registry */
+ static void copy(broker::ExchangeRegistry&, Vector& result);
+
+ static boost::shared_ptr<QueueReplicator> create(
+ HaBroker&, boost::shared_ptr<broker::Queue> q, boost::shared_ptr<broker::Link> l);
+
+ ~QueueReplicator();
+
+ void disconnect(); // Called when we are disconnected from the primary.
+
+ virtual std::string getType() const;
+
+ void route(broker::Deliverable&);
+
+ // Called via QueueObserver
+ void enqueued(const broker::Message&);
+ void dequeued(const broker::Message&);
+
+ // Set if the queue has ever been subscribed to, used for auto-delete cleanup.
+ void setSubscribed() { subscribed = true; }
+
+ boost::shared_ptr<broker::Queue> getQueue() const { return queue; }
+
+ // No-op unused Exchange virtual functions.
+ bool bind(boost::shared_ptr<broker::Queue>, const std::string&, const framing::FieldTable*);
+ bool unbind(boost::shared_ptr<broker::Queue>, const std::string&, const framing::FieldTable*);
+ bool isBound(boost::shared_ptr<broker::Queue>, const std::string* const, const framing::FieldTable* const);
+ bool hasBindings();
+
+ void promoted();
+
+ protected:
+ typedef boost::function<void(const std::string&, sys::Mutex::ScopedLock&)> DispatchFn;
+ typedef qpid::sys::unordered_map<std::string, DispatchFn> DispatchMap;
+
+ QueueReplicator(
+ HaBroker&, boost::shared_ptr<broker::Queue>, boost::shared_ptr<broker::Link>);
+
+ void initialize(); // Called as part of create()
+
+ virtual void deliver(const broker::Message&);
+
+ virtual void destroy(); // Called when the queue is destroyed.
+ virtual void destroy(sys::Mutex::ScopedLock&);
+
+ sys::Mutex lock;
+ HaBroker& haBroker;
+ const BrokerInfo brokerInfo;
+ DispatchMap dispatch;
+ boost::shared_ptr<broker::Link> link;
+ boost::weak_ptr<broker::Bridge> bridge;
+ boost::shared_ptr<broker::Queue> queue;
+ broker::SessionHandler* sessionHandler;
+
+ private:
+ typedef qpid::sys::unordered_map<
+ ReplicationId, QueuePosition, Hasher<ReplicationId> > PositionMap;
+ class ErrorListener;
+ class QueueObserver;
+
+ void initializeBridge(broker::Bridge& bridge, broker::SessionHandler& sessionHandler);
+
+ // Dispatch functions
+ void dequeueEvent(const std::string& data, sys::Mutex::ScopedLock&);
+ void idEvent(const std::string& data, sys::Mutex::ScopedLock&);
+
+ bool deletedOnPrimary(framing::execution::ErrorCode e, const std::string& msg);
+
+ LogPrefix2 logPrefix;
+ std::string bridgeName;
+
+ bool subscribed;
+ const Settings& settings;
+ PositionMap positions;
+ ReplicationIdSet idSet; // Set of replicationIds on the queue.
+ ReplicationId nextId; // ID for next message to arrive.
+ ReplicationId maxId; // Max ID used so far.
+
+ friend class ErrorListener;
+};
+
+
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_QUEUEREPLICATOR_H*/
diff --git a/qpid/cpp/src/qpid/ha/QueueSnapshot.h b/qpid/cpp/src/qpid/ha/QueueSnapshot.h
new file mode 100644
index 0000000000..577bd96ef7
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/QueueSnapshot.h
@@ -0,0 +1,68 @@
+#ifndef QPID_HA_IDSETOBSERVER_H
+#define QPID_HA_IDSETOBSERVER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/Message.h"
+#include "qpid/broker/QueueObserver.h"
+#include "qpid/sys/Mutex.h"
+
+namespace qpid {
+namespace ha {
+
+/**
+ * A QueueObserver that maintains a ReplicationIdSet of the ReplicationIds of
+ * the messages on the queue.
+ *
+ * THREAD SAFE: Note that QueueObserver methods are called under the Queues messageLock.
+ *
+ */
+class QueueSnapshot : public broker::QueueObserver
+{
+ public:
+ void enqueued(const broker::Message& m) {
+ sys::Mutex::ScopedLock l(lock);
+ set += m.getReplicationId();
+ }
+
+ void dequeued(const broker::Message& m) {
+ sys::Mutex::ScopedLock l(lock);
+ set -= m.getReplicationId();
+ }
+
+ void acquired(const broker::Message&) {}
+
+ void requeued(const broker::Message&) {}
+
+ ReplicationIdSet getSnapshot() {
+ sys::Mutex::ScopedLock l(lock);
+ return set;
+ }
+
+ private:
+ sys::Mutex lock;
+ ReplicationIdSet set;
+};
+
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_IDSETOBSERVER_H*/
diff --git a/qpid/cpp/src/qpid/ha/README.h b/qpid/cpp/src/qpid/ha/README.h
new file mode 100644
index 0000000000..9e8eda7e0d
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/README.h
@@ -0,0 +1,149 @@
+// This header file provides an overview of the ha module for doxygen.
+
+namespace qpid {
+namespace ha { // Auto-links for names in the ha namespace.
+
+/** \defgroup ha-module
+ \brief High Availability Module
+
+This is a brief overview of how HA replication works, intended as a starting
+point to understand the code. You should _first_ read the HA chapter of the "C++
+broker book" at http://qpid.apache.org/documentation.html for a general
+understanding of the active/passive, hot-standby strategy.
+
+Class names without a prefix are in the qpid::ha namespace. See the
+documentation of individual classes for more detail.
+
+## Overview of HA classes. ##
+
+HaBroker is the main container for HA code, it holds a Role which can be Primary
+or Backup. It is responsible for implementing QMF management functions.
+
+Backup implements the back-up Role. It holds a BrokerReplicator which subscribes
+to management events on the primary e.g. create/delete queue/exchange/binding.
+It uses a ReplicationTest to check if an object is configured for replication.
+For replicated objects it creates corresponding broker::Queue, broker::Exchange
+etc. on the local broker.
+
+For replicated queues the BrokerReplicator also creates a QueueReplicator. The
+QueueReplicator subscribes to the primary queue with arguments to tell the
+primary to use a ReplicatingSubscription. See "Queue Replication" below for
+more on the ReplicatingSubscription/QueueReplicator interaction.
+
+Primary implements the primary Role. For each replicated queue it creates an
+IdSetter to set replication IDs on messages delivered to the queue. Each backup
+that connects is tracked by a RemoteBackup.
+
+For each (queue,backup) pair, the Primary creates a ReplicatingSubscription and
+a QueueGuard. The QueueGuard delays completion of messages delivered to the
+queue until they are replicated to and acknowledged by the backup.
+
+When the primary fails, one of the backups is promoted by an external cluster
+resource manager. The other backups fail-over to the new primary. See "Queue
+Fail Over" below.
+
+## Locating the Primary ##
+
+Clients connect to the cluster via one of:
+- a virtual IP address that is assigned to the primary broker
+- a list of addresses for all brokers.
+
+If the connection fails the client re-tries its address or list of addresses
+till it re-connects. Backup brokers have a ConnectionObserver that rejects
+client connections by aborting the connection, causing the client to re-try.
+
+## Message Identifiers ##
+
+Replication IDs are sequence numbers assigned to a message *before* it is
+enqueued. We use the IDs to:
+- identify messages to dequeue on a backup.
+- remove extra messages from backup on failover.
+- avoid downloading messages already on backup on failover.
+
+Aside: Originally the queue position number was used as the ID, but that was
+insufficient for two reasons:
+- We sometimes need to identify messages that are not yet enqueued, for example messages in an open transaction.
+- We don't want to require maintaining identical message sequences on every broker e.g. so transactions can be committed independently by each broker.
+
+## At-least-once and delayed completion. ##
+
+We guarantee no message loss, aka at-least-once delivery. Any message received
+_and acknowledged_ by the primary will not be lost. Clients must keep sent
+messages until they are acknowledged. In a failure the client re-connects and
+re-sends all unacknowledged (or "in-doubt") messages.
+
+This ensures no messages are lost, but it is possible for in-doubt messages to
+be duplicated if the broker did receive them but failed before sending the
+acknowledgment. It is up to the application to handle duplicates correctly.
+
+We implement the broker's side of the bargain using _delayed completion_. When
+the primary receives a message it does not complete (acknowledge) the message
+until the message is replicated to and acknowledged by all the backups. In the
+case of persistent messages, that includes writing the message to persistent
+store.
+
+## Queue Replication ##
+
+Life-cycle of a message on a replicated queue, on the primary:
+
+Record: Received for primary queue.
+- IdSetter sets a replication ID.
+
+Enqueue: Published to primary queue.
+- QueueGuard delays completion (increments completion count).
+
+Deliver: Backup is ready to receive data.
+- ReplicatingSubscription sends message to backup QueueReplicator
+
+Acknowledge: Received from backup QueueReplicator.
+- ReplicatingSubscription completes (decrements completion count).
+
+Dequeue: Removed from queue
+- QueueGuard completes if not already completed by ReplicatingSubscription.
+- ReplicatingSubscription sends dequeued message ID to backup QueueReplicator.
+
+There is a simple protocol between ReplicatingSubscription and QueueReplicator
+(see Event sub-classes) so ReplicatingSubscription can send
+- replication IDs of messages as they are replicated.
+- replication IDs of messages that have been dequeued on the primary.
+
+## Queue Failover ##
+
+On failover, for each queue, the backup gets the QueueSnapshot of messages on
+the queue and sends it with the subscription to the primary queue. The primary
+responds with its own snapshot.
+
+Both parties use this information to determine:
+
+- Messages that are on the backup and don't need to be replicated again.
+- Messages that are dequeued on the primary and can be discarded by the backup.
+- Queue position for the QueueGuard to start delaying completion.
+- Queue position for the ReplicatingSubscription to start replicating from.
+
+After that handshake queue replication as described above begins.
+
+## Standalone replication ##
+
+The HA module supports ad-hoc replication between standalone brokers that are
+not in a cluster, using the same basic queue replication techniques.
+
+## Queue and Backup "ready" ##
+
+A QueueGuard is set on the queue when a backup first subscribes or when a backup
+fails over to a new primary. Messages before the _first guarded position_ cannot
+be delayed because they may have already been acknowledged to clients.
+
+A backup sends a set of acknowledged message ids when subscribing, messages that
+are already on the backup and therefore safe.
+
+A ReplicatingSubscription is _ready_ when all messages are safe or delayed. We
+know this is the case when both the following conditions hold:
+
+- The ReplicatingSubscription has reached the position preceding the first guarded position AND
+- All messages prior to the first guarded position are safe.
+
+
+*/
+/** \namespace qpid::ha \brief High Availability \ingroup ha-module */
+}} // namespace qpid::ha
+
diff --git a/qpid/cpp/src/qpid/ha/RemoteBackup.cpp b/qpid/cpp/src/qpid/ha/RemoteBackup.cpp
new file mode 100644
index 0000000000..d511b5bd0e
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/RemoteBackup.cpp
@@ -0,0 +1,116 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "RemoteBackup.h"
+#include "QueueGuard.h"
+#include "TxReplicator.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/Connection.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/QueueRegistry.h"
+#include "qpid/log/Statement.h"
+#include <boost/bind.hpp>
+
+namespace qpid {
+namespace ha {
+
+using sys::Mutex;
+using boost::bind;
+
+RemoteBackup::RemoteBackup(
+ const BrokerInfo& info, broker::Connection* c, const LogPrefix& lp
+) : logPrefix(lp), brokerInfo(info), replicationTest(NONE),
+ started(false), connection(c), reportedReady(false)
+{
+ std::ostringstream oss;
+ oss << "Remote backup at " << info << ": ";
+ logPrefix = oss.str();
+}
+
+RemoteBackup::~RemoteBackup() {
+ // Don't cancel here, cancel must be called explicitly in a locked context
+ // where we know the connection pointer is still good.
+}
+
+void RemoteBackup::cancel() {
+ QPID_LOG(debug, logPrefix << "Cancelled " << (connection? "connected":"disconnected")
+ << " backup: " << brokerInfo);
+ for (GuardMap::iterator i = guards.begin(); i != guards.end(); ++i)
+ i->second->cancel();
+ guards.clear();
+ if (connection) {
+ connection->abort();
+ connection = 0;
+ }
+}
+
+bool RemoteBackup::isReady() {
+ return started && connection && catchupQueues.empty();
+}
+
+void RemoteBackup::catchupQueue(const QueuePtr& q, bool createGuard) {
+ if (replicationTest.getLevel(*q) == ALL) {
+ QPID_LOG(debug, logPrefix << "Catch-up queue"
+ << (createGuard ? " and guard" : "") << ": " << q->getName());
+ catchupQueues.insert(q);
+ if (createGuard) guards[q].reset(new QueueGuard(*q, brokerInfo, logPrefix.prePrefix));
+ }
+}
+
+RemoteBackup::GuardPtr RemoteBackup::guard(const QueuePtr& q) {
+ GuardMap::iterator i = guards.find(q);
+ GuardPtr guard;
+ if (i != guards.end()) {
+ guard = i->second;
+ guards.erase(i);
+ }
+ return guard;
+}
+
+void RemoteBackup::ready(const QueuePtr& q) {
+ catchupQueues.erase(q);
+}
+
+// Called via BrokerObserver::queueCreate and from catchupQueue
+void RemoteBackup::queueCreate(const QueuePtr& q) {
+ if (replicationTest.getLevel(*q) == ALL)
+ guards[q].reset(new QueueGuard(*q, brokerInfo, logPrefix.prePrefix));
+}
+
+// Called via BrokerObserver
+void RemoteBackup::queueDestroy(const QueuePtr& q) {
+ catchupQueues.erase(q);
+ GuardMap::iterator i = guards.find(q);
+ if (i != guards.end()) {
+ i->second->cancel();
+ guards.erase(i);
+ }
+}
+
+bool RemoteBackup::reportReady() {
+ if (!reportedReady && isReady()) {
+ if (catchupQueues.empty()) QPID_LOG(debug, logPrefix << "Caught up.");
+ reportedReady = true;
+ return true;
+ }
+ return false;
+}
+
+}} // namespace qpid::ha
diff --git a/qpid/cpp/src/qpid/ha/RemoteBackup.h b/qpid/cpp/src/qpid/ha/RemoteBackup.h
new file mode 100644
index 0000000000..77c493d27e
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/RemoteBackup.h
@@ -0,0 +1,118 @@
+#ifndef QPID_HA_REMOTEBACKUP_H
+#define QPID_HA_REMOTEBACKUP_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "LogPrefix.h"
+#include "ReplicationTest.h"
+#include "BrokerInfo.h"
+#include "types.h"
+#include "hash.h"
+#include "qpid/sys/unordered_map.h"
+#include <set>
+
+namespace qpid {
+
+namespace broker {
+class Queue;
+class QueueRegistry;
+class Connection;
+}
+
+namespace ha {
+class QueueGuard;
+
+/**
+ * Track readiness for a remote broker.
+ * Creates queue guards on behalf of the remote broker to keep
+ * queues safe till the ReplicatingSubscription is ready.
+ *
+ * THREAD UNSAFE: Caller must serialize.
+ */
+class RemoteBackup
+{
+ public:
+ typedef boost::shared_ptr<QueueGuard> GuardPtr;
+ typedef boost::shared_ptr<broker::Queue> QueuePtr;
+
+ /** Note: isReady() can be true after construction
+ *@param connected true if the backup is already connected.
+ */
+ RemoteBackup(const BrokerInfo&, broker::Connection*, const LogPrefix&);
+ ~RemoteBackup();
+
+ /** Return guard associated with a queue. Used to create ReplicatingSubscription. */
+ GuardPtr guard(const QueuePtr&);
+
+ /** Is the remote backup connected? */
+ void setConnection(broker::Connection* c) { connection = c; }
+ broker::Connection* getConnection() const { return connection; }
+
+ /** ReplicatingSubscription associated with queue is ready.
+ * Note: may set isReady()
+ */
+ void ready(const QueuePtr& queue);
+
+ /** Called via BrokerObserver */
+ void queueCreate(const QueuePtr&);
+
+ /** Called via BrokerObserver. Note: may set isReady() */
+ void queueDestroy(const QueuePtr&);
+
+ /**@return true when all catch-up queues for this backup are ready. */
+ bool isReady();
+
+ /**@return true if isReady() and this is the first call to reportReady */
+ bool reportReady();
+
+ /**Cancel all queue guards, called if we are timed out. */
+ void cancel();
+
+ /** Set a catch-up queue for this backup.
+ *@createGuard if true create a guard immediately.
+ */
+ void catchupQueue(const QueuePtr&, bool createGuard);
+
+ BrokerInfo getBrokerInfo() const { return brokerInfo; }
+
+ void startCatchup() { started = true; }
+
+ private:
+ typedef qpid::sys::unordered_map<
+ QueuePtr, GuardPtr, Hasher<boost::shared_ptr<broker::Queue> >
+ > GuardMap;
+
+ typedef std::set<QueuePtr> QueueSet;
+
+ LogPrefix2 logPrefix;
+ BrokerInfo brokerInfo;
+ ReplicationTest replicationTest;
+ GuardMap guards;
+ QueueSet catchupQueues;
+ bool started;
+ broker::Connection* connection;
+ bool reportedReady;
+};
+
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_REMOTEBACKUP_H*/
diff --git a/qpid/cpp/src/qpid/ha/ReplicatingSubscription.cpp b/qpid/cpp/src/qpid/ha/ReplicatingSubscription.cpp
new file mode 100644
index 0000000000..ca4dd0099f
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/ReplicatingSubscription.cpp
@@ -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.
+ *
+ */
+
+#include "Event.h"
+#include "IdSetter.h"
+#include "QueueGuard.h"
+#include "QueueSnapshot.h"
+#include "ReplicatingSubscription.h"
+#include "TxReplicatingSubscription.h"
+#include "Primary.h"
+#include "HaBroker.h"
+#include "qpid/assert.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/SessionContext.h"
+#include "qpid/broker/amqp_0_10/MessageTransfer.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/log/Statement.h"
+#include "qpid/types/Uuid.h"
+#include <sstream>
+
+
+namespace qpid {
+namespace ha {
+
+using namespace framing;
+using namespace broker;
+using namespace std;
+using sys::Mutex;
+using broker::amqp_0_10::MessageTransfer;
+namespace { const string QPID_HA(QPID_HA_PREFIX); }
+const string ReplicatingSubscription::QPID_REPLICATING_SUBSCRIPTION(QPID_HA+"repsub");
+const string ReplicatingSubscription::QPID_BROKER_INFO(QPID_HA+"info");
+const string ReplicatingSubscription::QPID_ID_SET(QPID_HA+"ids");
+const string ReplicatingSubscription::QPID_QUEUE_REPLICATOR(QPID_HA+"qrep");
+const string ReplicatingSubscription::QPID_TX_REPLICATOR(QPID_HA+"txrep");
+
+/* Called by SemanticState::consume to create a consumer */
+boost::shared_ptr<broker::SemanticState::ConsumerImpl>
+ReplicatingSubscription::Factory::create(
+ SemanticState* parent,
+ const string& name,
+ Queue::shared_ptr queue,
+ bool ack,
+ bool acquire,
+ bool exclusive,
+ const string& tag,
+ const string& resumeId,
+ uint64_t resumeTtl,
+ const framing::FieldTable& arguments
+) {
+ boost::shared_ptr<ReplicatingSubscription> rs;
+ std::string type = arguments.getAsString(QPID_REPLICATING_SUBSCRIPTION);
+ if (type == QPID_QUEUE_REPLICATOR) {
+ rs.reset(new ReplicatingSubscription(
+ haBroker,
+ parent, name, queue, ack, acquire, exclusive, tag,
+ resumeId, resumeTtl, arguments));
+ }
+ else if (type == QPID_TX_REPLICATOR) {
+ rs.reset(new TxReplicatingSubscription(
+ haBroker,
+ parent, name, queue, ack, acquire, exclusive, tag,
+ resumeId, resumeTtl, arguments));
+ }
+ if (rs) rs->initialize();
+ return rs;
+}
+
+ReplicatingSubscription::ReplicatingSubscription(
+ HaBroker& hb,
+ SemanticState* parent,
+ const string& name,
+ Queue::shared_ptr queue_,
+ bool ack,
+ bool /*acquire*/,
+ bool exclusive,
+ const string& tag,
+ const string& resumeId,
+ uint64_t resumeTtl,
+ const framing::FieldTable& arguments
+) : ConsumerImpl(parent, name, queue_, ack, REPLICATOR, exclusive, tag,
+ resumeId, resumeTtl, arguments),
+ logPrefix(hb.logPrefix),
+ position(0), wasStopped(false), ready(false), cancelled(false),
+ haBroker(hb),
+ primary(boost::dynamic_pointer_cast<Primary>(haBroker.getRole()))
+{}
+
+// Called in subscription's connection thread when the subscription is created.
+// Separate from ctor because we need to use shared_from_this
+//
+void ReplicatingSubscription::initialize() {
+ try {
+ FieldTable ft;
+ if (!getArguments().getTable(ReplicatingSubscription::QPID_BROKER_INFO, ft))
+ throw InvalidArgumentException(
+ logPrefix.get()+"Can't subscribe, no broker info: "+getTag());
+ info.assign(ft);
+
+ // Set a log prefix message that identifies the remote broker.
+ ostringstream os;
+ os << "Subscription to " << queue->getName() << " at ";
+ info.printId(os) << ": ";
+ logPrefix = os.str();
+
+ // If there's already a guard (we are in failover) use it, else create one.
+ if (primary) guard = primary->getGuard(queue, info);
+ if (!guard) guard.reset(new QueueGuard(*queue, info, logPrefix.prePrefix));
+
+ // NOTE: Once the observer is attached we can have concurrent
+ // calls to dequeued so we need to lock use of this->dequeues.
+ //
+ // However we must attach the observer _before_ we snapshot for
+ // initial dequeues to be sure we don't miss any dequeues
+ // between the snapshot and attaching the observer.
+ queue->getObservers().add(
+ boost::dynamic_pointer_cast<ReplicatingSubscription>(shared_from_this()));
+ boost::shared_ptr<QueueSnapshot> snapshot = queue->getObservers().findType<QueueSnapshot>();
+ // There may be no snapshot if the queue is being deleted concurrently.
+ if (!snapshot) {
+ queue->getObservers().remove(
+ boost::dynamic_pointer_cast<ReplicatingSubscription>(shared_from_this()));
+ throw ResourceDeletedException(logPrefix.get()+"Can't subscribe, queue deleted");
+ }
+ ReplicationIdSet primaryIds = snapshot->getSnapshot();
+ std::string backupStr = getArguments().getAsString(ReplicatingSubscription::QPID_ID_SET);
+ ReplicationIdSet backupIds;
+ if (!backupStr.empty()) backupIds = decodeStr<ReplicationIdSet>(backupStr);
+
+ // Initial dequeues are messages on backup but not on primary.
+ ReplicationIdSet initDequeues = backupIds - primaryIds;
+ QueuePosition front,back;
+ queue->getRange(front, back, broker::REPLICATOR); // Outside lock, getRange locks queue
+ {
+ sys::Mutex::ScopedLock l(lock); // Concurrent calls to dequeued()
+ dequeues += initDequeues; // Messages on backup that are not on primary.
+ skipEnqueue = backupIds - initDequeues; // Messages already on the backup.
+ // Queue front is moving but we know this subscriptions will start at a
+ // position >= front so if front is safe then position must be.
+ position = front;
+
+ QPID_LOG(debug, logPrefix << "Subscribed: primary ["
+ << front << "," << back << "]=" << primaryIds
+ << ", guarded " << guard->getFirst()
+ << ", backup (keep " << skipEnqueue << ", drop " << initDequeues << ")");
+ checkReady(l);
+ }
+
+ if (primary) primary->addReplica(*this);
+ Mutex::ScopedLock l(lock); // Note dequeued() can be called concurrently.
+ // Send initial dequeues to the backup.
+ // There must be a shared_ptr(this) when sending.
+ sendDequeueEvent(l);
+ }
+ catch (const std::exception& e) {
+ QPID_LOG(error, logPrefix << "Subscribe failed: " << e.what());
+ throw;
+ }
+}
+
+ReplicatingSubscription::~ReplicatingSubscription() {}
+
+void ReplicatingSubscription::stopped() {
+ Mutex::ScopedLock l(lock);
+ // We have reached the last available message on the queue.
+ //
+ // Note that if messages have been removed out-of-order this may not be the
+ // head of the queue. We may not even have reached the guard
+ // position. However there are no more messages to protect and we will not
+ // be advanced any further, so we should consider ourselves guarded for
+ // purposes of readiness.
+ wasStopped = true;
+ checkReady(l);
+}
+
+// True if the next position for the ReplicatingSubscription is a guarded position.
+bool ReplicatingSubscription::isGuarded(sys::Mutex::ScopedLock&) {
+ // See comment in stopped()
+ return wasStopped || (position+1 >= guard->getFirst());
+}
+
+// Message is delivered in the subscription's connection thread.
+bool ReplicatingSubscription::deliver(
+ const qpid::broker::QueueCursor& c, const qpid::broker::Message& m)
+{
+ Mutex::ScopedLock l(lock);
+ ReplicationId id = m.getReplicationId();
+ position = m.getSequence();
+ try {
+ bool result = false;
+ if (skipEnqueue.contains(id)) {
+ QPID_LOG(trace, logPrefix << "Skip " << logMessageId(*getQueue(), m));
+ skipEnqueue -= id;
+ guard->complete(id); // This will never be acknowledged.
+ notify();
+ result = true;
+ }
+ else {
+ QPID_LOG(trace, logPrefix << "Replicated " << logMessageId(*getQueue(), m));
+ if (!ready && !isGuarded(l)) unready += id;
+ sendIdEvent(id, l);
+ result = ConsumerImpl::deliver(c, m);
+ }
+ checkReady(l);
+ return result;
+ } catch (const std::exception& e) {
+ QPID_LOG(critical, logPrefix << "Error replicating " << logMessageId(*getQueue(), m)
+ << ": " << e.what());
+ throw;
+ }
+}
+
+void ReplicatingSubscription::checkReady(sys::Mutex::ScopedLock& l) {
+ if (!ready && isGuarded(l) && unready.empty()) {
+ ready = true;
+ sys::Mutex::ScopedUnlock u(lock);
+ // Notify Primary that a subscription is ready.
+ if (position+1 >= guard->getFirst()) {
+ QPID_LOG(debug, logPrefix << "Caught up at " << position);
+ } else {
+ QPID_LOG(debug, logPrefix << "Caught up at " << position << "short of guard at " << guard->getFirst());
+ }
+
+ if (primary) primary->readyReplica(*this);
+ }
+}
+
+// Called in the subscription's connection thread.
+void ReplicatingSubscription::cancel()
+{
+ {
+ Mutex::ScopedLock l(lock);
+ if (cancelled) return;
+ cancelled = true;
+ }
+ QPID_LOG(debug, logPrefix << "Cancelled");
+ if (primary) primary->removeReplica(*this);
+ getQueue()->getObservers().remove(
+ boost::dynamic_pointer_cast<ReplicatingSubscription>(shared_from_this()));
+ guard->cancel();
+ ConsumerImpl::cancel();
+}
+
+// Consumer override, called on primary in the backup's IO thread.
+void ReplicatingSubscription::acknowledged(const broker::DeliveryRecord& r) {
+ // Finish completion of message, it has been acknowledged by the backup.
+ ReplicationId id = r.getReplicationId();
+ QPID_LOG(trace, logPrefix << "Acknowledged " <<
+ logMessageId(*getQueue(), r.getMessageId(), id));
+ guard->complete(id);
+ {
+ Mutex::ScopedLock l(lock);
+ unready -= id;
+ checkReady(l);
+ }
+ ConsumerImpl::acknowledged(r);
+}
+
+// Called with lock held. Called in subscription's connection thread.
+void ReplicatingSubscription::sendDequeueEvent(Mutex::ScopedLock& l)
+{
+ ReplicationIdSet oldDequeues = dequeues;
+ dequeues -= skipDequeue; // Don't send skipped dequeues
+ skipDequeue -= oldDequeues; // Forget dequeues that would have been sent.
+ if (dequeues.empty()) return;
+ QPID_LOG(trace, logPrefix << "Sending dequeues " << dequeues);
+ sendEvent(DequeueEvent(dequeues), l);
+}
+
+// Called after the message has been removed
+// from the deque and under the messageLock in the queue. Called in
+// arbitrary connection threads.
+void ReplicatingSubscription::dequeued(const broker::Message& m)
+{
+ ReplicationId id = m.getReplicationId();
+ QPID_LOG(trace, logPrefix << "Dequeued ID " << id);
+ {
+ Mutex::ScopedLock l(lock);
+ dequeues.add(id);
+ }
+ notify(); // Ensure a call to doDispatch
+}
+
+
+// Called with lock held. Called in subscription's connection thread.
+void ReplicatingSubscription::sendIdEvent(ReplicationId pos, Mutex::ScopedLock& l)
+{
+ sendEvent(IdEvent(pos), l);
+}
+
+void ReplicatingSubscription::sendEvent(const Event& event, Mutex::ScopedLock&)
+{
+ Mutex::ScopedUnlock u(lock);
+ // Send the event directly to the base consumer implementation. The dummy
+ // consumer prevents acknowledgements being handled, which is what we want
+ // for events
+ ConsumerImpl::deliver(QueueCursor(), event.message(), boost::shared_ptr<Consumer>());
+}
+
+// Called in subscription's connection thread.
+bool ReplicatingSubscription::doDispatch()
+{
+ {
+ Mutex::ScopedLock l(lock);
+ if (!dequeues.empty()) sendDequeueEvent(l);
+ }
+ try {
+ return ConsumerImpl::doDispatch();
+ }
+ catch (const std::exception& e) {
+ QPID_LOG(warning, logPrefix << " exception in dispatch: " << e.what());
+ return false;
+ }
+}
+
+void ReplicatingSubscription::skipEnqueues(const ReplicationIdSet& ids) {
+ Mutex::ScopedLock l(lock);
+ skipEnqueue += ids;
+}
+
+void ReplicatingSubscription::skipDequeues(const ReplicationIdSet& ids) {
+ Mutex::ScopedLock l(lock);
+ skipDequeue += ids;
+}
+
+}} // namespace qpid::ha
diff --git a/qpid/cpp/src/qpid/ha/ReplicatingSubscription.h b/qpid/cpp/src/qpid/ha/ReplicatingSubscription.h
new file mode 100644
index 0000000000..d6d41dd2cf
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/ReplicatingSubscription.h
@@ -0,0 +1,174 @@
+#ifndef QPID_BROKER_REPLICATINGSUBSCRIPTION_H
+#define QPID_BROKER_REPLICATINGSUBSCRIPTION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "BrokerInfo.h"
+#include "LogPrefix.h"
+#include "qpid/broker/SemanticState.h"
+#include "qpid/broker/ConsumerFactory.h"
+#include "qpid/broker/QueueObserver.h"
+#include <boost/enable_shared_from_this.hpp>
+#include <iosfwd>
+
+namespace qpid {
+
+namespace broker {
+class Message;
+class Queue;
+struct QueuedMessage;
+class OwnershipToken;
+}
+
+namespace framing {
+class Buffer;
+}
+
+namespace ha {
+class QueueGuard;
+class HaBroker;
+class Event;
+class Primary;
+
+/**
+ * A susbcription that replicates to a remote backup.
+ *
+ * Runs on the primary. In conjunction with a QueueGuard, delays completion of
+ * messages till the backup has acknowledged, informs backup of locally dequeued
+ * messages.
+ *
+ * A ReplicatingSubscription is "ready" when all the messages on the queue have
+ * either been acknowledged by the backup, or are protected by the queue guard.
+ * On a primary broker the ReplicatingSubscription calls Primary::readyReplica
+ * when it is ready.
+ *
+ * THREAD SAFE: Called in subscription's connection thread but also in arbitrary
+ * connection threads via dequeued.
+ *
+ * Lifecycle: broker::Queue holds shared_ptrs to this as a consumer.
+ *
+ * ReplicatingSubscription makes calls on QueueGuard, but not vice-versa.
+ */
+class ReplicatingSubscription :
+ public broker::SemanticState::ConsumerImpl,
+ public broker::QueueObserver
+{
+ public:
+ typedef broker::SemanticState::ConsumerImpl ConsumerImpl;
+
+ class Factory : public broker::ConsumerFactory {
+ public:
+ Factory(HaBroker& hb) : haBroker(hb) {}
+
+ HaBroker& getHaBroker() const { return haBroker; }
+
+ boost::shared_ptr<broker::SemanticState::ConsumerImpl> create(
+ broker::SemanticState* parent,
+ const std::string& name, boost::shared_ptr<broker::Queue> ,
+ bool ack, bool acquire, bool exclusive, const std::string& tag,
+ const std::string& resumeId, uint64_t resumeTtl,
+ const framing::FieldTable& arguments);
+ private:
+ HaBroker& haBroker;
+ };
+
+ // Argument names for consume command.
+ static const std::string QPID_REPLICATING_SUBSCRIPTION;
+ static const std::string QPID_BROKER_INFO;
+ static const std::string QPID_ID_SET;
+ // Replicator types: argument values for QPID_REPLICATING_SUBSCRIPTION argument.
+ static const std::string QPID_QUEUE_REPLICATOR;
+ static const std::string QPID_TX_REPLICATOR;
+
+ ReplicatingSubscription(HaBroker& haBroker,
+ broker::SemanticState* parent,
+ const std::string& name, boost::shared_ptr<broker::Queue> ,
+ bool ack, bool acquire, bool exclusive, const std::string& tag,
+ const std::string& resumeId, uint64_t resumeTtl,
+ const framing::FieldTable& arguments);
+
+ ~ReplicatingSubscription();
+
+
+ // Consumer overrides.
+ bool deliver(const broker::QueueCursor& cursor, const broker::Message& msg);
+ void cancel();
+ void acknowledged(const broker::DeliveryRecord&);
+ bool browseAcquired() const { return true; }
+ void stopped();
+
+ // Hide the "queue deleted" error for a ReplicatingSubscription when a
+ // queue is deleted, this is normal and not an error.
+ bool hideDeletedError() { return true; }
+
+ // QueueObserver overrides
+ void enqueued(const broker::Message&) {}
+ void dequeued(const broker::Message&);
+ void acquired(const broker::Message&) {}
+ void requeued(const broker::Message&) {}
+
+ /** A ReplicatingSubscription is a passive observer, not counted for auto
+ * deletion and immediate message purposes.
+ */
+ bool isCounted() { return false; }
+
+ /** Initialization that must be done separately from construction
+ * because it requires a shared_ptr to this to exist.
+ */
+ void initialize();
+
+ BrokerInfo getBrokerInfo() const { return info; }
+
+ void skipEnqueues(const ReplicationIdSet& ids);
+ void skipDequeues(const ReplicationIdSet& ids);
+
+ protected:
+ bool doDispatch();
+
+ private:
+ LogPrefix2 logPrefix;
+ QueuePosition position;
+ ReplicationIdSet dequeues; // Dequeues to be sent in next dequeue event.
+ ReplicationIdSet skipEnqueue; // Enqueues to skip: messages already on backup and tx enqueues.
+ ReplicationIdSet skipDequeue; // Dequeues to skip: tx dequeues.
+ ReplicationIdSet unready; // Unguarded, replicated and un-acknowledged.
+ bool wasStopped;
+ bool ready;
+ bool cancelled;
+ BrokerInfo info;
+ boost::shared_ptr<QueueGuard> guard;
+ HaBroker& haBroker;
+ boost::shared_ptr<Primary> primary;
+
+ bool isGuarded(sys::Mutex::ScopedLock&);
+ void dequeued(ReplicationId);
+ void sendDequeueEvent(sys::Mutex::ScopedLock&);
+ void sendIdEvent(ReplicationId, sys::Mutex::ScopedLock&);
+ void sendEvent(const Event&, sys::Mutex::ScopedLock&);
+ void checkReady(sys::Mutex::ScopedLock&);
+ friend class Factory;
+};
+
+
+}} // namespace qpid::broker
+
+#endif /*!QPID_BROKER_REPLICATINGSUBSCRIPTION_H*/
diff --git a/qpid/cpp/src/qpid/ha/ReplicationTest.cpp b/qpid/cpp/src/qpid/ha/ReplicationTest.cpp
new file mode 100644
index 0000000000..d2152363fe
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/ReplicationTest.cpp
@@ -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.
+ *
+ */
+#include "ReplicationTest.h"
+#include "qpid/log/Statement.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/Exchange.h"
+#include "qpid/framing/FieldTable.h"
+
+namespace qpid {
+namespace ha {
+
+using types::Variant;
+
+ReplicateLevel ReplicationTest::getLevel(const std::string& str) const {
+ Enum<ReplicateLevel> rl(replicateDefault);
+ if (!str.empty()) rl.parse(str);
+ return rl.get();
+}
+
+ReplicateLevel ReplicationTest::getLevel(const framing::FieldTable& f) const {
+ if (f.isSet(QPID_REPLICATE))
+ return getLevel(f.getAsString(QPID_REPLICATE));
+ else
+ return replicateDefault;
+}
+
+ReplicateLevel ReplicationTest::getLevel(const Variant::Map& m) const {
+ Variant::Map::const_iterator i = m.find(QPID_REPLICATE);
+ if (i != m.end())
+ return getLevel(i->second.asString());
+ else
+ return replicateDefault;
+}
+
+ReplicateLevel ReplicationTest::getLevel(const broker::Queue& q) const {
+ const Variant::Map& qmap(q.getSettings().original);
+ Variant::Map::const_iterator i = qmap.find(QPID_REPLICATE);
+ if (i != qmap.end())
+ return getLevel(i->second.asString());
+ else
+ return getLevel(q.getSettings().storeSettings);
+}
+
+ReplicateLevel ReplicationTest::getLevel(const broker::Exchange& ex) const {
+ return getLevel(ex.getArgs());
+}
+
+ReplicateLevel ReplicationTest::useLevel(const broker::Queue& q) const {
+ return q.getSettings().isTemporary ? ReplicationTest(NONE).getLevel(q) : getLevel(q);
+}
+
+ReplicateLevel ReplicationTest::useLevel(const broker::Exchange& ex) const {
+ return ReplicationTest::getLevel(ex);
+}
+
+
+}} // namespace qpid::ha
diff --git a/qpid/cpp/src/qpid/ha/ReplicationTest.h b/qpid/cpp/src/qpid/ha/ReplicationTest.h
new file mode 100644
index 0000000000..8b19dc2ee0
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/ReplicationTest.h
@@ -0,0 +1,76 @@
+#ifndef QPID_HA_REPLICATIONTEST_H
+#define QPID_HA_REPLICATIONTEST_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "types.h"
+#include "qpid/types/Variant.h"
+#include <string>
+
+namespace qpid {
+
+namespace broker {
+class Queue;
+class Exchange;
+}
+
+namespace framing {
+class FieldTable;
+}
+
+namespace ha {
+/**
+ * Test whether something is replicated, taking into account the
+ * default replication level.
+ *
+ * The primary uses a ReplicationTest with default based on configuration
+ * settings, and marks objects to be replicated with an explict replication
+ * argument.
+ *
+ * The backup uses a default of NONE, so it always accepts what the primary has
+ * marked on the object.
+ */
+class ReplicationTest
+{
+ public:
+ ReplicationTest(ReplicateLevel replicateDefault_) :
+ replicateDefault(replicateDefault_) {}
+
+ // Get the replication level set on an object, or default if not set.
+ ReplicateLevel getLevel(const std::string& str) const;
+ ReplicateLevel getLevel(const framing::FieldTable& f) const;
+ ReplicateLevel getLevel(const types::Variant::Map& m) const;
+ ReplicateLevel getLevel(const broker::Queue&) const;
+ ReplicateLevel getLevel(const broker::Exchange&) const;
+
+ // Calculate level for objects that may not have replication set,
+ // including auto-delete/exclusive settings.
+ ReplicateLevel useLevel(const broker::Queue&) const;
+ ReplicateLevel useLevel(const broker::Exchange&) const;
+
+ private:
+ ReplicateLevel replicateDefault;
+};
+
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_REPLICATIONTEST_H*/
diff --git a/qpid/cpp/src/qpid/ha/Role.h b/qpid/cpp/src/qpid/ha/Role.h
new file mode 100644
index 0000000000..5392ce1fff
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/Role.h
@@ -0,0 +1,54 @@
+#ifndef QPID_HA_ROLE_H
+#define QPID_HA_ROLE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <string>
+
+namespace qpid {
+struct Url;
+
+namespace ha {
+
+/**
+ * A HaBroker has a role, e.g. Primary, Backup, StandAlone.
+ * Role subclasses define the actions of the broker in each role.
+ * The Role interface allows the HaBroker to pass management actions
+ * to be implemented by the role.
+ */
+class Role
+{
+ public:
+ virtual ~Role() {}
+
+ /** QMF promote method handler.
+ * @return The new role if promoted, 0 if not. Caller takes ownership.
+ */
+ virtual Role* promote() = 0;
+
+ virtual void setBrokerUrl(const Url& url) = 0;
+
+ private:
+};
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_ROLE_H*/
diff --git a/qpid/cpp/src/qpid/ha/Settings.h b/qpid/cpp/src/qpid/ha/Settings.h
new file mode 100644
index 0000000000..9d5a5e6ae0
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/Settings.h
@@ -0,0 +1,61 @@
+#ifndef QPID_HA_SETTINGS_H
+#define QPID_HA_SETTINGS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "types.h"
+#include "qpid/sys/Time.h"
+#include "qpid/sys/IntegerTypes.h"
+#include <string>
+
+namespace qpid {
+namespace ha {
+
+/**
+ * Configurable settings for HA.
+ */
+class Settings
+{
+ public:
+ Settings() : cluster(false), queueReplication(false),
+ replicateDefault(NONE), backupTimeout(10*sys::TIME_SEC),
+ flowMessages(1000), flowBytes(0)
+ {}
+
+ bool cluster; // True if we are a cluster member.
+ bool queueReplication; // True if enabled.
+ std::string publicUrl;
+ std::string brokerUrl;
+ Enum<ReplicateLevel> replicateDefault;
+ std::string username, password, mechanism;
+ sys::Duration backupTimeout;
+
+ uint32_t flowMessages, flowBytes;
+
+ static const uint32_t NO_LIMIT=0xFFFFFFFF;
+ static uint32_t flowValue(uint32_t n) { return n ? n : NO_LIMIT; }
+ uint32_t getFlowMessages() const { return flowValue(flowMessages); }
+ uint32_t getFlowBytes() const { return flowValue(flowBytes); }
+};
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_SETTINGS_H*/
diff --git a/qpid/cpp/src/qpid/ha/StandAlone.h b/qpid/cpp/src/qpid/ha/StandAlone.h
new file mode 100644
index 0000000000..01bcf1a0b3
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/StandAlone.h
@@ -0,0 +1,41 @@
+#ifndef QPID_HA_STANDALONE_H
+#define QPID_HA_STANDALONE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+namespace qpid {
+struct Url;
+
+namespace ha {
+
+/**
+ * Stand-alone role: acts as a stand-alone broker, no clustering.
+ * HA module needed to setting up replication via QMF methods.
+ */
+class StandAlone : public Role
+{
+ public:
+ Role* promote() { return 0; }
+ void setBrokerUrl(const Url&) {}
+};
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_STANDALONE_H*/
diff --git a/qpid/cpp/src/qpid/ha/StatusCheck.cpp b/qpid/cpp/src/qpid/ha/StatusCheck.cpp
new file mode 100644
index 0000000000..8acf8d6cdc
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/StatusCheck.cpp
@@ -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.
+ *
+ */
+#include "StatusCheck.h"
+#include "ConnectionObserver.h"
+#include "HaBroker.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/log/Statement.h"
+#include "qpid/messaging/Address.h"
+#include "qpid/messaging/Connection.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/messaging/Receiver.h"
+#include "qpid/messaging/Sender.h"
+#include "qpid/messaging/Session.h"
+#include "qpid/types/Variant.h"
+
+namespace qpid {
+namespace ha {
+
+using namespace qpid::messaging;
+using namespace qpid::types;
+using namespace std;
+using namespace sys;
+
+const string HA_BROKER = "org.apache.qpid.ha:habroker:ha-broker";
+
+class StatusCheckThread : public sys::Runnable {
+ public:
+ StatusCheckThread(StatusCheck& sc, const qpid::Address& addr)
+ : url(addr), statusCheck(sc) {}
+ void run();
+ private:
+ Url url;
+ StatusCheck& statusCheck;
+};
+
+void StatusCheckThread::run() {
+ string logPrefix("Status check " + url.str() + ": ");
+ Connection c;
+ try {
+ // Check for self connections
+ Variant::Map options, clientProperties;
+ clientProperties[ConnectionObserver::ADMIN_TAG] = 1; // Allow connection to backups
+ clientProperties[ConnectionObserver::ADDRESS_TAG] = url.str();
+ clientProperties[ConnectionObserver::BACKUP_TAG] = statusCheck.brokerInfo.asMap();
+
+ // Set connection options
+ const Settings& settings = statusCheck.settings;
+ if (settings.username.size()) options["username"] = settings.username;
+ if (settings.password.size()) options["password"] = settings.password;
+ if (settings.mechanism.size()) options["sasl_mechanisms"] = settings.mechanism;
+ options["client-properties"] = clientProperties;
+ options["heartbeat"] = statusCheck.heartbeat/sys::TIME_SEC;
+
+ c = Connection(url.str(), options);
+ c.open();
+ Session session = c.createSession();
+ messaging::Address responses("#;{create:always,node:{x-declare:{exclusive:True,auto-delete:True,arguments:{'qpid.replicate':none}}}}");
+ Receiver r = session.createReceiver(responses);
+ Sender s = session.createSender("qmf.default.direct/broker");
+ Message request;
+ request.setReplyTo(responses);
+ request.setContentType("amqp/map");
+ request.setProperty("x-amqp-0-10.app-id", "qmf2");
+ request.setProperty("qmf.opcode", "_query_request");
+ Variant::Map oid;
+ oid["_object_name"] = HA_BROKER;
+ Variant::Map content;
+ content["_what"] = "OBJECT";
+ content["_object_id"] = oid;
+ encode(content, request);
+ s.send(request);
+ messaging::Duration timeout(statusCheck.heartbeat/sys::TIME_MSEC);
+ Message response = r.fetch(timeout);
+ session.acknowledge();
+ Variant::List contentIn;
+ decode(response, contentIn);
+ if (contentIn.size() == 1) {
+ Variant::Map details = contentIn.front().asMap()["_values"].asMap();
+ string status = details["status"].getString();
+ QPID_LOG(debug, logPrefix << status);
+ if (status != "joining") {
+ statusCheck.setPromote(false);
+ QPID_LOG(info, logPrefix << "Joining established cluster");
+ }
+ }
+ else
+ QPID_LOG(error, logPrefix << "Invalid response " << response.getContent());
+ } catch(...) {}
+ try { c.close(); } catch(...) {}
+ delete this;
+}
+
+// Note: Don't use hb outside of the constructor, it may be deleted.
+StatusCheck::StatusCheck(HaBroker& hb) :
+ promote(true),
+ settings(hb.getSettings()),
+ heartbeat(hb.getBroker().getLinkHeartbeatInterval()),
+ brokerInfo(hb.getBrokerInfo())
+{}
+
+StatusCheck::~StatusCheck() {
+ // Join any leftovers
+ for (size_t i = 0; i < threads.size(); ++i) threads[i].join();
+}
+
+void StatusCheck::setUrl(const Url& url) {
+ Mutex::ScopedLock l(lock);
+ for (size_t i = 0; i < url.size(); ++i)
+ threads.push_back(Thread(new StatusCheckThread(*this, url[i])));
+}
+
+bool StatusCheck::canPromote() {
+ Mutex::ScopedLock l(lock);
+ while (!threads.empty()) {
+ Thread t = threads.back();
+ threads.pop_back();
+ Mutex::ScopedUnlock u(lock);
+ t.join();
+ }
+ return promote;
+}
+
+void StatusCheck::setPromote(bool p) {
+ Mutex::ScopedLock l(lock);
+ promote = p;
+}
+
+}} // namespace qpid::ha
diff --git a/qpid/cpp/src/qpid/ha/StatusCheck.h b/qpid/cpp/src/qpid/ha/StatusCheck.h
new file mode 100644
index 0000000000..087e586b2b
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/StatusCheck.h
@@ -0,0 +1,76 @@
+#ifndef QPID_HA_STATUSCHECK_H
+#define QPID_HA_STATUSCHECK_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "BrokerInfo.h"
+#include "Settings.h"
+#include "qpid/Url.h"
+#include "qpid/sys/Thread.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/Runnable.h"
+#include "qpid/sys/Time.h"
+#include <vector>
+
+namespace qpid {
+namespace ha {
+
+class HaBroker;
+
+// TODO aconway 2012-12-21: This solution is incomplete. It will only protect
+// against bad promotion if there are READY brokers when this broker starts.
+// It will not help the situation where brokers became READY after this one starts.
+//
+
+/**
+ * Check whether a JOINING broker can be promoted .
+ *
+ * A JOINING broker can be promoted as long as all the other brokers are also
+ * JOINING. If there are READY brokers in the cluster the JOINING broker should
+ * refuse to promote so that one of the READY brokers can. This situation
+ * only comes about if the primary is dead and no new primary has been promoted.
+ *
+ * THREAD SAFE: setUrl and canPromote are called in arbitrary management threads.
+ */
+class StatusCheck
+{
+ public:
+ StatusCheck(HaBroker&);
+ ~StatusCheck();
+ void setUrl(const Url&);
+ bool canPromote();
+
+ private:
+ void setPromote(bool p);
+
+ sys::Mutex lock;
+ std::vector<sys::Thread> threads;
+ bool promote;
+ const Settings settings;
+ const sys::Duration heartbeat;
+ const BrokerInfo brokerInfo;
+
+ friend class StatusCheckThread;
+};
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_STATUSCHECK_H*/
diff --git a/qpid/cpp/src/qpid/ha/TxReplicatingSubscription.cpp b/qpid/cpp/src/qpid/ha/TxReplicatingSubscription.cpp
new file mode 100644
index 0000000000..15b33fe89d
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/TxReplicatingSubscription.cpp
@@ -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.
+ *
+ */
+
+#include "TxReplicatingSubscription.h"
+
+namespace qpid {
+namespace ha {
+using namespace std;
+using namespace broker;
+
+TxReplicatingSubscription::TxReplicatingSubscription(
+ HaBroker& hb,
+ SemanticState* parent,
+ const string& name,
+ boost::shared_ptr<Queue> queue,
+ bool ack,
+ bool acquire,
+ bool exclusive,
+ const string& tag,
+ const string& resumeId,
+ uint64_t resumeTtl,
+ const framing::FieldTable& arguments
+) : ReplicatingSubscription(hb, parent, name, queue, ack, acquire, exclusive, tag,
+ resumeId, resumeTtl, arguments)
+{}
+
+}} // namespace qpid::ha
diff --git a/qpid/cpp/src/qpid/ha/TxReplicatingSubscription.h b/qpid/cpp/src/qpid/ha/TxReplicatingSubscription.h
new file mode 100644
index 0000000000..a363d262a0
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/TxReplicatingSubscription.h
@@ -0,0 +1,50 @@
+#ifndef QPID_HA_TXREPLICATINGSUBSCRIPTION_H
+#define QPID_HA_TXREPLICATINGSUBSCRIPTION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "ReplicatingSubscription.h"
+
+namespace qpid {
+namespace ha {
+
+/**
+ * Replicating subscription for a TX queue.
+ */
+class TxReplicatingSubscription : public ReplicatingSubscription
+{
+ public:
+ TxReplicatingSubscription(HaBroker& haBroker,
+ broker::SemanticState* parent,
+ const std::string& name, boost::shared_ptr<broker::Queue> ,
+ bool ack, bool acquire, bool exclusive, const std::string& tag,
+ const std::string& resumeId, uint64_t resumeTtl,
+ const framing::FieldTable& arguments);
+
+ /** A TxReplicatingSubscription is counted for auto-delete so we can clean
+ * up the TX queue when all backups are done.
+ */
+ bool isCounted() { return true; }
+};
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_TXREPLICATINGSUBSCRIPTION_H*/
diff --git a/qpid/cpp/src/qpid/ha/TxReplicator.cpp b/qpid/cpp/src/qpid/ha/TxReplicator.cpp
new file mode 100644
index 0000000000..33adc9780d
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/TxReplicator.cpp
@@ -0,0 +1,273 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+
+#include "TxReplicator.h"
+#include "Role.h"
+#include "Backup.h"
+#include "BrokerReplicator.h"
+#include "Event.h"
+#include "HaBroker.h"
+#include "ReplicatingSubscription.h"
+#include "types.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/Link.h"
+#include "qpid/broker/QueueRegistry.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/SessionHandler.h"
+#include "qpid/broker/TxBuffer.h"
+#include "qpid/broker/TxAccept.h"
+#include "qpid/broker/amqp_0_10/Connection.h"
+#include "qpid/broker/DeliverableMessage.h"
+#include "qpid/framing/BufferTypes.h"
+#include "qpid/log/Statement.h"
+#include "qpid/broker/amqp_0_10/MessageTransfer.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include <boost/shared_ptr.hpp>
+#include <boost/bind.hpp>
+#include <sstream>
+
+namespace qpid {
+namespace ha {
+
+using namespace std;
+using namespace qpid::broker;
+using namespace qpid::framing;
+using qpid::broker::amqp_0_10::MessageTransfer;
+using qpid::types::Uuid;
+
+namespace {
+const string PREFIX(TRANSACTION_REPLICATOR_PREFIX);
+} // namespace
+
+bool TxReplicator::isTxQueue(const string& q) {
+ return startsWith(q, PREFIX);
+}
+
+Uuid TxReplicator::getTxId(const string& q) {
+ if (TxReplicator::isTxQueue(q)) {
+ std::istringstream is(q);
+ is.seekg(PREFIX.size());
+ Uuid id;
+ is >> id;
+ if (!is.fail()) return id;
+ }
+ throw Exception(QPID_MSG("Invalid tx queue: " << q));
+}
+
+string TxReplicator::getType() const { return ReplicatingSubscription::QPID_TX_REPLICATOR; }
+
+boost::shared_ptr<TxReplicator> TxReplicator::create(
+ HaBroker& hb,
+ const boost::shared_ptr<broker::Queue>& txQueue,
+ const boost::shared_ptr<broker::Link>& link)
+{
+ boost::shared_ptr<TxReplicator> tr(new TxReplicator(hb, txQueue, link));
+ tr->initialize();
+ return tr;
+}
+
+TxReplicator::TxReplicator(
+ HaBroker& hb,
+ const boost::shared_ptr<broker::Queue>& txQueue,
+ const boost::shared_ptr<broker::Link>& link) :
+ QueueReplicator(hb, txQueue, link),
+ logPrefix(hb.logPrefix),
+ store(hb.getBroker().hasStore() ? &hb.getBroker().getStore() : 0),
+ channel(link->nextChannel()),
+ empty(true), ended(false),
+ dequeueState(hb.getBroker().getQueues())
+{
+ logPrefix = "Backup of TX "+shortStr(getTxId(txQueue->getName()))+": ";
+ QPID_LOG(debug, logPrefix << "Started");
+ if (!store) throw Exception(QPID_MSG(logPrefix << "No message store loaded."));
+
+ // Dispatch transaction events.
+ dispatch[TxEnqueueEvent::KEY] =
+ boost::bind(&TxReplicator::enqueue, this, _1, _2);
+ dispatch[TxDequeueEvent::KEY] =
+ boost::bind(&TxReplicator::dequeue, this, _1, _2);
+ dispatch[TxPrepareEvent::KEY] =
+ boost::bind(&TxReplicator::prepare, this, _1, _2);
+ dispatch[TxCommitEvent::KEY] =
+ boost::bind(&TxReplicator::commit, this, _1, _2);
+ dispatch[TxRollbackEvent::KEY] =
+ boost::bind(&TxReplicator::rollback, this, _1, _2);
+ dispatch[TxBackupsEvent::KEY] =
+ boost::bind(&TxReplicator::backups, this, _1, _2);
+}
+
+TxReplicator::~TxReplicator() {
+ link->returnChannel(channel);
+}
+
+// Send a message to the primary tx.
+void TxReplicator::sendMessage(const broker::Message& msg, sys::Mutex::ScopedLock&) {
+ assert(sessionHandler);
+ const MessageTransfer& transfer(MessageTransfer::get(msg));
+ for (FrameSet::const_iterator i = transfer.getFrames().begin();
+ i != transfer.getFrames().end();
+ ++i)
+ {
+ sessionHandler->out.handle(const_cast<AMQFrame&>(*i));
+ }
+}
+
+void TxReplicator::deliver(const broker::Message& m_) {
+ boost::intrusive_ptr<broker::TxBuffer> txbuf;
+ broker::Message m(m_);
+ {
+ sys::Mutex::ScopedLock l(lock);
+ if (!txBuffer) return;
+ txbuf = txBuffer;
+ m.setReplicationId(enq.id); // Use enqueued replicated id.
+ }
+ // Deliver message to the target queue, not the tx-queue.
+ boost::shared_ptr<broker::Queue> queue = haBroker.getBroker().getQueues().get(enq.queue);
+ QPID_LOG(trace, logPrefix << "Deliver " << logMessageId(*queue, m.getReplicationId()));
+ DeliverableMessage dm(m, txbuf.get());
+ dm.deliverTo(queue);
+}
+
+void TxReplicator::enqueue(const string& data, sys::Mutex::ScopedLock&) {
+ sys::Mutex::ScopedLock l(lock);
+ if (!txBuffer) return;
+ TxEnqueueEvent e;
+ decodeStr(data, e);
+ QPID_LOG(trace, logPrefix << "Enqueue: " << e);
+ enq = e;
+ empty = false;
+}
+
+void TxReplicator::dequeue(const string& data, sys::Mutex::ScopedLock&) {
+ sys::Mutex::ScopedLock l(lock);
+ if (!txBuffer) return;
+ TxDequeueEvent e;
+ decodeStr(data, e);
+ QPID_LOG(trace, logPrefix << "Dequeue: " << e);
+ // NOTE: Backup does not see transactional dequeues until the transaction is
+ // prepared, then they are all receieved before the prepare event.
+ // We collect the events here so we can do a single scan of the queue in prepare.
+ dequeueState.add(e);
+ empty = false;
+}
+
+void TxReplicator::DequeueState::add(const TxDequeueEvent& event) {
+ events[event.queue] += event.id;
+}
+
+// Use this function as a seek() predicate to find the dequeued messages.
+bool TxReplicator::DequeueState::addRecord(
+ const broker::Message& m, const boost::shared_ptr<Queue>& queue,
+ const ReplicationIdSet& rids)
+{
+ if (rids.contains(m.getReplicationId())) {
+ DeliveryRecord dr(cursor, m.getSequence(), m.getReplicationId(), queue,
+ string() /*tag*/,
+ boost::shared_ptr<Consumer>(),
+ true /*acquired*/,
+ false /*accepted*/,
+ false /*credit.isWindowMode()*/,
+ 0 /*credit*/);
+ // Generate record ids, unique within this transaction.
+ dr.setId(nextId++);
+ records.push_back(dr);
+ recordIds += dr.getId();
+ }
+ return false;
+}
+
+void TxReplicator::DequeueState::addRecords(const EventMap::value_type& entry) {
+ // Process all the dequeues for a single queue, in one pass of seek()
+ boost::shared_ptr<broker::Queue> q = queues.get(entry.first);
+ q->seek(cursor, boost::bind(&TxReplicator::DequeueState::addRecord,
+ this, _1, q, entry.second));
+}
+
+boost::shared_ptr<TxAccept> TxReplicator::DequeueState::makeAccept() {
+ for_each(events.begin(), events.end(),
+ boost::bind(&TxReplicator::DequeueState::addRecords, this, _1));
+ return boost::shared_ptr<TxAccept>(
+ new TxAccept(boost::cref(recordIds), boost::ref(records)));
+}
+
+void TxReplicator::prepare(const string&, sys::Mutex::ScopedLock& l) {
+ if (!txBuffer) return;
+ txBuffer->enlist(dequeueState.makeAccept());
+ context = store->begin();
+ if (txBuffer->prepare(context.get())) {
+ QPID_LOG(debug, logPrefix << "Local prepare OK");
+ sendMessage(TxPrepareOkEvent(haBroker.getSystemId()).message(queue->getName()), l);
+ } else {
+ QPID_LOG(error, logPrefix << "Local prepare failed");
+ sendMessage(TxPrepareFailEvent(haBroker.getSystemId()).message(queue->getName()), l);
+ }
+}
+
+void TxReplicator::commit(const string&, sys::Mutex::ScopedLock& l) {
+ if (!txBuffer) return;
+ QPID_LOG(debug, logPrefix << "Commit");
+ if (context.get()) store->commit(*context);
+ txBuffer->commit();
+ end(l);
+}
+
+void TxReplicator::rollback(const string&, sys::Mutex::ScopedLock& l) {
+ if (!txBuffer) return;
+ // Don't bleat about rolling back empty transactions, this happens all the time
+ // when a session closes and rolls back its outstanding transaction.
+ if (!empty) QPID_LOG(debug, logPrefix << "Rollback");
+ if (context.get()) store->abort(*context);
+ txBuffer->rollback();
+ end(l);
+}
+
+void TxReplicator::backups(const string& data, sys::Mutex::ScopedLock& l) {
+ TxBackupsEvent e;
+ decodeStr(data, e);
+ if (!e.backups.count(haBroker.getMembership().getSelf().getSystemId())) {
+ QPID_LOG(info, logPrefix << "Not participating");
+ end(l);
+ } else {
+ QPID_LOG(debug, logPrefix << "Backups: " << e.backups);
+ txBuffer = new broker::TxBuffer;
+ }
+}
+
+void TxReplicator::end(sys::Mutex::ScopedLock&) {
+ ended = true;
+ txBuffer = 0;
+ // QueueReplicator::destroy cancels subscription to the primary tx-queue
+ // which allows the primary to clean up resources.
+ sys::Mutex::ScopedUnlock u(lock);
+ QueueReplicator::destroy();
+}
+
+// Called when the tx queue is deleted.
+void TxReplicator::destroy(sys::Mutex::ScopedLock& l) {
+ if (!ended) {
+ if (!empty) QPID_LOG(error, logPrefix << "Destroyed prematurely, rollback");
+ rollback(string(), l);
+ }
+ QueueReplicator::destroy(l);
+}
+
+}} // namespace qpid::ha
diff --git a/qpid/cpp/src/qpid/ha/TxReplicator.h b/qpid/cpp/src/qpid/ha/TxReplicator.h
new file mode 100644
index 0000000000..c7599d21b1
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/TxReplicator.h
@@ -0,0 +1,136 @@
+#ifndef QPID_HA_TRANSACTIONREPLICATOR_H
+#define QPID_HA_TRANSACTIONREPLICATOR_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "LogPrefix.h"
+#include "QueueReplicator.h"
+#include "Event.h"
+#include "qpid/broker/DeliveryRecord.h"
+#include "qpid/broker/TransactionalStore.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/types/Uuid.h"
+
+namespace qpid {
+
+namespace broker {
+class TxBuffer;
+class TxAccept;
+class DtxBuffer;
+class Broker;
+class MessageStore;
+class Deliverable;
+}
+
+namespace ha {
+class BrokerReplicator;
+
+/**
+ * Exchange created on a backup broker to replicate a transaction on the primary.
+ *
+ * Subscribes to a tx-queue like a normal queue but puts replicated messages and
+ * transaction events into a local TxBuffer.
+ *
+ * THREAD SAFE: Called in different connection threads.
+ */
+class TxReplicator : public QueueReplicator {
+ public:
+ typedef boost::shared_ptr<broker::Queue> QueuePtr;
+ typedef boost::shared_ptr<broker::Link> LinkPtr;
+
+ static bool isTxQueue(const std::string& queue);
+ static types::Uuid getTxId(const std::string& queue);
+
+ static boost::shared_ptr<TxReplicator> create(
+ HaBroker&, const QueuePtr& txQueue, const LinkPtr& link);
+
+ ~TxReplicator();
+
+ std::string getType() const;
+
+ // QueueReplicator overrides
+ using QueueReplicator::destroy;
+ void destroy(sys::Mutex::ScopedLock&);
+
+ protected:
+
+ void deliver(const broker::Message&);
+
+ private:
+
+ typedef void (TxReplicator::*DispatchFunction)(
+ const std::string&, sys::Mutex::ScopedLock&);
+ typedef qpid::sys::unordered_map<std::string, DispatchFunction> DispatchMap;
+ typedef qpid::sys::unordered_map<std::string, ReplicationIdSet> DequeueMap;
+
+ TxReplicator(HaBroker&, const QueuePtr& txQueue, const LinkPtr& link);
+ void sendMessage(const broker::Message&, sys::Mutex::ScopedLock&);
+ void enqueue(const std::string& data, sys::Mutex::ScopedLock&);
+ void dequeue(const std::string& data, sys::Mutex::ScopedLock&);
+ void prepare(const std::string& data, sys::Mutex::ScopedLock&);
+ void commit(const std::string& data, sys::Mutex::ScopedLock&);
+ void rollback(const std::string& data, sys::Mutex::ScopedLock&);
+ void backups(const std::string& data, sys::Mutex::ScopedLock&);
+ void end(sys::Mutex::ScopedLock&);
+
+ LogPrefix2 logPrefix;
+ TxEnqueueEvent enq; // Enqueue data for next deliver.
+ boost::intrusive_ptr<broker::TxBuffer> txBuffer;
+ broker::MessageStore* store;
+ std::auto_ptr<broker::TransactionContext> context;
+ framing::ChannelId channel; // Channel to send prepare-complete.
+ bool empty, ended;
+
+ // Class to process dequeues and create DeliveryRecords to populate a
+ // TxAccept.
+ class DequeueState {
+ public:
+ DequeueState(broker::QueueRegistry& qr) : queues(qr) {}
+ void add(const TxDequeueEvent&);
+ boost::shared_ptr<broker::TxAccept> makeAccept();
+
+ private:
+ // Delivery record IDs are command IDs from the session.
+ // On a backup we will just fake these Ids.
+ typedef framing::SequenceNumber Id;
+ typedef framing::SequenceSet IdSet;
+ typedef qpid::sys::unordered_map<std::string, ReplicationIdSet> EventMap;
+
+ bool addRecord(const broker::Message& m,
+ const boost::shared_ptr<broker::Queue>&,
+ const ReplicationIdSet& );
+ void addRecords(const DequeueMap::value_type& entry);
+
+ broker::QueueRegistry& queues;
+ EventMap events;
+ broker::DeliveryRecords records;
+ broker::QueueCursor cursor;
+ framing::SequenceNumber nextId;
+ IdSet recordIds;
+ };
+ DequeueState dequeueState;
+};
+
+
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_TRANSACTIONREPLICATOR_H*/
diff --git a/qpid/cpp/src/qpid/ha/hash.h b/qpid/cpp/src/qpid/ha/hash.h
new file mode 100644
index 0000000000..17f99fb666
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/hash.h
@@ -0,0 +1,75 @@
+#ifndef QPID_HA_HASH_H
+#define QPID_HA_HASH_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/types/Uuid.h"
+#include <boost/shared_ptr.hpp>
+#include <utility>
+
+namespace qpid {
+namespace ha {
+
+// TODO aconway 2013-08-06: would like to use boost::hash or std::hash here
+// but not available/working on some older compilers.
+// Add overloads as needed.
+
+inline std::size_t hashValue(bool v) { return static_cast<std::size_t>(v); }
+inline std::size_t hashValue(char v) { return static_cast<std::size_t>(v); }
+inline std::size_t hashValue(unsigned char v) { return static_cast<std::size_t>(v); }
+inline std::size_t hashValue(signed char v) { return static_cast<std::size_t>(v); }
+inline std::size_t hashValue(short v) { return static_cast<std::size_t>(v); }
+inline std::size_t hashValue(unsigned short v) { return static_cast<std::size_t>(v); }
+inline std::size_t hashValue(int v) { return static_cast<std::size_t>(v); }
+inline std::size_t hashValue(unsigned int v) { return static_cast<std::size_t>(v); }
+inline std::size_t hashValue(long v) { return static_cast<std::size_t>(v); }
+inline std::size_t hashValue(unsigned long v) { return static_cast<std::size_t>(v); }
+
+inline std::size_t hashValue(const types::Uuid& v) { return v.hash(); }
+
+template <class T> inline std::size_t hashValue(T* v) {
+ std::size_t x = static_cast<std::size_t>(reinterpret_cast<std::ptrdiff_t>(v));
+ return x + (x >> 3);
+}
+
+template <class T> inline std::size_t hashValue(boost::shared_ptr<T> v) {
+ return hashValue(v.get());
+}
+
+template <class T> inline void hashCombine(std::size_t& seed, const T& v) {
+ seed ^= hashValue(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
+}
+
+template <class T, class U> inline size_t hashValue(const std::pair<T,U>& v) {
+ std::size_t seed = 0;
+ hashCombine(seed, v.first);
+ hashCombine(seed, v.second);
+ return seed;
+}
+
+template<class T> struct Hasher {
+ size_t operator()(const T& v) const { return hashValue(v); }
+};
+
+}} // namespace qpid::ha
+
+#endif /*!QPID_HA_HASH_H*/
diff --git a/qpid/cpp/src/qpid/ha/management-schema.xml b/qpid/cpp/src/qpid/ha/management-schema.xml
new file mode 100644
index 0000000000..3da482e732
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/management-schema.xml
@@ -0,0 +1,63 @@
+<schema package="org.apache.qpid.ha">
+
+ <!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+ -->
+
+ <!-- Monitor and control HA status of a broker. -->
+ <class name="HaBroker">
+ <property name="name" type="sstr" access="RC" index="y" desc="Primary Key"/>
+
+ <property name="status" type="sstr" desc="HA status: primary or backup"/>
+
+ <property name="brokersUrl" type="sstr"
+ desc="URL with address of each broker in the cluster."/>
+
+ <property name="publicUrl" type="sstr"
+ desc="URL advertized to clients to connect to the cluster."/>
+
+ <property name="replicateDefault" type="sstr"
+ desc="Replication for queues/exchanges with no qpid.replicate argument"/>
+
+ <property name="members" type="list" desc="List of brokers in the cluster"/>
+
+ <property name="systemId" type="uuid" desc="Identifies the system."/>
+
+ <method name="promote" desc="Promote a backup broker to primary."/>
+
+ <method name="setBrokersUrl" desc="URL listing each broker in the cluster.">
+ <arg name="url" type="sstr" dir="I"/>
+ </method>
+
+ <method name="setPublicUrl" desc="URL advertized to clients.">
+ <arg name="url" type="sstr" dir="I"/>
+ </method>
+
+ <method name="replicate" desc="Replicate individual queue from remote broker.">
+ <arg name="broker" type="sstr" dir="I"/>
+ <arg name="queue" type="sstr" dir="I"/>
+ </method>
+ </class>
+
+ <eventArguments>
+ <arg name="members" type="list" desc="List of broker information maps"/>
+ </eventArguments>
+
+ <event name="membersUpdate" sev="inform" args="members"/>
+
+</schema>
diff --git a/qpid/cpp/src/qpid/ha/types.cpp b/qpid/cpp/src/qpid/ha/types.cpp
new file mode 100644
index 0000000000..60cc0f27ce
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/types.cpp
@@ -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.
+ *
+ */
+
+#include "types.h"
+#include "qpid/Msg.h"
+#include "qpid/broker/Message.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/Exception.h"
+#include <algorithm>
+#include <iostream>
+#include <iterator>
+#include <assert.h>
+
+namespace qpid {
+namespace ha {
+
+using namespace std;
+
+const string QPID_REPLICATE("qpid.replicate");
+const string QPID_HA_UUID("qpid.ha-uuid");
+
+const char* QPID_HA_PREFIX = "qpid.ha-";
+const char* QUEUE_REPLICATOR_PREFIX = "qpid.ha-q:";
+const char* TRANSACTION_REPLICATOR_PREFIX = "qpid.ha-tx:";
+
+bool startsWith(const string& name, const string& prefix) {
+ return name.compare(0, prefix.size(), prefix) == 0;
+}
+
+string EnumBase::str() const {
+ assert(value < count);
+ return names[value];
+}
+
+void EnumBase::parse(const string& s) {
+ if (!parseNoThrow(s))
+ throw Exception(QPID_MSG("Invalid " << name << " value: " << s));
+}
+
+bool EnumBase::parseNoThrow(const string& s) {
+ const char** i = find(names, names+count, s);
+ value = i - names;
+ return value < count;
+}
+
+template <> const char* Enum<ReplicateLevel>::NAME = "replication";
+template <> const char* Enum<ReplicateLevel>::NAMES[] = { "none", "configuration", "all" };
+template <> const size_t Enum<ReplicateLevel>::N = 3;
+
+template <> const char* Enum<BrokerStatus>::NAME = "HA broker status";
+
+// NOTE: Changing status names will have an impact on qpid-ha and
+// the qpidd-primary init script.
+// Don't change them unless you are going to update all dependent code.
+//
+template <> const char* Enum<BrokerStatus>::NAMES[] = {
+ "joining", "catchup", "ready", "recovering", "active", "standalone"
+};
+template <> const size_t Enum<BrokerStatus>::N = 6;
+
+ostream& operator<<(ostream& o, EnumBase e) {
+ return o << e.str();
+}
+
+istream& operator>>(istream& i, EnumBase& e) {
+ string s;
+ i >> s;
+ e.parse(s);
+ return i;
+}
+
+ostream& operator<<(ostream& o, const UuidSet& ids) {
+ ostream_iterator<qpid::types::Uuid> out(o, " ");
+ o << "{ ";
+ for (UuidSet::const_iterator i = ids.begin(); i != ids.end(); ++i)
+ o << shortStr(*i) << " ";
+ o << "}";
+ return o;
+}
+
+
+std::string logMessageId(const std::string& q, QueuePosition pos, ReplicationId id) {
+ return Msg() << q << "[" << pos << "]" << "=" << id;
+}
+std::string logMessageId(const std::string& q, ReplicationId id) {
+ return Msg() << q << "[]" << "=" << id;
+}
+std::string logMessageId(const std::string& q, const broker::Message& m) {
+ return logMessageId(q, m.getSequence(), m.getReplicationId());
+}
+std::string logMessageId(const broker::Queue& q, QueuePosition pos, ReplicationId id) {
+ return logMessageId(q.getName(), pos, id);
+}
+std::string logMessageId(const broker::Queue& q, ReplicationId id) {
+ return logMessageId(q.getName(), id);
+}
+std::string logMessageId(const broker::Queue& q, const broker::Message& m) {
+ return logMessageId(q.getName(), m);
+}
+
+void UuidSet::encode(framing::Buffer& b) const {
+ b.putLong(size());
+ for (const_iterator i = begin(); i != end(); ++i)
+ b.putRawData(i->data(), i->size());
+}
+
+void UuidSet::decode(framing::Buffer& b) {
+ size_t n = b.getLong();
+ for ( ; n > 0; --n) {
+ types::Uuid id;
+ b.getRawData(const_cast<unsigned char*>(id.data()), id.size());
+ insert(id);
+ }
+}
+
+size_t UuidSet::encodedSize() const {
+ return sizeof(uint32_t) + size()*16;
+}
+
+
+}} // namespace qpid::ha
diff --git a/qpid/cpp/src/qpid/ha/types.h b/qpid/cpp/src/qpid/ha/types.h
new file mode 100644
index 0000000000..ae4b948dfc
--- /dev/null
+++ b/qpid/cpp/src/qpid/ha/types.h
@@ -0,0 +1,147 @@
+#ifndef QPID_HA_ENUM_H
+#define QPID_HA_ENUM_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/types/Variant.h"
+#include "qpid/types/Uuid.h"
+#include "qpid/framing/SequenceSet.h"
+
+#include <boost/shared_ptr.hpp>
+
+#include <string>
+#include <set>
+#include <iosfwd>
+
+namespace qpid {
+
+namespace broker {
+class Message;
+class Queue;
+}
+namespace framing {
+class FieldTable;
+}
+
+namespace ha {
+
+/** Base class for enums with string conversion */
+class EnumBase {
+ public:
+ EnumBase(const char* name_, const char* names_[], size_t count_, unsigned value)
+ : name(name_), names(names_), count(count_), value(value) {}
+
+ /** Convert to string */
+ std::string str() const;
+ /** Parse from string, throw if unsuccessful */
+ void parse(const std::string&);
+ /** Parse from string, return false if unsuccessful. */
+ bool parseNoThrow(const std::string&);
+
+ protected:
+ const char* name;
+ const char** names;
+ size_t count;
+ unsigned value;
+};
+
+std::ostream& operator<<(std::ostream&, EnumBase);
+std::istream& operator>>(std::istream&, EnumBase&);
+
+/** Wrapper template for enums with string conversion */
+template <class T> class Enum : public EnumBase {
+ public:
+ Enum(T x=T()) : EnumBase(NAME, NAMES, N, x) {}
+ T get() const { return T(value); }
+ void operator=(T x) { value = x; }
+
+ private:
+ static const size_t N; // Number of enum values.
+ static const char* NAMES[]; // Names of enum values.
+ static const char* NAME; // Descriptive name for the enum type.
+};
+
+/** To print an enum x: o << printable(x) */
+template <class T> Enum<T> printable(T x) { return Enum<T>(x); }
+
+enum ReplicateLevel {
+ NONE, ///< Nothing is replicated
+ CONFIGURATION, ///< Wiring is replicated but not messages
+ ALL ///< Everything is replicated
+};
+
+/** State of a broker: see HaBroker::setStatus for state diagram */
+enum BrokerStatus {
+ JOINING, ///< New broker, looking for primary
+ CATCHUP, ///< Backup: Connected to primary, catching up on state.
+ READY, ///< Backup: Caught up, ready to take over.
+ RECOVERING, ///< Primary: waiting for backups to connect & sync
+ ACTIVE, ///< Primary: actively serving clients.
+ STANDALONE ///< Not part of a cluster.
+};
+
+inline bool isPrimary(BrokerStatus s) {
+ return s == RECOVERING || s == ACTIVE || s == STANDALONE;
+}
+
+inline bool isBackup(BrokerStatus s) { return !isPrimary(s); }
+
+// String constants, defined as char* to avoid initialization order problems.
+extern const std::string QPID_REPLICATE;
+extern const std::string QPID_HA_UUID;
+
+// Strings used as prefixes, defined as char* to avoid link order problems.
+extern const char* QPID_HA_PREFIX;
+extern const char* QUEUE_REPLICATOR_PREFIX;
+extern const char* TRANSACTION_REPLICATOR_PREFIX;
+
+bool startsWith(const std::string& name, const std::string& prefix);
+
+/** Define IdSet type, not a typedef so we can overload operator << and add encoding.*/
+class UuidSet : public std::set<types::Uuid> {
+ public:
+ void encode(framing::Buffer&) const;
+ void decode(framing::Buffer&);
+ size_t encodedSize() const;
+};
+
+std::ostream& operator<<(std::ostream& o, const UuidSet& ids);
+
+// Use type names to distinguish Positions from Replication Ids
+typedef framing::SequenceNumber QueuePosition;
+typedef framing::SequenceNumber ReplicationId;
+typedef framing::SequenceSet QueuePositionSet;
+typedef framing::SequenceSet ReplicationIdSet;
+
+/** Helpers for logging message ID */
+std::string logMessageId(const std::string& q, QueuePosition pos, ReplicationId id);
+std::string logMessageId(const std::string& q, ReplicationId id);
+std::string logMessageId(const std::string& q, const broker::Message& m);
+std::string logMessageId(const broker::Queue& q, QueuePosition pos, ReplicationId id);
+std::string logMessageId(const broker::Queue& q, ReplicationId id);
+std::string logMessageId(const broker::Queue& q, const broker::Message& m);
+
+/** Return short version of human-readable UUID. */
+inline std::string shortStr(const types::Uuid& uuid) { return uuid.str().substr(0,8); }
+
+}} // qpid::ha
+#endif /*!QPID_HA_ENUM_H*/
diff --git a/qpid/cpp/src/qpid/legacystore/BindingDbt.cpp b/qpid/cpp/src/qpid/legacystore/BindingDbt.cpp
new file mode 100644
index 0000000000..a48c156e71
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/BindingDbt.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/legacystore/BindingDbt.h"
+
+namespace mrg {
+namespace msgstore {
+
+BindingDbt::BindingDbt(const qpid::broker::PersistableExchange& e, const qpid::broker::PersistableQueue& q, const std::string& k, const qpid::framing::FieldTable& a)
+ : data(new char[encodedSize(e, q, k, a)]),
+ buffer(data, encodedSize(e, q, k, a))
+{
+ buffer.putLongLong(q.getPersistenceId());
+ buffer.putShortString(q.getName());
+ buffer.putShortString(k);
+ buffer.put(a);
+
+ set_data(data);
+ set_size(encodedSize(e, q, k, a));
+}
+
+BindingDbt::~BindingDbt()
+{
+ delete [] data;
+}
+
+uint32_t BindingDbt::encodedSize(const qpid::broker::PersistableExchange& /*not used*/, const qpid::broker::PersistableQueue& q, const std::string& k, const qpid::framing::FieldTable& a)
+{
+ return 8 /*queue id*/ + q.getName().size() + 1 + k.size() + 1 + a.encodedSize();
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/legacystore/BindingDbt.h b/qpid/cpp/src/qpid/legacystore/BindingDbt.h
new file mode 100644
index 0000000000..63c7cd144e
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/BindingDbt.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LEGACYSTORE_BINDINGDBT_H
+#define QPID_LEGACYSTORE_BINDINGDBT_H
+
+#include "db-inc.h"
+#include "qpid/broker/PersistableExchange.h"
+#include "qpid/broker/PersistableQueue.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/framing/FieldTable.h"
+
+namespace mrg{
+namespace msgstore{
+
+class BindingDbt : public Dbt
+{
+ char* data;
+ qpid::framing::Buffer buffer;
+
+ static uint32_t encodedSize(const qpid::broker::PersistableExchange& e,
+ const qpid::broker::PersistableQueue& q,
+ const std::string& k,
+ const qpid::framing::FieldTable& a);
+
+public:
+ BindingDbt(const qpid::broker::PersistableExchange& e,
+ const qpid::broker::PersistableQueue& q,
+ const std::string& k,
+ const qpid::framing::FieldTable& a);
+
+ virtual ~BindingDbt();
+
+};
+
+}}
+
+#endif // ifndef QPID_LEGACYSTORE_BINDINGDBT_H
diff --git a/qpid/cpp/src/qpid/legacystore/BufferValue.cpp b/qpid/cpp/src/qpid/legacystore/BufferValue.cpp
new file mode 100644
index 0000000000..fb2c471cd7
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/BufferValue.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/legacystore/BufferValue.h"
+
+namespace mrg {
+namespace msgstore {
+
+
+
+BufferValue::BufferValue(u_int32_t size, u_int64_t offset)
+ : data(new char[size]),
+ buffer(data, size)
+{
+ set_data(data);
+ set_size(size);
+ set_flags(DB_DBT_USERMEM | DB_DBT_PARTIAL);
+ set_doff(offset);
+ set_dlen(size);
+ set_ulen(size);
+}
+
+BufferValue::BufferValue(const qpid::broker::Persistable& p)
+ : data(new char[p.encodedSize()]),
+ buffer(data, p.encodedSize())
+{
+ p.encode(buffer);
+
+ set_data(data);
+ set_size(p.encodedSize());
+}
+
+BufferValue::~BufferValue()
+{
+ delete [] data;
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/legacystore/BufferValue.h b/qpid/cpp/src/qpid/legacystore/BufferValue.h
new file mode 100644
index 0000000000..527fbcf577
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/BufferValue.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LEGACYSTORE_BUFFERVALUE_H
+#define QPID_LEGACYSTORE_BUFFERVALUE_H
+
+#include "db-inc.h"
+#include "qpid/broker/Persistable.h"
+#include "qpid/framing/Buffer.h"
+
+namespace mrg{
+namespace msgstore{
+
+class BufferValue : public Dbt
+{
+ char* data;
+
+public:
+ qpid::framing::Buffer buffer;
+
+ BufferValue(u_int32_t size, u_int64_t offset);
+ BufferValue(const qpid::broker::Persistable& p);
+ virtual ~BufferValue();
+};
+
+}}
+
+#endif // ifndef QPID_LEGACYSTORE_BUFFERVALUE_H
diff --git a/qpid/cpp/src/qpid/legacystore/Cursor.h b/qpid/cpp/src/qpid/legacystore/Cursor.h
new file mode 100644
index 0000000000..0c869c29a0
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/Cursor.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LEGACYSTORE_CURSOR_H
+#define QPID_LEGACYSTORE_CURSOR_H
+
+#include <boost/shared_ptr.hpp>
+#include "db-inc.h"
+
+namespace mrg{
+namespace msgstore{
+
+class Cursor
+{
+ Dbc* cursor;
+public:
+ typedef boost::shared_ptr<Db> db_ptr;
+
+ Cursor() : cursor(0) {}
+ virtual ~Cursor() { if(cursor) cursor->close(); }
+
+ void open(db_ptr db, DbTxn* txn, u_int32_t flags = 0) { db->cursor(txn, &cursor, flags); }
+ void close() { if(cursor) cursor->close(); cursor = 0; }
+ Dbc* get() { return cursor; }
+ Dbc* operator->() { return cursor; }
+ bool next(Dbt& key, Dbt& value) { return cursor->get(&key, &value, DB_NEXT) == 0; }
+ bool current(Dbt& key, Dbt& value) { return cursor->get(&key, &value, DB_CURRENT) == 0; }
+};
+
+}}
+
+#endif // ifndef QPID_LEGACYSTORE_CURSOR_H
diff --git a/qpid/cpp/src/qpid/legacystore/DataTokenImpl.cpp b/qpid/cpp/src/qpid/legacystore/DataTokenImpl.cpp
new file mode 100644
index 0000000000..796d4c02f0
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/DataTokenImpl.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/legacystore/DataTokenImpl.h"
+
+using namespace mrg::msgstore;
+
+DataTokenImpl::DataTokenImpl():data_tok() {}
+
+DataTokenImpl::~DataTokenImpl() {}
diff --git a/qpid/cpp/src/qpid/legacystore/DataTokenImpl.h b/qpid/cpp/src/qpid/legacystore/DataTokenImpl.h
new file mode 100644
index 0000000000..e01d471e1b
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/DataTokenImpl.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LEGACYSTORE_DATATOKENIMPL_H
+#define QPID_LEGACYSTORE_DATATOKENIMPL_H
+
+#include "qpid/legacystore/jrnl/data_tok.h"
+#include "qpid/broker/PersistableMessage.h"
+#include <boost/intrusive_ptr.hpp>
+
+namespace mrg {
+namespace msgstore {
+
+class DataTokenImpl : public journal::data_tok, public qpid::RefCounted
+{
+ private:
+ boost::intrusive_ptr<qpid::broker::PersistableMessage> sourceMsg;
+ public:
+ DataTokenImpl();
+ virtual ~DataTokenImpl();
+
+ inline boost::intrusive_ptr<qpid::broker::PersistableMessage>& getSourceMessage() { return sourceMsg; }
+ inline void setSourceMessage(const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg) { sourceMsg = msg; }
+};
+
+} // namespace msgstore
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_DATATOKENIMPL_H
diff --git a/qpid/cpp/src/qpid/legacystore/IdDbt.cpp b/qpid/cpp/src/qpid/legacystore/IdDbt.cpp
new file mode 100644
index 0000000000..d9edaf80e6
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/IdDbt.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/legacystore/IdDbt.h"
+
+using namespace mrg::msgstore;
+
+IdDbt::IdDbt() : id(0)
+{
+ init();
+}
+
+IdDbt::IdDbt(u_int64_t _id) : id(_id)
+{
+ init();
+}
+
+void IdDbt::init()
+{
+ set_data(&id);
+ set_size(sizeof(u_int64_t));
+ set_ulen(sizeof(u_int64_t));
+ set_flags(DB_DBT_USERMEM);
+}
diff --git a/qpid/cpp/src/qpid/legacystore/IdDbt.h b/qpid/cpp/src/qpid/legacystore/IdDbt.h
new file mode 100644
index 0000000000..ecf5922963
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/IdDbt.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LEGACYSTORE_IDDBT_H
+#define QPID_LEGACYSTORE_IDDBT_H
+
+#include "db-inc.h"
+
+namespace mrg{
+namespace msgstore{
+
+class IdDbt : public Dbt
+{
+ void init();
+public:
+ u_int64_t id;
+
+ IdDbt(u_int64_t id);
+ IdDbt();
+};
+
+}}
+
+#endif // ifndef QPID_LEGACYSTORE_IDDBT_H
diff --git a/qpid/cpp/src/qpid/legacystore/IdSequence.cpp b/qpid/cpp/src/qpid/legacystore/IdSequence.cpp
new file mode 100644
index 0000000000..975b1107e7
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/IdSequence.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/legacystore/IdSequence.h"
+
+using namespace mrg::msgstore;
+using qpid::sys::Mutex;
+
+IdSequence::IdSequence() : id(1) {}
+
+u_int64_t IdSequence::next()
+{
+ Mutex::ScopedLock guard(lock);
+ if (!id) id++; // avoid 0 when folding around
+ return id++;
+}
+
+void IdSequence::reset(uint64_t value)
+{
+ //deliberately not threadsafe, used only on recovery
+ id = value;
+}
diff --git a/qpid/cpp/src/qpid/legacystore/IdSequence.h b/qpid/cpp/src/qpid/legacystore/IdSequence.h
new file mode 100644
index 0000000000..11d7ff61ca
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/IdSequence.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LEGACYSTORE_IDSEQUENCE_H
+#define QPID_LEGACYSTORE_IDSEQUENCE_H
+
+#include "qpid/framing/amqp_types.h"
+#include "qpid/sys/Mutex.h"
+#include <sys/types.h>
+
+namespace mrg{
+namespace msgstore{
+
+class IdSequence
+{
+ qpid::sys::Mutex lock;
+ uint64_t id;
+public:
+ IdSequence();
+ uint64_t next();
+ void reset(uint64_t value);
+};
+
+}}
+
+#endif // ifndef QPID_LEGACYSTORE_IDSEQUENCE_H
diff --git a/qpid/cpp/src/qpid/legacystore/JournalImpl.cpp b/qpid/cpp/src/qpid/legacystore/JournalImpl.cpp
new file mode 100644
index 0000000000..ba3f2aecae
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/JournalImpl.cpp
@@ -0,0 +1,633 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/legacystore/JournalImpl.h"
+
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include "qpid/legacystore/jrnl/jexception.h"
+#include "qpid/log/Statement.h"
+#include "qpid/management/ManagementAgent.h"
+#include "qmf/org/apache/qpid/legacystore/ArgsJournalExpand.h"
+#include "qmf/org/apache/qpid/legacystore/EventCreated.h"
+#include "qmf/org/apache/qpid/legacystore/EventEnqThresholdExceeded.h"
+#include "qmf/org/apache/qpid/legacystore/EventFull.h"
+#include "qmf/org/apache/qpid/legacystore/EventRecovered.h"
+#include "qpid/sys/Monitor.h"
+#include "qpid/sys/Timer.h"
+#include "qpid/legacystore/StoreException.h"
+
+using namespace mrg::msgstore;
+using namespace mrg::journal;
+using qpid::management::ManagementAgent;
+namespace _qmf = qmf::org::apache::qpid::legacystore;
+
+InactivityFireEvent::InactivityFireEvent(JournalImpl* p, const qpid::sys::Duration timeout):
+ qpid::sys::TimerTask(timeout, "JournalInactive:"+p->id()), _parent(p) {}
+
+void InactivityFireEvent::fire() { qpid::sys::Mutex::ScopedLock sl(_ife_lock); if (_parent) _parent->flushFire(); }
+
+GetEventsFireEvent::GetEventsFireEvent(JournalImpl* p, const qpid::sys::Duration timeout):
+ qpid::sys::TimerTask(timeout, "JournalGetEvents:"+p->id()), _parent(p) {}
+
+void GetEventsFireEvent::fire() { qpid::sys::Mutex::ScopedLock sl(_gefe_lock); if (_parent) _parent->getEventsFire(); }
+
+JournalImpl::JournalImpl(qpid::sys::Timer& timer_,
+ const std::string& journalId,
+ const std::string& journalDirectory,
+ const std::string& journalBaseFilename,
+ const qpid::sys::Duration getEventsTimeout,
+ const qpid::sys::Duration flushTimeout,
+ qpid::management::ManagementAgent* a,
+ DeleteCallback onDelete):
+ jcntl(journalId, journalDirectory, journalBaseFilename),
+ timer(timer_),
+ getEventsTimerSetFlag(false),
+ lastReadRid(0),
+ writeActivityFlag(false),
+ flushTriggeredFlag(true),
+ _xidp(0),
+ _datap(0),
+ _dlen(0),
+ _dtok(),
+ _external(false),
+ deleteCallback(onDelete)
+{
+ getEventsFireEventsPtr = new GetEventsFireEvent(this, getEventsTimeout);
+ inactivityFireEventPtr = new InactivityFireEvent(this, flushTimeout);
+ {
+ timer.start();
+ timer.add(inactivityFireEventPtr);
+ }
+
+ initManagement(a);
+
+ log(LOG_NOTICE, "Created");
+ std::ostringstream oss;
+ oss << "Journal directory = \"" << journalDirectory << "\"; Base file name = \"" << journalBaseFilename << "\"";
+ log(LOG_DEBUG, oss.str());
+}
+
+JournalImpl::~JournalImpl()
+{
+ if (deleteCallback) deleteCallback(*this);
+ if (_init_flag && !_stop_flag){
+ try { stop(true); } // NOTE: This will *block* until all outstanding disk aio calls are complete!
+ catch (const jexception& e) { log(LOG_ERROR, e.what()); }
+ }
+ getEventsFireEventsPtr->cancel();
+ inactivityFireEventPtr->cancel();
+ free_read_buffers();
+
+ if (_mgmtObject.get() != 0) {
+ _mgmtObject->resourceDestroy();
+ _mgmtObject.reset();
+ }
+
+ log(LOG_NOTICE, "Destroyed");
+}
+
+void
+JournalImpl::initManagement(qpid::management::ManagementAgent* a)
+{
+ _agent = a;
+ if (_agent != 0)
+ {
+ _mgmtObject = _qmf::Journal::shared_ptr (
+ new _qmf::Journal(_agent, this));
+
+ _mgmtObject->set_name(_jid);
+ _mgmtObject->set_directory(_jdir.dirname());
+ _mgmtObject->set_baseFileName(_base_filename);
+ _mgmtObject->set_readPageSize(JRNL_RMGR_PAGE_SIZE * JRNL_SBLK_SIZE * JRNL_DBLK_SIZE);
+ _mgmtObject->set_readPages(JRNL_RMGR_PAGES);
+
+ // The following will be set on initialize(), but being properties, these must be set to 0 in the meantime
+ _mgmtObject->set_initialFileCount(0);
+ _mgmtObject->set_dataFileSize(0);
+ _mgmtObject->set_currentFileCount(0);
+ _mgmtObject->set_writePageSize(0);
+ _mgmtObject->set_writePages(0);
+
+ _agent->addObject(_mgmtObject, 0, true);
+ }
+}
+
+
+void
+JournalImpl::initialize(const u_int16_t num_jfiles,
+ const bool auto_expand,
+ const u_int16_t ae_max_jfiles,
+ const u_int32_t jfsize_sblks,
+ const u_int16_t wcache_num_pages,
+ const u_int32_t wcache_pgsize_sblks,
+ mrg::journal::aio_callback* const cbp)
+{
+ std::ostringstream oss;
+ oss << "Initialize; num_jfiles=" << num_jfiles << " jfsize_sblks=" << jfsize_sblks;
+ oss << " wcache_pgsize_sblks=" << wcache_pgsize_sblks;
+ oss << " wcache_num_pages=" << wcache_num_pages;
+ log(LOG_DEBUG, oss.str());
+ jcntl::initialize(num_jfiles, auto_expand, ae_max_jfiles, jfsize_sblks, wcache_num_pages, wcache_pgsize_sblks, cbp);
+ log(LOG_DEBUG, "Initialization complete");
+
+ if (_mgmtObject.get() != 0)
+ {
+ _mgmtObject->set_initialFileCount(_lpmgr.num_jfiles());
+ _mgmtObject->set_autoExpand(_lpmgr.is_ae());
+ _mgmtObject->set_currentFileCount(_lpmgr.num_jfiles());
+ _mgmtObject->set_maxFileCount(_lpmgr.ae_max_jfiles());
+ _mgmtObject->set_dataFileSize(_jfsize_sblks * JRNL_SBLK_SIZE * JRNL_DBLK_SIZE);
+ _mgmtObject->set_writePageSize(wcache_pgsize_sblks * JRNL_SBLK_SIZE * JRNL_DBLK_SIZE);
+ _mgmtObject->set_writePages(wcache_num_pages);
+ }
+ if (_agent != 0)
+ _agent->raiseEvent(qmf::org::apache::qpid::legacystore::EventCreated(_jid, _jfsize_sblks * JRNL_SBLK_SIZE * JRNL_DBLK_SIZE, _lpmgr.num_jfiles()),
+ qpid::management::ManagementAgent::SEV_NOTE);
+}
+
+void
+JournalImpl::recover(const u_int16_t num_jfiles,
+ const bool auto_expand,
+ const u_int16_t ae_max_jfiles,
+ const u_int32_t jfsize_sblks,
+ const u_int16_t wcache_num_pages,
+ const u_int32_t wcache_pgsize_sblks,
+ mrg::journal::aio_callback* const cbp,
+ boost::ptr_list<msgstore::PreparedTransaction>* prep_tx_list_ptr,
+ u_int64_t& highest_rid,
+ u_int64_t queue_id)
+{
+ std::ostringstream oss1;
+ oss1 << "Recover; num_jfiles=" << num_jfiles << " jfsize_sblks=" << jfsize_sblks;
+ oss1 << " queue_id = 0x" << std::hex << queue_id << std::dec;
+ oss1 << " wcache_pgsize_sblks=" << wcache_pgsize_sblks;
+ oss1 << " wcache_num_pages=" << wcache_num_pages;
+ log(LOG_DEBUG, oss1.str());
+
+ if (_mgmtObject.get() != 0)
+ {
+ _mgmtObject->set_initialFileCount(_lpmgr.num_jfiles());
+ _mgmtObject->set_autoExpand(_lpmgr.is_ae());
+ _mgmtObject->set_currentFileCount(_lpmgr.num_jfiles());
+ _mgmtObject->set_maxFileCount(_lpmgr.ae_max_jfiles());
+ _mgmtObject->set_dataFileSize(_jfsize_sblks * JRNL_SBLK_SIZE * JRNL_DBLK_SIZE);
+ _mgmtObject->set_writePageSize(wcache_pgsize_sblks * JRNL_SBLK_SIZE * JRNL_DBLK_SIZE);
+ _mgmtObject->set_writePages(wcache_num_pages);
+ }
+
+ if (prep_tx_list_ptr) {
+ // Create list of prepared xids
+ std::vector<std::string> prep_xid_list;
+ for (msgstore::PreparedTransaction::list::iterator i = prep_tx_list_ptr->begin(); i != prep_tx_list_ptr->end(); i++) {
+ prep_xid_list.push_back(i->xid);
+ }
+
+ jcntl::recover(num_jfiles, auto_expand, ae_max_jfiles, jfsize_sblks, wcache_num_pages, wcache_pgsize_sblks,
+ cbp, &prep_xid_list, highest_rid);
+ } else {
+ jcntl::recover(num_jfiles, auto_expand, ae_max_jfiles, jfsize_sblks, wcache_num_pages, wcache_pgsize_sblks,
+ cbp, 0, highest_rid);
+ }
+
+ // Populate PreparedTransaction lists from _tmap
+ if (prep_tx_list_ptr)
+ {
+ for (msgstore::PreparedTransaction::list::iterator i = prep_tx_list_ptr->begin(); i != prep_tx_list_ptr->end(); i++) {
+ txn_data_list tdl = _tmap.get_tdata_list(i->xid); // tdl will be empty if xid not found
+ for (tdl_itr tdl_itr = tdl.begin(); tdl_itr < tdl.end(); tdl_itr++) {
+ if (tdl_itr->_enq_flag) { // enqueue op
+ i->enqueues->add(queue_id, tdl_itr->_rid);
+ } else { // dequeue op
+ i->dequeues->add(queue_id, tdl_itr->_drid);
+ }
+ }
+ }
+ }
+ std::ostringstream oss2;
+ oss2 << "Recover phase 1 complete; highest rid found = 0x" << std::hex << highest_rid;
+ oss2 << std::dec << "; emap.size=" << _emap.size() << "; tmap.size=" << _tmap.size();
+ oss2 << "; journal now read-only.";
+ log(LOG_DEBUG, oss2.str());
+
+ if (_mgmtObject.get() != 0)
+ {
+ _mgmtObject->inc_recordDepth(_emap.size());
+ _mgmtObject->inc_enqueues(_emap.size());
+ _mgmtObject->inc_txn(_tmap.size());
+ _mgmtObject->inc_txnEnqueues(_tmap.enq_cnt());
+ _mgmtObject->inc_txnDequeues(_tmap.deq_cnt());
+ }
+}
+
+void
+JournalImpl::recover_complete()
+{
+ jcntl::recover_complete();
+ log(LOG_DEBUG, "Recover phase 2 complete; journal now writable.");
+ if (_agent != 0)
+ _agent->raiseEvent(qmf::org::apache::qpid::legacystore::EventRecovered(_jid, _jfsize_sblks * JRNL_SBLK_SIZE * JRNL_DBLK_SIZE, _lpmgr.num_jfiles(),
+ _emap.size(), _tmap.size(), _tmap.enq_cnt(), _tmap.deq_cnt()), qpid::management::ManagementAgent::SEV_NOTE);
+}
+
+//#define MAX_AIO_SLEEPS 1000000 // tot: ~10 sec
+//#define AIO_SLEEP_TIME_US 10 // 0.01 ms
+// Return true if content is recovered from store; false if content is external and must be recovered from an external store.
+// Throw exception for all errors.
+bool
+JournalImpl::loadMsgContent(u_int64_t rid, std::string& data, size_t length, size_t offset)
+{
+ qpid::sys::Mutex::ScopedLock sl(_read_lock);
+ if (_dtok.rid() != rid)
+ {
+ // Free any previous msg
+ free_read_buffers();
+
+ // Last read encountered out-of-order rids, check if this rid is in that list
+ bool oooFlag = false;
+ for (std::vector<u_int64_t>::const_iterator i=oooRidList.begin(); i!=oooRidList.end() && !oooFlag; i++) {
+ if (*i == rid) {
+ oooFlag = true;
+ }
+ }
+
+ // TODO: This is a brutal approach - very inefficient and slow. Rather introduce a system of remembering
+ // jumpover points and allow the read to jump back to the first known jumpover point - but this needs
+ // a mechanism in rrfc to accomplish it. Also helpful is a struct containing a journal address - a
+ // combination of lid/offset.
+ // NOTE: The second part of the if stmt (rid < lastReadRid) is required to handle browsing.
+ if (oooFlag || rid < lastReadRid) {
+ _rmgr.invalidate();
+ oooRidList.clear();
+ }
+ _dlen = 0;
+ _dtok.reset();
+ _dtok.set_wstate(DataTokenImpl::ENQ);
+ _dtok.set_rid(0);
+ _external = false;
+ size_t xlen = 0;
+ bool transient = false;
+ bool done = false;
+ bool rid_found = false;
+ while (!done) {
+ iores res = read_data_record(&_datap, _dlen, &_xidp, xlen, transient, _external, &_dtok);
+ switch (res) {
+ case mrg::journal::RHM_IORES_SUCCESS:
+ if (_dtok.rid() != rid) {
+ // Check if this is an out-of-order rid that may impact next read
+ if (_dtok.rid() > rid)
+ oooRidList.push_back(_dtok.rid());
+ free_read_buffers();
+ // Reset data token for next read
+ _dlen = 0;
+ _dtok.reset();
+ _dtok.set_wstate(DataTokenImpl::ENQ);
+ _dtok.set_rid(0);
+ } else {
+ rid_found = _dtok.rid() == rid;
+ lastReadRid = rid;
+ done = true;
+ }
+ break;
+ case mrg::journal::RHM_IORES_PAGE_AIOWAIT:
+ if (get_wr_events(&_aio_cmpl_timeout) == journal::jerrno::AIO_TIMEOUT) {
+ std::stringstream ss;
+ ss << "read_data_record() returned " << mrg::journal::iores_str(res);
+ ss << "; timed out waiting for page to be processed.";
+ throw jexception(mrg::journal::jerrno::JERR__TIMEOUT, ss.str().c_str(), "JournalImpl",
+ "loadMsgContent");
+ }
+ break;
+ default:
+ std::stringstream ss;
+ ss << "read_data_record() returned " << mrg::journal::iores_str(res);
+ throw jexception(mrg::journal::jerrno::JERR__UNEXPRESPONSE, ss.str().c_str(), "JournalImpl",
+ "loadMsgContent");
+ }
+ }
+ if (!rid_found) {
+ std::stringstream ss;
+ ss << "read_data_record() was unable to find rid 0x" << std::hex << rid << std::dec;
+ ss << " (" << rid << "); last rid found was 0x" << std::hex << _dtok.rid() << std::dec;
+ ss << " (" << _dtok.rid() << ")";
+ throw jexception(mrg::journal::jerrno::JERR__RECNFOUND, ss.str().c_str(), "JournalImpl", "loadMsgContent");
+ }
+ }
+
+ if (_external) return false;
+
+ u_int32_t hdr_offs = qpid::framing::Buffer(static_cast<char*>(_datap), sizeof(u_int32_t)).getLong() + sizeof(u_int32_t);
+ if (hdr_offs + offset + length > _dlen) {
+ data.append((const char*)_datap + hdr_offs + offset, _dlen - hdr_offs - offset);
+ } else {
+ data.append((const char*)_datap + hdr_offs + offset, length);
+ }
+ return true;
+}
+
+void
+JournalImpl::enqueue_data_record(const void* const data_buff, const size_t tot_data_len,
+ const size_t this_data_len, data_tok* dtokp, const bool transient)
+{
+ handleIoResult(jcntl::enqueue_data_record(data_buff, tot_data_len, this_data_len, dtokp, transient));
+
+ if (_mgmtObject.get() != 0)
+ {
+ _mgmtObject->inc_enqueues();
+ _mgmtObject->inc_recordDepth();
+ }
+}
+
+void
+JournalImpl::enqueue_extern_data_record(const size_t tot_data_len, data_tok* dtokp,
+ const bool transient)
+{
+ handleIoResult(jcntl::enqueue_extern_data_record(tot_data_len, dtokp, transient));
+
+ if (_mgmtObject.get() != 0)
+ {
+ _mgmtObject->inc_enqueues();
+ _mgmtObject->inc_recordDepth();
+ }
+}
+
+void
+JournalImpl::enqueue_txn_data_record(const void* const data_buff, const size_t tot_data_len,
+ const size_t this_data_len, data_tok* dtokp, const std::string& xid, const bool transient)
+{
+ bool txn_incr = _mgmtObject.get() != 0 ? _tmap.in_map(xid) : false;
+
+ handleIoResult(jcntl::enqueue_txn_data_record(data_buff, tot_data_len, this_data_len, dtokp, xid, transient));
+
+ if (_mgmtObject.get() != 0)
+ {
+ if (!txn_incr) // If this xid was not in _tmap, it will be now...
+ _mgmtObject->inc_txn();
+ _mgmtObject->inc_enqueues();
+ _mgmtObject->inc_txnEnqueues();
+ _mgmtObject->inc_recordDepth();
+ }
+}
+
+void
+JournalImpl::enqueue_extern_txn_data_record(const size_t tot_data_len, data_tok* dtokp,
+ const std::string& xid, const bool transient)
+{
+ bool txn_incr = _mgmtObject.get() != 0 ? _tmap.in_map(xid) : false;
+
+ handleIoResult(jcntl::enqueue_extern_txn_data_record(tot_data_len, dtokp, xid, transient));
+
+ if (_mgmtObject.get() != 0)
+ {
+ if (!txn_incr) // If this xid was not in _tmap, it will be now...
+ _mgmtObject->inc_txn();
+ _mgmtObject->inc_enqueues();
+ _mgmtObject->inc_txnEnqueues();
+ _mgmtObject->inc_recordDepth();
+ }
+}
+
+void
+JournalImpl::dequeue_data_record(data_tok* const dtokp, const bool txn_coml_commit)
+{
+ handleIoResult(jcntl::dequeue_data_record(dtokp, txn_coml_commit));
+
+ if (_mgmtObject.get() != 0)
+ {
+ _mgmtObject->inc_dequeues();
+ _mgmtObject->inc_txnDequeues();
+ _mgmtObject->dec_recordDepth();
+ }
+}
+
+void
+JournalImpl::dequeue_txn_data_record(data_tok* const dtokp, const std::string& xid, const bool txn_coml_commit)
+{
+ bool txn_incr = _mgmtObject.get() != 0 ? _tmap.in_map(xid) : false;
+
+ handleIoResult(jcntl::dequeue_txn_data_record(dtokp, xid, txn_coml_commit));
+
+ if (_mgmtObject.get() != 0)
+ {
+ if (!txn_incr) // If this xid was not in _tmap, it will be now...
+ _mgmtObject->inc_txn();
+ _mgmtObject->inc_dequeues();
+ _mgmtObject->inc_txnDequeues();
+ _mgmtObject->dec_recordDepth();
+ }
+}
+
+void
+JournalImpl::txn_abort(data_tok* const dtokp, const std::string& xid)
+{
+ handleIoResult(jcntl::txn_abort(dtokp, xid));
+
+ if (_mgmtObject.get() != 0)
+ {
+ _mgmtObject->dec_txn();
+ _mgmtObject->inc_txnAborts();
+ }
+}
+
+void
+JournalImpl::txn_commit(data_tok* const dtokp, const std::string& xid)
+{
+ handleIoResult(jcntl::txn_commit(dtokp, xid));
+
+ if (_mgmtObject.get() != 0)
+ {
+ _mgmtObject->dec_txn();
+ _mgmtObject->inc_txnCommits();
+ }
+}
+
+void
+JournalImpl::stop(bool block_till_aio_cmpl)
+{
+ InactivityFireEvent* ifep = dynamic_cast<InactivityFireEvent*>(inactivityFireEventPtr.get());
+ assert(ifep); // dynamic_cast can return null if the cast fails
+ ifep->cancel();
+ jcntl::stop(block_till_aio_cmpl);
+
+ if (_mgmtObject.get() != 0) {
+ _mgmtObject->resourceDestroy();
+ _mgmtObject.reset();
+ }
+}
+
+iores
+JournalImpl::flush(const bool block_till_aio_cmpl)
+{
+ const iores res = jcntl::flush(block_till_aio_cmpl);
+ {
+ qpid::sys::Mutex::ScopedLock sl(_getf_lock);
+ if (_wmgr.get_aio_evt_rem() && !getEventsTimerSetFlag) { setGetEventTimer(); }
+ }
+ return res;
+}
+
+void
+JournalImpl::log(mrg::journal::log_level ll, const std::string& log_stmt) const
+{
+ log(ll, log_stmt.c_str());
+}
+
+void
+JournalImpl::log(mrg::journal::log_level ll, const char* const log_stmt) const
+{
+ switch (ll)
+ {
+ case LOG_TRACE: QPID_LOG(trace, "Journal \"" << _jid << "\": " << log_stmt); break;
+ case LOG_DEBUG: QPID_LOG(debug, "Journal \"" << _jid << "\": " << log_stmt); break;
+ case LOG_INFO: QPID_LOG(info, "Journal \"" << _jid << "\": " << log_stmt); break;
+ case LOG_NOTICE: QPID_LOG(notice, "Journal \"" << _jid << "\": " << log_stmt); break;
+ case LOG_WARN: QPID_LOG(warning, "Journal \"" << _jid << "\": " << log_stmt); break;
+ case LOG_ERROR: QPID_LOG(error, "Journal \"" << _jid << "\": " << log_stmt); break;
+ case LOG_CRITICAL: QPID_LOG(critical, "Journal \"" << _jid << "\": " << log_stmt); break;
+ }
+}
+
+void
+JournalImpl::getEventsFire()
+{
+ qpid::sys::Mutex::ScopedLock sl(_getf_lock);
+ getEventsTimerSetFlag = false;
+ if (_wmgr.get_aio_evt_rem()) { jcntl::get_wr_events(0); }
+ if (_wmgr.get_aio_evt_rem()) { setGetEventTimer(); }
+}
+
+void
+JournalImpl::flushFire()
+{
+ if (writeActivityFlag) {
+ writeActivityFlag = false;
+ flushTriggeredFlag = false;
+ } else {
+ if (!flushTriggeredFlag) {
+ flush();
+ flushTriggeredFlag = true;
+ }
+ }
+ inactivityFireEventPtr->setupNextFire();
+ {
+ timer.add(inactivityFireEventPtr);
+ }
+}
+
+void
+JournalImpl::wr_aio_cb(std::vector<data_tok*>& dtokl)
+{
+ for (std::vector<data_tok*>::const_iterator i=dtokl.begin(); i!=dtokl.end(); i++)
+ {
+ DataTokenImpl* dtokp = static_cast<DataTokenImpl*>(*i);
+ if (/*!is_stopped() &&*/ dtokp->getSourceMessage())
+ {
+ switch (dtokp->wstate())
+ {
+ case data_tok::ENQ:
+ dtokp->getSourceMessage()->enqueueComplete();
+ break;
+ case data_tok::DEQ:
+/* Don't need to signal until we have a way to ack completion of dequeue in AMQP
+ dtokp->getSourceMessage()->dequeueComplete();
+ if ( dtokp->getSourceMessage()->isDequeueComplete() ) // clear id after last dequeue
+ dtokp->getSourceMessage()->setPersistenceId(0);
+*/
+ break;
+ default: ;
+ }
+ }
+ dtokp->release();
+ }
+}
+
+void
+JournalImpl::rd_aio_cb(std::vector<u_int16_t>& /*pil*/)
+{}
+
+void
+JournalImpl::free_read_buffers()
+{
+ if (_xidp) {
+ ::free(_xidp);
+ _xidp = 0;
+ _datap = 0;
+ } else if (_datap) {
+ ::free(_datap);
+ _datap = 0;
+ }
+}
+
+void
+JournalImpl::handleIoResult(const iores r)
+{
+ writeActivityFlag = true;
+ switch (r)
+ {
+ case mrg::journal::RHM_IORES_SUCCESS:
+ return;
+ case mrg::journal::RHM_IORES_ENQCAPTHRESH:
+ {
+ std::ostringstream oss;
+ oss << "Enqueue capacity threshold exceeded on queue \"" << _jid << "\".";
+ log(LOG_WARN, oss.str());
+ if (_agent != 0)
+ _agent->raiseEvent(qmf::org::apache::qpid::legacystore::EventEnqThresholdExceeded(_jid, "Journal enqueue capacity threshold exceeded"),
+ qpid::management::ManagementAgent::SEV_WARN);
+ THROW_STORE_FULL_EXCEPTION(oss.str());
+ }
+ case mrg::journal::RHM_IORES_FULL:
+ {
+ std::ostringstream oss;
+ oss << "Journal full on queue \"" << _jid << "\".";
+ log(LOG_CRITICAL, oss.str());
+ if (_agent != 0)
+ _agent->raiseEvent(qmf::org::apache::qpid::legacystore::EventFull(_jid, "Journal full"), qpid::management::ManagementAgent::SEV_ERROR);
+ THROW_STORE_FULL_EXCEPTION(oss.str());
+ }
+ default:
+ {
+ std::ostringstream oss;
+ oss << "Unexpected I/O response (" << mrg::journal::iores_str(r) << ") on queue " << _jid << "\".";
+ log(LOG_ERROR, oss.str());
+ THROW_STORE_FULL_EXCEPTION(oss.str());
+ }
+ }
+}
+
+qpid::management::Manageable::status_t JournalImpl::ManagementMethod (uint32_t methodId,
+ qpid::management::Args& /*args*/,
+ std::string& /*text*/)
+{
+ Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD;
+
+ switch (methodId)
+ {
+ case _qmf::Journal::METHOD_EXPAND :
+ //_qmf::ArgsJournalExpand& eArgs = (_qmf::ArgsJournalExpand&) args;
+
+ // Implement "expand" using eArgs.i_by (expand-by argument)
+
+ status = Manageable::STATUS_NOT_IMPLEMENTED;
+ break;
+ }
+
+ return status;
+}
diff --git a/qpid/cpp/src/qpid/legacystore/JournalImpl.h b/qpid/cpp/src/qpid/legacystore/JournalImpl.h
new file mode 100644
index 0000000000..7227b2ffd4
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/JournalImpl.h
@@ -0,0 +1,265 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#ifndef QPID_LEGACYSTORE_JOURNALIMPL_H
+#define QPID_LEGACYSTORE_JOURNALIMPL_H
+
+#include <set>
+#include "qpid/legacystore/jrnl/enums.h"
+#include "qpid/legacystore/jrnl/jcntl.h"
+#include "qpid/legacystore/DataTokenImpl.h"
+#include "qpid/legacystore/PreparedTransaction.h"
+#include "qpid/broker/PersistableQueue.h"
+#include "qpid/sys/Timer.h"
+#include "qpid/sys/Time.h"
+#include <boost/ptr_container/ptr_list.hpp>
+#include <boost/intrusive_ptr.hpp>
+#include "qpid/management/Manageable.h"
+#include "qmf/org/apache/qpid/legacystore/Journal.h"
+
+namespace qpid { namespace sys {
+class Timer;
+}}
+
+namespace mrg {
+namespace msgstore {
+
+class JournalImpl;
+
+class InactivityFireEvent : public qpid::sys::TimerTask
+{
+ JournalImpl* _parent;
+ qpid::sys::Mutex _ife_lock;
+
+ public:
+ InactivityFireEvent(JournalImpl* p, const qpid::sys::Duration timeout);
+ virtual ~InactivityFireEvent() {}
+ void fire();
+ inline void cancel() { qpid::sys::Mutex::ScopedLock sl(_ife_lock); _parent = 0; }
+};
+
+class GetEventsFireEvent : public qpid::sys::TimerTask
+{
+ JournalImpl* _parent;
+ qpid::sys::Mutex _gefe_lock;
+
+ public:
+ GetEventsFireEvent(JournalImpl* p, const qpid::sys::Duration timeout);
+ virtual ~GetEventsFireEvent() {}
+ void fire();
+ inline void cancel() { qpid::sys::Mutex::ScopedLock sl(_gefe_lock); _parent = 0; }
+};
+
+class JournalImpl : public qpid::broker::ExternalQueueStore, public mrg::journal::jcntl, public mrg::journal::aio_callback
+{
+ public:
+ typedef boost::function<void (JournalImpl&)> DeleteCallback;
+
+ private:
+// static qpid::sys::Mutex _static_lock;
+// static u_int32_t cnt;
+
+ qpid::sys::Timer& timer;
+ bool getEventsTimerSetFlag;
+ boost::intrusive_ptr<qpid::sys::TimerTask> getEventsFireEventsPtr;
+ qpid::sys::Mutex _getf_lock;
+ qpid::sys::Mutex _read_lock;
+
+ u_int64_t lastReadRid; // rid of last read msg for loadMsgContent() - detects out-of-order read requests
+ std::vector<u_int64_t> oooRidList; // list of out-of-order rids (greater than current rid) encountered during read sequence
+
+ bool writeActivityFlag;
+ bool flushTriggeredFlag;
+ boost::intrusive_ptr<qpid::sys::TimerTask> inactivityFireEventPtr;
+
+ // temp local vars for loadMsgContent below
+ void* _xidp;
+ void* _datap;
+ size_t _dlen;
+ mrg::journal::data_tok _dtok;
+ bool _external;
+
+ qpid::management::ManagementAgent* _agent;
+ qmf::org::apache::qpid::legacystore::Journal::shared_ptr _mgmtObject;
+ DeleteCallback deleteCallback;
+
+ public:
+
+ JournalImpl(qpid::sys::Timer& timer,
+ const std::string& journalId,
+ const std::string& journalDirectory,
+ const std::string& journalBaseFilename,
+ const qpid::sys::Duration getEventsTimeout,
+ const qpid::sys::Duration flushTimeout,
+ qpid::management::ManagementAgent* agent,
+ DeleteCallback deleteCallback=DeleteCallback() );
+
+ virtual ~JournalImpl();
+
+ void initManagement(qpid::management::ManagementAgent* agent);
+
+ void initialize(const u_int16_t num_jfiles,
+ const bool auto_expand,
+ const u_int16_t ae_max_jfiles,
+ const u_int32_t jfsize_sblks,
+ const u_int16_t wcache_num_pages,
+ const u_int32_t wcache_pgsize_sblks,
+ mrg::journal::aio_callback* const cbp);
+
+ inline void initialize(const u_int16_t num_jfiles,
+ const bool auto_expand,
+ const u_int16_t ae_max_jfiles,
+ const u_int32_t jfsize_sblks,
+ const u_int16_t wcache_num_pages,
+ const u_int32_t wcache_pgsize_sblks) {
+ initialize(num_jfiles, auto_expand, ae_max_jfiles, jfsize_sblks, wcache_num_pages, wcache_pgsize_sblks,
+ this);
+ }
+
+ void recover(const u_int16_t num_jfiles,
+ const bool auto_expand,
+ const u_int16_t ae_max_jfiles,
+ const u_int32_t jfsize_sblks,
+ const u_int16_t wcache_num_pages,
+ const u_int32_t wcache_pgsize_sblks,
+ mrg::journal::aio_callback* const cbp,
+ boost::ptr_list<msgstore::PreparedTransaction>* prep_tx_list_ptr,
+ u_int64_t& highest_rid,
+ u_int64_t queue_id);
+
+ inline void recover(const u_int16_t num_jfiles,
+ const bool auto_expand,
+ const u_int16_t ae_max_jfiles,
+ const u_int32_t jfsize_sblks,
+ const u_int16_t wcache_num_pages,
+ const u_int32_t wcache_pgsize_sblks,
+ boost::ptr_list<msgstore::PreparedTransaction>* prep_tx_list_ptr,
+ u_int64_t& highest_rid,
+ u_int64_t queue_id) {
+ recover(num_jfiles, auto_expand, ae_max_jfiles, jfsize_sblks, wcache_num_pages, wcache_pgsize_sblks,
+ this, prep_tx_list_ptr, highest_rid, queue_id);
+ }
+
+ void recover_complete();
+
+ // Temporary fn to read and save last msg read from journal so it can be assigned
+ // in chunks. To be replaced when coding to do this direct from the journal is ready.
+ // Returns true if the record is extern, false if local.
+ bool loadMsgContent(u_int64_t rid, std::string& data, size_t length, size_t offset = 0);
+
+ // Overrides for write inactivity timer
+ void enqueue_data_record(const void* const data_buff, const size_t tot_data_len,
+ const size_t this_data_len, mrg::journal::data_tok* dtokp,
+ const bool transient = false);
+
+ void enqueue_extern_data_record(const size_t tot_data_len, mrg::journal::data_tok* dtokp,
+ const bool transient = false);
+
+ void enqueue_txn_data_record(const void* const data_buff, const size_t tot_data_len,
+ const size_t this_data_len, mrg::journal::data_tok* dtokp, const std::string& xid,
+ const bool transient = false);
+
+ void enqueue_extern_txn_data_record(const size_t tot_data_len, mrg::journal::data_tok* dtokp,
+ const std::string& xid, const bool transient = false);
+
+ void dequeue_data_record(mrg::journal::data_tok* const dtokp, const bool txn_coml_commit = false);
+
+ void dequeue_txn_data_record(mrg::journal::data_tok* const dtokp, const std::string& xid, const bool txn_coml_commit = false);
+
+ void txn_abort(mrg::journal::data_tok* const dtokp, const std::string& xid);
+
+ void txn_commit(mrg::journal::data_tok* const dtokp, const std::string& xid);
+
+ void stop(bool block_till_aio_cmpl = false);
+
+ // Logging
+ void log(mrg::journal::log_level level, const std::string& log_stmt) const;
+ void log(mrg::journal::log_level level, const char* const log_stmt) const;
+
+ // Overrides for get_events timer
+ mrg::journal::iores flush(const bool block_till_aio_cmpl = false);
+
+ // TimerTask callback
+ void getEventsFire();
+ void flushFire();
+
+ // AIO callbacks
+ virtual void wr_aio_cb(std::vector<mrg::journal::data_tok*>& dtokl);
+ virtual void rd_aio_cb(std::vector<u_int16_t>& pil);
+
+ qpid::management::ManagementObject::shared_ptr GetManagementObject (void) const
+ { return _mgmtObject; }
+
+ qpid::management::Manageable::status_t ManagementMethod (uint32_t,
+ qpid::management::Args&,
+ std::string&);
+
+ void resetDeleteCallback() { deleteCallback = DeleteCallback(); }
+
+ private:
+ void free_read_buffers();
+
+ inline void setGetEventTimer()
+ {
+ getEventsFireEventsPtr->setupNextFire();
+ timer.add(getEventsFireEventsPtr);
+ getEventsTimerSetFlag = true;
+ }
+ void handleIoResult(const mrg::journal::iores r);
+
+ // Management instrumentation callbacks overridden from jcntl
+ inline void instr_incr_outstanding_aio_cnt() {
+ if (_mgmtObject.get() != 0) _mgmtObject->inc_outstandingAIOs();
+ }
+ inline void instr_decr_outstanding_aio_cnt() {
+ if (_mgmtObject.get() != 0) _mgmtObject->dec_outstandingAIOs();
+ }
+
+}; // class JournalImpl
+
+class TplJournalImpl : public JournalImpl
+{
+ public:
+ TplJournalImpl(qpid::sys::Timer& timer,
+ const std::string& journalId,
+ const std::string& journalDirectory,
+ const std::string& journalBaseFilename,
+ const qpid::sys::Duration getEventsTimeout,
+ const qpid::sys::Duration flushTimeout,
+ qpid::management::ManagementAgent* agent) :
+ JournalImpl(timer, journalId, journalDirectory, journalBaseFilename, getEventsTimeout, flushTimeout, agent)
+ {}
+
+ virtual ~TplJournalImpl() {}
+
+ // Special version of read_data_record that ignores transactions - needed when reading the TPL
+ inline mrg::journal::iores read_data_record(void** const datapp, std::size_t& dsize,
+ void** const xidpp, std::size_t& xidsize, bool& transient, bool& external,
+ mrg::journal::data_tok* const dtokp) {
+ return JournalImpl::read_data_record(datapp, dsize, xidpp, xidsize, transient, external, dtokp, true);
+ }
+ inline void read_reset() { _rmgr.invalidate(); }
+}; // class TplJournalImpl
+
+} // namespace msgstore
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JOURNALIMPL_H
diff --git a/qpid/cpp/src/qpid/legacystore/MessageStoreImpl.cpp b/qpid/cpp/src/qpid/legacystore/MessageStoreImpl.cpp
new file mode 100644
index 0000000000..7a1fea95d5
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/MessageStoreImpl.cpp
@@ -0,0 +1,1730 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/legacystore/MessageStoreImpl.h"
+
+#include "db-inc.h"
+#include "qpid/broker/QueueSettings.h"
+#include "qpid/legacystore/BindingDbt.h"
+#include "qpid/legacystore/BufferValue.h"
+#include "qpid/legacystore/IdDbt.h"
+#include "qpid/legacystore/jrnl/txn_map.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/log/Statement.h"
+#include "qmf/org/apache/qpid/legacystore/Package.h"
+#include "qpid/legacystore/StoreException.h"
+#include <dirent.h>
+
+#define MAX_AIO_SLEEPS 100000 // tot: ~1 sec
+#define AIO_SLEEP_TIME_US 10 // 0.01 ms
+
+namespace _qmf = qmf::org::apache::qpid::legacystore;
+
+namespace mrg {
+namespace msgstore {
+
+
+const std::string MessageStoreImpl::storeTopLevelDir("rhm"); // Sets the top-level store dir name
+// FIXME aconway 2010-03-09: was 10
+qpid::sys::Duration MessageStoreImpl::defJournalGetEventsTimeout(1 * qpid::sys::TIME_MSEC); // 10ms
+qpid::sys::Duration MessageStoreImpl::defJournalFlushTimeout(500 * qpid::sys::TIME_MSEC); // 0.5s
+qpid::sys::Mutex TxnCtxt::globalSerialiser;
+
+MessageStoreImpl::TplRecoverStruct::TplRecoverStruct(const u_int64_t _rid,
+ const bool _deq_flag,
+ const bool _commit_flag,
+ const bool _tpc_flag) :
+ rid(_rid),
+ deq_flag(_deq_flag),
+ commit_flag(_commit_flag),
+ tpc_flag(_tpc_flag)
+{}
+
+MessageStoreImpl::MessageStoreImpl(qpid::broker::Broker* broker_, const char* envpath) :
+ numJrnlFiles(0),
+ autoJrnlExpand(false),
+ autoJrnlExpandMaxFiles(0),
+ jrnlFsizeSblks(0),
+ truncateFlag(false),
+ wCachePgSizeSblks(0),
+ wCacheNumPages(0),
+ tplNumJrnlFiles(0),
+ tplJrnlFsizeSblks(0),
+ tplWCachePgSizeSblks(0),
+ tplWCacheNumPages(0),
+ highestRid(0),
+ isInit(false),
+ envPath(envpath),
+ broker(broker_),
+ mgmtObject(),
+ agent(0)
+{}
+
+u_int16_t MessageStoreImpl::chkJrnlNumFilesParam(const u_int16_t param, const std::string paramName)
+{
+ if (param < JRNL_MIN_NUM_FILES || param > JRNL_MAX_NUM_FILES) {
+ std::ostringstream oss;
+ oss << "Parameter " << paramName << ": Illegal number of store journal files (" << param << "), must be " << JRNL_MIN_NUM_FILES << " to " << JRNL_MAX_NUM_FILES << " inclusive.";
+ THROW_STORE_EXCEPTION(oss.str());
+ }
+ return param;
+}
+
+u_int32_t MessageStoreImpl::chkJrnlFileSizeParam(const u_int32_t param, const std::string paramName, const u_int32_t wCachePgSizeSblks)
+{
+ if (param < (JRNL_MIN_FILE_SIZE / JRNL_RMGR_PAGE_SIZE) || (param > JRNL_MAX_FILE_SIZE / JRNL_RMGR_PAGE_SIZE)) {
+ std::ostringstream oss;
+ oss << "Parameter " << paramName << ": Illegal store journal file size (" << param << "), must be " << JRNL_MIN_FILE_SIZE / JRNL_RMGR_PAGE_SIZE << " to " << JRNL_MAX_FILE_SIZE / JRNL_RMGR_PAGE_SIZE << " inclusive.";
+ THROW_STORE_EXCEPTION(oss.str());
+ }
+ if (wCachePgSizeSblks > param * JRNL_RMGR_PAGE_SIZE) {
+ std::ostringstream oss;
+ oss << "Cannot create store with file size less than write page cache size. [file size = " << param << " (" << (param * JRNL_RMGR_PAGE_SIZE / 2) << " kB); write page cache = " << (wCachePgSizeSblks / 2) << " kB]";
+ THROW_STORE_EXCEPTION(oss.str());
+ }
+ return param;
+}
+
+u_int32_t MessageStoreImpl::chkJrnlWrPageCacheSize(const u_int32_t param, const std::string paramName, const u_int16_t jrnlFsizePgs)
+{
+ u_int32_t p = param;
+
+ if (jrnlFsizePgs == 1 && p > 64 ) {
+ p = 64;
+ QPID_LOG(warning, "parameter " << paramName << " (" << param << ") cannot set a page size greater than the journal file size; changing this parameter to the journal file size (" << p << ")");
+ }
+ else if (p == 0) {
+ // For zero value, use default
+ p = JRNL_WMGR_DEF_PAGE_SIZE * JRNL_DBLK_SIZE * JRNL_SBLK_SIZE / 1024;
+ QPID_LOG(warning, "parameter " << paramName << " (" << param << ") must be a power of 2 between 1 and 128; changing this parameter to default value (" << p << ")");
+ } else if ( p > 128 || (p & (p-1)) ) {
+ // For any positive value that is not a power of 2, use closest value
+ if (p < 6) p = 4;
+ else if (p < 12) p = 8;
+ else if (p < 24) p = 16;
+ else if (p < 48) p = 32;
+ else if (p < 96) p = 64;
+ else p = 128;
+ QPID_LOG(warning, "parameter " << paramName << " (" << param << ") must be a power of 2 between 1 and 128; changing this parameter to closest allowable value (" << p << ")");
+ }
+ return p;
+}
+
+u_int16_t MessageStoreImpl::getJrnlWrNumPages(const u_int32_t wrPageSizeKib)
+{
+ u_int32_t wrPageSizeSblks = wrPageSizeKib * 1024 / JRNL_DBLK_SIZE / JRNL_SBLK_SIZE; // convert from KiB to number sblks
+ u_int32_t defTotWCacheSize = JRNL_WMGR_DEF_PAGE_SIZE * JRNL_WMGR_DEF_PAGES; // in sblks. Currently 2014 sblks (1 MiB).
+ switch (wrPageSizeKib)
+ {
+ case 1:
+ case 2:
+ case 4:
+ // 256 KiB total cache
+ return defTotWCacheSize / wrPageSizeSblks / 4;
+ case 8:
+ case 16:
+ // 512 KiB total cache
+ return defTotWCacheSize / wrPageSizeSblks / 2;
+ default: // 32, 64, 128
+ // 1 MiB total cache
+ return defTotWCacheSize / wrPageSizeSblks;
+ }
+}
+
+void MessageStoreImpl::chkJrnlAutoExpandOptions(const StoreOptions* opts,
+ bool& autoJrnlExpand,
+ u_int16_t& autoJrnlExpandMaxFiles,
+ const std::string& autoJrnlExpandMaxFilesParamName,
+ const u_int16_t numJrnlFiles,
+ const std::string& numJrnlFilesParamName)
+{
+ if (!opts->autoJrnlExpand) {
+ // auto-expand disabled
+ autoJrnlExpand = false;
+ autoJrnlExpandMaxFiles = 0;
+ return;
+ }
+ u_int16_t p = opts->autoJrnlExpandMaxFiles;
+ if (numJrnlFiles == JRNL_MAX_NUM_FILES) {
+ // num-jfiles at max; disable auto-expand
+ autoJrnlExpand = false;
+ autoJrnlExpandMaxFiles = 0;
+ QPID_LOG(warning, "parameter " << autoJrnlExpandMaxFilesParamName << " (" << p << ") must be higher than parameter "
+ << numJrnlFilesParamName << " (" << numJrnlFiles << ") which is at the maximum allowable value; disabling auto-expand.");
+ return;
+ }
+ if (p > JRNL_MAX_NUM_FILES) {
+ // auto-expand-max-jfiles higher than max allowable, adjust
+ autoJrnlExpand = true;
+ autoJrnlExpandMaxFiles = JRNL_MAX_NUM_FILES;
+ QPID_LOG(warning, "parameter " << autoJrnlExpandMaxFilesParamName << " (" << p << ") is above allowable maximum ("
+ << JRNL_MAX_NUM_FILES << "); changing this parameter to maximum value.");
+ return;
+ }
+ if (p && p == defAutoJrnlExpandMaxFiles && numJrnlFiles != defTplNumJrnlFiles) {
+ // num-jfiles is different from the default AND max-auto-expand-jfiles is still at default
+ // change value of max-auto-expand-jfiles
+ autoJrnlExpand = true;
+ if (2 * numJrnlFiles <= JRNL_MAX_NUM_FILES) {
+ autoJrnlExpandMaxFiles = 2 * numJrnlFiles <= JRNL_MAX_NUM_FILES ? 2 * numJrnlFiles : JRNL_MAX_NUM_FILES;
+ QPID_LOG(warning, "parameter " << autoJrnlExpandMaxFilesParamName << " adjusted from its default value ("
+ << defAutoJrnlExpandMaxFiles << ") to twice that of parameter " << numJrnlFilesParamName << " (" << autoJrnlExpandMaxFiles << ").");
+ } else {
+ autoJrnlExpandMaxFiles = 2 * numJrnlFiles <= JRNL_MAX_NUM_FILES ? 2 * numJrnlFiles : JRNL_MAX_NUM_FILES;
+ QPID_LOG(warning, "parameter " << autoJrnlExpandMaxFilesParamName << " adjusted from its default to maximum allowable value ("
+ << JRNL_MAX_NUM_FILES << ") because of the value of " << numJrnlFilesParamName << " (" << numJrnlFiles << ").");
+ }
+ return;
+ }
+ // No adjustments req'd, set values
+ autoJrnlExpand = true;
+ autoJrnlExpandMaxFiles = p;
+}
+
+void MessageStoreImpl::initManagement ()
+{
+ if (broker != 0) {
+ agent = broker->getManagementAgent();
+ if (agent != 0) {
+ _qmf::Package packageInitializer(agent);
+ mgmtObject = _qmf::Store::shared_ptr (
+ new _qmf::Store(agent, this, broker));
+
+ mgmtObject->set_location(storeDir);
+ mgmtObject->set_defaultInitialFileCount(numJrnlFiles);
+ mgmtObject->set_defaultDataFileSize(jrnlFsizeSblks / JRNL_RMGR_PAGE_SIZE);
+ mgmtObject->set_tplIsInitialized(false);
+ mgmtObject->set_tplDirectory(getTplBaseDir());
+ mgmtObject->set_tplWritePageSize(tplWCachePgSizeSblks * JRNL_SBLK_SIZE * JRNL_DBLK_SIZE);
+ mgmtObject->set_tplWritePages(tplWCacheNumPages);
+ mgmtObject->set_tplInitialFileCount(tplNumJrnlFiles);
+ mgmtObject->set_tplDataFileSize(tplJrnlFsizeSblks * JRNL_SBLK_SIZE * JRNL_DBLK_SIZE);
+ mgmtObject->set_tplCurrentFileCount(tplNumJrnlFiles);
+
+ agent->addObject(mgmtObject, 0, true);
+
+ // Initialize all existing queues (ie those recovered before management was initialized)
+ for (JournalListMapItr i=journalList.begin(); i!=journalList.end(); i++) {
+ i->second->initManagement(agent);
+ }
+ }
+ }
+}
+
+bool MessageStoreImpl::init(const qpid::Options* options)
+{
+ // Extract and check options
+ const StoreOptions* opts = static_cast<const StoreOptions*>(options);
+ u_int16_t numJrnlFiles = chkJrnlNumFilesParam(opts->numJrnlFiles, "num-jfiles");
+ u_int32_t jrnlFsizePgs = chkJrnlFileSizeParam(opts->jrnlFsizePgs, "jfile-size-pgs");
+ u_int32_t jrnlWrCachePageSizeKib = chkJrnlWrPageCacheSize(opts->wCachePageSizeKib, "wcache-page-size", jrnlFsizePgs);
+ u_int16_t tplNumJrnlFiles = chkJrnlNumFilesParam(opts->tplNumJrnlFiles, "tpl-num-jfiles");
+ u_int32_t tplJrnlFSizePgs = chkJrnlFileSizeParam(opts->tplJrnlFsizePgs, "tpl-jfile-size-pgs");
+ u_int32_t tplJrnlWrCachePageSizeKib = chkJrnlWrPageCacheSize(opts->tplWCachePageSizeKib, "tpl-wcache-page-size", tplJrnlFSizePgs);
+ bool autoJrnlExpand;
+ u_int16_t autoJrnlExpandMaxFiles;
+ chkJrnlAutoExpandOptions(opts, autoJrnlExpand, autoJrnlExpandMaxFiles, "auto-expand-max-jfiles", numJrnlFiles, "num-jfiles");
+
+ // Pass option values to init(...)
+ return init(opts->storeDir, numJrnlFiles, jrnlFsizePgs, opts->truncateFlag, jrnlWrCachePageSizeKib, tplNumJrnlFiles, tplJrnlFSizePgs, tplJrnlWrCachePageSizeKib, autoJrnlExpand, autoJrnlExpandMaxFiles);
+}
+
+// These params, taken from options, are assumed to be correct and verified
+bool MessageStoreImpl::init(const std::string& dir,
+ u_int16_t jfiles,
+ u_int32_t jfileSizePgs,
+ const bool truncateFlag,
+ u_int32_t wCachePageSizeKib,
+ u_int16_t tplJfiles,
+ u_int32_t tplJfileSizePgs,
+ u_int32_t tplWCachePageSizeKib,
+ bool autoJExpand,
+ u_int16_t autoJExpandMaxFiles)
+{
+ if (isInit) return true;
+
+ // Set geometry members (converting to correct units where req'd)
+ numJrnlFiles = jfiles;
+ jrnlFsizeSblks = jfileSizePgs * JRNL_RMGR_PAGE_SIZE;
+ wCachePgSizeSblks = wCachePageSizeKib * 1024 / JRNL_DBLK_SIZE / JRNL_SBLK_SIZE; // convert from KiB to number sblks
+ wCacheNumPages = getJrnlWrNumPages(wCachePageSizeKib);
+ tplNumJrnlFiles = tplJfiles;
+ tplJrnlFsizeSblks = tplJfileSizePgs * JRNL_RMGR_PAGE_SIZE;
+ tplWCachePgSizeSblks = tplWCachePageSizeKib * 1024 / JRNL_DBLK_SIZE / JRNL_SBLK_SIZE; // convert from KiB to number sblks
+ tplWCacheNumPages = getJrnlWrNumPages(tplWCachePageSizeKib);
+ autoJrnlExpand = autoJExpand;
+ autoJrnlExpandMaxFiles = autoJExpandMaxFiles;
+ if (dir.size()>0) storeDir = dir;
+
+ if (truncateFlag)
+ truncateInit(false);
+ else
+ init();
+
+ QPID_LOG(notice, "Store module initialized; store-dir=" << dir);
+ QPID_LOG(info, "> Default files per journal: " << jfiles);
+// TODO: Uncomment these lines when auto-expand is enabled.
+// QPID_LOG(info, "> Auto-expand " << (autoJrnlExpand ? "enabled" : "disabled"));
+// if (autoJrnlExpand) QPID_LOG(info, "> Max auto-expand journal files: " << autoJrnlExpandMaxFiles);
+ QPID_LOG(info, "> Default journal file size: " << jfileSizePgs << " (wpgs)");
+ QPID_LOG(info, "> Default write cache page size: " << wCachePageSizeKib << " (KiB)");
+ QPID_LOG(info, "> Default number of write cache pages: " << wCacheNumPages);
+ QPID_LOG(info, "> TPL files per journal: " << tplNumJrnlFiles);
+ QPID_LOG(info, "> TPL journal file size: " << tplJfileSizePgs << " (wpgs)");
+ QPID_LOG(info, "> TPL write cache page size: " << tplWCachePageSizeKib << " (KiB)");
+ QPID_LOG(info, "> TPL number of write cache pages: " << tplWCacheNumPages);
+
+ return isInit;
+}
+
+void MessageStoreImpl::init()
+{
+ const int retryMax = 3;
+ int bdbRetryCnt = 0;
+ do {
+ if (bdbRetryCnt++ > 0)
+ {
+ closeDbs();
+ ::usleep(1000000); // 1 sec delay
+ QPID_LOG(error, "Previoius BDB store initialization failed, retrying (" << bdbRetryCnt << " of " << retryMax << ")...");
+ }
+
+ try {
+ journal::jdir::create_dir(getBdbBaseDir());
+
+ dbenv.reset(new DbEnv(0));
+ dbenv->set_errpfx("msgstore");
+ dbenv->set_lg_regionmax(256000); // default = 65000
+ dbenv->open(getBdbBaseDir().c_str(), DB_THREAD | DB_CREATE | DB_INIT_TXN | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_USE_ENVIRON | DB_RECOVER, 0);
+
+ // Databases are constructed here instead of the constructor so that the DB_RECOVER flag can be used
+ // against the database environment. Recover can only be performed if no databases have been created
+ // against the environment at the time of recovery, as recovery invalidates the environment.
+ queueDb.reset(new Db(dbenv.get(), 0));
+ dbs.push_back(queueDb);
+ configDb.reset(new Db(dbenv.get(), 0));
+ dbs.push_back(configDb);
+ exchangeDb.reset(new Db(dbenv.get(), 0));
+ dbs.push_back(exchangeDb);
+ mappingDb.reset(new Db(dbenv.get(), 0));
+ dbs.push_back(mappingDb);
+ bindingDb.reset(new Db(dbenv.get(), 0));
+ dbs.push_back(bindingDb);
+ generalDb.reset(new Db(dbenv.get(), 0));
+ dbs.push_back(generalDb);
+
+ TxnCtxt txn;
+ txn.begin(dbenv.get(), false);
+ try {
+ open(queueDb, txn.get(), "queues.db", false);
+ open(configDb, txn.get(), "config.db", false);
+ open(exchangeDb, txn.get(), "exchanges.db", false);
+ open(mappingDb, txn.get(), "mappings.db", true);
+ open(bindingDb, txn.get(), "bindings.db", true);
+ open(generalDb, txn.get(), "general.db", false);
+ txn.commit();
+ } catch (...) { txn.abort(); throw; }
+ // NOTE: during normal initialization, agent == 0 because the store is initialized before the management infrastructure.
+ // However during a truncated initialization in a cluster, agent != 0. We always pass 0 as the agent for the
+ // TplStore to keep things consistent in a cluster. See https://bugzilla.redhat.com/show_bug.cgi?id=681026
+ tplStorePtr.reset(new TplJournalImpl(broker->getTimer(), "TplStore", getTplBaseDir(), "tpl", defJournalGetEventsTimeout, defJournalFlushTimeout, 0));
+ isInit = true;
+ } catch (const DbException& e) {
+ if (e.get_errno() == DB_VERSION_MISMATCH)
+ {
+ QPID_LOG(error, "Database environment mismatch: This version of db4 does not match that which created the store database.: " << e.what());
+ THROW_STORE_EXCEPTION_2("Database environment mismatch: This version of db4 does not match that which created the store database. "
+ "(If recovery is not important, delete the contents of the store directory. Otherwise, try upgrading the database using "
+ "db_upgrade or using db_recover - but the db4-utils package must also be installed to use these utilities.)", e);
+ }
+ QPID_LOG(error, "BDB exception occurred while initializing store: " << e.what());
+ if (bdbRetryCnt >= retryMax)
+ THROW_STORE_EXCEPTION_2("BDB exception occurred while initializing store", e);
+ } catch (const StoreException&) {
+ throw;
+ } catch (const journal::jexception& e) {
+ QPID_LOG(error, "Journal Exception occurred while initializing store: " << e);
+ THROW_STORE_EXCEPTION_2("Journal Exception occurred while initializing store", e.what());
+ } catch (...) {
+ QPID_LOG(error, "Unknown exception occurred while initializing store.");
+ throw;
+ }
+ } while (!isInit);
+}
+
+void MessageStoreImpl::finalize()
+{
+ if (tplStorePtr.get() && tplStorePtr->is_ready()) tplStorePtr->stop(true);
+ {
+ qpid::sys::Mutex::ScopedLock sl(journalListLock);
+ for (JournalListMapItr i = journalList.begin(); i != journalList.end(); i++)
+ {
+ JournalImpl* jQueue = i->second;
+ jQueue->resetDeleteCallback();
+ if (jQueue->is_ready()) jQueue->stop(true);
+ }
+ }
+
+ if (mgmtObject.get() != 0) {
+ mgmtObject->resourceDestroy();
+ mgmtObject.reset();
+ }
+}
+
+void MessageStoreImpl::truncateInit(const bool saveStoreContent)
+{
+ if (isInit) {
+ {
+ qpid::sys::Mutex::ScopedLock sl(journalListLock);
+ if (journalList.size()) { // check no queues exist
+ std::ostringstream oss;
+ oss << "truncateInit() called with " << journalList.size() << " queues still in existence";
+ THROW_STORE_EXCEPTION(oss.str());
+ }
+ }
+ closeDbs();
+ dbs.clear();
+ if (tplStorePtr->is_ready()) tplStorePtr->stop(true);
+ dbenv->close(0);
+ isInit = false;
+ }
+ std::ostringstream oss;
+ oss << storeDir << "/" << storeTopLevelDir;
+ if (saveStoreContent) {
+ std::string dir = mrg::journal::jdir::push_down(storeDir, storeTopLevelDir, "cluster");
+ QPID_LOG(notice, "Store directory " << oss.str() << " was pushed down (saved) into directory " << dir << ".");
+ } else {
+ mrg::journal::jdir::delete_dir(oss.str().c_str());
+ QPID_LOG(notice, "Store directory " << oss.str() << " was truncated.");
+ }
+ init();
+}
+
+void MessageStoreImpl::chkTplStoreInit()
+{
+ // Prevent multiple threads from late-initializing the TPL
+ qpid::sys::Mutex::ScopedLock sl(tplInitLock);
+ if (!tplStorePtr->is_ready()) {
+ journal::jdir::create_dir(getTplBaseDir());
+ tplStorePtr->initialize(tplNumJrnlFiles, false, 0, tplJrnlFsizeSblks, tplWCacheNumPages, tplWCachePgSizeSblks);
+ if (mgmtObject.get() != 0) mgmtObject->set_tplIsInitialized(true);
+ }
+}
+
+void MessageStoreImpl::open(db_ptr db,
+ DbTxn* txn,
+ const char* file,
+ bool dupKey)
+{
+ if(dupKey) db->set_flags(DB_DUPSORT);
+ db->open(txn, file, 0, DB_BTREE, DB_CREATE | DB_THREAD, 0);
+}
+
+void MessageStoreImpl::closeDbs()
+{
+ for (std::list<db_ptr >::iterator i = dbs.begin(); i != dbs.end(); i++) {
+ (*i)->close(0);
+ }
+ dbs.clear();
+}
+
+MessageStoreImpl::~MessageStoreImpl()
+{
+ if (mgmtObject.get() != 0)
+ mgmtObject->debugStats("destroying");
+ finalize();
+ try {
+ closeDbs();
+ } catch (const DbException& e) {
+ QPID_LOG(error, "Error closing BDB databases: " << e.what());
+ } catch (const journal::jexception& e) {
+ QPID_LOG(error, "Error: " << e.what());
+ } catch (const std::exception& e) {
+ QPID_LOG(error, "Error: " << e.what());
+ } catch (...) {
+ QPID_LOG(error, "Unknown error in MessageStoreImpl::~MessageStoreImpl()");
+ }
+
+ if (mgmtObject.get() != 0) {
+ mgmtObject->resourceDestroy();
+ mgmtObject.reset();
+ }
+}
+
+void MessageStoreImpl::create(qpid::broker::PersistableQueue& queue,
+ const qpid::framing::FieldTable& args)
+{
+ checkInit();
+ if (queue.getPersistenceId()) {
+ THROW_STORE_EXCEPTION("Queue already created: " + queue.getName());
+ }
+ JournalImpl* jQueue = 0;
+ qpid::framing::FieldTable::ValuePtr value;
+
+ u_int16_t localFileCount = numJrnlFiles;
+ bool localAutoExpandFlag = autoJrnlExpand;
+ u_int16_t localAutoExpandMaxFileCount = autoJrnlExpandMaxFiles;
+ u_int32_t localFileSizeSblks = jrnlFsizeSblks;
+
+ value = args.get("qpid.file_count");
+ if (value.get() != 0 && !value->empty() && value->convertsTo<int>())
+ localFileCount = chkJrnlNumFilesParam((u_int16_t) value->get<int>(), "qpid.file_count");
+
+ value = args.get("qpid.file_size");
+ if (value.get() != 0 && !value->empty() && value->convertsTo<int>())
+ localFileSizeSblks = chkJrnlFileSizeParam((u_int32_t) value->get<int>(), "qpid.file_size", wCachePgSizeSblks) * JRNL_RMGR_PAGE_SIZE;
+
+ if (queue.getName().size() == 0)
+ {
+ QPID_LOG(error, "Cannot create store for empty (null) queue name - ignoring and attempting to continue.");
+ return;
+ }
+
+ jQueue = new JournalImpl(broker->getTimer(), queue.getName(), getJrnlDir(queue), std::string("JournalData"),
+ defJournalGetEventsTimeout, defJournalFlushTimeout, agent,
+ boost::bind(&MessageStoreImpl::journalDeleted, this, _1));
+ {
+ qpid::sys::Mutex::ScopedLock sl(journalListLock);
+ journalList[queue.getName()]=jQueue;
+ }
+
+ value = args.get("qpid.auto_expand");
+ if (value.get() != 0 && !value->empty() && value->convertsTo<bool>())
+ localAutoExpandFlag = (bool) value->get<bool>();
+
+ value = args.get("qpid.auto_expand_max_jfiles");
+ if (value.get() != 0 && !value->empty() && value->convertsTo<int>())
+ localAutoExpandMaxFileCount = (u_int16_t) value->get<int>();
+
+ queue.setExternalQueueStore(dynamic_cast<qpid::broker::ExternalQueueStore*>(jQueue));
+ try {
+ // init will create the deque's for the init...
+ jQueue->initialize(localFileCount, localAutoExpandFlag, localAutoExpandMaxFileCount, localFileSizeSblks, wCacheNumPages, wCachePgSizeSblks);
+ } catch (const journal::jexception& e) {
+ THROW_STORE_EXCEPTION(std::string("Queue ") + queue.getName() + ": create() failed: " + e.what());
+ }
+ try {
+ if (!create(queueDb, queueIdSequence, queue)) {
+ THROW_STORE_EXCEPTION("Queue already exists: " + queue.getName());
+ }
+ } catch (const DbException& e) {
+ THROW_STORE_EXCEPTION_2("Error creating queue named " + queue.getName(), e);
+ }
+}
+
+void MessageStoreImpl::destroy(qpid::broker::PersistableQueue& queue)
+{
+ checkInit();
+ destroy(queueDb, queue);
+ deleteBindingsForQueue(queue);
+ qpid::broker::ExternalQueueStore* eqs = queue.getExternalQueueStore();
+ if (eqs) {
+ JournalImpl* jQueue = static_cast<JournalImpl*>(eqs);
+ jQueue->delete_jrnl_files();
+ queue.setExternalQueueStore(0); // will delete the journal if exists
+ {
+ qpid::sys::Mutex::ScopedLock sl(journalListLock);
+ journalList.erase(queue.getName());
+ }
+ }
+}
+
+void MessageStoreImpl::create(const qpid::broker::PersistableExchange& exchange,
+ const qpid::framing::FieldTable& /*args*/)
+{
+ checkInit();
+ if (exchange.getPersistenceId()) {
+ THROW_STORE_EXCEPTION("Exchange already created: " + exchange.getName());
+ }
+ try {
+ if (!create(exchangeDb, exchangeIdSequence, exchange)) {
+ THROW_STORE_EXCEPTION("Exchange already exists: " + exchange.getName());
+ }
+ } catch (const DbException& e) {
+ THROW_STORE_EXCEPTION_2("Error creating exchange named " + exchange.getName(), e);
+ }
+}
+
+void MessageStoreImpl::destroy(const qpid::broker::PersistableExchange& exchange)
+{
+ checkInit();
+ destroy(exchangeDb, exchange);
+ //need to also delete bindings
+ IdDbt key(exchange.getPersistenceId());
+ bindingDb->del(0, &key, DB_AUTO_COMMIT);
+}
+
+void MessageStoreImpl::create(const qpid::broker::PersistableConfig& general)
+{
+ checkInit();
+ if (general.getPersistenceId()) {
+ THROW_STORE_EXCEPTION("General configuration item already created");
+ }
+ try {
+ if (!create(generalDb, generalIdSequence, general)) {
+ THROW_STORE_EXCEPTION("General configuration already exists");
+ }
+ } catch (const DbException& e) {
+ THROW_STORE_EXCEPTION_2("Error creating general configuration", e);
+ }
+}
+
+void MessageStoreImpl::destroy(const qpid::broker::PersistableConfig& general)
+{
+ checkInit();
+ destroy(generalDb, general);
+}
+
+bool MessageStoreImpl::create(db_ptr db,
+ IdSequence& seq,
+ const qpid::broker::Persistable& p)
+{
+ u_int64_t id (seq.next());
+ Dbt key(&id, sizeof(id));
+ BufferValue value (p);
+
+ int status;
+ TxnCtxt txn;
+ txn.begin(dbenv.get(), true);
+ try {
+ status = db->put(txn.get(), &key, &value, DB_NOOVERWRITE);
+ txn.commit();
+ } catch (...) {
+ txn.abort();
+ throw;
+ }
+ if (status == DB_KEYEXIST) {
+ return false;
+ } else {
+ p.setPersistenceId(id);
+ return true;
+ }
+}
+
+void MessageStoreImpl::destroy(db_ptr db, const qpid::broker::Persistable& p)
+{
+ qpid::sys::Mutex::ScopedLock sl(bdbLock);
+ IdDbt key(p.getPersistenceId());
+ db->del(0, &key, DB_AUTO_COMMIT);
+}
+
+
+void MessageStoreImpl::bind(const qpid::broker::PersistableExchange& e,
+ const qpid::broker::PersistableQueue& q,
+ const std::string& k,
+ const qpid::framing::FieldTable& a)
+{
+ checkInit();
+ IdDbt key(e.getPersistenceId());
+ BindingDbt value(e, q, k, a);
+ TxnCtxt txn;
+ txn.begin(dbenv.get(), true);
+ try {
+ put(bindingDb, txn.get(), key, value);
+ txn.commit();
+ } catch (...) {
+ txn.abort();
+ throw;
+ }
+}
+
+void MessageStoreImpl::unbind(const qpid::broker::PersistableExchange& e,
+ const qpid::broker::PersistableQueue& q,
+ const std::string& k,
+ const qpid::framing::FieldTable&)
+{
+ checkInit();
+ deleteBinding(e, q, k);
+}
+
+void MessageStoreImpl::recover(qpid::broker::RecoveryManager& registry)
+{
+ checkInit();
+ txn_list prepared;
+ recoverLockedMappings(prepared);
+
+ queue_index queues;//id->queue
+ exchange_index exchanges;//id->exchange
+ message_index messages;//id->message
+
+ TxnCtxt txn;
+ txn.begin(dbenv.get(), false);
+ try {
+ //read all queues, calls recoversMessages
+ recoverQueues(txn, registry, queues, prepared, messages);
+
+ //recover exchange & bindings:
+ recoverExchanges(txn, registry, exchanges);
+ recoverBindings(txn, exchanges, queues);
+
+ //recover general-purpose configuration
+ recoverGeneral(txn, registry);
+
+ txn.commit();
+ } catch (const DbException& e) {
+ txn.abort();
+ THROW_STORE_EXCEPTION_2("Error on recovery", e);
+ } catch (...) {
+ txn.abort();
+ throw;
+ }
+
+ //recover transactions:
+ for (txn_list::iterator i = prepared.begin(); i != prepared.end(); i++) {
+ const PreparedTransaction pt = *i;
+ if (mgmtObject.get() != 0) {
+ mgmtObject->inc_tplTransactionDepth();
+ mgmtObject->inc_tplTxnPrepares();
+ }
+
+ std::string xid = pt.xid;
+
+ // Restore data token state in TxnCtxt
+ TplRecoverMapCitr citr = tplRecoverMap.find(xid);
+ if (citr == tplRecoverMap.end()) THROW_STORE_EXCEPTION("XID not found in tplRecoverMap");
+
+ // If a record is found that is dequeued but not committed/aborted from tplStore, then a complete() call
+ // was interrupted part way through committing/aborting the impacted queues. Complete this process.
+ bool incomplTplTxnFlag = citr->second.deq_flag;
+
+ if (citr->second.tpc_flag) {
+ // Dtx (2PC) transaction
+ TPCTxnCtxt* tpcc = new TPCTxnCtxt(xid, &messageIdSequence);
+ std::auto_ptr<qpid::broker::TPCTransactionContext> txn(tpcc);
+ tpcc->recoverDtok(citr->second.rid, xid);
+ tpcc->prepare(tplStorePtr.get());
+
+ qpid::broker::RecoverableTransaction::shared_ptr dtx;
+ if (!incomplTplTxnFlag) dtx = registry.recoverTransaction(xid, txn);
+ if (pt.enqueues.get()) {
+ for (LockedMappings::iterator j = pt.enqueues->begin(); j != pt.enqueues->end(); j++) {
+ tpcc->addXidRecord(queues[j->first]->getExternalQueueStore());
+ if (!incomplTplTxnFlag) dtx->enqueue(queues[j->first], messages[j->second]);
+ }
+ }
+ if (pt.dequeues.get()) {
+ for (LockedMappings::iterator j = pt.dequeues->begin(); j != pt.dequeues->end(); j++) {
+ tpcc->addXidRecord(queues[j->first]->getExternalQueueStore());
+ if (!incomplTplTxnFlag) dtx->dequeue(queues[j->first], messages[j->second]);
+ }
+ }
+
+ if (incomplTplTxnFlag) {
+ tpcc->complete(citr->second.commit_flag);
+ }
+ } else {
+ // Local (1PC) transaction
+ boost::shared_ptr<TxnCtxt> opcc(new TxnCtxt(xid, &messageIdSequence));
+ opcc->recoverDtok(citr->second.rid, xid);
+ opcc->prepare(tplStorePtr.get());
+
+ if (pt.enqueues.get()) {
+ for (LockedMappings::iterator j = pt.enqueues->begin(); j != pt.enqueues->end(); j++) {
+ opcc->addXidRecord(queues[j->first]->getExternalQueueStore());
+ }
+ }
+ if (pt.dequeues.get()) {
+ for (LockedMappings::iterator j = pt.dequeues->begin(); j != pt.dequeues->end(); j++) {
+ opcc->addXidRecord(queues[j->first]->getExternalQueueStore());
+ }
+ }
+ if (incomplTplTxnFlag) {
+ opcc->complete(citr->second.commit_flag);
+ } else {
+ completed(*opcc.get(), citr->second.commit_flag);
+ }
+ }
+ }
+ registry.recoveryComplete();
+}
+
+void MessageStoreImpl::recoverQueues(TxnCtxt& txn,
+ qpid::broker::RecoveryManager& registry,
+ queue_index& queue_index,
+ txn_list& prepared,
+ message_index& messages)
+{
+ Cursor queues;
+ queues.open(queueDb, txn.get());
+
+ u_int64_t maxQueueId(1);
+
+ IdDbt key;
+ Dbt value;
+ //read all queues
+ while (queues.next(key, value)) {
+ qpid::framing::Buffer buffer(reinterpret_cast<char*>(value.get_data()), value.get_size());
+ //create a Queue instance
+ qpid::broker::RecoverableQueue::shared_ptr queue = registry.recoverQueue(buffer);
+ //set the persistenceId and update max as required
+ queue->setPersistenceId(key.id);
+
+ const std::string queueName = queue->getName().c_str();
+ JournalImpl* jQueue = 0;
+ if (queueName.size() == 0)
+ {
+ QPID_LOG(error, "Cannot recover empty (null) queue name - ignoring and attempting to continue.");
+ break;
+ }
+ jQueue = new JournalImpl(broker->getTimer(), queueName, getJrnlHashDir(queueName), std::string("JournalData"),
+ defJournalGetEventsTimeout, defJournalFlushTimeout, agent,
+ boost::bind(&MessageStoreImpl::journalDeleted, this, _1));
+ {
+ qpid::sys::Mutex::ScopedLock sl(journalListLock);
+ journalList[queueName] = jQueue;
+ }
+ queue->setExternalQueueStore(dynamic_cast<qpid::broker::ExternalQueueStore*>(jQueue));
+
+ try
+ {
+ long rcnt = 0L; // recovered msg count
+ long idcnt = 0L; // in-doubt msg count
+ u_int64_t thisHighestRid = 0ULL;
+ jQueue->recover(numJrnlFiles, autoJrnlExpand, autoJrnlExpandMaxFiles, jrnlFsizeSblks, wCacheNumPages, wCachePgSizeSblks, &prepared, thisHighestRid, key.id); // start recovery
+
+ // Check for changes to queue store settings qpid.file_count and qpid.file_size resulting
+ // from recovery of a store that has had its size changed externally by the resize utility.
+ // If so, update the queue store settings so that QMF queries will reflect the new values.
+ const qpid::framing::FieldTable& storeargs = queue->getSettings().storeSettings;
+ qpid::framing::FieldTable::ValuePtr value;
+ value = storeargs.get("qpid.file_count");
+ if (value.get() != 0 && !value->empty() && value->convertsTo<int>() && (u_int16_t)value->get<int>() != jQueue->num_jfiles()) {
+ queue->addArgument("qpid.file_count", jQueue->num_jfiles());
+ }
+ value = storeargs.get("qpid.file_size");
+ if (value.get() != 0 && !value->empty() && value->convertsTo<int>() && (u_int32_t)value->get<int>() != jQueue->jfsize_sblks()/JRNL_RMGR_PAGE_SIZE) {
+ queue->addArgument("qpid.file_size", jQueue->jfsize_sblks()/JRNL_RMGR_PAGE_SIZE);
+ }
+
+ if (highestRid == 0ULL)
+ highestRid = thisHighestRid;
+ else if (thisHighestRid - highestRid < 0x8000000000000000ULL) // RFC 1982 comparison for unsigned 64-bit
+ highestRid = thisHighestRid;
+ recoverMessages(txn, registry, queue, prepared, messages, rcnt, idcnt);
+ QPID_LOG(info, "Recovered queue \"" << queueName << "\": " << rcnt << " messages recovered; " << idcnt << " messages in-doubt.");
+ jQueue->recover_complete(); // start journal.
+ } catch (const journal::jexception& e) {
+ THROW_STORE_EXCEPTION(std::string("Queue ") + queueName + ": recoverQueues() failed: " + e.what());
+ }
+ //read all messages: done on a per queue basis if using Journal
+
+ queue_index[key.id] = queue;
+ maxQueueId = std::max(key.id, maxQueueId);
+ }
+
+ // NOTE: highestRid is set by both recoverQueues() and recoverTplStore() as
+ // the messageIdSequence is used for both queue journals and the tpl journal.
+ messageIdSequence.reset(highestRid + 1);
+ QPID_LOG(info, "Most recent persistence id found: 0x" << std::hex << highestRid << std::dec);
+
+ queueIdSequence.reset(maxQueueId + 1);
+}
+
+
+void MessageStoreImpl::recoverExchanges(TxnCtxt& txn,
+ qpid::broker::RecoveryManager& registry,
+ exchange_index& index)
+{
+ //TODO: this is a copy&paste from recoverQueues - refactor!
+ Cursor exchanges;
+ exchanges.open(exchangeDb, txn.get());
+
+ u_int64_t maxExchangeId(1);
+ IdDbt key;
+ Dbt value;
+ //read all exchanges
+ while (exchanges.next(key, value)) {
+ qpid::framing::Buffer buffer(reinterpret_cast<char*>(value.get_data()), value.get_size());
+ //create a Exchange instance
+ qpid::broker::RecoverableExchange::shared_ptr exchange = registry.recoverExchange(buffer);
+ if (exchange) {
+ //set the persistenceId and update max as required
+ exchange->setPersistenceId(key.id);
+ index[key.id] = exchange;
+ QPID_LOG(info, "Recovered exchange \"" << exchange->getName() << '"');
+ }
+ maxExchangeId = std::max(key.id, maxExchangeId);
+ }
+ exchangeIdSequence.reset(maxExchangeId + 1);
+}
+
+void MessageStoreImpl::recoverBindings(TxnCtxt& txn,
+ exchange_index& exchanges,
+ queue_index& queues)
+{
+ Cursor bindings;
+ bindings.open(bindingDb, txn.get());
+
+ IdDbt key;
+ Dbt value;
+ while (bindings.next(key, value)) {
+ qpid::framing::Buffer buffer(reinterpret_cast<char*>(value.get_data()), value.get_size());
+ if (buffer.available() < 8) {
+ QPID_LOG(error, "Not enough data for binding: " << buffer.available());
+ THROW_STORE_EXCEPTION("Not enough data for binding");
+ }
+ uint64_t queueId = buffer.getLongLong();
+ std::string queueName;
+ std::string routingkey;
+ qpid::framing::FieldTable args;
+ buffer.getShortString(queueName);
+ buffer.getShortString(routingkey);
+ buffer.get(args);
+ exchange_index::iterator exchange = exchanges.find(key.id);
+ queue_index::iterator queue = queues.find(queueId);
+ if (exchange != exchanges.end() && queue != queues.end()) {
+ //could use the recoverable queue here rather than the name...
+ exchange->second->bind(queueName, routingkey, args);
+ QPID_LOG(info, "Recovered binding exchange=" << exchange->second->getName()
+ << " key=" << routingkey
+ << " queue=" << queueName);
+ } else {
+ //stale binding, delete it
+ QPID_LOG(warning, "Deleting stale binding");
+ bindings->del(0);
+ }
+ }
+}
+
+void MessageStoreImpl::recoverGeneral(TxnCtxt& txn,
+ qpid::broker::RecoveryManager& registry)
+{
+ Cursor items;
+ items.open(generalDb, txn.get());
+
+ u_int64_t maxGeneralId(1);
+ IdDbt key;
+ Dbt value;
+ //read all items
+ while (items.next(key, value)) {
+ qpid::framing::Buffer buffer(reinterpret_cast<char*>(value.get_data()), value.get_size());
+ //create instance
+ qpid::broker::RecoverableConfig::shared_ptr config = registry.recoverConfig(buffer);
+ //set the persistenceId and update max as required
+ config->setPersistenceId(key.id);
+ maxGeneralId = std::max(key.id, maxGeneralId);
+ }
+ generalIdSequence.reset(maxGeneralId + 1);
+}
+
+void MessageStoreImpl::recoverMessages(TxnCtxt& /*txn*/,
+ qpid::broker::RecoveryManager& recovery,
+ qpid::broker::RecoverableQueue::shared_ptr& queue,
+ txn_list& prepared,
+ message_index& messages,
+ long& rcnt,
+ long& idcnt)
+{
+ size_t preambleLength = sizeof(u_int32_t)/*header size*/;
+
+ JournalImpl* jc = static_cast<JournalImpl*>(queue->getExternalQueueStore());
+ DataTokenImpl dtok;
+ size_t readSize = 0;
+ unsigned msg_count = 0;
+
+ // TODO: This optimization to skip reading if there are no enqueued messages to read
+ // breaks the python system test in phase 6 with "Exception: Cannot write lock file"
+ // Figure out what is breaking.
+ //bool read = jc->get_enq_cnt() > 0;
+ bool read = true;
+
+ void* dbuff = NULL; size_t dbuffSize = 0;
+ void* xidbuff = NULL; size_t xidbuffSize = 0;
+ bool transientFlag = false;
+ bool externalFlag = false;
+
+ dtok.set_wstate(DataTokenImpl::ENQ);
+
+ // Read the message from the Journal.
+ try {
+ unsigned aio_sleep_cnt = 0;
+ while (read) {
+ mrg::journal::iores res = jc->read_data_record(&dbuff, dbuffSize, &xidbuff, xidbuffSize, transientFlag, externalFlag, &dtok);
+ readSize = dtok.dsize();
+
+ switch (res)
+ {
+ case mrg::journal::RHM_IORES_SUCCESS: {
+ msg_count++;
+ qpid::broker::RecoverableMessage::shared_ptr msg;
+ char* data = (char*)dbuff;
+
+ unsigned headerSize;
+ if (externalFlag) {
+ msg = getExternMessage(recovery, dtok.rid(), headerSize); // large message external to jrnl
+ } else {
+ headerSize = qpid::framing::Buffer(data, preambleLength).getLong();
+ qpid::framing::Buffer headerBuff(data+ preambleLength, headerSize); /// do we want read size or header size ????
+ msg = recovery.recoverMessage(headerBuff);
+ }
+ msg->setPersistenceId(dtok.rid());
+ // At some future point if delivery attempts are stored, then this call would
+ // become optional depending on that information.
+ msg->setRedelivered();
+ // Reset the TTL for the recovered message
+ msg->computeExpiration();
+
+ u_int32_t contentOffset = headerSize + preambleLength;
+ u_int64_t contentSize = readSize - contentOffset;
+ if (msg->loadContent(contentSize) && !externalFlag) {
+ //now read the content
+ qpid::framing::Buffer contentBuff(data + contentOffset, contentSize);
+ msg->decodeContent(contentBuff);
+ }
+
+ PreparedTransaction::list::iterator i = PreparedTransaction::getLockedPreparedTransaction(prepared, queue->getPersistenceId(), dtok.rid());
+ if (i == prepared.end()) { // not in prepared list
+ rcnt++;
+ queue->recover(msg);
+ } else {
+ u_int64_t rid = dtok.rid();
+ std::string xid(i->xid);
+ TplRecoverMapCitr citr = tplRecoverMap.find(xid);
+ if (citr == tplRecoverMap.end()) THROW_STORE_EXCEPTION("XID not found in tplRecoverMap");
+
+ // deq present in prepared list: this xid is part of incomplete txn commit/abort
+ // or this is a 1PC txn that must be rolled forward
+ if (citr->second.deq_flag || !citr->second.tpc_flag) {
+ if (jc->is_enqueued(rid, true)) {
+ // Enqueue is non-tx, dequeue tx
+ assert(jc->is_locked(rid)); // This record MUST be locked by a txn dequeue
+ if (!citr->second.commit_flag) {
+ rcnt++;
+ queue->recover(msg); // recover message in abort case only
+ }
+ } else {
+ // Enqueue and/or dequeue tx
+ journal::txn_map& tmap = jc->get_txn_map();
+ journal::txn_data_list txnList = tmap.get_tdata_list(xid); // txnList will be empty if xid not found
+ bool enq = false;
+ bool deq = false;
+ for (journal::tdl_itr j = txnList.begin(); j<txnList.end(); j++) {
+ if (j->_enq_flag && j->_rid == rid) enq = true;
+ else if (!j->_enq_flag && j->_drid == rid) deq = true;
+ }
+ if (enq && !deq && citr->second.commit_flag) {
+ rcnt++;
+ queue->recover(msg); // recover txn message in commit case only
+ }
+ }
+ } else {
+ idcnt++;
+ messages[rid] = msg;
+ }
+ }
+
+ dtok.reset();
+ dtok.set_wstate(DataTokenImpl::ENQ);
+
+ if (xidbuff)
+ ::free(xidbuff);
+ else if (dbuff)
+ ::free(dbuff);
+ aio_sleep_cnt = 0;
+ break;
+ }
+ case mrg::journal::RHM_IORES_PAGE_AIOWAIT:
+ if (++aio_sleep_cnt > MAX_AIO_SLEEPS)
+ THROW_STORE_EXCEPTION("Timeout waiting for AIO in MessageStoreImpl::recoverMessages()");
+ ::usleep(AIO_SLEEP_TIME_US);
+ break;
+ case mrg::journal::RHM_IORES_EMPTY:
+ read = false;
+ break; // done with all messages. (add call in jrnl to test that _emap is empty.)
+ default:
+ std::ostringstream oss;
+ oss << "recoverMessages(): Queue: " << queue->getName() << ": Unexpected return from journal read: " << mrg::journal::iores_str(res);
+ THROW_STORE_EXCEPTION(oss.str());
+ } // switch
+ } // while
+ } catch (const journal::jexception& e) {
+ THROW_STORE_EXCEPTION(std::string("Queue ") + queue->getName() + ": recoverMessages() failed: " + e.what());
+ }
+}
+
+qpid::broker::RecoverableMessage::shared_ptr MessageStoreImpl::getExternMessage(qpid::broker::RecoveryManager& /*recovery*/,
+ uint64_t /*messageId*/,
+ unsigned& /*headerSize*/)
+{
+ throw mrg::journal::jexception(mrg::journal::jerrno::JERR__NOTIMPL, "MessageStoreImpl", "getExternMessage");
+}
+
+int MessageStoreImpl::enqueueMessage(TxnCtxt& txn,
+ IdDbt& msgId,
+ qpid::broker::RecoverableMessage::shared_ptr& msg,
+ queue_index& index,
+ txn_list& prepared,
+ message_index& messages)
+{
+ Cursor mappings;
+ mappings.open(mappingDb, txn.get());
+
+ IdDbt value;
+
+ int count(0);
+ for (int status = mappings->get(&msgId, &value, DB_SET); status == 0; status = mappings->get(&msgId, &value, DB_NEXT_DUP)) {
+ if (index.find(value.id) == index.end()) {
+ QPID_LOG(warning, "Recovered message for queue that no longer exists");
+ mappings->del(0);
+ } else {
+ qpid::broker::RecoverableQueue::shared_ptr queue = index[value.id];
+ if (PreparedTransaction::isLocked(prepared, value.id, msgId.id)) {
+ messages[msgId.id] = msg;
+ } else {
+ queue->recover(msg);
+ }
+ count++;
+ }
+ }
+ mappings.close();
+ return count;
+}
+
+void MessageStoreImpl::readTplStore()
+{
+ tplRecoverMap.clear();
+ journal::txn_map& tmap = tplStorePtr->get_txn_map();
+ DataTokenImpl dtok;
+ void* dbuff = NULL; size_t dbuffSize = 0;
+ void* xidbuff = NULL; size_t xidbuffSize = 0;
+ bool transientFlag = false;
+ bool externalFlag = false;
+ bool done = false;
+ try {
+ unsigned aio_sleep_cnt = 0;
+ while (!done) {
+ dtok.reset();
+ dtok.set_wstate(DataTokenImpl::ENQ);
+ mrg::journal::iores res = tplStorePtr->read_data_record(&dbuff, dbuffSize, &xidbuff, xidbuffSize, transientFlag, externalFlag, &dtok);
+ switch (res) {
+ case mrg::journal::RHM_IORES_SUCCESS: {
+ // Every TPL record contains both data and an XID
+ assert(dbuffSize>0);
+ assert(xidbuffSize>0);
+ std::string xid(static_cast<const char*>(xidbuff), xidbuffSize);
+ bool is2PC = *(static_cast<char*>(dbuff)) != 0;
+
+ // Check transaction details; add to recover map
+ journal::txn_data_list txnList = tmap.get_tdata_list(xid); // txnList will be empty if xid not found
+ if (!txnList.empty()) { // xid found in tmap
+ unsigned enqCnt = 0;
+ unsigned deqCnt = 0;
+ u_int64_t rid = 0;
+
+ // Assume commit (roll forward) in cases where only prepare has been called - ie only enqueue record exists.
+ // Note: will apply to both 1PC and 2PC transactions.
+ bool commitFlag = true;
+
+ for (journal::tdl_itr j = txnList.begin(); j<txnList.end(); j++) {
+ if (j->_enq_flag) {
+ rid = j->_rid;
+ enqCnt++;
+ } else {
+ commitFlag = j->_commit_flag;
+ deqCnt++;
+ }
+ }
+ assert(enqCnt == 1);
+ assert(deqCnt <= 1);
+ tplRecoverMap.insert(TplRecoverMapPair(xid, TplRecoverStruct(rid, deqCnt == 1, commitFlag, is2PC)));
+ }
+
+ ::free(xidbuff);
+ aio_sleep_cnt = 0;
+ break;
+ }
+ case mrg::journal::RHM_IORES_PAGE_AIOWAIT:
+ if (++aio_sleep_cnt > MAX_AIO_SLEEPS)
+ THROW_STORE_EXCEPTION("Timeout waiting for AIO in MessageStoreImpl::recoverTplStore()");
+ ::usleep(AIO_SLEEP_TIME_US);
+ break;
+ case mrg::journal::RHM_IORES_EMPTY:
+ done = true;
+ break; // done with all messages. (add call in jrnl to test that _emap is empty.)
+ default:
+ std::ostringstream oss;
+ oss << "readTplStore(): Unexpected result from journal read: " << mrg::journal::iores_str(res);
+ THROW_STORE_EXCEPTION(oss.str());
+ } // switch
+ }
+ } catch (const journal::jexception& e) {
+ THROW_STORE_EXCEPTION(std::string("TPL recoverTplStore() failed: ") + e.what());
+ }
+}
+
+void MessageStoreImpl::recoverTplStore()
+{
+ if (journal::jdir::exists(tplStorePtr->jrnl_dir() + tplStorePtr->base_filename() + ".jinf")) {
+ u_int64_t thisHighestRid = 0ULL;
+ tplStorePtr->recover(tplNumJrnlFiles, false, 0, tplJrnlFsizeSblks, tplWCachePgSizeSblks, tplWCacheNumPages, 0, thisHighestRid, 0);
+ if (highestRid == 0ULL)
+ highestRid = thisHighestRid;
+ else if (thisHighestRid - highestRid < 0x8000000000000000ULL) // RFC 1982 comparison for unsigned 64-bit
+ highestRid = thisHighestRid;
+
+ // Load tplRecoverMap by reading the TPL store
+ readTplStore();
+
+ tplStorePtr->recover_complete(); // start journal.
+ }
+}
+
+void MessageStoreImpl::recoverLockedMappings(txn_list& txns)
+{
+ if (!tplStorePtr->is_ready())
+ recoverTplStore();
+
+ // Abort unprepared xids and populate the locked maps
+ for (TplRecoverMapCitr i = tplRecoverMap.begin(); i != tplRecoverMap.end(); i++) {
+ LockedMappings::shared_ptr enq_ptr;
+ enq_ptr.reset(new LockedMappings);
+ LockedMappings::shared_ptr deq_ptr;
+ deq_ptr.reset(new LockedMappings);
+ txns.push_back(new PreparedTransaction(i->first, enq_ptr, deq_ptr));
+ }
+}
+
+void MessageStoreImpl::collectPreparedXids(std::set<std::string>& xids)
+{
+ if (tplStorePtr->is_ready()) {
+ tplStorePtr->read_reset();
+ readTplStore();
+ } else {
+ recoverTplStore();
+ }
+ for (TplRecoverMapCitr i = tplRecoverMap.begin(); i != tplRecoverMap.end(); i++) {
+ // Discard all txns that are to be rolled forward/back and 1PC transactions
+ if (!i->second.deq_flag && i->second.tpc_flag)
+ xids.insert(i->first);
+ }
+}
+
+void MessageStoreImpl::stage(const boost::intrusive_ptr<qpid::broker::PersistableMessage>& /*msg*/)
+{
+ throw mrg::journal::jexception(mrg::journal::jerrno::JERR__NOTIMPL, "MessageStoreImpl", "stage");
+}
+
+void MessageStoreImpl::destroy(qpid::broker::PersistableMessage& /*msg*/)
+{
+ throw mrg::journal::jexception(mrg::journal::jerrno::JERR__NOTIMPL, "MessageStoreImpl", "destroy");
+}
+
+void MessageStoreImpl::appendContent(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& /*msg*/,
+ const std::string& /*data*/)
+{
+ throw mrg::journal::jexception(mrg::journal::jerrno::JERR__NOTIMPL, "MessageStoreImpl", "appendContent");
+}
+
+void MessageStoreImpl::loadContent(const qpid::broker::PersistableQueue& queue,
+ const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg,
+ std::string& data,
+ u_int64_t offset,
+ u_int32_t length)
+{
+ checkInit();
+ u_int64_t messageId (msg->getPersistenceId());
+
+ if (messageId != 0) {
+ try {
+ JournalImpl* jc = static_cast<JournalImpl*>(queue.getExternalQueueStore());
+ if (jc && jc->is_enqueued(messageId) ) {
+ if (!jc->loadMsgContent(messageId, data, length, offset)) {
+ std::ostringstream oss;
+ oss << "Queue " << queue.getName() << ": loadContent() failed: Message " << messageId << " is extern";
+ THROW_STORE_EXCEPTION(oss.str());
+ }
+ } else {
+ std::ostringstream oss;
+ oss << "Queue " << queue.getName() << ": loadContent() failed: Message " << messageId << " not enqueued";
+ THROW_STORE_EXCEPTION(oss.str());
+ }
+ } catch (const journal::jexception& e) {
+ THROW_STORE_EXCEPTION(std::string("Queue ") + queue.getName() + ": loadContent() failed: " + e.what());
+ }
+ } else {
+ THROW_STORE_EXCEPTION("Cannot load content. Message not known to store!");
+ }
+}
+
+void MessageStoreImpl::flush(const qpid::broker::PersistableQueue& queue)
+{
+ if (queue.getExternalQueueStore() == 0) return;
+ checkInit();
+ std::string qn = queue.getName();
+ try {
+ JournalImpl* jc = static_cast<JournalImpl*>(queue.getExternalQueueStore());
+ if (jc) {
+ // TODO: check if this result should be used...
+ /*mrg::journal::iores res =*/ jc->flush();
+ }
+ } catch (const journal::jexception& e) {
+ THROW_STORE_EXCEPTION(std::string("Queue ") + qn + ": flush() failed: " + e.what() );
+ }
+}
+
+void MessageStoreImpl::enqueue(qpid::broker::TransactionContext* ctxt,
+ const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg,
+ const qpid::broker::PersistableQueue& queue)
+{
+ checkInit();
+ u_int64_t queueId (queue.getPersistenceId());
+ u_int64_t messageId (msg->getPersistenceId());
+ if (queueId == 0) {
+ THROW_STORE_EXCEPTION("Queue not created: " + queue.getName());
+ }
+
+ TxnCtxt implicit;
+ TxnCtxt* txn = 0;
+ if (ctxt) {
+ txn = check(ctxt);
+ } else {
+ txn = &implicit;
+ }
+
+ bool newId = false;
+ if (messageId == 0) {
+ messageId = messageIdSequence.next();
+ msg->setPersistenceId(messageId);
+ newId = true;
+ }
+ store(&queue, txn, msg, newId);
+
+ // add queue* to the txn map..
+ if (ctxt) txn->addXidRecord(queue.getExternalQueueStore());
+}
+
+u_int64_t MessageStoreImpl::msgEncode(std::vector<char>& buff, const boost::intrusive_ptr<qpid::broker::PersistableMessage>& message)
+{
+ u_int32_t headerSize = message->encodedHeaderSize();
+ u_int64_t size = message->encodedSize() + sizeof(u_int32_t);
+ try { buff = std::vector<char>(size); } // long + headers + content
+ catch (const std::exception& e) {
+ std::ostringstream oss;
+ oss << "Unable to allocate memory for encoding message; requested size: " << size << "; error: " << e.what();
+ THROW_STORE_EXCEPTION(oss.str());
+ }
+ qpid::framing::Buffer buffer(&buff[0],size);
+ buffer.putLong(headerSize);
+ message->encode(buffer);
+ return size;
+}
+
+void MessageStoreImpl::store(const qpid::broker::PersistableQueue* queue,
+ TxnCtxt* txn,
+ const boost::intrusive_ptr<qpid::broker::PersistableMessage>& message,
+ bool /*newId*/)
+{
+ std::vector<char> buff;
+ u_int64_t size = msgEncode(buff, message);
+
+ try {
+ if (queue) {
+ boost::intrusive_ptr<DataTokenImpl> dtokp(new DataTokenImpl);
+ dtokp->addRef();
+ dtokp->setSourceMessage(message);
+ dtokp->set_external_rid(true);
+ dtokp->set_rid(message->getPersistenceId()); // set the messageID into the Journal header (record-id)
+
+ JournalImpl* jc = static_cast<JournalImpl*>(queue->getExternalQueueStore());
+ if (txn->getXid().empty()) {
+ jc->enqueue_data_record(&buff[0], size, size, dtokp.get(), !message->isPersistent());
+ } else {
+ jc->enqueue_txn_data_record(&buff[0], size, size, dtokp.get(), txn->getXid(), !message->isPersistent());
+ }
+ } else {
+ THROW_STORE_EXCEPTION(std::string("MessageStoreImpl::store() failed: queue NULL."));
+ }
+ } catch (const journal::jexception& e) {
+ THROW_STORE_EXCEPTION(std::string("Queue ") + queue->getName() + ": MessageStoreImpl::store() failed: " +
+ e.what());
+ }
+}
+
+void MessageStoreImpl::dequeue(qpid::broker::TransactionContext* ctxt,
+ const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg,
+ const qpid::broker::PersistableQueue& queue)
+{
+ checkInit();
+ u_int64_t queueId (queue.getPersistenceId());
+ u_int64_t messageId (msg->getPersistenceId());
+ if (queueId == 0) {
+ THROW_STORE_EXCEPTION("Queue \"" + queue.getName() + "\" has null queue Id (has not been created)");
+ }
+ if (messageId == 0) {
+ THROW_STORE_EXCEPTION("Queue \"" + queue.getName() + "\": Dequeuing message with null persistence Id.");
+ }
+
+ TxnCtxt implicit;
+ TxnCtxt* txn = 0;
+ if (ctxt) {
+ txn = check(ctxt);
+ } else {
+ txn = &implicit;
+ }
+
+ // add queue* to the txn map..
+ if (ctxt) txn->addXidRecord(queue.getExternalQueueStore());
+ async_dequeue(ctxt, msg, queue);
+
+ msg->dequeueComplete();
+}
+
+void MessageStoreImpl::async_dequeue(qpid::broker::TransactionContext* ctxt,
+ const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg,
+ const qpid::broker::PersistableQueue& queue)
+{
+ boost::intrusive_ptr<DataTokenImpl> ddtokp(new DataTokenImpl);
+ ddtokp->setSourceMessage(msg);
+ ddtokp->set_external_rid(true);
+ ddtokp->set_rid(messageIdSequence.next());
+ ddtokp->set_dequeue_rid(msg->getPersistenceId());
+ ddtokp->set_wstate(DataTokenImpl::ENQ);
+ std::string tid;
+ if (ctxt) {
+ TxnCtxt* txn = check(ctxt);
+ tid = txn->getXid();
+ }
+ // Manually increase the ref count, as raw pointers are used beyond this point
+ ddtokp->addRef();
+ try {
+ JournalImpl* jc = static_cast<JournalImpl*>(queue.getExternalQueueStore());
+ if (tid.empty()) {
+ jc->dequeue_data_record(ddtokp.get());
+ } else {
+ jc->dequeue_txn_data_record(ddtokp.get(), tid);
+ }
+ } catch (const journal::jexception& e) {
+ ddtokp->release();
+ THROW_STORE_EXCEPTION(std::string("Queue ") + queue.getName() + ": async_dequeue() failed: " + e.what());
+ }
+}
+
+u_int32_t MessageStoreImpl::outstandingQueueAIO(const qpid::broker::PersistableQueue& /*queue*/)
+{
+ checkInit();
+ return 0;
+}
+
+void MessageStoreImpl::completed(TxnCtxt& txn,
+ bool commit)
+{
+ try {
+ chkTplStoreInit(); // Late initialize (if needed)
+
+ // Nothing to do if not prepared
+ if (txn.getDtok()->is_enqueued()) {
+ txn.incrDtokRef();
+ DataTokenImpl* dtokp = txn.getDtok();
+ dtokp->set_dequeue_rid(dtokp->rid());
+ dtokp->set_rid(messageIdSequence.next());
+ tplStorePtr->dequeue_txn_data_record(txn.getDtok(), txn.getXid(), commit);
+ }
+ txn.complete(commit);
+ if (mgmtObject.get() != 0) {
+ mgmtObject->dec_tplTransactionDepth();
+ if (commit)
+ mgmtObject->inc_tplTxnCommits();
+ else
+ mgmtObject->inc_tplTxnAborts();
+ }
+ } catch (const std::exception& e) {
+ QPID_LOG(error, "Error completing xid " << txn.getXid() << ": " << e.what());
+ throw;
+ }
+}
+
+std::auto_ptr<qpid::broker::TransactionContext> MessageStoreImpl::begin()
+{
+ checkInit();
+ // pass sequence number for c/a
+ return std::auto_ptr<qpid::broker::TransactionContext>(new TxnCtxt(&messageIdSequence));
+}
+
+std::auto_ptr<qpid::broker::TPCTransactionContext> MessageStoreImpl::begin(const std::string& xid)
+{
+ checkInit();
+ IdSequence* jtx = &messageIdSequence;
+ // pass sequence number for c/a
+ return std::auto_ptr<qpid::broker::TPCTransactionContext>(new TPCTxnCtxt(xid, jtx));
+}
+
+void MessageStoreImpl::prepare(qpid::broker::TPCTransactionContext& ctxt)
+{
+ checkInit();
+ TxnCtxt* txn = dynamic_cast<TxnCtxt*>(&ctxt);
+ if(!txn) throw qpid::broker::InvalidTransactionContextException();
+ localPrepare(txn);
+}
+
+void MessageStoreImpl::localPrepare(TxnCtxt* ctxt)
+{
+ try {
+ chkTplStoreInit(); // Late initialize (if needed)
+
+ // This sync is required to ensure multi-queue atomicity - ie all txn data
+ // must hit the disk on *all* queues before the TPL prepare (enq) is written.
+ ctxt->sync();
+
+ ctxt->incrDtokRef();
+ DataTokenImpl* dtokp = ctxt->getDtok();
+ dtokp->set_external_rid(true);
+ dtokp->set_rid(messageIdSequence.next());
+ char tpcFlag = static_cast<char>(ctxt->isTPC());
+ tplStorePtr->enqueue_txn_data_record(&tpcFlag, sizeof(char), sizeof(char), dtokp, ctxt->getXid(), false);
+ ctxt->prepare(tplStorePtr.get());
+ // make sure all the data is written to disk before returning
+ ctxt->sync();
+ if (mgmtObject.get() != 0) {
+ mgmtObject->inc_tplTransactionDepth();
+ mgmtObject->inc_tplTxnPrepares();
+ }
+ } catch (const std::exception& e) {
+ QPID_LOG(error, "Error preparing xid " << ctxt->getXid() << ": " << e.what());
+ throw;
+ }
+}
+
+void MessageStoreImpl::commit(qpid::broker::TransactionContext& ctxt)
+{
+ checkInit();
+ TxnCtxt* txn(check(&ctxt));
+ if (!txn->isTPC()) {
+ if (txn->impactedQueuesEmpty()) return;
+ localPrepare(dynamic_cast<TxnCtxt*>(txn));
+ }
+ completed(*dynamic_cast<TxnCtxt*>(txn), true);
+}
+
+void MessageStoreImpl::abort(qpid::broker::TransactionContext& ctxt)
+{
+ checkInit();
+ TxnCtxt* txn(check(&ctxt));
+ if (!txn->isTPC()) {
+ if (txn->impactedQueuesEmpty()) return;
+ localPrepare(dynamic_cast<TxnCtxt*>(txn));
+ }
+ completed(*dynamic_cast<TxnCtxt*>(txn), false);
+}
+
+TxnCtxt* MessageStoreImpl::check(qpid::broker::TransactionContext* ctxt)
+{
+ TxnCtxt* txn = dynamic_cast<TxnCtxt*>(ctxt);
+ if(!txn) throw qpid::broker::InvalidTransactionContextException();
+ return txn;
+}
+
+void MessageStoreImpl::put(db_ptr db,
+ DbTxn* txn,
+ Dbt& key,
+ Dbt& value)
+{
+ try {
+ int status = db->put(txn, &key, &value, DB_NODUPDATA);
+ if (status == DB_KEYEXIST) {
+ THROW_STORE_EXCEPTION("duplicate data");
+ } else if (status) {
+ THROW_STORE_EXCEPTION(DbEnv::strerror(status));
+ }
+ } catch (const DbException& e) {
+ THROW_STORE_EXCEPTION(e.what());
+ }
+}
+
+void MessageStoreImpl::deleteBindingsForQueue(const qpid::broker::PersistableQueue& queue)
+{
+ TxnCtxt txn;
+ txn.begin(dbenv.get(), true);
+ try {
+ {
+ Cursor bindings;
+ bindings.open(bindingDb, txn.get());
+
+ IdDbt key;
+ Dbt value;
+ while (bindings.next(key, value)) {
+ qpid::framing::Buffer buffer(reinterpret_cast<char*>(value.get_data()), value.get_size());
+ if (buffer.available() < 8) {
+ THROW_STORE_EXCEPTION("Not enough data for binding");
+ }
+ uint64_t queueId = buffer.getLongLong();
+ if (queue.getPersistenceId() == queueId) {
+ bindings->del(0);
+ QPID_LOG(debug, "Deleting binding for " << queue.getName() << " " << key.id << "->" << queueId);
+ }
+ }
+ }
+ txn.commit();
+ } catch (const std::exception& e) {
+ txn.abort();
+ THROW_STORE_EXCEPTION_2("Error deleting bindings", e.what());
+ } catch (...) {
+ txn.abort();
+ throw;
+ }
+ QPID_LOG(debug, "Deleted all bindings for " << queue.getName() << ":" << queue.getPersistenceId());
+}
+
+void MessageStoreImpl::deleteBinding(const qpid::broker::PersistableExchange& exchange,
+ const qpid::broker::PersistableQueue& queue,
+ const std::string& bkey)
+{
+ TxnCtxt txn;
+ txn.begin(dbenv.get(), true);
+ try {
+ {
+ Cursor bindings;
+ bindings.open(bindingDb, txn.get());
+
+ IdDbt key(exchange.getPersistenceId());
+ Dbt value;
+
+ for (int status = bindings->get(&key, &value, DB_SET); status == 0; status = bindings->get(&key, &value, DB_NEXT_DUP)) {
+ qpid::framing::Buffer buffer(reinterpret_cast<char*>(value.get_data()), value.get_size());
+ if (buffer.available() < 8) {
+ THROW_STORE_EXCEPTION("Not enough data for binding");
+ }
+ uint64_t queueId = buffer.getLongLong();
+ if (queue.getPersistenceId() == queueId) {
+ std::string q;
+ std::string k;
+ buffer.getShortString(q);
+ buffer.getShortString(k);
+ if (bkey == k) {
+ bindings->del(0);
+ QPID_LOG(debug, "Deleting binding for " << queue.getName() << " " << key.id << "->" << queueId);
+ }
+ }
+ }
+ }
+ txn.commit();
+ } catch (const std::exception& e) {
+ txn.abort();
+ THROW_STORE_EXCEPTION_2("Error deleting bindings", e.what());
+ } catch (...) {
+ txn.abort();
+ throw;
+ }
+}
+
+std::string MessageStoreImpl::getJrnlBaseDir()
+{
+ std::ostringstream dir;
+ dir << storeDir << "/" << storeTopLevelDir << "/jrnl/" ;
+ return dir.str();
+}
+
+std::string MessageStoreImpl::getBdbBaseDir()
+{
+ std::ostringstream dir;
+ dir << storeDir << "/" << storeTopLevelDir << "/dat/" ;
+ return dir.str();
+}
+
+std::string MessageStoreImpl::getTplBaseDir()
+{
+ std::ostringstream dir;
+ dir << storeDir << "/" << storeTopLevelDir << "/tpl/" ;
+ return dir.str();
+}
+
+std::string MessageStoreImpl::getJrnlDir(const qpid::broker::PersistableQueue& queue) //for exmaple /var/rhm/ + queueDir/
+{
+ return getJrnlHashDir(queue.getName().c_str());
+}
+
+u_int32_t MessageStoreImpl::bHash(const std::string str)
+{
+ // Daniel Bernstein hash fn
+ u_int32_t h = 0;
+ for (std::string::const_iterator i = str.begin(); i < str.end(); i++)
+ h = 33*h + *i;
+ return h;
+}
+
+std::string MessageStoreImpl::getJrnlHashDir(const std::string& queueName) //for exmaple /var/rhm/ + queueDir/
+{
+ std::stringstream dir;
+ dir << getJrnlBaseDir() << std::hex << std::setfill('0') << std::setw(4);
+ dir << (bHash(queueName.c_str()) % 29); // Use a prime number for better distribution across dirs
+ dir << "/" << queueName << "/";
+ return dir.str();
+}
+
+std::string MessageStoreImpl::getStoreDir() const { return storeDir; }
+
+void MessageStoreImpl::journalDeleted(JournalImpl& j) {
+ qpid::sys::Mutex::ScopedLock sl(journalListLock);
+ journalList.erase(j.id());
+}
+
+MessageStoreImpl::StoreOptions::StoreOptions(const std::string& name) :
+ qpid::Options(name),
+ numJrnlFiles(defNumJrnlFiles),
+ autoJrnlExpand(defAutoJrnlExpand),
+ autoJrnlExpandMaxFiles(defAutoJrnlExpandMaxFiles),
+ jrnlFsizePgs(defJrnlFileSizePgs),
+ truncateFlag(defTruncateFlag),
+ wCachePageSizeKib(defWCachePageSize),
+ tplNumJrnlFiles(defTplNumJrnlFiles),
+ tplJrnlFsizePgs(defTplJrnlFileSizePgs),
+ tplWCachePageSizeKib(defTplWCachePageSize)
+{
+ std::ostringstream oss1;
+ oss1 << "Default number of files for each journal instance (queue). [Allowable values: " <<
+ JRNL_MIN_NUM_FILES << " - " << JRNL_MAX_NUM_FILES << "]";
+ std::ostringstream oss2;
+ oss2 << "Default size for each journal file in multiples of read pages (1 read page = 64KiB). [Allowable values: " <<
+ JRNL_MIN_FILE_SIZE / JRNL_RMGR_PAGE_SIZE << " - " << JRNL_MAX_FILE_SIZE / JRNL_RMGR_PAGE_SIZE << "]";
+ std::ostringstream oss3;
+ oss3 << "Number of files for transaction prepared list journal instance. [Allowable values: " <<
+ JRNL_MIN_NUM_FILES << " - " << JRNL_MAX_NUM_FILES << "]";
+ std::ostringstream oss4;
+ oss4 << "Size of each transaction prepared list journal file in multiples of read pages (1 read page = 64KiB) [Allowable values: " <<
+ JRNL_MIN_FILE_SIZE / JRNL_RMGR_PAGE_SIZE << " - " << JRNL_MAX_FILE_SIZE / JRNL_RMGR_PAGE_SIZE << "]";
+ addOptions()
+ ("store-dir", qpid::optValue(storeDir, "DIR"),
+ "Store directory location for persistence (instead of using --data-dir value). "
+ "Required if --no-data-dir is also used.")
+ ("num-jfiles", qpid::optValue(numJrnlFiles, "N"), oss1.str().c_str())
+ ("jfile-size-pgs", qpid::optValue(jrnlFsizePgs, "N"), oss2.str().c_str())
+// TODO: Uncomment these lines when auto-expand is enabled.
+// ("auto-expand", qpid::optValue(autoJrnlExpand, "yes|no"),
+// "If yes|true|1, allows journal to auto-expand by adding additional journal files as needed. "
+// "If no|false|0, the number of journal files will remain fixed (num-jfiles).")
+// ("max-auto-expand-jfiles", qpid::optValue(autoJrnlExpandMaxFiles, "N"),
+// "Maximum number of journal files allowed from auto-expanding; must be greater than --num-jfiles parameter.")
+ ("truncate", qpid::optValue(truncateFlag, "yes|no"),
+ "If yes|true|1, will truncate the store (discard any existing records). If no|false|0, will preserve "
+ "the existing store files for recovery.")
+ ("wcache-page-size", qpid::optValue(wCachePageSizeKib, "N"),
+ "Size of the pages in the write page cache in KiB. "
+ "Allowable values - powers of 2: 1, 2, 4, ... , 128. "
+ "Lower values decrease latency at the expense of throughput.")
+ ("tpl-num-jfiles", qpid::optValue(tplNumJrnlFiles, "N"), oss3.str().c_str())
+ ("tpl-jfile-size-pgs", qpid::optValue(tplJrnlFsizePgs, "N"), oss4.str().c_str())
+ ("tpl-wcache-page-size", qpid::optValue(tplWCachePageSizeKib, "N"),
+ "Size of the pages in the transaction prepared list write page cache in KiB. "
+ "Allowable values - powers of 2: 1, 2, 4, ... , 128. "
+ "Lower values decrease latency at the expense of throughput.")
+ ;
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/legacystore/MessageStoreImpl.h b/qpid/cpp/src/qpid/legacystore/MessageStoreImpl.h
new file mode 100644
index 0000000000..f6fa68e481
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/MessageStoreImpl.h
@@ -0,0 +1,383 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#ifndef QPID_LEGACYSTORE_MESSAGESTOREIMPL_H
+#define QPID_LEGACYSTORE_MESSAGESTOREIMPL_H
+
+#include "qpid/broker/MessageStore.h"
+
+#include "qpid/Options.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/legacystore/Cursor.h"
+#include "qpid/legacystore/IdDbt.h"
+#include "qpid/legacystore/IdSequence.h"
+#include "qpid/legacystore/JournalImpl.h"
+#include "qpid/legacystore/jrnl/jcfg.h"
+#include "qpid/legacystore/PreparedTransaction.h"
+#include "qpid/legacystore/TxnCtxt.h"
+#include "qpid/management/Manageable.h"
+#include "qmf/org/apache/qpid/legacystore/Store.h"
+
+#include <string>
+
+#include "db-inc.h"
+
+// Assume DB_VERSION_MAJOR == 4
+#if (DB_VERSION_MINOR == 2)
+#include <errno.h>
+#define DB_BUFFER_SMALL ENOMEM
+#endif
+
+namespace qpid { namespace sys {
+class Timer;
+}}
+
+namespace mrg {
+namespace msgstore {
+
+/**
+ * An implementation of the MessageStore interface based on Berkeley DB
+ */
+class MessageStoreImpl : public qpid::broker::MessageStore, public qpid::management::Manageable
+{
+ public:
+ typedef boost::shared_ptr<Db> db_ptr;
+ typedef boost::shared_ptr<DbEnv> dbEnv_ptr;
+
+ struct StoreOptions : public qpid::Options {
+ StoreOptions(const std::string& name="Store Options");
+ std::string clusterName;
+ std::string storeDir;
+ u_int16_t numJrnlFiles;
+ bool autoJrnlExpand;
+ u_int16_t autoJrnlExpandMaxFiles;
+ u_int32_t jrnlFsizePgs;
+ bool truncateFlag;
+ u_int32_t wCachePageSizeKib;
+ u_int16_t tplNumJrnlFiles;
+ u_int32_t tplJrnlFsizePgs;
+ u_int32_t tplWCachePageSizeKib;
+ };
+
+ protected:
+ typedef std::map<u_int64_t, qpid::broker::RecoverableQueue::shared_ptr> queue_index;
+ typedef std::map<u_int64_t, qpid::broker::RecoverableExchange::shared_ptr> exchange_index;
+ typedef std::map<u_int64_t, qpid::broker::RecoverableMessage::shared_ptr> message_index;
+
+ typedef LockedMappings::map txn_lock_map;
+ typedef boost::ptr_list<PreparedTransaction> txn_list;
+
+ // Structs for Transaction Recover List (TPL) recover state
+ struct TplRecoverStruct {
+ u_int64_t rid; // rid of TPL record
+ bool deq_flag;
+ bool commit_flag;
+ bool tpc_flag;
+ TplRecoverStruct(const u_int64_t _rid, const bool _deq_flag, const bool _commit_flag, const bool _tpc_flag);
+ };
+ typedef TplRecoverStruct TplRecover;
+ typedef std::pair<std::string, TplRecover> TplRecoverMapPair;
+ typedef std::map<std::string, TplRecover> TplRecoverMap;
+ typedef TplRecoverMap::const_iterator TplRecoverMapCitr;
+
+ typedef std::map<std::string, JournalImpl*> JournalListMap;
+ typedef JournalListMap::iterator JournalListMapItr;
+
+ // Default store settings
+ static const u_int16_t defNumJrnlFiles = 8;
+ static const u_int32_t defJrnlFileSizePgs = 24;
+ static const bool defTruncateFlag = false;
+ static const u_int32_t defWCachePageSize = JRNL_WMGR_DEF_PAGE_SIZE * JRNL_DBLK_SIZE * JRNL_SBLK_SIZE / 1024;
+ static const u_int16_t defTplNumJrnlFiles = 8;
+ static const u_int32_t defTplJrnlFileSizePgs = 24;
+ static const u_int32_t defTplWCachePageSize = defWCachePageSize / 8;
+ // TODO: set defAutoJrnlExpand to true and defAutoJrnlExpandMaxFiles to 16 when auto-expand comes on-line
+ static const bool defAutoJrnlExpand = false;
+ static const u_int16_t defAutoJrnlExpandMaxFiles = 0;
+
+ static const std::string storeTopLevelDir;
+ static qpid::sys::Duration defJournalGetEventsTimeout;
+ static qpid::sys::Duration defJournalFlushTimeout;
+
+ std::list<db_ptr> dbs;
+ dbEnv_ptr dbenv;
+ db_ptr queueDb;
+ db_ptr configDb;
+ db_ptr exchangeDb;
+ db_ptr mappingDb;
+ db_ptr bindingDb;
+ db_ptr generalDb;
+
+ // Pointer to Transaction Prepared List (TPL) journal instance
+ boost::shared_ptr<TplJournalImpl> tplStorePtr;
+ TplRecoverMap tplRecoverMap;
+ qpid::sys::Mutex tplInitLock;
+ JournalListMap journalList;
+ qpid::sys::Mutex journalListLock;
+ qpid::sys::Mutex bdbLock;
+
+ IdSequence queueIdSequence;
+ IdSequence exchangeIdSequence;
+ IdSequence generalIdSequence;
+ IdSequence messageIdSequence;
+ std::string storeDir;
+ u_int16_t numJrnlFiles;
+ bool autoJrnlExpand;
+ u_int16_t autoJrnlExpandMaxFiles;
+ u_int32_t jrnlFsizeSblks;
+ bool truncateFlag;
+ u_int32_t wCachePgSizeSblks;
+ u_int16_t wCacheNumPages;
+ u_int16_t tplNumJrnlFiles;
+ u_int32_t tplJrnlFsizeSblks;
+ u_int32_t tplWCachePgSizeSblks;
+ u_int16_t tplWCacheNumPages;
+ u_int64_t highestRid;
+ bool isInit;
+ const char* envPath;
+ qpid::broker::Broker* broker;
+
+ qmf::org::apache::qpid::legacystore::Store::shared_ptr mgmtObject;
+ qpid::management::ManagementAgent* agent;
+
+
+ // Parameter validation and calculation
+ static u_int16_t chkJrnlNumFilesParam(const u_int16_t param,
+ const std::string paramName);
+ static u_int32_t chkJrnlFileSizeParam(const u_int32_t param,
+ const std::string paramName,
+ const u_int32_t wCachePgSizeSblks = 0);
+ static u_int32_t chkJrnlWrPageCacheSize(const u_int32_t param,
+ const std::string paramName,
+ const u_int16_t jrnlFsizePgs);
+ static u_int16_t getJrnlWrNumPages(const u_int32_t wrPageSizeKib);
+ void chkJrnlAutoExpandOptions(const MessageStoreImpl::StoreOptions* opts,
+ bool& autoJrnlExpand,
+ u_int16_t& autoJrnlExpandMaxFiles,
+ const std::string& autoJrnlExpandMaxFilesParamName,
+ const u_int16_t numJrnlFiles,
+ const std::string& numJrnlFilesParamName);
+
+ void init();
+
+ void recoverQueues(TxnCtxt& txn,
+ qpid::broker::RecoveryManager& recovery,
+ queue_index& index,
+ txn_list& locked,
+ message_index& messages);
+ void recoverMessages(TxnCtxt& txn,
+ qpid::broker::RecoveryManager& recovery,
+ queue_index& index,
+ txn_list& locked,
+ message_index& prepared);
+ void recoverMessages(TxnCtxt& txn,
+ qpid::broker::RecoveryManager& recovery,
+ qpid::broker::RecoverableQueue::shared_ptr& queue,
+ txn_list& locked,
+ message_index& prepared,
+ long& rcnt,
+ long& idcnt);
+ qpid::broker::RecoverableMessage::shared_ptr getExternMessage(qpid::broker::RecoveryManager& recovery,
+ uint64_t mId,
+ unsigned& headerSize);
+ void recoverExchanges(TxnCtxt& txn,
+ qpid::broker::RecoveryManager& recovery,
+ exchange_index& index);
+ void recoverBindings(TxnCtxt& txn,
+ exchange_index& exchanges,
+ queue_index& queues);
+ void recoverGeneral(TxnCtxt& txn,
+ qpid::broker::RecoveryManager& recovery);
+ int enqueueMessage(TxnCtxt& txn,
+ IdDbt& msgId,
+ qpid::broker::RecoverableMessage::shared_ptr& msg,
+ queue_index& index,
+ txn_list& locked,
+ message_index& prepared);
+ void readTplStore();
+ void recoverTplStore();
+ void recoverLockedMappings(txn_list& txns);
+ TxnCtxt* check(qpid::broker::TransactionContext* ctxt);
+ u_int64_t msgEncode(std::vector<char>& buff, const boost::intrusive_ptr<qpid::broker::PersistableMessage>& message);
+ void store(const qpid::broker::PersistableQueue* queue,
+ TxnCtxt* txn,
+ const boost::intrusive_ptr<qpid::broker::PersistableMessage>& message,
+ bool newId);
+ void async_dequeue(qpid::broker::TransactionContext* ctxt,
+ const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg,
+ const qpid::broker::PersistableQueue& queue);
+ void destroy(db_ptr db,
+ const qpid::broker::Persistable& p);
+ bool create(db_ptr db,
+ IdSequence& seq,
+ const qpid::broker::Persistable& p);
+ void completed(TxnCtxt& txn,
+ bool commit);
+ void deleteBindingsForQueue(const qpid::broker::PersistableQueue& queue);
+ void deleteBinding(const qpid::broker::PersistableExchange& exchange,
+ const qpid::broker::PersistableQueue& queue,
+ const std::string& key);
+
+ void put(db_ptr db,
+ DbTxn* txn,
+ Dbt& key,
+ Dbt& value);
+ void open(db_ptr db,
+ DbTxn* txn,
+ const char* file,
+ bool dupKey);
+ void closeDbs();
+
+ // journal functions
+ void createJrnlQueue(const qpid::broker::PersistableQueue& queue);
+ u_int32_t bHash(const std::string str);
+ std::string getJrnlDir(const qpid::broker::PersistableQueue& queue); //for exmaple /var/rhm/ + queueDir/
+ std::string getJrnlHashDir(const std::string& queueName);
+ std::string getJrnlBaseDir();
+ std::string getBdbBaseDir();
+ std::string getTplBaseDir();
+ inline void checkInit() {
+ // TODO: change the default dir to ~/.qpidd
+ if (!isInit) { init("/tmp"); isInit = true; }
+ }
+ void chkTplStoreInit();
+
+ // debug aid for printing XIDs that may contain non-printable chars
+ static std::string xid2str(const std::string xid) {
+ std::ostringstream oss;
+ oss << std::hex << std::setfill('0');
+ for (unsigned i=0; i<xid.size(); i++) {
+ if (isprint(xid[i]))
+ oss << xid[i];
+ else
+ oss << "/" << std::setw(2) << (int)((char)xid[i]);
+ }
+ return oss.str();
+ }
+
+ public:
+ typedef boost::shared_ptr<MessageStoreImpl> shared_ptr;
+
+ MessageStoreImpl(qpid::broker::Broker* broker, const char* envpath = 0);
+
+ virtual ~MessageStoreImpl();
+
+ bool init(const qpid::Options* options);
+
+ bool init(const std::string& dir,
+ u_int16_t jfiles = defNumJrnlFiles,
+ u_int32_t jfileSizePgs = defJrnlFileSizePgs,
+ const bool truncateFlag = false,
+ u_int32_t wCachePageSize = defWCachePageSize,
+ u_int16_t tplJfiles = defTplNumJrnlFiles,
+ u_int32_t tplJfileSizePgs = defTplJrnlFileSizePgs,
+ u_int32_t tplWCachePageSize = defTplWCachePageSize,
+ bool autoJExpand = defAutoJrnlExpand,
+ u_int16_t autoJExpandMaxFiles = defAutoJrnlExpandMaxFiles);
+
+ void truncateInit(const bool saveStoreContent = false);
+
+ void initManagement ();
+
+ void finalize();
+
+ void create(qpid::broker::PersistableQueue& queue,
+ const qpid::framing::FieldTable& args);
+
+ void destroy(qpid::broker::PersistableQueue& queue);
+
+ void create(const qpid::broker::PersistableExchange& queue,
+ const qpid::framing::FieldTable& args);
+
+ void destroy(const qpid::broker::PersistableExchange& queue);
+
+ void bind(const qpid::broker::PersistableExchange& exchange,
+ const qpid::broker::PersistableQueue& queue,
+ const std::string& key,
+ const qpid::framing::FieldTable& args);
+
+ void unbind(const qpid::broker::PersistableExchange& exchange,
+ const qpid::broker::PersistableQueue& queue,
+ const std::string& key,
+ const qpid::framing::FieldTable& args);
+
+ void create(const qpid::broker::PersistableConfig& config);
+
+ void destroy(const qpid::broker::PersistableConfig& config);
+
+ void recover(qpid::broker::RecoveryManager& queues);
+
+ void stage(const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg);
+
+ void destroy(qpid::broker::PersistableMessage& msg);
+
+ void appendContent(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg,
+ const std::string& data);
+
+ void loadContent(const qpid::broker::PersistableQueue& queue,
+ const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg,
+ std::string& data,
+ uint64_t offset,
+ uint32_t length);
+
+ void enqueue(qpid::broker::TransactionContext* ctxt,
+ const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg,
+ const qpid::broker::PersistableQueue& queue);
+
+ void dequeue(qpid::broker::TransactionContext* ctxt,
+ const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg,
+ const qpid::broker::PersistableQueue& queue);
+
+ void flush(const qpid::broker::PersistableQueue& queue);
+
+ u_int32_t outstandingQueueAIO(const qpid::broker::PersistableQueue& queue);
+
+ void collectPreparedXids(std::set<std::string>& xids);
+
+ std::auto_ptr<qpid::broker::TransactionContext> begin();
+
+ std::auto_ptr<qpid::broker::TPCTransactionContext> begin(const std::string& xid);
+
+ void prepare(qpid::broker::TPCTransactionContext& ctxt);
+
+ void localPrepare(TxnCtxt* ctxt);
+
+ void commit(qpid::broker::TransactionContext& ctxt);
+
+ void abort(qpid::broker::TransactionContext& ctxt);
+
+ qpid::management::ManagementObject::shared_ptr GetManagementObject (void) const
+ { return mgmtObject; }
+
+ inline qpid::management::Manageable::status_t ManagementMethod (u_int32_t, qpid::management::Args&, std::string&)
+ { return qpid::management::Manageable::STATUS_OK; }
+
+ std::string getStoreDir() const;
+
+ private:
+ void journalDeleted(JournalImpl&);
+
+}; // class MessageStoreImpl
+
+} // namespace msgstore
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_MESSAGESTOREIMPL_H
diff --git a/qpid/cpp/src/qpid/legacystore/PreparedTransaction.cpp b/qpid/cpp/src/qpid/legacystore/PreparedTransaction.cpp
new file mode 100644
index 0000000000..50b81e2824
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/PreparedTransaction.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/legacystore/PreparedTransaction.h"
+#include <algorithm>
+
+using namespace mrg::msgstore;
+using std::string;
+
+void LockedMappings::add(queue_id queue, message_id message)
+{
+ locked.push_back(std::make_pair(queue, message));
+}
+
+bool LockedMappings::isLocked(queue_id queue, message_id message)
+{
+ idpair op( std::make_pair(queue, message) );
+ return find(locked.begin(), locked.end(), op) != locked.end();
+}
+
+void LockedMappings::add(LockedMappings::map& map, std::string& key, queue_id queue, message_id message)
+{
+ LockedMappings::map::iterator i = map.find(key);
+ if (i == map.end()) {
+ LockedMappings::shared_ptr ptr(new LockedMappings());
+ i = map.insert(std::make_pair(key, ptr)).first;
+ }
+ i->second->add(queue, message);
+}
+
+bool PreparedTransaction::isLocked(queue_id queue, message_id message)
+{
+ return (enqueues.get() && enqueues->isLocked(queue, message))
+ || (dequeues.get() && dequeues->isLocked(queue, message));
+}
+
+
+bool PreparedTransaction::isLocked(PreparedTransaction::list& txns, queue_id queue, message_id message)
+{
+ for (PreparedTransaction::list::iterator i = txns.begin(); i != txns.end(); i++) {
+ if (i->isLocked(queue, message)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+PreparedTransaction::list::iterator PreparedTransaction::getLockedPreparedTransaction(PreparedTransaction::list& txns, queue_id queue, message_id message)
+{
+ for (PreparedTransaction::list::iterator i = txns.begin(); i != txns.end(); i++) {
+ if (i->isLocked(queue, message)) {
+ return i;
+ }
+ }
+ return txns.end();
+}
+
+PreparedTransaction::PreparedTransaction(const std::string& _xid,
+ LockedMappings::shared_ptr _enqueues,
+ LockedMappings::shared_ptr _dequeues)
+
+ : xid(_xid), enqueues(_enqueues), dequeues(_dequeues) {}
+
diff --git a/qpid/cpp/src/qpid/legacystore/PreparedTransaction.h b/qpid/cpp/src/qpid/legacystore/PreparedTransaction.h
new file mode 100644
index 0000000000..c5f7b9458a
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/PreparedTransaction.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LEGACYSTORE_PREPAREDTRANSACTION_H
+#define QPID_LEGACYSTORE_PREPAREDTRANSACTION_H
+
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+#include <boost/shared_ptr.hpp>
+#include <boost/ptr_container/ptr_list.hpp>
+
+namespace mrg{
+namespace msgstore{
+
+typedef u_int64_t queue_id;
+typedef u_int64_t message_id;
+
+class LockedMappings
+{
+public:
+ typedef boost::shared_ptr<LockedMappings> shared_ptr;
+ typedef std::map<std::string, shared_ptr> map;
+ typedef std::pair<queue_id, message_id> idpair;
+ typedef std::list<idpair>::iterator iterator;
+
+ void add(queue_id queue, message_id message);
+ bool isLocked(queue_id queue, message_id message);
+ std::size_t size() { return locked.size(); }
+ iterator begin() { return locked.begin(); }
+ iterator end() { return locked.end(); }
+
+ static void add(LockedMappings::map& map, std::string& key, queue_id queue, message_id message);
+
+private:
+ std::list<idpair> locked;
+};
+
+struct PreparedTransaction
+{
+ typedef boost::ptr_list<PreparedTransaction> list;
+
+ const std::string xid;
+ const LockedMappings::shared_ptr enqueues;
+ const LockedMappings::shared_ptr dequeues;
+
+ PreparedTransaction(const std::string& xid, LockedMappings::shared_ptr enqueues, LockedMappings::shared_ptr dequeues);
+ bool isLocked(queue_id queue, message_id message);
+ static bool isLocked(PreparedTransaction::list& txns, queue_id queue, message_id message);
+ static PreparedTransaction::list::iterator getLockedPreparedTransaction(PreparedTransaction::list& txns, queue_id queue, message_id message);
+};
+
+}}
+
+#endif // ifndef QPID_LEGACYSTORE_PREPAREDTRANSACTION_H
diff --git a/qpid/cpp/src/qpid/legacystore/StoreException.h b/qpid/cpp/src/qpid/legacystore/StoreException.h
new file mode 100644
index 0000000000..6624aafd5a
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/StoreException.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LEGACYSTORE_STOREEXCEPTION_H
+#define QPID_LEGACYSTORE_STOREEXCEPTION_H
+
+#include "qpid/legacystore/IdDbt.h"
+#include <boost/format.hpp>
+
+namespace mrg{
+namespace msgstore{
+
+class StoreException : public std::exception
+{
+ std::string text;
+public:
+ StoreException(const std::string& _text) : text(_text) {}
+ StoreException(const std::string& _text, const DbException& cause) : text(_text + ": " + cause.what()) {}
+ virtual ~StoreException() throw() {}
+ virtual const char* what() const throw() { return text.c_str(); }
+};
+
+class StoreFullException : public StoreException
+{
+public:
+ StoreFullException(const std::string& _text) : StoreException(_text) {}
+ StoreFullException(const std::string& _text, const DbException& cause) : StoreException(_text, cause) {}
+ virtual ~StoreFullException() throw() {}
+
+};
+
+#define THROW_STORE_EXCEPTION(MESSAGE) throw StoreException(boost::str(boost::format("%s (%s:%d)") % (MESSAGE) % __FILE__ % __LINE__))
+#define THROW_STORE_EXCEPTION_2(MESSAGE, EXCEPTION) throw StoreException(boost::str(boost::format("%s (%s:%d)") % (MESSAGE) % __FILE__ % __LINE__), EXCEPTION)
+#define THROW_STORE_FULL_EXCEPTION(MESSAGE) throw StoreFullException(boost::str(boost::format("%s (%s:%d)") % (MESSAGE) % __FILE__ % __LINE__))
+
+}}
+
+#endif // ifndef QPID_LEGACYSTORE_STOREEXCEPTION_H
diff --git a/qpid/cpp/src/qpid/legacystore/StorePlugin.cpp b/qpid/cpp/src/qpid/legacystore/StorePlugin.cpp
new file mode 100644
index 0000000000..5cba10d0f9
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/StorePlugin.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/broker/Broker.h"
+#include "qpid/Plugin.h"
+#include "qpid/Options.h"
+#include "qpid/DataDir.h"
+#include "qpid/log/Statement.h"
+#include "qpid/legacystore/MessageStoreImpl.h"
+
+using mrg::msgstore::MessageStoreImpl;
+
+namespace qpid {
+namespace broker {
+
+using namespace std;
+
+struct StorePlugin : public Plugin {
+
+ MessageStoreImpl::StoreOptions options;
+ boost::shared_ptr<MessageStoreImpl> store;
+
+ Options* getOptions() { return &options; }
+
+ void earlyInitialize (Plugin::Target& target)
+ {
+ Broker* broker = dynamic_cast<Broker*>(&target);
+ if (!broker) return;
+ store.reset(new MessageStoreImpl(broker));
+ const DataDir& dataDir = broker->getDataDir ();
+ if (options.storeDir.empty ())
+ {
+ if (!dataDir.isEnabled ())
+ throw Exception ("msgstore: If --data-dir is blank or --no-data-dir is specified, --store-dir must be present.");
+
+ options.storeDir = dataDir.getPath ();
+ }
+ store->init(&options);
+ boost::shared_ptr<qpid::broker::MessageStore> brokerStore(store);
+ broker->setStore(brokerStore);
+ target.addFinalizer(boost::bind(&StorePlugin::finalize, this));
+ }
+
+ void initialize(Plugin::Target& target)
+ {
+ Broker* broker = dynamic_cast<Broker*>(&target);
+ if (!broker) return;
+ if (!store) return;
+ QPID_LOG(info, "Enabling management instrumentation for the store.");
+ store->initManagement();
+ }
+
+ void finalize()
+ {
+ store.reset();
+ }
+
+ const char* id() {return "StorePlugin";}
+};
+
+static StorePlugin instance; // Static initialization.
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/legacystore/TxnCtxt.cpp b/qpid/cpp/src/qpid/legacystore/TxnCtxt.cpp
new file mode 100644
index 0000000000..b02b5e887f
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/TxnCtxt.cpp
@@ -0,0 +1,184 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/legacystore/TxnCtxt.h"
+
+#include <sstream>
+
+#include "qpid/legacystore/jrnl/jexception.h"
+#include "qpid/legacystore/StoreException.h"
+
+namespace mrg {
+namespace msgstore {
+
+void TxnCtxt::completeTxn(bool commit) {
+ sync();
+ for (ipqItr i = impactedQueues.begin(); i != impactedQueues.end(); i++) {
+ commitTxn(static_cast<JournalImpl*>(*i), commit);
+ }
+ impactedQueues.clear();
+ if (preparedXidStorePtr)
+ commitTxn(preparedXidStorePtr, commit);
+}
+
+void TxnCtxt::commitTxn(JournalImpl* jc, bool commit) {
+ if (jc && loggedtx) { /* if using journal */
+ boost::intrusive_ptr<DataTokenImpl> dtokp(new DataTokenImpl);
+ dtokp->addRef();
+ dtokp->set_external_rid(true);
+ dtokp->set_rid(loggedtx->next());
+ try {
+ if (commit) {
+ jc->txn_commit(dtokp.get(), getXid());
+ sync();
+ } else {
+ jc->txn_abort(dtokp.get(), getXid());
+ }
+ } catch (const journal::jexception& e) {
+ THROW_STORE_EXCEPTION(std::string("Error commit") + e.what());
+ }
+ }
+}
+
+// static
+qpid::sys::uuid_t TxnCtxt::uuid;
+
+// static
+IdSequence TxnCtxt::uuidSeq;
+
+// static
+bool TxnCtxt::staticInit = TxnCtxt::setUuid();
+
+// static
+bool TxnCtxt::setUuid() {
+ qpid::sys::uuid_generate(uuid);
+ return true;
+}
+
+TxnCtxt::TxnCtxt(IdSequence* _loggedtx) : loggedtx(_loggedtx), dtokp(new DataTokenImpl), preparedXidStorePtr(0), txn(0) {
+ if (loggedtx) {
+// // Human-readable tid: 53 bytes
+// // uuit_t is a char[16]
+// tid.reserve(53);
+// u_int64_t* u1 = (u_int64_t*)uuid;
+// u_int64_t* u2 = (u_int64_t*)(uuid + sizeof(u_int64_t));
+// std::stringstream s;
+// s << "tid:" << std::hex << std::setfill('0') << std::setw(16) << uuidSeq.next() << ":" << std::setw(16) << *u1 << std::setw(16) << *u2;
+// tid.assign(s.str());
+
+ // Binary tid: 24 bytes
+ tid.reserve(24);
+ u_int64_t c = uuidSeq.next();
+ tid.append((char*)&c, sizeof(c));
+ tid.append((char*)&uuid, sizeof(uuid));
+ }
+}
+
+TxnCtxt::TxnCtxt(std::string _tid, IdSequence* _loggedtx) : loggedtx(_loggedtx), dtokp(new DataTokenImpl), preparedXidStorePtr(0), tid(_tid), txn(0) {}
+
+TxnCtxt::~TxnCtxt() { abort(); }
+
+void TxnCtxt::sync() {
+ if (loggedtx) {
+ try {
+ for (ipqItr i = impactedQueues.begin(); i != impactedQueues.end(); i++)
+ jrnl_flush(static_cast<JournalImpl*>(*i));
+ if (preparedXidStorePtr)
+ jrnl_flush(preparedXidStorePtr);
+ for (ipqItr i = impactedQueues.begin(); i != impactedQueues.end(); i++)
+ jrnl_sync(static_cast<JournalImpl*>(*i), &journal::jcntl::_aio_cmpl_timeout);
+ if (preparedXidStorePtr)
+ jrnl_sync(preparedXidStorePtr, &journal::jcntl::_aio_cmpl_timeout);
+ } catch (const journal::jexception& e) {
+ THROW_STORE_EXCEPTION(std::string("Error during txn sync: ") + e.what());
+ }
+ }
+}
+
+void TxnCtxt::jrnl_flush(JournalImpl* jc) {
+ if (jc && !(jc->is_txn_synced(getXid())))
+ jc->flush();
+}
+
+void TxnCtxt::jrnl_sync(JournalImpl* jc, timespec* timeout) {
+ if (!jc || jc->is_txn_synced(getXid()))
+ return;
+ while (jc->get_wr_aio_evt_rem()) {
+ if (jc->get_wr_events(timeout) == journal::jerrno::AIO_TIMEOUT && timeout)
+ THROW_STORE_EXCEPTION(std::string("Error: timeout waiting for TxnCtxt::jrnl_sync()"));
+ }
+}
+
+void TxnCtxt::begin(DbEnv* env, bool sync) {
+ int err;
+ try { err = env->txn_begin(0, &txn, 0); }
+ catch (const DbException&) { txn = 0; throw; }
+ if (err != 0) {
+ std::ostringstream oss;
+ oss << "Error: Env::txn_begin() returned error code: " << err;
+ THROW_STORE_EXCEPTION(oss.str());
+ }
+ if (sync)
+ globalHolder = AutoScopedLock(new qpid::sys::Mutex::ScopedLock(globalSerialiser));
+}
+
+void TxnCtxt::commit() {
+ if (txn) {
+ txn->commit(0);
+ txn = 0;
+ globalHolder.reset();
+ }
+}
+
+void TxnCtxt::abort(){
+ if (txn) {
+ txn->abort();
+ txn = 0;
+ globalHolder.reset();
+ }
+}
+
+DbTxn* TxnCtxt::get() { return txn; }
+
+bool TxnCtxt::isTPC() { return false; }
+
+const std::string& TxnCtxt::getXid() { return tid; }
+
+void TxnCtxt::addXidRecord(qpid::broker::ExternalQueueStore* queue) { impactedQueues.insert(queue); }
+
+void TxnCtxt::complete(bool commit) { completeTxn(commit); }
+
+bool TxnCtxt::impactedQueuesEmpty() { return impactedQueues.empty(); }
+
+DataTokenImpl* TxnCtxt::getDtok() { return dtokp.get(); }
+
+void TxnCtxt::incrDtokRef() { dtokp->addRef(); }
+
+void TxnCtxt::recoverDtok(const u_int64_t rid, const std::string xid) {
+ dtokp->set_rid(rid);
+ dtokp->set_wstate(DataTokenImpl::ENQ);
+ dtokp->set_xid(xid);
+ dtokp->set_external_rid(true);
+}
+
+TPCTxnCtxt::TPCTxnCtxt(const std::string& _xid, IdSequence* _loggedtx) : TxnCtxt(_loggedtx), xid(_xid) {}
+
+}}
diff --git a/qpid/cpp/src/qpid/legacystore/TxnCtxt.h b/qpid/cpp/src/qpid/legacystore/TxnCtxt.h
new file mode 100644
index 0000000000..85f0226cdc
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/TxnCtxt.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LEGACYSTORE_TXNCTXT_H
+#define QPID_LEGACYSTORE_TXNCTXT_H
+
+#include "db-inc.h"
+#include <memory>
+#include <set>
+#include <string>
+
+#include "qpid/legacystore/DataTokenImpl.h"
+#include "qpid/legacystore/IdSequence.h"
+#include "qpid/legacystore/JournalImpl.h"
+#include "qpid/broker/PersistableQueue.h"
+#include "qpid/broker/TransactionalStore.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/uuid.h"
+
+#include <boost/intrusive_ptr.hpp>
+
+namespace mrg {
+namespace msgstore {
+
+class TxnCtxt : public qpid::broker::TransactionContext
+{
+ protected:
+ static qpid::sys::Mutex globalSerialiser;
+
+ static qpid::sys::uuid_t uuid;
+ static IdSequence uuidSeq;
+ static bool staticInit;
+ static bool setUuid();
+
+ typedef std::set<qpid::broker::ExternalQueueStore*> ipqdef;
+ typedef ipqdef::iterator ipqItr;
+ typedef std::auto_ptr<qpid::sys::Mutex::ScopedLock> AutoScopedLock;
+
+ ipqdef impactedQueues; // list of Queues used in the txn
+ IdSequence* loggedtx;
+ boost::intrusive_ptr<DataTokenImpl> dtokp;
+ AutoScopedLock globalHolder;
+ JournalImpl* preparedXidStorePtr;
+
+ /**
+ * local txn id, if non XA.
+ */
+ std::string tid;
+ DbTxn* txn;
+
+ virtual void completeTxn(bool commit);
+ void commitTxn(JournalImpl* jc, bool commit);
+ void jrnl_flush(JournalImpl* jc);
+ void jrnl_sync(JournalImpl* jc, timespec* timeout);
+
+ public:
+ TxnCtxt(IdSequence* _loggedtx=NULL);
+ TxnCtxt(std::string _tid, IdSequence* _loggedtx);
+ virtual ~TxnCtxt();
+
+ /**
+ * Call to make sure all the data for this txn is written to safe store
+ *
+ *@return if the data successfully synced.
+ */
+ void sync();
+ void begin(DbEnv* env, bool sync = false);
+ void commit();
+ void abort();
+ DbTxn* get();
+ virtual bool isTPC();
+ virtual const std::string& getXid();
+
+ void addXidRecord(qpid::broker::ExternalQueueStore* queue);
+ inline void prepare(JournalImpl* _preparedXidStorePtr) { preparedXidStorePtr = _preparedXidStorePtr; }
+ void complete(bool commit);
+ bool impactedQueuesEmpty();
+ DataTokenImpl* getDtok();
+ void incrDtokRef();
+ void recoverDtok(const u_int64_t rid, const std::string xid);
+};
+
+
+class TPCTxnCtxt : public TxnCtxt, public qpid::broker::TPCTransactionContext
+{
+ protected:
+ const std::string xid;
+
+ public:
+ TPCTxnCtxt(const std::string& _xid, IdSequence* _loggedtx);
+ inline virtual bool isTPC() { return true; }
+ inline virtual const std::string& getXid() { return xid; }
+};
+
+}}
+
+#endif // ifndef QPID_LEGACYSTORE_TXNCTXT_H
+
+
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/aio.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/aio.cpp
new file mode 100644
index 0000000000..ffbddd887e
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/aio.cpp
@@ -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.
+ *
+ */
+
+/**
+ * \file aio.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::aio (libaio interface
+ * encapsulation). See comments in file aio.h for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#include "qpid/legacystore/jrnl/aio.h"
+
+namespace mrg
+{
+namespace journal
+{
+
+} // namespace journal
+} // namespace mrg
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/aio.h b/qpid/cpp/src/qpid/legacystore/jrnl/aio.h
new file mode 100644
index 0000000000..b1de5f79f7
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/aio.h
@@ -0,0 +1,153 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file aio.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * This file contains an encapsulation of the libaio interface used
+ * by the journal.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_AIO_H
+#define QPID_LEGACYSTORE_JRNL_AIO_H
+
+#include <libaio.h>
+#include <cstring>
+#include <sys/types.h>
+#include <string.h>
+
+namespace mrg
+{
+namespace journal
+{
+
+typedef iocb aio_cb;
+typedef io_event aio_event;
+
+/**
+ * \brief This class is a C++ wrapper class for the libaio functions used by the journal. Note that only those
+ * functions used by the journal are included here. This is not a complete implementation of all libaio functions.
+ */
+class aio
+{
+public:
+ static inline int queue_init(int maxevents, io_context_t* ctxp)
+ {
+ return ::io_queue_init(maxevents, ctxp);
+ }
+
+ static inline int queue_release(io_context_t ctx)
+ {
+ return ::io_queue_release(ctx);
+ }
+
+ static inline int submit(io_context_t ctx, long nr, aio_cb* aios[])
+ {
+ return ::io_submit(ctx, nr, aios);
+ }
+
+ static inline int getevents(io_context_t ctx, long min_nr, long nr, aio_event* events, timespec* const timeout)
+ {
+ return ::io_getevents(ctx, min_nr, nr, events, timeout);
+ }
+
+ /**
+ * \brief This function allows iocbs to be initialized with a pointer that can be re-used. This prepares an
+ * aio_cb struct for read use. (This is a wrapper for libaio's ::io_prep_pread() function.)
+ *
+ * \param aiocbp Pointer to the aio_cb struct to be prepared.
+ * \param fd File descriptor to be used for read.
+ * \param buf Pointer to buffer in which read data is to be placed.
+ * \param count Number of bytes to read - buffer must be large enough.
+ * \param offset Offset within file from which data will be read.
+ */
+ static inline void prep_pread(aio_cb* aiocbp, int fd, void* buf, std::size_t count, int64_t offset)
+ {
+ ::io_prep_pread(aiocbp, fd, buf, count, offset);
+ }
+
+ /**
+ * \brief Special version of libaio's io_prep_pread() which preserves the value of the data pointer. This allows
+ * iocbs to be initialized with a pointer that can be re-used. This prepares a aio_cb struct for read use.
+ *
+ * \param aiocbp Pointer to the aio_cb struct to be prepared.
+ * \param fd File descriptor to be used for read.
+ * \param buf Pointer to buffer in which read data is to be placed.
+ * \param count Number of bytes to read - buffer must be large enough.
+ * \param offset Offset within file from which data will be read.
+ */
+ static inline void prep_pread_2(aio_cb* aiocbp, int fd, void* buf, std::size_t count, int64_t offset)
+ {
+ std::memset((void*) ((char*) aiocbp + sizeof(void*)), 0, sizeof(aio_cb) - sizeof(void*));
+ aiocbp->aio_fildes = fd;
+ aiocbp->aio_lio_opcode = IO_CMD_PREAD;
+ aiocbp->aio_reqprio = 0;
+ aiocbp->u.c.buf = buf;
+ aiocbp->u.c.nbytes = count;
+ aiocbp->u.c.offset = offset;
+ }
+
+ /**
+ * \brief This function allows iocbs to be initialized with a pointer that can be re-used. This function prepares
+ * an aio_cb struct for write use. (This is a wrapper for libaio's ::io_prep_pwrite() function.)
+ *
+ * \param aiocbp Pointer to the aio_cb struct to be prepared.
+ * \param fd File descriptor to be used for write.
+ * \param buf Pointer to buffer in which data to be written is located.
+ * \param count Number of bytes to write.
+ * \param offset Offset within file to which data will be written.
+ */
+ static inline void prep_pwrite(aio_cb* aiocbp, int fd, void* buf, std::size_t count, int64_t offset)
+ {
+ ::io_prep_pwrite(aiocbp, fd, buf, count, offset);
+ }
+
+ /**
+ * \brief Special version of libaio's io_prep_pwrite() which preserves the value of the data pointer. This allows
+ * iocbs to be initialized with a pointer that can be re-used. This function prepares an aio_cb struct for write
+ * use.
+ *
+ * \param aiocbp Pointer to the aio_cb struct to be prepared.
+ * \param fd File descriptor to be used for write.
+ * \param buf Pointer to buffer in which data to be written is located.
+ * \param count Number of bytes to write.
+ * \param offset Offset within file to which data will be written.
+ */
+ static inline void prep_pwrite_2(aio_cb* aiocbp, int fd, void* buf, std::size_t count, int64_t offset)
+ {
+ std::memset((void*) ((char*) aiocbp + sizeof(void*)), 0, sizeof(aio_cb) - sizeof(void*));
+ aiocbp->aio_fildes = fd;
+ aiocbp->aio_lio_opcode = IO_CMD_PWRITE;
+ aiocbp->aio_reqprio = 0;
+ aiocbp->u.c.buf = buf;
+ aiocbp->u.c.nbytes = count;
+ aiocbp->u.c.offset = offset;
+ }
+};
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_AIO_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/aio_callback.h b/qpid/cpp/src/qpid/legacystore/jrnl/aio_callback.h
new file mode 100644
index 0000000000..90249278a5
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/aio_callback.h
@@ -0,0 +1,57 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file aio_callback.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * This file contains the definition for the AIO callback function
+ * pointer.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_AIO_CALLBACK_H
+#define QPID_LEGACYSTORE_JRNL_AIO_CALLBACK_H
+
+#include <vector>
+#include <sys/types.h>
+
+namespace mrg
+{
+namespace journal
+{
+
+ class data_tok;
+
+ class aio_callback
+ {
+ public:
+ virtual ~aio_callback() {}
+ virtual void wr_aio_cb(std::vector<data_tok*>& dtokl) = 0;
+ virtual void rd_aio_cb(std::vector<u_int16_t>& pil) = 0;
+ };
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_AIO_CALLBACK_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/cvar.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/cvar.cpp
new file mode 100644
index 0000000000..e4010bf91f
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/cvar.cpp
@@ -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.
+ *
+ */
+
+/**
+ * \file cvar.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::cvar (condition variable). See
+ * comments in file cvar.h for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#include "qpid/legacystore/jrnl/cvar.h"
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/cvar.h b/qpid/cpp/src/qpid/legacystore/jrnl/cvar.h
new file mode 100644
index 0000000000..0498e743a2
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/cvar.h
@@ -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.
+ *
+ */
+
+/**
+ * \file cvar.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * This file contains a posix condition variable class.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_CVAR_H
+#define QPID_LEGACYSTORE_JRNL_CVAR_H
+
+#include <cstring>
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include "qpid/legacystore/jrnl/jexception.h"
+#include "qpid/legacystore/jrnl/smutex.h"
+#include "qpid/legacystore/jrnl/time_ns.h"
+#include <pthread.h>
+#include <sstream>
+
+namespace mrg
+{
+namespace journal
+{
+
+ // Ultra-simple thread condition variable class
+ class cvar
+ {
+ private:
+ const smutex& _sm;
+ pthread_cond_t _c;
+ public:
+ inline cvar(const smutex& sm) : _sm(sm) { ::pthread_cond_init(&_c, 0); }
+ inline ~cvar() { ::pthread_cond_destroy(&_c); }
+ inline void wait()
+ {
+ PTHREAD_CHK(::pthread_cond_wait(&_c, _sm.get()), "::pthread_cond_wait", "cvar", "wait");
+ }
+ inline void timedwait(timespec& ts)
+ {
+ PTHREAD_CHK(::pthread_cond_timedwait(&_c, _sm.get(), &ts), "::pthread_cond_timedwait", "cvar", "timedwait");
+ }
+ inline bool waitintvl(const long intvl_ns)
+ {
+ time_ns t; t.now(); t+=intvl_ns;
+ int ret = ::pthread_cond_timedwait(&_c, _sm.get(), &t);
+ if (ret == ETIMEDOUT)
+ return true;
+ PTHREAD_CHK(ret, "::pthread_cond_timedwait", "cvar", "waitintvl");
+ return false;
+ }
+ inline void signal()
+ {
+ PTHREAD_CHK(::pthread_cond_signal(&_c), "::pthread_cond_signal", "cvar", "notify");
+ }
+ inline void broadcast()
+ {
+ PTHREAD_CHK(::pthread_cond_broadcast(&_c), "::pthread_cond_broadcast", "cvar", "broadcast");
+ }
+ };
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_CVAR_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/data_tok.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/data_tok.cpp
new file mode 100644
index 0000000000..ce7206d80d
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/data_tok.cpp
@@ -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.
+ *
+ */
+
+/**
+ * \file data_tok.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::data_tok (data block token).
+ * See comments in file data_tok.h for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#include "qpid/legacystore/jrnl/data_tok.h"
+
+#include <iomanip>
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include "qpid/legacystore/jrnl/jexception.h"
+#include "qpid/legacystore/jrnl/slock.h"
+#include <sstream>
+
+namespace mrg
+{
+namespace journal
+{
+
+// Static members
+
+u_int64_t data_tok::_cnt = 0;
+smutex data_tok::_mutex;
+
+data_tok::data_tok():
+ _wstate(NONE),
+ _rstate(UNREAD),
+ _dsize(0),
+ _dblks_written(0),
+ _dblks_read(0),
+ _pg_cnt(0),
+ _fid(0),
+ _rid(0),
+ _xid(),
+ _dequeue_rid(0),
+ _external_rid(false)
+{
+ slock s(_mutex);
+ _icnt = _cnt++;
+}
+
+data_tok::~data_tok() {}
+
+const char*
+data_tok::wstate_str() const
+{
+ return wstate_str(_wstate);
+}
+
+const char*
+data_tok::wstate_str(write_state wstate)
+{
+ switch (wstate)
+ {
+ case NONE:
+ return "NONE";
+ case ENQ_CACHED:
+ return "ENQ_CACHED";
+ case ENQ_PART:
+ return "ENQ_PART";
+ case ENQ_SUBM:
+ return "ENQ_SUBM";
+ case ENQ:
+ return "ENQ";
+ case DEQ_CACHED:
+ return "DEQ_CACHED";
+ case DEQ_PART:
+ return "DEQ_PART";
+ case DEQ_SUBM:
+ return "DEQ_SUBM";
+ case DEQ:
+ return "DEQ";
+ case ABORT_CACHED:
+ return "ABORT_CACHED";
+ case ABORT_PART:
+ return "ABORT_PART";
+ case ABORT_SUBM:
+ return "ABORT_SUBM";
+ case ABORTED:
+ return "ABORTED";
+ case COMMIT_CACHED:
+ return "COMMIT_CACHED";
+ case COMMIT_PART:
+ return "COMMIT_PART";
+ case COMMIT_SUBM:
+ return "COMMIT_SUBM";
+ case COMMITTED:
+ return "COMMITTED";
+ }
+ // Not using default: forces compiler to ensure all cases are covered.
+ return "<wstate unknown>";
+}
+
+const char*
+data_tok::rstate_str() const
+{
+ return rstate_str(_rstate);
+}
+
+const char*
+data_tok::rstate_str(read_state rstate)
+{
+ switch (rstate)
+ {
+ case NONE:
+ return "NONE";
+ case READ_PART:
+ return "READ_PART";
+ case SKIP_PART:
+ return "SKIP_PART";
+ case READ:
+ return "READ";
+ // Not using default: forces compiler to ensure all cases are covered.
+ }
+ return "<rstate unknown>";
+}
+
+void
+data_tok::set_rstate(const read_state rstate)
+{
+ if (_wstate != ENQ && rstate != UNREAD)
+ {
+ std::ostringstream oss;
+ oss << "Attempted to change read state to " << rstate_str(rstate);
+ oss << " while write state is not enqueued (wstate ENQ); wstate=" << wstate_str() << ".";
+ throw jexception(jerrno::JERR_DTOK_ILLEGALSTATE, oss.str(), "data_tok",
+ "set_rstate");
+ }
+ _rstate = rstate;
+}
+
+void
+data_tok::reset()
+{
+ _wstate = NONE;
+ _rstate = UNREAD;
+ _dsize = 0;
+ _dblks_written = 0;
+ _dblks_read = 0;
+ _pg_cnt = 0;
+ _fid = 0;
+ _rid = 0;
+ _xid.clear();
+}
+
+// debug aid
+std::string
+data_tok::status_str() const
+{
+ std::ostringstream oss;
+ oss << std::hex << std::setfill('0');
+ oss << "dtok id=0x" << _icnt << "; ws=" << wstate_str() << "; rs=" << rstate_str();
+ oss << "; fid=0x" << _fid << "; rid=0x" << _rid << "; xid=";
+ for (unsigned i=0; i<_xid.size(); i++)
+ {
+ if (isprint(_xid[i]))
+ oss << _xid[i];
+ else
+ oss << "/" << std::setw(2) << (int)((char)_xid[i]);
+ }
+ oss << "; drid=0x" << _dequeue_rid << " extrid=" << (_external_rid?"T":"F");
+ oss << "; ds=0x" << _dsize << "; dw=0x" << _dblks_written << "; dr=0x" << _dblks_read;
+ oss << " pc=0x" << _pg_cnt;
+ return oss.str();
+}
+
+} // namespace journal
+} // namespace mrg
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/data_tok.h b/qpid/cpp/src/qpid/legacystore/jrnl/data_tok.h
new file mode 100644
index 0000000000..e35f069399
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/data_tok.h
@@ -0,0 +1,172 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file data_tok.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::data_tok (data block token).
+ * See class documentation for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_DATA_TOK_H
+#define QPID_LEGACYSTORE_JRNL_DATA_TOK_H
+
+namespace mrg
+{
+namespace journal
+{
+class data_tok;
+}
+}
+
+#include <cassert>
+#include <cstddef>
+#include "qpid/legacystore/jrnl/smutex.h"
+#include <pthread.h>
+#include <string>
+#include <sys/types.h>
+
+namespace mrg
+{
+
+namespace journal
+{
+
+ /**
+ * \class data_tok
+ * \brief Data block token (data_tok) used to track wstate of a data block through asynchronous
+ * I/O process
+ */
+ class data_tok
+ {
+ public:
+ // TODO: Fix this, separate write state from operation
+ // ie: wstate = NONE, CACHED, PART, SUBM, COMPL
+ // op = ENQUEUE, DEQUEUE, ABORT, COMMIT
+ enum write_state
+ {
+ NONE, ///< Data block not sent to journal
+ ENQ_CACHED, ///< Data block enqueue written to page cache
+ ENQ_PART, ///< Data block part-submitted to AIO, waiting for page buffer to free up
+ ENQ_SUBM, ///< Data block enqueue submitted to AIO
+ ENQ, ///< Data block enqueue AIO write complete (enqueue complete)
+ DEQ_CACHED, ///< Data block dequeue written to page cache
+ DEQ_PART, ///< Data block part-submitted to AIO, waiting for page buffer to free up
+ DEQ_SUBM, ///< Data block dequeue submitted to AIO
+ DEQ, ///< Data block dequeue AIO write complete (dequeue complete)
+ ABORT_CACHED,
+ ABORT_PART,
+ ABORT_SUBM,
+ ABORTED,
+ COMMIT_CACHED,
+ COMMIT_PART,
+ COMMIT_SUBM,
+ COMMITTED
+ };
+
+ enum read_state
+ {
+ UNREAD, ///< Data block not read
+ READ_PART, ///< Data block is part-read; waiting for page buffer to fill
+ SKIP_PART, ///< Prev. dequeued dblock is part-skipped; waiting for page buffer to fill
+ READ ///< Data block is fully read
+ };
+
+ protected:
+ static smutex _mutex;
+ static u_int64_t _cnt;
+ u_int64_t _icnt;
+ write_state _wstate; ///< Enqueued / dequeued state of data
+ read_state _rstate; ///< Read state of data
+ std::size_t _dsize; ///< Data size in bytes
+ u_int32_t _dblks_written; ///< Data blocks read/written
+ u_int32_t _dblks_read; ///< Data blocks read/written
+ u_int32_t _pg_cnt; ///< Page counter - incr for each page containing part of data
+ u_int16_t _fid; ///< FID containing header of enqueue record
+ u_int64_t _rid; ///< RID of data set by enqueue operation
+ std::string _xid; ///< XID set by enqueue operation
+ u_int64_t _dequeue_rid; ///< RID of data set by dequeue operation
+ bool _external_rid; ///< Flag to indicate external setting of rid
+
+ public:
+ data_tok();
+ virtual ~data_tok();
+
+ inline u_int64_t id() const { return _icnt; }
+ inline write_state wstate() const { return _wstate; }
+ const char* wstate_str() const;
+ static const char* wstate_str(write_state wstate);
+ inline read_state rstate() const { return _rstate; }
+ const char* rstate_str() const;
+ static const char* rstate_str(read_state rstate);
+ inline bool is_writable() const { return _wstate == NONE || _wstate == ENQ_PART; }
+ inline bool is_enqueued() const { return _wstate == ENQ; }
+ inline bool is_readable() const { return _wstate == ENQ; }
+ inline bool is_read() const { return _rstate == READ; }
+ inline bool is_dequeueable() const { return _wstate == ENQ || _wstate == DEQ_PART; }
+ inline void set_wstate(const write_state wstate) { _wstate = wstate; }
+ void set_rstate(const read_state rstate);
+ inline std::size_t dsize() const { return _dsize; }
+ inline void set_dsize(std::size_t dsize) { _dsize = dsize; }
+
+ inline u_int32_t dblocks_written() const { return _dblks_written; }
+ inline void incr_dblocks_written(u_int32_t dblks_written)
+ { _dblks_written += dblks_written; }
+ inline void set_dblocks_written(u_int32_t dblks_written) { _dblks_written = dblks_written; }
+
+ inline u_int32_t dblocks_read() const { return _dblks_read; }
+ inline void incr_dblocks_read(u_int32_t dblks_read) { _dblks_read += dblks_read; }
+ inline void set_dblocks_read(u_int32_t dblks_read) { _dblks_read = dblks_read; }
+
+ inline u_int32_t pg_cnt() const { return _pg_cnt; }
+ inline u_int32_t incr_pg_cnt() { return ++_pg_cnt; }
+ inline u_int32_t decr_pg_cnt() { assert(_pg_cnt != 0); return --_pg_cnt; }
+
+ inline u_int16_t fid() const { return _fid; }
+ inline void set_fid(const u_int16_t fid) { _fid = fid; }
+ inline u_int64_t rid() const { return _rid; }
+ inline void set_rid(const u_int64_t rid) { _rid = rid; }
+ inline u_int64_t dequeue_rid() const {return _dequeue_rid; }
+ inline void set_dequeue_rid(const u_int64_t rid) { _dequeue_rid = rid; }
+ inline bool external_rid() const { return _external_rid; }
+ inline void set_external_rid(const bool external_rid) { _external_rid = external_rid; }
+
+ inline bool has_xid() const { return !_xid.empty(); }
+ inline const std::string& xid() const { return _xid; }
+ inline void clear_xid() { _xid.clear(); }
+ inline void set_xid(const std::string& xid) { _xid.assign(xid); }
+ inline void set_xid(const void* xidp, const std::size_t xid_len)
+ { _xid.assign((const char*)xidp, xid_len); }
+
+ void reset();
+
+ // debug aid
+ std::string status_str() const;
+ };
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_DATA_TOK_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/deq_hdr.h b/qpid/cpp/src/qpid/legacystore/jrnl/deq_hdr.h
new file mode 100644
index 0000000000..ae7081eac1
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/deq_hdr.h
@@ -0,0 +1,141 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file deq_hdr.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::deq_hdr (dequeue record),
+ * used to dequeue a previously enqueued record.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_DEQ_HDR_H
+#define QPID_LEGACYSTORE_JRNL_DEQ_HDR_H
+
+#include <cstddef>
+#include "qpid/legacystore/jrnl/rec_hdr.h"
+
+namespace mrg
+{
+namespace journal
+{
+
+#pragma pack(1)
+
+ /**
+ * \brief Struct for dequeue record.
+ *
+ * Struct for dequeue record. If this record has a non-zero xidsize field (i.e., there is a
+ * valid XID), then this header is followed by the XID of xidsize bytes and a rec_tail. If,
+ * on the other hand, this record has a zero xidsize (i.e., there is no XID), then the rec_tail
+ * is absent.
+ *
+ * Note that this record had its own rid distinct from the rid of the record it is dequeueing.
+ * The rid field below is the rid of the dequeue record itself; the deq-rid field is the rid of a
+ * previous enqueue record being dequeued by this record.
+ *
+ * Record header info in binary format (32 bytes):
+ * <pre>
+ * 0 7
+ * +---+---+---+---+---+---+---+---+ -+
+ * | magic | v | e | flags | |
+ * +---+---+---+---+---+---+---+---+ | struct hdr
+ * | rid | |
+ * +---+---+---+---+---+---+---+---+ -+
+ * | deq-rid |
+ * +---+---+---+---+---+---+---+---+
+ * | xidsize |
+ * +---+---+---+---+---+---+---+---+
+ * v = file version (If the format or encoding of this file changes, then this
+ * number should be incremented)
+ * e = endian flag, false (0x00) for little endian, true (0x01) for big endian
+ * </pre>
+ *
+ * Note that journal files should be transferable between 32- and 64-bit
+ * hardware of the same endianness, but not between hardware of opposite
+ * entianness without some sort of binary conversion utility. Thus buffering
+ * will be needed for types that change size between 32- and 64-bit compiles.
+ */
+ struct deq_hdr : rec_hdr
+ {
+ u_int64_t _deq_rid; ///< Record ID of dequeued record
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ u_int32_t _filler0; ///< Big-endian filler for 32-bit size_t
+#endif
+ std::size_t _xidsize; ///< XID size
+#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT)
+ u_int32_t _filler0; ///< Little-endian filler for 32-bit size_t
+#endif
+ static const u_int16_t DEQ_HDR_TXNCMPLCOMMIT_MASK = 0x10;
+
+ /**
+ * \brief Default constructor, which sets all values to 0.
+ */
+ inline deq_hdr(): rec_hdr(), _deq_rid(0),
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ _filler0(0),
+#endif
+ _xidsize(0)
+#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT)
+ , _filler0(0)
+#endif
+ {}
+
+ /**
+ * \brief Convenience constructor which initializes values during construction.
+ */
+ inline deq_hdr(const u_int32_t magic, const u_int8_t version, const u_int64_t rid,
+ const u_int64_t deq_rid, const std::size_t xidsize, const bool owi,
+ const bool txn_coml_commit = false):
+ rec_hdr(magic, version, rid, owi), _deq_rid(deq_rid),
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ _filler0(0),
+#endif
+ _xidsize(xidsize)
+#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT)
+ , _filler0(0)
+#endif
+ { set_txn_coml_commit(txn_coml_commit); }
+
+
+ inline bool is_txn_coml_commit() const { return _uflag & DEQ_HDR_TXNCMPLCOMMIT_MASK; }
+
+ inline void set_txn_coml_commit(const bool commit)
+ {
+ _uflag = commit ? _uflag | DEQ_HDR_TXNCMPLCOMMIT_MASK :
+ _uflag & (~DEQ_HDR_TXNCMPLCOMMIT_MASK);
+ }
+
+ /**
+ * \brief Returns the size of the header in bytes.
+ */
+ inline static std::size_t size() { return sizeof(deq_hdr); }
+ };
+
+#pragma pack()
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_DEQ_HDR_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/deq_rec.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/deq_rec.cpp
new file mode 100644
index 0000000000..270ebdd69e
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/deq_rec.cpp
@@ -0,0 +1,461 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file deq_rec.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * This file contains the code for the mrg::journal::deq_rec (journal dequeue
+ * record) class. See comments in file deq_rec.h for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#include "qpid/legacystore/jrnl/deq_rec.h"
+
+#include <cassert>
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <iomanip>
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include "qpid/legacystore/jrnl/jexception.h"
+#include <sstream>
+
+namespace mrg
+{
+namespace journal
+{
+
+deq_rec::deq_rec():
+ _deq_hdr(RHM_JDAT_DEQ_MAGIC, RHM_JDAT_VERSION, 0, 0, 0, false),
+ _xidp(0),
+ _buff(0),
+ _deq_tail(_deq_hdr)
+{}
+
+deq_rec::deq_rec(const u_int64_t rid, const u_int64_t drid, const void* const xidp,
+ const std::size_t xidlen, const bool owi, const bool txn_coml_commit):
+ _deq_hdr(RHM_JDAT_DEQ_MAGIC, RHM_JDAT_VERSION, rid, drid, xidlen, owi, txn_coml_commit),
+ _xidp(xidp),
+ _buff(0),
+ _deq_tail(_deq_hdr)
+{}
+
+deq_rec::~deq_rec()
+{
+ clean();
+}
+
+void
+deq_rec::reset()
+{
+ _deq_hdr._rid = 0;
+ _deq_hdr.set_owi(false);
+ _deq_hdr.set_txn_coml_commit(false);
+ _deq_hdr._deq_rid = 0;
+ _deq_hdr._xidsize = 0;
+ _deq_tail._rid = 0;
+ _xidp = 0;
+ _buff = 0;
+}
+
+void
+deq_rec::reset(const u_int64_t rid, const u_int64_t drid, const void* const xidp,
+ const std::size_t xidlen, const bool owi, const bool txn_coml_commit)
+{
+ _deq_hdr._rid = rid;
+ _deq_hdr.set_owi(owi);
+ _deq_hdr.set_txn_coml_commit(txn_coml_commit);
+ _deq_hdr._deq_rid = drid;
+ _deq_hdr._xidsize = xidlen;
+ _deq_tail._rid = rid;
+ _xidp = xidp;
+ _buff = 0;
+}
+
+u_int32_t
+deq_rec::encode(void* wptr, u_int32_t rec_offs_dblks, u_int32_t max_size_dblks)
+{
+ assert(wptr != 0);
+ assert(max_size_dblks > 0);
+ if (_xidp == 0)
+ assert(_deq_hdr._xidsize == 0);
+
+ std::size_t rec_offs = rec_offs_dblks * JRNL_DBLK_SIZE;
+ std::size_t rem = max_size_dblks * JRNL_DBLK_SIZE;
+ std::size_t wr_cnt = 0;
+ if (rec_offs_dblks) // Continuation of split dequeue record (over 2 or more pages)
+ {
+ if (size_dblks(rec_size()) - rec_offs_dblks > max_size_dblks) // Further split required
+ {
+ rec_offs -= sizeof(_deq_hdr);
+ std::size_t wsize = _deq_hdr._xidsize > rec_offs ? _deq_hdr._xidsize - rec_offs : 0;
+ std::size_t wsize2 = wsize;
+ if (wsize)
+ {
+ if (wsize > rem)
+ wsize = rem;
+ std::memcpy(wptr, (const char*)_xidp + rec_offs, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ rec_offs -= _deq_hdr._xidsize - wsize2;
+ if (rem)
+ {
+ wsize = sizeof(_deq_tail) > rec_offs ? sizeof(_deq_tail) - rec_offs : 0;
+ wsize2 = wsize;
+ if (wsize)
+ {
+ if (wsize > rem)
+ wsize = rem;
+ std::memcpy((char*)wptr + wr_cnt, (char*)&_deq_tail + rec_offs, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ rec_offs -= sizeof(_deq_tail) - wsize2;
+ }
+ assert(rem == 0);
+ assert(rec_offs == 0);
+ }
+ else // No further split required
+ {
+ rec_offs -= sizeof(_deq_hdr);
+ std::size_t wsize = _deq_hdr._xidsize > rec_offs ? _deq_hdr._xidsize - rec_offs : 0;
+ if (wsize)
+ {
+ std::memcpy(wptr, (const char*)_xidp + rec_offs, wsize);
+ wr_cnt += wsize;
+ }
+ rec_offs -= _deq_hdr._xidsize - wsize;
+ wsize = sizeof(_deq_tail) > rec_offs ? sizeof(_deq_tail) - rec_offs : 0;
+ if (wsize)
+ {
+ std::memcpy((char*)wptr + wr_cnt, (char*)&_deq_tail + rec_offs, wsize);
+ wr_cnt += wsize;
+#ifdef RHM_CLEAN
+ std::size_t rec_offs = rec_offs_dblks * JRNL_DBLK_SIZE;
+ std::size_t dblk_rec_size = size_dblks(rec_size() - rec_offs) * JRNL_DBLK_SIZE;
+ std::memset((char*)wptr + wr_cnt, RHM_CLEAN_CHAR, dblk_rec_size - wr_cnt);
+#endif
+ }
+ rec_offs -= sizeof(_deq_tail) - wsize;
+ assert(rec_offs == 0);
+ }
+ }
+ else // Start at beginning of data record
+ {
+ // Assumption: the header will always fit into the first dblk
+ std::memcpy(wptr, (void*)&_deq_hdr, sizeof(_deq_hdr));
+ wr_cnt = sizeof(_deq_hdr);
+ if (size_dblks(rec_size()) > max_size_dblks) // Split required - can only occur with xid
+ {
+ std::size_t wsize;
+ rem -= sizeof(_deq_hdr);
+ if (rem)
+ {
+ wsize = rem >= _deq_hdr._xidsize ? _deq_hdr._xidsize : rem;
+ std::memcpy((char*)wptr + wr_cnt, _xidp, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ if (rem)
+ {
+ wsize = rem >= sizeof(_deq_tail) ? sizeof(_deq_tail) : rem;
+ std::memcpy((char*)wptr + wr_cnt, (void*)&_deq_tail, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ assert(rem == 0);
+ }
+ else // No split required
+ {
+ if (_deq_hdr._xidsize)
+ {
+ std::memcpy((char*)wptr + wr_cnt, _xidp, _deq_hdr._xidsize);
+ wr_cnt += _deq_hdr._xidsize;
+ std::memcpy((char*)wptr + wr_cnt, (void*)&_deq_tail, sizeof(_deq_tail));
+ wr_cnt += sizeof(_deq_tail);
+ }
+#ifdef RHM_CLEAN
+ std::size_t dblk_rec_size = size_dblks(rec_size()) * JRNL_DBLK_SIZE;
+ std::memset((char*)wptr + wr_cnt, RHM_CLEAN_CHAR, dblk_rec_size - wr_cnt);
+#endif
+ }
+ }
+ return size_dblks(wr_cnt);
+}
+
+u_int32_t
+deq_rec::decode(rec_hdr& h, void* rptr, u_int32_t rec_offs_dblks, u_int32_t max_size_dblks)
+{
+ assert(rptr != 0);
+ assert(max_size_dblks > 0);
+
+ std::size_t rd_cnt = 0;
+ if (rec_offs_dblks) // Continuation of record on new page
+ {
+ const u_int32_t hdr_xid_dblks = size_dblks(deq_hdr::size() + _deq_hdr._xidsize);
+ const u_int32_t hdr_xid_tail_dblks = size_dblks(deq_hdr::size() + _deq_hdr._xidsize +
+ rec_tail::size());
+ const std::size_t rec_offs = rec_offs_dblks * JRNL_DBLK_SIZE;
+
+ if (hdr_xid_tail_dblks - rec_offs_dblks <= max_size_dblks)
+ {
+ // Remainder of xid fits within this page
+ if (rec_offs - deq_hdr::size() < _deq_hdr._xidsize)
+ {
+ // Part of xid still outstanding, copy remainder of xid and tail
+ const std::size_t xid_offs = rec_offs - deq_hdr::size();
+ const std::size_t xid_rem = _deq_hdr._xidsize - xid_offs;
+ std::memcpy((char*)_buff + xid_offs, rptr, xid_rem);
+ rd_cnt = xid_rem;
+ std::memcpy((void*)&_deq_tail, ((char*)rptr + rd_cnt), sizeof(_deq_tail));
+ chk_tail();
+ rd_cnt += sizeof(_deq_tail);
+ }
+ else
+ {
+ // Tail or part of tail only outstanding, complete tail
+ const std::size_t tail_offs = rec_offs - deq_hdr::size() - _deq_hdr._xidsize;
+ const std::size_t tail_rem = rec_tail::size() - tail_offs;
+ std::memcpy((char*)&_deq_tail + tail_offs, rptr, tail_rem);
+ chk_tail();
+ rd_cnt = tail_rem;
+ }
+ }
+ else if (hdr_xid_dblks - rec_offs_dblks <= max_size_dblks)
+ {
+ // Remainder of xid fits within this page, tail split
+ const std::size_t xid_offs = rec_offs - deq_hdr::size();
+ const std::size_t xid_rem = _deq_hdr._xidsize - xid_offs;
+ std::memcpy((char*)_buff + xid_offs, rptr, xid_rem);
+ rd_cnt += xid_rem;
+ const std::size_t tail_rem = (max_size_dblks * JRNL_DBLK_SIZE) - rd_cnt;
+ if (tail_rem)
+ {
+ std::memcpy((void*)&_deq_tail, ((char*)rptr + xid_rem), tail_rem);
+ rd_cnt += tail_rem;
+ }
+ }
+ else
+ {
+ // Remainder of xid split
+ const std::size_t xid_cp_size = (max_size_dblks * JRNL_DBLK_SIZE);
+ std::memcpy((char*)_buff + rec_offs - deq_hdr::size(), rptr, xid_cp_size);
+ rd_cnt += xid_cp_size;
+ }
+ }
+ else // Start of record
+ {
+ // Get and check header
+ _deq_hdr.hdr_copy(h);
+ rd_cnt = sizeof(rec_hdr);
+ //_deq_hdr._deq_rid = *(u_int64_t*)((char*)rptr + rd_cnt);
+ std::memcpy((void*)&_deq_hdr._deq_rid, (char*)rptr + rd_cnt, sizeof(u_int64_t));
+ rd_cnt += sizeof(u_int64_t);
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ rd_cnt += sizeof(u_int32_t); // Filler 0
+#endif
+ //_deq_hdr._xidsize = *(std::size_t*)((char*)rptr + rd_cnt);
+ std::memcpy((void*)&_deq_hdr._xidsize, (char*)rptr + rd_cnt, sizeof(std::size_t));
+ rd_cnt = _deq_hdr.size();
+ chk_hdr();
+ if (_deq_hdr._xidsize)
+ {
+ _buff = std::malloc(_deq_hdr._xidsize);
+ MALLOC_CHK(_buff, "_buff", "deq_rec", "decode");
+ const u_int32_t hdr_xid_dblks = size_dblks(deq_hdr::size() + _deq_hdr._xidsize);
+ const u_int32_t hdr_xid_tail_dblks = size_dblks(deq_hdr::size() + _deq_hdr._xidsize +
+ rec_tail::size());
+
+ // Check if record (header + xid + tail) fits within this page, we can check the
+ // tail before the expense of copying data to memory
+ if (hdr_xid_tail_dblks <= max_size_dblks)
+ {
+ // Entire header, xid and tail fits within this page
+ std::memcpy(_buff, (char*)rptr + rd_cnt, _deq_hdr._xidsize);
+ rd_cnt += _deq_hdr._xidsize;
+ std::memcpy((void*)&_deq_tail, (char*)rptr + rd_cnt, sizeof(_deq_tail));
+ rd_cnt += sizeof(_deq_tail);
+ chk_tail();
+ }
+ else if (hdr_xid_dblks <= max_size_dblks)
+ {
+ // Entire header and xid fit within this page, tail split
+ std::memcpy(_buff, (char*)rptr + rd_cnt, _deq_hdr._xidsize);
+ rd_cnt += _deq_hdr._xidsize;
+ const std::size_t tail_rem = (max_size_dblks * JRNL_DBLK_SIZE) - rd_cnt;
+ if (tail_rem)
+ {
+ std::memcpy((void*)&_deq_tail, (char*)rptr + rd_cnt, tail_rem);
+ rd_cnt += tail_rem;
+ }
+ }
+ else
+ {
+ // Header fits within this page, xid split
+ const std::size_t xid_cp_size = (max_size_dblks * JRNL_DBLK_SIZE) - rd_cnt;
+ std::memcpy(_buff, (char*)rptr + rd_cnt, xid_cp_size);
+ rd_cnt += xid_cp_size;
+ }
+ }
+ }
+ return size_dblks(rd_cnt);
+}
+
+bool
+deq_rec::rcv_decode(rec_hdr h, std::ifstream* ifsp, std::size_t& rec_offs)
+{
+ if (rec_offs == 0)
+ {
+ _deq_hdr.hdr_copy(h);
+ ifsp->read((char*)&_deq_hdr._deq_rid, sizeof(u_int64_t));
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ ifsp->ignore(sizeof(u_int32_t)); // _filler0
+#endif
+ ifsp->read((char*)&_deq_hdr._xidsize, sizeof(std::size_t));
+#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT)
+ ifsp->ignore(sizeof(u_int32_t)); // _filler0
+#endif
+ rec_offs = sizeof(_deq_hdr);
+ // Read header, allocate (if req'd) for xid
+ if (_deq_hdr._xidsize)
+ {
+ _buff = std::malloc(_deq_hdr._xidsize);
+ MALLOC_CHK(_buff, "_buff", "enq_rec", "rcv_decode");
+ }
+ }
+ if (rec_offs < sizeof(_deq_hdr) + _deq_hdr._xidsize)
+ {
+ // Read xid (or continue reading xid)
+ std::size_t offs = rec_offs - sizeof(_deq_hdr);
+ ifsp->read((char*)_buff + offs, _deq_hdr._xidsize - offs);
+ std::size_t size_read = ifsp->gcount();
+ rec_offs += size_read;
+ if (size_read < _deq_hdr._xidsize - offs)
+ {
+ assert(ifsp->eof());
+ // As we may have read past eof, turn off fail bit
+ ifsp->clear(ifsp->rdstate()&(~std::ifstream::failbit));
+ assert(!ifsp->fail() && !ifsp->bad());
+ return false;
+ }
+ }
+ if (rec_offs < sizeof(_deq_hdr) +
+ (_deq_hdr._xidsize ? _deq_hdr._xidsize + sizeof(rec_tail) : 0))
+ {
+ // Read tail (or continue reading tail)
+ std::size_t offs = rec_offs - sizeof(_deq_hdr) - _deq_hdr._xidsize;
+ ifsp->read((char*)&_deq_tail + offs, sizeof(rec_tail) - offs);
+ std::size_t size_read = ifsp->gcount();
+ rec_offs += size_read;
+ if (size_read < sizeof(rec_tail) - offs)
+ {
+ assert(ifsp->eof());
+ // As we may have read past eof, turn off fail bit
+ ifsp->clear(ifsp->rdstate()&(~std::ifstream::failbit));
+ assert(!ifsp->fail() && !ifsp->bad());
+ return false;
+ }
+ }
+ ifsp->ignore(rec_size_dblks() * JRNL_DBLK_SIZE - rec_size());
+ if (_deq_hdr._xidsize)
+ chk_tail(); // Throws if tail invalid or record incomplete
+ assert(!ifsp->fail() && !ifsp->bad());
+ return true;
+}
+
+std::size_t
+deq_rec::get_xid(void** const xidpp)
+{
+ if (!_buff)
+ {
+ *xidpp = 0;
+ return 0;
+ }
+ *xidpp = _buff;
+ return _deq_hdr._xidsize;
+}
+
+std::string&
+deq_rec::str(std::string& str) const
+{
+ std::ostringstream oss;
+ oss << "deq_rec: m=" << _deq_hdr._magic;
+ oss << " v=" << (int)_deq_hdr._version;
+ oss << " rid=" << _deq_hdr._rid;
+ oss << " drid=" << _deq_hdr._deq_rid;
+ if (_xidp)
+ oss << " xid=\"" << _xidp << "\"";
+ str.append(oss.str());
+ return str;
+}
+
+std::size_t
+deq_rec::xid_size() const
+{
+ return _deq_hdr._xidsize;
+}
+
+std::size_t
+deq_rec::rec_size() const
+{
+ return deq_hdr::size() + (_deq_hdr._xidsize ? _deq_hdr._xidsize + rec_tail::size() : 0);
+}
+
+void
+deq_rec::chk_hdr() const
+{
+ jrec::chk_hdr(_deq_hdr);
+ if (_deq_hdr._magic != RHM_JDAT_DEQ_MAGIC)
+ {
+ std::ostringstream oss;
+ oss << std::hex << std::setfill('0');
+ oss << "deq magic: rid=0x" << std::setw(16) << _deq_hdr._rid;
+ oss << ": expected=0x" << std::setw(8) << RHM_JDAT_DEQ_MAGIC;
+ oss << " read=0x" << std::setw(2) << (int)_deq_hdr._magic;
+ throw jexception(jerrno::JERR_JREC_BADRECHDR, oss.str(), "deq_rec", "chk_hdr");
+ }
+}
+
+void
+deq_rec::chk_hdr(u_int64_t rid) const
+{
+ chk_hdr();
+ jrec::chk_rid(_deq_hdr, rid);
+}
+
+void
+deq_rec::chk_tail() const
+{
+ jrec::chk_tail(_deq_tail, _deq_hdr);
+}
+
+void
+deq_rec::clean()
+{
+ // clean up allocated memory here
+}
+
+} // namespace journal
+} // namespace mrg
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/deq_rec.h b/qpid/cpp/src/qpid/legacystore/jrnl/deq_rec.h
new file mode 100644
index 0000000000..d870b658da
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/deq_rec.h
@@ -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.
+ *
+ */
+
+/**
+ * \file deq_rec.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * This file contains the code for the mrg::journal::deq_rec (journal dequeue
+ * record) class. See class documentation for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_DEQ_REQ_H
+#define QPID_LEGACYSTORE_JRNL_DEQ_REQ_H
+
+namespace mrg
+{
+namespace journal
+{
+class deq_rec;
+}
+}
+
+#include <cstddef>
+#include "qpid/legacystore/jrnl/deq_hdr.h"
+#include "qpid/legacystore/jrnl/jrec.h"
+
+namespace mrg
+{
+namespace journal
+{
+
+ /**
+ * \class deq_rec
+ * \brief Class to handle a single journal dequeue record.
+ */
+ class deq_rec : public jrec
+ {
+ private:
+ deq_hdr _deq_hdr; ///< Dequeue header
+ const void* _xidp; ///< xid pointer for encoding (writing to disk)
+ void* _buff; ///< Pointer to buffer to receive data read from disk
+ rec_tail _deq_tail; ///< Record tail, only encoded if XID is present
+
+ public:
+ // constructor used for read operations and xid will have memory allocated
+ deq_rec();
+ // constructor used for write operations, where xid already exists
+ deq_rec(const u_int64_t rid, const u_int64_t drid, const void* const xidp,
+ const std::size_t xidlen, const bool owi, const bool txn_coml_commit);
+ virtual ~deq_rec();
+
+ // Prepare instance for use in reading data from journal
+ void reset();
+ // Prepare instance for use in writing data to journal
+ void reset(const u_int64_t rid, const u_int64_t drid, const void* const xidp,
+ const std::size_t xidlen, const bool owi, const bool txn_coml_commit);
+ u_int32_t encode(void* wptr, u_int32_t rec_offs_dblks, u_int32_t max_size_dblks);
+ u_int32_t decode(rec_hdr& h, void* rptr, u_int32_t rec_offs_dblks,
+ u_int32_t max_size_dblks);
+ // Decode used for recover
+ bool rcv_decode(rec_hdr h, std::ifstream* ifsp, std::size_t& rec_offs);
+
+ inline bool is_txn_coml_commit() const { return _deq_hdr.is_txn_coml_commit(); }
+ inline u_int64_t rid() const { return _deq_hdr._rid; }
+ inline u_int64_t deq_rid() const { return _deq_hdr._deq_rid; }
+ std::size_t get_xid(void** const xidpp);
+ std::string& str(std::string& str) const;
+ inline std::size_t data_size() const { return 0; } // This record never carries data
+ std::size_t xid_size() const;
+ std::size_t rec_size() const;
+
+ private:
+ virtual void chk_hdr() const;
+ virtual void chk_hdr(u_int64_t rid) const;
+ virtual void chk_tail() const;
+ virtual void clean();
+ }; // class deq_rec
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_DEQ_REQ_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/enq_hdr.h b/qpid/cpp/src/qpid/legacystore/jrnl/enq_hdr.h
new file mode 100644
index 0000000000..0d1e6116be
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/enq_hdr.h
@@ -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.
+ *
+ */
+
+/**
+ * \file enq_hdr.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::enq_hdr (enueue header),
+ * used to start an enqueue record in the journal.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_ENQ_HDR_H
+#define QPID_LEGACYSTORE_JRNL_ENQ_HDR_H
+
+#include <cstddef>
+#include "qpid/legacystore/jrnl/rec_hdr.h"
+
+namespace mrg
+{
+namespace journal
+{
+
+#pragma pack(1)
+
+ /**
+ * \brief Struct for enqueue record.
+ *
+ * Struct for enqueue record. In addition to the common data, this header includes both the
+ * xid and data blob sizes.
+ *
+ * This header precedes all enqueue data in journal files.
+ *
+ * Record header info in binary format (32 bytes):
+ * <pre>
+ * 0 7
+ * +---+---+---+---+---+---+---+---+ -+
+ * | magic | v | e | flags | |
+ * +---+---+---+---+---+---+---+---+ | struct hdr
+ * | rid | |
+ * +---+---+---+---+---+---+---+---+ -+
+ * | xidsize |
+ * +---+---+---+---+---+---+---+---+
+ * | dsize |
+ * +---+---+---+---+---+---+---+---+
+ * v = file version (If the format or encoding of this file changes, then this
+ * number should be incremented)
+ * e = endian flag, false (0x00) for little endian, true (0x01) for big endian
+ * </pre>
+ *
+ * Note that journal files should be transferable between 32- and 64-bit
+ * hardware of the same endianness, but not between hardware of opposite
+ * entianness without some sort of binary conversion utility. Thus buffering
+ * will be needed for types that change size between 32- and 64-bit compiles.
+ */
+ struct enq_hdr : rec_hdr
+ {
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ u_int32_t _filler0; ///< Big-endian filler for 32-bit size_t
+#endif
+ std::size_t _xidsize; ///< XID size
+#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT)
+ u_int32_t _filler0; ///< Little-endian filler for 32-bit size_t
+#endif
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ u_int32_t _filler1; ///< Big-endian filler for 32-bit size_t
+#endif
+ std::size_t _dsize; ///< Record data size
+#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT)
+ u_int32_t _filler1; ///< Little-endian filler for 32-bit size_t
+#endif
+ static const u_int16_t ENQ_HDR_TRANSIENT_MASK = 0x10;
+ static const u_int16_t ENQ_HDR_EXTERNAL_MASK = 0x20;
+
+ /**
+ * \brief Default constructor, which sets all values to 0.
+ */
+ inline enq_hdr(): rec_hdr(),
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ _filler0(0),
+#endif
+ _xidsize(0),
+#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT)
+ _filler0(0),
+#endif
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ _filler1(0),
+#endif
+ _dsize(0)
+#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT)
+ , _filler1(0)
+#endif
+ {}
+
+ /**
+ * \brief Convenience constructor which initializes values during construction.
+ */
+ inline enq_hdr(const u_int32_t magic, const u_int8_t version, const u_int64_t rid,
+ const std::size_t xidsize, const std::size_t dsize, const bool owi,
+ const bool transient = false): rec_hdr(magic, version, rid, owi),
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ _filler0(0),
+#endif
+ _xidsize(xidsize),
+#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT)
+ _filler0(0),
+#endif
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ _filler1(0),
+#endif
+ _dsize(dsize)
+#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT)
+ , _filler1(0)
+#endif
+ { set_transient(transient); }
+
+
+ inline bool is_transient() const { return _uflag & ENQ_HDR_TRANSIENT_MASK; }
+
+ inline void set_transient(const bool transient)
+ {
+ _uflag = transient ? _uflag | ENQ_HDR_TRANSIENT_MASK :
+ _uflag & (~ENQ_HDR_TRANSIENT_MASK);
+ }
+
+ inline bool is_external() const { return _uflag & ENQ_HDR_EXTERNAL_MASK; }
+
+ inline void set_external(const bool external)
+ {
+ _uflag = external ? _uflag | ENQ_HDR_EXTERNAL_MASK :
+ _uflag & (~ENQ_HDR_EXTERNAL_MASK);
+ }
+
+ /**
+ * \brief Returns the size of the header in bytes.
+ */
+ inline static std::size_t size() { return sizeof(enq_hdr); }
+ };
+
+#pragma pack()
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_ENQ_HDR_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/enq_map.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/enq_map.cpp
new file mode 100644
index 0000000000..d024b704a7
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/enq_map.cpp
@@ -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.
+ *
+ */
+
+/**
+ * \file enq_map.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::enq_map (enqueue map). See
+ * comments in file enq_map.h for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#include "qpid/legacystore/jrnl/enq_map.h"
+
+#include <iomanip>
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include "qpid/legacystore/jrnl/slock.h"
+#include <sstream>
+
+
+namespace mrg
+{
+namespace journal
+{
+
+// static return/error codes
+int16_t enq_map::EMAP_DUP_RID = -3;
+int16_t enq_map::EMAP_LOCKED = -2;
+int16_t enq_map::EMAP_RID_NOT_FOUND = -1;
+int16_t enq_map::EMAP_OK = 0;
+int16_t enq_map::EMAP_FALSE = 0;
+int16_t enq_map::EMAP_TRUE = 1;
+
+enq_map::enq_map():
+ _map(),
+ _pfid_enq_cnt()
+{}
+
+enq_map::~enq_map() {}
+
+void
+enq_map::set_num_jfiles(const u_int16_t num_jfiles)
+{
+ _pfid_enq_cnt.resize(num_jfiles, 0);
+}
+
+
+int16_t
+enq_map::insert_pfid(const u_int64_t rid, const u_int16_t pfid)
+{
+ return insert_pfid(rid, pfid, false);
+}
+
+int16_t
+enq_map::insert_pfid(const u_int64_t rid, const u_int16_t pfid, const bool locked)
+{
+ std::pair<emap_itr, bool> ret;
+ emap_data_struct rec(pfid, locked);
+ {
+ slock s(_mutex);
+ ret = _map.insert(emap_param(rid, rec));
+ }
+ if (ret.second == false)
+ return EMAP_DUP_RID;
+ _pfid_enq_cnt.at(pfid)++;
+ return EMAP_OK;
+}
+
+int16_t
+enq_map::get_pfid(const u_int64_t rid)
+{
+ slock s(_mutex);
+ emap_itr itr = _map.find(rid);
+ if (itr == _map.end()) // not found in map
+ return EMAP_RID_NOT_FOUND;
+ if (itr->second._lock)
+ return EMAP_LOCKED;
+ return itr->second._pfid;
+}
+
+int16_t
+enq_map::get_remove_pfid(const u_int64_t rid, const bool txn_flag)
+{
+ slock s(_mutex);
+ emap_itr itr = _map.find(rid);
+ if (itr == _map.end()) // not found in map
+ return EMAP_RID_NOT_FOUND;
+ if (itr->second._lock && !txn_flag) // locked, but not a commit/abort
+ return EMAP_LOCKED;
+ u_int16_t pfid = itr->second._pfid;
+ _map.erase(itr);
+ _pfid_enq_cnt.at(pfid)--;
+ return pfid;
+}
+
+bool
+enq_map::is_enqueued(const u_int64_t rid, bool ignore_lock)
+{
+ slock s(_mutex);
+ emap_itr itr = _map.find(rid);
+ if (itr == _map.end()) // not found in map
+ return false;
+ if (!ignore_lock && itr->second._lock) // locked
+ return false;
+ return true;
+}
+
+int16_t
+enq_map::lock(const u_int64_t rid)
+{
+ slock s(_mutex);
+ emap_itr itr = _map.find(rid);
+ if (itr == _map.end()) // not found in map
+ return EMAP_RID_NOT_FOUND;
+ itr->second._lock = true;
+ return EMAP_OK;
+}
+
+int16_t
+enq_map::unlock(const u_int64_t rid)
+{
+ slock s(_mutex);
+ emap_itr itr = _map.find(rid);
+ if (itr == _map.end()) // not found in map
+ return EMAP_RID_NOT_FOUND;
+ itr->second._lock = false;
+ return EMAP_OK;
+}
+
+int16_t
+enq_map::is_locked(const u_int64_t rid)
+{
+ slock s(_mutex);
+ emap_itr itr = _map.find(rid);
+ if (itr == _map.end()) // not found in map
+ return EMAP_RID_NOT_FOUND;
+ return itr->second._lock ? EMAP_TRUE : EMAP_FALSE;
+}
+
+void
+enq_map::rid_list(std::vector<u_int64_t>& rv)
+{
+ rv.clear();
+ {
+ slock s(_mutex);
+ for (emap_itr itr = _map.begin(); itr != _map.end(); itr++)
+ rv.push_back(itr->first);
+ }
+}
+
+void
+enq_map::pfid_list(std::vector<u_int16_t>& fv)
+{
+ fv.clear();
+ {
+ slock s(_mutex);
+ for (emap_itr itr = _map.begin(); itr != _map.end(); itr++)
+ fv.push_back(itr->second._pfid);
+ }
+}
+
+} // namespace journal
+} // namespace mrg
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/enq_map.h b/qpid/cpp/src/qpid/legacystore/jrnl/enq_map.h
new file mode 100644
index 0000000000..75404afebe
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/enq_map.h
@@ -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.
+ *
+ */
+
+/**
+ * \file enq_map.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::enq_map (enqueue map).
+ * See class documentation for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_ENQ_MAP_H
+#define QPID_LEGACYSTORE_JRNL_ENQ_MAP_H
+
+namespace mrg
+{
+namespace journal
+{
+class enq_map;
+}
+}
+
+#include "qpid/legacystore/jrnl/jexception.h"
+#include "qpid/legacystore/jrnl/smutex.h"
+#include <map>
+#include <pthread.h>
+#include <vector>
+
+namespace mrg
+{
+namespace journal
+{
+
+ /**
+ * \class enq_map
+ * \brief Class for storing the physical file id (pfid) and a transaction locked flag for each enqueued
+ * data block using the record id (rid) as a key. This is the primary mechanism for
+ * deterimining the enqueue low water mark: if a pfid exists in this map, then there is
+ * at least one still-enqueued record in that file. (The transaction map must also be
+ * clear, however.)
+ *
+ * Map rids against pfid and lock status. As records are enqueued, they are added to this
+ * map, and as they are dequeued, they are removed. An enqueue is locked when a transactional
+ * dequeue is pending that has been neither committed nor aborted.
+ * <pre>
+ * key data
+ *
+ * rid1 --- [ pfid, txn_lock ]
+ * rid2 --- [ pfid, txn_lock ]
+ * rid3 --- [ pfid, txn_lock ]
+ * ...
+ * </pre>
+ */
+ class enq_map
+ {
+ public:
+ // return/error codes
+ static int16_t EMAP_DUP_RID;
+ static int16_t EMAP_LOCKED;
+ static int16_t EMAP_RID_NOT_FOUND;
+ static int16_t EMAP_OK;
+ static int16_t EMAP_FALSE;
+ static int16_t EMAP_TRUE;
+
+ private:
+
+ struct emap_data_struct
+ {
+ u_int16_t _pfid;
+ bool _lock;
+ emap_data_struct(const u_int16_t pfid, const bool lock) : _pfid(pfid), _lock(lock) {}
+ };
+ typedef std::pair<u_int64_t, emap_data_struct> emap_param;
+ typedef std::map<u_int64_t, emap_data_struct> emap;
+ typedef emap::iterator emap_itr;
+
+ emap _map;
+ smutex _mutex;
+ std::vector<u_int32_t> _pfid_enq_cnt;
+
+ public:
+ enq_map();
+ virtual ~enq_map();
+
+ void set_num_jfiles(const u_int16_t num_jfiles);
+ inline u_int32_t get_enq_cnt(const u_int16_t pfid) const { return _pfid_enq_cnt.at(pfid); };
+
+ int16_t insert_pfid(const u_int64_t rid, const u_int16_t pfid); // 0=ok; -3=duplicate rid;
+ int16_t insert_pfid(const u_int64_t rid, const u_int16_t pfid, const bool locked); // 0=ok; -3=duplicate rid;
+ int16_t get_pfid(const u_int64_t rid); // >=0=pfid; -1=rid not found; -2=locked
+ int16_t get_remove_pfid(const u_int64_t rid, const bool txn_flag = false); // >=0=pfid; -1=rid not found; -2=locked
+ bool is_enqueued(const u_int64_t rid, bool ignore_lock = false);
+ int16_t lock(const u_int64_t rid); // 0=ok; -1=rid not found
+ int16_t unlock(const u_int64_t rid); // 0=ok; -1=rid not found
+ int16_t is_locked(const u_int64_t rid); // 1=true; 0=false; -1=rid not found
+ inline void clear() { _map.clear(); }
+ inline bool empty() const { return _map.empty(); }
+ inline u_int32_t size() const { return u_int32_t(_map.size()); }
+ void rid_list(std::vector<u_int64_t>& rv);
+ void pfid_list(std::vector<u_int16_t>& fv);
+ };
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_ENQ_MAP_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/enq_rec.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/enq_rec.cpp
new file mode 100644
index 0000000000..94f42066a0
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/enq_rec.cpp
@@ -0,0 +1,640 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file enq_rec.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * This file contains the code for the mrg::journal::enq_rec (journal enqueue
+ * record) class. See comments in file enq_rec.h for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#include "qpid/legacystore/jrnl/enq_rec.h"
+
+#include <cassert>
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <iomanip>
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include "qpid/legacystore/jrnl/jexception.h"
+#include <sstream>
+
+namespace mrg
+{
+namespace journal
+{
+
+// Constructor used for read operations, where buf contains preallocated space to receive data.
+enq_rec::enq_rec():
+ jrec(), // superclass
+ _enq_hdr(RHM_JDAT_ENQ_MAGIC, RHM_JDAT_VERSION, 0, 0, 0, false, false),
+ _xidp(0),
+ _data(0),
+ _buff(0),
+ _enq_tail(_enq_hdr)
+{}
+
+// Constructor used for transactional write operations, where dbuf contains data to be written.
+enq_rec::enq_rec(const u_int64_t rid, const void* const dbuf, const std::size_t dlen,
+ const void* const xidp, const std::size_t xidlen, const bool owi, const bool transient):
+ jrec(), // superclass
+ _enq_hdr(RHM_JDAT_ENQ_MAGIC, RHM_JDAT_VERSION, rid, xidlen, dlen, owi, transient),
+ _xidp(xidp),
+ _data(dbuf),
+ _buff(0),
+ _enq_tail(_enq_hdr)
+{}
+
+enq_rec::~enq_rec()
+{
+ clean();
+}
+
+// Prepare instance for use in reading data from journal, where buf contains preallocated space
+// to receive data.
+void
+enq_rec::reset()
+{
+ _enq_hdr._rid = 0;
+ _enq_hdr.set_owi(false);
+ _enq_hdr.set_transient(false);
+ _enq_hdr._xidsize = 0;
+ _enq_hdr._dsize = 0;
+ _xidp = 0;
+ _data = 0;
+ _buff = 0;
+ _enq_tail._rid = 0;
+}
+
+// Prepare instance for use in writing transactional data to journal, where dbuf contains data to
+// be written.
+void
+enq_rec::reset(const u_int64_t rid, const void* const dbuf, const std::size_t dlen,
+ const void* const xidp, const std::size_t xidlen, const bool owi, const bool transient,
+ const bool external)
+{
+ _enq_hdr._rid = rid;
+ _enq_hdr.set_owi(owi);
+ _enq_hdr.set_transient(transient);
+ _enq_hdr.set_external(external);
+ _enq_hdr._xidsize = xidlen;
+ _enq_hdr._dsize = dlen;
+ _xidp = xidp;
+ _data = dbuf;
+ _buff = 0;
+ _enq_tail._rid = rid;
+}
+
+u_int32_t
+enq_rec::encode(void* wptr, u_int32_t rec_offs_dblks, u_int32_t max_size_dblks)
+{
+ assert(wptr != 0);
+ assert(max_size_dblks > 0);
+ if (_xidp == 0)
+ assert(_enq_hdr._xidsize == 0);
+
+ std::size_t rec_offs = rec_offs_dblks * JRNL_DBLK_SIZE;
+ std::size_t rem = max_size_dblks * JRNL_DBLK_SIZE;
+ std::size_t wr_cnt = 0;
+ if (rec_offs_dblks) // Continuation of split data record (over 2 or more pages)
+ {
+ if (size_dblks(rec_size()) - rec_offs_dblks > max_size_dblks) // Further split required
+ {
+ rec_offs -= sizeof(_enq_hdr);
+ std::size_t wsize = _enq_hdr._xidsize > rec_offs ? _enq_hdr._xidsize - rec_offs : 0;
+ std::size_t wsize2 = wsize;
+ if (wsize)
+ {
+ if (wsize > rem)
+ wsize = rem;
+ std::memcpy(wptr, (const char*)_xidp + rec_offs, wsize);
+ wr_cnt = wsize;
+ rem -= wsize;
+ }
+ rec_offs -= _enq_hdr._xidsize - wsize2;
+ if (rem && !_enq_hdr.is_external())
+ {
+ wsize = _enq_hdr._dsize > rec_offs ? _enq_hdr._dsize - rec_offs : 0;
+ wsize2 = wsize;
+ if (wsize)
+ {
+ if (wsize > rem)
+ wsize = rem;
+ std::memcpy((char*)wptr + wr_cnt, (const char*)_data + rec_offs, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ rec_offs -= _enq_hdr._dsize - wsize2;
+ }
+ if (rem)
+ {
+ wsize = sizeof(_enq_tail) > rec_offs ? sizeof(_enq_tail) - rec_offs : 0;
+ wsize2 = wsize;
+ if (wsize)
+ {
+ if (wsize > rem)
+ wsize = rem;
+ std::memcpy((char*)wptr + wr_cnt, (char*)&_enq_tail + rec_offs, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ rec_offs -= sizeof(_enq_tail) - wsize2;
+ }
+ assert(rem == 0);
+ assert(rec_offs == 0);
+ }
+ else // No further split required
+ {
+ rec_offs -= sizeof(_enq_hdr);
+ std::size_t wsize = _enq_hdr._xidsize > rec_offs ? _enq_hdr._xidsize - rec_offs : 0;
+ if (wsize)
+ {
+ std::memcpy(wptr, (const char*)_xidp + rec_offs, wsize);
+ wr_cnt += wsize;
+ }
+ rec_offs -= _enq_hdr._xidsize - wsize;
+ wsize = _enq_hdr._dsize > rec_offs ? _enq_hdr._dsize - rec_offs : 0;
+ if (wsize && !_enq_hdr.is_external())
+ {
+ std::memcpy((char*)wptr + wr_cnt, (const char*)_data + rec_offs, wsize);
+ wr_cnt += wsize;
+ }
+ rec_offs -= _enq_hdr._dsize - wsize;
+ wsize = sizeof(_enq_tail) > rec_offs ? sizeof(_enq_tail) - rec_offs : 0;
+ if (wsize)
+ {
+ std::memcpy((char*)wptr + wr_cnt, (char*)&_enq_tail + rec_offs, wsize);
+ wr_cnt += wsize;
+#ifdef RHM_CLEAN
+ std::size_t rec_offs = rec_offs_dblks * JRNL_DBLK_SIZE;
+ std::size_t dblk_rec_size = size_dblks(rec_size() - rec_offs) * JRNL_DBLK_SIZE;
+ std::memset((char*)wptr + wr_cnt, RHM_CLEAN_CHAR, dblk_rec_size - wr_cnt);
+#endif
+ }
+ rec_offs -= sizeof(_enq_tail) - wsize;
+ assert(rec_offs == 0);
+ }
+ }
+ else // Start at beginning of data record
+ {
+ // Assumption: the header will always fit into the first dblk
+ std::memcpy(wptr, (void*)&_enq_hdr, sizeof(_enq_hdr));
+ wr_cnt = sizeof(_enq_hdr);
+ if (size_dblks(rec_size()) > max_size_dblks) // Split required
+ {
+ std::size_t wsize;
+ rem -= sizeof(_enq_hdr);
+ if (rem)
+ {
+ wsize = rem >= _enq_hdr._xidsize ? _enq_hdr._xidsize : rem;
+ std::memcpy((char*)wptr + wr_cnt, _xidp, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ if (rem && !_enq_hdr.is_external())
+ {
+ wsize = rem >= _enq_hdr._dsize ? _enq_hdr._dsize : rem;
+ std::memcpy((char*)wptr + wr_cnt, _data, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ if (rem)
+ {
+ wsize = rem >= sizeof(_enq_tail) ? sizeof(_enq_tail) : rem;
+ std::memcpy((char*)wptr + wr_cnt, (void*)&_enq_tail, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ assert(rem == 0);
+ }
+ else // No split required
+ {
+ if (_enq_hdr._xidsize)
+ {
+ std::memcpy((char*)wptr + wr_cnt, _xidp, _enq_hdr._xidsize);
+ wr_cnt += _enq_hdr._xidsize;
+ }
+ if (!_enq_hdr.is_external())
+ {
+ std::memcpy((char*)wptr + wr_cnt, _data, _enq_hdr._dsize);
+ wr_cnt += _enq_hdr._dsize;
+ }
+ std::memcpy((char*)wptr + wr_cnt, (void*)&_enq_tail, sizeof(_enq_tail));
+ wr_cnt += sizeof(_enq_tail);
+#ifdef RHM_CLEAN
+ std::size_t dblk_rec_size = size_dblks(rec_size()) * JRNL_DBLK_SIZE;
+ std::memset((char*)wptr + wr_cnt, RHM_CLEAN_CHAR, dblk_rec_size - wr_cnt);
+#endif
+ }
+ }
+ return size_dblks(wr_cnt);
+}
+
+u_int32_t
+enq_rec::decode(rec_hdr& h, void* rptr, u_int32_t rec_offs_dblks, u_int32_t max_size_dblks)
+{
+ assert(rptr != 0);
+ assert(max_size_dblks > 0);
+
+ std::size_t rd_cnt = 0;
+ if (rec_offs_dblks) // Continuation of record on new page
+ {
+ const u_int32_t hdr_xid_data_size = enq_hdr::size() + _enq_hdr._xidsize +
+ (_enq_hdr.is_external() ? 0 : _enq_hdr._dsize);
+ const u_int32_t hdr_xid_data_tail_size = hdr_xid_data_size + rec_tail::size();
+ const u_int32_t hdr_data_dblks = size_dblks(hdr_xid_data_size);
+ const u_int32_t hdr_tail_dblks = size_dblks(hdr_xid_data_tail_size);
+ const std::size_t rec_offs = rec_offs_dblks * JRNL_DBLK_SIZE;
+ const std::size_t offs = rec_offs - enq_hdr::size();
+
+ if (hdr_tail_dblks - rec_offs_dblks <= max_size_dblks)
+ {
+ // Remainder of record fits within this page
+ if (offs < _enq_hdr._xidsize)
+ {
+ // some XID still outstanding, copy remainder of XID, data and tail
+ const std::size_t rem = _enq_hdr._xidsize + _enq_hdr._dsize - offs;
+ std::memcpy((char*)_buff + offs, rptr, rem);
+ rd_cnt += rem;
+ std::memcpy((void*)&_enq_tail, ((char*)rptr + rd_cnt), sizeof(_enq_tail));
+ chk_tail();
+ rd_cnt += sizeof(_enq_tail);
+ }
+ else if (offs < _enq_hdr._xidsize + _enq_hdr._dsize && !_enq_hdr.is_external())
+ {
+ // some data still outstanding, copy remainder of data and tail
+ const std::size_t data_offs = offs - _enq_hdr._xidsize;
+ const std::size_t data_rem = _enq_hdr._dsize - data_offs;
+ std::memcpy((char*)_buff + offs, rptr, data_rem);
+ rd_cnt += data_rem;
+ std::memcpy((void*)&_enq_tail, ((char*)rptr + rd_cnt), sizeof(_enq_tail));
+ chk_tail();
+ rd_cnt += sizeof(_enq_tail);
+ }
+ else
+ {
+ // Tail or part of tail only outstanding, complete tail
+ const std::size_t tail_offs = rec_offs - enq_hdr::size() - _enq_hdr._xidsize -
+ _enq_hdr._dsize;
+ const std::size_t tail_rem = rec_tail::size() - tail_offs;
+ std::memcpy((char*)&_enq_tail + tail_offs, rptr, tail_rem);
+ chk_tail();
+ rd_cnt = tail_rem;
+ }
+ }
+ else if (hdr_data_dblks - rec_offs_dblks <= max_size_dblks)
+ {
+ // Remainder of xid & data fits within this page; tail split
+
+ /*
+ * TODO: This section needs revision. Since it is known that the end of the page falls within the
+ * tail record, it is only necessary to write from the current offset to the end of the page under
+ * all circumstances. The multiple if/else combinations may be eliminated, as well as one memcpy()
+ * operation.
+ *
+ * Also note that Coverity has detected a possible memory overwrite in this block. It occurs if
+ * both the following two if() stmsts (numbered) are false. With rd_cnt = 0, this would result in
+ * the value of tail_rem > sizeof(tail_rec). Practically, this could only happen if the start and
+ * end of a page both fall within the same tail record, in which case the tail would have to be
+ * (much!) larger. However, the logic here does not account for this possibility.
+ *
+ * If the optimization above is undertaken, this code would probably be removed.
+ */
+ if (offs < _enq_hdr._xidsize) // 1
+ {
+ // some XID still outstanding, copy remainder of XID and data
+ const std::size_t rem = _enq_hdr._xidsize + _enq_hdr._dsize - offs;
+ std::memcpy((char*)_buff + offs, rptr, rem);
+ rd_cnt += rem;
+ }
+ else if (offs < _enq_hdr._xidsize + _enq_hdr._dsize && !_enq_hdr.is_external()) // 2
+ {
+ // some data still outstanding, copy remainder of data
+ const std::size_t data_offs = offs - _enq_hdr._xidsize;
+ const std::size_t data_rem = _enq_hdr._dsize - data_offs;
+ std::memcpy((char*)_buff + offs, rptr, data_rem);
+ rd_cnt += data_rem;
+ }
+ const std::size_t tail_rem = (max_size_dblks * JRNL_DBLK_SIZE) - rd_cnt;
+ if (tail_rem)
+ {
+ std::memcpy((void*)&_enq_tail, ((char*)rptr + rd_cnt), tail_rem);
+ rd_cnt += tail_rem;
+ }
+ }
+ else
+ {
+ // Since xid and data are contiguous, both fit within current page - copy whole page
+ const std::size_t data_cp_size = (max_size_dblks * JRNL_DBLK_SIZE);
+ std::memcpy((char*)_buff + offs, rptr, data_cp_size);
+ rd_cnt += data_cp_size;
+ }
+ }
+ else // Start of record
+ {
+ // Get and check header
+ _enq_hdr.hdr_copy(h);
+ rd_cnt = sizeof(rec_hdr);
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ rd_cnt += sizeof(u_int32_t); // Filler 0
+#endif
+ //_enq_hdr._xidsize = *(std::size_t*)((char*)rptr + rd_cnt);
+ std::memcpy((void*)&_enq_hdr._xidsize, (char*)rptr + rd_cnt, sizeof(std::size_t));
+ rd_cnt += sizeof(std::size_t);
+#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT)
+ rd_cnt += sizeof(u_int32_t); // Filler 0
+#endif
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ rd_cnt += sizeof(u_int32_t); // Filler 1
+#endif
+ //_enq_hdr._dsize = *(std::size_t*)((char*)rptr + rd_cnt);
+ std::memcpy((void*)&_enq_hdr._dsize, (char*)rptr + rd_cnt, sizeof(std::size_t));
+ rd_cnt = _enq_hdr.size();
+ chk_hdr();
+ if (_enq_hdr._xidsize + (_enq_hdr.is_external() ? 0 : _enq_hdr._dsize))
+ {
+ _buff = std::malloc(_enq_hdr._xidsize + (_enq_hdr.is_external() ? 0 : _enq_hdr._dsize));
+ MALLOC_CHK(_buff, "_buff", "enq_rec", "decode");
+
+ const u_int32_t hdr_xid_size = enq_hdr::size() + _enq_hdr._xidsize;
+ const u_int32_t hdr_xid_data_size = hdr_xid_size + (_enq_hdr.is_external() ? 0 : _enq_hdr._dsize);
+ const u_int32_t hdr_xid_data_tail_size = hdr_xid_data_size + rec_tail::size();
+ const u_int32_t hdr_xid_dblks = size_dblks(hdr_xid_size);
+ const u_int32_t hdr_data_dblks = size_dblks(hdr_xid_data_size);
+ const u_int32_t hdr_tail_dblks = size_dblks(hdr_xid_data_tail_size);
+ // Check if record (header + data + tail) fits within this page, we can check the
+ // tail before the expense of copying data to memory
+ if (hdr_tail_dblks <= max_size_dblks)
+ {
+ // Header, xid, data and tail fits within this page
+ if (_enq_hdr._xidsize)
+ {
+ std::memcpy(_buff, (char*)rptr + rd_cnt, _enq_hdr._xidsize);
+ rd_cnt += _enq_hdr._xidsize;
+ }
+ if (_enq_hdr._dsize && !_enq_hdr.is_external())
+ {
+ std::memcpy((char*)_buff + _enq_hdr._xidsize, (char*)rptr + rd_cnt,
+ _enq_hdr._dsize);
+ rd_cnt += _enq_hdr._dsize;
+ }
+ std::memcpy((void*)&_enq_tail, (char*)rptr + rd_cnt, sizeof(_enq_tail));
+ chk_tail();
+ rd_cnt += sizeof(_enq_tail);
+ }
+ else if (hdr_data_dblks <= max_size_dblks)
+ {
+ // Header, xid and data fit within this page, tail split or separated
+ if (_enq_hdr._xidsize)
+ {
+ std::memcpy(_buff, (char*)rptr + rd_cnt, _enq_hdr._xidsize);
+ rd_cnt += _enq_hdr._xidsize;
+ }
+ if (_enq_hdr._dsize && !_enq_hdr.is_external())
+ {
+ std::memcpy((char*)_buff + _enq_hdr._xidsize, (char*)rptr + rd_cnt,
+ _enq_hdr._dsize);
+ rd_cnt += _enq_hdr._dsize;
+ }
+ const std::size_t tail_rem = (max_size_dblks * JRNL_DBLK_SIZE) - rd_cnt;
+ if (tail_rem)
+ {
+ std::memcpy((void*)&_enq_tail, (char*)rptr + rd_cnt, tail_rem);
+ rd_cnt += tail_rem;
+ }
+ }
+ else if (hdr_xid_dblks <= max_size_dblks)
+ {
+ // Header and xid fits within this page, data split or separated
+ if (_enq_hdr._xidsize)
+ {
+ std::memcpy(_buff, (char*)rptr + rd_cnt, _enq_hdr._xidsize);
+ rd_cnt += _enq_hdr._xidsize;
+ }
+ if (_enq_hdr._dsize && !_enq_hdr.is_external())
+ {
+ const std::size_t data_cp_size = (max_size_dblks * JRNL_DBLK_SIZE) - rd_cnt;
+ std::memcpy((char*)_buff + _enq_hdr._xidsize, (char*)rptr + rd_cnt, data_cp_size);
+ rd_cnt += data_cp_size;
+ }
+ }
+ else
+ {
+ // Header fits within this page, xid split or separated
+ const std::size_t data_cp_size = (max_size_dblks * JRNL_DBLK_SIZE) - rd_cnt;
+ std::memcpy(_buff, (char*)rptr + rd_cnt, data_cp_size);
+ rd_cnt += data_cp_size;
+ }
+ }
+ }
+ return size_dblks(rd_cnt);
+}
+
+bool
+enq_rec::rcv_decode(rec_hdr h, std::ifstream* ifsp, std::size_t& rec_offs)
+{
+ if (rec_offs == 0)
+ {
+ // Read header, allocate (if req'd) for xid
+ _enq_hdr.hdr_copy(h);
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ ifsp->ignore(sizeof(u_int32_t)); // _filler0
+#endif
+ ifsp->read((char*)&_enq_hdr._xidsize, sizeof(std::size_t));
+#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT)
+ ifsp->ignore(sizeof(u_int32_t)); // _filler0
+#endif
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ ifsp->ignore(sizeof(u_int32_t)); // _filler1
+#endif
+ ifsp->read((char*)&_enq_hdr._dsize, sizeof(std::size_t));
+#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT)
+ ifsp->ignore(sizeof(u_int32_t)); // _filler1
+#endif
+ rec_offs = sizeof(_enq_hdr);
+ if (_enq_hdr._xidsize)
+ {
+ _buff = std::malloc(_enq_hdr._xidsize);
+ MALLOC_CHK(_buff, "_buff", "enq_rec", "rcv_decode");
+ }
+ }
+ if (rec_offs < sizeof(_enq_hdr) + _enq_hdr._xidsize)
+ {
+ // Read xid (or continue reading xid)
+ std::size_t offs = rec_offs - sizeof(_enq_hdr);
+ ifsp->read((char*)_buff + offs, _enq_hdr._xidsize - offs);
+ std::size_t size_read = ifsp->gcount();
+ rec_offs += size_read;
+ if (size_read < _enq_hdr._xidsize - offs)
+ {
+ assert(ifsp->eof());
+ // As we may have read past eof, turn off fail bit
+ ifsp->clear(ifsp->rdstate()&(~std::ifstream::failbit));
+ assert(!ifsp->fail() && !ifsp->bad());
+ return false;
+ }
+ }
+ if (!_enq_hdr.is_external())
+ {
+ if (rec_offs < sizeof(_enq_hdr) + _enq_hdr._xidsize + _enq_hdr._dsize)
+ {
+ // Ignore data (or continue ignoring data)
+ std::size_t offs = rec_offs - sizeof(_enq_hdr) - _enq_hdr._xidsize;
+ ifsp->ignore(_enq_hdr._dsize - offs);
+ std::size_t size_read = ifsp->gcount();
+ rec_offs += size_read;
+ if (size_read < _enq_hdr._dsize - offs)
+ {
+ assert(ifsp->eof());
+ // As we may have read past eof, turn off fail bit
+ ifsp->clear(ifsp->rdstate()&(~std::ifstream::failbit));
+ assert(!ifsp->fail() && !ifsp->bad());
+ return false;
+ }
+ }
+ }
+ if (rec_offs < sizeof(_enq_hdr) + _enq_hdr._xidsize +
+ (_enq_hdr.is_external() ? 0 : _enq_hdr._dsize) + sizeof(rec_tail))
+ {
+ // Read tail (or continue reading tail)
+ std::size_t offs = rec_offs - sizeof(_enq_hdr) - _enq_hdr._xidsize;
+ if (!_enq_hdr.is_external())
+ offs -= _enq_hdr._dsize;
+ ifsp->read((char*)&_enq_tail + offs, sizeof(rec_tail) - offs);
+ std::size_t size_read = ifsp->gcount();
+ rec_offs += size_read;
+ if (size_read < sizeof(rec_tail) - offs)
+ {
+ assert(ifsp->eof());
+ // As we may have read past eof, turn off fail bit
+ ifsp->clear(ifsp->rdstate()&(~std::ifstream::failbit));
+ assert(!ifsp->fail() && !ifsp->bad());
+ return false;
+ }
+ }
+ ifsp->ignore(rec_size_dblks() * JRNL_DBLK_SIZE - rec_size());
+ chk_tail(); // Throws if tail invalid or record incomplete
+ assert(!ifsp->fail() && !ifsp->bad());
+ return true;
+}
+
+std::size_t
+enq_rec::get_xid(void** const xidpp)
+{
+ if (!_buff || !_enq_hdr._xidsize)
+ {
+ *xidpp = 0;
+ return 0;
+ }
+ *xidpp = _buff;
+ return _enq_hdr._xidsize;
+}
+
+std::size_t
+enq_rec::get_data(void** const datapp)
+{
+ if (!_buff)
+ {
+ *datapp = 0;
+ return 0;
+ }
+ if (_enq_hdr.is_external())
+ *datapp = 0;
+ else
+ *datapp = (void*)((char*)_buff + _enq_hdr._xidsize);
+ return _enq_hdr._dsize;
+}
+
+std::string&
+enq_rec::str(std::string& str) const
+{
+ std::ostringstream oss;
+ oss << "enq_rec: m=" << _enq_hdr._magic;
+ oss << " v=" << (int)_enq_hdr._version;
+ oss << " rid=" << _enq_hdr._rid;
+ if (_xidp)
+ oss << " xid=\"" << _xidp << "\"";
+ oss << " len=" << _enq_hdr._dsize;
+ str.append(oss.str());
+ return str;
+}
+
+std::size_t
+enq_rec::rec_size() const
+{
+ return rec_size(_enq_hdr._xidsize, _enq_hdr._dsize, _enq_hdr.is_external());
+}
+
+std::size_t
+enq_rec::rec_size(const std::size_t xidsize, const std::size_t dsize, const bool external)
+{
+ if (external)
+ return enq_hdr::size() + xidsize + rec_tail::size();
+ return enq_hdr::size() + xidsize + dsize + rec_tail::size();
+}
+
+void
+enq_rec::set_rid(const u_int64_t rid)
+{
+ _enq_hdr._rid = rid;
+ _enq_tail._rid = rid;
+}
+
+void
+enq_rec::chk_hdr() const
+{
+ jrec::chk_hdr(_enq_hdr);
+ if (_enq_hdr._magic != RHM_JDAT_ENQ_MAGIC)
+ {
+ std::ostringstream oss;
+ oss << std::hex << std::setfill('0');
+ oss << "enq magic: rid=0x" << std::setw(16) << _enq_hdr._rid;
+ oss << ": expected=0x" << std::setw(8) << RHM_JDAT_ENQ_MAGIC;
+ oss << " read=0x" << std::setw(2) << (int)_enq_hdr._magic;
+ throw jexception(jerrno::JERR_JREC_BADRECHDR, oss.str(), "enq_rec", "chk_hdr");
+ }
+}
+
+void
+enq_rec::chk_hdr(u_int64_t rid) const
+{
+ chk_hdr();
+ jrec::chk_rid(_enq_hdr, rid);
+}
+
+void
+enq_rec::chk_tail() const
+{
+ jrec::chk_tail(_enq_tail, _enq_hdr);
+}
+
+void
+enq_rec::clean()
+{
+ // clean up allocated memory here
+}
+
+} // namespace journal
+} // namespace mrg
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/enq_rec.h b/qpid/cpp/src/qpid/legacystore/jrnl/enq_rec.h
new file mode 100644
index 0000000000..805a96a1aa
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/enq_rec.h
@@ -0,0 +1,116 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file enq_rec.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * This file contains the code for the mrg::journal::enq_rec (journal enqueue
+ * record) class. See class documentation for details.
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_ENQ_REC_H
+#define QPID_LEGACYSTORE_JRNL_ENQ_REC_H
+
+namespace mrg
+{
+namespace journal
+{
+class enq_rec;
+}
+}
+
+#include <cstddef>
+#include "qpid/legacystore/jrnl/enq_hdr.h"
+#include "qpid/legacystore/jrnl/jrec.h"
+
+namespace mrg
+{
+namespace journal
+{
+
+ /**
+ * \class enq_rec
+ * \brief Class to handle a single journal enqueue record.
+ */
+ class enq_rec : public jrec
+ {
+ private:
+ enq_hdr _enq_hdr;
+ const void* _xidp; ///< xid pointer for encoding (for writing to disk)
+ const void* _data; ///< Pointer to data to be written to disk
+ void* _buff; ///< Pointer to buffer to receive data read from disk
+ rec_tail _enq_tail;
+
+ public:
+ /**
+ * \brief Constructor used for read operations.
+ */
+ enq_rec();
+
+ /**
+ * \brief Constructor used for write operations, where mbuf contains data to be written.
+ */
+ enq_rec(const u_int64_t rid, const void* const dbuf, const std::size_t dlen,
+ const void* const xidp, const std::size_t xidlen, const bool owi, const bool transient);
+
+ /**
+ * \brief Destructor
+ */
+ virtual ~enq_rec();
+
+ // Prepare instance for use in reading data from journal, xid and data will be allocated
+ void reset();
+ // Prepare instance for use in writing data to journal
+ void reset(const u_int64_t rid, const void* const dbuf, const std::size_t dlen,
+ const void* const xidp, const std::size_t xidlen, const bool owi, const bool transient,
+ const bool external);
+
+ u_int32_t encode(void* wptr, u_int32_t rec_offs_dblks, u_int32_t max_size_dblks);
+ u_int32_t decode(rec_hdr& h, void* rptr, u_int32_t rec_offs_dblks,
+ u_int32_t max_size_dblks);
+ // Decode used for recover
+ bool rcv_decode(rec_hdr h, std::ifstream* ifsp, std::size_t& rec_offs);
+
+ std::size_t get_xid(void** const xidpp);
+ std::size_t get_data(void** const datapp);
+ inline bool is_transient() const { return _enq_hdr.is_transient(); }
+ inline bool is_external() const { return _enq_hdr.is_external(); }
+ std::string& str(std::string& str) const;
+ inline std::size_t data_size() const { return _enq_hdr._dsize; }
+ inline std::size_t xid_size() const { return _enq_hdr._xidsize; }
+ std::size_t rec_size() const;
+ static std::size_t rec_size(const std::size_t xidsize, const std::size_t dsize, const bool external);
+ inline u_int64_t rid() const { return _enq_hdr._rid; }
+ void set_rid(const u_int64_t rid);
+
+ private:
+ void chk_hdr() const;
+ void chk_hdr(u_int64_t rid) const;
+ void chk_tail() const;
+ virtual void clean();
+ }; // class enq_rec
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_ENQ_REC_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/enums.h b/qpid/cpp/src/qpid/legacystore/jrnl/enums.h
new file mode 100644
index 0000000000..169a13fa4d
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/enums.h
@@ -0,0 +1,108 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file enums.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing definitions for namespace mrg::journal enums.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_ENUMS_H
+#define QPID_LEGACYSTORE_JRNL_ENUMS_H
+
+namespace mrg
+{
+namespace journal
+{
+
+ // TODO: Change this to flags, as multiple of these conditions may exist simultaneously
+ /**
+ * \brief Enumeration of possilbe return states from journal read and write operations.
+ */
+ enum _iores
+ {
+ RHM_IORES_SUCCESS = 0, ///< Success: IO operation completed noramlly.
+ RHM_IORES_PAGE_AIOWAIT, ///< IO operation suspended - next page is waiting for AIO.
+ RHM_IORES_FILE_AIOWAIT, ///< IO operation suspended - next file is waiting for AIO.
+ RHM_IORES_EMPTY, ///< During read operations, nothing further is available to read.
+ RHM_IORES_RCINVALID, ///< Read page cache is invalid (ie obsolete or uninitialized)
+ RHM_IORES_ENQCAPTHRESH, ///< Enqueue capacity threshold (limit) reached.
+ RHM_IORES_FULL, ///< During write operations, the journal files are full.
+ RHM_IORES_BUSY, ///< Another blocking operation is in progress.
+ RHM_IORES_TXPENDING, ///< Operation blocked by pending transaction.
+ RHM_IORES_NOTIMPL ///< Function is not yet implemented.
+ };
+ typedef _iores iores;
+
+ static inline const char* iores_str(iores res)
+ {
+ switch (res)
+ {
+ case RHM_IORES_SUCCESS: return "RHM_IORES_SUCCESS";
+ case RHM_IORES_PAGE_AIOWAIT: return "RHM_IORES_PAGE_AIOWAIT";
+ case RHM_IORES_FILE_AIOWAIT: return "RHM_IORES_FILE_AIOWAIT";
+ case RHM_IORES_EMPTY: return "RHM_IORES_EMPTY";
+ case RHM_IORES_RCINVALID: return "RHM_IORES_RCINVALID";
+ case RHM_IORES_ENQCAPTHRESH: return "RHM_IORES_ENQCAPTHRESH";
+ case RHM_IORES_FULL: return "RHM_IORES_FULL";
+ case RHM_IORES_BUSY: return "RHM_IORES_BUSY";
+ case RHM_IORES_TXPENDING: return "RHM_IORES_TXPENDING";
+ case RHM_IORES_NOTIMPL: return "RHM_IORES_NOTIMPL";
+ }
+ return "<iores unknown>";
+ }
+
+ enum _log_level
+ {
+ LOG_TRACE = 0,
+ LOG_DEBUG,
+ LOG_INFO,
+ LOG_NOTICE,
+ LOG_WARN,
+ LOG_ERROR,
+ LOG_CRITICAL
+ };
+ typedef _log_level log_level;
+
+ static inline const char* log_level_str(log_level ll)
+ {
+ switch (ll)
+ {
+ case LOG_TRACE: return "TRACE";
+ case LOG_DEBUG: return "DEBUG";
+ case LOG_INFO: return "INFO";
+ case LOG_NOTICE: return "NOTICE";
+ case LOG_WARN: return "WARN";
+ case LOG_ERROR: return "ERROR";
+ case LOG_CRITICAL: return "CRITICAL";
+ }
+ return "<log level unknown>";
+ }
+
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_ENUMS_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/fcntl.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/fcntl.cpp
new file mode 100644
index 0000000000..fbb176667e
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/fcntl.cpp
@@ -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.
+ *
+ */
+
+/**
+ * \file fcntl.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::fcntl (non-logging file
+ * handle), used for controlling journal log files. See comments in file
+ * fcntl.h for details.
+ */
+
+#include "qpid/legacystore/jrnl/fcntl.h"
+
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <fcntl.h>
+#include <iomanip>
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include "qpid/legacystore/jrnl/jexception.h"
+#include <sstream>
+#include <unistd.h>
+
+namespace mrg
+{
+namespace journal
+{
+
+fcntl::fcntl(const std::string& fbasename, const u_int16_t pfid, const u_int16_t lfid, const u_int32_t jfsize_sblks,
+ const rcvdat* const ro):
+ _fname(),
+ _pfid(pfid),
+ _lfid(lfid),
+ _ffull_dblks(JRNL_SBLK_SIZE * (jfsize_sblks + 1)),
+ _wr_fh(-1),
+ _rec_enqcnt(0),
+ _rd_subm_cnt_dblks(0),
+ _rd_cmpl_cnt_dblks(0),
+ _wr_subm_cnt_dblks(0),
+ _wr_cmpl_cnt_dblks(0),
+ _aio_cnt(0),
+ _fhdr_wr_aio_outstanding(false)
+{
+ initialize(fbasename, pfid, lfid, jfsize_sblks, ro);
+ open_wr_fh();
+}
+
+fcntl::~fcntl()
+{
+ close_wr_fh();
+}
+
+bool
+fcntl::reset(const rcvdat* const ro)
+{
+ rd_reset();
+ return wr_reset(ro);
+}
+
+void
+fcntl::rd_reset()
+{
+ _rd_subm_cnt_dblks = 0;
+ _rd_cmpl_cnt_dblks = 0;
+}
+
+bool
+fcntl::wr_reset(const rcvdat* const ro)
+{
+ if (ro)
+ {
+ if (!ro->_jempty)
+ {
+ if (ro->_lfid == _pfid)
+ {
+ _wr_subm_cnt_dblks = ro->_eo/JRNL_DBLK_SIZE;
+ _wr_cmpl_cnt_dblks = ro->_eo/JRNL_DBLK_SIZE;
+ }
+ else
+ {
+ _wr_subm_cnt_dblks = _ffull_dblks;
+ _wr_cmpl_cnt_dblks = _ffull_dblks;
+ }
+ _rec_enqcnt = ro->_enq_cnt_list[_pfid];
+ return true;
+ }
+ }
+ // Journal overflow test - checks if the file to be reset still contains enqueued records
+ // or outstanding aios
+ if (_rec_enqcnt || _aio_cnt)
+ return false;
+ _wr_subm_cnt_dblks = 0;
+ _wr_cmpl_cnt_dblks = 0;
+ return true;
+}
+
+int
+fcntl::open_wr_fh()
+{
+ if (_wr_fh < 0)
+ {
+ _wr_fh = ::open(_fname.c_str(), O_WRONLY | O_DIRECT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); // 0644 -rw-r--r--
+ if (_wr_fh < 0)
+ {
+ std::ostringstream oss;
+ oss << "pfid=" << _pfid << " lfid=" << _lfid << " file=\"" << _fname << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_FCNTL_OPENWR, oss.str(), "fcntl", "open_fh");
+ }
+ }
+ return _wr_fh;
+}
+
+void
+fcntl::close_wr_fh()
+{
+ if (_wr_fh >= 0)
+ {
+ ::close(_wr_fh);
+ _wr_fh = -1;
+ }
+}
+
+u_int32_t
+fcntl::add_enqcnt(u_int32_t a)
+{
+ _rec_enqcnt += a;
+ return _rec_enqcnt;
+}
+
+u_int32_t
+fcntl::decr_enqcnt()
+{
+ if (_rec_enqcnt == 0)
+ {
+ std::ostringstream oss;
+ oss << "pfid=" << _pfid << " lfid=" << _lfid;
+ throw jexception(jerrno::JERR__UNDERFLOW, oss.str(), "fcntl", "decr_enqcnt");
+ }
+ return --_rec_enqcnt;
+}
+
+u_int32_t
+fcntl::subtr_enqcnt(u_int32_t s)
+{
+ if (_rec_enqcnt < s)
+ {
+ std::ostringstream oss;
+ oss << "pfid=" << _pfid << " lfid=" << _lfid << " rec_enqcnt=" << _rec_enqcnt << " decr=" << s;
+ throw jexception(jerrno::JERR__UNDERFLOW, oss.str(), "fcntl", "subtr_enqcnt");
+ }
+ _rec_enqcnt -= s;
+ return _rec_enqcnt;
+}
+
+u_int32_t
+fcntl::add_rd_subm_cnt_dblks(u_int32_t a)
+{
+ if (_rd_subm_cnt_dblks + a > _wr_subm_cnt_dblks)
+ {
+ std::ostringstream oss;
+ oss << "pfid=" << _pfid << " lfid=" << _lfid << " rd_subm_cnt_dblks=" << _rd_subm_cnt_dblks << " incr=" << a;
+ oss << " wr_subm_cnt_dblks=" << _wr_subm_cnt_dblks;
+ throw jexception(jerrno::JERR_FCNTL_RDOFFSOVFL, oss.str(), "fcntl", "add_rd_subm_cnt_dblks");
+ }
+ _rd_subm_cnt_dblks += a;
+ return _rd_subm_cnt_dblks;
+}
+
+u_int32_t
+fcntl::add_rd_cmpl_cnt_dblks(u_int32_t a)
+{
+ if (_rd_cmpl_cnt_dblks + a > _rd_subm_cnt_dblks)
+ {
+ std::ostringstream oss;
+ oss << "pfid=" << _pfid << " lfid=" << _lfid << " rd_cmpl_cnt_dblks=" << _rd_cmpl_cnt_dblks << " incr=" << a;
+ oss << " rd_subm_cnt_dblks=" << _rd_subm_cnt_dblks;
+ throw jexception(jerrno::JERR_FCNTL_CMPLOFFSOVFL, oss.str(), "fcntl", "add_rd_cmpl_cnt_dblks");
+ }
+ _rd_cmpl_cnt_dblks += a;
+ return _rd_cmpl_cnt_dblks;
+}
+
+u_int32_t
+fcntl::add_wr_subm_cnt_dblks(u_int32_t a)
+{
+ if (_wr_subm_cnt_dblks + a > _ffull_dblks) // Allow for file header
+ {
+ std::ostringstream oss;
+ oss << "pfid=" << _pfid << " lfid=" << _lfid << " wr_subm_cnt_dblks=" << _wr_subm_cnt_dblks << " incr=" << a;
+ oss << " fsize=" << _ffull_dblks << " dblks";
+ throw jexception(jerrno::JERR_FCNTL_FILEOFFSOVFL, oss.str(), "fcntl", "add_wr_subm_cnt_dblks");
+ }
+ _wr_subm_cnt_dblks += a;
+ return _wr_subm_cnt_dblks;
+}
+
+u_int32_t
+fcntl::add_wr_cmpl_cnt_dblks(u_int32_t a)
+{
+ if (_wr_cmpl_cnt_dblks + a > _wr_subm_cnt_dblks)
+ {
+ std::ostringstream oss;
+ oss << "pfid=" << _pfid << " lfid=" << _lfid << " wr_cmpl_cnt_dblks=" << _wr_cmpl_cnt_dblks << " incr=" << a;
+ oss << " wr_subm_cnt_dblks=" << _wr_subm_cnt_dblks;
+ throw jexception(jerrno::JERR_FCNTL_CMPLOFFSOVFL, oss.str(), "fcntl", "add_wr_cmpl_cnt_dblks");
+ }
+ _wr_cmpl_cnt_dblks += a;
+ return _wr_cmpl_cnt_dblks;
+}
+
+u_int16_t
+fcntl::decr_aio_cnt()
+{
+ if(_aio_cnt == 0)
+ {
+ std::ostringstream oss;
+ oss << "pfid=" << _pfid << " lfid=" << _lfid << " Decremented aio_cnt to below zero";
+ throw jexception(jerrno::JERR__UNDERFLOW, oss.str(), "fcntl", "decr_aio_cnt");
+ }
+ return --_aio_cnt;
+}
+
+// Debug function
+const std::string
+fcntl::status_str() const
+{
+ std::ostringstream oss;
+ oss << "pfid=" << _pfid << " ws=" << _wr_subm_cnt_dblks << " wc=" << _wr_cmpl_cnt_dblks;
+ oss << " rs=" << _rd_subm_cnt_dblks << " rc=" << _rd_cmpl_cnt_dblks;
+ oss << " ec=" << _rec_enqcnt << " ac=" << _aio_cnt;
+ return oss.str();
+}
+
+// Protected functions
+
+void
+fcntl::initialize(const std::string& fbasename, const u_int16_t pfid, const u_int16_t lfid, const u_int32_t jfsize_sblks,
+ const rcvdat* const ro)
+{
+ _pfid = pfid;
+ _lfid = lfid;
+ _fname = filename(fbasename, pfid);
+
+#ifdef RHM_JOWRITE
+ // In test mode, only create file if it does not exist
+ struct stat s;
+ if (::stat(_fname.c_str(), &s))
+ {
+#endif
+ if (ro) // Recovery initialization: set counters only
+ {
+ if (!ro->_jempty)
+ {
+ // For last file only, set write counters to end of last record (the
+ // continuation point); for all others, set to eof.
+ if (ro->_lfid == _pfid)
+ {
+ _wr_subm_cnt_dblks = ro->_eo/JRNL_DBLK_SIZE;
+ _wr_cmpl_cnt_dblks = ro->_eo/JRNL_DBLK_SIZE;
+ }
+ else
+ {
+ _wr_subm_cnt_dblks = _ffull_dblks;
+ _wr_cmpl_cnt_dblks = _ffull_dblks;
+ }
+ // Set the number of enqueued records for this file.
+ _rec_enqcnt = ro->_enq_cnt_list[_pfid];
+ }
+ }
+ else // Normal initialization: create empty journal files
+ create_jfile(jfsize_sblks);
+#ifdef RHM_JOWRITE
+ }
+#endif
+}
+
+std::string
+fcntl::filename(const std::string& fbasename, const u_int16_t pfid)
+{
+ std::ostringstream oss;
+ oss << fbasename << ".";
+ oss << std::setw(4) << std::setfill('0') << std::hex << pfid;
+ oss << "." << JRNL_DATA_EXTENSION;
+ return oss.str();
+}
+
+void
+fcntl::clean_file(const u_int32_t jfsize_sblks)
+{
+ // NOTE: The journal file size is always one sblock bigger than the specified journal
+ // file size, which is the data content size. The extra block is for the journal file
+ // header which precedes all data on each file and is exactly one sblock in size.
+ u_int32_t nsblks = jfsize_sblks + 1;
+
+ // TODO - look at more efficient alternatives to allocating a null block:
+ // 1. mmap() against /dev/zero, but can alignment for O_DIRECT be assured?
+ // 2. ftruncate(), but does this result in a sparse file? If so, then this is no good.
+
+ // Create temp null block for writing
+ const std::size_t sblksize = JRNL_DBLK_SIZE * JRNL_SBLK_SIZE;
+ void* nullbuf = 0;
+ // Allocate no more than 2MB (4096 sblks) as a null buffer
+ const u_int32_t nullbuffsize_sblks = nsblks > 4096 ? 4096 : nsblks;
+ const std::size_t nullbuffsize = nullbuffsize_sblks * sblksize;
+ if (::posix_memalign(&nullbuf, sblksize, nullbuffsize))
+ {
+ std::ostringstream oss;
+ oss << "posix_memalign() failed: size=" << nullbuffsize << " blk_size=" << sblksize;
+ oss << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR__MALLOC, oss.str(), "fcntl", "clean_file");
+ }
+ std::memset(nullbuf, 0, nullbuffsize);
+
+ int fh = ::open(_fname.c_str(), O_WRONLY | O_CREAT | O_DIRECT,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); // 0644 -rw-r--r--
+ if (fh < 0)
+ {
+ std::free(nullbuf);
+ std::ostringstream oss;
+ oss << "open() failed:" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_FCNTL_OPENWR, oss.str(), "fcntl", "clean_file");
+ }
+
+ while (nsblks > 0)
+ {
+ u_int32_t this_write_sblks = nsblks >= nullbuffsize_sblks ? nullbuffsize_sblks : nsblks;
+ if (::write(fh, nullbuf, this_write_sblks * sblksize) == -1)
+ {
+ ::close(fh);
+ std::free(nullbuf);
+ std::ostringstream oss;
+ oss << "wr_size=" << (this_write_sblks * sblksize) << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_FCNTL_WRITE, oss.str(), "fcntl", "clean_file");
+ }
+ nsblks -= this_write_sblks;
+ }
+
+ // Clean up
+ std::free(nullbuf);
+ if (::close(fh))
+ {
+ std::ostringstream oss;
+ oss << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_FCNTL_CLOSE, oss.str(), "fcntl", "clean_file");
+ }
+}
+
+void
+fcntl::create_jfile(const u_int32_t jfsize_sblks)
+{
+ clean_file(jfsize_sblks);
+}
+
+} // namespace journal
+} // namespace mrg
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/fcntl.h b/qpid/cpp/src/qpid/legacystore/jrnl/fcntl.h
new file mode 100644
index 0000000000..a75e3bc84d
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/fcntl.h
@@ -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.
+ *
+ */
+
+/**
+ * \file fcntl.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::fcntl (non-logging file
+ * handle), used for controlling journal log files. See class documentation for
+ * details.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_FCNTL_H
+#define QPID_LEGACYSTORE_JRNL_FCNTL_H
+
+namespace mrg
+{
+namespace journal
+{
+class fcntl;
+}
+}
+
+#include <cstddef>
+#include <string>
+#include "qpid/legacystore/jrnl/rcvdat.h"
+#include <sys/types.h>
+
+namespace mrg
+{
+namespace journal
+{
+
+ /**
+ * \class fcntl
+ * \brief Journal file controller. There is one instance per journal file.
+ */
+ class fcntl
+ {
+ protected:
+ std::string _fname; ///< File name
+ u_int16_t _pfid; ///< Physical file ID (file number in order of creation)
+ u_int16_t _lfid; ///< Logical file ID (ordinal number in ring store)
+ const u_int32_t _ffull_dblks; ///< File size in dblks (incl. file header)
+ int _wr_fh; ///< Write file handle
+ u_int32_t _rec_enqcnt; ///< Count of enqueued records
+ u_int32_t _rd_subm_cnt_dblks; ///< Read file count (data blocks) for submitted AIO
+ u_int32_t _rd_cmpl_cnt_dblks; ///< Read file count (data blocks) for completed AIO
+ u_int32_t _wr_subm_cnt_dblks; ///< Write file count (data blocks) for submitted AIO
+ u_int32_t _wr_cmpl_cnt_dblks; ///< Write file count (data blocks) for completed AIO
+ u_int16_t _aio_cnt; ///< Outstanding AIO operations on this file
+ bool _fhdr_wr_aio_outstanding; ///< Outstanding file header write on this file
+
+ public:
+ // Constructors with implicit initialize() and open()
+ fcntl(const std::string& fbasename, const u_int16_t pfid, const u_int16_t lfid, const u_int32_t jfsize_sblks,
+ const rcvdat* const ro);
+ virtual ~fcntl();
+
+ virtual bool reset(const rcvdat* const ro = 0);
+ virtual void rd_reset();
+ virtual bool wr_reset(const rcvdat* const ro = 0);
+
+ virtual int open_wr_fh();
+ virtual void close_wr_fh();
+ inline bool is_wr_fh_open() const { return _wr_fh >= 0; }
+
+ inline const std::string& fname() const { return _fname; }
+ inline u_int16_t pfid() const { return _pfid; }
+ inline u_int16_t lfid() const { return _lfid; }
+ inline void set_lfid(const u_int16_t lfid) { _lfid = lfid; }
+ inline int wr_fh() const { return _wr_fh; }
+ inline u_int32_t enqcnt() const { return _rec_enqcnt; }
+ inline u_int32_t incr_enqcnt() { return ++_rec_enqcnt; }
+ u_int32_t add_enqcnt(u_int32_t a);
+ u_int32_t decr_enqcnt();
+ u_int32_t subtr_enqcnt(u_int32_t s);
+
+ inline u_int32_t rd_subm_cnt_dblks() const { return _rd_subm_cnt_dblks; }
+ inline std::size_t rd_subm_offs() const { return _rd_subm_cnt_dblks * JRNL_DBLK_SIZE; }
+ u_int32_t add_rd_subm_cnt_dblks(u_int32_t a);
+
+ inline u_int32_t rd_cmpl_cnt_dblks() const { return _rd_cmpl_cnt_dblks; }
+ inline std::size_t rd_cmpl_offs() const { return _rd_cmpl_cnt_dblks * JRNL_DBLK_SIZE; }
+ u_int32_t add_rd_cmpl_cnt_dblks(u_int32_t a);
+
+ inline u_int32_t wr_subm_cnt_dblks() const { return _wr_subm_cnt_dblks; }
+ inline std::size_t wr_subm_offs() const { return _wr_subm_cnt_dblks * JRNL_DBLK_SIZE; }
+ u_int32_t add_wr_subm_cnt_dblks(u_int32_t a);
+
+ inline u_int32_t wr_cmpl_cnt_dblks() const { return _wr_cmpl_cnt_dblks; }
+ inline std::size_t wr_cmpl_offs() const { return _wr_cmpl_cnt_dblks * JRNL_DBLK_SIZE; }
+ u_int32_t add_wr_cmpl_cnt_dblks(u_int32_t a);
+
+ inline u_int16_t aio_cnt() const { return _aio_cnt; }
+ inline u_int16_t incr_aio_cnt() { return ++_aio_cnt; }
+ u_int16_t decr_aio_cnt();
+
+ inline bool wr_fhdr_aio_outstanding() { return _fhdr_wr_aio_outstanding; }
+ inline void set_wr_fhdr_aio_outstanding(const bool wfao) { _fhdr_wr_aio_outstanding = wfao; }
+
+ // Derived helper functions
+
+ inline bool rd_void() const { return _wr_cmpl_cnt_dblks == 0; }
+ inline bool rd_empty() const { return _wr_cmpl_cnt_dblks <= JRNL_SBLK_SIZE; }
+ inline u_int32_t rd_remaining_dblks() const { return _wr_cmpl_cnt_dblks - _rd_subm_cnt_dblks; }
+ inline bool is_rd_full() const { return _wr_cmpl_cnt_dblks == _rd_subm_cnt_dblks; }
+ inline bool is_rd_compl() const { return _wr_cmpl_cnt_dblks == _rd_cmpl_cnt_dblks; }
+ inline u_int32_t rd_aio_outstanding_dblks() const { return _rd_subm_cnt_dblks - _rd_cmpl_cnt_dblks; }
+ inline bool rd_file_rotate() const { return is_rd_full() && is_wr_compl(); }
+
+ inline bool wr_void() const { return _wr_subm_cnt_dblks == 0; }
+ inline bool wr_empty() const { return _wr_subm_cnt_dblks <= JRNL_SBLK_SIZE; }
+ inline u_int32_t wr_remaining_dblks() const { return _ffull_dblks - _wr_subm_cnt_dblks; }
+ inline bool is_wr_full() const { return _ffull_dblks == _wr_subm_cnt_dblks; }
+ inline bool is_wr_compl() const { return _ffull_dblks == _wr_cmpl_cnt_dblks; }
+ inline u_int32_t wr_aio_outstanding_dblks() const { return _wr_subm_cnt_dblks - _wr_cmpl_cnt_dblks; }
+ inline bool wr_file_rotate() const { return is_wr_full(); }
+
+ // Debug aid
+ const std::string status_str() const;
+
+ protected:
+ virtual void initialize(const std::string& fbasename, const u_int16_t pfid, const u_int16_t lfid,
+ const u_int32_t jfsize_sblks, const rcvdat* const ro);
+
+ static std::string filename(const std::string& fbasename, const u_int16_t pfid);
+ void clean_file(const u_int32_t jfsize_sblks);
+ void create_jfile(const u_int32_t jfsize_sblks);
+ };
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_FCNTL_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/file_hdr.h b/qpid/cpp/src/qpid/legacystore/jrnl/file_hdr.h
new file mode 100644
index 0000000000..db20834cbb
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/file_hdr.h
@@ -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.
+ *
+ */
+
+/**
+ * \file file_hdr.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::file_hdr (file
+ * record header), used to start a journal file. It contains some
+ * file metadata and information to aid journal recovery.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_FILE_HDR_H
+#define QPID_LEGACYSTORE_JRNL_FILE_HDR_H
+
+#include <cerrno>
+#include <ctime>
+#include "qpid/legacystore/jrnl/rec_hdr.h"
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include "qpid/legacystore/jrnl/jexception.h"
+#include <sstream>
+
+namespace mrg
+{
+namespace journal
+{
+
+#pragma pack(1)
+
+ /**
+ * \brief Struct for data common to the head of all journal files. In addition to
+ * the common data, this includes the record ID and offset of the first record in
+ * the file.
+ *
+ * This header precedes all data in journal files and occupies the first complete
+ * block in the file. The record ID and offset are updated on each overwrite of the
+ * file.
+ *
+ * File header info in binary format (48 bytes):
+ * <pre>
+ * 0 7
+ * +---+---+---+---+---+---+---+---+ -+
+ * | magic | v | e | flags | |
+ * +---+---+---+---+---+---+---+---+ | struct hdr
+ * | first rid in file | |
+ * +---+---+---+---+---+---+---+---+ -+
+ * | pfid | lfid | reserved (0) |
+ * +---+---+---+---+---+---+---+---+
+ * | fro |
+ * +---+---+---+---+---+---+---+---+
+ * | timestamp (sec) |
+ * +---+---+---+---+---+---+---+---+
+ * | timestamp (ns) |
+ * +---+---+---+---+---+---+---+---+
+ * v = file version (If the format or encoding of this file changes, then this
+ * number should be incremented)
+ * e = endian flag, false (0x00) for little endian, true (0x01) for big endian
+ * pfid = File ID (number used in naming file)
+ * lfid = Logical ID (order used in circular buffer)
+ * fro = First record offset, offset from start of file to first record header
+ * </pre>
+ *
+ * Note that journal files should be transferable between 32- and 64-bit
+ * hardware of the same endianness, but not between hardware of opposite
+ * entianness without some sort of binary conversion utility. Thus buffering
+ * will be needed for types that change size between 32- and 64-bit compiles.
+ */
+ struct file_hdr : rec_hdr
+ {
+ u_int16_t _pfid; ///< Physical file ID (pfid)
+ u_int16_t _lfid; ///< Logical file ID (lfid)
+ u_int32_t _res; ///< Reserved (for alignment/flags)
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ u_int32_t _filler0; ///< Big-endian filler for 32-bit size_t
+#endif
+ std::size_t _fro; ///< First record offset
+#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT)
+ u_int32_t _filler0; ///< Little-endian filler for 32-bit size_t
+#endif
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ u_int32_t _filler1; ///< Big-endian filler for 32-bit time_t
+#endif
+ std::time_t _ts_sec; ///< Timestamp of journal initilailization
+#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT)
+ u_int32_t _filler1; ///< Little-endian filler for 32-bit time_t
+#endif
+#if defined(JRNL_BIG_ENDIAN)
+ u_int32_t _filler2; ///< Big endian filler for u_int32_t
+#endif
+ u_int32_t _ts_nsec; ///< Timestamp of journal initilailization
+#if defined(JRNL_LITTLE_ENDIAN)
+ u_int32_t _filler2; ///< Little-endian filler for u_int32_t
+#endif
+
+ /**
+ * \brief Default constructor, which sets all values to 0.
+ */
+ inline file_hdr(): rec_hdr(), _pfid(0), _lfid(0), _res(0),
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ _filler0(0),
+#endif
+ _fro(0),
+#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT)
+ _filler0(0),
+#endif
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ _filler1(0),
+#endif
+ _ts_sec(0),
+#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT)
+ _filler1(0),
+#endif
+#if defined(JRNL_BIG_ENDIAN)
+ _filler2(0),
+#endif
+ _ts_nsec(0)
+#if defined(JRNL_LITTLE_ENDIAN)
+ , _filler2(0)
+#endif
+ {}
+
+ /**
+ * \brief Convenience constructor which initializes values during construction.
+ */
+ inline file_hdr(const u_int32_t magic, const u_int8_t version, const u_int64_t rid,
+ const u_int16_t pfid, const u_int16_t lfid, const std::size_t fro,
+ const bool owi, const bool settime = false):
+ rec_hdr(magic, version, rid, owi), _pfid(pfid), _lfid(lfid), _res(0),
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ _filler0(0),
+#endif
+ _fro(fro),
+#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT)
+ _filler0(0),
+#endif
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ _filler1(0),
+#endif
+ _ts_sec(0),
+#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT)
+ _filler1(0),
+#endif
+#if defined(JRNL_BIG_ENDIAN)
+ _filler2(0),
+#endif
+ _ts_nsec(0)
+#if defined(JRNL_LITTLE_ENDIAN)
+ , _filler2(0)
+#endif
+ { if (settime) set_time(); }
+
+ /**
+ * \brief Gets the current time from the system clock and sets the timestamp in the struct.
+ */
+ inline void set_time()
+ {
+ // TODO: Standardize on method for getting time that does not requrie a context switch.
+ timespec ts;
+ if (::clock_gettime(CLOCK_REALTIME, &ts))
+ {
+ std::ostringstream oss;
+ oss << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR__RTCLOCK, oss.str(), "file_hdr", "set_time");
+ }
+ _ts_sec = ts.tv_sec;
+ _ts_nsec = ts.tv_nsec;
+ }
+
+ /**
+ * \brief Sets the timestamp in the struct to the provided value (in seconds and
+ * nanoseconds).
+ */
+ inline void set_time(timespec& ts)
+ {
+ _ts_sec = ts.tv_sec;
+ _ts_nsec = ts.tv_nsec;
+ }
+
+ /**
+ * \brief Returns the size of the header in bytes.
+ */
+ inline static std::size_t size() { return sizeof(file_hdr); }
+ }; // struct file_hdr
+
+#pragma pack()
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_FILE_HDR_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/jcfg.h b/qpid/cpp/src/qpid/legacystore/jrnl/jcfg.h
new file mode 100644
index 0000000000..01d92ee985
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/jcfg.h
@@ -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.
+ *
+ */
+
+/**
+ * \file jcfg.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * This file contains \#defines that control the implementation details of
+ * the journal.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_JCFG_H
+#define QPID_LEGACYSTORE_JRNL_JCFG_H
+
+#if defined(__i386__) || (__arm__) /* little endian, 32 bits */
+#define JRNL_LITTLE_ENDIAN
+#define JRNL_32_BIT
+#elif defined(__PPC__) || defined(__s390__) /* big endian, 32 bits */
+#define JRNL_BIG_ENDIAN
+#define JRNL_32_BIT
+#elif defined(__ia64__) || defined(__x86_64__) || defined(__alpha__) || (__arm64__) /* little endian, 64 bits */
+#define JRNL_LITTLE_ENDIAN
+#define JRNL_64_BIT
+#elif defined(__powerpc64__) || defined(__s390x__) /* big endian, 64 bits */
+#define JRNL_BIG_ENDIAN
+#define JRNL_64_BIT
+#else
+#error endian?
+#endif
+
+
+/**
+* <b>Rule:</b> Data block size (JRNL_DBLK_SIZE) MUST be a power of 2 such that
+* <pre>
+* JRNL_DBLK_SIZE * JRNL_SBLK_SIZE == n * 512 (n = 1,2,3...)
+* </pre>
+* (The disk softblock size is 512 for Linux kernels >= 2.6)
+*/
+#define JRNL_DBLK_SIZE 128 ///< Data block size in bytes (CANNOT BE LESS THAN 32!)
+#define JRNL_SBLK_SIZE 4 ///< Disk softblock size in multiples of JRNL_DBLK_SIZE
+#define JRNL_MIN_FILE_SIZE 128 ///< Min. jrnl file size in sblks (excl. file_hdr)
+#define JRNL_MAX_FILE_SIZE 4194176 ///< Max. jrnl file size in sblks (excl. file_hdr)
+#define JRNL_MIN_NUM_FILES 4 ///< Min. number of journal files
+#define JRNL_MAX_NUM_FILES 64 ///< Max. number of journal files
+#define JRNL_ENQ_THRESHOLD 80 ///< Percent full when enqueue connection will be closed
+
+#define JRNL_RMGR_PAGE_SIZE 128 ///< Journal page size in softblocks
+#define JRNL_RMGR_PAGES 16 ///< Number of pages to use in wmgr
+
+#define JRNL_WMGR_DEF_PAGE_SIZE 64 ///< Journal write page size in softblocks (default)
+#define JRNL_WMGR_DEF_PAGES 32 ///< Number of pages to use in wmgr (default)
+
+#define JRNL_WMGR_MAXDTOKPP 1024 ///< Max. dtoks (data blocks) per page in wmgr
+#define JRNL_WMGR_MAXWAITUS 100 ///< Max. wait time (us) before submitting AIO
+
+#define JRNL_INFO_EXTENSION "jinf" ///< Extension for journal info files
+#define JRNL_DATA_EXTENSION "jdat" ///< Extension for journal data files
+#define RHM_JDAT_TXA_MAGIC 0x614d4852 ///< ("RHMa" in little endian) Magic for dtx abort hdrs
+#define RHM_JDAT_TXC_MAGIC 0x634d4852 ///< ("RHMc" in little endian) Magic for dtx commit hdrs
+#define RHM_JDAT_DEQ_MAGIC 0x644d4852 ///< ("RHMd" in little endian) Magic for deq rec hdrs
+#define RHM_JDAT_ENQ_MAGIC 0x654d4852 ///< ("RHMe" in little endian) Magic for enq rec hdrs
+#define RHM_JDAT_FILE_MAGIC 0x664d4852 ///< ("RHMf" in little endian) Magic for file hdrs
+#define RHM_JDAT_EMPTY_MAGIC 0x784d4852 ///< ("RHMx" in little endian) Magic for empty dblk
+#define RHM_JDAT_VERSION 0x01 ///< Version (of file layout)
+#define RHM_CLEAN_CHAR 0xff ///< Char used to clear empty space on disk
+
+#define RHM_LENDIAN_FLAG 0 ///< Value of little endian flag on disk
+#define RHM_BENDIAN_FLAG 1 ///< Value of big endian flag on disk
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_JCFG_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/jcntl.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/jcntl.cpp
new file mode 100644
index 0000000000..21fcf099b4
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/jcntl.cpp
@@ -0,0 +1,984 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file jcntl.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * Messaging journal top-level control and interface class
+ * mrg::journal::jcntl. See comments in file jcntl.h for details.
+ *
+ * \author Kim van der Riet
+ */
+
+
+#include "qpid/legacystore/jrnl/jcntl.h"
+
+#include <algorithm>
+#include <cassert>
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include "qpid/legacystore/jrnl/file_hdr.h"
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include "qpid/legacystore/jrnl/jinf.h"
+#include <limits>
+#include <sstream>
+#include <unistd.h>
+
+namespace mrg
+{
+namespace journal
+{
+
+#define AIO_CMPL_TIMEOUT_SEC 5
+#define AIO_CMPL_TIMEOUT_NSEC 0
+#define FINAL_AIO_CMPL_TIMEOUT_SEC 15
+#define FINAL_AIO_CMPL_TIMEOUT_NSEC 0
+
+// Static
+timespec jcntl::_aio_cmpl_timeout; ///< Timeout for blocking libaio returns
+timespec jcntl::_final_aio_cmpl_timeout; ///< Timeout for blocking libaio returns when stopping or finalizing
+bool jcntl::_init = init_statics();
+bool jcntl::init_statics()
+{
+ _aio_cmpl_timeout.tv_sec = AIO_CMPL_TIMEOUT_SEC;
+ _aio_cmpl_timeout.tv_nsec = AIO_CMPL_TIMEOUT_NSEC;
+ _final_aio_cmpl_timeout.tv_sec = FINAL_AIO_CMPL_TIMEOUT_SEC;
+ _final_aio_cmpl_timeout.tv_nsec = FINAL_AIO_CMPL_TIMEOUT_NSEC;
+ return true;
+}
+
+
+// Functions
+
+jcntl::jcntl(const std::string& jid, const std::string& jdir, const std::string& base_filename):
+ _jid(jid),
+ _jdir(jdir, base_filename),
+ _base_filename(base_filename),
+ _init_flag(false),
+ _stop_flag(false),
+ _readonly_flag(false),
+ _autostop(true),
+ _jfsize_sblks(0),
+ _lpmgr(),
+ _emap(),
+ _tmap(),
+ _rrfc(&_lpmgr),
+ _wrfc(&_lpmgr),
+ _rmgr(this, _emap, _tmap, _rrfc),
+ _wmgr(this, _emap, _tmap, _wrfc),
+ _rcvdat()
+{}
+
+jcntl::~jcntl()
+{
+ if (_init_flag && !_stop_flag)
+ try { stop(true); }
+ catch (const jexception& e) { std::cerr << e << std::endl; }
+ _lpmgr.finalize();
+}
+
+void
+jcntl::initialize(const u_int16_t num_jfiles, const bool ae, const u_int16_t ae_max_jfiles,
+ const u_int32_t jfsize_sblks, const u_int16_t wcache_num_pages, const u_int32_t wcache_pgsize_sblks,
+ aio_callback* const cbp)
+{
+ _init_flag = false;
+ _stop_flag = false;
+ _readonly_flag = false;
+
+ _emap.clear();
+ _tmap.clear();
+
+ _lpmgr.finalize();
+
+ // Set new file geometry parameters
+ assert(num_jfiles >= JRNL_MIN_NUM_FILES);
+ assert(num_jfiles <= JRNL_MAX_NUM_FILES);
+ _emap.set_num_jfiles(num_jfiles);
+ _tmap.set_num_jfiles(num_jfiles);
+
+ assert(jfsize_sblks >= JRNL_MIN_FILE_SIZE);
+ assert(jfsize_sblks <= JRNL_MAX_FILE_SIZE);
+ _jfsize_sblks = jfsize_sblks;
+
+ // Clear any existing journal files
+ _jdir.clear_dir();
+ _lpmgr.initialize(num_jfiles, ae, ae_max_jfiles, this, &new_fcntl);
+
+ _wrfc.initialize(_jfsize_sblks);
+ _rrfc.initialize();
+ _rrfc.set_findex(0);
+ _rmgr.initialize(cbp);
+ _wmgr.initialize(cbp, wcache_pgsize_sblks, wcache_num_pages, JRNL_WMGR_MAXDTOKPP, JRNL_WMGR_MAXWAITUS);
+
+ // Write info file (<basename>.jinf) to disk
+ write_infofile();
+
+ _init_flag = true;
+}
+
+void
+jcntl::recover(const u_int16_t num_jfiles, const bool ae, const u_int16_t ae_max_jfiles,
+ const u_int32_t jfsize_sblks, const u_int16_t wcache_num_pages, const u_int32_t wcache_pgsize_sblks,
+// const rd_aio_cb rd_cb, const wr_aio_cb wr_cb, const std::vector<std::string>* prep_txn_list_ptr,
+ aio_callback* const cbp, const std::vector<std::string>* prep_txn_list_ptr,
+ u_int64_t& highest_rid)
+{
+ _init_flag = false;
+ _stop_flag = false;
+ _readonly_flag = false;
+
+ _emap.clear();
+ _tmap.clear();
+
+ _lpmgr.finalize();
+
+ assert(num_jfiles >= JRNL_MIN_NUM_FILES);
+ assert(num_jfiles <= JRNL_MAX_NUM_FILES);
+ assert(jfsize_sblks >= JRNL_MIN_FILE_SIZE);
+ assert(jfsize_sblks <= JRNL_MAX_FILE_SIZE);
+ _jfsize_sblks = jfsize_sblks;
+
+ // Verify journal dir and journal files
+ _jdir.verify_dir();
+ _rcvdat.reset(num_jfiles, ae, ae_max_jfiles);
+
+ rcvr_janalyze(_rcvdat, prep_txn_list_ptr);
+ highest_rid = _rcvdat._h_rid;
+ if (_rcvdat._jfull)
+ throw jexception(jerrno::JERR_JCNTL_RECOVERJFULL, "jcntl", "recover");
+ this->log(LOG_DEBUG, _rcvdat.to_log(_jid));
+
+ _lpmgr.recover(_rcvdat, this, &new_fcntl);
+
+ _wrfc.initialize(_jfsize_sblks, &_rcvdat);
+ _rrfc.initialize();
+ _rrfc.set_findex(_rcvdat.ffid());
+ _rmgr.initialize(cbp);
+ _wmgr.initialize(cbp, wcache_pgsize_sblks, wcache_num_pages, JRNL_WMGR_MAXDTOKPP, JRNL_WMGR_MAXWAITUS,
+ (_rcvdat._lffull ? 0 : _rcvdat._eo));
+
+ _readonly_flag = true;
+ _init_flag = true;
+}
+
+void
+jcntl::recover_complete()
+{
+ if (!_readonly_flag)
+ throw jexception(jerrno::JERR_JCNTL_NOTRECOVERED, "jcntl", "recover_complete");
+ for (u_int16_t i=0; i<_lpmgr.num_jfiles(); i++)
+ _lpmgr.get_fcntlp(i)->reset(&_rcvdat);
+ _wrfc.initialize(_jfsize_sblks, &_rcvdat);
+ _rrfc.initialize();
+ _rrfc.set_findex(_rcvdat.ffid());
+ _rmgr.recover_complete();
+ _readonly_flag = false;
+}
+
+void
+jcntl::delete_jrnl_files()
+{
+ stop(true); // wait for AIO to complete
+ _jdir.delete_dir();
+}
+
+
+iores
+jcntl::enqueue_data_record(const void* const data_buff, const std::size_t tot_data_len,
+ const std::size_t this_data_len, data_tok* dtokp, const bool transient)
+{
+ iores r;
+ check_wstatus("enqueue_data_record");
+ {
+ slock s(_wr_mutex);
+ while (handle_aio_wait(_wmgr.enqueue(data_buff, tot_data_len, this_data_len, dtokp, 0, 0, transient, false), r,
+ dtokp)) ;
+ }
+ return r;
+}
+
+iores
+jcntl::enqueue_extern_data_record(const std::size_t tot_data_len, data_tok* dtokp, const bool transient)
+{
+ iores r;
+ check_wstatus("enqueue_extern_data_record");
+ {
+ slock s(_wr_mutex);
+ while (handle_aio_wait(_wmgr.enqueue(0, tot_data_len, 0, dtokp, 0, 0, transient, true), r, dtokp)) ;
+ }
+ return r;
+}
+
+iores
+jcntl::enqueue_txn_data_record(const void* const data_buff, const std::size_t tot_data_len,
+ const std::size_t this_data_len, data_tok* dtokp, const std::string& xid,
+ const bool transient)
+{
+ iores r;
+ check_wstatus("enqueue_tx_data_record");
+ {
+ slock s(_wr_mutex);
+ while (handle_aio_wait(_wmgr.enqueue(data_buff, tot_data_len, this_data_len, dtokp, xid.data(), xid.size(),
+ transient, false), r, dtokp)) ;
+ }
+ return r;
+}
+
+iores
+jcntl::enqueue_extern_txn_data_record(const std::size_t tot_data_len, data_tok* dtokp,
+ const std::string& xid, const bool transient)
+{
+ iores r;
+ check_wstatus("enqueue_extern_txn_data_record");
+ {
+ slock s(_wr_mutex);
+ while (handle_aio_wait(_wmgr.enqueue(0, tot_data_len, 0, dtokp, xid.data(), xid.size(), transient, true), r,
+ dtokp)) ;
+ }
+ return r;
+}
+
+/* TODO
+iores
+jcntl::get_data_record(const u_int64_t& rid, const std::size_t& dsize, const std::size_t& dsize_avail,
+ const void** const data, bool auto_discard)
+{
+ check_rstatus("get_data_record");
+ return _rmgr.get(rid, dsize, dsize_avail, data, auto_discard);
+} */
+
+/* TODO
+iores
+jcntl::discard_data_record(data_tok* const dtokp)
+{
+ check_rstatus("discard_data_record");
+ return _rmgr.discard(dtokp);
+} */
+
+iores
+jcntl::read_data_record(void** const datapp, std::size_t& dsize, void** const xidpp, std::size_t& xidsize,
+ bool& transient, bool& external, data_tok* const dtokp, bool ignore_pending_txns)
+{
+ check_rstatus("read_data");
+ iores res = _rmgr.read(datapp, dsize, xidpp, xidsize, transient, external, dtokp, ignore_pending_txns);
+ if (res == RHM_IORES_RCINVALID)
+ {
+ get_wr_events(0); // check for outstanding write events
+ iores sres = _rmgr.synchronize(); // flushes all outstanding read events
+ if (sres != RHM_IORES_SUCCESS)
+ return sres;
+ _rmgr.wait_for_validity(&_aio_cmpl_timeout, true); // throw if timeout occurs
+ res = _rmgr.read(datapp, dsize, xidpp, xidsize, transient, external, dtokp, ignore_pending_txns);
+ }
+ return res;
+}
+
+iores
+jcntl::dequeue_data_record(data_tok* const dtokp, const bool txn_coml_commit)
+{
+ iores r;
+ check_wstatus("dequeue_data");
+ {
+ slock s(_wr_mutex);
+ while (handle_aio_wait(_wmgr.dequeue(dtokp, 0, 0, txn_coml_commit), r, dtokp)) ;
+ }
+ return r;
+}
+
+iores
+jcntl::dequeue_txn_data_record(data_tok* const dtokp, const std::string& xid, const bool txn_coml_commit)
+{
+ iores r;
+ check_wstatus("dequeue_data");
+ {
+ slock s(_wr_mutex);
+ while (handle_aio_wait(_wmgr.dequeue(dtokp, xid.data(), xid.size(), txn_coml_commit), r, dtokp)) ;
+ }
+ return r;
+}
+
+iores
+jcntl::txn_abort(data_tok* const dtokp, const std::string& xid)
+{
+ iores r;
+ check_wstatus("txn_abort");
+ {
+ slock s(_wr_mutex);
+ while (handle_aio_wait(_wmgr.abort(dtokp, xid.data(), xid.size()), r, dtokp)) ;
+ }
+ return r;
+}
+
+iores
+jcntl::txn_commit(data_tok* const dtokp, const std::string& xid)
+{
+ iores r;
+ check_wstatus("txn_commit");
+ {
+ slock s(_wr_mutex);
+ while (handle_aio_wait(_wmgr.commit(dtokp, xid.data(), xid.size()), r, dtokp)) ;
+ }
+ return r;
+}
+
+bool
+jcntl::is_txn_synced(const std::string& xid)
+{
+ slock s(_wr_mutex);
+ bool res = _wmgr.is_txn_synced(xid);
+ return res;
+}
+
+int32_t
+jcntl::get_wr_events(timespec* const timeout)
+{
+ stlock t(_wr_mutex);
+ if (!t.locked())
+ return jerrno::LOCK_TAKEN;
+ int32_t res = _wmgr.get_events(pmgr::UNUSED, timeout);
+ return res;
+}
+
+int32_t
+jcntl::get_rd_events(timespec* const timeout)
+{
+ return _rmgr.get_events(pmgr::AIO_COMPLETE, timeout);
+}
+
+void
+jcntl::stop(const bool block_till_aio_cmpl)
+{
+ if (_readonly_flag)
+ check_rstatus("stop");
+ else
+ check_wstatus("stop");
+ _stop_flag = true;
+ if (!_readonly_flag)
+ flush(block_till_aio_cmpl);
+ _rrfc.finalize();
+ _lpmgr.finalize();
+}
+
+u_int16_t
+jcntl::get_earliest_fid()
+{
+ u_int16_t ffid = _wrfc.earliest_index();
+ u_int16_t fid = _wrfc.index();
+ while ( _emap.get_enq_cnt(ffid) == 0 && _tmap.get_txn_pfid_cnt(ffid) == 0 && ffid != fid)
+ {
+ if (++ffid >= _lpmgr.num_jfiles())
+ ffid = 0;
+ }
+ if (!_rrfc.is_active())
+ _rrfc.set_findex(ffid);
+ return ffid;
+}
+
+iores
+jcntl::flush(const bool block_till_aio_cmpl)
+{
+ if (!_init_flag)
+ return RHM_IORES_SUCCESS;
+ if (_readonly_flag)
+ throw jexception(jerrno::JERR_JCNTL_READONLY, "jcntl", "flush");
+ iores res;
+ {
+ slock s(_wr_mutex);
+ res = _wmgr.flush();
+ }
+ if (block_till_aio_cmpl)
+ aio_cmpl_wait();
+ return res;
+}
+
+void
+jcntl::log(log_level ll, const std::string& log_stmt) const
+{
+ log(ll, log_stmt.c_str());
+}
+
+void
+jcntl::log(log_level ll, const char* const log_stmt) const
+{
+ if (ll > LOG_INFO)
+ {
+ std::cout << log_level_str(ll) << ": Journal \"" << _jid << "\": " << log_stmt << std::endl;
+ }
+}
+
+void
+jcntl::chk_wr_frot()
+{
+ if (_wrfc.index() == _rrfc.index())
+ _rmgr.invalidate();
+}
+
+void
+jcntl::fhdr_wr_sync(const u_int16_t lid)
+{
+ fcntl* fcntlp = _lpmgr.get_fcntlp(lid);
+ while (fcntlp->wr_fhdr_aio_outstanding())
+ {
+ if (get_wr_events(&_aio_cmpl_timeout) == jerrno::AIO_TIMEOUT)
+ throw jexception(jerrno::JERR_JCNTL_AIOCMPLWAIT, "jcntl", "fhdr_wr_sync");
+ }
+}
+
+fcntl*
+jcntl::new_fcntl(jcntl* const jcp, const u_int16_t lid, const u_int16_t fid, const rcvdat* const rdp)
+{
+ if (!jcp) return 0;
+ std::ostringstream oss;
+ oss << jcp->jrnl_dir() << "/" << jcp->base_filename();
+ return new fcntl(oss.str(), fid, lid, jcp->jfsize_sblks(), rdp);
+}
+
+// Protected/Private functions
+
+void
+jcntl::check_wstatus(const char* fn_name) const
+{
+ if (!_init_flag)
+ throw jexception(jerrno::JERR__NINIT, "jcntl", fn_name);
+ if (_readonly_flag)
+ throw jexception(jerrno::JERR_JCNTL_READONLY, "jcntl", fn_name);
+ if (_stop_flag)
+ throw jexception(jerrno::JERR_JCNTL_STOPPED, "jcntl", fn_name);
+}
+
+void
+jcntl::check_rstatus(const char* fn_name) const
+{
+ if (!_init_flag)
+ throw jexception(jerrno::JERR__NINIT, "jcntl", fn_name);
+ if (_stop_flag)
+ throw jexception(jerrno::JERR_JCNTL_STOPPED, "jcntl", fn_name);
+}
+
+void
+jcntl::write_infofile() const
+{
+ timespec ts;
+ if (::clock_gettime(CLOCK_REALTIME, &ts))
+ {
+ std::ostringstream oss;
+ oss << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR__RTCLOCK, oss.str(), "jcntl", "write_infofile");
+ }
+ jinf ji(_jid, _jdir.dirname(), _base_filename, _lpmgr.num_jfiles(), _lpmgr.is_ae(), _lpmgr.ae_max_jfiles(),
+ _jfsize_sblks, _wmgr.cache_pgsize_sblks(), _wmgr.cache_num_pages(), ts);
+ ji.write();
+}
+
+void
+jcntl::aio_cmpl_wait()
+{
+ //while (_wmgr.get_aio_evt_rem())
+ while (true)
+ {
+ u_int32_t aer;
+ {
+ slock s(_wr_mutex);
+ aer = _wmgr.get_aio_evt_rem();
+ }
+ if (aer == 0) break; // no events left
+ if (get_wr_events(&_aio_cmpl_timeout) == jerrno::AIO_TIMEOUT)
+ throw jexception(jerrno::JERR_JCNTL_AIOCMPLWAIT, "jcntl", "aio_cmpl_wait");
+ }
+}
+
+bool
+jcntl::handle_aio_wait(const iores res, iores& resout, const data_tok* dtp)
+{
+ resout = res;
+ if (res == RHM_IORES_PAGE_AIOWAIT)
+ {
+ while (_wmgr.curr_pg_blocked())
+ {
+ if (_wmgr.get_events(pmgr::UNUSED, &_aio_cmpl_timeout) == jerrno::AIO_TIMEOUT)
+ {
+ std::ostringstream oss;
+ oss << "get_events() returned JERR_JCNTL_AIOCMPLWAIT; wmgr_status: " << _wmgr.status_str();
+ this->log(LOG_CRITICAL, oss.str());
+ throw jexception(jerrno::JERR_JCNTL_AIOCMPLWAIT, "jcntl", "handle_aio_wait");
+ }
+ }
+ return true;
+ }
+ else if (res == RHM_IORES_FILE_AIOWAIT)
+ {
+ while (_wmgr.curr_file_blocked())
+ {
+ if (_wmgr.get_events(pmgr::UNUSED, &_aio_cmpl_timeout) == jerrno::AIO_TIMEOUT)
+ {
+ std::ostringstream oss;
+ oss << "get_events() returned JERR_JCNTL_AIOCMPLWAIT; wmgr_status: " << _wmgr.status_str();
+ this->log(LOG_CRITICAL, oss.str());
+ throw jexception(jerrno::JERR_JCNTL_AIOCMPLWAIT, "jcntl", "handle_aio_wait");
+ }
+ }
+ _wrfc.wr_reset();
+ resout = RHM_IORES_SUCCESS;
+ data_tok::write_state ws = dtp->wstate();
+ return ws == data_tok::ENQ_PART || ws == data_tok::DEQ_PART || ws == data_tok::ABORT_PART ||
+ ws == data_tok::COMMIT_PART;
+ }
+ return false;
+}
+
+void
+jcntl::rcvr_janalyze(rcvdat& rd, const std::vector<std::string>* prep_txn_list_ptr)
+{
+ jinf ji(_jdir.dirname() + "/" + _base_filename + "." + JRNL_INFO_EXTENSION, true);
+
+ // If the number of files does not tie up with the jinf file from the journal being recovered,
+ // use the jinf data.
+ if (rd._njf != ji.num_jfiles())
+ {
+ std::ostringstream oss;
+ oss << "Recovery found " << ji.num_jfiles() <<
+ " files (different from --num-jfiles value of " << rd._njf << ").";
+ this->log(LOG_INFO, oss.str());
+ rd._njf = ji.num_jfiles();
+ _rcvdat._enq_cnt_list.resize(rd._njf);
+ }
+ _emap.set_num_jfiles(rd._njf);
+ _tmap.set_num_jfiles(rd._njf);
+ if (_jfsize_sblks != ji.jfsize_sblks())
+ {
+ std::ostringstream oss;
+ oss << "Recovery found file size = " << (ji.jfsize_sblks() / JRNL_RMGR_PAGE_SIZE) <<
+ " (different from --jfile-size-pgs value of " <<
+ (_jfsize_sblks / JRNL_RMGR_PAGE_SIZE) << ").";
+ this->log(LOG_INFO, oss.str());
+ _jfsize_sblks = ji.jfsize_sblks();
+ }
+ if (_jdir.dirname().compare(ji.jdir()))
+ {
+ std::ostringstream oss;
+ oss << "Journal file location change: original = \"" << ji.jdir() <<
+ "\"; current = \"" << _jdir.dirname() << "\"";
+ this->log(LOG_WARN, oss.str());
+ ji.set_jdir(_jdir.dirname());
+ }
+
+ try
+ {
+ rd._ffid = ji.get_first_pfid();
+ rd._lfid = ji.get_last_pfid();
+ rd._owi = ji.get_initial_owi();
+ rd._frot = ji.get_frot();
+ rd._jempty = false;
+ ji.get_normalized_pfid_list(rd._fid_list); // _pfid_list
+ }
+ catch (const jexception& e)
+ {
+ if (e.err_code() != jerrno::JERR_JINF_JDATEMPTY) throw;
+ }
+
+ // Restore all read and write pointers and transactions
+ if (!rd._jempty)
+ {
+ u_int16_t fid = rd._ffid;
+ std::ifstream ifs;
+ bool lowi = rd._owi; // local copy of owi to be used during analysis
+ while (rcvr_get_next_record(fid, &ifs, lowi, rd)) ;
+ if (ifs.is_open()) ifs.close();
+
+ // Remove all txns from tmap that are not in the prepared list
+ if (prep_txn_list_ptr)
+ {
+ std::vector<std::string> xid_list;
+ _tmap.xid_list(xid_list);
+ for (std::vector<std::string>::iterator itr = xid_list.begin(); itr != xid_list.end(); itr++)
+ {
+ std::vector<std::string>::const_iterator pitr =
+ std::find(prep_txn_list_ptr->begin(), prep_txn_list_ptr->end(), *itr);
+ if (pitr == prep_txn_list_ptr->end()) // not found in prepared list
+ {
+ txn_data_list tdl = _tmap.get_remove_tdata_list(*itr); // tdl will be empty if xid not found
+ // Unlock any affected enqueues in emap
+ for (tdl_itr i=tdl.begin(); i<tdl.end(); i++)
+ {
+ if (i->_enq_flag) // enq op - decrement enqueue count
+ rd._enq_cnt_list[i->_pfid]--;
+ else if (_emap.is_enqueued(i->_drid, true)) // deq op - unlock enq record
+ {
+ int16_t ret = _emap.unlock(i->_drid);
+ if (ret < enq_map::EMAP_OK) // fail
+ {
+ // enq_map::unlock()'s only error is enq_map::EMAP_RID_NOT_FOUND
+ std::ostringstream oss;
+ oss << std::hex << "_emap.unlock(): drid=0x\"" << i->_drid;
+ throw jexception(jerrno::JERR_MAP_NOTFOUND, oss.str(), "jcntl", "rcvr_janalyze");
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // Check for file full condition - add one to _jfsize_sblks to account for file header
+ rd._lffull = rd._eo == (1 + _jfsize_sblks) * JRNL_SBLK_SIZE * JRNL_DBLK_SIZE;
+
+ // Check for journal full condition
+ u_int16_t next_wr_fid = (rd._lfid + 1) % rd._njf;
+ rd._jfull = rd._ffid == next_wr_fid && rd._enq_cnt_list[next_wr_fid] && rd._lffull;
+ }
+}
+
+bool
+jcntl::rcvr_get_next_record(u_int16_t& fid, std::ifstream* ifsp, bool& lowi, rcvdat& rd)
+{
+ std::size_t cum_size_read = 0;
+ void* xidp = 0;
+ rec_hdr h;
+
+ bool hdr_ok = false;
+ std::streampos file_pos;
+ while (!hdr_ok)
+ {
+ if (!ifsp->is_open())
+ {
+ if (!jfile_cycle(fid, ifsp, lowi, rd, true))
+ return false;
+ }
+ file_pos = ifsp->tellg();
+ ifsp->read((char*)&h, sizeof(rec_hdr));
+ if (ifsp->gcount() == sizeof(rec_hdr))
+ hdr_ok = true;
+ else
+ {
+ if (!jfile_cycle(fid, ifsp, lowi, rd, true))
+ return false;
+ }
+ }
+
+ switch(h._magic)
+ {
+ case RHM_JDAT_ENQ_MAGIC:
+ {
+ enq_rec er;
+ u_int16_t start_fid = fid; // fid may increment in decode() if record folds over file boundary
+ if (!decode(er, fid, ifsp, cum_size_read, h, lowi, rd, file_pos))
+ return false;
+ if (!er.is_transient()) // Ignore transient msgs
+ {
+ rd._enq_cnt_list[start_fid]++;
+ if (er.xid_size())
+ {
+ er.get_xid(&xidp);
+ assert(xidp != 0);
+ std::string xid((char*)xidp, er.xid_size());
+ _tmap.insert_txn_data(xid, txn_data(h._rid, 0, start_fid, true));
+ if (_tmap.set_aio_compl(xid, h._rid) < txn_map::TMAP_OK) // fail - xid or rid not found
+ {
+ std::ostringstream oss;
+ oss << std::hex << "_tmap.set_aio_compl: txn_enq xid=\"" << xid << "\" rid=0x" << h._rid;
+ throw jexception(jerrno::JERR_MAP_NOTFOUND, oss.str(), "jcntl", "rcvr_get_next_record");
+ }
+ std::free(xidp);
+ }
+ else
+ {
+ if (_emap.insert_pfid(h._rid, start_fid) < enq_map::EMAP_OK) // fail
+ {
+ // The only error code emap::insert_pfid() returns is enq_map::EMAP_DUP_RID.
+ std::ostringstream oss;
+ oss << std::hex << "rid=0x" << h._rid << " _pfid=0x" << start_fid;
+ throw jexception(jerrno::JERR_MAP_DUPLICATE, oss.str(), "jcntl", "rcvr_get_next_record");
+ }
+ }
+ }
+ }
+ break;
+ case RHM_JDAT_DEQ_MAGIC:
+ {
+ deq_rec dr;
+ u_int16_t start_fid = fid; // fid may increment in decode() if record folds over file boundary
+ if (!decode(dr, fid, ifsp, cum_size_read, h, lowi, rd, file_pos))
+ return false;
+ if (dr.xid_size())
+ {
+ // If the enqueue is part of a pending txn, it will not yet be in emap
+ _emap.lock(dr.deq_rid()); // ignore not found error
+ dr.get_xid(&xidp);
+ assert(xidp != 0);
+ std::string xid((char*)xidp, dr.xid_size());
+ _tmap.insert_txn_data(xid, txn_data(dr.rid(), dr.deq_rid(), start_fid, false,
+ dr.is_txn_coml_commit()));
+ if (_tmap.set_aio_compl(xid, dr.rid()) < txn_map::TMAP_OK) // fail - xid or rid not found
+ {
+ std::ostringstream oss;
+ oss << std::hex << "_tmap.set_aio_compl: txn_deq xid=\"" << xid << "\" rid=0x" << dr.rid();
+ throw jexception(jerrno::JERR_MAP_NOTFOUND, oss.str(), "jcntl", "rcvr_get_next_record");
+ }
+ std::free(xidp);
+ }
+ else
+ {
+ int16_t enq_fid = _emap.get_remove_pfid(dr.deq_rid(), true);
+ if (enq_fid >= enq_map::EMAP_OK) // ignore not found error
+ rd._enq_cnt_list[enq_fid]--;
+ }
+ }
+ break;
+ case RHM_JDAT_TXA_MAGIC:
+ {
+ txn_rec ar;
+ if (!decode(ar, fid, ifsp, cum_size_read, h, lowi, rd, file_pos))
+ return false;
+ // Delete this txn from tmap, unlock any locked records in emap
+ ar.get_xid(&xidp);
+ assert(xidp != 0);
+ std::string xid((char*)xidp, ar.xid_size());
+ txn_data_list tdl = _tmap.get_remove_tdata_list(xid); // tdl will be empty if xid not found
+ for (tdl_itr itr = tdl.begin(); itr != tdl.end(); itr++)
+ {
+ if (itr->_enq_flag)
+ rd._enq_cnt_list[itr->_pfid]--;
+ else
+ _emap.unlock(itr->_drid); // ignore not found error
+ }
+ std::free(xidp);
+ }
+ break;
+ case RHM_JDAT_TXC_MAGIC:
+ {
+ txn_rec cr;
+ if (!decode(cr, fid, ifsp, cum_size_read, h, lowi, rd, file_pos))
+ return false;
+ // Delete this txn from tmap, process records into emap
+ cr.get_xid(&xidp);
+ assert(xidp != 0);
+ std::string xid((char*)xidp, cr.xid_size());
+ txn_data_list tdl = _tmap.get_remove_tdata_list(xid); // tdl will be empty if xid not found
+ for (tdl_itr itr = tdl.begin(); itr != tdl.end(); itr++)
+ {
+ if (itr->_enq_flag) // txn enqueue
+ {
+ if (_emap.insert_pfid(itr->_rid, itr->_pfid) < enq_map::EMAP_OK) // fail
+ {
+ // The only error code emap::insert_pfid() returns is enq_map::EMAP_DUP_RID.
+ std::ostringstream oss;
+ oss << std::hex << "rid=0x" << itr->_rid << " _pfid=0x" << itr->_pfid;
+ throw jexception(jerrno::JERR_MAP_DUPLICATE, oss.str(), "jcntl", "rcvr_get_next_record");
+ }
+ }
+ else // txn dequeue
+ {
+ int16_t enq_fid = _emap.get_remove_pfid(itr->_drid, true);
+ if (enq_fid >= enq_map::EMAP_OK)
+ rd._enq_cnt_list[enq_fid]--;
+ }
+ }
+ std::free(xidp);
+ }
+ break;
+ case RHM_JDAT_EMPTY_MAGIC:
+ {
+ u_int32_t rec_dblks = jrec::size_dblks(sizeof(rec_hdr));
+ ifsp->ignore(rec_dblks * JRNL_DBLK_SIZE - sizeof(rec_hdr));
+ assert(!ifsp->fail() && !ifsp->bad());
+ if (!jfile_cycle(fid, ifsp, lowi, rd, false))
+ return false;
+ }
+ break;
+ case 0:
+ check_journal_alignment(fid, file_pos, rd);
+ return false;
+ default:
+ // Stop as this is the overwrite boundary.
+ check_journal_alignment(fid, file_pos, rd);
+ return false;
+ }
+ return true;
+}
+
+bool
+jcntl::decode(jrec& rec, u_int16_t& fid, std::ifstream* ifsp, std::size_t& cum_size_read,
+ rec_hdr& h, bool& lowi, rcvdat& rd, std::streampos& file_offs)
+{
+ u_int16_t start_fid = fid;
+ std::streampos start_file_offs = file_offs;
+ if (!check_owi(fid, h, lowi, rd, file_offs))
+ return false;
+ bool done = false;
+ while (!done)
+ {
+ try { done = rec.rcv_decode(h, ifsp, cum_size_read); }
+ catch (const jexception& e)
+ {
+// TODO - review this logic and tidy up how rd._lfid is assigned. See new jinf.get_end_file() fn.
+// Original
+// if (e.err_code() != jerrno::JERR_JREC_BADRECTAIL ||
+// fid != (rd._ffid ? rd._ffid - 1 : _num_jfiles - 1)) throw;
+// Tried this, but did not work
+// if (e.err_code() != jerrno::JERR_JREC_BADRECTAIL || h._magic != 0) throw;
+ check_journal_alignment(start_fid, start_file_offs, rd);
+// rd._lfid = start_fid;
+ return false;
+ }
+ if (!done && !jfile_cycle(fid, ifsp, lowi, rd, false))
+ {
+ check_journal_alignment(start_fid, start_file_offs, rd);
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+jcntl::jfile_cycle(u_int16_t& fid, std::ifstream* ifsp, bool& lowi, rcvdat& rd, const bool jump_fro)
+{
+ if (ifsp->is_open())
+ {
+ if (ifsp->eof() || !ifsp->good())
+ {
+ ifsp->clear();
+ rd._eo = ifsp->tellg(); // remember file offset before closing
+ assert(rd._eo != std::numeric_limits<std::size_t>::max()); // Check for error code -1
+ ifsp->close();
+ if (++fid >= rd._njf)
+ {
+ fid = 0;
+ lowi = !lowi; // Flip local owi
+ }
+ if (fid == rd._ffid) // used up all journal files
+ return false;
+ }
+ }
+ if (!ifsp->is_open())
+ {
+ std::ostringstream oss;
+ oss << _jdir.dirname() << "/" << _base_filename << ".";
+ oss << std::hex << std::setfill('0') << std::setw(4) << fid << "." << JRNL_DATA_EXTENSION;
+ ifsp->clear(); // clear eof flag, req'd for older versions of c++
+ ifsp->open(oss.str().c_str(), std::ios_base::in | std::ios_base::binary);
+ if (!ifsp->good())
+ throw jexception(jerrno::JERR__FILEIO, oss.str(), "jcntl", "jfile_cycle");
+
+ // Read file header
+ file_hdr fhdr;
+ ifsp->read((char*)&fhdr, sizeof(fhdr));
+ assert(ifsp->good());
+ if (fhdr._magic == RHM_JDAT_FILE_MAGIC)
+ {
+ assert(fhdr._lfid == fid);
+ if (!rd._fro)
+ rd._fro = fhdr._fro;
+ std::streamoff foffs = jump_fro ? fhdr._fro : JRNL_DBLK_SIZE * JRNL_SBLK_SIZE;
+ ifsp->seekg(foffs);
+ }
+ else
+ {
+ ifsp->close();
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+jcntl::check_owi(const u_int16_t fid, rec_hdr& h, bool& lowi, rcvdat& rd, std::streampos& file_pos)
+{
+ if (rd._ffid ? h.get_owi() == lowi : h.get_owi() != lowi) // Overwrite indicator changed
+ {
+ u_int16_t expected_fid = rd._ffid ? rd._ffid - 1 : rd._njf - 1;
+ if (fid == expected_fid)
+ {
+ check_journal_alignment(fid, file_pos, rd);
+ return false;
+ }
+ std::ostringstream oss;
+ oss << std::hex << std::setfill('0') << "Magic=0x" << std::setw(8) << h._magic;
+ oss << " fid=0x" << std::setw(4) << fid << " rid=0x" << std::setw(8) << h._rid;
+ oss << " foffs=0x" << std::setw(8) << file_pos;
+ oss << " expected_fid=0x" << std::setw(4) << expected_fid;
+ throw jexception(jerrno::JERR_JCNTL_OWIMISMATCH, oss.str(), "jcntl",
+ "check_owi");
+ }
+ if (rd._h_rid == 0)
+ rd._h_rid = h._rid;
+ else if (h._rid - rd._h_rid < 0x8000000000000000ULL) // RFC 1982 comparison for unsigned 64-bit
+ rd._h_rid = h._rid;
+ return true;
+}
+
+
+void
+jcntl::check_journal_alignment(const u_int16_t fid, std::streampos& file_pos, rcvdat& rd)
+{
+ unsigned sblk_offs = file_pos % (JRNL_DBLK_SIZE * JRNL_SBLK_SIZE);
+ if (sblk_offs)
+ {
+ {
+ std::ostringstream oss;
+ oss << std::hex << "Bad record alignment found at fid=0x" << fid;
+ oss << " offs=0x" << file_pos << " (likely journal overwrite boundary); " << std::dec;
+ oss << (JRNL_SBLK_SIZE - (sblk_offs/JRNL_DBLK_SIZE)) << " filler record(s) required.";
+ this->log(LOG_WARN, oss.str());
+ }
+ const u_int32_t xmagic = RHM_JDAT_EMPTY_MAGIC;
+ std::ostringstream oss;
+ oss << _jdir.dirname() << "/" << _base_filename << ".";
+ oss << std::hex << std::setfill('0') << std::setw(4) << fid << "." << JRNL_DATA_EXTENSION;
+ std::ofstream ofsp(oss.str().c_str(),
+ std::ios_base::in | std::ios_base::out | std::ios_base::binary);
+ if (!ofsp.good())
+ throw jexception(jerrno::JERR__FILEIO, oss.str(), "jcntl", "check_journal_alignment");
+ ofsp.seekp(file_pos);
+ void* buff = std::malloc(JRNL_DBLK_SIZE);
+ assert(buff != 0);
+ std::memcpy(buff, (const void*)&xmagic, sizeof(xmagic));
+ // Normally, RHM_CLEAN must be set before these fills are done, but this is a recover
+ // situation (i.e. performance is not an issue), and it makes the location of the write
+ // clear should inspection of the file be required.
+ std::memset((char*)buff + sizeof(xmagic), RHM_CLEAN_CHAR, JRNL_DBLK_SIZE - sizeof(xmagic));
+
+ while (file_pos % (JRNL_DBLK_SIZE * JRNL_SBLK_SIZE))
+ {
+ ofsp.write((const char*)buff, JRNL_DBLK_SIZE);
+ assert(!ofsp.fail());
+ std::ostringstream oss;
+ oss << std::hex << "Recover phase write: Wrote filler record: fid=0x" << fid << " offs=0x" << file_pos;
+ this->log(LOG_NOTICE, oss.str());
+ file_pos = ofsp.tellp();
+ }
+ ofsp.close();
+ std::free(buff);
+ rd._lfid = fid;
+ if (!rd._frot)
+ rd._ffid = (fid + 1) % rd._njf;
+ this->log(LOG_INFO, "Bad record alignment fixed.");
+ }
+ rd._eo = file_pos;
+}
+
+} // namespace journal
+} // namespace mrg
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/jcntl.h b/qpid/cpp/src/qpid/legacystore/jrnl/jcntl.h
new file mode 100644
index 0000000000..294e9ced05
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/jcntl.h
@@ -0,0 +1,722 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file jcntl.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * Messaging journal top-level control and interface class
+ * mrg::journal::jcntl. See class documentation for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_JCNTL_H
+#define QPID_LEGACYSTORE_JRNL_JCNTL_H
+
+namespace mrg
+{
+namespace journal
+{
+ class jcntl;
+}
+}
+
+#include <cstddef>
+#include <deque>
+#include "qpid/legacystore/jrnl/jdir.h"
+#include "qpid/legacystore/jrnl/fcntl.h"
+#include "qpid/legacystore/jrnl/lpmgr.h"
+#include "qpid/legacystore/jrnl/rcvdat.h"
+#include "qpid/legacystore/jrnl/slock.h"
+#include "qpid/legacystore/jrnl/smutex.h"
+#include "qpid/legacystore/jrnl/rmgr.h"
+#include "qpid/legacystore/jrnl/wmgr.h"
+#include "qpid/legacystore/jrnl/wrfc.h"
+
+namespace mrg
+{
+namespace journal
+{
+
+ /**
+ * \brief Access and control interface for the journal. This is the top-level class for the
+ * journal.
+ *
+ * This is the top-level journal class; one instance of this class controls one instance of the
+ * journal and all its files and associated control structures. Besides this class, the only
+ * other class that needs to be used at a higher level is the data_tok class, one instance of
+ * which is used per data block written to the journal, and is used to track its status through
+ * the AIO enqueue, read and dequeue process.
+ */
+ class jcntl
+ {
+ protected:
+ /**
+ * \brief Journal ID
+ *
+ * This string uniquely identifies this journal instance. It will most likely be associated
+ * with the identity of the message queue with which it is associated.
+ */
+ // TODO: This is not included in any files at present, add to file_hdr?
+ std::string _jid;
+
+ /**
+ * \brief Journal directory
+ *
+ * This string stores the path to the journal directory. It may be absolute or relative, and
+ * should not end in a file separator character. (e.g. "/fastdisk/jdata" is correct,
+ * "/fastdisk/jdata/" is not.)
+ */
+ jdir _jdir;
+
+ /**
+ * \brief Base filename
+ *
+ * This string contains the base filename used for the journal files. The filenames will
+ * start with this base, and have various sections added to it to derive the final file names
+ * that will be written to disk. No file separator characters should be included here, but
+ * all other legal filename characters are valid.
+ */
+ std::string _base_filename;
+
+ /**
+ * \brief Initialized flag
+ *
+ * This flag starts out set to false, is set to true once this object has been initialized,
+ * either by calling initialize() or recover().
+ */
+ bool _init_flag;
+
+ /**
+ * \brief Stopped flag
+ *
+ * This flag starts out false, and is set to true when stop() is called. At this point, the
+ * journal will no longer accept messages until either initialize() or recover() is called.
+ * There is no way other than through initialization to reset this flag.
+ */
+ // TODO: It would be helpful to distinguish between states stopping and stopped. If stop(true) is called,
+ // then we are stopping, but must wait for all outstanding aios to return before being finally stopped. During
+ // this period, however, no new enqueue/dequeue/read requests may be accepted.
+ bool _stop_flag;
+
+ /**
+ * \brief Read-only state flag used during recover.
+ *
+ * When true, this flag prevents journal write operations (enqueue and dequeue), but
+ * allows read to occur. It is used during recovery, and is reset when recovered() is
+ * called.
+ */
+ bool _readonly_flag;
+
+ /**
+ * \brief If set, calls stop() if the jouranl write pointer overruns dequeue low water
+ * marker. If not set, then attempts to write will throw exceptions until the journal
+ * file low water marker moves to the next journal file.
+ */
+ bool _autostop; ///< Autostop flag - stops journal when overrun occurs
+
+ // Journal control structures
+ u_int32_t _jfsize_sblks; ///< Journal file size in sblks
+ lpmgr _lpmgr; ///< LFID-PFID manager tracks inserted journal files
+ enq_map _emap; ///< Enqueue map for low water mark management
+ txn_map _tmap; ///< Transaction map open transactions
+ rrfc _rrfc; ///< Read journal rotating file controller
+ wrfc _wrfc; ///< Write journal rotating file controller
+ rmgr _rmgr; ///< Read page manager which manages AIO
+ wmgr _wmgr; ///< Write page manager which manages AIO
+ rcvdat _rcvdat; ///< Recovery data used for recovery
+ smutex _wr_mutex; ///< Mutex for journal writes
+
+ public:
+ static timespec _aio_cmpl_timeout; ///< Timeout for blocking libaio returns
+ static timespec _final_aio_cmpl_timeout; ///< Timeout for blocking libaio returns when stopping or finalizing
+
+ /**
+ * \brief Journal constructor.
+ *
+ * Constructor which sets the physical file location and base name.
+ *
+ * \param jid A unique identifier for this journal instance.
+ * \param jdir The directory which will contain the journal files.
+ * \param base_filename The string which will be used to start all journal filenames.
+ */
+ jcntl(const std::string& jid, const std::string& jdir, const std::string& base_filename);
+
+ /**
+ * \brief Destructor.
+ */
+ virtual ~jcntl();
+
+ inline const std::string& id() const { return _jid; }
+ inline const std::string& jrnl_dir() const { return _jdir.dirname(); }
+
+ /**
+ * \brief Initialize the journal for storing data.
+ *
+ * Initialize the journal by creating new journal data files and initializing internal
+ * control structures. When complete, the journal will be empty, and ready to store data.
+ *
+ * <b>NOTE: Any existing journal will be ignored by this operation.</b> To use recover
+ * the data from an existing journal, use recover().
+ *
+ * <b>NOTE: If <i>NULL</i> is passed to the deque pointers, they will be internally created
+ * and deleted.</b>
+ *
+ * <b>NOTE: If <i>NULL</i> is passed to the callbacks, internal default callbacks will be
+ * used.</b>
+ *
+ * \param num_jfiles The number of journal files to be created.
+ * \param auto_expand If true, allows journal file auto-expansion. In this mode, the journal will automatically
+ * add files to the journal if it runs out of space. No more than ae_max_jfiles may be added. If false, then
+ * no files are added and an exception will be thrown if the journal runs out of file space.
+ * \param ae_max_jfiles Upper limit of journal files for auto-expand mode. When auto_expand is true, this is the
+ * maximum total number of files allowed in the journal (original plus those added by auto-expand mode). If
+ * this number of files exist and the journal runs out of space, an exception will be thrown. This number
+ * must be greater than the num_jfiles parameter value but cannot exceed the maximum number of files for a
+ * single journal; if num_jfiles is already at its maximum value, then auto-expand will be disabled.
+ * \param jfsize_sblks The size of each journal file expressed in softblocks.
+ * \param wcache_num_pages The number of write cache pages to create.
+ * \param wcache_pgsize_sblks The size in sblks of each write cache page.
+ * \param cbp Pointer to object containing callback functions for read and write operations. May be 0 (NULL).
+ *
+ * \exception TODO
+ */
+ void initialize(const u_int16_t num_jfiles, const bool auto_expand, const u_int16_t ae_max_jfiles,
+ const u_int32_t jfsize_sblks, const u_int16_t wcache_num_pages, const u_int32_t wcache_pgsize_sblks,
+ aio_callback* const cbp);
+
+ /**
+ * /brief Initialize journal by recovering state from previously written journal.
+ *
+ * Initialize journal by recovering state from previously written journal. The journal files
+ * are analyzed, and all records that have not been dequeued and that remain in the journal
+ * will be available for reading. The journal is placed in a read-only state until
+ * recovered() is called; any calls to enqueue or dequeue will fail with an exception
+ * in this state.
+ *
+ * <b>NOTE: If <i>NULL</i> is passed to the deque pointers, they will be internally created
+ * and deleted.</b>
+ *
+ * <b>NOTE: If <i>NULL</i> is passed to the callbacks, internal default callbacks will be
+ * used.</b>
+ *
+ * \param num_jfiles The number of journal files to be created.
+ * \param auto_expand If true, allows journal file auto-expansion. In this mode, the journal will automatically
+ * add files to the journal if it runs out of space. No more than ae_max_jfiles may be added. If false, then
+ * no files are added and an exception will be thrown if the journal runs out of file space.
+ * \param ae_max_jfiles Upper limit of journal files for auto-expand mode. When auto_expand is true, this is the
+ * maximum total number of files allowed in the journal (original plus those added by auto-expand mode). If
+ * this number of files exist and the journal runs out of space, an exception will be thrown. This number
+ * must be greater than the num_jfiles parameter value but cannot exceed the maximum number of files for a
+ * single journal; if num_jfiles is already at its maximum value, then auto-expand will be disabled.
+ * \param jfsize_sblks The size of each journal file expressed in softblocks.
+ * \param wcache_num_pages The number of write cache pages to create.
+ * \param wcache_pgsize_sblks The size in sblks of each write cache page.
+ * \param cbp Pointer to object containing callback functions for read and write operations. May be 0 (NULL).
+ * \param prep_txn_list_ptr
+ * \param highest_rid Returns the highest rid found in the journal during recover
+ *
+ * \exception TODO
+ */
+ void recover(const u_int16_t num_jfiles, const bool auto_expand, const u_int16_t ae_max_jfiles,
+ const u_int32_t jfsize_sblks, const u_int16_t wcache_num_pages, const u_int32_t wcache_pgsize_sblks,
+ aio_callback* const cbp, const std::vector<std::string>* prep_txn_list_ptr, u_int64_t& highest_rid);
+
+ /**
+ * \brief Notification to the journal that recovery is complete and that normal operation
+ * may resume.
+ *
+ * This call notifies the journal that recovery is complete and that normal operation
+ * may resume. The read pointers are reset so that all records read as a part of recover
+ * may be re-read during normal operation. The read-only flag is then reset, allowing
+ * enqueue and dequeue operations to resume.
+ *
+ * \exception TODO
+ */
+ void recover_complete();
+
+ /**
+ * \brief Stops journal and deletes all journal files.
+ *
+ * Clear the journal directory of all journal files matching the base filename.
+ *
+ * \exception TODO
+ */
+ void delete_jrnl_files();
+
+ /**
+ * \brief Enqueue data.
+ *
+ * Enqueue data or part thereof. If a large data block is being written, then it may be
+ * enqueued in parts by setting this_data_len to the size of the data being written in this
+ * call. The total data size must be known in advance, however, as this is written into the
+ * record header on the first record write. The state of the write (i.e. how much has been
+ * written so far) is maintained in the data token dtokp. Partial writes will return in state
+ * ENQ_PART.
+ *
+ * Note that a return value of anything other than RHM_IORES_SUCCESS implies that this write
+ * operation did not complete successfully or was partially completed. The action taken under
+ * these conditions depends on the value of the return. For example, RHM_IORES_AIO_WAIT
+ * implies that all pages in the write page cache are waiting for AIO operations to return,
+ * and that the call should be remade after waiting a bit.
+ *
+ * Example: If a write of 99 kB is divided into three equal parts, then the following states
+ * and returns would characterize a successful operation:
+ * <pre>
+ * dtok. dtok. dtok.
+ * Pperation Return wstate() dsize() written() Comment
+ * -----------------+--------+--------+-------+---------+------------------------------------
+ * NONE 0 0 Value of dtok before op
+ * edr(99000, 33000) SUCCESS ENQ_PART 99000 33000 Enqueue part 1
+ * edr(99000, 33000) AIO_WAIT ENQ_PART 99000 50000 Enqueue part 2, not completed
+ * edr(99000, 33000) SUCCESS ENQ_PART 99000 66000 Enqueue part 2 again
+ * edr(99000, 33000) SUCCESS ENQ 99000 99000 Enqueue part 3
+ * </pre>
+ *
+ * \param data_buff Pointer to data to be enqueued for this enqueue operation.
+ * \param tot_data_len Total data length.
+ * \param this_data_len Amount to be written in this enqueue operation.
+ * \param dtokp Pointer to data token which contains the details of the enqueue operation.
+ * \param transient Flag indicating transient persistence (ie, ignored on recover).
+ *
+ * \exception TODO
+ */
+ iores enqueue_data_record(const void* const data_buff, const std::size_t tot_data_len,
+ const std::size_t this_data_len, data_tok* dtokp, const bool transient = false);
+
+ iores enqueue_extern_data_record(const std::size_t tot_data_len, data_tok* dtokp,
+ const bool transient = false);
+
+ /**
+ * \brief Enqueue data.
+ *
+ * \param data_buff Pointer to data to be enqueued for this enqueue operation.
+ * \param tot_data_len Total data length.
+ * \param this_data_len Amount to be written in this enqueue operation.
+ * \param dtokp Pointer to data token which contains the details of the enqueue operation.
+ * \param xid String containing xid. An empty string (i.e. length=0) will be considered
+ * non-transactional.
+ * \param transient Flag indicating transient persistence (ie, ignored on recover).
+ *
+ * \exception TODO
+ */
+ iores enqueue_txn_data_record(const void* const data_buff, const std::size_t tot_data_len,
+ const std::size_t this_data_len, data_tok* dtokp, const std::string& xid,
+ const bool transient = false);
+ iores enqueue_extern_txn_data_record(const std::size_t tot_data_len, data_tok* dtokp,
+ const std::string& xid, const bool transient = false);
+
+ /* TODO
+ **
+ * \brief Retrieve details of next record to be read without consuming the record.
+ *
+ * Retrieve information about current read record. A pointer to the data is returned, along
+ * with the data size and available data size. Data is considered "available" when the AIO
+ * operations to fill page-cache pages from disk have returned, and is ready for consumption.
+ *
+ * If <i>dsize_avail</i> &lt; <i>dsize</i>, then not all of the data is available or part of
+ * the data is in non-contiguous memory, and a subsequent call will update both the pointer
+ * and <i>dsize_avail</i> if more pages have returned from AIO.
+ *
+ * The <i>dsize_avail</i> parameter will return the amount of data from this record that is
+ * available in the page cache as contiguous memory, even if it spans page cache boundaries.
+ * However, if a record spans the end of the page cache and continues at the beginning, even
+ * if both parts are ready for consumption, then this must be divided into at least two
+ * get_data_record() operations, as the data is contained in at least two non-contiguous
+ * segments of the page cache.
+ *
+ * Once all the available data for a record is exposed, it can not be read again using
+ * this function. It must be consumed prior to getting the next record. This can be done by
+ * calling discard_data_record() or read_data_record(). However, if parameter
+ * <i>auto_discard</i> is set to <b><i>true</i></b>, then this record will be automatically
+ * consumed when the entire record has become available without having to explicitly call
+ * discard_next_data_record() or read_data_record().
+ *
+ * If the current record is an open transactional record, then it cannot be read until it is
+ * committed. If it is aborted, it can never be read. Under this condition, get_data_record()
+ * will return RHM_IORES_TXPENDING, the data pointer will be set to NULL and all data
+ * lengths will be set to 0.
+ *
+ * Example: Read a record of 30k. Assume a read page cache of 10 pages of size 10k starting
+ * at address base_ptr (page0 = base_ptr, page1 = page_ptr+10k, etc.). The first 15k of
+ * the record falls at the end of the page cache, the remaining 15k folded to the beginning.
+ * The current page (page 8) containing 5k is available, the remaining pages which contain
+ * this record are pending AIO return:
+ * <pre>
+ * call dsize
+ * no. dsize avail data ptr Return Comment
+ * ----+-----+-----+------------+--------+--------------------------------------------------
+ * 1 30k 5k base_ptr+85k SUCCESS Initial call, read first 5k
+ * 2 30k 0k base_ptr+90k AIO_WAIT AIO still pending; no further pages avail
+ * 3 30k 10k base_ptr+90k SUCCESS AIO now returned; now read till end of page cache
+ * 4 30k 15k base_ptr SUCCESS data_ptr now pointing to start of page cache
+ * </pre>
+ *
+ * \param rid Reference that returns the record ID (rid)
+ * \param dsize Reference that returns the total data size of the record data .
+ * \param dsize_avail Reference that returns the amount of the data that is available for
+ * consumption.
+ * \param data Pointer to data pointer which will point to the first byte of the next record
+ * data.
+ * \param auto_discard If <b><i>true</i></b>, automatically discard the record being read if
+ * the entire record is available (i.e. dsize == dsize_avail). Otherwise
+ * discard_next_data_record() must be explicitly called.
+ *
+ * \exception TODO
+ *
+ // *** NOT YET IMPLEMENTED ***
+ iores get_data_record(const u_int64_t& rid, const std::size_t& dsize,
+ const std::size_t& dsize_avail, const void** const data, bool auto_discard = false);
+ */
+
+ /* TODO
+ **
+ * \brief Discard (skip) next record to be read without reading or retrieving it.
+ *
+ * \exception TODO
+ *
+ // *** NOT YET IMPLEMENTED ***
+ iores discard_data_record(data_tok* const dtokp);
+ */
+
+ /**
+ * \brief Reads data from the journal. It is the responsibility of the reader to free
+ * the memory that is allocated through this call - see below for details.
+ *
+ * Reads the next non-dequeued data record from the journal.
+ *
+ * <b>Note</b> that this call allocates memory into which the data and XID are copied. It
+ * is the responsibility of the caller to free this memory. The memory for the data and
+ * XID are allocated in a single call, and the XID precedes the data in the memory space.
+ * Thus, where an XID exists, freeing the XID pointer will free both the XID and data memory.
+ * However, if an XID does not exist for the message, the XID pointer xidpp is set to NULL,
+ * and it is the data pointer datapp that must be freed. Should neither an XID nor data be
+ * present (ie an empty record), then no memory is allocated, and both pointers will be NULL.
+ * In this case, there is no need to free memory.
+ *
+ * TODO: Fix this lousy interface. The caller should NOT be required to clean up these
+ * pointers! Rather use a struct, or better still, let the data token carry the data and
+ * xid pointers and lengths, and have the data token both allocate and delete.
+ *
+ * \param datapp Pointer to pointer that will be set to point to memory allocated and
+ * containing the data. Will be set to NULL if the call fails or there is no data
+ * in the record.
+ * \param dsize Ref that will be set to the size of the data. Will be set to 0 if the call
+ * fails or if there is no data in the record.
+ * \param xidpp Pointer to pointer that will be set to point to memory allocated and
+ * containing the XID. Will be set to NULL if the call fails or there is no XID attached
+ * to this record.
+ * \param xidsize Ref that will be set to the size of the XID.
+ * \param transient Ref that will be set true if record is transient.
+ * \param external Ref that will be set true if record is external. In this case, the data
+ * pointer datapp will be set to NULL, but dsize will contain the size of the data.
+ * NOTE: If there is an xid, then xidpp must be freed.
+ * \param dtokp Pointer to data_tok instance for this data, used to track state of data
+ * through journal.
+ * \param ignore_pending_txns When false (default), if the next record to be read is locked
+ * by a pending transaction, the read fails with RHM_IORES_TXPENDING. However, if set
+ * to true, then locks are ignored. This is required for reading of the Transaction
+ * Prepared List (TPL) which may have its entries locked, but may be read from
+ * time-to-time, and needs all its records (locked and unlocked) to be available.
+ *
+ * \exception TODO
+ */
+ iores read_data_record(void** const datapp, std::size_t& dsize, void** const xidpp,
+ std::size_t& xidsize, bool& transient, bool& external, data_tok* const dtokp,
+ bool ignore_pending_txns = false);
+
+ /**
+ * \brief Dequeues (marks as no longer needed) data record in journal.
+ *
+ * Dequeues (marks as no longer needed) data record in journal. Note that it is possible
+ * to use the same data token instance used to enqueue this data; it contains the record ID
+ * needed to correctly mark this data as dequeued in the journal. Otherwise the RID of the
+ * record to be dequeued and the write state of ENQ must be manually set in a new or reset
+ * instance of data_tok.
+ *
+ * \param dtokp Pointer to data_tok instance for this data, used to track state of data
+ * through journal.
+ * \param txn_coml_commit Only used for preparedXID journal. When used for dequeueing
+ * prepared XID list items, sets whether the complete() was called in commit or abort
+ * mode.
+ *
+ * \exception TODO
+ */
+ iores dequeue_data_record(data_tok* const dtokp, const bool txn_coml_commit = false);
+
+ /**
+ * \brief Dequeues (marks as no longer needed) data record in journal.
+ *
+ * Dequeues (marks as no longer needed) data record in journal as part of a transaction.
+ * Note that it is possible to use the same data token instance used to enqueue this data;
+ * it contains the RID needed to correctly mark this data as dequeued in the journal.
+ * Otherwise the RID of the record to be dequeued and the write state of ENQ must be
+ * manually set in a new or reset instance of data_tok.
+ *
+ * \param dtokp Pointer to data_tok instance for this data, used to track state of data
+ * through journal.
+ * \param xid String containing xid. An empty string (i.e. length=0) will be considered
+ * non-transactional.
+ * \param txn_coml_commit Only used for preparedXID journal. When used for dequeueing
+ * prepared XID list items, sets whether the complete() was called in commit or abort
+ * mode.
+ *
+ * \exception TODO
+ */
+ iores dequeue_txn_data_record(data_tok* const dtokp, const std::string& xid, const bool txn_coml_commit = false);
+
+ /**
+ * \brief Abort the transaction for all records enqueued or dequeued with the matching xid.
+ *
+ * Abort the transaction for all records enqueued with the matching xid. All enqueued records
+ * are effectively deleted from the journal, and can not be read. All dequeued records remain
+ * as though they had never been dequeued.
+ *
+ * \param dtokp Pointer to data_tok instance for this data, used to track state of data
+ * through journal.
+ * \param xid String containing xid.
+ *
+ * \exception TODO
+ */
+ iores txn_abort(data_tok* const dtokp, const std::string& xid);
+
+ /**
+ * \brief Commit the transaction for all records enqueued or dequeued with the matching xid.
+ *
+ * Commit the transaction for all records enqueued with the matching xid. All enqueued
+ * records are effectively released for reading and dequeueing. All dequeued records are
+ * removed and can no longer be accessed.
+ *
+ * \param dtokp Pointer to data_tok instance for this data, used to track state of data
+ * through journal.
+ * \param xid String containing xid.
+ *
+ * \exception TODO
+ */
+ iores txn_commit(data_tok* const dtokp, const std::string& xid);
+
+ /**
+ * \brief Check whether all the enqueue records for the given xid have reached disk.
+ *
+ * \param xid String containing xid.
+ *
+ * \exception TODO
+ */
+ bool is_txn_synced(const std::string& xid);
+
+ /**
+ * \brief Forces a check for returned AIO write events.
+ *
+ * Forces a check for returned AIO write events. This is normally performed by enqueue() and
+ * dequeue() operations, but if these operations cease, then this call needs to be made to
+ * force the processing of any outstanding AIO operations.
+ */
+ int32_t get_wr_events(timespec* const timeout);
+
+ /**
+ * \brief Forces a check for returned AIO read events.
+ *
+ * Forces a check for returned AIO read events. This is normally performed by read_data()
+ * operations, but if these operations cease, then this call needs to be made to force the
+ * processing of any outstanding AIO operations.
+ */
+ int32_t get_rd_events(timespec* const timeout);
+
+ /**
+ * \brief Stop the journal from accepting any further requests to read or write data.
+ *
+ * This operation is used to stop the journal. This is the normal mechanism for bringing the
+ * journal to an orderly stop. Any outstanding AIO operations or partially written pages in
+ * the write page cache will by flushed and will complete.
+ *
+ * <b>Note:</b> The journal cannot be restarted without either initializing it or restoring
+ * it.
+ *
+ * \param block_till_aio_cmpl If true, will block the thread while waiting for all
+ * outstanding AIO operations to complete.
+ */
+ void stop(const bool block_till_aio_cmpl = false);
+
+ /**
+ * \brief Force a flush of the write page cache, creating a single AIO write operation.
+ */
+ iores flush(const bool block_till_aio_cmpl = false);
+
+ inline u_int32_t get_enq_cnt() const { return _emap.size(); }
+
+ inline u_int32_t get_wr_aio_evt_rem() const { slock l(_wr_mutex); return _wmgr.get_aio_evt_rem(); }
+
+ inline u_int32_t get_rd_aio_evt_rem() const { return _rmgr.get_aio_evt_rem(); }
+
+ inline u_int32_t get_wr_outstanding_aio_dblks() const
+ { return _wrfc.aio_outstanding_dblks(); }
+
+ inline u_int32_t get_wr_outstanding_aio_dblks(u_int16_t lfid) const
+ { return _lpmgr.get_fcntlp(lfid)->wr_aio_outstanding_dblks(); }
+
+ inline u_int32_t get_rd_outstanding_aio_dblks() const
+ { return _rrfc.aio_outstanding_dblks(); }
+
+ inline u_int32_t get_rd_outstanding_aio_dblks(u_int16_t lfid) const
+ { return _lpmgr.get_fcntlp(lfid)->rd_aio_outstanding_dblks(); }
+
+ inline u_int16_t get_rd_fid() const { return _rrfc.index(); }
+ inline u_int16_t get_wr_fid() const { return _wrfc.index(); }
+ u_int16_t get_earliest_fid();
+
+ /**
+ * \brief Check if a particular rid is enqueued. Note that this function will return
+ * false if the rid is transactionally enqueued and is not committed, or if it is
+ * locked (i.e. transactionally dequeued, but the dequeue has not been committed).
+ */
+ inline bool is_enqueued(const u_int64_t rid, bool ignore_lock = false)
+ { return _emap.is_enqueued(rid, ignore_lock); }
+ inline bool is_locked(const u_int64_t rid)
+ { if (_emap.is_enqueued(rid, true) < enq_map::EMAP_OK) return false; return _emap.is_locked(rid) == enq_map::EMAP_TRUE; }
+ inline void enq_rid_list(std::vector<u_int64_t>& rids) { _emap.rid_list(rids); }
+ inline void enq_xid_list(std::vector<std::string>& xids) { _tmap.xid_list(xids); }
+ inline u_int32_t get_open_txn_cnt() const { return _tmap.size(); }
+ // TODO Make this a const, but txn_map must support const first.
+ inline txn_map& get_txn_map() { return _tmap; }
+
+ /**
+ * \brief Check if the journal is stopped.
+ *
+ * \return <b><i>true</i></b> if the jouranl is stopped;
+ * <b><i>false</i></b> otherwise.
+ */
+ inline bool is_stopped() { return _stop_flag; }
+
+ /**
+ * \brief Check if the journal is ready to read and write data.
+ *
+ * Checks if the journal is ready to read and write data. This function will return
+ * <b><i>true</i></b> if the journal has been either initialized or restored, and the stop()
+ * function has not been called since the initialization.
+ *
+ * Note that the journal may also be stopped if an internal error occurs (such as running out
+ * of data journal file space).
+ *
+ * \return <b><i>true</i></b> if the journal is ready to read and write data;
+ * <b><i>false</i></b> otherwise.
+ */
+ inline bool is_ready() const { return _init_flag && !_stop_flag; }
+
+ inline bool is_read_only() const { return _readonly_flag; }
+
+ /**
+ * \brief Get the journal directory.
+ *
+ * This returns the journal directory as set during initialization. This is the directory
+ * into which the journal files will be written.
+ */
+ inline const std::string& dirname() const { return _jdir.dirname(); }
+
+ /**
+ * \brief Get the journal base filename.
+ *
+ * Get the journal base filename as set during initialization. This is the prefix used in all
+ * journal files of this instance. Note that if more than one instance of the journal shares
+ * the same directory, their base filenames <b>MUST</b> be different or else the instances
+ * will overwrite one another.
+ */
+ inline const std::string& base_filename() const { return _base_filename; }
+
+ inline u_int16_t num_jfiles() const { return _lpmgr.num_jfiles(); }
+
+ inline fcntl* get_fcntlp(const u_int16_t lfid) const { return _lpmgr.get_fcntlp(lfid); }
+
+ inline u_int32_t jfsize_sblks() const { return _jfsize_sblks; }
+
+ // Logging
+ virtual void log(log_level level, const std::string& log_stmt) const;
+ virtual void log(log_level level, const char* const log_stmt) const;
+
+ // FIXME these are _rmgr to _wmgr interactions, remove when _rmgr contains ref to _wmgr:
+ void chk_wr_frot();
+ inline u_int32_t unflushed_dblks() { return _wmgr.unflushed_dblks(); }
+ void fhdr_wr_sync(const u_int16_t lid);
+ inline u_int32_t wr_subm_cnt_dblks(const u_int16_t lfid) const { return _lpmgr.get_fcntlp(lfid)->wr_subm_cnt_dblks(); }
+
+ // Management instrumentation callbacks
+ inline virtual void instr_incr_outstanding_aio_cnt() {}
+ inline virtual void instr_decr_outstanding_aio_cnt() {}
+
+ /**
+ * /brief Static function for creating new fcntl objects for use with obj_arr.
+ */
+ static fcntl* new_fcntl(jcntl* const jcp, const u_int16_t lid, const u_int16_t fid, const rcvdat* const rdp);
+
+ protected:
+ static bool _init;
+ static bool init_statics();
+
+ /**
+ * \brief Check status of journal before allowing write operations.
+ */
+ void check_wstatus(const char* fn_name) const;
+
+ /**
+ * \brief Check status of journal before allowing read operations.
+ */
+ void check_rstatus(const char* fn_name) const;
+
+ /**
+ * \brief Write info file &lt;basefilename&gt;.jinf to disk
+ */
+ void write_infofile() const;
+
+ /**
+ * \brief Call that blocks while waiting for all outstanding AIOs to complete
+ */
+ void aio_cmpl_wait();
+
+ /**
+ * \brief Call that blocks until at least one message returns; used to wait for
+ * AIO wait conditions to clear.
+ */
+ bool handle_aio_wait(const iores res, iores& resout, const data_tok* dtp);
+
+ /**
+ * \brief Analyze journal for recovery.
+ */
+ void rcvr_janalyze(rcvdat& rd, const std::vector<std::string>* prep_txn_list_ptr);
+
+ bool rcvr_get_next_record(u_int16_t& fid, std::ifstream* ifsp, bool& lowi, rcvdat& rd);
+
+ bool decode(jrec& rec, u_int16_t& fid, std::ifstream* ifsp, std::size_t& cum_size_read,
+ rec_hdr& h, bool& lowi, rcvdat& rd, std::streampos& rec_offset);
+
+ bool jfile_cycle(u_int16_t& fid, std::ifstream* ifsp, bool& lowi, rcvdat& rd,
+ const bool jump_fro);
+
+ bool check_owi(const u_int16_t fid, rec_hdr& h, bool& lowi, rcvdat& rd,
+ std::streampos& read_pos);
+
+ void check_journal_alignment(const u_int16_t fid, std::streampos& rec_offset, rcvdat& rd);
+ };
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_JCNTL_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/jdir.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/jdir.cpp
new file mode 100644
index 0000000000..a874c6c945
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/jdir.cpp
@@ -0,0 +1,463 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file jdir.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::jdir (journal data
+ * directory), used for controlling and manipulating journal data
+ * direcories and files. See comments in file jdir.h for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#include "qpid/legacystore/jrnl/jdir.h"
+
+#include <cstdlib>
+#include <cstring>
+#include <cerrno>
+#include <iomanip>
+#include "qpid/legacystore/jrnl/jcfg.h"
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include "qpid/legacystore/jrnl/jexception.h"
+#include <sstream>
+#include <sys/stat.h>
+#include <unistd.h>
+
+namespace mrg
+{
+namespace journal
+{
+
+jdir::jdir(const std::string& dirname, const std::string& _base_filename):
+ _dirname(dirname),
+ _base_filename(_base_filename)
+{}
+
+jdir::~jdir()
+{}
+
+// === create_dir ===
+
+void
+jdir::create_dir()
+{
+ create_dir(_dirname);
+}
+
+
+void
+jdir::create_dir(const char* dirname)
+{
+ create_dir(std::string(dirname));
+}
+
+
+void
+jdir::create_dir(const std::string& dirname)
+{
+ std::size_t fdp = dirname.find_last_of('/');
+ if (fdp != std::string::npos)
+ {
+ std::string parent_dir = dirname.substr(0, fdp);
+ if (!exists(parent_dir))
+ create_dir(parent_dir);
+ }
+ if (::mkdir(dirname.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH))
+ {
+ if (errno != EEXIST) // Dir exists, ignore
+ {
+ std::ostringstream oss;
+ oss << "dir=\"" << dirname << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_MKDIR, oss.str(), "jdir", "create_dir");
+ }
+ }
+}
+
+
+// === clear_dir ===
+
+void
+jdir::clear_dir(const bool create_flag)
+{
+ clear_dir(_dirname, _base_filename, create_flag);
+}
+
+void
+jdir::clear_dir(const char* dirname, const char* base_filename, const bool create_flag)
+{
+ clear_dir(std::string(dirname), std::string(base_filename), create_flag);
+}
+
+
+void
+jdir::clear_dir(const std::string& dirname, const std::string&
+#ifndef RHM_JOWRITE
+ base_filename
+#endif
+ , const bool create_flag)
+{
+ DIR* dir = ::opendir(dirname.c_str());
+ if (!dir)
+ {
+ if (errno == 2 && create_flag) // ENOENT (No such file or dir)
+ {
+ create_dir(dirname);
+ return;
+ }
+ std::ostringstream oss;
+ oss << "dir=\"" << dirname << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_OPENDIR, oss.str(), "jdir", "clear_dir");
+ }
+#ifndef RHM_JOWRITE
+ struct dirent* entry;
+ bool found = false;
+ std::string bak_dir;
+ while ((entry = ::readdir(dir)) != 0)
+ {
+ // Ignore . and ..
+ if (std::strcmp(entry->d_name, ".") != 0 && std::strcmp(entry->d_name, "..") != 0)
+ {
+ if (std::strlen(entry->d_name) > base_filename.size())
+ {
+ if (std::strncmp(entry->d_name, base_filename.c_str(), base_filename.size()) == 0)
+ {
+ if (!found)
+ {
+ bak_dir = create_bak_dir(dirname, base_filename);
+ found = true;
+ }
+ std::ostringstream oldname;
+ oldname << dirname << "/" << entry->d_name;
+ std::ostringstream newname;
+ newname << bak_dir << "/" << entry->d_name;
+ if (::rename(oldname.str().c_str(), newname.str().c_str()))
+ {
+ ::closedir(dir);
+ std::ostringstream oss;
+ oss << "file=\"" << oldname.str() << "\" dest=\"" <<
+ newname.str() << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_FMOVE, oss.str(), "jdir", "clear_dir");
+ }
+ }
+ }
+ }
+ }
+// FIXME: Find out why this fails with false alarms/errors from time to time...
+// While commented out, there is no error capture from reading dir entries.
+// check_err(errno, dir, dirname, "clear_dir");
+#endif
+ close_dir(dir, dirname, "clear_dir");
+}
+
+// === push_down ===
+
+std::string
+jdir::push_down(const std::string& dirname, const std::string& target_dir, const std::string& bak_dir_base)
+{
+ std::string bak_dir_name = create_bak_dir(dirname, bak_dir_base);
+
+ DIR* dir = ::opendir(dirname.c_str());
+ if (!dir)
+ {
+ std::ostringstream oss;
+ oss << "dir=\"" << dirname << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_OPENDIR, oss.str(), "jdir", "push_down");
+ }
+ // Copy contents of targetDirName into bak dir
+ struct dirent* entry;
+ while ((entry = ::readdir(dir)) != 0)
+ {
+ // Search for targetDirName in storeDirName
+ if (std::strcmp(entry->d_name, target_dir.c_str()) == 0)
+ {
+ std::ostringstream oldname;
+ oldname << dirname << "/" << target_dir;
+ std::ostringstream newname;
+ newname << bak_dir_name << "/" << target_dir;
+ if (::rename(oldname.str().c_str(), newname.str().c_str()))
+ {
+ ::closedir(dir);
+ std::ostringstream oss;
+ oss << "file=\"" << oldname.str() << "\" dest=\"" << newname.str() << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_FMOVE, oss.str(), "jdir", "push_down");
+ }
+ break;
+ }
+ }
+ close_dir(dir, dirname, "push_down");
+ return bak_dir_name;
+}
+
+// === verify_dir ===
+
+void
+jdir::verify_dir()
+{
+ verify_dir(_dirname, _base_filename);
+}
+
+void
+jdir::verify_dir(const char* dirname, const char* base_filename)
+{
+ verify_dir(std::string(dirname), std::string(base_filename));
+}
+
+
+void
+jdir::verify_dir(const std::string& dirname, const std::string& base_filename)
+{
+ if (!is_dir(dirname))
+ {
+ std::ostringstream oss;
+ oss << "dir=\"" << dirname << "\"";
+ throw jexception(jerrno::JERR_JDIR_NOTDIR, oss.str(), "jdir", "verify_dir");
+ }
+
+ // Read jinf file, then verify all journal files are present
+ jinf ji(dirname + "/" + base_filename + "." + JRNL_INFO_EXTENSION, true);
+ for (u_int16_t fnum=0; fnum < ji.num_jfiles(); fnum++)
+ {
+ std::ostringstream oss;
+ oss << dirname << "/" << base_filename << ".";
+ oss << std::setw(4) << std::setfill('0') << std::hex << fnum;
+ oss << "." << JRNL_DATA_EXTENSION;
+ if (!exists(oss.str()))
+ throw jexception(jerrno::JERR_JDIR_NOSUCHFILE, oss.str(), "jdir", "verify_dir");
+ }
+}
+
+
+// === delete_dir ===
+
+void
+jdir::delete_dir(bool children_only)
+{
+ delete_dir(_dirname, children_only);
+}
+
+void
+jdir::delete_dir(const char* dirname, bool children_only)
+{
+ delete_dir(std::string(dirname), children_only);
+}
+
+void
+jdir::delete_dir(const std::string& dirname, bool children_only)
+{
+ struct dirent* entry;
+ struct stat s;
+ DIR* dir = ::opendir(dirname.c_str());
+ if (!dir)
+ {
+ if (errno == ENOENT) // dir does not exist.
+ return;
+
+ std::ostringstream oss;
+ oss << "dir=\"" << dirname << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_OPENDIR, oss.str(), "jdir", "delete_dir");
+ }
+ else
+ {
+ while ((entry = ::readdir(dir)) != 0)
+ {
+ // Ignore . and ..
+ if (std::strcmp(entry->d_name, ".") != 0 && std::strcmp(entry->d_name, "..") != 0)
+ {
+ std::string full_name(dirname + "/" + entry->d_name);
+ if (::lstat(full_name.c_str(), &s))
+ {
+ ::closedir(dir);
+ std::ostringstream oss;
+ oss << "stat: file=\"" << full_name << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_STAT, oss.str(), "jdir", "delete_dir");
+ }
+ if (S_ISREG(s.st_mode) || S_ISLNK(s.st_mode)) // This is a file or slink
+ {
+ if(::unlink(full_name.c_str()))
+ {
+ ::closedir(dir);
+ std::ostringstream oss;
+ oss << "unlink: file=\"" << entry->d_name << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_UNLINK, oss.str(), "jdir", "delete_dir");
+ }
+ }
+ else if (S_ISDIR(s.st_mode)) // This is a dir
+ {
+ delete_dir(full_name);
+ }
+ else // all other types, throw up!
+ {
+ ::closedir(dir);
+ std::ostringstream oss;
+ oss << "file=\"" << entry->d_name << "\" is not a dir, file or slink.";
+ oss << " (mode=0x" << std::hex << s.st_mode << std::dec << ")";
+ throw jexception(jerrno::JERR_JDIR_BADFTYPE, oss.str(), "jdir", "delete_dir");
+ }
+ }
+ }
+
+// FIXME: Find out why this fails with false alarms/errors from time to time...
+// While commented out, there is no error capture from reading dir entries.
+// check_err(errno, dir, dirname, "delete_dir");
+ }
+ // Now dir is empty, close and delete it
+ close_dir(dir, dirname, "delete_dir");
+
+ if (!children_only)
+ if (::rmdir(dirname.c_str()))
+ {
+ std::ostringstream oss;
+ oss << "dir=\"" << dirname << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_RMDIR, oss.str(), "jdir", "delete_dir");
+ }
+}
+
+
+std::string
+jdir::create_bak_dir(const std::string& dirname, const std::string& base_filename)
+{
+ DIR* dir = ::opendir(dirname.c_str());
+ long dir_num = 0L;
+ if (!dir)
+ {
+ std::ostringstream oss;
+ oss << "dir=\"" << dirname << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_OPENDIR, oss.str(), "jdir", "create_bak_dir");
+ }
+ struct dirent* entry;
+ while ((entry = ::readdir(dir)) != 0)
+ {
+ // Ignore . and ..
+ if (std::strcmp(entry->d_name, ".") != 0 && std::strcmp(entry->d_name, "..") != 0)
+ {
+ if (std::strlen(entry->d_name) == base_filename.size() + 10) // Format: basename.bak.XXXX
+ {
+ std::ostringstream oss;
+ oss << "_" << base_filename << ".bak.";
+ if (std::strncmp(entry->d_name, oss.str().c_str(), base_filename.size() + 6) == 0)
+ {
+ long this_dir_num = std::strtol(entry->d_name + base_filename.size() + 6, 0, 16);
+ if (this_dir_num > dir_num)
+ dir_num = this_dir_num;
+ }
+ }
+ }
+ }
+// FIXME: Find out why this fails with false alarms/errors from time to time...
+// While commented out, there is no error capture from reading dir entries.
+// check_err(errno, dir, dirname, "create_bak_dir");
+ close_dir(dir, dirname, "create_bak_dir");
+
+ std::ostringstream dn;
+ dn << dirname << "/_" << base_filename << ".bak." << std::hex << std::setw(4) <<
+ std::setfill('0') << ++dir_num;
+ if (::mkdir(dn.str().c_str(), S_IRWXU | S_IRWXG | S_IROTH))
+ {
+ std::ostringstream oss;
+ oss << "dir=\"" << dn.str() << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_MKDIR, oss.str(), "jdir", "create_bak_dir");
+ }
+ return std::string(dn.str());
+}
+
+bool
+jdir::is_dir(const char* name)
+{
+ struct stat s;
+ if (::stat(name, &s))
+ {
+ std::ostringstream oss;
+ oss << "file=\"" << name << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_STAT, oss.str(), "jdir", "is_dir");
+ }
+ return S_ISDIR(s.st_mode);
+}
+
+bool
+jdir::is_dir(const std::string& name)
+{
+ return is_dir(name.c_str());
+}
+
+bool
+jdir::exists(const char* name)
+{
+ struct stat s;
+ if (::stat(name, &s))
+ {
+ if (errno == ENOENT) // No such dir or file
+ return false;
+ // Throw for any other condition
+ std::ostringstream oss;
+ oss << "file=\"" << name << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_STAT, oss.str(), "jdir", "exists");
+ }
+ return true;
+}
+
+bool
+jdir::exists(const std::string& name)
+{
+ return exists(name.c_str());
+}
+
+void
+jdir::check_err(const int err_num, DIR* dir, const std::string& dir_name, const std::string& fn_name)
+{
+ if (err_num)
+ {
+ std::ostringstream oss;
+ oss << "dir=\"" << dir_name << "\"" << FORMAT_SYSERR(err_num);
+ ::closedir(dir); // Try to close, it makes no sense to trap errors here...
+ throw jexception(jerrno::JERR_JDIR_READDIR, oss.str(), "jdir", fn_name);
+ }
+}
+
+void
+jdir::close_dir(DIR* dir, const std::string& dir_name, const std::string& fn_name)
+{
+ if (::closedir(dir))
+ {
+ std::ostringstream oss;
+ oss << "dir=\"" << dir_name << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_CLOSEDIR, oss.str(), "jdir", fn_name);
+ }
+}
+
+std::ostream&
+operator<<(std::ostream& os, const jdir& jdir)
+{
+ os << jdir._dirname;
+ return os;
+}
+
+std::ostream&
+operator<<(std::ostream& os, const jdir* jdirPtr)
+{
+ os << jdirPtr->_dirname;
+ return os;
+}
+
+} // namespace journal
+} // namespace mrg
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/jdir.h b/qpid/cpp/src/qpid/legacystore/jrnl/jdir.h
new file mode 100644
index 0000000000..e129b794d6
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/jdir.h
@@ -0,0 +1,379 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file jdir.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::jdir (%journal data
+ * directory), used for controlling and manipulating %journal data
+ * directories and files. See class documentation for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_JDIR_H
+#define QPID_LEGACYSTORE_JRNL_JDIR_H
+
+namespace mrg
+{
+namespace journal
+{
+class jdir;
+}
+}
+
+#include "qpid/legacystore/jrnl/jinf.h"
+#include <dirent.h>
+
+namespace mrg
+{
+namespace journal
+{
+
+ /**
+ * \class jdir
+ * \brief Class to manage the %journal directory
+ */
+ class jdir
+ {
+ private:
+ std::string _dirname;
+ std::string _base_filename;
+
+ public:
+
+ /**
+ * \brief Sole constructor
+ *
+ * \param dirname Name of directory to be managed.
+ * \param base_filename Filename root used in the creation of %journal files
+ * and sub-directories.
+ */
+ jdir(const std::string& dirname, const std::string& base_filename);
+
+ virtual ~jdir();
+
+
+ /**
+ * \brief Create %journal directory as set in the dirname parameter of the constructor.
+ * Recursive creation is supported.
+ *
+ * \exception jerrno::JERR_JDIR_MKDIR The creation of dirname failed.
+ */
+ void create_dir();
+
+ /**
+ * \brief Static function to create a directory. Recursive creation is supported.
+ *
+ * \param dirname C-string containing name of directory.
+ *
+ * \exception jerrno::JERR_JDIR_MKDIR The creation of dirname failed.
+ */
+ static void create_dir(const char* dirname);
+
+ /**
+ * \brief Static function to create a directory. Recursive creation is supported.
+ *
+ * \param dirname String containing name of directory.
+ *
+ * \exception jerrno::JERR_JDIR_MKDIR The creation of dirname failed.
+ */
+ static void create_dir(const std::string& dirname);
+
+
+ /**
+ * \brief Clear the %journal directory of files matching the base filename
+ * by moving them into a subdirectory. This fn uses the dirname and base_filename
+ * that were set on construction.
+ *
+ * \param create_flag If set, create dirname if it is non-existent, otherwise throw
+ * exception.
+ *
+ * \exception jerrno::JERR_JDIR_OPENDIR The %journal directory could not be opened.
+ * \exception jerrno::JERR_JDIR_FMOVE Moving the files from the %journal directory to the created backup
+ * directory failed.
+ * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed.
+ */
+ void clear_dir(const bool create_flag = true);
+
+ /**
+ * \brief Clear the directory dirname of %journal files matching base_filename
+ * by moving them into a subdirectory.
+ *
+ * \param dirname C-string containing name of %journal directory.
+ * \param base_filename C-string containing base filename of %journal files to be matched
+ * for moving into subdirectory.
+ * \param create_flag If set, create dirname if it is non-existent, otherwise throw
+ * exception
+ *
+ * \exception jerrno::JERR_JDIR_OPENDIR The %journal directory could not be opened.
+ * \exception jerrno::JERR_JDIR_FMOVE Moving the files from the %journal directory to the created backup
+ * directory failed.
+ * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed.
+ */
+ static void clear_dir(const char* dirname, const char* base_filename,
+ const bool create_flag = true);
+
+ /**
+ * \brief Clear the directory dirname of %journal files matching base_filename
+ * by moving them into a subdirectory.
+ *
+ * \param dirname String containing name of %journal directory.
+ * \param base_filename String containing base filename of %journal files to be matched
+ * for moving into subdirectory.
+ * \param create_flag If set, create dirname if it is non-existent, otherwise throw
+ * exception
+ *
+ * \exception jerrno::JERR_JDIR_OPENDIR The %journal directory could not be opened.
+ * \exception jerrno::JERR_JDIR_FMOVE Moving the files from the %journal directory to the created backup
+ * directory failed.
+ * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed.
+ */
+ static void clear_dir(const std::string& dirname, const std::string& base_filename,
+ const bool create_flag = true);
+
+
+
+ /**
+ * \brief Move (push down) the directory target_dir located in directory dirname into a backup directory
+ * named _bak_dir_base.XXXX (note prepended underscore), where XXXX is an increasing hex serial number
+ * starting at 0000.
+ *
+ * \param dirname Full path to directory containing directory to be pushed down.
+ * \param target_dir Name of directory in dirname to be pushed down.
+ * \param bak_dir_base Base name for backup directory to be created in dirname, into which target_dir will be moved.
+ * \return Name of backup dir into which target_dir was pushed.
+ */
+ static std::string push_down(const std::string& dirname, const std::string& target_dir, const std::string& bak_dir_base);
+
+
+ /**
+ * \brief Verify that dirname is a valid %journal directory.
+ *
+ * The validation reads the .%jinf file, and using this information verifies that all the expected %journal
+ * (.jdat) files are present.
+ *
+ * \exception jerrno::JERR_JDIR_NOTDIR dirname is not a directory
+ * \exception jerrno::JERR_JDIR_STAT Could not stat dirname
+ * \exception jerrno::JERR__FILEIO Error reading %jinf file
+ * \exception jerrno::JERR_JINF_CVALIDFAIL Error validating %jinf file
+ * \exception jerrno::JERR_JDIR_NOSUCHFILE Expected jdat file is missing
+ */
+ void verify_dir();
+
+ /**
+ * \brief Verify that dirname is a valid %journal directory.
+ *
+ * The validation reads the .%jinf file, and using this information verifies that all the expected %journal
+ * (.jdat) files are present.
+ *
+ * \param dirname C-string containing name of %journal directory.
+ * \param base_filename C-string containing base filename of %journal files to be matched for moving into sub-directory.
+ *
+ * \exception jerrno::JERR_JDIR_NOTDIR dirname is not a directory
+ * \exception jerrno::JERR_JDIR_STAT Could not stat dirname
+ * \exception jerrno::JERR__FILEIO Error reading %jinf file
+ * \exception jerrno::JERR_JINF_CVALIDFAIL Error validating %jinf file
+ * \exception jerrno::JERR_JDIR_NOSUCHFILE Expected jdat file is missing
+ */
+ static void verify_dir(const char* dirname, const char* base_filename);
+
+ /**
+ * \brief Verify that dirname is a valid %journal directory.
+ *
+ * The validation reads the .%jinf file, and using this information verifies that all the expected %journal
+ * (.jdat) files are present.
+ *
+ * \param dirname String containing name of %journal directory.
+ * \param base_filename String containing base filename of %journal files to be matched for moving into sub-directory.
+ *
+ * \exception jerrno::JERR_JDIR_NOTDIR dirname is not a directory
+ * \exception jerrno::JERR_JDIR_STAT Could not stat dirname
+ * \exception jerrno::JERR__FILEIO Error reading %jinf file
+ * \exception jerrno::JERR_JINF_CVALIDFAIL Error validating %jinf file
+ * \exception jerrno::JERR_JDIR_NOSUCHFILE Expected jdat file is missing
+ */
+ static void verify_dir(const std::string& dirname, const std::string& base_filename);
+
+ /**
+ * \brief Delete the %journal directory and all files and sub--directories that it may
+ * contain. This is equivilent of rm -rf.
+ *
+ * FIXME: links are not handled correctly.
+ *
+ * \param children_only If true, delete only children of dirname, but leave dirname itself.
+ *
+ * \exception jerrno::JERR_JDIR_OPENDIR The %journal directory could not be opened.
+ * \exception jerrno::JERR_JDIR_STAT Could not stat dirname.
+ * \exception jerrno::JERR_JDIR_UNLINK A file could not be deleted.
+ * \exception jerrno::JERR_JDIR_BADFTYPE A dir entry is neiter a file nor a dir.
+ * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed.
+ * \exception jerrno::JERR_JDIR_RMDIR A directory could not be deleted.
+ */
+ void delete_dir(bool children_only = false );
+
+ /**
+ * \brief Delete the %journal directory and all files and sub--directories that it may
+ * contain. This is equivilent of rm -rf.
+ *
+ * FIXME: links are not handled correctly.
+ *
+ * \param dirname C-string containing name of directory to be deleted.
+ * \param children_only If true, delete only children of dirname, but leave dirname itself.
+ *
+ * \exception jerrno::JERR_JDIR_OPENDIR The %journal directory could not be opened.
+ * \exception jerrno::JERR_JDIR_STAT Could not stat dirname.
+ * \exception jerrno::JERR_JDIR_UNLINK A file could not be deleted.
+ * \exception jerrno::JERR_JDIR_BADFTYPE A dir entry is neiter a file nor a dir.
+ * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed.
+ * \exception jerrno::JERR_JDIR_RMDIR A directory could not be deleted.
+ */
+ static void delete_dir(const char* dirname, bool children_only = false);
+
+ /**
+ * \brief Delete the %journal directory and all files and sub--directories that it may
+ * contain. This is equivilent of rm -rf.
+ *
+ * FIXME: links are not handled correctly.
+ *
+ * \param dirname String containing name of directory to be deleted.
+ * \param children_only If true, delete only children of dirname, but leave dirname itself.
+ *
+ * \exception jerrno::JERR_JDIR_OPENDIR The %journal directory could not be opened.
+ * \exception jerrno::JERR_JDIR_STAT Could not stat dirname.
+ * \exception jerrno::JERR_JDIR_UNLINK A file could not be deleted.
+ * \exception jerrno::JERR_JDIR_BADFTYPE A dir entry is neiter a file nor a dir.
+ * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed.
+ * \exception jerrno::JERR_JDIR_RMDIR A directory could not be deleted.
+ */
+ static void delete_dir(const std::string& dirname, bool children_only = false);
+
+ /**
+ * \brief Create bakup directory that is next in sequence and move all %journal files
+ * matching base_filename into it.
+ *
+ * In directory dirname, search for existing backup directory using pattern
+ * "_basename.bak.XXXX" where XXXX is a hexadecimal sequence, and create next directory
+ * based on highest number found. Move all %journal files which match the base_fileaname
+ * parameter into this new backup directory.
+ *
+ * \param dirname String containing name of %journal directory.
+ * \param base_filename String containing base filename of %journal files to be matched
+ * for moving into subdirectory.
+ *
+ * \exception jerrno::JERR_JDIR_OPENDIR The %journal directory could not be opened.
+ * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed.
+ * \exception jerrno::JERR_JDIR_MKDIR The backup directory could not be deleted.
+ */
+ static std::string create_bak_dir(const std::string& dirname,
+ const std::string& base_filename);
+
+ /**
+ * \brief Return the directory name as a string.
+ */
+ inline const std::string& dirname() const { return _dirname; }
+
+ /**
+ * \brief Return the %journal base filename name as a string.
+ */
+ inline const std::string& base_filename() const { return _base_filename; }
+
+ /**
+ * \brief Test whether the named file is a directory.
+ *
+ * \param name Name of file to be tested.
+ * \return <b><i>true</i></b> if the named file is a directory; <b><i>false</i></b>
+ * otherwise.
+ * \exception jerrno::JERR_JDIR_STAT Could not stat name.
+ */
+ static bool is_dir(const char* name);
+
+ /**
+ * \brief Test whether the named file is a directory.
+ *
+ * \param name Name of file to be tested.
+ * \return <b><i>true</i></b> if the named file is a directory; <b><i>false</i></b>
+ * otherwise.
+ * \exception jerrno::JERR_JDIR_STAT Could not stat name.
+ */
+ static bool is_dir(const std::string& name);
+
+
+ /**
+ * \brief Test whether the named entity exists on the filesystem.
+ *
+ * If stat() fails with error ENOENT, then this will return <b><i>false</i></b>. If
+ * stat() succeeds, then <b><i>true</i></b> is returned, irrespective of the file type.
+ * If stat() fails with any other error, an exception is thrown.
+ *
+ * \param name Name of entity to be tested.
+ * \return <b><i>true</i></b> if the named entity exists; <b><i>false</i></b>
+ * otherwise.
+ * \exception jerrno::JERR_JDIR_STAT Could not stat name.
+ */
+ static bool exists(const char* name);
+
+ /**
+ * \brief Test whether the named entity exists on the filesystem.
+ *
+ * If stat() fails with error ENOENT, then this will return <b><i>false</i></b>. If
+ * stat() succeeds, then <b><i>true</i></b> is returned, irrespective of the file type.
+ * If stat() fails with any other error, an exception is thrown.
+ *
+ * \param name Name of entity to be tested.
+ * \return <b><i>true</i></b> if the named entity exists; <b><i>false</i></b>
+ * otherwise.
+ * \exception jerrno::JERR_JDIR_STAT Could not stat name.
+ */
+ static bool exists(const std::string& name);
+
+ /**
+ * \brief Stream operator
+ */
+ friend std::ostream& operator<<(std::ostream& os, const jdir& jdir);
+
+ /**
+ * \brief Stream operator
+ */
+ friend std::ostream& operator<<(std::ostream& os, const jdir* jdirPtr);
+
+ private:
+ /**
+ * \brief Check for error, if non-zero close DIR handle and throw JERR_JDIR_READDIR
+ *
+ * \exception jerrno::JERR_JDIR_READDIR Error while reading contents of dir.
+ */
+ static void check_err(const int err_num, DIR* dir, const std::string& dir_name, const std::string& fn_name);
+
+ /**
+ * \brief Close a DIR handle, throw JERR_JDIR_CLOSEDIR if error occurs during close
+ *
+ * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed.
+ */
+ static void close_dir(DIR* dir, const std::string& dir_name, const std::string& fn_name);
+ };
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_JDIR_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/jerrno.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/jerrno.cpp
new file mode 100644
index 0000000000..4962ce63ab
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/jerrno.cpp
@@ -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.
+ *
+ */
+
+/**
+ * \file jerrno.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::jerrno (journal error
+ * codes). See comments in file jerrno.h for details.
+ *
+ * See file jerrno.h for class details.
+ *
+ * \author Kim van der Riet
+ */
+
+#include "qpid/legacystore/jrnl/jerrno.h"
+
+namespace mrg
+{
+namespace journal
+{
+
+std::map<u_int32_t, const char*> jerrno::_err_map;
+std::map<u_int32_t, const char*>::iterator jerrno::_err_map_itr;
+bool jerrno::_initialized = jerrno::__init();
+
+// generic errors
+const u_int32_t jerrno::JERR__MALLOC = 0x0100;
+const u_int32_t jerrno::JERR__UNDERFLOW = 0x0101;
+const u_int32_t jerrno::JERR__NINIT = 0x0102;
+const u_int32_t jerrno::JERR__AIO = 0x0103;
+const u_int32_t jerrno::JERR__FILEIO = 0x0104;
+const u_int32_t jerrno::JERR__RTCLOCK = 0x0105;
+const u_int32_t jerrno::JERR__PTHREAD = 0x0106;
+const u_int32_t jerrno::JERR__TIMEOUT = 0x0107;
+const u_int32_t jerrno::JERR__UNEXPRESPONSE = 0x0108;
+const u_int32_t jerrno::JERR__RECNFOUND = 0x0109;
+const u_int32_t jerrno::JERR__NOTIMPL = 0x010a;
+
+// class jcntl
+const u_int32_t jerrno::JERR_JCNTL_STOPPED = 0x0200;
+const u_int32_t jerrno::JERR_JCNTL_READONLY = 0x0201;
+const u_int32_t jerrno::JERR_JCNTL_AIOCMPLWAIT = 0x0202;
+const u_int32_t jerrno::JERR_JCNTL_UNKNOWNMAGIC = 0x0203;
+const u_int32_t jerrno::JERR_JCNTL_NOTRECOVERED = 0x0204;
+const u_int32_t jerrno::JERR_JCNTL_RECOVERJFULL = 0x0205;
+const u_int32_t jerrno::JERR_JCNTL_OWIMISMATCH = 0x0206;
+
+// class jdir
+const u_int32_t jerrno::JERR_JDIR_NOTDIR = 0x0300;
+const u_int32_t jerrno::JERR_JDIR_MKDIR = 0x0301;
+const u_int32_t jerrno::JERR_JDIR_OPENDIR = 0x0302;
+const u_int32_t jerrno::JERR_JDIR_READDIR = 0x0303;
+const u_int32_t jerrno::JERR_JDIR_CLOSEDIR = 0x0304;
+const u_int32_t jerrno::JERR_JDIR_RMDIR = 0x0305;
+const u_int32_t jerrno::JERR_JDIR_NOSUCHFILE = 0x0306;
+const u_int32_t jerrno::JERR_JDIR_FMOVE = 0x0307;
+const u_int32_t jerrno::JERR_JDIR_STAT = 0x0308;
+const u_int32_t jerrno::JERR_JDIR_UNLINK = 0x0309;
+const u_int32_t jerrno::JERR_JDIR_BADFTYPE = 0x030a;
+
+// class fcntl
+const u_int32_t jerrno::JERR_FCNTL_OPENWR = 0x0400;
+const u_int32_t jerrno::JERR_FCNTL_WRITE = 0x0401;
+const u_int32_t jerrno::JERR_FCNTL_CLOSE = 0x0402;
+const u_int32_t jerrno::JERR_FCNTL_FILEOFFSOVFL = 0x0403;
+const u_int32_t jerrno::JERR_FCNTL_CMPLOFFSOVFL = 0x0404;
+const u_int32_t jerrno::JERR_FCNTL_RDOFFSOVFL = 0x0405;
+
+// class lfmgr
+const u_int32_t jerrno::JERR_LFMGR_BADAEFNUMLIM = 0x0500;
+const u_int32_t jerrno::JERR_LFMGR_AEFNUMLIMIT = 0x0501;
+const u_int32_t jerrno::JERR_LFMGR_AEDISABLED = 0x0502;
+
+// class rrfc
+const u_int32_t jerrno::JERR_RRFC_OPENRD = 0x0600;
+
+// class jrec, enq_rec, deq_rec, txn_rec
+const u_int32_t jerrno::JERR_JREC_BADRECHDR = 0x0700;
+const u_int32_t jerrno::JERR_JREC_BADRECTAIL = 0x0701;
+
+// class wmgr
+const u_int32_t jerrno::JERR_WMGR_BADPGSTATE = 0x0801;
+const u_int32_t jerrno::JERR_WMGR_BADDTOKSTATE = 0x0802;
+const u_int32_t jerrno::JERR_WMGR_ENQDISCONT = 0x0803;
+const u_int32_t jerrno::JERR_WMGR_DEQDISCONT = 0x0804;
+const u_int32_t jerrno::JERR_WMGR_DEQRIDNOTENQ = 0x0805;
+
+// class rmgr
+const u_int32_t jerrno::JERR_RMGR_UNKNOWNMAGIC = 0x0900;
+const u_int32_t jerrno::JERR_RMGR_RIDMISMATCH = 0x0901;
+//const u_int32_t jerrno::JERR_RMGR_FIDMISMATCH = 0x0902;
+const u_int32_t jerrno::JERR_RMGR_ENQSTATE = 0x0903;
+const u_int32_t jerrno::JERR_RMGR_BADRECTYPE = 0x0904;
+
+// class data_tok
+const u_int32_t jerrno::JERR_DTOK_ILLEGALSTATE = 0x0a00;
+// const u_int32_t jerrno::JERR_DTOK_RIDNOTSET = 0x0a01;
+
+// class enq_map, txn_map
+const u_int32_t jerrno::JERR_MAP_DUPLICATE = 0x0b00;
+const u_int32_t jerrno::JERR_MAP_NOTFOUND = 0x0b01;
+const u_int32_t jerrno::JERR_MAP_LOCKED = 0x0b02;
+
+// class jinf
+const u_int32_t jerrno::JERR_JINF_CVALIDFAIL = 0x0c00;
+const u_int32_t jerrno::JERR_JINF_NOVALUESTR = 0x0c01;
+const u_int32_t jerrno::JERR_JINF_BADVALUESTR = 0x0c02;
+const u_int32_t jerrno::JERR_JINF_JDATEMPTY = 0x0c03;
+const u_int32_t jerrno::JERR_JINF_TOOMANYFILES = 0x0c04;
+const u_int32_t jerrno::JERR_JINF_INVALIDFHDR = 0x0c05;
+const u_int32_t jerrno::JERR_JINF_STAT = 0x0c06;
+const u_int32_t jerrno::JERR_JINF_NOTREGFILE = 0x0c07;
+const u_int32_t jerrno::JERR_JINF_BADFILESIZE = 0x0c08;
+const u_int32_t jerrno::JERR_JINF_OWIBAD = 0x0c09;
+const u_int32_t jerrno::JERR_JINF_ZEROLENFILE = 0x0c0a;
+
+// Negative returns for some functions
+const int32_t jerrno::AIO_TIMEOUT = -1;
+const int32_t jerrno::LOCK_TAKEN = -2;
+
+
+// static initialization fn
+
+bool
+jerrno::__init()
+{
+ // generic errors
+ _err_map[JERR__MALLOC] = "JERR__MALLOC: Buffer memory allocation failed.";
+ _err_map[JERR__UNDERFLOW] = "JERR__UNDERFLOW: Underflow error";
+ _err_map[JERR__NINIT] = "JERR__NINIT: Operation on uninitialized class.";
+ _err_map[JERR__AIO] = "JERR__AIO: AIO error.";
+ _err_map[JERR__FILEIO] = "JERR__FILEIO: File read or write failure.";
+ _err_map[JERR__RTCLOCK] = "JERR__RTCLOCK: Reading real-time clock failed.";
+ _err_map[JERR__PTHREAD] = "JERR__PTHREAD: pthread failure.";
+ _err_map[JERR__TIMEOUT] = "JERR__TIMEOUT: Timeout waiting for event.";
+ _err_map[JERR__UNEXPRESPONSE] = "JERR__UNEXPRESPONSE: Unexpected response to call or event.";
+ _err_map[JERR__RECNFOUND] = "JERR__RECNFOUND: Record not found.";
+ _err_map[JERR__NOTIMPL] = "JERR__NOTIMPL: Not implemented";
+
+ // class jcntl
+ _err_map[JERR_JCNTL_STOPPED] = "JERR_JCNTL_STOPPED: Operation on stopped journal.";
+ _err_map[JERR_JCNTL_READONLY] = "JERR_JCNTL_READONLY: Write operation on read-only journal (during recovery).";
+ _err_map[JERR_JCNTL_AIOCMPLWAIT] = "JERR_JCNTL_AIOCMPLWAIT: Timeout waiting for AIOs to complete.";
+ _err_map[JERR_JCNTL_UNKNOWNMAGIC] = "JERR_JCNTL_UNKNOWNMAGIC: Found record with unknown magic.";
+ _err_map[JERR_JCNTL_NOTRECOVERED] = "JERR_JCNTL_NOTRECOVERED: Operation requires recover() to be run first.";
+ _err_map[JERR_JCNTL_RECOVERJFULL] = "JERR_JCNTL_RECOVERJFULL: Journal data files full, cannot write.";
+ _err_map[JERR_JCNTL_OWIMISMATCH] = "JERR_JCNTL_OWIMISMATCH: Overwrite Indicator (OWI) change found in unexpected location.";
+
+ // class jdir
+ _err_map[JERR_JDIR_NOTDIR] = "JERR_JDIR_NOTDIR: Directory name exists but is not a directory.";
+ _err_map[JERR_JDIR_MKDIR] = "JERR_JDIR_MKDIR: Directory creation failed.";
+ _err_map[JERR_JDIR_OPENDIR] = "JERR_JDIR_OPENDIR: Directory open failed.";
+ _err_map[JERR_JDIR_READDIR] = "JERR_JDIR_READDIR: Directory read failed.";
+ _err_map[JERR_JDIR_CLOSEDIR] = "JERR_JDIR_CLOSEDIR: Directory close failed.";
+ _err_map[JERR_JDIR_RMDIR] = "JERR_JDIR_RMDIR: Directory delete failed.";
+ _err_map[JERR_JDIR_NOSUCHFILE] = "JERR_JDIR_NOSUCHFILE: File does not exist.";
+ _err_map[JERR_JDIR_FMOVE] = "JERR_JDIR_FMOVE: File move failed.";
+ _err_map[JERR_JDIR_STAT] = "JERR_JDIR_STAT: File stat failed.";
+ _err_map[JERR_JDIR_UNLINK] = "JERR_JDIR_UNLINK: File delete failed.";
+ _err_map[JERR_JDIR_BADFTYPE] = "JERR_JDIR_BADFTYPE: Bad or unknown file type (stat mode).";
+
+ // class fcntl
+ _err_map[JERR_FCNTL_OPENWR] = "JERR_FCNTL_OPENWR: Unable to open file for write.";
+ _err_map[JERR_FCNTL_WRITE] = "JERR_FCNTL_WRITE: Unable to write to file.";
+ _err_map[JERR_FCNTL_CLOSE] = "JERR_FCNTL_CLOSE: File close failed.";
+ _err_map[JERR_FCNTL_FILEOFFSOVFL] = "JERR_FCNTL_FILEOFFSOVFL: Attempted increase file offset past file size.";
+ _err_map[JERR_FCNTL_CMPLOFFSOVFL] = "JERR_FCNTL_CMPLOFFSOVFL: Attempted increase completed file offset past submitted offset.";
+ _err_map[JERR_FCNTL_RDOFFSOVFL] = "JERR_FCNTL_RDOFFSOVFL: Attempted increase read offset past write offset.";
+
+ // class lfmgr
+ _err_map[JERR_LFMGR_BADAEFNUMLIM] = "JERR_LFMGR_BADAEFNUMLIM: Auto-expand file number limit lower than initial number of journal files.";
+ _err_map[JERR_LFMGR_AEFNUMLIMIT] = "JERR_LFMGR_AEFNUMLIMIT: Exceeded auto-expand file number limit.";
+ _err_map[JERR_LFMGR_AEDISABLED] = "JERR_LFMGR_AEDISABLED: Attempted to expand with auto-expand disabled.";
+
+ // class rrfc
+ _err_map[JERR_RRFC_OPENRD] = "JERR_RRFC_OPENRD: Unable to open file for read.";
+
+ // class jrec, enq_rec, deq_rec, txn_rec
+ _err_map[JERR_JREC_BADRECHDR] = "JERR_JREC_BADRECHDR: Invalid data record header.";
+ _err_map[JERR_JREC_BADRECTAIL] = "JERR_JREC_BADRECTAIL: Invalid data record tail.";
+
+ // class wmgr
+ _err_map[JERR_WMGR_BADPGSTATE] = "JERR_WMGR_BADPGSTATE: Page buffer in illegal state for operation.";
+ _err_map[JERR_WMGR_BADDTOKSTATE] = "JERR_WMGR_BADDTOKSTATE: Data token in illegal state for operation.";
+ _err_map[JERR_WMGR_ENQDISCONT] = "JERR_WMGR_ENQDISCONT: Enqueued new dtok when previous enqueue returned partly completed (state ENQ_PART).";
+ _err_map[JERR_WMGR_DEQDISCONT] = "JERR_WMGR_DEQDISCONT: Dequeued new dtok when previous dequeue returned partly completed (state DEQ_PART).";
+ _err_map[JERR_WMGR_DEQRIDNOTENQ] = "JERR_WMGR_DEQRIDNOTENQ: Dequeue rid is not enqueued.";
+
+ // class rmgr
+ _err_map[JERR_RMGR_UNKNOWNMAGIC] = "JERR_RMGR_UNKNOWNMAGIC: Found record with unknown magic.";
+ _err_map[JERR_RMGR_RIDMISMATCH] = "JERR_RMGR_RIDMISMATCH: RID mismatch between current record and dtok RID";
+ //_err_map[JERR_RMGR_FIDMISMATCH] = "JERR_RMGR_FIDMISMATCH: FID mismatch between emap and rrfc";
+ _err_map[JERR_RMGR_ENQSTATE] = "JERR_RMGR_ENQSTATE: Attempted read when data token wstate was not ENQ";
+ _err_map[JERR_RMGR_BADRECTYPE] = "JERR_RMGR_BADRECTYPE: Attempted operation on inappropriate record type";
+
+ // class data_tok
+ _err_map[JERR_DTOK_ILLEGALSTATE] = "JERR_MTOK_ILLEGALSTATE: Attempted to change to illegal state.";
+ //_err_map[JERR_DTOK_RIDNOTSET] = "JERR_DTOK_RIDNOTSET: Record ID not set.";
+
+ // class enq_map, txn_map
+ _err_map[JERR_MAP_DUPLICATE] = "JERR_MAP_DUPLICATE: Attempted to insert record into map using duplicate key.";
+ _err_map[JERR_MAP_NOTFOUND] = "JERR_MAP_NOTFOUND: Key not found in map.";
+ _err_map[JERR_MAP_LOCKED] = "JERR_MAP_LOCKED: Record ID locked by a pending transaction.";
+
+ // class jinf
+ _err_map[JERR_JINF_CVALIDFAIL] = "JERR_JINF_CVALIDFAIL: Journal compatibility validation failure.";
+ _err_map[JERR_JINF_NOVALUESTR] = "JERR_JINF_NOVALUESTR: No value attribute found in jinf file.";
+ _err_map[JERR_JINF_BADVALUESTR] = "JERR_JINF_BADVALUESTR: Bad format for value attribute in jinf file";
+ _err_map[JERR_JINF_JDATEMPTY] = "JERR_JINF_JDATEMPTY: Journal data files empty.";
+ _err_map[JERR_JINF_TOOMANYFILES] = "JERR_JINF_TOOMANYFILES: Too many journal data files.";
+ _err_map[JERR_JINF_INVALIDFHDR] = "JERR_JINF_INVALIDFHDR: Invalid journal data file header";
+ _err_map[JERR_JINF_STAT] = "JERR_JINF_STAT: Error while trying to stat a journal data file";
+ _err_map[JERR_JINF_NOTREGFILE] = "JERR_JINF_NOTREGFILE: Target journal data file is not a regular file";
+ _err_map[JERR_JINF_BADFILESIZE] = "JERR_JINF_BADFILESIZE: Journal data file is of incorrect or unexpected size";
+ _err_map[JERR_JINF_OWIBAD] = "JERR_JINF_OWIBAD: Journal data files have inconsistent OWI flags; >1 transition found in non-auto-expand or min-size journal";
+ _err_map[JERR_JINF_ZEROLENFILE] = "JERR_JINF_ZEROLENFILE: Journal info file zero length";
+
+ //_err_map[] = "";
+
+ return true;
+}
+
+const char*
+jerrno::err_msg(const u_int32_t err_no) throw ()
+{
+ _err_map_itr = _err_map.find(err_no);
+ if (_err_map_itr == _err_map.end())
+ return "<Unknown error code>";
+ return _err_map_itr->second;
+}
+
+} // namespace journal
+} // namespace mrg
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/jerrno.h b/qpid/cpp/src/qpid/legacystore/jrnl/jerrno.h
new file mode 100644
index 0000000000..4c8b71c423
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/jerrno.h
@@ -0,0 +1,173 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file jerrno.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::jerrno (journal error
+ * codes). See class documentation for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_JERRNO_H
+#define QPID_LEGACYSTORE_JRNL_JERRNO_H
+
+namespace mrg
+{
+namespace journal
+{
+class jerrno;
+}
+}
+
+#include <map>
+#include <string>
+#include <sys/types.h>
+
+namespace mrg
+{
+namespace journal
+{
+
+ /**
+ * \class jerrno
+ * \brief Class containing static error definitions and static map for error messages.
+ */
+ class jerrno
+ {
+ static std::map<u_int32_t, const char*> _err_map; ///< Map of error messages
+ static std::map<u_int32_t, const char*>::iterator _err_map_itr; ///< Iterator
+ static bool _initialized; ///< Dummy flag, used to initialise map.
+
+ public:
+ // generic errors
+ static const u_int32_t JERR__MALLOC; ///< Buffer memory allocation failed
+ static const u_int32_t JERR__UNDERFLOW; ///< Underflow error
+ static const u_int32_t JERR__NINIT; ///< Operation on uninitialized class
+ static const u_int32_t JERR__AIO; ///< AIO failure
+ static const u_int32_t JERR__FILEIO; ///< File read or write failure
+ static const u_int32_t JERR__RTCLOCK; ///< Reading real-time clock failed
+ static const u_int32_t JERR__PTHREAD; ///< pthread failure
+ static const u_int32_t JERR__TIMEOUT; ///< Timeout waiting for an event
+ static const u_int32_t JERR__UNEXPRESPONSE; ///< Unexpected response to call or event
+ static const u_int32_t JERR__RECNFOUND; ///< Record not found
+ static const u_int32_t JERR__NOTIMPL; ///< Not implemented
+
+ // class jcntl
+ static const u_int32_t JERR_JCNTL_STOPPED; ///< Operation on stopped journal
+ static const u_int32_t JERR_JCNTL_READONLY; ///< Write operation on read-only journal
+ static const u_int32_t JERR_JCNTL_AIOCMPLWAIT; ///< Timeout waiting for AIOs to complete
+ static const u_int32_t JERR_JCNTL_UNKNOWNMAGIC; ///< Found record with unknown magic
+ static const u_int32_t JERR_JCNTL_NOTRECOVERED; ///< Req' recover() to be called first
+ static const u_int32_t JERR_JCNTL_RECOVERJFULL; ///< Journal data files full, cannot write
+ static const u_int32_t JERR_JCNTL_OWIMISMATCH; ///< OWI change found in unexpected location
+
+ // class jdir
+ static const u_int32_t JERR_JDIR_NOTDIR; ///< Exists but is not a directory
+ static const u_int32_t JERR_JDIR_MKDIR; ///< Directory creation failed
+ static const u_int32_t JERR_JDIR_OPENDIR; ///< Directory open failed
+ static const u_int32_t JERR_JDIR_READDIR; ///< Directory read failed
+ static const u_int32_t JERR_JDIR_CLOSEDIR; ///< Directory close failed
+ static const u_int32_t JERR_JDIR_RMDIR; ///< Directory delete failed
+ static const u_int32_t JERR_JDIR_NOSUCHFILE; ///< File does not exist
+ static const u_int32_t JERR_JDIR_FMOVE; ///< File move failed
+ static const u_int32_t JERR_JDIR_STAT; ///< File stat failed
+ static const u_int32_t JERR_JDIR_UNLINK; ///< File delete failed
+ static const u_int32_t JERR_JDIR_BADFTYPE; ///< Bad or unknown file type (stat mode)
+
+ // class fcntl
+ static const u_int32_t JERR_FCNTL_OPENWR; ///< Unable to open file for write
+ static const u_int32_t JERR_FCNTL_WRITE; ///< Unable to write to file
+ static const u_int32_t JERR_FCNTL_CLOSE; ///< File close failed
+ static const u_int32_t JERR_FCNTL_FILEOFFSOVFL; ///< Increased offset past file size
+ static const u_int32_t JERR_FCNTL_CMPLOFFSOVFL; ///< Increased cmpl offs past subm offs
+ static const u_int32_t JERR_FCNTL_RDOFFSOVFL; ///< Increased read offs past write offs
+
+ // class lfmgr
+ static const u_int32_t JERR_LFMGR_BADAEFNUMLIM; ///< Bad auto-expand file number limit
+ static const u_int32_t JERR_LFMGR_AEFNUMLIMIT; ///< Exceeded auto-expand file number limit
+ static const u_int32_t JERR_LFMGR_AEDISABLED; ///< Attempted to expand with auto-expand disabled
+
+ // class rrfc
+ static const u_int32_t JERR_RRFC_OPENRD; ///< Unable to open file for read
+
+ // class jrec, enq_rec, deq_rec, txn_rec
+ static const u_int32_t JERR_JREC_BADRECHDR; ///< Invalid data record header
+ static const u_int32_t JERR_JREC_BADRECTAIL; ///< Invalid data record tail
+
+ // class wmgr
+ static const u_int32_t JERR_WMGR_BADPGSTATE; ///< Page buffer in illegal state.
+ static const u_int32_t JERR_WMGR_BADDTOKSTATE; ///< Data token in illegal state.
+ static const u_int32_t JERR_WMGR_ENQDISCONT; ///< Enq. new dtok when previous part compl.
+ static const u_int32_t JERR_WMGR_DEQDISCONT; ///< Deq. new dtok when previous part compl.
+ static const u_int32_t JERR_WMGR_DEQRIDNOTENQ; ///< Deq. rid not enqueued
+
+ // class rmgr
+ static const u_int32_t JERR_RMGR_UNKNOWNMAGIC; ///< Found record with unknown magic
+ static const u_int32_t JERR_RMGR_RIDMISMATCH; ///< RID mismatch between rec and dtok
+ //static const u_int32_t JERR_RMGR_FIDMISMATCH; ///< FID mismatch between emap and rrfc
+ static const u_int32_t JERR_RMGR_ENQSTATE; ///< Attempted read when wstate not ENQ
+ static const u_int32_t JERR_RMGR_BADRECTYPE; ///< Attempted op on incorrect rec type
+
+ // class data_tok
+ static const u_int32_t JERR_DTOK_ILLEGALSTATE; ///< Attempted to change to illegal state
+// static const u_int32_t JERR_DTOK_RIDNOTSET; ///< Record ID not set
+
+ // class enq_map, txn_map
+ static const u_int32_t JERR_MAP_DUPLICATE; ///< Attempted to insert using duplicate key
+ static const u_int32_t JERR_MAP_NOTFOUND; ///< Key not found in map
+ static const u_int32_t JERR_MAP_LOCKED; ///< rid locked by pending txn
+
+ // class jinf
+ static const u_int32_t JERR_JINF_CVALIDFAIL; ///< Compatibility validation failure
+ static const u_int32_t JERR_JINF_NOVALUESTR; ///< No value attr found in jinf file
+ static const u_int32_t JERR_JINF_BADVALUESTR; ///< Bad format for value attr in jinf file
+ static const u_int32_t JERR_JINF_JDATEMPTY; ///< Journal data files empty
+ static const u_int32_t JERR_JINF_TOOMANYFILES; ///< Too many journal data files
+ static const u_int32_t JERR_JINF_INVALIDFHDR; ///< Invalid file header
+ static const u_int32_t JERR_JINF_STAT; ///< Error while trying to stat a file
+ static const u_int32_t JERR_JINF_NOTREGFILE; ///< Target file is not a regular file
+ static const u_int32_t JERR_JINF_BADFILESIZE; ///< File is of incorrect or unexpected size
+ static const u_int32_t JERR_JINF_OWIBAD; ///< OWI inconsistent (>1 transition in non-ae journal)
+ static const u_int32_t JERR_JINF_ZEROLENFILE; ///< Journal info file is zero length (empty).
+
+ // Negative returns for some functions
+ static const int32_t AIO_TIMEOUT; ///< Timeout waiting for AIO return
+ static const int32_t LOCK_TAKEN; ///< Attempted to take lock, but it was taken by another thread
+ /**
+ * \brief Method to access error message from known error number.
+ */
+ static const char* err_msg(const u_int32_t err_no) throw ();
+
+ private:
+ /**
+ * \brief Static function to initialize map.
+ */
+ static bool __init();
+ };
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_JERRNO_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/jexception.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/jexception.cpp
new file mode 100644
index 0000000000..5c571020e4
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/jexception.cpp
@@ -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.
+ *
+ */
+
+/**
+ * \file jexception.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * Generic journal exception class mrg::journal::jexception. See comments
+ * in file jexception.h for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#include "qpid/legacystore/jrnl/jexception.h"
+
+#include <iomanip>
+#include <sstream>
+#include "qpid/legacystore/jrnl/jerrno.h"
+
+#define CATLEN(p) MAX_MSG_SIZE - std::strlen(p) - 1
+
+namespace mrg
+{
+namespace journal
+{
+
+jexception::jexception() throw ():
+ std::exception(),
+ _err_code(0)
+{
+ format();
+}
+
+jexception::jexception(const u_int32_t err_code) throw ():
+ std::exception(),
+ _err_code(err_code)
+{
+ format();
+}
+
+jexception::jexception(const char* additional_info) throw ():
+ std::exception(),
+ _err_code(0),
+ _additional_info(additional_info)
+{
+ format();
+}
+
+jexception::jexception(const std::string& additional_info) throw ():
+ std::exception(),
+ _err_code(0),
+ _additional_info(additional_info)
+{
+ format();
+}
+
+jexception::jexception(const u_int32_t err_code, const char* additional_info) throw ():
+ std::exception(),
+ _err_code(err_code),
+ _additional_info(additional_info)
+{
+ format();
+}
+
+jexception::jexception(const u_int32_t err_code, const std::string& additional_info) throw ():
+ std::exception(),
+ _err_code(err_code),
+ _additional_info(additional_info)
+{
+ format();
+}
+
+jexception::jexception(const u_int32_t err_code, const char* throwing_class,
+ const char* throwing_fn) throw ():
+ std::exception(),
+ _err_code(err_code),
+ _throwing_class(throwing_class),
+ _throwing_fn(throwing_fn)
+{
+ format();
+}
+
+jexception::jexception(const u_int32_t err_code, const std::string& throwing_class,
+ const std::string& throwing_fn) throw ():
+ std::exception(),
+ _err_code(err_code),
+ _throwing_class(throwing_class),
+ _throwing_fn(throwing_fn)
+{
+ format();
+}
+
+jexception::jexception(const u_int32_t err_code, const char* additional_info,
+ const char* throwing_class, const char* throwing_fn) throw ():
+ std::exception(),
+ _err_code(err_code),
+ _additional_info(additional_info),
+ _throwing_class(throwing_class),
+ _throwing_fn(throwing_fn)
+{
+ format();
+}
+
+jexception::jexception(const u_int32_t err_code, const std::string& additional_info,
+ const std::string& throwing_class, const std::string& throwing_fn) throw ():
+ std::exception(),
+ _err_code(err_code),
+ _additional_info(additional_info),
+ _throwing_class(throwing_class),
+ _throwing_fn(throwing_fn)
+{
+ format();
+}
+
+jexception::~jexception() throw ()
+{}
+
+void
+jexception::format()
+{
+ const bool ai = !_additional_info.empty();
+ const bool tc = !_throwing_class.empty();
+ const bool tf = !_throwing_fn.empty();
+ std::ostringstream oss;
+ oss << "jexception 0x" << std::hex << std::setfill('0') << std::setw(4) << _err_code << " ";
+ if (tc)
+ {
+ oss << _throwing_class;
+ if (tf)
+ oss << "::";
+ else
+ oss << " ";
+ }
+ if (tf)
+ oss << _throwing_fn << "() ";
+ if (tc || tf)
+ oss << "threw " << jerrno::err_msg(_err_code);
+ if (ai)
+ oss << " (" << _additional_info << ")";
+ _what.assign(oss.str());
+}
+
+const char*
+jexception::what() const throw ()
+{
+ return _what.c_str();
+}
+
+std::ostream&
+operator<<(std::ostream& os, const jexception& je)
+{
+ os << je.what();
+ return os;
+}
+
+std::ostream&
+operator<<(std::ostream& os, const jexception* jePtr)
+{
+ os << jePtr->what();
+ return os;
+}
+
+} // namespace journal
+} // namespace mrg
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/jexception.h b/qpid/cpp/src/qpid/legacystore/jrnl/jexception.h
new file mode 100644
index 0000000000..34d8373235
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/jexception.h
@@ -0,0 +1,142 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file jexception.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * Generic journal exception class mrg::journal::jexception (derived
+ * from class std::exception). Intended to serve as a common exception
+ * class for all more speicalized exceptions in the message journal. See
+ * class documentation for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_JEXCEPTION_H
+#define QPID_LEGACYSTORE_JRNL_JEXCEPTION_H
+
+namespace mrg
+{
+namespace journal
+{
+class jexception;
+}
+}
+
+#include <cerrno>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <exception>
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include <sstream>
+#include <string>
+#include <sys/types.h>
+
+// Macro for formatting commom system errors
+#define FORMAT_SYSERR(errno) " errno=" << errno << " (" << std::strerror(errno) << ")"
+
+#define MALLOC_CHK(ptr, var, cls, fn) if(ptr == 0) { \
+ clean(); \
+ std::ostringstream oss; \
+ oss << var << ": malloc() failed: " << FORMAT_SYSERR(errno); \
+ throw jexception(jerrno::JERR__MALLOC, oss.str(), cls, fn); \
+ }
+
+// TODO: The following is a temporary bug-tracking aid which forces a core.
+// Replace with the commented out version below when BZ484048 is resolved.
+#define PTHREAD_CHK(err, pfn, cls, fn) if(err != 0) { \
+ std::ostringstream oss; \
+ oss << cls << "::" << fn << "(): " << pfn; \
+ errno = err; \
+ ::perror(oss.str().c_str()); \
+ ::abort(); \
+ }
+/*
+#define PTHREAD_CHK(err, pfn, cls, fn) if(err != 0) { \
+ std::ostringstream oss; \
+ oss << pfn << " failed: " << FORMAT_SYSERR(err); \
+ throw jexception(jerrno::JERR__PTHREAD, oss.str(), cls, fn); \
+ }
+*/
+
+#define ASSERT(cond, msg) if(cond == 0) { \
+ std::cerr << msg << std::endl; \
+ ::abort(); \
+ }
+
+namespace mrg
+{
+namespace journal
+{
+ /**
+ * \class jexception
+ * \brief Generic journal exception class
+ */
+ class jexception : public std::exception
+ {
+ private:
+ u_int32_t _err_code;
+ std::string _additional_info;
+ std::string _throwing_class;
+ std::string _throwing_fn;
+ std::string _what;
+ void format();
+
+ public:
+ jexception() throw ();
+
+ jexception(const u_int32_t err_code) throw ();
+
+ jexception(const char* additional_info) throw ();
+ jexception(const std::string& additional_info) throw ();
+
+ jexception(const u_int32_t err_code, const char* additional_info) throw ();
+ jexception(const u_int32_t err_code, const std::string& additional_info) throw ();
+
+ jexception(const u_int32_t err_code, const char* throwing_class, const char* throwing_fn)
+ throw ();
+ jexception(const u_int32_t err_code, const std::string& throwing_class,
+ const std::string& throwing_fn) throw ();
+
+ jexception(const u_int32_t err_code, const char* additional_info,
+ const char* throwing_class, const char* throwing_fn) throw ();
+ jexception(const u_int32_t err_code, const std::string& additional_info,
+ const std::string& throwing_class, const std::string& throwing_fn) throw ();
+
+ virtual ~jexception() throw ();
+ virtual const char* what() const throw (); // override std::exception::what()
+
+ inline u_int32_t err_code() const throw () { return _err_code; }
+ inline const std::string additional_info() const throw () { return _additional_info; }
+ inline const std::string throwing_class() const throw () { return _throwing_class; }
+ inline const std::string throwing_fn() const throw () { return _throwing_fn; }
+
+ friend std::ostream& operator<<(std::ostream& os, const jexception& je);
+ friend std::ostream& operator<<(std::ostream& os, const jexception* jePtr);
+ }; // class jexception
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_JEXCEPTION_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/jinf.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/jinf.cpp
new file mode 100644
index 0000000000..3326005d9b
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/jinf.cpp
@@ -0,0 +1,540 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file jinf.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * This file contains the code for the mrg::journal::jinf class.
+ *
+ * See jinf.h comments for details of this class.
+ *
+ * \author Kim van der Riet
+ */
+
+#include "qpid/legacystore/jrnl/jinf.h"
+
+#include <cstdlib>
+#include <cstring>
+#include <ctime>
+#include <fstream>
+#include "qpid/legacystore/jrnl/file_hdr.h"
+#include "qpid/legacystore/jrnl/jcntl.h"
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include "qpid/legacystore/jrnl/lp_map.h"
+#include <sstream>
+#include <sys/stat.h>
+
+namespace mrg
+{
+namespace journal
+{
+
+jinf::jinf(const std::string& jinf_filename, bool validate_flag):
+ _jver(0),
+ _filename(jinf_filename),
+ _num_jfiles(0),
+ _ae(false),
+ _ae_max_jfiles(0),
+ _jfsize_sblks(0),
+ _sblk_size_dblks(0),
+ _dblk_size(0),
+ _wcache_pgsize_sblks(0),
+ _wcache_num_pages(0),
+ _rcache_pgsize_sblks(0),
+ _rcache_num_pages(0),
+ _tm_ptr(0),
+ _valid_flag(false),
+ _analyzed_flag(false),
+ _initial_owi(false),
+ _frot(false)
+{
+ read(_filename);
+ if (validate_flag)
+ validate();
+}
+
+jinf::jinf(const std::string& jid, const std::string& jdir, const std::string& base_filename, const u_int16_t num_jfiles,
+ const bool auto_expand, const u_int16_t ae_max_jfiles, const u_int32_t jfsize_sblks,
+ const u_int32_t wcache_pgsize_sblks, const u_int16_t wcache_num_pages, const timespec& ts):
+ _jver(RHM_JDAT_VERSION),
+ _jid(jid),
+ _jdir(jdir),
+ _base_filename(base_filename),
+ _ts(ts),
+ _num_jfiles(num_jfiles),
+ _ae(auto_expand),
+ _ae_max_jfiles(ae_max_jfiles),
+ _jfsize_sblks(jfsize_sblks),
+ _sblk_size_dblks(JRNL_SBLK_SIZE),
+ _dblk_size(JRNL_DBLK_SIZE),
+ _wcache_pgsize_sblks(wcache_pgsize_sblks),
+ _wcache_num_pages(wcache_num_pages),
+ _rcache_pgsize_sblks(JRNL_RMGR_PAGE_SIZE),
+ _rcache_num_pages(JRNL_RMGR_PAGES),
+ _tm_ptr(std::localtime(&ts.tv_sec)),
+ _valid_flag(false),
+ _analyzed_flag(false),
+ _initial_owi(false)
+{
+ set_filename();
+}
+
+jinf::~jinf()
+{}
+
+void
+jinf::validate()
+{
+ bool err = false;
+ std::ostringstream oss;
+ if (_jver != RHM_JDAT_VERSION)
+ {
+ oss << "File \"" << _filename << "\": ";
+ oss << "RHM_JDAT_VERSION mismatch: found=" << (int)_jver;
+ oss << "; required=" << RHM_JDAT_VERSION << std::endl;
+ err = true;
+ }
+ if (_num_jfiles < JRNL_MIN_NUM_FILES)
+ {
+ oss << "File \"" << _filename << "\": ";
+ oss << "Number of journal files too small: found=" << _num_jfiles;
+ oss << "; minimum=" << JRNL_MIN_NUM_FILES << std::endl;
+ err = true;
+ }
+ if (_num_jfiles > JRNL_MAX_NUM_FILES)
+ {
+ oss << "File \"" << _filename << "\": ";
+ oss << "Number of journal files too large: found=" << _num_jfiles;
+ oss << "; maximum=" << JRNL_MAX_NUM_FILES << std::endl;
+ err = true;
+ }
+ if (_ae)
+ {
+ if (_ae_max_jfiles < _num_jfiles)
+ {
+ oss << "File \"" << _filename << "\": ";
+ oss << "Number of journal files exceeds auto-expansion limit: found=" << _num_jfiles;
+ oss << "; maximum=" << _ae_max_jfiles;
+ err = true;
+ }
+ if (_ae_max_jfiles > JRNL_MAX_NUM_FILES)
+ {
+ oss << "File \"" << _filename << "\": ";
+ oss << "Auto-expansion file limit too large: found=" << _ae_max_jfiles;
+ oss << "; maximum=" << JRNL_MAX_NUM_FILES;
+ err = true;
+ }
+ }
+ if (_jfsize_sblks < JRNL_MIN_FILE_SIZE)
+ {
+ oss << "File \"" << _filename << "\": ";
+ oss << "Journal file size too small: found=" << _jfsize_sblks;
+ oss << "; minimum=" << JRNL_MIN_FILE_SIZE << " (sblks)" << std::endl;
+ err = true;
+ }
+ if (_sblk_size_dblks != JRNL_SBLK_SIZE)
+ {
+ oss << "File \"" << _filename << "\": ";
+ oss << "JRNL_SBLK_SIZE mismatch: found=" << _sblk_size_dblks;
+ oss << "; required=" << JRNL_SBLK_SIZE << std::endl;
+ err = true;
+ }
+ if (_dblk_size != JRNL_DBLK_SIZE)
+ {
+ oss << "File \"" << _filename << "\": ";
+ oss << "JRNL_DBLK_SIZE mismatch: found=" << _dblk_size;
+ oss << "; required=" << JRNL_DBLK_SIZE << std::endl;
+ err = true;
+ }
+ if (err)
+ throw jexception(jerrno::JERR_JINF_CVALIDFAIL, oss.str(), "jinf", "validate");
+ _valid_flag = true;
+}
+
+void
+jinf::analyze()
+{
+ lp_map early_map; // map for all owi flags same as pfid 0
+ lp_map late_map; // map for all owi flags opposite to pfid 0
+ bool late_latch = false; // latch for owi switchover
+
+ if (!_valid_flag)
+ validate();
+ bool done = false;
+ for (u_int16_t pfid=0; pfid<_num_jfiles && !done; pfid++)
+ {
+ std::ostringstream oss;
+ if (_jdir.at(_jdir.size() - 1) == '/')
+ oss << _jdir << _base_filename << ".";
+ else
+ oss << _jdir << "/" << _base_filename << ".";
+ oss << std::setw(4) << std::setfill('0') << std::hex << pfid;
+ oss << "." << JRNL_DATA_EXTENSION;
+
+ // Check size of each file is consistent and expected
+ u_int32_t fsize = get_filesize(oss.str());
+ if (fsize != (_jfsize_sblks + 1) * _sblk_size_dblks * _dblk_size)
+ {
+ std::ostringstream oss1;
+ oss1 << "File \"" << oss.str() << "\": size=" << fsize << "; expected=" << ((_jfsize_sblks + 1) * _sblk_size_dblks * _dblk_size);
+ throw jexception(jerrno::JERR_JINF_BADFILESIZE, oss1.str(), "jinf", "analyze");
+ }
+
+ std::ifstream jifs(oss.str().c_str());
+ if (!jifs.good())
+ throw jexception(jerrno::JERR__FILEIO, oss.str(), "jinf", "analyze");
+ file_hdr fhdr;
+ jifs.read((char*)&fhdr, sizeof(fhdr));
+ if (fhdr._magic != RHM_JDAT_FILE_MAGIC) // No file header
+ {
+ if (fhdr._magic != 0)
+ throw jexception(jerrno::JERR_JINF_INVALIDFHDR, oss.str(), "jinf", "analyze");
+ if (!pfid) // pfid 0 == lid 0 cannot be empty
+ throw jexception(jerrno::JERR_JINF_JDATEMPTY, oss.str(), "jinf", "analyze");
+ _frot = true;
+ done = true;
+ }
+ else
+ {
+ assert(pfid == fhdr._pfid);
+ if (pfid == 0)
+ {
+ _initial_owi = fhdr.get_owi();
+ early_map.insert(fhdr._lfid, pfid);
+ }
+ else
+ {
+ if (_initial_owi == fhdr.get_owi())
+ {
+ early_map.insert(fhdr._lfid, pfid);
+ if (late_latch && (!_ae || _num_jfiles == JRNL_MIN_NUM_FILES))
+ throw jexception(jerrno::JERR_JINF_OWIBAD, oss.str(), "jinf", "analyze");
+ }
+ else
+ {
+ late_map.insert(fhdr._lfid, pfid);
+ late_latch = true;
+ }
+ }
+ }
+ jifs.close();
+ } // for (pfid)
+
+ // If this is not the first rotation, all files should be in either early or late maps
+ if (!_frot) assert(early_map.size() + late_map.size() == _num_jfiles);
+
+ _pfid_list.clear();
+ late_map.get_pfid_list(_pfid_list);
+ early_map.get_pfid_list(_pfid_list);
+
+ // Check OWI consistency
+// for (u_int16_t lfid=0; lfid<_num_jfiles && !done; lfid++)
+// {
+// throw jexception(jerrno::JERR_JINF_OWIBAD, oss.str(), "jinf", "analyze");
+// }
+
+ _analyzed_flag = true;
+}
+
+void
+jinf::write()
+{
+ std::ostringstream oss;
+ oss << _jdir << "/" << _base_filename << "." << JRNL_INFO_EXTENSION;
+ std::ofstream of(oss.str().c_str(), std::ofstream::out | std::ofstream::trunc);
+ if (!of.good())
+ throw jexception(jerrno::JERR__FILEIO, oss.str(), "jinf", "write");
+ of << xml_str();
+ of.close();
+}
+
+u_int16_t
+jinf::incr_num_jfiles()
+{
+ if (_num_jfiles >= JRNL_MAX_NUM_FILES)
+ throw jexception(jerrno::JERR_JINF_TOOMANYFILES, "jinf", "incr_num_jfiles");
+ return ++_num_jfiles;
+}
+
+u_int16_t
+jinf::get_first_pfid()
+{
+ if (!_analyzed_flag)
+ analyze();
+ return *_pfid_list.begin();
+}
+
+u_int16_t
+jinf::get_last_pfid()
+{
+ if (!_analyzed_flag)
+ analyze();
+ return *_pfid_list.rbegin();
+}
+
+jinf::pfid_list&
+jinf::get_pfid_list()
+{
+ if (!_analyzed_flag)
+ analyze();
+ return _pfid_list;
+}
+
+void
+jinf::get_normalized_pfid_list(pfid_list& pfid_list)
+{
+ if (!_analyzed_flag)
+ analyze();
+ pfid_list.clear();
+ u_int16_t s = _pfid_list.size();
+ u_int16_t iz = 0; // index of 0 value
+ while (_pfid_list[iz] && iz < s)
+ iz++;
+ assert(_pfid_list[iz] == 0);
+ for (u_int16_t i = iz; i < iz + s; i++)
+ pfid_list.push_back(_pfid_list[i % s]);
+ assert(pfid_list[0] == 0);
+ assert(pfid_list.size() == s);
+}
+
+bool
+jinf::get_initial_owi()
+{
+ if (!_analyzed_flag)
+ analyze();
+ return _initial_owi;
+}
+
+bool
+jinf::get_frot()
+{
+ if (!_analyzed_flag)
+ analyze();
+ return _frot;
+}
+
+std::string
+jinf::to_string() const
+{
+ std::ostringstream oss;
+ oss << std::setfill('0');
+ oss << "Journal ID \"" << _jid << "\" initialized " << (_tm_ptr->tm_year + 1900) << "/";
+ oss << std::setw(2) << (_tm_ptr->tm_mon + 1) << "/" << std::setw(2) << _tm_ptr->tm_mday << " ";
+ oss << std::setw(2) << _tm_ptr->tm_hour << ":" << std::setw(2) << _tm_ptr->tm_min << ":";
+ oss << std::setw(2) << _tm_ptr->tm_sec << "." << std::setw(9) << _ts.tv_nsec << ":" << std::endl;
+ oss << " Journal directory: \"" << _jdir << "\"" << std::endl;
+ oss << " Journal base filename: \"" << _base_filename << "\"" << std::endl;
+ oss << " Journal version: " << (unsigned)_jver << std::endl;
+ oss << " Number of journal files: " << _num_jfiles << std::endl;
+// TODO: Uncomment these lines when auto-expand is enabled.
+// oss << " Auto-expand mode: " << (_ae ? "enabled" : "disabled") << std::endl;
+// if (_ae) oss << " Max. number of journal files (in auto-expand mode): " << _ae_max_jfiles << std::endl;
+ oss << " Journal file size: " << _jfsize_sblks << " sblks" << std::endl;
+ oss << " Softblock size (JRNL_SBLK_SIZE): " << _sblk_size_dblks << " dblks" << std::endl;
+ oss << " Datablock size (JRNL_DBLK_SIZE): " << _dblk_size << " bytes" << std::endl;
+ oss << " Write page size: " << _wcache_pgsize_sblks << " sblks" << std::endl;
+ oss << " Number of write pages: " << _wcache_num_pages << std::endl;
+ oss << " Read page size (JRNL_RMGR_PAGE_SIZE): " << _rcache_pgsize_sblks << " sblks" << std::endl;
+ oss << " Number of read pages (JRNL_RMGR_PAGES): " << _rcache_num_pages << std::endl;
+ return oss.str();
+}
+
+std::string
+jinf::xml_str() const
+{
+ // TODO: This is *not* an XML writer, rather for simplicity, it uses literals. I'm sure a more elegant way can be
+ // found to do this using the real thing...
+
+ std::ostringstream oss;
+ oss << std::setfill('0');
+ oss << "<?xml version=\"1.0\" ?>" << std::endl;
+ oss << "<jrnl>" << std::endl;
+ oss << " <journal_version value=\"" << (unsigned)_jver << "\" />" << std::endl;
+ oss << " <journal_id>" << std::endl;
+ oss << " <id_string value=\"" << _jid << "\" />" << std::endl;
+ oss << " <directory value=\"" << _jdir << "\" />" << std::endl;
+ oss << " <base_filename value=\"" << _base_filename << "\" />" << std::endl;
+ oss << " </journal_id>" << std::endl;
+ oss << " <creation_time>" << std::endl;
+ oss << " <seconds value=\"" << _ts.tv_sec << "\" />" << std::endl;
+ oss << " <nanoseconds value=\"" << _ts.tv_nsec << "\" />" << std::endl;
+ oss << " <string value=\"" << (_tm_ptr->tm_year + 1900) << "/";
+ oss << std::setw(2) << (_tm_ptr->tm_mon + 1) << "/" << std::setw(2) << _tm_ptr->tm_mday << " ";
+ oss << std::setw(2) << _tm_ptr->tm_hour << ":" << std::setw(2) << _tm_ptr->tm_min << ":";
+ oss << std::setw(2) << _tm_ptr->tm_sec << "." << std::setw(9) << _ts.tv_nsec;
+ oss << "\" />" << std::endl;
+ oss << " </creation_time>" << std::endl;
+ oss << " <journal_file_geometry>" << std::endl;
+ oss << " <number_jrnl_files value=\"" << _num_jfiles << "\" />" << std::endl;
+ oss << " <auto_expand value=\"" << (_ae ? "true" : "false") << "\" />" << std::endl;
+ if (_ae) oss << " <auto_expand_max_jrnl_files value=\"" << _ae_max_jfiles << "\" />" << std::endl;
+ oss << " <jrnl_file_size_sblks value=\"" << _jfsize_sblks << "\" />" << std::endl;
+ oss << " <JRNL_SBLK_SIZE value=\"" << _sblk_size_dblks << "\" />" << std::endl;
+ oss << " <JRNL_DBLK_SIZE value=\"" << _dblk_size << "\" />" << std::endl;
+ oss << " </journal_file_geometry>" << std::endl;
+ oss << " <cache_geometry>" << std::endl;
+ oss << " <wcache_pgsize_sblks value=\"" << _wcache_pgsize_sblks << "\" />" << std::endl;
+ oss << " <wcache_num_pages value=\"" << _wcache_num_pages << "\" />" << std::endl;
+ oss << " <JRNL_RMGR_PAGE_SIZE value=\"" << _rcache_pgsize_sblks << "\" />" << std::endl;
+ oss << " <JRNL_RMGR_PAGES value=\"" << _rcache_num_pages << "\" />" << std::endl;
+ oss << " </cache_geometry>" << std::endl;
+ oss << "</jrnl>" << std::endl;
+ return oss.str();
+}
+
+void
+jinf::set_filename()
+{
+ std::ostringstream oss;
+ oss << _jdir << "/" << _base_filename << "." << JRNL_INFO_EXTENSION;
+ _filename = oss.str().c_str();
+}
+
+void
+jinf::read(const std::string& jinf_filename)
+{
+ // TODO: This is *not* an XML reader, rather for simplicity, it is a brute-force line reader which relies on string
+ // recognition. It relies on the format of xml_str() above; it will not handle a XML restructuring.
+ // *** Can it be replaced cheaply by a real XML reader? Should it be, or is this sufficient? ***
+
+ char buff[1024]; // limit of line input length
+ std::ifstream jinfs(jinf_filename.c_str());
+ if (!jinfs.good())
+ throw jexception(jerrno::JERR__FILEIO, jinf_filename.c_str(), "jinf", "read");
+ u_int32_t charcnt = 0;
+ while (jinfs.good())
+ {
+ jinfs.getline(buff, 1023);
+ charcnt += std::strlen(buff);
+ if (std::strstr(buff, "journal_version"))
+ _jver = u_int16_value(buff);
+ else if(std::strstr(buff, "id_string"))
+ string_value(_jid, buff);
+ else if(std::strstr(buff, "directory"))
+ string_value(_jdir, buff);
+ else if(std::strstr(buff, "base_filename"))
+ string_value(_base_filename, buff);
+ else if(std::strstr(buff, "number_jrnl_files"))
+ _num_jfiles = u_int16_value(buff);
+ else if(std::strstr(buff, "auto_expand_max_jrnl_files"))
+ _ae_max_jfiles = u_int16_value(buff);
+ else if(std::strstr(buff, "auto_expand"))
+ _ae = bool_value(buff);
+ else if(std::strstr(buff, "jrnl_file_size_sblks"))
+ _jfsize_sblks = u_int32_value(buff);
+ else if(std::strstr(buff, "JRNL_SBLK_SIZE"))
+ _sblk_size_dblks = u_int16_value(buff);
+ else if(std::strstr(buff, "JRNL_DBLK_SIZE"))
+ _dblk_size = u_int32_value(buff);
+ else if(std::strstr(buff, "wcache_pgsize_sblks"))
+ _wcache_pgsize_sblks = u_int32_value(buff);
+ else if(std::strstr(buff, "wcache_num_pages"))
+ _wcache_num_pages = u_int32_value(buff);
+ else if(std::strstr(buff, "JRNL_RMGR_PAGE_SIZE"))
+ _rcache_pgsize_sblks = u_int32_value(buff);
+ else if(std::strstr(buff, "JRNL_RMGR_PAGES"))
+ _rcache_num_pages = u_int32_value(buff);
+ else if(std::strstr(buff, "nanoseconds"))
+ _ts.tv_nsec = u_int32_value(buff);
+ else if(std::strstr(buff, "seconds"))
+ {
+ _ts.tv_sec = u_int32_value(buff);
+ _tm_ptr = std::localtime(&_ts.tv_sec);
+ }
+ }
+ jinfs.close();
+ if (charcnt == 0)
+ throw jexception(jerrno::JERR_JINF_ZEROLENFILE, jinf_filename.c_str(), "jinf", "read");
+}
+
+bool
+jinf::bool_value(char* line) const
+{
+ return std::strcmp(find_value(line), "true") == 0;
+}
+
+u_int16_t
+jinf::u_int16_value(char* line) const
+{
+ return std::atoi(find_value(line));
+}
+
+u_int32_t
+jinf::u_int32_value(char* line) const
+{
+ return std::atol(find_value(line));
+}
+
+std::string&
+jinf::string_value(std::string& str, char* line) const
+{
+ str.assign(find_value(line));
+ return str;
+}
+
+char*
+jinf::find_value(char* line) const
+{
+ const char* target1_str = "value=\"";
+ int target2_char = '\"';
+ char* t1 = std::strstr(line, target1_str);
+ if (t1 == 0)
+ {
+ std::ostringstream oss;
+ oss << "File \"" << _filename << "\": line=" << line;
+ throw jexception(jerrno::JERR_JINF_NOVALUESTR, oss.str(), "jinf", "find_value");
+ }
+ t1 += std::strlen(target1_str);
+
+ char* t2 = std::strchr(t1, target2_char);
+ if (t2 == 0)
+ {
+ std::ostringstream oss;
+ oss << "File \"" << _filename << "\": line=" << line;
+ throw jexception(jerrno::JERR_JINF_BADVALUESTR, oss.str(), "jinf", "find_value");
+ }
+ *t2 = '\0';
+ return t1;
+}
+
+u_int32_t
+jinf::get_filesize(const std::string& file_name) const
+{
+ struct stat s;
+ if (::stat(file_name.c_str(), &s))
+ {
+ std::ostringstream oss;
+ oss << "stat: file=\"" << file_name << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JINF_STAT, oss.str(), "jinf", "get_filesize");
+ }
+ if (!S_ISREG(s.st_mode)) // not a regular file,
+ {
+ std::ostringstream oss;
+ oss << "File \"" << file_name << "\" is not a regular file: mode=0x" << std::hex << s.st_mode;
+ throw jexception(jerrno::JERR_JINF_NOTREGFILE, oss.str(), "jinf", "get_filesize");
+ }
+ return u_int32_t(s.st_size);
+}
+
+} // namespace journal
+} // namespace mrg
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/jinf.h b/qpid/cpp/src/qpid/legacystore/jrnl/jinf.h
new file mode 100644
index 0000000000..73f5386a19
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/jinf.h
@@ -0,0 +1,133 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file jinf.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * This file contains the code for the mrg::journal::jinf class.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_JINF_H
+#define QPID_LEGACYSTORE_JRNL_JINF_H
+
+#include <ctime>
+#include <string>
+#include <sys/types.h>
+#include <vector>
+
+namespace mrg
+{
+namespace journal
+{
+ /**
+ * \class jinf
+ * \brief Class to handle the journal information file &lt;basename&gt;.jinf.
+ */
+ class jinf
+ {
+ public:
+ typedef std::vector<u_int16_t> pfid_list; // pfids
+ typedef pfid_list::const_iterator pfidl_citr;
+
+ private:
+ u_int8_t _jver;
+ std::string _jid;
+ std::string _jdir;
+ std::string _base_filename;
+ std::string _filename;
+ timespec _ts;
+ u_int16_t _num_jfiles;
+ bool _ae;
+ u_int32_t _ae_max_jfiles;
+ u_int32_t _jfsize_sblks;
+ u_int16_t _sblk_size_dblks;
+ u_int32_t _dblk_size;
+ u_int32_t _wcache_pgsize_sblks;
+ u_int16_t _wcache_num_pages;
+ u_int32_t _rcache_pgsize_sblks;
+ u_int16_t _rcache_num_pages;
+ std::tm* _tm_ptr;
+ bool _valid_flag;
+ bool _analyzed_flag;
+ pfid_list _pfid_list;
+ bool _initial_owi;
+ bool _frot;
+
+ public:
+ // constructor for reading existing jinf file
+ jinf(const std::string& jinf_filename, bool validate_flag);
+ // constructor for writing jinf file
+ jinf(const std::string& jid, const std::string& jdir, const std::string& base_filename,
+ const u_int16_t num_jfiles, const bool auto_expand, const u_int16_t ae_max_jfiles,
+ const u_int32_t jfsize_sblks, const u_int32_t wcache_pgsize_sblks, const u_int16_t wcache_num_pages,
+ const timespec& ts);
+ virtual ~jinf();
+
+ void validate();
+ void analyze();
+ void write();
+
+ inline u_int8_t jver() const { return _jver; }
+ inline const std::string& jid() const { return _jid; }
+ inline const std::string& jdir() const { return _jdir; }
+ inline void set_jdir(const std::string& jdir) { _jdir = jdir; }
+ inline const std::string& base_filename() const { return _base_filename; }
+ inline const timespec& ts() const { return _ts; }
+ inline u_int16_t num_jfiles() const { return _num_jfiles; }
+ u_int16_t incr_num_jfiles();
+ inline bool is_ae() const { return _ae; }
+ inline u_int16_t ae_max_jfiles() const { return _ae_max_jfiles; }
+ inline u_int32_t jfsize_sblks() const { return _jfsize_sblks; }
+ inline u_int16_t sblk_size_dblks() const { return _sblk_size_dblks; }
+ inline u_int32_t dblk_size() const { return _dblk_size; }
+ inline u_int32_t wcache_pgsize_sblks() const { return _wcache_pgsize_sblks; }
+ inline u_int16_t wcache_num_pages() const { return _wcache_num_pages; }
+ inline u_int32_t rcache_pgsize_sblks() const { return _rcache_pgsize_sblks; }
+ inline u_int16_t rcache_num_pages() const { return _rcache_num_pages; }
+ u_int16_t get_first_pfid();
+ u_int16_t get_last_pfid();
+ pfid_list& get_pfid_list();
+ void get_normalized_pfid_list(pfid_list& pfid_list);
+ bool get_initial_owi();
+ bool get_frot();
+
+ std::string to_string() const;
+ std::string xml_str() const;
+
+ private:
+ void set_filename();
+ void read(const std::string& jinf_filename);
+ bool bool_value(char* line) const;
+ u_int16_t u_int16_value(char* line) const;
+ u_int32_t u_int32_value(char* line) const;
+ std::string& string_value(std::string& str, char* line) const;
+ char* find_value(char* line) const;
+ u_int32_t get_filesize(const std::string& file_name) const;
+ };
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_JINF_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/jrec.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/jrec.cpp
new file mode 100644
index 0000000000..61b9b6cc9b
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/jrec.cpp
@@ -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.
+ *
+ */
+
+/**
+ * \file jrec.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing source code for class mrg::journal::jrec (abstract journal
+ * jrecord). See comments in file jrec.h for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#include "qpid/legacystore/jrnl/jrec.h"
+
+#include <iomanip>
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include "qpid/legacystore/jrnl/jexception.h"
+#include <sstream>
+
+namespace mrg
+{
+namespace journal
+{
+
+jrec::jrec() {}
+jrec::~jrec() {}
+
+void
+jrec::chk_hdr(const rec_hdr& hdr)
+{
+ if (hdr._magic == 0)
+ {
+ std::ostringstream oss;
+ oss << std::hex << std::setfill('0');
+ oss << "enq magic NULL: rid=0x" << hdr._rid;
+ throw jexception(jerrno::JERR_JREC_BADRECHDR, oss.str(), "jrec", "chk_hdr");
+ }
+ if (hdr._version != RHM_JDAT_VERSION)
+ {
+ std::ostringstream oss;
+ oss << std::hex << std::setfill('0');
+ oss << "version: rid=0x" << hdr._rid;
+ oss << ": expected=0x" << std::setw(2) << (int)RHM_JDAT_VERSION;
+ oss << " read=0x" << std::setw(2) << (int)hdr._version;
+ throw jexception(jerrno::JERR_JREC_BADRECHDR, oss.str(), "jrec", "chk_hdr");
+ }
+#if defined (JRNL_LITTLE_ENDIAN)
+ u_int8_t endian_flag = RHM_LENDIAN_FLAG;
+#else
+ u_int8_t endian_flag = RHM_BENDIAN_FLAG;
+#endif
+ if (hdr._eflag != endian_flag)
+ {
+ std::ostringstream oss;
+ oss << std::hex << std::setfill('0');
+ oss << "endian_flag: rid=" << hdr._rid;
+ oss << ": expected=0x" << std::setw(2) << (int)endian_flag;
+ oss << " read=0x" << std::setw(2) << (int)hdr._eflag;
+ throw jexception(jerrno::JERR_JREC_BADRECHDR, oss.str(), "jrec", "chk_hdr");
+ }
+}
+
+void
+jrec::chk_rid(const rec_hdr& hdr, const u_int64_t rid)
+{
+ if (hdr._rid != rid)
+ {
+ std::ostringstream oss;
+ oss << std::hex << std::setfill('0');
+ oss << "rid mismatch: expected=0x" << rid;
+ oss << " read=0x" << hdr._rid;
+ throw jexception(jerrno::JERR_JREC_BADRECHDR, oss.str(), "jrec", "chk_hdr");
+ }
+}
+
+void
+jrec::chk_tail(const rec_tail& tail, const rec_hdr& hdr)
+{
+ if (tail._xmagic != ~hdr._magic)
+ {
+ std::ostringstream oss;
+ oss << std::hex << std::setfill('0');
+ oss << "magic: rid=0x" << hdr._rid;
+ oss << ": expected=0x" << ~hdr._magic;
+ oss << " read=0x" << tail._xmagic;
+ throw jexception(jerrno::JERR_JREC_BADRECTAIL, oss.str(), "jrec", "chk_tail");
+ }
+ if (tail._rid != hdr._rid)
+ {
+ std::ostringstream oss;
+ oss << std::hex << std::setfill('0');
+ oss << "rid: rid=0x" << hdr._rid;
+ oss << ": read=0x" << tail._rid;
+ throw jexception(jerrno::JERR_JREC_BADRECTAIL, oss.str(), "jrec", "chk_tail");
+ }
+}
+
+} // namespace journal
+} // namespace mrg
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/jrec.h b/qpid/cpp/src/qpid/legacystore/jrnl/jrec.h
new file mode 100644
index 0000000000..9d0771cabd
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/jrec.h
@@ -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.
+ *
+ */
+
+/**
+ * \file jrec.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing source code for class mrg::journal::jrec (abstract journal
+ * jrecord). See class documentation for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_JREC_H
+#define QPID_LEGACYSTORE_JRNL_JREC_H
+
+namespace mrg
+{
+namespace journal
+{
+class jrec;
+}
+}
+
+#include <cstddef>
+#include <fstream>
+#include "qpid/legacystore/jrnl/rec_hdr.h"
+#include "qpid/legacystore/jrnl/rec_tail.h"
+#include <string>
+#include <sys/types.h>
+
+namespace mrg
+{
+namespace journal
+{
+
+ /**
+ * \class jrec
+ * \brief Abstract class for all file jrecords, both data and log. This class establishes
+ * the common data format and structure for these jrecords.
+ */
+ class jrec
+ {
+ public:
+ jrec();
+ virtual ~jrec();
+
+ /**
+ * \brief Encode this instance of jrec into the write buffer at the disk-block-aligned
+ * pointer wptr starting at position rec_offs_dblks in the encoded record to a
+ * maximum size of max_size_dblks.
+ *
+ * This call encodes the content of the data contianed in this instance of jrec into a
+ * disk-softblock-aligned (defined by JRNL_SBLK_SIZE) buffer pointed to by parameter
+ * wptr. No more than paramter max_size_dblks data-blocks may be written to the buffer.
+ * The parameter rec_offs_dblks is the offset in data-blocks within the fully encoded
+ * data block this instance represents at which to start encoding.
+ *
+ * Encoding entails writing the record header (struct enq_hdr), the data and the record tail
+ * (struct enq_tail). The record must be data-block-aligned (defined by JRNL_DBLK_SIZE),
+ * thus any remaining space in the final data-block is ignored; the returned value is the
+ * number of data-blocks consumed from the page by the encode action. Provided the initial
+ * alignment requirements are met, records may be of arbitrary size and may span multiple
+ * data-blocks, disk-blocks and/or pages.
+ *
+ * Since the record size in data-blocks is known, the general usage pattern is to call
+ * encode() as many times as is needed to fully encode the data. Each call to encode()
+ * will encode as much of the record as it can to what remains of the current page cache,
+ * and will return the number of data-blocks actually encoded.
+ *
+ * <b>Example:</b> Assume that record r1 was previously written to page 0, and that this
+ * is an instance representing record r2. Being larger than the page size ps, r2 would span
+ * multiple pages as follows:
+ * <pre>
+ * |<---ps--->|
+ * +----------+----------+----------+----...
+ * | |r2a| r2b | r2c | |
+ * |<-r1-><----------r2----------> |
+ * +----------+----------+----------+----...
+ * page: p0 p1 p2
+ * </pre>
+ * Encoding record r2 will require multiple calls to encode; one for each page which
+ * is involved. Record r2 is divided logically into sections r2a, r2b and r2c at the
+ * points where the page boundaries intersect with the record. Assuming a page size
+ * of ps, the page boundary pointers are represented by their names p0, p1... and the
+ * sizes of the record segments are represented by their names r1, r2a, r2b..., the calls
+ * should be as follows:
+ * <pre>
+ * encode(p0+r1, 0, ps-r1); (returns r2a data-blocks)
+ * encode(p1, r2a, ps); (returns r2b data-blocks which equals ps)
+ * encode(p2, r2a+r2b, ps); (returns r2c data-blocks)
+ * </pre>
+ *
+ * \param wptr Data-block-aligned pointer to position in page buffer where encoding is to
+ * take place.
+ * \param rec_offs_dblks Offset in data-blocks within record from which to start encoding.
+ * \param max_size_dblks Maximum number of data-blocks to write to pointer wptr.
+ * \returns Number of data-blocks encoded.
+ */
+ virtual u_int32_t encode(void* wptr, u_int32_t rec_offs_dblks,
+ u_int32_t max_size_dblks) = 0;
+
+ /**
+ * \brief Decode into this instance of jrec from the read buffer at the disk-block-aligned
+ * pointer rptr starting at position jrec_offs_dblks in the encoded record to a
+ * maximum size of max_size_blks.
+ *
+ * This call decodes a record in the page buffer pointed to by the data-block-aligned
+ * (defined by JRNL_DBLK_SIZE) parameter rptr into this instance of jrec. No more than
+ * paramter max_size_dblks data-blocks may be read from the buffer. The parameter
+ * jrec_offs_dblks is the offset in data-blocks within the encoded record at which to start
+ * decoding.
+ *
+ * Decoding entails reading the record header, the data and the tail. The record is
+ * data-block-aligned (defined by JRNL_DBLK_SIZE); the returned value is the number of
+ * data-blocks read from the buffer by the decode action. As the record data size is only
+ * known once the header is read, the number of calls required to complete reading the
+ * record will depend on the vlaues within this instance which are set when the
+ * header is decoded.
+ *
+ * A non-zero value for jrec_offs_dblks implies that this is not the first call to
+ * decode and the record data will be appended at this offset.
+ *
+ * \param h Reference to instance of struct hdr, already read from page buffer and used
+ * to determine record type
+ * \param rptr Data-block-aligned pointer to position in page buffer where decoding is to
+ * begin.
+ * \param rec_offs_dblks Offset within record from which to start appending the decoded
+ * record.
+ * \param max_size_dblks Maximum number of data-blocks to read from pointer rptr.
+ * \returns Number of data-blocks read (consumed).
+ */
+ virtual u_int32_t decode(rec_hdr& h, void* rptr, u_int32_t rec_offs_dblks,
+ u_int32_t max_size_dblks) = 0;
+
+ virtual bool rcv_decode(rec_hdr h, std::ifstream* ifsp, std::size_t& rec_offs) = 0;
+
+ virtual std::string& str(std::string& str) const = 0;
+ virtual std::size_t data_size() const = 0;
+ virtual std::size_t xid_size() const = 0;
+ virtual std::size_t rec_size() const = 0;
+ inline virtual u_int32_t rec_size_dblks() const { return size_dblks(rec_size()); }
+ static inline u_int32_t size_dblks(const std::size_t size)
+ { return size_blks(size, JRNL_DBLK_SIZE); }
+ static inline u_int32_t size_sblks(const std::size_t size)
+ { return size_blks(size, JRNL_DBLK_SIZE * JRNL_SBLK_SIZE); }
+ static inline u_int32_t size_blks(const std::size_t size, const std::size_t blksize)
+ { return (size + blksize - 1)/blksize; }
+ virtual u_int64_t rid() const = 0;
+
+ protected:
+ virtual void chk_hdr() const = 0;
+ virtual void chk_hdr(u_int64_t rid) const = 0;
+ virtual void chk_tail() const = 0;
+ static void chk_hdr(const rec_hdr& hdr);
+ static void chk_rid(const rec_hdr& hdr, u_int64_t rid);
+ static void chk_tail(const rec_tail& tail, const rec_hdr& hdr);
+ virtual void clean() = 0;
+ }; // class jrec
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_JREC_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/lp_map.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/lp_map.cpp
new file mode 100644
index 0000000000..8024ddadd2
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/lp_map.cpp
@@ -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.
+ *
+ */
+
+/**
+ * \file lp_map.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::lp_map (logical file map). See
+ * comments in file lp_map.h for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#include "qpid/legacystore/jrnl/lp_map.h"
+
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include "qpid/legacystore/jrnl/jexception.h"
+#include <sstream>
+
+namespace mrg
+{
+namespace journal
+{
+lp_map::lp_map() : _map() {}
+lp_map::~lp_map() {}
+
+void
+lp_map::insert(u_int16_t lfid, u_int16_t pfid)
+{
+ lfpair ip = lfpair(lfid, pfid);
+ lfret ret = _map.insert(ip);
+ if (ret.second == false)
+ {
+ std::ostringstream oss;
+ oss << std::hex << "lfid=0x" << lfid << " pfid=0x" << pfid;
+ throw jexception(jerrno::JERR_MAP_DUPLICATE, oss.str(), "lp_map", "insert");
+ }
+}
+
+void
+lp_map::get_pfid_list(std::vector<u_int16_t>& pfid_list)
+{
+ for (lp_map_citr_t i = _map.begin(); i != _map.end(); i++)
+ pfid_list.push_back(i->second);
+}
+
+// debug aid
+std::string
+lp_map::to_string()
+{
+ std::ostringstream oss;
+ oss << "{lfid:pfid ";
+ for (lp_map_citr_t i=_map.begin(); i!=_map.end(); i++)
+ {
+ if (i != _map.begin()) oss << ", ";
+ oss << (*i).first << ":" << (*i).second;
+ }
+ oss << "}";
+ return oss.str();
+}
+
+} // namespace journal
+} // namespace mrg
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/lp_map.h b/qpid/cpp/src/qpid/legacystore/jrnl/lp_map.h
new file mode 100644
index 0000000000..c43cbc0173
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/lp_map.h
@@ -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.
+ *
+ */
+
+/**
+ * \file lp_map.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::lp_map (logical file map).
+ * See class documentation for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_LP_MAP_H
+#define QPID_LEGACYSTORE_JRNL_LP_MAP_H
+
+#include <map>
+#include <string>
+#include <sys/types.h>
+#include <vector>
+
+namespace mrg
+{
+namespace journal
+{
+ /**
+ * \class lp_map
+ * \brief Maps the logical file id (lfid) to the physical file id (pfid) in the journal.
+ *
+ * NOTE: NOT THREAD SAFE
+ */
+ class lp_map
+ {
+ public:
+ typedef std::map<u_int16_t, u_int16_t> lp_map_t;
+ typedef lp_map_t::const_iterator lp_map_citr_t;
+ typedef lp_map_t::const_reverse_iterator lp_map_critr_t;
+
+ private:
+ typedef std::pair<u_int16_t, u_int16_t> lfpair;
+ typedef std::pair<lp_map_t::iterator, bool> lfret;
+ lp_map_t _map;
+
+ public:
+ lp_map();
+ virtual ~lp_map();
+
+ void insert(u_int16_t lfid, u_int16_t pfid);
+ inline u_int16_t size() const { return u_int16_t(_map.size()); }
+ inline bool empty() const { return _map.empty(); }
+ inline lp_map_citr_t begin() { return _map.begin(); }
+ inline lp_map_citr_t end() { return _map.end(); }
+ inline lp_map_critr_t rbegin() { return _map.rbegin(); }
+ inline lp_map_critr_t rend() { return _map.rend(); }
+ void get_pfid_list(std::vector<u_int16_t>& pfid_list);
+
+ // debug aid
+ std::string to_string();
+ };
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_LP_MAP_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/lpmgr.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/lpmgr.cpp
new file mode 100644
index 0000000000..d7b0c9f516
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/lpmgr.cpp
@@ -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.
+ *
+ */
+
+/**
+ * \file lpmgr.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::lpmgr (non-logging file
+ * handle), used for controlling journal log files. See comments in file
+ * lpmgr.h for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#include "qpid/legacystore/jrnl/lpmgr.h"
+
+#include <cassert>
+#include <qpid/legacystore/jrnl/jerrno.h>
+#include <qpid/legacystore/jrnl/jexception.h>
+
+namespace mrg
+{
+namespace journal
+{
+
+lpmgr::lpmgr() : _ae(false), _ae_max_jfiles(0)
+{}
+
+lpmgr::~lpmgr()
+{
+ finalize();
+}
+
+void
+lpmgr::initialize(const u_int16_t num_jfiles,
+ const bool ae,
+ const u_int16_t ae_max_jfiles,
+ jcntl* const jcp,
+ new_obj_fn_ptr fp)
+{
+ assert(jcp != 0);
+ finalize();
+
+ // Validate params
+ if (ae && ae_max_jfiles > 0 && ae_max_jfiles <= num_jfiles)
+ {
+ std::ostringstream oss;
+ oss << "ae_max_jfiles (" << ae_max_jfiles << ") <= num_jfiles (" << num_jfiles << ")";
+ throw jexception(jerrno::JERR_LFMGR_BADAEFNUMLIM, oss.str(), "lpmgr", "initialize");
+ }
+ _ae = ae;
+ _ae_max_jfiles = ae_max_jfiles;
+
+ const std::size_t num_res_files = ae
+ ? (ae_max_jfiles ? ae_max_jfiles : JRNL_MAX_NUM_FILES)
+ : num_jfiles;
+ _fcntl_arr.reserve(num_res_files);
+ append(jcp, fp, num_jfiles);
+}
+
+void
+lpmgr::recover(const rcvdat& rd,
+ jcntl* const jcp,
+ new_obj_fn_ptr fp)
+{
+ assert(jcp != 0);
+ finalize();
+
+ // Validate rd params
+ if (rd._aemjf > 0 && rd._aemjf <= rd._njf)
+ {
+ std::ostringstream oss;
+ oss << "ae_max_jfiles (" << rd._aemjf << ") <= num_jfiles (" << rd._njf << ")";
+ throw jexception(jerrno::JERR_LFMGR_BADAEFNUMLIM, oss.str(), "lpmgr", "recover");
+ }
+ _ae = rd._ae;
+ _ae_max_jfiles = rd._aemjf;
+
+ const std::size_t num_res_files = rd._ae
+ ? (rd._aemjf ? rd._aemjf : JRNL_MAX_NUM_FILES)
+ : rd._njf;
+ _fcntl_arr.reserve(num_res_files);
+ _fcntl_arr.assign(rd._njf, 0);
+ std::vector<u_int16_t> lfid_list(rd._fid_list.size(), 0);
+ for (std::size_t lid = 0; lid < rd._fid_list.size(); lid++)
+ lfid_list[rd._fid_list[lid]] = lid;
+ // NOTE: rd._fid_list may be smaller than rd._njf (journal may be empty or not yet file-cycled)
+ for (std::size_t pfid = 0; pfid < rd._njf; pfid++)
+ if (pfid < rd._fid_list.size())
+ _fcntl_arr[lfid_list[pfid]] = fp(jcp, lfid_list[pfid], pfid, &rd);
+ else
+ _fcntl_arr[pfid] = fp(jcp, pfid, pfid, &rd);
+}
+
+void
+lpmgr::insert(const u_int16_t after_lfid,
+ jcntl* const jcp,
+ new_obj_fn_ptr fp,
+ const u_int16_t num_jfiles)
+{
+ assert(jcp != 0);
+ assert(after_lfid < _fcntl_arr.size());
+ if (!_ae) throw jexception(jerrno::JERR_LFMGR_AEDISABLED, "lpmgr", "insert");
+ if (num_jfiles == 0) return;
+ std::size_t pfid = _fcntl_arr.size();
+ const u_int16_t eff_ae_max_jfiles = _ae_max_jfiles ? _ae_max_jfiles : JRNL_MAX_NUM_FILES;
+ if (pfid + num_jfiles > eff_ae_max_jfiles)
+ {
+ std::ostringstream oss;
+ oss << "num_files=" << pfid << " incr=" << num_jfiles << " limit=" << _ae_max_jfiles;
+ throw jexception(jerrno::JERR_LFMGR_AEFNUMLIMIT, oss.str(), "lpmgr", "insert");
+ }
+ for (std::size_t lid = after_lfid + 1; lid <= after_lfid + num_jfiles; lid++, pfid++)
+ _fcntl_arr.insert(_fcntl_arr.begin() + lid, fp(jcp, lid, pfid, 0));
+ for (std::size_t lid = after_lfid + num_jfiles + 1; lid < _fcntl_arr.size(); lid++)
+ {
+ fcntl* p = _fcntl_arr[lid];
+ assert(p != 0);
+ p->set_lfid(p->lfid() + num_jfiles);
+ }
+}
+
+void
+lpmgr::finalize()
+{
+ for (u_int32_t i = 0; i < _fcntl_arr.size(); i++)
+ delete _fcntl_arr[i];
+ _fcntl_arr.clear();
+ _ae = false;
+ _ae_max_jfiles = 0;
+}
+
+void
+lpmgr::set_ae(const bool ae)
+{
+ if (ae && _ae_max_jfiles > 0 && _ae_max_jfiles <= _fcntl_arr.size())
+ {
+ std::ostringstream oss;
+ oss << "ae_max_jfiles (" << _ae_max_jfiles << ") <= _fcntl_arr.size (" << _fcntl_arr.size() << ")";
+ throw jexception(jerrno::JERR_LFMGR_BADAEFNUMLIM, oss.str(), "lpmgr", "set_ae");
+ }
+ if (ae && _fcntl_arr.max_size() < _ae_max_jfiles)
+ _fcntl_arr.reserve(_ae_max_jfiles ? _ae_max_jfiles : JRNL_MAX_NUM_FILES);
+ _ae = ae;
+}
+
+void
+lpmgr::set_ae_max_jfiles(const u_int16_t ae_max_jfiles)
+{
+ if (_ae && ae_max_jfiles > 0 && ae_max_jfiles <= _fcntl_arr.size())
+ {
+ std::ostringstream oss;
+ oss << "ae_max_jfiles (" << _ae_max_jfiles << ") <= _fcntl_arr.size() (" << _fcntl_arr.size() << ")";
+ throw jexception(jerrno::JERR_LFMGR_BADAEFNUMLIM, oss.str(), "lpmgr", "set_ae_max_jfiles");
+ }
+ if (_ae && _fcntl_arr.max_size() < ae_max_jfiles)
+ _fcntl_arr.reserve(ae_max_jfiles ? ae_max_jfiles : JRNL_MAX_NUM_FILES);
+ _ae_max_jfiles = ae_max_jfiles;
+}
+
+u_int16_t
+lpmgr::ae_jfiles_rem() const
+{
+ if (_ae_max_jfiles > _fcntl_arr.size()) return _ae_max_jfiles - _fcntl_arr.size();
+ if (_ae_max_jfiles == 0) return JRNL_MAX_NUM_FILES - _fcntl_arr.size();
+ return 0;
+}
+
+// Testing functions
+
+void
+lpmgr::get_pfid_list(std::vector<u_int16_t>& pfid_list) const
+{
+ pfid_list.clear();
+ for (std::size_t i = 0; i < _fcntl_arr.size(); i++)
+ pfid_list.push_back(_fcntl_arr[i]->pfid());
+}
+
+void
+lpmgr::get_lfid_list(std::vector<u_int16_t>& lfid_list) const
+{
+ lfid_list.clear();
+ lfid_list.assign(_fcntl_arr.size(), 0);
+ for (std::size_t i = 0; i < _fcntl_arr.size(); i++)
+ lfid_list[_fcntl_arr[i]->pfid()] = i;
+}
+
+// === protected fns ===
+
+void
+lpmgr::append(jcntl* const jcp,
+ new_obj_fn_ptr fp,
+ const u_int16_t num_jfiles)
+{
+ std::size_t s = _fcntl_arr.size();
+ if (_ae_max_jfiles && s + num_jfiles > _ae_max_jfiles)
+ {
+ std::ostringstream oss;
+ oss << "num_files=" << s << " incr=" << num_jfiles << " limit=" << _ae_max_jfiles;
+ throw jexception(jerrno::JERR_LFMGR_AEFNUMLIMIT, oss.str(), "lpmgr", "append");
+ }
+ for (std::size_t i = s; i < s + num_jfiles; i++)
+ _fcntl_arr.push_back(fp(jcp, i, i, 0));
+}
+
+} // namespace journal
+} // namespace mrg
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/lpmgr.h b/qpid/cpp/src/qpid/legacystore/jrnl/lpmgr.h
new file mode 100644
index 0000000000..be5c4494cc
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/lpmgr.h
@@ -0,0 +1,303 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file lpmgr.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * Class mrg::journal::lpmgr. See class documentation for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_LPMGR_H
+#define QPID_LEGACYSTORE_JRNL_LPMGR_H
+
+namespace mrg
+{
+namespace journal
+{
+ class jcntl;
+ class lpmgr;
+}
+}
+
+#include "qpid/legacystore/jrnl/fcntl.h"
+#include <vector>
+
+namespace mrg
+{
+namespace journal
+{
+
+ /**
+ * \brief LFID-PFID manager. This class maps the logical file id (lfid) to the physical file id (pfid) so that files
+ * may be inserted into the file ring buffer in (nearly) arbitrary logical locations while the physical ids continue
+ * to be appended. NOTE: NOT THREAD SAFE.
+ *
+ * The entire functionality of the LFID-PFID manager is to maintain an array of pointers to fcntl objects which have
+ * a one-to-one relationship to the physical %journal files. The logical file id (lfid) is used as an index to the
+ * array to read the mapped physical file id (pfid). By altering the order of these pointers within the array, the
+ * mapping of logical to physical files may be altered. This can be used to allow for the logical insertion of
+ * %journal files into a ring buffer, even though the physical file ids must be appended to those that preceded them.
+ *
+ * Since the insert() operation uses after-lfid as its position parameter, it is not possible to insert before lfid
+ * 0 - i.e. It is only possible to insert after an existing lfid. Consequently, lfid 0 and pfid 0 are always
+ * coincident in a %journal. Note, however, that inserting before lfid 0 is logically equivilent to inserting after
+ * the last lfid.
+ *
+ * When one or more files are inserted after a particular lfid, the lfids of the following files are incremented. The
+ * pfids of the inserted files follow those of all existing files, thus leading to a lfid-pfid discreppancy (ie no
+ * longer a one-to-one mapping):
+ *
+ * Example: Before insertion, %journal file headers would look as follows:
+ * <pre>
+ * Logical view (sorted by lfid): Physical view (sorted by pfid):
+ * +---+---+---+---+---+---+ +---+---+---+---+---+---+
+ * pfid --> | 0 | 1 | 2 | 3 | 4 | 5 | pfid --> | 0 | 1 | 2 | 3 | 4 | 5 |
+ * lfid --> | 0 | 1 | 2 | 3 | 4 | 5 | lfid --> | 0 | 1 | 2 | 3 | 4 | 5 |
+ * +---+---+---+---+---+---+ +---+---+---+---+---+---+
+ * </pre>
+ *
+ * After insertion of 2 files after lid 2 (marked with *s):
+ * <pre>
+ * Logical view (sorted by lfid): Physical view (sorted by pfid):
+ * +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+
+ * pfid --> | 0 | 1 | 2 |*6*|*7*| 3 | 4 | 5 | pfid --> | 0 | 1 | 2 | 3 | 4 | 5 |*6*|*7*|
+ * lfid --> | 0 | 1 | 2 |*3*|*4*| 5 | 6 | 7 | lfid --> | 0 | 1 | 2 | 5 | 6 | 7 |*3*|*4*|
+ * +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+
+ * </pre>
+ *
+ * The insert() function updates the internal map immediately, but the physical files (which have both the pfid and
+ * lfid written into the file header) are only updated as they are overwritten in the normal course of enqueueing
+ * and dequeueing messages. If the %journal should fail after insertion but before the files following those inserted
+ * are overwritten, then duplicate lfids will be present (though no duplicate pfids are possible). The overwrite
+ * indicator (owi) flag and the pfid numbers may be used to resolve the ambiguity and determine the logically earlier
+ * lfid in this case.
+ *
+ * Example: Before insertion, the current active write file being lfid/pfid 2 as determined by the owi flag, %journal
+ * file headers would look as follows:
+ * <pre>
+ * Logical view (sorted by lfid): Physical view (sorted by pfid):
+ * +---+---+---+---+---+---+ +---+---+---+---+---+---+
+ * pfid --> | 0 | 1 | 2 | 3 | 4 | 5 | pfid --> | 0 | 1 | 2 | 3 | 4 | 5 |
+ * lfid --> | 0 | 1 | 2 | 3 | 4 | 5 | lfid --> | 0 | 1 | 2 | 3 | 4 | 5 |
+ * owi --> | t | t | t | f | f | f | owi --> | t | t | t | f | f | f |
+ * +---+---+---+---+---+---+ +---+---+---+---+---+---+
+ * </pre>
+ *
+ * After inserting 2 files after lfid 2 and then 3 (the newly inserted file) - marked with *s:
+ * <pre>
+ * Logical view (sorted by lfid): Physical view (sorted by pfid):
+ * +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+
+ * pfid --> | 0 | 1 | 2 |*6*|*7*| 3 | 4 | 5 | pfid --> | 0 | 1 | 2 | 3 | 4 | 5 |*3*|*4*|
+ * lfid --> | 0 | 1 | 2 |*3*|*4*| 3 | 4 | 5 | lfid --> | 0 | 1 | 2 | 3 | 4 | 5 |*3*|*4*|
+ * owi --> | t | t | t | t | t | f | f | f | owi --> | t | t | t | f | f | f | t | t |
+ * +---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+---+
+ * </pre>
+ *
+ * If a broker failure occurs at this point, then there are two independent tests that may be made to resolve
+ * duplicate lfids during recovery in such cases:
+ * <ol>
+ * <li>The correct lfid has owi flag that matches that of pfid/lfid 0</li>
+ * <li>The most recently inserted (hence correct) lfid has pfids that are higher than the duplicate that was not
+ * overwritten</li>
+ * </ol>
+ *
+ * NOTE: NOT THREAD SAFE. Provide external thread protection if used in multi-threaded environments.
+ */
+ class lpmgr
+ {
+ public:
+ /**
+ * \brief Function pointer to function that will create a new fcntl object and return its pointer.
+ *
+ * \param jcp Pointer to jcntl instance from which journal file details will be obtained.
+ * \param lfid Logical file ID for new fcntl instance.
+ * \param pfid Physical file ID for file associated with new fcntl instance.
+ * \param rdp Pointer to rcvdat instance which conatins recovery information for new fcntl instance when
+ * recovering an existing file, or null if a new file is to be created.
+ */
+ typedef fcntl* (new_obj_fn_ptr)(jcntl* const jcp,
+ const u_int16_t lfid,
+ const u_int16_t pfid,
+ const rcvdat* const rdp);
+
+ private:
+ bool _ae; ///< Auto-expand mode
+ u_int16_t _ae_max_jfiles; ///< Max file count for auto-expansion; 0 = no limit
+ std::vector<fcntl*> _fcntl_arr; ///< Array of pointers to fcntl objects
+
+ public:
+ lpmgr();
+ virtual ~lpmgr();
+
+ /**
+ * \brief Initialize from scratch for a known number of %journal files. All lfid values are identical to pfid
+ * values (which is normal before any inserts have occurred).
+ *
+ * \param num_jfiles Number of files to be created, and consequently the number of fcntl objects in array
+ * _fcntl_arr.
+ * \param ae If true, allows auto-expansion; if false, disables auto-expansion.
+ * \param ae_max_jfiles The maximum number of files allowed for auto-expansion. Cannot be lower than the current
+ * number of files. However, a zero value disables the limit checks, and allows unlimited
+ * expansion.
+ * \param jcp Pointer to jcntl instance. This is used to find the file path and base filename so that
+ * new files may be created.
+ * \param fp Pointer to function which creates and returns a pointer to a new fcntl object (and hence
+ * causes a new %journal file to be created).
+ */
+ void initialize(const u_int16_t num_jfiles,
+ const bool ae,
+ const u_int16_t ae_max_jfiles,
+ jcntl* const jcp,
+ new_obj_fn_ptr fp);
+
+ /**
+ * \brief Initialize from a known lfid-pfid map pfid_list (within rcvdat param rd), which is usually obtained
+ * from a recover. The index of pfid_list is the logical file id (lfid); the value contained in the vector is
+ * the physical file id (pfid).
+ *
+ * \param rd Ref to rcvdat struct which contains recovery data and the pfid_list.
+ * \param jcp Pointer to jcntl instance. This is used to find the file path and base filename so that
+ * new files may be created.
+ * \param fp Pointer to function which creates and returns a pointer to a new fcntl object (and hence
+ * causes a new %journal file to be created).
+ */
+ void recover(const rcvdat& rd,
+ jcntl* const jcp,
+ new_obj_fn_ptr fp);
+
+ /**
+ * \brief Insert num_jfiles files after lfid index after_lfid. This causes all lfids after after_lfid to be
+ * increased by num_jfiles.
+ *
+ * Note that it is not possible to insert <i>before</i> lfid 0, and thus lfid 0 should always point to pfid 0.
+ * Inserting before lfid 0 is logically equivilent to inserting after the last lfid in a circular buffer.
+ *
+ * \param after_lfid Lid index after which to insert file(s).
+ * \param jcp Pointer to jcntl instance. This is used to find the file path and base filename so that
+ * new files may be created.
+ * \param fp Pointer to function which creates and returns a pointer to a new fcntl object (and hence
+ * causes a new %journal file to be created).
+ * \param num_jfiles The number of files by which to increase.
+ */
+ void insert(const u_int16_t after_lfid,
+ jcntl* const jcp,
+ new_obj_fn_ptr fp,
+ const u_int16_t num_jfiles = 1);
+
+ /**
+ * \brief Clears _fcntl_arr and deletes all fcntl instances.
+ */
+ void finalize();
+
+ /**
+ * \brief Returns true if initialized; false otherwise. After construction, will return false until initialize()
+ * is called; thereafter true until finalize() is called, whereupon it will return false again.
+ *
+ * \return True if initialized; false otherwise.
+ */
+ inline bool is_init() const { return _fcntl_arr.size() > 0; }
+
+ /**
+ * \brief Returns true if auto-expand mode is enabled; false if not.
+ *
+ * \return True if auto-expand mode is enabled; false if not.
+ */
+ inline bool is_ae() const { return _ae; }
+
+ /**
+ * \brief Sets the auto-expand mode to enabled if ae is true, to disabled otherwise. The value of _ae_max_jfiles
+ * must be valid to succeed (i.e. _ae_max_jfiles must be greater than the current number of files or be zero).
+ *
+ * \param ae If true will enable auto-expand mode; if false will disable it.
+ */
+ void set_ae(const bool ae);
+
+ /**
+ * \brief Returns the number of %journal files, including any that were appended or inserted since
+ * initialization.
+ *
+ * \return Number of %journal files if initialized; 0 otherwise.
+ */
+ inline u_int16_t num_jfiles() const { return static_cast<u_int16_t>(_fcntl_arr.size()); }
+
+ /**
+ * \brief Returns the maximum number of files allowed for auto-expansion.
+ *
+ * \return Maximum number of files allowed for auto-expansion. A zero value represents a disabled limit
+ * - i.e. unlimited expansion.
+ */
+ inline u_int16_t ae_max_jfiles() const { return _ae_max_jfiles; }
+
+ /**
+ * \brief Sets the maximum number of files allowed for auto-expansion. A zero value disables the limit.
+ *
+ * \param ae_max_jfiles The maximum number of files allowed for auto-expansion. Cannot be lower than the current
+ * number of files. However, a zero value disables the limit checks, and allows unlimited
+ * expansion.
+ */
+ void set_ae_max_jfiles(const u_int16_t ae_max_jfiles);
+
+ /**
+ * \brief Calculates the number of future files available for auto-expansion.
+ *
+ * \return The number of future files available for auto-expansion.
+ */
+ u_int16_t ae_jfiles_rem() const;
+
+ /**
+ * \brief Get a pointer to fcntl instance for a given lfid.
+ *
+ * \return Pointer to fcntl object corresponding to logical file id lfid, or 0 if lfid is out of range
+ * (greater than number of files in use).
+ */
+ inline fcntl* get_fcntlp(const u_int16_t lfid) const
+ { if (lfid >= _fcntl_arr.size()) return 0; return _fcntl_arr[lfid]; }
+
+ // Testing functions
+ void get_pfid_list(std::vector<u_int16_t>& pfid_list) const;
+ void get_lfid_list(std::vector<u_int16_t>& lfid_list) const;
+
+ protected:
+
+ /**
+ * \brief Append num_jfiles files to the end of the logical and file id sequence. This is similar to extending
+ * the from-scratch initialization.
+ *
+ * \param jcp Pointer to jcntl instance. This is used to find the file path and base filename so that
+ * new files may be created.
+ * \param fp Pointer to function which creates and returns a pointer to a new fcntl object (and hence
+ * causes a new %journal file to be created).
+ * \param num_jfiles The number of files by which to increase.
+ */
+ void append(jcntl* const jcp,
+ new_obj_fn_ptr fp,
+ const u_int16_t num_jfiles = 1);
+
+ };
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_LPMGR_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/pmgr.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/pmgr.cpp
new file mode 100644
index 0000000000..3dc61e2661
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/pmgr.cpp
@@ -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.
+ *
+ */
+
+/**
+ * \file pmgr.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::pmgr (page manager). See
+ * comments in file pmgr.h for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#include "qpid/legacystore/jrnl/pmgr.h"
+
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include "qpid/legacystore/jrnl/jcfg.h"
+#include "qpid/legacystore/jrnl/jcntl.h"
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include <sstream>
+
+
+namespace mrg
+{
+namespace journal
+{
+
+pmgr::page_cb::page_cb(u_int16_t index):
+ _index(index),
+ _state(UNUSED),
+ _wdblks(0),
+ _rdblks(0),
+ _pdtokl(0),
+ _wfh(0),
+ _rfh(0),
+ _pbuff(0)
+{}
+
+const char*
+pmgr::page_cb::state_str() const
+{
+ switch(_state)
+ {
+ case UNUSED:
+ return "UNUSED";
+ case IN_USE:
+ return "IN_USE";
+ case AIO_PENDING:
+ return "AIO_PENDING";
+ case AIO_COMPLETE:
+ return "AIO_COMPLETE";
+ }
+ return "<unknown>";
+}
+
+const u_int32_t pmgr::_sblksize = JRNL_SBLK_SIZE * JRNL_DBLK_SIZE;
+
+pmgr::pmgr(jcntl* jc, enq_map& emap, txn_map& tmap):
+ _cache_pgsize_sblks(0),
+ _cache_num_pages(0),
+ _jc(jc),
+ _emap(emap),
+ _tmap(tmap),
+ _page_base_ptr(0),
+ _page_ptr_arr(0),
+ _page_cb_arr(0),
+ _aio_cb_arr(0),
+ _aio_event_arr(0),
+ _ioctx(0),
+ _pg_index(0),
+ _pg_cntr(0),
+ _pg_offset_dblks(0),
+ _aio_evt_rem(0),
+ _cbp(0),
+ _enq_rec(),
+ _deq_rec(),
+ _txn_rec()
+{}
+
+pmgr::~pmgr()
+{
+ pmgr::clean();
+}
+
+void
+pmgr::initialize(aio_callback* const cbp, const u_int32_t cache_pgsize_sblks, const u_int16_t cache_num_pages)
+{
+ // As static use of this class keeps old values around, clean up first...
+ pmgr::clean();
+ _pg_index = 0;
+ _pg_cntr = 0;
+ _pg_offset_dblks = 0;
+ _aio_evt_rem = 0;
+ _cache_pgsize_sblks = cache_pgsize_sblks;
+ _cache_num_pages = cache_num_pages;
+ _cbp = cbp;
+
+ // 1. Allocate page memory (as a single block)
+ std::size_t cache_pgsize = _cache_num_pages * _cache_pgsize_sblks * _sblksize;
+ if (::posix_memalign(&_page_base_ptr, _sblksize, cache_pgsize))
+ {
+ clean();
+ std::ostringstream oss;
+ oss << "posix_memalign(): blksize=" << _sblksize << " size=" << cache_pgsize;
+ oss << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR__MALLOC, oss.str(), "pmgr", "initialize");
+ }
+ // 2. Allocate array of page pointers
+ _page_ptr_arr = (void**)std::malloc(_cache_num_pages * sizeof(void*));
+ MALLOC_CHK(_page_ptr_arr, "_page_ptr_arr", "pmgr", "initialize");
+
+ // 3. Allocate and initilaize page control block (page_cb) array
+ _page_cb_arr = (page_cb*)std::malloc(_cache_num_pages * sizeof(page_cb));
+ MALLOC_CHK(_page_cb_arr, "_page_cb_arr", "pmgr", "initialize");
+ std::memset(_page_cb_arr, 0, _cache_num_pages * sizeof(page_cb));
+
+ // 5. Allocate IO control block (iocb) array
+ _aio_cb_arr = (aio_cb*)std::malloc(_cache_num_pages * sizeof(aio_cb));
+ MALLOC_CHK(_aio_cb_arr, "_aio_cb_arr", "pmgr", "initialize");
+
+ // 6. Set page pointers in _page_ptr_arr, _page_cb_arr and iocbs to pages within page block
+ for (u_int16_t i=0; i<_cache_num_pages; i++)
+ {
+ _page_ptr_arr[i] = (void*)((char*)_page_base_ptr + _cache_pgsize_sblks * _sblksize * i);
+ _page_cb_arr[i]._index = i;
+ _page_cb_arr[i]._state = UNUSED;
+ _page_cb_arr[i]._pbuff = _page_ptr_arr[i];
+ _page_cb_arr[i]._pdtokl = new std::deque<data_tok*>;
+ _page_cb_arr[i]._pdtokl->clear();
+ _aio_cb_arr[i].data = (void*)&_page_cb_arr[i];
+ }
+
+ // 7. Allocate io_event array, max one event per cache page plus one for each file
+ const u_int16_t max_aio_evts = _cache_num_pages + _jc->num_jfiles();
+ _aio_event_arr = (aio_event*)std::malloc(max_aio_evts * sizeof(aio_event));
+ MALLOC_CHK(_aio_event_arr, "_aio_event_arr", "pmgr", "initialize");
+
+ // 8. Initialize AIO context
+ if (int ret = aio::queue_init(max_aio_evts, &_ioctx))
+ {
+ std::ostringstream oss;
+ oss << "io_queue_init() failed: " << FORMAT_SYSERR(-ret);
+ throw jexception(jerrno::JERR__AIO, oss.str(), "pmgr", "initialize");
+ }
+}
+
+void
+pmgr::clean()
+{
+ // clean up allocated memory here
+
+ if (_ioctx)
+ aio::queue_release(_ioctx);
+
+ std::free(_page_base_ptr);
+ _page_base_ptr = 0;
+
+ if (_page_cb_arr)
+ {
+ for (int i=0; i<_cache_num_pages; i++)
+ delete _page_cb_arr[i]._pdtokl;
+ std::free(_page_ptr_arr);
+ _page_ptr_arr = 0;
+ }
+
+ std::free(_page_cb_arr);
+ _page_cb_arr = 0;
+
+ std::free(_aio_cb_arr);
+ _aio_cb_arr = 0;
+
+ std::free(_aio_event_arr);
+ _aio_event_arr = 0;
+}
+
+const char*
+pmgr::page_state_str(page_state ps)
+{
+ switch (ps)
+ {
+ case UNUSED:
+ return "UNUSED";
+ case IN_USE:
+ return "IN_USE";
+ case AIO_PENDING:
+ return "AIO_PENDING";
+ case AIO_COMPLETE:
+ return "AIO_COMPLETE";
+ }
+ return "<page_state unknown>";
+}
+
+} // namespace journal
+} // namespace mrg
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/pmgr.h b/qpid/cpp/src/qpid/legacystore/jrnl/pmgr.h
new file mode 100644
index 0000000000..64115e225e
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/pmgr.h
@@ -0,0 +1,142 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file pmgr.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::pmgr (page manager). See
+ * class documentation for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_PMGR_H
+#define QPID_LEGACYSTORE_JRNL_PMGR_H
+
+namespace mrg
+{
+namespace journal
+{
+ class pmgr;
+ class jcntl;
+}
+}
+
+#include <deque>
+#include "qpid/legacystore/jrnl/aio.h"
+#include "qpid/legacystore/jrnl/aio_callback.h"
+#include "qpid/legacystore/jrnl/data_tok.h"
+#include "qpid/legacystore/jrnl/deq_rec.h"
+#include "qpid/legacystore/jrnl/enq_map.h"
+#include "qpid/legacystore/jrnl/enq_rec.h"
+#include "qpid/legacystore/jrnl/fcntl.h"
+#include "qpid/legacystore/jrnl/txn_map.h"
+#include "qpid/legacystore/jrnl/txn_rec.h"
+
+namespace mrg
+{
+namespace journal
+{
+
+ /**
+ * \brief Abstract class for managing either read or write page cache of arbitrary size and
+ * number of cache_num_pages.
+ */
+ class pmgr
+ {
+ public:
+ /**
+ * \brief Enumeration of possible stats of a page within a page cache.
+ */
+ enum page_state
+ {
+ UNUSED, ///< A page is uninitialized, contains no data.
+ IN_USE, ///< Page is in use.
+ AIO_PENDING, ///< An AIO request outstanding.
+ AIO_COMPLETE ///< An AIO request is complete.
+ };
+
+ protected:
+ /**
+ * \brief Page control block, carries control and state information for each page in the
+ * cache.
+ */
+ struct page_cb
+ {
+ u_int16_t _index; ///< Index of this page
+ page_state _state; ///< Status of page
+ u_int64_t _frid; ///< First rid in page (used for fhdr init)
+ u_int32_t _wdblks; ///< Total number of dblks in page so far
+ u_int32_t _rdblks; ///< Total number of dblks in page
+ std::deque<data_tok*>* _pdtokl; ///< Page message tokens list
+ fcntl* _wfh; ///< File handle for incrementing write compl counts
+ fcntl* _rfh; ///< File handle for incrementing read compl counts
+ void* _pbuff; ///< Page buffer
+
+ page_cb(u_int16_t index); ///< Convenience constructor
+ const char* state_str() const; ///< Return state as string for this pcb
+ };
+
+ static const u_int32_t _sblksize; ///< Disk softblock size
+ u_int32_t _cache_pgsize_sblks; ///< Size of page cache cache_num_pages
+ u_int16_t _cache_num_pages; ///< Number of page cache cache_num_pages
+ jcntl* _jc; ///< Pointer to journal controller
+ enq_map& _emap; ///< Ref to enqueue map
+ txn_map& _tmap; ///< Ref to transaction map
+ void* _page_base_ptr; ///< Base pointer to page memory
+ void** _page_ptr_arr; ///< Array of pointers to cache_num_pages in page memory
+ page_cb* _page_cb_arr; ///< Array of page_cb structs
+ aio_cb* _aio_cb_arr; ///< Array of iocb structs
+ aio_event* _aio_event_arr; ///< Array of io_events
+ io_context_t _ioctx; ///< AIO context for read/write operations
+ u_int16_t _pg_index; ///< Index of current page being used
+ u_int32_t _pg_cntr; ///< Page counter; determines if file rotation req'd
+ u_int32_t _pg_offset_dblks; ///< Page offset (used so far) in data blocks
+ u_int32_t _aio_evt_rem; ///< Remaining AIO events
+ aio_callback* _cbp; ///< Pointer to callback object
+
+ enq_rec _enq_rec; ///< Enqueue record used for encoding/decoding
+ deq_rec _deq_rec; ///< Dequeue record used for encoding/decoding
+ txn_rec _txn_rec; ///< Transaction record used for encoding/decoding
+
+ public:
+ pmgr(jcntl* jc, enq_map& emap, txn_map& tmap);
+ virtual ~pmgr();
+
+ virtual int32_t get_events(page_state state, timespec* const timeout, bool flush = false) = 0;
+ inline u_int32_t get_aio_evt_rem() const { return _aio_evt_rem; }
+ static const char* page_state_str(page_state ps);
+ inline u_int32_t cache_pgsize_sblks() const { return _cache_pgsize_sblks; }
+ inline u_int16_t cache_num_pages() const { return _cache_num_pages; }
+
+ protected:
+ virtual void initialize(aio_callback* const cbp, const u_int32_t cache_pgsize_sblks,
+ const u_int16_t cache_num_pages);
+ virtual void rotate_page() = 0;
+ virtual void clean();
+ };
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_PMGR_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/rcvdat.h b/qpid/cpp/src/qpid/legacystore/jrnl/rcvdat.h
new file mode 100644
index 0000000000..a7ef2341f0
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/rcvdat.h
@@ -0,0 +1,181 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file rcvdat.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * Contains structure for recovery status and offset data.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_RCVDAT_H
+#define QPID_LEGACYSTORE_JRNL_RCVDAT_H
+
+#include <cstddef>
+#include <iomanip>
+#include <map>
+#include "qpid/legacystore/jrnl/jcfg.h"
+#include <sstream>
+#include <sys/types.h>
+#include <vector>
+
+namespace mrg
+{
+namespace journal
+{
+
+ struct rcvdat
+ {
+ u_int16_t _njf; ///< Number of journal files
+ bool _ae; ///< Auto-expand mode
+ u_int16_t _aemjf; ///< Auto-expand mode max journal files
+ bool _owi; ///< Overwrite indicator
+ bool _frot; ///< First rotation flag
+ bool _jempty; ///< Journal data files empty
+ u_int16_t _ffid; ///< First file id
+ std::size_t _fro; ///< First record offset in ffid
+ u_int16_t _lfid; ///< Last file id
+ std::size_t _eo; ///< End offset (first byte past last record)
+ u_int64_t _h_rid; ///< Highest rid found
+ bool _lffull; ///< Last file is full
+ bool _jfull; ///< Journal is full
+ std::vector<u_int16_t> _fid_list; ///< Fid-lid mapping - list of fids in order of lid
+ std::vector<u_int32_t> _enq_cnt_list; ///< Number enqueued records found for each file
+
+ rcvdat():
+ _njf(0),
+ _ae(false),
+ _aemjf(0),
+ _owi(false),
+ _frot(false),
+ _jempty(true),
+ _ffid(0),
+ _fro(0),
+ _lfid(0),
+ _eo(0),
+ _h_rid(0),
+ _lffull(false),
+ _jfull(false),
+ _fid_list(),
+ _enq_cnt_list()
+ {}
+
+ void reset(const u_int16_t num_jfiles, const bool auto_expand, const u_int16_t ae_max_jfiles)
+ {
+ _njf = num_jfiles;
+ _ae = auto_expand;
+ _aemjf = ae_max_jfiles;
+ _owi = false;
+ _frot = false;
+ _jempty = true;
+ _ffid = 0;
+ _fro = 0;
+ _lfid = 0;
+ _eo = 0;
+ _h_rid = 0;
+ _lffull = false;
+ _jfull = false;
+ _fid_list.clear();
+ _enq_cnt_list.clear();
+ _enq_cnt_list.resize(num_jfiles, 0);
+ }
+
+ // Find first fid with enqueued records
+ u_int16_t ffid()
+ {
+ u_int16_t index = _ffid;
+ while (index != _lfid && _enq_cnt_list[index] == 0)
+ {
+ if (++index >= _njf)
+ index = 0;
+ }
+ return index;
+ }
+
+ std::string to_string(const std::string& jid)
+ {
+ std::ostringstream oss;
+ oss << "Recover file analysis (jid=\"" << jid << "\"):" << std::endl;
+ oss << " Number of journal files (_njf) = " << _njf << std::endl;
+ oss << " Auto-expand mode (_ae) = " << (_ae ? "TRUE" : "FALSE") << std::endl;
+ if (_ae) oss << " Auto-expand mode max journal files (_aemjf) = " << _aemjf << std::endl;
+ oss << " Overwrite indicator (_owi) = " << (_owi ? "TRUE" : "FALSE") << std::endl;
+ oss << " First rotation (_frot) = " << (_frot ? "TRUE" : "FALSE") << std::endl;
+ oss << " Journal empty (_jempty) = " << (_jempty ? "TRUE" : "FALSE") << std::endl;
+ oss << " First (earliest) fid (_ffid) = " << _ffid << std::endl;
+ oss << " First record offset in first fid (_fro) = 0x" << std::hex << _fro <<
+ std::dec << " (" << (_fro/JRNL_DBLK_SIZE) << " dblks)" << std::endl;
+ oss << " Last (most recent) fid (_lfid) = " << _lfid << std::endl;
+ oss << " End offset (_eo) = 0x" << std::hex << _eo << std::dec << " (" <<
+ (_eo/JRNL_DBLK_SIZE) << " dblks)" << std::endl;
+ oss << " Highest rid (_h_rid) = 0x" << std::hex << _h_rid << std::dec << std::endl;
+ oss << " Last file full (_lffull) = " << (_lffull ? "TRUE" : "FALSE") << std::endl;
+ oss << " Journal full (_jfull) = " << (_jfull ? "TRUE" : "FALSE") << std::endl;
+ oss << " Normalized fid list (_fid_list) = [";
+ for (std::vector<u_int16_t>::const_iterator i = _fid_list.begin(); i < _fid_list.end(); i++)
+ {
+ if (i != _fid_list.begin()) oss << ", ";
+ oss << *i;
+ }
+ oss << "]" << std::endl;
+ oss << " Enqueued records (txn & non-txn):" << std::endl;
+ for (unsigned i=0; i<_enq_cnt_list.size(); i++)
+ oss << " File " << std::setw(2) << i << ": " << _enq_cnt_list[i] <<
+ std::endl;
+ return oss.str();
+ }
+
+ std::string to_log(const std::string& jid)
+ {
+ std::ostringstream oss;
+ oss << "Recover file analysis (jid=\"" << jid << "\"):";
+ oss << " njf=" << _njf;
+ oss << " ae=" << (_owi ? "T" : "F");
+ oss << " aemjf=" << _aemjf;
+ oss << " owi=" << (_ae ? "T" : "F");
+ oss << " frot=" << (_frot ? "T" : "F");
+ oss << " jempty=" << (_jempty ? "T" : "F");
+ oss << " ffid=" << _ffid;
+ oss << " fro=0x" << std::hex << _fro << std::dec << " (" <<
+ (_fro/JRNL_DBLK_SIZE) << " dblks)";
+ oss << " lfid=" << _lfid;
+ oss << " eo=0x" << std::hex << _eo << std::dec << " (" <<
+ (_eo/JRNL_DBLK_SIZE) << " dblks)";
+ oss << " h_rid=0x" << std::hex << _h_rid << std::dec;
+ oss << " lffull=" << (_lffull ? "T" : "F");
+ oss << " jfull=" << (_jfull ? "T" : "F");
+ oss << " Enqueued records (txn & non-txn): [ ";
+ for (unsigned i=0; i<_enq_cnt_list.size(); i++)
+ {
+ if (i) oss << " ";
+ oss << "fid_" << std::setw(2) << std::setfill('0') << i << "=" << _enq_cnt_list[i];
+ }
+ oss << " ]";
+ return oss.str();
+ }
+ };
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_RCVDAT_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/rec_hdr.h b/qpid/cpp/src/qpid/legacystore/jrnl/rec_hdr.h
new file mode 100644
index 0000000000..ff6325a760
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/rec_hdr.h
@@ -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.
+ *
+ */
+
+/**
+ * \file rec_hdr.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::rec_hdr (record header),
+ * which is a common initial header used for all journal record structures
+ * except the record tail (rec_tail).
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_REC_HDR_H
+#define QPID_LEGACYSTORE_JRNL_REC_HDR_H
+
+#include <cstddef>
+#include "qpid/legacystore/jrnl/jcfg.h"
+#include <sys/types.h>
+
+namespace mrg
+{
+namespace journal
+{
+
+#pragma pack(1)
+
+ /**
+ * \brief Struct for data common to the head of all journal files and records.
+ * This includes identification for the file type, the encoding version, endian
+ * indicator and a record ID.
+ *
+ * File header info in binary format (16 bytes):
+ * <pre>
+ * 0 7
+ * +---+---+---+---+---+---+---+---+
+ * | magic | v | e | flags |
+ * +---+---+---+---+---+---+---+---+
+ * | rid |
+ * +---+---+---+---+---+---+---+---+
+ * v = file version (If the format or encoding of this file changes, then this
+ * number should be incremented)
+ * e = endian flag, false (0x00) for little endian, true (0x01) for big endian
+ * </pre>
+ *
+ * Note that journal files should be transferable between 32- and 64-bit
+ * hardware of the same endianness, but not between hardware of opposite
+ * entianness without some sort of binary conversion utility. Thus buffering
+ * will be needed for types that change size between 32- and 64-bit compiles.
+ */
+ struct rec_hdr
+ {
+ u_int32_t _magic; ///< File type identifier (magic number)
+ u_int8_t _version; ///< File encoding version
+ u_int8_t _eflag; ///< Flag for determining endianness
+ u_int16_t _uflag; ///< User-defined flags
+ u_int64_t _rid; ///< Record ID (rotating 64-bit counter)
+
+ // Global flags
+ static const u_int16_t HDR_OVERWRITE_INDICATOR_MASK = 0x1;
+
+ // Convenience constructors and methods
+ /**
+ * \brief Default constructor, which sets all values to 0.
+ */
+ inline rec_hdr(): _magic(0), _version(0), _eflag(0), _uflag(0), _rid(0) {}
+
+ /**
+ * \brief Convenience constructor which initializes values during construction.
+ */
+ inline rec_hdr(const u_int32_t magic, const u_int8_t version, const u_int64_t rid,
+ const bool owi): _magic(magic), _version(version),
+#if defined(JRNL_BIG_ENDIAN)
+ _eflag(RHM_BENDIAN_FLAG),
+#else
+ _eflag(RHM_LENDIAN_FLAG),
+#endif
+ _uflag(owi ? HDR_OVERWRITE_INDICATOR_MASK : 0), _rid(rid) {}
+
+ /**
+ * \brief Convenience copy method.
+ */
+ inline void hdr_copy(const rec_hdr& h)
+ {
+ _magic = h._magic;
+ _version = h._version;
+ _eflag = h._eflag;
+ _uflag = h._uflag;
+ _rid =h._rid;
+ }
+
+ /**
+ * \brief Resets all fields to 0
+ */
+ inline void reset()
+ {
+ _magic = 0;
+ _version = 0;
+ _eflag = 0;
+ _uflag = 0;
+ _rid = 0;
+ }
+
+ inline bool get_owi() const { return _uflag & HDR_OVERWRITE_INDICATOR_MASK; }
+
+ inline void set_owi(const bool owi)
+ {
+ _uflag = owi ? _uflag | HDR_OVERWRITE_INDICATOR_MASK :
+ _uflag & (~HDR_OVERWRITE_INDICATOR_MASK);
+ }
+
+ /**
+ * \brief Returns the size of the header in bytes.
+ */
+ inline static std::size_t size() { return sizeof(rec_hdr); }
+ }; // struct rec_hdr
+
+#pragma pack()
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_REC_HDR_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/rec_tail.h b/qpid/cpp/src/qpid/legacystore/jrnl/rec_tail.h
new file mode 100644
index 0000000000..0c36151927
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/rec_tail.h
@@ -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.
+ *
+ */
+
+/**
+ * \file rec_tail.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::rec_tail (record tail), used to
+ * finalize a persistent record. The presence of a valid tail at the expected
+ * position in the journal file indicates that the record write was completed.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_REC_TAIL_H
+#define QPID_LEGACYSTORE_JRNL_REC_TAIL_H
+
+#include <cstddef>
+#include "qpid/legacystore/jrnl/jcfg.h"
+
+namespace mrg
+{
+namespace journal
+{
+
+#pragma pack(1)
+
+ /**
+ * \brief Struct for data common to the tail of all records. The magic number
+ * used here is the binary inverse (1's complement) of the magic used in the
+ * record header; this minimizes possible confusion with other headers that may
+ * be present during recovery. The tail is used with all records that have either
+ * XIDs or data - ie any size-variable content. Currently the only records that
+ * do NOT use the tail are non-transactional dequeues and filler records.
+ *
+ * Record header info in binary format (12 bytes):
+ * <pre>
+ * 0 7
+ * +---+---+---+---+---+---+---+---+
+ * | ~(magic) | rid |
+ * +---+---+---+---+---+---+---+---+
+ * | rid (con't) |
+ * +---+---+---+---+
+ * </pre>
+ */
+ struct rec_tail
+ {
+ u_int32_t _xmagic; ///< Binary inverse (1's complement) of hdr magic number
+ u_int64_t _rid; ///< ID (rotating 64-bit counter)
+
+
+ /**
+ * \brief Default constructor, which sets all values to 0.
+ */
+ inline rec_tail(): _xmagic(0xffffffff), _rid(0) {}
+
+ /**
+ * \brief Convenience constructor which initializes values during construction from
+ * existing enq_hdr instance.
+ */
+ inline rec_tail(const rec_hdr& h): _xmagic(~h._magic), _rid(h._rid) {}
+
+ /**
+ * \brief Convenience constructor which initializes values during construction.
+ */
+ inline rec_tail(const u_int32_t xmagic, const u_int64_t rid): _xmagic(xmagic), _rid(rid) {}
+
+ /**
+ * \brief Returns the size of the header in bytes.
+ */
+ inline static std::size_t size() { return sizeof(rec_tail); }
+ };
+
+#pragma pack()
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_REC_TAIL_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/rfc.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/rfc.cpp
new file mode 100644
index 0000000000..9b5ed95e81
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/rfc.cpp
@@ -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.
+ *
+ */
+
+/**
+ * \file rfc.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::rfc (rotating
+ * file controller). See comments in file rfc.h for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#include "qpid/legacystore/jrnl/rfc.h"
+
+#include <cassert>
+
+namespace mrg
+{
+namespace journal
+{
+
+rfc::rfc(const lpmgr* lpmp): _lpmp(lpmp), _fc_index(0), _curr_fc(0)
+{}
+
+rfc::~rfc()
+{}
+
+void
+rfc::finalize()
+{
+ unset_findex();
+}
+
+void
+rfc::set_findex(const u_int16_t fc_index)
+{
+ _fc_index = fc_index;
+ _curr_fc = _lpmp->get_fcntlp(fc_index);
+ _curr_fc->rd_reset();
+}
+
+void
+rfc::unset_findex()
+{
+ _fc_index = 0;
+ _curr_fc = 0;
+}
+
+std::string
+rfc::status_str() const
+{
+ if (!_lpmp->is_init())
+ return "state: Uninitialized";
+ if (_curr_fc == 0)
+ return "state: Inactive";
+ std::ostringstream oss;
+ oss << "state: Active";
+ return oss.str();
+}
+
+} // namespace journal
+} // namespace mrg
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/rfc.h b/qpid/cpp/src/qpid/legacystore/jrnl/rfc.h
new file mode 100644
index 0000000000..faa5d566ba
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/rfc.h
@@ -0,0 +1,193 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file rfc.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::rfc (rotating
+ * file controller). See class documentation for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_RFC_H
+#define QPID_LEGACYSTORE_JRNL_RFC_H
+
+namespace mrg
+{
+namespace journal
+{
+class rfc;
+}
+}
+
+#include "qpid/legacystore/jrnl/lpmgr.h"
+#include "qpid/legacystore/jrnl/enums.h"
+
+namespace mrg
+{
+namespace journal
+{
+
+ /**
+ * \class rfc
+ * \brief Rotating File Controller (rfc) - Class to handle the manangement of an array of file controllers (fcntl)
+ * objects for use in a circular disk buffer (journal). Each fcntl object corresponds to a file in the journal.
+ *
+ * The following states exist in this class:
+ *
+ * <pre>
+ * is_init() is_active()
+ * +===+ _lpmp.is_init() == false
+ * +---------->| | Uninitialized: _curr_fc == 0 F F
+ * | +-->+===+ --+
+ * | | |
+ * | | |
+ * | finalize() initialize()
+ * | | |
+ * | | |
+ * | +-- +===+<--+ _lpmp.is_init() == true
+ * finalize() | | Inactive: _curr_fc == 0 T F
+ * | +-->+===+ --+
+ * | | |
+ * | | |
+ * | unset_findex() set_findex()
+ * | | |
+ * | | |
+ * | +-- +===+<--+ _lpmp.is_init() == true
+ * +---------- | | Active: _curr_fc != 0 T T
+ * +===+
+ * </pre>
+ *
+ * The Uninitialized state is where the class starts after construction. Once the number of files is known and
+ * the array of file controllers allocated, then initialize() is called to set these, causing the state to move
+ * to Inactive.
+ *
+ * The Inactive state has the file controllers allocated and pointing to their respective journal files, but no
+ * current file controller has been selected. The pointer to the current file controller _curr_fc is null. Once the
+ * index of the active file is known, then calling set_findex() will set the index and internal pointer
+ * to the currently active file controller. This moves the state to Active.
+ *
+ * Note TODO: Comment on sync issues between change in num files in _lpmp and _fc_index/_curr_fc.
+ */
+ class rfc
+ {
+ protected:
+ const lpmgr* _lpmp; ///< Pointer to jcntl's lpmgr instance containing lfid/pfid map and fcntl objects
+ u_int16_t _fc_index; ///< Index of current file controller
+ fcntl* _curr_fc; ///< Pointer to current file controller
+
+ public:
+ rfc(const lpmgr* lpmp);
+ virtual ~rfc();
+
+ /**
+ * \brief Initialize the controller, moving from state Uninitialized to Inactive. The main function of
+ * initialize() is to set the number of files and the pointer to the fcntl array.
+ */
+ virtual inline void initialize() {}
+
+ /**
+ * \brief Reset the controller to Uninitialized state, usually called when the journal is stopped. Once called,
+ * initialize() must be called to reuse an instance.
+ */
+ virtual void finalize();
+
+ /**
+ * \brief Check initialization state: true = Not Uninitialized, ie Initialized or Active; false = Uninitialized.
+ */
+ virtual inline bool is_init() const { return _lpmp->is_init(); }
+
+ /**
+ * \brief Check active state: true = Initialized and _curr_fc not null; false otherwise.
+ */
+ virtual inline bool is_active() const { return _lpmp->is_init() && _curr_fc != 0; }
+
+ /**
+ * \brief Sets the current file index and active fcntl object. Moves to state Active.
+ */
+ virtual void set_findex(const u_int16_t fc_index);
+
+ /**
+ * \brief Nulls the current file index and active fcntl pointer, moves to state Inactive.
+ */
+ virtual void unset_findex();
+
+ /**
+ * \brief Rotate active file controller to next file in rotating file group.
+ * \exception jerrno::JERR__NINIT if called before calling initialize().
+ */
+ virtual iores rotate() = 0;
+
+ /**
+ * \brief Returns the index of the currently active file within the rotating file group.
+ */
+ inline u_int16_t index() const { return _fc_index; }
+
+ /**
+ * \brief Returns the currently active journal file controller within the rotating file group.
+ */
+ inline fcntl* file_controller() const { return _curr_fc; }
+
+ /**
+ * \brief Returns the currently active physical file id (pfid)
+ */
+ inline u_int16_t pfid() const { return _curr_fc->pfid(); }
+
+ // Convenience access methods to current file controller
+ // Note: Do not call when not in active state
+
+ inline u_int32_t enqcnt() const { return _curr_fc->enqcnt(); }
+ inline u_int32_t incr_enqcnt() { return _curr_fc->incr_enqcnt(); }
+ inline u_int32_t incr_enqcnt(const u_int16_t fid) { return _lpmp->get_fcntlp(fid)->incr_enqcnt(); }
+ inline u_int32_t add_enqcnt(const u_int32_t a) { return _curr_fc->add_enqcnt(a); }
+ inline u_int32_t add_enqcnt(const u_int16_t fid, const u_int32_t a)
+ { return _lpmp->get_fcntlp(fid)->add_enqcnt(a); }
+ inline u_int32_t decr_enqcnt(const u_int16_t fid) { return _lpmp->get_fcntlp(fid)->decr_enqcnt(); }
+ inline u_int32_t subtr_enqcnt(const u_int16_t fid, const u_int32_t s)
+ { return _lpmp->get_fcntlp(fid)->subtr_enqcnt(s); }
+
+ virtual inline u_int32_t subm_cnt_dblks() const = 0;
+ virtual inline std::size_t subm_offs() const = 0;
+ virtual inline u_int32_t add_subm_cnt_dblks(u_int32_t a) = 0;
+
+ virtual inline u_int32_t cmpl_cnt_dblks() const = 0;
+ virtual inline std::size_t cmpl_offs() const = 0;
+ virtual inline u_int32_t add_cmpl_cnt_dblks(u_int32_t a) = 0;
+
+ virtual inline bool is_void() const = 0;
+ virtual inline bool is_empty() const = 0;
+ virtual inline u_int32_t remaining_dblks() const = 0;
+ virtual inline bool is_full() const = 0;
+ virtual inline bool is_compl() const = 0;
+ virtual inline u_int32_t aio_outstanding_dblks() const = 0;
+ virtual inline bool file_rotate() const = 0;
+
+ // Debug aid
+ virtual std::string status_str() const;
+ }; // class rfc
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_RFC_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/rmgr.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/rmgr.cpp
new file mode 100644
index 0000000000..204affd1d1
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/rmgr.cpp
@@ -0,0 +1,698 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file rmgr.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::rmgr (read manager). See
+ * comments in file rmgr.h for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#include "qpid/legacystore/jrnl/rmgr.h"
+
+#include <cassert>
+#include <cerrno>
+#include <cstdlib>
+#include "qpid/legacystore/jrnl/jcntl.h"
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include <sstream>
+
+namespace mrg
+{
+namespace journal
+{
+
+rmgr::rmgr(jcntl* jc, enq_map& emap, txn_map& tmap, rrfc& rrfc):
+ pmgr(jc, emap, tmap),
+ _rrfc(rrfc),
+ _hdr(),
+ _fhdr_buffer(0),
+ _fhdr_aio_cb_ptr(0),
+ _fhdr_rd_outstanding(false)
+{}
+
+rmgr::~rmgr()
+{
+ rmgr::clean();
+}
+
+void
+rmgr::initialize(aio_callback* const cbp)
+{
+ pmgr::initialize(cbp, JRNL_RMGR_PAGE_SIZE, JRNL_RMGR_PAGES);
+ clean();
+ // Allocate memory for reading file header
+ if (::posix_memalign(&_fhdr_buffer, _sblksize, _sblksize))
+ {
+ std::ostringstream oss;
+ oss << "posix_memalign(): blksize=" << _sblksize << " size=" << _sblksize;
+ oss << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR__MALLOC, oss.str(), "rmgr", "initialize");
+ }
+ _fhdr_aio_cb_ptr = new aio_cb;
+ std::memset(_fhdr_aio_cb_ptr, 0, sizeof(aio_cb));
+}
+
+void
+rmgr::clean()
+{
+ std::free(_fhdr_buffer);
+ _fhdr_buffer = 0;
+
+ if (_fhdr_aio_cb_ptr)
+ {
+ delete _fhdr_aio_cb_ptr;
+ _fhdr_aio_cb_ptr = 0;
+ }
+}
+
+iores
+rmgr::read(void** const datapp, std::size_t& dsize, void** const xidpp, std::size_t& xidsize,
+ bool& transient, bool& external, data_tok* dtokp, bool ignore_pending_txns)
+{
+ iores res = pre_read_check(dtokp);
+ if (res != RHM_IORES_SUCCESS)
+ {
+ set_params_null(datapp, dsize, xidpp, xidsize);
+ return res;
+ }
+
+ if (dtokp->rstate() == data_tok::SKIP_PART)
+ {
+ if (_page_cb_arr[_pg_index]._state != AIO_COMPLETE)
+ {
+ aio_cycle(); // check if rd AIOs returned; initiate new reads if possible
+ return RHM_IORES_PAGE_AIOWAIT;
+ }
+ const iores res = skip(dtokp);
+ if (res != RHM_IORES_SUCCESS)
+ {
+ set_params_null(datapp, dsize, xidpp, xidsize);
+ return res;
+ }
+ }
+ if (dtokp->rstate() == data_tok::READ_PART)
+ {
+ assert(dtokp->rid() == _hdr._rid);
+ void* rptr = (void*)((char*)_page_ptr_arr[_pg_index] + (_pg_offset_dblks * JRNL_DBLK_SIZE));
+ const iores res = read_enq(_hdr, rptr, dtokp);
+ dsize = _enq_rec.get_data(datapp);
+ xidsize = _enq_rec.get_xid(xidpp);
+ transient = _enq_rec.is_transient();
+ external = _enq_rec.is_external();
+ return res;
+ }
+
+ set_params_null(datapp, dsize, xidpp, xidsize);
+ _hdr.reset();
+ // Read header, determine next record type
+ while (true)
+ {
+ if(dblks_rem() == 0 && _rrfc.is_compl() && !_rrfc.is_wr_aio_outstanding())
+ {
+ aio_cycle(); // check if rd AIOs returned; initiate new reads if possible
+ if(dblks_rem() == 0 && _rrfc.is_compl() && !_rrfc.is_wr_aio_outstanding())
+ {
+ if (_jc->unflushed_dblks() > 0)
+ _jc->flush();
+ else if (!_aio_evt_rem)
+ return RHM_IORES_EMPTY;
+ }
+ }
+ if (_page_cb_arr[_pg_index]._state != AIO_COMPLETE)
+ {
+ aio_cycle();
+ return RHM_IORES_PAGE_AIOWAIT;
+ }
+ void* rptr = (void*)((char*)_page_ptr_arr[_pg_index] + (_pg_offset_dblks * JRNL_DBLK_SIZE));
+ std::memcpy(&_hdr, rptr, sizeof(rec_hdr));
+ switch (_hdr._magic)
+ {
+ case RHM_JDAT_ENQ_MAGIC:
+ {
+ _enq_rec.reset(); // sets enqueue rec size
+ // Check if RID of this rec is still enqueued, if so read it, else skip
+ bool is_enq = false;
+ int16_t fid = _emap.get_pfid(_hdr._rid);
+ if (fid < enq_map::EMAP_OK)
+ {
+ bool enforce_txns = !_jc->is_read_only() && !ignore_pending_txns;
+ // Block read for transactionally locked record (only when not recovering)
+ if (fid == enq_map::EMAP_LOCKED && enforce_txns)
+ return RHM_IORES_TXPENDING;
+
+ // (Recover mode only) Ok, not in emap - now search tmap, if present then read
+ is_enq = _tmap.is_enq(_hdr._rid);
+ if (enforce_txns && is_enq)
+ return RHM_IORES_TXPENDING;
+ }
+ else
+ is_enq = true;
+
+ if (is_enq) // ok, this record is enqueued, check it, then read it...
+ {
+ if (dtokp->rid())
+ {
+ if (_hdr._rid != dtokp->rid())
+ {
+ std::ostringstream oss;
+ oss << std::hex << "rid=0x" << _hdr._rid << "; dtok_rid=0x" << dtokp->rid()
+ << "; dtok_id=0x" << dtokp->id();
+ throw jexception(jerrno::JERR_RMGR_RIDMISMATCH, oss.str(), "rmgr", "read");
+ }
+ }
+ else
+ dtokp->set_rid(_hdr._rid);
+
+// TODO: Add member _fid to pmgr::page_cb which indicates the fid from which this page was
+// populated. When this value is set in wmgr::flush() somewehere, then uncomment the following
+// check:
+// if (fid != _page_cb_arr[_pg_index]._fid)
+// {
+// std::ostringstream oss;
+// oss << std::hex << std::setfill('0');
+// oss << "rid=0x" << std::setw(16) << _hdr._rid;
+// oss << "; emap_fid=0x" << std::setw(4) << fid;
+// oss << "; current_fid=" << _rrfc.fid();
+// throw jexception(jerrno::JERR_RMGR_FIDMISMATCH, oss.str(), "rmgr",
+// "read");
+// }
+
+ const iores res = read_enq(_hdr, rptr, dtokp);
+ dsize = _enq_rec.get_data(datapp);
+ xidsize = _enq_rec.get_xid(xidpp);
+ transient = _enq_rec.is_transient();
+ external = _enq_rec.is_external();
+ return res;
+ }
+ else // skip this record, it is already dequeued
+ consume_xid_rec(_hdr, rptr, dtokp);
+ break;
+ }
+ case RHM_JDAT_DEQ_MAGIC:
+ consume_xid_rec(_hdr, rptr, dtokp);
+ break;
+ case RHM_JDAT_TXA_MAGIC:
+ consume_xid_rec(_hdr, rptr, dtokp);
+ break;
+ case RHM_JDAT_TXC_MAGIC:
+ consume_xid_rec(_hdr, rptr, dtokp);
+ break;
+ case RHM_JDAT_EMPTY_MAGIC:
+ consume_filler();
+ break;
+ default:
+ return RHM_IORES_EMPTY;
+ }
+ }
+}
+
+int32_t
+rmgr::get_events(page_state state, timespec* const timeout, bool flush)
+{
+ if (_aio_evt_rem == 0) // no events to get
+ return 0;
+
+ int32_t ret;
+ if ((ret = aio::getevents(_ioctx, flush ? _aio_evt_rem : 1, _aio_evt_rem/*_cache_num_pages + _jc->num_jfiles()*/, _aio_event_arr, timeout)) < 0)
+ {
+ if (ret == -EINTR) // Interrupted by signal
+ return 0;
+ std::ostringstream oss;
+ oss << "io_getevents() failed: " << std::strerror(-ret) << " (" << ret << ")";
+ throw jexception(jerrno::JERR__AIO, oss.str(), "rmgr", "get_events");
+ }
+ if (ret == 0 && timeout)
+ return jerrno::AIO_TIMEOUT;
+
+ std::vector<u_int16_t> pil;
+ pil.reserve(ret);
+ for (int i=0; i<ret; i++) // Index of returned AIOs
+ {
+ if (_aio_evt_rem == 0)
+ {
+ std::ostringstream oss;
+ oss << "_aio_evt_rem; evt " << (i + 1) << " of " << ret;
+ throw jexception(jerrno::JERR__UNDERFLOW, oss.str(), "rmgr", "get_events");
+ }
+ _aio_evt_rem--;
+ aio_cb* aiocbp = _aio_event_arr[i].obj; // This I/O control block (iocb)
+ page_cb* pcbp = (page_cb*)(aiocbp->data); // This page control block (pcb)
+ long aioret = (long)_aio_event_arr[i].res;
+ if (aioret < 0)
+ {
+ std::ostringstream oss;
+ oss << "AIO read operation failed: " << std::strerror(-aioret) << " (" << aioret << ")";
+ oss << " [pg=" << pcbp->_index << " buf=" << aiocbp->u.c.buf;
+ oss << " rsize=0x" << std::hex << aiocbp->u.c.nbytes;
+ oss << " offset=0x" << aiocbp->u.c.offset << std::dec;
+ oss << " fh=" << aiocbp->aio_fildes << "]";
+ throw jexception(jerrno::JERR__AIO, oss.str(), "rmgr", "get_events");
+ }
+
+ if (pcbp) // Page reads have pcb
+ {
+ if (pcbp->_rfh->rd_subm_cnt_dblks() >= JRNL_SBLK_SIZE) // Detects if write reset of this fcntl obj has occurred.
+ {
+ // Increment the completed read offset
+ // NOTE: We cannot use _rrfc here, as it may have rotated since submitting count.
+ // Use stored pointer to fcntl in the pcb instead.
+ pcbp->_rdblks = aiocbp->u.c.nbytes / JRNL_DBLK_SIZE;
+ pcbp->_rfh->add_rd_cmpl_cnt_dblks(pcbp->_rdblks);
+ pcbp->_state = state;
+ pil[i] = pcbp->_index;
+ }
+ }
+ else // File header reads have no pcb
+ {
+ std::memcpy(&_fhdr, _fhdr_buffer, sizeof(file_hdr));
+ _rrfc.add_cmpl_cnt_dblks(JRNL_SBLK_SIZE);
+
+ u_int32_t fro_dblks = (_fhdr._fro / JRNL_DBLK_SIZE) - JRNL_SBLK_SIZE;
+ // Check fro_dblks does not exceed the write pointers which can happen in some corrupted journal recoveries
+ if (fro_dblks > _jc->wr_subm_cnt_dblks(_fhdr._pfid) - JRNL_SBLK_SIZE)
+ fro_dblks = _jc->wr_subm_cnt_dblks(_fhdr._pfid) - JRNL_SBLK_SIZE;
+ _pg_cntr = fro_dblks / (JRNL_RMGR_PAGE_SIZE * JRNL_SBLK_SIZE);
+ u_int32_t tot_pg_offs_dblks = _pg_cntr * JRNL_RMGR_PAGE_SIZE * JRNL_SBLK_SIZE;
+ _pg_index = _pg_cntr % JRNL_RMGR_PAGES;
+ _pg_offset_dblks = fro_dblks - tot_pg_offs_dblks;
+ _rrfc.add_subm_cnt_dblks(tot_pg_offs_dblks);
+ _rrfc.add_cmpl_cnt_dblks(tot_pg_offs_dblks);
+
+ _fhdr_rd_outstanding = false;
+ _rrfc.set_valid();
+ }
+ }
+
+ // Perform AIO return callback
+ if (_cbp && ret)
+ _cbp->rd_aio_cb(pil);
+ return ret;
+}
+
+void
+rmgr::recover_complete()
+{}
+
+void
+rmgr::invalidate()
+{
+ if (_rrfc.is_valid())
+ _rrfc.set_invalid();
+}
+
+void
+rmgr::flush(timespec* timeout)
+{
+ // Wait for any outstanding AIO read operations to complete before synchronizing
+ while (_aio_evt_rem)
+ {
+ if (get_events(AIO_COMPLETE, timeout) == jerrno::AIO_TIMEOUT) // timed out, nothing returned
+ {
+ throw jexception(jerrno::JERR__TIMEOUT,
+ "Timed out waiting for outstanding read aio to return", "rmgr", "init_validation");
+ }
+ }
+
+ // Reset all read states and pointers
+ for (int i=0; i<_cache_num_pages; i++)
+ _page_cb_arr[i]._state = UNUSED;
+ _rrfc.unset_findex();
+ _pg_index = 0;
+ _pg_offset_dblks = 0;
+}
+
+bool
+rmgr::wait_for_validity(timespec* timeout, const bool throw_on_timeout)
+{
+ bool timed_out = false;
+ while (!_rrfc.is_valid() && !timed_out)
+ {
+ timed_out = get_events(AIO_COMPLETE, timeout) == jerrno::AIO_TIMEOUT;
+ if (timed_out && throw_on_timeout)
+ throw jexception(jerrno::JERR__TIMEOUT, "Timed out waiting for read validity", "rmgr", "wait_for_validity");
+ }
+ return _rrfc.is_valid();
+}
+
+iores
+rmgr::pre_read_check(data_tok* dtokp)
+{
+ if (_aio_evt_rem)
+ get_events(AIO_COMPLETE, 0);
+
+ if (!_rrfc.is_valid())
+ return RHM_IORES_RCINVALID;
+
+ // block reads until outstanding file header read completes as fro is needed to read
+ if (_fhdr_rd_outstanding)
+ return RHM_IORES_PAGE_AIOWAIT;
+
+ if(dblks_rem() == 0 && _rrfc.is_compl() && !_rrfc.is_wr_aio_outstanding())
+ {
+ aio_cycle(); // check if any AIOs have returned
+ if(dblks_rem() == 0 && _rrfc.is_compl() && !_rrfc.is_wr_aio_outstanding())
+ {
+ if (_jc->unflushed_dblks() > 0)
+ _jc->flush();
+ else if (!_aio_evt_rem)
+ return RHM_IORES_EMPTY;
+ }
+ }
+
+ // Check write state of this token is ENQ - required for read
+ if (dtokp)
+ {
+ if (!dtokp->is_readable())
+ {
+ std::ostringstream oss;
+ oss << std::hex << std::setfill('0');
+ oss << "dtok_id=0x" << std::setw(8) << dtokp->id();
+ oss << "; dtok_rid=0x" << std::setw(16) << dtokp->rid();
+ oss << "; dtok_wstate=" << dtokp->wstate_str();
+ throw jexception(jerrno::JERR_RMGR_ENQSTATE, oss.str(), "rmgr", "pre_read_check");
+ }
+ }
+
+ return RHM_IORES_SUCCESS;
+}
+
+iores
+rmgr::read_enq(rec_hdr& h, void* rptr, data_tok* dtokp)
+{
+ if (_page_cb_arr[_pg_index]._state != AIO_COMPLETE)
+ {
+ aio_cycle(); // check if any AIOs have returned
+ return RHM_IORES_PAGE_AIOWAIT;
+ }
+
+ // Read data from this page, first block will have header and data size.
+ u_int32_t dblks_rd = _enq_rec.decode(h, rptr, dtokp->dblocks_read(), dblks_rem());
+ dtokp->incr_dblocks_read(dblks_rd);
+
+ _pg_offset_dblks += dblks_rd;
+
+ // If data still incomplete, move to next page and decode again
+ while (dtokp->dblocks_read() < _enq_rec.rec_size_dblks())
+ {
+ rotate_page();
+ if (_page_cb_arr[_pg_index]._state != AIO_COMPLETE)
+ {
+ dtokp->set_rstate(data_tok::READ_PART);
+ dtokp->set_dsize(_enq_rec.data_size());
+ return RHM_IORES_PAGE_AIOWAIT;
+ }
+
+ rptr = (void*)((char*)_page_ptr_arr[_pg_index]);
+ dblks_rd = _enq_rec.decode(h, rptr, dtokp->dblocks_read(), dblks_rem());
+ dtokp->incr_dblocks_read(dblks_rd);
+ _pg_offset_dblks += dblks_rd;
+ }
+
+ // If we have finished with this page, rotate it
+ if (dblks_rem() == 0)
+ rotate_page();
+
+ // Set the record size in dtokp
+ dtokp->set_rstate(data_tok::READ);
+ dtokp->set_dsize(_enq_rec.data_size());
+ return RHM_IORES_SUCCESS;
+}
+
+void
+rmgr::consume_xid_rec(rec_hdr& h, void* rptr, data_tok* dtokp)
+{
+ if (h._magic == RHM_JDAT_ENQ_MAGIC)
+ {
+ enq_hdr ehdr;
+ std::memcpy(&ehdr, rptr, sizeof(enq_hdr));
+ if (ehdr.is_external())
+ dtokp->set_dsize(ehdr._xidsize + sizeof(enq_hdr) + sizeof(rec_tail));
+ else
+ dtokp->set_dsize(ehdr._xidsize + ehdr._dsize + sizeof(enq_hdr) + sizeof(rec_tail));
+ }
+ else if (h._magic == RHM_JDAT_DEQ_MAGIC)
+ {
+ deq_hdr dhdr;
+ std::memcpy(&dhdr, rptr, sizeof(deq_hdr));
+ if (dhdr._xidsize)
+ dtokp->set_dsize(dhdr._xidsize + sizeof(deq_hdr) + sizeof(rec_tail));
+ else
+ dtokp->set_dsize(sizeof(deq_hdr));
+ }
+ else if (h._magic == RHM_JDAT_TXA_MAGIC || h._magic == RHM_JDAT_TXC_MAGIC)
+ {
+ txn_hdr thdr;
+ std::memcpy(&thdr, rptr, sizeof(txn_hdr));
+ dtokp->set_dsize(thdr._xidsize + sizeof(txn_hdr) + sizeof(rec_tail));
+ }
+ else
+ {
+ std::ostringstream oss;
+ oss << "Record type found = \"" << (char*)&h._magic << "\"";
+ throw jexception(jerrno::JERR_RMGR_BADRECTYPE, oss.str(), "rmgr", "consume_xid_rec");
+ }
+ dtokp->set_dblocks_read(0);
+ skip(dtokp);
+}
+
+void
+rmgr::consume_filler()
+{
+ // Filler (Magic "RHMx") is one dblk by definition
+ _pg_offset_dblks++;
+ if (dblks_rem() == 0)
+ rotate_page();
+}
+
+iores
+rmgr::skip(data_tok* dtokp)
+{
+ u_int32_t dsize_dblks = jrec::size_dblks(dtokp->dsize());
+ u_int32_t tot_dblk_cnt = dtokp->dblocks_read();
+ while (true)
+ {
+ u_int32_t this_dblk_cnt = 0;
+ if (dsize_dblks - tot_dblk_cnt > dblks_rem())
+ this_dblk_cnt = dblks_rem();
+ else
+ this_dblk_cnt = dsize_dblks - tot_dblk_cnt;
+ if (this_dblk_cnt)
+ {
+ dtokp->incr_dblocks_read(this_dblk_cnt);
+ _pg_offset_dblks += this_dblk_cnt;
+ tot_dblk_cnt += this_dblk_cnt;
+ }
+ // If skip still incomplete, move to next page and decode again
+ if (tot_dblk_cnt < dsize_dblks)
+ {
+ if (dblks_rem() == 0)
+ rotate_page();
+ if (_page_cb_arr[_pg_index]._state != AIO_COMPLETE)
+ {
+ dtokp->set_rstate(data_tok::SKIP_PART);
+ return RHM_IORES_PAGE_AIOWAIT;
+ }
+ }
+ else
+ {
+ // Skip complete, put state back to unread
+ dtokp->set_rstate(data_tok::UNREAD);
+ dtokp->set_dsize(0);
+ dtokp->set_dblocks_read(0);
+
+ // If we have finished with this page, rotate it
+ if (dblks_rem() == 0)
+ rotate_page();
+ return RHM_IORES_SUCCESS;
+ }
+ }
+}
+
+iores
+rmgr::aio_cycle()
+{
+ // Perform validity checks
+ if (_fhdr_rd_outstanding) // read of file header still outstanding in aio
+ return RHM_IORES_SUCCESS;
+ if (!_rrfc.is_valid())
+ {
+ // Flush and reset all read states and pointers
+ flush(&jcntl::_aio_cmpl_timeout);
+
+ _jc->get_earliest_fid(); // determine initial file to read; calls _rrfc.set_findex() to set value
+ // If this file has not yet been written to, return RHM_IORES_EMPTY
+ if (_rrfc.is_void() && !_rrfc.is_wr_aio_outstanding())
+ return RHM_IORES_EMPTY;
+ init_file_header_read(); // send off AIO read request for file header
+ return RHM_IORES_SUCCESS;
+ }
+
+ int16_t first_uninit = -1;
+ u_int16_t num_uninit = 0;
+ u_int16_t num_compl = 0;
+ bool outstanding = false;
+ // Index must start with current buffer and cycle around so that first
+ // uninitialized buffer is initialized first
+ for (u_int16_t i=_pg_index; i<_pg_index+_cache_num_pages; i++)
+ {
+ int16_t ci = i % _cache_num_pages;
+ switch (_page_cb_arr[ci]._state)
+ {
+ case UNUSED:
+ if (first_uninit < 0)
+ first_uninit = ci;
+ num_uninit++;
+ break;
+ case IN_USE:
+ break;
+ case AIO_PENDING:
+ outstanding = true;
+ break;
+ case AIO_COMPLETE:
+ num_compl++;
+ break;
+ default:;
+ }
+ }
+ iores res = RHM_IORES_SUCCESS;
+ if (num_uninit)
+ res = init_aio_reads(first_uninit, num_uninit);
+ else if (num_compl == _cache_num_pages) // This condition exists after invalidation
+ res = init_aio_reads(0, _cache_num_pages);
+ if (outstanding)
+ get_events(AIO_COMPLETE, 0);
+ return res;
+}
+
+iores
+rmgr::init_aio_reads(const int16_t first_uninit, const u_int16_t num_uninit)
+{
+ for (int16_t i=0; i<num_uninit; i++)
+ {
+ if (_rrfc.is_void()) // Nothing to do; this file not yet written to
+ break;
+
+ if (_rrfc.subm_offs() == 0)
+ {
+ _rrfc.add_subm_cnt_dblks(JRNL_SBLK_SIZE);
+ _rrfc.add_cmpl_cnt_dblks(JRNL_SBLK_SIZE);
+ }
+
+ // TODO: Future perf improvement: Do a single AIO read for all available file
+ // space into all contiguous empty pages in one AIO operation.
+
+ u_int32_t file_rem_dblks = _rrfc.remaining_dblks();
+ file_rem_dblks -= file_rem_dblks % JRNL_SBLK_SIZE; // round down to closest sblk boundary
+ u_int32_t pg_size_dblks = JRNL_RMGR_PAGE_SIZE * JRNL_SBLK_SIZE;
+ u_int32_t rd_size = file_rem_dblks > pg_size_dblks ? pg_size_dblks : file_rem_dblks;
+ if (rd_size)
+ {
+ int16_t pi = (i + first_uninit) % _cache_num_pages;
+ // TODO: For perf, combine contiguous pages into single read
+ // 1 or 2 AIOs needed depending on whether read block folds
+ aio_cb* aiocbp = &_aio_cb_arr[pi];
+ aio::prep_pread_2(aiocbp, _rrfc.fh(), _page_ptr_arr[pi], rd_size * JRNL_DBLK_SIZE, _rrfc.subm_offs());
+ if (aio::submit(_ioctx, 1, &aiocbp) < 0)
+ throw jexception(jerrno::JERR__AIO, "rmgr", "init_aio_reads");
+ _rrfc.add_subm_cnt_dblks(rd_size);
+ _aio_evt_rem++;
+ _page_cb_arr[pi]._state = AIO_PENDING;
+ _page_cb_arr[pi]._rfh = _rrfc.file_controller();
+ }
+ else // If there is nothing to read for this page, neither will there be for the others...
+ break;
+ if (_rrfc.file_rotate())
+ _rrfc.rotate();
+ }
+ return RHM_IORES_SUCCESS;
+}
+
+void
+rmgr::rotate_page()
+{
+ _page_cb_arr[_pg_index]._rdblks = 0;
+ _page_cb_arr[_pg_index]._state = UNUSED;
+ if (_pg_offset_dblks >= JRNL_RMGR_PAGE_SIZE * JRNL_SBLK_SIZE)
+ {
+ _pg_offset_dblks = 0;
+ _pg_cntr++;
+ }
+ if (++_pg_index >= _cache_num_pages)
+ _pg_index = 0;
+ aio_cycle();
+ _pg_offset_dblks = 0;
+ // This counter is for bookkeeping only, page rotates are handled directly in init_aio_reads()
+ // FIXME: _pg_cntr should be sync'd with aio ops, not use of page as it is now...
+ // Need to move reset into if (_rrfc.file_rotate()) above.
+ if (_pg_cntr >= (_jc->jfsize_sblks() / JRNL_RMGR_PAGE_SIZE))
+ _pg_cntr = 0;
+}
+
+u_int32_t
+rmgr::dblks_rem() const
+{
+ return _page_cb_arr[_pg_index]._rdblks - _pg_offset_dblks;
+}
+
+void
+rmgr::set_params_null(void** const datapp, std::size_t& dsize, void** const xidpp, std::size_t& xidsize)
+{
+ *datapp = 0;
+ dsize = 0;
+ *xidpp = 0;
+ xidsize = 0;
+}
+
+void
+rmgr::init_file_header_read()
+{
+ _jc->fhdr_wr_sync(_rrfc.index()); // wait if the file header write is outstanding
+ int rfh = _rrfc.fh();
+ aio::prep_pread_2(_fhdr_aio_cb_ptr, rfh, _fhdr_buffer, _sblksize, 0);
+ if (aio::submit(_ioctx, 1, &_fhdr_aio_cb_ptr) < 0)
+ throw jexception(jerrno::JERR__AIO, "rmgr", "init_file_header_read");
+ _aio_evt_rem++;
+ _rrfc.add_subm_cnt_dblks(JRNL_SBLK_SIZE);
+ _fhdr_rd_outstanding = true;
+}
+
+/* TODO (sometime in the future)
+const iores
+rmgr::get(const u_int64_t& rid, const std::size_t& dsize, const std::size_t& dsize_avail,
+ const void** const data, bool auto_discard)
+{
+ return RHM_IORES_SUCCESS;
+}
+
+const iores
+rmgr::discard(data_tok* dtokp)
+{
+ return RHM_IORES_SUCCESS;
+}
+*/
+
+} // namespace journal
+} // namespace mrg
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/rmgr.h b/qpid/cpp/src/qpid/legacystore/jrnl/rmgr.h
new file mode 100644
index 0000000000..47a36ef35c
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/rmgr.h
@@ -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.
+ *
+ */
+
+/**
+ * \file rmgr.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::rmgr (read manager). See
+ * class documentation for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_RMGR_H
+#define QPID_LEGACYSTORE_JRNL_RMGR_H
+
+namespace mrg
+{
+namespace journal
+{
+class rmgr;
+}
+}
+
+#include <cstring>
+#include "qpid/legacystore/jrnl/enums.h"
+#include "qpid/legacystore/jrnl/file_hdr.h"
+#include "qpid/legacystore/jrnl/pmgr.h"
+#include "qpid/legacystore/jrnl/rec_hdr.h"
+#include "qpid/legacystore/jrnl/rrfc.h"
+
+namespace mrg
+{
+namespace journal
+{
+
+ /**
+ * \brief Class for managing a read page cache of arbitrary size and number of pages.
+ *
+ * The read page cache works on the principle of filling as many pages as possilbe in advance of
+ * reading the data. This ensures that delays caused by AIO operations are minimized.
+ */
+ class rmgr : public pmgr
+ {
+ private:
+ rrfc& _rrfc; ///< Ref to read rotating file controller
+ rec_hdr _hdr; ///< Header used to determind record type
+
+ void* _fhdr_buffer; ///< Buffer used for fhdr reads
+ aio_cb* _fhdr_aio_cb_ptr; ///< iocb pointer for fhdr reads
+ file_hdr _fhdr; ///< file header instance for reading file headers
+ bool _fhdr_rd_outstanding; ///< true if a fhdr read is outstanding
+
+ public:
+ rmgr(jcntl* jc, enq_map& emap, txn_map& tmap, rrfc& rrfc);
+ virtual ~rmgr();
+
+ using pmgr::initialize;
+ void initialize(aio_callback* const cbp);
+ iores read(void** const datapp, std::size_t& dsize, void** const xidpp,
+ std::size_t& xidsize, bool& transient, bool& external, data_tok* dtokp,
+ bool ignore_pending_txns);
+ int32_t get_events(page_state state, timespec* const timeout, bool flush = false);
+ void recover_complete();
+ inline iores synchronize() { if (_rrfc.is_valid()) return RHM_IORES_SUCCESS; return aio_cycle(); }
+ void invalidate();
+ bool wait_for_validity(timespec* const timeout, const bool throw_on_timeout = false);
+
+ /* TODO (if required)
+ const iores get(const u_int64_t& rid, const std::size_t& dsize, const std::size_t& dsize_avail,
+ const void** const data, bool auto_discard);
+ const iores discard(data_tok* dtok);
+ */
+
+ private:
+ void clean();
+ void flush(timespec* timeout);
+ iores pre_read_check(data_tok* dtokp);
+ iores read_enq(rec_hdr& h, void* rptr, data_tok* dtokp);
+ void consume_xid_rec(rec_hdr& h, void* rptr, data_tok* dtokp);
+ void consume_filler();
+ iores skip(data_tok* dtokp);
+ iores aio_cycle();
+ iores init_aio_reads(const int16_t first_uninit, const u_int16_t num_uninit);
+ void rotate_page();
+ u_int32_t dblks_rem() const;
+ void set_params_null(void** const datapp, std::size_t& dsize, void** const xidpp,
+ std::size_t& xidsize);
+ void init_file_header_read();
+ };
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_RMGR_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/rrfc.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/rrfc.cpp
new file mode 100644
index 0000000000..fc6f5d427f
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/rrfc.cpp
@@ -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.
+ *
+ */
+
+/**
+ * \file rrfc.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::rrfc (rotating
+ * file controller). See comments in file rrfc.h for details.
+ *
+ * \author Kim van der Riet
+ */
+
+
+#include "qpid/legacystore/jrnl/rrfc.h"
+
+#include <cerrno>
+#include <fcntl.h>
+#include <unistd.h>
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include "qpid/legacystore/jrnl/jexception.h"
+
+namespace mrg
+{
+namespace journal
+{
+
+rrfc::rrfc(const lpmgr* lpmp): rfc(lpmp), _fh(-1), _valid(false)
+{}
+
+rrfc::~rrfc()
+{
+ close_fh();
+}
+
+void
+rrfc::finalize()
+{
+ unset_findex();
+ rfc::finalize();
+}
+
+void
+rrfc::set_findex(const u_int16_t fc_index)
+{
+ rfc::set_findex(fc_index);
+ open_fh(_curr_fc->fname());
+}
+
+void
+rrfc::unset_findex()
+{
+ set_invalid();
+ close_fh();
+ rfc::unset_findex();
+}
+
+iores
+rrfc::rotate()
+{
+ if (!_lpmp->num_jfiles())
+ throw jexception(jerrno::JERR__NINIT, "rrfc", "rotate");
+ u_int16_t next_fc_index = _fc_index + 1;
+ if (next_fc_index == _lpmp->num_jfiles())
+ next_fc_index = 0;
+ set_findex(next_fc_index);
+ return RHM_IORES_SUCCESS;
+}
+
+std::string
+rrfc::status_str() const
+{
+ std::ostringstream oss;
+ oss << "rrfc: " << rfc::status_str();
+ if (is_active())
+ oss << " fcntl[" << _fc_index << "]: " << _curr_fc->status_str();
+ return oss.str();
+}
+
+// === protected functions ===
+
+void
+rrfc::open_fh(const std::string& fn)
+{
+ close_fh();
+ _fh = ::open(fn.c_str(), O_RDONLY | O_DIRECT);
+ if (_fh < 0)
+ {
+ std::ostringstream oss;
+ oss << "file=\"" << fn << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_RRFC_OPENRD, oss.str(), "rrfc", "open_fh");
+ }
+}
+
+void
+rrfc::close_fh()
+{
+ if (_fh >= 0)
+ {
+ ::close(_fh);
+ _fh = -1;
+ }
+}
+
+} // namespace journal
+} // namespace mrg
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/rrfc.h b/qpid/cpp/src/qpid/legacystore/jrnl/rrfc.h
new file mode 100644
index 0000000000..5066d6048a
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/rrfc.h
@@ -0,0 +1,179 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file rrfc.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::rrfc (rotating
+ * file controller). See class documentation for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_RRFC_H
+#define QPID_LEGACYSTORE_JRNL_RRFC_H
+
+namespace mrg
+{
+namespace journal
+{
+class rrfc;
+}
+}
+
+#include "qpid/legacystore/jrnl/fcntl.h"
+#include "qpid/legacystore/jrnl/rfc.h"
+
+namespace mrg
+{
+namespace journal
+{
+
+ /**
+ * \class rrfc
+ * \brief Read Rotating File Controller (rrfc) - Subclassed from pure virtual class rfc. Used to control the read
+ * pipeline in a rotating file buffer or journal. See class rfc for further details.
+ *
+ * The states that exist in this class are identical to class rfc from which it inherits, but in addition, the value
+ * of the read file handle _fh is also considered. The calls to set_findex also opens the file handle _fh to the
+ * active file for reading. Similarly, unset_findex() closes this file handle.
+ *
+ * <pre>
+ * is_init() is_active()
+ * +===+ _lpmp.is_init() == false
+ * +---------->| | Uninitialized: _curr_fc == 0 F F
+ * | +-->+===+ --+ _fh == -1
+ * | | |
+ * | | |
+ * | finalize() initialize()
+ * | | |
+ * | | |
+ * | +-- +===+<--+ _lpmp.is_init() == true
+ * finalize() | | Inactive: _curr_fc == 0 T F
+ * | +-->+===+ --+ _fh == -1
+ * | | |
+ * | | |
+ * | unset_findex() set_findex()
+ * | | |
+ * | | |
+ * | +-- +===+<--+ _lpmp.is_init() == true
+ * +---------- | | Active: _curr_fc != 0 T T
+ * +===+ _fh >= 0
+ * </pre>
+ *
+ * In adition to the states above, class rrfc contains a validity flag. This is operated indepenedently of the state
+ * machine. This flag (_valid) indicates when the read buffers are valid for reading. This is not strictly a state,
+ * but simply a flag used to keep track of the status, and is set/unset with calls to set_valid() and set_invalid()
+ * respectively.
+ */
+ class rrfc : public rfc
+ {
+ protected:
+ int _fh; ///< Read file handle
+ bool _valid; ///< Flag is true when read pages contain vailid data
+
+ public:
+ rrfc(const lpmgr* lpmp);
+ virtual ~rrfc();
+
+ /**
+ * \brief Initialize the controller, moving from state Uninitialized to Initialized. The main function of
+ * initialize() is to set the number of files and the pointer to the fcntl array.
+ */
+ inline void initialize() { rfc::initialize(); _valid = false; }
+
+ /**
+ * \brief Reset the controller to Uninitialized state, usually called when the journal is stopped. Once called,
+ * initialize() must be called to reuse an instance.
+ */
+ void finalize();
+
+ /**
+ * \brief Opens the file handle for reading a particular fid. Moves to state open.
+ */
+ void set_findex(const u_int16_t fc_index);
+
+ /**
+ * \brief Closes the read file handle and nulls the active fcntl pointer. Moves to state closed.
+ */
+ void unset_findex();
+
+ /**
+ * \brief Check the state: true = open; false = closed.
+ */
+ inline bool is_active() const { return _curr_fc != 0 && _fh >= 0; }
+
+ /**
+ * \brief Sets the validity flag which indicates that the read buffers contain valid data for reading.
+ */
+ inline void set_invalid() { _valid = false; }
+
+ /**
+ * \brief Resets the validity flag wich indicates that the read buffers are no longer synchronized and cannot
+ * be read whithout resynchronization.
+ */
+ inline void set_valid() { _valid = true; }
+
+ /**
+ * \brief Checks the read buffer validity status: true = valid, can be read; false = invalid, synchronization
+ * required.
+ */
+ inline bool is_valid() const { return _valid; }
+
+ /**
+ * \brief Rotate active file controller to next file in rotating file group.
+ * \exception jerrno::JERR__NINIT if called before calling initialize().
+ */
+ iores rotate();
+
+ inline int fh() const { return _fh; }
+
+ inline u_int32_t subm_cnt_dblks() const { return _curr_fc->rd_subm_cnt_dblks(); }
+ inline std::size_t subm_offs() const { return _curr_fc->rd_subm_offs(); }
+ inline u_int32_t add_subm_cnt_dblks(u_int32_t a) { return _curr_fc->add_rd_subm_cnt_dblks(a); }
+
+ inline u_int32_t cmpl_cnt_dblks() const { return _curr_fc->rd_cmpl_cnt_dblks(); }
+ inline std::size_t cmpl_offs() const { return _curr_fc->rd_cmpl_offs(); }
+ inline u_int32_t add_cmpl_cnt_dblks(u_int32_t a) { return _curr_fc->add_rd_cmpl_cnt_dblks(a); }
+
+ inline bool is_void() const { return _curr_fc->rd_void(); }
+ inline bool is_empty() const { return _curr_fc->rd_empty(); }
+ inline u_int32_t remaining_dblks() const { return _curr_fc->rd_remaining_dblks(); }
+ inline bool is_full() const { return _curr_fc->is_rd_full(); }
+ inline bool is_compl() const { return _curr_fc->is_rd_compl(); }
+ inline u_int32_t aio_outstanding_dblks() const { return _curr_fc->rd_aio_outstanding_dblks(); }
+ inline bool file_rotate() const { return _curr_fc->rd_file_rotate(); }
+ inline bool is_wr_aio_outstanding() const { return _curr_fc->wr_aio_outstanding_dblks() > 0; }
+
+ // Debug aid
+ std::string status_str() const;
+
+ protected:
+ void open_fh(const std::string& fn);
+ void close_fh();
+ }; // class rrfc
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_RRFC_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/slock.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/slock.cpp
new file mode 100644
index 0000000000..8f26d349ef
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/slock.cpp
@@ -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.
+ *
+ */
+
+/**
+ * \file slock.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::slock (scoped lock). See
+ * comments in file slock.h for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#include "qpid/legacystore/jrnl/slock.h"
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/slock.h b/qpid/cpp/src/qpid/legacystore/jrnl/slock.h
new file mode 100644
index 0000000000..c05b5cf336
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/slock.h
@@ -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.
+ *
+ */
+
+/**
+ * \file slock.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * Messaging journal scoped lock class mrg::journal::slock and scoped try-lock
+ * class mrg::journal::stlock.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_SLOCK_H
+#define QPID_LEGACYSTORE_JRNL_SLOCK_H
+
+#include "qpid/legacystore/jrnl/jexception.h"
+#include "qpid/legacystore/jrnl/smutex.h"
+#include <pthread.h>
+
+namespace mrg
+{
+namespace journal
+{
+
+ // Ultra-simple scoped lock class, auto-releases mutex when it goes out-of-scope
+ class slock
+ {
+ protected:
+ const smutex& _sm;
+ public:
+ inline slock(const smutex& sm) : _sm(sm)
+ {
+ PTHREAD_CHK(::pthread_mutex_lock(_sm.get()), "::pthread_mutex_lock", "slock", "slock");
+ }
+ inline ~slock()
+ {
+ PTHREAD_CHK(::pthread_mutex_unlock(_sm.get()), "::pthread_mutex_unlock", "slock", "~slock");
+ }
+ };
+
+ // Ultra-simple scoped try-lock class, auto-releases mutex when it goes out-of-scope
+ class stlock
+ {
+ protected:
+ const smutex& _sm;
+ bool _locked;
+ public:
+ inline stlock(const smutex& sm) : _sm(sm), _locked(false)
+ {
+ int ret = ::pthread_mutex_trylock(_sm.get());
+ _locked = (ret == 0); // check if lock obtained
+ if (!_locked && ret != EBUSY) PTHREAD_CHK(ret, "::pthread_mutex_trylock", "stlock", "stlock");
+ }
+ inline ~stlock()
+ {
+ if (_locked)
+ PTHREAD_CHK(::pthread_mutex_unlock(_sm.get()), "::pthread_mutex_unlock", "stlock", "~stlock");
+ }
+ inline bool locked() const { return _locked; }
+ };
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_SLOCK_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/smutex.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/smutex.cpp
new file mode 100644
index 0000000000..6f8991ca5b
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/smutex.cpp
@@ -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.
+ *
+ */
+
+/**
+ * \file smutex.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::smutex (scoped mutex). See
+ * comments in file smutex.h for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#include "qpid/legacystore/jrnl/smutex.h"
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/smutex.h b/qpid/cpp/src/qpid/legacystore/jrnl/smutex.h
new file mode 100644
index 0000000000..def0fb70f6
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/smutex.h
@@ -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.
+ *
+ */
+
+/**
+ * \file smutex.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * Messaging journal scoped mutex class mrg::journal::smutex.
+ *
+ * \author Kim van der Riet
+ */
+
+
+#ifndef QPID_LEGACYSTORE_JRNL_SMUTEX_H
+#define QPID_LEGACYSTORE_JRNL_SMUTEX_H
+
+#include "qpid/legacystore/jrnl/jexception.h"
+#include <pthread.h>
+
+namespace mrg
+{
+namespace journal
+{
+
+ // Ultra-simple scoped mutex class that allows a posix mutex to be initialized and destroyed with error checks
+ class smutex
+ {
+ protected:
+ mutable pthread_mutex_t _m;
+ public:
+ inline smutex()
+ {
+ PTHREAD_CHK(::pthread_mutex_init(&_m, 0), "::pthread_mutex_init", "smutex", "smutex");
+ }
+ inline virtual ~smutex()
+ {
+ PTHREAD_CHK(::pthread_mutex_destroy(&_m), "::pthread_mutex_destroy", "smutex", "~smutex");
+ }
+ inline pthread_mutex_t* get() const { return &_m; }
+ };
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_SMUTEX_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/time_ns.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/time_ns.cpp
new file mode 100644
index 0000000000..976068ef68
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/time_ns.cpp
@@ -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.
+ *
+ */
+
+/**
+ * \file time_ns.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * Messaging journal time struct mrg::journal::time_ns, derived from
+ * the timespec struct and provided with helper functions.
+ *
+ * \author Kim van der Riet
+ */
+
+#include "qpid/legacystore/jrnl/time_ns.h"
+
+#include <sstream>
+
+namespace mrg
+{
+namespace journal
+{
+
+const std::string
+time_ns::str(int precision) const
+{
+ const double t = tv_sec + (tv_nsec/1e9);
+ std::ostringstream oss;
+ oss.setf(std::ios::fixed, std::ios::floatfield);
+ oss.precision(precision);
+ oss << t;
+ return oss.str();
+}
+
+
+} // namespace journal
+} // namespace mrg
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/time_ns.h b/qpid/cpp/src/qpid/legacystore/jrnl/time_ns.h
new file mode 100644
index 0000000000..a9f69e2631
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/time_ns.h
@@ -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.
+ *
+ */
+
+/**
+ * \file time_ns.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * Messaging journal time struct mrg::journal::time_ns, derived from
+ * the timespec struct and provided with helper functions.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_TIME_NS_H
+#define QPID_LEGACYSTORE_JRNL_TIME_NS_H
+
+#include <cerrno>
+#include <ctime>
+#include <string>
+
+namespace mrg
+{
+namespace journal
+{
+
+struct time_ns : public timespec
+{
+ inline time_ns() { tv_sec = 0; tv_nsec = 0; }
+ inline time_ns(const std::time_t sec, const long nsec = 0) { tv_sec = sec; tv_nsec = nsec; }
+ inline time_ns(const time_ns& t) { tv_sec = t.tv_sec; tv_nsec = t.tv_nsec; }
+
+ inline void set_zero() { tv_sec = 0; tv_nsec = 0; }
+ inline bool is_zero() const { return tv_sec == 0 && tv_nsec == 0; }
+ inline int now() { if(::clock_gettime(CLOCK_REALTIME, this)) return errno; return 0; }
+ const std::string str(int precision = 6) const;
+
+ inline time_ns& operator=(const time_ns& rhs)
+ { tv_sec = rhs.tv_sec; tv_nsec = rhs.tv_nsec; return *this; }
+ inline time_ns& operator+=(const time_ns& rhs)
+ {
+ tv_nsec += rhs.tv_nsec;
+ if (tv_nsec >= 1000000000L) { tv_sec++; tv_nsec -= 1000000000L; }
+ tv_sec += rhs.tv_sec;
+ return *this;
+ }
+ inline time_ns& operator+=(const long ns)
+ {
+ tv_nsec += ns;
+ if (tv_nsec >= 1000000000L) { tv_sec++; tv_nsec -= 1000000000L; }
+ return *this;
+ }
+ inline time_ns& operator-=(const long ns)
+ {
+ tv_nsec -= ns;
+ if (tv_nsec < 0) { tv_sec--; tv_nsec += 1000000000L; }
+ return *this;
+ }
+ inline time_ns& operator-=(const time_ns& rhs)
+ {
+ tv_nsec -= rhs.tv_nsec;
+ if (tv_nsec < 0) { tv_sec--; tv_nsec += 1000000000L; }
+ tv_sec -= rhs.tv_sec;
+ return *this;
+ }
+ inline const time_ns operator+(const time_ns& rhs)
+ { time_ns t(*this); t += rhs; return t; }
+ inline const time_ns operator-(const time_ns& rhs)
+ { time_ns t(*this); t -= rhs; return t; }
+ inline bool operator==(const time_ns& rhs)
+ { return tv_sec == rhs.tv_sec && tv_nsec == rhs.tv_nsec; }
+ inline bool operator!=(const time_ns& rhs)
+ { return tv_sec != rhs.tv_sec || tv_nsec != rhs.tv_nsec; }
+ inline bool operator>(const time_ns& rhs)
+ { if(tv_sec == rhs.tv_sec) return tv_nsec > rhs.tv_nsec; return tv_sec > rhs.tv_sec; }
+ inline bool operator>=(const time_ns& rhs)
+ { if(tv_sec == rhs.tv_sec) return tv_nsec >= rhs.tv_nsec; return tv_sec >= rhs.tv_sec; }
+ inline bool operator<(const time_ns& rhs)
+ { if(tv_sec == rhs.tv_sec) return tv_nsec < rhs.tv_nsec; return tv_sec < rhs.tv_sec; }
+ inline bool operator<=(const time_ns& rhs)
+ { if(tv_sec == rhs.tv_sec) return tv_nsec <= rhs.tv_nsec; return tv_sec <= rhs.tv_sec; }
+};
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_TIME_NS_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/txn_hdr.h b/qpid/cpp/src/qpid/legacystore/jrnl/txn_hdr.h
new file mode 100644
index 0000000000..94b812ccec
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/txn_hdr.h
@@ -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.
+ *
+ */
+
+/**
+ * \file txn_hdr.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::txn_hdr (transaction
+ * record header), used to start a transaction (commit or abort) record.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_TXN_HDR_H
+#define QPID_LEGACYSTORE_JRNL_TXN_HDR_H
+
+#include <cstddef>
+#include "qpid/legacystore/jrnl/rec_hdr.h"
+
+namespace mrg
+{
+namespace journal
+{
+
+#pragma pack(1)
+
+ /**
+ * \brief Struct for transaction commit and abort records.
+ *
+ * Struct for DTX commit and abort records. Only the magic distinguishes between them. Since
+ * this record must be used in the context of a valid XID, the xidsize field must not be zero.
+ * Immediately following this record is the XID itself which is xidsize bytes long, followed by
+ * a rec_tail.
+ *
+ * Note that this record had its own rid distinct from the rids of the record(s) making up the
+ * transaction it is committing or aborting.
+ *
+ * Record header info in binary format (24 bytes):
+ * <pre>
+ * 0 7
+ * +---+---+---+---+---+---+---+---+ -+
+ * | magic | v | e | flags | |
+ * +---+---+---+---+---+---+---+---+ | struct hdr
+ * | rid | |
+ * +---+---+---+---+---+---+---+---+ -+
+ * | xidsize |
+ * +---+---+---+---+---+---+---+---+
+ * v = file version (If the format or encoding of this file changes, then this
+ * number should be incremented)
+ * e = endian flag, false (0x00) for little endian, true (0x01) for big endian
+ * </pre>
+ *
+ * Note that journal files should be transferable between 32- and 64-bit
+ * hardware of the same endianness, but not between hardware of opposite
+ * entianness without some sort of binary conversion utility. Thus buffering
+ * will be needed for types that change size between 32- and 64-bit compiles.
+ */
+ struct txn_hdr : rec_hdr
+ {
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ u_int32_t _filler0; ///< Big-endian filler for 32-bit size_t
+#endif
+ std::size_t _xidsize; ///< XID size
+#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT)
+ u_int32_t _filler0; ///< Little-endian filler for 32-bit size_t
+#endif
+
+ /**
+ * \brief Default constructor, which sets all values to 0.
+ */
+ txn_hdr(): rec_hdr(),
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ _filler0(0),
+#endif
+ _xidsize(0)
+#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT)
+ , _filler0(0)
+#endif
+ {}
+
+ /**
+ * \brief Convenience constructor which initializes values during construction.
+ */
+ txn_hdr(const u_int32_t magic, const u_int8_t version, const u_int64_t rid,
+ const std::size_t xidsize, const bool owi): rec_hdr(magic, version, rid, owi),
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ _filler0(0),
+#endif
+ _xidsize(xidsize)
+#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT)
+ , _filler0(0)
+#endif
+ {}
+
+ /**
+ * \brief Returns the size of the header in bytes.
+ */
+ inline static std::size_t size() { return sizeof(txn_hdr); }
+ };
+
+#pragma pack()
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_TXN_HDR_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/txn_map.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/txn_map.cpp
new file mode 100644
index 0000000000..c514670601
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/txn_map.cpp
@@ -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.
+ *
+ */
+
+/**
+ * \file txn_map.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::txn_map (transaction map). See
+ * comments in file txn_map.h for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#include "qpid/legacystore/jrnl/txn_map.h"
+
+#include <iomanip>
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include "qpid/legacystore/jrnl/jexception.h"
+#include "qpid/legacystore/jrnl/slock.h"
+#include <sstream>
+
+namespace mrg
+{
+namespace journal
+{
+
+// return/error codes
+int16_t txn_map::TMAP_RID_NOT_FOUND = -2;
+int16_t txn_map::TMAP_XID_NOT_FOUND = -1;
+int16_t txn_map::TMAP_OK = 0;
+int16_t txn_map::TMAP_NOT_SYNCED = 0;
+int16_t txn_map::TMAP_SYNCED = 1;
+
+txn_data_struct::txn_data_struct(const u_int64_t rid, const u_int64_t drid, const u_int16_t pfid,
+ const bool enq_flag, const bool commit_flag):
+ _rid(rid),
+ _drid(drid),
+ _pfid(pfid),
+ _enq_flag(enq_flag),
+ _commit_flag(commit_flag),
+ _aio_compl(false)
+{}
+
+txn_map::txn_map():
+ _map(),
+ _pfid_txn_cnt()
+{}
+
+txn_map::~txn_map() {}
+
+void
+txn_map::set_num_jfiles(const u_int16_t num_jfiles)
+{
+ _pfid_txn_cnt.resize(num_jfiles, 0);
+}
+
+u_int32_t
+txn_map::get_txn_pfid_cnt(const u_int16_t pfid) const
+{
+ return _pfid_txn_cnt.at(pfid);
+}
+
+bool
+txn_map::insert_txn_data(const std::string& xid, const txn_data& td)
+{
+ bool ok = true;
+ slock s(_mutex);
+ xmap_itr itr = _map.find(xid);
+ if (itr == _map.end()) // not found in map
+ {
+ txn_data_list list;
+ list.push_back(td);
+ std::pair<xmap_itr, bool> ret = _map.insert(xmap_param(xid, list));
+ if (!ret.second) // duplicate
+ ok = false;
+ }
+ else
+ itr->second.push_back(td);
+ _pfid_txn_cnt.at(td._pfid)++;
+ return ok;
+}
+
+const txn_data_list
+txn_map::get_tdata_list(const std::string& xid)
+{
+ slock s(_mutex);
+ return get_tdata_list_nolock(xid);
+}
+
+const txn_data_list
+txn_map::get_tdata_list_nolock(const std::string& xid)
+{
+ xmap_itr itr = _map.find(xid);
+ if (itr == _map.end()) // not found in map
+ return _empty_data_list;
+ return itr->second;
+}
+
+const txn_data_list
+txn_map::get_remove_tdata_list(const std::string& xid)
+{
+ slock s(_mutex);
+ xmap_itr itr = _map.find(xid);
+ if (itr == _map.end()) // not found in map
+ return _empty_data_list;
+ txn_data_list list = itr->second;
+ _map.erase(itr);
+ for (tdl_itr i=list.begin(); i!=list.end(); i++)
+ _pfid_txn_cnt.at(i->_pfid)--;
+ return list;
+}
+
+bool
+txn_map::in_map(const std::string& xid)
+{
+ slock s(_mutex);
+ xmap_itr itr= _map.find(xid);
+ return itr != _map.end();
+}
+
+u_int32_t
+txn_map::enq_cnt()
+{
+ return cnt(true);
+}
+
+u_int32_t
+txn_map::deq_cnt()
+{
+ return cnt(true);
+}
+
+u_int32_t
+txn_map::cnt(const bool enq_flag)
+{
+ slock s(_mutex);
+ u_int32_t c = 0;
+ for (xmap_itr i = _map.begin(); i != _map.end(); i++)
+ {
+ for (tdl_itr j = i->second.begin(); j < i->second.end(); j++)
+ {
+ if (j->_enq_flag == enq_flag)
+ c++;
+ }
+ }
+ return c;
+}
+
+int16_t
+txn_map::is_txn_synced(const std::string& xid)
+{
+ slock s(_mutex);
+ xmap_itr itr = _map.find(xid);
+ if (itr == _map.end()) // not found in map
+ return TMAP_XID_NOT_FOUND;
+ bool is_synced = true;
+ for (tdl_itr litr = itr->second.begin(); litr < itr->second.end(); litr++)
+ {
+ if (!litr->_aio_compl)
+ {
+ is_synced = false;
+ break;
+ }
+ }
+ return is_synced ? TMAP_SYNCED : TMAP_NOT_SYNCED;
+}
+
+int16_t
+txn_map::set_aio_compl(const std::string& xid, const u_int64_t rid)
+{
+ slock s(_mutex);
+ xmap_itr itr = _map.find(xid);
+ if (itr == _map.end()) // xid not found in map
+ return TMAP_XID_NOT_FOUND;
+ for (tdl_itr litr = itr->second.begin(); litr < itr->second.end(); litr++)
+ {
+ if (litr->_rid == rid)
+ {
+ litr->_aio_compl = true;
+ return TMAP_OK; // rid found
+ }
+ }
+ // xid present, but rid not found
+ return TMAP_RID_NOT_FOUND;
+}
+
+bool
+txn_map::data_exists(const std::string& xid, const u_int64_t rid)
+{
+ bool found = false;
+ {
+ slock s(_mutex);
+ txn_data_list tdl = get_tdata_list_nolock(xid);
+ tdl_itr itr = tdl.begin();
+ while (itr != tdl.end() && !found)
+ {
+ found = itr->_rid == rid;
+ itr++;
+ }
+ }
+ return found;
+}
+
+bool
+txn_map::is_enq(const u_int64_t rid)
+{
+ bool found = false;
+ {
+ slock s(_mutex);
+ for (xmap_itr i = _map.begin(); i != _map.end() && !found; i++)
+ {
+ txn_data_list list = i->second;
+ for (tdl_itr j = list.begin(); j < list.end() && !found; j++)
+ {
+ if (j->_enq_flag)
+ found = j->_rid == rid;
+ else
+ found = j->_drid == rid;
+ }
+ }
+ }
+ return found;
+}
+
+void
+txn_map::xid_list(std::vector<std::string>& xv)
+{
+ xv.clear();
+ {
+ slock s(_mutex);
+ for (xmap_itr itr = _map.begin(); itr != _map.end(); itr++)
+ xv.push_back(itr->first);
+ }
+}
+
+} // namespace journal
+} // namespace mrg
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/txn_map.h b/qpid/cpp/src/qpid/legacystore/jrnl/txn_map.h
new file mode 100644
index 0000000000..6b38564e53
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/txn_map.h
@@ -0,0 +1,159 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file txn_map.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::txn_map (transaction map).
+ * See class documentation for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_TXN_MAP_H
+#define QPID_LEGACYSTORE_JRNL_TXN_MAP_H
+
+namespace mrg
+{
+namespace journal
+{
+ class txn_map;
+}
+}
+
+#include "qpid/legacystore/jrnl/smutex.h"
+#include <map>
+#include <pthread.h>
+#include <string>
+#include <sys/types.h>
+#include <vector>
+
+namespace mrg
+{
+namespace journal
+{
+
+ /**
+ * \struct txn_data_struct
+ * \brief Struct encapsulating transaction data necessary for processing a transaction
+ * in the journal once it is closed with either a commit or abort.
+ */
+ struct txn_data_struct
+ {
+ u_int64_t _rid; ///< Record id for this operation
+ u_int64_t _drid; ///< Dequeue record id for this operation
+ u_int16_t _pfid; ///< Physical file id, to be used when transferring to emap on commit
+ bool _enq_flag; ///< If true, enq op, otherwise deq op
+ bool _commit_flag; ///< (2PC transactions) Records 2PC complete c/a mode
+ bool _aio_compl; ///< Initially false, set to true when record AIO returns
+ txn_data_struct(const u_int64_t rid, const u_int64_t drid, const u_int16_t pfid,
+ const bool enq_flag, const bool commit_flag = false);
+ };
+ typedef txn_data_struct txn_data;
+ typedef std::vector<txn_data> txn_data_list;
+ typedef txn_data_list::iterator tdl_itr;
+
+ /**
+ * \class txn_map
+ * \brief Class for storing transaction data for each open (ie not committed or aborted)
+ * xid in the store. If aborted, records are discarded; if committed, they are
+ * transferred to the enqueue map.
+ *
+ * The data is encapsulated by struct txn_data_struct. A vector containing the information
+ * for each operation included as part of the same transaction is mapped against the
+ * xid.
+ *
+ * The aio_compl flag is set true as each AIO write operation for the enqueue or dequeue
+ * returns. Checking that all of these flags are true for a given xid is the mechanism
+ * used to determine if the transaction is syncronized (through method is_txn_synced()).
+ *
+ * On transaction commit, then each operation is handled as follows:
+ *
+ * If an enqueue (_enq_flag is true), then the rid and pfid are transferred to the enq_map.
+ * If a dequeue (_enq_flag is false), then the rid stored in the drid field is used to
+ * remove the corresponding record from the enq_map.
+ *
+ * On transaction abort, then each operation is handled as follows:
+ *
+ * If an enqueue (_enq_flag is true), then the data is simply discarded.
+ * If a dequeue (_enq_flag is false), then the lock for the corresponding enqueue in enq_map
+ * (if not a part of the same transaction) is removed, and the data discarded.
+ *
+ * <pre>
+ * key data
+ *
+ * xid1 --- vector< [ rid, drid, pfid, enq_flag, commit_flag, aio_compl ] >
+ * xid2 --- vector< [ rid, drid, pfid, enq_flag, commit_flag, aio_compl ] >
+ * xid3 --- vector< [ rid, drid, pfid, enq_flag, commit_flag, aio_compl ] >
+ * ...
+ * </pre>
+ */
+ class txn_map
+ {
+ public:
+ // return/error codes
+ static int16_t TMAP_RID_NOT_FOUND;
+ static int16_t TMAP_XID_NOT_FOUND;
+ static int16_t TMAP_OK;
+ static int16_t TMAP_NOT_SYNCED;
+ static int16_t TMAP_SYNCED;
+
+ private:
+ typedef std::pair<std::string, txn_data_list> xmap_param;
+ typedef std::map<std::string, txn_data_list> xmap;
+ typedef xmap::iterator xmap_itr;
+
+ xmap _map;
+ smutex _mutex;
+ std::vector<u_int32_t> _pfid_txn_cnt;
+ const txn_data_list _empty_data_list;
+
+ public:
+ txn_map();
+ virtual ~txn_map();
+
+ void set_num_jfiles(const u_int16_t num_jfiles);
+ u_int32_t get_txn_pfid_cnt(const u_int16_t pfid) const;
+ bool insert_txn_data(const std::string& xid, const txn_data& td);
+ const txn_data_list get_tdata_list(const std::string& xid);
+ const txn_data_list get_remove_tdata_list(const std::string& xid);
+ bool in_map(const std::string& xid);
+ u_int32_t enq_cnt();
+ u_int32_t deq_cnt();
+ int16_t is_txn_synced(const std::string& xid); // -1=xid not found; 0=not synced; 1=synced
+ int16_t set_aio_compl(const std::string& xid, const u_int64_t rid); // -2=rid not found; -1=xid not found; 0=done
+ bool data_exists(const std::string& xid, const u_int64_t rid);
+ bool is_enq(const u_int64_t rid);
+ inline void clear() { _map.clear(); }
+ inline bool empty() const { return _map.empty(); }
+ inline size_t size() const { return _map.size(); }
+ void xid_list(std::vector<std::string>& xv);
+ private:
+ u_int32_t cnt(const bool enq_flag);
+ const txn_data_list get_tdata_list_nolock(const std::string& xid);
+ };
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_TXN_MAP_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/txn_rec.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/txn_rec.cpp
new file mode 100644
index 0000000000..1008e7700e
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/txn_rec.cpp
@@ -0,0 +1,448 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file txn_rec.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * This file contains the code for the mrg::journal::txn_rec (journal dequeue
+ * record) class. See comments in file txn_rec.h for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#include "qpid/legacystore/jrnl/txn_rec.h"
+
+#include <cassert>
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include <iomanip>
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include "qpid/legacystore/jrnl/jexception.h"
+#include <sstream>
+
+namespace mrg
+{
+namespace journal
+{
+
+txn_rec::txn_rec():
+ _txn_hdr(),
+ _xidp(0),
+ _buff(0),
+ _txn_tail()
+{
+ _txn_hdr._version = RHM_JDAT_VERSION;
+}
+
+txn_rec::txn_rec(const u_int32_t magic, const u_int64_t rid, const void* const xidp,
+ const std::size_t xidlen, const bool owi):
+ _txn_hdr(magic, RHM_JDAT_VERSION, rid, xidlen, owi),
+ _xidp(xidp),
+ _buff(0),
+ _txn_tail(_txn_hdr)
+{}
+
+txn_rec::~txn_rec()
+{
+ clean();
+}
+
+void
+txn_rec::reset(const u_int32_t magic)
+{
+ _txn_hdr._magic = magic;
+ _txn_hdr._rid = 0;
+ _txn_hdr._xidsize = 0;
+ _xidp = 0;
+ _buff = 0;
+ _txn_tail._xmagic = ~magic;
+ _txn_tail._rid = 0;
+}
+
+void
+txn_rec::reset(const u_int32_t magic, const u_int64_t rid, const void* const xidp,
+ const std::size_t xidlen, const bool owi)
+{
+ _txn_hdr._magic = magic;
+ _txn_hdr._rid = rid;
+ _txn_hdr.set_owi(owi);
+ _txn_hdr._xidsize = xidlen;
+ _xidp = xidp;
+ _buff = 0;
+ _txn_tail._xmagic = ~magic;
+ _txn_tail._rid = rid;
+}
+
+u_int32_t
+txn_rec::encode(void* wptr, u_int32_t rec_offs_dblks, u_int32_t max_size_dblks)
+{
+ assert(wptr != 0);
+ assert(max_size_dblks > 0);
+ assert(_xidp != 0 && _txn_hdr._xidsize > 0);
+
+ std::size_t rec_offs = rec_offs_dblks * JRNL_DBLK_SIZE;
+ std::size_t rem = max_size_dblks * JRNL_DBLK_SIZE;
+ std::size_t wr_cnt = 0;
+ if (rec_offs_dblks) // Continuation of split dequeue record (over 2 or more pages)
+ {
+ if (size_dblks(rec_size()) - rec_offs_dblks > max_size_dblks) // Further split required
+ {
+ rec_offs -= sizeof(_txn_hdr);
+ std::size_t wsize = _txn_hdr._xidsize > rec_offs ? _txn_hdr._xidsize - rec_offs : 0;
+ std::size_t wsize2 = wsize;
+ if (wsize)
+ {
+ if (wsize > rem)
+ wsize = rem;
+ std::memcpy(wptr, (const char*)_xidp + rec_offs, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ rec_offs -= _txn_hdr._xidsize - wsize2;
+ if (rem)
+ {
+ wsize = sizeof(_txn_tail) > rec_offs ? sizeof(_txn_tail) - rec_offs : 0;
+ wsize2 = wsize;
+ if (wsize)
+ {
+ if (wsize > rem)
+ wsize = rem;
+ std::memcpy((char*)wptr + wr_cnt, (char*)&_txn_tail + rec_offs, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ rec_offs -= sizeof(_txn_tail) - wsize2;
+ }
+ assert(rem == 0);
+ assert(rec_offs == 0);
+ }
+ else // No further split required
+ {
+ rec_offs -= sizeof(_txn_hdr);
+ std::size_t wsize = _txn_hdr._xidsize > rec_offs ? _txn_hdr._xidsize - rec_offs : 0;
+ if (wsize)
+ {
+ std::memcpy(wptr, (const char*)_xidp + rec_offs, wsize);
+ wr_cnt += wsize;
+ }
+ rec_offs -= _txn_hdr._xidsize - wsize;
+ wsize = sizeof(_txn_tail) > rec_offs ? sizeof(_txn_tail) - rec_offs : 0;
+ if (wsize)
+ {
+ std::memcpy((char*)wptr + wr_cnt, (char*)&_txn_tail + rec_offs, wsize);
+ wr_cnt += wsize;
+#ifdef RHM_CLEAN
+ std::size_t rec_offs = rec_offs_dblks * JRNL_DBLK_SIZE;
+ std::size_t dblk_rec_size = size_dblks(rec_size() - rec_offs) * JRNL_DBLK_SIZE;
+ std::memset((char*)wptr + wr_cnt, RHM_CLEAN_CHAR, dblk_rec_size - wr_cnt);
+#endif
+ }
+ rec_offs -= sizeof(_txn_tail) - wsize;
+ assert(rec_offs == 0);
+ }
+ }
+ else // Start at beginning of data record
+ {
+ // Assumption: the header will always fit into the first dblk
+ std::memcpy(wptr, (void*)&_txn_hdr, sizeof(_txn_hdr));
+ wr_cnt = sizeof(_txn_hdr);
+ if (size_dblks(rec_size()) > max_size_dblks) // Split required
+ {
+ std::size_t wsize;
+ rem -= sizeof(_txn_hdr);
+ if (rem)
+ {
+ wsize = rem >= _txn_hdr._xidsize ? _txn_hdr._xidsize : rem;
+ std::memcpy((char*)wptr + wr_cnt, _xidp, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ if (rem)
+ {
+ wsize = rem >= sizeof(_txn_tail) ? sizeof(_txn_tail) : rem;
+ std::memcpy((char*)wptr + wr_cnt, (void*)&_txn_tail, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ assert(rem == 0);
+ }
+ else // No split required
+ {
+ std::memcpy((char*)wptr + wr_cnt, _xidp, _txn_hdr._xidsize);
+ wr_cnt += _txn_hdr._xidsize;
+ std::memcpy((char*)wptr + wr_cnt, (void*)&_txn_tail, sizeof(_txn_tail));
+ wr_cnt += sizeof(_txn_tail);
+#ifdef RHM_CLEAN
+ std::size_t dblk_rec_size = size_dblks(rec_size()) * JRNL_DBLK_SIZE;
+ std::memset((char*)wptr + wr_cnt, RHM_CLEAN_CHAR, dblk_rec_size - wr_cnt);
+#endif
+ }
+ }
+ return size_dblks(wr_cnt);
+}
+
+u_int32_t
+txn_rec::decode(rec_hdr& h, void* rptr, u_int32_t rec_offs_dblks, u_int32_t max_size_dblks)
+{
+ assert(rptr != 0);
+ assert(max_size_dblks > 0);
+
+ std::size_t rd_cnt = 0;
+ if (rec_offs_dblks) // Continuation of record on new page
+ {
+ const u_int32_t hdr_xid_dblks = size_dblks(txn_hdr::size() + _txn_hdr._xidsize);
+ const u_int32_t hdr_xid_tail_dblks = size_dblks(txn_hdr::size() + _txn_hdr._xidsize +
+ rec_tail::size());
+ const std::size_t rec_offs = rec_offs_dblks * JRNL_DBLK_SIZE;
+
+ if (hdr_xid_tail_dblks - rec_offs_dblks <= max_size_dblks)
+ {
+ // Remainder of xid fits within this page
+ if (rec_offs - txn_hdr::size() < _txn_hdr._xidsize)
+ {
+ // Part of xid still outstanding, copy remainder of xid and tail
+ const std::size_t xid_offs = rec_offs - txn_hdr::size();
+ const std::size_t xid_rem = _txn_hdr._xidsize - xid_offs;
+ std::memcpy((char*)_buff + xid_offs, rptr, xid_rem);
+ rd_cnt = xid_rem;
+ std::memcpy((void*)&_txn_tail, ((char*)rptr + rd_cnt), sizeof(_txn_tail));
+ chk_tail();
+ rd_cnt += sizeof(_txn_tail);
+ }
+ else
+ {
+ // Tail or part of tail only outstanding, complete tail
+ const std::size_t tail_offs = rec_offs - txn_hdr::size() - _txn_hdr._xidsize;
+ const std::size_t tail_rem = rec_tail::size() - tail_offs;
+ std::memcpy((char*)&_txn_tail + tail_offs, rptr, tail_rem);
+ chk_tail();
+ rd_cnt = tail_rem;
+ }
+ }
+ else if (hdr_xid_dblks - rec_offs_dblks <= max_size_dblks)
+ {
+ // Remainder of xid fits within this page, tail split
+ const std::size_t xid_offs = rec_offs - txn_hdr::size();
+ const std::size_t xid_rem = _txn_hdr._xidsize - xid_offs;
+ std::memcpy((char*)_buff + xid_offs, rptr, xid_rem);
+ rd_cnt += xid_rem;
+ const std::size_t tail_rem = (max_size_dblks * JRNL_DBLK_SIZE) - rd_cnt;
+ if (tail_rem)
+ {
+ std::memcpy((void*)&_txn_tail, ((char*)rptr + xid_rem), tail_rem);
+ rd_cnt += tail_rem;
+ }
+ }
+ else
+ {
+ // Remainder of xid split
+ const std::size_t xid_cp_size = (max_size_dblks * JRNL_DBLK_SIZE);
+ std::memcpy((char*)_buff + rec_offs - txn_hdr::size(), rptr, xid_cp_size);
+ rd_cnt += xid_cp_size;
+ }
+ }
+ else // Start of record
+ {
+ // Get and check header
+ _txn_hdr.hdr_copy(h);
+ rd_cnt = sizeof(rec_hdr);
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ rd_cnt += sizeof(u_int32_t); // Filler 0
+#endif
+ //_txn_hdr._xidsize = *(std::size_t*)((char*)rptr + rd_cnt);
+ std::memcpy((void*)&_txn_hdr._xidsize, (char*)rptr + rd_cnt, sizeof(std::size_t));
+ rd_cnt = _txn_hdr.size();
+ chk_hdr();
+ _buff = std::malloc(_txn_hdr._xidsize);
+ MALLOC_CHK(_buff, "_buff", "txn_rec", "decode");
+ const u_int32_t hdr_xid_dblks = size_dblks(txn_hdr::size() + _txn_hdr._xidsize);
+ const u_int32_t hdr_xid_tail_dblks = size_dblks(txn_hdr::size() + _txn_hdr._xidsize +
+ rec_tail::size());
+
+ // Check if record (header + xid + tail) fits within this page, we can check the
+ // tail before the expense of copying data to memory
+ if (hdr_xid_tail_dblks <= max_size_dblks)
+ {
+ // Entire header, xid and tail fits within this page
+ std::memcpy(_buff, (char*)rptr + rd_cnt, _txn_hdr._xidsize);
+ rd_cnt += _txn_hdr._xidsize;
+ std::memcpy((void*)&_txn_tail, (char*)rptr + rd_cnt, sizeof(_txn_tail));
+ rd_cnt += sizeof(_txn_tail);
+ chk_tail();
+ }
+ else if (hdr_xid_dblks <= max_size_dblks)
+ {
+ // Entire header and xid fit within this page, tail split
+ std::memcpy(_buff, (char*)rptr + rd_cnt, _txn_hdr._xidsize);
+ rd_cnt += _txn_hdr._xidsize;
+ const std::size_t tail_rem = (max_size_dblks * JRNL_DBLK_SIZE) - rd_cnt;
+ if (tail_rem)
+ {
+ std::memcpy((void*)&_txn_tail, (char*)rptr + rd_cnt, tail_rem);
+ rd_cnt += tail_rem;
+ }
+ }
+ else
+ {
+ // Header fits within this page, xid split
+ const std::size_t xid_cp_size = (max_size_dblks * JRNL_DBLK_SIZE) - rd_cnt;
+ std::memcpy(_buff, (char*)rptr + rd_cnt, xid_cp_size);
+ rd_cnt += xid_cp_size;
+ }
+ }
+ return size_dblks(rd_cnt);
+}
+
+bool
+txn_rec::rcv_decode(rec_hdr h, std::ifstream* ifsp, std::size_t& rec_offs)
+{
+ if (rec_offs == 0)
+ {
+ // Read header, allocate for xid
+ _txn_hdr.hdr_copy(h);
+#if defined(JRNL_BIG_ENDIAN) && defined(JRNL_32_BIT)
+ ifsp->ignore(sizeof(u_int32_t)); // _filler0
+#endif
+ ifsp->read((char*)&_txn_hdr._xidsize, sizeof(std::size_t));
+#if defined(JRNL_LITTLE_ENDIAN) && defined(JRNL_32_BIT)
+ ifsp->ignore(sizeof(u_int32_t)); // _filler0
+#endif
+ rec_offs = sizeof(_txn_hdr);
+ _buff = std::malloc(_txn_hdr._xidsize);
+ MALLOC_CHK(_buff, "_buff", "txn_rec", "rcv_decode");
+ }
+ if (rec_offs < sizeof(_txn_hdr) + _txn_hdr._xidsize)
+ {
+ // Read xid (or continue reading xid)
+ std::size_t offs = rec_offs - sizeof(_txn_hdr);
+ ifsp->read((char*)_buff + offs, _txn_hdr._xidsize - offs);
+ std::size_t size_read = ifsp->gcount();
+ rec_offs += size_read;
+ if (size_read < _txn_hdr._xidsize - offs)
+ {
+ assert(ifsp->eof());
+ // As we may have read past eof, turn off fail bit
+ ifsp->clear(ifsp->rdstate()&(~std::ifstream::failbit));
+ assert(!ifsp->fail() && !ifsp->bad());
+ return false;
+ }
+ }
+ if (rec_offs < sizeof(_txn_hdr) + _txn_hdr._xidsize + sizeof(rec_tail))
+ {
+ // Read tail (or continue reading tail)
+ std::size_t offs = rec_offs - sizeof(_txn_hdr) - _txn_hdr._xidsize;
+ ifsp->read((char*)&_txn_tail + offs, sizeof(rec_tail) - offs);
+ std::size_t size_read = ifsp->gcount();
+ rec_offs += size_read;
+ if (size_read < sizeof(rec_tail) - offs)
+ {
+ assert(ifsp->eof());
+ // As we may have read past eof, turn off fail bit
+ ifsp->clear(ifsp->rdstate()&(~std::ifstream::failbit));
+ assert(!ifsp->fail() && !ifsp->bad());
+ return false;
+ }
+ }
+ ifsp->ignore(rec_size_dblks() * JRNL_DBLK_SIZE - rec_size());
+ chk_tail(); // Throws if tail invalid or record incomplete
+ assert(!ifsp->fail() && !ifsp->bad());
+ return true;
+}
+
+std::size_t
+txn_rec::get_xid(void** const xidpp)
+{
+ if (!_buff)
+ {
+ *xidpp = 0;
+ return 0;
+ }
+ *xidpp = _buff;
+ return _txn_hdr._xidsize;
+}
+
+std::string&
+txn_rec::str(std::string& str) const
+{
+ std::ostringstream oss;
+ if (_txn_hdr._magic == RHM_JDAT_TXA_MAGIC)
+ oss << "dtxa_rec: m=" << _txn_hdr._magic;
+ else
+ oss << "dtxc_rec: m=" << _txn_hdr._magic;
+ oss << " v=" << (int)_txn_hdr._version;
+ oss << " rid=" << _txn_hdr._rid;
+ oss << " xid=\"" << _xidp << "\"";
+ str.append(oss.str());
+ return str;
+}
+
+std::size_t
+txn_rec::xid_size() const
+{
+ return _txn_hdr._xidsize;
+}
+
+std::size_t
+txn_rec::rec_size() const
+{
+ return txn_hdr::size() + _txn_hdr._xidsize + rec_tail::size();
+}
+
+void
+txn_rec::chk_hdr() const
+{
+ jrec::chk_hdr(_txn_hdr);
+ if (_txn_hdr._magic != RHM_JDAT_TXA_MAGIC && _txn_hdr._magic != RHM_JDAT_TXC_MAGIC)
+ {
+ std::ostringstream oss;
+ oss << std::hex << std::setfill('0');
+ oss << "dtx magic: rid=0x" << std::setw(16) << _txn_hdr._rid;
+ oss << ": expected=(0x" << std::setw(8) << RHM_JDAT_TXA_MAGIC;
+ oss << " or 0x" << RHM_JDAT_TXC_MAGIC;
+ oss << ") read=0x" << std::setw(2) << (int)_txn_hdr._magic;
+ throw jexception(jerrno::JERR_JREC_BADRECHDR, oss.str(), "txn_rec", "chk_hdr");
+ }
+}
+
+void
+txn_rec::chk_hdr(u_int64_t rid) const
+{
+ chk_hdr();
+ jrec::chk_rid(_txn_hdr, rid);
+}
+
+void
+txn_rec::chk_tail() const
+{
+ jrec::chk_tail(_txn_tail, _txn_hdr);
+}
+
+void
+txn_rec::clean()
+{
+ // clean up allocated memory here
+}
+
+} // namespace journal
+} // namespace mrg
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/txn_rec.h b/qpid/cpp/src/qpid/legacystore/jrnl/txn_rec.h
new file mode 100644
index 0000000000..1a49df1c96
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/txn_rec.h
@@ -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.
+ *
+ */
+
+/**
+ * \file txn_rec.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * This file contains the code for the mrg::journal::txn_rec (journal data
+ * record) class. See class documentation for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_TXN_REC_H
+#define QPID_LEGACYSTORE_JRNL_TXN_REC_H
+
+namespace mrg
+{
+namespace journal
+{
+class txn_rec;
+}
+}
+
+#include <cstddef>
+#include "qpid/legacystore/jrnl/jrec.h"
+#include "qpid/legacystore/jrnl/txn_hdr.h"
+
+namespace mrg
+{
+namespace journal
+{
+
+ /**
+ * \class txn_rec
+ * \brief Class to handle a single journal DTX commit or abort record.
+ */
+ class txn_rec : public jrec
+ {
+ private:
+ txn_hdr _txn_hdr; ///< transaction header
+ const void* _xidp; ///< xid pointer for encoding (writing to disk)
+ void* _buff; ///< Pointer to buffer to receive data read from disk
+ rec_tail _txn_tail; ///< Record tail
+
+ public:
+ // constructor used for read operations and xid must have memory allocated
+ txn_rec();
+ // constructor used for write operations, where xid already exists
+ txn_rec(const u_int32_t magic, const u_int64_t rid, const void* const xidp,
+ const std::size_t xidlen, const bool owi);
+ virtual ~txn_rec();
+
+ // Prepare instance for use in reading data from journal
+ void reset(const u_int32_t magic);
+ // Prepare instance for use in writing data to journal
+ void reset(const u_int32_t magic, const u_int64_t rid, const void* const xidp,
+ const std::size_t xidlen, const bool owi);
+ u_int32_t encode(void* wptr, u_int32_t rec_offs_dblks, u_int32_t max_size_dblks);
+ u_int32_t decode(rec_hdr& h, void* rptr, u_int32_t rec_offs_dblks,
+ u_int32_t max_size_dblks);
+ // Decode used for recover
+ bool rcv_decode(rec_hdr h, std::ifstream* ifsp, std::size_t& rec_offs);
+
+ std::size_t get_xid(void** const xidpp);
+ std::string& str(std::string& str) const;
+ inline std::size_t data_size() const { return 0; } // This record never carries data
+ std::size_t xid_size() const;
+ std::size_t rec_size() const;
+ inline u_int64_t rid() const { return _txn_hdr._rid; }
+
+ private:
+ void chk_hdr() const;
+ void chk_hdr(u_int64_t rid) const;
+ void chk_tail() const;
+ virtual void clean();
+ }; // class txn_rec
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_TXN_REC_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/wmgr.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/wmgr.cpp
new file mode 100644
index 0000000000..4353fcfbca
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/wmgr.cpp
@@ -0,0 +1,1051 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file wmgr.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::wmgr (write manager). See
+ * comments in file wmgr.h for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#include "qpid/legacystore/jrnl/wmgr.h"
+
+#include <cassert>
+#include <cerrno>
+#include <cstdlib>
+#include <cstring>
+#include "qpid/legacystore/jrnl/file_hdr.h"
+#include "qpid/legacystore/jrnl/jcntl.h"
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include <sstream>
+
+namespace mrg
+{
+namespace journal
+{
+
+wmgr::wmgr(jcntl* jc, enq_map& emap, txn_map& tmap, wrfc& wrfc):
+ pmgr(jc, emap, tmap),
+ _wrfc(wrfc),
+ _max_dtokpp(0),
+ _max_io_wait_us(0),
+ _fhdr_base_ptr(0),
+ _fhdr_ptr_arr(0),
+ _fhdr_aio_cb_arr(0),
+ _cached_offset_dblks(0),
+ _jfsize_dblks(0),
+ _jfsize_pgs(0),
+ _num_jfiles(0),
+ _enq_busy(false),
+ _deq_busy(false),
+ _abort_busy(false),
+ _commit_busy(false),
+ _txn_pending_set()
+{}
+
+wmgr::wmgr(jcntl* jc, enq_map& emap, txn_map& tmap, wrfc& wrfc,
+ const u_int32_t max_dtokpp, const u_int32_t max_iowait_us):
+ pmgr(jc, emap, tmap /* , dtoklp */),
+ _wrfc(wrfc),
+ _max_dtokpp(max_dtokpp),
+ _max_io_wait_us(max_iowait_us),
+ _fhdr_base_ptr(0),
+ _fhdr_ptr_arr(0),
+ _fhdr_aio_cb_arr(0),
+ _cached_offset_dblks(0),
+ _jfsize_dblks(0),
+ _jfsize_pgs(0),
+ _num_jfiles(0),
+ _enq_busy(false),
+ _deq_busy(false),
+ _abort_busy(false),
+ _commit_busy(false),
+ _txn_pending_set()
+{}
+
+wmgr::~wmgr()
+{
+ wmgr::clean();
+}
+
+void
+wmgr::initialize(aio_callback* const cbp, const u_int32_t wcache_pgsize_sblks,
+ const u_int16_t wcache_num_pages, const u_int32_t max_dtokpp, const u_int32_t max_iowait_us,
+ std::size_t eo)
+{
+ _enq_busy = false;
+ _deq_busy = false;
+ _abort_busy = false;
+ _commit_busy = false;
+ _max_dtokpp = max_dtokpp;
+ _max_io_wait_us = max_iowait_us;
+
+ initialize(cbp, wcache_pgsize_sblks, wcache_num_pages);
+
+ _jfsize_dblks = _jc->jfsize_sblks() * JRNL_SBLK_SIZE;
+ _jfsize_pgs = _jc->jfsize_sblks() / _cache_pgsize_sblks;
+ assert(_jc->jfsize_sblks() % JRNL_RMGR_PAGE_SIZE == 0);
+
+ if (eo)
+ {
+ const u_int32_t wr_pg_size_dblks = _cache_pgsize_sblks * JRNL_SBLK_SIZE;
+ u_int32_t data_dblks = (eo / JRNL_DBLK_SIZE) - 4; // 4 dblks for file hdr
+ _pg_cntr = data_dblks / wr_pg_size_dblks;
+ _pg_offset_dblks = data_dblks - (_pg_cntr * wr_pg_size_dblks);
+ }
+}
+
+iores
+wmgr::enqueue(const void* const data_buff, const std::size_t tot_data_len,
+ const std::size_t this_data_len, data_tok* dtokp, const void* const xid_ptr,
+ const std::size_t xid_len, const bool transient, const bool external)
+{
+ if (xid_len)
+ assert(xid_ptr != 0);
+
+ if (_deq_busy || _abort_busy || _commit_busy)
+ return RHM_IORES_BUSY;
+
+ if (this_data_len != tot_data_len && !external)
+ return RHM_IORES_NOTIMPL;
+
+ iores res = pre_write_check(WMGR_ENQUEUE, dtokp, xid_len, tot_data_len, external);
+ if (res != RHM_IORES_SUCCESS)
+ return res;
+
+ bool cont = false;
+ if (_enq_busy) // If enqueue() exited last time with RHM_IORES_FULL or RHM_IORES_PAGE_AIOWAIT
+ {
+ if (dtokp->wstate() == data_tok::ENQ_PART)
+ cont = true;
+ else
+ {
+ std::ostringstream oss;
+ oss << "This data_tok: id=" << dtokp->id() << " state=" << dtokp->wstate_str();
+ throw jexception(jerrno::JERR_WMGR_ENQDISCONT, oss.str(), "wmgr", "enqueue");
+ }
+ }
+
+ u_int64_t rid = (dtokp->external_rid() | cont) ? dtokp->rid() : _wrfc.get_incr_rid();
+ _enq_rec.reset(rid, data_buff, tot_data_len, xid_ptr, xid_len, _wrfc.owi(), transient,
+ external);
+ if (!cont)
+ {
+ dtokp->set_rid(rid);
+ dtokp->set_dequeue_rid(0);
+ if (xid_len)
+ dtokp->set_xid(xid_ptr, xid_len);
+ else
+ dtokp->clear_xid();
+ _enq_busy = true;
+ }
+ bool done = false;
+ while (!done)
+ {
+ assert(_pg_offset_dblks < _cache_pgsize_sblks * JRNL_SBLK_SIZE);
+ void* wptr = (void*)((char*)_page_ptr_arr[_pg_index] + _pg_offset_dblks * JRNL_DBLK_SIZE);
+ u_int32_t data_offs_dblks = dtokp->dblocks_written();
+ u_int32_t ret = _enq_rec.encode(wptr, data_offs_dblks,
+ (_cache_pgsize_sblks * JRNL_SBLK_SIZE) - _pg_offset_dblks);
+
+ // Remember fid which contains the record header in case record is split over several files
+ if (data_offs_dblks == 0)
+ dtokp->set_fid(_wrfc.index());
+ _pg_offset_dblks += ret;
+ _cached_offset_dblks += ret;
+ dtokp->incr_dblocks_written(ret);
+ dtokp->incr_pg_cnt();
+ _page_cb_arr[_pg_index]._pdtokl->push_back(dtokp);
+
+ // Is the encoding of this record complete?
+ if (dtokp->dblocks_written() >= _enq_rec.rec_size_dblks())
+ {
+ // TODO: Incorrect - must set state to ENQ_CACHED; ENQ_SUBM is set when AIO returns.
+ dtokp->set_wstate(data_tok::ENQ_SUBM);
+ dtokp->set_dsize(tot_data_len);
+ // Only add this data token to page token list when submit is complete, this way
+ // long multi-page messages have their token on the page containing the END of the
+ // message. AIO callbacks will then only process this token when entire message is
+ // enqueued.
+ _wrfc.incr_enqcnt(dtokp->fid());
+
+ if (xid_len) // If part of transaction, add to transaction map
+ {
+ std::string xid((const char*)xid_ptr, xid_len);
+ _tmap.insert_txn_data(xid, txn_data(rid, 0, dtokp->fid(), true));
+ }
+ else
+ {
+ if (_emap.insert_pfid(rid, dtokp->fid()) < enq_map::EMAP_OK) // fail
+ {
+ // The only error code emap::insert_pfid() returns is enq_map::EMAP_DUP_RID.
+ std::ostringstream oss;
+ oss << std::hex << "rid=0x" << rid << " _pfid=0x" << dtokp->fid();
+ throw jexception(jerrno::JERR_MAP_DUPLICATE, oss.str(), "wmgr", "enqueue");
+ }
+ }
+
+ done = true;
+ }
+ else
+ dtokp->set_wstate(data_tok::ENQ_PART);
+
+ file_header_check(rid, cont, _enq_rec.rec_size_dblks() - data_offs_dblks);
+ flush_check(res, cont, done);
+ }
+ if (dtokp->wstate() >= data_tok::ENQ_SUBM)
+ _enq_busy = false;
+ return res;
+}
+
+iores
+wmgr::dequeue(data_tok* dtokp, const void* const xid_ptr, const std::size_t xid_len, const bool txn_coml_commit)
+{
+ if (xid_len)
+ assert(xid_ptr != 0);
+
+ if (_enq_busy || _abort_busy || _commit_busy)
+ return RHM_IORES_BUSY;
+
+ iores res = pre_write_check(WMGR_DEQUEUE, dtokp);
+ if (res != RHM_IORES_SUCCESS)
+ return res;
+
+ bool cont = false;
+ if (_deq_busy) // If dequeue() exited last time with RHM_IORES_FULL or RHM_IORES_PAGE_AIOWAIT
+ {
+ if (dtokp->wstate() == data_tok::DEQ_PART)
+ cont = true;
+ else
+ {
+ std::ostringstream oss;
+ oss << "This data_tok: id=" << dtokp->id() << " state=" << dtokp->wstate_str();
+ throw jexception(jerrno::JERR_WMGR_DEQDISCONT, oss.str(), "wmgr", "dequeue");
+ }
+ }
+
+ const bool ext_rid = dtokp->external_rid();
+ u_int64_t rid = (ext_rid | cont) ? dtokp->rid() : _wrfc.get_incr_rid();
+ u_int64_t dequeue_rid = (ext_rid | cont) ? dtokp->dequeue_rid() : dtokp->rid();
+ _deq_rec.reset(rid, dequeue_rid, xid_ptr, xid_len, _wrfc.owi(), txn_coml_commit);
+ if (!cont)
+ {
+ if (!ext_rid)
+ {
+ dtokp->set_rid(rid);
+ dtokp->set_dequeue_rid(dequeue_rid);
+ }
+ if (xid_len)
+ dtokp->set_xid(xid_ptr, xid_len);
+ else
+ dtokp->clear_xid();
+ dequeue_check(dtokp->xid(), dequeue_rid);
+ dtokp->set_dblocks_written(0); // Reset dblks_written from previous op
+ _deq_busy = true;
+ }
+ bool done = false;
+ while (!done)
+ {
+ assert(_pg_offset_dblks < _cache_pgsize_sblks * JRNL_SBLK_SIZE);
+ void* wptr = (void*)((char*)_page_ptr_arr[_pg_index] + _pg_offset_dblks * JRNL_DBLK_SIZE);
+ u_int32_t data_offs_dblks = dtokp->dblocks_written();
+ u_int32_t ret = _deq_rec.encode(wptr, data_offs_dblks,
+ (_cache_pgsize_sblks * JRNL_SBLK_SIZE) - _pg_offset_dblks);
+
+ // Remember fid which contains the record header in case record is split over several files
+ if (data_offs_dblks == 0)
+ dtokp->set_fid(_wrfc.index());
+ _pg_offset_dblks += ret;
+ _cached_offset_dblks += ret;
+ dtokp->incr_dblocks_written(ret);
+ dtokp->incr_pg_cnt();
+ _page_cb_arr[_pg_index]._pdtokl->push_back(dtokp);
+
+ // Is the encoding of this record complete?
+ if (dtokp->dblocks_written() >= _deq_rec.rec_size_dblks())
+ {
+ // TODO: Incorrect - must set state to ENQ_CACHED; ENQ_SUBM is set when AIO returns.
+ dtokp->set_wstate(data_tok::DEQ_SUBM);
+
+ if (xid_len) // If part of transaction, add to transaction map
+ {
+ // If the enqueue is part of a pending txn, it will not yet be in emap
+ _emap.lock(dequeue_rid); // ignore rid not found error
+ std::string xid((const char*)xid_ptr, xid_len);
+ _tmap.insert_txn_data(xid, txn_data(rid, dequeue_rid, dtokp->fid(), false));
+ }
+ else
+ {
+ int16_t fid = _emap.get_remove_pfid(dtokp->dequeue_rid());
+ if (fid < enq_map::EMAP_OK) // fail
+ {
+ if (fid == enq_map::EMAP_RID_NOT_FOUND)
+ {
+ std::ostringstream oss;
+ oss << std::hex << "rid=0x" << rid;
+ throw jexception(jerrno::JERR_MAP_NOTFOUND, oss.str(), "wmgr", "dequeue");
+ }
+ if (fid == enq_map::EMAP_LOCKED)
+ {
+ std::ostringstream oss;
+ oss << std::hex << "rid=0x" << rid;
+ throw jexception(jerrno::JERR_MAP_LOCKED, oss.str(), "wmgr", "dequeue");
+ }
+ }
+ _wrfc.decr_enqcnt(fid);
+ }
+
+ done = true;
+ }
+ else
+ dtokp->set_wstate(data_tok::DEQ_PART);
+
+ file_header_check(rid, cont, _deq_rec.rec_size_dblks() - data_offs_dblks);
+ flush_check(res, cont, done);
+ }
+ if (dtokp->wstate() >= data_tok::DEQ_SUBM)
+ _deq_busy = false;
+ return res;
+}
+
+iores
+wmgr::abort(data_tok* dtokp, const void* const xid_ptr, const std::size_t xid_len)
+{
+ // commit and abort MUST have a valid xid
+ assert(xid_ptr != 0 && xid_len > 0);
+
+ if (_enq_busy || _deq_busy || _commit_busy)
+ return RHM_IORES_BUSY;
+
+ iores res = pre_write_check(WMGR_ABORT, dtokp);
+ if (res != RHM_IORES_SUCCESS)
+ return res;
+
+ bool cont = false;
+ if (_abort_busy) // If abort() exited last time with RHM_IORES_FULL or RHM_IORES_PAGE_AIOWAIT
+ {
+ if (dtokp->wstate() == data_tok::ABORT_PART)
+ cont = true;
+ else
+ {
+ std::ostringstream oss;
+ oss << "This data_tok: id=" << dtokp->id() << " state=" << dtokp->wstate_str();
+ throw jexception(jerrno::JERR_WMGR_DEQDISCONT, oss.str(), "wmgr", "abort");
+ }
+ }
+
+ u_int64_t rid = (dtokp->external_rid() | cont) ? dtokp->rid() : _wrfc.get_incr_rid();
+ _txn_rec.reset(RHM_JDAT_TXA_MAGIC, rid, xid_ptr, xid_len, _wrfc.owi());
+ if (!cont)
+ {
+ dtokp->set_rid(rid);
+ dtokp->set_dequeue_rid(0);
+ dtokp->set_xid(xid_ptr, xid_len);
+ dtokp->set_dblocks_written(0); // Reset dblks_written from previous op
+ _abort_busy = true;
+ }
+ bool done = false;
+ while (!done)
+ {
+ assert(_pg_offset_dblks < _cache_pgsize_sblks * JRNL_SBLK_SIZE);
+ void* wptr = (void*)((char*)_page_ptr_arr[_pg_index] + _pg_offset_dblks * JRNL_DBLK_SIZE);
+ u_int32_t data_offs_dblks = dtokp->dblocks_written();
+ u_int32_t ret = _txn_rec.encode(wptr, data_offs_dblks,
+ (_cache_pgsize_sblks * JRNL_SBLK_SIZE) - _pg_offset_dblks);
+
+ // Remember fid which contains the record header in case record is split over several files
+ if (data_offs_dblks == 0)
+ dtokp->set_fid(_wrfc.index());
+ _pg_offset_dblks += ret;
+ _cached_offset_dblks += ret;
+ dtokp->incr_dblocks_written(ret);
+ dtokp->incr_pg_cnt();
+ _page_cb_arr[_pg_index]._pdtokl->push_back(dtokp);
+
+ // Is the encoding of this record complete?
+ if (dtokp->dblocks_written() >= _txn_rec.rec_size_dblks())
+ {
+ dtokp->set_wstate(data_tok::ABORT_SUBM);
+
+ // Delete this txn from tmap, unlock any locked records in emap
+ std::string xid((const char*)xid_ptr, xid_len);
+ txn_data_list tdl = _tmap.get_remove_tdata_list(xid); // tdl will be empty if xid not found
+ for (tdl_itr itr = tdl.begin(); itr != tdl.end(); itr++)
+ {
+ if (!itr->_enq_flag)
+ _emap.unlock(itr->_drid); // ignore rid not found error
+ if (itr->_enq_flag)
+ _wrfc.decr_enqcnt(itr->_pfid);
+ }
+ std::pair<std::set<std::string>::iterator, bool> res = _txn_pending_set.insert(xid);
+ if (!res.second)
+ {
+ std::ostringstream oss;
+ oss << std::hex << "_txn_pending_set: xid=\"" << xid << "\"";
+ throw jexception(jerrno::JERR_MAP_DUPLICATE, oss.str(), "wmgr", "abort");
+ }
+
+ done = true;
+ }
+ else
+ dtokp->set_wstate(data_tok::ABORT_PART);
+
+ file_header_check(rid, cont, _txn_rec.rec_size_dblks() - data_offs_dblks);
+ flush_check(res, cont, done);
+ }
+ if (dtokp->wstate() >= data_tok::ABORT_SUBM)
+ _abort_busy = false;
+ return res;
+}
+
+iores
+wmgr::commit(data_tok* dtokp, const void* const xid_ptr, const std::size_t xid_len)
+{
+ // commit and abort MUST have a valid xid
+ assert(xid_ptr != 0 && xid_len > 0);
+
+ if (_enq_busy || _deq_busy || _abort_busy)
+ return RHM_IORES_BUSY;
+
+ iores res = pre_write_check(WMGR_COMMIT, dtokp);
+ if (res != RHM_IORES_SUCCESS)
+ return res;
+
+ bool cont = false;
+ if (_commit_busy) // If commit() exited last time with RHM_IORES_FULL or RHM_IORES_PAGE_AIOWAIT
+ {
+ if (dtokp->wstate() == data_tok::COMMIT_PART)
+ cont = true;
+ else
+ {
+ std::ostringstream oss;
+ oss << "This data_tok: id=" << dtokp->id() << " state=" << dtokp->wstate_str();
+ throw jexception(jerrno::JERR_WMGR_DEQDISCONT, oss.str(), "wmgr", "commit");
+ }
+ }
+
+ u_int64_t rid = (dtokp->external_rid() | cont) ? dtokp->rid() : _wrfc.get_incr_rid();
+ _txn_rec.reset(RHM_JDAT_TXC_MAGIC, rid, xid_ptr, xid_len, _wrfc.owi());
+ if (!cont)
+ {
+ dtokp->set_rid(rid);
+ dtokp->set_dequeue_rid(0);
+ dtokp->set_xid(xid_ptr, xid_len);
+ dtokp->set_dblocks_written(0); // Reset dblks_written from previous op
+ _commit_busy = true;
+ }
+ bool done = false;
+ while (!done)
+ {
+ assert(_pg_offset_dblks < _cache_pgsize_sblks * JRNL_SBLK_SIZE);
+ void* wptr = (void*)((char*)_page_ptr_arr[_pg_index] + _pg_offset_dblks * JRNL_DBLK_SIZE);
+ u_int32_t data_offs_dblks = dtokp->dblocks_written();
+ u_int32_t ret = _txn_rec.encode(wptr, data_offs_dblks,
+ (_cache_pgsize_sblks * JRNL_SBLK_SIZE) - _pg_offset_dblks);
+
+ // Remember fid which contains the record header in case record is split over several files
+ if (data_offs_dblks == 0)
+ dtokp->set_fid(_wrfc.index());
+ _pg_offset_dblks += ret;
+ _cached_offset_dblks += ret;
+ dtokp->incr_dblocks_written(ret);
+ dtokp->incr_pg_cnt();
+ _page_cb_arr[_pg_index]._pdtokl->push_back(dtokp);
+
+ // Is the encoding of this record complete?
+ if (dtokp->dblocks_written() >= _txn_rec.rec_size_dblks())
+ {
+ dtokp->set_wstate(data_tok::COMMIT_SUBM);
+
+ // Delete this txn from tmap, process records into emap
+ std::string xid((const char*)xid_ptr, xid_len);
+ txn_data_list tdl = _tmap.get_remove_tdata_list(xid); // tdl will be empty if xid not found
+ for (tdl_itr itr = tdl.begin(); itr != tdl.end(); itr++)
+ {
+ if (itr->_enq_flag) // txn enqueue
+ {
+ if (_emap.insert_pfid(itr->_rid, itr->_pfid) < enq_map::EMAP_OK) // fail
+ {
+ // The only error code emap::insert_pfid() returns is enq_map::EMAP_DUP_RID.
+ std::ostringstream oss;
+ oss << std::hex << "rid=0x" << itr->_rid << " _pfid=0x" << itr->_pfid;
+ throw jexception(jerrno::JERR_MAP_DUPLICATE, oss.str(), "wmgr", "commit");
+ }
+ }
+ else // txn dequeue
+ {
+ int16_t fid = _emap.get_remove_pfid(itr->_drid, true);
+ if (fid < enq_map::EMAP_OK) // fail
+ {
+ if (fid == enq_map::EMAP_RID_NOT_FOUND)
+ {
+ std::ostringstream oss;
+ oss << std::hex << "rid=0x" << rid;
+ throw jexception(jerrno::JERR_MAP_NOTFOUND, oss.str(), "wmgr", "dequeue");
+ }
+ if (fid == enq_map::EMAP_LOCKED)
+ {
+ std::ostringstream oss;
+ oss << std::hex << "rid=0x" << rid;
+ throw jexception(jerrno::JERR_MAP_LOCKED, oss.str(), "wmgr", "dequeue");
+ }
+ }
+ _wrfc.decr_enqcnt(fid);
+ }
+ }
+ std::pair<std::set<std::string>::iterator, bool> res = _txn_pending_set.insert(xid);
+ if (!res.second)
+ {
+ std::ostringstream oss;
+ oss << std::hex << "_txn_pending_set: xid=\"" << xid << "\"";
+ throw jexception(jerrno::JERR_MAP_DUPLICATE, oss.str(), "wmgr", "commit");
+ }
+
+ done = true;
+ }
+ else
+ dtokp->set_wstate(data_tok::COMMIT_PART);
+
+ file_header_check(rid, cont, _txn_rec.rec_size_dblks() - data_offs_dblks);
+ flush_check(res, cont, done);
+ }
+ if (dtokp->wstate() >= data_tok::COMMIT_SUBM)
+ _commit_busy = false;
+ return res;
+}
+
+void
+wmgr::file_header_check(const u_int64_t rid, const bool cont, const u_int32_t rec_dblks_rem)
+{
+ // Has the file header been written (i.e. write pointers still at 0)?
+ if (_wrfc.is_void())
+ {
+ bool file_fit = rec_dblks_rem <= _jfsize_dblks;
+ bool file_full = rec_dblks_rem == _jfsize_dblks;
+ std::size_t fro = 0;
+ if (cont)
+ {
+ if (file_fit && !file_full)
+ fro = (rec_dblks_rem + JRNL_SBLK_SIZE) * JRNL_DBLK_SIZE;
+ }
+ else
+ fro = JRNL_SBLK_SIZE * JRNL_DBLK_SIZE;
+ write_fhdr(rid, _wrfc.index(), _wrfc.index(), fro);
+ }
+}
+
+void
+wmgr::flush_check(iores& res, bool& cont, bool& done)
+{
+ // Is page is full, flush
+ if (_pg_offset_dblks >= _cache_pgsize_sblks * JRNL_SBLK_SIZE)
+ {
+ res = write_flush();
+ assert(res == RHM_IORES_SUCCESS);
+
+ if (_page_cb_arr[_pg_index]._state == AIO_PENDING && !done)
+ {
+ res = RHM_IORES_PAGE_AIOWAIT;
+ done = true;
+ }
+
+ // If file is full, rotate to next file
+ if (_pg_cntr >= _jfsize_pgs)
+ {
+ iores rfres = rotate_file();
+ if (rfres != RHM_IORES_SUCCESS)
+ res = rfres;
+ if (!done)
+ {
+ if (rfres == RHM_IORES_SUCCESS)
+ cont = true;
+ else
+ done = true;
+ }
+ }
+ }
+}
+
+iores
+wmgr::flush()
+{
+ iores res = write_flush();
+ if (_pg_cntr >= _jfsize_pgs)
+ {
+ iores rfres = rotate_file();
+ if (rfres != RHM_IORES_SUCCESS)
+ res = rfres;
+ }
+ return res;
+}
+
+iores
+wmgr::write_flush()
+{
+ iores res = RHM_IORES_SUCCESS;
+ // Don't bother flushing an empty page or one that is still in state AIO_PENDING
+ if (_cached_offset_dblks)
+ {
+ if (_page_cb_arr[_pg_index]._state == AIO_PENDING)
+ res = RHM_IORES_PAGE_AIOWAIT;
+ else
+ {
+ if (_page_cb_arr[_pg_index]._state != IN_USE)
+ {
+ std::ostringstream oss;
+ oss << "pg_index=" << _pg_index << " state=" << _page_cb_arr[_pg_index].state_str();
+ throw jexception(jerrno::JERR_WMGR_BADPGSTATE, oss.str(), "wmgr",
+ "write_flush");
+ }
+
+ // Send current page using AIO
+
+ // In manual flushes, dblks may not coincide with sblks, add filler records ("RHMx")
+ // if necessary.
+ dblk_roundup();
+
+ std::size_t pg_offs = (_pg_offset_dblks - _cached_offset_dblks) * JRNL_DBLK_SIZE;
+ aio_cb* aiocbp = &_aio_cb_arr[_pg_index];
+ aio::prep_pwrite_2(aiocbp, _wrfc.fh(),
+ (char*)_page_ptr_arr[_pg_index] + pg_offs, _cached_offset_dblks * JRNL_DBLK_SIZE,
+ _wrfc.subm_offs());
+ page_cb* pcbp = (page_cb*)(aiocbp->data); // This page control block (pcb)
+ pcbp->_wdblks = _cached_offset_dblks;
+ pcbp->_wfh = _wrfc.file_controller();
+ if (aio::submit(_ioctx, 1, &aiocbp) < 0)
+ throw jexception(jerrno::JERR__AIO, "wmgr", "write_flush");
+ _wrfc.add_subm_cnt_dblks(_cached_offset_dblks);
+ _wrfc.incr_aio_cnt();
+ _aio_evt_rem++;
+ _cached_offset_dblks = 0;
+ _jc->instr_incr_outstanding_aio_cnt();
+
+ rotate_page(); // increments _pg_index, resets _pg_offset_dblks if req'd
+ if (_page_cb_arr[_pg_index]._state == UNUSED)
+ _page_cb_arr[_pg_index]._state = IN_USE;
+ }
+ }
+ get_events(UNUSED, 0);
+ if (_page_cb_arr[_pg_index]._state == UNUSED)
+ _page_cb_arr[_pg_index]._state = IN_USE;
+ return res;
+}
+
+iores
+wmgr::rotate_file()
+{
+ _pg_cntr = 0;
+ iores res = _wrfc.rotate();
+ _jc->chk_wr_frot();
+ return res;
+}
+
+int32_t
+wmgr::get_events(page_state state, timespec* const timeout, bool flush)
+{
+ if (_aio_evt_rem == 0) // no events to get
+ return 0;
+
+ int ret = 0;
+ if ((ret = aio::getevents(_ioctx, flush ? _aio_evt_rem : 1, _aio_evt_rem/*_cache_num_pages + _jc->num_jfiles()*/, _aio_event_arr, timeout)) < 0)
+ {
+ if (ret == -EINTR) // Interrupted by signal
+ return 0;
+ std::ostringstream oss;
+ oss << "io_getevents() failed: " << std::strerror(-ret) << " (" << ret << ")";
+ throw jexception(jerrno::JERR__AIO, oss.str(), "wmgr", "get_events");
+ }
+
+ if (ret == 0 && timeout)
+ return jerrno::AIO_TIMEOUT;
+
+ int32_t tot_data_toks = 0;
+ for (int i=0; i<ret; i++) // Index of returned AIOs
+ {
+ if (_aio_evt_rem == 0)
+ {
+ std::ostringstream oss;
+ oss << "_aio_evt_rem; evt " << (i + 1) << " of " << ret;
+ throw jexception(jerrno::JERR__UNDERFLOW, oss.str(), "wmgr", "get_events");
+ }
+ _aio_evt_rem--;
+ aio_cb* aiocbp = _aio_event_arr[i].obj; // This I/O control block (iocb)
+ page_cb* pcbp = (page_cb*)(aiocbp->data); // This page control block (pcb)
+ long aioret = (long)_aio_event_arr[i].res;
+ if (aioret < 0)
+ {
+ std::ostringstream oss;
+ oss << "AIO write operation failed: " << std::strerror(-aioret) << " (" << aioret << ") [";
+ if (pcbp)
+ oss << "pg=" << pcbp->_index;
+ else
+ {
+ file_hdr* fhp = (file_hdr*)aiocbp->u.c.buf;
+ oss << "fid=" << fhp->_pfid;
+ }
+ oss << " size=" << aiocbp->u.c.nbytes;
+ oss << " offset=" << aiocbp->u.c.offset << " fh=" << aiocbp->aio_fildes << "]";
+ throw jexception(jerrno::JERR__AIO, oss.str(), "wmgr", "get_events");
+ }
+ if (pcbp) // Page writes have pcb
+ {
+ u_int32_t s = pcbp->_pdtokl->size();
+ std::vector<data_tok*> dtokl;
+ dtokl.reserve(s);
+ for (u_int32_t k=0; k<s; k++)
+ {
+ data_tok* dtokp = pcbp->_pdtokl->at(k);
+ if (dtokp->decr_pg_cnt() == 0)
+ {
+ std::set<std::string>::iterator it;
+ switch (dtokp->wstate())
+ {
+ case data_tok::ENQ_SUBM:
+ dtokl.push_back(dtokp);
+ tot_data_toks++;
+ dtokp->set_wstate(data_tok::ENQ);
+ if (dtokp->has_xid())
+ // Ignoring return value here. A non-zero return can signify that the transaction
+ // has committed or aborted, and which was completed prior to the aio returning.
+ _tmap.set_aio_compl(dtokp->xid(), dtokp->rid());
+ break;
+ case data_tok::DEQ_SUBM:
+ dtokl.push_back(dtokp);
+ tot_data_toks++;
+ dtokp->set_wstate(data_tok::DEQ);
+ if (dtokp->has_xid())
+ // Ignoring return value - see note above.
+ _tmap.set_aio_compl(dtokp->xid(), dtokp->rid());
+ break;
+ case data_tok::ABORT_SUBM:
+ dtokl.push_back(dtokp);
+ tot_data_toks++;
+ dtokp->set_wstate(data_tok::ABORTED);
+ it = _txn_pending_set.find(dtokp->xid());
+ if (it == _txn_pending_set.end())
+ {
+ std::ostringstream oss;
+ oss << std::hex << "_txn_pending_set: abort xid=\"";
+ oss << dtokp->xid() << "\"";
+ throw jexception(jerrno::JERR_MAP_NOTFOUND, oss.str(), "wmgr",
+ "get_events");
+ }
+ _txn_pending_set.erase(it);
+ break;
+ case data_tok::COMMIT_SUBM:
+ dtokl.push_back(dtokp);
+ tot_data_toks++;
+ dtokp->set_wstate(data_tok::COMMITTED);
+ it = _txn_pending_set.find(dtokp->xid());
+ if (it == _txn_pending_set.end())
+ {
+ std::ostringstream oss;
+ oss << std::hex << "_txn_pending_set: commit xid=\"";
+ oss << dtokp->xid() << "\"";
+ throw jexception(jerrno::JERR_MAP_NOTFOUND, oss.str(), "wmgr",
+ "get_events");
+ }
+ _txn_pending_set.erase(it);
+ break;
+ case data_tok::ENQ_PART:
+ case data_tok::DEQ_PART:
+ case data_tok::ABORT_PART:
+ case data_tok::COMMIT_PART:
+ // ignore these
+ break;
+ default:
+ // throw for anything else
+ std::ostringstream oss;
+ oss << "dtok_id=" << dtokp->id() << " dtok_state=" << dtokp->wstate_str();
+ throw jexception(jerrno::JERR_WMGR_BADDTOKSTATE, oss.str(), "wmgr",
+ "get_events");
+ } // switch
+ } // if
+ } // for
+
+ // Increment the completed write offset
+ // NOTE: We cannot use _wrfc here, as it may have rotated since submitting count.
+ // Use stored pointer to fcntl in the pcb instead.
+ pcbp->_wfh->add_wr_cmpl_cnt_dblks(pcbp->_wdblks);
+ pcbp->_wfh->decr_aio_cnt();
+ _jc->instr_decr_outstanding_aio_cnt();
+
+ // Clean up this pcb's data_tok list
+ pcbp->_pdtokl->clear();
+ pcbp->_state = state;
+
+ // Perform AIO return callback
+ if (_cbp && tot_data_toks)
+ _cbp->wr_aio_cb(dtokl);
+ }
+ else // File header writes have no pcb
+ {
+ // get lfid from original file header record, update info for that lfid
+ file_hdr* fhp = (file_hdr*)aiocbp->u.c.buf;
+ u_int32_t lfid = fhp->_lfid;
+ fcntl* fcntlp = _jc->get_fcntlp(lfid);
+ fcntlp->add_wr_cmpl_cnt_dblks(JRNL_SBLK_SIZE);
+ fcntlp->decr_aio_cnt();
+ fcntlp->set_wr_fhdr_aio_outstanding(false);
+ }
+ }
+
+ return tot_data_toks;
+}
+
+bool
+wmgr::is_txn_synced(const std::string& xid)
+{
+ // Ignore xid not found error here
+ if (_tmap.is_txn_synced(xid) == txn_map::TMAP_NOT_SYNCED)
+ return false;
+ // Check for outstanding commit/aborts
+ std::set<std::string>::iterator it = _txn_pending_set.find(xid);
+ return it == _txn_pending_set.end();
+}
+
+void
+wmgr::initialize(aio_callback* const cbp, const u_int32_t wcache_pgsize_sblks, const u_int16_t wcache_num_pages)
+{
+ pmgr::initialize(cbp, wcache_pgsize_sblks, wcache_num_pages);
+ wmgr::clean();
+ _num_jfiles = _jc->num_jfiles();
+ if (::posix_memalign(&_fhdr_base_ptr, _sblksize, _sblksize * _num_jfiles))
+ {
+ wmgr::clean();
+ std::ostringstream oss;
+ oss << "posix_memalign(): blksize=" << _sblksize << " size=" << _sblksize;
+ oss << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR__MALLOC, oss.str(), "wmgr", "initialize");
+ }
+ _fhdr_ptr_arr = (void**)std::malloc(_num_jfiles * sizeof(void*));
+ MALLOC_CHK(_fhdr_ptr_arr, "_fhdr_ptr_arr", "wmgr", "initialize");
+ _fhdr_aio_cb_arr = (aio_cb**)std::malloc(sizeof(aio_cb*) * _num_jfiles);
+ MALLOC_CHK(_fhdr_aio_cb_arr, "_fhdr_aio_cb_arr", "wmgr", "initialize");
+ std::memset(_fhdr_aio_cb_arr, 0, sizeof(aio_cb*) * _num_jfiles);
+ for (u_int16_t i=0; i<_num_jfiles; i++)
+ {
+ _fhdr_ptr_arr[i] = (void*)((char*)_fhdr_base_ptr + _sblksize * i);
+ _fhdr_aio_cb_arr[i] = new aio_cb;
+ }
+ _page_cb_arr[0]._state = IN_USE;
+ _ddtokl.clear();
+ _cached_offset_dblks = 0;
+ _enq_busy = false;
+}
+
+iores
+wmgr::pre_write_check(const _op_type op, const data_tok* const dtokp,
+ const std::size_t xidsize, const std::size_t dsize, const bool external
+ ) const
+{
+ // Check status of current file
+ if (!_wrfc.is_wr_reset())
+ {
+ if (!_wrfc.wr_reset())
+ return RHM_IORES_FULL;
+ }
+
+ // Check status of current page is ok for writing
+ if (_page_cb_arr[_pg_index]._state != IN_USE)
+ {
+ if (_page_cb_arr[_pg_index]._state == UNUSED)
+ _page_cb_arr[_pg_index]._state = IN_USE;
+ else if (_page_cb_arr[_pg_index]._state == AIO_PENDING)
+ return RHM_IORES_PAGE_AIOWAIT;
+ else
+ {
+ std::ostringstream oss;
+ oss << "jrnl=" << _jc->id() << " op=" << _op_str[op];
+ oss << " index=" << _pg_index << " pg_state=" << _page_cb_arr[_pg_index].state_str();
+ throw jexception(jerrno::JERR_WMGR_BADPGSTATE, oss.str(), "wmgr", "pre_write_check");
+ }
+ }
+
+ // operation-specific checks
+ switch (op)
+ {
+ case WMGR_ENQUEUE:
+ {
+ // Check for enqueue reaching cutoff threshold
+ u_int32_t size_dblks = jrec::size_dblks(enq_rec::rec_size(xidsize, dsize,
+ external));
+ if (!_enq_busy && _wrfc.enq_threshold(_cached_offset_dblks + size_dblks))
+ return RHM_IORES_ENQCAPTHRESH;
+ if (!dtokp->is_writable())
+ {
+ std::ostringstream oss;
+ oss << "jrnl=" << _jc->id() << " op=" << _op_str[op];
+ oss << " dtok_id=" << dtokp->id() << " dtok_state=" << dtokp->wstate_str();
+ throw jexception(jerrno::JERR_WMGR_BADDTOKSTATE, oss.str(), "wmgr",
+ "pre_write_check");
+ }
+ }
+ break;
+ case WMGR_DEQUEUE:
+ if (!dtokp->is_dequeueable())
+ {
+ std::ostringstream oss;
+ oss << "jrnl=" << _jc->id() << " op=" << _op_str[op];
+ oss << " dtok_id=" << dtokp->id() << " dtok_state=" << dtokp->wstate_str();
+ throw jexception(jerrno::JERR_WMGR_BADDTOKSTATE, oss.str(), "wmgr",
+ "pre_write_check");
+ }
+ break;
+ case WMGR_ABORT:
+ break;
+ case WMGR_COMMIT:
+ break;
+ }
+
+ return RHM_IORES_SUCCESS;
+}
+
+void
+wmgr::dequeue_check(const std::string& xid, const u_int64_t drid)
+{
+ // First check emap
+ bool found = false;
+ int16_t fid = _emap.get_pfid(drid);
+ if (fid < enq_map::EMAP_OK) // fail
+ {
+ if (fid == enq_map::EMAP_RID_NOT_FOUND)
+ {
+ if (xid.size())
+ found = _tmap.data_exists(xid, drid);
+ }
+ else if (fid == enq_map::EMAP_LOCKED)
+ {
+ std::ostringstream oss;
+ oss << std::hex << "drid=0x" << drid;
+ throw jexception(jerrno::JERR_MAP_LOCKED, oss.str(), "wmgr", "dequeue_check");
+ }
+ }
+ else
+ found = true;
+ if (!found)
+ {
+ std::ostringstream oss;
+ oss << "jrnl=" << _jc->id() << " drid=0x" << std::hex << drid;
+ throw jexception(jerrno::JERR_WMGR_DEQRIDNOTENQ, oss.str(), "wmgr", "dequeue_check");
+ }
+}
+
+void
+wmgr::dblk_roundup()
+{
+ const u_int32_t xmagic = RHM_JDAT_EMPTY_MAGIC;
+ u_int32_t wdblks = jrec::size_blks(_cached_offset_dblks, JRNL_SBLK_SIZE) * JRNL_SBLK_SIZE;
+ while (_cached_offset_dblks < wdblks)
+ {
+ void* wptr = (void*)((char*)_page_ptr_arr[_pg_index] + _pg_offset_dblks * JRNL_DBLK_SIZE);
+ std::memcpy(wptr, (const void*)&xmagic, sizeof(xmagic));
+#ifdef RHM_CLEAN
+ std::memset((char*)wptr + sizeof(xmagic), RHM_CLEAN_CHAR, JRNL_DBLK_SIZE - sizeof(xmagic));
+#endif
+ _pg_offset_dblks++;
+ _cached_offset_dblks++;
+ }
+}
+
+void
+wmgr::write_fhdr(u_int64_t rid, u_int16_t fid, u_int16_t lid, std::size_t fro)
+{
+ file_hdr fhdr(RHM_JDAT_FILE_MAGIC, RHM_JDAT_VERSION, rid, fid, lid, fro, _wrfc.owi(), true);
+ std::memcpy(_fhdr_ptr_arr[fid], &fhdr, sizeof(fhdr));
+#ifdef RHM_CLEAN
+ std::memset((char*)_fhdr_ptr_arr[fid] + sizeof(fhdr), RHM_CLEAN_CHAR, _sblksize - sizeof(fhdr));
+#endif
+ aio_cb* aiocbp = _fhdr_aio_cb_arr[fid];
+ aio::prep_pwrite(aiocbp, _wrfc.fh(), _fhdr_ptr_arr[fid], _sblksize, 0);
+ if (aio::submit(_ioctx, 1, &aiocbp) < 0)
+ throw jexception(jerrno::JERR__AIO, "wmgr", "write_fhdr");
+ _aio_evt_rem++;
+ _wrfc.add_subm_cnt_dblks(JRNL_SBLK_SIZE);
+ _wrfc.incr_aio_cnt();
+ _wrfc.file_controller()->set_wr_fhdr_aio_outstanding(true);
+}
+
+void
+wmgr::rotate_page()
+{
+ _page_cb_arr[_pg_index]._state = AIO_PENDING;
+ if (_pg_offset_dblks >= _cache_pgsize_sblks * JRNL_SBLK_SIZE)
+ {
+ _pg_offset_dblks = 0;
+ _pg_cntr++;
+ }
+ if (++_pg_index >= _cache_num_pages)
+ _pg_index = 0;
+}
+
+void
+wmgr::clean()
+{
+ std::free(_fhdr_base_ptr);
+ _fhdr_base_ptr = 0;
+
+ std::free(_fhdr_ptr_arr);
+ _fhdr_ptr_arr = 0;
+
+ if (_fhdr_aio_cb_arr)
+ {
+ for (u_int32_t i=0; i<_num_jfiles; i++)
+ delete _fhdr_aio_cb_arr[i];
+ std::free(_fhdr_aio_cb_arr);
+ _fhdr_aio_cb_arr = 0;
+ }
+}
+
+const std::string
+wmgr::status_str() const
+{
+ std::ostringstream oss;
+ oss << "wmgr: pi=" << _pg_index << " pc=" << _pg_cntr;
+ oss << " po=" << _pg_offset_dblks << " aer=" << _aio_evt_rem;
+ oss << " edac:" << (_enq_busy?"T":"F") << (_deq_busy?"T":"F");
+ oss << (_abort_busy?"T":"F") << (_commit_busy?"T":"F");
+ oss << " ps=[";
+ for (int i=0; i<_cache_num_pages; i++)
+ {
+ switch (_page_cb_arr[i]._state)
+ {
+ case UNUSED: oss << "-"; break;
+ case IN_USE: oss << "U"; break;
+ case AIO_PENDING: oss << "A"; break;
+ case AIO_COMPLETE: oss << "*"; break;
+ default: oss << _page_cb_arr[i]._state;
+ }
+ }
+ oss << "] " << _wrfc.status_str();
+ return oss.str();
+}
+
+// static
+
+const char* wmgr::_op_str[] = {"enqueue", "dequeue", "abort", "commit"};
+
+} // namespace journal
+} // namespace mrg
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/wmgr.h b/qpid/cpp/src/qpid/legacystore/jrnl/wmgr.h
new file mode 100644
index 0000000000..8347221b1d
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/wmgr.h
@@ -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.
+ *
+ */
+
+/**
+ * \file wmgr.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::wmgr (write manager). See
+ * class documentation for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_WMGR_H
+#define QPID_LEGACYSTORE_JRNL_WMGR_H
+
+namespace mrg
+{
+namespace journal
+{
+class wmgr;
+}
+}
+
+#include <cstring>
+#include "qpid/legacystore/jrnl/enums.h"
+#include "qpid/legacystore/jrnl/pmgr.h"
+#include "qpid/legacystore/jrnl/wrfc.h"
+#include <set>
+
+namespace mrg
+{
+namespace journal
+{
+
+ /**
+ * \brief Class for managing a write page cache of arbitrary size and number of pages.
+ *
+ * The write page cache works on the principle of caching the write data within a page until
+ * that page is either full or flushed; this initiates a single AIO write operation to store
+ * the data on disk.
+ *
+ * The maximum disk throughput is achieved by keeping the write operations of uniform size.
+ * Waiting for a page cache to fill achieves this; and in high data volume/throughput situations
+ * achieves the optimal disk throughput. Calling flush() forces a write of the current page cache
+ * no matter how full it is, and disrupts the uniformity of the write operations. This should
+ * normally only be done if throughput drops and there is a danger of a page of unwritten data
+ * waiting around for excessive time.
+ *
+ * The usual tradeoff between data storage latency and throughput performance applies.
+ */
+ class wmgr : public pmgr
+ {
+ private:
+ wrfc& _wrfc; ///< Ref to write rotating file controller
+ u_int32_t _max_dtokpp; ///< Max data writes per page
+ u_int32_t _max_io_wait_us; ///< Max wait in microseconds till submit
+ void* _fhdr_base_ptr; ///< Base pointer to file header memory
+ void** _fhdr_ptr_arr; ///< Array of pointers to file headers memory
+ aio_cb** _fhdr_aio_cb_arr; ///< Array of iocb pointers for file header writes
+ u_int32_t _cached_offset_dblks; ///< Amount of unwritten data in page (dblocks)
+ std::deque<data_tok*> _ddtokl; ///< Deferred dequeue data_tok list
+ u_int32_t _jfsize_dblks; ///< Journal file size in dblks (NOT sblks!)
+ u_int32_t _jfsize_pgs; ///< Journal file size in cache pages
+ u_int16_t _num_jfiles; ///< Number of files used in iocb mallocs
+
+ // TODO: Convert _enq_busy etc into a proper threadsafe lock
+ // TODO: Convert to enum? Are these encodes mutually exclusive?
+ bool _enq_busy; ///< Flag true if enqueue is in progress
+ bool _deq_busy; ///< Flag true if dequeue is in progress
+ bool _abort_busy; ///< Flag true if abort is in progress
+ bool _commit_busy; ///< Flag true if commit is in progress
+
+ enum _op_type { WMGR_ENQUEUE = 0, WMGR_DEQUEUE, WMGR_ABORT, WMGR_COMMIT };
+ static const char* _op_str[];
+
+ enq_rec _enq_rec; ///< Enqueue record used for encoding/decoding
+ deq_rec _deq_rec; ///< Dequeue record used for encoding/decoding
+ txn_rec _txn_rec; ///< Transaction record used for encoding/decoding
+ std::set<std::string> _txn_pending_set; ///< Set containing xids of pending commits/aborts
+
+ public:
+ wmgr(jcntl* jc, enq_map& emap, txn_map& tmap, wrfc& wrfc);
+ wmgr(jcntl* jc, enq_map& emap, txn_map& tmap, wrfc& wrfc, const u_int32_t max_dtokpp,
+ const u_int32_t max_iowait_us);
+ virtual ~wmgr();
+
+ void initialize(aio_callback* const cbp, const u_int32_t wcache_pgsize_sblks,
+ const u_int16_t wcache_num_pages, const u_int32_t max_dtokpp,
+ const u_int32_t max_iowait_us, std::size_t eo = 0);
+ iores enqueue(const void* const data_buff, const std::size_t tot_data_len,
+ const std::size_t this_data_len, data_tok* dtokp, const void* const xid_ptr,
+ const std::size_t xid_len, const bool transient, const bool external);
+ iores dequeue(data_tok* dtokp, const void* const xid_ptr, const std::size_t xid_len,
+ const bool txn_coml_commit);
+ iores abort(data_tok* dtokp, const void* const xid_ptr, const std::size_t xid_len);
+ iores commit(data_tok* dtokp, const void* const xid_ptr, const std::size_t xid_len);
+ iores flush();
+ int32_t get_events(page_state state, timespec* const timeout, bool flush = false);
+ bool is_txn_synced(const std::string& xid);
+ inline bool curr_pg_blocked() const { return _page_cb_arr[_pg_index]._state != UNUSED; }
+ inline bool curr_file_blocked() const { return _wrfc.aio_cnt() > 0; }
+ inline u_int32_t unflushed_dblks() { return _cached_offset_dblks; }
+
+ // Debug aid
+ const std::string status_str() const;
+
+ private:
+ void initialize(aio_callback* const cbp, const u_int32_t wcache_pgsize_sblks,
+ const u_int16_t wcache_num_pages);
+ iores pre_write_check(const _op_type op, const data_tok* const dtokp,
+ const std::size_t xidsize = 0, const std::size_t dsize = 0, const bool external = false)
+ const;
+ void dequeue_check(const std::string& xid, const u_int64_t drid);
+ void file_header_check(const u_int64_t rid, const bool cont, const u_int32_t rec_dblks_rem);
+ void flush_check(iores& res, bool& cont, bool& done);
+ iores write_flush();
+ iores rotate_file();
+ void dblk_roundup();
+ void write_fhdr(u_int64_t rid, u_int16_t fid, u_int16_t lid, std::size_t fro);
+ void rotate_page();
+ void clean();
+ };
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_WMGR_H
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/wrfc.cpp b/qpid/cpp/src/qpid/legacystore/jrnl/wrfc.cpp
new file mode 100644
index 0000000000..64ee65f1ac
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/wrfc.cpp
@@ -0,0 +1,164 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file wrfc.cpp
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::wrfc (rotating
+ * file controller). See comments in file wrfc.h for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#include "qpid/legacystore/jrnl/wrfc.h"
+
+#include <cmath>
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include "qpid/legacystore/jrnl/jexception.h"
+
+namespace mrg
+{
+namespace journal
+{
+
+wrfc::wrfc(const lpmgr* lpmp):
+ rfc(lpmp),
+ _fsize_sblks(0),
+ _fsize_dblks(0),
+ _enq_cap_offs_dblks(0),
+ _rid(0),
+ _reset_ok(false),
+ _owi(false),
+ _frot(true)
+{}
+
+wrfc::~wrfc()
+{}
+
+void
+wrfc::initialize(const u_int32_t fsize_sblks, rcvdat* rdp)
+{
+ if (rdp)
+ {
+ _fc_index = rdp->_lfid;
+ _curr_fc = _lpmp->get_fcntlp(_fc_index);
+ _curr_fc->wr_reset(rdp);
+ _rid = rdp->_h_rid + 1;
+ _reset_ok = true;
+ _owi = rdp->_owi;
+ _frot = rdp->_frot;
+ if (rdp->_lffull)
+ rotate();
+ }
+ else
+ {
+ rfc::initialize();
+ rfc::set_findex(0);
+ _rid = 0ULL;
+ _reset_ok = false;
+ }
+ _fsize_sblks = fsize_sblks;
+ _fsize_dblks = fsize_sblks * JRNL_SBLK_SIZE;
+ _enq_cap_offs_dblks = (u_int32_t)std::ceil(_fsize_dblks * _lpmp->num_jfiles() * (100.0 - JRNL_ENQ_THRESHOLD) / 100);
+ // Check the offset is at least one file; if not, make it so
+ if (_enq_cap_offs_dblks < _fsize_dblks)
+ _enq_cap_offs_dblks = _fsize_dblks;
+}
+
+iores wrfc::rotate()
+{
+ if (!_lpmp->num_jfiles())
+ throw jexception(jerrno::JERR__NINIT, "wrfc", "rotate");
+ _fc_index++;
+ if (_fc_index == _lpmp->num_jfiles())
+ {
+ _fc_index = 0;
+ _owi = !_owi;
+ _frot = false;
+ }
+ _curr_fc = _lpmp->get_fcntlp(_fc_index);
+ if (_curr_fc->aio_cnt())
+ return RHM_IORES_FILE_AIOWAIT;
+ if (!wr_reset()) //Checks if file is still in use (ie not fully dequeued yet)
+ return RHM_IORES_FULL;
+ return RHM_IORES_SUCCESS;
+}
+
+u_int16_t wrfc::earliest_index() const
+{
+ if (_frot)
+ return 0;
+ u_int16_t next_index = _fc_index + 1;
+ if (next_index >= _lpmp->num_jfiles())
+ next_index = 0;
+ return next_index;
+}
+
+bool
+wrfc::enq_threshold(const u_int32_t enq_dsize_dblks) const
+{
+ u_int32_t subm_dblks = subm_cnt_dblks(); // includes file hdr if > 0
+ // This compensates for new files which don't have their file headers written yet,
+ // as file header space cannot be included in this calculation.
+ if (subm_dblks != 0)
+ subm_dblks -= 4;
+ u_int32_t fwd_dblks = subm_dblks + enq_dsize_dblks + _enq_cap_offs_dblks;
+ u_int16_t findex = _fc_index;
+ fcntl* fcp = _curr_fc;
+ bool in_use = false; // at least one file contains an enqueued record
+ bool overwrite = false; // reached the original journal file we started with
+ while (fwd_dblks && !(findex != _fc_index && fcp->enqcnt()))
+ {
+ fwd_dblks -= fwd_dblks > _fsize_dblks ? _fsize_dblks : fwd_dblks;
+ if (fwd_dblks)
+ {
+ if (++findex == _lpmp->num_jfiles())
+ findex = 0;
+ overwrite |= findex == _fc_index;
+ fcp = _lpmp->get_fcntlp(findex);
+ }
+ in_use |= fcp->enqcnt() > 0;
+ }
+ // Return true if threshold exceeded
+ return (findex != _fc_index && in_use) || overwrite;
+}
+
+bool wrfc::wr_reset()
+{
+ _reset_ok = _curr_fc->reset(); // returns false if full (ie file still contains enqueued recs)
+ return _reset_ok;
+}
+
+// TODO: update this to reflect all status data
+std::string
+wrfc::status_str() const
+{
+ std::ostringstream oss;
+ oss << "wrfc: " << rfc::status_str();
+ if (is_active())
+ oss << " fcntl[" << _fc_index << "]: " << _curr_fc->status_str();
+ return oss.str();
+}
+
+} // namespace journal
+} // namespace mrg
diff --git a/qpid/cpp/src/qpid/legacystore/jrnl/wrfc.h b/qpid/cpp/src/qpid/legacystore/jrnl/wrfc.h
new file mode 100644
index 0000000000..f0e4e73151
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/jrnl/wrfc.h
@@ -0,0 +1,154 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/**
+ * \file wrfc.h
+ *
+ * Qpid asynchronous store plugin library
+ *
+ * File containing code for class mrg::journal::wrfc (write rotating
+ * file controller). See class documentation for details.
+ *
+ * \author Kim van der Riet
+ */
+
+#ifndef QPID_LEGACYSTORE_JRNL_WRFC_H
+#define QPID_LEGACYSTORE_JRNL_WRFC_H
+
+namespace mrg
+{
+namespace journal
+{
+class wrfc;
+}
+}
+
+#include <cstddef>
+#include "qpid/legacystore/jrnl/enums.h"
+#include "qpid/legacystore/jrnl/rrfc.h"
+
+namespace mrg
+{
+namespace journal
+{
+
+ /**
+ * \class wrfc
+ * \brief Class to handle write management of a journal rotating file controller.
+ */
+ class wrfc : public rfc
+ {
+ private:
+ u_int32_t _fsize_sblks; ///< Size of journal files in sblks
+ u_int32_t _fsize_dblks; ///< Size of journal files in dblks
+ u_int32_t _enq_cap_offs_dblks; ///< Enqueue capacity offset
+ u_int64_t _rid; ///< Master counter for record ID (rid)
+ bool _reset_ok; ///< Flag set when reset succeeds
+ bool _owi; ///< Overwrite indicator
+ bool _frot; ///< Flag is true for first rotation, false otherwise
+
+ public:
+ wrfc(const lpmgr* lpmp);
+ virtual ~wrfc();
+
+ /**
+ * \brief Initialize the controller.
+ * \param fsize_sblks Size of each journal file in sblks.
+ * \param rdp Struct carrying restore information. Optional for non-restore use, defaults to 0 (NULL).
+ */
+ using rfc::initialize;
+ void initialize(const u_int32_t fsize_sblks, rcvdat* rdp = 0);
+
+ /**
+ * \brief Rotate active file controller to next file in rotating file group.
+ * \exception jerrno::JERR__NINIT if called before calling initialize().
+ */
+ iores rotate();
+
+ /**
+ * \brief Returns the index of the earliest complete file within the rotating
+ * file group. Unwritten files are excluded. The currently active file is
+ * excluded unless it is the only written file.
+ */
+ u_int16_t earliest_index() const;
+
+ /**
+ * \brief Determines if a proposed write would cause the enqueue threshold to be exceeded.
+ *
+ * The following routine finds whether the next write will take the write pointer to beyond the
+ * enqueue limit threshold. The following illustrates how this is achieved.
+ * <pre>
+ * Current file index: 4 +---+----------+
+ * X's mark still-enqueued records |msg| 1-thresh |
+ * msg = current msg size + unwritten cache +---+----------+
+ * thresh = JRNL_ENQ_THRESHOLD as a fraction ^ V
+ * +-------+-------+-------+-------+--+----+-------+-+-----+-------+
+ * file num ->| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
+ * enq recs ->| X XX |XX XXX |XX XXXX|XXXXXXX|XX | | | X |
+ * +-------+-------+-------+-------+--+----+-------+-+-----+-------+
+ * ^ ^ ^
+ * subm_dblks --+ | |
+ * These files must be free of enqueues
+ * If not, return true.
+ * </pre>
+ * \param enq_dsize_dblks Proposed size of write in dblocks
+ */
+ bool enq_threshold(const u_int32_t enq_dsize_dblks) const;
+
+ inline u_int64_t rid() const { return _rid; }
+ inline u_int64_t get_incr_rid() { return _rid++; }
+ bool wr_reset();
+ inline bool is_wr_reset() const { return _reset_ok; }
+ inline bool owi() const { return _owi; }
+ inline bool frot() const { return _frot; }
+
+ // Convenience access methods to current file controller
+
+ inline int fh() const { return _curr_fc->wr_fh(); }
+
+ inline u_int32_t subm_cnt_dblks() const { return _curr_fc->wr_subm_cnt_dblks(); }
+ inline std::size_t subm_offs() const { return _curr_fc->wr_subm_offs(); }
+ inline u_int32_t add_subm_cnt_dblks(u_int32_t a) { return _curr_fc->add_wr_subm_cnt_dblks(a); }
+
+ inline u_int32_t cmpl_cnt_dblks() const { return _curr_fc->wr_cmpl_cnt_dblks(); }
+ inline std::size_t cmpl_offs() const { return _curr_fc->wr_cmpl_offs(); }
+ inline u_int32_t add_cmpl_cnt_dblks(u_int32_t a) { return _curr_fc->add_wr_cmpl_cnt_dblks(a); }
+
+ inline u_int16_t aio_cnt() const { return _curr_fc->aio_cnt(); }
+ inline u_int16_t incr_aio_cnt() { return _curr_fc->incr_aio_cnt(); }
+ inline u_int16_t decr_aio_cnt() { return _curr_fc->decr_aio_cnt(); }
+
+ inline bool is_void() const { return _curr_fc->wr_void(); }
+ inline bool is_empty() const { return _curr_fc->wr_empty(); }
+ inline u_int32_t remaining_dblks() const { return _curr_fc->wr_remaining_dblks(); }
+ inline bool is_full() const { return _curr_fc->is_wr_full(); };
+ inline bool is_compl() const { return _curr_fc->is_wr_compl(); };
+ inline u_int32_t aio_outstanding_dblks() const { return _curr_fc->wr_aio_outstanding_dblks(); }
+ inline bool file_rotate() const { return _curr_fc->wr_file_rotate(); }
+
+ // Debug aid
+ std::string status_str() const;
+ };
+
+} // namespace journal
+} // namespace mrg
+
+#endif // ifndef QPID_LEGACYSTORE_JRNL_WRFC_H
diff --git a/qpid/cpp/src/qpid/legacystore/management-schema.xml b/qpid/cpp/src/qpid/legacystore/management-schema.xml
new file mode 100644
index 0000000000..71497cc0f9
--- /dev/null
+++ b/qpid/cpp/src/qpid/legacystore/management-schema.xml
@@ -0,0 +1,99 @@
+<schema package="org.apache.qpid.legacystore">
+
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+
+ <class name="Store">
+ <property name="brokerRef" type="objId" access="RO" references="qpid.Broker" index="y" parentRef="y"/>
+ <property name="location" type="sstr" access="RO" desc="Logical directory on disk"/>
+ <property name="defaultInitialFileCount" type="uint16" access="RO" unit="file" desc="Default number of files initially allocated to each journal"/>
+ <property name="defaultDataFileSize" type="uint32" access="RO" unit="RdPg" desc="Default size of each journal data file"/>
+ <property name="tplIsInitialized" type="bool" access="RO" desc="Transaction prepared list has been initialized by a transactional prepare"/>
+ <property name="tplDirectory" type="sstr" access="RO" desc="Transaction prepared list directory"/>
+ <property name="tplWritePageSize" type="uint32" access="RO" unit="byte" desc="Page size in transaction prepared list write-page-cache"/>
+ <property name="tplWritePages" type="uint32" access="RO" unit="wpage" desc="Number of pages in transaction prepared list write-page-cache"/>
+ <property name="tplInitialFileCount" type="uint16" access="RO" unit="file" desc="Number of files initially allocated to transaction prepared list journal"/>
+ <property name="tplDataFileSize" type="uint32" access="RO" unit="byte" desc="Size of each journal data file in transaction prepared list journal"/>
+ <property name="tplCurrentFileCount" type="uint32" access="RO" unit="file" desc="Number of files currently allocated to transaction prepared list journal"/>
+
+ <statistic name="tplTransactionDepth" type="hilo32" unit="txn" desc="Number of currently enqueued prepared transactions"/>
+ <statistic name="tplTxnPrepares" type="count64" unit="record" desc="Total transaction prepares on transaction prepared list"/>
+ <statistic name="tplTxnCommits" type="count64" unit="record" desc="Total transaction commits on transaction prepared list"/>
+ <statistic name="tplTxnAborts" type="count64" unit="record" desc="Total transaction aborts on transaction prepared list"/>
+ <statistic name="tplOutstandingAIOs" type="hilo32" unit="aio_op" desc="Deprecated"/>
+ </class>
+
+ <class name="Journal">
+ <property name="queueRef" type="objId" access="RO" references="qpid.Queue" isGeneralReference="y"/>
+ <property name="name" type="sstr" access="RO" index="y"/>
+ <property name="directory" type="sstr" access="RO" desc="Directory containing journal files"/>
+ <property name="baseFileName" type="sstr" access="RO" desc="Base filename prefix for journal"/>
+ <property name="writePageSize" type="uint32" access="RO" unit="byte" desc="Page size in write-page-cache"/>
+ <property name="writePages" type="uint32" access="RO" unit="wpage" desc="Number of pages in write-page-cache"/>
+ <property name="readPageSize" type="uint32" access="RO" unit="byte" desc="Page size in read-page-cache"/>
+ <property name="readPages" type="uint32" access="RO" unit="rpage" desc="Number of pages in read-page-cache"/>
+ <property name="initialFileCount" type="uint16" access="RO" unit="file" desc="Number of files initially allocated to this journal"/>
+ <property name="autoExpand" type="bool" access="RO" desc="Deprecated"/>
+ <property name="currentFileCount" type="uint16" access="RO" unit="file" desc="Number of files currently allocated to this journal"/>
+ <property name="maxFileCount" type="uint16" access="RO" unit="file" desc="Deprecated"/>
+ <property name="dataFileSize" type="uint32" access="RO" unit="byte" desc="Size of each journal data file"/>
+
+ <statistic name="recordDepth" type="hilo32" unit="record" desc="Number of currently enqueued records (durable messages)"/>
+ <statistic name="enqueues" type="count64" unit="record" desc="Total enqueued records on journal"/>
+ <statistic name="dequeues" type="count64" unit="record" desc="Total dequeued records on journal"/>
+ <statistic name="txn" type="count32" unit="record" desc="Total open transactions (xids) on journal"/>
+ <statistic name="txnEnqueues" type="count64" unit="record" desc="Total transactional enqueued records on journal"/>
+ <statistic name="txnDequeues" type="count64" unit="record" desc="Total transactional dequeued records on journal"/>
+ <statistic name="txnCommits" type="count64" unit="record" desc="Total transactional commit records on journal"/>
+ <statistic name="txnAborts" type="count64" unit="record" desc="Total transactional abort records on journal"/>
+ <statistic name="outstandingAIOs" type="hilo32" unit="aio_op" desc="Number of currently outstanding AIO requests in Async IO system"/>
+
+<!--
+ The following are not yet "wired up" in JournalImpl.cpp
+-->
+ <statistic name="freeFileCount" type="hilo32" unit="file" desc="Deprecated"/>
+ <statistic name="availableFileCount" type="hilo32" unit="file" desc="Deprecated"/>
+ <statistic name="writeWaitFailures" type="count64" unit="record" desc="Deprecated"/>
+ <statistic name="writeBusyFailures" type="count64" unit="record" desc="Deprecated"/>
+ <statistic name="readRecordCount" type="count64" unit="record" desc="Deprecated"/>
+ <statistic name="readBusyFailures" type="count64" unit="record" desc="Deprecated"/>
+ <statistic name="writePageCacheDepth" type="hilo32" unit="wpage" desc="Deprecated"/>
+ <statistic name="readPageCacheDepth" type="hilo32" unit="rpage" desc="Deprecated"/>
+
+ <method name="expand" desc="Increase number of files allocated for this journal">
+ <arg name="by" type="uint32" dir="I" desc="Number of files to increase journal size by"/>
+ </method>
+ </class>
+
+ <eventArguments>
+ <arg name="autoExpand" type="bool" desc="Journal auto-expand enabled"/>
+ <arg name="fileSize" type="uint32" desc="Journal file size in bytes"/>
+ <arg name="jrnlId" type="sstr" desc="Journal Id"/>
+ <arg name="numEnq" type="uint32" desc="Number of recovered enqueues"/>
+ <arg name="numFiles" type="uint16" desc="Number of journal files"/>
+ <arg name="numTxn" type="uint32" desc="Number of recovered transactions"/>
+ <arg name="numTxnDeq" type="uint32" desc="Number of recovered transactional dequeues"/>
+ <arg name="numTxnEnq" type="uint32" desc="Number of recovered transactional enqueues"/>
+ <arg name="what" type="sstr" desc="Description of event"/>
+ </eventArguments>
+ <event name="enqThresholdExceeded" sev="warn" args="jrnlId, what"/>
+ <event name="created" sev="notice" args="jrnlId, fileSize, numFiles"/>
+ <event name="full" sev="error" args="jrnlId, what"/>
+ <event name="recovered" sev="notice" args="jrnlId, fileSize, numFiles, numEnq, numTxn, numTxnEnq, numTxnDeq"/>
+</schema>
diff --git a/qpid/cpp/src/qpid/linearstore/BindingDbt.cpp b/qpid/cpp/src/qpid/linearstore/BindingDbt.cpp
new file mode 100644
index 0000000000..47738cce39
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/BindingDbt.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/linearstore/BindingDbt.h"
+
+namespace qpid {
+namespace linearstore {
+
+BindingDbt::BindingDbt(const qpid::broker::PersistableExchange& e, const qpid::broker::PersistableQueue& q, const std::string& k, const qpid::framing::FieldTable& a)
+ : data(new char[encodedSize(e, q, k, a)]),
+ buffer(data, encodedSize(e, q, k, a))
+{
+ buffer.putLongLong(q.getPersistenceId());
+ buffer.putShortString(q.getName());
+ buffer.putShortString(k);
+ buffer.put(a);
+
+ set_data(data);
+ set_size(encodedSize(e, q, k, a));
+}
+
+BindingDbt::~BindingDbt()
+{
+ delete [] data;
+}
+
+uint32_t BindingDbt::encodedSize(const qpid::broker::PersistableExchange& /*not used*/, const qpid::broker::PersistableQueue& q, const std::string& k, const qpid::framing::FieldTable& a)
+{
+ return 8 /*queue id*/ + q.getName().size() + 1 + k.size() + 1 + a.encodedSize();
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/linearstore/BindingDbt.h b/qpid/cpp/src/qpid/linearstore/BindingDbt.h
new file mode 100644
index 0000000000..e5d61de248
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/BindingDbt.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_BINDINGDBT_H
+#define QPID_LINEARSTORE_BINDINGDBT_H
+
+#include "db-inc.h"
+#include "qpid/broker/PersistableExchange.h"
+#include "qpid/broker/PersistableQueue.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/framing/FieldTable.h"
+
+namespace qpid{
+namespace linearstore{
+
+class BindingDbt : public Dbt
+{
+ char* data;
+ qpid::framing::Buffer buffer;
+
+ static uint32_t encodedSize(const qpid::broker::PersistableExchange& e,
+ const qpid::broker::PersistableQueue& q,
+ const std::string& k,
+ const qpid::framing::FieldTable& a);
+
+public:
+ BindingDbt(const qpid::broker::PersistableExchange& e,
+ const qpid::broker::PersistableQueue& q,
+ const std::string& k,
+ const qpid::framing::FieldTable& a);
+
+ virtual ~BindingDbt();
+
+};
+
+}}
+
+#endif // ifndef QPID_LINEARSTORE_BINDINGDBT_H
diff --git a/qpid/cpp/src/qpid/linearstore/BufferValue.cpp b/qpid/cpp/src/qpid/linearstore/BufferValue.cpp
new file mode 100644
index 0000000000..5115055375
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/BufferValue.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/linearstore/BufferValue.h"
+
+namespace qpid {
+namespace linearstore {
+
+
+
+BufferValue::BufferValue(uint32_t size, uint64_t offset)
+ : data(new char[size]),
+ buffer(data, size)
+{
+ set_data(data);
+ set_size(size);
+ set_flags(DB_DBT_USERMEM | DB_DBT_PARTIAL);
+ set_doff(offset);
+ set_dlen(size);
+ set_ulen(size);
+}
+
+BufferValue::BufferValue(const qpid::broker::Persistable& p)
+ : data(new char[p.encodedSize()]),
+ buffer(data, p.encodedSize())
+{
+ p.encode(buffer);
+
+ set_data(data);
+ set_size(p.encodedSize());
+}
+
+BufferValue::~BufferValue()
+{
+ delete [] data;
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/linearstore/BufferValue.h b/qpid/cpp/src/qpid/linearstore/BufferValue.h
new file mode 100644
index 0000000000..daeb81306a
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/BufferValue.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_BUFFERVALUE_H
+#define QPID_LINEARSTORE_BUFFERVALUE_H
+
+#include "db-inc.h"
+#include "qpid/broker/Persistable.h"
+#include "qpid/framing/Buffer.h"
+
+namespace qpid{
+namespace linearstore{
+
+class BufferValue : public Dbt
+{
+ char* data;
+
+public:
+ qpid::framing::Buffer buffer;
+
+ BufferValue(uint32_t size, uint64_t offset);
+ BufferValue(const qpid::broker::Persistable& p);
+ virtual ~BufferValue();
+};
+
+}}
+
+#endif // ifndef QPID_LINEARSTORE_BUFFERVALUE_H
diff --git a/qpid/cpp/src/qpid/linearstore/Cursor.h b/qpid/cpp/src/qpid/linearstore/Cursor.h
new file mode 100644
index 0000000000..0287803b21
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/Cursor.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_CURSOR_H
+#define QPID_LINEARSTORE_CURSOR_H
+
+#include <boost/shared_ptr.hpp>
+#include "db-inc.h"
+
+namespace qpid{
+namespace linearstore{
+
+class Cursor
+{
+ Dbc* cursor;
+public:
+ typedef boost::shared_ptr<Db> db_ptr;
+
+ Cursor() : cursor(0) {}
+ virtual ~Cursor() { if(cursor) cursor->close(); }
+
+ void open(db_ptr db, DbTxn* txn, uint32_t flags = 0) { db->cursor(txn, &cursor, flags); }
+ void close() { if(cursor) cursor->close(); cursor = 0; }
+ Dbc* get() { return cursor; }
+ Dbc* operator->() { return cursor; }
+ bool next(Dbt& key, Dbt& value) { return cursor->get(&key, &value, DB_NEXT) == 0; }
+ bool current(Dbt& key, Dbt& value) { return cursor->get(&key, &value, DB_CURRENT) == 0; }
+};
+
+}}
+
+#endif // ifndef QPID_LINEARSTORE_CURSOR_H
diff --git a/qpid/cpp/src/qpid/linearstore/DataTokenImpl.cpp b/qpid/cpp/src/qpid/linearstore/DataTokenImpl.cpp
new file mode 100644
index 0000000000..0b1f3d7941
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/DataTokenImpl.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/linearstore/DataTokenImpl.h"
+
+using namespace qpid::linearstore;
+
+DataTokenImpl::DataTokenImpl():data_tok() {}
+
+DataTokenImpl::~DataTokenImpl() {}
diff --git a/qpid/cpp/src/qpid/linearstore/DataTokenImpl.h b/qpid/cpp/src/qpid/linearstore/DataTokenImpl.h
new file mode 100644
index 0000000000..7152cef6a0
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/DataTokenImpl.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_DATATOKENIMPL_H
+#define QPID_LINEARSTORE_DATATOKENIMPL_H
+
+#include "qpid/linearstore/journal/data_tok.h"
+#include "qpid/broker/PersistableMessage.h"
+#include <boost/intrusive_ptr.hpp>
+
+namespace qpid{
+namespace linearstore{
+
+class DataTokenImpl : public qpid::linearstore::journal::data_tok, public qpid::RefCounted
+{
+ private:
+ boost::intrusive_ptr<qpid::broker::PersistableMessage> sourceMsg;
+ public:
+ DataTokenImpl();
+ virtual ~DataTokenImpl();
+
+ inline boost::intrusive_ptr<qpid::broker::PersistableMessage>& getSourceMessage() { return sourceMsg; }
+ inline void setSourceMessage(const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg) { sourceMsg = msg; }
+};
+
+} // namespace msgstore
+} // namespace mrg
+
+#endif // ifndef QPID_LINEARSTORE_DATATOKENIMPL_H
diff --git a/qpid/cpp/src/qpid/linearstore/ISSUES b/qpid/cpp/src/qpid/linearstore/ISSUES
new file mode 100644
index 0000000000..4023ba9629
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/ISSUES
@@ -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.
+#
+
+Linear Store issues:
+
+Current/pending:
+================
+ Q-JIRA RHBZ Description / Comments
+ ------ ------- ----------------------
+ 5359 - Linearstore: Implement new management schema and wire into store
+ 5360 - Linearstore: Evaluate and rework logging to produce a consistent log output
+* 5361 1145359 Linearstore: No tests for linearstore functionality currently exist
+ svn r.1564893 2014-02-05: Added tx-test-soak.sh
+ svn r.1564935 2014-02-05: Added license text to tx-test-soak.sh
+ svn r.1625283 2014-09-16: Basic python tests from legacystore ported over to linearstore
+ * No existing tests for linearstore:
+ ** Basic broker-level tests for txn and non-txn recovery
+ ** Store-level tests which check write boundary conditions
+ ** EFP tests, including file recovery, error management
+ ** Unit tests
+ ** Basic performance tests
+ 5464 - [linearstore] Incompletely created journal files accumulate in EFP
+ - 1088944 [Linearstore] store does not return all files to EFP after purging big queue <queue purge issue>
+* - 1066256 [LinearStore] changing efp size after using store broke the new durable nodes creation
+ - 1067480 [LinearStore] Provide a way to limit max count/size of empty files in EFP
+ - 1067429 [LinearStore] last file from deleted queue is not moved to EFP <queue delete issue>
+ - 1067482 [LinearStore] Provide a way to preallocate empty pages in EFP
+* 6303 1180660 [linearstore] Roll back auto-upgrade of store directory structure
+* 5362 1145363 Linearstore: No store tools exist for examining the journals
+ svn r.1556888 2014-01-09: WIP checkin for linearstore version of qpid_qls_analyze. Needs testing and tidy-up.
+ svn r.1560530 2014-01-22: Bugfixes for qpid_qls_analyze
+ svn r.1561848 2014-01-27: Bugfixes and enhancements for qpid_qls_analyze
+ svn r.1564808 2014-02-05: Bugfixes and enhancements for qpid_qls_analyze
+ svn r.1578899 2014-03-18: Bugfixes and enhancements for qpid_qls_analyze
+ svn r.1583778 2014-04-01: Bugfix for qpid_qls_analyze
+ * Store analysis and status
+ * Recovery/reading of message content
+ * Empty file pool status and management
+
+
+
+
+Fixed/closed:
+=============
+ Q-JIRA RHBZ Description / Comments
+ ------ ------- ----------------------
+ 5357 1052518 Linearstore: Empty file recycling not functional
+ svn r.1545563 2013-11-26: Propsed fix. VERIFIED
+ 5358 1052727 Linearstore: Checksums not implemented in record tail
+ svn r.1547601 2013-12-03: Propsed fix. NEEDINFO on algorithm
+ 5387 1036071 Linearstore: Segmentation fault when deleting queue
+ svn r.1547641 2013-12-03: Propsed fix. VERIFIED
+ 5388 1035802 Linearstore: Segmentation fault when recovering empty queue
+ svn r.1547921 2013-12-04: Propsed fix. VERIFIED
+NO-JIRA - Added missing Apache copyright/license text
+ svn r.1551304 2013-12-16: Propsed fix
+ 5425 1052445 Linearstore: Transaction Prepared List (TPL) fails with jexception 0x0402 AtomicCounter::addLimit() threw JERR_JNLF_FILEOFFSOVFL
+ svn r.1551361 2013-12-16: Proposed fix VERIFIED
+ 5442 1039949 Linearstore: Dtx recover test fails
+ svn r.1552772 2013-12-20: Proposed fix VERIFIED
+ 5444 1052775 Linearstore: Recovering from qpid-txtest fails with "Inconsistent TPL 2PC count" error message
+ svn r.1553148 2013-12-23: Proposed fix NEEDIFNO on reproduction and testing
+ - 1038599 [LinearStore] Abort when deleting used queue after restart
+ CLOSED-NOTABUG 2014-01-06
+ 5460 1051097 [linearstore] Recovery of store which contains prepared but incomplete transactions results in message loss
+ svn r.1556892 2014-01-09: Proposed fix VERIFIED
+ 5473 1051924 [linearstore] Recovery of journal in which last logical file contains truncated record causes crash
+ svn r.1557620 2014-01-12: Proposed fix MODIFIED
+ 5483 - [linearstore] Recovery of journal with partly written record fails with "JERR_JREC_BADRECTAIL: Invalid data record tail" error message
+ svn r.1558589 2014-01-15: Proposed fix
+ * May be linked to RHBZ 1039522 - VERIFIED
+ * May be linked to RHBZ 1039525 - VERIFIED
+ - 1039522 Qpid crashes while recovering from linear store around apid::linearstore::journal::JournalFile::getFqFileName() including enq_rec::decode() threw JERR_JREC_BAD_RECTAIL
+ * Possible dup of 1039525
+ * May be fixed by QPID-5483 - waiting for needinfo, recommend rebuilding with QPID-5483 fix and re-testing. VERIFIED.
+ - 1039525 Qpid crashes while recovering from linear store around apid::linearstore::journal::jexception::format including enq_rec::decode() threw JERR_JREC_BAD_REC_TAIL
+ * Possible dup of 1039522
+ * May be fixed by QPID-5483 - waiting for needinfo, recommend rebuilding with QPID-5483 fix and re-testing. VERIFIED.
+ 5487 1054448 [linearstore] Replace use of /dev/urandom with c random generator calls
+ svn r.1558913 2014-01-16: Proposed fix VEFIFIED
+ 5484 1035843 Slow performance for producers
+ svn r.1558592 2014-01-15 fixes an issue with using /dev/random as a source of random numbers for Journal serial numbers.
+ svn r.1558913 2014-01-16 replaces use of /dev/urandom with several calls to rand() to construct a 64-bit random number.
+ * Recommend rebuilding and testing for performance again with these two fixes. VERIFIED.
+ 5479 1053701 [linearstore] Using recovered store results in "JERR_JNLF_FILEOFFSOVFL: Attempted to increase submitted offset past file size. (JournalFile::submittedDblkCount)" error message
+ * Probability: 2 of 600 (0.3%) using tx-test-soak.sh
+ * Fixed by checkin for QPID-5480, no longer able to reproduce. VERIFIED
+ 5480 1053749 [linearstore] Recovery of store failure with "JERR_MAP_NOTFOUND: Key not found in map." error message
+ svn r.1564877 2014-02-05: Proposed fix
+ * Probability: 6 of 600 (1.0%) using tx-test-soak.sh
+ * If broker is started a second time after failure, it starts correctly and test completes ok.
+ * Problem: File is being recycled to EFP with still-locked enqueues in it (ie dequeued transactionally).
+ * Problem: Record alignment check writes filler records to wrong file when decoding bad record moves across a file boundary
+ 5603 1063700 [linearstore] broker restart fails under stress test
+ svn r.1574513 2014-03-05: Proposed fix. POST
+ * jexception 0x0701 RecoveryManager::readNextRemainingRecord() threw JERR_JREC_BADRECTAIL
+ 5607 1064181 [linearstore] Qpidd closes transactional client session&connection with async_dequeue() failed
+ svn r.1575009 2014-03-06 Proposed fix. POST
+ * jexception 0x010b LinearFileController::getCurrentSerial() threw JERR_NULL
+ - 1064230 [linearstore] Qpidd linearstore recovery sometimes fail to recover messages with recoverMessages() failed
+ * jexception 0x0701 RecoveryManager::readNextRemainingRecord() threw JERR_JREC_BADRECTAIL
+ * possible dup of 1063700
+ - 1036026 [LinearStore] Qpid linear store unable to create durable queue - framing-error: Queue <q-name>: create() failed: jexception 0x0000
+ * UNABLE TO REPRODUCE - but Frantizek has additional info
+ * Retested after checkin 1575009, problem solved. VERIFIED
+ 5651 - [C++ broker] segfault in qpid::linearstore::journal::jdir::clear_dir when declaring durable queue
+ svn r.1582730 2014-03-28 Proposed fix by Pavel Moravec
+ * Bug introduced by r.1578899.
+ 5661 - [linearstore] Set default cmake build to exclude linearstore
+ svn r.1584379 2014-04-03 Proposed solution.
+ * Run ccmake, select BUILD_LINEARSTORE to change its value to ON to build.
+ 5750 1078142 [linearstore] qpidd closes connection with (distributed) transactional client while checking previous transaction, broker signals error (closed by error: Queue Ve0-2: async_dequeue() failed: exception 0x0103 wmgr::get_events() threw JERR__AIO: AIO error)
+ svn r.1594215 2014-05-13 Proposed solution.
+ * jexception 0x0103 wmgr::get_events() threw JERR__AIO: AIO error. (AIO write operation failed: Invalid argument (-22) [pg=0 size=8192 offset=4096 fh=22])
+ 5655 1078937 [linearstore] Installation and tests for new store analysis tool qpid-qls-analyze
+ svn r.1596633 2014-05-21: Modified to run from installed location
+ 5767 1098118 [linearstore] broker segfaults when recovering journal file with damaged header
+ svn r.1596509 2014-05-21 Proposed solution (committed by pmoravec)
+ svn r.1599243 2014-06-02 Solution to additional case of file header corruption
+ 5924 1124906 [linearstore] Qpidd Will Not Start with Large Number of Queues
+ svn r.1614665 2014-07-30 Proposed solution
+ 5948 1121660 [AMQP 1.0] Broker restart failure with durable topic using non-durable exchange
+ svn r.1616287 2014-08-06 Proposed solution checked in by gsim
+ This turned out to be an AMQP error, fix does not affect store code.
+ 6043 1089652 [RFE]: Configuration option for linear store to delete or overwrite the used journal files.
+ svn r.1620426 2014-08-25 Proposed solution
+ 6147 1152012 [C++ broker linearstore] missing journal id in "trace Mgmt create journal." log
+ svn r.1631360 2014-10-13 Proposed solution
+ 6157 1150397 linearstore: segfault when 2 journals request new journal file from empty EFP
+ svn r.1632504 2014-10-17 Proposed solution by pmoravec
+ 6230 1165200 [linearstore] qpid-qls-analyze fails when analyzing empty journal
+ svn r.1643053 2014-11-18: Proposed fix
+ 6248 1167911 [linearstore] Symlink creation fails if store dir path is not absolute
+ svn r.1641689 2014-11-25 Proposed solution
+ 5671 1160367 [linearstore] Add ability to use disk partitions and select per-queue EFPs
+ svn r.1636598 2014-11-04: WIP: New EFP and journal dir structure using symlinks
+ svn r.1637985 2014-11-10: WIP: Auto-upgrade from old dir structure to new
+ svn r.1649081 2015-01-02: WIP: Specify new queue using qpid-config --durable together with --efp-partition-num and/or --efp-pool-file-size. Needs testing.
+ - 1148807 [linearstore] Restarting broker with empty journal raises confusing warning
+ Fixed by svn r.1649081 of bug 5671 / 1160367 above
+
+
+Ordered checkin list:
+=====================
+In order to port the linearstore changes from trunk to a branch, the following svn checkins need to be ported in order:
+
+no. svn r Q-JIRA RHBZ Date Alt Committer
+--- ------- ------- -------- ---------- -------------
+ 1. 1545563 5357 1052518 2013-11-26 0.22-mrg
+ 2. 1547601 5358 1052727 2013-12-03 0.22-mrg
+ 3. 1547641 5387 1036071 2013-12-03 0.22-mrg
+ 4. 1547921 5388 1035802 2013-12-04 0.22-mrg
+ 5. 1551304 NO-JIRA - 2013-12-16 0.22-mrg (aconway)
+ 6. 1551361 5425 1052445 2013-12-16 0.22-mrg
+ 7. 1552772 5442 1039949 2013-12-20 0.22-mrg
+ 8. 1553148 5444 1052775 2013-12-23 0.22-mrg
+ 9. 1556888 5362 - 2014-01-09
+10. 1556892 5460 1051097 2014-01-09 0.22-mrg
+11. 1557620 5473 1051924 2014-01-12 0.22-mrg
+12. 1558589 5483 - 2014-01-15 0.22-mrg
+13. 1558592 5484 1035843 2014-01-15 0.22-mrg
+14. 1558913 5487 1054448 2014-01-16 0.22-mrg
+15. 1560530 5362 - 2014-01-22
+16. 1561848 5362 - 2014-01-27
+17. 1564808 5362 - 2014-02-05
+18. 1564877 5480 1053749 2014-02-05 0.22-mrg
+19. 1564893 5361 - 2014-02-05
+20. 1564935 5361 - 2014-02-05
+21. 1574513 5603 1063700 2014-03-05 0.22-mrg
+22. 1575009 5607 1064181 2014-03-06 0.22-mrg
+23. 1578899 5362 - 2014-03-18 parts in 0.22-mrg
+24. 1582730 5651 - 2014-03-28 0.22-mrg (pmoravec)
+25. 1583778 5362 - 2014-04-01
+26. 1584379 5661 - 2014-04-03
+27. 1594215 5750 1078142 2014-05-13 0.22-mrg
+28. 1596509 5767 1098118 2014-05-21 0.22-mrg (pmoravec)
+29. 1596633 NO-JIRA 1078937 2014-05-21 (includes tools install update)
+30. 1599243 5767 1098118 2014-06-02 0.22-mrg
+31. 1599243 5767 1098118 2014-06-02
+32. 1614665 5924 1124906 2014-07-30
+33. 1620426 6043 1089652 2014-08-25
+34. 1631360 6147 1152012 2014-10-13 (pmoravec)
+35. 1632504 6157 1150397 2014-10-17 (pmoravec)
+36. 1636598 5671 1160367 2014-11-04
+37. 1637985 5671 1160367 2014-11-10
+38. 1643053 6230 1165200 2014-11-18
+39. 1641689 6248 1167911 2014-11-25
+40. 1649081 5671 1160367 2015-01-02
+41. 1649082 NO-JIRA - 2015-01-02
+
+See above sections for details on these checkins.
+
+Future work:
+============
+* One journal file lost when queue deleted. All files except for one are recycled back to the EFP.
+* Complete exceptions - several exceptions thrown using jexception have no exception numbers
+* Investigate ability of store to detect missing journal files, especially from logical end of a journal
+* Investigate ability of store to handle file muddle-ups (ie journal files from EFP which are not zeroed or other journals)
+* Look at improving the efficiency of recovery - right now the entire store is read once, and then each recovered record xid and data is read again
+
+Code tidy-up
+------------
+* Remove old comments
+* Use c++ cast templates instead of (xxx)y
+* Member names: xxx_
+* Rename classes, functions and variables to camel-case
+* Add Doxygen docs to classes
+* Make fid's consistent in name (fid, file_id, pfid) and format (hex vs decimal)
diff --git a/qpid/cpp/src/qpid/linearstore/IdDbt.cpp b/qpid/cpp/src/qpid/linearstore/IdDbt.cpp
new file mode 100644
index 0000000000..d427085bbe
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/IdDbt.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/linearstore/IdDbt.h"
+
+using namespace qpid::linearstore;
+
+IdDbt::IdDbt() : id(0)
+{
+ init();
+}
+
+IdDbt::IdDbt(uint64_t _id) : id(_id)
+{
+ init();
+}
+
+void IdDbt::init()
+{
+ set_data(&id);
+ set_size(sizeof(uint64_t));
+ set_ulen(sizeof(uint64_t));
+ set_flags(DB_DBT_USERMEM);
+}
diff --git a/qpid/cpp/src/qpid/linearstore/IdDbt.h b/qpid/cpp/src/qpid/linearstore/IdDbt.h
new file mode 100644
index 0000000000..c7264491ab
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/IdDbt.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_IDDBT_H
+#define QPID_LINEARSTORE_IDDBT_H
+
+#include "db-inc.h"
+
+namespace qpid{
+namespace linearstore{
+
+class IdDbt : public Dbt
+{
+ void init();
+public:
+ uint64_t id;
+
+ IdDbt(uint64_t id);
+ IdDbt();
+};
+
+}}
+
+#endif // ifndef QPID_LINEARSTORE_IDDBT_H
diff --git a/qpid/cpp/src/qpid/linearstore/IdSequence.cpp b/qpid/cpp/src/qpid/linearstore/IdSequence.cpp
new file mode 100644
index 0000000000..4d3172ffe9
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/IdSequence.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/linearstore/IdSequence.h"
+
+using namespace qpid::linearstore;
+using qpid::sys::Mutex;
+
+IdSequence::IdSequence() : id(1) {}
+
+uint64_t IdSequence::next()
+{
+ Mutex::ScopedLock guard(lock);
+ if (!id) id++; // avoid 0 when folding around
+ return id++;
+}
+
+void IdSequence::reset(uint64_t value)
+{
+ //deliberately not threadsafe, used only on recovery
+ id = value;
+}
diff --git a/qpid/cpp/src/qpid/linearstore/IdSequence.h b/qpid/cpp/src/qpid/linearstore/IdSequence.h
new file mode 100644
index 0000000000..17996eec52
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/IdSequence.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_IDSEQUENCE_H
+#define QPID_LINEARSTORE_IDSEQUENCE_H
+
+#include "qpid/framing/amqp_types.h"
+#include "qpid/sys/Mutex.h"
+
+namespace qpid{
+namespace linearstore{
+
+class IdSequence
+{
+ qpid::sys::Mutex lock;
+ uint64_t id;
+public:
+ IdSequence();
+ uint64_t next();
+ void reset(uint64_t value);
+};
+
+}}
+
+#endif // ifndef QPID_LINEARSTORE_IDSEQUENCE_H
diff --git a/qpid/cpp/src/qpid/linearstore/JournalImpl.cpp b/qpid/cpp/src/qpid/linearstore/JournalImpl.cpp
new file mode 100644
index 0000000000..b2d41275a0
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/JournalImpl.cpp
@@ -0,0 +1,516 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/linearstore/JournalImpl.h"
+
+#include "qpid/linearstore/DataTokenImpl.h"
+#include "qpid/linearstore/JournalLogImpl.h"
+#include "qpid/linearstore/journal/jexception.h"
+#include "qpid/linearstore/StoreException.h"
+#include "qpid/management/ManagementAgent.h"
+
+namespace qpid {
+namespace linearstore {
+
+InactivityFireEvent::InactivityFireEvent(JournalImpl* p,
+ const ::qpid::sys::Duration timeout):
+ ::qpid::sys::TimerTask(timeout, "JournalInactive:"+p->id()), _parent(p) {}
+
+void InactivityFireEvent::fire() {
+ ::qpid::sys::Mutex::ScopedLock sl(_ife_lock);
+ if (_parent) {
+ _parent->flushFire();
+ }
+}
+
+GetEventsFireEvent::GetEventsFireEvent(JournalImpl* p,
+ const ::qpid::sys::Duration timeout):
+ ::qpid::sys::TimerTask(timeout, "JournalGetEvents:"+p->id()), _parent(p)
+{}
+
+void GetEventsFireEvent::fire() {
+ ::qpid::sys::Mutex::ScopedLock sl(_gefe_lock);
+ if (_parent) {
+ _parent->getEventsFire();
+ }
+}
+
+JournalImpl::JournalImpl(::qpid::sys::Timer& timer_,
+ const std::string& journalId,
+ const std::string& journalDirectory,
+ JournalLogImpl& journalLogRef,
+ const ::qpid::sys::Duration getEventsTimeout,
+ const ::qpid::sys::Duration flushTimeout,
+ ::qpid::management::ManagementAgent* a,
+ DeleteCallback onDelete):
+ jcntl(journalId, journalDirectory, journalLogRef),
+ timer(timer_),
+ _journalLogRef(journalLogRef),
+ getEventsTimerSetFlag(false),
+ writeActivityFlag(false),
+ flushTriggeredFlag(true),
+ deleteCallback(onDelete)
+{
+ getEventsFireEventsPtr = new GetEventsFireEvent(this, getEventsTimeout);
+ inactivityFireEventPtr = new InactivityFireEvent(this, flushTimeout);
+ {
+ timer.start();
+ timer.add(inactivityFireEventPtr);
+ }
+
+ initManagement(a);
+
+ QLS_LOG2(info, _jid, "Created");
+ std::ostringstream oss;
+ oss << "Journal directory = \"" << journalDirectory << "\"";
+ QLS_LOG2(debug, _jid, oss.str());
+}
+
+JournalImpl::~JournalImpl()
+{
+ if (deleteCallback) deleteCallback(*this);
+ if (_init_flag && !_stop_flag){
+ try { stop(true); } // NOTE: This will *block* until all outstanding disk aio calls are complete!
+ catch (const ::qpid::linearstore::journal::jexception& e) { QLS_LOG2(error, _jid, e.what()); }
+ }
+ getEventsFireEventsPtr->cancel();
+ inactivityFireEventPtr->cancel();
+
+ if (_mgmtObject.get() != 0) {
+ _mgmtObject->resourceDestroy();
+ _mgmtObject.reset();
+ }
+
+ QLS_LOG2(info, _jid, "Destroyed");
+}
+
+void
+JournalImpl::initManagement(::qpid::management::ManagementAgent* a)
+{
+ _agent = a;
+ if (_agent != 0)
+ {
+ _mgmtObject = ::qmf::org::apache::qpid::linearstore::Journal::shared_ptr (
+ new ::qmf::org::apache::qpid::linearstore::Journal(_agent, this, _jid));
+
+ _mgmtObject->set_directory(_jdir.dirname());
+// _mgmtObject->set_baseFileName(_base_filename);
+// _mgmtObject->set_readPageSize(JRNL_RMGR_PAGE_SIZE * JRNL_SBLK_SIZE);
+// _mgmtObject->set_readPages(JRNL_RMGR_PAGES);
+
+ // The following will be set on initialize(), but being properties, these must be set to 0 in the meantime
+ //_mgmtObject->set_initialFileCount(0);
+ //_mgmtObject->set_dataFileSize(0);
+ //_mgmtObject->set_currentFileCount(0);
+ _mgmtObject->set_writePageSize(0);
+ _mgmtObject->set_writePages(0);
+
+ _agent->addObject(_mgmtObject, 0, true);
+ }
+}
+
+
+void
+JournalImpl::initialize(::qpid::linearstore::journal::EmptyFilePool* efpp_,
+ const uint16_t wcache_num_pages,
+ const uint32_t wcache_pgsize_sblks,
+ ::qpid::linearstore::journal::aio_callback* const cbp)
+{
+// efpp->createJournal(_jdir);
+// QLS_LOG2(info, _jid, "Initialized");
+// std::ostringstream oss;
+//// oss << "Initialize; num_jfiles=" << num_jfiles << " jfsize_sblks=" << jfsize_sblks;
+// oss << "Initialize; efpPartitionNumber=" << efpp_->getPartitionNumber();
+// oss << " efpFileSizeKb=" << efpp_->fileSizeKib();
+// oss << " wcache_pgsize_sblks=" << wcache_pgsize_sblks;
+// oss << " wcache_num_pages=" << wcache_num_pages;
+// QLS_LOG2(debug, _jid, oss.str());
+ jcntl::initialize(efpp_, wcache_num_pages, wcache_pgsize_sblks, cbp);
+// QLS_LOG2(debug, _jid, "Initialization complete");
+ // TODO: replace for linearstore: _lpmgr
+/*
+ if (_mgmtObject.get() != 0)
+ {
+ _mgmtObject->set_initialFileCount(_lpmgr.num_jfiles());
+ _mgmtObject->set_autoExpand(_lpmgr.is_ae());
+ _mgmtObject->set_currentFileCount(_lpmgr.num_jfiles());
+ _mgmtObject->set_maxFileCount(_lpmgr.ae_max_jfiles());
+ _mgmtObject->set_dataFileSize(_jfsize_sblks * JRNL_SBLK_SIZE);
+ _mgmtObject->set_writePageSize(wcache_pgsize_sblks * JRNL_SBLK_SIZE);
+ _mgmtObject->set_writePages(wcache_num_pages);
+ }
+ if (_agent != 0)
+ _agent->raiseEvent::(qmf::org::apache::qpid::linearstore::EventCreated(_jid, _jfsize_sblks * JRNL_SBLK_SIZE, _lpmgr.num_jfiles()),
+ qpid::management::ManagementAgent::SEV_NOTE);
+*/
+}
+
+void
+JournalImpl::recover(boost::shared_ptr< ::qpid::linearstore::journal::EmptyFilePoolManager> efpm,
+ const uint16_t wcache_num_pages,
+ const uint32_t wcache_pgsize_sblks,
+ ::qpid::linearstore::journal::aio_callback* const cbp,
+ boost::ptr_list<PreparedTransaction>* prep_tx_list_ptr,
+ uint64_t& highest_rid,
+ uint64_t queue_id)
+{
+ std::ostringstream oss1;
+ oss1 << "Recover;";
+ oss1 << " queue_id = 0x" << std::hex << queue_id << std::dec;
+ oss1 << " wcache_pgsize_sblks=" << wcache_pgsize_sblks;
+ oss1 << " wcache_num_pages=" << wcache_num_pages;
+ QLS_LOG2(debug, _jid, oss1.str());
+ // TODO: replace for linearstore: _lpmgr
+/*
+ if (_mgmtObject.get() != 0)
+ {
+ _mgmtObject->set_initialFileCount(_lpmgr.num_jfiles());
+ _mgmtObject->set_autoExpand(_lpmgr.is_ae());
+ _mgmtObject->set_currentFileCount(_lpmgr.num_jfiles());
+ _mgmtObject->set_maxFileCount(_lpmgr.ae_max_jfiles());
+ _mgmtObject->set_dataFileSize(_jfsize_sblks * JRNL_SBLK_SIZE);
+ _mgmtObject->set_writePageSize(wcache_pgsize_sblks * JRNL_SBLK_SIZE);
+ _mgmtObject->set_writePages(wcache_num_pages);
+ }
+*/
+
+ // TODO: This is ugly, find a way for RecoveryManager to use boost::ptr_list<PreparedTransaction>* directly
+ if (prep_tx_list_ptr) {
+ // Create list of prepared xids
+ std::vector<std::string> prep_xid_list;
+ for (PreparedTransaction::list::iterator i = prep_tx_list_ptr->begin(); i != prep_tx_list_ptr->end(); i++) {
+ prep_xid_list.push_back(i->xid);
+ }
+
+ jcntl::recover(efpm.get(), wcache_num_pages, wcache_pgsize_sblks, cbp, &prep_xid_list, highest_rid);
+ } else {
+ jcntl::recover(efpm.get(), wcache_num_pages, wcache_pgsize_sblks, cbp, 0, highest_rid);
+ }
+
+ // Populate PreparedTransaction lists from _tmap
+ if (prep_tx_list_ptr)
+ {
+ for (PreparedTransaction::list::iterator i = prep_tx_list_ptr->begin(); i != prep_tx_list_ptr->end(); i++) {
+ ::qpid::linearstore::journal::txn_data_list_t tdl = _tmap.get_tdata_list(i->xid); // tdl will be empty if xid not found
+ for (::qpid::linearstore::journal::tdl_itr_t tdl_itr = tdl.begin(); tdl_itr < tdl.end(); tdl_itr++) {
+ if (tdl_itr->enq_flag_) { // enqueue op
+ i->enqueues->add(queue_id, tdl_itr->rid_);
+ } else { // dequeue op
+ i->dequeues->add(queue_id, tdl_itr->drid_);
+ }
+ }
+ }
+ }
+ std::ostringstream oss2;
+ oss2 << "Recover phase 1 complete; highest rid found = 0x" << std::hex << highest_rid;
+ oss2 << std::dec << "; emap.size=" << _emap.size() << "; tmap.size=" << _tmap.size();
+ oss2 << "; journal now read-only.";
+ QLS_LOG2(debug, _jid, oss2.str());
+
+ if (_mgmtObject.get() != 0)
+ {
+ _mgmtObject->inc_recordDepth(_emap.size());
+ _mgmtObject->inc_enqueues(_emap.size());
+ _mgmtObject->inc_txn(_tmap.size());
+ _mgmtObject->inc_txnEnqueues(_tmap.enq_cnt());
+ _mgmtObject->inc_txnDequeues(_tmap.deq_cnt());
+ }
+}
+
+void
+JournalImpl::recover_complete()
+{
+ jcntl::recover_complete();
+ QLS_LOG2(debug, _jid, "Recover phase 2 complete; journal now writable.");
+ // TODO: replace for linearstore: _lpmgr
+/*
+ if (_agent != 0)
+ _agent->raiseEvent(qmf::org::apache::qpid::linearstore::EventRecovered(_jid, _jfsize_sblks * JRNL_SBLK_SIZE, _lpmgr.num_jfiles(),
+ _emap.size(), _tmap.size(), _tmap.enq_cnt(), _tmap.deq_cnt()), qpid::management::ManagementAgent::SEV_NOTE);
+*/
+}
+
+
+void
+JournalImpl::enqueue_data_record(const void* const data_buff,
+ const size_t tot_data_len,
+ const size_t this_data_len,
+ ::qpid::linearstore::journal::data_tok* dtokp,
+ const bool transient)
+{
+ handleIoResult(jcntl::enqueue_data_record(data_buff, tot_data_len, this_data_len, dtokp, transient));
+
+ if (_mgmtObject.get() != 0)
+ {
+ _mgmtObject->inc_enqueues();
+ _mgmtObject->inc_recordDepth();
+ }
+}
+
+void
+JournalImpl::enqueue_extern_data_record(const size_t tot_data_len,
+ ::qpid::linearstore::journal::data_tok* dtokp,
+ const bool transient)
+{
+ handleIoResult(jcntl::enqueue_extern_data_record(tot_data_len, dtokp, transient));
+
+ if (_mgmtObject.get() != 0)
+ {
+ _mgmtObject->inc_enqueues();
+ _mgmtObject->inc_recordDepth();
+ }
+}
+
+void
+JournalImpl::enqueue_txn_data_record(const void* const data_buff,
+ const size_t tot_data_len,
+ const size_t this_data_len,
+ ::qpid::linearstore::journal::data_tok* dtokp,
+ const std::string& xid,
+ const bool tpc_flag,
+ const bool transient)
+{
+ bool txn_incr = _mgmtObject.get() != 0 ? _tmap.in_map(xid) : false;
+
+ handleIoResult(jcntl::enqueue_txn_data_record(data_buff, tot_data_len, this_data_len, dtokp, xid, tpc_flag, transient));
+
+ if (_mgmtObject.get() != 0)
+ {
+ if (!txn_incr) // If this xid was not in _tmap, it will be now...
+ _mgmtObject->inc_txn();
+ _mgmtObject->inc_enqueues();
+ _mgmtObject->inc_txnEnqueues();
+ _mgmtObject->inc_recordDepth();
+ }
+}
+
+void
+JournalImpl::enqueue_extern_txn_data_record(const size_t tot_data_len,
+ ::qpid::linearstore::journal::data_tok* dtokp,
+ const std::string& xid,
+ const bool tpc_flag,
+ const bool transient)
+{
+ bool txn_incr = _mgmtObject.get() != 0 ? _tmap.in_map(xid) : false;
+
+ handleIoResult(jcntl::enqueue_extern_txn_data_record(tot_data_len, dtokp, xid, tpc_flag, transient));
+
+ if (_mgmtObject.get() != 0)
+ {
+ if (!txn_incr) // If this xid was not in _tmap, it will be now...
+ _mgmtObject->inc_txn();
+ _mgmtObject->inc_enqueues();
+ _mgmtObject->inc_txnEnqueues();
+ _mgmtObject->inc_recordDepth();
+ }
+}
+
+void
+JournalImpl::dequeue_data_record(::qpid::linearstore::journal::data_tok* const dtokp,
+ const bool txn_coml_commit)
+{
+ handleIoResult(jcntl::dequeue_data_record(dtokp, txn_coml_commit));
+
+ if (_mgmtObject.get() != 0)
+ {
+ _mgmtObject->inc_dequeues();
+ _mgmtObject->inc_txnDequeues();
+ _mgmtObject->dec_recordDepth();
+ }
+}
+
+void
+JournalImpl::dequeue_txn_data_record(::qpid::linearstore::journal::data_tok* const dtokp,
+ const std::string& xid,
+ const bool tpc_flag,
+ const bool txn_coml_commit)
+{
+ bool txn_incr = _mgmtObject.get() != 0 ? _tmap.in_map(xid) : false;
+
+ handleIoResult(jcntl::dequeue_txn_data_record(dtokp, xid, tpc_flag, txn_coml_commit));
+
+ if (_mgmtObject.get() != 0)
+ {
+ if (!txn_incr) // If this xid was not in _tmap, it will be now...
+ _mgmtObject->inc_txn();
+ _mgmtObject->inc_dequeues();
+ _mgmtObject->inc_txnDequeues();
+ _mgmtObject->dec_recordDepth();
+ }
+}
+
+void
+JournalImpl::txn_abort(::qpid::linearstore::journal::data_tok* const dtokp,
+ const std::string& xid)
+{
+ handleIoResult(jcntl::txn_abort(dtokp, xid));
+
+ if (_mgmtObject.get() != 0)
+ {
+ _mgmtObject->dec_txn();
+ _mgmtObject->inc_txnAborts();
+ }
+}
+
+void
+JournalImpl::txn_commit(::qpid::linearstore::journal::data_tok* const dtokp,
+ const std::string& xid)
+{
+ handleIoResult(jcntl::txn_commit(dtokp, xid));
+
+ if (_mgmtObject.get() != 0)
+ {
+ _mgmtObject->dec_txn();
+ _mgmtObject->inc_txnCommits();
+ }
+}
+
+void
+JournalImpl::stop(bool block_till_aio_cmpl)
+{
+ InactivityFireEvent* ifep = dynamic_cast<InactivityFireEvent*>(inactivityFireEventPtr.get());
+ assert(ifep); // dynamic_cast can return null if the cast fails
+ ifep->cancel();
+ jcntl::stop(block_till_aio_cmpl);
+
+ if (_mgmtObject.get() != 0) {
+ _mgmtObject->resourceDestroy();
+ _mgmtObject.reset();
+ }
+}
+
+::qpid::linearstore::journal::iores
+JournalImpl::flush(const bool block_till_aio_cmpl)
+{
+ const ::qpid::linearstore::journal::iores res = jcntl::flush(block_till_aio_cmpl);
+ {
+ ::qpid::sys::Mutex::ScopedLock sl(_getf_lock);
+ if (_wmgr.get_aio_evt_rem() && !getEventsTimerSetFlag) { setGetEventTimer(); }
+ }
+ return res;
+}
+
+void
+JournalImpl::getEventsFire()
+{
+ ::qpid::sys::Mutex::ScopedLock sl(_getf_lock);
+ getEventsTimerSetFlag = false;
+ if (_wmgr.get_aio_evt_rem()) { jcntl::get_wr_events(0); }
+ if (_wmgr.get_aio_evt_rem()) { setGetEventTimer(); }
+}
+
+void
+JournalImpl::flushFire()
+{
+ if (writeActivityFlag) {
+ writeActivityFlag = false;
+ flushTriggeredFlag = false;
+ } else {
+ if (!flushTriggeredFlag) {
+ flush(false);
+ flushTriggeredFlag = true;
+ }
+ }
+ inactivityFireEventPtr->setupNextFire();
+ {
+ timer.add(inactivityFireEventPtr);
+ }
+}
+
+void
+JournalImpl::wr_aio_cb(std::vector< ::qpid::linearstore::journal::data_tok*>& dtokl)
+{
+ for (std::vector< ::qpid::linearstore::journal::data_tok*>::const_iterator i=dtokl.begin(); i!=dtokl.end(); i++)
+ {
+ DataTokenImpl* dtokp = static_cast<DataTokenImpl*>(*i);
+ if (/*!is_stopped() &&*/ dtokp->getSourceMessage())
+ {
+ switch (dtokp->wstate())
+ {
+ case ::qpid::linearstore::journal::data_tok::ENQ:
+//std::cout << "<<<>>> JournalImpl::wr_aio_cb() ENQ dtokp rid=0x" << std::hex << dtokp->rid() << std::dec << std::endl << std::flush; // DEBUG
+ dtokp->getSourceMessage()->enqueueComplete();
+ break;
+ case ::qpid::linearstore::journal::data_tok::DEQ:
+//std::cout << "<<<>>> JournalImpl::wr_aio_cb() DEQ dtokp rid=0x" << std::hex << dtokp->rid() << std::dec << std::endl << std::flush; // DEBUG
+/* Don't need to signal until we have a way to ack completion of dequeue in AMQP
+ dtokp->getSourceMessage()->dequeueComplete();
+ if ( dtokp->getSourceMessage()->isDequeueComplete() ) // clear id after last dequeue
+ dtokp->getSourceMessage()->setPersistenceId(0);
+*/
+ break;
+ default: ;
+ }
+ }
+ dtokp->release();
+ }
+}
+
+void
+JournalImpl::rd_aio_cb(std::vector<uint16_t>& /*pil*/)
+{}
+
+void
+JournalImpl::createStore() {
+
+}
+
+void
+JournalImpl::handleIoResult(const ::qpid::linearstore::journal::iores r)
+{
+ writeActivityFlag = true;
+ switch (r)
+ {
+ case ::qpid::linearstore::journal::RHM_IORES_SUCCESS:
+ return;
+ default:
+ {
+ std::ostringstream oss;
+ oss << "Unexpected I/O response (" << ::qpid::linearstore::journal::iores_str(r) << ").";
+ QLS_LOG2(error, _jid, oss.str());
+ THROW_STORE_FULL_EXCEPTION(oss.str());
+ }
+ }
+}
+
+::qpid::management::Manageable::status_t JournalImpl::ManagementMethod (uint32_t /*methodId*/,
+ ::qpid::management::Args& /*args*/,
+ std::string& /*text*/)
+{
+ Manageable::status_t status = Manageable::STATUS_UNKNOWN_METHOD;
+
+/*
+ switch (methodId)
+ {
+ case _qmf::Journal::METHOD_EXPAND :
+ //_qmf::ArgsJournalExpand& eArgs = (_qmf::ArgsJournalExpand&) args;
+
+ // Implement "expand" using eArgs.i_by (expand-by argument)
+
+ status = Manageable::STATUS_NOT_IMPLEMENTED;
+ break;
+ }
+*/
+
+ return status;
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/linearstore/JournalImpl.h b/qpid/cpp/src/qpid/linearstore/JournalImpl.h
new file mode 100644
index 0000000000..667579253e
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/JournalImpl.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNALIMPL_H
+#define QPID_LINEARSTORE_JOURNALIMPL_H
+
+#include <boost/ptr_container/ptr_list.hpp>
+#include "qpid/broker/PersistableQueue.h"
+#include "qpid/linearstore/journal/aio_callback.h"
+#include "qpid/linearstore/journal/jcntl.h"
+#include "qpid/linearstore/PreparedTransaction.h"
+#include "qpid/sys/Timer.h"
+
+#include "qmf/org/apache/qpid/linearstore/Journal.h"
+
+namespace qpid{
+
+namespace sys {
+//class Timer;
+}
+
+namespace linearstore{
+namespace journal {
+// class EmptyFilePool;
+}
+class JournalImpl;
+class JournalLogImpl;
+
+class InactivityFireEvent : public ::qpid::sys::TimerTask
+{
+ JournalImpl* _parent;
+ ::qpid::sys::Mutex _ife_lock;
+
+ public:
+ InactivityFireEvent(JournalImpl* p,
+ const ::qpid::sys::Duration timeout);
+ virtual ~InactivityFireEvent() {}
+ void fire();
+ inline void cancel() { ::qpid::sys::Mutex::ScopedLock sl(_ife_lock); _parent = 0; }
+};
+
+class GetEventsFireEvent : public ::qpid::sys::TimerTask
+{
+ JournalImpl* _parent;
+ ::qpid::sys::Mutex _gefe_lock;
+
+ public:
+ GetEventsFireEvent(JournalImpl* p,
+ const ::qpid::sys::Duration timeout);
+ virtual ~GetEventsFireEvent() {}
+ void fire();
+ inline void cancel() { ::qpid::sys::Mutex::ScopedLock sl(_gefe_lock); _parent = 0; }
+};
+
+class JournalImpl : public ::qpid::broker::ExternalQueueStore,
+ public ::qpid::linearstore::journal::jcntl,
+ public ::qpid::linearstore::journal::aio_callback
+{
+ public:
+ typedef boost::function<void (JournalImpl&)> DeleteCallback;
+
+ protected:
+ ::qpid::sys::Timer& timer;
+ JournalLogImpl& _journalLogRef;
+ bool getEventsTimerSetFlag;
+ boost::intrusive_ptr< ::qpid::sys::TimerTask> getEventsFireEventsPtr;
+ ::qpid::sys::Mutex _getf_lock;
+ ::qpid::sys::Mutex _read_lock;
+
+ bool writeActivityFlag;
+ bool flushTriggeredFlag;
+ boost::intrusive_ptr< ::qpid::sys::TimerTask> inactivityFireEventPtr;
+
+ ::qpid::management::ManagementAgent* _agent;
+ ::qmf::org::apache::qpid::linearstore::Journal::shared_ptr _mgmtObject;
+ DeleteCallback deleteCallback;
+
+ public:
+
+ JournalImpl(::qpid::sys::Timer& timer,
+ const std::string& journalId,
+ const std::string& journalDirectory,
+ JournalLogImpl& journalLogRef,
+ const ::qpid::sys::Duration getEventsTimeout,
+ const ::qpid::sys::Duration flushTimeout,
+ ::qpid::management::ManagementAgent* agent,
+ DeleteCallback deleteCallback=DeleteCallback() );
+
+ virtual ~JournalImpl();
+
+ void initManagement(::qpid::management::ManagementAgent* agent);
+
+ void initialize(::qpid::linearstore::journal::EmptyFilePool* efp,
+ const uint16_t wcache_num_pages,
+ const uint32_t wcache_pgsize_sblks,
+ ::qpid::linearstore::journal::aio_callback* const cbp);
+
+ inline void initialize(::qpid::linearstore::journal::EmptyFilePool* efpp,
+ const uint16_t wcache_num_pages,
+ const uint32_t wcache_pgsize_sblks) {
+ initialize(efpp, wcache_num_pages, wcache_pgsize_sblks, this);
+ }
+
+ void recover(boost::shared_ptr< ::qpid::linearstore::journal::EmptyFilePoolManager> efpm,
+ const uint16_t wcache_num_pages,
+ const uint32_t wcache_pgsize_sblks,
+ ::qpid::linearstore::journal::aio_callback* const cbp,
+ boost::ptr_list<PreparedTransaction>* prep_tx_list_ptr,
+ uint64_t& highest_rid,
+ uint64_t queue_id);
+
+ inline void recover(boost::shared_ptr< ::qpid::linearstore::journal::EmptyFilePoolManager> efpm,
+ const uint16_t wcache_num_pages,
+ const uint32_t wcache_pgsize_sblks,
+ boost::ptr_list<PreparedTransaction>* prep_tx_list_ptr,
+ uint64_t& highest_rid,
+ uint64_t queue_id) {
+ recover(efpm, wcache_num_pages, wcache_pgsize_sblks, this, prep_tx_list_ptr, highest_rid, queue_id);
+ }
+
+ void recover_complete();
+
+ // Overrides for write inactivity timer
+ void enqueue_data_record(const void* const data_buff,
+ const size_t tot_data_len,
+ const size_t this_data_len,
+ ::qpid::linearstore::journal::data_tok* dtokp,
+ const bool transient);
+
+ void enqueue_extern_data_record(const size_t tot_data_len,
+ ::qpid::linearstore::journal::data_tok* dtokp,
+ const bool transient);
+
+ void enqueue_txn_data_record(const void* const data_buff,
+ const size_t tot_data_len,
+ const size_t this_data_len,
+ ::qpid::linearstore::journal::data_tok* dtokp,
+ const std::string& xid,
+ const bool tpc_flag,
+ const bool transient);
+
+ void enqueue_extern_txn_data_record(const size_t tot_data_len,
+ ::qpid::linearstore::journal::data_tok* dtokp,
+ const std::string& xid,
+ const bool tpc_flag,
+ const bool transient);
+
+ void dequeue_data_record(::qpid::linearstore::journal::data_tok*
+ const dtokp,
+ const bool txn_coml_commit);
+
+ void dequeue_txn_data_record(::qpid::linearstore::journal::data_tok* const dtokp,
+ const std::string& xid,
+ const bool tpc_flag,
+ const bool txn_coml_commit);
+
+ void txn_abort(::qpid::linearstore::journal::data_tok* const dtokp, const std::string& xid);
+
+ void txn_commit(::qpid::linearstore::journal::data_tok* const dtokp, const std::string& xid);
+
+ void stop(bool block_till_aio_cmpl = false);
+
+ // Overrides for get_events timer
+ ::qpid::linearstore::journal::iores flush(const bool block_till_aio_cmpl);
+
+ // TimerTask callback
+ void getEventsFire();
+ void flushFire();
+
+ // AIO callbacks
+ virtual void wr_aio_cb(std::vector< ::qpid::linearstore::journal::data_tok*>& dtokl);
+ virtual void rd_aio_cb(std::vector<uint16_t>& pil);
+
+ ::qpid::management::ManagementObject::shared_ptr GetManagementObject (void) const
+ { return _mgmtObject; }
+
+ ::qpid::management::Manageable::status_t ManagementMethod(uint32_t,
+ ::qpid::management::Args&,
+ std::string&);
+
+ void resetDeleteCallback() { deleteCallback = DeleteCallback(); }
+
+ protected:
+ void createStore();
+
+ inline void setGetEventTimer()
+ {
+ getEventsFireEventsPtr->setupNextFire();
+ timer.add(getEventsFireEventsPtr);
+ getEventsTimerSetFlag = true;
+ }
+ void handleIoResult(const ::qpid::linearstore::journal::iores r);
+
+ // Management instrumentation callbacks overridden from jcntl
+ inline void instr_incr_outstanding_aio_cnt() {
+ if (_mgmtObject.get() != 0) _mgmtObject->inc_outstandingAIOs();
+ }
+ inline void instr_decr_outstanding_aio_cnt() {
+ if (_mgmtObject.get() != 0) _mgmtObject->dec_outstandingAIOs();
+ }
+
+}; // class JournalImpl
+
+class TplJournalImpl : public JournalImpl
+{
+ public:
+ TplJournalImpl(::qpid::sys::Timer& timer,
+ const std::string& journalId,
+ const std::string& journalDirectory,
+ JournalLogImpl& journalLogRef,
+ const ::qpid::sys::Duration getEventsTimeout,
+ const ::qpid::sys::Duration flushTimeout,
+ ::qpid::management::ManagementAgent* agent) :
+ JournalImpl(timer, journalId, journalDirectory, journalLogRef, getEventsTimeout, flushTimeout, agent)
+ {}
+
+ virtual ~TplJournalImpl() {}
+
+ // Special version of read_data_record that ignores transactions - needed when reading the TPL
+ inline ::qpid::linearstore::journal::iores read_data_record(void** const datapp,
+ std::size_t& dsize,
+ void** const xidpp,
+ std::size_t& xidsize,
+ bool& transient,
+ bool& external,
+ ::qpid::linearstore::journal::data_tok* const dtokp) {
+ return JournalImpl::read_data_record(datapp, dsize, xidpp, xidsize, transient, external, dtokp, false);
+ }
+}; // class TplJournalImpl
+
+} // namespace msgstore
+} // namespace mrg
+
+#endif // ifndef QPID_LINEARSTORE_JOURNALIMPL_H
diff --git a/qpid/cpp/src/qpid/linearstore/JournalLogImpl.cpp b/qpid/cpp/src/qpid/linearstore/JournalLogImpl.cpp
new file mode 100644
index 0000000000..c3e631a6ca
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/JournalLogImpl.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/linearstore/JournalLogImpl.h"
+
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace linearstore {
+
+JournalLogImpl::JournalLogImpl(const qpid::linearstore::journal::JournalLog::log_level_t logLevelThreshold) : qpid::linearstore::journal::JournalLog(logLevelThreshold) {}
+JournalLogImpl::~JournalLogImpl() {}
+
+void
+JournalLogImpl::log(const qpid::linearstore::journal::JournalLog::log_level_t level,
+ const std::string& log_stmt) const {
+ switch (level) {
+ case LOG_CRITICAL: QPID_LOG(critical, "Linear Store: " << log_stmt); break;
+ case LOG_ERROR: QPID_LOG(error, "Linear Store: " << log_stmt); break;
+ case LOG_WARN: QPID_LOG(warning, "Linear Store: " << log_stmt); break;
+ case LOG_NOTICE: QPID_LOG(notice, "Linear Store: " << log_stmt); break;
+ case LOG_INFO: QPID_LOG(info, "Linear Store: " << log_stmt); break;
+ case LOG_DEBUG: QPID_LOG(debug, "Linear Store: " << log_stmt); break;
+ default: QPID_LOG(trace, "Linear Store: " << log_stmt);
+ }
+}
+
+void
+JournalLogImpl::log(const qpid::linearstore::journal::JournalLog::log_level_t level,
+ const std::string& jid,
+ const std::string& log_stmt) const {
+ switch (level) {
+ case LOG_CRITICAL: QPID_LOG(critical, "Linear Store: Journal \"" << jid << "\": " << log_stmt); break;
+ case LOG_ERROR: QPID_LOG(error, "Linear Store: Journal \"" << jid << "\": " << log_stmt); break;
+ case LOG_WARN: QPID_LOG(warning, "Linear Store: Journal \"" << jid << "\": " << log_stmt); break;
+ case LOG_NOTICE: QPID_LOG(notice, "Linear Store: Journal \"" << jid << "\": " << log_stmt); break;
+ case LOG_INFO: QPID_LOG(info, "Linear Store: Journal \"" << jid << "\": " << log_stmt); break;
+ case LOG_DEBUG: QPID_LOG(debug, "Linear Store: Journal \"" << jid << "\": " << log_stmt); break;
+ default: QPID_LOG(trace, "Linear Store: Journal \"" << jid << "\": " << log_stmt);
+ }
+}
+
+}} // namespace qpid::linearstore
diff --git a/qpid/cpp/src/qpid/linearstore/JournalLogImpl.h b/qpid/cpp/src/qpid/linearstore/JournalLogImpl.h
new file mode 100644
index 0000000000..846eaac124
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/JournalLogImpl.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_LOG_H
+#define QPID_LINEARSTORE_LOG_H
+
+#include "qpid/linearstore/journal/JournalLog.h"
+
+#define QLS_LOG(level, msg) QPID_LOG(level, "Linear Store: " << msg)
+#define QLS_LOG2(level, queue, msg) QPID_LOG(level, "Linear Store: Journal \"" << queue << "\":" << msg)
+
+namespace qpid {
+namespace linearstore {
+
+class JournalLogImpl : public qpid::linearstore::journal::JournalLog
+{
+public:
+ JournalLogImpl(const qpid::linearstore::journal::JournalLog::log_level_t logLevelThreshold);
+ virtual ~JournalLogImpl();
+ virtual void log(const qpid::linearstore::journal::JournalLog::log_level_t logLevel,
+ const std::string& logStatement) const;
+ virtual void log(const qpid::linearstore::journal::JournalLog::log_level_t logLevel,
+ const std::string& journalId,
+ const std::string& logStatement) const;
+};
+
+}} // namespace qpid::linearstore
+
+#endif // QPID_LINEARSTORE_LOG_H
diff --git a/qpid/cpp/src/qpid/linearstore/MessageStoreImpl.cpp b/qpid/cpp/src/qpid/linearstore/MessageStoreImpl.cpp
new file mode 100644
index 0000000000..70eac27f48
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/MessageStoreImpl.cpp
@@ -0,0 +1,1559 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/linearstore/MessageStoreImpl.h"
+
+#include "qpid/broker/Broker.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/linearstore/BindingDbt.h"
+#include "qpid/linearstore/BufferValue.h"
+#include "qpid/linearstore/Cursor.h"
+#include "qpid/linearstore/DataTokenImpl.h"
+#include "qpid/linearstore/IdDbt.h"
+#include "qpid/linearstore/JournalImpl.h"
+#include "qpid/linearstore/journal/EmptyFilePoolManager.h"
+#include "qpid/linearstore/StoreException.h"
+#include "qpid/linearstore/TxnCtxt.h"
+#include "qpid/log/Statement.h"
+
+#include "qmf/org/apache/qpid/linearstore/Package.h"
+
+#define MAX_AIO_SLEEPS 100000 // tot: ~1 sec
+#define AIO_SLEEP_TIME_US 10 // 0.01 ms
+
+//namespace _qmf = qmf::org::apache::qpid::linearstore;
+
+namespace qpid{
+namespace linearstore{
+
+const std::string MessageStoreImpl::storeTopLevelDir("qls"); // Sets the top-level store dir name
+
+// FIXME aconway 2010-03-09: was 10
+qpid::sys::Duration MessageStoreImpl::defJournalGetEventsTimeout(1 * qpid::sys::TIME_MSEC); // 10ms
+qpid::sys::Duration MessageStoreImpl::defJournalFlushTimeout(500 * qpid::sys::TIME_MSEC); // 0.5s
+qpid::sys::Mutex TxnCtxt::globalSerialiser;
+
+MessageStoreImpl::MessageStoreImpl(qpid::broker::Broker* broker_, const char* envpath_) :
+ defaultEfpPartitionNumber(0),
+ defaultEfpFileSize_kib(0),
+ overwriteBeforeReturnFlag(false),
+ wCachePgSizeSblks(0),
+ wCacheNumPages(0),
+ tplWCachePgSizeSblks(0),
+ tplWCacheNumPages(0),
+ highestRid(0),
+ isInit(false),
+ envPath(envpath_),
+ broker(broker_),
+ jrnlLog(qpid::linearstore::journal::JournalLog::LOG_NOTICE),
+ mgmtObject(),
+ agent(0)
+{
+ // Test of values for QLS_RAND_SHIFT1, QLS_RAND_SHIFT2 and QLS_RAND_MASK
+ if((((uint64_t)RAND_MAX << QLS_RAND_SHIFT1) ^ ((uint64_t)RAND_MAX << QLS_RAND_SHIFT2) ^ (RAND_MAX & QLS_RAND_MASK)) != 0xffffffffffffffffULL) {
+ THROW_STORE_EXCEPTION("[linearstore] 64-bit random number generation alignment error");
+ }
+ ::srand(::time(NULL));
+}
+
+uint32_t MessageStoreImpl::chkJrnlWrPageCacheSize(const uint32_t param_, const std::string& paramName_)
+{
+ uint32_t p = param_;
+
+ if (p == 0) {
+ // For zero value, use default
+ p = QLS_WMGR_DEF_PAGE_SIZE_KIB;
+ QLS_LOG(warning, "parameter " << paramName_ << " (" << param_ << ") must be a power of 2 between 1 and 128; changing this parameter to default value (" << p << ")");
+ } else if ( p > 128 || (p & (p-1)) ) {
+ // For any positive value that is not a power of 2, use closest value
+ if (p < 6) p = 4;
+ else if (p < 12) p = 8;
+ else if (p < 24) p = 16;
+ else if (p < 48) p = 32;
+ else if (p < 96) p = 64;
+ else p = 128;
+ QLS_LOG(warning, "parameter " << paramName_ << " (" << param_ << ") must be a power of 2 between 1 and 128; changing this parameter to closest allowable value (" << p << ")");
+ }
+ return p;
+}
+
+uint16_t MessageStoreImpl::getJrnlWrNumPages(const uint32_t wrPageSizeKib_)
+{
+ uint32_t wrPageSizeSblks = wrPageSizeKib_ / QLS_SBLK_SIZE_KIB; // convert from KiB to number sblks
+ uint32_t defTotWCacheSizeSblks = QLS_WMGR_DEF_PAGE_SIZE_SBLKS * QLS_WMGR_DEF_PAGES;
+ switch (wrPageSizeKib_)
+ {
+ case 1:
+ case 2:
+ case 4:
+ // 256 KiB total cache
+ return defTotWCacheSizeSblks / wrPageSizeSblks / 4;
+ case 8:
+ case 16:
+ // 512 KiB total cache
+ return defTotWCacheSizeSblks / wrPageSizeSblks / 2;
+ default: // 32, 64, 128
+ // 1 MiB total cache
+ return defTotWCacheSizeSblks / wrPageSizeSblks;
+ }
+}
+
+qpid::linearstore::journal::efpPartitionNumber_t MessageStoreImpl::chkEfpPartition(const qpid::linearstore::journal::efpPartitionNumber_t partition_,
+ const std::string& /*paramName_*/) {
+ // TODO: check against list of existing partitions, throw if not found
+ return partition_;
+}
+
+qpid::linearstore::journal::efpDataSize_kib_t MessageStoreImpl::chkEfpFileSizeKiB(const qpid::linearstore::journal::efpDataSize_kib_t efpFileSizeKib_,
+ const std::string& paramName_) {
+ uint8_t rem = efpFileSizeKib_ % uint64_t(QLS_SBLK_SIZE_KIB);
+ if (rem != 0) {
+ uint64_t newVal = efpFileSizeKib_ - rem;
+ if (rem >= (QLS_SBLK_SIZE_KIB / 2))
+ newVal += QLS_SBLK_SIZE_KIB;
+ QLS_LOG(warning, "Parameter " << paramName_ << " (" << efpFileSizeKib_ << ") must be a multiple of " <<
+ QLS_SBLK_SIZE_KIB << "; changing this parameter to the closest allowable value (" <<
+ newVal << ")");
+ return newVal;
+ }
+ return efpFileSizeKib_;
+
+ // TODO: check against list of existing pools in the given partition
+}
+
+void MessageStoreImpl::initManagement ()
+{
+ if (broker != 0) {
+ agent = broker->getManagementAgent();
+ if (agent != 0) {
+ qmf::org::apache::qpid::linearstore::Package packageInitializer(agent);
+ mgmtObject = qmf::org::apache::qpid::linearstore::Store::shared_ptr (
+ new qmf::org::apache::qpid::linearstore::Store(agent, this, broker));
+
+ mgmtObject->set_storeDir(storeDir);
+ mgmtObject->set_tplIsInitialized(false);
+ mgmtObject->set_tplDirectory(getTplBaseDir());
+ mgmtObject->set_tplWritePageSize(tplWCachePgSizeSblks * QLS_SBLK_SIZE_BYTES);
+ mgmtObject->set_tplWritePages(tplWCacheNumPages);
+
+ agent->addObject(mgmtObject, 0, true);
+
+ // Initialize all existing queues (ie those recovered before management was initialized)
+ for (JournalListMapItr i=journalList.begin(); i!=journalList.end(); i++) {
+ i->second->initManagement(agent);
+ }
+ }
+ }
+}
+
+bool MessageStoreImpl::init(const qpid::Options* options_)
+{
+ // Extract and check options
+ const StoreOptions* opts = static_cast<const StoreOptions*>(options_);
+ qpid::linearstore::journal::efpPartitionNumber_t efpPartition = chkEfpPartition(opts->efpPartition, "efp-partition");
+ qpid::linearstore::journal::efpDataSize_kib_t efpFilePoolSize_kib = chkEfpFileSizeKiB(opts->efpFileSizeKib, "efp-file-size");
+ uint32_t jrnlWrCachePageSizeKib = chkJrnlWrPageCacheSize(opts->wCachePageSizeKib, "wcache-page-size");
+ uint32_t tplJrnlWrCachePageSizeKib = chkJrnlWrPageCacheSize(opts->tplWCachePageSizeKib, "tpl-wcache-page-size");
+
+ // Pass option values to init()
+ return init(opts->storeDir, efpPartition, efpFilePoolSize_kib, opts->truncateFlag, jrnlWrCachePageSizeKib,
+ tplJrnlWrCachePageSizeKib, opts->overwriteBeforeReturnFlag);
+}
+
+// These params, taken from options, are assumed to be correct and verified
+bool MessageStoreImpl::init(const std::string& storeDir_,
+ qpid::linearstore::journal::efpPartitionNumber_t efpPartition_,
+ qpid::linearstore::journal::efpDataSize_kib_t efpFileSize_kib_,
+ const bool truncateFlag_,
+ uint32_t wCachePageSizeKib_,
+ uint32_t tplWCachePageSizeKib_,
+ const bool overwriteBeforeReturnFlag_)
+{
+ if (isInit) return true;
+
+ // Set geometry members (converting to correct units where req'd)
+ overwriteBeforeReturnFlag = overwriteBeforeReturnFlag_;
+ defaultEfpPartitionNumber = efpPartition_;
+ defaultEfpFileSize_kib = efpFileSize_kib_;
+ wCachePgSizeSblks = wCachePageSizeKib_ / QLS_SBLK_SIZE_KIB; // convert from KiB to number sblks
+ wCacheNumPages = getJrnlWrNumPages(wCachePageSizeKib_);
+ tplWCachePgSizeSblks = tplWCachePageSizeKib_ / QLS_SBLK_SIZE_KIB; // convert from KiB to number sblks
+ tplWCacheNumPages = getJrnlWrNumPages(tplWCachePageSizeKib_);
+ if (storeDir_.size()>0) storeDir = storeDir_;
+
+ if (truncateFlag_)
+ truncateInit();
+ init(truncateFlag_);
+
+ QLS_LOG(notice, "Store module initialized; store-dir=" << storeDir_);
+ QLS_LOG(info, "> Default EFP partition: " << defaultEfpPartitionNumber);
+ QLS_LOG(info, "> Default EFP file size: " << defaultEfpFileSize_kib << " (KiB)");
+ QLS_LOG(info, "> Default write cache page size: " << wCachePageSizeKib_ << " (KiB)");
+ QLS_LOG(info, "> Default number of write cache pages: " << wCacheNumPages);
+ QLS_LOG(info, "> TPL write cache page size: " << tplWCachePageSizeKib_ << " (KiB)");
+ QLS_LOG(info, "> TPL number of write cache pages: " << tplWCacheNumPages);
+ QLS_LOG(info, "> EFP partition: " << defaultEfpPartitionNumber);
+ QLS_LOG(info, "> EFP file size pool: " << defaultEfpFileSize_kib << " (KiB)");
+ QLS_LOG(info, "> Overwrite before return to EFP: " << (overwriteBeforeReturnFlag?"True":"False"));
+
+ return isInit;
+}
+
+void MessageStoreImpl::init(const bool truncateFlag)
+{
+ const int retryMax = 3;
+ int bdbRetryCnt = 0;
+ do {
+ if (bdbRetryCnt++ > 0)
+ {
+ closeDbs();
+ ::usleep(1000000); // 1 sec delay
+ QLS_LOG(error, "Previoius BDB store initialization failed, retrying (" << bdbRetryCnt << " of " << retryMax << ")...");
+ }
+
+ try {
+ qpid::linearstore::journal::jdir::create_dir(getBdbBaseDir());
+
+ dbenv.reset(new DbEnv(0));
+ dbenv->set_errpfx("linearstore");
+ dbenv->set_lg_regionmax(256000); // default = 65000
+ dbenv->open(getBdbBaseDir().c_str(), DB_THREAD | DB_CREATE | DB_INIT_TXN | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_USE_ENVIRON | DB_RECOVER, 0);
+
+ // Databases are constructed here instead of the constructor so that the DB_RECOVER flag can be used
+ // against the database environment. Recover can only be performed if no databases have been created
+ // against the environment at the time of recovery, as recovery invalidates the environment.
+ queueDb.reset(new Db(dbenv.get(), 0));
+ dbs.push_back(queueDb);
+ configDb.reset(new Db(dbenv.get(), 0));
+ dbs.push_back(configDb);
+ exchangeDb.reset(new Db(dbenv.get(), 0));
+ dbs.push_back(exchangeDb);
+ mappingDb.reset(new Db(dbenv.get(), 0));
+ dbs.push_back(mappingDb);
+ bindingDb.reset(new Db(dbenv.get(), 0));
+ dbs.push_back(bindingDb);
+ generalDb.reset(new Db(dbenv.get(), 0));
+ dbs.push_back(generalDb);
+
+ TxnCtxt txn;
+ txn.begin(dbenv.get(), false);
+ try {
+ open(queueDb, txn.get(), "queues.db", false);
+ open(configDb, txn.get(), "config.db", false);
+ open(exchangeDb, txn.get(), "exchanges.db", false);
+ open(mappingDb, txn.get(), "mappings.db", true);
+ open(bindingDb, txn.get(), "bindings.db", true);
+ open(generalDb, txn.get(), "general.db", false);
+ txn.commit();
+ } catch (...) { txn.abort(); throw; }
+ // NOTE: during normal initialization, agent == 0 because the store is initialized before the management infrastructure.
+ // However during a truncated initialization in a cluster, agent != 0. We always pass 0 as the agent for the
+ // TplStore to keep things consistent in a cluster. See https://bugzilla.redhat.com/show_bug.cgi?id=681026
+ tplStorePtr.reset(new TplJournalImpl(broker->getTimer(), "TplStore", getTplBaseDir(), jrnlLog, defJournalGetEventsTimeout, defJournalFlushTimeout, 0));
+ isInit = true;
+ } catch (const DbException& e) {
+ if (e.get_errno() == DB_VERSION_MISMATCH)
+ {
+ QLS_LOG(error, "Database environment mismatch: This version of db4 does not match that which created the store database.: " << e.what());
+ THROW_STORE_EXCEPTION_2("Database environment mismatch: This version of db4 does not match that which created the store database. "
+ "(If recovery is not important, delete the contents of the store directory. Otherwise, try upgrading the database using "
+ "db_upgrade or using db_recover - but the db4-utils package must also be installed to use these utilities.)", e);
+ }
+ QLS_LOG(error, "BDB exception occurred while initializing store: " << e.what());
+ if (bdbRetryCnt >= retryMax)
+ THROW_STORE_EXCEPTION_2("BDB exception occurred while initializing store", e);
+ } catch (const StoreException&) {
+ throw;
+ } catch (const qpid::linearstore::journal::jexception& e) {
+ QLS_LOG(error, "Journal Exception occurred while initializing store: " << e);
+ THROW_STORE_EXCEPTION_2("Journal Exception occurred while initializing store", e.what());
+ } catch (...) {
+ QLS_LOG(error, "Unknown exception occurred while initializing store.");
+ throw;
+ }
+ } while (!isInit);
+
+ efpMgr.reset(new qpid::linearstore::journal::EmptyFilePoolManager(getStoreTopLevelDir(),
+ defaultEfpPartitionNumber,
+ defaultEfpFileSize_kib,
+ overwriteBeforeReturnFlag,
+ truncateFlag,
+ jrnlLog));
+ efpMgr->findEfpPartitions();
+}
+
+void MessageStoreImpl::finalize()
+{
+ if (tplStorePtr.get() && tplStorePtr->is_ready()) tplStorePtr->stop(true);
+ {
+ qpid::sys::Mutex::ScopedLock sl(journalListLock);
+ for (JournalListMapItr i = journalList.begin(); i != journalList.end(); i++)
+ {
+ JournalImpl* jQueue = i->second;
+ jQueue->resetDeleteCallback();
+ if (jQueue->is_ready()) jQueue->stop(true);
+ }
+ }
+
+ if (mgmtObject.get() != 0) {
+ mgmtObject->resourceDestroy();
+ mgmtObject.reset();
+ }
+}
+
+void MessageStoreImpl::truncateInit()
+{
+ if (isInit) {
+ {
+ qpid::sys::Mutex::ScopedLock sl(journalListLock);
+ if (journalList.size()) { // check no queues exist
+ std::ostringstream oss;
+ oss << "truncateInit() called with " << journalList.size() << " queues still in existence";
+ THROW_STORE_EXCEPTION(oss.str());
+ }
+ }
+ closeDbs();
+ dbs.clear();
+ if (tplStorePtr->is_ready()) tplStorePtr->stop(true);
+ dbenv->close(0);
+ isInit = false;
+ }
+
+ qpid::linearstore::journal::jdir::delete_dir(getBdbBaseDir());
+
+ // TODO: Linearstore: harvest all discarded journal files into the empty file pool(s).
+ qpid::linearstore::journal::jdir::delete_dir(getJrnlBaseDir());
+ qpid::linearstore::journal::jdir::delete_dir(getTplBaseDir());
+ QLS_LOG(info, "Store directory " << getStoreTopLevelDir() << " was truncated.");
+}
+
+void MessageStoreImpl::chkTplStoreInit()
+{
+ // Prevent multiple threads from late-initializing the TPL
+ qpid::sys::Mutex::ScopedLock sl(tplInitLock);
+ if (!tplStorePtr->is_ready()) {
+ qpid::linearstore::journal::jdir::create_dir(getTplBaseDir());
+ tplStorePtr->initialize(getEmptyFilePool(defaultEfpPartitionNumber, defaultEfpFileSize_kib), tplWCacheNumPages, tplWCachePgSizeSblks);
+ if (mgmtObject.get() != 0) mgmtObject->set_tplIsInitialized(true);
+ }
+}
+
+void MessageStoreImpl::open(db_ptr db_,
+ DbTxn* txn_,
+ const char* file_,
+ bool dupKey_)
+{
+ if(dupKey_) db_->set_flags(DB_DUPSORT);
+ db_->open(txn_, file_, 0, DB_BTREE, DB_CREATE | DB_THREAD, 0);
+}
+
+void MessageStoreImpl::closeDbs()
+{
+ for (std::list<db_ptr >::iterator i = dbs.begin(); i != dbs.end(); i++) {
+ (*i)->close(0);
+ }
+ dbs.clear();
+}
+
+MessageStoreImpl::~MessageStoreImpl()
+{
+ finalize();
+ try {
+ closeDbs();
+ } catch (const DbException& e) {
+ QLS_LOG(error, "Error closing BDB databases: " << e.what());
+ } catch (const qpid::linearstore::journal::jexception& e) {
+ QLS_LOG(error, "Error: " << e.what());
+ } catch (const std::exception& e) {
+ QLS_LOG(error, "Error: " << e.what());
+ } catch (...) {
+ QLS_LOG(error, "Unknown error in MessageStoreImpl::~MessageStoreImpl()");
+ }
+
+ if (mgmtObject.get() != 0) {
+ mgmtObject->resourceDestroy();
+ mgmtObject.reset();
+ }
+}
+
+void MessageStoreImpl::create(qpid::broker::PersistableQueue& queue_,
+ const qpid::framing::FieldTable& args_)
+{
+ QLS_LOG(info, "*** MessageStoreImpl::create() queue=\"" << queue_.getName() << "\""); // DEBUG
+ checkInit();
+ if (queue_.getPersistenceId()) {
+ THROW_STORE_EXCEPTION("Queue already created: " + queue_.getName());
+ }
+ JournalImpl* jQueue = 0;
+
+ if (queue_.getName().size() == 0)
+ {
+ QLS_LOG(error, "Cannot create store for empty (null) queue name - queue create ignored.");
+ return;
+ }
+
+ jQueue = new JournalImpl(broker->getTimer(), queue_.getName(), getJrnlDir(queue_.getName()), jrnlLog,
+ defJournalGetEventsTimeout, defJournalFlushTimeout, agent,
+ boost::bind(&MessageStoreImpl::journalDeleted, this, _1));
+ {
+ qpid::sys::Mutex::ScopedLock sl(journalListLock);
+ journalList[queue_.getName()]=jQueue;
+ }
+
+ queue_.setExternalQueueStore(dynamic_cast<qpid::broker::ExternalQueueStore*>(jQueue));
+ try {
+ jQueue->initialize(getEmptyFilePool(args_), wCacheNumPages, wCachePgSizeSblks);
+ } catch (const qpid::linearstore::journal::jexception& e) {
+ THROW_STORE_EXCEPTION(std::string("Queue ") + queue_.getName() + ": create() failed: " + e.what());
+ }
+ try {
+ if (!create(queueDb, queueIdSequence, queue_)) {
+ THROW_STORE_EXCEPTION("Queue already exists: " + queue_.getName());
+ }
+ } catch (const DbException& e) {
+ THROW_STORE_EXCEPTION_2("Error creating queue named " + queue_.getName(), e);
+ }
+}
+
+qpid::linearstore::journal::EmptyFilePool*
+MessageStoreImpl::getEmptyFilePool(const qpid::linearstore::journal::efpPartitionNumber_t efpPartitionNumber_,
+ const qpid::linearstore::journal::efpDataSize_kib_t efpFileSizeKib_) {
+ qpid::linearstore::journal::EmptyFilePool* efpp = efpMgr->getEmptyFilePool(efpPartitionNumber_, efpFileSizeKib_);
+ if (efpp == 0) {
+ std::ostringstream oss;
+ oss << "Partition=" << efpPartitionNumber_ << "; EfpFileSize=" << efpFileSizeKib_ << " KiB";
+ throw qpid::linearstore::journal::jexception(qpid::linearstore::journal::jerrno::JERR_EFP_NOEFP, oss.str(), "MessageStoreImpl", "getEmptyFilePool");
+ }
+ return efpp;
+}
+
+qpid::linearstore::journal::EmptyFilePool*
+MessageStoreImpl::getEmptyFilePool(const qpid::framing::FieldTable& args_) {
+ qpid::framing::FieldTable::ValuePtr value;
+ qpid::linearstore::journal::efpPartitionNumber_t localEfpPartition = defaultEfpPartitionNumber;
+ value = args_.get("qpid.efp_partition_num");
+ if (value.get() != 0 && !value->empty() && value->convertsTo<int>()) {
+ localEfpPartition = chkEfpPartition((uint32_t)value->get<int>(), "qpid.efp_partition_num");
+ }
+
+ qpid::linearstore::journal::efpDataSize_kib_t localEfpFileSizeKib = defaultEfpFileSize_kib;
+ value = args_.get("qpid.efp_pool_file_size");
+ if (value.get() != 0 && !value->empty() && value->convertsTo<int>()) {
+ localEfpFileSizeKib = chkEfpFileSizeKiB((uint32_t)value->get<int>(), "qpid.efp_pool_file_size");
+ }
+ return getEmptyFilePool(localEfpPartition, localEfpFileSizeKib);
+}
+
+void MessageStoreImpl::destroy(qpid::broker::PersistableQueue& queue_)
+{
+ QLS_LOG(info, "*** MessageStoreImpl::destroy() queue=\"" << queue_.getName() << "\"");
+ checkInit();
+ destroy(queueDb, queue_);
+ deleteBindingsForQueue(queue_);
+ qpid::broker::ExternalQueueStore* eqs = queue_.getExternalQueueStore();
+ if (eqs) {
+ JournalImpl* jQueue = static_cast<JournalImpl*>(eqs);
+ jQueue->delete_jrnl_files();
+ queue_.setExternalQueueStore(0); // will delete the journal if exists
+ {
+ qpid::sys::Mutex::ScopedLock sl(journalListLock);
+ journalList.erase(queue_.getName());
+ }
+ }
+}
+
+void MessageStoreImpl::create(const qpid::broker::PersistableExchange& exchange_,
+ const qpid::framing::FieldTable& /*args_*/)
+{
+ checkInit();
+ if (exchange_.getPersistenceId()) {
+ THROW_STORE_EXCEPTION("Exchange already created: " + exchange_.getName());
+ }
+ try {
+ if (!create(exchangeDb, exchangeIdSequence, exchange_)) {
+ THROW_STORE_EXCEPTION("Exchange already exists: " + exchange_.getName());
+ }
+ } catch (const DbException& e) {
+ THROW_STORE_EXCEPTION_2("Error creating exchange named " + exchange_.getName(), e);
+ }
+}
+
+void MessageStoreImpl::destroy(const qpid::broker::PersistableExchange& exchange)
+{
+ checkInit();
+ destroy(exchangeDb, exchange);
+ //need to also delete bindings
+ IdDbt key(exchange.getPersistenceId());
+ bindingDb->del(0, &key, DB_AUTO_COMMIT);
+}
+
+void MessageStoreImpl::create(const qpid::broker::PersistableConfig& general_)
+{
+ checkInit();
+ if (general_.getPersistenceId()) {
+ THROW_STORE_EXCEPTION("General configuration item already created");
+ }
+ try {
+ if (!create(generalDb, generalIdSequence, general_)) {
+ THROW_STORE_EXCEPTION("General configuration already exists");
+ }
+ } catch (const DbException& e) {
+ THROW_STORE_EXCEPTION_2("Error creating general configuration", e);
+ }
+}
+
+void MessageStoreImpl::destroy(const qpid::broker::PersistableConfig& general_)
+{
+ checkInit();
+ destroy(generalDb, general_);
+}
+
+bool MessageStoreImpl::create(db_ptr db_,
+ IdSequence& seq_,
+ const qpid::broker::Persistable& p_)
+{
+ uint64_t id (seq_.next());
+ Dbt key(&id, sizeof(id));
+ BufferValue value (p_);
+
+ int status;
+ TxnCtxt txn;
+ txn.begin(dbenv.get(), true);
+ try {
+ status = db_->put(txn.get(), &key, &value, DB_NOOVERWRITE);
+ txn.commit();
+ } catch (...) {
+ txn.abort();
+ throw;
+ }
+ if (status == DB_KEYEXIST) {
+ return false;
+ } else {
+ p_.setPersistenceId(id);
+ return true;
+ }
+}
+
+void MessageStoreImpl::destroy(db_ptr db, const qpid::broker::Persistable& p_)
+{
+ qpid::sys::Mutex::ScopedLock sl(bdbLock);
+ IdDbt key(p_.getPersistenceId());
+ db->del(0, &key, DB_AUTO_COMMIT);
+}
+
+
+void MessageStoreImpl::bind(const qpid::broker::PersistableExchange& e_,
+ const qpid::broker::PersistableQueue& q_,
+ const std::string& k_,
+ const qpid::framing::FieldTable& a_)
+{
+ checkInit();
+ IdDbt key(e_.getPersistenceId());
+ BindingDbt value(e_, q_, k_, a_);
+ TxnCtxt txn;
+ txn.begin(dbenv.get(), true);
+ try {
+ put(bindingDb, txn.get(), key, value);
+ txn.commit();
+ } catch (...) {
+ txn.abort();
+ throw;
+ }
+}
+
+void MessageStoreImpl::unbind(const qpid::broker::PersistableExchange& e_,
+ const qpid::broker::PersistableQueue& q_,
+ const std::string& k_,
+ const qpid::framing::FieldTable& /*a_*/)
+{
+ checkInit();
+ deleteBinding(e_, q_, k_);
+}
+
+void MessageStoreImpl::recover(qpid::broker::RecoveryManager& registry_)
+{
+ checkInit();
+ txn_list prepared;
+ recoverLockedMappings(prepared);
+
+ std::ostringstream oss;
+ oss << "Recovered transaction prepared list:";
+ for (txn_list::iterator i = prepared.begin(); i != prepared.end(); i++) {
+ oss << std::endl << " " << qpid::linearstore::journal::jcntl::str2hexnum(i->xid);
+ }
+ QLS_LOG(debug, oss.str());
+
+ queue_index queues;//id->queue
+ exchange_index exchanges;//id->exchange
+ message_index messages;//id->message
+
+ TxnCtxt txn;
+ txn.begin(dbenv.get(), false);
+ try {
+ //read all queues, calls recoversMessages for each queue
+ recoverQueues(txn, registry_, queues, prepared, messages);
+
+ //recover exchange & bindings:
+ recoverExchanges(txn, registry_, exchanges);
+ recoverBindings(txn, exchanges, queues);
+
+ //recover general-purpose configuration
+ recoverGeneral(txn, registry_);
+
+ txn.commit();
+ } catch (const DbException& e) {
+ txn.abort();
+ THROW_STORE_EXCEPTION_2("Error on recovery", e);
+ } catch (...) {
+ txn.abort();
+ throw;
+ }
+
+ //recover transactions:
+ qpid::linearstore::journal::txn_map& txn_map_ref = tplStorePtr->get_txn_map();
+ for (txn_list::iterator i = prepared.begin(); i != prepared.end(); i++) {
+ const PreparedTransaction pt = *i;
+ if (mgmtObject.get() != 0) {
+ mgmtObject->inc_tplTransactionDepth();
+ mgmtObject->inc_tplTxnPrepares();
+ }
+
+ std::string xid = pt.xid;
+ qpid::linearstore::journal::txn_data_list_t tdl = txn_map_ref.get_tdata_list(xid);
+ if (tdl.size() == 0) THROW_STORE_EXCEPTION("XID not found in txn_map");
+ qpid::linearstore::journal::txn_op_stats_t txn_op_stats(tdl);
+ bool commitFlag = txn_op_stats.abortCnt == 0;
+
+ // If a record is found that is dequeued but not committed/aborted from tplStore, then a complete() call
+ // was interrupted part way through committing/aborting the impacted queues. Complete this process.
+ bool incomplTplTxnFlag = txn_op_stats.deqCnt > 0;
+
+ if (txn_op_stats.tpcCnt > 0) {
+ // Dtx (2PC) transaction
+ TPCTxnCtxt* tpcc = new TPCTxnCtxt(xid, &messageIdSequence);
+ std::auto_ptr<qpid::broker::TPCTransactionContext> txn(tpcc);
+ tpcc->recoverDtok(txn_op_stats.rid, xid);
+ tpcc->prepare(tplStorePtr.get());
+
+ qpid::broker::RecoverableTransaction::shared_ptr dtx;
+ if (!incomplTplTxnFlag) dtx = registry_.recoverTransaction(xid, txn);
+ if (pt.enqueues.get()) {
+ for (LockedMappings::iterator j = pt.enqueues->begin(); j != pt.enqueues->end(); j++) {
+ tpcc->addXidRecord(queues[j->first]->getExternalQueueStore());
+ if (!incomplTplTxnFlag) dtx->enqueue(queues[j->first], messages[j->second]);
+ }
+ }
+ if (pt.dequeues.get()) {
+ for (LockedMappings::iterator j = pt.dequeues->begin(); j != pt.dequeues->end(); j++) {
+ tpcc->addXidRecord(queues[j->first]->getExternalQueueStore());
+ if (!incomplTplTxnFlag) dtx->dequeue(queues[j->first], messages[j->second]);
+ }
+ }
+
+ if (incomplTplTxnFlag) {
+ tpcc->complete(commitFlag);
+ }
+ } else {
+ // Local (1PC) transaction
+ boost::shared_ptr<TxnCtxt> opcc(new TxnCtxt(xid, &messageIdSequence));
+ opcc->recoverDtok(txn_op_stats.rid, xid);
+ opcc->prepare(tplStorePtr.get());
+
+ if (pt.enqueues.get()) {
+ for (LockedMappings::iterator j = pt.enqueues->begin(); j != pt.enqueues->end(); j++) {
+ opcc->addXidRecord(queues[j->first]->getExternalQueueStore());
+ }
+ }
+ if (pt.dequeues.get()) {
+ for (LockedMappings::iterator j = pt.dequeues->begin(); j != pt.dequeues->end(); j++) {
+ opcc->addXidRecord(queues[j->first]->getExternalQueueStore());
+ }
+ }
+ if (incomplTplTxnFlag) {
+ opcc->complete(commitFlag);
+ } else {
+ completed(*opcc.get(), commitFlag);
+ }
+ }
+
+ }
+ registry_.recoveryComplete();
+}
+
+void MessageStoreImpl::recoverQueues(TxnCtxt& txn,
+ qpid::broker::RecoveryManager& registry,
+ queue_index& queue_index,
+ txn_list& prepared,
+ message_index& messages)
+{
+ Cursor queues;
+ queues.open(queueDb, txn.get());
+
+ uint64_t maxQueueId(1);
+
+ IdDbt key;
+ Dbt value;
+ //read all queues
+ while (queues.next(key, value)) {
+ qpid::framing::Buffer buffer(reinterpret_cast<char*>(value.get_data()), value.get_size());
+ //create a Queue instance
+ qpid::broker::RecoverableQueue::shared_ptr queue = registry.recoverQueue(buffer);
+ //set the persistenceId and update max as required
+ queue->setPersistenceId(key.id);
+
+ const std::string queueName = queue->getName().c_str();
+ JournalImpl* jQueue = 0;
+ if (queueName.size() == 0)
+ {
+ QLS_LOG(error, "Cannot recover empty (null) queue name - ignoring and attempting to continue.");
+ break;
+ }
+ jQueue = new JournalImpl(broker->getTimer(), queueName, getJrnlDir(queueName),jrnlLog,
+ defJournalGetEventsTimeout, defJournalFlushTimeout, agent,
+ boost::bind(&MessageStoreImpl::journalDeleted, this, _1));
+ {
+ qpid::sys::Mutex::ScopedLock sl(journalListLock);
+ journalList[queueName] = jQueue;
+ }
+ queue->setExternalQueueStore(dynamic_cast<qpid::broker::ExternalQueueStore*>(jQueue));
+
+ try
+ {
+ long rcnt = 0L; // recovered msg count
+ long idcnt = 0L; // in-doubt msg count
+ uint64_t thisHighestRid = 0ULL;
+ jQueue->recover(boost::dynamic_pointer_cast<qpid::linearstore::journal::EmptyFilePoolManager>(efpMgr), wCacheNumPages, wCachePgSizeSblks, &prepared, thisHighestRid, key.id);
+
+ // Check for changes to queue store settings qpid.file_count and qpid.file_size resulting
+ // from recovery of a store that has had its size changed externally by the resize utility.
+ // If so, update the queue store settings so that QMF queries will reflect the new values.
+ // TODO: Update this for new settings, as qpid.file_count and qpid.file_size no longer apply
+/*
+ const qpid::framing::FieldTable& storeargs = queue->getSettings().storeSettings;
+ qpid::framing::FieldTable::ValuePtr value;
+ value = storeargs.get("qpid.file_count");
+ if (value.get() != 0 && !value->empty() && value->convertsTo<int>() && (uint16_t)value->get<int>() != jQueue->num_jfiles()) {
+ queue->addArgument("qpid.file_count", jQueue->num_jfiles());
+ }
+ value = storeargs.get("qpid.file_size");
+ if (value.get() != 0 && !value->empty() && value->convertsTo<int>() && (uint32_t)value->get<int>() != jQueue->jfsize_sblks()/JRNL_RMGR_PAGE_SIZE) {
+ queue->addArgument("qpid.file_size", jQueue->jfsize_sblks()/JRNL_RMGR_PAGE_SIZE);
+ }
+*/
+
+ if (highestRid == 0ULL)
+ highestRid = thisHighestRid;
+ else if (thisHighestRid - highestRid < 0x8000000000000000ULL) // RFC 1982 comparison for unsigned 64-bit
+ highestRid = thisHighestRid;
+ recoverMessages(txn, registry, queue, prepared, messages, rcnt, idcnt);
+ QLS_LOG(info, "Recovered queue \"" << queueName << "\": " << rcnt << " messages recovered; " << idcnt << " messages in-doubt.");
+ jQueue->recover_complete(); // start journal.
+ } catch (const qpid::linearstore::journal::jexception& e) {
+ THROW_STORE_EXCEPTION(std::string("Queue ") + queueName + ": recoverQueues() failed: " + e.what());
+ }
+ //read all messages: done on a per queue basis if using Journal
+
+ queue_index[key.id] = queue;
+ maxQueueId = std::max(key.id, maxQueueId);
+ }
+
+ // NOTE: highestRid is set by both recoverQueues() and recoverTplStore() as
+ // the messageIdSequence is used for both queue journals and the tpl journal.
+ messageIdSequence.reset(highestRid + 1);
+ QLS_LOG(info, "Most recent persistence id found: 0x" << std::hex << highestRid << std::dec);
+
+ queueIdSequence.reset(maxQueueId + 1);
+}
+
+
+void MessageStoreImpl::recoverExchanges(TxnCtxt& txn_,
+ qpid::broker::RecoveryManager& registry_,
+ exchange_index& index_)
+{
+ //TODO: this is a copy&paste from recoverQueues - refactor!
+ Cursor exchanges;
+ exchanges.open(exchangeDb, txn_.get());
+
+ uint64_t maxExchangeId(1);
+ IdDbt key;
+ Dbt value;
+ //read all exchanges
+ while (exchanges.next(key, value)) {
+ qpid::framing::Buffer buffer(reinterpret_cast<char*>(value.get_data()), value.get_size());
+ //create a Exchange instance
+ qpid::broker::RecoverableExchange::shared_ptr exchange = registry_.recoverExchange(buffer);
+ if (exchange) {
+ //set the persistenceId and update max as required
+ exchange->setPersistenceId(key.id);
+ index_[key.id] = exchange;
+ QLS_LOG(info, "Recovered exchange \"" << exchange->getName() << '"');
+ }
+ maxExchangeId = std::max(key.id, maxExchangeId);
+ }
+ exchangeIdSequence.reset(maxExchangeId + 1);
+}
+
+void MessageStoreImpl::recoverBindings(TxnCtxt& txn_,
+ exchange_index& exchanges_,
+ queue_index& queues_)
+{
+ Cursor bindings;
+ bindings.open(bindingDb, txn_.get());
+
+ IdDbt key;
+ Dbt value;
+ while (bindings.next(key, value)) {
+ qpid::framing::Buffer buffer(reinterpret_cast<char*>(value.get_data()), value.get_size());
+ if (buffer.available() < 8) {
+ QLS_LOG(error, "Not enough data for binding: " << buffer.available());
+ THROW_STORE_EXCEPTION("Not enough data for binding");
+ }
+ uint64_t queueId = buffer.getLongLong();
+ std::string queueName;
+ std::string routingkey;
+ qpid::framing::FieldTable args;
+ buffer.getShortString(queueName);
+ buffer.getShortString(routingkey);
+ buffer.get(args);
+ exchange_index::iterator exchange = exchanges_.find(key.id);
+ queue_index::iterator queue = queues_.find(queueId);
+ if (exchange != exchanges_.end() && queue != queues_.end()) {
+ //could use the recoverable queue here rather than the name...
+ exchange->second->bind(queueName, routingkey, args);
+ QLS_LOG(info, "Recovered binding exchange=" << exchange->second->getName()
+ << " key=" << routingkey
+ << " queue=" << queueName);
+ } else {
+ //stale binding, delete it
+ QLS_LOG(warning, "Deleting stale binding");
+ bindings->del(0);
+ }
+ }
+}
+
+void MessageStoreImpl::recoverGeneral(TxnCtxt& txn_,
+ qpid::broker::RecoveryManager& registry_)
+{
+ Cursor items;
+ items.open(generalDb, txn_.get());
+
+ uint64_t maxGeneralId(1);
+ IdDbt key;
+ Dbt value;
+ //read all items
+ while (items.next(key, value)) {
+ qpid::framing::Buffer buffer(reinterpret_cast<char*>(value.get_data()), value.get_size());
+ //create instance
+ qpid::broker::RecoverableConfig::shared_ptr config = registry_.recoverConfig(buffer);
+ //set the persistenceId and update max as required
+ config->setPersistenceId(key.id);
+ maxGeneralId = std::max(key.id, maxGeneralId);
+ }
+ generalIdSequence.reset(maxGeneralId + 1);
+}
+
+void MessageStoreImpl::recoverMessages(TxnCtxt& /*txn*/,
+ qpid::broker::RecoveryManager& recovery,
+ qpid::broker::RecoverableQueue::shared_ptr& queue,
+ txn_list& prepared,
+ message_index& messages,
+ long& rcnt,
+ long& idcnt)
+{
+ size_t preambleLength = sizeof(uint32_t)/*header size*/;
+
+ JournalImpl* jc = static_cast<JournalImpl*>(queue->getExternalQueueStore());
+ unsigned msg_count = 0;
+
+ // TODO: This optimization to skip reading if there are no enqueued messages to read
+ // breaks the python system test in phase 6 with "Exception: Cannot write lock file"
+ // Figure out what is breaking.
+ //bool read = jc->get_enq_cnt() > 0;
+ bool read = true;
+
+ void* dbuff = NULL;
+ size_t dbuffSize = 0;
+ void* xidbuff = NULL;
+ size_t xidbuffSize = 0;
+ bool transientFlag = false;
+ bool externalFlag = false;
+ DataTokenImpl dtok;
+ dtok.set_wstate(DataTokenImpl::NONE);
+ qpid::linearstore::journal::txn_map& txn_map_ref = tplStorePtr->get_txn_map();
+
+ // Read the message from the Journal.
+ try {
+ unsigned aio_sleep_cnt = 0;
+ while (read) {
+ qpid::linearstore::journal::iores res = jc->read_data_record(&dbuff, dbuffSize, &xidbuff, xidbuffSize, transientFlag, externalFlag, &dtok, false);
+
+ switch (res)
+ {
+ case qpid::linearstore::journal::RHM_IORES_SUCCESS: {
+ msg_count++;
+ qpid::broker::RecoverableMessage::shared_ptr msg;
+ char* data = (char*)dbuff;
+
+ unsigned headerSize;
+ if (externalFlag) {
+ msg = getExternMessage(recovery, dtok.rid(), headerSize); // large message external to jrnl
+ } else {
+ headerSize = qpid::framing::Buffer(data, preambleLength).getLong();
+ qpid::framing::Buffer headerBuff(data+ preambleLength, headerSize);
+ msg = recovery.recoverMessage(headerBuff);
+ }
+ msg->setPersistenceId(dtok.rid());
+ // At some future point if delivery attempts are stored, then this call would
+ // become optional depending on that information.
+ msg->setRedelivered();
+ // Reset the TTL for the recovered message
+ msg->computeExpiration();
+
+ uint32_t contentOffset = headerSize + preambleLength;
+ uint64_t contentSize = dbuffSize - contentOffset;
+ if (msg->loadContent(contentSize) && !externalFlag) {
+ //now read the content
+ qpid::framing::Buffer contentBuff(data + contentOffset, contentSize);
+ msg->decodeContent(contentBuff);
+ }
+
+ PreparedTransaction::list::iterator i = PreparedTransaction::getLockedPreparedTransaction(prepared, queue->getPersistenceId(), dtok.rid());
+ if (i == prepared.end()) { // not in prepared list
+ rcnt++;
+ queue->recover(msg);
+ } else {
+ uint64_t rid = dtok.rid();
+ std::string xid(i->xid);
+ qpid::linearstore::journal::txn_data_list_t tdl = txn_map_ref.get_tdata_list(xid);
+ if (tdl.size() == 0) THROW_STORE_EXCEPTION("XID not found in txn_map");
+ qpid::linearstore::journal::txn_op_stats_t txn_op_stats(tdl);
+ if (txn_op_stats.deqCnt > 0 || txn_op_stats.tpcCnt == 0) {
+ if (jc->is_enqueued(rid, true)) {
+ // Enqueue is non-tx, dequeue tx
+ assert(jc->is_locked(rid)); // This record MUST be locked by a txn dequeue
+ if (txn_op_stats.abortCnt > 0) {
+ rcnt++;
+ queue->recover(msg); // recover message in abort case only
+ }
+ } else {
+ // Enqueue and/or dequeue tx
+ qpid::linearstore::journal::txn_map& tmap = jc->get_txn_map();
+ qpid::linearstore::journal::txn_data_list_t txnList = tmap.get_tdata_list(xid); // txnList will be empty if xid not found
+ bool enq = false;
+ bool deq = false;
+ for (qpid::linearstore::journal::tdl_itr_t j = txnList.begin(); j<txnList.end(); j++) {
+ if (j->enq_flag_ && j->rid_ == rid)
+ enq = true;
+ else if (!j->enq_flag_ && j->drid_ == rid)
+ deq = true;
+ }
+ if (enq && !deq && txn_op_stats.abortCnt == 0) {
+ rcnt++;
+ queue->recover(msg); // recover txn message in commit case only
+ }
+ }
+ } else {
+ idcnt++;
+ messages[rid] = msg;
+ }
+ }
+
+ dtok.reset();
+ dtok.set_wstate(DataTokenImpl::NONE);
+
+ if (xidbuff) {
+ ::free(xidbuff);
+ xidbuff = NULL;
+ }
+ if (dbuff) {
+ ::free(dbuff);
+ dbuff = NULL;
+ }
+ aio_sleep_cnt = 0;
+ break;
+ }
+ case qpid::linearstore::journal::RHM_IORES_PAGE_AIOWAIT:
+ if (++aio_sleep_cnt > MAX_AIO_SLEEPS)
+ THROW_STORE_EXCEPTION("Timeout waiting for AIO in MessageStoreImpl::recoverMessages()");
+ ::usleep(AIO_SLEEP_TIME_US);
+ break;
+ case qpid::linearstore::journal::RHM_IORES_EMPTY:
+ read = false;
+ break; // done with all messages. (add call in jrnl to test that _emap is empty.)
+ default:
+ std::ostringstream oss;
+ oss << "recoverMessages(): Queue: " << queue->getName() << ": Unexpected return from journal read: " << qpid::linearstore::journal::iores_str(res);
+ THROW_STORE_EXCEPTION(oss.str());
+ } // switch
+ } // while
+ } catch (const qpid::linearstore::journal::jexception& e) {
+ THROW_STORE_EXCEPTION(std::string("Queue ") + queue->getName() + ": recoverMessages() failed: " + e.what());
+ }
+}
+
+qpid::broker::RecoverableMessage::shared_ptr MessageStoreImpl::getExternMessage(qpid::broker::RecoveryManager& /*recovery*/,
+ uint64_t /*messageId*/,
+ unsigned& /*headerSize*/)
+{
+ throw qpid::linearstore::journal::jexception(qpid::linearstore::journal::jerrno::JERR__NOTIMPL, "MessageStoreImpl", "getExternMessage");
+}
+
+int MessageStoreImpl::enqueueMessage(TxnCtxt& txn_,
+ IdDbt& msgId_,
+ qpid::broker::RecoverableMessage::shared_ptr& msg_,
+ queue_index& index_,
+ txn_list& prepared_,
+ message_index& messages_)
+{
+ Cursor mappings;
+ mappings.open(mappingDb, txn_.get());
+
+ IdDbt value;
+
+ int count(0);
+ for (int status = mappings->get(&msgId_, &value, DB_SET); status == 0; status = mappings->get(&msgId_, &value, DB_NEXT_DUP)) {
+ if (index_.find(value.id) == index_.end()) {
+ QLS_LOG(warning, "Recovered message for queue that no longer exists");
+ mappings->del(0);
+ } else {
+ qpid::broker::RecoverableQueue::shared_ptr queue = index_[value.id];
+ if (PreparedTransaction::isLocked(prepared_, value.id, msgId_.id)) {
+ messages_[msgId_.id] = msg_;
+ } else {
+ queue->recover(msg_);
+ }
+ count++;
+ }
+ }
+ mappings.close();
+ return count;
+}
+
+
+void MessageStoreImpl::recoverTplStore()
+{
+ if (qpid::linearstore::journal::jdir::exists(tplStorePtr->jrnl_dir())) {
+ uint64_t thisHighestRid = 0ULL;
+ tplStorePtr->recover(boost::dynamic_pointer_cast<qpid::linearstore::journal::EmptyFilePoolManager>(efpMgr), tplWCacheNumPages, tplWCachePgSizeSblks, 0, thisHighestRid, 0);
+ if (highestRid == 0ULL)
+ highestRid = thisHighestRid;
+ else if (thisHighestRid - highestRid < 0x8000000000000000ULL) // RFC 1982 comparison for unsigned 64-bit
+ highestRid = thisHighestRid;
+ tplStorePtr->recover_complete(); // start TPL
+ }
+}
+
+void MessageStoreImpl::recoverLockedMappings(txn_list& txns)
+{
+ if (!tplStorePtr->is_ready())
+ recoverTplStore();
+ std::vector<std::string> xidList;
+ tplStorePtr->get_txn_map().xid_list(xidList);
+ for (std::vector<std::string>::const_iterator i=xidList.begin(); i!=xidList.end(); ++i) {
+ LockedMappings::shared_ptr enq_ptr;
+ enq_ptr.reset(new LockedMappings);
+ LockedMappings::shared_ptr deq_ptr;
+ deq_ptr.reset(new LockedMappings);
+ txns.push_back(new PreparedTransaction(*i, enq_ptr, deq_ptr));
+ }
+}
+
+void MessageStoreImpl::collectPreparedXids(std::set<std::string>& xids)
+{
+ if (!tplStorePtr->is_ready()) {
+ recoverTplStore();
+ }
+ std::vector<std::string> xidList;
+ tplStorePtr->get_txn_map().xid_list(xidList);
+ for (std::vector<std::string>::const_iterator i=xidList.begin(); i!=xidList.end(); ++i) {
+ qpid::linearstore::journal::txn_data_list_t tdl = tplStorePtr->get_txn_map().get_tdata_list(*i);
+ qpid::linearstore::journal::txn_op_stats_t txn_op_stats(tdl);
+ if (txn_op_stats.tpcCnt > 0) {
+ if (txn_op_stats.enqCnt - txn_op_stats.deqCnt > 0) {
+ xids.insert(*i);
+ }
+ }
+ }
+}
+
+void MessageStoreImpl::stage(const boost::intrusive_ptr<qpid::broker::PersistableMessage>& /*msg*/)
+{
+ throw qpid::linearstore::journal::jexception(qpid::linearstore::journal::jerrno::JERR__NOTIMPL, "MessageStoreImpl", "stage");
+}
+
+void MessageStoreImpl::destroy(qpid::broker::PersistableMessage& /*msg*/)
+{
+ throw qpid::linearstore::journal::jexception(qpid::linearstore::journal::jerrno::JERR__NOTIMPL, "MessageStoreImpl", "destroy");
+}
+
+void MessageStoreImpl::appendContent(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& /*msg*/,
+ const std::string& /*data*/)
+{
+ throw qpid::linearstore::journal::jexception(qpid::linearstore::journal::jerrno::JERR__NOTIMPL, "MessageStoreImpl", "appendContent");
+}
+
+void MessageStoreImpl::loadContent(const qpid::broker::PersistableQueue& /*queue*/,
+ const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& /*msg*/,
+ std::string& /*data*/,
+ uint64_t /*offset*/,
+ uint32_t /*length*/)
+{
+ throw qpid::linearstore::journal::jexception(qpid::linearstore::journal::jerrno::JERR__NOTIMPL, "MessageStoreImpl", "loadContent");
+}
+
+void MessageStoreImpl::flush(const qpid::broker::PersistableQueue& queue_)
+{
+// QLS_LOG(info, "*** MessageStoreImpl::flush() queue=\"" << queue_.getName() << "\"");
+ if (queue_.getExternalQueueStore() == 0) return;
+ checkInit();
+ std::string qn = queue_.getName();
+ try {
+ JournalImpl* jc = static_cast<JournalImpl*>(queue_.getExternalQueueStore());
+ if (jc) {
+ // TODO: check if this result should be used...
+ /*mrg::journal::iores res =*/ jc->flush(false);
+ }
+ } catch (const qpid::linearstore::journal::jexception& e) {
+ THROW_STORE_EXCEPTION(std::string("Queue ") + qn + ": flush() failed: " + e.what() );
+ }
+}
+
+void MessageStoreImpl::enqueue(qpid::broker::TransactionContext* ctxt_,
+ const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg_,
+ const qpid::broker::PersistableQueue& queue_)
+{
+ //QLS_LOG(info, "*** MessageStoreImpl::enqueue() queue=\"" << queue_.getName() << "\"");
+ checkInit();
+ uint64_t queueId (queue_.getPersistenceId());
+ if (queueId == 0) {
+ THROW_STORE_EXCEPTION("Queue not created: " + queue_.getName());
+ }
+
+ TxnCtxt implicit;
+ TxnCtxt* txn = 0;
+ if (ctxt_) {
+ txn = check(ctxt_);
+ } else {
+ txn = &implicit;
+ }
+
+ if (msg_->getPersistenceId() == 0) {
+ msg_->setPersistenceId(messageIdSequence.next());
+ }
+ store(&queue_, txn, msg_);
+
+ // add queue* to the txn map..
+ if (ctxt_) txn->addXidRecord(queue_.getExternalQueueStore());
+}
+
+uint64_t MessageStoreImpl::msgEncode(std::vector<char>& buff_,
+ const boost::intrusive_ptr<qpid::broker::PersistableMessage>& message_)
+{
+ uint32_t headerSize = message_->encodedHeaderSize();
+ uint64_t size = message_->encodedSize() + sizeof(uint32_t);
+ try { buff_ = std::vector<char>(size); } // long + headers + content
+ catch (const std::exception& e) {
+ std::ostringstream oss;
+ oss << "Unable to allocate memory for encoding message; requested size: " << size << "; error: " << e.what();
+ THROW_STORE_EXCEPTION(oss.str());
+ }
+ qpid::framing::Buffer buffer(&buff_[0],size);
+ buffer.putLong(headerSize);
+ message_->encode(buffer);
+ return size;
+}
+
+void MessageStoreImpl::store(const qpid::broker::PersistableQueue* queue_,
+ TxnCtxt* txn_,
+ const boost::intrusive_ptr<qpid::broker::PersistableMessage>& message_)
+{
+ //QLS_LOG(info, "*** MessageStoreImpl::store() queue=\"" << queue_->getName() << "\"");
+ std::vector<char> buff;
+ uint64_t size = msgEncode(buff, message_);
+
+ try {
+ if (queue_) {
+ boost::intrusive_ptr<DataTokenImpl> dtokp(new DataTokenImpl);
+ dtokp->addRef();
+ dtokp->setSourceMessage(message_);
+ dtokp->set_external_rid(true);
+ dtokp->set_rid(message_->getPersistenceId()); // set the messageID into the Journal header (record-id)
+
+ JournalImpl* jc = static_cast<JournalImpl*>(queue_->getExternalQueueStore());
+ if (txn_->getXid().empty()) {
+ jc->enqueue_data_record(&buff[0], size, size, dtokp.get(), !message_->isPersistent());
+ } else {
+ jc->enqueue_txn_data_record(&buff[0], size, size, dtokp.get(), txn_->getXid(), txn_->isTPC(), !message_->isPersistent());
+ }
+ } else {
+ THROW_STORE_EXCEPTION(std::string("MessageStoreImpl::store() failed: queue NULL."));
+ }
+ } catch (const qpid::linearstore::journal::jexception& e) {
+ THROW_STORE_EXCEPTION(std::string("Queue ") + queue_->getName() + ": MessageStoreImpl::store() failed: " +
+ e.what());
+ }
+}
+
+void MessageStoreImpl::dequeue(qpid::broker::TransactionContext* ctxt_,
+ const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg_,
+ const qpid::broker::PersistableQueue& queue_)
+{
+ //QLS_LOG(info, "*** MessageStoreImpl::dequeue() queue=\"" << queue_.getName() << "\"");
+ checkInit();
+ uint64_t queueId (queue_.getPersistenceId());
+ uint64_t messageId (msg_->getPersistenceId());
+ if (queueId == 0) {
+ THROW_STORE_EXCEPTION("Queue \"" + queue_.getName() + "\" has null queue Id (has not been created)");
+ }
+ if (messageId == 0) {
+ THROW_STORE_EXCEPTION("Queue \"" + queue_.getName() + "\": Dequeuing message with null persistence Id.");
+ }
+
+ TxnCtxt implicit;
+ TxnCtxt* txn = 0;
+ if (ctxt_) {
+ txn = check(ctxt_);
+ } else {
+ txn = &implicit;
+ }
+
+ // add queue* to the txn map..
+ if (ctxt_) txn->addXidRecord(queue_.getExternalQueueStore());
+ async_dequeue(ctxt_, msg_, queue_);
+ msg_->dequeueComplete();
+}
+
+void MessageStoreImpl::async_dequeue(qpid::broker::TransactionContext* ctxt_,
+ const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg_,
+ const qpid::broker::PersistableQueue& queue_)
+{
+ //QLS_LOG(info, "*** MessageStoreImpl::async_dequeue() queue=\"" << queue_.getName() << "\"");
+ boost::intrusive_ptr<DataTokenImpl> ddtokp(new DataTokenImpl);
+ ddtokp->setSourceMessage(msg_);
+ ddtokp->set_external_rid(true);
+ ddtokp->set_rid(messageIdSequence.next());
+ ddtokp->set_dequeue_rid(msg_->getPersistenceId());
+ ddtokp->set_wstate(DataTokenImpl::ENQ);
+ TxnCtxt* txn = 0;
+ std::string tid;
+ if (ctxt_) {
+ txn = check(ctxt_);
+ tid = txn->getXid();
+ }
+ // Manually increase the ref count, as raw pointers are used beyond this point
+ ddtokp->addRef();
+ try {
+ JournalImpl* jc = static_cast<JournalImpl*>(queue_.getExternalQueueStore());
+ if (tid.empty()) {
+ jc->dequeue_data_record(ddtokp.get(), false);
+ } else {
+ jc->dequeue_txn_data_record(ddtokp.get(), tid, txn?txn->isTPC():false, false);
+ }
+ } catch (const qpid::linearstore::journal::jexception& e) {
+ ddtokp->release();
+ THROW_STORE_EXCEPTION(std::string("Queue ") + queue_.getName() + ": async_dequeue() failed: " + e.what());
+ }
+}
+
+void MessageStoreImpl::completed(TxnCtxt& txn_,
+ bool commit_)
+{
+ try {
+ chkTplStoreInit(); // Late initialize (if needed)
+
+ // Nothing to do if not prepared
+ if (txn_.getDtok()->is_enqueued()) {
+ txn_.incrDtokRef();
+ DataTokenImpl* dtokp = txn_.getDtok();
+ dtokp->set_dequeue_rid(dtokp->rid());
+ dtokp->set_rid(messageIdSequence.next());
+ tplStorePtr->dequeue_txn_data_record(txn_.getDtok(), txn_.getXid(), txn_.isTPC(), commit_);
+ }
+ txn_.complete(commit_);
+ if (mgmtObject.get() != 0) {
+ mgmtObject->dec_tplTransactionDepth();
+ if (commit_)
+ mgmtObject->inc_tplTxnCommits();
+ else
+ mgmtObject->inc_tplTxnAborts();
+ }
+ } catch (const std::exception& e) {
+ QLS_LOG(error, "Error completing xid " << qpid::linearstore::journal::jcntl::str2hexnum(txn_.getXid()) << ": " << e.what());
+ throw;
+ }
+}
+
+std::auto_ptr<qpid::broker::TransactionContext> MessageStoreImpl::begin()
+{
+ checkInit();
+ // pass sequence number for c/a
+ return std::auto_ptr<qpid::broker::TransactionContext>(new TxnCtxt(&messageIdSequence));
+}
+
+std::auto_ptr<qpid::broker::TPCTransactionContext> MessageStoreImpl::begin(const std::string& xid_)
+{
+ checkInit();
+ IdSequence* jtx = &messageIdSequence;
+ // pass sequence number for c/a
+ return std::auto_ptr<qpid::broker::TPCTransactionContext>(new TPCTxnCtxt(xid_, jtx));
+}
+
+void MessageStoreImpl::prepare(qpid::broker::TPCTransactionContext& ctxt_)
+{
+ checkInit();
+ TxnCtxt* txn = dynamic_cast<TxnCtxt*>(&ctxt_);
+//std::string xid=txn->getXid(); std::cout << "*** MessageStoreImpl::prepare() xid=" << std::hex;
+//for (unsigned i=0; i<xid.length(); ++i) std::cout << "\\" << (int)xid.at(i); std::cout << " ***" << std::dec << std::endl;
+ if(!txn) throw qpid::broker::InvalidTransactionContextException();
+ localPrepare(txn);
+}
+
+void MessageStoreImpl::localPrepare(TxnCtxt* ctxt_)
+{
+//std::string xid=ctxt_->getXid(); std::cout << "*** MessageStoreImpl::localPrepare() xid=" << std::hex;
+//for (unsigned i=0; i<xid.length(); ++i) std::cout << "\\" << (int)xid.at(i); std::cout << " ***" << std::dec << std::endl;
+ try {
+ chkTplStoreInit(); // Late initialize (if needed)
+
+ // This sync is required to ensure multi-queue atomicity - ie all txn data
+ // must hit the disk on *all* queues before the TPL prepare (enq) is written.
+ ctxt_->sync();
+
+ ctxt_->incrDtokRef();
+ DataTokenImpl* dtokp = ctxt_->getDtok();
+ dtokp->set_external_rid(true);
+ dtokp->set_rid(messageIdSequence.next());
+ char tpcFlag = static_cast<char>(ctxt_->isTPC());
+ tplStorePtr->enqueue_txn_data_record(&tpcFlag, sizeof(char), sizeof(char), dtokp, ctxt_->getXid(), tpcFlag != 0, false);
+ ctxt_->prepare(tplStorePtr.get());
+ // make sure all the data is written to disk before returning
+ ctxt_->sync();
+ if (mgmtObject.get() != 0) {
+ mgmtObject->inc_tplTransactionDepth();
+ mgmtObject->inc_tplTxnPrepares();
+ }
+ } catch (const std::exception& e) {
+ QLS_LOG(error, "Error preparing xid " << ctxt_->getXid() << ": " << e.what());
+ throw;
+ }
+}
+
+void MessageStoreImpl::commit(qpid::broker::TransactionContext& ctxt_)
+{
+ checkInit();
+ TxnCtxt* txn(check(&ctxt_));
+ if (!txn->isTPC()) {
+ if (txn->impactedQueuesEmpty()) return;
+ localPrepare(dynamic_cast<TxnCtxt*>(txn));
+ }
+ completed(*dynamic_cast<TxnCtxt*>(txn), true);
+}
+
+void MessageStoreImpl::abort(qpid::broker::TransactionContext& ctxt_)
+{
+ checkInit();
+ TxnCtxt* txn(check(&ctxt_));
+ if (!txn->isTPC()) {
+ if (txn->impactedQueuesEmpty()) return;
+ localPrepare(dynamic_cast<TxnCtxt*>(txn));
+ }
+ completed(*dynamic_cast<TxnCtxt*>(txn), false);
+}
+
+TxnCtxt* MessageStoreImpl::check(qpid::broker::TransactionContext* ctxt_)
+{
+ TxnCtxt* txn = dynamic_cast<TxnCtxt*>(ctxt_);
+ if(!txn) throw qpid::broker::InvalidTransactionContextException();
+ return txn;
+}
+
+void MessageStoreImpl::put(db_ptr db_,
+ DbTxn* txn_,
+ Dbt& key_,
+ Dbt& value_)
+{
+ try {
+ int status = db_->put(txn_, &key_, &value_, DB_NODUPDATA);
+ if (status == DB_KEYEXIST) {
+ THROW_STORE_EXCEPTION("duplicate data");
+ } else if (status) {
+ THROW_STORE_EXCEPTION(DbEnv::strerror(status));
+ }
+ } catch (const DbException& e) {
+ THROW_STORE_EXCEPTION(e.what());
+ }
+}
+
+void MessageStoreImpl::deleteBindingsForQueue(const qpid::broker::PersistableQueue& queue_)
+{
+ TxnCtxt txn;
+ txn.begin(dbenv.get(), true);
+ try {
+ {
+ Cursor bindings;
+ bindings.open(bindingDb, txn.get());
+
+ IdDbt key;
+ Dbt value;
+ while (bindings.next(key, value)) {
+ qpid::framing::Buffer buffer(reinterpret_cast<char*>(value.get_data()), value.get_size());
+ if (buffer.available() < 8) {
+ THROW_STORE_EXCEPTION("Not enough data for binding");
+ }
+ uint64_t queueId = buffer.getLongLong();
+ if (queue_.getPersistenceId() == queueId) {
+ bindings->del(0);
+ QLS_LOG(debug, "Deleting binding for " << queue_.getName() << " " << key.id << "->" << queueId);
+ }
+ }
+ }
+ txn.commit();
+ } catch (const std::exception& e) {
+ txn.abort();
+ THROW_STORE_EXCEPTION_2("Error deleting bindings", e.what());
+ } catch (...) {
+ txn.abort();
+ throw;
+ }
+ QLS_LOG(debug, "Deleted all bindings for " << queue_.getName() << ":" << queue_.getPersistenceId());
+}
+
+void MessageStoreImpl::deleteBinding(const qpid::broker::PersistableExchange& exchange_,
+ const qpid::broker::PersistableQueue& queue_,
+ const std::string& bkey_)
+{
+ TxnCtxt txn;
+ txn.begin(dbenv.get(), true);
+ try {
+ {
+ Cursor bindings;
+ bindings.open(bindingDb, txn.get());
+
+ IdDbt key(exchange_.getPersistenceId());
+ Dbt value;
+
+ for (int status = bindings->get(&key, &value, DB_SET); status == 0; status = bindings->get(&key, &value, DB_NEXT_DUP)) {
+ qpid::framing::Buffer buffer(reinterpret_cast<char*>(value.get_data()), value.get_size());
+ if (buffer.available() < 8) {
+ THROW_STORE_EXCEPTION("Not enough data for binding");
+ }
+ uint64_t queueId = buffer.getLongLong();
+ if (queue_.getPersistenceId() == queueId) {
+ std::string q;
+ std::string k;
+ buffer.getShortString(q);
+ buffer.getShortString(k);
+ if (bkey_ == k) {
+ bindings->del(0);
+ QLS_LOG(debug, "Deleting binding for " << queue_.getName() << " " << key.id << "->" << queueId);
+ }
+ }
+ }
+ }
+ txn.commit();
+ } catch (const std::exception& e) {
+ txn.abort();
+ THROW_STORE_EXCEPTION_2("Error deleting bindings", e.what());
+ } catch (...) {
+ txn.abort();
+ throw;
+ }
+}
+
+std::string MessageStoreImpl::getStoreTopLevelDir() {
+ std::ostringstream dir;
+ dir << storeDir << "/" << storeTopLevelDir;
+ return dir.str();
+}
+
+
+std::string MessageStoreImpl::getJrnlBaseDir()
+{
+ std::ostringstream dir;
+ dir << storeDir << "/" << storeTopLevelDir << "/jrnl2/" ;
+ return dir.str();
+}
+
+std::string MessageStoreImpl::getBdbBaseDir()
+{
+ std::ostringstream dir;
+ dir << storeDir << "/" << storeTopLevelDir << "/dat2/" ;
+ return dir.str();
+}
+
+std::string MessageStoreImpl::getTplBaseDir()
+{
+ std::ostringstream dir;
+ dir << storeDir << "/" << storeTopLevelDir << "/tpl2/" ;
+ return dir.str();
+}
+
+std::string MessageStoreImpl::getJrnlDir(const std::string& queueName_)
+{
+ std::ostringstream oss;
+ oss << getJrnlBaseDir() << queueName_;
+ return oss.str();
+}
+
+std::string MessageStoreImpl::getStoreDir() const { return storeDir; }
+
+void MessageStoreImpl::journalDeleted(JournalImpl& j_) {
+ qpid::sys::Mutex::ScopedLock sl(journalListLock);
+ journalList.erase(j_.id());
+}
+
+MessageStoreImpl::StoreOptions::StoreOptions(const std::string& name_) :
+ qpid::Options(name_),
+ truncateFlag(defTruncateFlag),
+ wCachePageSizeKib(defWCachePageSizeKib),
+ tplWCachePageSizeKib(defTplWCachePageSizeKib),
+ efpPartition(defEfpPartition),
+ efpFileSizeKib(defEfpFileSizeKib),
+ overwriteBeforeReturnFlag(defOverwriteBeforeReturnFlag)
+{
+ addOptions()
+ ("store-dir", qpid::optValue(storeDir, "DIR"),
+ "Store directory location for persistence (instead of using --data-dir value). "
+ "Required if --no-data-dir is also used.")
+ ("truncate", qpid::optValue(truncateFlag, "yes|no"),
+ "If yes|true|1, will truncate the store (discard any existing records). If no|false|0, will preserve "
+ "the existing store files for recovery.")
+ ("wcache-page-size", qpid::optValue(wCachePageSizeKib, "N"),
+ "Size of the pages in the write page cache in KiB. "
+ "Allowable values - powers of 2: 1, 2, 4, ... , 128. "
+ "Lower values decrease latency at the expense of throughput.")
+ ("tpl-wcache-page-size", qpid::optValue(tplWCachePageSizeKib, "N"),
+ "Size of the pages in the transaction prepared list write page cache in KiB. "
+ "Allowable values - powers of 2: 1, 2, 4, ... , 128. "
+ "Lower values decrease latency at the expense of throughput.")
+ ("efp-partition", qpid::optValue(efpPartition, "N"),
+ "Empty File Pool partition to use for finding empty journal files")
+ ("efp-file-size", qpid::optValue(efpFileSizeKib, "N"),
+ "Empty File Pool file size in KiB to use for journal files. Must be a multiple of 4 KiB.")
+ ("overwrite-before-return", qpid::optValue(overwriteBeforeReturnFlag, "yes|no"),
+ "If yes|true|1, will overwrite each store file with zeros before returning "
+ "it to the Empty File Pool. When not in use (the default), then old message data remains "
+ "in the file, but is overwritten on next use. This option should only be used where security "
+ "considerations justify it as it makes the store somewhat slower.")
+ ;
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/linearstore/MessageStoreImpl.h b/qpid/cpp/src/qpid/linearstore/MessageStoreImpl.h
new file mode 100644
index 0000000000..236fcf2cf8
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/MessageStoreImpl.h
@@ -0,0 +1,351 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_MESSAGESTOREIMPL_H
+#define QPID_LINEARSTORE_MESSAGESTOREIMPL_H
+
+#include "qpid/broker/MessageStore.h"
+
+#include "qpid/Options.h"
+#include "qpid/linearstore/IdSequence.h"
+#include "qpid/linearstore/JournalLogImpl.h"
+#include "qpid/linearstore/journal/jcfg.h"
+#include "qpid/linearstore/journal/EmptyFilePoolTypes.h"
+#include "qpid/linearstore/PreparedTransaction.h"
+
+#include "qmf/org/apache/qpid/linearstore/Store.h"
+
+#include <iomanip>
+
+// Assume DB_VERSION_MAJOR == 4
+#if (DB_VERSION_MINOR == 2)
+#include <errno.h>
+#define DB_BUFFER_SMALL ENOMEM
+#endif
+
+class Db;
+class DbEnv;
+class Dbt;
+class DbTxn;
+
+namespace qpid {
+namespace broker {
+ class Broker;
+}
+namespace sys {
+ class Timer;
+}
+namespace linearstore{
+namespace journal {
+ class EmptyFilePool;
+ class EmptyFilePoolManager;
+}
+
+class IdDbt;
+class JournalImpl;
+class TplJournalImpl;
+class TxnCtxt;
+
+/**
+ * An implementation of the MessageStore interface based on Berkeley DB
+ */
+class MessageStoreImpl : public qpid::broker::MessageStore, public qpid::management::Manageable
+{
+ public:
+ typedef boost::shared_ptr<Db> db_ptr;
+ typedef boost::shared_ptr<DbEnv> dbEnv_ptr;
+
+ struct StoreOptions : public qpid::Options {
+ StoreOptions(const std::string& name="Linear Store Options");
+ std::string clusterName;
+ std::string storeDir;
+ bool truncateFlag;
+ uint32_t wCachePageSizeKib;
+ uint32_t tplWCachePageSizeKib;
+ uint16_t efpPartition;
+ uint64_t efpFileSizeKib;
+ bool overwriteBeforeReturnFlag;
+ };
+
+ protected:
+ typedef std::map<uint64_t, qpid::broker::RecoverableQueue::shared_ptr> queue_index;
+ typedef std::map<uint64_t, qpid::broker::RecoverableExchange::shared_ptr> exchange_index;
+ typedef std::map<uint64_t, qpid::broker::RecoverableMessage::shared_ptr> message_index;
+
+ typedef LockedMappings::map txn_lock_map;
+ typedef boost::ptr_list<PreparedTransaction> txn_list;
+
+ typedef std::map<std::string, JournalImpl*> JournalListMap;
+ typedef JournalListMap::iterator JournalListMapItr;
+
+ // Default store settings
+ static const bool defTruncateFlag = false;
+ static const uint32_t defWCachePageSizeKib = QLS_WMGR_DEF_PAGE_SIZE_KIB;
+ static const uint32_t defTplWCachePageSizeKib = defWCachePageSizeKib / 8;
+ static const uint16_t defEfpPartition = 1;
+ static const uint64_t defEfpFileSizeKib = 512 * QLS_SBLK_SIZE_KIB;
+ static const bool defOverwriteBeforeReturnFlag = false;
+ static const std::string storeTopLevelDir;
+
+ static qpid::sys::Duration defJournalGetEventsTimeout;
+ static qpid::sys::Duration defJournalFlushTimeout;
+
+ std::list<db_ptr> dbs;
+ dbEnv_ptr dbenv;
+ db_ptr queueDb;
+ db_ptr configDb;
+ db_ptr exchangeDb;
+ db_ptr mappingDb;
+ db_ptr bindingDb;
+ db_ptr generalDb;
+
+ // Pointer to Transaction Prepared List (TPL) journal instance
+ boost::shared_ptr<TplJournalImpl> tplStorePtr;
+ qpid::sys::Mutex tplInitLock;
+ JournalListMap journalList;
+ qpid::sys::Mutex journalListLock;
+ qpid::sys::Mutex bdbLock;
+
+ IdSequence queueIdSequence;
+ IdSequence exchangeIdSequence;
+ IdSequence generalIdSequence;
+ IdSequence messageIdSequence;
+ std::string storeDir;
+ qpid::linearstore::journal::efpPartitionNumber_t defaultEfpPartitionNumber;
+ qpid::linearstore::journal::efpDataSize_kib_t defaultEfpFileSize_kib;
+ bool overwriteBeforeReturnFlag;
+ uint32_t wCachePgSizeSblks;
+ uint16_t wCacheNumPages;
+ uint32_t tplWCachePgSizeSblks;
+ uint16_t tplWCacheNumPages;
+ uint64_t highestRid;
+ bool isInit;
+ const char* envPath;
+ qpid::broker::Broker* broker;
+ JournalLogImpl jrnlLog;
+ boost::shared_ptr<qpid::linearstore::journal::EmptyFilePoolManager> efpMgr;
+
+ qmf::org::apache::qpid::linearstore::Store::shared_ptr mgmtObject;
+ qpid::management::ManagementAgent* agent;
+
+
+ // Parameter validation and calculation
+ static uint32_t chkJrnlWrPageCacheSize(const uint32_t param,
+ const std::string& paramName/*,
+ const uint16_t jrnlFsizePgs*/);
+ static uint16_t getJrnlWrNumPages(const uint32_t wrPageSizeKiB);
+ static qpid::linearstore::journal::efpPartitionNumber_t chkEfpPartition(const qpid::linearstore::journal::efpPartitionNumber_t partition,
+ const std::string& paramName);
+ static qpid::linearstore::journal::efpDataSize_kib_t chkEfpFileSizeKiB(const qpid::linearstore::journal::efpDataSize_kib_t efpFileSizeKiB,
+ const std::string& paramName);
+
+ void init(const bool truncateFlag);
+
+ void recoverQueues(TxnCtxt& txn,
+ qpid::broker::RecoveryManager& recovery,
+ queue_index& index,
+ txn_list& locked,
+ message_index& messages);
+ void recoverMessages(TxnCtxt& txn,
+ qpid::broker::RecoveryManager& recovery,
+ queue_index& index,
+ txn_list& locked,
+ message_index& prepared);
+ void recoverMessages(TxnCtxt& txn,
+ qpid::broker::RecoveryManager& recovery,
+ qpid::broker::RecoverableQueue::shared_ptr& queue,
+ txn_list& locked,
+ message_index& prepared,
+ long& rcnt,
+ long& idcnt);
+ qpid::broker::RecoverableMessage::shared_ptr getExternMessage(qpid::broker::RecoveryManager& recovery,
+ uint64_t mId,
+ unsigned& headerSize);
+ void recoverExchanges(TxnCtxt& txn,
+ qpid::broker::RecoveryManager& recovery,
+ exchange_index& index);
+ void recoverBindings(TxnCtxt& txn,
+ exchange_index& exchanges,
+ queue_index& queues);
+ void recoverGeneral(TxnCtxt& txn,
+ qpid::broker::RecoveryManager& recovery);
+ int enqueueMessage(TxnCtxt& txn,
+ IdDbt& msgId,
+ qpid::broker::RecoverableMessage::shared_ptr& msg,
+ queue_index& index,
+ txn_list& locked,
+ message_index& prepared);
+ void recoverTplStore();
+ void recoverLockedMappings(txn_list& txns);
+ TxnCtxt* check(qpid::broker::TransactionContext* ctxt);
+ uint64_t msgEncode(std::vector<char>& buff, const boost::intrusive_ptr<qpid::broker::PersistableMessage>& message);
+ void store(const qpid::broker::PersistableQueue* queue,
+ TxnCtxt* txn,
+ const boost::intrusive_ptr<qpid::broker::PersistableMessage>& message);
+ void async_dequeue(qpid::broker::TransactionContext* ctxt,
+ const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg,
+ const qpid::broker::PersistableQueue& queue);
+ void destroy(db_ptr db,
+ const qpid::broker::Persistable& p);
+ bool create(db_ptr db,
+ IdSequence& seq,
+ const qpid::broker::Persistable& p);
+ void completed(TxnCtxt& txn,
+ bool commit);
+ void deleteBindingsForQueue(const qpid::broker::PersistableQueue& queue);
+ void deleteBinding(const qpid::broker::PersistableExchange& exchange,
+ const qpid::broker::PersistableQueue& queue,
+ const std::string& key);
+
+ void put(db_ptr db,
+ DbTxn* txn,
+ Dbt& key,
+ Dbt& value);
+ void open(db_ptr db,
+ DbTxn* txn,
+ const char* file,
+ bool dupKey);
+ void closeDbs();
+
+ // journal functions
+ void createJrnlQueue(const qpid::broker::PersistableQueue& queue);
+ std::string getJrnlDir(const std::string& queueName);
+ qpid::linearstore::journal::EmptyFilePool* getEmptyFilePool(const qpid::linearstore::journal::efpPartitionNumber_t p, const qpid::linearstore::journal::efpDataSize_kib_t s);
+ qpid::linearstore::journal::EmptyFilePool* getEmptyFilePool(const qpid::framing::FieldTable& args);
+ std::string getStoreTopLevelDir();
+ std::string getJrnlBaseDir();
+ std::string getBdbBaseDir();
+ std::string getTplBaseDir();
+ inline void checkInit() {
+ // TODO: change the default dir to ~/.qpidd
+ if (!isInit) { init("/tmp"); isInit = true; }
+ }
+ void chkTplStoreInit();
+
+ public:
+ typedef boost::shared_ptr<MessageStoreImpl> shared_ptr;
+
+ MessageStoreImpl(qpid::broker::Broker* broker, const char* envpath = 0);
+
+ virtual ~MessageStoreImpl();
+
+ bool init(const qpid::Options* options);
+
+ bool init(const std::string& dir,
+ qpid::linearstore::journal::efpPartitionNumber_t efpPartition = defEfpPartition,
+ qpid::linearstore::journal::efpDataSize_kib_t efpFileSizeKib = defEfpFileSizeKib,
+ const bool truncateFlag = false,
+ uint32_t wCachePageSize = defWCachePageSizeKib,
+ uint32_t tplWCachePageSize = defTplWCachePageSizeKib,
+ const bool overwriteBeforeReturnFlag_ = false);
+
+ void truncateInit();
+
+ void initManagement ();
+
+ void finalize();
+
+ // --- Implementation of qpid::broker::MessageStore ---
+
+ void create(qpid::broker::PersistableQueue& queue,
+ const qpid::framing::FieldTable& args);
+
+ void destroy(qpid::broker::PersistableQueue& queue);
+
+ void create(const qpid::broker::PersistableExchange& queue,
+ const qpid::framing::FieldTable& args);
+
+ void destroy(const qpid::broker::PersistableExchange& queue);
+
+ void bind(const qpid::broker::PersistableExchange& exchange,
+ const qpid::broker::PersistableQueue& queue,
+ const std::string& key,
+ const qpid::framing::FieldTable& args);
+
+ void unbind(const qpid::broker::PersistableExchange& exchange,
+ const qpid::broker::PersistableQueue& queue,
+ const std::string& key,
+ const qpid::framing::FieldTable& args);
+
+ void create(const qpid::broker::PersistableConfig& config);
+
+ void destroy(const qpid::broker::PersistableConfig& config);
+
+ void recover(qpid::broker::RecoveryManager& queues);
+
+ void stage(const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg);
+
+ void destroy(qpid::broker::PersistableMessage& msg);
+
+ void appendContent(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg,
+ const std::string& data);
+
+ void loadContent(const qpid::broker::PersistableQueue& queue,
+ const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg,
+ std::string& data,
+ uint64_t offset,
+ uint32_t length);
+
+ void enqueue(qpid::broker::TransactionContext* ctxt,
+ const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg,
+ const qpid::broker::PersistableQueue& queue);
+
+ void dequeue(qpid::broker::TransactionContext* ctxt,
+ const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg,
+ const qpid::broker::PersistableQueue& queue);
+
+ void flush(const qpid::broker::PersistableQueue& queue);
+
+ inline uint32_t outstandingQueueAIO(const qpid::broker::PersistableQueue& /*queue*/) { return 0; }; // TODO: Deprecate this call
+
+ void collectPreparedXids(std::set<std::string>& xids);
+
+ std::auto_ptr<qpid::broker::TransactionContext> begin();
+
+ std::auto_ptr<qpid::broker::TPCTransactionContext> begin(const std::string& xid);
+
+ void prepare(qpid::broker::TPCTransactionContext& ctxt);
+
+ void localPrepare(TxnCtxt* ctxt);
+
+ void commit(qpid::broker::TransactionContext& ctxt);
+
+ void abort(qpid::broker::TransactionContext& ctxt);
+
+ // --- Implementation of qpid::management::Managable ---
+
+ qpid::management::ManagementObject::shared_ptr GetManagementObject (void) const
+ { return mgmtObject; }
+
+ inline qpid::management::Manageable::status_t ManagementMethod (uint32_t, qpid::management::Args&, std::string&)
+ { return qpid::management::Manageable::STATUS_OK; }
+
+ std::string getStoreDir() const;
+
+ private:
+ void journalDeleted(JournalImpl&);
+
+}; // class MessageStoreImpl
+
+} // namespace msgstore
+} // namespace mrg
+
+#endif // ifndef QPID_LINEARSTORE_MESSAGESTOREIMPL_H
diff --git a/qpid/cpp/src/qpid/linearstore/PreparedTransaction.cpp b/qpid/cpp/src/qpid/linearstore/PreparedTransaction.cpp
new file mode 100644
index 0000000000..1b92ca8c23
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/PreparedTransaction.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/linearstore/PreparedTransaction.h"
+
+namespace qpid {
+namespace linearstore {
+
+void LockedMappings::add(queue_id queue, message_id message)
+{
+ locked.push_back(std::make_pair(queue, message));
+}
+
+bool LockedMappings::isLocked(queue_id queue, message_id message)
+{
+ idpair op( std::make_pair(queue, message) );
+ return find(locked.begin(), locked.end(), op) != locked.end();
+}
+
+void LockedMappings::add(LockedMappings::map& map, std::string& key, queue_id queue, message_id message)
+{
+ LockedMappings::map::iterator i = map.find(key);
+ if (i == map.end()) {
+ LockedMappings::shared_ptr ptr(new LockedMappings());
+ i = map.insert(std::make_pair(key, ptr)).first;
+ }
+ i->second->add(queue, message);
+}
+
+bool PreparedTransaction::isLocked(queue_id queue, message_id message)
+{
+ return (enqueues.get() && enqueues->isLocked(queue, message))
+ || (dequeues.get() && dequeues->isLocked(queue, message));
+}
+
+
+bool PreparedTransaction::isLocked(PreparedTransaction::list& txns, queue_id queue, message_id message)
+{
+ for (PreparedTransaction::list::iterator i = txns.begin(); i != txns.end(); i++) {
+ if (i->isLocked(queue, message)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+PreparedTransaction::list::iterator PreparedTransaction::getLockedPreparedTransaction(PreparedTransaction::list& txns, queue_id queue, message_id message)
+{
+ for (PreparedTransaction::list::iterator i = txns.begin(); i != txns.end(); i++) {
+ if (i->isLocked(queue, message)) {
+ return i;
+ }
+ }
+ return txns.end();
+}
+
+PreparedTransaction::PreparedTransaction(const std::string& _xid,
+ LockedMappings::shared_ptr _enqueues,
+ LockedMappings::shared_ptr _dequeues)
+
+ : xid(_xid), enqueues(_enqueues), dequeues(_dequeues) {}
+
+}}
diff --git a/qpid/cpp/src/qpid/linearstore/PreparedTransaction.h b/qpid/cpp/src/qpid/linearstore/PreparedTransaction.h
new file mode 100644
index 0000000000..7b381ba3b9
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/PreparedTransaction.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_PREPAREDTRANSACTION_H
+#define QPID_LINEARSTORE_PREPAREDTRANSACTION_H
+
+#include <boost/ptr_container/ptr_list.hpp>
+#include <boost/shared_ptr.hpp>
+#include <list>
+#include <map>
+#include <stdint.h>
+
+namespace qpid{
+namespace linearstore{
+
+typedef uint64_t queue_id;
+typedef uint64_t message_id;
+
+class LockedMappings
+{
+public:
+ typedef boost::shared_ptr<LockedMappings> shared_ptr;
+ typedef std::map<std::string, shared_ptr> map;
+ typedef std::pair<queue_id, message_id> idpair;
+ typedef std::list<idpair>::iterator iterator;
+
+ void add(queue_id queue, message_id message);
+ bool isLocked(queue_id queue, message_id message);
+ std::size_t size() { return locked.size(); }
+ iterator begin() { return locked.begin(); }
+ iterator end() { return locked.end(); }
+
+ static void add(LockedMappings::map& map, std::string& key, queue_id queue, message_id message);
+
+private:
+ std::list<idpair> locked;
+};
+
+struct PreparedTransaction
+{
+ typedef boost::ptr_list<PreparedTransaction> list;
+
+ const std::string xid;
+ const LockedMappings::shared_ptr enqueues;
+ const LockedMappings::shared_ptr dequeues;
+
+ PreparedTransaction(const std::string& xid, LockedMappings::shared_ptr enqueues, LockedMappings::shared_ptr dequeues);
+ bool isLocked(queue_id queue, message_id message);
+ static bool isLocked(PreparedTransaction::list& txns, queue_id queue, message_id message);
+ static PreparedTransaction::list::iterator getLockedPreparedTransaction(PreparedTransaction::list& txns, queue_id queue, message_id message);
+};
+
+}}
+
+#endif // ifndef QPID_LINEARSTORE_PREPAREDTRANSACTION_H
diff --git a/qpid/cpp/src/qpid/linearstore/StoreException.h b/qpid/cpp/src/qpid/linearstore/StoreException.h
new file mode 100644
index 0000000000..7a598a524f
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/StoreException.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_STOREEXCEPTION_H
+#define QPID_LINEARSTORE_STOREEXCEPTION_H
+
+#include <boost/format.hpp>
+#include "db-inc.h"
+
+namespace qpid{
+namespace linearstore{
+
+class StoreException : public std::exception
+{
+ std::string text;
+public:
+ StoreException(const std::string& _text) : text(_text) {}
+ StoreException(const std::string& _text, const DbException& cause) : text(_text + ": " + cause.what()) {}
+ virtual ~StoreException() throw() {}
+ virtual const char* what() const throw() { return text.c_str(); }
+};
+
+class StoreFullException : public StoreException
+{
+public:
+ StoreFullException(const std::string& _text) : StoreException(_text) {}
+ StoreFullException(const std::string& _text, const DbException& cause) : StoreException(_text, cause) {}
+ virtual ~StoreFullException() throw() {}
+
+};
+
+#define THROW_STORE_EXCEPTION(MESSAGE) throw StoreException(boost::str(boost::format("%s (%s:%d)") % (MESSAGE) % __FILE__ % __LINE__))
+#define THROW_STORE_EXCEPTION_2(MESSAGE, EXCEPTION) throw StoreException(boost::str(boost::format("%s (%s:%d)") % (MESSAGE) % __FILE__ % __LINE__), EXCEPTION)
+#define THROW_STORE_FULL_EXCEPTION(MESSAGE) throw StoreFullException(boost::str(boost::format("%s (%s:%d)") % (MESSAGE) % __FILE__ % __LINE__))
+
+}}
+
+#endif // ifndef QPID_LINEARSTORE_STOREEXCEPTION_H
diff --git a/qpid/cpp/src/qpid/linearstore/StorePlugin.cpp b/qpid/cpp/src/qpid/linearstore/StorePlugin.cpp
new file mode 100644
index 0000000000..cd8c7ed8a3
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/StorePlugin.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/broker/Broker.h"
+#include "qpid/Plugin.h"
+#include "qpid/Options.h"
+#include "qpid/DataDir.h"
+#include "qpid/linearstore/JournalLogImpl.h"
+#include "qpid/linearstore/MessageStoreImpl.h"
+#include "qpid/log/Statement.h"
+
+using qpid::linearstore::MessageStoreImpl;
+
+namespace qpid {
+namespace broker {
+
+using namespace std;
+
+struct StorePlugin : public Plugin {
+
+ MessageStoreImpl::StoreOptions options;
+ boost::shared_ptr<MessageStoreImpl> store;
+
+ Options* getOptions() { return &options; }
+
+ void earlyInitialize (Plugin::Target& target)
+ {
+ Broker* broker = dynamic_cast<Broker*>(&target);
+ if (!broker) return;
+ store.reset(new MessageStoreImpl(broker));
+ const DataDir& dataDir = broker->getDataDir ();
+ if (options.storeDir.empty ())
+ {
+ if (!dataDir.isEnabled ())
+ throw Exception ("linearstore: If broker option --data-dir is blank or --no-data-dir is specified, linearstore option --store-dir must be present.");
+
+ options.storeDir = dataDir.getPath ();
+ } else {
+ // Check if store dir is absolute. If not, make it absolute using qpidd executable dir as base
+ if (options.storeDir.at(0) != '/') {
+ char buf[1024];
+ if (::getcwd(buf, sizeof(buf)-1) == 0) {
+ std::ostringstream oss;
+ oss << "linearstore: getcwd() unable to read current directory: errno=" << errno << " (" << strerror(errno) << ")";
+ throw Exception(oss.str());
+ }
+ std::string newStoreDir = std::string(buf) + "/" + options.storeDir;
+ std::ostringstream oss;
+ oss << "store-dir option \"" << options.storeDir << "\" is not absolute, changed to \"" << newStoreDir << "\"";
+ QLS_LOG(warning, oss.str());
+ options.storeDir = newStoreDir;
+ }
+ }
+ store->init(&options);
+ boost::shared_ptr<qpid::broker::MessageStore> brokerStore(store);
+ broker->setStore(brokerStore);
+ target.addFinalizer(boost::bind(&StorePlugin::finalize, this));
+ }
+
+ void initialize(Plugin::Target& target)
+ {
+ Broker* broker = dynamic_cast<Broker*>(&target);
+ if (!broker) return;
+ if (!store) return;
+ QLS_LOG(info, "Enabling management instrumentation.");
+ store->initManagement();
+ }
+
+ void finalize()
+ {
+ store.reset();
+ }
+
+ const char* id() {return "StorePlugin";}
+};
+
+static StorePlugin instance; // Static initialization.
+
+}} // namespace qpid::broker
diff --git a/qpid/cpp/src/qpid/linearstore/TxnCtxt.cpp b/qpid/cpp/src/qpid/linearstore/TxnCtxt.cpp
new file mode 100644
index 0000000000..e26f0b8b6f
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/TxnCtxt.cpp
@@ -0,0 +1,185 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/linearstore/TxnCtxt.h"
+
+#include "qpid/linearstore/DataTokenImpl.h"
+#include "qpid/linearstore/JournalImpl.h"
+#include "qpid/linearstore/StoreException.h"
+
+namespace qpid{
+namespace linearstore{
+
+void TxnCtxt::completeTxn(bool commit) {
+ sync();
+ for (ipqItr i = impactedQueues.begin(); i != impactedQueues.end(); i++) {
+ commitTxn(static_cast<JournalImpl*>(*i), commit);
+ }
+ impactedQueues.clear();
+ if (preparedXidStorePtr)
+ commitTxn(preparedXidStorePtr, commit);
+}
+
+void TxnCtxt::commitTxn(JournalImpl* jc, bool commit) {
+ if (jc && loggedtx) { /* if using journal */
+ boost::intrusive_ptr<DataTokenImpl> dtokp(new DataTokenImpl);
+ dtokp->addRef();
+ dtokp->set_external_rid(true);
+ dtokp->set_rid(loggedtx->next());
+ try {
+ if (commit) {
+ jc->txn_commit(dtokp.get(), getXid());
+ sync();
+ } else {
+ jc->txn_abort(dtokp.get(), getXid());
+ }
+ } catch (const qpid::linearstore::journal::jexception& e) {
+ std::ostringstream oss;
+ oss << "Error during " << (commit ? "commit" : "abort") << ": " << e.what();
+ THROW_STORE_EXCEPTION(oss.str());
+ }
+ }
+}
+
+// static
+sys::uuid_t TxnCtxt::uuid;
+
+// static
+IdSequence TxnCtxt::uuidSeq;
+
+// static
+bool TxnCtxt::staticInit = TxnCtxt::setUuid();
+
+// static
+bool TxnCtxt::setUuid() {
+ qpid::sys::uuid_generate(uuid);
+ return true;
+}
+
+TxnCtxt::TxnCtxt(IdSequence* _loggedtx) : loggedtx(_loggedtx), dtokp(new DataTokenImpl), preparedXidStorePtr(0), txn(0) {
+ if (loggedtx) {
+// // Human-readable tid: 53 bytes
+// // uuit_t is a char[16]
+// tid.reserve(53);
+// uint64_t* u1 = (uint64_t*)uuid;
+// uint64_t* u2 = (uint64_t*)(uuid + sizeof(uint64_t));
+// std::stringstream s;
+// s << "tid:" << std::hex << std::setfill('0') << std::setw(16) << uuidSeq.next() << ":" << std::setw(16) << *u1 << std::setw(16) << *u2;
+// tid.assign(s.str());
+
+ // Binary tid: 24 bytes
+ tid.reserve(24);
+ uint64_t c = uuidSeq.next();
+ tid.append((char*)&c, sizeof(c));
+ tid.append((char*)&uuid, sizeof(uuid));
+ }
+}
+
+TxnCtxt::TxnCtxt(std::string _tid, IdSequence* _loggedtx) : loggedtx(_loggedtx), dtokp(new DataTokenImpl), preparedXidStorePtr(0), tid(_tid), txn(0) {}
+
+TxnCtxt::~TxnCtxt() { abort(); }
+
+void TxnCtxt::sync() {
+ if (loggedtx) {
+ try {
+ for (ipqItr i = impactedQueues.begin(); i != impactedQueues.end(); i++)
+ jrnl_flush(static_cast<JournalImpl*>(*i));
+ if (preparedXidStorePtr)
+ jrnl_flush(preparedXidStorePtr);
+ for (ipqItr i = impactedQueues.begin(); i != impactedQueues.end(); i++)
+ jrnl_sync(static_cast<JournalImpl*>(*i), &qpid::linearstore::journal::jcntl::_aio_cmpl_timeout);
+ if (preparedXidStorePtr)
+ jrnl_sync(preparedXidStorePtr, &qpid::linearstore::journal::jcntl::_aio_cmpl_timeout);
+ } catch (const qpid::linearstore::journal::jexception& e) {
+ THROW_STORE_EXCEPTION(std::string("Error during txn sync: ") + e.what());
+ }
+ }
+}
+
+void TxnCtxt::jrnl_flush(JournalImpl* jc) {
+ if (jc && !(jc->is_txn_synced(getXid())))
+ jc->flush(false);
+}
+
+void TxnCtxt::jrnl_sync(JournalImpl* jc, timespec* timeout) {
+ if (!jc || jc->is_txn_synced(getXid()))
+ return;
+ while (jc->get_wr_aio_evt_rem()) {
+ if (jc->get_wr_events(timeout) == qpid::linearstore::journal::jerrno::AIO_TIMEOUT && timeout)
+ THROW_STORE_EXCEPTION(std::string("Error: timeout waiting for TxnCtxt::jrnl_sync()"));
+ }
+}
+
+void TxnCtxt::begin(DbEnv* env, bool sync) {
+ int err;
+ try { err = env->txn_begin(0, &txn, 0); }
+ catch (const DbException&) { txn = 0; throw; }
+ if (err != 0) {
+ std::ostringstream oss;
+ oss << "Error: Env::txn_begin() returned error code: " << err;
+ THROW_STORE_EXCEPTION(oss.str());
+ }
+ if (sync)
+ globalHolder = AutoScopedLock(new qpid::sys::Mutex::ScopedLock(globalSerialiser));
+}
+
+void TxnCtxt::commit() {
+ if (txn) {
+ txn->commit(0);
+ txn = 0;
+ globalHolder.reset();
+ }
+}
+
+void TxnCtxt::abort(){
+ if (txn) {
+ txn->abort();
+ txn = 0;
+ globalHolder.reset();
+ }
+}
+
+DbTxn* TxnCtxt::get() { return txn; }
+
+bool TxnCtxt::isTPC() { return false; }
+
+const std::string& TxnCtxt::getXid() { return tid; }
+
+void TxnCtxt::addXidRecord(qpid::broker::ExternalQueueStore* queue) { impactedQueues.insert(queue); }
+
+void TxnCtxt::complete(bool commit) { completeTxn(commit); }
+
+bool TxnCtxt::impactedQueuesEmpty() { return impactedQueues.empty(); }
+
+DataTokenImpl* TxnCtxt::getDtok() { return dtokp.get(); }
+
+void TxnCtxt::incrDtokRef() { dtokp->addRef(); }
+
+void TxnCtxt::recoverDtok(const uint64_t rid, const std::string xid) {
+ dtokp->set_rid(rid);
+ dtokp->set_wstate(DataTokenImpl::ENQ);
+ dtokp->set_xid(xid);
+ dtokp->set_external_rid(true);
+}
+
+TPCTxnCtxt::TPCTxnCtxt(const std::string& _xid, IdSequence* _loggedtx) : TxnCtxt(_loggedtx), xid(_xid) {}
+
+}}
diff --git a/qpid/cpp/src/qpid/linearstore/TxnCtxt.h b/qpid/cpp/src/qpid/linearstore/TxnCtxt.h
new file mode 100644
index 0000000000..764063a615
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/TxnCtxt.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_TXNCTXT_H
+#define QPID_LINEARSTORE_TXNCTXT_H
+
+#include <boost/intrusive_ptr.hpp>
+#include "qpid/broker/TransactionalStore.h"
+#include "qpid/linearstore/IdSequence.h"
+#include "qpid/sys/uuid.h"
+
+class DbEnv;
+class DbTxn;
+
+namespace qpid {
+namespace broker {
+ class ExternalQueueStore;
+}
+namespace linearstore{
+ class DataTokenImpl;
+ class JournalImpl;
+
+class TxnCtxt : public qpid::broker::TransactionContext
+{
+ protected:
+ static qpid::sys::Mutex globalSerialiser;
+
+ static sys::uuid_t uuid;
+ static IdSequence uuidSeq;
+ static bool staticInit;
+ static bool setUuid();
+
+ typedef std::set<qpid::broker::ExternalQueueStore*> ipqdef;
+ typedef ipqdef::iterator ipqItr;
+ typedef std::auto_ptr<qpid::sys::Mutex::ScopedLock> AutoScopedLock;
+
+ ipqdef impactedQueues; // list of Queues used in the txn
+ IdSequence* loggedtx;
+ boost::intrusive_ptr<DataTokenImpl> dtokp;
+ AutoScopedLock globalHolder;
+ JournalImpl* preparedXidStorePtr;
+
+ /**
+ * local txn id, if non XA.
+ */
+ std::string tid;
+ DbTxn* txn;
+
+ virtual void completeTxn(bool commit);
+ void commitTxn(JournalImpl* jc, bool commit);
+ void jrnl_flush(JournalImpl* jc);
+ void jrnl_sync(JournalImpl* jc, timespec* timeout);
+
+ public:
+ TxnCtxt(IdSequence* _loggedtx=NULL);
+ TxnCtxt(std::string _tid, IdSequence* _loggedtx);
+ virtual ~TxnCtxt();
+
+ /**
+ * Call to make sure all the data for this txn is written to safe store
+ *
+ *@return if the data successfully synced.
+ */
+ void sync();
+ void begin(DbEnv* env, bool sync = false);
+ void commit();
+ void abort();
+ DbTxn* get();
+ virtual bool isTPC();
+ virtual const std::string& getXid();
+
+ void addXidRecord(qpid::broker::ExternalQueueStore* queue);
+ inline void prepare(JournalImpl* _preparedXidStorePtr) { preparedXidStorePtr = _preparedXidStorePtr; }
+ void complete(bool commit);
+ bool impactedQueuesEmpty();
+ DataTokenImpl* getDtok();
+ void incrDtokRef();
+ void recoverDtok(const uint64_t rid, const std::string xid);
+};
+
+
+class TPCTxnCtxt : public TxnCtxt, public qpid::broker::TPCTransactionContext
+{
+ protected:
+ const std::string xid;
+
+ public:
+ TPCTxnCtxt(const std::string& _xid, IdSequence* _loggedtx);
+ inline virtual bool isTPC() { return true; }
+ inline virtual const std::string& getXid() { return xid; }
+};
+
+}}
+
+#endif // ifndef QPID_LINEARSTORE_TXNCTXT_H
+
+
diff --git a/qpid/cpp/src/qpid/linearstore/journal/AtomicCounter.h b/qpid/cpp/src/qpid/linearstore/journal/AtomicCounter.h
new file mode 100644
index 0000000000..73e5fecf93
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/AtomicCounter.h
@@ -0,0 +1,133 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_ATOMICCOUNTER_H_
+#define QPID_LINEARSTORE_JOURNAL_ATOMICCOUNTER_H_
+
+#include "qpid/linearstore/journal/slock.h"
+#include <string>
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+template <class T>
+class AtomicCounter
+{
+private:
+ std::string id_;
+ T count_;
+ mutable smutex countMutex;
+
+public:
+ AtomicCounter(const std::string& id, const T& initValue) : id_(id), count_(initValue) {}
+
+ virtual ~AtomicCounter() {}
+
+ T get() const {
+ slock l(countMutex);
+ return count_;
+ }
+
+ void set(const T v) {
+ slock l(countMutex);
+ count_ = v;
+ }
+
+ T increment() {
+ slock l(countMutex);
+ return ++count_;
+ }
+
+ T add(const T& a) {
+ slock l(countMutex);
+ count_ += a;
+ return count_;
+ }
+
+ T addLimit(const T& a, const T& limit, const uint32_t jerr) {
+ slock l(countMutex);
+ if (count_ + a > limit) throw jexception(jerr, id_, "AtomicCounter", "addLimit");
+ count_ += a;
+ return count_;
+ }
+
+ T decrement() {
+ slock l(countMutex);
+ return --count_;
+ }
+
+ T decrementLimit(const T& limit = T(0), const uint32_t jerr = jerrno::JERR__UNDERFLOW) {
+ slock l(countMutex);
+ if (count_ < limit + 1) {
+ throw jexception(jerr, id_, "AtomicCounter", "decrementLimit");
+ }
+ return --count_;
+ }
+
+ T subtract(const T& s) {
+ slock l(countMutex);
+ count_ -= s;
+ return count_;
+ }
+
+ T subtractLimit(const T& s, const T& limit = T(0), const uint32_t jerr = jerrno::JERR__UNDERFLOW) {
+ slock l(countMutex);
+ if (count_ < limit + s) throw jexception(jerr, id_, "AtomicCounter", "subtractLimit");
+ count_ -= s;
+ return count_;
+ }
+
+ bool operator==(const T& o) const {
+ slock l(countMutex);
+ return count_ == o;
+ }
+
+ bool operator<(const T& o) const {
+ slock l(countMutex);
+ return count_ < o;
+ }
+
+ bool operator<=(const T& o) const {
+ slock l(countMutex);
+ return count_ <= o;
+ }
+
+ friend T operator-(const T& a, const AtomicCounter& b) {
+ slock l(b.countMutex);
+ return a - b.count_;
+ }
+
+ friend T operator-(const AtomicCounter& a, const T& b) {
+ slock l(a.countMutex);
+ return a.count_ - b;
+ }
+
+ friend T operator-(const AtomicCounter&a, const AtomicCounter& b) {
+ slock l1(a.countMutex);
+ slock l2(b.countMutex);
+ return a.count_ - b.count_;
+ }
+};
+
+}}} // namespace qpid::qls_jrnl
+
+#endif // QPID_LINEARSTORE_JOURNAL_ATOMICCOUNTER_H_
diff --git a/qpid/cpp/src/qpid/linearstore/journal/Checksum.cpp b/qpid/cpp/src/qpid/linearstore/journal/Checksum.cpp
new file mode 100644
index 0000000000..eaede12d8e
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/Checksum.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/linearstore/journal/Checksum.h"
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+Checksum::Checksum() : a(1UL), b(0UL), MOD_ADLER(65521UL) {}
+
+Checksum::~Checksum() {}
+
+void Checksum::addData(const unsigned char* data, const std::size_t len) {
+ if (data) {
+ for (uint32_t i = 0; i < len; i++) {
+ a = (a + data[i]) % MOD_ADLER;
+ b = (a + b) % MOD_ADLER;
+ }
+ }
+}
+
+uint32_t Checksum::getChecksum() {
+ return (b << 16) | a;
+}
+
+}}}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/Checksum.h b/qpid/cpp/src/qpid/linearstore/journal/Checksum.h
new file mode 100644
index 0000000000..d96aac2991
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/Checksum.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_CHECKSUM_H_
+#define QPID_LINEARSTORE_JOURNAL_CHECKSUM_H_
+
+#include <cstddef>
+#include <stdint.h>
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+/*
+ * This checksum routine uses the Adler-32 algorithm as described in
+ * http://en.wikipedia.org/wiki/Adler-32. It is structured so that the
+ * data for which the checksum must be calculated can be added in several
+ * stages through the addData() function, and when complete, the checksum
+ * is obtained through a call to getChecksum().
+ */
+class Checksum
+{
+private:
+ uint32_t a;
+ uint32_t b;
+ const uint32_t MOD_ADLER;
+public:
+ Checksum();
+ virtual ~Checksum();
+ void addData(const unsigned char* data, const std::size_t len);
+ uint32_t getChecksum();
+};
+
+}}}
+
+#endif // QPID_LINEARSTORE_JOURNAL_CHECKSUM_H_
diff --git a/qpid/cpp/src/qpid/linearstore/journal/EmptyFilePool.cpp b/qpid/cpp/src/qpid/linearstore/journal/EmptyFilePool.cpp
new file mode 100644
index 0000000000..08db3f75bd
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/EmptyFilePool.cpp
@@ -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.
+ *
+ */
+
+#include "EmptyFilePool.h"
+
+#include <fstream>
+#include "qpid/linearstore/journal/EmptyFilePoolPartition.h"
+#include "qpid/linearstore/journal/jcfg.h"
+#include "qpid/linearstore/journal/jdir.h"
+#include "qpid/linearstore/journal/JournalLog.h"
+#include "qpid/linearstore/journal/slock.h"
+#include "qpid/linearstore/journal/utils/file_hdr.h"
+#include "qpid/types/Uuid.h"
+#include <sys/stat.h>
+#include <unistd.h>
+#include <vector>
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+// static
+std::string EmptyFilePool::s_inuseFileDirectory_ = "in_use";
+
+// static
+std::string EmptyFilePool::s_returnedFileDirectory_ = "returned";
+
+EmptyFilePool::EmptyFilePool(const std::string& efpDirectory,
+ const EmptyFilePoolPartition* partitionPtr,
+ const bool overwriteBeforeReturnFlag,
+ const bool truncateFlag,
+ JournalLog& journalLogRef) :
+ efpDirectory_(efpDirectory),
+ efpDataSize_kib_(dataSizeFromDirName_kib(efpDirectory, partitionPtr->getPartitionNumber())),
+ partitionPtr_(partitionPtr),
+ overwriteBeforeReturnFlag_(overwriteBeforeReturnFlag),
+ truncateFlag_(truncateFlag),
+ journalLogRef_(journalLogRef)
+{}
+
+EmptyFilePool::~EmptyFilePool() {}
+
+void EmptyFilePool::initialize() {
+ if (::mkdir(efpDirectory_.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH)) { // Create EFP dir if it does not yet exist
+ if (errno != EEXIST) {
+ std::ostringstream oss;
+ oss << "directory=" << efpDirectory_ << " " << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_EFP_MKDIR, oss.str(), "EmptyFilePool", "initialize");
+ }
+ }
+
+ // Process empty files in main dir
+ std::vector<std::string> dirList;
+ jdir::read_dir(efpDirectory_, dirList, false, true, false, false);
+ for (std::vector<std::string>::iterator i = dirList.begin(); i != dirList.end(); ++i) {
+ size_t dotPos = i->rfind(".");
+ if (dotPos != std::string::npos) {
+ if (i->substr(dotPos).compare(".jrnl") == 0 && i->length() == 41) {
+ std::string emptyFileName(efpDirectory_ + "/" + (*i));
+ if (validateEmptyFile(emptyFileName)) {
+ pushEmptyFile(emptyFileName);
+ }
+ }
+ }
+ }
+
+ // Create 'in_use' and 'returned' subdirs if they don't already exist
+ // Retern files to EFP in 'in_use' and 'returned' subdirs if they do exist
+ initializeSubDirectory(efpDirectory_ + "/" + s_inuseFileDirectory_);
+ initializeSubDirectory(efpDirectory_ + "/" + s_returnedFileDirectory_);
+}
+
+efpDataSize_kib_t EmptyFilePool::dataSize_kib() const {
+ return efpDataSize_kib_;
+}
+
+efpFileSize_kib_t EmptyFilePool::fileSize_kib() const {
+ return efpDataSize_kib_ + (QLS_JRNL_FHDR_RES_SIZE_SBLKS * QLS_SBLK_SIZE_KIB);
+}
+
+efpDataSize_sblks_t EmptyFilePool::dataSize_sblks() const {
+ return efpDataSize_kib_ / QLS_SBLK_SIZE_KIB;
+}
+
+efpFileSize_sblks_t EmptyFilePool::fileSize_sblks() const {
+ return (efpDataSize_kib_ / QLS_SBLK_SIZE_KIB) + QLS_JRNL_FHDR_RES_SIZE_SBLKS;
+}
+
+efpFileCount_t EmptyFilePool::numEmptyFiles() const {
+ slock l(emptyFileListMutex_);
+ return efpFileCount_t(emptyFileList_.size());
+}
+
+efpDataSize_kib_t EmptyFilePool::cumFileSize_kib() const {
+ slock l(emptyFileListMutex_);
+ return efpDataSize_kib_t(emptyFileList_.size()) * efpDataSize_kib_;
+}
+
+efpPartitionNumber_t EmptyFilePool::getPartitionNumber() const {
+ return partitionPtr_->getPartitionNumber();
+}
+
+const EmptyFilePoolPartition* EmptyFilePool::getPartition() const {
+ return partitionPtr_;
+}
+
+const efpIdentity_t EmptyFilePool::getIdentity() const {
+ return efpIdentity_t(partitionPtr_->getPartitionNumber(), efpDataSize_kib_);
+}
+
+std::string EmptyFilePool::takeEmptyFile(const std::string& destDirectory) {
+ std::string emptyFileName = popEmptyFile();
+ std::string newFileName = efpDirectory_ + "/" + s_inuseFileDirectory_ + emptyFileName.substr(emptyFileName.rfind('/')); // NOTE: substr() includes leading '/'
+ std::string symlinkName = destDirectory + emptyFileName.substr(emptyFileName.rfind('/')); // NOTE: substr() includes leading '/'
+ if (!moveFile(emptyFileName, newFileName)) {
+ // Try again with new UUID for file name
+ newFileName = efpDirectory_ + "/" + s_inuseFileDirectory_ + "/" + getEfpFileName();
+ if (!moveFile(emptyFileName, newFileName)) {
+//std::cerr << "*** DEBUG: pushEmptyFile " << emptyFileName << "from EmptyFilePool::takeEmptyFile()" << std::endl; // DEBUG
+ pushEmptyFile(emptyFileName); // Return empty file to pool
+ std::ostringstream oss;
+ oss << "file=\"" << emptyFileName << "\" dest=\"" << newFileName << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_FMOVE, oss.str(), "EmptyFilePool", "takeEmptyFile");
+ }
+ }
+ if (createSymLink(newFileName, symlinkName)) {
+ std::ostringstream oss;
+ oss << "file=\"" << emptyFileName << "\" dest=\"" << newFileName << "\" symlink=\"" << symlinkName << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR__SYMLINK, oss.str(), "EmptyFilePool", "takeEmptyFile");
+ }
+ return symlinkName;
+}
+
+void EmptyFilePool::returnEmptyFileSymlink(const std::string& emptyFileSymlink) {
+ if (isFile(emptyFileSymlink)) {
+ returnEmptyFile(emptyFileSymlink);
+ } else if(isSymlink(emptyFileSymlink)) {
+ returnEmptyFile(deleteSymlink(emptyFileSymlink));
+ } else {
+ std::ostringstream oss;
+ oss << "File \"" << emptyFileSymlink << "\" is neither a file nor a symlink";
+ throw jexception(jerrno::JERR_EFP_BADFILETYPE, oss.str(), "EmptyFilePool", "returnEmptyFileSymlink");
+ }
+}
+
+//static
+std::string EmptyFilePool::dirNameFromDataSize(const efpDataSize_kib_t efpDataSize_kib) {
+ std::ostringstream oss;
+ oss << efpDataSize_kib << "k";
+ return oss.str();
+}
+
+
+// static
+efpDataSize_kib_t EmptyFilePool::dataSizeFromDirName_kib(const std::string& dirName,
+ const efpPartitionNumber_t partitionNumber) {
+ // Check for dirName format 'NNNk', where NNN is a number, convert NNN into an integer. NNN cannot be 0.
+ std::string n(dirName.substr(dirName.rfind('/')+1));
+ bool valid = true;
+ for (uint16_t charNum = 0; charNum < n.length(); ++charNum) {
+ if (charNum < n.length()-1) {
+ if (!::isdigit((int)n[charNum])) {
+ valid = false;
+ break;
+ }
+ } else {
+ valid = n[charNum] == 'k';
+ }
+ }
+ efpDataSize_kib_t s = ::atol(n.c_str());
+ if (!valid || s == 0 || s % QLS_SBLK_SIZE_KIB != 0) {
+ std::ostringstream oss;
+ oss << "Partition: " << partitionNumber << "; EFP directory: \'" << n << "\'";
+ throw jexception(jerrno::JERR_EFP_BADEFPDIRNAME, oss.str(), "EmptyFilePool", "fileSizeKbFromDirName");
+ }
+ return s;
+}
+
+// --- protected functions ---
+void EmptyFilePool::checkIosState(std::ofstream& ofs,
+ const uint32_t jerrno,
+ const std::string& fqFileName,
+ const std::string& operation,
+ const std::string& errorMessage,
+ const std::string& className,
+ const std::string& fnName) {
+ if (!ofs.good()) {
+ if (ofs.is_open()) {
+ ofs.close();
+ }
+ std::ostringstream oss;
+ oss << "IO failure: eofbit=" << (ofs.eof()?"T":"F") << " failbit=" << (ofs.fail()?"T":"F") << " badbit="
+ << (ofs.bad()?"T":"F") << " file=" << fqFileName << " operation=" << operation << ": " << errorMessage;
+ throw jexception(jerrno, oss.str(), className, fnName);
+ }
+}
+
+std::string EmptyFilePool::createEmptyFile() {
+ std::string efpfn = getEfpFileName();
+ overwriteFileContents(efpfn);
+ return efpfn;
+}
+
+std::string EmptyFilePool::getEfpFileName() {
+ qpid::types::Uuid uuid(true);
+ std::ostringstream oss;
+ oss << efpDirectory_ << "/" << uuid << QLS_JRNL_FILE_EXTENSION;
+ return oss.str();
+}
+
+void EmptyFilePool::initializeSubDirectory(const std::string& fqDirName) {
+ std::vector<std::string> dirList;
+ if (jdir::exists(fqDirName)) {
+ if (truncateFlag_) {
+ jdir::read_dir(fqDirName, dirList, false, true, false, false);
+ for (std::vector<std::string>::iterator i = dirList.begin(); i != dirList.end(); ++i) {
+ size_t dotPos = i->rfind(".");
+ if (i->substr(dotPos).compare(".jrnl") == 0 && i->length() == 41) {
+ returnEmptyFile(fqDirName + "/" + (*i));
+ } else {
+ std::ostringstream oss;
+ oss << "File \'" << *i << "\' was not a journal file and was not returned to EFP.";
+ journalLogRef_.log(JournalLog::LOG_WARN, oss.str());
+ }
+ }
+ }
+ } else {
+ jdir::create_dir(fqDirName);
+ }
+}
+
+void EmptyFilePool::overwriteFileContents(const std::string& fqFileName) {
+ ::file_hdr_t fh;
+ ::file_hdr_create(&fh, QLS_FILE_MAGIC, QLS_JRNL_VERSION, QLS_JRNL_FHDR_RES_SIZE_SBLKS, partitionPtr_->getPartitionNumber(), efpDataSize_kib_);
+ std::ofstream ofs(fqFileName.c_str(), std::ofstream::out | std::ofstream::binary);
+ checkIosState(ofs, jerrno::JERR_EFP_FOPEN, fqFileName, "constructor", "Failed to create file", "EmptyFilePool", "overwriteFileContents");
+ ofs.write((char*)&fh, sizeof(::file_hdr_t));
+ checkIosState(ofs, jerrno::JERR_EFP_FWRITE, fqFileName, "write()", "Failed to write header", "EmptyFilePool", "overwriteFileContents");
+ uint64_t rem = ((efpDataSize_kib_ + (QLS_JRNL_FHDR_RES_SIZE_SBLKS * QLS_SBLK_SIZE_KIB)) * 1024) - sizeof(::file_hdr_t);
+ while (rem--) {
+ ofs.put('\0');
+ checkIosState(ofs, jerrno::JERR_EFP_FWRITE, fqFileName, "put()", "Failed to put \0", "EmptyFilePool", "overwriteFileContents");
+ }
+ ofs.close();
+//std::cout << "*** WARNING: EFP " << efpDirectory_ << " is empty - created new journal file " << fqFileName.substr(fqFileName.rfind('/') + 1) << " on the fly" << std::endl; // DEBUG
+}
+
+std::string EmptyFilePool::popEmptyFile() {
+ std::string emptyFileName;
+ bool listEmptyFlag;
+ {
+ slock l(emptyFileListMutex_);
+ listEmptyFlag = emptyFileList_.empty();
+ if (!listEmptyFlag) {
+ emptyFileName = emptyFileList_.front();
+ emptyFileList_.pop_front();
+ }
+ }
+ // If the list is empty, create a new file and return the file name.
+ if (listEmptyFlag) {
+ emptyFileName = createEmptyFile();
+ }
+ return emptyFileName;
+}
+
+void EmptyFilePool::pushEmptyFile(const std::string fqFileName) {
+ slock l(emptyFileListMutex_);
+ emptyFileList_.push_back(fqFileName);
+}
+
+void EmptyFilePool::returnEmptyFile(const std::string& emptyFileName) {
+ std::string returnedFileName = efpDirectory_ + "/" + s_returnedFileDirectory_ + emptyFileName.substr(emptyFileName.rfind('/')); // NOTE: substr() includes leading '/'
+ if (!moveFile(emptyFileName, returnedFileName)) {
+ ::unlink(emptyFileName.c_str());
+//std::cerr << "*** WARNING: Unable to move file " << emptyFileName << " to " << returnedFileName << "; deleted." << std::endl; // DEBUG
+ }
+
+ // TODO: On a separate thread, process returned files by overwriting headers and, optionally, their contents and
+ // returning them to the EFP directory
+ resetEmptyFileHeader(returnedFileName);
+ if (overwriteBeforeReturnFlag_) {
+ overwriteFileContents(returnedFileName);
+ }
+ std::string sanitizedEmptyFileName = efpDirectory_ + returnedFileName.substr(returnedFileName.rfind('/')); // NOTE: substr() includes leading '/'
+ if (!moveFile(returnedFileName, sanitizedEmptyFileName)) {
+ ::unlink(returnedFileName.c_str());
+//std::cerr << "*** WARNING: Unable to move file " << returnedFileName << " to " << sanitizedEmptyFileName << "; deleted." << std::endl; // DEBUG
+ } else {
+ pushEmptyFile(sanitizedEmptyFileName);
+ }
+}
+
+void EmptyFilePool::resetEmptyFileHeader(const std::string& fqFileName) {
+ std::fstream fs(fqFileName.c_str(), std::fstream::in | std::fstream::out | std::fstream::binary);
+ if (fs.good()) {
+ const std::streamsize buffsize = QLS_JRNL_FHDR_RES_SIZE_SBLKS * QLS_SBLK_SIZE_BYTES;
+ char buff[buffsize];
+ fs.read((char*)buff, buffsize);
+ std::streampos bytesRead = fs.tellg();
+ if (std::streamoff(bytesRead) == buffsize) {
+ ::file_hdr_reset((::file_hdr_t*)buff);
+ // set rest of buffer to 0
+ ::memset(buff + sizeof(::file_hdr_t), 0, MAX_FILE_HDR_LEN - sizeof(::file_hdr_t));
+ fs.seekp(0, std::fstream::beg);
+ fs.write(buff, buffsize);
+ std::streampos bytesWritten = fs.tellp();
+ if (std::streamoff(bytesWritten) != buffsize) {
+//std::cerr << "*** ERROR: Unable to write file header of file \"" << fqFileName << "\": tried to write " << buffsize << " bytes; wrote " << bytesWritten << " bytes." << std::endl; // DEBUG
+ }
+ } else {
+//std::cerr << "*** ERROR: Unable to read file header of file \"" << fqFileName << "\": tried to read " << sizeof(::file_hdr_t) << " bytes; read " << bytesRead << " bytes." << std::endl; // DEBUG
+ }
+ fs.close();
+ } else {
+//std::cerr << "*** ERROR: Unable to open file \"" << fqFileName << "\" for reading" << std::endl; // DEBUG
+ }
+}
+
+bool EmptyFilePool::validateEmptyFile(const std::string& emptyFileName) const {
+ std::ostringstream oss;
+ struct stat s;
+ if (::stat(emptyFileName.c_str(), &s))
+ {
+ oss << "stat: file=\"" << emptyFileName << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_STAT, oss.str(), "EmptyFilePool", "validateEmptyFile");
+ }
+
+ // Size matches pool
+ efpDataSize_kib_t expectedSize = (QLS_SBLK_SIZE_KIB + efpDataSize_kib_) * 1024;
+ if ((efpDataSize_kib_t)s.st_size != expectedSize) {
+ oss << "ERROR: File " << emptyFileName << ": Incorrect size: Expected=" << expectedSize
+ << "; actual=" << s.st_size;
+ journalLogRef_.log(JournalLog::LOG_ERROR, oss.str());
+ return false;
+ }
+
+ // Open file and read header
+ std::fstream fs(emptyFileName.c_str(), std::fstream::in | std::fstream::out | std::fstream::binary);
+ if (!fs) {
+ oss << "ERROR: File " << emptyFileName << ": Unable to open for reading";
+ journalLogRef_.log(JournalLog::LOG_ERROR, oss.str());
+ return false;
+ }
+ const std::streamsize buffsize = QLS_JRNL_FHDR_RES_SIZE_SBLKS * QLS_SBLK_SIZE_BYTES;
+ char buff[buffsize];
+ fs.read((char*)buff, buffsize);
+ std::streampos bytesRead = fs.tellg();
+ if (std::streamoff(bytesRead) != buffsize) {
+ oss << "ERROR: Unable to read file header of file \"" << emptyFileName << "\": tried to read "
+ << buffsize << " bytes; read " << bytesRead << " bytes";
+ journalLogRef_.log(JournalLog::LOG_ERROR, oss.str());
+ fs.close();
+ return false;
+ }
+
+ // Check file header
+ ::file_hdr_t* fhp = (::file_hdr_t*)buff;
+ const bool jrnlMagicError = fhp->_rhdr._magic != QLS_FILE_MAGIC;
+ const bool jrnlVersionError = fhp->_rhdr._version != QLS_JRNL_VERSION;
+ const bool jrnlPartitionError = fhp->_efp_partition != partitionPtr_->getPartitionNumber();
+ const bool jrnlFileSizeError = fhp->_data_size_kib != efpDataSize_kib_;
+ if (jrnlMagicError || jrnlVersionError || jrnlPartitionError || jrnlFileSizeError)
+ {
+ oss << "ERROR: File " << emptyFileName << ": Invalid file header - mismatched header fields: " <<
+ (jrnlMagicError ? "magic " : "") <<
+ (jrnlVersionError ? "version " : "") <<
+ (jrnlPartitionError ? "partition" : "") <<
+ (jrnlFileSizeError ? "file-size" : "");
+ journalLogRef_.log(JournalLog::LOG_ERROR, oss.str());
+ fs.close();
+ return false;
+ }
+
+ // Check file header is reset
+ if (!::is_file_hdr_reset(fhp)) {
+ ::file_hdr_reset(fhp);
+ ::memset(buff + sizeof(::file_hdr_t), 0, MAX_FILE_HDR_LEN - sizeof(::file_hdr_t)); // set rest of buffer to 0
+ fs.seekp(0, std::fstream::beg);
+ fs.write(buff, buffsize);
+ std::streampos bytesWritten = fs.tellp();
+ if (std::streamoff(bytesWritten) != buffsize) {
+ oss << "ERROR: Unable to write file header of file \"" << emptyFileName << "\": tried to write "
+ << buffsize << " bytes; wrote " << bytesWritten << " bytes";
+ journalLogRef_.log(JournalLog::LOG_ERROR, oss.str());
+ fs.close();
+ return false;
+ }
+ oss << "WARNING: File " << emptyFileName << ": File header not reset";
+ journalLogRef_.log(JournalLog::LOG_WARN, oss.str());
+ }
+
+ // Close file
+ fs.close();
+ return true;
+}
+
+//static
+int EmptyFilePool::createSymLink(const std::string& fqFileName,
+ const std::string& fqLinkName) {
+ if(::symlink(fqFileName.c_str(), fqLinkName.c_str())) {
+ if (errno == EEXIST) return errno; // File name exists
+ std::ostringstream oss;
+ oss << "file=\"" << fqFileName << "\" symlink=\"" << fqLinkName << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR__SYMLINK, oss.str(), "EmptyFilePool", "createSymLink");
+ }
+ return 0;
+}
+
+//static
+std::string EmptyFilePool::deleteSymlink(const std::string& fqLinkName) {
+ char buff[1024];
+ ssize_t len = ::readlink(fqLinkName.c_str(), buff, 1024);
+ if (len < 0) {
+ std::ostringstream oss;
+ oss << "symlink=\"" << fqLinkName << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR__SYMLINK, oss.str(), "EmptyFilePool", "deleteSymlink");
+ }
+ ::unlink(fqLinkName.c_str());
+ return std::string(buff, len);
+}
+
+//static
+bool EmptyFilePool::isFile(const std::string& fqName) {
+ struct stat buff;
+ if (::lstat(fqName.c_str(), &buff)) {
+ std::ostringstream oss;
+ oss << "lstat file=\"" << fqName << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_EFP_LSTAT, oss.str(), "EmptyFilePool", "isFile");
+ }
+ return S_ISREG(buff.st_mode);
+}
+
+//static
+bool EmptyFilePool::isSymlink(const std::string& fqName) {
+ struct stat buff;
+ if (::lstat(fqName.c_str(), &buff)) {
+ std::ostringstream oss;
+ oss << "lstat file=\"" << fqName << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_EFP_LSTAT, oss.str(), "EmptyFilePool", "isSymlink");
+ }
+ return S_ISLNK(buff.st_mode);
+
+}
+
+// static
+bool EmptyFilePool::moveFile(const std::string& from,
+ const std::string& to) {
+ if (::rename(from.c_str(), to.c_str())) {
+ if (errno == EEXIST) {
+ return false; // File name exists
+ }
+ std::ostringstream oss;
+ oss << "file=\"" << from << "\" dest=\"" << to << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_FMOVE, oss.str(), "EmptyFilePool", "returnEmptyFile");
+ }
+ return true;
+}
+
+}}}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/EmptyFilePool.h b/qpid/cpp/src/qpid/linearstore/journal/EmptyFilePool.h
new file mode 100644
index 0000000000..dc567ff917
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/EmptyFilePool.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_EMPTYFILEPOOL_H_
+#define QPID_LINEARSTORE_JOURNAL_EMPTYFILEPOOL_H_
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+ class EmptyFilePool;
+}}}
+
+#include <deque>
+#include "qpid/linearstore/journal/EmptyFilePoolTypes.h"
+#include "qpid/linearstore/journal/smutex.h"
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+class EmptyFilePoolPartition;
+class jdir;
+class JournalFile;
+class JournalLog;
+
+class EmptyFilePool
+{
+protected:
+ typedef std::deque<std::string> emptyFileList_t;
+ typedef emptyFileList_t::const_iterator emptyFileListConstItr_t;
+
+ static std::string s_inuseFileDirectory_;
+ static std::string s_returnedFileDirectory_;
+
+ const std::string efpDirectory_;
+ const efpDataSize_kib_t efpDataSize_kib_;
+ const EmptyFilePoolPartition* partitionPtr_;
+ const bool overwriteBeforeReturnFlag_;
+ const bool truncateFlag_;
+ JournalLog& journalLogRef_;
+
+private:
+ emptyFileList_t emptyFileList_;
+ smutex emptyFileListMutex_;
+
+public:
+ EmptyFilePool(const std::string& efpDirectory,
+ const EmptyFilePoolPartition* partitionPtr,
+ const bool overwriteBeforeReturnFlag,
+ const bool truncateFlag,
+ JournalLog& journalLogRef);
+ virtual ~EmptyFilePool();
+
+ void initialize();
+ efpDataSize_kib_t dataSize_kib() const;
+ efpFileSize_kib_t fileSize_kib() const;
+ efpDataSize_sblks_t dataSize_sblks() const;
+ efpFileSize_sblks_t fileSize_sblks() const;
+ efpFileCount_t numEmptyFiles() const;
+ efpDataSize_kib_t cumFileSize_kib() const;
+ efpPartitionNumber_t getPartitionNumber() const;
+ const EmptyFilePoolPartition* getPartition() const;
+ const efpIdentity_t getIdentity() const;
+
+ std::string takeEmptyFile(const std::string& destDirectory);
+ void returnEmptyFileSymlink(const std::string& emptyFileSymlink);
+
+ static std::string dirNameFromDataSize(const efpDataSize_kib_t efpDataSize_kib);
+ static efpDataSize_kib_t dataSizeFromDirName_kib(const std::string& dirName,
+ const efpPartitionNumber_t partitionNumber);
+
+protected:
+ void checkIosState(std::ofstream& ofs,
+ const uint32_t jerrno,
+ const std::string& fqFileName,
+ const std::string& operation,
+ const std::string& errorMessage,
+ const std::string& className,
+ const std::string& fnName);
+ std::string createEmptyFile();
+ std::string getEfpFileName();
+ void initializeSubDirectory(const std::string& fqDirName);
+ void overwriteFileContents(const std::string& fqFileName);
+ std::string popEmptyFile();
+ void pushEmptyFile(const std::string fqFileName);
+ void returnEmptyFile(const std::string& emptyFileName);
+ void resetEmptyFileHeader(const std::string& fqFileName);
+ bool validateEmptyFile(const std::string& emptyFileName) const;
+
+ static int createSymLink(const std::string& fqFileName,
+ const std::string& fqLinkName);
+ static std::string deleteSymlink(const std::string& fqLinkName);
+ static bool isFile(const std::string& fqName);
+ static bool isSymlink(const std::string& fqName);
+ static bool moveFile(const std::string& fromFqPath,
+ const std::string& toFqPath);
+};
+
+}}}
+
+#endif /* QPID_LINEARSTORE_JOURNAL_EMPTYFILEPOOL_H_ */
diff --git a/qpid/cpp/src/qpid/linearstore/journal/EmptyFilePoolManager.cpp b/qpid/cpp/src/qpid/linearstore/journal/EmptyFilePoolManager.cpp
new file mode 100644
index 0000000000..a02679736e
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/EmptyFilePoolManager.cpp
@@ -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.
+ *
+ */
+
+#include "EmptyFilePoolManager.h"
+
+#include "qpid/linearstore/journal/EmptyFilePool.h"
+#include "qpid/linearstore/journal/EmptyFilePoolPartition.h"
+#include "qpid/linearstore/journal/jdir.h"
+#include "qpid/linearstore/journal/JournalLog.h"
+#include "qpid/linearstore/journal/slock.h"
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+EmptyFilePoolManager::EmptyFilePoolManager(const std::string& qlsStorePath,
+ const efpPartitionNumber_t defaultPartitionNumber,
+ const efpDataSize_kib_t defaultEfpDataSize_kib,
+ const bool overwriteBeforeReturnFlag,
+ const bool truncateFlag,
+ JournalLog& journalLogRef) :
+ qlsStorePath_(qlsStorePath),
+ defaultPartitionNumber_(defaultPartitionNumber),
+ defaultEfpDataSize_kib_(defaultEfpDataSize_kib),
+ overwriteBeforeReturnFlag_(overwriteBeforeReturnFlag),
+ truncateFlag_(truncateFlag),
+ journalLogRef_(journalLogRef)
+{}
+
+EmptyFilePoolManager::~EmptyFilePoolManager() {
+ slock l(partitionMapMutex_);
+ for (partitionMapItr_t i = partitionMap_.begin(); i != partitionMap_.end(); ++i) {
+ delete i->second;
+ }
+ partitionMap_.clear();
+}
+
+void EmptyFilePoolManager::findEfpPartitions() {
+//std::cout << "*** Reading " << qlsStorePath_ << std::endl; // DEBUG
+ bool foundPartition = false;
+ std::vector<std::string> dirList;
+ while (!foundPartition) {
+ jdir::read_dir(qlsStorePath_, dirList, true, false, true, false);
+ for (std::vector<std::string>::iterator i = dirList.begin(); i != dirList.end(); ++i) {
+ efpPartitionNumber_t pn = EmptyFilePoolPartition::getPartitionNumber(*i);
+ if (pn > 0) { // valid partition name found
+ std::string fullDirPath(qlsStorePath_ + "/" + (*i));
+ EmptyFilePoolPartition* efppp = insertPartition(pn, fullDirPath);
+ if (efppp != 0)
+ efppp->findEmptyFilePools();
+ foundPartition = true;
+ }
+ }
+
+ // If no partition was found, create an empty default partition.
+ if (!foundPartition) {
+ std::ostringstream oss1;
+ oss1 << qlsStorePath_ << "/" << EmptyFilePoolPartition::getPartionDirectoryName(defaultPartitionNumber_)
+ << "/" << EmptyFilePoolPartition::s_efpTopLevelDir_
+ << "/" << EmptyFilePool::dirNameFromDataSize(defaultEfpDataSize_kib_);
+ jdir::create_dir(oss1.str());
+ insertPartition(defaultPartitionNumber_, oss1.str());
+ std::ostringstream oss2;
+ oss2 << "No EFP partition found, creating an empty partition at " << oss1.str();
+ journalLogRef_.log(JournalLog::LOG_INFO, oss2.str());
+ }
+ }
+
+ journalLogRef_.log(JournalLog::LOG_INFO, "EFP Manager initialization complete");
+ std::vector<qpid::linearstore::journal::EmptyFilePoolPartition*> partitionList;
+ getEfpPartitions(partitionList);
+ if (partitionList.size() == 0) {
+ journalLogRef_.log(JournalLog::LOG_WARN, "NO EFP PARTITIONS FOUND! No queue creation is possible.");
+ } else {
+ std::stringstream oss;
+ oss << "EFP Partitions found: " << partitionList.size();
+ journalLogRef_.log(JournalLog::LOG_INFO, oss.str());
+ for (std::vector<qpid::linearstore::journal::EmptyFilePoolPartition*>::const_iterator i=partitionList.begin(); i!= partitionList.end(); ++i) {
+ journalLogRef_.log(JournalLog::LOG_INFO, (*i)->toString(5U));
+ }
+ }
+}
+
+void EmptyFilePoolManager::getEfpFileSizes(std::vector<efpDataSize_kib_t>& efpFileSizeList,
+ const efpPartitionNumber_t efpPartitionNumber) const {
+ if (efpPartitionNumber == 0) {
+ for (partitionMapConstItr_t i=partitionMap_.begin(); i!=partitionMap_.end(); ++i) {
+ i->second->getEmptyFilePoolSizes_kib(efpFileSizeList);
+ }
+ } else {
+ partitionMapConstItr_t i = partitionMap_.find(efpPartitionNumber);
+ if (i != partitionMap_.end()) {
+ i->second->getEmptyFilePoolSizes_kib(efpFileSizeList);
+ }
+ }
+}
+
+EmptyFilePoolPartition* EmptyFilePoolManager::getEfpPartition(const efpPartitionNumber_t partitionNumber) {
+ partitionMapItr_t i = partitionMap_.find(partitionNumber);
+ if (i == partitionMap_.end())
+ return 0;
+ else
+ return i->second;
+}
+
+void EmptyFilePoolManager::getEfpPartitionNumbers(std::vector<efpPartitionNumber_t>& partitionNumberList,
+ const efpDataSize_kib_t efpDataSize_kib) const {
+ slock l(partitionMapMutex_);
+ for (partitionMapConstItr_t i=partitionMap_.begin(); i!=partitionMap_.end(); ++i) {
+ if (efpDataSize_kib == 0) {
+ partitionNumberList.push_back(i->first);
+ } else {
+ std::vector<efpDataSize_kib_t> efpFileSizeList;
+ i->second->getEmptyFilePoolSizes_kib(efpFileSizeList);
+ for (std::vector<efpDataSize_kib_t>::iterator j=efpFileSizeList.begin(); j!=efpFileSizeList.end(); ++j) {
+ if (*j == efpDataSize_kib) {
+ partitionNumberList.push_back(i->first);
+ break;
+ }
+ }
+ }
+ }
+}
+
+void EmptyFilePoolManager::getEfpPartitions(std::vector<EmptyFilePoolPartition*>& partitionList,
+ const efpDataSize_kib_t efpDataSize_kib) {
+ slock l(partitionMapMutex_);
+ for (partitionMapConstItr_t i=partitionMap_.begin(); i!=partitionMap_.end(); ++i) {
+ if (efpDataSize_kib == 0) {
+ partitionList.push_back(i->second);
+ } else {
+ std::vector<efpDataSize_kib_t> efpFileSizeList;
+ i->second->getEmptyFilePoolSizes_kib(efpFileSizeList);
+ for (std::vector<efpDataSize_kib_t>::iterator j=efpFileSizeList.begin(); j!=efpFileSizeList.end(); ++j) {
+ if (*j == efpDataSize_kib) {
+ partitionList.push_back(i->second);
+ break;
+ }
+ }
+ }
+ }
+}
+
+EmptyFilePool* EmptyFilePoolManager::getEmptyFilePool(const efpIdentity_t efpIdentity) {
+ return getEmptyFilePool(efpIdentity.pn_, efpIdentity.ds_);
+}
+
+EmptyFilePool* EmptyFilePoolManager::getEmptyFilePool(const efpPartitionNumber_t partitionNumber,
+ const efpDataSize_kib_t efpDataSize_kib) {
+ EmptyFilePoolPartition* efppp = getEfpPartition(partitionNumber > 0 ? partitionNumber : defaultPartitionNumber_);
+ if (efppp == 0) {
+ return 0;
+ }
+ return efppp->getEmptyFilePool(efpDataSize_kib > 0 ? efpDataSize_kib : defaultEfpDataSize_kib_, true);
+}
+
+void EmptyFilePoolManager::getEmptyFilePools(std::vector<EmptyFilePool*>& emptyFilePoolList,
+ const efpPartitionNumber_t efpPartitionNumber) {
+ if (efpPartitionNumber == 0) {
+ for (partitionMapConstItr_t i=partitionMap_.begin(); i!=partitionMap_.end(); ++i) {
+ i->second->getEmptyFilePools(emptyFilePoolList);
+ }
+ } else {
+ partitionMapConstItr_t i = partitionMap_.find(efpPartitionNumber);
+ if (i != partitionMap_.end()) {
+ i->second->getEmptyFilePools(emptyFilePoolList);
+ }
+ }
+}
+
+uint16_t EmptyFilePoolManager::getNumEfpPartitions() const {
+ return partitionMap_.size();
+}
+
+EmptyFilePoolPartition* EmptyFilePoolManager::insertPartition(const efpPartitionNumber_t pn, const std::string& fullPartitionPath) {
+ EmptyFilePoolPartition* efppp = 0;
+ try {
+ efppp = new EmptyFilePoolPartition(pn, fullPartitionPath, overwriteBeforeReturnFlag_, truncateFlag_, journalLogRef_);
+ {
+ slock l(partitionMapMutex_);
+ partitionMap_[pn] = efppp;
+ }
+ } catch (const std::exception& e) {
+ if (efppp != 0) {
+ delete efppp;
+ efppp = 0;
+ }
+//std::cerr << "*** Unable to initialize partition " << pn << " (\'" << fullPartitionPath << "\'): " << e.what() << std::endl; // DEBUG
+ }
+ return efppp;
+}
+
+}}}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/EmptyFilePoolManager.h b/qpid/cpp/src/qpid/linearstore/journal/EmptyFilePoolManager.h
new file mode 100644
index 0000000000..d0aa7fa7d6
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/EmptyFilePoolManager.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_QLS_JRNL_EMPTYFILEPOOLMANAGER_H_
+#define QPID_QLS_JRNL_EMPTYFILEPOOLMANAGER_H_
+
+#include <map>
+#include "qpid/linearstore/journal/EmptyFilePoolTypes.h"
+#include "qpid/linearstore/journal/smutex.h"
+#include <vector>
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+class EmptyFilePool;
+class EmptyFilePoolPartition;
+class JournalLog;
+
+class EmptyFilePoolManager
+{
+protected:
+ typedef std::map<efpPartitionNumber_t, EmptyFilePoolPartition*> partitionMap_t;
+ typedef partitionMap_t::iterator partitionMapItr_t;
+ typedef partitionMap_t::const_iterator partitionMapConstItr_t;
+
+ const std::string qlsStorePath_;
+ const efpPartitionNumber_t defaultPartitionNumber_;
+ const efpDataSize_kib_t defaultEfpDataSize_kib_;
+ const bool overwriteBeforeReturnFlag_;
+ const bool truncateFlag_;
+ JournalLog& journalLogRef_;
+ partitionMap_t partitionMap_;
+ smutex partitionMapMutex_;
+
+public:
+ EmptyFilePoolManager(const std::string& qlsStorePath_,
+ const efpPartitionNumber_t defaultPartitionNumber,
+ const efpDataSize_kib_t defaultEfpDataSize_kib,
+ const bool overwriteBeforeReturnFlag,
+ const bool truncateFlag,
+ JournalLog& journalLogRef_);
+ virtual ~EmptyFilePoolManager();
+
+ void findEfpPartitions();
+ void getEfpFileSizes(std::vector<efpDataSize_kib_t>& efpFileSizeList,
+ const efpPartitionNumber_t efpPartitionNumber = 0) const;
+ EmptyFilePoolPartition* getEfpPartition(const efpPartitionNumber_t partitionNumber);
+ void getEfpPartitionNumbers(std::vector<efpPartitionNumber_t>& partitionNumberList,
+ const efpDataSize_kib_t efpDataSize_kib = 0) const;
+ void getEfpPartitions(std::vector<EmptyFilePoolPartition*>& partitionList,
+ const efpDataSize_kib_t efpDataSize_kib = 0);
+ EmptyFilePool* getEmptyFilePool(const efpIdentity_t efpIdentity);
+ EmptyFilePool* getEmptyFilePool(const efpPartitionNumber_t partitionNumber,
+ const efpDataSize_kib_t efpDataSize_kib);
+ void getEmptyFilePools(std::vector<EmptyFilePool*>& emptyFilePoolList,
+ const efpPartitionNumber_t efpPartitionNumber = 0);
+ uint16_t getNumEfpPartitions() const;
+protected:
+ EmptyFilePoolPartition* insertPartition(const efpPartitionNumber_t pn, const std::string& fullPartitionPath);
+};
+
+}}}
+
+#endif /* QPID_QLS_JRNL_EMPTYFILEPOOLMANAGER_H_ */
diff --git a/qpid/cpp/src/qpid/linearstore/journal/EmptyFilePoolPartition.cpp b/qpid/cpp/src/qpid/linearstore/journal/EmptyFilePoolPartition.cpp
new file mode 100644
index 0000000000..12d2db74b8
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/EmptyFilePoolPartition.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/linearstore/journal/EmptyFilePoolPartition.h"
+
+#include <iomanip>
+#include "qpid/linearstore/journal/EmptyFilePool.h"
+#include "qpid/linearstore/journal/jdir.h"
+#include "qpid/linearstore/journal/JournalLog.h"
+#include "qpid/linearstore/journal/slock.h"
+#include <unistd.h>
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+// static
+const std::string EmptyFilePoolPartition::s_efpTopLevelDir_("efp"); // Sets the top-level efp dir within a partition
+
+EmptyFilePoolPartition::EmptyFilePoolPartition(const efpPartitionNumber_t partitionNum,
+ const std::string& partitionDir,
+ const bool overwriteBeforeReturnFlag,
+ const bool truncateFlag,
+ JournalLog& journalLogRef) :
+ partitionNum_(partitionNum),
+ partitionDir_(partitionDir),
+ overwriteBeforeReturnFlag_(overwriteBeforeReturnFlag),
+ truncateFlag_(truncateFlag),
+ journalLogRef_(journalLogRef)
+{
+ validatePartitionDir();
+}
+
+EmptyFilePoolPartition::~EmptyFilePoolPartition() {
+ slock l(efpMapMutex_);
+ for (efpMapItr_t i = efpMap_.begin(); i != efpMap_.end(); ++i) {
+ delete i->second;
+ }
+ efpMap_.clear();
+}
+
+void
+EmptyFilePoolPartition::findEmptyFilePools() {
+//std::cout << "*** EmptyFilePoolPartition::findEmptyFilePools(): Reading " << partitionDir_ << std::endl; // DEBUG
+ std::string efpDir(partitionDir_ + "/" + s_efpTopLevelDir_);
+ if (jdir::is_dir(efpDir)) {
+ std::vector<std::string> dirList;
+ jdir::read_dir(efpDir, dirList, true, false, false, true);
+ for (std::vector<std::string>::iterator i = dirList.begin(); i != dirList.end(); ++i) {
+ createEmptyFilePool(*i);
+ }
+ } else {
+ std::ostringstream oss;
+ oss << "Partition \"" << partitionDir_ << "\" does not contain top level EFP dir \"" << s_efpTopLevelDir_ << "\"";
+ journalLogRef_.log(JournalLog::LOG_WARN, oss.str());
+ }
+}
+
+EmptyFilePool* EmptyFilePoolPartition::getEmptyFilePool(const efpDataSize_kib_t efpDataSize_kib, const bool createIfNonExistent) {
+ {
+ slock l(efpMapMutex_);
+ efpMapItr_t i = efpMap_.find(efpDataSize_kib);
+ if (i != efpMap_.end())
+ return i->second;
+ }
+ if (createIfNonExistent) {
+ return createEmptyFilePool(efpDataSize_kib);
+ }
+ return 0;
+}
+
+void EmptyFilePoolPartition::getEmptyFilePools(std::vector<EmptyFilePool*>& efpList) {
+ slock l(efpMapMutex_);
+ for (efpMapItr_t i=efpMap_.begin(); i!=efpMap_.end(); ++i) {
+ efpList.push_back(i->second);
+ }
+}
+
+void EmptyFilePoolPartition::getEmptyFilePoolSizes_kib(std::vector<efpDataSize_kib_t>& efpDataSizesList_kib) const {
+ slock l(efpMapMutex_);
+ for (efpMapConstItr_t i=efpMap_.begin(); i!=efpMap_.end(); ++i) {
+ efpDataSizesList_kib.push_back(i->first);
+ }
+}
+
+std::string EmptyFilePoolPartition::getPartitionDirectory() const {
+ return partitionDir_;
+}
+
+efpPartitionNumber_t EmptyFilePoolPartition::getPartitionNumber() const {
+ return partitionNum_;
+}
+
+std::string EmptyFilePoolPartition::toString(const uint16_t indent) const {
+ std::string indentStr(indent, ' ');
+ std::stringstream oss;
+ oss << "EFP Partition " << partitionNum_ << ":" << std::endl;
+ oss << indentStr << "EFP Partition Analysis (partition " << partitionNum_ << " at \"" << partitionDir_ << "\"):" << std::endl;
+ if (efpMap_.empty()) {
+ oss << indentStr << "<Partition empty, no EFPs found>" << std::endl;
+ } else {
+ oss << indentStr << std::setw(12) << "efp_size_kib"
+ << std::setw(12) << "num_files"
+ << std::setw(18) << "tot_capacity_kib" << std::endl;
+ oss << indentStr << std::setw(12) << "------------"
+ << std::setw(12) << "----------"
+ << std::setw(18) << "----------------" << std::endl;
+ {
+ slock l(efpMapMutex_);
+ for (efpMapConstItr_t i=efpMap_.begin(); i!= efpMap_.end(); ++i) {
+ oss << indentStr << std::setw(12) << i->first
+ << std::setw(12) << i->second->numEmptyFiles()
+ << std::setw(18) << i->second->cumFileSize_kib() << std::endl;
+ }
+ }
+ }
+ return oss.str();
+}
+
+// static
+std::string EmptyFilePoolPartition::getPartionDirectoryName(const efpPartitionNumber_t partitionNumber) {
+ std::ostringstream oss;
+ oss << "p" << std::setfill('0') << std::setw(3) << partitionNumber;
+ return oss.str();
+}
+
+//static
+efpPartitionNumber_t EmptyFilePoolPartition::getPartitionNumber(const std::string& name) {
+ if (name.length() == 4 && name[0] == 'p' && ::isdigit(name[1]) && ::isdigit(name[2]) && ::isdigit(name[3])) {
+ long pn = ::strtol(name.c_str() + 1, 0, 10);
+ if (pn == 0 && errno) {
+ return 0;
+ } else {
+ return (efpPartitionNumber_t)pn;
+ }
+ }
+ return 0;
+}
+
+// --- protected functions ---
+
+EmptyFilePool* EmptyFilePoolPartition::createEmptyFilePool(const efpDataSize_kib_t efpDataSize_kib) {
+ std::string fqEfpDirectoryName(partitionDir_ + "/" + EmptyFilePoolPartition::s_efpTopLevelDir_ + "/" + EmptyFilePool::dirNameFromDataSize(efpDataSize_kib));
+ return createEmptyFilePool(fqEfpDirectoryName);
+}
+
+EmptyFilePool* EmptyFilePoolPartition::createEmptyFilePool(const std::string fqEfpDirectoryName) {
+ EmptyFilePool* efpp = 0;
+ try {
+ efpp = new EmptyFilePool(fqEfpDirectoryName, this, overwriteBeforeReturnFlag_, truncateFlag_, journalLogRef_);
+ {
+ slock l(efpMapMutex_);
+ efpMap_[efpp->dataSize_kib()] = efpp;
+ }
+ }
+ catch (const std::exception& e) {
+ if (efpp != 0) {
+ delete efpp;
+ efpp = 0;
+ }
+ std::ostringstream oss;
+ oss << "EmptyFilePool create failed: " << e.what();
+ journalLogRef_.log(JournalLog::LOG_WARN, oss.str());
+ }
+ if (efpp != 0) {
+ efpp->initialize();
+ }
+ return efpp;
+}
+
+void EmptyFilePoolPartition::validatePartitionDir() {
+ std::ostringstream ss;
+ if (!jdir::is_dir(partitionDir_)) {
+ ss << "Invalid partition directory: \'" << partitionDir_ << "\' is not a directory";
+ throw jexception(jerrno::JERR_EFP_BADPARTITIONDIR, ss.str(), "EmptyFilePoolPartition", "validatePartitionDir");
+ }
+
+ // TODO: other validity checks here
+}
+
+}}}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/EmptyFilePoolPartition.h b/qpid/cpp/src/qpid/linearstore/journal/EmptyFilePoolPartition.h
new file mode 100644
index 0000000000..570e2b073f
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/EmptyFilePoolPartition.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_EMPTYFILEPOOLPARTITION_H_
+#define QPID_LINEARSTORE_JOURNAL_EMPTYFILEPOOLPARTITION_H_
+
+#include <map>
+#include "qpid/linearstore/journal/EmptyFilePoolTypes.h"
+#include "qpid/linearstore/journal/smutex.h"
+#include <string>
+#include <vector>
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+class EmptyFilePool;
+class JournalLog;
+
+class EmptyFilePoolPartition
+{
+public:
+ static const std::string s_efpTopLevelDir_;
+protected:
+ typedef std::map<efpDataSize_kib_t, EmptyFilePool*> efpMap_t;
+ typedef efpMap_t::iterator efpMapItr_t;
+ typedef efpMap_t::const_iterator efpMapConstItr_t;
+
+ const efpPartitionNumber_t partitionNum_;
+ const std::string partitionDir_;
+ const bool overwriteBeforeReturnFlag_;
+ const bool truncateFlag_;
+ JournalLog& journalLogRef_;
+ efpMap_t efpMap_;
+ smutex efpMapMutex_;
+
+public:
+ EmptyFilePoolPartition(const efpPartitionNumber_t partitionNum,
+ const std::string& partitionDir,
+ const bool overwriteBeforeReturnFlag,
+ const bool truncateFlag,
+ JournalLog& journalLogRef);
+ virtual ~EmptyFilePoolPartition();
+
+ void findEmptyFilePools();
+ EmptyFilePool* getEmptyFilePool(const efpDataSize_kib_t efpDataSize_kib, const bool createIfNonExistent);
+ void getEmptyFilePools(std::vector<EmptyFilePool*>& efpList);
+ void getEmptyFilePoolSizes_kib(std::vector<efpDataSize_kib_t>& efpDataSizesList) const;
+ std::string getPartitionDirectory() const;
+ efpPartitionNumber_t getPartitionNumber() const;
+ std::string toString(const uint16_t indent) const;
+
+ static std::string getPartionDirectoryName(const efpPartitionNumber_t partitionNumber);
+ static efpPartitionNumber_t getPartitionNumber(const std::string& name);
+
+protected:
+ EmptyFilePool* createEmptyFilePool(const efpDataSize_kib_t efpDataSize_kib);
+ EmptyFilePool* createEmptyFilePool(const std::string fqEfpDirectoryName);
+ void validatePartitionDir();
+};
+
+}}}
+
+#endif /* QPID_LINEARSTORE_JOURNAL_EMPTYFILEPOOLPARTITION_H_ */
diff --git a/qpid/cpp/src/qpid/linearstore/journal/EmptyFilePoolTypes.h b/qpid/cpp/src/qpid/linearstore/journal/EmptyFilePoolTypes.h
new file mode 100644
index 0000000000..4cae4e6538
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/EmptyFilePoolTypes.h
@@ -0,0 +1,57 @@
+ /*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_EMPTYFILEPOOLTYPES_H_
+#define QPID_LINEARSTORE_JOURNAL_EMPTYFILEPOOLTYPES_H_
+
+#include <iostream>
+#include <sstream>
+#include <stdint.h>
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+typedef uint64_t efpDataSize_kib_t; ///< Size of data part of file (excluding file header) in kib
+typedef uint64_t efpFileSize_kib_t; ///< Size of file (header + data) in kib
+typedef uint32_t efpDataSize_sblks_t; ///< Size of data part of file (excluding file header) in sblks
+typedef uint32_t efpFileSize_sblks_t; ///< Size of file (header + data) in sblks
+typedef uint32_t efpFileCount_t; ///< Number of files in a partition or pool
+typedef uint16_t efpPartitionNumber_t; ///< Number assigned to a partition
+
+typedef struct efpIdentity_t {
+ efpPartitionNumber_t pn_;
+ efpDataSize_kib_t ds_;
+ efpIdentity_t() : pn_(0), ds_(0) {}
+ efpIdentity_t(efpPartitionNumber_t pn, efpDataSize_kib_t ds) : pn_(pn), ds_(ds) {}
+ efpIdentity_t(const efpIdentity_t& ei) : pn_(ei.pn_), ds_(ei.ds_) {}
+ friend std::ostream& operator<<(std::ostream& os, const efpIdentity_t& id) {
+ // This two-stage write allows this << operator to be used with std::setw() for formatted writes
+ std::ostringstream oss;
+ oss << id.pn_ << "," << id.ds_;
+ os << oss.str();
+ return os;
+ }
+} efpIdentity_t;
+
+}}}
+
+#endif /* QPID_LINEARSTORE_JOURNAL_EMPTYFILEPOOLTYPES_H_ */
diff --git a/qpid/cpp/src/qpid/linearstore/journal/JournalFile.cpp b/qpid/cpp/src/qpid/linearstore/journal/JournalFile.cpp
new file mode 100644
index 0000000000..ed03a8413f
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/JournalFile.cpp
@@ -0,0 +1,349 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/linearstore/journal/JournalFile.h"
+
+#include <fcntl.h>
+#include "qpid/linearstore/journal/jcfg.h"
+#include "qpid/linearstore/journal/pmgr.h"
+#include "qpid/linearstore/journal/utils/file_hdr.h"
+#include <unistd.h>
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+JournalFile::JournalFile(const std::string& fqFileName,
+ const efpIdentity_t& efpIdentity,
+ const uint64_t fileSeqNum,
+ const std::string queueName) :
+ efpIdentity_(efpIdentity),
+ fqFileName_(fqFileName),
+ fileSeqNum_(fileSeqNum),
+ queueName_(queueName),
+ serial_(getRandom64()),
+ firstRecordOffset_(0ULL),
+ fileHandle_(-1),
+ fileCloseFlag_(false),
+ fileHeaderBasePtr_ (0),
+ fileHeaderPtr_(0),
+ aioControlBlockPtr_(0),
+ fileSize_dblks_(((efpIdentity.ds_ * 1024) + (QLS_JRNL_FHDR_RES_SIZE_SBLKS * QLS_SBLK_SIZE_BYTES)) / QLS_DBLK_SIZE_BYTES),
+ initializedFlag_(false),
+ enqueuedRecordCount_("JournalFile::enqueuedRecordCount", 0),
+ submittedDblkCount_("JournalFile::submittedDblkCount", 0),
+ completedDblkCount_("JournalFile::completedDblkCount", 0),
+ outstandingAioOpsCount_("JournalFile::outstandingAioOpsCount", 0)
+{}
+
+JournalFile::JournalFile(const std::string& fqFileName,
+ const ::file_hdr_t& fileHeader,
+ const std::string queueName) :
+ efpIdentity_(fileHeader._efp_partition, fileHeader._data_size_kib),
+ fqFileName_(fqFileName),
+ fileSeqNum_(fileHeader._file_number),
+ queueName_(queueName),
+ serial_(fileHeader._rhdr._serial),
+ firstRecordOffset_(fileHeader._fro),
+ fileHandle_(-1),
+ fileCloseFlag_(false),
+ fileHeaderBasePtr_ (0),
+ fileHeaderPtr_(0),
+ aioControlBlockPtr_(0),
+ fileSize_dblks_(((fileHeader._data_size_kib * 1024) + (QLS_JRNL_FHDR_RES_SIZE_SBLKS * QLS_SBLK_SIZE_BYTES)) / QLS_DBLK_SIZE_BYTES),
+ initializedFlag_(false),
+ enqueuedRecordCount_("JournalFile::enqueuedRecordCount", 0),
+ submittedDblkCount_("JournalFile::submittedDblkCount", 0),
+ completedDblkCount_("JournalFile::completedDblkCount", 0),
+ outstandingAioOpsCount_("JournalFile::outstandingAioOpsCount", 0)
+{}
+
+JournalFile::~JournalFile() {
+ finalize();
+}
+
+void
+JournalFile::initialize(const uint32_t completedDblkCount) {
+ if (!initializedFlag_) {
+ if (::posix_memalign(&fileHeaderBasePtr_, QLS_AIO_ALIGN_BOUNDARY_BYTES, QLS_JRNL_FHDR_RES_SIZE_SBLKS * QLS_SBLK_SIZE_KIB * 1024))
+ {
+ std::ostringstream oss;
+ oss << "posix_memalign(): blksize=" << QLS_AIO_ALIGN_BOUNDARY_BYTES << " size=" << (QLS_JRNL_FHDR_RES_SIZE_SBLKS * QLS_SBLK_SIZE_KIB * 1024);
+ oss << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR__MALLOC, oss.str(), "JournalFile", "initialize");
+ }
+ fileHeaderPtr_ = (::file_hdr_t*)fileHeaderBasePtr_;
+ aioControlBlockPtr_ = new aio_cb;
+ initializedFlag_ = true;
+ }
+ if (completedDblkCount > 0UL) {
+ submittedDblkCount_.set(completedDblkCount);
+ completedDblkCount_.set(completedDblkCount);
+ }
+}
+
+void
+JournalFile::finalize() {
+ if (fileHeaderBasePtr_ != 0) {
+ std::free(fileHeaderBasePtr_);
+ fileHeaderBasePtr_ = 0;
+ fileHeaderPtr_ = 0;
+ }
+ if (aioControlBlockPtr_ != 0) {
+ delete(aioControlBlockPtr_);
+ aioControlBlockPtr_ = 0;
+ }
+}
+
+const std::string JournalFile::getFqFileName() const {
+ return fqFileName_;
+}
+
+uint64_t JournalFile::getFileSeqNum() const {
+ return fileSeqNum_;
+}
+
+uint64_t JournalFile::getSerial() const {
+ return serial_;
+}
+
+int JournalFile::open() {
+ fileHandle_ = ::open(fqFileName_.c_str(), O_WRONLY | O_DIRECT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); // 0644 -rw-r--r--
+ if (fileHandle_ < 0) {
+ std::ostringstream oss;
+ oss << "file=\"" << fqFileName_ << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JNLF_OPEN, oss.str(), "JournalFile", "open");
+ }
+ return fileHandle_;
+}
+
+void JournalFile::close() {
+ if (fileHandle_ >= 0) {
+ if (getOutstandingAioDblks()) {
+ fileCloseFlag_ = true; // Close later when all outstanding AIOs have returned
+ } else {
+ int res = ::close(fileHandle_);
+ fileHandle_ = -1;
+ if (res != 0) {
+ std::ostringstream oss;
+ oss << "file=\"" << fqFileName_ << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JNLF_CLOSE, oss.str(), "JournalFile", "open");
+ }
+ }
+ }
+}
+
+void JournalFile::asyncFileHeaderWrite(io_context_t ioContextPtr,
+ const efpPartitionNumber_t efpPartitionNumber,
+ const efpDataSize_kib_t efpDataSize_kib,
+ const uint16_t userFlags,
+ const uint64_t recordId,
+ const uint64_t firstRecordOffset) {
+ firstRecordOffset_ = firstRecordOffset;
+ ::file_hdr_create(fileHeaderPtr_, QLS_FILE_MAGIC, QLS_JRNL_VERSION, QLS_JRNL_FHDR_RES_SIZE_SBLKS, efpPartitionNumber, efpDataSize_kib);
+ ::file_hdr_init(fileHeaderBasePtr_,
+ QLS_JRNL_FHDR_RES_SIZE_SBLKS * QLS_SBLK_SIZE_KIB * 1024,
+ userFlags,
+ serial_,
+ recordId,
+ firstRecordOffset,
+ fileSeqNum_,
+ queueName_.size(),
+ queueName_.data());
+ const std::size_t wr_size = QLS_JRNL_FHDR_RES_SIZE_SBLKS * QLS_SBLK_SIZE_KIB * 1024;
+ if (!isOpen()) open();
+ aio::prep_pwrite(aioControlBlockPtr_, fileHandle_, (void*)fileHeaderBasePtr_, wr_size, 0UL);
+ if (!aio::is_aligned(aioControlBlockPtr_->u.c.buf, QLS_AIO_ALIGN_BOUNDARY_BYTES)) {
+ std::ostringstream oss;
+ oss << "AIO operation on misaligned buffer: iocb->u.c.buf=" << aioControlBlockPtr_->u.c.buf << std::endl;
+ throw jexception(jerrno::JERR__AIO, oss.str(), "JournalFile", "asyncFileHeaderWrite");
+ }
+ if (aio::submit(ioContextPtr, 1, &aioControlBlockPtr_) < 0) {
+ std::ostringstream oss;
+ oss << "queue=\"" << queueName_ << "\" fid=0x" << std::hex << fileSeqNum_ << " wr_size=0x" << wr_size << " foffs=0x0";
+ throw jexception(jerrno::JERR__AIO, oss.str(), "JournalFile", "asyncFileHeaderWrite");
+ }
+ addSubmittedDblkCount(QLS_JRNL_FHDR_RES_SIZE_SBLKS * QLS_SBLK_SIZE_DBLKS);
+ incrOutstandingAioOperationCount();
+}
+
+void JournalFile::asyncPageWrite(io_context_t ioContextPtr,
+ aio_cb* aioControlBlockPtr,
+ void* data,
+ uint32_t dataSize_dblks) {
+ const std::size_t wr_size = dataSize_dblks * QLS_DBLK_SIZE_BYTES;
+ const uint64_t foffs = submittedDblkCount_.get() * QLS_DBLK_SIZE_BYTES;
+ if (!isOpen()) open();
+ aio::prep_pwrite_2(aioControlBlockPtr, fileHandle_, data, wr_size, foffs);
+ if (!aio::is_aligned(aioControlBlockPtr->u.c.buf, QLS_AIO_ALIGN_BOUNDARY_BYTES)) {
+ std::ostringstream oss;
+ oss << "AIO operation on misaligned buffer: iocb->u.c.buf=" << aioControlBlockPtr->u.c.buf << std::endl;
+ throw jexception(jerrno::JERR__AIO, oss.str(), "JournalFile", "asyncPageWrite");
+ }
+ pmgr::page_cb* pcbp = (pmgr::page_cb*)(aioControlBlockPtr->data); // This page's control block (pcb)
+ pcbp->_wdblks = dataSize_dblks;
+ pcbp->_jfp = this;
+ if (aio::submit(ioContextPtr, 1, &aioControlBlockPtr) < 0) {
+ std::ostringstream oss;
+ oss << "queue=\"" << queueName_ << "\" fid=0x" << std::hex << fileSeqNum_ << " wr_size=0x" << wr_size << " foffs=0x" << foffs;
+ throw jexception(jerrno::JERR__AIO, oss.str(), "JournalFile", "asyncPageWrite");
+ }
+ addSubmittedDblkCount(dataSize_dblks);
+ incrOutstandingAioOperationCount();
+}
+
+uint32_t JournalFile::getEnqueuedRecordCount() const {
+ return enqueuedRecordCount_.get();
+}
+
+uint32_t JournalFile::incrEnqueuedRecordCount() {
+ return enqueuedRecordCount_.increment();
+}
+
+uint32_t JournalFile::decrEnqueuedRecordCount() {
+ return enqueuedRecordCount_.decrementLimit();
+}
+
+uint32_t JournalFile::addCompletedDblkCount(const uint32_t a) {
+ return completedDblkCount_.addLimit(a, submittedDblkCount_.get(), jerrno::JERR_JNLF_CMPLOFFSOVFL);
+}
+
+uint16_t JournalFile::getOutstandingAioOperationCount() const {
+ return outstandingAioOpsCount_.get();
+}
+
+uint16_t JournalFile::decrOutstandingAioOperationCount() {
+ uint16_t r = outstandingAioOpsCount_.decrementLimit();
+ if (fileCloseFlag_ && outstandingAioOpsCount_ == 0) { // Delayed close
+ close();
+ }
+ return r;
+}
+
+efpIdentity_t JournalFile::getEfpIdentity() const {
+ return efpIdentity_;
+}
+
+uint64_t JournalFile::getFirstRecordOffset() const {
+ return firstRecordOffset_;
+}
+
+void JournalFile::setFirstRecordOffset(const uint64_t firstRecordOffset) {
+ firstRecordOffset_ = firstRecordOffset;
+}
+
+// --- Status helper functions ---
+
+bool JournalFile::isEmpty() const {
+ return submittedDblkCount_ == 0;
+}
+
+bool JournalFile::isNoEnqueuedRecordsRemaining() const {
+ return /*!enqueueStarted_ &&*/ // Not part-way through encoding an enqueue
+ isFullAndComplete() && // Full with all AIO returned
+ enqueuedRecordCount_ == 0; // No remaining enqueued records
+}
+
+// debug aid
+const std::string JournalFile::status_str(const uint8_t indentDepth) const {
+ std::string indent((size_t)indentDepth, '.');
+ std::ostringstream oss;
+ oss << indent << "JournalFile: fileName=" << getFileName() << std::endl;
+ oss << indent << " directory=" << getDirectory() << std::endl;
+ oss << indent << " fileSizeDblks=" << fileSize_dblks_ << std::endl;
+ oss << indent << " open=" << (isOpen() ? "T" : "F") << std::endl;
+ oss << indent << " fileHandle=" << fileHandle_ << std::endl;
+ oss << indent << " enqueuedRecordCount=" << getEnqueuedRecordCount() << std::endl;
+ oss << indent << " submittedDblkCount=" << getSubmittedDblkCount() << std::endl;
+ oss << indent << " completedDblkCount=" << getCompletedDblkCount() << std::endl;
+ oss << indent << " outstandingAioOpsCount=" << getOutstandingAioOperationCount() << std::endl;
+ oss << indent << " isEmpty()=" << (isEmpty() ? "T" : "F") << std::endl;
+ oss << indent << " isDataEmpty()=" << (isDataEmpty() ? "T" : "F") << std::endl;
+ oss << indent << " dblksRemaining()=" << dblksRemaining() << std::endl;
+ oss << indent << " isFull()=" << (isFull() ? "T" : "F") << std::endl;
+ oss << indent << " isFullAndComplete()=" << (isFullAndComplete() ? "T" : "F") << std::endl;
+ oss << indent << " getOutstandingAioDblks()=" << getOutstandingAioDblks() << std::endl;
+ oss << indent << " getNextFile()=" << (getNextFile() ? "T" : "F") << std::endl;
+ return oss.str();
+}
+
+// --- protected functions ---
+
+const std::string JournalFile::getDirectory() const {
+ return fqFileName_.substr(0, fqFileName_.rfind('/'));
+}
+
+const std::string JournalFile::getFileName() const {
+ return fqFileName_.substr(fqFileName_.rfind('/')+1);
+}
+
+//static
+uint64_t JournalFile::getRandom64() {
+ // TODO: ::rand() is not thread safe, either lock or use rand_r(seed) with a thread-local seed.
+ return ((uint64_t)::rand() << QLS_RAND_SHIFT1) | ((uint64_t)::rand() << QLS_RAND_SHIFT2) | (::rand() & QLS_RAND_MASK);
+}
+
+bool JournalFile::isOpen() const {
+ return fileHandle_ >= 0;
+}
+
+uint32_t JournalFile::getSubmittedDblkCount() const {
+ return submittedDblkCount_.get();
+}
+
+uint32_t JournalFile::addSubmittedDblkCount(const uint32_t a) {
+ return submittedDblkCount_.addLimit(a, fileSize_dblks_, jerrno::JERR_JNLF_FILEOFFSOVFL);
+}
+
+uint32_t JournalFile::getCompletedDblkCount() const {
+ return completedDblkCount_.get();
+}
+
+uint16_t JournalFile::incrOutstandingAioOperationCount() {
+ return outstandingAioOpsCount_.increment();
+}
+
+u_int32_t JournalFile::dblksRemaining() const {
+ return fileSize_dblks_ - submittedDblkCount_;
+}
+
+bool JournalFile::getNextFile() const {
+ return isFull();
+}
+
+u_int32_t JournalFile::getOutstandingAioDblks() const {
+ return submittedDblkCount_ - completedDblkCount_;
+}
+
+bool JournalFile::isDataEmpty() const {
+ return submittedDblkCount_ <= QLS_JRNL_FHDR_RES_SIZE_SBLKS * QLS_SBLK_SIZE_DBLKS;
+}
+
+bool JournalFile::isFull() const {
+ return submittedDblkCount_ == fileSize_dblks_;
+}
+
+bool JournalFile::isFullAndComplete() const {
+ return completedDblkCount_ == fileSize_dblks_;
+}
+
+
+}}}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/JournalFile.h b/qpid/cpp/src/qpid/linearstore/journal/JournalFile.h
new file mode 100644
index 0000000000..e33830ef7f
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/JournalFile.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_JOURNALFILE_H_
+#define QPID_LINEARSTORE_JOURNAL_JOURNALFILE_H_
+
+#include "qpid/linearstore/journal/aio.h"
+#include "qpid/linearstore/journal/AtomicCounter.h"
+#include "qpid/linearstore/journal/EmptyFilePoolTypes.h"
+
+class file_hdr_t;
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+class JournalFile
+{
+protected:
+ const efpIdentity_t efpIdentity_;
+ const std::string fqFileName_;
+ const uint64_t fileSeqNum_;
+ const std::string queueName_;
+ const uint64_t serial_;
+ uint64_t firstRecordOffset_;
+ int fileHandle_;
+ bool fileCloseFlag_;
+ void* fileHeaderBasePtr_;
+ ::file_hdr_t* fileHeaderPtr_;
+ aio_cb* aioControlBlockPtr_;
+ uint32_t fileSize_dblks_; ///< File size in data blocks, including file header
+ bool initializedFlag_;
+
+ AtomicCounter<uint32_t> enqueuedRecordCount_; ///< Count of enqueued records
+ AtomicCounter<uint32_t> submittedDblkCount_; ///< Write file count (data blocks) for submitted AIO
+ AtomicCounter<uint32_t> completedDblkCount_; ///< Write file count (data blocks) for completed AIO
+ AtomicCounter<uint16_t> outstandingAioOpsCount_; ///< Outstanding AIO operations on this file
+
+public:
+ // Constructor for creating new file with known fileSeqNum and random serial
+ JournalFile(const std::string& fqFileName,
+ const efpIdentity_t& efpIdentity,
+ const uint64_t fileSeqNum,
+ const std::string queueName);
+ // Constructor for recovery in which fileSeqNum and serial are recovered from fileHeader param
+ JournalFile(const std::string& fqFileName,
+ const ::file_hdr_t& fileHeader,
+ const std::string queueName);
+ virtual ~JournalFile();
+
+ void initialize(const uint32_t completedDblkCount);
+ void finalize();
+
+ const std::string getFqFileName() const;
+ uint64_t getFileSeqNum() const;
+ uint64_t getSerial() const;
+
+ int open();
+ void close();
+ void asyncFileHeaderWrite(io_context_t ioContextPtr,
+ const efpPartitionNumber_t efpPartitionNumber,
+ const efpDataSize_kib_t efpDataSize_kib,
+ const uint16_t userFlags,
+ const uint64_t recordId,
+ const uint64_t firstRecordOffset);
+ void asyncPageWrite(io_context_t ioContextPtr,
+ aio_cb* aioControlBlockPtr,
+ void* data,
+ uint32_t dataSize_dblks);
+
+ uint32_t getSubmittedDblkCount() const;
+ uint32_t getEnqueuedRecordCount() const;
+ uint32_t incrEnqueuedRecordCount();
+ uint32_t decrEnqueuedRecordCount();
+
+ uint32_t addCompletedDblkCount(const uint32_t a);
+
+ uint16_t getOutstandingAioOperationCount() const;
+ uint16_t decrOutstandingAioOperationCount();
+
+ efpIdentity_t getEfpIdentity() const;
+ uint64_t getFirstRecordOffset() const;
+ void setFirstRecordOffset(const uint64_t firstRecordOffset);
+
+ // Status helper functions
+ bool isEmpty() const; ///< True if no writes of any kind have occurred
+ bool isNoEnqueuedRecordsRemaining() const; ///< True when all enqueued records (or parts) have been dequeued
+
+ // debug aid
+ const std::string status_str(const uint8_t indentDepth) const;
+
+protected:
+ const std::string getDirectory() const;
+ const std::string getFileName() const;
+ static uint64_t getRandom64();
+ bool isOpen() const;
+
+ uint32_t addSubmittedDblkCount(const uint32_t a);
+
+ uint32_t getCompletedDblkCount() const;
+
+ uint16_t incrOutstandingAioOperationCount();
+
+ u_int32_t dblksRemaining() const; ///< Dblks remaining until full
+ bool getNextFile() const; ///< True when next file is needed
+ u_int32_t getOutstandingAioDblks() const; ///< Dblks still to be written
+ bool isDataEmpty() const; ///< True if only file header written, data is still empty
+ bool isFull() const; ///< True if all possible dblks have been submitted (but may not yet have returned from AIO)
+ bool isFullAndComplete() const; ///< True if all submitted dblks have returned from AIO
+};
+
+}}}
+
+#endif // QPID_LINEARSTORE_JOURNAL_JOURNALFILE_H_
diff --git a/qpid/cpp/src/qpid/linearstore/journal/JournalLog.cpp b/qpid/cpp/src/qpid/linearstore/journal/JournalLog.cpp
new file mode 100644
index 0000000000..c35ec97e91
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/JournalLog.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/linearstore/journal/JournalLog.h"
+
+#include <iostream>
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+JournalLog::JournalLog(log_level_t logLevelThreshold) : logLevelThreshold_(logLevelThreshold) {}
+
+JournalLog::~JournalLog() {}
+
+void JournalLog::log(const log_level_t logLevel,
+ const std::string& logStatement) const {
+ if (logLevel >= logLevelThreshold_) {
+ std::cerr << log_level_str(logLevel) << ": " << logStatement << std::endl;
+ }
+}
+
+void JournalLog::log(log_level_t logLevel,
+ const std::string& journalId,
+ const std::string& logStatement) const {
+ if (logLevel >= logLevelThreshold_) {
+ std::cerr << log_level_str(logLevel) << ": Journal \"" << journalId << "\": " << logStatement << std::endl;
+ }
+}
+
+const char* JournalLog::log_level_str(log_level_t logLevel) {
+ switch (logLevel)
+ {
+ case LOG_TRACE: return "TRACE";
+ case LOG_DEBUG: return "DEBUG";
+ case LOG_INFO: return "INFO";
+ case LOG_NOTICE: return "NOTICE";
+ case LOG_WARN: return "WARN";
+ case LOG_ERROR: return "ERROR";
+ case LOG_CRITICAL: return "CRITICAL";
+ }
+ return "<log level unknown>";
+}
+
+}}}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/JournalLog.h b/qpid/cpp/src/qpid/linearstore/journal/JournalLog.h
new file mode 100644
index 0000000000..cf503cb9d2
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/JournalLog.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_JOURNALLOG_H_
+#define QPID_LINEARSTORE_JOURNAL_JOURNALLOG_H_
+
+#include <string>
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+class JournalLog
+{
+public:
+ typedef enum _log_level {
+ LOG_TRACE = 0,
+ LOG_DEBUG,
+ LOG_INFO,
+ LOG_NOTICE,
+ LOG_WARN,
+ LOG_ERROR,
+ LOG_CRITICAL
+ } log_level_t;
+
+protected:
+ const log_level_t logLevelThreshold_;
+
+public:
+ JournalLog(log_level_t logLevelThreshold);
+ virtual ~JournalLog();
+ virtual void log(const log_level_t logLevel,
+ const std::string& logStatement) const;
+ virtual void log(const log_level_t logLevel,
+ const std::string& journalId,
+ const std::string& logStatement) const;
+ static const char* log_level_str(const log_level_t logLevel);
+};
+
+}}}
+
+#endif // QPID_LINEARSTORE_JOURNAL_JOURNALLOG_H_
diff --git a/qpid/cpp/src/qpid/linearstore/journal/LinearFileController.cpp b/qpid/cpp/src/qpid/linearstore/journal/LinearFileController.cpp
new file mode 100644
index 0000000000..08d565ca2e
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/LinearFileController.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/linearstore/journal/LinearFileController.h"
+
+#include "qpid/linearstore/journal/EmptyFilePool.h"
+#include "qpid/linearstore/journal/jcntl.h"
+#include "qpid/linearstore/journal/JournalFile.h"
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+LinearFileController::LinearFileController(jcntl& jcntlRef) :
+ jcntlRef_(jcntlRef),
+ emptyFilePoolPtr_(0),
+ fileSeqCounter_("LinearFileController::fileSeqCounter", 0),
+ recordIdCounter_("LinearFileController::recordIdCounter", 0),
+ decrCounter_("LinearFileController::decrCounter", 0),
+ currentJournalFilePtr_(0)
+{}
+
+LinearFileController::~LinearFileController() {}
+
+void LinearFileController::initialize(const std::string& journalDirectory,
+ EmptyFilePool* emptyFilePoolPtr,
+ uint64_t initialFileNumberVal) {
+ journalDirectory_.assign(journalDirectory);
+ emptyFilePoolPtr_ = emptyFilePoolPtr;
+ fileSeqCounter_.set(initialFileNumberVal);
+}
+
+void LinearFileController::finalize() {
+ if (currentJournalFilePtr_) {
+ currentJournalFilePtr_->close();
+ currentJournalFilePtr_ = 0;
+ }
+ while (!journalFileList_.empty()) {
+ delete journalFileList_.front();
+ journalFileList_.pop_front();
+ }
+}
+
+void LinearFileController::addJournalFile(JournalFile* journalFilePtr,
+ const uint32_t completedDblkCount,
+ const bool makeCurrentFlag) {
+ if (makeCurrentFlag && currentJournalFilePtr_) {
+ currentJournalFilePtr_->close();
+ currentJournalFilePtr_ = 0;
+ }
+ journalFilePtr->initialize(completedDblkCount);
+ {
+ slock l(journalFileListMutex_);
+ journalFileList_.push_back(journalFilePtr);
+ }
+ if (makeCurrentFlag) {
+ currentJournalFilePtr_ = journalFilePtr;
+ }
+}
+
+efpDataSize_sblks_t LinearFileController::dataSize_sblks() const {
+ return emptyFilePoolPtr_->dataSize_sblks();
+}
+
+efpFileSize_sblks_t LinearFileController::fileSize_sblks() const {
+ return emptyFilePoolPtr_->fileSize_sblks();
+}
+
+void LinearFileController::getNextJournalFile() {
+ if (currentJournalFilePtr_)
+ currentJournalFilePtr_->close();
+ pullEmptyFileFromEfp();
+}
+
+uint64_t LinearFileController::getNextRecordId() {
+ return recordIdCounter_.increment();
+}
+
+void LinearFileController::removeFileToEfp(const std::string& fileName) {
+ if (emptyFilePoolPtr_) {
+ emptyFilePoolPtr_->returnEmptyFileSymlink(fileName);
+ }
+}
+
+void LinearFileController::restoreEmptyFile(const std::string& fileName) {
+ // TODO: Add checks that this file is of a valid size; if not, delete this and get one from the EFP
+ addJournalFile(fileName, emptyFilePoolPtr_->getIdentity(), getNextFileSeqNum(), 0);
+}
+
+void LinearFileController::purgeEmptyFilesToEfp() {
+ slock l(journalFileListMutex_);
+ while (journalFileList_.front()->isNoEnqueuedRecordsRemaining() && journalFileList_.size() > 1) { // Can't purge last file, even if it has no enqueued records
+ emptyFilePoolPtr_->returnEmptyFileSymlink(journalFileList_.front()->getFqFileName());
+ delete journalFileList_.front();
+ journalFileList_.pop_front();
+ }
+}
+
+uint32_t LinearFileController::getEnqueuedRecordCount(const uint64_t fileSeqNumber) {
+ return find(fileSeqNumber)->getEnqueuedRecordCount();
+}
+
+uint32_t LinearFileController::incrEnqueuedRecordCount(const uint64_t fileSeqNumber) {
+ return find(fileSeqNumber)->incrEnqueuedRecordCount();
+}
+
+uint32_t LinearFileController::decrEnqueuedRecordCount(const uint64_t fileSeqNumber) {
+ uint32_t r = find(fileSeqNumber)->decrEnqueuedRecordCount();
+
+ // TODO: Re-evaluate after testing and profiling
+ // This is the first go at implementing auto-purge, which checks for all trailing empty files and recycles
+ // them back to the EFP. This version checks every 100 decrements using decrCounter_ (an action which releases
+ // records). We need to check this rather simple scheme works for outlying scenarios (large and tiny data
+ // records) without impacting performance or performing badly (leaving excessive empty files in the journals).
+ if (decrCounter_.increment() % 100ULL == 0ULL) {
+ purgeEmptyFilesToEfp();
+ }
+ return r;
+}
+
+uint32_t LinearFileController::addWriteCompletedDblkCount(const uint64_t fileSeqNumber, const uint32_t a) {
+ return find(fileSeqNumber)->addCompletedDblkCount(a);
+}
+
+uint16_t LinearFileController::decrOutstandingAioOperationCount(const uint64_t fileSeqNumber) {
+ return find(fileSeqNumber)->decrOutstandingAioOperationCount();
+}
+
+void LinearFileController::asyncFileHeaderWrite(io_context_t ioContextPtr,
+ const uint16_t userFlags,
+ const uint64_t recordId,
+ const uint64_t firstRecordOffset) {
+ currentJournalFilePtr_->asyncFileHeaderWrite(ioContextPtr,
+ emptyFilePoolPtr_->getPartitionNumber(),
+ emptyFilePoolPtr_->dataSize_kib(),
+ userFlags,
+ recordId,
+ firstRecordOffset);
+}
+
+void LinearFileController::asyncPageWrite(io_context_t ioContextPtr,
+ aio_cb* aioControlBlockPtr,
+ void* data,
+ uint32_t dataSize_dblks) {
+ assertCurrentJournalFileValid("asyncPageWrite");
+ currentJournalFilePtr_->asyncPageWrite(ioContextPtr, aioControlBlockPtr, data, dataSize_dblks);
+}
+
+uint64_t LinearFileController::getCurrentFileSeqNum() const {
+ assertCurrentJournalFileValid("getCurrentFileSeqNum");
+ return currentJournalFilePtr_->getFileSeqNum();
+}
+
+uint64_t LinearFileController::getCurrentSerial() const {
+ assertCurrentJournalFileValid("getCurrentSerial");
+ return currentJournalFilePtr_->getSerial();
+}
+
+bool LinearFileController::isEmpty() const {
+ assertCurrentJournalFileValid("isEmpty");
+ return currentJournalFilePtr_->isEmpty();
+}
+
+const std::string LinearFileController::status(const uint8_t indentDepth) const {
+ std::string indent((size_t)indentDepth, '.');
+ std::ostringstream oss;
+ oss << indent << "LinearFileController: queue=" << jcntlRef_.id() << std::endl;
+ oss << indent << " journalDirectory=" << journalDirectory_ << std::endl;
+ oss << indent << " fileSeqCounter=" << fileSeqCounter_.get() << std::endl;
+ oss << indent << " recordIdCounter=" << recordIdCounter_.get() << std::endl;
+ oss << indent << " journalFileList.size=" << journalFileList_.size() << std::endl;
+ if (checkCurrentJournalFileValid()) {
+ oss << currentJournalFilePtr_->status_str(indentDepth+2);
+ } else {
+ oss << indent << " <No current journal file>" << std::endl;
+ }
+ return oss.str();
+}
+
+// --- protected functions ---
+
+void LinearFileController::addJournalFile(const std::string& fileName,
+ const efpIdentity_t& efpIdentity,
+ const uint64_t fileSeqNumber,
+ const uint32_t completedDblkCount) {
+ JournalFile* jfp = new JournalFile(fileName, efpIdentity, fileSeqNumber, jcntlRef_.id());
+ addJournalFile(jfp, completedDblkCount, true);
+}
+
+void LinearFileController::assertCurrentJournalFileValid(const char* const functionName) const {
+ if (!checkCurrentJournalFileValid()) {
+ throw jexception(jerrno::JERR__NULL, "LinearFileController", functionName);
+ }
+}
+
+bool LinearFileController::checkCurrentJournalFileValid() const {
+ return currentJournalFilePtr_ != 0;
+}
+
+JournalFile* LinearFileController::find(const uint64_t fileSeqNumber) {
+ if (currentJournalFilePtr_ && currentJournalFilePtr_->getFileSeqNum() == fileSeqNumber)
+ return currentJournalFilePtr_;
+
+ slock l(journalFileListMutex_);
+ for (JournalFileListItr_t i=journalFileList_.begin(); i!=journalFileList_.end(); ++i) {
+ if ((*i)->getFileSeqNum() == fileSeqNumber) {
+ return *i;
+ }
+ }
+
+ std::ostringstream oss;
+ oss << "fileSeqNumber=" << fileSeqNumber;
+ throw jexception(jerrno::JERR_LFCR_SEQNUMNOTFOUND, oss.str(), "LinearFileController", "find");
+}
+
+uint64_t LinearFileController::getNextFileSeqNum() {
+ return fileSeqCounter_.increment();
+}
+
+void LinearFileController::pullEmptyFileFromEfp() {
+ std::string efn = emptyFilePoolPtr_->takeEmptyFile(journalDirectory_); // Moves file from EFP only (ie no file init), returns new file name
+ addJournalFile(efn, emptyFilePoolPtr_->getIdentity(), getNextFileSeqNum(), 0);
+}
+
+}}}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/LinearFileController.h b/qpid/cpp/src/qpid/linearstore/journal/LinearFileController.h
new file mode 100644
index 0000000000..3cdfb72a37
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/LinearFileController.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_LINEARFILECONTROLLER_H_
+#define QPID_LINEARSTORE_JOURNAL_LINEARFILECONTROLLER_H_
+
+#include <deque>
+#include "qpid/linearstore/journal/aio.h"
+#include "qpid/linearstore/journal/AtomicCounter.h"
+#include "qpid/linearstore/journal/EmptyFilePoolTypes.h"
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+class EmptyFilePool;
+class jcntl;
+class JournalFile;
+
+class LinearFileController
+{
+protected:
+ typedef std::deque<JournalFile*> JournalFileList_t;
+ typedef JournalFileList_t::iterator JournalFileListItr_t;
+
+ jcntl& jcntlRef_;
+ std::string journalDirectory_;
+ EmptyFilePool* emptyFilePoolPtr_;
+ AtomicCounter<uint64_t> fileSeqCounter_;
+ AtomicCounter<uint64_t> recordIdCounter_;
+ AtomicCounter<uint64_t> decrCounter_;
+
+ JournalFileList_t journalFileList_;
+ JournalFile* currentJournalFilePtr_;
+ smutex journalFileListMutex_;
+
+public:
+ LinearFileController(jcntl& jcntlRef);
+ virtual ~LinearFileController();
+
+ void initialize(const std::string& journalDirectory,
+ EmptyFilePool* emptyFilePoolPtr,
+ uint64_t initialFileNumberVal);
+ void finalize();
+
+ void addJournalFile(JournalFile* journalFilePtr,
+ const uint32_t completedDblkCount,
+ const bool makeCurrentFlag);
+
+ efpDataSize_sblks_t dataSize_sblks() const;
+ efpFileSize_sblks_t fileSize_sblks() const;
+ void getNextJournalFile();
+ uint64_t getNextRecordId();
+ void removeFileToEfp(const std::string& fileName);
+ void restoreEmptyFile(const std::string& fileName);
+ void purgeEmptyFilesToEfp();
+
+ // Functions for manipulating counts of non-current JournalFile instances in journalFileList_
+ uint32_t getEnqueuedRecordCount(const uint64_t fileSeqNumber);
+ uint32_t incrEnqueuedRecordCount(const uint64_t fileSeqNumber);
+ uint32_t decrEnqueuedRecordCount(const uint64_t fileSeqNumber);
+ uint32_t addWriteCompletedDblkCount(const uint64_t fileSeqNumber,
+ const uint32_t a);
+ uint16_t decrOutstandingAioOperationCount(const uint64_t fileSeqNumber);
+
+ // Pass-through functions for current JournalFile class
+ void asyncFileHeaderWrite(io_context_t ioContextPtr,
+ const uint16_t userFlags,
+ const uint64_t recordId,
+ const uint64_t firstRecordOffset);
+ void asyncPageWrite(io_context_t ioContextPtr,
+ aio_cb* aioControlBlockPtr,
+ void* data,
+ uint32_t dataSize_dblks);
+
+ uint64_t getCurrentFileSeqNum() const;
+ uint64_t getCurrentSerial() const;
+ bool isEmpty() const;
+
+ // Debug aid
+ const std::string status(const uint8_t indentDepth) const;
+
+protected:
+ void addJournalFile(const std::string& fileName,
+ const efpIdentity_t& efpIdentity,
+ const uint64_t fileSeqNumber,
+ const uint32_t completedDblkCount);
+ void assertCurrentJournalFileValid(const char* const functionName) const;
+ bool checkCurrentJournalFileValid() const;
+ JournalFile* find(const uint64_t fileSeqNumber);
+ uint64_t getNextFileSeqNum();
+ void pullEmptyFileFromEfp();
+};
+
+typedef void (LinearFileController::*lfcAddJournalFileFn)(JournalFile* journalFilePtr,
+ const uint32_t completedDblkCount,
+ const bool makeCurrentFlag);
+
+}}}
+
+#endif // QPID_LINEARSTORE_JOURNAL_LINEARFILECONTROLLER_H_
diff --git a/qpid/cpp/src/qpid/linearstore/journal/RecoveryManager.cpp b/qpid/cpp/src/qpid/linearstore/journal/RecoveryManager.cpp
new file mode 100644
index 0000000000..254566e824
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/RecoveryManager.cpp
@@ -0,0 +1,949 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/linearstore/journal/RecoveryManager.h"
+
+#include <algorithm>
+#include <cstdlib>
+#include <iomanip>
+#include "qpid/linearstore/journal/Checksum.h"
+#include "qpid/linearstore/journal/data_tok.h"
+#include "qpid/linearstore/journal/deq_rec.h"
+#include "qpid/linearstore/journal/EmptyFilePool.h"
+#include "qpid/linearstore/journal/EmptyFilePoolManager.h"
+#include "qpid/linearstore/journal/enq_map.h"
+#include "qpid/linearstore/journal/enq_rec.h"
+#include "qpid/linearstore/journal/jcfg.h"
+#include "qpid/linearstore/journal/jdir.h"
+#include "qpid/linearstore/journal/JournalFile.h"
+#include "qpid/linearstore/journal/JournalLog.h"
+#include "qpid/linearstore/journal/jrec.h"
+#include "qpid/linearstore/journal/LinearFileController.h"
+#include "qpid/linearstore/journal/txn_map.h"
+#include "qpid/linearstore/journal/txn_rec.h"
+#include "qpid/linearstore/journal/utils/enq_hdr.h"
+#include "qpid/linearstore/journal/utils/file_hdr.h"
+#include <sstream>
+#include <string>
+#include <unistd.h>
+#include <vector>
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+RecoveredRecordData_t::RecoveredRecordData_t(const uint64_t rid, const uint64_t fid, const std::streampos foffs, bool ptxn) :
+ recordId_(rid),
+ fileId_(fid),
+ fileOffset_(foffs),
+ pendingTransaction_(ptxn)
+{}
+
+bool recordIdListCompare(RecoveredRecordData_t a, RecoveredRecordData_t b) {
+ return a.recordId_ < b.recordId_;
+}
+
+RecoveredFileData_t::RecoveredFileData_t(JournalFile* journalFilePtr, const uint32_t completedDblkCount) :
+ journalFilePtr_(journalFilePtr),
+ completedDblkCount_(completedDblkCount)
+{}
+
+RecoveryManager::RecoveryManager(const std::string& journalDirectory,
+ const std::string& queuename,
+ enq_map& enqueueMapRef,
+ txn_map& transactionMapRef,
+ JournalLog& journalLogRef) :
+ journalDirectory_(journalDirectory),
+ queueName_(queuename),
+ enqueueMapRef_(enqueueMapRef),
+ transactionMapRef_(transactionMapRef),
+ journalLogRef_(journalLogRef),
+ journalEmptyFlag_(false),
+ firstRecordOffset_(0),
+ endOffset_(0),
+ highestRecordId_(0ULL),
+ highestFileNumber_(0ULL),
+ lastFileFullFlag_(false),
+ initial_fid_(0),
+ currentSerial_(0),
+ efpFileSize_kib_(0)
+{}
+
+RecoveryManager::~RecoveryManager() {
+ for (fileNumberMapItr_t i = fileNumberMap_.begin(); i != fileNumberMap_.end(); ++i) {
+ delete i->second;
+ }
+ fileNumberMap_.clear();
+}
+
+void RecoveryManager::analyzeJournals(const std::vector<std::string>* preparedTransactionListPtr,
+ EmptyFilePoolManager* emptyFilePoolManager,
+ EmptyFilePool** emptyFilePoolPtrPtr) {
+ // Analyze file headers of existing journal files
+ efpIdentity_t efpIdentity;
+ analyzeJournalFileHeaders(efpIdentity);
+
+ if (journalEmptyFlag_) {
+ if (uninitFileList_.empty()) {
+ *emptyFilePoolPtrPtr = emptyFilePoolManager->getEmptyFilePool(0, 0); // Use default EFP
+ } else {
+ *emptyFilePoolPtrPtr = emptyFilePoolManager->getEmptyFilePool(efpIdentity);
+ }
+ } else {
+ *emptyFilePoolPtrPtr = emptyFilePoolManager->getEmptyFilePool(efpIdentity);
+ if (! *emptyFilePoolPtrPtr) {
+ // TODO: At a later time, this could be used to establish a new pool size provided the partition exists.
+ // If the partition does not exist, this is always an error. For now, throw an exception, as this should
+ // not occur in any practical application. Once multiple partitions and mixed EFPs are supported, this
+ // needs to be resolved. Note that EFP size is always a multiple of QLS_SBLK_SIZE_BYTES (currently 4096
+ // bytes, any other value cannot be used and should be rejected as an error.
+ std::ostringstream oss;
+ oss << "Invalid EFP identity: Partition=" << efpIdentity.pn_ << " Size=" << efpIdentity.ds_ << "k";
+ throw jexception(jerrno::JERR_RCVM_INVALIDEFPID, oss.str(), "RecoveryManager", "analyzeJournals");
+ }
+ efpFileSize_kib_ = (*emptyFilePoolPtrPtr)->fileSize_kib();
+
+ // Read all records, establish remaining enqueued records
+ if (inFileStream_.is_open()) {
+ inFileStream_.close();
+ }
+ while (getNextRecordHeader()) {}
+ if (inFileStream_.is_open()) {
+ inFileStream_.close();
+ }
+
+ // Check for file full condition
+ lastFileFullFlag_ = endOffset_ == (std::streamoff)(*emptyFilePoolPtrPtr)->fileSize_kib() * 1024;
+
+ // Remove leading files which have no enqueued records
+ removeEmptyFiles(*emptyFilePoolPtrPtr);
+
+ // Remove all txns from tmap that are not in the prepared list
+ if (preparedTransactionListPtr) {
+ std::vector<std::string> xidList;
+ transactionMapRef_.xid_list(xidList);
+ for (std::vector<std::string>::iterator itr = xidList.begin(); itr != xidList.end(); itr++) {
+ std::vector<std::string>::const_iterator pitr =
+ std::find(preparedTransactionListPtr->begin(), preparedTransactionListPtr->end(), *itr);
+ if (pitr == preparedTransactionListPtr->end()) { // not found in prepared list
+ txn_data_list_t tdl = transactionMapRef_.get_remove_tdata_list(*itr); // tdl will be empty if xid not found
+ // Unlock any affected enqueues in emap
+ for (tdl_itr_t i=tdl.begin(); i<tdl.end(); i++) {
+ if (i->enq_flag_) { // enq op - decrement enqueue count
+ fileNumberMap_[i->fid_]->journalFilePtr_->decrEnqueuedRecordCount();
+ } else if (enqueueMapRef_.is_enqueued(i->drid_, true)) { // deq op - unlock enq record
+ if (enqueueMapRef_.unlock(i->drid_) < enq_map::EMAP_OK) { // fail
+ // enq_map::unlock()'s only error is enq_map::EMAP_RID_NOT_FOUND
+ std::ostringstream oss;
+ oss << std::hex << "_emap.unlock(): drid=0x\"" << i->drid_;
+ throw jexception(jerrno::JERR_MAP_NOTFOUND, oss.str(), "RecoveryManager", "analyzeJournals");
+ }
+ }
+ }
+ }
+ }
+ }
+ prepareRecordList();
+ }
+}
+
+std::streamoff RecoveryManager::getEndOffset() const {
+ return endOffset_;
+}
+
+uint64_t RecoveryManager::getHighestFileNumber() const {
+ return highestFileNumber_;
+}
+
+uint64_t RecoveryManager::getHighestRecordId() const {
+ return highestRecordId_;
+}
+
+bool RecoveryManager::isLastFileFull() const {
+ return lastFileFullFlag_;
+}
+
+bool RecoveryManager::readNextRemainingRecord(void** const dataPtrPtr,
+ std::size_t& dataSize,
+ void** const xidPtrPtr,
+ std::size_t& xidSize,
+ bool& transient,
+ bool& external,
+ data_tok* const dtokp,
+ bool ignore_pending_txns) {
+ bool foundRecord = false;
+ do {
+ if (recordIdListConstItr_ == recordIdList_.end()) {
+ return false;
+ }
+ if (recordIdListConstItr_->pendingTransaction_ && ignore_pending_txns) { // Pending transaction
+ ++recordIdListConstItr_; // ignore, go to next record
+ } else {
+ foundRecord = true;
+ }
+ } while (!foundRecord);
+
+ if (!inFileStream_.is_open() || currentJournalFileItr_->first != recordIdListConstItr_->fileId_) {
+ if (!getFile(recordIdListConstItr_->fileId_, false)) {
+ std::ostringstream oss;
+ oss << "Failed to open file with file-id=" << recordIdListConstItr_->fileId_;
+ throw jexception(jerrno::JERR__FILEIO, oss.str(), "RecoveryManager", "readNextRemainingRecord");
+ }
+ }
+ inFileStream_.seekg(recordIdListConstItr_->fileOffset_, std::ifstream::beg);
+ if (!inFileStream_.good()) {
+ std::ostringstream oss;
+ oss << "Could not find offset 0x" << std::hex << recordIdListConstItr_->fileOffset_ << " in file " << getCurrentFileName();
+ throw jexception(jerrno::JERR__FILEIO, oss.str(), "RecoveryManager", "readNextRemainingRecord");
+ }
+
+ ::enq_hdr_t enqueueHeader;
+ inFileStream_.read((char*)&enqueueHeader, sizeof(::enq_hdr_t));
+ if (inFileStream_.gcount() != sizeof(::enq_hdr_t)) {
+ std::ostringstream oss;
+ oss << "Could not read enqueue header from file " << getCurrentFileName() << " at offset 0x" << std::hex << recordIdListConstItr_->fileOffset_;
+ throw jexception(jerrno::JERR__FILEIO, oss.str(), "RecoveryManager", "readNextRemainingRecord");
+ }
+ // check flags
+ transient = ::is_enq_transient(&enqueueHeader);
+ external = ::is_enq_external(&enqueueHeader);
+
+ // read xid
+ xidSize = enqueueHeader._xidsize;
+ *xidPtrPtr = ::malloc(xidSize);
+ if (*xidPtrPtr == 0) {
+ std::ostringstream oss;
+ oss << "xidPtr, size=0x" << std::hex << xidSize;
+ throw jexception(jerrno::JERR__MALLOC, oss.str(), "RecoveryManager", "readNextRemainingRecord");
+ }
+ readJournalData((char*)*xidPtrPtr, xidSize);
+
+ // read data
+ dataSize = enqueueHeader._dsize;
+ *dataPtrPtr = ::malloc(dataSize);
+ if (*xidPtrPtr == 0) {
+ std::ostringstream oss;
+ oss << "dataPtr, size=0x" << std::hex << dataSize;
+ throw jexception(jerrno::JERR__MALLOC, oss.str(), "RecoveryManager", "readNextRemainingRecord");
+ }
+ readJournalData((char*)*dataPtrPtr, dataSize);
+
+ // Check enqueue record checksum
+ Checksum checksum;
+ checksum.addData((const unsigned char*)&enqueueHeader, sizeof(::enq_hdr_t));
+ if (xidSize > 0) {
+ checksum.addData((const unsigned char*)*xidPtrPtr, xidSize);
+ }
+ if (dataSize > 0) {
+ checksum.addData((const unsigned char*)*dataPtrPtr, dataSize);
+ }
+ ::rec_tail_t enqueueTail;
+ readJournalData((char*)&enqueueTail, sizeof(::rec_tail_t));
+ uint32_t cs = checksum.getChecksum();
+ uint16_t res = ::rec_tail_check(&enqueueTail, &enqueueHeader._rhdr, cs);
+ if (res != 0) {
+ std::stringstream oss;
+ oss << "Bad record tail:" << std::hex;
+ if (res & ::REC_TAIL_MAGIC_ERR_MASK) {
+ oss << std::endl << " Magic: expected 0x" << ~enqueueHeader._rhdr._magic << "; found 0x" << enqueueTail._xmagic;
+ }
+ if (res & ::REC_TAIL_SERIAL_ERR_MASK) {
+ oss << std::endl << " Serial: expected 0x" << enqueueHeader._rhdr._serial << "; found 0x" << enqueueTail._serial;
+ }
+ if (res & ::REC_TAIL_RID_ERR_MASK) {
+ oss << std::endl << " Record Id: expected 0x" << enqueueHeader._rhdr._rid << "; found 0x" << enqueueTail._rid;
+ }
+ if (res & ::REC_TAIL_CHECKSUM_ERR_MASK) {
+ oss << std::endl << " Checksum: expected 0x" << cs << "; found 0x" << enqueueTail._checksum;
+ }
+ throw jexception(jerrno::JERR_JREC_BADRECTAIL, oss.str(), "RecoveryManager", "readNextRemainingRecord"); // TODO: Don't throw exception, log info
+ }
+
+ // Set data token
+ dtokp->set_wstate(data_tok::ENQ);
+ dtokp->set_rid(enqueueHeader._rhdr._rid);
+ dtokp->set_dsize(dataSize);
+ if (xidSize) {
+ dtokp->set_xid(*xidPtrPtr, xidSize);
+ }
+
+ ++recordIdListConstItr_;
+ return true;
+}
+
+void RecoveryManager::recoveryComplete() {
+ if(inFileStream_.is_open()) {
+ inFileStream_.close();
+ }
+}
+
+void RecoveryManager::setLinearFileControllerJournals(lfcAddJournalFileFn fnPtr,
+ LinearFileController* lfcPtr) {
+ if (journalEmptyFlag_) {
+ if (uninitFileList_.size() > 0) {
+ // TODO: Handle case if uninitFileList_.size() > 1, but this should not happen in normal operation. Here we assume only one item in the list.
+ std::string uninitFile = uninitFileList_.back();
+ uninitFileList_.pop_back();
+ lfcPtr->restoreEmptyFile(uninitFile);
+ }
+ } else {
+ if (initial_fid_ == 0) {
+ throw jexception(jerrno::JERR_RCVM_NULLFID, "RecoveryManager", "setLinearFileControllerJournals");
+ }
+ for (fileNumberMapConstItr_t i = fileNumberMap_.begin(); i != fileNumberMap_.end(); ++i) {
+ (lfcPtr->*fnPtr)(i->second->journalFilePtr_, i->second->completedDblkCount_, i->first == initial_fid_);
+ }
+ }
+
+ std::ostringstream oss;
+ bool logFlag = !notNeededFilesList_.empty();
+ if (logFlag) {
+ oss << "Files removed from head of journal: prior truncation during recovery:";
+ }
+ while (!notNeededFilesList_.empty()) {
+ lfcPtr->removeFileToEfp(notNeededFilesList_.back());
+ oss << std::endl << " * " << notNeededFilesList_.back();
+ notNeededFilesList_.pop_back();
+ }
+ if (logFlag) {
+ journalLogRef_.log(JournalLog::LOG_NOTICE, queueName_, oss.str());
+ }
+}
+
+std::string RecoveryManager::toString(const std::string& jid, const uint16_t indent) const {
+ std::string indentStr(indent, ' ');
+ std::ostringstream oss;
+ oss << std::endl << indentStr << "Journal recovery analysis (jid=\"" << jid << "\"):" << std::endl;
+ if (journalEmptyFlag_) {
+ oss << indentStr << "<Journal empty, no journal files found>" << std::endl;
+ } else {
+ oss << indentStr << std::setw(7) << "file_id"
+ << std::setw(43) << "file_name"
+ << std::setw(12) << "record_cnt"
+ << std::setw(16) << "fro"
+ << std::setw(12) << "efp_id"
+ << std::endl;
+ oss << indentStr << std::setw(7) << "-------"
+ << std::setw(43) << "-----------------------------------------"
+ << std::setw(12) << "----------"
+ << std::setw(16) << "--------------"
+ << std::setw(12) << "----------"
+ << std::endl;
+ uint32_t totalRecordCount(0UL);
+ for (fileNumberMapConstItr_t k=fileNumberMap_.begin(); k!=fileNumberMap_.end(); ++k) {
+ std::string fqFileName = k->second->journalFilePtr_->getFqFileName();
+ std::ostringstream fid;
+ fid << std::hex << "0x" << k->first;
+ std::ostringstream fro;
+ fro << std::hex << "0x" << k->second->journalFilePtr_->getFirstRecordOffset();
+ oss << indentStr << std::setw(7) << fid.str()
+ << std::setw(43) << fqFileName.substr(fqFileName.rfind('/')+1)
+ << std::setw(12) << k->second->journalFilePtr_->getEnqueuedRecordCount()
+ << std::setw(16) << fro.str()
+ << std::setw(12) << k->second->journalFilePtr_->getEfpIdentity()
+ << std::endl;
+ totalRecordCount += k->second->journalFilePtr_->getEnqueuedRecordCount();
+ }
+ oss << indentStr << std::setw(62) << "----------" << std::endl;
+ oss << indentStr << std::setw(62) << totalRecordCount << std::endl;
+ oss << indentStr << "First record offset in first file = 0x" << std::hex << firstRecordOffset_ <<
+ std::dec << " (" << (firstRecordOffset_/QLS_DBLK_SIZE_BYTES) << " dblks)" << std::endl;
+ oss << indentStr << "End offset in last file = 0x" << std::hex << endOffset_ << std::dec << " (" <<
+ (endOffset_/QLS_DBLK_SIZE_BYTES) << " dblks)" << std::endl;
+ oss << indentStr << "Highest rid found = 0x" << std::hex << highestRecordId_ << std::dec << std::endl;
+ oss << indentStr << "Last file full = " << (lastFileFullFlag_ ? "TRUE" : "FALSE") << std::endl;
+ }
+ return oss.str();
+}
+
+// --- protected functions ---
+
+void RecoveryManager::analyzeJournalFileHeaders(efpIdentity_t& efpIdentity) {
+ std::string headerQueueName;
+ ::file_hdr_t fileHeader;
+ stringList_t directoryList;
+ jdir::read_dir(journalDirectory_, directoryList, false, true, false, true);
+ for (stringListConstItr_t i = directoryList.begin(); i != directoryList.end(); ++i) {
+ bool hdrOk = readJournalFileHeader(*i, fileHeader, headerQueueName);
+ bool hdrEmpty = ::is_file_hdr_reset(&fileHeader);
+ if (!hdrOk) {
+ std::ostringstream oss;
+ oss << "Journal file " << (*i) << " is corrupted or invalid";
+ journalLogRef_.log(JournalLog::LOG_WARN, queueName_, oss.str());
+ } else if (hdrEmpty) {
+ // Read symlink, find efp directory name which is efp size in KiB
+ // TODO: place this bit into a common function as it is also used in EmptyFilePool.cpp::deleteSymlink()
+ char buff[1024];
+ ssize_t len = ::readlink((*i).c_str(), buff, 1024);
+ if (len < 0) {
+ std::ostringstream oss;
+ oss << "symlink=\"" << (*i) << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR__SYMLINK, oss.str(), "RecoveryManager", "analyzeJournalFileHeaders");
+ }
+ // Find second and third '/' from back of string, which contains the EFP directory name
+ *(::strrchr(buff, '/')) = '\0';
+ *(::strrchr(buff, '/')) = '\0';
+ int efpDataSize_kib = atoi(::strrchr(buff, '/') + 1);
+ uninitFileList_.push_back(*i);
+ efpIdentity.pn_ = fileHeader._efp_partition;
+ efpIdentity.ds_ = efpDataSize_kib;
+ } else if (headerQueueName.compare(queueName_) != 0) {
+ std::ostringstream oss;
+ oss << "Journal file " << (*i) << " belongs to queue \"" << headerQueueName << "\": ignoring";
+ journalLogRef_.log(JournalLog::LOG_WARN, queueName_, oss.str());
+ } else {
+ JournalFile* jfp = new JournalFile(*i, fileHeader, queueName_);
+ std::pair<fileNumberMapItr_t, bool> res = fileNumberMap_.insert(
+ std::pair<uint64_t, RecoveredFileData_t*>(fileHeader._file_number, new RecoveredFileData_t(jfp, 0)));
+ if (!res.second) {
+ std::ostringstream oss;
+ oss << "Journal file " << (*i) << " has fid=0x" << std::hex << jfp->getFileSeqNum() << " which already exists for this journal.";
+ throw jexception(oss.str()); // TODO: complete this exception
+ }
+ if (fileHeader._file_number > highestFileNumber_) {
+ highestFileNumber_ = fileHeader._file_number;
+ }
+ // TODO: Logic weak here for detecting error conditions in journal, specifically when no
+ // valid files exist, or files from mixed EFPs. Currently last read file header determines
+ // efpIdentity.
+ efpIdentity.pn_ = fileHeader._efp_partition;
+ efpIdentity.ds_ = fileHeader._data_size_kib;
+ }
+ }
+
+//std::cerr << "*** RecoveryManager::analyzeJournalFileHeaders() fileNumberMap_.size()=" << fileNumberMap_.size() << std::endl; // DEBUG
+ if (fileNumberMap_.empty()) {
+ journalEmptyFlag_ = true;
+ } else {
+ currentJournalFileItr_ = fileNumberMap_.begin();
+ }
+}
+
+void RecoveryManager::checkFileStreamOk(bool checkEof) {
+ if (inFileStream_.fail() || inFileStream_.bad() || checkEof ? inFileStream_.eof() : false) {
+ std::ostringstream oss;
+ oss << "Stream status: fail=" << (inFileStream_.fail()?"T":"F") << " bad=" << (inFileStream_.bad()?"T":"F");
+ if (checkEof) {
+ oss << " eof=" << (inFileStream_.eof()?"T":"F");
+ }
+ throw jexception(jerrno::JERR_RCVM_STREAMBAD, oss.str(), "RecoveryManager", "checkFileStreamOk");
+ }
+}
+
+void RecoveryManager::checkJournalAlignment(const uint64_t start_fid, const std::streampos recordPosition) {
+ if (recordPosition % QLS_DBLK_SIZE_BYTES != 0) {
+ std::ostringstream oss;
+ oss << "Current read pointer not dblk aligned: recordPosition=0x" << std::hex << recordPosition;
+ oss << " (dblk alignment offset = 0x" << (recordPosition % QLS_DBLK_SIZE_BYTES);
+ throw jexception(jerrno::JERR_RCVM_NOTDBLKALIGNED, oss.str(), "RecoveryManager", "checkJournalAlignment");
+ }
+ std::streampos currentPosn = recordPosition;
+ unsigned sblkOffset = currentPosn % QLS_SBLK_SIZE_BYTES;
+ if (sblkOffset)
+ {
+ std::ostringstream oss1;
+ oss1 << std::hex << "Bad record alignment found at fid=0x" << start_fid;
+ oss1 << " offs=0x" << currentPosn << " (likely journal overwrite boundary); " << std::dec;
+ oss1 << (QLS_SBLK_SIZE_DBLKS - (sblkOffset/QLS_DBLK_SIZE_BYTES)) << " filler record(s) required.";
+ journalLogRef_.log(JournalLog::LOG_WARN, queueName_, oss1.str());
+
+ fileNumberMapConstItr_t fnmItr = fileNumberMap_.find(start_fid);
+ std::ofstream outFileStream(fnmItr->second->journalFilePtr_->getFqFileName().c_str(), std::ios_base::in | std::ios_base::out | std::ios_base::binary);
+ if (!outFileStream.good()) {
+ throw jexception(jerrno::JERR__FILEIO, getCurrentFileName(), "RecoveryManager", "checkJournalAlignment");
+ }
+ outFileStream.seekp(currentPosn);
+
+ // Prepare write buffer containing a single empty record (1 dblk)
+ void* writeBuffer = std::malloc(QLS_DBLK_SIZE_BYTES);
+ if (writeBuffer == 0) {
+ throw jexception(jerrno::JERR__MALLOC, "RecoveryManager", "checkJournalAlignment");
+ }
+ const uint32_t xmagic = QLS_EMPTY_MAGIC;
+ ::memcpy(writeBuffer, (const void*)&xmagic, sizeof(xmagic));
+ ::memset((char*)writeBuffer + sizeof(xmagic), QLS_CLEAN_CHAR, QLS_DBLK_SIZE_BYTES - sizeof(xmagic));
+
+ // Write as many empty records as are needed to get to sblk boundary
+ while (currentPosn % QLS_SBLK_SIZE_BYTES) {
+ outFileStream.write((const char*)writeBuffer, QLS_DBLK_SIZE_BYTES);
+ if (outFileStream.fail()) {
+ throw jexception(jerrno::JERR_RCVM_WRITE, "RecoveryManager", "checkJournalAlignment");
+ }
+ std::ostringstream oss2;
+ oss2 << std::hex << "Recover phase write: Wrote filler record: fid=0x" << start_fid;
+ oss2 << " offs=0x" << currentPosn;
+ journalLogRef_.log(JournalLog::LOG_NOTICE, queueName_, oss2.str());
+ currentPosn = outFileStream.tellp();
+ }
+ outFileStream.close();
+ std::free(writeBuffer);
+ journalLogRef_.log(JournalLog::LOG_INFO, queueName_, "Bad record alignment fixed.");
+ }
+ lastRecord(start_fid, currentPosn);
+}
+
+bool RecoveryManager::decodeRecord(jrec& record,
+ std::size_t& cumulativeSizeRead,
+ ::rec_hdr_t& headerRecord,
+ const uint64_t start_fid,
+ const std::streampos recordOffset)
+{
+ if (highestRecordId_ == 0) {
+ highestRecordId_ = headerRecord._rid;
+ } else if (headerRecord._rid - highestRecordId_ < 0x8000000000000000ULL) { // RFC 1982 comparison for unsigned 64-bit
+ highestRecordId_ = headerRecord._rid;
+ }
+
+ bool done = false;
+ while (!done) {
+ try {
+ done = record.decode(headerRecord, &inFileStream_, cumulativeSizeRead, recordOffset);
+ }
+ catch (const jexception& e) {
+ if (e.err_code() == jerrno::JERR_JREC_BADRECTAIL) {
+ std::ostringstream oss;
+ oss << jerrno::err_msg(e.err_code()) << e.additional_info();
+ journalLogRef_.log(JournalLog::LOG_INFO, queueName_, oss.str());
+ } else {
+ journalLogRef_.log(JournalLog::LOG_INFO, queueName_, e.what());
+ }
+ checkJournalAlignment(start_fid, recordOffset);
+ return false;
+ }
+ if (!done && needNextFile()) {
+ if (!getNextFile(false)) {
+ checkJournalAlignment(start_fid, recordOffset);
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+std::string RecoveryManager::getCurrentFileName() const {
+ return currentJournalFileItr_->second->journalFilePtr_->getFqFileName();
+}
+
+uint64_t RecoveryManager::getCurrentFileNumber() const {
+ return currentJournalFileItr_->first;
+}
+
+bool RecoveryManager::getFile(const uint64_t fileNumber, bool jumpToFirstRecordOffsetFlag) {
+ if (inFileStream_.is_open()) {
+ inFileStream_.close();
+//std::cout << " f=" << getCurrentFileName() << "]" << std::flush; // DEBUG
+ inFileStream_.clear(); // clear eof flag, req'd for older versions of c++
+ }
+ currentJournalFileItr_ = fileNumberMap_.find(fileNumber);
+ if (currentJournalFileItr_ == fileNumberMap_.end()) {
+ return false;
+ }
+ inFileStream_.open(getCurrentFileName().c_str(), std::ios_base::in | std::ios_base::binary);
+ if (!inFileStream_.good()) {
+ throw jexception(jerrno::JERR__FILEIO, getCurrentFileName(), "RecoveryManager", "getFile");
+ }
+//std::cout << " [F=" << getCurrentFileName() << std::flush; // DEBUG
+
+ if (!readFileHeader()) {
+ return false;
+ }
+ std::streamoff foffs = jumpToFirstRecordOffsetFlag ? firstRecordOffset_ : QLS_JRNL_FHDR_RES_SIZE_SBLKS * QLS_SBLK_SIZE_BYTES;
+ inFileStream_.seekg(foffs);
+ return true;
+}
+
+bool RecoveryManager::getNextFile(bool jumpToFirstRecordOffsetFlag) {
+ if (fileNumberMap_.empty()) {
+ return false;
+ }
+ if (inFileStream_.is_open()) {
+ inFileStream_.close();
+//std::cout << " .f=" << getCurrentFileName() << "]" << std::flush; // DEBUG
+ currentJournalFileItr_->second->completedDblkCount_ = efpFileSize_kib_ * 1024 / QLS_DBLK_SIZE_BYTES;
+ if (++currentJournalFileItr_ == fileNumberMap_.end()) {
+ return false;
+ }
+ inFileStream_.clear(); // clear eof flag, req'd for older versions of c++
+ }
+ inFileStream_.open(getCurrentFileName().c_str(), std::ios_base::in | std::ios_base::binary);
+ if (!inFileStream_.good()) {
+ throw jexception(jerrno::JERR__FILEIO, getCurrentFileName(), "RecoveryManager", "getNextFile");
+ }
+//std::cout << " [.F=" << getCurrentFileName() << std::flush; // DEBUG
+
+ if (!readFileHeader()) {
+ return false;
+ }
+ std::streamoff foffs = jumpToFirstRecordOffsetFlag ? firstRecordOffset_ : QLS_JRNL_FHDR_RES_SIZE_SBLKS * QLS_SBLK_SIZE_BYTES;
+ inFileStream_.seekg(foffs);
+ return true;
+}
+
+bool RecoveryManager::getNextRecordHeader()
+{
+ std::size_t cum_size_read = 0;
+ void* xidp = 0;
+ rec_hdr_t h;
+
+ bool hdr_ok = false;
+ uint64_t file_id = currentJournalFileItr_->second->journalFilePtr_->getFileSeqNum();
+ std::streampos file_pos = 0;
+ if (inFileStream_.is_open()) {
+ inFileStream_.clear();
+ file_pos = inFileStream_.tellg();
+ }
+ if (file_pos == std::streampos(-1)) {
+ std::ostringstream oss;
+ oss << "tellg() failure: fail=" << (inFileStream_.fail()?"T":"F") << " bad=" << (inFileStream_.bad()?"T":"F");
+ oss << " eof=" << (inFileStream_.eof()?"T":"F") << " good=" << (inFileStream_.good()?"T":"F");
+ oss << " rdstate=0x" << std::hex << inFileStream_.rdstate() << std::dec;
+ throw jexception(jerrno::JERR_RCVM_STREAMBAD, oss.str(), "RecoveryManager", "getNextRecordHeader");
+ }
+ while (!hdr_ok) {
+ if (needNextFile()) {
+ if (!getNextFile(true)) {
+ lastRecord(file_id, file_pos);
+ return false;
+ }
+ }
+ file_id = currentJournalFileItr_->second->journalFilePtr_->getFileSeqNum();
+ file_pos = inFileStream_.tellg();
+ if (file_pos == std::streampos(-1)) {
+ std::ostringstream oss;
+ oss << "tellg() failure: fail=" << (inFileStream_.fail()?"T":"F") << " bad=" << (inFileStream_.bad()?"T":"F");
+ oss << " eof=" << (inFileStream_.eof()?"T":"F") << " good=" << (inFileStream_.good()?"T":"F");
+ oss << " rdstate=0x" << std::hex << inFileStream_.rdstate() << std::dec;
+ throw jexception(jerrno::JERR_RCVM_STREAMBAD, oss.str(), "RecoveryManager", "getNextRecordHeader");
+ }
+ inFileStream_.read((char*)&h, sizeof(rec_hdr_t));
+ if (inFileStream_.gcount() == sizeof(rec_hdr_t)) {
+ hdr_ok = true;
+ } else {
+ if (needNextFile()) {
+ if (!getNextFile(true)) {
+ lastRecord(file_id, file_pos);
+ return false;
+ }
+ }
+ }
+ }
+
+ uint64_t start_fid = getCurrentFileNumber(); // fid may increment in decode() if record folds over file boundary
+ switch(h._magic) {
+ case QLS_ENQ_MAGIC:
+ {
+//std::cout << " 0x" << std::hex << file_pos << ".e.0x" << h._rid << std::dec << std::flush; // DEBUG
+ if (::rec_hdr_check(&h, QLS_ENQ_MAGIC, QLS_JRNL_VERSION, currentSerial_) != 0) {
+ checkJournalAlignment(file_id, file_pos);
+ return false;
+ }
+ enq_rec er;
+ if (!decodeRecord(er, cum_size_read, h, start_fid, file_pos)) {
+ return false;
+ }
+ if (!er.is_transient()) { // Ignore transient msgs
+ fileNumberMap_[start_fid]->journalFilePtr_->incrEnqueuedRecordCount();
+ if (er.xid_size()) {
+ er.get_xid(&xidp);
+ if (xidp == 0) {
+ throw jexception(jerrno::JERR_RCVM_NULLXID, "ENQ", "RecoveryManager", "getNextRecordHeader");
+ }
+ std::string xid((char*)xidp, er.xid_size());
+ transactionMapRef_.insert_txn_data(xid, txn_data_t(h._rid, 0, start_fid, file_pos, true, false, false));
+ if (transactionMapRef_.set_aio_compl(xid, h._rid) < txn_map::TMAP_OK) { // fail - xid or rid not found
+ std::ostringstream oss;
+ oss << std::hex << "_tmap.set_aio_compl: txn_enq xid=\"" << xid << "\" rid=0x" << h._rid;
+ throw jexception(jerrno::JERR_MAP_NOTFOUND, oss.str(), "RecoveryManager", "getNextRecordHeader");
+ }
+ } else {
+ if (enqueueMapRef_.insert_pfid(h._rid, start_fid, file_pos) < enq_map::EMAP_OK) { // fail
+ // The only error code emap::insert_pfid() returns is enq_map::EMAP_DUP_RID.
+ std::ostringstream oss;
+ oss << std::hex << "rid=0x" << h._rid << " _pfid=0x" << start_fid;
+ throw jexception(jerrno::JERR_MAP_DUPLICATE, oss.str(), "RecoveryManager", "getNextRecordHeader");
+ }
+ }
+ }
+ }
+ break;
+ case QLS_DEQ_MAGIC:
+ {
+//std::cout << " 0x" << std::hex << file_pos << ".d.0x" << h._rid << std::dec << std::flush; // DEBUG
+ if (::rec_hdr_check(&h, QLS_DEQ_MAGIC, QLS_JRNL_VERSION, currentSerial_) != 0) {
+ checkJournalAlignment(file_id, file_pos);
+ return false;
+ }
+ deq_rec dr;
+ if (!decodeRecord(dr, cum_size_read, h, start_fid, file_pos)) {
+ return false;
+ }
+ if (dr.xid_size()) {
+ // If the enqueue is part of a pending txn, it will not yet be in emap
+ enqueueMapRef_.lock(dr.deq_rid()); // ignore not found error
+ dr.get_xid(&xidp);
+ if (xidp == 0) {
+ throw jexception(jerrno::JERR_RCVM_NULLXID, "DEQ", "RecoveryManager", "getNextRecordHeader");
+ }
+ std::string xid((char*)xidp, dr.xid_size());
+ transactionMapRef_.insert_txn_data(xid, txn_data_t(dr.rid(), dr.deq_rid(), start_fid, file_pos,
+ false, false, dr.is_txn_coml_commit()));
+ if (transactionMapRef_.set_aio_compl(xid, dr.rid()) < txn_map::TMAP_OK) { // fail - xid or rid not found
+ std::ostringstream oss;
+ oss << std::hex << "_tmap.set_aio_compl: txn_deq xid=\"" << xid << "\" rid=0x" << dr.rid();
+ throw jexception(jerrno::JERR_MAP_NOTFOUND, oss.str(), "RecoveryManager", "getNextRecordHeader");
+ }
+ } else {
+ uint64_t enq_fid;
+ if (enqueueMapRef_.get_remove_pfid(dr.deq_rid(), enq_fid, true) == enq_map::EMAP_OK) { // ignore not found error
+ fileNumberMap_[enq_fid]->journalFilePtr_->decrEnqueuedRecordCount();
+ }
+ }
+ }
+ break;
+ case QLS_TXA_MAGIC:
+ {
+//std::cout << " 0x" << std::hex << file_pos << ".a.0x" << h._rid << std::dec << std::flush; // DEBUG
+ if (::rec_hdr_check(&h, QLS_TXA_MAGIC, QLS_JRNL_VERSION, currentSerial_) != 0) {
+ checkJournalAlignment(file_id, file_pos);
+ return false;
+ }
+ txn_rec ar;
+ if (!decodeRecord(ar, cum_size_read, h, start_fid, file_pos)) {
+ return false;
+ }
+ // Delete this txn from tmap, unlock any locked records in emap
+ ar.get_xid(&xidp);
+ if (xidp == 0) {
+ throw jexception(jerrno::JERR_RCVM_NULLXID, "ABT", "RecoveryManager", "getNextRecordHeader");
+ }
+ std::string xid((char*)xidp, ar.xid_size());
+ txn_data_list_t tdl = transactionMapRef_.get_remove_tdata_list(xid); // tdl will be empty if xid not found
+ for (tdl_itr_t itr = tdl.begin(); itr != tdl.end(); itr++) {
+ if (itr->enq_flag_) {
+ fileNumberMap_[itr->fid_]->journalFilePtr_->decrEnqueuedRecordCount();
+ } else {
+ enqueueMapRef_.unlock(itr->drid_); // ignore not found error
+ }
+ }
+ }
+ break;
+ case QLS_TXC_MAGIC:
+ {
+//std::cout << " 0x" << std::hex << file_pos << ".c.0x" << h._rid << std::dec << std::flush; // DEBUG
+ if (::rec_hdr_check(&h, QLS_TXC_MAGIC, QLS_JRNL_VERSION, currentSerial_) != 0) {
+ checkJournalAlignment(file_id, file_pos);
+ return false;
+ }
+ txn_rec cr;
+ if (!decodeRecord(cr, cum_size_read, h, start_fid, file_pos)) {
+ return false;
+ }
+ // Delete this txn from tmap, process records into emap
+ cr.get_xid(&xidp);
+ if (xidp == 0) {
+ throw jexception(jerrno::JERR_RCVM_NULLXID, "CMT", "RecoveryManager", "getNextRecordHeader");
+ }
+ std::string xid((char*)xidp, cr.xid_size());
+ txn_data_list_t tdl = transactionMapRef_.get_remove_tdata_list(xid); // tdl will be empty if xid not found
+ for (tdl_itr_t itr = tdl.begin(); itr != tdl.end(); itr++) {
+ if (itr->enq_flag_) { // txn enqueue
+//std::cout << "[rid=0x" << std::hex << itr->rid_ << std::dec << " fid=" << itr->fid_ << " fpos=0x" << std::hex << itr->foffs_ << "]" << std::dec << std::flush; // DEBUG
+ if (enqueueMapRef_.insert_pfid(itr->rid_, itr->fid_, itr->foffs_) < enq_map::EMAP_OK) { // fail
+ // The only error code emap::insert_pfid() returns is enq_map::EMAP_DUP_RID.
+ std::ostringstream oss;
+ oss << std::hex << "rid=0x" << itr->rid_ << " _pfid=0x" << itr->fid_;
+ throw jexception(jerrno::JERR_MAP_DUPLICATE, oss.str(), "RecoveryManager", "getNextRecordHeader");
+ }
+ } else { // txn dequeue
+ uint64_t enq_fid;
+ if (enqueueMapRef_.get_remove_pfid(itr->drid_, enq_fid, true) == enq_map::EMAP_OK) // ignore not found error
+ fileNumberMap_[enq_fid]->journalFilePtr_->decrEnqueuedRecordCount();
+ }
+ }
+ }
+ break;
+ case QLS_EMPTY_MAGIC:
+ {
+//std::cout << ".x" << std::flush; // DEBUG
+ uint32_t rec_dblks = jrec::size_dblks(sizeof(::rec_hdr_t));
+ inFileStream_.ignore(rec_dblks * QLS_DBLK_SIZE_BYTES - sizeof(::rec_hdr_t));
+ checkFileStreamOk(false);
+ if (needNextFile()) {
+ file_pos += rec_dblks * QLS_DBLK_SIZE_BYTES;
+ if (!getNextFile(false)) {
+ lastRecord(start_fid, file_pos);
+ return false;
+ }
+ }
+ }
+ break;
+ case 0:
+//std::cout << " 0x" << std::hex << file_pos << ".0" << std::dec << std::endl << std::flush; // DEBUG
+ checkJournalAlignment(getCurrentFileNumber(), file_pos);
+ return false;
+ default:
+//std::cout << " 0x" << std::hex << file_pos << ".?" << std::dec << std::endl << std::flush; // DEBUG
+ // Stop as this is the overwrite boundary.
+ checkJournalAlignment(getCurrentFileNumber(), file_pos);
+ return false;
+ }
+ return true;
+}
+
+void RecoveryManager::lastRecord(const uint64_t file_id, const std::streamoff endOffset) {
+ endOffset_ = endOffset;
+ initial_fid_ = file_id;
+ fileNumberMap_[file_id]->completedDblkCount_ = endOffset_ / QLS_DBLK_SIZE_BYTES;
+
+ // Remove any files in fileNumberMap_ beyond initial_fid_
+ fileNumberMapItr_t unwantedFirstItr = fileNumberMap_.find(file_id);
+ if (++unwantedFirstItr != fileNumberMap_.end()) {
+ fileNumberMapItr_t itr = unwantedFirstItr;
+ notNeededFilesList_.push_back(unwantedFirstItr->second->journalFilePtr_->getFqFileName());
+ while (++itr != fileNumberMap_.end()) {
+ notNeededFilesList_.push_back(itr->second->journalFilePtr_->getFqFileName());
+ delete itr->second->journalFilePtr_;
+ delete itr->second;
+ }
+ fileNumberMap_.erase(unwantedFirstItr, fileNumberMap_.end());
+ }
+}
+
+bool RecoveryManager::needNextFile() {
+ if (inFileStream_.is_open()) {
+ return inFileStream_.eof() || inFileStream_.tellg() >= std::streampos(efpFileSize_kib_ * 1024);
+ }
+ return true;
+}
+
+void RecoveryManager::prepareRecordList() {
+ // Set up recordIdList_ from enqueue map and transaction map
+ recordIdList_.clear();
+
+ // Extract records from enqueue list
+ std::vector<uint64_t> ridList;
+ enqueueMapRef_.rid_list(ridList);
+ qpid::linearstore::journal::enq_map::emap_data_struct_t eds;
+ for (std::vector<uint64_t>::const_iterator i=ridList.begin(); i!=ridList.end(); ++i) {
+ enqueueMapRef_.get_data(*i, eds);
+ recordIdList_.push_back(RecoveredRecordData_t(*i, eds._pfid, eds._file_posn, false));
+ }
+
+ // Extract records from pending transaction enqueues
+ std::vector<std::string> xidList;
+ transactionMapRef_.xid_list(xidList);
+ for (std::vector<std::string>::const_iterator j=xidList.begin(); j!=xidList.end(); ++j) {
+ qpid::linearstore::journal::txn_data_list_t tdsl = transactionMapRef_.get_tdata_list(*j);
+ for (qpid::linearstore::journal::tdl_itr_t k=tdsl.begin(); k!=tdsl.end(); ++k) {
+ if (k->enq_flag_) {
+ recordIdList_.push_back(RecoveredRecordData_t(k->rid_, k->fid_, k->foffs_, true));
+ }
+ }
+ }
+
+ std::sort(recordIdList_.begin(), recordIdList_.end(), recordIdListCompare);
+ recordIdListConstItr_ = recordIdList_.begin();
+}
+
+void RecoveryManager::readJournalData(char* target,
+ const std::streamsize readSize) {
+ std::streamoff bytesRead = 0;
+ while (bytesRead < readSize) {
+ std::streampos file_pos = inFileStream_.tellg();
+ if (file_pos == std::streampos(-1)) {
+ std::ostringstream oss;
+ oss << "tellg() failure: fail=" << (inFileStream_.fail()?"T":"F") << " bad=" << (inFileStream_.bad()?"T":"F");
+ throw jexception(jerrno::JERR_RCVM_STREAMBAD, oss.str(), "RecoveryManager", "readJournalData");
+ }
+ inFileStream_.read(target + bytesRead, readSize - bytesRead);
+ std::streamoff thisReadSize = inFileStream_.gcount();
+ if (thisReadSize < readSize) {
+ if (needNextFile()) {
+ getNextFile(false);
+ }
+ file_pos = inFileStream_.tellg();
+ if (file_pos == std::streampos(-1)) {
+ std::ostringstream oss;
+ oss << "tellg() failure: fail=" << (inFileStream_.fail()?"T":"F") << " bad=" << (inFileStream_.bad()?"T":"F");
+ throw jexception(jerrno::JERR_RCVM_STREAMBAD, oss.str(), "RecoveryManager", "readJournalData");
+ }
+ }
+ bytesRead += thisReadSize;
+ }
+}
+
+bool RecoveryManager::readFileHeader() {
+ file_hdr_t fhdr;
+ inFileStream_.read((char*)&fhdr, sizeof(fhdr));
+ checkFileStreamOk(true);
+ if (::file_hdr_check(&fhdr, QLS_FILE_MAGIC, QLS_JRNL_VERSION, efpFileSize_kib_, QLS_MAX_QUEUE_NAME_LEN) != 0) {
+ firstRecordOffset_ = fhdr._fro;
+ currentSerial_ = fhdr._rhdr._serial;
+ } else {
+ inFileStream_.close();
+ if (currentJournalFileItr_ == fileNumberMap_.begin()) {
+ journalEmptyFlag_ = true;
+ }
+ return false;
+ }
+ return true;
+}
+
+// static private
+bool RecoveryManager::readJournalFileHeader(const std::string& journalFileName,
+ ::file_hdr_t& fileHeaderRef,
+ std::string& queueName) {
+ const std::size_t headerBlockSize = QLS_JRNL_FHDR_RES_SIZE_SBLKS * QLS_SBLK_SIZE_KIB * 1024;
+ char buffer[headerBlockSize];
+ std::ifstream ifs(journalFileName.c_str(), std::ifstream::in | std::ifstream::binary);
+ if (!ifs.good()) {
+ std::ostringstream oss;
+ oss << "File=" << journalFileName;
+ throw jexception(jerrno::JERR_RCVM_OPENRD, oss.str(), "RecoveryManager", "readJournalFileHeader");
+ }
+ ifs.read(buffer, headerBlockSize);
+ if (!ifs) {
+ std::streamsize s = ifs.gcount();
+ ifs.close();
+ std::ostringstream oss;
+ oss << "File=" << journalFileName << "; attempted_read_size=" << headerBlockSize << "; actual_read_size=" << s;
+ throw jexception(jerrno::JERR_RCVM_READ, oss.str(), "RecoveryManager", "readJournalFileHeader");
+ }
+ ifs.close();
+ ::memcpy(&fileHeaderRef, buffer, sizeof(::file_hdr_t));
+ if (::file_hdr_check(&fileHeaderRef, QLS_FILE_MAGIC, QLS_JRNL_VERSION, 0, QLS_MAX_QUEUE_NAME_LEN)) {
+ return false;
+ }
+ queueName.assign(buffer + sizeof(::file_hdr_t), fileHeaderRef._queue_name_len);
+ return true;
+}
+
+void RecoveryManager::removeEmptyFiles(EmptyFilePool* emptyFilePoolPtr) {
+ while (fileNumberMap_.begin()->second->journalFilePtr_->getEnqueuedRecordCount() == 0 && fileNumberMap_.size() > 1) {
+ RecoveredFileData_t* rfdp = fileNumberMap_.begin()->second;
+ emptyFilePoolPtr->returnEmptyFileSymlink(rfdp->journalFilePtr_->getFqFileName());
+ delete rfdp->journalFilePtr_;
+ delete rfdp;
+ fileNumberMap_.erase(fileNumberMap_.begin()->first);
+ }
+}
+
+}}}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/RecoveryManager.h b/qpid/cpp/src/qpid/linearstore/journal/RecoveryManager.h
new file mode 100644
index 0000000000..55cc6f8329
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/RecoveryManager.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_RECOVERYSTATE_H_
+#define QPID_LINEARSTORE_JOURNAL_RECOVERYSTATE_H_
+
+#include <fstream>
+#include <map>
+#include "qpid/linearstore/journal/LinearFileController.h"
+#include <stdint.h>
+#include <vector>
+
+struct file_hdr_t;
+struct rec_hdr_t;
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+class data_tok;
+class enq_map;
+class EmptyFilePool;
+class EmptyFilePoolManager;
+class JournalLog;
+class jrec;
+class txn_map;
+
+struct RecoveredRecordData_t {
+ uint64_t recordId_;
+ uint64_t fileId_;
+ std::streampos fileOffset_;
+ bool pendingTransaction_;
+ RecoveredRecordData_t(const uint64_t rid, const uint64_t fid, const std::streampos foffs, bool ptxn);
+};
+
+struct RecoveredFileData_t {
+ JournalFile* journalFilePtr_;
+ uint32_t completedDblkCount_;
+ RecoveredFileData_t(JournalFile* journalFilePtr, const uint32_t completedDblkCount);
+};
+
+bool recordIdListCompare(RecoveredRecordData_t a, RecoveredRecordData_t b);
+
+class RecoveryManager
+{
+protected:
+ // Types
+ typedef std::vector<std::string> stringList_t;
+ typedef stringList_t::const_iterator stringListConstItr_t;
+ typedef std::map<uint64_t, RecoveredFileData_t*> fileNumberMap_t;
+ typedef fileNumberMap_t::iterator fileNumberMapItr_t;
+ typedef fileNumberMap_t::const_iterator fileNumberMapConstItr_t;
+ typedef std::vector<RecoveredRecordData_t> recordIdList_t;
+ typedef recordIdList_t::const_iterator recordIdListConstItr_t;
+
+ // Location and identity
+ const std::string journalDirectory_;
+ const std::string queueName_;
+ enq_map& enqueueMapRef_;
+ txn_map& transactionMapRef_;
+ JournalLog& journalLogRef_;
+
+ // Initial journal analysis data
+ fileNumberMap_t fileNumberMap_; ///< File number - JournalFilePtr map
+ stringList_t notNeededFilesList_; ///< Files not needed and to be returned to EFP
+ stringList_t uninitFileList_; ///< File name of uninitialized journal files found during header analysis
+ bool journalEmptyFlag_; ///< Journal data files empty
+ std::streamoff firstRecordOffset_; ///< First record offset in ffid
+ std::streamoff endOffset_; ///< End offset (first byte past last record)
+ uint64_t highestRecordId_; ///< Highest rid found
+ uint64_t highestFileNumber_; ///< Highest file number found
+ bool lastFileFullFlag_; ///< Last file is full
+ uint64_t initial_fid_; ///< File id where initial write after recovery will occur
+
+ // State for recovery of individual enqueued records
+ uint64_t currentSerial_;
+ uint32_t efpFileSize_kib_;
+ fileNumberMapConstItr_t currentJournalFileItr_;
+ std::string currentFileName_;
+ std::ifstream inFileStream_;
+ recordIdList_t recordIdList_;
+ recordIdListConstItr_t recordIdListConstItr_;
+
+public:
+ RecoveryManager(const std::string& journalDirectory,
+ const std::string& queuename,
+ enq_map& enqueueMapRef,
+ txn_map& transactionMapRef,
+ JournalLog& journalLogRef);
+ virtual ~RecoveryManager();
+
+ void analyzeJournals(const std::vector<std::string>* preparedTransactionListPtr,
+ EmptyFilePoolManager* emptyFilePoolManager,
+ EmptyFilePool** emptyFilePoolPtrPtr);
+ std::streamoff getEndOffset() const;
+ uint64_t getHighestFileNumber() const;
+ uint64_t getHighestRecordId() const;
+ bool isLastFileFull() const;
+ bool readNextRemainingRecord(void** const dataPtrPtr,
+ std::size_t& dataSize,
+ void** const xidPtrPtr,
+ std::size_t& xidSize,
+ bool& transient,
+ bool& external,
+ data_tok* const dtokp,
+ bool ignore_pending_txns);
+ void recoveryComplete();
+ void setLinearFileControllerJournals(lfcAddJournalFileFn fnPtr,
+ LinearFileController* lfcPtr);
+ std::string toString(const std::string& jid, const uint16_t indent) const;
+protected:
+ void analyzeJournalFileHeaders(efpIdentity_t& efpIdentity);
+ void checkFileStreamOk(bool checkEof);
+ void checkJournalAlignment(const uint64_t start_fid, const std::streampos recordPosition);
+ bool decodeRecord(jrec& record,
+ std::size_t& cumulativeSizeRead,
+ ::rec_hdr_t& recordHeader,
+ const uint64_t start_fid,
+ const std::streampos recordOffset);
+ std::string getCurrentFileName() const;
+ uint64_t getCurrentFileNumber() const;
+ bool getFile(const uint64_t fileNumber, bool jumpToFirstRecordOffsetFlag);
+ bool getNextFile(bool jumpToFirstRecordOffsetFlag);
+ bool getNextRecordHeader();
+ void lastRecord(const uint64_t file_id, const std::streamoff endOffset);
+ bool needNextFile();
+ void prepareRecordList();
+ bool readFileHeader();
+ void readJournalData(char* target, const std::streamsize size);
+ void removeEmptyFiles(EmptyFilePool* emptyFilePoolPtr);
+
+ static bool readJournalFileHeader(const std::string& journalFileName,
+ ::file_hdr_t& fileHeaderRef,
+ std::string& queueName);
+};
+
+}}}
+
+#endif // QPID_LINEARSTORE_JOURNAL_RECOVERYSTATE_H_
diff --git a/qpid/cpp/src/qpid/linearstore/journal/aio.h b/qpid/cpp/src/qpid/linearstore/journal/aio.h
new file mode 100644
index 0000000000..14589e7580
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/aio.h
@@ -0,0 +1,201 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_AIO_H
+#define QPID_LINEARSTORE_JOURNAL_AIO_H
+
+#include <libaio.h>
+#include <cstring>
+#include <stdint.h>
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+typedef iocb aio_cb;
+typedef io_event aio_event;
+
+/**
+ * \brief This class is a C++ wrapper class for the libaio functions used by the journal. Note that only those
+ * functions used by the journal are included here. This is not a complete implementation of all libaio functions.
+ */
+class aio
+{
+public:
+ /*
+ * \brief Initialize an AIO context. Causes kernel resources to be initialized for
+ * AIO operations.
+ *
+ * \param maxevents The maximum number of events to be handled
+ * \param ctxp Pointer to context struct to be initialized
+ */
+ static inline int queue_init(int maxevents, io_context_t* ctxp)
+ {
+ return ::io_queue_init(maxevents, ctxp);
+ }
+
+ /*
+ * \brief Release an AIO context. Causes kernel resources previously initialized to
+ * be released.
+ *
+ * \param ctx AIO context struct to be released
+ */
+ static inline int queue_release(io_context_t ctx)
+ {
+ return ::io_queue_release(ctx);
+ }
+
+ /*
+ * \brief Submit asynchronous I/O blocks for processing
+ *
+ * The io_submit() system call queues nr I/O request blocks for processing in the AIO context ctx.
+ * The iocbpp argument should be an array of nr AIO control blocks, which will be submitted to context ctx.
+ *
+ * \param ctx AIO context
+ * \param nr Number of AIO operations
+ * \param aios Array of nr pointers to AIO control blocks, one for each AIO operation
+ * \return On success, io_submit() returns the number of iocbs submitted (which may be 0 if nr is zero).
+ * A negative number indicates an error:
+ * - -EAGAIN Insufficient resources are available to queue any iocbs.
+ * - -EBADF The file descriptor specified in the first iocb is invalid.
+ * - -EFAULT One of the data structures points to invalid data.
+ * - -EINVAL The AIO context specified by ctx_id is invalid. nr is less than 0. The iocb at *iocbpp[0]
+ * is not properly initialized, or the operation specified is invalid for the file descriptor
+ * in the iocb.
+ */
+ static inline int submit(io_context_t ctx, long nr, aio_cb* aios[])
+ {
+ return ::io_submit(ctx, nr, aios);
+ }
+
+ /*
+ * \brief Get list of completed AIO operations
+ *
+ * The io_getevents() system call attempts to read at least min_nr events and up to nr events from the
+ * completion queue of the AIO context specified by ctx_id. The timeout argument specifies the amount of time
+ * to wait for events, where a NULL timeout waits until at least min_nr events have been seen. Note that timeout
+ * is relative.
+ *
+ * \param ctx AIO context
+ * \param min_nr Minimum number of events to return, will wait until min_nr events are accumulated or until timeout
+ * \param nr Number of events to return
+ * \param events Pointer to array of aio_event structs, one for each completed event
+ * \param timeout Time to wait for min_nr events; 0 will cause an indefinite wait for min_nr events
+ * \return On success, number of events read: 0 if no events are available, or less than min_nr
+ * if the timeout has elapsed. A negative number indicates an error:
+ * - -EFAULT Either events or timeout is an invalid pointer.
+ * - -EINVAL ctx_id is invalid. min_nr is out of range or nr is out of range.
+ * - -EINTR Interrupted by a signal handler; see signal(7).
+ */
+ static inline int getevents(io_context_t ctx, long min_nr, long nr, aio_event* events, timespec* const timeout)
+ {
+ return ::io_getevents(ctx, min_nr, nr, events, timeout);
+ }
+
+ /**
+ * \brief This function allows iocbs to be initialized with a pointer that can be re-used. This prepares an
+ * aio_cb struct for read use. (This is a wrapper for libaio's ::io_prep_pread() function.)
+ *
+ * \param aiocbp Pointer to the aio_cb struct to be prepared.
+ * \param fd File descriptor to be used for read.
+ * \param buf Pointer to buffer in which read data is to be placed. MUST BE PAGE_ALIGNED.
+ * \param count Number of bytes to read - buffer must be large enough.
+ * \param offset Offset within file from which data will be read.
+ */
+ static inline void prep_pread(aio_cb* aiocbp, int fd, void* buf, std::size_t count, int64_t offset)
+ {
+ ::io_prep_pread(aiocbp, fd, buf, count, offset);
+ }
+
+ /**
+ * \brief Special version of libaio's io_prep_pread() which preserves the value of the data pointer. This allows
+ * iocbs to be initialized with a pointer that can be re-used. This prepares a aio_cb struct for read use.
+ *
+ * \param aiocbp Pointer to the aio_cb struct to be prepared.
+ * \param fd File descriptor to be used for read.
+ * \param buf Pointer to buffer in which read data is to be placed. MUST BE PAGE_ALIGNED.
+ * \param count Number of bytes to read - buffer must be large enough.
+ * \param offset Offset within file from which data will be read.
+ */
+ static inline void prep_pread_2(aio_cb* aiocbp, int fd, void* buf, std::size_t count, int64_t offset)
+ {
+ std::memset((void*) ((char*) aiocbp + sizeof(void*)), 0, sizeof(aio_cb) - sizeof(void*));
+ aiocbp->aio_fildes = fd;
+ aiocbp->aio_lio_opcode = IO_CMD_PREAD;
+ aiocbp->aio_reqprio = 0;
+ aiocbp->u.c.buf = buf;
+ aiocbp->u.c.nbytes = count;
+ aiocbp->u.c.offset = offset;
+ }
+
+ /**
+ * \brief This function allows iocbs to be initialized with a pointer that can be re-used. This function prepares
+ * an aio_cb struct for write use. (This is a wrapper for libaio's ::io_prep_pwrite() function.)
+ *
+ * \param aiocbp Pointer to the aio_cb struct to be prepared.
+ * \param fd File descriptor to be used for write.
+ * \param buf Pointer to buffer in which data to be written is located. MUST BE PAGE_ALIGNED.
+ * \param count Number of bytes to write.
+ * \param offset Offset within file to which data will be written.
+ */
+ static inline void prep_pwrite(aio_cb* aiocbp, int fd, void* buf, std::size_t count, int64_t offset)
+ {
+ ::io_prep_pwrite(aiocbp, fd, buf, count, offset);
+ }
+
+ /**
+ * \brief Special version of libaio's io_prep_pwrite() which preserves the value of the data pointer. This allows
+ * iocbs to be initialized with a pointer that can be re-used. This function prepares an aio_cb struct for write
+ * use.
+ *
+ * \param aiocbp Pointer to the aio_cb struct to be prepared.
+ * \param fd File descriptor to be used for write.
+ * \param buf Pointer to buffer in which data to be written is located. MUST BE PAGE_ALIGNED.
+ * \param count Number of bytes to write.
+ * \param offset Offset within file to which data will be written.
+ */
+ static inline void prep_pwrite_2(aio_cb* aiocbp, int fd, void* buf, std::size_t count, int64_t offset)
+ {
+ std::memset((void*) ((char*) aiocbp + sizeof(void*)), 0, sizeof(aio_cb) - sizeof(void*));
+ aiocbp->aio_fildes = fd;
+ aiocbp->aio_lio_opcode = IO_CMD_PWRITE;
+ aiocbp->aio_reqprio = 0;
+ aiocbp->u.c.buf = buf;
+ aiocbp->u.c.nbytes = count;
+ aiocbp->u.c.offset = offset;
+ }
+
+ /**
+ * \brief Function to check the alignment of memory.
+ *
+ * \param ptr Pointer to be checked
+ * \param byte_count Alignment count (or boundary)
+ * \returns true if ptr is aligned with byte_count, false otherwise
+ */
+ static inline bool is_aligned(const void* ptr, uint64_t byte_count)
+ {
+ return ((uintptr_t)(ptr)) % (byte_count) == 0;
+ }
+};
+
+}}}
+
+#endif // ifndef QPID_LINEARSTORE_JOURNAL_AIO_H
diff --git a/qpid/cpp/src/qpid/linearstore/journal/aio_callback.h b/qpid/cpp/src/qpid/linearstore/journal/aio_callback.h
new file mode 100644
index 0000000000..f21b62617b
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/aio_callback.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_AIO_CALLBACK_H
+#define QPID_LINEARSTORE_JOURNAL_AIO_CALLBACK_H
+
+#include <stdint.h>
+#include <vector>
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+class data_tok;
+
+class aio_callback
+{
+public:
+ virtual ~aio_callback() {}
+ virtual void wr_aio_cb(std::vector<data_tok*>& dtokl) = 0;
+ virtual void rd_aio_cb(std::vector<uint16_t>& pil) = 0;
+};
+
+}}}
+
+#endif // ifndef QPID_LINEARSTORE_JOURNAL_AIO_CALLBACK_H
diff --git a/qpid/cpp/src/qpid/linearstore/journal/data_tok.cpp b/qpid/cpp/src/qpid/linearstore/journal/data_tok.cpp
new file mode 100644
index 0000000000..3952c403a1
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/data_tok.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/linearstore/journal/data_tok.h"
+
+#include <iomanip>
+#include "qpid/linearstore/journal/slock.h"
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+// Static members
+
+uint64_t data_tok::_cnt = 0;
+smutex data_tok::_mutex;
+
+data_tok::data_tok():
+ _wstate(NONE),
+ _dsize(0),
+ _dblks_written(0),
+ _pg_cnt(0),
+ _fid(0),
+ _rid(0),
+ _xid(),
+ _dequeue_rid(0),
+ _external_rid(false)
+{
+ slock s(_mutex);
+ _icnt = _cnt++;
+}
+
+data_tok::~data_tok() {}
+
+const char*
+data_tok::wstate_str() const
+{
+ return wstate_str(_wstate);
+}
+
+const char*
+data_tok::wstate_str(write_state wstate)
+{
+ switch (wstate)
+ {
+ case NONE:
+ return "NONE";
+ case ENQ_CACHED:
+ return "ENQ_CACHED";
+ case ENQ_PART:
+ return "ENQ_PART";
+ case ENQ_SUBM:
+ return "ENQ_SUBM";
+ case ENQ:
+ return "ENQ";
+ case DEQ_CACHED:
+ return "DEQ_CACHED";
+ case DEQ_PART:
+ return "DEQ_PART";
+ case DEQ_SUBM:
+ return "DEQ_SUBM";
+ case DEQ:
+ return "DEQ";
+ case ABORT_CACHED:
+ return "ABORT_CACHED";
+ case ABORT_PART:
+ return "ABORT_PART";
+ case ABORT_SUBM:
+ return "ABORT_SUBM";
+ case ABORTED:
+ return "ABORTED";
+ case COMMIT_CACHED:
+ return "COMMIT_CACHED";
+ case COMMIT_PART:
+ return "COMMIT_PART";
+ case COMMIT_SUBM:
+ return "COMMIT_SUBM";
+ case COMMITTED:
+ return "COMMITTED";
+ }
+ // Not using default: forces compiler to ensure all cases are covered.
+ return "<wstate unknown>";
+}
+
+void
+data_tok::reset()
+{
+ _wstate = NONE;
+ _dsize = 0;
+ _dblks_written = 0;
+ _pg_cnt = 0;
+ _fid = 0;
+ _rid = 0;
+ _xid.clear();
+}
+
+// debug aid
+std::string
+data_tok::status_str() const
+{
+ std::ostringstream oss;
+ oss << std::hex << std::setfill('0');
+ oss << "dtok id=0x" << _icnt << "; ws=" << wstate_str()/* << "; rs=" << rstate_str()*/;
+ oss << "; fid=0x" << _fid << "; rid=0x" << _rid << "; xid=";
+ for (unsigned i=0; i<_xid.size(); i++)
+ {
+ if (isprint(_xid[i]))
+ oss << _xid[i];
+ else
+ oss << "/" << std::setw(2) << (int)((char)_xid[i]);
+ }
+ oss << "; drid=0x" << _dequeue_rid << " extrid=" << (_external_rid?"T":"F");
+ oss << "; ds=0x" << _dsize << "; dw=0x" << _dblks_written/* << "; dr=0x" << _dblks_read*/;
+ oss << "; pc=0x" << _pg_cnt;
+ return oss.str();
+}
+
+}}}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/data_tok.h b/qpid/cpp/src/qpid/linearstore/journal/data_tok.h
new file mode 100644
index 0000000000..67e0ec9683
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/data_tok.h
@@ -0,0 +1,133 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_DATA_TOK_H
+#define QPID_LINEARSTORE_JOURNAL_DATA_TOK_H
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+class data_tok;
+}}}
+
+#include <cassert>
+#include "qpid/linearstore/journal/smutex.h"
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+ /**
+ * \class data_tok
+ * \brief Data block token (data_tok) used to track wstate of a data block through asynchronous
+ * I/O process
+ */
+ class data_tok
+ {
+ public:
+ // TODO: Fix this, separate write state from operation
+ // ie: wstate = NONE, CACHED, PART, SUBM, COMPL
+ // op = ENQUEUE, DEQUEUE, ABORT, COMMIT
+ enum write_state
+ {
+ NONE, ///< Data block not sent to journal
+ ENQ_CACHED, ///< Data block enqueue written to page cache
+ ENQ_PART, ///< Data block part-submitted to AIO, waiting for page buffer to free up
+ ENQ_SUBM, ///< Data block enqueue submitted to AIO
+ ENQ, ///< Data block enqueue AIO write complete (enqueue complete)
+ DEQ_CACHED, ///< Data block dequeue written to page cache
+ DEQ_PART, ///< Data block part-submitted to AIO, waiting for page buffer to free up
+ DEQ_SUBM, ///< Data block dequeue submitted to AIO
+ DEQ, ///< Data block dequeue AIO write complete (dequeue complete)
+ ABORT_CACHED,
+ ABORT_PART,
+ ABORT_SUBM,
+ ABORTED,
+ COMMIT_CACHED,
+ COMMIT_PART,
+ COMMIT_SUBM,
+ COMMITTED
+ };
+
+ protected:
+ static smutex _mutex;
+ static uint64_t _cnt;
+ uint64_t _icnt;
+ write_state _wstate; ///< Enqueued / dequeued state of data
+ std::size_t _dsize; ///< Data size in bytes
+ uint32_t _dblks_written; ///< Data blocks read/written
+ uint32_t _pg_cnt; ///< Page counter - incr for each page containing part of data
+ uint64_t _fid; ///< FID containing header of enqueue record
+ uint64_t _rid; ///< RID of data set by enqueue operation
+ std::string _xid; ///< XID set by enqueue operation
+ uint64_t _dequeue_rid; ///< RID of data set by dequeue operation
+ bool _external_rid; ///< Flag to indicate external setting of rid
+
+ public:
+ data_tok();
+ virtual ~data_tok();
+
+ inline uint64_t id() const { return _icnt; }
+ inline write_state wstate() const { return _wstate; }
+ const char* wstate_str() const;
+ static const char* wstate_str(write_state wstate);
+ inline bool is_writable() const { return _wstate == NONE || _wstate == ENQ_PART; }
+ inline bool is_enqueued() const { return _wstate == ENQ; }
+ inline bool is_readable() const { return _wstate == ENQ; }
+ inline bool is_dequeueable() const { return _wstate == ENQ || _wstate == DEQ_PART; }
+ inline void set_wstate(const write_state wstate) { _wstate = wstate; }
+ inline std::size_t dsize() const { return _dsize; }
+ inline void set_dsize(std::size_t dsize) { _dsize = dsize; }
+
+ inline uint32_t dblocks_written() const { return _dblks_written; }
+ inline void incr_dblocks_written(uint32_t dblks_written)
+ { _dblks_written += dblks_written; }
+ inline void set_dblocks_written(uint32_t dblks_written) { _dblks_written = dblks_written; }
+
+ inline uint32_t pg_cnt() const { return _pg_cnt; }
+ inline uint32_t incr_pg_cnt() { return ++_pg_cnt; }
+ inline uint32_t decr_pg_cnt() { assert(_pg_cnt != 0); return --_pg_cnt; }
+
+ inline uint64_t fid() const { return _fid; }
+ inline void set_fid(const uint64_t fid) { _fid = fid; }
+ inline uint64_t rid() const { return _rid; }
+ inline void set_rid(const uint64_t rid) { _rid = rid; }
+ inline uint64_t dequeue_rid() const {return _dequeue_rid; }
+ inline void set_dequeue_rid(const uint64_t rid) { _dequeue_rid = rid; }
+ inline bool external_rid() const { return _external_rid; }
+ inline void set_external_rid(const bool external_rid) { _external_rid = external_rid; }
+
+ inline bool has_xid() const { return !_xid.empty(); }
+ inline const std::string& xid() const { return _xid; }
+ inline void clear_xid() { _xid.clear(); }
+ inline void set_xid(const std::string& xid) { _xid.assign(xid); }
+ inline void set_xid(const void* xidp, const std::size_t xid_len)
+ { _xid.assign((const char*)xidp, xid_len); }
+
+ void reset();
+
+ // debug aid
+ std::string status_str() const;
+ };
+
+}}}
+
+#endif // ifndef QPID_LINEARSTORE_JOURNAL_DATA_TOK_H
diff --git a/qpid/cpp/src/qpid/linearstore/journal/deq_rec.cpp b/qpid/cpp/src/qpid/linearstore/journal/deq_rec.cpp
new file mode 100644
index 0000000000..90ca27d082
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/deq_rec.cpp
@@ -0,0 +1,313 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/linearstore/journal/deq_rec.h"
+
+#include <cassert>
+#include <cstring>
+#include "qpid/linearstore/journal/Checksum.h"
+#include "qpid/linearstore/journal/jexception.h"
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+deq_rec::deq_rec():
+ _xidp(0),
+ _xid_buff(0)
+{
+ ::deq_hdr_init(&_deq_hdr, QLS_DEQ_MAGIC, QLS_JRNL_VERSION, 0, 0, 0, 0, 0);
+ ::rec_tail_copy(&_deq_tail, &_deq_hdr._rhdr, 0);
+}
+
+deq_rec::~deq_rec()
+{
+ clean();
+}
+
+void
+deq_rec::reset(const uint64_t serial, const uint64_t rid, const uint64_t drid, const void* const xidp,
+ const std::size_t xidlen, const bool txn_coml_commit)
+{
+ _deq_hdr._rhdr._serial = serial;
+ _deq_hdr._rhdr._rid = rid;
+ ::set_txn_coml_commit(&_deq_hdr, txn_coml_commit);
+ _deq_hdr._deq_rid = drid;
+ _deq_hdr._xidsize = xidlen;
+ _xidp = xidp;
+ _xid_buff = 0;
+ _deq_tail._serial = serial;
+ _deq_tail._rid = rid;
+ _deq_tail._checksum = 0UL;
+}
+
+uint32_t
+deq_rec::encode(void* wptr, uint32_t rec_offs_dblks, uint32_t max_size_dblks, Checksum& checksum)
+{
+ assert(wptr != 0);
+ assert(max_size_dblks > 0);
+ if (_xidp == 0)
+ assert(_deq_hdr._xidsize == 0);
+
+ std::size_t rec_offs = rec_offs_dblks * QLS_DBLK_SIZE_BYTES;
+ std::size_t rem = max_size_dblks * QLS_DBLK_SIZE_BYTES;
+ std::size_t wr_cnt = 0;
+
+ if (rec_offs_dblks) // Continuation of split dequeue record (over 2 or more pages)
+ {
+ if (size_dblks(rec_size()) - rec_offs_dblks > max_size_dblks) // Further split required
+ {
+ rec_offs -= sizeof(_deq_hdr);
+ std::size_t wsize = _deq_hdr._xidsize > rec_offs ? _deq_hdr._xidsize - rec_offs : 0;
+ std::size_t wsize2 = wsize;
+ if (wsize)
+ {
+ if (wsize > rem)
+ wsize = rem;
+ std::memcpy(wptr, (const char*)_xidp + rec_offs, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ rec_offs -= _deq_hdr._xidsize - wsize2;
+ checksum.addData((unsigned char*)wptr, wr_cnt);
+ if (rem)
+ {
+ _deq_tail._checksum = checksum.getChecksum();
+ wsize = sizeof(_deq_tail) > rec_offs ? sizeof(_deq_tail) - rec_offs : 0;
+ wsize2 = wsize;
+ if (wsize)
+ {
+ if (wsize > rem)
+ wsize = rem;
+ std::memcpy((char*)wptr + wr_cnt, (char*)&_deq_tail + rec_offs, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ rec_offs -= sizeof(_deq_tail) - wsize2;
+ }
+ assert(rem == 0);
+ assert(rec_offs == 0);
+ }
+ else // No further split required
+ {
+ rec_offs -= sizeof(_deq_hdr);
+ std::size_t wsize = _deq_hdr._xidsize > rec_offs ? _deq_hdr._xidsize - rec_offs : 0;
+ if (wsize)
+ {
+ std::memcpy(wptr, (const char*)_xidp + rec_offs, wsize);
+ wr_cnt += wsize;
+ checksum.addData((unsigned char*)wptr, wr_cnt);
+ }
+ rec_offs -= _deq_hdr._xidsize - wsize;
+ _deq_tail._checksum = checksum.getChecksum();
+ wsize = sizeof(_deq_tail) > rec_offs ? sizeof(_deq_tail) - rec_offs : 0;
+ if (wsize)
+ {
+ std::memcpy((char*)wptr + wr_cnt, (char*)&_deq_tail + rec_offs, wsize);
+ wr_cnt += wsize;
+#ifdef QLS_CLEAN
+ std::size_t rec_offs = rec_offs_dblks * QLS_DBLK_SIZE_BYTES;
+ std::size_t dblk_rec_size = size_dblks(rec_size() - rec_offs) * QLS_DBLK_SIZE_BYTES;
+ std::memset((char*)wptr + wr_cnt, QLS_CLEAN_CHAR, dblk_rec_size - wr_cnt);
+#endif
+ }
+ rec_offs -= sizeof(_deq_tail) - wsize;
+ assert(rec_offs == 0);
+ }
+ }
+ else // Start at beginning of data record
+ {
+ // Assumption: the header will always fit into the first dblk
+ std::memcpy(wptr, (void*)&_deq_hdr, sizeof(_deq_hdr));
+ wr_cnt = sizeof(_deq_hdr);
+ if (size_dblks(rec_size()) > max_size_dblks) // Split required - can only occur with xid
+ {
+ std::size_t wsize;
+ rem -= sizeof(_deq_hdr);
+ if (rem)
+ {
+ wsize = rem >= _deq_hdr._xidsize ? _deq_hdr._xidsize : rem;
+ std::memcpy((char*)wptr + wr_cnt, _xidp, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ checksum.addData((unsigned char*)wptr, wr_cnt);
+ if (rem)
+ {
+ _deq_tail._checksum = checksum.getChecksum();
+ wsize = rem >= sizeof(_deq_tail) ? sizeof(_deq_tail) : rem;
+ std::memcpy((char*)wptr + wr_cnt, (void*)&_deq_tail, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ assert(rem == 0);
+ }
+ else // No split required
+ {
+ if (_deq_hdr._xidsize)
+ {
+ std::memcpy((char*)wptr + wr_cnt, _xidp, _deq_hdr._xidsize);
+ wr_cnt += _deq_hdr._xidsize;
+ checksum.addData((unsigned char*)wptr, wr_cnt);
+ _deq_tail._checksum = checksum.getChecksum();
+ std::memcpy((char*)wptr + wr_cnt, (void*)&_deq_tail, sizeof(_deq_tail));
+ wr_cnt += sizeof(_deq_tail);
+ }
+#ifdef QLS_CLEAN
+ std::size_t dblk_rec_size = size_dblks(rec_size()) * QLS_DBLK_SIZE_BYTES;
+ std::memset((char*)wptr + wr_cnt, QLS_CLEAN_CHAR, dblk_rec_size - wr_cnt);
+#endif
+ }
+ }
+ return size_dblks(wr_cnt);
+}
+
+bool
+deq_rec::decode(::rec_hdr_t& h, std::ifstream* ifsp, std::size_t& rec_offs, const std::streampos rec_start)
+{
+ if (rec_offs == 0)
+ {
+ ::rec_hdr_copy(&_deq_hdr._rhdr, &h);
+ ifsp->read((char*)&_deq_hdr._deq_rid, sizeof(_deq_hdr._deq_rid));
+ ifsp->read((char*)&_deq_hdr._xidsize, sizeof(_deq_hdr._xidsize));
+ rec_offs = sizeof(::deq_hdr_t);
+ // Read header, allocate (if req'd) for xid
+ if (_deq_hdr._xidsize)
+ {
+ _xid_buff = std::malloc(_deq_hdr._xidsize);
+ MALLOC_CHK(_xid_buff, "_buff", "enq_rec", "rcv_decode");
+ }
+ }
+ if (rec_offs < sizeof(_deq_hdr) + _deq_hdr._xidsize)
+ {
+ // Read xid (or continue reading xid)
+ std::size_t offs = rec_offs - sizeof(_deq_hdr);
+ ifsp->read((char*)_xid_buff + offs, _deq_hdr._xidsize - offs);
+ std::size_t size_read = ifsp->gcount();
+ rec_offs += size_read;
+ if (size_read < _deq_hdr._xidsize - offs)
+ {
+ assert(ifsp->eof());
+ // As we may have read past eof, turn off fail bit
+ ifsp->clear(ifsp->rdstate()&(~std::ifstream::failbit));
+ assert(!ifsp->fail() && !ifsp->bad());
+ return false;
+ }
+ }
+ if (rec_offs < sizeof(_deq_hdr) +
+ (_deq_hdr._xidsize ? _deq_hdr._xidsize + sizeof(rec_tail_t) : 0))
+ {
+ // Read tail (or continue reading tail)
+ std::size_t offs = rec_offs - sizeof(_deq_hdr) - _deq_hdr._xidsize;
+ ifsp->read((char*)&_deq_tail + offs, sizeof(rec_tail_t) - offs);
+ std::size_t size_read = ifsp->gcount();
+ rec_offs += size_read;
+ if (size_read < sizeof(rec_tail_t) - offs)
+ {
+ assert(ifsp->eof());
+ // As we may have read past eof, turn off fail bit
+ ifsp->clear(ifsp->rdstate()&(~std::ifstream::failbit));
+ assert(!ifsp->fail() && !ifsp->bad());
+ return false;
+ }
+ check_rec_tail(rec_start);
+ }
+ ifsp->ignore(rec_size_dblks() * QLS_DBLK_SIZE_BYTES - rec_size());
+ assert(!ifsp->fail() && !ifsp->bad());
+ return true;
+}
+
+std::size_t
+deq_rec::get_xid(void** const xidpp)
+{
+ if (!_xid_buff)
+ {
+ *xidpp = 0;
+ return 0;
+ }
+ *xidpp = _xid_buff;
+ return _deq_hdr._xidsize;
+}
+
+std::string&
+deq_rec::str(std::string& str) const
+{
+ std::ostringstream oss;
+ oss << "deq_rec: m=" << _deq_hdr._rhdr._magic;
+ oss << " v=" << (int)_deq_hdr._rhdr._version;
+ oss << " rid=" << _deq_hdr._rhdr._rid;
+ oss << " drid=" << _deq_hdr._deq_rid;
+ if (_xidp)
+ oss << " xid=\"" << _xidp << "\"";
+ str.append(oss.str());
+ return str;
+}
+
+std::size_t
+deq_rec::xid_size() const
+{
+ return _deq_hdr._xidsize;
+}
+
+std::size_t
+deq_rec::rec_size() const
+{
+ return sizeof(deq_hdr_t) + (_deq_hdr._xidsize ? _deq_hdr._xidsize + sizeof(rec_tail_t) : 0);
+}
+
+void
+deq_rec::check_rec_tail(const std::streampos rec_start) const {
+ Checksum checksum;
+ checksum.addData((const unsigned char*)&_deq_hdr, sizeof(::deq_hdr_t));
+ if (_deq_hdr._xidsize > 0) {
+ checksum.addData((const unsigned char*)_xid_buff, _deq_hdr._xidsize);
+ }
+ uint32_t cs = checksum.getChecksum();
+ uint16_t res = ::rec_tail_check(&_deq_tail, &_deq_hdr._rhdr, cs);
+ if (res != 0) {
+ std::stringstream oss;
+ oss << std::endl << " Record offset: 0x" << std::hex << rec_start;
+ if (res & ::REC_TAIL_MAGIC_ERR_MASK) {
+ oss << std::endl << " Magic: expected 0x" << ~_deq_hdr._rhdr._magic << "; found 0x" << _deq_tail._xmagic;
+ }
+ if (res & ::REC_TAIL_SERIAL_ERR_MASK) {
+ oss << std::endl << " Serial: expected 0x" << _deq_hdr._rhdr._serial << "; found 0x" << _deq_tail._serial;
+ }
+ if (res & ::REC_TAIL_RID_ERR_MASK) {
+ oss << std::endl << " Record Id: expected 0x" << _deq_hdr._rhdr._rid << "; found 0x" << _deq_tail._rid;
+ }
+ if (res & ::REC_TAIL_CHECKSUM_ERR_MASK) {
+ oss << std::endl << " Checksum: expected 0x" << cs << "; found 0x" << _deq_tail._checksum;
+ }
+ throw jexception(jerrno::JERR_JREC_BADRECTAIL, oss.str(), "deq_rec", "check_rec_tail");
+ }
+}
+
+void
+deq_rec::clean()
+{
+ if (_xid_buff) {
+ std::free(_xid_buff);
+ _xid_buff = 0;
+ }
+}
+
+}}}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/deq_rec.h b/qpid/cpp/src/qpid/linearstore/journal/deq_rec.h
new file mode 100644
index 0000000000..9f55032e76
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/deq_rec.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_DEQ_REQ_H
+#define QPID_LINEARSTORE_JOURNAL_DEQ_REQ_H
+
+#include "qpid/linearstore/journal/jrec.h"
+#include "qpid/linearstore/journal/utils/deq_hdr.h"
+#include "qpid/linearstore/journal/utils/rec_tail.h"
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+/**
+* \class deq_rec
+* \brief Class to handle a single journal dequeue record.
+*/
+class deq_rec : public jrec
+{
+private:
+ ::deq_hdr_t _deq_hdr; ///< Local instance of dequeue header struct
+ const void* _xidp; ///< xid pointer for encoding (writing to disk)
+ void* _xid_buff; ///< Pointer to buffer to receive xid read from disk
+ ::rec_tail_t _deq_tail; ///< Local instance of enqueue tail struct, only encoded if XID is present
+
+public:
+ deq_rec();
+ virtual ~deq_rec();
+
+ void reset(const uint64_t serial, const uint64_t rid, const uint64_t drid, const void* const xidp,
+ const std::size_t xidlen, const bool txn_coml_commit);
+ uint32_t encode(void* wptr, uint32_t rec_offs_dblks, uint32_t max_size_dblks, Checksum& checksum);
+ bool decode(::rec_hdr_t& h, std::ifstream* ifsp, std::size_t& rec_offs, const std::streampos rec_start);
+
+ inline bool is_txn_coml_commit() const { return ::is_txn_coml_commit(&_deq_hdr); }
+ inline uint64_t rid() const { return _deq_hdr._rhdr._rid; }
+ inline uint64_t deq_rid() const { return _deq_hdr._deq_rid; }
+ std::size_t get_xid(void** const xidpp);
+ std::string& str(std::string& str) const;
+ inline std::size_t data_size() const { return 0; } // This record never carries data
+ std::size_t xid_size() const;
+ std::size_t rec_size() const;
+ void check_rec_tail(const std::streampos rec_start) const;
+
+private:
+ virtual void clean();
+};
+
+}}}
+
+#endif // ifndef QPID_LINEARSTORE_JOURNAL_DEQ_REQ_H
diff --git a/qpid/cpp/src/qpid/linearstore/journal/enq_map.cpp b/qpid/cpp/src/qpid/linearstore/journal/enq_map.cpp
new file mode 100644
index 0000000000..4eaaa64992
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/enq_map.cpp
@@ -0,0 +1,181 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/linearstore/journal/enq_map.h"
+
+#include "qpid/linearstore/journal/slock.h"
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+// static return/error codes
+int16_t enq_map::EMAP_DUP_RID = -3;
+int16_t enq_map::EMAP_LOCKED = -2;
+int16_t enq_map::EMAP_RID_NOT_FOUND = -1;
+int16_t enq_map::EMAP_OK = 0;
+int16_t enq_map::EMAP_FALSE = 0;
+int16_t enq_map::EMAP_TRUE = 1;
+
+enq_map::enq_map():
+ _map(){}
+
+enq_map::~enq_map() {}
+
+
+short
+enq_map::insert_pfid(const uint64_t rid, const uint64_t pfid, const std::streampos file_posn)
+{
+ return insert_pfid(rid, pfid, file_posn, false);
+}
+
+short
+enq_map::insert_pfid(const uint64_t rid, const uint64_t pfid, const std::streampos file_posn, const bool locked)
+{
+ std::pair<emap_itr, bool> ret;
+ emap_data_struct_t rec(pfid, file_posn, locked);
+ {
+ slock s(_mutex);
+ ret = _map.insert(emap_param(rid, rec));
+ }
+ if (ret.second == false)
+ return EMAP_DUP_RID;
+ return EMAP_OK;
+}
+
+short
+enq_map::get_pfid(const uint64_t rid, uint64_t& pfid)
+{
+ slock s(_mutex);
+ emap_itr itr = _map.find(rid);
+ if (itr == _map.end()) // not found in map
+ return EMAP_RID_NOT_FOUND;
+ if (itr->second._lock)
+ return EMAP_LOCKED;
+ pfid = itr->second._pfid;
+ return EMAP_OK;
+}
+
+short
+enq_map::get_remove_pfid(const uint64_t rid, uint64_t& pfid, const bool txn_flag)
+{
+ slock s(_mutex);
+ emap_itr itr = _map.find(rid);
+ if (itr == _map.end()) // not found in map
+ return EMAP_RID_NOT_FOUND;
+ if (itr->second._lock && !txn_flag) // locked, but not a commit/abort
+ return EMAP_LOCKED;
+ pfid = itr->second._pfid;
+ _map.erase(itr);
+ return EMAP_OK;
+}
+
+short
+enq_map::get_file_posn(const uint64_t rid, std::streampos& file_posn) {
+ slock s(_mutex);
+ emap_itr itr = _map.find(rid);
+ if (itr == _map.end()) // not found in map
+ return EMAP_RID_NOT_FOUND;
+ if (itr->second._lock)
+ return EMAP_LOCKED;
+ file_posn = itr->second._file_posn;
+ return EMAP_OK;
+}
+
+short
+enq_map::get_data(const uint64_t rid, emap_data_struct_t& eds) {
+ slock s(_mutex);
+ emap_itr itr = _map.find(rid);
+ if (itr == _map.end()) // not found in map
+ return EMAP_RID_NOT_FOUND;
+ eds._pfid = itr->second._pfid;
+ eds._file_posn = itr->second._file_posn;
+ eds._lock = itr->second._lock;
+ return EMAP_OK;
+}
+
+bool
+enq_map::is_enqueued(const uint64_t rid, bool ignore_lock)
+{
+ slock s(_mutex);
+ emap_itr itr = _map.find(rid);
+ if (itr == _map.end()) // not found in map
+ return false;
+ if (!ignore_lock && itr->second._lock) // locked
+ return false;
+ return true;
+}
+
+short
+enq_map::lock(const uint64_t rid)
+{
+ slock s(_mutex);
+ emap_itr itr = _map.find(rid);
+ if (itr == _map.end()) // not found in map
+ return EMAP_RID_NOT_FOUND;
+ itr->second._lock = true;
+ return EMAP_OK;
+}
+
+short
+enq_map::unlock(const uint64_t rid)
+{
+ slock s(_mutex);
+ emap_itr itr = _map.find(rid);
+ if (itr == _map.end()) // not found in map
+ return EMAP_RID_NOT_FOUND;
+ itr->second._lock = false;
+ return EMAP_OK;
+}
+
+short
+enq_map::is_locked(const uint64_t rid)
+{
+ slock s(_mutex);
+ emap_itr itr = _map.find(rid);
+ if (itr == _map.end()) // not found in map
+ return EMAP_RID_NOT_FOUND;
+ return itr->second._lock ? EMAP_TRUE : EMAP_FALSE;
+}
+
+void
+enq_map::rid_list(std::vector<uint64_t>& rv)
+{
+ rv.clear();
+ {
+ slock s(_mutex);
+ for (emap_itr itr = _map.begin(); itr != _map.end(); itr++)
+ rv.push_back(itr->first);
+ }
+}
+
+void
+enq_map::pfid_list(std::vector<uint64_t>& fv)
+{
+ fv.clear();
+ {
+ slock s(_mutex);
+ for (emap_itr itr = _map.begin(); itr != _map.end(); itr++)
+ fv.push_back(itr->second._pfid);
+ }
+}
+
+}}}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/enq_map.h b/qpid/cpp/src/qpid/linearstore/journal/enq_map.h
new file mode 100644
index 0000000000..912a583ab9
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/enq_map.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_ENQ_MAP_H
+#define QPID_LINEARSTORE_JOURNAL_ENQ_MAP_H
+
+#include "qpid/linearstore/journal/smutex.h"
+#include <vector>
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+/**
+* \class enq_map
+* \brief Class for storing the physical file id (pfid) and a transaction locked flag for each enqueued
+* data block using the record id (rid) as a key. This is the primary mechanism for
+* deterimining the enqueue low water mark: if a pfid exists in this map, then there is
+* at least one still-enqueued record in that file. (The transaction map must also be
+* clear, however.)
+*
+* Map rids against pfid and lock status. As records are enqueued, they are added to this
+* map, and as they are dequeued, they are removed. An enqueue is locked when a transactional
+* dequeue is pending that has been neither committed nor aborted.
+* <pre>
+* key data
+*
+* rid1 --- [ pfid, txn_lock ]
+* rid2 --- [ pfid, txn_lock ]
+* rid3 --- [ pfid, txn_lock ]
+* ...
+* </pre>
+*/
+class enq_map
+{
+public:
+ // return/error codes
+ static short EMAP_DUP_RID;
+ static short EMAP_LOCKED;
+ static short EMAP_RID_NOT_FOUND;
+ static short EMAP_OK;
+ static short EMAP_FALSE;
+ static short EMAP_TRUE;
+
+ typedef struct emap_data_struct_t {
+ uint64_t _pfid;
+ std::streampos _file_posn;
+ bool _lock;
+ emap_data_struct_t() : _pfid(0), _file_posn(0), _lock(false) {}
+ emap_data_struct_t(const uint64_t pfid, const std::streampos file_posn, const bool lock) : _pfid(pfid), _file_posn(file_posn), _lock(lock) {}
+ } emqp_data_struct_t;
+ typedef std::pair<uint64_t, emap_data_struct_t> emap_param;
+ typedef std::map<uint64_t, emap_data_struct_t> emap;
+ typedef emap::iterator emap_itr;
+
+private:
+ emap _map;
+ smutex _mutex;
+
+public:
+ enq_map();
+ virtual ~enq_map();
+
+ short insert_pfid(const uint64_t rid, const uint64_t pfid, const std::streampos file_posn); // 0=ok; -3=duplicate rid;
+ short insert_pfid(const uint64_t rid, const uint64_t pfid, const std::streampos file_posn, const bool locked); // 0=ok; -3=duplicate rid;
+ short get_pfid(const uint64_t rid, uint64_t& pfid); // >=0=pfid; -1=rid not found; -2=locked
+ short get_remove_pfid(const uint64_t rid, uint64_t& pfid, const bool txn_flag = false); // >=0=pfid; -1=rid not found; -2=locked
+ short get_file_posn(const uint64_t rid, std::streampos& file_posn); // -1=rid not found; -2=locked
+ short get_data(const uint64_t rid, emap_data_struct_t& eds);
+ bool is_enqueued(const uint64_t rid, bool ignore_lock = false);
+ short lock(const uint64_t rid); // 0=ok; -1=rid not found
+ short unlock(const uint64_t rid); // 0=ok; -1=rid not found
+ short is_locked(const uint64_t rid); // 1=true; 0=false; -1=rid not found
+ inline void clear() { _map.clear(); }
+ inline bool empty() const { return _map.empty(); }
+ inline uint32_t size() const { return uint32_t(_map.size()); }
+ void rid_list(std::vector<uint64_t>& rv);
+ void pfid_list(std::vector<uint64_t>& fv);
+};
+
+}}}
+
+#endif // ifndef QPID_LINEARSTORE_JOURNAL_ENQ_MAP_H
diff --git a/qpid/cpp/src/qpid/linearstore/journal/enq_rec.cpp b/qpid/cpp/src/qpid/linearstore/journal/enq_rec.cpp
new file mode 100644
index 0000000000..0fecd90cbf
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/enq_rec.cpp
@@ -0,0 +1,397 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/linearstore/journal/enq_rec.h"
+
+#include <cassert>
+#include <cstring>
+#include "qpid/linearstore/journal/Checksum.h"
+#include "qpid/linearstore/journal/jexception.h"
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+enq_rec::enq_rec():
+ jrec(), // superclass
+ _xidp(0),
+ _data(0),
+ _xid_buff(0),
+ _data_buff(0)
+{
+ ::enq_hdr_init(&_enq_hdr, QLS_ENQ_MAGIC, QLS_JRNL_VERSION, 0, 0, 0, 0, false);
+ ::rec_tail_copy(&_enq_tail, &_enq_hdr._rhdr, 0);
+}
+
+enq_rec::~enq_rec()
+{
+ clean();
+}
+
+void
+enq_rec::reset(const uint64_t serial, const uint64_t rid, const void* const dbuf, const std::size_t dlen,
+ const void* const xidp, const std::size_t xidlen, const bool transient, const bool external)
+{
+ _enq_hdr._rhdr._serial = serial;
+ _enq_hdr._rhdr._rid = rid;
+ ::set_enq_transient(&_enq_hdr, transient);
+ ::set_enq_external(&_enq_hdr, external);
+ _enq_hdr._xidsize = xidlen;
+ _enq_hdr._dsize = dlen;
+ _xidp = xidp;
+ _data = dbuf;
+ _enq_tail._serial = serial;
+ _enq_tail._rid = rid;
+}
+
+uint32_t
+enq_rec::encode(void* wptr, uint32_t rec_offs_dblks, uint32_t max_size_dblks, Checksum& checksum)
+{
+ assert(wptr != 0);
+ assert(max_size_dblks > 0);
+ if (_xidp == 0)
+ assert(_enq_hdr._xidsize == 0);
+
+ std::size_t rec_offs = rec_offs_dblks * QLS_DBLK_SIZE_BYTES;
+ std::size_t rem = max_size_dblks * QLS_DBLK_SIZE_BYTES;
+ std::size_t wr_cnt = 0;
+ if (rec_offs_dblks) // Continuation of split data record (over 2 or more pages)
+ {
+ if (size_dblks(rec_size()) - rec_offs_dblks > max_size_dblks) // Further split required
+ {
+ rec_offs -= sizeof(_enq_hdr);
+ std::size_t wsize = _enq_hdr._xidsize > rec_offs ? _enq_hdr._xidsize - rec_offs : 0;
+ std::size_t wsize2 = wsize;
+ if (wsize)
+ {
+ if (wsize > rem)
+ wsize = rem;
+ std::memcpy(wptr, (const char*)_xidp + rec_offs, wsize);
+ wr_cnt = wsize;
+ rem -= wsize;
+ }
+ rec_offs -= _enq_hdr._xidsize - wsize2;
+ if (rem && !::is_enq_external(&_enq_hdr))
+ {
+ wsize = _enq_hdr._dsize > rec_offs ? _enq_hdr._dsize - rec_offs : 0;
+ wsize2 = wsize;
+ if (wsize)
+ {
+ if (wsize > rem)
+ wsize = rem;
+ std::memcpy((char*)wptr + wr_cnt, (const char*)_data + rec_offs, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ rec_offs -= _enq_hdr._dsize - wsize2;
+ }
+ checksum.addData((unsigned char*)wptr, wr_cnt);
+ if (rem)
+ {
+ _enq_tail._checksum = checksum.getChecksum();
+ wsize = sizeof(_enq_tail) > rec_offs ? sizeof(_enq_tail) - rec_offs : 0;
+ wsize2 = wsize;
+ if (wsize)
+ {
+ if (wsize > rem)
+ wsize = rem;
+ std::memcpy((char*)wptr + wr_cnt, (char*)&_enq_tail + rec_offs, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ rec_offs -= sizeof(_enq_tail) - wsize2;
+ }
+ assert(rem == 0);
+ assert(rec_offs == 0);
+ }
+ else // No further split required
+ {
+ rec_offs -= sizeof(_enq_hdr);
+ std::size_t xid_wsize = _enq_hdr._xidsize > rec_offs ? _enq_hdr._xidsize - rec_offs : 0;
+ if (xid_wsize)
+ {
+ std::memcpy(wptr, (const char*)_xidp + rec_offs, xid_wsize);
+ wr_cnt += xid_wsize;
+ }
+ rec_offs -= _enq_hdr._xidsize - xid_wsize;
+ std::size_t data_wsize = _enq_hdr._dsize > rec_offs ? _enq_hdr._dsize - rec_offs : 0;
+ if (data_wsize && !::is_enq_external(&_enq_hdr))
+ {
+ std::memcpy((char*)wptr + wr_cnt, (const char*)_data + rec_offs, data_wsize);
+ wr_cnt += data_wsize;
+ }
+ rec_offs -= _enq_hdr._dsize - data_wsize;
+ if (xid_wsize || data_wsize) {
+ checksum.addData((unsigned char*)wptr, wr_cnt);
+ }
+ _enq_tail._checksum = checksum.getChecksum();
+ std::size_t wsize = sizeof(_enq_tail) > rec_offs ? sizeof(_enq_tail) - rec_offs : 0;
+ if (wsize)
+ {
+ std::memcpy((char*)wptr + wr_cnt, (char*)&_enq_tail + rec_offs, wsize);
+ wr_cnt += wsize;
+#ifdef QLS_CLEAN
+ std::size_t rec_offs = rec_offs_dblks * QLS_DBLK_SIZE_BYTES;
+ std::size_t dblk_rec_size = size_dblks(rec_size() - rec_offs) * QLS_DBLK_SIZE_BYTES;
+ std::memset((char*)wptr + wr_cnt, QLS_CLEAN_CHAR, dblk_rec_size - wr_cnt);
+#endif
+ }
+ rec_offs -= sizeof(_enq_tail) - wsize;
+ assert(rec_offs == 0);
+ }
+ }
+ else // Start at beginning of data record
+ {
+ // Assumption: the header will always fit into the first dblk
+ std::memcpy(wptr, (void*)&_enq_hdr, sizeof(_enq_hdr));
+ wr_cnt = sizeof(_enq_hdr);
+ if (size_dblks(rec_size()) > max_size_dblks) // Split required
+ {
+ std::size_t wsize;
+ rem -= sizeof(_enq_hdr);
+ if (rem)
+ {
+ wsize = rem >= _enq_hdr._xidsize ? _enq_hdr._xidsize : rem;
+ std::memcpy((char*)wptr + wr_cnt, _xidp, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ if (rem && !::is_enq_external(&_enq_hdr))
+ {
+ wsize = rem >= _enq_hdr._dsize ? _enq_hdr._dsize : rem;
+ std::memcpy((char*)wptr + wr_cnt, _data, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ checksum.addData((unsigned char*)wptr, wr_cnt);
+ if (rem)
+ {
+ _enq_tail._checksum = checksum.getChecksum();
+ wsize = rem >= sizeof(_enq_tail) ? sizeof(_enq_tail) : rem;
+ std::memcpy((char*)wptr + wr_cnt, (void*)&_enq_tail, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ assert(rem == 0);
+ }
+ else // No split required
+ {
+ if (_enq_hdr._xidsize)
+ {
+ std::memcpy((char*)wptr + wr_cnt, _xidp, _enq_hdr._xidsize);
+ wr_cnt += _enq_hdr._xidsize;
+ }
+ if (!::is_enq_external(&_enq_hdr))
+ {
+ std::memcpy((char*)wptr + wr_cnt, _data, _enq_hdr._dsize);
+ wr_cnt += _enq_hdr._dsize;
+ }
+ checksum.addData((unsigned char*)wptr, wr_cnt);
+ _enq_tail._checksum = checksum.getChecksum();
+ std::memcpy((char*)wptr + wr_cnt, (void*)&_enq_tail, sizeof(_enq_tail));
+ wr_cnt += sizeof(_enq_tail);
+#ifdef QLS_CLEAN
+ std::size_t dblk_rec_size = size_dblks(rec_size()) * QLS_DBLK_SIZE_BYTES;
+ std::memset((char*)wptr + wr_cnt, QLS_CLEAN_CHAR, dblk_rec_size - wr_cnt);
+#endif
+ }
+ }
+ return size_dblks(wr_cnt);
+}
+
+bool
+enq_rec::decode(::rec_hdr_t& h, std::ifstream* ifsp, std::size_t& rec_offs, const std::streampos rec_start)
+{
+ if (rec_offs == 0)
+ {
+ // Read header, allocate (if req'd) for xid
+ ::rec_hdr_copy(&_enq_hdr._rhdr, &h);
+ ifsp->read((char*)&_enq_hdr._xidsize, sizeof(_enq_hdr._xidsize));
+ ifsp->read((char*)&_enq_hdr._dsize, sizeof(_enq_hdr._dsize));
+ rec_offs = sizeof(::enq_hdr_t);
+ if (_enq_hdr._xidsize > 0)
+ {
+ _xid_buff = std::malloc(_enq_hdr._xidsize);
+ MALLOC_CHK(_xid_buff, "_xid_buff", "enq_rec", "decode");
+ }
+ if (_enq_hdr._dsize > 0)
+ {
+ _data_buff = std::malloc(_enq_hdr._dsize);
+ MALLOC_CHK(_data_buff, "_data_buff", "enq_rec", "decode")
+ }
+ }
+ if (rec_offs < sizeof(_enq_hdr) + _enq_hdr._xidsize)
+ {
+ // Read xid (or continue reading xid)
+ std::size_t offs = rec_offs - sizeof(_enq_hdr);
+ ifsp->read((char*)_xid_buff + offs, _enq_hdr._xidsize - offs);
+ std::size_t size_read = ifsp->gcount();
+ rec_offs += size_read;
+ if (size_read < _enq_hdr._xidsize - offs)
+ {
+ assert(ifsp->eof());
+ // As we may have read past eof, turn off fail bit
+ ifsp->clear(ifsp->rdstate()&(~std::ifstream::failbit));
+ assert(!ifsp->fail() && !ifsp->bad());
+ return false;
+ }
+ }
+ if (!::is_enq_external(&_enq_hdr))
+ {
+ if (rec_offs < sizeof(_enq_hdr) + _enq_hdr._xidsize + _enq_hdr._dsize)
+ {
+ // Read data (or continue reading data)
+ std::size_t offs = rec_offs - sizeof(_enq_hdr) - _enq_hdr._xidsize;
+ ifsp->read((char*)_data_buff + offs, _enq_hdr._dsize - offs);
+ std::size_t size_read = ifsp->gcount();
+ rec_offs += size_read;
+ if (size_read < _enq_hdr._dsize - offs)
+ {
+ assert(ifsp->eof());
+ // As we may have read past eof, turn off fail bit
+ ifsp->clear(ifsp->rdstate()&(~std::ifstream::failbit));
+ assert(!ifsp->fail() && !ifsp->bad());
+ return false;
+ }
+ }
+ }
+ if (rec_offs < sizeof(_enq_hdr) + _enq_hdr._xidsize +
+ (::is_enq_external(&_enq_hdr) ? 0 : _enq_hdr._dsize) + sizeof(rec_tail_t))
+ {
+ // Read tail (or continue reading tail)
+ std::size_t offs = rec_offs - sizeof(_enq_hdr) - _enq_hdr._xidsize;
+ if (!::is_enq_external(&_enq_hdr))
+ offs -= _enq_hdr._dsize;
+ ifsp->read((char*)&_enq_tail + offs, sizeof(rec_tail_t) - offs);
+ std::size_t size_read = ifsp->gcount();
+ rec_offs += size_read;
+ if (size_read < sizeof(rec_tail_t) - offs)
+ {
+ assert(ifsp->eof());
+ // As we may have read past eof, turn off fail bit
+ ifsp->clear(ifsp->rdstate()&(~std::ifstream::failbit));
+ assert(!ifsp->fail() && !ifsp->bad());
+ return false;
+ }
+ check_rec_tail(rec_start);
+ }
+ ifsp->ignore(rec_size_dblks() * QLS_DBLK_SIZE_BYTES - rec_size());
+ assert(!ifsp->fail() && !ifsp->bad());
+ return true;
+}
+
+std::size_t
+enq_rec::get_xid(void** const xidpp)
+{
+ if (!_xid_buff || !_enq_hdr._xidsize) {
+ *xidpp = 0;
+ return 0;
+ }
+ *xidpp = _xid_buff;
+ return _enq_hdr._xidsize;
+}
+
+std::size_t
+enq_rec::get_data(void** const datapp)
+{
+ if (!_data_buff) {
+ *datapp = 0;
+ return 0;
+ }
+ if (::is_enq_external(&_enq_hdr))
+ *datapp = 0;
+ else
+ *datapp = _data_buff;
+ return _enq_hdr._dsize;
+}
+
+std::string&
+enq_rec::str(std::string& str) const
+{
+ std::ostringstream oss;
+ oss << "enq_rec: m=" << _enq_hdr._rhdr._magic;
+ oss << " v=" << (int)_enq_hdr._rhdr._version;
+ oss << " rid=" << _enq_hdr._rhdr._rid;
+ if (_xidp)
+ oss << " xid=\"" << _xidp << "\"";
+ oss << " len=" << _enq_hdr._dsize;
+ str.append(oss.str());
+ return str;
+}
+
+std::size_t
+enq_rec::rec_size() const
+{
+ return rec_size(_enq_hdr._xidsize, _enq_hdr._dsize, ::is_enq_external(&_enq_hdr));
+}
+
+std::size_t
+enq_rec::rec_size(const std::size_t xidsize, const std::size_t dsize, const bool external)
+{
+ if (external)
+ return sizeof(enq_hdr_t) + xidsize + sizeof(rec_tail_t);
+ return sizeof(enq_hdr_t) + xidsize + dsize + sizeof(rec_tail_t);
+}
+
+void
+enq_rec::check_rec_tail(const std::streampos rec_start) const {
+ Checksum checksum;
+ checksum.addData((const unsigned char*)&_enq_hdr, sizeof(::enq_hdr_t));
+ if (_enq_hdr._xidsize > 0) {
+ checksum.addData((const unsigned char*)_xid_buff, _enq_hdr._xidsize);
+ }
+ if (_enq_hdr._dsize > 0) {
+ checksum.addData((const unsigned char*)_data_buff, _enq_hdr._dsize);
+ }
+ uint32_t cs = checksum.getChecksum();
+ uint16_t res = ::rec_tail_check(&_enq_tail, &_enq_hdr._rhdr, cs);
+ if (res != 0) {
+ std::stringstream oss;
+ oss << std::endl << " Record offset: 0x" << std::hex << rec_start;
+ if (res & ::REC_TAIL_MAGIC_ERR_MASK) {
+ oss << std::endl << " Magic: expected 0x" << ~_enq_hdr._rhdr._magic << "; found 0x" << _enq_tail._xmagic;
+ }
+ if (res & ::REC_TAIL_SERIAL_ERR_MASK) {
+ oss << std::endl << " Serial: expected 0x" << _enq_hdr._rhdr._serial << "; found 0x" << _enq_tail._serial;
+ }
+ if (res & ::REC_TAIL_RID_ERR_MASK) {
+ oss << std::endl << " Record Id: expected 0x" << _enq_hdr._rhdr._rid << "; found 0x" << _enq_tail._rid;
+ }
+ if (res & ::REC_TAIL_CHECKSUM_ERR_MASK) {
+ oss << std::endl << " Checksum: expected 0x" << cs << "; found 0x" << _enq_tail._checksum;
+ }
+ throw jexception(jerrno::JERR_JREC_BADRECTAIL, oss.str(), "enq_rec", "check_rec_tail");
+ }
+}
+
+void
+enq_rec::clean() {
+ if (_xid_buff) {
+ std::free(_xid_buff);
+ _xid_buff = 0;
+ }
+ if (_data_buff) {
+ std::free(_data_buff);
+ _data_buff = 0;
+ }
+}
+
+}}}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/enq_rec.h b/qpid/cpp/src/qpid/linearstore/journal/enq_rec.h
new file mode 100644
index 0000000000..d85cde42f5
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/enq_rec.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_ENQ_REC_H
+#define QPID_LINEARSTORE_JOURNAL_ENQ_REC_H
+
+#include "qpid/linearstore/journal/jrec.h"
+#include "qpid/linearstore/journal/utils/enq_hdr.h"
+#include "qpid/linearstore/journal/utils/rec_tail.h"
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+/**
+* \class enq_rec
+* \brief Class to handle a single journal enqueue record.
+*/
+class enq_rec : public jrec
+{
+private:
+ ::enq_hdr_t _enq_hdr; ///< Local instance of enqueue header struct
+ const void* _xidp; ///< xid pointer for encoding (for writing to disk)
+ const void* _data; ///< Pointer to data to be written to disk
+ void* _xid_buff;
+ void* _data_buff;
+ ::rec_tail_t _enq_tail; ///< Local instance of enqueue tail struct
+
+public:
+ enq_rec();
+ virtual ~enq_rec();
+
+ void reset(const uint64_t serial, const uint64_t rid, const void* const dbuf, const std::size_t dlen,
+ const void* const xidp, const std::size_t xidlen, const bool transient, const bool external);
+ uint32_t encode(void* wptr, uint32_t rec_offs_dblks, uint32_t max_size_dblks, Checksum& checksum);
+ bool decode(::rec_hdr_t& h, std::ifstream* ifsp, std::size_t& rec_offs, const std::streampos rec_start);
+
+ std::size_t get_xid(void** const xidpp);
+ std::size_t get_data(void** const datapp);
+ inline bool is_transient() const { return ::is_enq_transient(&_enq_hdr); }
+ inline bool is_external() const { return ::is_enq_external(&_enq_hdr); }
+ std::string& str(std::string& str) const;
+ inline std::size_t data_size() const { return _enq_hdr._dsize; }
+ inline std::size_t xid_size() const { return _enq_hdr._xidsize; }
+ std::size_t rec_size() const;
+ static std::size_t rec_size(const std::size_t xidsize, const std::size_t dsize, const bool external);
+ inline uint64_t rid() const { return _enq_hdr._rhdr._rid; }
+ void check_rec_tail(const std::streampos rec_start) const;
+
+private:
+ virtual void clean();
+};
+
+}}}
+
+#endif // ifndef QPID_LINEARSTORE_JOURNAL_ENQ_REC_H
diff --git a/qpid/cpp/src/qpid/linearstore/journal/enums.h b/qpid/cpp/src/qpid/linearstore/journal/enums.h
new file mode 100644
index 0000000000..90ec355955
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/enums.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_ENUMS_H
+#define QPID_LINEARSTORE_JOURNAL_ENUMS_H
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+// TODO: Change this to flags, as multiple of these conditions may exist simultaneously
+/**
+* \brief Enumeration of possible return states from journal read and write operations.
+*/
+enum _iores
+{
+ RHM_IORES_SUCCESS = 0, ///< Success: IO operation completed noramlly.
+ RHM_IORES_PAGE_AIOWAIT, ///< IO operation suspended - next page is waiting for AIO.
+ RHM_IORES_FILE_AIOWAIT, ///< IO operation suspended - next file is waiting for AIO.
+ RHM_IORES_EMPTY, ///< During read operations, nothing further is available to read.
+ RHM_IORES_TXPENDING ///< Operation blocked by pending transaction.
+};
+typedef _iores iores;
+
+static inline const char* iores_str(iores res)
+{
+ switch (res)
+ {
+ case RHM_IORES_SUCCESS: return "RHM_IORES_SUCCESS";
+ case RHM_IORES_PAGE_AIOWAIT: return "RHM_IORES_PAGE_AIOWAIT";
+ case RHM_IORES_FILE_AIOWAIT: return "RHM_IORES_FILE_AIOWAIT";
+ case RHM_IORES_EMPTY: return "RHM_IORES_EMPTY";
+ case RHM_IORES_TXPENDING: return "RHM_IORES_TXPENDING";
+ }
+ return "<iores unknown>";
+}
+
+}}}
+
+#endif // ifndef QPID_LINEARSTORE_JOURNAL_ENUMS_H
diff --git a/qpid/cpp/src/qpid/linearstore/journal/jcfg.h b/qpid/cpp/src/qpid/linearstore/journal/jcfg.h
new file mode 100644
index 0000000000..b33a419a9d
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/jcfg.h
@@ -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.
+ *
+ */
+
+#include <cmath>
+#include <cstdlib>
+
+#ifndef QPID_QLS_JRNL_JCFG_H
+#define QPID_QLS_JRNL_JCFG_H
+
+#define QLS_SBLK_SIZE_BYTES 4096 /**< Disk softblock size in bytes, should match size used on disk media */
+#define QLS_AIO_ALIGN_BOUNDARY_BYTES QLS_SBLK_SIZE_BYTES /** Memory alignment boundary used for DMA */
+/**
+* <b>Rule:</b> Data block size (QLS_DBLK_SIZE_BYTES) MUST be a power of 2 AND
+* a power of 2 factor of the disk softblock size (QLS_SBLK_SIZE_BYTES):
+* <pre>
+* n * QLS_DBLK_SIZE_BYTES == QLS_SBLK_SIZE_BYTES (n = 1,2,4,8...)
+* </pre>
+*/
+#define QLS_DBLK_SIZE_BYTES 128 /**< Data block size in bytes (CANNOT BE LESS THAN 32!) */
+#define QLS_SBLK_SIZE_DBLKS (QLS_SBLK_SIZE_BYTES / QLS_DBLK_SIZE_BYTES) /**< Disk softblock size in multiples of QLS_DBLK_SIZE_BYTES */
+#define QLS_SBLK_SIZE_KIB (QLS_SBLK_SIZE_BYTES / 1024) /**< Disk softblock size in KiB */
+
+#define QLS_WMGR_DEF_PAGE_SIZE_KIB 32 /**< Journal write page size in KiB (default) */
+#define QLS_WMGR_DEF_PAGE_SIZE_SBLKS (QLS_WMGR_DEF_PAGE_SIZE_KIB / QLS_SBLK_SIZE_KIB) /**< Journal write page size in softblocks (default) */
+#define QLS_WMGR_DEF_PAGES 32 /**< Number of pages to use in wmgr (default) */
+
+#define QLS_WMGR_MAXDTOKPP 1024 /**< Max. dtoks (data blocks) per page in wmgr */
+#define QLS_WMGR_MAXWAITUS 100 /**< Max. wait time (us) before submitting AIO */
+
+#define QLS_JRNL_FILE_EXTENSION ".jrnl" /**< Extension for journal data files */
+#define QLS_TXA_MAGIC 0x61534c51 /**< ("QLSa" in little endian) Magic for dtx abort hdrs */
+#define QLS_TXC_MAGIC 0x63534c51 /**< ("QLSc" in little endian) Magic for dtx commit hdrs */
+#define QLS_DEQ_MAGIC 0x64534c51 /**< ("QLSd" in little endian) Magic for deq rec hdrs */
+#define QLS_ENQ_MAGIC 0x65534c51 /**< ("QLSe" in little endian) Magic for enq rec hdrs */
+#define QLS_FILE_MAGIC 0x66534c51 /**< ("QLSf" in little endian) Magic for file hdrs */
+#define QLS_EMPTY_MAGIC 0x78534c51 /**< ("QLSx" in little endian) Magic for empty dblk */
+#define QLS_JRNL_VERSION 2 /**< Version (of file layout) */
+#define QLS_JRNL_FHDR_RES_SIZE_SBLKS 1 /**< Journal file header reserved size in sblks (as defined by QLS_SBLK_SIZE_BYTES) */
+#define QLS_MAX_QUEUE_NAME_LEN (QLS_JRNL_FHDR_RES_SIZE_SBLKS * QLS_SBLK_SIZE_BYTES) - sizeof(file_hdr_t)
+
+#define QLS_CLEAN /**< If defined, writes QLS_CLEAN_CHAR to all filled areas on disk */
+#define QLS_CLEAN_CHAR 0xff /**< Char used to clear empty space on disk */
+
+namespace qpid {
+namespace linearstore {
+
+ const int QLS_RAND_WIDTH = (int)(::log((RAND_MAX + 1ULL))/::log(2));
+ const int QLS_RAND_SHIFT1 = 64 - QLS_RAND_WIDTH;
+ const int QLS_RAND_SHIFT2 = QLS_RAND_SHIFT1 - QLS_RAND_WIDTH;
+ const int QLS_RAND_MASK = (int)::pow(2, QLS_RAND_SHIFT2) - 1;
+
+}}
+
+#endif /* ifndef QPID_QLS_JRNL_JCFG_H */
diff --git a/qpid/cpp/src/qpid/linearstore/journal/jcntl.cpp b/qpid/cpp/src/qpid/linearstore/journal/jcntl.cpp
new file mode 100644
index 0000000000..cc31f2e1df
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/jcntl.cpp
@@ -0,0 +1,440 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/linearstore/journal/jcntl.h"
+
+#include <iomanip>
+#include "qpid/linearstore/journal/data_tok.h"
+#include "qpid/linearstore/journal/JournalLog.h"
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+#define AIO_CMPL_TIMEOUT_SEC 5
+#define AIO_CMPL_TIMEOUT_NSEC 0
+#define FINAL_AIO_CMPL_TIMEOUT_SEC 15
+#define FINAL_AIO_CMPL_TIMEOUT_NSEC 0
+
+// Static
+timespec jcntl::_aio_cmpl_timeout; ///< Timeout for blocking libaio returns
+timespec jcntl::_final_aio_cmpl_timeout; ///< Timeout for blocking libaio returns when stopping or finalizing
+bool jcntl::_init = init_statics();
+bool jcntl::init_statics()
+{
+ _aio_cmpl_timeout.tv_sec = AIO_CMPL_TIMEOUT_SEC;
+ _aio_cmpl_timeout.tv_nsec = AIO_CMPL_TIMEOUT_NSEC;
+ _final_aio_cmpl_timeout.tv_sec = FINAL_AIO_CMPL_TIMEOUT_SEC;
+ _final_aio_cmpl_timeout.tv_nsec = FINAL_AIO_CMPL_TIMEOUT_NSEC;
+ return true;
+}
+
+
+// Functions
+
+jcntl::jcntl(const std::string& jid,
+ const std::string& jdir,
+ JournalLog& jrnl_log):
+ _jid(jid),
+ _jdir(jdir),
+ _init_flag(false),
+ _stop_flag(false),
+ _readonly_flag(false),
+ _jrnl_log(jrnl_log),
+ _linearFileController(*this),
+ _emptyFilePoolPtr(0),
+ _emap(),
+ _tmap(),
+ _wmgr(this, _emap, _tmap, _linearFileController),
+ _recoveryManager(_jdir.dirname(), _jid, _emap, _tmap, jrnl_log)
+{}
+
+jcntl::~jcntl()
+{
+ if (_init_flag && !_stop_flag)
+ try { stop(true); }
+ catch (const jexception& e) { std::cerr << e << std::endl; }
+ _linearFileController.finalize();
+}
+
+void
+jcntl::initialize(EmptyFilePool* efpp,
+ const uint16_t wcache_num_pages,
+ const uint32_t wcache_pgsize_sblks,
+ aio_callback* const cbp)
+{
+ _init_flag = false;
+ _stop_flag = false;
+ _readonly_flag = false;
+
+ _emap.clear();
+ _tmap.clear();
+
+ _linearFileController.finalize();
+ _jdir.clear_dir(); // Clear any existing journal files
+ _linearFileController.initialize(_jdir.dirname(), efpp, 0ULL);
+ _linearFileController.getNextJournalFile();
+ _wmgr.initialize(cbp, wcache_pgsize_sblks, wcache_num_pages, QLS_WMGR_MAXDTOKPP, QLS_WMGR_MAXWAITUS, 0);
+ _init_flag = true;
+}
+
+void
+jcntl::recover(EmptyFilePoolManager* efpmp,
+ const uint16_t wcache_num_pages,
+ const uint32_t wcache_pgsize_sblks,
+ aio_callback* const cbp,
+ const std::vector<std::string>* prep_txn_list_ptr,
+ uint64_t& highest_rid)
+{
+ _init_flag = false;
+ _stop_flag = false;
+ _readonly_flag = false;
+
+ _emap.clear();
+ _tmap.clear();
+
+ _linearFileController.finalize();
+
+ // Verify journal dir and journal files
+ _jdir.verify_dir();
+ _recoveryManager.analyzeJournals(prep_txn_list_ptr, efpmp, &_emptyFilePoolPtr);
+ assert(_emptyFilePoolPtr != 0);
+
+ highest_rid = _recoveryManager.getHighestRecordId();
+ _jrnl_log.log(/*LOG_DEBUG*/JournalLog::LOG_INFO, _jid, _recoveryManager.toString(_jid, 5U));
+ _linearFileController.initialize(_jdir.dirname(), _emptyFilePoolPtr, _recoveryManager.getHighestFileNumber());
+ _recoveryManager.setLinearFileControllerJournals(&qpid::linearstore::journal::LinearFileController::addJournalFile, &_linearFileController);
+ if (_recoveryManager.isLastFileFull()) {
+ _linearFileController.getNextJournalFile();
+ }
+ _wmgr.initialize(cbp, wcache_pgsize_sblks, wcache_num_pages, QLS_WMGR_MAXDTOKPP, QLS_WMGR_MAXWAITUS,
+ (_recoveryManager.isLastFileFull() ? 0 : _recoveryManager.getEndOffset()));
+
+ _readonly_flag = true;
+ _init_flag = true;
+}
+
+void
+jcntl::recover_complete()
+{
+ if (!_readonly_flag)
+ throw jexception(jerrno::JERR_JCNTL_NOTRECOVERED, "jcntl", "recover_complete");
+ _recoveryManager.recoveryComplete();
+ _readonly_flag = false;
+}
+
+void
+jcntl::delete_jrnl_files()
+{
+ stop(true); // wait for AIO to complete
+ _linearFileController.purgeEmptyFilesToEfp();
+ _jdir.delete_dir();
+}
+
+
+iores
+jcntl::enqueue_data_record(const void* const data_buff,
+ const std::size_t tot_data_len,
+ const std::size_t this_data_len,
+ data_tok* dtokp,
+ const bool transient)
+{
+ iores r;
+ check_wstatus("enqueue_data_record");
+ {
+ slock s(_wr_mutex);
+ while (handle_aio_wait(_wmgr.enqueue(data_buff, tot_data_len, this_data_len, dtokp, 0, 0, false, transient, false), r,
+ dtokp)) ;
+ }
+ return r;
+}
+
+iores
+jcntl::enqueue_extern_data_record(const std::size_t tot_data_len,
+ data_tok* dtokp,
+ const bool transient)
+{
+ iores r;
+ check_wstatus("enqueue_extern_data_record");
+ {
+ slock s(_wr_mutex);
+ while (handle_aio_wait(_wmgr.enqueue(0, tot_data_len, 0, dtokp, 0, 0, false, transient, true), r, dtokp)) ;
+ }
+ return r;
+}
+
+iores
+jcntl::enqueue_txn_data_record(const void* const data_buff,
+ const std::size_t tot_data_len,
+ const std::size_t this_data_len,
+ data_tok* dtokp,
+ const std::string& xid,
+ const bool tpc_flag,
+ const bool transient)
+{
+ iores r;
+ check_wstatus("enqueue_tx_data_record");
+ {
+ slock s(_wr_mutex);
+ while (handle_aio_wait(_wmgr.enqueue(data_buff, tot_data_len, this_data_len, dtokp, xid.data(), xid.size(),
+ tpc_flag, transient, false), r, dtokp)) ;
+ }
+ return r;
+}
+
+iores
+jcntl::enqueue_extern_txn_data_record(const std::size_t tot_data_len,
+ data_tok* dtokp,
+ const std::string& xid,
+ const bool tpc_flag,
+ const bool transient)
+{
+ iores r;
+ check_wstatus("enqueue_extern_txn_data_record");
+ {
+ slock s(_wr_mutex);
+ while (handle_aio_wait(_wmgr.enqueue(0, tot_data_len, 0, dtokp, xid.data(), xid.size(), tpc_flag, transient,
+ true), r, dtokp)) ;
+ }
+ return r;
+}
+
+iores
+jcntl::read_data_record(void** const datapp,
+ std::size_t& dsize,
+ void** const xidpp,
+ std::size_t& xidsize,
+ bool& transient,
+ bool& external,
+ data_tok* const dtokp,
+ bool ignore_pending_txns)
+{
+ check_rstatus("read_data");
+ if (_recoveryManager.readNextRemainingRecord(datapp, dsize, xidpp, xidsize, transient, external, dtokp, ignore_pending_txns)) {
+ return RHM_IORES_SUCCESS;
+ }
+ return RHM_IORES_EMPTY;
+}
+
+iores
+jcntl::dequeue_data_record(data_tok* const dtokp,
+ const bool txn_coml_commit)
+{
+ iores r;
+ check_wstatus("dequeue_data");
+ {
+ slock s(_wr_mutex);
+ while (handle_aio_wait(_wmgr.dequeue(dtokp, 0, 0, false, txn_coml_commit), r, dtokp)) ;
+ }
+ return r;
+}
+
+iores
+jcntl::dequeue_txn_data_record(data_tok* const dtokp,
+ const std::string& xid,
+ const bool tpc_flag,
+ const bool txn_coml_commit)
+{
+ iores r;
+ check_wstatus("dequeue_data");
+ {
+ slock s(_wr_mutex);
+ while (handle_aio_wait(_wmgr.dequeue(dtokp, xid.data(), xid.size(), tpc_flag, txn_coml_commit), r, dtokp)) ;
+ }
+ return r;
+}
+
+iores
+jcntl::txn_abort(data_tok* const dtokp,
+ const std::string& xid)
+{
+ iores r;
+ check_wstatus("txn_abort");
+ {
+ slock s(_wr_mutex);
+ while (handle_aio_wait(_wmgr.abort(dtokp, xid.data(), xid.size()), r, dtokp)) ;
+ }
+ return r;
+}
+
+iores
+jcntl::txn_commit(data_tok* const dtokp,
+ const std::string& xid)
+{
+ iores r;
+ check_wstatus("txn_commit");
+ {
+ slock s(_wr_mutex);
+ while (handle_aio_wait(_wmgr.commit(dtokp, xid.data(), xid.size()), r, dtokp)) ;
+ }
+ return r;
+}
+
+bool
+jcntl::is_txn_synced(const std::string& xid)
+{
+ slock s(_wr_mutex);
+ bool res = _wmgr.is_txn_synced(xid);
+ return res;
+}
+
+int32_t
+jcntl::get_wr_events(timespec* const timeout)
+{
+ stlock t(_wr_mutex);
+ if (!t.locked())
+ return jerrno::LOCK_TAKEN;
+ return _wmgr.get_events(timeout, false);
+}
+
+void
+jcntl::stop(const bool block_till_aio_cmpl)
+{
+ if (_readonly_flag)
+ check_rstatus("stop");
+ else
+ check_wstatus("stop");
+ _stop_flag = true;
+ if (!_readonly_flag)
+ flush(block_till_aio_cmpl);
+}
+
+LinearFileController&
+jcntl::getLinearFileControllerRef() {
+ return _linearFileController;
+}
+
+// static
+std::string
+jcntl::str2hexnum(const std::string& str) {
+ if (str.empty()) {
+ return "<null>";
+ }
+ std::ostringstream oss;
+ oss << "(" << str.size() << ")0x" << std::hex;
+ for (unsigned i=str.size(); i>0; --i) {
+ oss << std::setfill('0') << std::setw(2) << (uint16_t)(uint8_t)str[i-1];
+ }
+ return oss.str();
+}
+
+iores
+jcntl::flush(const bool block_till_aio_cmpl)
+{
+ if (!_init_flag)
+ return RHM_IORES_SUCCESS;
+ if (_readonly_flag)
+ throw jexception(jerrno::JERR_JCNTL_READONLY, "jcntl", "flush");
+ iores res;
+ {
+ slock s(_wr_mutex);
+ res = _wmgr.flush();
+ }
+ if (block_till_aio_cmpl)
+ aio_cmpl_wait();
+ return res;
+}
+
+// Protected/Private functions
+
+void
+jcntl::check_wstatus(const char* fn_name) const
+{
+ if (!_init_flag)
+ throw jexception(jerrno::JERR__NINIT, "jcntl", fn_name);
+ if (_readonly_flag)
+ throw jexception(jerrno::JERR_JCNTL_READONLY, "jcntl", fn_name);
+ if (_stop_flag)
+ throw jexception(jerrno::JERR_JCNTL_STOPPED, "jcntl", fn_name);
+}
+
+void
+jcntl::check_rstatus(const char* fn_name) const
+{
+ if (!_init_flag)
+ throw jexception(jerrno::JERR__NINIT, "jcntl", fn_name);
+ if (_stop_flag)
+ throw jexception(jerrno::JERR_JCNTL_STOPPED, "jcntl", fn_name);
+}
+
+
+void
+jcntl::aio_cmpl_wait()
+{
+ //while (_wmgr.get_aio_evt_rem())
+ while (true)
+ {
+ uint32_t aer;
+ {
+ slock s(_wr_mutex);
+ aer = _wmgr.get_aio_evt_rem();
+ }
+ if (aer == 0) break; // no events left
+ if (get_wr_events(&_aio_cmpl_timeout) == jerrno::AIO_TIMEOUT)
+ throw jexception(jerrno::JERR_JCNTL_AIOCMPLWAIT, "jcntl", "aio_cmpl_wait");
+ }
+}
+
+
+bool
+jcntl::handle_aio_wait(const iores res, iores& resout, const data_tok* dtp)
+{
+ resout = res;
+ if (res == RHM_IORES_PAGE_AIOWAIT)
+ {
+ while (_wmgr.curr_pg_blocked())
+ {
+ if (_wmgr.get_aio_evt_rem() == 0) {
+//std::cout << "&&&&&& jcntl::handle_aio_wait() " << _wmgr.status_str() << std::endl; // DEBUG
+ throw jexception("_wmgr.curr_pg_blocked() with no events remaining"); // TODO - complete exception
+ }
+ if (_wmgr.get_events(&_aio_cmpl_timeout, false) == jerrno::AIO_TIMEOUT)
+ {
+ std::ostringstream oss;
+ oss << "get_events() returned JERR_JCNTL_AIOCMPLWAIT; wmgr_status: " << _wmgr.status_str();
+ _jrnl_log.log(JournalLog::LOG_CRITICAL, _jid, oss.str());
+ throw jexception(jerrno::JERR_JCNTL_AIOCMPLWAIT, "jcntl", "handle_aio_wait");
+ }
+ }
+ return true;
+ }
+ else if (res == RHM_IORES_FILE_AIOWAIT)
+ {
+// while (_wmgr.curr_file_blocked())
+// {
+// if (_wmgr.get_events(pmgr::UNUSED, &_aio_cmpl_timeout) == jerrno::AIO_TIMEOUT)
+// {
+// std::ostringstream oss;
+// oss << "get_events() returned JERR_JCNTL_AIOCMPLWAIT; wmgr_status: " << _wmgr.status_str();
+// this->log(LOG_CRITICAL, oss.str());
+// throw jexception(jerrno::JERR_JCNTL_AIOCMPLWAIT, "jcntl", "handle_aio_wait");
+// }
+// }
+// _wrfc.wr_reset();
+ resout = RHM_IORES_SUCCESS;
+ data_tok::write_state ws = dtp->wstate();
+ return ws == data_tok::ENQ_PART || ws == data_tok::DEQ_PART || ws == data_tok::ABORT_PART ||
+ ws == data_tok::COMMIT_PART;
+ }
+ return false;
+}
+
+}}}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/jcntl.h b/qpid/cpp/src/qpid/linearstore/journal/jcntl.h
new file mode 100644
index 0000000000..94c00d2fab
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/jcntl.h
@@ -0,0 +1,570 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_JCNTL_H
+#define QPID_LINEARSTORE_JOURNAL_JCNTL_H
+
+#include <qpid/linearstore/journal/LinearFileController.h>
+#include "qpid/linearstore/journal/jdir.h"
+#include "qpid/linearstore/journal/RecoveryManager.h"
+#include "qpid/linearstore/journal/wmgr.h"
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+class EmptyFilePool;
+class EmptyFilePoolManager;
+class JournalLog;
+
+/**
+* \brief Access and control interface for the journal. This is the top-level class for the
+* journal.
+*
+* This is the top-level journal class; one instance of this class controls one instance of the
+* journal and all its files and associated control structures. Besides this class, the only
+* other class that needs to be used at a higher level is the data_tok class, one instance of
+* which is used per data block written to the journal, and is used to track its status through
+* the AIO enqueue, read and dequeue process.
+*/
+class jcntl
+{
+protected:
+ /**
+ * \brief Journal ID
+ *
+ * This string uniquely identifies this journal instance. It will most likely be associated
+ * with the identity of the message queue with which it is associated.
+ */
+ // TODO: This is not included in any files at present, add to file_hdr?
+ std::string _jid;
+
+ /**
+ * \brief Journal directory
+ *
+ * This string stores the path to the journal directory. It may be absolute or relative, and
+ * should not end in a file separator character. (e.g. "/fastdisk/jdata" is correct,
+ * "/fastdisk/jdata/" is not.)
+ */
+ jdir _jdir;
+
+ /**
+ * \brief Initialized flag
+ *
+ * This flag starts out set to false, is set to true once this object has been initialized,
+ * either by calling initialize() or recover().
+ */
+ bool _init_flag;
+
+ /**
+ * \brief Stopped flag
+ *
+ * This flag starts out false, and is set to true when stop() is called. At this point, the
+ * journal will no longer accept messages until either initialize() or recover() is called.
+ * There is no way other than through initialization to reset this flag.
+ */
+ // TODO: It would be helpful to distinguish between states stopping and stopped. If stop(true) is called,
+ // then we are stopping, but must wait for all outstanding aios to return before being finally stopped. During
+ // this period, however, no new enqueue/dequeue/read requests may be accepted.
+ bool _stop_flag;
+
+ /**
+ * \brief Read-only state flag used during recover.
+ *
+ * When true, this flag prevents journal write operations (enqueue and dequeue), but
+ * allows read to occur. It is used during recovery, and is reset when recovered() is
+ * called.
+ */
+ bool _readonly_flag;
+
+ // Journal control structures
+ JournalLog& _jrnl_log; ///< Ref to Journal Log instance
+ LinearFileController _linearFileController; ///< Linear File Controller
+ EmptyFilePool* _emptyFilePoolPtr; ///< Pointer to Empty File Pool for this queue
+ enq_map _emap; ///< Enqueue map for low water mark management
+ txn_map _tmap; ///< Transaction map open transactions
+ wmgr _wmgr; ///< Write page manager which manages AIO
+ RecoveryManager _recoveryManager; ///< Recovery data used for recovery
+ smutex _wr_mutex; ///< Mutex for journal writes
+
+public:
+ static timespec _aio_cmpl_timeout; ///< Timeout for blocking libaio returns
+ static timespec _final_aio_cmpl_timeout; ///< Timeout for blocking libaio returns when stopping or finalizing
+
+ /**
+ * \brief Journal constructor.
+ *
+ * Constructor which sets the physical file location and base name.
+ *
+ * \param jid A unique identifier for this journal instance.
+ * \param jdir The directory which will contain the journal files.
+ * \param base_filename The string which will be used to start all journal filenames.
+ */
+ jcntl(const std::string& jid,
+ const std::string& jdir,
+ JournalLog& jrnl_log);
+
+ /**
+ * \brief Destructor.
+ */
+ virtual ~jcntl();
+
+ inline const std::string& id() const { return _jid; }
+
+ inline const std::string& jrnl_dir() const { return _jdir.dirname(); }
+
+ /**
+ * \brief Initialize the journal for storing data.
+ *
+ * Initialize the journal by creating new journal data files and initializing internal
+ * control structures. When complete, the journal will be empty, and ready to store data.
+ *
+ * <b>NOTE: Any existing journal will be ignored by this operation.</b> To use recover
+ * the data from an existing journal, use recover().
+ *
+ * <b>NOTE: If <i>NULL</i> is passed to the deque pointers, they will be internally created
+ * and deleted.</b>
+ *
+ * <b>NOTE: If <i>NULL</i> is passed to the callbacks, internal default callbacks will be
+ * used.</b>
+ *
+ * \param num_jfiles The number of journal files to be created.
+ * \param auto_expand If true, allows journal file auto-expansion. In this mode, the journal will automatically
+ * add files to the journal if it runs out of space. No more than ae_max_jfiles may be added. If false, then
+ * no files are added and an exception will be thrown if the journal runs out of file space.
+ * \param ae_max_jfiles Upper limit of journal files for auto-expand mode. When auto_expand is true, this is the
+ * maximum total number of files allowed in the journal (original plus those added by auto-expand mode). If
+ * this number of files exist and the journal runs out of space, an exception will be thrown. This number
+ * must be greater than the num_jfiles parameter value but cannot exceed the maximum number of files for a
+ * single journal; if num_jfiles is already at its maximum value, then auto-expand will be disabled.
+ * \param jfsize_sblks The size of each journal file expressed in softblocks.
+ * \param wcache_num_pages The number of write cache pages to create.
+ * \param wcache_pgsize_sblks The size in sblks of each write cache page.
+ * \param cbp Pointer to object containing callback functions for read and write operations. May be 0 (NULL).
+ *
+ * \exception TODO
+ */
+ void initialize(EmptyFilePool* efpp,
+ const uint16_t wcache_num_pages,
+ const uint32_t wcache_pgsize_sblks,
+ aio_callback* const cbp);
+
+ /**
+ * /brief Initialize journal by recovering state from previously written journal.
+ *
+ * Initialize journal by recovering state from previously written journal. The journal files
+ * are analyzed, and all records that have not been dequeued and that remain in the journal
+ * will be available for reading. The journal is placed in a read-only state until
+ * recovered() is called; any calls to enqueue or dequeue will fail with an exception
+ * in this state.
+ *
+ * <b>NOTE: If <i>NULL</i> is passed to the deque pointers, they will be internally created
+ * and deleted.</b>
+ *
+ * <b>NOTE: If <i>NULL</i> is passed to the callbacks, internal default callbacks will be
+ * used.</b>
+ *
+ * \param num_jfiles The number of journal files to be created.
+ * \param auto_expand If true, allows journal file auto-expansion. In this mode, the journal will automatically
+ * add files to the journal if it runs out of space. No more than ae_max_jfiles may be added. If false, then
+ * no files are added and an exception will be thrown if the journal runs out of file space.
+ * \param ae_max_jfiles Upper limit of journal files for auto-expand mode. When auto_expand is true, this is the
+ * maximum total number of files allowed in the journal (original plus those added by auto-expand mode). If
+ * this number of files exist and the journal runs out of space, an exception will be thrown. This number
+ * must be greater than the num_jfiles parameter value but cannot exceed the maximum number of files for a
+ * single journal; if num_jfiles is already at its maximum value, then auto-expand will be disabled.
+ * \param jfsize_sblks The size of each journal file expressed in softblocks.
+ * \param wcache_num_pages The number of write cache pages to create.
+ * \param wcache_pgsize_sblks The size in sblks of each write cache page.
+ * \param cbp Pointer to object containing callback functions for read and write operations. May be 0 (NULL).
+ * \param prep_txn_list_ptr
+ * \param highest_rid Returns the highest rid found in the journal during recover
+ *
+ * \exception TODO
+ */
+ void recover(EmptyFilePoolManager* efpm,
+ const uint16_t wcache_num_pages,
+ const uint32_t wcache_pgsize_sblks,
+ aio_callback* const cbp,
+ const std::vector<std::string>* prep_txn_list_ptr,
+ uint64_t& highest_rid);
+
+ /**
+ * \brief Notification to the journal that recovery is complete and that normal operation
+ * may resume.
+ *
+ * This call notifies the journal that recovery is complete and that normal operation
+ * may resume. The read pointers are reset so that all records read as a part of recover
+ * may be re-read during normal operation. The read-only flag is then reset, allowing
+ * enqueue and dequeue operations to resume.
+ *
+ * \exception TODO
+ */
+ void recover_complete();
+
+ /**
+ * \brief Stops journal and deletes all journal files.
+ *
+ * Clear the journal directory of all journal files matching the base filename.
+ *
+ * \exception TODO
+ */
+ void delete_jrnl_files();
+
+ /**
+ * \brief Enqueue data.
+ *
+ * Enqueue data or part thereof. If a large data block is being written, then it may be
+ * enqueued in parts by setting this_data_len to the size of the data being written in this
+ * call. The total data size must be known in advance, however, as this is written into the
+ * record header on the first record write. The state of the write (i.e. how much has been
+ * written so far) is maintained in the data token dtokp. Partial writes will return in state
+ * ENQ_PART.
+ *
+ * Note that a return value of anything other than RHM_IORES_SUCCESS implies that this write
+ * operation did not complete successfully or was partially completed. The action taken under
+ * these conditions depends on the value of the return. For example, RHM_IORES_AIO_WAIT
+ * implies that all pages in the write page cache are waiting for AIO operations to return,
+ * and that the call should be remade after waiting a bit.
+ *
+ * Example: If a write of 99 kB is divided into three equal parts, then the following states
+ * and returns would characterize a successful operation:
+ * <pre>
+ * dtok. dtok. dtok.
+ * Pperation Return wstate() dsize() written() Comment
+ * -----------------+--------+--------+-------+---------+------------------------------------
+ * NONE 0 0 Value of dtok before op
+ * edr(99000, 33000) SUCCESS ENQ_PART 99000 33000 Enqueue part 1
+ * edr(99000, 33000) AIO_WAIT ENQ_PART 99000 50000 Enqueue part 2, not completed
+ * edr(99000, 33000) SUCCESS ENQ_PART 99000 66000 Enqueue part 2 again
+ * edr(99000, 33000) SUCCESS ENQ 99000 99000 Enqueue part 3
+ * </pre>
+ *
+ * \param data_buff Pointer to data to be enqueued for this enqueue operation.
+ * \param tot_data_len Total data length.
+ * \param this_data_len Amount to be written in this enqueue operation.
+ * \param dtokp Pointer to data token which contains the details of the enqueue operation.
+ * \param transient Flag indicating transient persistence (ie, ignored on recover).
+ *
+ * \exception TODO
+ */
+ iores enqueue_data_record(const void* const data_buff,
+ const std::size_t tot_data_len,
+ const std::size_t this_data_len,
+ data_tok* dtokp,
+ const bool transient);
+
+ iores enqueue_extern_data_record(const std::size_t tot_data_len,
+ data_tok* dtokp,
+ const bool transient);
+
+ /**
+ * \brief Enqueue data.
+ *
+ * \param data_buff Pointer to data to be enqueued for this enqueue operation.
+ * \param tot_data_len Total data length.
+ * \param this_data_len Amount to be written in this enqueue operation.
+ * \param dtokp Pointer to data token which contains the details of the enqueue operation.
+ * \param xid String containing xid. An empty string (i.e. length=0) will be considered
+ * non-transactional.
+ * \param transient Flag indicating transient persistence (ie, ignored on recover).
+ *
+ * \exception TODO
+ */
+ iores enqueue_txn_data_record(const void* const data_buff,
+ const std::size_t tot_data_len,
+ const std::size_t this_data_len,
+ data_tok* dtokp,
+ const std::string& xid,
+ const bool tpc_flag,
+ const bool transient);
+
+ iores enqueue_extern_txn_data_record(const std::size_t tot_data_len,
+ data_tok* dtokp,
+ const std::string& xid,
+ const bool tpc_flag,
+ const bool transient);
+
+ /**
+ * \brief Reads data from the journal. It is the responsibility of the reader to free
+ * the memory that is allocated through this call - see below for details.
+ *
+ * Reads the next non-dequeued data record from the journal.
+ *
+ * <b>Note</b> that this call allocates memory into which the data and XID are copied. It
+ * is the responsibility of the caller to free this memory. The memory for the data and
+ * XID are allocated in a single call, and the XID precedes the data in the memory space.
+ * Thus, where an XID exists, freeing the XID pointer will free both the XID and data memory.
+ * However, if an XID does not exist for the message, the XID pointer xidpp is set to NULL,
+ * and it is the data pointer datapp that must be freed. Should neither an XID nor data be
+ * present (ie an empty record), then no memory is allocated, and both pointers will be NULL.
+ * In this case, there is no need to free memory.
+ *
+ * TODO: Fix this lousy interface. The caller should NOT be required to clean up these
+ * pointers! Rather use a struct, or better still, let the data token carry the data and
+ * xid pointers and lengths, and have the data token both allocate and delete.
+ *
+ * \param datapp Pointer to pointer that will be set to point to memory allocated and
+ * containing the data. Will be set to NULL if the call fails or there is no data
+ * in the record.
+ * \param dsize Ref that will be set to the size of the data. Will be set to 0 if the call
+ * fails or if there is no data in the record.
+ * \param xidpp Pointer to pointer that will be set to point to memory allocated and
+ * containing the XID. Will be set to NULL if the call fails or there is no XID attached
+ * to this record.
+ * \param xidsize Ref that will be set to the size of the XID.
+ * \param transient Ref that will be set true if record is transient.
+ * \param external Ref that will be set true if record is external. In this case, the data
+ * pointer datapp will be set to NULL, but dsize will contain the size of the data.
+ * NOTE: If there is an xid, then xidpp must be freed.
+ * \param dtokp Pointer to data_tok instance for this data, used to track state of data
+ * through journal.
+ * \param ignore_pending_txns When false (default), if the next record to be read is locked
+ * by a pending transaction, the read fails with RHM_IORES_TXPENDING. However, if set
+ * to true, then locks are ignored. This is required for reading of the Transaction
+ * Prepared List (TPL) which may have its entries locked, but may be read from
+ * time-to-time, and needs all its records (locked and unlocked) to be available.
+ *
+ * \exception TODO
+ */
+ iores read_data_record(void** const datapp,
+ std::size_t& dsize,
+ void** const xidpp,
+ std::size_t& xidsize,
+ bool& transient,
+ bool& external,
+ data_tok* const dtokp,
+ bool ignore_pending_txns);
+
+ /**
+ * \brief Dequeues (marks as no longer needed) data record in journal.
+ *
+ * Dequeues (marks as no longer needed) data record in journal. Note that it is possible
+ * to use the same data token instance used to enqueue this data; it contains the record ID
+ * needed to correctly mark this data as dequeued in the journal. Otherwise the RID of the
+ * record to be dequeued and the write state of ENQ must be manually set in a new or reset
+ * instance of data_tok.
+ *
+ * \param dtokp Pointer to data_tok instance for this data, used to track state of data
+ * through journal.
+ * \param txn_coml_commit Only used for preparedXID journal. When used for dequeueing
+ * prepared XID list items, sets whether the complete() was called in commit or abort
+ * mode.
+ *
+ * \exception TODO
+ */
+ iores dequeue_data_record(data_tok* const dtokp,
+ const bool txn_coml_commit);
+
+ /**
+ * \brief Dequeues (marks as no longer needed) data record in journal.
+ *
+ * Dequeues (marks as no longer needed) data record in journal as part of a transaction.
+ * Note that it is possible to use the same data token instance used to enqueue this data;
+ * it contains the RID needed to correctly mark this data as dequeued in the journal.
+ * Otherwise the RID of the record to be dequeued and the write state of ENQ must be
+ * manually set in a new or reset instance of data_tok.
+ *
+ * \param dtokp Pointer to data_tok instance for this data, used to track state of data
+ * through journal.
+ * \param xid String containing xid. An empty string (i.e. length=0) will be considered
+ * non-transactional.
+ * \param txn_coml_commit Only used for preparedXID journal. When used for dequeueing
+ * prepared XID list items, sets whether the complete() was called in commit or abort
+ * mode.
+ *
+ * \exception TODO
+ */
+ iores dequeue_txn_data_record(data_tok* const dtokp,
+ const std::string& xid,
+ const bool tpc_flag,
+ const bool txn_coml_commit);
+
+ /**
+ * \brief Abort the transaction for all records enqueued or dequeued with the matching xid.
+ *
+ * Abort the transaction for all records enqueued with the matching xid. All enqueued records
+ * are effectively deleted from the journal, and can not be read. All dequeued records remain
+ * as though they had never been dequeued.
+ *
+ * \param dtokp Pointer to data_tok instance for this data, used to track state of data
+ * through journal.
+ * \param xid String containing xid.
+ *
+ * \exception TODO
+ */
+ iores txn_abort(data_tok* const dtokp,
+ const std::string& xid);
+
+ /**
+ * \brief Commit the transaction for all records enqueued or dequeued with the matching xid.
+ *
+ * Commit the transaction for all records enqueued with the matching xid. All enqueued
+ * records are effectively released for reading and dequeueing. All dequeued records are
+ * removed and can no longer be accessed.
+ *
+ * \param dtokp Pointer to data_tok instance for this data, used to track state of data
+ * through journal.
+ * \param xid String containing xid.
+ *
+ * \exception TODO
+ */
+ iores txn_commit(data_tok* const dtokp,
+ const std::string& xid);
+
+ /**
+ * \brief Check whether all the enqueue records for the given xid have reached disk.
+ *
+ * \param xid String containing xid.
+ *
+ * \exception TODO
+ */
+ bool is_txn_synced(const std::string& xid);
+
+ /**
+ * \brief Forces a check for returned AIO write events.
+ *
+ * Forces a check for returned AIO write events. This is normally performed by enqueue() and
+ * dequeue() operations, but if these operations cease, then this call needs to be made to
+ * force the processing of any outstanding AIO operations.
+ */
+ int32_t get_wr_events(timespec* const timeout);
+
+ /**
+ * \brief Stop the journal from accepting any further requests to read or write data.
+ *
+ * This operation is used to stop the journal. This is the normal mechanism for bringing the
+ * journal to an orderly stop. Any outstanding AIO operations or partially written pages in
+ * the write page cache will by flushed and will complete.
+ *
+ * <b>Note:</b> The journal cannot be restarted without either initializing it or restoring
+ * it.
+ *
+ * \param block_till_aio_cmpl If true, will block the thread while waiting for all
+ * outstanding AIO operations to complete.
+ */
+ void stop(const bool block_till_aio_cmpl);
+
+ /**
+ * \brief Force a flush of the write page cache, creating a single AIO write operation.
+ */
+ iores flush(const bool block_till_aio_cmpl);
+
+ inline uint32_t get_enq_cnt() const { return _emap.size(); } // TODO: _emap: Thread safe?
+
+ inline uint32_t get_wr_aio_evt_rem() const { slock l(_wr_mutex); return _wmgr.get_aio_evt_rem(); }
+
+ uint32_t get_wr_outstanding_aio_dblks() const;
+
+ uint32_t get_rd_outstanding_aio_dblks() const;
+
+ LinearFileController& getLinearFileControllerRef();
+
+ /**
+ * \brief Check if a particular rid is enqueued. Note that this function will return
+ * false if the rid is transactionally enqueued and is not committed, or if it is
+ * locked (i.e. transactionally dequeued, but the dequeue has not been committed).
+ */
+ inline bool is_enqueued(const uint64_t rid, bool ignore_lock) { return _emap.is_enqueued(rid, ignore_lock); }
+
+ inline bool is_locked(const uint64_t rid) {
+ if (_emap.is_enqueued(rid, true) < enq_map::EMAP_OK)
+ return false;
+ return _emap.is_locked(rid) == enq_map::EMAP_TRUE;
+ }
+
+ inline void enq_rid_list(std::vector<uint64_t>& rids) { _emap.rid_list(rids); }
+
+ inline void enq_xid_list(std::vector<std::string>& xids) { _tmap.xid_list(xids); }
+
+ inline uint32_t get_open_txn_cnt() const { return _tmap.size(); }
+
+ // TODO Make this a const, but txn_map must support const first.
+ inline txn_map& get_txn_map() { return _tmap; }
+
+ /**
+ * \brief Check if the journal is stopped.
+ *
+ * \return <b><i>true</i></b> if the jouranl is stopped;
+ * <b><i>false</i></b> otherwise.
+ */
+ inline bool is_stopped() { return _stop_flag; }
+
+ /**
+ * \brief Check if the journal is ready to read and write data.
+ *
+ * Checks if the journal is ready to read and write data. This function will return
+ * <b><i>true</i></b> if the journal has been either initialized or restored, and the stop()
+ * function has not been called since the initialization.
+ *
+ * Note that the journal may also be stopped if an internal error occurs (such as running out
+ * of data journal file space).
+ *
+ * \return <b><i>true</i></b> if the journal is ready to read and write data;
+ * <b><i>false</i></b> otherwise.
+ */
+ inline bool is_ready() const { return _init_flag && !_stop_flag; }
+
+ inline bool is_read_only() const { return _readonly_flag; }
+
+ /**
+ * \brief Get the journal directory.
+ *
+ * This returns the journal directory as set during initialization. This is the directory
+ * into which the journal files will be written.
+ */
+ inline const std::string& dirname() const { return _jdir.dirname(); }
+
+ // Management instrumentation callbacks
+ inline virtual void instr_incr_outstanding_aio_cnt() {}
+ inline virtual void instr_decr_outstanding_aio_cnt() {}
+
+ static std::string str2hexnum(const std::string& str);
+
+protected:
+ static bool _init;
+ static bool init_statics();
+
+ /**
+ * \brief Check status of journal before allowing write operations.
+ */
+ void check_wstatus(const char* fn_name) const;
+
+ /**
+ * \brief Check status of journal before allowing read operations.
+ */
+ void check_rstatus(const char* fn_name) const;
+
+ /**
+ * \brief Call that blocks while waiting for all outstanding AIOs to complete
+ */
+ void aio_cmpl_wait();
+
+ /**
+ * \brief Call that blocks until at least one message returns; used to wait for
+ * AIO wait conditions to clear.
+ */
+ bool handle_aio_wait(const iores res, iores& resout, const data_tok* dtp);
+};
+
+}}}
+
+#endif // ifndef QPID_LINEARSTORE_JOURNAL_JCNTL_H
diff --git a/qpid/cpp/src/qpid/linearstore/journal/jdir.cpp b/qpid/cpp/src/qpid/linearstore/journal/jdir.cpp
new file mode 100644
index 0000000000..72b94d0098
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/jdir.cpp
@@ -0,0 +1,457 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/linearstore/journal/jdir.h"
+
+#include <cstring>
+#include <cerrno>
+#include <iomanip>
+#include "qpid/linearstore/journal/jexception.h"
+#include <sys/stat.h>
+#include <unistd.h>
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+jdir::jdir(const std::string& dirname/*, const std::string& _base_filename*/):
+ _dirname(dirname)/*,
+ _base_filename(_base_filename)*/
+{}
+
+jdir::~jdir()
+{}
+
+// === create_dir ===
+
+void
+jdir::create_dir()
+{
+ create_dir(_dirname);
+}
+
+
+void
+jdir::create_dir(const char* dirname)
+{
+ create_dir(std::string(dirname));
+}
+
+
+void
+jdir::create_dir(const std::string& dirname)
+{
+ std::size_t fdp = dirname.find_last_of('/');
+ if (fdp != std::string::npos)
+ {
+ std::string parent_dir = dirname.substr(0, fdp);
+ if (!exists(parent_dir))
+ create_dir(parent_dir);
+ }
+ if (::mkdir(dirname.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH))
+ {
+ if (errno != EEXIST) // Dir exists, ignore
+ {
+ std::ostringstream oss;
+ oss << "dir=\"" << dirname << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_MKDIR, oss.str(), "jdir", "create_dir");
+ }
+ }
+}
+
+
+// === clear_dir ===
+
+void
+jdir::clear_dir(const bool create_flag)
+{
+ clear_dir(_dirname/*, _base_filename*/, create_flag);
+}
+
+void
+jdir::clear_dir(const char* dirname/*, const char* base_filename*/, const bool create_flag)
+{
+ clear_dir(std::string(dirname)/*, std::string(base_filename)*/, create_flag);
+}
+
+
+void
+jdir::clear_dir(const std::string& dirname/*, const std::string&
+#ifndef RHM_JOWRITE
+ base_filename
+#endif
+*/
+ , const bool create_flag)
+{
+ DIR* dir = open_dir(dirname, "clear_dir", true);
+ if (!dir && create_flag) {
+ create_dir(dirname);
+ dir = open_dir(dirname, "clear_dir", true);
+ }
+//#ifndef RHM_JOWRITE
+ struct dirent* entry;
+ bool found = false;
+ std::string bak_dir;
+ while ((entry = ::readdir(dir)) != 0)
+ {
+ // Ignore . and ..
+ if (std::strcmp(entry->d_name, ".") != 0 && std::strcmp(entry->d_name, "..") != 0)
+ {
+ if (std::strlen(entry->d_name) >= 3) // 'bak'
+ {
+ if (std::strncmp(entry->d_name, "bak", 3) == 0)
+ {
+ if (!found)
+ {
+ bak_dir = create_bak_dir(dirname/*, base_filename*/);
+ found = true;
+ }
+ std::ostringstream oldname;
+ oldname << dirname << "/" << entry->d_name;
+ std::ostringstream newname;
+ newname << bak_dir << "/" << entry->d_name;
+ if (::rename(oldname.str().c_str(), newname.str().c_str()))
+ {
+ ::closedir(dir);
+ std::ostringstream oss;
+ oss << "file=\"" << oldname.str() << "\" dest=\"" <<
+ newname.str() << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_FMOVE, oss.str(), "jdir", "clear_dir");
+ }
+ }
+ }
+ }
+ }
+// FIXME: Find out why this fails with false alarms/errors from time to time...
+// While commented out, there is no error capture from reading dir entries.
+// check_err(errno, dir, dirname, "clear_dir");
+//#endif
+ close_dir(dir, dirname, "clear_dir");
+}
+
+// === push_down ===
+
+std::string
+jdir::push_down(const std::string& dirname, const std::string& target_dir/*, const std::string& bak_dir_base*/)
+{
+ std::string bak_dir_name = create_bak_dir(dirname/*, bak_dir_base*/);
+
+ DIR* dir = open_dir(dirname, "push_down", false);
+ // Copy contents of targetDirName into bak dir
+ struct dirent* entry;
+ while ((entry = ::readdir(dir)) != 0)
+ {
+ // Search for targetDirName in storeDirName
+ if (std::strcmp(entry->d_name, target_dir.c_str()) == 0)
+ {
+ std::ostringstream oldname;
+ oldname << dirname << "/" << target_dir;
+ std::ostringstream newname;
+ newname << bak_dir_name << "/" << target_dir;
+ if (::rename(oldname.str().c_str(), newname.str().c_str()))
+ {
+ ::closedir(dir);
+ std::ostringstream oss;
+ oss << "file=\"" << oldname.str() << "\" dest=\"" << newname.str() << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_FMOVE, oss.str(), "jdir", "push_down");
+ }
+ break;
+ }
+ }
+ close_dir(dir, dirname, "push_down");
+ return bak_dir_name;
+}
+
+// === verify_dir ===
+
+void
+jdir::verify_dir()
+{
+ verify_dir(_dirname/*, _base_filename*/);
+}
+
+void
+jdir::verify_dir(const char* dirname/*, const char* base_filename*/)
+{
+ verify_dir(std::string(dirname)/*, std::string(base_filename)*/);
+}
+
+
+void
+jdir::verify_dir(const std::string& dirname/*, const std::string& base_filename*/)
+{
+ if (!is_dir(dirname))
+ {
+ std::ostringstream oss;
+ oss << "dir=\"" << dirname << "\"";
+ throw jexception(jerrno::JERR_JDIR_NOTDIR, oss.str(), "jdir", "verify_dir");
+ }
+
+ // Read jinf file, then verify all journal files are present
+// jinf ji(dirname + "/" + base_filename + "." + QLS_JRNL_FILE_EXTENSION, true);
+// for (uint16_t fnum=0; fnum < ji.num_jfiles(); fnum++)
+// {
+// std::ostringstream oss;
+// oss << dirname << "/" << base_filename << ".";
+// oss << std::setw(4) << std::setfill('0') << std::hex << fnum;
+// oss << "." << QLS_JRNL_FILE_EXTENSION;
+// if (!exists(oss.str()))
+// throw jexception(jerrno::JERR_JDIR_NOSUCHFILE, oss.str(), "jdir", "verify_dir");
+// }
+}
+
+
+// === delete_dir ===
+
+void
+jdir::delete_dir(bool children_only)
+{
+ delete_dir(_dirname, children_only);
+}
+
+void
+jdir::delete_dir(const char* dirname, bool children_only)
+{
+ delete_dir(std::string(dirname), children_only);
+}
+
+void
+jdir::delete_dir(const std::string& dirname, bool children_only)
+{
+ struct dirent* entry;
+ struct stat s;
+ DIR* dir = open_dir(dirname, "delete_dir", true); // true = allow dir does not exist, return 0
+ if (!dir) return;
+ while ((entry = ::readdir(dir)) != 0)
+ {
+ // Ignore . and ..
+ if (std::strcmp(entry->d_name, ".") != 0 && std::strcmp(entry->d_name, "..") != 0)
+ {
+ std::string full_name(dirname + "/" + entry->d_name);
+ if (::lstat(full_name.c_str(), &s))
+ {
+ ::closedir(dir);
+ std::ostringstream oss;
+ oss << "stat: file=\"" << full_name << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_STAT, oss.str(), "jdir", "delete_dir");
+ }
+ if (S_ISREG(s.st_mode) || S_ISLNK(s.st_mode)) // This is a file or slink
+ {
+ if(::unlink(full_name.c_str()))
+ {
+ ::closedir(dir);
+ std::ostringstream oss;
+ oss << "unlink: file=\"" << entry->d_name << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_UNLINK, oss.str(), "jdir", "delete_dir");
+ }
+ }
+ else if (S_ISDIR(s.st_mode)) // This is a dir
+ {
+ delete_dir(full_name);
+ }
+ else // all other types, throw up!
+ {
+ ::closedir(dir);
+ std::ostringstream oss;
+ oss << "file=\"" << entry->d_name << "\" is not a dir, file or slink.";
+ oss << " (mode=0x" << std::hex << s.st_mode << std::dec << ")";
+ throw jexception(jerrno::JERR_JDIR_BADFTYPE, oss.str(), "jdir", "delete_dir");
+ }
+ }
+ }
+
+// FIXME: Find out why this fails with false alarms/errors from time to time...
+// While commented out, there is no error capture from reading dir entries.
+// check_err(errno, dir, dirname, "delete_dir");
+ // Now dir is empty, close and delete it
+ close_dir(dir, dirname, "delete_dir");
+
+ if (!children_only)
+ if (::rmdir(dirname.c_str()))
+ {
+ std::ostringstream oss;
+ oss << "dir=\"" << dirname << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_RMDIR, oss.str(), "jdir", "delete_dir");
+ }
+}
+
+
+std::string
+jdir::create_bak_dir(const std::string& dirname)
+{
+ DIR* dir = open_dir(dirname, "create_bak_dir", false);
+ long dir_num = 0L;
+ struct dirent* entry;
+ while ((entry = ::readdir(dir)) != 0)
+ {
+ // Ignore . and ..
+ if (std::strcmp(entry->d_name, ".") != 0 && std::strcmp(entry->d_name, "..") != 0)
+ {
+ if (std::strlen(entry->d_name) == 9) // Format: _bak.XXXX
+ {
+ if (std::strncmp(entry->d_name, "_bak.", 5) == 0)
+ {
+ long this_dir_num = std::strtol(entry->d_name + 5, 0, 16);
+ if (this_dir_num > dir_num)
+ dir_num = this_dir_num;
+ }
+ }
+ }
+ }
+// FIXME: Find out why this fails with false alarms/errors from time to time...
+// While commented out, there is no error capture from reading dir entries.
+// check_err(errno, dir, dirname, "create_bak_dir");
+ close_dir(dir, dirname, "create_bak_dir");
+
+ std::ostringstream dn;
+ dn << dirname << "/_bak." << std::hex << std::setw(4) << std::setfill('0') << ++dir_num;
+ if (::mkdir(dn.str().c_str(), S_IRWXU | S_IRWXG | S_IROTH))
+ {
+ std::ostringstream oss;
+ oss << "dir=\"" << dn.str() << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_MKDIR, oss.str(), "jdir", "create_bak_dir");
+ }
+ return std::string(dn.str());
+}
+
+bool
+jdir::is_dir(const char* name)
+{
+ struct stat s;
+ if (::stat(name, &s))
+ {
+ std::ostringstream oss;
+ oss << "file=\"" << name << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_STAT, oss.str(), "jdir", "is_dir");
+ }
+ return S_ISDIR(s.st_mode);
+}
+
+bool
+jdir::is_dir(const std::string& name)
+{
+ return is_dir(name.c_str());
+}
+
+bool
+jdir::exists(const char* name)
+{
+ struct stat s;
+ if (::stat(name, &s))
+ {
+ if (errno == ENOENT) // No such dir or file
+ return false;
+ // Throw for any other condition
+ std::ostringstream oss;
+ oss << "file=\"" << name << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_STAT, oss.str(), "jdir", "exists");
+ }
+ return true;
+}
+
+bool
+jdir::exists(const std::string& name)
+{
+ return exists(name.c_str());
+}
+
+void
+jdir::read_dir(const std::string& name, std::vector<std::string>& dir_list, const bool incl_dirs, const bool incl_files, const bool incl_links, const bool return_fqfn) {
+ struct stat s;
+ if (is_dir(name)) {
+ DIR* dir = open_dir(name, "read_dir", false);
+ struct dirent* entry;
+ while ((entry = ::readdir(dir)) != 0) {
+ if (std::strcmp(entry->d_name, ".") != 0 && std::strcmp(entry->d_name, "..") != 0) { // Ignore . and ..
+ std::string full_name(name + "/" + entry->d_name);
+ if (::stat(full_name.c_str(), &s))
+ {
+ ::closedir(dir);
+ std::ostringstream oss;
+ oss << "stat: file=\"" << full_name << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_STAT, oss.str(), "jdir", "delete_dir");
+ }
+ if ((S_ISREG(s.st_mode) && incl_files) || (S_ISDIR(s.st_mode) && incl_dirs) || (S_ISLNK(s.st_mode) && incl_links)) {
+ if (return_fqfn) {
+ dir_list.push_back(name + "/" + entry->d_name);
+ } else {
+ dir_list.push_back(entry->d_name);
+ }
+ }
+ }
+ }
+ close_dir(dir, name, "read_dir");
+ }
+}
+
+void
+jdir::check_err(const int err_num, DIR* dir, const std::string& dir_name, const std::string& fn_name)
+{
+ if (err_num)
+ {
+ std::ostringstream oss;
+ oss << "dir=\"" << dir_name << "\"" << FORMAT_SYSERR(err_num);
+ ::closedir(dir); // Try to close, it makes no sense to trap errors here...
+ throw jexception(jerrno::JERR_JDIR_READDIR, oss.str(), "jdir", fn_name);
+ }
+}
+
+void
+jdir::close_dir(DIR* dir, const std::string& dir_name, const std::string& fn_name)
+{
+ if (::closedir(dir))
+ {
+ std::ostringstream oss;
+ oss << "dir=\"" << dir_name << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_CLOSEDIR, oss.str(), "jdir", fn_name);
+ }
+}
+
+DIR*
+jdir::open_dir(const std::string& dir_name, const std::string& fn_name, const bool test_enoent)
+{
+ DIR* dir = ::opendir(dir_name.c_str());
+ if (!dir) {
+ if (test_enoent && errno == ENOENT) {
+ return 0;
+ }
+ std::ostringstream oss;
+ oss << "dir=\"" << dir_name << "\"" << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR_JDIR_OPENDIR, oss.str(), "jdir", fn_name);
+ }
+ return dir;
+}
+
+std::ostream&
+operator<<(std::ostream& os, const jdir& jdir)
+{
+ os << jdir._dirname;
+ return os;
+}
+
+std::ostream&
+operator<<(std::ostream& os, const jdir* jdirPtr)
+{
+ os << jdirPtr->_dirname;
+ return os;
+}
+
+}}}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/jdir.h b/qpid/cpp/src/qpid/linearstore/journal/jdir.h
new file mode 100644
index 0000000000..59f21ce499
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/jdir.h
@@ -0,0 +1,362 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_JDIR_H
+#define QPID_LINEARSTORE_JOURNAL_JDIR_H
+
+#include <dirent.h>
+#include <string>
+#include <vector>
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+ /**
+ * \class jdir
+ * \brief Class to manage the %journal directory
+ */
+ class jdir
+ {
+ private:
+ std::string _dirname;
+ //std::string _base_filename;
+
+ public:
+
+ /**
+ * \brief Sole constructor
+ *
+ * \param dirname Name of directory to be managed.
+ * \param base_filename Filename root used in the creation of %journal files
+ * and sub-directories.
+ */
+ jdir(const std::string& dirname/*, const std::string& base_filename*/);
+
+ virtual ~jdir();
+
+
+ /**
+ * \brief Create %journal directory as set in the dirname parameter of the constructor.
+ * Recursive creation is supported.
+ *
+ * \exception jerrno::JERR_JDIR_MKDIR The creation of dirname failed.
+ */
+ void create_dir();
+
+ /**
+ * \brief Static function to create a directory. Recursive creation is supported.
+ *
+ * \param dirname C-string containing name of directory.
+ *
+ * \exception jerrno::JERR_JDIR_MKDIR The creation of dirname failed.
+ */
+ static void create_dir(const char* dirname);
+
+ /**
+ * \brief Static function to create a directory. Recursive creation is supported.
+ *
+ * \param dirname String containing name of directory.
+ *
+ * \exception jerrno::JERR_JDIR_MKDIR The creation of dirname failed.
+ */
+ static void create_dir(const std::string& dirname);
+
+
+ /**
+ * \brief Clear the %journal directory of files matching the base filename
+ * by moving them into a subdirectory. This fn uses the dirname and base_filename
+ * that were set on construction.
+ *
+ * \param create_flag If set, create dirname if it is non-existent, otherwise throw
+ * exception.
+ *
+ * \exception jerrno::JERR_JDIR_OPENDIR The %journal directory could not be opened.
+ * \exception jerrno::JERR_JDIR_FMOVE Moving the files from the %journal directory to the created backup
+ * directory failed.
+ * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed.
+ */
+ void clear_dir(const bool create_flag = true);
+
+ /**
+ * \brief Clear the directory dirname of %journal files matching base_filename
+ * by moving them into a subdirectory.
+ *
+ * \param dirname C-string containing name of %journal directory.
+ * \param base_filename C-string containing base filename of %journal files to be matched
+ * for moving into subdirectory.
+ * \param create_flag If set, create dirname if it is non-existent, otherwise throw
+ * exception
+ *
+ * \exception jerrno::JERR_JDIR_OPENDIR The %journal directory could not be opened.
+ * \exception jerrno::JERR_JDIR_FMOVE Moving the files from the %journal directory to the created backup
+ * directory failed.
+ * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed.
+ */
+ static void clear_dir(const char* dirname/*, const char* base_filename*/,
+ const bool create_flag = true);
+
+ /**
+ * \brief Clear the directory dirname of %journal files matching base_filename
+ * by moving them into a subdirectory.
+ *
+ * \param dirname String containing name of %journal directory.
+ * \param base_filename String containing base filename of %journal files to be matched
+ * for moving into subdirectory.
+ * \param create_flag If set, create dirname if it is non-existent, otherwise throw
+ * exception
+ *
+ * \exception jerrno::JERR_JDIR_OPENDIR The %journal directory could not be opened.
+ * \exception jerrno::JERR_JDIR_FMOVE Moving the files from the %journal directory to the created backup
+ * directory failed.
+ * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed.
+ */
+ static void clear_dir(const std::string& dirname/*, const std::string& base_filename*/,
+ const bool create_flag = true);
+
+
+
+ /**
+ * \brief Move (push down) the directory target_dir located in directory dirname into a backup directory
+ * named _bak_dir_base.XXXX (note prepended underscore), where XXXX is an increasing hex serial number
+ * starting at 0000.
+ *
+ * \param dirname Full path to directory containing directory to be pushed down.
+ * \param target_dir Name of directory in dirname to be pushed down.
+ * \param bak_dir_base Base name for backup directory to be created in dirname, into which target_dir will be moved.
+ * \return Name of backup dir into which target_dir was pushed.
+ */
+ static std::string push_down(const std::string& dirname, const std::string& target_dir/*, const std::string& bak_dir_base*/);
+
+
+ /**
+ * \brief Verify that dirname is a valid %journal directory.
+ *
+ * The validation reads the .%jinf file, and using this information verifies that all the expected %journal
+ * (.jdat) files are present.
+ *
+ * \exception jerrno::JERR_JDIR_NOTDIR dirname is not a directory
+ * \exception jerrno::JERR_JDIR_STAT Could not stat dirname
+ * \exception jerrno::JERR__FILEIO Error reading %jinf file
+ * \exception jerrno::JERR_JINF_CVALIDFAIL Error validating %jinf file
+ * \exception jerrno::JERR_JDIR_NOSUCHFILE Expected jdat file is missing
+ */
+ void verify_dir();
+
+ /**
+ * \brief Verify that dirname is a valid %journal directory.
+ *
+ * The validation reads the .%jinf file, and using this information verifies that all the expected %journal
+ * (.jdat) files are present.
+ *
+ * \param dirname C-string containing name of %journal directory.
+ * \param base_filename C-string containing base filename of %journal files to be matched for moving into sub-directory.
+ *
+ * \exception jerrno::JERR_JDIR_NOTDIR dirname is not a directory
+ * \exception jerrno::JERR_JDIR_STAT Could not stat dirname
+ * \exception jerrno::JERR__FILEIO Error reading %jinf file
+ * \exception jerrno::JERR_JINF_CVALIDFAIL Error validating %jinf file
+ * \exception jerrno::JERR_JDIR_NOSUCHFILE Expected jdat file is missing
+ */
+ static void verify_dir(const char* dirname/*, const char* base_filename*/);
+
+ /**
+ * \brief Verify that dirname is a valid %journal directory.
+ *
+ * The validation reads the .%jinf file, and using this information verifies that all the expected %journal
+ * (.jdat) files are present.
+ *
+ * \param dirname String containing name of %journal directory.
+ * \param base_filename String containing base filename of %journal files to be matched for moving into sub-directory.
+ *
+ * \exception jerrno::JERR_JDIR_NOTDIR dirname is not a directory
+ * \exception jerrno::JERR_JDIR_STAT Could not stat dirname
+ * \exception jerrno::JERR__FILEIO Error reading %jinf file
+ * \exception jerrno::JERR_JINF_CVALIDFAIL Error validating %jinf file
+ * \exception jerrno::JERR_JDIR_NOSUCHFILE Expected jdat file is missing
+ */
+ static void verify_dir(const std::string& dirname/*, const std::string& base_filename*/);
+
+ /**
+ * \brief Delete the %journal directory and all files and sub--directories that it may
+ * contain. This is equivilent of rm -rf.
+ *
+ * FIXME: links are not handled correctly.
+ *
+ * \param children_only If true, delete only children of dirname, but leave dirname itself.
+ *
+ * \exception jerrno::JERR_JDIR_OPENDIR The %journal directory could not be opened.
+ * \exception jerrno::JERR_JDIR_STAT Could not stat dirname.
+ * \exception jerrno::JERR_JDIR_UNLINK A file could not be deleted.
+ * \exception jerrno::JERR_JDIR_BADFTYPE A dir entry is neiter a file nor a dir.
+ * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed.
+ * \exception jerrno::JERR_JDIR_RMDIR A directory could not be deleted.
+ */
+ void delete_dir(bool children_only = false );
+
+ /**
+ * \brief Delete the %journal directory and all files and sub--directories that it may
+ * contain. This is equivilent of rm -rf.
+ *
+ * FIXME: links are not handled correctly.
+ *
+ * \param dirname C-string containing name of directory to be deleted.
+ * \param children_only If true, delete only children of dirname, but leave dirname itself.
+ *
+ * \exception jerrno::JERR_JDIR_OPENDIR The %journal directory could not be opened.
+ * \exception jerrno::JERR_JDIR_STAT Could not stat dirname.
+ * \exception jerrno::JERR_JDIR_UNLINK A file could not be deleted.
+ * \exception jerrno::JERR_JDIR_BADFTYPE A dir entry is neiter a file nor a dir.
+ * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed.
+ * \exception jerrno::JERR_JDIR_RMDIR A directory could not be deleted.
+ */
+ static void delete_dir(const char* dirname, bool children_only = false);
+
+ /**
+ * \brief Delete the %journal directory and all files and sub--directories that it may
+ * contain. This is equivilent of rm -rf.
+ *
+ * FIXME: links are not handled correctly.
+ *
+ * \param dirname String containing name of directory to be deleted.
+ * \param children_only If true, delete only children of dirname, but leave dirname itself.
+ *
+ * \exception jerrno::JERR_JDIR_OPENDIR The %journal directory could not be opened.
+ * \exception jerrno::JERR_JDIR_STAT Could not stat dirname.
+ * \exception jerrno::JERR_JDIR_UNLINK A file could not be deleted.
+ * \exception jerrno::JERR_JDIR_BADFTYPE A dir entry is neiter a file nor a dir.
+ * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed.
+ * \exception jerrno::JERR_JDIR_RMDIR A directory could not be deleted.
+ */
+ static void delete_dir(const std::string& dirname, bool children_only = false);
+
+ /**
+ * \brief Create bakup directory that is next in sequence and move all %journal files
+ * matching base_filename into it.
+ *
+ * In directory dirname, search for existing backup directory using pattern
+ * "_basename.bak.XXXX" where XXXX is a hexadecimal sequence, and create next directory
+ * based on highest number found. Move all %journal files which match the base_fileaname
+ * parameter into this new backup directory.
+ *
+ * \param dirname String containing name of %journal directory.
+ * \param base_filename String containing base filename of %journal files to be matched
+ * for moving into subdirectory.
+ *
+ * \exception jerrno::JERR_JDIR_OPENDIR The %journal directory could not be opened.
+ * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed.
+ * \exception jerrno::JERR_JDIR_MKDIR The backup directory could not be deleted.
+ */
+ static std::string create_bak_dir(const std::string& dirname/*,
+ const std::string& base_filename*/);
+
+ /**
+ * \brief Return the directory name as a string.
+ */
+ inline const std::string& dirname() const { return _dirname; }
+
+ /**
+ * \brief Return the %journal base filename name as a string.
+ */
+// inline const std::string& base_filename() const { return _base_filename; }
+
+ /**
+ * \brief Test whether the named file is a directory.
+ *
+ * \param name Name of file to be tested.
+ * \return <b><i>true</i></b> if the named file is a directory; <b><i>false</i></b>
+ * otherwise.
+ * \exception jerrno::JERR_JDIR_STAT Could not stat name.
+ */
+ static bool is_dir(const char* name);
+
+ /**
+ * \brief Test whether the named file is a directory.
+ *
+ * \param name Name of file to be tested.
+ * \return <b><i>true</i></b> if the named file is a directory; <b><i>false</i></b>
+ * otherwise.
+ * \exception jerrno::JERR_JDIR_STAT Could not stat name.
+ */
+ static bool is_dir(const std::string& name);
+
+
+ /**
+ * \brief Test whether the named entity exists on the filesystem.
+ *
+ * If stat() fails with error ENOENT, then this will return <b><i>false</i></b>. If
+ * stat() succeeds, then <b><i>true</i></b> is returned, irrespective of the file type.
+ * If stat() fails with any other error, an exception is thrown.
+ *
+ * \param name Name of entity to be tested.
+ * \return <b><i>true</i></b> if the named entity exists; <b><i>false</i></b>
+ * otherwise.
+ * \exception jerrno::JERR_JDIR_STAT Could not stat name.
+ */
+ static bool exists(const char* name);
+
+ /**
+ * \brief Test whether the named entity exists on the filesystem.
+ *
+ * If stat() fails with error ENOENT, then this will return <b><i>false</i></b>. If
+ * stat() succeeds, then <b><i>true</i></b> is returned, irrespective of the file type.
+ * If stat() fails with any other error, an exception is thrown.
+ *
+ * \param name Name of entity to be tested.
+ * \return <b><i>true</i></b> if the named entity exists; <b><i>false</i></b>
+ * otherwise.
+ * \exception jerrno::JERR_JDIR_STAT Could not stat name.
+ */
+ static bool exists(const std::string& name);
+
+ static void read_dir(const std::string& name, std::vector<std::string>& dir_list, const bool incl_dirs, const bool incl_files, const bool incl_links, const bool return_fqfn);
+
+ /**
+ * \brief Stream operator
+ */
+ friend std::ostream& operator<<(std::ostream& os, const jdir& jdir);
+
+ /**
+ * \brief Stream operator
+ */
+ friend std::ostream& operator<<(std::ostream& os, const jdir* jdirPtr);
+
+ private:
+ /**
+ * \brief Check for error, if non-zero close DIR handle and throw JERR_JDIR_READDIR
+ *
+ * \exception jerrno::JERR_JDIR_READDIR Error while reading contents of dir.
+ */
+ static void check_err(const int err_num, DIR* dir, const std::string& dir_name, const std::string& fn_name);
+
+ /**
+ * \brief Close a DIR handle, throw JERR_JDIR_CLOSEDIR if error occurs during close
+ *
+ * \exception jerrno::JERR_JDIR_CLOSEDIR The directory handle could not be closed.
+ */
+ static void close_dir(DIR* dir, const std::string& dir_name, const std::string& fn_name);
+
+ static DIR* open_dir(const std::string& dir_name, const std::string& fn_name, const bool test_enoent);
+ };
+
+}}}
+
+#endif // ifndef QPID_LINEARSTORE_JOURNAL_JDIR_H
diff --git a/qpid/cpp/src/qpid/linearstore/journal/jerrno.cpp b/qpid/cpp/src/qpid/linearstore/journal/jerrno.cpp
new file mode 100644
index 0000000000..ce88e7809c
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/jerrno.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/linearstore/journal/jerrno.h"
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+std::map<uint32_t, const char*> jerrno::_err_map;
+std::map<uint32_t, const char*>::iterator jerrno::_err_map_itr;
+bool jerrno::_initialized = jerrno::__init();
+
+// generic errors
+const uint32_t jerrno::JERR__MALLOC = 0x0100;
+const uint32_t jerrno::JERR__UNDERFLOW = 0x0101;
+const uint32_t jerrno::JERR__NINIT = 0x0102;
+const uint32_t jerrno::JERR__AIO = 0x0103;
+const uint32_t jerrno::JERR__FILEIO = 0x0104;
+const uint32_t jerrno::JERR__RTCLOCK = 0x0105;
+const uint32_t jerrno::JERR__PTHREAD = 0x0106;
+const uint32_t jerrno::JERR__TIMEOUT = 0x0107;
+const uint32_t jerrno::JERR__UNEXPRESPONSE = 0x0108;
+const uint32_t jerrno::JERR__RECNFOUND = 0x0109;
+const uint32_t jerrno::JERR__NOTIMPL = 0x010a;
+const uint32_t jerrno::JERR__NULL = 0x010b;
+const uint32_t jerrno::JERR__SYMLINK = 0x010c;
+
+// class jcntl
+const uint32_t jerrno::JERR_JCNTL_STOPPED = 0x0200;
+const uint32_t jerrno::JERR_JCNTL_READONLY = 0x0201;
+const uint32_t jerrno::JERR_JCNTL_AIOCMPLWAIT = 0x0202;
+const uint32_t jerrno::JERR_JCNTL_UNKNOWNMAGIC = 0x0203;
+const uint32_t jerrno::JERR_JCNTL_NOTRECOVERED = 0x0204;
+const uint32_t jerrno::JERR_JCNTL_ENQSTATE = 0x0207;
+const uint32_t jerrno::JERR_JCNTL_INVALIDENQHDR = 0x0208;
+
+// class jdir
+const uint32_t jerrno::JERR_JDIR_NOTDIR = 0x0300;
+const uint32_t jerrno::JERR_JDIR_MKDIR = 0x0301;
+const uint32_t jerrno::JERR_JDIR_OPENDIR = 0x0302;
+const uint32_t jerrno::JERR_JDIR_READDIR = 0x0303;
+const uint32_t jerrno::JERR_JDIR_CLOSEDIR = 0x0304;
+const uint32_t jerrno::JERR_JDIR_RMDIR = 0x0305;
+const uint32_t jerrno::JERR_JDIR_NOSUCHFILE = 0x0306;
+const uint32_t jerrno::JERR_JDIR_FMOVE = 0x0307;
+const uint32_t jerrno::JERR_JDIR_STAT = 0x0308;
+const uint32_t jerrno::JERR_JDIR_UNLINK = 0x0309;
+const uint32_t jerrno::JERR_JDIR_BADFTYPE = 0x030a;
+
+// class JournalFile
+const uint32_t jerrno::JERR_JNLF_OPEN = 0x0400;
+const uint32_t jerrno::JERR_JNLF_CLOSE = 0x0401;
+const uint32_t jerrno::JERR_JNLF_FILEOFFSOVFL = 0x0402;
+const uint32_t jerrno::JERR_JNLF_CMPLOFFSOVFL = 0x0403;
+
+// class LinearFileController
+const uint32_t jerrno::JERR_LFCR_SEQNUMNOTFOUND = 0x0500;
+
+// class jrec, enq_rec, deq_rec, txn_rec
+const uint32_t jerrno::JERR_JREC_BADRECHDR = 0x0700;
+const uint32_t jerrno::JERR_JREC_BADRECTAIL = 0x0701;
+
+// class wmgr
+const uint32_t jerrno::JERR_WMGR_BADPGSTATE = 0x0801;
+const uint32_t jerrno::JERR_WMGR_BADDTOKSTATE = 0x0802;
+const uint32_t jerrno::JERR_WMGR_ENQDISCONT = 0x0803;
+const uint32_t jerrno::JERR_WMGR_DEQDISCONT = 0x0804;
+const uint32_t jerrno::JERR_WMGR_DEQRIDNOTENQ = 0x0805;
+const uint32_t jerrno::JERR_WMGR_BADFH = 0x0806;
+const uint32_t jerrno::JERR_WMGR_NOTSBLKALIGNED = 0x0807;
+
+// class RecoveryManager
+const uint32_t jerrno::JERR_RCVM_OPENRD = 0x0900;
+const uint32_t jerrno::JERR_RCVM_STREAMBAD = 0x0901;
+const uint32_t jerrno::JERR_RCVM_READ = 0x0902;
+const uint32_t jerrno::JERR_RCVM_WRITE = 0x0903;
+const uint32_t jerrno::JERR_RCVM_NULLXID = 0x0904;
+const uint32_t jerrno::JERR_RCVM_NOTDBLKALIGNED = 0x0905;
+const uint32_t jerrno::JERR_RCVM_NULLFID = 0x0907;
+const uint32_t jerrno::JERR_RCVM_INVALIDEFPID = 0x0908;
+
+// class data_tok
+const uint32_t jerrno::JERR_DTOK_ILLEGALSTATE = 0x0a00;
+// const uint32_t jerrno::JERR_DTOK_RIDNOTSET = 0x0a01;
+
+// class enq_map, txn_map
+const uint32_t jerrno::JERR_MAP_DUPLICATE = 0x0b00;
+const uint32_t jerrno::JERR_MAP_NOTFOUND = 0x0b01;
+const uint32_t jerrno::JERR_MAP_LOCKED = 0x0b02;
+
+// EFP errors
+const uint32_t jerrno::JERR_EFP_BADPARTITIONNAME = 0x0d01;
+const uint32_t jerrno::JERR_EFP_BADPARTITIONDIR = 0x0d02;
+const uint32_t jerrno::JERR_EFP_BADEFPDIRNAME = 0x0d03;
+const uint32_t jerrno::JERR_EFP_NOEFP = 0x0d04;
+const uint32_t jerrno::JERR_EFP_EMPTY = 0x0d05;
+const uint32_t jerrno::JERR_EFP_LSTAT = 0x0d06;
+const uint32_t jerrno::JERR_EFP_BADFILETYPE = 0x0d07;
+const uint32_t jerrno::JERR_EFP_FOPEN = 0x0d08;
+const uint32_t jerrno::JERR_EFP_FWRITE = 0x0d09;
+const uint32_t jerrno::JERR_EFP_MKDIR = 0x0d0a;
+
+// Negative returns for some functions
+const int32_t jerrno::AIO_TIMEOUT = -1;
+const int32_t jerrno::LOCK_TAKEN = -2;
+
+
+// static initialization fn
+
+bool
+jerrno::__init()
+{
+ // generic errors
+ _err_map[JERR__MALLOC] = "JERR__MALLOC: Buffer memory allocation failed.";
+ _err_map[JERR__UNDERFLOW] = "JERR__UNDERFLOW: Underflow error";
+ _err_map[JERR__NINIT] = "JERR__NINIT: Operation on uninitialized class.";
+ _err_map[JERR__AIO] = "JERR__AIO: AIO error.";
+ _err_map[JERR__FILEIO] = "JERR__FILEIO: File read or write failure.";
+ _err_map[JERR__RTCLOCK] = "JERR__RTCLOCK: Reading real-time clock failed.";
+ _err_map[JERR__PTHREAD] = "JERR__PTHREAD: pthread failure.";
+ _err_map[JERR__TIMEOUT] = "JERR__TIMEOUT: Timeout waiting for event.";
+ _err_map[JERR__UNEXPRESPONSE] = "JERR__UNEXPRESPONSE: Unexpected response to call or event.";
+ _err_map[JERR__RECNFOUND] = "JERR__RECNFOUND: Record not found.";
+ _err_map[JERR__NOTIMPL] = "JERR__NOTIMPL: Not implemented";
+ _err_map[JERR__NULL] = "JERR__NULL: Operation on null pointer";
+ _err_map[JERR__SYMLINK] = "JERR__SYMLINK: Symbolic link operation failed";
+
+ // class jcntl
+ _err_map[JERR_JCNTL_STOPPED] = "JERR_JCNTL_STOPPED: Operation on stopped journal.";
+ _err_map[JERR_JCNTL_READONLY] = "JERR_JCNTL_READONLY: Write operation on read-only journal (during recovery).";
+ _err_map[JERR_JCNTL_AIOCMPLWAIT] = "JERR_JCNTL_AIOCMPLWAIT: Timeout waiting for AIOs to complete.";
+ _err_map[JERR_JCNTL_UNKNOWNMAGIC] = "JERR_JCNTL_UNKNOWNMAGIC: Found record with unknown magic.";
+ _err_map[JERR_JCNTL_NOTRECOVERED] = "JERR_JCNTL_NOTRECOVERED: Operation requires recover() to be run first.";
+ _err_map[JERR_JCNTL_ENQSTATE] = "JERR_JCNTL_ENQSTATE: Read error: Record not in ENQ state";
+ _err_map[JERR_JCNTL_INVALIDENQHDR] = "JERR_JCNTL_INVALIDENQHDR: Invalid ENQ header";
+
+ // class jdir
+ _err_map[JERR_JDIR_NOTDIR] = "JERR_JDIR_NOTDIR: Directory name exists but is not a directory.";
+ _err_map[JERR_JDIR_MKDIR] = "JERR_JDIR_MKDIR: Directory creation failed.";
+ _err_map[JERR_JDIR_OPENDIR] = "JERR_JDIR_OPENDIR: Directory open failed.";
+ _err_map[JERR_JDIR_READDIR] = "JERR_JDIR_READDIR: Directory read failed.";
+ _err_map[JERR_JDIR_CLOSEDIR] = "JERR_JDIR_CLOSEDIR: Directory close failed.";
+ _err_map[JERR_JDIR_RMDIR] = "JERR_JDIR_RMDIR: Directory delete failed.";
+ _err_map[JERR_JDIR_NOSUCHFILE] = "JERR_JDIR_NOSUCHFILE: File does not exist.";
+ _err_map[JERR_JDIR_FMOVE] = "JERR_JDIR_FMOVE: File move failed.";
+ _err_map[JERR_JDIR_STAT] = "JERR_JDIR_STAT: File stat failed.";
+ _err_map[JERR_JDIR_UNLINK] = "JERR_JDIR_UNLINK: File delete failed.";
+ _err_map[JERR_JDIR_BADFTYPE] = "JERR_JDIR_BADFTYPE: Bad or unknown file type (stat mode).";
+
+ // class JournalFile
+ _err_map[JERR_JNLF_OPEN] = "JERR_JNLF_OPEN: Unable to open file for write";
+ _err_map[JERR_JNLF_CLOSE] = "JERR_JNLF_CLOSE: Unable to close file";
+ _err_map[JERR_JNLF_FILEOFFSOVFL] = "JERR_JNLF_FILEOFFSOVFL: Attempted to increase submitted offset past file size.";
+ _err_map[JERR_JNLF_CMPLOFFSOVFL] = "JERR_JNLF_CMPLOFFSOVFL: Attempted to increase completed file offset past submitted offset.";
+
+ // class LinearFileController
+ _err_map[JERR_LFCR_SEQNUMNOTFOUND] = "JERR_LFCR_SEQNUMNOTFOUND: File sequence number not found";
+
+ // class jrec, enq_rec, deq_rec, txn_rec
+ _err_map[JERR_JREC_BADRECHDR] = "JERR_JREC_BADRECHDR: Invalid record header.";
+ _err_map[JERR_JREC_BADRECTAIL] = "JERR_JREC_BADRECTAIL: Invalid record tail.";
+
+ // class wmgr
+ _err_map[JERR_WMGR_BADPGSTATE] = "JERR_WMGR_BADPGSTATE: Page buffer in illegal state for operation.";
+ _err_map[JERR_WMGR_BADDTOKSTATE] = "JERR_WMGR_BADDTOKSTATE: Data token in illegal state for operation.";
+ _err_map[JERR_WMGR_ENQDISCONT] = "JERR_WMGR_ENQDISCONT: Enqueued new dtok when previous enqueue returned partly completed (state ENQ_PART).";
+ _err_map[JERR_WMGR_DEQDISCONT] = "JERR_WMGR_DEQDISCONT: Dequeued new dtok when previous dequeue returned partly completed (state DEQ_PART).";
+ _err_map[JERR_WMGR_DEQRIDNOTENQ] = "JERR_WMGR_DEQRIDNOTENQ: Dequeue rid is not enqueued.";
+ _err_map[JERR_WMGR_BADFH] = "JERR_WMGR_BADFH: Bad file handle.";
+ _err_map[JERR_WMGR_NOTSBLKALIGNED] = "JERR_WMGR_NOTSBLKALIGNED: Offset is not soft block (sblk)-aligned";
+
+ // class RecoveryManager
+ _err_map[JERR_RCVM_OPENRD] = "JERR_RCVM_OPENRD: Unable to open file for read";
+ _err_map[JERR_RCVM_STREAMBAD] = "JERR_RCVM_STREAMBAD: Read/write stream error";
+ _err_map[JERR_RCVM_READ] = "JERR_RCVM_READ: Read error: no or insufficient data to read";
+ _err_map[JERR_RCVM_WRITE] = "JERR_RCVM_WRITE: Write error";
+ _err_map[JERR_RCVM_NULLXID] = "JERR_RCVM_NULLXID: Null XID when XID length non-null in header";
+ _err_map[JERR_RCVM_NOTDBLKALIGNED] = "JERR_RCVM_NOTDBLKALIGNED: Offset is not data block (dblk)-aligned";
+ _err_map[JERR_RCVM_NULLFID] = "JERR_RCVM_NULLFID: Null file id (FID)";
+ _err_map[JERR_RCVM_INVALIDEFPID] = "JERR_RCVM_INVALIDEFPID: Invalid EFP identity (partition/size)";
+
+ // class data_tok
+ _err_map[JERR_DTOK_ILLEGALSTATE] = "JERR_MTOK_ILLEGALSTATE: Attempted to change to illegal state.";
+ //_err_map[JERR_DTOK_RIDNOTSET] = "JERR_DTOK_RIDNOTSET: Record ID not set.";
+
+ // class enq_map, txn_map
+ _err_map[JERR_MAP_DUPLICATE] = "JERR_MAP_DUPLICATE: Attempted to insert record into map using duplicate key.";
+ _err_map[JERR_MAP_NOTFOUND] = "JERR_MAP_NOTFOUND: Key not found in map.";
+ _err_map[JERR_MAP_LOCKED] = "JERR_MAP_LOCKED: Record ID locked by a pending transaction.";
+
+ // EFP errors
+ _err_map[JERR_EFP_BADPARTITIONNAME] = "JERR_EFP_BADPARTITIONNAME: Invalid partition name (must be \'pNNN\' where NNN is a non-zero number)";
+ _err_map[JERR_EFP_BADEFPDIRNAME] = "JERR_EFP_BADEFPDIRNAME: Bad Empty File Pool directory name (must be \'NNNk\', where NNN is a number which is a multiple of 4)";
+ _err_map[JERR_EFP_BADPARTITIONDIR] = "JERR_EFP_BADPARTITIONDIR: Invalid partition directory";
+ _err_map[JERR_EFP_NOEFP] = "JERR_EFP_NOEFP: No Empty File Pool found for given partition and empty file size";
+ _err_map[JERR_EFP_EMPTY] = "JERR_EFP_EMPTY: Empty File Pool is empty";
+ _err_map[JERR_EFP_LSTAT] = "JERR_EFP_LSTAT: lstat() operation failed";
+ _err_map[JERR_EFP_BADFILETYPE] = "JERR_EFP_BADFILETYPE: File type incorrect for operation";
+ _err_map[JERR_EFP_FOPEN] = "JERR_EFP_FOPEN: Unable to fopen file for write";
+ _err_map[JERR_EFP_FWRITE] = "JERR_EFP_FWRITE: Write failed";
+ _err_map[JERR_EFP_MKDIR] = "JERR_EFP_MKDIR: Directory creation failed";
+
+ //_err_map[] = "";
+
+ return true;
+}
+
+const char*
+jerrno::err_msg(const uint32_t err_no) throw ()
+{
+ _err_map_itr = _err_map.find(err_no);
+ if (_err_map_itr == _err_map.end())
+ return "<Unknown error code>";
+ return _err_map_itr->second;
+}
+
+}}}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/jerrno.h b/qpid/cpp/src/qpid/linearstore/journal/jerrno.h
new file mode 100644
index 0000000000..6e817682ca
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/jerrno.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_JERRNO_H
+#define QPID_LINEARSTORE_JOURNAL_JERRNO_H
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+class jerrno;
+}}}
+
+#include <map>
+#include <stdint.h>
+#include <string>
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+ /**
+ * \class jerrno
+ * \brief Class containing static error definitions and static map for error messages.
+ */
+ class jerrno
+ {
+ static std::map<uint32_t, const char*> _err_map; ///< Map of error messages
+ static std::map<uint32_t, const char*>::iterator _err_map_itr; ///< Iterator
+ static bool _initialized; ///< Dummy flag, used to initialise map.
+
+ public:
+ // generic errors
+ static const uint32_t JERR__MALLOC; ///< Buffer memory allocation failed
+ static const uint32_t JERR__UNDERFLOW; ///< Underflow error
+ static const uint32_t JERR__NINIT; ///< Operation on uninitialized class
+ static const uint32_t JERR__AIO; ///< AIO failure
+ static const uint32_t JERR__FILEIO; ///< File read or write failure
+ static const uint32_t JERR__RTCLOCK; ///< Reading real-time clock failed
+ static const uint32_t JERR__PTHREAD; ///< pthread failure
+ static const uint32_t JERR__TIMEOUT; ///< Timeout waiting for an event
+ static const uint32_t JERR__UNEXPRESPONSE; ///< Unexpected response to call or event
+ static const uint32_t JERR__RECNFOUND; ///< Record not found
+ static const uint32_t JERR__NOTIMPL; ///< Not implemented
+ static const uint32_t JERR__NULL; ///< Operation on null pointer
+ static const uint32_t JERR__SYMLINK; ///< Symbolic Link operation failed
+
+ // class jcntl
+ static const uint32_t JERR_JCNTL_STOPPED; ///< Operation on stopped journal
+ static const uint32_t JERR_JCNTL_READONLY; ///< Write operation on read-only journal
+ static const uint32_t JERR_JCNTL_AIOCMPLWAIT; ///< Timeout waiting for AIOs to complete
+ static const uint32_t JERR_JCNTL_UNKNOWNMAGIC; ///< Found record with unknown magic
+ static const uint32_t JERR_JCNTL_NOTRECOVERED; ///< Req' recover() to be called first
+ static const uint32_t JERR_JCNTL_ENQSTATE; ///< Read error: Record not in ENQ state
+ static const uint32_t JERR_JCNTL_INVALIDENQHDR; ///< Invalid ENQ header
+
+ // class jdir
+ static const uint32_t JERR_JDIR_NOTDIR; ///< Exists but is not a directory
+ static const uint32_t JERR_JDIR_MKDIR; ///< Directory creation failed
+ static const uint32_t JERR_JDIR_OPENDIR; ///< Directory open failed
+ static const uint32_t JERR_JDIR_READDIR; ///< Directory read failed
+ static const uint32_t JERR_JDIR_CLOSEDIR; ///< Directory close failed
+ static const uint32_t JERR_JDIR_RMDIR; ///< Directory delete failed
+ static const uint32_t JERR_JDIR_NOSUCHFILE; ///< File does not exist
+ static const uint32_t JERR_JDIR_FMOVE; ///< File move failed
+ static const uint32_t JERR_JDIR_STAT; ///< File stat failed
+ static const uint32_t JERR_JDIR_UNLINK; ///< File delete failed
+ static const uint32_t JERR_JDIR_BADFTYPE; ///< Bad or unknown file type (stat mode)
+
+ // class JournalFile
+ static const uint32_t JERR_JNLF_OPEN; ///< Unable to open file for write
+ static const uint32_t JERR_JNLF_CLOSE; ///< Unable to close file
+ static const uint32_t JERR_JNLF_FILEOFFSOVFL; ///< Increased offset past file size
+ static const uint32_t JERR_JNLF_CMPLOFFSOVFL; ///< Increased cmpl offs past subm offs
+
+ // class LinearFileController
+ static const uint32_t JERR_LFCR_SEQNUMNOTFOUND; ///< File sequence number not found
+
+ // class jrec, enq_rec, deq_rec, txn_rec
+ static const uint32_t JERR_JREC_BADRECHDR; ///< Invalid data record header
+ static const uint32_t JERR_JREC_BADRECTAIL; ///< Invalid data record tail
+
+ // class wmgr
+ static const uint32_t JERR_WMGR_BADPGSTATE; ///< Page buffer in illegal state.
+ static const uint32_t JERR_WMGR_BADDTOKSTATE; ///< Data token in illegal state.
+ static const uint32_t JERR_WMGR_ENQDISCONT; ///< Enq. new dtok when previous part compl.
+ static const uint32_t JERR_WMGR_DEQDISCONT; ///< Deq. new dtok when previous part compl.
+ static const uint32_t JERR_WMGR_DEQRIDNOTENQ; ///< Deq. rid not enqueued
+ static const uint32_t JERR_WMGR_BADFH; ///< Bad file handle
+ static const uint32_t JERR_WMGR_NOTSBLKALIGNED; ///< Offset is not soft block (sblk)-aligned
+
+ // class RecoveryManager
+ static const uint32_t JERR_RCVM_OPENRD; ///< Unable to open file for read
+ static const uint32_t JERR_RCVM_STREAMBAD; ///< Read/write stream error
+ static const uint32_t JERR_RCVM_READ; ///< Read error: no or insufficient data to read
+ static const uint32_t JERR_RCVM_WRITE; ///< Write error
+ static const uint32_t JERR_RCVM_NULLXID; ///< Null XID when XID length non-null in header
+ static const uint32_t JERR_RCVM_NOTDBLKALIGNED; ///< Offset is not data block (dblk)-aligned
+ static const uint32_t JERR_RCVM_NULLFID; ///< Null file ID (FID)
+ static const uint32_t JERR_RCVM_INVALIDEFPID; ///< Invalid EFP identity (partition/size)
+
+ // class data_tok
+ static const uint32_t JERR_DTOK_ILLEGALSTATE; ///< Attempted to change to illegal state
+// static const uint32_t JERR_DTOK_RIDNOTSET; ///< Record ID not set
+
+ // class enq_map, txn_map
+ static const uint32_t JERR_MAP_DUPLICATE; ///< Attempted to insert using duplicate key
+ static const uint32_t JERR_MAP_NOTFOUND; ///< Key not found in map
+ static const uint32_t JERR_MAP_LOCKED; ///< rid locked by pending txn
+
+ // EFP errors
+ static const uint32_t JERR_EFP_BADPARTITIONNAME; ///< Partition name invalid or of value 0
+ static const uint32_t JERR_EFP_BADEFPDIRNAME; ///< Empty File Pool directory name invalid
+ static const uint32_t JERR_EFP_BADPARTITIONDIR; ///< Invalid partition directory
+ static const uint32_t JERR_EFP_NOEFP; ///< No EFP found for given partition and file size
+ static const uint32_t JERR_EFP_EMPTY; ///< EFP empty
+ static const uint32_t JERR_EFP_LSTAT; ///< lstat operation failed
+ static const uint32_t JERR_EFP_BADFILETYPE; ///< Bad file type
+ static const uint32_t JERR_EFP_FOPEN; ///< Unable to fopen file for write
+ static const uint32_t JERR_EFP_FWRITE; ///< Write failed
+ static const uint32_t JERR_EFP_MKDIR; ///< Directory creation failed
+
+ // Negative returns for some functions
+ static const int32_t AIO_TIMEOUT; ///< Timeout waiting for AIO return
+ static const int32_t LOCK_TAKEN; ///< Attempted to take lock, but it was taken by another thread
+ /**
+ * \brief Method to access error message from known error number.
+ */
+ static const char* err_msg(const uint32_t err_no) throw ();
+
+ private:
+ /**
+ * \brief Static function to initialize map.
+ */
+ static bool __init();
+ };
+
+}}}
+
+#endif // ifndef QPID_LINEARSTORE_JOURNAL_JERRNO_H
diff --git a/qpid/cpp/src/qpid/linearstore/journal/jexception.cpp b/qpid/cpp/src/qpid/linearstore/journal/jexception.cpp
new file mode 100644
index 0000000000..49f486746a
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/jexception.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/linearstore/journal/jexception.h"
+
+#include <iomanip>
+
+#define CATLEN(p) MAX_MSG_SIZE - std::strlen(p) - 1
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+jexception::jexception() throw ():
+ std::exception(),
+ _err_code(0)
+{
+ format();
+}
+
+jexception::jexception(const uint32_t err_code) throw ():
+ std::exception(),
+ _err_code(err_code)
+{
+ format();
+}
+
+jexception::jexception(const char* additional_info) throw ():
+ std::exception(),
+ _err_code(0),
+ _additional_info(additional_info)
+{
+ format();
+}
+
+jexception::jexception(const std::string& additional_info) throw ():
+ std::exception(),
+ _err_code(0),
+ _additional_info(additional_info)
+{
+ format();
+}
+
+jexception::jexception(const uint32_t err_code, const char* additional_info) throw ():
+ std::exception(),
+ _err_code(err_code),
+ _additional_info(additional_info)
+{
+ format();
+}
+
+jexception::jexception(const uint32_t err_code, const std::string& additional_info) throw ():
+ std::exception(),
+ _err_code(err_code),
+ _additional_info(additional_info)
+{
+ format();
+}
+
+jexception::jexception(const uint32_t err_code, const char* throwing_class,
+ const char* throwing_fn) throw ():
+ std::exception(),
+ _err_code(err_code),
+ _throwing_class(throwing_class),
+ _throwing_fn(throwing_fn)
+{
+ format();
+}
+
+jexception::jexception(const uint32_t err_code, const std::string& throwing_class,
+ const std::string& throwing_fn) throw ():
+ std::exception(),
+ _err_code(err_code),
+ _throwing_class(throwing_class),
+ _throwing_fn(throwing_fn)
+{
+ format();
+}
+
+jexception::jexception(const uint32_t err_code, const char* additional_info,
+ const char* throwing_class, const char* throwing_fn) throw ():
+ std::exception(),
+ _err_code(err_code),
+ _additional_info(additional_info),
+ _throwing_class(throwing_class),
+ _throwing_fn(throwing_fn)
+{
+ format();
+}
+
+jexception::jexception(const uint32_t err_code, const std::string& additional_info,
+ const std::string& throwing_class, const std::string& throwing_fn) throw ():
+ std::exception(),
+ _err_code(err_code),
+ _additional_info(additional_info),
+ _throwing_class(throwing_class),
+ _throwing_fn(throwing_fn)
+{
+ format();
+}
+
+jexception::~jexception() throw ()
+{}
+
+void
+jexception::format()
+{
+ const bool ai = !_additional_info.empty();
+ const bool tc = !_throwing_class.empty();
+ const bool tf = !_throwing_fn.empty();
+ std::ostringstream oss;
+ oss << "jexception 0x" << std::hex << std::setfill('0') << std::setw(4) << _err_code << " ";
+ if (tc)
+ {
+ oss << _throwing_class;
+ if (tf)
+ oss << "::";
+ else
+ oss << " ";
+ }
+ if (tf)
+ oss << _throwing_fn << "() ";
+ if (tc || tf)
+ oss << "threw " << jerrno::err_msg(_err_code);
+ if (ai)
+ oss << " (" << _additional_info << ")";
+ _what.assign(oss.str());
+}
+
+const char*
+jexception::what() const throw ()
+{
+ return _what.c_str();
+}
+
+std::ostream&
+operator<<(std::ostream& os, const jexception& je)
+{
+ os << je.what();
+ return os;
+}
+
+std::ostream&
+operator<<(std::ostream& os, const jexception* jePtr)
+{
+ os << jePtr->what();
+ return os;
+}
+
+}}}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/jexception.h b/qpid/cpp/src/qpid/linearstore/journal/jexception.h
new file mode 100644
index 0000000000..d03ee32e3f
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/jexception.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_JEXCEPTION_H
+#define QPID_LINEARSTORE_JOURNAL_JEXCEPTION_H
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+class jexception;
+}}}
+
+#include <cerrno>
+#include <cstdio>
+#include <cstdlib>
+#include <cstring>
+#include <exception>
+#include "qpid/linearstore/journal/jerrno.h"
+#include <sstream>
+#include <string>
+
+// Macro for formatting commom system errors
+#define FORMAT_SYSERR(errno) " errno=" << errno << " (" << std::strerror(errno) << ")"
+
+#define MALLOC_CHK(ptr, var, cls, fn) if(ptr == 0) { \
+ clean(); \
+ std::ostringstream oss; \
+ oss << var << ": malloc() failed: " << FORMAT_SYSERR(errno); \
+ throw jexception(jerrno::JERR__MALLOC, oss.str(), cls, fn); \
+ }
+
+// TODO: The following is a temporary bug-tracking aid which forces a core.
+// Replace with the commented out version below when BZ484048 is resolved.
+#define PTHREAD_CHK(err, pfn, cls, fn) if(err != 0) { \
+ std::ostringstream oss; \
+ oss << cls << "::" << fn << "(): " << pfn; \
+ errno = err; \
+ ::perror(oss.str().c_str()); \
+ ::abort(); \
+ }
+/*
+#define PTHREAD_CHK(err, pfn, cls, fn) if(err != 0) { \
+ std::ostringstream oss; \
+ oss << pfn << " failed: " << FORMAT_SYSERR(err); \
+ throw jexception(jerrno::JERR__PTHREAD, oss.str(), cls, fn); \
+ }
+*/
+
+#define ASSERT(cond, msg) if(cond == 0) { \
+ std::cerr << msg << std::endl; \
+ ::abort(); \
+ }
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+ /**
+ * \class jexception
+ * \brief Generic journal exception class
+ */
+ class jexception : public std::exception
+ {
+ private:
+ uint32_t _err_code;
+ std::string _additional_info;
+ std::string _throwing_class;
+ std::string _throwing_fn;
+ std::string _what;
+ void format();
+
+ public:
+ jexception() throw ();
+
+ jexception(const uint32_t err_code) throw ();
+
+ jexception(const char* additional_info) throw ();
+ jexception(const std::string& additional_info) throw ();
+
+ jexception(const uint32_t err_code, const char* additional_info) throw ();
+ jexception(const uint32_t err_code, const std::string& additional_info) throw ();
+
+ jexception(const uint32_t err_code, const char* throwing_class, const char* throwing_fn)
+ throw ();
+ jexception(const uint32_t err_code, const std::string& throwing_class,
+ const std::string& throwing_fn) throw ();
+
+ jexception(const uint32_t err_code, const char* additional_info,
+ const char* throwing_class, const char* throwing_fn) throw ();
+ jexception(const uint32_t err_code, const std::string& additional_info,
+ const std::string& throwing_class, const std::string& throwing_fn) throw ();
+
+ virtual ~jexception() throw ();
+ virtual const char* what() const throw (); // override std::exception::what()
+
+ inline uint32_t err_code() const throw () { return _err_code; }
+ inline const std::string additional_info() const throw () { return _additional_info; }
+ inline const std::string throwing_class() const throw () { return _throwing_class; }
+ inline const std::string throwing_fn() const throw () { return _throwing_fn; }
+
+ friend std::ostream& operator<<(std::ostream& os, const jexception& je);
+ friend std::ostream& operator<<(std::ostream& os, const jexception* jePtr);
+ }; // class jexception
+
+}}}
+
+#endif // ifndef QPID_LINEARSTORE_JOURNAL_JEXCEPTION_H
diff --git a/qpid/cpp/src/qpid/linearstore/journal/jrec.h b/qpid/cpp/src/qpid/linearstore/journal/jrec.h
new file mode 100644
index 0000000000..cad0e5d7a2
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/jrec.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_JREC_H
+#define QPID_LINEARSTORE_JOURNAL_JREC_H
+
+#include <fstream>
+#include "qpid/linearstore/journal/jcfg.h"
+#include <stdint.h>
+
+struct rec_hdr_t;
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+class Checksum;
+
+/**
+* \class jrec
+* \brief Abstract class for all file jrecords, both data and log. This class establishes
+* the common data format and structure for these jrecords.
+*/
+class jrec
+{
+public:
+ jrec() {}
+ virtual ~jrec() {}
+
+ /**
+ * \brief Encode this instance of jrec into the write buffer at the disk-block-aligned
+ * pointer wptr starting at position rec_offs_dblks in the encoded record to a
+ * maximum size of max_size_dblks.
+ *
+ * This call encodes the content of the data contianed in this instance of jrec into a
+ * disk-softblock-aligned (defined by JRNL_SBLK_SIZE) buffer pointed to by parameter
+ * wptr. No more than paramter max_size_dblks data-blocks may be written to the buffer.
+ * The parameter rec_offs_dblks is the offset in data-blocks within the fully encoded
+ * data block this instance represents at which to start encoding.
+ *
+ * Encoding entails writing the record header (struct enq_hdr), the data and the record tail
+ * (struct enq_tail). The record must be data-block-aligned (defined by JRNL_DBLK_SIZE),
+ * thus any remaining space in the final data-block is ignored; the returned value is the
+ * number of data-blocks consumed from the page by the encode action. Provided the initial
+ * alignment requirements are met, records may be of arbitrary size and may span multiple
+ * data-blocks, disk-blocks and/or pages.
+ *
+ * Since the record size in data-blocks is known, the general usage pattern is to call
+ * encode() as many times as is needed to fully encode the data. Each call to encode()
+ * will encode as much of the record as it can to what remains of the current page cache,
+ * and will return the number of data-blocks actually encoded.
+ *
+ * <b>Example:</b> Assume that record r1 was previously written to page 0, and that this
+ * is an instance representing record r2. Being larger than the page size ps, r2 would span
+ * multiple pages as follows:
+ * <pre>
+ * |<---ps--->|
+ * +----------+----------+----------+----...
+ * | |r2a| r2b | r2c | |
+ * |<-r1-><----------r2----------> |
+ * +----------+----------+----------+----...
+ * page: p0 p1 p2
+ * </pre>
+ * Encoding record r2 will require multiple calls to encode; one for each page which
+ * is involved. Record r2 is divided logically into sections r2a, r2b and r2c at the
+ * points where the page boundaries intersect with the record. Assuming a page size
+ * of ps, the page boundary pointers are represented by their names p0, p1... and the
+ * sizes of the record segments are represented by their names r1, r2a, r2b..., the calls
+ * should be as follows:
+ * <pre>
+ * encode(p0+r1, 0, ps-r1); (returns r2a data-blocks)
+ * encode(p1, r2a, ps); (returns r2b data-blocks which equals ps)
+ * encode(p2, r2a+r2b, ps); (returns r2c data-blocks)
+ * </pre>
+ *
+ * \param wptr Data-block-aligned pointer to position in page buffer where encoding is to
+ * take place.
+ * \param rec_offs_dblks Offset in data-blocks within record from which to start encoding.
+ * \param max_size_dblks Maximum number of data-blocks to write to pointer wptr.
+ * \returns Number of data-blocks encoded.
+ */
+ virtual uint32_t encode(void* wptr, uint32_t rec_offs_dblks, uint32_t max_size_dblks, Checksum& checksum) = 0;
+ virtual bool decode(::rec_hdr_t& h, std::ifstream* ifsp, std::size_t& rec_offs, const std::streampos rec_start) = 0;
+
+ virtual std::string& str(std::string& str) const = 0;
+ virtual std::size_t data_size() const = 0;
+ virtual std::size_t xid_size() const = 0;
+ virtual std::size_t rec_size() const = 0;
+ inline virtual uint32_t rec_size_dblks() const { return size_dblks(rec_size()); }
+ static inline uint32_t size_dblks(const std::size_t size)
+ { return size_blks(size, QLS_DBLK_SIZE_BYTES); }
+ static inline uint32_t size_sblks(const std::size_t size)
+ { return size_blks(size, QLS_SBLK_SIZE_BYTES); }
+ static inline uint32_t size_blks(const std::size_t size, const std::size_t blksize)
+ { return (size + blksize - 1)/blksize; }
+ virtual uint64_t rid() const = 0;
+
+protected:
+ virtual void clean() = 0;
+};
+
+}}}
+
+#endif // ifndef QPID_LINEARSTORE_JRNL_JREC_H
diff --git a/qpid/cpp/src/qpid/linearstore/journal/pmgr.cpp b/qpid/cpp/src/qpid/linearstore/journal/pmgr.cpp
new file mode 100644
index 0000000000..764beaa879
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/pmgr.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/linearstore/journal/pmgr.h"
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+pmgr::page_cb::page_cb(uint16_t index):
+ _index(index),
+ _state(UNUSED),
+ _frid(0),
+ _wdblks(0),
+ _pdtokl(0),
+ _jfp(0),
+ _pbuff(0)
+{}
+
+// TODO: almost identical to pmgr::page_state_str() below - resolve
+const char*
+pmgr::page_cb::state_str() const
+{
+ switch(_state)
+ {
+ case UNUSED:
+ return "UNUSED";
+ case IN_USE:
+ return "IN_USE";
+ case AIO_PENDING:
+ return "AIO_PENDING";
+ }
+ return "<unknown>";
+}
+
+// static
+const uint32_t pmgr::_sblkSizeBytes = QLS_SBLK_SIZE_BYTES;
+
+pmgr::pmgr(jcntl* jc, enq_map& emap, txn_map& tmap):
+ _cache_pgsize_sblks(0),
+ _cache_num_pages(0),
+ _jc(jc),
+ _emap(emap),
+ _tmap(tmap),
+ _page_base_ptr(0),
+ _page_ptr_arr(0),
+ _page_cb_arr(0),
+ _aio_cb_arr(0),
+ _aio_event_arr(0),
+ _ioctx(0),
+ _pg_index(0),
+ _pg_cntr(0),
+ _pg_offset_dblks(0),
+ _aio_evt_rem(0),
+ _cbp(0),
+ _enq_rec(),
+ _deq_rec(),
+ _txn_rec()
+{}
+
+pmgr::~pmgr()
+{
+ pmgr::clean();
+}
+
+void
+pmgr::initialize(aio_callback* const cbp, const uint32_t cache_pgsize_sblks, const uint16_t cache_num_pages)
+{
+ // As static use of this class keeps old values around, clean up first...
+ pmgr::clean();
+ _pg_index = 0;
+ _pg_cntr = 0;
+ _pg_offset_dblks = 0;
+ _aio_evt_rem = 0;
+ _cache_pgsize_sblks = cache_pgsize_sblks;
+ _cache_num_pages = cache_num_pages;
+ _cbp = cbp;
+
+ // 1. Allocate page memory (as a single block)
+ std::size_t cache_pgsize = _cache_num_pages * _cache_pgsize_sblks * _sblkSizeBytes;
+ if (::posix_memalign(&_page_base_ptr, QLS_AIO_ALIGN_BOUNDARY_BYTES, cache_pgsize))
+ {
+ clean();
+ std::ostringstream oss;
+ oss << "posix_memalign(): alignment=" << QLS_AIO_ALIGN_BOUNDARY_BYTES << " size=" << cache_pgsize;
+ oss << FORMAT_SYSERR(errno);
+ throw jexception(jerrno::JERR__MALLOC, oss.str(), "pmgr", "initialize");
+ }
+
+ // 2. Allocate array of page pointers
+ _page_ptr_arr = (void**)std::malloc(_cache_num_pages * sizeof(void*));
+ MALLOC_CHK(_page_ptr_arr, "_page_ptr_arr", "pmgr", "initialize");
+
+ // 3. Allocate and initialize page control block (page_cb) array
+ _page_cb_arr = (page_cb*)std::malloc(_cache_num_pages * sizeof(page_cb));
+ MALLOC_CHK(_page_cb_arr, "_page_cb_arr", "pmgr", "initialize");
+ std::memset(_page_cb_arr, 0, _cache_num_pages * sizeof(page_cb));
+
+ // 4. Allocate IO control block (iocb) array
+ _aio_cb_arr = (aio_cb*)std::malloc(_cache_num_pages * sizeof(aio_cb));
+ MALLOC_CHK(_aio_cb_arr, "_aio_cb_arr", "pmgr", "initialize");
+
+ // 5. Set page pointers in _page_ptr_arr, _page_cb_arr and iocbs to pages within page block
+ for (uint16_t i=0; i<_cache_num_pages; i++)
+ {
+ _page_ptr_arr[i] = (void*)((char*)_page_base_ptr + _cache_pgsize_sblks * _sblkSizeBytes * i);
+ _page_cb_arr[i]._index = i;
+ _page_cb_arr[i]._state = UNUSED;
+ _page_cb_arr[i]._pbuff = _page_ptr_arr[i];
+ _page_cb_arr[i]._pdtokl = new std::deque<data_tok*>;
+ _page_cb_arr[i]._pdtokl->clear();
+ _aio_cb_arr[i].data = (void*)&_page_cb_arr[i];
+ }
+
+ // 6. Allocate io_event array, max one event per cache page plus one for each file
+ const uint16_t max_aio_evts = _cache_num_pages + 1; // One additional event for file header writes
+ _aio_event_arr = (aio_event*)std::malloc(max_aio_evts * sizeof(aio_event));
+ MALLOC_CHK(_aio_event_arr, "_aio_event_arr", "pmgr", "initialize");
+
+ // 7. Initialize AIO context
+ if (int ret = aio::queue_init(max_aio_evts, &_ioctx))
+ {
+ std::ostringstream oss;
+ oss << "io_queue_init() failed: " << FORMAT_SYSERR(-ret);
+ throw jexception(jerrno::JERR__AIO, oss.str(), "pmgr", "initialize");
+ }
+}
+
+void
+pmgr::clean()
+{
+ // Clean up allocated memory here
+
+ if (_ioctx)
+ aio::queue_release(_ioctx);
+
+ std::free(_page_base_ptr);
+ _page_base_ptr = 0;
+
+ if (_page_cb_arr)
+ {
+ for (int i=0; i<_cache_num_pages; i++)
+ delete _page_cb_arr[i]._pdtokl;
+ std::free(_page_ptr_arr);
+ _page_ptr_arr = 0;
+ }
+
+ std::free(_page_cb_arr);
+ _page_cb_arr = 0;
+
+ std::free(_aio_cb_arr);
+ _aio_cb_arr = 0;
+
+ std::free(_aio_event_arr);
+ _aio_event_arr = 0;
+}
+
+// TODO: almost identical to pmgr::page_cb::state_str() above - resolve
+const char*
+pmgr::page_state_str(page_state ps)
+{
+ switch (ps)
+ {
+ case UNUSED:
+ return "UNUSED";
+ case IN_USE:
+ return "IN_USE";
+ case AIO_PENDING:
+ return "AIO_PENDING";
+ }
+ return "<page_state unknown>";
+}
+
+}}}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/pmgr.h b/qpid/cpp/src/qpid/linearstore/journal/pmgr.h
new file mode 100644
index 0000000000..e618397647
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/pmgr.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_PMGR_H
+#define QPID_LINEARSTORE_JOURNAL_PMGR_H
+
+#include <deque>
+#include "qpid/linearstore/journal/aio.h"
+#include "qpid/linearstore/journal/deq_rec.h"
+#include "qpid/linearstore/journal/enq_map.h"
+#include "qpid/linearstore/journal/enq_rec.h"
+#include "qpid/linearstore/journal/txn_map.h"
+#include "qpid/linearstore/journal/txn_rec.h"
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+class aio_callback;
+class data_tok;
+class jcntl;
+class JournalFile;
+
+/**
+* \brief Abstract class for managing either read or write page cache of arbitrary size and
+* number of cache_num_pages.
+*/
+class pmgr
+{
+public:
+ /**
+ * \brief Enumeration of possible stats of a page within a page cache.
+ */
+ enum page_state
+ {
+ UNUSED, ///< A page is uninitialized, contains no data.
+ IN_USE, ///< Page is in use.
+ AIO_PENDING ///< An AIO request outstanding.
+ };
+
+ /**
+ * \brief Page control block, carries control and state information for each page in the
+ * cache.
+ */
+ struct page_cb
+ {
+ uint16_t _index; ///< Index of this page
+ page_state _state; ///< Status of page
+ uint64_t _frid; ///< First rid in page (used for fhdr init)
+ uint32_t _wdblks; ///< Total number of dblks in page so far
+ std::deque<data_tok*>* _pdtokl; ///< Page message tokens list
+ JournalFile* _jfp; ///< Journal file for incrementing compl counts
+ void* _pbuff; ///< Page buffer
+
+ page_cb(uint16_t index); ///< Convenience constructor
+ const char* state_str() const; ///< Return state as string for this pcb
+ };
+
+protected:
+ static const uint32_t _sblkSizeBytes; ///< Disk softblock size
+ uint32_t _cache_pgsize_sblks; ///< Size of page cache cache_num_pages
+ uint16_t _cache_num_pages; ///< Number of page cache cache_num_pages
+ jcntl* _jc; ///< Pointer to journal controller
+ enq_map& _emap; ///< Ref to enqueue map
+ txn_map& _tmap; ///< Ref to transaction map
+ void* _page_base_ptr; ///< Base pointer to page memory
+ void** _page_ptr_arr; ///< Array of pointers to cache_num_pages in page memory
+ page_cb* _page_cb_arr; ///< Array of page_cb structs
+ aio_cb* _aio_cb_arr; ///< Array of iocb structs
+ aio_event* _aio_event_arr; ///< Array of io_events
+ io_context_t _ioctx; ///< AIO context for read/write operations
+ uint16_t _pg_index; ///< Index of current page being used
+ uint32_t _pg_cntr; ///< Page counter; determines if file rotation req'd
+ uint32_t _pg_offset_dblks; ///< Page offset (used so far) in data blocks
+ uint32_t _aio_evt_rem; ///< Remaining AIO events
+ aio_callback* _cbp; ///< Pointer to callback object
+
+ enq_rec _enq_rec; ///< Enqueue record used for encoding/decoding
+ deq_rec _deq_rec; ///< Dequeue record used for encoding/decoding
+ txn_rec _txn_rec; ///< Transaction record used for encoding/decoding
+
+public:
+ pmgr(jcntl* jc, enq_map& emap, txn_map& tmap);
+ virtual ~pmgr();
+
+ virtual int32_t get_events(timespec* const timeout, bool flush) = 0;
+ inline uint32_t get_aio_evt_rem() const { return _aio_evt_rem; }
+ static const char* page_state_str(page_state ps);
+ inline uint32_t cache_pgsize_sblks() const { return _cache_pgsize_sblks; }
+ inline uint16_t cache_num_pages() const { return _cache_num_pages; }
+
+protected:
+ virtual void initialize(aio_callback* const cbp, const uint32_t cache_pgsize_sblks,
+ const uint16_t cache_num_pages);
+ virtual void rotate_page() = 0;
+ virtual void clean();
+};
+
+}}}
+
+#endif // ifndef QPID_LINEARSTORE_JOURNAL_PMGR_H
diff --git a/qpid/cpp/src/qpid/linearstore/journal/slock.h b/qpid/cpp/src/qpid/linearstore/journal/slock.h
new file mode 100644
index 0000000000..12e9e2d08c
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/slock.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_SLOCK_H
+#define QPID_LINEARSTORE_JOURNAL_SLOCK_H
+
+#include "qpid/linearstore/journal/smutex.h"
+#include <pthread.h>
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+// Ultra-simple scoped lock class, auto-releases mutex when it goes out-of-scope
+class slock
+{
+protected:
+ const smutex& _sm;
+public:
+ inline slock(const smutex& sm) : _sm(sm)
+ {
+ PTHREAD_CHK(::pthread_mutex_lock(_sm.get()), "::pthread_mutex_lock", "slock", "slock");
+ }
+ inline ~slock()
+ {
+ PTHREAD_CHK(::pthread_mutex_unlock(_sm.get()), "::pthread_mutex_unlock", "slock", "~slock");
+ }
+};
+
+// Ultra-simple scoped try-lock class, auto-releases mutex when it goes out-of-scope
+class stlock
+{
+protected:
+ const smutex& _sm;
+ bool _locked;
+public:
+ inline stlock(const smutex& sm) : _sm(sm), _locked(false)
+ {
+ int ret = ::pthread_mutex_trylock(_sm.get());
+ _locked = (ret == 0); // check if lock obtained
+ if (!_locked && ret != EBUSY) PTHREAD_CHK(ret, "::pthread_mutex_trylock", "stlock", "stlock");
+ }
+ inline ~stlock()
+ {
+ if (_locked)
+ PTHREAD_CHK(::pthread_mutex_unlock(_sm.get()), "::pthread_mutex_unlock", "stlock", "~stlock");
+ }
+ inline bool locked() const { return _locked; }
+};
+
+}}}
+
+#endif // ifndef QPID_LINEARSTORE_JOURNAL_SLOCK_H
diff --git a/qpid/cpp/src/qpid/linearstore/journal/smutex.h b/qpid/cpp/src/qpid/linearstore/journal/smutex.h
new file mode 100644
index 0000000000..b43f55944c
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/smutex.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_SMUTEX_H
+#define QPID_LINEARSTORE_JOURNAL_SMUTEX_H
+
+#include "qpid/linearstore/journal/jexception.h"
+#include <pthread.h>
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+ // Ultra-simple scoped mutex class that allows a posix mutex to be initialized and destroyed with error checks
+ class smutex
+ {
+ protected:
+ mutable pthread_mutex_t _m;
+ public:
+ inline smutex()
+ {
+ PTHREAD_CHK(::pthread_mutex_init(&_m, 0), "::pthread_mutex_init", "smutex", "smutex");
+ }
+ inline virtual ~smutex()
+ {
+ PTHREAD_CHK(::pthread_mutex_destroy(&_m), "::pthread_mutex_destroy", "smutex", "~smutex");
+ }
+ inline pthread_mutex_t* get() const { return &_m; }
+ };
+
+}}}
+
+#endif // ifndef QPID_LINEARSTORE_JOURNAL_SMUTEX_H
diff --git a/qpid/cpp/src/qpid/linearstore/journal/time_ns.cpp b/qpid/cpp/src/qpid/linearstore/journal/time_ns.cpp
new file mode 100644
index 0000000000..39f2cd1d88
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/time_ns.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/linearstore/journal/time_ns.h"
+
+#include <sstream>
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+const std::string
+time_ns::str(int precision) const
+{
+ const double t = tv_sec + (tv_nsec/1e9);
+ std::ostringstream oss;
+ oss.setf(std::ios::fixed, std::ios::floatfield);
+ oss.precision(precision);
+ oss << t;
+ return oss.str();
+}
+
+}}}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/time_ns.h b/qpid/cpp/src/qpid/linearstore/journal/time_ns.h
new file mode 100644
index 0000000000..a228d47475
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/time_ns.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_TIME_NS_H
+#define QPID_LINEARSTORE_JOURNAL_TIME_NS_H
+
+#include <cerrno>
+#include <ctime>
+#include <string>
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+struct time_ns : public timespec
+{
+ inline time_ns() { tv_sec = 0; tv_nsec = 0; }
+ inline time_ns(const std::time_t sec, const long nsec = 0) { tv_sec = sec; tv_nsec = nsec; }
+ inline time_ns(const time_ns& t) { tv_sec = t.tv_sec; tv_nsec = t.tv_nsec; }
+
+ inline void set_zero() { tv_sec = 0; tv_nsec = 0; }
+ inline bool is_zero() const { return tv_sec == 0 && tv_nsec == 0; }
+ inline int now() { if(::clock_gettime(CLOCK_REALTIME, this)) return errno; return 0; }
+ const std::string str(int precision = 6) const;
+
+ inline time_ns& operator=(const time_ns& rhs)
+ { tv_sec = rhs.tv_sec; tv_nsec = rhs.tv_nsec; return *this; }
+ inline time_ns& operator+=(const time_ns& rhs)
+ {
+ tv_nsec += rhs.tv_nsec;
+ if (tv_nsec >= 1000000000L) { tv_sec++; tv_nsec -= 1000000000L; }
+ tv_sec += rhs.tv_sec;
+ return *this;
+ }
+ inline time_ns& operator+=(const long ns)
+ {
+ tv_nsec += ns;
+ if (tv_nsec >= 1000000000L) { tv_sec++; tv_nsec -= 1000000000L; }
+ return *this;
+ }
+ inline time_ns& operator-=(const long ns)
+ {
+ tv_nsec -= ns;
+ if (tv_nsec < 0) { tv_sec--; tv_nsec += 1000000000L; }
+ return *this;
+ }
+ inline time_ns& operator-=(const time_ns& rhs)
+ {
+ tv_nsec -= rhs.tv_nsec;
+ if (tv_nsec < 0) { tv_sec--; tv_nsec += 1000000000L; }
+ tv_sec -= rhs.tv_sec;
+ return *this;
+ }
+ inline const time_ns operator+(const time_ns& rhs)
+ { time_ns t(*this); t += rhs; return t; }
+ inline const time_ns operator-(const time_ns& rhs)
+ { time_ns t(*this); t -= rhs; return t; }
+ inline bool operator==(const time_ns& rhs)
+ { return tv_sec == rhs.tv_sec && tv_nsec == rhs.tv_nsec; }
+ inline bool operator!=(const time_ns& rhs)
+ { return tv_sec != rhs.tv_sec || tv_nsec != rhs.tv_nsec; }
+ inline bool operator>(const time_ns& rhs)
+ { if(tv_sec == rhs.tv_sec) return tv_nsec > rhs.tv_nsec; return tv_sec > rhs.tv_sec; }
+ inline bool operator>=(const time_ns& rhs)
+ { if(tv_sec == rhs.tv_sec) return tv_nsec >= rhs.tv_nsec; return tv_sec >= rhs.tv_sec; }
+ inline bool operator<(const time_ns& rhs)
+ { if(tv_sec == rhs.tv_sec) return tv_nsec < rhs.tv_nsec; return tv_sec < rhs.tv_sec; }
+ inline bool operator<=(const time_ns& rhs)
+ { if(tv_sec == rhs.tv_sec) return tv_nsec <= rhs.tv_nsec; return tv_sec <= rhs.tv_sec; }
+};
+
+}}}
+
+#endif // ifndef QPID_LINEARSTORE_JOURNAL_TIME_NS_H
diff --git a/qpid/cpp/src/qpid/linearstore/journal/txn_map.cpp b/qpid/cpp/src/qpid/linearstore/journal/txn_map.cpp
new file mode 100644
index 0000000000..8336d36b80
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/txn_map.cpp
@@ -0,0 +1,263 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/linearstore/journal/txn_map.h"
+
+#include "qpid/linearstore/journal/slock.h"
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+// return/error codes
+int16_t txn_map::TMAP_RID_NOT_FOUND = -2;
+int16_t txn_map::TMAP_XID_NOT_FOUND = -1;
+int16_t txn_map::TMAP_OK = 0;
+int16_t txn_map::TMAP_NOT_SYNCED = 0;
+int16_t txn_map::TMAP_SYNCED = 1;
+
+txn_data_t::txn_data_t(const uint64_t rid,
+ const uint64_t drid,
+ const uint64_t fid,
+ const uint64_t foffs,
+ const bool enq_flag,
+ const bool tpc_flag,
+ const bool commit_flag):
+ rid_(rid),
+ drid_(drid),
+ fid_(fid),
+ foffs_(foffs),
+ enq_flag_(enq_flag),
+ tpc_flag_(tpc_flag),
+ commit_flag_(commit_flag),
+ aio_compl_(false)
+{}
+
+txn_op_stats_t::txn_op_stats_t(const txn_data_list_t& tdl) :
+ enqCnt(0U),
+ deqCnt(0U),
+ tpcCnt(0U),
+ abortCnt(0U),
+ commitCnt(0U),
+ rid(0ULL)
+{
+ for (tdl_const_itr_t i=tdl.begin(); i!=tdl.end(); ++i) {
+ if (i->enq_flag_) {
+ ++enqCnt;
+ rid = i->rid_;
+ } else {
+ ++deqCnt;
+ if (i->commit_flag_) {
+ ++commitCnt;
+ } else {
+ ++abortCnt;
+ }
+ }
+ if (i->tpc_flag_) {
+ ++tpcCnt;
+ }
+ }
+ if (tpcCnt > 0 && tpcCnt != tdl.size()) {
+ throw jexception("Inconsistent 2PC count"); // TODO: complete exception details
+ }
+ if (abortCnt > 0 && commitCnt > 0) {
+ throw jexception("Both abort and commit in same transaction"); // TODO: complete exception details
+ }
+}
+
+txn_map::txn_map():
+ _map()/*,
+ _pfid_txn_cnt()*/
+{}
+
+txn_map::~txn_map() {}
+
+bool
+txn_map::insert_txn_data(const std::string& xid, const txn_data_t& td)
+{
+ bool ok = true;
+ slock s(_mutex);
+ xmap_itr itr = _map.find(xid);
+ if (itr == _map.end()) // not found in map
+ {
+ txn_data_list_t list;
+ list.push_back(td);
+ std::pair<xmap_itr, bool> ret = _map.insert(xmap_param(xid, list));
+ if (!ret.second) // duplicate
+ ok = false;
+ }
+ else
+ itr->second.push_back(td);
+ return ok;
+}
+
+const txn_data_list_t
+txn_map::get_tdata_list(const std::string& xid)
+{
+ slock s(_mutex);
+ return get_tdata_list_nolock(xid);
+}
+
+const txn_data_list_t
+txn_map::get_tdata_list_nolock(const std::string& xid)
+{
+ xmap_itr itr = _map.find(xid);
+ if (itr == _map.end()) // not found in map
+ return _empty_data_list;
+ return itr->second;
+}
+
+const txn_data_list_t
+txn_map::get_remove_tdata_list(const std::string& xid)
+{
+ slock s(_mutex);
+ xmap_itr itr = _map.find(xid);
+ if (itr == _map.end()) // not found in map
+ return _empty_data_list;
+ txn_data_list_t list = itr->second;
+ _map.erase(itr);
+ return list;
+}
+
+bool
+txn_map::in_map(const std::string& xid)
+{
+ slock s(_mutex);
+ xmap_itr itr= _map.find(xid);
+ return itr != _map.end();
+}
+
+uint32_t
+txn_map::enq_cnt()
+{
+ return cnt(true);
+}
+
+uint32_t
+txn_map::deq_cnt()
+{
+ return cnt(true);
+}
+
+uint32_t
+txn_map::cnt(const bool enq_flag)
+{
+ slock s(_mutex);
+ uint32_t c = 0;
+ for (xmap_itr i = _map.begin(); i != _map.end(); i++)
+ {
+ for (tdl_itr_t j = i->second.begin(); j < i->second.end(); j++)
+ {
+ if (j->enq_flag_ == enq_flag)
+ c++;
+ }
+ }
+ return c;
+}
+
+int16_t
+txn_map::is_txn_synced(const std::string& xid)
+{
+ slock s(_mutex);
+ xmap_itr itr = _map.find(xid);
+ if (itr == _map.end()) // not found in map
+ return TMAP_XID_NOT_FOUND;
+ bool is_synced = true;
+ for (tdl_itr_t litr = itr->second.begin(); litr < itr->second.end(); litr++)
+ {
+ if (!litr->aio_compl_)
+ {
+ is_synced = false;
+ break;
+ }
+ }
+ return is_synced ? TMAP_SYNCED : TMAP_NOT_SYNCED;
+}
+
+int16_t
+txn_map::set_aio_compl(const std::string& xid, const uint64_t rid)
+{
+ slock s(_mutex);
+ xmap_itr itr = _map.find(xid);
+ if (itr == _map.end()) // xid not found in map
+ return TMAP_XID_NOT_FOUND;
+ for (tdl_itr_t litr = itr->second.begin(); litr < itr->second.end(); litr++)
+ {
+ if (litr->rid_ == rid)
+ {
+ litr->aio_compl_ = true;
+ return TMAP_OK; // rid found
+ }
+ }
+ // xid present, but rid not found
+ return TMAP_RID_NOT_FOUND;
+}
+
+bool
+txn_map::data_exists(const std::string& xid, const uint64_t rid)
+{
+ bool found = false;
+ {
+ slock s(_mutex);
+ txn_data_list_t tdl = get_tdata_list_nolock(xid);
+ tdl_itr_t itr = tdl.begin();
+ while (itr != tdl.end() && !found)
+ {
+ found = itr->rid_ == rid;
+ itr++;
+ }
+ }
+ return found;
+}
+
+bool
+txn_map::is_enq(const uint64_t rid)
+{
+ bool found = false;
+ {
+ slock s(_mutex);
+ for (xmap_itr i = _map.begin(); i != _map.end() && !found; i++)
+ {
+ txn_data_list_t list = i->second;
+ for (tdl_itr_t j = list.begin(); j < list.end() && !found; j++)
+ {
+ if (j->enq_flag_)
+ found = j->rid_ == rid;
+ else
+ found = j->drid_ == rid;
+ }
+ }
+ }
+ return found;
+}
+
+void
+txn_map::xid_list(std::vector<std::string>& xv)
+{
+ xv.clear();
+ {
+ slock s(_mutex);
+ for (xmap_itr itr = _map.begin(); itr != _map.end(); itr++)
+ xv.push_back(itr->first);
+ }
+}
+
+}}}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/txn_map.h b/qpid/cpp/src/qpid/linearstore/journal/txn_map.h
new file mode 100644
index 0000000000..e79c0522d8
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/txn_map.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_TXN_MAP_H
+#define QPID_LINEARSTORE_JOURNAL_TXN_MAP_H
+
+#include "qpid/linearstore/journal/smutex.h"
+#include <map>
+#include <vector>
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+ /**
+ * \struct txn_data_struct
+ * \brief Struct encapsulating transaction data necessary for processing a transaction
+ * in the journal once it is closed with either a commit or abort.
+ */
+ typedef struct txn_data_t
+ {
+ uint64_t rid_; ///< Record id for this operation
+ uint64_t drid_; ///< Dequeue record id for this operation
+ uint64_t fid_; ///< File seq number, to be used when transferring to emap on commit
+ uint64_t foffs_; ///< Offset in file for this record
+ bool enq_flag_; ///< If true, enq op, otherwise deq op
+ bool tpc_flag_; ///< 2PC transaction if true
+ bool commit_flag_; ///< TPL only: (2PC transactions) Records 2PC complete c/a mode
+ bool aio_compl_; ///< Initially false, set to true when record AIO returns
+ txn_data_t(const uint64_t rid,
+ const uint64_t drid,
+ const uint64_t fid,
+ const uint64_t foffs,
+ const bool enq_flag,
+ const bool tpc_flag,
+ const bool commit_flag);
+ } txn_data_t;
+ typedef std::vector<txn_data_t> txn_data_list_t;
+ typedef txn_data_list_t::iterator tdl_itr_t;
+ typedef txn_data_list_t::const_iterator tdl_const_itr_t;
+
+ typedef struct txn_op_stats_t
+ {
+ uint16_t enqCnt;
+ uint16_t deqCnt;
+ uint16_t tpcCnt;
+ uint16_t abortCnt;
+ uint16_t commitCnt;
+ uint64_t rid;
+ txn_op_stats_t(const txn_data_list_t& tdl);
+ } txn_op_stats_t;
+
+ /**
+ * \class txn_map
+ * \brief Class for storing transaction data for each open (ie not committed or aborted)
+ * xid in the store. If aborted, records are discarded; if committed, they are
+ * transferred to the enqueue map.
+ *
+ * The data is encapsulated by struct txn_data_struct. A vector containing the information
+ * for each operation included as part of the same transaction is mapped against the
+ * xid.
+ *
+ * The aio_compl flag is set true as each AIO write operation for the enqueue or dequeue
+ * returns. Checking that all of these flags are true for a given xid is the mechanism
+ * used to determine if the transaction is syncronized (through method is_txn_synced()).
+ *
+ * On transaction commit, then each operation is handled as follows:
+ *
+ * If an enqueue (_enq_flag is true), then the rid and pfid are transferred to the enq_map.
+ * If a dequeue (_enq_flag is false), then the rid stored in the drid field is used to
+ * remove the corresponding record from the enq_map.
+ *
+ * On transaction abort, then each operation is handled as follows:
+ *
+ * If an enqueue (_enq_flag is true), then the data is simply discarded.
+ * If a dequeue (_enq_flag is false), then the lock for the corresponding enqueue in enq_map
+ * (if not a part of the same transaction) is removed, and the data discarded.
+ *
+ * <pre>
+ * key data
+ *
+ * xid1 --- vector< [ rid, drid, pfid, enq_flag, commit_flag, aio_compl ] >
+ * xid2 --- vector< [ rid, drid, pfid, enq_flag, commit_flag, aio_compl ] >
+ * xid3 --- vector< [ rid, drid, pfid, enq_flag, commit_flag, aio_compl ] >
+ * ...
+ * </pre>
+ */
+ class txn_map
+ {
+ public:
+ // return/error codes
+ static int16_t TMAP_RID_NOT_FOUND;
+ static int16_t TMAP_XID_NOT_FOUND;
+ static int16_t TMAP_OK;
+ static int16_t TMAP_NOT_SYNCED;
+ static int16_t TMAP_SYNCED;
+
+ private:
+ typedef std::pair<std::string, txn_data_list_t> xmap_param;
+ typedef std::map<std::string, txn_data_list_t> xmap;
+ typedef xmap::iterator xmap_itr;
+
+ xmap _map;
+ smutex _mutex;
+ const txn_data_list_t _empty_data_list;
+
+ public:
+ txn_map();
+ virtual ~txn_map();
+
+ bool insert_txn_data(const std::string& xid, const txn_data_t& td);
+ const txn_data_list_t get_tdata_list(const std::string& xid);
+ const txn_data_list_t get_remove_tdata_list(const std::string& xid);
+ bool in_map(const std::string& xid);
+ uint32_t enq_cnt();
+ uint32_t deq_cnt();
+ int16_t is_txn_synced(const std::string& xid); // -1=xid not found; 0=not synced; 1=synced
+ int16_t set_aio_compl(const std::string& xid, const uint64_t rid); // -2=rid not found; -1=xid not found; 0=done
+ bool data_exists(const std::string& xid, const uint64_t rid);
+ bool is_enq(const uint64_t rid);
+ inline void clear() { _map.clear(); }
+ inline bool empty() const { return _map.empty(); }
+ inline size_t size() const { return _map.size(); }
+ void xid_list(std::vector<std::string>& xv);
+ private:
+ uint32_t cnt(const bool enq_flag);
+ const txn_data_list_t get_tdata_list_nolock(const std::string& xid);
+ };
+
+}}}
+
+#endif // ifndef QPID_LINEARSTORE_JOURNAL_TXN_MAP_H
diff --git a/qpid/cpp/src/qpid/linearstore/journal/txn_rec.cpp b/qpid/cpp/src/qpid/linearstore/journal/txn_rec.cpp
new file mode 100644
index 0000000000..298ab608b1
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/txn_rec.cpp
@@ -0,0 +1,305 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/linearstore/journal/txn_rec.h"
+
+#include <cassert>
+#include <cstring>
+#include "qpid/linearstore/journal/Checksum.h"
+#include "qpid/linearstore/journal/jexception.h"
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+txn_rec::txn_rec():
+ _xidp(0),
+ _xid_buff(0)
+{
+ ::txn_hdr_init(&_txn_hdr, 0, QLS_JRNL_VERSION, 0, 0, 0, 0);
+ ::rec_tail_init(&_txn_tail, 0, 0, 0, 0);
+}
+
+txn_rec::~txn_rec()
+{
+ clean();
+}
+
+void
+txn_rec::reset(const bool commitFlag, const uint64_t serial, const uint64_t rid, const void* const xidp,
+ const std::size_t xidlen)
+{
+ _txn_hdr._rhdr._magic = commitFlag ? QLS_TXC_MAGIC : QLS_TXA_MAGIC;
+ _txn_hdr._rhdr._serial = serial;
+ _txn_hdr._rhdr._rid = rid;
+ _txn_hdr._xidsize = xidlen;
+ _xidp = xidp;
+ _xid_buff = 0;
+ _txn_tail._xmagic = ~_txn_hdr._rhdr._magic;
+ _txn_tail._serial = serial;
+ _txn_tail._rid = rid;
+ _txn_tail._checksum = 0UL;
+}
+
+uint32_t
+txn_rec::encode(void* wptr, uint32_t rec_offs_dblks, uint32_t max_size_dblks, Checksum& checksum)
+{
+ assert(wptr != 0);
+ assert(max_size_dblks > 0);
+ assert(_xidp != 0 && _txn_hdr._xidsize > 0);
+
+ std::size_t rec_offs = rec_offs_dblks * QLS_DBLK_SIZE_BYTES;
+ std::size_t rem = max_size_dblks * QLS_DBLK_SIZE_BYTES;
+ std::size_t wr_cnt = 0;
+ if (rec_offs_dblks) // Continuation of split dequeue record (over 2 or more pages)
+ {
+ if (size_dblks(rec_size()) - rec_offs_dblks > max_size_dblks) // Further split required
+ {
+ rec_offs -= sizeof(txn_hdr_t);
+ std::size_t wsize = _txn_hdr._xidsize > rec_offs ? _txn_hdr._xidsize - rec_offs : 0;
+ std::size_t wsize2 = wsize;
+ if (wsize)
+ {
+ if (wsize > rem)
+ wsize = rem;
+ std::memcpy(wptr, (const char*)_xidp + rec_offs, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ rec_offs -= _txn_hdr._xidsize - wsize2;
+ checksum.addData((unsigned char*)wptr, wr_cnt);
+ if (rem)
+ {
+ _txn_tail._checksum = checksum.getChecksum();
+ wsize = sizeof(_txn_tail) > rec_offs ? sizeof(_txn_tail) - rec_offs : 0;
+ wsize2 = wsize;
+ if (wsize)
+ {
+ if (wsize > rem)
+ wsize = rem;
+ std::memcpy((char*)wptr + wr_cnt, (char*)&_txn_tail + rec_offs, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ rec_offs -= sizeof(_txn_tail) - wsize2;
+ }
+ assert(rem == 0);
+ assert(rec_offs == 0);
+ }
+ else // No further split required
+ {
+ rec_offs -= sizeof(txn_hdr_t);
+ std::size_t wsize = _txn_hdr._xidsize > rec_offs ? _txn_hdr._xidsize - rec_offs : 0;
+ if (wsize)
+ {
+ std::memcpy(wptr, (const char*)_xidp + rec_offs, wsize);
+ wr_cnt += wsize;
+ checksum.addData((unsigned char*)wptr, wr_cnt);
+ }
+ rec_offs -= _txn_hdr._xidsize - wsize;
+ _txn_tail._checksum = checksum.getChecksum();
+ wsize = sizeof(_txn_tail) > rec_offs ? sizeof(_txn_tail) - rec_offs : 0;
+ if (wsize)
+ {
+ std::memcpy((char*)wptr + wr_cnt, (char*)&_txn_tail + rec_offs, wsize);
+ wr_cnt += wsize;
+#ifdef QLS_CLEAN
+ std::size_t rec_offs = rec_offs_dblks * QLS_DBLK_SIZE_BYTES;
+ std::size_t dblk_rec_size = size_dblks(rec_size() - rec_offs) * QLS_DBLK_SIZE_BYTES;
+ std::memset((char*)wptr + wr_cnt, QLS_CLEAN_CHAR, dblk_rec_size - wr_cnt);
+#endif
+ }
+ rec_offs -= sizeof(_txn_tail) - wsize;
+ assert(rec_offs == 0);
+ }
+ }
+ else // Start at beginning of data record
+ {
+ // Assumption: the header will always fit into the first dblk
+ std::memcpy(wptr, (void*)&_txn_hdr, sizeof(txn_hdr_t));
+ wr_cnt = sizeof(txn_hdr_t);
+ if (size_dblks(rec_size()) > max_size_dblks) // Split required
+ {
+ std::size_t wsize;
+ rem -= sizeof(txn_hdr_t);
+ if (rem)
+ {
+ wsize = rem >= _txn_hdr._xidsize ? _txn_hdr._xidsize : rem;
+ std::memcpy((char*)wptr + wr_cnt, _xidp, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ checksum.addData((unsigned char*)wptr, wr_cnt);
+ if (rem)
+ {
+ _txn_tail._checksum = checksum.getChecksum();
+ wsize = rem >= sizeof(_txn_tail) ? sizeof(_txn_tail) : rem;
+ std::memcpy((char*)wptr + wr_cnt, (void*)&_txn_tail, wsize);
+ wr_cnt += wsize;
+ rem -= wsize;
+ }
+ assert(rem == 0);
+ }
+ else // No split required
+ {
+ std::memcpy((char*)wptr + wr_cnt, _xidp, _txn_hdr._xidsize);
+ wr_cnt += _txn_hdr._xidsize;
+ checksum.addData((unsigned char*)wptr, wr_cnt);
+ _txn_tail._checksum = checksum.getChecksum();
+ std::memcpy((char*)wptr + wr_cnt, (void*)&_txn_tail, sizeof(_txn_tail));
+ wr_cnt += sizeof(_txn_tail);
+#ifdef QLS_CLEAN
+ std::size_t dblk_rec_size = size_dblks(rec_size()) * QLS_DBLK_SIZE_BYTES;
+ std::memset((char*)wptr + wr_cnt, QLS_CLEAN_CHAR, dblk_rec_size - wr_cnt);
+#endif
+ }
+ }
+ return size_dblks(wr_cnt);
+}
+
+bool
+txn_rec::decode(::rec_hdr_t& h, std::ifstream* ifsp, std::size_t& rec_offs, const std::streampos rec_start)
+{
+ if (rec_offs == 0)
+ {
+ // Read header, allocate for xid
+ ::rec_hdr_copy(&_txn_hdr._rhdr, &h);
+ ifsp->read((char*)&_txn_hdr._xidsize, sizeof(_txn_hdr._xidsize));
+ rec_offs = sizeof(::txn_hdr_t);
+ _xid_buff = std::malloc(_txn_hdr._xidsize);
+ MALLOC_CHK(_xid_buff, "_buff", "txn_rec", "rcv_decode");
+ }
+ if (rec_offs < sizeof(txn_hdr_t) + _txn_hdr._xidsize)
+ {
+ // Read xid (or continue reading xid)
+ std::size_t offs = rec_offs - sizeof(txn_hdr_t);
+ ifsp->read((char*)_xid_buff + offs, _txn_hdr._xidsize - offs);
+ std::size_t size_read = ifsp->gcount();
+ rec_offs += size_read;
+ if (size_read < _txn_hdr._xidsize - offs)
+ {
+ assert(ifsp->eof());
+ // As we may have read past eof, turn off fail bit
+ ifsp->clear(ifsp->rdstate()&(~std::ifstream::failbit));
+ assert(!ifsp->fail() && !ifsp->bad());
+ return false;
+ }
+ }
+ if (rec_offs < sizeof(txn_hdr_t) + _txn_hdr._xidsize + sizeof(rec_tail_t))
+ {
+ // Read tail (or continue reading tail)
+ std::size_t offs = rec_offs - sizeof(txn_hdr_t) - _txn_hdr._xidsize;
+ ifsp->read((char*)&_txn_tail + offs, sizeof(rec_tail_t) - offs);
+ std::size_t size_read = ifsp->gcount();
+ rec_offs += size_read;
+ if (size_read < sizeof(rec_tail_t) - offs)
+ {
+ assert(ifsp->eof());
+ // As we may have read past eof, turn off fail bit
+ ifsp->clear(ifsp->rdstate()&(~std::ifstream::failbit));
+ assert(!ifsp->fail() && !ifsp->bad());
+ return false;
+ }
+ check_rec_tail(rec_start);
+ }
+ ifsp->ignore(rec_size_dblks() * QLS_DBLK_SIZE_BYTES - rec_size());
+ assert(!ifsp->fail() && !ifsp->bad());
+ assert(_txn_hdr._xidsize > 0);
+ return true;
+}
+
+std::size_t
+txn_rec::get_xid(void** const xidpp)
+{
+ if (!_xid_buff)
+ {
+ *xidpp = 0;
+ return 0;
+ }
+ *xidpp = _xid_buff;
+ return _txn_hdr._xidsize;
+}
+
+std::string&
+txn_rec::str(std::string& str) const
+{
+ std::ostringstream oss;
+ if (_txn_hdr._rhdr._magic == QLS_TXA_MAGIC)
+ oss << "dtxa_rec: m=" << _txn_hdr._rhdr._magic;
+ else
+ oss << "dtxc_rec: m=" << _txn_hdr._rhdr._magic;
+ oss << " v=" << (int)_txn_hdr._rhdr._version;
+ oss << " rid=" << _txn_hdr._rhdr._rid;
+ oss << " xid=\"" << _xidp << "\"";
+ str.append(oss.str());
+ return str;
+}
+
+std::size_t
+txn_rec::xid_size() const
+{
+ return _txn_hdr._xidsize;
+}
+
+std::size_t
+txn_rec::rec_size() const
+{
+ return sizeof(txn_hdr_t) + _txn_hdr._xidsize + sizeof(rec_tail_t);
+}
+
+void
+txn_rec::check_rec_tail(const std::streampos rec_start) const {
+ Checksum checksum;
+ checksum.addData((const unsigned char*)&_txn_hdr, sizeof(::txn_hdr_t));
+ if (_txn_hdr._xidsize > 0) {
+ checksum.addData((const unsigned char*)_xid_buff, _txn_hdr._xidsize);
+ }
+ uint32_t cs = checksum.getChecksum();
+ uint16_t res = ::rec_tail_check(&_txn_tail, &_txn_hdr._rhdr, cs);
+ if (res != 0) {
+ std::stringstream oss;
+ oss << std::endl << " Record offset: 0x" << std::hex << rec_start;
+ if (res & ::REC_TAIL_MAGIC_ERR_MASK) {
+ oss << std::endl << " Magic: expected 0x" << ~_txn_hdr._rhdr._magic << "; found 0x" << _txn_tail._xmagic;
+ }
+ if (res & ::REC_TAIL_SERIAL_ERR_MASK) {
+ oss << std::endl << " Serial: expected 0x" << _txn_hdr._rhdr._serial << "; found 0x" << _txn_tail._serial;
+ }
+ if (res & ::REC_TAIL_RID_ERR_MASK) {
+ oss << std::endl << " Record Id: expected 0x" << _txn_hdr._rhdr._rid << "; found 0x" << _txn_tail._rid;
+ }
+ if (res & ::REC_TAIL_CHECKSUM_ERR_MASK) {
+ oss << std::endl << " Checksum: expected 0x" << cs << "; found 0x" << _txn_tail._checksum;
+ }
+ throw jexception(jerrno::JERR_JREC_BADRECTAIL, oss.str(), "txn_rec", "check_rec_tail");
+ }
+}
+
+void
+txn_rec::clean()
+{
+ if (_xid_buff) {
+ std::free(_xid_buff);
+ _xid_buff = 0;
+ }
+}
+
+}}}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/txn_rec.h b/qpid/cpp/src/qpid/linearstore/journal/txn_rec.h
new file mode 100644
index 0000000000..4552071595
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/txn_rec.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_TXN_REC_H
+#define QPID_LINEARSTORE_JOURNAL_TXN_REC_H
+
+#include "qpid/linearstore/journal/jrec.h"
+#include "qpid/linearstore/journal/utils/txn_hdr.h"
+#include "qpid/linearstore/journal/utils/rec_tail.h"
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+/**
+* \class txn_rec
+* \brief Class to handle a single journal commit or abort record.
+*/
+class txn_rec : public jrec
+{
+private:
+ ::txn_hdr_t _txn_hdr; ///< Local instance of transaction header struct
+ const void* _xidp; ///< xid pointer for encoding (writing to disk)
+ void* _xid_buff; ///< Pointer to buffer to receive xid read from disk
+ ::rec_tail_t _txn_tail; ///< Local instance of enqueue tail struct
+
+public:
+ txn_rec();
+ virtual ~txn_rec();
+
+ void reset(const bool commitFlag, const uint64_t serial, const uint64_t rid, const void* const xidp,
+ const std::size_t xidlen);
+ uint32_t encode(void* wptr, uint32_t rec_offs_dblks, uint32_t max_size_dblks, Checksum& checksum);
+ bool decode(::rec_hdr_t& h, std::ifstream* ifsp, std::size_t& rec_offs, const std::streampos rec_start);
+
+ std::size_t get_xid(void** const xidpp);
+ std::string& str(std::string& str) const;
+ inline std::size_t data_size() const { return 0; } // This record never carries data
+ std::size_t xid_size() const;
+ std::size_t rec_size() const;
+ inline uint64_t rid() const { return _txn_hdr._rhdr._rid; }
+ void check_rec_tail(const std::streampos rec_start) const;
+
+private:
+ virtual void clean();
+};
+
+}}}
+
+#endif // ifndef QPID_LINEARSTORE_JOURNAL_TXN_REC_H
diff --git a/qpid/cpp/src/qpid/linearstore/journal/utils/deq_hdr.c b/qpid/cpp/src/qpid/linearstore/journal/utils/deq_hdr.c
new file mode 100644
index 0000000000..b55c1c16c8
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/utils/deq_hdr.c
@@ -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.
+ *
+ */
+
+#include "deq_hdr.h"
+
+/*static const uint16_t DEQ_HDR_TXNCMPLCOMMIT_MASK = 0x10;*/
+
+void deq_hdr_init(deq_hdr_t* dest, const uint32_t magic, const uint16_t version, const uint16_t uflag,
+ const uint64_t serial, const uint64_t rid, const uint64_t deq_rid, const uint64_t xidsize) {
+ rec_hdr_init(&dest->_rhdr, magic, version, uflag, serial, rid);
+ dest->_deq_rid = deq_rid;
+ dest->_xidsize = xidsize;
+}
+
+void deq_hdr_copy(deq_hdr_t* dest, const deq_hdr_t* src) {
+ rec_hdr_copy(&dest->_rhdr, &src->_rhdr);
+ dest->_deq_rid = src->_deq_rid;
+ dest->_xidsize = src->_xidsize;
+}
+
+bool is_txn_coml_commit(const deq_hdr_t *dh) {
+ return dh->_rhdr._uflag & DEQ_HDR_TXNCMPLCOMMIT_MASK;
+}
+
+void set_txn_coml_commit(deq_hdr_t *dh, const bool commit) {
+ dh->_rhdr._uflag = commit ? dh->_rhdr._uflag | DEQ_HDR_TXNCMPLCOMMIT_MASK : // set flag bit
+ dh->_rhdr._uflag & (~DEQ_HDR_TXNCMPLCOMMIT_MASK); // unset flag bit
+}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/utils/deq_hdr.h b/qpid/cpp/src/qpid/linearstore/journal/utils/deq_hdr.h
new file mode 100644
index 0000000000..3392867153
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/utils/deq_hdr.h
@@ -0,0 +1,83 @@
+#ifndef QPID_LINEARSTORE_JOURNAL_UTILS_DEQ_HDR_H
+#define QPID_LINEARSTORE_JOURNAL_UTILS_DEQ_HDR_H
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <stdbool.h>
+#include "rec_hdr.h"
+
+#ifdef __cplusplus
+extern "C"{
+#endif
+
+#pragma pack(1)
+
+/**
+ * \brief Struct for dequeue record.
+ *
+ * Struct for dequeue record. If this record has a non-zero xidsize field (i.e., there is a
+ * valid XID), then this header is followed by the XID of xidsize bytes and a rec_tail. If,
+ * on the other hand, this record has a zero xidsize (i.e., there is no XID), then the rec_tail
+ * is absent.
+ *
+ * Note that this record had its own rid distinct from the rid of the record it is dequeueing.
+ * The rid field below is the rid of the dequeue record itself; the deq-rid field is the rid of a
+ * previous enqueue record being dequeued by this record.
+ *
+ * Record header info in binary format (40 bytes):
+ * <pre>
+ * 0 7
+ * +---+---+---+---+---+---+---+---+ -+
+ * | magic | ver | flags | |
+ * +---+---+---+---+---+---+---+---+ |
+ * | serial | | struct rec_hdr_t
+ * +---+---+---+---+---+---+---+---+ |
+ * | rid | |
+ * +---+---+---+---+---+---+---+---+ -+
+ * | deq-rid |
+ * +---+---+---+---+---+---+---+---+
+ * | xidsize |
+ * +---+---+---+---+---+---+---+---+
+ *
+ * deq-rid = dequeue record ID
+ * </pre>
+ */
+typedef struct deq_hdr_t {
+ rec_hdr_t _rhdr; /**< Common record header struct */
+ uint64_t _deq_rid; /**< Record ID of record being dequeued */
+ uint64_t _xidsize; /**< XID size */
+} deq_hdr_t;
+
+static const uint16_t DEQ_HDR_TXNCMPLCOMMIT_MASK = 0x10;
+
+void deq_hdr_init(deq_hdr_t* dest, const uint32_t magic, const uint16_t version, const uint16_t uflag,
+ const uint64_t serial, const uint64_t rid, const uint64_t deq_rid, const uint64_t xidsize);
+void deq_hdr_copy(deq_hdr_t* dest, const deq_hdr_t* src);
+bool is_txn_coml_commit(const deq_hdr_t *dh);
+void set_txn_coml_commit(deq_hdr_t *dh, const bool commit);
+
+#pragma pack()
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ifndef QPID_LINEARSTORE_JOURNAL_UTILS_DEQ_HDR_H */
diff --git a/qpid/cpp/src/qpid/linearstore/journal/utils/enq_hdr.c b/qpid/cpp/src/qpid/linearstore/journal/utils/enq_hdr.c
new file mode 100644
index 0000000000..b4e8b62ff1
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/utils/enq_hdr.c
@@ -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.
+ *
+ */
+
+#include "enq_hdr.h"
+
+//static const uint16_t ENQ_HDR_TRANSIENT_MASK = 0x10;
+//static const uint16_t ENQ_HDR_EXTERNAL_MASK = 0x20;
+
+void enq_hdr_init(enq_hdr_t* dest, const uint32_t magic, const uint16_t version, const uint16_t uflag,
+ const uint64_t serial, const uint64_t rid, const uint64_t xidsize, const uint64_t dsize) {
+ rec_hdr_init(&dest->_rhdr, magic, version, uflag, serial, rid);
+ dest->_xidsize = xidsize;
+ dest->_dsize = dsize;
+}
+
+void enq_hdr_copy(enq_hdr_t* dest, const enq_hdr_t* src) {
+ rec_hdr_copy(&dest->_rhdr, &src->_rhdr);
+ dest->_xidsize = src->_xidsize;
+ dest->_dsize = src->_dsize;
+}
+
+bool is_enq_transient(const enq_hdr_t *eh) {
+ return eh->_rhdr._uflag & ENQ_HDR_TRANSIENT_MASK;
+}
+
+void set_enq_transient(enq_hdr_t *eh, const bool transient) {
+ eh->_rhdr._uflag = transient ? eh->_rhdr._uflag | ENQ_HDR_TRANSIENT_MASK :
+ eh->_rhdr._uflag & (~ENQ_HDR_TRANSIENT_MASK);
+}
+
+bool is_enq_external(const enq_hdr_t *eh) {
+ return eh->_rhdr._uflag & ENQ_HDR_EXTERNAL_MASK;
+}
+
+void set_enq_external(enq_hdr_t *eh, const bool external) {
+ eh->_rhdr._uflag = external ? eh->_rhdr._uflag | ENQ_HDR_EXTERNAL_MASK :
+ eh->_rhdr._uflag & (~ENQ_HDR_EXTERNAL_MASK);
+}
+
+bool validate_enq_hdr(enq_hdr_t *eh, const uint32_t magic, const uint16_t version, const uint64_t rid) {
+ return eh->_rhdr._magic == magic &&
+ eh->_rhdr._version == version &&
+ rid > 0 ? eh->_rhdr._rid == rid /* If rid == 0, don't compare rids */
+ : true;
+}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/utils/enq_hdr.h b/qpid/cpp/src/qpid/linearstore/journal/utils/enq_hdr.h
new file mode 100644
index 0000000000..00108792bc
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/utils/enq_hdr.h
@@ -0,0 +1,83 @@
+#ifndef QPID_LINEARSTORE_JOURNAL_UTILS_ENQ_HDR_H
+#define QPID_LINEARSTORE_JOURNAL_UTILS_ENQ_HDR_H
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <stdbool.h>
+#include "rec_hdr.h"
+
+#ifdef __cplusplus
+extern "C"{
+#endif
+
+#pragma pack(1)
+
+/**
+ * \brief Struct for enqueue record.
+ *
+ * Struct for enqueue record. In addition to the common data, this header includes both the
+ * xid and data blob sizes.
+ *
+ * This header precedes all enqueue data in journal files.
+ *
+ * Record header info in binary format (40 bytes):
+ * <pre>
+ * 0 7
+ * +---+---+---+---+---+---+---+---+ -+
+ * | magic | ver | flags | |
+ * +---+---+---+---+---+---+---+---+ |
+ * | serial | | struct rec_hdr_t
+ * +---+---+---+---+---+---+---+---+ |
+ * | rid | |
+ * +---+---+---+---+---+---+---+---+ -+
+ * | xidsize |
+ * +---+---+---+---+---+---+---+---+
+ * | dsize |
+ * +---+---+---+---+---+---+---+---+
+ * v = file version (If the format or encoding of this file changes, then this
+ * number should be incremented)
+ * </pre>
+ */
+typedef struct enq_hdr_t {
+ rec_hdr_t _rhdr; /**< Common record header struct */
+ uint64_t _xidsize; /**< XID size in octets */
+ uint64_t _dsize; /**< Record data size in octets */
+} enq_hdr_t;
+
+static const uint16_t ENQ_HDR_TRANSIENT_MASK = 0x10;
+static const uint16_t ENQ_HDR_EXTERNAL_MASK = 0x20;
+
+void enq_hdr_init(enq_hdr_t* dest, const uint32_t magic, const uint16_t version, const uint16_t uflag,
+ const uint64_t serial, const uint64_t rid, const uint64_t xidsize, const uint64_t dsize);
+void enq_hdr_copy(enq_hdr_t* dest, const enq_hdr_t* src);
+bool is_enq_transient(const enq_hdr_t *eh);
+void set_enq_transient(enq_hdr_t *eh, const bool transient);
+bool is_enq_external(const enq_hdr_t *eh);
+void set_enq_external(enq_hdr_t *eh, const bool external);
+bool validate_enq_hdr(enq_hdr_t *eh, const uint32_t magic, const uint16_t version, const uint64_t rid);
+
+#pragma pack()
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ifndef QPID_LINEARSTORE_JOURNAL_UTILS_ENQ_HDR_H */
diff --git a/qpid/cpp/src/qpid/linearstore/journal/utils/file_hdr.c b/qpid/cpp/src/qpid/linearstore/journal/utils/file_hdr.c
new file mode 100644
index 0000000000..4e6cf1b8fa
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/utils/file_hdr.c
@@ -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.
+ *
+ */
+
+#include "file_hdr.h"
+#include <string.h>
+
+void file_hdr_create(file_hdr_t* dest, const uint32_t magic, const uint16_t version, const uint16_t fhdr_size_sblks,
+ const uint16_t efp_partition, const uint64_t file_size) {
+ rec_hdr_init(&dest->_rhdr, magic, version, 0, 0, 0);
+ dest->_fhdr_size_sblks = fhdr_size_sblks;
+ dest->_efp_partition = efp_partition;
+ dest->_reserved = 0;
+ dest->_data_size_kib = file_size;
+ dest->_fro = 0;
+ dest->_ts_nsec = 0;
+ dest->_ts_sec = 0;
+ dest->_file_number = 0;
+ dest->_queue_name_len = 0;
+}
+
+int file_hdr_init(void* dest, const uint64_t dest_len, const uint16_t uflag, const uint64_t serial, const uint64_t rid,
+ const uint64_t fro, const uint64_t file_number, const uint16_t queue_name_len, const char* queue_name) {
+ file_hdr_t* fhp = (file_hdr_t*)dest;
+ fhp->_rhdr._uflag = uflag;
+ fhp->_rhdr._serial = serial;
+ fhp->_rhdr._rid = rid;
+ fhp->_fro = fro;
+ fhp->_file_number = file_number;
+ if (sizeof(file_hdr_t) + queue_name_len < MAX_FILE_HDR_LEN) {
+ fhp->_queue_name_len = queue_name_len;
+ } else {
+ fhp->_queue_name_len = MAX_FILE_HDR_LEN - sizeof(file_hdr_t);
+ }
+ fhp->_queue_name_len = queue_name_len;
+ memcpy((char*)dest + sizeof(file_hdr_t), queue_name, queue_name_len);
+ memset((char*)dest + sizeof(file_hdr_t) + queue_name_len, 0, dest_len - sizeof(file_hdr_t) - queue_name_len);
+ return set_time_now(dest);
+}
+
+int file_hdr_check(file_hdr_t* hdr, const uint32_t magic, const uint16_t version, const uint64_t data_size_kib, const uint16_t max_queue_name_len) {
+ int err = rec_hdr_check_base(&hdr->_rhdr, magic, version);
+ if (data_size_kib && hdr->_data_size_kib != data_size_kib) err |= 0x1000;
+ if (hdr->_queue_name_len > max_queue_name_len) err |= 0x10000;
+ return err;
+}
+
+void file_hdr_copy(file_hdr_t* dest, const file_hdr_t* src) {
+ rec_hdr_copy(&dest->_rhdr, &src->_rhdr);
+ dest->_fhdr_size_sblks = src->_fhdr_size_sblks; // Should this be copied?
+ dest->_efp_partition = src->_efp_partition; // Should this be copied?
+ dest->_data_size_kib = src->_data_size_kib;
+ dest->_fro = src->_fro;
+ dest->_ts_sec = src->_ts_sec;
+ dest->_ts_nsec = src->_ts_nsec;
+ dest->_file_number = src->_file_number;
+}
+
+void file_hdr_reset(file_hdr_t* target) {
+ target->_rhdr._uflag = 0;
+ target->_rhdr._serial = 0;
+ target->_rhdr._rid = 0;
+ target->_fro = 0;
+ target->_ts_sec = 0;
+ target->_ts_nsec = 0;
+ target->_file_number = 0;
+ target->_queue_name_len = 0;
+}
+
+int is_file_hdr_reset(file_hdr_t* target) {
+ return target->_rhdr._uflag == 0 &&
+ target->_rhdr._serial == 0 &&
+ target->_rhdr._rid == 0 &&
+ target->_ts_sec == 0 &&
+ target->_ts_nsec == 0 &&
+ target->_file_number == 0 &&
+ target->_queue_name_len == 0;
+}
+
+int set_time_now(file_hdr_t *fh)
+{
+ struct timespec ts;
+ int err = clock_gettime(CLOCK_REALTIME, &ts);
+ if (err)
+ return err;
+ fh->_ts_sec = ts.tv_sec;
+ fh->_ts_nsec = ts.tv_nsec;
+ return 0;
+}
+
+
+void set_time(file_hdr_t *fh, struct timespec *ts)
+{
+ fh->_ts_sec = ts->tv_sec;
+ fh->_ts_nsec = ts->tv_nsec;
+}
+
+
diff --git a/qpid/cpp/src/qpid/linearstore/journal/utils/file_hdr.h b/qpid/cpp/src/qpid/linearstore/journal/utils/file_hdr.h
new file mode 100644
index 0000000000..5987e1871e
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/utils/file_hdr.h
@@ -0,0 +1,111 @@
+#ifndef QPID_LINEARSTORE_JOURNAL_UTILS_FILE_HDR_H
+#define QPID_LINEARSTORE_JOURNAL_UTILS_FILE_HDR_H
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <time.h>
+#include "rec_hdr.h"
+
+#ifdef __cplusplus
+extern "C"{
+#endif
+
+#define MAX_FILE_HDR_LEN 4096 // Set to 1 sblk
+
+#pragma pack(1)
+
+/**
+ * \brief Struct for data common to the head of all journal files. In addition to
+ * the common data, this includes the record ID and offset of the first record in
+ * the file.
+ *
+ * This header precedes all data in journal files and occupies the first complete
+ * block in the file. The record ID and offset are updated on each overwrite of the
+ * file.
+ *
+ * File header info in binary format (74 bytes + size of file name in octets):
+ * <pre>
+ * 0 7
+ * +---+---+---+---+---+---+---+---+ -+
+ * | magic | ver | flags | |
+ * +---+---+---+---+---+---+---+---+ |
+ * | serial | | struct rec_hdr_t
+ * +---+---+---+---+---+---+---+---+ |
+ * | rid | |
+ * +---+---+---+---+---+---+---+---+ -+
+ * | fhs | partn | reserved |
+ * +---+---+---+---+---+---+---+---+
+ * | data-size |
+ * +---+---+---+---+---+---+---+---+
+ * | fro |
+ * +---+---+---+---+---+---+---+---+
+ * | timestamp (sec) |
+ * +---+---+---+---+---+---+---+---+
+ * | timestamp (ns) |
+ * +---+---+---+---+---+---+---+---+
+ * | file-number |
+ * +---+---+---+---+---+---+---+---+
+ * | qnl | Queue Name... |
+ * +-------+ |
+ * | |
+ * +---+---+---+---+---+---+---+---+
+ *
+ * ver = Journal version
+ * rid = Record ID
+ * fhs = File header size in sblks (defined by JRNL_SBLK_SIZE)
+ * partn = EFP partition from which this file came
+ * fro = First Record Offset
+ * qnl = Length of the queue name in octets.
+ * </pre>
+ */
+typedef struct file_hdr_t {
+ rec_hdr_t _rhdr; /**< Common record header struct, but rid field is used for rid of first compete record in file */
+ uint16_t _fhdr_size_sblks; /**< File header size in sblks (defined by JRNL_SBLK_SIZE) */
+ uint16_t _efp_partition; /**< EFP Partition number from which this file was obtained */
+ uint32_t _reserved;
+ uint64_t _data_size_kib; /**< Size of the data part of this file in KiB. (ie file size excluding file header sblk) */
+ uint64_t _fro; /**< First Record Offset (FRO) */
+ uint64_t _ts_sec; /**< Time stamp (seconds part) */
+ uint64_t _ts_nsec; /**< Time stamp (nanoseconds part) */
+ uint64_t _file_number; /**< The logical number of this file in a monotonically increasing sequence */
+ uint16_t _queue_name_len; /**< Length of the queue name in octets, which follows this struct in the header */
+} file_hdr_t;
+
+void file_hdr_create(file_hdr_t* dest, const uint32_t magic, const uint16_t version,
+ const uint16_t fhdr_size_sblks, const uint16_t efp_partition, const uint64_t file_size);
+int file_hdr_init(void* dest, const uint64_t dest_len, const uint16_t uflag, const uint64_t serial, const uint64_t rid,
+ const uint64_t fro, const uint64_t file_number, const uint16_t queue_name_len,
+ const char* queue_name);
+int file_hdr_check(file_hdr_t* hdr, const uint32_t magic, const uint16_t version, const uint64_t data_size_kib,
+ const uint16_t max_queue_name_len);
+void file_hdr_reset(file_hdr_t* target);
+int is_file_hdr_reset(file_hdr_t* target);
+void file_hdr_copy(file_hdr_t* dest, const file_hdr_t* src);
+int set_time_now(file_hdr_t *fh);
+void set_time(file_hdr_t *fh, struct timespec *ts);
+
+#pragma pack()
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ifndef QPID_LINEARSTORE_JOURNAL_UTILS_FILE_HDR_H */
diff --git a/qpid/cpp/src/qpid/linearstore/journal/utils/rec_hdr.c b/qpid/cpp/src/qpid/linearstore/journal/utils/rec_hdr.c
new file mode 100644
index 0000000000..32eda8de5a
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/utils/rec_hdr.c
@@ -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.
+ *
+ */
+
+#include "rec_hdr.h"
+
+void rec_hdr_init(rec_hdr_t* dest, const uint32_t magic, const uint16_t version, const uint16_t uflag, const uint64_t serial, const uint64_t rid) {
+ dest->_magic = magic;
+ dest->_version = version;
+ dest->_uflag = uflag;
+ dest->_serial = serial;
+ dest->_rid = rid;
+}
+
+void rec_hdr_copy(rec_hdr_t* dest, const rec_hdr_t* src) {
+ dest->_magic = src->_magic;
+ dest->_version = src->_version;
+ dest->_uflag = src->_uflag;
+ dest->_serial = src->_serial;
+ dest->_rid = src->_rid;
+}
+
+int rec_hdr_check_base(rec_hdr_t* header, const uint32_t magic, const uint16_t version) {
+ int err = 0;
+ if (header->_magic != magic) err |= 0x1;
+ if (header->_version != version) err |= 0x10;
+ return err;
+}
+
+int rec_hdr_check(rec_hdr_t* header, const uint32_t magic, const uint16_t version, const uint64_t serial) {
+ int err = rec_hdr_check_base(header, magic, version);
+ if (header->_serial != serial) err |= 0x100;
+ return err;
+}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/utils/rec_hdr.h b/qpid/cpp/src/qpid/linearstore/journal/utils/rec_hdr.h
new file mode 100644
index 0000000000..64349b5ab8
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/utils/rec_hdr.h
@@ -0,0 +1,72 @@
+#ifndef QPID_LINEARSTORE_JOURNAL_UTILS_REC_HDR_H
+#define QPID_LINEARSTORE_JOURNAL_UTILS_REC_HDR_H
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C"{
+#endif
+
+#pragma pack(1)
+
+/**
+ * \brief Struct for data common to the head of all journal files and records.
+ * This includes identification for the file type, the encoding version, endian
+ * indicator and a record ID.
+ *
+ * File header info in binary format (24 bytes):
+ * <pre>
+ * 0 7
+ * +---+---+---+---+---+---+---+---+
+ * | magic | ver | uflag |
+ * +---+---+---+---+---+---+---+---+
+ * | serial |
+ * +---+---+---+---+---+---+---+---+
+ * | rid |
+ * +---+---+---+---+---+---+---+---+
+ *
+ * ver = file version (If the format or encoding of this file changes, then this
+ * number should be incremented)
+ * rid = Record ID
+ * </pre>
+ */
+typedef struct rec_hdr_t {
+ uint32_t _magic; /**< File type identifier (magic number) */
+ uint16_t _version; /**< File encoding version */
+ uint16_t _uflag; /**< User-defined flags */
+ uint64_t _serial; /**< Serial number for this journal file */
+ uint64_t _rid; /**< Record ID (rotating 64-bit counter) */
+} rec_hdr_t;
+
+void rec_hdr_init(rec_hdr_t* dest, const uint32_t magic, const uint16_t version, const uint16_t uflag, const uint64_t serial, const uint64_t rid);
+void rec_hdr_copy(rec_hdr_t* dest, const rec_hdr_t* src);
+int rec_hdr_check_base(rec_hdr_t* header, const uint32_t magic, const uint16_t version);
+int rec_hdr_check(rec_hdr_t* header, const uint32_t magic, const uint16_t version, const uint64_t serial);
+
+#pragma pack()
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ifndef QPID_LINEARSTORE_JOURNAL_UTILS_REC_HDR_H */
diff --git a/qpid/cpp/src/qpid/linearstore/journal/utils/rec_tail.c b/qpid/cpp/src/qpid/linearstore/journal/utils/rec_tail.c
new file mode 100644
index 0000000000..7128c96f32
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/utils/rec_tail.c
@@ -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.
+ *
+ */
+
+#include "rec_tail.h"
+
+void rec_tail_init(rec_tail_t* dest, const uint32_t xmagic, const uint32_t checksum, const uint64_t serial,
+ const uint64_t rid) {
+ dest->_xmagic = xmagic;
+ dest->_checksum = checksum;
+ dest->_serial = serial;
+ dest->_rid = rid;
+}
+
+void rec_tail_copy(rec_tail_t* dest, const rec_hdr_t* src, const uint32_t checksum) {
+ dest->_xmagic = ~(src->_magic);
+ dest->_checksum = checksum;
+ dest->_serial = src->_serial;
+ dest->_rid = src->_rid;
+}
+
+uint16_t rec_tail_check(const rec_tail_t* tail, const rec_hdr_t* header, const uint32_t checksum) {
+ uint16_t err = 0;
+ if (tail->_xmagic != ~header->_magic) err |= REC_TAIL_MAGIC_ERR_MASK;
+ if (tail->_serial != header->_serial) err |= REC_TAIL_SERIAL_ERR_MASK;
+ if (tail->_rid != header->_rid) err |= REC_TAIL_RID_ERR_MASK;
+ if (tail->_checksum != checksum) err |= REC_TAIL_CHECKSUM_ERR_MASK;
+ return err;
+}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/utils/rec_tail.h b/qpid/cpp/src/qpid/linearstore/journal/utils/rec_tail.h
new file mode 100644
index 0000000000..afc71c104a
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/utils/rec_tail.h
@@ -0,0 +1,82 @@
+#ifndef QPID_LINEARSTORE_JOURNAL_UTILS_REC_TAIL_H
+#define QPID_LINEARSTORE_JOURNAL_UTILS_REC_TAIL_H
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <stdint.h>
+#include "rec_hdr.h"
+
+#ifdef __cplusplus
+extern "C"{
+#endif
+
+#pragma pack(1)
+
+/**
+ * \brief Struct for data common to the tail of all records. The magic number
+ * used here is the binary inverse (1's complement) of the magic used in the
+ * record header; this minimizes possible confusion with other headers that may
+ * be present during recovery. The tail is used with all records that have either
+ * XIDs or data - ie any size-variable content. Currently the only records that
+ * do NOT use the tail are non-transactional dequeues and filler records.
+ *
+ * The checksum is used to verify the xid and/or data portion of the record
+ * on recovery, and excludes the header and tail.
+ *
+ * Record header info in binary format (24 bytes):
+ * <pre>
+ * 0 7
+ * +---+---+---+---+---+---+---+---+
+ * | ~(magic) | checksum |
+ * +---+---+---+---+---+---+---+---+
+ * | serial |
+ * +---+---+---+---+---+---+---+---+
+ * | rid |
+ * +---+---+---+---+---+---+---+---+
+ *
+ * ~(magic) = 1's compliment of magic of matching record header
+ * rid = Record ID of matching record header
+ * </pre>
+ */
+typedef struct rec_tail_t {
+ uint32_t _xmagic; /**< Binary inverse (1's complement) of hdr magic number */
+ uint32_t _checksum; /**< Checksum of xid and data (excluding header itself) */
+ uint64_t _serial; /**< Serial number for this journal file */
+ uint64_t _rid; /**< Record ID (rotating 64-bit counter) */
+} rec_tail_t;
+
+static const uint16_t REC_TAIL_MAGIC_ERR_MASK = 0x01;
+static const uint16_t REC_TAIL_SERIAL_ERR_MASK = 0x02;
+static const uint16_t REC_TAIL_RID_ERR_MASK = 0x04;
+static const uint16_t REC_TAIL_CHECKSUM_ERR_MASK = 0x08;
+
+void rec_tail_init(rec_tail_t* dest, const uint32_t xmagic, const uint32_t checksum, const uint64_t serial,
+ const uint64_t rid);
+void rec_tail_copy(rec_tail_t* dest, const rec_hdr_t* src, const uint32_t checksum);
+uint16_t rec_tail_check(const rec_tail_t* tail, const rec_hdr_t* header, const uint32_t checksum);
+
+#pragma pack()
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ifnedf QPID_LINEARSTORE_JOURNAL_UTILS_REC_TAIL_H */
diff --git a/qpid/cpp/src/qpid/linearstore/journal/utils/txn_hdr.c b/qpid/cpp/src/qpid/linearstore/journal/utils/txn_hdr.c
new file mode 100644
index 0000000000..58d4cdebe4
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/utils/txn_hdr.c
@@ -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.
+ *
+ */
+
+#include "txn_hdr.h"
+
+void txn_hdr_init(txn_hdr_t* dest, const uint32_t magic, const uint16_t version, const uint16_t uflag,
+ const uint64_t serial, const uint64_t rid, const uint64_t xidsize) {
+ rec_hdr_init(&dest->_rhdr, magic, version, uflag, serial, rid);
+ dest->_xidsize = xidsize;
+}
+
+void txn_hdr_copy(txn_hdr_t* dest, const txn_hdr_t* src) {
+ rec_hdr_copy(&dest->_rhdr, &src->_rhdr);
+ dest->_xidsize = src->_xidsize;
+}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/utils/txn_hdr.h b/qpid/cpp/src/qpid/linearstore/journal/utils/txn_hdr.h
new file mode 100644
index 0000000000..442a1d373d
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/utils/txn_hdr.h
@@ -0,0 +1,72 @@
+#ifndef QPID_LINEARSTORE_JOURNAL_UTILS_TXN_HDR_H
+#define QPID_LINEARSTORE_JOURNAL_UTILS_TXN_HDR_H
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "rec_hdr.h"
+
+#ifdef __cplusplus
+extern "C"{
+#endif
+
+#pragma pack(1)
+
+/**
+ * \brief Struct for transaction commit and abort records.
+ *
+ * Struct for local and DTX commit and abort records. Only the magic distinguishes between them.
+ * Since this record must be used in the context of a valid XID, the xidsize field must not be
+ * zero. Immediately following this record is the XID itself which is xidsize bytes long,
+ * followed by a rec_tail.
+ *
+ * Note that this record had its own rid distinct from the rids of the record(s) making up the
+ * transaction it is committing or aborting.
+ *
+ * Record header info in binary format (32 bytes):
+ * <pre>
+ * 0 7
+ * +---+---+---+---+---+---+---+---+ -+
+ * | magic | ver | flags | |
+ * +---+---+---+---+---+---+---+---+ |
+ * | serial | | struct rec_hdr_t
+ * +---+---+---+---+---+---+---+---+ |
+ * | rid | |
+ * +---+---+---+---+---+---+---+---+ -+
+ * | xidsize |
+ * +---+---+---+---+---+---+---+---+
+ * </pre>
+ */
+typedef struct txn_hdr_t {
+ rec_hdr_t _rhdr; /**< Common record header struct */
+ uint64_t _xidsize; /**< XID size */
+} txn_hdr_t;
+
+void txn_hdr_init(txn_hdr_t* dest, const uint32_t magic, const uint16_t version, const uint16_t uflag,
+ const uint64_t serial, const uint64_t rid, const uint64_t xidsize);
+void txn_hdr_copy(txn_hdr_t* dest, const txn_hdr_t* src);
+
+#pragma pack()
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* ifndef QPID_LINEARSTORE_JOURNAL_UTILS_TXN_HDR_H */
diff --git a/qpid/cpp/src/qpid/linearstore/journal/wmgr.cpp b/qpid/cpp/src/qpid/linearstore/journal/wmgr.cpp
new file mode 100644
index 0000000000..1ff18da663
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/wmgr.cpp
@@ -0,0 +1,1086 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/linearstore/journal/wmgr.h"
+
+#include <cassert>
+#include "qpid/linearstore/journal/aio_callback.h"
+#include "qpid/linearstore/journal/Checksum.h"
+#include "qpid/linearstore/journal/data_tok.h"
+#include "qpid/linearstore/journal/jcntl.h"
+#include "qpid/linearstore/journal/JournalFile.h"
+#include "qpid/linearstore/journal/LinearFileController.h"
+#include "qpid/linearstore/journal/utils/file_hdr.h"
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+wmgr::wmgr(jcntl* jc,
+ enq_map& emap,
+ txn_map& tmap,
+ LinearFileController& lfc):
+ pmgr(jc, emap, tmap),
+ _lfc(lfc),
+ _max_dtokpp(0),
+ _max_io_wait_us(0),
+ _cached_offset_dblks(0),
+ _enq_busy(false),
+ _deq_busy(false),
+ _abort_busy(false),
+ _commit_busy(false),
+ _txn_pending_map()
+{}
+
+wmgr::wmgr(jcntl* jc,
+ enq_map& emap,
+ txn_map& tmap,
+ LinearFileController& lfc,
+ const uint32_t max_dtokpp,
+ const uint32_t max_iowait_us):
+ pmgr(jc, emap, tmap),
+ _lfc(lfc),
+ _max_dtokpp(max_dtokpp),
+ _max_io_wait_us(max_iowait_us),
+ _cached_offset_dblks(0),
+ _enq_busy(false),
+ _deq_busy(false),
+ _abort_busy(false),
+ _commit_busy(false),
+ _txn_pending_map()
+{}
+
+wmgr::~wmgr()
+{
+ wmgr::clean();
+}
+
+void
+wmgr::initialize(aio_callback* const cbp,
+ const uint32_t wcache_pgsize_sblks,
+ const uint16_t wcache_num_pages,
+ const uint32_t max_dtokpp,
+ const uint32_t max_iowait_us,
+ std::size_t end_offset)
+{
+ _enq_busy = false;
+ _deq_busy = false;
+ _abort_busy = false;
+ _commit_busy = false;
+ _max_dtokpp = max_dtokpp;
+ _max_io_wait_us = max_iowait_us;
+
+ initialize(cbp, wcache_pgsize_sblks, wcache_num_pages);
+
+ if (end_offset)
+ {
+ if(!aio::is_aligned((const void*)end_offset, QLS_AIO_ALIGN_BOUNDARY_BYTES)) {
+ std::ostringstream oss;
+ oss << "Recovery using misaligned end_offset (0x" << std::hex << end_offset << std::dec << ")" << std::endl;
+ throw jexception(jerrno::JERR_WMGR_NOTSBLKALIGNED, oss.str(), "wmgr", "initialize");
+ }
+ const uint32_t wr_pg_size_dblks = _cache_pgsize_sblks * QLS_SBLK_SIZE_DBLKS;
+ uint32_t data_dblks = (end_offset / QLS_DBLK_SIZE_BYTES) - (QLS_JRNL_FHDR_RES_SIZE_SBLKS * QLS_SBLK_SIZE_DBLKS); // exclude file header
+ _pg_cntr = data_dblks / wr_pg_size_dblks; // Must be set to get file rotation synchronized (this is determined by value of _pg_cntr)
+ _pg_offset_dblks = data_dblks - (_pg_cntr * wr_pg_size_dblks);
+ }
+}
+
+iores
+wmgr::enqueue(const void* const data_buff,
+ const std::size_t tot_data_len,
+ const std::size_t this_data_len,
+ data_tok* dtokp,
+ const void* const xid_ptr,
+ const std::size_t xid_len,
+ const bool tpc_flag,
+ const bool transient,
+ const bool external)
+{
+//std::cout << _lfc.status(10) << std::endl;
+ if (xid_len)
+ assert(xid_ptr != 0);
+
+ if (_deq_busy || _abort_busy || _commit_busy) {
+ std::ostringstream oss;
+ oss << "RHM_IORES_BUSY: enqueue while part way through another op:";
+ oss << " _deq_busy=" << (_deq_busy?"T":"F");
+ oss << " _abort_busy=" << (_abort_busy?"T":"F");
+ oss << " _commit_busy=" << (_commit_busy?"T":"F");
+ throw jexception(oss.str()); // TODO: complete exception
+ }
+
+ if (this_data_len != tot_data_len && !external) {
+ throw jexception("RHM_IORES_NOTIMPL: partial enqueues not implemented"); // TODO: complete exception;
+ }
+
+ iores res = pre_write_check(WMGR_ENQUEUE, dtokp, xid_len, tot_data_len, external);
+ if (res != RHM_IORES_SUCCESS)
+ return res;
+
+ bool cont = false;
+ if (_enq_busy) // If enqueue() exited last time with RHM_IORES_FULL or RHM_IORES_PAGE_AIOWAIT
+ {
+ if (dtokp->wstate() == data_tok::ENQ_PART)
+ cont = true;
+ else
+ {
+ std::ostringstream oss;
+ oss << "This data_tok: id=" << dtokp->id() << " state=" << dtokp->wstate_str();
+ throw jexception(jerrno::JERR_WMGR_ENQDISCONT, oss.str(), "wmgr", "enqueue");
+ }
+ }
+
+ uint64_t rid = (dtokp->external_rid() | cont) ? dtokp->rid() : _lfc.getNextRecordId();
+ _enq_rec.reset(_lfc.getCurrentSerial(), rid, data_buff, tot_data_len, xid_ptr, xid_len, transient, external);
+ if (!cont)
+ {
+ dtokp->set_rid(rid);
+ dtokp->set_dequeue_rid(0);
+ if (xid_len)
+ dtokp->set_xid(xid_ptr, xid_len);
+ else
+ dtokp->clear_xid();
+ _enq_busy = true;
+ }
+//std::cout << "---+++ wmgr::enqueue() ENQ rid=0x" << std::hex << rid << " po=0x" << _pg_offset_dblks << " cs=0x" << (_cache_pgsize_sblks * QLS_SBLK_SIZE_DBLKS) << " " << std::dec << std::flush; // DEBUG
+ bool done = false;
+ Checksum checksum;
+ while (!done)
+ {
+//std::cout << "*" << std::flush; // DEBUG
+ assert(_pg_offset_dblks < _cache_pgsize_sblks * QLS_SBLK_SIZE_DBLKS);
+ void* wptr = (void*)((char*)_page_ptr_arr[_pg_index] + _pg_offset_dblks * QLS_DBLK_SIZE_BYTES);
+ uint32_t data_offs_dblks = dtokp->dblocks_written();
+ uint32_t ret = _enq_rec.encode(wptr, data_offs_dblks,
+ (_cache_pgsize_sblks * QLS_SBLK_SIZE_DBLKS) - _pg_offset_dblks, checksum);
+
+ // Remember fid which contains the record header in case record is split over several files
+ if (data_offs_dblks == 0) {
+ dtokp->set_fid(_lfc.getCurrentFileSeqNum());
+ }
+ _pg_offset_dblks += ret;
+ _cached_offset_dblks += ret;
+ dtokp->incr_dblocks_written(ret);
+ dtokp->incr_pg_cnt();
+ _page_cb_arr[_pg_index]._pdtokl->push_back(dtokp);
+
+ // Is the encoding of this record complete?
+ if (dtokp->dblocks_written() >= _enq_rec.rec_size_dblks())
+ {
+//std::cout << "!" << std::flush; // DEBUG
+ // TODO: Incorrect - must set state to ENQ_CACHED; ENQ_SUBM is set when AIO returns.
+ dtokp->set_wstate(data_tok::ENQ_SUBM);
+ dtokp->set_dsize(tot_data_len);
+ // Only add this data token to page token list when submit is complete, this way
+ // long multi-page messages have their token on the page containing the END of the
+ // message. AIO callbacks will then only process this token when entire message is
+ // enqueued.
+ _lfc.incrEnqueuedRecordCount(dtokp->fid());
+//std::cout << "[0x" << std::hex << _lfc.getEnqueuedRecordCount(dtokp->fid()) << std::dec << std::flush; // DEBUG
+
+ if (xid_len) // If part of transaction, add to transaction map
+ {
+ std::string xid((const char*)xid_ptr, xid_len);
+ _tmap.insert_txn_data(xid, txn_data_t(rid, 0, dtokp->fid(), 0, true, tpc_flag, false));
+ }
+ else
+ {
+ if (_emap.insert_pfid(rid, dtokp->fid(), 0) < enq_map::EMAP_OK) // fail
+ {
+ // The only error code emap::insert_pfid() returns is enq_map::EMAP_DUP_RID.
+ std::ostringstream oss;
+ oss << std::hex << "rid=0x" << rid << " _pfid=0x" << dtokp->fid();
+ throw jexception(jerrno::JERR_MAP_DUPLICATE, oss.str(), "wmgr", "enqueue");
+ }
+ }
+
+ done = true;
+ } else {
+//std::cout << "$" << std::flush; // DEBUG
+ dtokp->set_wstate(data_tok::ENQ_PART);
+ }
+
+ file_header_check(rid, cont, _enq_rec.rec_size_dblks() - data_offs_dblks);
+ flush_check(res, cont, done, rid);
+ }
+ if (dtokp->wstate() >= data_tok::ENQ_SUBM)
+ _enq_busy = false;
+//std::cout << " res=" << iores_str(res) << " _enq_busy=" << (_enq_busy?"T":"F") << std::endl << std::flush; // DEBUG
+ return res;
+}
+
+iores
+wmgr::dequeue(data_tok* dtokp,
+ const void* const xid_ptr,
+ const std::size_t xid_len,
+ const bool tpc_flag,
+ const bool txn_coml_commit)
+{
+ if (xid_len)
+ assert(xid_ptr != 0);
+
+ if (_enq_busy || _abort_busy || _commit_busy) {
+ std::ostringstream oss;
+ oss << "RHM_IORES_BUSY: dequeue while part way through another op:";
+ oss << " _enq_busy=" << (_enq_busy?"T":"F");
+ oss << " _abort_busy=" << (_abort_busy?"T":"F");
+ oss << " _commit_busy=" << (_commit_busy?"T":"F");
+ throw jexception(oss.str()); // TODO: complete exception
+ }
+
+ iores res = pre_write_check(WMGR_DEQUEUE, dtokp);
+ if (res != RHM_IORES_SUCCESS)
+ return res;
+
+ bool cont = false;
+ if (_deq_busy) // If dequeue() exited last time with RHM_IORES_FULL or RHM_IORES_PAGE_AIOWAIT
+ {
+ if (dtokp->wstate() == data_tok::DEQ_PART)
+ cont = true;
+ else
+ {
+ std::ostringstream oss;
+ oss << "This data_tok: id=" << dtokp->id() << " state=" << dtokp->wstate_str();
+ throw jexception(jerrno::JERR_WMGR_DEQDISCONT, oss.str(), "wmgr", "dequeue");
+ }
+ }
+
+ const bool ext_rid = dtokp->external_rid();
+ uint64_t rid = (ext_rid | cont) ? dtokp->rid() : _lfc.getNextRecordId();
+ uint64_t dequeue_rid = (ext_rid | cont) ? dtokp->dequeue_rid() : dtokp->rid();
+ _deq_rec.reset(_lfc.getCurrentSerial(), rid, dequeue_rid, xid_ptr, xid_len, txn_coml_commit);
+ if (!cont)
+ {
+ if (!ext_rid)
+ {
+ dtokp->set_rid(rid);
+ dtokp->set_dequeue_rid(dequeue_rid);
+ }
+ if (xid_len)
+ dtokp->set_xid(xid_ptr, xid_len);
+ else
+ dtokp->clear_xid();
+ dequeue_check(dtokp->xid(), dequeue_rid);
+ dtokp->set_dblocks_written(0); // Reset dblks_written from previous op
+ _deq_busy = true;
+ }
+//std::cout << "---+++ wmgr::dequeue() DEQ rid=0x" << std::hex << rid << " drid=0x" << dequeue_rid << " " << std::dec << std::flush; // DEBUG
+ std::string xid((const char*)xid_ptr, xid_len);
+ bool done = false;
+ Checksum checksum;
+ while (!done)
+ {
+//std::cout << "*" << std::flush; // DEBUG
+ assert(_pg_offset_dblks < _cache_pgsize_sblks * QLS_SBLK_SIZE_DBLKS);
+ void* wptr = (void*)((char*)_page_ptr_arr[_pg_index] + _pg_offset_dblks * QLS_DBLK_SIZE_BYTES);
+ uint32_t data_offs_dblks = dtokp->dblocks_written();
+ uint32_t ret = _deq_rec.encode(wptr, data_offs_dblks,
+ (_cache_pgsize_sblks * QLS_SBLK_SIZE_DBLKS) - _pg_offset_dblks, checksum);
+
+ if (data_offs_dblks == 0) {
+ uint64_t fid;
+ short eres = _emap.get_pfid(dtokp->dequeue_rid(), fid);
+ if (eres == enq_map::EMAP_OK) {
+ dtokp->set_fid(fid);
+ } else if (xid_len > 0) {
+ txn_data_list_t tdl = _tmap.get_tdata_list(xid);
+ bool found = false;
+ for (tdl_const_itr_t i=tdl.begin(); i!=tdl.end() && !found; ++i) {
+ if (i->rid_ == dtokp->dequeue_rid()) {
+ found = true;
+ dtokp->set_fid(i->fid_);
+ break;
+ }
+ }
+ if (!found) {
+ throw jexception("rid found in neither emap nor tmap, transactional");
+ }
+ } else {
+ throw jexception("rid not found in emap, non-transactional");
+ }
+ }
+ _pg_offset_dblks += ret;
+ _cached_offset_dblks += ret;
+ dtokp->incr_dblocks_written(ret);
+ dtokp->incr_pg_cnt();
+ _page_cb_arr[_pg_index]._pdtokl->push_back(dtokp);
+
+ // Is the encoding of this record complete?
+ if (dtokp->dblocks_written() >= _deq_rec.rec_size_dblks())
+ {
+//std::cout << "!" << std::flush; // DEBUG
+ // TODO: Incorrect - must set state to ENQ_CACHED; ENQ_SUBM is set when AIO returns.
+ dtokp->set_wstate(data_tok::DEQ_SUBM);
+
+ if (xid_len) // If part of transaction, add to transaction map
+ {
+ // If the enqueue is part of a pending txn, it will not yet be in emap
+ _emap.lock(dequeue_rid); // ignore rid not found error
+ std::string xid((const char*)xid_ptr, xid_len);
+ _tmap.insert_txn_data(xid, txn_data_t(rid, dequeue_rid, dtokp->fid(), 0, false, tpc_flag, false));
+ }
+ else
+ {
+ uint64_t fid;
+ short eres = _emap.get_remove_pfid(dtokp->dequeue_rid(), fid);
+ if (eres < enq_map::EMAP_OK) // fail
+ {
+ if (eres == enq_map::EMAP_RID_NOT_FOUND)
+ {
+ std::ostringstream oss;
+ oss << std::hex << "emap: rid=0x" << rid;
+ throw jexception(jerrno::JERR_MAP_NOTFOUND, oss.str(), "wmgr", "dequeue");
+ }
+ if (eres == enq_map::EMAP_LOCKED)
+ {
+ std::ostringstream oss;
+ oss << std::hex << "rid=0x" << rid;
+ throw jexception(jerrno::JERR_MAP_LOCKED, oss.str(), "wmgr", "dequeue");
+ }
+ }
+ }
+
+ done = true;
+ } else {
+//std::cout << "$" << std::flush; // DEBUG
+ dtokp->set_wstate(data_tok::DEQ_PART);
+ }
+
+ file_header_check(rid, cont, _deq_rec.rec_size_dblks() - data_offs_dblks);
+ flush_check(res, cont, done, rid);
+ }
+ if (dtokp->wstate() >= data_tok::DEQ_SUBM)
+ _deq_busy = false;
+//std::cout << " res=" << iores_str(res) << " _deq_busy=" << (_deq_busy?"T":"F") << std::endl << std::flush; // DEBUG
+ return res;
+}
+
+iores
+wmgr::abort(data_tok* dtokp,
+ const void* const xid_ptr,
+ const std::size_t xid_len)
+{
+ // commit and abort MUST have a valid xid
+ assert(xid_ptr != 0 && xid_len > 0);
+
+ if (_enq_busy || _deq_busy || _commit_busy) {
+ std::ostringstream oss;
+ oss << "RHM_IORES_BUSY: abort while part way through another op:";
+ oss << " _enq_busy=" << (_enq_busy?"T":"F");
+ oss << " _deq_busy=" << (_deq_busy?"T":"F");
+ oss << " _commit_busy=" << (_commit_busy?"T":"F");
+ throw jexception(oss.str()); // TODO: complete exception
+ }
+
+ iores res = pre_write_check(WMGR_ABORT, dtokp);
+ if (res != RHM_IORES_SUCCESS)
+ return res;
+
+ bool cont = false;
+ if (_abort_busy) // If abort() exited last time with RHM_IORES_FULL or RHM_IORES_PAGE_AIOWAIT
+ {
+ if (dtokp->wstate() == data_tok::ABORT_PART)
+ cont = true;
+ else
+ {
+ std::ostringstream oss;
+ oss << "This data_tok: id=" << dtokp->id() << " state=" << dtokp->wstate_str();
+ throw jexception(jerrno::JERR_WMGR_DEQDISCONT, oss.str(), "wmgr", "abort");
+ }
+ }
+
+ uint64_t rid = (dtokp->external_rid() | cont) ? dtokp->rid() : _lfc.getNextRecordId();
+ _txn_rec.reset(false, _lfc.getCurrentSerial(), rid, xid_ptr, xid_len);
+ if (!cont)
+ {
+ dtokp->set_rid(rid);
+ dtokp->set_dequeue_rid(0);
+ dtokp->set_xid(xid_ptr, xid_len);
+ dtokp->set_dblocks_written(0); // Reset dblks_written from previous op
+ _abort_busy = true;
+ }
+ bool done = false;
+ Checksum checksum;
+ while (!done)
+ {
+ assert(_pg_offset_dblks < _cache_pgsize_sblks * QLS_SBLK_SIZE_DBLKS);
+ void* wptr = (void*)((char*)_page_ptr_arr[_pg_index] + _pg_offset_dblks * QLS_DBLK_SIZE_BYTES);
+ uint32_t data_offs_dblks = dtokp->dblocks_written();
+ uint32_t ret = _txn_rec.encode(wptr, data_offs_dblks,
+ (_cache_pgsize_sblks * QLS_SBLK_SIZE_DBLKS) - _pg_offset_dblks, checksum);
+
+ // Remember fid which contains the record header in case record is split over several files
+ if (data_offs_dblks == 0)
+ dtokp->set_fid(_lfc.getCurrentFileSeqNum());
+ _pg_offset_dblks += ret;
+ _cached_offset_dblks += ret;
+ dtokp->incr_dblocks_written(ret);
+ dtokp->incr_pg_cnt();
+ _page_cb_arr[_pg_index]._pdtokl->push_back(dtokp);
+
+ // Is the encoding of this record complete?
+ if (dtokp->dblocks_written() >= _txn_rec.rec_size_dblks())
+ {
+ dtokp->set_wstate(data_tok::ABORT_SUBM);
+
+ // Delete this txn from tmap, unlock any locked records in emap
+ std::string xid((const char*)xid_ptr, xid_len);
+ txn_data_list_t tdl = _tmap.get_remove_tdata_list(xid); // tdl will be empty if xid not found
+ fidl_t fidl;
+ for (tdl_itr_t itr = tdl.begin(); itr != tdl.end(); itr++)
+ {
+ if (!itr->enq_flag_)
+ _emap.unlock(itr->drid_); // ignore rid not found error
+ if (itr->enq_flag_) {
+ fidl.push_back(itr->fid_);
+ }
+ }
+ std::pair<pending_txn_map_itr_t, bool> res = _txn_pending_map.insert(std::pair<std::string, fidl_t>(xid, fidl));
+ if (!res.second)
+ {
+ std::ostringstream oss;
+ oss << std::hex << "_txn_pending_set: xid=\"" << xid << "\"";
+ throw jexception(jerrno::JERR_MAP_DUPLICATE, oss.str(), "wmgr", "abort");
+ }
+
+ done = true;
+ } else {
+ dtokp->set_wstate(data_tok::ABORT_PART);
+ }
+
+ file_header_check(rid, cont, _txn_rec.rec_size_dblks() - data_offs_dblks);
+ flush_check(res, cont, done, rid);
+ }
+ if (dtokp->wstate() >= data_tok::ABORT_SUBM)
+ _abort_busy = false;
+ return res;
+}
+
+iores
+wmgr::commit(data_tok* dtokp,
+ const void* const xid_ptr,
+ const std::size_t xid_len)
+{
+ // commit and abort MUST have a valid xid
+ assert(xid_ptr != 0 && xid_len > 0);
+
+ if (_enq_busy || _deq_busy || _abort_busy) {
+ std::ostringstream oss;
+ oss << "RHM_IORES_BUSY: commit while part way through another op:";
+ oss << " _enq_busy=" << (_enq_busy?"T":"F");
+ oss << " _deq_busy=" << (_deq_busy?"T":"F");
+ oss << " _abort_busy=" << (_abort_busy?"T":"F");
+ throw jexception(oss.str()); // TODO: complete exception
+ }
+
+ iores res = pre_write_check(WMGR_COMMIT, dtokp);
+ if (res != RHM_IORES_SUCCESS)
+ return res;
+
+ bool cont = false;
+ if (_commit_busy) // If commit() exited last time with RHM_IORES_FULL or RHM_IORES_PAGE_AIOWAIT
+ {
+ if (dtokp->wstate() == data_tok::COMMIT_PART)
+ cont = true;
+ else
+ {
+ std::ostringstream oss;
+ oss << "This data_tok: id=" << dtokp->id() << " state=" << dtokp->wstate_str();
+ throw jexception(jerrno::JERR_WMGR_DEQDISCONT, oss.str(), "wmgr", "commit");
+ }
+ }
+
+ uint64_t rid = (dtokp->external_rid() | cont) ? dtokp->rid() : _lfc.getNextRecordId();
+ _txn_rec.reset(true, _lfc.getCurrentSerial(), rid, xid_ptr, xid_len);
+ if (!cont)
+ {
+ dtokp->set_rid(rid);
+ dtokp->set_dequeue_rid(0);
+ dtokp->set_xid(xid_ptr, xid_len);
+ dtokp->set_dblocks_written(0); // Reset dblks_written from previous op
+ _commit_busy = true;
+ }
+ bool done = false;
+ Checksum checksum;
+ while (!done)
+ {
+ assert(_pg_offset_dblks < _cache_pgsize_sblks * QLS_SBLK_SIZE_DBLKS);
+ void* wptr = (void*)((char*)_page_ptr_arr[_pg_index] + _pg_offset_dblks * QLS_DBLK_SIZE_BYTES);
+ uint32_t data_offs_dblks = dtokp->dblocks_written();
+ uint32_t ret = _txn_rec.encode(wptr, data_offs_dblks,
+ (_cache_pgsize_sblks * QLS_SBLK_SIZE_DBLKS) - _pg_offset_dblks, checksum);
+
+ // Remember fid which contains the record header in case record is split over several files
+ if (data_offs_dblks == 0)
+ dtokp->set_fid(_lfc.getCurrentFileSeqNum());
+ _pg_offset_dblks += ret;
+ _cached_offset_dblks += ret;
+ dtokp->incr_dblocks_written(ret);
+ dtokp->incr_pg_cnt();
+ _page_cb_arr[_pg_index]._pdtokl->push_back(dtokp);
+
+ // Is the encoding of this record complete?
+ if (dtokp->dblocks_written() >= _txn_rec.rec_size_dblks())
+ {
+ dtokp->set_wstate(data_tok::COMMIT_SUBM);
+
+ // Delete this txn from tmap, process records into emap
+ std::string xid((const char*)xid_ptr, xid_len);
+ txn_data_list_t tdl = _tmap.get_remove_tdata_list(xid); // tdl will be empty if xid not found
+ fidl_t fidl;
+ for (tdl_itr_t itr = tdl.begin(); itr != tdl.end(); itr++)
+ {
+ if (itr->enq_flag_) // txn enqueue
+ {
+ if (_emap.insert_pfid(itr->rid_, itr->fid_, 0) < enq_map::EMAP_OK) // fail
+ {
+ // The only error code emap::insert_pfid() returns is enq_map::EMAP_DUP_RID.
+ std::ostringstream oss;
+ oss << std::hex << "rid=0x" << itr->rid_ << " _pfid=0x" << itr->fid_;
+ throw jexception(jerrno::JERR_MAP_DUPLICATE, oss.str(), "wmgr", "commit");
+ }
+ }
+ else // txn dequeue
+ {
+ uint64_t fid;
+ short eres = _emap.get_remove_pfid(itr->drid_, fid, true);
+ if (eres < enq_map::EMAP_OK) // fail
+ {
+ if (eres == enq_map::EMAP_RID_NOT_FOUND)
+ {
+ std::ostringstream oss;
+ oss << std::hex << "emap: rid=0x" << itr->drid_;
+ throw jexception(jerrno::JERR_MAP_NOTFOUND, oss.str(), "wmgr", "commit");
+ }
+ if (eres == enq_map::EMAP_LOCKED)
+ {
+ std::ostringstream oss;
+ oss << std::hex << "rid=0x" << itr->drid_;
+ throw jexception(jerrno::JERR_MAP_LOCKED, oss.str(), "wmgr", "commit");
+ }
+ }
+ fidl.push_back(fid);
+ }
+ }
+ std::pair<pending_txn_map_itr_t, bool> res = _txn_pending_map.insert(std::pair<std::string, fidl_t>(xid, fidl));
+ if (!res.second)
+ {
+ std::ostringstream oss;
+ oss << std::hex << "_txn_pending_set: xid=\"" << xid << "\"";
+ throw jexception(jerrno::JERR_MAP_DUPLICATE, oss.str(), "wmgr", "commit");
+ }
+
+ done = true;
+ } else {
+ dtokp->set_wstate(data_tok::COMMIT_PART);
+ }
+
+ file_header_check(rid, cont, _txn_rec.rec_size_dblks() - data_offs_dblks);
+ flush_check(res, cont, done, rid);
+ }
+ if (dtokp->wstate() >= data_tok::COMMIT_SUBM)
+ _commit_busy = false;
+ return res;
+}
+
+void
+wmgr::file_header_check(const uint64_t rid,
+ const bool cont,
+ const uint32_t rec_dblks_rem)
+{
+ if (_lfc.isEmpty()) // File never written (i.e. no header or data)
+ {
+//std::cout << "e" << std::flush;
+ std::size_t fro = 0;
+ if (cont) {
+ bool file_fit = rec_dblks_rem <= _lfc.dataSize_sblks() * QLS_SBLK_SIZE_DBLKS; // Will fit within this journal file
+ bool file_full = rec_dblks_rem == _lfc.dataSize_sblks() * QLS_SBLK_SIZE_DBLKS; // Will exactly fill this journal file
+ if (file_fit && !file_full) {
+ fro = (rec_dblks_rem + (QLS_JRNL_FHDR_RES_SIZE_SBLKS * QLS_SBLK_SIZE_DBLKS)) * QLS_DBLK_SIZE_BYTES;
+ }
+ } else {
+ fro = QLS_JRNL_FHDR_RES_SIZE_SBLKS * QLS_SBLK_SIZE_BYTES;
+ }
+ _lfc.asyncFileHeaderWrite(_ioctx, 0, rid, fro);
+ _aio_evt_rem++;
+ }
+}
+
+void
+wmgr::flush_check(iores& res,
+ bool& cont,
+ bool& done, const uint64_t /*rid*/) // DEBUG
+{
+ // Is page is full, flush
+ if (_pg_offset_dblks >= _cache_pgsize_sblks * QLS_SBLK_SIZE_DBLKS)
+ {
+//std::cout << "^" << _pg_offset_dblks << ">=" << (_cache_pgsize_sblks * QLS_SBLK_SIZE_DBLKS) << std::flush;
+ res = write_flush();
+ assert(res == RHM_IORES_SUCCESS);
+
+ if (_page_cb_arr[_pg_index]._state == AIO_PENDING && !done)
+ {
+ res = RHM_IORES_PAGE_AIOWAIT;
+ done = true;
+ }
+
+ // If file is full, rotate to next file
+ uint32_t dataSize_pgs = _lfc.dataSize_sblks() / _cache_pgsize_sblks;
+ if (_pg_cntr >= dataSize_pgs)
+ {
+//std::cout << _pg_cntr << ">=" << fileSize_pgs << std::flush;
+ get_next_file();
+ if (!done) {
+ cont = true;
+ }
+//std::cout << "***** wmgr::flush_check(): GET NEXT FILE: rid=0x" << std::hex << rid << std::dec << " res=" << iores_str(res) << " cont=" << (cont?"T":"F") << " done=" << (done?"T":"F") << std::endl; // DEBUG
+ }
+ }
+}
+
+iores
+wmgr::flush()
+{
+ iores res = write_flush();
+ uint32_t dataSize_pgs = _lfc.dataSize_sblks() / _cache_pgsize_sblks;
+ if (res == RHM_IORES_SUCCESS && _pg_cntr >= dataSize_pgs) {
+ get_next_file();
+ }
+ return res;
+}
+
+iores
+wmgr::write_flush()
+{
+ iores res = RHM_IORES_SUCCESS;
+ // Don't bother flushing an empty page or one that is still in state AIO_PENDING
+ if (_cached_offset_dblks)
+ {
+ if (_page_cb_arr[_pg_index]._state == AIO_PENDING) {
+//std::cout << "#" << std::flush; // DEBUG
+ res = RHM_IORES_PAGE_AIOWAIT;
+ } else {
+ if (_page_cb_arr[_pg_index]._state != IN_USE)
+ {
+ std::ostringstream oss;
+ oss << "pg_index=" << _pg_index << " state=" << _page_cb_arr[_pg_index].state_str();
+ throw jexception(jerrno::JERR_WMGR_BADPGSTATE, oss.str(), "wmgr", "write_flush");
+ }
+
+ // Send current page using AIO
+
+ // In manual flushes, dblks may not coincide with sblks, add filler records ("RHMx") if necessary.
+ dblk_roundup();
+
+ std::size_t pg_offs = (_pg_offset_dblks - _cached_offset_dblks) * QLS_DBLK_SIZE_BYTES;
+ aio_cb* aiocbp = &_aio_cb_arr[_pg_index];
+ _lfc.asyncPageWrite(_ioctx, aiocbp, (char*)_page_ptr_arr[_pg_index] + pg_offs, _cached_offset_dblks);
+ _page_cb_arr[_pg_index]._state = AIO_PENDING;
+ _aio_evt_rem++;
+//std::cout << "." << _aio_evt_rem << std::flush; // DEBUG
+ _cached_offset_dblks = 0;
+ _jc->instr_incr_outstanding_aio_cnt();
+
+ rotate_page(); // increments _pg_index, resets _pg_offset_dblks if req'd
+ if (_page_cb_arr[_pg_index]._state == UNUSED)
+ _page_cb_arr[_pg_index]._state = IN_USE;
+ }
+ }
+ get_events(0, false);
+ if (_page_cb_arr[_pg_index]._state == UNUSED)
+ _page_cb_arr[_pg_index]._state = IN_USE;
+ return res;
+}
+
+void
+wmgr::get_next_file()
+{
+ _pg_cntr = 0;
+//std::cout << "&&&&& wmgr::get_next_file(): " << status_str() << std::flush << std::endl; // DEBUG
+ _lfc.getNextJournalFile();
+}
+
+int32_t
+wmgr::get_events(timespec* const timeout,
+ bool flush)
+{
+ if (_aio_evt_rem == 0) // no events to get
+ return 0;
+
+ int ret = 0;
+ if ((ret = aio::getevents(_ioctx, flush ? _aio_evt_rem : 1, _aio_evt_rem, _aio_event_arr, timeout)) < 0)
+ {
+ if (ret == -EINTR) // Interrupted by signal
+ return 0;
+ std::ostringstream oss;
+ oss << "io_getevents() failed: " << std::strerror(-ret) << " (" << ret << ") ctx_id=" << _ioctx;
+ oss << " min_nr=" << (flush ? _aio_evt_rem : 1) << " nr=" << _aio_evt_rem;
+ throw jexception(jerrno::JERR__AIO, oss.str(), "wmgr", "get_events");
+ }
+
+ if (ret == 0 && timeout)
+ return jerrno::AIO_TIMEOUT;
+
+ int32_t tot_data_toks = 0;
+ for (int i=0; i<ret; i++) // Index of returned AIOs
+ {
+ if (_aio_evt_rem == 0)
+ {
+ std::ostringstream oss;
+ oss << "_aio_evt_rem; evt " << (i + 1) << " of " << ret;
+ throw jexception(jerrno::JERR__UNDERFLOW, oss.str(), "wmgr", "get_events");
+ }
+ _aio_evt_rem--;
+//std::cout << "'" << _aio_evt_rem; // DEBUG
+ aio_cb* aiocbp = _aio_event_arr[i].obj; // This I/O control block (iocb)
+ page_cb* pcbp = (page_cb*)(aiocbp->data); // This page control block (pcb)
+ long aioret = (long)_aio_event_arr[i].res;
+ if (aioret < 0) {
+ std::ostringstream oss;
+ oss << "AIO write operation failed: " << std::strerror(-aioret) << " (" << aioret << ")" << std::endl;
+ oss << " data=" << _aio_event_arr[i].data << std::endl;
+ oss << " obj=" << _aio_event_arr[i].obj << std::endl;
+ oss << " res=" << _aio_event_arr[i].res << std::endl;
+ oss << " res2=" << _aio_event_arr[i].res2 << std::endl;
+ oss << " iocb->data=" << aiocbp->data << std::endl;
+ oss << " iocb->key=" << aiocbp->key << std::endl;
+ oss << " iocb->aio_lio_opcode=" << aiocbp->aio_lio_opcode << std::endl;
+ oss << " iocb->aio_reqprio=" << aiocbp->aio_reqprio << std::endl;
+ oss << " iocb->aio_fildes=" << aiocbp->aio_fildes << std::endl;
+ oss << " iocb->u.c.buf=" << aiocbp->u.c.buf << std::endl;
+ oss << " iocb->u.c.nbytes=0x" << std::hex << aiocbp->u.c.nbytes << std::dec << " (" << aiocbp->u.c.nbytes << ")" << std::endl;
+ oss << " iocb->u.c.offset=0x" << std::hex << aiocbp->u.c.offset << std::dec << " (" << aiocbp->u.c.offset << ")" << std::endl;
+ oss << " iocb->u.c.flags=0x" << std::hex << aiocbp->u.c.flags << std::dec << " (" << aiocbp->u.c.flags << ")" << std::endl;
+ oss << " iocb->u.c.resfd=" << aiocbp->u.c.resfd << std::endl;
+ if (pcbp) {
+ oss << " Page Control Block: (iocb->data):" << std::endl;
+ oss << " pcb.index=" << pcbp->_index << std::endl;
+ oss << " pcb.state=" << pcbp->_state << " (" << pmgr::page_state_str(pcbp->_state) << ")" << std::endl;
+ oss << " pcb.frid=0x" << std::hex << pcbp->_frid << std::dec << std::endl;
+ oss << " pcb.wdblks=0x" << std::hex << pcbp->_wdblks << std::dec << std::endl;
+ oss << " pcb.pdtokl.size=" << pcbp->_pdtokl->size() << std::endl;
+ oss << " pcb.pbuff=" << pcbp->_pbuff << std::endl;
+ oss << " JournalFile (pcb.jfp):" << std::endl;
+ oss << pcbp->_jfp->status_str(6) << std::endl;
+ } else {
+ file_hdr_t* fhp = (file_hdr_t*)aiocbp->u.c.buf;
+ oss << "fnum=" << fhp->_file_number;
+ oss << " qname=" << std::string((char*)fhp + sizeof(file_hdr_t), fhp->_queue_name_len);
+ }
+ throw jexception(jerrno::JERR__AIO, oss.str(), "wmgr", "get_events");
+ }
+ if (pcbp) // Page writes have pcb
+ {
+//std::cout << "p"; // DEBUG
+ uint32_t s = pcbp->_pdtokl->size();
+ std::vector<data_tok*> dtokl;
+ dtokl.reserve(s);
+ for (uint32_t k=0; k<s; k++)
+ {
+ data_tok* dtokp = pcbp->_pdtokl->at(k);
+ if (dtokp->decr_pg_cnt() == 0)
+ {
+ pending_txn_map_itr_t it;
+ switch (dtokp->wstate())
+ {
+ case data_tok::ENQ_SUBM:
+ dtokl.push_back(dtokp);
+ tot_data_toks++;
+ dtokp->set_wstate(data_tok::ENQ);
+ if (dtokp->has_xid())
+ // Ignoring return value here. A non-zero return can signify that the transaction
+ // has committed or aborted, and which was completed prior to the aio returning.
+ _tmap.set_aio_compl(dtokp->xid(), dtokp->rid());
+ break;
+ case data_tok::DEQ_SUBM:
+ if (!dtokp->has_xid()) {
+ _lfc.decrEnqueuedRecordCount(dtokp->fid());
+ }
+ dtokl.push_back(dtokp);
+ tot_data_toks++;
+ dtokp->set_wstate(data_tok::DEQ);
+ if (dtokp->has_xid())
+ // Ignoring return value - see note above.
+ _tmap.set_aio_compl(dtokp->xid(), dtokp->rid());
+ break;
+ case data_tok::ABORT_SUBM:
+ dtokl.push_back(dtokp);
+ tot_data_toks++;
+ dtokp->set_wstate(data_tok::ABORTED);
+ it = _txn_pending_map.find(dtokp->xid());
+ if (it == _txn_pending_map.end())
+ {
+ std::ostringstream oss;
+ oss << std::hex << "_txn_pending_set: abort xid=\""
+ << qpid::linearstore::journal::jcntl::str2hexnum(dtokp->xid()) << "\"";
+ throw jexception(jerrno::JERR_MAP_NOTFOUND, oss.str(), "wmgr", "get_events");
+ }
+ for (fidl_itr_t i=it->second.begin(); i!=it->second.end(); ++i) {
+ _lfc.decrEnqueuedRecordCount(*i);
+ }
+ _txn_pending_map.erase(it);
+ break;
+ case data_tok::COMMIT_SUBM:
+ dtokl.push_back(dtokp);
+ tot_data_toks++;
+ dtokp->set_wstate(data_tok::COMMITTED);
+ it = _txn_pending_map.find(dtokp->xid());
+ if (it == _txn_pending_map.end())
+ {
+ std::ostringstream oss;
+ oss << std::hex << "_txn_pending_set: commit xid=\""
+ << qpid::linearstore::journal::jcntl::str2hexnum(dtokp->xid()) << "\"";
+ throw jexception(jerrno::JERR_MAP_NOTFOUND, oss.str(), "wmgr", "get_events");
+ }
+ for (fidl_itr_t i=it->second.begin(); i!=it->second.end(); ++i) {
+ _lfc.decrEnqueuedRecordCount(*i);
+ }
+ _txn_pending_map.erase(it);
+ break;
+ case data_tok::ENQ_PART:
+ case data_tok::DEQ_PART:
+ case data_tok::ABORT_PART:
+ case data_tok::COMMIT_PART:
+ // ignore these
+ break;
+ default:
+ // throw for anything else
+ std::ostringstream oss;
+ oss << "dtok_id=" << dtokp->id() << " dtok_state=" << dtokp->wstate_str();
+ throw jexception(jerrno::JERR_WMGR_BADDTOKSTATE, oss.str(), "wmgr",
+ "get_events");
+ }
+ }
+ }
+
+ // Increment the completed write offset
+ // NOTE: We cannot use _wrfc here, as it may have rotated since submitting count.
+ // Use stored pointer to fcntl in the pcb instead.
+ pcbp->_jfp->addCompletedDblkCount(pcbp->_wdblks);
+ pcbp->_jfp->decrOutstandingAioOperationCount();
+ _jc->instr_decr_outstanding_aio_cnt();
+
+ // Clean up this pcb's data_tok list
+ pcbp->_pdtokl->clear();
+ pcbp->_state = UNUSED;
+//std::cout << "c" << pcbp->_index << pcbp->state_str(); // DEBUG
+
+ // Perform AIO return callback
+ if (_cbp && tot_data_toks)
+ _cbp->wr_aio_cb(dtokl);
+ }
+ else // File header writes have no pcb
+ {
+//std::cout << "f"; // DEBUG
+ file_hdr_t* fhp = (file_hdr_t*)aiocbp->u.c.buf;
+ _lfc.addWriteCompletedDblkCount(fhp->_file_number, QLS_JRNL_FHDR_RES_SIZE_SBLKS * QLS_SBLK_SIZE_DBLKS);
+ _lfc.decrOutstandingAioOperationCount(fhp->_file_number);
+ }
+ }
+
+ return tot_data_toks;
+}
+
+bool
+wmgr::is_txn_synced(const std::string& xid)
+{
+ // Ignore xid not found error here
+ if (_tmap.is_txn_synced(xid) == txn_map::TMAP_NOT_SYNCED)
+ return false;
+ // Check for outstanding commit/aborts
+ pending_txn_map_itr_t it = _txn_pending_map.find(xid);
+ return it == _txn_pending_map.end();
+}
+
+void
+wmgr::initialize(aio_callback* const cbp,
+ const uint32_t wcache_pgsize_sblks,
+ const uint16_t wcache_num_pages)
+{
+
+ pmgr::initialize(cbp, wcache_pgsize_sblks, wcache_num_pages);
+ wmgr::clean();
+ _page_cb_arr[0]._state = IN_USE;
+ _cached_offset_dblks = 0;
+ _enq_busy = false;
+}
+
+iores
+wmgr::pre_write_check(const _op_type op,
+ const data_tok* const dtokp,
+ const std::size_t /*xidsize*/,
+ const std::size_t /*dsize*/,
+ const bool /*external*/) const
+{
+ // Check status of current file
+ // TODO: Replace for LFC
+/*
+ if (!_wrfc.is_wr_reset())
+ {
+ if (!_wrfc.wr_reset())
+ return RHM_IORES_FULL;
+ }
+*/
+
+ // Check status of current page is ok for writing
+ if (_page_cb_arr[_pg_index]._state != IN_USE)
+ {
+ if (_page_cb_arr[_pg_index]._state == UNUSED)
+ _page_cb_arr[_pg_index]._state = IN_USE;
+ else if (_page_cb_arr[_pg_index]._state == AIO_PENDING)
+ return RHM_IORES_PAGE_AIOWAIT;
+ else
+ {
+ std::ostringstream oss;
+ oss << "jrnl=" << _jc->id() << " op=" << _op_str[op];
+ oss << " index=" << _pg_index << " pg_state=" << _page_cb_arr[_pg_index].state_str();
+ throw jexception(jerrno::JERR_WMGR_BADPGSTATE, oss.str(), "wmgr", "pre_write_check");
+ }
+ }
+
+ // operation-specific checks
+ switch (op)
+ {
+ case WMGR_ENQUEUE:
+ {
+ if (!dtokp->is_writable())
+ {
+ std::ostringstream oss;
+ oss << "jrnl=" << _jc->id() << " op=" << _op_str[op];
+ oss << " dtok_id=" << dtokp->id() << " dtok_state=" << dtokp->wstate_str();
+ throw jexception(jerrno::JERR_WMGR_BADDTOKSTATE, oss.str(), "wmgr",
+ "pre_write_check");
+ }
+ }
+ break;
+ case WMGR_DEQUEUE:
+ if (!dtokp->is_dequeueable())
+ {
+ std::ostringstream oss;
+ oss << "jrnl=" << _jc->id() << " op=" << _op_str[op];
+ oss << " dtok_id=" << dtokp->id() << " dtok_state=" << dtokp->wstate_str();
+ throw jexception(jerrno::JERR_WMGR_BADDTOKSTATE, oss.str(), "wmgr",
+ "pre_write_check");
+ }
+ break;
+ case WMGR_ABORT:
+ break;
+ case WMGR_COMMIT:
+ break;
+ }
+
+ return RHM_IORES_SUCCESS;
+}
+
+void
+wmgr::dequeue_check(const std::string& xid,
+ const uint64_t drid)
+{
+ // First check emap
+ bool found = false;
+ uint64_t fid;
+ short eres = _emap.get_pfid(drid, fid);
+ if (eres < enq_map::EMAP_OK) { // fail
+ if (eres == enq_map::EMAP_RID_NOT_FOUND) {
+ if (xid.size()) {
+ found = _tmap.data_exists(xid, drid);
+ }
+ } else if (eres == enq_map::EMAP_LOCKED) {
+ std::ostringstream oss;
+ oss << std::hex << "drid=0x" << drid;
+ throw jexception(jerrno::JERR_MAP_LOCKED, oss.str(), "wmgr", "dequeue_check");
+ }
+ } else {
+ found = true;
+ }
+ if (!found) {
+ std::ostringstream oss;
+ oss << "jrnl=" << _jc->id() << " drid=0x" << std::hex << drid;
+ throw jexception(jerrno::JERR_WMGR_DEQRIDNOTENQ, oss.str(), "wmgr", "dequeue_check");
+ }
+}
+
+void
+wmgr::dblk_roundup()
+{
+ const uint32_t xmagic = QLS_EMPTY_MAGIC;
+ uint32_t wdblks = jrec::size_blks(_cached_offset_dblks, QLS_SBLK_SIZE_DBLKS) * QLS_SBLK_SIZE_DBLKS;
+ while (_cached_offset_dblks < wdblks)
+ {
+//std::cout << "^0x" << std::hex << _cached_offset_dblks << "<0x" << wdblks << std::dec << std::flush;
+ void* wptr = (void*)((char*)_page_ptr_arr[_pg_index] + _pg_offset_dblks * QLS_DBLK_SIZE_BYTES);
+ std::memcpy(wptr, (const void*)&xmagic, sizeof(xmagic));
+#ifdef QLS_CLEAN
+ std::memset((char*)wptr + sizeof(xmagic), QLS_CLEAN_CHAR, QLS_DBLK_SIZE_BYTES - sizeof(xmagic));
+#endif
+ _pg_offset_dblks++;
+ _cached_offset_dblks++;
+ }
+}
+
+void
+wmgr::rotate_page()
+{
+//std::cout << "^^^^^ wmgr::rotate_page() " << status_str() << " pi=" << _pg_index; // DEBUG
+ if (_pg_offset_dblks >= _cache_pgsize_sblks * QLS_SBLK_SIZE_DBLKS)
+ {
+ _pg_offset_dblks = 0;
+ _pg_cntr++;
+ }
+ if (++_pg_index >= _cache_num_pages)
+ _pg_index = 0;
+//std::cout << "->" << _pg_index << std::endl; // DEBUG
+}
+
+void
+wmgr::clean() {
+ // Clean up allocated memory here
+}
+
+const std::string
+wmgr::status_str() const
+{
+ std::ostringstream oss;
+ oss << "wmgr: pi=" << _pg_index << " pc=" << _pg_cntr;
+ oss << " po=" << _pg_offset_dblks << " aer=" << _aio_evt_rem;
+ oss << " edac=" << (_enq_busy?"T":"F") << (_deq_busy?"T":"F");
+ oss << (_abort_busy?"T":"F") << (_commit_busy?"T":"F");
+ oss << " ps=[";
+ for (int i=0; i<_cache_num_pages; i++)
+ {
+ switch (_page_cb_arr[i]._state)
+ {
+ case UNUSED: oss << "-"; break;
+ case IN_USE: oss << "U"; break;
+ case AIO_PENDING: oss << "A"; break;
+ default: oss << _page_cb_arr[i]._state;
+ }
+ }
+ oss << "] ";
+ return oss.str();
+}
+
+// static
+
+const char* wmgr::_op_str[] = {"enqueue", "dequeue", "abort", "commit"};
+
+}}}
diff --git a/qpid/cpp/src/qpid/linearstore/journal/wmgr.h b/qpid/cpp/src/qpid/linearstore/journal/wmgr.h
new file mode 100644
index 0000000000..99da20bab9
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/journal/wmgr.h
@@ -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.
+ *
+ */
+
+#ifndef QPID_LINEARSTORE_JOURNAL_WMGR_H
+#define QPID_LINEARSTORE_JOURNAL_WMGR_H
+
+#include <deque>
+#include <map>
+#include "qpid/linearstore/journal/enums.h"
+#include "qpid/linearstore/journal/pmgr.h"
+#include <vector>
+
+namespace qpid {
+namespace linearstore {
+namespace journal {
+
+class LinearFileController;
+
+/**
+* \brief Class for managing a write page cache of arbitrary size and number of pages.
+*
+* The write page cache works on the principle of caching the write data within a page until
+* that page is either full or flushed; this initiates a single AIO write operation to store
+* the data on disk.
+*
+* The maximum disk throughput is achieved by keeping the write operations of uniform size.
+* Waiting for a page cache to fill achieves this; and in high data volume/throughput situations
+* achieves the optimal disk throughput. Calling flush() forces a write of the current page cache
+* no matter how full it is, and disrupts the uniformity of the write operations. This should
+* normally only be done if throughput drops and there is a danger of a page of unwritten data
+* waiting around for excessive time.
+*
+* The usual tradeoff between data storage latency and throughput performance applies.
+*/
+class wmgr : public pmgr
+{
+private:
+ typedef std::vector<uint64_t> fidl_t;
+ typedef fidl_t::iterator fidl_itr_t;
+ typedef std::map<std::string, fidl_t> pending_txn_map_t;
+ typedef pending_txn_map_t::iterator pending_txn_map_itr_t;
+
+ LinearFileController& _lfc; ///< Linear File Controller ref
+ uint32_t _max_dtokpp; ///< Max data writes per page
+ uint32_t _max_io_wait_us; ///< Max wait in microseconds till submit
+ uint32_t _cached_offset_dblks; ///< Amount of unwritten data in page (dblocks)
+
+ // TODO: Convert _enq_busy etc into a proper threadsafe lock
+ // TODO: Convert to enum? Are these encodes mutually exclusive?
+ bool _enq_busy; ///< Flag true if enqueue is in progress
+ bool _deq_busy; ///< Flag true if dequeue is in progress
+ bool _abort_busy; ///< Flag true if abort is in progress
+ bool _commit_busy; ///< Flag true if commit is in progress
+
+ enum _op_type { WMGR_ENQUEUE = 0, WMGR_DEQUEUE, WMGR_ABORT, WMGR_COMMIT };
+ static const char* _op_str[];
+
+ enq_rec _enq_rec; ///< Enqueue record used for encoding/decoding
+ deq_rec _deq_rec; ///< Dequeue record used for encoding/decoding
+ txn_rec _txn_rec; ///< Transaction record used for encoding/decoding
+ pending_txn_map_t _txn_pending_map; ///< Set containing xids of pending commits/aborts
+
+public:
+ wmgr(jcntl* jc,
+ enq_map& emap,
+ txn_map& tmap,
+ LinearFileController& lfc);
+ wmgr(jcntl* jc,
+ enq_map& emap,
+ txn_map& tmap,
+ LinearFileController& lfc,
+ const uint32_t max_dtokpp,
+ const uint32_t max_iowait_us);
+ virtual ~wmgr();
+
+ void initialize(aio_callback* const cbp,
+ const uint32_t wcache_pgsize_sblks,
+ const uint16_t wcache_num_pages,
+ const uint32_t max_dtokpp,
+ const uint32_t max_iowait_us,
+ std::size_t end_offset);
+ iores enqueue(const void* const data_buff,
+ const std::size_t tot_data_len,
+ const std::size_t this_data_len,
+ data_tok* dtokp,
+ const void* const xid_ptr,
+ const std::size_t xid_len,
+ const bool tpc_flag,
+ const bool transient,
+ const bool external);
+ iores dequeue(data_tok* dtokp,
+ const void* const xid_ptr,
+ const std::size_t xid_len,
+ const bool tpc_flag,
+ const bool txn_coml_commit);
+ iores abort(data_tok* dtokp,
+ const void* const xid_ptr,
+ const std::size_t xid_len);
+ iores commit(data_tok* dtokp,
+ const void* const xid_ptr,
+ const std::size_t xid_len);
+ iores flush();
+ int32_t get_events(timespec* const timeout,
+ bool flush);
+ bool is_txn_synced(const std::string& xid);
+ inline bool curr_pg_blocked() const { return _page_cb_arr[_pg_index]._state != UNUSED; }
+ inline uint32_t unflushed_dblks() { return _cached_offset_dblks; }
+
+ // Debug aid
+ const std::string status_str() const;
+
+private:
+ void initialize(aio_callback* const cbp,
+ const uint32_t wcache_pgsize_sblks,
+ const uint16_t wcache_num_pages);
+ iores pre_write_check(const _op_type op,
+ const data_tok* const dtokp,
+ const std::size_t xidsize = 0,
+ const std::size_t dsize = 0,
+ const bool external = false) const;
+ void dequeue_check(const std::string& xid,
+ const uint64_t drid);
+ void file_header_check(const uint64_t rid,
+ const bool cont,
+ const uint32_t rec_dblks_rem);
+ void flush_check(iores& res,
+ bool& cont,
+ bool& done, const uint64_t rid);
+ iores write_flush();
+ void get_next_file();
+ void dblk_roundup();
+ void rotate_page();
+ void clean();
+};
+
+}}}
+
+#endif // ifndef QPID_LINEARSTORE_JOURNAL_WMGR_H
diff --git a/qpid/cpp/src/qpid/linearstore/management-schema.xml b/qpid/cpp/src/qpid/linearstore/management-schema.xml
new file mode 100644
index 0000000000..ebd388593e
--- /dev/null
+++ b/qpid/cpp/src/qpid/linearstore/management-schema.xml
@@ -0,0 +1,54 @@
+<schema package="org.apache.qpid.linearstore">
+
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+
+ <class name="Store">
+ <property name="brokerRef" type="objId" access="RO" references="qpid.Broker" index="y" parentRef="y"/>
+ <property name="storeDir" type="sstr" access="RO" desc="Logical directory on disk"/>
+ <property name="tplIsInitialized" type="bool" access="RO" desc="Transaction prepared list has been initialized by a transactional prepare"/>
+ <property name="tplDirectory" type="sstr" access="RO" desc="Transaction prepared list directory"/>
+ <property name="tplWritePageSize" type="uint32" access="RO" unit="byte" desc="Page size in transaction prepared list write-page-cache"/>
+ <property name="tplWritePages" type="uint32" access="RO" unit="wpage" desc="Number of pages in transaction prepared list write-page-cache"/>
+
+ <statistic name="tplTransactionDepth" type="hilo32" unit="txn" desc="Number of currently enqueued prepared transactions"/>
+ <statistic name="tplTxnPrepares" type="count64" unit="record" desc="Total transaction prepares on transaction prepared list"/>
+ <statistic name="tplTxnCommits" type="count64" unit="record" desc="Total transaction commits on transaction prepared list"/>
+ <statistic name="tplTxnAborts" type="count64" unit="record" desc="Total transaction aborts on transaction prepared list"/>
+ </class>
+
+ <class name="Journal">
+ <property name="queueRef" type="objId" access="RO" references="qpid.Queue" isGeneralReference="y"/>
+ <property name="queueName" type="sstr" access="RC" index="y"/>
+ <property name="directory" type="sstr" access="RO" desc="Directory containing journal files"/>
+ <property name="writePageSize" type="uint32" access="RO" unit="byte" desc="Deprecated"/>
+ <property name="writePages" type="uint32" access="RO" unit="wpage" desc="Deprecated"/>
+
+ <statistic name="recordDepth" type="hilo32" unit="record" desc="Number of currently enqueued records (durable messages)"/>
+ <statistic name="enqueues" type="count64" unit="record" desc="Total enqueued records on journal"/>
+ <statistic name="dequeues" type="count64" unit="record" desc="Total dequeued records on journal"/>
+ <statistic name="txn" type="count32" unit="record" desc="Total open transactions (xids) on journal"/>
+ <statistic name="txnEnqueues" type="count64" unit="record" desc="Total transactional enqueued records on journal"/>
+ <statistic name="txnDequeues" type="count64" unit="record" desc="Total transactional dequeued records on journal"/>
+ <statistic name="txnCommits" type="count64" unit="record" desc="Total transactional commit records on journal"/>
+ <statistic name="txnAborts" type="count64" unit="record" desc="Total transactional abort records on journal"/>
+ <statistic name="outstandingAIOs" type="hilo32" unit="aio_op" desc="Number of currently outstanding AIO requests in Async IO system"/>
+
+ </class>
+</schema>
diff --git a/qpid/cpp/src/qpid/log/Helpers.h b/qpid/cpp/src/qpid/log/Helpers.h
new file mode 100644
index 0000000000..82ef8244be
--- /dev/null
+++ b/qpid/cpp/src/qpid/log/Helpers.h
@@ -0,0 +1,79 @@
+#ifndef QPID_LOG_HELPERS_H
+#define QPID_LOG_HELPERS_H
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <boost/range.hpp>
+
+#include <ostream>
+
+namespace qpid {
+namespace log {
+
+/** @file Helper classes for logging complex types */
+
+/// @internal
+template <class Range>
+struct ListFormatter {
+ typedef typename boost::range_const_iterator<Range>::type Iterator;
+ boost::iterator_range<Iterator> range;
+ const char* separator;
+
+ ListFormatter(const Range& r, const char* s=", ") : range(r), separator(s) {}
+};
+
+/// @internal
+template <class Range>
+std::ostream& operator<<(std::ostream& out, const ListFormatter<Range>& sl) {
+ typename ListFormatter<Range>::Iterator i = sl.range.begin();
+ if (i != sl.range.end()) out << *(i++);
+ while (i != sl.range.end()) out << sl.separator << *(i++);
+ return out;
+}
+
+/** Return a formatting object with operator <<
+ * to stream range as a separated list.
+ *@param range: a range - all standard containers are ranges,
+ * as is a pair of iterators.
+ *@param separator: printed between elements, default ", "
+ */
+template <class Range>
+ListFormatter<Range> formatList(const Range& range, const char* separator=", ") {
+ return ListFormatter<Range>(range, separator);
+}
+
+/** Return a formatting object with operator <<
+ * to stream the range defined by iterators [begin, end)
+ * as a separated list.
+ *@param begin, end: Beginning and end of range.
+ *@param separator: printed between elements, default ", "
+ */
+template <class U, class V>
+ListFormatter<std::pair<U,V> > formatList(U begin, V end, const char* separator=", ") {
+ return formatList(std::make_pair(begin,end), separator);
+}
+
+
+}} // namespace qpid::log
+
+
+
+#endif /*!QPID_LOG_HELPERS_H*/
diff --git a/qpid/cpp/src/qpid/log/Logger.cpp b/qpid/cpp/src/qpid/log/Logger.cpp
new file mode 100644
index 0000000000..fc254f2857
--- /dev/null
+++ b/qpid/cpp/src/qpid/log/Logger.cpp
@@ -0,0 +1,200 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/log/Logger.h"
+#include "qpid/log/Options.h"
+#include "qpid/log/SinkOptions.h"
+#include "qpid/memory.h"
+#include "qpid/sys/Thread.h"
+#include "qpid/sys/Time.h"
+#include "qpid/DisableExceptionLogging.h"
+
+#include "boost/version.hpp"
+#if (BOOST_VERSION >= 104000)
+#include <boost/serialization/singleton.hpp>
+#else
+#include <boost/pool/detail/singleton.hpp>
+#endif
+
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <algorithm>
+#include <sstream>
+#include <iomanip>
+#include <stdexcept>
+#include <time.h>
+
+
+namespace qpid {
+namespace log {
+
+using namespace std;
+
+typedef sys::Mutex::ScopedLock ScopedLock;
+
+inline void Logger::enable_unlocked(Statement* s) {
+ s->enabled=selector.isEnabled(s->level, s->function, s->category);
+}
+
+Logger& Logger::instance() {
+#if (BOOST_VERSION >= 104000)
+ return boost::serialization::singleton<Logger>::get_mutable_instance();
+#else
+ return boost::details::pool::singleton_default<Logger>::instance();
+#endif
+}
+
+Logger::Logger() : flags(0) {
+ // Disable automatic logging in Exception constructors to avoid
+ // re-entrant use of logger singleton if there is an error in
+ // option parsing.
+ DisableExceptionLogging del;
+
+ // Initialize myself from env variables so all programs
+ // (e.g. tests) can use logging even if they don't parse
+ // command line args.
+ Options opts;
+ opts.parse(0, 0);
+ configure(opts);
+}
+
+Logger::~Logger() {}
+
+void Logger::select(const Selector& s) {
+ ScopedLock l(lock);
+ selector=s;
+ std::for_each(statements.begin(), statements.end(),
+ boost::bind(&Logger::enable_unlocked, this, _1));
+}
+
+Logger::Output::Output() {}
+Logger::Output::~Output() {}
+
+void Logger::log(const Statement& s, const std::string& msg) {
+ // Format the message outside the lock.
+ std::ostringstream os;
+ if (!prefix.empty())
+ os << prefix << ": ";
+ if (flags&TIME) {
+ if (flags&HIRES)
+ qpid::sys::outputHiresNow(os);
+ else
+ qpid::sys::outputFormattedNow(os);
+ }
+ if (flags&CATEGORY)
+ os << "[" << CategoryTraits::name(s.category) << "] ";
+ if (flags&LEVEL)
+ os << LevelTraits::name(s.level) << " ";
+ if (flags&THREAD)
+ os << "[0x" << hex << qpid::sys::Thread::logId() << "] ";
+ if (flags&FILE)
+ os << s.file << ":";
+ if (flags&LINE)
+ os << dec << s.line << ":";
+ if ((flags&FUNCTION) && s.function)
+ os << s.function << ":";
+ if (flags & (FILE|LINE|FUNCTION))
+ os << " ";
+ os << msg << endl;
+ std::string formatted=os.str();
+ {
+ ScopedLock l(lock);
+ std::for_each(outputs.begin(), outputs.end(),
+ boost::bind(&Output::log, _1, s, formatted));
+ }
+}
+
+void Logger::output(std::auto_ptr<Output> out) {
+ ScopedLock l(lock);
+ outputs.push_back(out.release());
+}
+
+void Logger::clear() {
+ select(Selector()); // locked
+ format(0); // locked
+ ScopedLock l(lock);
+ outputs.clear();
+}
+
+void Logger::format(int formatFlags) {
+ ScopedLock l(lock);
+ flags=formatFlags;
+}
+
+static int bitIf(bool test, int bit) {
+ return test ? bit : 0;
+}
+
+int Logger::format(const Options& opts) {
+ int flags=
+ bitIf(opts.level, LEVEL) |
+ bitIf(opts.time, TIME) |
+ bitIf(opts.source, (FILE|LINE)) |
+ bitIf(opts.function, FUNCTION) |
+ bitIf(opts.thread, THREAD) |
+ bitIf(opts.hiresTs, HIRES) |
+ bitIf(opts.category, CATEGORY);
+ format(flags);
+ return flags;
+}
+
+void Logger::add(Statement& s) {
+ ScopedLock l(lock);
+ enable_unlocked(&s);
+ statements.insert(&s);
+}
+
+void Logger::configure(const Options& opts) {
+ clear();
+ Options o(opts);
+ if (o.trace)
+ o.selectors.push_back("trace+");
+ format(o);
+ select(Selector(o));
+ options = opts;
+ setPrefix(opts.prefix);
+ options.sinkOptions->setup(this);
+}
+
+void Logger::reconfigure(const std::vector<std::string>& selectors) {
+ Options o(options);
+ o.selectors = selectors;
+ o.deselectors.clear();
+ select(Selector(o));
+ options = o; // Don't update options till selectors has been validated.
+}
+
+void Logger::setPrefix(const std::string& p) { prefix = p; }
+
+
+bool Logger::getHiresTimestamp()
+{
+ return flags & HIRES;
+}
+
+
+void Logger::setHiresTimestamp(bool setting)
+{
+ ScopedLock l(lock);
+ if (setting)
+ flags |= HIRES;
+ else
+ flags &= ~HIRES;
+}
+
+}} // namespace qpid::log
diff --git a/qpid/cpp/src/qpid/log/Logger.h b/qpid/cpp/src/qpid/log/Logger.h
new file mode 100644
index 0000000000..8c4beb0785
--- /dev/null
+++ b/qpid/cpp/src/qpid/log/Logger.h
@@ -0,0 +1,122 @@
+#ifndef QPID_LOG_LOGGER_H
+#define QPID_LOG_LOGGER_H
+
+/*
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/log/Selector.h"
+#include "qpid/log/Options.h"
+#include "qpid/sys/Mutex.h"
+#include <boost/ptr_container/ptr_vector.hpp>
+#include <boost/noncopyable.hpp>
+#include <set>
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+namespace log {
+
+/**
+ * Central logging agent.
+ *
+ * Thread safe, singleton.
+ *
+ * The Logger provides all needed functionality for selecting and
+ * formatting logging output. The actual outputting of log records
+ * is handled by Logger::Output-derived classes instantiated by the
+ * platform's sink-related options.
+ */
+class QPID_COMMON_CLASS_EXTERN Logger : private boost::noncopyable {
+ public:
+ /** Flags indicating what to include in the log output */
+ enum FormatFlag { FILE=1, LINE=2, FUNCTION=4, LEVEL=8, TIME=16, THREAD=32, HIRES=64, CATEGORY=128};
+
+ /**
+ * Logging output sink.
+ *
+ * The Output sink provides an interface to direct logging output to.
+ * Logging sinks are primarily platform-specific as provided for on
+ * each platform.
+ *
+ * Implementations of Output must be thread safe.
+ */
+ class Output {
+ public:
+ QPID_COMMON_EXTERN Output();
+ QPID_COMMON_EXTERN virtual ~Output();
+ /** Receives the statemnt of origin and formatted message to log. */
+ virtual void log(const Statement&, const std::string&) =0;
+ };
+
+ QPID_COMMON_EXTERN static Logger& instance();
+
+ QPID_COMMON_EXTERN Logger();
+ QPID_COMMON_EXTERN ~Logger();
+
+ /** Select the messages to be logged. */
+ QPID_COMMON_EXTERN void select(const Selector& s);
+
+ /** Set the formatting flags, bitwise OR of FormatFlag values. */
+ QPID_COMMON_EXTERN void format(int formatFlags);
+
+ /** Set format flags from options object.
+ *@returns computed flags.
+ */
+ QPID_COMMON_EXTERN int format(const Options&);
+
+ /** Configure logger from Options */
+ QPID_COMMON_EXTERN void configure(const Options& o);
+
+ /** Reset the log selectors */
+ QPID_COMMON_EXTERN void reconfigure(const std::vector<std::string>& selectors);
+
+ /** Add a statement. */
+ QPID_COMMON_EXTERN void add(Statement& s);
+
+ /** Log a message. */
+ QPID_COMMON_EXTERN void log(const Statement&, const std::string&);
+
+ /** Add an output destination for messages */
+ QPID_COMMON_EXTERN void output(std::auto_ptr<Output> out);
+
+ /** Set a prefix for all messages */
+ QPID_COMMON_EXTERN void setPrefix(const std::string& prefix);
+
+ /** Reset the logger. */
+ QPID_COMMON_EXTERN void clear();
+
+ /** Get the options used to configure the logger. */
+ QPID_COMMON_INLINE_EXTERN const Options& getOptions() const { return options; }
+
+ /** Get the hires timestamp setting */
+ QPID_COMMON_EXTERN bool getHiresTimestamp();
+
+ /** Set the hires timestamp setting */
+ QPID_COMMON_EXTERN void setHiresTimestamp(bool setting);
+
+ private:
+ typedef boost::ptr_vector<Output> Outputs;
+ typedef std::set<Statement*> Statements;
+
+ sys::Mutex lock;
+ inline void enable_unlocked(Statement* s);
+
+ Statements statements;
+ Outputs outputs;
+ Selector selector;
+ int flags;
+ std::string prefix;
+ Options options;
+};
+
+}} // namespace qpid::log
+
+
+#endif /*!QPID_LOG_LOGGER_H*/
diff --git a/qpid/cpp/src/qpid/log/Options.cpp b/qpid/cpp/src/qpid/log/Options.cpp
new file mode 100644
index 0000000000..f816124b4e
--- /dev/null
+++ b/qpid/cpp/src/qpid/log/Options.cpp
@@ -0,0 +1,148 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/log/Options.h"
+#include "qpid/log/SinkOptions.h"
+#include "qpid/log/Statement.h"
+#include "qpid/Options.h"
+#include <map>
+#include <string>
+#include <algorithm>
+
+namespace qpid {
+namespace log {
+
+Options::Options(const std::string& argv0_, const std::string& name_) :
+ qpid::Options(name_),
+ argv0(argv0_),
+ name(name_),
+ time(true),
+ level(true),
+ thread(false),
+ source(false),
+ function(false),
+ hiresTs(false),
+ category(true),
+ trace(false),
+ sinkOptions (SinkOptions::create(argv0_))
+{
+ selectors.push_back("notice+");
+
+ addOptions()
+ ("trace,t", optValue(trace), "Enables all logging" )
+ ("log-enable", optValue(selectors, "RULE"),
+ ("Enables logging for selected levels and components. "
+ "RULE is in the form 'LEVEL[+-][:PATTERN]'\n"
+ "LEVEL is one of: \n\t "+getLevels()+"\n"
+ "PATTERN is a logging category name, or a namespace-qualified "
+ "function name or name fragment. "
+ "Logging category names are: \n\t "+getCategories()+"\n"
+ "For example:\n"
+ "\t'--log-enable warning+'\n"
+ "logs all warning, error and critical messages.\n"
+ "\t'--log-enable trace+:Broker'\n"
+ "logs all category 'Broker' messages.\n"
+ "\t'--log-enable debug:framing'\n"
+ "logs debug messages from all functions with 'framing' in the namespace or function name.\n"
+ "This option can be used multiple times").c_str())
+ ("log-disable", optValue(deselectors, "RULE"),
+ ("Disables logging for selected levels and components. "
+ "RULE is in the form 'LEVEL[+-][:PATTERN]'\n"
+ "LEVEL is one of: \n\t "+getLevels()+"\n"
+ "PATTERN is a logging category name, or a namespace-qualified "
+ "function name or name fragment. "
+ "Logging category names are: \n\t "+getCategories()+"\n"
+ "For example:\n"
+ "\t'--log-disable warning-'\n"
+ "disables logging all warning, notice, info, debug, and trace messages.\n"
+ "\t'--log-disable trace:Broker'\n"
+ "disables all category 'Broker' trace messages.\n"
+ "\t'--log-disable debug-:qmf::'\n"
+ "disables logging debug and trace messages from all functions with 'qmf::' in the namespace.\n"
+ "This option can be used multiple times").c_str())
+ ("log-time", optValue(time, "yes|no"), "Include time in log messages")
+ ("log-level", optValue(level,"yes|no"), "Include severity level in log messages")
+ ("log-source", optValue(source,"yes|no"), "Include source file:line in log messages")
+ ("log-thread", optValue(thread,"yes|no"), "Include thread ID in log messages")
+ ("log-function", optValue(function,"yes|no"), "Include function signature in log messages")
+ ("log-hires-timestamp", optValue(hiresTs,"yes|no"), "Use hi-resolution timestamps in log messages")
+ ("log-category", optValue(category,"yes|no"), "Include category in log messages")
+ ("log-prefix", optValue(prefix,"STRING"), "Prefix to prepend to all log messages")
+ ;
+ add(*sinkOptions);
+}
+
+Options::Options(const Options &o) :
+ qpid::Options(o.name),
+ argv0(o.argv0),
+ name(o.name),
+ selectors(o.selectors),
+ deselectors(o.deselectors),
+ time(o.time),
+ level(o.level),
+ thread(o.thread),
+ source(o.source),
+ function(o.function),
+ hiresTs(o.hiresTs),
+ category(o.category),
+ trace(o.trace),
+ prefix(o.prefix),
+ sinkOptions (SinkOptions::create(o.argv0))
+{
+ *sinkOptions = *o.sinkOptions;
+}
+
+Options& Options::operator=(const Options& x) {
+ if (this != &x) {
+ argv0 = x.argv0;
+ name = x.name;
+ selectors = x.selectors;
+ deselectors = x.deselectors;
+ time = x.time;
+ level= x.level;
+ thread = x.thread;
+ source = x.source;
+ function = x.function;
+ hiresTs = x.hiresTs;
+ category = x.category;
+ trace = x.trace;
+ prefix = x.prefix;
+ *sinkOptions = *x.sinkOptions;
+ }
+ return *this;
+}
+
+std::string getLevels()
+{
+ std::ostringstream levels;
+ levels << LevelTraits::name(Level(0));
+ for (int i = 1; i < LevelTraits::COUNT; ++i)
+ levels << " " << LevelTraits::name(Level(i));
+ return levels.str();
+}
+
+std::string getCategories()
+{
+ std::ostringstream categories;
+ categories << CategoryTraits::name(Category(0));
+ for (int i = 1; i < CategoryTraits::COUNT; ++i)
+ categories << " " << CategoryTraits::name(Category(i));
+ return categories.str();
+}
+
+}} // namespace qpid::log
diff --git a/qpid/cpp/src/qpid/log/Options.h b/qpid/cpp/src/qpid/log/Options.h
new file mode 100644
index 0000000000..ed534168e8
--- /dev/null
+++ b/qpid/cpp/src/qpid/log/Options.h
@@ -0,0 +1,57 @@
+#ifndef QPID_LOG_OPTIONS_H
+#define QPID_LOG_OPTIONS_H
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#include "qpid/Options.h"
+#include "qpid/CommonImportExport.h"
+#include "qpid/log/SinkOptions.h"
+#include <iosfwd>
+#include <memory>
+
+namespace qpid {
+namespace log {
+
+/** Logging options for config parser. */
+struct Options : public qpid::Options {
+ /** Pass argv[0] for use in syslog output */
+ QPID_COMMON_EXTERN Options(const std::string& argv0_=std::string(),
+ const std::string& name_="Logging options");
+ QPID_COMMON_EXTERN Options(const Options &);
+
+ QPID_COMMON_EXTERN Options& operator=(const Options&);
+
+ std::string argv0;
+ std::string name;
+ std::vector<std::string> selectors;
+ std::vector<std::string> deselectors;
+ bool time, level, thread, source, function, hiresTs, category;
+ bool trace;
+ std::string prefix;
+ std::auto_ptr<SinkOptions> sinkOptions;
+};
+
+/** Get a string list of the allowed levels */
+QPID_COMMON_EXTERN std::string getLevels();
+
+/** Get a string list of the allowed categories */
+QPID_COMMON_EXTERN std::string getCategories();
+
+}} // namespace qpid::log
+
+#endif /*!QPID_LOG_OPTIONS_H*/
diff --git a/qpid/cpp/src/qpid/log/OstreamOutput.cpp b/qpid/cpp/src/qpid/log/OstreamOutput.cpp
new file mode 100644
index 0000000000..9b6ec1f8aa
--- /dev/null
+++ b/qpid/cpp/src/qpid/log/OstreamOutput.cpp
@@ -0,0 +1,41 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/log/OstreamOutput.h"
+#include <stdexcept>
+
+using namespace std;
+
+namespace qpid {
+namespace log {
+
+OstreamOutput::OstreamOutput(std::ostream& o) : out(&o) {}
+
+OstreamOutput::OstreamOutput(const std::string& file)
+ : out(new ofstream(file.c_str(), ios_base::out | ios_base::app)),
+ mine(out)
+{
+ if (!out->good())
+ throw std::runtime_error("Can't open log file: "+file);
+}
+
+void OstreamOutput::log(const Statement&, const std::string& m) {
+ *out << m << flush;
+}
+
+}} // namespace qpid::log
diff --git a/qpid/cpp/src/qpid/log/OstreamOutput.h b/qpid/cpp/src/qpid/log/OstreamOutput.h
new file mode 100644
index 0000000000..12fd4ce425
--- /dev/null
+++ b/qpid/cpp/src/qpid/log/OstreamOutput.h
@@ -0,0 +1,41 @@
+#ifndef QPID_LOG_OSTREAMOUTPUT_H
+#define QPID_LOG_OSTREAMOUTPUT_H
+
+/*
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/log/Logger.h"
+#include <boost/scoped_ptr.hpp>
+#include <fstream>
+#include <ostream>
+
+namespace qpid {
+namespace log {
+
+/**
+ * OstreamOutput is a reusable logging sink that directs logging to a C++
+ * ostream.
+ */
+class OstreamOutput : public qpid::log::Logger::Output {
+public:
+ QPID_COMMON_EXTERN OstreamOutput(std::ostream& o);
+ QPID_COMMON_EXTERN OstreamOutput(const std::string& file);
+
+ virtual void log(const Statement&, const std::string& m);
+
+private:
+ std::ostream* out;
+ boost::scoped_ptr<std::ostream> mine;
+};
+
+}} // namespace qpid::log
+
+#endif /*!QPID_LOG_OSTREAMOUTPUT_H*/
diff --git a/qpid/cpp/src/qpid/log/Selector.cpp b/qpid/cpp/src/qpid/log/Selector.cpp
new file mode 100644
index 0000000000..a517cb34f4
--- /dev/null
+++ b/qpid/cpp/src/qpid/log/Selector.cpp
@@ -0,0 +1,237 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/log/Selector.h"
+#include "qpid/log/Options.h"
+#include <boost/bind.hpp>
+#include <algorithm>
+#include <string.h>
+
+namespace qpid {
+namespace log {
+
+using namespace std;
+
+const char LOG_SYMBOL_DISABLE ('!');
+const char LOG_SYMBOL_SEPERATOR(':');
+const char LOG_SYMBOL_AND_ABOVE('+');
+const char LOG_SYMBOL_AND_BELOW('-');
+
+//
+// Parse an enable or disable entry into usable fields.
+// Throws if 'level' field is not recognized.
+//
+SelectorElement::SelectorElement(const std::string cliEntry) :
+ level(qpid::log::debug),
+ category(qpid::log::unspecified),
+ isDisable(false),
+ isCategory(false),
+ isLevelAndAbove(false),
+ isLevelAndBelow(false)
+{
+ if (cliEntry.empty())
+ return;
+ std::string working(cliEntry);
+ if (LOG_SYMBOL_DISABLE == working[0]) {
+ isDisable = true;
+ working = working.substr(1);
+ }
+ size_t c=working.find(LOG_SYMBOL_SEPERATOR);
+ if (c==string::npos) {
+ levelStr=working;
+ } else {
+ levelStr=working.substr(0,c);
+ patternStr=working.substr(c+1);
+ }
+ if (!levelStr.empty()) {
+ if (levelStr[levelStr.size()-1]==LOG_SYMBOL_AND_ABOVE) {
+ isLevelAndAbove = true;
+ levelStr = levelStr.substr(0, levelStr.size()-1);
+ } else if (levelStr[levelStr.size()-1]==LOG_SYMBOL_AND_BELOW) {
+ isLevelAndBelow = true;
+ levelStr = levelStr.substr(0, levelStr.size()-1);
+ }
+ }
+ level = LevelTraits::level(levelStr); // throws if bad level name
+ isCategory = CategoryTraits::isCategory(patternStr);
+ if (isCategory) {
+ category = CategoryTraits::category(patternStr);
+ }
+}
+
+// Empty selector
+Selector::Selector() {
+ reset();
+}
+
+
+// Selector from options
+Selector::Selector(const Options& opt){
+ reset();
+ for_each(opt.selectors.begin(), opt.selectors.end(),
+ boost::bind(&Selector::enable, this, _1));
+ for_each(opt.deselectors.begin(), opt.deselectors.end(),
+ boost::bind(&Selector::disable, this, _1));
+}
+
+
+// Selector from single level
+Selector::Selector(Level l, const std::string& s) {
+ reset();
+ enable(l, s);
+}
+
+
+// Selector from single enable
+Selector::Selector(const std::string& selector) {
+ reset();
+ enable(selector);
+}
+
+
+/**
+ * Process a single CLI --log-enable option
+ */
+void Selector::enable(const string& enableStr) {
+ if (enableStr.empty())
+ return;
+ SelectorElement se(enableStr);
+ if (se.isDisable) {
+ // Disable statements are allowed in an enable string as a convenient
+ // way to process management strings that have enable/disable mixed.
+ disable(enableStr);
+ } else if (se.isLevelAndAbove) {
+ for (int lvl = se.level; lvl < LevelTraits::COUNT; ++lvl) {
+ if (se.isCategory) {
+ enableFlags[lvl][se.category] = true;
+ } else {
+ enable(Level(lvl), se.patternStr);
+ }
+ }
+ } else if (se.isLevelAndBelow) {
+ for (int lvl = se.level; lvl >= 0; --lvl) {
+ if (se.isCategory) {
+ enableFlags[lvl][se.category] = true;
+ } else {
+ enable(Level(lvl), se.patternStr);
+ }
+ }
+ } else {
+ if (se.isCategory) {
+ enableFlags[se.level][se.category] = true;
+ } else {
+ enable(se.level, se.patternStr);
+ }
+ }
+}
+
+void Selector::disable(const string& disableStr) {
+ if (disableStr.empty())
+ return;
+ SelectorElement se(disableStr);
+ if (se.isLevelAndAbove) {
+ for (int lvl = se.level; lvl < LevelTraits::COUNT; ++lvl) {
+ if (se.isCategory) {
+ disableFlags[lvl][se.category] = true;
+ } else {
+ disable(Level(lvl), se.patternStr);
+ }
+ }
+ } else if (se.isLevelAndBelow) {
+ for (int lvl = se.level; lvl >= 0; --lvl) {
+ if (se.isCategory) {
+ disableFlags[lvl][se.category] = true;
+ } else {
+ disable(Level(lvl), se.patternStr);
+ }
+ }
+ } else {
+ if (se.isCategory) {
+ disableFlags[se.level][se.category] = true;
+ } else {
+ disable(se.level, se.patternStr);
+ }
+ }
+}
+
+
+/**
+* Enable/disable messages with level in levels where the file
+* name contains substring.
+*/
+void Selector::enable(Level level, const std::string& substring) {
+ enabledFunctions[level].push_back(substring);
+}
+
+
+void Selector::disable(Level level, const std::string& substring) {
+ disabledFunctions[level].push_back(substring);
+}
+
+
+void Selector::reset() {
+ // Initialize fields in a Selector that are not automatically set
+ for (int lt = 0; lt < LevelTraits::COUNT; ++lt)
+ for (int ct = 0; ct < CategoryTraits::COUNT; ++ct)
+ enableFlags[lt][ct] = disableFlags[lt][ct] = false;
+}
+
+
+bool Selector::lookupFuncName(Level level, const char* function, FunctionNameTable& table) {
+ const char* functionEnd = function+::strlen(function);
+ for (std::vector<std::string>::iterator i=table[level].begin();
+ i != table[level].end();
+ ++i)
+ {
+ if (std::search(function, functionEnd, i->begin(), i->end()) != functionEnd)
+ return true;
+ }
+ return false;
+}
+
+
+bool Selector::isEnabled(Level level, const char* function) {
+ return lookupFuncName(level, function, enabledFunctions);
+}
+
+bool Selector::isDisabled(Level level, const char* function) {
+ return lookupFuncName(level, function, disabledFunctions);
+}
+
+//
+// isEnabled
+//
+// Determines if all the fields in this Selector enable or disable a
+// level/function/category set from an actual QPID_LOG Statement.
+//
+bool Selector::isEnabled(Level level, const char* function, Category category) {
+ if (level==critical)
+ return true; // critical cannot be disabled
+ if (isDisabled(level, function))
+ return false; // Disabled by function name
+ if (disableFlags[level][category])
+ return false; // Disabled by category name
+ if (isEnabled(level, function))
+ return true; // Enabled by function name
+ if (enableFlags[level][category])
+ return true; // Enabled by category name
+ else
+ return false; // Unspecified defaults to disabled
+}
+
+}} // namespace qpid::log
diff --git a/qpid/cpp/src/qpid/log/Selector.h b/qpid/cpp/src/qpid/log/Selector.h
new file mode 100644
index 0000000000..1d025e9646
--- /dev/null
+++ b/qpid/cpp/src/qpid/log/Selector.h
@@ -0,0 +1,99 @@
+#ifndef SELECTOR_H
+#define SELECTOR_H
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/log/Statement.h"
+#include "qpid/CommonImportExport.h"
+#include <vector>
+
+namespace qpid {
+namespace log {
+struct Options;
+
+/**
+ * SelectorElement parses a cli/mgmt enable/disable entry into usable fields
+ * where cliEntry = [!]LEVEL[+-][:PATTERN]
+ */
+struct SelectorElement {
+ QPID_COMMON_EXTERN SelectorElement(const std::string cliEntry);
+ std::string levelStr;
+ std::string patternStr;
+ Level level;
+ Category category;
+ bool isDisable;
+ bool isCategory;
+ bool isLevelAndAbove;
+ bool isLevelAndBelow;
+};
+
+/**
+ * A selector identifies the set of log messages to enable.
+ *
+ * Thread object unsafe, pass-by-value type.
+ */
+class Selector {
+ public:
+ /** Empty selector selects nothing */
+ QPID_COMMON_EXTERN Selector();
+
+ /** Set selector from Options */
+ QPID_COMMON_EXTERN Selector(const Options&);
+
+ /** Equavlient to: Selector s; s.enable(l, s) */
+ QPID_COMMON_EXTERN Selector(Level l, const std::string& s=std::string());
+
+ /** Selector from string */
+ QPID_COMMON_EXTERN Selector(const std::string& selector);
+
+ /** push option settings into runtime lookup structs */
+ QPID_COMMON_EXTERN void enable(const std::string& enableStr);
+ QPID_COMMON_EXTERN void disable(const std::string& disableStr);
+
+ /**
+ * Enable/disable messages with level in levels where the file
+ * name contains substring. Empty string matches all.
+ */
+ QPID_COMMON_EXTERN void enable(Level level, const std::string& substring=std::string());
+ QPID_COMMON_EXTERN void disable(Level level, const std::string& substring=std::string());
+
+ /** Tests to determine if function names are in enable/disable tables */
+ QPID_COMMON_EXTERN bool isEnabled(Level level, const char* function);
+ QPID_COMMON_EXTERN bool isDisabled(Level level, const char* function);
+
+ /** Test to determine if log Statement is enabled */
+ QPID_COMMON_EXTERN bool isEnabled(Level level, const char* function, Category category);
+
+ private:
+ typedef std::vector<std::string> FunctionNameTable [LevelTraits::COUNT];
+ FunctionNameTable enabledFunctions; // log function names explicitly enabled
+ FunctionNameTable disabledFunctions; // log function names explicitly disabled
+ bool enableFlags[LevelTraits::COUNT][CategoryTraits::COUNT];
+ bool disableFlags[LevelTraits::COUNT][CategoryTraits::COUNT];
+
+ bool lookupFuncName(Level level, const char* function, FunctionNameTable& table);
+ /** Reset the category enable flags */
+ QPID_COMMON_EXTERN void reset();
+};
+
+
+}} // namespace qpid::log
+
+
+#endif /*!SELECTOR_H*/
diff --git a/qpid/cpp/src/qpid/log/SinkOptions.h b/qpid/cpp/src/qpid/log/SinkOptions.h
new file mode 100644
index 0000000000..7ec2cfbc17
--- /dev/null
+++ b/qpid/cpp/src/qpid/log/SinkOptions.h
@@ -0,0 +1,64 @@
+#ifndef QPID_LOG_SINKOPTIONS_H
+#define QPID_LOG_SINKOPTIONS_H
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/Options.h"
+#include <string>
+
+namespace qpid {
+namespace log {
+
+class Logger;
+
+/**
+ * Logging sink options.
+ *
+ * Most logging sink options will be platform-specific, even if some are
+ * duplicated. The range of platforms to which this code may be ported
+ * can't be assumed to all have C++ iostreams or files. Thus, this class
+ * is primarily for implementing in a platform-specific way.
+ */
+struct SinkOptions : public qpid::Options {
+
+ // Create a platform's SinkOptions. Pass argv0 as the program name,
+ // useful for syslog-type logging.
+ static SinkOptions *create(const std::string& argv0=std::string());
+
+ SinkOptions(const std::string& name="Logging sink options")
+ : qpid::Options(name)
+ {}
+ virtual ~SinkOptions() {}
+
+ virtual SinkOptions& operator=(const SinkOptions&) = 0;
+
+ // This allows the caller to indicate that there's no normal outputs
+ // available. For example, when running as a daemon. In these cases, the
+ // platform's "syslog"-type output should replace the default stderr
+ // unless some other sink has been selected.
+ virtual void detached(void) = 0;
+
+ // The Logger acting on these options calls setup() to request any
+ // Sinks be set up and fed back to the logger.
+ virtual void setup(Logger *logger) = 0;
+};
+
+}} // namespace qpid::log
+
+#endif /*!QPID_LOG_OPTIONS_H*/
diff --git a/qpid/cpp/src/qpid/log/Statement.cpp b/qpid/cpp/src/qpid/log/Statement.cpp
new file mode 100644
index 0000000000..86b069fd91
--- /dev/null
+++ b/qpid/cpp/src/qpid/log/Statement.cpp
@@ -0,0 +1,218 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/log/Statement.h"
+#include "qpid/log/Logger.h"
+#include <boost/bind.hpp>
+#include <stdexcept>
+#include <algorithm>
+#include <list>
+#include <ctype.h>
+
+namespace qpid {
+namespace log {
+
+namespace {
+struct NonPrint { bool operator()(unsigned char c) { return !isprint(c) && !isspace(c); } };
+
+const char hex[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+std::string quote(const std::string& str) {
+ NonPrint nonPrint;
+ size_t n = std::count_if(str.begin(), str.end(), nonPrint);
+ if (n==0) return str;
+ std::string ret;
+ ret.reserve(str.size()+3*n); // Avoid extra allocations.
+ for (std::string::const_iterator i = str.begin(); i != str.end(); ++i) {
+ if (nonPrint(*i)) {
+ ret.push_back('\\');
+ ret.push_back('x');
+ ret.push_back(hex[((*i) >> 4)&0xf]);
+ ret.push_back(hex[(*i) & 0xf]);
+ }
+ else ret.push_back(*i);
+ }
+ return ret;
+}
+}
+
+//
+// Instance of name hints
+//
+class CategoryFileNameHints {
+public:
+ CategoryFileNameHints(){
+ hintList.push_back(std::make_pair("AsynchIo", network));
+ hintList.push_back(std::make_pair("TCP", network));
+ hintList.push_back(std::make_pair("epoll", network));
+ hintList.push_back(std::make_pair("Pollable", network));
+ hintList.push_back(std::make_pair("Socket", network));
+
+ hintList.push_back(std::make_pair("Sasl", security));
+ hintList.push_back(std::make_pair("Ssl", security));
+ hintList.push_back(std::make_pair("Acl", security));
+ hintList.push_back(std::make_pair("acl", security));
+ hintList.push_back(std::make_pair("cyrus", security));
+
+ hintList.push_back(std::make_pair("amqp_", protocol));
+ hintList.push_back(std::make_pair("framing", protocol));
+
+ hintList.push_back(std::make_pair("management", management));
+ hintList.push_back(std::make_pair("qmf", management));
+ hintList.push_back(std::make_pair("console", management));
+ hintList.push_back(std::make_pair("Management", management));
+
+ hintList.push_back(std::make_pair("cluster", ha));
+ hintList.push_back(std::make_pair("qpid/ha", ha));
+ hintList.push_back(std::make_pair("qpid\\ha", ha));
+ hintList.push_back(std::make_pair("replication", ha));
+ hintList.push_back(std::make_pair("ClusterSafe", ha));
+
+ hintList.push_back(std::make_pair("broker", broker));
+ hintList.push_back(std::make_pair("SessionState",broker));
+ hintList.push_back(std::make_pair("DataDir", broker));
+ hintList.push_back(std::make_pair("qpidd", broker));
+ hintList.push_back(std::make_pair("xml", broker));
+ hintList.push_back(std::make_pair("QpidBroker", broker));
+
+ hintList.push_back(std::make_pair("store", store));
+
+ hintList.push_back(std::make_pair("assert", system));
+ hintList.push_back(std::make_pair("Exception", system));
+ hintList.push_back(std::make_pair("sys", system));
+ hintList.push_back(std::make_pair("SCM", system));
+
+ hintList.push_back(std::make_pair("tests", test));
+
+ hintList.push_back(std::make_pair("messaging", messaging));
+ hintList.push_back(std::make_pair("types", messaging));
+
+ hintList.push_back(std::make_pair("client", client));
+ }
+
+ static Category categoryOf(const char*const fName);
+
+private:
+ std::list<std::pair<const char* const, Category> > hintList;
+};
+
+static CategoryFileNameHints filenameHints;
+
+Category CategoryFileNameHints::categoryOf(const char* const fName) {
+ for (std::list<std::pair<const char* const, Category> >::iterator
+ it = filenameHints.hintList.begin();
+ it != filenameHints.hintList.end();
+ ++it) {
+ if (strstr(fName, (const char* const)it->first) != 0) {
+ return it->second;
+ }
+ }
+ return unspecified;
+}
+
+
+void Statement::categorize(Statement& s) {
+ // given a statement and it's category
+ // if the category is Unspecified then try to find a
+ // better category based on the path and file name.
+ if (s.category == log::unspecified) {
+ s.category = CategoryFileNameHints::categoryOf(s.file);
+ } else {
+ // already has a category so leave it alone
+ }
+}
+
+
+void Statement::log(const std::string& message) {
+ Logger::instance().log(*this, quote(message));
+}
+
+
+Statement::Initializer::Initializer(Statement& s) : statement(s) {
+ // QPID-3891:
+ // From the given BOOST_CURRENT_FUNCTION name extract only the
+ // namespace-qualified-functionName, skipping return and calling args.
+ // Given:
+ // <possible return value type> qpid::name::space::Function(args)
+ // Return:
+ // "qpid::name::space::Function".
+ if (s.function) {
+ const char* end = s.function + strlen(s.function);
+ const char* fEnd = std::find(s.function, end, '(');
+ typedef std::reverse_iterator<const char*> Reverse;
+ const char* fBegin = find(Reverse(fEnd), Reverse(s.function), ' ').base();
+ size_t n = fEnd - fBegin;
+ char* name = new char[n+1];
+ std::copy(fBegin, fEnd, name);
+ name[n] = '\0';
+ s.function = name;
+ }
+ Statement::categorize(s);
+ Logger::instance().add(s);
+}
+
+Statement::Initializer::~Initializer() {
+ delete[] const_cast<char*>(statement.function);
+ statement.function = 0;
+}
+
+
+namespace {
+const char* names[LevelTraits::COUNT] = {
+ "trace", "debug", "info", "notice", "warning", "error", "critical"
+};
+
+const char* catNames[CategoryTraits::COUNT] = {
+ "Security", "Broker", "Management", "Protocol", "System", "HA", "Messaging",
+ "Store", "Network", "Test", "Client", "Application", "Model", "Unspecified"
+};
+
+} // namespace
+Level LevelTraits::level(const char* name) {
+ for (int i =0; i < LevelTraits::COUNT; ++i) {
+ if (strcmp(names[i], name)==0)
+ return Level(i);
+ }
+ throw std::runtime_error(std::string("Invalid log level name: ")+name);
+}
+
+const char* LevelTraits::name(Level l) {
+ return names[l];
+}
+
+bool CategoryTraits::isCategory(const std::string& name) {
+ for (int i =0; i < CategoryTraits::COUNT; ++i) {
+ if (strcmp(catNames[i], name.c_str())==0)
+ return true;
+ }
+ return false;
+}
+
+Category CategoryTraits::category(const char* name) {
+ for (int i =0; i < CategoryTraits::COUNT; ++i) {
+ if (strcmp(catNames[i], name)==0)
+ return Category(i);
+ }
+ throw std::runtime_error(std::string("Invalid log category name: ")+name);
+}
+
+const char* CategoryTraits::name(Category c) {
+ return catNames[c];
+}
+
+}} // namespace qpid::log
diff --git a/qpid/cpp/src/qpid/log/Statement.h b/qpid/cpp/src/qpid/log/Statement.h
new file mode 100644
index 0000000000..410ffaaa3d
--- /dev/null
+++ b/qpid/cpp/src/qpid/log/Statement.h
@@ -0,0 +1,244 @@
+#ifndef STATEMENT_H
+#define STATEMENT_H
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/Msg.h"
+#include "qpid/CommonImportExport.h"
+#include <boost/current_function.hpp>
+
+namespace qpid {
+namespace log {
+
+/** Debugging severity levels
+ * - trace: High-volume debugging messages.
+ * - debug: Debugging messages.
+ * - info: Informational messages.
+ * - notice: Normal but significant condition.
+ * - warning: Warn of a possible problem.
+ * - error: A definite error has occured.
+ * - critical: System in danger of severe failure.
+ */
+enum Level { trace, debug, info, notice, warning, error, critical };
+struct LevelTraits {
+ static const int COUNT=critical+1;
+
+ /** Get level from string name.
+ *@exception if name invalid.
+ */
+ static Level level(const char* name);
+
+ /** Get level from string name.
+ *@exception if name invalid.
+ */
+ static Level level(const std::string& name) {
+ return level(name.c_str());
+ }
+
+ /** String name of level */
+ static const char* name(Level);
+};
+
+/** Formal message categories
+ * https://issues.apache.org/jira/browse/QPID-3902
+ *
+ * Category Source code directory
+ * -------- ---------------------
+ * Security acl ssl gssapi sasl cyrus
+ * Broker broker
+ * Management agent console qmf
+ * Protocol amqp_0_10 framing
+ * System log sys types xml thread mutex fork pipe time ...
+ * HA cluster ha replication
+ * Messaging messaging
+ * Client client
+ * Store store
+ * Network tcp rdma AsynchIO socket epoll
+ * Test
+ * External_application <no directory - signifies log message from non qpid application code>
+ * Model <not related to a directory>
+ * Unspecified <must be last in enum>
+ */
+enum Category { security, broker, management, protocol, system, ha, messaging,
+ store, network, test, client, external_application, model, unspecified };
+struct CategoryTraits {
+ static const int COUNT=unspecified+1;
+
+ /** Test if given name is a Category name
+ */
+ static bool isCategory(const std::string& name);
+
+ /** Get category from string name
+ * @exception if name invalid.
+ */
+ static Category category(const char* name);
+
+ /** Get category from string name.
+ * @exception if name invalid.
+ */
+ static Category category(const std::string& name) {
+ return category(name.c_str());
+ }
+
+ /** String name of category */
+ static const char* name(Category);
+};
+
+ /** POD struct representing a logging statement in source code. */
+struct Statement {
+ bool enabled;
+ const char* file;
+ int line;
+ const char* function;
+ Level level;
+ Category category;
+
+ QPID_COMMON_EXTERN void log(const std::string& message);
+ QPID_COMMON_EXTERN static void categorize(Statement& s);
+
+ struct Initializer {
+ QPID_COMMON_EXTERN Initializer(Statement& s);
+ QPID_COMMON_EXTERN ~Initializer();
+ Statement& statement;
+ };
+};
+
+///@internal static initializer for a Statement.
+#define QPID_LOG_STATEMENT_INIT_CAT(LEVEL, CATEGORY) \
+{ 0, __FILE__, __LINE__, BOOST_CURRENT_FUNCTION, (::qpid::log::LEVEL), \
+(::qpid::log::CATEGORY) }
+
+
+///@internal static initializer for a Statement with unspecified category
+#define QPID_LOG_STATEMENT_INIT(LEVEL) \
+QPID_LOG_STATEMENT_INIT_CAT ( LEVEL , unspecified )
+
+/**
+ * Like QPID_LOG but computes an additional boolean test expression
+ * to determine if the message should be logged. Evaluation of both
+ * the test and message expressions occurs only if the requested log level
+ * is enabled.
+ *@param LEVEL severity Level for message, should be one of:
+ * debug, info, notice, warning, error, critical. NB no qpid::log:: prefix.
+ *@param TEST message is logged only if expression TEST evaluates to true.
+ *@param MESSAGE any object with an @eostream operator<<, or a sequence
+ * like of ostreamable objects separated by @e<<.
+ */
+#define QPID_LOG_IF(LEVEL, TEST, MESSAGE) \
+ do { \
+ using ::qpid::log::Statement; \
+ static Statement stmt_= QPID_LOG_STATEMENT_INIT(LEVEL); \
+ static Statement::Initializer init_(stmt_); \
+ if (stmt_.enabled && (TEST)) \
+ stmt_.log(::qpid::Msg() << MESSAGE); \
+ } while(0)
+
+/**
+ * Line QPID_LOG_IF but with the additional specification of a category.
+ * @param CATEGORY message category.
+ */
+#define QPID_LOG_IF_CAT(LEVEL, CATEGORY, TEST, MESSAGE) \
+ do { \
+ using ::qpid::log::Statement; \
+ static Statement stmt_= QPID_LOG_STATEMENT_INIT_CAT(LEVEL, CATEGORY); \
+ static Statement::Initializer init_(stmt_); \
+ if (stmt_.enabled && (TEST)) \
+ stmt_.log(::qpid::Msg() << MESSAGE); \
+ } while(0)
+
+/**
+ * FLAG must be a boolean variable. Assigns FLAG to true iff logging
+ * is enabled for LEVEL in the calling context. Use when extra
+ * support code is needed to generate log messages, to ensure that it
+ * is only run if the logging level is enabled.
+ * e.g.
+ * bool logWarning;
+ * QPID_LOG_TEST(warning, logWarning);
+ * if (logWarning) { do stuff needed for warning log messages }
+ */
+#define QPID_LOG_TEST(LEVEL, FLAG) \
+ do { \
+ using ::qpid::log::Statement; \
+ static Statement stmt_= QPID_LOG_STATEMENT_INIT(LEVEL); \
+ static Statement::Initializer init_(stmt_); \
+ FLAG = stmt_.enabled; \
+ } while(0)
+
+ /**
+ * FLAG must be a boolean variable. Assigns FLAG to true iff logging
+ * is enabled for LEVEL in the calling context. Use when extra
+ * support code is needed to generate log messages, to ensure that it
+ * is only run if the logging level is enabled.
+ * e.g.
+ * bool logWarning;
+ * QPID_LOG_TEST_CAT(warning, System, logWarning);
+ * if (logWarning) { do stuff needed for warning log messages }
+ */
+ #define QPID_LOG_TEST_CAT(LEVEL, CATEGORY, FLAG) \
+ do { \
+ using ::qpid::log::Statement; \
+ static Statement stmt_= QPID_LOG_STATEMENT_INIT_CAT(LEVEL, CATEGORY); \
+ static Statement::Initializer init_(stmt_); \
+ FLAG = stmt_.enabled; \
+ } while(0)
+
+/**
+ * Macro for log statements. Example of use:
+ * @code
+ * QPID_LOG(debug, "There are " << foocount << " foos in the bar.");
+ * QPID_LOG(error, boost::format("Dohickey %s exploded") % dohicky.name());
+ * @endcode
+ * Using QPID_LOG implies a category of Unspecified.
+ *
+ * You can subscribe to log messages by level, by component, by filename
+ * or a combination @see Configuration.
+
+ *@param LEVEL severity Level for message, should be one of:
+ * debug, info, notice, warning, error, critical. NB no qpid::log:: prefix.
+ *@param MESSAGE any object with an @eostream operator<<, or a sequence
+ * like of ostreamable objects separated by @e<<.
+ */
+#define QPID_LOG(LEVEL, MESSAGE) QPID_LOG_IF(LEVEL, true, MESSAGE);
+
+/**
+ * Macro for log statements. Example of use:
+ * @code
+ * QPID_LOG_CAT(debug, System, "There are " << foocount << " foos in the bar.");
+ * QPID_LOG_CAT(error, System, boost::format("Dohickey %s exploded") % dohicky.name());
+ * @endcode
+ * Using QPID_LOG_CAT requires the specification of a category.
+ *
+ * You can subscribe to log messages by level, by component, by filename
+ * or a combination @see Configuration.
+ *
+ *@param LEVEL severity Level for message, should be one of:
+ * debug, info, notice, warning, error, critical. NB no qpid::log:: prefix.
+ *@param CATEGORY basic Category for the message.
+ *@param MESSAGE any object with an @eostream operator<<, or a sequence
+ * like of ostreamable objects separated by @e<<.
+ */
+#define QPID_LOG_CAT(LEVEL, CATEGORY, MESSAGE) QPID_LOG_IF_CAT(LEVEL, CATEGORY, true, MESSAGE);
+
+}} // namespace qpid::log
+
+
+
+
+#endif /*!STATEMENT_H*/
+
diff --git a/qpid/cpp/src/qpid/log/posix/SinkOptions.cpp b/qpid/cpp/src/qpid/log/posix/SinkOptions.cpp
new file mode 100644
index 0000000000..210022551e
--- /dev/null
+++ b/qpid/cpp/src/qpid/log/posix/SinkOptions.cpp
@@ -0,0 +1,222 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/log/posix/SinkOptions.h"
+#include "qpid/log/SinkOptions.h"
+#include "qpid/log/Logger.h"
+#include "qpid/log/OstreamOutput.h"
+#include "qpid/OptionsTemplates.h"
+#include "qpid/memory.h"
+#include "qpid/Exception.h"
+
+#include <iostream>
+#include <map>
+#include <string>
+#include <syslog.h>
+
+#include <boost/lexical_cast.hpp>
+
+using std::string;
+using qpid::Exception;
+
+namespace qpid {
+
+template po::value_semantic* create_value(log::posix::SyslogFacility& val, const std::string& arg);
+
+namespace log {
+namespace posix {
+
+namespace {
+
+// SyslogFacilities maps from syslog values to the text equivalents.
+class SyslogFacilities {
+public:
+ typedef std::map<string, int> ByName;
+ typedef std::map<int, string> ByValue;
+
+ SyslogFacilities() {
+ struct NameValue { const char* name; int value; };
+ NameValue nameValue[] = {
+ { "AUTH", LOG_AUTH },
+#ifdef HAVE_LOG_AUTHPRIV
+ { "AUTHPRIV", LOG_AUTHPRIV },
+#endif
+ { "CRON", LOG_CRON },
+ { "DAEMON", LOG_DAEMON },
+#ifdef HAVE_LOG_FTP
+ { "FTP", LOG_FTP },
+#endif
+ { "KERN", LOG_KERN },
+ { "LOCAL0", LOG_LOCAL0 },
+ { "LOCAL1", LOG_LOCAL1 },
+ { "LOCAL2", LOG_LOCAL2 },
+ { "LOCAL3", LOG_LOCAL3 },
+ { "LOCAL4", LOG_LOCAL4 },
+ { "LOCAL5", LOG_LOCAL5 },
+ { "LOCAL6", LOG_LOCAL6 },
+ { "LOCAL7", LOG_LOCAL7 },
+ { "LPR", LOG_LPR },
+ { "MAIL", LOG_MAIL },
+ { "NEWS", LOG_NEWS },
+ { "SYSLOG", LOG_SYSLOG },
+ { "USER", LOG_USER },
+ { "UUCP", LOG_UUCP }
+ };
+ for (size_t i = 0; i < sizeof(nameValue)/sizeof(nameValue[0]); ++i) {
+ byName.insert(ByName::value_type(nameValue[i].name, nameValue[i].value));
+ // Recognise with and without LOG_ prefix e.g.: AUTH and LOG_AUTH
+ byName.insert(ByName::value_type(string("LOG_")+nameValue[i].name, nameValue[i].value));
+ byValue.insert(ByValue::value_type(nameValue[i].value, string("LOG_")+nameValue[i].name));
+ }
+ }
+
+ int value(const string& name) const {
+ string key(name);
+ std::transform(key.begin(), key.end(), key.begin(), ::toupper);
+ ByName::const_iterator i = byName.find(key);
+ if (i == byName.end())
+ throw Exception("Not a valid syslog facility: " + name);
+ return i->second;
+ }
+
+ string name(int value) const {
+ ByValue::const_iterator i = byValue.find(value);
+ if (i == byValue.end())
+ throw Exception("Not a valid syslog value: " + boost::lexical_cast<string>(value));
+ return i->second;
+ }
+
+ private:
+ ByName byName;
+ ByValue byValue;
+};
+
+// 'priorities' maps qpid log levels to syslog priorities. They are in
+// order of qpid log levels and must map to:
+// "trace", "debug", "info", "notice", "warning", "error", "critical"
+static int priorities[qpid::log::LevelTraits::COUNT] = {
+ LOG_DEBUG, LOG_DEBUG, LOG_INFO, LOG_NOTICE,
+ LOG_WARNING, LOG_ERR, LOG_CRIT
+};
+
+std::string basename(const std::string path) {
+ size_t i = path.find_last_of('/');
+ return path.substr((i == std::string::npos) ? 0 : i+1);
+}
+
+} // namespace
+
+std::ostream& operator<<(std::ostream& o, const SyslogFacility& f) {
+ return o << SyslogFacilities().name(f.value);
+}
+
+std::istream& operator>>(std::istream& i, SyslogFacility& f) {
+ std::string name;
+ i >> name;
+ f.value = SyslogFacilities().value(name);
+ return i;
+}
+
+class SyslogOutput : public qpid::log::Logger::Output {
+public:
+ SyslogOutput(const std::string& logName, const SyslogFacility& logFacility)
+ : name(logName), facility(logFacility.value)
+ {
+ ::openlog(name.c_str(), LOG_PID, facility);
+ }
+
+ virtual ~SyslogOutput() {
+ ::closelog();
+ }
+
+ virtual void log(const Statement& s, const std::string& m)
+ {
+ syslog(priorities[s.level], "%s", m.c_str());
+ }
+
+private:
+ std::string name;
+ int facility;
+};
+
+SinkOptions::SinkOptions(const std::string& argv0)
+ : qpid::log::SinkOptions(),
+ logToStderr(true),
+ logToStdout(false),
+ logToSyslog(false),
+ syslogName(basename(argv0)),
+ syslogFacility(LOG_DAEMON) {
+
+ addOptions()
+ ("log-to-stderr", optValue(logToStderr, "yes|no"), "Send logging output to stderr")
+ ("log-to-stdout", optValue(logToStdout, "yes|no"), "Send logging output to stdout")
+ ("log-to-file", optValue(logFile, "FILE"), "Send log output to FILE.")
+ ("log-to-syslog", optValue(logToSyslog, "yes|no"), "Send logging output to syslog;\n\tcustomize using --syslog-name and --syslog-facility")
+ ("syslog-name", optValue(syslogName, "NAME"), "Name to use in syslog messages")
+ ("syslog-facility", optValue(syslogFacility,"LOG_XXX"), "Facility to use in syslog messages")
+ ;
+
+}
+
+qpid::log::SinkOptions& SinkOptions::operator=(const qpid::log::SinkOptions& rhs) {
+ const SinkOptions *prhs = dynamic_cast<const SinkOptions*>(&rhs);
+ if (this != prhs) {
+ logToStderr = prhs->logToStderr;
+ logToStdout = prhs->logToStdout;
+ logToSyslog = prhs->logToSyslog;
+ logFile = prhs->logFile;
+ syslogName = prhs->syslogName;
+ syslogFacility.value = prhs->syslogFacility.value;
+ }
+ return *this;
+}
+
+void SinkOptions::detached(void) {
+ if (logToStderr && !logToStdout && !logToSyslog && logFile.empty()) {
+ logToStderr = false;
+ logToSyslog = true;
+ }
+}
+
+// The Logger acting on these options calls setup() to request any
+// Sinks be set up and fed back to the logger.
+void SinkOptions::setup(qpid::log::Logger *logger) {
+ if (logToStderr)
+ logger->output(make_auto_ptr<qpid::log::Logger::Output>
+ (new qpid::log::OstreamOutput(std::clog)));
+ if (logToStdout)
+ logger->output(make_auto_ptr<qpid::log::Logger::Output>
+ (new qpid::log::OstreamOutput(std::cout)));
+
+ if (logFile.length() > 0)
+ logger->output(make_auto_ptr<qpid::log::Logger::Output>
+ (new qpid::log::OstreamOutput(logFile)));
+
+ if (logToSyslog)
+ logger->output(make_auto_ptr<qpid::log::Logger::Output>
+ (new SyslogOutput(syslogName, syslogFacility)));
+
+}
+
+} // namespace qpid::log::posix
+
+SinkOptions* SinkOptions::create(const std::string& argv0) {
+ return new qpid::log::posix::SinkOptions (argv0);
+}
+
+}} // namespace qpid::log
diff --git a/qpid/cpp/src/qpid/log/posix/SinkOptions.h b/qpid/cpp/src/qpid/log/posix/SinkOptions.h
new file mode 100644
index 0000000000..d929c29025
--- /dev/null
+++ b/qpid/cpp/src/qpid/log/posix/SinkOptions.h
@@ -0,0 +1,64 @@
+#ifndef QPID_LOG_POSIX_SINKOPTIONS_H
+#define QPID_LOG_POSIX_SINKOPTIONS_H
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/log/SinkOptions.h"
+#include <string>
+
+namespace qpid {
+namespace log {
+namespace posix {
+
+/**
+ * Provides a type that can be passed to << and >> operators to convert
+ * syslog facility values to/from strings.
+ */
+struct SyslogFacility {
+ int value;
+ SyslogFacility(int i=0) : value(i) {}
+};
+
+struct SinkOptions : public qpid::log::SinkOptions {
+ SinkOptions(const std::string& argv0);
+ virtual ~SinkOptions() {}
+
+ virtual qpid::log::SinkOptions& operator=(const qpid::log::SinkOptions& rhs);
+
+ // This allows the caller to indicate that there's no normal outputs
+ // available. For example, when running as a daemon. In these cases, the
+ // platform's "syslog"-type output should replace the default stderr
+ // unless some other sink has been selected.
+ virtual void detached(void);
+
+ // The Logger acting on these options calls setup() to request any
+ // Sinks be set up and fed back to the logger.
+ virtual void setup(qpid::log::Logger *logger);
+
+ bool logToStderr;
+ bool logToStdout;
+ bool logToSyslog;
+ std::string logFile;
+ std::string syslogName;
+ SyslogFacility syslogFacility;
+};
+
+}}} // namespace qpid::log::posix
+
+#endif /*!QPID_LOG_POSIX_SINKOPTIONS_H*/
diff --git a/qpid/cpp/src/qpid/log/windows/SinkOptions.cpp b/qpid/cpp/src/qpid/log/windows/SinkOptions.cpp
new file mode 100644
index 0000000000..0c74bea64e
--- /dev/null
+++ b/qpid/cpp/src/qpid/log/windows/SinkOptions.cpp
@@ -0,0 +1,148 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/log/windows/SinkOptions.h"
+#include "qpid/log/SinkOptions.h"
+#include "qpid/log/Logger.h"
+#include "qpid/log/OstreamOutput.h"
+#include "qpid/memory.h"
+#include "qpid/Exception.h"
+#include <iostream>
+#include <map>
+#include <string>
+
+#include <windows.h>
+
+using qpid::Exception;
+
+namespace qpid {
+namespace log {
+namespace windows {
+
+namespace {
+
+// 'eventTypes' maps qpid log levels to Windows event types. They are in
+// order of qpid log levels and must map to:
+// "trace", "debug", "info", "notice", "warning", "error", "critical"
+static int eventTypes[qpid::log::LevelTraits::COUNT] = {
+ EVENTLOG_INFORMATION_TYPE, /* trace */
+ EVENTLOG_INFORMATION_TYPE, /* debug */
+ EVENTLOG_INFORMATION_TYPE, /* info */
+ EVENTLOG_INFORMATION_TYPE, /* notice */
+ EVENTLOG_WARNING_TYPE, /* warning */
+ EVENTLOG_ERROR_TYPE, /* error */
+ EVENTLOG_ERROR_TYPE /* critical */
+};
+
+} // namespace
+
+class EventLogOutput : public qpid::log::Logger::Output {
+public:
+ EventLogOutput(const std::string& /*sourceName*/) : logHandle(0)
+ {
+ logHandle = OpenEventLog(0, "Application");
+ }
+
+ virtual ~EventLogOutput() {
+ if (logHandle)
+ CloseEventLog(logHandle);
+ }
+
+ virtual void log(const Statement& s, const std::string& m)
+ {
+ if (logHandle) {
+ const char *msg = m.c_str();
+ ReportEvent(logHandle,
+ eventTypes[s.level],
+ 0, /* category unused */
+ 0, /* event id */
+ 0, /* user security id */
+ 1, /* number of strings */
+ 0, /* no event-specific data */
+ &msg,
+ 0);
+ }
+ }
+
+private:
+ HANDLE logHandle;
+};
+
+SinkOptions::SinkOptions(const std::string& /*argv0*/)
+ : qpid::log::SinkOptions(),
+ logToStderr(true),
+ logToStdout(false),
+ logToEventLog(false),
+ eventSource("Application")
+{
+ addOptions()
+ ("log-to-stderr", optValue(logToStderr, "yes|no"), "Send logging output to stderr")
+ ("log-to-stdout", optValue(logToStdout, "yes|no"), "Send logging output to stdout")
+ ("log-to-file", optValue(logFile, "FILE"), "Send log output to FILE.")
+ ("log-to-eventlog", optValue(logToEventLog, "yes|no"), "Send logging output to event log;\n\tcustomize using --syslog-name and --syslog-facility")
+ ("eventlog-source-name", optValue(eventSource, "Application"), "Event source to log to")
+ ;
+
+}
+
+qpid::log::SinkOptions& SinkOptions::operator=(const qpid::log::SinkOptions& rhs) {
+ const SinkOptions *prhs = dynamic_cast<const SinkOptions*>(&rhs);
+ if (this != prhs) {
+ logToStderr = prhs->logToStderr;
+ logToStdout = prhs->logToStdout;
+ logToEventLog = prhs->logToEventLog;
+ eventSource = prhs->eventSource;
+ logFile = prhs->logFile;
+ }
+ return *this;
+}
+
+void SinkOptions::detached(void) {
+ if (logToStderr && !logToStdout && !logToEventLog) {
+ logToStderr = false;
+ logToEventLog = true;
+ }
+}
+
+// The Logger acting on these options calls setup() to request any
+// Sinks be set up and fed back to the logger.
+void SinkOptions::setup(qpid::log::Logger *logger) {
+ if (logToStderr)
+ logger->output(make_auto_ptr<qpid::log::Logger::Output>
+ (new qpid::log::OstreamOutput(std::clog)));
+ if (logToStdout)
+ logger->output(make_auto_ptr<qpid::log::Logger::Output>
+ (new qpid::log::OstreamOutput(std::cout)));
+
+ if (logFile.length() > 0)
+ logger->output(make_auto_ptr<qpid::log::Logger::Output>
+ (new qpid::log::OstreamOutput(logFile)));
+
+ if (logToEventLog)
+ logger->output(make_auto_ptr<qpid::log::Logger::Output>
+ (new EventLogOutput(eventSource)));
+
+}
+
+} // namespace windows
+
+SinkOptions* SinkOptions::create(const std::string& argv0) {
+ return new qpid::log::windows::SinkOptions (argv0);
+}
+
+}} // namespace qpid::log
diff --git a/qpid/cpp/src/qpid/log/windows/SinkOptions.h b/qpid/cpp/src/qpid/log/windows/SinkOptions.h
new file mode 100644
index 0000000000..f270c504a2
--- /dev/null
+++ b/qpid/cpp/src/qpid/log/windows/SinkOptions.h
@@ -0,0 +1,54 @@
+#ifndef QPID_LOG_WINDOWS_SINKOPTIONS_H
+#define QPID_LOG_WINDOWS_SINKOPTIONS_H
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/log/SinkOptions.h"
+#include <string>
+
+namespace qpid {
+namespace log {
+namespace windows {
+
+struct QPID_COMMON_CLASS_EXTERN SinkOptions : public qpid::log::SinkOptions {
+ QPID_COMMON_EXTERN SinkOptions(const std::string& argv0);
+ virtual ~SinkOptions() {}
+
+ QPID_COMMON_EXTERN virtual qpid::log::SinkOptions& operator=(const qpid::log::SinkOptions& rhs);
+
+ // This allows the caller to indicate that there's no normal outputs
+ // available. For example, when running as a service. In these cases, the
+ // platform's "syslog"-type output should replace the default stderr
+ // unless some other sink has been selected.
+ QPID_COMMON_EXTERN virtual void detached(void);
+
+ // The Logger acting on these options calls setup() to request any
+ // Sinks be set up and fed back to the logger.
+ QPID_COMMON_EXTERN virtual void setup(qpid::log::Logger *logger);
+
+ bool logToStderr;
+ bool logToStdout;
+ bool logToEventLog;
+ std::string eventSource;
+ std::string logFile;
+};
+
+}}} // namespace qpid::log::windows
+
+#endif /*!QPID_LOG_WINDOWS_SINKOPTIONS_H*/
diff --git a/qpid/cpp/src/qpid/management/Args.h b/qpid/cpp/src/qpid/management/Args.h
new file mode 100644
index 0000000000..5d1cb7e01d
--- /dev/null
+++ b/qpid/cpp/src/qpid/management/Args.h
@@ -0,0 +1,44 @@
+#ifndef _Args_
+#define _Args_
+
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+
+namespace qpid {
+namespace management {
+
+class Args
+{
+ public:
+
+ virtual ~Args (void) = 0;
+
+};
+
+inline Args::~Args (void) {}
+
+class ArgsNone : public Args
+{
+};
+
+}}
+
+
+#endif /*!_Args_*/
diff --git a/qpid/cpp/src/qpid/management/Buffer.cpp b/qpid/cpp/src/qpid/management/Buffer.cpp
new file mode 100644
index 0000000000..0ad6e9a851
--- /dev/null
+++ b/qpid/cpp/src/qpid/management/Buffer.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/management/Buffer.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/amqp_0_10/Codecs.h"
+
+using namespace std;
+
+namespace qpid {
+namespace management {
+
+Buffer::Buffer(char* data, uint32_t size) : impl(new framing::Buffer(data, size)) {}
+Buffer::~Buffer() { delete impl; }
+void Buffer::reset() { impl->reset(); }
+uint32_t Buffer::available() { return impl->available(); }
+uint32_t Buffer::getSize() { return impl->getSize(); }
+uint32_t Buffer::getPosition() { return impl->getPosition(); }
+void Buffer::setPosition(uint32_t p) { impl->setPosition(p); }
+char* Buffer::getPointer() { return impl->getPointer(); }
+void Buffer::putOctet(uint8_t i) { impl->putOctet(i); }
+void Buffer::putShort(uint16_t i) { impl->putShort(i); }
+void Buffer::putLong(uint32_t i) { impl->putLong(i); }
+void Buffer::putLongLong(uint64_t i) { impl->putLongLong(i); }
+void Buffer::putInt8(int8_t i) { impl->putInt8(i); }
+void Buffer::putInt16(int16_t i) { impl->putInt16(i); }
+void Buffer::putInt32(int32_t i) { impl->putInt32(i); }
+void Buffer::putInt64(int64_t i) { impl->putInt64(i); }
+void Buffer::putFloat(float i) { impl->putFloat(i); }
+void Buffer::putDouble(double i) { impl->putDouble(i); }
+void Buffer::putBin128(const uint8_t* i) { impl->putBin128(i); }
+uint8_t Buffer::getOctet() { return impl->getOctet(); }
+uint16_t Buffer::getShort() { return impl->getShort(); }
+uint32_t Buffer::getLong() { return impl->getLong(); }
+uint64_t Buffer::getLongLong() { return impl->getLongLong(); }
+int8_t Buffer:: getInt8() { return impl-> getInt8(); }
+int16_t Buffer::getInt16() { return impl->getInt16(); }
+int32_t Buffer::getInt32() { return impl->getInt32(); }
+int64_t Buffer::getInt64() { return impl->getInt64(); }
+float Buffer::getFloat() { return impl->getFloat(); }
+double Buffer::getDouble() { return impl->getDouble(); }
+void Buffer::putShortString(const string& i) { impl->putShortString(i); }
+void Buffer::putMediumString(const string& i) { impl->putMediumString(i); }
+void Buffer::putLongString(const string& i) { impl->putLongString(i); }
+void Buffer::getShortString(string& i) { impl->getShortString(i); }
+void Buffer::getMediumString(string& i) { impl->getMediumString(i); }
+void Buffer::getLongString(string& i) { impl->getLongString(i); }
+void Buffer::getBin128(uint8_t* i) { impl->getBin128(i); }
+void Buffer::putRawData(const string& i) { impl->putRawData(i); }
+void Buffer::getRawData(string& s, uint32_t size) { impl->getRawData(s, size); }
+void Buffer::putRawData(const uint8_t* data, size_t size) { impl->putRawData(data, size); }
+void Buffer::getRawData(uint8_t* data, size_t size) { impl->getRawData(data, size); }
+
+void Buffer::putMap(const types::Variant::Map& i)
+{
+ string encoded;
+ amqp_0_10::MapCodec::encode(i, encoded);
+ impl->putRawData(encoded);
+}
+
+void Buffer::putList(const types::Variant::List& i)
+{
+ string encoded;
+ amqp_0_10::ListCodec::encode(i, encoded);
+ impl->putRawData(encoded);
+}
+
+void Buffer::getMap(types::Variant::Map& map)
+{
+ string encoded;
+ uint32_t saved = impl->getPosition();
+ uint32_t length = impl->getLong();
+ impl->setPosition(saved);
+ impl->getRawData(encoded, length + sizeof(uint32_t));
+ amqp_0_10::MapCodec::decode(encoded, map);
+}
+
+void Buffer::getList(types::Variant::List& list)
+{
+ string encoded;
+ uint32_t saved = impl->getPosition();
+ uint32_t length = impl->getLong();
+ impl->setPosition(saved);
+ impl->getRawData(encoded, length + sizeof(uint32_t));
+ amqp_0_10::ListCodec::decode(encoded, list);
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/management/Buffer.h b/qpid/cpp/src/qpid/management/Buffer.h
new file mode 100644
index 0000000000..1ac52bf276
--- /dev/null
+++ b/qpid/cpp/src/qpid/management/Buffer.h
@@ -0,0 +1,105 @@
+#ifndef _Management_Buffer_
+#define _Management_Buffer_
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/CommonImportExport.h"
+#include "qpid/types/Exception.h"
+#include "qpid/types/Variant.h"
+#include <string>
+
+namespace qpid {
+namespace framing {
+ class Buffer;
+}
+
+namespace management {
+
+struct OutOfBounds : qpid::types::Exception {
+ OutOfBounds() : qpid::types::Exception(std::string("Out of Bounds")) {}
+};
+
+
+/**
+ * This class is a wrapper around qpid::framing::Buffer that does not include any dependencies
+ * from boost or from qpid::framing.
+ */
+class Buffer
+{
+public:
+ QPID_COMMON_EXTERN Buffer(char* data=0, uint32_t size=0);
+ QPID_COMMON_EXTERN ~Buffer();
+
+ QPID_COMMON_EXTERN void reset();
+
+ QPID_COMMON_EXTERN uint32_t available();
+ QPID_COMMON_EXTERN uint32_t getSize();
+ QPID_COMMON_EXTERN uint32_t getPosition();
+ QPID_COMMON_EXTERN void setPosition(uint32_t);
+ QPID_COMMON_EXTERN char* getPointer();
+
+ QPID_COMMON_EXTERN void putOctet(uint8_t i);
+ QPID_COMMON_EXTERN void putShort(uint16_t i);
+ QPID_COMMON_EXTERN void putLong(uint32_t i);
+ QPID_COMMON_EXTERN void putLongLong(uint64_t i);
+ QPID_COMMON_EXTERN void putInt8(int8_t i);
+ QPID_COMMON_EXTERN void putInt16(int16_t i);
+ QPID_COMMON_EXTERN void putInt32(int32_t i);
+ QPID_COMMON_EXTERN void putInt64(int64_t i);
+ QPID_COMMON_EXTERN void putFloat(float f);
+ QPID_COMMON_EXTERN void putDouble(double f);
+ QPID_COMMON_EXTERN void putBin128(const uint8_t* b);
+
+ QPID_COMMON_EXTERN uint8_t getOctet();
+ QPID_COMMON_EXTERN uint16_t getShort();
+ QPID_COMMON_EXTERN uint32_t getLong();
+ QPID_COMMON_EXTERN uint64_t getLongLong();
+ QPID_COMMON_EXTERN int8_t getInt8();
+ QPID_COMMON_EXTERN int16_t getInt16();
+ QPID_COMMON_EXTERN int32_t getInt32();
+ QPID_COMMON_EXTERN int64_t getInt64();
+ QPID_COMMON_EXTERN float getFloat();
+ QPID_COMMON_EXTERN double getDouble();
+
+ QPID_COMMON_EXTERN void putShortString(const std::string& s);
+ QPID_COMMON_EXTERN void putMediumString(const std::string& s);
+ QPID_COMMON_EXTERN void putLongString(const std::string& s);
+ QPID_COMMON_EXTERN void getShortString(std::string& s);
+ QPID_COMMON_EXTERN void getMediumString(std::string& s);
+ QPID_COMMON_EXTERN void getLongString(std::string& s);
+ QPID_COMMON_EXTERN void getBin128(uint8_t* b);
+
+ QPID_COMMON_EXTERN void putMap(const types::Variant::Map& map);
+ QPID_COMMON_EXTERN void putList(const types::Variant::List& list);
+ QPID_COMMON_EXTERN void getMap(types::Variant::Map& map);
+ QPID_COMMON_EXTERN void getList(types::Variant::List& list);
+
+ QPID_COMMON_EXTERN void putRawData(const std::string& s);
+ QPID_COMMON_EXTERN void getRawData(std::string& s, uint32_t size);
+
+ QPID_COMMON_EXTERN void putRawData(const uint8_t* data, size_t size);
+ QPID_COMMON_EXTERN void getRawData(uint8_t* data, size_t size);
+
+private:
+ framing::Buffer* impl;
+};
+
+}} // namespace qpid::management
+
+#endif
diff --git a/qpid/cpp/src/qpid/management/ConnectionSettings.cpp b/qpid/cpp/src/qpid/management/ConnectionSettings.cpp
new file mode 100644
index 0000000000..1421a26867
--- /dev/null
+++ b/qpid/cpp/src/qpid/management/ConnectionSettings.cpp
@@ -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.
+ *
+ */
+#include "qpid/management/ConnectionSettings.h"
+#include "qpid/Version.h"
+
+qpid::management::ConnectionSettings::ConnectionSettings() :
+ protocol("tcp"),
+ host("localhost"),
+ port(5672),
+ locale("en_US"),
+ heartbeat(0),
+ maxChannels(32767),
+ maxFrameSize(65535),
+ bounds(2),
+ tcpNoDelay(false),
+ service(qpid::saslName),
+ minSsf(0),
+ maxSsf(256)
+{}
+
+qpid::management::ConnectionSettings::~ConnectionSettings() {}
+
diff --git a/qpid/cpp/src/qpid/management/ConnectionSettings.h b/qpid/cpp/src/qpid/management/ConnectionSettings.h
new file mode 100644
index 0000000000..b631ffa658
--- /dev/null
+++ b/qpid/cpp/src/qpid/management/ConnectionSettings.h
@@ -0,0 +1,118 @@
+#ifndef _management_ConnectionSettings_h
+#define _management_ConnectionSettings_h
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/CommonImportExport.h"
+#include "qpid/types/Variant.h"
+#include <string>
+
+namespace qpid {
+namespace management {
+
+/**
+ * Settings for a Connection.
+ */
+struct ConnectionSettings {
+
+ QPID_COMMON_EXTERN ConnectionSettings();
+ QPID_COMMON_EXTERN virtual ~ConnectionSettings();
+
+ /**
+ * The protocol used for the connection (defaults to 'tcp')
+ */
+ std::string protocol;
+
+ /**
+ * The host (or ip address) to connect to (defaults to 'localhost').
+ */
+ std::string host;
+ /**
+ * The port to connect to (defaults to 5672).
+ */
+ uint16_t port;
+ /**
+ * Allows an AMQP 'virtual host' to be specified for the
+ * connection.
+ */
+ std::string virtualhost;
+
+ /**
+ * The username to use when authenticating the connection. If not
+ * specified the current users login is used if available.
+ */
+ std::string username;
+ /**
+ * The password to use when authenticating the connection.
+ */
+ std::string password;
+ /**
+ * The SASL mechanism to use when authenticating the connection;
+ * the options are currently PLAIN or ANONYMOUS.
+ */
+ std::string mechanism;
+ /**
+ * Allows a locale to be specified for the connection.
+ */
+ std::string locale;
+ /**
+ * Allows a heartbeat frequency to be specified
+ */
+ uint16_t heartbeat;
+ /**
+ * The maximum number of channels that the client will request for
+ * use on this connection.
+ */
+ uint16_t maxChannels;
+ /**
+ * The maximum frame size that the client will request for this
+ * connection.
+ */
+ uint16_t maxFrameSize;
+ /**
+ * Limit the size of the connections send buffer . The buffer
+ * is limited to bounds * maxFrameSize.
+ */
+ unsigned int bounds;
+ /**
+ * If true, TCP_NODELAY will be set for the connection.
+ */
+ bool tcpNoDelay;
+ /**
+ * SASL service name
+ */
+ std::string service;
+ /**
+ * Minimum acceptable strength of any SASL negotiated security
+ * layer. 0 means no security layer required.
+ */
+ unsigned int minSsf;
+ /**
+ * Maximum acceptable strength of any SASL negotiated security
+ * layer. 0 means no security layer allowed.
+ */
+ unsigned int maxSsf;
+};
+
+}}
+
+#endif
+
diff --git a/qpid/cpp/src/qpid/management/Manageable.cpp b/qpid/cpp/src/qpid/management/Manageable.cpp
new file mode 100644
index 0000000000..651215ffb5
--- /dev/null
+++ b/qpid/cpp/src/qpid/management/Manageable.cpp
@@ -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.
+//
+
+#include "qpid/management/Manageable.h"
+
+using namespace qpid::management;
+using std::string;
+
+string Manageable::StatusText (status_t status, string text)
+{
+ if ((status & STATUS_USER) == STATUS_USER)
+ return text;
+
+ switch (status)
+ {
+ case STATUS_OK : return "OK";
+ case STATUS_UNKNOWN_OBJECT : return "UnknownObject";
+ case STATUS_UNKNOWN_METHOD : return "UnknownMethod";
+ case STATUS_NOT_IMPLEMENTED : return "NotImplemented";
+ case STATUS_PARAMETER_INVALID : return "InvalidParameter";
+ case STATUS_FEATURE_NOT_IMPLEMENTED : return "FeatureNotImplemented";
+ case STATUS_FORBIDDEN : return "Forbidden";
+ }
+
+ return "??";
+}
+
+Manageable::status_t Manageable::ManagementMethod (uint32_t, Args&, std::string&)
+{
+ return STATUS_UNKNOWN_METHOD;
+}
+
+bool Manageable::AuthorizeMethod(uint32_t, Args&, const std::string&)
+{
+ return true;
+}
+
diff --git a/qpid/cpp/src/qpid/management/Manageable.h b/qpid/cpp/src/qpid/management/Manageable.h
new file mode 100644
index 0000000000..ede5c29e43
--- /dev/null
+++ b/qpid/cpp/src/qpid/management/Manageable.h
@@ -0,0 +1,81 @@
+#ifndef _Manageable_
+#define _Manageable_
+
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+#include "qpid/management/ManagementObject.h"
+#include "qpid/management/Args.h"
+#include <string>
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+namespace management {
+
+class QPID_COMMON_EXTERN Manageable
+{
+ public:
+
+ virtual ~Manageable(void) = 0;
+
+ // status_t is a type used to pass completion status from the method handler.
+ //
+ typedef uint32_t status_t;
+ static std::string StatusText(status_t status, std::string text = std::string());
+
+ static const status_t STATUS_OK = 0;
+ static const status_t STATUS_UNKNOWN_OBJECT = 1;
+ static const status_t STATUS_UNKNOWN_METHOD = 2;
+ static const status_t STATUS_NOT_IMPLEMENTED = 3;
+ static const status_t STATUS_PARAMETER_INVALID = 4;
+ static const status_t STATUS_FEATURE_NOT_IMPLEMENTED = 5;
+ static const status_t STATUS_FORBIDDEN = 6;
+ static const status_t STATUS_EXCEPTION = 7;
+ static const status_t STATUS_USER = 0x00010000;
+
+ // Every "Manageable" object must hold a reference to exactly one
+ // management object. This object is always of a class derived from
+ // the pure-virtual "ManagementObject".
+ //
+ // This accessor function returns a pointer to the management object.
+ //
+#ifdef _IN_QPID_BROKER
+ virtual ManagementObject::shared_ptr GetManagementObject() const = 0;
+#else
+ virtual ManagementObject* GetManagementObject() const = 0;
+#endif
+
+ // Every "Manageable" object must implement ManagementMethod. This
+ // function is called when a remote management client invokes a method
+ // on this object. The input and output arguments are specific to the
+ // method being called and must be down-cast to the appropriate sub class
+ // before use.
+ virtual status_t ManagementMethod(uint32_t methodId, Args& args, std::string& text);
+
+ // This optional method can be overridden to allow the agent application to
+ // authorize method invocations. Return true iff the authenticated user identified
+ // in userId us authorized to execute the method.
+ virtual bool AuthorizeMethod(uint32_t methodId, Args& args, const std::string& userId);
+};
+
+inline Manageable::~Manageable(void) {}
+
+}}
+
+#endif /*!_Manageable_*/
diff --git a/qpid/cpp/src/qpid/management/ManagementAgent.cpp b/qpid/cpp/src/qpid/management/ManagementAgent.cpp
new file mode 100644
index 0000000000..516babce61
--- /dev/null
+++ b/qpid/cpp/src/qpid/management/ManagementAgent.cpp
@@ -0,0 +1,2832 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+
+// NOTE on use of log levels: The criteria for using trace vs. debug
+// is to use trace for log messages that are generated for each
+// unbatched stats/props notification and debug for everything else.
+
+#include "qpid/management/ManagementAgent.h"
+#include "qpid/management/ManagementObject.h"
+#include "qpid/broker/DeliverableMessage.h"
+#include "qpid/log/Statement.h"
+#include "qpid/broker/Message.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/broker/amqp_0_10/MessageTransfer.h"
+#include "qpid/sys/Time.h"
+#include "qpid/sys/Timer.h"
+#include "qpid/sys/Thread.h"
+#include "qpid/sys/PollableQueue.h"
+#include "qpid/broker/Connection.h"
+#include "qpid/broker/AclModule.h"
+#include "qpid/broker/Protocol.h"
+#include "qpid/types/Variant.h"
+#include "qpid/types/Uuid.h"
+#include "qpid/framing/List.h"
+#include "qpid/amqp_0_10/Codecs.h"
+#include <list>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <typeinfo>
+
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+
+namespace qpid {
+namespace management {
+
+using boost::intrusive_ptr;
+using qpid::framing::Uuid;
+using qpid::types::Variant;
+using qpid::amqp_0_10::MapCodec;
+using qpid::amqp_0_10::ListCodec;
+using namespace qpid::framing;
+using namespace qpid::broker;
+using namespace qpid;
+using namespace std;
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+
+namespace {
+const size_t qmfV1BufferSize(65536);
+const string defaultVendorName("vendor");
+const string defaultProductName("product");
+
+// Create a valid binding key substring by
+// replacing all '.' chars with '_'
+const string keyifyNameStr(const string& name)
+{
+ string n2 = name;
+
+ size_t pos = n2.find('.');
+ while (pos != n2.npos) {
+ n2.replace(pos, 1, "_");
+ pos = n2.find('.', pos);
+ }
+ return n2;
+}
+
+struct ScopedManagementContext
+{
+ const Connection* context;
+
+ ScopedManagementContext(const Connection* p) : context(p)
+ {
+ if (p) setManagementExecutionContext(*p);
+ }
+
+ management::ObjectId getObjectId() const
+ {
+ return context ? context->getObjectId() : management::ObjectId();
+ }
+ std::string getUserId() const
+ {
+ return context ? context->getUserId() : std::string();
+ }
+ std::string getMgmtId() const
+ {
+ return context ? context->getMgmtId() : std::string();
+ }
+
+
+ ~ScopedManagementContext()
+ {
+ resetManagementExecutionContext();
+ }
+};
+
+typedef boost::function0<void> FireFunction;
+struct Periodic : public qpid::sys::TimerTask
+{
+ FireFunction fireFunction;
+ qpid::sys::Timer* timer;
+
+ Periodic (FireFunction f, qpid::sys::Timer* t, uint32_t seconds);
+ virtual ~Periodic ();
+ void fire ();
+};
+
+Periodic::Periodic (FireFunction f, qpid::sys::Timer* t, uint32_t _seconds)
+ : TimerTask(sys::Duration((_seconds ? _seconds : 1) * sys::TIME_SEC),
+ "ManagementAgent::periodicProcessing"),
+ fireFunction(f), timer(t) {}
+
+Periodic::~Periodic() {}
+
+void Periodic::fire()
+{
+ setupNextFire();
+ timer->add(this);
+ fireFunction();
+}
+
+}
+
+
+static Variant::Map mapEncodeSchemaId(const string& pname,
+ const string& cname,
+ const string& type,
+ const uint8_t *md5Sum)
+{
+ Variant::Map map_;
+
+ map_["_package_name"] = pname;
+ map_["_class_name"] = cname;
+ map_["_type"] = type;
+ map_["_hash"] = qpid::types::Uuid(md5Sum);
+ return map_;
+}
+
+
+ManagementAgent::RemoteAgent::~RemoteAgent ()
+{
+ QPID_LOG(debug, "Remote Agent removed bank=[" << brokerBank << "." << agentBank << "]");
+ if (mgmtObject != 0) {
+ mgmtObject->resourceDestroy();
+ agent.deleteObjectNow(mgmtObject->getObjectId());
+ mgmtObject.reset();
+ }
+}
+
+ManagementAgent::ManagementAgent (const bool qmfV1, const bool qmfV2) :
+ threadPoolSize(1), publish(true), interval(10), broker(0), timer(0), protocols(0),
+ startTime(sys::now()),
+ suppressed(false), disallowAllV1Methods(false),
+ vendorNameKey(defaultVendorName), productNameKey(defaultProductName),
+ qmf1Support(qmfV1), qmf2Support(qmfV2), maxReplyObjs(100)
+{
+ nextObjectId = 1;
+ brokerBank = 1;
+ bootSequence = 1;
+ nextRemoteBank = 10;
+ nextRequestSequence = 1;
+ clientWasAdded = false;
+ attrMap["_vendor"] = defaultVendorName;
+ attrMap["_product"] = defaultProductName;
+
+ memstat = _qmf::Memory::shared_ptr(new qmf::org::apache::qpid::broker::Memory(this, 0, "amqp-broker"));
+ addObject(memstat, "amqp-broker");
+}
+
+ManagementAgent::~ManagementAgent ()
+{
+ {
+ sys::Mutex::ScopedLock lock (userLock);
+
+ // Reset the shared pointers to exchanges. If this is not done now, the exchanges
+ // will stick around until dExchange and mExchange are implicitly destroyed (long
+ // after this destructor completes). Those exchanges hold references to management
+ // objects that will be invalid.
+ dExchange.reset();
+ mExchange.reset();
+ v2Topic.reset();
+ v2Direct.reset();
+
+ remoteAgents.clear();
+ }
+}
+
+void ManagementAgent::configure(const string& _dataDir, bool _publish, uint16_t _interval,
+ qpid::broker::Broker* _broker, int _threads)
+{
+ dataDir = _dataDir;
+ publish = _publish;
+ interval = _interval;
+ broker = _broker;
+ threadPoolSize = _threads;
+ ManagementObject::maxThreads = threadPoolSize;
+ sendQueue.reset(
+ new EventQueue(boost::bind(&ManagementAgent::sendEvents, this, _1), broker->getPoller()));
+ sendQueue->start();
+ timer = &broker->getTimer();
+ timer->add(new Periodic(boost::bind(&ManagementAgent::periodicProcessing, this), timer, interval));
+
+ protocols = &broker->getProtocolRegistry();
+ // Get from file or generate and save to file.
+ if (dataDir.empty())
+ {
+ uuid.generate();
+ QPID_LOG (info, "ManagementAgent has no data directory, generated new broker ID: "
+ << uuid);
+ }
+ else
+ {
+ string filename(dataDir + "/.mbrokerdata");
+ ifstream inFile(filename.c_str ());
+
+ if (inFile.good())
+ {
+ inFile >> uuid;
+ inFile >> bootSequence;
+ inFile >> nextRemoteBank;
+ inFile.close();
+ if (uuid.isNull()) {
+ uuid.generate();
+ QPID_LOG (info, "No stored broker ID found - ManagementAgent generated broker ID: " << uuid);
+ } else
+ QPID_LOG (info, "ManagementAgent restored broker ID: " << uuid);
+
+ // if sequence goes beyond a 12-bit field, skip zero and wrap to 1.
+ bootSequence++;
+ if (bootSequence & 0xF000)
+ bootSequence = 1;
+ writeData();
+ }
+ else
+ {
+ uuid.generate();
+ QPID_LOG (info, "ManagementAgent generated broker ID: " << uuid);
+ writeData();
+ }
+
+ QPID_LOG (debug, "ManagementAgent boot sequence: " << bootSequence);
+ }
+}
+
+void ManagementAgent::setName(const string& vendor, const string& product, const string& instance)
+{
+ if (vendor.find(':') != vendor.npos) {
+ throw Exception("vendor string cannot contain a ':' character.");
+ }
+ if (product.find(':') != product.npos) {
+ throw Exception("product string cannot contain a ':' character.");
+ }
+ attrMap["_vendor"] = vendor;
+ attrMap["_product"] = product;
+ string inst;
+ if (instance.empty()) {
+ if (uuid.isNull())
+ {
+ throw Exception("ManagementAgent::configure() must be called if default name is used.");
+ }
+ inst = uuid.str();
+ } else
+ inst = instance;
+
+ name_address = vendor + ":" + product + ":" + inst;
+ attrMap["_instance"] = inst;
+ attrMap["_name"] = name_address;
+
+ vendorNameKey = keyifyNameStr(vendor);
+ productNameKey = keyifyNameStr(product);
+ instanceNameKey = keyifyNameStr(inst);
+}
+
+
+void ManagementAgent::getName(string& vendor, string& product, string& instance)
+{
+ vendor = std::string(attrMap["_vendor"]);
+ product = std::string(attrMap["_product"]);
+ instance = std::string(attrMap["_instance"]);
+}
+
+
+const std::string& ManagementAgent::getAddress()
+{
+ return name_address;
+}
+
+
+void ManagementAgent::writeData ()
+{
+ string filename (dataDir + "/.mbrokerdata");
+ ofstream outFile (filename.c_str ());
+
+ if (outFile.good())
+ {
+ outFile << uuid << " " << bootSequence << " " << nextRemoteBank << endl;
+ outFile.close();
+ }
+}
+
+void ManagementAgent::setExchange(qpid::broker::Exchange::shared_ptr _mexchange,
+ qpid::broker::Exchange::shared_ptr _dexchange)
+{
+ mExchange = _mexchange;
+ dExchange = _dexchange;
+}
+
+void ManagementAgent::setExchangeV2(qpid::broker::Exchange::shared_ptr _texchange,
+ qpid::broker::Exchange::shared_ptr _dexchange)
+{
+ v2Topic = _texchange;
+ v2Direct = _dexchange;
+}
+
+void ManagementAgent::registerClass (const string& packageName,
+ const string& className,
+ uint8_t* md5Sum,
+ ManagementObject::writeSchemaCall_t schemaCall)
+{
+ sys::Mutex::ScopedLock lock(userLock);
+ PackageMap::iterator pIter = findOrAddPackageLH(packageName);
+ addClassLH(ManagementItem::CLASS_KIND_TABLE, pIter, className, md5Sum, schemaCall);
+}
+
+void ManagementAgent::registerEvent (const string& packageName,
+ const string& eventName,
+ uint8_t* md5Sum,
+ ManagementObject::writeSchemaCall_t schemaCall)
+{
+ sys::Mutex::ScopedLock lock(userLock);
+ PackageMap::iterator pIter = findOrAddPackageLH(packageName);
+ addClassLH(ManagementItem::CLASS_KIND_EVENT, pIter, eventName, md5Sum, schemaCall);
+}
+
+// Deprecated: V1 objects
+ObjectId ManagementAgent::addObject(ManagementObject::shared_ptr object, uint64_t persistId, bool persistent)
+{
+ uint16_t sequence;
+ uint64_t objectNum;
+
+ sys::Mutex::ScopedLock lock(addLock);
+ sequence = persistent ? 0 : bootSequence;
+ objectNum = persistId ? persistId : nextObjectId++;
+
+ ObjectId objId(0 /*flags*/, sequence, brokerBank, objectNum);
+ objId.setV2Key(*object); // let object generate the v2 key
+
+ object->setObjectId(objId);
+
+ newManagementObjects.push_back(object);
+ QPID_LOG(debug, "Management object (V1) added: " << objId.getV2Key());
+ return objId;
+}
+
+
+
+ObjectId ManagementAgent::addObject(ManagementObject::shared_ptr object,
+ const string& key,
+ bool persistent)
+{
+ uint16_t sequence;
+
+ sequence = persistent ? 0 : bootSequence;
+
+ ObjectId objId(0 /*flags*/, sequence, brokerBank);
+ if (key.empty()) {
+ objId.setV2Key(*object); // let object generate the key
+ } else {
+ objId.setV2Key(key);
+ }
+
+ object->setObjectId(objId);
+ {
+ sys::Mutex::ScopedLock lock(addLock);
+ newManagementObjects.push_back(object);
+ }
+ QPID_LOG(debug, "Management object added: " << objId.getV2Key());
+ return objId;
+}
+
+void ManagementAgent::raiseEvent(const ManagementEvent& event, severity_t severity)
+{
+ static const std::string severityStr[] = {
+ "emerg", "alert", "crit", "error", "warn",
+ "note", "info", "debug"
+ };
+ uint8_t sev = (severity == SEV_DEFAULT) ? event.getSeverity() : (uint8_t) severity;
+
+ if (qmf1Support) {
+ char buffer[qmfV1BufferSize];
+ Buffer outBuffer(buffer, qmfV1BufferSize);
+
+ encodeHeader(outBuffer, 'e');
+ outBuffer.putShortString(event.getPackageName());
+ outBuffer.putShortString(event.getEventName());
+ outBuffer.putBin128(event.getMd5Sum());
+ outBuffer.putLongLong(sys::Duration::FromEpoch());
+ outBuffer.putOctet(sev);
+ string sBuf;
+ event.encode(sBuf);
+ outBuffer.putRawData(sBuf);
+ sendBuffer(outBuffer, mExchange,
+ "console.event.1.0." + event.getPackageName() + "." + event.getEventName());
+ QPID_LOG(debug, "SEND raiseEvent (v1) class=" << event.getPackageName() << "." << event.getEventName());
+ }
+
+ if (qmf2Support) {
+ Variant::Map map_;
+ Variant::Map schemaId;
+ Variant::Map values;
+ Variant::Map headers;
+
+ map_["_schema_id"] = mapEncodeSchemaId(event.getPackageName(),
+ event.getEventName(),
+ "_event",
+ event.getMd5Sum());
+ event.mapEncode(values);
+ map_["_values"] = values;
+ map_["_timestamp"] = uint64_t(sys::Duration::FromEpoch());
+ map_["_severity"] = sev;
+
+ headers["method"] = "indication";
+ headers["qmf.opcode"] = "_data_indication";
+ headers["qmf.content"] = "_event";
+ headers["qmf.agent"] = name_address;
+
+ stringstream key;
+ key << "agent.ind.event." << keyifyNameStr(event.getPackageName())
+ << "." << keyifyNameStr(event.getEventName())
+ << "." << severityStr[sev]
+ << "." << vendorNameKey
+ << "." << productNameKey;
+ if (!instanceNameKey.empty())
+ key << "." << instanceNameKey;
+
+
+ string content;
+ Variant::List list_;
+ list_.push_back(map_);
+ ListCodec::encode(list_, content);
+ sendBuffer(content, "", headers, "amqp/list", v2Topic, key.str());
+ QPID_LOG(debug, "SEND raiseEvent (v2) class=" << event.getPackageName() << "." << event.getEventName());
+ }
+}
+
+void ManagementAgent::clientAdded (const string& routingKey)
+{
+ sys::Mutex::ScopedLock lock(userLock);
+
+ //
+ // If this routing key is not relevant to object updates, exit.
+ //
+ if ((routingKey.compare(0, 1, "#") != 0) &&
+ (routingKey.compare(0, 9, "console.#") != 0) &&
+ (routingKey.compare(0, 12, "console.obj.") != 0))
+ return;
+
+ //
+ // Mark local objects for full-update.
+ //
+ clientWasAdded = true;
+
+ //
+ // If the routing key is relevant for local objects only, don't involve
+ // any of the remote agents.
+ //
+ if (routingKey.compare(0, 39, "console.obj.*.*.org.apache.qpid.broker.") == 0)
+ return;
+
+ std::list<std::string> rkeys;
+
+ for (RemoteAgentMap::iterator aIter = remoteAgents.begin();
+ aIter != remoteAgents.end();
+ aIter++) {
+ rkeys.push_back(aIter->second->routingKey);
+ }
+
+ while (rkeys.size()) {
+ char localBuffer[16];
+ Buffer outBuffer(localBuffer, 16);
+
+ encodeHeader(outBuffer, 'x');
+ sendBuffer(outBuffer, dExchange, rkeys.front());
+ QPID_LOG(debug, "SEND ConsoleAddedIndication to=" << rkeys.front());
+ rkeys.pop_front();
+ }
+}
+
+void ManagementAgent::encodeHeader (Buffer& buf, uint8_t opcode, uint32_t seq)
+{
+ buf.putOctet ('A');
+ buf.putOctet ('M');
+ buf.putOctet ('2');
+ buf.putOctet (opcode);
+ buf.putLong (seq);
+}
+
+bool ManagementAgent::checkHeader (Buffer& buf, uint8_t *opcode, uint32_t *seq)
+{
+ uint8_t h1 = buf.getOctet();
+ uint8_t h2 = buf.getOctet();
+ uint8_t h3 = buf.getOctet();
+
+ *opcode = buf.getOctet();
+ *seq = buf.getLong();
+
+ return h1 == 'A' && h2 == 'M' && h3 == '2';
+}
+
+void ManagementAgent::sendBuffer(Buffer& buf,
+ qpid::broker::Exchange::shared_ptr exchange,
+ const string& routingKey)
+{
+ if (suppressed) {
+ QPID_LOG(debug, "Suppressing management message to " << routingKey);
+ return;
+ }
+ if (exchange.get() == 0) return;
+
+ intrusive_ptr<qpid::broker::amqp_0_10::MessageTransfer> transfer(new qpid::broker::amqp_0_10::MessageTransfer());
+ AMQFrame method((MessageTransferBody(ProtocolVersion(), exchange->getName (), 0, 0)));
+ AMQFrame header((AMQHeaderBody()));
+ AMQFrame content((AMQContentBody()));
+
+ size_t length = buf.getPosition();
+ buf.reset();
+ content.castBody<AMQContentBody>()->decode(buf, length);
+
+ method.setEof(false);
+ header.setBof(false);
+ header.setEof(false);
+ content.setBof(false);
+
+ transfer->getFrames().append(method);
+ transfer->getFrames().append(header);
+
+ MessageProperties* props =
+ transfer->getFrames().getHeaders()->get<MessageProperties>(true);
+ props->setContentLength(length);
+
+ DeliveryProperties* dp =
+ transfer->getFrames().getHeaders()->get<DeliveryProperties>(true);
+ dp->setRoutingKey(routingKey);
+
+ transfer->getFrames().append(content);
+ transfer->setIsManagementMessage(true);
+ Message msg(transfer, transfer);
+ sendQueue->push(make_pair(exchange, msg));
+ buf.reset();
+}
+
+
+void ManagementAgent::sendBuffer(Buffer& buf,
+ const string& exchange,
+ const string& routingKey)
+{
+ qpid::broker::Exchange::shared_ptr ex(broker->getExchanges().get(exchange));
+ if (ex.get() != 0)
+ sendBuffer(buf, ex, routingKey);
+}
+
+
+void ManagementAgent::sendBuffer(const string& data,
+ const string& cid,
+ const Variant::Map& headers,
+ const string& content_type,
+ qpid::broker::Exchange::shared_ptr exchange,
+ const string& routingKey,
+ uint64_t ttl_msec)
+{
+ Variant::Map::const_iterator i;
+
+ if (suppressed) {
+ QPID_LOG(debug, "Suppressing management message to " << routingKey);
+ return;
+ }
+ if (exchange.get() == 0) return;
+
+ intrusive_ptr<qpid::broker::amqp_0_10::MessageTransfer> transfer(new qpid::broker::amqp_0_10::MessageTransfer);
+ AMQFrame method((MessageTransferBody(ProtocolVersion(), exchange->getName (), 0, 0)));
+ AMQFrame header((AMQHeaderBody()));
+ AMQFrame content((AMQContentBody(data)));
+
+ method.setEof(false);
+ header.setBof(false);
+ header.setEof(false);
+ content.setBof(false);
+
+ transfer->getFrames().append(method);
+ transfer->getFrames().append(header);
+
+ MessageProperties* props =
+ transfer->getFrames().getHeaders()->get<MessageProperties>(true);
+ props->setContentLength(data.length());
+ if (!cid.empty()) {
+ props->setCorrelationId(cid);
+ }
+ props->setContentType(content_type);
+ props->setAppId("qmf2");
+
+ for (i = headers.begin(); i != headers.end(); ++i) {
+ props->getApplicationHeaders().setString(i->first, i->second.asString());
+ }
+
+ DeliveryProperties* dp =
+ transfer->getFrames().getHeaders()->get<DeliveryProperties>(true);
+ dp->setRoutingKey(routingKey);
+ if (ttl_msec) {
+ dp->setTtl(ttl_msec);
+ }
+ transfer->getFrames().append(content);
+ transfer->computeRequiredCredit();
+ transfer->setIsManagementMessage(true);
+ transfer->computeExpiration();
+ Message msg(transfer, transfer);
+
+ sendQueue->push(make_pair(exchange, msg));
+}
+
+
+void ManagementAgent::sendBuffer(const string& data,
+ const string& cid,
+ const Variant::Map& headers,
+ const string& content_type,
+ const string& exchange,
+ const string& routingKey,
+ uint64_t ttl_msec)
+{
+ qpid::broker::Exchange::shared_ptr ex(broker->getExchanges().get(exchange));
+ if (ex.get() != 0)
+ sendBuffer(data, cid, headers, content_type, ex, routingKey, ttl_msec);
+}
+
+
+/** Objects that have been added since the last periodic poll are temporarily
+ * saved in the newManagementObjects list. This allows objects to be
+ * added without needing to block on the userLock (objectLock is used instead).
+ * These new objects need to be integrated into the object database
+ * (managementObjects) *before* they can be properly managed. This routine
+ * performs the integration.
+ *
+ * Note well: objects on the newManagementObjects list may have been
+ * marked as "deleted", and, possibly re-added. This would result in
+ * duplicate object ids. To avoid clashes, don't put deleted objects
+ * into the active object database.
+ */
+void ManagementAgent::moveNewObjects()
+{
+ sys::Mutex::ScopedLock lock(addLock);
+ sys::Mutex::ScopedLock objLock (objectLock);
+ while (!newManagementObjects.empty()) {
+ ManagementObject::shared_ptr object = newManagementObjects.back();
+ newManagementObjects.pop_back();
+
+ if (object->isDeleted()) {
+ DeletedObject::shared_ptr dptr(new DeletedObject(object, qmf1Support, qmf2Support));
+ pendingDeletedObjs[dptr->getKey()].push_back(dptr);
+ } else { // add to active object list, check for duplicates.
+ ObjectId oid = object->getObjectId();
+ ManagementObjectMap::iterator destIter = managementObjects.find(oid);
+ if (destIter != managementObjects.end()) {
+ // duplicate found. It is OK if the old object has been marked
+ // deleted, just replace the old with the new.
+ ManagementObject::shared_ptr oldObj = destIter->second;
+ if (!oldObj->isDeleted()) {
+ // Duplicate non-deleted objects? This is a user error - oids must be unique.
+ // for now, leak the old object (safer than deleting - may still be referenced)
+ // and complain loudly...
+ QPID_LOG(error, "Detected two management objects with the same identifier: " << oid);
+ oldObj->resourceDestroy();
+ }
+ DeletedObject::shared_ptr dptr(new DeletedObject(oldObj, qmf1Support, qmf2Support));
+ pendingDeletedObjs[dptr->getKey()].push_back(dptr);
+ // QPID-3666: be sure to replace the -index- also, as non-key members of
+ // the index object may be different for the new object! So erase the
+ // entry, rather than []= assign here:
+ managementObjects.erase(destIter);
+ }
+ managementObjects[oid] = object;
+ }
+ }
+}
+
+void ManagementAgent::periodicProcessing (void)
+{
+#define HEADROOM 4096
+ sys::Mutex::ScopedLock lock (userLock);
+ debugSnapshot("Management agent periodic processing");
+ string routingKey;
+ string sBuf;
+
+ moveNewObjects();
+
+ //
+ // If we're publishing updates, get the latest memory statistics and uptime now
+ //
+ if (publish) {
+ uint64_t uptime = sys::Duration(startTime, sys::now());
+ boost::dynamic_pointer_cast<_qmf::Broker>(broker->GetManagementObject())->set_uptime(uptime);
+ qpid::sys::MemStat::loadMemInfo(memstat.get());
+ }
+
+ //
+ // Use a copy of the management object map to avoid holding the objectLock
+ //
+ ManagementObjectVector localManagementObjects;
+ {
+ sys::Mutex::ScopedLock objLock(objectLock);
+ std::transform(managementObjects.begin(), managementObjects.end(),
+ std::back_inserter(localManagementObjects),
+ boost::bind(&ManagementObjectMap::value_type::second, _1));
+ }
+
+ //
+ // Clear the been-here flag on all objects in the map.
+ //
+ for (ManagementObjectVector::iterator iter = localManagementObjects.begin();
+ iter != localManagementObjects.end();
+ iter++) {
+ ManagementObject::shared_ptr object = *iter;
+ object->setFlags(0);
+ if (clientWasAdded) {
+ object->setForcePublish(true);
+ }
+ }
+
+ clientWasAdded = false;
+
+ // first send the pending deletes before sending updates. This prevents a
+ // "false delete" scenario: if an object was deleted then re-added during
+ // the last poll cycle, it will have a delete entry and an active entry.
+ // if we sent the active update first, _then_ the delete update, clients
+ // would incorrectly think the object was deleted. See QPID-2997
+ //
+ bool objectsDeleted = moveDeletedObjects();
+ PendingDeletedObjsMap localPendingDeletedObjs;
+ {
+ sys::Mutex::ScopedLock objLock(objectLock);
+ localPendingDeletedObjs.swap(pendingDeletedObjs);
+ }
+
+ //
+ // If we are not publishing updates, just clear the pending deletes. There's no
+ // need to tell anybody.
+ //
+ if (!publish)
+ localPendingDeletedObjs.clear();
+
+ ResizableBuffer msgBuffer(qmfV1BufferSize);
+ if (!localPendingDeletedObjs.empty()) {
+ for (PendingDeletedObjsMap::iterator mIter = localPendingDeletedObjs.begin();
+ mIter != localPendingDeletedObjs.end();
+ mIter++) {
+ std::string packageName;
+ std::string className;
+ msgBuffer.reset();
+ uint32_t v1Objs = 0;
+ uint32_t v2Objs = 0;
+ Variant::List list_;
+
+ size_t pos = mIter->first.find(":");
+ packageName = mIter->first.substr(0, pos);
+ className = mIter->first.substr(pos+1);
+
+ for (DeletedObjectList::iterator lIter = mIter->second.begin();
+ lIter != mIter->second.end(); lIter++) {
+ msgBuffer.makeAvailable(HEADROOM); // Make sure there's buffer space.
+ std::string oid = (*lIter)->objectId;
+ if (!(*lIter)->encodedV1Config.empty()) {
+ encodeHeader(msgBuffer, 'c');
+ msgBuffer.putRawData((*lIter)->encodedV1Config);
+ QPID_LOG(trace, "Deleting V1 properties " << oid
+ << " len=" << (*lIter)->encodedV1Config.size());
+ v1Objs++;
+ }
+ if (!(*lIter)->encodedV1Inst.empty()) {
+ encodeHeader(msgBuffer, 'i');
+ msgBuffer.putRawData((*lIter)->encodedV1Inst);
+ QPID_LOG(trace, "Deleting V1 statistics " << oid
+ << " len=" << (*lIter)->encodedV1Inst.size());
+ v1Objs++;
+ }
+ if (v1Objs >= maxReplyObjs) {
+ v1Objs = 0;
+ stringstream key;
+ key << "console.obj.1.0." << packageName << "." << className;
+ size_t contentSize = msgBuffer.getPosition();
+ sendBuffer(msgBuffer, mExchange, key.str());
+ QPID_LOG(debug, "SEND V1 Multicast ContentInd V1 (delete) to="
+ << key.str() << " len=" << contentSize);
+ }
+
+ if (!(*lIter)->encodedV2.empty()) {
+ QPID_LOG(trace, "Deleting V2 " << "map=" << (*lIter)->encodedV2);
+ list_.push_back((*lIter)->encodedV2);
+ if (++v2Objs >= maxReplyObjs) {
+ v2Objs = 0;
+
+ string content;
+ ListCodec::encode(list_, content);
+ list_.clear();
+ if (content.length()) {
+ stringstream key;
+ Variant::Map headers;
+ key << "agent.ind.data." << keyifyNameStr(packageName)
+ << "." << keyifyNameStr(className)
+ << "." << vendorNameKey
+ << "." << productNameKey;
+ if (!instanceNameKey.empty())
+ key << "." << instanceNameKey;
+
+ headers["method"] = "indication";
+ headers["qmf.opcode"] = "_data_indication";
+ headers["qmf.content"] = "_data";
+ headers["qmf.agent"] = name_address;
+
+ sendBuffer(content, "", headers, "amqp/list", v2Topic, key.str(), 0);
+ QPID_LOG(debug, "SEND Multicast ContentInd V2 (delete) to=" << key.str() << " len=" << content.length());
+ }
+ }
+ }
+ } // end current list
+
+ // send any remaining objects...
+
+ if (v1Objs) {
+ stringstream key;
+ key << "console.obj.1.0." << packageName << "." << className;
+ size_t contentSize = msgBuffer.getPosition();
+ sendBuffer(msgBuffer, mExchange, key.str());
+ QPID_LOG(debug, "SEND V1 Multicast ContentInd V1 (delete) to=" << key.str() << " len=" << contentSize);
+ }
+
+ if (!list_.empty()) {
+ string content;
+ ListCodec::encode(list_, content);
+ list_.clear();
+ if (content.length()) {
+ stringstream key;
+ Variant::Map headers;
+ key << "agent.ind.data." << keyifyNameStr(packageName)
+ << "." << keyifyNameStr(className)
+ << "." << vendorNameKey
+ << "." << productNameKey;
+ if (!instanceNameKey.empty())
+ key << "." << instanceNameKey;
+
+ headers["method"] = "indication";
+ headers["qmf.opcode"] = "_data_indication";
+ headers["qmf.content"] = "_data";
+ headers["qmf.agent"] = name_address;
+
+ sendBuffer(content, "", headers, "amqp/list", v2Topic, key.str(), 0);
+ QPID_LOG(debug, "SEND Multicast ContentInd V2 (delete) to=" << key.str() << " len=" << content.length());
+ }
+ }
+ } // end map
+ }
+
+ //
+ // Process the entire object map.
+ //
+ // If publish is disabled, don't send any updates.
+ //
+ while (publish) {
+ msgBuffer.reset();
+ Variant::List list_;
+ uint32_t pcount;
+ uint32_t scount;
+ uint32_t v1Objs, v2Objs;
+ ManagementObjectVector::iterator baseIter;
+ std::string packageName;
+ std::string className;
+
+ for (baseIter = localManagementObjects.begin();
+ baseIter != localManagementObjects.end();
+ baseIter++) {
+ ManagementObject::shared_ptr baseObject = *baseIter;
+ //
+ // Skip until we find a base object requiring processing...
+ //
+ if (baseObject->getFlags() == 0) {
+ packageName = baseObject->getPackageName();
+ className = baseObject->getClassName();
+ break;
+ }
+ }
+
+ if (baseIter == localManagementObjects.end())
+ break; // done - all objects processed
+
+ pcount = scount = 0;
+ v1Objs = 0;
+ v2Objs = 0;
+ list_.clear();
+ msgBuffer.reset();
+
+ for (ManagementObjectVector::iterator iter = baseIter;
+ iter != localManagementObjects.end();
+ iter++) {
+ msgBuffer.makeAvailable(HEADROOM); // Make sure there's buffer space
+ ManagementObject::shared_ptr baseObject = *baseIter;
+ ManagementObject::shared_ptr object = *iter;
+ bool send_stats, send_props;
+ if (baseObject->isSameClass(*object) && object->getFlags() == 0) {
+ object->setFlags(1);
+ if (object->getConfigChanged() || object->getInstChanged())
+ object->setUpdateTime();
+
+ // skip any objects marked deleted since our first pass. Deal with them
+ // on the next periodic cycle...
+ if (object->isDeleted()) {
+ continue;
+ }
+
+ send_props = (object->getConfigChanged() || object->getForcePublish());
+ send_stats = (object->hasInst() && (object->getInstChanged() || object->getForcePublish()));
+
+ if (send_props && qmf1Support) {
+ size_t pos = msgBuffer.getPosition();
+ encodeHeader(msgBuffer, 'c');
+ sBuf.clear();
+ object->writeProperties(sBuf);
+ msgBuffer.putRawData(sBuf);
+ QPID_LOG(trace, "Changed V1 properties "
+ << object->getObjectId().getV2Key()
+ << " len=" << msgBuffer.getPosition()-pos);
+ ++v1Objs;
+ }
+
+ if (send_stats && qmf1Support) {
+ size_t pos = msgBuffer.getPosition();
+ encodeHeader(msgBuffer, 'i');
+ sBuf.clear();
+ object->writeStatistics(sBuf);
+ msgBuffer.putRawData(sBuf);
+ QPID_LOG(trace, "Changed V1 statistics "
+ << object->getObjectId().getV2Key()
+ << " len=" << msgBuffer.getPosition()-pos);
+ ++v1Objs;
+ }
+
+ if ((send_stats || send_props) && qmf2Support) {
+ Variant::Map map_;
+ Variant::Map values;
+ Variant::Map oid;
+
+ object->getObjectId().mapEncode(oid);
+ map_["_object_id"] = oid;
+ map_["_schema_id"] = mapEncodeSchemaId(object->getPackageName(),
+ object->getClassName(),
+ "_data",
+ object->getMd5Sum());
+ object->writeTimestamps(map_);
+ object->mapEncodeValues(values, send_props, send_stats);
+ map_["_values"] = values;
+ list_.push_back(map_);
+ v2Objs++;
+ QPID_LOG(trace, "Changed V2"
+ << (send_stats? " statistics":"")
+ << (send_props? " properties":"")
+ << " map=" << map_);
+ }
+
+ if (send_props) pcount++;
+ if (send_stats) scount++;
+
+ object->setForcePublish(false);
+
+ if ((qmf1Support && (v1Objs >= maxReplyObjs)) ||
+ (qmf2Support && (v2Objs >= maxReplyObjs)))
+ break; // have enough objects, send an indication...
+ }
+ }
+
+ if (pcount || scount) {
+ if (qmf1Support) {
+ if (msgBuffer.getPosition() > 0) {
+ stringstream key;
+ key << "console.obj.1.0." << packageName << "." << className;
+ size_t contentSize = msgBuffer.getPosition();
+ sendBuffer(msgBuffer, mExchange, key.str());
+ QPID_LOG(debug, "SEND V1 Multicast ContentInd to=" << key.str()
+ << " props=" << pcount
+ << " stats=" << scount
+ << " len=" << contentSize);
+ }
+ }
+
+ if (qmf2Support) {
+ string content;
+ ListCodec::encode(list_, content);
+ if (content.length()) {
+ stringstream key;
+ Variant::Map headers;
+ key << "agent.ind.data." << keyifyNameStr(packageName)
+ << "." << keyifyNameStr(className)
+ << "." << vendorNameKey
+ << "." << productNameKey;
+ if (!instanceNameKey.empty())
+ key << "." << instanceNameKey;
+
+ headers["method"] = "indication";
+ headers["qmf.opcode"] = "_data_indication";
+ headers["qmf.content"] = "_data";
+ headers["qmf.agent"] = name_address;
+
+ sendBuffer(content, "", headers, "amqp/list", v2Topic, key.str(), 0);
+ QPID_LOG(debug, "SEND Multicast ContentInd to=" << key.str()
+ << " props=" << pcount
+ << " stats=" << scount
+ << " len=" << content.length());
+ }
+ }
+ }
+ } // end processing updates for all objects
+
+ if (objectsDeleted) {
+ sys::Mutex::ScopedLock lock (userLock);
+ deleteOrphanedAgentsLH();
+ }
+
+ // heartbeat generation. Note that heartbeats need to be sent even if publish is disabled.
+
+ if (qmf1Support) {
+ char msgChars[qmfV1BufferSize];
+ Buffer msgBuffer(msgChars, qmfV1BufferSize);
+ encodeHeader(msgBuffer, 'h');
+ msgBuffer.putLongLong(sys::Duration::FromEpoch());
+
+ routingKey = "console.heartbeat.1.0";
+ sendBuffer(msgBuffer, mExchange, routingKey);
+ QPID_LOG(debug, "SEND HeartbeatInd to=" << routingKey);
+ }
+
+ if (qmf2Support) {
+ std::stringstream addr_key;
+
+ addr_key << "agent.ind.heartbeat." << vendorNameKey << "." << productNameKey;
+ if (!instanceNameKey.empty())
+ addr_key << "." << instanceNameKey;
+
+ Variant::Map map;
+ Variant::Map headers;
+
+ headers["method"] = "indication";
+ headers["qmf.opcode"] = "_agent_heartbeat_indication";
+ headers["qmf.agent"] = name_address;
+
+ map["_values"] = attrMap;
+ map["_values"].asMap()["_timestamp"] = uint64_t(sys::Duration::FromEpoch());
+ map["_values"].asMap()["_heartbeat_interval"] = interval;
+ map["_values"].asMap()["_epoch"] = bootSequence;
+
+ string content;
+ MapCodec::encode(map, content);
+
+ // Set TTL (in msecs) on outgoing heartbeat indications based on the interval
+ // time to prevent stale heartbeats from getting to the consoles.
+ sendBuffer(content, "", headers, "amqp/map", v2Topic, addr_key.str(), interval * 2 * 1000);
+
+ QPID_LOG(debug, "SENT AgentHeartbeat name=" << name_address);
+ }
+}
+
+void ManagementAgent::deleteObjectNow(const ObjectId& oid)
+{
+ ManagementObject::shared_ptr object;
+ {
+ sys::Mutex::ScopedLock lock(objectLock);
+ ManagementObjectMap::iterator iter = managementObjects.find(oid);
+ if (iter == managementObjects.end())
+ return;
+ object = iter->second;
+ if (!object->isDeleted())
+ return;
+ managementObjects.erase(oid);
+ }
+
+#define DNOW_BUFSIZE 2048
+ char msgChars[DNOW_BUFSIZE];
+ Buffer msgBuffer(msgChars, DNOW_BUFSIZE);
+ Variant::List list_;
+ stringstream v1key, v2key;
+
+ if (publish && qmf1Support) {
+ string sBuf;
+
+ v1key << "console.obj.1.0." << object->getPackageName() << "." << object->getClassName();
+ encodeHeader(msgBuffer, 'c');
+ object->writeProperties(sBuf);
+ msgBuffer.putRawData(sBuf);
+ }
+
+ if (publish && qmf2Support) {
+ Variant::Map map_;
+ Variant::Map values;
+
+ map_["_schema_id"] = mapEncodeSchemaId(object->getPackageName(),
+ object->getClassName(),
+ "_data",
+ object->getMd5Sum());
+ object->writeTimestamps(map_);
+ object->mapEncodeValues(values, true, false);
+ map_["_values"] = values;
+ list_.push_back(map_);
+ v2key << "agent.ind.data." << keyifyNameStr(object->getPackageName())
+ << "." << keyifyNameStr(object->getClassName())
+ << "." << vendorNameKey
+ << "." << productNameKey;
+ if (!instanceNameKey.empty())
+ v2key << "." << instanceNameKey;
+ }
+
+ object.reset();
+
+ // object deleted, ok to drop lock now.
+
+ if (publish && qmf1Support) {
+ sendBuffer(msgBuffer, mExchange, v1key.str());
+ QPID_LOG(debug, "SEND Immediate(delete) ContentInd to=" << v1key.str());
+ }
+
+ if (publish && qmf2Support) {
+ Variant::Map headers;
+ headers["method"] = "indication";
+ headers["qmf.opcode"] = "_data_indication";
+ headers["qmf.content"] = "_data";
+ headers["qmf.agent"] = name_address;
+
+ string content;
+ ListCodec::encode(list_, content);
+ sendBuffer(content, "", headers, "amqp/list", v2Topic, v2key.str(), 0);
+ QPID_LOG(debug, "SEND Immediate(delete) ContentInd to=" << v2key.str());
+ }
+}
+
+void ManagementAgent::sendCommandComplete(const string& replyToKey, uint32_t sequence,
+ uint32_t code, const string& text)
+{
+ ResizableBuffer outBuffer (qmfV1BufferSize);
+
+ encodeHeader (outBuffer, 'z', sequence);
+ outBuffer.putLong (code);
+ outBuffer.putShortString (text);
+ sendBuffer(outBuffer, dExchange, replyToKey);
+ QPID_LOG(debug, "SEND CommandCompleteInd code=" << code << " text=" << text << " to=" <<
+ replyToKey << " seq=" << sequence);
+}
+
+void ManagementAgent::sendException(const string& rte, const string& rtk, const string& cid,
+ const string& text, uint32_t code, bool viaLocal)
+{
+ static const string addr_exchange("qmf.default.direct");
+
+ Variant::Map map;
+ Variant::Map headers;
+ Variant::Map values;
+ string content;
+
+ headers["method"] = "indication";
+ headers["qmf.opcode"] = "_exception";
+ headers["qmf.agent"] = viaLocal ? "broker" : name_address;
+
+ values["error_code"] = code;
+ values["error_text"] = text;
+ map["_values"] = values;
+
+ MapCodec::encode(map, content);
+ sendBuffer(content, cid, headers, "amqp/map", rte, rtk);
+
+ QPID_LOG(debug, "SENT Exception code=" << code <<" text=" << text);
+}
+
+bool ManagementAgent::dispatchCommand (Deliverable& deliverable,
+ const string& routingKey,
+ const FieldTable* /*args*/,
+ const bool topic,
+ int qmfVersion)
+{
+ Message& msg = ((DeliverableMessage&) deliverable).getMessage ();
+
+ if (topic && qmfVersion == 1) {
+
+ // qmf1 is bound only to the topic management exchange.
+ // Parse the routing key. This management broker should act as though it
+ // is bound to the exchange to match the following keys:
+ //
+ // agent.1.0.#
+ // broker
+ // schema.#
+
+ if (routingKey == "broker") {
+ dispatchAgentCommand(msg);
+ return false;
+ }
+
+ if (routingKey.length() > 6) {
+
+ if (routingKey.compare(0, 9, "agent.1.0") == 0) {
+ dispatchAgentCommand(msg);
+ return false;
+ }
+
+ if (routingKey.compare(0, 8, "agent.1.") == 0) {
+ return authorizeAgentMessage(msg);
+ }
+
+ if (routingKey.compare(0, 7, "schema.") == 0) {
+ dispatchAgentCommand(msg);
+ return true;
+ }
+ }
+ }
+
+ if (qmfVersion == 2) {
+
+ if (topic) {
+ // Intercept messages bound to:
+ // "console.ind.locate.# - process these messages, and also allow them to be forwarded.
+ if (routingKey == "console.request.agent_locate") {
+ dispatchAgentCommand(msg);
+ return true;
+ }
+
+ } else { // direct exchange
+
+ // Intercept messages bound to:
+ // "broker" - generic alias for the local broker
+ // "<name_address>" - the broker agent's proper name
+ // and do not forward them futher
+ if (routingKey == "broker" || routingKey == name_address) {
+ dispatchAgentCommand(msg, routingKey == "broker");
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+void ManagementAgent::handleMethodRequest(Buffer& inBuffer, const string& replyToKey, uint32_t sequence, const string& userId)
+{
+ moveNewObjects();
+
+ string methodName;
+ string packageName;
+ string className;
+ uint8_t hash[16];
+ ResizableBuffer outBuffer (qmfV1BufferSize);
+ AclModule* acl = broker->getAcl();
+ string inArgs;
+
+ string sBuf;
+ inBuffer.getRawData(sBuf, 16);
+ ObjectId objId;
+ objId.decode(sBuf);
+ inBuffer.getShortString(packageName);
+ inBuffer.getShortString(className);
+ inBuffer.getBin128(hash);
+ inBuffer.getShortString(methodName);
+ inBuffer.getRawData(inArgs, inBuffer.available());
+
+ QPID_LOG(debug, "RECV MethodRequest (v1) class=" << packageName << ":" << className << "(" << Uuid(hash) << ") method=" <<
+ methodName << " replyTo=" << replyToKey);
+
+ encodeHeader(outBuffer, 'm', sequence);
+
+ if (disallowAllV1Methods) {
+ outBuffer.putLong(Manageable::STATUS_FORBIDDEN);
+ outBuffer.putMediumString("QMFv1 methods forbidden on this broker, use QMFv2");
+ sendBuffer(outBuffer, dExchange, replyToKey);
+ QPID_LOG(debug, "SEND MethodResponse status=FORBIDDEN reason='All QMFv1 Methods Forbidden' seq=" << sequence);
+ return;
+ }
+
+ DisallowedMethods::const_iterator i = disallowed.find(make_pair(className, methodName));
+ if (i != disallowed.end()) {
+ outBuffer.putLong(Manageable::STATUS_FORBIDDEN);
+ outBuffer.putMediumString(i->second);
+ sendBuffer(outBuffer, dExchange, replyToKey);
+ QPID_LOG(debug, "SEND MethodResponse status=FORBIDDEN text=" << i->second << " seq=" << sequence);
+ return;
+ }
+
+ if (acl != 0) {
+ map<acl::Property, string> params;
+ params[acl::PROP_SCHEMAPACKAGE] = packageName;
+ params[acl::PROP_SCHEMACLASS] = className;
+
+ if (!acl->authorise(userId, acl::ACT_ACCESS, acl::OBJ_METHOD, methodName, &params)) {
+ outBuffer.putLong(Manageable::STATUS_FORBIDDEN);
+ outBuffer.putMediumString(Manageable::StatusText(Manageable::STATUS_FORBIDDEN));
+ sendBuffer(outBuffer, dExchange, replyToKey);
+ QPID_LOG(debug, "SEND MethodResponse status=FORBIDDEN" << " seq=" << sequence);
+ return;
+ }
+ }
+
+ ManagementObject::shared_ptr object;
+ {
+ sys::Mutex::ScopedLock lock(objectLock);
+ ManagementObjectMap::iterator iter = numericFind(objId);
+ if (iter != managementObjects.end())
+ object = iter->second;
+ }
+
+ if (!object || object->isDeleted()) {
+ outBuffer.putLong (Manageable::STATUS_UNKNOWN_OBJECT);
+ outBuffer.putMediumString(Manageable::StatusText (Manageable::STATUS_UNKNOWN_OBJECT));
+ } else {
+ if ((object->getPackageName() != packageName) ||
+ (object->getClassName() != className)) {
+ outBuffer.putLong (Manageable::STATUS_PARAMETER_INVALID);
+ outBuffer.putMediumString(Manageable::StatusText (Manageable::STATUS_PARAMETER_INVALID));
+ }
+ else {
+ uint32_t pos = outBuffer.getPosition();
+ try {
+ string outBuf;
+ object->doMethod(methodName, inArgs, outBuf, userId);
+ outBuffer.putRawData(outBuf);
+ } catch(exception& e) {
+ outBuffer.setPosition(pos);;
+ outBuffer.putLong(Manageable::STATUS_EXCEPTION);
+ outBuffer.putMediumString(e.what());
+ }
+ }
+ }
+
+ sendBuffer(outBuffer, dExchange, replyToKey);
+ QPID_LOG(debug, "SEND MethodResponse (v1) to=" << replyToKey << " seq=" << sequence);
+}
+
+
+void ManagementAgent::handleMethodRequest (const string& body, const string& rte, const string& rtk,
+ const string& cid, const string& userId, bool viaLocal)
+{
+ moveNewObjects();
+
+ string methodName;
+ Variant::Map inMap;
+ MapCodec::decode(body, inMap);
+ Variant::Map::const_iterator oid, mid;
+ string content;
+ string error;
+ uint32_t errorCode(0);
+
+ Variant::Map outMap;
+ Variant::Map headers;
+
+ headers["method"] = "response";
+ headers["qmf.opcode"] = "_method_response";
+ headers["qmf.agent"] = viaLocal ? "broker" : name_address;
+
+ if ((oid = inMap.find("_object_id")) == inMap.end() ||
+ (mid = inMap.find("_method_name")) == inMap.end()) {
+ sendException(rte, rtk, cid, Manageable::StatusText(Manageable::STATUS_PARAMETER_INVALID),
+ Manageable::STATUS_PARAMETER_INVALID, viaLocal);
+ return;
+ }
+
+ ObjectId objId;
+ Variant::Map inArgs;
+ Variant::Map callMap;
+
+ try {
+ // coversions will throw if input is invalid.
+ objId = ObjectId(oid->second.asMap());
+ methodName = mid->second.getString();
+
+ mid = inMap.find("_arguments");
+ if (mid != inMap.end()) {
+ inArgs = (mid->second).asMap();
+ }
+ } catch(exception& e) {
+ sendException(rte, rtk, cid, e.what(), Manageable::STATUS_EXCEPTION, viaLocal);
+ return;
+ }
+
+ ManagementObject::shared_ptr object;
+ {
+ sys::Mutex::ScopedLock lock(objectLock);
+ ManagementObjectMap::iterator iter = managementObjects.find(objId);
+ if (iter != managementObjects.end())
+ object = iter->second;
+ }
+
+ if (!object || object->isDeleted()) {
+ stringstream estr;
+ estr << "No object found with ID=" << objId;
+ sendException(rte, rtk, cid, estr.str(), 1, viaLocal);
+ return;
+ }
+
+ // validate
+ AclModule* acl = broker->getAcl();
+ DisallowedMethods::const_iterator i;
+
+ i = disallowed.find(make_pair(object->getClassName(), methodName));
+ if (i != disallowed.end()) {
+ sendException(rte, rtk, cid, i->second, Manageable::STATUS_FORBIDDEN, viaLocal);
+ return;
+ }
+
+ if (acl != 0) {
+ map<acl::Property, string> params;
+ params[acl::PROP_SCHEMAPACKAGE] = object->getPackageName();
+ params[acl::PROP_SCHEMACLASS] = object->getClassName();
+
+ if (!acl->authorise(userId, acl::ACT_ACCESS, acl::OBJ_METHOD, methodName, &params)) {
+ sendException(rte, rtk, cid, Manageable::StatusText(Manageable::STATUS_FORBIDDEN),
+ Manageable::STATUS_FORBIDDEN, viaLocal);
+ return;
+ }
+ }
+
+ // invoke the method
+
+ QPID_LOG(debug, "RECV MethodRequest (v2) class=" << object->getPackageName()
+ << ":" << object->getClassName() << " method=" <<
+ methodName << " replyTo=" << rte << "/" << rtk << " objId=" << objId << " inArgs=" << inArgs);
+
+ try {
+ object->doMethod(methodName, inArgs, callMap, userId);
+ errorCode = callMap["_status_code"].asUint32();
+ if (errorCode == 0) {
+ outMap["_arguments"] = Variant::Map();
+ for (Variant::Map::const_iterator iter = callMap.begin();
+ iter != callMap.end(); iter++)
+ if (iter->first != "_status_code" && iter->first != "_status_text")
+ outMap["_arguments"].asMap()[iter->first] = iter->second;
+ } else
+ error = callMap["_status_text"].asString();
+ } catch(exception& e) {
+ sendException(rte, rtk, cid, e.what(), Manageable::STATUS_EXCEPTION, viaLocal);
+ return;
+ }
+
+ if (errorCode != 0) {
+ sendException(rte, rtk, cid, error, errorCode, viaLocal);
+ return;
+ }
+
+ MapCodec::encode(outMap, content);
+ sendBuffer(content, cid, headers, "amqp/map", rte, rtk);
+ QPID_LOG(debug, "SEND MethodResponse (v2) to=" << rte << "/" << rtk << " seq=" << cid << " map=" << outMap);
+}
+
+
+void ManagementAgent::handleBrokerRequest (Buffer&, const string& replyToKey, uint32_t sequence)
+{
+ ResizableBuffer outBuffer (qmfV1BufferSize);
+
+ QPID_LOG(debug, "RECV BrokerRequest replyTo=" << replyToKey);
+
+ encodeHeader (outBuffer, 'b', sequence);
+ uuid.encode (outBuffer);
+
+ sendBuffer(outBuffer, dExchange, replyToKey);
+ QPID_LOG(debug, "SEND BrokerResponse to=" << replyToKey);
+}
+
+void ManagementAgent::handlePackageQuery (Buffer&, const string& replyToKey, uint32_t sequence)
+{
+ QPID_LOG(debug, "RECV PackageQuery replyTo=" << replyToKey);
+ ResizableBuffer outBuffer (qmfV1BufferSize);
+
+ {
+ sys::Mutex::ScopedLock lock(userLock);
+ for (PackageMap::iterator pIter = packages.begin ();
+ pIter != packages.end ();
+ pIter++)
+ {
+ encodeHeader (outBuffer, 'p', sequence);
+ encodePackageIndication (outBuffer, pIter);
+ }
+ }
+
+ if (outBuffer.getPosition() > 0) {
+ sendBuffer(outBuffer, dExchange, replyToKey);
+ QPID_LOG(debug, "SEND PackageInd to=" << replyToKey << " seq=" << sequence);
+ }
+
+ sendCommandComplete(replyToKey, sequence);
+}
+
+void ManagementAgent::handlePackageInd (Buffer& inBuffer, const string& replyToKey, uint32_t sequence)
+{
+ string packageName;
+
+ inBuffer.getShortString(packageName);
+
+ QPID_LOG(debug, "RECV PackageInd package=" << packageName << " replyTo=" << replyToKey << " seq=" << sequence);
+
+ sys::Mutex::ScopedLock lock(userLock);
+ findOrAddPackageLH(packageName);
+}
+
+void ManagementAgent::handleClassQuery(Buffer& inBuffer, const string& replyToKey, uint32_t sequence)
+{
+ string packageName;
+
+ inBuffer.getShortString(packageName);
+
+ QPID_LOG(debug, "RECV ClassQuery package=" << packageName << " replyTo=" << replyToKey << " seq=" << sequence);
+
+ typedef std::pair<SchemaClassKey, uint8_t> _ckeyType;
+ std::list<_ckeyType> classes;
+ {
+ sys::Mutex::ScopedLock lock(userLock);
+ PackageMap::iterator pIter = packages.find(packageName);
+ if (pIter != packages.end())
+ {
+ ClassMap &cMap = pIter->second;
+ for (ClassMap::iterator cIter = cMap.begin();
+ cIter != cMap.end();
+ cIter++) {
+ if (cIter->second.hasSchema()) {
+ classes.push_back(make_pair(cIter->first, cIter->second.kind));
+ }
+ }
+ }
+ }
+
+ while (classes.size()) {
+ ResizableBuffer outBuffer(qmfV1BufferSize);
+
+ encodeHeader(outBuffer, 'q', sequence);
+ encodeClassIndication(outBuffer, packageName, classes.front().first, classes.front().second);
+
+ sendBuffer(outBuffer, dExchange, replyToKey);
+ QPID_LOG(debug, "SEND ClassInd class=" << packageName << ":" << classes.front().first.name <<
+ "(" << Uuid(classes.front().first.hash) << ") to=" << replyToKey << " seq=" << sequence);
+ classes.pop_front();
+ }
+ sendCommandComplete(replyToKey, sequence);
+}
+
+void ManagementAgent::handleClassInd (Buffer& inBuffer, const string& replyToKey, uint32_t)
+{
+ string packageName;
+ SchemaClassKey key;
+
+ uint8_t kind = inBuffer.getOctet();
+ inBuffer.getShortString(packageName);
+ inBuffer.getShortString(key.name);
+ inBuffer.getBin128(key.hash);
+
+ QPID_LOG(debug, "RECV ClassInd class=" << packageName << ":" << key.name << "(" << Uuid(key.hash) <<
+ "), replyTo=" << replyToKey);
+
+ sys::Mutex::ScopedLock lock(userLock);
+ PackageMap::iterator pIter = findOrAddPackageLH(packageName);
+ ClassMap::iterator cIter = pIter->second.find(key);
+ if (cIter == pIter->second.end() || !cIter->second.hasSchema()) {
+ ResizableBuffer outBuffer (qmfV1BufferSize);
+ uint32_t sequence = nextRequestSequence++;
+
+ // Schema Request
+ encodeHeader (outBuffer, 'S', sequence);
+ outBuffer.putShortString(packageName);
+ key.encode(outBuffer);
+ sendBuffer(outBuffer, dExchange, replyToKey);
+ QPID_LOG(debug, "SEND SchemaRequest class=" << packageName << ":" << key.name << "(" << Uuid(key.hash) <<
+ "), to=" << replyToKey << " seq=" << sequence);
+
+ if (cIter != pIter->second.end())
+ pIter->second.erase(key);
+
+ pIter->second.insert(pair<SchemaClassKey, SchemaClass>(key, SchemaClass(kind, sequence)));
+ }
+}
+
+void ManagementAgent::SchemaClass::appendSchema(Buffer& buf)
+{
+ // If the management package is attached locally (embedded in the broker or
+ // linked in via plug-in), call the schema handler directly. If the package
+ // is from a remote management agent, send the stored schema information.
+
+ if (writeSchemaCall != 0) {
+ string schema;
+ writeSchemaCall(schema);
+ buf.putRawData(schema);
+ } else
+ buf.putRawData(reinterpret_cast<uint8_t*>(&data[0]), data.size());
+}
+
+void ManagementAgent::handleSchemaRequest(Buffer& inBuffer, const string& rte, const string& rtk, uint32_t sequence)
+{
+ string packageName;
+ SchemaClassKey key;
+
+ inBuffer.getShortString (packageName);
+ key.decode(inBuffer);
+
+ QPID_LOG(debug, "RECV SchemaRequest class=" << packageName << ":" << key.name << "(" << Uuid(key.hash) <<
+ "), replyTo=" << rte << "/" << rtk << " seq=" << sequence);
+
+ sys::Mutex::ScopedLock lock(userLock);
+ PackageMap::iterator pIter = packages.find(packageName);
+ if (pIter != packages.end()) {
+ ClassMap& cMap = pIter->second;
+ ClassMap::iterator cIter = cMap.find(key);
+ if (cIter != cMap.end()) {
+ ResizableBuffer outBuffer(qmfV1BufferSize);
+ SchemaClass& classInfo = cIter->second;
+
+ if (classInfo.hasSchema()) {
+ encodeHeader(outBuffer, 's', sequence);
+ classInfo.appendSchema(outBuffer);
+ sendBuffer(outBuffer, rte, rtk);
+ QPID_LOG(debug, "SEND SchemaResponse to=" << rte << "/" << rtk << " seq=" << sequence);
+ }
+ else
+ sendCommandComplete(rtk, sequence, 1, "Schema not available");
+ }
+ else
+ sendCommandComplete(rtk, sequence, 1, "Class key not found");
+ }
+ else
+ sendCommandComplete(rtk, sequence, 1, "Package not found");
+}
+
+void ManagementAgent::handleSchemaResponse(Buffer& inBuffer, const string& /*replyToKey*/, uint32_t sequence)
+{
+ string packageName;
+ SchemaClassKey key;
+
+ uint32_t pos = inBuffer.getPosition();
+ inBuffer.getOctet();
+ inBuffer.getShortString(packageName);
+ key.decode(inBuffer);
+ inBuffer.setPosition(pos);;
+
+ QPID_LOG(debug, "RECV SchemaResponse class=" << packageName << ":" << key.name << "(" << Uuid(key.hash) << ")" << " seq=" << sequence);
+
+ sys::Mutex::ScopedLock lock(userLock);
+ PackageMap::iterator pIter = packages.find(packageName);
+ if (pIter != packages.end()) {
+ ClassMap& cMap = pIter->second;
+ ClassMap::iterator cIter = cMap.find(key);
+ if (cIter != cMap.end() && cIter->second.pendingSequence == sequence) {
+ size_t length = validateSchema(inBuffer, cIter->second.kind);
+ if (length == 0) {
+ QPID_LOG(warning, "Management Agent received invalid schema response: " << packageName << "." << key.name);
+ cMap.erase(key);
+ } else {
+ cIter->second.data.resize(length);
+ inBuffer.getRawData(reinterpret_cast<uint8_t*>(&cIter->second.data[0]), length);
+
+ // Publish a class-indication message
+ ResizableBuffer outBuffer(qmfV1BufferSize);
+
+ encodeHeader(outBuffer, 'q');
+ encodeClassIndication(outBuffer, pIter->first, cIter->first, cIter->second.kind);
+ sendBuffer(outBuffer, mExchange, "schema.class");
+ QPID_LOG(debug, "SEND ClassInd class=" << packageName << ":" << key.name << "(" << Uuid(key.hash) << ")" <<
+ " to=schema.class");
+ }
+ }
+ }
+}
+
+bool ManagementAgent::bankInUse (uint32_t bank)
+{
+ for (RemoteAgentMap::iterator aIter = remoteAgents.begin();
+ aIter != remoteAgents.end();
+ aIter++)
+ if (aIter->second->agentBank == bank)
+ return true;
+ return false;
+}
+
+uint32_t ManagementAgent::allocateNewBank ()
+{
+ while (bankInUse (nextRemoteBank))
+ nextRemoteBank++;
+
+ uint32_t allocated = nextRemoteBank++;
+ writeData ();
+ return allocated;
+}
+
+uint32_t ManagementAgent::assignBankLH (uint32_t requestedBank)
+{
+ if (requestedBank == 0 || bankInUse (requestedBank))
+ return allocateNewBank ();
+ return requestedBank;
+}
+
+void ManagementAgent::deleteOrphanedAgentsLH()
+{
+ list<ObjectId> deleteList;
+
+ for (RemoteAgentMap::const_iterator aIter = remoteAgents.begin(); aIter != remoteAgents.end(); aIter++) {
+ bool found = false;
+
+ for (ManagementObjectMap::iterator iter = managementObjects.begin();
+ iter != managementObjects.end();
+ iter++) {
+ if (iter->first == aIter->first && !iter->second->isDeleted()) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ deleteList.push_back(aIter->first);
+ }
+
+ for (list<ObjectId>::const_iterator dIter = deleteList.begin(); dIter != deleteList.end(); dIter++)
+ remoteAgents.erase(*dIter);
+}
+
+void ManagementAgent::handleAttachRequest (Buffer& inBuffer, const string& replyToKey, uint32_t sequence, const ObjectId& connectionRef)
+{
+ string label;
+ uint32_t requestedBrokerBank, requestedAgentBank;
+ uint32_t assignedBank;
+ Uuid systemId;
+
+ moveNewObjects();
+
+ sys::Mutex::ScopedLock lock(userLock);
+ deleteOrphanedAgentsLH();
+ RemoteAgentMap::iterator aIter = remoteAgents.find(connectionRef);
+ if (aIter != remoteAgents.end()) {
+ // There already exists an agent on this session. Reject the request.
+ sendCommandComplete(replyToKey, sequence, 1, "Connection already has remote agent");
+ return;
+ }
+
+ inBuffer.getShortString(label);
+ systemId.decode(inBuffer);
+ requestedBrokerBank = inBuffer.getLong();
+ requestedAgentBank = inBuffer.getLong();
+
+ QPID_LOG(debug, "RECV (Agent)AttachRequest label=" << label << " reqBrokerBank=" << requestedBrokerBank <<
+ " reqAgentBank=" << requestedAgentBank << " replyTo=" << replyToKey << " seq=" << sequence);
+
+ assignedBank = assignBankLH(requestedAgentBank);
+
+ boost::shared_ptr<RemoteAgent> agent(new RemoteAgent(*this));
+ agent->brokerBank = brokerBank;
+ agent->agentBank = assignedBank;
+ agent->routingKey = replyToKey;
+ agent->connectionRef = connectionRef;
+ agent->mgmtObject = _qmf::Agent::shared_ptr(new _qmf::Agent (this, agent.get()));
+ agent->mgmtObject->set_connectionRef(agent->connectionRef);
+ agent->mgmtObject->set_label (label);
+ agent->mgmtObject->set_registeredTo (broker->GetManagementObject()->getObjectId());
+ agent->mgmtObject->set_systemId ((const unsigned char*)systemId.data());
+ agent->mgmtObject->set_brokerBank (brokerBank);
+ agent->mgmtObject->set_agentBank (assignedBank);
+ addObject (agent->mgmtObject, 0);
+ remoteAgents[connectionRef] = agent;
+
+ QPID_LOG(debug, "Remote Agent registered bank=[" << brokerBank << "." << assignedBank << "] replyTo=" << replyToKey);
+
+ // Send an Attach Response
+ ResizableBuffer outBuffer (qmfV1BufferSize);
+
+ encodeHeader (outBuffer, 'a', sequence);
+ outBuffer.putLong (brokerBank);
+ outBuffer.putLong (assignedBank);
+ sendBuffer(outBuffer, dExchange, replyToKey);
+ QPID_LOG(debug, "SEND AttachResponse brokerBank=" << brokerBank << " agentBank=" << assignedBank <<
+ " to=" << replyToKey << " seq=" << sequence);
+}
+
+void ManagementAgent::handleGetQuery(Buffer& inBuffer, const string& replyToKey, uint32_t sequence, const string& userId)
+{
+ FieldTable ft;
+ FieldTable::ValuePtr value;
+ AclModule* acl = broker->getAcl();
+
+ moveNewObjects();
+
+ ft.decode(inBuffer);
+
+ QPID_LOG(debug, "RECV GetQuery (v1) query=" << ft << " seq=" << sequence);
+
+ value = ft.get("_class");
+ if (value.get() == 0 || !value->convertsTo<string>()) {
+ value = ft.get("_objectid");
+ if (value.get() == 0 || !value->convertsTo<string>())
+ return;
+
+ ObjectId selector(value->get<string>());
+
+ ManagementObject::shared_ptr object;
+ {
+ sys::Mutex::ScopedLock lock(objectLock);
+ ManagementObjectMap::iterator iter = numericFind(selector);
+ if (iter != managementObjects.end())
+ object = iter->second;
+ }
+
+ if (object) {
+ ResizableBuffer outBuffer (qmfV1BufferSize);
+ if (acl != 0) {
+ map<acl::Property, string> params;
+ params[acl::PROP_SCHEMACLASS] = object->getClassName();
+
+ if (!acl->authorise(userId, acl::ACT_ACCESS, acl::OBJ_QUERY, object->getObjectId().getV2Key(), &params)) {
+ throw framing::UnauthorizedAccessException(QPID_MSG("unauthorized-access: ACL denied QMF query of object " << object->getObjectId().getV2Key() << " from " << userId));
+ }
+ }
+
+ if (object->getConfigChanged() || object->getInstChanged())
+ object->setUpdateTime();
+
+ if (!object->isDeleted()) {
+ string sBuf;
+ encodeHeader(outBuffer, 'g', sequence);
+ object->writeProperties(sBuf);
+ outBuffer.putRawData(sBuf);
+ sBuf.clear();
+ object->writeStatistics(sBuf, true);
+ outBuffer.putRawData(sBuf);
+ sendBuffer(outBuffer, dExchange, replyToKey);
+ QPID_LOG(debug, "SEND GetResponse (v1) to=" << replyToKey << " seq=" << sequence);
+ }
+ }
+ sendCommandComplete(replyToKey, sequence);
+ return;
+ }
+
+ string className (value->get<string>());
+ std::list<ManagementObject::shared_ptr> matches;
+
+ if (acl != 0) {
+ map<acl::Property, string> params;
+ params[acl::PROP_SCHEMACLASS] = className;
+
+ if (!acl->authorise(userId, acl::ACT_ACCESS, acl::OBJ_QUERY, className /* class-wide query */, &params)) {
+ throw framing::UnauthorizedAccessException(QPID_MSG("unauthorized-access: ACL denied QMF query of object class " << className << " from " << userId));
+ }
+ }
+
+ if (className == "memory")
+ qpid::sys::MemStat::loadMemInfo(memstat.get());
+
+ if (className == "broker") {
+ uint64_t uptime = sys::Duration(startTime, sys::now());
+ boost::dynamic_pointer_cast<_qmf::Broker>(broker->GetManagementObject())->set_uptime(uptime);
+ }
+
+
+ // build up a set of all objects to be dumped
+ {
+ sys::Mutex::ScopedLock lock(objectLock);
+ for (ManagementObjectMap::iterator iter = managementObjects.begin();
+ iter != managementObjects.end();
+ iter++) {
+ ManagementObject::shared_ptr object = iter->second;
+ if (object->getClassName () == className) {
+ matches.push_back(object);
+ }
+ }
+ }
+
+ // send them
+ ResizableBuffer outBuffer (qmfV1BufferSize);
+ while (matches.size()) {
+ ManagementObject::shared_ptr object = matches.front();
+ if (object->getConfigChanged() || object->getInstChanged())
+ object->setUpdateTime();
+
+ if (!object->isDeleted()) {
+ string sProps, sStats;
+ object->writeProperties(sProps);
+ object->writeStatistics(sStats, true);
+
+ size_t len = 8 + sProps.length() + sStats.length(); // 8 == size of header in bytes.
+ if (len > qmfV1BufferSize) {
+ QPID_LOG(error, "Object " << object->getObjectId() << " too large for output buffer - discarded!");
+ } else {
+ if (outBuffer.available() < len) { // not enough room in current buffer, send it.
+ sendBuffer(outBuffer, dExchange, replyToKey);
+ QPID_LOG(debug, "SEND GetResponse (v1) to=" << replyToKey << " seq=" << sequence);
+ continue; // lock dropped, need to re-find _SAME_ objid as it may have been deleted.
+ }
+ encodeHeader(outBuffer, 'g', sequence);
+ outBuffer.putRawData(sProps);
+ outBuffer.putRawData(sStats);
+ }
+ }
+ matches.pop_front();
+ }
+
+ if (outBuffer.getPosition() > 0) {
+ sendBuffer(outBuffer, dExchange, replyToKey);
+ QPID_LOG(debug, "SEND GetResponse (v1) to=" << replyToKey << " seq=" << sequence);
+ }
+
+ sendCommandComplete(replyToKey, sequence);
+}
+
+
+void ManagementAgent::handleGetQuery(const string& body, const string& rte, const string& rtk, const string& cid, const std::string& userId, bool viaLocal)
+{
+ moveNewObjects();
+
+ Variant::Map inMap;
+ Variant::Map::const_iterator i;
+ Variant::Map headers;
+ AclModule* acl = broker->getAcl();
+
+ MapCodec::decode(body, inMap);
+ QPID_LOG(debug, "RECV GetQuery (v2): map=" << inMap << " seq=" << cid);
+
+ headers["method"] = "response";
+ headers["qmf.opcode"] = "_query_response";
+ headers["qmf.content"] = "_data";
+ headers["qmf.agent"] = viaLocal ? "broker" : name_address;
+
+ /*
+ * Unpack the _what element of the query. Currently we only support OBJECT queries.
+ */
+ i = inMap.find("_what");
+ if (i == inMap.end()) {
+ sendException(rte, rtk, cid, "_what element missing in Query");
+ return;
+ }
+
+ if (i->second.getType() != qpid::types::VAR_STRING) {
+ sendException(rte, rtk, cid, "_what element is not a string");
+ return;
+ }
+
+ if (i->second.asString() != "OBJECT") {
+ sendException(rte, rtk, cid, "Query for _what => '" + i->second.asString() + "' not supported");
+ return;
+ }
+
+ string className;
+ string packageName;
+
+ /*
+ * Handle the _schema_id element, if supplied.
+ */
+ i = inMap.find("_schema_id");
+ if (i != inMap.end() && i->second.getType() == qpid::types::VAR_MAP) {
+ const Variant::Map& schemaIdMap(i->second.asMap());
+
+ Variant::Map::const_iterator s_iter = schemaIdMap.find("_class_name");
+ if (s_iter != schemaIdMap.end() && s_iter->second.getType() == qpid::types::VAR_STRING)
+ className = s_iter->second.asString();
+
+ s_iter = schemaIdMap.find("_package_name");
+ if (s_iter != schemaIdMap.end() && s_iter->second.getType() == qpid::types::VAR_STRING)
+ packageName = s_iter->second.asString();
+ }
+
+ if (className == "memory")
+ qpid::sys::MemStat::loadMemInfo(memstat.get());
+
+ if (className == "broker") {
+ uint64_t uptime = sys::Duration(startTime, sys::now());
+ boost::dynamic_pointer_cast<_qmf::Broker>(broker->GetManagementObject())->set_uptime(uptime);
+ }
+
+ /*
+ * Unpack the _object_id element of the query if it is present. If it is present, find that one
+ * object and return it. If it is not present, send a class-based result.
+ */
+ i = inMap.find("_object_id");
+ if (i != inMap.end() && i->second.getType() == qpid::types::VAR_MAP) {
+ Variant::List list_;
+ ObjectId objId(i->second.asMap());
+
+ ManagementObject::shared_ptr object;
+ {
+ sys::Mutex::ScopedLock lock (objectLock);
+ ManagementObjectMap::iterator iter = managementObjects.find(objId);
+ if (iter != managementObjects.end())
+ object = iter->second;
+ }
+ if (object) {
+ if (acl != 0) {
+ map<acl::Property, string> params;
+ params[acl::PROP_SCHEMACLASS] = object->getClassName();
+
+ if (!acl->authorise(userId, acl::ACT_ACCESS, acl::OBJ_QUERY, object->getObjectId().getV2Key(), &params)) {
+ throw framing::UnauthorizedAccessException(QPID_MSG("unauthorized-access: ACL denied QMF query of object " << object->getObjectId().getV2Key() << " from " << userId));
+ }
+ }
+ if (object->getConfigChanged() || object->getInstChanged())
+ object->setUpdateTime();
+
+ if (!object->isDeleted()) {
+ Variant::Map map_;
+ Variant::Map values;
+ Variant::Map oidMap;
+
+ object->writeTimestamps(map_);
+ object->mapEncodeValues(values, true, true); // write both stats and properties
+ objId.mapEncode(oidMap);
+ map_["_values"] = values;
+ map_["_object_id"] = oidMap;
+ map_["_schema_id"] = mapEncodeSchemaId(object->getPackageName(),
+ object->getClassName(),
+ "_data",
+ object->getMd5Sum());
+ list_.push_back(map_);
+ }
+
+ string content;
+
+ ListCodec::encode(list_, content);
+ sendBuffer(content, cid, headers, "amqp/list", rte, rtk);
+ QPID_LOG(debug, "SENT QueryResponse (query by object_id) to=" << rte << "/" << rtk);
+ return;
+ }
+ } else {
+ // send class-based result.
+ if (acl != 0) {
+ map<acl::Property, string> params;
+ params[acl::PROP_SCHEMACLASS] = className;
+
+ if (!acl->authorise(userId, acl::ACT_ACCESS, acl::OBJ_QUERY, className /* class-wide query */, &params)) {
+ throw framing::UnauthorizedAccessException(QPID_MSG("unauthorized-access: ACL denied QMF query of object class " << className << " from " << userId));
+ }
+ }
+ Variant::List _list;
+ Variant::List _subList;
+ unsigned int objCount = 0;
+
+ ManagementObjectVector localManagementObjects;
+ {
+ sys::Mutex::ScopedLock objLock(objectLock);
+ std::transform(managementObjects.begin(), managementObjects.end(),
+ std::back_inserter(localManagementObjects),
+ boost::bind(&ManagementObjectMap::value_type::second, _1));
+ }
+
+ for (ManagementObjectVector::iterator iter = localManagementObjects.begin();
+ iter != localManagementObjects.end();
+ iter++) {
+ ManagementObject::shared_ptr object = *iter;
+ if (object->getClassName() == className &&
+ (packageName.empty() || object->getPackageName() == packageName)) {
+
+
+ if (!object->isDeleted()) {
+ Variant::Map map_;
+ Variant::Map values;
+ Variant::Map oidMap;
+
+ if (object->getConfigChanged() || object->getInstChanged())
+ object->setUpdateTime();
+
+ object->writeTimestamps(map_);
+ object->mapEncodeValues(values, true, true); // write both stats and properties
+ object->getObjectId().mapEncode(oidMap);
+
+ map_["_values"] = values;
+ map_["_object_id"] = oidMap;
+ map_["_schema_id"] = mapEncodeSchemaId(object->getPackageName(),
+ object->getClassName(),
+ "_data",
+ object->getMd5Sum());
+ _subList.push_back(map_);
+ if (++objCount >= maxReplyObjs) {
+ objCount = 0;
+ _list.push_back(_subList);
+ _subList.clear();
+ }
+ }
+ }
+ }
+
+ if (_subList.size())
+ _list.push_back(_subList);
+
+ headers["partial"] = Variant();
+ string content;
+ while (_list.size() > 1) {
+ ListCodec::encode(_list.front().asList(), content);
+ sendBuffer(content, cid, headers, "amqp/list", rte, rtk);
+ _list.pop_front();
+ QPID_LOG(debug, "SENT QueryResponse (partial, query by schema_id) to=" << rte << "/" << rtk << " len=" << content.length());
+ }
+ headers.erase("partial");
+ ListCodec::encode(_list.size() ? _list.front().asList() : Variant::List(), content);
+ sendBuffer(content, cid, headers, "amqp/list", rte, rtk);
+ QPID_LOG(debug, "SENT QueryResponse (query by schema_id) to=" << rte << "/" << rtk << " len=" << content.length());
+ return;
+ }
+
+ // Unrecognized query - Send empty message to indicate CommandComplete
+ string content;
+ ListCodec::encode(Variant::List(), content);
+ sendBuffer(content, cid, headers, "amqp/list", rte, rtk);
+ QPID_LOG(debug, "SENT QueryResponse (empty) to=" << rte << "/" << rtk);
+}
+
+
+void ManagementAgent::handleLocateRequest(const string&, const string& rte, const string& rtk, const string& cid)
+{
+ QPID_LOG(debug, "RCVD AgentLocateRequest");
+
+ Variant::Map map;
+ Variant::Map headers;
+
+ headers["method"] = "indication";
+ headers["qmf.opcode"] = "_agent_locate_response";
+ headers["qmf.agent"] = name_address;
+
+ map["_values"] = attrMap;
+ map["_values"].asMap()["_timestamp"] = uint64_t(sys::Duration::FromEpoch());
+ map["_values"].asMap()["_heartbeat_interval"] = interval;
+ map["_values"].asMap()["_epoch"] = bootSequence;
+
+ string content;
+ MapCodec::encode(map, content);
+ sendBuffer(content, cid, headers, "amqp/map", rte, rtk);
+ clientWasAdded = true;
+
+ QPID_LOG(debug, "SENT AgentLocateResponse replyTo=" << rte << "/" << rtk);
+}
+
+
+bool ManagementAgent::authorizeAgentMessage(Message& msg)
+{
+ sys::Mutex::ScopedLock lock(userLock);
+ ResizableBuffer inBuffer (qmfV1BufferSize);
+ uint32_t sequence = 0;
+ bool methodReq = false;
+ bool mapMsg = false;
+ string packageName;
+ string className;
+ string methodName;
+ string cid;
+
+ boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer> transfer = protocols->translate(msg);
+ //
+ // If the message is larger than our working buffer size (or if it
+ // could not be converted to an 0-10 messgae-transfer), we can't
+ // determine if it's authorized or not. In this case, return true
+ // (authorized) if there is no ACL in place, otherwise return
+ // false;
+ //
+ if (!transfer || transfer->getContentSize() > qmfV1BufferSize)
+ return broker->getAcl() == 0;
+
+ inBuffer.putRawData(transfer->getContent());
+ uint32_t bufferLen = inBuffer.getPosition();
+ inBuffer.reset();
+
+ const framing::MessageProperties* p =
+ transfer ? transfer->getFrames().getHeaders()->get<framing::MessageProperties>() : 0;
+
+ const framing::FieldTable *headers = p ? &p->getApplicationHeaders() : 0;
+
+ if (headers && p->getAppId() == "qmf2")
+ {
+ mapMsg = true;
+
+ if (p && p->hasCorrelationId()) {
+ cid = p->getCorrelationId();
+ }
+
+ if (headers->getAsString("qmf.opcode") == "_method_request")
+ {
+ methodReq = true;
+
+ // extract object id and method name
+
+ string body;
+ inBuffer.getRawData(body, bufferLen);
+ Variant::Map inMap;
+ MapCodec::decode(body, inMap);
+ Variant::Map::const_iterator oid, mid;
+
+ ObjectId objId;
+
+ if ((oid = inMap.find("_object_id")) == inMap.end() ||
+ (mid = inMap.find("_method_name")) == inMap.end()) {
+ QPID_LOG(warning,
+ "Missing fields in QMF authorize req received.");
+ return false;
+ }
+
+ try {
+ // coversions will throw if input is invalid.
+ objId = ObjectId(oid->second.asMap());
+ methodName = mid->second.getString();
+ } catch(exception& /*e*/) {
+ QPID_LOG(warning,
+ "Badly formatted QMF authorize req received.");
+ return false;
+ }
+
+ // look up schema for object to get package and class name
+ sys::Mutex::ScopedLock lock(objectLock);
+ ManagementObjectMap::iterator iter = managementObjects.find(objId);
+
+ if (iter == managementObjects.end() || iter->second->isDeleted()) {
+ QPID_LOG(debug, "ManagementAgent::authorizeAgentMessage: stale object id " <<
+ objId);
+ return false;
+ }
+
+ packageName = iter->second->getPackageName();
+ className = iter->second->getClassName();
+ }
+ } else { // old style binary message format
+
+ uint8_t opcode;
+
+ if (!checkHeader(inBuffer, &opcode, &sequence))
+ return false;
+
+ if (opcode == 'M') {
+ methodReq = true;
+
+ // extract method name & schema package and class name
+
+ uint8_t hash[16];
+ inBuffer.getLongLong(); // skip over object id
+ inBuffer.getLongLong();
+ inBuffer.getShortString(packageName);
+ inBuffer.getShortString(className);
+ inBuffer.getBin128(hash);
+ inBuffer.getShortString(methodName);
+
+ }
+ }
+
+ if (methodReq) {
+ map<acl::Property, string> params;
+ AclModule* acl = broker->getAcl();
+ if (acl == 0)
+ return true;
+
+ string userId = msg.getUserId();
+ params[acl::PROP_SCHEMAPACKAGE] = packageName;
+ params[acl::PROP_SCHEMACLASS] = className;
+
+ if (acl->authorise(userId, acl::ACT_ACCESS, acl::OBJ_METHOD, methodName, &params))
+ return true;
+
+ // authorization failed, send reply if replyTo present
+
+ boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer> transfer = protocols->translate(msg);
+ const framing::MessageProperties* p =
+ transfer ? transfer->getFrames().getHeaders()->get<framing::MessageProperties>() : 0;
+ if (p && p->hasReplyTo()) {
+ const framing::ReplyTo& rt = p->getReplyTo();
+ string rte = rt.getExchange();
+ string rtk = rt.getRoutingKey();
+ string cid;
+ if (p && p->hasCorrelationId())
+ cid = p->getCorrelationId();
+
+ if (mapMsg) {
+ sendException(rte, rtk, cid, Manageable::StatusText(Manageable::STATUS_FORBIDDEN),
+ Manageable::STATUS_FORBIDDEN, false);
+ } else {
+
+ ResizableBuffer outBuffer(qmfV1BufferSize);
+
+ encodeHeader(outBuffer, 'm', sequence);
+ outBuffer.putLong(Manageable::STATUS_FORBIDDEN);
+ outBuffer.putMediumString(Manageable::StatusText(Manageable::STATUS_FORBIDDEN));
+ sendBuffer(outBuffer, rte, rtk);
+ }
+
+ QPID_LOG(debug, "SEND MethodResponse status=FORBIDDEN" << " seq=" << sequence);
+ }
+
+ return false;
+ }
+
+ return true;
+}
+
+void ManagementAgent::dispatchAgentCommand(Message& msg, bool viaLocal)
+{
+ string rte;
+ string rtk;
+
+ boost::intrusive_ptr<const qpid::broker::amqp_0_10::MessageTransfer> transfer = protocols->translate(msg);
+ const framing::MessageProperties* p = transfer ?
+ transfer->getFrames().getHeaders()->get<framing::MessageProperties>() : 0;
+ if (p && p->hasReplyTo()) {
+ const framing::ReplyTo& rt = p->getReplyTo();
+ rte = rt.getExchange();
+ rtk = rt.getRoutingKey();
+ }
+ else
+ return;
+
+ ResizableBuffer inBuffer(qmfV1BufferSize);
+ uint8_t opcode;
+
+ if (transfer->getContentSize() > qmfV1BufferSize) {
+ QPID_LOG(debug, "ManagementAgent::dispatchAgentCommandLH: Message too large: " <<
+ transfer->getContentSize());
+ return;
+ }
+
+ inBuffer.putRawData(transfer->getContent());
+ uint32_t bufferLen = inBuffer.getPosition();
+ inBuffer.reset();
+
+ ScopedManagementContext context(msg.getPublisher());
+ const framing::FieldTable *headers = p ? &p->getApplicationHeaders() : 0;
+ if (headers && p->getAppId() == "qmf2")
+ {
+ string opcode = headers->getAsString("qmf.opcode");
+ string contentType = headers->getAsString("qmf.content");
+ string body;
+ string cid;
+ inBuffer.getRawData(body, bufferLen);
+ {
+ if (p && p->hasCorrelationId()) {
+ cid = p->getCorrelationId();
+ }
+
+ if (opcode == "_method_request")
+ return handleMethodRequest(body, rte, rtk, cid, context.getUserId(), viaLocal);
+ else if (opcode == "_query_request")
+ return handleGetQuery(body, rte, rtk, cid, context.getUserId(), viaLocal);
+ else if (opcode == "_agent_locate_request")
+ return handleLocateRequest(body, rte, rtk, cid);
+ }
+ QPID_LOG(warning, "Support for QMF Opcode [" << opcode << "] TBD!!!");
+ return;
+ }
+
+ // old preV2 binary messages
+ while (inBuffer.getPosition() < bufferLen) {
+ uint32_t sequence;
+ if (!checkHeader(inBuffer, &opcode, &sequence))
+ return;
+
+ if (opcode == 'B') handleBrokerRequest (inBuffer, rtk, sequence);
+ else if (opcode == 'P') handlePackageQuery (inBuffer, rtk, sequence);
+ else if (opcode == 'p') handlePackageInd (inBuffer, rtk, sequence);
+ else if (opcode == 'Q') handleClassQuery (inBuffer, rtk, sequence);
+ else if (opcode == 'q') handleClassInd (inBuffer, rtk, sequence);
+ else if (opcode == 'S') handleSchemaRequest (inBuffer, rte, rtk, sequence);
+ else if (opcode == 's') handleSchemaResponse (inBuffer, rtk, sequence);
+ else if (opcode == 'A') handleAttachRequest (inBuffer, rtk, sequence, context.getObjectId());
+ else if (opcode == 'G') handleGetQuery (inBuffer, rtk, sequence, context.getMgmtId());
+ else if (opcode == 'M') handleMethodRequest (inBuffer, rtk, sequence, context.getMgmtId());
+ }
+}
+
+ManagementAgent::PackageMap::iterator ManagementAgent::findOrAddPackageLH(string name)
+{
+ PackageMap::iterator pIter = packages.find (name);
+ if (pIter != packages.end ())
+ return pIter;
+
+ // No such package found, create a new map entry.
+ pair<PackageMap::iterator, bool> result =
+ packages.insert(pair<string, ClassMap>(name, ClassMap()));
+ QPID_LOG (debug, "ManagementAgent added package " << name);
+
+ // Publish a package-indication message
+ ResizableBuffer outBuffer (qmfV1BufferSize);
+
+ encodeHeader (outBuffer, 'p');
+ encodePackageIndication (outBuffer, result.first);
+ sendBuffer(outBuffer, mExchange, "schema.package");
+ QPID_LOG(debug, "SEND PackageInd package=" << name << " to=schema.package");
+
+ return result.first;
+}
+
+void ManagementAgent::addClassLH(uint8_t kind,
+ PackageMap::iterator pIter,
+ const string& className,
+ uint8_t* md5Sum,
+ ManagementObject::writeSchemaCall_t schemaCall)
+{
+ SchemaClassKey key;
+ ClassMap& cMap = pIter->second;
+
+ key.name = className;
+ memcpy(&key.hash, md5Sum, 16);
+
+ ClassMap::iterator cIter = cMap.find(key);
+ if (cIter != cMap.end())
+ return;
+
+ // No such class found, create a new class with local information.
+ QPID_LOG (debug, "ManagementAgent added class " << pIter->first << ":" <<
+ key.name);
+
+ cMap.insert(pair<SchemaClassKey, SchemaClass>(key, SchemaClass(kind, schemaCall)));
+ cIter = cMap.find(key);
+}
+
+void ManagementAgent::encodePackageIndication(Buffer& buf,
+ PackageMap::iterator pIter)
+{
+ buf.putShortString((*pIter).first);
+}
+
+void ManagementAgent::encodeClassIndication(Buffer& buf,
+ const std::string packageName,
+ const SchemaClassKey key,
+ uint8_t kind)
+{
+ buf.putOctet(kind);
+ buf.putShortString(packageName);
+ key.encode(buf);
+}
+
+size_t ManagementAgent::validateSchema(Buffer& inBuffer, uint8_t kind)
+{
+ if (kind == ManagementItem::CLASS_KIND_TABLE)
+ return validateTableSchema(inBuffer);
+ else if (kind == ManagementItem::CLASS_KIND_EVENT)
+ return validateEventSchema(inBuffer);
+ return 0;
+}
+
+size_t ManagementAgent::validateTableSchema(Buffer& inBuffer)
+{
+ uint32_t start = inBuffer.getPosition();
+ uint32_t end;
+ string text;
+ uint8_t hash[16];
+
+ try {
+ uint8_t kind = inBuffer.getOctet();
+ if (kind != ManagementItem::CLASS_KIND_TABLE)
+ return 0;
+
+ inBuffer.getShortString(text);
+ inBuffer.getShortString(text);
+ inBuffer.getBin128(hash);
+
+ uint8_t superType = 0; //inBuffer.getOctet();
+
+ uint16_t propCount = inBuffer.getShort();
+ uint16_t statCount = inBuffer.getShort();
+ uint16_t methCount = inBuffer.getShort();
+
+ if (superType == 1) {
+ inBuffer.getShortString(text);
+ inBuffer.getShortString(text);
+ inBuffer.getBin128(hash);
+ }
+
+ for (uint16_t idx = 0; idx < propCount + statCount; idx++) {
+ FieldTable ft;
+ ft.decode(inBuffer);
+ }
+
+ for (uint16_t idx = 0; idx < methCount; idx++) {
+ FieldTable ft;
+ ft.decode(inBuffer);
+ if (!ft.isSet("argCount"))
+ return 0;
+ int argCount = ft.getAsInt("argCount");
+ for (int mIdx = 0; mIdx < argCount; mIdx++) {
+ FieldTable aft;
+ aft.decode(inBuffer);
+ }
+ }
+ } catch (exception& /*e*/) {
+ return 0;
+ }
+
+ end = inBuffer.getPosition();
+ inBuffer.setPosition(start); // restore original position
+ return end - start;
+}
+
+size_t ManagementAgent::validateEventSchema(Buffer& inBuffer)
+{
+ uint32_t start = inBuffer.getPosition();
+ uint32_t end;
+ string text;
+ uint8_t hash[16];
+
+ try {
+ uint8_t kind = inBuffer.getOctet();
+ if (kind != ManagementItem::CLASS_KIND_EVENT)
+ return 0;
+
+ inBuffer.getShortString(text);
+ inBuffer.getShortString(text);
+ inBuffer.getBin128(hash);
+
+ uint8_t superType = 0; //inBuffer.getOctet();
+
+ uint16_t argCount = inBuffer.getShort();
+
+ if (superType == 1) {
+ inBuffer.getShortString(text);
+ inBuffer.getShortString(text);
+ inBuffer.getBin128(hash);
+ }
+ for (uint16_t idx = 0; idx < argCount; idx++) {
+ FieldTable ft;
+ ft.decode(inBuffer);
+ }
+ } catch (exception& /*e*/) {
+ return 0;
+ }
+
+ end = inBuffer.getPosition();
+ inBuffer.setPosition(start); // restore original position
+ return end - start;
+}
+
+ManagementObjectMap::iterator ManagementAgent::numericFind(const ObjectId& oid)
+{
+ ManagementObjectMap::iterator iter = managementObjects.begin();
+ for (; iter != managementObjects.end(); iter++) {
+ if (oid.equalV1(iter->first))
+ break;
+ }
+
+ return iter;
+}
+
+void ManagementAgent::disallow(const string& className, const string& methodName, const string& message) {
+ disallowed[make_pair(className, methodName)] = message;
+}
+
+void ManagementAgent::SchemaClassKey::mapEncode(Variant::Map& _map) const {
+ _map["_cname"] = name;
+ _map["_hash"] = qpid::types::Uuid(hash);
+}
+
+void ManagementAgent::SchemaClassKey::mapDecode(const Variant::Map& _map) {
+ Variant::Map::const_iterator i;
+
+ if ((i = _map.find("_cname")) != _map.end()) {
+ name = i->second.asString();
+ }
+
+ if ((i = _map.find("_hash")) != _map.end()) {
+ const qpid::types::Uuid& uuid = i->second.asUuid();
+ memcpy(hash, uuid.data(), uuid.size());
+ }
+}
+
+void ManagementAgent::SchemaClassKey::encode(qpid::framing::Buffer& buffer) const {
+ buffer.checkAvailable(encodedBufSize());
+ buffer.putShortString(name);
+ buffer.putBin128(hash);
+}
+
+void ManagementAgent::SchemaClassKey::decode(qpid::framing::Buffer& buffer) {
+ buffer.checkAvailable(encodedBufSize());
+ buffer.getShortString(name);
+ buffer.getBin128(hash);
+}
+
+uint32_t ManagementAgent::SchemaClassKey::encodedBufSize() const {
+ return 1 + name.size() + 16 /* bin128 */;
+}
+
+void ManagementAgent::SchemaClass::mapEncode(Variant::Map& _map) const {
+ _map["_type"] = kind;
+ _map["_pending_sequence"] = pendingSequence;
+ _map["_data"] = data;
+}
+
+void ManagementAgent::SchemaClass::mapDecode(const Variant::Map& _map) {
+ Variant::Map::const_iterator i;
+
+ if ((i = _map.find("_type")) != _map.end()) {
+ kind = i->second;
+ }
+ if ((i = _map.find("_pending_sequence")) != _map.end()) {
+ pendingSequence = i->second;
+ }
+ if ((i = _map.find("_data")) != _map.end()) {
+ data = i->second.asString();
+ }
+}
+
+void ManagementAgent::RemoteAgent::mapEncode(Variant::Map& map_) const {
+ Variant::Map _objId, _values;
+
+ map_["_brokerBank"] = brokerBank;
+ map_["_agentBank"] = agentBank;
+ map_["_routingKey"] = routingKey;
+
+ connectionRef.mapEncode(_objId);
+ map_["_object_id"] = _objId;
+
+ mgmtObject->mapEncodeValues(_values, true, false);
+ map_["_values"] = _values;
+}
+
+void ManagementAgent::RemoteAgent::mapDecode(const Variant::Map& map_) {
+ Variant::Map::const_iterator i;
+
+ if ((i = map_.find("_brokerBank")) != map_.end()) {
+ brokerBank = i->second;
+ }
+
+ if ((i = map_.find("_agentBank")) != map_.end()) {
+ agentBank = i->second;
+ }
+
+ if ((i = map_.find("_routingKey")) != map_.end()) {
+ routingKey = i->second.getString();
+ }
+
+ if ((i = map_.find("_object_id")) != map_.end()) {
+ connectionRef.mapDecode(i->second.asMap());
+ }
+
+ mgmtObject = _qmf::Agent::shared_ptr(new _qmf::Agent(&agent, this));
+
+ if ((i = map_.find("_values")) != map_.end()) {
+ mgmtObject->mapDecodeValues(i->second.asMap());
+ }
+
+ // TODO aconway 2010-03-04: see comment in encode(), readProperties doesn't set v2key.
+ mgmtObject->set_connectionRef(connectionRef);
+}
+
+namespace {
+bool isDeletedMap(const ManagementObjectMap::value_type& value) {
+ return value.second->isDeleted();
+}
+
+bool isDeletedVector(const ManagementObjectVector::value_type& value) {
+ return value->isDeleted();
+}
+
+string summarizeMap(const char* name, const ManagementObjectMap& map) {
+ ostringstream o;
+ size_t deleted = std::count_if(map.begin(), map.end(), isDeletedMap);
+ o << map.size() << " " << name << " (" << deleted << " deleted), ";
+ return o.str();
+}
+
+string summarizeVector(const char* name, const ManagementObjectVector& map) {
+ ostringstream o;
+ size_t deleted = std::count_if(map.begin(), map.end(), isDeletedVector);
+ o << map.size() << " " << name << " (" << deleted << " deleted), ";
+ return o.str();
+}
+
+string dumpMap(const ManagementObjectMap& map) {
+ ostringstream o;
+ for (ManagementObjectMap::const_iterator i = map.begin(); i != map.end(); ++i) {
+ o << endl << " " << i->second->getObjectId().getV2Key()
+ << (i->second->isDeleted() ? " (deleted)" : "");
+ }
+ return o.str();
+}
+
+string dumpVector(const ManagementObjectVector& map) {
+ ostringstream o;
+ for (ManagementObjectVector::const_iterator i = map.begin(); i != map.end(); ++i) {
+ o << endl << " " << (*i)->getObjectId().getV2Key()
+ << ((*i)->isDeleted() ? " (deleted)" : "");
+ }
+ return o.str();
+}
+
+} // namespace
+
+string ManagementAgent::summarizeAgents() {
+ ostringstream msg;
+ if (!remoteAgents.empty()) {
+ msg << remoteAgents.size() << " agents(";
+ for (RemoteAgentMap::const_iterator i=remoteAgents.begin();
+ i != remoteAgents.end(); ++i)
+ msg << " " << i->second->routingKey;
+ msg << "), ";
+ }
+ return msg.str();
+}
+
+
+void ManagementAgent::debugSnapshot(const char* title) {
+ sys::Mutex::ScopedLock lock(addLock);
+ sys::Mutex::ScopedLock objLock (objectLock);
+ QPID_LOG(debug, title << ": management snapshot: "
+ << packages.size() << " packages, "
+ << summarizeMap("objects", managementObjects)
+ << summarizeVector("new objects ", newManagementObjects)
+ << pendingDeletedObjs.size() << " pending deletes"
+ << summarizeAgents());
+
+ QPID_LOG_IF(trace, managementObjects.size(),
+ title << ": objects" << dumpMap(managementObjects));
+ QPID_LOG_IF(trace, newManagementObjects.size(),
+ title << ": new objects" << dumpVector(newManagementObjects));
+}
+
+
+Variant::Map ManagementAgent::toMap(const FieldTable& from)
+{
+ Variant::Map map;
+ qpid::amqp_0_10::translate(from, map);
+ return map;
+}
+
+// construct a DeletedObject from a management object.
+ManagementAgent::DeletedObject::DeletedObject(ManagementObject::shared_ptr src, bool v1, bool v2)
+ : packageName(src->getPackageName()),
+ className(src->getClassName())
+{
+ bool send_stats = (src->hasInst() && (src->getInstChanged() || src->getForcePublish()));
+
+ stringstream oid;
+ oid << src->getObjectId();
+ objectId = oid.str();
+
+ if (v1) {
+ src->writeProperties(encodedV1Config);
+ if (send_stats) {
+ src->writeStatistics(encodedV1Inst);
+ }
+ }
+
+ if (v2) {
+ Variant::Map map_;
+ Variant::Map values;
+ Variant::Map oid;
+
+ src->getObjectId().mapEncode(oid);
+ map_["_object_id"] = oid;
+ map_["_schema_id"] = mapEncodeSchemaId(src->getPackageName(),
+ src->getClassName(),
+ "_data",
+ src->getMd5Sum());
+ src->writeTimestamps(map_);
+ src->mapEncodeValues(values, true, send_stats);
+ map_["_values"] = values;
+
+ encodedV2 = map_;
+ }
+}
+
+// Remove Deleted objects, and save for later publishing...
+bool ManagementAgent::moveDeletedObjects() {
+ typedef vector<pair<ObjectId, ManagementObject::shared_ptr> > DeleteList;
+
+ sys::Mutex::ScopedLock lock (objectLock);
+
+ DeleteList deleteList;
+ for (ManagementObjectMap::iterator iter = managementObjects.begin();
+ iter != managementObjects.end();
+ ++iter)
+ {
+ ManagementObject::shared_ptr object = iter->second;
+ if (object->isDeleted()) deleteList.push_back(*iter);
+ }
+
+ // Iterate in reverse over deleted object list
+ for (DeleteList::reverse_iterator iter = deleteList.rbegin();
+ iter != deleteList.rend();
+ iter++)
+ {
+ ManagementObject::shared_ptr delObj = iter->second;
+ assert(delObj->isDeleted());
+ DeletedObject::shared_ptr dptr(new DeletedObject(delObj, qmf1Support, qmf2Support));
+
+ pendingDeletedObjs[dptr->getKey()].push_back(dptr);
+ managementObjects.erase(iter->first);
+ }
+ return !deleteList.empty();
+}
+
+ManagementAgent::EventQueue::Batch::const_iterator ManagementAgent::sendEvents(
+ const EventQueue::Batch& batch)
+{
+ EventQueue::Batch::const_iterator i;
+ for (i = batch.begin(); i != batch.end(); ++i) {
+ DeliverableMessage deliverable (i->second, 0);
+ try {
+ i->first->route(deliverable);
+ } catch(exception& e) {
+ QPID_LOG(warning, "ManagementAgent failed to route event: " << e.what());
+ }
+ }
+ return i;
+}
+
+namespace {
+QPID_TSS const Connection* currentPublisher = 0;
+}
+
+void setManagementExecutionContext(const Connection& p)
+{
+ currentPublisher = &p;
+}
+
+void resetManagementExecutionContext()
+{
+ currentPublisher = 0;
+}
+
+const Connection* getCurrentPublisher()
+{
+ return currentPublisher;
+}
+
+}}
+
diff --git a/qpid/cpp/src/qpid/management/ManagementAgent.h b/qpid/cpp/src/qpid/management/ManagementAgent.h
new file mode 100644
index 0000000000..81bf542766
--- /dev/null
+++ b/qpid/cpp/src/qpid/management/ManagementAgent.h
@@ -0,0 +1,388 @@
+#ifndef _ManagementAgent_
+#define _ManagementAgent_
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/BrokerImportExport.h"
+#include "qpid/Options.h"
+#include "qpid/broker/Exchange.h"
+#include "qpid/framing/Uuid.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/management/ManagementObject.h"
+#include "qpid/management/ManagementEvent.h"
+#include "qpid/management/Manageable.h"
+#include "qmf/org/apache/qpid/broker/Agent.h"
+#include "qmf/org/apache/qpid/broker/Memory.h"
+#include "qpid/sys/MemStat.h"
+#include "qpid/sys/PollableQueue.h"
+#include "qpid/types/Variant.h"
+#include <qpid/framing/AMQFrame.h>
+#include <qpid/framing/ResizableBuffer.h>
+#include <boost/shared_ptr.hpp>
+#include <memory>
+#include <string>
+#include <map>
+
+namespace qpid {
+namespace broker {
+class Connection;
+class ProtocolRegistry;
+}
+namespace sys {
+class Timer;
+}
+namespace management {
+
+class ManagementAgent
+{
+private:
+
+ int threadPoolSize;
+
+public:
+ typedef enum {
+ SEV_EMERG = 0,
+ SEV_ALERT = 1,
+ SEV_CRIT = 2,
+ SEV_ERROR = 3,
+ SEV_WARN = 4,
+ SEV_NOTE = 5,
+ SEV_INFO = 6,
+ SEV_DEBUG = 7,
+ SEV_DEFAULT = 8
+ } severity_t;
+
+
+ ManagementAgent (const bool qmfV1, const bool qmfV2);
+ virtual ~ManagementAgent ();
+
+ /** Called before plugins are initialized */
+ void configure (const std::string& dataDir, bool publish, uint16_t interval,
+ qpid::broker::Broker* broker, int threadPoolSize);
+
+ void setName(const std::string& vendor,
+ const std::string& product,
+ const std::string& instance="");
+ void getName(std::string& vendor, std::string& product, std::string& instance);
+ const std::string& getAddress();
+
+ void setInterval(uint16_t _interval) { interval = _interval; }
+ void setExchange(qpid::broker::Exchange::shared_ptr mgmtExchange,
+ qpid::broker::Exchange::shared_ptr directExchange);
+ void setExchangeV2(qpid::broker::Exchange::shared_ptr topicExchange,
+ qpid::broker::Exchange::shared_ptr directExchange);
+
+ int getMaxThreads () { return threadPoolSize; }
+ QPID_BROKER_EXTERN void registerClass (const std::string& packageName,
+ const std::string& className,
+ uint8_t* md5Sum,
+ ManagementObject::writeSchemaCall_t schemaCall);
+ QPID_BROKER_EXTERN void registerEvent (const std::string& packageName,
+ const std::string& eventName,
+ uint8_t* md5Sum,
+ ManagementObject::writeSchemaCall_t schemaCall);
+ QPID_BROKER_EXTERN ObjectId addObject (ManagementObject::shared_ptr object,
+ uint64_t persistId = 0,
+ bool persistent = false);
+ QPID_BROKER_EXTERN ObjectId addObject (ManagementObject::shared_ptr object,
+ const std::string& key,
+ bool persistent = false);
+ QPID_BROKER_EXTERN void raiseEvent(const ManagementEvent& event,
+ severity_t severity = SEV_DEFAULT);
+ QPID_BROKER_EXTERN void clientAdded (const std::string& routingKey);
+
+ bool dispatchCommand (qpid::broker::Deliverable& msg,
+ const std::string& routingKey,
+ const framing::FieldTable* args,
+ const bool topic,
+ int qmfVersion);
+
+ /** Disallow a method. Attempts to call it will receive an exception with message. */
+ void disallow(const std::string& className, const std::string& methodName, const std::string& message);
+
+ uint16_t getBootSequence(void) { return bootSequence; }
+ void setBootSequence(uint16_t b) { bootSequence = b; writeData(); }
+
+ const framing::Uuid& getUuid() const { return uuid; }
+ void setUuid(const framing::Uuid& id) { uuid = id; writeData(); }
+
+ static types::Variant::Map toMap(const framing::FieldTable& from);
+
+ class DeletedObject {
+ public:
+ typedef boost::shared_ptr<DeletedObject> shared_ptr;
+ DeletedObject(ManagementObject::shared_ptr, bool v1, bool v2);
+ ~DeletedObject() {};
+ const std::string getKey() const {
+ // used to batch up objects of the same class type
+ return std::string(packageName + std::string(":") + className);
+ }
+
+ private:
+ friend class ManagementAgent;
+
+ std::string packageName;
+ std::string className;
+ std::string objectId;
+
+ std::string encodedV1Config; // qmfv1 properties
+ std::string encodedV1Inst; // qmfv1 statistics
+ qpid::types::Variant::Map encodedV2;
+ };
+
+ typedef std::vector<DeletedObject::shared_ptr> DeletedObjectList;
+
+private:
+ // Storage for tracking remote management agents, attached via the client
+ // management agent API.
+ //
+ struct RemoteAgent : public Manageable
+ {
+ ManagementAgent& agent;
+ uint32_t brokerBank;
+ uint32_t agentBank;
+ std::string routingKey;
+ ObjectId connectionRef;
+ qmf::org::apache::qpid::broker::Agent::shared_ptr mgmtObject;
+ RemoteAgent(ManagementAgent& _agent) : agent(_agent) {}
+ ManagementObject::shared_ptr GetManagementObject (void) const { return mgmtObject; }
+
+ virtual ~RemoteAgent ();
+ void mapEncode(qpid::types::Variant::Map& _map) const;
+ void mapDecode(const qpid::types::Variant::Map& _map);
+ };
+
+ typedef std::map<ObjectId, boost::shared_ptr<RemoteAgent> > RemoteAgentMap;
+
+ // Storage for known schema classes:
+ //
+ // SchemaClassKey -- Key elements for map lookups
+ // SchemaClassKeyComp -- Comparison class for SchemaClassKey
+ // SchemaClass -- Non-key elements for classes
+ //
+ struct SchemaClassKey
+ {
+ std::string name;
+ uint8_t hash[16];
+
+ void mapEncode(qpid::types::Variant::Map& _map) const;
+ void mapDecode(const qpid::types::Variant::Map& _map);
+ void encode(framing::Buffer& buffer) const;
+ void decode(framing::Buffer& buffer);
+ uint32_t encodedBufSize() const;
+ };
+
+ struct SchemaClassKeyComp
+ {
+ bool operator() (const SchemaClassKey& lhs, const SchemaClassKey& rhs) const
+ {
+ if (lhs.name != rhs.name)
+ return lhs.name < rhs.name;
+ else
+ for (int i = 0; i < 16; i++)
+ if (lhs.hash[i] != rhs.hash[i])
+ return lhs.hash[i] < rhs.hash[i];
+ return false;
+ }
+ };
+
+
+ struct SchemaClass
+ {
+ uint8_t kind;
+ ManagementObject::writeSchemaCall_t writeSchemaCall;
+ std::string data;
+ uint32_t pendingSequence;
+
+ SchemaClass(uint8_t _kind=0, uint32_t seq=0) :
+ kind(_kind), writeSchemaCall(0), pendingSequence(seq) {}
+ SchemaClass(uint8_t _kind, ManagementObject::writeSchemaCall_t call) :
+ kind(_kind), writeSchemaCall(call), pendingSequence(0) {}
+ bool hasSchema () { return (writeSchemaCall != 0) || !data.empty(); }
+ void appendSchema (framing::Buffer& buf);
+
+ void mapEncode(qpid::types::Variant::Map& _map) const;
+ void mapDecode(const qpid::types::Variant::Map& _map);
+ };
+
+ typedef std::map<SchemaClassKey, SchemaClass, SchemaClassKeyComp> ClassMap;
+ typedef std::map<std::string, ClassMap> PackageMap;
+
+ RemoteAgentMap remoteAgents;
+ PackageMap packages;
+
+ //
+ // Protected by objectLock
+ //
+ ManagementObjectMap managementObjects;
+
+ //
+ // Protected by addLock
+ //
+ ManagementObjectVector newManagementObjects;
+
+ framing::Uuid uuid;
+
+ //
+ // Lock ordering: userLock -> addLock -> objectLock
+ //
+ sys::Mutex userLock;
+ sys::Mutex addLock;
+ sys::Mutex objectLock;
+
+ qpid::broker::Exchange::shared_ptr mExchange;
+ qpid::broker::Exchange::shared_ptr dExchange;
+ qpid::broker::Exchange::shared_ptr v2Topic;
+ qpid::broker::Exchange::shared_ptr v2Direct;
+ std::string dataDir;
+ bool publish;
+ uint16_t interval;
+ qpid::broker::Broker* broker;
+ qpid::sys::Timer* timer;
+ qpid::broker::ProtocolRegistry* protocols;
+ uint16_t bootSequence;
+ uint32_t nextObjectId;
+ uint32_t brokerBank;
+ uint32_t nextRemoteBank;
+ uint32_t nextRequestSequence;
+ bool clientWasAdded;
+ const qpid::sys::AbsTime startTime;
+ bool suppressed;
+
+ typedef std::pair<std::string,std::string> MethodName;
+ typedef std::map<MethodName, std::string> DisallowedMethods;
+ DisallowedMethods disallowed;
+ bool disallowAllV1Methods;
+
+ // Agent name and address
+ qpid::types::Variant::Map attrMap;
+ std::string name_address;
+ std::string vendorNameKey; // "." --> "_"
+ std::string productNameKey; // "." --> "_"
+ std::string instanceNameKey; // "." --> "_"
+
+ // supported management protocol
+ bool qmf1Support;
+ bool qmf2Support;
+
+ // Maximum # of objects allowed in a single V2 response
+ // message.
+ uint32_t maxReplyObjs;
+
+ // list of objects that have been deleted, but have yet to be published
+ // one final time.
+ // Indexed by a string composed of the object's package and class name.
+ // Protected by objectLock.
+ typedef std::map<std::string, DeletedObjectList> PendingDeletedObjsMap;
+ PendingDeletedObjsMap pendingDeletedObjs;
+
+ // Pollable queue to serialize event messages
+ typedef std::pair<boost::shared_ptr<broker::Exchange>,
+ broker::Message> ExchangeAndMessage;
+ typedef sys::PollableQueue<ExchangeAndMessage> EventQueue;
+
+ //
+ // Memory statistics object
+ //
+ qmf::org::apache::qpid::broker::Memory::shared_ptr memstat;
+
+ void writeData ();
+ void periodicProcessing (void);
+ void deleteObjectNow(const ObjectId& oid);
+ void encodeHeader (framing::Buffer& buf, uint8_t opcode, uint32_t seq = 0);
+ bool checkHeader (framing::Buffer& buf, uint8_t *opcode, uint32_t *seq);
+ EventQueue::Batch::const_iterator sendEvents(const EventQueue::Batch& batch);
+ void sendBuffer(framing::Buffer& buf,
+ qpid::broker::Exchange::shared_ptr exchange,
+ const std::string& routingKey);
+ void sendBuffer(framing::Buffer& buf,
+ const std::string& exchange,
+ const std::string& routingKey);
+ void sendBuffer(const std::string& data,
+ const std::string& cid,
+ const qpid::types::Variant::Map& headers,
+ const std::string& content_type,
+ qpid::broker::Exchange::shared_ptr exchange,
+ const std::string& routingKey,
+ uint64_t ttl_msec = 0);
+ void sendBuffer(const std::string& data,
+ const std::string& cid,
+ const qpid::types::Variant::Map& headers,
+ const std::string& content_type,
+ const std::string& exchange,
+ const std::string& routingKey,
+ uint64_t ttl_msec = 0);
+ void moveNewObjects();
+ bool moveDeletedObjects();
+
+ bool authorizeAgentMessage(qpid::broker::Message& msg);
+ void dispatchAgentCommand(qpid::broker::Message& msg, bool viaLocal=false);
+
+ PackageMap::iterator findOrAddPackageLH(std::string name);
+ void addClassLH(uint8_t kind,
+ PackageMap::iterator pIter,
+ const std::string& className,
+ uint8_t* md5Sum,
+ ManagementObject::writeSchemaCall_t schemaCall);
+ void encodePackageIndication (framing::Buffer& buf,
+ PackageMap::iterator pIter);
+ void encodeClassIndication (framing::Buffer& buf,
+ const std::string packageName,
+ const struct SchemaClassKey key,
+ uint8_t kind);
+ bool bankInUse (uint32_t bank);
+ uint32_t allocateNewBank ();
+ uint32_t assignBankLH (uint32_t requestedPrefix);
+ void deleteOrphanedAgentsLH();
+ void sendCommandComplete(const std::string& replyToKey, uint32_t sequence,
+ uint32_t code = 0, const std::string& text = "OK");
+ void sendException(const std::string& rte, const std::string& rtk, const std::string& cid, const std::string& text, uint32_t code=1, bool viaLocal=false);
+ void handleBrokerRequest (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence);
+ void handlePackageQuery (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence);
+ void handlePackageInd (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence);
+ void handleClassQuery (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence);
+ void handleClassInd (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence);
+ void handleSchemaRequest (framing::Buffer& inBuffer, const std::string& replyToEx, const std::string& replyToKey, uint32_t sequence);
+ void handleSchemaResponse (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence);
+ void handleAttachRequest (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence, const ObjectId& objectId);
+ void handleGetQuery (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence, const std::string& userId);
+ void handleMethodRequest (framing::Buffer& inBuffer, const std::string& replyToKey, uint32_t sequence, const std::string& userId);
+ void handleGetQuery (const std::string& body, const std::string& replyToEx, const std::string& replyToKey, const std::string& cid, const std::string& userId, bool viaLocal);
+ void handleMethodRequest (const std::string& body, const std::string& replyToEx, const std::string& replyToKey, const std::string& cid, const std::string& userId, bool viaLocal);
+ void handleLocateRequest (const std::string& body, const std::string& replyToEx, const std::string &replyToKey, const std::string& cid);
+
+
+ size_t validateSchema(framing::Buffer&, uint8_t kind);
+ size_t validateTableSchema(framing::Buffer&);
+ size_t validateEventSchema(framing::Buffer&);
+ ManagementObjectMap::iterator numericFind(const ObjectId& oid);
+
+ std::string summarizeAgents();
+ void debugSnapshot(const char* title);
+ std::auto_ptr<EventQueue> sendQueue;
+};
+
+void setManagementExecutionContext(const broker::Connection&);
+void resetManagementExecutionContext();
+const broker::Connection* getCurrentPublisher();
+}}
+
+#endif /*!_ManagementAgent_*/
diff --git a/qpid/cpp/src/qpid/management/ManagementDirectExchange.cpp b/qpid/cpp/src/qpid/management/ManagementDirectExchange.cpp
new file mode 100644
index 0000000000..8ede6940b0
--- /dev/null
+++ b/qpid/cpp/src/qpid/management/ManagementDirectExchange.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/management/ManagementDirectExchange.h"
+#include "qpid/log/Statement.h"
+#include <assert.h>
+
+using namespace qpid::management;
+using namespace qpid::broker;
+using namespace qpid::framing;
+using namespace qpid::sys;
+
+ManagementDirectExchange::ManagementDirectExchange(const std::string& _name, Manageable* _parent, Broker* b) :
+ Exchange (_name, _parent, b),
+ DirectExchange(_name, _parent, b),
+ managementAgent(0) {}
+ManagementDirectExchange::ManagementDirectExchange(const std::string& _name,
+ bool _durable,
+ const FieldTable& _args,
+ Manageable* _parent, Broker* b) :
+ Exchange (_name, _durable, false, _args, _parent, b),
+ DirectExchange(_name, _durable, false, _args, _parent, b),
+ managementAgent(0) {}
+
+void ManagementDirectExchange::route(Deliverable& msg)
+{
+ bool routeIt = true;
+
+ if (managementAgent)
+ routeIt = managementAgent->dispatchCommand(msg, msg.getMessage().getRoutingKey(), 0/*args - TODO*/, false, qmfVersion);
+
+ if (routeIt)
+ DirectExchange::route(msg);
+}
+
+void ManagementDirectExchange::setManagmentAgent(ManagementAgent* agent, int qv)
+{
+ managementAgent = agent;
+ qmfVersion = qv;
+ assert(qmfVersion == 2); // QMFv1 doesn't use a specialized direct exchange
+}
+
+
+ManagementDirectExchange::~ManagementDirectExchange() {}
+
+const std::string ManagementDirectExchange::typeName("management-direct");
+
diff --git a/qpid/cpp/src/qpid/management/ManagementDirectExchange.h b/qpid/cpp/src/qpid/management/ManagementDirectExchange.h
new file mode 100644
index 0000000000..582354d723
--- /dev/null
+++ b/qpid/cpp/src/qpid/management/ManagementDirectExchange.h
@@ -0,0 +1,57 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#ifndef _ManagementDirectExchange_
+#define _ManagementDirectExchange_
+
+#include "qpid/broker/DirectExchange.h"
+#include "qpid/management/ManagementAgent.h"
+
+namespace qpid {
+namespace broker {
+
+class ManagementDirectExchange : public virtual DirectExchange
+{
+ private:
+ management::ManagementAgent* managementAgent;
+ int qmfVersion;
+
+ public:
+ static const std::string typeName;
+
+ ManagementDirectExchange(const std::string& name, Manageable* _parent = 0, Broker* broker = 0);
+ ManagementDirectExchange(const std::string& _name, bool _durable,
+ const qpid::framing::FieldTable& _args,
+ Manageable* _parent = 0, Broker* broker = 0);
+
+ virtual std::string getType() const { return typeName; }
+
+ virtual void route(Deliverable& msg);
+
+ void setManagmentAgent(management::ManagementAgent* agent, int qmfVersion);
+
+ virtual ~ManagementDirectExchange();
+};
+
+
+}
+}
+
+#endif
diff --git a/qpid/cpp/src/qpid/management/ManagementEvent.h b/qpid/cpp/src/qpid/management/ManagementEvent.h
new file mode 100644
index 0000000000..e80175096f
--- /dev/null
+++ b/qpid/cpp/src/qpid/management/ManagementEvent.h
@@ -0,0 +1,53 @@
+#ifndef _ManagementEvent_
+#define _ManagementEvent_
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/management/ManagementObject.h"
+#include "qpid/types/Variant.h"
+#include <string>
+
+namespace qpid {
+namespace management {
+
+class ManagementAgent;
+
+class ManagementEvent : public ManagementItem {
+ public:
+ static const uint8_t MD5_LEN = 16;
+ //typedef void (*writeSchemaCall_t)(qpid::framing::Buffer&);
+ typedef void (*writeSchemaCall_t)(std::string&);
+ virtual ~ManagementEvent() {}
+
+ virtual writeSchemaCall_t getWriteSchemaCall(void) = 0;
+ //virtual mapEncodeSchemaCall_t getMapEncodeSchemaCall(void) = 0;
+ virtual std::string& getEventName() const = 0;
+ virtual std::string& getPackageName() const = 0;
+ virtual uint8_t* getMd5Sum() const = 0;
+ virtual uint8_t getSeverity() const = 0;
+ virtual void encode(std::string&) const = 0;
+ virtual void mapEncode(qpid::types::Variant::Map&) const = 0;
+};
+
+}}
+
+#endif /*!_ManagementEvent_*/
diff --git a/qpid/cpp/src/qpid/management/ManagementObject.cpp b/qpid/cpp/src/qpid/management/ManagementObject.cpp
new file mode 100644
index 0000000000..019963e832
--- /dev/null
+++ b/qpid/cpp/src/qpid/management/ManagementObject.cpp
@@ -0,0 +1,385 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/management/Manageable.h"
+#include "qpid/management/ManagementObject.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/sys/Time.h"
+#include "qpid/sys/Thread.h"
+#include "qpid/log/Statement.h"
+#include <boost/lexical_cast.hpp>
+
+#include <stdlib.h>
+
+using namespace std;
+using namespace qpid;
+using namespace qpid::management;
+
+void AgentAttachment::setBanks(uint32_t broker, uint32_t bank)
+{
+ first =
+ ((uint64_t) (broker & 0x000fffff)) << 28 |
+ ((uint64_t) (bank & 0x0fffffff));
+}
+
+// Deprecated
+ObjectId::ObjectId(uint8_t flags, uint16_t seq, uint32_t broker, uint64_t object)
+ : agent(0), agentEpoch(seq)
+{
+ first =
+ ((uint64_t) (flags & 0x0f)) << 60 |
+ ((uint64_t) (seq & 0x0fff)) << 48 |
+ ((uint64_t) (broker & 0x000fffff)) << 28;
+ second = object;
+}
+
+
+ObjectId::ObjectId(uint8_t flags, uint16_t seq, uint32_t broker)
+ : agent(0), second(0), agentEpoch(seq)
+{
+ first =
+ ((uint64_t) (flags & 0x0f)) << 60 |
+ ((uint64_t) (seq & 0x0fff)) << 48 |
+ ((uint64_t) (broker & 0x000fffff)) << 28;
+}
+
+ObjectId::ObjectId(AgentAttachment* _agent, uint8_t flags, uint16_t seq)
+ : agent(_agent), second(0), agentEpoch(seq)
+{
+
+ first =
+ ((uint64_t) (flags & 0x0f)) << 60 |
+ ((uint64_t) (seq & 0x0fff)) << 48;
+}
+
+
+ObjectId::ObjectId(istream& in) : agent(0)
+{
+ string text;
+ in >> text;
+ fromString(text);
+}
+
+ObjectId::ObjectId(const string& text) : agent(0)
+{
+ fromString(text);
+}
+
+void ObjectId::fromString(const string& text)
+{
+#define FIELDS 5
+#if defined (_WIN32) && !defined (atoll)
+# define atoll(X) _atoi64(X)
+#endif
+
+ // format:
+ // V1: <flags>-<sequence>-<broker-bank>-<agent-bank>-<uint64-app-id>
+ // V2: Not used
+
+ string copy(text.c_str());
+ char* cText;
+ char* field[FIELDS];
+ bool atFieldStart = true;
+ int idx = 0;
+
+ cText = const_cast<char*>(copy.c_str());
+ for (char* cursor = cText; *cursor; cursor++) {
+ if (atFieldStart) {
+ if (idx >= FIELDS)
+ throw Exception("Invalid ObjectId format");
+ field[idx++] = cursor;
+ atFieldStart = false;
+ } else {
+ if (*cursor == '-') {
+ *cursor = '\0';
+ atFieldStart = true;
+ }
+ }
+ }
+
+ if (idx != FIELDS)
+ throw Exception("Invalid ObjectId format");
+
+ agentEpoch = atoll(field[1]);
+
+ first = (atoll(field[0]) << 60) +
+ (atoll(field[1]) << 48) +
+ (atoll(field[2]) << 28);
+
+ agentName = string(field[3]);
+ second = atoll(field[4]);
+}
+
+
+bool ObjectId::operator==(const ObjectId &other) const
+{
+ return v2Key == other.v2Key;
+}
+
+bool ObjectId::operator<(const ObjectId &other) const
+{
+ return v2Key < other.v2Key;
+}
+
+bool ObjectId::equalV1(const ObjectId &other) const
+{
+ uint64_t otherFirst = agent == 0 ? other.first : other.first & 0xffff000000000000LL;
+ return first == otherFirst && second == other.second;
+}
+
+// encode as V1-format binary
+void ObjectId::encode(string& buffer) const
+{
+ const uint32_t len = 16;
+ char _data[len];
+ qpid::framing::Buffer body(_data, len);
+
+ if (agent == 0)
+ body.putLongLong(first);
+ else
+ body.putLongLong(first | agent->first);
+ body.putLongLong(second);
+
+ body.reset();
+ body.getRawData(buffer, len);
+}
+
+// decode as V1-format binary
+void ObjectId::decode(const string& buffer)
+{
+ const uint32_t len = 16;
+ char _data[len];
+ qpid::framing::Buffer body(_data, len);
+
+ body.checkAvailable(buffer.length());
+ body.putRawData(buffer);
+ body.reset();
+ first = body.getLongLong();
+ second = body.getLongLong();
+ v2Key = boost::lexical_cast<string>(second);
+}
+
+// generate the V2 key from the index fields defined
+// in the schema.
+void ObjectId::setV2Key(const ManagementObject& object)
+{
+ stringstream oname;
+ oname << object.getPackageName() << ":" << object.getClassName() << ":" << object.getKey();
+ v2Key = oname.str();
+}
+
+
+// encode as V2-format map
+void ObjectId::mapEncode(types::Variant::Map& map) const
+{
+ map["_object_name"] = v2Key;
+ if (!agentName.empty())
+ map["_agent_name"] = agentName;
+ if (agentEpoch)
+ map["_agent_epoch"] = agentEpoch;
+}
+
+// decode as v2-format map
+void ObjectId::mapDecode(const types::Variant::Map& map)
+{
+ types::Variant::Map::const_iterator i;
+
+ if ((i = map.find("_object_name")) != map.end())
+ v2Key = i->second.asString();
+ else
+ throw Exception("Required _object_name field missing.");
+
+ if ((i = map.find("_agent_name")) != map.end())
+ agentName = i->second.asString();
+
+ if ((i = map.find("_agent_epoch")) != map.end())
+ agentEpoch = i->second.asInt64();
+}
+
+
+ObjectId::operator types::Variant::Map() const
+{
+ types::Variant::Map m;
+ mapEncode(m);
+ return m;
+}
+
+
+
+namespace qpid {
+namespace management {
+
+ostream& operator<<(ostream& out, const ObjectId& i)
+{
+ uint64_t virtFirst = i.first;
+ if (i.agent)
+ virtFirst |= i.agent->getFirst();
+
+ out << ((virtFirst & 0xF000000000000000LL) >> 60) <<
+ "-" << ((virtFirst & 0x0FFF000000000000LL) >> 48) <<
+ "-" << ((virtFirst & 0x0000FFFFF0000000LL) >> 28) <<
+ "-" << i.agentName <<
+ "-" << i.second <<
+ "(" << i.v2Key << ")";
+ return out;
+}
+
+}}
+
+ManagementObject::ManagementObject(Manageable* _core) :
+ createTime(qpid::sys::Duration::FromEpoch()),
+ destroyTime(0), updateTime(createTime), configChanged(true),
+ instChanged(true), deleted(false),
+ coreObject(_core), flags(0), forcePublish(false) {}
+
+void ManagementObject::setUpdateTime()
+{
+ updateTime = sys::Duration::FromEpoch();
+}
+
+void ManagementObject::resourceDestroy()
+{
+ QPID_LOG(trace, "Management object marked deleted: " << getObjectId().getV2Key());
+ destroyTime = sys::Duration::FromEpoch();
+ deleted = true;
+}
+
+int ManagementObject::maxThreads = 1;
+int ManagementObject::nextThreadIndex = 0;
+
+void ManagementObject::writeTimestamps (string& buf) const
+{
+ char _data[4000];
+ qpid::framing::Buffer body(_data, 4000);
+
+ body.putShortString (getPackageName ());
+ body.putShortString (getClassName ());
+ body.putBin128 (getMd5Sum ());
+ body.putLongLong (updateTime);
+ body.putLongLong (createTime);
+ body.putLongLong (destroyTime);
+
+ uint32_t len = body.getPosition();
+ body.reset();
+ body.getRawData(buf, len);
+
+ string oid;
+ objectId.encode(oid);
+ buf += oid;
+}
+
+void ManagementObject::readTimestamps (const string& buf)
+{
+ char _data[4000];
+ qpid::framing::Buffer body(_data, 4000);
+ string unused;
+ uint8_t unusedUuid[16];
+
+ body.checkAvailable(buf.length());
+ body.putRawData(buf);
+ body.reset();
+
+ body.getShortString(unused);
+ body.getShortString(unused);
+ body.getBin128(unusedUuid);
+ updateTime = body.getLongLong();
+ createTime = body.getLongLong();
+ destroyTime = body.getLongLong();
+}
+
+uint32_t ManagementObject::writeTimestampsSize() const
+{
+ return 1 + getPackageName().length() + // str8
+ 1 + getClassName().length() + // str8
+ 16 + // bin128
+ 8 + // uint64
+ 8 + // uint64
+ 8 + // uint64
+ objectId.encodedSize(); // objectId
+}
+
+
+void ManagementObject::writeTimestamps (types::Variant::Map& map) const
+{
+ // types::Variant::Map oid, sid;
+
+ // sid["_package_name"] = getPackageName();
+ // sid["_class_name"] = getClassName();
+ // sid["_hash"] = qpid::types::Uuid(getMd5Sum());
+ // map["_schema_id"] = sid;
+
+ // objectId.mapEncode(oid);
+ // map["_object_id"] = oid;
+
+ map["_update_ts"] = updateTime;
+ map["_create_ts"] = createTime;
+ map["_delete_ts"] = destroyTime;
+}
+
+void ManagementObject::readTimestamps (const types::Variant::Map& map)
+{
+ types::Variant::Map::const_iterator i;
+
+ if ((i = map.find("_update_ts")) != map.end())
+ updateTime = i->second.asUint64();
+ if ((i = map.find("_create_ts")) != map.end())
+ createTime = i->second.asUint64();
+ if ((i = map.find("_delete_ts")) != map.end())
+ destroyTime = i->second.asUint64();
+}
+
+
+void ManagementObject::setReference(ObjectId) {}
+
+int ManagementObject::getThreadIndex() {
+ static QPID_TSS int thisIndex = -1;
+ if (thisIndex == -1) {
+ Mutex::ScopedLock mutex(accessLock);
+ thisIndex = nextThreadIndex;
+ if (nextThreadIndex < maxThreads - 1)
+ nextThreadIndex++;
+ }
+ return thisIndex;
+}
+
+
+// void ManagementObject::mapEncode(types::Variant::Map& map,
+// bool includeProperties,
+// bool includeStatistics)
+// {
+// types::Variant::Map values;
+
+// writeTimestamps(map);
+
+// mapEncodeValues(values, includeProperties, includeStatistics);
+// map["_values"] = values;
+// }
+
+// void ManagementObject::mapDecode(const types::Variant::Map& map)
+// {
+// types::Variant::Map::const_iterator i;
+
+// readTimestamps(map);
+
+// if ((i = map.find("_values")) != map.end())
+// mapDecodeValues(i->second.asMap());
+// }
diff --git a/qpid/cpp/src/qpid/management/ManagementObject.h b/qpid/cpp/src/qpid/management/ManagementObject.h
new file mode 100644
index 0000000000..93fbec7bc7
--- /dev/null
+++ b/qpid/cpp/src/qpid/management/ManagementObject.h
@@ -0,0 +1,246 @@
+#ifndef _ManagementObject_
+#define _ManagementObject_
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/CommonImportExport.h"
+
+#include "qpid/management/Mutex.h"
+#include "qpid/types/Variant.h"
+#include <map>
+#include <vector>
+
+#ifdef _IN_QPID_BROKER
+#include <boost/shared_ptr.hpp>
+#endif
+
+namespace qpid {
+namespace management {
+
+class Manageable;
+class ObjectId;
+class ManagementObject;
+
+
+class AgentAttachment {
+ friend class ObjectId;
+private:
+ uint64_t first;
+public:
+ AgentAttachment() : first(0) {}
+ QPID_COMMON_EXTERN void setBanks(uint32_t broker, uint32_t bank);
+ uint64_t getFirst() const { return first; }
+};
+
+
+class ObjectId {
+protected:
+ const AgentAttachment* agent;
+ uint64_t first;
+ uint64_t second;
+ uint64_t agentEpoch;
+ std::string v2Key;
+ std::string agentName;
+ void fromString(const std::string&);
+public:
+ QPID_COMMON_INLINE_EXTERN ObjectId() : agent(0), first(0), second(0), agentEpoch(0) {}
+ QPID_COMMON_INLINE_EXTERN ObjectId(const types::Variant& map) :
+ agent(0), first(0), second(0), agentEpoch(0) { mapDecode(map.asMap()); }
+ QPID_COMMON_EXTERN ObjectId(uint8_t flags, uint16_t seq, uint32_t broker);
+ QPID_COMMON_EXTERN ObjectId(AgentAttachment* _agent, uint8_t flags, uint16_t seq);
+ QPID_COMMON_EXTERN ObjectId(std::istream&);
+ QPID_COMMON_EXTERN ObjectId(const std::string&);
+ QPID_COMMON_INLINE_EXTERN ObjectId(const std::string& agentAddress, const std::string& key,
+ uint64_t epoch=0) : agent(0), first(0), second(0),
+ agentEpoch(epoch), v2Key(key), agentName(agentAddress) {}
+
+ // Deprecated:
+ QPID_COMMON_EXTERN ObjectId(uint8_t flags, uint16_t seq, uint32_t broker, uint64_t object);
+ QPID_COMMON_EXTERN bool operator==(const ObjectId &other) const;
+ QPID_COMMON_EXTERN bool operator<(const ObjectId &other) const;
+ QPID_COMMON_EXTERN void mapEncode(types::Variant::Map& map) const;
+ QPID_COMMON_EXTERN void mapDecode(const types::Variant::Map& map);
+ QPID_COMMON_EXTERN operator types::Variant::Map() const;
+ QPID_COMMON_INLINE_EXTERN uint32_t encodedSize() const { return 16; };
+ QPID_COMMON_EXTERN void encode(std::string& buffer) const;
+ QPID_COMMON_EXTERN void decode(const std::string& buffer);
+ QPID_COMMON_EXTERN bool equalV1(const ObjectId &other) const;
+ QPID_COMMON_INLINE_EXTERN void setV2Key(const std::string& _key) { v2Key = _key; }
+ QPID_COMMON_EXTERN void setV2Key(const ManagementObject& object);
+ QPID_COMMON_INLINE_EXTERN void setAgentName(const std::string& _name) { agentName = _name; }
+ QPID_COMMON_INLINE_EXTERN const std::string& getAgentName() const { return agentName; }
+ QPID_COMMON_INLINE_EXTERN const std::string& getV2Key() const { return v2Key; }
+ friend QPID_COMMON_EXTERN std::ostream& operator<<(std::ostream&, const ObjectId&);
+};
+
+class ManagementItem {
+public:
+ static const uint8_t TYPE_U8 = 1;
+ static const uint8_t TYPE_U16 = 2;
+ static const uint8_t TYPE_U32 = 3;
+ static const uint8_t TYPE_U64 = 4;
+ static const uint8_t TYPE_SSTR = 6;
+ static const uint8_t TYPE_LSTR = 7;
+ static const uint8_t TYPE_ABSTIME = 8;
+ static const uint8_t TYPE_DELTATIME = 9;
+ static const uint8_t TYPE_REF = 10;
+ static const uint8_t TYPE_BOOL = 11;
+ static const uint8_t TYPE_FLOAT = 12;
+ static const uint8_t TYPE_DOUBLE = 13;
+ static const uint8_t TYPE_UUID = 14;
+ static const uint8_t TYPE_FTABLE = 15;
+ static const uint8_t TYPE_S8 = 16;
+ static const uint8_t TYPE_S16 = 17;
+ static const uint8_t TYPE_S32 = 18;
+ static const uint8_t TYPE_S64 = 19;
+ static const uint8_t TYPE_LIST = 21;
+
+ static const uint8_t ACCESS_RC = 1;
+ static const uint8_t ACCESS_RW = 2;
+ static const uint8_t ACCESS_RO = 3;
+
+ static const uint8_t DIR_I = 1;
+ static const uint8_t DIR_O = 2;
+ static const uint8_t DIR_IO = 3;
+
+ static const uint8_t FLAG_CONFIG = 0x01;
+ static const uint8_t FLAG_INDEX = 0x02;
+ static const uint8_t FLAG_END = 0x80;
+
+ const static uint8_t CLASS_KIND_TABLE = 1;
+ const static uint8_t CLASS_KIND_EVENT = 2;
+
+
+
+public:
+ virtual ~ManagementItem() {}
+};
+
+class QPID_COMMON_CLASS_EXTERN ManagementObject : public ManagementItem
+{
+protected:
+
+ uint64_t createTime;
+ uint64_t destroyTime;
+ uint64_t updateTime;
+ ObjectId objectId;
+ mutable bool configChanged;
+ mutable bool instChanged;
+ bool deleted;
+ Manageable* coreObject;
+ mutable Mutex accessLock;
+ uint32_t flags;
+
+ static int nextThreadIndex;
+ bool forcePublish;
+
+ QPID_COMMON_EXTERN int getThreadIndex();
+ QPID_COMMON_EXTERN void writeTimestamps(std::string& buf) const;
+ QPID_COMMON_EXTERN void readTimestamps(const std::string& buf);
+ QPID_COMMON_EXTERN uint32_t writeTimestampsSize() const;
+
+ public:
+#ifdef _IN_QPID_BROKER
+ typedef boost::shared_ptr<ManagementObject> shared_ptr;
+#endif
+
+ QPID_COMMON_EXTERN static const uint8_t MD5_LEN = 16;
+ QPID_COMMON_EXTERN static int maxThreads;
+ //typedef void (*writeSchemaCall_t) (qpid::framing::Buffer&);
+ typedef void (*writeSchemaCall_t) (std::string&);
+
+ QPID_COMMON_EXTERN ManagementObject(Manageable* _core);
+ virtual ~ManagementObject() {}
+
+ virtual writeSchemaCall_t getWriteSchemaCall() = 0;
+ virtual std::string getKey() const = 0;
+
+ // Encode & Decode the property and statistics values
+ // for this object.
+ virtual void mapEncodeValues(types::Variant::Map& map,
+ bool includeProperties,
+ bool includeStatistics) = 0;
+ virtual void mapDecodeValues(const types::Variant::Map& map) = 0;
+ virtual void doMethod(std::string& methodName,
+ const types::Variant::Map& inMap,
+ types::Variant::Map& outMap,
+ const std::string& userId) = 0;
+ QPID_COMMON_EXTERN void writeTimestamps(types::Variant::Map& map) const;
+ QPID_COMMON_EXTERN void readTimestamps(const types::Variant::Map& buf);
+
+ /**
+ * The following five methods are not pure-virtual because they will only
+ * be overridden in cases where QMFv1 is to be supported.
+ */
+ virtual uint32_t writePropertiesSize() const { return 0; }
+ virtual void readProperties(const std::string&) {}
+ virtual void writeProperties(std::string&) const {}
+ virtual void writeStatistics(std::string&, bool = false) {}
+ virtual void doMethod(std::string&, const std::string&, std::string&, const std::string&) {}
+
+ QPID_COMMON_EXTERN virtual void setReference(ObjectId objectId);
+
+ virtual std::string& getClassName() const = 0;
+ virtual std::string& getPackageName() const = 0;
+ virtual uint8_t* getMd5Sum() const = 0;
+
+ void setObjectId(ObjectId oid) { objectId = oid; }
+ ObjectId getObjectId() { return objectId; }
+ inline bool getConfigChanged() { return configChanged; }
+ virtual bool getInstChanged() { return instChanged; }
+ virtual bool hasInst() { return true; }
+ inline void setForcePublish(bool f) { forcePublish = f; }
+ inline bool getForcePublish() { return forcePublish; }
+ QPID_COMMON_EXTERN void setUpdateTime();
+ QPID_COMMON_EXTERN void resourceDestroy();
+ inline bool isDeleted() { return deleted; }
+ inline void setFlags(uint32_t f) { flags = f; }
+ inline uint32_t getFlags() { return flags; }
+ bool isSameClass(ManagementObject& other) {
+ for (int idx = 0; idx < MD5_LEN; idx++)
+ if (other.getMd5Sum()[idx] != getMd5Sum()[idx])
+ return false;
+ return other.getClassName() == getClassName() &&
+ other.getPackageName() == getPackageName();
+ }
+
+ // QPID_COMMON_EXTERN void encode(qpid::framing::Buffer& buf) const { writeProperties(buf); }
+ // QPID_COMMON_EXTERN void decode(qpid::framing::Buffer& buf) { readProperties(buf); }
+ //QPID_COMMON_EXTERN uint32_t encodedSize() const { return writePropertiesSize(); }
+
+ // Encode/Decode the entire object as a map
+ //QPID_COMMON_EXTERN void mapEncode(types::Variant::Map& map,
+ //bool includeProperties=true,
+ //bool includeStatistics=true);
+
+ //QPID_COMMON_EXTERN void mapDecode(const types::Variant::Map& map);
+};
+
+#ifdef _IN_QPID_BROKER
+typedef std::map<ObjectId, ManagementObject::shared_ptr> ManagementObjectMap;
+typedef std::vector<ManagementObject::shared_ptr> ManagementObjectVector;
+#endif
+
+}}
+
+
+
+#endif /*!_ManagementObject_*/
diff --git a/qpid/cpp/src/qpid/management/ManagementTopicExchange.cpp b/qpid/cpp/src/qpid/management/ManagementTopicExchange.cpp
new file mode 100644
index 0000000000..0241d5a404
--- /dev/null
+++ b/qpid/cpp/src/qpid/management/ManagementTopicExchange.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/management/ManagementTopicExchange.h"
+#include "qpid/log/Statement.h"
+
+using namespace qpid::management;
+using namespace qpid::broker;
+using namespace qpid::framing;
+using namespace qpid::sys;
+
+ManagementTopicExchange::ManagementTopicExchange(const std::string& _name, Manageable* _parent, Broker* b) :
+ Exchange (_name, _parent, b),
+ TopicExchange(_name, _parent, b),
+ managementAgent(0) {}
+ManagementTopicExchange::ManagementTopicExchange(const std::string& _name,
+ bool _durable,
+ const FieldTable& _args,
+ Manageable* _parent, Broker* b) :
+ Exchange (_name, _durable, false, _args, _parent, b),
+ TopicExchange(_name, _durable, false, _args, _parent, b),
+ managementAgent(0) {}
+
+void ManagementTopicExchange::route(Deliverable& msg)
+{
+ bool routeIt = true;
+
+ // Intercept management agent commands
+ if (managementAgent)
+ routeIt = managementAgent->dispatchCommand(msg, msg.getMessage().getRoutingKey(), 0/*args - TODO*/, true, qmfVersion);
+
+ if (routeIt)
+ TopicExchange::route(msg);
+}
+
+bool ManagementTopicExchange::bind(Queue::shared_ptr queue,
+ const std::string& routingKey,
+ const qpid::framing::FieldTable* args)
+{
+ if (qmfVersion == 1)
+ managementAgent->clientAdded(routingKey);
+ return TopicExchange::bind(queue, routingKey, args);
+}
+
+void ManagementTopicExchange::setManagmentAgent(ManagementAgent* agent, int qv)
+{
+ managementAgent = agent;
+ qmfVersion = qv;
+}
+
+
+ManagementTopicExchange::~ManagementTopicExchange() {}
+
+const std::string ManagementTopicExchange::typeName("management-topic");
+
diff --git a/qpid/cpp/src/qpid/management/ManagementTopicExchange.h b/qpid/cpp/src/qpid/management/ManagementTopicExchange.h
new file mode 100644
index 0000000000..f5192a0936
--- /dev/null
+++ b/qpid/cpp/src/qpid/management/ManagementTopicExchange.h
@@ -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.
+ *
+ */
+#ifndef _ManagementTopicExchange_
+#define _ManagementTopicExchange_
+
+#include "qpid/broker/TopicExchange.h"
+#include "qpid/management/ManagementAgent.h"
+
+namespace qpid {
+namespace broker {
+
+class ManagementTopicExchange : public virtual TopicExchange
+{
+ private:
+ management::ManagementAgent* managementAgent;
+ int qmfVersion;
+
+ public:
+ static const std::string typeName;
+
+ ManagementTopicExchange(const std::string& name, Manageable* _parent = 0, Broker* broker = 0);
+ ManagementTopicExchange(const std::string& _name, bool _durable,
+ const qpid::framing::FieldTable& _args,
+ Manageable* _parent = 0, Broker* broker = 0);
+
+ virtual std::string getType() const { return typeName; }
+
+ virtual void route(Deliverable& msg);
+
+ virtual bool bind(Queue::shared_ptr queue,
+ const std::string& routingKey,
+ const qpid::framing::FieldTable* args);
+
+ void setManagmentAgent(management::ManagementAgent* agent, int qmfVersion);
+
+ virtual ~ManagementTopicExchange();
+};
+
+
+}
+}
+
+#endif
diff --git a/qpid/cpp/src/qpid/management/Mutex.cpp b/qpid/cpp/src/qpid/management/Mutex.cpp
new file mode 100644
index 0000000000..f05abb01dc
--- /dev/null
+++ b/qpid/cpp/src/qpid/management/Mutex.cpp
@@ -0,0 +1,29 @@
+/*
+ *
+ * Copyright (c) 2008 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/management/Mutex.h"
+#include "qpid/sys/Mutex.h"
+
+using namespace std;
+using namespace qpid::management;
+
+Mutex::Mutex() : impl(new sys::Mutex()) {}
+Mutex::~Mutex() { delete impl; }
+void Mutex::lock() { impl->lock(); }
+void Mutex::unlock() { impl->unlock(); }
+
diff --git a/qpid/cpp/src/qpid/management/Mutex.h b/qpid/cpp/src/qpid/management/Mutex.h
new file mode 100644
index 0000000000..67ae04bae9
--- /dev/null
+++ b/qpid/cpp/src/qpid/management/Mutex.h
@@ -0,0 +1,67 @@
+#ifndef _management_Mutex_h
+#define _management_Mutex_h
+
+/*
+ *
+ * Copyright (c) 2008 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+ namespace sys {
+ class Mutex;
+ }
+
+ namespace management {
+
+ /**
+ * Scoped lock template: calls lock() in ctor, unlock() in dtor.
+ * L can be any class with lock() and unlock() functions.
+ */
+ template <class L> class ScopedLockTemplate {
+ public:
+ ScopedLockTemplate(L& l) : mutex(l) { mutex.lock(); }
+ ~ScopedLockTemplate() { mutex.unlock(); }
+ private:
+ L& mutex;
+ };
+
+ template <class L> class ScopedUnlockTemplate {
+ public:
+ ScopedUnlockTemplate(L& l) : mutex(l) { mutex.unlock(); }
+ ~ScopedUnlockTemplate() { mutex.lock(); }
+ private:
+ L& mutex;
+ };
+
+ class Mutex {
+ public:
+ typedef ScopedLockTemplate<Mutex> ScopedLock;
+ typedef ScopedUnlockTemplate<Mutex> ScopedUnlock;
+
+ QPID_COMMON_EXTERN Mutex();
+ QPID_COMMON_EXTERN ~Mutex();
+ QPID_COMMON_EXTERN void lock();
+ QPID_COMMON_EXTERN void unlock();
+ private:
+ sys::Mutex* impl;
+ };
+ }
+}
+
+#endif
+
diff --git a/qpid/cpp/src/qpid/memory.h b/qpid/cpp/src/qpid/memory.h
new file mode 100644
index 0000000000..99d7a71e7b
--- /dev/null
+++ b/qpid/cpp/src/qpid/memory.h
@@ -0,0 +1,32 @@
+#ifndef QPID_AUTO_PTR_H
+#define QPID_AUTO_PTR_H
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <memory>
+namespace qpid {
+/** Convenient template for creating auto_ptr in-place in an argument list. */
+template <class T>
+std::auto_ptr<T> make_auto_ptr(T* ptr) { return std::auto_ptr<T>(ptr); }
+
+} // namespace qpid
+
+
+
+#endif /*!QPID_AUTO_PTR_H*/
diff --git a/qpid/cpp/src/qpid/messaging/Address.cpp b/qpid/cpp/src/qpid/messaging/Address.cpp
new file mode 100644
index 0000000000..6fbaeef661
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/Address.cpp
@@ -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.
+ *
+ */
+#include "qpid/messaging/Address.h"
+#include "qpid/messaging/AddressImpl.h"
+#include "qpid/messaging/AddressParser.h"
+#include "qpid/framing/Uuid.h"
+#include <sstream>
+#include <boost/format.hpp>
+
+namespace qpid {
+namespace messaging {
+
+using namespace qpid::types;
+
+namespace {
+const std::string SUBJECT_DIVIDER = "/";
+const std::string OPTIONS_DIVIDER = ";";
+const std::string SPACE = " ";
+const std::string TYPE = "type";
+}
+Address::Address() : impl(new AddressImpl()) {}
+Address::Address(const std::string& address) : impl(new AddressImpl())
+{
+ AddressParser parser(address);
+ parser.parse(*this);
+}
+Address::Address(const std::string& name, const std::string& subject, const Variant::Map& options,
+ const std::string& type)
+ : impl(new AddressImpl(name, subject, options)) { setType(type); }
+Address::Address(const Address& a) :
+ impl(new AddressImpl(a.impl->name, a.impl->subject, a.impl->options)) { impl->temporary = a.impl->temporary; }
+Address::~Address() { delete impl; }
+
+Address& Address::operator=(const Address& a) { *impl = *a.impl; return *this; }
+
+
+std::string Address::str() const
+{
+ std::stringstream out;
+ out << impl->name;
+ if (!impl->subject.empty()) out << SUBJECT_DIVIDER << impl->subject;
+ if (!impl->options.empty()) out << OPTIONS_DIVIDER << impl->options;
+ return out.str();
+}
+Address::operator bool() const { return !impl->name.empty(); }
+bool Address::operator !() const { return impl->name.empty(); }
+
+const std::string& Address::getName() const { return impl->name; }
+void Address::setName(const std::string& name) { impl->name = name; }
+const std::string& Address::getSubject() const { return impl->subject; }
+void Address::setSubject(const std::string& subject) { impl->subject = subject; }
+const Variant::Map& Address::getOptions() const { return impl->options; }
+Variant::Map& Address::getOptions() { return impl->options; }
+void Address::setOptions(const Variant::Map& options) { impl->options = options; }
+
+
+namespace{
+const Variant EMPTY_VARIANT;
+const std::string EMPTY_STRING;
+const std::string NODE_PROPERTIES="node";
+}
+
+const Variant& find(const Variant::Map& map, const std::string& key)
+{
+ Variant::Map::const_iterator i = map.find(key);
+ if (i == map.end()) return EMPTY_VARIANT;
+ else return i->second;
+}
+
+std::string Address::getType() const
+{
+ const Variant& props = find(impl->options, NODE_PROPERTIES);
+ if (props.getType() == VAR_MAP) {
+ const Variant& type = find(props.asMap(), TYPE);
+ if (!type.isVoid()) return type.asString();
+ }
+ return EMPTY_STRING;
+}
+
+void Address::setType(const std::string& type)
+{
+ Variant& props = impl->options[NODE_PROPERTIES];
+ if (props.isVoid()) props = Variant::Map();
+ props.asMap()[TYPE] = type;
+}
+
+std::ostream& operator<<(std::ostream& out, const Address& address)
+{
+ out << address.str();
+ return out;
+}
+
+}} // namespace qpid::messaging
diff --git a/qpid/cpp/src/qpid/messaging/AddressImpl.h b/qpid/cpp/src/qpid/messaging/AddressImpl.h
new file mode 100644
index 0000000000..8d34bd73c4
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/AddressImpl.h
@@ -0,0 +1,45 @@
+#ifndef QPID_MESSAGING_ADDRESSIMPL_H
+#define QPID_MESSAGING_ADDRESSIMPL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/messaging/Address.h"
+#include "qpid/types/Variant.h"
+namespace qpid {
+namespace messaging {
+
+class AddressImpl
+{
+ public:
+ std::string name;
+ std::string subject;
+ qpid::types::Variant::Map options;
+ bool temporary;
+
+ AddressImpl() : temporary(false) {}
+ AddressImpl(const std::string& n, const std::string& s, const qpid::types::Variant::Map& o) :
+ name(n), subject(s), options(o), temporary(false) {}
+ static void setTemporary(Address& a, bool value) { a.impl->temporary = value; }
+ static bool isTemporary(const Address& a) { return a.impl->temporary; }
+};
+}} // namespace qpid::messaging
+
+#endif /*!QPID_MESSAGING_ADDRESSIMPL_H*/
diff --git a/qpid/cpp/src/qpid/messaging/AddressParser.cpp b/qpid/cpp/src/qpid/messaging/AddressParser.cpp
new file mode 100644
index 0000000000..882deba463
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/AddressParser.cpp
@@ -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.
+ *
+ */
+#include "AddressParser.h"
+#include "AddressImpl.h"
+#include "qpid/framing/Uuid.h"
+#include <boost/format.hpp>
+
+namespace qpid {
+namespace messaging {
+
+using namespace qpid::types;
+
+AddressParser::AddressParser(const std::string& s) : input(s), current(0) {}
+
+bool AddressParser::error(const std::string& message)
+{
+ throw MalformedAddress((boost::format("%1%, character %2% of %3%") % message % current % input).str());
+}
+
+bool AddressParser::parse(Address& address)
+{
+ std::string name;
+ if (readName(name)) {
+ if (name.find('#') == 0) {
+ name = qpid::framing::Uuid(true).str() + name;
+ AddressImpl::setTemporary(address, true);
+ }
+ address.setName(name);
+ if (readChar('/')) {
+ std::string subject;
+ readSubject(subject);
+ address.setSubject(subject);
+ }
+ if (readChar(';')) {
+ Variant options = Variant::Map();
+ if (readMap(options)) {
+ address.setOptions(options.asMap());
+ }
+ }
+ //skip trailing whitespace
+ while (!eos() && iswhitespace()) ++current;
+ return eos() || error("Unexpected chars in address: " + input.substr(current));
+ } else {
+ return input.empty() || error("Expected name");
+ }
+}
+
+bool AddressParser::parseMap(Variant::Map& map)
+{
+ if (readChar('{')) {
+ readMapEntries(map);
+ return readChar('}') || error("Unmatched '{'!");
+ } else {
+ return false;
+ }
+}
+
+bool AddressParser::parseList(Variant::List& list)
+{
+ if (readChar('[')) {
+ readListItems(list);
+ return readChar(']') || error("Unmatched '['!");
+ } else {
+ return false;
+ }
+}
+
+
+bool AddressParser::readList(Variant& value)
+{
+ if (readChar('[')) {
+ value = Variant::List();
+ readListItems(value.asList());
+ return readChar(']') || error("Unmatched '['!");
+ } else {
+ return false;
+ }
+}
+
+void AddressParser::readListItems(Variant::List& list)
+{
+ Variant item;
+ while (readValueIfExists(item)) {
+ list.push_back(item);
+ if (!readChar(',')) break;
+ }
+}
+
+bool AddressParser::readMap(Variant& value)
+{
+ if (readChar('{')) {
+ value = Variant::Map();
+ readMapEntries(value.asMap());
+ return readChar('}') || error("Unmatched '{'!");
+ } else {
+ return false;
+ }
+}
+
+void AddressParser::readMapEntries(Variant::Map& map)
+{
+ while (readKeyValuePair(map) && readChar(',')) {}
+}
+
+bool AddressParser::readKeyValuePair(Variant::Map& map)
+{
+ std::string key;
+ Variant value;
+ if (readKey(key)) {
+ if (readChar(':') && readValue(value)) {
+ map[key] = value;
+ return true;
+ } else {
+ return error("Bad key-value pair, expected ':'");
+ }
+ } else {
+ return false;
+ }
+}
+
+bool AddressParser::readKey(std::string& key)
+{
+ return readWord(key) || readQuotedString(key);
+}
+
+bool AddressParser::readValue(Variant& value)
+{
+ return readValueIfExists(value) || error("Expected value");
+}
+
+bool AddressParser::readValueIfExists(Variant& value)
+{
+ return readSimpleValue(value) || readQuotedValue(value) ||
+ readMap(value) || readList(value);
+}
+
+bool AddressParser::readString(std::string& value, char delimiter)
+{
+ if (readChar(delimiter)) {
+ std::string::size_type start = current;
+ while (!eos()) {
+ if (input.at(current) == delimiter) {
+ if (current > start) {
+ value = input.substr(start, current - start);
+ } else {
+ value = "";
+ }
+ ++current;
+ return true;
+ } else {
+ ++current;
+ }
+ }
+ return error("Unmatched delimiter");
+ } else {
+ return false;
+ }
+}
+
+bool AddressParser::readName(std::string& name)
+{
+ return readQuotedString(name) || readWord(name, "/;");
+}
+
+bool AddressParser::readSubject(std::string& subject)
+{
+ return readQuotedString(subject) || readWord(subject, ";");
+}
+
+bool AddressParser::readQuotedString(std::string& s)
+{
+ return readString(s, '"') || readString(s, '\'');
+}
+
+bool AddressParser::readQuotedValue(Variant& value)
+{
+ std::string s;
+ if (readQuotedString(s)) {
+ value = s;
+ value.setEncoding("utf8");
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool AddressParser::readSimpleValue(Variant& value)
+{
+ std::string s;
+ if (readWord(s)) {
+ value.parse(s);
+ if (value.getType() == VAR_STRING) value.setEncoding("utf8");
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool AddressParser::readWord(std::string& value, const std::string& delims)
+{
+ //skip leading whitespace
+ while (!eos() && iswhitespace()) ++current;
+
+ //read any number of non-whitespace, non-reserved chars into value
+ std::string::size_type start = current;
+ while (!eos() && !iswhitespace() && !in(delims)) ++current;
+
+ if (current > start) {
+ value = input.substr(start, current - start);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool AddressParser::readChar(char c)
+{
+ while (!eos()) {
+ if (iswhitespace()) {
+ ++current;
+ } else if (input.at(current) == c) {
+ ++current;
+ return true;
+ } else {
+ return false;
+ }
+ }
+ return false;
+}
+
+bool AddressParser::iswhitespace()
+{
+ return ::isspace(input.at(current));
+}
+
+bool AddressParser::isreserved()
+{
+ return in(RESERVED);
+}
+
+bool AddressParser::in(const std::string& chars)
+{
+ return chars.find(input.at(current)) != std::string::npos;
+}
+
+bool AddressParser::eos()
+{
+ return current >= input.size();
+}
+
+const std::string AddressParser::RESERVED = "\'\"{}[],:/";
+
+}} // namespace qpid::messaging
diff --git a/qpid/cpp/src/qpid/messaging/AddressParser.h b/qpid/cpp/src/qpid/messaging/AddressParser.h
new file mode 100644
index 0000000000..5e429e1ca9
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/AddressParser.h
@@ -0,0 +1,67 @@
+#ifndef QPID_MESSAGING_ADDRESSPARSER_H
+#define QPID_MESSAGING_ADDRESSPARSER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/messaging/ImportExport.h"
+#include "qpid/messaging/Address.h"
+
+namespace qpid {
+namespace messaging {
+
+class AddressParser
+{
+ public:
+ QPID_MESSAGING_EXTERN AddressParser(const std::string&);
+ bool parse(Address& address);
+ QPID_MESSAGING_EXTERN bool parseMap(qpid::types::Variant::Map& map);
+ QPID_MESSAGING_EXTERN bool parseList(qpid::types::Variant::List& list);
+ private:
+ const std::string& input;
+ std::string::size_type current;
+ static const std::string RESERVED;
+
+ bool readChar(char c);
+ bool readQuotedString(std::string& s);
+ bool readQuotedValue(qpid::types::Variant& value);
+ bool readString(std::string& value, char delimiter);
+ bool readWord(std::string& word, const std::string& delims = RESERVED);
+ bool readSimpleValue(qpid::types::Variant& word);
+ bool readKey(std::string& key);
+ bool readValue(qpid::types::Variant& value);
+ bool readValueIfExists(qpid::types::Variant& value);
+ bool readKeyValuePair(qpid::types::Variant::Map& map);
+ bool readMap(qpid::types::Variant& value);
+ bool readList(qpid::types::Variant& value);
+ bool readName(std::string& name);
+ bool readSubject(std::string& subject);
+ bool error(const std::string& message);
+ bool eos();
+ bool iswhitespace();
+ bool in(const std::string& delims);
+ bool isreserved();
+ void readListItems(qpid::types::Variant::List& list);
+ void readMapEntries(qpid::types::Variant::Map& map);
+};
+
+}} // namespace qpid::messaging
+
+#endif /*!QPID_MESSAGING_ADDRESSPARSER_H*/
diff --git a/qpid/cpp/src/qpid/messaging/Connection.cpp b/qpid/cpp/src/qpid/messaging/Connection.cpp
new file mode 100644
index 0000000000..c40d32cbc1
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/Connection.cpp
@@ -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.
+ *
+ */
+#include "qpid/messaging/Connection.h"
+#include "qpid/messaging/AddressParser.h"
+#include "qpid/messaging/ConnectionImpl.h"
+#include "qpid/messaging/Session.h"
+#include "qpid/messaging/SessionImpl.h"
+#include "qpid/messaging/PrivateImplRef.h"
+#include "qpid/messaging/ProtocolRegistry.h"
+#include "qpid/client/amqp0_10/ConnectionImpl.h"
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace messaging {
+
+// Explicitly instantiate Handle superclass
+template class Handle<ConnectionImpl>;
+
+using namespace qpid::types;
+
+typedef PrivateImplRef<qpid::messaging::Connection> PI;
+
+Connection::Connection(ConnectionImpl* impl) { PI::ctor(*this, impl); }
+Connection::Connection(const Connection& c) : Handle<ConnectionImpl>() { PI::copy(*this, c); }
+Connection& Connection::operator=(const Connection& c) { return PI::assign(*this, c); }
+Connection::~Connection() { PI::dtor(*this); }
+
+Connection::Connection(const std::string& url, const std::string& o)
+{
+ Variant::Map options;
+ AddressParser parser(o);
+ if (o.empty() || parser.parseMap(options)) {
+ PI::ctor(*this, ProtocolRegistry::create(url, options));
+ } else {
+ throw InvalidOptionString("Invalid option string: " + o);
+ }
+}
+Connection::Connection(const std::string& url, const Variant::Map& options)
+{
+ PI::ctor(*this, ProtocolRegistry::create(url, options));
+}
+
+Connection::Connection()
+{
+ Variant::Map options;
+ std::string url = "127.0.0.1:5672";
+ PI::ctor(*this, ProtocolRegistry::create(url, options));
+}
+
+void Connection::open()
+{
+ while (true) {
+ try {
+ impl->open();
+ return;
+ } catch (const ProtocolVersionError& e) {
+ PI::set(*this, ProtocolRegistry::next(PI::get(impl).get()));
+ QPID_LOG(info, e.what() << ", trying alternative protocol version...");
+ }
+ }
+}
+bool Connection::isOpen() { return impl->isOpen(); }
+bool Connection::isOpen() const { return impl->isOpen(); }
+void Connection::close() { impl->close(); }
+Session Connection::createSession(const std::string& name) { return impl->newSession(false, name); }
+Session Connection::createTransactionalSession(const std::string& name)
+{
+ return impl->newSession(true, name);
+}
+Session Connection::getSession(const std::string& name) const { return impl->getSession(name); }
+void Connection::setOption(const std::string& name, const Variant& value)
+{
+ impl->setOption(name, value);
+}
+std::string Connection::getAuthenticatedUsername()
+{
+ return impl->getAuthenticatedUsername();
+}
+
+void Connection::reconnect(const std::string& url)
+{
+ impl->reconnect(url);
+}
+void Connection::reconnect()
+{
+ impl->reconnect();
+}
+std::string Connection::getUrl() const
+{
+ return impl->getUrl();
+}
+
+}} // namespace qpid::messaging
diff --git a/qpid/cpp/src/qpid/messaging/ConnectionImpl.h b/qpid/cpp/src/qpid/messaging/ConnectionImpl.h
new file mode 100644
index 0000000000..05d835b282
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/ConnectionImpl.h
@@ -0,0 +1,60 @@
+#ifndef QPID_MESSAGING_CONNECTIONIMPL_H
+#define QPID_MESSAGING_CONNECTIONIMPL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <string>
+#include <boost/function.hpp>
+#include "qpid/RefCounted.h"
+
+namespace qpid {
+
+namespace types {
+class Variant;
+}
+
+namespace messaging {
+
+class ProtocolRegistry;
+class Session;
+
+class ConnectionImpl : public virtual qpid::RefCounted
+{
+ public:
+ virtual ~ConnectionImpl() {}
+ virtual void open() = 0;
+ virtual bool isOpen() const = 0;
+ virtual void close() = 0;
+ virtual Session newSession(bool transactional, const std::string& name) = 0;
+ virtual Session getSession(const std::string& name) const = 0;
+ virtual void setOption(const std::string& name, const qpid::types::Variant& value) = 0;
+ virtual std::string getAuthenticatedUsername() = 0;
+ virtual void reconnect(const std::string& url) = 0;
+ virtual void reconnect() = 0;
+ virtual std::string getUrl() const = 0;
+ private:
+ friend class ProtocolRegistry;
+ boost::function<ConnectionImpl*()> next;
+};
+
+}} // namespace qpid::messaging
+
+#endif /*!QPID_MESSAGING_CONNECTIONIMPL_H*/
diff --git a/qpid/cpp/src/qpid/messaging/ConnectionOptions.cpp b/qpid/cpp/src/qpid/messaging/ConnectionOptions.cpp
new file mode 100644
index 0000000000..10c131c22f
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/ConnectionOptions.cpp
@@ -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.
+ *
+ */
+#include "qpid/messaging/ConnectionOptions.h"
+#include "qpid/messaging/exceptions.h"
+#include "qpid/types/Variant.h"
+#include "qpid/log/Statement.h"
+#include <algorithm>
+#include <limits>
+
+namespace qpid {
+namespace messaging {
+
+namespace {
+double FOREVER(std::numeric_limits<double>::max());
+
+double timeValue(const qpid::types::Variant& value) {
+ if (types::isIntegerType(value.getType()))
+ return double(value.asInt64());
+ return value.asDouble();
+}
+
+void merge(const std::string& value, std::vector<std::string>& list) {
+ if (std::find(list.begin(), list.end(), value) == list.end())
+ list.push_back(value);
+}
+
+void merge(const qpid::types::Variant::List& from, std::vector<std::string>& to)
+{
+ for (qpid::types::Variant::List::const_iterator i = from.begin(); i != from.end(); ++i)
+ merge(i->asString(), to);
+}
+
+}
+
+ConnectionOptions::ConnectionOptions(const std::map<std::string, qpid::types::Variant>& options)
+ : replaceUrls(false), reconnect(false), timeout(FOREVER), limit(-1), minReconnectInterval(0.001), maxReconnectInterval(2),
+ retries(0), reconnectOnLimitExceeded(true), nestAnnotations(false), setToOnSend(false)
+{
+ for (qpid::types::Variant::Map::const_iterator i = options.begin(); i != options.end(); ++i) {
+ set(i->first, i->second);
+ }
+}
+
+void ConnectionOptions::set(const std::string& name, const qpid::types::Variant& value)
+{
+ if (name == "reconnect") {
+ reconnect = value;
+ } else if (name == "reconnect-timeout" || name == "reconnect_timeout") {
+ timeout = timeValue(value);
+ } else if (name == "reconnect-limit" || name == "reconnect_limit") {
+ limit = value;
+ } else if (name == "reconnect-interval" || name == "reconnect_interval") {
+ maxReconnectInterval = minReconnectInterval = timeValue(value);
+ } else if (name == "reconnect-interval-min" || name == "reconnect_interval_min") {
+ minReconnectInterval = timeValue(value);
+ } else if (name == "reconnect-interval-max" || name == "reconnect_interval_max") {
+ maxReconnectInterval = timeValue(value);
+ } else if (name == "reconnect-urls-replace" || name == "reconnect_urls_replace") {
+ replaceUrls = value.asBool();
+ } else if (name == "reconnect-urls" || name == "reconnect_urls") {
+ if (replaceUrls) urls.clear();
+ if (value.getType() == qpid::types::VAR_LIST) {
+ merge(value.asList(), urls);
+ } else {
+ merge(value.asString(), urls);
+ }
+ } else if (name == "username") {
+ username = value.asString();
+ } else if (name == "password") {
+ password = value.asString();
+ } else if (name == "sasl-mechanism" || name == "sasl_mechanism" ||
+ name == "sasl-mechanisms" || name == "sasl_mechanisms") {
+ mechanism = value.asString();
+ } else if (name == "sasl-service" || name == "sasl_service") {
+ service = value.asString();
+ } else if (name == "sasl-min-ssf" || name == "sasl_min_ssf") {
+ minSsf = value;
+ } else if (name == "sasl-max-ssf" || name == "sasl_max_ssf") {
+ maxSsf = value;
+ } else if (name == "heartbeat") {
+ heartbeat = value;
+ } else if (name == "tcp-nodelay" || name == "tcp_nodelay") {
+ tcpNoDelay = value;
+ } else if (name == "locale") {
+ locale = value.asString();
+ } else if (name == "max-channels" || name == "max_channels") {
+ maxChannels = value;
+ } else if (name == "max-frame-size" || name == "max_frame_size") {
+ maxFrameSize = value;
+ } else if (name == "bounds") {
+ bounds = value;
+ } else if (name == "transport") {
+ protocol = value.asString();
+ } else if (name == "ssl-cert-name" || name == "ssl_cert_name") {
+ sslCertName = value.asString();
+ } else if (name == "ssl-ignore-hostname-verification-failure" || name == "ssl_ignore_hostname_verification_failure") {
+ sslIgnoreHostnameVerificationFailure = value;
+ } else if (name == "x-reconnect-on-limit-exceeded" || name == "x_reconnect_on_limit_exceeded") {
+ reconnectOnLimitExceeded = value;
+ } else if (name == "container-id" || name == "container_id") {
+ identifier = value.asString();
+ } else if (name == "nest-annotations" || name == "nest_annotations") {
+ nestAnnotations = value;
+ } else if (name == "set-to-on-send" || name == "set_to_on_send") {
+ setToOnSend = value;
+ } else if (name == "properties" || name == "client-properties" || name == "client_properties") {
+ properties = value.asMap();
+ } else {
+ throw qpid::messaging::MessagingException(QPID_MSG("Invalid option: " << name << " not recognised"));
+ }
+}
+
+}} // namespace qpid::messaging
diff --git a/qpid/cpp/src/qpid/messaging/ConnectionOptions.h b/qpid/cpp/src/qpid/messaging/ConnectionOptions.h
new file mode 100644
index 0000000000..c8c8798b7b
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/ConnectionOptions.h
@@ -0,0 +1,57 @@
+#ifndef QPID_MESSAGING_CONNECTIONOPTIONS_H
+#define QPID_MESSAGING_CONNECTIONOPTIONS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/messaging/ImportExport.h"
+
+#include "qpid/client/ConnectionSettings.h"
+#include <map>
+#include <vector>
+
+namespace qpid {
+namespace types {
+class Variant;
+}
+namespace messaging {
+
+struct ConnectionOptions : qpid::client::ConnectionSettings
+{
+ std::vector<std::string> urls;
+ bool replaceUrls;
+ bool reconnect;
+ double timeout;
+ int32_t limit;
+ double minReconnectInterval;
+ double maxReconnectInterval;
+ int32_t retries;
+ bool reconnectOnLimitExceeded;
+ std::string identifier;
+ bool nestAnnotations;
+ bool setToOnSend;
+ std::map<std::string, qpid::types::Variant> properties;
+
+ QPID_MESSAGING_EXTERN ConnectionOptions(const std::map<std::string, qpid::types::Variant>&);
+ QPID_MESSAGING_EXTERN void set(const std::string& name, const qpid::types::Variant& value);
+};
+}} // namespace qpid::messaging
+
+#endif /*!QPID_MESSAGING_CONNECTIONOPTIONS_H*/
diff --git a/qpid/cpp/src/qpid/messaging/Duration.cpp b/qpid/cpp/src/qpid/messaging/Duration.cpp
new file mode 100644
index 0000000000..a23e9f5bcb
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/Duration.cpp
@@ -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.
+ *
+ */
+#include "qpid/messaging/Duration.h"
+#include <limits>
+
+namespace qpid {
+namespace messaging {
+
+Duration::Duration(uint64_t ms) : milliseconds(ms) {}
+uint64_t Duration::getMilliseconds() const { return milliseconds; }
+
+Duration operator*(const Duration& duration, uint64_t multiplier)
+{
+ return Duration(duration.getMilliseconds() * multiplier);
+}
+
+Duration operator*(uint64_t multiplier, const Duration& duration)
+{
+ return Duration(duration.getMilliseconds() * multiplier);
+}
+
+bool operator==(const Duration& a, const Duration& b)
+{
+ return a.getMilliseconds() == b.getMilliseconds();
+}
+
+bool operator!=(const Duration& a, const Duration& b)
+{
+ return a.getMilliseconds() != b.getMilliseconds();
+}
+
+const Duration Duration::FOREVER(std::numeric_limits<uint64_t>::max());
+const Duration Duration::IMMEDIATE(0);
+const Duration Duration::SECOND(1000);
+const Duration Duration::MINUTE(SECOND * 60);
+
+}} // namespace qpid::messaging
diff --git a/qpid/cpp/src/qpid/messaging/FailoverUpdates.cpp b/qpid/cpp/src/qpid/messaging/FailoverUpdates.cpp
new file mode 100644
index 0000000000..4f2fcf2e82
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/FailoverUpdates.cpp
@@ -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.
+ *
+ */
+#include "qpid/messaging/FailoverUpdates.h"
+#include "qpid/messaging/Connection.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/messaging/Receiver.h"
+#include "qpid/messaging/Session.h"
+#include "qpid/sys/Runnable.h"
+#include "qpid/sys/Thread.h"
+#include "qpid/log/Statement.h"
+#include "qpid/Exception.h"
+#include "qpid/Url.h"
+#include "qpid/framing/Uuid.h"
+#include <vector>
+
+namespace qpid {
+namespace messaging {
+using framing::Uuid;
+
+struct FailoverUpdatesImpl : qpid::sys::Runnable
+{
+ Connection connection;
+ Session session;
+ Receiver receiver;
+ qpid::sys::Thread thread;
+
+ FailoverUpdatesImpl(Connection& c) : connection(c)
+ {
+ session = connection.createSession("failover-updates."+Uuid(true).str());
+ receiver = session.createReceiver("amq.failover");
+ thread = qpid::sys::Thread(*this);
+ }
+
+ ~FailoverUpdatesImpl() {
+ try {
+ session.close();
+ } catch(...) {} // Squash exceptions in a destructor.
+ thread.join();
+ }
+
+ void run()
+ {
+ try {
+ Message message;
+ while (receiver.fetch(message)) {
+ connection.setOption("reconnect-urls", message.getProperties()["amq.failover"]);
+ QPID_LOG(debug, "Set reconnect-urls to " << message.getProperties()["amq.failover"]);
+ session.acknowledge();
+ }
+ }
+ catch (const ClosedException&) {}
+ catch (const qpid::TransportFailure& e) {
+ QPID_LOG(warning, "Failover updates stopped on loss of connection. " << e.what());
+ }
+ catch (const std::exception& e) {
+ QPID_LOG(warning, "Failover updates stopped due to exception: " << e.what());
+ }
+ }
+};
+
+FailoverUpdates::FailoverUpdates(Connection& connection) : impl(new FailoverUpdatesImpl(connection)) {}
+FailoverUpdates::~FailoverUpdates() { if (impl) { delete impl; } }
+FailoverUpdates::FailoverUpdates(const FailoverUpdates&) : impl(0) {}
+FailoverUpdates& FailoverUpdates::operator=(const FailoverUpdates&) { return *this; }
+
+
+}} // namespace qpid::messaging
diff --git a/qpid/cpp/src/qpid/messaging/Logger.cpp b/qpid/cpp/src/qpid/messaging/Logger.cpp
new file mode 100644
index 0000000000..c259cb4c1b
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/Logger.cpp
@@ -0,0 +1,200 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/messaging/Logger.h"
+
+#include "qpid/log/Logger.h"
+#include "qpid/log/OstreamOutput.h"
+#include "qpid/messaging/exceptions.h"
+
+#include <sstream>
+#include <string>
+#include <vector>
+
+using std::string;
+using std::vector;
+
+namespace qpid {
+namespace messaging {
+
+// Proxy class to call the users output class/routine
+class ProxyOutput : public qpid::log::Logger::Output {
+ LoggerOutput& output;
+
+ void log(const qpid::log::Statement& s, const string& message) {
+ output.log(qpid::messaging::Level(s.level), s.category==qpid::log::external_application, s.file, s.line, s.function, message);
+ }
+
+public:
+ ProxyOutput(LoggerOutput& o) :
+ output(o)
+ {}
+};
+
+LoggerOutput::~LoggerOutput()
+{
+}
+
+inline qpid::log::Logger& logger() {
+ static qpid::log::Logger& theLogger=qpid::log::Logger::instance();
+ return theLogger;
+}
+
+namespace {
+ std::string loggerUsage;
+ qpid::log::Selector loggerSelector;
+}
+
+std::string Logger::usage()
+{
+ return loggerUsage;
+}
+
+void Logger::configure(int argc, const char* argv[], const string& pre)
+try
+{
+ bool logToStdout = false;
+ bool logToStderr = false;
+ string logFile;
+ std::vector<std::string> selectors;
+ std::vector<std::string> deselectors;
+ bool time = false;
+ bool level = false;
+ bool thread = false;
+ bool source = false;
+ bool function = false;
+ bool hiresTs = false;
+
+ selectors.push_back("notice+"); // Set this for the usage message default
+
+ string prefix = pre.empty() ? pre : pre+"-";
+ qpid::Options myOptions;
+ myOptions.addOptions()
+ ((prefix+"log-enable").c_str(), optValue(selectors, "RULE"),
+ ("Enables logging for selected levels and components. "
+ "RULE is in the form 'LEVEL[+-][:PATTERN]'\n"
+ "LEVEL is one of: \n\t "+qpid::log::getLevels()+"\n"
+ "PATTERN is a logging category name, or a namespace-qualified "
+ "function name or name fragment. "
+ "Logging category names are: \n\t "+qpid::log::getCategories()+"\n"
+ "The category \"Application\" contains all messages logged by the application.\n"
+ "For example:\n"
+ "\t'--log-enable warning+'\n"
+ "logs all warning, error and critical messages.\n"
+ "\t'--log-enable trace+:Application --log-enable notice+'\n"
+ "logs all application messages, but only notice or higher for the qpid library messages\n"
+ "\t'--log-enable debug:framing'\n"
+ "logs debug messages from all functions with 'framing' in the namespace or function name.\n"
+ "This option can be used multiple times").c_str())
+ ((prefix+"log-disable").c_str(), optValue(deselectors, "RULE"),
+ ("Disables logging for selected levels and components. "
+ "RULE is in the form 'LEVEL[+-][:PATTERN]'\n"
+ "LEVEL is one of: \n\t "+qpid::log::getLevels()+"\n"
+ "PATTERN is a logging category name, or a namespace-qualified "
+ "function name or name fragment. "
+ "Logging category names are: \n\t "+qpid::log::getCategories()+"\n"
+ "For example:\n"
+ "\t'--log-disable warning-'\n"
+ "disables logging all warning, notice, info, debug, and trace messages.\n"
+ "\t'--log-disable trace:Application'\n"
+ "disables all application trace messages.\n"
+ "\t'--log-disable debug-:qmf::'\n"
+ "disables logging debug and trace messages from all functions with 'qmf::' in the namespace.\n"
+ "This option can be used multiple times").c_str())
+ ((prefix+"log-time").c_str(), optValue(time, "yes|no"), "Include time in log messages")
+ ((prefix+"log-level").c_str(), optValue(level,"yes|no"), "Include severity level in log messages")
+ ((prefix+"log-source").c_str(), optValue(source,"yes|no"), "Include source file:line in log messages")
+ ((prefix+"log-thread").c_str(), optValue(thread,"yes|no"), "Include thread ID in log messages")
+ ((prefix+"log-function").c_str(), optValue(function,"yes|no"), "Include function signature in log messages")
+ ((prefix+"log-hires-timestamp").c_str(), optValue(hiresTs,"yes|no"), "Use hi-resolution timestamps in log messages")
+ ((prefix+"log-to-stderr").c_str(), optValue(logToStderr, "yes|no"), "Send logging output to stderr")
+ ((prefix+"log-to-stdout").c_str(), optValue(logToStdout, "yes|no"), "Send logging output to stdout")
+ ((prefix+"log-to-file").c_str(), optValue(logFile, "FILE"), "Send log output to FILE.")
+ ;
+
+ std::ostringstream loggerSStream;
+ myOptions.print(loggerSStream);
+
+ loggerUsage=loggerSStream.str();
+
+ selectors.clear(); // Clear to give passed in options precedence
+
+ // Parse the command line not failing for unrecognised options
+ myOptions.parse(argc, argv, std::string(), true);
+
+ // If no passed in enable or disable log specification then go back to default
+ if (selectors.empty() && deselectors.empty())
+ selectors.push_back("notice+");
+ // Set the logger options according to what we just parsed
+ qpid::log::Options logOptions;
+ logOptions.selectors = selectors;
+ logOptions.deselectors = deselectors;
+ logOptions.time = time;
+ logOptions.level = level;
+ logOptions.category = false;
+ logOptions.thread = thread;
+ logOptions.source = source;
+ logOptions.function = function;
+ logOptions.hiresTs = hiresTs;
+
+ loggerSelector = qpid::log::Selector(logOptions);
+ logger().clear(); // Need to clear before configuring as it will have been initialised statically already
+ logger().format(logOptions);
+ logger().select(loggerSelector);
+
+ // Have to set up the standard output sinks manually
+ if (logToStderr)
+ logger().output(std::auto_ptr<qpid::log::Logger::Output>
+ (new qpid::log::OstreamOutput(std::clog)));
+ if (logToStdout)
+ logger().output(std::auto_ptr<qpid::log::Logger::Output>
+ (new qpid::log::OstreamOutput(std::cout)));
+
+ if (logFile.length() > 0)
+ logger().output(std::auto_ptr<qpid::log::Logger::Output>
+ (new qpid::log::OstreamOutput(logFile)));
+}
+catch (std::exception& e)
+{
+ throw MessagingException(e.what());
+}
+
+void Logger::setOutput(LoggerOutput& o)
+{
+ logger().output(std::auto_ptr<qpid::log::Logger::Output>(new ProxyOutput(o)));
+}
+
+void Logger::log(Level level, const char* file, int line, const char* function, const string& message)
+{
+ if (loggerSelector.isEnabled(qpid::log::Level(level), function, qpid::log::unspecified)) {
+ qpid::log::Statement s = {
+ true,
+ file,
+ line,
+ function,
+ qpid::log::Level(level),
+ qpid::log::external_application,
+ };
+ logger().log(s, message);
+ }
+}
+
+}} // namespace qpid::messaging
diff --git a/qpid/cpp/src/qpid/messaging/Message.cpp b/qpid/cpp/src/qpid/messaging/Message.cpp
new file mode 100644
index 0000000000..62ee93b6c2
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/Message.cpp
@@ -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.
+ *
+ */
+#include "qpid/messaging/Message.h"
+#include "qpid/messaging/MessageImpl.h"
+#include "qpid/amqp_0_10/Codecs.h"
+#include <qpid/Exception.h>
+#include <boost/format.hpp>
+
+namespace qpid {
+namespace messaging {
+
+using namespace qpid::types;
+
+Message::Message(const std::string& bytes) : impl(new MessageImpl(bytes)) {}
+Message::Message(const char* bytes, size_t count) : impl(new MessageImpl(bytes, count)) {}
+Message::Message(qpid::types::Variant& c) : impl(new MessageImpl(std::string()))
+{
+ setContentObject(c);
+}
+
+Message::Message(const Message& m) : impl(new MessageImpl(*m.impl)) {}
+Message::~Message() { delete impl; }
+
+Message& Message::operator=(const Message& m) { *impl = *m.impl; return *this; }
+
+void Message::setReplyTo(const Address& d) { impl->setReplyTo(d); }
+const Address& Message::getReplyTo() const { return impl->getReplyTo(); }
+
+void Message::setSubject(const std::string& s) { impl->setSubject(s); }
+const std::string& Message::getSubject() const { return impl->getSubject(); }
+
+void Message::setContentType(const std::string& s) { impl->setContentType(s); }
+const std::string& Message::getContentType() const { return impl->getContentType(); }
+
+void Message::setMessageId(const std::string& id) { impl->setMessageId(id); }
+const std::string& Message::getMessageId() const { return impl->getMessageId(); }
+
+void Message::setUserId(const std::string& id) { impl->setUserId(id); }
+const std::string& Message::getUserId() const { return impl->getUserId(); }
+
+void Message::setCorrelationId(const std::string& id) { impl->setCorrelationId(id); }
+const std::string& Message::getCorrelationId() const { return impl->getCorrelationId(); }
+
+uint8_t Message::getPriority() const { return impl->getPriority(); }
+void Message::setPriority(uint8_t priority) { impl->setPriority(priority); }
+
+void Message::setTtl(Duration ttl) { impl->setTtl(ttl.getMilliseconds()); }
+Duration Message::getTtl() const { return Duration(impl->getTtl()); }
+
+void Message::setDurable(bool durable) { impl->setDurable(durable); }
+bool Message::getDurable() const { return impl->isDurable(); }
+
+bool Message::getRedelivered() const { return impl->isRedelivered(); }
+void Message::setRedelivered(bool redelivered) { impl->setRedelivered(redelivered); }
+
+const Variant::Map& Message::getProperties() const { return impl->getHeaders(); }
+Variant::Map& Message::getProperties() { return impl->getHeaders(); }
+void Message::setProperties(const Variant::Map& p) { getProperties() = p; }
+void Message::setProperty(const std::string& k, const qpid::types::Variant& v) { impl->setHeader(k,v); }
+
+void Message::setContent(const std::string& c) { impl->setBytes(c); }
+void Message::setContent(const char* chars, size_t count) { impl->setBytes(chars, count); }
+std::string Message::getContent() const { return impl->getBytes(); }
+
+void Message::setContentBytes(const std::string& c) { impl->setBytes(c); }
+std::string Message::getContentBytes() const { return impl->getBytes(); }
+
+qpid::types::Variant& Message::getContentObject() { return impl->getContent(); }
+void Message::setContentObject(const qpid::types::Variant& c) { impl->getContent() = c; }
+const qpid::types::Variant& Message::getContentObject() const { return impl->getContent(); }
+
+const char* Message::getContentPtr() const
+{
+ return impl->getBytes().data();
+}
+
+size_t Message::getContentSize() const
+{
+ return impl->getBytes().size();
+}
+
+EncodingException::EncodingException(const std::string& msg) : qpid::types::Exception(msg) {}
+
+const std::string BAD_ENCODING("Unsupported encoding: %1% (only %2% is supported at present).");
+
+template <class C> struct MessageCodec
+{
+ static bool checkEncoding(const std::string& requested)
+ {
+ if (requested.size()) {
+ if (requested == C::contentType) return true;
+ else throw EncodingException((boost::format(BAD_ENCODING) % requested % C::contentType).str());
+ } else {
+ return false;
+ }
+ }
+
+ /*
+ * Currently only support a single encoding type for both list and
+ * map, based on AMQP 0-10, though wider support is anticipated in the
+ * future. This method simply checks that the desired encoding (if one
+ * is specified, either through the message-content or through an
+ * override) is indeed supported.
+ */
+ static void checkEncoding(const Message& message, const std::string& requested)
+ {
+ checkEncoding(requested) || checkEncoding(message.getContentType());
+ }
+
+ static void decode(const Message& message, typename C::ObjectType& object, const std::string& encoding)
+ {
+ checkEncoding(message, encoding);
+ try {
+ C::decode(message.getContent(), object);
+ } catch (const qpid::Exception &ex) {
+ throw EncodingException(ex.what());
+ }
+ }
+
+ static void encode(const typename C::ObjectType& map, Message& message, const std::string& encoding)
+ {
+ checkEncoding(message, encoding);
+ std::string content;
+ C::encode(map, content);
+ message.setContentType(C::contentType);
+ message.setContent(content);
+ }
+};
+
+void decode(const Message& message, Variant::Map& map, const std::string& encoding)
+{
+ MessageCodec<qpid::amqp_0_10::MapCodec>::decode(message, map, encoding);
+}
+void decode(const Message& message, Variant::List& list, const std::string& encoding)
+{
+ MessageCodec<qpid::amqp_0_10::ListCodec>::decode(message, list, encoding);
+}
+void encode(const Variant::Map& map, Message& message, const std::string& encoding)
+{
+ MessageCodec<qpid::amqp_0_10::MapCodec>::encode(map, message, encoding);
+}
+void encode(const Variant::List& list, Message& message, const std::string& encoding)
+{
+ MessageCodec<qpid::amqp_0_10::ListCodec>::encode(list, message, encoding);
+}
+
+}} // namespace qpid::messaging
diff --git a/qpid/cpp/src/qpid/messaging/MessageImpl.cpp b/qpid/cpp/src/qpid/messaging/MessageImpl.cpp
new file mode 100644
index 0000000000..620e48ec2e
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/MessageImpl.cpp
@@ -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.
+ *
+ */
+#include "MessageImpl.h"
+#include "qpid/messaging/Message.h"
+
+namespace qpid {
+namespace messaging {
+
+namespace {
+const std::string EMPTY_STRING = "";
+}
+
+using namespace qpid::types;
+
+MessageImpl::MessageImpl(const std::string& c) :
+ priority(0),
+ ttl(0),
+ durable(false),
+ redelivered(false),
+ bytes(c),
+ contentDecoded(false),
+ internalId(0) {}
+MessageImpl::MessageImpl(const char* chars, size_t count) :
+ priority(0),
+ ttl(0),
+ durable (false),
+ redelivered(false),
+ bytes(chars, count),
+ contentDecoded(false),
+ internalId(0) {}
+
+void MessageImpl::clear()
+{
+ replyTo = Address();
+ subject = std::string();
+ contentType = std::string();
+ messageId = std::string();
+ userId= std::string();
+ correlationId = std::string();
+ priority = 0;
+ ttl = 0;
+ durable = false;
+ redelivered = false;
+ headers = qpid::types::Variant::Map();
+
+ bytes = std::string();
+ content = qpid::types::Variant();
+ contentDecoded = false;
+ encoded = boost::shared_ptr<const qpid::messaging::amqp::EncodedMessage>();
+ internalId = 0;
+}
+
+void MessageImpl::setReplyTo(const Address& d)
+{
+ replyTo = d;
+ updated();
+}
+const Address& MessageImpl::getReplyTo() const
+{
+ if (!replyTo && encoded) encoded->getReplyTo(replyTo);
+ return replyTo;
+}
+
+void MessageImpl::setSubject(const std::string& s)
+{
+ subject = s;
+ updated();
+}
+const std::string& MessageImpl::getSubject() const
+{
+ if (!subject.size() && encoded) encoded->getSubject(subject);
+ return subject;
+}
+
+void MessageImpl::setContentType(const std::string& s)
+{
+ contentType = s;
+ updated();
+}
+const std::string& MessageImpl::getContentType() const
+{
+ if (!contentType.size() && encoded) encoded->getContentType(contentType);
+ return contentType;
+}
+
+void MessageImpl::setMessageId(const std::string& s)
+{
+ messageId = s;
+ updated();
+}
+const std::string& MessageImpl::getMessageId() const
+{
+ if (!messageId.size() && encoded) encoded->getMessageId(messageId);
+ return messageId;
+}
+void MessageImpl::setUserId(const std::string& s)
+{
+ userId = s;
+ updated();
+}
+const std::string& MessageImpl::getUserId() const
+{
+ if (!userId.size() && encoded) encoded->getUserId(userId);
+ return userId;
+}
+void MessageImpl::setCorrelationId(const std::string& s)
+{
+ correlationId = s;
+ updated();
+}
+const std::string& MessageImpl::getCorrelationId() const
+{
+ if (!correlationId.size() && encoded) encoded->getCorrelationId(correlationId);
+ return correlationId;
+}
+void MessageImpl::setPriority(uint8_t p)
+{
+ priority = p;
+}
+uint8_t MessageImpl::getPriority() const
+{
+ return priority;
+}
+void MessageImpl::setTtl(uint64_t t)
+{
+ ttl = t;
+}
+uint64_t MessageImpl::getTtl() const
+{
+ return ttl;
+}
+void MessageImpl::setDurable(bool d)
+{
+ durable = d;
+}
+bool MessageImpl::isDurable() const
+{
+ return durable;
+}
+void MessageImpl::setRedelivered(bool b)
+{
+ redelivered = b;
+}
+bool MessageImpl::isRedelivered() const
+{
+ return redelivered;
+}
+
+const Variant::Map& MessageImpl::getHeaders() const
+{
+ if (!headers.size() && encoded) encoded->populate(headers);
+ return headers;
+}
+Variant::Map& MessageImpl::getHeaders() {
+ if (!headers.size() && encoded) encoded->populate(headers);
+ updated();
+ return headers;
+}
+void MessageImpl::setHeader(const std::string& key, const qpid::types::Variant& val)
+{
+ headers[key] = val; updated();
+}
+
+//should these methods be on MessageContent?
+void MessageImpl::setBytes(const std::string& c)
+{
+ bytes = c;
+ updated();
+}
+void MessageImpl::setBytes(const char* chars, size_t count)
+{
+ bytes.assign(chars, count);
+ updated();
+}
+const std::string& MessageImpl::getBytes() const
+{
+ if (encoded && !contentDecoded) {
+ encoded->getBody(bytes, content);
+ contentDecoded = true;
+ }
+ if (bytes.empty() && content.getType() == VAR_STRING) return content.getString();
+ else return bytes;
+}
+std::string& MessageImpl::getBytes()
+{
+ updated();//have to assume body may be edited, invalidating our message
+ if (bytes.empty() && content.getType() == VAR_STRING) return content.getString();
+ else return bytes;
+}
+
+qpid::types::Variant& MessageImpl::getContent()
+{
+ updated();//have to assume content may be edited, invalidating our message
+ return content;
+}
+
+const qpid::types::Variant& MessageImpl::getContent() const
+{
+ if (encoded && !contentDecoded) {
+ encoded->getBody(bytes, content);
+ contentDecoded = true;
+ }
+ return content;
+}
+
+void MessageImpl::setInternalId(qpid::framing::SequenceNumber i) { internalId = i; }
+qpid::framing::SequenceNumber MessageImpl::getInternalId() { return internalId; }
+
+void MessageImpl::updated()
+{
+
+ if (!replyTo && encoded) encoded->getReplyTo(replyTo);
+ if (!subject.size() && encoded) encoded->getSubject(subject);
+ if (!contentType.size() && encoded) encoded->getContentType(contentType);
+ if (!messageId.size() && encoded) encoded->getMessageId(messageId);
+ if (!userId.size() && encoded) encoded->getUserId(userId);
+ if (!correlationId.size() && encoded) encoded->getCorrelationId(correlationId);
+ if (!headers.size() && encoded) encoded->populate(headers);
+ if (encoded && !contentDecoded) {
+ encoded->getBody(bytes, content);
+ contentDecoded = true;
+ }
+
+ encoded.reset();
+}
+
+MessageImpl& MessageImplAccess::get(Message& msg)
+{
+ return *msg.impl;
+}
+const MessageImpl& MessageImplAccess::get(const Message& msg)
+{
+ return *msg.impl;
+}
+}} // namespace qpid::messaging
diff --git a/qpid/cpp/src/qpid/messaging/MessageImpl.h b/qpid/cpp/src/qpid/messaging/MessageImpl.h
new file mode 100644
index 0000000000..647972de16
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/MessageImpl.h
@@ -0,0 +1,122 @@
+#ifndef QPID_MESSAGING_MESSAGEIMPL_H
+#define QPID_MESSAGING_MESSAGEIMPL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/messaging/ImportExport.h"
+
+#include "qpid/messaging/Address.h"
+#include "qpid/types/Variant.h"
+#include "qpid/framing/SequenceNumber.h"
+#include "qpid/messaging/amqp/EncodedMessage.h"
+#include <vector>
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace messaging {
+
+class MessageImpl
+{
+ private:
+ mutable Address replyTo;
+ mutable std::string subject;
+ mutable std::string contentType;
+ mutable std::string messageId;
+ mutable std::string userId;
+ mutable std::string correlationId;
+ uint8_t priority;
+ uint64_t ttl;
+ bool durable;
+ bool redelivered;
+ mutable qpid::types::Variant::Map headers;
+
+ mutable std::string bytes;
+ mutable qpid::types::Variant content;
+ mutable bool contentDecoded;
+ boost::shared_ptr<const qpid::messaging::amqp::EncodedMessage> encoded;
+
+ qpid::framing::SequenceNumber internalId;
+
+ void updated();
+ public:
+ MessageImpl(const std::string& c);
+ MessageImpl(const char* chars, size_t count);
+
+ void clear();
+ void setReplyTo(const Address& d);
+ QPID_MESSAGING_EXTERN const Address& getReplyTo() const;
+
+ void setSubject(const std::string& s);
+ QPID_MESSAGING_EXTERN const std::string& getSubject() const;
+
+ void setContentType(const std::string& s);
+ QPID_MESSAGING_EXTERN const std::string& getContentType() const;
+
+ void setMessageId(const std::string&);
+ QPID_MESSAGING_EXTERN const std::string& getMessageId() const;
+ void setUserId(const std::string& );
+ QPID_MESSAGING_EXTERN const std::string& getUserId() const;
+ void setCorrelationId(const std::string& );
+ QPID_MESSAGING_EXTERN const std::string& getCorrelationId() const;
+ void setPriority(uint8_t);
+ QPID_MESSAGING_EXTERN uint8_t getPriority() const;
+ void setTtl(uint64_t);
+ QPID_MESSAGING_EXTERN uint64_t getTtl() const;
+ void setDurable(bool);
+ QPID_MESSAGING_EXTERN bool isDurable() const;
+ void setRedelivered(bool);
+ QPID_MESSAGING_EXTERN bool isRedelivered() const;
+
+
+ QPID_MESSAGING_EXTERN const qpid::types::Variant::Map& getHeaders() const;
+ qpid::types::Variant::Map& getHeaders();
+ void setHeader(const std::string& key, const qpid::types::Variant& val);
+
+ void setBytes(const std::string& bytes);
+ void setBytes(const char* chars, size_t count);
+ QPID_MESSAGING_EXTERN const std::string& getBytes() const;
+ std::string& getBytes();
+ qpid::types::Variant& getContent();
+ QPID_MESSAGING_EXTERN const qpid::types::Variant& getContent() const;
+
+ QPID_MESSAGING_EXTERN void setInternalId(qpid::framing::SequenceNumber id);
+ QPID_MESSAGING_EXTERN qpid::framing::SequenceNumber getInternalId();
+ void setEncoded(boost::shared_ptr<const qpid::messaging::amqp::EncodedMessage> e) { encoded = e; }
+ boost::shared_ptr<const qpid::messaging::amqp::EncodedMessage> getEncoded() const { return encoded; }
+};
+
+class Message;
+
+/**
+ * Provides access to the internal MessageImpl for a message which is
+ * useful when accessing any message state not exposed directly
+ * through the public API.
+ */
+struct MessageImplAccess
+{
+ QPID_MESSAGING_EXTERN static MessageImpl& get(Message&);
+ QPID_MESSAGING_EXTERN static const MessageImpl& get(const Message&);
+};
+
+}} // namespace qpid::messaging
+
+#endif /*!QPID_MESSAGING_MESSAGEIMPL_H*/
diff --git a/qpid/cpp/src/qpid/messaging/Message_io.cpp b/qpid/cpp/src/qpid/messaging/Message_io.cpp
new file mode 100644
index 0000000000..b6bc43a4e6
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/Message_io.cpp
@@ -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.
+ *
+ */
+#include "qpid/messaging/Message_io.h"
+#include <ostream>
+
+namespace qpid {
+namespace messaging {
+
+using namespace std;
+ostream& operator<<(ostream& o, const Message& message) {
+ o << "Message(properties=" << message.getProperties();
+ if (!message.getSubject().empty()) {
+ o << ", subject='" << message.getSubject() << "'";
+ }
+ if (!message.getContentObject().isVoid()) {
+ o << ", content='";
+ if (message.getContentType() == "amqp/map") {
+ o << message.getContentObject().asMap();
+ } else {
+ o << message.getContentObject();
+ }
+ }
+ o << "')";
+ return o;
+}
+
+}} // namespace qpid::messaging
diff --git a/qpid/cpp/src/qpid/messaging/PrivateImplRef.h b/qpid/cpp/src/qpid/messaging/PrivateImplRef.h
new file mode 100644
index 0000000000..a60f4eeadf
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/PrivateImplRef.h
@@ -0,0 +1,94 @@
+#ifndef QPID_MESSAGING_PRIVATEIMPL_H
+#define QPID_MESSAGING_PRIVATEIMPL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/messaging/ImportExport.h"
+#include <boost/intrusive_ptr.hpp>
+#include "qpid/RefCounted.h"
+
+namespace qpid {
+namespace messaging {
+
+/**
+ * Helper class to implement a class with a private, reference counted
+ * implementation and reference semantics.
+ *
+ * Such classes are used in the public API to hide implementation, they
+ * should. Example of use:
+ *
+ * === Foo.h
+ *
+ * template <class T> PrivateImplRef;
+ * class FooImpl;
+ *
+ * Foo : public Handle<FooImpl> {
+ * public:
+ * Foo(FooImpl* = 0);
+ * Foo(const Foo&);
+ * ~Foo();
+ * Foo& operator=(const Foo&);
+ *
+ * int fooDo(); // and other Foo functions...
+ *
+ * private:
+ * typedef FooImpl Impl;
+ * Impl* impl;
+ * friend class PrivateImplRef<Foo>;
+ *
+ * === Foo.cpp
+ *
+ * typedef PrivateImplRef<Foo> PI;
+ * Foo::Foo(FooImpl* p) { PI::ctor(*this, p); }
+ * Foo::Foo(const Foo& c) : Handle<FooImpl>() { PI::copy(*this, c); }
+ * Foo::~Foo() { PI::dtor(*this); }
+ * Foo& Foo::operator=(const Foo& c) { return PI::assign(*this, c); }
+ *
+ * int foo::fooDo() { return impl->fooDo(); }
+ *
+ */
+template <class T> class PrivateImplRef {
+ public:
+ typedef typename T::Impl Impl;
+ typedef boost::intrusive_ptr<Impl> intrusive_ptr;
+
+ /** Get the implementation pointer from a handle */
+ static intrusive_ptr get(const T& t) { return intrusive_ptr(t.impl); }
+
+ /** Set the implementation pointer in a handle */
+ static void set(T& t, const intrusive_ptr& p) {
+ if (t.impl == p) return;
+ if (t.impl) intrusive_ptr_release(t.impl);
+ t.impl = p.get();
+ if (t.impl) intrusive_ptr_add_ref(t.impl);
+ }
+
+ // Helper functions to implement the ctor, dtor, copy, assign
+ static void ctor(T& t, Impl* p) { t.impl = p; if (p) intrusive_ptr_add_ref(p); }
+ static void copy(T& t, const T& x) { if (&t == &x) return; t.impl = 0; assign(t, x); }
+ static void dtor(T& t) { if(t.impl) intrusive_ptr_release(t.impl); }
+ static T& assign(T& t, const T& x) { set(t, get(x)); return t;}
+};
+
+}} // namespace qpid::messaging
+
+#endif /*!QPID_MESSAGING_PRIVATEIMPL_H*/
diff --git a/qpid/cpp/src/qpid/messaging/ProtocolRegistry.cpp b/qpid/cpp/src/qpid/messaging/ProtocolRegistry.cpp
new file mode 100644
index 0000000000..dbb0d6dfc2
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/ProtocolRegistry.cpp
@@ -0,0 +1,200 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "ProtocolRegistry.h"
+#include "qpid/messaging/exceptions.h"
+#include "qpid/client/amqp0_10/ConnectionImpl.h"
+#include "qpid/client/LoadPlugins.h"
+#include "qpid/log/Statement.h"
+#include "qpid/Options.h"
+#include "qpid/StringUtils.h"
+#include "config.h"
+#include <map>
+#include <sstream>
+#include <boost/bind.hpp>
+
+using qpid::types::Variant;
+
+namespace qpid {
+namespace messaging {
+namespace {
+struct ProtocolOptions : qpid::Options
+{
+ std::string protocolDefaults;
+
+ ProtocolOptions() : qpid::Options("Protocol Settings")
+ {
+ addOptions()
+ ("protocol-defaults", optValue(protocolDefaults, "PROTOCOLS"), "Protocols to use when none are specified");
+ }
+};
+const std::string SEPARATOR(", ");
+const std::string EMPTY;
+std::string join(const std::vector<std::string>& in, const std::string& base=EMPTY, const std::string& separator = SEPARATOR)
+{
+ std::stringstream out;
+ if (!base.empty()) out << base;
+ for (std::vector<std::string>::const_iterator i = in.begin(); i != in.end(); ++i) {
+ if (i != in.begin()) out << separator;
+ out << *i;
+ }
+ return out.str();
+}
+
+typedef std::map<std::string, ProtocolRegistry::Factory*> Factories;
+
+ConnectionImpl* create_0_10(const std::string& url, const qpid::types::Variant::Map& options)
+{
+ return new qpid::client::amqp0_10::ConnectionImpl(url, options);
+}
+
+class Registry
+{
+ public:
+ Registry()
+ {
+ factories["amqp0-10"] = &create_0_10;
+ CommonOptions common("", "", QPIDC_CONF_FILE);
+ ProtocolOptions options;
+ try {
+ common.parse(0, 0, common.clientConfig, true);
+ options.parse (0, 0, common.clientConfig, true);
+ } catch (const std::exception& e) {
+ throw qpid::types::Exception(QPID_MSG("Failed to parse options while initialising Protocol Registry: " << e.what()));
+ }
+ QPID_LOG(debug, "Protocol defaults: " << options.protocolDefaults);
+ if (!options.protocolDefaults.empty()) {
+ split(versions, options.protocolDefaults, ", ");
+ }
+ }
+ ProtocolRegistry::Factory* find(const std::string& name) const
+ {
+ Factories::const_iterator i = factories.find(name);
+ if (i == factories.end()) {
+ std::stringstream error;
+ error << "Unsupported protocol: " << name;
+ error << " (valid values are " << getNames() << ")";
+ throw MessagingException(error.str());
+ } else {
+ return i->second;
+ }
+ }
+ void add(const std::string& name, ProtocolRegistry::Factory* factory)
+ {
+ factories[name] = factory;
+ }
+ std::string getNames() const
+ {
+ std::stringstream names;
+ for (Factories::const_iterator i = factories.begin(); i != factories.end(); ++i) {
+ if (i != factories.begin()) names << ", ";
+ names << i->first;
+ }
+ return names.str();
+ }
+ void collectNames(std::vector<std::string>& names) const
+ {
+ for (std::vector< std::string >::const_iterator i = versions.begin(); i != versions.end(); ++i) {
+ Factories::const_iterator j = factories.find(*i);
+ if (j == factories.end()) {
+ QPID_LOG(notice, "Unsupported protocol specified in defaults " << *i);
+ } else {
+ names.push_back(*i);
+ }
+ }
+ if (names.empty()) {
+ if (!versions.empty()) {
+ QPID_LOG(warning, "Protocol defaults specified are not valid (" << join(versions) << ") falling back to " << getNames());
+ }
+ for (Factories::const_iterator i = factories.begin(); i != factories.end(); ++i) {
+ names.push_back(i->first);
+ }
+ }
+ }
+ private:
+ Factories factories;
+ std::vector<std::string> versions;
+};
+
+Registry& theRegistry()
+{
+ static Registry factories;
+ return factories;
+}
+
+bool extract(const std::string& key, Variant& value, const Variant::Map& in, Variant::Map& out)
+{
+ bool matched = false;
+ for (Variant::Map::const_iterator i = in.begin(); i != in.end(); ++i) {
+ if (i->first == key) {
+ value = i->second;
+ matched = true;
+ } else {
+ out.insert(*i);
+ }
+ }
+ return matched;
+}
+}
+
+ConnectionImpl* ProtocolRegistry::create(const std::string& url, const Variant::Map& options)
+{
+ qpid::client::theModuleLoader();//ensure modules are loaded
+ Variant name;
+ Variant::Map stripped;
+ std::vector<std::string> versions;
+ if (extract("protocol", name, options, stripped)) {
+ split(versions, name.asString(), ", ");
+ } else {
+ theRegistry().collectNames(versions);
+ }
+ bool debugOn;
+ QPID_LOG_TEST(debug, debugOn);
+ if (debugOn) {
+ QPID_LOG(debug, "Trying versions " << join(versions));
+ }
+ return createInternal(versions, url, stripped, join(versions, "No suitable protocol version supported by peer, tried "));
+}
+
+ConnectionImpl* ProtocolRegistry::createInternal(const std::vector<std::string>& requested, const std::string& url, const Variant::Map& options, const std::string& error)
+{
+ std::vector<std::string>::const_iterator i = requested.begin();
+ if (i == requested.end())
+ throw MessagingException(error);
+ std::string name = *i;
+ ConnectionImpl* result = theRegistry().find(name)(url, options);
+ result->next = boost::bind(&ProtocolRegistry::createInternal, std::vector<std::string>(++i, requested.end()), url, options, error);
+ return result;
+ }
+
+ConnectionImpl* ProtocolRegistry::next(ConnectionImpl* last)
+{
+ if (last->next) {
+ return last->next();
+ }
+ throw MessagingException("No suitable protocol version supported by peer");
+}
+
+void ProtocolRegistry::add(const std::string& name, Factory* factory)
+{
+ theRegistry().add(name, factory);
+}
+
+}} // namespace qpid::messaging
diff --git a/qpid/cpp/src/qpid/messaging/ProtocolRegistry.h b/qpid/cpp/src/qpid/messaging/ProtocolRegistry.h
new file mode 100644
index 0000000000..6a6f5962c3
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/ProtocolRegistry.h
@@ -0,0 +1,47 @@
+#ifndef QPID_MESSAGING_PROTOCOLREGISTRY_H
+#define QPID_MESSAGING_PROTOCOLREGISTRY_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/messaging/ImportExport.h"
+#include "qpid/types/Variant.h"
+#include <vector>
+
+namespace qpid {
+namespace messaging {
+class ConnectionImpl;
+/**
+ * Registry for different implementations of the messaging API e.g AMQP 1.0
+ */
+class ProtocolRegistry
+{
+ public:
+ typedef ConnectionImpl* Factory(const std::string& url, const qpid::types::Variant::Map& options);
+ static ConnectionImpl* create(const std::string& url, const qpid::types::Variant::Map& options);
+ static ConnectionImpl* next(ConnectionImpl*);
+ QPID_MESSAGING_EXTERN static void add(const std::string& name, Factory* factory);
+ private:
+ static ConnectionImpl* createInternal(const std::vector<std::string>& versions, const std::string& url, const qpid::types::Variant::Map& options, const std::string& error);
+};
+}} // namespace qpid::messaging
+
+#endif /*!QPID_MESSAGING_PROTOCOLREGISTRY_H*/
diff --git a/qpid/cpp/src/qpid/messaging/Receiver.cpp b/qpid/cpp/src/qpid/messaging/Receiver.cpp
new file mode 100644
index 0000000000..a2c6b4cade
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/Receiver.cpp
@@ -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.
+ *
+ */
+#include "qpid/messaging/Receiver.h"
+#include "qpid/messaging/Address.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/messaging/MessageImpl.h"
+#include "qpid/messaging/ReceiverImpl.h"
+#include "qpid/messaging/Session.h"
+#include "qpid/messaging/PrivateImplRef.h"
+
+namespace qpid {
+namespace messaging {
+
+// Explicitly instantiate Handle superclass
+template class Handle<ReceiverImpl>;
+
+typedef PrivateImplRef<qpid::messaging::Receiver> PI;
+
+Receiver::Receiver(ReceiverImpl* impl) { PI::ctor(*this, impl); }
+Receiver::Receiver(const Receiver& s) : Handle<ReceiverImpl>() { PI::copy(*this, s); }
+Receiver::~Receiver() { PI::dtor(*this); }
+Receiver& Receiver::operator=(const Receiver& s) { return PI::assign(*this, s); }
+bool Receiver::get(Message& message, Duration timeout)
+{
+ MessageImplAccess::get(message).clear();
+ return impl->get(message, timeout);
+}
+Message Receiver::get(Duration timeout) { return impl->get(timeout); }
+bool Receiver::fetch(Message& message, Duration timeout)
+{
+ MessageImplAccess::get(message).clear();
+ return impl->fetch(message, timeout);
+}
+Message Receiver::fetch(Duration timeout) { return impl->fetch(timeout); }
+void Receiver::setCapacity(uint32_t c) { impl->setCapacity(c); }
+uint32_t Receiver::getCapacity() { return impl->getCapacity(); }
+uint32_t Receiver::getAvailable() { return impl->getAvailable(); }
+uint32_t Receiver::getUnsettled() { return impl->getUnsettled(); }
+void Receiver::close() { impl->close(); }
+const std::string& Receiver::getName() const { return impl->getName(); }
+Session Receiver::getSession() const { return impl->getSession(); }
+bool Receiver::isClosed() const { return impl->isClosed(); }
+Address Receiver::getAddress() const { return impl->getAddress(); }
+}} // namespace qpid::messaging
diff --git a/qpid/cpp/src/qpid/messaging/ReceiverImpl.h b/qpid/cpp/src/qpid/messaging/ReceiverImpl.h
new file mode 100644
index 0000000000..59ccc3214e
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/ReceiverImpl.h
@@ -0,0 +1,56 @@
+#ifndef QPID_MESSAGING_RECEIVERIMPL_H
+#define QPID_MESSAGING_RECEIVERIMPL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/RefCounted.h"
+#include "qpid/sys/IntegerTypes.h"
+
+namespace qpid {
+namespace messaging {
+
+class Address;
+class Duration;
+class Message;
+class MessageListener;
+class Session;
+
+class ReceiverImpl : public virtual qpid::RefCounted
+{
+ public:
+ virtual ~ReceiverImpl() {}
+ virtual bool get(Message& message, Duration timeout) = 0;
+ virtual Message get(Duration timeout) = 0;
+ virtual bool fetch(Message& message, Duration timeout) = 0;
+ virtual Message fetch(Duration timeout) = 0;
+ virtual void setCapacity(uint32_t) = 0;
+ virtual uint32_t getCapacity() = 0;
+ virtual uint32_t getAvailable() = 0;
+ virtual uint32_t getUnsettled() = 0;
+ virtual void close() = 0;
+ virtual const std::string& getName() const = 0;
+ virtual Session getSession() const = 0;
+ virtual bool isClosed() const = 0;
+ virtual Address getAddress() const = 0;
+};
+}} // namespace qpid::messaging
+
+#endif /*!QPID_MESSAGING_RECEIVERIMPL_H*/
diff --git a/qpid/cpp/src/qpid/messaging/Sender.cpp b/qpid/cpp/src/qpid/messaging/Sender.cpp
new file mode 100644
index 0000000000..a26f2544c8
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/Sender.cpp
@@ -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.
+ *
+ */
+#include "qpid/messaging/Sender.h"
+#include "qpid/messaging/Address.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/messaging/SenderImpl.h"
+#include "qpid/messaging/Session.h"
+#include "qpid/messaging/PrivateImplRef.h"
+
+namespace qpid {
+namespace messaging {
+
+// Explicitly instantiate Handle superclass
+template class Handle<SenderImpl>;
+
+typedef PrivateImplRef<qpid::messaging::Sender> PI;
+
+Sender::Sender(SenderImpl* impl) { PI::ctor(*this, impl); }
+Sender::Sender(const Sender& s) : qpid::messaging::Handle<SenderImpl>() { PI::copy(*this, s); }
+Sender::~Sender() { PI::dtor(*this); }
+Sender& Sender::operator=(const Sender& s) { return PI::assign(*this, s); }
+void Sender::send(const Message& message, bool sync) { impl->send(message, sync); }
+void Sender::close() { impl->close(); }
+void Sender::setCapacity(uint32_t c) { impl->setCapacity(c); }
+uint32_t Sender::getCapacity() { return impl->getCapacity(); }
+uint32_t Sender::getUnsettled() { return impl->getUnsettled(); }
+uint32_t Sender::getAvailable() { return getCapacity() - getUnsettled(); }
+const std::string& Sender::getName() const { return impl->getName(); }
+Session Sender::getSession() const { return impl->getSession(); }
+Address Sender::getAddress() const { return impl->getAddress(); }
+}} // namespace qpid::messaging
diff --git a/qpid/cpp/src/qpid/messaging/SenderImpl.h b/qpid/cpp/src/qpid/messaging/SenderImpl.h
new file mode 100644
index 0000000000..91fd9b1536
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/SenderImpl.h
@@ -0,0 +1,50 @@
+#ifndef QPID_MESSAGING_SENDERIMPL_H
+#define QPID_MESSAGING_SENDERIMPL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/RefCounted.h"
+#include "qpid/sys/IntegerTypes.h"
+
+namespace qpid {
+namespace messaging {
+
+class Address;
+class Message;
+class Session;
+
+class SenderImpl : public virtual qpid::RefCounted
+{
+ public:
+ virtual ~SenderImpl() {}
+ virtual void send(const Message& message, bool sync) = 0;
+ virtual void close() = 0;
+ virtual void setCapacity(uint32_t) = 0;
+ virtual uint32_t getCapacity() = 0;
+ virtual uint32_t getUnsettled() = 0;
+ virtual const std::string& getName() const = 0;
+ virtual Session getSession() const = 0;
+ virtual Address getAddress() const = 0;
+ private:
+};
+}} // namespace qpid::messaging
+
+#endif /*!QPID_MESSAGING_SENDERIMPL_H*/
diff --git a/qpid/cpp/src/qpid/messaging/Session.cpp b/qpid/cpp/src/qpid/messaging/Session.cpp
new file mode 100644
index 0000000000..fd0519705d
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/Session.cpp
@@ -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.
+ *
+ */
+#include "qpid/messaging/Session.h"
+#include "qpid/messaging/Address.h"
+#include "qpid/messaging/Connection.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/messaging/Sender.h"
+#include "qpid/messaging/Receiver.h"
+#include "qpid/messaging/SessionImpl.h"
+#include "qpid/messaging/PrivateImplRef.h"
+
+namespace qpid {
+namespace messaging {
+
+// Explicitly instantiate Handle superclass
+template class Handle<SessionImpl>;
+
+typedef PrivateImplRef<qpid::messaging::Session> PI;
+
+Session::Session(SessionImpl* impl) { PI::ctor(*this, impl); }
+Session::Session(const Session& s) : Handle<SessionImpl>() { PI::copy(*this, s); }
+Session::~Session() { PI::dtor(*this); }
+Session& Session::operator=(const Session& s) { return PI::assign(*this, s); }
+void Session::commit() { impl->commit(); }
+void Session::rollback() { impl->rollback(); }
+void Session::acknowledge(bool sync) { impl->acknowledge(sync); }
+void Session::acknowledge(Message& m, bool s) { impl->acknowledge(m, false); sync(s); }
+void Session::acknowledgeUpTo(Message& m, bool s) { impl->acknowledge(m, true); sync(s); }
+void Session::reject(Message& m) { impl->reject(m); }
+void Session::release(Message& m) { impl->release(m); }
+void Session::close() { impl->close(); }
+
+Sender Session::createSender(const Address& address)
+{
+ return impl->createSender(address);
+}
+Receiver Session::createReceiver(const Address& address)
+{
+ return impl->createReceiver(address);
+}
+
+Sender Session::createSender(const std::string& address)
+{
+ return impl->createSender(Address(address));
+}
+Receiver Session::createReceiver(const std::string& address)
+{
+ return impl->createReceiver(Address(address));
+}
+
+void Session::sync(bool block)
+{
+ impl->sync(block);
+}
+
+bool Session::nextReceiver(Receiver& receiver, Duration timeout)
+{
+ return impl->nextReceiver(receiver, timeout);
+}
+
+
+Receiver Session::nextReceiver(Duration timeout)
+{
+ return impl->nextReceiver(timeout);
+}
+
+uint32_t Session::getReceivable() { return impl->getReceivable(); }
+uint32_t Session::getUnsettledAcks() { return impl->getUnsettledAcks(); }
+
+Sender Session::getSender(const std::string& name) const
+{
+ return impl->getSender(name);
+}
+Receiver Session::getReceiver(const std::string& name) const
+{
+ return impl->getReceiver(name);
+}
+
+Connection Session::getConnection() const
+{
+ return impl->getConnection();
+}
+
+void Session::checkError() { impl->checkError(); }
+bool Session::hasError()
+{
+ try {
+ checkError();
+ return false;
+ } catch (const std::exception&) {
+ return true;
+ }
+}
+
+}} // namespace qpid::messaging
diff --git a/qpid/cpp/src/qpid/messaging/SessionImpl.h b/qpid/cpp/src/qpid/messaging/SessionImpl.h
new file mode 100644
index 0000000000..60ae615253
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/SessionImpl.h
@@ -0,0 +1,63 @@
+#ifndef QPID_MESSAGING_SESSIONIMPL_H
+#define QPID_MESSAGING_SESSIONIMPL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/RefCounted.h"
+#include <string>
+#include "qpid/messaging/Duration.h"
+
+namespace qpid {
+namespace messaging {
+
+class Address;
+class Connection;
+class Message;
+class Sender;
+class Receiver;
+
+class SessionImpl : public virtual qpid::RefCounted
+{
+ public:
+ virtual ~SessionImpl() {}
+ virtual void commit() = 0;
+ virtual void rollback() = 0;
+ virtual void acknowledge(bool sync) = 0;
+ virtual void acknowledge(Message&, bool cumulative) = 0;
+ virtual void reject(Message&) = 0;
+ virtual void release(Message&) = 0;
+ virtual void close() = 0;
+ virtual void sync(bool block) = 0;
+ virtual Sender createSender(const Address& address) = 0;
+ virtual Receiver createReceiver(const Address& address) = 0;
+ virtual bool nextReceiver(Receiver& receiver, Duration timeout) = 0;
+ virtual Receiver nextReceiver(Duration timeout) = 0;
+ virtual uint32_t getReceivable() = 0;
+ virtual uint32_t getUnsettledAcks() = 0;
+ virtual Sender getSender(const std::string& name) const = 0;
+ virtual Receiver getReceiver(const std::string& name) const = 0;
+ virtual Connection getConnection() const = 0;
+ virtual void checkError() = 0;
+ private:
+};
+}} // namespace qpid::messaging
+
+#endif /*!QPID_MESSAGING_SESSIONIMPL_H*/
diff --git a/qpid/cpp/src/qpid/messaging/amqp/AddressHelper.cpp b/qpid/cpp/src/qpid/messaging/amqp/AddressHelper.cpp
new file mode 100644
index 0000000000..8033cc5dee
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/AddressHelper.cpp
@@ -0,0 +1,781 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "PnData.h"
+#include "qpid/messaging/amqp/AddressHelper.h"
+#include "qpid/messaging/Address.h"
+#include "qpid/messaging/AddressImpl.h"
+#include "qpid/amqp/descriptors.h"
+#include "qpid/types/encodings.h"
+#include "qpid/log/Statement.h"
+#include <vector>
+#include <set>
+#include <sstream>
+#include <boost/assign.hpp>
+#include <boost/format.hpp>
+extern "C" {
+#include <proton/engine.h>
+}
+
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+
+using qpid::types::Variant;
+
+namespace {
+//policy types
+const std::string CREATE("create");
+const std::string ASSERT("assert");
+const std::string DELETE("delete");
+
+//policy values
+const std::string ALWAYS("always");
+const std::string NEVER("never");
+const std::string RECEIVER("receiver");
+const std::string SENDER("sender");
+
+const std::string NODE("node");
+const std::string LINK("link");
+const std::string CAPABILITIES("capabilities");
+const std::string PROPERTIES("properties");
+const std::string MODE("mode");
+const std::string BROWSE("browse");
+const std::string CONSUME("consume");
+const std::string TIMEOUT("timeout");
+
+const std::string TYPE("type");
+const std::string TOPIC("topic");
+const std::string QUEUE("queue");
+const std::string DURABLE("durable");
+const std::string NAME("name");
+const std::string RELIABILITY("reliability");
+const std::string SELECTOR("selector");
+const std::string FILTER("filter");
+const std::string DESCRIPTOR("descriptor");
+const std::string VALUE("value");
+const std::string SUBJECT_FILTER("subject-filter");
+const std::string SOURCE("sender-source");
+const std::string TARGET("receiver-target");
+
+//reliability options:
+const std::string UNRELIABLE("unreliable");
+const std::string AT_MOST_ONCE("at-most-once");
+const std::string AT_LEAST_ONCE("at-least-once");
+const std::string EXACTLY_ONCE("exactly-once");
+
+//distribution modes:
+const std::string MOVE("move");
+const std::string COPY("copy");
+
+const std::string SUPPORTED_DIST_MODES("supported-dist-modes");
+const std::string AUTO_DELETE("auto-delete");
+const std::string LIFETIME_POLICY("lifetime-policy");
+const std::string DELETE_ON_CLOSE("delete-on-close");
+const std::string DELETE_IF_UNUSED("delete-if-unused");
+const std::string DELETE_IF_EMPTY("delete-if-empty");
+const std::string DELETE_IF_UNUSED_AND_EMPTY("delete-if-unused-and-empty");
+const std::string CREATE_ON_DEMAND("create-on-demand");
+
+const std::string X_DECLARE("x-declare");
+const std::string X_BINDINGS("x-bindings");
+const std::string X_SUBSCRIBE("x-subscribe");
+const std::string ARGUMENTS("arguments");
+const std::string EXCHANGE_TYPE("exchange-type");
+
+const std::vector<std::string> RECEIVER_MODES = boost::assign::list_of<std::string>(ALWAYS) (RECEIVER);
+const std::vector<std::string> SENDER_MODES = boost::assign::list_of<std::string>(ALWAYS) (SENDER);
+
+class Verifier
+{
+ public:
+ Verifier();
+ void verify(const Address& address) const;
+ private:
+ Variant::Map defined;
+ void verify(const Variant::Map& allowed, const Variant::Map& actual) const;
+};
+const Verifier verifier;
+
+pn_bytes_t convert(const std::string& s)
+{
+ pn_bytes_t result;
+ result.start = const_cast<char*>(s.data());
+ result.size = s.size();
+ return result;
+}
+
+std::string convert(pn_bytes_t in)
+{
+ return std::string(in.start, in.size);
+}
+
+bool hasWildcards(const std::string& key)
+{
+ return key.find('*') != std::string::npos || key.find('#') != std::string::npos;
+}
+
+uint64_t getFilterDescriptor(const std::string& key)
+{
+ return hasWildcards(key) ? qpid::amqp::filters::LEGACY_TOPIC_FILTER_CODE : qpid::amqp::filters::LEGACY_DIRECT_FILTER_CODE;
+}
+
+bool test(const Variant::Map& options, const std::string& name)
+{
+ Variant::Map::const_iterator j = options.find(name);
+ if (j == options.end()) {
+ return false;
+ } else {
+ return j->second;
+ }
+}
+
+template <typename T> T get(const Variant::Map& options, const std::string& name, T defaultValue)
+{
+ Variant::Map::const_iterator j = options.find(name);
+ if (j == options.end()) {
+ return defaultValue;
+ } else {
+ return j->second;
+ }
+}
+
+bool bind(const Variant::Map& options, const std::string& name, std::string& variable)
+{
+ Variant::Map::const_iterator j = options.find(name);
+ if (j == options.end()) {
+ return false;
+ } else {
+ variable = j->second.asString();
+ return true;
+ }
+}
+
+bool bind(const Variant::Map& options, const std::string& name, Variant::Map& variable)
+{
+ Variant::Map::const_iterator j = options.find(name);
+ if (j == options.end()) {
+ return false;
+ } else {
+ variable = j->second.asMap();
+ return true;
+ }
+}
+
+bool bind(const Variant::Map& options, const std::string& name, Variant::List& variable)
+{
+ Variant::Map::const_iterator j = options.find(name);
+ if (j == options.end()) {
+ return false;
+ } else {
+ variable = j->second.asList();
+ return true;
+ }
+}
+
+bool bind(const Address& address, const std::string& name, std::string& variable)
+{
+ return bind(address.getOptions(), name, variable);
+}
+
+bool bind(const Address& address, const std::string& name, Variant::Map& variable)
+{
+ return bind(address.getOptions(), name, variable);
+}
+
+bool in(const std::string& value, const std::vector<std::string>& choices)
+{
+ for (std::vector<std::string>::const_iterator i = choices.begin(); i != choices.end(); ++i) {
+ if (value == *i) return true;
+ }
+ return false;
+}
+void add(Variant::Map& target, const Variant::Map& source)
+{
+ for (Variant::Map::const_iterator i = source.begin(); i != source.end(); ++i) {
+ target[i->first] = i->second;
+ }
+}
+void flatten(Variant::Map& base, const std::string& nested)
+{
+ Variant::Map::iterator i = base.find(nested);
+ if (i != base.end()) {
+ add(base, i->second.asMap());
+ base.erase(i);
+ }
+}
+bool replace(Variant::Map& map, const std::string& original, const std::string& desired)
+{
+ Variant::Map::iterator i = map.find(original);
+ if (i != map.end()) {
+ map[desired] = i->second;
+ map.erase(original);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+const uint32_t DEFAULT_DURABLE_TIMEOUT(2*60);//2 minutes
+const uint32_t DEFAULT_TIMEOUT(0);
+}
+
+AddressHelper::AddressHelper(const Address& address) :
+ isTemporary(AddressImpl::isTemporary(address)),
+ name(address.getName()),
+ type(address.getType()),
+ durableNode(false),
+ durableLink(false),
+ timeout(0),
+ browse(false)
+{
+ verifier.verify(address);
+ bind(address, CREATE, createPolicy);
+ bind(address, DELETE, deletePolicy);
+ bind(address, ASSERT, assertPolicy);
+
+ bind(address, NODE, node);
+ bind(address, LINK, link);
+ bind(node, PROPERTIES, properties);
+ bind(node, CAPABILITIES, capabilities);
+ bind(link, RELIABILITY, reliability);
+ durableNode = test(node, DURABLE);
+ durableLink = test(link, DURABLE);
+ timeout = get(link, TIMEOUT, durableLink && reliability != AT_LEAST_ONCE ? DEFAULT_DURABLE_TIMEOUT : DEFAULT_TIMEOUT);
+ std::string mode;
+ if (bind(address, MODE, mode)) {
+ if (mode == BROWSE) {
+ browse = true;
+ } else if (mode != CONSUME) {
+ throw qpid::messaging::AddressError("Invalid value for mode; must be 'browse' or 'consume'.");
+ }
+ }
+
+ if (!deletePolicy.empty()) {
+ throw qpid::messaging::AddressError("Delete policies not supported over AMQP 1.0.");
+ }
+ if (node.find(X_BINDINGS) != node.end()) {
+ throw qpid::messaging::AddressError("Node scoped x-bindings element not supported over AMQP 1.0.");
+ }
+ if (link.find(X_BINDINGS) != link.end()) {
+ throw qpid::messaging::AddressError("Link scoped x-bindings element not supported over AMQP 1.0.");
+ }
+ if (link.find(X_SUBSCRIBE) != link.end()) {
+ throw qpid::messaging::AddressError("Link scoped x-subscribe element not supported over AMQP 1.0.");
+ }
+ if (link.find(X_DECLARE) != link.end()) {
+ throw qpid::messaging::AddressError("Link scoped x-declare element not supported over AMQP 1.0.");
+ }
+ //massage x-declare into properties
+ Variant::Map::iterator i = node.find(X_DECLARE);
+ if (i != node.end()) {
+ Variant::Map x_declare = i->second.asMap();
+ replace(x_declare, TYPE, EXCHANGE_TYPE);
+ flatten(x_declare, ARGUMENTS);
+ add(properties, x_declare);
+ node.erase(i);
+ }
+ //for temp queues, if neither lifetime-policy nor autodelete are specified, assume delete-on-close
+ if (isTemporary && properties.find(LIFETIME_POLICY) == properties.end() && properties.find(AUTO_DELETE) == properties.end()) {
+ properties[LIFETIME_POLICY] = DELETE_ON_CLOSE;
+ }
+
+ if (properties.size() && !(isTemporary || !createPolicy.empty() || !assertPolicy.empty())) {
+ QPID_LOG(warning, "Properties will be ignored! " << address);
+ }
+
+ qpid::types::Variant::Map::const_iterator selector = link.find(SELECTOR);
+ if (selector != link.end()) {
+ addFilter(SELECTOR, qpid::amqp::filters::SELECTOR_FILTER_CODE, selector->second);
+ }
+ if (!address.getSubject().empty()) {
+ addFilter(SUBJECT_FILTER, getFilterDescriptor(address.getSubject()), address.getSubject());
+ }
+ qpid::types::Variant::Map::const_iterator filter = link.find(FILTER);
+ if (filter != link.end()) {
+ if (filter->second.getType() == qpid::types::VAR_MAP) {
+ addFilter(filter->second.asMap());
+ } else if (filter->second.getType() == qpid::types::VAR_LIST) {
+ addFilters(filter->second.asList());
+ } else {
+ throw qpid::messaging::AddressError("Filter must be a map or a list of maps, each containing name, descriptor and value.");
+ }
+ }
+}
+
+void AddressHelper::addFilters(const qpid::types::Variant::List& f)
+{
+ for (qpid::types::Variant::List::const_iterator i = f.begin(); i != f.end(); ++i) {
+ addFilter(i->asMap());
+ }
+}
+
+void AddressHelper::addFilter(const qpid::types::Variant::Map& f)
+{
+ qpid::types::Variant::Map::const_iterator name = f.find(NAME);
+ qpid::types::Variant::Map::const_iterator descriptor = f.find(DESCRIPTOR);
+ qpid::types::Variant::Map::const_iterator value = f.find(VALUE);
+ //all fields are required at present (may relax this at a later stage):
+ if (name == f.end()) {
+ throw qpid::messaging::AddressError("Filter entry must specify name");
+ }
+ if (descriptor == f.end()) {
+ throw qpid::messaging::AddressError("Filter entry must specify descriptor");
+ }
+ if (value == f.end()) {
+ throw qpid::messaging::AddressError("Filter entry must specify value");
+ }
+ try {
+ addFilter(name->second.asString(), descriptor->second.asUint64(), value->second);
+ } catch (const qpid::types::InvalidConversion&) {
+ addFilter(name->second.asString(), descriptor->second.asString(), value->second);
+ }
+
+}
+
+AddressHelper::Filter::Filter() : descriptorCode(0), confirmed(false) {}
+AddressHelper::Filter::Filter(const std::string& n, uint64_t d, const qpid::types::Variant& v) : name(n), descriptorCode(d), value(v), confirmed(false) {}
+AddressHelper::Filter::Filter(const std::string& n, const std::string& d, const qpid::types::Variant& v) : name(n), descriptorSymbol(d), descriptorCode(0), value(v), confirmed(false) {}
+
+void AddressHelper::addFilter(const std::string& name, uint64_t descriptor, const qpid::types::Variant& value)
+{
+ filters.push_back(Filter(name, descriptor, value));
+}
+void AddressHelper::addFilter(const std::string& name, const std::string& descriptor, const qpid::types::Variant& value)
+{
+ filters.push_back(Filter(name, descriptor, value));
+}
+
+namespace {
+bool checkLifetimePolicy(const std::string& requested, const std::string& actual)
+{
+ if (actual == qpid::amqp::lifetime_policy::DELETE_ON_CLOSE_SYMBOL && requested == DELETE_ON_CLOSE) return true;
+ else if (actual == qpid::amqp::lifetime_policy::DELETE_ON_NO_LINKS_SYMBOL && requested == DELETE_IF_UNUSED) return true;
+ else if (actual == qpid::amqp::lifetime_policy::DELETE_ON_NO_MESSAGES_SYMBOL && requested == DELETE_IF_EMPTY) return true;
+ else if (actual == qpid::amqp::lifetime_policy::DELETE_ON_NO_LINKS_OR_MESSAGES_SYMBOL && requested == DELETE_IF_UNUSED_AND_EMPTY) return true;
+ else return actual == requested;
+}
+bool checkLifetimePolicy(const std::string& requested, uint64_t actual)
+{
+ if (actual == qpid::amqp::lifetime_policy::DELETE_ON_CLOSE_CODE)
+ return checkLifetimePolicy(requested, qpid::amqp::lifetime_policy::DELETE_ON_CLOSE_SYMBOL);
+ else if (actual == qpid::amqp::lifetime_policy::DELETE_ON_NO_LINKS_CODE)
+ return checkLifetimePolicy(requested, qpid::amqp::lifetime_policy::DELETE_ON_NO_LINKS_SYMBOL);
+ else if (actual == qpid::amqp::lifetime_policy::DELETE_ON_NO_MESSAGES_CODE)
+ return checkLifetimePolicy(requested, qpid::amqp::lifetime_policy::DELETE_ON_NO_MESSAGES_SYMBOL);
+ else if (actual == qpid::amqp::lifetime_policy::DELETE_ON_NO_LINKS_OR_MESSAGES_CODE)
+ return checkLifetimePolicy(requested, qpid::amqp::lifetime_policy::DELETE_ON_NO_LINKS_OR_MESSAGES_SYMBOL);
+ else
+ return false;
+}
+bool checkLifetimePolicy(const std::string& requested, pn_data_t* actual)
+{
+ bool result(false);
+ if (pn_data_is_described(actual)) {
+ pn_data_enter(actual);
+ pn_data_next(actual);
+ if (pn_data_type(actual) == PN_ULONG) {
+ result = checkLifetimePolicy(requested, pn_data_get_ulong(actual));
+ } else if (pn_data_type(actual) == PN_SYMBOL) {
+ result = checkLifetimePolicy(requested, convert(pn_data_get_symbol(actual)));
+ }
+ pn_data_exit(actual);
+ }
+ return result;
+}
+}
+void AddressHelper::checkAssertion(pn_terminus_t* terminus, CheckMode mode)
+{
+ if (assertEnabled(mode)) {
+ QPID_LOG(debug, "checking capabilities: " << capabilities);
+ //ensure all desired capabilities have been offered
+ std::set<std::string> desired;
+ for (Variant::List::const_iterator i = capabilities.begin(); i != capabilities.end(); ++i) {
+ if (*i != CREATE_ON_DEMAND) desired.insert(i->asString());
+ }
+ pn_data_t* data = pn_terminus_capabilities(terminus);
+ if (pn_data_next(data)) {
+ pn_type_t type = pn_data_type(data);
+ if (type == PN_ARRAY) {
+ pn_data_enter(data);
+ while (pn_data_next(data)) {
+ desired.erase(convert(pn_data_get_symbol(data)));
+ }
+ pn_data_exit(data);
+ } else if (type == PN_SYMBOL) {
+ desired.erase(convert(pn_data_get_symbol(data)));
+ } else {
+ QPID_LOG(error, "Skipping capabilities field of type " << pn_type_name(type));
+ }
+ }
+
+ if (desired.size()) {
+ std::stringstream missing;
+ missing << "Desired capabilities not met: ";
+ bool first(true);
+ for (std::set<std::string>::const_iterator i = desired.begin(); i != desired.end(); ++i) {
+ if (first) first = false;
+ else missing << ", ";
+ missing << *i;
+ }
+ throw qpid::messaging::AssertionFailed(missing.str());
+ }
+
+ //ensure all desired filters are in use
+ data = pn_terminus_filter(terminus);
+ if (pn_data_next(data)) {
+ size_t count = pn_data_get_map(data);
+ pn_data_enter(data);
+ for (size_t i = 0; i < count && pn_data_next(data); ++i) {
+ //skip key:
+ if (!pn_data_next(data)) break;
+ //expecting described value:
+ if (pn_data_is_described(data)) {
+ pn_data_enter(data);
+ pn_data_next(data);
+ if (pn_data_type(data) == PN_ULONG) {
+ confirmFilter(pn_data_get_ulong(data));
+ } else if (pn_data_type(data) == PN_SYMBOL) {
+ confirmFilter(convert(pn_data_get_symbol(data)));
+ }
+ pn_data_exit(data);
+ }
+ }
+ pn_data_exit(data);
+ }
+ std::stringstream missing;
+ missing << "Desired filters not in use: ";
+ bool first(true);
+ for (std::vector<Filter>::iterator i = filters.begin(); i != filters.end(); ++i) {
+ if (!i->confirmed) {
+ if (first) first = false;
+ else missing << ", ";
+ missing << i->name << "(";
+ if (i->descriptorSymbol.empty()) missing << "0x" << std::hex << i->descriptorCode;
+ else missing << i->descriptorSymbol;
+ missing << ")";
+ }
+ }
+ if (!first) throw qpid::messaging::AssertionFailed(missing.str());
+
+ //assert on properties (Note: this violates the AMQP 1.0
+ //specification - as does the create option - by sending
+ //node-properties even if the dynamic option is not
+ //set. However this can be avoided by not specifying any node
+ //properties when asserting)
+ if (!type.empty() || durableNode || !properties.empty()) {
+ bool isAutoDeleted = false;
+ qpid::types::Variant::Map requested = properties;
+ if (!type.empty()) requested[SUPPORTED_DIST_MODES] = type == TOPIC ? COPY : MOVE;
+ if (durableNode) requested[DURABLE] = true;
+
+ data = pn_terminus_properties(terminus);
+ if (pn_data_next(data)) {
+ size_t count = pn_data_get_map(data);
+ pn_data_enter(data);
+ for (size_t i = 0; i < count && pn_data_next(data); ++i) {
+ std::string key = convert(pn_data_get_symbol(data));
+ pn_data_next(data);
+ qpid::types::Variant::Map::const_iterator j = requested.find(key);
+ qpid::types::Variant v;
+ if (key == LIFETIME_POLICY) {
+ isAutoDeleted = true;
+ if (j != requested.end() && checkLifetimePolicy(j->second.asString(), data)) {
+ requested.erase(j->first);
+ }
+ } else if (key == AUTO_DELETE) {
+ PnData(data).get(v);
+ isAutoDeleted = v.asBool();
+ } else if (j != requested.end() && (PnData(data).get(v) && v.asString() == j->second.asString())) {
+ requested.erase(j->first);
+ }
+ }
+ pn_data_exit(data);
+ qpid::types::Variant::Map::iterator i = requested.find(AUTO_DELETE);
+ if (i != requested.end() && i->second.asBool() == isAutoDeleted) {
+ requested.erase(i);
+ }
+ if (!requested.empty()) {
+ std::stringstream missing;
+ missing << "Requested node properties not met: " << requested;
+ throw qpid::messaging::AssertionFailed(missing.str());
+ }
+ }
+ }
+ }
+}
+
+void AddressHelper::confirmFilter(const std::string& descriptor)
+{
+ for (std::vector<Filter>::iterator i = filters.begin(); i != filters.end(); ++i) {
+ if (descriptor == i->descriptorSymbol) i->confirmed = true;
+ }
+}
+
+void AddressHelper::confirmFilter(uint64_t descriptor)
+{
+ for (std::vector<Filter>::iterator i = filters.begin(); i != filters.end(); ++i) {
+ if (descriptor == i->descriptorCode) i->confirmed = true;
+ }
+}
+
+bool AddressHelper::createEnabled(CheckMode mode) const
+{
+ return enabled(createPolicy, mode);
+}
+
+bool AddressHelper::assertEnabled(CheckMode mode) const
+{
+ return enabled(assertPolicy, mode);
+}
+
+bool AddressHelper::enabled(const std::string& policy, CheckMode mode) const
+{
+ bool result = false;
+ switch (mode) {
+ case FOR_RECEIVER:
+ result = in(policy, RECEIVER_MODES);
+ break;
+ case FOR_SENDER:
+ result = in(policy, SENDER_MODES);
+ break;
+ }
+ return result;
+}
+
+bool AddressHelper::isUnreliable() const
+{
+ return reliability == AT_MOST_ONCE || reliability == UNRELIABLE ||
+ (reliability.empty() && browse); // A browser defaults to unreliable.
+}
+
+const qpid::types::Variant::Map& AddressHelper::getNodeProperties() const
+{
+ return node;
+}
+const qpid::types::Variant::Map& AddressHelper::getLinkProperties() const
+{
+ return link;
+}
+
+bool AddressHelper::getLinkSource(std::string& out) const
+{
+ return getLinkOption(SOURCE, out);
+}
+
+bool AddressHelper::getLinkTarget(std::string& out) const
+{
+ return getLinkOption(TARGET, out);
+}
+
+bool AddressHelper::getLinkOption(const std::string& name, std::string& out) const
+{
+ qpid::types::Variant::Map::const_iterator i = link.find(name);
+ if (i != link.end()) {
+ out = i->second.asString();
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void AddressHelper::configure(pn_link_t* link, pn_terminus_t* terminus, CheckMode mode)
+{
+ bool createOnDemand(false);
+ if (isTemporary) {
+ //application expects a name to be generated
+ pn_terminus_set_dynamic(terminus, true);
+ setNodeProperties(terminus);
+ } else {
+ pn_terminus_set_address(terminus, name.c_str());
+ if (createEnabled(mode)) {
+ //application expects name of node to be as specified
+ setNodeProperties(terminus);
+ createOnDemand = true;
+ } else if (assertEnabled(mode)) {
+ setNodeProperties(terminus);
+ }
+ }
+
+ setCapabilities(terminus, createOnDemand);
+ if (durableLink) {
+ pn_terminus_set_durability(terminus, PN_DELIVERIES);
+ }
+ if (mode == FOR_RECEIVER) {
+ if (timeout) pn_terminus_set_timeout(terminus, timeout);
+ if (browse) {
+ pn_terminus_set_distribution_mode(terminus, PN_DIST_MODE_COPY);
+ }
+ //set filter(s):
+ if (!filters.empty()) {
+ pn_data_t* filter = pn_terminus_filter(terminus);
+ pn_data_put_map(filter);
+ pn_data_enter(filter);
+ for (std::vector<Filter>::const_iterator i = filters.begin(); i != filters.end(); ++i) {
+ pn_data_put_symbol(filter, convert(i->name));
+ pn_data_put_described(filter);
+ pn_data_enter(filter);
+ if (i->descriptorSymbol.size()) {
+ pn_data_put_symbol(filter, convert(i->descriptorSymbol));
+ } else {
+ pn_data_put_ulong(filter, i->descriptorCode);
+ }
+ PnData(filter).put(i->value);
+ pn_data_exit(filter);
+ }
+ pn_data_exit(filter);
+ }
+ }
+ if (isUnreliable()) {
+ pn_link_set_snd_settle_mode(link, PN_SND_SETTLED);
+ } else if (!reliability.empty()) {
+ if (reliability == EXACTLY_ONCE ) {
+ QPID_LOG(warning, "Unsupported reliability mode: " << reliability);
+ } else if (reliability != AT_LEAST_ONCE ) {
+ QPID_LOG(warning, "Unrecognised reliability mode: " << reliability);
+ }
+ pn_link_set_snd_settle_mode(link, PN_SND_UNSETTLED);
+ }
+}
+
+void AddressHelper::setCapabilities(pn_terminus_t* terminus, bool create)
+{
+ if (create) capabilities.push_back(CREATE_ON_DEMAND);
+ if (!type.empty()) capabilities.push_back(type);
+ if (durableNode) capabilities.push_back(DURABLE);
+
+ pn_data_t* data = pn_terminus_capabilities(terminus);
+ if (capabilities.size() == 1) {
+ pn_data_put_symbol(data, convert(capabilities.front().asString()));
+ } else if (capabilities.size() > 1) {
+ pn_data_put_array(data, false, PN_SYMBOL);
+ pn_data_enter(data);
+ for (qpid::types::Variant::List::const_iterator i = capabilities.begin(); i != capabilities.end(); ++i) {
+ pn_data_put_symbol(data, convert(i->asString()));
+ }
+ pn_data_exit(data);
+ }
+}
+std::string AddressHelper::getLinkName(const Address& address)
+{
+ AddressHelper helper(address);
+ const qpid::types::Variant::Map& linkProps = helper.getLinkProperties();
+ qpid::types::Variant::Map::const_iterator i = linkProps.find(NAME);
+ if (i != linkProps.end()) {
+ return i->second.asString();
+ } else {
+ std::stringstream name;
+ name << address.getName() << "_" << qpid::types::Uuid(true);
+ return name.str();
+ }
+}
+namespace {
+std::string toLifetimePolicy(const std::string& value)
+{
+ if (value == DELETE_ON_CLOSE) return qpid::amqp::lifetime_policy::DELETE_ON_CLOSE_SYMBOL;
+ else if (value == DELETE_IF_UNUSED) return qpid::amqp::lifetime_policy::DELETE_ON_NO_LINKS_SYMBOL;
+ else if (value == DELETE_IF_EMPTY) return qpid::amqp::lifetime_policy::DELETE_ON_NO_MESSAGES_SYMBOL;
+ else if (value == DELETE_IF_UNUSED_AND_EMPTY) return qpid::amqp::lifetime_policy::DELETE_ON_NO_LINKS_OR_MESSAGES_SYMBOL;
+ else return value;//asume value is itself the symbolic descriptor
+}
+void putLifetimePolicy(pn_data_t* data, const std::string& value)
+{
+ pn_data_put_described(data);
+ pn_data_enter(data);
+ pn_data_put_symbol(data, convert(value));
+ pn_data_put_list(data);
+ pn_data_exit(data);
+}
+}
+void AddressHelper::setNodeProperties(pn_terminus_t* terminus)
+{
+ if (properties.size() || type.size() || durableNode) {
+ pn_data_t* data = pn_terminus_properties(terminus);
+ pn_data_put_map(data);
+ pn_data_enter(data);
+ if (type.size()) {
+ pn_data_put_symbol(data, convert(SUPPORTED_DIST_MODES));
+ pn_data_put_string(data, convert(type == TOPIC ? COPY : MOVE));
+ }
+ if (durableNode) {
+ pn_data_put_symbol(data, convert(DURABLE));
+ pn_data_put_bool(data, true);
+ }
+ for (qpid::types::Variant::Map::const_iterator i = properties.begin(); i != properties.end(); ++i) {
+ if (i->first == LIFETIME_POLICY) {
+ pn_data_put_symbol(data, convert(i->first));
+ putLifetimePolicy(data, toLifetimePolicy(i->second.asString()));
+ } else {
+ pn_data_put_symbol(data, convert(i->first));
+ PnData(data).put(i->second);
+ }
+ }
+ pn_data_exit(data);
+ }
+}
+
+Verifier::Verifier()
+{
+ defined[CREATE] = true;
+ defined[ASSERT] = true;
+ defined[DELETE] = true;
+ defined[MODE] = true;
+ Variant::Map node;
+ node[TYPE] = true;
+ node[DURABLE] = true;
+ node[PROPERTIES] = true;
+ node[CAPABILITIES] = true;
+ node[X_DECLARE] = true;
+ node[X_BINDINGS] = true;
+ defined[NODE] = node;
+ Variant::Map link;
+ link[NAME] = true;
+ link[DURABLE] = true;
+ link[RELIABILITY] = true;
+ link[TIMEOUT] = true;
+ link[SOURCE] = true;
+ link[TARGET] = true;
+ link[X_SUBSCRIBE] = true;
+ link[X_DECLARE] = true;
+ link[X_BINDINGS] = true;
+ link[SELECTOR] = true;
+ link[FILTER] = true;
+ defined[LINK] = link;
+}
+void Verifier::verify(const Address& address) const
+{
+ verify(defined, address.getOptions());
+}
+
+void Verifier::verify(const Variant::Map& allowed, const Variant::Map& actual) const
+{
+ for (Variant::Map::const_iterator i = actual.begin(); i != actual.end(); ++i) {
+ Variant::Map::const_iterator option = allowed.find(i->first);
+ if (option == allowed.end()) {
+ throw AddressError((boost::format("Unrecognised option: %1%") % i->first).str());
+ } else if (option->second.getType() == qpid::types::VAR_MAP) {
+ verify(option->second.asMap(), i->second.asMap());
+ }
+ }
+}
+
+}}} // namespace qpid::messaging::amqp
diff --git a/qpid/cpp/src/qpid/messaging/amqp/AddressHelper.h b/qpid/cpp/src/qpid/messaging/amqp/AddressHelper.h
new file mode 100644
index 0000000000..3ee58cad8d
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/AddressHelper.h
@@ -0,0 +1,95 @@
+#ifndef QPID_MESSAGING_AMQP_ADDRESSHELPER_H
+#define QPID_MESSAGING_AMQP_ADDRESSHELPER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/types/Variant.h"
+#include <vector>
+
+struct pn_link_t;
+struct pn_terminus_t;
+
+namespace qpid {
+namespace messaging {
+class Address;
+namespace amqp {
+class AddressHelper
+{
+ public:
+ enum CheckMode {FOR_RECEIVER, FOR_SENDER};
+
+ AddressHelper(const Address& address);
+ void configure(pn_link_t* link, pn_terminus_t* terminus, CheckMode mode);
+ void checkAssertion(pn_terminus_t* terminus, CheckMode mode);
+
+ bool isUnreliable() const;
+ const qpid::types::Variant::Map& getNodeProperties() const;
+ bool getLinkSource(std::string& out) const;
+ bool getLinkTarget(std::string& out) const;
+ const qpid::types::Variant::Map& getLinkProperties() const;
+ static std::string getLinkName(const Address& address);
+ private:
+ struct Filter
+ {
+ std::string name;
+ std::string descriptorSymbol;
+ uint64_t descriptorCode;
+ qpid::types::Variant value;
+ bool confirmed;
+
+ Filter();
+ Filter(const std::string& name, uint64_t descriptor, const qpid::types::Variant& value);
+ Filter(const std::string& name, const std::string& descriptor, const qpid::types::Variant& value);
+ };
+
+ bool isTemporary;
+ std::string createPolicy;
+ std::string assertPolicy;
+ std::string deletePolicy;
+ qpid::types::Variant::Map node;
+ qpid::types::Variant::Map link;
+ qpid::types::Variant::Map properties;
+ qpid::types::Variant::List capabilities;
+ std::string name;
+ std::string type;
+ std::string reliability;
+ bool durableNode;
+ bool durableLink;
+ uint32_t timeout;
+ bool browse;
+ std::vector<Filter> filters;
+
+ bool enabled(const std::string& policy, CheckMode mode) const;
+ bool createEnabled(CheckMode mode) const;
+ bool assertEnabled(CheckMode mode) const;
+ void setCapabilities(pn_terminus_t* terminus, bool create);
+ void setNodeProperties(pn_terminus_t* terminus);
+ void addFilter(const qpid::types::Variant::Map&);
+ void addFilter(const std::string& name, uint64_t descriptor, const qpid::types::Variant& value);
+ void addFilter(const std::string& name, const std::string& descriptor, const qpid::types::Variant& value);
+ void addFilters(const qpid::types::Variant::List&);
+ void confirmFilter(const std::string& descriptor);
+ void confirmFilter(uint64_t descriptor);
+ bool getLinkOption(const std::string& name, std::string& out) const;
+};
+}}} // namespace qpid::messaging::amqp
+
+#endif /*!QPID_MESSAGING_AMQP_ADDRESSHELPER_H*/
diff --git a/qpid/cpp/src/qpid/messaging/amqp/ConnectionContext.cpp b/qpid/cpp/src/qpid/messaging/amqp/ConnectionContext.cpp
new file mode 100644
index 0000000000..1b8c848941
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/ConnectionContext.cpp
@@ -0,0 +1,1317 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "ConnectionContext.h"
+#include "DriverImpl.h"
+#include "PnData.h"
+#include "ReceiverContext.h"
+#include "Sasl.h"
+#include "SenderContext.h"
+#include "SessionContext.h"
+#include "Transaction.h"
+#include "Transport.h"
+#include "util.h"
+#include "qpid/amqp/descriptors.h"
+#include "qpid/amqp/Encoder.h"
+#include "qpid/amqp/Descriptor.h"
+#include "qpid/messaging/exceptions.h"
+#include "qpid/messaging/AddressImpl.h"
+#include "qpid/messaging/Duration.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/messaging/MessageImpl.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/framing/ProtocolInitiation.h"
+#include "qpid/framing/Uuid.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/SecurityLayer.h"
+#include "qpid/sys/SystemInfo.h"
+#include "qpid/sys/Time.h"
+#include "qpid/sys/Timer.h"
+#include "qpid/sys/urlAdd.h"
+#include "config.h"
+#include <boost/lexical_cast.hpp>
+#include <boost/bind.hpp>
+#include <vector>
+extern "C" {
+#include <proton/engine.h>
+}
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+using types::Variant;
+
+namespace {
+
+void do_trace(pn_transport_t* transport, const char* message)
+{
+ ConnectionContext* c = reinterpret_cast<ConnectionContext*>(pn_transport_get_context(transport));
+ if (c) c->trace(message);
+}
+
+void set_tracer(pn_transport_t* transport, void* context)
+{
+ pn_transport_set_context(transport, context);
+ pn_transport_set_tracer(transport, &do_trace);
+}
+
+#ifdef USE_PROTON_TRANSPORT_CONDITION
+std::string get_error(pn_connection_t* connection, pn_transport_t* transport)
+{
+ std::stringstream text;
+ pn_error_t* cerror = pn_connection_error(connection);
+ if (cerror) text << "connection error " << pn_error_text(cerror) << " [" << cerror << "]";
+ pn_condition_t* tcondition = pn_transport_condition(transport);
+ if (pn_condition_is_set(tcondition)) text << get_error_string(tcondition, "transport error", ": ");
+ return text.str();
+}
+#else
+std::string get_error(pn_connection_t* connection, pn_transport_t* transport)
+{
+ std::stringstream text;
+ pn_error_t* cerror = pn_connection_error(connection);
+ if (cerror) text << "connection error " << pn_error_text(cerror) << " [" << cerror << "]";
+ pn_error_t* terror = pn_transport_error(transport);
+ if (terror) text << "transport error " << pn_error_text(terror) << " [" << terror << "]";
+ return text.str();
+}
+#endif
+
+class ConnectionTickerTask : public qpid::sys::TimerTask
+{
+ qpid::sys::Timer& timer;
+ ConnectionContext& connection;
+ public:
+ ConnectionTickerTask(const qpid::sys::Duration& interval, qpid::sys::Timer& t, ConnectionContext& c) :
+ TimerTask(interval, "ConnectionTicker"),
+ timer(t),
+ connection(c)
+ {}
+
+ void fire() {
+ QPID_LOG(debug, "ConnectionTickerTask fired");
+ // Setup next firing
+ setupNextFire();
+ timer.add(this);
+
+ // Send Ticker
+ connection.activateOutput();
+ }
+};
+}
+
+void ConnectionContext::trace(const char* message) const
+{
+ QPID_LOG_CAT(trace, protocol, "[" << identifier << "]: " << message);
+}
+
+ConnectionContext::ConnectionContext(const std::string& url, const qpid::types::Variant::Map& o)
+ : qpid::messaging::ConnectionOptions(o),
+ fullUrl(url, protocol.empty() ? qpid::Address::TCP : protocol),
+ engine(pn_transport()),
+ connection(pn_connection()),
+ //note: disabled read/write of header as now handled by engine
+ writeHeader(false),
+ readHeader(false),
+ haveOutput(false),
+ state(DISCONNECTED),
+ codecAdapter(*this),
+ notifyOnWrite(false)
+{
+ // Concatenate all known URLs into a single URL, get rid of duplicate addresses.
+ sys::urlAddStrings(fullUrl, urls.begin(), urls.end(), protocol.empty() ?
+ qpid::Address::TCP : protocol);
+ if (identifier.empty()) {
+ identifier = qpid::types::Uuid(true).str();
+ }
+ configureConnection();
+}
+
+ConnectionContext::~ConnectionContext()
+{
+ if (ticker) ticker->cancel();
+ close();
+ sessions.clear();
+ pn_connection_free(connection);
+ pn_transport_free(engine);
+}
+
+bool ConnectionContext::isOpen() const
+{
+ sys::Monitor::ScopedLock l(lock);
+ return state == CONNECTED && pn_connection_state(connection) & (PN_LOCAL_ACTIVE | PN_REMOTE_ACTIVE);
+}
+
+void ConnectionContext::sync(boost::shared_ptr<SessionContext> ssn)
+{
+ sys::Monitor::ScopedLock l(lock);
+ syncLH(ssn, l);
+}
+
+void ConnectionContext::syncLH(boost::shared_ptr<SessionContext> ssn, sys::Monitor::ScopedLock&) {
+ while (!ssn->settled()) {
+ QPID_LOG(debug, "Waiting for sends to settle on sync()");
+ wait(ssn);//wait until message has been confirmed
+ wakeupDriver();
+ }
+ checkClosed(ssn);
+}
+
+void ConnectionContext::endSession(boost::shared_ptr<SessionContext> ssn)
+{
+ sys::Monitor::ScopedLock l(lock);
+ if (pn_session_state(ssn->session) & PN_REMOTE_ACTIVE) {
+ //explicitly release messages that have yet to be fetched
+ for (SessionContext::ReceiverMap::iterator i = ssn->receivers.begin(); i != ssn->receivers.end(); ++i) {
+ drain_and_release_messages(ssn, i->second);
+ }
+ syncLH(ssn, l);
+ }
+
+ if (pn_session_state(ssn->session) & PN_REMOTE_ACTIVE) {
+ pn_session_close(ssn->session);
+ }
+ sessions.erase(ssn->getName());
+
+ wakeupDriver();
+}
+
+void ConnectionContext::close()
+{
+ sys::Monitor::ScopedLock l(lock);
+ if (state != CONNECTED) return;
+ if (!(pn_connection_state(connection) & PN_LOCAL_CLOSED)) {
+ for (SessionMap::iterator i = sessions.begin(); i != sessions.end(); ++i) {
+ syncLH(i->second, l);
+ if (!(pn_session_state(i->second->session) & PN_LOCAL_CLOSED)) {
+ pn_session_close(i->second->session);
+ }
+ }
+ pn_connection_close(connection);
+ wakeupDriver();
+ //wait for close to be confirmed by peer?
+ while (!(pn_connection_state(connection) & PN_REMOTE_CLOSED)) {
+ if (state == DISCONNECTED) {
+ QPID_LOG(warning, "Disconnected before close received from peer.");
+ break;
+ }
+ lock.wait();
+ }
+ sessions.clear();
+ }
+ if (state != DISCONNECTED) {
+ transport->close();
+ while (state != DISCONNECTED) {
+ lock.wait();
+ }
+ }
+ if (ticker) {
+ ticker->cancel();
+ ticker = boost::intrusive_ptr<qpid::sys::TimerTask>();
+ }
+}
+
+bool ConnectionContext::fetch(boost::shared_ptr<SessionContext> ssn, boost::shared_ptr<ReceiverContext> lnk, qpid::messaging::Message& message, qpid::messaging::Duration timeout)
+{
+ /**
+ * For fetch() on a receiver with zero capacity, need to reissue the
+ * credit on reconnect, so track the fetches in progress.
+ */
+ qpid::sys::AtomicCount::ScopedIncrement track(lnk->fetching);
+ {
+ sys::Monitor::ScopedLock l(lock);
+ checkClosed(ssn, lnk);
+ if (!lnk->capacity) {
+ pn_link_flow(lnk->receiver, 1);
+ wakeupDriver();
+ }
+ }
+ if (get(ssn, lnk, message, timeout)) {
+ return true;
+ } else {
+ {
+ sys::Monitor::ScopedLock l(lock);
+ pn_link_drain(lnk->receiver, 0);
+ wakeupDriver();
+ while (pn_link_draining(lnk->receiver) && !pn_link_queued(lnk->receiver)) {
+ QPID_LOG(debug, "Waiting for message or for credit to be drained: credit=" << pn_link_credit(lnk->receiver) << ", queued=" << pn_link_queued(lnk->receiver));
+ wait(ssn, lnk);
+ }
+ if (lnk->capacity && pn_link_queued(lnk->receiver) == 0) {
+ pn_link_flow(lnk->receiver, lnk->capacity);
+ }
+ }
+ if (get(ssn, lnk, message, qpid::messaging::Duration::IMMEDIATE)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+}
+
+qpid::sys::AbsTime convert(qpid::messaging::Duration timeout)
+{
+ qpid::sys::AbsTime until;
+ uint64_t ms = timeout.getMilliseconds();
+ if (ms < (uint64_t) (qpid::sys::TIME_INFINITE/qpid::sys::TIME_MSEC)) {
+ return qpid::sys::AbsTime(qpid::sys::now(), ms * qpid::sys::TIME_MSEC);
+ } else {
+ return qpid::sys::FAR_FUTURE;
+ }
+}
+
+bool ConnectionContext::get(boost::shared_ptr<SessionContext> ssn, boost::shared_ptr<ReceiverContext> lnk, qpid::messaging::Message& message, qpid::messaging::Duration timeout)
+{
+ qpid::sys::AbsTime until(convert(timeout));
+ while (true) {
+ sys::Monitor::ScopedLock l(lock);
+ checkClosed(ssn, lnk);
+ pn_delivery_t* current = pn_link_current((pn_link_t*) lnk->receiver);
+ QPID_LOG(debug, "In ConnectionContext::get(), current=" << current);
+ if (current) {
+ qpid::messaging::MessageImpl& impl = MessageImplAccess::get(message);
+ boost::shared_ptr<EncodedMessage> encoded(new EncodedMessage(pn_delivery_pending(current)));
+ encoded->setNestAnnotationsOption(nestAnnotations);
+ ssize_t read = pn_link_recv(lnk->receiver, encoded->getData(), encoded->getSize());
+ if (read < 0) throw qpid::messaging::MessagingException("Failed to read message");
+ encoded->trim((size_t) read);
+ QPID_LOG(debug, "Received message of " << encoded->getSize() << " bytes: ");
+ encoded->init(impl);
+ impl.setEncoded(encoded);
+ impl.setInternalId(ssn->record(current));
+ if (lnk->capacity) {
+ pn_link_flow(lnk->receiver, 1);
+ if (lnk->wakeupToIssueCredit()) {
+ wakeupDriver();
+ } else {
+ haveOutput = true;
+ }
+ }
+ // Automatically ack messages if we are in a transaction.
+ if (ssn->transaction)
+ acknowledgeLH(ssn, &message, false, l);
+ return true;
+ } else if (until > qpid::sys::now()) {
+ waitUntil(ssn, lnk, until);
+ } else {
+ return false;
+ }
+ }
+ return false;
+}
+
+boost::shared_ptr<ReceiverContext> ConnectionContext::nextReceiver(boost::shared_ptr<SessionContext> ssn, qpid::messaging::Duration timeout)
+{
+ qpid::sys::AbsTime until(convert(timeout));
+ while (true) {
+ sys::Monitor::ScopedLock l(lock);
+ checkClosed(ssn);
+ boost::shared_ptr<ReceiverContext> r = ssn->nextReceiver();
+ if (r) {
+ return r;
+ } else if (until > qpid::sys::now()) {
+ waitUntil(ssn, until);
+ } else {
+ return boost::shared_ptr<ReceiverContext>();
+ }
+ }
+}
+
+void ConnectionContext::acknowledge(boost::shared_ptr<SessionContext> ssn, qpid::messaging::Message* message, bool cumulative) {
+ sys::Monitor::ScopedLock l(lock);
+ acknowledgeLH(ssn, message, cumulative, l);
+}
+
+void ConnectionContext::acknowledgeLH(boost::shared_ptr<SessionContext> ssn, qpid::messaging::Message* message, bool cumulative, sys::Monitor::ScopedLock&)
+{
+ checkClosed(ssn);
+ if (message) {
+ ssn->acknowledge(MessageImplAccess::get(*message).getInternalId(), cumulative);
+ } else {
+ ssn->acknowledge();
+ }
+ wakeupDriver();
+}
+
+void ConnectionContext::nack(boost::shared_ptr<SessionContext> ssn, qpid::messaging::Message& message, bool reject)
+{
+ sys::Monitor::ScopedLock l(lock);
+ checkClosed(ssn);
+ ssn->nack(MessageImplAccess::get(message).getInternalId(), reject);
+ wakeupDriver();
+}
+
+void ConnectionContext::detach(boost::shared_ptr<SessionContext> ssn, boost::shared_ptr<SenderContext> lnk)
+{
+ sys::Monitor::ScopedLock l(lock);
+ if (pn_link_state(lnk->sender) & PN_LOCAL_ACTIVE) {
+ lnk->close();
+ }
+ wakeupDriver();
+ while (pn_link_state(lnk->sender) & PN_REMOTE_ACTIVE) {
+ wait(ssn);
+ }
+ ssn->removeSender(lnk->getName());
+}
+
+void ConnectionContext::drain_and_release_messages(boost::shared_ptr<SessionContext> ssn, boost::shared_ptr<ReceiverContext> lnk)
+{
+ pn_link_drain(lnk->receiver, 0);
+ wakeupDriver();
+ //Not all implementations handle drain correctly, so limit the
+ //time spent waiting for it
+ qpid::sys::AbsTime until(qpid::sys::now(), qpid::sys::TIME_SEC*2);
+ while (pn_link_credit(lnk->receiver) > pn_link_queued(lnk->receiver) && until > qpid::sys::now()) {
+ QPID_LOG(debug, "Waiting for credit to be drained: credit=" << pn_link_credit(lnk->receiver) << ", queued=" << pn_link_queued(lnk->receiver));
+ waitUntil(ssn, lnk, until);
+ }
+ //release as yet unfetched messages:
+ for (pn_delivery_t* d = pn_link_current(lnk->receiver); d; d = pn_link_current(lnk->receiver)) {
+ pn_link_advance(lnk->receiver);
+ pn_delivery_update(d, PN_RELEASED);
+ pn_delivery_settle(d);
+ }
+}
+
+void ConnectionContext::detach(boost::shared_ptr<SessionContext> ssn, boost::shared_ptr<ReceiverContext> lnk)
+{
+ sys::Monitor::ScopedLock l(lock);
+ drain_and_release_messages(ssn, lnk);
+ if (pn_link_state(lnk->receiver) & PN_LOCAL_ACTIVE) {
+ lnk->close();
+ }
+ wakeupDriver();
+ while (pn_link_state(lnk->receiver) & PN_REMOTE_ACTIVE) {
+ wait(ssn);
+ }
+ ssn->removeReceiver(lnk->getName());
+}
+
+void ConnectionContext::attach(boost::shared_ptr<SessionContext> ssn, boost::shared_ptr<SenderContext> lnk)
+{
+ lnk->configure();
+ attach(ssn, lnk->sender);
+ checkClosed(ssn, lnk);
+ lnk->verify();
+ QPID_LOG(debug, "Attach succeeded to " << lnk->getTarget());
+}
+
+void ConnectionContext::attach(boost::shared_ptr<SessionContext> ssn, boost::shared_ptr<ReceiverContext> lnk)
+{
+ lnk->configure();
+ attach(ssn, lnk->receiver, lnk->capacity);
+ checkClosed(ssn, lnk);
+ lnk->verify();
+ QPID_LOG(debug, "Attach succeeded from " << lnk->getSource());
+}
+
+void ConnectionContext::attach(boost::shared_ptr<SessionContext> ssn, pn_link_t* link, int credit)
+{
+ pn_link_open(link);
+ QPID_LOG(debug, "Link attach sent for " << link << ", state=" << pn_link_state(link));
+ if (credit) pn_link_flow(link, credit);
+ wakeupDriver();
+ while (pn_link_state(link) & PN_REMOTE_UNINIT) {
+ QPID_LOG(debug, "Waiting for confirmation of link attach for " << link << ", state=" << pn_link_state(link) << "...");
+ wait(ssn);
+ }
+}
+
+boost::shared_ptr<SenderContext> ConnectionContext::createSender(boost::shared_ptr<SessionContext> session, const qpid::messaging::Address& address)
+{
+ sys::Monitor::ScopedLock l(lock);
+ boost::shared_ptr<SenderContext> sender = session->createSender(address, setToOnSend);
+ try {
+ attach(session, sender);
+ return sender;
+ } catch (...) {
+ session->removeSender(sender->getName());
+ throw;
+ }
+
+}
+boost::shared_ptr<ReceiverContext> ConnectionContext::createReceiver(boost::shared_ptr<SessionContext> session, const qpid::messaging::Address& address)
+{
+ sys::Monitor::ScopedLock l(lock);
+ boost::shared_ptr<ReceiverContext> receiver = session->createReceiver(address);
+ try {
+ attach(session, receiver);
+ return receiver;
+ } catch (...) {
+ session->removeReceiver(receiver->getName());
+ throw;
+ }
+}
+boost::shared_ptr<SenderContext> ConnectionContext::getSender(boost::shared_ptr<SessionContext> session, const std::string& name) const
+{
+ sys::Monitor::ScopedLock l(lock);
+ return session->getSender(name);
+}
+
+boost::shared_ptr<ReceiverContext> ConnectionContext::getReceiver(boost::shared_ptr<SessionContext> session, const std::string& name) const
+{
+ sys::Monitor::ScopedLock l(lock);
+ return session->getReceiver(name);
+}
+
+void ConnectionContext::send(
+ boost::shared_ptr<SessionContext> ssn,
+ boost::shared_ptr<SenderContext> snd,
+ const qpid::messaging::Message& message,
+ bool sync,
+ SenderContext::Delivery** delivery)
+{
+ sys::Monitor::ScopedLock l(lock);
+ sendLH(ssn, snd, message, sync, delivery, l);
+}
+
+void ConnectionContext::sendLH(
+ boost::shared_ptr<SessionContext> ssn,
+ boost::shared_ptr<SenderContext> snd,
+ const qpid::messaging::Message& message,
+ bool sync,
+ SenderContext::Delivery** delivery,
+ sys::Monitor::ScopedLock&)
+{
+ checkClosed(ssn);
+ while (pn_transport_pending(engine) > 65536) {
+ QPID_LOG(debug, "Have " << pn_transport_pending(engine) << " bytes of output pending; waiting for this to be written...");
+ notifyOnWrite = true;
+ wakeupDriver();
+ wait(ssn, snd);
+ notifyOnWrite = false;
+ }
+ while (!snd->send(message, delivery)) {
+ QPID_LOG(debug, "Waiting for capacity...");
+ wait(ssn, snd);//wait for capacity
+ }
+ wakeupDriver();
+ if (sync && *delivery) {
+ while (!(*delivery)->delivered()) {
+ QPID_LOG(debug, "Waiting for confirmation...");
+ wait(ssn, snd);//wait until message has been confirmed
+ }
+ if ((*delivery)->rejected()) {
+ throw MessageRejected("Message was rejected by peer");
+ }
+
+ }
+}
+
+void ConnectionContext::setCapacity(boost::shared_ptr<SenderContext> sender, uint32_t capacity)
+{
+ sys::Monitor::ScopedLock l(lock);
+ sender->setCapacity(capacity);
+}
+uint32_t ConnectionContext::getCapacity(boost::shared_ptr<SenderContext> sender)
+{
+ sys::Monitor::ScopedLock l(lock);
+ return sender->getCapacity();
+}
+uint32_t ConnectionContext::getUnsettled(boost::shared_ptr<SenderContext> sender)
+{
+ sys::Monitor::ScopedLock l(lock);
+ return sender->getUnsettled();
+}
+
+void ConnectionContext::setCapacity(boost::shared_ptr<ReceiverContext> receiver, uint32_t capacity)
+{
+ sys::Monitor::ScopedLock l(lock);
+ receiver->setCapacity(capacity);
+ pn_link_flow((pn_link_t*) receiver->receiver, receiver->getCapacity());
+ wakeupDriver();
+}
+uint32_t ConnectionContext::getCapacity(boost::shared_ptr<ReceiverContext> receiver)
+{
+ sys::Monitor::ScopedLock l(lock);
+ return receiver->getCapacity();
+}
+uint32_t ConnectionContext::getAvailable(boost::shared_ptr<ReceiverContext> receiver)
+{
+ sys::Monitor::ScopedLock l(lock);
+ return receiver->getAvailable();
+}
+uint32_t ConnectionContext::getUnsettled(boost::shared_ptr<ReceiverContext> receiver)
+{
+ sys::Monitor::ScopedLock l(lock);
+ return receiver->getUnsettled();
+}
+
+void ConnectionContext::activateOutput()
+{
+ sys::Monitor::ScopedLock l(lock);
+ if (state == CONNECTED) wakeupDriver();
+}
+/**
+ * Expects lock to be held by caller
+ */
+void ConnectionContext::wakeupDriver()
+{
+ switch (state) {
+ case CONNECTED:
+ haveOutput = true;
+ transport->activateOutput();
+ QPID_LOG(debug, "wakeupDriver()");
+ break;
+ case DISCONNECTED:
+ case CONNECTING:
+ QPID_LOG(error, "wakeupDriver() called while not connected");
+ break;
+ }
+}
+
+namespace {
+pn_state_t REQUIRES_CLOSE = PN_LOCAL_ACTIVE | PN_REMOTE_CLOSED;
+pn_state_t IS_CLOSED = PN_LOCAL_CLOSED | PN_REMOTE_CLOSED;
+}
+
+void ConnectionContext::reset()
+{
+ pn_connection_free(connection);
+ pn_transport_free(engine);
+
+ engine = pn_transport();
+ connection = pn_connection();
+ configureConnection();
+
+ for (SessionMap::iterator i = sessions.begin(); i != sessions.end(); ++i) {
+ i->second->reset(connection);
+ }
+}
+
+bool ConnectionContext::check() {
+ if (checkDisconnected()) {
+ if (ConnectionOptions::reconnect) {
+ QPID_LOG(notice, "Auto-reconnecting to " << fullUrl);
+ autoconnect();
+ QPID_LOG(notice, "Auto-reconnected to " << currentUrl);
+ } else {
+ throw qpid::messaging::TransportFailure("Disconnected (reconnect disabled)");
+ }
+ return true;
+ }
+ return false;
+}
+
+bool ConnectionContext::checkDisconnected() {
+ if (state == DISCONNECTED) {
+ reset();
+ } else {
+ if ((pn_connection_state(connection) & REQUIRES_CLOSE) == REQUIRES_CLOSE) {
+ std::string text = get_error_string(pn_connection_remote_condition(connection), "Connection closed by peer");
+ pn_connection_close(connection);
+ throw qpid::messaging::ConnectionError(text);
+ }
+ }
+ return state == DISCONNECTED;
+}
+
+void ConnectionContext::wait()
+{
+ if (check()) return; // Reconnected, may need to re-test condition.
+ lock.wait();
+ check();
+}
+void ConnectionContext::waitUntil(qpid::sys::AbsTime until)
+{
+ lock.wait(until);
+ check();
+}
+void ConnectionContext::wait(boost::shared_ptr<SessionContext> ssn)
+{
+ wait();
+ checkClosed(ssn);
+}
+void ConnectionContext::wait(boost::shared_ptr<SessionContext> ssn, boost::shared_ptr<ReceiverContext> lnk)
+{
+ wait();
+ checkClosed(ssn, lnk);
+}
+void ConnectionContext::wait(boost::shared_ptr<SessionContext> ssn, boost::shared_ptr<SenderContext> lnk)
+{
+ wait();
+ checkClosed(ssn, lnk);
+}
+void ConnectionContext::waitUntil(boost::shared_ptr<SessionContext> ssn, qpid::sys::AbsTime until)
+{
+ waitUntil(until);
+ checkClosed(ssn);
+}
+void ConnectionContext::waitUntil(boost::shared_ptr<SessionContext> ssn, boost::shared_ptr<ReceiverContext> lnk, qpid::sys::AbsTime until)
+{
+ waitUntil(until);
+ checkClosed(ssn, lnk);
+}
+void ConnectionContext::waitUntil(boost::shared_ptr<SessionContext> ssn, boost::shared_ptr<SenderContext> lnk, qpid::sys::AbsTime until)
+{
+ waitUntil(until);
+ checkClosed(ssn, lnk);
+}
+void ConnectionContext::checkClosed(boost::shared_ptr<SessionContext> ssn)
+{
+ check();
+ ssn->error.raise();
+ if ((pn_session_state(ssn->session) & REQUIRES_CLOSE) == REQUIRES_CLOSE) {
+ std::string text = get_error_string(pn_session_remote_condition(ssn->session), "Session ended by peer");
+ pn_session_close(ssn->session);
+ throw qpid::messaging::SessionError(text);
+ } else if ((pn_session_state(ssn->session) & IS_CLOSED) == IS_CLOSED) {
+ throw qpid::messaging::SessionClosed();
+ }
+}
+
+bool ConnectionContext::isClosed(boost::shared_ptr<SessionContext> ssn, boost::shared_ptr<ReceiverContext> lnk)
+{
+ try {
+ checkClosed(ssn, lnk->receiver);
+ return false;
+ } catch (const LinkError&) {
+ return true;
+ }
+}
+void ConnectionContext::checkClosed(boost::shared_ptr<SessionContext> ssn, boost::shared_ptr<ReceiverContext> lnk)
+{
+ checkClosed(ssn, lnk->receiver);
+}
+void ConnectionContext::checkClosed(boost::shared_ptr<SessionContext> ssn, boost::shared_ptr<SenderContext> lnk)
+{
+ checkClosed(ssn, lnk->sender);
+}
+void ConnectionContext::checkClosed(boost::shared_ptr<SessionContext> ssn, pn_link_t* lnk)
+{
+ checkClosed(ssn);
+ if ((pn_link_state(lnk) & REQUIRES_CLOSE) == REQUIRES_CLOSE) {
+ pn_condition_t* error = pn_link_remote_condition(lnk);
+ std::string text = get_error_string(error, "Link detached by peer");
+ pn_link_close(lnk);
+ std::string name = pn_condition_get_name(error);
+ if (name == qpid::amqp::error_conditions::NOT_FOUND) {
+ throw qpid::messaging::NotFound(text);
+ } else if (name == qpid::amqp::error_conditions::UNAUTHORIZED_ACCESS) {
+ throw qpid::messaging::UnauthorizedAccess(text);
+ } else {
+ throw qpid::messaging::LinkError(text);
+ }
+ } else if ((pn_link_state(lnk) & IS_CLOSED) == IS_CLOSED) {
+ throw qpid::messaging::LinkError("Link is not attached");
+ }
+}
+
+void ConnectionContext::restartSession(boost::shared_ptr<SessionContext> s)
+{
+ if (s->error) return;
+ pn_session_open(s->session);
+ wakeupDriver();
+ while (pn_session_state(s->session) & PN_REMOTE_UNINIT) {
+ wait();
+ }
+
+ for (SessionContext::SenderMap::iterator i = s->senders.begin(); i != s->senders.end(); ++i) {
+ QPID_LOG(debug, id << " reattaching sender " << i->first);
+ attach(s, i->second->sender);
+ i->second->verify();
+ QPID_LOG(debug, id << " sender " << i->first << " reattached");
+ i->second->resend();
+ }
+ for (SessionContext::ReceiverMap::iterator i = s->receivers.begin(); i != s->receivers.end(); ++i) {
+ QPID_LOG(debug, id << " reattaching receiver " << i->first);
+ if (i->second->capacity) {
+ attach(s, i->second->receiver, i->second->capacity);
+ } else {
+ attach(s, i->second->receiver, (uint32_t) i->second->fetching);
+ }
+ i->second->verify();
+ QPID_LOG(debug, id << " receiver " << i->first << " reattached");
+ }
+ wakeupDriver();
+}
+
+boost::shared_ptr<SessionContext> ConnectionContext::newSession(bool transactional, const std::string& n)
+{
+ boost::shared_ptr<SessionContext> session;
+ std::string name = n.empty() ? qpid::framing::Uuid(true).str() : n;
+ {
+ sys::Monitor::ScopedLock l(lock);
+ SessionMap::const_iterator i = sessions.find(name);
+ if (i == sessions.end()) {
+ session = boost::shared_ptr<SessionContext>(new SessionContext(connection));
+ session->setName(name);
+ pn_session_open(session->session);
+ wakeupDriver();
+ sessions[name] = session; // Add it now so it will be restarted if we reconnect in wait()
+ while (pn_session_state(session->session) & PN_REMOTE_UNINIT) {
+ wait();
+ }
+ } else {
+ throw qpid::messaging::KeyError(std::string("Session already exists: ") + name);
+ }
+
+ }
+ if (transactional) { // Outside of lock
+ startTxSession(session);
+ }
+ return session;
+}
+
+boost::shared_ptr<SessionContext> ConnectionContext::getSession(const std::string& name) const
+{
+ SessionMap::const_iterator i = sessions.find(name);
+ if (i == sessions.end()) {
+ throw qpid::messaging::KeyError(std::string("No such session") + name);
+ } else {
+ return i->second;
+ }
+}
+
+void ConnectionContext::setOption(const std::string& name, const qpid::types::Variant& value)
+{
+ set(name, value);
+}
+
+std::string ConnectionContext::getAuthenticatedUsername()
+{
+ return sasl.get() ? sasl->getAuthenticatedUsername() : std::string();
+}
+
+std::size_t ConnectionContext::decodePlain(const char* buffer, std::size_t size)
+{
+ sys::Monitor::ScopedLock l(lock);
+ QPID_LOG(trace, id << " decode(" << size << ")");
+ if (readHeader) {
+ size_t decoded = readProtocolHeader(buffer, size);
+ if (decoded < size) {
+ decoded += decode(buffer + decoded, size - decoded);
+ }
+ return decoded;
+ }
+
+ //TODO: Fix pn_engine_input() to take const buffer
+ ssize_t n = pn_transport_input(engine, const_cast<char*>(buffer), size);
+ if (n > 0 || n == PN_EOS) {
+ // PN_EOS either means we received a Close (which also means we've
+ // consumed all the input), OR some Very Bad Thing happened and this
+ // connection is toast.
+ if (n == PN_EOS)
+ {
+ std::string error;
+ if (checkTransportError(error)) {
+ // "He's dead, Jim."
+ QPID_LOG_CAT(error, network, id << " connection failed: " << error);
+ transport->abort();
+ return 0;
+ } else {
+ n = size; // assume all consumed
+ }
+ }
+ QPID_LOG_CAT(debug, network, id << " decoded " << n << " bytes from " << size)
+ pn_transport_tick(engine, qpid::sys::Duration::FromEpoch() / qpid::sys::TIME_MSEC);
+ lock.notifyAll();
+ return n;
+ } else if (n == PN_ERR) {
+ std::string error;
+ checkTransportError(error);
+ QPID_LOG_CAT(error, network, id << " connection error: " << error);
+ transport->abort();
+ return 0;
+ } else {
+ return 0;
+ }
+
+}
+std::size_t ConnectionContext::encodePlain(char* buffer, std::size_t size)
+{
+ sys::Monitor::ScopedLock l(lock);
+ QPID_LOG(trace, id << " encode(" << size << ")");
+ if (writeHeader) {
+ size_t encoded = writeProtocolHeader(buffer, size);
+ if (encoded < size) {
+ encoded += encode(buffer + encoded, size - encoded);
+ }
+ return encoded;
+ }
+
+ ssize_t n = pn_transport_output(engine, buffer, size);
+ if (n > 0) {
+ QPID_LOG_CAT(debug, network, id << " encoded " << n << " bytes from " << size)
+ haveOutput = true;
+ if (notifyOnWrite) lock.notifyAll();
+ return n;
+ } else if (n == PN_ERR) {
+ std::string error;
+ checkTransportError(error);
+ QPID_LOG_CAT(error, network, id << " connection error: " << error);
+ transport->abort();
+ return 0;
+ } else if (n == PN_EOS) {
+ haveOutput = false;
+ // Normal close, or error?
+ std::string error;
+ if (checkTransportError(error)) {
+ QPID_LOG_CAT(error, network, id << " connection failed: " << error);
+ transport->abort();
+ }
+ return 0;
+ } else {
+ haveOutput = false;
+ return 0;
+ }
+}
+bool ConnectionContext::canEncodePlain()
+{
+ sys::Monitor::ScopedLock l(lock);
+ pn_transport_tick(engine, qpid::sys::Duration::FromEpoch() / qpid::sys::TIME_MSEC);
+ return haveOutput && state == CONNECTED;
+}
+void ConnectionContext::closed()
+{
+ sys::Monitor::ScopedLock l(lock);
+ state = DISCONNECTED;
+ lock.notifyAll();
+}
+void ConnectionContext::opened()
+{
+ sys::Monitor::ScopedLock l(lock);
+ state = CONNECTED;
+ lock.notifyAll();
+}
+bool ConnectionContext::isClosed() const
+{
+ return !isOpen();
+}
+namespace {
+qpid::framing::ProtocolVersion AMQP_1_0_PLAIN(1,0,qpid::framing::ProtocolVersion::AMQP);
+}
+
+std::string ConnectionContext::getError()
+{
+ return get_error(connection, engine);
+}
+
+framing::ProtocolVersion ConnectionContext::getVersion() const
+{
+ return AMQP_1_0_PLAIN;
+}
+
+std::size_t ConnectionContext::readProtocolHeader(const char* buffer, std::size_t size)
+{
+ framing::ProtocolInitiation pi(getVersion());
+ if (size >= pi.encodedSize()) {
+ readHeader = false;
+ qpid::framing::Buffer out(const_cast<char*>(buffer), size);
+ pi.decode(out);
+ QPID_LOG_CAT(debug, protocol, id << " read protocol header: " << pi);
+ return pi.encodedSize();
+ } else {
+ return 0;
+ }
+}
+std::size_t ConnectionContext::writeProtocolHeader(char* buffer, std::size_t size)
+{
+ framing::ProtocolInitiation pi(getVersion());
+ if (size >= pi.encodedSize()) {
+ QPID_LOG_CAT(debug, protocol, id << " writing protocol header: " << pi);
+ writeHeader = false;
+ qpid::framing::Buffer out(buffer, size);
+ pi.encode(out);
+ return pi.encodedSize();
+ } else {
+ QPID_LOG_CAT(debug, protocol, id << " insufficient buffer for protocol header: " << size)
+ return 0;
+ }
+}
+bool ConnectionContext::useSasl()
+{
+ return !(mechanism == "none" || mechanism == "NONE" || mechanism == "None");
+}
+
+qpid::sys::Codec& ConnectionContext::getCodec()
+{
+ return *this;
+}
+
+const qpid::messaging::ConnectionOptions* ConnectionContext::getOptions()
+{
+ return this;
+}
+
+std::size_t ConnectionContext::decode(const char* buffer, std::size_t size)
+{
+ sys::Monitor::ScopedLock l(lock);
+ size_t decoded = 0;
+ try {
+ if (sasl.get() && !sasl->authenticated()) {
+ decoded = sasl->decode(buffer, size);
+ if (!sasl->authenticated()) return decoded;
+ }
+ if (decoded < size) {
+ if (sasl.get() && sasl->getSecurityLayer()) decoded += sasl->getSecurityLayer()->decode(buffer+decoded, size-decoded);
+ else decoded += decodePlain(buffer+decoded, size-decoded);
+ }
+ } catch (const AuthenticationFailure&) {
+ transport->close();
+ }
+ return decoded;
+}
+std::size_t ConnectionContext::encode(char* buffer, std::size_t size)
+{
+ sys::Monitor::ScopedLock l(lock);
+ size_t encoded = 0;
+ try {
+ if (sasl.get() && sasl->canEncode()) {
+ encoded += sasl->encode(buffer, size);
+ if (!sasl->authenticated()) return encoded;
+ }
+ if (encoded < size) {
+ if (sasl.get() && sasl->getSecurityLayer()) encoded += sasl->getSecurityLayer()->encode(buffer+encoded, size-encoded);
+ else encoded += encodePlain(buffer+encoded, size-encoded);
+ }
+ } catch (const AuthenticationFailure&) {
+ transport->close();
+ }
+ return encoded;
+}
+bool ConnectionContext::canEncode()
+{
+ sys::Monitor::ScopedLock l(lock);
+ if (sasl.get()) {
+ try {
+ if (sasl->canEncode()) return true;
+ else if (!sasl->authenticated()) return false;
+ else if (sasl->getSecurityLayer()) return sasl->getSecurityLayer()->canEncode();
+ } catch (const AuthenticationFailure&) {
+ transport->close();
+ return false;
+ }
+ }
+ return canEncodePlain();
+}
+
+namespace {
+const std::string CLIENT_PROCESS_NAME("qpid.client_process");
+const std::string CLIENT_PID("qpid.client_pid");
+const std::string CLIENT_PPID("qpid.client_ppid");
+}
+void ConnectionContext::setProperties()
+{
+ PnData data(pn_connection_properties(connection));
+ pn_data_put_map(data.data);
+ pn_data_enter(data.data);
+ data.putSymbol(CLIENT_PROCESS_NAME);
+ data.putSymbol(sys::SystemInfo::getProcessName());
+ data.putSymbol(CLIENT_PID);
+ data.put(int32_t(sys::SystemInfo::getProcessId()));
+ data.putSymbol(CLIENT_PPID);
+ data.put(int32_t(sys::SystemInfo::getParentProcessId()));
+ for (Variant::Map::const_iterator i = properties.begin(); i != properties.end(); ++i)
+ {
+ data.putSymbol(i->first);
+ data.put(i->second);
+ }
+ pn_data_exit(data.data);
+}
+
+const qpid::sys::SecuritySettings* ConnectionContext::getTransportSecuritySettings()
+{
+ return transport ? transport->getSecuritySettings() : 0;
+}
+
+void ConnectionContext::open()
+{
+ sys::Monitor::ScopedLock l(lock);
+ if (state != DISCONNECTED) throw qpid::messaging::ConnectionError("Connection was already opened!");
+ if (!driver) driver = DriverImpl::getDefault();
+ QPID_LOG(info, "Starting connection to " << fullUrl);
+ autoconnect();
+}
+
+
+namespace {
+double FOREVER(std::numeric_limits<double>::max());
+bool expired(const sys::AbsTime& start, double timeout)
+{
+ if (timeout == 0) return true;
+ if (timeout == FOREVER) return false;
+ qpid::sys::Duration used(start, qpid::sys::now());
+ qpid::sys::Duration allowed((int64_t)(timeout*qpid::sys::TIME_SEC));
+ return allowed < used;
+}
+const std::string COLON(":");
+}
+
+void throwConnectFail(const Url& url, const std::string& msg) {
+ throw qpid::messaging::TransportFailure(
+ Msg() << "Connect failed to " << url << ": " << msg);
+}
+
+void ConnectionContext::autoconnect()
+{
+ qpid::sys::AbsTime started(qpid::sys::now());
+ for (double i = minReconnectInterval; !tryConnectUrl(fullUrl); i = std::min(i*2, maxReconnectInterval)) {
+ if (!ConnectionOptions::reconnect) throwConnectFail(fullUrl, "Reconnect disabled");
+ if (limit >= 0 && retries++ >= limit) throwConnectFail(fullUrl, "Exceeded retries");
+ if (expired(started, timeout)) throwConnectFail(fullUrl, "Exceeded timeout");
+ QPID_LOG(debug, "Connection retry in " << i*1000*1000 << " microseconds to"
+ << fullUrl);
+ qpid::sys::usleep(int64_t(i*1000*1000)); // Sleep in microseconds.
+ }
+ retries = 0;
+}
+
+void ConnectionContext::reconnect(const Url& url) {
+ QPID_LOG(notice, "Reconnecting to " << url);
+ sys::Monitor::ScopedLock l(lock);
+ if (state != DISCONNECTED) throw qpid::messaging::ConnectionError("Connection was already opened!");
+ if (!driver) driver = DriverImpl::getDefault();
+ reset();
+ if (!tryConnectUrl(url)) throwConnectFail(url, "Failed to reconnect");
+ QPID_LOG(notice, "Reconnected to " << currentUrl);
+}
+
+void ConnectionContext::reconnect(const std::string& url) { reconnect(Url(url)); }
+
+void ConnectionContext::reconnect() { reconnect(fullUrl); }
+
+void ConnectionContext::waitNoReconnect() {
+ if (!checkDisconnected()) {
+ lock.wait();
+ checkDisconnected();
+ }
+}
+
+// Try to connect to a URL, i.e. try to connect to each of its addresses in turn
+// till one succeeds or they all fail.
+// @return true if we connect successfully
+bool ConnectionContext::tryConnectUrl(const Url& url)
+{
+ if (url.getUser().size()) username = url.getUser();
+ if (url.getPass().size()) password = url.getPass();
+
+ for (Url::const_iterator i = url.begin(); i != url.end(); ++i) {
+ QPID_LOG(info, "Connecting to " << *i);
+ if (tryConnectAddr(*i) && tryOpenAddr(*i)) {
+ QPID_LOG(info, "Connected to " << *i);
+ return true;
+ }
+ }
+ return false;
+}
+
+// Try to open an AMQP protocol connection on an address, after we have already
+// established a transport connect (see tryConnectAddr below)
+// @return true if the AMQP connection is succesfully opened.
+bool ConnectionContext::tryOpenAddr(const qpid::Address& addr) {
+ currentUrl = Url(addr);
+ if (sasl.get()) {
+ wakeupDriver();
+ while (!sasl->authenticated() && state != DISCONNECTED) {
+ QPID_LOG(debug, id << " Waiting to be authenticated...");
+ waitNoReconnect();
+ }
+ if (state == DISCONNECTED) return false;
+ QPID_LOG(debug, id << " Authenticated");
+ }
+
+ QPID_LOG(debug, id << " Opening...");
+ pn_connection_open(connection);
+ wakeupDriver(); //want to write
+ while ((pn_connection_state(connection) & PN_REMOTE_UNINIT) &&
+ state != DISCONNECTED)
+ waitNoReconnect();
+ if (state == DISCONNECTED) return false;
+ if (!(pn_connection_state(connection) & PN_REMOTE_ACTIVE)) {
+ throw qpid::messaging::ConnectionError("Failed to open connection");
+ }
+
+ // Connection open - check for idle timeout from the remote and start a
+ // periodic tick to monitor for idle connections
+ pn_timestamp_t remote = pn_transport_get_remote_idle_timeout(engine);
+ pn_timestamp_t local = pn_transport_get_idle_timeout(engine);
+ uint64_t shortest = ((remote && local)
+ ? std::min(remote, local)
+ : (remote) ? remote : local);
+ if (shortest) {
+ // send an idle frame at least twice before timeout
+ shortest = (shortest + 1)/2;
+ qpid::sys::Duration d(shortest * qpid::sys::TIME_MSEC);
+ ticker = boost::intrusive_ptr<qpid::sys::TimerTask>(new ConnectionTickerTask(d, driver->getTimer(), *this));
+ driver->getTimer().add(ticker);
+ QPID_LOG(debug, id << " AMQP 1.0 idle-timeout set:"
+ << " local=" << pn_transport_get_idle_timeout(engine)
+ << " remote=" << pn_transport_get_remote_idle_timeout(engine));
+ }
+
+ QPID_LOG(debug, id << " Opened");
+
+ return restartSessions();
+}
+
+std::string ConnectionContext::getUrl() const
+{
+ sys::Monitor::ScopedLock l(lock);
+ return (state == CONNECTED) ? currentUrl.str() : std::string();
+}
+
+// Try to establish a transport connect to an individual address (typically a
+// TCP host:port)
+// @return true if we succeed in connecting.
+bool ConnectionContext::tryConnectAddr(const qpid::Address& address)
+{
+ transport = driver->getTransport(address.protocol, *this);
+ id = boost::lexical_cast<std::string>(address);
+ if (useSasl()) {
+ sasl = std::auto_ptr<Sasl>(new Sasl(id, *this, address.host));
+ }
+ state = CONNECTING;
+ try {
+ QPID_LOG(debug, id << " Connecting ...");
+ transport->connect(address.host, boost::lexical_cast<std::string>(address.port));
+ bool waiting(true);
+ while (waiting) {
+ switch (state) {
+ case CONNECTED:
+ QPID_LOG(debug, id << " Connected");
+ return true;
+ case CONNECTING:
+ lock.wait();
+ break;
+ case DISCONNECTED:
+ waiting = false;
+ break;
+ }
+ }
+ } catch (const std::exception& e) {
+ QPID_LOG(info, id << " Error while connecting: " << e.what());
+ state = DISCONNECTED;
+ }
+ transport = boost::shared_ptr<Transport>();
+ return false;
+}
+
+bool ConnectionContext::restartSessions()
+{
+ try {
+ for (SessionMap::iterator i = sessions.begin(); i != sessions.end(); ++i) {
+ restartSession(i->second);
+ }
+ return true;
+ } catch (const qpid::TransportFailure& e) {
+ QPID_LOG(debug, "Connection Failed to re-initialize sessions: " << e.what());
+ return false;
+ }
+}
+
+void ConnectionContext::initSecurityLayer(qpid::sys::SecurityLayer& s)
+{
+ s.init(&codecAdapter);
+}
+
+ConnectionContext::CodecAdapter::CodecAdapter(ConnectionContext& c) : context(c) {}
+std::size_t ConnectionContext::CodecAdapter::decode(const char* buffer, std::size_t size)
+{
+ return context.decodePlain(buffer, size);
+}
+std::size_t ConnectionContext::CodecAdapter::encode(char* buffer, std::size_t size)
+{
+ return context.encodePlain(buffer, size);
+}
+bool ConnectionContext::CodecAdapter::canEncode()
+{
+ return context.canEncodePlain();
+}
+
+void ConnectionContext::startTxSession(boost::shared_ptr<SessionContext> session) {
+ try {
+ QPID_LOG(debug, id << " attaching transaction for " << session->getName());
+ boost::shared_ptr<Transaction> tx(new Transaction(session->session));
+ session->transaction = tx;
+ {
+ sys::Monitor::ScopedLock l(lock);
+ attach(session, boost::shared_ptr<SenderContext>(tx));
+ }
+ tx->declare(boost::bind(&ConnectionContext::send, this, _1, _2, _3, _4, _5), session);
+ } catch (const Exception& e) {
+ throw TransactionError(Msg() << "Cannot start transaction: " << e.what());
+ }
+}
+
+void ConnectionContext::discharge(boost::shared_ptr<SessionContext> session, bool fail) {
+ {
+ sys::Monitor::ScopedLock l(lock);
+ checkClosed(session);
+ if (!session->transaction)
+ throw TransactionError("No Transaction");
+ Transaction::SendFunction sendFn = boost::bind(
+ &ConnectionContext::sendLH, this, _1, _2, _3, _4, _5, boost::ref(l));
+ syncLH(session, boost::ref(l)); // Sync to make sure all tx transfers have been received.
+ session->transaction->discharge(sendFn, session, fail);
+ session->transaction->declare(sendFn, session);
+ }
+}
+
+void ConnectionContext::commit(boost::shared_ptr<SessionContext> session) {
+ discharge(session, false);
+}
+
+void ConnectionContext::rollback(boost::shared_ptr<SessionContext> session) {
+ discharge(session, true);
+}
+
+
+// setup the transport and connection objects:
+void ConnectionContext::configureConnection()
+{
+ pn_connection_set_container(connection, identifier.c_str());
+ setProperties();
+ if (heartbeat) {
+ // fail an idle connection at 2 x heartbeat (in msecs)
+ pn_transport_set_idle_timeout(engine, heartbeat*2*1000);
+ }
+
+ bool enableTrace(false);
+ QPID_LOG_TEST_CAT(trace, protocol, enableTrace);
+ if (enableTrace) {
+ pn_transport_trace(engine, PN_TRACE_FRM);
+ set_tracer(engine, this);
+ }
+
+ int err = pn_transport_bind(engine, connection);
+ if (err)
+ QPID_LOG(error, id << " Error binding connection and transport: " << err);
+}
+
+
+// check for failures of the transport:
+bool ConnectionContext::checkTransportError(std::string& text)
+{
+ std::stringstream info;
+
+#ifdef USE_PROTON_TRANSPORT_CONDITION
+ pn_condition_t* tcondition = pn_transport_condition(engine);
+ if (pn_condition_is_set(tcondition))
+ info << get_error_string(tcondition, "transport error", ": ");
+#else
+ pn_error_t* terror = pn_transport_error(engine);
+ if (terror) info << "transport error " << pn_error_text(terror) << " [" << terror << "]";
+#endif
+
+ text = info.str();
+ return !text.empty();
+}
+}}} // namespace qpid::messaging::amqp
diff --git a/qpid/cpp/src/qpid/messaging/amqp/ConnectionContext.h b/qpid/cpp/src/qpid/messaging/amqp/ConnectionContext.h
new file mode 100644
index 0000000000..ba3220c0ab
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/ConnectionContext.h
@@ -0,0 +1,233 @@
+#ifndef QPID_MESSAGING_AMQP_CONNECTIONCONTEXT_H
+#define QPID_MESSAGING_AMQP_CONNECTIONCONTEXT_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <deque>
+#include <map>
+#include <memory>
+#include <string>
+#include <boost/intrusive_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+#include "qpid/Url.h"
+#include "qpid/messaging/ConnectionOptions.h"
+#include "qpid/sys/AtomicValue.h"
+#include "qpid/sys/ConnectionCodec.h"
+#include "qpid/sys/Monitor.h"
+#include "qpid/types/Variant.h"
+#include "qpid/messaging/amqp/TransportContext.h"
+#include "SenderContext.h"
+
+struct pn_connection_t;
+struct pn_link_t;
+struct pn_session_t;
+struct pn_transport_t;
+
+
+namespace qpid {
+namespace framing {
+class ProtocolVersion;
+}
+namespace sys {
+class SecurityLayer;
+struct SecuritySettings;
+class TimerTask;
+}
+namespace messaging {
+class Duration;
+class Message;
+namespace amqp {
+
+class DriverImpl;
+class ReceiverContext;
+class Sasl;
+class SessionContext;
+class Transport;
+
+/**
+ *
+ */
+class ConnectionContext : public qpid::sys::ConnectionCodec, public qpid::messaging::ConnectionOptions, public TransportContext
+{
+ public:
+ ConnectionContext(const std::string& url, const qpid::types::Variant::Map& options);
+ ~ConnectionContext();
+ void open();
+ bool isOpen() const;
+ void close();
+ boost::shared_ptr<SessionContext> newSession(bool transactional, const std::string& name);
+ boost::shared_ptr<SessionContext> getSession(const std::string& name) const;
+ void endSession(boost::shared_ptr<SessionContext>);
+ boost::shared_ptr<SenderContext> createSender(boost::shared_ptr<SessionContext>, const qpid::messaging::Address& address);
+ boost::shared_ptr<ReceiverContext> createReceiver(boost::shared_ptr<SessionContext>, const qpid::messaging::Address& address);
+ boost::shared_ptr<SenderContext> getSender(boost::shared_ptr<SessionContext>, const std::string& name) const;
+ boost::shared_ptr<ReceiverContext> getReceiver(boost::shared_ptr<SessionContext>, const std::string& name) const;
+
+ void detach(boost::shared_ptr<SessionContext>, boost::shared_ptr<SenderContext>);
+ void detach(boost::shared_ptr<SessionContext>, boost::shared_ptr<ReceiverContext>);
+ void drain_and_release_messages(boost::shared_ptr<SessionContext>, boost::shared_ptr<ReceiverContext>);
+ bool isClosed(boost::shared_ptr<SessionContext>, boost::shared_ptr<ReceiverContext>);
+
+ // Link operations
+ void send(boost::shared_ptr<SessionContext>, boost::shared_ptr<SenderContext> ctxt,
+ const qpid::messaging::Message& message, bool sync,
+ SenderContext::Delivery** delivery);
+
+ bool fetch(boost::shared_ptr<SessionContext> ssn, boost::shared_ptr<ReceiverContext> lnk, qpid::messaging::Message& message, qpid::messaging::Duration timeout);
+ bool get(boost::shared_ptr<SessionContext> ssn, boost::shared_ptr<ReceiverContext> lnk, qpid::messaging::Message& message, qpid::messaging::Duration timeout);
+
+ // Session operations
+ void acknowledge(boost::shared_ptr<SessionContext> ssn, qpid::messaging::Message* message, bool cumulative);
+ void commit(boost::shared_ptr<SessionContext> ssn);
+ void rollback(boost::shared_ptr<SessionContext> ssn);
+
+ void nack(boost::shared_ptr<SessionContext> ssn, qpid::messaging::Message& message, bool reject);
+ void sync(boost::shared_ptr<SessionContext> ssn);
+ boost::shared_ptr<ReceiverContext> nextReceiver(boost::shared_ptr<SessionContext> ssn, qpid::messaging::Duration timeout);
+
+ void setOption(const std::string& name, const qpid::types::Variant& value);
+ std::string getAuthenticatedUsername();
+
+ // Link operations
+ void setCapacity(boost::shared_ptr<SenderContext>, uint32_t);
+ uint32_t getCapacity(boost::shared_ptr<SenderContext>);
+ uint32_t getUnsettled(boost::shared_ptr<SenderContext>);
+ void setCapacity(boost::shared_ptr<ReceiverContext>, uint32_t);
+ uint32_t getCapacity(boost::shared_ptr<ReceiverContext>);
+ uint32_t getAvailable(boost::shared_ptr<ReceiverContext>);
+ uint32_t getUnsettled(boost::shared_ptr<ReceiverContext>);
+
+
+ void activateOutput();
+ qpid::sys::Codec& getCodec();
+ const qpid::messaging::ConnectionOptions* getOptions();
+ //ConnectionCodec interface:
+ std::size_t decode(const char* buffer, std::size_t size);
+ std::size_t encode(char* buffer, std::size_t size);
+ bool canEncode();
+ void closed();
+ bool isClosed() const;
+ framing::ProtocolVersion getVersion() const;
+ //additionally, Transport needs:
+ void opened();//signal successful connection
+ void reconnect(const std::string& url);
+ void reconnect();
+ std::string getUrl() const;
+ const qpid::sys::SecuritySettings* getTransportSecuritySettings();
+ void initSecurityLayer(qpid::sys::SecurityLayer&);
+ void trace(const char*) const;
+
+ private:
+ typedef std::map<std::string, boost::shared_ptr<SessionContext> > SessionMap;
+ class CodecAdapter : public qpid::sys::Codec
+ {
+ public:
+ CodecAdapter(ConnectionContext&);
+ std::size_t decode(const char* buffer, std::size_t size);
+ std::size_t encode(char* buffer, std::size_t size);
+ bool canEncode();
+ private:
+ ConnectionContext& context;
+ };
+
+ Url fullUrl; // Combined URL of all known addresses.
+ Url currentUrl; // URL of currently connected address.
+
+ boost::shared_ptr<DriverImpl> driver;
+ boost::shared_ptr<Transport> transport;
+
+ pn_transport_t* engine;
+ pn_connection_t* connection;
+ SessionMap sessions;
+ mutable qpid::sys::Monitor lock;
+ bool writeHeader;
+ bool readHeader;
+ bool haveOutput;
+ std::string id;
+ enum {
+ DISCONNECTED,
+ CONNECTING,
+ CONNECTED
+ } state;
+ std::auto_ptr<Sasl> sasl;
+ CodecAdapter codecAdapter;
+ bool notifyOnWrite;
+ boost::intrusive_ptr<qpid::sys::TimerTask> ticker;
+
+ bool check();
+ bool checkDisconnected();
+ void waitNoReconnect();
+
+ // NOTE: All wait*() functions must be called in a loop that checks for the
+ // waited condition with the lock held.
+ void wait();
+ void waitUntil(qpid::sys::AbsTime until);
+ void wait(boost::shared_ptr<SessionContext>);
+ void waitUntil(boost::shared_ptr<SessionContext>, qpid::sys::AbsTime until);
+ void wait(boost::shared_ptr<SessionContext>, boost::shared_ptr<ReceiverContext>);
+ void wait(boost::shared_ptr<SessionContext>, boost::shared_ptr<SenderContext>);
+ void waitUntil(boost::shared_ptr<SessionContext>, boost::shared_ptr<ReceiverContext>, qpid::sys::AbsTime until);
+ void waitUntil(boost::shared_ptr<SessionContext>, boost::shared_ptr<SenderContext>, qpid::sys::AbsTime until);
+
+ void checkClosed(boost::shared_ptr<SessionContext>);
+ void checkClosed(boost::shared_ptr<SessionContext>, boost::shared_ptr<ReceiverContext>);
+ void checkClosed(boost::shared_ptr<SessionContext>, boost::shared_ptr<SenderContext>);
+ void checkClosed(boost::shared_ptr<SessionContext>, pn_link_t*);
+
+ void wakeupDriver();
+ void attach(boost::shared_ptr<SessionContext>, pn_link_t*, int credit=0);
+ void attach(boost::shared_ptr<SessionContext>, boost::shared_ptr<SenderContext>);
+ void attach(boost::shared_ptr<SessionContext>, boost::shared_ptr<ReceiverContext>);
+ void autoconnect();
+ bool tryConnectUrl(const qpid::Url& url);
+ bool tryOpenAddr(const qpid::Address& address);
+ bool tryConnectAddr(const qpid::Address& address);
+ void reconnect(const Url& url);
+ void reset();
+ bool restartSessions();
+ void restartSession(boost::shared_ptr<SessionContext>);
+
+ std::size_t decodePlain(const char* buffer, std::size_t size);
+ std::size_t encodePlain(char* buffer, std::size_t size);
+ bool canEncodePlain();
+
+ std::size_t readProtocolHeader(const char* buffer, std::size_t size);
+ std::size_t writeProtocolHeader(char* buffer, std::size_t size);
+ std::string getError();
+ bool useSasl();
+ void setProperties();
+
+ void configureConnection();
+ bool checkTransportError(std::string&);
+
+ void discharge(boost::shared_ptr<SessionContext>, bool fail);
+ void startTxSession(boost::shared_ptr<SessionContext>);
+
+ void syncLH(boost::shared_ptr<SessionContext> ssn, sys::Monitor::ScopedLock&);
+ void sendLH(boost::shared_ptr<SessionContext>, boost::shared_ptr<SenderContext> ctxt,
+ const qpid::messaging::Message& message, bool sync,
+ SenderContext::Delivery** delivery, sys::Monitor::ScopedLock&);
+ void acknowledgeLH(boost::shared_ptr<SessionContext> ssn, qpid::messaging::Message* message, bool cumulative, sys::Monitor::ScopedLock&);
+};
+
+}}} // namespace qpid::messaging::amqp
+
+#endif /*!QPID_MESSAGING_AMQP_CONNECTIONCONTEXT_H*/
diff --git a/qpid/cpp/src/qpid/messaging/amqp/ConnectionHandle.cpp b/qpid/cpp/src/qpid/messaging/amqp/ConnectionHandle.cpp
new file mode 100644
index 0000000000..90227fa29b
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/ConnectionHandle.cpp
@@ -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.
+ *
+ */
+#include "ConnectionHandle.h"
+#include "ConnectionContext.h"
+#include "SessionHandle.h"
+#include "qpid/messaging/Session.h"
+#include "qpid/messaging/ProtocolRegistry.h"
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+// Static constructor which registers this implementation in the ProtocolRegistry
+namespace {
+ConnectionImpl* create(const std::string& u, const qpid::types::Variant::Map& o)
+{
+ try {
+ return new ConnectionHandle(u, o);
+ } catch (const types::Exception& ) {
+ throw;
+ } catch (const qpid::Exception& e) {
+ throw messaging::ConnectionError( e.what() );
+ }
+}
+
+struct StaticInit
+{
+ StaticInit()
+ {
+ ProtocolRegistry::add("amqp1.0", &create);
+ };
+} init;
+}
+
+ConnectionHandle::ConnectionHandle(const std::string& url, const qpid::types::Variant::Map& options) : connection(new ConnectionContext(url, options)) {}
+ConnectionHandle::ConnectionHandle(boost::shared_ptr<ConnectionContext> c) : connection(c) {}
+
+void ConnectionHandle::open()
+{
+ connection->open();
+}
+
+bool ConnectionHandle::isOpen() const
+{
+ return connection->isOpen();
+}
+
+void ConnectionHandle::close()
+{
+ connection->close();
+}
+
+Session ConnectionHandle::newSession(bool transactional, const std::string& name)
+{
+ return qpid::messaging::Session(new SessionHandle(connection, connection->newSession(transactional, name)));
+}
+
+Session ConnectionHandle::getSession(const std::string& name) const
+{
+ return qpid::messaging::Session(new SessionHandle(connection, connection->getSession(name)));
+}
+
+void ConnectionHandle::setOption(const std::string& name, const qpid::types::Variant& value)
+{
+ connection->setOption(name, value);
+}
+
+std::string ConnectionHandle::getAuthenticatedUsername()
+{
+ return connection->getAuthenticatedUsername();
+}
+
+void ConnectionHandle::reconnect(const std::string& url)
+{
+ connection->reconnect(url);
+}
+void ConnectionHandle::reconnect()
+{
+ connection->reconnect();
+}
+std::string ConnectionHandle::getUrl() const
+{
+ return connection->getUrl();
+}
+
+}}} // namespace qpid::messaging::amqp
diff --git a/qpid/cpp/src/qpid/messaging/amqp/ConnectionHandle.h b/qpid/cpp/src/qpid/messaging/amqp/ConnectionHandle.h
new file mode 100644
index 0000000000..0238313f93
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/ConnectionHandle.h
@@ -0,0 +1,61 @@
+#ifndef QPID_MESSAGING_AMQP_CONNECTIONHANDLE_H
+#define QPID_MESSAGING_AMQP_CONNECTIONHANDLE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <boost/shared_ptr.hpp>
+#include "qpid/messaging/ConnectionImpl.h"
+#include "qpid/types/Variant.h"
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+
+class ConnectionContext;
+/**
+ * Handles are directly referenced by applications; Contexts are
+ * referenced by Handles. This allows a graph structure that
+ * remains intact as long as the application references any part
+ * of it, but that can be automatically reclaimed if the whole
+ * graph becomes unreferenced.
+ */
+class ConnectionHandle : public qpid::messaging::ConnectionImpl
+{
+ public:
+ ConnectionHandle(const std::string& url, const qpid::types::Variant::Map& options);
+ ConnectionHandle(boost::shared_ptr<ConnectionContext>);
+ void open();
+ bool isOpen() const;
+ void close();
+ Session newSession(bool transactional, const std::string& name);
+ Session getSession(const std::string& name) const;
+ void setOption(const std::string& name, const qpid::types::Variant& value);
+ std::string getAuthenticatedUsername();
+ void reconnect(const std::string& url);
+ void reconnect();
+ std::string getUrl() const;
+ private:
+ boost::shared_ptr<ConnectionContext> connection;
+};
+
+}}} // namespace qpid::messaging::amqp_1.0
+
+#endif /*!QPID_MESSAGING_AMQP_CONNECTIONHANDLE_H*/
diff --git a/qpid/cpp/src/qpid/messaging/amqp/DriverImpl.cpp b/qpid/cpp/src/qpid/messaging/amqp/DriverImpl.cpp
new file mode 100644
index 0000000000..ebe3fff1cb
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/DriverImpl.cpp
@@ -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.
+ *
+ */
+#include "DriverImpl.h"
+#include "Transport.h"
+#include "qpid/messaging/exceptions.h"
+#include "qpid/sys/Poller.h"
+#include "qpid/sys/Timer.h"
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+
+DriverImpl::DriverImpl() : poller(new qpid::sys::Poller), timer(new qpid::sys::Timer)
+{
+ start();
+}
+DriverImpl::~DriverImpl()
+{
+ stop();
+}
+
+void DriverImpl::start()
+{
+ thread = qpid::sys::Thread(*poller);
+ QPID_LOG(debug, "Driver started");
+}
+
+void DriverImpl::stop()
+{
+ QPID_LOG(debug, "Driver stopped");
+ poller->shutdown();
+ thread.join();
+ timer->stop();
+}
+
+boost::shared_ptr<Transport> DriverImpl::getTransport(const std::string& protocol, TransportContext& connection)
+{
+ boost::shared_ptr<Transport> t(Transport::create(protocol, connection, poller));
+ if (!t) throw qpid::messaging::ConnectionError("No such transport: " + protocol);
+ return t;
+}
+
+
+qpid::sys::Mutex DriverImpl::defaultLock;
+boost::weak_ptr<DriverImpl> DriverImpl::theDefault;
+boost::shared_ptr<DriverImpl> DriverImpl::getDefault()
+{
+ qpid::sys::Mutex::ScopedLock l(defaultLock);
+ boost::shared_ptr<DriverImpl> p = theDefault.lock();
+ if (!p) {
+ p = boost::shared_ptr<DriverImpl>(new DriverImpl);
+ theDefault = p;
+ }
+ return p;
+}
+
+}}} // namespace qpid::messaging::amqp
diff --git a/qpid/cpp/src/qpid/messaging/amqp/DriverImpl.h b/qpid/cpp/src/qpid/messaging/amqp/DriverImpl.h
new file mode 100644
index 0000000000..36cb196343
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/DriverImpl.h
@@ -0,0 +1,64 @@
+#ifndef QPID_MESSAGING_AMQP_DRIVERIMPL_H
+#define QPID_MESSAGING_AMQP_DRIVERIMPL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/Thread.h"
+#include <boost/shared_ptr.hpp>
+#include <boost/weak_ptr.hpp>
+
+namespace qpid {
+namespace sys {
+class Poller;
+class Timer;
+}
+namespace messaging {
+namespace amqp {
+class TransportContext;
+class Transport;
+/**
+ *
+ */
+class DriverImpl
+{
+ public:
+ DriverImpl();
+ ~DriverImpl();
+
+ void start();
+ void stop();
+
+ boost::shared_ptr<Transport> getTransport(const std::string& protocol, TransportContext& connection);
+ sys::Timer& getTimer() { return *timer; }
+
+ static boost::shared_ptr<DriverImpl> getDefault();
+ private:
+ boost::shared_ptr<qpid::sys::Poller> poller;
+ qpid::sys::Thread thread;
+ std::auto_ptr<sys::Timer> timer;
+
+ static qpid::sys::Mutex defaultLock;
+ static boost::weak_ptr<DriverImpl> theDefault;
+};
+}}} // namespace qpid::messaging::amqp
+
+#endif /*!QPID_MESSAGING_AMQP_DRIVERIMPL_H*/
diff --git a/qpid/cpp/src/qpid/messaging/amqp/EncodedMessage.cpp b/qpid/cpp/src/qpid/messaging/amqp/EncodedMessage.cpp
new file mode 100644
index 0000000000..cf60046245
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/EncodedMessage.cpp
@@ -0,0 +1,366 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/messaging/amqp/EncodedMessage.h"
+#include "qpid/messaging/Address.h"
+#include "qpid/messaging/exceptions.h"
+#include "qpid/messaging/MessageImpl.h"
+#include "qpid/Exception.h"
+#include "qpid/amqp/Decoder.h"
+#include "qpid/amqp/DataBuilder.h"
+#include "qpid/amqp/ListBuilder.h"
+#include "qpid/amqp/MapBuilder.h"
+#include "qpid/amqp/typecodes.h"
+#include "qpid/types/encodings.h"
+#include "qpid/log/Statement.h"
+#include <boost/lexical_cast.hpp>
+#include <string.h>
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+using namespace qpid::amqp;
+
+EncodedMessage::EncodedMessage(size_t s) : size(s), data(size ? new char[size] : 0), nestAnnotations(false)
+{
+ init();
+}
+
+EncodedMessage::EncodedMessage() : size(0), data(0), nestAnnotations(false)
+{
+ init();
+}
+
+EncodedMessage::EncodedMessage(const EncodedMessage& other) : size(other.size), data(size ? new char[size] : 0), nestAnnotations(false)
+{
+ init();
+}
+
+void EncodedMessage::init()
+{
+ //init all CharSequence members
+ deliveryAnnotations.init();
+ messageAnnotations.init();
+ userId.init();
+ to.init();
+ subject.init();
+ replyTo.init();
+ contentType.init();
+ contentEncoding.init();
+ groupId.init();
+ replyToGroupId.init();
+ applicationProperties.init();
+ body.init();
+ footer.init();
+}
+
+EncodedMessage::~EncodedMessage()
+{
+ delete[] data;
+}
+
+size_t EncodedMessage::getSize() const
+{
+ return size;
+}
+void EncodedMessage::trim(size_t t)
+{
+ size = t;
+}
+void EncodedMessage::resize(size_t s)
+{
+ delete[] data;
+ size = s;
+ data = new char[size];
+}
+
+char* EncodedMessage::getData()
+{
+ return data;
+}
+const char* EncodedMessage::getData() const
+{
+ return data;
+}
+
+void EncodedMessage::init(qpid::messaging::MessageImpl& impl)
+{
+ try {
+ //initial scan of raw data
+ qpid::amqp::Decoder decoder(data, size);
+ InitialScan reader(*this, impl);
+ decoder.read(reader);
+ bareMessage = reader.getBareMessage();
+ if (bareMessage.data && !bareMessage.size) {
+ bareMessage.size = (data + size) - bareMessage.data;
+ }
+ } catch (const qpid::Exception& e) {
+ throw FetchError(e.what());
+ }
+}
+void EncodedMessage::setNestAnnotationsOption(bool b) { nestAnnotations = b; }
+
+namespace {
+using qpid::types::Variant;
+void merge(qpid::types::Variant::Map& map, const qpid::types::Variant::Map& additions)
+{
+ for (Variant::Map::const_iterator i = additions.begin(); i != additions.end(); ++i)
+ {
+ if (map.find(i->first) == map.end()) {
+ map[i->first] = i->second;
+ } else {
+ QPID_LOG(info, "Annotation " << i->first << " hidden by application property of the same name (consider using nest_annotations option?)");
+ }
+ }
+}
+}
+
+void EncodedMessage::populate(qpid::types::Variant::Map& map) const
+{
+ try {
+ //decode application properties
+ if (applicationProperties) {
+ qpid::amqp::Decoder decoder(applicationProperties.data, applicationProperties.size);
+ decoder.readMap(map);
+ }
+ //add in 'x-amqp-' prefixed values
+ if (!!firstAcquirer) {
+ map["x-amqp-first-acquirer"] = firstAcquirer.get();
+ }
+ if (!!deliveryCount) {
+ map["x-amqp-delivery-count"] = deliveryCount.get();
+ }
+ if (to) {
+ map["x-amqp-to"] = to.str();
+ }
+ if (contentEncoding) {
+ map["x-amqp-content-encoding"] = contentEncoding.str();
+ }
+ if (!!absoluteExpiryTime) {
+ map["x-amqp-absolute-expiry-time"] = absoluteExpiryTime.get();
+ }
+ if (!!creationTime) {
+ map["x-amqp-creation-time"] = creationTime.get();
+ }
+ if (groupId) {
+ map["x-amqp-group-id"] = groupId.str();
+ }
+ if (!!groupSequence) {
+ map["x-amqp-group-sequence"] = groupSequence.get();
+ }
+ if (replyToGroupId) {
+ map["x-amqp-reply-to-group-id"] = replyToGroupId.str();
+ }
+ //add in any annotations
+ if (deliveryAnnotations) {
+ qpid::amqp::Decoder decoder(deliveryAnnotations.data, deliveryAnnotations.size);
+ if (nestAnnotations) {
+ map["x-amqp-delivery-annotations"] = decoder.readMap();
+ } else {
+ merge(map, decoder.readMap());
+ }
+ }
+ if (messageAnnotations) {
+ qpid::amqp::Decoder decoder(messageAnnotations.data, messageAnnotations.size);
+ if (nestAnnotations) {
+ map["x-amqp-message-annotations"] = decoder.readMap();
+ } else {
+ merge(map, decoder.readMap());
+ }
+ }
+ } catch (const qpid::Exception& e) {
+ throw FetchError(e.what());
+ }
+}
+qpid::amqp::CharSequence EncodedMessage::getBareMessage() const
+{
+ return bareMessage;
+}
+
+void EncodedMessage::getReplyTo(qpid::messaging::Address& a) const
+{
+ std::string rt = replyTo.str();
+ std::string::size_type i = rt.find('/');
+ if (i != std::string::npos && i > 0 && rt.find('/', i+1) == std::string::npos) {
+ //handle <name>/<subject> special case
+ a.setName(rt.substr(0, i));
+ a.setSubject(rt.substr(i+1));
+ } else {
+ a.setName(rt);
+ }
+}
+void EncodedMessage::getSubject(std::string& s) const
+{
+ s.assign(subject.data, subject.size);
+}
+void EncodedMessage::getContentType(std::string& s) const
+{
+ s.assign(contentType.data, contentType.size);
+}
+void EncodedMessage::getUserId(std::string& s) const
+{
+ s.assign(userId.data, userId.size);
+}
+void EncodedMessage::getMessageId(std::string& s) const
+{
+ messageId.assign(s);
+}
+void EncodedMessage::getCorrelationId(std::string& s) const
+{
+ correlationId.assign(s);
+}
+void EncodedMessage::getBody(std::string& raw, qpid::types::Variant& c) const
+{
+ try {
+ if (!content.isVoid()) {
+ c = content;//integer types, floats, bool etc
+ //TODO: populate raw data?
+ } else {
+ if (bodyType.empty()
+ || bodyType == qpid::amqp::typecodes::BINARY_NAME
+ || bodyType == qpid::types::encodings::UTF8
+ || bodyType == qpid::types::encodings::ASCII)
+ {
+ c = std::string(body.data, body.size);
+ c.setEncoding(bodyType);
+ } else if (bodyType == qpid::amqp::typecodes::LIST_NAME) {
+ qpid::amqp::ListBuilder builder;
+ qpid::amqp::Decoder decoder(body.data, body.size);
+ decoder.read(builder);
+ c = builder.getList();
+ raw.assign(body.data, body.size);
+ } else if (bodyType == qpid::amqp::typecodes::MAP_NAME) {
+ qpid::types::Variant v = qpid::types::Variant::Map();
+ qpid::amqp::DataBuilder builder(v);
+ qpid::amqp::Decoder decoder(body.data, body.size);
+ decoder.read(builder);
+ c = builder.getValue().asMap();
+ raw.assign(body.data, body.size);
+ } else if (bodyType == qpid::amqp::typecodes::UUID_NAME) {
+ if (body.size == qpid::types::Uuid::SIZE) c = qpid::types::Uuid(body.data);
+ raw.assign(body.data, body.size);
+ } else if (bodyType == qpid::amqp::typecodes::ARRAY_NAME) {
+ raw.assign(body.data, body.size);
+ }
+ }
+ } catch (const qpid::Exception& e) {
+ throw FetchError(e.what());
+ }
+}
+
+qpid::amqp::CharSequence EncodedMessage::getBody() const
+{
+ return body;
+}
+
+bool EncodedMessage::hasHeaderChanged(const qpid::messaging::MessageImpl& msg) const
+{
+ if (!durable) {
+ if (msg.isDurable()) return true;
+ } else {
+ if (durable.get() != msg.isDurable()) return true;
+ }
+
+ if (!priority) {
+ if (msg.getPriority() != 4) return true;
+ } else {
+ if (priority.get() != msg.getPriority()) return true;
+ }
+
+ if (msg.getTtl() && (!ttl || msg.getTtl() != ttl.get())) {
+ return true;
+ }
+
+ //first-acquirer can't be changed via Message interface as yet
+
+ if (msg.isRedelivered() && (!deliveryCount || deliveryCount.get() == 0)) {
+ return true;
+ }
+
+ return false;
+}
+
+
+
+EncodedMessage::InitialScan::InitialScan(EncodedMessage& e, qpid::messaging::MessageImpl& m) : em(e), mi(m)
+{
+ //set up defaults as needed:
+ mi.setPriority(4);
+}
+//header:
+void EncodedMessage::InitialScan::onDurable(bool b) { mi.setDurable(b); em.durable = b; }
+void EncodedMessage::InitialScan::onPriority(uint8_t i) { mi.setPriority(i); em.priority = i; }
+void EncodedMessage::InitialScan::onTtl(uint32_t i) { mi.setTtl(i); em.ttl = i; }
+void EncodedMessage::InitialScan::onFirstAcquirer(bool b) { em.firstAcquirer = b; }
+void EncodedMessage::InitialScan::onDeliveryCount(uint32_t i)
+{
+ mi.setRedelivered(i);
+ em.deliveryCount = i;
+}
+
+//properties:
+void EncodedMessage::InitialScan::onMessageId(uint64_t v) { em.messageId.set(v); }
+void EncodedMessage::InitialScan::onMessageId(const qpid::amqp::CharSequence& v, qpid::types::VariantType t) { em.messageId.set(v, t); }
+void EncodedMessage::InitialScan::onUserId(const qpid::amqp::CharSequence& v) { em.userId = v; }
+void EncodedMessage::InitialScan::onTo(const qpid::amqp::CharSequence& v) { em.to = v; }
+void EncodedMessage::InitialScan::onSubject(const qpid::amqp::CharSequence& v) { em.subject = v; }
+void EncodedMessage::InitialScan::onReplyTo(const qpid::amqp::CharSequence& v) { em.replyTo = v;}
+void EncodedMessage::InitialScan::onCorrelationId(uint64_t v) { em.correlationId.set(v); }
+void EncodedMessage::InitialScan::onCorrelationId(const qpid::amqp::CharSequence& v, qpid::types::VariantType t) { em.correlationId.set(v, t); }
+void EncodedMessage::InitialScan::onContentType(const qpid::amqp::CharSequence& v) { em.contentType = v; }
+void EncodedMessage::InitialScan::onContentEncoding(const qpid::amqp::CharSequence& v) { em.contentEncoding = v; }
+void EncodedMessage::InitialScan::onAbsoluteExpiryTime(int64_t i) { em.absoluteExpiryTime = i; }
+void EncodedMessage::InitialScan::onCreationTime(int64_t i) { em.creationTime = i; }
+void EncodedMessage::InitialScan::onGroupId(const qpid::amqp::CharSequence& v) { em.groupId = v; }
+void EncodedMessage::InitialScan::onGroupSequence(uint32_t i) { em.groupSequence = i; }
+void EncodedMessage::InitialScan::onReplyToGroupId(const qpid::amqp::CharSequence& v) { em.replyToGroupId = v; }
+
+void EncodedMessage::InitialScan::onApplicationProperties(const qpid::amqp::CharSequence& v, const qpid::amqp::CharSequence&) { em.applicationProperties = v; }
+void EncodedMessage::InitialScan::onDeliveryAnnotations(const qpid::amqp::CharSequence& v, const qpid::amqp::CharSequence&) { em.deliveryAnnotations = v; }
+void EncodedMessage::InitialScan::onMessageAnnotations(const qpid::amqp::CharSequence& v, const qpid::amqp::CharSequence&) { em.messageAnnotations = v; }
+
+void EncodedMessage::InitialScan::onData(const qpid::amqp::CharSequence& v)
+{
+ em.body = v;
+}
+void EncodedMessage::InitialScan::onAmqpSequence(const qpid::amqp::CharSequence& v)
+{
+ em.body = v;
+ em.bodyType = qpid::amqp::typecodes::LIST_NAME;
+}
+void EncodedMessage::InitialScan::onAmqpValue(const qpid::amqp::CharSequence& v, const std::string& type, const qpid::amqp::Descriptor*)
+{
+ em.body = v;
+ if (type == qpid::amqp::typecodes::STRING_NAME) {
+ em.bodyType = qpid::types::encodings::UTF8;
+ } else if (type == qpid::amqp::typecodes::SYMBOL_NAME) {
+ em.bodyType = qpid::types::encodings::ASCII;
+ } else {
+ em.bodyType = type;
+ }
+}
+void EncodedMessage::InitialScan::onAmqpValue(const qpid::types::Variant& v, const qpid::amqp::Descriptor*)
+{
+ em.content = v;
+}
+
+void EncodedMessage::InitialScan::onFooter(const qpid::amqp::CharSequence& v, const qpid::amqp::CharSequence&) { em.footer = v; }
+
+}}} // namespace qpid::messaging::amqp
diff --git a/qpid/cpp/src/qpid/messaging/amqp/EncodedMessage.h b/qpid/cpp/src/qpid/messaging/amqp/EncodedMessage.h
new file mode 100644
index 0000000000..241118386c
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/EncodedMessage.h
@@ -0,0 +1,190 @@
+#ifndef QPID_MESSAGING_AMQP_ENCODEDMESSAGE_H
+#define QPID_MESSAGING_AMQP_ENCODEDMESSAGE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/messaging/ImportExport.h"
+
+#include "qpid/amqp/CharSequence.h"
+#include "qpid/amqp/MessageId.h"
+#include "qpid/amqp/MessageReader.h"
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/types/Variant.h"
+#include <boost/optional.hpp>
+
+namespace qpid {
+namespace amqp {
+struct Descriptor;
+}
+namespace messaging {
+class Address;
+class MessageImpl;
+namespace amqp {
+
+/**
+ * Used to 'lazy-decode' an AMQP 1.0 message.
+ *
+ * There are four categories of data item:
+ *
+ * (i) simple, fixed width primitives - priority, ttl, durability,
+ * delivery count - for which lazy-decoding doesn't buy much. These
+ * are decoded unconditionally on an initial scan of the message.
+ *
+ * (ii) standard variable length string properties - subject,
+ * message-id, user-id etc - which require conversion to a std::string
+ * for returning to the application. By delaying the conversion of
+ * these to a std::string we can avoid allocation & copying until it
+ * is actually required. The initial scan of the message merely
+ * records the position of these strings within the raw message data.
+ *
+ * (iii) custom, application defined headers. These form a map, and
+ * again, delaying the creation of that map until it is actually
+ * required can be advantageous. The initial scan of the message merely
+ * records the position of this section within the raw message data.
+ *
+ * (iv) the body content. This may be retreived as a std::string, or
+ * as a char*. Avoiding conversion to the string until it is required
+ * is advantageous. The initial scan of the message merely records the
+ * position of this section within the raw message data.
+ *
+ * At present the Message class only explicitly exposes some of the
+ * standard property and headers defined by AMQP 1.0. The remainder
+ * will have to be accessed through the message 'headers' map, using
+ * the 'x-amqp-' prefix.
+ */
+class EncodedMessage
+{
+ public:
+ QPID_MESSAGING_EXTERN EncodedMessage();
+ QPID_MESSAGING_EXTERN EncodedMessage(size_t);
+ QPID_MESSAGING_EXTERN EncodedMessage(const EncodedMessage&);
+ QPID_MESSAGING_EXTERN ~EncodedMessage();
+
+
+ QPID_MESSAGING_EXTERN size_t getSize() const;
+ QPID_MESSAGING_EXTERN char* getData();
+ QPID_MESSAGING_EXTERN const char* getData() const;
+ QPID_MESSAGING_EXTERN void trim(size_t);
+ QPID_MESSAGING_EXTERN void resize(size_t);
+
+ QPID_MESSAGING_EXTERN void setNestAnnotationsOption(bool);
+ void getReplyTo(qpid::messaging::Address&) const;
+ void getSubject(std::string&) const;
+ void getContentType(std::string&) const;
+ void getMessageId(std::string&) const;
+ void getUserId(std::string&) const;
+ void getCorrelationId(std::string&) const;
+ void populate(qpid::types::Variant::Map&) const;
+ void getBody(std::string&, qpid::types::Variant&) const;
+
+ QPID_MESSAGING_EXTERN void init(qpid::messaging::MessageImpl&);
+ QPID_MESSAGING_EXTERN qpid::amqp::CharSequence getBareMessage() const;
+ qpid::amqp::CharSequence getBody() const;
+ QPID_MESSAGING_EXTERN bool hasHeaderChanged(const qpid::messaging::MessageImpl&) const;
+ private:
+ size_t size;
+ char* data;
+ bool nestAnnotations;
+
+ class InitialScan : public qpid::amqp::MessageReader
+ {
+ public:
+ InitialScan(EncodedMessage& e, qpid::messaging::MessageImpl& m);
+ //header:
+ void onDurable(bool b);
+ void onPriority(uint8_t i);
+ void onTtl(uint32_t i);
+ void onFirstAcquirer(bool b);
+ void onDeliveryCount(uint32_t i);
+ //properties:
+ void onMessageId(uint64_t);
+ void onMessageId(const qpid::amqp::CharSequence&, qpid::types::VariantType);
+ void onUserId(const qpid::amqp::CharSequence& v);
+ void onTo(const qpid::amqp::CharSequence& v);
+ void onSubject(const qpid::amqp::CharSequence& v);
+ void onReplyTo(const qpid::amqp::CharSequence& v);
+ void onCorrelationId(uint64_t);
+ void onCorrelationId(const qpid::amqp::CharSequence&, qpid::types::VariantType);
+ void onContentType(const qpid::amqp::CharSequence& v);
+ void onContentEncoding(const qpid::amqp::CharSequence& v);
+ void onAbsoluteExpiryTime(int64_t i);
+ void onCreationTime(int64_t);
+ void onGroupId(const qpid::amqp::CharSequence&);
+ void onGroupSequence(uint32_t);
+ void onReplyToGroupId(const qpid::amqp::CharSequence&);
+
+ void onApplicationProperties(const qpid::amqp::CharSequence&, const qpid::amqp::CharSequence&);
+ void onDeliveryAnnotations(const qpid::amqp::CharSequence&, const qpid::amqp::CharSequence&);
+ void onMessageAnnotations(const qpid::amqp::CharSequence&, const qpid::amqp::CharSequence&);
+
+ void onData(const qpid::amqp::CharSequence&);
+ void onAmqpSequence(const qpid::amqp::CharSequence&);
+ void onAmqpValue(const qpid::amqp::CharSequence&, const std::string& type, const qpid::amqp::Descriptor*);
+ void onAmqpValue(const qpid::types::Variant&, const qpid::amqp::Descriptor*);
+
+ void onFooter(const qpid::amqp::CharSequence&, const qpid::amqp::CharSequence&);
+ private:
+ EncodedMessage& em;
+ qpid::messaging::MessageImpl& mi;
+ };
+ //header:
+ boost::optional<bool> durable;
+ boost::optional<uint8_t> priority;
+ boost::optional<uint32_t> ttl;
+ boost::optional<bool> firstAcquirer;
+ boost::optional<uint32_t> deliveryCount;
+ //annotations:
+ qpid::amqp::CharSequence deliveryAnnotations;
+ qpid::amqp::CharSequence messageAnnotations;
+
+ qpid::amqp::CharSequence bareMessage;//properties, application-properties and content
+ //properties:
+ qpid::amqp::MessageId messageId;
+ qpid::amqp::CharSequence userId;
+ qpid::amqp::CharSequence to;
+ qpid::amqp::CharSequence subject;
+ qpid::amqp::CharSequence replyTo;
+ qpid::amqp::MessageId correlationId;
+ qpid::amqp::CharSequence contentType;
+ qpid::amqp::CharSequence contentEncoding;
+ boost::optional<int64_t> absoluteExpiryTime;
+ boost::optional<int64_t> creationTime;
+ qpid::amqp::CharSequence groupId;
+ boost::optional<uint32_t> groupSequence;
+ qpid::amqp::CharSequence replyToGroupId;
+ //application-properties:
+ qpid::amqp::CharSequence applicationProperties;
+ //application data:
+ qpid::amqp::CharSequence body;
+ std::string bodyType;
+ qpid::types::Variant content;
+
+ //footer:
+ qpid::amqp::CharSequence footer;
+
+ void init();
+ //not implemented:
+ EncodedMessage& operator=(const EncodedMessage&);
+};
+}}} // namespace qpid::messaging::amqp
+
+#endif /*!QPID_MESSAGING_ENCODEDMESSAGE_H*/
diff --git a/qpid/cpp/src/qpid/messaging/amqp/PnData.cpp b/qpid/cpp/src/qpid/messaging/amqp/PnData.cpp
new file mode 100644
index 0000000000..3309d1a683
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/PnData.cpp
@@ -0,0 +1,246 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "PnData.h"
+#include "qpid/types/encodings.h"
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+
+using types::Variant;
+using namespace types::encodings;
+
+// TODO aconway 2014-11-20: PnData duplicates functionality of qpid::amqp::Encoder,Decoder.
+// Collapse them all into a single proton-based codec.
+
+void PnData::put(const Variant::Map& map)
+{
+ pn_data_put_map(data);
+ pn_data_enter(data);
+ for (Variant::Map::const_iterator i = map.begin(); i != map.end(); ++i) {
+ pn_data_put_string(data, bytes(i->first));
+ put(i->second);
+ }
+ pn_data_exit(data);
+}
+
+void PnData::put(const Variant::List& list)
+{
+ pn_data_put_list(data);
+ pn_data_enter(data);
+ for (Variant::List::const_iterator i = list.begin(); i != list.end(); ++i) {
+ put(*i);
+ }
+ pn_data_exit(data);
+}
+
+void PnData::put(const Variant& value)
+{
+ // Open data descriptors associated with the value.
+ const Variant::List& descriptors = value.getDescriptors();
+ for (Variant::List::const_iterator i = descriptors.begin(); i != descriptors.end(); ++i) {
+ pn_data_put_described(data);
+ pn_data_enter(data);
+ if (i->getType() == types::VAR_STRING)
+ pn_data_put_symbol(data, bytes(i->asString()));
+ else
+ pn_data_put_ulong(data, i->asUint64());
+ }
+
+ // Put the variant value
+ switch (value.getType()) {
+ case qpid::types::VAR_VOID:
+ pn_data_put_null(data);
+ break;
+ case qpid::types::VAR_BOOL:
+ pn_data_put_bool(data, value.asBool());
+ break;
+ case qpid::types::VAR_UINT64:
+ pn_data_put_ulong(data, value.asUint64());
+ break;
+ case qpid::types::VAR_INT64:
+ pn_data_put_long(data, value.asInt64());
+ break;
+ case qpid::types::VAR_DOUBLE:
+ pn_data_put_double(data, value.asDouble());
+ break;
+ case qpid::types::VAR_STRING:
+ if (value.getEncoding() == ASCII)
+ pn_data_put_symbol(data, bytes(value.asString()));
+ else if (value.getEncoding() == BINARY)
+ pn_data_put_binary(data, bytes(value.asString()));
+ else
+ pn_data_put_string(data, bytes(value.asString()));
+ break;
+ case qpid::types::VAR_MAP:
+ put(value.asMap());
+ break;
+ case qpid::types::VAR_LIST:
+ put(value.asList());
+ break;
+ default:
+ break;
+ }
+
+ // Close any descriptors.
+ for (Variant::List::const_iterator i = descriptors.begin(); i != descriptors.end(); ++i)
+ pn_data_exit(data);
+}
+
+bool PnData::get(qpid::types::Variant& value)
+{
+ return get(pn_data_type(data), value);
+}
+
+void PnData::getList(qpid::types::Variant::List& value)
+{
+ size_t count = pn_data_get_list(data);
+ pn_data_enter(data);
+ for (size_t i = 0; i < count && pn_data_next(data); ++i) {
+ qpid::types::Variant e;
+ if (get(e)) value.push_back(e);
+ }
+ pn_data_exit(data);
+}
+
+void PnData::getMap(qpid::types::Variant::Map& value)
+{
+ size_t count = pn_data_get_list(data);
+ pn_data_enter(data);
+ for (size_t i = 0; i < (count/2) && pn_data_next(data); ++i) {
+ std::string key = string(pn_data_get_symbol(data));
+ pn_data_next(data);
+ qpid::types::Variant e;
+ if (get(e)) value[key]= e;
+ }
+ pn_data_exit(data);
+}
+
+void PnData::getArray(qpid::types::Variant::List& value)
+{
+ size_t count = pn_data_get_array(data);
+ pn_type_t type = pn_data_get_array_type(data);
+ pn_data_enter(data);
+ for (size_t i = 0; i < count && pn_data_next(data); ++i) {
+ qpid::types::Variant e;
+ if (get(type, e)) value.push_back(e);
+ }
+ pn_data_exit(data);
+}
+
+bool PnData::get(pn_type_t type, qpid::types::Variant& value)
+{
+ switch (type) {
+ case PN_NULL:
+ if (value.getType() != qpid::types::VAR_VOID) value = qpid::types::Variant();
+ return true;
+ case PN_BOOL:
+ value = pn_data_get_bool(data);
+ return true;
+ case PN_UBYTE:
+ value = pn_data_get_ubyte(data);
+ return true;
+ case PN_BYTE:
+ value = pn_data_get_byte(data);
+ return true;
+ case PN_USHORT:
+ value = pn_data_get_ushort(data);
+ return true;
+ case PN_SHORT:
+ value = pn_data_get_short(data);
+ return true;
+ case PN_UINT:
+ value = pn_data_get_uint(data);
+ return true;
+ case PN_INT:
+ value = pn_data_get_int(data);
+ return true;
+ case PN_CHAR:
+ value = pn_data_get_char(data);
+ return true;
+ case PN_ULONG:
+ value = pn_data_get_ulong(data);
+ return true;
+ case PN_LONG:
+ value = pn_data_get_long(data);
+ return true;
+ case PN_TIMESTAMP:
+ value = pn_data_get_timestamp(data);
+ return true;
+ case PN_FLOAT:
+ value = pn_data_get_float(data);
+ return true;
+ case PN_DOUBLE:
+ value = pn_data_get_double(data);
+ return true;
+ case PN_UUID:
+ value = qpid::types::Uuid(pn_data_get_uuid(data).bytes);
+ return true;
+ case PN_BINARY:
+ value = string(pn_data_get_binary(data));
+ value.setEncoding(qpid::types::encodings::BINARY);
+ return true;
+ case PN_STRING:
+ value = string(pn_data_get_string(data));
+ value.setEncoding(qpid::types::encodings::UTF8);
+ return true;
+ case PN_SYMBOL:
+ value = string(pn_data_get_string(data));
+ value.setEncoding(qpid::types::encodings::ASCII);
+ return true;
+ case PN_LIST:
+ value = qpid::types::Variant::List();
+ getList(value.asList());
+ return true;
+ break;
+ case PN_MAP:
+ value = qpid::types::Variant::Map();
+ getMap(value.asMap());
+ return true;
+ case PN_ARRAY:
+ value = qpid::types::Variant::List();
+ getArray(value.asList());
+ return true;
+ case PN_DESCRIBED:
+ // TODO aconway 2014-11-20: get described values.
+ case PN_DECIMAL32:
+ case PN_DECIMAL64:
+ case PN_DECIMAL128:
+ default:
+ return false;
+ }
+}
+
+pn_bytes_t PnData::bytes(const std::string& s)
+{
+ pn_bytes_t result;
+ result.start = const_cast<char*>(s.data());
+ result.size = s.size();
+ return result;
+}
+
+std::string PnData::string(const pn_bytes_t& in)
+{
+ return std::string(in.start, in.size);
+}
+
+}}} // namespace qpid::messaging::amqp
diff --git a/qpid/cpp/src/qpid/messaging/amqp/PnData.h b/qpid/cpp/src/qpid/messaging/amqp/PnData.h
new file mode 100644
index 0000000000..b0119f88fd
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/PnData.h
@@ -0,0 +1,61 @@
+#ifndef QPID_MESSAGING_AMQP_PNDATA_H
+#define QPID_MESSAGING_AMQP_PNDATA_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/types/Variant.h"
+extern "C" {
+#include <proton/engine.h>
+}
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+
+/**
+ * Helper class to put/get messaging types to/from pn_data_t.
+ */
+class PnData
+{
+ public:
+ pn_data_t* data;
+
+ PnData(pn_data_t* d) : data(d) {}
+
+ void put(const types::Variant& value);
+ void put(const types::Variant::Map& map);
+ void put(const types::Variant::List& list);
+ void put(int32_t n) { pn_data_put_int(data, n); }
+ void putSymbol(const std::string& symbol) { pn_data_put_symbol(data, bytes(symbol)); }
+
+ bool get(pn_type_t type, types::Variant& value);
+ bool get(types::Variant& value);
+ void getList(types::Variant::List& value);
+ void getMap(types::Variant::Map& value);
+ void getArray(types::Variant::List& value);
+
+ static pn_bytes_t bytes(const std::string&);
+ static std::string string(const pn_bytes_t&);
+};
+}}} // namespace messaging::amqp
+
+#endif /*!QPID_MESSAGING_AMQP_PNDATA_H*/
diff --git a/qpid/cpp/src/qpid/messaging/amqp/ReceiverContext.cpp b/qpid/cpp/src/qpid/messaging/amqp/ReceiverContext.cpp
new file mode 100644
index 0000000000..a28509b0b1
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/ReceiverContext.cpp
@@ -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.
+ *
+ */
+#include "qpid/messaging/amqp/ReceiverContext.h"
+#include "qpid/messaging/AddressImpl.h"
+#include "qpid/messaging/Duration.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/log/Statement.h"
+extern "C" {
+#include <proton/engine.h>
+}
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+//TODO: proper conversion to wide string for address
+ReceiverContext::ReceiverContext(pn_session_t* session, const std::string& n, const qpid::messaging::Address& a)
+ : name(n),
+ address(a),
+ helper(address),
+ receiver(pn_receiver(session, name.c_str())),
+ capacity(0), used(0) {}
+
+ReceiverContext::~ReceiverContext()
+{
+ if (receiver) pn_link_free(receiver);
+}
+
+void ReceiverContext::setCapacity(uint32_t c)
+{
+ if (c != capacity) {
+ //stop
+ capacity = c;
+ //reissue credit
+ }
+}
+
+uint32_t ReceiverContext::getCapacity()
+{
+ return capacity;
+}
+
+uint32_t ReceiverContext::getAvailable()
+{
+ return pn_link_queued(receiver);
+}
+
+uint32_t ReceiverContext::getUnsettled()
+{
+ assert(pn_link_unsettled(receiver) >= pn_link_queued(receiver));
+ return pn_link_unsettled(receiver) - pn_link_queued(receiver);
+}
+
+void ReceiverContext::close()
+{
+ if (receiver) pn_link_close(receiver);
+}
+
+const std::string& ReceiverContext::getName() const
+{
+ return name;
+}
+
+const std::string& ReceiverContext::getSource() const
+{
+ return address.getName();
+}
+void ReceiverContext::verify()
+{
+ pn_terminus_t* source = pn_link_remote_source(receiver);
+ if (!pn_terminus_get_address(source)) {
+ std::string msg("No such source : ");
+ msg += getSource();
+ QPID_LOG(debug, msg);
+ throw qpid::messaging::NotFound(msg);
+ } else if (AddressImpl::isTemporary(address)) {
+ address.setName(pn_terminus_get_address(source));
+ QPID_LOG(debug, "Dynamic source name set to " << address.getName());
+ }
+ helper.checkAssertion(source, AddressHelper::FOR_RECEIVER);
+}
+void ReceiverContext::configure()
+{
+ if (receiver) configure(pn_link_source(receiver));
+}
+void ReceiverContext::configure(pn_terminus_t* source)
+{
+ helper.configure(receiver, source, AddressHelper::FOR_RECEIVER);
+ std::string option;
+ if (helper.getLinkTarget(option)) {
+ pn_terminus_set_address(pn_link_target(receiver), option.c_str());
+ } else {
+ pn_terminus_set_address(pn_link_target(receiver), pn_terminus_get_address(pn_link_source(receiver)));
+ }
+}
+
+Address ReceiverContext::getAddress() const
+{
+ return address;
+}
+
+void ReceiverContext::reset(pn_session_t* session)
+{
+ receiver = session ? pn_receiver(session, name.c_str()) : 0;
+ if (receiver) configure();
+}
+
+bool ReceiverContext::hasCurrent()
+{
+ return receiver && pn_link_current(receiver);
+}
+
+bool ReceiverContext::wakeupToIssueCredit()
+{
+ if (++used >= (capacity/2)) {
+ used = 0;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+}}} // namespace qpid::messaging::amqp
diff --git a/qpid/cpp/src/qpid/messaging/amqp/ReceiverContext.h b/qpid/cpp/src/qpid/messaging/amqp/ReceiverContext.h
new file mode 100644
index 0000000000..dd1352aecb
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/ReceiverContext.h
@@ -0,0 +1,77 @@
+#ifndef QPID_MESSAGING_AMQP_RECEIVERCONTEXT_H
+#define QPID_MESSAGING_AMQP_RECEIVERCONTEXT_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/messaging/Address.h"
+#include "qpid/messaging/amqp/AddressHelper.h"
+#include <string>
+#include "qpid/sys/AtomicCount.h"
+#include "qpid/sys/IntegerTypes.h"
+
+struct pn_link_t;
+struct pn_session_t;
+struct pn_terminus_t;
+
+namespace qpid {
+namespace messaging {
+
+class Duration;
+class Message;
+
+namespace amqp {
+
+/**
+ *
+ */
+class ReceiverContext
+{
+ public:
+ ReceiverContext(pn_session_t* session, const std::string& name, const qpid::messaging::Address& source);
+ virtual ~ReceiverContext();
+ void reset(pn_session_t* session);
+ void setCapacity(uint32_t);
+ uint32_t getCapacity();
+ uint32_t getAvailable();
+ uint32_t getUnsettled();
+ void attach();
+ void close();
+ const std::string& getName() const;
+ const std::string& getSource() const;
+ void configure();
+ void verify();
+ Address getAddress() const;
+ bool hasCurrent();
+ private:
+ friend class ConnectionContext;
+ const std::string name;
+ Address address;
+ AddressHelper helper;
+ pn_link_t* receiver;
+ uint32_t capacity;
+ uint32_t used;
+ qpid::sys::AtomicCount fetching;
+ void configure(pn_terminus_t*);
+ bool wakeupToIssueCredit();
+};
+}}} // namespace qpid::messaging::amqp
+
+#endif /*!QPID_MESSAGING_AMQP_RECEIVERCONTEXT_H*/
diff --git a/qpid/cpp/src/qpid/messaging/amqp/ReceiverHandle.cpp b/qpid/cpp/src/qpid/messaging/amqp/ReceiverHandle.cpp
new file mode 100644
index 0000000000..bd7082079c
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/ReceiverHandle.cpp
@@ -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.
+ *
+ */
+#include "ReceiverHandle.h"
+#include "ConnectionContext.h"
+#include "SessionContext.h"
+#include "SessionHandle.h"
+#include "ReceiverContext.h"
+#include "qpid/messaging/Duration.h"
+#include "qpid/messaging/exceptions.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/messaging/Session.h"
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+
+ReceiverHandle::ReceiverHandle(boost::shared_ptr<ConnectionContext> c,
+ boost::shared_ptr<SessionContext> s,
+ boost::shared_ptr<ReceiverContext> r
+) : connection(c), session(s), receiver(r) {}
+
+
+bool ReceiverHandle::get(qpid::messaging::Message& message, qpid::messaging::Duration timeout)
+{
+ return connection->get(session, receiver, message, timeout);
+}
+
+qpid::messaging::Message ReceiverHandle::get(qpid::messaging::Duration timeout)
+{
+ qpid::messaging::Message result;
+ if (!get(result, timeout)) throw qpid::messaging::NoMessageAvailable();
+ return result;
+}
+
+bool ReceiverHandle::fetch(qpid::messaging::Message& message, qpid::messaging::Duration timeout)
+{
+ return connection->fetch(session, receiver, message, timeout);
+}
+
+qpid::messaging::Message ReceiverHandle::fetch(qpid::messaging::Duration timeout)
+{
+ qpid::messaging::Message result;
+ if (!fetch(result, timeout)) throw qpid::messaging::NoMessageAvailable();
+ return result;
+}
+
+void ReceiverHandle::setCapacity(uint32_t capacity)
+{
+ connection->setCapacity(receiver, capacity);
+}
+
+uint32_t ReceiverHandle::getCapacity()
+{
+ return connection->getCapacity(receiver);
+}
+
+uint32_t ReceiverHandle::getAvailable()
+{
+ return connection->getAvailable(receiver);
+}
+
+uint32_t ReceiverHandle::getUnsettled()
+{
+ return connection->getUnsettled(receiver);
+}
+
+void ReceiverHandle::close()
+{
+ connection->detach(session, receiver);
+}
+
+const std::string& ReceiverHandle::getName() const
+{
+ return receiver->getName();
+}
+
+qpid::messaging::Session ReceiverHandle::getSession() const
+{
+ //create new SessionHandle instance; i.e. create new handle that shares the same context
+ return qpid::messaging::Session(new SessionHandle(connection, session));
+}
+
+bool ReceiverHandle::isClosed() const
+{
+ return connection->isClosed(session, receiver);
+}
+
+Address ReceiverHandle::getAddress() const
+{
+ return receiver->getAddress();
+}
+
+}}} // namespace qpid::messaging::amqp
diff --git a/qpid/cpp/src/qpid/messaging/amqp/ReceiverHandle.h b/qpid/cpp/src/qpid/messaging/amqp/ReceiverHandle.h
new file mode 100644
index 0000000000..08a95fb585
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/ReceiverHandle.h
@@ -0,0 +1,64 @@
+#ifndef QPID_MESSAGING_AMQP_RECEIVERHANDLE_H
+#define QPID_MESSAGING_AMQP_RECEIVERHANDLE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <boost/shared_ptr.hpp>
+#include "qpid/messaging/ReceiverImpl.h"
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+
+class ConnectionContext;
+class SessionContext;
+class ReceiverContext;
+/**
+ *
+ */
+class ReceiverHandle : public qpid::messaging::ReceiverImpl
+{
+ public:
+ ReceiverHandle(boost::shared_ptr<ConnectionContext>,
+ boost::shared_ptr<SessionContext>,
+ boost::shared_ptr<ReceiverContext>
+ );
+ bool get(Message& message, qpid::messaging::Duration timeout);
+ qpid::messaging::Message get(qpid::messaging::Duration timeout);
+ bool fetch(Message& message, qpid::messaging::Duration timeout);
+ qpid::messaging::Message fetch(qpid::messaging::Duration timeout);
+ void setCapacity(uint32_t);
+ uint32_t getCapacity();
+ uint32_t getAvailable();
+ uint32_t getUnsettled();
+ void close();
+ const std::string& getName() const;
+ qpid::messaging::Session getSession() const;
+ bool isClosed() const;
+ Address getAddress() const;
+ private:
+ boost::shared_ptr<ConnectionContext> connection;
+ boost::shared_ptr<SessionContext> session;
+ boost::shared_ptr<ReceiverContext> receiver;
+};
+}}} // namespace qpid::messaging::amqp
+
+#endif /*!QPID_MESSAGING_AMQP_RECEIVERHANDLE_H*/
diff --git a/qpid/cpp/src/qpid/messaging/amqp/Sasl.cpp b/qpid/cpp/src/qpid/messaging/amqp/Sasl.cpp
new file mode 100644
index 0000000000..e1c15c2c22
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/Sasl.cpp
@@ -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.
+ *
+ */
+#include "ConnectionContext.h"
+#include "qpid/messaging/amqp/Sasl.h"
+#include "qpid/messaging/exceptions.h"
+#include "qpid/sys/SecurityLayer.h"
+#include "qpid/log/Statement.h"
+#include "qpid/Sasl.h"
+#include "qpid/SaslFactory.h"
+#include "qpid/StringUtils.h"
+#include <sstream>
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+
+Sasl::Sasl(const std::string& id, ConnectionContext& c, const std::string& hostname_)
+ : qpid::amqp::SaslClient(id), context(c),
+ sasl(qpid::SaslFactory::getInstance().create(c.username, c.password, c.service, hostname_, c.minSsf, c.maxSsf, false)),
+ hostname(hostname_), readHeader(true), writeHeader(true), haveOutput(false), state(NONE) {}
+
+Sasl::~Sasl() {}
+std::size_t Sasl::decode(const char* buffer, std::size_t size)
+{
+ size_t decoded = 0;
+ if (readHeader) {
+ decoded += readProtocolHeader(buffer, size);
+ readHeader = !decoded;
+ }
+ if (state == NONE && decoded < size) {
+ decoded += read(buffer + decoded, size - decoded);
+ }
+ QPID_LOG(trace, id << " Sasl::decode(" << size << "): " << decoded);
+ return decoded;
+}
+
+std::size_t Sasl::encode(char* buffer, std::size_t size)
+{
+ size_t encoded = 0;
+ if (writeHeader) {
+ encoded += writeProtocolHeader(buffer, size);
+ writeHeader = !encoded;
+ }
+ if (encoded < size) {
+ encoded += write(buffer + encoded, size - encoded);
+ }
+ haveOutput = (encoded == size);
+ QPID_LOG(trace, id << " Sasl::encode(" << size << "): " << encoded);
+ return encoded;
+}
+
+bool Sasl::canEncode()
+{
+ QPID_LOG(trace, id << " Sasl::canEncode(): " << writeHeader << " || " << haveOutput);
+ return writeHeader || haveOutput;
+}
+
+void Sasl::mechanisms(const std::string& offered)
+{
+ QPID_LOG_CAT(debug, protocol, id << " Received SASL-MECHANISMS(" << offered << ")");
+ std::string response;
+
+ std::string mechanisms;
+ if (context.mechanism.size()) {
+ std::vector<std::string> allowed = split(context.mechanism, " ");
+ std::vector<std::string> supported = split(offered, " ");
+ std::stringstream intersection;
+ for (std::vector<std::string>::const_iterator i = allowed.begin(); i != allowed.end(); ++i) {
+ if (std::find(supported.begin(), supported.end(), *i) != supported.end()) {
+ intersection << *i << " ";
+ }
+ }
+ mechanisms = intersection.str();
+ } else {
+ mechanisms = offered;
+ }
+
+ try {
+ if (sasl->start(mechanisms, response, context.getTransportSecuritySettings())) {
+ init(sasl->getMechanism(), &response, hostname.size() ? &hostname : 0);
+ } else {
+ init(sasl->getMechanism(), 0, hostname.size() ? &hostname : 0);
+ }
+ haveOutput = true;
+ context.activateOutput();
+ } catch (const std::exception& e) {
+ failed(e.what());
+ }
+}
+void Sasl::challenge(const std::string& challenge)
+{
+ QPID_LOG_CAT(debug, protocol, id << " Received SASL-CHALLENGE(" << challenge.size() << " bytes)");
+ try {
+ std::string r = sasl->step(challenge);
+ response(&r);
+ haveOutput = true;
+ context.activateOutput();
+ } catch (const std::exception& e) {
+ failed(e.what());
+ }
+}
+namespace {
+const std::string EMPTY;
+}
+void Sasl::challenge()
+{
+ QPID_LOG_CAT(debug, protocol, id << " Received SASL-CHALLENGE(null)");
+ try {
+ std::string r = sasl->step(EMPTY);
+ response(&r);
+ } catch (const std::exception& e) {
+ failed(e.what());
+ }
+}
+void Sasl::outcome(uint8_t result, const std::string& extra)
+{
+ QPID_LOG_CAT(debug, protocol, id << " Received SASL-OUTCOME(" << result << ", " << extra << ")");
+ outcome(result);
+}
+void Sasl::outcome(uint8_t result)
+{
+ QPID_LOG_CAT(debug, protocol, id << " Received SASL-OUTCOME(" << result << ")");
+ if (result) state = FAILED;
+ else state = SUCCEEDED;
+
+ securityLayer = sasl->getSecurityLayer(context.maxFrameSize);
+ if (securityLayer.get()) {
+ context.initSecurityLayer(*securityLayer);
+ }
+ context.activateOutput();
+}
+
+bool Sasl::stopReading()
+{
+ return state != NONE;
+}
+
+qpid::sys::Codec* Sasl::getSecurityLayer()
+{
+ return securityLayer.get();
+}
+
+namespace {
+const std::string DEFAULT_ERROR("Authentication failed");
+}
+
+bool Sasl::authenticated()
+{
+ switch (state) {
+ case SUCCEEDED: return true;
+ case FAILED: throw qpid::messaging::AuthenticationFailure(error.size() ? error : DEFAULT_ERROR);
+ case NONE: default: return false;
+ }
+}
+
+void Sasl::failed(const std::string& text)
+{
+ QPID_LOG_CAT(info, client, id << " Failure during authentication: " << text);
+ error = text;
+ state = FAILED;
+}
+
+std::string Sasl::getAuthenticatedUsername()
+{
+ return sasl->getUserId();
+}
+
+}}} // namespace qpid::messaging::amqp
diff --git a/qpid/cpp/src/qpid/messaging/amqp/Sasl.h b/qpid/cpp/src/qpid/messaging/amqp/Sasl.h
new file mode 100644
index 0000000000..a836e2e465
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/Sasl.h
@@ -0,0 +1,77 @@
+#ifndef QPID_MESSAGING_AMQP_SASL_H
+#define QPID_MESSAGING_AMQP_SASL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/sys/Codec.h"
+#include "qpid/amqp/SaslClient.h"
+#include <memory>
+
+namespace qpid {
+class Sasl;
+namespace sys {
+class SecurityLayer;
+}
+namespace messaging {
+struct ConnectionOptions;
+namespace amqp {
+class ConnectionContext;
+
+/**
+ *
+ */
+class Sasl : public qpid::sys::Codec, qpid::amqp::SaslClient
+{
+ public:
+ Sasl(const std::string& id, ConnectionContext& context, const std::string& hostname);
+ ~Sasl();
+ std::size_t decode(const char* buffer, std::size_t size);
+ std::size_t encode(char* buffer, std::size_t size);
+ bool canEncode();
+
+ bool authenticated();
+ qpid::sys::Codec* getSecurityLayer();
+ std::string getAuthenticatedUsername();
+ private:
+ ConnectionContext& context;
+ std::auto_ptr<qpid::Sasl> sasl;
+ std::string hostname;
+ bool readHeader;
+ bool writeHeader;
+ bool haveOutput;
+ enum {
+ NONE, FAILED, SUCCEEDED
+ } state;
+ std::auto_ptr<qpid::sys::SecurityLayer> securityLayer;
+ std::string error;
+
+ void mechanisms(const std::string&);
+ void challenge(const std::string&);
+ void challenge(); //null != empty string
+ void outcome(uint8_t result, const std::string&);
+ void outcome(uint8_t result);
+ void failed(const std::string&);
+ protected:
+ bool stopReading();
+};
+}}} // namespace qpid::messaging::amqp
+
+#endif /*!QPID_MESSAGING_AMQP_SASL_H*/
diff --git a/qpid/cpp/src/qpid/messaging/amqp/SenderContext.cpp b/qpid/cpp/src/qpid/messaging/amqp/SenderContext.cpp
new file mode 100644
index 0000000000..5289fbdf9b
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/SenderContext.cpp
@@ -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.
+ *
+ */
+#include "SenderContext.h"
+#include "Transaction.h"
+#include "EncodedMessage.h"
+#include "PnData.h"
+#include "util.h"
+#include "qpid/messaging/AddressImpl.h"
+#include "qpid/messaging/exceptions.h"
+#include "qpid/Exception.h"
+#include "qpid/amqp/descriptors.h"
+#include "qpid/amqp/MapHandler.h"
+#include "qpid/amqp/MessageEncoder.h"
+#include "qpid/messaging/exceptions.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/messaging/MessageImpl.h"
+#include "qpid/log/Statement.h"
+#include "config.h"
+extern "C" {
+#include <proton/engine.h>
+}
+#include <boost/shared_ptr.hpp>
+#include <string.h>
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+
+//TODO: proper conversion to wide string for address
+SenderContext::SenderContext(pn_session_t* session, const std::string& n,
+ const qpid::messaging::Address& a,
+ bool setToOnSend_,
+ const CoordinatorPtr& coord)
+ : sender(pn_sender(session, n.c_str())),
+ name(n),
+ address(a),
+ helper(address),
+ nextId(0), capacity(50), unreliable(helper.isUnreliable()),
+ setToOnSend(setToOnSend_),
+ transaction(coord)
+{}
+
+SenderContext::~SenderContext()
+{
+ if (sender) pn_link_free(sender);
+}
+
+void SenderContext::close()
+{
+ if (sender) pn_link_close(sender);
+}
+
+void SenderContext::setCapacity(uint32_t c)
+{
+ if (c < deliveries.size()) throw qpid::messaging::SenderError("Desired capacity is less than unsettled message count!");
+ capacity = c;
+}
+
+uint32_t SenderContext::getCapacity()
+{
+ return capacity;
+}
+
+uint32_t SenderContext::getUnsettled()
+{
+ return processUnsettled(true/*always allow retrieval of unsettled count, even if link has failed*/);
+}
+
+const std::string& SenderContext::getName() const
+{
+ return name;
+}
+
+const std::string& SenderContext::getTarget() const
+{
+ return address.getName();
+}
+
+bool SenderContext::send(const qpid::messaging::Message& message, SenderContext::Delivery** out)
+{
+ resend();//if there are any messages needing to be resent at the front of the queue, send them first
+ if (processUnsettled(false) < capacity && pn_link_credit(sender)) {
+ types::Variant state;
+ if (transaction)
+ state = transaction->getSendState();
+ if (unreliable) {
+ Delivery delivery(nextId++);
+ delivery.encode(MessageImplAccess::get(message), address, setToOnSend);
+ delivery.send(sender, unreliable, state);
+ *out = 0;
+ return true;
+ } else {
+ deliveries.push_back(Delivery(nextId++));
+ try {
+ Delivery& delivery = deliveries.back();
+ delivery.encode(MessageImplAccess::get(message), address, setToOnSend);
+ delivery.send(sender, unreliable, state);
+ *out = &delivery;
+ return true;
+ } catch (const std::exception& e) {
+ deliveries.pop_back();
+ --nextId;
+ throw SendError(e.what());
+ }
+ }
+ } else {
+ return false;
+ }
+}
+
+void SenderContext::check()
+{
+ if (pn_link_state(sender) & PN_REMOTE_CLOSED && !(pn_link_state(sender) & PN_LOCAL_CLOSED)) {
+ std::string text = get_error_string(pn_link_remote_condition(sender), "Link detached by peer");
+ pn_link_close(sender);
+ throw qpid::messaging::LinkError(text);
+ }
+}
+
+uint32_t SenderContext::processUnsettled(bool silent)
+{
+ if (!silent) {
+ check();
+ }
+ //remove messages from front of deque once peer has confirmed receipt
+ while (!deliveries.empty() && deliveries.front().delivered() && !(pn_link_state(sender) & PN_REMOTE_CLOSED)) {
+ deliveries.front().settle();
+ deliveries.pop_front();
+ }
+ return deliveries.size();
+}
+namespace {
+const std::string X_AMQP("x-amqp-");
+const std::string X_AMQP_FIRST_ACQUIRER("x-amqp-first-acquirer");
+const std::string X_AMQP_DELIVERY_COUNT("x-amqp-delivery-count");
+const std::string X_AMQP_0_10_APP_ID("x-amqp-0-10.app-id");
+
+class HeaderAdapter : public qpid::amqp::MessageEncoder::Header
+{
+ public:
+ HeaderAdapter(const qpid::messaging::MessageImpl& impl) : msg(impl), headers(msg.getHeaders()) {}
+ virtual bool isDurable() const
+ {
+ return msg.isDurable();
+ }
+ virtual uint8_t getPriority() const
+ {
+ return msg.getPriority();
+ }
+ virtual bool hasTtl() const
+ {
+ return msg.getTtl();
+ }
+ virtual uint32_t getTtl() const
+ {
+ return msg.getTtl();
+ }
+ virtual bool isFirstAcquirer() const
+ {
+ qpid::types::Variant::Map::const_iterator i = headers.find(X_AMQP_FIRST_ACQUIRER);
+ if (i != headers.end()) {
+ return i->second;
+ } else {
+ return false;
+ }
+ }
+ virtual uint32_t getDeliveryCount() const
+ {
+ qpid::types::Variant::Map::const_iterator i = headers.find(X_AMQP_DELIVERY_COUNT);
+ if (i != headers.end()) {
+ return i->second;
+ } else {
+ return msg.isRedelivered() ? 1 : 0;
+ }
+ }
+ private:
+ const qpid::messaging::MessageImpl& msg;
+ const qpid::types::Variant::Map& headers;
+};
+const std::string EMPTY;
+const std::string FORWARD_SLASH("/");
+const std::string X_AMQP_TO("x-amqp-to");
+const std::string X_AMQP_CONTENT_ENCODING("x-amqp-content-encoding");
+const std::string X_AMQP_CREATION_TIME("x-amqp-creation-time");
+const std::string X_AMQP_ABSOLUTE_EXPIRY_TIME("x-amqp-absolute-expiry-time");
+const std::string X_AMQP_GROUP_ID("x-amqp-group-id");
+const std::string X_AMQP_GROUP_SEQUENCE("x-amqp-group-sequence");
+const std::string X_AMQP_REPLY_TO_GROUP_ID("x-amqp-reply-to-group-id");
+const std::string X_AMQP_MESSAGE_ANNOTATIONS("x-amqp-message-annotations");
+const std::string X_AMQP_DELIVERY_ANNOTATIONS("x-amqp-delivery-annotations");
+
+class PropertiesAdapter : public qpid::amqp::MessageEncoder::Properties
+{
+ public:
+ PropertiesAdapter(const qpid::messaging::MessageImpl& impl, const std::string& s, const std::string& t) : msg(impl), headers(msg.getHeaders()), subject(s), to(t) {}
+ bool hasMessageId() const
+ {
+ return getMessageId().size();
+ }
+ std::string getMessageId() const
+ {
+ return msg.getMessageId();
+ }
+
+ bool hasUserId() const
+ {
+ return getUserId().size();
+ }
+
+ std::string getUserId() const
+ {
+ return msg.getUserId();
+ }
+
+ bool hasTo() const
+ {
+ return hasHeader(X_AMQP_TO) || !to.empty();
+ }
+
+ std::string getTo() const
+ {
+ qpid::types::Variant::Map::const_iterator i = headers.find(X_AMQP_TO);
+ if (i == headers.end()) return to;
+ else return i->second;
+ }
+
+ bool hasSubject() const
+ {
+ return subject.size() || getSubject().size();
+ }
+
+ std::string getSubject() const
+ {
+ return subject.size() ? subject : msg.getSubject();
+ }
+
+ bool hasReplyTo() const
+ {
+ return msg.getReplyTo();
+ }
+
+ std::string getReplyTo() const
+ {
+ Address a = msg.getReplyTo();
+ if (a.getSubject().size()) {
+ return a.getName() + FORWARD_SLASH + a.getSubject();
+ } else {
+ return a.getName();
+ }
+ }
+
+ bool hasCorrelationId() const
+ {
+ return getCorrelationId().size();
+ }
+
+ std::string getCorrelationId() const
+ {
+ return msg.getCorrelationId();
+ }
+
+ bool hasContentType() const
+ {
+ return getContentType().size();
+ }
+
+ std::string getContentType() const
+ {
+ return msg.getContentType();
+ }
+
+ bool hasContentEncoding() const
+ {
+ return hasHeader(X_AMQP_CONTENT_ENCODING);
+ }
+
+ std::string getContentEncoding() const
+ {
+ return headers.find(X_AMQP_CONTENT_ENCODING)->second;
+ }
+
+ bool hasAbsoluteExpiryTime() const
+ {
+ return hasHeader(X_AMQP_ABSOLUTE_EXPIRY_TIME);
+ }
+
+ int64_t getAbsoluteExpiryTime() const
+ {
+ return headers.find(X_AMQP_ABSOLUTE_EXPIRY_TIME)->second;
+ }
+
+ bool hasCreationTime() const
+ {
+ return hasHeader(X_AMQP_CREATION_TIME);
+ }
+
+ int64_t getCreationTime() const
+ {
+ return headers.find(X_AMQP_CREATION_TIME)->second;
+ }
+
+ bool hasGroupId() const
+ {
+ return hasHeader(X_AMQP_GROUP_ID);
+ }
+
+ std::string getGroupId() const
+ {
+ return headers.find(X_AMQP_GROUP_ID)->second;
+ }
+
+ bool hasGroupSequence() const
+ {
+ return hasHeader(X_AMQP_GROUP_SEQUENCE);
+ }
+
+ uint32_t getGroupSequence() const
+ {
+ return headers.find(X_AMQP_GROUP_SEQUENCE)->second;
+ }
+
+ bool hasReplyToGroupId() const
+ {
+ return hasHeader(X_AMQP_REPLY_TO_GROUP_ID);
+ }
+
+ std::string getReplyToGroupId() const
+ {
+ return headers.find(X_AMQP_REPLY_TO_GROUP_ID)->second;
+ }
+ private:
+ const qpid::messaging::MessageImpl& msg;
+ const qpid::types::Variant::Map& headers;
+ const std::string subject;
+ const std::string to;
+
+ bool hasHeader(const std::string& key) const
+ {
+ return headers.find(key) != headers.end();
+ }
+};
+
+bool startsWith(const std::string& input, const std::string& pattern)
+{
+ if (input.size() < pattern.size()) return false;
+ for (std::string::const_iterator b = pattern.begin(), a = input.begin(); b != pattern.end(); ++b, ++a) {
+ if (*a != *b) return false;
+ }
+ return true;
+}
+class ApplicationPropertiesAdapter : public qpid::amqp::MessageEncoder::ApplicationProperties
+{
+ public:
+ ApplicationPropertiesAdapter(const qpid::types::Variant::Map& h) : headers(h) {}
+ void handle(qpid::amqp::MapHandler& h) const
+ {
+ for (qpid::types::Variant::Map::const_iterator i = headers.begin(); i != headers.end(); ++i) {
+ //strip out values with special keys as they are sent in standard fields
+ if (!startsWith(i->first, X_AMQP) || i->first == X_AMQP_0_10_APP_ID) {
+ qpid::amqp::CharSequence key(convert(i->first));
+ switch (i->second.getType()) {
+ case qpid::types::VAR_VOID:
+ h.handleVoid(key);
+ break;
+ case qpid::types::VAR_BOOL:
+ h.handleBool(key, i->second);
+ break;
+ case qpid::types::VAR_UINT8:
+ h.handleUint8(key, i->second);
+ break;
+ case qpid::types::VAR_UINT16:
+ h.handleUint16(key, i->second);
+ break;
+ case qpid::types::VAR_UINT32:
+ h.handleUint32(key, i->second);
+ break;
+ case qpid::types::VAR_UINT64:
+ h.handleUint64(key, i->second);
+ break;
+ case qpid::types::VAR_INT8:
+ h.handleInt8(key, i->second);
+ break;
+ case qpid::types::VAR_INT16:
+ h.handleInt16(key, i->second);
+ break;
+ case qpid::types::VAR_INT32:
+ h.handleInt32(key, i->second);
+ break;
+ case qpid::types::VAR_INT64:
+ h.handleInt64(key, i->second);
+ break;
+ case qpid::types::VAR_FLOAT:
+ h.handleFloat(key, i->second);
+ break;
+ case qpid::types::VAR_DOUBLE:
+ h.handleDouble(key, i->second);
+ break;
+ case qpid::types::VAR_STRING:
+ h.handleString(key, convert(i->second), convert(i->second.getEncoding()));
+ break;
+ case qpid::types::VAR_UUID:
+ QPID_LOG(warning, "Skipping UUID in application properties; not yet handled correctly.");
+ break;
+ case qpid::types::VAR_MAP:
+ case qpid::types::VAR_LIST:
+ QPID_LOG(warning, "Skipping nested list and map; not allowed in application properties.");
+ break;
+ }
+ }
+ }
+ }
+ private:
+ const qpid::types::Variant::Map& headers;
+
+ static qpid::amqp::CharSequence convert(const std::string& in)
+ {
+ qpid::amqp::CharSequence out;
+ out.data = in.data();
+ out.size = in.size();
+ return out;
+ }
+};
+
+bool changedSubject(const qpid::messaging::MessageImpl& msg, const qpid::messaging::Address& address)
+{
+ return address.getSubject().size() && address.getSubject() != msg.getSubject();
+}
+
+}
+
+SenderContext::Delivery::Delivery(int32_t i) : id(i), token(0), presettled(false) {}
+
+void SenderContext::Delivery::reset()
+{
+ token = 0;
+}
+
+void SenderContext::Delivery::encode(const qpid::messaging::MessageImpl& msg, const qpid::messaging::Address& address, bool setToField)
+{
+ try {
+ boost::shared_ptr<const EncodedMessage> original = msg.getEncoded();
+
+ if (original && !changedSubject(msg, address)) { //still have the content as received, send at least the bare message unaltered
+ //do we need to alter the header? are durable, priority, ttl, first-acquirer, delivery-count different from what was received?
+ if (original->hasHeaderChanged(msg)) {
+ //since as yet have no annotations, just write the revised header then the rest of the message as received
+ encoded.resize(16/*max header size*/ + original->getBareMessage().size);
+ qpid::amqp::MessageEncoder encoder(encoded.getData(), encoded.getSize());
+ HeaderAdapter header(msg);
+ encoder.writeHeader(header);
+ ::memcpy(encoded.getData() + encoder.getPosition(), original->getBareMessage().data, original->getBareMessage().size);
+ } else {
+ //since as yet have no annotations, if the header hasn't
+ //changed and we still have the original bare message, can
+ //send the entire content as is
+ encoded.resize(original->getSize());
+ ::memcpy(encoded.getData(), original->getData(), original->getSize());
+ }
+ } else {
+ HeaderAdapter header(msg);
+ PropertiesAdapter properties(msg, address.getSubject(), setToField ? address.getName() : EMPTY);
+ ApplicationPropertiesAdapter applicationProperties(msg.getHeaders());
+ //compute size:
+ size_t contentSize = qpid::amqp::MessageEncoder::getEncodedSize(header)
+ + qpid::amqp::MessageEncoder::getEncodedSize(properties)
+ + qpid::amqp::MessageEncoder::getEncodedSize(applicationProperties);
+ if (msg.getContent().isVoid()) {
+ contentSize += qpid::amqp::MessageEncoder::getEncodedSizeForContent(msg.getBytes());
+ } else {
+ contentSize += qpid::amqp::MessageEncoder::getEncodedSizeForValue(msg.getContent()) + 3/*descriptor*/;
+ }
+ encoded.resize(contentSize);
+ QPID_LOG(debug, "Sending message, buffer is " << encoded.getSize() << " bytes")
+ qpid::amqp::MessageEncoder encoder(encoded.getData(), encoded.getSize());
+ //write header:
+ encoder.writeHeader(header);
+ //write delivery-annotations, write message-annotations (none yet supported)
+ //write properties
+ encoder.writeProperties(properties);
+ //write application-properties
+ encoder.writeApplicationProperties(applicationProperties);
+ //write body
+ if (!msg.getContent().isVoid()) {
+ //write as AmqpValue
+ encoder.writeValue(msg.getContent(), &qpid::amqp::message::AMQP_VALUE);
+ } else if (msg.getBytes().size()) {
+ encoder.writeBinary(msg.getBytes(), &qpid::amqp::message::DATA);//structured content not yet directly supported
+ }
+ if (encoder.getPosition() < encoded.getSize()) {
+ QPID_LOG(debug, "Trimming buffer from " << encoded.getSize() << " to " << encoder.getPosition());
+ encoded.trim(encoder.getPosition());
+ }
+ //write footer (no annotations yet supported)
+ }
+ } catch (const qpid::Exception& e) {
+ throw SendError(e.what());
+ }
+}
+
+void SenderContext::Delivery::send(pn_link_t* sender, bool unreliable, const types::Variant& state)
+{
+ pn_delivery_tag_t tag;
+ tag.size = sizeof(id);
+#ifdef NO_PROTON_DELIVERY_TAG_T
+ tag.start = reinterpret_cast<const char*>(&id);
+#else
+ tag.bytes = reinterpret_cast<const char*>(&id);
+#endif
+ token = pn_delivery(sender, tag);
+ if (!state.isVoid()) { // Add transaction state
+ PnData data(pn_disposition_data(pn_delivery_local(token)));
+ data.put(state);
+ pn_delivery_update(token, qpid::amqp::transaction::TRANSACTIONAL_STATE_CODE);
+ }
+ pn_link_send(sender, encoded.getData(), encoded.getSize());
+ if (unreliable) {
+ pn_delivery_settle(token);
+ presettled = true;
+ }
+ pn_link_advance(sender);
+}
+
+bool SenderContext::Delivery::sent() const
+{
+ return presettled || token;
+}
+bool SenderContext::Delivery::delivered()
+{
+ if (presettled || (token && (pn_delivery_remote_state(token) || pn_delivery_settled(token)))) {
+ //TODO: need a better means for signalling outcomes other than accepted
+ if (rejected()) {
+ QPID_LOG(warning, "delivery " << id << " was rejected by peer");
+ } else if (!accepted()) {
+ QPID_LOG(info, "delivery " << id << " was not accepted by peer");
+ }
+ return true;
+ } else {
+ return false;
+ }
+}
+bool SenderContext::Delivery::accepted()
+{
+ return pn_delivery_remote_state(token) == PN_ACCEPTED;
+}
+bool SenderContext::Delivery::rejected()
+{
+ return pn_delivery_remote_state(token) == PN_REJECTED;
+}
+
+std::string SenderContext::Delivery::error()
+{
+ pn_condition_t *condition = pn_disposition_condition(pn_delivery_remote(token));
+ return (condition && pn_condition_is_set(condition)) ?
+ Msg() << get_error_string(condition, std::string(), std::string()) :
+ std::string();
+}
+
+void SenderContext::Delivery::settle()
+{
+ pn_delivery_settle(token);
+}
+void SenderContext::verify()
+{
+ pn_terminus_t* target = pn_link_remote_target(sender);
+ if (!pn_terminus_get_address(target)) {
+ std::string msg("No such target : ");
+ msg += getTarget();
+ QPID_LOG(debug, msg);
+ throw qpid::messaging::NotFound(msg);
+ } else if (AddressImpl::isTemporary(address)) {
+ address.setName(pn_terminus_get_address(target));
+ QPID_LOG(debug, "Dynamic target name set to " << address.getName());
+ }
+
+ helper.checkAssertion(target, AddressHelper::FOR_SENDER);
+}
+
+void SenderContext::configure()
+{
+ if (sender) configure(pn_link_target(sender));
+}
+
+void SenderContext::configure(pn_terminus_t* target)
+{
+ helper.configure(sender, target, AddressHelper::FOR_SENDER);
+ std::string option;
+ if (helper.getLinkSource(option)) {
+ pn_terminus_set_address(pn_link_source(sender), option.c_str());
+ } else {
+ pn_terminus_set_address(pn_link_source(sender), pn_terminus_get_address(pn_link_target(sender)));
+ }
+}
+
+bool SenderContext::settled()
+{
+ return processUnsettled(false) == 0;
+}
+
+bool SenderContext::closed()
+{
+ return pn_link_state(sender) & PN_LOCAL_CLOSED;
+}
+
+Address SenderContext::getAddress() const
+{
+ return address;
+}
+
+
+void SenderContext::reset(pn_session_t* session)
+{
+ sender = session ? pn_sender(session, name.c_str()) : 0;
+ if (sender) configure();
+ for (Deliveries::iterator i = deliveries.begin(); i != deliveries.end(); ++i)
+ i->reset();
+}
+
+void SenderContext::resend()
+{
+ for (Deliveries::iterator i = deliveries.begin(); i != deliveries.end() && pn_link_credit(sender) && !i->sent(); ++i) {
+ i->send(sender, false/*only resend reliable transfers*/);
+ }
+}
+
+}}} // namespace qpid::messaging::amqp
diff --git a/qpid/cpp/src/qpid/messaging/amqp/SenderContext.h b/qpid/cpp/src/qpid/messaging/amqp/SenderContext.h
new file mode 100644
index 0000000000..467a8e0d3d
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/SenderContext.h
@@ -0,0 +1,119 @@
+#ifndef QPID_MESSAGING_AMQP_SENDERCONTEXT_H
+#define QPID_MESSAGING_AMQP_SENDERCONTEXT_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <deque>
+#include <string>
+#include <vector>
+#include <boost/shared_ptr.hpp>
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/messaging/Address.h"
+#include "qpid/messaging/amqp/AddressHelper.h"
+#include "qpid/messaging/amqp/EncodedMessage.h"
+
+struct pn_delivery_t;
+struct pn_link_t;
+struct pn_session_t;
+struct pn_terminus_t;
+
+namespace qpid {
+namespace messaging {
+
+class Message;
+class MessageImpl;
+
+namespace amqp {
+
+class Transaction;
+
+
+class SenderContext
+{
+ public:
+ class Delivery
+ {
+ public:
+ Delivery(int32_t id);
+ void encode(const qpid::messaging::MessageImpl& message, const qpid::messaging::Address&, bool setToField);
+ void send(pn_link_t*, bool unreliable, const types::Variant& state=types::Variant());
+ bool delivered();
+ bool accepted();
+ bool rejected();
+ void settle();
+ void reset();
+ bool sent() const;
+ pn_delivery_t* getToken() const { return token; }
+ std::string error();
+ private:
+ int32_t id;
+ pn_delivery_t* token;
+ EncodedMessage encoded;
+ bool presettled;
+ };
+
+ typedef boost::shared_ptr<Transaction> CoordinatorPtr;
+
+ SenderContext(pn_session_t* session, const std::string& name,
+ const qpid::messaging::Address& target,
+ bool setToOnSend,
+ const CoordinatorPtr& transaction = CoordinatorPtr());
+ virtual ~SenderContext();
+
+ virtual void reset(pn_session_t* session);
+ virtual void close();
+ virtual void setCapacity(uint32_t);
+ virtual uint32_t getCapacity();
+ virtual uint32_t getUnsettled();
+ virtual const std::string& getName() const;
+ virtual const std::string& getTarget() const;
+ virtual bool send(const qpid::messaging::Message& message, Delivery**);
+ virtual void configure();
+ virtual void verify();
+ virtual void check();
+ virtual bool settled();
+ virtual bool closed();
+ virtual Address getAddress() const;
+
+ protected:
+ pn_link_t* sender;
+
+ private:
+ friend class ConnectionContext;
+ typedef std::deque<Delivery> Deliveries;
+
+ const std::string name;
+ qpid::messaging::Address address;
+ AddressHelper helper;
+ int32_t nextId;
+ Deliveries deliveries;
+ uint32_t capacity;
+ bool unreliable;
+ bool setToOnSend;
+ boost::shared_ptr<Transaction> transaction;
+
+ uint32_t processUnsettled(bool silent);
+ void configure(pn_terminus_t*);
+ void resend();
+};
+}}} // namespace qpid::messaging::amqp
+
+#endif /*!QPID_MESSAGING_AMQP_SENDERCONTEXT_H*/
diff --git a/qpid/cpp/src/qpid/messaging/amqp/SenderHandle.cpp b/qpid/cpp/src/qpid/messaging/amqp/SenderHandle.cpp
new file mode 100644
index 0000000000..98f2d34e7d
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/SenderHandle.cpp
@@ -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.
+ *
+ */
+#include "SenderHandle.h"
+#include "ConnectionContext.h"
+#include "SessionContext.h"
+#include "SessionHandle.h"
+#include "SenderContext.h"
+#include "qpid/messaging/Duration.h"
+#include "qpid/messaging/exceptions.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/messaging/Session.h"
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+
+SenderHandle::SenderHandle(boost::shared_ptr<ConnectionContext> c,
+ boost::shared_ptr<SessionContext> s,
+ boost::shared_ptr<SenderContext> sndr
+) : connection(c), session(s), sender(sndr) {}
+
+void SenderHandle::send(const Message& message, bool sync)
+{
+ SenderContext::Delivery* d = 0;
+ connection->send(session, sender, message, sync, &d);
+}
+
+void SenderHandle::close()
+{
+ connection->detach(session, sender);
+}
+
+void SenderHandle::setCapacity(uint32_t capacity)
+{
+ connection->setCapacity(sender, capacity);
+}
+
+uint32_t SenderHandle::getCapacity()
+{
+ return connection->getCapacity(sender);
+}
+
+uint32_t SenderHandle::getUnsettled()
+{
+ return connection->getUnsettled(sender);
+}
+
+const std::string& SenderHandle::getName() const
+{
+ return sender->getName();
+}
+
+qpid::messaging::Session SenderHandle::getSession() const
+{
+ return qpid::messaging::Session(new SessionHandle(connection, session));
+}
+
+Address SenderHandle::getAddress() const
+{
+ return sender->getAddress();
+}
+
+}}} // namespace qpid::messaging::amqp
diff --git a/qpid/cpp/src/qpid/messaging/amqp/SenderHandle.h b/qpid/cpp/src/qpid/messaging/amqp/SenderHandle.h
new file mode 100644
index 0000000000..fab158c1ef
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/SenderHandle.h
@@ -0,0 +1,59 @@
+#ifndef QPID_MESSAGING_AMQP_SENDERHANDLE_H
+#define QPID_MESSAGING_AMQP_SENDERHANDLE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <boost/shared_ptr.hpp>
+#include "qpid/messaging/SenderImpl.h"
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+
+class ConnectionContext;
+class SessionContext;
+class SenderContext;
+/**
+ *
+ */
+class SenderHandle : public qpid::messaging::SenderImpl
+{
+ public:
+ SenderHandle(boost::shared_ptr<ConnectionContext> connection,
+ boost::shared_ptr<SessionContext> session,
+ boost::shared_ptr<SenderContext> sender
+ );
+ void send(const Message& message, bool sync);
+ void close();
+ void setCapacity(uint32_t);
+ uint32_t getCapacity();
+ uint32_t getUnsettled();
+ const std::string& getName() const;
+ Session getSession() const;
+ Address getAddress() const;
+ private:
+ boost::shared_ptr<ConnectionContext> connection;
+ boost::shared_ptr<SessionContext> session;
+ boost::shared_ptr<SenderContext> sender;
+};
+}}} // namespace qpid::messaging::amqp
+
+#endif /*!QPID_MESSAGING_AMQP_SENDERHANDLE_H*/
diff --git a/qpid/cpp/src/qpid/messaging/amqp/SessionContext.cpp b/qpid/cpp/src/qpid/messaging/amqp/SessionContext.cpp
new file mode 100644
index 0000000000..92bdea7dbc
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/SessionContext.cpp
@@ -0,0 +1,258 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "SessionContext.h"
+#include "SenderContext.h"
+#include "ReceiverContext.h"
+#include "Transaction.h"
+#include "PnData.h"
+#include <boost/format.hpp>
+#include "qpid/messaging/Address.h"
+#include "qpid/messaging/Duration.h"
+#include "qpid/messaging/exceptions.h"
+#include "qpid/log/Statement.h"
+#include "qpid/amqp/descriptors.h"
+
+extern "C" {
+#include <proton/engine.h>
+}
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+
+SessionContext::SessionContext(pn_connection_t* connection) : session(pn_session(connection)) {}
+
+SessionContext::~SessionContext()
+{
+ // Clear all pointers to senders and receivers before we free the session.
+ senders.clear();
+ receivers.clear();
+ transaction.reset(); // Transaction is a sender.
+ if (!error && session)
+ pn_session_free(session);
+}
+
+boost::shared_ptr<SenderContext> SessionContext::createSender(const qpid::messaging::Address& address, bool setToOnSend)
+{
+ error.raise();
+ std::string name = AddressHelper::getLinkName(address);
+ if (senders.find(name) != senders.end())
+ throw LinkError("Link name must be unique within the scope of the connection");
+ boost::shared_ptr<SenderContext> s(
+ new SenderContext(session, name, address, setToOnSend, transaction));
+ senders[name] = s;
+ return s;
+}
+
+boost::shared_ptr<ReceiverContext> SessionContext::createReceiver(const qpid::messaging::Address& address)
+{
+ error.raise();
+ std::string name = AddressHelper::getLinkName(address);
+ if (receivers.find(name) != receivers.end()) throw LinkError("Link name must be unique within the scope of the connection");
+ boost::shared_ptr<ReceiverContext> r(new ReceiverContext(session, name, address));
+ receivers[name] = r;
+ return r;
+}
+
+boost::shared_ptr<SenderContext> SessionContext::getSender(const std::string& name) const
+{
+ error.raise();
+ SenderMap::const_iterator i = senders.find(name);
+ if (i == senders.end()) {
+ throw qpid::messaging::KeyError(std::string("No such sender") + name);
+ } else {
+ return i->second;
+ }
+}
+
+boost::shared_ptr<ReceiverContext> SessionContext::getReceiver(const std::string& name) const
+{
+ error.raise();
+ ReceiverMap::const_iterator i = receivers.find(name);
+ if (i == receivers.end()) {
+ throw qpid::messaging::KeyError(std::string("No such receiver") + name);
+ } else {
+ return i->second;
+ }
+}
+
+void SessionContext::removeReceiver(const std::string& n)
+{
+ error.raise();
+ receivers.erase(n);
+}
+
+void SessionContext::removeSender(const std::string& n)
+{
+ error.raise();
+ senders.erase(n);
+}
+
+boost::shared_ptr<ReceiverContext> SessionContext::nextReceiver()
+{
+ error.raise();
+ for (SessionContext::ReceiverMap::iterator i = receivers.begin(); i != receivers.end(); ++i) {
+ if (i->second->hasCurrent()) {
+ return i->second;
+ }
+ }
+
+ return boost::shared_ptr<ReceiverContext>();
+}
+
+uint32_t SessionContext::getReceivable()
+{
+ error.raise();
+ return 0;//TODO
+}
+
+uint32_t SessionContext::getUnsettledAcks()
+{
+ error.raise();
+ return 0;//TODO
+}
+
+qpid::framing::SequenceNumber SessionContext::record(pn_delivery_t* delivery)
+{
+ error.raise();
+ qpid::framing::SequenceNumber id = next++;
+ if (!pn_delivery_settled(delivery)) {
+ unacked[id] = delivery;
+ QPID_LOG(debug, "Recorded delivery " << id << " -> " << delivery);
+ pn_link_advance(pn_delivery_link(delivery));
+ } else {
+ pn_delivery_settle(delivery); // Automatically advances the link.
+ }
+ return id;
+}
+
+void SessionContext::acknowledge(DeliveryMap::iterator begin, DeliveryMap::iterator end)
+{
+ error.raise();
+ for (DeliveryMap::iterator i = begin; i != end; ++i) {
+ types::Variant txState;
+ if (transaction) {
+ QPID_LOG(trace, "Setting disposition for transactional delivery "
+ << i->first << " -> " << i->second);
+ transaction->acknowledge(i->second);
+ } else {
+ QPID_LOG(trace, "Setting disposition for delivery " << i->first << " -> " << i->second);
+ pn_delivery_update(i->second, PN_ACCEPTED);
+ pn_delivery_settle(i->second); //TODO: different settlement modes?
+ }
+ }
+ unacked.erase(begin, end);
+}
+
+void SessionContext::acknowledge()
+{
+ error.raise();
+ QPID_LOG(debug, "acknowledging all " << unacked.size() << " messages");
+ acknowledge(unacked.begin(), unacked.end());
+}
+
+void SessionContext::acknowledge(const qpid::framing::SequenceNumber& id, bool cumulative)
+{
+ error.raise();
+ QPID_LOG(debug, "acknowledging selected messages, id=" << id << ", cumulative=" << cumulative);
+ DeliveryMap::iterator i = unacked.find(id);
+ if (i != unacked.end()) {
+ DeliveryMap::iterator start = cumulative ? unacked.begin() : i;
+ acknowledge(start, ++i);
+ } else {
+ QPID_LOG(debug, "selective acknowledgement failed; message not found for id " << id);
+ }
+}
+
+void SessionContext::nack(const qpid::framing::SequenceNumber& id, bool reject)
+{
+ error.raise();
+ DeliveryMap::iterator i = unacked.find(id);
+ if (i != unacked.end()) {
+ if (reject) {
+ QPID_LOG(debug, "rejecting message with id=" << id);
+ pn_delivery_update(i->second, PN_REJECTED);
+ } else {
+ QPID_LOG(debug, "releasing message with id=" << id);
+ pn_delivery_update(i->second, PN_MODIFIED);
+ pn_disposition_set_failed(pn_delivery_local(i->second), true);
+ }
+ pn_delivery_settle(i->second);
+ unacked.erase(i);
+ }
+}
+
+bool SessionContext::settled()
+{
+ error.raise();
+ bool result = true;
+
+ for (SenderMap::iterator i = senders.begin(); i != senders.end(); ++i) {
+ try {
+ if (!i->second->closed() && !i->second->settled()) result = false;
+ } catch (const std::exception&) {
+ senders.erase(i);
+ throw;
+ }
+ }
+ return result;
+}
+
+void SessionContext::setName(const std::string& n)
+{
+ name = n;
+}
+std::string SessionContext::getName() const
+{
+ return name;
+}
+
+void SessionContext::reset(pn_connection_t* connection)
+{
+ unacked.clear();
+ if (transaction) {
+ if (transaction->isCommitting())
+ error = new TransactionUnknown("Transaction outcome unknown: transport failure");
+ else
+ error = new TransactionAborted("Transaction aborted: transport failure");
+ resetSession(0);
+ senders.clear();
+ receivers.clear();
+ transaction.reset();
+ return;
+ }
+ resetSession(pn_session(connection));
+
+}
+
+void SessionContext::resetSession(pn_session_t* session_) {
+ session = session_;
+ if (transaction) transaction->reset(session);
+ for (SessionContext::SenderMap::iterator i = senders.begin(); i != senders.end(); ++i) {
+ i->second->reset(session);
+ }
+ for (SessionContext::ReceiverMap::iterator i = receivers.begin(); i != receivers.end(); ++i) {
+ i->second->reset(session);
+ }
+}
+
+
+}}} // namespace qpid::messaging::amqp
diff --git a/qpid/cpp/src/qpid/messaging/amqp/SessionContext.h b/qpid/cpp/src/qpid/messaging/amqp/SessionContext.h
new file mode 100644
index 0000000000..67b3c1e401
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/SessionContext.h
@@ -0,0 +1,95 @@
+#ifndef QPID_MESSAGING_AMQP_SESSIONCONTEXT_H
+#define QPID_MESSAGING_AMQP_SESSIONCONTEXT_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <map>
+#include <string>
+#include <boost/shared_ptr.hpp>
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/framing/SequenceNumber.h"
+#include "qpid/sys/ExceptionHolder.h"
+
+struct pn_connection_t;
+struct pn_session_t;
+struct pn_delivery_t;
+
+namespace qpid {
+namespace messaging {
+
+class Address;
+class Duration;
+
+namespace amqp {
+
+class ConnectionContext;
+class SenderContext;
+class ReceiverContext;
+class Transaction;
+
+/**
+ *
+ */
+class SessionContext
+{
+ public:
+ SessionContext(pn_connection_t*);
+ ~SessionContext();
+ void reset(pn_connection_t*);
+ boost::shared_ptr<SenderContext> createSender(const qpid::messaging::Address& address, bool setToOnSend);
+ boost::shared_ptr<ReceiverContext> createReceiver(const qpid::messaging::Address& address);
+ boost::shared_ptr<SenderContext> getSender(const std::string& name) const;
+ boost::shared_ptr<ReceiverContext> getReceiver(const std::string& name) const;
+ void removeReceiver(const std::string&);
+ void removeSender(const std::string&);
+ boost::shared_ptr<ReceiverContext> nextReceiver();
+ uint32_t getReceivable();
+ uint32_t getUnsettledAcks();
+ bool settled();
+ void setName(const std::string&);
+ std::string getName() const;
+
+ void nack(const qpid::framing::SequenceNumber& id, bool reject);
+
+ private:
+ friend class ConnectionContext;
+ typedef std::map<std::string, boost::shared_ptr<SenderContext> > SenderMap;
+ typedef std::map<std::string, boost::shared_ptr<ReceiverContext> > ReceiverMap;
+ typedef std::map<qpid::framing::SequenceNumber, pn_delivery_t*> DeliveryMap;
+
+ pn_session_t* session;
+ SenderMap senders;
+ boost::shared_ptr<Transaction> transaction;
+ ReceiverMap receivers;
+ DeliveryMap unacked;
+ qpid::framing::SequenceNumber next;
+ std::string name;
+ sys::ExceptionHolder error;
+
+ qpid::framing::SequenceNumber record(pn_delivery_t*);
+ void acknowledge();
+ void acknowledge(const qpid::framing::SequenceNumber& id, bool cummulative);
+ void acknowledge(DeliveryMap::iterator begin, DeliveryMap::iterator end);
+ void resetSession(pn_session_t*);
+};
+}}} // namespace qpid::messaging::amqp
+
+#endif /*!QPID_MESSAGING_AMQP_SESSIONCONTEXT_H*/
diff --git a/qpid/cpp/src/qpid/messaging/amqp/SessionHandle.cpp b/qpid/cpp/src/qpid/messaging/amqp/SessionHandle.cpp
new file mode 100644
index 0000000000..6b90d69c7f
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/SessionHandle.cpp
@@ -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.
+ *
+ */
+#include "SessionHandle.h"
+#include "ConnectionContext.h"
+#include "ConnectionHandle.h"
+#include "ReceiverContext.h"
+#include "ReceiverHandle.h"
+#include "SenderContext.h"
+#include "SenderHandle.h"
+#include "SessionContext.h"
+#include "qpid/messaging/Connection.h"
+#include "qpid/messaging/Duration.h"
+#include "qpid/messaging/exceptions.h"
+#include "qpid/messaging/Receiver.h"
+#include "qpid/messaging/Sender.h"
+#include "qpid/messaging/Session.h"
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+
+SessionHandle::SessionHandle(boost::shared_ptr<ConnectionContext> c, boost::shared_ptr<SessionContext> s) : connection(c), session(s) {}
+
+void SessionHandle::commit()
+{
+ connection->commit(session);
+}
+
+void SessionHandle::rollback()
+{
+ connection->rollback(session);
+}
+
+void SessionHandle::acknowledge(bool /*sync*/)
+{
+ connection->acknowledge(session, 0, false);
+}
+
+void SessionHandle::acknowledge(qpid::messaging::Message& msg, bool cumulative)
+{
+ connection->acknowledge(session, &msg, cumulative);
+}
+
+void SessionHandle::reject(qpid::messaging::Message& msg)
+{
+ connection->nack(session, msg, true);
+}
+
+void SessionHandle::release(qpid::messaging::Message& msg)
+{
+ connection->nack(session, msg, false);
+}
+
+void SessionHandle::close()
+{
+ connection->endSession(session);
+}
+
+void SessionHandle::sync(bool block)
+{
+ if (block) {
+ connection->sync(session);
+ }
+}
+
+qpid::messaging::Sender SessionHandle::createSender(const qpid::messaging::Address& address)
+{
+ boost::shared_ptr<SenderContext> sender = connection->createSender(session, address);
+ return qpid::messaging::Sender(new SenderHandle(connection, session, sender));
+}
+
+qpid::messaging::Receiver SessionHandle::createReceiver(const qpid::messaging::Address& address)
+{
+ boost::shared_ptr<ReceiverContext> receiver = connection->createReceiver(session, address);
+ return qpid::messaging::Receiver(new ReceiverHandle(connection, session, receiver));
+}
+
+bool SessionHandle::nextReceiver(Receiver& receiver, Duration timeout)
+{
+ boost::shared_ptr<ReceiverContext> r = connection->nextReceiver(session, timeout);
+ if (r) {
+ //TODO: cache handles in this case to avoid frequent allocation
+ receiver = qpid::messaging::Receiver(new ReceiverHandle(connection, session, r));
+ return true;
+ } else {
+ return false;
+ }
+}
+
+qpid::messaging::Receiver SessionHandle::nextReceiver(Duration timeout)
+{
+ qpid::messaging::Receiver r;
+ if (nextReceiver(r, timeout)) return r;
+ else throw qpid::messaging::NoMessageAvailable();
+}
+
+uint32_t SessionHandle::getReceivable()
+{
+ return session->getReceivable();
+}
+
+uint32_t SessionHandle::getUnsettledAcks()
+{
+ return session->getUnsettledAcks();
+}
+
+Sender SessionHandle::getSender(const std::string& name) const
+{
+ return qpid::messaging::Sender(new SenderHandle(connection, session, connection->getSender(session, name)));
+}
+
+Receiver SessionHandle::getReceiver(const std::string& name) const
+{
+ return qpid::messaging::Receiver(new ReceiverHandle(connection, session, connection->getReceiver(session, name)));
+}
+
+Connection SessionHandle::getConnection() const
+{
+ return qpid::messaging::Connection(new ConnectionHandle(connection));
+}
+
+void SessionHandle::checkError()
+{
+
+}
+
+
+}}} // namespace qpid::messaging::amqp
diff --git a/qpid/cpp/src/qpid/messaging/amqp/SessionHandle.h b/qpid/cpp/src/qpid/messaging/amqp/SessionHandle.h
new file mode 100644
index 0000000000..5e843aaacc
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/SessionHandle.h
@@ -0,0 +1,64 @@
+#ifndef QPID_MESSAGING_AMQP_SESSIONIMPL_H
+#define QPID_MESSAGING_AMQP_SESSIONIMPL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <boost/shared_ptr.hpp>
+#include "qpid/messaging/SessionImpl.h"
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+
+class ConnectionContext;
+class SessionContext;
+/**
+ *
+ */
+class SessionHandle : public qpid::messaging::SessionImpl
+{
+ public:
+ SessionHandle(boost::shared_ptr<ConnectionContext>, boost::shared_ptr<SessionContext>);
+ void commit();
+ void rollback();
+ void acknowledge(bool sync);
+ void acknowledge(Message&, bool);
+ void reject(Message&);
+ void release(Message&);
+ void close();
+ void sync(bool block);
+ qpid::messaging::Sender createSender(const Address& address);
+ qpid::messaging::Receiver createReceiver(const Address& address);
+ bool nextReceiver(Receiver& receiver, Duration timeout);
+ qpid::messaging::Receiver nextReceiver(Duration timeout);
+ uint32_t getReceivable();
+ uint32_t getUnsettledAcks();
+ qpid::messaging::Sender getSender(const std::string& name) const;
+ qpid::messaging::Receiver getReceiver(const std::string& name) const;
+ qpid::messaging::Connection getConnection() const;
+ void checkError();
+ private:
+ boost::shared_ptr<ConnectionContext> connection;
+ boost::shared_ptr<SessionContext> session;
+};
+}}} // namespace qpid::messaging::amqp
+
+#endif /*!QPID_MESSAGING_AMQP_SESSIONIMPL_H*/
diff --git a/qpid/cpp/src/qpid/messaging/amqp/SslTransport.cpp b/qpid/cpp/src/qpid/messaging/amqp/SslTransport.cpp
new file mode 100644
index 0000000000..e8ef2d587b
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/SslTransport.cpp
@@ -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.
+ *
+ */
+#include "SslTransport.h"
+#include "TransportContext.h"
+#include "qpid/messaging/ConnectionOptions.h"
+#include "qpid/sys/ssl/SslSocket.h"
+#include "qpid/sys/AsynchIO.h"
+#include "qpid/sys/ConnectionCodec.h"
+#include "qpid/sys/Poller.h"
+#include "qpid/client/ssl.h"
+#include "qpid/log/Statement.h"
+#include <boost/bind.hpp>
+#include <boost/format.hpp>
+
+using namespace qpid::sys;
+using namespace qpid::sys::ssl;
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+
+// Static constructor which registers connector here
+namespace {
+Transport* create(TransportContext& c, Poller::shared_ptr p)
+{
+ qpid::client::initialiseSSL();
+ return new SslTransport(c, p);
+}
+
+struct StaticInit
+{
+ StaticInit()
+ {
+ Transport::add("ssl", &create);
+ };
+
+ ~StaticInit()
+ {
+ qpid::client::shutdownSSL();
+ }
+} init;
+}
+
+
+SslTransport::SslTransport(TransportContext& c, boost::shared_ptr<Poller> p) : context(c), connector(0), aio(0), poller(p)
+{
+ const ConnectionOptions* options = context.getOptions();
+ options->configureSocket(socket);
+ if (options->sslCertName != "") {
+ QPID_LOG(debug, "ssl-cert-name = " << options->sslCertName);
+ socket.setCertName(options->sslCertName);
+ }
+ if (options->sslIgnoreHostnameVerificationFailure) {
+ socket.ignoreHostnameVerificationFailure();
+ }
+}
+
+void SslTransport::connect(const std::string& host, const std::string& port)
+{
+ assert(!connector);
+ assert(!aio);
+ connector = AsynchConnector::create(
+ socket,
+ host, port,
+ boost::bind(&SslTransport::connected, this, _1),
+ boost::bind(&SslTransport::failed, this, _3));
+
+ connector->start(poller);
+}
+
+void SslTransport::failed(const std::string& msg)
+{
+ QPID_LOG(debug, "Failed to connect: " << msg);
+ socket.close();
+ context.closed();
+}
+
+void SslTransport::connected(const Socket&)
+{
+ context.opened();
+ aio = AsynchIO::create(socket,
+ boost::bind(&SslTransport::read, this, _1, _2),
+ boost::bind(&SslTransport::eof, this, _1),
+ boost::bind(&SslTransport::disconnected, this, _1),
+ boost::bind(&SslTransport::socketClosed, this, _1, _2),
+ 0, // nobuffs
+ boost::bind(&SslTransport::write, this, _1));
+ aio->createBuffers(std::numeric_limits<uint16_t>::max());//note: AMQP 1.0 _can_ handle large frame sizes
+ id = boost::str(boost::format("[%1%]") % socket.getFullAddress());
+ aio->start(poller);
+}
+
+void SslTransport::read(AsynchIO&, AsynchIO::BufferBase* buffer)
+{
+ int32_t decoded = context.getCodec().decode(buffer->bytes+buffer->dataStart, buffer->dataCount);
+ if (decoded < buffer->dataCount) {
+ // Adjust buffer for used bytes and then "unread them"
+ buffer->dataStart += decoded;
+ buffer->dataCount -= decoded;
+ aio->unread(buffer);
+ } else {
+ // Give whole buffer back to aio subsystem
+ aio->queueReadBuffer(buffer);
+ }
+}
+
+void SslTransport::write(AsynchIO&)
+{
+ if (context.getCodec().canEncode()) {
+ AsynchIO::BufferBase* buffer = aio->getQueuedBuffer();
+ if (buffer) {
+ size_t encoded = context.getCodec().encode(buffer->bytes, buffer->byteCount);
+
+ buffer->dataStart = 0;
+ buffer->dataCount = encoded;
+ aio->queueWrite(buffer);
+ }
+ }
+
+}
+
+void SslTransport::close()
+{
+ QPID_LOG(debug, id << " SslTransport closing...");
+ if (aio)
+ aio->queueWriteClose();
+}
+
+void SslTransport::eof(AsynchIO&)
+{
+ close();
+}
+
+void SslTransport::disconnected(AsynchIO&)
+{
+ close();
+ socketClosed(*aio, socket);
+}
+
+void SslTransport::socketClosed(AsynchIO&, const Socket&)
+{
+ if (aio)
+ aio->queueForDeletion();
+ context.closed();
+ QPID_LOG(debug, id << " Socket closed");
+}
+
+void SslTransport::abort()
+{
+ if (aio) {
+ // Established connection
+ aio->requestCallback(boost::bind(&SslTransport::eof, this, _1));
+ }
+}
+
+void SslTransport::activateOutput()
+{
+ if (aio) aio->notifyPendingWrite();
+}
+
+const qpid::sys::SecuritySettings* SslTransport::getSecuritySettings()
+{
+ securitySettings.ssf = socket.getKeyLen();
+ securitySettings.authid = "dummy";//set to non-empty string to enable external authentication
+ return &securitySettings;
+}
+
+}}} // namespace qpid::messaging::amqp
diff --git a/qpid/cpp/src/qpid/messaging/amqp/SslTransport.h b/qpid/cpp/src/qpid/messaging/amqp/SslTransport.h
new file mode 100644
index 0000000000..2972be4fac
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/SslTransport.h
@@ -0,0 +1,78 @@
+#ifndef QPID_MESSAGING_AMQP_SSLTRANSPORT_H
+#define QPID_MESSAGING_AMQP_SSLTRANSPORT_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/messaging/amqp/Transport.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/SecuritySettings.h"
+#include "qpid/sys/ssl/SslSocket.h"
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace sys {
+class ConnectionCodec;
+class Poller;
+class AsynchConnector;
+class AsynchIO;
+class AsynchIOBufferBase;
+}
+
+namespace messaging {
+namespace amqp {
+class TransportContext;
+
+class SslTransport : public Transport
+{
+ public:
+ SslTransport(TransportContext&, boost::shared_ptr<qpid::sys::Poller> p);
+
+ void connect(const std::string& host, const std::string& port);
+
+ void activateOutput();
+ void abort();
+ void connectionEstablished() {};
+ void close();
+ const qpid::sys::SecuritySettings* getSecuritySettings();
+
+ private:
+ qpid::sys::ssl::SslSocket socket;
+ TransportContext& context;
+ qpid::sys::AsynchConnector* connector;
+ qpid::sys::AsynchIO* aio;
+ boost::shared_ptr<qpid::sys::Poller> poller;
+ bool closed;
+ std::string id;
+ qpid::sys::SecuritySettings securitySettings;
+
+ void connected(const qpid::sys::Socket&);
+ void failed(const std::string& msg);
+ void read(qpid::sys::AsynchIO&, qpid::sys::AsynchIOBufferBase*);
+ void write(qpid::sys::AsynchIO&);
+ void eof(qpid::sys::AsynchIO&);
+ void disconnected(qpid::sys::AsynchIO&);
+ void socketClosed(qpid::sys::AsynchIO&, const qpid::sys::Socket&);
+
+ friend class DriverImpl;
+};
+}}} // namespace qpid::messaging::amqp
+
+#endif /*!QPID_MESSAGING_AMQP_SSLTRANSPORT_H*/
diff --git a/qpid/cpp/src/qpid/messaging/amqp/TcpTransport.cpp b/qpid/cpp/src/qpid/messaging/amqp/TcpTransport.cpp
new file mode 100644
index 0000000000..a919e974d6
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/TcpTransport.cpp
@@ -0,0 +1,185 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "TcpTransport.h"
+#include "ConnectionContext.h"
+#include "qpid/messaging/ConnectionOptions.h"
+#include "qpid/sys/AsynchIO.h"
+#include "qpid/sys/ConnectionCodec.h"
+#include "qpid/sys/Poller.h"
+#include "qpid/log/Statement.h"
+#include <boost/bind.hpp>
+#include <boost/format.hpp>
+
+using namespace qpid::sys;
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+// Static constructor which registers connector here
+namespace {
+Transport* create(TransportContext& c, Poller::shared_ptr p)
+{
+ return new TcpTransport(c, p);
+}
+
+struct StaticInit
+{
+ StaticInit()
+ {
+ Transport::add("tcp", &create);
+ };
+} init;
+}
+
+TcpTransport::TcpTransport(TransportContext& c, boost::shared_ptr<Poller> p) : socket(createSocket()), context(c), connector(0), aio(0), poller(p), closed(false) {}
+
+void TcpTransport::connect(const std::string& host, const std::string& port)
+{
+ assert(!connector);
+ assert(!aio);
+ context.getOptions()->configureSocket(*socket);
+ connector = AsynchConnector::create(
+ *socket,
+ host, port,
+ boost::bind(&TcpTransport::connected, this, _1),
+ boost::bind(&TcpTransport::failed, this, _3));
+
+ connector->start(poller);
+}
+
+void TcpTransport::failed(const std::string& msg)
+{
+ QPID_LOG(debug, "Failed to connect: " << msg);
+ closed = true;
+ connector = 0;
+ socket->close();
+ context.closed();
+}
+
+void TcpTransport::connected(const Socket&)
+{
+ context.opened();
+ connector = 0;
+ aio = AsynchIO::create(*socket,
+ boost::bind(&TcpTransport::read, this, _1, _2),
+ boost::bind(&TcpTransport::eof, this, _1),
+ boost::bind(&TcpTransport::disconnected, this, _1),
+ boost::bind(&TcpTransport::socketClosed, this, _1, _2),
+ 0, // nobuffs
+ boost::bind(&TcpTransport::write, this, _1));
+ aio->createBuffers(std::numeric_limits<uint16_t>::max());//note: AMQP 1.0 _can_ handle large frame sizes
+ id = boost::str(boost::format("[%1%]") % socket->getFullAddress());
+ aio->start(poller);
+}
+
+void TcpTransport::read(AsynchIO&, AsynchIO::BufferBase* buffer)
+{
+ int32_t decoded = context.getCodec().decode(buffer->bytes+buffer->dataStart, buffer->dataCount);
+ if (decoded < buffer->dataCount) {
+ // Adjust buffer for used bytes and then "unread them"
+ buffer->dataStart += decoded;
+ buffer->dataCount -= decoded;
+ aio->unread(buffer);
+ } else {
+ // Give whole buffer back to aio subsystem
+ aio->queueReadBuffer(buffer);
+ }
+}
+
+void TcpTransport::write(AsynchIO&)
+{
+ if (context.getCodec().canEncode()) {
+ AsynchIO::BufferBase* buffer = aio->getQueuedBuffer();
+ if (buffer) {
+ size_t encoded = context.getCodec().encode(buffer->bytes, buffer->byteCount);
+
+ buffer->dataStart = 0;
+ buffer->dataCount = encoded;
+ aio->queueWrite(buffer);
+ }
+ }
+
+}
+
+void TcpTransport::close()
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ if (!closed) {
+ QPID_LOG(debug, id << " TcpTransport closing...");
+ if (aio)
+ aio->queueWriteClose();
+ }
+}
+
+void TcpTransport::eof(AsynchIO&)
+{
+ close();
+}
+
+void TcpTransport::disconnected(AsynchIO&)
+{
+ close();
+ socketClosed(*aio, *socket);
+}
+
+void TcpTransport::socketClosed(AsynchIO&, const Socket&)
+{
+ bool notify(false);
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ if (!closed) {
+ closed = true;
+ if (aio)
+ aio->queueForDeletion();
+ QPID_LOG(debug, id << " Socket closed");
+ notify = true;
+ } //else has already been closed
+ }
+ if (notify) context.closed();
+}
+
+void TcpTransport::abort()
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ if (!closed) {
+ if (aio) {
+ // Established connection
+ aio->requestCallback(boost::bind(&TcpTransport::eof, this, _1));
+ } else if (connector) {
+ // We're still connecting
+ connector->stop();
+ failed("Connection timedout");
+ }
+ }
+}
+
+void TcpTransport::activateOutput()
+{
+ qpid::sys::Mutex::ScopedLock l(lock);
+ if (!closed && aio) aio->notifyPendingWrite();
+}
+
+const qpid::sys::SecuritySettings* TcpTransport::getSecuritySettings()
+{
+ return 0;
+}
+
+}}} // namespace qpid::messaging::amqp
diff --git a/qpid/cpp/src/qpid/messaging/amqp/TcpTransport.h b/qpid/cpp/src/qpid/messaging/amqp/TcpTransport.h
new file mode 100644
index 0000000000..3e59ec97b2
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/TcpTransport.h
@@ -0,0 +1,78 @@
+#ifndef QPID_MESSAGING_AMQP_TCPTRANSPORT_H
+#define QPID_MESSAGING_AMQP_TCPTRANSPORT_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/messaging/amqp/Transport.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/Socket.h"
+#include <boost/scoped_ptr.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace sys {
+class ConnectionCodec;
+class AsynchConnector;
+class AsynchIO;
+struct AsynchIOBufferBase;
+class Poller;
+}
+namespace messaging {
+namespace amqp {
+class TransportContext;
+
+class TcpTransport : public Transport
+{
+ public:
+ TcpTransport(TransportContext&, boost::shared_ptr<qpid::sys::Poller>);
+
+ void connect(const std::string& host, const std::string& port);
+
+ void activateOutput();
+ void abort();
+ void connectionEstablished() {};
+ void close();
+ const qpid::sys::SecuritySettings* getSecuritySettings();
+
+ protected:
+ boost::scoped_ptr<qpid::sys::Socket> socket;
+ TransportContext& context;
+ qpid::sys::AsynchConnector* connector;
+ qpid::sys::AsynchIO* aio;
+ boost::shared_ptr<qpid::sys::Poller> poller;
+ std::string id;
+
+ virtual ~TcpTransport() {}
+ virtual void connected(const qpid::sys::Socket&);
+ void failed(const std::string& msg);
+ void read(qpid::sys::AsynchIO&, qpid::sys::AsynchIOBufferBase*);
+ void write(qpid::sys::AsynchIO&);
+ void eof(qpid::sys::AsynchIO&);
+ void disconnected(qpid::sys::AsynchIO&);
+ void socketClosed(qpid::sys::AsynchIO&, const qpid::sys::Socket&);
+
+ private:
+ bool closed;
+ qpid::sys::Mutex lock;
+};
+}}} // namespace qpid::messaging::amqp
+
+#endif /*!QPID_MESSAGING_AMQP_TCPTRANSPORT_H*/
diff --git a/qpid/cpp/src/qpid/messaging/amqp/Transaction.cpp b/qpid/cpp/src/qpid/messaging/amqp/Transaction.cpp
new file mode 100644
index 0000000000..754b00d802
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/Transaction.cpp
@@ -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.
+ */
+
+#include "Transaction.h"
+#include "SessionContext.h"
+#include "ConnectionContext.h"
+#include "PnData.h"
+#include <proton/engine.h>
+#include <qpid/Exception.h>
+#include <qpid/amqp/descriptors.h>
+#include <qpid/messaging/exceptions.h>
+#include <qpid/log/Statement.h>
+#include "qpid/messaging/Message.h"
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+
+using namespace types;
+using types::Exception;
+
+namespace {
+const std::string LOCAL_TRANSACTIONS("amqp:local-transactions");
+const std::string TX_COORDINATOR("tx-transaction");
+const std::string ADDRESS("tx-transaction;{link:{reliability:at-least-once}}");
+}
+
+Transaction::Transaction(pn_session_t* session) :
+ SenderContext(session, TX_COORDINATOR, Address(ADDRESS), false), committing(false)
+{}
+
+void Transaction::clear() {
+ id.clear();
+ sendState.reset();
+ acceptState.reset();
+}
+
+void Transaction::configure() {
+ SenderContext::configure();
+ pn_terminus_t* target = pn_link_target(sender);
+ pn_terminus_set_type(target, PN_COORDINATOR);
+ PnData(pn_terminus_capabilities(target)).putSymbol(LOCAL_TRANSACTIONS);
+}
+
+void Transaction::verify() {}
+
+const std::string& Transaction::getTarget() const { return getName(); }
+
+void Transaction::declare(SendFunction send, const SessionPtr& session) {
+ committing = false;
+ error.raise();
+ clear();
+ Variant declare = Variant::described(qpid::amqp::transaction::DECLARE_CODE, Variant::List());
+ SenderContext::Delivery* delivery = 0;
+ send(session, shared_from_this(), Message(declare), true, &delivery);
+ setId(*delivery);
+}
+
+void Transaction::discharge(SendFunction send, const SessionPtr& session, bool fail) {
+ error.raise();
+ committing = !fail;
+ try {
+ // Send a discharge message to the remote coordinator.
+ Variant::List dischargeList;
+ dischargeList.push_back(Variant(id));
+ dischargeList.push_back(Variant(fail));
+ Variant discharge(dischargeList);
+ discharge.setDescriptor(qpid::amqp::transaction::DISCHARGE_CODE);
+ SenderContext::Delivery* delivery = 0;
+ send(session, shared_from_this(), Message(discharge), true, &delivery);
+ if (!delivery->accepted())
+ throw TransactionAborted(delivery->error());
+ committing = false;
+ }
+ catch(const TransactionError&) {
+ throw;
+ }
+ catch(const Exception& e) {
+ committing = false;
+ throw TransactionAborted(e.what());
+ }
+}
+
+// Set the transaction ID from the delivery returned by the remote coordinator.
+void Transaction::setId(const SenderContext::Delivery& delivery)
+{
+ if (delivery.getToken() &&
+ pn_delivery_remote_state(delivery.getToken()) == qpid::amqp::transaction::DECLARED_CODE)
+ {
+ pn_data_t* data = pn_disposition_data(pn_delivery_remote(delivery.getToken()));
+ if (data && pn_data_next(data)) {
+ size_t count = pn_data_get_list(data);
+ if (count > 0) {
+ pn_data_enter(data);
+ pn_data_next(data);
+ setId(PnData::string(pn_data_get_binary(data)));
+ pn_data_exit(data);
+ return;
+ }
+ }
+ }
+ throw TransactionError("No transaction ID returned by remote coordinator.");
+}
+
+void Transaction::setId(const std::string& id_) {
+ id = id_;
+ if (id.empty()) {
+ clear();
+ }
+ else {
+ // NOTE: The send and accept states are NOT described, the descriptor
+ // is added in pn_delivery_update.
+ Variant::List list;
+ list.push_back(Variant(id, "binary"));
+ sendState = Variant(list);
+
+ Variant accepted = Variant::described(qpid::amqp::message::ACCEPTED_CODE, Variant::List());
+ list.push_back(accepted);
+ acceptState = Variant(list);
+ }
+}
+
+types::Variant Transaction::getSendState() const {
+ error.raise();
+ return sendState;
+}
+
+void Transaction::acknowledge(pn_delivery_t* delivery)
+{
+ error.raise();
+ PnData data(pn_disposition_data(pn_delivery_local(delivery)));
+ data.put(acceptState);
+ pn_delivery_update(delivery, qpid::amqp::transaction::TRANSACTIONAL_STATE_CODE);
+ pn_delivery_settle(delivery);
+}
+
+
+
+}}} // namespace qpid::messaging::amqp
diff --git a/qpid/cpp/src/qpid/messaging/amqp/Transaction.h b/qpid/cpp/src/qpid/messaging/amqp/Transaction.h
new file mode 100644
index 0000000000..35492c9bb3
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/Transaction.h
@@ -0,0 +1,95 @@
+#ifndef COORDINATORCONTEXT_H
+#define COORDINATORCONTEXT_H
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+#include "SenderContext.h"
+#include <qpid/types/Variant.h>
+#include "qpid/sys/ExceptionHolder.h"
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/function.hpp>
+
+struct pn_session_t;
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+
+class SessionContext;
+class ConnectionContext;
+
+/**
+ * Track the current transaction for a session.
+ *
+ * Implements SenderContext, to send transaction command messages to remote coordinator.
+ */
+class Transaction : public SenderContext, public boost::enable_shared_from_this<Transaction> {
+ public:
+ typedef boost::shared_ptr<SessionContext> SessionPtr;
+
+ typedef boost::function<void (boost::shared_ptr<SessionContext> ssn,
+ boost::shared_ptr<SenderContext> snd,
+ const qpid::messaging::Message& message,
+ bool sync,
+ SenderContext::Delivery** delivery)> SendFunction;
+
+ Transaction(pn_session_t*);
+
+ sys::ExceptionHolder error;
+
+ /** Declare a transaction using connection and session to send to remote co-ordinator. */
+ void declare(SendFunction, const SessionPtr& session);
+
+ /** Discharge a transaction using connection and session to send to remote co-ordinator.
+ *@param fail: true means rollback, false means commit.
+ */
+ void discharge(SendFunction, const SessionPtr& session, bool fail);
+
+ /** Update a delivery with a transactional accept state. */
+ void acknowledge(pn_delivery_t* delivery);
+
+ /** Get delivery state to attach to transfers sent in a transaction. */
+ types::Variant getSendState() const;
+
+ /** Override SenderContext::getTarget with a more readable value */
+ const std::string& getTarget() const;
+
+ bool isCommitting() const { return committing; }
+
+ protected:
+ // SenderContext overrides
+ void configure();
+ void verify();
+
+ private:
+ std::string id;
+ types::Variant sendState;
+ types::Variant acceptState;
+ bool committing;
+
+
+ void clear();
+ void setId(const SenderContext::Delivery& delivery);
+ void setId(const std::string& id);
+};
+
+}}}
+
+#endif
diff --git a/qpid/cpp/src/qpid/messaging/amqp/Transport.cpp b/qpid/cpp/src/qpid/messaging/amqp/Transport.cpp
new file mode 100644
index 0000000000..21f51046b1
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/Transport.cpp
@@ -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.
+ *
+ */
+#include "qpid/messaging/amqp/Transport.h"
+#include "qpid/messaging/amqp/TransportContext.h"
+#include <map>
+#include <string>
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+namespace {
+typedef std::map<std::string, Transport::Factory*> Registry;
+
+Registry& theRegistry()
+{
+ static Registry factories;
+ return factories;
+}
+}
+
+Transport* Transport::create(const std::string& name, TransportContext& context, boost::shared_ptr<qpid::sys::Poller> poller)
+{
+ Registry::const_iterator i = theRegistry().find(name);
+ if (i != theRegistry().end()) return (i->second)(context, poller);
+ else return 0;
+}
+void Transport::add(const std::string& name, Factory* factory)
+{
+ theRegistry()[name] = factory;
+}
+
+}}} // namespace qpid::messaging::amqp
diff --git a/qpid/cpp/src/qpid/messaging/amqp/Transport.h b/qpid/cpp/src/qpid/messaging/amqp/Transport.h
new file mode 100644
index 0000000000..6ec99ab58f
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/Transport.h
@@ -0,0 +1,52 @@
+#ifndef QPID_MESSAGING_AMQP_TRANSPORT_H
+#define QPID_MESSAGING_AMQP_TRANSPORT_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/CommonImportExport.h"
+#include "qpid/sys/OutputControl.h"
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace sys {
+class Poller;
+struct SecuritySettings;
+}
+namespace messaging {
+namespace amqp {
+class TransportContext;
+
+class Transport : public qpid::sys::OutputControl
+{
+ public:
+ virtual ~Transport() {}
+ virtual void connect(const std::string& host, const std::string& port) = 0;
+ virtual void close() = 0;
+ virtual void abort() = 0;
+ virtual const qpid::sys::SecuritySettings* getSecuritySettings() = 0;
+
+ typedef Transport* Factory(TransportContext&, boost::shared_ptr<qpid::sys::Poller>);
+ QPID_COMMON_EXTERN static Transport* create(const std::string& name, TransportContext&, boost::shared_ptr<qpid::sys::Poller>);
+ QPID_COMMON_EXTERN static void add(const std::string& name, Factory* factory);
+};
+}}} // namespace qpid::messaging::amqp
+
+#endif /*!QPID_MESSAGING_AMQP_TRANSPORT_H*/
diff --git a/qpid/cpp/src/qpid/messaging/amqp/TransportContext.h b/qpid/cpp/src/qpid/messaging/amqp/TransportContext.h
new file mode 100644
index 0000000000..df9add3e0b
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/TransportContext.h
@@ -0,0 +1,50 @@
+#ifndef QPID_MESSAGING_AMQP_TRANSPORTCONTEXT_H
+#define QPID_MESSAGING_AMQP_TRANSPORTCONTEXT_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+namespace qpid {
+namespace sys {
+class Codec;
+}
+namespace messaging {
+struct ConnectionOptions;
+
+namespace amqp {
+
+/**
+ * Interface to be supplied by 'users' of Transport interface, in
+ * order to provide codec and handle callbaskc for opening and closing
+ * of connection.
+ */
+class TransportContext
+{
+ public:
+ virtual ~TransportContext() {}
+ virtual qpid::sys::Codec& getCodec() = 0;
+ virtual const qpid::messaging::ConnectionOptions* getOptions() = 0;
+ virtual void closed() = 0;
+ virtual void opened() = 0;
+ private:
+};
+}}} // namespace qpid::messaging::amqp
+
+#endif /*!QPID_MESSAGING_AMQP_TRANSPORTCONTEXT_H*/
diff --git a/qpid/cpp/src/qpid/messaging/amqp/util.cpp b/qpid/cpp/src/qpid/messaging/amqp/util.cpp
new file mode 100644
index 0000000000..870a89e364
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/util.cpp
@@ -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.
+ *
+ */
+#include "util.h"
+#include <sstream>
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+
+std::string get_error_string(pn_condition_t* error, const std::string& general, const std::string& delim)
+{
+ std::string name;
+ std::stringstream text;
+ if (pn_condition_is_set(error)) {
+ name = pn_condition_get_name(error);
+ text << general << delim << name;
+ const char* desc = pn_condition_get_description(error);
+ if (desc) text << ": " << desc;
+ } else {
+ text << general;
+ }
+ return text.str();
+}
+
+}}} // namespace qpid::messaging::amqp
diff --git a/qpid/cpp/src/qpid/messaging/amqp/util.h b/qpid/cpp/src/qpid/messaging/amqp/util.h
new file mode 100644
index 0000000000..d10ef406dc
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/util.h
@@ -0,0 +1,36 @@
+#ifndef QPID_MESSAGING_AMQP_UTIL_H
+#define QPID_MESSAGING_AMQP_UTIL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <string>
+extern "C" {
+#include <proton/engine.h>
+}
+namespace qpid {
+namespace messaging {
+namespace amqp {
+
+std::string get_error_string(pn_condition_t* error, const std::string& general, const std::string& delim = std::string(" with "));
+
+}}} // namespace qpid::messaging::amqp
+
+#endif /*!QPID_MESSAGING_AMQP_UTIL_H*/
diff --git a/qpid/cpp/src/qpid/messaging/amqp/windows/SslTransport.cpp b/qpid/cpp/src/qpid/messaging/amqp/windows/SslTransport.cpp
new file mode 100644
index 0000000000..5dbc13175f
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/amqp/windows/SslTransport.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/messaging/amqp/TcpTransport.h"
+#include "qpid/messaging/amqp/TransportContext.h"
+#include "qpid/messaging/ConnectionOptions.h"
+#include "qpid/sys/SecuritySettings.h"
+#include "qpid/sys/ConnectionCodec.h"
+#include "qpid/sys/Poller.h"
+#include "qpid/log/Statement.h"
+#include <boost/bind.hpp>
+#include <boost/format.hpp>
+
+#include "qpid/sys/windows/check.h"
+#include "qpid/sys/windows/util.h"
+#include "qpid/sys/windows/SslAsynchIO.h"
+#include "qpid/sys/windows/SslCredential.h"
+
+using namespace qpid::sys;
+
+namespace qpid {
+namespace messaging {
+namespace amqp {
+
+class SslTransport : public TcpTransport
+{
+ public:
+ SslTransport(TransportContext&, boost::shared_ptr<qpid::sys::Poller> p);
+
+ void connect(const std::string& host, const std::string& port);
+ void negotiationDone(SECURITY_STATUS status);
+ const qpid::sys::SecuritySettings* getSecuritySettings();
+
+ private:
+ std::string brokerHost;
+ qpid::sys::windows::SslCredential sslCredential;
+ bool certLoaded;
+ qpid::sys::SecuritySettings securitySettings;
+
+ void connected(const qpid::sys::Socket&);
+};
+
+// Static constructor which registers connector here
+namespace {
+Transport* create(TransportContext& c, Poller::shared_ptr p)
+{
+ return new SslTransport(c, p);
+}
+
+struct StaticInit
+{
+ StaticInit()
+ {
+ Transport::add("ssl", &create);
+ };
+} init;
+}
+
+
+void SslTransport::negotiationDone(SECURITY_STATUS status)
+{
+ if (status == SEC_E_OK) {
+ connector = 0;
+ context.opened();
+ id = boost::str(boost::format("[%1%]") % socket->getFullAddress());
+ } else {
+ if (status == SEC_E_INCOMPLETE_CREDENTIALS && !certLoaded) {
+ // Server requested a client cert but we supplied none for the following reason:
+ failed(QPID_MSG(sslCredential.error()));
+ }
+ else
+ failed(QPID_MSG(qpid::sys::strError(status)));
+ }
+}
+
+SslTransport::SslTransport(TransportContext& c, boost::shared_ptr<Poller> p) : TcpTransport(c, p)
+{
+ const ConnectionOptions* options = context.getOptions();
+ if (options->sslIgnoreHostnameVerificationFailure) {
+ sslCredential.ignoreHostnameVerificationFailure();
+ }
+ const std::string& name = (options->sslCertName != "") ?
+ options->sslCertName : qpid::sys::ssl::SslOptions::global.certName;
+ certLoaded = sslCredential.load(name);
+ QPID_LOG(debug, "SslTransport created");
+}
+
+void SslTransport::connect(const std::string& host, const std::string& port)
+{
+ brokerHost = host;
+ TcpTransport::connect(host, port);
+}
+
+void SslTransport::connected(const Socket& s)
+{
+ aio = new qpid::sys::windows::ClientSslAsynchIO(brokerHost,
+ s,
+ sslCredential.handle(),
+ boost::bind(&SslTransport::read, this, _1, _2),
+ boost::bind(&SslTransport::eof, this, _1),
+ boost::bind(&SslTransport::disconnected, this, _1),
+ boost::bind(&SslTransport::socketClosed, this, _1, _2),
+ 0, // nobuffs
+ boost::bind(&SslTransport::write, this, _1),
+ boost::bind(&SslTransport::negotiationDone, this, _1));
+
+ aio->createBuffers(std::numeric_limits<uint16_t>::max());//note: AMQP 1.0 _can_ handle large frame sizes
+ aio->start(poller);
+}
+
+const qpid::sys::SecuritySettings* SslTransport::getSecuritySettings()
+{
+ securitySettings.ssf = socket->getKeyLen();
+ securitySettings.authid = "dummy";//set to non-empty string to enable external authentication
+ return &securitySettings;
+}
+
+}}} // namespace qpid::messaging::amqp
diff --git a/qpid/cpp/src/qpid/messaging/exceptions.cpp b/qpid/cpp/src/qpid/messaging/exceptions.cpp
new file mode 100644
index 0000000000..af8ab22251
--- /dev/null
+++ b/qpid/cpp/src/qpid/messaging/exceptions.cpp
@@ -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.
+ *
+ */
+#include "qpid/messaging/exceptions.h"
+
+namespace qpid {
+namespace messaging {
+
+MessagingException::MessagingException(const std::string& msg) : qpid::types::Exception(msg) {}
+MessagingException::~MessagingException() throw() {}
+
+InvalidOptionString::InvalidOptionString(const std::string& msg) : MessagingException(msg) {}
+KeyError::KeyError(const std::string& msg) : MessagingException(msg) {}
+
+
+LinkError::LinkError(const std::string& msg) : MessagingException(msg) {}
+
+AddressError::AddressError(const std::string& msg) : LinkError(msg) {}
+ResolutionError::ResolutionError(const std::string& msg) : AddressError(msg) {}
+MalformedAddress::MalformedAddress(const std::string& msg) : AddressError(msg) {}
+AssertionFailed::AssertionFailed(const std::string& msg) : ResolutionError(msg) {}
+NotFound::NotFound(const std::string& msg) : ResolutionError(msg) {}
+
+ReceiverError::ReceiverError(const std::string& msg) : LinkError(msg) {}
+FetchError::FetchError(const std::string& msg) : ReceiverError(msg) {}
+NoMessageAvailable::NoMessageAvailable() : FetchError("No message to fetch") {}
+
+SenderError::SenderError(const std::string& msg) : LinkError(msg) {}
+SendError::SendError(const std::string& msg) : SenderError(msg) {}
+MessageRejected::MessageRejected(const std::string& msg) : SendError(msg) {}
+TargetCapacityExceeded::TargetCapacityExceeded(const std::string& msg) : SendError(msg) {}
+OutOfCapacity::OutOfCapacity(const std::string& msg) : SendError(msg) {}
+
+SessionError::SessionError(const std::string& msg) : MessagingException(msg) {}
+SessionClosed::SessionClosed() : SessionError("Session Closed") {}
+
+TransactionError::TransactionError(const std::string& msg) : SessionError(msg) {}
+TransactionAborted::TransactionAborted(const std::string& msg) : TransactionError(msg) {}
+TransactionUnknown::TransactionUnknown(const std::string& msg) : TransactionError(msg) {}
+UnauthorizedAccess::UnauthorizedAccess(const std::string& msg) : SessionError(msg) {}
+
+ConnectionError::ConnectionError(const std::string& msg) : MessagingException(msg) {}
+ProtocolVersionError::ProtocolVersionError(const std::string& msg) : ConnectionError(msg) {}
+AuthenticationFailure::AuthenticationFailure(const std::string& msg) : ConnectionError(msg) {}
+
+TransportFailure::TransportFailure(const std::string& msg) : MessagingException(msg) {}
+
+}} // namespace qpid::messaging
diff --git a/qpid/cpp/src/qpid/pointer_to_other.h b/qpid/cpp/src/qpid/pointer_to_other.h
new file mode 100644
index 0000000000..a99dc89658
--- /dev/null
+++ b/qpid/cpp/src/qpid/pointer_to_other.h
@@ -0,0 +1,62 @@
+#ifndef QPID_POINTERTOOTHER_H
+#define QPID_POINTERTOOTHER_H
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+namespace qpid {
+
+// Defines the same pointer type (raw or smart) to another pointee type
+
+template<class T, class U>
+struct pointer_to_other;
+
+template<class T, class U,
+ template<class> class Sp>
+struct pointer_to_other< Sp<T>, U >
+ {
+ typedef Sp<U> type;
+ };
+
+template<class T, class T2, class U,
+ template<class, class> class Sp>
+struct pointer_to_other< Sp<T, T2>, U >
+ {
+ typedef Sp<U, T2> type;
+ };
+
+template<class T, class T2, class T3, class U,
+ template<class, class, class> class Sp>
+struct pointer_to_other< Sp<T, T2, T3>, U >
+ {
+ typedef Sp<U, T2, T3> type;
+ };
+
+template<class T, class U>
+struct pointer_to_other< T*, U >
+{
+ typedef U* type;
+};
+
+} // namespace qpid
+
+
+
+#endif /*!QPID_POINTERTOOTHER_H*/
diff --git a/qpid/cpp/src/qpid/ptr_map.h b/qpid/cpp/src/qpid/ptr_map.h
new file mode 100644
index 0000000000..6ffcd48e89
--- /dev/null
+++ b/qpid/cpp/src/qpid/ptr_map.h
@@ -0,0 +1,57 @@
+#ifndef QPID_PTR_MAP
+#define QPID_PTR_MAP
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <boost/ptr_container/ptr_map.hpp>
+#include <boost/utility/enable_if.hpp>
+#include <boost/type_traits/is_same.hpp>
+#include <boost/type_traits/remove_const.hpp>
+
+namespace qpid {
+
+/** @file
+ * Workaround for API change between boost 1.33 and 1.34.
+ *
+ * To be portable across these versions, code using boost::ptr_map
+ * iterators should use ptr_map_ptr(i) to get the pointer from
+ * boost::ptr_map::iterator i.
+ *
+ * @see http://www.boost.org/libs/ptr_container/doc/ptr_container.html#upgrading-from-boost-v-1-33
+ */
+
+
+typedef boost::is_same<boost::ptr_map<int, int>::iterator::value_type, int> IsOldPtrMap;
+
+template <class Iter>
+typename boost::enable_if<IsOldPtrMap, typename Iter::value_type*>::type
+ptr_map_ptr(const Iter& i) { return &*i; }
+
+template <class Iter>
+typename boost::disable_if<IsOldPtrMap,
+ typename boost::remove_const<typename Iter::value_type::second_type>::type
+ >::type
+ptr_map_ptr(const Iter& i) { return i->second; }
+
+} // namespace qpid
+
+#endif /*!QPID_PTR_MAP*/
diff --git a/qpid/cpp/src/qpid/store/CMakeLists.txt b/qpid/cpp/src/qpid/store/CMakeLists.txt
new file mode 100644
index 0000000000..ee7894730a
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/CMakeLists.txt
@@ -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.
+#
+
+project(qpidc_store)
+
+#set (CMAKE_VERBOSE_MAKEFILE ON) # for debugging
+
+include_directories( ${Boost_INCLUDE_DIR} )
+
+include_directories( ${CMAKE_CURRENT_SOURCE_DIR} )
+include_directories( ${CMAKE_HOME_DIRECTORY}/include )
+
+set (store_SOURCES
+ MessageStorePlugin.cpp
+ )
+add_library (store MODULE ${store_SOURCES})
+target_link_libraries (store qpidbroker qpidcommon)
+if (CMAKE_COMPILER_IS_GNUCXX)
+ set (GCC_CATCH_UNDEFINED "-Wl,--no-undefined")
+ # gcc on SunOS uses native linker whose "-z defs" is too fussy
+ if (CMAKE_SYSTEM_NAME STREQUAL SunOS)
+ set (GCC_CATCH_UNDEFINED "")
+ endif (CMAKE_SYSTEM_NAME STREQUAL SunOS)
+
+ set_target_properties (store PROPERTIES
+ PREFIX ""
+ COMPILE_DEFINITIONS _IN_QPID_BROKER
+ LINK_FLAGS "${GCC_CATCH_UNDEFINED}")
+endif (CMAKE_COMPILER_IS_GNUCXX)
+
+if (CMAKE_SYSTEM_NAME STREQUAL Windows)
+ if (MSVC)
+ add_definitions(
+ /D "NOMINMAX"
+ /D "WIN32_LEAN_AND_MEAN"
+ )
+ endif (MSVC)
+endif (CMAKE_SYSTEM_NAME STREQUAL Windows)
+
+set_target_properties (store PROPERTIES
+ COMPILE_DEFINITIONS _IN_QPID_BROKER
+ VERSION ${qpidc_version})
+install (TARGETS store # RUNTIME
+ DESTINATION ${QPIDD_MODULE_DIR}
+ COMPONENT ${QPID_COMPONENT_BROKER})
+
+# Build the MS SQL Storage Provider plugin
+set (mssql_default ON)
+if (NOT CMAKE_SYSTEM_NAME STREQUAL Windows)
+ set(mssql_default OFF)
+endif (NOT CMAKE_SYSTEM_NAME STREQUAL Windows)
+option(BUILD_MSSQL "Build MS SQL Store provider plugin" ${mssql_default})
+if (BUILD_MSSQL)
+ add_library (mssql_store MODULE
+ ms-sql/MsSqlProvider.cpp
+ ms-sql/AmqpTransaction.cpp
+ ms-sql/BindingRecordset.cpp
+ ms-sql/BlobAdapter.cpp
+ ms-sql/BlobEncoder.cpp
+ ms-sql/BlobRecordset.cpp
+ ms-sql/DatabaseConnection.cpp
+ ms-sql/MessageMapRecordset.cpp
+ ms-sql/MessageRecordset.cpp
+ ms-sql/Recordset.cpp
+ ms-sql/SqlTransaction.cpp
+ ms-sql/State.cpp
+ ms-sql/TplRecordset.cpp
+ ms-sql/VariantHelper.cpp)
+ set_target_properties (mssql_store PROPERTIES COMPILE_DEFINITIONS _IN_QPID_BROKER)
+ target_link_libraries (mssql_store qpidbroker qpidcommon)
+ install (TARGETS mssql_store # RUNTIME
+ DESTINATION ${QPIDD_MODULE_DIR}
+ COMPONENT ${QPID_COMPONENT_BROKER})
+endif (BUILD_MSSQL)
+
+# Build the MS SQL-CLFS Storage Provider plugin
+set (msclfs_default ON)
+if (NOT CMAKE_SYSTEM_NAME STREQUAL Windows)
+ set(msclfs_default OFF)
+endif (NOT CMAKE_SYSTEM_NAME STREQUAL Windows)
+option(BUILD_MSCLFS "Build MS hybrid SQL-CLFS Store provider plugin" ${msclfs_default})
+if (BUILD_MSCLFS)
+ add_library (msclfs_store MODULE
+ ms-clfs/MsSqlClfsProvider.cpp
+ ms-clfs/Log.cpp
+ ms-clfs/MessageLog.cpp
+ ms-clfs/Messages.cpp
+ ms-clfs/Transaction.cpp
+ ms-clfs/TransactionLog.cpp
+ ms-sql/BindingRecordset.cpp
+ ms-sql/BlobAdapter.cpp
+ ms-sql/BlobEncoder.cpp
+ ms-sql/BlobRecordset.cpp
+ ms-sql/DatabaseConnection.cpp
+ ms-sql/Recordset.cpp
+ ms-sql/State.cpp
+ ms-sql/VariantHelper.cpp)
+ include_directories(ms-sql)
+ set_target_properties (msclfs_store PROPERTIES COMPILE_DEFINITIONS _IN_QPID_BROKER)
+ target_link_libraries (msclfs_store qpidbroker qpidcommon clfsw32.lib)
+ install (TARGETS msclfs_store # RUNTIME
+ DESTINATION ${QPIDD_MODULE_DIR}
+ COMPONENT ${QPID_COMPONENT_BROKER})
+endif (BUILD_MSCLFS)
diff --git a/qpid/cpp/src/qpid/store/MessageStorePlugin.cpp b/qpid/cpp/src/qpid/store/MessageStorePlugin.cpp
new file mode 100644
index 0000000000..b876bd6b6d
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/MessageStorePlugin.cpp
@@ -0,0 +1,463 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "MessageStorePlugin.h"
+#include "StorageProvider.h"
+#include "StoreException.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/Plugin.h"
+#include "qpid/Options.h"
+#include "qpid/DataDir.h"
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace store {
+
+/*
+ * The MessageStore pointer given to the Broker points to static storage.
+ * Thus, it cannot be deleted, especially by the broker. To prevent deletion,
+ * this no-op deleter is used with the boost::shared_ptr. When the last
+ * shared_ptr is destroyed, the deleter is called rather than delete().
+ */
+namespace {
+ class NoopDeleter {
+ public:
+ NoopDeleter() {}
+ void operator()(qpid::broker::MessageStore * /*p*/) {}
+ };
+}
+
+static MessageStorePlugin static_instance_registers_plugin;
+
+
+MessageStorePlugin::StoreOptions::StoreOptions(const std::string& name) :
+ qpid::Options(name)
+{
+ addOptions()
+ ("storage-provider", qpid::optValue(providerName, "PROVIDER"),
+ "Name of the storage provider to use.")
+ ;
+}
+
+
+void
+MessageStorePlugin::earlyInitialize (qpid::Plugin::Target& target)
+{
+ qpid::broker::Broker* b =
+ dynamic_cast<qpid::broker::Broker*>(&target);
+ if (0 == b)
+ return; // Only listen to Broker targets
+
+ broker = b;
+
+ // See if there are any storage provider plugins ready. If not, we can't
+ // do a message store.
+ qpid::Plugin::earlyInitAll(*this);
+
+ if (providers.empty()) {
+ QPID_LOG(warning,
+ "Message store plugin: No storage providers available.");
+ provider = providers.end();
+ return;
+ }
+ if (!options.providerName.empty()) {
+ // If specific one was chosen, locate it in loaded set of providers.
+ provider = providers.find(options.providerName);
+ if (provider == providers.end())
+ throw Exception("Message store plugin: storage provider '" +
+ options.providerName +
+ "' does not exist.");
+ }
+ else {
+ // No specific provider chosen; if there's only one, use it. Else
+ // report the need to pick one.
+ if (providers.size() > 1) {
+ provider = providers.end();
+ throw Exception("Message store plugin: multiple provider plugins "
+ "loaded; must either load only one or select one "
+ "using --storage-provider");
+ }
+ provider = providers.begin();
+ }
+
+ provider->second->activate(*this);
+ NoopDeleter d;
+ boost::shared_ptr<qpid::broker::MessageStore> sp(this, d);
+ broker->setStore(sp);
+ target.addFinalizer(boost::bind(&MessageStorePlugin::finalizeMe, this));
+}
+
+void
+MessageStorePlugin::initialize(qpid::Plugin::Target& target)
+{
+ qpid::broker::Broker* broker =
+ dynamic_cast<qpid::broker::Broker*>(&target);
+ if (0 == broker)
+ return; // Only listen to Broker targets
+
+ // Pass along the initialize step to the provider that's activated.
+ if (provider != providers.end()) {
+ provider->second->initialize(*this);
+ }
+ // qpid::Plugin::initializeAll(*this);
+}
+
+void
+MessageStorePlugin::finalizeMe()
+{
+ finalize(); // Call finalizers on any Provider plugins
+}
+
+void
+MessageStorePlugin::providerAvailable(const std::string name,
+ StorageProvider *be)
+{
+ ProviderMap::value_type newSp(name, be);
+ std::pair<ProviderMap::iterator, bool> inserted = providers.insert(newSp);
+ if (inserted.second == false)
+ QPID_LOG(warning, "Storage provider " << name << " duplicate; ignored.");
+}
+
+
+/**
+ * Record the existence of a durable queue
+ */
+void
+MessageStorePlugin::create(broker::PersistableQueue& queue,
+ const framing::FieldTable& args)
+{
+ if (queue.getName().size() == 0)
+ {
+ QPID_LOG(error,
+ "Cannot create store for empty (null) queue name - "
+ "ignoring and attempting to continue.");
+ return;
+ }
+ if (queue.getPersistenceId()) {
+ THROW_STORE_EXCEPTION("Queue already created: " + queue.getName());
+ }
+ provider->second->create(queue, args);
+}
+
+/**
+ * Destroy a durable queue
+ */
+void
+MessageStorePlugin::destroy(broker::PersistableQueue& queue)
+{
+ provider->second->destroy(queue);
+}
+
+/**
+ * Record the existence of a durable exchange
+ */
+void
+MessageStorePlugin::create(const broker::PersistableExchange& exchange,
+ const framing::FieldTable& args)
+{
+ if (exchange.getPersistenceId()) {
+ THROW_STORE_EXCEPTION("Exchange already created: " + exchange.getName());
+ }
+ provider->second->create(exchange, args);
+}
+
+/**
+ * Destroy a durable exchange
+ */
+void
+MessageStorePlugin::destroy(const broker::PersistableExchange& exchange)
+{
+ provider->second->destroy(exchange);
+}
+
+/**
+ * Record a binding
+ */
+void
+MessageStorePlugin::bind(const broker::PersistableExchange& exchange,
+ const broker::PersistableQueue& queue,
+ const std::string& key,
+ const framing::FieldTable& args)
+{
+ provider->second->bind(exchange, queue, key, args);
+}
+
+/**
+ * Forget a binding
+ */
+void
+MessageStorePlugin::unbind(const broker::PersistableExchange& exchange,
+ const broker::PersistableQueue& queue,
+ const std::string& key,
+ const framing::FieldTable& args)
+{
+ provider->second->unbind(exchange, queue, key, args);
+}
+
+/**
+ * Record generic durable configuration
+ */
+void
+MessageStorePlugin::create(const broker::PersistableConfig& config)
+{
+ if (config.getPersistenceId()) {
+ THROW_STORE_EXCEPTION("Config item already created: " +
+ config.getName());
+ }
+ provider->second->create(config);
+}
+
+/**
+ * Destroy generic durable configuration
+ */
+void
+MessageStorePlugin::destroy(const broker::PersistableConfig& config)
+{
+ provider->second->destroy(config);
+}
+
+/**
+ * Stores a message before it has been enqueued
+ * (enqueueing automatically stores the message so this is
+ * only required if storage is required prior to that
+ * point).
+ */
+void
+MessageStorePlugin::stage(const boost::intrusive_ptr<broker::PersistableMessage>& msg)
+{
+ if (msg->getPersistenceId() == 0) {
+ provider->second->stage(msg);
+ }
+}
+
+/**
+ * Destroys a previously staged message. This only needs
+ * to be called if the message is never enqueued. (Once
+ * enqueued, deletion will be automatic when the message
+ * is dequeued from all queues it was enqueued onto).
+ */
+void
+MessageStorePlugin::destroy(broker::PersistableMessage& msg)
+{
+ if (msg.getPersistenceId())
+ provider->second->destroy(msg);
+}
+
+/**
+ * Appends content to a previously staged message
+ */
+void
+MessageStorePlugin::appendContent
+ (const boost::intrusive_ptr<const broker::PersistableMessage>& msg,
+ const std::string& data)
+{
+ if (msg->getPersistenceId())
+ provider->second->appendContent(msg, data);
+ else
+ THROW_STORE_EXCEPTION("Cannot append content. Message not known to store!");
+}
+
+/**
+ * Loads (a section) of content data for the specified
+ * message (previously stored through a call to stage or
+ * enqueue) into data. The offset refers to the content
+ * only (i.e. an offset of 0 implies that the start of the
+ * content should be loaded, not the headers or related
+ * meta-data).
+ */
+void
+MessageStorePlugin::loadContent(const broker::PersistableQueue& queue,
+ const boost::intrusive_ptr<const broker::PersistableMessage>& msg,
+ std::string& data,
+ uint64_t offset,
+ uint32_t length)
+{
+ if (msg->getPersistenceId())
+ provider->second->loadContent(queue, msg, data, offset, length);
+ else
+ THROW_STORE_EXCEPTION("Cannot load content. Message not known to store!");
+}
+
+/**
+ * Enqueues a message, storing the message if it has not
+ * been previously stored and recording that the given
+ * message is on the given queue.
+ *
+ * Note: The operation is asynchronous so the return of this function does
+ * not mean the operation is complete.
+ */
+void
+MessageStorePlugin::enqueue(broker::TransactionContext* ctxt,
+ const boost::intrusive_ptr<broker::PersistableMessage>& msg,
+ const broker::PersistableQueue& queue)
+{
+ if (queue.getPersistenceId() == 0) {
+ THROW_STORE_EXCEPTION("Queue not created: " + queue.getName());
+ }
+ provider->second->enqueue(ctxt, msg, queue);
+}
+
+/**
+ * Dequeues a message, recording that the given message is
+ * no longer on the given queue and deleting the message
+ * if it is no longer on any other queue.
+ *
+ * Note: The operation is asynchronous so the return of this function does
+ * not mean the operation is complete.
+ */
+void
+MessageStorePlugin::dequeue(broker::TransactionContext* ctxt,
+ const boost::intrusive_ptr<broker::PersistableMessage>& msg,
+ const broker::PersistableQueue& queue)
+{
+ provider->second->dequeue(ctxt, msg, queue);
+}
+
+/**
+ * Flushes all async messages to disk for the specified queue
+ *
+ * Note: The operation is asynchronous so the return of this function does
+ * not mean the operation is complete.
+ */
+void
+MessageStorePlugin::flush(const broker::PersistableQueue& queue)
+{
+ provider->second->flush(queue);
+}
+
+/**
+ * Returns the number of outstanding AIO's for a given queue
+ *
+ * If 0, than all the enqueue / dequeues have been stored
+ * to disk.
+ */
+uint32_t
+MessageStorePlugin::outstandingQueueAIO(const broker::PersistableQueue& queue)
+{
+ return provider->second->outstandingQueueAIO(queue);
+}
+
+std::auto_ptr<broker::TransactionContext>
+MessageStorePlugin::begin()
+{
+ return provider->second->begin();
+}
+
+std::auto_ptr<broker::TPCTransactionContext>
+MessageStorePlugin::begin(const std::string& xid)
+{
+ return provider->second->begin(xid);
+}
+
+void
+MessageStorePlugin::prepare(broker::TPCTransactionContext& ctxt)
+{
+ provider->second->prepare(ctxt);
+}
+
+void
+MessageStorePlugin::commit(broker::TransactionContext& ctxt)
+{
+ provider->second->commit(ctxt);
+}
+
+void
+MessageStorePlugin::abort(broker::TransactionContext& ctxt)
+{
+ provider->second->abort(ctxt);
+}
+
+void
+MessageStorePlugin::collectPreparedXids(std::set<std::string>& xids)
+{
+ provider->second->collectPreparedXids(xids);
+}
+
+/**
+ * Request recovery of queue and message state; inherited from Recoverable
+ */
+void
+MessageStorePlugin::recover(broker::RecoveryManager& recoverer)
+{
+ ExchangeMap exchanges;
+ QueueMap queues;
+ MessageMap messages;
+ MessageQueueMap messageQueueMap;
+ std::vector<std::string> xids;
+ PreparedTransactionMap dtxMap;
+
+ provider->second->recoverConfigs(recoverer);
+ provider->second->recoverExchanges(recoverer, exchanges);
+ provider->second->recoverQueues(recoverer, queues);
+ provider->second->recoverBindings(recoverer, exchanges, queues);
+ // Important to recover messages before transactions in the SQL-CLFS
+ // case. If this becomes a problem, it may be possible to resolve it.
+ // If in doubt please raise a jira and notify Steve Huston
+ // <shuston@riverace.com>.
+ provider->second->recoverMessages(recoverer, messages, messageQueueMap);
+ provider->second->recoverTransactions(recoverer, dtxMap);
+ // Enqueue msgs where needed.
+ for (MessageQueueMap::const_iterator i = messageQueueMap.begin();
+ i != messageQueueMap.end();
+ ++i) {
+ // Locate the message corresponding to the current message Id
+ MessageMap::const_iterator iMsg = messages.find(i->first);
+ if (iMsg == messages.end()) {
+ std::ostringstream oss;
+ oss << "No matching message trying to re-enqueue message "
+ << i->first;
+ THROW_STORE_EXCEPTION(oss.str());
+ }
+ broker::RecoverableMessage::shared_ptr msg = iMsg->second;
+ // Now for each queue referenced in the queue map, locate it
+ // and re-enqueue the message.
+ for (std::vector<QueueEntry>::const_iterator j = i->second.begin();
+ j != i->second.end();
+ ++j) {
+ // Locate the queue corresponding to the current queue Id
+ QueueMap::const_iterator iQ = queues.find(j->queueId);
+ if (iQ == queues.end()) {
+ std::ostringstream oss;
+ oss << "No matching queue trying to re-enqueue message "
+ << " on queue Id " << j->queueId;
+ THROW_STORE_EXCEPTION(oss.str());
+ }
+ // Messages involved in prepared transactions have their status
+ // updated accordingly. First, though, restore a message that
+ // is expected to be on a queue, including non-transacted
+ // messages and those pending dequeue in a dtx.
+ if (j->tplStatus != QueueEntry::ADDING)
+ iQ->second->recover(msg);
+ switch(j->tplStatus) {
+ case QueueEntry::ADDING:
+ dtxMap[j->xid]->enqueue(iQ->second, msg);
+ break;
+ case QueueEntry::REMOVING:
+ dtxMap[j->xid]->dequeue(iQ->second, msg);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+}
+
+}} // namespace qpid::store
diff --git a/qpid/cpp/src/qpid/store/MessageStorePlugin.h b/qpid/cpp/src/qpid/store/MessageStorePlugin.h
new file mode 100644
index 0000000000..5290fc16db
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/MessageStorePlugin.h
@@ -0,0 +1,280 @@
+#ifndef QPID_STORE_MESSAGESTOREPLUGIN_H
+#define QPID_STORE_MESSAGESTOREPLUGIN_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/Plugin.h"
+#include "qpid/Options.h"
+#include "qpid/broker/MessageStore.h"
+//#include "qpid/management/Manageable.h"
+
+#include <string>
+
+using namespace qpid;
+
+namespace qpid {
+
+namespace broker {
+class Broker;
+class PersistableExchange;
+class PersistableMessage;
+class PersistableQueue;
+}
+
+namespace store {
+
+class StorageProvider;
+
+/**
+ * @class MessageStorePlugin
+ *
+ * MessageStorePlugin is the front end of the persistent message store
+ * plugin. It is responsible for coordinating recovery, initialization,
+ * transactions (both local and distributed), flow-to-disk loading and
+ * unloading and persisting broker state (queues, bindings etc.).
+ * Actual storage operations are carried out by a message store storage
+ * provider that implements the qpid::store::StorageProvider interface.
+ */
+class MessageStorePlugin :
+ public qpid::Plugin,
+ public qpid::broker::MessageStore, // Frontend classes
+ public qpid::Plugin::Target // Provider target
+ // @TODO Need a mgmt story for this. Maybe allow r/o access to provider store info? public qpid::management::Manageable
+{
+ public:
+ MessageStorePlugin() : broker(0) {}
+
+ /**
+ * @name Methods inherited from qpid::Plugin
+ */
+ //@{
+ virtual Options* getOptions() { return &options; }
+ virtual void earlyInitialize (Plugin::Target& target);
+ virtual void initialize(Plugin::Target& target);
+ //@}
+
+ /// Finalizer; calls Target::finalize() to run finalizers on
+ /// StorageProviders.
+ void finalizeMe();
+
+ /**
+ * Called by StorageProvider instances during the earlyInitialize sequence.
+ * Each StorageProvider must supply a unique name by which it is known and a
+ * pointer to itself.
+ */
+ virtual void providerAvailable(const std::string name, StorageProvider *be);
+
+ /**
+ * @name Methods inherited from qpid::broker::MessageStore
+ */
+
+ /**
+ * Record the existence of a durable queue
+ */
+ virtual void create(broker::PersistableQueue& queue,
+ const framing::FieldTable& args);
+ /**
+ * Destroy a durable queue
+ */
+ virtual void destroy(broker::PersistableQueue& queue);
+
+ /**
+ * Record the existence of a durable exchange
+ */
+ virtual void create(const broker::PersistableExchange& exchange,
+ const framing::FieldTable& args);
+ /**
+ * Destroy a durable exchange
+ */
+ virtual void destroy(const broker::PersistableExchange& exchange);
+
+ /**
+ * Record a binding
+ */
+ virtual void bind(const broker::PersistableExchange& exchange,
+ const broker::PersistableQueue& queue,
+ const std::string& key,
+ const framing::FieldTable& args);
+
+ /**
+ * Forget a binding
+ */
+ virtual void unbind(const broker::PersistableExchange& exchange,
+ const broker::PersistableQueue& queue,
+ const std::string& key,
+ const framing::FieldTable& args);
+
+ /**
+ * Record generic durable configuration
+ */
+ virtual void create(const broker::PersistableConfig& config);
+
+ /**
+ * Destroy generic durable configuration
+ */
+ virtual void destroy(const broker::PersistableConfig& config);
+
+ /**
+ * Stores a message before it has been enqueued
+ * (enqueueing automatically stores the message so this is
+ * only required if storage is required prior to that
+ * point). If the message has not yet been stored it will
+ * store the headers as well as any content passed in. A
+ * persistence id will be set on the message which can be
+ * used to load the content or to append to it.
+ */
+ virtual void stage(const boost::intrusive_ptr<broker::PersistableMessage>& msg);
+
+ /**
+ * Destroys a previously staged message. This only needs
+ * to be called if the message is never enqueued. (Once
+ * enqueued, deletion will be automatic when the message
+ * is dequeued from all queues it was enqueued onto).
+ */
+ virtual void destroy(broker::PersistableMessage& msg);
+
+ /**
+ * Appends content to a previously staged message
+ */
+ virtual void appendContent(const boost::intrusive_ptr<const broker::PersistableMessage>& msg,
+ const std::string& data);
+
+ /**
+ * Loads (a section) of content data for the specified
+ * message (previously stored through a call to stage or
+ * enqueue) into data. The offset refers to the content
+ * only (i.e. an offset of 0 implies that the start of the
+ * content should be loaded, not the headers or related
+ * meta-data).
+ */
+ virtual void loadContent(const broker::PersistableQueue& queue,
+ const boost::intrusive_ptr<const broker::PersistableMessage>& msg,
+ std::string& data,
+ uint64_t offset,
+ uint32_t length);
+
+ /**
+ * Enqueues a message, storing the message if it has not
+ * been previously stored and recording that the given
+ * message is on the given queue.
+ *
+ * Note: The operation is asynchronous so the return of this function does
+ * not mean the operation is complete.
+ *
+ * @param msg the message to enqueue
+ * @param queue the name of the queue onto which it is to be enqueued
+ * @param xid (a pointer to) an identifier of the
+ * distributed transaction in which the operation takes
+ * place or null for 'local' transactions
+ */
+ virtual void enqueue(broker::TransactionContext* ctxt,
+ const boost::intrusive_ptr<broker::PersistableMessage>& msg,
+ const broker::PersistableQueue& queue);
+
+ /**
+ * Dequeues a message, recording that the given message is
+ * no longer on the given queue and deleting the message
+ * if it is no longer on any other queue.
+ *
+ *
+ * Note: The operation is asynchronous so the return of this function does
+ * not mean the operation is complete.
+ *
+ * @param msg the message to dequeue
+ * @param queue the name of the queue from which it is to be dequeued
+ * @param xid (a pointer to) an identifier of the
+ * distributed transaction in which the operation takes
+ * place or null for 'local' transactions
+ */
+ virtual void dequeue(broker::TransactionContext* ctxt,
+ const boost::intrusive_ptr<broker::PersistableMessage>& msg,
+ const broker::PersistableQueue& queue);
+
+ /**
+ * Flushes all async messages to disk for the specified queue
+ *
+ *
+ * Note: The operation is asynchronous so the return of this function does
+ * not mean the operation is complete.
+ *
+ * @param queue the name of the queue from which it is to be dequeued
+ */
+ virtual void flush(const broker::PersistableQueue& queue);
+
+ /**
+ * Returns the number of outstanding AIO's for a given queue
+ *
+ * If 0, than all the enqueue / dequeues have been stored
+ * to disk
+ *
+ * @param queue the name of the queue to check for outstanding AIO
+ */
+ virtual uint32_t outstandingQueueAIO(const broker::PersistableQueue& queue);
+ //@}
+
+ /**
+ * @name Methods inherited from qpid::broker::TransactionalStore
+ */
+ //@{
+ std::auto_ptr<broker::TransactionContext> begin();
+
+ std::auto_ptr<broker::TPCTransactionContext> begin(const std::string& xid);
+
+ void prepare(broker::TPCTransactionContext& ctxt);
+
+ void commit(broker::TransactionContext& ctxt);
+
+ void abort(broker::TransactionContext& ctxt);
+
+ void collectPreparedXids(std::set<std::string>& xids);
+ //@}
+
+ /**
+ * Request recovery of queue and message state; inherited from Recoverable
+ */
+ virtual void recover(broker::RecoveryManager& recoverer);
+
+ // inline management::Manageable::status_t ManagementMethod (uint32_t, management::Args&, std::string&)
+ // { return management::Manageable::STATUS_OK; }
+
+ // So storage provider can get the broker info.
+ broker::Broker *getBroker() { return broker; }
+
+ protected:
+
+ struct StoreOptions : public qpid::Options {
+ StoreOptions(const std::string& name="Store Options");
+ std::string providerName;
+ };
+ StoreOptions options;
+
+ typedef std::map<std::string, StorageProvider*> ProviderMap;
+ ProviderMap providers;
+ ProviderMap::const_iterator provider;
+
+ broker::Broker *broker;
+
+}; // class MessageStoreImpl
+
+}} // namespace qpid::store
+
+#endif /* QPID_SERIALIZER_H */
diff --git a/qpid/cpp/src/qpid/store/StorageProvider.h b/qpid/cpp/src/qpid/store/StorageProvider.h
new file mode 100644
index 0000000000..de12ffb869
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/StorageProvider.h
@@ -0,0 +1,329 @@
+#ifndef QPID_STORE_STORAGEPROVIDER_H
+#define QPID_STORE_STORAGEPROVIDER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <map>
+#include <stdexcept>
+#include <vector>
+#include "qpid/Exception.h"
+#include "qpid/Plugin.h"
+#include "qpid/Options.h"
+#include "qpid/broker/MessageStore.h"
+
+using qpid::broker::PersistableConfig;
+using qpid::broker::PersistableExchange;
+using qpid::broker::PersistableMessage;
+using qpid::broker::PersistableQueue;
+
+namespace qpid {
+namespace store {
+
+typedef std::map<uint64_t, qpid::broker::RecoverableExchange::shared_ptr>
+ ExchangeMap;
+typedef std::map<uint64_t, qpid::broker::RecoverableQueue::shared_ptr>
+ QueueMap;
+typedef std::map<uint64_t, qpid::broker::RecoverableMessage::shared_ptr>
+ MessageMap;
+// Msg Id -> vector of queue entries where message is queued
+struct QueueEntry {
+ enum TplStatus { NONE = 0, ADDING = 1, REMOVING = 2 };
+ uint64_t queueId;
+ TplStatus tplStatus;
+ std::string xid;
+
+ QueueEntry(uint64_t id, TplStatus tpl = NONE, const std::string& x = "")
+ : queueId(id), tplStatus(tpl), xid(x) {}
+
+ bool operator==(const QueueEntry& rhs) const {
+ if (queueId != rhs.queueId) return false;
+ if (tplStatus == NONE && rhs.tplStatus == NONE) return true;
+ return xid == rhs.xid;
+ }
+};
+typedef std::map<uint64_t, std::vector<QueueEntry> > MessageQueueMap;
+typedef std::map<std::string, qpid::broker::RecoverableTransaction::shared_ptr>
+ PreparedTransactionMap;
+
+class MessageStorePlugin;
+
+/**
+ * @class StorageProvider
+ *
+ * StorageProvider defines the interface for the storage provider plugin to the
+ * Qpid broker persistence store plugin.
+ *
+ * @TODO Should StorageProvider also inherit from MessageStore? If so, then
+ * maybe remove Recoverable from MessageStore's inheritance and move it
+ * to MessageStorePlugin? In any event, somehow the discardInit() feature
+ * needs to get added here.
+ */
+class StorageProvider : public qpid::Plugin, public qpid::broker::MessageStore
+{
+public:
+
+ class Exception : public qpid::Exception
+ {
+ public:
+ virtual ~Exception() throw() {}
+ virtual const char *what() const throw() = 0;
+ };
+
+ /**
+ * @name Methods inherited from qpid::Plugin
+ */
+ //@{
+ /**
+ * Return a pointer to the provider's options. The options will be
+ * updated during option parsing by the host program; therefore, the
+ * referenced Options object must remain valid past this function's return.
+ *
+ * @return An options group or 0 for no options. Default returns 0.
+ * Plugin retains ownership of return value.
+ */
+ virtual qpid::Options* getOptions() = 0;
+
+ /**
+ * Initialize Plugin functionality on a Target, called before
+ * initializing the target.
+ *
+ * StorageProviders should respond only to Targets of class
+ * qpid::store::MessageStorePlugin and ignore all others.
+ *
+ * When called, the provider should invoke the method
+ * qpid::store::MessageStorePlugin::providerAvailable() to alert the
+ * message store of StorageProvider's availability.
+ *
+ * Called before the target itself is initialized.
+ */
+ virtual void earlyInitialize (Plugin::Target& target) = 0;
+
+ /**
+ * Initialize StorageProvider functionality. Called after initializing
+ * the target.
+ *
+ * StorageProviders should respond only to Targets of class
+ * qpid::store::MessageStorePlugin and ignore all others.
+ *
+ * Called after the target is fully initialized.
+ */
+ virtual void initialize(Plugin::Target& target) = 0;
+ //@}
+
+ /**
+ * Receive notification that this provider is the one that will actively
+ * handle storage for the target. If the provider is to be used, this
+ * method will be called after earlyInitialize() and before any
+ * recovery operations (recovery, in turn, precedes call to initialize()).
+ * Thus, it is wise to not actually do any database ops from within
+ * earlyInitialize() - they can wait until activate() is called because
+ * at that point it is certain the database will be needed.
+ */
+ virtual void activate(MessageStorePlugin &store) = 0;
+
+ /**
+ * @name Methods inherited from qpid::broker::MessageStore
+ */
+
+ /**
+ * Record the existence of a durable queue
+ */
+ virtual void create(PersistableQueue& queue,
+ const qpid::framing::FieldTable& args) = 0;
+ /**
+ * Destroy a durable queue
+ */
+ virtual void destroy(PersistableQueue& queue) = 0;
+
+ /**
+ * Record the existence of a durable exchange
+ */
+ virtual void create(const PersistableExchange& exchange,
+ const qpid::framing::FieldTable& args) = 0;
+ /**
+ * Destroy a durable exchange
+ */
+ virtual void destroy(const PersistableExchange& exchange) = 0;
+
+ /**
+ * Record a binding
+ */
+ virtual void bind(const PersistableExchange& exchange,
+ const PersistableQueue& queue,
+ const std::string& key,
+ const qpid::framing::FieldTable& args) = 0;
+
+ /**
+ * Forget a binding
+ */
+ virtual void unbind(const PersistableExchange& exchange,
+ const PersistableQueue& queue,
+ const std::string& key,
+ const qpid::framing::FieldTable& args) = 0;
+
+ /**
+ * Record generic durable configuration
+ */
+ virtual void create(const PersistableConfig& config) = 0;
+
+ /**
+ * Destroy generic durable configuration
+ */
+ virtual void destroy(const PersistableConfig& config) = 0;
+
+ /**
+ * Stores a messages before it has been enqueued
+ * (enqueueing automatically stores the message so this is
+ * only required if storage is required prior to that
+ * point). If the message has not yet been stored it will
+ * store the headers as well as any content passed in. A
+ * persistence id will be set on the message which can be
+ * used to load the content or to append to it.
+ */
+ virtual void stage(const boost::intrusive_ptr<PersistableMessage>& msg) = 0;
+
+ /**
+ * Destroys a previously staged message. This only needs
+ * to be called if the message is never enqueued. (Once
+ * enqueued, deletion will be automatic when the message
+ * is dequeued from all queues it was enqueued onto).
+ */
+ virtual void destroy(PersistableMessage& msg) = 0;
+
+ /**
+ * Appends content to a previously staged message
+ */
+ virtual void appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg,
+ const std::string& data) = 0;
+
+ /**
+ * Loads (a section) of content data for the specified
+ * message (previously stored through a call to stage or
+ * enqueue) into data. The offset refers to the content
+ * only (i.e. an offset of 0 implies that the start of the
+ * content should be loaded, not the headers or related
+ * meta-data).
+ */
+ virtual void loadContent(const PersistableQueue& queue,
+ const boost::intrusive_ptr<const PersistableMessage>& msg,
+ std::string& data,
+ uint64_t offset,
+ uint32_t length) = 0;
+
+ /**
+ * Enqueues a message, storing the message if it has not
+ * been previously stored and recording that the given
+ * message is on the given queue.
+ *
+ * Note: that this is async so the return of the function does
+ * not mean the opperation is complete.
+ *
+ * @param msg the message to enqueue
+ * @param queue the name of the queue onto which it is to be enqueued
+ * @param xid (a pointer to) an identifier of the
+ * distributed transaction in which the operation takes
+ * place or null for 'local' transactions
+ */
+ virtual void enqueue(qpid::broker::TransactionContext* ctxt,
+ const boost::intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue& queue) = 0;
+
+ /**
+ * Dequeues a message, recording that the given message is
+ * no longer on the given queue and deleting the message
+ * if it is no longer on any other queue.
+ *
+ * Note: that this is async so the return of the function does
+ * not mean the opperation is complete.
+ *
+ * @param msg the message to dequeue
+ * @param queue the name of the queue from which it is to be dequeued
+ * @param xid (a pointer to) an identifier of the
+ * distributed transaction in which the operation takes
+ * place or null for 'local' transactions
+ */
+ virtual void dequeue(qpid::broker::TransactionContext* ctxt,
+ const boost::intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue& queue) = 0;
+
+ /**
+ * Flushes all async messages to disk for the specified queue
+ *
+ * Note: that this is async so the return of the function does
+ * not mean the opperation is complete.
+ *
+ * @param queue the name of the queue from which it is to be dequeued
+ */
+ virtual void flush(const qpid::broker::PersistableQueue& queue) = 0;
+
+ /**
+ * Returns the number of outstanding AIO's for a given queue
+ *
+ * If 0, than all the enqueue / dequeues have been stored
+ * to disk
+ *
+ * @param queue the name of the queue to check for outstanding AIO
+ */
+ virtual uint32_t outstandingQueueAIO(const PersistableQueue& queue) = 0;
+ //@}
+
+ /**
+ * @TODO This should probably not be here - it's only here because
+ * MessageStore inherits from Recoverable... maybe move that derivation.
+ *
+ * As it is now, we don't use this. Separate recover methods are
+ * declared below for individual types, which also set up maps of
+ * messages, queues, transactions for the main store plugin to handle
+ * properly.
+ *
+ * Request recovery of queue and message state.
+ */
+ virtual void recover(qpid::broker::RecoveryManager& /*recoverer*/) {}
+
+ /**
+ * @name Methods that do the recovery of the various objects that
+ * were saved.
+ */
+ //@{
+
+ /**
+ * Recover bindings.
+ */
+ virtual void recoverConfigs(qpid::broker::RecoveryManager& recoverer) = 0;
+ virtual void recoverExchanges(qpid::broker::RecoveryManager& recoverer,
+ ExchangeMap& exchangeMap) = 0;
+ virtual void recoverQueues(qpid::broker::RecoveryManager& recoverer,
+ QueueMap& queueMap) = 0;
+ virtual void recoverBindings(qpid::broker::RecoveryManager& recoverer,
+ const ExchangeMap& exchangeMap,
+ const QueueMap& queueMap) = 0;
+ virtual void recoverMessages(qpid::broker::RecoveryManager& recoverer,
+ MessageMap& messageMap,
+ MessageQueueMap& messageQueueMap) = 0;
+ virtual void recoverTransactions(qpid::broker::RecoveryManager& recoverer,
+ PreparedTransactionMap& dtxMap) = 0;
+ //@}
+};
+
+}} // namespace qpid::store
+
+#endif /* QPID_STORE_STORAGEPROVIDER_H */
diff --git a/qpid/cpp/src/qpid/store/StoreException.h b/qpid/cpp/src/qpid/store/StoreException.h
new file mode 100644
index 0000000000..1dc7f670ec
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/StoreException.h
@@ -0,0 +1,49 @@
+#ifndef QPID_STORE_STOREEXCEPTION_H
+#define QPID_STORE_STOREEXCEPTION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <exception>
+#include <boost/format.hpp>
+#include "StorageProvider.h"
+
+namespace qpid {
+namespace store {
+
+class StoreException : public std::exception
+{
+ std::string text;
+public:
+ StoreException(const std::string& _text) : text(_text) {}
+ StoreException(const std::string& _text,
+ const StorageProvider::Exception& cause)
+ : text(_text + ": " + cause.what()) {}
+ virtual ~StoreException() throw() {}
+ virtual const char* what() const throw() { return text.c_str(); }
+};
+
+#define THROW_STORE_EXCEPTION(MESSAGE) throw qpid::store::StoreException(boost::str(boost::format("%s (%s:%d)") % (MESSAGE) % __FILE__ % __LINE__))
+#define THROW_STORE_EXCEPTION_2(MESSAGE, EXCEPTION) throw qpid::store::StoreException(boost::str(boost::format("%s (%s:%d)") % (MESSAGE) % __FILE__ % __LINE__), EXCEPTION)
+
+}} // namespace qpid::store
+
+#endif /* QPID_STORE_STOREEXCEPTION_H */
diff --git a/qpid/cpp/src/qpid/store/ms-clfs/Log.cpp b/qpid/cpp/src/qpid/store/ms-clfs/Log.cpp
new file mode 100644
index 0000000000..e6cb10c133
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-clfs/Log.cpp
@@ -0,0 +1,182 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <windows.h>
+#include <clfsw32.h>
+#include <clfsmgmtw32.h>
+#include <sstream>
+#include <string>
+#include <vector>
+#include <stdlib.h>
+#include <qpid/sys/windows/check.h>
+
+#include "Log.h"
+
+namespace qpid {
+namespace store {
+namespace ms_clfs {
+
+Log::~Log()
+{
+ if (marshal != 0)
+ ::DeleteLogMarshallingArea(marshal);
+ ::CloseHandle(handle);
+}
+
+void
+Log::open(const std::string& path, const TuningParameters& params)
+{
+ this->containerSize = static_cast<ULONGLONG>(params.containerSize);
+ logPath = path;
+ std::string logSpec = "log:" + path;
+ size_t specLength = logSpec.length();
+ std::auto_ptr<wchar_t> wLogSpec(new wchar_t[specLength + 1]);
+ size_t converted;
+ mbstowcs_s(&converted,
+ wLogSpec.get(), specLength+1,
+ logSpec.c_str(), specLength);
+ handle = ::CreateLogFile(wLogSpec.get(),
+ GENERIC_WRITE | GENERIC_READ,
+ 0,
+ 0,
+ OPEN_ALWAYS,
+ 0);
+ QPID_WINDOWS_CHECK_NOT(handle, INVALID_HANDLE_VALUE);
+ CLFS_INFORMATION info;
+ ULONG infoSize = sizeof(info);
+ BOOL ok = ::GetLogFileInformation(handle, &info, &infoSize);
+ QPID_WINDOWS_CHECK_NOT(ok, 0);
+ ok = ::RegisterManageableLogClient(handle, 0);
+ QPID_WINDOWS_CHECK_NOT(ok, 0);
+
+ // Set up policies for how many containers to initially create and how
+ // large each container should be. Also, auto-grow the log when container
+ // space runs out.
+ CLFS_MGMT_POLICY logPolicy;
+ logPolicy.Version = CLFS_MGMT_POLICY_VERSION;
+ logPolicy.LengthInBytes = sizeof(logPolicy);
+ logPolicy.PolicyFlags = 0;
+
+ // If this is the first time this log is opened, give an opportunity to
+ // initialize its content.
+ bool needInitialize(false);
+ if (info.TotalContainers == 0) {
+ // New log; set the configured container size and create the
+ // initial set of containers.
+ logPolicy.PolicyType = ClfsMgmtPolicyNewContainerSize;
+ logPolicy.PolicyParameters.NewContainerSize.SizeInBytes = containerSize;
+ ok = ::InstallLogPolicy(handle, &logPolicy);
+ QPID_WINDOWS_CHECK_NOT(ok, 0);
+
+ ULONGLONG desired(params.containers), actual(0);
+ ok = ::SetLogFileSizeWithPolicy(handle, &desired, &actual);
+ QPID_WINDOWS_CHECK_NOT(ok, 0);
+
+ needInitialize = true;
+ }
+ // Ensure that the log is extended as needed and will shrink when 50%
+ // becomes unused.
+ logPolicy.PolicyType = ClfsMgmtPolicyAutoGrow;
+ logPolicy.PolicyParameters.AutoGrow.Enabled = 1;
+ ok = ::InstallLogPolicy(handle, &logPolicy);
+ QPID_WINDOWS_CHECK_NOT(ok, 0);
+ logPolicy.PolicyType = ClfsMgmtPolicyAutoShrink;
+ logPolicy.PolicyParameters.AutoShrink.Percentage = params.shrinkPct;
+ ok = ::InstallLogPolicy(handle, &logPolicy);
+ QPID_WINDOWS_CHECK_NOT(ok, 0);
+
+ // Need a marshaling area
+ ok = ::CreateLogMarshallingArea(handle,
+ NULL, NULL, NULL, // Alloc, free, context
+ marshallingBufferSize(),
+ params.maxWriteBuffers,
+ 1, // Max read buffers
+ &marshal);
+ QPID_WINDOWS_CHECK_NOT(ok, 0);
+ if (needInitialize)
+ initialize();
+}
+
+uint32_t
+Log::marshallingBufferSize()
+{
+ // Default implementation returns the minimum marshalling buffer size;
+ // derived ones should come up with a more fitting value.
+ //
+ // Find the directory name part of the log specification, including the
+ // trailing '\'.
+ size_t dirMarker = logPath.rfind('\\');
+ if (dirMarker == std::string::npos)
+ dirMarker = logPath.rfind('/');
+ DWORD bytesPerSector;
+ DWORD dontCare;
+ ::GetDiskFreeSpace(logPath.substr(0, dirMarker).c_str(),
+ &dontCare,
+ &bytesPerSector,
+ &dontCare,
+ &dontCare);
+ return bytesPerSector;
+}
+
+CLFS_LSN
+Log::write(void* entry, uint32_t length, CLFS_LSN* prev)
+{
+ CLFS_WRITE_ENTRY desc;
+ desc.Buffer = entry;
+ desc.ByteLength = length;
+ CLFS_LSN lsn;
+ BOOL ok = ::ReserveAndAppendLog(marshal,
+ &desc, 1, // Buffer descriptor
+ 0, prev, // Undo-Next, Prev
+ 0, 0, // Reservation
+ CLFS_FLAG_FORCE_FLUSH,
+ &lsn,
+ 0);
+ QPID_WINDOWS_CHECK_NOT(ok, 0);
+ return lsn;
+}
+
+// Get the current base LSN of the log.
+CLFS_LSN
+Log::getBase()
+{
+ CLFS_INFORMATION info;
+ ULONG infoSize = sizeof(info);
+ BOOL ok = ::GetLogFileInformation(handle, &info, &infoSize);
+ QPID_WINDOWS_CHECK_NOT(ok, 0);
+ return info.BaseLsn;
+}
+
+void
+Log::moveTail(const CLFS_LSN& oldest)
+{
+ BOOL ok = ::AdvanceLogBase(marshal,
+ const_cast<PCLFS_LSN>(&oldest),
+ 0, NULL);
+ // If multiple threads are manipulating things they may get out of
+ // order when moving the tail; if someone already moved it further
+ // than this, it's ok - ignore it.
+ if (ok || ::GetLastError() == ERROR_LOG_START_OF_LOG)
+ return;
+ QPID_WINDOWS_CHECK_NOT(ok, 0);
+}
+
+}}} // namespace qpid::store::ms_clfs
diff --git a/qpid/cpp/src/qpid/store/ms-clfs/Log.h b/qpid/cpp/src/qpid/store/ms-clfs/Log.h
new file mode 100644
index 0000000000..2f7eb6cada
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-clfs/Log.h
@@ -0,0 +1,78 @@
+#ifndef QPID_STORE_MSCLFS_LOG_H
+#define QPID_STORE_MSCLFS_LOG_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <string>
+#include <windows.h>
+#include <clfsw32.h>
+#include <qpid/sys/IntegerTypes.h>
+
+namespace qpid {
+namespace store {
+namespace ms_clfs {
+
+/**
+ * @class Log
+ *
+ * Represents a CLFS-housed log.
+ */
+class Log {
+
+protected:
+ HANDLE handle;
+ ULONGLONG containerSize;
+ std::string logPath;
+ PVOID marshal;
+
+ // Give subclasses a chance to initialize a new log. Called after a new
+ // log is created, initial set of containers is added, and marshalling
+ // area is allocated.
+ virtual void initialize() {}
+
+public:
+ struct TuningParameters {
+ size_t containerSize;
+ unsigned short containers;
+ unsigned short shrinkPct;
+ uint32_t maxWriteBuffers;
+ };
+
+ Log() : handle(INVALID_HANDLE_VALUE), containerSize(0), marshal(0) {}
+ virtual ~Log();
+
+ void open(const std::string& path, const TuningParameters& params);
+
+ virtual uint32_t marshallingBufferSize();
+
+ CLFS_LSN write(void* entry, uint32_t length, CLFS_LSN* prev = 0);
+
+ // Get the current base LSN of the log.
+ CLFS_LSN getBase();
+
+ // Move the log tail to the indicated LSN.
+ void moveTail(const CLFS_LSN& oldest);
+};
+
+}}} // namespace qpid::store::ms_clfs
+
+#endif /* QPID_STORE_MSCLFS_LOG_H */
diff --git a/qpid/cpp/src/qpid/store/ms-clfs/Lsn.h b/qpid/cpp/src/qpid/store/ms-clfs/Lsn.h
new file mode 100644
index 0000000000..7f46c1f266
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-clfs/Lsn.h
@@ -0,0 +1,36 @@
+#ifndef QPID_STORE_MSCLFS_LSN_H
+#define QPID_STORE_MSCLFS_LSN_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <clfsw32.h>
+
+namespace {
+ // Make it easy to assign LSNs
+ inline CLFS_LSN idToLsn(const uint64_t val)
+ { CLFS_LSN lsn; lsn.Internal = val; return lsn; }
+
+ inline uint64_t lsnToId(const CLFS_LSN& lsn)
+ { uint64_t val = lsn.Internal; return val; }
+}
+
+#endif /* QPID_STORE_MSCLFS_LSN_H */
diff --git a/qpid/cpp/src/qpid/store/ms-clfs/MSSqlClfsProvider.cpp b/qpid/cpp/src/qpid/store/ms-clfs/MSSqlClfsProvider.cpp
new file mode 100644
index 0000000000..5ff24e7d33
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-clfs/MSSqlClfsProvider.cpp
@@ -0,0 +1,1102 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <list>
+#include <map>
+#include <set>
+#include <stdlib.h>
+#include <string>
+#include <windows.h>
+#include <clfsw32.h>
+#include <qpid/broker/Broker.h>
+#include <qpid/broker/RecoverableQueue.h>
+#include <qpid/log/Statement.h>
+#include <qpid/store/MessageStorePlugin.h>
+#include <qpid/store/StoreException.h>
+#include <qpid/store/StorageProvider.h>
+#include <qpid/sys/Mutex.h>
+#include <boost/foreach.hpp>
+
+// From ms-sql...
+#include "BlobAdapter.h"
+#include "BlobRecordset.h"
+#include "BindingRecordset.h"
+#include "DatabaseConnection.h"
+#include "Exception.h"
+#include "State.h"
+#include "VariantHelper.h"
+using qpid::store::ms_sql::BlobAdapter;
+using qpid::store::ms_sql::BlobRecordset;
+using qpid::store::ms_sql::BindingRecordset;
+using qpid::store::ms_sql::DatabaseConnection;
+using qpid::store::ms_sql::ADOException;
+using qpid::store::ms_sql::State;
+using qpid::store::ms_sql::VariantHelper;
+
+#include "Log.h"
+#include "Messages.h"
+#include "Transaction.h"
+#include "TransactionLog.h"
+
+// Bring in ADO 2.8 (yes, I know it says "15", but that's it...)
+#import "C:\Program Files\Common Files\System\ado\msado15.dll" \
+ no_namespace rename("EOF", "EndOfFile")
+#include <comdef.h>
+namespace {
+inline void TESTHR(HRESULT x) {if FAILED(x) _com_issue_error(x);};
+
+// Table names
+const std::string TblBinding("tblBinding");
+const std::string TblConfig("tblConfig");
+const std::string TblExchange("tblExchange");
+const std::string TblQueue("tblQueue");
+
+}
+
+namespace qpid {
+namespace store {
+namespace ms_clfs {
+
+/**
+ * @class MSSqlClfsProvider
+ *
+ * Implements a qpid::store::StorageProvider that uses a hybrid Microsoft
+ * SQL Server and Windows CLFS approach as the backend data store for Qpid.
+ */
+class MSSqlClfsProvider : public qpid::store::StorageProvider
+{
+protected:
+ void finalizeMe();
+
+ void dump();
+
+public:
+ MSSqlClfsProvider();
+ ~MSSqlClfsProvider();
+
+ virtual qpid::Options* getOptions() { return &options; }
+
+ virtual void earlyInitialize (Plugin::Target& target);
+ virtual void initialize(Plugin::Target& target);
+
+ /**
+ * Receive notification that this provider is the one that will actively
+ * handle provider storage for the target. If the provider is to be used,
+ * this method will be called after earlyInitialize() and before any
+ * recovery operations (recovery, in turn, precedes call to initialize()).
+ */
+ virtual void activate(MessageStorePlugin &store);
+
+ /**
+ * @name Methods inherited from qpid::broker::MessageStore
+ */
+
+ /**
+ * Record the existence of a durable queue
+ */
+ virtual void create(PersistableQueue& queue,
+ const qpid::framing::FieldTable& args);
+ /**
+ * Destroy a durable queue
+ */
+ virtual void destroy(PersistableQueue& queue);
+
+ /**
+ * Record the existence of a durable exchange
+ */
+ virtual void create(const PersistableExchange& exchange,
+ const qpid::framing::FieldTable& args);
+ /**
+ * Destroy a durable exchange
+ */
+ virtual void destroy(const PersistableExchange& exchange);
+
+ /**
+ * Record a binding
+ */
+ virtual void bind(const PersistableExchange& exchange,
+ const PersistableQueue& queue,
+ const std::string& key,
+ const qpid::framing::FieldTable& args);
+
+ /**
+ * Forget a binding
+ */
+ virtual void unbind(const PersistableExchange& exchange,
+ const PersistableQueue& queue,
+ const std::string& key,
+ const qpid::framing::FieldTable& args);
+
+ /**
+ * Record generic durable configuration
+ */
+ virtual void create(const PersistableConfig& config);
+
+ /**
+ * Destroy generic durable configuration
+ */
+ virtual void destroy(const PersistableConfig& config);
+
+ /**
+ * Stores a messages before it has been enqueued
+ * (enqueueing automatically stores the message so this is
+ * only required if storage is required prior to that
+ * point). If the message has not yet been stored it will
+ * store the headers as well as any content passed in. A
+ * persistence id will be set on the message which can be
+ * used to load the content or to append to it.
+ */
+ virtual void stage(const boost::intrusive_ptr<PersistableMessage>& msg);
+
+ /**
+ * Destroys a previously staged message. This only needs
+ * to be called if the message is never enqueued. (Once
+ * enqueued, deletion will be automatic when the message
+ * is dequeued from all queues it was enqueued onto).
+ */
+ virtual void destroy(PersistableMessage& msg);
+
+ /**
+ * Appends content to a previously staged message
+ */
+ virtual void appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg,
+ const std::string& data);
+
+ /**
+ * Loads (a section) of content data for the specified
+ * message (previously stored through a call to stage or
+ * enqueue) into data. The offset refers to the content
+ * only (i.e. an offset of 0 implies that the start of the
+ * content should be loaded, not the headers or related
+ * meta-data).
+ */
+ virtual void loadContent(const qpid::broker::PersistableQueue& queue,
+ const boost::intrusive_ptr<const PersistableMessage>& msg,
+ std::string& data,
+ uint64_t offset,
+ uint32_t length);
+
+ /**
+ * Enqueues a message, storing the message if it has not
+ * been previously stored and recording that the given
+ * message is on the given queue.
+ *
+ * Note: that this is async so the return of the function does
+ * not mean the opperation is complete.
+ *
+ * @param msg the message to enqueue
+ * @param queue the name of the queue onto which it is to be enqueued
+ * @param xid (a pointer to) an identifier of the
+ * distributed transaction in which the operation takes
+ * place or null for 'local' transactions
+ */
+ virtual void enqueue(qpid::broker::TransactionContext* ctxt,
+ const boost::intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue& queue);
+
+ /**
+ * Dequeues a message, recording that the given message is
+ * no longer on the given queue and deleting the message
+ * if it is no longer on any other queue.
+ *
+ * Note: that this is async so the return of the function does
+ * not mean the opperation is complete.
+ *
+ * @param msg the message to dequeue
+ * @param queue the name of the queue from which it is to be dequeued
+ * @param xid (a pointer to) an identifier of the
+ * distributed transaction in which the operation takes
+ * place or null for 'local' transactions
+ */
+ virtual void dequeue(qpid::broker::TransactionContext* ctxt,
+ const boost::intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue& queue);
+
+ /**
+ * Flushes all async messages to disk for the specified queue
+ *
+ * Note: this is a no-op for this provider.
+ *
+ * @param queue the name of the queue from which it is to be dequeued
+ */
+ virtual void flush(const PersistableQueue& queue) {};
+
+ /**
+ * Returns the number of outstanding AIO's for a given queue
+ *
+ * If 0, than all the enqueue / dequeues have been stored
+ * to disk
+ *
+ * @param queue the name of the queue to check for outstanding AIO
+ */
+ virtual uint32_t outstandingQueueAIO(const PersistableQueue& queue)
+ {return 0;}
+ //@}
+
+ /**
+ * @name Methods inherited from qpid::broker::TransactionalStore
+ */
+ //@{
+ virtual std::auto_ptr<qpid::broker::TransactionContext> begin();
+ virtual std::auto_ptr<qpid::broker::TPCTransactionContext> begin(const std::string& xid);
+ virtual void prepare(qpid::broker::TPCTransactionContext& txn);
+ virtual void commit(qpid::broker::TransactionContext& txn);
+ virtual void abort(qpid::broker::TransactionContext& txn);
+ virtual void collectPreparedXids(std::set<std::string>& xids);
+ //@}
+
+ virtual void recoverConfigs(qpid::broker::RecoveryManager& recoverer);
+ virtual void recoverExchanges(qpid::broker::RecoveryManager& recoverer,
+ ExchangeMap& exchangeMap);
+ virtual void recoverQueues(qpid::broker::RecoveryManager& recoverer,
+ QueueMap& queueMap);
+ virtual void recoverBindings(qpid::broker::RecoveryManager& recoverer,
+ const ExchangeMap& exchangeMap,
+ const QueueMap& queueMap);
+ virtual void recoverMessages(qpid::broker::RecoveryManager& recoverer,
+ MessageMap& messageMap,
+ MessageQueueMap& messageQueueMap);
+ virtual void recoverTransactions(qpid::broker::RecoveryManager& recoverer,
+ PreparedTransactionMap& dtxMap);
+
+private:
+ struct ProviderOptions : public qpid::Options
+ {
+ std::string connectString;
+ std::string catalogName;
+ std::string storeDir;
+ size_t containerSize;
+ unsigned short initialContainers;
+ uint32_t maxWriteBuffers;
+
+ ProviderOptions(const std::string &name)
+ : qpid::Options(name),
+ catalogName("QpidStore"),
+ containerSize(1024 * 1024),
+ initialContainers(2),
+ maxWriteBuffers(10)
+ {
+ const enum { NAMELEN = MAX_COMPUTERNAME_LENGTH + 1 };
+ TCHAR myName[NAMELEN];
+ DWORD myNameLen = NAMELEN;
+ GetComputerName(myName, &myNameLen);
+ connectString = "Data Source=";
+ connectString += myName;
+ connectString += "\\SQLEXPRESS;Integrated Security=SSPI";
+ addOptions()
+ ("connect",
+ qpid::optValue(connectString, "STRING"),
+ "Connection string for the database to use. Will prepend "
+ "Provider=SQLOLEDB;")
+ ("catalog",
+ qpid::optValue(catalogName, "DB NAME"),
+ "Catalog (database) name")
+ ("store-dir",
+ qpid::optValue(storeDir, "DIR"),
+ "Location to store message and transaction data "
+ "(default uses data-dir if available)")
+ ("container-size",
+ qpid::optValue(containerSize, "VALUE"),
+ "Bytes per container; min 512K. Only used when creating "
+ "a new log")
+ ("initial-containers",
+ qpid::optValue(initialContainers, "VALUE"),
+ "Number of containers to add if creating a new log")
+ ("max-write-buffers",
+ qpid::optValue(maxWriteBuffers, "VALUE"),
+ "Maximum write buffers outstanding before log is flushed "
+ "(0 means no limit)")
+ ;
+ }
+ };
+ ProviderOptions options;
+ std::string brokerDataDir;
+ Messages messages;
+ // TransactionLog requires itself to have a shared_ptr reference to start.
+ TransactionLog::shared_ptr transactions;
+
+ // Each thread has a separate connection to the database and also needs
+ // to manage its COM initialize/finalize individually. This is done by
+ // keeping a thread-specific State.
+ boost::thread_specific_ptr<State> dbState;
+
+ State *initState();
+ DatabaseConnection *initConnection(void);
+ void createDb(DatabaseConnection *db, const std::string &name);
+ void createLogs();
+};
+
+static MSSqlClfsProvider static_instance_registers_plugin;
+
+void
+MSSqlClfsProvider::finalizeMe()
+{
+ dbState.reset();
+}
+
+MSSqlClfsProvider::MSSqlClfsProvider()
+ : options("MS SQL/CLFS Provider options")
+{
+ transactions.reset(new TransactionLog());
+}
+
+MSSqlClfsProvider::~MSSqlClfsProvider()
+{
+}
+
+void
+MSSqlClfsProvider::earlyInitialize(Plugin::Target &target)
+{
+ MessageStorePlugin *store = dynamic_cast<MessageStorePlugin *>(&target);
+ if (store) {
+ // Check the store dir option; if not specified, need to
+ // grab the broker's data dir.
+ if (options.storeDir.empty()) {
+ const DataDir& dir = store->getBroker()->getDataDir();
+ if (dir.isEnabled()) {
+ options.storeDir = dir.getPath();
+ }
+ else {
+ QPID_LOG(error,
+ "MSSQL-CLFS: --store-dir required if --no-data-dir specified");
+ return;
+ }
+ }
+
+ // If CLFS is not available on this system, give up now.
+ try {
+ Log::TuningParameters params;
+ params.containerSize = options.containerSize;
+ params.containers = options.initialContainers;
+ params.shrinkPct = 50;
+ params.maxWriteBuffers = options.maxWriteBuffers;
+ std::string msgPath = options.storeDir + "\\" + "messages";
+ messages.openLog(msgPath, params);
+ std::string transPath = options.storeDir + "\\" + "transactions";
+ transactions->open(transPath, params);
+ }
+ catch (std::exception &e) {
+ QPID_LOG(error, e.what());
+ return;
+ }
+
+ // If the database init fails, report it and don't register; give
+ // the rest of the broker a chance to run.
+ //
+ // Don't try to initConnection() since that will fail if the
+ // database doesn't exist. Instead, try to open a connection without
+ // a database name, then search for the database. There's still a
+ // chance this provider won't be selected for the store too, so be
+ // be sure to close the database connection before return to avoid
+ // leaving a connection up that will not be used.
+ try {
+ initState(); // This initializes COM
+ std::auto_ptr<DatabaseConnection> db(new DatabaseConnection());
+ db->open(options.connectString, "");
+ _ConnectionPtr conn(*db);
+ _RecordsetPtr pCatalogs = NULL;
+ VariantHelper<std::string> catalogName(options.catalogName);
+ pCatalogs = conn->OpenSchema(adSchemaCatalogs, catalogName);
+ if (pCatalogs->EndOfFile) {
+ // Database doesn't exist; create it
+ QPID_LOG(notice,
+ "MSSQL-CLFS: Creating database " + options.catalogName);
+ createDb(db.get(), options.catalogName);
+ }
+ else {
+ QPID_LOG(notice,
+ "MSSQL-CLFS: Database located: " + options.catalogName);
+ }
+ if (pCatalogs) {
+ if (pCatalogs->State == adStateOpen)
+ pCatalogs->Close();
+ pCatalogs = 0;
+ }
+ db->close();
+ store->providerAvailable("MSSQL-CLFS", this);
+ }
+ catch (qpid::Exception &e) {
+ QPID_LOG(error, e.what());
+ return;
+ }
+ store->addFinalizer(boost::bind(&MSSqlClfsProvider::finalizeMe, this));
+ }
+}
+
+void
+MSSqlClfsProvider::initialize(Plugin::Target& target)
+{
+}
+
+void
+MSSqlClfsProvider::activate(MessageStorePlugin &store)
+{
+ QPID_LOG(info, "MS SQL/CLFS Provider is up");
+}
+
+void
+MSSqlClfsProvider::create(PersistableQueue& queue,
+ const qpid::framing::FieldTable& /*args needed for jrnl*/)
+{
+ DatabaseConnection *db = initConnection();
+ BlobRecordset rsQueues;
+ try {
+ db->beginTransaction();
+ rsQueues.open(db, TblQueue);
+ rsQueues.add(queue);
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error creating queue " + queue.getName(), e, errs);
+ }
+ catch(std::exception& e) {
+ db->rollbackTransaction();
+ THROW_STORE_EXCEPTION(e.what());
+ }
+}
+
+/**
+ * Destroy a durable queue
+ */
+void
+MSSqlClfsProvider::destroy(PersistableQueue& queue)
+{
+ DatabaseConnection *db = initConnection();
+ BlobRecordset rsQueues;
+ BindingRecordset rsBindings;
+ try {
+ db->beginTransaction();
+ rsQueues.open(db, TblQueue);
+ rsBindings.open(db, TblBinding);
+ // Remove bindings first; the queue IDs can't be ripped out from
+ // under the references in the bindings table.
+ rsBindings.removeForQueue(queue.getPersistenceId());
+ rsQueues.remove(queue);
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error deleting queue " + queue.getName(), e, errs);
+ }
+
+ /*
+ * Now that the SQL stuff has recorded the queue deletion, expunge
+ * all record of the queue from the messages set. Any errors logging
+ * these removals are swallowed because during a recovery the queue
+ * Id won't be present (the SQL stuff already committed) so any references
+ * to it in message operations will be removed.
+ */
+ messages.expunge(queue.getPersistenceId());
+}
+
+/**
+ * Record the existence of a durable exchange
+ */
+void
+MSSqlClfsProvider::create(const PersistableExchange& exchange,
+ const qpid::framing::FieldTable& args)
+{
+ DatabaseConnection *db = initConnection();
+ BlobRecordset rsExchanges;
+ try {
+ db->beginTransaction();
+ rsExchanges.open(db, TblExchange);
+ rsExchanges.add(exchange);
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error creating exchange " + exchange.getName(),
+ e,
+ errs);
+ }
+}
+
+/**
+ * Destroy a durable exchange
+ */
+void
+MSSqlClfsProvider::destroy(const PersistableExchange& exchange)
+{
+ DatabaseConnection *db = initConnection();
+ BlobRecordset rsExchanges;
+ BindingRecordset rsBindings;
+ try {
+ db->beginTransaction();
+ rsExchanges.open(db, TblExchange);
+ rsBindings.open(db, TblBinding);
+ // Remove bindings first; the exchange IDs can't be ripped out from
+ // under the references in the bindings table.
+ rsBindings.removeForExchange(exchange.getPersistenceId());
+ rsExchanges.remove(exchange);
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error deleting exchange " + exchange.getName(),
+ e,
+ errs);
+ }
+}
+
+/**
+ * Record a binding
+ */
+void
+MSSqlClfsProvider::bind(const PersistableExchange& exchange,
+ const PersistableQueue& queue,
+ const std::string& key,
+ const qpid::framing::FieldTable& args)
+{
+ DatabaseConnection *db = initConnection();
+ BindingRecordset rsBindings;
+ try {
+ db->beginTransaction();
+ rsBindings.open(db, TblBinding);
+ rsBindings.add(exchange.getPersistenceId(),
+ queue.getPersistenceId(),
+ key,
+ args);
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error binding exchange " + exchange.getName() +
+ " to queue " + queue.getName(),
+ e,
+ errs);
+ }
+}
+
+/**
+ * Forget a binding
+ */
+void
+MSSqlClfsProvider::unbind(const PersistableExchange& exchange,
+ const PersistableQueue& queue,
+ const std::string& key,
+ const qpid::framing::FieldTable& args)
+{
+ DatabaseConnection *db = initConnection();
+ BindingRecordset rsBindings;
+ try {
+ db->beginTransaction();
+ rsBindings.open(db, TblBinding);
+ rsBindings.remove(exchange.getPersistenceId(),
+ queue.getPersistenceId(),
+ key,
+ args);
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error unbinding exchange " + exchange.getName() +
+ " from queue " + queue.getName(),
+ e,
+ errs);
+ }
+}
+
+/**
+ * Record generic durable configuration
+ */
+void
+MSSqlClfsProvider::create(const PersistableConfig& config)
+{
+ DatabaseConnection *db = initConnection();
+ BlobRecordset rsConfigs;
+ try {
+ db->beginTransaction();
+ rsConfigs.open(db, TblConfig);
+ rsConfigs.add(config);
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error creating config " + config.getName(), e, errs);
+ }
+}
+
+/**
+ * Destroy generic durable configuration
+ */
+void
+MSSqlClfsProvider::destroy(const PersistableConfig& config)
+{
+ DatabaseConnection *db = initConnection();
+ BlobRecordset rsConfigs;
+ try {
+ db->beginTransaction();
+ rsConfigs.open(db, TblConfig);
+ rsConfigs.remove(config);
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error deleting config " + config.getName(), e, errs);
+ }
+}
+
+/**
+ * Stores a messages before it has been enqueued
+ * (enqueueing automatically stores the message so this is
+ * only required if storage is required prior to that
+ * point). If the message has not yet been stored it will
+ * store the headers as well as any content passed in. A
+ * persistence id will be set on the message which can be
+ * used to load the content or to append to it.
+ */
+void
+MSSqlClfsProvider::stage(const boost::intrusive_ptr<PersistableMessage>& msg)
+{
+#if 0
+ DatabaseConnection *db = initConnection();
+ MessageRecordset rsMessages;
+ try {
+ db->beginTransaction();
+ rsMessages.open(db, TblMessage);
+ rsMessages.add(msg);
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error staging message", e, errs);
+ }
+#endif
+}
+
+/**
+ * Destroys a previously staged message. This only needs
+ * to be called if the message is never enqueued. (Once
+ * enqueued, deletion will be automatic when the message
+ * is dequeued from all queues it was enqueued onto).
+ */
+void
+MSSqlClfsProvider::destroy(PersistableMessage& msg)
+{
+#if 0
+ DatabaseConnection *db = initConnection();
+ BlobRecordset rsMessages;
+ try {
+ db->beginTransaction();
+ rsMessages.open(db, TblMessage);
+ rsMessages.remove(msg);
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error deleting message", e, errs);
+ }
+#endif
+}
+
+/**
+ * Appends content to a previously staged message
+ */
+void
+MSSqlClfsProvider::appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg,
+ const std::string& data)
+{
+#if 0
+ DatabaseConnection *db = initConnection();
+ MessageRecordset rsMessages;
+ try {
+ db->beginTransaction();
+ rsMessages.open(db, TblMessage);
+ rsMessages.append(msg, data);
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error appending to message", e, errs);
+ }
+#endif
+}
+
+/**
+ * Loads (a section) of content data for the specified
+ * message (previously stored through a call to stage or
+ * enqueue) into data. The offset refers to the content
+ * only (i.e. an offset of 0 implies that the start of the
+ * content should be loaded, not the headers or related
+ * meta-data).
+ */
+void
+MSSqlClfsProvider::loadContent(const qpid::broker::PersistableQueue& /*queue*/,
+ const boost::intrusive_ptr<const PersistableMessage>& msg,
+ std::string& data,
+ uint64_t offset,
+ uint32_t length)
+{
+ // Message log keeps all messages in one log, so we don't need the
+ // queue reference.
+ messages.loadContent(msg->getPersistenceId(), data, offset, length);
+}
+
+/**
+ * Enqueues a message, storing the message if it has not
+ * been previously stored and recording that the given
+ * message is on the given queue.
+ *
+ * @param ctxt The transaction context under which this enqueue happens.
+ * @param msg The message to enqueue
+ * @param queue the name of the queue onto which it is to be enqueued
+ */
+void
+MSSqlClfsProvider::enqueue(qpid::broker::TransactionContext* ctxt,
+ const boost::intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue& queue)
+{
+ Transaction::shared_ptr t;
+ TransactionContext *ctx = dynamic_cast<TransactionContext*>(ctxt);
+ if (ctx)
+ t = ctx->getTransaction();
+ else {
+ TPCTransactionContext *tctx;
+ tctx = dynamic_cast<TPCTransactionContext*>(ctxt);
+ if (tctx)
+ t = tctx->getTransaction();
+ }
+ uint64_t msgId = msg->getPersistenceId();
+ if (msgId == 0) {
+ messages.add(msg);
+ msgId = msg->getPersistenceId();
+ }
+ messages.enqueue(msgId, queue.getPersistenceId(), t);
+ msg->enqueueComplete();
+}
+
+/**
+ * Dequeues a message, recording that the given message is
+ * no longer on the given queue and deleting the message
+ * if it is no longer on any other queue.
+ *
+ * @param ctxt The transaction context under which this dequeue happens.
+ * @param msg The message to dequeue
+ * @param queue The queue from which it is to be dequeued
+ */
+void
+MSSqlClfsProvider::dequeue(qpid::broker::TransactionContext* ctxt,
+ const boost::intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue& queue)
+{
+ Transaction::shared_ptr t;
+ TransactionContext *ctx = dynamic_cast<TransactionContext*>(ctxt);
+ if (ctx)
+ t = ctx->getTransaction();
+ else {
+ TPCTransactionContext *tctx;
+ tctx = dynamic_cast<TPCTransactionContext*>(ctxt);
+ if (tctx)
+ t = tctx->getTransaction();
+ }
+ messages.dequeue(msg->getPersistenceId(), queue.getPersistenceId(), t);
+ msg->dequeueComplete();
+}
+
+std::auto_ptr<qpid::broker::TransactionContext>
+MSSqlClfsProvider::begin()
+{
+ Transaction::shared_ptr t = transactions->begin();
+ std::auto_ptr<qpid::broker::TransactionContext> tc(new TransactionContext(t));
+ return tc;
+}
+
+std::auto_ptr<qpid::broker::TPCTransactionContext>
+MSSqlClfsProvider::begin(const std::string& xid)
+{
+ TPCTransaction::shared_ptr t = transactions->begin(xid);
+ std::auto_ptr<qpid::broker::TPCTransactionContext> tc(new TPCTransactionContext(t));
+ return tc;
+}
+
+void
+MSSqlClfsProvider::prepare(qpid::broker::TPCTransactionContext& txn)
+{
+ TPCTransactionContext *ctx = dynamic_cast<TPCTransactionContext*> (&txn);
+ if (ctx == 0)
+ throw qpid::broker::InvalidTransactionContextException();
+ ctx->getTransaction()->prepare();
+}
+
+void
+MSSqlClfsProvider::commit(qpid::broker::TransactionContext& txn)
+{
+ Transaction::shared_ptr t;
+ TransactionContext *ctx = dynamic_cast<TransactionContext*>(&txn);
+ if (ctx)
+ t = ctx->getTransaction();
+ else {
+ TPCTransactionContext *tctx;
+ tctx = dynamic_cast<TPCTransactionContext*>(&txn);
+ if (tctx == 0)
+ throw qpid::broker::InvalidTransactionContextException();
+ t = tctx->getTransaction();
+ }
+ t->commit(messages);
+}
+
+void
+MSSqlClfsProvider::abort(qpid::broker::TransactionContext& txn)
+{
+ Transaction::shared_ptr t;
+ TransactionContext *ctx = dynamic_cast<TransactionContext*>(&txn);
+ if (ctx)
+ t = ctx->getTransaction();
+ else {
+ TPCTransactionContext *tctx;
+ tctx = dynamic_cast<TPCTransactionContext*>(&txn);
+ if (tctx == 0)
+ throw qpid::broker::InvalidTransactionContextException();
+ t = tctx->getTransaction();
+ }
+ t->abort(messages);
+}
+
+void
+MSSqlClfsProvider::collectPreparedXids(std::set<std::string>& xids)
+{
+ std::map<std::string, TPCTransaction::shared_ptr> preparedMap;
+ transactions->collectPreparedXids(preparedMap);
+ std::map<std::string, TPCTransaction::shared_ptr>::const_iterator i;
+ for (i = preparedMap.begin(); i != preparedMap.end(); ++i) {
+ xids.insert(i->first);
+ }
+}
+
+// @TODO Much of this recovery code is way too similar... refactor to
+// a recover template method on BlobRecordset.
+
+void
+MSSqlClfsProvider::recoverConfigs(qpid::broker::RecoveryManager& recoverer)
+{
+ DatabaseConnection *db = initConnection();
+ BlobRecordset rsConfigs;
+ rsConfigs.open(db, TblConfig);
+ _RecordsetPtr p = (_RecordsetPtr)rsConfigs;
+ if (p->BOF && p->EndOfFile)
+ return; // Nothing to do
+ p->MoveFirst();
+ while (!p->EndOfFile) {
+ uint64_t id = p->Fields->Item["persistenceId"]->Value;
+ long blobSize = p->Fields->Item["fieldTableBlob"]->ActualSize;
+ BlobAdapter blob(blobSize);
+ blob = p->Fields->Item["fieldTableBlob"]->GetChunk(blobSize);
+ // Recreate the Config instance and reset its ID.
+ broker::RecoverableConfig::shared_ptr config =
+ recoverer.recoverConfig(blob);
+ config->setPersistenceId(id);
+ p->MoveNext();
+ }
+}
+
+void
+MSSqlClfsProvider::recoverExchanges(qpid::broker::RecoveryManager& recoverer,
+ ExchangeMap& exchangeMap)
+{
+ DatabaseConnection *db = initConnection();
+ BlobRecordset rsExchanges;
+ rsExchanges.open(db, TblExchange);
+ _RecordsetPtr p = (_RecordsetPtr)rsExchanges;
+ if (p->BOF && p->EndOfFile)
+ return; // Nothing to do
+ p->MoveFirst();
+ while (!p->EndOfFile) {
+ uint64_t id = p->Fields->Item["persistenceId"]->Value;
+ long blobSize = p->Fields->Item["fieldTableBlob"]->ActualSize;
+ BlobAdapter blob(blobSize);
+ blob = p->Fields->Item["fieldTableBlob"]->GetChunk(blobSize);
+ // Recreate the Exchange instance, reset its ID, and remember the
+ // ones restored for matching up when recovering bindings.
+ broker::RecoverableExchange::shared_ptr exchange =
+ recoverer.recoverExchange(blob);
+ exchange->setPersistenceId(id);
+ exchangeMap[id] = exchange;
+ p->MoveNext();
+ }
+}
+
+void
+MSSqlClfsProvider::recoverQueues(qpid::broker::RecoveryManager& recoverer,
+ QueueMap& queueMap)
+{
+ DatabaseConnection *db = initConnection();
+ BlobRecordset rsQueues;
+ rsQueues.open(db, TblQueue);
+ _RecordsetPtr p = (_RecordsetPtr)rsQueues;
+ if (p->BOF && p->EndOfFile)
+ return; // Nothing to do
+ p->MoveFirst();
+ while (!p->EndOfFile) {
+ uint64_t id = p->Fields->Item["persistenceId"]->Value;
+ long blobSize = p->Fields->Item["fieldTableBlob"]->ActualSize;
+ BlobAdapter blob(blobSize);
+ blob = p->Fields->Item["fieldTableBlob"]->GetChunk(blobSize);
+ // Recreate the Queue instance and reset its ID.
+ broker::RecoverableQueue::shared_ptr queue =
+ recoverer.recoverQueue(blob);
+ queue->setPersistenceId(id);
+ queueMap[id] = queue;
+ p->MoveNext();
+ }
+}
+
+void
+MSSqlClfsProvider::recoverBindings(qpid::broker::RecoveryManager& recoverer,
+ const ExchangeMap& exchangeMap,
+ const QueueMap& queueMap)
+{
+ DatabaseConnection *db = initConnection();
+ BindingRecordset rsBindings;
+ rsBindings.open(db, TblBinding);
+ rsBindings.recover(recoverer, exchangeMap, queueMap);
+}
+
+void
+MSSqlClfsProvider::recoverMessages(qpid::broker::RecoveryManager& recoverer,
+ MessageMap& messageMap,
+ MessageQueueMap& messageQueueMap)
+{
+ // Read the list of valid queue Ids to ensure that no broken msg->queue
+ // refs get restored.
+ DatabaseConnection *db = initConnection();
+ BlobRecordset rsQueues;
+ rsQueues.open(db, TblQueue);
+ _RecordsetPtr p = (_RecordsetPtr)rsQueues;
+ std::set<uint64_t> validQueues;
+ if (!(p->BOF && p->EndOfFile)) {
+ p->MoveFirst();
+ while (!p->EndOfFile) {
+ uint64_t id = p->Fields->Item["persistenceId"]->Value;
+ validQueues.insert(id);
+ p->MoveNext();
+ }
+ }
+ std::map<uint64_t, Transaction::shared_ptr> transMap;
+ transactions->recover(transMap);
+ messages.recover(recoverer,
+ validQueues,
+ transMap,
+ messageMap,
+ messageQueueMap);
+}
+
+void
+MSSqlClfsProvider::recoverTransactions(qpid::broker::RecoveryManager& recoverer,
+ PreparedTransactionMap& dtxMap)
+{
+ std::map<std::string, TPCTransaction::shared_ptr> preparedMap;
+ transactions->collectPreparedXids(preparedMap);
+ std::map<std::string, TPCTransaction::shared_ptr>::const_iterator i;
+ for (i = preparedMap.begin(); i != preparedMap.end(); ++i) {
+ std::auto_ptr<TPCTransactionContext> ctx(new TPCTransactionContext(i->second));
+ std::auto_ptr<qpid::broker::TPCTransactionContext> brokerCtx(ctx);
+ dtxMap[i->first] = recoverer.recoverTransaction(i->first, brokerCtx);
+ }
+}
+
+////////////// Internal Methods
+
+State *
+MSSqlClfsProvider::initState()
+{
+ State *state = dbState.get(); // See if thread has initialized
+ if (!state) {
+ state = new State;
+ dbState.reset(state);
+ }
+ return state;
+}
+
+DatabaseConnection *
+MSSqlClfsProvider::initConnection(void)
+{
+ State *state = initState();
+ if (state->dbConn != 0)
+ return state->dbConn; // And the DatabaseConnection is set up too
+ std::auto_ptr<DatabaseConnection> db(new DatabaseConnection);
+ db->open(options.connectString, options.catalogName);
+ state->dbConn = db.release();
+ return state->dbConn;
+}
+
+void
+MSSqlClfsProvider::createDb(DatabaseConnection *db, const std::string &name)
+{
+ const std::string dbCmd = "CREATE DATABASE " + name;
+ const std::string useCmd = "USE " + name;
+ const std::string tableCmd = "CREATE TABLE ";
+ const std::string colSpecs =
+ " (persistenceId bigint PRIMARY KEY NOT NULL IDENTITY(1,1),"
+ " fieldTableBlob varbinary(MAX) NOT NULL)";
+ const std::string bindingSpecs =
+ " (exchangeId bigint REFERENCES tblExchange(persistenceId) NOT NULL,"
+ " queueId bigint REFERENCES tblQueue(persistenceId) NOT NULL,"
+ " routingKey varchar(255),"
+ " fieldTableBlob varbinary(MAX))";
+
+ _variant_t unused;
+ _bstr_t dbStr = dbCmd.c_str();
+ _ConnectionPtr conn(*db);
+ try {
+ conn->Execute(dbStr, &unused, adExecuteNoRecords);
+ _bstr_t useStr = useCmd.c_str();
+ conn->Execute(useStr, &unused, adExecuteNoRecords);
+ std::string makeTable = tableCmd + TblQueue + colSpecs;
+ _bstr_t makeTableStr = makeTable.c_str();
+ conn->Execute(makeTableStr, &unused, adExecuteNoRecords);
+ makeTable = tableCmd + TblExchange + colSpecs;
+ makeTableStr = makeTable.c_str();
+ conn->Execute(makeTableStr, &unused, adExecuteNoRecords);
+ makeTable = tableCmd + TblConfig + colSpecs;
+ makeTableStr = makeTable.c_str();
+ conn->Execute(makeTableStr, &unused, adExecuteNoRecords);
+ makeTable = tableCmd + TblBinding + bindingSpecs;
+ makeTableStr = makeTable.c_str();
+ conn->Execute(makeTableStr, &unused, adExecuteNoRecords);
+ }
+ catch(_com_error &e) {
+ throw ADOException("MSSQL can't create " + name, e, db->getErrors());
+ }
+}
+
+void
+MSSqlClfsProvider::dump()
+{
+ // dump all db records to qpid_log
+ QPID_LOG(notice, "DB Dump: (not dumping anything)");
+ // rsQueues.dump();
+}
+
+
+}}} // namespace qpid::store::ms_sql
diff --git a/qpid/cpp/src/qpid/store/ms-clfs/MessageLog.cpp b/qpid/cpp/src/qpid/store/ms-clfs/MessageLog.cpp
new file mode 100644
index 0000000000..849a0a44e8
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-clfs/MessageLog.cpp
@@ -0,0 +1,406 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <windows.h>
+#include <clfsw32.h>
+#include <exception>
+#include <malloc.h>
+#include <memory.h>
+#include <qpid/framing/Buffer.h>
+#include <qpid/log/Statement.h>
+#include <qpid/sys/IntegerTypes.h>
+#include <qpid/sys/windows/check.h>
+
+#include "MessageLog.h"
+#include "Lsn.h"
+
+namespace qpid {
+namespace store {
+namespace ms_clfs {
+
+namespace {
+
+// Structures that hold log records. Each has a type field at the start.
+enum MessageEntryType {
+ MessageStartEntry = 1,
+ MessageChunkEntry = 2,
+ MessageDeleteEntry = 3,
+ MessageEnqueueEntry = 4,
+ MessageDequeueEntry = 5
+};
+static const uint32_t MaxMessageContentLength = 64 * 1024;
+
+// Message-Start
+struct MessageStart {
+ MessageEntryType type;
+ // If the complete message encoding doesn't fit, remainder is in
+ // MessageChunk records to follow.
+ // headerLength is the size of the message's header in content. It is
+ // part of the totalLength and the segmentLength.
+ uint32_t headerLength;
+ uint32_t totalLength;
+ uint32_t segmentLength;
+ char content[MaxMessageContentLength];
+
+ MessageStart()
+ : type(MessageStartEntry),
+ headerLength(0),
+ totalLength(0),
+ segmentLength(0) {}
+};
+// Message-Chunk
+struct MessageChunk {
+ MessageEntryType type;
+ uint32_t segmentLength;
+ char content[MaxMessageContentLength];
+
+ MessageChunk() : type(MessageChunkEntry), segmentLength(0) {}
+};
+// Message-Delete
+struct MessageDelete {
+ MessageEntryType type;
+
+ MessageDelete() : type(MessageDeleteEntry) {}
+};
+// Message-Enqueue
+struct MessageEnqueue {
+ MessageEntryType type;
+ uint64_t queueId;
+ uint64_t transId;
+
+ MessageEnqueue(uint64_t qId = 0, uint64_t tId = 0)
+ : type(MessageEnqueueEntry), queueId(qId), transId(tId) {}
+};
+// Message-Dequeue
+struct MessageDequeue {
+ MessageEntryType type;
+ uint64_t queueId;
+ uint64_t transId;
+
+ MessageDequeue(uint64_t qId = 0, uint64_t tId = 0)
+ : type(MessageDequeueEntry), queueId(qId), transId(tId) {}
+};
+
+} // namespace
+
+void
+MessageLog::initialize()
+{
+ // Write something to occupy the first record, preventing a real message
+ // from being lsn/id 0. Delete of a non-existant id is easily tossed
+ // during recovery if no other messages have caused the tail to be moved
+ // up past this dummy record by then.
+ deleteMessage(0, 0);
+}
+
+uint32_t
+MessageLog::marshallingBufferSize()
+{
+ size_t biggestNeed = std::max(sizeof(MessageStart), sizeof(MessageEnqueue));
+ uint32_t defSize = static_cast<uint32_t>(biggestNeed);
+ uint32_t minSize = Log::marshallingBufferSize();
+ if (defSize <= minSize)
+ return minSize;
+ // Round up to multiple of minSize
+ return (defSize + minSize) / minSize * minSize;
+}
+
+uint64_t
+MessageLog::add(const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg)
+{
+ // The message may be too long to fit in one record; if so, write
+ // Message-Chunk records to contain the rest. If it does all fit in one
+ // record, though, optimize the encoding by going straight to the
+ // Message-Start record rather than encoding then copying to the record.
+ // In all case
+ MessageStart entry;
+ uint32_t encodedMessageLength = msg->encodedSize();
+ entry.headerLength = msg->encodedHeaderSize();
+ entry.totalLength = encodedMessageLength;
+ CLFS_LSN location, lastChunkLsn;
+ std::auto_ptr<char> encodeStage;
+ char *encodeBuff = 0;
+ bool oneRecord = encodedMessageLength <= MaxMessageContentLength;
+ if (oneRecord) {
+ encodeBuff = entry.content;
+ entry.segmentLength = encodedMessageLength;
+ }
+ else {
+ encodeStage.reset(new char[encodedMessageLength]);
+ encodeBuff = encodeStage.get();
+ entry.segmentLength = MaxMessageContentLength;
+ }
+ qpid::framing::Buffer buff(encodeBuff, encodedMessageLength);
+ msg->encode(buff);
+ if (!oneRecord)
+ memcpy_s(entry.content, sizeof(entry.content),
+ encodeBuff, entry.segmentLength);
+ uint32_t entryLength = static_cast<uint32_t>(sizeof(entry));
+ entryLength -= (MaxMessageContentLength - entry.segmentLength);
+ location = write(&entry, entryLength);
+ // Write any Message-Chunk records before setting the message's id.
+ uint32_t sent = entry.segmentLength;
+ uint32_t remaining = encodedMessageLength - entry.segmentLength;
+ while (remaining > 0) {
+ MessageChunk chunk;
+ chunk.segmentLength = std::max(MaxMessageContentLength, remaining);
+ memcpy_s(chunk.content, sizeof(chunk.content),
+ encodeStage.get() + sent, chunk.segmentLength);
+ entryLength = static_cast<uint32_t>(sizeof(chunk));
+ entryLength -= (MaxMessageContentLength - chunk.segmentLength);
+ lastChunkLsn = write(&chunk, entryLength, &location);
+ sent += chunk.segmentLength;
+ remaining -= chunk.segmentLength;
+ }
+ return lsnToId(location);
+}
+
+void
+MessageLog::deleteMessage(uint64_t messageId, uint64_t newFirstId)
+{
+ MessageDelete deleteEntry;
+ CLFS_LSN msgLsn = idToLsn(messageId);
+ write(&deleteEntry, sizeof(deleteEntry), &msgLsn);
+ if (newFirstId != 0)
+ moveTail(idToLsn(newFirstId));
+}
+
+// Load part or all of a message's content from previously stored
+// log record(s).
+void
+MessageLog::loadContent(uint64_t messageId,
+ std::string& data,
+ uint64_t offset,
+ uint32_t length)
+{
+}
+
+void
+MessageLog::recordEnqueue (uint64_t messageId,
+ uint64_t queueId,
+ uint64_t transactionId)
+{
+ MessageEnqueue entry(queueId, transactionId);
+ CLFS_LSN msgLsn = idToLsn(messageId);
+ write(&entry, sizeof(entry), &msgLsn);
+}
+
+void
+MessageLog::recordDequeue (uint64_t messageId,
+ uint64_t queueId,
+ uint64_t transactionId)
+{
+ MessageDequeue entry(queueId, transactionId);
+ CLFS_LSN msgLsn = idToLsn(messageId);
+ write(&entry, sizeof(entry), &msgLsn);
+}
+
+void
+MessageLog::recover(qpid::broker::RecoveryManager& recoverer,
+ qpid::store::MessageMap& messageMap,
+ std::map<uint64_t, std::vector<RecoveredMsgOp> >& messageOps)
+{
+ // If context and content needs to be saved while reassembling messages
+ // split across log records, save the info and reassembly buffer.
+ struct MessageBlocks {
+ uint32_t totalLength;
+ uint32_t soFarLength;
+ boost::shared_ptr<char> content;
+
+ MessageBlocks() : totalLength(0), soFarLength(0), content((char*)0) {}
+ };
+ std::map<uint64_t, MessageBlocks> reassemblies;
+ std::map<uint64_t, MessageBlocks>::iterator at;
+
+ QPID_LOG(debug, "Recovering message log");
+
+ // Note that there may be message refs in the log which are deleted, so
+ // be sure to only add msgs at message-start record, and ignore those
+ // that don't have an existing message record.
+ // Get the base LSN - that's how to say "start reading at the beginning"
+ CLFS_INFORMATION info;
+ ULONG infoLength = sizeof (info);
+ BOOL ok = ::GetLogFileInformation(handle, &info, &infoLength);
+ QPID_WINDOWS_CHECK_NOT(ok, 0);
+
+ // Pointers for the various record types that can be assigned in the
+ // reading loop below.
+ MessageStart *start;
+ MessageChunk *chunk;
+ MessageEnqueue *enqueue;
+ MessageDequeue *dequeue;
+
+ qpid::store::MessageMap::iterator messageMapSpot;
+ qpid::store::MessageQueueMap::iterator queueMapSpot;
+ PVOID recordPointer;
+ ULONG recordLength;
+ CLFS_RECORD_TYPE recordType = ClfsDataRecord;
+ CLFS_LSN messageLsn, current, undoNext;
+ PVOID readContext;
+ uint64_t msgId;
+ // Note 'current' in case it's needed below; ReadNextLogRecord returns it
+ // via a parameter.
+ current = info.BaseLsn;
+ ok = ::ReadLogRecord(marshal,
+ &info.BaseLsn,
+ ClfsContextForward,
+ &recordPointer,
+ &recordLength,
+ &recordType,
+ &undoNext,
+ &messageLsn,
+ &readContext,
+ 0);
+ while (ok) {
+ // All the record types this class writes have a MessageEntryType in the
+ // beginning. Based on that, do what's needed.
+ MessageEntryType *t =
+ reinterpret_cast<MessageEntryType *>(recordPointer);
+ switch(*t) {
+ case MessageStartEntry:
+ start = reinterpret_cast<MessageStart *>(recordPointer);
+ msgId = lsnToId(current);
+ QPID_LOG(debug, "Message Start, id " << msgId);
+ // If the message content is split across multiple log records, save
+ // this content off to the side until the remaining record(s) are
+ // located.
+ if (start->totalLength == start->segmentLength) { // Whole thing
+ // Start by recovering the header then see if the rest of
+ // the content is desired.
+ qpid::framing::Buffer buff(start->content, start->headerLength);
+ qpid::broker::RecoverableMessage::shared_ptr m =
+ recoverer.recoverMessage(buff);
+ m->setPersistenceId(msgId);
+ messageMap[msgId] = m;
+ uint32_t contentLength =
+ start->totalLength - start->headerLength;
+ if (m->loadContent(contentLength)) {
+ qpid::framing::Buffer content(&(start->content[start->headerLength]),
+ contentLength);
+ m->decodeContent(content);
+ }
+ }
+ else {
+ // Save it in a block big enough.
+ MessageBlocks b;
+ b.totalLength = start->totalLength;
+ b.soFarLength = start->segmentLength;
+ b.content.reset(new char[b.totalLength]);
+ memcpy_s(b.content.get(), b.totalLength,
+ start->content, start->segmentLength);
+ reassemblies[msgId] = b;
+ }
+ break;
+ case MessageChunkEntry:
+ chunk = reinterpret_cast<MessageChunk *>(recordPointer);
+ // Remember, all entries chained to MessageStart via previous.
+ msgId = lsnToId(messageLsn);
+ QPID_LOG(debug, "Message Chunk for id " << msgId);
+ at = reassemblies.find(msgId);
+ if (at == reassemblies.end()) {
+ QPID_LOG(debug, "Message frag for " << msgId <<
+ " but no start; discarded");
+ }
+ else {
+ MessageBlocks *b = &(at->second);
+ if (b->soFarLength + chunk->segmentLength > b->totalLength)
+ throw std::runtime_error("Invalid message chunk length");
+ memcpy_s(b->content.get() + b->soFarLength,
+ b->totalLength - b->soFarLength,
+ chunk->content,
+ chunk->segmentLength);
+ b->soFarLength += chunk->segmentLength;
+ if (b->totalLength == b->soFarLength) {
+ qpid::framing::Buffer buff(b->content.get(),
+ b->totalLength);
+ qpid::broker::RecoverableMessage::shared_ptr m =
+ recoverer.recoverMessage(buff);
+ m->setPersistenceId(msgId);
+ messageMap[msgId] = m;
+ reassemblies.erase(at);
+ }
+ }
+ break;
+ case MessageDeleteEntry:
+ msgId = lsnToId(messageLsn);
+ QPID_LOG(debug, "Message Delete, id " << msgId);
+ messageMap.erase(msgId);
+ messageOps.erase(msgId);
+ break;
+ case MessageEnqueueEntry:
+ enqueue = reinterpret_cast<MessageEnqueue *>(recordPointer);
+ msgId = lsnToId(messageLsn);
+ QPID_LOG(debug, "Message " << msgId << " Enqueue on queue " <<
+ enqueue->queueId << ", txn " << enqueue->transId);
+ if (messageMap.find(msgId) == messageMap.end()) {
+ QPID_LOG(debug,
+ "Message " << msgId << " doesn't exist; discarded");
+ }
+ else {
+ std::vector<RecoveredMsgOp>& ops = messageOps[msgId];
+ RecoveredMsgOp op(RECOVERED_ENQUEUE,
+ enqueue->queueId,
+ enqueue->transId);
+ ops.push_back(op);
+ }
+ break;
+ case MessageDequeueEntry:
+ dequeue = reinterpret_cast<MessageDequeue *>(recordPointer);
+ msgId = lsnToId(messageLsn);
+ QPID_LOG(debug, "Message " << msgId << " Dequeue from queue " <<
+ dequeue->queueId);
+ if (messageMap.find(msgId) == messageMap.end()) {
+ QPID_LOG(debug,
+ "Message " << msgId << " doesn't exist; discarded");
+ }
+ else {
+ std::vector<RecoveredMsgOp>& ops = messageOps[msgId];
+ RecoveredMsgOp op(RECOVERED_DEQUEUE,
+ dequeue->queueId,
+ dequeue->transId);
+ ops.push_back(op);
+ }
+ break;
+ default:
+ throw std::runtime_error("Bad message log entry type");
+ }
+
+ recordType = ClfsDataRecord;
+ ok = ::ReadNextLogRecord(readContext,
+ &recordPointer,
+ &recordLength,
+ &recordType,
+ 0, // No userLsn
+ &undoNext,
+ &messageLsn,
+ &current,
+ 0);
+ }
+ DWORD status = ::GetLastError();
+ ::TerminateReadLog(readContext);
+ if (status == ERROR_HANDLE_EOF) { // No more records
+ QPID_LOG(debug, "Message log recovered");
+ return;
+ }
+ throw QPID_WINDOWS_ERROR(status);
+}
+
+}}} // namespace qpid::store::ms_clfs
diff --git a/qpid/cpp/src/qpid/store/ms-clfs/MessageLog.h b/qpid/cpp/src/qpid/store/ms-clfs/MessageLog.h
new file mode 100644
index 0000000000..b3705287a6
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-clfs/MessageLog.h
@@ -0,0 +1,107 @@
+#ifndef QPID_STORE_MSCLFS_MESSAGELOG_H
+#define QPID_STORE_MSCLFS_MESSAGELOG_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <boost/intrusive_ptr.hpp>
+#include <qpid/broker/PersistableMessage.h>
+#include <qpid/broker/RecoveryManager.h>
+#include <qpid/sys/IntegerTypes.h>
+#include <qpid/store/StorageProvider.h>
+
+#include "Log.h"
+
+namespace qpid {
+namespace store {
+namespace ms_clfs {
+
+/**
+ * @class MessageLog
+ *
+ * Represents a CLFS-housed message log.
+ */
+class MessageLog : public Log {
+
+protected:
+ // Message log needs to have a no-op first record written in the log
+ // to ensure that no real message gets an ID 0.
+ virtual void initialize();
+
+public:
+ // Inherited and reimplemented from Log. Figure the minimum marshalling
+ // buffer size needed for the records this class writes.
+ virtual uint32_t marshallingBufferSize();
+
+ // Add the specified message to the log; Return the persistence Id.
+ uint64_t add(const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg);
+
+ // Write a Delete entry for messageId. If newFirstId is not 0, it is now
+ // the earliest valid message in the log, so move the tail up to it.
+ void deleteMessage(uint64_t messageId, uint64_t newFirstId);
+
+ // Load part or all of a message's content from previously stored
+ // log record(s).
+ void loadContent(uint64_t messageId,
+ std::string& data,
+ uint64_t offset,
+ uint32_t length);
+
+ // Enqueue and dequeue operations track messages' transit across
+ // queues; each operation may be associated with a transaction. If
+ // the transactionId is 0 the operation is not associated with a
+ // transaction.
+ void recordEnqueue (uint64_t messageId,
+ uint64_t queueId,
+ uint64_t transactionId);
+ void recordDequeue (uint64_t messageId,
+ uint64_t queueId,
+ uint64_t transactionId);
+
+ // Recover the messages and their queueing records from the log.
+ // @param recoverer Recovery manager used to recreate broker objects from
+ // encoded framing buffers recovered from the log.
+ // @param messageMap This method fills in the map of id -> ptr of
+ // recovered messages.
+ // @param messageOps This method fills in the map of msg id ->
+ // vector of operations involving the message that were
+ // recovered from the log. It is the caller's
+ // responsibility to sort the operations out and
+ // ascertain which operations should be acted on. The
+ // order of operations in the vector is as they were
+ // read in order from the log.
+ typedef enum { RECOVERED_ENQUEUE = 1, RECOVERED_DEQUEUE } RecoveredOpType;
+ struct RecoveredMsgOp {
+ RecoveredOpType op;
+ uint64_t queueId;
+ uint64_t txnId;
+
+ RecoveredMsgOp(RecoveredOpType o, const uint64_t& q, const uint64_t& t)
+ : op(o), queueId(q), txnId(t) {}
+ };
+ void recover(qpid::broker::RecoveryManager& recoverer,
+ qpid::store::MessageMap& messageMap,
+ std::map<uint64_t, std::vector<RecoveredMsgOp> >& messageOps);
+};
+
+}}} // namespace qpid::store::ms_clfs
+
+#endif /* QPID_STORE_MSCLFS_MESSAGELOG_H */
diff --git a/qpid/cpp/src/qpid/store/ms-clfs/Messages.cpp b/qpid/cpp/src/qpid/store/ms-clfs/Messages.cpp
new file mode 100644
index 0000000000..db5d2ebf4c
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-clfs/Messages.cpp
@@ -0,0 +1,472 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <qpid/log/Statement.h>
+
+#include "Messages.h"
+#include "Lsn.h"
+#include "qpid/store/StoreException.h"
+#include <boost/foreach.hpp>
+
+namespace qpid {
+namespace store {
+namespace ms_clfs {
+
+void
+Messages::openLog(const std::string& path, const Log::TuningParameters& params)
+{
+ log.open (path, params);
+}
+
+void
+Messages::add(const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg)
+{
+ uint64_t id = log.add(msg);
+ msg->setPersistenceId(id);
+ std::auto_ptr<MessageInfo> autom(new MessageInfo);
+ MessageInfo::shared_ptr m(autom);
+ std::pair<uint64_t, MessageInfo::shared_ptr> p(id, m);
+ {
+ qpid::sys::ScopedWlock<qpid::sys::RWlock> l(lock);
+ messages.insert(p);
+ // If there's only this one message there, move the tail to it.
+ // This prevents the log from continually growing when messages
+ // are added and removed one at a time.
+ if (messages.size() == 1) {
+ CLFS_LSN newTail = idToLsn(id);
+ log.moveTail(newTail);
+ }
+ }
+}
+
+void
+Messages::enqueue(uint64_t msgId, uint64_t queueId, Transaction::shared_ptr& t)
+{
+ MessageInfo::shared_ptr p;
+ {
+ qpid::sys::ScopedRlock<qpid::sys::RWlock> l(lock);
+ MessageMap::const_iterator i = messages.find(msgId);
+ if (i == messages.end())
+ THROW_STORE_EXCEPTION("Message does not exist");
+ p = i->second;
+ }
+ MessageInfo::Location loc(queueId, t, MessageInfo::TRANSACTION_ENQUEUE);
+ {
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(p->whereLock);
+ p->where.push_back(loc);
+ uint64_t transactionId = 0;
+ if (t.get() != 0) {
+ transactionId = t->getId();
+ t->enroll(msgId);
+ }
+ try {
+ log.recordEnqueue(msgId, queueId, transactionId);
+ }
+ catch (...) {
+ // Undo the record-keeping if the log wasn't written correctly.
+ if (transactionId != 0)
+ t->unenroll(msgId);
+ p->where.pop_back();
+ throw;
+ }
+ }
+}
+
+void
+Messages::dequeue(uint64_t msgId, uint64_t queueId, Transaction::shared_ptr& t)
+{
+ MessageInfo::shared_ptr p;
+ {
+ qpid::sys::ScopedRlock<qpid::sys::RWlock> l(lock);
+ MessageMap::const_iterator i = messages.find(msgId);
+ if (i == messages.end())
+ THROW_STORE_EXCEPTION("Message does not exist");
+ p = i->second;
+ }
+ {
+ // Locate the 'where' entry for the specified queue. Once this operation
+ // is recorded in the log, update the 'where' entry to reflect it.
+ // Note that an existing entry in 'where' that refers to a transaction
+ // is not eligible for this operation.
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(p->whereLock);
+ std::list<MessageInfo::Location>::iterator i;
+ for (i = p->where.begin(); i != p->where.end(); ++i) {
+ if (i->queueId == queueId && i->transaction.get() == 0)
+ break;
+ }
+ if (i == p->where.end())
+ THROW_STORE_EXCEPTION("Message not on queue");
+ uint64_t transactionId = 0;
+ if (t.get() != 0) {
+ transactionId = t->getId();
+ t->enroll(msgId);
+ }
+ try {
+ log.recordDequeue(msgId, queueId, transactionId);
+ }
+ catch (...) {
+ // Undo the record-keeping if the log wasn't written correctly.
+ if (transactionId != 0)
+ t->unenroll(msgId);
+ throw;
+ }
+ // Ok, logged successfully. If this is a transactional op, note
+ // the transaction. If non-transactional, remove the 'where' entry.
+ if (transactionId != 0) {
+ i->transaction = t;
+ i->disposition = MessageInfo::TRANSACTION_DEQUEUE;
+ }
+ else {
+ p->where.erase(i);
+ // If the message doesn't exist on any other queues, remove it.
+ if (p->where.empty())
+ remove(msgId);
+ }
+ }
+}
+
+// Commit a previous provisional enqueue or dequeue of a particular message
+// actions under a specified transaction. If this results in the message's
+// being removed from all queues, it is deleted.
+void
+Messages::commit(uint64_t msgId, Transaction::shared_ptr& t)
+{
+ MessageInfo::shared_ptr p;
+ {
+ qpid::sys::ScopedRlock<qpid::sys::RWlock> l(lock);
+ MessageMap::const_iterator i = messages.find(msgId);
+ if (i == messages.end())
+ THROW_STORE_EXCEPTION("Message does not exist");
+ p = i->second;
+ }
+ {
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(p->whereLock);
+ std::list<MessageInfo::Location>::iterator i;
+ for (i = p->where.begin(); i != p->where.end(); ++i) {
+ if (i->transaction != t)
+ continue;
+ // Transactional dequeues can now remove the item from the
+ // where list; enqueues just clear the transaction reference.
+ if (i->disposition == MessageInfo::TRANSACTION_DEQUEUE)
+ i = p->where.erase(i);
+ else
+ i->transaction.reset();
+ }
+ }
+ // If committing results in this message having no further enqueue
+ // references, delete it. If the delete fails, swallow the exception
+ // and let recovery take care of removing it later.
+ if (p->where.empty()) {
+ try {
+ remove(msgId);
+ }
+ catch(...) {}
+ }
+}
+
+// Abort a previous provisional enqueue or dequeue of a particular message
+// actions under a specified transaction. If this results in the message's
+// being removed from all queues, it is deleted.
+void
+Messages::abort(uint64_t msgId, Transaction::shared_ptr& t)
+{
+ MessageInfo::shared_ptr p;
+ {
+ qpid::sys::ScopedRlock<qpid::sys::RWlock> l(lock);
+ MessageMap::const_iterator i = messages.find(msgId);
+ if (i == messages.end())
+ THROW_STORE_EXCEPTION("Message does not exist");
+ p = i->second;
+ }
+ {
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(p->whereLock);
+ std::list<MessageInfo::Location>::iterator i = p->where.begin();
+ while (i != p->where.end()) {
+ if (i->transaction != t) {
+ ++i;
+ continue;
+ }
+ // Aborted transactional dequeues result in the message remaining
+ // enqueued like before the operation; enqueues clear the
+ // message from the where list - like the enqueue never happened.
+ if (i->disposition == MessageInfo::TRANSACTION_ENQUEUE)
+ i = p->where.erase(i);
+ else {
+ i->transaction.reset();
+ ++i;
+ }
+ }
+ }
+ // If aborting results in this message having no further enqueue
+ // references, delete it. If the delete fails, swallow the exception
+ // and let recovery take care of removing it later.
+ if (p->where.empty()) {
+ try {
+ remove(msgId);
+ }
+ catch(...) {}
+ }
+}
+
+// Load part or all of a message's content from previously stored
+// log record(s).
+void
+Messages::loadContent(uint64_t msgId,
+ std::string& data,
+ uint64_t offset,
+ uint32_t length)
+{
+ log.loadContent(msgId, data, offset, length);
+}
+
+// Recover the current set of messages and where they're queued from
+// the log.
+void
+Messages::recover(qpid::broker::RecoveryManager& recoverer,
+ const std::set<uint64_t> &validQueues,
+ const std::map<uint64_t, Transaction::shared_ptr>& transMap,
+ qpid::store::MessageMap& messageMap,
+ qpid::store::MessageQueueMap& messageQueueMap)
+{
+ std::map<uint64_t, std::vector<MessageLog::RecoveredMsgOp> > messageOps;
+ log.recover(recoverer, messageMap, messageOps);
+ // Now read through the messageOps replaying the operations with the
+ // knowledge of which transactions committed, aborted, etc. A transaction
+ // should not be deleted until there are no messages referencing it so
+ // a message operation with a transaction id not found in transMap is
+ // a serious problem.
+ QPID_LOG(debug, "Beginning CLFS-recovered message operation replay");
+ // Keep track of any messages that are recovered from the log but don't
+ // have any place to be. This can happen, for example, if the broker
+ // crashes while logging a message deletion. After all the recovery is
+ // done, delete all the homeless messages.
+ std::vector<uint64_t> homeless;
+ std::map<uint64_t, std::vector<MessageLog::RecoveredMsgOp> >::const_iterator msg;
+ for (msg = messageOps.begin(); msg != messageOps.end(); ++msg) {
+ uint64_t msgId = msg->first;
+ const std::vector<MessageLog::RecoveredMsgOp>& ops = msg->second;
+ QPID_LOG(debug, "Message " << msgId << "; " << ops.size() << " op(s)");
+ MessageInfo::shared_ptr m(new MessageInfo);
+ std::vector<QueueEntry>& entries = messageQueueMap[msgId];
+ std::vector<MessageLog::RecoveredMsgOp>::const_iterator op;
+ for (op = ops.begin(); op != ops.end(); ++op) {
+ QueueEntry entry(op->queueId);
+ MessageInfo::Location loc(op->queueId);
+ std::string dir =
+ op->op == MessageLog::RECOVERED_ENQUEUE ? "enqueue"
+ : "dequeue";
+ if (validQueues.find(op->queueId) == validQueues.end()) {
+ QPID_LOG(info,
+ "Message " << msgId << dir << " on non-existant queue "
+ << op->queueId << "; dropped");
+ continue;
+ }
+ if (op->txnId != 0) {
+ // Be sure to enroll this message in the transaction even if
+ // it has committed or aborted. This ensures that the
+ // transaction isn't removed from the log while finalizing the
+ // recovery. If it were to be removed and the broker failed
+ // again before removing this message during normal operation,
+ // it couldn't be recovered again.
+ //
+ // Recall what is being reconstructed; 2 things:
+ // 1. This class's 'messages' list which keeps track
+ // of the queues each message is on and the transactions
+ // each message is enrolled in. For this, aborted
+ // transactions cause the result of the operation to be
+ // ignored, but the message does need to be enrolled in
+ // the transaction to properly maintain the transaction
+ // references until the message is deleted.
+ // 2. The StorageProvider's MessageQueueMap, which also
+ // has an entry for each queue each message is on and
+ // its TPL status and associated xid.
+ const Transaction::shared_ptr &t =
+ transMap.find(op->txnId)->second;
+ // Prepared transactions cause the operation to be
+ // provisionally acted on, and the message to be enrolled in
+ // the transaction for when it commits/aborts. This is
+ // noted in the QueueEntry for the StorageProvider's map.
+ if (t->getState() == Transaction::TRANS_PREPARED) {
+ QPID_LOG(debug, dir << " for queue " << op->queueId <<
+ ", prepared txn " << op->txnId);
+ TPCTransaction::shared_ptr tpct(boost::dynamic_pointer_cast<TPCTransaction>(t));
+ if (tpct.get() == 0)
+ THROW_STORE_EXCEPTION("Invalid transaction state");
+ t->enroll(msgId);
+ entry.xid = tpct->getXid();
+ loc.transaction = t;
+ if (op->op == MessageLog::RECOVERED_ENQUEUE) {
+ entry.tplStatus = QueueEntry::ADDING;
+ loc.disposition = MessageInfo::TRANSACTION_ENQUEUE;
+ }
+ else {
+ entry.tplStatus = QueueEntry::REMOVING;
+ loc.disposition = MessageInfo::TRANSACTION_DEQUEUE;
+ }
+ }
+ else if (t->getState() != Transaction::TRANS_COMMITTED) {
+ QPID_LOG(debug, dir << " for queue " << op->queueId <<
+ ", txn " << op->txnId << ", rolling back");
+ continue;
+ }
+ }
+ // Here for non-transactional and prepared transactional operations
+ // to set up the messageQueueMap entries. Note that at this point
+ // a committed transactional operation looks like a
+ // non-transactional one as far as the QueueEntry is
+ // concerned - just do it. If this is an entry enqueuing a
+ // message, just add it to the entries list. If it's a dequeue
+ // operation, locate the matching entry for the queue and delete
+ // it if the current op is non-transactional; if it's a prepared
+ // transaction then replace the existing entry with the current
+ // one that notes the message is enqueued but being removed under
+ // a prepared transaction.
+ QPID_LOG(debug, dir + " at queue " << entry.queueId);
+ if (op->op == MessageLog::RECOVERED_ENQUEUE) {
+ entries.push_back(entry);
+ m->where.push_back(loc);
+ }
+ else {
+ std::vector<QueueEntry>::iterator i = entries.begin();
+ while (i != entries.end()) {
+ if (i->queueId == entry.queueId) {
+ if (entry.tplStatus != QueueEntry::NONE)
+ *i = entry;
+ else
+ entries.erase(i);
+ break;
+ }
+ ++i;
+ }
+ std::list<MessageInfo::Location>::iterator w = m->where.begin();
+ while (w != m->where.end()) {
+ if (w->queueId == loc.queueId) {
+ if (loc.transaction.get() != 0) {
+ *w = loc;
+ ++w;
+ }
+ else {
+ w = m->where.erase(w);
+ }
+ }
+ }
+ }
+ }
+ // Now that all the queue entries have been set correctly, see if
+ // there are any entries; they may have all been removed during
+ // recovery. If there are none, add this message to the homeless
+ // list to be deleted from the log after the recovery is done.
+ if (m->where.size() == 0) {
+ homeless.push_back(msgId);
+ messageMap.erase(msgId);
+ messageQueueMap.erase(msgId);
+ }
+ else {
+ std::pair<uint64_t, MessageInfo::shared_ptr> p(msgId, m);
+ messages.insert(p);
+ }
+ }
+
+ QPID_LOG(debug, "Message log recovery done.");
+ // Done! Ok, go back and delete all the homeless messages.
+ BOOST_FOREACH(uint64_t msg, homeless) {
+ QPID_LOG(debug, "Deleting homeless message " << msg);
+ remove(msg);
+ }
+}
+
+// Expunge is called when a queue is deleted. All references to that
+// queue must be expunged from all messages. 'Dequeue' log records are
+// written for each queue entry removed, but any errors are swallowed.
+// On recovery there's a list of valid queues passed in. The deleted
+// queue will not be on that list so if any references to it are
+// recovered they'll get weeded out then.
+void
+Messages::expunge(uint64_t queueId)
+{
+ std::vector<uint64_t> toBeDeleted; // Messages to be deleted later.
+
+ {
+ // Lock everybody out since all messages are possibly in play.
+ // There also may be other threads already working on particular
+ // messages so individual message mutex still must be acquired.
+ qpid::sys::ScopedWlock<qpid::sys::RWlock> l(lock);
+ MessageMap::iterator m;
+ for (m = messages.begin(); m != messages.end(); ++m) {
+ MessageInfo::shared_ptr p = m->second;
+ {
+ qpid::sys::ScopedLock<qpid::sys::Mutex> ml(p->whereLock);
+ std::list<MessageInfo::Location>::iterator i = p->where.begin();
+ while (i != p->where.end()) {
+ if (i->queueId != queueId) {
+ ++i;
+ continue;
+ }
+ // If this entry is involved in a transaction, unenroll it.
+ // Then remove the entry.
+ if (i->transaction.get() != 0)
+ i->transaction->unenroll(m->first);
+ i = p->where.erase(i);
+ try {
+ log.recordDequeue(m->first, queueId, 0);
+ }
+ catch(...) {
+ }
+ }
+ if (p->where.size() == 0)
+ toBeDeleted.push_back(m->first);
+ }
+ }
+ }
+ // Swallow any exceptions during this; don't care. Recover it later
+ // if needed.
+ try {
+ BOOST_FOREACH(uint64_t msg, toBeDeleted)
+ remove(msg);
+ }
+ catch(...) {
+ }
+}
+
+// Remove a specified message from those controlled by this object.
+void
+Messages::remove(uint64_t messageId)
+{
+ uint64_t newFirstId = 0;
+ {
+ qpid::sys::ScopedWlock<qpid::sys::RWlock> l(lock);
+ messages.erase(messageId);
+ // May have deleted the first entry; if so the log can release that.
+ // If this message being deleted results in an empty list of
+ // messages, move the tail up to this message's LSN. This may
+ // result in one or more messages being stranded in the log
+ // until there's more activity. If a restart happens while these
+ // unneeded log records are there, the presence of the MessageDelete
+ // entry will cause the message(s) to be ignored anyway.
+ if (messages.empty())
+ newFirstId = messageId;
+ else if (messages.begin()->first > messageId)
+ newFirstId = messages.begin()->first;
+ }
+ log.deleteMessage(messageId, newFirstId);
+}
+
+}}} // namespace qpid::store::ms_clfs
diff --git a/qpid/cpp/src/qpid/store/ms-clfs/Messages.h b/qpid/cpp/src/qpid/store/ms-clfs/Messages.h
new file mode 100644
index 0000000000..93cc8bfe62
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-clfs/Messages.h
@@ -0,0 +1,144 @@
+#ifndef QPID_STORE_MSCLFS_MESSAGES_H
+#define QPID_STORE_MSCLFS_MESSAGES_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <windows.h>
+#include <map>
+#include <set>
+#include <vector>
+#include <boost/intrusive_ptr.hpp>
+#include <qpid/broker/PersistableMessage.h>
+#include <qpid/sys/Mutex.h>
+
+#include "MessageLog.h"
+#include "Transaction.h"
+
+namespace qpid {
+namespace store {
+namespace ms_clfs {
+
+class Messages {
+
+ struct MessageInfo {
+ // How many queues this message is on, whether actually (non-transacted)
+ // or provisionally (included in a non-yet-committed transaction).
+ volatile LONG enqueuedCount;
+
+ // Keep a list of transactional operations this message is
+ // referenced in. When the transaction changes/finalizes these all
+ // need to be acted on.
+ typedef enum { TRANSACTION_NONE = 0,
+ TRANSACTION_ENQUEUE,
+ TRANSACTION_DEQUEUE } TransType;
+#if 0
+ std::map<Transaction::shared_ptr, std::vector<TransType> > transOps;
+ qpid::sys::Mutex transOpsLock;
+#endif
+ // Think what I need is a list of "where is this message" - queue id,
+ // transaction ref, what kind of trans op (enq/deq). Then "remove all
+ // queue refs" can search through all messages looking for queue ids
+ // and undo them. Write "remove from queue" record to log. Also need to
+ // add "remove from queue" to recovery.
+ struct Location {
+ uint64_t queueId;
+ Transaction::shared_ptr transaction;
+ TransType disposition;
+
+ Location(uint64_t q)
+ : queueId(q), transaction(), disposition(TRANSACTION_NONE) {}
+ Location(uint64_t q, Transaction::shared_ptr& t, TransType d)
+ : queueId(q), transaction(t), disposition(d) {}
+ };
+ qpid::sys::Mutex whereLock;
+ std::list<Location> where;
+ // The transactions vector just keeps a shared_ptr to each
+ // Transaction this message was involved in, regardless of the
+ // disposition or transaction state. Keeping a valid shared_ptr
+ // prevents the Transaction from being deleted. As long as there
+ // are any messages that referred to a transaction, that
+ // transaction's state needs to be known so the message disposition
+ // can be correctly recovered if needed.
+ std::vector<Transaction::shared_ptr> transactions;
+
+ typedef boost::shared_ptr<MessageInfo> shared_ptr;
+
+ MessageInfo() : enqueuedCount(0) {}
+ };
+
+ qpid::sys::RWlock lock;
+ typedef std::map<uint64_t, MessageInfo::shared_ptr> MessageMap;
+ MessageMap messages;
+ MessageLog log;
+
+ // Remove a specified message from those controlled by this object.
+ void remove(uint64_t messageId);
+
+public:
+ void openLog(const std::string& path, const Log::TuningParameters& params);
+
+ // Add the specified message to the log and list of known messages.
+ // Upon successful return the message's persistenceId is set.
+ void add(const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg);
+
+ // Add the specified queue to the message's list of places it is
+ // enqueued.
+ void enqueue(uint64_t msgId, uint64_t queueId, Transaction::shared_ptr& t);
+
+ // Remove the specified queue from the message's list of places it is
+ // enqueued. If there are no other queues holding the message, it is
+ // deleted.
+ void dequeue(uint64_t msgId, uint64_t queueId, Transaction::shared_ptr& t);
+
+ // Commit a previous provisional enqueue or dequeue of a particular message
+ // actions under a specified transaction. If this results in the message's
+ // being removed from all queues, it is deleted.
+ void commit(uint64_t msgId, Transaction::shared_ptr& transaction);
+
+ // Abort a previous provisional enqueue or dequeue of a particular message
+ // actions under a specified transaction. If this results in the message's
+ // being removed from all queues, it is deleted.
+ void abort(uint64_t msgId, Transaction::shared_ptr& transaction);
+
+ // Load part or all of a message's content from previously stored
+ // log record(s).
+ void loadContent(uint64_t msgId,
+ std::string& data,
+ uint64_t offset,
+ uint32_t length);
+
+ // Expunge is called when a queue is deleted. All references to that
+ // queue must be expunged from all messages.
+ void expunge(uint64_t queueId);
+
+ // Recover the current set of messages and where they're queued from
+ // the log.
+ void recover(qpid::broker::RecoveryManager& recoverer,
+ const std::set<uint64_t> &validQueues,
+ const std::map<uint64_t, Transaction::shared_ptr>& transMap,
+ qpid::store::MessageMap& messageMap,
+ qpid::store::MessageQueueMap& messageQueueMap);
+};
+
+}}} // namespace qpid::store::ms_clfs
+
+#endif /* QPID_STORE_MSCLFS_MESSAGES_H */
diff --git a/qpid/cpp/src/qpid/store/ms-clfs/Transaction.cpp b/qpid/cpp/src/qpid/store/ms-clfs/Transaction.cpp
new file mode 100644
index 0000000000..f94fef6f84
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-clfs/Transaction.cpp
@@ -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.
+ *
+ */
+
+#include "Transaction.h"
+#include "Messages.h"
+
+namespace qpid {
+namespace store {
+namespace ms_clfs {
+
+Transaction::~Transaction()
+{
+ // Transactions that are recovered then found to be deleted get destroyed
+ // but need not be logged.
+ if (state != TRANS_DELETED)
+ log->deleteTransaction(id);
+}
+
+void
+Transaction::enroll(uint64_t msgId)
+{
+ qpid::sys::ScopedWlock<qpid::sys::RWlock> l(enrollLock);
+ enrolledMessages.push_back(msgId);
+}
+
+void
+Transaction::unenroll(uint64_t msgId)
+{
+ qpid::sys::ScopedWlock<qpid::sys::RWlock> l(enrollLock);
+ for (std::vector<uint64_t>::iterator i = enrolledMessages.begin();
+ i < enrolledMessages.end();
+ ++i) {
+ if (*i == msgId) {
+ enrolledMessages.erase(i);
+ break;
+ }
+ }
+}
+
+void
+Transaction::abort(Messages& messages)
+{
+ log->recordAbort(id);
+ for (size_t i = 0; i < enrolledMessages.size(); ++i)
+ messages.abort(enrolledMessages[i], shared_from_this());
+ state = TRANS_ABORTED;
+}
+
+void
+Transaction::commit(Messages& messages)
+{
+ log->recordCommit(id);
+ for (size_t i = 0; i < enrolledMessages.size(); ++i)
+ messages.commit(enrolledMessages[i], shared_from_this());
+ state = TRANS_COMMITTED;
+}
+
+void
+TPCTransaction::prepare(void)
+{
+ log->recordPrepare(id);
+ state = TRANS_PREPARED;
+}
+
+}}} // namespace qpid::store::ms_clfs
diff --git a/qpid/cpp/src/qpid/store/ms-clfs/Transaction.h b/qpid/cpp/src/qpid/store/ms-clfs/Transaction.h
new file mode 100644
index 0000000000..499b01d503
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-clfs/Transaction.h
@@ -0,0 +1,147 @@
+#ifndef QPID_STORE_MSCLFS_TRANSACTION_H
+#define QPID_STORE_MSCLFS_TRANSACTION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <qpid/broker/TransactionalStore.h>
+#include <qpid/sys/Mutex.h>
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/shared_ptr.hpp>
+#include <string>
+#include <vector>
+
+#include "TransactionLog.h"
+
+namespace qpid {
+namespace store {
+namespace ms_clfs {
+
+class Messages;
+
+/**
+ * @class Transaction
+ *
+ * Class representing an AMQP transaction. This is used around a set of
+ * enqueue and dequeue operations that occur when the broker is acting
+ * on a transaction commit/abort from the client.
+ * This class is what the store uses internally to implement things a
+ * transaction needs; the broker knows about TransactionContext, which
+ * holds a pointer to Transaction.
+ *
+ * NOTE: All references to Transactions (and TPCTransactions, below) are
+ * through Boost shared_ptr instances. All messages enrolled in a transaction
+ * hold a shared_ptr. Thus, a Transaction object will not be deleted until all
+ * messages holding a reference to it are deleted. This fact is also used
+ * during recovery to automatically clean up and delete any Transaction without
+ * messages left referring to it.
+ */
+class Transaction : public boost::enable_shared_from_this<Transaction> {
+private:
+ // TransactionLog has to create all Transaction instances.
+ Transaction() {}
+
+public:
+
+ typedef boost::shared_ptr<Transaction> shared_ptr;
+ typedef enum { TRANS_OPEN = 1,
+ TRANS_PREPARED,
+ TRANS_ABORTED,
+ TRANS_COMMITTED,
+ TRANS_DELETED } State;
+
+ virtual ~Transaction();
+
+ uint64_t getId() { return id; }
+ State getState() { return state; }
+
+ void enroll(uint64_t msgId);
+ void unenroll(uint64_t msgId); // For failed ops, not normal end-of-trans
+
+ void abort(Messages& messages);
+ void commit(Messages& messages);
+
+protected:
+ friend class TransactionLog;
+ Transaction(uint64_t _id, const TransactionLog::shared_ptr& _log)
+ : id(_id), state(TRANS_OPEN), log(_log) {}
+
+ uint64_t id;
+ State state;
+ TransactionLog::shared_ptr log;
+ std::vector<uint64_t> enrolledMessages;
+ qpid::sys::RWlock enrollLock;
+};
+
+class TransactionContext : public qpid::broker::TransactionContext {
+ Transaction::shared_ptr transaction;
+
+public:
+ TransactionContext(const Transaction::shared_ptr& _transaction)
+ : transaction(_transaction) {}
+
+ virtual Transaction::shared_ptr& getTransaction() { return transaction; }
+};
+
+/**
+ * @class TPCTransaction
+ *
+ * Class representing a Two-Phase-Commit (TPC) AMQP transaction. This is
+ * used around a set of enqueue and dequeue operations that occur when the
+ * broker is acting on a transaction prepare/commit/abort from the client.
+ * This class is what the store uses internally to implement things a
+ * transaction needs; the broker knows about TPCTransactionContext, which
+ * holds a pointer to TPCTransaction.
+ */
+class TPCTransaction : public Transaction {
+
+ friend class TransactionLog;
+ TPCTransaction(uint64_t _id,
+ const TransactionLog::shared_ptr& _log,
+ const std::string& _xid)
+ : Transaction(_id, _log), xid(_xid) {}
+
+ std::string xid;
+
+public:
+ typedef boost::shared_ptr<TPCTransaction> shared_ptr;
+
+ virtual ~TPCTransaction() {}
+
+ void prepare();
+ bool isPrepared() const { return state == TRANS_PREPARED; }
+
+ const std::string& getXid(void) const { return xid; }
+};
+
+class TPCTransactionContext : public qpid::broker::TPCTransactionContext {
+ TPCTransaction::shared_ptr transaction;
+
+public:
+ TPCTransactionContext(const TPCTransaction::shared_ptr& _transaction)
+ : transaction(_transaction) {}
+
+ virtual TPCTransaction::shared_ptr& getTransaction() { return transaction; }
+};
+
+}}} // namespace qpid::store::ms_clfs
+
+#endif /* QPID_STORE_MSCLFS_TRANSACTION_H */
diff --git a/qpid/cpp/src/qpid/store/ms-clfs/TransactionLog.cpp b/qpid/cpp/src/qpid/store/ms-clfs/TransactionLog.cpp
new file mode 100644
index 0000000000..0ef046d7c8
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-clfs/TransactionLog.cpp
@@ -0,0 +1,428 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <windows.h>
+#include <clfsw32.h>
+#include <exception>
+#include <malloc.h>
+#include <memory.h>
+#include <qpid/framing/Buffer.h>
+#include <qpid/log/Statement.h>
+#include <qpid/sys/IntegerTypes.h>
+#include <qpid/sys/windows/check.h>
+
+#include "TransactionLog.h"
+#include "Transaction.h"
+#include "Lsn.h"
+
+namespace qpid {
+namespace store {
+namespace ms_clfs {
+
+namespace {
+
+// Structures that hold log records. Each has a type field at the start.
+enum TransactionEntryType {
+ TransactionStartDtxEntry = 1,
+ TransactionStartTxEntry = 2,
+ TransactionPrepareEntry = 3,
+ TransactionCommitEntry = 4,
+ TransactionAbortEntry = 5,
+ TransactionDeleteEntry = 6
+};
+// The only thing that really takes up space in transaction records is the
+// xid. Max xid length is in the neighborhood of 600 bytes. Leave some room.
+static const uint32_t MaxTransactionContentLength = 1024;
+
+// Dtx-Start
+struct TransactionStartDtx {
+ TransactionEntryType type;
+ uint32_t length;
+ char content[MaxTransactionContentLength];
+
+ TransactionStartDtx()
+ : type(TransactionStartDtxEntry), length(0) {}
+};
+// Tx-Start
+struct TransactionStartTx {
+ TransactionEntryType type;
+
+ TransactionStartTx()
+ : type(TransactionStartTxEntry) {}
+};
+// Prepare
+struct TransactionPrepare {
+ TransactionEntryType type;
+
+ TransactionPrepare()
+ : type(TransactionPrepareEntry) {}
+};
+// Commit
+struct TransactionCommit {
+ TransactionEntryType type;
+
+ TransactionCommit()
+ : type(TransactionCommitEntry) {}
+};
+// Abort
+struct TransactionAbort {
+ TransactionEntryType type;
+
+ TransactionAbort()
+ : type(TransactionAbortEntry) {}
+};
+// Delete
+struct TransactionDelete {
+ TransactionEntryType type;
+
+ TransactionDelete()
+ : type(TransactionDeleteEntry) {}
+};
+
+} // namespace
+
+void
+TransactionLog::initialize()
+{
+ // Write something to occupy the first record, preventing a real
+ // transaction from being lsn/id 0. Delete of a non-existant id is easily
+ // tossed during recovery if no other transactions have caused the tail
+ // to be moved up past this dummy record by then.
+ deleteTransaction(0);
+}
+
+uint32_t
+TransactionLog::marshallingBufferSize()
+{
+ size_t biggestNeed = sizeof(TransactionStartDtx);
+ uint32_t defSize = static_cast<uint32_t>(biggestNeed);
+ uint32_t minSize = Log::marshallingBufferSize();
+ if (defSize <= minSize)
+ return minSize;
+ // Round up to multiple of minSize
+ return (defSize + minSize) / minSize * minSize;
+}
+
+// Get a new Transaction
+boost::shared_ptr<Transaction>
+TransactionLog::begin()
+{
+ TransactionStartTx entry;
+ CLFS_LSN location;
+ uint64_t id;
+ uint32_t entryLength = static_cast<uint32_t>(sizeof(entry));
+ location = write(&entry, entryLength);
+ try {
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(idsLock);
+ id = lsnToId(location);
+ std::auto_ptr<Transaction> t(new Transaction(id, shared_from_this()));
+ boost::shared_ptr<Transaction> t2(t);
+ boost::weak_ptr<Transaction> weak_t2(t2);
+ {
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(idsLock);
+ validIds[id] = weak_t2;
+ }
+ return t2;
+ }
+ catch(...) {
+ deleteTransaction(id);
+ throw;
+ }
+}
+
+// Get a new TPCTransaction
+boost::shared_ptr<TPCTransaction>
+TransactionLog::begin(const std::string& xid)
+{
+ TransactionStartDtx entry;
+ CLFS_LSN location;
+ uint64_t id;
+ uint32_t entryLength = static_cast<uint32_t>(sizeof(entry));
+ entry.length = static_cast<uint32_t>(xid.length());
+ memcpy_s(entry.content, sizeof(entry.content),
+ xid.c_str(), xid.length());
+ entryLength -= (sizeof(entry.content) - entry.length);
+ location = write(&entry, entryLength);
+ try {
+ id = lsnToId(location);
+ std::auto_ptr<TPCTransaction> t(new TPCTransaction(id,
+ shared_from_this(),
+ xid));
+ boost::shared_ptr<TPCTransaction> t2(t);
+ boost::weak_ptr<Transaction> weak_t2(t2);
+ {
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(idsLock);
+ validIds[id] = weak_t2;
+ }
+ return t2;
+ }
+ catch(...) {
+ deleteTransaction(id);
+ throw;
+ }
+}
+
+void
+TransactionLog::recordPrepare(uint64_t transId)
+{
+ TransactionPrepare entry;
+ CLFS_LSN transLsn = idToLsn(transId);
+ write(&entry, sizeof(entry), &transLsn);
+}
+
+void
+TransactionLog::recordCommit(uint64_t transId)
+{
+ TransactionCommit entry;
+ CLFS_LSN transLsn = idToLsn(transId);
+ write(&entry, sizeof(entry), &transLsn);
+ {
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(idsLock);
+ validIds[transId].reset();
+ }
+}
+
+void
+TransactionLog::recordAbort(uint64_t transId)
+{
+ TransactionAbort entry;
+ CLFS_LSN transLsn = idToLsn(transId);
+ write(&entry, sizeof(entry), &transLsn);
+ {
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(idsLock);
+ validIds[transId].reset();
+ }
+}
+
+void
+TransactionLog::deleteTransaction(uint64_t transId)
+{
+ uint64_t newFirstId = 0;
+ {
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(idsLock);
+ validIds.erase(transId);
+ // May have deleted the first entry; if so the log can release that.
+ // If this deletion results in an empty list of transactions,
+ // move the tail up to this transaction's LSN. This may result in
+ // one or more transactions being stranded in the log until there's
+ // more activity. If a restart happens while these unneeded log
+ // records are there, the presence of the TransactionDelete
+ // entry will cause them to be ignored anyway.
+ if (validIds.empty())
+ newFirstId = transId;
+ else if (validIds.begin()->first > transId)
+ newFirstId = validIds.begin()->first;
+ }
+ TransactionDelete deleteEntry;
+ CLFS_LSN transLsn = idToLsn(transId);
+ write(&deleteEntry, sizeof(deleteEntry), &transLsn);
+ if (newFirstId != 0)
+ moveTail(idToLsn(newFirstId));
+}
+
+void
+TransactionLog::collectPreparedXids(std::map<std::string, TPCTransaction::shared_ptr>& preparedMap)
+{
+ // Go through all the known transactions; if the transaction is still
+ // valid (open or prepared) it will have weak_ptr to the Transaction.
+ // If it can be downcast and has a state of TRANS_PREPARED, add to the map.
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(idsLock);
+ std::map<uint64_t, boost::weak_ptr<Transaction> >::const_iterator i;
+ for (i = validIds.begin(); i != validIds.end(); ++i) {
+ Transaction::shared_ptr t = i->second.lock();
+ if (t.get() == 0)
+ continue;
+ TPCTransaction::shared_ptr tpct(boost::dynamic_pointer_cast<TPCTransaction>(t));
+ if (tpct.get() == 0)
+ continue;
+ if (tpct->state == Transaction::TRANS_PREPARED)
+ preparedMap[tpct->getXid()] = tpct;
+ }
+}
+
+void
+TransactionLog::recover(std::map<uint64_t, Transaction::shared_ptr>& transMap)
+{
+ QPID_LOG(debug, "Recovering transaction log");
+
+ // Note that there may be transaction refs in the log which are deleted,
+ // so be sure to only add transactions at Start records, and ignore those
+ // that don't have an existing message record.
+ // Get the base LSN - that's how to say "start reading at the beginning"
+ CLFS_INFORMATION info;
+ ULONG infoLength = sizeof (info);
+ BOOL ok = ::GetLogFileInformation(handle, &info, &infoLength);
+ QPID_WINDOWS_CHECK_NOT(ok, 0);
+
+ // Pointers for the various record types that can be assigned in the
+ // reading loop below.
+ TransactionStartDtx *startDtxEntry;
+ TransactionStartTx *startTxEntry;
+
+ PVOID recordPointer;
+ ULONG recordLength;
+ CLFS_RECORD_TYPE recordType = ClfsDataRecord;
+ CLFS_LSN transLsn, current, undoNext;
+ PVOID readContext;
+ uint64_t transId;
+ // Note 'current' in case it's needed below; ReadNextLogRecord returns it
+ // via a parameter.
+ current = info.BaseLsn;
+ ok = ::ReadLogRecord(marshal,
+ &info.BaseLsn,
+ ClfsContextForward,
+ &recordPointer,
+ &recordLength,
+ &recordType,
+ &undoNext,
+ &transLsn,
+ &readContext,
+ 0);
+
+ std::auto_ptr<Transaction> tPtr;
+ std::auto_ptr<TPCTransaction> tpcPtr;
+ while (ok) {
+ std::string xid;
+
+ // All the record types this class writes have a TransactionEntryType
+ // in the beginning. Based on that, do what's needed.
+ TransactionEntryType *t =
+ reinterpret_cast<TransactionEntryType *>(recordPointer);
+ switch(*t) {
+ case TransactionStartDtxEntry:
+ startDtxEntry =
+ reinterpret_cast<TransactionStartDtx *>(recordPointer);
+ transId = lsnToId(current);
+ QPID_LOG(debug, "Dtx start, id " << transId);
+ xid.assign(startDtxEntry->content, startDtxEntry->length);
+ tpcPtr.reset(new TPCTransaction(transId, shared_from_this(), xid));
+ transMap[transId] = boost::shared_ptr<TPCTransaction>(tpcPtr);
+ break;
+ case TransactionStartTxEntry:
+ startTxEntry =
+ reinterpret_cast<TransactionStartTx *>(recordPointer);
+ transId = lsnToId(current);
+ QPID_LOG(debug, "Tx start, id " << transId);
+ tPtr.reset(new Transaction(transId, shared_from_this()));
+ transMap[transId] = boost::shared_ptr<Transaction>(tPtr);
+ break;
+ case TransactionPrepareEntry:
+ transId = lsnToId(transLsn);
+ QPID_LOG(debug, "Dtx prepare, id " << transId);
+ if (transMap.find(transId) == transMap.end()) {
+ QPID_LOG(debug,
+ "Dtx " << transId << " doesn't exist; discarded");
+ }
+ else {
+ transMap[transId]->state = Transaction::TRANS_PREPARED;
+ }
+ break;
+ case TransactionCommitEntry:
+ transId = lsnToId(transLsn);
+ QPID_LOG(debug, "Txn commit, id " << transId);
+ if (transMap.find(transId) == transMap.end()) {
+ QPID_LOG(debug,
+ "Txn " << transId << " doesn't exist; discarded");
+ }
+ else {
+ transMap[transId]->state = Transaction::TRANS_COMMITTED;
+ }
+ break;
+ case TransactionAbortEntry:
+ transId = lsnToId(transLsn);
+ QPID_LOG(debug, "Txn abort, id " << transId);
+ if (transMap.find(transId) == transMap.end()) {
+ QPID_LOG(debug,
+ "Txn " << transId << " doesn't exist; discarded");
+ }
+ else {
+ transMap[transId]->state = Transaction::TRANS_ABORTED;
+ }
+ break;
+ case TransactionDeleteEntry:
+ transId = lsnToId(transLsn);
+ QPID_LOG(debug, "Txn delete, id " << transId);
+ if (transMap.find(transId) == transMap.end()) {
+ QPID_LOG(debug,
+ "Txn " << transId << " doesn't exist; discarded");
+ }
+ else {
+ transMap[transId]->state = Transaction::TRANS_DELETED;
+ transMap.erase(transId);
+ }
+ break;
+ default:
+ throw std::runtime_error("Bad transaction log entry type");
+ }
+
+ recordType = ClfsDataRecord;
+ ok = ::ReadNextLogRecord(readContext,
+ &recordPointer,
+ &recordLength,
+ &recordType,
+ 0, // No userLsn
+ &undoNext,
+ &transLsn,
+ &current,
+ 0);
+ }
+ DWORD status = ::GetLastError();
+ ::TerminateReadLog(readContext);
+ if (status != ERROR_HANDLE_EOF) // No more records
+ throw QPID_WINDOWS_ERROR(status);
+
+ QPID_LOG(debug, "Transaction log recovered");
+
+ // At this point we have a list of all the not-deleted transactions that
+ // were in existence when the broker last ran. All transactions of both
+ // Dtx and Tx types that haven't prepared or committed will be aborted.
+ // This will give the proper background against which to decide each
+ // message's disposition when recovering messages that were involved in
+ // transactions.
+ // In addition to recovering and aborting transactions, rebuild the
+ // validIds map now that we know which ids are really valid.
+ std::map<uint64_t, Transaction::shared_ptr>::const_iterator i;
+ for (i = transMap.begin(); i != transMap.end(); ++i) {
+ switch(i->second->state) {
+ case Transaction::TRANS_OPEN:
+ QPID_LOG(debug, "Txn " << i->first << " was open; aborted");
+ i->second->state = Transaction::TRANS_ABORTED;
+ break;
+ case Transaction::TRANS_ABORTED:
+ QPID_LOG(debug, "Txn " << i->first << " was aborted");
+ break;
+ case Transaction::TRANS_COMMITTED:
+ QPID_LOG(debug, "Txn " << i->first << " was committed");
+ break;
+ case Transaction::TRANS_PREPARED:
+ QPID_LOG(debug, "Txn " << i->first << " was prepared");
+ break;
+ case Transaction::TRANS_DELETED:
+ QPID_LOG(error,
+ "Txn " << i->first << " was deleted; shouldn't be here");
+ break;
+ }
+ boost::weak_ptr<Transaction> weak_txn(i->second);
+ validIds[i->first] = weak_txn;
+ }
+}
+
+}}} // namespace qpid::store::ms_clfs
diff --git a/qpid/cpp/src/qpid/store/ms-clfs/TransactionLog.h b/qpid/cpp/src/qpid/store/ms-clfs/TransactionLog.h
new file mode 100644
index 0000000000..7ca27c229e
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-clfs/TransactionLog.h
@@ -0,0 +1,104 @@
+#ifndef QPID_STORE_MSCLFS_TRANSACTIONLOG_H
+#define QPID_STORE_MSCLFS_TRANSACTIONLOG_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <set>
+
+#include <boost/enable_shared_from_this.hpp>
+#include <boost/shared_ptr.hpp>
+
+#include <qpid/broker/RecoveryManager.h>
+#include <qpid/sys/IntegerTypes.h>
+#include <qpid/sys/Mutex.h>
+
+#include "Log.h"
+
+namespace qpid {
+namespace store {
+namespace ms_clfs {
+
+class Transaction;
+class TPCTransaction;
+
+/**
+ * @class TransactionLog
+ *
+ * Represents a CLFS-housed transaction log.
+ */
+class TransactionLog : public Log,
+ public boost::enable_shared_from_this<TransactionLog> {
+
+ // To know when it's ok to move the log tail the lowest valid Id must
+ // always be known. Keep track of valid Ids here. These are transactions
+ // which have not yet been Deleted in the log. They may be new, in progress,
+ // prepared, committed, or aborted - but not deleted.
+ // Entries corresponding to not-yet-finalized transactions (i.e., open or
+ // prepared) also have a weak_ptr so the Transaction can be accessed.
+ // This is primarily to check its state and get a list of prepared Xids.
+ std::map<uint64_t, boost::weak_ptr<Transaction> > validIds;
+ qpid::sys::Mutex idsLock;
+
+protected:
+ // Transaction log needs to have a no-op first record written in the log
+ // to ensure that no real transaction gets an ID 0; messages think trans
+ // id 0 means "no transaction."
+ virtual void initialize();
+
+public:
+ // Inherited and reimplemented from Log. Figure the minimum marshalling
+ // buffer size needed for the records this class writes.
+ virtual uint32_t marshallingBufferSize();
+
+ typedef boost::shared_ptr<TransactionLog> shared_ptr;
+
+ // Get a new Transaction
+ boost::shared_ptr<Transaction> begin();
+
+ // Get a new TPCTransaction
+ boost::shared_ptr<TPCTransaction> begin(const std::string& xid);
+
+ void recordPrepare(uint64_t transId);
+ void recordCommit(uint64_t transId);
+ void recordAbort(uint64_t transId);
+ void deleteTransaction(uint64_t transId);
+
+ // Fill @arg preparedMap with Xid->TPCTransaction::shared_ptr for all
+ // currently prepared transactions.
+ void collectPreparedXids(std::map<std::string, boost::shared_ptr<TPCTransaction> >& preparedMap);
+
+ // Recover the transactions and their state from the log.
+ // Every non-deleted transaction recovered from the log will be
+ // represented in @arg transMap. The recovering messages can use this
+ // information to tell if a transaction referred to in an enqueue/dequeue
+ // operation should be recovered or dropped by examining transaction state.
+ //
+ // @param recoverer Recovery manager used to recreate broker objects from
+ // entries recovered from the log.
+ // @param transMap This method fills in the map of id -> shared_ptr of
+ // recovered transactions.
+ void recover(std::map<uint64_t, boost::shared_ptr<Transaction> >& transMap);
+};
+
+}}} // namespace qpid::store::ms_clfs
+
+#endif /* QPID_STORE_MSCLFS_TRANSACTIONLOG_H */
diff --git a/qpid/cpp/src/qpid/store/ms-sql/AmqpTransaction.cpp b/qpid/cpp/src/qpid/store/ms-sql/AmqpTransaction.cpp
new file mode 100644
index 0000000000..095d1bf331
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/AmqpTransaction.cpp
@@ -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.
+ *
+ */
+
+#include "AmqpTransaction.h"
+#include "DatabaseConnection.h"
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+AmqpTransaction::AmqpTransaction(const boost::shared_ptr<DatabaseConnection>& _db)
+ : db(_db), sqlTrans(_db)
+{
+}
+
+AmqpTransaction::~AmqpTransaction()
+{
+}
+
+void
+AmqpTransaction::sqlBegin()
+{
+ sqlTrans.begin();
+}
+
+void
+AmqpTransaction::sqlCommit()
+{
+ sqlTrans.commit();
+}
+
+void
+AmqpTransaction::sqlAbort()
+{
+ sqlTrans.abort();
+}
+
+
+AmqpTPCTransaction::AmqpTPCTransaction(const boost::shared_ptr<DatabaseConnection>& db,
+ const std::string& _xid)
+ : AmqpTransaction(db), prepared(false), xid(_xid)
+{
+}
+
+AmqpTPCTransaction::~AmqpTPCTransaction()
+{
+}
+
+}}} // namespace qpid::store::ms_sql
diff --git a/qpid/cpp/src/qpid/store/ms-sql/AmqpTransaction.h b/qpid/cpp/src/qpid/store/ms-sql/AmqpTransaction.h
new file mode 100644
index 0000000000..625fab5595
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/AmqpTransaction.h
@@ -0,0 +1,85 @@
+#ifndef QPID_STORE_MSSQL_AMQPTRANSACTION_H
+#define QPID_STORE_MSSQL_AMQPTRANSACTION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <qpid/broker/TransactionalStore.h>
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+#include "SqlTransaction.h"
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+class DatabaseConnection;
+
+/**
+ * @class AmqpTransaction
+ *
+ * Class representing an AMQP transaction. This is used around a set of
+ * enqueue and dequeue operations that occur when the broker is acting
+ * on a transaction commit/abort from the client.
+ */
+class AmqpTransaction : public qpid::broker::TransactionContext {
+
+ boost::shared_ptr<DatabaseConnection> db;
+ SqlTransaction sqlTrans;
+
+public:
+ AmqpTransaction(const boost::shared_ptr<DatabaseConnection>& _db);
+ virtual ~AmqpTransaction();
+
+ DatabaseConnection *dbConn() { return db.get(); }
+
+ void sqlBegin();
+ void sqlCommit();
+ void sqlAbort();
+};
+
+/**
+ * @class AmqpTPCTransaction
+ *
+ * Class representing a Two-Phase-Commit (TPC) AMQP transaction. This is
+ * used around a set of enqueue and dequeue operations that occur when the
+ * broker is acting on a transaction prepare/commit/abort from the client.
+ */
+class AmqpTPCTransaction : public AmqpTransaction,
+ public qpid::broker::TPCTransactionContext {
+ bool prepared;
+ std::string xid;
+
+public:
+ AmqpTPCTransaction(const boost::shared_ptr<DatabaseConnection>& db,
+ const std::string& _xid);
+ virtual ~AmqpTPCTransaction();
+
+ void setPrepared(void) { prepared = true; }
+ bool isPrepared(void) const { return prepared; }
+
+ const std::string& getXid(void) const { return xid; }
+};
+
+}}} // namespace qpid::store::ms_sql
+
+#endif /* QPID_STORE_MSSQL_AMQPTRANSACTION_H */
diff --git a/qpid/cpp/src/qpid/store/ms-sql/BindingRecordset.cpp b/qpid/cpp/src/qpid/store/ms-sql/BindingRecordset.cpp
new file mode 100644
index 0000000000..1dc4370312
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/BindingRecordset.cpp
@@ -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.
+ *
+ */
+
+#include <qpid/Exception.h>
+#include <qpid/log/Statement.h>
+
+#include "BindingRecordset.h"
+#include "BlobAdapter.h"
+#include "BlobEncoder.h"
+#include "VariantHelper.h"
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+void
+BindingRecordset::removeFilter(const std::string& filter)
+{
+ rs->PutFilter (VariantHelper<std::string>(filter));
+ long recs = rs->GetRecordCount();
+ if (recs == 0)
+ return; // Nothing to do
+ while (recs > 0) {
+ // Deleting adAffectAll doesn't work as documented; go one by one.
+ rs->Delete(adAffectCurrent);
+ if (--recs > 0)
+ rs->MoveNext();
+ }
+ rs->Update();
+}
+
+void
+BindingRecordset::add(uint64_t exchangeId,
+ uint64_t queueId,
+ const std::string& routingKey,
+ const qpid::framing::FieldTable& args)
+{
+ VariantHelper<std::string> routingKeyStr(routingKey);
+ BlobEncoder blob (args); // Marshall field table to a blob
+ rs->AddNew();
+ rs->Fields->GetItem("exchangeId")->Value = exchangeId;
+ rs->Fields->GetItem("queueId")->Value = queueId;
+ rs->Fields->GetItem("routingKey")->Value = routingKeyStr;
+ rs->Fields->GetItem("fieldTableBlob")->AppendChunk(blob);
+ rs->Update();
+}
+
+void
+BindingRecordset::remove(uint64_t exchangeId,
+ uint64_t queueId,
+ const std::string& routingKey,
+ const qpid::framing::FieldTable& /*args*/)
+{
+ // Look up the affected binding.
+ std::ostringstream filter;
+ filter << "exchangeId = " << exchangeId
+ << " AND queueId = " << queueId
+ << " AND routingKey = '" << routingKey << "'" << std::ends;
+ removeFilter(filter.str());
+}
+
+void
+BindingRecordset::removeForExchange(uint64_t exchangeId)
+{
+ // Look up the affected bindings by the exchange ID
+ std::ostringstream filter;
+ filter << "exchangeId = " << exchangeId << std::ends;
+ removeFilter(filter.str());
+}
+
+void
+BindingRecordset::removeForQueue(uint64_t queueId)
+{
+ // Look up the affected bindings by the queue ID
+ std::ostringstream filter;
+ filter << "queueId = " << queueId << std::ends;
+ removeFilter(filter.str());
+}
+
+void
+BindingRecordset::recover(broker::RecoveryManager& recoverer,
+ const store::ExchangeMap& exchMap,
+ const store::QueueMap& queueMap)
+{
+ if (rs->BOF && rs->EndOfFile)
+ return; // Nothing to do
+ rs->MoveFirst();
+ Binding b;
+ IADORecordBinding *piAdoRecordBinding;
+ rs->QueryInterface(__uuidof(IADORecordBinding),
+ (LPVOID *)&piAdoRecordBinding);
+ piAdoRecordBinding->BindToRecordset(&b);
+ while (!rs->EndOfFile) {
+ long blobSize = rs->Fields->Item["fieldTableBlob"]->ActualSize;
+ BlobAdapter blob(blobSize);
+ blob = rs->Fields->Item["fieldTableBlob"]->GetChunk(blobSize);
+ store::ExchangeMap::const_iterator exch = exchMap.find(b.exchangeId);
+ if (exch == exchMap.end()) {
+ std::ostringstream msg;
+ msg << "Error recovering bindings; exchange ID " << b.exchangeId
+ << " not found in exchange map";
+ throw qpid::Exception(msg.str());
+ }
+ broker::RecoverableExchange::shared_ptr exchPtr = exch->second;
+ store::QueueMap::const_iterator q = queueMap.find(b.queueId);
+ if (q == queueMap.end()) {
+ std::ostringstream msg;
+ msg << "Error recovering bindings; queue ID " << b.queueId
+ << " not found in queue map";
+ throw qpid::Exception(msg.str());
+ }
+ broker::RecoverableQueue::shared_ptr qPtr = q->second;
+ // The recovery manager wants the queue name, so get it from the
+ // RecoverableQueue.
+ std::string key(b.routingKey);
+ exchPtr->bind(qPtr->getName(), key, blob);
+ rs->MoveNext();
+ }
+
+ piAdoRecordBinding->Release();
+}
+
+void
+BindingRecordset::dump()
+{
+ Recordset::dump();
+ if (rs->EndOfFile && rs->BOF) // No records
+ return;
+ rs->MoveFirst();
+
+ Binding b;
+ IADORecordBinding *piAdoRecordBinding;
+ rs->QueryInterface(__uuidof(IADORecordBinding),
+ (LPVOID *)&piAdoRecordBinding);
+ piAdoRecordBinding->BindToRecordset(&b);
+
+ while (VARIANT_FALSE == rs->EndOfFile) {
+ QPID_LOG(notice, "exch Id " << b.exchangeId
+ << ", q Id " << b.queueId
+ << ", k: " << b.routingKey);
+ rs->MoveNext();
+ }
+
+ piAdoRecordBinding->Release();
+}
+
+}}} // namespace qpid::store::ms_sql
diff --git a/qpid/cpp/src/qpid/store/ms-sql/BindingRecordset.h b/qpid/cpp/src/qpid/store/ms-sql/BindingRecordset.h
new file mode 100644
index 0000000000..3cb732de75
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/BindingRecordset.h
@@ -0,0 +1,88 @@
+#ifndef QPID_STORE_MSSQL_BINDINGRECORDSET_H
+#define QPID_STORE_MSSQL_BINDINGRECORDSET_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <icrsint.h>
+#include "Recordset.h"
+#include <qpid/store/StorageProvider.h>
+#include <qpid/broker/RecoveryManager.h>
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+/**
+ * @class BindingRecordset
+ *
+ * Class for the binding records.
+ */
+class BindingRecordset : public Recordset {
+
+ class Binding : public CADORecordBinding {
+ BEGIN_ADO_BINDING(Binding)
+ ADO_FIXED_LENGTH_ENTRY2(1, adBigInt, exchangeId, FALSE)
+ ADO_FIXED_LENGTH_ENTRY2(2, adBigInt, queueId, FALSE)
+ ADO_VARIABLE_LENGTH_ENTRY4(3, adVarChar, routingKey,
+ sizeof(routingKey), FALSE)
+ END_ADO_BINDING()
+
+ public:
+ uint64_t exchangeId;
+ uint64_t queueId;
+ char routingKey[256];
+ };
+
+ // Remove all records matching the specified filter/query.
+ void removeFilter(const std::string& filter);
+
+public:
+ // Add a new binding
+ void add(uint64_t exchangeId,
+ uint64_t queueId,
+ const std::string& routingKey,
+ const qpid::framing::FieldTable& args);
+
+ // Remove a specific binding
+ void remove(uint64_t exchangeId,
+ uint64_t queueId,
+ const std::string& routingKey,
+ const qpid::framing::FieldTable& args);
+
+ // Remove all bindings for the specified exchange
+ void removeForExchange(uint64_t exchangeId);
+
+ // Remove all bindings for the specified queue
+ void removeForQueue(uint64_t queueId);
+
+ // Recover bindings set using exchMap to get from Id to RecoverableExchange.
+ void recover(qpid::broker::RecoveryManager& recoverer,
+ const qpid::store::ExchangeMap& exchMap,
+ const qpid::store::QueueMap& queueMap);
+
+ // Dump table contents; useful for debugging.
+ void dump();
+};
+
+}}} // namespace qpid::store::ms_sql
+
+#endif /* QPID_STORE_MSSQL_BINDINGRECORDSET_H */
diff --git a/qpid/cpp/src/qpid/store/ms-sql/BlobAdapter.cpp b/qpid/cpp/src/qpid/store/ms-sql/BlobAdapter.cpp
new file mode 100644
index 0000000000..1889f34e41
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/BlobAdapter.cpp
@@ -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.
+ *
+ */
+
+#include "BlobAdapter.h"
+#include <qpid/Exception.h>
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+void
+BlobAdapter::extractBuff()
+{
+ // To give a valid Buffer back, lock the safearray, obtaining a pointer to
+ // the actual data. Record the pointer in the Buffer so the destructor
+ // knows to unlock the safearray.
+ if (buff.getPointer() == 0) {
+ char *blob;
+ SafeArrayAccessData(this->parray, (void **)&blob);
+ qpid::framing::Buffer lockedBuff(blob, buff.getSize());
+ buff = lockedBuff;
+ }
+}
+
+
+BlobAdapter::~BlobAdapter()
+{
+ // If buff's pointer is set, the safearray is locked, so unlock it
+ if (buff.getPointer() != 0)
+ SafeArrayUnaccessData(this->parray);
+}
+
+BlobAdapter::operator qpid::framing::Buffer& ()
+{
+ extractBuff();
+ return buff;
+}
+
+BlobAdapter::operator qpid::framing::FieldTable& ()
+{
+ extractBuff();
+ fields.decode(buff);
+ return fields;
+}
+
+}}} // namespace qpid::store::ms_sql
diff --git a/qpid/cpp/src/qpid/store/ms-sql/BlobAdapter.h b/qpid/cpp/src/qpid/store/ms-sql/BlobAdapter.h
new file mode 100644
index 0000000000..1c666392bc
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/BlobAdapter.h
@@ -0,0 +1,62 @@
+#ifndef QPID_STORE_MSSQL_BLOBADAPTER_H
+#define QPID_STORE_MSSQL_BLOBADAPTER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <comutil.h>
+#include <qpid/framing/Buffer.h>
+#include <qpid/framing/FieldTable.h>
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+/**
+ * @class BlobAdapter
+ *
+ * Adapter for accessing a blob (varbinary SQL field) as a qpid::framing::Buffer
+ * in an exception-safe way.
+ */
+class BlobAdapter : public _variant_t {
+private:
+ // This Buffer's pointer indicates whether or not a safearray has
+ // been locked; if it's 0, no locking was done.
+ qpid::framing::Buffer buff;
+ qpid::framing::FieldTable fields;
+
+ void extractBuff();
+
+public:
+ // Initialize with the known length of the data that will come.
+ // Assigning a _variant_t to this object will set up the array to be
+ // accessed with the operator Buffer&()
+ BlobAdapter(long blobSize) : _variant_t(), buff(0, blobSize) {}
+ ~BlobAdapter();
+ BlobAdapter& operator=(_variant_t& var_t_Src)
+ { _variant_t::operator=(var_t_Src); return *this; }
+ operator qpid::framing::Buffer& ();
+ operator qpid::framing::FieldTable& ();
+};
+
+}}} // namespace qpid::store::ms_sql
+
+#endif /* QPID_STORE_MSSQL_BLOBADAPTER_H */
diff --git a/qpid/cpp/src/qpid/store/ms-sql/BlobEncoder.cpp b/qpid/cpp/src/qpid/store/ms-sql/BlobEncoder.cpp
new file mode 100644
index 0000000000..75d3dc2d86
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/BlobEncoder.cpp
@@ -0,0 +1,133 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "BlobEncoder.h"
+#include <qpid/Exception.h>
+#include <qpid/broker/Persistable.h>
+#include <qpid/broker/PersistableMessage.h>
+#include <boost/intrusive_ptr.hpp>
+#include <memory.h>
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+template <class ITEM> void
+BlobEncoder::encode(const ITEM &item)
+{
+ SAFEARRAYBOUND bound[1] = {0, 0};
+ bound[0].cElements = item.encodedSize();
+ blob = SafeArrayCreate(VT_UI1, 1, bound);
+ if (S_OK != SafeArrayLock(blob)) {
+ SafeArrayDestroy(blob);
+ blob = 0;
+ throw qpid::Exception("Error locking blob area for persistable item");
+ }
+ try {
+ qpid::framing::Buffer buff((char *)blob->pvData, bound[0].cElements);
+ item.encode(buff);
+ }
+ catch(...) {
+ SafeArrayUnlock(blob);
+ SafeArrayDestroy(blob);
+ blob = 0;
+ throw;
+ }
+ this->vt = VT_ARRAY | VT_UI1;
+ this->parray = blob;
+ SafeArrayUnlock(blob);
+}
+
+template <> void
+BlobEncoder::encode(const boost::intrusive_ptr<qpid::broker::PersistableMessage> &item)
+{
+ // NOTE! If this code changes, verify the recovery code in MessageRecordset
+ SAFEARRAYBOUND bound[1] = {0, 0};
+ bound[0].cElements = item->encodedSize() + sizeof(uint32_t);
+ blob = SafeArrayCreate(VT_UI1, 1, bound);
+ if (S_OK != SafeArrayLock(blob)) {
+ SafeArrayDestroy(blob);
+ blob = 0;
+ throw qpid::Exception("Error locking blob area for message");
+ }
+ try {
+ uint32_t headerSize = item->encodedHeaderSize();
+ qpid::framing::Buffer buff((char *)blob->pvData, bound[0].cElements);
+ buff.putLong(headerSize);
+ item->encode(buff);
+ }
+ catch(...) {
+ SafeArrayUnlock(blob);
+ SafeArrayDestroy(blob);
+ blob = 0;
+ throw;
+ }
+ this->vt = VT_ARRAY | VT_UI1;
+ this->parray = blob;
+ SafeArrayUnlock(blob);
+}
+
+template <> void
+BlobEncoder::encode(const std::string &item)
+{
+ SAFEARRAYBOUND bound[1] = {0, 0};
+ bound[0].cElements = item.size();
+ blob = SafeArrayCreate(VT_UI1, 1, bound);
+ if (S_OK != SafeArrayLock(blob)) {
+ SafeArrayDestroy(blob);
+ blob = 0;
+ throw qpid::Exception("Error locking blob area for string");
+ }
+ memcpy_s(blob->pvData, item.size(), item.data(), item.size());
+ this->vt = VT_ARRAY | VT_UI1;
+ this->parray = blob;
+ SafeArrayUnlock(blob);
+}
+
+BlobEncoder::BlobEncoder(const qpid::broker::Persistable &item) : blob(0)
+{
+ encode(item);
+}
+
+BlobEncoder::BlobEncoder(const boost::intrusive_ptr<qpid::broker::PersistableMessage> &msg) : blob(0)
+{
+ encode(msg);
+}
+
+BlobEncoder::BlobEncoder(const qpid::framing::FieldTable &fields) : blob(0)
+{
+ encode(fields);
+}
+
+BlobEncoder::BlobEncoder(const std::string &data) : blob(0)
+{
+ encode(data);
+}
+
+BlobEncoder::~BlobEncoder()
+{
+ if (blob)
+ SafeArrayDestroy(blob);
+ blob = 0;
+ this->parray = 0;
+}
+
+}}} // namespace qpid::store::ms_sql
diff --git a/qpid/cpp/src/qpid/store/ms-sql/BlobEncoder.h b/qpid/cpp/src/qpid/store/ms-sql/BlobEncoder.h
new file mode 100644
index 0000000000..d2b56223c1
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/BlobEncoder.h
@@ -0,0 +1,61 @@
+#ifndef QPID_STORE_MSSQL_BLOBENCODER_H
+#define QPID_STORE_MSSQL_BLOBENCODER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <comutil.h>
+#include <string>
+#include <boost/intrusive_ptr.hpp>
+#include <qpid/broker/Persistable.h>
+#include <qpid/broker/PersistableMessage.h>
+#include <qpid/framing/Buffer.h>
+#include <qpid/framing/FieldTable.h>
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+/**
+ * @class BlobEncoder
+ *
+ * Encodes a blob (varbinary) field from a qpid::broker::Persistable or a
+ * qpid::framing::FieldTable (both of which can be encoded to
+ * qpid::framing::Buffer) so it can be passed to ADO methods for writing
+ * to the database.
+ */
+class BlobEncoder : public _variant_t {
+private:
+ SAFEARRAY *blob;
+
+ template <class ITEM> void encode(const ITEM &item);
+
+public:
+ BlobEncoder(const qpid::broker::Persistable &item);
+ BlobEncoder(const boost::intrusive_ptr<qpid::broker::PersistableMessage> &msg);
+ BlobEncoder(const qpid::framing::FieldTable &fields);
+ BlobEncoder(const std::string& data);
+ ~BlobEncoder();
+};
+
+}}} // namespace qpid::store::ms_sql
+
+#endif /* QPID_STORE_MSSQL_BLOBENCODER_H */
diff --git a/qpid/cpp/src/qpid/store/ms-sql/BlobRecordset.cpp b/qpid/cpp/src/qpid/store/ms-sql/BlobRecordset.cpp
new file mode 100644
index 0000000000..ef1757dbad
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/BlobRecordset.cpp
@@ -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.
+ *
+ */
+
+#include <qpid/Exception.h>
+#include <qpid/log/Statement.h>
+
+#include "BlobRecordset.h"
+#include "BlobEncoder.h"
+#include "VariantHelper.h"
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+void
+BlobRecordset::add(const qpid::broker::Persistable& item)
+{
+ BlobEncoder blob (item); // Marshall item info to a blob
+ rs->AddNew();
+ rs->Fields->GetItem("fieldTableBlob")->AppendChunk(blob);
+ rs->Update();
+ uint64_t id = rs->Fields->Item["persistenceId"]->Value;
+ item.setPersistenceId(id);
+}
+
+void
+BlobRecordset::remove(uint64_t id)
+{
+ // Look up the item by its persistenceId
+ std::ostringstream filter;
+ filter << "persistenceId = " << id << std::ends;
+ rs->PutFilter (VariantHelper<std::string>(filter.str()));
+ if (!rs->EndOfFile) {
+ // Delete the record
+ rs->Delete(adAffectCurrent);
+ rs->Update();
+ }
+}
+
+void
+BlobRecordset::remove(const qpid::broker::Persistable& item)
+{
+ remove(item.getPersistenceId());
+}
+
+void
+BlobRecordset::dump()
+{
+ Recordset::dump();
+#if 1
+ if (rs->EndOfFile && rs->BOF) // No records
+ return;
+
+ rs->MoveFirst();
+ while (!rs->EndOfFile) {
+ uint64_t id = rs->Fields->Item["persistenceId"]->Value;
+ QPID_LOG(notice, " -> " << id);
+ rs->MoveNext();
+ }
+#else
+ for (Iterator iter = begin(); iter != end(); ++iter) {
+ uint64_t id = *iter.first;
+ QPID_LOG(notice, " -> " << id);
+ }
+#endif
+}
+
+}}} // namespace qpid::store::ms_sql
diff --git a/qpid/cpp/src/qpid/store/ms-sql/BlobRecordset.h b/qpid/cpp/src/qpid/store/ms-sql/BlobRecordset.h
new file mode 100644
index 0000000000..4d1c338746
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/BlobRecordset.h
@@ -0,0 +1,54 @@
+#ifndef QPID_STORE_MSSQL_BLOBRECORDSET_H
+#define QPID_STORE_MSSQL_BLOBRECORDSET_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "Recordset.h"
+#include <qpid/broker/Persistable.h>
+#include <string>
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+/**
+ * @class BlobRecordset
+ *
+ * Class for the "blob" records that record an id, varbinary(max) pair.
+ */
+class BlobRecordset : public Recordset {
+protected:
+
+public:
+ void add(const qpid::broker::Persistable& item);
+
+ // Remove a record given its Id.
+ void remove(uint64_t id);
+ void remove(const qpid::broker::Persistable& item);
+
+ // Dump table contents; useful for debugging.
+ void dump();
+};
+
+}}} // namespace qpid::store::ms_sql
+
+#endif /* QPID_STORE_MSSQL_BLOBRECORDSET_H */
diff --git a/qpid/cpp/src/qpid/store/ms-sql/DatabaseConnection.cpp b/qpid/cpp/src/qpid/store/ms-sql/DatabaseConnection.cpp
new file mode 100644
index 0000000000..3219ea526a
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/DatabaseConnection.cpp
@@ -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.
+ *
+ */
+
+#include "DatabaseConnection.h"
+#include "Exception.h"
+#include <comdef.h>
+namespace {
+inline void TESTHR(HRESULT x) {if FAILED(x) _com_issue_error(x);};
+}
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+DatabaseConnection::DatabaseConnection() : conn(0)
+{
+}
+
+DatabaseConnection::~DatabaseConnection()
+{
+ close();
+}
+
+void
+DatabaseConnection::open(const std::string& connectString,
+ const std::string& dbName)
+{
+ if (conn && conn->State == adStateOpen)
+ return;
+ std::string adoConnect = "Provider=SQLOLEDB;" + connectString;
+ try {
+ TESTHR(conn.CreateInstance(__uuidof(Connection)));
+ conn->ConnectionString = adoConnect.c_str();
+ conn->Open("", "", "", adConnectUnspecified);
+ if (dbName.length() > 0)
+ conn->DefaultDatabase = dbName.c_str();
+ }
+ catch(_com_error &e) {
+ close();
+ throw ADOException("MSSQL can't open " + dbName + " at " + adoConnect, e);
+ }
+}
+
+void
+DatabaseConnection::close()
+{
+ if (conn && conn->State == adStateOpen)
+ conn->Close();
+ conn = 0;
+}
+
+std::string
+DatabaseConnection::getErrors()
+{
+ long errCount = conn->Errors->Count;
+ if (errCount <= 0)
+ return "";
+ // Collection ranges from 0 to nCount -1.
+ std::ostringstream messages;
+ ErrorPtr pErr = NULL;
+ for (long i = 0 ; i < errCount ; i++ ) {
+ if (i > 0)
+ messages << "\n";
+ messages << "[" << i << "] ";
+ pErr = conn->Errors->GetItem(i);
+ messages << "Error " << pErr->Number << ": "
+ << (LPCSTR)pErr->Description;
+ }
+ messages << std::ends;
+ return messages.str();
+}
+
+}}} // namespace qpid::store::ms_sql
diff --git a/qpid/cpp/src/qpid/store/ms-sql/DatabaseConnection.h b/qpid/cpp/src/qpid/store/ms-sql/DatabaseConnection.h
new file mode 100644
index 0000000000..785d1587c5
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/DatabaseConnection.h
@@ -0,0 +1,64 @@
+#ifndef QPID_STORE_MSSQL_DATABASECONNECTION_H
+#define QPID_STORE_MSSQL_DATABASECONNECTION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+// Bring in ADO 2.8 (yes, I know it says "15", but that's it...)
+#import "C:\Program Files\Common Files\System\ado\msado15.dll" \
+ no_namespace rename("EOF", "EndOfFile")
+
+#include <string>
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+/**
+ * @class DatabaseConnection
+ *
+ * Represents a connection to the SQL database. This class wraps the
+ * needed _ConnectionPtr for ADO as well as the needed COM initialization
+ * and cleanup that each thread requires. It is expected that this class
+ * will be maintained in thread-specific storage so it has no locks.
+ */
+class DatabaseConnection {
+protected:
+ _ConnectionPtr conn;
+
+public:
+ DatabaseConnection();
+ ~DatabaseConnection();
+ void open(const std::string& connectString,
+ const std::string& dbName = "");
+ void close();
+ operator _ConnectionPtr () { return conn; }
+
+ void beginTransaction() { conn->BeginTrans(); }
+ void commitTransaction() {conn->CommitTrans(); }
+ void rollbackTransaction() { conn->RollbackTrans(); }
+
+ std::string getErrors();
+};
+
+}}} // namespace qpid::store::ms_sql
+
+#endif /* QPID_STORE_MSSQL_DATABASECONNECTION_H */
diff --git a/qpid/cpp/src/qpid/store/ms-sql/Exception.h b/qpid/cpp/src/qpid/store/ms-sql/Exception.h
new file mode 100644
index 0000000000..65ec3388ff
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/Exception.h
@@ -0,0 +1,66 @@
+#ifndef QPID_STORE_MSSQL_EXCEPTION_H
+#define QPID_STORE_MSSQL_EXCEPTION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <string>
+#include <comdef.h>
+#include <qpid/store/StorageProvider.h>
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+class Exception : public qpid::store::StorageProvider::Exception
+{
+protected:
+ std::string text;
+public:
+ Exception(const std::string& _text) : text(_text) {}
+ virtual ~Exception() {}
+ virtual const char* what() const throw() { return text.c_str(); }
+};
+
+class ADOException : public Exception
+{
+public:
+ ADOException(const std::string& _text,
+ _com_error &e,
+ const std::string& providerErrors = "")
+ : Exception(_text) {
+ text += ": ";
+ text += e.ErrorMessage();
+ IErrorInfo *i = e.ErrorInfo();
+ if (i != 0) {
+ text += ": ";
+ _bstr_t wmsg = e.Description();
+ text += (const char *)wmsg;
+ i->Release();
+ }
+ if (providerErrors.length() > 0)
+ text += providerErrors;
+ }
+};
+
+}}} // namespace qpid::store::ms_sql
+
+#endif /* QPID_STORE_MSSQL_EXCEPTION_H */
diff --git a/qpid/cpp/src/qpid/store/ms-sql/MSSqlProvider.cpp b/qpid/cpp/src/qpid/store/ms-sql/MSSqlProvider.cpp
new file mode 100644
index 0000000000..1432cc8fca
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/MSSqlProvider.cpp
@@ -0,0 +1,1286 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <stdlib.h>
+#include <string>
+#include <windows.h>
+#include <qpid/broker/RecoverableQueue.h>
+#include <qpid/log/Statement.h>
+#include <qpid/store/MessageStorePlugin.h>
+#include <qpid/store/StorageProvider.h>
+#include "AmqpTransaction.h"
+#include "BlobAdapter.h"
+#include "BlobRecordset.h"
+#include "BindingRecordset.h"
+#include "MessageMapRecordset.h"
+#include "MessageRecordset.h"
+#include "TplRecordset.h"
+#include "DatabaseConnection.h"
+#include "Exception.h"
+#include "State.h"
+#include "VariantHelper.h"
+
+// Bring in ADO 2.8 (yes, I know it says "15", but that's it...)
+#import "C:\Program Files\Common Files\System\ado\msado15.dll" \
+ no_namespace rename("EOF", "EndOfFile")
+#include <comdef.h>
+namespace {
+inline void TESTHR(HRESULT x) {if FAILED(x) _com_issue_error(x);};
+
+// Table names
+const std::string TblBinding("tblBinding");
+const std::string TblConfig("tblConfig");
+const std::string TblExchange("tblExchange");
+const std::string TblMessage("tblMessage");
+const std::string TblMessageMap("tblMessageMap");
+const std::string TblQueue("tblQueue");
+const std::string TblTpl("tblTPL");
+}
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+/**
+ * @class MSSqlProvider
+ *
+ * Implements a qpid::store::StorageProvider that uses Microsoft SQL Server as
+ * the backend data store for Qpid.
+ */
+class MSSqlProvider : public qpid::store::StorageProvider
+{
+protected:
+ void finalizeMe();
+
+ void dump();
+
+public:
+ MSSqlProvider();
+ ~MSSqlProvider();
+
+ virtual qpid::Options* getOptions() { return &options; }
+
+ virtual void earlyInitialize (Plugin::Target& target);
+ virtual void initialize(Plugin::Target& target);
+
+ /**
+ * Receive notification that this provider is the one that will actively
+ * handle provider storage for the target. If the provider is to be used,
+ * this method will be called after earlyInitialize() and before any
+ * recovery operations (recovery, in turn, precedes call to initialize()).
+ */
+ virtual void activate(MessageStorePlugin &store);
+
+ /**
+ * @name Methods inherited from qpid::broker::MessageStore
+ */
+
+ /**
+ * Record the existence of a durable queue
+ */
+ virtual void create(PersistableQueue& queue,
+ const qpid::framing::FieldTable& args);
+ /**
+ * Destroy a durable queue
+ */
+ virtual void destroy(PersistableQueue& queue);
+
+ /**
+ * Record the existence of a durable exchange
+ */
+ virtual void create(const PersistableExchange& exchange,
+ const qpid::framing::FieldTable& args);
+ /**
+ * Destroy a durable exchange
+ */
+ virtual void destroy(const PersistableExchange& exchange);
+
+ /**
+ * Record a binding
+ */
+ virtual void bind(const PersistableExchange& exchange,
+ const PersistableQueue& queue,
+ const std::string& key,
+ const qpid::framing::FieldTable& args);
+
+ /**
+ * Forget a binding
+ */
+ virtual void unbind(const PersistableExchange& exchange,
+ const PersistableQueue& queue,
+ const std::string& key,
+ const qpid::framing::FieldTable& args);
+
+ /**
+ * Record generic durable configuration
+ */
+ virtual void create(const PersistableConfig& config);
+
+ /**
+ * Destroy generic durable configuration
+ */
+ virtual void destroy(const PersistableConfig& config);
+
+ /**
+ * Stores a messages before it has been enqueued
+ * (enqueueing automatically stores the message so this is
+ * only required if storage is required prior to that
+ * point). If the message has not yet been stored it will
+ * store the headers as well as any content passed in. A
+ * persistence id will be set on the message which can be
+ * used to load the content or to append to it.
+ */
+ virtual void stage(const boost::intrusive_ptr<PersistableMessage>& msg);
+
+ /**
+ * Destroys a previously staged message. This only needs
+ * to be called if the message is never enqueued. (Once
+ * enqueued, deletion will be automatic when the message
+ * is dequeued from all queues it was enqueued onto).
+ */
+ virtual void destroy(PersistableMessage& msg);
+
+ /**
+ * Appends content to a previously staged message
+ */
+ virtual void appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg,
+ const std::string& data);
+
+ /**
+ * Loads (a section) of content data for the specified
+ * message (previously stored through a call to stage or
+ * enqueue) into data. The offset refers to the content
+ * only (i.e. an offset of 0 implies that the start of the
+ * content should be loaded, not the headers or related
+ * meta-data).
+ */
+ virtual void loadContent(const qpid::broker::PersistableQueue& queue,
+ const boost::intrusive_ptr<const PersistableMessage>& msg,
+ std::string& data,
+ uint64_t offset,
+ uint32_t length);
+
+ /**
+ * Enqueues a message, storing the message if it has not
+ * been previously stored and recording that the given
+ * message is on the given queue.
+ *
+ * Note: that this is async so the return of the function does
+ * not mean the opperation is complete.
+ *
+ * @param msg the message to enqueue
+ * @param queue the name of the queue onto which it is to be enqueued
+ * @param xid (a pointer to) an identifier of the
+ * distributed transaction in which the operation takes
+ * place or null for 'local' transactions
+ */
+ virtual void enqueue(qpid::broker::TransactionContext* ctxt,
+ const boost::intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue& queue);
+
+ /**
+ * Dequeues a message, recording that the given message is
+ * no longer on the given queue and deleting the message
+ * if it is no longer on any other queue.
+ *
+ * Note: that this is async so the return of the function does
+ * not mean the opperation is complete.
+ *
+ * @param msg the message to dequeue
+ * @param queue the name of the queue from which it is to be dequeued
+ * @param xid (a pointer to) an identifier of the
+ * distributed transaction in which the operation takes
+ * place or null for 'local' transactions
+ */
+ virtual void dequeue(qpid::broker::TransactionContext* ctxt,
+ const boost::intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue& queue);
+
+ /**
+ * Flushes all async messages to disk for the specified queue
+ *
+ * Note: this is a no-op for this provider.
+ *
+ * @param queue the name of the queue from which it is to be dequeued
+ */
+ virtual void flush(const PersistableQueue& queue) {};
+
+ /**
+ * Returns the number of outstanding AIO's for a given queue
+ *
+ * If 0, than all the enqueue / dequeues have been stored
+ * to disk
+ *
+ * @param queue the name of the queue to check for outstanding AIO
+ */
+ virtual uint32_t outstandingQueueAIO(const PersistableQueue& queue)
+ {return 0;}
+ //@}
+
+ /**
+ * @name Methods inherited from qpid::broker::TransactionalStore
+ */
+ //@{
+ virtual std::auto_ptr<qpid::broker::TransactionContext> begin();
+ virtual std::auto_ptr<qpid::broker::TPCTransactionContext> begin(const std::string& xid);
+ virtual void prepare(qpid::broker::TPCTransactionContext& txn);
+ virtual void commit(qpid::broker::TransactionContext& txn);
+ virtual void abort(qpid::broker::TransactionContext& txn);
+ virtual void collectPreparedXids(std::set<std::string>& xids);
+ //@}
+
+ virtual void recoverConfigs(qpid::broker::RecoveryManager& recoverer);
+ virtual void recoverExchanges(qpid::broker::RecoveryManager& recoverer,
+ ExchangeMap& exchangeMap);
+ virtual void recoverQueues(qpid::broker::RecoveryManager& recoverer,
+ QueueMap& queueMap);
+ virtual void recoverBindings(qpid::broker::RecoveryManager& recoverer,
+ const ExchangeMap& exchangeMap,
+ const QueueMap& queueMap);
+ virtual void recoverMessages(qpid::broker::RecoveryManager& recoverer,
+ MessageMap& messageMap,
+ MessageQueueMap& messageQueueMap);
+ virtual void recoverTransactions(qpid::broker::RecoveryManager& recoverer,
+ PreparedTransactionMap& dtxMap);
+
+private:
+ struct ProviderOptions : public qpid::Options
+ {
+ std::string connectString;
+ std::string catalogName;
+
+ ProviderOptions(const std::string &name)
+ : qpid::Options(name),
+ catalogName("QpidStore")
+ {
+ const enum { NAMELEN = MAX_COMPUTERNAME_LENGTH + 1 };
+ TCHAR myName[NAMELEN];
+ DWORD myNameLen = NAMELEN;
+ GetComputerName(myName, &myNameLen);
+ connectString = "Data Source=";
+ connectString += myName;
+ connectString += "\\SQLEXPRESS;Integrated Security=SSPI";
+ addOptions()
+ ("connect",
+ qpid::optValue(connectString, "STRING"),
+ "Connection string for the database to use. Will prepend "
+ "Provider=SQLOLEDB;")
+ ("catalog",
+ qpid::optValue(catalogName, "DB NAME"),
+ "Catalog (database) name")
+ ;
+ }
+ };
+ ProviderOptions options;
+
+ // Each thread has a separate connection to the database and also needs
+ // to manage its COM initialize/finalize individually. This is done by
+ // keeping a thread-specific State.
+ boost::thread_specific_ptr<State> dbState;
+
+ State *initState();
+ DatabaseConnection *initConnection(void);
+ void createDb(DatabaseConnection *db, const std::string &name);
+};
+
+static MSSqlProvider static_instance_registers_plugin;
+
+void
+MSSqlProvider::finalizeMe()
+{
+ dbState.reset();
+}
+
+MSSqlProvider::MSSqlProvider()
+ : options("MS SQL Provider options")
+{
+}
+
+MSSqlProvider::~MSSqlProvider()
+{
+}
+
+void
+MSSqlProvider::earlyInitialize(Plugin::Target &target)
+{
+ MessageStorePlugin *store = dynamic_cast<MessageStorePlugin *>(&target);
+ if (store) {
+ // If the database init fails, report it and don't register; give
+ // the rest of the broker a chance to run.
+ //
+ // Don't try to initConnection() since that will fail if the
+ // database doesn't exist. Instead, try to open a connection without
+ // a database name, then search for the database. There's still a
+ // chance this provider won't be selected for the store too, so be
+ // be sure to close the database connection before return to avoid
+ // leaving a connection up that will not be used.
+ try {
+ initState(); // This initializes COM
+ std::auto_ptr<DatabaseConnection> db(new DatabaseConnection());
+ db->open(options.connectString, "");
+ _ConnectionPtr conn(*db);
+ _RecordsetPtr pCatalogs = NULL;
+ VariantHelper<std::string> catalogName(options.catalogName);
+ pCatalogs = conn->OpenSchema(adSchemaCatalogs, catalogName);
+ if (pCatalogs->EndOfFile) {
+ // Database doesn't exist; create it
+ QPID_LOG(notice,
+ "MSSQL: Creating database " + options.catalogName);
+ createDb(db.get(), options.catalogName);
+ }
+ else {
+ QPID_LOG(notice,
+ "MSSQL: Database located: " + options.catalogName);
+ }
+ if (pCatalogs) {
+ if (pCatalogs->State == adStateOpen)
+ pCatalogs->Close();
+ pCatalogs = 0;
+ }
+ db->close();
+ store->providerAvailable("MSSQL", this);
+ }
+ catch (qpid::Exception &e) {
+ QPID_LOG(error, e.what());
+ return;
+ }
+ store->addFinalizer(boost::bind(&MSSqlProvider::finalizeMe, this));
+ }
+}
+
+void
+MSSqlProvider::initialize(Plugin::Target& target)
+{
+}
+
+void
+MSSqlProvider::activate(MessageStorePlugin &store)
+{
+ QPID_LOG(info, "MS SQL Provider is up");
+}
+
+void
+MSSqlProvider::create(PersistableQueue& queue,
+ const qpid::framing::FieldTable& /*args needed for jrnl*/)
+{
+ DatabaseConnection *db = initConnection();
+ BlobRecordset rsQueues;
+ try {
+ db->beginTransaction();
+ rsQueues.open(db, TblQueue);
+ rsQueues.add(queue);
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error creating queue " + queue.getName(), e, errs);
+ }
+}
+
+/**
+ * Destroy a durable queue
+ */
+void
+MSSqlProvider::destroy(PersistableQueue& queue)
+{
+ DatabaseConnection *db = initConnection();
+ BlobRecordset rsQueues;
+ BindingRecordset rsBindings;
+ MessageRecordset rsMessages;
+ MessageMapRecordset rsMessageMaps;
+ try {
+ db->beginTransaction();
+ rsQueues.open(db, TblQueue);
+ rsBindings.open(db, TblBinding);
+ rsMessages.open(db, TblMessage);
+ rsMessageMaps.open(db, TblMessageMap);
+ // Remove bindings first; the queue IDs can't be ripped out from
+ // under the references in the bindings table. Then remove the
+ // message->queue entries for the queue, also because the queue can't
+ // be deleted while there are references to it. If there are messages
+ // orphaned by removing the queue references, they're deleted by
+ // a trigger on the tblMessageMap table. Lastly, the queue record
+ // can be removed.
+ rsBindings.removeForQueue(queue.getPersistenceId());
+ rsMessageMaps.removeForQueue(queue.getPersistenceId());
+ rsQueues.remove(queue);
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error deleting queue " + queue.getName(), e, errs);
+ }
+}
+
+/**
+ * Record the existence of a durable exchange
+ */
+void
+MSSqlProvider::create(const PersistableExchange& exchange,
+ const qpid::framing::FieldTable& args)
+{
+ DatabaseConnection *db = initConnection();
+ BlobRecordset rsExchanges;
+ try {
+ db->beginTransaction();
+ rsExchanges.open(db, TblExchange);
+ rsExchanges.add(exchange);
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error creating exchange " + exchange.getName(),
+ e,
+ errs);
+ }
+}
+
+/**
+ * Destroy a durable exchange
+ */
+void
+MSSqlProvider::destroy(const PersistableExchange& exchange)
+{
+ DatabaseConnection *db = initConnection();
+ BlobRecordset rsExchanges;
+ BindingRecordset rsBindings;
+ try {
+ db->beginTransaction();
+ rsExchanges.open(db, TblExchange);
+ rsBindings.open(db, TblBinding);
+ // Remove bindings first; the exchange IDs can't be ripped out from
+ // under the references in the bindings table.
+ rsBindings.removeForExchange(exchange.getPersistenceId());
+ rsExchanges.remove(exchange);
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error deleting exchange " + exchange.getName(),
+ e,
+ errs);
+ }
+}
+
+/**
+ * Record a binding
+ */
+void
+MSSqlProvider::bind(const PersistableExchange& exchange,
+ const PersistableQueue& queue,
+ const std::string& key,
+ const qpid::framing::FieldTable& args)
+{
+ DatabaseConnection *db = initConnection();
+ BindingRecordset rsBindings;
+ try {
+ db->beginTransaction();
+ rsBindings.open(db, TblBinding);
+ rsBindings.add(exchange.getPersistenceId(),
+ queue.getPersistenceId(),
+ key,
+ args);
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error binding exchange " + exchange.getName() +
+ " to queue " + queue.getName(),
+ e,
+ errs);
+ }
+}
+
+/**
+ * Forget a binding
+ */
+void
+MSSqlProvider::unbind(const PersistableExchange& exchange,
+ const PersistableQueue& queue,
+ const std::string& key,
+ const qpid::framing::FieldTable& args)
+{
+ DatabaseConnection *db = initConnection();
+ BindingRecordset rsBindings;
+ try {
+ db->beginTransaction();
+ rsBindings.open(db, TblBinding);
+ rsBindings.remove(exchange.getPersistenceId(),
+ queue.getPersistenceId(),
+ key,
+ args);
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error unbinding exchange " + exchange.getName() +
+ " from queue " + queue.getName(),
+ e,
+ errs);
+ }
+}
+
+/**
+ * Record generic durable configuration
+ */
+void
+MSSqlProvider::create(const PersistableConfig& config)
+{
+ DatabaseConnection *db = initConnection();
+ BlobRecordset rsConfigs;
+ try {
+ db->beginTransaction();
+ rsConfigs.open(db, TblConfig);
+ rsConfigs.add(config);
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error creating config " + config.getName(), e, errs);
+ }
+}
+
+/**
+ * Destroy generic durable configuration
+ */
+void
+MSSqlProvider::destroy(const PersistableConfig& config)
+{
+ DatabaseConnection *db = initConnection();
+ BlobRecordset rsConfigs;
+ try {
+ db->beginTransaction();
+ rsConfigs.open(db, TblConfig);
+ rsConfigs.remove(config);
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error deleting config " + config.getName(), e, errs);
+ }
+}
+
+/**
+ * Stores a messages before it has been enqueued
+ * (enqueueing automatically stores the message so this is
+ * only required if storage is required prior to that
+ * point). If the message has not yet been stored it will
+ * store the headers as well as any content passed in. A
+ * persistence id will be set on the message which can be
+ * used to load the content or to append to it.
+ */
+void
+MSSqlProvider::stage(const boost::intrusive_ptr<PersistableMessage>& msg)
+{
+ DatabaseConnection *db = initConnection();
+ MessageRecordset rsMessages;
+ try {
+ db->beginTransaction();
+ rsMessages.open(db, TblMessage);
+ rsMessages.add(msg);
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error staging message", e, errs);
+ }
+}
+
+/**
+ * Destroys a previously staged message. This only needs
+ * to be called if the message is never enqueued. (Once
+ * enqueued, deletion will be automatic when the message
+ * is dequeued from all queues it was enqueued onto).
+ */
+void
+MSSqlProvider::destroy(PersistableMessage& msg)
+{
+ DatabaseConnection *db = initConnection();
+ BlobRecordset rsMessages;
+ try {
+ db->beginTransaction();
+ rsMessages.open(db, TblMessage);
+ rsMessages.remove(msg);
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error deleting message", e, errs);
+ }
+}
+
+/**
+ * Appends content to a previously staged message
+ */
+void
+MSSqlProvider::appendContent(const boost::intrusive_ptr<const PersistableMessage>& msg,
+ const std::string& data)
+{
+ DatabaseConnection *db = initConnection();
+ MessageRecordset rsMessages;
+ try {
+ db->beginTransaction();
+ rsMessages.open(db, TblMessage);
+ rsMessages.append(msg, data);
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error appending to message", e, errs);
+ }
+}
+
+/**
+ * Loads (a section) of content data for the specified
+ * message (previously stored through a call to stage or
+ * enqueue) into data. The offset refers to the content
+ * only (i.e. an offset of 0 implies that the start of the
+ * content should be loaded, not the headers or related
+ * meta-data).
+ */
+void
+MSSqlProvider::loadContent(const qpid::broker::PersistableQueue& /*queue*/,
+ const boost::intrusive_ptr<const PersistableMessage>& msg,
+ std::string& data,
+ uint64_t offset,
+ uint32_t length)
+{
+ // SQL store keeps all messages in one table, so we don't need the
+ // queue reference.
+ DatabaseConnection *db = initConnection();
+ MessageRecordset rsMessages;
+ try {
+ rsMessages.open(db, TblMessage);
+ rsMessages.loadContent(msg, data, offset, length);
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ throw ADOException("Error loading message content", e, errs);
+ }
+}
+
+/**
+ * Enqueues a message, storing the message if it has not
+ * been previously stored and recording that the given
+ * message is on the given queue.
+ *
+ * @param ctxt The transaction context under which this enqueue happens.
+ * @param msg The message to enqueue
+ * @param queue the name of the queue onto which it is to be enqueued
+ */
+void
+MSSqlProvider::enqueue(qpid::broker::TransactionContext* ctxt,
+ const boost::intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue& queue)
+{
+ // If this enqueue is in the context of a transaction, use the specified
+ // transaction to nest a new transaction for this operation. However, if
+ // this is not in the context of a transaction, then just use the thread's
+ // DatabaseConnection with a ADO transaction.
+ DatabaseConnection *db = 0;
+ std::string xid;
+ AmqpTransaction *atxn = dynamic_cast<AmqpTransaction*> (ctxt);
+ if (atxn == 0) {
+ db = initConnection();
+ db->beginTransaction();
+ }
+ else {
+ (void)initState(); // Ensure this thread is initialized
+ // It's a transactional enqueue; if it's TPC, grab the xid.
+ AmqpTPCTransaction *tpcTxn = dynamic_cast<AmqpTPCTransaction*> (ctxt);
+ if (tpcTxn)
+ xid = tpcTxn->getXid();
+ db = atxn->dbConn();
+ try {
+ atxn->sqlBegin();
+ }
+ catch(_com_error &e) {
+ throw ADOException("Error queuing message", e, db->getErrors());
+ }
+ }
+
+ MessageRecordset rsMessages;
+ MessageMapRecordset rsMap;
+ try {
+ if (msg->getPersistenceId() == 0) { // Message itself not yet saved
+ rsMessages.open(db, TblMessage);
+ rsMessages.add(msg);
+ }
+ rsMap.open(db, TblMessageMap);
+ rsMap.add(msg->getPersistenceId(), queue.getPersistenceId(), xid);
+ if (atxn)
+ atxn->sqlCommit();
+ else
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ if (atxn)
+ atxn->sqlAbort();
+ else
+ db->rollbackTransaction();
+ throw ADOException("Error queuing message", e, errs);
+ }
+ msg->enqueueComplete();
+}
+
+/**
+ * Dequeues a message, recording that the given message is
+ * no longer on the given queue and deleting the message
+ * if it is no longer on any other queue.
+ *
+ * @param ctxt The transaction context under which this dequeue happens.
+ * @param msg The message to dequeue
+ * @param queue The queue from which it is to be dequeued
+ */
+void
+MSSqlProvider::dequeue(qpid::broker::TransactionContext* ctxt,
+ const boost::intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue& queue)
+{
+ // If this dequeue is in the context of a transaction, use the specified
+ // transaction to nest a new transaction for this operation. However, if
+ // this is not in the context of a transaction, then just use the thread's
+ // DatabaseConnection with a ADO transaction.
+ DatabaseConnection *db = 0;
+ std::string xid;
+ AmqpTransaction *atxn = dynamic_cast<AmqpTransaction*> (ctxt);
+ if (atxn == 0) {
+ db = initConnection();
+ db->beginTransaction();
+ }
+ else {
+ (void)initState(); // Ensure this thread is initialized
+ // It's a transactional dequeue; if it's TPC, grab the xid.
+ AmqpTPCTransaction *tpcTxn = dynamic_cast<AmqpTPCTransaction*> (ctxt);
+ if (tpcTxn)
+ xid = tpcTxn->getXid();
+ db = atxn->dbConn();
+ try {
+ atxn->sqlBegin();
+ }
+ catch(_com_error &e) {
+ throw ADOException("Error queuing message", e, db->getErrors());
+ }
+ }
+
+ MessageMapRecordset rsMap;
+ try {
+ rsMap.open(db, TblMessageMap);
+ // TPC dequeues are just marked pending and will actually be removed
+ // when the transaction commits; Single-phase dequeues are removed
+ // now, relying on the SQL transaction to put it back if the
+ // transaction doesn't commit.
+ if (!xid.empty()) {
+ rsMap.pendingRemove(msg->getPersistenceId(),
+ queue.getPersistenceId(),
+ xid);
+ }
+ else {
+ rsMap.remove(msg->getPersistenceId(),
+ queue.getPersistenceId());
+ }
+ if (atxn)
+ atxn->sqlCommit();
+ else
+ db->commitTransaction();
+ }
+ catch(ms_sql::Exception&) {
+ if (atxn)
+ atxn->sqlAbort();
+ else
+ db->rollbackTransaction();
+ throw;
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ if (atxn)
+ atxn->sqlAbort();
+ else
+ db->rollbackTransaction();
+ throw ADOException("Error dequeuing message", e, errs);
+ }
+ msg->dequeueComplete();
+}
+
+std::auto_ptr<qpid::broker::TransactionContext>
+MSSqlProvider::begin()
+{
+ (void)initState(); // Ensure this thread is initialized
+
+ // Transactions are associated with the Connection, so this transaction
+ // context needs its own connection. At the time of writing, single-phase
+ // transactions are dealt with completely on one thread, so we really
+ // could just use the thread-specific DatabaseConnection for this.
+ // However, that would introduce an ugly, hidden coupling, so play
+ // it safe and handle this just like a TPC transaction, which actually
+ // can be prepared and committed/aborted from different threads,
+ // making it a bad idea to try using the thread-local DatabaseConnection.
+ boost::shared_ptr<DatabaseConnection> db(new DatabaseConnection);
+ db->open(options.connectString, options.catalogName);
+ std::auto_ptr<AmqpTransaction> tx(new AmqpTransaction(db));
+ tx->sqlBegin();
+ std::auto_ptr<qpid::broker::TransactionContext> tc(tx);
+ return tc;
+}
+
+std::auto_ptr<qpid::broker::TPCTransactionContext>
+MSSqlProvider::begin(const std::string& xid)
+{
+ (void)initState(); // Ensure this thread is initialized
+ boost::shared_ptr<DatabaseConnection> db(new DatabaseConnection);
+ db->open(options.connectString, options.catalogName);
+ std::auto_ptr<AmqpTPCTransaction> tx(new AmqpTPCTransaction(db, xid));
+ tx->sqlBegin();
+
+ TplRecordset rsTpl;
+ try {
+ tx->sqlBegin();
+ rsTpl.open(db.get(), TblTpl);
+ rsTpl.add(xid);
+ tx->sqlCommit();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ tx->sqlAbort();
+ throw ADOException("Error adding TPL record", e, errs);
+ }
+
+ std::auto_ptr<qpid::broker::TPCTransactionContext> tc(tx);
+ return tc;
+}
+
+void
+MSSqlProvider::prepare(qpid::broker::TPCTransactionContext& txn)
+{
+ // Commit all the marked-up enqueue/dequeue ops and the TPL record.
+ // On commit/rollback the TPL will be removed and the TPL markups
+ // on the message map will be cleaned up as well.
+ (void)initState(); // Ensure this thread is initialized
+ AmqpTPCTransaction *atxn = dynamic_cast<AmqpTPCTransaction*> (&txn);
+ if (atxn == 0)
+ throw qpid::broker::InvalidTransactionContextException();
+ try {
+ atxn->sqlCommit();
+ }
+ catch(_com_error &e) {
+ throw ADOException("Error preparing", e, atxn->dbConn()->getErrors());
+ }
+ atxn->setPrepared();
+}
+
+void
+MSSqlProvider::commit(qpid::broker::TransactionContext& txn)
+{
+ (void)initState(); // Ensure this thread is initialized
+ /*
+ * One-phase transactions simply commit the outer SQL transaction
+ * that was begun on begin(). Two-phase transactions are different -
+ * the SQL transaction started on begin() was committed on prepare()
+ * so all the SQL records reflecting the enqueue/dequeue actions for
+ * the transaction are recorded but with xid markups on them to reflect
+ * that they are prepared but not committed. Now go back and remove
+ * the markups, deleting those marked for removal.
+ */
+ AmqpTPCTransaction *p2txn = dynamic_cast<AmqpTPCTransaction*> (&txn);
+ if (p2txn == 0) {
+ AmqpTransaction *p1txn = dynamic_cast<AmqpTransaction*> (&txn);
+ if (p1txn == 0)
+ throw qpid::broker::InvalidTransactionContextException();
+ p1txn->sqlCommit();
+ return;
+ }
+
+ DatabaseConnection *db(p2txn->dbConn());
+ TplRecordset rsTpl;
+ MessageMapRecordset rsMessageMap;
+ try {
+ db->beginTransaction();
+ rsTpl.open(db, TblTpl);
+ rsMessageMap.open(db, TblMessageMap);
+ rsMessageMap.commitPrepared(p2txn->getXid());
+ rsTpl.remove(p2txn->getXid());
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error committing transaction", e, errs);
+ }
+}
+
+void
+MSSqlProvider::abort(qpid::broker::TransactionContext& txn)
+{
+ (void)initState(); // Ensure this thread is initialized
+ /*
+ * One-phase and non-prepared two-phase transactions simply abort
+ * the outer SQL transaction that was begun on begin(). However, prepared
+ * two-phase transactions are different - the SQL transaction started
+ * on begin() was committed on prepare() so all the SQL records
+ * reflecting the enqueue/dequeue actions for the transaction are
+ * recorded but with xid markups on them to reflect that they are
+ * prepared but not committed. Now go back and remove the markups,
+ * deleting those marked for addition.
+ */
+ AmqpTPCTransaction *p2txn = dynamic_cast<AmqpTPCTransaction*> (&txn);
+ if (p2txn == 0 || !p2txn->isPrepared()) {
+ AmqpTransaction *p1txn = dynamic_cast<AmqpTransaction*> (&txn);
+ if (p1txn == 0)
+ throw qpid::broker::InvalidTransactionContextException();
+ p1txn->sqlAbort();
+ return;
+ }
+
+ DatabaseConnection *db(p2txn->dbConn());
+ TplRecordset rsTpl;
+ MessageMapRecordset rsMessageMap;
+ try {
+ db->beginTransaction();
+ rsTpl.open(db, TblTpl);
+ rsMessageMap.open(db, TblMessageMap);
+ rsMessageMap.abortPrepared(p2txn->getXid());
+ rsTpl.remove(p2txn->getXid());
+ db->commitTransaction();
+ }
+ catch(_com_error &e) {
+ std::string errs = db->getErrors();
+ db->rollbackTransaction();
+ throw ADOException("Error committing transaction", e, errs);
+ }
+
+
+ (void)initState(); // Ensure this thread is initialized
+ AmqpTransaction *atxn = dynamic_cast<AmqpTransaction*> (&txn);
+ if (atxn == 0)
+ throw qpid::broker::InvalidTransactionContextException();
+ atxn->sqlAbort();
+}
+
+void
+MSSqlProvider::collectPreparedXids(std::set<std::string>& xids)
+{
+ DatabaseConnection *db = initConnection();
+ try {
+ TplRecordset rsTpl;
+ rsTpl.open(db, TblTpl);
+ rsTpl.recover(xids);
+ }
+ catch(_com_error &e) {
+ throw ADOException("Error reading TPL", e, db->getErrors());
+ }
+}
+
+// @TODO Much of this recovery code is way too similar... refactor to
+// a recover template method on BlobRecordset.
+
+void
+MSSqlProvider::recoverConfigs(qpid::broker::RecoveryManager& recoverer)
+{
+ DatabaseConnection *db = 0;
+ try {
+ db = initConnection();
+ BlobRecordset rsConfigs;
+ rsConfigs.open(db, TblConfig);
+ _RecordsetPtr p = (_RecordsetPtr)rsConfigs;
+ if (p->BOF && p->EndOfFile)
+ return; // Nothing to do
+ p->MoveFirst();
+ while (!p->EndOfFile) {
+ uint64_t id = p->Fields->Item["persistenceId"]->Value;
+ long blobSize = p->Fields->Item["fieldTableBlob"]->ActualSize;
+ BlobAdapter blob(blobSize);
+ blob = p->Fields->Item["fieldTableBlob"]->GetChunk(blobSize);
+ // Recreate the Config instance and reset its ID.
+ broker::RecoverableConfig::shared_ptr config =
+ recoverer.recoverConfig(blob);
+ config->setPersistenceId(id);
+ p->MoveNext();
+ }
+ }
+ catch(_com_error &e) {
+ throw ADOException("Error recovering configs",
+ e,
+ db ? db->getErrors() : "");
+ }
+}
+
+void
+MSSqlProvider::recoverExchanges(qpid::broker::RecoveryManager& recoverer,
+ ExchangeMap& exchangeMap)
+{
+ DatabaseConnection *db = 0;
+ try {
+ db = initConnection();
+ BlobRecordset rsExchanges;
+ rsExchanges.open(db, TblExchange);
+ _RecordsetPtr p = (_RecordsetPtr)rsExchanges;
+ if (p->BOF && p->EndOfFile)
+ return; // Nothing to do
+ p->MoveFirst();
+ while (!p->EndOfFile) {
+ uint64_t id = p->Fields->Item["persistenceId"]->Value;
+ long blobSize = p->Fields->Item["fieldTableBlob"]->ActualSize;
+ BlobAdapter blob(blobSize);
+ blob = p->Fields->Item["fieldTableBlob"]->GetChunk(blobSize);
+ // Recreate the Exchange instance, reset its ID, and remember the
+ // ones restored for matching up when recovering bindings.
+ broker::RecoverableExchange::shared_ptr exchange =
+ recoverer.recoverExchange(blob);
+ exchange->setPersistenceId(id);
+ exchangeMap[id] = exchange;
+ p->MoveNext();
+ }
+ }
+ catch(_com_error &e) {
+ throw ADOException("Error recovering exchanges",
+ e,
+ db ? db->getErrors() : "");
+ }
+}
+
+void
+MSSqlProvider::recoverQueues(qpid::broker::RecoveryManager& recoverer,
+ QueueMap& queueMap)
+{
+ DatabaseConnection *db = 0;
+ try {
+ db = initConnection();
+ BlobRecordset rsQueues;
+ rsQueues.open(db, TblQueue);
+ _RecordsetPtr p = (_RecordsetPtr)rsQueues;
+ if (p->BOF && p->EndOfFile)
+ return; // Nothing to do
+ p->MoveFirst();
+ while (!p->EndOfFile) {
+ uint64_t id = p->Fields->Item["persistenceId"]->Value;
+ long blobSize = p->Fields->Item["fieldTableBlob"]->ActualSize;
+ BlobAdapter blob(blobSize);
+ blob = p->Fields->Item["fieldTableBlob"]->GetChunk(blobSize);
+ // Recreate the Queue instance and reset its ID.
+ broker::RecoverableQueue::shared_ptr queue =
+ recoverer.recoverQueue(blob);
+ queue->setPersistenceId(id);
+ queueMap[id] = queue;
+ p->MoveNext();
+ }
+ }
+ catch(_com_error &e) {
+ throw ADOException("Error recovering queues",
+ e,
+ db ? db->getErrors() : "");
+ }
+}
+
+void
+MSSqlProvider::recoverBindings(qpid::broker::RecoveryManager& recoverer,
+ const ExchangeMap& exchangeMap,
+ const QueueMap& queueMap)
+{
+ DatabaseConnection *db = 0;
+ try {
+ db = initConnection();
+ BindingRecordset rsBindings;
+ rsBindings.open(db, TblBinding);
+ rsBindings.recover(recoverer, exchangeMap, queueMap);
+ }
+ catch(_com_error &e) {
+ throw ADOException("Error recovering bindings",
+ e,
+ db ? db->getErrors() : "");
+ }
+}
+
+void
+MSSqlProvider::recoverMessages(qpid::broker::RecoveryManager& recoverer,
+ MessageMap& messageMap,
+ MessageQueueMap& messageQueueMap)
+{
+ DatabaseConnection *db = 0;
+ try {
+ db = initConnection();
+ MessageRecordset rsMessages;
+ rsMessages.open(db, TblMessage);
+ rsMessages.recover(recoverer, messageMap);
+
+ MessageMapRecordset rsMessageMaps;
+ rsMessageMaps.open(db, TblMessageMap);
+ rsMessageMaps.recover(messageQueueMap);
+ }
+ catch(_com_error &e) {
+ throw ADOException("Error recovering messages",
+ e,
+ db ? db->getErrors() : "");
+ }
+}
+
+void
+MSSqlProvider::recoverTransactions(qpid::broker::RecoveryManager& recoverer,
+ PreparedTransactionMap& dtxMap)
+{
+ DatabaseConnection *db = initConnection();
+ std::set<std::string> xids;
+ try {
+ TplRecordset rsTpl;
+ rsTpl.open(db, TblTpl);
+ rsTpl.recover(xids);
+ }
+ catch(_com_error &e) {
+ throw ADOException("Error recovering TPL records", e, db->getErrors());
+ }
+
+ try {
+ // Rebuild the needed RecoverableTransactions.
+ for (std::set<std::string>::const_iterator iXid = xids.begin();
+ iXid != xids.end();
+ ++iXid) {
+ boost::shared_ptr<DatabaseConnection> dbX(new DatabaseConnection);
+ dbX->open(options.connectString, options.catalogName);
+ std::auto_ptr<AmqpTPCTransaction> tx(new AmqpTPCTransaction(dbX,
+ *iXid));
+ tx->setPrepared();
+ std::auto_ptr<qpid::broker::TPCTransactionContext> tc(tx);
+ dtxMap[*iXid] = recoverer.recoverTransaction(*iXid, tc);
+ }
+ }
+ catch(_com_error &e) {
+ throw ADOException("Error recreating dtx connection", e);
+ }
+}
+
+////////////// Internal Methods
+
+State *
+MSSqlProvider::initState()
+{
+ State *state = dbState.get(); // See if thread has initialized
+ if (!state) {
+ state = new State;
+ dbState.reset(state);
+ }
+ return state;
+}
+
+DatabaseConnection *
+MSSqlProvider::initConnection(void)
+{
+ State *state = initState();
+ if (state->dbConn != 0)
+ return state->dbConn; // And the DatabaseConnection is set up too
+ std::auto_ptr<DatabaseConnection> db(new DatabaseConnection);
+ db->open(options.connectString, options.catalogName);
+ state->dbConn = db.release();
+ return state->dbConn;
+}
+
+void
+MSSqlProvider::createDb(DatabaseConnection *db, const std::string &name)
+{
+ const std::string dbCmd = "CREATE DATABASE " + name;
+ const std::string useCmd = "USE " + name;
+ const std::string tableCmd = "CREATE TABLE ";
+ const std::string colSpecs =
+ " (persistenceId bigint PRIMARY KEY NOT NULL IDENTITY(1,1),"
+ " fieldTableBlob varbinary(MAX) NOT NULL)";
+ const std::string bindingSpecs =
+ " (exchangeId bigint REFERENCES tblExchange(persistenceId) NOT NULL,"
+ " queueId bigint REFERENCES tblQueue(persistenceId) NOT NULL,"
+ " routingKey varchar(255),"
+ " fieldTableBlob varbinary(MAX))";
+ const std::string messageMapSpecs =
+ " (messageId bigint REFERENCES tblMessage(persistenceId) NOT NULL,"
+ " queueId bigint REFERENCES tblQueue(persistenceId) NOT NULL,"
+ " prepareStatus tinyint CHECK (prepareStatus IS NULL OR "
+ " prepareStatus IN (1, 2)),"
+ " xid varbinary(512) REFERENCES tblTPL(xid)"
+ " CONSTRAINT CK_NoDups UNIQUE NONCLUSTERED (messageId, queueId) )";
+ const std::string tplSpecs = " (xid varbinary(512) PRIMARY KEY NOT NULL)";
+ // SET NOCOUNT ON added to prevent extra result sets from
+ // interfering with SELECT statements. (Added by SQL Management)
+ const std::string removeUnrefMsgsTrigger =
+ "CREATE TRIGGER dbo.RemoveUnreferencedMessages "
+ "ON tblMessageMap AFTER DELETE AS BEGIN "
+ "SET NOCOUNT ON; "
+ "DELETE FROM tblMessage "
+ "WHERE tblMessage.persistenceId IN "
+ " (SELECT messageId FROM deleted) AND"
+ " NOT EXISTS(SELECT * FROM tblMessageMap"
+ " WHERE tblMessageMap.messageId IN"
+ " (SELECT messageId FROM deleted)) "
+ "END";
+
+ _variant_t unused;
+ _bstr_t dbStr = dbCmd.c_str();
+ _ConnectionPtr conn(*db);
+ try {
+ conn->Execute(dbStr, &unused, adExecuteNoRecords);
+ _bstr_t useStr = useCmd.c_str();
+ conn->Execute(useStr, &unused, adExecuteNoRecords);
+ std::string makeTable = tableCmd + TblQueue + colSpecs;
+ _bstr_t makeTableStr = makeTable.c_str();
+ conn->Execute(makeTableStr, &unused, adExecuteNoRecords);
+ makeTable = tableCmd + TblExchange + colSpecs;
+ makeTableStr = makeTable.c_str();
+ conn->Execute(makeTableStr, &unused, adExecuteNoRecords);
+ makeTable = tableCmd + TblConfig + colSpecs;
+ makeTableStr = makeTable.c_str();
+ conn->Execute(makeTableStr, &unused, adExecuteNoRecords);
+ makeTable = tableCmd + TblMessage + colSpecs;
+ makeTableStr = makeTable.c_str();
+ conn->Execute(makeTableStr, &unused, adExecuteNoRecords);
+ makeTable = tableCmd + TblBinding + bindingSpecs;
+ makeTableStr = makeTable.c_str();
+ conn->Execute(makeTableStr, &unused, adExecuteNoRecords);
+ makeTable = tableCmd + TblTpl + tplSpecs;
+ makeTableStr = makeTable.c_str();
+ conn->Execute(makeTableStr, &unused, adExecuteNoRecords);
+ makeTable = tableCmd + TblMessageMap + messageMapSpecs;
+ makeTableStr = makeTable.c_str();
+ conn->Execute(makeTableStr, &unused, adExecuteNoRecords);
+ _bstr_t addTriggerStr = removeUnrefMsgsTrigger.c_str();
+ conn->Execute(addTriggerStr, &unused, adExecuteNoRecords);
+ }
+ catch(_com_error &e) {
+ throw ADOException("MSSQL can't create " + name, e, db->getErrors());
+ }
+}
+
+void
+MSSqlProvider::dump()
+{
+ // dump all db records to qpid_log
+ QPID_LOG(notice, "DB Dump: (not dumping anything)");
+ // rsQueues.dump();
+}
+
+
+}}} // namespace qpid::store::ms_sql
diff --git a/qpid/cpp/src/qpid/store/ms-sql/MessageMapRecordset.cpp b/qpid/cpp/src/qpid/store/ms-sql/MessageMapRecordset.cpp
new file mode 100644
index 0000000000..ce9fa61010
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/MessageMapRecordset.cpp
@@ -0,0 +1,267 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <qpid/Exception.h>
+#include <qpid/log/Statement.h>
+#include <qpid/store/StorageProvider.h>
+
+#include "MessageMapRecordset.h"
+#include "BlobEncoder.h"
+#include "DatabaseConnection.h"
+#include "Exception.h"
+#include "VariantHelper.h"
+
+namespace {
+inline void TESTHR(HRESULT x) {if FAILED(x) _com_issue_error(x);};
+}
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+void
+MessageMapRecordset::open(DatabaseConnection* conn, const std::string& table)
+{
+ init(conn, table);
+}
+
+void
+MessageMapRecordset::add(uint64_t messageId,
+ uint64_t queueId,
+ const std::string& xid)
+{
+ std::ostringstream command;
+ command << "INSERT INTO " << tableName
+ << " (messageId, queueId";
+ if (!xid.empty())
+ command << ", prepareStatus, xid";
+ command << ") VALUES (" << messageId << "," << queueId;
+ if (!xid.empty())
+ command << "," << PREPARE_ADD << ",?";
+ command << ")" << std::ends;
+
+ _CommandPtr cmd = NULL;
+ _ParameterPtr xidVal = NULL;
+ TESTHR(cmd.CreateInstance(__uuidof(Command)));
+ _ConnectionPtr p = *dbConn;
+ cmd->ActiveConnection = p;
+ cmd->CommandText = command.str().c_str();
+ cmd->CommandType = adCmdText;
+ if (!xid.empty()) {
+ TESTHR(xidVal.CreateInstance(__uuidof(Parameter)));
+ xidVal->Name = "@xid";
+ xidVal->Type = adVarBinary;
+ xidVal->Size = xid.length();
+ xidVal->Direction = adParamInput;
+ xidVal->Value = BlobEncoder(xid);
+ cmd->Parameters->Append(xidVal);
+ }
+ cmd->Execute(NULL, NULL, adCmdText | adExecuteNoRecords);
+}
+
+void
+MessageMapRecordset::remove(uint64_t messageId, uint64_t queueId)
+{
+ std::ostringstream command;
+ command << "DELETE FROM " << tableName
+ << " WHERE queueId = " << queueId
+ << " AND messageId = " << messageId << std::ends;
+ _CommandPtr cmd = NULL;
+ TESTHR(cmd.CreateInstance(__uuidof(Command)));
+ _ConnectionPtr p = *dbConn;
+ cmd->ActiveConnection = p;
+ cmd->CommandText = command.str().c_str();
+ cmd->CommandType = adCmdText;
+ _variant_t deletedRecords;
+ cmd->Execute(&deletedRecords, NULL, adCmdText | adExecuteNoRecords);
+ if ((long)deletedRecords == 0)
+ throw ms_sql::Exception("Message does not exist in queue mapping");
+ // Trigger on deleting the mapping takes care of deleting orphaned
+ // message record from tblMessage.
+}
+
+void
+MessageMapRecordset::pendingRemove(uint64_t messageId,
+ uint64_t queueId,
+ const std::string& xid)
+{
+ // Look up the mapping for the specified message and queue. There
+ // should be only one because of the uniqueness constraint in the
+ // SQL table. Update it to reflect it's pending delete with
+ // the specified xid.
+ std::ostringstream command;
+ command << "UPDATE " << tableName
+ << " SET prepareStatus=" << PREPARE_REMOVE
+ << " , xid=?"
+ << " WHERE queueId = " << queueId
+ << " AND messageId = " << messageId << std::ends;
+
+ _CommandPtr cmd = NULL;
+ _ParameterPtr xidVal = NULL;
+ TESTHR(cmd.CreateInstance(__uuidof(Command)));
+ TESTHR(xidVal.CreateInstance(__uuidof(Parameter)));
+ _ConnectionPtr p = *dbConn;
+ cmd->ActiveConnection = p;
+ cmd->CommandText = command.str().c_str();
+ cmd->CommandType = adCmdText;
+ xidVal->Name = "@xid";
+ xidVal->Type = adVarBinary;
+ xidVal->Size = xid.length();
+ xidVal->Direction = adParamInput;
+ xidVal->Value = BlobEncoder(xid);
+ cmd->Parameters->Append(xidVal);
+ cmd->Execute(NULL, NULL, adCmdText | adExecuteNoRecords);
+}
+
+void
+MessageMapRecordset::removeForQueue(uint64_t queueId)
+{
+ std::ostringstream command;
+ command << "DELETE FROM " << tableName
+ << " WHERE queueId = " << queueId << std::ends;
+ _CommandPtr cmd = NULL;
+
+ TESTHR(cmd.CreateInstance(__uuidof(Command)));
+ _ConnectionPtr p = *dbConn;
+ cmd->ActiveConnection = p;
+ cmd->CommandText = command.str().c_str();
+ cmd->CommandType = adCmdText;
+ cmd->Execute(NULL, NULL, adCmdText | adExecuteNoRecords);
+}
+
+void
+MessageMapRecordset::commitPrepared(const std::string& xid)
+{
+ // Find all the records for the specified xid. Records marked as adding
+ // are now permanent so remove the xid and prepareStatus. Records marked
+ // as removing are removed entirely.
+ openRs();
+ MessageMap m;
+ IADORecordBinding *piAdoRecordBinding;
+ rs->QueryInterface(__uuidof(IADORecordBinding),
+ (LPVOID *)&piAdoRecordBinding);
+ piAdoRecordBinding->BindToRecordset(&m);
+ for (; !rs->EndOfFile; rs->MoveNext()) {
+ if (m.xidStatus != adFldOK)
+ continue;
+ const std::string x(m.xid, m.xidLength);
+ if (x != xid)
+ continue;
+ if (m.prepareStatus == PREPARE_REMOVE) {
+ rs->Delete(adAffectCurrent);
+ }
+ else {
+ _variant_t dbNull;
+ dbNull.ChangeType(VT_NULL);
+ rs->Fields->GetItem("prepareStatus")->Value = dbNull;
+ rs->Fields->GetItem("xid")->Value = dbNull;
+ }
+ rs->Update();
+ }
+ piAdoRecordBinding->Release();
+}
+
+void
+MessageMapRecordset::abortPrepared(const std::string& xid)
+{
+ // Find all the records for the specified xid. Records marked as adding
+ // need to be removed while records marked as removing are put back to
+ // no xid and no prepareStatus.
+ openRs();
+ MessageMap m;
+ IADORecordBinding *piAdoRecordBinding;
+ rs->QueryInterface(__uuidof(IADORecordBinding),
+ (LPVOID *)&piAdoRecordBinding);
+ piAdoRecordBinding->BindToRecordset(&m);
+ for (; !rs->EndOfFile; rs->MoveNext()) {
+ if (m.xidStatus != adFldOK)
+ continue;
+ const std::string x(m.xid, m.xidLength);
+ if (x != xid)
+ continue;
+ if (m.prepareStatus == PREPARE_ADD) {
+ rs->Delete(adAffectCurrent);
+ }
+ else {
+ _variant_t dbNull;
+ dbNull.ChangeType(VT_NULL);
+ rs->Fields->GetItem("prepareStatus")->Value = dbNull;
+ rs->Fields->GetItem("xid")->Value = dbNull;
+ }
+ rs->Update();
+ }
+ piAdoRecordBinding->Release();
+}
+
+void
+MessageMapRecordset::recover(MessageQueueMap& msgMap)
+{
+ openRs();
+ if (rs->BOF && rs->EndOfFile)
+ return; // Nothing to do
+ rs->MoveFirst();
+ MessageMap b;
+ IADORecordBinding *piAdoRecordBinding;
+ rs->QueryInterface(__uuidof(IADORecordBinding),
+ (LPVOID *)&piAdoRecordBinding);
+ piAdoRecordBinding->BindToRecordset(&b);
+ while (!rs->EndOfFile) {
+ qpid::store::QueueEntry entry(b.queueId);
+ if (b.xidStatus == adFldOK && b.xidLength > 0) {
+ entry.xid.assign(b.xid, b.xidLength);
+ entry.tplStatus =
+ b.prepareStatus == PREPARE_ADD ? QueueEntry::ADDING
+ : QueueEntry::REMOVING;
+ }
+ else {
+ entry.tplStatus = QueueEntry::NONE;
+ }
+ msgMap[b.messageId].push_back(entry);
+ rs->MoveNext();
+ }
+
+ piAdoRecordBinding->Release();
+}
+
+void
+MessageMapRecordset::dump()
+{
+ openRs();
+ Recordset::dump();
+ if (rs->EndOfFile && rs->BOF) // No records
+ return;
+ rs->MoveFirst();
+
+ MessageMap m;
+ IADORecordBinding *piAdoRecordBinding;
+ rs->QueryInterface(__uuidof(IADORecordBinding),
+ (LPVOID *)&piAdoRecordBinding);
+ piAdoRecordBinding->BindToRecordset(&m);
+
+ while (!rs->EndOfFile) {
+ QPID_LOG(notice, "msg " << m.messageId << " on queue " << m.queueId);
+ rs->MoveNext();
+ }
+
+ piAdoRecordBinding->Release();
+}
+
+}}} // namespace qpid::store::ms_sql
diff --git a/qpid/cpp/src/qpid/store/ms-sql/MessageMapRecordset.h b/qpid/cpp/src/qpid/store/ms-sql/MessageMapRecordset.h
new file mode 100644
index 0000000000..1b0c2f073e
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/MessageMapRecordset.h
@@ -0,0 +1,100 @@
+#ifndef QPID_STORE_MSSQL_MESSAGEMAPRECORDSET_H
+#define QPID_STORE_MSSQL_MESSAGEMAPRECORDSET_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <icrsint.h>
+#include "Recordset.h"
+#include <qpid/broker/RecoveryManager.h>
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+/**
+ * @class MessageMapRecordset
+ *
+ * Class for the message map (message -> queue) records.
+ */
+class MessageMapRecordset : public Recordset {
+
+ // These values are defined in a constraint on the tblMessageMap table.
+ // the prepareStatus column can only be null, 1, or 2.
+ enum { PREPARE_ADD=1, PREPARE_REMOVE=2 };
+
+ class MessageMap : public CADORecordBinding {
+ BEGIN_ADO_BINDING(MessageMap)
+ ADO_FIXED_LENGTH_ENTRY2(1, adBigInt, messageId, FALSE)
+ ADO_FIXED_LENGTH_ENTRY2(2, adBigInt, queueId, FALSE)
+ ADO_FIXED_LENGTH_ENTRY2(3, adTinyInt, prepareStatus, FALSE)
+ ADO_VARIABLE_LENGTH_ENTRY(4, adVarBinary, xid, sizeof(xid),
+ xidStatus, xidLength, FALSE)
+ END_ADO_BINDING()
+
+ public:
+ uint64_t messageId;
+ uint64_t queueId;
+ uint8_t prepareStatus;
+ char xid[512];
+ int xidStatus;
+ uint32_t xidLength;
+ };
+
+ void selectOnXid(const std::string& xid);
+
+public:
+ virtual void open(DatabaseConnection* conn, const std::string& table);
+
+ // Add a new mapping
+ void add(uint64_t messageId,
+ uint64_t queueId,
+ const std::string& xid = "");
+
+ // Remove a specific mapping.
+ void remove(uint64_t messageId, uint64_t queueId);
+
+ // Mark the indicated message->queue entry pending removal. The entry
+ // for the mapping is updated to indicate pending removal with the
+ // specified xid.
+ void pendingRemove(uint64_t messageId,
+ uint64_t queueId,
+ const std::string& xid);
+
+ // Remove mappings for all messages on a specified queue.
+ void removeForQueue(uint64_t queueId);
+
+ // Commit records recorded as prepared.
+ void commitPrepared(const std::string& xid);
+
+ // Abort prepared changes.
+ void abortPrepared(const std::string& xid);
+
+ // Recover the mappings of message ID -> vector<queue ID>.
+ void recover(MessageQueueMap& msgMap);
+
+ // Dump table contents; useful for debugging.
+ void dump();
+};
+
+}}} // namespace qpid::store::ms_sql
+
+#endif /* QPID_STORE_MSSQL_MESSAGEMAPRECORDSET_H */
diff --git a/qpid/cpp/src/qpid/store/ms-sql/MessageRecordset.cpp b/qpid/cpp/src/qpid/store/ms-sql/MessageRecordset.cpp
new file mode 100644
index 0000000000..495f1a08c2
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/MessageRecordset.cpp
@@ -0,0 +1,184 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <qpid/Exception.h>
+#include <qpid/log/Statement.h>
+
+#include "MessageRecordset.h"
+#include "BlobAdapter.h"
+#include "BlobEncoder.h"
+#include "VariantHelper.h"
+
+#include <boost/intrusive_ptr.hpp>
+
+class qpid::broker::PersistableMessage;
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+void
+MessageRecordset::add(const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg)
+{
+ BlobEncoder blob (msg); // Marshall headers and content to a blob
+ rs->AddNew();
+ rs->Fields->GetItem("fieldTableBlob")->AppendChunk(blob);
+ rs->Update();
+ uint64_t id = rs->Fields->Item["persistenceId"]->Value;
+ msg->setPersistenceId(id);
+}
+
+void
+MessageRecordset::append(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg,
+ const std::string& data)
+{
+ // Look up the message by its Id
+ std::ostringstream filter;
+ filter << "persistenceId = " << msg->getPersistenceId() << std::ends;
+ rs->PutFilter (VariantHelper<std::string>(filter.str()));
+ if (rs->RecordCount == 0) {
+ throw Exception("Can't append to message not stored in database");
+ }
+ BlobEncoder blob (data); // Marshall string data to a blob
+ rs->Fields->GetItem("fieldTableBlob")->AppendChunk(blob);
+ rs->Update();
+}
+
+void
+MessageRecordset::remove(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg)
+{
+ BlobRecordset::remove(msg->getPersistenceId());
+}
+
+void
+MessageRecordset::loadContent(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg,
+ std::string& data,
+ uint64_t offset,
+ uint32_t length)
+{
+ // Look up the message by its Id
+ std::ostringstream filter;
+ filter << "persistenceId = " << msg->getPersistenceId() << std::ends;
+ rs->PutFilter (VariantHelper<std::string>(filter.str()));
+ if (rs->RecordCount == 0) {
+ throw Exception("Can't load message not stored in database");
+ }
+
+ // NOTE! If this code needs to change, please verify the encoding
+ // code in BlobEncoder.
+ long blobSize = rs->Fields->Item["fieldTableBlob"]->ActualSize;
+ uint32_t headerSize;
+ const size_t headerFieldLength = sizeof(headerSize);
+ BlobAdapter blob(headerFieldLength);
+ blob =
+ rs->Fields->Item["fieldTableBlob"]->GetChunk((long)headerFieldLength);
+ headerSize = ((qpid::framing::Buffer&)blob).getLong();
+
+ // GetChunk always begins reading where the previous GetChunk left off,
+ // so we can't just tell it to ignore the header and read the data.
+ // So, read the header plus the offset, plus the desired data, then
+ // copy the desired data to the supplied string. If this ends up asking
+ // for more than is available in the field, reduce it to what's there.
+ long getSize = headerSize + offset + length;
+ if (getSize + (long)headerFieldLength > blobSize) {
+ size_t reduce = (getSize + headerFieldLength) - blobSize;
+ getSize -= reduce;
+ length -= reduce;
+ }
+ BlobAdapter header_plus(getSize);
+ header_plus = rs->Fields->Item["fieldTableBlob"]->GetChunk(getSize);
+ uint8_t *throw_away = new uint8_t[headerSize + offset];
+ ((qpid::framing::Buffer&)header_plus).getRawData(throw_away, headerSize + offset);
+ delete throw_away;
+ ((qpid::framing::Buffer&)header_plus).getRawData(data, length);
+}
+
+void
+MessageRecordset::recover(qpid::broker::RecoveryManager& recoverer,
+ std::map<uint64_t, broker::RecoverableMessage::shared_ptr>& messageMap)
+{
+ if (rs->BOF && rs->EndOfFile)
+ return; // Nothing to do
+ rs->MoveFirst();
+ Binding b;
+ IADORecordBinding *piAdoRecordBinding;
+ rs->QueryInterface(__uuidof(IADORecordBinding),
+ (LPVOID *)&piAdoRecordBinding);
+ piAdoRecordBinding->BindToRecordset(&b);
+ while (!rs->EndOfFile) {
+ // The blob was written as normal, but with the header length
+ // prepended in a uint32_t. Due to message staging threshold
+ // limits, the header may be all that's read in; get it first,
+ // recover that message header, then see if the rest is needed.
+ //
+ // NOTE! If this code needs to change, please verify the encoding
+ // code in BlobEncoder.
+ long blobSize = rs->Fields->Item["fieldTableBlob"]->ActualSize;
+ uint32_t headerSize;
+ const size_t headerFieldLength = sizeof(headerSize);
+ BlobAdapter blob(headerFieldLength);
+ blob =
+ rs->Fields->Item["fieldTableBlob"]->GetChunk((long)headerFieldLength);
+ headerSize = ((qpid::framing::Buffer&)blob).getLong();
+ BlobAdapter header(headerSize);
+ header = rs->Fields->Item["fieldTableBlob"]->GetChunk(headerSize);
+ broker::RecoverableMessage::shared_ptr msg;
+ msg = recoverer.recoverMessage(header);
+ msg->setPersistenceId(b.messageId);
+ messageMap[b.messageId] = msg;
+
+ // Now, do we need the rest of the content?
+ long contentLength = blobSize - headerFieldLength - headerSize;
+ if (contentLength > 0 && msg->loadContent(contentLength)) {
+ BlobAdapter content(contentLength);
+ content =
+ rs->Fields->Item["fieldTableBlob"]->GetChunk(contentLength);
+ msg->decodeContent(content);
+ }
+ rs->MoveNext();
+ }
+
+ piAdoRecordBinding->Release();
+}
+
+void
+MessageRecordset::dump()
+{
+ Recordset::dump();
+ if (rs->EndOfFile && rs->BOF) // No records
+ return;
+ rs->MoveFirst();
+
+ Binding b;
+ IADORecordBinding *piAdoRecordBinding;
+ rs->QueryInterface(__uuidof(IADORecordBinding),
+ (LPVOID *)&piAdoRecordBinding);
+ piAdoRecordBinding->BindToRecordset(&b);
+
+ while (VARIANT_FALSE == rs->EndOfFile) {
+ QPID_LOG(notice, "Msg " << b.messageId);
+ rs->MoveNext();
+ }
+
+ piAdoRecordBinding->Release();
+}
+
+}}} // namespace qpid::store::ms_sql
diff --git a/qpid/cpp/src/qpid/store/ms-sql/MessageRecordset.h b/qpid/cpp/src/qpid/store/ms-sql/MessageRecordset.h
new file mode 100644
index 0000000000..698b2561fe
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/MessageRecordset.h
@@ -0,0 +1,85 @@
+#ifndef QPID_STORE_MSSQL_MESSAGERECORDSET_H
+#define QPID_STORE_MSSQL_MESSAGERECORDSET_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <icrsint.h>
+#include "BlobRecordset.h"
+#include <qpid/broker/PersistableMessage.h>
+#include <qpid/broker/RecoveryManager.h>
+#include <boost/intrusive_ptr.hpp>
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+/**
+ * @class MessageRecordset
+ *
+ * Class for storing and recovering messages. Messages are primarily blobs
+ * and handled similarly. However, messages larger than the staging threshold
+ * are not contained completely in memory; they're left mostly in the store
+ * and the header is held in memory. So when the message "blob" is saved,
+ * an additional size-of-the-header field is prepended to the blob.
+ * On recovery, the size-of-the-header is used to get only what's needed
+ * until it's determined if the entire message is to be recovered to memory.
+ */
+class MessageRecordset : public BlobRecordset {
+ class Binding : public CADORecordBinding {
+ BEGIN_ADO_BINDING(Binding)
+ ADO_FIXED_LENGTH_ENTRY2(1, adBigInt, messageId, FALSE)
+ END_ADO_BINDING()
+
+ public:
+ uint64_t messageId;
+ };
+
+public:
+ // Store a message. Store the header size (4 bytes) then the regular
+ // blob comprising the message.
+ void add(const boost::intrusive_ptr<qpid::broker::PersistableMessage>& msg);
+
+ // Append additional content to an existing message.
+ void append(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg,
+ const std::string& data);
+
+ // Remove an existing message
+ void remove(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg);
+
+ // Load all or part of a stored message. This skips the header parts and
+ // loads content.
+ void loadContent(const boost::intrusive_ptr<const qpid::broker::PersistableMessage>& msg,
+ std::string& data,
+ uint64_t offset,
+ uint32_t length);
+
+ // Recover messages and save a map of those recovered.
+ void recover(qpid::broker::RecoveryManager& recoverer,
+ std::map<uint64_t, broker::RecoverableMessage::shared_ptr>& messageMap);
+
+ // Dump table contents; useful for debugging.
+ void dump();
+};
+
+}}} // namespace qpid::store::ms_sql
+
+#endif /* QPID_STORE_MSSQL_MESSAGERECORDSET_H */
diff --git a/qpid/cpp/src/qpid/store/ms-sql/Recordset.cpp b/qpid/cpp/src/qpid/store/ms-sql/Recordset.cpp
new file mode 100644
index 0000000000..e706799951
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/Recordset.cpp
@@ -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.
+ *
+ */
+
+#include <qpid/Exception.h>
+#include <qpid/log/Statement.h>
+
+#include "Recordset.h"
+#include "BlobEncoder.h"
+#include "DatabaseConnection.h"
+#include "VariantHelper.h"
+
+namespace {
+inline void TESTHR(HRESULT x) {if FAILED(x) _com_issue_error(x);};
+}
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+
+void
+Recordset::init(DatabaseConnection* conn, const std::string& table)
+{
+ dbConn = conn;
+ TESTHR(rs.CreateInstance(__uuidof(::Recordset)));
+ tableName = table;
+}
+
+void
+Recordset::openRs()
+{
+ // Client-side cursors needed to get access to newly added
+ // identity column immediately. Recordsets need this to get the
+ // persistence ID for the broker objects.
+ rs->CursorLocation = adUseClient;
+ _ConnectionPtr p = *dbConn;
+ rs->Open(tableName.c_str(),
+ _variant_t((IDispatch *)p, true),
+ adOpenStatic,
+ adLockOptimistic,
+ adCmdTable);
+}
+
+void
+Recordset::open(DatabaseConnection* conn, const std::string& table)
+{
+ init(conn, table);
+ openRs();
+}
+
+void
+Recordset::close()
+{
+ if (rs && rs->State == adStateOpen)
+ rs->Close();
+}
+
+void
+Recordset::requery()
+{
+ // Restore the recordset to reflect all current records.
+ rs->Filter = "";
+ rs->Requery(-1);
+}
+
+void
+Recordset::dump()
+{
+ long count = rs->RecordCount;
+ QPID_LOG(notice, "DB Dump: " + tableName <<
+ ": " << count << " records");
+}
+
+}}} // namespace qpid::store::ms_sql
diff --git a/qpid/cpp/src/qpid/store/ms-sql/Recordset.h b/qpid/cpp/src/qpid/store/ms-sql/Recordset.h
new file mode 100644
index 0000000000..032b2bd434
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/Recordset.h
@@ -0,0 +1,75 @@
+#ifndef QPID_STORE_MSSQL_RECORDSET_H
+#define QPID_STORE_MSSQL_RECORDSET_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+
+// Bring in ADO 2.8 (yes, I know it says "15", but that's it...)
+#import "C:\Program Files\Common Files\System\ado\msado15.dll" \
+ no_namespace rename("EOF", "EndOfFile")
+#include <comdef.h>
+#include <comutil.h>
+#include <string>
+#if 0
+#include <utility>
+#endif
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+class DatabaseConnection;
+
+/**
+ * @class Recordset
+ *
+ * Represents an ADO Recordset, abstracting out the common operations needed
+ * on the common tables used that have 2 fields, persistence ID and blob.
+ */
+class Recordset {
+protected:
+ _RecordsetPtr rs;
+ DatabaseConnection* dbConn;
+ std::string tableName;
+
+ void init(DatabaseConnection* conn, const std::string& table);
+ void openRs();
+
+public:
+ Recordset() : rs(0), dbConn(0) {}
+ virtual ~Recordset() { close(); rs = 0; dbConn = 0; }
+
+ /**
+ * Default open() reads all records into the recordset.
+ */
+ virtual void open(DatabaseConnection* conn, const std::string& table);
+ void close();
+ void requery();
+ operator _RecordsetPtr () { return rs; }
+
+ // Dump table contents; useful for debugging.
+ void dump();
+};
+
+}}} // namespace qpid::store::ms_sql
+
+#endif /* QPID_STORE_MSSQL_RECORDSET_H */
diff --git a/qpid/cpp/src/qpid/store/ms-sql/SqlTransaction.cpp b/qpid/cpp/src/qpid/store/ms-sql/SqlTransaction.cpp
new file mode 100644
index 0000000000..6ad7725570
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/SqlTransaction.cpp
@@ -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.
+ *
+ */
+
+#include "SqlTransaction.h"
+#include "DatabaseConnection.h"
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+SqlTransaction::SqlTransaction(const boost::shared_ptr<DatabaseConnection>& _db)
+ : db(_db), transDepth(0)
+{
+}
+
+SqlTransaction::~SqlTransaction()
+{
+ if (transDepth > 0)
+ this->abort();
+}
+
+void
+SqlTransaction::begin()
+{
+ _bstr_t beginCmd("BEGIN TRANSACTION");
+ _ConnectionPtr c = *db;
+ c->Execute(beginCmd, NULL, adExecuteNoRecords);
+ ++transDepth;
+}
+
+void
+SqlTransaction::commit()
+{
+ if (transDepth > 0) {
+ _bstr_t commitCmd("COMMIT TRANSACTION");
+ _ConnectionPtr c = *db;
+ c->Execute(commitCmd, NULL, adExecuteNoRecords);
+ --transDepth;
+ }
+}
+
+void
+SqlTransaction::abort()
+{
+ if (transDepth > 0) {
+ _bstr_t rollbackCmd("ROLLBACK TRANSACTION");
+ _ConnectionPtr c = *db;
+ c->Execute(rollbackCmd, NULL, adExecuteNoRecords);
+ transDepth = 0;
+ }
+}
+
+}}} // namespace qpid::store::ms_sql
diff --git a/qpid/cpp/src/qpid/store/ms-sql/SqlTransaction.h b/qpid/cpp/src/qpid/store/ms-sql/SqlTransaction.h
new file mode 100644
index 0000000000..8b5239b786
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/SqlTransaction.h
@@ -0,0 +1,67 @@
+#ifndef QPID_STORE_MSSQL_SQLTRANSACTION_H
+#define QPID_STORE_MSSQL_SQLTRANSACTION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+class DatabaseConnection;
+
+/**
+ * @class SqlTransaction
+ *
+ * Class representing an SQL transaction.
+ * Since ADO w/ SQLOLEDB can't do nested transaction via its BeginTrans(),
+ * et al, nested transactions are carried out with direct SQL commands.
+ * To ensure the state of this is known, keep track of how deeply the
+ * transactions are nested. This is more of a safety/sanity check since
+ * AMQP doesn't provide nested transactions.
+ */
+class SqlTransaction {
+
+ boost::shared_ptr<DatabaseConnection> db;
+
+ // Since ADO w/ SQLOLEDB can't do nested transaction via its BeginTrans(),
+ // et al, nested transactions are carried out with direct SQL commands.
+ // To ensure the state of this is known, keep track of how deeply the
+ // transactions are nested.
+ unsigned int transDepth;
+
+public:
+ SqlTransaction(const boost::shared_ptr<DatabaseConnection>& _db);
+ ~SqlTransaction();
+
+ DatabaseConnection *dbConn() { return db.get(); }
+
+ void begin();
+ void commit();
+ void abort();
+};
+
+}}} // namespace qpid::store::ms_sql
+
+#endif /* QPID_STORE_MSSQL_SQLTRANSACTION_H */
diff --git a/qpid/cpp/src/qpid/store/ms-sql/State.cpp b/qpid/cpp/src/qpid/store/ms-sql/State.cpp
new file mode 100644
index 0000000000..720603dd11
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/State.cpp
@@ -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.
+ *
+ */
+
+#include "State.h"
+#include "DatabaseConnection.h"
+#include "Exception.h"
+#include <comdef.h>
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+State::State() : dbConn(0)
+{
+ HRESULT hr = ::CoInitializeEx(NULL, COINIT_MULTITHREADED);
+ if (hr != S_OK && hr != S_FALSE)
+ throw Exception("Error initializing COM");
+}
+
+State::~State()
+{
+ if (dbConn)
+ delete dbConn;
+ ::CoUninitialize();
+}
+
+}}} // namespace qpid::store::ms_sql
diff --git a/qpid/cpp/src/qpid/store/ms-sql/State.h b/qpid/cpp/src/qpid/store/ms-sql/State.h
new file mode 100644
index 0000000000..6350bc5bd2
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/State.h
@@ -0,0 +1,52 @@
+#ifndef QPID_STORE_MSSQL_STATE_H
+#define QPID_STORE_MSSQL_STATE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+class DatabaseConnection;
+
+/**
+ * @struct State
+ *
+ * Represents a thread's state for accessing ADO and the database.
+ * Creating an instance of State initializes COM for this thread, and
+ * destroying it uninitializes COM. There's also a DatabaseConnection
+ * for this thread's default access to the database. More DatabaseConnections
+ * can always be created, but State has one that can always be used by
+ * the thread whose state is represented.
+ *
+ * This class is intended to be one-per-thread, so it should be accessed
+ * via thread-specific storage.
+ */
+struct State {
+ State();
+ ~State();
+ DatabaseConnection *dbConn;
+};
+
+}}} // namespace qpid::store::ms_sql
+
+#endif /* QPID_STORE_MSSQL_STATE_H */
diff --git a/qpid/cpp/src/qpid/store/ms-sql/TplRecordset.cpp b/qpid/cpp/src/qpid/store/ms-sql/TplRecordset.cpp
new file mode 100644
index 0000000000..1309d921a9
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/TplRecordset.cpp
@@ -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.
+ *
+ */
+
+#include <string>
+#include <qpid/Exception.h>
+#include <qpid/log/Statement.h>
+
+#include "TplRecordset.h"
+#include "BlobEncoder.h"
+#include "DatabaseConnection.h"
+#include "VariantHelper.h"
+
+namespace {
+inline void TESTHR(HRESULT x) {if FAILED(x) _com_issue_error(x);};
+}
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+void
+TplRecordset::open(DatabaseConnection* conn, const std::string& table)
+{
+ init(conn, table);
+ // Don't actually open until we know what to do. It's far easier and more
+ // efficient to simply do most of these TPL/xid ops in a single statement.
+}
+
+void
+TplRecordset::add(const std::string& xid)
+{
+ const std::string command =
+ "INSERT INTO " + tableName + " ( xid ) VALUES ( ? )";
+ _CommandPtr cmd = NULL;
+ _ParameterPtr xidVal = NULL;
+
+ TESTHR(cmd.CreateInstance(__uuidof(Command)));
+ TESTHR(xidVal.CreateInstance(__uuidof(Parameter)));
+ _ConnectionPtr p = *dbConn;
+ cmd->ActiveConnection = p;
+ cmd->CommandText = command.c_str();
+ cmd->CommandType = adCmdText;
+ xidVal->Name = "@xid";
+ xidVal->Type = adVarBinary;
+ xidVal->Size = xid.length();
+ xidVal->Direction = adParamInput;
+ xidVal->Value = BlobEncoder(xid);
+ cmd->Parameters->Append(xidVal);
+ cmd->Execute(NULL, NULL, adCmdText | adExecuteNoRecords);
+}
+
+void
+TplRecordset::remove(const std::string& xid)
+{
+ // Look up the item by its xid
+ const std::string command =
+ "DELETE FROM " + tableName + " WHERE xid = ?";
+ _CommandPtr cmd = NULL;
+ _ParameterPtr xidVal = NULL;
+
+ TESTHR(cmd.CreateInstance(__uuidof(Command)));
+ TESTHR(xidVal.CreateInstance(__uuidof(Parameter)));
+ _ConnectionPtr p = *dbConn;
+ cmd->ActiveConnection = p;
+ cmd->CommandText = command.c_str();
+ cmd->CommandType = adCmdText;
+ xidVal->Name = "@xid";
+ xidVal->Type = adVarBinary;
+ xidVal->Size = xid.length();
+ xidVal->Direction = adParamInput;
+ xidVal->Value = BlobEncoder(xid);
+ cmd->Parameters->Append(xidVal);
+ _variant_t deletedRecords;
+ cmd->Execute(&deletedRecords, NULL, adCmdText | adExecuteNoRecords);
+}
+
+void
+TplRecordset::recover(std::set<std::string>& xids)
+{
+ openRs();
+ if (rs->BOF && rs->EndOfFile)
+ return; // Nothing to do
+ rs->MoveFirst();
+ while (!rs->EndOfFile) {
+ _variant_t wxid = rs->Fields->Item["xid"]->Value;
+ char *xidBytes;
+ SafeArrayAccessData(wxid.parray, (void **)&xidBytes);
+ std::string xid(xidBytes, rs->Fields->Item["xid"]->ActualSize);
+ xids.insert(xid);
+ SafeArrayUnaccessData(wxid.parray);
+ rs->MoveNext();
+ }
+}
+
+void
+TplRecordset::dump()
+{
+ Recordset::dump();
+ if (rs->EndOfFile && rs->BOF) // No records
+ return;
+
+ rs->MoveFirst();
+ while (!rs->EndOfFile) {
+ _bstr_t wxid = rs->Fields->Item["xid"]->Value;
+ QPID_LOG(notice, " -> " << (const char *)wxid);
+ rs->MoveNext();
+ }
+}
+
+}}} // namespace qpid::store::ms_sql
diff --git a/qpid/cpp/src/qpid/store/ms-sql/TplRecordset.h b/qpid/cpp/src/qpid/store/ms-sql/TplRecordset.h
new file mode 100644
index 0000000000..fbde51738c
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/TplRecordset.h
@@ -0,0 +1,58 @@
+#ifndef QPID_STORE_MSSQL_TPLRECORDSET_H
+#define QPID_STORE_MSSQL_TPLRECORDSET_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "Recordset.h"
+#include <string>
+#include <set>
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+/**
+ * @class TplRecordset
+ *
+ * Class for the TPL (Transaction Prepared List) records.
+ */
+class TplRecordset : public Recordset {
+protected:
+
+public:
+ virtual void open(DatabaseConnection* conn, const std::string& table);
+
+ void add(const std::string& xid);
+
+ // Remove a record given its xid.
+ void remove(const std::string& xid);
+
+ // Recover prepared transaction XIDs.
+ void recover(std::set<std::string>& xids);
+
+ // Dump table contents; useful for debugging.
+ void dump();
+};
+
+}}} // namespace qpid::store::ms_sql
+
+#endif /* QPID_STORE_MSSQL_TPLRECORDSET_H */
diff --git a/qpid/cpp/src/qpid/store/ms-sql/VariantHelper.cpp b/qpid/cpp/src/qpid/store/ms-sql/VariantHelper.cpp
new file mode 100644
index 0000000000..acec95c1f9
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/VariantHelper.cpp
@@ -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.
+ *
+ */
+
+#include <string>
+#include "VariantHelper.h"
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+template <class Wrapped>
+VariantHelper<Wrapped>::VariantHelper()
+{
+ var.vt = VT_EMPTY;
+}
+
+template <class Wrapped>
+VariantHelper<Wrapped>::operator const _variant_t& () const
+{
+ return var;
+}
+
+// Specialization for using _variant_t to wrap a std::string
+VariantHelper<std::string>::VariantHelper(const std::string &init)
+{
+ if (init.empty() || init.length() == 0) {
+ var.vt = VT_BSTR;
+ var.bstrVal = NULL;
+ }
+ else {
+ var.SetString(init.c_str());
+ }
+}
+
+VariantHelper<std::string>&
+VariantHelper<std::string>::operator=(const std::string &rhs)
+{
+ if (rhs.empty() || rhs.length() == 0) {
+ var.vt = VT_BSTR;
+ var.bstrVal = NULL;
+ }
+ else {
+ var.SetString(rhs.c_str());
+ }
+ return *this;
+}
+
+VariantHelper<std::string>::operator const _variant_t& () const
+{
+ return var;
+}
+
+}}} // namespace qpid::store::ms_sql
diff --git a/qpid/cpp/src/qpid/store/ms-sql/VariantHelper.h b/qpid/cpp/src/qpid/store/ms-sql/VariantHelper.h
new file mode 100644
index 0000000000..723dbc3b76
--- /dev/null
+++ b/qpid/cpp/src/qpid/store/ms-sql/VariantHelper.h
@@ -0,0 +1,61 @@
+#ifndef QPID_STORE_MSSQL_VARIANTHELPER_H
+#define QPID_STORE_MSSQL_VARIANTHELPER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <comutil.h>
+
+namespace qpid {
+namespace store {
+namespace ms_sql {
+
+/**
+ * @class VariantHelper
+ *
+ * Class template to wrap the details of working with _variant_t objects.
+ */
+template <class Wrapped> class VariantHelper {
+private:
+ _variant_t var;
+
+public:
+ VariantHelper();
+ VariantHelper(const Wrapped &init);
+
+ VariantHelper& operator =(const Wrapped& rhs);
+ operator const _variant_t& () const;
+};
+
+// Specialization for using _variant_t to wrap a std::string
+template<> class VariantHelper<std::string> {
+private:
+ _variant_t var;
+
+public:
+ VariantHelper(const std::string &init);
+ VariantHelper& operator =(const std::string& rhs);
+ operator const _variant_t& () const;
+};
+
+}}} // namespace qpid::store::ms_sql
+
+#endif /* QPID_STORE_MSSQL_VARIANTHELPER_H */
diff --git a/qpid/cpp/src/qpid/sys/AggregateOutput.cpp b/qpid/cpp/src/qpid/sys/AggregateOutput.cpp
new file mode 100644
index 0000000000..773ea68e55
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/AggregateOutput.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/AggregateOutput.h"
+#include "qpid/log/Statement.h"
+#include <algorithm>
+
+namespace qpid {
+namespace sys {
+
+AggregateOutput::AggregateOutput() : busy(false) {}
+
+namespace {
+// Clear the busy flag and notify waiting threads in destructor.
+struct ScopedBusy {
+ bool& flag;
+ Monitor& monitor;
+ ScopedBusy(bool& f, Monitor& m) : flag(f), monitor(m) { f = true; }
+ ~ScopedBusy() { flag = false; monitor.notifyAll(); }
+};
+}
+
+bool AggregateOutput::doOutput() {
+ Mutex::ScopedLock l(lock);
+ ScopedBusy sb(busy, lock);
+
+ while (!tasks.empty()) {
+ OutputTask* t=tasks.front();
+ tasks.pop_front();
+ taskSet.erase(t);
+ bool didOutput;
+ {
+ // Allow concurrent call to addOutputTask.
+ // removeOutputTask will wait till !busy before removing a task.
+ Mutex::ScopedUnlock u(lock);
+ didOutput = t->doOutput();
+ }
+ if (didOutput) {
+ if (taskSet.insert(t).second) {
+ tasks.push_back(t);
+ }
+ return true;
+ }
+ }
+ return false;
+}
+
+void AggregateOutput::addOutputTask(OutputTask* task) {
+ Mutex::ScopedLock l(lock);
+ if (taskSet.insert(task).second) {
+ tasks.push_back(task);
+ }
+}
+
+void AggregateOutput::removeOutputTask(OutputTask* task) {
+ Mutex::ScopedLock l(lock);
+ while (busy) lock.wait();
+ taskSet.erase(task);
+ tasks.erase(std::remove(tasks.begin(), tasks.end(), task), tasks.end());
+}
+
+void AggregateOutput::removeAll()
+{
+ Mutex::ScopedLock l(lock);
+ while (busy) lock.wait();
+ taskSet.clear();
+ tasks.clear();
+}
+
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/AggregateOutput.h b/qpid/cpp/src/qpid/sys/AggregateOutput.h
new file mode 100644
index 0000000000..c8dd8d989a
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/AggregateOutput.h
@@ -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.
+ *
+ */
+#ifndef _AggregateOutput_
+#define _AggregateOutput_
+
+#include "qpid/sys/Monitor.h"
+#include "qpid/sys/OutputTask.h"
+
+#include "qpid/CommonImportExport.h"
+
+#include <algorithm>
+#include <deque>
+#include <set>
+
+namespace qpid {
+namespace sys {
+
+/**
+ * Holds a collection of output tasks, doOutput picks the next one to execute.
+ *
+ * Tasks are automatically removed if their doOutput() or hasOutput() returns false.
+ *
+ * Thread safe. addOutputTask may be called in one connection thread while
+ * doOutput is called in another.
+ */
+
+class QPID_COMMON_CLASS_EXTERN AggregateOutput : public OutputTask
+{
+ typedef std::deque<OutputTask*> TaskList;
+ typedef std::set<OutputTask*> TaskSet;
+
+ Monitor lock;
+ TaskList tasks;
+ TaskSet taskSet;
+ bool busy;
+
+ public:
+ QPID_COMMON_EXTERN AggregateOutput();
+
+ // These may be called concurrently with any function.
+ QPID_COMMON_EXTERN void addOutputTask(OutputTask* t);
+
+ // These functions must not be called concurrently with each other.
+ QPID_COMMON_EXTERN bool doOutput();
+ QPID_COMMON_EXTERN void removeOutputTask(OutputTask* t);
+ QPID_COMMON_EXTERN void removeAll();
+
+ /** Apply f to each OutputTask* in the tasks list */
+ template <class F> void eachOutput(F f) {
+ Mutex::ScopedLock l(lock);
+ std::for_each(tasks.begin(), tasks.end(), f);
+ }
+};
+
+}} // namespace qpid::sys
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/sys/AsynchIO.h b/qpid/cpp/src/qpid/sys/AsynchIO.h
new file mode 100644
index 0000000000..09402e9e44
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/AsynchIO.h
@@ -0,0 +1,175 @@
+#ifndef _sys_AsynchIO
+#define _sys_AsynchIO
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/CommonImportExport.h"
+
+#include "qpid/sys/IntegerTypes.h"
+
+#include <string.h>
+
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace sys {
+
+struct SecuritySettings;
+class Socket;
+class Poller;
+
+/*
+ * Asynchronous acceptor: accepts connections then does a callback with the
+ * accepted fd
+ */
+class AsynchAcceptor {
+public:
+ typedef boost::function1<void, const Socket&> Callback;
+
+ QPID_COMMON_EXTERN static AsynchAcceptor* create(const Socket& s, Callback callback);
+ virtual ~AsynchAcceptor() {};
+ virtual void start(boost::shared_ptr<Poller> poller) = 0;
+};
+
+/*
+ * Asynchronous connector: starts the process of initiating a connection and
+ * invokes a callback when completed or failed.
+ */
+class AsynchConnector {
+public:
+ typedef boost::function1<void, const Socket&> ConnectedCallback;
+ typedef boost::function3<void, const Socket&, int, const std::string&> FailedCallback;
+ typedef boost::function1<void, AsynchConnector&> RequestCallback;
+
+ // Call create() to allocate a new AsynchConnector object with the
+ // specified poller, addressing, and callbacks.
+ // This method is implemented in platform-specific code to
+ // create a correctly typed object. The platform code also manages
+ // deletes. To correctly manage heaps when needed, the allocate and
+ // delete should both be done from the same class/library.
+ QPID_COMMON_EXTERN static AsynchConnector* create(const Socket& s,
+ const std::string& hostname,
+ const std::string& port,
+ ConnectedCallback connCb,
+ FailedCallback failCb);
+ virtual void start(boost::shared_ptr<Poller> poller) = 0;
+ virtual void stop() {};
+ virtual void requestCallback(RequestCallback) = 0;
+protected:
+ AsynchConnector() {}
+ virtual ~AsynchConnector() {}
+};
+
+struct AsynchIOBufferBase {
+ char* bytes;
+ int32_t byteCount;
+ int32_t dataStart;
+ int32_t dataCount;
+
+ AsynchIOBufferBase(char* const b, const int32_t s) :
+ bytes(b),
+ byteCount(s),
+ dataStart(0),
+ dataCount(0)
+ {}
+
+ virtual ~AsynchIOBufferBase()
+ {}
+
+ void squish() {
+ if (dataStart != 0) {
+ ::memmove(bytes, bytes + dataStart, dataCount);
+ dataStart = 0;
+ }
+ }
+};
+
+/*
+ * Asychronous reader/writer:
+ * Reader accepts buffers to read into; reads into the provided buffers
+ * and then does a callback with the buffer and amount read. Optionally it
+ * can callback when there is something to read but no buffer to read it into.
+ *
+ * Writer accepts a buffer and queues it for writing; can also be given
+ * a callback for when writing is "idle" (ie fd is writable, but nothing
+ * to write).
+ */
+class AsynchIO {
+public:
+ typedef AsynchIOBufferBase BufferBase;
+
+ typedef boost::function2<void, AsynchIO&, BufferBase*> ReadCallback;
+ typedef boost::function1<void, AsynchIO&> EofCallback;
+ typedef boost::function1<void, AsynchIO&> DisconnectCallback;
+ typedef boost::function2<void, AsynchIO&, const Socket&> ClosedCallback;
+ typedef boost::function1<void, AsynchIO&> BuffersEmptyCallback;
+ typedef boost::function1<void, AsynchIO&> IdleCallback;
+ typedef boost::function1<void, AsynchIO&> RequestCallback;
+
+ // Call create() to allocate a new AsynchIO object with the specified
+ // callbacks. This method is implemented in platform-specific code to
+ // create a correctly typed object. The platform code also manages
+ // deletes. To correctly manage heaps when needed, the allocate and
+ // delete should both be done from the same class/library.
+ QPID_COMMON_EXTERN static AsynchIO* create(const Socket& s,
+ ReadCallback rCb,
+ EofCallback eofCb,
+ DisconnectCallback disCb,
+ ClosedCallback cCb = 0,
+ BuffersEmptyCallback eCb = 0,
+ IdleCallback iCb = 0);
+public:
+ /*
+ * Size of IO buffers - this is the maximum possible frame size + 1
+ */
+ const static uint32_t MaxBufferSize = 65536;
+
+ /*
+ * Number of IO buffers allocated - 1 for reading and 1 for writing.
+ */
+ const static uint32_t BufferCount = 2;
+
+ virtual void queueForDeletion() = 0;
+
+ virtual void start(boost::shared_ptr<Poller> poller) = 0;
+ virtual void createBuffers(uint32_t size = MaxBufferSize) = 0;
+ virtual void queueReadBuffer(BufferBase* buff) = 0;
+ virtual void unread(BufferBase* buff) = 0;
+ virtual void queueWrite(BufferBase* buff) = 0;
+ virtual void notifyPendingWrite() = 0;
+ virtual void queueWriteClose() = 0;
+ virtual bool writeQueueEmpty() = 0;
+ virtual void requestCallback(RequestCallback) = 0;
+ virtual BufferBase* getQueuedBuffer() = 0;
+
+ virtual SecuritySettings getSecuritySettings() = 0;
+
+protected:
+ // Derived class manages lifetime; must be constructed using the
+ // static create() method. Deletes not allowed from outside.
+ AsynchIO() {}
+ virtual ~AsynchIO() {}
+};
+
+}}
+
+#endif // _sys_AsynchIO
diff --git a/qpid/cpp/src/qpid/sys/AsynchIOHandler.cpp b/qpid/cpp/src/qpid/sys/AsynchIOHandler.cpp
new file mode 100644
index 0000000000..2037ba38ab
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/AsynchIOHandler.cpp
@@ -0,0 +1,235 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/AsynchIOHandler.h"
+#include "qpid/sys/AsynchIO.h"
+#include "qpid/sys/Socket.h"
+#include "qpid/sys/SecuritySettings.h"
+#include "qpid/sys/Timer.h"
+#include "qpid/framing/AMQP_HighestVersion.h"
+#include "qpid/framing/ProtocolInitiation.h"
+#include "qpid/log/Statement.h"
+
+#include <boost/bind.hpp>
+
+namespace qpid {
+namespace sys {
+
+struct ProtocolTimeoutTask : public sys::TimerTask {
+ AsynchIOHandler& handler;
+ std::string id;
+ Duration timeout;
+
+ ProtocolTimeoutTask(const std::string& i, const Duration& timeout_, AsynchIOHandler& h) :
+ TimerTask(timeout_, "ProtocolTimeout"),
+ handler(h),
+ id(i),
+ timeout(timeout_)
+ {}
+
+ void fire() {
+ // If this fires it means that we didn't negotiate the connection in the timeout period
+ // Schedule closing the connection for the io thread
+ QPID_LOG(error, "Connection " << id << " No protocol received after " << timeout
+ << ", closing");
+ handler.abort();
+ }
+};
+
+AsynchIOHandler::AsynchIOHandler(const std::string& id, ConnectionCodec::Factory* f, bool isClient0, bool nodict0) :
+ identifier(id),
+ aio(0),
+ factory(f),
+ codec(0),
+ readError(false),
+ isClient(isClient0),
+ nodict(nodict0),
+ headerSent(false)
+{}
+
+AsynchIOHandler::~AsynchIOHandler() {
+ if (codec)
+ codec->closed();
+ if (timeoutTimerTask)
+ timeoutTimerTask->cancel();
+ delete codec;
+}
+
+namespace {
+ SecuritySettings getSecuritySettings(AsynchIO* aio, bool nodict)
+ {
+ SecuritySettings settings = aio->getSecuritySettings();
+ settings.nodict = nodict;
+ return settings;
+ }
+}
+
+void AsynchIOHandler::init(qpid::sys::AsynchIO* a, qpid::sys::Timer& timer, uint32_t maxTime) {
+ aio = a;
+
+ // Start timer for this connection
+ timeoutTimerTask = new ProtocolTimeoutTask(identifier, maxTime*TIME_MSEC, *this);
+ timer.add(timeoutTimerTask);
+
+ // Give connection some buffers to use
+ aio->createBuffers();
+
+ if (isClient) {
+ codec = factory->create(*this, identifier, getSecuritySettings(aio, nodict));
+ }
+}
+
+void AsynchIOHandler::write(const framing::ProtocolInitiation& data)
+{
+ QPID_LOG(debug, "SENT [" << identifier << "]: INIT(" << data << ")");
+ AsynchIO::BufferBase* buff = aio->getQueuedBuffer();
+ assert(buff);
+ framing::Buffer out(buff->bytes, buff->byteCount);
+ data.encode(out);
+ buff->dataCount = data.encodedSize();
+ aio->queueWrite(buff);
+}
+
+void AsynchIOHandler::abort() {
+ // Don't disconnect if we're already disconnecting
+ if (!readError) {
+ aio->requestCallback(boost::bind(&AsynchIOHandler::eof, this, _1));
+ }
+ aio->queueWriteClose();
+}
+
+void AsynchIOHandler::connectionEstablished() {
+ if (timeoutTimerTask) {
+ timeoutTimerTask->cancel();
+ timeoutTimerTask = 0;
+ }
+}
+
+void AsynchIOHandler::activateOutput() {
+ aio->notifyPendingWrite();
+}
+
+void AsynchIOHandler::readbuff(AsynchIO& , AsynchIO::BufferBase* buff) {
+ if (readError) {
+ return;
+ }
+
+ size_t decoded = 0;
+ if (codec) { // Already initiated
+ try {
+ decoded = codec->decode(buff->bytes+buff->dataStart, buff->dataCount);
+ }catch(const std::exception& e){
+ QPID_LOG(error, e.what());
+ readError = true;
+ aio->queueWriteClose();
+ }
+ }else{
+ framing::Buffer in(buff->bytes+buff->dataStart, buff->dataCount);
+ framing::ProtocolInitiation protocolInit;
+ if (protocolInit.decode(in)) {
+ decoded = in.getPosition();
+
+ QPID_LOG(debug, "RECV [" << identifier << "]: INIT(" << protocolInit << ")");
+ try {
+ codec = factory->create(protocolInit.getVersion(), *this, identifier, getSecuritySettings(aio, nodict));
+ if (!codec) {
+ //TODO: may still want to revise this...
+ //send valid version header & close connection.
+ write(framing::ProtocolInitiation(factory->supportedVersion()));
+ readError = true;
+ aio->queueWriteClose();
+ } else {
+ //read any further data that may already have been sent
+ decoded += codec->decode(buff->bytes+buff->dataStart+in.getPosition(), buff->dataCount-in.getPosition());
+ }
+ } catch (const std::exception& e) {
+ QPID_LOG(error, e.what());
+ readError = true;
+ aio->queueWriteClose();
+ }
+ }
+ }
+ // TODO: unreading needs to go away, and when we can cope
+ // with multiple sub-buffers in the general buffer scheme, it will
+ if (decoded != size_t(buff->dataCount)) {
+ // Adjust buffer for used bytes and then "unread them"
+ buff->dataStart += decoded;
+ buff->dataCount -= decoded;
+ aio->unread(buff);
+ } else {
+ // Give whole buffer back to aio subsystem
+ aio->queueReadBuffer(buff);
+ }
+}
+
+void AsynchIOHandler::eof(AsynchIO& a) {
+ disconnect(a);
+ readError = true;
+ aio->queueWriteClose();
+}
+
+void AsynchIOHandler::closedSocket(AsynchIO&, const Socket& s) {
+ // If we closed with data still to send log a warning
+ if (!aio->writeQueueEmpty()) {
+ QPID_LOG(warning, "CLOSING [" << identifier << "] unsent data (probably due to client disconnect)");
+ }
+ delete &s;
+ aio->queueForDeletion();
+ delete this;
+}
+
+void AsynchIOHandler::disconnect(AsynchIO&) {
+ QPID_LOG(debug, "DISCONNECTED [" << identifier << "]");
+ if (codec) codec->closed();
+}
+
+// Notifications
+void AsynchIOHandler::nobuffs(AsynchIO&) {
+}
+
+void AsynchIOHandler::idle(AsynchIO&){
+ if (isClient && !headerSent) {
+ write(framing::ProtocolInitiation(codec->getVersion()));
+ headerSent = true;
+ return;
+ }
+ if (codec == 0) return;
+ if (!codec->canEncode()) {
+ return;
+ }
+ AsynchIO::BufferBase* buff = aio->getQueuedBuffer();
+ if (buff) {
+ try {
+ size_t encoded=codec->encode(buff->bytes, buff->byteCount);
+ buff->dataCount = encoded;
+ aio->queueWrite(buff);
+ if (!codec->isClosed()) {
+ return;
+ }
+ } catch (const std::exception& e) {
+ QPID_LOG(error, e.what());
+ }
+ }
+ readError = true;
+ aio->queueWriteClose();
+}
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/AsynchIOHandler.h b/qpid/cpp/src/qpid/sys/AsynchIOHandler.h
new file mode 100644
index 0000000000..698ac5fc9c
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/AsynchIOHandler.h
@@ -0,0 +1,82 @@
+#ifndef _sys_AsynchIOHandler_h
+#define _sys_AsynchIOHandler_h
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/OutputControl.h"
+#include "qpid/sys/ConnectionCodec.h"
+#include "qpid/sys/AtomicValue.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/CommonImportExport.h"
+
+#include <boost/intrusive_ptr.hpp>
+
+namespace qpid {
+
+namespace framing {
+ class ProtocolInitiation;
+}
+
+namespace sys {
+
+class AsynchIO;
+struct AsynchIOBufferBase;
+class Socket;
+class Timer;
+class TimerTask;
+
+class AsynchIOHandler : public OutputControl {
+ std::string identifier;
+ AsynchIO* aio;
+ ConnectionCodec::Factory* factory;
+ ConnectionCodec* codec;
+ bool readError;
+ bool isClient;
+ bool nodict;
+ bool headerSent;
+ boost::intrusive_ptr<sys::TimerTask> timeoutTimerTask;
+
+ void write(const framing::ProtocolInitiation&);
+
+ public:
+ QPID_COMMON_EXTERN AsynchIOHandler(const std::string& id, qpid::sys::ConnectionCodec::Factory* f, bool isClient, bool nodict);
+ QPID_COMMON_EXTERN ~AsynchIOHandler();
+ QPID_COMMON_EXTERN void init(AsynchIO* a, Timer& timer, uint32_t maxTime);
+
+ // Output side
+ QPID_COMMON_EXTERN void abort();
+ QPID_COMMON_EXTERN void connectionEstablished();
+ QPID_COMMON_EXTERN void activateOutput();
+
+ // Input side
+ QPID_COMMON_EXTERN void readbuff(AsynchIO& aio, AsynchIOBufferBase* buff);
+ QPID_COMMON_EXTERN void eof(AsynchIO& aio);
+ QPID_COMMON_EXTERN void disconnect(AsynchIO& aio);
+
+ // Notifications
+ QPID_COMMON_EXTERN void nobuffs(AsynchIO& aio);
+ QPID_COMMON_EXTERN void idle(AsynchIO& aio);
+ QPID_COMMON_EXTERN void closedSocket(AsynchIO& aio, const Socket& s);
+};
+
+}} // namespace qpid::sys
+
+#endif // _sys_AsynchIOHandler_h
diff --git a/qpid/cpp/src/qpid/sys/AtomicCount.h b/qpid/cpp/src/qpid/sys/AtomicCount.h
new file mode 100644
index 0000000000..94580c61f3
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/AtomicCount.h
@@ -0,0 +1,52 @@
+#ifndef _posix_AtomicCount_h
+#define _posix_AtomicCount_h
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <boost/detail/atomic_count.hpp>
+#include "qpid/sys/ScopedIncrement.h"
+
+namespace qpid {
+namespace sys {
+
+/**
+ * Atomic counter.
+ */
+class AtomicCount {
+ public:
+ typedef ::qpid::sys::ScopedDecrement<AtomicCount> ScopedDecrement;
+ typedef ::qpid::sys::ScopedIncrement<AtomicCount> ScopedIncrement;
+
+ AtomicCount(long value = 0) : count(value) {}
+
+ void operator++() { ++count ; }
+
+ long operator--() { return --count; }
+
+ operator long() const { return count; }
+
+ private:
+ boost::detail::atomic_count count;
+};
+
+
+}}
+
+
+#endif // _posix_AtomicCount_h
diff --git a/qpid/cpp/src/qpid/sys/AtomicValue.h b/qpid/cpp/src/qpid/sys/AtomicValue.h
new file mode 100644
index 0000000000..bf995f991e
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/AtomicValue.h
@@ -0,0 +1,39 @@
+#ifndef QPID_SYS_ATOMICVALUE_H
+#define QPID_SYS_ATOMICVALUE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+// Have to check for clang before gcc as clang pretends to be gcc too
+#if defined( __clang__ )
+// Use the clang doesn't support atomic builtins for 64 bit values, so use the slow versions
+#include "qpid/sys/AtomicValue_mutex.h"
+
+#elif defined( __GNUC__ ) && __GNUC__ >= 4 && ( defined( __i686__ ) || defined( __x86_64__ ) )
+// Use the Gnu C built-in atomic operations if compiling with gcc on a suitable platform.
+#include "qpid/sys/AtomicValue_gcc.h"
+
+#else
+// Fall-back to mutex locked operations if we don't have atomic ops.
+#include "qpid/sys/AtomicValue_mutex.h"
+#endif
+
+#endif /*!QPID_SYS_ATOMICVALUE_GCC_H*/
diff --git a/qpid/cpp/src/qpid/sys/AtomicValue_gcc.h b/qpid/cpp/src/qpid/sys/AtomicValue_gcc.h
new file mode 100644
index 0000000000..724bae422e
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/AtomicValue_gcc.h
@@ -0,0 +1,71 @@
+#ifndef QPID_SYS_ATOMICVALUE_GCC_H
+#define QPID_SYS_ATOMICVALUE_GCC_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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 !defined(QPID_SYS_ATOMICVALUE_H)
+#error "This file should only be included via AtomicValue.h."
+#endif
+
+namespace qpid {
+namespace sys {
+
+/**
+ * Atomic value of type T. T must be an integral type of size 1,2,4 or 8 bytes.
+ * All operations are atomic and preform a full memory barrier unless otherwise noted.
+ */
+template <class T>
+class AtomicValue
+{
+ public:
+ AtomicValue(T init=0) : value(init) {}
+
+ // Not atomic. Don't call concurrently with atomic ops.
+ AtomicValue<T>& operator=(T newValue) { value = newValue; return *this; }
+
+ // Update and return new value.
+ inline T operator+=(T n) { return __sync_add_and_fetch(&value, n); }
+ inline T operator-=(T n) { return __sync_sub_and_fetch(&value, n); }
+ inline T operator++() { return *this += 1; }
+ inline T operator--() { return *this -= 1; }
+
+ // Update and return old value.
+ inline T fetchAndAdd(T n) { return __sync_fetch_and_add(&value, n); }
+ inline T fetchAndSub(T n) { return __sync_fetch_and_sub(&value, n); }
+ inline T operator++(int) { return fetchAndAdd(1); }
+ inline T operator--(int) { return fetchAndSub(1); }
+
+ /** If current value == testval then set to newval. Returns the old value. */
+ T valueCompareAndSwap(T testval, T newval) { return __sync_val_compare_and_swap(&value, testval, newval); }
+
+ /** If current value == testval then set to newval. Returns true if the swap was performed. */
+ bool boolCompareAndSwap(T testval, T newval) { return __sync_bool_compare_and_swap(&value, testval, newval); }
+
+ T get() const { return const_cast<AtomicValue<T>*>(this)->fetchAndAdd(static_cast<T>(0)); }
+
+ private:
+ T value;
+};
+
+}} // namespace qpid::sys
+
+#endif /*!QPID_SYS_ATOMICVALUE_GCC_H*/
diff --git a/qpid/cpp/src/qpid/sys/AtomicValue_mutex.h b/qpid/cpp/src/qpid/sys/AtomicValue_mutex.h
new file mode 100644
index 0000000000..e4d433e7f5
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/AtomicValue_mutex.h
@@ -0,0 +1,83 @@
+#ifndef QPID_SYS_ATOMICVALUE_MUTEX_H
+#define QPID_SYS_ATOMICVALUE_MUTEX_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT 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 !defined(QPID_SYS_ATOMICVALUE_H)
+#error "This file should only be included via AtomicValue.h."
+#endif
+
+#include "qpid/sys/Mutex.h"
+
+namespace qpid {
+namespace sys {
+
+/**
+ * Atomic value of type T. T must be an integral type of size 1,2,4 or 8 bytes.
+ * All operations are atomic and preform a full memory barrier unless otherwise noted.
+ */
+template <class T>
+class AtomicValue
+{
+ public:
+ AtomicValue(T init=0) : value(init) {}
+
+ // Update and return new value.
+ inline T operator+=(T n) { Lock l(lock); return value += n; }
+ inline T operator-=(T n) { Lock l(lock); return value -= n; }
+ inline T operator++() { return *this += 1; }
+ inline T operator--() { return *this -= 1; }
+
+ // Update and return old value.
+ inline T fetchAndAdd(T n) { Lock l(lock); T old=value; value += n; return old; }
+ inline T fetchAndSub(T n) { Lock l(lock); T old=value; value -= n; return old; }
+ inline T operator++(int) { return fetchAndAdd(1); }
+ inline T operator--(int) { return fetchAndSub(1); }
+
+ AtomicValue& operator=(T newval) { Lock l(lock); value = newval; return *this; }
+
+ /** If current value == testval then set to newval. Returns the old value. */
+ T valueCompareAndSwap(T testval, T newval) {
+ Lock l(lock);
+ T old=value;
+ if (value == testval) value = newval;
+ return old;
+ }
+
+ /** If current value == testval then set to newval. Returns true if the swap was performed. */
+ bool boolCompareAndSwap(T testval, T newval) {
+ Lock l(lock);
+ if (value == testval) { value = newval; return true; }
+ return false;
+ }
+
+ T get() const { Lock l(lock); return value; }
+
+ private:
+ typedef Mutex::ScopedLock Lock;
+ T value;
+ mutable Mutex lock;
+};
+
+}} // namespace qpid::sys
+
+#endif /*!QPID_SYS_ATOMICVALUE_MUTEX_H*/
diff --git a/qpid/cpp/src/qpid/sys/BlockingQueue.h b/qpid/cpp/src/qpid/sys/BlockingQueue.h
new file mode 100644
index 0000000000..ca6b529930
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/BlockingQueue.h
@@ -0,0 +1,129 @@
+#ifndef QPID_SYS_BLOCKINGQUEUE_H
+#define QPID_SYS_BLOCKINGQUEUE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/Waitable.h"
+
+#include <queue>
+
+namespace qpid {
+namespace sys {
+
+/**
+ * A simple blocking queue template
+ */
+template <class T>
+class BlockingQueue
+{
+ mutable sys::Waitable waitable;
+ std::queue<T> queue;
+
+public:
+ BlockingQueue() {}
+ ~BlockingQueue() { close(); }
+
+ /** Pop from the queue, block up to timeout if empty.
+ *@param result Set to value popped from queue.
+ *@param timeout Defaults to infinite.
+ *@return true if result was set, false if queue empty after timeout.
+ */
+ bool pop(T& result, Duration timeout=TIME_INFINITE) {
+ Mutex::ScopedLock l(waitable);
+ {
+ Waitable::ScopedWait w(waitable);
+ if (timeout == TIME_INFINITE) {
+ while (queue.empty()) waitable.wait();
+ } else if (timeout) {
+ AbsTime deadline(now(),timeout);
+ while (queue.empty() && deadline > now()) waitable.wait(deadline);
+ } else {
+ //ensure zero timeout pop does not miss the fact that
+ //queue is closed
+ waitable.checkException();
+ }
+ }
+ if (queue.empty()) return false;
+ result = queue.front();
+ queue.pop();
+ if (!queue.empty())
+ waitable.notify(); // Notify another waiter.
+ return true;
+ }
+
+ T pop(Duration timeout=TIME_INFINITE) {
+ T result;
+ bool ok = pop(result, timeout);
+ if (!ok)
+ throw Exception("Timed out waiting on a blocking queue");
+ return result;
+ }
+
+ /** Push a value onto the queue.
+ * Note it is not an error to push onto a closed queue.
+ */
+ void push(const T& t) {
+ Mutex::ScopedLock l(waitable);
+ queue.push(t);
+ waitable.notify(); // Notify a waiter.
+ }
+
+ /**
+ * Close the queue.
+ *@ex exception to throw to waiting threads. ClosedException by default.
+ */
+ void close(const ExceptionHolder& ex=ExceptionHolder(new ClosedException()))
+ {
+ Mutex::ScopedLock l(waitable);
+ if (!waitable.hasException()) {
+ waitable.setException(ex);
+ waitable.notifyAll();
+ waitable.waitWaiters(); // Ensure no threads are still waiting.
+ }
+ }
+
+ /** Open a closed queue. */
+ void open() {
+ Mutex::ScopedLock l(waitable);
+ waitable.resetException();
+ }
+
+ bool isClosed() const {
+ Mutex::ScopedLock l(waitable);
+ return waitable.hasException();
+ }
+
+ bool empty() const {
+ Mutex::ScopedLock l(waitable);
+ return queue.empty();
+ }
+ size_t size() const {
+ Mutex::ScopedLock l(waitable);
+ return queue.size();
+ }
+};
+
+}}
+
+
+
+#endif /*!QPID_SYS_BLOCKINGQUEUE_H*/
diff --git a/qpid/cpp/src/qpid/sys/Codec.h b/qpid/cpp/src/qpid/sys/Codec.h
new file mode 100644
index 0000000000..e398403e47
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/Codec.h
@@ -0,0 +1,52 @@
+#ifndef QPID_SYS_CODEC_H
+#define QPID_SYS_CODEC_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <cstddef>
+
+namespace qpid {
+namespace sys {
+
+/**
+ * Generic codec interface
+ */
+class Codec
+{
+ public:
+ virtual ~Codec() {}
+
+ /** Decode from buffer, return number of bytes decoded.
+ * @return may be less than size if there was incomplete
+ * data at the end of the buffer.
+ */
+ virtual std::size_t decode(const char* buffer, std::size_t size) = 0;
+
+
+ /** Encode into buffer, return number of bytes encoded */
+ virtual std::size_t encode(char* buffer, std::size_t size) = 0;
+
+ /** Return true if we have data to encode */
+ virtual bool canEncode() = 0;
+};
+}} // namespace qpid::sys
+
+#endif /*!QPID_SYS_CODEC_H*/
diff --git a/qpid/cpp/src/qpid/sys/Condition.h b/qpid/cpp/src/qpid/sys/Condition.h
new file mode 100644
index 0000000000..9be4b357fe
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/Condition.h
@@ -0,0 +1,33 @@
+#ifndef _sys_Condition_h
+#define _sys_Condition_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#ifdef USE_APR_PLATFORM
+#include "apr/Condition.h"
+#elif defined (_WIN32)
+#include "windows/Condition.h"
+#else
+#include "posix/Condition.h"
+#endif
+
+#endif /*!_sys_Condition_h*/
diff --git a/qpid/cpp/src/qpid/sys/ConnectionCodec.h b/qpid/cpp/src/qpid/sys/ConnectionCodec.h
new file mode 100644
index 0000000000..8b5b69cdb4
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/ConnectionCodec.h
@@ -0,0 +1,70 @@
+#ifndef QPID_SYS_CONNECTION_CODEC_H
+#define QPID_SYS_CONNECTION_CODEC_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/sys/Codec.h"
+#include "qpid/framing/ProtocolVersion.h"
+
+namespace qpid {
+
+namespace sys {
+
+class InputHandlerFactory;
+class OutputControl;
+struct SecuritySettings;
+
+/**
+ * Interface of coder/decoder for a connection of a specific protocol
+ * version.
+ */
+class ConnectionCodec : public Codec {
+ public:
+ virtual ~ConnectionCodec() {}
+
+ /** Network connection was closed from other end. */
+ virtual void closed() = 0;
+
+ virtual bool isClosed() const = 0;
+
+ virtual framing::ProtocolVersion getVersion() const = 0;
+
+ struct Factory {
+ virtual ~Factory() {}
+
+ /** Return 0 if version unknown */
+ virtual ConnectionCodec* create(
+ const framing::ProtocolVersion&, OutputControl&, const std::string& id,
+ const SecuritySettings&
+ ) = 0;
+
+ /** Return "preferred" codec for outbound connections. */
+ virtual ConnectionCodec* create(
+ OutputControl&, const std::string& id, const SecuritySettings&
+ ) = 0;
+
+ virtual framing::ProtocolVersion supportedVersion() const = 0;
+ };
+};
+
+}} // namespace qpid::sys
+
+#endif /*!QPID_SYS_CONNECTION_CODEC_H*/
diff --git a/qpid/cpp/src/qpid/sys/ConnectionInputHandler.h b/qpid/cpp/src/qpid/sys/ConnectionInputHandler.h
new file mode 100644
index 0000000000..f6fcdb7479
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/ConnectionInputHandler.h
@@ -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.
+ *
+ */
+#ifndef _ConnectionInputHandler_
+#define _ConnectionInputHandler_
+
+#include "qpid/framing/InputHandler.h"
+#include "qpid/sys/OutputTask.h"
+
+namespace qpid {
+namespace sys {
+
+
+/**
+ * ConnectionInputHandler provides methods to process incoming frames
+ * using InputHandler::receive() and to generate outgoing messages in
+ * OutputTask::doOutput()
+ *
+ */
+
+ class ConnectionInputHandler :
+ public qpid::framing::InputHandler,
+ public OutputTask
+ {
+ public:
+
+ virtual void closed() = 0;
+ };
+
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/sys/ConnectionInputHandlerFactory.h b/qpid/cpp/src/qpid/sys/ConnectionInputHandlerFactory.h
new file mode 100644
index 0000000000..9bb7e13686
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/ConnectionInputHandlerFactory.h
@@ -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.
+ *
+ */
+#ifndef _ConnectionInputHandlerFactory_
+#define _ConnectionInputHandlerFactory_
+
+#include <boost/noncopyable.hpp>
+#include <string>
+
+namespace qpid {
+namespace sys {
+
+class ConnectionOutputHandler;
+class ConnectionInputHandler;
+
+/**
+ * Callback interface used by the Acceptor to
+ * create a ConnectionInputHandler for each new connection.
+ */
+class ConnectionInputHandlerFactory : private boost::noncopyable
+{
+ public:
+ /**
+ *@param out handler for connection output.
+ *@param id identify the connection for management purposes.
+ */
+ virtual ConnectionInputHandler* create(ConnectionOutputHandler* out,
+ const std::string& id,
+ bool isClient) = 0;
+
+ virtual ~ConnectionInputHandlerFactory(){}
+};
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/sys/ConnectionOutputHandler.h b/qpid/cpp/src/qpid/sys/ConnectionOutputHandler.h
new file mode 100644
index 0000000000..3b1440d613
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/ConnectionOutputHandler.h
@@ -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.
+ *
+ */
+#ifndef _ConnectionOutputHandler_
+#define _ConnectionOutputHandler_
+
+#include "qpid/framing/FrameHandler.h"
+#include "qpid/sys/OutputControl.h"
+
+namespace qpid {
+namespace sys {
+
+/**
+ * Provides the output handler associated with a connection.
+ */
+class ConnectionOutputHandler : public virtual qpid::framing::FrameHandler, public OutputControl
+{
+ public:
+ virtual void close() = 0;
+};
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/sys/CopyOnWriteArray.h b/qpid/cpp/src/qpid/sys/CopyOnWriteArray.h
new file mode 100644
index 0000000000..41384fc5a4
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/CopyOnWriteArray.h
@@ -0,0 +1,162 @@
+#ifndef QPID_SYS_COPYONWRITEARRAY_H
+#define QPID_SYS_COPYONWRITEARRAY_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/Mutex.h"
+#include <algorithm>
+#include <vector>
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace sys {
+
+/**
+ * An array that copies on adding/removing element allowing lock-free
+ * iteration.
+ */
+template <class T>
+class CopyOnWriteArray
+{
+public:
+ typedef boost::shared_ptr<const std::vector<T> > ConstPtr;
+
+ CopyOnWriteArray() {}
+ CopyOnWriteArray(const CopyOnWriteArray& c) : array(c.array) {}
+
+ bool empty()
+ {
+ Mutex::ScopedLock l(lock);
+ return array ? array->empty() : true;
+ }
+
+ void add(T& t)
+ {
+ Mutex::ScopedLock l(lock);
+ ArrayPtr copy(array ? new std::vector<T>(*array) : new std::vector<T>());
+ copy->push_back(t);
+ array = copy;
+ }
+
+ bool remove(T& t)
+ {
+ Mutex::ScopedLock l(lock);
+ if (array && std::find(array->begin(), array->end(), t) != array->end()) {
+ ArrayPtr copy(new std::vector<T>(*array));
+ copy->erase(std::find(copy->begin(), copy->end(), t));
+ array = copy;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ bool clear()
+ {
+ Mutex::ScopedLock l(lock);
+ if (array && !array->empty()) {
+ ArrayPtr copy;
+ array = copy;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ template <class F>
+ bool add_unless(T& t, F f)
+ {
+ Mutex::ScopedLock l(lock);
+ if (array && std::find_if(array->begin(), array->end(), f) != array->end()) {
+ return false;
+ } else {
+ ArrayPtr copy(array ? new std::vector<T>(*array) : new std::vector<T>());
+ copy->push_back(t);
+ array = copy;
+ return true;
+ }
+ }
+
+ template <class F>
+ bool remove_if(F f)
+ {
+ Mutex::ScopedLock l(lock);
+ if (array && std::find_if(array->begin(), array->end(), f) != array->end()) {
+ ArrayPtr copy(new std::vector<T>(*array));
+ copy->erase(std::remove_if(copy->begin(), copy->end(), f), copy->end());
+ array = copy;
+ return true;
+ }
+ return false;
+ }
+
+ template <class TestFn, class ModifierFn>
+ bool modify_if(TestFn f, ModifierFn & m)
+ {
+ if (!array)
+ return false;
+ {
+ Mutex::ScopedLock l(lock);
+ if (std::find_if(array->begin(), array->end(), f) != array->end())
+ {
+ ArrayPtr copy(new std::vector<T>(*array));
+ m(*std::find_if(copy->begin(), copy->end(), f));
+ array = copy;
+ return true;
+ }
+ }
+ return false;
+ }
+
+ template <class F>
+ F for_each(F f)
+ {
+ ArrayPtr a;
+ {
+ Mutex::ScopedLock l(lock);
+ a = array;
+ }
+ if (!a) return f;
+ return std::for_each(a->begin(), a->end(), f);
+ }
+
+ ConstPtr snapshot()
+ {
+ ConstPtr a;
+ {
+ Mutex::ScopedLock l(lock);
+ a = array;
+ }
+ return a;
+ }
+
+private:
+ typedef boost::shared_ptr< std::vector<T> > ArrayPtr;
+ Mutex lock;
+ ArrayPtr array;
+};
+
+}}
+
+
+
+#endif /*!QPID_SYS_COPYONWRITEARRAY_H*/
diff --git a/qpid/cpp/src/qpid/sys/DeletionManager.h b/qpid/cpp/src/qpid/sys/DeletionManager.h
new file mode 100644
index 0000000000..c1fea19f30
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/DeletionManager.h
@@ -0,0 +1,162 @@
+#ifndef _sys_DeletionManager_h
+#define _sys_DeletionManager_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <vector>
+#include <algorithm>
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace sys {
+
+struct deleter
+{
+ template <typename T>
+ void operator()(T* ptr){ delete ptr;}
+};
+
+/**
+ * DeletionManager keeps track of handles that need to be deleted but may still be
+ * in use by one of the threads concurrently.
+ *
+ * The mode of operation is like this:
+ * - When we want to delete but we might still be using the handle we
+ * * Transfer ownership of the handle to this class
+ * * Mark the handle as (potentially) in use by every thread
+ * - Then subsequently at points where the thread code knows it isn't
+ * using any handles it declares that it is using no handles
+ * - When the last thread declares no use of a handle it automatically
+ * gets deleted by the shared_ptr implementation
+ *
+ * The class only has static members and data and so can only be used once for
+ * any particular handle type
+ */
+template <typename H>
+class DeletionManager
+{
+ struct ThreadStatus;
+
+public:
+ // Mark every thread as using the handle - it will be deleted
+ // below after every thread marks the handle as unused
+ static void markForDeletion(H* handle) {
+ allThreadsStatuses.addHandle(shared_ptr(handle));
+ }
+
+ // Mark this thread is not using any handle -
+ // handles get deleted here when no one else
+ // is using them either
+ static void markAllUnusedInThisThread() {
+ ThreadStatus* threadStatus = getThreadStatus();
+ ScopedLock<Mutex> l(threadStatus->lock);
+
+ // The actual deletions will happen here when all the shared_ptr
+ // ref counts hit 0 (that is when every thread marks the handle unused)
+ threadStatus->handles.clear();
+ }
+
+ static void destroyThreadState() {
+ ThreadStatus* threadStatus = getThreadStatus();
+ allThreadsStatuses.delThreadStatus(threadStatus);
+ delete threadStatus;
+ threadStatus = 0;
+ }
+
+private:
+
+ static ThreadStatus*& getThreadStatus() {
+ static __thread ThreadStatus* threadStatus = 0;
+
+ // Thread local vars can't be dynamically constructed so we need
+ // to check whether we've made it yet and construct it if not
+ // (no locking necessary for the check as it's thread local!)
+ if (!threadStatus) {
+ threadStatus = new ThreadStatus;
+ allThreadsStatuses.addThreadStatus(threadStatus);
+ }
+
+ return threadStatus;
+ }
+
+ typedef boost::shared_ptr<H> shared_ptr;
+
+ // In theory we know that we never need more handles than the number of
+ // threads runnning so we could use a fixed size array. However at this point
+ // in the code we don't have easy access to this information.
+ struct ThreadStatus
+ {
+ Mutex lock;
+ std::vector<shared_ptr> handles;
+ };
+
+ class AllThreadsStatuses
+ {
+ Mutex lock;
+ std::vector<ThreadStatus*> statuses;
+
+ struct handleAdder
+ {
+ shared_ptr handle;
+
+ handleAdder(shared_ptr h): handle(h) {}
+
+ void operator()(ThreadStatus* ptr) {
+ ScopedLock<Mutex> l(ptr->lock);
+ ptr->handles.push_back(handle);
+ }
+ };
+
+ public:
+ // Need this to be able to do static initialisation
+ explicit AllThreadsStatuses(int) {}
+
+ ~AllThreadsStatuses() {
+ ScopedLock<Mutex> l(lock);
+ std::for_each(statuses.begin(), statuses.end(), deleter());
+ }
+
+ void addThreadStatus(ThreadStatus* t) {
+ ScopedLock<Mutex> l(lock);
+ statuses.push_back(t);
+ }
+
+ void delThreadStatus(ThreadStatus* t) {
+ ScopedLock<Mutex> l(lock);
+ typename std::vector<ThreadStatus*>::iterator it =
+ std::find(statuses.begin(),statuses.end(), t);
+ if (it != statuses.end()) {
+ statuses.erase(it);
+ }
+ }
+
+ void addHandle(shared_ptr h) {
+ ScopedLock<Mutex> l(lock);
+ std::for_each(statuses.begin(), statuses.end(), handleAdder(h));
+ }
+ };
+
+ static AllThreadsStatuses allThreadsStatuses;
+};
+
+}}
+#endif // _sys_DeletionManager_h
diff --git a/qpid/cpp/src/qpid/sys/DispatchHandle.cpp b/qpid/cpp/src/qpid/sys/DispatchHandle.cpp
new file mode 100644
index 0000000000..5d6fc4e72f
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/DispatchHandle.cpp
@@ -0,0 +1,352 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/DispatchHandle.h"
+#include "qpid/log/Statement.h"
+
+#include <algorithm>
+
+#include <boost/cast.hpp>
+
+#include <assert.h>
+
+namespace qpid {
+namespace sys {
+
+DispatchHandle::DispatchHandle(const IOHandle& h, Callback rCb, Callback wCb, Callback dCb) :
+ PollerHandle(h),
+ readableCallback(rCb),
+ writableCallback(wCb),
+ disconnectedCallback(dCb),
+ state(IDLE)
+{
+}
+
+
+DispatchHandle::~DispatchHandle() {
+}
+
+void DispatchHandle::startWatch(Poller::shared_ptr poller0) {
+ bool r = readableCallback;
+ bool w = writableCallback;
+
+ ScopedLock<Mutex> lock(stateLock);
+ assert(state == IDLE);
+
+ poller = poller0;
+ poller->registerHandle(*this);
+ state = WAITING;
+ Poller::Direction dir = r ?
+ ( w ? Poller::INOUT : Poller::INPUT ) :
+ ( w ? Poller::OUTPUT : Poller::NONE );
+ poller->monitorHandle(*this, dir);
+}
+
+void DispatchHandle::rewatch() {
+ bool r = readableCallback;
+ bool w = writableCallback;
+ if (!r && !w) {
+ return;
+ }
+ Poller::Direction dir = r ?
+ ( w ? Poller::INOUT : Poller::INPUT ) :
+ ( w ? Poller::OUTPUT : Poller::NONE );
+
+ ScopedLock<Mutex> lock(stateLock);
+ switch(state) {
+ case IDLE:
+ case STOPPING:
+ case DELETING:
+ return;
+ default:
+ break;
+ }
+ assert(poller);
+ poller->monitorHandle(*this, dir);
+}
+
+void DispatchHandle::rewatchRead() {
+ if (!readableCallback) {
+ return;
+ }
+
+ ScopedLock<Mutex> lock(stateLock);
+ switch(state) {
+ case IDLE:
+ case STOPPING:
+ case DELETING:
+ return;
+ default:
+ break;
+ }
+ assert(poller);
+ poller->monitorHandle(*this, Poller::INPUT);
+}
+
+void DispatchHandle::rewatchWrite() {
+ if (!writableCallback) {
+ return;
+ }
+
+ ScopedLock<Mutex> lock(stateLock);
+ switch(state) {
+ case IDLE:
+ case STOPPING:
+ case DELETING:
+ return;
+ default:
+ break;
+ }
+ assert(poller);
+ poller->monitorHandle(*this, Poller::OUTPUT);
+}
+
+void DispatchHandle::unwatchRead() {
+ if (!readableCallback) {
+ return;
+ }
+
+ ScopedLock<Mutex> lock(stateLock);
+ switch(state) {
+ case IDLE:
+ case STOPPING:
+ case DELETING:
+ return;
+ default:
+ break;
+ }
+ assert(poller);
+ poller->unmonitorHandle(*this, Poller::INPUT);
+}
+
+void DispatchHandle::unwatchWrite() {
+ if (!writableCallback) {
+ return;
+ }
+
+ ScopedLock<Mutex> lock(stateLock);
+ switch(state) {
+ case IDLE:
+ case STOPPING:
+ case DELETING:
+ return;
+ default:
+ break;
+ }
+ assert(poller);
+ poller->unmonitorHandle(*this, Poller::OUTPUT);
+}
+
+void DispatchHandle::unwatch() {
+ ScopedLock<Mutex> lock(stateLock);
+ switch(state) {
+ case IDLE:
+ case STOPPING:
+ case DELETING:
+ return;
+ default:
+ break;
+ }
+ assert(poller);
+ poller->unmonitorHandle(*this, Poller::INOUT);
+}
+
+void DispatchHandle::stopWatch() {
+ ScopedLock<Mutex> lock(stateLock);
+ switch (state) {
+ case IDLE:
+ assert(state != IDLE);
+ return;
+ case STOPPING:
+ assert(state != STOPPING);
+ return;
+ case CALLING:
+ state = STOPPING;
+ break;
+ case WAITING:
+ state = IDLE;
+ break;
+ case DELETING:
+ return;
+ }
+ assert(poller);
+ poller->unregisterHandle(*this);
+ poller.reset();
+}
+
+// If we are in the IDLE/STOPPING state we can't do the callback as we've
+// not/no longer got the fd registered in any poller
+void DispatchHandle::call(Callback iCb) {
+ assert(iCb);
+ ScopedLock<Mutex> lock(stateLock);
+ switch (state) {
+ case IDLE:
+ case STOPPING:
+ case DELETING:
+ return;
+ default:
+ interruptedCallbacks.push(iCb);
+ assert(poller);
+ (void) poller->interrupt(*this);
+ }
+}
+
+// The slightly strange switch structure
+// is to ensure that the lock is released before
+// we do the delete
+void DispatchHandle::doDelete() {
+ {
+ ScopedLock<Mutex> lock(stateLock);
+ // Ensure that we're no longer watching anything
+ switch (state) {
+ case IDLE:
+ state = DELETING;
+ break;
+ case STOPPING:
+ state = DELETING;
+ return;
+ case WAITING:
+ state = DELETING;
+ assert(poller);
+ (void) poller->interrupt(*this);
+ poller->unregisterHandle(*this);
+ return;
+ case CALLING:
+ state = DELETING;
+ assert(poller);
+ poller->unregisterHandle(*this);
+ return;
+ case DELETING:
+ return;
+ }
+ }
+ // If we're IDLE we can do this right away
+ delete this;
+}
+
+void DispatchHandle::processEvent(Poller::EventType type) {
+
+ // Phase I
+ {
+ ScopedLock<Mutex> lock(stateLock);
+
+ switch(state) {
+ case IDLE:
+ // Can get here if a non connection thread stops watching
+ // whilst we were stuck in the above lock
+ return;
+ case WAITING:
+ state = CALLING;
+ break;
+ case CALLING:
+ assert(state!=CALLING);
+ return;
+ case STOPPING:
+ assert(state!=STOPPING);
+ return;
+ case DELETING:
+ // Need to make sure we clean up any pending callbacks in this case
+ std::swap(callbacks, interruptedCallbacks);
+ goto saybyebye;
+ }
+
+ std::swap(callbacks, interruptedCallbacks);
+ }
+
+ // Do callbacks - whilst we are doing the callbacks we are prevented from processing
+ // the same handle until we re-enable it. To avoid rentering the callbacks for a single
+ // handle re-enabling in the callbacks is actually deferred until they are complete.
+ try {
+ switch (type) {
+ case Poller::READABLE:
+ readableCallback(*this);
+ break;
+ case Poller::WRITABLE:
+ writableCallback(*this);
+ break;
+ case Poller::READ_WRITABLE:
+ readableCallback(*this);
+ writableCallback(*this);
+ break;
+ case Poller::DISCONNECTED:
+ if (disconnectedCallback) {
+ disconnectedCallback(*this);
+ }
+ break;
+ case Poller::INTERRUPTED:
+ {
+ // We'll actually do the interrupt below
+ }
+ break;
+ default:
+ assert(false);
+ }
+
+ // If we have any callbacks do them now -
+ // (because we use a copy from before the previous callbacks we won't
+ // do anything yet that was just added)
+ while (callbacks.size() > 0) {
+ {
+ ScopedLock<Mutex> lock(stateLock);
+ switch (state) {
+ case DELETING:
+ goto finishcallbacks;
+ default:
+ break;
+ }
+ }
+ Callback cb = callbacks.front();
+ assert(cb);
+ cb(*this);
+ callbacks.pop();
+ }
+ } catch (std::exception& e) {
+ // One of the callbacks threw an exception - that's not allowed
+ QPID_LOG(error, "Caught exception in state: " << state << " with event: " << type << ": " << e.what());
+ // It would be nice to clean up and delete ourselves here, but we can't
+ }
+
+finishcallbacks:
+ {
+ ScopedLock<Mutex> lock(stateLock);
+ switch (state) {
+ case IDLE:
+ assert(state!=IDLE);
+ return;
+ case STOPPING:
+ state = IDLE;
+ return;
+ case WAITING:
+ assert(state!=WAITING);
+ return;
+ case CALLING:
+ state = WAITING;
+ return;
+ case DELETING:
+ break;
+ }
+ }
+
+saybyebye:
+ delete this;
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/sys/DispatchHandle.h b/qpid/cpp/src/qpid/sys/DispatchHandle.h
new file mode 100644
index 0000000000..115a3c44f7
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/DispatchHandle.h
@@ -0,0 +1,150 @@
+#ifndef _sys_DispatchHandle_h
+#define _sys_DispatchHandle_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/Poller.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/CommonImportExport.h"
+#include <boost/function.hpp>
+
+#include <queue>
+
+namespace qpid {
+namespace sys {
+
+class DispatchHandleRef;
+/**
+ * In order to have your own handle (file descriptor on Unix) watched by the poller
+ * you need to:
+ *
+ * - Subclass IOHandle, in the constructor supply an appropriate
+ * IOHandlerPrivate object for the platform.
+ *
+ * - Construct a DispatchHandle passing it your IOHandle and
+ * callback functions for read, write and disconnect events.
+ *
+ * - Ensure the DispatchHandle is not deleted until the poller is no longer using it.
+ * TODO: astitcher document DispatchHandleRef to simplify this.
+ *
+ * When an event occurs on the handle, the poller calls the relevant callback and
+ * stops watching that handle. Your callback can call rewatch() or related functions
+ * to re-enable polling.
+ */
+class DispatchHandle : public PollerHandle {
+ friend class DispatchHandleRef;
+public:
+ typedef boost::function1<void, DispatchHandle&> Callback;
+ typedef std::queue<Callback> CallbackQueue;
+
+private:
+ Callback readableCallback;
+ Callback writableCallback;
+ Callback disconnectedCallback;
+ CallbackQueue interruptedCallbacks;
+ CallbackQueue callbacks; // Double buffer
+ Poller::shared_ptr poller;
+ Mutex stateLock;
+ enum {
+ IDLE,
+ STOPPING,
+ WAITING,
+ CALLING,
+ DELETING
+ } state;
+
+public:
+ /**
+ * Provide a handle to poll and a set of callbacks. Note
+ * callbacks can be 0, meaning you are not interested in that
+ * event.
+ *
+ *@param h: the handle to watch. The IOHandle encapsulates a
+ * platfrom-specific handle to an IO object (e.g. a file descriptor
+ * on Unix.)
+ *@param rCb Callback called when the handle is readable.
+ *@param wCb Callback called when the handle is writable.
+ *@param dCb Callback called when the handle is disconnected.
+ */
+ QPID_COMMON_EXTERN DispatchHandle(const IOHandle& h, Callback rCb, Callback wCb, Callback dCb);
+ QPID_COMMON_EXTERN ~DispatchHandle();
+
+ /** Add this DispatchHandle to the poller to be watched. */
+ QPID_COMMON_EXTERN void startWatch(Poller::shared_ptr poller);
+
+ /** Resume watching for all non-0 callbacks. */
+ QPID_COMMON_EXTERN void rewatch();
+ /** Resume watching for read only. */
+ QPID_COMMON_EXTERN void rewatchRead();
+
+ /** Resume watching for write only. */
+ QPID_COMMON_EXTERN void rewatchWrite();
+
+ /** Stop watching temporarily. The DispatchHandle remains
+ associated with the poller and can be re-activated using
+ rewatch. */
+ QPID_COMMON_EXTERN void unwatch();
+ /** Stop watching for read */
+ QPID_COMMON_EXTERN void unwatchRead();
+ /** Stop watching for write */
+ QPID_COMMON_EXTERN void unwatchWrite();
+
+ /** Stop watching permanently. Disassociates from the poller. */
+ QPID_COMMON_EXTERN void stopWatch();
+
+ /** Interrupt watching this handle and make a serialised callback that respects the
+ * same exclusivity guarantees as the other callbacks
+ */
+ QPID_COMMON_EXTERN void call(Callback iCb);
+
+protected:
+ QPID_COMMON_EXTERN void doDelete();
+
+private:
+ QPID_COMMON_EXTERN void processEvent(Poller::EventType dir);
+};
+
+class DispatchHandleRef {
+ DispatchHandle* ref;
+
+public:
+ typedef boost::function1<void, DispatchHandle&> Callback;
+ DispatchHandleRef(const IOHandle& h, Callback rCb, Callback wCb, Callback dCb) :
+ ref(new DispatchHandle(h, rCb, wCb, dCb))
+ {}
+
+ ~DispatchHandleRef() { ref->doDelete(); }
+
+ void startWatch(Poller::shared_ptr poller) { ref->startWatch(poller); }
+ void rewatch() { ref->rewatch(); }
+ void rewatchRead() { ref->rewatchRead(); }
+ void rewatchWrite() { ref->rewatchWrite(); }
+ void unwatch() { ref->unwatch(); }
+ void unwatchRead() { ref->unwatchRead(); }
+ void unwatchWrite() { ref->unwatchWrite(); }
+ void stopWatch() { ref->stopWatch(); }
+ void call(Callback iCb) { ref->call(iCb); }
+};
+
+}}
+
+#endif // _sys_DispatchHandle_h
diff --git a/qpid/cpp/src/qpid/sys/Dispatcher.cpp b/qpid/cpp/src/qpid/sys/Dispatcher.cpp
new file mode 100644
index 0000000000..5f52dcd990
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/Dispatcher.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/Dispatcher.h"
+
+#include <assert.h>
+
+namespace qpid {
+namespace sys {
+
+Dispatcher::Dispatcher(Poller::shared_ptr poller0) :
+ poller(poller0) {
+}
+
+Dispatcher::~Dispatcher() {
+}
+
+void Dispatcher::run() {
+ poller->run();
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/sys/Dispatcher.h b/qpid/cpp/src/qpid/sys/Dispatcher.h
new file mode 100644
index 0000000000..e8213d0579
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/Dispatcher.h
@@ -0,0 +1,44 @@
+#ifndef _sys_Dispatcher_h
+#define _sys_Dispatcher_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/Poller.h"
+#include "qpid/sys/Runnable.h"
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+namespace sys {
+
+class Dispatcher : public Runnable {
+ const Poller::shared_ptr poller;
+
+public:
+ QPID_COMMON_EXTERN Dispatcher(Poller::shared_ptr poller);
+ QPID_COMMON_EXTERN ~Dispatcher();
+
+ QPID_COMMON_EXTERN void run();
+};
+
+}}
+
+#endif // _sys_Dispatcher_h
diff --git a/qpid/cpp/src/qpid/sys/ExceptionHolder.h b/qpid/cpp/src/qpid/sys/ExceptionHolder.h
new file mode 100644
index 0000000000..4bc934cf75
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/ExceptionHolder.h
@@ -0,0 +1,71 @@
+#ifndef QPID_EXCEPTIONHOLDER_H
+#define QPID_EXCEPTIONHOLDER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <boost/shared_ptr.hpp>
+
+
+namespace qpid {
+namespace sys {
+
+struct Raisable {
+ virtual ~Raisable() {};
+ virtual void raise() const=0;
+ virtual std::string what() const=0;
+};
+
+/**
+ * Holder for exceptions. Allows the thread that notices an error condition to
+ * create an exception and store it to be thrown by another thread.
+ */
+class ExceptionHolder : public Raisable {
+ public:
+ ExceptionHolder() {}
+ // Use default copy & assign.
+
+ /** Take ownership of ex */
+ template <class Ex> ExceptionHolder(Ex* ex) { wrap(ex); }
+ template <class Ex> ExceptionHolder& operator=(Ex* ex) { wrap(ex); return *this; }
+
+ void raise() const { if (wrapper.get()) wrapper->raise() ; }
+ std::string what() const { return wrapper.get() ? wrapper->what() : std::string(); }
+ bool empty() const { return !wrapper.get(); }
+ operator bool() const { return !empty(); }
+ void reset() { wrapper.reset(); }
+
+ private:
+ template <class Ex> struct Wrapper : public Raisable {
+ Wrapper(Ex* ptr) : exception(ptr) {}
+ void raise() const { throw *exception; }
+ std::string what() const { return exception->what(); }
+ boost::shared_ptr<Ex> exception;
+ };
+ template <class Ex> void wrap(Ex* ex) { wrapper.reset(new Wrapper<Ex>(ex)); }
+ boost::shared_ptr<Raisable> wrapper;
+};
+
+
+}} // namespace qpid::sys
+
+
+#endif /*!QPID_EXCEPTIONHOLDER_H*/
diff --git a/qpid/cpp/src/qpid/sys/FileSysDir.h b/qpid/cpp/src/qpid/sys/FileSysDir.h
new file mode 100755
index 0000000000..7432fe39c9
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/FileSysDir.h
@@ -0,0 +1,71 @@
+#ifndef QPID_SYS_FILESYSDIR_H
+#define QPID_SYS_FILESYSDIR_H
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <string>
+
+namespace qpid {
+namespace sys {
+
+/**
+ * @class FileSysDir
+ *
+ * Represents a filesystem directory accessible from the local host.
+ * This class simply checks existence of, and creates, a directory. It could
+ * be added to later to list contents, etc.
+ */
+class FileSysDir
+{
+ const std::string dirPath;
+
+ public:
+
+ FileSysDir (std::string path) : dirPath(path) {}
+ ~FileSysDir () {}
+
+ /**
+ * Check to see if the directory exists and is a directory. Throws an
+ * exception if there is an error checking existence or if the path
+ * exists but is not a directory.
+ *
+ * @retval true if the path exists and is a directory.
+ * @retval false if the path does not exist.
+ */
+ bool exists (void) const;
+
+ void mkdir(void);
+
+ typedef void Callback(const std::string&);
+
+ /**
+ * Call the Callback function for every regular file in the directory
+ *
+ * @param cb Callback function that receives the full path to the file
+ */
+ void forEachFile(Callback cb) const;
+
+ std::string getPath () { return dirPath; }
+};
+
+}} // namespace qpid::sys
+
+#endif /*!QPID_SYS_FILESYSDIR_H*/
diff --git a/qpid/cpp/src/qpid/sys/Fork.h b/qpid/cpp/src/qpid/sys/Fork.h
new file mode 100644
index 0000000000..4ec061f7bc
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/Fork.h
@@ -0,0 +1,24 @@
+#ifndef QPID_SYS_FORK_H
+#define QPID_SYS_FORK_H
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "posix/Fork.h"
+
+#endif /*!QPID_SYS_FORK_H*/
diff --git a/qpid/cpp/src/qpid/sys/FreeBSD/uuid.cpp b/qpid/cpp/src/qpid/sys/FreeBSD/uuid.cpp
new file mode 100644
index 0000000000..076056b2af
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/FreeBSD/uuid.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/uuid.h"
+
+#include <string.h>
+#include <uuid.h>
+
+extern "C"
+void uuid_generate (uint8_t out[qpid::sys::UuidSize])
+{
+ uuid_t uuid;
+ uint32_t status;
+ uuid_create(&uuid, &status);
+ out[0] = (uuid.time_low & 0xff000000) >> 24;
+ out[1] = (uuid.time_low & 0x00ff0000) >> 16;
+ out[2] = (uuid.time_low & 0x0000ff00) >> 8;
+ out[3] = (uuid.time_low & 0x000000ff);
+ out[4] = (uuid.time_mid & 0xff00) >> 8;
+ out[5] = (uuid.time_mid & 0x00ff);
+ out[6] = (uuid.time_hi_and_version & 0xff00) >> 8;
+ out[7] = (uuid.time_hi_and_version & 0x00ff);
+ out[8] = uuid.clock_seq_hi_and_reserved;
+ out[9] = uuid.clock_seq_low;
+ ::memcpy(&out[10], &uuid.node, sizeof(uuid.node));
+}
diff --git a/qpid/cpp/src/qpid/sys/IOHandle.h b/qpid/cpp/src/qpid/sys/IOHandle.h
new file mode 100644
index 0000000000..06ae65f879
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/IOHandle.h
@@ -0,0 +1,36 @@
+#ifndef _sys_IOHandle_h
+#define _sys_IOHandle_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+namespace qpid {
+namespace sys {
+
+/**
+ * This is a class intended to abstract the Unix concept of file descriptor
+ * or the Windows concept of HANDLE
+ */
+class IOHandle;
+
+}}
+
+#endif // _sys_IOHandle_h
diff --git a/qpid/cpp/src/qpid/sys/LockFile.h b/qpid/cpp/src/qpid/sys/LockFile.h
new file mode 100644
index 0000000000..14a76cbf3e
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/LockFile.h
@@ -0,0 +1,64 @@
+#ifndef _sys_LockFile_h
+#define _sys_LockFile_h
+
+/*
+ *
+ * Copyright (c) 2008 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+#include "qpid/CommonImportExport.h"
+#include "qpid/sys/IntegerTypes.h"
+
+namespace qpid {
+namespace sys {
+
+class LockFilePrivate;
+
+/**
+ * @class LockFile
+ *
+ * LockFile represents a locked file suitable for a coarse-grain system
+ * lock. For example, the broker uses this to ensure that only one broker
+ * runs. A common usage idiom is to store the current "owner" process ID
+ * in the lock file - if the lock file exists, but the stored process ID
+ * doesn't, the old owner has probably died without cleaning up the lock
+ * file.
+ */
+class LockFile : private boost::noncopyable
+{
+ std::string path;
+ bool created;
+ boost::shared_ptr<LockFilePrivate> impl;
+
+protected:
+ int read(void*, size_t) const;
+ int write(void*, size_t) const;
+
+public:
+ QPID_COMMON_EXTERN LockFile(const std::string& path_, bool create);
+ QPID_COMMON_EXTERN ~LockFile();
+};
+
+}} /* namespace qpid::sys */
+
+#endif /*!_sys_LockFile_h*/
+
+
+
diff --git a/qpid/cpp/src/qpid/sys/LockPtr.h b/qpid/cpp/src/qpid/sys/LockPtr.h
new file mode 100644
index 0000000000..738a864317
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/LockPtr.h
@@ -0,0 +1,89 @@
+#ifndef QPID_SYS_LOCKPTR_H
+#define QPID_SYS_LOCKPTR_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/Mutex.h"
+#include <boost/noncopyable.hpp>
+
+namespace qpid {
+namespace sys {
+
+class Mutex;
+
+/**
+ * LockPtr is a smart pointer to T. It is constructed from a volatile
+ * T* and a Lock (by default a Mutex). It const_casts away the
+ * volatile qualifier and locks the Lock for the duration of its
+ *
+ * Used in conjuntion with the "volatile" keyword to get the compiler
+ * to help enforce correct concurrent use of mutli-threaded objects.
+ * See ochttp://www.ddj.com/cpp/184403766 for a detailed discussion.
+ *
+ * To summarize the convention:
+ * - Declare thread-safe member functions as volatile.
+ * - Declare instances of the class that may be called concurrently as volatile.
+ * - Use LockPtr to cast away the volatile qualifier while taking a lock.
+ *
+ * This means that code calling on a concurrently-used object
+ * (declared volatile) can only call thread-safe (volatile) member
+ * functions. Code that needs to use thread-unsafe members must use a
+ * LockPtr, thereby acquiring the lock and making it safe to do so.
+ *
+ * A good type-safe pattern is the internally-locked object:
+ * - It has it's own private lock member.
+ * - All public functions are thread safe and declared volatile.
+ * - Any thread-unsafe, non-volatile functions are private.
+ * - Only member function implementations use LockPtr to access private functions.
+ *
+ * This encapsulates all the locking logic inside the class.
+ *
+ * One nice feature of this convention is the common case where you
+ * need a public, locked version of some function foo() and also a
+ * private unlocked version to avoid recursive locks. They can be declared as
+ * volatile and non-volatile overloads of the same function:
+ *
+ * // public
+ * void Thing::foo() volatile { LockPtr<Thing>(this, myLock)->foo(); }
+ * // private
+ * void Thing::foo() { ... do stuff ...}
+ */
+
+template <class T, class Lock> class LockPtr : public boost::noncopyable {
+ public:
+ LockPtr(volatile T* p, Lock& l) : ptr(const_cast<T*>(p)), lock(l) { lock.lock(); }
+ LockPtr(volatile T* p, volatile Lock& l) : ptr(const_cast<T*>(p)), lock(const_cast<Lock&>(l)) { lock.lock(); }
+ ~LockPtr() { lock.unlock(); }
+
+ T& operator*() { return *ptr; }
+ T* operator->() { return ptr; }
+
+ private:
+ T* ptr;
+ Lock& lock;
+};
+
+
+}} // namespace qpid::sys
+
+
+#endif /*!QPID_SYS_LOCKPTR_H*/
diff --git a/qpid/cpp/src/qpid/sys/MemStat.cpp b/qpid/cpp/src/qpid/sys/MemStat.cpp
new file mode 100644
index 0000000000..c71fba785c
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/MemStat.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/MemStat.h"
+
+// Null memory stats provider:
+// This is for platforms that do not have a way to get allocated
+// memory status
+void qpid::sys::MemStat::loadMemInfo(qmf::org::apache::qpid::broker::Memory*)
+{
+}
+
+
diff --git a/qpid/cpp/src/qpid/sys/MemStat.h b/qpid/cpp/src/qpid/sys/MemStat.h
new file mode 100644
index 0000000000..d855786cd5
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/MemStat.h
@@ -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.
+ *
+ */
+#ifndef sys_MemStat
+#define sys_MemStat
+
+#include "qpid/CommonImportExport.h"
+#include "qmf/org/apache/qpid/broker/Memory.h"
+
+namespace qpid {
+namespace sys {
+
+ class QPID_COMMON_CLASS_EXTERN MemStat {
+ public:
+ QPID_COMMON_EXTERN static void loadMemInfo(qmf::org::apache::qpid::broker::Memory* object);
+ };
+
+}}
+
+#endif
+
diff --git a/qpid/cpp/src/qpid/sys/MemoryMappedFile.h b/qpid/cpp/src/qpid/sys/MemoryMappedFile.h
new file mode 100644
index 0000000000..ecf38e98e6
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/MemoryMappedFile.h
@@ -0,0 +1,79 @@
+#ifndef QPID_SYS_MEMORYMAPPEDFILE_H
+#define QPID_SYS_MEMORYMAPPEDFILE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/CommonImportExport.h"
+#include <string>
+
+namespace qpid {
+namespace sys {
+
+class MemoryMappedFilePrivate;
+/**
+ * Abstraction of memory mapping functionality
+ */
+class MemoryMappedFile {
+ public:
+ QPID_COMMON_EXTERN MemoryMappedFile();
+ QPID_COMMON_EXTERN ~MemoryMappedFile();
+ /**
+ * Opens a file that can be mapped by region into memory
+ */
+ QPID_COMMON_EXTERN void open(const std::string& name, const std::string& directory);
+ /**
+ * Closes and removes the file that can be mapped by region into memory
+ */
+ QPID_COMMON_EXTERN void close();
+ /**
+ * Returns the page size
+ */
+ QPID_COMMON_EXTERN size_t getPageSize();
+ /**
+ * Load a portion of the file into memory
+ */
+ QPID_COMMON_EXTERN char* map(size_t offset, size_t size);
+ /**
+ * Evict a portion of the file from memory
+ */
+ QPID_COMMON_EXTERN void unmap(char* region, size_t size);
+ /**
+ * Flush any changes to a previously mapped region of the file
+ * back to disk
+ */
+ QPID_COMMON_EXTERN void flush(char* region, size_t size);
+ /**
+ * Expand the capacity of the file
+ */
+ QPID_COMMON_EXTERN void expand(size_t offset);
+ /**
+ * Returns true if memory mapping is supported, false otherwise
+ */
+ QPID_COMMON_EXTERN static bool isSupported();
+ private:
+ MemoryMappedFilePrivate* state;
+
+ MemoryMappedFile(const MemoryMappedFile&);
+ MemoryMappedFile& operator=(const MemoryMappedFile&);
+};
+}} // namespace qpid::sys
+
+#endif /*!QPID_SYS_MEMORYMAPPEDFILE_H*/
diff --git a/qpid/cpp/src/qpid/sys/Monitor.h b/qpid/cpp/src/qpid/sys/Monitor.h
new file mode 100644
index 0000000000..123bf92dcb
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/Monitor.h
@@ -0,0 +1,49 @@
+#ifndef _sys_Monitor_h
+#define _sys_Monitor_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/Condition.h"
+
+namespace qpid {
+namespace sys {
+
+/**
+ * A monitor is a condition variable and a mutex
+ */
+class Monitor : public Mutex, public Condition {
+ public:
+ inline void wait();
+ inline bool wait(const AbsTime& absoluteTime);
+};
+
+
+void Monitor::wait() {
+ Condition::wait(*this);
+}
+
+bool Monitor::wait(const AbsTime& absoluteTime) {
+ return Condition::wait(*this, absoluteTime);
+}
+
+}}
+#endif /*!_sys_Monitor_h*/
diff --git a/qpid/cpp/src/qpid/sys/Mutex.h b/qpid/cpp/src/qpid/sys/Mutex.h
new file mode 100644
index 0000000000..e718586a39
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/Mutex.h
@@ -0,0 +1,91 @@
+#ifndef _sys_Mutex_h
+#define _sys_Mutex_h
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+namespace qpid {
+namespace sys {
+
+/**
+ * Scoped lock template: calls lock() in ctor, unlock() in dtor.
+ * L can be any class with lock() and unlock() functions.
+ */
+template <class L>
+class ScopedLock
+{
+ public:
+ ScopedLock(L& l) : mutex(l) { mutex.lock(); }
+ ~ScopedLock() { mutex.unlock(); }
+ private:
+ L& mutex;
+};
+
+template <class L>
+class ScopedUnlock
+{
+ public:
+ ScopedUnlock(L& l) : mutex(l) { mutex.unlock(); }
+ ~ScopedUnlock() { mutex.lock(); }
+ private:
+ L& mutex;
+};
+
+template <class L>
+class ScopedRlock
+{
+ public:
+ ScopedRlock(L& l) : mutex(l) { mutex.rlock(); }
+ ~ScopedRlock() { mutex.unlock(); }
+ private:
+ L& mutex;
+};
+
+template <class L>
+class ScopedWlock
+{
+ public:
+ ScopedWlock(L& l) : mutex(l) { mutex.wlock(); }
+ ~ScopedWlock() { mutex.unlock(); }
+ private:
+ L& mutex;
+};
+
+template <class L>
+class ConditionalScopedLock
+{
+ public:
+ ConditionalScopedLock(L& l) : mutex(l) { acquired = mutex.trylock(); }
+ ~ConditionalScopedLock() { if (acquired) mutex.unlock(); }
+ bool lockAcquired() { return acquired; }
+ private:
+ L& mutex;
+ bool acquired;
+};
+
+}}
+
+#ifdef USE_APR_PLATFORM
+#include "apr/Mutex.h"
+#elif defined (_WIN32)
+#include "windows/Mutex.h"
+#else
+#include "posix/Mutex.h"
+#endif
+
+#endif /*!_sys_Mutex_h*/
diff --git a/qpid/cpp/src/qpid/sys/OutputControl.h b/qpid/cpp/src/qpid/sys/OutputControl.h
new file mode 100644
index 0000000000..f990594637
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/OutputControl.h
@@ -0,0 +1,43 @@
+#ifndef QPID_SYS_OUTPUT_CONTROL_H
+#define QPID_SYS_OUTPUT_CONTROL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/IntegerTypes.h"
+
+namespace qpid {
+namespace sys {
+
+ class OutputControl
+ {
+ public:
+ virtual ~OutputControl() {}
+ virtual void abort() = 0;
+ virtual void connectionEstablished() = 0;
+ virtual void activateOutput() = 0;
+ };
+
+}
+}
+
+
+#endif /*!QPID_SYS_OUTPUT_CONTROL_H*/
diff --git a/qpid/cpp/src/qpid/sys/OutputTask.h b/qpid/cpp/src/qpid/sys/OutputTask.h
new file mode 100644
index 0000000000..fb08a63cd0
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/OutputTask.h
@@ -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.
+ *
+ */
+#ifndef _OutputTask_
+#define _OutputTask_
+
+namespace qpid {
+namespace sys {
+
+ class OutputTask
+ {
+ public:
+ virtual ~OutputTask() {}
+ /** Generate some output.
+ *@return true if output was generated, false if there is no work to do.
+ */
+ virtual bool doOutput() = 0;
+ };
+
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/sys/Path.h b/qpid/cpp/src/qpid/sys/Path.h
new file mode 100644
index 0000000000..9b440b3c00
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/Path.h
@@ -0,0 +1,61 @@
+#ifndef QPID_SYS_PATH_H
+#define QPID_SYS_PATH_H
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/CommonImportExport.h"
+#include <string>
+
+namespace qpid {
+namespace sys {
+
+/**
+ * @class Path
+ *
+ * Represents a filesystem path with some basic operations. Can be extended.
+ */
+class QPID_COMMON_CLASS_EXTERN Path {
+
+ std::string path;
+
+ public:
+ // Path separator, forward or backslash
+ static const QPID_COMMON_EXTERN std::string separator;
+
+ Path(const std::string& path_=std::string()) : path(path_) {}
+
+ std::string str() const { return path; }
+ bool empty() const { return path.empty(); }
+
+ QPID_COMMON_EXTERN bool exists() const;
+ QPID_COMMON_EXTERN bool isFile() const;
+ QPID_COMMON_EXTERN bool isDirectory() const;
+ QPID_COMMON_EXTERN bool isAbsolute() const;
+
+ /** Join with appropriate path separator. */
+ Path& operator+=(const Path& tail) { path = path + separator + tail.path; return *this; }
+};
+
+inline Path operator+(const Path& head, const Path& tail) { Path p(head); return p += tail; }
+
+}} // namespace qpid::sys
+
+#endif /*!QPID_SYS_PATH_H*/
diff --git a/qpid/cpp/src/qpid/sys/PipeHandle.h b/qpid/cpp/src/qpid/sys/PipeHandle.h
new file mode 100755
index 0000000000..8aac76996b
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/PipeHandle.h
@@ -0,0 +1,51 @@
+#ifndef _sys_PipeHandle_h
+#define _sys_PipeHandle_h
+
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/CommonImportExport.h"
+#include <string>
+
+// This class is a portability wrapper around pipe fds.
+// It currently exists primarily and solely for the purpose of
+// integration with single-threaded components that require QMF
+// integration through a signalling fd.
+
+namespace qpid {
+namespace sys {
+
+ class PipeHandle {
+ private:
+ int writeFd;
+ int readFd;
+ public:
+ QPID_COMMON_EXTERN PipeHandle(bool nonBlocking=true);
+ QPID_COMMON_EXTERN ~PipeHandle();
+ QPID_COMMON_EXTERN int read(void* buf, size_t bufSize);
+ QPID_COMMON_EXTERN int write(const void* buf, size_t bufSize);
+ QPID_COMMON_EXTERN int getReadHandle();
+ };
+
+}}
+
+#endif /*!_sys_PipeHandle_h*/
diff --git a/qpid/cpp/src/qpid/sys/PollableCondition.h b/qpid/cpp/src/qpid/sys/PollableCondition.h
new file mode 100644
index 0000000000..2eb6f2d947
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/PollableCondition.h
@@ -0,0 +1,64 @@
+#ifndef QPID_SYS_POLLABLECONDITION_H
+#define QPID_SYS_POLLABLECONDITION_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/Poller.h"
+#include "qpid/CommonImportExport.h"
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+
+
+namespace qpid {
+namespace sys {
+
+class PollableConditionPrivate;
+
+class PollableCondition {
+public:
+ typedef boost::function1<void, PollableCondition&> Callback;
+
+ QPID_COMMON_EXTERN PollableCondition(const Callback& cb,
+ const boost::shared_ptr<sys::Poller>& poller);
+
+ QPID_COMMON_EXTERN ~PollableCondition();
+
+ /**
+ * Set the condition. Triggers callback to Callback from Poller.
+ */
+ QPID_COMMON_EXTERN void set();
+
+ /**
+ * Clear the condition. Stops callbacks from Poller.
+ */
+ QPID_COMMON_EXTERN void clear();
+
+ private:
+ PollableConditionPrivate *impl;
+
+ Callback callback;
+ boost::shared_ptr<sys::Poller> poller;
+};
+
+}} // namespace qpid::sys
+
+#endif /*!QPID_SYS_POLLABLECONDITION_H*/
diff --git a/qpid/cpp/src/qpid/sys/PollableQueue.h b/qpid/cpp/src/qpid/sys/PollableQueue.h
new file mode 100644
index 0000000000..5a3e281e9f
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/PollableQueue.h
@@ -0,0 +1,177 @@
+#ifndef QPID_SYS_POLLABLEQUEUE_H
+#define QPID_SYS_POLLABLEQUEUE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/PollableCondition.h"
+#include "qpid/sys/Monitor.h"
+#include "qpid/sys/Thread.h"
+#include <boost/function.hpp>
+#include <boost/bind.hpp>
+#include <algorithm>
+#include <deque>
+#include "qpid/log/Statement.h" // FIXME aconway 2011-08-05:
+
+namespace qpid {
+namespace sys {
+
+class Poller;
+
+/**
+ * A queue whose item processing is dispatched by sys::Poller.
+ * Any thread can push to the queue; items pushed trigger an event the Poller
+ * recognizes. When a Poller I/O thread dispatches the event, a
+ * user-specified callback is invoked with all items on the queue.
+ */
+template <class T>
+class PollableQueue {
+ public:
+ typedef std::deque<T> Batch;
+ typedef T value_type;
+
+ /**
+ * Callback to process a batch of items from the queue.
+ *
+ * @param batch Queue of values to process. Any items remaining
+ * on return from Callback are put back on the queue.
+ * @return iterator pointing to the first un-processed item in batch.
+ * Items from this point up to batch.end() are put back on the queue.
+ */
+ typedef boost::function<typename Batch::const_iterator (const Batch& batch)> Callback;
+
+ /**
+ * Constructor; sets necessary parameters.
+ *
+ * @param cb Callback that will be called to process items on the
+ * queue. Will be called from a Poller I/O thread.
+ * @param poller Poller to use for dispatching queue events.
+ */
+ PollableQueue(const Callback& cb,
+ const boost::shared_ptr<sys::Poller>& poller);
+
+ ~PollableQueue();
+
+ /** Push a value onto the queue. Thread safe */
+ void push(const T& t);
+
+ /** Start polling. */
+ void start();
+
+ /** Stop polling and wait for the current callback, if any, to complete. */
+ void stop();
+
+ /** Are we currently stopped?*/
+ bool isStopped() const { ScopedLock l(lock); return stopped; }
+
+ size_t size() { ScopedLock l(lock); return queue.size(); }
+ bool empty() { ScopedLock l(lock); return queue.empty(); }
+
+ /**
+ * Allow any queued events to be processed; intended for calling
+ * after all dispatch threads exit the Poller loop in order to
+ * ensure clean shutdown with no events left on the queue.
+ */
+ void shutdown();
+
+ private:
+ typedef sys::Monitor::ScopedLock ScopedLock;
+ typedef sys::Monitor::ScopedUnlock ScopedUnlock;
+
+ void dispatch(PollableCondition& cond);
+ void process();
+
+ mutable sys::Monitor lock;
+ Callback callback;
+ PollableCondition condition;
+ Batch queue, batch;
+ Thread dispatcher;
+ bool stopped;
+};
+
+template <class T> PollableQueue<T>::PollableQueue(
+ const Callback& cb, const boost::shared_ptr<sys::Poller>& p)
+ : callback(cb),
+ condition(boost::bind(&PollableQueue<T>::dispatch, this, _1), p),
+ stopped(true)
+{
+}
+
+template <class T> void PollableQueue<T>::start() {
+ ScopedLock l(lock);
+ if (!stopped) return;
+ stopped = false;
+ if (!queue.empty()) condition.set();
+}
+
+template <class T> PollableQueue<T>::~PollableQueue() {
+}
+
+template <class T> void PollableQueue<T>::push(const T& t) {
+ ScopedLock l(lock);
+ if (queue.empty() && !stopped) condition.set();
+ queue.push_back(t);
+}
+
+template <class T> void PollableQueue<T>::dispatch(PollableCondition& cond) {
+ ScopedLock l(lock);
+ assert(!dispatcher);
+ dispatcher = Thread::current();
+ process();
+ dispatcher = Thread();
+ if (queue.empty()) cond.clear();
+ if (stopped) lock.notifyAll();
+}
+
+template <class T> void PollableQueue<T>::process() {
+ // Called with lock held
+ if (!stopped && !queue.empty()) {
+ assert(batch.empty());
+ batch.swap(queue);
+ typename Batch::const_iterator putBack;
+ {
+ ScopedUnlock u(lock); // Allow concurrent push to queue.
+ putBack = callback(batch);
+ }
+ // put back unprocessed items.
+ queue.insert(queue.begin(), putBack, typename Batch::const_iterator(batch.end()));
+ batch.clear();
+ }
+}
+
+template <class T> void PollableQueue<T>::shutdown() {
+ ScopedLock l(lock);
+ process();
+}
+
+template <class T> void PollableQueue<T>::stop() {
+ ScopedLock l(lock);
+ if (stopped) return;
+ condition.clear();
+ stopped = true;
+ // Avoid deadlock if stop is called from the dispatch thread
+ if (dispatcher && dispatcher != Thread::current())
+ while (dispatcher) lock.wait();
+}
+
+}} // namespace qpid::sys
+
+#endif /*!QPID_SYS_POLLABLEQUEUE_H*/
diff --git a/qpid/cpp/src/qpid/sys/Poller.h b/qpid/cpp/src/qpid/sys/Poller.h
new file mode 100644
index 0000000000..01ee139ee6
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/Poller.h
@@ -0,0 +1,135 @@
+#ifndef _sys_Poller_h
+#define _sys_Poller_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/Time.h"
+#include "qpid/sys/Runnable.h"
+#include "qpid/CommonImportExport.h"
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace sys {
+
+/**
+ * Poller is an abstract base class that registers callbacks to be
+ * called when there is IO activity. Concrete derived classes
+ * implement polling APIs such as epoll or equivalents on other
+ * operating systems.
+ *
+ * On the broker, Connection::received() is called with incoming
+ * frames from clients, and Connection::doOutput() is called when a
+ * connection is writeable.
+ *
+ * @see DispatchHandler for more details of normal use.
+ */
+class PollerHandle;
+class PollerPrivate;
+class Poller : public Runnable {
+ PollerPrivate* const impl;
+
+public:
+ typedef boost::shared_ptr<Poller> shared_ptr;
+
+ enum Direction {
+ NONE = 0,
+ INPUT,
+ OUTPUT,
+ INOUT
+ };
+
+ enum EventType {
+ INVALID = 0,
+ READABLE,
+ WRITABLE,
+ READ_WRITABLE,
+ DISCONNECTED,
+ SHUTDOWN,
+ TIMEOUT,
+ INTERRUPTED
+ };
+
+ struct Event {
+ PollerHandle* handle;
+ EventType type;
+
+ Event(PollerHandle* handle0, EventType type0) :
+ handle(handle0),
+ type(type0) {
+ }
+
+ void process();
+ };
+
+ QPID_COMMON_EXTERN Poller();
+ QPID_COMMON_EXTERN ~Poller();
+ /** Note: this function is async-signal safe */
+ QPID_COMMON_EXTERN void shutdown();
+
+ // Interrupt waiting for a specific poller handle
+ // returns true if we could interrupt the handle
+ // - in this case on return the handle is no longer being monitored,
+ // but we will receive an event from some invocation of poller::wait
+ // with the handle and the INTERRUPTED event type
+ // if it returns false then the handle is not being monitored by the poller
+ // - This can either be because it has just received an event which has been
+ // reported and has not been reenabled since.
+ // - Because it was removed from the monitoring set
+ // - Or because it is already being interrupted
+ QPID_COMMON_EXTERN bool interrupt(PollerHandle& handle);
+
+ // Poller run loop
+ QPID_COMMON_EXTERN void run();
+
+ QPID_COMMON_EXTERN void registerHandle(PollerHandle& handle);
+ QPID_COMMON_EXTERN void unregisterHandle(PollerHandle& handle);
+ QPID_COMMON_EXTERN void monitorHandle(PollerHandle& handle, Direction dir);
+ QPID_COMMON_EXTERN void unmonitorHandle(PollerHandle& handle, Direction dir);
+ QPID_COMMON_EXTERN Event wait(Duration timeout = TIME_INFINITE);
+
+ QPID_COMMON_EXTERN bool hasShutdown();
+};
+
+/**
+ * Handle class to use for polling
+ */
+class IOHandle;
+class PollerHandlePrivate;
+class PollerHandle {
+ friend class Poller;
+ friend class PollerPrivate;
+ friend struct Poller::Event;
+
+ PollerHandlePrivate* const impl;
+ QPID_COMMON_INLINE_EXTERN virtual void processEvent(Poller::EventType) {};
+
+public:
+ QPID_COMMON_EXTERN PollerHandle(const IOHandle& h);
+ QPID_COMMON_EXTERN virtual ~PollerHandle();
+};
+
+inline void Poller::Event::process() {
+ handle->processEvent(type);
+}
+
+}}
+#endif // _sys_Poller_h
diff --git a/qpid/cpp/src/qpid/sys/Probes.h b/qpid/cpp/src/qpid/sys/Probes.h
new file mode 100644
index 0000000000..d30181c357
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/Probes.h
@@ -0,0 +1,65 @@
+#ifndef _sys_Probes
+#define _sys_Probes
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "config.h"
+
+#ifdef HAVE_SYS_SDT_H
+#include <sys/sdt.h>
+#endif
+
+// Pragmatically it seems that Linux and Solaris versions of sdt.h which support
+// user static probes define up to DTRACE_PROBE8, but FreeBSD 8 which doesn't
+// support usdt only defines up to DTRACE_PROBE4 - FreeBSD 9 which does support usdt
+// defines up to DTRACE_PROBE5.
+
+#ifdef DTRACE_PROBE5
+// Versions for Linux Systemtap/Solaris/FreeBSD 9
+#define QPID_PROBE(probe) DTRACE_PROBE(qpid, probe)
+#define QPID_PROBE1(probe, p1) DTRACE_PROBE1(qpid, probe, p1)
+#define QPID_PROBE2(probe, p1, p2) DTRACE_PROBE2(qpid, probe, p1, p2)
+#define QPID_PROBE3(probe, p1, p2, p3) DTRACE_PROBE3(qpid, probe, p1, p2, p3)
+#define QPID_PROBE4(probe, p1, p2, p3, p4) DTRACE_PROBE4(qpid, probe, p1, p2, p3, p4)
+#define QPID_PROBE5(probe, p1, p2, p3, p4, p5) DTRACE_PROBE5(qpid, probe, p1, p2, p3, p4, p5)
+#else
+// FreeBSD 8
+#define QPID_PROBE(probe)
+#define QPID_PROBE1(probe, p1)
+#define QPID_PROBE2(probe, p1, p2)
+#define QPID_PROBE3(probe, p1, p2, p3)
+#define QPID_PROBE4(probe, p1, p2, p3, p4)
+#define QPID_PROBE5(probe, p1, p2, p3, p4, p5)
+#endif
+
+#ifdef DTRACE_PROBE8
+// Versions for Linux Systemtap
+#define QPID_PROBE6(probe, p1, p2, p3, p4, p5, p6) DTRACE_PROBE6(qpid, probe, p1, p2, p3, p4, p5, p6)
+#define QPID_PROBE7(probe, p1, p2, p3, p4, p5, p6, p7) DTRACE_PROBE7(qpid, probe, p1, p2, p3, p4, p5, p6, p7)
+#define QPID_PROBE8(probe, p1, p2, p3, p4, p5, p6, p7, p8) DTRACE_PROBE8(qpid, probe, p1, p2, p3, p4, p5, p6, p7, p8)
+#else
+// Versions for Solaris/FreeBSD
+#define QPID_PROBE6(probe, p1, p2, p3, p4, p5, p6)
+#define QPID_PROBE7(probe, p1, p2, p3, p4, p5, p6, p7)
+#define QPID_PROBE8(probe, p1, p2, p3, p4, p5, p6, p7, p8)
+#endif
+
+#endif // _sys_Probes
diff --git a/qpid/cpp/src/qpid/sys/RdmaIOPlugin.cpp b/qpid/cpp/src/qpid/sys/RdmaIOPlugin.cpp
new file mode 100644
index 0000000000..9a7b8ef201
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/RdmaIOPlugin.cpp
@@ -0,0 +1,393 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/TransportFactory.h"
+
+#include "qpid/Plugin.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/NameGenerator.h"
+#include "qpid/framing/AMQP_HighestVersion.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/rdma/RdmaIO.h"
+#include "qpid/sys/rdma/rdma_exception.h"
+#include "qpid/sys/OutputControl.h"
+#include "qpid/sys/SecuritySettings.h"
+
+#include <boost/bind.hpp>
+#include <boost/lexical_cast.hpp>
+#include <memory>
+
+#include <netdb.h>
+
+using std::auto_ptr;
+using std::string;
+using std::stringstream;
+
+namespace qpid {
+namespace sys {
+
+class RdmaIOHandler : public OutputControl {
+ std::string identifier;
+ ConnectionCodec::Factory* factory;
+ ConnectionCodec* codec;
+ bool readError;
+
+ sys::Mutex pollingLock;
+ bool polling;
+
+ Rdma::AsynchIO* aio;
+ Rdma::Connection::intrusive_ptr connection;
+
+ void write(const framing::ProtocolInitiation&);
+ void disconnectAction();
+
+ public:
+ RdmaIOHandler(Rdma::Connection::intrusive_ptr c, ConnectionCodec::Factory* f);
+ ~RdmaIOHandler();
+ void init(Rdma::AsynchIO* a);
+ void start(Poller::shared_ptr poller);
+
+ // Output side
+ void close();
+ void abort();
+ void connectionEstablished();
+ void activateOutput();
+ void initProtocolOut();
+
+ // Input side
+ void readbuff(Rdma::AsynchIO& aio, Rdma::Buffer* buff);
+ void initProtocolIn(Rdma::Buffer* buff);
+
+ // Notifications
+ void full(Rdma::AsynchIO& aio);
+ void idle(Rdma::AsynchIO& aio);
+ void error(Rdma::AsynchIO& aio);
+ void disconnected();
+ void drained();
+};
+
+RdmaIOHandler::RdmaIOHandler(Rdma::Connection::intrusive_ptr c, qpid::sys::ConnectionCodec::Factory* f) :
+ identifier(broker::QPID_NAME_PREFIX+c->getFullName()),
+ factory(f),
+ codec(0),
+ readError(false),
+ polling(false),
+ connection(c)
+{
+}
+
+RdmaIOHandler::~RdmaIOHandler() {
+ if (codec)
+ codec->closed();
+ delete codec;
+ delete aio;
+}
+
+void RdmaIOHandler::init(Rdma::AsynchIO* a) {
+ aio = a;
+}
+
+void RdmaIOHandler::start(Poller::shared_ptr poller) {
+ Mutex::ScopedLock l(pollingLock);
+ assert(!polling);
+
+ polling = true;
+
+ aio->start(poller);
+}
+
+void RdmaIOHandler::write(const framing::ProtocolInitiation& data)
+{
+ QPID_LOG(debug, "Rdma: SENT [" << identifier << "]: INIT(" << data << ")");
+ Rdma::Buffer* buff = aio->getSendBuffer();
+ assert(buff);
+ framing::Buffer out(buff->bytes(), buff->byteCount());
+ data.encode(out);
+ buff->dataCount(data.encodedSize());
+ aio->queueWrite(buff);
+}
+
+void RdmaIOHandler::close() {
+ aio->drainWriteQueue(boost::bind(&RdmaIOHandler::drained, this));
+}
+
+// TODO: Dummy implementation, need to fill this in for heartbeat timeout to work
+void RdmaIOHandler::abort() {
+}
+
+// TODO: Dummy implementation, need to fill this in for connection establishment timeout to work
+void RdmaIOHandler::connectionEstablished() {
+}
+
+void RdmaIOHandler::activateOutput() {
+ aio->notifyPendingWrite();
+}
+
+void RdmaIOHandler::idle(Rdma::AsynchIO&) {
+ // TODO: Shouldn't need this test as idle() should only ever be called when
+ // the connection is writable anyway
+ if ( !aio->writable() ) {
+ return;
+ }
+ if (codec == 0) return;
+ if (!codec->canEncode()) {
+ return;
+ }
+ Rdma::Buffer* buff = aio->getSendBuffer();
+ if (buff) {
+ size_t encoded=codec->encode(buff->bytes(), buff->byteCount());
+ buff->dataCount(encoded);
+ aio->queueWrite(buff);
+ if (codec->isClosed()) {
+ close();
+ }
+ }
+}
+
+void RdmaIOHandler::initProtocolOut() {
+ // We mustn't have already started the conversation
+ // but we must be able to send
+ assert( codec == 0 );
+ assert( aio->writable() );
+ codec = factory->create(*this, identifier, SecuritySettings());
+ write(framing::ProtocolInitiation(codec->getVersion()));
+}
+
+void RdmaIOHandler::error(Rdma::AsynchIO&) {
+ disconnected();
+}
+
+namespace {
+ void stopped(RdmaIOHandler* async) {
+ delete async;
+ }
+}
+
+void RdmaIOHandler::disconnectAction() {
+ {
+ Mutex::ScopedLock l(pollingLock);
+ // If we're closed already then we'll get to drained() anyway
+ if (!polling) return;
+ polling = false;
+ }
+ aio->stop(boost::bind(&stopped, this));
+}
+
+void RdmaIOHandler::disconnected() {
+ aio->requestCallback(boost::bind(&RdmaIOHandler::disconnectAction, this));
+}
+
+void RdmaIOHandler::drained() {
+ // We know we've drained the write queue now, but we don't have to do anything
+ // because we can rely on the client to disconnect to trigger the connection
+ // cleanup.
+}
+
+void RdmaIOHandler::full(Rdma::AsynchIO&) {
+ QPID_LOG(debug, "Rdma: buffer full [" << identifier << "]");
+}
+
+// The logic here is subtly different from TCP as RDMA is message oriented
+// so we define that an RDMA message is a frame - in this case there is no putting back
+// of any message remainder - there shouldn't be any. And what we read here can't be
+// smaller than a frame
+void RdmaIOHandler::readbuff(Rdma::AsynchIO&, Rdma::Buffer* buff) {
+ if (readError) {
+ return;
+ }
+ try {
+ if (codec) {
+ (void) codec->decode(buff->bytes(), buff->dataCount());
+ }else{
+ // Need to start protocol processing
+ initProtocolIn(buff);
+ }
+ }catch(const std::exception& e){
+ QPID_LOG(error, e.what());
+ readError = true;
+ close();
+ }
+}
+
+void RdmaIOHandler::initProtocolIn(Rdma::Buffer* buff) {
+ framing::Buffer in(buff->bytes(), buff->dataCount());
+ framing::ProtocolInitiation protocolInit;
+ if (protocolInit.decode(in)) {
+ QPID_LOG(debug, "Rdma: RECV [" << identifier << "]: INIT(" << protocolInit << ")");
+
+ codec = factory->create(protocolInit.getVersion(), *this, identifier, SecuritySettings());
+
+ // If we failed to create the codec then we don't understand the offered protocol version
+ if (!codec) {
+ // send valid version header & close connection.
+ write(framing::ProtocolInitiation(framing::highestProtocolVersion));
+ readError = true;
+ close();
+ }
+ }
+}
+
+class RdmaIOProtocolFactory : public TransportAcceptor, public TransportConnector {
+ auto_ptr<Rdma::Listener> listener;
+ const uint16_t listeningPort;
+
+ public:
+ RdmaIOProtocolFactory(int16_t port, int backlog);
+ void accept(Poller::shared_ptr, ConnectionCodec::Factory*);
+ void connect(Poller::shared_ptr, const std::string& name, const string& host, const std::string& port, ConnectionCodec::Factory*, ConnectFailedCallback);
+
+ uint16_t getPort() const;
+
+ private:
+ bool request(Rdma::Connection::intrusive_ptr, const Rdma::ConnectionParams&, ConnectionCodec::Factory*);
+ void established(Poller::shared_ptr, Rdma::Connection::intrusive_ptr);
+ void connected(Poller::shared_ptr, Rdma::Connection::intrusive_ptr, const Rdma::ConnectionParams&, ConnectionCodec::Factory*);
+ void connectionError(Rdma::Connection::intrusive_ptr, Rdma::ErrorType);
+ void disconnected(Rdma::Connection::intrusive_ptr);
+ void rejected(Rdma::Connection::intrusive_ptr, const Rdma::ConnectionParams&, ConnectFailedCallback);
+};
+
+// Static instance to initialise plugin
+static class RdmaIOPlugin : public Plugin {
+ void earlyInitialize(Target&) {
+ }
+
+ void initialize(Target& target) {
+ // Check whether we actually have any rdma devices
+ if ( Rdma::deviceCount() == 0 ) {
+ QPID_LOG(info, "Rdma: Disabled: no rdma devices found");
+ return;
+ }
+
+ broker::Broker* broker = dynamic_cast<broker::Broker*>(&target);
+ // Only provide to a Broker
+ if (broker) {
+ boost::shared_ptr<RdmaIOProtocolFactory> protocol(new RdmaIOProtocolFactory(broker->getPortOption(), broker->getConnectionBacklog()));
+ uint16_t port = protocol->getPort();
+ QPID_LOG(notice, "Rdma: Listening on RDMA port " << port);
+ broker->registerTransport("rdma", protocol, protocol, port);
+ }
+ }
+} rdmaPlugin;
+
+RdmaIOProtocolFactory::RdmaIOProtocolFactory(int16_t port, int /*backlog*/) :
+ listeningPort(port)
+{}
+
+void RdmaIOProtocolFactory::established(Poller::shared_ptr poller, Rdma::Connection::intrusive_ptr ci) {
+ RdmaIOHandler* async = ci->getContext<RdmaIOHandler>();
+ async->start(poller);
+}
+
+bool RdmaIOProtocolFactory::request(Rdma::Connection::intrusive_ptr ci, const Rdma::ConnectionParams& cp,
+ ConnectionCodec::Factory* f) {
+ try {
+ if (cp.rdmaProtocolVersion == 0) {
+ QPID_LOG(warning, "Rdma: connection from protocol version 0 client");
+ }
+ RdmaIOHandler* async = new RdmaIOHandler(ci, f);
+ Rdma::AsynchIO* aio =
+ new Rdma::AsynchIO(ci->getQueuePair(),
+ cp.rdmaProtocolVersion,
+ cp.maxRecvBufferSize, cp.initialXmitCredit, Rdma::DEFAULT_WR_ENTRIES,
+ boost::bind(&RdmaIOHandler::readbuff, async, _1, _2),
+ boost::bind(&RdmaIOHandler::idle, async, _1),
+ 0, // boost::bind(&RdmaIOHandler::full, async, _1),
+ boost::bind(&RdmaIOHandler::error, async, _1));
+ async->init(aio);
+
+ // Record aio so we can get it back from a connection
+ ci->addContext(async);
+ return true;
+ } catch (const Rdma::Exception& e) {
+ QPID_LOG(error, "Rdma: Cannot accept new connection (Rdma exception): " << e.what());
+ } catch (const std::exception& e) {
+ QPID_LOG(error, "Rdma: Cannot accept new connection (unknown exception): " << e.what());
+ }
+
+ // If we get here we caught an exception so reject connection
+ return false;
+}
+
+void RdmaIOProtocolFactory::connectionError(Rdma::Connection::intrusive_ptr, Rdma::ErrorType) {
+}
+
+void RdmaIOProtocolFactory::disconnected(Rdma::Connection::intrusive_ptr ci) {
+ // If we've got a connection already tear it down, otherwise ignore
+ RdmaIOHandler* async = ci->getContext<RdmaIOHandler>();
+ if (async) {
+ // Make sure we don't disconnect more than once
+ ci->removeContext();
+ async->disconnected();
+ }
+}
+
+uint16_t RdmaIOProtocolFactory::getPort() const {
+ return listeningPort; // Immutable no need for lock.
+}
+
+void RdmaIOProtocolFactory::accept(Poller::shared_ptr poller, ConnectionCodec::Factory* fact) {
+ listener.reset(
+ new Rdma::Listener(
+ Rdma::ConnectionParams(65536, Rdma::DEFAULT_WR_ENTRIES),
+ boost::bind(&RdmaIOProtocolFactory::established, this, poller, _1),
+ boost::bind(&RdmaIOProtocolFactory::connectionError, this, _1, _2),
+ boost::bind(&RdmaIOProtocolFactory::disconnected, this, _1),
+ boost::bind(&RdmaIOProtocolFactory::request, this, _1, _2, fact)));
+
+ SocketAddress sa("",boost::lexical_cast<std::string>(listeningPort));
+ listener->start(poller, sa);
+}
+
+// Only used for outgoing connections (in federation)
+void RdmaIOProtocolFactory::rejected(Rdma::Connection::intrusive_ptr, const Rdma::ConnectionParams&, ConnectFailedCallback failed) {
+ failed(-1, "Connection rejected");
+}
+
+// Do the same as connection request and established but mark a client too
+void RdmaIOProtocolFactory::connected(Poller::shared_ptr poller, Rdma::Connection::intrusive_ptr ci, const Rdma::ConnectionParams& cp,
+ ConnectionCodec::Factory* f) {
+ (void) request(ci, cp, f);
+ established(poller, ci);
+ RdmaIOHandler* async = ci->getContext<RdmaIOHandler>();
+ async->initProtocolOut();
+}
+
+void RdmaIOProtocolFactory::connect(
+ Poller::shared_ptr poller,
+ const std::string& /*name*/,
+ const std::string& host, const std::string& port,
+ ConnectionCodec::Factory* f,
+ ConnectFailedCallback failed)
+{
+ Rdma::Connector* c =
+ new Rdma::Connector(
+ Rdma::ConnectionParams(8000, Rdma::DEFAULT_WR_ENTRIES),
+ boost::bind(&RdmaIOProtocolFactory::connected, this, poller, _1, _2, f),
+ boost::bind(&RdmaIOProtocolFactory::connectionError, this, _1, _2),
+ boost::bind(&RdmaIOProtocolFactory::disconnected, this, _1),
+ boost::bind(&RdmaIOProtocolFactory::rejected, this, _1, _2, failed));
+
+ SocketAddress sa(host, port);
+ c->start(poller, sa);
+}
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/Runnable.cpp b/qpid/cpp/src/qpid/sys/Runnable.cpp
new file mode 100644
index 0000000000..325d87c91b
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/Runnable.cpp
@@ -0,0 +1,32 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/sys/Runnable.h"
+#include <boost/bind.hpp>
+
+namespace qpid {
+namespace sys {
+
+Runnable::~Runnable() {}
+
+Runnable::Functor Runnable::functor()
+{
+ return boost::bind(&Runnable::run, this);
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/sys/Runnable.h b/qpid/cpp/src/qpid/sys/Runnable.h
new file mode 100644
index 0000000000..fed7663cb6
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/Runnable.h
@@ -0,0 +1,51 @@
+#ifndef _Runnable_
+#define _Runnable_
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <boost/function.hpp>
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+namespace sys {
+
+/**
+ * Interface for objects that can be run, e.g. in a thread.
+ */
+class QPID_COMMON_CLASS_EXTERN Runnable
+{
+ public:
+ /** Type to represent a runnable as a Functor */
+ typedef boost::function0<void> Functor;
+
+ QPID_COMMON_EXTERN virtual ~Runnable();
+
+ /** Derived classes override run(). */
+ virtual void run() = 0;
+
+ /** Create a functor object that will call this->run(). */
+ Functor functor();
+};
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/sys/ScopedIncrement.h b/qpid/cpp/src/qpid/sys/ScopedIncrement.h
new file mode 100644
index 0000000000..8645ab2484
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/ScopedIncrement.h
@@ -0,0 +1,67 @@
+#ifndef _posix_ScopedIncrement_h
+#define _posix_ScopedIncrement_h
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <boost/noncopyable.hpp>
+#include <boost/function.hpp>
+
+namespace qpid {
+namespace sys {
+
+/**
+ * Increment counter in constructor and decrement in destructor.
+ * Optionally call a function if the decremented counter value is 0.
+ * Note the function must not throw, it is called in the destructor.
+ */
+template <class T, class F=boost::function<void()> >
+class ScopedIncrement : boost::noncopyable
+{
+ public:
+ ScopedIncrement(T& c, F f=0)
+ : count(c), callback(f) { ++count; }
+ ~ScopedIncrement() { if (--count == 0 && callback) callback(); }
+
+ private:
+ T& count;
+ F callback;
+};
+
+
+/** Decrement counter in constructor and increment in destructor. */
+template <class T>
+class ScopedDecrement : boost::noncopyable
+{
+ public:
+ ScopedDecrement(T& c) : count(c) { value = --count; }
+ ~ScopedDecrement() { ++count; }
+
+ /** Return the value after the decrement. */
+ operator long() { return value; }
+
+ private:
+ T& count;
+ long value;
+};
+
+
+}}
+
+
+#endif // _posix_ScopedIncrement_h
diff --git a/qpid/cpp/src/qpid/sys/SecurityLayer.h b/qpid/cpp/src/qpid/sys/SecurityLayer.h
new file mode 100644
index 0000000000..317ada16de
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/SecurityLayer.h
@@ -0,0 +1,46 @@
+#ifndef QPID_SYS_SECURITYLAYER_H
+#define QPID_SYS_SECURITYLAYER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/sys/Codec.h"
+
+namespace qpid {
+namespace sys {
+
+/**
+ * Defines interface to a SASL negotiated Security Layer (for
+ * encryption/integrity)
+ */
+class SecurityLayer : public Codec
+{
+ public:
+ SecurityLayer(int ssf_) : ssf(ssf_) {}
+ int getSsf() const { return ssf; }
+ virtual void init(Codec*) = 0;
+ virtual ~SecurityLayer() {}
+ private:
+ int ssf;
+};
+
+}} // namespace qpid::sys
+
+#endif /*!QPID_SYS_SECURITYLAYER_H*/
diff --git a/qpid/cpp/src/qpid/sys/SecuritySettings.h b/qpid/cpp/src/qpid/sys/SecuritySettings.h
new file mode 100644
index 0000000000..d595cad660
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/SecuritySettings.h
@@ -0,0 +1,60 @@
+#ifndef QPID_SYS_SECURITYSETTINGS_H
+#define QPID_SYS_SECURITYSETTINGS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <string>
+
+namespace qpid {
+namespace sys {
+
+/**
+ * Conveys security information from a given transport to the upper
+ * layers.
+ */
+struct SecuritySettings
+{
+ /**
+ * Security Strength Factor (SSF). Possible values are:
+ *
+ * @li 0 No security
+ * @li 1 Integrity checking only
+ * @li >1 Integrity and confidentiality with the number
+ * giving the encryption key length.
+ */
+ unsigned int ssf;
+ /**
+ * An authorisation id
+ */
+ std::string authid;
+
+ /**
+ * Disables SASL mechanisms that are vulnerable to passive
+ * dictionary-based password attacks
+ */
+ bool nodict;
+
+ SecuritySettings() : ssf(0), nodict(false) {}
+};
+
+}} // namespace qpid::sys
+
+#endif /*!QPID_SYS_SECURITYSETTINGS_H*/
diff --git a/qpid/cpp/src/qpid/sys/Semaphore.h b/qpid/cpp/src/qpid/sys/Semaphore.h
new file mode 100644
index 0000000000..9d70f89aeb
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/Semaphore.h
@@ -0,0 +1,79 @@
+#ifndef _sys_Semaphore_h
+#define _sys_Semaphore_h
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/sys/Monitor.h"
+
+namespace qpid {
+namespace sys {
+
+class Semaphore
+{
+public:
+ Semaphore(uint c = 1) : count(c) {}
+
+ void lock() { acquire(); }
+ void unlock() { release(); }
+ bool trylock() { return tryAcquire(); }
+
+ bool tryAcquire()
+ {
+ Monitor::ScopedLock l(monitor);
+ if (count) {
+ count--;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ void acquire()
+ {
+ Monitor::ScopedLock l(monitor);
+ while (count == 0) monitor.wait();
+ count--;
+ }
+
+ void release(uint n)
+ {
+ Monitor::ScopedLock l(monitor);
+ if (count==0) monitor.notifyAll();
+ count+=n;
+ }
+
+ void release()
+ {
+ release(1);
+ }
+
+ void forceLock()
+ {
+ Monitor::ScopedLock l(monitor);
+ count = 0;
+ }
+
+private:
+ Monitor monitor;
+ uint count;
+};
+
+}}
+
+#endif /*!_sys_Semaphore_h*/
diff --git a/qpid/cpp/src/qpid/sys/Shlib.cpp b/qpid/cpp/src/qpid/sys/Shlib.cpp
new file mode 100644
index 0000000000..342d726876
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/Shlib.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/Shlib.h"
+
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace sys {
+
+AutoShlib::~AutoShlib() throw() {
+ try {
+ unload();
+ } catch(const std::exception& e) {
+ QPID_LOG(error, "Unloading shared library: " << e.what());
+ }
+}
+
+// Note: Other functions are defined in apr/Shlib.cpp or posix/Shlib.cpp.
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/Shlib.h b/qpid/cpp/src/qpid/sys/Shlib.h
new file mode 100644
index 0000000000..2ce5a49fcb
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/Shlib.h
@@ -0,0 +1,77 @@
+#ifndef QPID_SYS_SHLIB_H
+#define QPID_SYS_SHLIB_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/CommonImportExport.h"
+#include "qpid/sys/IntegerTypes.h"
+#include <boost/noncopyable.hpp>
+#include <iostream>
+
+namespace qpid {
+namespace sys {
+
+/** Encapsulates a shared library handle.
+ *@see AutoShlib
+ */
+class Shlib {
+ public:
+ /** Load a shared library */
+ Shlib(const char* libname) { load(libname); }
+
+ /** Load a shared library */
+ Shlib(const std::string& libname) { load(libname.c_str()); }
+
+ /** Unload shared library. */
+ QPID_COMMON_EXTERN void unload();
+
+ /** Look up symbol. */
+ QPID_COMMON_EXTERN void* getSymbol(const char* symbol);
+
+ /** Look up symbol in shared library, cast it to the desired
+ * pointer type, void* by default.
+ */
+ template <class T>
+ T getSymbol(const char* symbol) {
+ // Double cast avoids warning about casting object to function pointer
+ return reinterpret_cast<T>(reinterpret_cast<intptr_t>(
+ this->getSymbol(symbol)));
+ }
+
+ private:
+ void* handle;
+ QPID_COMMON_EXTERN void load(const char* libname);
+};
+
+/** A shared library handle that unloads the shlib in it's dtor */
+class AutoShlib : public Shlib {
+ public:
+ /** Load shared library */
+ AutoShlib(const std::string& libname) : Shlib(libname) {}
+ /** Calls unload() */
+ QPID_COMMON_EXTERN ~AutoShlib() throw();
+};
+
+
+}} // namespace qpid::sys
+
+#endif /*!QPID_SYS_SHLIB_H*/
diff --git a/qpid/cpp/src/qpid/sys/ShutdownHandler.h b/qpid/cpp/src/qpid/sys/ShutdownHandler.h
new file mode 100644
index 0000000000..88baecb5b6
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/ShutdownHandler.h
@@ -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.
+ *
+ */
+#ifndef _ShutdownHandler_
+#define _ShutdownHandler_
+
+namespace qpid {
+namespace sys {
+
+ class ShutdownHandler
+ {
+ public:
+ virtual void shutdown() = 0;
+ virtual ~ShutdownHandler(){}
+ };
+
+}
+}
+
+#endif
diff --git a/qpid/cpp/src/qpid/sys/Socket.h b/qpid/cpp/src/qpid/sys/Socket.h
new file mode 100644
index 0000000000..38183bd5fd
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/Socket.h
@@ -0,0 +1,100 @@
+#ifndef _sys_Socket_h
+#define _sys_Socket_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/CommonImportExport.h"
+#include <string>
+
+namespace qpid {
+namespace sys {
+
+class Duration;
+class IOHandle;
+class SocketAddress;
+
+class Socket
+{
+public:
+ virtual ~Socket() {};
+
+ virtual operator const IOHandle&() const = 0;
+
+ /** Set socket non blocking */
+ virtual void setNonblocking() const = 0;
+
+ virtual void setTcpNoDelay() const = 0;
+
+ virtual void connect(const SocketAddress&) const = 0;
+ virtual void finishConnect(const SocketAddress&) const = 0;
+
+ virtual void close() const = 0;
+
+ /** Bind to a port and start listening.
+ *@param port 0 means choose an available port.
+ *@param backlog maximum number of pending connections.
+ *@return The bound port.
+ */
+ virtual int listen(const SocketAddress&, int backlog = 10) const = 0;
+
+ /**
+ * Returns an address (host and port) for the remote end of the
+ * socket
+ */
+ virtual std::string getPeerAddress() const = 0;
+ /**
+ * Returns an address (host and port) for the local end of the
+ * socket
+ */
+ virtual std::string getLocalAddress() const = 0;
+
+ /**
+ * Returns the full address of the connection: local and remote host and port.
+ */
+ QPID_COMMON_INLINE_EXTERN std::string getFullAddress() const { return getLocalAddress()+"-"+getPeerAddress(); }
+
+ /**
+ * Returns the error code stored in the socket. This may be used
+ * to determine the result of a non-blocking connect.
+ */
+ virtual int getError() const = 0;
+
+ /** Accept a connection from a socket that is already listening
+ * and has an incoming connection
+ */
+ virtual Socket* accept() const = 0;
+
+ virtual int read(void *buf, size_t count) const = 0;
+ virtual int write(const void *buf, size_t count) const = 0;
+
+ /* Transport security related: */
+ virtual int getKeyLen() const = 0;
+ virtual std::string getClientAuthId() const = 0;
+};
+
+/** Make the default socket for whatever platform we are executing on
+ */
+QPID_COMMON_EXTERN Socket* createSocket();
+
+}}
+#endif /*!_sys_Socket_h*/
diff --git a/qpid/cpp/src/qpid/sys/SocketAddress.h b/qpid/cpp/src/qpid/sys/SocketAddress.h
new file mode 100644
index 0000000000..00b05f4c6c
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/SocketAddress.h
@@ -0,0 +1,82 @@
+#ifndef _sys_SocketAddress_h
+#define _sys_SocketAddress_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/CommonImportExport.h"
+#include <string>
+
+struct addrinfo;
+struct sockaddr;
+
+namespace qpid {
+namespace sys {
+
+class SocketAddress {
+ friend const ::addrinfo& getAddrInfo(const SocketAddress&);
+
+public:
+ /** Create a SocketAddress from hostname and port*/
+ QPID_COMMON_EXTERN SocketAddress(const std::string& host, const std::string& port);
+ QPID_COMMON_EXTERN SocketAddress(const SocketAddress&);
+ QPID_COMMON_EXTERN SocketAddress& operator=(const SocketAddress&);
+ QPID_COMMON_EXTERN ~SocketAddress();
+
+ QPID_COMMON_EXTERN void firstAddress() const;
+ QPID_COMMON_EXTERN bool nextAddress() const;
+ QPID_COMMON_EXTERN std::string asString(bool numeric=true,
+ bool dispNameOnly=false,
+ bool hideDecoration=false) const;
+ QPID_COMMON_EXTERN std::string getHost() const;
+
+ QPID_COMMON_EXTERN static std::string asString(::sockaddr const * const addr,
+ size_t addrlen,
+ bool dispNameOnly=false,
+ bool hideDecoration=false);
+ QPID_COMMON_EXTERN static uint16_t getPort(::sockaddr const * const addr);
+
+ QPID_COMMON_EXTERN bool isIp() const;
+
+ QPID_COMMON_EXTERN std::string comparisonDetails(const SocketAddress& ) const;
+
+ QPID_COMMON_EXTERN bool isComparable(const SocketAddress& saHi) const;
+
+ QPID_COMMON_EXTERN bool inRange(const SocketAddress& ,
+ const SocketAddress& ) const;
+ QPID_COMMON_EXTERN bool inRange(const struct addrinfo&,
+ const struct addrinfo&,
+ const struct addrinfo&) const;
+
+ QPID_COMMON_EXTERN bool compareAddresses(const struct addrinfo&,
+ const struct addrinfo&,
+ int&) const;
+
+private:
+ std::string host;
+ std::string port;
+ mutable ::addrinfo* addrInfo;
+ mutable ::addrinfo* currentAddrInfo;
+};
+
+}}
+#endif /*!_sys_SocketAddress_h*/
diff --git a/qpid/cpp/src/qpid/sys/SocketTransport.cpp b/qpid/cpp/src/qpid/sys/SocketTransport.cpp
new file mode 100644
index 0000000000..86c9d301e9
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/SocketTransport.cpp
@@ -0,0 +1,221 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+// Turn off unintialised warnings as errors when compiling under Red Enterprise Linux 6
+// as an unitialised variable warning is unavoidable there.
+#if __GNUC__ == 4 && __GNUC_MINOR__ == 4
+#pragma GCC diagnostic warning "-Wuninitialized"
+#endif
+
+#include "qpid/sys/SocketTransport.h"
+
+#include "qpid/broker/NameGenerator.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/AsynchIOHandler.h"
+#include "qpid/sys/AsynchIO.h"
+#include "qpid/sys/Socket.h"
+#include "qpid/sys/SocketAddress.h"
+#include "qpid/sys/SystemInfo.h"
+
+#include <boost/bind.hpp>
+#include <boost/lexical_cast.hpp>
+
+namespace qpid {
+namespace sys {
+
+namespace {
+ void establishedCommon(
+ AsynchIOHandler* async,
+ boost::shared_ptr<Poller> poller, const SocketTransportOptions& opts, Timer* timer,
+ const Socket& s)
+ {
+ if (opts.tcpNoDelay) {
+ s.setTcpNoDelay();
+ QPID_LOG(info, "Set TCP_NODELAY on connection to " << s.getPeerAddress());
+ }
+
+ AsynchIO* aio = AsynchIO::create
+ (s,
+ boost::bind(&AsynchIOHandler::readbuff, async, _1, _2),
+ boost::bind(&AsynchIOHandler::eof, async, _1),
+ boost::bind(&AsynchIOHandler::disconnect, async, _1),
+ boost::bind(&AsynchIOHandler::closedSocket, async, _1, _2),
+ boost::bind(&AsynchIOHandler::nobuffs, async, _1),
+ boost::bind(&AsynchIOHandler::idle, async, _1));
+
+ async->init(aio, *timer, opts.maxNegotiateTime);
+ aio->start(poller);
+ }
+
+ void establishedIncoming(
+ boost::shared_ptr<Poller> poller, const SocketTransportOptions& opts, Timer* timer,
+ const Socket& s, ConnectionCodec::Factory* f)
+ {
+ AsynchIOHandler* async = new AsynchIOHandler(broker::QPID_NAME_PREFIX+s.getFullAddress(), f, false, opts.nodict);
+ establishedCommon(async, poller, opts, timer, s);
+ }
+
+ void establishedOutgoing(
+ boost::shared_ptr<Poller> poller, const SocketTransportOptions& opts, Timer* timer,
+ const Socket& s, ConnectionCodec::Factory* f, const std::string& name)
+ {
+ AsynchIOHandler* async = new AsynchIOHandler(name, f, true, opts.nodict);
+ establishedCommon(async, poller, opts, timer, s);
+ }
+
+ void connectFailed(
+ const Socket& s, int ec, const std::string& emsg,
+ SocketConnector::ConnectFailedCallback failedCb)
+ {
+ failedCb(ec, emsg);
+ s.close();
+ delete &s;
+ }
+
+ // Expand list of Interfaces and addresses to a list of addresses
+ std::vector<std::string> expandInterfaces(const std::vector<std::string>& interfaces) {
+ std::vector<std::string> addresses;
+ // If there are no specific interfaces listed use a single "" to listen on every interface
+ if (interfaces.empty()) {
+ addresses.push_back("");
+ return addresses;
+ }
+ for (unsigned i = 0; i < interfaces.size(); ++i) {
+ const std::string& interface = interfaces[i];
+ if (!(SystemInfo::getInterfaceAddresses(interface, addresses))) {
+ // We don't have an interface of that name -
+ // Check for IPv6 ('[' ']') brackets and remove them
+ // then pass to be looked up directly
+ if (interface[0]=='[' && interface[interface.size()-1]==']') {
+ addresses.push_back(interface.substr(1, interface.size()-2));
+ } else {
+ addresses.push_back(interface);
+ }
+ }
+ }
+ return addresses;
+ }
+}
+
+SocketAcceptor::SocketAcceptor(bool tcpNoDelay, bool nodict, uint32_t maxNegotiateTime, Timer& timer0) :
+ timer(timer0),
+ options(tcpNoDelay, nodict, maxNegotiateTime),
+ established(boost::bind(&establishedIncoming, _1, options, &timer, _2, _3))
+{}
+
+SocketAcceptor::SocketAcceptor(bool tcpNoDelay, bool nodict, uint32_t maxNegotiateTime, Timer& timer0, const EstablishedCallback& established0) :
+ timer(timer0),
+ options(tcpNoDelay, nodict, maxNegotiateTime),
+ established(established0)
+{}
+
+void SocketAcceptor::addListener(Socket* socket)
+{
+ listeners.push_back(socket);
+}
+
+uint16_t SocketAcceptor::listen(const std::vector<std::string>& interfaces, uint16_t port, int backlog, const SocketFactory& factory)
+{
+ std::vector<std::string> addresses = expandInterfaces(interfaces);
+ std::string sport(boost::lexical_cast<std::string>(port));
+
+ if (addresses.empty()) {
+ // We specified some interfaces, but couldn't find addresses for them
+ QPID_LOG(warning, "TCP/TCP6: No specified network interfaces found: Not Listening");
+ return 0;
+ }
+
+ int listeningPort = 0;
+ for (unsigned i = 0; i<addresses.size(); ++i) {
+ QPID_LOG(debug, "Using interface: " << addresses[i]);
+ SocketAddress sa(addresses[i], sport);
+
+ do {
+ try {
+ // If we were told to figure out the port then only allow listening to one address
+ if (port==0 && listeningPort!=0) {
+ // Print warning if the user specified more than one interface
+ QPID_LOG(warning, "Specified port=0: Only listened to: " << sa.asString());
+ return listeningPort;
+ }
+
+ QPID_LOG(info, "Listening to: " << sa.asString());
+ std::auto_ptr<Socket> s(factory());
+ uint16_t lport = s->listen(sa, backlog);
+ QPID_LOG(debug, "Listened to: " << lport);
+ addListener(s.release());
+
+ if (listeningPort==0) listeningPort = lport;
+ } catch (std::exception& e) {
+ QPID_LOG(warning, "Couldn't listen to: " << sa.asString() << ": " << e.what());
+ }
+ } while (sa.nextAddress());
+ }
+ if (listeningPort==0) {
+ throw Exception("Couldn't find any network address to listen to");
+ }
+ return listeningPort;
+}
+
+void SocketAcceptor::accept(boost::shared_ptr<Poller> poller, ConnectionCodec::Factory* f)
+{
+ for (unsigned i = 0; i<listeners.size(); ++i) {
+ acceptors.push_back(
+ AsynchAcceptor::create(listeners[i], boost::bind(established, poller, _1, f)));
+ acceptors[i].start(poller);
+ }
+}
+
+SocketConnector::SocketConnector(bool tcpNoDelay, bool nodict, uint32_t maxNegotiateTime, Timer& timer0, const SocketFactory& factory0) :
+ timer(timer0),
+ factory(factory0),
+ options(tcpNoDelay, nodict, maxNegotiateTime)
+{}
+
+void SocketConnector::connect(
+ boost::shared_ptr<Poller> poller,
+ const std::string& name,
+ const std::string& host, const std::string& port,
+ ConnectionCodec::Factory* fact,
+ ConnectFailedCallback failed)
+{
+ // Note that the following logic does not cause a memory leak.
+ // The allocated Socket is freed either by the AsynchConnector
+ // upon connection failure or by the AsynchIO upon connection
+ // shutdown. The allocated AsynchConnector frees itself when it
+ // is no longer needed.
+ Socket* socket = factory();
+ try {
+ AsynchConnector* c = AsynchConnector::create(
+ *socket,
+ host,
+ port,
+ boost::bind(&establishedOutgoing, poller, options, &timer, _1, fact, name),
+ boost::bind(&connectFailed, _1, _2, _3, failed));
+ c->start(poller);
+ } catch (std::exception&) {
+ // TODO: Design question - should we do the error callback and also throw?
+ int errCode = socket->getError();
+ connectFailed(*socket, errCode, strError(errCode), failed);
+ throw;
+ }
+}
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/SocketTransport.h b/qpid/cpp/src/qpid/sys/SocketTransport.h
new file mode 100644
index 0000000000..5aca52c372
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/SocketTransport.h
@@ -0,0 +1,91 @@
+#ifndef QPID_SYS_SOCKETTRANSPORT_H
+#define QPID_SYS_SOCKETTRANSPORT_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/TransportFactory.h"
+
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/sys/ConnectionCodec.h"
+#include <boost/ptr_container/ptr_vector.hpp>
+#include <boost/function.hpp>
+
+namespace qpid {
+namespace sys {
+
+class AsynchAcceptor;
+class Poller;
+class Timer;
+class Socket;
+typedef boost::function0<Socket*> SocketFactory;
+typedef boost::function3<void, boost::shared_ptr<Poller>, const Socket&, ConnectionCodec::Factory*> EstablishedCallback;
+
+struct SocketTransportOptions {
+ bool tcpNoDelay;
+ bool nodict;
+ uint32_t maxNegotiateTime;
+
+ SocketTransportOptions(bool t, bool d, uint32_t m) :
+ tcpNoDelay(t),
+ nodict(d),
+ maxNegotiateTime(m)
+ {}
+};
+
+class SocketAcceptor : public TransportAcceptor {
+ boost::ptr_vector<Socket> listeners;
+ boost::ptr_vector<AsynchAcceptor> acceptors;
+ Timer& timer;
+ SocketTransportOptions options;
+ const EstablishedCallback established;
+
+public:
+ SocketAcceptor(bool tcpNoDelay, bool nodict, uint32_t maxNegotiateTime, Timer& timer);
+ SocketAcceptor(bool tcpNoDelay, bool nodict, uint32_t maxNegotiateTime, Timer& timer, const EstablishedCallback& established);
+
+ // Create sockets from list of interfaces and listen to them
+ uint16_t listen(const std::vector<std::string>& interfaces, uint16_t port, int backlog, const SocketFactory& factory);
+
+ // Import sockets that are already being listened to
+ void addListener(Socket* socket);
+
+ void accept(boost::shared_ptr<Poller> poller, ConnectionCodec::Factory* f);
+};
+
+class SocketConnector : public TransportConnector {
+ Timer& timer;
+ const SocketFactory factory;
+ SocketTransportOptions options;
+
+public:
+ SocketConnector(bool tcpNoDelay, bool nodict, uint32_t maxNegotiateTime, Timer& timer, const SocketFactory& factory);
+
+ void connect(boost::shared_ptr<Poller> poller,
+ const std::string& name,
+ const std::string& host, const std::string& port,
+ ConnectionCodec::Factory* f,
+ ConnectFailedCallback failed);
+};
+
+}}
+
+#endif
diff --git a/qpid/cpp/src/qpid/sys/SslPlugin.cpp b/qpid/cpp/src/qpid/sys/SslPlugin.cpp
new file mode 100644
index 0000000000..0ec31b1e47
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/SslPlugin.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/TransportFactory.h"
+
+#include "qpid/Plugin.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/AsynchIO.h"
+#include "qpid/sys/SocketTransport.h"
+#include "qpid/sys/ssl/util.h"
+#include "qpid/sys/ssl/SslSocket.h"
+
+#include <boost/bind.hpp>
+
+namespace qpid {
+namespace sys {
+
+class Timer;
+
+using namespace qpid::sys::ssl;
+
+struct SslServerOptions : ssl::SslOptions
+{
+ uint16_t port;
+ bool clientAuth;
+ bool nodict;
+
+ SslServerOptions() : port(5671),
+ clientAuth(false),
+ nodict(false)
+ {
+ addOptions()
+ ("ssl-port", optValue(port, "PORT"), "Port on which to listen for SSL connections")
+ ("ssl-require-client-authentication", optValue(clientAuth),
+ "Forces clients to authenticate in order to establish an SSL connection")
+ ("ssl-sasl-no-dict", optValue(nodict),
+ "Disables SASL mechanisms that are vulnerable to passive dictionary-based password attacks");
+ }
+};
+
+namespace {
+ Socket* createServerSSLSocket(const SslServerOptions& options) {
+ return new SslSocket(options.certName, options.clientAuth);
+ }
+
+ Socket* createServerSSLMuxSocket(const SslServerOptions& options) {
+ return new SslMuxSocket(options.certName, options.clientAuth);
+ }
+
+ Socket* createClientSSLSocket() {
+ return new SslSocket();
+ }
+
+}
+
+// Static instance to initialise plugin
+static struct SslPlugin : public Plugin {
+ SslServerOptions options;
+ bool nssInitialized;
+ bool multiplex;
+
+ Options* getOptions() { return &options; }
+
+ SslPlugin() : nssInitialized(false), multiplex(false) {}
+ ~SslPlugin() { if (nssInitialized) ssl::shutdownNSS(); }
+
+ void earlyInitialize(Target& target) {
+ broker::Broker* broker = dynamic_cast<broker::Broker*>(&target);
+ if (broker && broker->shouldListen("ssl")) {
+ if (options.certDbPath.empty()) {
+ QPID_LOG(notice, "SSL plugin not enabled, you must set --ssl-cert-db to enable it.");
+ broker->disableListening("ssl");
+ return;
+ }
+
+ try {
+ ssl::initNSS(options, true);
+ nssInitialized = true;
+ } catch (const std::exception& e) {
+ QPID_LOG(error, "Failed to initialise SSL plugin: " << e.what());
+ broker->disableListening("ssl");
+ return;
+ }
+
+ if (broker->getPortOption() == options.port && // AMQP & AMQPS ports are the same
+ broker->getPortOption() != 0 &&
+ broker->shouldListen("tcp")) {
+ multiplex = true;
+ broker->disableListening("tcp");
+ }
+ }
+ }
+
+ void initialize(Target& target) {
+ QPID_LOG(trace, "Initialising SSL plugin");
+ broker::Broker* broker = dynamic_cast<broker::Broker*>(&target);
+ // Only provide to a Broker
+ if (broker) {
+ uint16_t port = options.port;
+ TransportAcceptor::shared_ptr ta;
+ if (broker->shouldListen("ssl")) {
+ SocketAcceptor* sa =
+ new SocketAcceptor(broker->getTcpNoDelay(), options.nodict, broker->getMaxNegotiateTime(), broker->getTimer());
+ port = sa->listen(broker->getListenInterfaces(), options.port, broker->getConnectionBacklog(),
+ multiplex ?
+ boost::bind(&createServerSSLMuxSocket, options) :
+ boost::bind(&createServerSSLSocket, options));
+ if ( port!=0 ) {
+ ta.reset(sa);
+ QPID_LOG(notice, "Listening for " <<
+ (multiplex ? "SSL or TCP" : "SSL") <<
+ " connections on TCP/TCP6 port " <<
+ port);
+ }
+ }
+ TransportConnector::shared_ptr tc(
+ new SocketConnector(broker->getTcpNoDelay(), options.nodict, broker->getMaxNegotiateTime(), broker->getTimer(),
+ &createClientSSLSocket));
+ broker->registerTransport("ssl", ta, tc, port);
+ }
+ }
+} sslPlugin;
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/StateMonitor.h b/qpid/cpp/src/qpid/sys/StateMonitor.h
new file mode 100644
index 0000000000..eac37a8543
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/StateMonitor.h
@@ -0,0 +1,78 @@
+#ifndef QPID_SYS_STATEMONITOR_H
+#define QPID_SYS_STATEMONITOR_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/sys/Waitable.h"
+
+#include <bitset>
+
+namespace qpid {
+namespace sys {
+
+/**
+ * A monitor with an enum state value.
+ *
+ *@param Enum: enum type to use for states.
+ *@param EnumMax: Highest enum value.
+ */
+template <class Enum, size_t MaxEnum>
+class StateMonitor : public Waitable
+{
+ public:
+ struct Set : public std::bitset<MaxEnum + 1> {
+ Set() {}
+ Set(Enum s) { set(s); }
+ Set(Enum s, Enum t) { std::bitset<MaxEnum + 1>::set(s).set(t); }
+ Set(Enum s, Enum t, Enum u) { std::bitset<MaxEnum + 1>::set(s).set(t).set(u); }
+ Set(Enum s, Enum t, Enum u, Enum v) { std::bitset<MaxEnum + 1>::set(s).set(t).set(u).set(v); }
+ };
+
+
+ StateMonitor(Enum initial) { state=initial; }
+
+ /** @pre Caller holds a ScopedLock. */
+ void set(Enum s) { state=s; notifyAll(); }
+ /** @pre Caller holds a ScopedLock. */
+ StateMonitor& operator=(Enum s) { set(s); return *this; }
+
+ /** @pre Caller holds a ScopedLock. */
+ Enum get() const { return state; }
+ /** @pre Caller holds a ScopedLock. */
+ operator Enum() const { return state; }
+
+ /** @pre Caller holds a ScopedLock */
+ void waitFor(Enum s) { ScopedWait w(*this); while (s != state) wait(); }
+ /** @pre Caller holds a ScopedLock */
+ void waitFor(Set s) { ScopedWait w(*this); while (!s.test(state)) wait(); }
+ /** @pre Caller holds a ScopedLock */
+ void waitNot(Enum s) { ScopedWait w(*this); while (s == state) wait(); }
+ /** @pre Caller holds a ScopedLock */
+ void waitNot(Set s) { ScopedWait w(*this); while (s.test(state)) wait(); }
+
+ private:
+ Enum state;
+};
+
+}}
+
+
+#endif /*!QPID_SYS_STATEMONITOR_H*/
diff --git a/qpid/cpp/src/qpid/sys/StrError.h b/qpid/cpp/src/qpid/sys/StrError.h
new file mode 100644
index 0000000000..36489dd0fc
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/StrError.h
@@ -0,0 +1,36 @@
+#ifndef _sys_StrError_h
+#define _sys_StrError_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <string>
+#include "qpid/CommonImportExport.h"
+
+namespace qpid {
+namespace sys {
+
+/** Get the error message for a system number err, e.g. errno. */
+QPID_COMMON_EXTERN std::string strError(int err);
+
+}} // namespace qpid
+
+#endif // _sys_StrError_h
diff --git a/qpid/cpp/src/qpid/sys/SystemInfo.h b/qpid/cpp/src/qpid/sys/SystemInfo.h
new file mode 100644
index 0000000000..1b5720a5f0
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/SystemInfo.h
@@ -0,0 +1,109 @@
+#ifndef QPID_SYS_SYSTEMINFO_H
+#define QPID_SYS_SYSTEMINFO_H
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/Address.h"
+#include "qpid/CommonImportExport.h"
+#include <vector>
+
+namespace qpid {
+namespace sys {
+
+/**
+ * Retrieve information about the system we are running on.
+ * Results may be dependent on OS/hardware.
+ */
+namespace SystemInfo {
+/**
+ * Estimate available concurrency, e.g. number of CPU cores.
+ * -1 means estimate not available on this platform.
+ */
+QPID_COMMON_EXTERN long concurrency();
+
+/**
+ * Get the local host name and set it in the specified.
+ * Returns false if it can't be obtained and sets errno to any error value.
+ */
+QPID_COMMON_EXTERN bool getLocalHostname (Address &address);
+
+/**
+ * Get the names of all the network interfaces connected to
+ * this host.
+ * @param names Receives the list of interface names
+ */
+QPID_COMMON_EXTERN void getInterfaceNames(std::vector<std::string>& names );
+
+/**
+ * Get strings for each of the IP addresses associated with a named network
+ * interface.
+ * If there is no interface of that name an empty list will be returned.
+ *
+ * @param interface The name of the network interface
+ * @param addresses The list of the strings for the IP addresses are pushed on the back of this parameter
+ * to get just the list you need to clear the vector before using it.
+ * @return true if an interface of the correct name was found, false otherwise
+ */
+QPID_COMMON_EXTERN bool getInterfaceAddresses(const std::string& interface, std::vector<std::string>& addresses);
+
+/**
+ * Retrieve system identifiers and versions. This is information that can
+ * generally be retrieved via POSIX uname().
+ *
+ * @param osName Receives the OS name; e.g., GNU/Linux or Windows
+ * @param nodeName Receives the nodename. This may or may not match the
+ * set hostname from getLocalHostname().
+ * @param release Receives the OS release identifier.
+ * @param version Receives the OS release version (kernel, build, sp, etc.)
+ * @param machine Receives the hardware type.
+ */
+QPID_COMMON_EXTERN void getSystemId (std::string &osName,
+ std::string &nodeName,
+ std::string &release,
+ std::string &version,
+ std::string &machine);
+
+/**
+ * Get the process ID of the current process.
+ */
+QPID_COMMON_EXTERN uint32_t getProcessId();
+
+/**
+ * Get the process ID of the parent of the current process.
+ */
+QPID_COMMON_EXTERN uint32_t getParentProcessId();
+
+/**
+ * Get the name of the current process (i.e. the name of the executable)
+ */
+QPID_COMMON_EXTERN std::string getProcessName();
+
+/**
+ * Can thread related primitives be trusted during runtime house-cleaning?
+ * (i.e. static destructors, atexit()).
+ */
+QPID_COMMON_EXTERN bool threadSafeShutdown();
+
+
+}}} // namespace qpid::sys::SystemInfo
+
+#endif /*!QPID_SYS_SYSTEMINFO_H*/
diff --git a/qpid/cpp/src/qpid/sys/TCPIOPlugin.cpp b/qpid/cpp/src/qpid/sys/TCPIOPlugin.cpp
new file mode 100644
index 0000000000..9e240ad7e3
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/TCPIOPlugin.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/TransportFactory.h"
+
+#include "qpid/Plugin.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/AsynchIO.h"
+#include "qpid/sys/Socket.h"
+#include "qpid/sys/SocketTransport.h"
+
+namespace qpid {
+namespace sys {
+
+// Static instance to initialise plugin
+static class TCPIOPlugin : public Plugin {
+ void earlyInitialize(Target&) {
+ }
+
+ void initialize(Target& target) {
+ broker::Broker* broker = dynamic_cast<broker::Broker*>(&target);
+ // Only provide to a Broker
+ if (broker) {
+ uint16_t port = broker->getPortOption();
+ TransportAcceptor::shared_ptr ta;
+ if (broker->shouldListen("tcp")) {
+ SocketAcceptor* aa = new SocketAcceptor(broker->getTcpNoDelay(), false, broker->getMaxNegotiateTime(), broker->getTimer());
+ ta.reset(aa);
+ port = aa->listen(broker->getListenInterfaces(), port, broker->getConnectionBacklog(), &createSocket);
+ if ( port!=0 ) {
+ QPID_LOG(notice, "Listening on TCP/TCP6 port " << port);
+ }
+ }
+
+ TransportConnector::shared_ptr tc(new SocketConnector(broker->getTcpNoDelay(), false, broker->getMaxNegotiateTime(), broker->getTimer(), &createSocket));
+
+ broker->registerTransport("tcp", ta, tc, port);
+ }
+ }
+} tcpPlugin;
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/Thread.h b/qpid/cpp/src/qpid/sys/Thread.h
new file mode 100644
index 0000000000..cb0a1adce6
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/Thread.h
@@ -0,0 +1,73 @@
+#ifndef _sys_Thread_h
+#define _sys_Thread_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <boost/shared_ptr.hpp>
+#include "qpid/CommonImportExport.h"
+
+#ifdef _WIN32
+# ifdef _MSC_VER
+# define QPID_TSS __declspec(thread)
+# else
+# define QPID_TSS __thread
+# endif
+#elif defined (__GNUC__)
+# define QPID_TSS __thread
+#elif defined (__SUNPRO_CC)
+# define QPID_TSS __thread
+#elif defined (__IBMCPP__)
+# define QPID_TSS __thread
+#else
+# error "Dont know how to define QPID_TSS for this platform"
+#endif
+
+namespace qpid {
+namespace sys {
+
+class Runnable;
+class ThreadPrivate;
+
+class Thread
+{
+ boost::shared_ptr<ThreadPrivate> impl;
+
+ public:
+ QPID_COMMON_EXTERN Thread();
+ QPID_COMMON_EXTERN explicit Thread(qpid::sys::Runnable*);
+ QPID_COMMON_EXTERN explicit Thread(qpid::sys::Runnable&);
+
+ QPID_COMMON_EXTERN operator bool();
+ QPID_COMMON_EXTERN bool operator==(const Thread&) const;
+ QPID_COMMON_EXTERN bool operator!=(const Thread&) const;
+
+ QPID_COMMON_EXTERN void join();
+
+ QPID_COMMON_EXTERN static Thread current();
+
+ /** ID of current thread for logging.
+ * Workaround for broken Thread::current() in APR
+ */
+ QPID_COMMON_EXTERN static unsigned long logId();
+};
+
+}}
+#endif /*!_sys_Thread_h*/
diff --git a/qpid/cpp/src/qpid/sys/Time.h b/qpid/cpp/src/qpid/sys/Time.h
new file mode 100644
index 0000000000..8a82b5f826
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/Time.h
@@ -0,0 +1,181 @@
+#ifndef _sys_Time_h
+#define _sys_Time_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/IntegerTypes.h"
+/*
+ * The platform defines its notion of time as a TimePrivate type. The
+ * platform's implementation knows how to handle this type.
+ */
+#if defined (_WIN32)
+# include "windows/Time.h"
+#else
+# include "posix/Time.h"
+#endif
+
+#include "qpid/CommonImportExport.h"
+
+#include <limits>
+#include <iosfwd>
+
+namespace qpid {
+namespace sys {
+
+class Duration;
+
+/**
+ * @class AbsTime
+ *
+ * Class to represent an instant in time.
+ *
+ * The time resolution is in nanosecs, and this is held with 64 bits
+ * giving a total time span from about 25 million years ago to 25 million
+ * years hence. As an aside the internal time can sensibly be negative
+ * meaning before the epoch (probably 1/1/1970 although this class doesn't
+ * care).
+ *
+ * The AbsTime class is a value class and so you don't need to add any
+ * accessors to its internal state. If you think you want to replace its value,
+ * you need to construct a new AbsTime and assign it, viz:
+ *
+ * AbsTime when = now();
+ * ...
+ * when = AbsTime(when, 2*TIME_SEC); // Advance timer 2 secs
+ *
+ * AbsTime is not intended to be used to represent calendar dates/times.
+ * There is a specific way to construct a Duration since the Unix Epoch,
+ * 1970-1-1-00:00:
+ *
+ * int64_t nanosec_since_epoch = Duration::FromEpoch();
+ *
+ * There are some sensible operations that are currently missing from
+ * AbsTime, but nearly all that's needed can be done with a mixture of
+ * AbsTimes and Durations.
+ *
+ * For example, convenience operators to add a Duration and AbsTime returning
+ * an AbsTime would fit here (although you can already perform the operation
+ * with one of the AbsTime constructors). However trying to add 2 AbsTimes
+ * doesn't make sense.
+ */
+class AbsTime {
+ friend class Duration;
+ friend class Condition;
+
+ TimePrivate timepoint;
+
+public:
+
+ inline AbsTime() : timepoint() {}
+ QPID_COMMON_EXTERN AbsTime(const AbsTime& time0, const Duration& duration);
+ // Default assignment operation fine
+ // Default copy constructor fine
+
+ QPID_COMMON_EXTERN static AbsTime now();
+ QPID_COMMON_EXTERN static AbsTime epoch();
+ QPID_COMMON_EXTERN static AbsTime FarFuture();
+ QPID_COMMON_EXTERN static AbsTime Zero();
+
+ bool operator==(const AbsTime& t) const { return t.timepoint == timepoint; }
+
+ friend bool operator<(const AbsTime& a, const AbsTime& b);
+ friend bool operator>(const AbsTime& a, const AbsTime& b);
+ QPID_COMMON_EXTERN friend std::ostream& operator << (std::ostream&, const AbsTime&);
+};
+
+QPID_COMMON_EXTERN std::ostream& operator << (std::ostream&, const AbsTime&);
+
+/**
+ * @class Duration
+ * Class to represent the duration between instants of time.
+ *
+ * As AbsTime, this class also uses nanosecs for its time
+ * resolution where possible. For the most part a duration can be dealt
+ * with like a 64 bit integer, and indeed there is an implicit conversion which
+ * makes this quite convenient.
+ */
+class Duration {
+ static int64_t max() { return std::numeric_limits<int64_t>::max(); }
+ int64_t nanosecs;
+
+ friend class AbsTime;
+
+public:
+ QPID_COMMON_INLINE_EXTERN inline Duration(int64_t time0 = 0);
+ QPID_COMMON_EXTERN explicit Duration(const AbsTime& start, const AbsTime& finish);
+
+ /** Duration since the Unix epoch: 1970-01-01T00:00:00 */
+ QPID_COMMON_EXTERN static Duration FromEpoch();
+
+ inline operator int64_t() const;
+};
+
+QPID_COMMON_EXTERN std::ostream& operator << (std::ostream&, const Duration&);
+QPID_COMMON_EXTERN std::istream& operator >> (std::istream&, Duration&);
+
+inline AbsTime now() { return AbsTime::now(); }
+
+inline bool operator<(const AbsTime& a, const AbsTime& b)
+{ return a.timepoint < b.timepoint; }
+inline bool operator>(const AbsTime& a, const AbsTime& b)
+{ return a.timepoint > b.timepoint; }
+
+Duration::Duration(int64_t time0) :
+ nanosecs(time0)
+{}
+
+Duration::operator int64_t() const
+{ return nanosecs; }
+
+/** Nanoseconds per second. */
+const Duration TIME_SEC = 1000*1000*1000;
+/** Nanoseconds per millisecond */
+const Duration TIME_MSEC = 1000*1000;
+/** Nanoseconds per microseconds. */
+const Duration TIME_USEC = 1000;
+/** Nanoseconds per nanosecond. */
+const Duration TIME_NSEC = 1;
+
+/** Value to represent an infinite timeout */
+const Duration TIME_INFINITE = std::numeric_limits<int64_t>::max();
+
+/** Absolute time zero point */
+const AbsTime ZERO = AbsTime::Zero();
+
+/** Time greater than any other time */
+const AbsTime FAR_FUTURE = AbsTime::FarFuture();
+
+/** Portable sleep for a number of seconds */
+QPID_COMMON_EXTERN void sleep(int secs);
+
+/** Portable sleep for a number of microseconds */
+QPID_COMMON_EXTERN void usleep(uint64_t usecs);
+
+/** Output formatted date/time for now*/
+void outputFormattedNow(std::ostream&);
+
+/** Output unformatted nanosecond-resolution time for now */
+void outputHiresNow(std::ostream&);
+
+}}
+
+#endif /*!_sys_Time_h*/
diff --git a/qpid/cpp/src/qpid/sys/Timer.cpp b/qpid/cpp/src/qpid/sys/Timer.cpp
new file mode 100644
index 0000000000..f8eef2c9ec
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/Timer.cpp
@@ -0,0 +1,235 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/sys/Timer.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/log/Statement.h"
+
+#include <numeric>
+
+using boost::intrusive_ptr;
+using std::max;
+
+namespace qpid {
+namespace sys {
+
+TimerTask::TimerTask(Duration timeout, const std::string& n) :
+ name(n),
+ sortTime(AbsTime::FarFuture()),
+ period(timeout),
+ nextFireTime(AbsTime::now(), timeout),
+ state(WAITING)
+{}
+
+TimerTask::TimerTask(AbsTime time, const std::string& n) :
+ name(n),
+ sortTime(AbsTime::FarFuture()),
+ period(0),
+ nextFireTime(time),
+ state(WAITING)
+{}
+
+TimerTask::~TimerTask() {}
+
+bool TimerTask::readyToFire() const {
+ return !(nextFireTime > AbsTime::now());
+}
+
+bool TimerTask::prepareToFire() {
+ Monitor::ScopedLock l(stateMonitor);
+ if (state != CANCELLED) {
+ state = CALLING;
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void TimerTask::fireTask() {
+ fire();
+}
+
+void TimerTask::finishFiring() {
+ Monitor::ScopedLock l(stateMonitor);
+ if (state != CANCELLED) {
+ state = WAITING;
+ stateMonitor.notifyAll();
+ }
+}
+
+// This can only be used to setup the next fire time. After the Timer has already fired
+void TimerTask::setupNextFire() {
+ if (period && readyToFire()) {
+ nextFireTime = max(AbsTime::now(), AbsTime(nextFireTime, period));
+ } else {
+ QPID_LOG(error, name << " couldn't setup next timer firing: " << Duration(nextFireTime, AbsTime::now()) << "[" << period << "]");
+ }
+}
+
+// Only allow tasks to be delayed
+void TimerTask::restart() {
+ nextFireTime = max(nextFireTime, AbsTime(AbsTime::now(), period));
+}
+
+void TimerTask::cancel() {
+ Monitor::ScopedLock l(stateMonitor);
+ while (state == CALLING) {
+ stateMonitor.wait();
+ }
+ state = CANCELLED;
+}
+
+// TODO AStitcher 21/08/09 The threshholds for emitting warnings are a little arbitrary
+Timer::Timer() :
+ active(false),
+ late(50 * TIME_MSEC),
+ overran(2 * TIME_MSEC),
+ lateCancel(500 * TIME_MSEC),
+ warn(60 * TIME_SEC)
+{
+ start();
+}
+
+Timer::~Timer()
+{
+ stop();
+}
+
+class TimerTaskCallbackScope {
+ TimerTask& tt;
+public:
+ explicit TimerTaskCallbackScope(TimerTask& t) :
+ tt(t)
+ {}
+
+ operator bool() {
+ return !tt.prepareToFire();
+ }
+
+ ~TimerTaskCallbackScope() {
+ tt.finishFiring();
+ }
+};
+
+void Timer::run()
+{
+ Monitor::ScopedLock l(monitor);
+ while (active) {
+ if (tasks.empty()) {
+ monitor.wait();
+ } else {
+ intrusive_ptr<TimerTask> t = tasks.top();
+ tasks.pop();
+ assert(!(t->nextFireTime < t->sortTime));
+
+ // warn on extreme lateness
+ AbsTime start(AbsTime::now());
+ Duration delay(t->sortTime, start);
+ {
+ TimerTaskCallbackScope s(*t);
+ if (s) {
+ if (delay > lateCancel) {
+ QPID_LOG(debug, t->name << " cancelled timer woken up " <<
+ delay / TIME_MSEC << "ms late");
+ }
+ continue;
+ } else if(Duration(t->nextFireTime, start) >= 0) {
+ {
+ Monitor::ScopedUnlock u(monitor);
+ fire(t);
+ }
+ // Warn if callback overran next timer's start.
+ AbsTime end(AbsTime::now());
+ Duration overrun (0);
+ if (!tasks.empty()) {
+ overrun = Duration(tasks.top()->nextFireTime, end);
+ }
+ bool warningsEnabled; // TimerWarning enabled
+ QPID_LOG_TEST(debug, warningsEnabled); // TimerWarning emitted at debug level
+ if (warningsEnabled) {
+ if (overrun > overran) {
+ if (delay > overran) // if delay is significant to an overrun.
+ warn.lateAndOverran(t->name, delay, overrun, Duration(start, end));
+ else
+ warn.overran(t->name, overrun, Duration(start, end));
+ }
+ else if (delay > late)
+ warn.late(t->name, delay);
+ }
+ continue;
+ } else {
+ // If the timer was adjusted into the future it might no longer
+ // be the next event, so push and then get top to make sure
+ // You can only push events into the future
+ t->sortTime = t->nextFireTime;
+ tasks.push(t);
+ }
+ }
+ assert(!tasks.empty());
+ monitor.wait(tasks.top()->sortTime);
+ }
+ }
+}
+
+void Timer::add(intrusive_ptr<TimerTask> task)
+{
+ Monitor::ScopedLock l(monitor);
+ task->sortTime = task->nextFireTime;
+ tasks.push(task);
+ monitor.notify();
+}
+
+void Timer::start()
+{
+ Monitor::ScopedLock l(monitor);
+ if (!active) {
+ active = true;
+ runner = Thread(this);
+ }
+}
+
+void Timer::stop()
+{
+ {
+ Monitor::ScopedLock l(monitor);
+ if (!active) return;
+ active = false;
+ monitor.notifyAll();
+ }
+ runner.join();
+}
+
+// Allow subclasses to override behavior when firing a task.
+void Timer::fire(boost::intrusive_ptr<TimerTask> t) {
+ try {
+ t->fireTask();
+ } catch (const std::exception& e) {
+ QPID_LOG(error, "Exception thrown by timer task " << t->getName() << ": " << e.what());
+ }
+}
+
+bool operator<(const intrusive_ptr<TimerTask>& a,
+ const intrusive_ptr<TimerTask>& b)
+{
+ // Lower priority if time is later
+ return a.get() && b.get() && a->sortTime > b->sortTime;
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/sys/Timer.h b/qpid/cpp/src/qpid/sys/Timer.h
new file mode 100644
index 0000000000..6281e08913
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/Timer.h
@@ -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.
+ *
+ */
+#ifndef sys_Timer
+#define sys_Timer
+
+#include "qpid/sys/TimerWarnings.h"
+#include "qpid/sys/Monitor.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/Thread.h"
+#include "qpid/sys/Runnable.h"
+#include "qpid/RefCounted.h"
+#include "qpid/CommonImportExport.h"
+#include <memory>
+#include <queue>
+
+#include <boost/intrusive_ptr.hpp>
+
+namespace qpid {
+namespace sys {
+
+class Timer;
+
+class TimerTask : public RefCounted {
+ friend class Timer;
+ friend class TimerTaskCallbackScope;
+ friend bool operator<(const boost::intrusive_ptr<TimerTask>&,
+ const boost::intrusive_ptr<TimerTask>&);
+
+ std::string name;
+ AbsTime sortTime;
+ Duration period;
+ AbsTime nextFireTime;
+ qpid::sys::Monitor stateMonitor;
+ enum {WAITING, CALLING, CANCELLED} state;
+
+ bool prepareToFire();
+ void finishFiring();
+ bool readyToFire() const;
+ void fireTask();
+
+ public:
+ /** Create a periodic TimerTask
+ *
+ * This TimerTask type will trigger after the specified duration
+ * and can also be retriggered again after the same duration
+ * by using setupNextFire() after it has been fired.
+ *
+ * Before it has been triggered you can use restart() to push off
+ * triggering the TimerTask by the specified duration.
+ */
+ QPID_COMMON_EXTERN TimerTask(Duration period, const std::string& name);
+
+ /** Create a TimerTask that fires at a given absolute time
+ *
+ * This is a once only Timer and cannot be restarted after it has fired.
+ */
+ QPID_COMMON_EXTERN TimerTask(AbsTime fireTime, const std::string& name);
+
+ QPID_COMMON_EXTERN virtual ~TimerTask();
+
+ /** Adjust a periodic TimerTask for next firing time
+ *
+ * Called after a TimerTask has been triggered - probably in the fire()
+ * callback function itself. This will set up a TimerTask for the next
+ * triggering.
+ *
+ * Note that the TimerTask will need to be added again to the Timer.
+ */
+ QPID_COMMON_EXTERN void setupNextFire();
+
+ /** Restart a TimerTask so to delay it being firing
+ *
+ * This can be called either with the TimerTask already added to a Timer
+ * or after the task has been triggered. It has the effect of delaying
+ * the task triggering by the initially specified duration.
+ */
+ QPID_COMMON_EXTERN void restart();
+
+ /** Cancel a TimerTask so that it is no longer triggered
+ *
+ * After cancelling the only thing you can do nothing further
+ * with a TimerTask.
+ *
+ * The Timer will delete the cancelled TimerTask.
+ */
+ QPID_COMMON_EXTERN void cancel();
+
+ std::string getName() const { return name; }
+
+ protected:
+ // Must be overridden with callback
+ virtual void fire() = 0;
+};
+
+// For the priority_queue order
+bool operator<(const boost::intrusive_ptr<TimerTask>& a,
+ const boost::intrusive_ptr<TimerTask>& b);
+
+class Timer : private Runnable {
+ qpid::sys::Monitor monitor;
+ std::priority_queue<boost::intrusive_ptr<TimerTask> > tasks;
+ qpid::sys::Thread runner;
+ bool active;
+
+ // Runnable interface
+ void run();
+
+ public:
+ QPID_COMMON_EXTERN Timer();
+ QPID_COMMON_EXTERN virtual ~Timer();
+
+ /** Add an TimerTask to the Timer queue
+ *
+ * Once a TimerTask has been triggered by (calling its fire() function)
+ * the TimerTask is no longer on the Timer queue and needs to be added again.
+ *
+ * Note that TimerTasks must never be added more than once to a Timer
+ * and must never be added simultaneuosly to multiple Timers.
+ */
+ QPID_COMMON_EXTERN virtual void add(boost::intrusive_ptr<TimerTask> task);
+
+ /** Start the Timer
+ *
+ * This will start a new thread that runs the Timer and the fire callbacks.
+ */
+ QPID_COMMON_EXTERN virtual void start();
+
+ /** Stop the Timer
+ *
+ * This will stop the Timer and its thread.
+ */
+ QPID_COMMON_EXTERN virtual void stop();
+
+ protected:
+ QPID_COMMON_EXTERN virtual void fire(boost::intrusive_ptr<TimerTask> task);
+
+ // Allow derived classes to change the late/overran thresholds.
+ Duration late;
+ Duration overran;
+ Duration lateCancel;
+ TimerWarnings warn;
+};
+
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/sys/TimerWarnings.cpp b/qpid/cpp/src/qpid/sys/TimerWarnings.cpp
new file mode 100644
index 0000000000..00fb0d9db6
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/TimerWarnings.cpp
@@ -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.
+ *
+ */
+#include "TimerWarnings.h"
+#include "qpid/log/Statement.h"
+
+namespace qpid {
+namespace sys {
+
+TimerWarnings::TimerWarnings(Duration reportInterval) :
+ interval(reportInterval),
+ nextReport(now(), reportInterval)
+{}
+
+void TimerWarnings::late(const std::string& task, Duration delay) {
+ taskStats[task].lateDelay.add(delay);
+ log();
+}
+
+void TimerWarnings::overran(const std::string& task, Duration overrun, Duration time)
+{
+ taskStats[task].overranOverrun.add(overrun);
+ taskStats[task].overranTime.add(time);
+ log();
+}
+
+void TimerWarnings::lateAndOverran(
+ const std::string& task, Duration delay, Duration overrun, Duration time)
+{
+ taskStats[task].lateAndOverranDelay.add(delay);
+ taskStats[task].lateAndOverranOverrun.add(overrun);
+ taskStats[task].lateAndOverranTime.add(time);
+ log();
+}
+
+void TimerWarnings::log() {
+ if (!taskStats.empty() && nextReport < now()) {
+ for (TaskStatsMap::iterator i = taskStats.begin(); i != taskStats.end(); ++i) {
+ std::string task = i->first;
+ TaskStats& stats = i->second;
+ if (stats.lateDelay.count)
+ QPID_LOG(debug, task << " task late "
+ << stats.lateDelay.count << " times by "
+ << stats.lateDelay.average()/TIME_MSEC << "ms on average.");
+
+ if (stats.overranOverrun.count)
+ QPID_LOG(debug, task << " task overran "
+ << stats.overranOverrun.count << " times by "
+ << stats.overranOverrun.average()/TIME_MSEC << "ms (taking "
+ << stats.overranTime.average() << "ns) on average.");
+
+ if (stats.lateAndOverranOverrun.count)
+ QPID_LOG(debug, task << " task late and overran "
+ << stats.lateAndOverranOverrun.count << " times: late "
+ << stats.lateAndOverranDelay.average()/TIME_MSEC << "ms, overran "
+ << stats.lateAndOverranOverrun.average()/TIME_MSEC << "ms (taking "
+ << stats.lateAndOverranTime.average() << "ns) on average.");
+
+ }
+ nextReport = AbsTime(now(), interval);
+ taskStats.clear();
+ }
+}
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/TimerWarnings.h b/qpid/cpp/src/qpid/sys/TimerWarnings.h
new file mode 100644
index 0000000000..337a434ab5
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/TimerWarnings.h
@@ -0,0 +1,81 @@
+#ifndef QPID_SYS_TIMERWARNINGS_H
+#define QPID_SYS_TIMERWARNINGS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/Time.h"
+#include <map>
+#include <string>
+
+namespace qpid {
+namespace sys {
+
+/**
+ * The Timer class logs warnings when timer tasks are late and/or overrun.
+ *
+ * It is too expensive to log a warning for every late/overrun
+ * incident, doing so aggravates the problem of tasks over-running and
+ * being late.
+ *
+ * This class collects statistical data about each incident and prints
+ * an aggregated report at regular intervals.
+ */
+class TimerWarnings
+{
+ public:
+ TimerWarnings(Duration reportInterval);
+
+ void late(const std::string& task, Duration delay);
+
+ void overran(const std::string& task, Duration overrun, Duration time);
+
+ void lateAndOverran(const std::string& task,
+ Duration delay, Duration overrun, Duration time);
+
+ private:
+ struct Statistic {
+ Statistic() : total(0), count(0) {}
+ void add(int64_t value) { total += value; ++count; }
+ int64_t average() const { return count ? total/count : 0; }
+ int64_t total;
+ int64_t count;
+ };
+
+ // Keep statistics for 3 classes of incident: late, overrun and both.
+ struct TaskStats {
+ Statistic lateDelay; // Just late
+ Statistic overranOverrun, overranTime; // Just overrun
+ // Both
+ Statistic lateAndOverranDelay, lateAndOverranOverrun, lateAndOverranTime;
+ };
+
+ typedef std::map<std::string, TaskStats> TaskStatsMap;
+
+ void log();
+
+ Duration interval;
+ AbsTime nextReport;
+ TaskStatsMap taskStats;
+};
+}} // namespace qpid::sys
+
+#endif /*!QPID_SYS_TIMERWARNINGS_H*/
diff --git a/qpid/cpp/src/qpid/sys/TransportFactory.h b/qpid/cpp/src/qpid/sys/TransportFactory.h
new file mode 100644
index 0000000000..06aa168024
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/TransportFactory.h
@@ -0,0 +1,65 @@
+#ifndef QPID_SYS_TRANSPORTFACTORY_H
+#define QPID_SYS_TRANSPORTFACTORY_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/SharedObject.h"
+#include "qpid/sys/ConnectionCodec.h"
+#include <string>
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+
+namespace qpid {
+namespace sys {
+
+class AsynchAcceptor;
+class Poller;
+class Timer;
+
+class TransportAcceptor : public qpid::SharedObject<TransportAcceptor>
+{
+ public:
+ virtual ~TransportAcceptor() = 0;
+ virtual void accept(boost::shared_ptr<Poller>, ConnectionCodec::Factory*) = 0;
+};
+
+inline TransportAcceptor::~TransportAcceptor() {}
+
+class TransportConnector : public qpid::SharedObject<TransportConnector>
+{
+public:
+ typedef boost::function2<void, int, std::string> ConnectFailedCallback;
+
+ virtual ~TransportConnector() = 0;
+ virtual void connect(
+ boost::shared_ptr<Poller>,
+ const std::string& name,
+ const std::string& host, const std::string& port,
+ ConnectionCodec::Factory* codec,
+ ConnectFailedCallback failed) = 0;
+};
+
+inline TransportConnector::~TransportConnector() {}
+
+}}
+
+#endif
diff --git a/qpid/cpp/src/qpid/sys/Waitable.h b/qpid/cpp/src/qpid/sys/Waitable.h
new file mode 100644
index 0000000000..56f71023c5
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/Waitable.h
@@ -0,0 +1,114 @@
+#ifndef QPID_SYS_WAITABLE_H
+#define QPID_SYS_WAITABLE_H
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/Monitor.h"
+#include "qpid/sys/ExceptionHolder.h"
+#include <assert.h>
+
+namespace qpid {
+namespace sys {
+
+/**
+ * A monitor that keeps track of waiting threads. Threads declare a
+ * ScopedWait around wait() inside a ScopedLock to be considered
+ * waiters.
+ *
+ * Allows waiting threads to be interrupted by an exception.
+ */
+class Waitable : public Monitor {
+ public:
+ Waitable() : waiters(0) {}
+
+ ~Waitable() { assert(waiters == 0); }
+
+ /** Use this inside a scoped lock around the
+ * call to wait() to be counted as a waiter.
+ */
+ struct ScopedWait {
+ Waitable& w;
+ ScopedWait(Waitable& w_) : w(w_) { ++w.waiters; }
+ ~ScopedWait() { if (--w.waiters==0) w.notifyAll(); }
+ };
+
+ /** Block till there are no more waiters in ScopedWaits.
+ * waitWaiters() does not raise an exception even if waiters
+ * were interrupted by one.
+ *@pre Must be called inside a ScopedLock but NOT a ScopedWait.
+ */
+ void waitWaiters() {
+ while (waiters != 0)
+ Monitor::wait();
+ }
+
+ /** Returns the number of outstanding ScopedWaits.
+ * Must be called with the lock held.
+ */
+ size_t hasWaiters() const {
+ return waiters;
+ }
+
+ /** Set an execption to interrupt waiters in ScopedWait.
+ * Must be called with the lock held.
+ */
+ void setException(const ExceptionHolder& e) {
+ exception = e;
+ notifyAll();
+
+ }
+
+ /** True if the waitable has an exception */
+ bool hasException() const { return exception; }
+
+ /** Throws if the waitable has an exception */
+ void checkException() const { exception.raise(); }
+
+ /** Clear the exception if any */
+ void resetException() { exception.reset(); }
+
+ /** Throws an exception if one is set before or during the wait. */
+ void wait() {
+ exception.raise();
+ Monitor::wait();
+ exception.raise();
+ }
+
+ /** Throws an exception if one is set before or during the wait. */
+ bool wait(const AbsTime& absoluteTime) {
+ exception.raise();
+ bool result = Monitor::wait(absoluteTime);
+ exception.raise();
+ return result;
+ }
+
+ private:
+ size_t waiters;
+ ExceptionHolder exception;
+
+ friend struct ScopedWait;
+};
+
+}} // namespace qpid::sys
+
+
+
+#endif /*!QPID_SYS_WAITABLE_H*/
diff --git a/qpid/cpp/src/qpid/sys/aix/SystemInfo.cpp b/qpid/cpp/src/qpid/sys/aix/SystemInfo.cpp
new file mode 100644
index 0000000000..d28d9b7b37
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/aix/SystemInfo.cpp
@@ -0,0 +1,201 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/log/Statement.h"
+#include "qpid/sys/SystemInfo.h"
+#include "qpid/sys/posix/check.h"
+#include "qpid/sys/posix/PrivatePosix.h"
+#include <procinfo.h>
+#include <arpa/inet.h>
+#include <net/if.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <unistd.h>
+#include <map>
+#include <netdb.h>
+#include <string.h>
+
+#ifndef HOST_NAME_MAX
+# define HOST_NAME_MAX 256
+#endif
+
+using namespace std;
+
+namespace qpid {
+namespace sys {
+
+long SystemInfo::concurrency() {
+ return sysconf(_SC_NPROCESSORS_ONLN);
+}
+
+bool SystemInfo::getLocalHostname (Address &address) {
+ char name[HOST_NAME_MAX];
+ if (::gethostname(name, sizeof(name)) != 0)
+ return false;
+ address.host = name;
+ return true;
+}
+
+static const string LOOPBACK("127.0.0.1");
+static const string TCP("tcp");
+
+// Test IPv4 address for loopback
+inline bool IN_IS_ADDR_LOOPBACK(const ::in_addr* a) {
+ return ((ntohl(a->s_addr) & 0xff000000) == 0x7f000000);
+}
+
+inline bool isLoopback(const ::sockaddr* addr) {
+ switch (addr->sa_family) {
+ case AF_INET: return IN_IS_ADDR_LOOPBACK(&((const ::sockaddr_in*)(const void*)addr)->sin_addr);
+ case AF_INET6: return IN6_IS_ADDR_LOOPBACK(&((const ::sockaddr_in6*)(const void*)addr)->sin6_addr);
+ default: return false;
+ }
+}
+
+namespace {
+ class HandleCloser : public IOHandle {
+ public:
+ HandleCloser(int fd) : IOHandle(fd) {}
+ ~HandleCloser() { ::close(fd); fd = -1; }
+ };
+
+ inline bool isInetOrInet6(::sockaddr* sa) {
+ switch (sa->sa_family) {
+ case AF_INET:
+ case AF_INET6:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ inline void *InetAddr(::sockaddr* sa) {
+ switch (sa->sa_family) {
+ case AF_INET:
+ return &(reinterpret_cast<struct sockaddr_in *>(sa)->sin_addr);
+ case AF_INET6:
+ return &(reinterpret_cast<struct sockaddr_in6 *>(sa)->sin6_addr);
+ default:
+ return 0;
+ }
+ }
+
+ typedef std::map<std::string, std::vector<std::string> > InterfaceInfo;
+ std::map<std::string, std::vector<std::string> > cachedInterfaces;
+
+ void cacheInterfaceInfo() {
+ int status = 0;
+ int handle = ::socket (PF_INET, SOCK_DGRAM, 0);
+ QPID_POSIX_CHECK(handle);
+ HandleCloser h(handle);
+
+ size_t num_ifs = 0;
+ struct ifconf ifc;
+ status = ::ioctl(handle, SIOCGSIZIFCONF, (caddr_t)&ifc.ifc_len);
+ QPID_POSIX_CHECK(status);
+
+ std::auto_ptr<char> auto_ifc_buf(new char[ifc.ifc_len]);
+ memset (auto_ifc_buf.get(), 0, ifc.ifc_len);
+
+ status = ::ioctl(handle, SIOCGIFCONF, (caddr_t)auto_ifc_buf.get());
+ QPID_POSIX_CHECK(status);
+
+ char *buf_start = auto_ifc_buf.get();
+ char *buf_end = buf_start + ifc.ifc_len;
+
+ for (char *ptr = buf_start; ptr < buf_end; ) {
+ struct ifreq *req = reinterpret_cast<struct ifreq *>(ptr);
+ ptr += IFNAMSIZ;
+ ptr += req->ifr_addr.sa_len;
+ if (!strcmp("lo0", req->ifr_name) || !isInetOrInet6(&req->ifr_addr))
+ continue;
+ char dots[INET6_ADDRSTRLEN];
+ if (! ::inet_ntop(req->ifr_addr.sa_family, InetAddr(&req->ifr_addr), dots, sizeof(dots)))
+ throw QPID_POSIX_ERROR(errno);
+ std::string address(dots);
+ cachedInterfaces[req->ifr_name].push_back(address);
+ }
+ }
+}
+
+bool SystemInfo::getInterfaceAddresses(const std::string& interface, std::vector<std::string>& addresses) {
+ if ( cachedInterfaces.empty() ) cacheInterfaceInfo();
+ InterfaceInfo::iterator i = cachedInterfaces.find(interface);
+ if ( i==cachedInterfaces.end() ) return false;
+ std::copy(i->second.begin(), i->second.end(), std::back_inserter(addresses));
+ return true;
+}
+
+void SystemInfo::getInterfaceNames(std::vector<std::string>& names ) {
+ if ( cachedInterfaces.empty() ) cacheInterfaceInfo();
+
+ for (InterfaceInfo::const_iterator i = cachedInterfaces.begin(); i!=cachedInterfaces.end(); ++i) {
+ names.push_back(i->first);
+ }
+}
+
+void SystemInfo::getSystemId (std::string &osName,
+ std::string &nodeName,
+ std::string &release,
+ std::string &version,
+ std::string &machine)
+{
+ struct utsname _uname;
+ if (uname (&_uname) == 0)
+ {
+ osName = _uname.sysname;
+ nodeName = _uname.nodename;
+ release = _uname.release;
+ version = _uname.version;
+ machine = _uname.machine;
+ }
+}
+
+uint32_t SystemInfo::getProcessId()
+{
+ return (uint32_t) ::getpid();
+}
+
+uint32_t SystemInfo::getParentProcessId()
+{
+ return (uint32_t) ::getppid();
+}
+
+// AIX specific
+string SystemInfo::getProcessName()
+{
+ struct procsinfo my_info;
+ pid_t my_pid = getpid();
+ int status = getprocs(&my_info, sizeof(my_info), 0, 0, &my_pid, 1);
+ QPID_POSIX_CHECK(status);
+ std::string my_name(my_info.pi_comm);
+ return my_name;
+}
+
+// Always true. Only Windows has exception cases.
+bool SystemInfo::threadSafeShutdown()
+{
+ return true;
+}
+
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.cpp b/qpid/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.cpp
new file mode 100644
index 0000000000..79d9d08a59
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.cpp
@@ -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.
+ *
+ */
+#include <unistd.h>
+#include "qpid/sys/cyrus/CyrusSecurityLayer.h"
+#include <algorithm>
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/log/Statement.h"
+#include <string.h>
+
+namespace qpid {
+namespace sys {
+namespace cyrus {
+
+CyrusSecurityLayer::CyrusSecurityLayer(sasl_conn_t* c, uint16_t maxFrameSize, int ssf) :
+ SecurityLayer(ssf), conn(c), decrypted(0), decryptedSize(0), encrypted(0), encryptedSize(0), codec(0), maxInputSize(0),
+ decodeBuffer(maxFrameSize), encodeBuffer(maxFrameSize), encoded(0)
+{
+ const void* value(0);
+ int result = sasl_getprop(conn, SASL_MAXOUTBUF, &value);
+ if (result != SASL_OK) {
+ throw framing::InternalErrorException(QPID_MSG("SASL encode error: " << sasl_errdetail(conn)));
+ }
+ maxInputSize = *(reinterpret_cast<const unsigned*>(value));
+}
+
+size_t CyrusSecurityLayer::decode(const char* input, size_t size)
+{
+ size_t inStart = 0;
+ do {
+ size_t inSize = std::min(size - inStart, maxInputSize);
+ int result = sasl_decode(conn, input + inStart, inSize, &decrypted, &decryptedSize);
+ if (result != SASL_OK) {
+ throw framing::InternalErrorException(QPID_MSG("SASL decode error: " << sasl_errdetail(conn)));
+ }
+ inStart += inSize;
+ size_t copied = 0;
+ do {
+ size_t count = std::min(decryptedSize - copied, decodeBuffer.size - decodeBuffer.position);
+ ::memcpy(decodeBuffer.data + decodeBuffer.position, decrypted + copied, count);
+ copied += count;
+ decodeBuffer.position += count;
+ size_t decodedSize = codec->decode(decodeBuffer.data, decodeBuffer.position);
+ if (decodedSize == 0) break;
+ if (decodedSize < decodeBuffer.position) {
+ ::memmove(decodeBuffer.data, decodeBuffer.data + decodedSize, decodeBuffer.position - decodedSize);
+ }
+ decodeBuffer.position -= decodedSize;
+ } while (copied < decryptedSize);
+ } while (inStart < size);
+ return size;
+}
+
+size_t CyrusSecurityLayer::encode(char* buffer, size_t size)
+{
+ size_t processed = 0;//records how many bytes have been written to buffer
+ do {
+ if (!encrypted) {
+ if (!encoded) {
+ encodeBuffer.position = 0;
+ encoded = codec->encode(encodeBuffer.data, encodeBuffer.size);
+ if (!encoded) break;//nothing more to do
+ }
+
+ size_t encryptable = std::min(encoded, maxInputSize);
+ int result = sasl_encode(conn, encodeBuffer.data + encodeBuffer.position, encryptable, &encrypted, &encryptedSize);
+ if (result != SASL_OK) {
+ throw framing::InternalErrorException(QPID_MSG("SASL encode error: " << sasl_errdetail(conn)));
+ }
+ encodeBuffer.position += encryptable;
+ encoded -= encryptable;
+ }
+ size_t remaining = size - processed;
+ if (remaining < encryptedSize) {
+ //can't fit all encrypted data in the buffer we've
+ //been given, copy in what we can and hold on to the
+ //rest until the next call
+ ::memcpy(buffer + processed, encrypted, remaining);
+ processed += remaining;
+ encrypted += remaining;
+ encryptedSize -= remaining;
+ } else {
+ ::memcpy(buffer + processed, encrypted, encryptedSize);
+ processed += encryptedSize;
+ encrypted = 0;
+ encryptedSize = 0;
+ }
+ } while (processed < size);
+ return processed;
+}
+
+bool CyrusSecurityLayer::canEncode()
+{
+ return codec && (encrypted || codec->canEncode());
+}
+
+void CyrusSecurityLayer::init(qpid::sys::Codec* c)
+{
+ codec = c;
+}
+
+CyrusSecurityLayer::DataBuffer::DataBuffer(size_t s) : position(0), size(s)
+{
+ data = new char[size];
+}
+
+CyrusSecurityLayer::DataBuffer::~DataBuffer()
+{
+ delete[] data;
+}
+
+}}} // namespace qpid::sys::cyrus
diff --git a/qpid/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.h b/qpid/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.h
new file mode 100644
index 0000000000..ae86ba5569
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/cyrus/CyrusSecurityLayer.h
@@ -0,0 +1,68 @@
+#ifndef QPID_SYS_CYRUS_CYRUSSECURITYLAYER_H
+#define QPID_SYS_CYRUS_CYRUSSECURITYLAYER_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/sys/SecurityLayer.h"
+#include <sasl/sasl.h>
+
+namespace qpid {
+namespace sys {
+namespace cyrus {
+
+
+/**
+ * Implementation of SASL security layer using cyrus-sasl library
+ */
+class CyrusSecurityLayer : public qpid::sys::SecurityLayer
+{
+ public:
+ CyrusSecurityLayer(sasl_conn_t*, uint16_t maxFrameSize, int ssf);
+ size_t decode(const char* buffer, size_t size);
+ size_t encode(char* buffer, size_t size);
+ bool canEncode();
+ void init(qpid::sys::Codec*);
+ private:
+ struct DataBuffer
+ {
+ char* data;
+ size_t position;
+ const size_t size;
+ DataBuffer(size_t);
+ ~DataBuffer();
+ };
+
+ sasl_conn_t* conn;
+ const char* decrypted;
+ unsigned decryptedSize;
+ const char* encrypted;
+ unsigned encryptedSize;
+ qpid::sys::Codec* codec;
+ size_t maxInputSize;
+ DataBuffer decodeBuffer;
+ DataBuffer encodeBuffer;
+ size_t encoded;
+};
+}}} // namespace qpid::sys::cyrus
+
+#endif /*!QPID_SYS_CYRUS_CYRUSSECURITYLAYER_H*/
diff --git a/qpid/cpp/src/qpid/sys/epoll/EpollPoller.cpp b/qpid/cpp/src/qpid/sys/epoll/EpollPoller.cpp
new file mode 100644
index 0000000000..6fdf99637f
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/epoll/EpollPoller.cpp
@@ -0,0 +1,677 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/Poller.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/AtomicCount.h"
+#include "qpid/sys/DeletionManager.h"
+#include "qpid/sys/posix/check.h"
+#include "qpid/sys/posix/PrivatePosix.h"
+#include "qpid/log/Statement.h"
+
+#include <sys/epoll.h>
+#include <errno.h>
+#include <signal.h>
+
+#include <assert.h>
+#include <queue>
+#include <set>
+#include <exception>
+
+namespace qpid {
+namespace sys {
+
+// Deletion manager to handle deferring deletion of PollerHandles to when they definitely aren't being used
+DeletionManager<PollerHandlePrivate> PollerHandleDeletionManager;
+
+// Instantiate (and define) class static for DeletionManager
+template <>
+DeletionManager<PollerHandlePrivate>::AllThreadsStatuses DeletionManager<PollerHandlePrivate>::allThreadsStatuses(0);
+
+class PollerHandlePrivate {
+ friend class Poller;
+ friend class PollerPrivate;
+ friend class PollerHandle;
+
+ enum FDStat {
+ ABSENT,
+ MONITORED,
+ INACTIVE,
+ HUNGUP,
+ MONITORED_HUNGUP,
+ INTERRUPTED,
+ INTERRUPTED_HUNGUP,
+ DELETED
+ };
+
+ ::__uint32_t events;
+ const IOHandle* ioHandle;
+ PollerHandle* pollerHandle;
+ FDStat stat;
+ Mutex lock;
+
+ PollerHandlePrivate(const IOHandle* h, PollerHandle* p) :
+ events(0),
+ ioHandle(h),
+ pollerHandle(p),
+ stat(ABSENT) {
+ }
+
+ int fd() const {
+ return ioHandle->fd;
+ }
+
+ bool isActive() const {
+ return stat == MONITORED || stat == MONITORED_HUNGUP;
+ }
+
+ void setActive() {
+ stat = (stat == HUNGUP || stat == INTERRUPTED_HUNGUP)
+ ? MONITORED_HUNGUP
+ : MONITORED;
+ }
+
+ bool isInactive() const {
+ return stat == INACTIVE || stat == HUNGUP;
+ }
+
+ void setInactive() {
+ stat = INACTIVE;
+ }
+
+ bool isIdle() const {
+ return stat == ABSENT;
+ }
+
+ void setIdle() {
+ stat = ABSENT;
+ }
+
+ bool isHungup() const {
+ return
+ stat == MONITORED_HUNGUP ||
+ stat == HUNGUP ||
+ stat == INTERRUPTED_HUNGUP;
+ }
+
+ void setHungup() {
+ assert(stat == MONITORED);
+ stat = HUNGUP;
+ }
+
+ bool isInterrupted() const {
+ return stat == INTERRUPTED || stat == INTERRUPTED_HUNGUP;
+ }
+
+ void setInterrupted() {
+ stat = (stat == MONITORED_HUNGUP || stat == HUNGUP)
+ ? INTERRUPTED_HUNGUP
+ : INTERRUPTED;
+ }
+
+ bool isDeleted() const {
+ return stat == DELETED;
+ }
+
+ void setDeleted() {
+ stat = DELETED;
+ }
+};
+
+PollerHandle::PollerHandle(const IOHandle& h) :
+ impl(new PollerHandlePrivate(&h, this))
+{}
+
+PollerHandle::~PollerHandle() {
+ {
+ ScopedLock<Mutex> l(impl->lock);
+ if (impl->isDeleted()) {
+ return;
+ }
+ impl->pollerHandle = 0;
+ if (impl->isInterrupted()) {
+ impl->setDeleted();
+ return;
+ }
+ assert(impl->isIdle());
+ impl->setDeleted();
+ }
+ PollerHandleDeletionManager.markForDeletion(impl);
+}
+
+class HandleSet
+{
+ Mutex lock;
+ std::set<PollerHandle*> handles;
+ public:
+ void add(PollerHandle*);
+ void remove(PollerHandle*);
+ void cleanup();
+};
+
+void HandleSet::add(PollerHandle* h)
+{
+ ScopedLock<Mutex> l(lock);
+ handles.insert(h);
+}
+void HandleSet::remove(PollerHandle* h)
+{
+ ScopedLock<Mutex> l(lock);
+ handles.erase(h);
+}
+void HandleSet::cleanup()
+{
+ // Inform all registered handles of disconnection
+ std::set<PollerHandle*> copy;
+ handles.swap(copy);
+ for (std::set<PollerHandle*>::const_iterator i = copy.begin(); i != copy.end(); ++i) {
+ Poller::Event event(*i, Poller::DISCONNECTED);
+ event.process();
+ }
+}
+
+/**
+ * Concrete implementation of Poller to use the Linux specific epoll
+ * interface
+ */
+class PollerPrivate {
+ friend class Poller;
+
+ static const int DefaultFds = 256;
+
+ struct ReadablePipe {
+ int fds[2];
+
+ /**
+ * This encapsulates an always readable pipe which we can add
+ * to the epoll set to force epoll_wait to return
+ */
+ ReadablePipe() {
+ QPID_POSIX_CHECK(::pipe(fds));
+ // Just write the pipe's fds to the pipe
+ QPID_POSIX_CHECK(::write(fds[1], fds, 2));
+ }
+
+ ~ReadablePipe() {
+ ::close(fds[0]);
+ ::close(fds[1]);
+ }
+
+ int getFD() {
+ return fds[0];
+ }
+ };
+
+ ReadablePipe alwaysReadable;
+ int alwaysReadableFd;
+
+ class InterruptHandle: public PollerHandle {
+ std::queue<PollerHandle*> handles;
+
+ void processEvent(Poller::EventType) {
+ PollerHandle* handle = handles.front();
+ handles.pop();
+ assert(handle);
+
+ // Synthesise event
+ Poller::Event event(handle, Poller::INTERRUPTED);
+
+ // Process synthesised event
+ event.process();
+ }
+
+ public:
+ InterruptHandle() :
+ PollerHandle(DummyIOHandle)
+ {}
+
+ void addHandle(PollerHandle& h) {
+ handles.push(&h);
+ }
+
+ PollerHandle* getHandle() {
+ PollerHandle* handle = handles.front();
+ handles.pop();
+ return handle;
+ }
+
+ bool queuedHandles() {
+ return handles.size() > 0;
+ }
+ };
+
+ const int epollFd;
+ bool isShutdown;
+ InterruptHandle interruptHandle;
+ HandleSet registeredHandles;
+ AtomicCount threadCount;
+
+ static ::__uint32_t directionToEpollEvent(Poller::Direction dir) {
+ switch (dir) {
+ case Poller::INPUT: return ::EPOLLIN;
+ case Poller::OUTPUT: return ::EPOLLOUT;
+ case Poller::INOUT: return ::EPOLLIN | ::EPOLLOUT;
+ default: return 0;
+ }
+ }
+
+ static Poller::EventType epollToDirection(::__uint32_t events) {
+ // POLLOUT & POLLHUP are mutually exclusive really, but at least socketpairs
+ // can give you both!
+ events = (events & ::EPOLLHUP) ? events & ~::EPOLLOUT : events;
+ ::__uint32_t e = events & (::EPOLLIN | ::EPOLLOUT);
+ switch (e) {
+ case ::EPOLLIN: return Poller::READABLE;
+ case ::EPOLLOUT: return Poller::WRITABLE;
+ case ::EPOLLIN | ::EPOLLOUT: return Poller::READ_WRITABLE;
+ default:
+ return (events & (::EPOLLHUP | ::EPOLLERR)) ?
+ Poller::DISCONNECTED : Poller::INVALID;
+ }
+ }
+
+ PollerPrivate() :
+ alwaysReadableFd(alwaysReadable.getFD()),
+ epollFd(::epoll_create(DefaultFds)),
+ isShutdown(false) {
+ QPID_POSIX_CHECK(epollFd);
+ // Add always readable fd into our set (but not listening to it yet)
+ ::epoll_event epe;
+ epe.events = 0;
+ epe.data.u64 = 1;
+ QPID_POSIX_CHECK(::epoll_ctl(epollFd, EPOLL_CTL_ADD, alwaysReadableFd, &epe));
+ }
+
+ ~PollerPrivate() {
+ // It's probably okay to ignore any errors here as there can't be data loss
+ ::close(epollFd);
+
+ // Need to put the interruptHandle in idle state to delete it
+ static_cast<PollerHandle&>(interruptHandle).impl->setIdle();
+ }
+
+ void resetMode(PollerHandlePrivate& handle);
+
+ void interrupt() {
+ ::epoll_event epe;
+ // Use EPOLLONESHOT so we only wake a single thread
+ epe.events = ::EPOLLIN | ::EPOLLONESHOT;
+ epe.data.u64 = 0; // Keep valgrind happy
+ epe.data.ptr = &static_cast<PollerHandle&>(interruptHandle);
+ QPID_POSIX_CHECK(::epoll_ctl(epollFd, EPOLL_CTL_MOD, alwaysReadableFd, &epe));
+ }
+
+ void interruptAll() {
+ ::epoll_event epe;
+ // Not EPOLLONESHOT, so we eventually get all threads
+ epe.events = ::EPOLLIN;
+ epe.data.u64 = 2; // Keep valgrind happy
+ QPID_POSIX_CHECK(::epoll_ctl(epollFd, EPOLL_CTL_MOD, alwaysReadableFd, &epe));
+ }
+};
+
+void Poller::registerHandle(PollerHandle& handle) {
+ PollerHandlePrivate& eh = *handle.impl;
+ ScopedLock<Mutex> l(eh.lock);
+ assert(eh.isIdle());
+
+ ::epoll_event epe;
+ epe.events = ::EPOLLONESHOT;
+ epe.data.u64 = 0; // Keep valgrind happy
+ epe.data.ptr = &eh;
+
+ impl->registeredHandles.add(&handle);
+ QPID_POSIX_CHECK(::epoll_ctl(impl->epollFd, EPOLL_CTL_ADD, eh.fd(), &epe));
+
+ eh.setActive();
+}
+
+void Poller::unregisterHandle(PollerHandle& handle) {
+ PollerHandlePrivate& eh = *handle.impl;
+ ScopedLock<Mutex> l(eh.lock);
+ assert(!eh.isIdle());
+
+ impl->registeredHandles.remove(&handle);
+ int rc = ::epoll_ctl(impl->epollFd, EPOLL_CTL_DEL, eh.fd(), 0);
+ // Ignore EBADF since deleting a nonexistent fd has the overall required result!
+ // And allows the case where a sloppy program closes the fd and then does the delFd()
+ if (rc == -1 && errno != EBADF) {
+ QPID_POSIX_CHECK(rc);
+ }
+
+ eh.setIdle();
+}
+
+void PollerPrivate::resetMode(PollerHandlePrivate& eh) {
+ PollerHandle* ph;
+ {
+ ScopedLock<Mutex> l(eh.lock);
+ assert(!eh.isActive());
+
+ if (eh.isIdle() || eh.isDeleted()) {
+ return;
+ }
+
+ if (eh.events==0) {
+ eh.setActive();
+ return;
+ }
+
+ if (!eh.isInterrupted()) {
+ ::epoll_event epe;
+ epe.events = eh.events | ::EPOLLONESHOT;
+ epe.data.u64 = 0; // Keep valgrind happy
+ epe.data.ptr = &eh;
+
+ int rc = ::epoll_ctl(epollFd, EPOLL_CTL_MOD, eh.fd(), &epe);
+ // If something has closed the fd in the meantime try adding it back
+ if (rc ==-1 && errno == ENOENT) {
+ eh.setIdle(); // Reset our handle as if starting from scratch
+ rc = ::epoll_ctl(epollFd, EPOLL_CTL_ADD, eh.fd(), &epe);
+ }
+ QPID_POSIX_CHECK(rc);
+
+ eh.setActive();
+ return;
+ }
+ ph = eh.pollerHandle;
+ }
+
+ PollerHandlePrivate& ihp = *static_cast<PollerHandle&>(interruptHandle).impl;
+ ScopedLock<Mutex> l(ihp.lock);
+ interruptHandle.addHandle(*ph);
+ ihp.setActive();
+ interrupt();
+}
+
+void Poller::monitorHandle(PollerHandle& handle, Direction dir) {
+ PollerHandlePrivate& eh = *handle.impl;
+ ScopedLock<Mutex> l(eh.lock);
+ assert(!eh.isIdle());
+
+ ::__uint32_t oldEvents = eh.events;
+ eh.events |= PollerPrivate::directionToEpollEvent(dir);
+
+ // If no change nothing more to do - avoid unnecessary system call
+ if (oldEvents==eh.events) {
+ return;
+ }
+
+ // If we're not actually listening wait till we are to perform change
+ if (!eh.isActive()) {
+ return;
+ }
+
+ ::epoll_event epe;
+ epe.events = eh.events | ::EPOLLONESHOT;
+ epe.data.u64 = 0; // Keep valgrind happy
+ epe.data.ptr = &eh;
+
+ QPID_POSIX_CHECK(::epoll_ctl(impl->epollFd, EPOLL_CTL_MOD, eh.fd(), &epe));
+}
+
+void Poller::unmonitorHandle(PollerHandle& handle, Direction dir) {
+ PollerHandlePrivate& eh = *handle.impl;
+ ScopedLock<Mutex> l(eh.lock);
+ assert(!eh.isIdle());
+
+ ::__uint32_t oldEvents = eh.events;
+ eh.events &= ~PollerPrivate::directionToEpollEvent(dir);
+
+ // If no change nothing more to do - avoid unnecessary system call
+ if (oldEvents==eh.events) {
+ return;
+ }
+
+ // If we're not actually listening wait till we are to perform change
+ if (!eh.isActive()) {
+ return;
+ }
+
+ ::epoll_event epe;
+ epe.events = eh.events | ::EPOLLONESHOT;
+ epe.data.u64 = 0; // Keep valgrind happy
+ epe.data.ptr = &eh;
+
+ QPID_POSIX_CHECK(::epoll_ctl(impl->epollFd, EPOLL_CTL_MOD, eh.fd(), &epe));
+}
+
+void Poller::shutdown() {
+ // NB: this function must be async-signal safe, it must not
+ // call any function that is not async-signal safe.
+
+ // Allow sloppy code to shut us down more than once
+ if (impl->isShutdown)
+ return;
+
+ // Don't use any locking here - isShutdown will be visible to all
+ // after the epoll_ctl() anyway (it's a memory barrier)
+ impl->isShutdown = true;
+
+ impl->interruptAll();
+}
+
+bool Poller::interrupt(PollerHandle& handle) {
+ {
+ PollerHandlePrivate& eh = *handle.impl;
+ ScopedLock<Mutex> l(eh.lock);
+ if (eh.isIdle() || eh.isDeleted()) {
+ return false;
+ }
+
+ if (eh.isInterrupted()) {
+ return true;
+ }
+
+ // Stop monitoring handle for read or write
+ ::epoll_event epe;
+ epe.events = 0;
+ epe.data.u64 = 0; // Keep valgrind happy
+ epe.data.ptr = &eh;
+ QPID_POSIX_CHECK(::epoll_ctl(impl->epollFd, EPOLL_CTL_MOD, eh.fd(), &epe));
+
+ if (eh.isInactive()) {
+ eh.setInterrupted();
+ return true;
+ }
+ eh.setInterrupted();
+ }
+
+ PollerPrivate::InterruptHandle& ih = impl->interruptHandle;
+ PollerHandlePrivate& eh = *static_cast<PollerHandle&>(ih).impl;
+ ScopedLock<Mutex> l(eh.lock);
+ ih.addHandle(handle);
+
+ impl->interrupt();
+ eh.setActive();
+ return true;
+}
+
+void Poller::run() {
+ // Ensure that we exit thread responsibly under all circumstances
+ try {
+ // Make sure we can't be interrupted by signals at a bad time
+ ::sigset_t ss;
+ ::sigfillset(&ss);
+ ::pthread_sigmask(SIG_SETMASK, &ss, 0);
+
+ ++(impl->threadCount);
+ do {
+ Event event = wait();
+
+ // If can read/write then dispatch appropriate callbacks
+ if (event.handle) {
+ event.process();
+ } else {
+ // Handle shutdown
+ switch (event.type) {
+ case SHUTDOWN:
+ PollerHandleDeletionManager.destroyThreadState();
+ //last thread to respond to shutdown cleans up:
+ if (--(impl->threadCount) == 0) impl->registeredHandles.cleanup();
+ return;
+ default:
+ // This should be impossible
+ assert(false);
+ }
+ }
+ } while (true);
+ } catch (const std::exception& e) {
+ QPID_LOG(error, "IO worker thread exiting with unhandled exception: " << e.what());
+ }
+ PollerHandleDeletionManager.destroyThreadState();
+ --(impl->threadCount);
+}
+
+bool Poller::hasShutdown()
+{
+ return impl->isShutdown;
+}
+
+Poller::Event Poller::wait(Duration timeout) {
+ static __thread PollerHandlePrivate* lastReturnedHandle = 0;
+ epoll_event epe;
+ int timeoutMs = (timeout == TIME_INFINITE) ? -1 : timeout / TIME_MSEC;
+ AbsTime targetTimeout =
+ (timeout == TIME_INFINITE) ?
+ FAR_FUTURE :
+ AbsTime(now(), timeout);
+
+ if (lastReturnedHandle) {
+ impl->resetMode(*lastReturnedHandle);
+ lastReturnedHandle = 0;
+ }
+
+ // Repeat until we weren't interrupted by signal
+ do {
+ PollerHandleDeletionManager.markAllUnusedInThisThread();
+ int rc = ::epoll_wait(impl->epollFd, &epe, 1, timeoutMs);
+ if (rc ==-1 && errno != EINTR) {
+ QPID_POSIX_CHECK(rc);
+ } else if (rc > 0) {
+ assert(rc == 1);
+ void* dataPtr = epe.data.ptr;
+
+ // Check if this is an interrupt
+ PollerPrivate::InterruptHandle& interruptHandle = impl->interruptHandle;
+ if (dataPtr == &interruptHandle) {
+ // If we are shutting down we need to rearm the shutdown interrupt to
+ // ensure everyone still sees it. It's okay that this might be overridden
+ // below as we will be back here if it is.
+ if (impl->isShutdown) {
+ impl->interruptAll();
+ }
+ PollerHandle* wrappedHandle = 0;
+ {
+ ScopedLock<Mutex> l(interruptHandle.impl->lock);
+ if (interruptHandle.impl->isActive()) {
+ wrappedHandle = interruptHandle.getHandle();
+ // If there is an interrupt queued behind this one we need to arm it
+ // We do it this way so that another thread can pick it up
+ if (interruptHandle.queuedHandles()) {
+ impl->interrupt();
+ interruptHandle.impl->setActive();
+ } else {
+ interruptHandle.impl->setInactive();
+ }
+ }
+ }
+ if (wrappedHandle) {
+ PollerHandlePrivate& eh = *wrappedHandle->impl;
+ {
+ ScopedLock<Mutex> l(eh.lock);
+ if (!eh.isDeleted()) {
+ if (!eh.isIdle()) {
+ eh.setInactive();
+ }
+ lastReturnedHandle = &eh;
+ assert(eh.pollerHandle == wrappedHandle);
+ return Event(wrappedHandle, INTERRUPTED);
+ }
+ }
+ PollerHandleDeletionManager.markForDeletion(&eh);
+ }
+ continue;
+ }
+
+ // Check for shutdown
+ if (impl->isShutdown) {
+ PollerHandleDeletionManager.markAllUnusedInThisThread();
+ return Event(0, SHUTDOWN);
+ }
+
+ PollerHandlePrivate& eh = *static_cast<PollerHandlePrivate*>(dataPtr);
+ ScopedLock<Mutex> l(eh.lock);
+
+ // the handle could have gone inactive since we left the epoll_wait
+ if (eh.isActive()) {
+ PollerHandle* handle = eh.pollerHandle;
+ assert(handle);
+
+ // If the connection has been hungup we could still be readable
+ // (just not writable), allow us to readable until we get here again
+ if (epe.events & ::EPOLLHUP) {
+ if (eh.isHungup()) {
+ eh.setInactive();
+ // Don't set up last Handle so that we don't reset this handle
+ // on re-entering Poller::wait. This means that we will never
+ // be set active again once we've returned disconnected, and so
+ // can never be returned again.
+ return Event(handle, DISCONNECTED);
+ }
+ eh.setHungup();
+ } else {
+ eh.setInactive();
+ }
+ lastReturnedHandle = &eh;
+ return Event(handle, PollerPrivate::epollToDirection(epe.events));
+ }
+ }
+ // We only get here if one of the following:
+ // * epoll_wait was interrupted by a signal
+ // * epoll_wait timed out
+ // * the state of the handle changed after being returned by epoll_wait
+ //
+ // The only things we can do here are return a timeout or wait more.
+ // Obviously if we timed out we return timeout; if the wait was meant to
+ // be indefinite then we should never return with a time out so we go again.
+ // If the wait wasn't indefinite, we check whether we are after the target wait
+ // time or not
+ if (timeoutMs == -1) {
+ continue;
+ }
+ if (rc == 0 && now() > targetTimeout) {
+ PollerHandleDeletionManager.markAllUnusedInThisThread();
+ return Event(0, TIMEOUT);
+ }
+ } while (true);
+}
+
+// Concrete constructors
+Poller::Poller() :
+ impl(new PollerPrivate())
+{}
+
+Poller::~Poller() {
+ delete impl;
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/sys/posix/AsynchIO.cpp b/qpid/cpp/src/qpid/sys/posix/AsynchIO.cpp
new file mode 100644
index 0000000000..7d04d2214d
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/AsynchIO.cpp
@@ -0,0 +1,655 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/AsynchIO.h"
+#include "qpid/sys/SecuritySettings.h"
+#include "qpid/sys/Socket.h"
+#include "qpid/sys/SocketAddress.h"
+#include "qpid/sys/Poller.h"
+#include "qpid/sys/Probes.h"
+#include "qpid/sys/DispatchHandle.h"
+#include "qpid/sys/Time.h"
+#include "qpid/log/Statement.h"
+
+// TODO The basic algorithm here is not really POSIX specific and with a
+// bit more abstraction could (should) be promoted to be platform portable
+// - The POSIX specific code here is ignoring SIGPIPE which should really
+// be part of the socket code.
+// - And checking errno to detect specific read/write conditions.
+//
+#include <errno.h>
+#include <signal.h>
+
+#include <boost/bind.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/shared_array.hpp>
+
+namespace qpid {
+namespace sys {
+namespace posix {
+
+namespace {
+
+struct StaticInit {
+ StaticInit() {
+ /**
+ * Make *process* not generate SIGPIPE when writing to closed
+ * pipe/socket (necessary as default action is to terminate process)
+ */
+ ::signal(SIGPIPE, SIG_IGN);
+ };
+} init;
+
+/*
+ * We keep per thread state to avoid locking overhead. The assumption is that
+ * on average all the connections are serviced by all the threads so the state
+ * recorded in each thread is about the same. If this turns out not to be the
+ * case we could rebalance the info occasionally.
+ */
+__thread int threadReadTotal = 0;
+__thread int threadReadCount = 0;
+__thread int threadWriteTotal = 0;
+__thread int threadWriteCount = 0;
+__thread int64_t threadMaxIoTimeNs = 2 * 1000000; // start at 2ms
+}
+
+/*
+ * Asynch Acceptor
+ */
+class AsynchAcceptor : public qpid::sys::AsynchAcceptor {
+public:
+ AsynchAcceptor(const Socket& s, AsynchAcceptor::Callback callback);
+ ~AsynchAcceptor();
+ void start(Poller::shared_ptr poller);
+
+private:
+ void readable(DispatchHandle& handle);
+
+private:
+ AsynchAcceptor::Callback acceptedCallback;
+ DispatchHandle handle;
+ const Socket& socket;
+
+};
+
+AsynchAcceptor::AsynchAcceptor(const Socket& s,
+ AsynchAcceptor::Callback callback) :
+ acceptedCallback(callback),
+ handle((const IOHandle&)s, boost::bind(&AsynchAcceptor::readable, this, _1), 0, 0),
+ socket(s) {
+
+ s.setNonblocking();
+}
+
+AsynchAcceptor::~AsynchAcceptor() {
+ handle.stopWatch();
+}
+
+void AsynchAcceptor::start(Poller::shared_ptr poller) {
+ handle.startWatch(poller);
+}
+
+/*
+ * We keep on accepting as long as there is something to accept
+ */
+void AsynchAcceptor::readable(DispatchHandle& h) {
+ Socket* s;
+ do {
+ errno = 0;
+ // TODO: Currently we ignore the peers address, perhaps we should
+ // log it or use it for connection acceptance.
+ try {
+ s = socket.accept();
+ if (s) {
+ acceptedCallback(*s);
+ } else {
+ break;
+ }
+ } catch (const std::exception& e) {
+ QPID_LOG(error, "Could not accept socket: " << e.what());
+ break;
+ }
+ } while (true);
+
+ h.rewatch();
+}
+
+/*
+ * POSIX version of AsynchIO TCP socket connector.
+ *
+ * The class is implemented in terms of DispatchHandle to allow it to be
+ * deleted by deleting the contained DispatchHandle.
+ */
+class AsynchConnector : public qpid::sys::AsynchConnector,
+ private DispatchHandle {
+
+private:
+ void connComplete(DispatchHandle& handle);
+ void requestedCall(RequestCallback rCb);
+
+private:
+ ConnectedCallback connCallback;
+ FailedCallback failCallback;
+ const Socket& socket;
+ SocketAddress sa;
+
+public:
+ AsynchConnector(const Socket& socket,
+ const std::string& hostname,
+ const std::string& port,
+ ConnectedCallback connCb,
+ FailedCallback failCb);
+ void start(Poller::shared_ptr poller);
+ void stop();
+ void requestCallback(RequestCallback rCb);
+};
+
+AsynchConnector::AsynchConnector(const Socket& s,
+ const std::string& hostname,
+ const std::string& port,
+ ConnectedCallback connCb,
+ FailedCallback failCb) :
+ DispatchHandle((const IOHandle&)s,
+ 0,
+ boost::bind(&AsynchConnector::connComplete, this, _1),
+ boost::bind(&AsynchConnector::connComplete, this, _1)),
+ connCallback(connCb),
+ failCallback(failCb),
+ socket(s),
+ sa(hostname, port)
+{
+ socket.setNonblocking();
+
+ // Note, not catching any exceptions here, also has effect of destructing
+ QPID_LOG(info, "Connecting: " << sa.asString());
+ socket.connect(sa);
+}
+
+void AsynchConnector::start(Poller::shared_ptr poller)
+{
+ startWatch(poller);
+}
+
+void AsynchConnector::stop()
+{
+ stopWatch();
+}
+
+void AsynchConnector::requestCallback(RequestCallback callback) {
+ // TODO creating a function object every time isn't all that
+ // efficient - if this becomes heavily used do something better (what?)
+ assert(callback);
+ DispatchHandle::call(boost::bind(&AsynchConnector::requestedCall, this, callback));
+}
+
+void AsynchConnector::requestedCall(RequestCallback callback) {
+ assert(callback);
+ callback(*this);
+}
+
+void AsynchConnector::connComplete(DispatchHandle& h)
+{
+ int errCode = socket.getError();
+ if (errCode == 0) {
+ h.stopWatch();
+ try {
+ socket.finishConnect(sa);
+ } catch (const std::exception& e) {
+ failCallback(socket, 0, e.what());
+ DispatchHandle::doDelete();
+ return;
+ }
+ connCallback(socket);
+ } else {
+ // Retry while we cause an immediate exception
+ // (asynch failure will be handled by re-entering here at the top)
+ while (sa.nextAddress()) {
+ try {
+ // Try next address without deleting ourselves
+ QPID_LOG(debug, "Ignored socket connect error: " << strError(errCode));
+ QPID_LOG(info, "Retrying connect: " << sa.asString());
+ socket.connect(sa);
+ return;
+ } catch (const std::exception& e) {
+ QPID_LOG(debug, "Ignored socket connect exception: " << e.what());
+ }
+ errCode = socket.getError();
+ }
+ h.stopWatch();
+ failCallback(socket, errCode, strError(errCode));
+ }
+ DispatchHandle::doDelete();
+}
+
+/*
+ * POSIX version of AsynchIO reader/writer
+ *
+ * The class is implemented in terms of DispatchHandle to allow it to be
+ * deleted by deleting the contained DispatchHandle.
+ */
+class AsynchIO : public qpid::sys::AsynchIO, private DispatchHandle {
+
+public:
+ AsynchIO(const Socket& s,
+ ReadCallback rCb,
+ EofCallback eofCb,
+ DisconnectCallback disCb,
+ ClosedCallback cCb = 0,
+ BuffersEmptyCallback eCb = 0,
+ IdleCallback iCb = 0);
+
+ // Methods inherited from qpid::sys::AsynchIO
+
+ virtual void queueForDeletion();
+
+ virtual void start(Poller::shared_ptr poller);
+ virtual void createBuffers(uint32_t size);
+ virtual void queueReadBuffer(BufferBase* buff);
+ virtual void unread(BufferBase* buff);
+ virtual void queueWrite(BufferBase* buff);
+ virtual void notifyPendingWrite();
+ virtual void queueWriteClose();
+ virtual bool writeQueueEmpty();
+ virtual void requestCallback(RequestCallback);
+ virtual BufferBase* getQueuedBuffer();
+ virtual SecuritySettings getSecuritySettings();
+
+private:
+ ~AsynchIO();
+
+ // Methods that are callback targets from Dispatcher.
+ void readable(DispatchHandle& handle);
+ void writeable(DispatchHandle& handle);
+ void disconnected(DispatchHandle& handle);
+ void requestedCall(RequestCallback);
+ void close(DispatchHandle& handle);
+
+private:
+ ReadCallback readCallback;
+ EofCallback eofCallback;
+ DisconnectCallback disCallback;
+ ClosedCallback closedCallback;
+ BuffersEmptyCallback emptyCallback;
+ IdleCallback idleCallback;
+ const Socket& socket;
+ std::deque<BufferBase*> bufferQueue;
+ std::deque<BufferBase*> writeQueue;
+ std::vector<BufferBase> buffers;
+ boost::shared_array<char> bufferMemory;
+ bool queuedClose;
+ /**
+ * This flag is used to detect and handle concurrency between
+ * calls to notifyPendingWrite() (which can be made from any thread) and
+ * the execution of the writeable() method (which is always on the
+ * thread processing this handle.
+ */
+ volatile bool writePending;
+};
+
+AsynchIO::AsynchIO(const Socket& s,
+ ReadCallback rCb, EofCallback eofCb, DisconnectCallback disCb,
+ ClosedCallback cCb, BuffersEmptyCallback eCb, IdleCallback iCb) :
+
+ DispatchHandle((const IOHandle&)s,
+ boost::bind(&AsynchIO::readable, this, _1),
+ boost::bind(&AsynchIO::writeable, this, _1),
+ boost::bind(&AsynchIO::disconnected, this, _1)),
+ readCallback(rCb),
+ eofCallback(eofCb),
+ disCallback(disCb),
+ closedCallback(cCb),
+ emptyCallback(eCb),
+ idleCallback(iCb),
+ socket(s),
+ queuedClose(false),
+ writePending(false) {
+
+ s.setNonblocking();
+}
+
+AsynchIO::~AsynchIO() {
+}
+
+void AsynchIO::queueForDeletion() {
+ DispatchHandle::doDelete();
+}
+
+void AsynchIO::start(Poller::shared_ptr poller) {
+ DispatchHandle::startWatch(poller);
+}
+
+void AsynchIO::createBuffers(uint32_t size) {
+ // Allocate all the buffer memory at once
+ bufferMemory.reset(new char[size*BufferCount]);
+
+ // Create the Buffer structs in a vector
+ // And push into the buffer queue
+ buffers.reserve(BufferCount);
+ for (uint32_t i = 0; i < BufferCount; i++) {
+ buffers.push_back(BufferBase(&bufferMemory[i*size], size));
+ queueReadBuffer(&buffers[i]);
+ }
+}
+
+void AsynchIO::queueReadBuffer(BufferBase* buff) {
+ assert(buff);
+ buff->dataStart = 0;
+ buff->dataCount = 0;
+
+ bool queueWasEmpty = bufferQueue.empty();
+ bufferQueue.push_back(buff);
+ if (queueWasEmpty)
+ DispatchHandle::rewatchRead();
+}
+
+void AsynchIO::unread(BufferBase* buff) {
+ assert(buff);
+ buff->squish();
+
+ bool queueWasEmpty = bufferQueue.empty();
+ bufferQueue.push_front(buff);
+ if (queueWasEmpty)
+ DispatchHandle::rewatchRead();
+}
+
+void AsynchIO::queueWrite(BufferBase* buff) {
+ assert(buff);
+ // If we've already closed the socket then throw the write away
+ if (queuedClose) {
+ queueReadBuffer(buff);
+ return;
+ } else {
+ writeQueue.push_front(buff);
+ }
+ writePending = false;
+ DispatchHandle::rewatchWrite();
+}
+
+// This can happen outside the callback context
+void AsynchIO::notifyPendingWrite() {
+ writePending = true;
+ DispatchHandle::rewatchWrite();
+}
+
+void AsynchIO::queueWriteClose() {
+ queuedClose = true;
+ DispatchHandle::rewatchWrite();
+}
+
+bool AsynchIO::writeQueueEmpty() {
+ return writeQueue.empty();
+}
+
+void AsynchIO::requestCallback(RequestCallback callback) {
+ // TODO creating a function object every time isn't all that
+ // efficient - if this becomes heavily used do something better (what?)
+ assert(callback);
+ DispatchHandle::call(boost::bind(&AsynchIO::requestedCall, this, callback));
+}
+
+void AsynchIO::requestedCall(RequestCallback callback) {
+ assert(callback);
+ callback(*this);
+}
+
+/** Return a queued buffer if there are enough
+ * to spare
+ */
+AsynchIO::BufferBase* AsynchIO::getQueuedBuffer() {
+ BufferBase* buff = bufferQueue.empty() ? 0 : bufferQueue.back();
+ // An "unread" buffer is reserved for future read operations (which
+ // take from the front of the queue).
+ if (!buff || (buff->dataCount && bufferQueue.size() == 1)) {
+ QPID_LOG(error, "No IO buffers available");
+ return 0;
+ }
+ assert(buff->dataCount == 0);
+ bufferQueue.pop_back();
+ return buff;
+}
+
+/*
+ * We keep on reading as long as we have something to read, a buffer
+ * to put it in and reading is not stopped by flow control.
+ */
+void AsynchIO::readable(DispatchHandle& h) {
+ AbsTime readStartTime = AbsTime::now();
+ size_t total = 0;
+ int readCalls = 0;
+ do {
+ // (Try to) get a buffer
+ if (!bufferQueue.empty()) {
+ // Read into buffer
+ BufferBase* buff = bufferQueue.front();
+ assert(buff);
+ bufferQueue.pop_front();
+ errno = 0;
+ int readCount = buff->byteCount-buff->dataCount;
+ int rc = socket.read(buff->bytes + buff->dataCount, readCount);
+ ++readCalls;
+ if (rc > 0) {
+ buff->dataCount += rc;
+ threadReadTotal += rc;
+ total += rc;
+
+ readCallback(*this, buff);
+ int64_t duration = Duration(readStartTime, AbsTime::now());
+ if (rc != readCount) {
+ // If we didn't fill the read buffer then time to stop reading
+ QPID_PROBE4(asynchio_read_finished_done, &h, duration, total, readCalls);
+ break;
+ }
+
+ // Stop reading if we've overrun our timeslot
+ if ( duration > threadMaxIoTimeNs) {
+ QPID_PROBE4(asynchio_read_finished_maxtime, &h, duration, total, readCalls);
+ break;
+ }
+
+ } else {
+ // Put buffer back (at front so it doesn't interfere with unread buffers)
+ bufferQueue.push_front(buff);
+ assert(buff);
+
+ QPID_PROBE5(asynchio_read_finished_error, &h, Duration(readStartTime, AbsTime::now()), total, readCalls, errno);
+ // Eof or other side has gone away
+ if (rc == 0 || errno == ECONNRESET) {
+ eofCallback(*this);
+ h.unwatchRead();
+ break;
+ } else if (errno == EAGAIN) {
+ // We have just put a buffer back so we know
+ // we can carry on watching for reads
+ break;
+ } else {
+ // Report error then just treat as a socket disconnect
+ QPID_LOG(error, "Error reading socket: " << qpid::sys::strError(errno) << "(" << errno << ")" );
+ eofCallback(*this);
+ h.unwatchRead();
+ break;
+ }
+ }
+ } else {
+ // Something to read but no buffer
+ if (emptyCallback) {
+ emptyCallback(*this);
+ }
+ // If we still have no buffers we can't do anything more
+ if (bufferQueue.empty()) {
+ h.unwatchRead();
+ QPID_PROBE4(asynchio_read_finished_nobuffers, &h, Duration(readStartTime, AbsTime::now()), total, readCalls);
+ break;
+ }
+
+ }
+ } while (true);
+
+ ++threadReadCount;
+ return;
+}
+
+/*
+ * We carry on writing whilst we have data to write and we can write
+ */
+void AsynchIO::writeable(DispatchHandle& h) {
+ AbsTime writeStartTime = AbsTime::now();
+ size_t total = 0;
+ int writeCalls = 0;
+ do {
+ // See if we've got something to write
+ if (!writeQueue.empty()) {
+ // Write buffer
+ BufferBase* buff = writeQueue.back();
+ writeQueue.pop_back();
+ errno = 0;
+ assert(buff->dataStart+buff->dataCount <= buff->byteCount);
+ int rc = socket.write(buff->bytes+buff->dataStart, buff->dataCount);
+ int64_t duration = Duration(writeStartTime, AbsTime::now());
+ ++writeCalls;
+ if (rc >= 0) {
+ threadWriteTotal += rc;
+ total += rc;
+
+ // If we didn't write full buffer put rest back
+ if (rc != buff->dataCount) {
+ buff->dataStart += rc;
+ buff->dataCount -= rc;
+ writeQueue.push_back(buff);
+ QPID_PROBE4(asynchio_write_finished_done, &h, duration, total, writeCalls);
+ break;
+ }
+
+ // Recycle the buffer
+ queueReadBuffer(buff);
+
+ // Stop writing if we've overrun our timeslot
+ if (duration > threadMaxIoTimeNs) {
+ QPID_PROBE4(asynchio_write_finished_maxtime, &h, duration, total, writeCalls);
+ break;
+ }
+ } else {
+ // Put buffer back
+ writeQueue.push_back(buff);
+ QPID_PROBE5(asynchio_write_finished_error, &h, duration, total, writeCalls, errno);
+
+ if (errno == ECONNRESET || errno == EPIPE) {
+ // Just stop watching for write here - we'll get a
+ // disconnect callback soon enough
+ h.unwatchWrite();
+ break;
+ } else if (errno == EAGAIN) {
+ // We have just put a buffer back so we know
+ // we can carry on watching for writes
+ break;
+ } else {
+ // Report error then just treat as a socket disconnect
+ QPID_LOG(error, "Error writing socket: " << qpid::sys::strError(errno) << "(" << errno << ")" );
+ h.unwatchWrite();
+ break;
+ }
+ }
+ } else {
+ int64_t duration = Duration(writeStartTime, AbsTime::now());
+ (void) duration; // force duration to be used if no probes are compiled
+
+ // If we're waiting to close the socket then can do it now as there is nothing to write
+ if (queuedClose) {
+ close(h);
+ QPID_PROBE4(asynchio_write_finished_closed, &h, duration, total, writeCalls);
+ break;
+ }
+ // Fd is writable, but nothing to write
+ if (idleCallback) {
+ writePending = false;
+ idleCallback(*this);
+ }
+ // If we still have no buffers to write we can't do anything more
+ if (writeQueue.empty() && !writePending && !queuedClose) {
+ h.unwatchWrite();
+ // The following handles the case where writePending is
+ // set to true after the test above; in this case its
+ // possible that the unwatchWrite overwrites the
+ // desired rewatchWrite so we correct that here
+ if (writePending)
+ h.rewatchWrite();
+ QPID_PROBE4(asynchio_write_finished_nodata, &h, duration, total, writeCalls);
+ break;
+ }
+ }
+ } while (true);
+
+ ++threadWriteCount;
+ return;
+}
+
+void AsynchIO::disconnected(DispatchHandle& h) {
+ // If we have not already queued close then call disconnected callback before closing
+ if (!queuedClose && disCallback) disCallback(*this);
+ close(h);
+}
+
+/*
+ * Close the socket and callback to say we've done it
+ */
+void AsynchIO::close(DispatchHandle& h) {
+ h.stopWatch();
+ socket.close();
+ if (closedCallback) {
+ closedCallback(*this, socket);
+ }
+}
+
+SecuritySettings AsynchIO::getSecuritySettings() {
+ SecuritySettings settings;
+ settings.ssf = socket.getKeyLen();
+ settings.authid = socket.getClientAuthId();
+ return settings;
+}
+
+} // namespace posix
+
+AsynchAcceptor* AsynchAcceptor::create(const Socket& s,
+ Callback callback)
+{
+ return new posix::AsynchAcceptor(s, callback);
+}
+
+AsynchConnector* AsynchConnector::create(const Socket& s,
+ const std::string& hostname,
+ const std::string& port,
+ ConnectedCallback connCb,
+ FailedCallback failCb)
+{
+ return new posix::AsynchConnector(s, hostname, port, connCb, failCb);
+}
+
+AsynchIO* AsynchIO::create(const Socket& s,
+ AsynchIO::ReadCallback rCb,
+ AsynchIO::EofCallback eofCb,
+ AsynchIO::DisconnectCallback disCb,
+ AsynchIO::ClosedCallback cCb,
+ AsynchIO::BuffersEmptyCallback eCb,
+ AsynchIO::IdleCallback iCb)
+{
+ return new posix::AsynchIO(s, rCb, eofCb, disCb, cCb, eCb, iCb);
+}
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/posix/BSDSocket.cpp b/qpid/cpp/src/qpid/sys/posix/BSDSocket.cpp
new file mode 100644
index 0000000000..7c31b13ae9
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/BSDSocket.cpp
@@ -0,0 +1,264 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/posix/BSDSocket.h"
+
+#include "qpid/sys/SocketAddress.h"
+#include "qpid/sys/posix/check.h"
+#include "qpid/sys/posix/PrivatePosix.h"
+
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/errno.h>
+#include <unistd.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netdb.h>
+#include <cstdlib>
+#include <string.h>
+
+namespace qpid {
+namespace sys {
+
+namespace {
+std::string getName(int fd, bool local)
+{
+ ::sockaddr_storage name_s; // big enough for any socket address
+ ::sockaddr* name = (::sockaddr*)&name_s;
+ ::socklen_t namelen = sizeof(name_s);
+
+ if (local) {
+ QPID_POSIX_CHECK( ::getsockname(fd, name, &namelen) );
+ } else {
+ QPID_POSIX_CHECK( ::getpeername(fd, name, &namelen) );
+ }
+
+ return SocketAddress::asString(name, namelen);
+}
+
+uint16_t getLocalPort(int fd)
+{
+ ::sockaddr_storage name_s; // big enough for any socket address
+ ::sockaddr* name = (::sockaddr*)&name_s;
+ ::socklen_t namelen = sizeof(name_s);
+
+ QPID_POSIX_CHECK( ::getsockname(fd, name, &namelen) );
+
+ return SocketAddress::getPort(name);
+}
+}
+
+BSDSocket::BSDSocket() :
+ fd(-1),
+ handle(new IOHandle),
+ nonblocking(false),
+ nodelay(false)
+{}
+
+Socket* createSocket()
+{
+ return new BSDSocket;
+}
+
+BSDSocket::BSDSocket(int fd0) :
+ fd(fd0),
+ handle(new IOHandle(fd)),
+ nonblocking(false),
+ nodelay(false)
+{}
+
+BSDSocket::~BSDSocket()
+{}
+
+BSDSocket::operator const IOHandle&() const
+{
+ return *handle;
+}
+
+void BSDSocket::createSocket(const SocketAddress& sa) const
+{
+ int& socket = fd;
+ if (socket != -1) BSDSocket::close();
+ int s = ::socket(getAddrInfo(sa).ai_family, getAddrInfo(sa).ai_socktype, 0);
+ if (s < 0) throw QPID_POSIX_ERROR(errno);
+ socket = s;
+ *handle = IOHandle(s);
+
+ try {
+ if (nonblocking) setNonblocking();
+ if (nodelay) setTcpNoDelay();
+ if (getAddrInfo(sa).ai_family == AF_INET6) {
+ int flag = 1;
+ int result = ::setsockopt(socket, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&flag, sizeof(flag));
+ QPID_POSIX_CHECK(result);
+ }
+ } catch (std::exception&) {
+ ::close(s);
+ socket = -1;
+ *handle = IOHandle();
+ throw;
+ }
+}
+
+void BSDSocket::setNonblocking() const {
+ int& socket = fd;
+ nonblocking = true;
+ if (socket != -1) {
+ QPID_POSIX_CHECK(::fcntl(socket, F_SETFL, O_NONBLOCK));
+ }
+}
+
+void BSDSocket::setTcpNoDelay() const
+{
+ int& socket = fd;
+ nodelay = true;
+ if (socket != -1) {
+ int flag = 1;
+ int result = ::setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag));
+ QPID_POSIX_CHECK(result);
+ }
+}
+
+void BSDSocket::connect(const SocketAddress& addr) const
+{
+ // The display name for an outbound connection needs to be the name that was specified
+ // for the address rather than a resolved IP address as we don't know which of
+ // the IP addresses is actually the one that will be connected to.
+ peername = addr.asString(false);
+
+ // However the string we compare with the local port must be numeric or it might not
+ // match when it should as getLocalAddress() will always be numeric
+ std::string connectname = addr.asString();
+
+ createSocket(addr);
+
+ const int& socket = fd;
+ // TODO the correct thing to do here is loop on failure until you've used all the returned addresses
+ if ((::connect(socket, getAddrInfo(addr).ai_addr, getAddrInfo(addr).ai_addrlen) < 0) &&
+ (errno != EINPROGRESS)) {
+ throw Exception(QPID_MSG(strError(errno) << ": " << peername));
+ }
+ // When connecting to a port on the same host which no longer has
+ // a process associated with it, the OS occasionally chooses the
+ // remote port (which is unoccupied) as the port to bind the local
+ // end of the socket, resulting in a "circular" connection.
+ //
+ // Raise an error if we see such a connection, since we know there is
+ // no listener on the peer address.
+ //
+ if (getLocalAddress() == connectname) {
+ close();
+ throw Exception(QPID_MSG("Connection refused: " << peername));
+ }
+}
+
+void BSDSocket::finishConnect(const SocketAddress&) const
+{
+}
+
+void
+BSDSocket::close() const
+{
+ int& socket = fd;
+ if (socket == -1) return;
+ if (::close(socket) < 0) throw QPID_POSIX_ERROR(errno);
+ socket = -1;
+ *handle = IOHandle();
+}
+
+int BSDSocket::listen(const SocketAddress& sa, int backlog) const
+{
+ createSocket(sa);
+
+ const int& socket = fd;
+ int yes=1;
+ QPID_POSIX_CHECK(::setsockopt(socket,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(yes)));
+
+ if (::bind(socket, getAddrInfo(sa).ai_addr, getAddrInfo(sa).ai_addrlen) < 0)
+ throw Exception(QPID_MSG("Can't bind to port " << sa.asString() << ": " << strError(errno)));
+ if (::listen(socket, backlog) < 0)
+ throw Exception(QPID_MSG("Can't listen on port " << sa.asString() << ": " << strError(errno)));
+
+ return getLocalPort(socket);
+}
+
+Socket* BSDSocket::accept() const
+{
+ int afd = ::accept(fd, 0, 0);
+ if ( afd >= 0) {
+ BSDSocket* s = new BSDSocket(afd);
+ s->localname = localname;
+ return s;
+ }
+ else if (errno == EAGAIN)
+ return 0;
+ else throw QPID_POSIX_ERROR(errno);
+}
+
+int BSDSocket::read(void *buf, size_t count) const
+{
+ return ::read(fd, buf, count);
+}
+
+int BSDSocket::write(const void *buf, size_t count) const
+{
+ return ::write(fd, buf, count);
+}
+
+std::string BSDSocket::getPeerAddress() const
+{
+ if (peername.empty()) {
+ peername = getName(fd, false);
+ }
+ return peername;
+}
+
+std::string BSDSocket::getLocalAddress() const
+{
+ if (localname.empty()) {
+ localname = getName(fd, true);
+ }
+ return localname;
+}
+
+int BSDSocket::getError() const
+{
+ int result;
+ socklen_t rSize = sizeof (result);
+
+ if (::getsockopt(fd, SOL_SOCKET, SO_ERROR, &result, &rSize) < 0)
+ throw QPID_POSIX_ERROR(errno);
+
+ return result;
+}
+
+int BSDSocket::getKeyLen() const
+{
+ return 0;
+}
+
+std::string BSDSocket::getClientAuthId() const
+{
+ return std::string();
+}
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/posix/BSDSocket.h b/qpid/cpp/src/qpid/sys/posix/BSDSocket.h
new file mode 100644
index 0000000000..ae73718d55
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/BSDSocket.h
@@ -0,0 +1,113 @@
+#ifndef QPID_SYS_BSDSOCKET_H
+#define QPID_SYS_BSDSOCKET_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/Socket.h"
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/CommonImportExport.h"
+#include <string>
+
+#include <boost/scoped_ptr.hpp>
+
+namespace qpid {
+namespace sys {
+
+class Duration;
+class IOHandle;
+class SocketAddress;
+
+namespace ssl {
+class SslMuxSocket;
+}
+
+class QPID_COMMON_CLASS_EXTERN BSDSocket : public Socket
+{
+public:
+ /** Create a socket wrapper for descriptor. */
+ QPID_COMMON_EXTERN BSDSocket();
+
+ /** Construct socket with existing fd (posix specific and not in Socket interface) */
+ QPID_COMMON_EXTERN BSDSocket(int fd);
+
+ QPID_COMMON_EXTERN ~BSDSocket();
+
+ QPID_COMMON_EXTERN operator const IOHandle&() const;
+
+ /** Set socket non blocking */
+ QPID_COMMON_EXTERN virtual void setNonblocking() const;
+
+ QPID_COMMON_EXTERN virtual void setTcpNoDelay() const;
+
+ QPID_COMMON_EXTERN virtual void connect(const SocketAddress&) const;
+ QPID_COMMON_EXTERN virtual void finishConnect(const SocketAddress&) const;
+
+ QPID_COMMON_EXTERN virtual void close() const;
+
+ /** Bind to a port and start listening.
+ *@return The bound port number
+ */
+ QPID_COMMON_EXTERN virtual int listen(const SocketAddress&, int backlog = 10) const;
+
+ /**
+ * Returns an address (host and port) for the remote end of the
+ * socket
+ */
+ QPID_COMMON_EXTERN std::string getPeerAddress() const;
+ /**
+ * Returns an address (host and port) for the local end of the
+ * socket
+ */
+ QPID_COMMON_EXTERN std::string getLocalAddress() const;
+
+ /**
+ * Returns the error code stored in the socket. This may be used
+ * to determine the result of a non-blocking connect.
+ */
+ QPID_COMMON_EXTERN int getError() const;
+
+ /** Accept a connection from a socket that is already listening
+ * and has an incoming connection
+ */
+ QPID_COMMON_EXTERN virtual Socket* accept() const;
+
+ // TODO The following are raw operations, maybe they need better wrapping?
+ QPID_COMMON_EXTERN virtual int read(void *buf, size_t count) const;
+ QPID_COMMON_EXTERN virtual int write(const void *buf, size_t count) const;
+
+ QPID_COMMON_EXTERN int getKeyLen() const;
+ QPID_COMMON_EXTERN std::string getClientAuthId() const;
+
+protected:
+ /** Create socket */
+ void createSocket(const SocketAddress&) const;
+
+ mutable int fd;
+ mutable boost::scoped_ptr<IOHandle> handle;
+ mutable std::string localname;
+ mutable std::string peername;
+ mutable bool nonblocking;
+ mutable bool nodelay;
+};
+
+}}
+#endif /*!QPID_SYS_BSDSOCKET_H*/
diff --git a/qpid/cpp/src/qpid/sys/posix/Condition.cpp b/qpid/cpp/src/qpid/sys/posix/Condition.cpp
new file mode 100644
index 0000000000..f629e50cd7
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/Condition.cpp
@@ -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.
+ *
+ */
+
+#include "Condition.h"
+
+namespace qpid {
+namespace sys {
+
+namespace {
+
+struct ClockMonotonicAttr {
+ ::pthread_condattr_t attr;
+
+ ClockMonotonicAttr() {
+ QPID_POSIX_ASSERT_THROW_IF(pthread_condattr_init(&attr));
+ QPID_POSIX_ASSERT_THROW_IF(pthread_condattr_setclock(&attr, CLOCK_MONOTONIC));
+ }
+};
+
+}
+
+Condition::Condition() {
+ static ClockMonotonicAttr attr;
+ QPID_POSIX_ASSERT_THROW_IF(pthread_cond_init(&condition, &attr.attr));
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/sys/posix/Condition.h b/qpid/cpp/src/qpid/sys/posix/Condition.h
new file mode 100644
index 0000000000..66f95d5fc8
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/Condition.h
@@ -0,0 +1,82 @@
+#ifndef _sys_posix_Condition_h
+#define _sys_posix_Condition_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/posix/PrivatePosix.h"
+
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/Time.h"
+
+#include <time.h>
+#include <sys/errno.h>
+#include <boost/noncopyable.hpp>
+
+namespace qpid {
+namespace sys {
+
+/**
+ * A condition variable for thread synchronization.
+ */
+class Condition
+{
+ public:
+ Condition();
+ ~Condition();
+ void wait(Mutex&);
+ bool wait(Mutex&, const AbsTime& absoluteTime);
+ void notify();
+ void notifyAll();
+
+ private:
+ pthread_cond_t condition;
+};
+
+inline Condition::~Condition() {
+ QPID_POSIX_ABORT_IF(pthread_cond_destroy(&condition));
+}
+
+inline void Condition::wait(Mutex& mutex) {
+ QPID_POSIX_ASSERT_THROW_IF(pthread_cond_wait(&condition, &mutex.mutex));
+}
+
+inline bool Condition::wait(Mutex& mutex, const AbsTime& absoluteTime){
+ struct timespec ts;
+ toTimespec(ts, absoluteTime);
+ int status = pthread_cond_timedwait(&condition, &mutex.mutex, &ts);
+ if (status != 0) {
+ if (status == ETIMEDOUT) return false;
+ throw QPID_POSIX_ERROR(status);
+ }
+ return true;
+}
+
+inline void Condition::notify(){
+ QPID_POSIX_ASSERT_THROW_IF(pthread_cond_signal(&condition));
+}
+
+inline void Condition::notifyAll(){
+ QPID_POSIX_ASSERT_THROW_IF(pthread_cond_broadcast(&condition));
+}
+
+}}
+#endif /*!_sys_posix_Condition_h*/
diff --git a/qpid/cpp/src/qpid/sys/posix/FileSysDir.cpp b/qpid/cpp/src/qpid/sys/posix/FileSysDir.cpp
new file mode 100755
index 0000000000..cec580164d
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/FileSysDir.cpp
@@ -0,0 +1,80 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/sys/FileSysDir.h"
+#include "qpid/sys/StrError.h"
+#include "qpid/log/Statement.h"
+#include "qpid/Exception.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <cerrno>
+#include <unistd.h>
+#include <dirent.h>
+#include <stdlib.h>
+
+namespace qpid {
+namespace sys {
+
+bool FileSysDir::exists (void) const
+{
+ const char *cpath = dirPath.c_str ();
+ struct stat s;
+ if (::stat(cpath, &s)) {
+ if (errno == ENOENT) {
+ return false;
+ }
+ throw qpid::Exception (strError(errno) +
+ ": Can't check directory: " + dirPath);
+ }
+ if (S_ISDIR(s.st_mode))
+ return true;
+ throw qpid::Exception(dirPath + " is not a directory");
+}
+
+void FileSysDir::mkdir(void)
+{
+ if (::mkdir(dirPath.c_str(), 0755))
+ throw Exception ("Can't create directory: " + dirPath);
+}
+
+void FileSysDir::forEachFile(Callback cb) const {
+
+ ::dirent** namelist;
+
+ int n = scandir(dirPath.c_str(), &namelist, 0, alphasort);
+ if (n == -1) throw Exception (strError(errno) + ": Can't scan directory: " + dirPath);
+
+ for (int i = 0; i<n; ++i) {
+ std::string fullpath = dirPath + "/" + namelist[i]->d_name;
+ // Filter out non files/stat problems etc.
+ struct ::stat s;
+ // Can't throw here without leaking memory, so just do nothing with
+ // entries for which stat() fails.
+ if (!::stat(fullpath.c_str(), &s)) {
+ if (S_ISREG(s.st_mode)) {
+ cb(fullpath);
+ }
+ }
+ ::free(namelist[i]);
+ }
+ ::free(namelist);
+}
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/posix/Fork.cpp b/qpid/cpp/src/qpid/sys/posix/Fork.cpp
new file mode 100644
index 0000000000..a0d404a16e
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/Fork.cpp
@@ -0,0 +1,129 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#include "qpid/sys/Fork.h"
+#include "qpid/log/Statement.h"
+#include "qpid/Exception.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/select.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+namespace qpid {
+namespace sys {
+
+using namespace std;
+
+namespace {
+
+void writeStr(int fd, const std::string& str) {
+ const char* WRITE_ERR = "Error writing to parent process";
+ int size = str.size();
+ if (int(sizeof(size)) > ::write(fd, &size, sizeof(size))) throw ErrnoException(WRITE_ERR);
+ if (size > ::write(fd, str.data(), size)) throw ErrnoException(WRITE_ERR);
+}
+
+string readStr(int fd) {
+ string value;
+ const char* READ_ERR = "Error reading from forked process";
+ int size;
+ if (int(sizeof(size)) > ::read(fd, &size, sizeof(size))) throw ErrnoException(READ_ERR);
+ if (size > 0) { // Read string message
+ value.resize(size);
+ if (size > ::read(fd, const_cast<char*>(value.data()), size)) throw ErrnoException(READ_ERR);
+ }
+ return value;
+}
+
+} // namespace
+
+Fork::Fork() {}
+Fork::~Fork() {}
+
+void Fork::fork() {
+ pid_t pid = ::fork();
+ if (pid < 0) throw ErrnoException("Failed to fork the process");
+ if (pid == 0) child();
+ else parent(pid);
+}
+
+ForkWithMessage::ForkWithMessage() {
+ pipeFds[0] = pipeFds[1] = -1;
+}
+
+struct AutoCloseFd {
+ int fd;
+ AutoCloseFd(int d) : fd(d) {}
+ ~AutoCloseFd() { ::close(fd); }
+};
+
+void ForkWithMessage::fork() {
+ if(::pipe(pipeFds) < 0) throw ErrnoException("Can't create pipe");
+ pid_t pid = ::fork();
+ if(pid < 0) throw ErrnoException("Fork fork failed");
+ if (pid == 0) { // Child
+ AutoCloseFd ac(pipeFds[1]); // Write side.
+ ::close(pipeFds[0]); // Read side
+ try {
+ child();
+ }
+ catch (const std::exception& e) {
+ QPID_LOG(error, "Error in forked child: " << e.what());
+ std::string msg = e.what();
+ if (msg.empty()) msg = " "; // Make sure we send a non-empty error string.
+ writeStr(pipeFds[1], msg);
+ }
+ }
+ else { // Parent
+ close(pipeFds[1]); // Write side.
+ AutoCloseFd ac(pipeFds[0]); // Read side
+ parent(pid);
+ }
+}
+
+string ForkWithMessage::wait(int timeout) { // parent waits for child.
+ errno = 0;
+ struct timeval tv;
+ tv.tv_sec = timeout;
+ tv.tv_usec = 0;
+
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(pipeFds[0], &fds);
+ int n=select(FD_SETSIZE, &fds, 0, 0, &tv);
+ if(n<0) throw ErrnoException("Error waiting for fork");
+ if (n==0) throw Exception("Timed out waiting for fork");
+
+ string error = readStr(pipeFds[0]);
+ if (error.empty()) return readStr(pipeFds[0]);
+ else throw Exception("Error in forked process: " + error);
+}
+
+// Write empty error string followed by value string to pipe.
+void ForkWithMessage::ready(const string& value) { // child
+ // Write empty string for error followed by value.
+ writeStr(pipeFds[1], string()); // No error
+ writeStr(pipeFds[1], value);
+}
+
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/posix/Fork.h b/qpid/cpp/src/qpid/sys/posix/Fork.h
new file mode 100644
index 0000000000..698c61ed30
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/Fork.h
@@ -0,0 +1,82 @@
+#ifndef QPID_SYS_POSIX_FORK_H
+#define QPID_SYS_POSIX_FORK_H
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <string>
+#include <sys/types.h>
+
+namespace qpid {
+namespace sys {
+
+/**
+ * Fork the process. Call parent() in parent and child() in child.
+ */
+class Fork {
+ public:
+ Fork();
+ virtual ~Fork();
+
+ /**
+ * Fork the process.
+ * Calls parent() in the parent process, child() in the child.
+ */
+ virtual void fork();
+
+ protected:
+
+ /** Called in parent process.
+ *@child pid of child process
+ */
+ virtual void parent(pid_t child) = 0;
+
+ /** Called in child process */
+ virtual void child() = 0;
+};
+
+/**
+ * Like Fork but also allows the child to send a string message
+ * or throw an exception to the parent.
+ */
+class ForkWithMessage : public Fork {
+ public:
+ ForkWithMessage();
+ void fork();
+
+ protected:
+ /** Call from parent(): wait for child to send a value or throw exception.
+ * @timeout in seconds to wait for response.
+ * @return value passed by child to ready().
+ */
+ std::string wait(int timeout);
+
+ /** Call from child(): Send a value to the parent.
+ *@param value returned by parent call to wait().
+ */
+ void ready(const std::string& value);
+
+ private:
+ int pipeFds[2];
+};
+
+}} // namespace qpid::sys
+
+
+
+#endif /*!QPID_SYS_POSIX_FORK_H*/
diff --git a/qpid/cpp/src/qpid/sys/posix/IOHandle.cpp b/qpid/cpp/src/qpid/sys/posix/IOHandle.cpp
new file mode 100644
index 0000000000..d3f502a63c
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/IOHandle.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/posix/PrivatePosix.h"
+
+namespace qpid {
+namespace sys {
+
+NullIOHandle DummyIOHandle;
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/posix/LockFile.cpp b/qpid/cpp/src/qpid/sys/posix/LockFile.cpp
new file mode 100755
index 0000000000..9fdf83f1bd
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/LockFile.cpp
@@ -0,0 +1,107 @@
+/*
+ *
+ * Copyright (c) 2008 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/sys/LockFile.h"
+#include "qpid/sys/posix/PidFile.h"
+
+#include <string>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "qpid/sys/posix/check.h"
+
+namespace qpid {
+namespace sys {
+
+class LockFilePrivate {
+ friend class LockFile;
+ friend class PidFile;
+
+ int fd;
+
+public:
+ LockFilePrivate(int f) : fd(f) {}
+};
+
+LockFile::LockFile(const std::string& path_, bool create)
+ : path(path_), created(create) {
+
+ errno = 0;
+ int flags=create ? O_WRONLY|O_CREAT|O_NOFOLLOW : O_RDWR;
+ int fd = ::open(path.c_str(), flags, 0644);
+ if (fd < 0) throw ErrnoException("Cannot open lock file " + path, errno);
+ if (::lockf(fd, F_TLOCK, 0) < 0) {
+ ::close(fd);
+ throw ErrnoException("Cannot lock " + path, errno);
+ }
+ impl.reset(new LockFilePrivate(fd));
+}
+
+LockFile::~LockFile() {
+ if (impl) {
+ int f = impl->fd;
+ if (f >= 0) {
+ if(::lockf(f, F_ULOCK, 0)) {} // Suppress warnings about ignoring return value.
+ ::close(f);
+ impl->fd = -1;
+ }
+ }
+}
+
+int LockFile::read(void* bytes, size_t len) const {
+ if (!impl)
+ throw Exception("Lock file not open: " + path);
+
+ ssize_t rc = ::read(impl->fd, bytes, len);
+ if ((ssize_t)len > rc) {
+ throw Exception("Cannot read lock file: " + path);
+ }
+ return rc;
+}
+
+int LockFile::write(void* bytes, size_t len) const {
+ if (!impl)
+ throw Exception("Lock file not open: " + path);
+
+ ssize_t rc = ::write(impl->fd, bytes, len);
+ if ((ssize_t)len > rc) {
+ throw Exception("Cannot write lock file: " + path);
+ }
+ return rc;
+}
+
+PidFile::PidFile(const std::string& path_, bool create):
+ LockFile(path_, create)
+{}
+
+pid_t PidFile::readPid(void) const {
+ pid_t pid;
+ int desired_read = sizeof(pid_t);
+ read(&pid, desired_read);
+ return pid;
+}
+
+void PidFile::writePid(void) {
+ pid_t pid = getpid();
+ int desired_write = sizeof(pid_t);
+ write(&pid, desired_write);
+}
+
+}} /* namespace qpid::sys */
diff --git a/qpid/cpp/src/qpid/sys/posix/MemStat.cpp b/qpid/cpp/src/qpid/sys/posix/MemStat.cpp
new file mode 100644
index 0000000000..2fbf119cab
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/MemStat.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/MemStat.h"
+
+#include <malloc.h>
+
+void qpid::sys::MemStat::loadMemInfo(qmf::org::apache::qpid::broker::Memory* object)
+{
+ struct mallinfo info(mallinfo());
+
+ object->set_malloc_arena(info.arena);
+ object->set_malloc_ordblks(info.ordblks);
+ object->set_malloc_hblks(info.hblks);
+ object->set_malloc_hblkhd(info.hblkhd);
+ object->set_malloc_uordblks(info.uordblks);
+ object->set_malloc_fordblks(info.fordblks);
+ object->set_malloc_keepcost(info.keepcost);
+}
+
diff --git a/qpid/cpp/src/qpid/sys/posix/MemoryMappedFile.cpp b/qpid/cpp/src/qpid/sys/posix/MemoryMappedFile.cpp
new file mode 100644
index 0000000000..b4292aa4bc
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/MemoryMappedFile.cpp
@@ -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.
+ *
+ */
+#include "qpid/sys/MemoryMappedFile.h"
+#include "qpid/Exception.h"
+#include "qpid/Msg.h"
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+namespace qpid {
+namespace sys {
+namespace {
+const std::string PAGEFILE_PREFIX("pf_");
+const std::string PATH_SEPARATOR("/");
+const std::string ESCAPE("%");
+const std::string VALID("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-.");
+std::string getFileName(const std::string& name, const std::string& dir)
+{
+ std::stringstream filename;
+ if (dir.size()) filename << dir << PATH_SEPARATOR << PAGEFILE_PREFIX;
+ size_t start = 0;
+ while (true) {
+ size_t i = name.find_first_not_of(VALID, start);
+ if (i == std::string::npos) {
+ filename << name.substr(start);
+ return filename.str();
+ } else {
+ if (i > start) filename << name.substr(start, i-start);
+ filename << ESCAPE << (int) name.at(i);
+ start = i+1;
+ }
+ }
+
+}
+}
+
+class MemoryMappedFilePrivate
+{
+ friend class MemoryMappedFile;
+ std::string path;
+ int fd;
+ MemoryMappedFilePrivate() : fd(0) {}
+};
+MemoryMappedFile::MemoryMappedFile() : state(new MemoryMappedFilePrivate) {}
+MemoryMappedFile::~MemoryMappedFile() { delete state; }
+
+void MemoryMappedFile::open(const std::string& name, const std::string& directory)
+{
+ // Ensure directory exists
+ if ( ::mkdir(directory.c_str(), S_IRWXU | S_IRGRP | S_IXGRP )!=0 && errno!=EEXIST ) {
+ throw qpid::Exception(QPID_MSG("Failed to create memory mapped file directory " << directory << ": " << qpid::sys::strError(errno)));
+ }
+
+ state->path = getFileName(name, directory);
+
+ int flags = O_CREAT | O_TRUNC | O_RDWR;
+ int fd = ::open(state->path.c_str(), flags, S_IRUSR | S_IWUSR);
+ if (fd == -1) throw qpid::Exception(QPID_MSG("Failed to open memory mapped file " << state->path << ": " << qpid::sys::strError(errno) << " [flags=" << flags << "]"));
+ state->fd = fd;
+}
+
+void MemoryMappedFile::close()
+{
+ ::close(state->fd);
+ ::unlink(state->path.c_str());
+}
+
+size_t MemoryMappedFile::getPageSize()
+{
+ return ::sysconf(_SC_PAGE_SIZE);
+}
+
+char* MemoryMappedFile::map(size_t offset, size_t size)
+{
+ int protection = PROT_READ | PROT_WRITE;
+ char* region = (char*) ::mmap(0, size, protection, MAP_SHARED, state->fd, offset);
+ if (region == MAP_FAILED) {
+ throw qpid::Exception(QPID_MSG("Failed to map page into memory: " << qpid::sys::strError(errno)));
+ }
+ return region;
+
+}
+
+void MemoryMappedFile::unmap(char* region, size_t size)
+{
+ ::munmap(region, size);
+}
+
+void MemoryMappedFile::flush(char* region, size_t size)
+{
+ ::msync(region, size, MS_ASYNC);
+}
+
+void MemoryMappedFile::expand(size_t offset)
+{
+ if ((::lseek(state->fd, offset - 1, SEEK_SET) == -1) || (::write(state->fd, "", 1) == -1)) {
+ throw qpid::Exception(QPID_MSG("Failed to expand paged queue file: " << qpid::sys::strError(errno)));
+ }
+}
+
+bool MemoryMappedFile::isSupported()
+{
+ return true;
+}
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/posix/Mutex.cpp b/qpid/cpp/src/qpid/sys/posix/Mutex.cpp
new file mode 100644
index 0000000000..0e1f0d30c2
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/Mutex.cpp
@@ -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.
+ *
+ */
+#include "qpid/sys/Mutex.h"
+
+namespace qpid {
+namespace sys {
+
+/**
+ * Initialise a recursive mutex attr for use in creating mutexes later
+ * (we use pthread_once to make sure it is initialised exactly once)
+ */
+
+namespace {
+pthread_once_t onceControl = PTHREAD_ONCE_INIT;
+pthread_mutexattr_t mutexattr;
+
+void initMutexattr() {
+ pthread_mutexattr_init(&mutexattr);
+ pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_RECURSIVE);
+}
+}
+
+const pthread_mutexattr_t* Mutex::getAttribute() {
+ pthread_once(&onceControl, initMutexattr);
+ return &mutexattr;
+}
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/posix/Mutex.h b/qpid/cpp/src/qpid/sys/posix/Mutex.h
new file mode 100644
index 0000000000..e2b21b5a56
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/Mutex.h
@@ -0,0 +1,158 @@
+#ifndef _sys_posix_Mutex_h
+#define _sys_posix_Mutex_h
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/sys/posix/check.h"
+
+#include <pthread.h>
+#include <boost/noncopyable.hpp>
+
+namespace qpid {
+namespace sys {
+
+class Condition;
+
+/**
+ * Mutex lock.
+ */
+class Mutex : private boost::noncopyable {
+ friend class Condition;
+ static const pthread_mutexattr_t* getAttribute();
+
+public:
+ typedef ::qpid::sys::ScopedLock<Mutex> ScopedLock;
+ typedef ::qpid::sys::ScopedUnlock<Mutex> ScopedUnlock;
+
+ inline Mutex();
+ inline ~Mutex();
+ inline void lock();
+ inline void unlock();
+ inline bool trylock();
+
+
+protected:
+ pthread_mutex_t mutex;
+};
+
+/**
+ * RW lock.
+ */
+class RWlock : private boost::noncopyable {
+ friend class Condition;
+
+public:
+ typedef ::qpid::sys::ScopedRlock<RWlock> ScopedRlock;
+ typedef ::qpid::sys::ScopedWlock<RWlock> ScopedWlock;
+
+ inline RWlock();
+ inline ~RWlock();
+ inline void wlock(); // will write-lock
+ inline void rlock(); // will read-lock
+ inline void unlock();
+ inline void trywlock(); // will write-try
+ inline void tryrlock(); // will read-try
+
+protected:
+ pthread_rwlock_t rwlock;
+};
+
+
+/**
+ * PODMutex is a POD, can be static-initialized with
+ * PODMutex m = QPID_PODMUTEX_INITIALIZER
+ */
+struct PODMutex
+{
+ typedef ::qpid::sys::ScopedLock<PODMutex> ScopedLock;
+
+ inline void lock();
+ inline void unlock();
+ inline bool trylock();
+
+ // Must be public to be a POD:
+ pthread_mutex_t mutex;
+};
+
+#define QPID_MUTEX_INITIALIZER { PTHREAD_MUTEX_INITIALIZER }
+
+void PODMutex::lock() {
+ QPID_POSIX_ASSERT_THROW_IF(pthread_mutex_lock(&mutex));
+}
+
+void PODMutex::unlock() {
+ QPID_POSIX_ASSERT_THROW_IF(pthread_mutex_unlock(&mutex));
+}
+
+bool PODMutex::trylock() {
+ return pthread_mutex_trylock(&mutex) == 0;
+}
+
+Mutex::Mutex() {
+ QPID_POSIX_ASSERT_THROW_IF(pthread_mutex_init(&mutex, getAttribute()));
+}
+
+Mutex::~Mutex(){
+ QPID_POSIX_ABORT_IF(pthread_mutex_destroy(&mutex));
+}
+
+void Mutex::lock() {
+ QPID_POSIX_ASSERT_THROW_IF(pthread_mutex_lock(&mutex));
+}
+
+void Mutex::unlock() {
+ QPID_POSIX_ASSERT_THROW_IF(pthread_mutex_unlock(&mutex));
+}
+
+bool Mutex::trylock() {
+ return pthread_mutex_trylock(&mutex) == 0;
+}
+
+
+RWlock::RWlock() {
+ QPID_POSIX_ASSERT_THROW_IF(pthread_rwlock_init(&rwlock, NULL));
+}
+
+RWlock::~RWlock(){
+ QPID_POSIX_ABORT_IF(pthread_rwlock_destroy(&rwlock));
+}
+
+void RWlock::wlock() {
+ QPID_POSIX_ASSERT_THROW_IF(pthread_rwlock_wrlock(&rwlock));
+}
+
+void RWlock::rlock() {
+ QPID_POSIX_ASSERT_THROW_IF(pthread_rwlock_rdlock(&rwlock));
+}
+
+void RWlock::unlock() {
+ QPID_POSIX_ASSERT_THROW_IF(pthread_rwlock_unlock(&rwlock));
+}
+
+void RWlock::trywlock() {
+ QPID_POSIX_ASSERT_THROW_IF(pthread_rwlock_trywrlock(&rwlock));
+}
+
+void RWlock::tryrlock() {
+ QPID_POSIX_ASSERT_THROW_IF(pthread_rwlock_tryrdlock(&rwlock));
+}
+
+
+}}
+#endif /*!_sys_posix_Mutex_h*/
diff --git a/qpid/cpp/src/qpid/sys/posix/Path.cpp b/qpid/cpp/src/qpid/sys/posix/Path.cpp
new file mode 100644
index 0000000000..063e3cfc51
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/Path.cpp
@@ -0,0 +1,60 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/sys/Path.h"
+#include "qpid/sys/StrError.h"
+#include "qpid/Exception.h"
+
+#include <sys/stat.h>
+#include <errno.h>
+
+#include <sys/types.h>
+
+
+
+namespace qpid {
+namespace sys {
+
+const std::string Path::separator("/");
+
+namespace {
+// Return true for success, false for ENOENT, throw otherwise.
+bool getStat(const std::string& path, struct ::stat& s) {
+ if (::stat(path.c_str(), &s)) {
+ if (errno == ENOENT) return false;
+ throw Exception(strError(errno) + ": Invalid path: " + path);
+ }
+ return true;
+}
+
+bool isFlag(const std::string& path, unsigned long flag) {
+ struct ::stat s;
+ return getStat(path, s) && (s.st_mode & flag);
+}
+}
+
+bool Path::exists () const {
+ struct ::stat s;
+ return getStat(path, s);
+}
+
+bool Path::isFile() const { return isFlag(path, S_IFREG); }
+bool Path::isDirectory() const { return isFlag(path, S_IFDIR); }
+bool Path::isAbsolute() const { return (path.size() > 0 && path[0] == separator[0]); }
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/posix/PidFile.h b/qpid/cpp/src/qpid/sys/posix/PidFile.h
new file mode 100644
index 0000000000..fb19d407f4
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/PidFile.h
@@ -0,0 +1,62 @@
+#ifndef _sys_PidFile_h
+#define _sys_PidFile_h
+
+/*
+ *
+ * Copyright (c) 2008 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/sys/LockFile.h"
+
+#include "qpid/CommonImportExport.h"
+#include "qpid/sys/IntegerTypes.h"
+
+#include <boost/noncopyable.hpp>
+#include <boost/shared_ptr.hpp>
+#include <string>
+
+namespace qpid {
+namespace sys {
+
+class PidFile : public LockFile
+{
+public:
+ QPID_COMMON_EXTERN PidFile(const std::string& path_, bool create);
+
+ /**
+ * Read the process ID from the lock file. This method assumes that
+ * if there is a process ID in the file, it was written there by
+ * writePid(); thus, it's at the start of the file.
+ *
+ * Throws an exception if there is an error reading the file.
+ *
+ * @returns The stored process ID. No validity check is done on it.
+ */
+ QPID_COMMON_EXTERN pid_t readPid(void) const;
+
+ /**
+ * Write the current process's ID to the lock file. It's written at
+ * the start of the file and will overwrite any other content that
+ * may be in the file.
+ *
+ * Throws an exception if the write fails.
+ */
+ QPID_COMMON_EXTERN void writePid(void);
+};
+
+}} /* namespace qpid::sys */
+
+#endif /*!_sys_PidFile_h*/
diff --git a/qpid/cpp/src/qpid/sys/posix/PipeHandle.cpp b/qpid/cpp/src/qpid/sys/posix/PipeHandle.cpp
new file mode 100755
index 0000000000..4b19783338
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/PipeHandle.cpp
@@ -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.
+//
+
+#include "qpid/sys/PipeHandle.h"
+#include "qpid/sys/posix/check.h"
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+
+namespace qpid {
+namespace sys {
+
+PipeHandle::PipeHandle(bool nonBlocking) {
+
+ int pair[2];
+ pair[0] = pair[1] = -1;
+
+ if (socketpair(PF_UNIX, SOCK_STREAM, 0, pair) == -1)
+ throw qpid::Exception(QPID_MSG("Creation of pipe failed"));
+
+ writeFd = pair[0];
+ readFd = pair[1];
+
+ // Set the socket to non-blocking
+ if (nonBlocking) {
+ int flags = fcntl(readFd, F_GETFL);
+ fcntl(readFd, F_SETFL, flags | O_NONBLOCK);
+ }
+}
+
+PipeHandle::~PipeHandle() {
+ close(readFd);
+ close(writeFd);
+}
+
+int PipeHandle::read(void* buf, size_t bufSize) {
+ return ::read(readFd,buf,bufSize);
+}
+
+int PipeHandle::write(const void* buf, size_t bufSize) {
+ return ::write(writeFd,buf,bufSize);
+}
+
+int PipeHandle::getReadHandle() {
+ return readFd;
+}
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/posix/PollableCondition.cpp b/qpid/cpp/src/qpid/sys/posix/PollableCondition.cpp
new file mode 100644
index 0000000000..aa129faf20
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/PollableCondition.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/PollableCondition.h"
+#include "qpid/sys/DispatchHandle.h"
+#include "qpid/sys/posix/PrivatePosix.h"
+#include "qpid/Exception.h"
+
+#include <boost/bind.hpp>
+
+#include <unistd.h>
+#include <fcntl.h>
+
+namespace qpid {
+namespace sys {
+
+class PollableConditionPrivate : public sys::IOHandle {
+ friend class PollableCondition;
+
+private:
+ PollableConditionPrivate(const sys::PollableCondition::Callback& cb,
+ sys::PollableCondition& parent,
+ const boost::shared_ptr<sys::Poller>& poller);
+ ~PollableConditionPrivate();
+
+ void dispatch(sys::DispatchHandle& h);
+ void set();
+ void clear();
+
+private:
+ PollableCondition::Callback cb;
+ PollableCondition& parent;
+ boost::shared_ptr<sys::Poller> poller;
+ int writeFd;
+ std::auto_ptr<DispatchHandleRef> handle;
+};
+
+PollableConditionPrivate::PollableConditionPrivate(
+ const sys::PollableCondition::Callback& cb,
+ sys::PollableCondition& parent,
+ const boost::shared_ptr<sys::Poller>& poller
+) : cb(cb), parent(parent)
+{
+ int fds[2];
+ if (::pipe(fds) == -1)
+ throw ErrnoException(QPID_MSG("Can't create PollableCondition"));
+ fd = fds[0];
+ writeFd = fds[1];
+ if (::fcntl(fd, F_SETFL, O_NONBLOCK) == -1)
+ throw ErrnoException(QPID_MSG("Can't create PollableCondition"));
+ if (::fcntl(writeFd, F_SETFL, O_NONBLOCK) == -1)
+ throw ErrnoException(QPID_MSG("Can't create PollableCondition"));
+ handle.reset (new DispatchHandleRef(
+ *this,
+ boost::bind(&sys::PollableConditionPrivate::dispatch, this, _1),
+ 0, 0));
+ handle->startWatch(poller);
+ handle->unwatch();
+
+ // Make the read FD readable
+ static const char dummy=0;
+ ssize_t n = ::write(writeFd, &dummy, 1);
+ if (n == -1 && errno != EAGAIN)
+ throw ErrnoException("Error setting PollableCondition");
+}
+
+PollableConditionPrivate::~PollableConditionPrivate() {
+ handle->stopWatch();
+ close(writeFd);
+}
+
+void PollableConditionPrivate::dispatch(sys::DispatchHandle&) {
+ cb(parent);
+}
+
+void PollableConditionPrivate::set() {
+ handle->rewatch();
+}
+
+void PollableConditionPrivate::clear() {
+ handle->unwatch();
+}
+
+
+PollableCondition::PollableCondition(const Callback& cb,
+ const boost::shared_ptr<sys::Poller>& poller
+) : impl(new PollableConditionPrivate(cb, *this, poller))
+{
+}
+
+PollableCondition::~PollableCondition()
+{
+ delete impl;
+}
+
+void PollableCondition::set() { impl->set(); }
+
+void PollableCondition::clear() { impl->clear(); }
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/posix/PosixPoller.cpp b/qpid/cpp/src/qpid/sys/posix/PosixPoller.cpp
new file mode 100644
index 0000000000..ae839b2e20
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/PosixPoller.cpp
@@ -0,0 +1,793 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/Poller.h"
+#include "qpid/sys/IOHandle.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/AtomicCount.h"
+#include "qpid/sys/DeletionManager.h"
+#include "qpid/sys/posix/check.h"
+#include "qpid/sys/posix/PrivatePosix.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/Condition.h"
+
+#include <poll.h>
+#include <errno.h>
+#include <signal.h>
+
+#include <assert.h>
+#include <queue>
+#include <set>
+#include <exception>
+
+/*
+ *
+ * This is a qpid::sys::Poller implementation for Posix systems.
+ *
+ * This module follows the structure of the Linux EpollPoller as closely as possible
+ * to simplify maintainability. Noteworthy differences:
+ *
+ * The Linux epoll_xxx() calls present one event at a time to multiple callers whereas poll()
+ * returns one or more events to a single caller. The EventStream class layers a
+ * "one event per call" view of the poll() result to multiple threads.
+ *
+ * The HandleSet is the master set of in-use PollerHandles. The EventStream
+ * maintains a snapshot copy taken just before the call to poll() that remains static
+ * until all flagged events have been processed.
+ *
+ * There is an additional window where the PollerHandlePrivate class may survive the
+ * parent PollerHandle destructor, i.e. between snapshots.
+ *
+ * Safe interrupting of the Poller is implemented using the "self-pipe trick".
+ *
+ */
+
+namespace qpid {
+namespace sys {
+
+// Deletion manager to handle deferring deletion of PollerHandles to when they definitely aren't being used
+DeletionManager<PollerHandlePrivate> PollerHandleDeletionManager;
+
+// Instantiate (and define) class static for DeletionManager
+template <>
+DeletionManager<PollerHandlePrivate>::AllThreadsStatuses DeletionManager<PollerHandlePrivate>::allThreadsStatuses(0);
+
+class PollerHandlePrivate {
+ friend class Poller;
+ friend class PollerPrivate;
+ friend class PollerHandle;
+ friend class HandleSet;
+
+ enum FDStat {
+ ABSENT,
+ MONITORED,
+ INACTIVE,
+ HUNGUP,
+ MONITORED_HUNGUP,
+ INTERRUPTED,
+ INTERRUPTED_HUNGUP,
+ DELETED
+ };
+
+ short events;
+ const IOHandle* ioHandle;
+ PollerHandle* pollerHandle;
+ FDStat stat;
+ Mutex lock;
+
+ PollerHandlePrivate(const IOHandle* h, PollerHandle* p) :
+ events(0),
+ ioHandle(h),
+ pollerHandle(p),
+ stat(ABSENT) {
+ }
+
+ int fd() const {
+ return ioHandle->fd;
+ }
+
+ bool isActive() const {
+ return stat == MONITORED || stat == MONITORED_HUNGUP;
+ }
+
+ void setActive() {
+ stat = (stat == HUNGUP || stat == INTERRUPTED_HUNGUP)
+ ? MONITORED_HUNGUP
+ : MONITORED;
+ }
+
+ bool isInactive() const {
+ return stat == INACTIVE || stat == HUNGUP;
+ }
+
+ void setInactive() {
+ stat = INACTIVE;
+ }
+
+ bool isIdle() const {
+ return stat == ABSENT;
+ }
+
+ void setIdle() {
+ stat = ABSENT;
+ }
+
+ bool isHungup() const {
+ return
+ stat == MONITORED_HUNGUP ||
+ stat == HUNGUP ||
+ stat == INTERRUPTED_HUNGUP;
+ }
+
+ void setHungup() {
+ assert(stat == MONITORED);
+ stat = HUNGUP;
+ }
+
+ bool isInterrupted() const {
+ return stat == INTERRUPTED || stat == INTERRUPTED_HUNGUP;
+ }
+
+ void setInterrupted() {
+ stat = (stat == MONITORED_HUNGUP || stat == HUNGUP)
+ ? INTERRUPTED_HUNGUP
+ : INTERRUPTED;
+ }
+
+ bool isDeleted() const {
+ return stat == DELETED;
+ }
+
+ void setDeleted() {
+ stat = DELETED;
+ }
+};
+
+PollerHandle::PollerHandle(const IOHandle& h) :
+ impl(new PollerHandlePrivate(&h, this))
+{}
+
+PollerHandle::~PollerHandle() {
+ {
+ ScopedLock<Mutex> l(impl->lock);
+ if (impl->isDeleted()) {
+ return;
+ }
+ impl->pollerHandle = 0;
+ if (impl->isInterrupted()) {
+ impl->setDeleted();
+ return;
+ }
+ assert(impl->isIdle());
+ impl->setDeleted();
+ }
+ PollerHandleDeletionManager.markForDeletion(impl);
+}
+
+class HandleSet
+{
+ Mutex lock;
+ bool stale;
+ std::set<PollerHandlePrivate*> handles;
+ public:
+ HandleSet() : stale(true) {}
+ void add(PollerHandlePrivate*);
+ void remove(PollerHandlePrivate*);
+ void cleanup();
+ bool snapshot(std::vector<PollerHandlePrivate *>& , std::vector<struct ::pollfd>&);
+ void setStale();
+};
+
+void HandleSet::add(PollerHandlePrivate* h)
+{
+ ScopedLock<Mutex> l(lock);
+ handles.insert(h);
+}
+void HandleSet::remove(PollerHandlePrivate* h)
+{
+ ScopedLock<Mutex> l(lock);
+ handles.erase(h);
+}
+void HandleSet::cleanup()
+{
+ // Inform all registered handles of disconnection
+ std::set<PollerHandlePrivate*> copy;
+ handles.swap(copy);
+ for (std::set<PollerHandlePrivate*>::const_iterator i = copy.begin(); i != copy.end(); ++i) {
+ PollerHandlePrivate& eh = **i;
+ {
+ ScopedLock<Mutex> l(eh.lock);
+ if (!eh.isDeleted()) {
+ Poller::Event event((*i)->pollerHandle, Poller::DISCONNECTED);
+ event.process();
+ }
+ }
+ }
+}
+void HandleSet::setStale()
+{
+ // invalidate cached pollfds for next snapshot
+ ScopedLock<Mutex> l(lock);
+ stale = true;
+}
+
+/**
+ * Concrete implementation of Poller to use Posix poll()
+ * interface
+ */
+class PollerPrivate {
+ friend class Poller;
+ friend class EventStream;
+ friend class HandleSet;
+
+ class SignalPipe {
+ /**
+ * Used to wakeup a thread in ::poll()
+ */
+ int fds[2];
+ bool signaled;
+ bool permanent;
+ Mutex lock;
+ public:
+ SignalPipe() : signaled(false), permanent(false) {
+ QPID_POSIX_CHECK(::pipe(fds));
+ }
+
+ ~SignalPipe() {
+ ::close(fds[0]);
+ ::close(fds[1]);
+ }
+
+ int getFD() {
+ return fds[0];
+ }
+
+ bool isSet() {
+ return signaled;
+ }
+
+ void set() {
+ ScopedLock<Mutex> l(lock);
+ if (signaled)
+ return;
+ signaled = true;
+ QPID_POSIX_CHECK(::write(fds[1], " ", 1));
+ }
+
+ void reset() {
+ if (permanent)
+ return;
+ ScopedLock<Mutex> l(lock);
+ if (signaled) {
+ char ignore;
+ QPID_POSIX_CHECK(::read(fds[0], &ignore, 1));
+ signaled = false;
+ }
+ }
+
+ void setPermanently() {
+ // async signal safe calls only. No locking.
+ permanent = true;
+ signaled = true;
+ QPID_POSIX_CHECK(::write(fds[1], " ", 2));
+ // poll() should never block now
+ }
+ };
+
+ // Collect pending events and serialize access. Maintain array of pollfd structs.
+ class EventStream {
+ typedef Poller::Event Event;
+ PollerPrivate& pollerPrivate;
+ SignalPipe& signalPipe;
+ std::queue<PollerHandlePrivate*> interruptedHandles;
+ std::vector<struct ::pollfd> pollfds;
+ std::vector<PollerHandlePrivate*> pollHandles;
+ Mutex streamLock;
+ Mutex serializeLock;
+ Condition serializer;
+ bool busy;
+ int currentPollfd;
+ int pollCount;
+ int waiters;
+
+ public:
+
+ EventStream(PollerPrivate* p) : pollerPrivate(*p), signalPipe(p->signalPipe), busy(false),
+ currentPollfd(0), pollCount(0), waiters(0) {
+ // The signal pipe is the first element of pollfds and pollHandles
+ pollfds.reserve(8);
+ pollfds.resize(1);
+ pollfds[0].fd = pollerPrivate.signalPipe.getFD();
+ pollfds[0].events = POLLIN;
+ pollfds[0].revents = 0;
+
+ pollHandles.reserve(8);
+ pollHandles.resize(1);
+ pollHandles[0] = 0;
+ }
+
+ void addInterrupt(PollerHandle& handle) {
+ ScopedLock<Mutex> l(streamLock);
+ interruptedHandles.push(handle.impl);
+ }
+
+ // Serialize access to the stream.
+ Event next(Duration timeout) {
+ AbsTime targetTimeout =
+ (timeout == TIME_INFINITE) ?
+ FAR_FUTURE :
+ AbsTime(now(), timeout);
+
+
+ ScopedLock<Mutex> l(serializeLock);
+ Event event(0, Poller::INVALID);
+ while (busy) {
+ waiters++;
+ bool timedout = !serializer.wait(serializeLock, targetTimeout);
+ waiters--;
+
+ if (busy && timedout) {
+ return Event(0, Poller::TIMEOUT);
+ }
+ }
+ busy = true;
+ {
+ ScopedUnlock<Mutex> ul(serializeLock);
+ event = getEvent(targetTimeout);
+ }
+ busy = false;
+
+ if (waiters > 0)
+ serializer.notify();
+ return event;
+ }
+
+ Event getEvent(AbsTime targetTimeout) {
+ bool timeoutPending = false;
+
+ ScopedLock<Mutex> l(streamLock); // hold lock except for poll()
+
+ // loop until poll event, async interrupt, or timeout
+ while (true) {
+
+ // first check for any interrupts
+ while (interruptedHandles.size() > 0) {
+ PollerHandlePrivate& eh = *interruptedHandles.front();
+ interruptedHandles.pop();
+ {
+ ScopedLock<Mutex> lk(eh.lock);
+ if (!eh.isDeleted()) {
+ if (!eh.isIdle()) {
+ eh.setInactive();
+ }
+
+ // nullify the corresponding pollfd event, if any
+ int ehfd = eh.fd();
+ std::vector<struct ::pollfd>::iterator i = pollfds.begin() + 1; // skip self pipe at front
+ for (; i != pollfds.end(); i++) {
+ if (i->fd == ehfd) {
+ i->events = 0;
+ if (i->revents) {
+ i->revents = 0;
+ pollCount--;
+ }
+ break;
+ }
+ }
+ return Event(eh.pollerHandle, Poller::INTERRUPTED);
+ }
+ }
+ PollerHandleDeletionManager.markForDeletion(&eh);
+ }
+
+ // Check for shutdown
+ if (pollerPrivate.isShutdown) {
+ PollerHandleDeletionManager.markAllUnusedInThisThread();
+ return Event(0, Poller::SHUTDOWN);
+ }
+
+ // search for any remaining events from earlier poll()
+ int nfds = pollfds.size();
+ while ((pollCount > 0) && (currentPollfd < nfds)) {
+ int index = currentPollfd++;
+ short evt = pollfds[index].revents;
+ if (evt != 0) {
+ pollCount--;
+ PollerHandlePrivate& eh = *pollHandles[index];
+ ScopedLock<Mutex> l(eh.lock);
+ // stop polling this handle until resetMode()
+ pollfds[index].events = 0;
+
+ // the handle could have gone inactive since snapshot taken
+ if (eh.isActive()) {
+ PollerHandle* handle = eh.pollerHandle;
+ assert(handle);
+
+ // If the connection has been hungup we could still be readable
+ // (just not writable), allow us to readable until we get here again
+ if (evt & POLLHUP) {
+ if (eh.isHungup()) {
+ eh.setInactive();
+ // Don't set up last Handle so that we don't reset this handle
+ // on re-entering Poller::wait. This means that we will never
+ // be set active again once we've returned disconnected, and so
+ // can never be returned again.
+ return Event(handle, Poller::DISCONNECTED);
+ }
+ eh.setHungup();
+ } else {
+ eh.setInactive();
+ }
+ return Event(handle, PollerPrivate::epollToDirection(evt));
+ }
+ }
+ }
+
+ if (timeoutPending) {
+ return Event(0, Poller::TIMEOUT);
+ }
+
+ // no outstanding events, poll() for more
+ {
+ ScopedUnlock<Mutex> ul(streamLock);
+
+ bool refreshed = pollerPrivate.registeredHandles.snapshot(pollHandles, pollfds);
+ if (refreshed) {
+ // we just drained all interruptedHandles and got a fresh snapshot
+ PollerHandleDeletionManager.markAllUnusedInThisThread();
+ }
+
+ if (!signalPipe.isSet()) {
+ int timeoutMs = -1;
+ if (!(targetTimeout == FAR_FUTURE)) {
+ timeoutMs = Duration(now(), targetTimeout) / TIME_MSEC;
+ if (timeoutMs < 0)
+ timeoutMs = 0;
+ }
+
+ pollCount = ::poll(&pollfds[0], pollfds.size(), timeoutMs);
+
+ if (pollCount ==-1 && errno != EINTR) {
+ QPID_POSIX_CHECK(pollCount);
+ }
+ else if (pollCount == 0) {
+ // timeout, unless shutdown or interrupt arrives in another thread
+ timeoutPending = true;
+ }
+ else {
+ if (pollfds[0].revents) {
+ pollCount--; // signal pipe doesn't count
+ }
+ }
+ }
+ else
+ pollCount = 0;
+ signalPipe.reset();
+ }
+ currentPollfd = 1;
+ }
+ }
+ };
+
+ bool isShutdown;
+ HandleSet registeredHandles;
+ AtomicCount threadCount;
+ SignalPipe signalPipe;
+ EventStream eventStream;
+
+ static short directionToEpollEvent(Poller::Direction dir) {
+ switch (dir) {
+ case Poller::INPUT: return POLLIN;
+ case Poller::OUTPUT: return POLLOUT;
+ case Poller::INOUT: return POLLIN | POLLOUT;
+ default: return 0;
+ }
+ }
+
+ static Poller::EventType epollToDirection(short events) {
+ // POLLOUT & POLLHUP are mutually exclusive really, but at least socketpairs
+ // can give you both!
+ events = (events & POLLHUP) ? events & ~POLLOUT : events;
+ short e = events & (POLLIN | POLLOUT);
+ switch (e) {
+ case POLLIN: return Poller::READABLE;
+ case POLLOUT: return Poller::WRITABLE;
+ case POLLIN | POLLOUT: return Poller::READ_WRITABLE;
+ default:
+ return (events & (POLLHUP | POLLERR)) ?
+ Poller::DISCONNECTED : Poller::INVALID;
+ }
+ }
+
+ PollerPrivate() :
+ isShutdown(false), eventStream(this) {
+ }
+
+ ~PollerPrivate() {}
+
+ void resetMode(PollerHandlePrivate& handle);
+
+ void interrupt() {
+ signalPipe.set();
+ }
+
+ void interruptAll() {
+ // be async signal safe
+ signalPipe.setPermanently();
+ }
+};
+
+
+void Poller::registerHandle(PollerHandle& handle) {
+ PollerHandlePrivate& eh = *handle.impl;
+ ScopedLock<Mutex> l(eh.lock);
+ assert(eh.isIdle());
+
+ eh.setActive();
+ impl->registeredHandles.add(handle.impl);
+ // not stale until monitored
+}
+
+void Poller::unregisterHandle(PollerHandle& handle) {
+ PollerHandlePrivate& eh = *handle.impl;
+ ScopedLock<Mutex> l(eh.lock);
+ assert(!eh.isIdle());
+
+ eh.setIdle();
+ impl->registeredHandles.remove(handle.impl);
+ impl->registeredHandles.setStale();
+ impl->interrupt();
+}
+
+void PollerPrivate::resetMode(PollerHandlePrivate& eh) {
+ PollerHandle* ph;
+ {
+ // Called after an event has been processed for a handle
+ ScopedLock<Mutex> l(eh.lock);
+ assert(!eh.isActive());
+
+ if (eh.isIdle() || eh.isDeleted()) {
+ return;
+ }
+
+ if (eh.events==0) {
+ eh.setActive();
+ return;
+ }
+
+ if (!eh.isInterrupted()) {
+ // Handle still in use, allow events to resume.
+ eh.setActive();
+ registeredHandles.setStale();
+ // Ouch. This scales poorly for large handle sets.
+ // TODO: avoid new snapshot, perhaps create an index to pollfds or a
+ // pending reset queue to be processed before each poll(). However, the real
+ // scalable solution is to implement the OS-specific epoll equivalent.
+ interrupt();
+ return;
+ }
+ ph = eh.pollerHandle;
+ }
+
+ eventStream.addInterrupt(*ph);
+ interrupt();
+}
+
+void Poller::monitorHandle(PollerHandle& handle, Direction dir) {
+ PollerHandlePrivate& eh = *handle.impl;
+ ScopedLock<Mutex> l(eh.lock);
+ assert(!eh.isIdle());
+
+ short oldEvents = eh.events;
+ eh.events |= PollerPrivate::directionToEpollEvent(dir);
+
+ // If no change nothing more to do - avoid unnecessary system call
+ if (oldEvents==eh.events) {
+ return;
+ }
+
+ // If we're not actually listening wait till we are to perform change
+ if (!eh.isActive()) {
+ return;
+ }
+
+ // tell polling thread to update its pollfds
+ impl->registeredHandles.setStale();
+ impl->interrupt();
+}
+
+void Poller::unmonitorHandle(PollerHandle& handle, Direction dir) {
+ PollerHandlePrivate& eh = *handle.impl;
+ ScopedLock<Mutex> l(eh.lock);
+ assert(!eh.isIdle());
+
+ short oldEvents = eh.events;
+ eh.events &= ~PollerPrivate::directionToEpollEvent(dir);
+
+ // If no change nothing more to do - avoid unnecessary system call
+ if (oldEvents==eh.events) {
+ return;
+ }
+
+ // If we're not actually listening wait till we are to perform change
+ if (!eh.isActive()) {
+ return;
+ }
+
+ impl->registeredHandles.setStale();
+ impl->interrupt();
+}
+
+void Poller::shutdown() {
+ // NB: this function must be async-signal safe, it must not
+ // call any function that is not async-signal safe.
+
+ // Allow sloppy code to shut us down more than once
+ if (impl->isShutdown)
+ return;
+
+ // Don't use any locking here - isShutdown will be visible to all
+ // after the write() anyway (it's a memory barrier)
+ impl->isShutdown = true;
+
+ impl->interruptAll();
+}
+
+bool Poller::interrupt(PollerHandle& handle) {
+ {
+ PollerHandlePrivate& eh = *handle.impl;
+ ScopedLock<Mutex> l(eh.lock);
+ if (eh.isIdle() || eh.isDeleted()) {
+ return false;
+ }
+
+ if (eh.isInterrupted()) {
+ return true;
+ }
+
+ if (eh.isInactive()) {
+ eh.setInterrupted();
+ return true;
+ }
+ eh.setInterrupted();
+ eh.events = 0;
+ }
+
+ impl->registeredHandles.setStale();
+ impl->eventStream.addInterrupt(handle);
+ impl->interrupt();
+ return true;
+}
+
+void Poller::run() {
+ // Ensure that we exit thread responsibly under all circumstances
+ try {
+ // Make sure we can't be interrupted by signals at a bad time
+ ::sigset_t ss;
+ ::sigfillset(&ss);
+ ::pthread_sigmask(SIG_SETMASK, &ss, 0);
+
+ ++(impl->threadCount);
+ do {
+ Event event = wait();
+
+ // If can read/write then dispatch appropriate callbacks
+ if (event.handle) {
+ event.process();
+ } else {
+ // Handle shutdown
+ switch (event.type) {
+ case SHUTDOWN:
+ //last thread to respond to shutdown cleans up:
+ if (--(impl->threadCount) == 0) impl->registeredHandles.cleanup();
+ PollerHandleDeletionManager.destroyThreadState();
+ return;
+ default:
+ // This should be impossible
+ assert(false);
+ }
+ }
+ } while (true);
+ } catch (const std::exception& e) {
+ QPID_LOG(error, "IO worker thread exiting with unhandled exception: " << e.what());
+ }
+ PollerHandleDeletionManager.destroyThreadState();
+ --(impl->threadCount);
+}
+
+bool Poller::hasShutdown()
+{
+ return impl->isShutdown;
+}
+
+Poller::Event Poller::wait(Duration timeout) {
+ static __thread PollerHandlePrivate* lastReturnedHandle = 0;
+
+ if (lastReturnedHandle) {
+ impl->resetMode(*lastReturnedHandle);
+ lastReturnedHandle = 0;
+ }
+
+ Event event = impl->eventStream.next(timeout);
+
+ switch (event.type) {
+ case INTERRUPTED:
+ case READABLE:
+ case WRITABLE:
+ case READ_WRITABLE:
+ lastReturnedHandle = event.handle->impl;
+ break;
+ default:
+ ;
+ }
+
+ return event;
+}
+
+// Concrete constructors
+Poller::Poller() :
+ impl(new PollerPrivate())
+{}
+
+Poller::~Poller() {
+ delete impl;
+}
+
+
+bool HandleSet::snapshot(std::vector<PollerHandlePrivate *>& hs , std::vector<struct ::pollfd>& fds)
+{
+ // Element 0 of the vectors is always the signal pipe, leave undisturbed
+ {
+ ScopedLock<Mutex> l(lock);
+ if (!stale)
+ return false; // no refresh done
+
+ hs.resize(1);
+ for (std::set<PollerHandlePrivate*>::const_iterator i = handles.begin(); i != handles.end(); ++i) {
+ hs.push_back(*i);
+ }
+ stale = false;
+ // have copy of handle set (in vector form), drop the lock and build the pollfds
+ }
+
+ // sync pollfds to same sizing as the handles
+ int sz = hs.size();
+ fds.resize(sz);
+
+ for (int j = 1; j < sz; ++j) {
+ // create a pollfd entry for each handle
+ struct ::pollfd& pollfd = fds[j];
+ PollerHandlePrivate& eh = *hs[j];
+ ScopedLock<Mutex> lk(eh.lock);
+
+ if (!eh.isInactive() && !eh.isDeleted()) {
+ pollfd.fd = eh.fd();
+ pollfd.events = eh.events;
+ } else {
+ pollfd.fd = -1; // tell poll() to ignore this fd
+ pollfd.events = 0;
+ }
+ }
+ return true;
+}
+
+
+}}
diff --git a/qpid/cpp/src/qpid/sys/posix/PrivatePosix.h b/qpid/cpp/src/qpid/sys/posix/PrivatePosix.h
new file mode 100644
index 0000000000..34a2022694
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/PrivatePosix.h
@@ -0,0 +1,65 @@
+#ifndef _sys_posix_PrivatePosix_h
+#define _sys_posix_PrivatePosix_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/Time.h"
+
+struct timespec;
+struct timeval;
+struct addrinfo;
+
+namespace qpid {
+namespace sys {
+
+// Private Time related implementation details
+struct timespec& toTimespec(struct timespec& ts, const AbsTime& t);
+struct timeval& toTimeval(struct timeval& tv, const Duration& t);
+Duration toTime(const struct timespec& ts);
+
+// Private SocketAddress details
+class SocketAddress;
+const struct addrinfo& getAddrInfo(const SocketAddress&);
+
+// Posix fd as an IOHandle
+class IOHandle {
+public:
+ IOHandle(int fd0 = -1) :
+ fd(fd0)
+ {}
+
+ int fd;
+};
+
+// Dummy IOHandle for places it's required in the API
+// but we promise not to actually try to do any operations on the IOHandle
+class NullIOHandle : public IOHandle {
+public:
+ NullIOHandle()
+ {}
+};
+
+extern NullIOHandle DummyIOHandle;
+
+}}
+
+#endif /*!_sys_posix_PrivatePosix_h*/
diff --git a/qpid/cpp/src/qpid/sys/posix/Shlib.cpp b/qpid/cpp/src/qpid/sys/posix/Shlib.cpp
new file mode 100644
index 0000000000..3fb685d5b8
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/Shlib.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/Shlib.h"
+#include "qpid/Exception.h"
+#include "qpid/Msg.h"
+#include <dlfcn.h>
+
+
+namespace qpid {
+namespace sys {
+
+void Shlib::load(const char* name) {
+ ::dlerror();
+ handle = ::dlopen(name, RTLD_NOW);
+ const char* error = ::dlerror();
+ if (error) {
+ throw Exception(QPID_MSG(error << ": " << name));
+ }
+}
+
+void Shlib::unload() {
+ if (handle) {
+ ::dlerror();
+ ::dlclose(handle);
+ const char* error = ::dlerror();
+ if (error) {
+ throw Exception(QPID_MSG(error));
+ }
+ handle = 0;
+ }
+}
+
+void* Shlib::getSymbol(const char* name) {
+ ::dlerror();
+ void* sym = ::dlsym(handle, name);
+ const char* error = ::dlerror();
+ if (error)
+ throw Exception(QPID_MSG(error << ": " << name));
+ return sym;
+}
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/posix/SocketAddress.cpp b/qpid/cpp/src/qpid/sys/posix/SocketAddress.cpp
new file mode 100644
index 0000000000..4c860a7ef7
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/SocketAddress.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/SocketAddress.h"
+
+#include "qpid/Exception.h"
+#include "qpid/Msg.h"
+#include "qpid/log/Logger.h"
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include <iosfwd>
+
+namespace qpid {
+namespace sys {
+
+SocketAddress::SocketAddress(const std::string& host0, const std::string& port0) :
+ host(host0),
+ port(port0),
+ addrInfo(0),
+ currentAddrInfo(0)
+{
+}
+
+SocketAddress::SocketAddress(const SocketAddress& sa) :
+ host(sa.host),
+ port(sa.port),
+ addrInfo(0),
+ currentAddrInfo(0)
+{
+}
+
+SocketAddress& SocketAddress::operator=(const SocketAddress& sa)
+{
+ SocketAddress temp(sa);
+
+ std::swap(temp, *this);
+ return *this;
+}
+
+SocketAddress::~SocketAddress()
+{
+ if (addrInfo) {
+ ::freeaddrinfo(addrInfo);
+ }
+}
+
+std::string SocketAddress::asString(::sockaddr const * const addr, size_t addrlen, bool dispNameOnly, bool hideDecoration)
+{
+ char servName[NI_MAXSERV];
+ char dispName[NI_MAXHOST];
+ if (int rc=::getnameinfo(addr, addrlen,
+ dispName, sizeof(dispName),
+ servName, sizeof(servName),
+ NI_NUMERICHOST | NI_NUMERICSERV) != 0)
+ throw qpid::Exception(QPID_MSG(gai_strerror(rc)));
+ std::string s;
+ switch (addr->sa_family) {
+ case AF_INET: s += dispName; break;
+ case AF_INET6:
+ if (!hideDecoration) {
+ s += "["; s += dispName; s+= "]";
+ } else {
+ s += dispName;
+ }
+ break;
+ case AF_UNIX: s += "UNIX:"; break;
+ default: throw Exception(QPID_MSG("Unexpected socket type"));
+ }
+ if (!dispNameOnly) {
+ s += ":";
+ s += servName;
+ }
+ return s;
+}
+
+uint16_t SocketAddress::getPort(::sockaddr const * const addr)
+{
+ switch (addr->sa_family) {
+ case AF_INET: return ntohs(((const ::sockaddr_in*)(const void*)addr)->sin_port);
+ case AF_INET6: return ntohs(((const ::sockaddr_in6*)(const void*)addr)->sin6_port);
+ default:throw Exception(QPID_MSG("Unexpected socket type"));
+ }
+}
+
+std::string SocketAddress::asString(bool numeric, bool dispNameOnly, bool hideDecoration) const
+{
+ if (!numeric)
+ return host + ":" + port;
+ // Canonicalise into numeric id
+ const ::addrinfo& ai = getAddrInfo(*this);
+
+ return asString(ai.ai_addr, ai.ai_addrlen, dispNameOnly, hideDecoration);
+}
+
+std::string SocketAddress::getHost() const
+{
+ return host;
+}
+
+/**
+ * Return true if this SocketAddress is IPv4 or IPv6
+ */
+bool SocketAddress::isIp() const
+{
+ const ::addrinfo& ai = getAddrInfo(*this);
+ return ai.ai_family == AF_INET || ai.ai_family == AF_INET6;
+}
+
+/**
+ * this represents the low address of an ACL address range.
+ * Given rangeHi that represents the high address,
+ * return a string showing the numeric comparisons that the
+ * inRange checks will do for address pair.
+ */
+std::string SocketAddress::comparisonDetails(const SocketAddress& rangeHi) const
+{
+ std::ostringstream os;
+ SocketAddress thisSa(*this);
+ SocketAddress rangeHiSa(rangeHi);
+ (void) getAddrInfo(thisSa);
+ (void) getAddrInfo(rangeHiSa);
+ os << "(" << thisSa.asString(true, true, false) <<
+ "," << rangeHiSa.asString(true, true, false) << ")";
+ while (thisSa.nextAddress()) {
+ if (!rangeHiSa.nextAddress()) {
+ throw(Exception(QPID_MSG("Comparison iteration fails: " + (*this).asString() +
+ rangeHi.asString())));
+ }
+ os << ",(" << thisSa.asString(true, true, false) <<
+ "," << rangeHiSa.asString(true, true, false) << ")";
+ }
+ if (rangeHiSa.nextAddress()) {
+ throw(Exception(QPID_MSG("Comparison iteration fails: " + (*this).asString() +
+ rangeHi.asString())));
+ }
+ std::string result = os.str();
+ return result;
+}
+
+/**
+ * For ACL address matching make sure that the two addresses, *this
+ * which is the low address and hiPeer which is the high address, are
+ * both numeric ip addresses of the same family and that hi > *this.
+ *
+ * Note that if the addresses resolve to more than one struct addrinfo
+ * then this and the hiPeer must be equal. This avoids having to do
+ * difficult range checks where the this and hiPeer both resolve to
+ * multiple IPv4 or IPv6 addresses.
+ *
+ * This check is run at acl file load time and not at run tme.
+ */
+bool SocketAddress::isComparable(const SocketAddress& hiPeer) const {
+ try {
+ // May only compare if this socket is IPv4 or IPv6
+ SocketAddress lo(*this);
+ const ::addrinfo& peerLoInfo = getAddrInfo(lo);
+ if (!(peerLoInfo.ai_family == AF_INET || peerLoInfo.ai_family == AF_INET6)) {
+ return false;
+ }
+ try {
+ // May only compare if peer socket is same family
+ SocketAddress hi(hiPeer);
+ const ::addrinfo& peerHiInfo = getAddrInfo(hi);
+ if (peerLoInfo.ai_family != peerHiInfo.ai_family) {
+ return false;
+ }
+ // Host names that resolve to lists are allowed if they are equal.
+ // For example: localhost, or fjord.lab.example.com
+ if ((*this).asString() == hiPeer.asString()) {
+ return true;
+ }
+ // May only compare if this and peer resolve to single address.
+ if (lo.nextAddress() || hi.nextAddress()) {
+ return false;
+ }
+ // Make sure that the lo/hi relationship is ok
+ int res;
+ if (!compareAddresses(peerLoInfo, peerHiInfo, res) || res < 0) {
+ return false;
+ }
+ return true;
+ } catch (Exception) {
+ // failed to resolve hi
+ return false;
+ }
+ } catch (Exception) {
+ // failed to resolve lo
+ return false;
+ }
+}
+
+/**
+ * *this SocketAddress was created from the numeric IP address of a
+ * connecting host.
+ * The lo and hi addresses are the limit checks from the ACL file.
+ * Return true if this address is in range of any of the address pairs
+ * in the limit check range.
+ *
+ * This check is executed on every incoming connection.
+ */
+bool SocketAddress::inRange(const SocketAddress& lo,
+ const SocketAddress& hi) const
+{
+ (*this).firstAddress();
+ lo.firstAddress();
+ hi.firstAddress();
+ const ::addrinfo& thisInfo = getAddrInfo(*this);
+ const ::addrinfo& loInfo = getAddrInfo(lo);
+ const ::addrinfo& hiInfo = getAddrInfo(hi);
+ if (inRange(thisInfo, loInfo, hiInfo)) {
+ return true;
+ }
+ while (lo.nextAddress()) {
+ if (!hi.nextAddress()) {
+ assert (false);
+ throw(Exception(QPID_MSG("Comparison iteration fails: " +
+ lo.asString() + hi.asString())));
+ }
+ const ::addrinfo& loInfo = getAddrInfo(lo);
+ const ::addrinfo& hiInfo = getAddrInfo(hi);
+ if (inRange(thisInfo, loInfo, hiInfo)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * *this SocketAddress was created from the numeric IP address of a
+ * connecting host.
+ * The lo and hi addresses are one binary address pair from a range
+ * given in an ACL file.
+ * Return true if this binary address is '>= lo' and '<= hi'.
+ */
+bool SocketAddress::inRange(const ::addrinfo& thisInfo,
+ const ::addrinfo& lo,
+ const ::addrinfo& hi) const
+{
+ int resLo;
+ int resHi;
+ if (!compareAddresses(lo, thisInfo, resLo)) {
+ return false;
+ }
+ if (!compareAddresses(hi, thisInfo, resHi)) {
+ return false;
+ }
+ if (resLo < 0) {
+ return false;
+ }
+ if (resHi > 0) {
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Compare this address against two binary low/high addresses.
+ * return true with result holding the comparison.
+ */
+bool SocketAddress::compareAddresses(const struct addrinfo& lo,
+ const struct addrinfo& hi,
+ int& result) const
+{
+ if (lo.ai_family != hi.ai_family) {
+ return false;
+ }
+ if (lo.ai_family == AF_INET) {
+ void* taddr;
+
+ taddr = (void*)lo.ai_addr;
+ struct sockaddr_in* sin4lo = (struct sockaddr_in*)taddr;
+ taddr = (void*)hi.ai_addr;
+ struct sockaddr_in* sin4hi = (struct sockaddr_in*)taddr;
+ result = memcmp(&sin4hi->sin_addr, &sin4lo->sin_addr, sizeof(in_addr));
+ } else if (lo.ai_family == AF_INET6) {
+ void* taddr;
+
+ taddr = (void*)lo.ai_addr;
+ struct sockaddr_in6* sin6lo = (struct sockaddr_in6*)taddr;
+ taddr = (void*)hi.ai_addr;
+ struct sockaddr_in6* sin6hi = (struct sockaddr_in6*)taddr;
+ result = memcmp(&sin6hi->sin6_addr, &sin6lo->sin6_addr, sizeof(in6_addr));
+ } else {
+ assert (false);
+ return false;
+ }
+ return true;
+}
+
+void SocketAddress::firstAddress() const {
+ if (addrInfo) {
+ currentAddrInfo = addrInfo;
+ } else {
+ (void) getAddrInfo(*this);
+ }
+}
+
+bool SocketAddress::nextAddress() const {
+ bool r = currentAddrInfo->ai_next != 0;
+ if (r)
+ currentAddrInfo = currentAddrInfo->ai_next;
+ return r;
+}
+
+const ::addrinfo& getAddrInfo(const SocketAddress& sa)
+{
+ if (!sa.addrInfo) {
+ ::addrinfo hints;
+ ::memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_UNSPEC; // Allow both IPv4 and IPv6
+ hints.ai_socktype = SOCK_STREAM;
+
+ const char* node = 0;
+ if (sa.host.empty()) {
+ hints.ai_flags = AI_PASSIVE;
+ } else {
+ hints.ai_flags = AI_ADDRCONFIG; // Only use protocols that we have configured interfaces for
+ node = sa.host.c_str();
+ }
+ const char* service = sa.port.empty() ? "0" : sa.port.c_str();
+
+ int n = ::getaddrinfo(node, service, &hints, &sa.addrInfo);
+ if (n != 0)
+ throw Exception(QPID_MSG("Cannot resolve " << sa.asString(false) << ": " << ::gai_strerror(n)));
+ sa.currentAddrInfo = sa.addrInfo;
+ }
+
+ return *sa.currentAddrInfo;
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/sys/posix/StrError.cpp b/qpid/cpp/src/qpid/sys/posix/StrError.cpp
new file mode 100644
index 0000000000..633e20213c
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/StrError.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/StrError.h"
+
+#include <string.h>
+
+namespace qpid {
+namespace sys {
+
+std::string strError(int err) {
+ char buf[512] = "Unknown error";
+#ifdef _GNU_SOURCE
+ // GNU strerror_r returns the message
+ return ::strerror_r(err, buf, sizeof(buf));
+#else
+ // POSIX strerror_r doesn't return the buffer
+ ::strerror_r(err, buf, sizeof(buf));
+ return std::string(buf);
+#endif
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/sys/posix/SystemInfo.cpp b/qpid/cpp/src/qpid/sys/posix/SystemInfo.cpp
new file mode 100755
index 0000000000..2a42a5b2a7
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/SystemInfo.cpp
@@ -0,0 +1,201 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/log/Statement.h"
+#include "qpid/sys/SystemInfo.h"
+#include "qpid/sys/posix/check.h"
+#include <arpa/inet.h>
+#include <sys/ioctl.h>
+#include <sys/utsname.h>
+#include <sys/types.h> // For FreeBSD
+#include <sys/socket.h> // For FreeBSD
+#include <netinet/in.h> // For FreeBSD
+#include <ifaddrs.h>
+#include <unistd.h>
+#include <iostream>
+#include <fstream>
+#include <sstream>
+#include <map>
+#include <netdb.h>
+#include <string.h>
+
+#ifndef HOST_NAME_MAX
+# define HOST_NAME_MAX 256
+#endif
+
+using namespace std;
+
+namespace qpid {
+namespace sys {
+
+long SystemInfo::concurrency() {
+#ifdef _SC_NPROCESSORS_ONLN // Linux specific.
+ return sysconf(_SC_NPROCESSORS_ONLN);
+#else
+ return -1;
+#endif
+}
+
+bool SystemInfo::getLocalHostname (Address &address) {
+ char name[HOST_NAME_MAX];
+ if (::gethostname(name, sizeof(name)) != 0)
+ return false;
+ address.host = name;
+ return true;
+}
+
+static const string LOOPBACK("127.0.0.1");
+static const string TCP("tcp");
+
+// Test IPv4 address for loopback
+inline bool IN_IS_ADDR_LOOPBACK(const ::in_addr* a) {
+ return ((ntohl(a->s_addr) & 0xff000000) == 0x7f000000);
+}
+
+inline bool isLoopback(const ::sockaddr* addr) {
+ switch (addr->sa_family) {
+ case AF_INET: return IN_IS_ADDR_LOOPBACK(&((const ::sockaddr_in*)(const void*)addr)->sin_addr);
+ case AF_INET6: return IN6_IS_ADDR_LOOPBACK(&((const ::sockaddr_in6*)(const void*)addr)->sin6_addr);
+ default: return false;
+ }
+}
+
+namespace {
+ inline socklen_t sa_len(::sockaddr* sa)
+ {
+ switch (sa->sa_family) {
+ case AF_INET:
+ return sizeof(struct sockaddr_in);
+ case AF_INET6:
+ return sizeof(struct sockaddr_in6);
+ default:
+ return sizeof(struct sockaddr_storage);
+ }
+ }
+
+ inline bool isInetOrInet6(::sockaddr* sa) {
+ switch (sa->sa_family) {
+ case AF_INET:
+ case AF_INET6:
+ return true;
+ default:
+ return false;
+ }
+ }
+ typedef std::map<std::string, std::vector<std::string> > InterfaceInfo;
+ std::map<std::string, std::vector<std::string> > cachedInterfaces;
+
+ void cacheInterfaceInfo() {
+ // Get interface info
+ ::ifaddrs* interfaceInfo;
+ QPID_POSIX_CHECK( ::getifaddrs(&interfaceInfo) );
+
+ char name[NI_MAXHOST];
+ for (::ifaddrs* info = interfaceInfo; info != 0; info = info->ifa_next) {
+
+ // Only use IPv4/IPv6 interfaces
+ if (!info->ifa_addr || !isInetOrInet6(info->ifa_addr)) continue;
+
+ int rc=::getnameinfo(info->ifa_addr, sa_len(info->ifa_addr),
+ name, sizeof(name), 0, 0,
+ NI_NUMERICHOST);
+ if (rc >= 0) {
+ std::string address(name);
+ cachedInterfaces[info->ifa_name].push_back(address);
+ } else {
+ throw qpid::Exception(QPID_MSG(gai_strerror(rc)));
+ }
+ }
+ ::freeifaddrs(interfaceInfo);
+ }
+}
+
+bool SystemInfo::getInterfaceAddresses(const std::string& interface, std::vector<std::string>& addresses) {
+ if ( cachedInterfaces.empty() ) cacheInterfaceInfo();
+ InterfaceInfo::iterator i = cachedInterfaces.find(interface);
+ if ( i==cachedInterfaces.end() ) return false;
+ std::copy(i->second.begin(), i->second.end(), std::back_inserter(addresses));
+ return true;
+}
+
+void SystemInfo::getInterfaceNames(std::vector<std::string>& names ) {
+ if ( cachedInterfaces.empty() ) cacheInterfaceInfo();
+
+ for (InterfaceInfo::const_iterator i = cachedInterfaces.begin(); i!=cachedInterfaces.end(); ++i) {
+ names.push_back(i->first);
+ }
+}
+
+void SystemInfo::getSystemId (std::string &osName,
+ std::string &nodeName,
+ std::string &release,
+ std::string &version,
+ std::string &machine)
+{
+ struct utsname _uname;
+ if (uname (&_uname) == 0)
+ {
+ osName = _uname.sysname;
+ nodeName = _uname.nodename;
+ release = _uname.release;
+ version = _uname.version;
+ machine = _uname.machine;
+ }
+}
+
+uint32_t SystemInfo::getProcessId()
+{
+ return (uint32_t) ::getpid();
+}
+
+uint32_t SystemInfo::getParentProcessId()
+{
+ return (uint32_t) ::getppid();
+}
+
+// Linux specific (Solaris has quite different stuff in /proc)
+string SystemInfo::getProcessName()
+{
+ string value;
+
+ ifstream input("/proc/self/status");
+ if (input.good()) {
+ while (!input.eof()) {
+ string key;
+ input >> key;
+ if (key == "Name:") {
+ input >> value;
+ break;
+ }
+ }
+ input.close();
+ }
+
+ return value;
+}
+
+// Always true. Only Windows has exception cases.
+bool SystemInfo::threadSafeShutdown()
+{
+ return true;
+}
+
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/posix/Thread.cpp b/qpid/cpp/src/qpid/sys/posix/Thread.cpp
new file mode 100644
index 0000000000..349e35d643
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/Thread.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/Thread.h"
+
+#include "qpid/sys/Runnable.h"
+#include "qpid/sys/posix/check.h"
+
+#include <pthread.h>
+
+namespace qpid {
+namespace sys {
+
+namespace {
+void* runRunnable(void* p)
+{
+ static_cast<Runnable*>(p)->run();
+ return 0;
+}
+}
+
+class ThreadPrivate {
+public:
+ pthread_t thread;
+
+ ThreadPrivate(Runnable* runnable) {
+ QPID_POSIX_ASSERT_THROW_IF(::pthread_create(&thread, NULL, runRunnable, runnable));
+ }
+
+ ThreadPrivate() : thread(::pthread_self()) {}
+};
+
+Thread::Thread() {}
+
+Thread::Thread(Runnable* runnable) : impl(new ThreadPrivate(runnable)) {}
+
+Thread::Thread(Runnable& runnable) : impl(new ThreadPrivate(&runnable)) {}
+
+Thread::operator bool() {
+ return !!impl;
+}
+
+bool Thread::operator==(const Thread& t) const {
+ return pthread_equal(impl->thread, t.impl->thread) != 0;
+}
+
+bool Thread::operator!=(const Thread& t) const {
+ return !(*this==t);
+}
+
+void Thread::join(){
+ if (impl) {
+ QPID_POSIX_ASSERT_THROW_IF(::pthread_join(impl->thread, 0));
+ }
+}
+
+unsigned long Thread::logId() {
+ // This does need to be the C cast operator as
+ // pthread_t could be either a pointer or an integer
+ // and so we can't know static_cast<> or reinterpret_cast<>
+ return (unsigned long) ::pthread_self();
+}
+
+Thread Thread::current() {
+ Thread t;
+ t.impl.reset(new ThreadPrivate());
+ return t;
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/sys/posix/Time.cpp b/qpid/cpp/src/qpid/sys/posix/Time.cpp
new file mode 100644
index 0000000000..10a5d944b1
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/Time.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/posix/PrivatePosix.h"
+
+#include "qpid/sys/Time.h"
+#include <ostream>
+#include <istream>
+#include <sstream>
+#include <time.h>
+#include <stdio.h>
+#include <sys/time.h>
+#include <unistd.h>
+#include <iomanip>
+#include <cctype>
+
+namespace {
+int64_t max_abstime() { return std::numeric_limits<int64_t>::max(); }
+}
+
+namespace qpid {
+namespace sys {
+
+AbsTime::AbsTime(const AbsTime& t, const Duration& d) :
+ timepoint(d == Duration::max() ? max_abstime() : t.timepoint+d.nanosecs)
+{}
+
+AbsTime AbsTime::Zero() {
+ AbsTime epoch; epoch.timepoint = 0;
+ return epoch;
+}
+
+AbsTime AbsTime::FarFuture() {
+ AbsTime ff; ff.timepoint = max_abstime(); return ff;
+}
+
+AbsTime AbsTime::now() {
+ struct timespec ts;
+ ::clock_gettime(CLOCK_MONOTONIC, &ts);
+ AbsTime time_now;
+ time_now.timepoint = toTime(ts).nanosecs;
+ return time_now;
+}
+
+AbsTime AbsTime::epoch() {
+ return AbsTime(now(), -Duration::FromEpoch());
+}
+
+Duration Duration::FromEpoch() {
+ struct timespec ts;
+ ::clock_gettime(CLOCK_REALTIME, &ts);
+ return toTime(ts).nanosecs;
+}
+
+Duration::Duration(const AbsTime& start, const AbsTime& finish) :
+ nanosecs(finish.timepoint - start.timepoint)
+{}
+
+namespace {
+/** type conversion helper: an infinite timeout for time_t sized types **/
+const time_t TIME_T_MAX = std::numeric_limits<time_t>::max();
+}
+
+struct timespec& toTimespec(struct timespec& ts, const AbsTime& a) {
+ Duration t(ZERO, a);
+ Duration secs = t / TIME_SEC;
+ ts.tv_sec = (secs > TIME_T_MAX) ? TIME_T_MAX : static_cast<time_t>(secs);
+ ts.tv_nsec = static_cast<long>(t % TIME_SEC);
+ return ts;
+}
+
+Duration toTime(const struct timespec& ts) {
+ return ts.tv_sec*TIME_SEC + ts.tv_nsec;
+}
+
+std::ostream& operator<<(std::ostream& o, const Duration& d) {
+ if (d >= TIME_SEC) return o << (double(d)/TIME_SEC) << "s";
+ if (d >= TIME_MSEC) return o << (double(d)/TIME_MSEC) << "ms";
+ if (d >= TIME_USEC) return o << (double(d)/TIME_USEC) << "us";
+ return o << int64_t(d) << "ns";
+}
+
+std::istream& operator>>(std::istream& i, Duration& d) {
+ // Don't throw, let the istream throw if it's configured to do so.
+ double number;
+ i >> number;
+ if (i.fail()) return i;
+
+ if (i.eof() || std::isspace(i.peek())) // No suffix
+ d = int64_t(number*TIME_SEC);
+ else {
+ std::stringbuf suffix;
+ i >> &suffix;
+ if (i.fail()) return i;
+ std::string suffix_str = suffix.str();
+ if (suffix_str.compare("s") == 0) d = int64_t(number*TIME_SEC);
+ else if (suffix_str.compare("ms") == 0) d = int64_t(number*TIME_MSEC);
+ else if (suffix_str.compare("us") == 0) d = int64_t(number*TIME_USEC);
+ else if (suffix_str.compare("ns") == 0) d = int64_t(number*TIME_NSEC);
+ else i.setstate(std::ios::failbit);
+ }
+ return i;
+}
+
+namespace {
+inline std::ostream& outputFormattedTime(std::ostream& o, const ::time_t* time) {
+ ::tm timeinfo;
+ char time_string[100];
+ ::strftime(time_string, 100,
+ "%Y-%m-%d %H:%M:%S",
+ localtime_r(time, &timeinfo));
+ return o << time_string;
+}
+}
+
+std::ostream& operator<<(std::ostream& o, const AbsTime& t) {
+ ::time_t rawtime(t.timepoint/TIME_SEC);
+ return outputFormattedTime(o, &rawtime);
+}
+
+void outputFormattedNow(std::ostream& o) {
+ ::time_t rawtime;
+ ::time(&rawtime);
+ outputFormattedTime(o, &rawtime);
+ o << " ";
+}
+
+void outputHiresNow(std::ostream& o) {
+ ::timespec time;
+ ::clock_gettime(CLOCK_REALTIME, &time);
+ ::time_t seconds = time.tv_sec;
+ outputFormattedTime(o, &seconds);
+ o << "." << std::setw(9) << std::setfill('0') << time.tv_nsec << " ";
+}
+
+void sleep(int secs) {
+ ::sleep(secs);
+}
+
+void usleep(uint64_t usecs) {
+ ::usleep(usecs);
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/sys/posix/Time.h b/qpid/cpp/src/qpid/sys/posix/Time.h
new file mode 100755
index 0000000000..62d734c816
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/Time.h
@@ -0,0 +1,34 @@
+#ifndef QPID_SYS_POSIX_TIME_H
+#define QPID_SYS_POSIX_TIME_H
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/sys/IntegerTypes.h"
+
+namespace qpid {
+namespace sys {
+
+/**
+ * Class to represent an instant in time.
+ */
+typedef int64_t TimePrivate;
+
+}} // namespace qpid::sys
+
+#endif /*!QPID_SYS_POSIX_TIME_H*/
diff --git a/qpid/cpp/src/qpid/sys/posix/check.h b/qpid/cpp/src/qpid/sys/posix/check.h
new file mode 100644
index 0000000000..1bfe5d6d78
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/posix/check.h
@@ -0,0 +1,53 @@
+#ifndef _posix_check_h
+#define _posix_check_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/Exception.h"
+#include "qpid/Msg.h"
+
+#include <cerrno>
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#define QPID_POSIX_ERROR(ERRNO) qpid::Exception(QPID_MSG(qpid::sys::strError(ERRNO)))
+
+/** THROW QPID_POSIX_ERROR(errno) if RESULT is less than zero */
+#define QPID_POSIX_CHECK(RESULT) \
+ if ((RESULT) < 0) throw QPID_POSIX_ERROR((errno))
+
+/** Throw a posix error if ERRNO is non-zero */
+#define QPID_POSIX_THROW_IF(ERRNO) \
+ do { int e=(ERRNO); if (e) throw QPID_POSIX_ERROR(e); } while(0)
+
+/** Same as _THROW_IF in a release build, but abort a debug build */
+#ifdef NDEBUG
+#define QPID_POSIX_ASSERT_THROW_IF(ERRNO) QPID_POSIX_THROW_IF(ERRNO)
+#else
+#define QPID_POSIX_ASSERT_THROW_IF(ERRNO) \
+ do { int e=(ERRNO); if (e) { errno=e; ::perror(0); assert(0); } } while(0)
+#endif
+
+#define QPID_POSIX_ABORT_IF(ERRNO) if ((int) ERRNO) { errno=ERRNO; ::perror(0); abort(); }
+
+#endif /*!_posix_check_h*/
diff --git a/qpid/cpp/src/qpid/sys/rdma/RdmaClient.cpp b/qpid/cpp/src/qpid/sys/rdma/RdmaClient.cpp
new file mode 100644
index 0000000000..504000af08
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/rdma/RdmaClient.cpp
@@ -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.
+ *
+ */
+#include "qpid/sys/rdma/RdmaIO.h"
+#include "qpid/sys/rdma/rdma_exception.h"
+#include "qpid/sys/Time.h"
+#include "qpid/sys/Thread.h"
+
+#include <netdb.h>
+#include <arpa/inet.h>
+
+#include <vector>
+#include <string>
+#include <iostream>
+#include <algorithm>
+#include <cmath>
+#include <boost/bind.hpp>
+
+using std::vector;
+using std::string;
+using std::cout;
+using std::cerr;
+using std::copy;
+using std::rand;
+
+using qpid::sys::Thread;
+using qpid::sys::Poller;
+using qpid::sys::Dispatcher;
+using qpid::sys::SocketAddress;
+using qpid::sys::AbsTime;
+using qpid::sys::Duration;
+using qpid::sys::TIME_SEC;
+using qpid::sys::TIME_INFINITE;
+
+namespace qpid {
+namespace tests {
+
+// count of messages
+int64_t smsgs = 0;
+int64_t sbytes = 0;
+int64_t rmsgs = 0;
+int64_t rbytes = 0;
+
+int target = 1000000;
+int msgsize = 200;
+AbsTime startTime;
+Duration sendingDuration(TIME_INFINITE);
+Duration fullTestDuration(TIME_INFINITE);
+
+// Random generator
+// This is an RNG designed by George Marsaglia see http://en.wikipedia.org/wiki/Xorshift
+class Xor128Generator {
+ uint32_t x;
+ uint32_t y;
+ uint32_t z;
+ uint32_t w;
+
+public:
+ Xor128Generator() :
+ x(123456789),y(362436069),z(521288629),w(88675123)
+ {++(*this);}
+
+ Xor128Generator& operator++() {
+ uint32_t t = x ^ (x << 11);
+ x = y; y = z; z = w;
+ w = w ^ (w >> 19) ^ t ^ (t >> 8);
+ return *this;
+ }
+
+ uint32_t operator*() {
+ return w;
+ }
+};
+
+Xor128Generator output;
+Xor128Generator input;
+
+void write(Rdma::AsynchIO& aio) {
+ while (aio.writable() && smsgs < target) {
+ Rdma::Buffer* b = aio.getSendBuffer();
+ if (!b) break;
+ b->dataCount(msgsize);
+ uint32_t* ip = b->words();
+ uint32_t* lip = ip + b->wordCount();
+ while (ip != lip) {*ip++ = *output; ++output;}
+ aio.queueWrite(b);
+ ++smsgs;
+ sbytes += msgsize;
+ }
+}
+
+void dataError(Rdma::AsynchIO&) {
+ cout << "Data error:\n";
+}
+
+void data(Poller::shared_ptr p, Rdma::AsynchIO& aio, Rdma::Buffer* b) {
+ ++rmsgs;
+ rbytes += b->dataCount();
+
+ // Check message is unaltered
+ bool match = true;
+ uint32_t* ip = b->words();
+ uint32_t* lip = ip + b->wordCount();
+ while (ip != lip) { if (*ip++ != *input) {match = false; break;} ++input;}
+ if (!match) {
+ cout << "Data doesn't match: at msg " << rmsgs << " byte " << rbytes-b->dataCount() << " (ish)\n";
+ exit(1);
+ }
+
+ // When all messages have been recvd stop
+ if (rmsgs < target) {
+ write(aio);
+ } else {
+ fullTestDuration = std::min(fullTestDuration, Duration(startTime, AbsTime::now()));
+ if (aio.incompletedWrites() == 0)
+ p->shutdown();
+ }
+}
+
+void full(Rdma::AsynchIO& a, Rdma::Buffer* b) {
+ // Warn as we shouldn't get here anymore
+ cerr << "!";
+
+ // Don't need to keep buffer just adjust the counts
+ --smsgs;
+ sbytes -= b->dataCount();
+
+ // Give buffer back
+ a.returnSendBuffer(b);
+}
+
+void idle(Poller::shared_ptr p, Rdma::AsynchIO& aio) {
+ if (smsgs < target) {
+ write(aio);
+ } else {
+ sendingDuration = std::min(sendingDuration, Duration(startTime, AbsTime::now()));
+ if (rmsgs >= target && aio.incompletedWrites() == 0)
+ p->shutdown();
+ }
+}
+
+void drained(Rdma::AsynchIO&) {
+ cout << "Drained:\n";
+}
+
+void connected(Poller::shared_ptr poller, Rdma::Connection::intrusive_ptr& ci, const Rdma::ConnectionParams& cp) {
+ cout << "Connected\n";
+ Rdma::QueuePair::intrusive_ptr q = ci->getQueuePair();
+
+ Rdma::AsynchIO* aio = new Rdma::AsynchIO(ci->getQueuePair(),
+ cp.rdmaProtocolVersion,
+ cp.maxRecvBufferSize, cp.initialXmitCredit , Rdma::DEFAULT_WR_ENTRIES,
+ boost::bind(&data, poller, _1, _2),
+ boost::bind(&idle, poller, _1),
+ &full,
+ dataError);
+
+ startTime = AbsTime::now();
+ write(*aio);
+
+ aio->start(poller);
+}
+
+void disconnected(boost::shared_ptr<Poller> p, Rdma::Connection::intrusive_ptr&) {
+ cout << "Disconnected\n";
+ p->shutdown();
+}
+
+void connectionError(boost::shared_ptr<Poller> p, Rdma::Connection::intrusive_ptr&, const Rdma::ErrorType) {
+ cout << "Connection error\n";
+ p->shutdown();
+}
+
+void rejected(boost::shared_ptr<Poller> p, Rdma::Connection::intrusive_ptr&, const Rdma::ConnectionParams&) {
+ cout << "Connection rejected\n";
+ p->shutdown();
+}
+
+}} // namespace qpid::tests
+
+using namespace qpid::tests;
+
+int main(int argc, char* argv[]) {
+ vector<string> args(&argv[0], &argv[argc]);
+
+ string host = args[1];
+ string port = (args.size() < 3) ? "20079" : args[2];
+
+ if (args.size() > 3)
+ msgsize = atoi(args[3].c_str());
+ cout << "Message size: " << msgsize << "\n";
+
+ try {
+ boost::shared_ptr<Poller> p(new Poller());
+
+ Rdma::Connector c(
+ Rdma::ConnectionParams(msgsize, Rdma::DEFAULT_WR_ENTRIES),
+ boost::bind(&connected, p, _1, _2),
+ boost::bind(&connectionError, p, _1, _2),
+ boost::bind(&disconnected, p, _1),
+ boost::bind(&rejected, p, _1, _2));
+
+ SocketAddress sa(host, port);
+ cout << "Connecting to: " << sa.asString() <<"\n";
+ c.start(p, sa);
+
+ // The poller loop blocks all signals so run in its own thread
+ Thread t(*p);
+ t.join();
+ } catch (Rdma::Exception& e) {
+ int err = e.getError();
+ cerr << "Error: " << e.what() << "(" << err << ")\n";
+ }
+
+ cout
+ << "Sent: " << smsgs
+ << "msgs (" << sbytes
+ << "bytes) in: " << double(sendingDuration)/TIME_SEC
+ << "s: " << double(smsgs)*TIME_SEC/sendingDuration
+ << "msgs/s(" << double(sbytes)*TIME_SEC/sendingDuration
+ << "bytes/s)\n";
+ cout
+ << "Recd: " << rmsgs
+ << "msgs (" << rbytes
+ << "bytes) in: " << double(fullTestDuration)/TIME_SEC
+ << "s: " << double(rmsgs)*TIME_SEC/fullTestDuration
+ << "msgs/s(" << double(rbytes)*TIME_SEC/fullTestDuration
+ << "bytes/s)\n";
+
+}
diff --git a/qpid/cpp/src/qpid/sys/rdma/RdmaIO.cpp b/qpid/cpp/src/qpid/sys/rdma/RdmaIO.cpp
new file mode 100644
index 0000000000..2cc6573b74
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/rdma/RdmaIO.cpp
@@ -0,0 +1,724 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/sys/rdma/RdmaIO.h"
+
+#include "qpid/log/Statement.h"
+
+#include <string>
+#include <boost/bind.hpp>
+
+using qpid::sys::SocketAddress;
+using qpid::sys::DispatchHandle;
+using qpid::sys::Poller;
+using qpid::sys::ScopedLock;
+using qpid::sys::Mutex;
+
+namespace Rdma {
+ // Set packing as these are 'on the wire' structures
+# pragma pack(push, 1)
+
+ // Header structure for each transmitted frame
+ struct FrameHeader {
+ const static uint32_t FlagsMask = 0xf0000000;
+ uint32_t data; // written in network order
+
+ FrameHeader() {}
+ FrameHeader(uint32_t credit, uint32_t flags = 0) {
+ data = htonl((credit & ~FlagsMask) | (flags & FlagsMask));
+ }
+
+ uint32_t credit() const {
+ return ntohl(data) & ~FlagsMask;
+ }
+
+ uint32_t flags() const {
+ return ntohl(data) & FlagsMask;
+ }
+ };
+
+ const size_t FrameHeaderSize = sizeof(FrameHeader);
+
+ // Structure for Connection Parameters on the network
+ //
+ // The original version (now called 0) of these parameters had a couple of mistakes:
+ // * No way to version the protocol (need to introduce a new protocol for iWarp)
+ // * Used host order int32 (but only deployed on LE archs as far as we know)
+ // so effectively was LE on the wire which is the opposite of network order.
+ //
+ // Fortunately the values sent were sufficiently restricted that a 16 bit short could
+ // be carved out to indicate the protocol version as these bits were always sent as 0.
+ //
+ // So the current version of parameters uses the last 2 bytes to indicate the protocol
+ // version, if this is 0 then we interpret the rest of the struct without byte swapping
+ // to remain compatible with the previous protocol.
+ struct NConnectionParams {
+ uint32_t maxRecvBufferSize;
+ uint16_t initialXmitCredit;
+ uint16_t rdmaProtocolVersion;
+
+ NConnectionParams(const ConnectionParams& c) :
+ maxRecvBufferSize(c.rdmaProtocolVersion ? htonl(c.maxRecvBufferSize) : c.maxRecvBufferSize),
+ initialXmitCredit(c.rdmaProtocolVersion ? htons(c.initialXmitCredit) : c.initialXmitCredit),
+ // 0 is the same with/without byteswapping!
+ rdmaProtocolVersion(htons(c.rdmaProtocolVersion))
+ {}
+
+ operator ConnectionParams() const {
+ return
+ ConnectionParams(
+ rdmaProtocolVersion ? ntohl(maxRecvBufferSize) : maxRecvBufferSize,
+ rdmaProtocolVersion ? ntohs(initialXmitCredit) : initialXmitCredit,
+ ntohs(rdmaProtocolVersion));
+ }
+ };
+# pragma pack(pop)
+
+ class IOException : public std::exception {
+ std::string s;
+
+ public:
+ IOException(std::string s0): s(s0) {}
+ ~IOException() throw() {}
+
+ const char* what() const throw() {
+ return s.c_str();
+ }
+ };
+
+ AsynchIO::AsynchIO(
+ QueuePair::intrusive_ptr q,
+ int version,
+ int size,
+ int xCredit,
+ int rCount,
+ ReadCallback rc,
+ IdleCallback ic,
+ FullCallback fc,
+ ErrorCallback ec
+ ) :
+ protocolVersion(version),
+ bufferSize(size),
+ recvCredit(0),
+ xmitCredit(xCredit),
+ recvBufferCount(rCount),
+ xmitBufferCount(xCredit),
+ outstandingWrites(0),
+ draining(false),
+ state(IDLE),
+ qp(q),
+ dataHandle(*qp, boost::bind(&AsynchIO::dataEvent, this), 0, 0),
+ readCallback(rc),
+ idleCallback(ic),
+ fullCallback(fc),
+ errorCallback(ec),
+ pendingWriteAction(boost::bind(&AsynchIO::writeEvent, this))
+ {
+ if (protocolVersion > maxSupportedProtocolVersion)
+ throw IOException("Unsupported Rdma Protocol");
+ qp->nonblocking();
+ qp->notifyRecv();
+ qp->notifySend();
+
+ // Prepost recv buffers before we go any further
+ qp->allocateRecvBuffers(recvBufferCount, bufferSize+FrameHeaderSize);
+
+ // Create xmit buffers, reserve space for frame header.
+ qp->createSendBuffers(xmitBufferCount, bufferSize, FrameHeaderSize);
+ }
+
+ AsynchIO::~AsynchIO() {
+ // Warn if we are deleting whilst there are still unreclaimed write buffers
+ if ( outstandingWrites>0 )
+ QPID_LOG(error, "RDMA: qp=" << qp << ": Deleting queue before all write buffers finished");
+
+ // Turn off callbacks if necessary (before doing the deletes)
+ if (state != STOPPED) {
+ QPID_LOG(error, "RDMA: qp=" << qp << ": Deleting queue whilst not shutdown");
+ dataHandle.stopWatch();
+ }
+ // TODO: It might turn out to be more efficient in high connection loads to reuse the
+ // buffers rather than having to reregister them all the time (this would be straightforward if all
+ // connections haver the same buffer size and harder otherwise)
+ }
+
+ void AsynchIO::start(Poller::shared_ptr poller) {
+ dataHandle.startWatch(poller);
+ }
+
+ // State constraints
+ // On entry: None
+ // On exit: STOPPED
+ // Mark for deletion/Delete this object when we have no outstanding writes
+ void AsynchIO::stop(NotifyCallback nc) {
+ ScopedLock<Mutex> l(stateLock);
+ state = STOPPED;
+ notifyCallback = nc;
+ dataHandle.call(boost::bind(&AsynchIO::doStoppedCallback, this));
+ }
+
+ namespace {
+ void requestedCall(AsynchIO* aio, AsynchIO::RequestCallback callback) {
+ assert(callback);
+ callback(*aio);
+ }
+ }
+
+ void AsynchIO::requestCallback(RequestCallback callback) {
+ // TODO creating a function object every time isn't all that
+ // efficient - if this becomes heavily used do something better (what?)
+ assert(callback);
+ dataHandle.call(boost::bind(&requestedCall, this, callback));
+ }
+
+ // Mark writing closed (so we don't accept any more writes or make any idle callbacks)
+ void AsynchIO::drainWriteQueue(NotifyCallback nc) {
+ draining = true;
+ notifyCallback = nc;
+ }
+
+ void AsynchIO::queueBuffer(Buffer* buff, int credit) {
+ switch (protocolVersion) {
+ case 0:
+ if (!buff) {
+ Buffer* ob = getSendBuffer();
+ // Have to send something as adapters hate it when you try to transfer 0 bytes
+ char* bytes = ob->bytes();
+ bytes[0] = 0xFF & (credit >> 24);
+ bytes[1] = 0xFF & (credit >> 16);
+ bytes[2] = 0xFF & (credit >> 8);
+ bytes[3] = 0xFF & (credit );
+ ob->dataCount(sizeof(uint32_t));
+ qp->postSend(credit | IgnoreData, ob);
+ } else if (credit > 0) {
+ qp->postSend(credit, buff);
+ } else {
+ qp->postSend(buff);
+ }
+ break;
+ case 1:
+ if (!buff)
+ buff = getSendBuffer();
+ // Add FrameHeader after frame data
+ FrameHeader header(credit);
+ assert(buff->dataCount() <= buff->byteCount()); // ensure app data doesn't impinge on reserved space.
+ ::memcpy(buff->bytes()+buff->dataCount(), &header, FrameHeaderSize);
+ buff->dataCount(buff->dataCount()+FrameHeaderSize);
+ qp->postSend(buff);
+ break;
+ }
+ }
+
+ Buffer* AsynchIO::extractBuffer(const QueuePairEvent& e) {
+ Buffer* b = e.getBuffer();
+ switch (protocolVersion) {
+ case 0: {
+ bool dataPresent = true;
+ // Get our xmitCredit if it was sent
+ if (e.immPresent() ) {
+ assert(xmitCredit>=0);
+ xmitCredit += (e.getImm() & ~FlagsMask);
+ dataPresent = ((e.getImm() & IgnoreData) == 0);
+ assert(xmitCredit>0);
+ }
+ if (!dataPresent) {
+ b->dataCount(0);
+ }
+ break;
+ }
+ case 1:
+ b->dataCount(b->dataCount()-FrameHeaderSize);
+ FrameHeader header;
+ ::memcpy(&header, b->bytes()+b->dataCount(), FrameHeaderSize);
+ assert(xmitCredit>=0);
+ xmitCredit += header.credit();
+ assert(xmitCredit>=0);
+ break;
+ }
+
+ return b;
+ }
+
+ void AsynchIO::queueWrite(Buffer* buff) {
+ // Make sure we don't overrun our available buffers
+ // either at our end or the known available at the peers end
+ if (writable()) {
+ // TODO: We might want to batch up sending credit
+ int creditSent = recvCredit & ~FlagsMask;
+ queueBuffer(buff, creditSent);
+ recvCredit -= creditSent;
+ ++outstandingWrites;
+ --xmitCredit;
+ assert(xmitCredit>=0);
+ } else {
+ if (fullCallback) {
+ fullCallback(*this, buff);
+ } else {
+ QPID_LOG(error, "RDMA: qp=" << qp << ": Write queue full, but no callback, throwing buffer away");
+ returnSendBuffer(buff);
+ }
+ }
+ }
+
+ // State constraints
+ // On entry: None
+ // On exit: NOTIFY_PENDING || STOPPED
+ void AsynchIO::notifyPendingWrite() {
+ ScopedLock<Mutex> l(stateLock);
+ switch (state) {
+ case IDLE:
+ dataHandle.call(pendingWriteAction);
+ // Fall Thru
+ case NOTIFY:
+ state = NOTIFY_PENDING;
+ break;
+ case NOTIFY_PENDING:
+ case STOPPED:
+ break;
+ }
+ }
+
+ // State constraints
+ // On entry: IDLE || STOPPED
+ // On exit: IDLE || STOPPED
+ void AsynchIO::dataEvent() {
+ {
+ ScopedLock<Mutex> l(stateLock);
+
+ if (state == STOPPED) return;
+
+ state = NOTIFY_PENDING;
+ }
+ processCompletions();
+
+ writeEvent();
+ }
+
+ // State constraints
+ // On entry: NOTIFY_PENDING || STOPPED
+ // On exit: IDLE || STOPPED
+ void AsynchIO::writeEvent() {
+ State newState;
+ do {
+ {
+ ScopedLock<Mutex> l(stateLock);
+
+ switch (state) {
+ case STOPPED:
+ return;
+ default:
+ state = NOTIFY;
+ }
+ }
+
+ doWriteCallback();
+
+ {
+ ScopedLock<Mutex> l(stateLock);
+
+ newState = state;
+ switch (newState) {
+ case NOTIFY_PENDING:
+ case STOPPED:
+ break;
+ default:
+ state = IDLE;
+ }
+ }
+ } while (newState == NOTIFY_PENDING);
+ }
+
+ void AsynchIO::processCompletions() {
+ QueuePair::intrusive_ptr q = qp->getNextChannelEvent();
+
+ // Re-enable notification for queue:
+ // This needs to happen before we could do anything that could generate more work completion
+ // events (ie the callbacks etc. in the following).
+ // This can't make us reenter this code as the handle attached to the completion queue will still be
+ // disabled by the poller until we leave this code
+ qp->notifyRecv();
+ qp->notifySend();
+
+ int recvEvents = 0;
+ int sendEvents = 0;
+
+ // If no event do nothing
+ if (!q)
+ return;
+
+ assert(q == qp);
+
+ // Repeat until no more events
+ do {
+ QueuePairEvent e(qp->getNextEvent());
+ if (!e)
+ break;
+
+ ::ibv_wc_status status = e.getEventStatus();
+ if (status != IBV_WC_SUCCESS) {
+ // Need special check for IBV_WC_WR_FLUSH_ERR here
+ // we will get this for every send/recv queue entry that was pending
+ // when disconnected, these aren't real errors and mostly need to be ignored
+ if (status == IBV_WC_WR_FLUSH_ERR) {
+ QueueDirection dir = e.getDirection();
+ if (dir == SEND) {
+ Buffer* b = e.getBuffer();
+ ++sendEvents;
+ returnSendBuffer(b);
+ --outstandingWrites;
+ } else {
+ ++recvEvents;
+ }
+ continue;
+ }
+ errorCallback(*this);
+ // TODO: Probably need to flush queues at this point
+ return;
+ }
+
+ // Test if recv (or recv with imm)
+ //::ibv_wc_opcode eventType = e.getEventType();
+ QueueDirection dir = e.getDirection();
+ if (dir == RECV) {
+ ++recvEvents;
+
+ Buffer* b = extractBuffer(e);
+
+ // if there was no data sent then the message was only to update our credit
+ if ( b->dataCount() > 0 ) {
+ readCallback(*this, b);
+ }
+
+ // At this point the buffer has been consumed so put it back on the recv queue
+ // TODO: Is this safe to do if the connection is disconnected already?
+ qp->postRecv(b);
+
+ // Received another message
+ ++recvCredit;
+
+ // Send recvCredit if it is large enough (it will have got this large because we've not sent anything recently)
+ if (recvCredit > recvBufferCount/2) {
+ // TODO: This should use RDMA write with imm as there might not ever be a buffer to receive this message
+ // but this is a little unlikely, as to get in this state we have to have received messages without sending any
+ // for a while so its likely we've received an credit update from the far side.
+ if (writable()) {
+ int creditSent = recvCredit & ~FlagsMask;
+ queueBuffer(0, creditSent);
+ recvCredit -= creditSent;
+ ++outstandingWrites;
+ --xmitCredit;
+ assert(xmitCredit>=0);
+ } else {
+ QPID_LOG(warning, "RDMA: qp=" << qp << ": Unable to send unsolicited credit");
+ }
+ }
+ } else {
+ Buffer* b = e.getBuffer();
+ ++sendEvents;
+ returnSendBuffer(b);
+ --outstandingWrites;
+ }
+ } while (true);
+
+ // Not sure if this is expected or not
+ if (recvEvents == 0 && sendEvents == 0) {
+ QPID_LOG(debug, "RDMA: qp=" << qp << ": Got channel event with no recv/send completions");
+ }
+ }
+
+ void AsynchIO::doWriteCallback() {
+ // TODO: maybe don't call idle unless we're low on write buffers
+ // Keep on calling the idle routine as long as we are writable and we got something to write last call
+
+ // Do callback even if there are no available free buffers as the application itself might be
+ // holding onto buffers
+ while (writable()) {
+ int xc = xmitCredit;
+ idleCallback(*this);
+ // Check whether we actually wrote anything
+ if (xmitCredit == xc) {
+ QPID_LOG(debug, "RDMA: qp=" << qp << ": Called for data, but got none: xmitCredit=" << xmitCredit);
+ return;
+ }
+ }
+
+ checkDrained();
+ }
+
+ void AsynchIO::checkDrained() {
+ // If we've got all the write confirmations and we're draining
+ // We might get deleted in the drained callback so return immediately
+ if (draining) {
+ if (outstandingWrites == 0) {
+ draining = false;
+ NotifyCallback nc;
+ nc.swap(notifyCallback);
+ nc(*this);
+ }
+ return;
+ }
+ }
+
+ void AsynchIO::doStoppedCallback() {
+ // Ensure we can't get any more callbacks (except for the stopped callback)
+ dataHandle.stopWatch();
+
+ NotifyCallback nc;
+ nc.swap(notifyCallback);
+ nc(*this);
+ }
+
+ ConnectionManager::ConnectionManager(
+ ErrorCallback errc,
+ DisconnectedCallback dc
+ ) :
+ state(IDLE),
+ ci(Connection::make()),
+ handle(*ci, boost::bind(&ConnectionManager::event, this, _1), 0, 0),
+ errorCallback(errc),
+ disconnectedCallback(dc)
+ {
+ QPID_LOG(debug, "RDMA: ci=" << ci << ": Creating ConnectionManager");
+ ci->nonblocking();
+ }
+
+ ConnectionManager::~ConnectionManager()
+ {
+ QPID_LOG(debug, "RDMA: ci=" << ci << ": Deleting ConnectionManager");
+ }
+
+ void ConnectionManager::start(Poller::shared_ptr poller, const qpid::sys::SocketAddress& addr) {
+ startConnection(ci, addr);
+ handle.startWatch(poller);
+ }
+
+ void ConnectionManager::doStoppedCallback() {
+ // Ensure we can't get any more callbacks (except for the stopped callback)
+ handle.stopWatch();
+
+ NotifyCallback nc;
+ nc.swap(notifyCallback);
+ nc(*this);
+ }
+
+ void ConnectionManager::stop(NotifyCallback nc) {
+ state = STOPPED;
+ notifyCallback = nc;
+ handle.call(boost::bind(&ConnectionManager::doStoppedCallback, this));
+ }
+
+ void ConnectionManager::event(DispatchHandle&) {
+ if (state.get() == STOPPED) return;
+ connectionEvent(ci);
+ }
+
+ Listener::Listener(
+ const ConnectionParams& cp,
+ EstablishedCallback ec,
+ ErrorCallback errc,
+ DisconnectedCallback dc,
+ ConnectionRequestCallback crc
+ ) :
+ ConnectionManager(errc, dc),
+ checkConnectionParams(cp),
+ connectionRequestCallback(crc),
+ establishedCallback(ec)
+ {
+ }
+
+ void Listener::startConnection(Connection::intrusive_ptr ci, const qpid::sys::SocketAddress& addr) {
+ ci->bind(addr);
+ ci->listen();
+ }
+
+ namespace {
+ const int64_t PoisonContext = -1;
+ }
+
+ void Listener::connectionEvent(Connection::intrusive_ptr ci) {
+ ConnectionEvent e(ci->getNextEvent());
+
+ // If (for whatever reason) there was no event do nothing
+ if (!e)
+ return;
+
+ // Important documentation ommision the new rdma_cm_id
+ // you get from CONNECT_REQUEST has the same context info
+ // as its parent listening rdma_cm_id
+ ::rdma_cm_event_type eventType = e.getEventType();
+ ::rdma_conn_param conn_param = e.getConnectionParam();
+ Rdma::Connection::intrusive_ptr id = e.getConnection();
+
+ // Check for previous disconnection (it appears that you actually can get connection
+ // request events after a disconnect event in rare circumstances)
+ if (reinterpret_cast<int64_t>(id->getContext<void*>())==PoisonContext)
+ return;
+
+ switch (eventType) {
+ case RDMA_CM_EVENT_CONNECT_REQUEST: {
+ // Make sure peer has sent params we can use
+ if (!conn_param.private_data || conn_param.private_data_len < sizeof(NConnectionParams)) {
+ QPID_LOG(warning, "Rdma: rejecting connection attempt: unusable connection parameters");
+ id->reject();
+ break;
+ }
+
+ const NConnectionParams* rcp = static_cast<const NConnectionParams*>(conn_param.private_data);
+ ConnectionParams cp = *rcp;
+
+ // Reject if requested msg size is bigger than we allow
+ if (
+ cp.maxRecvBufferSize > checkConnectionParams.maxRecvBufferSize ||
+ cp.initialXmitCredit > checkConnectionParams.initialXmitCredit
+ ) {
+ QPID_LOG(warning, "Rdma: rejecting connection attempt: connection parameters out of range: ("
+ << cp.maxRecvBufferSize << ">" << checkConnectionParams.maxRecvBufferSize << " || "
+ << cp.initialXmitCredit << ">" << checkConnectionParams.initialXmitCredit
+ << ")");
+ id->reject(&checkConnectionParams);
+ break;
+ }
+
+ bool accept = true;
+ if (connectionRequestCallback)
+ accept = connectionRequestCallback(id, cp);
+
+ if (accept) {
+ // Accept connection
+ cp.initialXmitCredit = checkConnectionParams.initialXmitCredit;
+ id->accept(conn_param, rcp);
+ } else {
+ // Reject connection
+ QPID_LOG(warning, "Rdma: rejecting connection attempt: application policy");
+ id->reject();
+ }
+ break;
+ }
+ case RDMA_CM_EVENT_ESTABLISHED:
+ establishedCallback(id);
+ break;
+ case RDMA_CM_EVENT_DISCONNECTED:
+ disconnectedCallback(id);
+ // Poison the id context so that we do no more callbacks on it
+ id->removeContext();
+ id->addContext(reinterpret_cast<void*>(PoisonContext));
+ break;
+ case RDMA_CM_EVENT_CONNECT_ERROR:
+ errorCallback(id, CONNECT_ERROR);
+ break;
+ default:
+ // Unexpected response
+ errorCallback(id, UNKNOWN);
+ //std::cerr << "Warning: unexpected response to listen - " << eventType << "\n";
+ }
+ }
+
+ Connector::Connector(
+ const ConnectionParams& cp,
+ ConnectedCallback cc,
+ ErrorCallback errc,
+ DisconnectedCallback dc,
+ RejectedCallback rc
+ ) :
+ ConnectionManager(errc, dc),
+ connectionParams(cp),
+ rejectedCallback(rc),
+ connectedCallback(cc)
+ {
+ }
+
+ void Connector::startConnection(Connection::intrusive_ptr ci, const qpid::sys::SocketAddress& addr) {
+ ci->resolve_addr(addr);
+ }
+
+ void Connector::connectionEvent(Connection::intrusive_ptr ci) {
+ ConnectionEvent e(ci->getNextEvent());
+
+ // If (for whatever reason) there was no event do nothing
+ if (!e)
+ return;
+
+ ::rdma_cm_event_type eventType = e.getEventType();
+ ::rdma_conn_param conn_param = e.getConnectionParam();
+ Rdma::Connection::intrusive_ptr id = e.getConnection();
+ switch (eventType) {
+ case RDMA_CM_EVENT_ADDR_RESOLVED:
+ // RESOLVE_ADDR
+ ci->resolve_route();
+ break;
+ case RDMA_CM_EVENT_ADDR_ERROR:
+ // RESOLVE_ADDR
+ errorCallback(ci, ADDR_ERROR);
+ break;
+ case RDMA_CM_EVENT_ROUTE_RESOLVED: {
+ // RESOLVE_ROUTE:
+ NConnectionParams rcp(connectionParams);
+ ci->connect(&rcp);
+ break;
+ }
+ case RDMA_CM_EVENT_ROUTE_ERROR:
+ // RESOLVE_ROUTE:
+ errorCallback(ci, ROUTE_ERROR);
+ break;
+ case RDMA_CM_EVENT_CONNECT_ERROR:
+ // CONNECTING
+ errorCallback(ci, CONNECT_ERROR);
+ break;
+ case RDMA_CM_EVENT_UNREACHABLE:
+ // CONNECTING
+ errorCallback(ci, UNREACHABLE);
+ break;
+ case RDMA_CM_EVENT_REJECTED: {
+ // CONNECTING
+
+ // We can get this event if our peer is not running on the other side
+ // in this case we could get nearly anything in the private data:
+ // From private_data == 0 && private_data_len == 0 (Chelsio iWarp)
+ // to 148 bytes of zeros (Mellanox IB)
+ //
+ // So assume that if the the private data is absent or not the size of
+ // the connection parameters it isn't valid
+ ConnectionParams cp(0, 0, 0);
+ if (conn_param.private_data && conn_param.private_data_len == sizeof(NConnectionParams)) {
+ // Extract private data from event
+ const NConnectionParams* rcp = static_cast<const NConnectionParams*>(conn_param.private_data);
+ cp = *rcp;
+ }
+ rejectedCallback(ci, cp);
+ break;
+ }
+ case RDMA_CM_EVENT_ESTABLISHED: {
+ // CONNECTING
+ // Extract private data from event
+ assert(conn_param.private_data && conn_param.private_data_len >= sizeof(NConnectionParams));
+ const NConnectionParams* rcp = static_cast<const NConnectionParams*>(conn_param.private_data);
+ ConnectionParams cp = *rcp;
+ connectedCallback(ci, cp);
+ break;
+ }
+ case RDMA_CM_EVENT_DISCONNECTED:
+ // ESTABLISHED
+ disconnectedCallback(ci);
+ break;
+ default:
+ QPID_LOG(warning, "RDMA: Unexpected event in connect: " << eventType);
+ }
+ }
+}
diff --git a/qpid/cpp/src/qpid/sys/rdma/RdmaIO.h b/qpid/cpp/src/qpid/sys/rdma/RdmaIO.h
new file mode 100644
index 0000000000..ec9caaf08d
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/rdma/RdmaIO.h
@@ -0,0 +1,250 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#ifndef Rdma_Acceptor_h
+#define Rdma_Acceptor_h
+
+#include "qpid/sys/rdma/rdma_wrap.h"
+
+#include "qpid/sys/AtomicValue.h"
+#include "qpid/sys/Dispatcher.h"
+#include "qpid/sys/DispatchHandle.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/SocketAddress.h"
+
+#include <netinet/in.h>
+
+#include <boost/function.hpp>
+
+namespace Rdma {
+
+ class Connection;
+
+ class AsynchIO
+ {
+ typedef boost::function1<void, AsynchIO&> ErrorCallback;
+ typedef boost::function2<void, AsynchIO&, Buffer*> ReadCallback;
+ typedef boost::function1<void, AsynchIO&> IdleCallback;
+ typedef boost::function2<void, AsynchIO&, Buffer*> FullCallback;
+ typedef boost::function1<void, AsynchIO&> NotifyCallback;
+
+ int protocolVersion;
+ int bufferSize;
+ int recvCredit;
+ int xmitCredit;
+ int recvBufferCount;
+ int xmitBufferCount;
+ int outstandingWrites;
+ bool draining;
+ enum State {IDLE, NOTIFY, NOTIFY_PENDING, STOPPED};
+ State state;
+ qpid::sys::Mutex stateLock;
+ QueuePair::intrusive_ptr qp;
+ qpid::sys::DispatchHandleRef dataHandle;
+
+ ReadCallback readCallback;
+ IdleCallback idleCallback;
+ FullCallback fullCallback;
+ ErrorCallback errorCallback;
+ NotifyCallback notifyCallback;
+ qpid::sys::DispatchHandle::Callback pendingWriteAction;
+
+ public:
+ typedef boost::function1<void, AsynchIO&> RequestCallback;
+
+ // TODO: Instead of specifying a buffer size specify the amount of memory the AsynchIO class can use
+ // for buffers both read and write (allocate half to each up front) and fail if we cannot allocate that much
+ // locked memory
+ AsynchIO(
+ QueuePair::intrusive_ptr q,
+ int version,
+ int size,
+ int xCredit,
+ int rCount,
+ ReadCallback rc,
+ IdleCallback ic,
+ FullCallback fc,
+ ErrorCallback ec
+ );
+ ~AsynchIO();
+
+ void start(qpid::sys::Poller::shared_ptr poller);
+ bool writable() const;
+ void queueWrite(Buffer* buff);
+ void notifyPendingWrite();
+ void drainWriteQueue(NotifyCallback);
+ void stop(NotifyCallback);
+ void requestCallback(RequestCallback);
+ int incompletedWrites() const;
+ Buffer* getSendBuffer();
+ void returnSendBuffer(Buffer*);
+
+ private:
+ const static int maxSupportedProtocolVersion = 1;
+
+ // Constants for the peer-peer command messages
+ // These are sent in the high bits if the imm data of an rdma message
+ // The low bits are used to send the credit
+ const static int FlagsMask = 0xF0000000; // Mask for all flag bits - be sure to update this if you add more command bits
+ const static int IgnoreData = 0x10000000; // Message contains no application data
+
+ void dataEvent();
+ void writeEvent();
+ void processCompletions();
+ void doWriteCallback();
+ void checkDrained();
+ void doStoppedCallback();
+
+ void queueBuffer(Buffer* buff, int credit);
+ Buffer* extractBuffer(const QueuePairEvent& e);
+ };
+
+ // We're only writable if:
+ // * not draining write queue
+ // * we've got space in the transmit queue
+ // * we've got credit to transmit
+ // * if there's only 1 transmit credit we must send some credit
+ inline bool AsynchIO::writable() const {
+ assert(xmitCredit>=0);
+ return !draining &&
+ outstandingWrites < xmitBufferCount &&
+ xmitCredit > 0 &&
+ ( xmitCredit > 1 || recvCredit > 0);
+ }
+
+ inline int AsynchIO::incompletedWrites() const {
+ return outstandingWrites;
+ }
+
+ inline Buffer* AsynchIO::getSendBuffer() {
+ return qp->getSendBuffer();
+ }
+
+ inline void AsynchIO::returnSendBuffer(Buffer* b) {
+ qp->returnSendBuffer(b);
+ }
+
+ // These are the parameters necessary to start the conversation
+ // * Each peer HAS to allocate buffers of the size of the maximum receive from its peer
+ // * Each peer HAS to know the initial "credit" it has for transmitting to its peer
+ struct ConnectionParams {
+ uint32_t maxRecvBufferSize;
+ uint16_t initialXmitCredit;
+ uint16_t rdmaProtocolVersion;
+
+ // Default to protocol version 1
+ ConnectionParams(uint32_t s, uint16_t c, uint16_t v = 1) :
+ maxRecvBufferSize(s),
+ initialXmitCredit(c),
+ rdmaProtocolVersion(v)
+ {}
+ };
+
+ enum ErrorType {
+ ADDR_ERROR,
+ ROUTE_ERROR,
+ CONNECT_ERROR,
+ UNREACHABLE,
+ UNKNOWN
+ };
+
+ typedef boost::function2<void, Rdma::Connection::intrusive_ptr, ErrorType> ErrorCallback;
+ typedef boost::function1<void, Rdma::Connection::intrusive_ptr> DisconnectedCallback;
+
+ class ConnectionManager {
+ typedef boost::function1<void, ConnectionManager&> NotifyCallback;
+
+ enum State {IDLE, STOPPED};
+ qpid::sys::AtomicValue<State> state;
+ Connection::intrusive_ptr ci;
+ qpid::sys::DispatchHandleRef handle;
+ NotifyCallback notifyCallback;
+
+ protected:
+ ErrorCallback errorCallback;
+ DisconnectedCallback disconnectedCallback;
+
+ public:
+ ConnectionManager(
+ ErrorCallback errc,
+ DisconnectedCallback dc
+ );
+
+ virtual ~ConnectionManager();
+
+ void start(qpid::sys::Poller::shared_ptr poller, const qpid::sys::SocketAddress& addr);
+ void stop(NotifyCallback);
+
+ private:
+ void event(qpid::sys::DispatchHandle& handle);
+ void doStoppedCallback();
+
+ virtual void startConnection(Connection::intrusive_ptr ci, const qpid::sys::SocketAddress& addr) = 0;
+ virtual void connectionEvent(Connection::intrusive_ptr ci) = 0;
+ };
+
+ typedef boost::function2<bool, Rdma::Connection::intrusive_ptr, const ConnectionParams&> ConnectionRequestCallback;
+ typedef boost::function1<void, Rdma::Connection::intrusive_ptr> EstablishedCallback;
+
+ class Listener : public ConnectionManager
+ {
+ ConnectionParams checkConnectionParams;
+ ConnectionRequestCallback connectionRequestCallback;
+ EstablishedCallback establishedCallback;
+
+ public:
+ Listener(
+ const ConnectionParams& cp,
+ EstablishedCallback ec,
+ ErrorCallback errc,
+ DisconnectedCallback dc,
+ ConnectionRequestCallback crc = 0
+ );
+
+ private:
+ void startConnection(Connection::intrusive_ptr ci, const qpid::sys::SocketAddress& addr);
+ void connectionEvent(Connection::intrusive_ptr ci);
+ };
+
+ typedef boost::function2<void, Rdma::Connection::intrusive_ptr, const ConnectionParams&> RejectedCallback;
+ typedef boost::function2<void, Rdma::Connection::intrusive_ptr, const ConnectionParams&> ConnectedCallback;
+
+ class Connector : public ConnectionManager
+ {
+ ConnectionParams connectionParams;
+ RejectedCallback rejectedCallback;
+ ConnectedCallback connectedCallback;
+
+ public:
+ Connector(
+ const ConnectionParams& cp,
+ ConnectedCallback cc,
+ ErrorCallback errc,
+ DisconnectedCallback dc,
+ RejectedCallback rc = 0
+ );
+
+ private:
+ void startConnection(Connection::intrusive_ptr ci, const qpid::sys::SocketAddress& addr);
+ void connectionEvent(Connection::intrusive_ptr ci);
+ };
+}
+
+#endif // Rdma_Acceptor_h
diff --git a/qpid/cpp/src/qpid/sys/rdma/RdmaServer.cpp b/qpid/cpp/src/qpid/sys/rdma/RdmaServer.cpp
new file mode 100644
index 0000000000..9b0710fd8f
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/rdma/RdmaServer.cpp
@@ -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.
+ *
+ */
+#include "qpid/sys/Thread.h"
+#include "qpid/sys/rdma/RdmaIO.h"
+#include "qpid/sys/rdma/rdma_exception.h"
+
+#include <arpa/inet.h>
+
+#include <vector>
+#include <queue>
+#include <string>
+#include <iostream>
+
+#include <boost/bind.hpp>
+
+using std::vector;
+using std::queue;
+using std::string;
+using std::cout;
+using std::cerr;
+
+using qpid::sys::Thread;
+using qpid::sys::SocketAddress;
+using qpid::sys::Poller;
+
+// All the accepted connections
+namespace qpid {
+namespace tests {
+
+struct Buffer {
+ char* bytes() const {return bytes_;}
+ int32_t byteCount() const {return size;}
+
+ Buffer(const int32_t s):
+ bytes_(new char[s]),
+ size(s)
+ {
+ }
+
+ ~Buffer() {
+ delete [] bytes_;
+ }
+private:
+ char* bytes_;
+ int32_t size;
+};
+
+struct ConRec {
+ Rdma::Connection::intrusive_ptr connection;
+ Rdma::AsynchIO* data;
+ queue<Buffer*> queuedWrites;
+
+ ConRec(Rdma::Connection::intrusive_ptr c) :
+ connection(c)
+ {}
+};
+
+void dataError(Rdma::AsynchIO&) {
+ cout << "Data error:\n";
+}
+
+void idle(ConRec* cr, Rdma::AsynchIO& a) {
+ // Need to make sure full is not called as it would reorder messages
+ while (!cr->queuedWrites.empty() && a.writable()) {
+ Rdma::Buffer* rbuf = a.getSendBuffer();
+ if (!rbuf) break;
+ Buffer* buf = cr->queuedWrites.front();
+ cr->queuedWrites.pop();
+ std::copy(buf->bytes(), buf->bytes()+buf->byteCount(), rbuf->bytes());
+ rbuf->dataCount(buf->byteCount());
+ delete buf;
+ a.queueWrite(rbuf);
+ }
+}
+
+void data(ConRec* cr, Rdma::AsynchIO& a, Rdma::Buffer* b) {
+ // Echo data back
+ Rdma::Buffer* buf = 0;
+ if (cr->queuedWrites.empty() && a.writable()) {
+ buf = a.getSendBuffer();
+ }
+ if (buf) {
+ std::copy(b->bytes(), b->bytes()+b->dataCount(), buf->bytes());
+ buf->dataCount(b->dataCount());
+ a.queueWrite(buf);
+ } else {
+ Buffer* buf = new Buffer(b->dataCount());
+ std::copy(b->bytes(), b->bytes()+b->dataCount(), buf->bytes());
+ cr->queuedWrites.push(buf);
+ // Try to empty queue
+ idle(cr, a);
+ }
+}
+
+void full(ConRec*, Rdma::AsynchIO&, Rdma::Buffer*) {
+ // Shouldn't ever be called
+ cout << "!";
+}
+
+void drained(Rdma::AsynchIO&) {
+ cout << "Drained:\n";
+}
+
+void disconnected(Rdma::Connection::intrusive_ptr& ci) {
+ ConRec* cr = ci->getContext<ConRec>();
+ cr->connection->disconnect();
+ cr->data->drainWriteQueue(drained);
+ delete cr;
+ cout << "Disconnected: " << cr << "\n";
+}
+
+void connectionError(Rdma::Connection::intrusive_ptr& ci, Rdma::ErrorType) {
+ ConRec* cr = ci->getContext<ConRec>();
+ cr->connection->disconnect();
+ if (cr) {
+ cr->data->drainWriteQueue(drained);
+ delete cr;
+ }
+ cout << "Connection error: " << cr << "\n";
+}
+
+bool connectionRequest(Rdma::Connection::intrusive_ptr& ci, const Rdma::ConnectionParams& cp) {
+ cout << "Incoming connection: ";
+
+ // For fun reject alternate connection attempts
+ static bool x = false;
+ x = true;
+
+ // Must create aio here so as to prepost buffers *before* we accept connection
+ if (x) {
+ ConRec* cr = new ConRec(ci);
+ Rdma::AsynchIO* aio =
+ new Rdma::AsynchIO(ci->getQueuePair(),
+ cp.rdmaProtocolVersion,
+ cp.maxRecvBufferSize, cp.initialXmitCredit, Rdma::DEFAULT_WR_ENTRIES,
+ boost::bind(data, cr, _1, _2),
+ boost::bind(idle, cr, _1),
+ boost::bind(full, cr, _1, _2),
+ dataError);
+ ci->addContext(cr);
+ cr->data = aio;
+ cout << "Accept=>" << cr << "\n";
+ } else {
+ cout << "Reject\n";
+ }
+
+ return x;
+}
+
+void connected(Poller::shared_ptr poller, Rdma::Connection::intrusive_ptr& ci) {
+ static int cnt = 0;
+ ConRec* cr = ci->getContext<ConRec>();
+ cout << "Connected: " << cr << "(" << ++cnt << ")\n";
+
+ cr->data->start(poller);
+}
+
+}} // namespace qpid::tests
+
+using namespace qpid::tests;
+
+int main(int argc, char* argv[]) {
+ vector<string> args(&argv[0], &argv[argc]);
+
+ std::string port = (args.size() < 2) ? "20079" : args[1];
+ cout << "Listening on port: " << port << "\n";
+
+ try {
+ boost::shared_ptr<Poller> p(new Poller());
+
+ Rdma::Listener a(
+ Rdma::ConnectionParams(16384, Rdma::DEFAULT_WR_ENTRIES),
+ boost::bind(connected, p, _1),
+ connectionError,
+ disconnected,
+ connectionRequest);
+
+
+ SocketAddress sa("", port);
+ a.start(p, sa);
+
+ // The poller loop blocks all signals so run in its own thread
+ Thread t(*p);
+
+ ::pause();
+ p->shutdown();
+ t.join();
+ } catch (Rdma::Exception& e) {
+ int err = e.getError();
+ cerr << "Error: " << e.what() << "(" << err << ")\n";
+ }
+}
diff --git a/qpid/cpp/src/qpid/sys/rdma/rdma_exception.h b/qpid/cpp/src/qpid/sys/rdma/rdma_exception.h
new file mode 100644
index 0000000000..a3a289e38a
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/rdma/rdma_exception.h
@@ -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.
+ *
+ */
+#ifndef RDMA_EXCEPTION_H
+#define RDMA_EXCEPTION_H
+
+#include <exception>
+
+#include <errno.h>
+#include <string.h>
+
+namespace Rdma {
+ static __thread char s[50];
+ class Exception : public std::exception {
+ int err;
+
+ public:
+ Exception(int e) : err(e) {}
+ int getError() { return err; }
+ const char* what() const throw() {
+ return ::strerror_r(err, s, 50);
+ }
+ };
+
+ inline void THROW_ERRNO() {
+ throw Rdma::Exception(errno);
+ }
+
+ inline void CHECK(int rc) {
+ if (rc != 0)
+ throw Rdma::Exception((rc == -1) ? errno : rc >0 ? rc : -rc);
+ }
+
+ inline int GETERR(int rc) {
+ return (rc == -1) ? errno : rc > 0 ? rc : -rc;
+ }
+
+ inline void CHECK_IBV(int rc) {
+ if (rc != 0)
+ throw Rdma::Exception(rc);
+ }
+
+ template <typename T>
+ inline
+ T* CHECK_NULL(T* rc) {
+ if (rc == 0)
+ THROW_ERRNO();
+ return rc;
+ }
+}
+
+#endif // RDMA_EXCEPTION_H
diff --git a/qpid/cpp/src/qpid/sys/rdma/rdma_factories.cpp b/qpid/cpp/src/qpid/sys/rdma/rdma_factories.cpp
new file mode 100644
index 0000000000..a66f5b4035
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/rdma/rdma_factories.cpp
@@ -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.
+ *
+ */
+#include "qpid/sys/rdma/rdma_factories.h"
+
+#include "qpid/sys/rdma/rdma_exception.h"
+
+
+namespace Rdma {
+ // Intentionally ignore return values for these functions
+ // - we can't do anything about then anyway
+ void acker(::rdma_cm_event* e) throw () {
+ if (e) (void) ::rdma_ack_cm_event(e);
+ }
+
+ void destroyEChannel(::rdma_event_channel* c) throw () {
+ if (c) (void) ::rdma_destroy_event_channel(c);
+ }
+
+ void destroyId(::rdma_cm_id* i) throw () {
+ if (i) (void) ::rdma_destroy_id(i);
+ }
+
+ void deallocPd(::ibv_pd* p) throw () {
+ if (p) (void) ::ibv_dealloc_pd(p);
+ }
+
+ void deregMr(::ibv_mr* mr) throw () {
+ if (mr) (void) ::ibv_dereg_mr(mr);
+ }
+
+ void destroyCChannel(::ibv_comp_channel* c) throw () {
+ if (c) (void) ::ibv_destroy_comp_channel(c);
+ }
+
+ void destroyCq(::ibv_cq* cq) throw () {
+ if (cq) (void) ::ibv_destroy_cq(cq);
+ }
+
+ void destroyQp(::ibv_qp* qp) throw () {
+ if (qp) (void) ::ibv_destroy_qp(qp);
+ }
+
+ boost::shared_ptr< ::rdma_cm_id > mkId(::rdma_cm_id* i) {
+ return boost::shared_ptr< ::rdma_cm_id >(i, destroyId);
+ }
+
+ boost::shared_ptr< ::rdma_cm_event > mkEvent(::rdma_cm_event* e) {
+ return boost::shared_ptr< ::rdma_cm_event >(e, acker);
+ }
+
+ boost::shared_ptr< ::ibv_qp > mkQp(::ibv_qp* qp) {
+ return boost::shared_ptr< ::ibv_qp > (qp, destroyQp);
+ }
+
+ boost::shared_ptr< ::rdma_event_channel > mkEChannel() {
+ ::rdma_event_channel* c = CHECK_NULL(::rdma_create_event_channel());
+ return boost::shared_ptr< ::rdma_event_channel >(c, destroyEChannel);
+ }
+
+ boost::shared_ptr< ::rdma_cm_id >
+ mkId(::rdma_event_channel* ec, void* context, ::rdma_port_space ps) {
+ ::rdma_cm_id* i;
+ CHECK(::rdma_create_id(ec, &i, context, ps));
+ return mkId(i);
+ }
+
+ boost::shared_ptr< ::ibv_pd > allocPd(::ibv_context* c) {
+ ::ibv_pd* pd = CHECK_NULL(::ibv_alloc_pd(c));
+ return boost::shared_ptr< ::ibv_pd >(pd, deallocPd);
+ }
+
+ boost::shared_ptr< ::ibv_mr > regMr(::ibv_pd* pd, void* addr, size_t length, ::ibv_access_flags access) {
+ ::ibv_mr* mr = CHECK_NULL(::ibv_reg_mr(pd, addr, length, access));
+ return boost::shared_ptr< ::ibv_mr >(mr, deregMr);
+ }
+
+ boost::shared_ptr< ::ibv_comp_channel > mkCChannel(::ibv_context* c) {
+ ::ibv_comp_channel* cc = CHECK_NULL(::ibv_create_comp_channel(c));
+ return boost::shared_ptr< ::ibv_comp_channel >(cc, destroyCChannel);
+ }
+
+ boost::shared_ptr< ::ibv_cq >
+ mkCq(::ibv_context* c, int cqe, void* context, ::ibv_comp_channel* cc) {
+ ::ibv_cq* cq = CHECK_NULL(::ibv_create_cq(c, cqe, context, cc, 0));
+ return boost::shared_ptr< ::ibv_cq >(cq, destroyCq);
+ }
+}
diff --git a/qpid/cpp/src/qpid/sys/rdma/rdma_factories.h b/qpid/cpp/src/qpid/sys/rdma/rdma_factories.h
new file mode 100644
index 0000000000..bfca71fc7e
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/rdma/rdma_factories.h
@@ -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.
+ *
+ */
+#ifndef RDMA_FACTORIES_H
+#define RDMA_FACTORIES_H
+
+#include <rdma/rdma_cma.h>
+
+#include <boost/shared_ptr.hpp>
+
+namespace Rdma {
+ boost::shared_ptr< ::rdma_event_channel > mkEChannel();
+ boost::shared_ptr< ::rdma_cm_id > mkId(::rdma_event_channel* ec, void* context, ::rdma_port_space ps);
+ boost::shared_ptr< ::rdma_cm_id > mkId(::rdma_cm_id* i);
+ boost::shared_ptr< ::rdma_cm_event > mkEvent(::rdma_cm_event* e);
+ boost::shared_ptr< ::ibv_qp > mkQp(::ibv_qp* qp);
+ boost::shared_ptr< ::ibv_pd > allocPd(::ibv_context* c);
+ boost::shared_ptr< ::ibv_mr > regMr(::ibv_pd* pd, void* addr, size_t length, ::ibv_access_flags access);
+ boost::shared_ptr< ::ibv_comp_channel > mkCChannel(::ibv_context* c);
+ boost::shared_ptr< ::ibv_cq > mkCq(::ibv_context* c, int cqe, void* context, ::ibv_comp_channel* cc);
+}
+
+#endif // RDMA_FACTORIES_H
diff --git a/qpid/cpp/src/qpid/sys/rdma/rdma_wrap.cpp b/qpid/cpp/src/qpid/sys/rdma/rdma_wrap.cpp
new file mode 100644
index 0000000000..39c7cc2ab4
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/rdma/rdma_wrap.cpp
@@ -0,0 +1,576 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/rdma/rdma_wrap.h"
+
+#include "qpid/sys/rdma/rdma_factories.h"
+#include "qpid/sys/rdma/rdma_exception.h"
+
+#include "qpid/sys/posix/PrivatePosix.h"
+
+#include <fcntl.h>
+#include <netdb.h>
+
+#include <iostream>
+#include <stdexcept>
+
+namespace Rdma {
+ const ::rdma_conn_param DEFAULT_CONNECT_PARAM = {
+ 0, // .private_data
+ 0, // .private_data_len
+ 4, // .responder_resources
+ 4, // .initiator_depth
+ 0, // .flow_control
+ 5, // .retry_count
+ 7 // .rnr_retry_count
+ };
+
+ // This is moderately inefficient so don't use in a critical path
+ int deviceCount() {
+ int count;
+ ::ibv_free_device_list(::ibv_get_device_list(&count));
+ return count;
+ }
+
+ Buffer::Buffer(uint32_t lkey, char* bytes, const int32_t byteCount,
+ const int32_t reserve) :
+ bufferSize(byteCount + reserve), reserved(reserve)
+ {
+ sge.addr = (uintptr_t) bytes;
+ sge.length = 0;
+ sge.lkey = lkey;
+ }
+
+ QueuePairEvent::QueuePairEvent() :
+ dir(NONE)
+ {}
+
+ QueuePairEvent::QueuePairEvent(
+ const ::ibv_wc& w,
+ boost::shared_ptr< ::ibv_cq > c,
+ QueueDirection d) :
+ cq(c),
+ wc(w),
+ dir(d)
+ {
+ assert(dir != NONE);
+ }
+
+ QueuePairEvent::operator bool() const {
+ return dir != NONE;
+ }
+
+ bool QueuePairEvent::immPresent() const {
+ return wc.wc_flags & IBV_WC_WITH_IMM;
+ }
+
+ uint32_t QueuePairEvent::getImm() const {
+ return ntohl(wc.imm_data);
+ }
+
+ QueueDirection QueuePairEvent::getDirection() const {
+ return dir;
+ }
+
+ ::ibv_wc_opcode QueuePairEvent::getEventType() const {
+ return wc.opcode;
+ }
+
+ ::ibv_wc_status QueuePairEvent::getEventStatus() const {
+ return wc.status;
+ }
+
+ Buffer* QueuePairEvent::getBuffer() const {
+ Buffer* b = reinterpret_cast<Buffer*>(wc.wr_id);
+ b->dataCount(wc.byte_len);
+ return b;
+ }
+
+ QueuePair::QueuePair(boost::shared_ptr< ::rdma_cm_id > i) :
+ handle(new qpid::sys::IOHandle),
+ pd(allocPd(i->verbs)),
+ cchannel(mkCChannel(i->verbs)),
+ scq(mkCq(i->verbs, DEFAULT_CQ_ENTRIES, 0, cchannel.get())),
+ rcq(mkCq(i->verbs, DEFAULT_CQ_ENTRIES, 0, cchannel.get())),
+ outstandingSendEvents(0),
+ outstandingRecvEvents(0)
+ {
+ handle->fd = cchannel->fd;
+
+ // Set cq context to this QueuePair object so we can find
+ // ourselves again
+ scq->cq_context = this;
+ rcq->cq_context = this;
+
+ ::ibv_device_attr dev_attr;
+ CHECK(::ibv_query_device(i->verbs, &dev_attr));
+
+ ::ibv_qp_init_attr qp_attr = {};
+
+ // TODO: make a default struct for this
+ qp_attr.cap.max_send_wr = DEFAULT_WR_ENTRIES;
+ qp_attr.cap.max_send_sge = 1;
+ qp_attr.cap.max_recv_wr = DEFAULT_WR_ENTRIES;
+ qp_attr.cap.max_recv_sge = 1;
+
+ qp_attr.send_cq = scq.get();
+ qp_attr.recv_cq = rcq.get();
+ qp_attr.qp_type = IBV_QPT_RC;
+
+ CHECK(::rdma_create_qp(i.get(), pd.get(), &qp_attr));
+ qp = mkQp(i->qp);
+
+ // Set the qp context to this so we can find ourselves again
+ qp->qp_context = this;
+ }
+
+ QueuePair::~QueuePair() {
+ // Reset back pointer in case someone else has the qp
+ qp->qp_context = 0;
+
+ // Dispose queue pair before we ack events
+ qp.reset();
+
+ if (outstandingSendEvents > 0)
+ ::ibv_ack_cq_events(scq.get(), outstandingSendEvents);
+ if (outstandingRecvEvents > 0)
+ ::ibv_ack_cq_events(rcq.get(), outstandingRecvEvents);
+
+ // Deallocate recv buffer memory
+ if (rmr) delete [] static_cast<char*>(rmr->addr);
+
+ // Deallocate recv buffer memory
+ if (smr) delete [] static_cast<char*>(smr->addr);
+
+ // The buffers vectors automatically deletes all the buffers we've allocated
+ }
+
+ QueuePair::operator qpid::sys::IOHandle&() const
+ {
+ return *handle;
+ }
+
+ // Create buffers to use for writing
+ void QueuePair::createSendBuffers(int sendBufferCount, int bufferSize, int reserved)
+ {
+ assert(!smr);
+
+ // Round up buffersize to cacheline (64 bytes)
+ int dataLength = (bufferSize+reserved+63) & (~63);
+
+ // Allocate memory block for all receive buffers
+ char* mem = new char [sendBufferCount * dataLength];
+ smr = regMr(pd.get(), mem, sendBufferCount * dataLength, ::IBV_ACCESS_LOCAL_WRITE);
+ sendBuffers.reserve(sendBufferCount);
+ freeBuffers.reserve(sendBufferCount);
+ for (int i = 0; i<sendBufferCount; ++i) {
+ // Allocate xmit buffer
+ sendBuffers.push_back(Buffer(smr->lkey, &mem[i*dataLength], bufferSize, reserved));
+ freeBuffers.push_back(i);
+ }
+ }
+
+ Buffer* QueuePair::getSendBuffer() {
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(bufferLock);
+ if (freeBuffers.empty())
+ return 0;
+ int i = freeBuffers.back();
+ freeBuffers.pop_back();
+ assert(i >= 0 && i < int(sendBuffers.size()));
+ Buffer* b = &sendBuffers[i];
+ b->dataCount(0);
+ return b;
+ }
+
+ void QueuePair::returnSendBuffer(Buffer* b) {
+ qpid::sys::ScopedLock<qpid::sys::Mutex> l(bufferLock);
+ int i = b - &sendBuffers[0];
+ assert(i >= 0 && i < int(sendBuffers.size()));
+ freeBuffers.push_back(i);
+ }
+
+ void QueuePair::allocateRecvBuffers(int recvBufferCount, int bufferSize)
+ {
+ assert(!rmr);
+
+ // Round up buffersize to cacheline (64 bytes)
+ bufferSize = (bufferSize+63) & (~63);
+
+ // Allocate memory block for all receive buffers
+ char* mem = new char [recvBufferCount * bufferSize];
+ rmr = regMr(pd.get(), mem, recvBufferCount * bufferSize, ::IBV_ACCESS_LOCAL_WRITE);
+ recvBuffers.reserve(recvBufferCount);
+ for (int i = 0; i<recvBufferCount; ++i) {
+ // Allocate recv buffer
+ recvBuffers.push_back(Buffer(rmr->lkey, &mem[i*bufferSize], bufferSize));
+ postRecv(&recvBuffers[i]);
+ }
+ }
+
+ // Make channel non-blocking by making
+ // associated fd nonblocking
+ void QueuePair::nonblocking() {
+ ::fcntl(cchannel->fd, F_SETFL, O_NONBLOCK);
+ }
+
+ // If we get EAGAIN because the channel has been set non blocking
+ // and we'd have to wait then return an empty event
+ QueuePair::intrusive_ptr QueuePair::getNextChannelEvent() {
+ // First find out which cq has the event
+ ::ibv_cq* cq;
+ void* ctx;
+ int rc = ::ibv_get_cq_event(cchannel.get(), &cq, &ctx);
+ if (rc == -1 && errno == EAGAIN)
+ return 0;
+ CHECK(rc);
+
+ // Batch acknowledge the event
+ if (cq == scq.get()) {
+ if (++outstandingSendEvents > DEFAULT_CQ_ENTRIES / 2) {
+ ::ibv_ack_cq_events(cq, outstandingSendEvents);
+ outstandingSendEvents = 0;
+ }
+ } else if (cq == rcq.get()) {
+ if (++outstandingRecvEvents > DEFAULT_CQ_ENTRIES / 2) {
+ ::ibv_ack_cq_events(cq, outstandingRecvEvents);
+ outstandingRecvEvents = 0;
+ }
+ }
+
+ return static_cast<QueuePair*>(ctx);
+ }
+
+ QueuePairEvent QueuePair::getNextEvent() {
+ ::ibv_wc w;
+ if (::ibv_poll_cq(scq.get(), 1, &w) == 1)
+ return QueuePairEvent(w, scq, SEND);
+ else if (::ibv_poll_cq(rcq.get(), 1, &w) == 1)
+ return QueuePairEvent(w, rcq, RECV);
+ else
+ return QueuePairEvent();
+ }
+
+ void QueuePair::notifyRecv() {
+ CHECK_IBV(ibv_req_notify_cq(rcq.get(), 0));
+ }
+
+ void QueuePair::notifySend() {
+ CHECK_IBV(ibv_req_notify_cq(scq.get(), 0));
+ }
+
+ void QueuePair::postRecv(Buffer* buf) {
+ ::ibv_recv_wr rwr = {};
+
+ rwr.wr_id = reinterpret_cast<uint64_t>(buf);
+ // We are given the whole buffer
+ buf->dataCount(buf->byteCount());
+ rwr.sg_list = &buf->sge;
+ rwr.num_sge = 1;
+
+ ::ibv_recv_wr* badrwr = 0;
+ CHECK(::ibv_post_recv(qp.get(), &rwr, &badrwr));
+ if (badrwr)
+ throw std::logic_error("ibv_post_recv(): Bad rwr");
+ }
+
+ void QueuePair::postSend(Buffer* buf) {
+ ::ibv_send_wr swr = {};
+
+ swr.wr_id = reinterpret_cast<uint64_t>(buf);
+ swr.opcode = IBV_WR_SEND;
+ swr.send_flags = IBV_SEND_SIGNALED;
+ swr.sg_list = &buf->sge;
+ swr.num_sge = 1;
+
+ ::ibv_send_wr* badswr = 0;
+ CHECK(::ibv_post_send(qp.get(), &swr, &badswr));
+ if (badswr)
+ throw std::logic_error("ibv_post_send(): Bad swr");
+ }
+
+ void QueuePair::postSend(uint32_t imm, Buffer* buf) {
+ ::ibv_send_wr swr = {};
+
+ swr.wr_id = reinterpret_cast<uint64_t>(buf);
+ swr.imm_data = htonl(imm);
+ swr.opcode = IBV_WR_SEND_WITH_IMM;
+ swr.send_flags = IBV_SEND_SIGNALED;
+ swr.sg_list = &buf->sge;
+ swr.num_sge = 1;
+
+ ::ibv_send_wr* badswr = 0;
+ CHECK(::ibv_post_send(qp.get(), &swr, &badswr));
+ if (badswr)
+ throw std::logic_error("ibv_post_send(): Bad swr");
+ }
+
+ ConnectionEvent::ConnectionEvent(::rdma_cm_event* e) :
+ id((e->event != RDMA_CM_EVENT_CONNECT_REQUEST) ?
+ Connection::find(e->id) : new Connection(e->id)),
+ listen_id(Connection::find(e->listen_id)),
+ event(mkEvent(e))
+ {}
+
+ ConnectionEvent::operator bool() const {
+ return !!event;
+ }
+
+ ::rdma_cm_event_type ConnectionEvent::getEventType() const {
+ return event->event;
+ }
+
+ ::rdma_conn_param ConnectionEvent::getConnectionParam() const {
+ // It's badly documented, but it seems from the librdma source code that all the following
+ // event types have a valid param.conn
+ switch (event->event) {
+ case RDMA_CM_EVENT_CONNECT_REQUEST:
+ case RDMA_CM_EVENT_ESTABLISHED:
+ case RDMA_CM_EVENT_REJECTED:
+ case RDMA_CM_EVENT_DISCONNECTED:
+ case RDMA_CM_EVENT_CONNECT_ERROR:
+ return event->param.conn;
+ default:
+ ::rdma_conn_param p = {};
+ return p;
+ }
+ }
+
+ boost::intrusive_ptr<Connection> ConnectionEvent::getConnection () const {
+ return id;
+ }
+
+ boost::intrusive_ptr<Connection> ConnectionEvent::getListenId() const {
+ return listen_id;
+ }
+
+ // Wrap the passed in rdma_cm_id with a Connection
+ // this basically happens only on connection request
+ Connection::Connection(::rdma_cm_id* i) :
+ handle(new qpid::sys::IOHandle),
+ id(mkId(i)),
+ context(0)
+ {
+ handle->fd = id->channel->fd;
+
+ // Just overwrite the previous context as it will
+ // have come from the listening connection
+ if (i)
+ i->context = this;
+ }
+
+ Connection::Connection() :
+ handle(new qpid::sys::IOHandle),
+ channel(mkEChannel()),
+ id(mkId(channel.get(), this, RDMA_PS_TCP)),
+ context(0)
+ {
+ handle->fd = channel->fd;
+ }
+
+ Connection::~Connection() {
+ // Reset the id context in case someone else has it
+ id->context = 0;
+ }
+
+ Connection::operator qpid::sys::IOHandle&() const
+ {
+ return *handle;
+ }
+
+ void Connection::ensureQueuePair() {
+ assert(id.get());
+
+ // Only allocate a queue pair if there isn't one already
+ if (qp)
+ return;
+
+ qp = new QueuePair(id);
+ }
+
+ Connection::intrusive_ptr Connection::make() {
+ return new Connection();
+ }
+
+ Connection::intrusive_ptr Connection::find(::rdma_cm_id* i) {
+ if (!i)
+ return 0;
+ Connection* id = static_cast< Connection* >(i->context);
+ if (!id)
+ throw std::logic_error("Couldn't find existing Connection");
+ return id;
+ }
+
+ // Make channel non-blocking by making
+ // associated fd nonblocking
+ void Connection::nonblocking() {
+ assert(id.get());
+ ::fcntl(id->channel->fd, F_SETFL, O_NONBLOCK);
+ }
+
+ // If we get EAGAIN because the channel has been set non blocking
+ // and we'd have to wait then return an empty event
+ ConnectionEvent Connection::getNextEvent() {
+ assert(id.get());
+ ::rdma_cm_event* e;
+ int rc = ::rdma_get_cm_event(id->channel, &e);
+ if (GETERR(rc) == EAGAIN)
+ return ConnectionEvent();
+ CHECK(rc);
+ return ConnectionEvent(e);
+ }
+
+ void Connection::bind(const qpid::sys::SocketAddress& src_addr) const {
+ assert(id.get());
+ CHECK(::rdma_bind_addr(id.get(), getAddrInfo(src_addr).ai_addr));
+ }
+
+ void Connection::listen(int backlog) const {
+ assert(id.get());
+ CHECK(::rdma_listen(id.get(), backlog));
+ }
+
+ void Connection::resolve_addr(
+ const qpid::sys::SocketAddress& dst_addr,
+ int timeout_ms) const
+ {
+ assert(id.get());
+ CHECK(::rdma_resolve_addr(id.get(), 0, getAddrInfo(dst_addr).ai_addr, timeout_ms));
+ }
+
+ void Connection::resolve_route(int timeout_ms) const {
+ assert(id.get());
+ CHECK(::rdma_resolve_route(id.get(), timeout_ms));
+ }
+
+ void Connection::disconnect() const {
+ assert(id.get());
+ int rc = ::rdma_disconnect(id.get());
+ // iWarp doesn't let you disconnect a disconnected connection
+ // but Infiniband can do so it's okay to call rdma_disconnect()
+ // in response to a disconnect event, but we may get an error
+ if (GETERR(rc) == EINVAL)
+ return;
+ CHECK(rc);
+ }
+
+ // TODO: Currently you can only connect with the default connection parameters
+ void Connection::connect(const void* data, size_t len) {
+ assert(id.get());
+ // Need to have a queue pair before we can connect
+ ensureQueuePair();
+
+ ::rdma_conn_param p = DEFAULT_CONNECT_PARAM;
+ p.private_data = data;
+ p.private_data_len = len;
+ CHECK(::rdma_connect(id.get(), &p));
+ }
+
+ void Connection::connect() {
+ connect(0, 0);
+ }
+
+ void Connection::accept(const ::rdma_conn_param& param, const void* data, size_t len) {
+ assert(id.get());
+ // Need to have a queue pair before we can accept
+ ensureQueuePair();
+
+ ::rdma_conn_param p = param;
+ p.private_data = data;
+ p.private_data_len = len;
+ CHECK(::rdma_accept(id.get(), &p));
+ }
+
+ void Connection::accept(const ::rdma_conn_param& param) {
+ accept(param, 0, 0);
+ }
+
+ void Connection::reject(const void* data, size_t len) const {
+ assert(id.get());
+ CHECK(::rdma_reject(id.get(), data, len));
+ }
+
+ void Connection::reject() const {
+ assert(id.get());
+ CHECK(::rdma_reject(id.get(), 0, 0));
+ }
+
+ QueuePair::intrusive_ptr Connection::getQueuePair() {
+ assert(id.get());
+
+ ensureQueuePair();
+
+ return qp;
+ }
+
+ std::string Connection::getLocalName() const {
+ ::sockaddr* addr = ::rdma_get_local_addr(id.get());
+ char hostName[NI_MAXHOST];
+ char portName[NI_MAXSERV];
+ CHECK_IBV(::getnameinfo(
+ addr, sizeof(::sockaddr_storage),
+ hostName, sizeof(hostName),
+ portName, sizeof(portName),
+ NI_NUMERICHOST | NI_NUMERICSERV));
+ std::string r(hostName);
+ r += ":";
+ r += portName;
+ return r;
+ }
+
+ std::string Connection::getPeerName() const {
+ ::sockaddr* addr = ::rdma_get_peer_addr(id.get());
+ char hostName[NI_MAXHOST];
+ char portName[NI_MAXSERV];
+ CHECK_IBV(::getnameinfo(
+ addr, sizeof(::sockaddr_storage),
+ hostName, sizeof(hostName),
+ portName, sizeof(portName),
+ NI_NUMERICHOST | NI_NUMERICSERV));
+ std::string r(hostName);
+ r += ":";
+ r += portName;
+ return r;
+ }
+}
+
+std::ostream& operator<<(std::ostream& o, ::rdma_cm_event_type t) {
+# define CHECK_TYPE(t) case t: o << #t; break;
+ switch(t) {
+ CHECK_TYPE(RDMA_CM_EVENT_ADDR_RESOLVED)
+ CHECK_TYPE(RDMA_CM_EVENT_ADDR_ERROR)
+ CHECK_TYPE(RDMA_CM_EVENT_ROUTE_RESOLVED)
+ CHECK_TYPE(RDMA_CM_EVENT_ROUTE_ERROR)
+ CHECK_TYPE(RDMA_CM_EVENT_CONNECT_REQUEST)
+ CHECK_TYPE(RDMA_CM_EVENT_CONNECT_RESPONSE)
+ CHECK_TYPE(RDMA_CM_EVENT_CONNECT_ERROR)
+ CHECK_TYPE(RDMA_CM_EVENT_UNREACHABLE)
+ CHECK_TYPE(RDMA_CM_EVENT_REJECTED)
+ CHECK_TYPE(RDMA_CM_EVENT_ESTABLISHED)
+ CHECK_TYPE(RDMA_CM_EVENT_DISCONNECTED)
+ CHECK_TYPE(RDMA_CM_EVENT_DEVICE_REMOVAL)
+ CHECK_TYPE(RDMA_CM_EVENT_MULTICAST_JOIN)
+ CHECK_TYPE(RDMA_CM_EVENT_MULTICAST_ERROR)
+ default:
+ o << "UNKNOWN_EVENT";
+ }
+# undef CHECK_TYPE
+ return o;
+}
diff --git a/qpid/cpp/src/qpid/sys/rdma/rdma_wrap.h b/qpid/cpp/src/qpid/sys/rdma/rdma_wrap.h
new file mode 100644
index 0000000000..5d9b681da8
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/rdma/rdma_wrap.h
@@ -0,0 +1,305 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#ifndef RDMA_WRAP_H
+#define RDMA_WRAP_H
+
+#include <rdma/rdma_cma.h>
+
+#include "qpid/RefCounted.h"
+#include "qpid/sys/IOHandle.h"
+#include "qpid/sys/Mutex.h"
+
+#include <boost/shared_ptr.hpp>
+#include <boost/scoped_ptr.hpp>
+#include <boost/intrusive_ptr.hpp>
+#include <boost/ptr_container/ptr_deque.hpp>
+
+#include <vector>
+
+namespace qpid {
+namespace sys {
+ class SocketAddress;
+}}
+
+namespace Rdma {
+ const int DEFAULT_TIMEOUT = 2000; // 2 secs
+ const int DEFAULT_BACKLOG = 100;
+ const int DEFAULT_CQ_ENTRIES = 256;
+ const int DEFAULT_WR_ENTRIES = 64;
+ extern const ::rdma_conn_param DEFAULT_CONNECT_PARAM;
+
+ int deviceCount();
+
+ struct Buffer {
+ friend class QueuePair;
+ friend class QueuePairEvent;
+
+ char* bytes() const;
+ uint32_t* words() const;
+ int32_t byteCount() const;
+ int32_t wordCount() const;
+ int32_t dataCount() const;
+ void dataCount(int32_t);
+
+ private:
+ Buffer(uint32_t lkey, char* bytes, const int32_t byteCount, const int32_t reserve=0);
+ int32_t bufferSize;
+ int32_t reserved; // for framing header
+ ::ibv_sge sge;
+ };
+
+ inline char* Buffer::bytes() const {
+ return (char*) sge.addr;
+ }
+
+ inline uint32_t* Buffer::words() const {
+ return (uint32_t*) sge.addr;
+ }
+
+ /** return the number of bytes available for application data */
+ inline int32_t Buffer::byteCount() const {
+ return bufferSize - reserved;
+ }
+
+ /** return the number of words available for application data */
+ inline int32_t Buffer::wordCount() const {
+ return (bufferSize - reserved) / sizeof(uint32_t);
+ }
+
+ inline int32_t Buffer::dataCount() const {
+ return sge.length;
+ }
+
+ inline void Buffer::dataCount(int32_t s) {
+ // catch any attempt to overflow a buffer
+ assert(s <= bufferSize + reserved);
+ sge.length = s;
+ }
+
+ class Connection;
+
+ enum QueueDirection {
+ NONE,
+ SEND,
+ RECV
+ };
+
+ class QueuePairEvent {
+ boost::shared_ptr< ::ibv_cq > cq;
+ ::ibv_wc wc;
+ QueueDirection dir;
+
+ friend class QueuePair;
+
+ QueuePairEvent();
+ QueuePairEvent(
+ const ::ibv_wc& w,
+ boost::shared_ptr< ::ibv_cq > c,
+ QueueDirection d);
+
+ public:
+ operator bool() const;
+ bool immPresent() const;
+ uint32_t getImm() const;
+ QueueDirection getDirection() const;
+ ::ibv_wc_opcode getEventType() const;
+ ::ibv_wc_status getEventStatus() const;
+ Buffer* getBuffer() const;
+ };
+
+ // Wrapper for a queue pair - this has the functionality for
+ // putting buffers on the receive queue and for sending buffers
+ // to the other end of the connection.
+ class QueuePair : public qpid::RefCounted {
+ friend class Connection;
+
+ boost::scoped_ptr< qpid::sys::IOHandle > handle;
+ boost::shared_ptr< ::ibv_pd > pd;
+ boost::shared_ptr< ::ibv_mr > smr;
+ boost::shared_ptr< ::ibv_mr > rmr;
+ boost::shared_ptr< ::ibv_comp_channel > cchannel;
+ boost::shared_ptr< ::ibv_cq > scq;
+ boost::shared_ptr< ::ibv_cq > rcq;
+ boost::shared_ptr< ::ibv_qp > qp;
+ int outstandingSendEvents;
+ int outstandingRecvEvents;
+ std::vector<Buffer> sendBuffers;
+ std::vector<Buffer> recvBuffers;
+ qpid::sys::Mutex bufferLock;
+ std::vector<int> freeBuffers;
+
+ QueuePair(boost::shared_ptr< ::rdma_cm_id > id);
+ ~QueuePair();
+
+ public:
+ typedef boost::intrusive_ptr<QueuePair> intrusive_ptr;
+
+ operator qpid::sys::IOHandle&() const;
+
+ // Create a buffers to use for writing
+ void createSendBuffers(int sendBufferCount, int dataSize, int headerSize);
+
+ // Get a send buffer
+ Buffer* getSendBuffer();
+
+ // Return buffer to pool after use
+ void returnSendBuffer(Buffer* b);
+
+ // Create and post recv buffers
+ void allocateRecvBuffers(int recvBufferCount, int bufferSize);
+
+ // Make channel non-blocking by making
+ // associated fd nonblocking
+ void nonblocking();
+
+ // If we get EAGAIN because the channel has been set non blocking
+ // and we'd have to wait then return an empty event
+ QueuePair::intrusive_ptr getNextChannelEvent();
+
+ QueuePairEvent getNextEvent();
+
+ void postRecv(Buffer* buf);
+ void postSend(Buffer* buf);
+ void postSend(uint32_t imm, Buffer* buf);
+ void notifyRecv();
+ void notifySend();
+ };
+
+ class ConnectionEvent {
+ friend class Connection;
+
+ // The order of the members is important as we have to acknowledge
+ // the event before destroying the ids on destruction
+ boost::intrusive_ptr<Connection> id;
+ boost::intrusive_ptr<Connection> listen_id;
+ boost::shared_ptr< ::rdma_cm_event > event;
+
+ ConnectionEvent() {}
+ ConnectionEvent(::rdma_cm_event* e);
+
+ // Default copy, assignment and destructor ok
+ public:
+ operator bool() const;
+ ::rdma_cm_event_type getEventType() const;
+ ::rdma_conn_param getConnectionParam() const;
+ boost::intrusive_ptr<Connection> getConnection () const;
+ boost::intrusive_ptr<Connection> getListenId() const;
+ };
+
+ // For the moment this is a fairly simple wrapper for rdma_cm_id.
+ //
+ // NB: It allocates a protection domain (pd) per connection which means that
+ // registered buffers can't be shared between different connections
+ // (this can only happen between connections on the same controller in any case,
+ // so needs careful management if used)
+ class Connection : public qpid::RefCounted {
+ boost::scoped_ptr< qpid::sys::IOHandle > handle;
+ boost::shared_ptr< ::rdma_event_channel > channel;
+ boost::shared_ptr< ::rdma_cm_id > id;
+ QueuePair::intrusive_ptr qp;
+
+ void* context;
+
+ friend class ConnectionEvent;
+ friend class QueuePair;
+
+ // Wrap the passed in rdma_cm_id with a Connection
+ // this basically happens only on connection request
+ Connection(::rdma_cm_id* i);
+ Connection();
+ ~Connection();
+
+ void ensureQueuePair();
+
+ public:
+ typedef boost::intrusive_ptr<Connection> intrusive_ptr;
+
+ operator qpid::sys::IOHandle&() const;
+
+ static intrusive_ptr make();
+ static intrusive_ptr find(::rdma_cm_id* i);
+
+ template <typename T>
+ void addContext(T* c) {
+ // Don't allow replacing context
+ if (!context)
+ context = c;
+ }
+
+ void removeContext() {
+ context = 0;
+ }
+
+ template <typename T>
+ T* getContext() {
+ return static_cast<T*>(context);
+ }
+
+ // Make channel non-blocking by making
+ // associated fd nonblocking
+ void nonblocking();
+
+ // If we get EAGAIN because the channel has been set non blocking
+ // and we'd have to wait then return an empty event
+ ConnectionEvent getNextEvent();
+
+ void bind(const qpid::sys::SocketAddress& src_addr) const;
+ void listen(int backlog = DEFAULT_BACKLOG) const;
+ void resolve_addr(
+ const qpid::sys::SocketAddress& dst_addr,
+ int timeout_ms = DEFAULT_TIMEOUT) const;
+ void resolve_route(int timeout_ms = DEFAULT_TIMEOUT) const;
+ void disconnect() const;
+
+ // TODO: Currently you can only connect with the default connection parameters
+ void connect(const void* data, size_t len);
+ void connect();
+ template <typename T>
+ void connect(const T* data) {
+ connect(data, sizeof(T));
+ }
+
+ // TODO: Not sure how to default accept params - they come from the connection request
+ // event
+ void accept(const ::rdma_conn_param& param, const void* data, size_t len);
+ void accept(const ::rdma_conn_param& param);
+ template <typename T>
+ void accept(const ::rdma_conn_param& param, const T* data) {
+ accept(param, data, sizeof(T));
+ }
+
+ void reject(const void* data, size_t len) const;
+ void reject() const;
+ template <typename T>
+ void reject(const T* data) const {
+ reject(data, sizeof(T));
+ }
+
+ QueuePair::intrusive_ptr getQueuePair();
+ std::string getLocalName() const;
+ std::string getPeerName() const;
+ std::string getFullName() const { return getLocalName()+"-"+getPeerName(); }
+ };
+}
+
+std::ostream& operator<<(std::ostream& o, ::rdma_cm_event_type t);
+
+#endif // RDMA_WRAP_H
diff --git a/qpid/cpp/src/qpid/sys/regex.h b/qpid/cpp/src/qpid/sys/regex.h
new file mode 100644
index 0000000000..77de6a7f5c
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/regex.h
@@ -0,0 +1,81 @@
+#ifndef QPID_SYS_REGEX_H
+#define QPID_SYS_REGEX_H
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#if defined(_POSIX_SOURCE) || defined(__unix__)
+# include <stdexcept>
+# include <string>
+# include <regex.h>
+#elif defined(_MSC_VER)
+# include <regex>
+#else
+#error "No known regex implementation"
+#endif
+
+// This is a very simple wrapper for Basic Regular Expression facilities either
+// provided by POSIX or C++ tr1
+//
+// Matching expressions are not supported and so the only useful operation is
+// a simple boolean match indicator.
+
+namespace qpid {
+namespace sys {
+
+#if defined(_POSIX_SOURCE) || defined(__unix__)
+
+class regex {
+ ::regex_t re;
+
+public:
+ regex(const std::string& s) {
+ int rc = ::regcomp(&re, s.c_str(), REG_NOSUB);
+ if (rc != 0) throw std::logic_error("Regular expression compilation error");
+ }
+
+ ~regex() {
+ ::regfree(&re);
+ }
+
+ friend bool regex_match(const std::string& s, const regex& re);
+};
+
+inline bool regex_match(const std::string& s, const regex& re) {
+ return ::regexec(&(re.re), s.c_str(), 0, 0, 0)==0;
+}
+
+#elif defined(_MSC_VER)
+
+class regex : public std::tr1::regex {
+public:
+ regex(const std::string& s) :
+ std::tr1::regex(s, std::tr1::regex::basic)
+ {}
+};
+
+using std::tr1::regex_match;
+
+#else
+#error "No known regex implementation"
+#endif
+
+}}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/sys/solaris/ECFPoller.cpp b/qpid/cpp/src/qpid/sys/solaris/ECFPoller.cpp
new file mode 100644
index 0000000000..06d542c938
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/solaris/ECFPoller.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/log/Logger.h"
+#include "qpid/sys/Poller.h"
+#include "qpid/sys/IOHandle.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/DeletionManager.h"
+#include "qpid/sys/posix/check.h"
+#include "qpid/sys/posix/PrivatePosix.h"
+
+#include <port.h>
+#include <poll.h>
+#include <errno.h>
+#include <pthread.h>
+#include <signal.h>
+
+#include <assert.h>
+#include <queue>
+#include <exception>
+
+
+//TODO: Remove this
+#include "qpid/sys/Dispatcher.h"
+
+namespace qpid {
+namespace sys {
+
+// Deletion manager to handle deferring deletion of PollerHandles to when they definitely aren't being used
+DeletionManager<PollerHandlePrivate> PollerHandleDeletionManager;
+
+// Instantiate (and define) class static for DeletionManager
+template <>
+DeletionManager<PollerHandlePrivate>::AllThreadsStatuses DeletionManager<PollerHandlePrivate>::allThreadsStatuses(0);
+
+class PollerHandlePrivate {
+ friend class Poller;
+ friend class PollerHandle;
+
+ enum FDStat {
+ ABSENT,
+ MONITORED,
+ INACTIVE,
+ HUNGUP,
+ MONITORED_HUNGUP,
+ DELETED
+ };
+
+ int fd;
+ uint32_t events;
+ FDStat stat;
+ Mutex lock;
+
+ PollerHandlePrivate(int f) :
+ fd(f),
+ events(0),
+ stat(ABSENT) {
+ }
+
+ bool isActive() const {
+ return stat == MONITORED || stat == MONITORED_HUNGUP;
+ }
+
+ void setActive() {
+ stat = (stat == HUNGUP) ? MONITORED_HUNGUP : MONITORED;
+ }
+
+ bool isInactive() const {
+ return stat == INACTIVE || stat == HUNGUP;
+ }
+
+ void setInactive() {
+ stat = INACTIVE;
+ }
+
+ bool isIdle() const {
+ return stat == ABSENT;
+ }
+
+ void setIdle() {
+ stat = ABSENT;
+ }
+
+ bool isHungup() const {
+ return stat == MONITORED_HUNGUP || stat == HUNGUP;
+ }
+
+ void setHungup() {
+ assert(stat == MONITORED);
+ stat = HUNGUP;
+ }
+
+ bool isDeleted() const {
+ return stat == DELETED;
+ }
+
+ void setDeleted() {
+ stat = DELETED;
+ }
+};
+
+PollerHandle::PollerHandle(const IOHandle& h) :
+ impl(new PollerHandlePrivate(toFd(h.impl)))
+{}
+
+PollerHandle::~PollerHandle() {
+ {
+ ScopedLock<Mutex> l(impl->lock);
+ if (impl->isDeleted()) {
+ return;
+ }
+ if (impl->isActive()) {
+ impl->setDeleted();
+ }
+ }
+ PollerHandleDeletionManager.markForDeletion(impl);
+}
+
+/**
+ * Concrete implementation of Poller to use the Solaris Event Completion
+ * Framework interface
+ */
+class PollerPrivate {
+ friend class Poller;
+
+ class InterruptHandle: public PollerHandle {
+ std::queue<PollerHandle*> handles;
+
+ void processEvent(Poller::EventType) {
+ PollerHandle* handle = handles.front();
+ handles.pop();
+ assert(handle);
+
+ //Synthesise event
+ Poller::Event event(handle, Poller::INTERRUPTED);
+
+ //Process synthesised event
+ event.process();
+ }
+
+ public:
+ InterruptHandle() : PollerHandle(DummyIOHandle) {}
+
+ void addHandle(PollerHandle& h) {
+ handles.push(&h);
+ }
+
+ PollerHandle *getHandle() {
+ PollerHandle* handle = handles.front();
+ handles.pop();
+ return handle;
+ }
+
+ bool queuedHandles() {
+ return handles.size() > 0;
+ }
+ };
+
+ const int portId;
+ bool isShutdown;
+ InterruptHandle interruptHandle;
+
+ static uint32_t directionToPollEvent(Poller::Direction dir) {
+ switch (dir) {
+ case Poller::INPUT: return POLLIN;
+ case Poller::OUTPUT: return POLLOUT;
+ case Poller::INOUT: return POLLIN | POLLOUT;
+ default: return 0;
+ }
+ }
+
+ static Poller::EventType pollToDirection(uint32_t events) {
+ uint32_t e = events & (POLLIN | POLLOUT);
+ switch (e) {
+ case POLLIN: return Poller::READABLE;
+ case POLLOUT: return Poller::WRITABLE;
+ case POLLIN | POLLOUT: return Poller::READ_WRITABLE;
+ default:
+ return (events & (POLLHUP | POLLERR)) ?
+ Poller::DISCONNECTED : Poller::INVALID;
+ }
+ }
+
+ PollerPrivate() :
+ portId(::port_create()),
+ isShutdown(false) {
+ QPID_POSIX_CHECK(portId);
+ QPID_LOG(trace, "port_create returned port Id: " << portId);
+ }
+
+ ~PollerPrivate() {
+ }
+
+ void interrupt() {
+ //Send an Alarm to the port
+ //We need to send a nonzero event mask, using POLLHUP,
+ //nevertheless the wait method will only look for a PORT_ALERT_SET
+ QPID_LOG(trace, "Sending a port_alert to " << portId);
+ QPID_POSIX_CHECK(::port_alert(portId, PORT_ALERT_SET, POLLHUP,
+ &static_cast<PollerHandle&>(interruptHandle)));
+ }
+};
+
+void Poller::addFd(PollerHandle& handle, Direction dir) {
+ PollerHandlePrivate& eh = *handle.impl;
+ ScopedLock<Mutex> l(eh.lock);
+
+ uint32_t events = 0;
+
+ if (eh.isIdle()) {
+ events = PollerPrivate::directionToPollEvent(dir);
+ } else {
+ assert(eh.isActive());
+ events = eh.events | PollerPrivate::directionToPollEvent(dir);
+ }
+
+ //port_associate can be used to add an association or modify an
+ //existing one
+ QPID_POSIX_CHECK(::port_associate(impl->portId, PORT_SOURCE_FD, (uintptr_t) eh.fd, events, &handle));
+ eh.events = events;
+ eh.setActive();
+ QPID_LOG(trace, "Poller::addFd(handle=" << &handle
+ << "[" << typeid(&handle).name()
+ << "], fd=" << eh.fd << ")");
+}
+
+void Poller::delFd(PollerHandle& handle) {
+ PollerHandlePrivate& eh = *handle.impl;
+ ScopedLock<Mutex> l(eh.lock);
+ assert(!eh.isIdle());
+ int rc = ::port_dissociate(impl->portId, PORT_SOURCE_FD, (uintptr_t) eh.fd);
+ //Allow closing an invalid fd, allowing users to close fd before
+ //doing delFd()
+ if (rc == -1 && errno != EBADFD) {
+ QPID_POSIX_CHECK(rc);
+ }
+ eh.setIdle();
+ QPID_LOG(trace, "Poller::delFd(handle=" << &handle
+ << ", fd=" << eh.fd << ")");
+}
+
+// modFd is equivalent to delFd followed by addFd
+void Poller::modFd(PollerHandle& handle, Direction dir) {
+ PollerHandlePrivate& eh = *handle.impl;
+ ScopedLock<Mutex> l(eh.lock);
+ assert(!eh.isIdle());
+
+ eh.events = PollerPrivate::directionToPollEvent(dir);
+
+ //If fd is already associated, events and user arguments are updated
+ //So, no need to check if fd is already associated
+ QPID_POSIX_CHECK(::port_associate(impl->portId, PORT_SOURCE_FD, (uintptr_t) eh.fd, eh.events, &handle));
+ eh.setActive();
+ QPID_LOG(trace, "Poller::modFd(handle=" << &handle
+ << ", fd=" << eh.fd << ")");
+}
+
+void Poller::rearmFd(PollerHandle& handle) {
+ PollerHandlePrivate& eh = *handle.impl;
+ ScopedLock<Mutex> l(eh.lock);
+ assert(eh.isInactive());
+
+ QPID_POSIX_CHECK(::port_associate(impl->portId, PORT_SOURCE_FD, (uintptr_t) eh.fd, eh.events, &handle));
+ eh.setActive();
+ QPID_LOG(trace, "Poller::rearmdFd(handle=" << &handle
+ << ", fd=" << eh.fd << ")");
+}
+
+void Poller::shutdown() {
+ //Allow sloppy code to shut us down more than once
+ if (impl->isShutdown)
+ return;
+
+ impl->isShutdown = true;
+ impl->interrupt();
+}
+
+bool Poller::hasShutdown()
+{
+ return impl->isShutdown;
+}
+
+bool Poller::interrupt(PollerHandle& handle) {
+ PollerPrivate::InterruptHandle& ih = impl->interruptHandle;
+ PollerHandlePrivate& eh = *static_cast<PollerHandle&>(ih).impl;
+ ScopedLock<Mutex> l(eh.lock);
+ ih.addHandle(handle);
+ impl->interrupt();
+ eh.setActive();
+ return true;
+}
+
+void Poller::run() {
+ // Make sure we can't be interrupted by signals at a bad time
+ ::sigset_t ss;
+ ::sigfillset(&ss);
+ ::pthread_sigmask(SIG_SETMASK, &ss, 0);
+
+ do {
+ Event event = wait();
+
+ // If can read/write then dispatch appropriate callbacks
+ if (event.handle) {
+ event.process();
+ } else {
+ // Handle shutdown
+ switch (event.type) {
+ case SHUTDOWN:
+ return;
+ default:
+ // This should be impossible
+ assert(false);
+ }
+ }
+ } while (true);
+}
+
+Poller::Event Poller::wait(Duration timeout) {
+ timespec_t tout;
+ timespec_t* ptout = NULL;
+ port_event_t pe;
+
+ AbsTime targetTimeout = (timeout == TIME_INFINITE) ? FAR_FUTURE :
+ AbsTime(now(), timeout);
+
+ if (timeout != TIME_INFINITE) {
+ tout.tv_sec = 0;
+ tout.tv_nsec = timeout;
+ ptout = &tout;
+ }
+
+ do {
+ PollerHandleDeletionManager.markAllUnusedInThisThread();
+ QPID_LOG(trace, "About to enter port_get on " << impl->portId
+ << ". Thread " << pthread_self()
+ << ", timeout=" << timeout);
+
+
+ int rc = ::port_get(impl->portId, &pe, ptout);
+
+ QPID_LOG(trace, "port_get on " << impl->portId
+ << " returned " << rc);
+
+ if (impl->isShutdown) {
+ PollerHandleDeletionManager.markAllUnusedInThisThread();
+ return Event(0, SHUTDOWN);
+ }
+
+ if (rc < 0) {
+ switch (errno) {
+ case EINTR:
+ continue;
+ case ETIME:
+ return Event(0, TIMEOUT);
+ default:
+ QPID_POSIX_CHECK(rc);
+ }
+ } else {
+ PollerHandle* handle = static_cast<PollerHandle*>(pe.portev_user);
+ PollerHandlePrivate& eh = *handle->impl;
+ ScopedLock<Mutex> l(eh.lock);
+
+ if (eh.isActive()) {
+ QPID_LOG(trace, "Handle is active");
+ //We use alert mode to notify interrupts
+ if (pe.portev_source == PORT_SOURCE_ALERT &&
+ handle == &impl->interruptHandle) {
+ QPID_LOG(trace, "Interrupt notified");
+
+ PollerHandle* wrappedHandle = impl->interruptHandle.getHandle();
+
+ if (impl->interruptHandle.queuedHandles()) {
+ impl->interrupt();
+ eh.setActive();
+ } else {
+ eh.setInactive();
+ }
+ return Event(wrappedHandle, INTERRUPTED);
+ }
+
+ if (pe.portev_source == PORT_SOURCE_FD) {
+ QPID_LOG(trace, "About to send handle: " << handle);
+ if (pe.portev_events & POLLHUP) {
+ if (eh.isHungup()) {
+ return Event(handle, DISCONNECTED);
+ }
+ eh.setHungup();
+ } else {
+ eh.setInactive();
+ }
+ QPID_LOG(trace, "Sending event (thread: "
+ << pthread_self() << ") for handle " << handle
+ << ", direction= "
+ << PollerPrivate::pollToDirection(pe.portev_events));
+ return Event(handle, PollerPrivate::pollToDirection(pe.portev_events));
+ }
+ } else if (eh.isDeleted()) {
+ //Remove the handle from the poller
+ int rc = ::port_dissociate(impl->portId, PORT_SOURCE_FD,
+ (uintptr_t) eh.fd);
+ if (rc == -1 && errno != EBADFD) {
+ QPID_POSIX_CHECK(rc);
+ }
+ }
+ }
+
+ if (timeout == TIME_INFINITE) {
+ continue;
+ }
+ if (rc == 0 && now() > targetTimeout) {
+ PollerHandleDeletionManager.markAllUnusedInThisThread();
+ return Event(0, TIMEOUT);
+ }
+ } while (true);
+}
+
+// Concrete constructors
+Poller::Poller() :
+ impl(new PollerPrivate())
+{}
+
+Poller::~Poller() {
+ delete impl;
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/sys/solaris/SystemInfo.cpp b/qpid/cpp/src/qpid/sys/solaris/SystemInfo.cpp
new file mode 100755
index 0000000000..0e754e048b
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/solaris/SystemInfo.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/SystemInfo.h"
+
+#define BSD_COMP
+#include <sys/ioctl.h>
+#include <netdb.h>
+#undef BDS_COMP
+
+
+#include <unistd.h>
+#include <net/if.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdio.h>
+#include <errno.h>
+#include <limits.h>
+#include <procfs.h>
+#include <fcntl.h>
+#include <sys/types.h>
+
+using namespace std;
+
+namespace qpid {
+namespace sys {
+
+long SystemInfo::concurrency() {
+ return sysconf(_SC_NPROCESSORS_ONLN);
+}
+
+bool SystemInfo::getLocalHostname(Address &address) {
+ char name[MAXHOSTNAMELEN];
+ if (::gethostname(name, sizeof(name)) != 0)
+ return false;
+ address.host = name;
+ return true;
+}
+
+static const string LOCALHOST("127.0.0.1");
+static const string TCP("tcp");
+
+void SystemInfo::getSystemId(std::string &osName,
+ std::string &nodeName,
+ std::string &release,
+ std::string &version,
+ std::string &machine) {
+ struct utsname _uname;
+ if (uname (&_uname) == 0) {
+ osName = _uname.sysname;
+ nodeName = _uname.nodename;
+ release = _uname.release;
+ version = _uname.version;
+ machine = _uname.machine;
+ }
+}
+
+uint32_t SystemInfo::getProcessId()
+{
+ return (uint32_t) ::getpid();
+}
+
+uint32_t SystemInfo::getParentProcessId()
+{
+ return (uint32_t) ::getppid();
+}
+
+string SystemInfo::getProcessName()
+{
+ psinfo processInfo;
+ char procfile[PATH_MAX];
+ int fd;
+ string value;
+
+ snprintf(procfile, PATH_MAX, "/proc/%d/psinfo", getProcessId());
+ if ((fd = open(procfile, O_RDONLY)) >= 0) {
+ if (read(fd, (void *) &processInfo, sizeof(processInfo)) == sizeof(processInfo)) {
+ value = processInfo.pr_fname;
+ }
+ }
+ return value;
+}
+
+// Always true. Only Windows has exception cases.
+bool SystemInfo::threadSafeShutdown()
+{
+ return true;
+}
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/ssl/SslSocket.cpp b/qpid/cpp/src/qpid/sys/ssl/SslSocket.cpp
new file mode 100644
index 0000000000..32bc78d22d
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/ssl/SslSocket.cpp
@@ -0,0 +1,379 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/ssl/SslSocket.h"
+#include "qpid/sys/SocketAddress.h"
+#include "qpid/sys/ssl/check.h"
+#include "qpid/sys/ssl/util.h"
+#include "qpid/Exception.h"
+#include "qpid/sys/posix/check.h"
+#include "qpid/sys/posix/PrivatePosix.h"
+#include "qpid/log/Statement.h"
+
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/errno.h>
+#include <poll.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <netdb.h>
+#include <cstdlib>
+#include <string.h>
+#include <iostream>
+
+#include <private/pprio.h>
+#include <nss.h>
+#include <pk11pub.h>
+#include <ssl.h>
+#include <key.h>
+#include <sslerr.h>
+
+#include <boost/format.hpp>
+
+namespace qpid {
+namespace sys {
+namespace ssl {
+
+namespace {
+const std::string DOMAIN_SEPARATOR("@");
+const std::string DC_SEPARATOR(".");
+const std::string DC("DC");
+const std::string DN_DELIMS(" ,=");
+
+std::string getDomainFromSubject(std::string subject)
+{
+ std::string::size_type last = subject.find_first_not_of(DN_DELIMS, 0);
+ std::string::size_type i = subject.find_first_of(DN_DELIMS, last);
+
+ std::string domain;
+ bool nextTokenIsDC = false;
+ while (std::string::npos != i || std::string::npos != last)
+ {
+ std::string token = subject.substr(last, i - last);
+ if (nextTokenIsDC) {
+ if (domain.size()) domain += DC_SEPARATOR;
+ domain += token;
+ nextTokenIsDC = false;
+ } else if (token == DC) {
+ nextTokenIsDC = true;
+ }
+ last = subject.find_first_not_of(DN_DELIMS, i);
+ i = subject.find_first_of(DN_DELIMS, last);
+ }
+ return domain;
+}
+}
+
+SslSocket::SslSocket(const std::string& certName, bool clientAuth) :
+ nssSocket(0), certname(certName), prototype(0), hostnameVerification(true)
+{
+ //configure prototype socket:
+ prototype = SSL_ImportFD(0, PR_NewTCPSocket());
+
+ if (clientAuth) {
+ NSS_CHECK(SSL_OptionSet(prototype, SSL_REQUEST_CERTIFICATE, PR_TRUE));
+ NSS_CHECK(SSL_OptionSet(prototype, SSL_REQUIRE_CERTIFICATE, PR_TRUE));
+ }
+}
+
+/**
+ * This form of the constructor is used with the server-side sockets
+ * returned from accept. Because we use posix accept rather than
+ * PR_Accept, we have to reset the handshake.
+ */
+SslSocket::SslSocket(int fd, PRFileDesc* model) : BSDSocket(fd), nssSocket(0), prototype(0)
+{
+ nssSocket = SSL_ImportFD(model, PR_ImportTCPSocket(fd));
+ NSS_CHECK(SSL_ResetHandshake(nssSocket, PR_TRUE));
+}
+
+void SslSocket::ignoreHostnameVerificationFailure()
+{
+ hostnameVerification = false;
+}
+
+void SslSocket::setNonblocking() const
+{
+ if (!nssSocket) {
+ BSDSocket::setNonblocking();
+ return;
+ }
+ PRSocketOptionData option;
+ option.option = PR_SockOpt_Nonblocking;
+ option.value.non_blocking = true;
+ PR_SetSocketOption(nssSocket, &option);
+}
+
+void SslSocket::setTcpNoDelay() const
+{
+ if (!nssSocket) {
+ BSDSocket::setTcpNoDelay();
+ return;
+ }
+ PRSocketOptionData option;
+ option.option = PR_SockOpt_NoDelay;
+ option.value.no_delay = true;
+ PR_SetSocketOption(nssSocket, &option);
+}
+
+void SslSocket::connect(const SocketAddress& addr) const
+{
+ BSDSocket::connect(addr);
+}
+
+namespace {
+SECStatus bad_certificate(void* arg, PRFileDesc* /*fd*/) {
+ switch (PR_GetError()) {
+ case SSL_ERROR_BAD_CERT_DOMAIN:
+ QPID_LOG(info, "Ignoring hostname verification failure for " << (const char*) arg);
+ return SECSuccess;
+ default:
+ return SECFailure;
+ }
+}
+}
+
+void SslSocket::finishConnect(const SocketAddress& addr) const
+{
+ nssSocket = SSL_ImportFD(0, PR_ImportTCPSocket(fd));
+
+ void* arg;
+ // Use the connection's cert-name if it has one; else use global cert-name
+ if (certname != "") {
+ arg = const_cast<char*>(certname.c_str());
+ } else if (SslOptions::global.certName.empty()) {
+ arg = 0;
+ } else {
+ arg = const_cast<char*>(SslOptions::global.certName.c_str());
+ }
+ NSS_CHECK(SSL_GetClientAuthDataHook(nssSocket, NSS_GetClientAuthData, arg));
+
+ url = addr.getHost();
+ if (!hostnameVerification) {
+ NSS_CHECK(SSL_BadCertHook(nssSocket, bad_certificate, const_cast<char*>(url.data())));
+ }
+ NSS_CHECK(SSL_SetURL(nssSocket, url.data()));
+
+ NSS_CHECK(SSL_ResetHandshake(nssSocket, PR_FALSE));
+ NSS_CHECK(SSL_ForceHandshake(nssSocket));
+}
+
+void SslSocket::close() const
+{
+ if (!nssSocket) {
+ BSDSocket::close();
+ return;
+ }
+ if (fd > 0) {
+ PR_Close(nssSocket);
+ fd = -1;
+ }
+}
+
+int SslSocket::listen(const SocketAddress& sa, int backlog) const
+{
+ //get certificate and key (is this the correct way?)
+ std::string cName( (certname == "") ? "localhost.localdomain" : certname);
+ CERTCertificate *cert = PK11_FindCertFromNickname(const_cast<char*>(cName.c_str()), 0);
+ if (!cert) throw Exception(QPID_MSG("Failed to load certificate '" << cName << "'"));
+ SECKEYPrivateKey *key = PK11_FindKeyByAnyCert(cert, 0);
+ if (!key) throw Exception(QPID_MSG("Failed to retrieve private key from certificate"));
+ NSS_CHECK(SSL_ConfigSecureServer(prototype, cert, key, NSS_FindCertKEAType(cert)));
+ SECKEY_DestroyPrivateKey(key);
+ CERT_DestroyCertificate(cert);
+
+ return BSDSocket::listen(sa, backlog);
+}
+
+Socket* SslSocket::accept() const
+{
+ QPID_LOG(trace, "Accepting SSL connection.");
+ int afd = ::accept(fd, 0, 0);
+ if ( afd >= 0) {
+ return new SslSocket(afd, prototype);
+ } else if (errno == EAGAIN) {
+ return 0;
+ } else {
+ throw QPID_POSIX_ERROR(errno);
+ }
+}
+
+#define SSL_STREAM_MAX_WAIT_ms 20
+#define SSL_STREAM_MAX_RETRIES 2
+
+static bool isSslStream(int afd) {
+ int retries = SSL_STREAM_MAX_RETRIES;
+ unsigned char buf[5] = {};
+
+ do {
+ struct pollfd fd = {afd, POLLIN, 0};
+
+ /*
+ * Note that this is blocking the accept thread, so connections that
+ * send no data can limit the rate at which we can accept new
+ * connections.
+ */
+ if (::poll(&fd, 1, SSL_STREAM_MAX_WAIT_ms) > 0) {
+ errno = 0;
+ int result = recv(afd, buf, sizeof(buf), MSG_PEEK | MSG_DONTWAIT);
+ if (result == sizeof(buf)) {
+ break;
+ }
+ if (errno && errno != EAGAIN) {
+ int err = errno;
+ ::close(afd);
+ throw QPID_POSIX_ERROR(err);
+ }
+ }
+ } while (retries-- > 0);
+
+ if (retries < 0) {
+ return false;
+ }
+
+ /*
+ * SSLv2 Client Hello format
+ * http://www.mozilla.org/projects/security/pki/nss/ssl/draft02.html
+ *
+ * Bytes 0-1: RECORD-LENGTH
+ * Byte 2: MSG-CLIENT-HELLO (1)
+ * Byte 3: CLIENT-VERSION-MSB
+ * Byte 4: CLIENT-VERSION-LSB
+ *
+ * Allowed versions:
+ * 2.0 - SSLv2
+ * 3.0 - SSLv3
+ * 3.1 - TLS 1.0
+ * 3.2 - TLS 1.1
+ * 3.3 - TLS 1.2
+ *
+ * The version sent in the Client-Hello is the latest version supported by
+ * the client. NSS may send version 3.x in an SSLv2 header for
+ * maximum compatibility.
+ */
+ bool isSSL2Handshake = buf[2] == 1 && // MSG-CLIENT-HELLO
+ ((buf[3] == 3 && buf[4] <= 3) || // SSL 3.0 & TLS 1.0-1.2 (v3.1-3.3)
+ (buf[3] == 2 && buf[4] == 0)); // SSL 2
+
+ /*
+ * SSLv3/TLS Client Hello format
+ * RFC 2246
+ *
+ * Byte 0: ContentType (handshake - 22)
+ * Bytes 1-2: ProtocolVersion {major, minor}
+ *
+ * Allowed versions:
+ * 3.0 - SSLv3
+ * 3.1 - TLS 1.0
+ * 3.2 - TLS 1.1
+ * 3.3 - TLS 1.2
+ */
+ bool isSSL3Handshake = buf[0] == 22 && // handshake
+ (buf[1] == 3 && buf[2] <= 3); // SSL 3.0 & TLS 1.0-1.2 (v3.1-3.3)
+
+ return isSSL2Handshake || isSSL3Handshake;
+}
+
+SslMuxSocket::SslMuxSocket(const std::string& certName, bool clientAuth) :
+ SslSocket(certName, clientAuth)
+{
+}
+
+Socket* SslMuxSocket::accept() const
+{
+ int afd = ::accept(fd, 0, 0);
+ if (afd >= 0) {
+ QPID_LOG(trace, "Accepting connection with optional SSL wrapper.");
+ if (isSslStream(afd)) {
+ QPID_LOG(trace, "Accepted SSL connection.");
+ return new SslSocket(afd, prototype);
+ } else {
+ QPID_LOG(trace, "Accepted Plaintext connection.");
+ return new BSDSocket(afd);
+ }
+ } else if (errno == EAGAIN) {
+ return 0;
+ } else {
+ throw QPID_POSIX_ERROR(errno);
+ }
+}
+
+int SslSocket::read(void *buf, size_t count) const
+{
+ return PR_Read(nssSocket, buf, count);
+}
+
+int SslSocket::write(const void *buf, size_t count) const
+{
+ return PR_Write(nssSocket, buf, count);
+}
+
+void SslSocket::setCertName(const std::string& name)
+{
+ certname = name;
+}
+
+
+/** get the bit length of the current cipher's key */
+int SslSocket::getKeyLen() const
+{
+ int enabled = 0;
+ int keySize = 0;
+ SECStatus rc;
+
+ rc = SSL_SecurityStatus( nssSocket,
+ &enabled,
+ NULL,
+ NULL,
+ &keySize,
+ NULL, NULL );
+ if (rc == SECSuccess && enabled) {
+ return keySize;
+ }
+ return 0;
+}
+
+std::string SslSocket::getClientAuthId() const
+{
+ std::string authId;
+ CERTCertificate* cert = SSL_PeerCertificate(nssSocket);
+ if (cert) {
+ char *cn = CERT_GetCommonName(&(cert->subject));
+ if (cn) {
+ authId = std::string(cn);
+ /*
+ * The NSS function CERT_GetDomainComponentName only returns
+ * the last component of the domain name, so we have to parse
+ * the subject manually to extract the full domain.
+ */
+ std::string domain = getDomainFromSubject(cert->subjectName);
+ if (!domain.empty()) {
+ authId += DOMAIN_SEPARATOR;
+ authId += domain;
+ }
+ }
+ CERT_DestroyCertificate(cert);
+ }
+ return authId;
+}
+
+}}} // namespace qpid::sys::ssl
diff --git a/qpid/cpp/src/qpid/sys/ssl/SslSocket.h b/qpid/cpp/src/qpid/sys/ssl/SslSocket.h
new file mode 100644
index 0000000000..2407a1bf4b
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/ssl/SslSocket.h
@@ -0,0 +1,112 @@
+#ifndef _sys_ssl_Socket_h
+#define _sys_ssl_Socket_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/IOHandle.h"
+#include "qpid/sys/posix/BSDSocket.h"
+#include <nspr.h>
+
+#include <string>
+
+struct sockaddr;
+
+namespace qpid {
+namespace sys {
+
+class Duration;
+
+namespace ssl {
+
+class SslSocket : public qpid::sys::BSDSocket
+{
+public:
+ /** Create a socket wrapper for descriptor.
+ *@param certName name of certificate to use to identify the socket
+ */
+ SslSocket(const std::string& certName = "", bool clientAuth = false);
+
+ /** Proceed with connect inspite of hostname verifcation failures*/
+ void ignoreHostnameVerificationFailure();
+
+ /** Set socket non blocking */
+ void setNonblocking() const;
+
+ /** Set tcp-nodelay */
+ void setTcpNoDelay() const;
+
+ /** Set SSL cert-name. Allows the cert-name to be set per
+ * connection, overriding global cert-name settings from
+ * NSSInit().*/
+ void setCertName(const std::string& certName);
+
+ void connect(const SocketAddress&) const;
+ void finishConnect(const SocketAddress&) const;
+
+ void close() const;
+
+ /** Bind to a port and start listening.
+ *@param port 0 means choose an available port.
+ *@param backlog maximum number of pending connections.
+ *@return The bound port.
+ */
+ int listen(const SocketAddress&, int backlog = 10) const;
+
+ /**
+ * Accept a connection from a socket that is already listening
+ * and has an incoming connection
+ */
+ virtual Socket* accept() const;
+
+ // TODO The following are raw operations, maybe they need better wrapping?
+ int read(void *buf, size_t count) const;
+ int write(const void *buf, size_t count) const;
+
+ int getKeyLen() const;
+ std::string getClientAuthId() const;
+
+protected:
+ mutable PRFileDesc* nssSocket;
+ std::string certname;
+ mutable std::string url;
+
+ /**
+ * 'model' socket, with configuration to use when importing
+ * accepted sockets for use as ssl sockets. Set on listen(), used
+ * in accept to pass through to newly created socket instances.
+ */
+ mutable PRFileDesc* prototype;
+ bool hostnameVerification;
+
+ SslSocket(int fd, PRFileDesc* model);
+ friend class SslMuxSocket; // Needed for this constructor
+};
+
+class SslMuxSocket : public SslSocket
+{
+public:
+ SslMuxSocket(const std::string& certName = "", bool clientAuth = false);
+ Socket* accept() const;
+};
+
+}}}
+#endif /*!_sys_ssl_Socket_h*/
diff --git a/qpid/cpp/src/qpid/sys/ssl/check.cpp b/qpid/cpp/src/qpid/sys/ssl/check.cpp
new file mode 100644
index 0000000000..72a2e265bd
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/ssl/check.cpp
@@ -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.
+ *
+ */
+#include "qpid/sys/ssl/check.h"
+#include <secerr.h>
+#include <sslerr.h>
+#include <boost/format.hpp>
+
+using boost::format;
+using boost::str;
+
+namespace qpid {
+namespace sys {
+namespace ssl {
+
+ErrorString::ErrorString() : code(PR_GetError()), buffer(new char[PR_GetErrorTextLength()]), used(PR_GetErrorText(buffer)) {}
+
+ErrorString::~ErrorString()
+{
+ delete[] buffer;
+}
+
+std::string ErrorString::getString() const
+{
+ std::string msg = std::string(buffer, used);
+ if (!used) {
+ //seems most of the NSPR/NSS errors don't have text set for
+ //them, add a few specific ones in here. (TODO: more complete
+ //list?):
+ return getErrorString(code);
+ } else {
+ return str(format("%1% [%2%]") % msg % code);
+ }
+}
+
+std::string getErrorString(int code)
+{
+ std::string msg;
+ switch (code) {
+ case SSL_ERROR_EXPORT_ONLY_SERVER: msg = "Unable to communicate securely. Peer does not support high-grade encryption."; break;
+ case SSL_ERROR_US_ONLY_SERVER: msg = "Unable to communicate securely. Peer requires high-grade encryption which is not supported."; break;
+ case SSL_ERROR_NO_CYPHER_OVERLAP: msg = "Cannot communicate securely with peer: no common encryption algorithm(s)."; break;
+ case SSL_ERROR_NO_CERTIFICATE: msg = "Unable to find the certificate or key necessary for authentication."; break;
+ case SSL_ERROR_BAD_CERTIFICATE: msg = "Unable to communicate securely with peer: peers's certificate was rejected."; break;
+ case SSL_ERROR_UNSUPPORTED_CERTIFICATE_TYPE: msg = "Unsupported certificate type."; break;
+ case SSL_ERROR_WRONG_CERTIFICATE: msg = "Client authentication failed: private key in key database does not correspond to public key in certificate database."; break;
+ case SSL_ERROR_BAD_CERT_DOMAIN: msg = "Unable to communicate securely with peer: requested domain name does not match the server's certificate."; break;
+ case SSL_ERROR_BAD_CERT_ALERT: msg = "SSL peer cannot verify your certificate."; break;
+ case SSL_ERROR_REVOKED_CERT_ALERT: msg = "SSL peer rejected your certificate as revoked."; break;
+ case SSL_ERROR_EXPIRED_CERT_ALERT: msg = "SSL peer rejected your certificate as expired."; break;
+
+ case PR_DIRECTORY_LOOKUP_ERROR: msg = "A directory lookup on a network address has failed"; break;
+ case PR_CONNECT_RESET_ERROR: msg = "TCP connection reset by peer"; break;
+ case PR_END_OF_FILE_ERROR: msg = "Encountered end of file"; break;
+ case SEC_ERROR_EXPIRED_CERTIFICATE: msg = "Peer's certificate has expired"; break;
+ default: msg = (code < -6000) ? "NSS error" : "NSPR error"; break;
+ }
+ return str(format("%1% [%2%]") % msg % code);
+}
+
+std::ostream& operator<<(std::ostream& out, const ErrorString& err)
+{
+ out << err.getString();
+ return out;
+}
+
+
+}}} // namespace qpid::sys::ssl
diff --git a/qpid/cpp/src/qpid/sys/ssl/check.h b/qpid/cpp/src/qpid/sys/ssl/check.h
new file mode 100644
index 0000000000..28d3c74ad0
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/ssl/check.h
@@ -0,0 +1,57 @@
+#ifndef QPID_SYS_SSL_CHECK_H
+#define QPID_SYS_SSL_CHECK_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/Msg.h"
+
+#include <iostream>
+#include <string>
+#include <nspr.h>
+#include <nss.h>
+
+namespace qpid {
+namespace sys {
+namespace ssl {
+
+std::string getErrorString(int code);
+
+class ErrorString
+{
+ public:
+ ErrorString();
+ ~ErrorString();
+ std::string getString() const;
+ private:
+ const int code;
+ char* const buffer;
+ const size_t used;
+};
+
+std::ostream& operator<<(std::ostream& out, const ErrorString& err);
+
+}}} // namespace qpid::sys::ssl
+
+
+#define NSS_CHECK(value) if (value != SECSuccess) { throw Exception(QPID_MSG("Failed: " << qpid::sys::ssl::ErrorString())); }
+#define PR_CHECK(value) if (value != PR_SUCCESS) { throw Exception(QPID_MSG("Failed: " << qpid::sys::ssl::ErrorString())); }
+
+#endif /*!QPID_SYS_SSL_CHECK_H*/
diff --git a/qpid/cpp/src/qpid/sys/ssl/util.cpp b/qpid/cpp/src/qpid/sys/ssl/util.cpp
new file mode 100644
index 0000000000..9f5493cbbf
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/ssl/util.cpp
@@ -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.
+ *
+ */
+#include "qpid/sys/ssl/util.h"
+#include "qpid/sys/ssl/check.h"
+#include "qpid/Exception.h"
+#include "qpid/sys/SystemInfo.h"
+
+#include <unistd.h>
+#include <nspr.h>
+#include <nss.h>
+#include <pk11pub.h>
+#include <ssl.h>
+
+#include <iostream>
+#include <fstream>
+
+namespace qpid {
+namespace sys {
+namespace ssl {
+
+static const std::string LOCALHOST("127.0.0.1");
+
+std::string defaultCertName()
+{
+ Address address;
+ if (SystemInfo::getLocalHostname(address)) {
+ return address.host;
+ } else {
+ return LOCALHOST;
+ }
+}
+
+SslOptions::SslOptions() : qpid::Options("SSL Settings"),
+ certName(defaultCertName()),
+ exportPolicy(false)
+{
+ addOptions()
+ ("ssl-use-export-policy", optValue(exportPolicy), "Use NSS export policy")
+ ("ssl-cert-password-file", optValue(certPasswordFile, "PATH"), "File containing password to use for accessing certificate database")
+ ("ssl-cert-db", optValue(certDbPath, "PATH"), "Path to directory containing certificate database")
+ ("ssl-cert-name", optValue(certName, "NAME"), "Name of the certificate to use");
+}
+
+SslOptions& SslOptions::operator=(const SslOptions& o)
+{
+ certDbPath = o.certDbPath;
+ certName = o.certName;
+ certPasswordFile = o.certPasswordFile;
+ exportPolicy = o.exportPolicy;
+ return *this;
+}
+
+char* promptForPassword(PK11SlotInfo*, PRBool retry, void*)
+{
+ if (retry) return 0;
+ //TODO: something else?
+ return PL_strdup(getpass("Please enter the password for accessing the certificate database:"));
+}
+
+SslOptions SslOptions::global;
+
+char* readPasswordFromFile(PK11SlotInfo*, PRBool retry, void*)
+{
+ const std::string& passwordFile = SslOptions::global.certPasswordFile;
+ if (retry || passwordFile.empty()) return 0;
+ std::ifstream file(passwordFile.c_str());
+ if (!file) return 0;
+
+ std::string password;
+ getline(file, password);
+ return PL_strdup(password.c_str());
+}
+
+void initNSS(const SslOptions& options, bool server)
+{
+ SslOptions::global = options;
+ if (options.certPasswordFile.empty()) {
+ PK11_SetPasswordFunc(promptForPassword);
+ } else {
+ PK11_SetPasswordFunc(readPasswordFromFile);
+ }
+ NSS_CHECK(NSS_Init(options.certDbPath.c_str()));
+ if (options.exportPolicy) {
+ NSS_CHECK(NSS_SetExportPolicy());
+ } else {
+ NSS_CHECK(NSS_SetDomesticPolicy());
+ }
+ if (server) {
+ //use defaults for all args, TODO: may want to make this configurable
+ SSL_ConfigServerSessionIDCache(0, 0, 0, 0);
+ }
+
+ // disable SSLv2 and SSLv3 versions of the protocol - they are
+ // no longer considered secure
+ SSLVersionRange vrange;
+ const uint16_t tlsv1 = 0x0301; // Protocol version for TLSv1.0
+ NSS_CHECK(SSL_VersionRangeGetDefault(ssl_variant_stream, &vrange));
+ if (vrange.min < tlsv1) {
+ vrange.min = tlsv1;
+ NSS_CHECK(SSL_VersionRangeSetDefault(ssl_variant_stream, &vrange));
+ }
+}
+
+void shutdownNSS()
+{
+ NSS_Shutdown();
+}
+
+}}} // namespace qpid::sys::ssl
diff --git a/qpid/cpp/src/qpid/sys/ssl/util.h b/qpid/cpp/src/qpid/sys/ssl/util.h
new file mode 100644
index 0000000000..f34adab7be
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/ssl/util.h
@@ -0,0 +1,50 @@
+#ifndef QPID_SYS_SSL_UTIL_H
+#define QPID_SYS_SSL_UTIL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/Options.h"
+#include <string>
+
+namespace qpid {
+namespace sys {
+namespace ssl {
+
+struct SslOptions : qpid::Options
+{
+ static SslOptions global;
+
+ std::string certDbPath;
+ std::string certName;
+ std::string certPasswordFile;
+ bool exportPolicy;
+
+ SslOptions();
+ SslOptions& operator=(const SslOptions&);
+};
+
+void initNSS(const SslOptions& options, bool server = false);
+void shutdownNSS();
+
+}}} // namespace qpid::sys::ssl
+
+#endif /*!QPID_SYS_SSL_UTIL_H*/
diff --git a/qpid/cpp/src/qpid/sys/unordered_map.h b/qpid/cpp/src/qpid/sys/unordered_map.h
new file mode 100644
index 0000000000..1b27770804
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/unordered_map.h
@@ -0,0 +1,41 @@
+#ifndef _sys_unordered_map_h
+#define _sys_unordered_map_h
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// unordered_map include path is platform specific
+
+#if defined(_MSC_VER) || defined(_LIBCPP_VERSION) || __cplusplus >= 201103L
+# include <unordered_map>
+#elif defined(__SUNPRO_CC) || defined(__IBMCPP__)
+# include <boost/tr1/unordered_map.hpp>
+#else
+# include <tr1/unordered_map>
+#endif /* _MSC_VER */
+namespace qpid {
+namespace sys {
+#if defined(_LIBCPP_VERSION) || __cplusplus >= 201103L
+ using std::unordered_map;
+#else
+ using std::tr1::unordered_map;
+#endif
+}}
+
+
+#endif /* _sys_unordered_map_h */
diff --git a/qpid/cpp/src/qpid/sys/urlAdd.h b/qpid/cpp/src/qpid/sys/urlAdd.h
new file mode 100644
index 0000000000..e78c5d586c
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/urlAdd.h
@@ -0,0 +1,62 @@
+#ifndef QPID_SYS_URLADD_H
+#define QPID_SYS_URLADD_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <qpid/Url.h>
+#include <boost/bind.hpp>
+#include <algorithm>
+
+namespace qpid {
+namespace sys {
+
+/** Add addr to url if it is not already present. */
+inline void urlAddAddress(Url& url, const Address& addr) {
+ if (std::find(url.begin(), url.end(), addr) == url.end()) url.push_back(addr);
+}
+
+/** Add all addresses in more that are not already in url to url */
+inline void urlAddUrl(Url& url, const Url& more) {
+ for_each(more.begin(), more.end(), boost::bind(&urlAddAddress, boost::ref(url), _1));
+}
+
+/** Convert str to a Url and do urlAddUrl. */
+inline void urlAddString(Url& url, const std::string& str, const std::string& defaultProtocol) {
+ urlAddUrl(url, Url(str, defaultProtocol));
+}
+
+/** For each URL in a range, do urlAddUrl */
+template <class UrlIterator>
+void urlAddUrls(Url& url, UrlIterator i, UrlIterator j) {
+ for_each(i, j, boost::bind(&urlAddUrl, boost::ref(url), _1));
+}
+
+/** For each string in a range, do urlAddUrl(Url(string)) */
+template <class StringIterator>
+void urlAddStrings(Url& url, StringIterator i, StringIterator j,
+ const std::string& defaultProtocol) {
+ for_each(i, j, boost::bind(&urlAddString, boost::ref(url), _1, defaultProtocol));
+}
+
+}} // namespace qpid::sys
+
+#endif /*!QPID_SYS_URLADD_H*/
diff --git a/qpid/cpp/src/qpid/sys/uuid.h b/qpid/cpp/src/qpid/sys/uuid.h
new file mode 100644
index 0000000000..6c32c059c4
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/uuid.h
@@ -0,0 +1,37 @@
+#ifndef _sys_uuid_h
+#define _sys_uuid_h
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/types/ImportExport.h"
+
+#include "qpid/sys/IntegerTypes.h"
+
+namespace qpid {
+namespace sys {
+
+const int UuidSize = 16;
+typedef uint8_t uuid_t[UuidSize];
+
+extern "C"
+QPID_TYPES_EXTERN void uuid_generate (uint8_t out[UuidSize]);
+
+}}
+
+#endif /* _sys_uuid_h */
diff --git a/qpid/cpp/src/qpid/sys/windows/AsynchIO.cpp b/qpid/cpp/src/qpid/sys/windows/AsynchIO.cpp
new file mode 100644
index 0000000000..d65aad1304
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/AsynchIO.cpp
@@ -0,0 +1,713 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/windows/AsynchIoResult.h"
+#include "qpid/sys/windows/IoHandlePrivate.h"
+#include "qpid/sys/AsynchIO.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/Socket.h"
+#include "qpid/sys/windows/WinSocket.h"
+#include "qpid/sys/SecuritySettings.h"
+#include "qpid/sys/SocketAddress.h"
+#include "qpid/sys/Poller.h"
+#include "qpid/sys/Thread.h"
+#include "qpid/sys/Time.h"
+#include "qpid/log/Statement.h"
+
+#include "qpid/sys/windows/check.h"
+#include "qpid/sys/windows/mingw32_compat.h"
+
+#include <boost/thread/once.hpp>
+
+#include <queue>
+#include <winsock2.h>
+#include <mswsock.h>
+#include <windows.h>
+
+#include <boost/bind.hpp>
+#include <boost/shared_array.hpp>
+#include "qpid/sys/windows/AsynchIO.h"
+
+namespace {
+
+ typedef qpid::sys::ScopedLock<qpid::sys::Mutex> QLock;
+
+/*
+ * The function pointers for AcceptEx and ConnectEx need to be looked up
+ * at run time.
+ */
+const LPFN_ACCEPTEX lookUpAcceptEx(const qpid::sys::IOHandle& io) {
+ SOCKET h = io.fd;
+ GUID guidAcceptEx = WSAID_ACCEPTEX;
+ DWORD dwBytes = 0;
+ LPFN_ACCEPTEX fnAcceptEx;
+ WSAIoctl(h,
+ SIO_GET_EXTENSION_FUNCTION_POINTER,
+ &guidAcceptEx,
+ sizeof(guidAcceptEx),
+ &fnAcceptEx,
+ sizeof(fnAcceptEx),
+ &dwBytes,
+ NULL,
+ NULL);
+ if (fnAcceptEx == 0)
+ throw qpid::Exception(QPID_MSG("Failed to look up AcceptEx"));
+ return fnAcceptEx;
+}
+
+}
+
+namespace qpid {
+namespace sys {
+namespace windows {
+
+/*
+ * Asynch Acceptor
+ *
+ */
+AsynchAcceptor::AsynchAcceptor(const Socket& s, Callback callback)
+ : acceptedCallback(callback),
+ socket(s),
+ wSocket(IOHandle(s).fd),
+ fnAcceptEx(lookUpAcceptEx(s)) {
+
+ s.setNonblocking();
+}
+
+AsynchAcceptor::~AsynchAcceptor()
+{
+ socket.close();
+}
+
+void AsynchAcceptor::start(Poller::shared_ptr poller) {
+ PollerHandle ph = PollerHandle(socket);
+ poller->monitorHandle(ph, Poller::INPUT);
+ restart ();
+}
+
+void AsynchAcceptor::restart(void) {
+ DWORD bytesReceived = 0; // Not used, needed for AcceptEx API
+ AsynchAcceptResult *result = new AsynchAcceptResult(acceptedCallback,
+ this,
+ socket);
+ BOOL status;
+ status = fnAcceptEx(wSocket,
+ IOHandle(*result->newSocket).fd,
+ result->addressBuffer,
+ 0,
+ AsynchAcceptResult::SOCKADDRMAXLEN,
+ AsynchAcceptResult::SOCKADDRMAXLEN,
+ &bytesReceived,
+ result->overlapped());
+ QPID_WINDOWS_CHECK_ASYNC_START(status);
+}
+
+
+Socket* createSameTypeSocket(const Socket& sock) {
+ SOCKET socket = IOHandle(sock).fd;
+ // Socket currently has no actual socket attached
+ if (socket == INVALID_SOCKET)
+ return new WinSocket;
+
+ ::sockaddr_storage sa;
+ ::socklen_t salen = sizeof(sa);
+ QPID_WINSOCK_CHECK(::getsockname(socket, (::sockaddr*)&sa, &salen));
+ SOCKET s = ::socket(sa.ss_family, SOCK_STREAM, 0); // Currently only work with SOCK_STREAM
+ if (s == INVALID_SOCKET) throw QPID_WINDOWS_ERROR(WSAGetLastError());
+ return new WinSocket(s);
+}
+
+AsynchAcceptResult::AsynchAcceptResult(AsynchAcceptor::Callback cb,
+ AsynchAcceptor *acceptor,
+ const Socket& lsocket)
+ : callback(cb), acceptor(acceptor),
+ listener(IOHandle(lsocket).fd),
+ newSocket(createSameTypeSocket(lsocket)) {
+}
+
+void AsynchAcceptResult::success(size_t /*bytesTransferred*/) {
+ ::setsockopt (IOHandle(*newSocket).fd,
+ SOL_SOCKET,
+ SO_UPDATE_ACCEPT_CONTEXT,
+ (char*)&listener,
+ sizeof (listener));
+ callback(*(newSocket.release()));
+ acceptor->restart ();
+ delete this;
+}
+
+void AsynchAcceptResult::failure(int /*status*/) {
+ //if (status != WSA_OPERATION_ABORTED)
+ // Can there be anything else? ;
+ delete this;
+}
+
+/*
+ * AsynchConnector does synchronous connects for now... to do asynch the
+ * IocpPoller will need some extension to register an event handle as a
+ * CONNECT-type "direction", the connect completion/result will need an
+ * event handle to associate with the connecting handle. But there's no
+ * time for that right now...
+ */
+AsynchConnector::AsynchConnector(const Socket& sock,
+ const std::string& hname,
+ const std::string& p,
+ ConnectedCallback connCb,
+ FailedCallback failCb) :
+ connCallback(connCb), failCallback(failCb), socket(sock),
+ hostname(hname), port(p)
+{
+}
+
+void AsynchConnector::start(Poller::shared_ptr)
+{
+ try {
+ socket.connect(SocketAddress(hostname, port));
+ socket.setNonblocking();
+ connCallback(socket);
+ } catch(std::exception& e) {
+ if (failCallback)
+ failCallback(socket, -1, std::string(e.what()));
+ socket.close();
+ }
+}
+
+// This can never be called in the current windows code as connect
+// is blocking and requestCallback only makes sense if connect is
+// non-blocking with the results returned via a poller callback.
+void AsynchConnector::requestCallback(RequestCallback rCb)
+{
+}
+
+} // namespace windows
+
+AsynchAcceptor* AsynchAcceptor::create(const Socket& s,
+ Callback callback)
+{
+ return new windows::AsynchAcceptor(s, callback);
+}
+
+AsynchConnector* qpid::sys::AsynchConnector::create(const Socket& s,
+ const std::string& hostname,
+ const std::string& port,
+ ConnectedCallback connCb,
+ FailedCallback failCb)
+{
+ return new windows::AsynchConnector(s,
+ hostname,
+ port,
+ connCb,
+ failCb);
+}
+
+
+/*
+ * Asynch reader/writer
+ */
+
+namespace windows {
+
+// This is used to encapsulate pure callbacks into a handle
+class CallbackHandle : public IOHandle {
+public:
+ CallbackHandle(AsynchIoResult::Completer completeCb,
+ AsynchIO::RequestCallback reqCb = 0) :
+ IOHandle(INVALID_SOCKET, completeCb, reqCb)
+ {}
+};
+
+AsynchIO::AsynchIO(const Socket& s,
+ ReadCallback rCb,
+ EofCallback eofCb,
+ DisconnectCallback disCb,
+ ClosedCallback cCb,
+ BuffersEmptyCallback eCb,
+ IdleCallback iCb) :
+
+ readCallback(rCb),
+ eofCallback(eofCb),
+ disCallback(disCb),
+ closedCallback(cCb),
+ emptyCallback(eCb),
+ idleCallback(iCb),
+ socket(s),
+ bufferCount(BufferCount),
+ opsInProgress(0),
+ writeInProgress(false),
+ readInProgress(false),
+ queuedDelete(false),
+ queuedClose(false),
+ working(false) {
+}
+
+AsynchIO::~AsynchIO() {
+}
+
+void AsynchIO::queueForDeletion() {
+ {
+ ScopedLock<Mutex> l(completionLock);
+ assert(!queuedDelete);
+ queuedDelete = true;
+ if (working || opsInProgress > 0) {
+ QPID_LOG(info, "Delete AsynchIO queued; ops in progress");
+ // AsynchIOHandler calls this then deletes itself; don't do any more
+ // callbacks.
+ readCallback = 0;
+ eofCallback = 0;
+ disCallback = 0;
+ closedCallback = 0;
+ emptyCallback = 0;
+ idleCallback = 0;
+ return;
+ }
+ }
+ delete this;
+}
+
+void AsynchIO::start(Poller::shared_ptr poller0) {
+ PollerHandle ph = PollerHandle(socket);
+ poller = poller0;
+ poller->monitorHandle(ph, Poller::INPUT);
+ if (writeQueue.size() > 0) // Already have data queued for write
+ notifyPendingWrite();
+ startReading();
+}
+
+uint32_t AsynchIO::getBufferCount(void) { return bufferCount; }
+
+void AsynchIO::setBufferCount(uint32_t count) { bufferCount = count; }
+
+
+void AsynchIO::createBuffers(uint32_t size) {
+ // Allocate all the buffer memory at once
+ bufferMemory.reset(new char[size*bufferCount]);
+
+ // Create the Buffer structs in a vector
+ // And push into the buffer queue
+ buffers.reserve(bufferCount);
+ for (uint32_t i = 0; i < bufferCount; i++) {
+ buffers.push_back(BufferBase(&bufferMemory[i*size], size));
+ queueReadBuffer(&buffers[i]);
+ }
+}
+
+void AsynchIO::queueReadBuffer(AsynchIO::BufferBase* buff) {
+ assert(buff);
+ buff->dataStart = 0;
+ buff->dataCount = 0;
+ QLock l(bufferQueueLock);
+ bufferQueue.push_back(buff);
+}
+
+void AsynchIO::unread(AsynchIO::BufferBase* buff) {
+ assert(buff);
+ buff->squish();
+ QLock l(bufferQueueLock);
+ bufferQueue.push_front(buff);
+}
+
+void AsynchIO::queueWrite(AsynchIO::BufferBase* buff) {
+ assert(buff);
+ QLock l(bufferQueueLock);
+ writeQueue.push_back(buff);
+ if (!writeInProgress)
+ notifyPendingWrite();
+}
+
+void AsynchIO::notifyPendingWrite() {
+ // This method is generally called from a processing thread; transfer
+ // work on this to an I/O thread. Much of the upper layer code assumes
+ // that all I/O-related things happen in an I/O thread.
+ if (poller == 0) // Not really going yet...
+ return;
+
+ InterlockedIncrement(&opsInProgress);
+ PollerHandle ph(CallbackHandle(boost::bind(&AsynchIO::completion, this, _1)));
+ poller->monitorHandle(ph, Poller::OUTPUT);
+}
+
+void AsynchIO::queueWriteClose() {
+ {
+ ScopedLock<Mutex> l(completionLock);
+ queuedClose = true;
+ if (working || writeInProgress)
+ // no need to summon an IO thread
+ return;
+ }
+ notifyPendingWrite();
+}
+
+bool AsynchIO::writeQueueEmpty() {
+ QLock l(bufferQueueLock);
+ return writeQueue.size() == 0;
+}
+
+/*
+ * Initiate a read operation. AsynchIO::readComplete() will be
+ * called when the read is complete and data is available.
+ */
+void AsynchIO::startReading() {
+ if (queuedDelete || queuedClose)
+ return;
+
+ // (Try to) get a buffer; look on the front since there may be an
+ // "unread" one there with data remaining from last time.
+ AsynchIO::BufferBase *buff = 0;
+ {
+ QLock l(bufferQueueLock);
+
+ if (!bufferQueue.empty()) {
+ buff = bufferQueue.front();
+ assert(buff);
+ bufferQueue.pop_front();
+ }
+ else {
+ logNoBuffers("startReading");
+ }
+ }
+ if (buff != 0) {
+ int readCount = buff->byteCount - buff->dataCount;
+ AsynchReadResult *result =
+ new AsynchReadResult(boost::bind(&AsynchIO::completion, this, _1),
+ buff,
+ readCount);
+ DWORD bytesReceived = 0, flags = 0;
+ InterlockedIncrement(&opsInProgress);
+ readInProgress = true;
+ int status = WSARecv(IOHandle(socket).fd,
+ const_cast<LPWSABUF>(result->getWSABUF()), 1,
+ &bytesReceived,
+ &flags,
+ result->overlapped(),
+ 0);
+ if (status != 0) {
+ int error = WSAGetLastError();
+ if (error != WSA_IO_PENDING) {
+ result->failure(error);
+ result = 0; // result is invalid here
+ return;
+ }
+ }
+ // On status 0 or WSA_IO_PENDING, completion will handle the rest.
+ }
+ else {
+ notifyBuffersEmpty();
+ }
+ return;
+}
+
+// Queue the specified callback for invocation from an I/O thread.
+void AsynchIO::requestCallback(RequestCallback callback) {
+ // This method is generally called from a processing thread; transfer
+ // work on this to an I/O thread. Much of the upper layer code assumes
+ // that all I/O-related things happen in an I/O thread.
+ if (poller == 0) // Not really going yet...
+ return;
+
+ InterlockedIncrement(&opsInProgress);
+ PollerHandle ph(CallbackHandle(
+ boost::bind(&AsynchIO::completion, this, _1),
+ callback));
+ poller->monitorHandle(ph, Poller::INPUT);
+}
+
+/**
+ * Return a queued buffer if there are enough to spare.
+ */
+AsynchIO::BufferBase* AsynchIO::getQueuedBuffer() {
+ QLock l(bufferQueueLock);
+ BufferBase* buff = bufferQueue.empty() ? 0 : bufferQueue.back();
+ // An "unread" buffer is reserved for future read operations (which
+ // take from the front of the queue).
+ if (!buff || (buff->dataCount && bufferQueue.size() == 1)) {
+ if (buff)
+ logNoBuffers("getQueuedBuffer with unread data");
+ else
+ logNoBuffers("getQueuedBuffer with empty queue");
+ return 0;
+ }
+ assert(buff->dataCount == 0);
+ bufferQueue.pop_back();
+ return buff;
+}
+
+void AsynchIO::notifyEof(void) {
+ if (eofCallback)
+ eofCallback(*this);
+}
+
+void AsynchIO::notifyDisconnect(void) {
+ if (disCallback) {
+ DisconnectCallback dcb = disCallback;
+ closedCallback = 0;
+ disCallback = 0;
+ dcb(*this);
+ // May have just been deleted.
+ return;
+ }
+}
+
+void AsynchIO::notifyClosed(void) {
+ if (closedCallback) {
+ ClosedCallback ccb = closedCallback;
+ closedCallback = 0;
+ disCallback = 0;
+ ccb(*this, socket);
+ // May have just been deleted.
+ return;
+ }
+}
+
+void AsynchIO::notifyBuffersEmpty(void) {
+ if (emptyCallback)
+ emptyCallback(*this);
+}
+
+void AsynchIO::notifyIdle(void) {
+ if (idleCallback)
+ idleCallback(*this);
+}
+
+/*
+ * Asynch reader/writer using overlapped I/O
+ */
+
+void AsynchIO::startWrite(AsynchIO::BufferBase* buff) {
+ writeInProgress = true;
+ InterlockedIncrement(&opsInProgress);
+ AsynchWriteResult *result =
+ new AsynchWriteResult(boost::bind(&AsynchIO::completion, this, _1),
+ buff,
+ buff->dataCount);
+ DWORD bytesSent = 0;
+ int status = WSASend(IOHandle(socket).fd,
+ const_cast<LPWSABUF>(result->getWSABUF()), 1,
+ &bytesSent,
+ 0,
+ result->overlapped(),
+ 0);
+ if (status != 0) {
+ int error = WSAGetLastError();
+ if (error != WSA_IO_PENDING) {
+ result->failure(error); // Also decrements in-progress count
+ result = 0; // result is invalid here
+ return;
+ }
+ }
+ // On status 0 or WSA_IO_PENDING, completion will handle the rest.
+ return;
+}
+
+/*
+ * Close the socket and callback to say we've done it
+ */
+void AsynchIO::close(void) {
+ socket.close();
+ notifyClosed();
+}
+
+SecuritySettings AsynchIO::getSecuritySettings() {
+ SecuritySettings settings;
+ settings.ssf = socket.getKeyLen();
+ settings.authid = socket.getClientAuthId();
+ return settings;
+}
+
+void AsynchIO::readComplete(AsynchReadResult *result) {
+ int status = result->getStatus();
+ size_t bytes = result->getTransferred();
+ readInProgress = false;
+ if (status == 0 && bytes > 0) {
+ if (readCallback)
+ readCallback(*this, result->getBuff());
+ startReading();
+ }
+ else {
+ // No data read, so put the buffer back. It may be partially filled,
+ // so "unread" it back to the front of the queue.
+ unread(result->getBuff());
+ if (queuedClose) {
+ return; // Expected from cancelRead()
+ }
+ notifyEof();
+ if (status != 0)
+ {
+ notifyDisconnect();
+ }
+ }
+}
+
+/*
+ * NOTE - this completion is called for completed writes and also when
+ * a write is desired. The difference is in the buff - if a write is desired
+ * the buff is 0.
+ */
+void AsynchIO::writeComplete(AsynchWriteResult *result) {
+ int status = result->getStatus();
+ size_t bytes = result->getTransferred();
+ AsynchIO::BufferBase *buff = result->getBuff();
+ if (buff != 0) {
+ writeInProgress = false;
+ if (status == 0 && bytes > 0) {
+ if (bytes < result->getRequested()) // Still more to go; resubmit
+ startWrite(buff);
+ else
+ queueReadBuffer(buff); // All done; back to the pool
+ }
+ else {
+ // An error... if it's a connection close, ignore it - it will be
+ // noticed and handled on a read completion any moment now.
+ // What to do with real error??? Save the Buffer? TBD.
+ queueReadBuffer(buff); // All done; back to the pool
+ }
+ }
+
+ // If there are no writes outstanding, check for more writes to initiate
+ // (either queued or via idle). The opsInProgress count is handled in
+ // completion()
+ if (!writeInProgress) {
+ bool writing = false;
+ {
+ QLock l(bufferQueueLock);
+ if (writeQueue.size() > 0) {
+ buff = writeQueue.front();
+ assert(buff);
+ writeQueue.pop_front();
+ startWrite(buff);
+ writing = true;
+ }
+ }
+ if (!writing && !queuedClose) {
+ notifyIdle();
+ }
+ }
+ return;
+}
+
+void AsynchIO::completion(AsynchIoResult *result) {
+ bool closing = false;
+ bool deleting = false;
+ {
+ ScopedLock<Mutex> l(completionLock);
+ if (working) {
+ completionQueue.push(result);
+ return;
+ }
+
+ // First thread in with something to do; note we're working then keep
+ // handling completions.
+ working = true;
+ while (result != 0) {
+ // New scope to unlock temporarily.
+ {
+ ScopedUnlock<Mutex> ul(completionLock);
+ AsynchReadResult *r = dynamic_cast<AsynchReadResult*>(result);
+ if (r != 0)
+ readComplete(r);
+ else {
+ AsynchWriteResult *w =
+ dynamic_cast<AsynchWriteResult*>(result);
+ if (w != 0)
+ writeComplete(w);
+ else {
+ AsynchCallbackRequest *req =
+ dynamic_cast<AsynchCallbackRequest*>(result);
+ req->reqCallback(*this);
+ }
+ }
+ delete result;
+ result = 0;
+ InterlockedDecrement(&opsInProgress);
+ if (queuedClose && opsInProgress == 1 && readInProgress)
+ cancelRead();
+ }
+ // Lock is held again.
+ if (completionQueue.empty())
+ continue;
+ result = completionQueue.front();
+ completionQueue.pop();
+ }
+ working = false;
+ if (opsInProgress == 0) {
+ closing = queuedClose;
+ deleting = queuedDelete;
+ }
+ }
+ // Lock released; ok to close if ops are done and close requested.
+ // Layer above will call back to queueForDeletion() if it hasn't
+ // already been done. If it already has, go ahead and delete.
+ if (deleting)
+ delete this;
+ else if (closing)
+ // close() may cause a delete; don't trust 'this' on return
+ close();
+}
+
+/*
+ * NOTE - this method must be called in the same context as other completions,
+ * so that the resulting readComplete, and final AsynchIO::close() is serialized
+ * after this method returns.
+ */
+void AsynchIO::cancelRead() {
+ if (queuedDelete)
+ return; // socket already deleted
+ else {
+ ScopedLock<Mutex> l(completionLock);;
+ if (!completionQueue.empty())
+ return; // process it; come back later if necessary
+ }
+ // Cancel outstanding read and force to completion. Otherwise, on a faulty
+ // physical link, the pending read can remain uncompleted indefinitely.
+ // Draining the pending read will result in the official close (and
+ // notifyClosed). CancelIoEX() is the natural choice, but not available in
+ // XP, so we make do with closesocket().
+ socket.close();
+}
+
+/*
+ * Track down cause of unavailable buffer if it recurs: QPID-5033
+ */
+void AsynchIO::logNoBuffers(const char *context) {
+ QPID_LOG(error, "No IO buffers available: " << context <<
+ ". Debug data: " << bufferQueue.size() <<
+ ' ' << writeQueue.size() <<
+ ' ' << completionQueue.size() <<
+ ' ' << opsInProgress <<
+ ' ' << writeInProgress <<
+ ' ' << readInProgress <<
+ ' ' << working);
+}
+
+
+} // namespace windows
+
+AsynchIO* qpid::sys::AsynchIO::create(const Socket& s,
+ AsynchIO::ReadCallback rCb,
+ AsynchIO::EofCallback eofCb,
+ AsynchIO::DisconnectCallback disCb,
+ AsynchIO::ClosedCallback cCb,
+ AsynchIO::BuffersEmptyCallback eCb,
+ AsynchIO::IdleCallback iCb)
+{
+ return new qpid::sys::windows::AsynchIO(s, rCb, eofCb, disCb, cCb, eCb, iCb);
+}
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/windows/AsynchIO.h b/qpid/cpp/src/qpid/sys/windows/AsynchIO.h
new file mode 100644
index 0000000000..a50864b561
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/AsynchIO.h
@@ -0,0 +1,235 @@
+#ifndef _sys_windows_AsynchIO
+#define _sys_windows_AsynchIO
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "AsynchIoResult.h"
+#include "qpid/sys/AsynchIO.h"
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/sys/Poller.h"
+#include "qpid/CommonImportExport.h"
+#include "qpid/sys/Mutex.h"
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/shared_array.hpp>
+#include <winsock2.h>
+#include <mswsock.h>
+#include <windows.h>
+
+// security.h needs to see this to distinguish from kernel use.
+#define SECURITY_WIN32
+#include <security.h>
+#include <Schnlsp.h>
+#undef SECURITY_WIN32
+
+namespace qpid {
+namespace sys {
+namespace windows {
+
+/*
+ * Asynch Acceptor
+ */
+
+class AsynchAcceptor : public qpid::sys::AsynchAcceptor {
+
+ friend class AsynchAcceptResult;
+
+public:
+ AsynchAcceptor(const Socket& s, AsynchAcceptor::Callback callback);
+ ~AsynchAcceptor();
+ void start(Poller::shared_ptr poller);
+
+private:
+ void restart(void);
+
+ AsynchAcceptor::Callback acceptedCallback;
+ const Socket& socket;
+ const SOCKET wSocket;
+ const LPFN_ACCEPTEX fnAcceptEx;
+};
+
+
+class AsynchConnector : public qpid::sys::AsynchConnector {
+private:
+ ConnectedCallback connCallback;
+ FailedCallback failCallback;
+ const Socket& socket;
+ const std::string hostname;
+ const std::string port;
+
+public:
+ AsynchConnector(const Socket& socket,
+ const std::string& hostname,
+ const std::string& port,
+ ConnectedCallback connCb,
+ FailedCallback failCb = 0);
+ void start(Poller::shared_ptr poller);
+ void requestCallback(RequestCallback rCb);
+};
+
+class AsynchIO : public qpid::sys::AsynchIO {
+
+ friend class SslAsynchIO;
+
+public:
+ AsynchIO(const Socket& s,
+ ReadCallback rCb,
+ EofCallback eofCb,
+ DisconnectCallback disCb,
+ ClosedCallback cCb = 0,
+ BuffersEmptyCallback eCb = 0,
+ IdleCallback iCb = 0);
+ ~AsynchIO();
+
+ // Methods inherited from qpid::sys::AsynchIO
+
+ /**
+ * Notify the object is should delete itself as soon as possible.
+ */
+ virtual void queueForDeletion();
+
+ /// Take any actions needed to prepare for working with the poller.
+ virtual void start(Poller::shared_ptr poller);
+ virtual void createBuffers(uint32_t size);
+ virtual void queueReadBuffer(BufferBase* buff);
+ virtual void unread(BufferBase* buff);
+ virtual void queueWrite(BufferBase* buff);
+ virtual void notifyPendingWrite();
+ virtual void queueWriteClose();
+ virtual bool writeQueueEmpty();
+ virtual void requestCallback(RequestCallback);
+
+ /**
+ * getQueuedBuffer returns a buffer from the buffer queue, if one is
+ * available.
+ *
+ * @retval Pointer to BufferBase buffer; 0 if none is available.
+ */
+ virtual BufferBase* getQueuedBuffer();
+
+ virtual SecuritySettings getSecuritySettings(void);
+
+private:
+ ReadCallback readCallback;
+ EofCallback eofCallback;
+ DisconnectCallback disCallback;
+ ClosedCallback closedCallback;
+ BuffersEmptyCallback emptyCallback;
+ IdleCallback idleCallback;
+ const Socket& socket;
+ Poller::shared_ptr poller;
+ uint32_t bufferCount;
+
+ std::deque<BufferBase*> bufferQueue;
+ std::deque<BufferBase*> writeQueue;
+ /* The MSVC-supplied deque is not thread-safe; keep locks to serialize
+ * access to the buffer queue and write queue.
+ */
+ Mutex bufferQueueLock;
+ std::vector<BufferBase> buffers;
+ boost::shared_array<char> bufferMemory;
+
+ // Number of outstanding I/O operations.
+ volatile LONG opsInProgress;
+ // Is there a write in progress?
+ volatile bool writeInProgress;
+ // Or a read?
+ volatile bool readInProgress;
+ // Deletion requested, but there are callbacks in progress.
+ volatile bool queuedDelete;
+ // Socket close requested, but there are operations in progress.
+ volatile bool queuedClose;
+
+protected:
+ uint32_t getBufferCount(void);
+ void setBufferCount(uint32_t);
+
+private:
+ // Dispatch events that have completed.
+ void notifyEof(void);
+ void notifyDisconnect(void);
+ void notifyClosed(void);
+ void notifyBuffersEmpty(void);
+ void notifyIdle(void);
+
+ /**
+ * Initiate a write of the specified buffer. There's no callback for
+ * write completion to the AsynchIO object.
+ */
+ void startWrite(AsynchIO::BufferBase* buff);
+
+ void close(void);
+
+ /**
+ * startReading initiates reading, readComplete() is
+ * called when the read completes.
+ */
+ void startReading();
+
+ /**
+ * readComplete is called when a read request is complete.
+ *
+ * @param result Results of the operation.
+ */
+ void readComplete(AsynchReadResult *result);
+
+ /**
+ * writeComplete is called when a write request is complete.
+ *
+ * @param result Results of the operation.
+ */
+ void writeComplete(AsynchWriteResult *result);
+
+ /**
+ * Queue of completions to run. This queue enforces the requirement
+ * from upper layers that only one thread at a time is allowed to act
+ * on any given connection. Once a thread is busy processing a completion
+ * on this object, other threads that dispatch completions queue the
+ * completions here for the in-progress thread to handle when done.
+ * Thus, any threads can dispatch a completion from the IocpPoller, but
+ * this class ensures that actual processing at the connection level is
+ * only on one thread at a time.
+ */
+ std::queue<AsynchIoResult *> completionQueue;
+ volatile bool working;
+ Mutex completionLock;
+
+ /**
+ * Called when there's a completion to process.
+ */
+ void completion(AsynchIoResult *result);
+
+ /**
+ * Helper function to facilitate the close operation
+ */
+ void cancelRead();
+
+ /**
+ * Log information about buffer depletion, which should never happen.
+ * See QPID-5033.
+ */
+ void logNoBuffers(const char*);
+};
+
+}}} // namespace qpid::sys::windows
+
+#endif // _sys_windows_AsynchIO
diff --git a/qpid/cpp/src/qpid/sys/windows/AsynchIoResult.h b/qpid/cpp/src/qpid/sys/windows/AsynchIoResult.h
new file mode 100755
index 0000000000..27e4c22138
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/AsynchIoResult.h
@@ -0,0 +1,204 @@
+#ifndef _windows_asynchIoResult_h
+#define _windows_asynchIoResult_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/AsynchIO.h"
+#include "qpid/sys/Socket.h"
+#include <memory.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+
+namespace qpid {
+namespace sys {
+namespace windows {
+
+/*
+ * AsynchIoResult defines the class that receives the result of an
+ * asynchronous I/O operation, either send/recv or accept/connect.
+ *
+ * Operation factories should set one of these up before beginning the
+ * operation. Poller knows how to dispatch completion to this class.
+ * This class must be subclassed for needed operations; this class provides
+ * an interface only and cannot be instantiated.
+ *
+ * This class is tied to Windows; it inherits from OVERLAPPED so that the
+ * IocpPoller can cast OVERLAPPED pointers back to AsynchIoResult and call
+ * the completion handler.
+ */
+class AsynchResult : private OVERLAPPED {
+public:
+ LPOVERLAPPED overlapped(void) { return this; }
+ static AsynchResult* from_overlapped(LPOVERLAPPED ol) {
+ return static_cast<AsynchResult*>(ol);
+ }
+ virtual void success (size_t bytesTransferred) {
+ bytes = bytesTransferred;
+ status = 0;
+ complete();
+ }
+ virtual void failure (int error) {
+ bytes = 0;
+ status = error;
+ complete();
+ }
+ size_t getTransferred(void) const { return bytes; }
+ int getStatus(void) const { return status; }
+
+protected:
+ AsynchResult() : bytes(0), status(0)
+ { memset(overlapped(), 0, sizeof(OVERLAPPED)); }
+ ~AsynchResult() {}
+ virtual void complete(void) = 0;
+
+ size_t bytes;
+ int status;
+};
+
+class AsynchAcceptor;
+
+class AsynchAcceptResult : public AsynchResult {
+
+ friend class AsynchAcceptor;
+
+public:
+ AsynchAcceptResult(qpid::sys::AsynchAcceptor::Callback cb,
+ AsynchAcceptor *acceptor,
+ const qpid::sys::Socket& listener);
+ virtual void success (size_t bytesTransferred);
+ virtual void failure (int error);
+
+private:
+ virtual void complete(void) {} // No-op for this class.
+
+ qpid::sys::AsynchAcceptor::Callback callback;
+ AsynchAcceptor *acceptor;
+ SOCKET listener;
+ std::auto_ptr<qpid::sys::Socket> newSocket;
+
+ // AcceptEx needs a place to write the local and remote addresses
+ // when accepting the connection. Place those here; get enough for
+ // IPv6 addresses, even if the socket is IPv4.
+ enum { SOCKADDRMAXLEN = sizeof(sockaddr_in6) + 16,
+ SOCKADDRBUFLEN = 2 * SOCKADDRMAXLEN };
+ char addressBuffer[SOCKADDRBUFLEN];
+};
+
+class AsynchIoResult : public AsynchResult {
+public:
+ typedef boost::function1<void, AsynchIoResult *> Completer;
+
+ virtual ~AsynchIoResult() {}
+ qpid::sys::AsynchIO::BufferBase *getBuff(void) const { return iobuff; }
+ size_t getRequested(void) const { return requested; }
+ const WSABUF *getWSABUF(void) const { return &wsabuf; }
+
+protected:
+ void setBuff (qpid::sys::AsynchIO::BufferBase *buffer) { iobuff = buffer; }
+
+protected:
+ AsynchIoResult(Completer cb,
+ qpid::sys::AsynchIO::BufferBase *buff, size_t length)
+ : completionCallback(cb), iobuff(buff), requested(length) {}
+
+ virtual void complete(void) = 0;
+ WSABUF wsabuf;
+ Completer completionCallback;
+
+private:
+ qpid::sys::AsynchIO::BufferBase *iobuff;
+ size_t requested; // Number of bytes in original I/O request
+};
+
+class AsynchReadResult : public AsynchIoResult {
+
+ // complete() updates buffer then does completion callback.
+ virtual void complete(void) {
+ getBuff()->dataCount += bytes;
+ completionCallback(this);
+ }
+
+public:
+ AsynchReadResult(AsynchIoResult::Completer cb,
+ qpid::sys::AsynchIO::BufferBase *buff,
+ size_t length)
+ : AsynchIoResult(cb, buff, length) {
+ wsabuf.buf = buff->bytes + buff->dataCount;
+ wsabuf.len = length;
+ }
+};
+
+class AsynchWriteResult : public AsynchIoResult {
+
+ // complete() updates buffer then does completion callback.
+ virtual void complete(void) {
+ qpid::sys::AsynchIO::BufferBase *b = getBuff();
+ b->dataStart += bytes;
+ b->dataCount -= bytes;
+ completionCallback(this);
+ }
+
+public:
+ AsynchWriteResult(AsynchIoResult::Completer cb,
+ qpid::sys::AsynchIO::BufferBase *buff,
+ size_t length)
+ : AsynchIoResult(cb, buff, length) {
+ wsabuf.buf = buff ? buff->bytes : 0;
+ wsabuf.len = length;
+ }
+};
+
+class AsynchWriteWanted : public AsynchWriteResult {
+
+ // complete() just does completion callback; no buffers used.
+ virtual void complete(void) {
+ completionCallback(this);
+ }
+
+public:
+ AsynchWriteWanted(AsynchIoResult::Completer cb)
+ : AsynchWriteResult(cb, 0, 0) {
+ wsabuf.buf = 0;
+ wsabuf.len = 0;
+ }
+};
+
+class AsynchCallbackRequest : public AsynchIoResult {
+ // complete() needs to simply call the completionCallback; no buffers.
+ virtual void complete(void) {
+ completionCallback(this);
+ }
+
+public:
+ AsynchCallbackRequest(AsynchIoResult::Completer cb,
+ qpid::sys::AsynchIO::RequestCallback reqCb)
+ : AsynchIoResult(cb, 0, 0), reqCallback(reqCb) {
+ wsabuf.buf = 0;
+ wsabuf.len = 0;
+ }
+
+ qpid::sys::AsynchIO::RequestCallback reqCallback;
+};
+
+}}} // qpid::sys::windows
+
+#endif /*!_windows_asynchIoResult_h*/
diff --git a/qpid/cpp/src/qpid/sys/windows/Condition.h b/qpid/cpp/src/qpid/sys/windows/Condition.h
new file mode 100755
index 0000000000..cd5aebbf09
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/Condition.h
@@ -0,0 +1,77 @@
+#ifndef _sys_windows_Condition_h
+#define _sys_windows_Condition_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/Time.h"
+
+#include <time.h>
+#include <boost/noncopyable.hpp>
+#include <boost/thread/condition.hpp>
+#include <boost/thread/thread_time.hpp>
+#include <windows.h>
+
+namespace qpid {
+namespace sys {
+
+/**
+ * A condition variable for thread synchronization.
+ */
+class Condition : private boost::noncopyable
+{
+ public:
+ inline Condition();
+ inline ~Condition();
+ inline void wait(Mutex&);
+ inline bool wait(Mutex&, const AbsTime& absoluteTime);
+ inline void notify();
+ inline void notifyAll();
+
+ private:
+ boost::condition_variable_any condition;
+};
+
+Condition::Condition() {
+}
+
+Condition::~Condition() {
+}
+
+void Condition::wait(Mutex& mutex) {
+ condition.wait(mutex.mutex);
+}
+
+bool Condition::wait(Mutex& mutex, const AbsTime& absoluteTime){
+ return condition.timed_wait(mutex.mutex, absoluteTime.timepoint);
+}
+
+void Condition::notify(){
+ condition.notify_one();
+}
+
+void Condition::notifyAll(){
+ condition.notify_all();
+}
+
+}}
+#endif /*!_sys_windows_Condition_h*/
diff --git a/qpid/cpp/src/qpid/sys/windows/FileSysDir.cpp b/qpid/cpp/src/qpid/sys/windows/FileSysDir.cpp
new file mode 100644
index 0000000000..5128f0f8d6
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/FileSysDir.cpp
@@ -0,0 +1,90 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/sys/FileSysDir.h"
+#include "qpid/sys/StrError.h"
+#include "qpid/Exception.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <direct.h>
+#include <errno.h>
+#include <windows.h>
+#include <strsafe.h>
+
+
+namespace qpid {
+namespace sys {
+
+bool FileSysDir::exists (void) const
+{
+ const char *cpath = dirPath.c_str ();
+ struct _stat s;
+ if (::_stat(cpath, &s)) {
+ if (errno == ENOENT) {
+ return false;
+ }
+ throw qpid::Exception (strError(errno) +
+ ": Can't check directory: " + dirPath);
+ }
+ if (s.st_mode & _S_IFDIR)
+ return true;
+ throw qpid::Exception(dirPath + " is not a directory");
+}
+
+void FileSysDir::mkdir(void)
+{
+ if (::_mkdir(dirPath.c_str()) == -1)
+ throw Exception ("Can't create directory: " + dirPath);
+}
+
+void FileSysDir::forEachFile(Callback cb) const {
+
+ WIN32_FIND_DATAA findFileData;
+ char szDir[MAX_PATH];
+ size_t dirPathLength;
+ HANDLE hFind = INVALID_HANDLE_VALUE;
+
+ // create dirPath+"\*" in szDir
+ StringCchLength (dirPath.c_str(), MAX_PATH, &dirPathLength);
+
+ if (dirPathLength > (MAX_PATH - 3)) {
+ throw Exception ("Directory path is too long: " + dirPath);
+ }
+
+ StringCchCopy(szDir, MAX_PATH, dirPath.c_str());
+ StringCchCat(szDir, MAX_PATH, TEXT("\\*"));
+
+ // Special work for first file
+ hFind = FindFirstFileA(szDir, &findFileData);
+ if (INVALID_HANDLE_VALUE == hFind) {
+ return;
+ }
+
+ // process everything that isn't a directory
+ do {
+ if (!(findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
+ std::string fileName(dirPath);
+ fileName += "\\";
+ fileName += findFileData.cFileName;
+ cb(fileName);
+ }
+ } while (FindNextFile(hFind, &findFileData) != 0);
+}
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/windows/IOHandle.cpp b/qpid/cpp/src/qpid/sys/windows/IOHandle.cpp
new file mode 100755
index 0000000000..19a1c44875
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/IOHandle.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/windows/IoHandlePrivate.h"
+#include <windows.h>
+
+namespace qpid {
+namespace sys {
+
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/windows/IoHandlePrivate.h b/qpid/cpp/src/qpid/sys/windows/IoHandlePrivate.h
new file mode 100755
index 0000000000..4529ad93ec
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/IoHandlePrivate.h
@@ -0,0 +1,58 @@
+#ifndef _sys_windows_IoHandlePrivate_h
+#define _sys_windows_IoHandlePrivate_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/AsynchIO.h"
+#include "qpid/sys/windows/AsynchIoResult.h"
+#include "qpid/CommonImportExport.h"
+
+#include <winsock2.h>
+
+namespace qpid {
+namespace sys {
+
+// Private fd related implementation details
+// There should be either a valid socket handle or a completer callback.
+// Handle is used to associate with poller's iocp; completer is used to
+// inject a completion that will very quickly trigger a callback to the
+// completer from an I/O thread. If the callback mechanism is used, there
+// can be a RequestCallback set - this carries the callback object through
+// from AsynchIO::requestCallback() through to the I/O completion processing.
+class IOHandle {
+public:
+ IOHandle(SOCKET f = INVALID_SOCKET,
+ windows::AsynchIoResult::Completer cb = 0,
+ AsynchIO::RequestCallback reqCallback = 0) :
+ fd(f),
+ event(cb),
+ cbRequest(reqCallback)
+ {}
+
+ SOCKET fd;
+ windows::AsynchIoResult::Completer event;
+ AsynchIO::RequestCallback cbRequest;
+};
+
+}}
+
+#endif /* _sys_windows_IoHandlePrivate_h */
diff --git a/qpid/cpp/src/qpid/sys/windows/IocpPoller.cpp b/qpid/cpp/src/qpid/sys/windows/IocpPoller.cpp
new file mode 100755
index 0000000000..ecb33c5517
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/IocpPoller.cpp
@@ -0,0 +1,220 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/Poller.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/Dispatcher.h"
+#include "qpid/sys/IOHandle.h"
+#include "qpid/sys/windows/AsynchIoResult.h"
+#include "qpid/sys/windows/IoHandlePrivate.h"
+#include "qpid/sys/windows/check.h"
+
+#include <winsock2.h>
+#include <windows.h>
+
+#include <assert.h>
+#include <vector>
+#include <exception>
+
+namespace qpid {
+namespace sys {
+
+class PollerHandlePrivate {
+ friend class Poller;
+ friend class PollerHandle;
+
+ SOCKET fd;
+ windows::AsynchIoResult::Completer cb;
+ AsynchIO::RequestCallback cbRequest;
+
+ PollerHandlePrivate(SOCKET f,
+ windows::AsynchIoResult::Completer cb0 = 0,
+ AsynchIO::RequestCallback rcb = 0)
+ : fd(f), cb(cb0), cbRequest(rcb)
+ {
+ }
+
+};
+
+PollerHandle::PollerHandle(const IOHandle& h) :
+ impl(new PollerHandlePrivate(h.fd, h.event, h.cbRequest))
+{}
+
+PollerHandle::~PollerHandle() {
+ delete impl;
+}
+
+/**
+ * Concrete implementation of Poller to use the Windows I/O Completion
+ * port (IOCP) facility.
+ */
+class PollerPrivate {
+ friend class Poller;
+
+ const HANDLE iocp;
+
+ // The number of threads running the event loop.
+ volatile LONG threadsRunning;
+
+ // Shutdown request is handled by setting isShutdown and injecting a
+ // well-formed completion event into the iocp.
+ bool isShutdown;
+
+ PollerPrivate() :
+ iocp(::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)),
+ threadsRunning(0),
+ isShutdown(false) {
+ QPID_WINDOWS_CHECK_NULL(iocp);
+ }
+
+ ~PollerPrivate() {
+ // It's probably okay to ignore any errors here as there can't be
+ // data loss
+ ::CloseHandle(iocp);
+ }
+};
+
+void Poller::shutdown() {
+ // Allow sloppy code to shut us down more than once.
+ if (impl->isShutdown)
+ return;
+ impl->isShutdown = true;
+ ULONG_PTR key = 1; // Tell wait() it's a shutdown, not I/O
+ PostQueuedCompletionStatus(impl->iocp, 0, key, 0);
+}
+
+bool Poller::hasShutdown()
+{
+ return impl->isShutdown;
+}
+
+bool Poller::interrupt(PollerHandle&) {
+ return false; // There's no concept of a registered handle.
+}
+
+void Poller::run() {
+ while (!impl->isShutdown) {
+ Poller::Event event = this->wait();
+
+ // Handle shutdown
+ switch (event.type) {
+ case Poller::SHUTDOWN:
+ return;
+ break;
+ case Poller::INVALID: // On any type of success or fail completion
+ break;
+ default:
+ // This should be impossible
+ assert(false);
+ }
+ }
+}
+
+void Poller::monitorHandle(PollerHandle& handle, Direction dir) {
+ HANDLE h = (HANDLE)(handle.impl->fd);
+ if (h != INVALID_HANDLE_VALUE) {
+ HANDLE iocpHandle = ::CreateIoCompletionPort (h, impl->iocp, 0, 0);
+ QPID_WINDOWS_CHECK_NULL(iocpHandle);
+ }
+ else {
+ // INPUT is used to request a callback; OUTPUT to request a write
+ assert(dir == Poller::INPUT || dir == Poller::OUTPUT);
+
+ if (dir == Poller::OUTPUT) {
+ windows::AsynchWriteWanted *result =
+ new windows::AsynchWriteWanted(handle.impl->cb);
+ PostQueuedCompletionStatus(impl->iocp, 0, 0, result->overlapped());
+ }
+ else {
+ windows::AsynchCallbackRequest *result =
+ new windows::AsynchCallbackRequest(handle.impl->cb,
+ handle.impl->cbRequest);
+ PostQueuedCompletionStatus(impl->iocp, 0, 0, result->overlapped());
+ }
+ }
+}
+
+// All no-ops...
+void Poller::unmonitorHandle(PollerHandle& /*handle*/, Direction /*dir*/) {}
+void Poller::registerHandle(PollerHandle& /*handle*/) {}
+void Poller::unregisterHandle(PollerHandle& /*handle*/) {}
+
+Poller::Event Poller::wait(Duration timeout) {
+ DWORD timeoutMs = 0;
+ DWORD numTransferred = 0;
+ ULONG_PTR completionKey = 0;
+ OVERLAPPED *overlapped = 0;
+ windows::AsynchResult *result = 0;
+
+ // Wait for either an I/O operation to finish (thus signaling the
+ // IOCP handle) or a shutdown request to be made (thus signaling the
+ // shutdown event).
+ if (timeout == TIME_INFINITE)
+ timeoutMs = INFINITE;
+ else
+ timeoutMs = static_cast<DWORD>(timeout / TIME_MSEC);
+
+ InterlockedIncrement(&impl->threadsRunning);
+ bool goodOp = ::GetQueuedCompletionStatus (impl->iocp,
+ &numTransferred,
+ &completionKey,
+ &overlapped,
+ timeoutMs);
+ LONG remainingThreads = InterlockedDecrement(&impl->threadsRunning);
+ if (goodOp) {
+ // Dequeued a successful completion. If it's a posted packet from
+ // shutdown() the overlapped ptr is 0 and key is 1. Else downcast
+ // the OVERLAPPED pointer to an AsynchIoResult and call the
+ // completion handler.
+ if (overlapped == 0 && completionKey == 1) {
+ // If there are other threads still running this wait, re-post
+ // the completion.
+ if (remainingThreads > 0)
+ PostQueuedCompletionStatus(impl->iocp, 0, completionKey, 0);
+ return Event(0, SHUTDOWN);
+ }
+
+ result = windows::AsynchResult::from_overlapped(overlapped);
+ result->success (static_cast<size_t>(numTransferred));
+ }
+ else {
+ if (overlapped != 0) {
+ // Dequeued a completion for a failed operation. Downcast back
+ // to the result object and inform it that the operation failed.
+ DWORD status = ::GetLastError();
+ result = windows::AsynchResult::from_overlapped(overlapped);
+ result->failure (static_cast<int>(status));
+ }
+ }
+ return Event(0, INVALID); // TODO - this may need to be changed.
+
+}
+
+// Concrete constructors
+Poller::Poller() :
+ impl(new PollerPrivate())
+{}
+
+Poller::~Poller() {
+ delete impl;
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/sys/windows/LockFile.cpp b/qpid/cpp/src/qpid/sys/windows/LockFile.cpp
new file mode 100755
index 0000000000..048c2d5b18
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/LockFile.cpp
@@ -0,0 +1,64 @@
+/*
+ *
+ * Copyright (c) 2008 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/sys/LockFile.h"
+#include "qpid/sys/windows/check.h"
+
+#include <windows.h>
+
+namespace qpid {
+namespace sys {
+
+class LockFilePrivate {
+ friend class LockFile;
+
+ HANDLE fd;
+
+public:
+ LockFilePrivate(HANDLE f) : fd(f) {}
+};
+
+LockFile::LockFile(const std::string& path_, bool create)
+ : path(path_), created(create) {
+
+ HANDLE h = ::CreateFile(path.c_str(),
+ create ? (GENERIC_READ|GENERIC_WRITE) : GENERIC_READ,
+ FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
+ 0, /* Default security */
+ create ? OPEN_ALWAYS : OPEN_EXISTING,
+ FILE_FLAG_DELETE_ON_CLOSE, /* Delete file when closed */
+ NULL);
+ if (h == INVALID_HANDLE_VALUE)
+ throw qpid::Exception(path + ": " + qpid::sys::strError(GetLastError()));
+
+ // Lock up to 4Gb
+ if (!::LockFile(h, 0, 0, 0xffffffff, 0))
+ throw qpid::Exception(path + ": " + qpid::sys::strError(GetLastError()));
+ impl.reset(new LockFilePrivate(h));
+}
+
+LockFile::~LockFile() {
+ if (impl) {
+ if (impl->fd != INVALID_HANDLE_VALUE) {
+ ::UnlockFile(impl->fd, 0, 0, 0xffffffff, 0);
+ ::CloseHandle(impl->fd);
+ }
+ }
+}
+
+}} /* namespace qpid::sys */
diff --git a/qpid/cpp/src/qpid/sys/windows/MemoryMappedFile.cpp b/qpid/cpp/src/qpid/sys/windows/MemoryMappedFile.cpp
new file mode 100644
index 0000000000..60b3df7da6
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/MemoryMappedFile.cpp
@@ -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.
+ *
+ */
+#include "qpid/sys/MemoryMappedFile.h"
+
+namespace qpid {
+namespace sys {
+class MemoryMappedFilePrivate {};
+
+MemoryMappedFile::MemoryMappedFile() : state(0) {}
+MemoryMappedFile::~MemoryMappedFile() {}
+
+void MemoryMappedFile::open(const std::string& /*name*/, const std::string& /*directory*/)
+{
+}
+void MemoryMappedFile::close()
+{
+}
+size_t MemoryMappedFile::getPageSize()
+{
+ return 0;
+}
+char* MemoryMappedFile::map(size_t /*offset*/, size_t /*size*/)
+{
+ return 0;
+}
+void MemoryMappedFile::unmap(char* /*region*/, size_t /*size*/)
+{
+}
+void MemoryMappedFile::flush(char* /*region*/, size_t /*size*/)
+{
+}
+void MemoryMappedFile::expand(size_t /*offset*/)
+{
+}
+bool MemoryMappedFile::isSupported()
+{
+ return false;
+}
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/windows/Mutex.h b/qpid/cpp/src/qpid/sys/windows/Mutex.h
new file mode 100755
index 0000000000..5dcc69e836
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/Mutex.h
@@ -0,0 +1,188 @@
+#ifndef _sys_windows_Mutex_h
+#define _sys_windows_Mutex_h
+
+/*
+ *
+ * Copyright (c) 2008 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/sys/windows/check.h"
+
+#include <boost/version.hpp>
+#if (BOOST_VERSION < 103500)
+#error The Windows port requires Boost version 1.35.0 or later
+#endif
+
+#include <boost/noncopyable.hpp>
+#include <boost/thread/recursive_mutex.hpp>
+#include <boost/thread/shared_mutex.hpp>
+#include <boost/thread/thread_time.hpp>
+#include <boost/thread/tss.hpp>
+
+namespace qpid {
+namespace sys {
+
+class Condition;
+
+/**
+ * Mutex lock.
+ */
+class Mutex : private boost::noncopyable {
+ friend class Condition;
+
+public:
+ typedef ::qpid::sys::ScopedLock<Mutex> ScopedLock;
+ typedef ::qpid::sys::ScopedUnlock<Mutex> ScopedUnlock;
+
+ inline Mutex();
+ inline ~Mutex();
+ inline void lock();
+ inline void unlock();
+ inline bool trylock();
+
+
+protected:
+ boost::recursive_mutex mutex;
+};
+
+/**
+ * RW lock.
+ */
+class RWlock : private boost::noncopyable {
+ friend class Condition;
+
+public:
+ typedef ::qpid::sys::ScopedRlock<RWlock> ScopedRlock;
+ typedef ::qpid::sys::ScopedWlock<RWlock> ScopedWlock;
+
+ inline RWlock();
+ inline ~RWlock();
+ inline void wlock(); // will write-lock
+ inline void rlock(); // will read-lock
+ inline void unlock();
+ inline void trywlock(); // will write-try
+ inline void tryrlock(); // will read-try
+
+protected:
+ boost::shared_mutex rwMutex;
+ boost::thread_specific_ptr<bool> haveWrite;
+
+ inline bool &write (void);
+};
+
+
+/**
+ * PODMutex is a POD, can be static-initialized with
+ * PODMutex m = QPID_PODMUTEX_INITIALIZER
+ */
+struct PODMutex
+{
+ typedef ::qpid::sys::ScopedLock<PODMutex> ScopedLock;
+
+ inline void lock();
+ inline void unlock();
+ inline bool trylock();
+
+ // Must be public to be a POD:
+ boost::recursive_mutex mutex;
+};
+
+#define QPID_MUTEX_INITIALIZER 0
+
+void PODMutex::lock() {
+ mutex.lock();
+}
+
+void PODMutex::unlock() {
+ mutex.unlock();
+}
+
+bool PODMutex::trylock() {
+ return mutex.try_lock();
+}
+
+Mutex::Mutex() {
+}
+
+Mutex::~Mutex(){
+}
+
+void Mutex::lock() {
+ mutex.lock();
+}
+
+void Mutex::unlock() {
+ mutex.unlock();
+}
+
+bool Mutex::trylock() {
+ return mutex.try_lock();
+}
+
+
+RWlock::RWlock() {
+}
+
+RWlock::~RWlock(){
+}
+
+void RWlock::wlock() {
+ bool &writer = write();
+ rwMutex.lock();
+ writer = true; // Remember this thread has write lock held.
+}
+
+void RWlock::rlock() {
+ bool &writer = write();
+ rwMutex.lock_shared();
+ writer = false; // Remember this thread has shared lock held.
+}
+
+void RWlock::unlock() {
+ bool &writer = write();
+ if (writer)
+ rwMutex.unlock();
+ else
+ rwMutex.unlock_shared();
+}
+
+void RWlock::trywlock() {
+ bool &writer = write();
+ // shared_mutex::try_lock() seems to not be available... emulate it with
+ // a timed lock().
+ boost::system_time now = boost::get_system_time();
+ if (rwMutex.timed_lock(now))
+ writer = true;
+}
+
+void RWlock::tryrlock() {
+ bool &writer = write();
+ if (rwMutex.try_lock_shared())
+ writer = false;
+}
+
+bool & RWlock::write (void) {
+ // Accessing thread-specific and stack-local info, so no locks needed.
+ bool *writePtr = haveWrite.get();
+ if (writePtr == 0) {
+ writePtr = new bool(false);
+ haveWrite.reset(writePtr);
+ }
+ return *writePtr;
+}
+
+}}
+#endif /*!_sys_windows_Mutex_h*/
diff --git a/qpid/cpp/src/qpid/sys/windows/Path.cpp b/qpid/cpp/src/qpid/sys/windows/Path.cpp
new file mode 100644
index 0000000000..1cb4521fde
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/Path.cpp
@@ -0,0 +1,65 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/sys/Path.h"
+#include "qpid/sys/StrError.h"
+#include "qpid/Exception.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <direct.h>
+#include <errno.h>
+#include <windows.h>
+#include <strsafe.h>
+
+
+namespace qpid {
+namespace sys {
+
+const std::string Path::separator("\\");
+
+namespace {
+// Return true for success, false for ENOENT, throw otherwise.
+bool getStat(const std::string& path, struct _stat& s) {
+ if (::_stat(path.c_str(), &s)) {
+ if (errno == ENOENT) return false;
+ throw qpid::Exception("cannot stat: " + path + ": " + strError(errno));
+ }
+ return true;
+}
+
+bool isFlag(const std::string& path, unsigned long flag) {
+ struct _stat s;
+ return getStat(path, s) && (s.st_mode & flag);
+}
+}
+
+bool Path::exists () const {
+ struct _stat s;
+ return getStat(path, s);
+}
+
+bool Path::isFile() const { return isFlag(path, _S_IFREG); }
+bool Path::isDirectory() const { return isFlag(path, _S_IFDIR); }
+
+bool Path::isAbsolute() const {
+ return (path.size() > 0 && (path[0] == separator[0] || path[0] == '/'))
+ || (path.size() > 1 && (isalpha(path[0]) && path[1] == ':'));
+}
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/windows/PipeHandle.cpp b/qpid/cpp/src/qpid/sys/windows/PipeHandle.cpp
new file mode 100755
index 0000000000..062458ae5f
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/PipeHandle.cpp
@@ -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.
+//
+
+#include "qpid/sys/PipeHandle.h"
+#include "qpid/sys/windows/check.h"
+#include <winsock2.h>
+
+namespace qpid {
+namespace sys {
+
+PipeHandle::PipeHandle(bool nonBlocking) {
+
+ SOCKET listener, pair[2];
+ struct sockaddr_in addr;
+ int err;
+ int addrlen = sizeof(addr);
+ pair[0] = pair[1] = INVALID_SOCKET;
+ if ((listener = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
+ throw QPID_WINDOWS_ERROR(WSAGetLastError());
+
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+ addr.sin_port = 0;
+
+ err = bind(listener, (const struct sockaddr*) &addr, sizeof(addr));
+ if (err == SOCKET_ERROR) {
+ err = WSAGetLastError();
+ closesocket(listener);
+ throw QPID_WINDOWS_ERROR(err);
+ }
+
+ err = getsockname(listener, (struct sockaddr*) &addr, &addrlen);
+ if (err == SOCKET_ERROR) {
+ err = WSAGetLastError();
+ closesocket(listener);
+ throw QPID_WINDOWS_ERROR(err);
+ }
+
+ try {
+ if (listen(listener, 1) == SOCKET_ERROR)
+ throw QPID_WINDOWS_ERROR(WSAGetLastError());
+ if ((pair[0] = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET)
+ throw QPID_WINDOWS_ERROR(WSAGetLastError());
+ if (connect(pair[0], (const struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR)
+ throw QPID_WINDOWS_ERROR(WSAGetLastError());
+ if ((pair[1] = accept(listener, NULL, NULL)) == INVALID_SOCKET)
+ throw QPID_WINDOWS_ERROR(WSAGetLastError());
+
+ closesocket(listener);
+ writeFd = pair[0];
+ readFd = pair[1];
+ }
+ catch (...) {
+ closesocket(listener);
+ if (pair[0] != INVALID_SOCKET)
+ closesocket(pair[0]);
+ throw;
+ }
+
+ // Set the socket to non-blocking
+ if (nonBlocking) {
+ unsigned long nonblock = 1;
+ ioctlsocket(readFd, FIONBIO, &nonblock);
+ }
+}
+
+PipeHandle::~PipeHandle() {
+ closesocket(readFd);
+ closesocket(writeFd);
+}
+
+int PipeHandle::read(void* buf, size_t bufSize) {
+ return ::recv(readFd, (char *)buf, bufSize, 0);
+}
+
+int PipeHandle::write(const void* buf, size_t bufSize) {
+ return ::send(writeFd, (const char *)buf, bufSize, 0);
+}
+
+int PipeHandle::getReadHandle() {
+ return readFd;
+}
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/windows/PollableCondition.cpp b/qpid/cpp/src/qpid/sys/windows/PollableCondition.cpp
new file mode 100644
index 0000000000..3e2a5fb36c
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/PollableCondition.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/PollableCondition.h"
+#include "qpid/sys/IOHandle.h"
+#include "qpid/sys/windows/AsynchIoResult.h"
+#include "qpid/sys/windows/IoHandlePrivate.h"
+
+#include <boost/bind.hpp>
+#include <windows.h>
+
+namespace qpid {
+namespace sys {
+
+// PollableConditionPrivate will reuse the IocpPoller's ability to queue
+// a completion to the IOCP and have it dispatched to the completer callback
+// noted in the IOHandlePrivate when the request is queued. The
+// AsynchCallbackRequest object is not really used - we already have the
+// desired callback for the user of PollableCondition.
+class PollableConditionPrivate : private IOHandle {
+ friend class PollableCondition;
+
+private:
+ PollableConditionPrivate(const sys::PollableCondition::Callback& cb,
+ sys::PollableCondition& parent,
+ const boost::shared_ptr<sys::Poller>& poller);
+ ~PollableConditionPrivate();
+
+ void poke();
+ void dispatch(windows::AsynchIoResult *result);
+
+private:
+ PollableCondition::Callback cb;
+ PollableCondition& parent;
+ boost::shared_ptr<sys::Poller> poller;
+ LONG isSet;
+ LONG isDispatching;
+};
+
+PollableConditionPrivate::PollableConditionPrivate(const sys::PollableCondition::Callback& cb,
+ sys::PollableCondition& parent,
+ const boost::shared_ptr<sys::Poller>& poller)
+ : IOHandle(INVALID_SOCKET, boost::bind(&PollableConditionPrivate::dispatch, this, _1)),
+ cb(cb), parent(parent), poller(poller), isSet(0), isDispatching(0)
+{
+}
+
+PollableConditionPrivate::~PollableConditionPrivate()
+{
+}
+
+void PollableConditionPrivate::poke()
+{
+ // monitorHandle will queue a completion for the IOCP; when it's handled, a
+ // poller thread will call back to dispatch() below.
+ PollerHandle ph(*this);
+ poller->monitorHandle(ph, Poller::INPUT);
+}
+
+void PollableConditionPrivate::dispatch(windows::AsynchIoResult *result)
+{
+ delete result; // Poller::monitorHandle() allocates this
+ // If isDispatching is already set, just return. Else, enter.
+ if (::InterlockedCompareExchange(&isDispatching, 1, 0) == 1)
+ return;
+ cb(parent);
+ LONG oops = ::InterlockedDecrement(&isDispatching); // Result must be 0
+ assert(!oops);
+ if (isSet)
+ poke();
+}
+
+ /* PollableCondition */
+
+PollableCondition::PollableCondition(const Callback& cb,
+ const boost::shared_ptr<sys::Poller>& poller)
+ : impl(new PollableConditionPrivate(cb, *this, poller))
+{
+}
+
+PollableCondition::~PollableCondition()
+{
+ delete impl;
+}
+
+void PollableCondition::set() {
+ // Add one to the set count and poke it to provoke a callback
+ ::InterlockedIncrement(&impl->isSet);
+ impl->poke();
+}
+
+void PollableCondition::clear() {
+ ::InterlockedExchange(&impl->isSet, 0);
+}
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/windows/QpidDllMain.h b/qpid/cpp/src/qpid/sys/windows/QpidDllMain.h
new file mode 100644
index 0000000000..74eaf0256a
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/QpidDllMain.h
@@ -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.
+ *
+ */
+
+/*
+ * Include this file once in each DLL that relies on SystemInfo.h:
+ * threadSafeShutdown(). Note that Thread.cpp has a more elaborate
+ * DllMain, that also provides this functionality separately.
+ *
+ * Teardown is in the reverse order of the DLL dependencies used
+ * during the load phase. The calls to DllMain and the static
+ * destructors are from the same thread, so no locking is necessary
+ * and there is no downside to an invocation of DllMain by multiple
+ * Qpid DLLs.
+ */
+
+#ifdef _DLL
+
+#include <qpid/ImportExport.h>
+#include <windows.h>
+
+namespace qpid {
+namespace sys {
+namespace windows {
+
+QPID_IMPORT bool processExiting;
+QPID_IMPORT bool libraryUnloading;
+
+}}} // namespace qpid::sys::SystemInfo
+
+
+BOOL APIENTRY DllMain(HMODULE hm, DWORD reason, LPVOID reserved) {
+ switch (reason) {
+ case DLL_PROCESS_ATTACH:
+ case DLL_THREAD_ATTACH:
+ case DLL_THREAD_DETACH:
+ break;
+
+ case DLL_PROCESS_DETACH:
+ // Remember how the process is terminating this DLL.
+ if (reserved != NULL) {
+ qpid::sys::windows::processExiting = true;
+ // Danger: all threading suspect, including indirect use of malloc or locks.
+ // Think twice before adding more functionality here.
+ return TRUE;
+ }
+ else {
+ qpid::sys::windows::libraryUnloading = true;
+ }
+ break;
+ }
+ return TRUE;
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/sys/windows/Shlib.cpp b/qpid/cpp/src/qpid/sys/windows/Shlib.cpp
new file mode 100644
index 0000000000..ba18747eb4
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/Shlib.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/Shlib.h"
+#include "qpid/Exception.h"
+#include "qpid/sys/windows/check.h"
+#include <windows.h>
+
+namespace qpid {
+namespace sys {
+
+void Shlib::load(const char* name) {
+ HMODULE h = LoadLibrary(name);
+ if (h == NULL) {
+ throw QPID_WINDOWS_ERROR(GetLastError());
+ }
+ handle = static_cast<void*>(h);
+}
+
+void Shlib::unload() {
+ if (handle) {
+ if (FreeLibrary(static_cast<HMODULE>(handle)) == 0) {
+ throw QPID_WINDOWS_ERROR(GetLastError());
+ }
+ handle = 0;
+ }
+}
+
+void* Shlib::getSymbol(const char* name) {
+ // Double cast avoids warning about casting function pointer to object
+ void *sym = reinterpret_cast<void*>(reinterpret_cast<intptr_t>(GetProcAddress(static_cast<HMODULE>(handle), name)));
+ if (sym == NULL)
+ throw QPID_WINDOWS_ERROR(GetLastError());
+ return sym;
+}
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/windows/SocketAddress.cpp b/qpid/cpp/src/qpid/sys/windows/SocketAddress.cpp
new file mode 100644
index 0000000000..b0903752c6
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/SocketAddress.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/SocketAddress.h"
+
+#include "qpid/Exception.h"
+#include "qpid/Msg.h"
+#include "qpid/log/Logger.h"
+
+// Ensure we get all of winsock2.h
+#ifndef _WIN32_WINNT
+#define _WIN32_WINNT 0x0501
+#endif
+
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <string.h>
+
+namespace qpid {
+namespace sys {
+
+SocketAddress::SocketAddress(const std::string& host0, const std::string& port0) :
+ host(host0),
+ port(port0),
+ addrInfo(0),
+ currentAddrInfo(0)
+{
+}
+
+SocketAddress::SocketAddress(const SocketAddress& sa) :
+ host(sa.host),
+ port(sa.port),
+ addrInfo(0),
+ currentAddrInfo(0)
+{
+}
+
+SocketAddress& SocketAddress::operator=(const SocketAddress& sa)
+{
+ SocketAddress temp(sa);
+
+ std::swap(temp, *this);
+ return *this;
+}
+
+SocketAddress::~SocketAddress()
+{
+ if (addrInfo) {
+ ::freeaddrinfo(addrInfo);
+ }
+}
+
+std::string SocketAddress::asString(::sockaddr const * const addr, size_t addrlen, bool dispNameOnly, bool hideDecoration)
+{
+ char servName[NI_MAXSERV];
+ char dispName[NI_MAXHOST];
+ if (int rc=::getnameinfo(addr, addrlen,
+ dispName, sizeof(dispName),
+ servName, sizeof(servName),
+ NI_NUMERICHOST | NI_NUMERICSERV) != 0)
+ throw qpid::Exception(QPID_MSG(gai_strerror(rc)));
+ std::string s;
+ switch (addr->sa_family) {
+ case AF_INET: s += dispName; break;
+ case AF_INET6:
+ if (!hideDecoration) {
+ s += "["; s += dispName; s+= "]";
+ } else {
+ s += dispName;
+ }
+ break;
+ default: throw Exception(QPID_MSG("Unexpected socket type"));
+ }
+ if (!dispNameOnly) {
+ s += ":";
+ s += servName;
+ }
+ return s;
+}
+
+uint16_t SocketAddress::getPort(::sockaddr const * const addr)
+{
+ switch (addr->sa_family) {
+ case AF_INET: return ntohs(((::sockaddr_in*)addr)->sin_port);
+ case AF_INET6: return ntohs(((::sockaddr_in6*)addr)->sin6_port);
+ default:throw Exception(QPID_MSG("Unexpected socket type"));
+ }
+}
+
+std::string SocketAddress::asString(bool numeric, bool dispNameOnly, bool hideDecoration) const
+{
+ if (!numeric)
+ return host + ":" + port;
+ // Canonicalise into numeric id
+ const ::addrinfo& ai = getAddrInfo(*this);
+
+ return asString(ai.ai_addr, ai.ai_addrlen, dispNameOnly, hideDecoration);
+}
+
+std::string SocketAddress::getHost() const
+{
+ return host;
+}
+
+/**
+ * Return true if this SocketAddress is IPv4 or IPv6
+ */
+bool SocketAddress::isIp() const
+{
+ const ::addrinfo& ai = getAddrInfo(*this);
+ return ai.ai_family == AF_INET || ai.ai_family == AF_INET6;
+}
+
+/**
+ * this represents the low address of an ACL address range.
+ * Given rangeHi that represents the high address,
+ * return a string showing the numeric comparisons that the
+ * inRange checks will do for address pair.
+ */
+std::string SocketAddress::comparisonDetails(const SocketAddress& rangeHi) const
+{
+ std::ostringstream os;
+ SocketAddress thisSa(*this);
+ SocketAddress rangeHiSa(rangeHi);
+ (void) getAddrInfo(thisSa);
+ (void) getAddrInfo(rangeHiSa);
+ os << "(" << thisSa.asString(true, true, false) <<
+ "," << rangeHiSa.asString(true, true, false) << ")";
+ while (thisSa.nextAddress()) {
+ if (!rangeHiSa.nextAddress()) {
+ throw(Exception(QPID_MSG("Comparison iteration fails: " + (*this).asString() +
+ rangeHi.asString())));
+ }
+ os << ",(" << thisSa.asString(true, true, false) <<
+ "," << rangeHiSa.asString(true, true, false) << ")";
+ }
+ if (rangeHiSa.nextAddress()) {
+ throw(Exception(QPID_MSG("Comparison iteration fails: " + (*this).asString() +
+ rangeHi.asString())));
+ }
+ std::string result = os.str();
+ return result;
+}
+
+/**
+ * For ACL address matching make sure that the two addresses, *this
+ * which is the low address and hiPeer which is the high address, are
+ * both numeric ip addresses of the same family and that hi > *this.
+ *
+ * Note that if the addresses resolve to more than one struct addrinfo
+ * then this and the hiPeer must be equal. This avoids having to do
+ * difficult range checks where the this and hiPeer both resolve to
+ * multiple IPv4 or IPv6 addresses.
+ *
+ * This check is run at acl file load time and not at run tme.
+ */
+bool SocketAddress::isComparable(const SocketAddress& hiPeer) const {
+ try {
+ // May only compare if this socket is IPv4 or IPv6
+ SocketAddress lo(*this);
+ const ::addrinfo& peerLoInfo = getAddrInfo(lo);
+ if (!(peerLoInfo.ai_family == AF_INET || peerLoInfo.ai_family == AF_INET6)) {
+ return false;
+ }
+ try {
+ // May only compare if peer socket is same family
+ SocketAddress hi(hiPeer);
+ const ::addrinfo& peerHiInfo = getAddrInfo(hi);
+ if (peerLoInfo.ai_family != peerHiInfo.ai_family) {
+ return false;
+ }
+ // Host names that resolve to lists are allowed if they are equal.
+ // For example: localhost, or fjord.lab.example.com
+ if ((*this).asString() == hiPeer.asString()) {
+ return true;
+ }
+ // May only compare if this and peer resolve to single address.
+ if (lo.nextAddress() || hi.nextAddress()) {
+ return false;
+ }
+ // Make sure that the lo/hi relationship is ok
+ int res;
+ if (!compareAddresses(peerLoInfo, peerHiInfo, res) || res < 0) {
+ return false;
+ }
+ return true;
+ } catch (Exception) {
+ // failed to resolve hi
+ return false;
+ }
+ } catch (Exception) {
+ // failed to resolve lo
+ return false;
+ }
+}
+
+/**
+ * *this SocketAddress was created from the numeric IP address of a
+ * connecting host.
+ * The lo and hi addresses are the limit checks from the ACL file.
+ * Return true if this address is in range of any of the address pairs
+ * in the limit check range.
+ *
+ * This check is executed on every incoming connection.
+ */
+bool SocketAddress::inRange(const SocketAddress& lo,
+ const SocketAddress& hi) const
+{
+ (*this).firstAddress();
+ lo.firstAddress();
+ hi.firstAddress();
+ const ::addrinfo& thisInfo = getAddrInfo(*this);
+ const ::addrinfo& loInfo = getAddrInfo(lo);
+ const ::addrinfo& hiInfo = getAddrInfo(hi);
+ if (inRange(thisInfo, loInfo, hiInfo)) {
+ return true;
+ }
+ while (lo.nextAddress()) {
+ if (!hi.nextAddress()) {
+ assert (false);
+ throw(Exception(QPID_MSG("Comparison iteration fails: " +
+ lo.asString() + hi.asString())));
+ }
+ const ::addrinfo& loInfo = getAddrInfo(lo);
+ const ::addrinfo& hiInfo = getAddrInfo(hi);
+ if (inRange(thisInfo, loInfo, hiInfo)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * *this SocketAddress was created from the numeric IP address of a
+ * connecting host.
+ * The lo and hi addresses are one binary address pair from a range
+ * given in an ACL file.
+ * Return true if this binary address is '>= lo' and '<= hi'.
+ */
+bool SocketAddress::inRange(const ::addrinfo& thisInfo,
+ const ::addrinfo& lo,
+ const ::addrinfo& hi) const
+{
+ int resLo;
+ int resHi;
+ if (!compareAddresses(lo, thisInfo, resLo)) {
+ return false;
+ }
+ if (!compareAddresses(hi, thisInfo, resHi)) {
+ return false;
+ }
+ if (resLo < 0) {
+ return false;
+ }
+ if (resHi > 0) {
+ return false;
+ }
+ return true;
+}
+
+/**
+ * Compare this address against two binary low/high addresses.
+ * return true with result holding the comparison.
+ */
+bool SocketAddress::compareAddresses(const struct addrinfo& lo,
+ const struct addrinfo& hi,
+ int& result) const
+{
+ if (lo.ai_family != hi.ai_family) {
+ return false;
+ }
+ if (lo.ai_family == AF_INET) {
+ struct sockaddr_in* sin4lo = (struct sockaddr_in*)lo.ai_addr;
+ struct sockaddr_in* sin4hi = (struct sockaddr_in*)hi.ai_addr;
+ result = memcmp(&sin4hi->sin_addr, &sin4lo->sin_addr, sizeof(in_addr));
+ } else if (lo.ai_family == AF_INET6) {
+ struct sockaddr_in6* sin6lo = (struct sockaddr_in6*)lo.ai_addr;
+ struct sockaddr_in6* sin6hi = (struct sockaddr_in6*)hi.ai_addr;
+ result = memcmp(&sin6hi->sin6_addr, &sin6lo->sin6_addr, sizeof(in6_addr));
+ } else {
+ assert (false);
+ return false;
+ }
+ return true;
+}
+
+void SocketAddress::firstAddress() const {
+ if (addrInfo) {
+ currentAddrInfo = addrInfo;
+ } else {
+ (void) getAddrInfo(*this);
+ }
+}
+
+bool SocketAddress::nextAddress() const {
+ bool r = currentAddrInfo->ai_next != 0;
+ if (r)
+ currentAddrInfo = currentAddrInfo->ai_next;
+ return r;
+}
+
+const ::addrinfo& getAddrInfo(const SocketAddress& sa)
+{
+ if (!sa.addrInfo) {
+ ::addrinfo hints;
+ ::memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_ADDRCONFIG; // Only use protocols that we have configured interfaces for
+ hints.ai_family = AF_UNSPEC; // Allow both IPv4 and IPv6
+ hints.ai_socktype = SOCK_STREAM;
+
+ const char* node = 0;
+ if (sa.host.empty()) {
+ hints.ai_flags |= AI_PASSIVE;
+ } else {
+ node = sa.host.c_str();
+ }
+ const char* service = sa.port.empty() ? "0" : sa.port.c_str();
+
+ int n = ::getaddrinfo(node, service, &hints, &sa.addrInfo);
+ if (n != 0)
+ throw Exception(QPID_MSG("Cannot resolve " << sa.asString(false) << ": " << ::gai_strerror(n)));
+ sa.currentAddrInfo = sa.addrInfo;
+ }
+
+ return *sa.currentAddrInfo;
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/sys/windows/SslAsynchIO.cpp b/qpid/cpp/src/qpid/sys/windows/SslAsynchIO.cpp
new file mode 100644
index 0000000000..29f673c156
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/SslAsynchIO.cpp
@@ -0,0 +1,735 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "SslAsynchIO.h"
+#include "qpid/sys/Mutex.h"
+#include "qpid/sys/Socket.h"
+#include "qpid/sys/Poller.h"
+#include "qpid/sys/SecuritySettings.h"
+#include "qpid/sys/Thread.h"
+#include "qpid/sys/Time.h"
+#include "qpid/log/Statement.h"
+
+#include "qpid/sys/windows/check.h"
+
+// security.h needs to see this to distinguish from kernel use.
+#define SECURITY_WIN32
+#include <security.h>
+#include <Schnlsp.h>
+#undef SECURITY_WIN32
+
+#include <queue>
+#include <boost/bind.hpp>
+#include "AsynchIO.h"
+
+namespace qpid {
+namespace sys {
+namespace windows {
+
+namespace {
+
+ /*
+ * To make the SSL encryption more efficient, set up a new BufferBase
+ * that leaves room for the SSL header to be prepended and the SSL
+ * trailer to be appended.
+ *
+ * This works by accepting a properly formed BufferBase, remembering it,
+ * and resetting the members of this struct to reflect the reserved
+ * header and trailer areas. It's only needed for giving buffers up to
+ * the frame layer for writing into.
+ */
+ struct SslIoBuff : public qpid::sys::AsynchIO::BufferBase {
+ qpid::sys::AsynchIO::BufferBase* aioBuff;
+
+ SslIoBuff (qpid::sys::AsynchIO::BufferBase *base,
+ const SecPkgContext_StreamSizes &sizes)
+ : qpid::sys::AsynchIO::BufferBase(&base->bytes[sizes.cbHeader],
+ std::min(base->byteCount - sizes.cbHeader - sizes.cbTrailer,
+ sizes.cbMaximumMessage)),
+ aioBuff(base)
+ {}
+
+ ~SslIoBuff() {}
+ };
+}
+
+SslAsynchIO::SslAsynchIO(const qpid::sys::Socket& s,
+ CredHandle hCred,
+ ReadCallback rCb,
+ EofCallback eofCb,
+ DisconnectCallback disCb,
+ ClosedCallback cCb,
+ BuffersEmptyCallback eCb,
+ IdleCallback iCb,
+ NegotiateDoneCallback nCb) :
+ credHandle(hCred),
+ aio(0),
+ state(Negotiating),
+ readCallback(rCb),
+ idleCallback(iCb),
+ negotiateDoneCallback(nCb),
+ queuedDelete(false),
+ queuedClose(false),
+ reapCheckPending(false),
+ started(false),
+ leftoverPlaintext(0)
+{
+ SecInvalidateHandle(&ctxtHandle);
+ peerAddress = s.getPeerAddress();
+ aio = qpid::sys::AsynchIO::create(s,
+ boost::bind(&SslAsynchIO::sslDataIn, this, _1, _2),
+ eofCb,
+ disCb,
+ cCb,
+ eCb,
+ boost::bind(&SslAsynchIO::idle, this, _1));
+}
+
+SslAsynchIO::~SslAsynchIO() {
+ leftoverPlaintext = 0;
+}
+
+void SslAsynchIO::queueForDeletion() {
+ // Called exactly once, always on the IO completion thread.
+ bool authenticated = (state != Negotiating);
+ state = ShuttingDown;
+ if (authenticated) {
+ // Tell SChannel we are done.
+ DWORD shutdown = SCHANNEL_SHUTDOWN;
+ SecBuffer shutBuff;
+ shutBuff.cbBuffer = sizeof(DWORD);
+ shutBuff.BufferType = SECBUFFER_TOKEN;
+ shutBuff.pvBuffer = &shutdown;
+ SecBufferDesc desc;
+ desc.ulVersion = SECBUFFER_VERSION;
+ desc.cBuffers = 1;
+ desc.pBuffers = &shutBuff;
+ ::ApplyControlToken(&ctxtHandle, &desc);
+ negotiateStep(0);
+ }
+
+ queueWriteClose();
+ queuedDelete = true;
+
+ // This method effectively disconnects the layer above; pass it on the
+ // AsynchIO and delete.
+ aio->queueForDeletion();
+
+ if (!reapCheckPending)
+ delete(this);
+}
+
+void SslAsynchIO::start(qpid::sys::Poller::shared_ptr poller) {
+ aio->start(poller);
+ started = true;
+ startNegotiate();
+}
+
+void SslAsynchIO::createBuffers(uint32_t size) {
+ // Reserve an extra buffer to hold unread plaintext or trailing encrypted input.
+ windows::AsynchIO *waio = dynamic_cast<windows::AsynchIO*>(aio);
+ waio->setBufferCount(waio->getBufferCount() + 1);
+ aio->createBuffers(size);
+}
+
+void SslAsynchIO::queueReadBuffer(AsynchIO::BufferBase* buff) {
+ aio->queueReadBuffer(buff);
+}
+
+void SslAsynchIO::unread(AsynchIO::BufferBase* buff) {
+ // This is plaintext data being given back for more. Since it's already
+ // decrypted, don't give it back to the aio layer; keep it to append
+ // any new data for the upper layer.
+ assert(buff);
+ buff->squish();
+ assert(leftoverPlaintext == 0);
+ leftoverPlaintext = buff;
+}
+
+void SslAsynchIO::queueWrite(AsynchIO::BufferBase* buff) {
+ // @@TODO: Need to delay the write if the session is renegotiating.
+
+ // Should not have gotten here without an SslIoBuff. This assert is
+ // primarily to catch any stray cases where write is called with a buffer
+ // not obtained via getQueuedBuffer.
+ SslIoBuff *sslBuff = dynamic_cast<SslIoBuff*>(buff);
+ assert(sslBuff != 0);
+
+ // Encrypt and hand off to the io layer. Remember that the upper layer's
+ // encoding was working on, and adjusting counts for, the SslIoBuff.
+ // Update the count of the original BufferBase before handing off to
+ // the I/O layer.
+ buff = sslBuff->aioBuff;
+ SecBuffer buffs[4];
+ buffs[0].cbBuffer = schSizes.cbHeader;
+ buffs[0].BufferType = SECBUFFER_STREAM_HEADER;
+ buffs[0].pvBuffer = buff->bytes; // This space was left by SslIoBuff
+ buffs[1].cbBuffer = sslBuff->dataCount;
+ buffs[1].BufferType = SECBUFFER_DATA;
+ buffs[1].pvBuffer = sslBuff->bytes;
+ buffs[2].cbBuffer = schSizes.cbTrailer;
+ buffs[2].BufferType = SECBUFFER_STREAM_TRAILER;
+ buffs[2].pvBuffer = &sslBuff->bytes[sslBuff->dataCount];
+ buffs[3].cbBuffer = 0;
+ buffs[3].BufferType = SECBUFFER_EMPTY;
+ buffs[3].pvBuffer = 0;
+ SecBufferDesc buffDesc;
+ buffDesc.ulVersion = SECBUFFER_VERSION;
+ buffDesc.cBuffers = 4;
+ buffDesc.pBuffers = buffs;
+ SECURITY_STATUS status = ::EncryptMessage(&ctxtHandle, 0, &buffDesc, 0);
+
+ // EncryptMessage encrypts the data in place. The header and trailer
+ // areas were left previously and must now be included in the updated
+ // count of bytes to write to the peer.
+ delete sslBuff;
+ buff->dataCount = buffs[0].cbBuffer + buffs[1].cbBuffer + buffs[2].cbBuffer;
+ aio->queueWrite(buff);
+}
+
+void SslAsynchIO::notifyPendingWrite() {
+ aio->notifyPendingWrite();
+}
+
+void SslAsynchIO::queueWriteClose() {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ if (queuedClose)
+ return;
+ queuedClose = true;
+ if (started) {
+ reapCheckPending = true;
+ // Move tear down logic to an IO thread.
+ aio->requestCallback(boost::bind(&SslAsynchIO::reapCheck, this));
+ }
+ aio->queueWriteClose();
+}
+
+void SslAsynchIO::reapCheck() {
+ // Serialized check in the IO thread whether to self-delete.
+ reapCheckPending = false;
+ if (queuedDelete)
+ delete(this);
+}
+
+bool SslAsynchIO::writeQueueEmpty() {
+ return aio->writeQueueEmpty();
+}
+
+// Queue the specified callback for invocation from an I/O thread.
+void SslAsynchIO::requestCallback(RequestCallback callback) {
+ aio->requestCallback(callback);
+}
+
+/**
+ * Return a queued buffer read to put new data in for writing.
+ * This method ALWAYS returns a SslIoBuff reflecting a BufferBase from
+ * the aio layer that has header and trailer space reserved.
+ */
+AsynchIO::BufferBase* SslAsynchIO::getQueuedBuffer() {
+ SslIoBuff *sslBuff = 0;
+ BufferBase* buff = aio->getQueuedBuffer();
+ if (buff == 0)
+ return 0;
+
+ sslBuff = new SslIoBuff(buff, schSizes);
+ return sslBuff;
+}
+
+SecuritySettings SslAsynchIO::getSecuritySettings() {
+ SecPkgContext_KeyInfo info;
+ memset(&info, 0, sizeof(info));
+ ::QueryContextAttributes(&ctxtHandle, SECPKG_ATTR_KEY_INFO, &info);
+
+ SecuritySettings settings;
+ settings.ssf = info.KeySize;
+ settings.authid = std::string();
+ return settings;
+}
+
+void SslAsynchIO::negotiationDone() {
+ switch(state) {
+ case Negotiating:
+ ::QueryContextAttributes(&ctxtHandle,
+ SECPKG_ATTR_STREAM_SIZES,
+ &schSizes);
+ state = Running;
+ if (negotiateDoneCallback)
+ negotiateDoneCallback(SEC_E_OK);
+ break;
+ case Redo:
+ state = Running;
+ break;
+ case ShuttingDown:
+ break;
+ default:
+ assert(0);
+ }
+}
+
+void SslAsynchIO::negotiationFailed(SECURITY_STATUS status) {
+ QPID_LOG(notice, "SSL negotiation failed to " << peerAddress << ": " <<
+ qpid::sys::strError(status));
+ if (negotiateDoneCallback)
+ negotiateDoneCallback(status);
+ else
+ queueWriteClose();
+}
+
+void SslAsynchIO::sslDataIn(qpid::sys::AsynchIO& a, BufferBase *buff) {
+ if (state == ShuttingDown) {
+ return;
+ }
+ if (state != Running) {
+ negotiateStep(buff);
+ return;
+ }
+
+ // Decrypt one block; if there's legit data, pass it on through.
+ // However, it's also possible that the peer hasn't supplied enough
+ // data yet, or the session needs to be renegotiated, or the session
+ // is ending.
+ SecBuffer recvBuffs[4];
+ recvBuffs[0].cbBuffer = buff->dataCount;
+ recvBuffs[0].BufferType = SECBUFFER_DATA;
+ recvBuffs[0].pvBuffer = &buff->bytes[buff->dataStart];
+ recvBuffs[1].BufferType = SECBUFFER_EMPTY;
+ recvBuffs[2].BufferType = SECBUFFER_EMPTY;
+ recvBuffs[3].BufferType = SECBUFFER_EMPTY;
+ SecBufferDesc buffDesc;
+ buffDesc.ulVersion = SECBUFFER_VERSION;
+ buffDesc.cBuffers = 4;
+ buffDesc.pBuffers = recvBuffs;
+ SECURITY_STATUS status = ::DecryptMessage(&ctxtHandle, &buffDesc, 0, NULL);
+ if (status != SEC_E_OK) {
+ if (status == SEC_E_INCOMPLETE_MESSAGE) {
+ // Give the partially filled buffer back and get more data
+ a.unread(buff);
+ }
+ else {
+ // Don't need this any more...
+ a.queueReadBuffer(buff);
+
+ if (status == SEC_I_RENEGOTIATE) {
+ state = Redo;
+ negotiateStep(0);
+ }
+ else if (status == SEC_I_CONTEXT_EXPIRED) {
+ queueWriteClose();
+ }
+ else {
+ throw QPID_WINDOWS_ERROR(status);
+ }
+ }
+ return;
+ }
+
+ // All decrypted and verified... continue with AMQP. The recvBuffs have
+ // been set up by DecryptMessage to demarcate the SSL header, data, and
+ // trailer, as well as any extra data left over. Walk through and find
+ // that info, adjusting the buff data accordingly to reflect only the
+ // actual decrypted data.
+ // If there's extra data, copy that out to a new buffer and run through
+ // this method again.
+ char *extraBytes = 0;
+ int32_t extraLength = 0;
+ BufferBase *extraBuff = 0;
+ for (int i = 0; i < 4; i++) {
+ switch (recvBuffs[i].BufferType) {
+ case SECBUFFER_STREAM_HEADER:
+ buff->dataStart += recvBuffs[i].cbBuffer;
+ // Fall through - also don't count these bytes as data
+ case SECBUFFER_STREAM_TRAILER:
+ buff->dataCount -= recvBuffs[i].cbBuffer;
+ break;
+ case SECBUFFER_EXTRA:
+ extraBytes = (char *) recvBuffs[i].pvBuffer;
+ extraLength = recvBuffs[i].cbBuffer;
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Since we've already taken (possibly) all the available bytes from the
+ // aio layer, need to be sure that everything that's processable is
+ // processed before returning back to aio. It could be that any
+ // leftoverPlaintext data plus new buff data won't fit in one buffer, so
+ // need to keep going around the input processing loop until either
+ // all the bytes are gone, or there's less than a full frame remaining
+ // (so we can count on more bytes being on the way via aio).
+ do {
+ BufferBase *temp = 0;
+ // See if there was partial data left over from last time. If so, append this new
+ // data to that and release the current buff back to aio. Assume that
+ // leftoverPlaintext was squished so the data starts at 0.
+ if (leftoverPlaintext != 0) {
+ // There is leftover data; append all the new data that will fit.
+ int32_t count = buff->dataCount;
+ if (count) {
+ if (leftoverPlaintext->dataCount + count > leftoverPlaintext->byteCount)
+ count = (leftoverPlaintext->byteCount - leftoverPlaintext->dataCount);
+ ::memmove(&leftoverPlaintext->bytes[leftoverPlaintext->dataCount],
+ &buff->bytes[buff->dataStart], count);
+ leftoverPlaintext->dataCount += count;
+ buff->dataCount -= count;
+ buff->dataStart += count;
+ // Prepare to pass the buffer up. Beware that the read callback
+ // may do an unread(), so move the leftoverPlaintext pointer
+ // out of the way. It also may release the buffer back to aio,
+ // so in either event, the pointer passed to the callback is not
+ // valid on return.
+ temp = leftoverPlaintext;
+ leftoverPlaintext = 0;
+ }
+ else {
+ // All decrypted data used up, decrypt some more or get more from the aio
+ if (extraLength) {
+ buff->dataStart = extraBytes - buff->bytes;
+ buff->dataCount = extraLength;
+ sslDataIn(a, buff);
+ return;
+ }
+ else {
+ a.queueReadBuffer(buff);
+ return;
+ }
+ }
+ }
+ else {
+ // Use buff, but first offload data not yet encrypted
+ if (extraLength) {
+ // Very important to get this buffer from the downstream aio.
+ // The ones constructed from the local getQueuedBuffer() are
+ // restricted size for encrypting. However, data coming up from
+ // TCP may have a bunch of SSL segments coalesced and be much
+ // larger than the maximum single SSL segment.
+ extraBuff = a.getQueuedBuffer();
+ if (0 == extraBuff) {
+ // No leftoverPlaintext, so a spare buffer should be available
+ throw QPID_WINDOWS_ERROR(WSAENOBUFS);
+ }
+ memmove(extraBuff->bytes, extraBytes, extraLength);
+ extraBuff->dataCount = extraLength;
+ extraLength = 0;
+ }
+ temp = buff;
+ buff = 0;
+ }
+ if (readCallback) {
+ // The callback guard here is to prevent an upcall from deleting
+ // this out from under us via queueForDeletion().
+ readCallback(*this, temp);
+ }
+ else
+ a.queueReadBuffer(temp); // What else can we do with this???
+ } while (buff != 0);
+
+ // Ok, the current decrypted data is done. If there was any extra data,
+ // go back and handle that.
+ if (extraBuff != 0) {
+ sslDataIn(a, extraBuff);
+ }
+}
+
+void SslAsynchIO::idle(qpid::sys::AsynchIO&) {
+ // Don't relay idle indication to layer above until SSL session is up.
+ if (state == Running) {
+ state = Running;
+ if (idleCallback)
+ idleCallback(*this);
+ }
+}
+
+/**************************************************/
+
+namespace {
+
+bool unsafeNegotiatedTlsVersion(CtxtHandle &ctxtHandle) {
+ // See if SChannel ultimately negotiated <= SSL3, perhaps due to
+ // global registry settings.
+ SecPkgContext_ConnectionInfo info;
+ ::QueryContextAttributes(&ctxtHandle, SECPKG_ATTR_CONNECTION_INFO, &info);
+ // Ascending bit patterns denote newer SSL/TLS protocol versions
+ return (info.dwProtocol < SP_PROT_TLS1_SERVER) ? true : false;
+}
+
+} // namespace
+
+/**************************************************/
+
+ClientSslAsynchIO::ClientSslAsynchIO(const std::string& brokerHost,
+ const qpid::sys::Socket& s,
+ CredHandle hCred,
+ ReadCallback rCb,
+ EofCallback eofCb,
+ DisconnectCallback disCb,
+ ClosedCallback cCb,
+ BuffersEmptyCallback eCb,
+ IdleCallback iCb,
+ NegotiateDoneCallback nCb) :
+ SslAsynchIO(s, hCred, rCb, eofCb, disCb, cCb, eCb, iCb, nCb),
+ serverHost(brokerHost), clientCertRequested(false)
+{
+}
+
+void ClientSslAsynchIO::startNegotiate() {
+ // SEC_CHAR is non-const, so do all the typing here.
+ SEC_CHAR *host = const_cast<SEC_CHAR *>(serverHost.c_str());
+
+ // Need a buffer to receive the token to send to the server.
+ BufferBase *buff = aio->getQueuedBuffer();
+ ULONG ctxtRequested = ISC_REQ_STREAM | ISC_REQ_USE_SUPPLIED_CREDS;
+ ULONG ctxtAttrs;
+ // sendBuffs gets information to forward to the peer.
+ SecBuffer sendBuffs[2];
+ sendBuffs[0].cbBuffer = buff->byteCount;
+ sendBuffs[0].BufferType = SECBUFFER_TOKEN;
+ sendBuffs[0].pvBuffer = buff->bytes;
+ sendBuffs[1].cbBuffer = 0;
+ sendBuffs[1].BufferType = SECBUFFER_EMPTY;
+ sendBuffs[1].pvBuffer = 0;
+ SecBufferDesc sendBuffDesc;
+ sendBuffDesc.ulVersion = SECBUFFER_VERSION;
+ sendBuffDesc.cBuffers = 2;
+ sendBuffDesc.pBuffers = sendBuffs;
+ SECURITY_STATUS status = ::InitializeSecurityContext(&credHandle,
+ NULL,
+ host,
+ ctxtRequested,
+ 0,
+ 0,
+ NULL,
+ 0,
+ &ctxtHandle,
+ &sendBuffDesc,
+ &ctxtAttrs,
+ NULL);
+
+ if (status == SEC_I_CONTINUE_NEEDED) {
+ buff->dataCount = sendBuffs[0].cbBuffer;
+ aio->queueWrite(buff);
+ }
+}
+
+void ClientSslAsynchIO::negotiateStep(BufferBase* buff) {
+ // SEC_CHAR is non-const, so do all the typing here.
+ SEC_CHAR *host = const_cast<SEC_CHAR *>(serverHost.c_str());
+ ULONG ctxtRequested = ISC_REQ_STREAM | ISC_REQ_USE_SUPPLIED_CREDS;
+ ULONG ctxtAttrs;
+
+ // tokenBuffs describe the buffer that's coming in. It should have
+ // a token from the SSL server.
+ SecBuffer tokenBuffs[2];
+ tokenBuffs[0].cbBuffer = buff ? buff->dataCount : 0;
+ tokenBuffs[0].BufferType = SECBUFFER_TOKEN;
+ tokenBuffs[0].pvBuffer = buff ? buff->bytes : 0;
+ tokenBuffs[1].cbBuffer = 0;
+ tokenBuffs[1].BufferType = SECBUFFER_EMPTY;
+ tokenBuffs[1].pvBuffer = 0;
+ SecBufferDesc tokenBuffDesc;
+ tokenBuffDesc.ulVersion = SECBUFFER_VERSION;
+ tokenBuffDesc.cBuffers = 2;
+ tokenBuffDesc.pBuffers = tokenBuffs;
+
+ // Need a buffer to receive any token to send back to the server.
+ BufferBase *sendbuff = aio->getQueuedBuffer();
+ // sendBuffs gets information to forward to the peer.
+ SecBuffer sendBuffs[2];
+ sendBuffs[0].cbBuffer = sendbuff->byteCount;
+ sendBuffs[0].BufferType = SECBUFFER_TOKEN;
+ sendBuffs[0].pvBuffer = sendbuff->bytes;
+ sendBuffs[1].cbBuffer = 0;
+ sendBuffs[1].BufferType = SECBUFFER_EMPTY;
+ sendBuffs[1].pvBuffer = 0;
+ SecBufferDesc sendBuffDesc;
+ sendBuffDesc.ulVersion = SECBUFFER_VERSION;
+ sendBuffDesc.cBuffers = 2;
+ sendBuffDesc.pBuffers = sendBuffs;
+
+ SECURITY_STATUS status = ::InitializeSecurityContext(&credHandle,
+ &ctxtHandle,
+ host,
+ ctxtRequested,
+ 0,
+ 0,
+ &tokenBuffDesc,
+ 0,
+ NULL,
+ &sendBuffDesc,
+ &ctxtAttrs,
+ NULL);
+
+ if (status == SEC_E_INCOMPLETE_MESSAGE) {
+ // Not enough - get more data from the server then try again.
+ aio->unread(buff);
+ aio->queueReadBuffer(sendbuff); // Don't need this one for now...
+ return;
+ }
+ // Done with the buffer that came in...
+ if (buff)
+ aio->queueReadBuffer(buff);
+ if (status == SEC_I_CONTINUE_NEEDED) {
+ // check if server has requested a client certificate
+ if (!clientCertRequested) {
+ SecPkgContext_IssuerListInfoEx caList;
+ memset(&caList, 0, sizeof(caList));
+ ::QueryContextAttributes(&ctxtHandle, SECPKG_ATTR_ISSUER_LIST_EX, &caList);
+ if (caList.cIssuers > 0)
+ clientCertRequested = true;
+ if (caList.aIssuers)
+ ::FreeContextBuffer(caList.aIssuers);
+ }
+
+ sendbuff->dataCount = sendBuffs[0].cbBuffer;
+ aio->queueWrite(sendbuff);
+ return;
+ }
+ // Nothing to send back to the server...
+ aio->queueReadBuffer(sendbuff);
+
+ if (status == SEC_E_OK && unsafeNegotiatedTlsVersion(ctxtHandle)) {
+ // Refuse a connection that negotiates to less than TLS 1.0.
+ QPID_LOG(notice, "client SSL negotiation to unsafe protocol version.");
+ status = SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ // SEC_I_CONTEXT_EXPIRED means session stop complete; SEC_E_OK can be
+ // either session stop or negotiation done (session up).
+ if (status == SEC_E_OK || status == SEC_I_CONTEXT_EXPIRED)
+ negotiationDone();
+ else {
+ if (clientCertRequested && status == SEC_E_CERT_UNKNOWN)
+ // ISC_REQ_USE_SUPPLIED_CREDS makes us reponsible for this case
+ // (no client cert). Map it to its counterpart:
+ status = SEC_E_INCOMPLETE_CREDENTIALS;
+ negotiationFailed(status);
+ }
+}
+
+/*************************************************/
+
+ServerSslAsynchIO::ServerSslAsynchIO(bool clientMustAuthenticate,
+ const qpid::sys::Socket& s,
+ CredHandle hCred,
+ ReadCallback rCb,
+ EofCallback eofCb,
+ DisconnectCallback disCb,
+ ClosedCallback cCb,
+ BuffersEmptyCallback eCb,
+ IdleCallback iCb,
+ NegotiateDoneCallback nCb) :
+ SslAsynchIO(s, hCred, rCb, eofCb, disCb, cCb, eCb, iCb, nCb),
+ clientAuth(clientMustAuthenticate)
+{
+}
+
+void ServerSslAsynchIO::startNegotiate() {
+ // Nothing... need the client to send a token first.
+}
+
+void ServerSslAsynchIO::negotiateStep(BufferBase* buff) {
+ ULONG ctxtRequested = ASC_REQ_STREAM;
+ if (clientAuth)
+ ctxtRequested |= ASC_REQ_MUTUAL_AUTH;
+ ULONG ctxtAttrs;
+
+ // tokenBuffs describe the buffer that's coming in. It should have
+ // a token from the SSL server except if shutting down or renegotiating.
+ SecBuffer tokenBuffs[2];
+ tokenBuffs[0].cbBuffer = buff ? buff->dataCount : 0;
+ tokenBuffs[0].BufferType = SECBUFFER_TOKEN;
+ tokenBuffs[0].pvBuffer = buff ? buff->bytes : 0;
+ tokenBuffs[1].cbBuffer = 0;
+ tokenBuffs[1].BufferType = SECBUFFER_EMPTY;
+ tokenBuffs[1].pvBuffer = 0;
+ SecBufferDesc tokenBuffDesc;
+ tokenBuffDesc.ulVersion = SECBUFFER_VERSION;
+ tokenBuffDesc.cBuffers = 2;
+ tokenBuffDesc.pBuffers = tokenBuffs;
+
+ // Need a buffer to receive any token to send back to the server.
+ BufferBase *sendbuff = aio->getQueuedBuffer();
+ // sendBuffs gets information to forward to the peer.
+ SecBuffer sendBuffs[2];
+ sendBuffs[0].cbBuffer = sendbuff->byteCount;
+ sendBuffs[0].BufferType = SECBUFFER_TOKEN;
+ sendBuffs[0].pvBuffer = sendbuff->bytes;
+ sendBuffs[1].cbBuffer = 0;
+ sendBuffs[1].BufferType = SECBUFFER_EMPTY;
+ sendBuffs[1].pvBuffer = 0;
+ SecBufferDesc sendBuffDesc;
+ sendBuffDesc.ulVersion = SECBUFFER_VERSION;
+ sendBuffDesc.cBuffers = 2;
+ sendBuffDesc.pBuffers = sendBuffs;
+ PCtxtHandle ctxtHandlePtr = (SecIsValidHandle(&ctxtHandle)) ? &ctxtHandle : 0;
+ SECURITY_STATUS status = ::AcceptSecurityContext(&credHandle,
+ ctxtHandlePtr,
+ &tokenBuffDesc,
+ ctxtRequested,
+ 0,
+ &ctxtHandle,
+ &sendBuffDesc,
+ &ctxtAttrs,
+ NULL);
+ if (status == SEC_E_INCOMPLETE_MESSAGE) {
+ // Not enough - get more data from the server then try again.
+ if (buff)
+ aio->unread(buff);
+ aio->queueReadBuffer(sendbuff); // Don't need this one for now...
+ return;
+ }
+ // Done with the buffer that came in...
+ if (buff)
+ aio->queueReadBuffer(buff);
+ if (status == SEC_I_CONTINUE_NEEDED) {
+ sendbuff->dataCount = sendBuffs[0].cbBuffer;
+ aio->queueWrite(sendbuff);
+ return;
+ }
+ // There may have been a token generated; if so, send it to the client.
+ if (sendBuffs[0].cbBuffer > 0 && state != ShuttingDown) {
+ sendbuff->dataCount = sendBuffs[0].cbBuffer;
+ aio->queueWrite(sendbuff);
+ }
+ else
+ // Nothing to send back to the server...
+ aio->queueReadBuffer(sendbuff);
+
+ if (status == SEC_E_OK && unsafeNegotiatedTlsVersion(ctxtHandle)) {
+ // Refuse a connection that negotiates to less than TLS 1.0.
+ QPID_LOG(notice, "server SSL negotiation to unsafe protocol version.");
+ status = SEC_E_UNSUPPORTED_FUNCTION;
+ }
+
+ // SEC_I_CONTEXT_EXPIRED means session stop complete; SEC_E_OK can be
+ // either session stop or negotiation done (session up).
+ if (status == SEC_E_OK || status == SEC_I_CONTEXT_EXPIRED) {
+ if (clientAuth)
+ QPID_LOG(warning, "DID WE CHECK FOR CLIENT AUTH???");
+
+ negotiationDone();
+ }
+ else {
+ negotiationFailed(status);
+ }
+}
+
+}}} // namespace qpid::sys::windows
diff --git a/qpid/cpp/src/qpid/sys/windows/SslAsynchIO.h b/qpid/cpp/src/qpid/sys/windows/SslAsynchIO.h
new file mode 100644
index 0000000000..3d00e1c429
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/SslAsynchIO.h
@@ -0,0 +1,192 @@
+#ifndef _sys_windows_SslAsynchIO
+#define _sys_windows_SslAsynchIO
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/AsynchIO.h"
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/sys/Poller.h"
+#include "qpid/CommonImportExport.h"
+#include "qpid/sys/Mutex.h"
+#include <boost/function.hpp>
+#include <boost/shared_ptr.hpp>
+#include <windows.h>
+// security.h needs to see this to distinguish from kernel use.
+#define SECURITY_WIN32
+#include <security.h>
+#include <Schnlsp.h>
+#undef SECURITY_WIN32
+
+namespace qpid {
+namespace sys {
+namespace windows {
+
+/*
+ * SSL/Schannel shim between the frame-handling and AsynchIO layers.
+ * SslAsynchIO creates a regular AsynchIO object to handle I/O and this class
+ * gets involved for SSL negotiations and encrypt/decrypt. The details of
+ * how this all works are invisible to the layers on either side. The only
+ * change from normal AsynchIO usage is that there's an extra callback
+ * from SslAsynchIO to indicate that the initial session negotiation is
+ * complete.
+ *
+ * The details of session negotiation are different for client and server
+ * SSL roles. These differences are handled by deriving separate client
+ * and server role classes.
+ */
+class SslAsynchIO : public qpid::sys::AsynchIO {
+public:
+ typedef boost::function1<void, SECURITY_STATUS> NegotiateDoneCallback;
+
+ SslAsynchIO(const qpid::sys::Socket& s,
+ CredHandle hCred,
+ ReadCallback rCb,
+ EofCallback eofCb,
+ DisconnectCallback disCb,
+ ClosedCallback cCb = 0,
+ BuffersEmptyCallback eCb = 0,
+ IdleCallback iCb = 0,
+ NegotiateDoneCallback nCb = 0);
+ ~SslAsynchIO();
+
+ virtual void queueForDeletion();
+
+ virtual void start(qpid::sys::Poller::shared_ptr poller);
+ virtual void createBuffers(uint32_t size);
+ virtual void queueReadBuffer(BufferBase* buff);
+ virtual void unread(BufferBase* buff);
+ virtual void queueWrite(BufferBase* buff);
+ virtual void notifyPendingWrite();
+ virtual void queueWriteClose();
+ virtual bool writeQueueEmpty();
+ virtual void requestCallback(RequestCallback);
+ virtual BufferBase* getQueuedBuffer();
+ virtual SecuritySettings getSecuritySettings(void);
+
+protected:
+ CredHandle credHandle;
+
+ // AsynchIO layer below that's actually doing the I/O
+ qpid::sys::AsynchIO *aio;
+ Mutex lock;
+
+ // Track what the state of the SSL session is. Have to know when it's
+ // time to notify the upper layer that the session is up, and also to
+ // know when it's not legit to pass data through to either side.
+ enum { Negotiating, Running, Redo, ShuttingDown } state;
+ CtxtHandle ctxtHandle;
+ TimeStamp credExpiry;
+
+ // Client- and server-side SSL subclasses implement these to do the
+ // proper negotiation steps. negotiateStep() is called with a buffer
+ // just received from the peer.
+ virtual void startNegotiate() = 0;
+ virtual void negotiateStep(BufferBase *buff) = 0;
+
+ // The negotiating steps call one of these when it's finalized:
+ void negotiationDone();
+ void negotiationFailed(SECURITY_STATUS status);
+
+private:
+ // These are callbacks from AsynchIO to here.
+ void sslDataIn(qpid::sys::AsynchIO& a, BufferBase *buff);
+ void idle(qpid::sys::AsynchIO&);
+ void reapCheck();
+
+ // These callbacks are to the layer above.
+ ReadCallback readCallback;
+ IdleCallback idleCallback;
+ NegotiateDoneCallback negotiateDoneCallback;
+
+ volatile bool queuedDelete;
+ volatile bool queuedClose;
+ volatile bool reapCheckPending;
+ bool started;
+
+ // Address of peer, in case it's needed for logging.
+ std::string peerAddress;
+
+ // Partial buffer of decrypted plaintext given back by the layer above.
+ AsynchIO::BufferBase *leftoverPlaintext;
+
+ SecPkgContext_StreamSizes schSizes;
+};
+
+/*
+ * SSL/Schannel client-side shim between the frame-handling and AsynchIO
+ * layers.
+ */
+class ClientSslAsynchIO : public SslAsynchIO {
+public:
+ // Args same as for SslIoShim, with the addition of brokerHost which is
+ // the expected SSL name of the server.
+ QPID_COMMON_EXTERN ClientSslAsynchIO(const std::string& brokerHost,
+ const qpid::sys::Socket& s,
+ CredHandle hCred,
+ ReadCallback rCb,
+ EofCallback eofCb,
+ DisconnectCallback disCb,
+ ClosedCallback cCb = 0,
+ BuffersEmptyCallback eCb = 0,
+ IdleCallback iCb = 0,
+ NegotiateDoneCallback nCb = 0);
+
+private:
+ std::string serverHost;
+ bool clientCertRequested;
+
+ // Client- and server-side SSL subclasses implement these to do the
+ // proper negotiation steps. negotiateStep() is called with a buffer
+ // just received from the peer.
+ void startNegotiate();
+ void negotiateStep(BufferBase *buff);
+};
+/*
+ * SSL/Schannel server-side shim between the frame-handling and AsynchIO
+ * layers.
+ */
+class ServerSslAsynchIO : public SslAsynchIO {
+public:
+ QPID_COMMON_EXTERN ServerSslAsynchIO(bool clientMustAuthenticate,
+ const qpid::sys::Socket& s,
+ CredHandle hCred,
+ ReadCallback rCb,
+ EofCallback eofCb,
+ DisconnectCallback disCb,
+ ClosedCallback cCb = 0,
+ BuffersEmptyCallback eCb = 0,
+ IdleCallback iCb = 0,
+ NegotiateDoneCallback nCb = 0);
+
+private:
+ bool clientAuth;
+
+ // Client- and server-side SSL subclasses implement these to do the
+ // proper negotiation steps. negotiateStep() is called with a buffer
+ // just received from the peer.
+ void startNegotiate();
+ void negotiateStep(BufferBase *buff);
+};
+
+}}} // namespace qpid::sys::windows
+
+#endif // _sys_windows_SslAsynchIO
diff --git a/qpid/cpp/src/qpid/sys/windows/SslCredential.cpp b/qpid/cpp/src/qpid/sys/windows/SslCredential.cpp
new file mode 100644
index 0000000000..de8f10b0e9
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/SslCredential.cpp
@@ -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.
+ *
+ */
+
+
+#include <string>
+#include <windows.h>
+#include "qpid/Msg.h"
+#include "qpid/log/Logger.h"
+#include "qpid/sys/windows/check.h"
+#include "qpid/sys/windows/util.h"
+#include "qpid/sys/windows/SslCredential.h"
+
+
+namespace qpid {
+namespace sys {
+namespace windows {
+
+
+SslCredential::SslCredential() : certStore(0), cert(0), hostnameVerification(true)
+{
+ SecInvalidateHandle(&credHandle);
+ memset(&cred, 0, sizeof(cred));
+ cred.dwVersion = SCHANNEL_CRED_VERSION;
+ cred.dwFlags = SCH_CRED_NO_DEFAULT_CREDS;
+}
+
+SslCredential::~SslCredential()
+{
+ if (SecIsValidHandle(&credHandle))
+ ::FreeCredentialsHandle(&credHandle);
+ if (cert)
+ ::CertFreeCertificateContext(cert);
+ if (certStore)
+ ::CertCloseStore(certStore, CERT_CLOSE_STORE_FORCE_FLAG);
+}
+
+bool SslCredential::load(const std::string& certName)
+{
+ cert = findCertificate(certName);
+ if (cert != NULL) {
+ // assign the certificate into the credentials
+ cred.paCred = &cert;
+ cred.cCreds = 1;
+ }
+ if (!hostnameVerification)
+ cred.dwFlags |= SCH_CRED_NO_SERVERNAME_CHECK;
+
+ SECURITY_STATUS status = ::AcquireCredentialsHandle(NULL,
+ UNISP_NAME,
+ SECPKG_CRED_OUTBOUND,
+ NULL,
+ &cred,
+ NULL,
+ NULL,
+ &credHandle,
+ &credExpiry);
+ if (status != SEC_E_OK)
+ throw QPID_WINDOWS_ERROR(status);
+
+ return (cert != NULL);
+}
+
+CredHandle SslCredential::handle()
+{
+ return credHandle;
+}
+
+std::string SslCredential::error()
+{
+ // Certificate needed after all. Return main error and log additional context
+ if (!loadError.logMessage.empty())
+ QPID_LOG(warning, loadError.logMessage);
+ return loadError.error;
+}
+
+void SslCredential::ignoreHostnameVerificationFailure(){
+ hostnameVerification = false;
+}
+
+void SslCredential::loadPrivCertStore()
+{
+ // Get a handle to the system store or pkcs#12 file
+ qpid::sys::ssl::SslOptions& opts = qpid::sys::ssl::SslOptions::global;
+ if (opts.certFilename.empty()) {
+ // opening a system store, names are not case sensitive
+ std::string store = opts.certStore.empty() ? "my" : opts.certStore;
+ std::transform(store.begin(), store.end(), store.begin(), ::tolower);
+ // map confusing GUI name to actual registry store name
+ if (store == "personal")
+ store = "my";
+ certStore = ::CertOpenStore(CERT_STORE_PROV_SYSTEM_A, 0, NULL,
+ CERT_STORE_OPEN_EXISTING_FLAG | CERT_STORE_READONLY_FLAG |
+ CERT_SYSTEM_STORE_CURRENT_USER, store.c_str());
+ if (!certStore) {
+ HRESULT status = GetLastError();
+ loadError.set(Msg() << "Could not open system certificate store: " << store, status);
+ return;
+ }
+ QPID_LOG(debug, "SslConnector using certifcates from system store: " << store);
+ } else {
+ // opening the store from file and populating it with a private key
+ HANDLE certFileHandle = NULL;
+ certFileHandle = CreateFile(opts.certFilename.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ if (INVALID_HANDLE_VALUE == certFileHandle) {
+ HRESULT status = GetLastError();
+ loadError.set(Msg() << "Failed to open the file holding the private key: " << opts.certFilename, status);
+ return;
+ }
+ std::vector<BYTE> certEncoded;
+ DWORD certEncodedSize = 0L;
+ const DWORD fileSize = GetFileSize(certFileHandle, NULL);
+ if (INVALID_FILE_SIZE != fileSize) {
+ certEncoded.resize(fileSize);
+ bool result = false;
+ result = ReadFile(certFileHandle, &certEncoded[0],
+ fileSize,
+ &certEncodedSize,
+ NULL);
+ if (!result) {
+ // the read failed, return the error as an HRESULT
+ HRESULT status = GetLastError();
+ CloseHandle(certFileHandle);
+ loadError.set(Msg() << "Reading the private key from file failed " << opts.certFilename, status);
+ return;
+ }
+ }
+ else {
+ HRESULT status = GetLastError();
+ loadError.set(Msg() << "Unable to read the certificate file " << opts.certFilename, status);
+ return;
+ }
+ CloseHandle(certFileHandle);
+
+ CRYPT_DATA_BLOB blobData;
+ blobData.cbData = certEncodedSize;
+ blobData.pbData = &certEncoded[0];
+
+ // get passwd from file and convert to null terminated wchar_t (Windows UCS2)
+ std::string passwd = getPasswd(opts.certPasswordFile);
+ if (loadError.pending())
+ return;
+ int pwlen = passwd.length();
+ std::vector<wchar_t> pwUCS2(pwlen + 1, L'\0');
+ int nwc = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, passwd.data(), pwlen, &pwUCS2[0], pwlen);
+ if (!nwc) {
+ HRESULT status = GetLastError();
+ loadError.set("Error converting password from UTF8", status);
+ return;
+ }
+
+ certStore = PFXImportCertStore(&blobData, &pwUCS2[0], 0);
+ if (certStore == NULL) {
+ HRESULT status = GetLastError();
+ loadError.set("Failed to open the certificate store", status);
+ return;
+ }
+ QPID_LOG(debug, "SslConnector using certificate from pkcs#12 file: " << opts.certFilename);
+ }
+}
+
+
+PCCERT_CONTEXT SslCredential::findCertificate(const std::string& name)
+{
+ loadPrivCertStore();
+ if (loadError.pending())
+ return NULL;
+
+ // search for the certificate by Friendly Name
+ PCCERT_CONTEXT tmpctx = NULL;
+ while (tmpctx = CertEnumCertificatesInStore(certStore, tmpctx)) {
+ DWORD len = CertGetNameString(tmpctx, CERT_NAME_FRIENDLY_DISPLAY_TYPE,
+ 0, NULL, NULL, 0);
+ if (len == 1)
+ continue;
+ std::vector<char> ctxname(len);
+ CertGetNameString(tmpctx, CERT_NAME_FRIENDLY_DISPLAY_TYPE,
+ 0, NULL, &ctxname[0], len);
+ bool found = !name.compare(&ctxname[0]);
+ if (found)
+ break;
+ }
+
+ // verify whether some certificate has been found
+ if (tmpctx == NULL) {
+ loadError.set(Msg() << "Client SSL/TLS certificate not found in the certificate store for name " << name,
+ "client certificate not found");
+ }
+ return tmpctx;
+}
+
+
+std::string SslCredential::getPasswd(const std::string& filename)
+{
+ std::string passwd;
+ if (filename == "")
+ return passwd;
+
+ HANDLE pwfHandle = CreateFile(filename.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+
+ if (INVALID_HANDLE_VALUE == pwfHandle) {
+ HRESULT status = GetLastError();
+ loadError.set(Msg() << "Failed to open the password file: " << filename, status);
+ return passwd;
+ }
+
+ const DWORD fileSize = GetFileSize(pwfHandle, NULL);
+ if (fileSize == INVALID_FILE_SIZE) {
+ CloseHandle(pwfHandle);
+ loadError.set("", "Cannot read password file");
+ return passwd;
+ }
+
+ std::vector<char> pwbuf;
+ pwbuf.resize(fileSize);
+ DWORD nbytes = 0;
+ if (!ReadFile(pwfHandle, &pwbuf[0], fileSize, &nbytes, NULL)) {
+ HRESULT status = GetLastError();
+ CloseHandle(pwfHandle);
+ loadError.set("Error reading password file", status);
+ return passwd;
+ }
+ CloseHandle(pwfHandle);
+
+ if (nbytes == 0)
+ return passwd;
+
+ while (nbytes) {
+ if ((pwbuf[nbytes-1] == 012) || (pwbuf[nbytes-1] == 015))
+ nbytes--;
+ else
+ break;
+ }
+
+ if (nbytes)
+ passwd.assign(&pwbuf[0], nbytes);
+
+ return passwd;
+}
+
+void SslCredential::SavedError::set(const std::string &lm, const std::string es) {
+ logMessage = lm;
+ error = es;
+}
+
+void SslCredential::SavedError::set(const std::string &lm, int status) {
+ logMessage = lm;
+ error = qpid::sys::strError(status);
+}
+
+void SslCredential::SavedError::clear() {
+ logMessage.clear();
+ error.clear();
+}
+
+bool SslCredential::SavedError::pending() {
+ return !logMessage.empty() || !error.empty();
+}
+
+}}}
diff --git a/qpid/cpp/src/qpid/sys/windows/SslCredential.h b/qpid/cpp/src/qpid/sys/windows/SslCredential.h
new file mode 100644
index 0000000000..25d174a2fa
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/SslCredential.h
@@ -0,0 +1,84 @@
+#ifndef _sys_SslCredential
+#define _sys_SslCredential
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/CommonImportExport.h"
+
+#include <string.h>
+// security.h needs to see this to distinguish from kernel use.
+#define SECURITY_WIN32
+#include <security.h>
+#include <Schnlsp.h>
+#undef SECURITY_WIN32
+
+namespace qpid {
+namespace sys {
+namespace windows {
+
+/*
+ * Manage certificate data structures for SChannel.
+ *
+ * Note on client certificates: The Posix/NSS implementation performs a lazy
+ * client certificate search part way through the ssl handshake if the server
+ * requests one. Here, it is not known in advance if the server will
+ * request the certificate so the certificate is pre-loaded (even if never
+ * used). To match the Linux behavior, client certificate load problems are
+ * remembered and reported later if appropriate, but do not prevent the
+ * connection attempt.
+ */
+
+class SslCredential {
+public:
+ QPID_COMMON_EXTERN SslCredential();
+ QPID_COMMON_EXTERN ~SslCredential();
+ QPID_COMMON_EXTERN bool load(const std::string& certName);
+ QPID_COMMON_EXTERN CredHandle handle();
+ QPID_COMMON_EXTERN std::string error();
+ /** Proceed with connect inspite of hostname verifcation failures*/
+ QPID_COMMON_EXTERN void ignoreHostnameVerificationFailure();
+
+private:
+ struct SavedError {
+ std::string logMessage;
+ std::string error;
+ void set(const std::string &lm, const std::string es);
+ void set(const std::string &lm, int status);
+ void clear();
+ bool pending();
+ };
+
+ HCERTSTORE certStore;
+ PCCERT_CONTEXT cert;
+ SCHANNEL_CRED cred;
+ CredHandle credHandle;
+ TimeStamp credExpiry;
+ SavedError loadError;
+ bool hostnameVerification;
+
+ PCCERT_CONTEXT findCertificate(const std::string& name);
+ void loadPrivCertStore();
+ std::string getPasswd(const std::string& filename);
+};
+
+}}}
+
+#endif // _sys_SslCredential
diff --git a/qpid/cpp/src/qpid/sys/windows/StrError.cpp b/qpid/cpp/src/qpid/sys/windows/StrError.cpp
new file mode 100755
index 0000000000..546d399d16
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/StrError.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/StrError.h"
+#include <string>
+#include <string.h>
+#include <windows.h>
+
+namespace qpid {
+namespace sys {
+
+std::string strError(int err) {
+ const size_t bufsize = 512;
+ char buf[bufsize];
+ buf[0] = 0;
+ if (0 == FormatMessage (FORMAT_MESSAGE_MAX_WIDTH_MASK
+ | FORMAT_MESSAGE_FROM_SYSTEM,
+ 0,
+ err,
+ 0, // Default language
+ buf,
+ bufsize,
+ 0))
+ {
+#ifdef _MSC_VER
+ strerror_s(buf, bufsize, err);
+#else
+ return std::string(strerror(err));
+#endif
+ }
+ return std::string(buf);
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/sys/windows/SystemInfo.cpp b/qpid/cpp/src/qpid/sys/windows/SystemInfo.cpp
new file mode 100755
index 0000000000..fb58d53b81
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/SystemInfo.cpp
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+/* GetNativeSystemInfo call requires _WIN32_WINNT 0x0501 or higher */
+#ifndef _WIN32_WINNT
+# define _WIN32_WINNT 0x0501
+#endif
+
+#include "qpid/sys/SystemInfo.h"
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/Exception.h"
+#include "qpid/log/Statement.h"
+
+#include <assert.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#include <windows.h>
+#include <tlhelp32.h>
+
+#ifndef HOST_NAME_MAX
+# define HOST_NAME_MAX 256
+#endif
+
+namespace qpid {
+namespace sys {
+
+long SystemInfo::concurrency() {
+ SYSTEM_INFO sys_info;
+ ::GetSystemInfo (&sys_info);
+ long activeProcessors = 0;
+ DWORD_PTR mask = sys_info.dwActiveProcessorMask;
+ while (mask != 0) {
+ if (mask & 1)
+ ++activeProcessors;
+ mask >>= 1;
+ }
+ return activeProcessors;
+}
+
+bool SystemInfo::getLocalHostname (Address &address) {
+ char name[HOST_NAME_MAX];
+ if (::gethostname(name, sizeof(name)) != 0) {
+ errno = WSAGetLastError();
+ return false;
+ }
+ address.host = name;
+ return true;
+}
+
+static const std::string LOCALHOST("127.0.0.1");
+static const std::string TCP("tcp");
+
+// Null function which always fails to find an network interface name
+bool SystemInfo::getInterfaceAddresses(const std::string&, std::vector<std::string>&)
+{
+ return false;
+}
+
+void SystemInfo::getSystemId (std::string &osName,
+ std::string &nodeName,
+ std::string &release,
+ std::string &version,
+ std::string &machine)
+{
+ osName = "Microsoft Windows";
+
+ char node[MAX_COMPUTERNAME_LENGTH + 1];
+ DWORD nodelen = MAX_COMPUTERNAME_LENGTH + 1;
+ GetComputerName (node, &nodelen);
+ nodeName = node;
+
+ OSVERSIONINFOEX vinfo;
+ vinfo.dwOSVersionInfoSize = sizeof(vinfo);
+ GetVersionEx ((OSVERSIONINFO *)&vinfo);
+
+ SYSTEM_INFO sinfo;
+ GetNativeSystemInfo(&sinfo);
+
+ switch(vinfo.dwMajorVersion) {
+ case 5:
+ switch(vinfo.dwMinorVersion) {
+ case 0:
+ release ="2000";
+ break;
+ case 1:
+ release = "XP";
+ break;
+ case 2:
+ if (sinfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 ||
+ sinfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64)
+ release = "XP-64";
+ else
+ release = "Server 2003";
+ break;
+ default:
+ release = "Windows";
+ }
+ break;
+ case 6:
+ if (vinfo.wProductType == VER_NT_SERVER)
+ release = "Server 2008";
+ else
+ release = "Vista";
+ break;
+ default:
+ release = "Microsoft Windows";
+ }
+ version = vinfo.szCSDVersion;
+
+ switch(sinfo.wProcessorArchitecture) {
+ case PROCESSOR_ARCHITECTURE_AMD64:
+ machine = "x86-64";
+ break;
+ case PROCESSOR_ARCHITECTURE_IA64:
+ machine = "IA64";
+ break;
+ case PROCESSOR_ARCHITECTURE_INTEL:
+ machine = "x86";
+ break;
+ default:
+ machine = "unknown";
+ break;
+ }
+}
+
+uint32_t SystemInfo::getProcessId()
+{
+ return static_cast<uint32_t>(::GetCurrentProcessId());
+}
+
+uint32_t SystemInfo::getParentProcessId()
+{
+ // Only want info for the current process, so ask for something specific.
+ // The module info won't be used here but it keeps the snapshot limited to
+ // the current process so a search through all processes is not needed.
+ HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0);
+ if (snap == INVALID_HANDLE_VALUE)
+ return 0;
+ PROCESSENTRY32 entry;
+ entry.dwSize = sizeof(entry);
+ if (!Process32First(snap, &entry))
+ entry.th32ParentProcessID = 0;
+ CloseHandle(snap);
+ return static_cast<uint32_t>(entry.th32ParentProcessID);
+}
+
+std::string SystemInfo::getProcessName()
+{
+ std::string name;
+
+ // Only want info for the current process, so ask for something specific.
+ // The module info won't be used here but it keeps the snapshot limited to
+ // the current process so a search through all processes is not needed.
+ HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0);
+ if (snap == INVALID_HANDLE_VALUE)
+ return name;
+ PROCESSENTRY32 entry;
+ entry.dwSize = sizeof(entry);
+ if (!Process32First(snap, &entry))
+ entry.szExeFile[0] = '\0';
+ CloseHandle(snap);
+ name = entry.szExeFile;
+ return name;
+}
+
+
+#ifdef _DLL
+namespace windows {
+// set from one or more Qpid DLLs: i.e. in DllMain with DLL_PROCESS_DETACH
+QPID_EXPORT bool processExiting = false;
+QPID_EXPORT bool libraryUnloading = false;
+}
+#endif
+
+bool SystemInfo::threadSafeShutdown()
+{
+#ifdef _DLL
+ if (!windows::processExiting && !windows::libraryUnloading) {
+ // called before exit() or FreeLibrary(), or by a DLL without
+ // a participating DllMain.
+ QPID_LOG(warning, "invalid query for shutdown state");
+ throw qpid::Exception(QPID_MSG("Unable to determine shutdown state."));
+ }
+ return !windows::processExiting;
+#else
+ // Not a DLL: shutdown can only be by exit() or return from main().
+ return false;
+#endif
+}
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/windows/Thread.cpp b/qpid/cpp/src/qpid/sys/windows/Thread.cpp
new file mode 100755
index 0000000000..8034680664
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/Thread.cpp
@@ -0,0 +1,340 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+// Ensure definition of OpenThread in mingw
+#ifndef _WIN32_WINNT
+#define _WIN32_WINNT 0x0501
+#endif
+
+#include "qpid/sys/Thread.h"
+#include "qpid/sys/Runnable.h"
+#include "qpid/sys/windows/check.h"
+#include "qpid/sys/SystemInfo.h"
+
+#include <process.h>
+#include <windows.h>
+
+/*
+ * This implementation distinguishes between two types of thread: Qpid
+ * threads (based on qpid::sys::Runnable) and the rest. It provides a
+ * join() that will not deadlock against the Windows loader lock for
+ * Qpid threads.
+ *
+ * System thread identifiers are unique per Windows thread; thread
+ * handles are not. Thread identifiers can be recycled, but keeping a
+ * handle open against the thread prevents recycling as long as
+ * shared_ptr references to a ThreadPrivate structure remain.
+ *
+ * There is a 1-1 relationship between Qpid threads and their
+ * ThreadPrivate structure. Non-Qpid threads do not need to find the
+ * qpidThreadDone handle, so there may be a 1-many relationship for
+ * them.
+ *
+ * TLS storage is used for a lockless solution for static library
+ * builds. The special case of LoadLibrary/FreeLibrary requires
+ * additional synchronization variables and resource cleanup in
+ * DllMain. _DLL marks the dynamic case.
+ */
+
+namespace qpid {
+namespace sys {
+
+class ThreadPrivate {
+public:
+ friend class Thread;
+ friend unsigned __stdcall runThreadPrivate(void*);
+ typedef boost::shared_ptr<ThreadPrivate> shared_ptr;
+ ~ThreadPrivate();
+
+private:
+ unsigned threadId;
+ HANDLE threadHandle;
+ HANDLE initCompleted;
+ HANDLE qpidThreadDone;
+ Runnable* runnable;
+ shared_ptr keepAlive;
+
+ ThreadPrivate() : threadId(GetCurrentThreadId()), initCompleted(NULL),
+ qpidThreadDone(NULL), runnable(NULL) {
+ threadHandle = OpenThread (SYNCHRONIZE, FALSE, threadId);
+ QPID_WINDOWS_CHECK_CRT_NZ(threadHandle);
+ }
+
+ ThreadPrivate(Runnable* r) : threadHandle(NULL), initCompleted(NULL),
+ qpidThreadDone(NULL), runnable(r) {}
+
+ void start(shared_ptr& p);
+ static shared_ptr createThread(Runnable* r);
+};
+
+}} // namespace qpid::sys
+
+
+namespace {
+using namespace qpid::sys;
+
+#ifdef _DLL
+class ScopedCriticalSection
+{
+ public:
+ ScopedCriticalSection(CRITICAL_SECTION& cs) : criticalSection(cs) { EnterCriticalSection(&criticalSection); }
+ ~ScopedCriticalSection() { LeaveCriticalSection(&criticalSection); }
+ private:
+ CRITICAL_SECTION& criticalSection;
+};
+
+CRITICAL_SECTION threadLock;
+long runningThreads = 0;
+HANDLE threadsDone;
+bool terminating = false;
+#endif
+
+
+DWORD volatile tlsIndex = TLS_OUT_OF_INDEXES;
+
+DWORD getTlsIndex() {
+ if (tlsIndex != TLS_OUT_OF_INDEXES)
+ return tlsIndex; // already set
+
+ DWORD trialIndex = TlsAlloc();
+ QPID_WINDOWS_CHECK_NOT(trialIndex, TLS_OUT_OF_INDEXES); // No OS resource
+
+ // only one thread gets to set the value
+ DWORD actualIndex = (DWORD) InterlockedCompareExchange((LONG volatile *) &tlsIndex, (LONG) trialIndex, (LONG) TLS_OUT_OF_INDEXES);
+ if (actualIndex == TLS_OUT_OF_INDEXES)
+ return trialIndex; // we won the race
+ else {
+ TlsFree(trialIndex);
+ return actualIndex;
+ }
+}
+
+} // namespace
+
+namespace qpid {
+namespace sys {
+
+unsigned __stdcall runThreadPrivate(void* p)
+{
+ ThreadPrivate* threadPrivate = static_cast<ThreadPrivate*>(p);
+ TlsSetValue(getTlsIndex(), threadPrivate);
+
+ WaitForSingleObject (threadPrivate->initCompleted, INFINITE);
+ CloseHandle (threadPrivate->initCompleted);
+ threadPrivate->initCompleted = NULL;
+
+ try {
+ threadPrivate->runnable->run();
+ } catch (...) {
+ // not our concern
+ }
+
+ SetEvent (threadPrivate->qpidThreadDone); // allow join()
+ threadPrivate->keepAlive.reset(); // may run ThreadPrivate destructor
+
+#ifdef _DLL
+ {
+ ScopedCriticalSection l(threadLock);
+ if (--runningThreads == 0)
+ SetEvent(threadsDone);
+ }
+#endif
+ return 0;
+}
+
+
+ThreadPrivate::shared_ptr ThreadPrivate::createThread(Runnable* runnable) {
+ ThreadPrivate::shared_ptr tp(new ThreadPrivate(runnable));
+ tp->start(tp);
+ return tp;
+}
+
+void ThreadPrivate::start(ThreadPrivate::shared_ptr& tp) {
+ getTlsIndex(); // fail here if OS problem, not in new thread
+
+ initCompleted = CreateEvent (NULL, TRUE, FALSE, NULL);
+ QPID_WINDOWS_CHECK_CRT_NZ(initCompleted);
+ qpidThreadDone = CreateEvent (NULL, TRUE, FALSE, NULL);
+ QPID_WINDOWS_CHECK_CRT_NZ(qpidThreadDone);
+
+#ifdef _DLL
+ {
+ ScopedCriticalSection l(threadLock);
+ if (terminating)
+ throw qpid::Exception(QPID_MSG("creating thread after exit/FreeLibrary"));
+ runningThreads++;
+ }
+#endif
+
+ uintptr_t h = _beginthreadex(0,
+ 0,
+ runThreadPrivate,
+ (void *)this,
+ 0,
+ &threadId);
+
+#ifdef _DLL
+ if (h == NULL) {
+ ScopedCriticalSection l(threadLock);
+ if (--runningThreads == 0)
+ SetEvent(threadsDone);
+ }
+#endif
+
+ QPID_WINDOWS_CHECK_CRT_NZ(h);
+
+ // Success
+ keepAlive = tp;
+ threadHandle = reinterpret_cast<HANDLE>(h);
+ SetEvent (initCompleted);
+}
+
+ThreadPrivate::~ThreadPrivate() {
+ if (threadHandle)
+ CloseHandle (threadHandle);
+ if (initCompleted)
+ CloseHandle (initCompleted);
+ if (qpidThreadDone)
+ CloseHandle (qpidThreadDone);
+}
+
+
+Thread::Thread() {}
+
+Thread::Thread(Runnable* runnable) : impl(ThreadPrivate::createThread(runnable)) {}
+
+Thread::Thread(Runnable& runnable) : impl(ThreadPrivate::createThread(&runnable)) {}
+
+Thread::operator bool() {
+ return !!impl;
+}
+
+bool Thread::operator==(const Thread& t) const {
+ if (!impl || !t.impl)
+ return false;
+ return impl->threadId == t.impl->threadId;
+}
+
+bool Thread::operator!=(const Thread& t) const {
+ return !(*this==t);
+}
+
+void Thread::join() {
+ if (impl) {
+ DWORD status;
+ if (impl->runnable) {
+ HANDLE handles[2] = {impl->qpidThreadDone, impl->threadHandle};
+ // wait for either. threadHandle not signalled if loader
+ // lock held (FreeLibrary). qpidThreadDone not signalled
+ // if thread terminated by exit().
+ status = WaitForMultipleObjects (2, handles, false, INFINITE);
+ }
+ else
+ status = WaitForSingleObject (impl->threadHandle, INFINITE);
+ QPID_WINDOWS_CHECK_NOT(status, WAIT_FAILED);
+ }
+}
+
+unsigned long Thread::logId() {
+ return GetCurrentThreadId();
+}
+
+/* static */
+Thread Thread::current() {
+ ThreadPrivate* tlsValue = (ThreadPrivate *) TlsGetValue(getTlsIndex());
+ Thread t;
+ if (tlsValue != NULL) {
+ // called from within Runnable->run(), so keepAlive has positive use count
+ t.impl = tlsValue->keepAlive;
+ }
+ else
+ t.impl.reset(new ThreadPrivate());
+ return t;
+}
+
+}} // namespace qpid::sys
+
+
+#ifdef _DLL
+
+namespace qpid {
+namespace sys {
+namespace windows {
+
+extern bool processExiting;
+extern bool libraryUnloading;
+
+}}} // namespace qpid::sys::SystemInfo
+
+// DllMain: called possibly many times in a process lifetime if dll
+// loaded and freed repeatedly. Be mindful of Windows loader lock
+// and other DllMain restrictions.
+
+BOOL APIENTRY DllMain(HMODULE hm, DWORD reason, LPVOID reserved) {
+ switch (reason) {
+ case DLL_PROCESS_ATTACH:
+ InitializeCriticalSection(&threadLock);
+ threadsDone = CreateEvent(NULL, TRUE, FALSE, NULL);
+ break;
+
+ case DLL_PROCESS_DETACH:
+ terminating = true;
+ if (reserved != NULL) {
+ // process exit(): threads are stopped arbitrarily and
+ // possibly in an inconsistent state. Not even threadLock
+ // can be trusted. All static destructors for this unit
+ // are pending and face the same unsafe environment.
+ // Any resources this unit knows about will be released as
+ // part of process tear down by the OS. Accordingly, skip
+ // any clean up tasks.
+ qpid::sys::windows::processExiting = true;
+ return TRUE;
+ }
+ else {
+ // FreeLibrary(): threads are still running and we are
+ // encouraged to clean up to avoid leaks. Mostly we just
+ // want any straggler threads to finish and notify
+ // threadsDone as the last thing they do.
+ qpid::sys::windows::libraryUnloading = true;
+ while (1) {
+ {
+ ScopedCriticalSection l(threadLock);
+ if (runningThreads == 0)
+ break;
+ ResetEvent(threadsDone);
+ }
+ WaitForSingleObject(threadsDone, INFINITE);
+ }
+ if (tlsIndex != TLS_OUT_OF_INDEXES)
+ TlsFree(getTlsIndex());
+ CloseHandle(threadsDone);
+ DeleteCriticalSection(&threadLock);
+ }
+ break;
+
+ case DLL_THREAD_ATTACH:
+ case DLL_THREAD_DETACH:
+ break;
+ }
+ return TRUE;
+}
+
+#endif
diff --git a/qpid/cpp/src/qpid/sys/windows/Time.cpp b/qpid/cpp/src/qpid/sys/windows/Time.cpp
new file mode 100644
index 0000000000..4169ef1f0a
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/Time.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/Time.h"
+#include <cmath>
+#include <ostream>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/thread/thread_time.hpp>
+#include <windows.h>
+#include <time.h>
+
+using namespace boost::posix_time;
+
+namespace {
+
+// High-res timing support. This will display times since program start,
+// more or less. Keep track of the start value and the conversion factor to
+// seconds.
+bool timeInitialized = false;
+LARGE_INTEGER start_hpc;
+double hpc_freq = 1.0;
+
+double start_time;
+
+/// Static constant to remove time skew between FILETIME and POSIX
+/// time. POSIX and Win32 use different epochs (Jan. 1, 1970 v.s.
+/// Jan. 1, 1601). The following constant defines the difference
+/// in 100ns ticks.
+const DWORDLONG FILETIME_to_timval_skew = 0x19db1ded53e8000;
+
+}
+
+namespace qpid {
+namespace sys {
+
+AbsTime::AbsTime(const AbsTime& t, const Duration& d) {
+ if (d == Duration::max()) {
+ timepoint = ptime(max_date_time);
+ }
+ else {
+ time_duration td = microseconds(d.nanosecs / 1000);
+ timepoint = t.timepoint + td;
+ }
+}
+
+AbsTime AbsTime::FarFuture() {
+ AbsTime ff;
+ ptime maxd(max_date_time);
+ ff.timepoint = maxd;
+ return ff;
+}
+
+AbsTime AbsTime::Zero() {
+ AbsTime time_epoch;
+ time_epoch.timepoint = boost::posix_time::from_time_t(0);
+ return time_epoch;
+}
+
+AbsTime AbsTime::epoch() {
+ AbsTime time_epoch;
+ time_epoch.timepoint = boost::posix_time::from_time_t(0);
+ return time_epoch;
+}
+
+AbsTime AbsTime::now() {
+ AbsTime time_now;
+ time_now.timepoint = boost::get_system_time();
+ return time_now;
+}
+
+Duration Duration::FromEpoch() {
+ time_duration d = boost::get_system_time() - boost::posix_time::from_time_t(0);
+ return d.total_nanoseconds();
+}
+
+Duration::Duration(const AbsTime& start, const AbsTime& finish) {
+ time_duration d = finish.timepoint - start.timepoint;
+ nanosecs = d.total_nanoseconds();
+}
+
+std::ostream& operator<<(std::ostream& o, const Duration& d) {
+ if (d >= TIME_SEC) return o << (double(d)/TIME_SEC) << "s";
+ if (d >= TIME_MSEC) return o << (double(d)/TIME_MSEC) << "ms";
+ if (d >= TIME_USEC) return o << (double(d)/TIME_USEC) << "us";
+ return o << int64_t(d) << "ns";
+}
+
+std::istream& operator>>(std::istream& i, Duration& d) {
+ // Don't throw, let the istream throw if it's configured to do so.
+ double number;
+ i >> number;
+ if (i.fail()) return i;
+
+ if (i.eof() || std::isspace(i.peek())) // No suffix
+ d = number*TIME_SEC;
+ else {
+ std::string suffix;
+ i >> suffix;
+ if (i.fail()) return i;
+ if (suffix.compare("s") == 0) d = number*TIME_SEC;
+ else if (suffix.compare("ms") == 0) d = number*TIME_MSEC;
+ else if (suffix.compare("us") == 0) d = number*TIME_USEC;
+ else if (suffix.compare("ns") == 0) d = number*TIME_NSEC;
+ else i.setstate(std::ios::failbit);
+ }
+ return i;
+}
+
+std::ostream& operator<<(std::ostream& o, const AbsTime& t) {
+ std::string time_string = to_simple_string(t.timepoint);
+ return o << time_string;
+}
+
+
+void sleep(int secs) {
+ ::Sleep(secs * 1000);
+}
+
+void usleep(uint64_t usecs) {
+ DWORD msecs = usecs / 1000;
+ if (msecs == 0)
+ msecs = 1;
+ ::Sleep(msecs);
+}
+
+void outputFormattedNow(std::ostream& o) {
+ ::time_t rawtime;
+ ::tm timeinfo;
+ char time_string[100];
+
+ ::time( &rawtime );
+#ifdef _MSC_VER
+ ::localtime_s(&timeinfo, &rawtime);
+#else
+ timeinfo = *(::localtime(&rawtime));
+#endif
+ ::strftime(time_string, 100,
+ "%Y-%m-%d %H:%M:%S",
+ &timeinfo);
+ o << time_string << " ";
+}
+
+void outputHiresNow(std::ostream& o) {
+ ::time_t tv_sec;
+ ::tm timeinfo;
+ char time_string[100];
+
+ if (!timeInitialized) {
+ // To start, get the current time from FILETIME which includes
+ // sub-second resolution. However, since FILETIME is updated a bit
+ // "bumpy" every 15 msec or so, future time displays will be the
+ // starting FILETIME plus a delta based on the high-resolution
+ // performance counter.
+ FILETIME file_time;
+ ULARGE_INTEGER start_usec;
+ ::GetSystemTimeAsFileTime(&file_time); // This is in 100ns units
+ start_usec.LowPart = file_time.dwLowDateTime;
+ start_usec.HighPart = file_time.dwHighDateTime;
+ start_usec.QuadPart -= FILETIME_to_timval_skew;
+ start_usec.QuadPart /= 10; // Convert 100ns to usec
+ tv_sec = (time_t)(start_usec.QuadPart / (1000 * 1000));
+ long tv_usec = (long)(start_usec.QuadPart % (1000 * 1000));
+ start_time = static_cast<double>(tv_sec);
+ start_time += tv_usec / 1000000.0;
+
+ start_hpc.QuadPart = 0;
+ LARGE_INTEGER iFreq;
+ iFreq.QuadPart = 1;
+ QueryPerformanceCounter(&start_hpc);
+ QueryPerformanceFrequency(&iFreq);
+ hpc_freq = static_cast<double>(iFreq.QuadPart);
+ timeInitialized = true;
+ }
+ LARGE_INTEGER hpc_now;
+ hpc_now.QuadPart = 0;
+ QueryPerformanceCounter(&hpc_now);
+ hpc_now.QuadPart -= start_hpc.QuadPart;
+ if (hpc_now.QuadPart < 0)
+ hpc_now.QuadPart = 0;
+ double now = static_cast<double>(hpc_now.QuadPart);
+ now /= hpc_freq; // now is seconds after this
+ double fnow = start_time + now;
+ double usec, sec;
+ usec = modf(fnow, &sec);
+ tv_sec = static_cast<time_t>(sec);
+#ifdef _MSC_VER
+ ::localtime_s(&timeinfo, &tv_sec);
+#else
+ timeinfo = *(::localtime(&tv_sec));
+#endif
+ ::strftime(time_string, 100,
+ "%Y-%m-%d %H:%M:%S",
+ &timeinfo);
+ // No way to set "max field width" to cleanly output the double usec so
+ // convert it back to integral number of usecs and print that.
+ unsigned long i_usec = usec * 1000 * 1000;
+ o << time_string << "." << std::setw(6) << std::setfill('0') << i_usec << " ";
+}
+}}
diff --git a/qpid/cpp/src/qpid/sys/windows/Time.h b/qpid/cpp/src/qpid/sys/windows/Time.h
new file mode 100644
index 0000000000..2987b1c8b2
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/Time.h
@@ -0,0 +1,36 @@
+#ifndef QPID_SYS_WINDOWS_TIME_H
+#define QPID_SYS_WINDOWS_TIME_H
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <boost/date_time/posix_time/posix_time_types.hpp>
+
+namespace qpid {
+namespace sys {
+
+/**
+ * Class to represent an instant in time. Boost has this stuff already done
+ * so just reuse it. We can also grab this for quick use with the Condition
+ * wait operations.
+ */
+typedef boost::posix_time::ptime TimePrivate;
+
+}} // namespace qpid::sys
+
+#endif /*!QPID_SYS_WINDOWS_TIME_H*/
diff --git a/qpid/cpp/src/qpid/sys/windows/WinSocket.cpp b/qpid/cpp/src/qpid/sys/windows/WinSocket.cpp
new file mode 100644
index 0000000000..5637f6a9fb
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/WinSocket.cpp
@@ -0,0 +1,276 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/windows/WinSocket.h"
+
+#include "qpid/sys/SocketAddress.h"
+#include "qpid/sys/windows/check.h"
+#include "qpid/sys/windows/IoHandlePrivate.h"
+#include "qpid/sys/SystemInfo.h"
+
+namespace qpid {
+namespace sys {
+
+// Need to initialize WinSock. Ideally, this would be a singleton or embedded
+// in some one-time initialization function. I tried boost singleton and could
+// not get it to compile (and others located in google had the same problem).
+// So, this simple static with an interlocked increment will do for known
+// use cases at this time. Since this will only shut down winsock at process
+// termination, there may be some problems with client programs that also
+// expect to load and unload winsock, but we'll see...
+// If someone does get an easy-to-use singleton sometime, converting to it
+// may be preferable.
+
+namespace {
+
+static LONG volatile initialized = 0;
+
+class WinSockSetup {
+ // : public boost::details::pool::singleton_default<WinSockSetup> {
+
+public:
+ WinSockSetup() {
+ LONG timesEntered = InterlockedIncrement(&initialized);
+ if (timesEntered > 1)
+ return;
+ err = 0;
+ WORD wVersionRequested;
+ WSADATA wsaData;
+
+ /* Request WinSock 2.2 */
+ wVersionRequested = MAKEWORD(2, 2);
+ err = WSAStartup(wVersionRequested, &wsaData);
+ }
+
+ ~WinSockSetup() {
+ if (SystemInfo::threadSafeShutdown())
+ WSACleanup();
+ }
+
+public:
+ int error(void) const { return err; }
+
+protected:
+ DWORD err;
+};
+
+static WinSockSetup setup;
+
+std::string getName(SOCKET fd, bool local)
+{
+ ::sockaddr_storage name_s; // big enough for any socket address
+ ::sockaddr* name = (::sockaddr*)&name_s;
+ ::socklen_t namelen = sizeof(name_s);
+
+ if (local) {
+ QPID_WINSOCK_CHECK(::getsockname(fd, name, &namelen));
+ } else {
+ QPID_WINSOCK_CHECK(::getpeername(fd, name, &namelen));
+ }
+
+ return SocketAddress::asString(name, namelen);
+}
+
+uint16_t getLocalPort(int fd)
+{
+ ::sockaddr_storage name_s; // big enough for any socket address
+ ::sockaddr* name = (::sockaddr*)&name_s;
+ ::socklen_t namelen = sizeof(name_s);
+
+ QPID_WINSOCK_CHECK(::getsockname(fd, name, &namelen));
+
+ return SocketAddress::getPort(name);
+}
+} // namespace
+
+WinSocket::WinSocket() :
+ handle(new IOHandle),
+ nonblocking(false),
+ nodelay(false)
+{}
+
+Socket* createSocket()
+{
+ return new WinSocket;
+}
+
+WinSocket::WinSocket(SOCKET fd) :
+ handle(new IOHandle(fd)),
+ nonblocking(false),
+ nodelay(false)
+{}
+
+WinSocket::operator const IOHandle&() const
+{
+ return *handle;
+}
+
+void WinSocket::createSocket(const SocketAddress& sa) const
+{
+ SOCKET& socket = handle->fd;
+ if (socket != INVALID_SOCKET) WinSocket::close();
+
+ SOCKET s = ::socket (getAddrInfo(sa).ai_family,
+ getAddrInfo(sa).ai_socktype,
+ 0);
+ if (s == INVALID_SOCKET) throw QPID_WINDOWS_ERROR(WSAGetLastError());
+ socket = s;
+
+ try {
+ if (nonblocking) setNonblocking();
+ if (nodelay) setTcpNoDelay();
+ } catch (std::exception&) {
+ ::closesocket(s);
+ socket = INVALID_SOCKET;
+ throw;
+ }
+}
+
+void WinSocket::setNonblocking() const {
+ u_long nonblock = 1;
+ QPID_WINSOCK_CHECK(ioctlsocket(handle->fd, FIONBIO, &nonblock));
+}
+
+void
+WinSocket::connect(const SocketAddress& addr) const
+{
+ peername = addr.asString(false);
+
+ createSocket(addr);
+
+ const SOCKET& socket = handle->fd;
+ int err;
+ WSASetLastError(0);
+ if ((::connect(socket, getAddrInfo(addr).ai_addr, getAddrInfo(addr).ai_addrlen) != 0) &&
+ ((err = ::WSAGetLastError()) != WSAEWOULDBLOCK))
+ throw qpid::Exception(QPID_MSG(strError(err) << ": " << peername));
+}
+
+void
+WinSocket::finishConnect(const SocketAddress&) const
+{
+}
+
+void
+WinSocket::close() const
+{
+ SOCKET& socket = handle->fd;
+ if (socket == INVALID_SOCKET) return;
+ QPID_WINSOCK_CHECK(closesocket(socket));
+ socket = INVALID_SOCKET;
+}
+
+
+int WinSocket::write(const void *buf, size_t count) const
+{
+ const SOCKET& socket = handle->fd;
+ int sent = ::send(socket, (const char *)buf, count, 0);
+ if (sent == SOCKET_ERROR)
+ return -1;
+ return sent;
+}
+
+int WinSocket::read(void *buf, size_t count) const
+{
+ const SOCKET& socket = handle->fd;
+ int received = ::recv(socket, (char *)buf, count, 0);
+ if (received == SOCKET_ERROR)
+ return -1;
+ return received;
+}
+
+int WinSocket::listen(const SocketAddress& addr, int backlog) const
+{
+ createSocket(addr);
+
+ const SOCKET& socket = handle->fd;
+ BOOL yes=1;
+ QPID_WINSOCK_CHECK(setsockopt(socket, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (char *)&yes, sizeof(yes)));
+
+ if (::bind(socket, getAddrInfo(addr).ai_addr, getAddrInfo(addr).ai_addrlen) == SOCKET_ERROR)
+ throw Exception(QPID_MSG("Can't bind to " << addr.asString() << ": " << strError(WSAGetLastError())));
+ if (::listen(socket, backlog) == SOCKET_ERROR)
+ throw Exception(QPID_MSG("Can't listen on " <<addr.asString() << ": " << strError(WSAGetLastError())));
+
+ return getLocalPort(socket);
+}
+
+Socket* WinSocket::accept() const
+{
+ SOCKET afd = ::accept(handle->fd, 0, 0);
+ if (afd != INVALID_SOCKET)
+ return new WinSocket(afd);
+ else if (WSAGetLastError() == EAGAIN)
+ return 0;
+ else throw QPID_WINDOWS_ERROR(WSAGetLastError());
+}
+
+std::string WinSocket::getPeerAddress() const
+{
+ if (peername.empty()) {
+ peername = getName(handle->fd, false);
+ }
+ return peername;
+}
+
+std::string WinSocket::getLocalAddress() const
+{
+ if (localname.empty()) {
+ localname = getName(handle->fd, true);
+ }
+ return localname;
+}
+
+int WinSocket::getError() const
+{
+ int result;
+ socklen_t rSize = sizeof (result);
+
+ QPID_WINSOCK_CHECK(::getsockopt(handle->fd, SOL_SOCKET, SO_ERROR, (char *)&result, &rSize));
+ return result;
+}
+
+void WinSocket::setTcpNoDelay() const
+{
+ SOCKET& socket = handle->fd;
+ nodelay = true;
+ if (socket != INVALID_SOCKET) {
+ int flag = 1;
+ int result = setsockopt(handle->fd,
+ IPPROTO_TCP,
+ TCP_NODELAY,
+ (char *)&flag,
+ sizeof(flag));
+ QPID_WINSOCK_CHECK(result);
+ }
+}
+
+int WinSocket::getKeyLen() const
+{
+ return 0;
+}
+
+std::string WinSocket::getClientAuthId() const
+{
+ return std::string();
+}
+
+}} // namespace qpid::sys
diff --git a/qpid/cpp/src/qpid/sys/windows/WinSocket.h b/qpid/cpp/src/qpid/sys/windows/WinSocket.h
new file mode 100644
index 0000000000..bee6a58e7a
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/WinSocket.h
@@ -0,0 +1,118 @@
+#ifndef QPID_SYS_WINDOWS_BSDSOCKET_H
+#define QPID_SYS_WINDOWS_BSDSOCKET_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/sys/Socket.h"
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/CommonImportExport.h"
+#include <string>
+
+#include <boost/scoped_ptr.hpp>
+
+// Ensure we get all of winsock2.h
+#ifndef _WIN32_WINNT
+#define _WIN32_WINNT 0x0501
+#endif
+
+#include <winsock2.h>
+
+namespace qpid {
+namespace sys {
+
+namespace windows {
+Socket* createSameTypeSocket(const Socket&);
+}
+
+class Duration;
+class IOHandle;
+class SocketAddress;
+
+class QPID_COMMON_CLASS_EXTERN WinSocket : public Socket
+{
+public:
+ /** Create a socket wrapper for descriptor. */
+ QPID_COMMON_EXTERN WinSocket();
+
+ QPID_COMMON_EXTERN operator const IOHandle&() const;
+
+ /** Set socket non blocking */
+ QPID_COMMON_EXTERN virtual void setNonblocking() const;
+
+ QPID_COMMON_EXTERN virtual void setTcpNoDelay() const;
+
+ QPID_COMMON_EXTERN virtual void connect(const SocketAddress&) const;
+ QPID_COMMON_EXTERN virtual void finishConnect(const SocketAddress&) const;
+
+ QPID_COMMON_EXTERN virtual void close() const;
+
+ /** Bind to a port and start listening.
+ *@return The bound port number
+ */
+ QPID_COMMON_EXTERN virtual int listen(const SocketAddress&, int backlog = 10) const;
+
+ /**
+ * Returns an address (host and port) for the remote end of the
+ * socket
+ */
+ QPID_COMMON_EXTERN std::string getPeerAddress() const;
+ /**
+ * Returns an address (host and port) for the local end of the
+ * socket
+ */
+ QPID_COMMON_EXTERN std::string getLocalAddress() const;
+
+ /**
+ * Returns the error code stored in the socket. This may be used
+ * to determine the result of a non-blocking connect.
+ */
+ QPID_COMMON_EXTERN int getError() const;
+
+ /** Accept a connection from a socket that is already listening
+ * and has an incoming connection
+ */
+ QPID_COMMON_EXTERN virtual Socket* accept() const;
+
+ // TODO The following are raw operations, maybe they need better wrapping?
+ QPID_COMMON_EXTERN virtual int read(void *buf, size_t count) const;
+ QPID_COMMON_EXTERN virtual int write(const void *buf, size_t count) const;
+
+ QPID_COMMON_EXTERN int getKeyLen() const;
+ QPID_COMMON_EXTERN std::string getClientAuthId() const;
+
+protected:
+ /** Create socket */
+ void createSocket(const SocketAddress&) const;
+
+ mutable boost::scoped_ptr<IOHandle> handle;
+ mutable std::string localname;
+ mutable std::string peername;
+ mutable bool nonblocking;
+ mutable bool nodelay;
+
+ /** Construct socket with existing handle */
+ friend Socket* qpid::sys::windows::createSameTypeSocket(const Socket&);
+ WinSocket(SOCKET fd);
+};
+
+}}
+#endif /*!QPID_SYS_WINDOWS_BSDSOCKET_H*/
diff --git a/qpid/cpp/src/qpid/sys/windows/check.h b/qpid/cpp/src/qpid/sys/windows/check.h
new file mode 100755
index 0000000000..2a8e439bed
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/check.h
@@ -0,0 +1,49 @@
+#ifndef _windows_check_h
+#define _windows_check_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/Exception.h"
+#include "qpid/Msg.h"
+#include "qpid/sys/StrError.h"
+
+#define QPID_WINDOWS_ERROR(ERRVAL) qpid::Exception(QPID_MSG(qpid::sys::strError(ERRVAL)))
+#define QPID_WINDOWS_CRT_ERROR(ERRNO) qpid::Exception(QPID_MSG(qpid::sys::strError(ERRNO)))
+
+/** THROW QPID_WINDOWS_ERROR(::GetLastError()) if RESULT is NULL */
+#define QPID_WINDOWS_CHECK_NULL(RESULT) \
+ if ((RESULT) == NULL) throw QPID_WINDOWS_ERROR((::GetLastError()))
+
+#define QPID_WINDOWS_CHECK_NOT(RESULT,VAL) \
+ if ((RESULT) == (VAL)) throw QPID_WINDOWS_ERROR((::GetLastError()))
+
+#define QPID_WINDOWS_CHECK_ASYNC_START(STATUS) \
+ if (!(STATUS) && ::WSAGetLastError() != ERROR_IO_PENDING) \
+ throw QPID_WINDOWS_ERROR((::WSAGetLastError()))
+
+#define QPID_WINDOWS_CHECK_CRT_NZ(VAL) \
+ if ((VAL) == 0) throw QPID_WINDOWS_CRT_ERROR(errno)
+
+#define QPID_WINSOCK_CHECK(OP) \
+ if ((OP) == SOCKET_ERROR) throw QPID_WINDOWS_ERROR((::WSAGetLastError()))
+
+#endif /*!_windows_check_h*/
diff --git a/qpid/cpp/src/qpid/sys/windows/mingw32_compat.h b/qpid/cpp/src/qpid/sys/windows/mingw32_compat.h
new file mode 100644
index 0000000000..51f613cc25
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/mingw32_compat.h
@@ -0,0 +1,39 @@
+#ifndef _sys_windows_mingw32_compat
+#define _sys_windows_mingw32_compat
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#ifdef WIN32
+#ifndef _MSC_VER
+
+//
+// The following definitions for extension function GUIDs and signatures are taken from
+// MswSock.h in the Windows32 SDK. These rightfully belong in the mingw32 version of
+// mswsock.h, but are not included presently.
+//
+
+#define WSAID_ACCEPTEX {0xb5367df1,0xcbac,0x11cf,{0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92}}
+typedef BOOL (PASCAL *LPFN_ACCEPTEX)(SOCKET,SOCKET,PVOID,DWORD,DWORD,DWORD,LPDWORD,LPOVERLAPPED);
+
+#endif
+#endif
+
+#endif
diff --git a/qpid/cpp/src/qpid/sys/windows/util.cpp b/qpid/cpp/src/qpid/sys/windows/util.cpp
new file mode 100644
index 0000000000..75aef26c35
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/util.cpp
@@ -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.
+ *
+ */
+#include "qpid/sys/windows/util.h"
+#include "qpid/Exception.h"
+#include "qpid/sys/SystemInfo.h"
+
+#include <iostream>
+#include <fstream>
+
+namespace qpid {
+namespace sys {
+namespace ssl {
+
+static const std::string LOCALHOST("127.0.0.1");
+
+std::string defaultCertName()
+{
+ Address address;
+ if (SystemInfo::getLocalHostname(address)) {
+ return address.host;
+ } else {
+ return LOCALHOST;
+ }
+}
+
+SslOptions::SslOptions() : qpid::Options("SSL Settings"),
+ certName(defaultCertName())
+{
+ addOptions()
+ ("ssl-cert-password-file", optValue(certPasswordFile, "PATH"), "File containing password to use for accessing certificates")
+ ("ssl-cert-store", optValue(certStore, "NAME"), "Windows certificate store containing the certificate")
+ ("ssl-cert-Filename", optValue(certFilename, "PATH"), "Path to PKCS#12 file containing the certificate")
+ ("ssl-cert-name", optValue(certName, "NAME"), "Friendly Name of the certificate to use");
+}
+
+SslOptions& SslOptions::operator=(const SslOptions& o)
+{
+ certStore = o.certStore;
+ certName = o.certName;
+ certPasswordFile = o.certPasswordFile;
+ certFilename = o.certFilename;
+
+ return *this;
+}
+
+SslOptions SslOptions::global;
+
+void initWinSsl(const SslOptions& options, bool)
+{
+ SslOptions::global = options;
+}
+}}} // namespace qpid::sys::ssl
diff --git a/qpid/cpp/src/qpid/sys/windows/util.h b/qpid/cpp/src/qpid/sys/windows/util.h
new file mode 100644
index 0000000000..2855a90955
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/util.h
@@ -0,0 +1,50 @@
+#ifndef QPID_SYS_SSL_UTIL_H
+#define QPID_SYS_SSL_UTIL_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/CommonImportExport.h"
+#include "qpid/Options.h"
+#include <string>
+
+namespace qpid {
+namespace sys {
+namespace ssl {
+
+struct SslOptions : qpid::Options
+{
+ QPID_COMMON_EXTERN static SslOptions global;
+
+ std::string certStore;
+ std::string certName;
+ std::string certPasswordFile;
+ std::string certFilename;
+
+ QPID_COMMON_EXTERN SslOptions();
+ QPID_COMMON_EXTERN SslOptions& operator=(const SslOptions&);
+};
+
+QPID_COMMON_EXTERN void initWinSsl(const SslOptions& options, bool server = false);
+
+}}} // namespace qpid::sys::ssl
+
+#endif /*!QPID_SYS_SSL_UTIL_H*/
diff --git a/qpid/cpp/src/qpid/sys/windows/uuid.cpp b/qpid/cpp/src/qpid/sys/windows/uuid.cpp
new file mode 100644
index 0000000000..4ff75ca627
--- /dev/null
+++ b/qpid/cpp/src/qpid/sys/windows/uuid.cpp
@@ -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.
+ *
+ */
+
+/*
+ * UUIDs and GUIDs (both RFC 4122) differ on byte positions of the
+ * internal representation. This matters when encoding to the wire
+ * and adhering to versioning info. Microsoft APIs used here operate
+ * on GUIDs even if the name implies UUIDs. AMQP expects the UUID 128
+ * bit format which is used here unless otherwise noted.
+ */
+
+#include <rpc.h>
+#ifdef uuid_t /* Done in rpcdce.h */
+# undef uuid_t
+#endif
+
+#include "qpid/sys/uuid.h"
+
+#include <string.h>
+
+namespace {
+inline void iswap (char *p1, char *p2) {
+ char t = *p1;
+ *p1 = *p2;
+ *p2 = t;
+}
+
+void toUuid (const UUID *guid, uint8_t uuid[qpid::sys::UuidSize]) {
+ // copy then swap bytes
+ memcpy ((char *) uuid, (char *) guid, qpid::sys::UuidSize);
+ char *p = (char *) uuid;
+ iswap (p, p+3);
+ iswap (p+1, p+2);
+ iswap (p+4, p+5);
+ iswap (p+6, p+7);
+}
+
+} // namespace
+
+namespace qpid {
+namespace sys {
+
+void uuid_generate (uint8_t out[qpid::sys::UuidSize]) {
+ UUID guid;
+ UuidCreate (&guid);
+ // Version 4 GUID, convert to UUID
+ toUuid (&guid, out);
+}
+
+}}
diff --git a/qpid/cpp/src/qpid/types/Exception.cpp b/qpid/cpp/src/qpid/types/Exception.cpp
new file mode 100644
index 0000000000..71390e6abd
--- /dev/null
+++ b/qpid/cpp/src/qpid/types/Exception.cpp
@@ -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.
+ *
+ */
+#include "qpid/types/Exception.h"
+
+namespace qpid {
+namespace types {
+
+Exception::Exception(const std::string& msg) throw() : message(msg) {}
+Exception::~Exception() throw() {}
+const char* Exception::what() const throw() { return message.c_str(); }
+
+}} // namespace qpid::types
diff --git a/qpid/cpp/src/qpid/types/Uuid.cpp b/qpid/cpp/src/qpid/types/Uuid.cpp
new file mode 100644
index 0000000000..c0e38e1da5
--- /dev/null
+++ b/qpid/cpp/src/qpid/types/Uuid.cpp
@@ -0,0 +1,208 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/types/Uuid.h"
+#include "qpid/sys/uuid.h"
+#include "qpid/sys/IntegerTypes.h"
+#include <sstream>
+#include <iostream>
+#include <iomanip>
+#include <stdio.h>
+#include <string.h>
+
+namespace qpid {
+namespace types {
+
+using namespace std;
+
+const size_t Uuid::SIZE=16;
+static const int UNPARSED_SIZE=36;
+
+Uuid::Uuid(bool unique)
+{
+ if (unique) {
+ generate();
+ } else {
+ clear();
+ }
+}
+
+Uuid::Uuid(const Uuid& other)
+{
+ ::memcpy(bytes, other.bytes, Uuid::SIZE);
+}
+
+Uuid::Uuid(const unsigned char* uuid)
+{
+ ::memcpy(bytes, uuid, Uuid::SIZE);
+}
+
+Uuid::Uuid(const char* uuid)
+{
+ ::memcpy(bytes, uuid, Uuid::SIZE);
+}
+
+Uuid& Uuid::operator=(const Uuid& other)
+{
+ if (this == &other) return *this;
+ ::memcpy(bytes, other.bytes, Uuid::SIZE);
+ return *this;
+}
+
+void Uuid::generate()
+{
+ sys::uuid_generate(bytes);
+}
+
+void Uuid::clear()
+{
+ ::memset(bytes, 0, Uuid::SIZE);
+}
+
+bool Uuid::isNull() const
+{
+ static Uuid nullUuid;
+ return *this == nullUuid;
+}
+
+Uuid::operator bool() const { return !isNull(); }
+bool Uuid::operator!() const { return isNull(); }
+
+size_t Uuid::size() const { return SIZE; }
+
+const unsigned char* Uuid::data() const
+{
+ return bytes;
+}
+
+bool operator==(const Uuid& a, const Uuid& b)
+{
+ return ::memcmp(a.bytes, b.bytes, Uuid::SIZE) == 0;
+}
+
+bool operator!=(const Uuid& a, const Uuid& b)
+{
+ return !(a == b);
+}
+
+bool operator<(const Uuid& a, const Uuid& b)
+{
+ return ::memcmp(a.bytes, b.bytes, Uuid::SIZE) < 0;
+}
+
+bool operator>(const Uuid& a, const Uuid& b)
+{
+ return ::memcmp(a.bytes, b.bytes, Uuid::SIZE) > 0;
+}
+
+bool operator<=(const Uuid& a, const Uuid& b)
+{
+ return ::memcmp(a.bytes, b.bytes, Uuid::SIZE) <= 0;
+}
+
+bool operator>=(const Uuid& a, const Uuid& b)
+{
+ return ::memcmp(a.bytes, b.bytes, Uuid::SIZE) >= 0;
+}
+
+ostream& operator<<(ostream& out, Uuid uuid)
+{
+ const uint8_t* bytes = uuid.bytes;
+
+ ios_base::fmtflags f = out.flags();
+ out << hex << setfill('0')
+ << setw(2) << int(bytes[0])
+ << setw(2) << int(bytes[1])
+ << setw(2) << int(bytes[2])
+ << setw(2) << int(bytes[3])
+ << "-"
+ << setw(2) << int(bytes[4])
+ << setw(2) << int(bytes[5])
+ << "-"
+ << setw(2) << int(bytes[6])
+ << setw(2) << int(bytes[7])
+ << "-"
+ << setw(2) << int(bytes[8])
+ << setw(2) << int(bytes[9])
+ << "-"
+ << setw(2) << int(bytes[10])
+ << setw(2) << int(bytes[11])
+ << setw(2) << int(bytes[12])
+ << setw(2) << int(bytes[13])
+ << setw(2) << int(bytes[14])
+ << setw(2) << int(bytes[15]);
+ out.flags(f);
+ return out;
+}
+
+istream& operator>>(istream& in, Uuid& uuid)
+{
+ unsigned bytes[16];
+ char unparsed[UNPARSED_SIZE + 1] = {0};
+
+ istream::sentry s(in);
+ if ( !s ) return in;
+
+ in.get(unparsed, UNPARSED_SIZE+1);
+
+ // Check if we read enough characters
+ if ( in.gcount()!=UNPARSED_SIZE ) {
+ in.setstate(ios::failbit);
+ return in;
+ }
+ int r = ::sscanf(unparsed, "%2x%2x%2x%2x-"
+ "%2x%2x-"
+ "%2x%2x-"
+ "%2x%2x-"
+ "%2x%2x%2x%2x%2x%2x",
+ &bytes[0], &bytes[1], &bytes[2], &bytes[3],
+ &bytes[4], &bytes[5],
+ &bytes[6], &bytes[7],
+ &bytes[8], &bytes[9],
+ &bytes[10], &bytes[11], &bytes[12], &bytes[13], &bytes[14], &bytes[15]
+ );
+ // Check if we got enough converted input
+ if ( r!=int(Uuid::SIZE) ) {
+ in.setstate(ios::failbit);
+ return in;
+ }
+
+ for (unsigned i=0; i<16; ++i) {
+ uuid.bytes[i] = bytes[i];
+ }
+ return in;
+}
+
+std::string Uuid::str() const
+{
+ std::ostringstream os;
+ os << *this;
+ return os.str();
+}
+
+size_t Uuid::hash() const {
+ std::size_t seed = 0;
+ for(size_t i = 0; i < SIZE; ++i)
+ seed ^= static_cast<std::size_t>(bytes[i]) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
+ return seed;
+}
+
+
+}} // namespace qpid::types
diff --git a/qpid/cpp/src/qpid/types/Variant.cpp b/qpid/cpp/src/qpid/types/Variant.cpp
new file mode 100644
index 0000000000..26dbe0c91e
--- /dev/null
+++ b/qpid/cpp/src/qpid/types/Variant.cpp
@@ -0,0 +1,962 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/types/Variant.h"
+#include "qpid/log/Statement.h"
+#include "encodings.h"
+#include <boost/format.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/algorithm/string.hpp>
+#include <algorithm>
+#include <limits>
+#include <sstream>
+
+namespace qpid {
+namespace types {
+
+namespace {
+const std::string EMPTY;
+const std::string PREFIX("invalid conversion: ");
+}
+
+InvalidConversion::InvalidConversion(const std::string& msg) : Exception(PREFIX + msg) {}
+InvalidConversion::~InvalidConversion() throw() {}
+
+class VariantImpl
+{
+ public:
+ VariantImpl();
+ void reset();
+ void set(bool);
+ void set(uint8_t);
+ void set(uint16_t);
+ void set(uint32_t);
+ void set(uint64_t);
+ void set(int8_t);
+ void set(int16_t);
+ void set(int32_t);
+ void set(int64_t);
+ void set(float);
+ void set(double);
+ void set(const std::string&, const std::string& encoding=std::string());
+ void set(const Variant::Map&);
+ void set(const Variant::List&);
+ void set(const Uuid&);
+ void set(const Variant&);
+ ~VariantImpl();
+
+ VariantType getType() const;
+
+ bool asBool() const;
+ uint8_t asUint8() const;
+ uint16_t asUint16() const;
+ uint32_t asUint32() const;
+ uint64_t asUint64() const;
+ int8_t asInt8() const;
+ int16_t asInt16() const;
+ int32_t asInt32() const;
+ int64_t asInt64() const;
+ float asFloat() const;
+ double asDouble() const;
+ std::string asString() const;
+ Uuid asUuid() const;
+
+ const Variant::Map& asMap() const;
+ Variant::Map& asMap();
+ const Variant::List& asList() const;
+ Variant::List& asList();
+
+ const std::string& getString() const;
+ std::string& getString();
+
+ void setEncoding(const std::string&);
+ const std::string& getEncoding() const;
+
+ bool isEqualTo(VariantImpl&) const;
+ bool isEquivalentTo(VariantImpl&) const;
+
+ Variant::List descriptors; // Optional descriptors for described value.
+
+ private:
+ VariantType type;
+ union {
+ bool b;
+ uint8_t ui8;
+ uint16_t ui16;
+ uint32_t ui32;
+ uint64_t ui64;
+ int8_t i8;
+ int16_t i16;
+ int32_t i32;
+ int64_t i64;
+ float f;
+ double d;
+ Uuid* uuid;
+ Variant::Map* map;
+ Variant::List* list;
+ std::string* string;
+ } value;
+ std::string encoding; // Optional encoding for variable length data.
+
+ template<class T> T convertFromString() const
+ {
+ const std::string& s = *value.string;
+
+ try {
+ // Extra shenanigans to work around negative zero
+ // conversion error in older GCC libs.
+ if ( s[0] != '-' ) {
+ return boost::lexical_cast<T>(s);
+ } else {
+ T r = boost::lexical_cast<T>(s.substr(1));
+ if (std::numeric_limits<T>::is_signed) {
+ return -r;
+ } else {
+ if (r==0) return 0;
+ }
+ }
+ } catch(const boost::bad_lexical_cast&) {
+ }
+ throw InvalidConversion(QPID_MSG("Cannot convert " << s));
+ }
+
+};
+
+VariantImpl::VariantImpl() : type(VAR_VOID) {}
+
+void VariantImpl::set(bool b) { reset(); type = VAR_BOOL; value.b = b; }
+void VariantImpl::set(uint8_t i) { reset(); type = VAR_UINT8; value.ui8 = i; }
+void VariantImpl::set(uint16_t i) { reset(); type = VAR_UINT16; value.ui16 = i; }
+void VariantImpl::set(uint32_t i) { reset(); type = VAR_UINT32; value.ui32 = i; }
+void VariantImpl::set(uint64_t i) { reset(); type = VAR_UINT64; value.ui64 = i; }
+void VariantImpl::set(int8_t i) { reset(); type = VAR_INT8; value.i8 = i; }
+void VariantImpl::set(int16_t i) { reset(); type = VAR_INT16; value.i16 = i; }
+void VariantImpl::set(int32_t i) { reset(); type = VAR_INT32; value.i32 = i; }
+void VariantImpl::set(int64_t i) { reset(); type = VAR_INT64; value.i64 = i; }
+void VariantImpl::set(float f) { reset(); type = VAR_FLOAT; value.f = f; }
+void VariantImpl::set(double d) { reset(); type = VAR_DOUBLE; value.d = d; }
+void VariantImpl::set(const std::string& s, const std::string& e) { reset(); type = VAR_STRING; encoding = e; value.string = new std::string(s); }
+
+void VariantImpl::set(const Variant::Map& m) {
+ reset();
+ type = VAR_MAP;
+ value.map = new Variant::Map(m);
+}
+
+void VariantImpl::set(const Variant::List& l) { reset(); type = VAR_LIST; value.list = new Variant::List(l); }
+
+void VariantImpl::set(const Uuid& u) { reset(); type = VAR_UUID; value.uuid = new Uuid(u); }
+
+VariantImpl::~VariantImpl() { reset(); }
+
+void VariantImpl::reset() {
+ switch (type) {
+ case VAR_STRING:
+ delete value.string;
+ break;
+ case VAR_MAP:
+ delete value.map;
+ break;
+ case VAR_LIST:
+ delete value.list;
+ break;
+ case VAR_UUID:
+ delete value.uuid;
+ break;
+ default:
+ break;
+ }
+ type = VAR_VOID;
+}
+
+VariantType VariantImpl::getType() const { return type; }
+
+namespace {
+
+bool same_char(char a, char b)
+{
+ return toupper(a) == toupper(b);
+}
+
+bool caseInsensitiveMatch(const std::string& a, const std::string& b)
+{
+ return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin(), &same_char);
+}
+
+const std::string TRUE_STRING("True");
+const std::string FALSE_STRING("False");
+
+bool toBool(const std::string& s)
+{
+ if (caseInsensitiveMatch(s, TRUE_STRING)) return true;
+ if (caseInsensitiveMatch(s, FALSE_STRING)) return false;
+ try { return boost::lexical_cast<int>(s); } catch(const boost::bad_lexical_cast&) {}
+ throw InvalidConversion(QPID_MSG("Cannot convert " << s << " to bool"));
+}
+
+template <class T> std::string toString(const T& t)
+{
+ std::stringstream out;
+ out << t;
+ return out.str();
+}
+
+template <class T> bool equal(const T& a, const T& b)
+{
+ return a.size() == b.size() && std::equal(a.begin(), a.end(), b.begin());
+}
+
+}
+
+bool VariantImpl::asBool() const
+{
+ switch(type) {
+ case VAR_VOID: return false;
+ case VAR_BOOL: return value.b;
+ case VAR_UINT8: return value.ui8;
+ case VAR_UINT16: return value.ui16;
+ case VAR_UINT32: return value.ui32;
+ case VAR_UINT64: return value.ui64;
+ case VAR_INT8: return value.i8;
+ case VAR_INT16: return value.i16;
+ case VAR_INT32: return value.i32;
+ case VAR_INT64: return value.i64;
+ case VAR_STRING: return toBool(*value.string);
+ default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_BOOL)));
+ }
+}
+uint8_t VariantImpl::asUint8() const
+{
+ switch(type) {
+ case VAR_UINT8: return value.ui8;
+ case VAR_UINT16:
+ if (value.ui16 <= 0x00ff)
+ return uint8_t(value.ui16);
+ break;
+ case VAR_UINT32:
+ if (value.ui32 <= 0x000000ff)
+ return uint8_t(value.ui32);
+ break;
+ case VAR_UINT64:
+ if (value.ui64 <= 0x00000000000000ff)
+ return uint8_t(value.ui64);
+ break;
+ case VAR_INT8:
+ if (value.i8 >= 0)
+ return uint8_t(value.i8);
+ break;
+ case VAR_INT16:
+ if (value.i16 >= 0 && value.i16 <= 0x00ff)
+ return uint8_t(value.i16);
+ break;
+ case VAR_INT32:
+ if (value.i32 >= 0 && value.i32 <= 0x000000ff)
+ return uint8_t(value.i32);
+ break;
+ case VAR_INT64:
+ if (value.i64 >= 0 && value.i64 <= 0x00000000000000ff)
+ return uint8_t(value.i64);
+ break;
+ case VAR_STRING: return convertFromString<uint8_t>();
+ default: break;
+ }
+ throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_UINT8)));
+}
+uint16_t VariantImpl::asUint16() const
+{
+ switch(type) {
+ case VAR_UINT8: return value.ui8;
+ case VAR_UINT16: return value.ui16;
+ case VAR_UINT32:
+ if (value.ui32 <= 0x0000ffff)
+ return uint16_t(value.ui32);
+ break;
+ case VAR_UINT64:
+ if (value.ui64 <= 0x000000000000ffff)
+ return uint16_t(value.ui64);
+ break;
+ case VAR_INT8:
+ if (value.i8 >= 0)
+ return uint16_t(value.i8);
+ break;
+ case VAR_INT16:
+ if (value.i16 >= 0)
+ return uint16_t(value.i16);
+ break;
+ case VAR_INT32:
+ if (value.i32 >= 0 && value.i32 <= 0x0000ffff)
+ return uint16_t(value.i32);
+ break;
+ case VAR_INT64:
+ if (value.i64 >= 0 && value.i64 <= 0x000000000000ffff)
+ return uint16_t(value.i64);
+ break;
+ case VAR_STRING: return convertFromString<uint16_t>();
+ default: break;
+ }
+ throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_UINT16)));
+}
+uint32_t VariantImpl::asUint32() const
+{
+ switch(type) {
+ case VAR_UINT8: return value.ui8;
+ case VAR_UINT16: return value.ui16;
+ case VAR_UINT32: return value.ui32;
+ case VAR_UINT64:
+ if (value.ui64 <= 0x00000000ffffffff)
+ return uint32_t(value.ui64);
+ break;
+ case VAR_INT8:
+ if (value.i8 >= 0)
+ return uint32_t(value.i8);
+ break;
+ case VAR_INT16:
+ if (value.i16 >= 0)
+ return uint32_t(value.i16);
+ break;
+ case VAR_INT32:
+ if (value.i32 >= 0)
+ return uint32_t(value.i32);
+ break;
+ case VAR_INT64:
+ if (value.i64 >= 0 && value.i64 <= 0x00000000ffffffff)
+ return uint32_t(value.i64);
+ break;
+ case VAR_STRING: return convertFromString<uint32_t>();
+ default: break;
+ }
+ throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_UINT32)));
+}
+uint64_t VariantImpl::asUint64() const
+{
+ switch(type) {
+ case VAR_UINT8: return value.ui8;
+ case VAR_UINT16: return value.ui16;
+ case VAR_UINT32: return value.ui32;
+ case VAR_UINT64: return value.ui64;
+ case VAR_INT8:
+ if (value.i8 >= 0)
+ return uint64_t(value.i8);
+ break;
+ case VAR_INT16:
+ if (value.i16 >= 0)
+ return uint64_t(value.i16);
+ break;
+ case VAR_INT32:
+ if (value.i32 >= 0)
+ return uint64_t(value.i32);
+ break;
+ case VAR_INT64:
+ if (value.i64 >= 0)
+ return uint64_t(value.i64);
+ break;
+ case VAR_STRING: return convertFromString<uint64_t>();
+ default: break;
+ }
+ throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_UINT64)));
+}
+
+int8_t VariantImpl::asInt8() const
+{
+ switch(type) {
+ case VAR_INT8: return value.i8;
+ case VAR_INT16:
+ if ((value.i16 >= std::numeric_limits<int8_t>::min()) && (value.i16 <= std::numeric_limits<int8_t>::max()))
+ return int8_t(value.i16);
+ break;
+ case VAR_INT32:
+ if ((value.i32 >= std::numeric_limits<int8_t>::min()) && (value.i32 <= std::numeric_limits<int8_t>::max()))
+ return int8_t(value.i32);
+ break;
+ case VAR_INT64:
+ if ((value.i64 >= std::numeric_limits<int8_t>::min()) && (value.i64 <= std::numeric_limits<int8_t>::max()))
+ return int8_t(value.i64);
+ break;
+ case VAR_UINT8:
+ if (value.ui8 <= std::numeric_limits<int8_t>::max())
+ return int8_t(value.ui8);
+ break;
+ case VAR_UINT16:
+ if (value.ui16 <= std::numeric_limits<int8_t>::max())
+ return int8_t(value.ui16);
+ break;
+ case VAR_UINT32:
+ if (value.ui32 <= (uint32_t) std::numeric_limits<int8_t>::max())
+ return int8_t(value.ui32);
+ break;
+ case VAR_UINT64:
+ if (value.ui64 <= (uint64_t) std::numeric_limits<int8_t>::max())
+ return int8_t(value.ui64);
+ break;
+ case VAR_STRING: return convertFromString<int8_t>();
+ default: break;
+ }
+ throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_INT8)));
+}
+int16_t VariantImpl::asInt16() const
+{
+ switch(type) {
+ case VAR_INT8: return value.i8;
+ case VAR_INT16: return value.i16;
+ case VAR_INT32:
+ if ((value.i32 >= std::numeric_limits<int16_t>::min()) && (value.i32 <= std::numeric_limits<int16_t>::max()))
+ return int16_t(value.i32);
+ break;
+ case VAR_INT64:
+ if ((value.i64 >= std::numeric_limits<int16_t>::min()) && (value.i64 <= std::numeric_limits<int16_t>::max()))
+ return int16_t(value.i64);
+ break;
+ case VAR_UINT8: return int16_t(value.ui8);
+ case VAR_UINT16:
+ if (value.ui16 <= std::numeric_limits<int16_t>::max())
+ return int16_t(value.ui16);
+ break;
+ case VAR_UINT32:
+ if (value.ui32 <= (uint32_t) std::numeric_limits<int16_t>::max())
+ return int16_t(value.ui32);
+ break;
+ case VAR_UINT64:
+ if (value.ui64 <= (uint64_t) std::numeric_limits<int16_t>::max())
+ return int16_t(value.ui64);
+ break;
+ case VAR_STRING: return convertFromString<int16_t>();
+ default: break;
+ }
+ throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_INT16)));
+}
+int32_t VariantImpl::asInt32() const
+{
+ switch(type) {
+ case VAR_INT8: return value.i8;
+ case VAR_INT16: return value.i16;
+ case VAR_INT32: return value.i32;
+ case VAR_INT64:
+ if ((value.i64 >= std::numeric_limits<int32_t>::min()) && (value.i64 <= std::numeric_limits<int32_t>::max()))
+ return int32_t(value.i64);
+ break;
+ case VAR_UINT8: return int32_t(value.ui8);
+ case VAR_UINT16: return int32_t(value.ui16);
+ case VAR_UINT32:
+ if (value.ui32 <= (uint32_t) std::numeric_limits<int32_t>::max())
+ return int32_t(value.ui32);
+ break;
+ case VAR_UINT64:
+ if (value.ui64 <= (uint64_t) std::numeric_limits<int32_t>::max())
+ return int32_t(value.ui64);
+ break;
+ case VAR_STRING: return convertFromString<int32_t>();
+ default: break;
+ }
+ throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_INT32)));
+}
+int64_t VariantImpl::asInt64() const
+{
+ switch(type) {
+ case VAR_INT8: return value.i8;
+ case VAR_INT16: return value.i16;
+ case VAR_INT32: return value.i32;
+ case VAR_INT64: return value.i64;
+ case VAR_UINT8: return int64_t(value.ui8);
+ case VAR_UINT16: return int64_t(value.ui16);
+ case VAR_UINT32: return int64_t(value.ui32);
+ case VAR_UINT64:
+ if (value.ui64 <= (uint64_t) std::numeric_limits<int64_t>::max())
+ return int64_t(value.ui64);
+ break;
+ case VAR_STRING: return convertFromString<int64_t>();
+ default: break;
+ }
+ throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_INT64)));
+}
+float VariantImpl::asFloat() const
+{
+ switch(type) {
+ case VAR_FLOAT: return value.f;
+ case VAR_STRING: return convertFromString<float>();
+ default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_FLOAT)));
+ }
+}
+double VariantImpl::asDouble() const
+{
+ switch(type) {
+ case VAR_FLOAT: return value.f;
+ case VAR_DOUBLE: return value.d;
+ case VAR_STRING: return convertFromString<double>();
+ default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_DOUBLE)));
+ }
+}
+std::string VariantImpl::asString() const
+{
+ switch(type) {
+ case VAR_VOID: return EMPTY;
+ case VAR_BOOL: return value.b ? TRUE_STRING : FALSE_STRING;
+ case VAR_UINT8: return boost::lexical_cast<std::string>((int) value.ui8);
+ case VAR_UINT16: return boost::lexical_cast<std::string>(value.ui16);
+ case VAR_UINT32: return boost::lexical_cast<std::string>(value.ui32);
+ case VAR_UINT64: return boost::lexical_cast<std::string>(value.ui64);
+ case VAR_INT8: return boost::lexical_cast<std::string>((int) value.i8);
+ case VAR_INT16: return boost::lexical_cast<std::string>(value.i16);
+ case VAR_INT32: return boost::lexical_cast<std::string>(value.i32);
+ case VAR_INT64: return boost::lexical_cast<std::string>(value.i64);
+ case VAR_DOUBLE: return boost::lexical_cast<std::string>(value.d);
+ case VAR_FLOAT: return boost::lexical_cast<std::string>(value.f);
+ case VAR_STRING: return *value.string;
+ case VAR_UUID: return value.uuid->str();
+ case VAR_LIST: return toString(asList());
+ case VAR_MAP: return toString(asMap());
+ default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_STRING)));
+ }
+}
+Uuid VariantImpl::asUuid() const
+{
+ switch(type) {
+ case VAR_UUID: return *value.uuid;
+ default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_UUID)));
+ }
+}
+
+bool VariantImpl::isEqualTo(VariantImpl& other) const
+{
+ if (type == other.type) {
+ switch(type) {
+ case VAR_VOID: return true;
+ case VAR_BOOL: return value.b == other.value.b;
+ case VAR_UINT8: return value.ui8 == other.value.ui8;
+ case VAR_UINT16: return value.ui16 == other.value.ui16;
+ case VAR_UINT32: return value.ui32 == other.value.ui32;
+ case VAR_UINT64: return value.ui64 == other.value.ui64;
+ case VAR_INT8: return value.i8 == other.value.i8;
+ case VAR_INT16: return value.i16 == other.value.i16;
+ case VAR_INT32: return value.i32 == other.value.i32;
+ case VAR_INT64: return value.i64 == other.value.i64;
+ case VAR_DOUBLE: return value.d == other.value.d;
+ case VAR_FLOAT: return value.f == other.value.f;
+ case VAR_STRING: return *value.string == *other.value.string;
+ case VAR_UUID: return *value.uuid == *other.value.uuid;
+ case VAR_LIST: return equal(asList(), other.asList());
+ case VAR_MAP: return equal(asMap(), other.asMap());
+ }
+ }
+ return false;
+}
+
+const Variant::Map& VariantImpl::asMap() const
+{
+ switch(type) {
+ case VAR_MAP: return *value.map;
+ default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_MAP)));
+ }
+}
+
+Variant::Map& VariantImpl::asMap()
+{
+ switch(type) {
+ case VAR_MAP: return *value.map;
+ default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_MAP)));
+ }
+}
+
+const Variant::List& VariantImpl::asList() const
+{
+ switch(type) {
+ case VAR_LIST: return *value.list;
+ default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_LIST)));
+ }
+}
+
+Variant::List& VariantImpl::asList()
+{
+ switch(type) {
+ case VAR_LIST: return *value.list;
+ default: throw InvalidConversion(QPID_MSG("Cannot convert from " << getTypeName(type) << " to " << getTypeName(VAR_LIST)));
+ }
+}
+
+std::string& VariantImpl::getString()
+{
+ switch(type) {
+ case VAR_STRING: return *value.string;
+ default: throw InvalidConversion(QPID_MSG("Variant is not a string; use asString() if conversion is required."));
+ }
+}
+
+const std::string& VariantImpl::getString() const
+{
+ switch(type) {
+ case VAR_STRING: return *value.string;
+ default: throw InvalidConversion(QPID_MSG("Variant is not a string; use asString() if conversion is required."));
+ }
+}
+
+void VariantImpl::setEncoding(const std::string& s) { encoding = s; }
+const std::string& VariantImpl::getEncoding() const { return encoding; }
+
+std::string getTypeName(VariantType type)
+{
+ switch (type) {
+ case VAR_VOID: return "void";
+ case VAR_BOOL: return "bool";
+ case VAR_UINT8: return "uint8";
+ case VAR_UINT16: return "uint16";
+ case VAR_UINT32: return "uint32";
+ case VAR_UINT64: return "uint64";
+ case VAR_INT8: return "int8";
+ case VAR_INT16: return "int16";
+ case VAR_INT32: return "int32";
+ case VAR_INT64: return "int64";
+ case VAR_FLOAT: return "float";
+ case VAR_DOUBLE: return "double";
+ case VAR_STRING: return "string";
+ case VAR_MAP: return "map";
+ case VAR_LIST: return "list";
+ case VAR_UUID: return "uuid";
+ }
+ return "<unknown>";//should never happen
+}
+
+bool isIntegerType(VariantType type)
+{
+ switch (type) {
+ case VAR_BOOL:
+ case VAR_UINT8:
+ case VAR_UINT16:
+ case VAR_UINT32:
+ case VAR_UINT64:
+ case VAR_INT8:
+ case VAR_INT16:
+ case VAR_INT32:
+ case VAR_INT64:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void VariantImpl::set(const Variant& v)
+{
+ switch (v.getType()) {
+ case VAR_BOOL: set(v.asBool()); break;
+ case VAR_UINT8: set(v.asUint8()); break;
+ case VAR_UINT16: set(v.asUint16()); break;
+ case VAR_UINT32: set(v.asUint32()); break;
+ case VAR_UINT64: set(v.asUint64()); break;
+ case VAR_INT8: set(v.asInt8()); break;
+ case VAR_INT16: set(v.asInt16()); break;
+ case VAR_INT32: set(v.asInt32()); break;
+ case VAR_INT64: set(v.asInt64()); break;
+ case VAR_FLOAT: set(v.asFloat()); break;
+ case VAR_DOUBLE: set(v.asDouble()); break;
+ case VAR_STRING: set(v.asString(), v.getEncoding()); break;
+ case VAR_MAP: set(v.asMap()); break;
+ case VAR_LIST: set(v.asList()); break;
+ case VAR_UUID: set(v.asUuid()); break;
+ default: reset();
+ }
+ encoding = v.getEncoding();
+ descriptors = v.getDescriptors();
+}
+
+Variant::Variant() : impl(0) {}
+Variant::Variant(bool b) : impl(new VariantImpl()) { impl->set(b); }
+Variant::Variant(uint8_t i) : impl(new VariantImpl()) { impl->set(i); }
+Variant::Variant(uint16_t i) : impl(new VariantImpl()) { impl->set(i); }
+Variant::Variant(uint32_t i) : impl(new VariantImpl()) { impl->set(i); }
+Variant::Variant(uint64_t i) : impl(new VariantImpl()) { impl->set(i); }
+Variant::Variant(int8_t i) : impl(new VariantImpl()) { impl->set(i); }
+Variant::Variant(int16_t i) : impl(new VariantImpl()) { impl->set(i); }
+Variant::Variant(int32_t i) : impl(new VariantImpl()) { impl->set(i); }
+Variant::Variant(int64_t i) : impl(new VariantImpl()) { impl->set(i); }
+Variant::Variant(float f) : impl(new VariantImpl()) { impl->set(f); }
+Variant::Variant(double d) : impl(new VariantImpl()) { impl->set(d); }
+Variant::Variant(const std::string& s) : impl(new VariantImpl()) { impl->set(s); }
+Variant::Variant(const std::string& s, const std::string& encoding) : impl(new VariantImpl()) { impl->set(s, encoding); }
+Variant::Variant(const char* s) : impl(new VariantImpl()) { impl->set(std::string(s)); }
+Variant::Variant(const char* s, const char* encoding) : impl(new VariantImpl()) { impl->set(std::string(s), std::string(encoding)); }
+Variant::Variant(const Map& m) : impl(new VariantImpl()) { impl->set(m); }
+Variant::Variant(const List& l) : impl(new VariantImpl()) { impl->set(l); }
+Variant::Variant(const Variant& v) : impl(new VariantImpl()) { impl->set(v); }
+Variant::Variant(const Uuid& u) : impl(new VariantImpl()) { impl->set(u); }
+
+Variant::~Variant() { if (impl) delete impl; }
+
+void Variant::reset()
+{
+ if (impl) delete impl;
+ impl = 0;
+}
+
+namespace {
+VariantImpl* assure(VariantImpl*& ptr) {
+ if (!ptr) ptr = new VariantImpl();
+ return ptr;
+}
+}
+
+Variant& Variant::operator=(bool b)
+{
+ assure(impl)->set(b);
+ return *this;
+}
+
+Variant& Variant::operator=(uint8_t i)
+{
+ assure(impl)->set(i);
+ return *this;
+}
+Variant& Variant::operator=(uint16_t i)
+{
+ assure(impl)->set(i);
+ return *this;
+}
+Variant& Variant::operator=(uint32_t i)
+{
+ assure(impl)->set(i);
+ return *this;
+}
+Variant& Variant::operator=(uint64_t i)
+{
+ assure(impl)->set(i);
+ return *this;
+}
+
+Variant& Variant::operator=(int8_t i)
+{
+ assure(impl)->set(i);
+ return *this;
+}
+Variant& Variant::operator=(int16_t i)
+{
+ assure(impl)->set(i);
+ return *this;
+}
+Variant& Variant::operator=(int32_t i)
+{
+ assure(impl)->set(i);
+ return *this;
+}
+Variant& Variant::operator=(int64_t i)
+{
+ assure(impl)->set(i);
+ return *this;
+}
+
+Variant& Variant::operator=(float f)
+{
+ assure(impl)->set(f);
+ return *this;
+}
+Variant& Variant::operator=(double d)
+{
+ assure(impl)->set(d);
+ return *this;
+}
+
+Variant& Variant::operator=(const std::string& s)
+{
+ assure(impl)->set(s);
+ return *this;
+}
+
+Variant& Variant::operator=(const char* s)
+{
+ assure(impl)->set(std::string(s));
+ return *this;
+}
+
+Variant& Variant::operator=(const Uuid& u)
+{
+ assure(impl)->set(u);
+ return *this;
+}
+
+Variant& Variant::operator=(const Map& m)
+{
+ assure(impl)->set(m);
+ return *this;
+}
+
+Variant& Variant::operator=(const List& l)
+{
+ assure(impl)->set(l);
+ return *this;
+}
+
+Variant& Variant::operator=(const Variant& v)
+{
+ assure(impl)->set(v);
+ return *this;
+}
+
+Variant& Variant::parse(const std::string& s)
+{
+ operator=(s);
+ try {
+ return operator=(asInt64());
+ } catch (const InvalidConversion&) {}
+ try {
+ return operator=(asUint64());
+ } catch (const InvalidConversion&) {}
+ try {
+ return operator=(asDouble());
+ } catch (const InvalidConversion&) {}
+ try {
+ return operator=(asBool());
+ } catch (const InvalidConversion&) {}
+ setEncoding(qpid::types::encodings::UTF8);
+ return *this;
+}
+
+
+VariantType Variant::getType() const { return impl ? impl->getType() : VAR_VOID; }
+bool Variant::isVoid() const { return getType() == VAR_VOID; }
+bool Variant::asBool() const { return impl && impl->asBool(); }
+uint8_t Variant::asUint8() const { return impl ? impl->asUint8() : 0; }
+uint16_t Variant::asUint16() const { return impl ? impl->asUint16() : 0; }
+uint32_t Variant::asUint32() const { return impl ? impl->asUint32() : 0; }
+uint64_t Variant::asUint64() const { return impl ? impl->asUint64() : 0; }
+int8_t Variant::asInt8() const { return impl ? impl->asInt8() : 0; }
+int16_t Variant::asInt16() const { return impl ? impl->asInt16() : 0; }
+int32_t Variant::asInt32() const { return impl ? impl->asInt32(): 0; }
+int64_t Variant::asInt64() const { return impl ? impl->asInt64() : 0; }
+float Variant::asFloat() const { return impl ? impl->asFloat() : 0; }
+double Variant::asDouble() const { return impl ? impl->asDouble() : 0; }
+std::string Variant::asString() const { return impl ? impl->asString() : EMPTY; }
+Uuid Variant::asUuid() const { return impl ? impl->asUuid() : Uuid(); }
+const Variant::Map& Variant::asMap() const { if (!impl) throw InvalidConversion("Can't convert VOID to MAP"); return impl->asMap(); }
+Variant::Map& Variant::asMap() { if (!impl) throw InvalidConversion("Can't convert VOID to MAP"); return impl->asMap(); }
+const Variant::List& Variant::asList() const { if (!impl) throw InvalidConversion("Can't convert VOID to LIST"); return impl->asList(); }
+Variant::List& Variant::asList() { if (!impl) throw InvalidConversion("Can't convert VOID to LIST"); return impl->asList(); }
+const std::string& Variant::getString() const { if (!impl) throw InvalidConversion("Can't convert VOID to STRING"); return impl->getString(); }
+std::string& Variant::getString() { if (!impl) throw InvalidConversion("Can't convert VOID to STRING"); return impl->getString(); }
+void Variant::setEncoding(const std::string& s) {
+ assure(impl)->setEncoding(s);
+}
+const std::string& Variant::getEncoding() const { return impl ? impl->getEncoding() : EMPTY; }
+
+Variant::operator bool() const { return asBool(); }
+Variant::operator uint8_t() const { return asUint8(); }
+Variant::operator uint16_t() const { return asUint16(); }
+Variant::operator uint32_t() const { return asUint32(); }
+Variant::operator uint64_t() const { return asUint64(); }
+Variant::operator int8_t() const { return asInt8(); }
+Variant::operator int16_t() const { return asInt16(); }
+Variant::operator int32_t() const { return asInt32(); }
+Variant::operator int64_t() const { return asInt64(); }
+Variant::operator float() const { return asFloat(); }
+Variant::operator double() const { return asDouble(); }
+Variant::operator std::string() const { return asString(); }
+Variant::operator Uuid() const { return asUuid(); }
+
+std::ostream& operator<<(std::ostream& out, const Variant::Map& map)
+{
+ out << "{";
+ for (Variant::Map::const_iterator i = map.begin(); i != map.end(); ++i) {
+ if (i != map.begin()) out << ", ";
+ out << i->first << ":" << i->second;
+ }
+ out << "}";
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const Variant::List& list)
+{
+ out << "[";
+ for (Variant::List::const_iterator i = list.begin(); i != list.end(); ++i) {
+ if (i != list.begin()) out << ", ";
+ out << *i;
+ }
+ out << "]";
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const Variant& value)
+{
+ // Print the descriptors
+ const Variant::List& descriptors = value.getDescriptors();
+ for (Variant::List::const_iterator i = descriptors.begin(); i != descriptors.end(); ++i)
+ out << "@" << *i << " ";
+
+ // Print the value
+ switch (value.getType()) {
+ case VAR_MAP:
+ out << value.asMap();
+ break;
+ case VAR_LIST:
+ out << value.asList();
+ break;
+ case VAR_VOID:
+ out << "<void>";
+ break;
+ default:
+ out << value.asString();
+ break;
+ }
+ return out;
+}
+
+bool operator==(const Variant& a, const Variant& b)
+{
+ return a.isEqualTo(b);
+}
+
+bool operator!=(const Variant& a, const Variant& b) { return !(a == b); }
+
+bool Variant::isEqualTo(const Variant& other) const
+{
+ if (isVoid() && other.isVoid()) return true;
+ if (isVoid() || other.isVoid()) return false;
+ return impl && impl->isEqualTo(*other.impl);
+}
+
+bool Variant::isDescribed() const {
+ return impl && !impl->descriptors.empty();
+}
+
+Variant::List& Variant::getDescriptors() {
+ return assure(impl)->descriptors;
+}
+
+const Variant::List& Variant::getDescriptors() const {
+ return assure(impl)->descriptors;
+}
+
+Variant Variant::getDescriptor() const {
+ if (getDescriptors().size() > 0) return getDescriptors().front();
+ else return Variant();
+}
+
+void Variant::setDescriptor(const Variant& descriptor) {
+ getDescriptors().clear();
+ getDescriptors().push_back(descriptor);
+}
+
+Variant Variant::described(const Variant& descriptor, const Variant& value) {
+ Variant described(value);
+ described.setDescriptor(descriptor);
+ return described;
+}
+
+Variant Variant::described(const Variant& descriptor, const List& value) {
+ Variant described(value);
+ described.setDescriptor(descriptor);
+ return described;
+}
+
+}} // namespace qpid::types
diff --git a/qpid/cpp/src/qpid/types/encodings.h b/qpid/cpp/src/qpid/types/encodings.h
new file mode 100644
index 0000000000..571e8607aa
--- /dev/null
+++ b/qpid/cpp/src/qpid/types/encodings.h
@@ -0,0 +1,35 @@
+#ifndef QPID_TYPES_ENCODINGS_H
+#define QPID_TYPES_ENCODINGS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+namespace qpid {
+namespace types {
+
+namespace encodings {
+const std::string BINARY("binary");
+const std::string UTF8("utf8");
+const std::string ASCII("ascii");
+}
+
+}} // namespace qpid::types
+
+#endif /*!QPID_TYPES_ENCODINGS_H*/
diff --git a/qpid/cpp/src/qpid/xml/XmlExchange.cpp b/qpid/cpp/src/qpid/xml/XmlExchange.cpp
new file mode 100644
index 0000000000..ffe9a66656
--- /dev/null
+++ b/qpid/cpp/src/qpid/xml/XmlExchange.cpp
@@ -0,0 +1,474 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "config.h"
+
+#include "qpid/xml/XmlExchange.h"
+
+#include "qpid/amqp/CharSequence.h"
+#include "qpid/broker/DeliverableMessage.h"
+
+#include "qpid/log/Statement.h"
+#include "qpid/broker/FedOps.h"
+#include "qpid/amqp/MapHandler.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/framing/reply_exceptions.h"
+
+#include "qpid/Plugin.h"
+
+#include <xercesc/framework/MemBufInputSource.hpp>
+#include <xercesc/util/XMLEntityResolver.hpp>
+
+#ifdef XQ_EFFECTIVE_BOOLEAN_VALUE_HPP
+#include <xqilla/ast/XQEffectiveBooleanValue.hpp>
+#endif
+
+#include <xqilla/ast/XQGlobalVariable.hpp>
+
+#include <xqilla/context/ItemFactory.hpp>
+#include <xqilla/xqilla-simple.hpp>
+
+#include <boost/bind.hpp>
+#include <functional>
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+
+using namespace qpid::framing;
+using namespace qpid::sys;
+using qpid::management::Manageable;
+namespace _qmf = qmf::org::apache::qpid::broker;
+
+namespace qpid {
+namespace broker {
+
+namespace {
+const char* DUMMY("dummy");
+}
+class XmlNullResolver : public XERCES_CPP_NAMESPACE::XMLEntityResolver
+{
+ public:
+ XERCES_CPP_NAMESPACE::InputSource* resolveEntity(XERCES_CPP_NAMESPACE::XMLResourceIdentifier* xmlri)
+ {
+ if (xmlri->getResourceIdentifierType() == XERCES_CPP_NAMESPACE::XMLResourceIdentifier::ExternalEntity) {
+ return new XERCES_CPP_NAMESPACE::MemBufInputSource(0, 0, DUMMY);
+ } else {
+ return 0;
+ }
+ }
+};
+
+
+XQilla XmlBinding::xqilla;
+
+XmlBinding::XmlBinding(const std::string& key, const Queue::shared_ptr queue, const std::string& _fedOrigin, Exchange* parent,
+ const ::qpid::framing::FieldTable& _arguments, const std::string& queryText )
+ : Binding(key, queue, parent, _arguments),
+ xquery(),
+ parse_message_content(true),
+ fedOrigin(_fedOrigin)
+{
+ startManagement();
+
+ QPID_LOG(trace, "Creating binding with query: " << queryText );
+
+ try {
+ Query q(xqilla.parse(X(queryText.c_str())));
+ xquery = q;
+
+ QPID_LOG(trace, "Bound successfully with query: " << queryText );
+
+ parse_message_content = false;
+
+ if (xquery->getQueryBody()->getStaticAnalysis().areContextFlagsUsed()) {
+ parse_message_content = true;
+ }
+ else {
+ GlobalVariables &vars = const_cast<GlobalVariables&>(xquery->getVariables());
+ for (GlobalVariables::iterator it = vars.begin(); it != vars.end(); ++it) {
+ if ((*it)->getStaticAnalysis().areContextFlagsUsed()) {
+ parse_message_content = true;
+ break;
+ }
+ }
+ }
+ }
+ catch (XQException& e) {
+ throw InternalErrorException(QPID_MSG("Could not parse xquery:"+ queryText));
+ }
+ catch (...) {
+ throw InternalErrorException(QPID_MSG("Unexpected error - Could not parse xquery:"+ queryText));
+ }
+}
+
+
+XmlExchange::XmlExchange(const std::string& _name, Manageable* _parent, Broker* b) : Exchange(_name, _parent, b)
+{
+ if (mgmtExchange != 0)
+ mgmtExchange->set_type (typeName);
+}
+
+XmlExchange::XmlExchange(const std::string& _name, bool _durable, bool autodelete,
+ const FieldTable& _args, Manageable* _parent, Broker* b) :
+ Exchange(_name, _durable, autodelete, _args, _parent, b), resolver(new XmlNullResolver)
+{
+ if (mgmtExchange != 0)
+ mgmtExchange->set_type (typeName);
+}
+
+bool XmlExchange::bind(Queue::shared_ptr queue, const std::string& bindingKey, const FieldTable* args)
+{
+
+ // Federation uses bind for unbind and reorigin comands as well as for binds.
+ //
+ // Both federated and local binds are done in this method. Other
+ // federated requests are done by calling the relevent methods.
+
+ std::string fedOp;
+ std::string fedTags;
+ std::string fedOrigin;
+
+ if (args)
+ fedOp = args->getAsString(qpidFedOp);
+ if (! fedOp.empty()) {
+ fedTags = args->getAsString(qpidFedTags);
+ fedOrigin = args->getAsString(qpidFedOrigin);
+ }
+
+ if (fedOp == fedOpUnbind) {
+ return fedUnbind(fedOrigin, fedTags, queue, bindingKey, args);
+ }
+ else if (fedOp == fedOpReorigin) {
+ fedReorigin();
+ return true;
+ }
+
+ // OK, looks like we're really going to bind
+
+ else if (fedOp.empty() || fedOp == fedOpBind) {
+
+ std::string queryText = args->getAsString("xquery");
+
+ RWlock::ScopedWlock l(lock);
+
+ XmlBinding::vector& bindings(bindingsMap[bindingKey]);
+ XmlBinding::vector::ConstPtr p = bindings.snapshot();
+
+ if (!p || std::find_if(p->begin(), p->end(), MatchQueueAndOrigin(queue, fedOrigin)) == p->end()) {
+
+ XmlBinding::shared_ptr binding(new XmlBinding (bindingKey, queue, fedOrigin, this, *args, queryText));
+ bindings.add(binding);
+
+ if (mgmtExchange != 0) {
+ mgmtExchange->inc_bindingCount();
+ }
+ } else {
+ return false;
+ }
+ }
+ else {
+ QPID_LOG(warning, "Unknown Federation Op: " << fedOp);
+ }
+
+ routeIVE();
+ propagateFedOp(bindingKey, fedTags, fedOp, fedOrigin, args);
+
+ return true;
+}
+
+bool XmlExchange::unbind(Queue::shared_ptr queue, const std::string& bindingKey, const FieldTable* args)
+{
+ RWlock::ScopedWlock l(lock);
+ return unbindLH(queue, bindingKey, args);
+}
+
+bool XmlExchange::unbindLH(Queue::shared_ptr queue, const std::string& bindingKey, const FieldTable* args)
+{
+ /*
+ * When called directly, no qpidFedOrigin argument will be
+ * present. When called from federation, it will be present.
+ *
+ * This is a bit of a hack - the binding needs the origin, but
+ * this interface, as originally defined, would not supply one.
+ *
+ * Note: caller must hold Wlock
+ */
+ std::string fedOrigin;
+ if (args) fedOrigin = args->getAsString(qpidFedOrigin);
+
+ if (bindingsMap[bindingKey].remove_if(MatchQueueAndOrigin(queue, fedOrigin))) {
+ if (mgmtExchange != 0) {
+ mgmtExchange->dec_bindingCount();
+ }
+ if (bindingsMap[bindingKey].empty()) bindingsMap.erase(bindingKey);
+ if (bindingsMap.empty()) checkAutodelete();
+ return true;
+ } else {
+ return false;
+ }
+}
+
+namespace {
+class DefineExternals : public qpid::amqp::MapHandler
+{
+ public:
+ DefineExternals(DynamicContext* c) : context(c) { assert(context); }
+ void handleBool(const qpid::amqp::CharSequence& key, bool value) { process(std::string(key.data, key.size), (int) value); }
+ void handleUint8(const qpid::amqp::CharSequence& key, uint8_t value) { process(std::string(key.data, key.size), (int) value); }
+ void handleUint16(const qpid::amqp::CharSequence& key, uint16_t value) { process(std::string(key.data, key.size), (int) value); }
+ void handleUint32(const qpid::amqp::CharSequence& key, uint32_t value) { process(std::string(key.data, key.size), (int) value); }
+ void handleUint64(const qpid::amqp::CharSequence& key, uint64_t value) { process(std::string(key.data, key.size), (int) value); }
+ void handleInt8(const qpid::amqp::CharSequence& key, int8_t value) { process(std::string(key.data, key.size), (int) value); }
+ void handleInt16(const qpid::amqp::CharSequence& key, int16_t value) { process(std::string(key.data, key.size), (int) value); }
+ void handleInt32(const qpid::amqp::CharSequence& key, int32_t value) { process(std::string(key.data, key.size), (int) value); }
+ void handleInt64(const qpid::amqp::CharSequence& key, int64_t value) { process(std::string(key.data, key.size), (int) value); }
+ void handleFloat(const qpid::amqp::CharSequence& key, float value) { process(std::string(key.data, key.size), value); }
+ void handleDouble(const qpid::amqp::CharSequence& key, double value) { process(std::string(key.data, key.size), value); }
+ void handleString(const qpid::amqp::CharSequence& key, const qpid::amqp::CharSequence& value, const qpid::amqp::CharSequence& /*encoding*/)
+ {
+ process(std::string(key.data, key.size), std::string(value.data, value.size));
+ }
+ void handleVoid(const qpid::amqp::CharSequence&) {}
+ private:
+ void process(const std::string& key, double value)
+ {
+ QPID_LOG(trace, "XmlExchange, external variable (double): " << key << " = " << value);
+ Item::Ptr item = context->getItemFactory()->createDouble(value, context);
+ context->setExternalVariable(X(key.c_str()), item);
+ }
+ void process(const std::string& key, int value)
+ {
+ QPID_LOG(trace, "XmlExchange, external variable (int):" << key << " = " << value);
+ Item::Ptr item = context->getItemFactory()->createInteger(value, context);
+ context->setExternalVariable(X(key.c_str()), item);
+ }
+ void process(const std::string& key, const std::string& value)
+ {
+ QPID_LOG(trace, "XmlExchange, external variable (string):" << key << " = " << value);
+ Item::Ptr item = context->getItemFactory()->createString(X(value.c_str()), context);
+ context->setExternalVariable(X(key.c_str()), item);
+ }
+
+ DynamicContext* context;
+};
+
+}
+
+bool XmlExchange::matches(Query& query, Deliverable& msg, bool parse_message_content)
+{
+ std::string msgContent;
+
+ try {
+ QPID_LOG(trace, "matches: query is [" << UTF8(query->getQueryText()) << "]");
+
+ boost::scoped_ptr<DynamicContext> context(query->createDynamicContext());
+ if (!context.get()) {
+ throw InternalErrorException(QPID_MSG("Query context looks munged ..."));
+ }
+
+ if (parse_message_content) {
+
+ if (resolver) context->setXMLEntityResolver(resolver.get());
+ msgContent = msg.getMessage().getContent();
+
+ QPID_LOG(trace, "matches: message content is [" << msgContent << "]");
+
+ XERCES_CPP_NAMESPACE::MemBufInputSource xml((const XMLByte*) msgContent.c_str(),
+ msgContent.length(), "input" );
+
+ // This will parse the document using either Xerces or FastXDM, depending
+ // on your XQilla configuration. FastXDM can be as much as 10x faster.
+
+ Sequence seq(context->parseDocument(xml));
+
+ if(!seq.isEmpty() && seq.first()->isNode()) {
+ context->setContextItem(seq.first());
+ context->setContextPosition(1);
+ context->setContextSize(1);
+ }
+ }
+
+ DefineExternals f(context.get());
+ msg.getMessage().processProperties(f);
+
+ Result result = query->execute(context.get());
+#ifdef XQ_EFFECTIVE_BOOLEAN_VALUE_HPP
+ Item::Ptr first_ = result->next(context.get());
+ Item::Ptr second_ = result->next(context.get());
+ return XQEffectiveBooleanValue::get(first_, second_, context.get(), 0);
+#else
+ return result->getEffectiveBooleanValue(context.get(), 0);
+#endif
+ }
+ catch (XQException& e) {
+ QPID_LOG(warning, "Could not parse XML content (or message headers):" << msgContent);
+ }
+ catch (...) {
+ QPID_LOG(warning, "Unexpected error routing message: " << msgContent);
+ }
+ return 0;
+}
+
+// Future optimization: If any query in a binding for a given routing key requires
+// message content, parse the message once, and use that parsed form for all bindings.
+//
+// Future optimization: XQilla does not currently do document projection for data
+// accessed via the context item. If there is a single query for a given routing key,
+// and it accesses document data, this could be a big win.
+//
+// Document projection often is not a win if you have multiple queries on the same data.
+// But for very large messages, if all these queries are on the first part of the data,
+// it could still be a big win.
+
+void XmlExchange::route(Deliverable& msg)
+{
+ const std::string& routingKey = msg.getMessage().getRoutingKey();
+ PreRoute pr(msg, this);
+ try {
+ XmlBinding::vector::ConstPtr p;
+ BindingList b(new std::vector<boost::shared_ptr<qpid::broker::Exchange::Binding> >);
+ {
+ RWlock::ScopedRlock l(lock);
+ p = bindingsMap[routingKey].snapshot();
+ }
+
+ if (p.get()) {
+ for (std::vector<XmlBinding::shared_ptr>::const_iterator i = p->begin(); i != p->end(); i++) {
+ if (matches((*i)->xquery, msg, (*i)->parse_message_content)) {
+ b->push_back(*i);
+ }
+ }
+ }
+ // else allow stats to be counted, even for non-matched messages
+ doRoute(msg, b);
+ } catch (...) {
+ QPID_LOG(warning, "XMLExchange " << getName() << ": exception routing message with query " << routingKey);
+ }
+}
+
+
+bool XmlExchange::isBound(Queue::shared_ptr queue, const std::string* const bindingKey, const FieldTable* const)
+{
+ RWlock::ScopedRlock l(lock);
+ if (bindingKey) {
+ XmlBindingsMap::iterator i = bindingsMap.find(*bindingKey);
+
+ if (i == bindingsMap.end())
+ return false;
+ if (!queue)
+ return true;
+ XmlBinding::vector::ConstPtr p = i->second.snapshot();
+ return p && std::find_if(p->begin(), p->end(), MatchQueue(queue)) != p->end();
+ } else if (!queue) {
+ //if no queue or routing key is specified, just report whether any bindings exist
+ return bindingsMap.size() > 0;
+ } else {
+ for (XmlBindingsMap::iterator i = bindingsMap.begin(); i != bindingsMap.end(); i++) {
+ XmlBinding::vector::ConstPtr p = i->second.snapshot();
+ if (p && std::find_if(p->begin(), p->end(), MatchQueue(queue)) != p->end()) return true;
+ }
+ return false;
+ }
+
+}
+
+XmlExchange::~XmlExchange()
+{
+ if (mgmtExchange != 0)
+ mgmtExchange->debugStats("destroying");
+ bindingsMap.clear();
+}
+
+void XmlExchange::propagateFedOp(const std::string& bindingKey, const std::string& fedTags, const std::string& fedOp, const std::string& fedOrigin, const qpid::framing::FieldTable* args)
+{
+ FieldTable nonFedArgs;
+
+ if (args) {
+ for (qpid::framing::FieldTable::ValueMap::const_iterator i=args->begin(); i != args->end(); ++i) {
+ const std::string& name(i->first);
+ if (name != qpidFedOp &&
+ name != qpidFedTags &&
+ name != qpidFedOrigin) {
+ nonFedArgs.insert((*i));
+ }
+ }
+ }
+
+ FieldTable* propArgs = (nonFedArgs.count() > 0 ? &nonFedArgs : 0);
+ Exchange::propagateFedOp(bindingKey, fedTags, fedOp, fedOrigin, propArgs);
+}
+
+bool XmlExchange::fedUnbind(const std::string& fedOrigin, const std::string& fedTags, Queue::shared_ptr queue, const std::string& bindingKey, const FieldTable* args)
+{
+ RWlock::ScopedWlock l(lock);
+
+ if (unbindLH(queue, bindingKey, args)) {
+ propagateFedOp(bindingKey, fedTags, fedOpUnbind, fedOrigin);
+ return true;
+ }
+ return false;
+}
+
+void XmlExchange::fedReorigin()
+{
+ std::vector<std::string> keys2prop;
+ {
+ RWlock::ScopedRlock l(lock);
+ for (XmlBindingsMap::iterator i = bindingsMap.begin(); i != bindingsMap.end(); ++i) {
+ XmlBinding::vector::ConstPtr p = i->second.snapshot();
+ if (std::find_if(p->begin(), p->end(), MatchOrigin(std::string())) != p->end()) {
+ keys2prop.push_back(i->first);
+ }
+ }
+ } /* lock dropped */
+ for (std::vector<std::string>::const_iterator key = keys2prop.begin();
+ key != keys2prop.end(); key++) {
+ propagateFedOp( *key, std::string(), fedOpBind, std::string());
+ }
+}
+
+
+XmlExchange::MatchOrigin::MatchOrigin(const std::string& _origin) : origin(_origin) {}
+
+bool XmlExchange::MatchOrigin::operator()(XmlBinding::shared_ptr b)
+{
+ return b->fedOrigin == origin;
+}
+
+
+XmlExchange::MatchQueueAndOrigin::MatchQueueAndOrigin(Queue::shared_ptr _queue, const std::string& _origin) : queue(_queue), origin(_origin) {}
+
+bool XmlExchange::MatchQueueAndOrigin::operator()(XmlBinding::shared_ptr b)
+{
+ return b->queue == queue and b->fedOrigin == origin;
+}
+
+
+const std::string XmlExchange::typeName("xml");
+
+bool XmlExchange::hasBindings()
+{
+ RWlock::ScopedRlock l(lock);
+ return !bindingsMap.empty();
+}
+}
+}
diff --git a/qpid/cpp/src/qpid/xml/XmlExchange.h b/qpid/cpp/src/qpid/xml/XmlExchange.h
new file mode 100644
index 0000000000..4115f3d530
--- /dev/null
+++ b/qpid/cpp/src/qpid/xml/XmlExchange.h
@@ -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.
+ *
+ */
+#ifndef _XmlExchange_
+#define _XmlExchange_
+
+#include "qpid/broker/Exchange.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/sys/CopyOnWriteArray.h"
+#include "qpid/sys/Monitor.h"
+#include "qpid/broker/Queue.h"
+
+#include <xqilla/xqilla-simple.hpp>
+
+#include <boost/scoped_ptr.hpp>
+
+#include <map>
+#include <vector>
+#include <string>
+
+namespace qpid {
+namespace broker {
+
+class Broker;
+
+typedef boost::shared_ptr<XQQuery> Query;
+
+struct XmlBinding : public Exchange::Binding {
+
+ static XQilla xqilla;
+
+ typedef boost::shared_ptr<XmlBinding> shared_ptr;
+ typedef qpid::sys::CopyOnWriteArray<XmlBinding::shared_ptr> vector;
+
+ Query xquery;
+ bool parse_message_content;
+ const std::string fedOrigin; // empty for local bindings
+
+ XmlBinding(const std::string& key, const Queue::shared_ptr queue, const std::string& fedOrigin, Exchange* parent,
+ const ::qpid::framing::FieldTable& _arguments, const std::string& );
+
+};
+
+class XmlNullResolver;
+
+class XmlExchange : public virtual Exchange {
+
+ typedef std::map<std::string, XmlBinding::vector> XmlBindingsMap;
+ XmlBindingsMap bindingsMap;
+
+ qpid::sys::RWlock lock;
+ boost::shared_ptr<XmlNullResolver> resolver;
+
+ bool matches(Query& query, Deliverable& msg, bool parse_message_content);
+
+ public:
+ static const std::string typeName;
+
+ XmlExchange(const std::string& name, management::Manageable* parent = 0, Broker* broker = 0);
+ XmlExchange(const std::string& _name, bool _durable, bool autodelete,
+ const qpid::framing::FieldTable& _args, management::Manageable* parent = 0, Broker* broker = 0);
+
+ virtual std::string getType() const { return typeName; }
+
+ virtual bool bind(Queue::shared_ptr queue, const std::string& routingKey, const qpid::framing::FieldTable* args);
+
+ virtual bool unbind(Queue::shared_ptr queue, const std::string& routingKey, const qpid::framing::FieldTable* args);
+
+ virtual void route(Deliverable& msg);
+
+ virtual bool isBound(Queue::shared_ptr queue, const std::string* const routingKey, const qpid::framing::FieldTable* const args);
+
+ virtual void propagateFedOp(const std::string& bindingKey, const std::string& fedTags, const std::string& fedOp, const std::string& fedOrigin, const qpid::framing::FieldTable* args=0);
+
+ virtual bool fedUnbind(const std::string& fedOrigin, const std::string& fedTags, Queue::shared_ptr queue, const std::string& bindingKey, const qpid::framing::FieldTable* args);
+
+ virtual void fedReorigin();
+
+ virtual bool supportsDynamicBinding() { return true; }
+
+ virtual ~XmlExchange();
+
+ struct MatchOrigin {
+ const std::string origin;
+ MatchOrigin(const std::string& origin);
+ bool operator()(XmlBinding::shared_ptr b);
+ };
+
+ struct MatchQueueAndOrigin {
+ const Queue::shared_ptr queue;
+ const std::string origin;
+ MatchQueueAndOrigin(Queue::shared_ptr queue, const std::string& origin);
+ bool operator()(XmlBinding::shared_ptr b);
+ };
+
+ protected:
+ bool hasBindings();
+ private:
+ bool unbindLH(Queue::shared_ptr queue, const std::string& routingKey, const qpid::framing::FieldTable* args);
+};
+
+
+}
+}
+
+
+#endif
diff --git a/qpid/cpp/src/qpid/xml/XmlExchangePlugin.cpp b/qpid/cpp/src/qpid/xml/XmlExchangePlugin.cpp
new file mode 100644
index 0000000000..02f0110faf
--- /dev/null
+++ b/qpid/cpp/src/qpid/xml/XmlExchangePlugin.cpp
@@ -0,0 +1,70 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <sstream>
+#include "qpid/acl/Acl.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/Plugin.h"
+#include "qpid/log/Statement.h"
+
+#include <boost/shared_ptr.hpp>
+#include <boost/utility/in_place_factory.hpp>
+
+#include "qpid/xml/XmlExchange.h"
+
+namespace qpid {
+namespace broker { // ACL uses the acl namespace here - should I?
+
+using namespace std;
+class Broker;
+
+Exchange::shared_ptr create(const std::string& name, bool durable,
+ bool autodelete,
+ const framing::FieldTable& args,
+ management::Manageable* parent,
+ Broker* broker)
+{
+ Exchange::shared_ptr e(new XmlExchange(name, durable, autodelete, args, parent, broker));
+ return e;
+}
+
+
+class XmlExchangePlugin : public Plugin
+{
+public:
+ void earlyInitialize(Plugin::Target& target);
+ void initialize(Plugin::Target& target);
+};
+
+
+void XmlExchangePlugin::earlyInitialize(Plugin::Target& target)
+{
+ Broker* broker = dynamic_cast<broker::Broker*>(&target);
+ if (broker) {
+ broker->getExchanges().registerType(XmlExchange::typeName, &create);
+ QPID_LOG(info, "Registered xml exchange");
+ }
+}
+
+void XmlExchangePlugin::initialize(Target&) {}
+
+
+static XmlExchangePlugin matchingPlugin;
+
+
+}} // namespace qpid::acl
diff --git a/qpid/cpp/src/qpidd.cpp b/qpid/cpp/src/qpidd.cpp
new file mode 100644
index 0000000000..e17dea3164
--- /dev/null
+++ b/qpid/cpp/src/qpidd.cpp
@@ -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.
+ *
+ */
+
+#include "./qpidd.h"
+#include "qpid/Plugin.h"
+#include "qpid/Version.h"
+#include "qpid/log/Logger.h"
+#include "qpid/log/Statement.h"
+
+#include <iostream>
+#include <memory>
+using namespace std;
+
+namespace qpid {
+namespace broker {
+
+auto_ptr<QpiddOptions> options;
+
+// Broker real entry; various system-invoked entrypoints call here.
+int run_broker(int argc, char *argv[], bool hidden)
+{
+ try
+ {
+ BootstrapOptions bootOptions(argv[0]);
+ string defaultPath (bootOptions.module.loadDir);
+
+ // --version causes print and exit
+ if (bootOptions.findArg(argc, argv, "version")) {
+ cout << "qpidd (" << qpid::product << ") version "
+ << qpid::version << endl;
+ return 0;
+ }
+
+ // --help sets a flag so that its presence is known despite
+ // subsequent parse problems.
+ bool helpArgSeen = bootOptions.findArg(argc, argv, "help");
+
+ // Parse only the common, load, and log options to see which
+ // modules need to be loaded. Once the modules are loaded,
+ // the command line will be re-parsed with all of the
+ // module-supplied options.
+ try {
+ bootOptions.parse (argc, argv, bootOptions.common.config, true);
+ if (hidden)
+ bootOptions.log.sinkOptions->detached();
+ qpid::log::Logger::instance().configure(bootOptions.log);
+ } catch (const std::exception& e) {
+ if (helpArgSeen) {
+ // provide help even when parsing fails
+ bootOptions.usage();
+ }
+ // Couldn't configure logging so write the message direct to stderr.
+ cerr << endl << "Unexpected error: " << e.what() << endl;
+ return 1;
+ }
+
+ for (vector<string>::iterator iter = bootOptions.module.load.begin();
+ iter != bootOptions.module.load.end();
+ iter++)
+ qpid::tryShlib (*iter);
+
+ if (!bootOptions.module.noLoad) {
+ bool isDefault = defaultPath == bootOptions.module.loadDir;
+ qpid::loadModuleDir (bootOptions.module.loadDir, isDefault);
+ }
+
+ // Parse options. In the second pass, do not allow unknown options.
+ // All the modules have been added now, so any unknown options
+ // should be flagged as errors.
+ try {
+ options.reset(new QpiddOptions(argv[0]));
+ options->parse(argc, argv, options->common.config, false);
+ } catch (const std::exception& /*e*/) {
+ if (helpArgSeen) {
+ // provide help even when parsing fails
+ options->usage();
+ }
+ throw;
+ }
+
+ // Options that just print information.
+ if (helpArgSeen) {
+ options->usage();
+ return 0;
+ }
+
+ // Everything else is driven by the platform-specific broker
+ // logic.
+ QpiddBroker broker;
+ return broker.execute(options.get());
+ }
+ catch(const exception& e) {
+ QPID_LOG(critical, "Unexpected error: " << e.what());
+ }
+ return 1;
+}
+}}
diff --git a/qpid/cpp/src/qpidd.h b/qpid/cpp/src/qpidd.h
new file mode 100644
index 0000000000..38328139c3
--- /dev/null
+++ b/qpid/cpp/src/qpidd.h
@@ -0,0 +1,78 @@
+#ifndef QPID_H
+#define QPID_H
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/Modules.h"
+#include "qpid/Options.h"
+#include "qpid/broker/BrokerOptions.h"
+#include "qpid/log/Options.h"
+
+#include <memory>
+
+namespace qpid {
+namespace broker {
+
+// BootstrapOptions is a minimal subset of options used for a pre-parse
+// of the command line to discover which plugin modules need to be loaded.
+// The pre-parse is necessary because plugin modules may supply their own
+// set of options. CommonOptions is needed to properly support loading
+// from a configuration file.
+struct BootstrapOptions : public qpid::Options {
+ qpid::CommonOptions common;
+ qpid::ModuleOptions module;
+ qpid::log::Options log;
+
+ BootstrapOptions(const char *argv0);
+ void usage() const;
+};
+
+// Each platform derives an options struct from QpiddOptionsPrivate, adding
+// platform-specific option types. QpiddOptions needs to allocation one of
+// these derived structs from its constructor.
+struct QpiddOptions;
+struct QpiddOptionsPrivate {
+ QpiddOptions *options;
+ QpiddOptionsPrivate(QpiddOptions *parent) : options(parent) {}
+ virtual ~QpiddOptionsPrivate() {}
+protected:
+ QpiddOptionsPrivate() {}
+};
+
+struct QpiddOptions : public qpid::Options {
+ qpid::CommonOptions common;
+ qpid::ModuleOptions module;
+ qpid::broker::BrokerOptions broker;
+ qpid::log::Options log;
+ std::auto_ptr<QpiddOptionsPrivate> platform;
+
+ QpiddOptions(const char *argv0);
+ void usage() const;
+};
+
+class QpiddBroker {
+public:
+ int execute (QpiddOptions *options);
+};
+
+// Broker real entry; various system-invoked entrypoints call here.
+int run_broker(int argc, char *argv[], bool hidden = false);
+
+}}
+#endif /*!QPID_H*/
diff --git a/qpid/cpp/src/rdma.cmake b/qpid/cpp/src/rdma.cmake
new file mode 100644
index 0000000000..9db06269af
--- /dev/null
+++ b/qpid/cpp/src/rdma.cmake
@@ -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.
+#
+#
+# RDMA (Remote DMA) wrapper CMake fragment, to be included in CMakeLists.txt
+#
+
+# Optional RDMA support. Requires ibverbs and rdma_cm.
+
+include(CheckIncludeFiles)
+include(CheckLibraryExists)
+
+CHECK_LIBRARY_EXISTS (ibverbs ibv_create_qp "" HAVE_IBVERBS)
+CHECK_LIBRARY_EXISTS (rdmacm rdma_create_id "" HAVE_RDMACM)
+CHECK_INCLUDE_FILES (infiniband/verbs.h HAVE_IBVERBS_H)
+CHECK_INCLUDE_FILES (rdma/rdma_cma.h HAVE_RDMACM_H)
+
+set (rdma_default ${rdma_force})
+if (HAVE_IBVERBS AND HAVE_IBVERBS_H)
+ if (HAVE_RDMACM AND HAVE_RDMACM_H)
+ set (rdma_default ON)
+ endif (HAVE_RDMACM AND HAVE_RDMACM_H)
+endif (HAVE_IBVERBS AND HAVE_IBVERBS_H)
+
+option(BUILD_RDMA "Build with support for Remote DMA protocols" ${rdma_default})
+if (BUILD_RDMA)
+ if (NOT HAVE_IBVERBS)
+ message(FATAL_ERROR "libibverbs not found, required for RDMA support")
+ endif (NOT HAVE_IBVERBS)
+ if (NOT HAVE_RDMACM)
+ message(FATAL_ERROR "librdmacm not found, required for RDMA support")
+ endif (NOT HAVE_RDMACM)
+ if (NOT HAVE_IBVERBS_H)
+ message(FATAL_ERROR "ibverbs headers not found, required for RDMA support")
+ endif (NOT HAVE_IBVERBS_H)
+ if (NOT HAVE_RDMACM_H)
+ message(FATAL_ERROR "rdmacm headers not found, required for RDMA support")
+ endif (NOT HAVE_RDMACM_H)
+
+ set (rdma_SOURCES
+ qpid/sys/rdma/rdma_exception.h
+ qpid/sys/rdma/rdma_factories.cpp
+ qpid/sys/rdma/rdma_factories.h
+ qpid/sys/rdma/RdmaIO.cpp
+ qpid/sys/rdma/RdmaIO.h
+ qpid/sys/rdma/rdma_wrap.cpp
+ qpid/sys/rdma/rdma_wrap.h
+ )
+
+ add_library (rdmawrap SHARED ${rdma_SOURCES})
+ target_link_libraries (rdmawrap qpidcommon rdmacm ibverbs)
+ set_target_properties (rdmawrap PROPERTIES
+ VERSION ${rdmawrap_version}
+ SOVERSION ${rdmawrap_version_major})
+ if (CMAKE_COMPILER_IS_GNUCXX)
+ set_target_properties(rdmawrap PROPERTIES
+ COMPILE_FLAGS -Wno-missing-field-initializers)
+ endif (CMAKE_COMPILER_IS_GNUCXX)
+
+ install (TARGETS rdmawrap
+ DESTINATION ${QPID_INSTALL_LIBDIR}
+ COMPONENT ${QPID_COMPONENT_COMMON})
+
+ add_library (rdma MODULE qpid/sys/RdmaIOPlugin.cpp)
+ target_link_libraries (rdma qpidbroker qpidcommon rdmawrap)
+ set_target_properties (rdma PROPERTIES
+ COMPILE_DEFINITIONS _IN_QPID_BROKER
+ PREFIX "")
+
+ if (CMAKE_COMPILER_IS_GNUCXX)
+ set_target_properties(rdma PROPERTIES
+ COMPILE_FLAGS -Wno-missing-field-initializers)
+ endif (CMAKE_COMPILER_IS_GNUCXX)
+
+ install (TARGETS rdma
+ DESTINATION ${QPIDD_MODULE_DIR}
+ COMPONENT ${QPID_COMPONENT_BROKER})
+
+ add_library (rdmaconnector MODULE qpid/client/RdmaConnector.cpp)
+ target_link_libraries (rdmaconnector qpidclient qpidcommon rdmawrap)
+ set_target_properties (rdmaconnector PROPERTIES
+ PREFIX "")
+
+ if (CMAKE_COMPILER_IS_GNUCXX)
+ set_target_properties(rdmaconnector PROPERTIES
+ COMPILE_FLAGS -Wno-missing-field-initializers)
+ endif (CMAKE_COMPILER_IS_GNUCXX)
+
+ install (TARGETS rdmaconnector
+ DESTINATION ${QPIDC_MODULE_DIR}
+ COMPONENT ${QPID_COMPONENT_CLIENT})
+
+ # RDMA test/sample programs
+ add_executable (RdmaServer qpid/sys/rdma/RdmaServer.cpp)
+ target_link_libraries (RdmaServer rdmawrap qpidcommon)
+ add_executable (RdmaClient qpid/sys/rdma/RdmaClient.cpp)
+ target_link_libraries (RdmaClient rdmawrap qpidcommon)
+ if (CMAKE_COMPILER_IS_GNUCXX)
+ set_target_properties(RdmaClient PROPERTIES
+ COMPILE_FLAGS -Wno-missing-field-initializers)
+ endif (CMAKE_COMPILER_IS_GNUCXX)
+
+endif (BUILD_RDMA)
diff --git a/qpid/cpp/src/tests/.valgrind.supp b/qpid/cpp/src/tests/.valgrind.supp
new file mode 100644
index 0000000000..1a24a9178e
--- /dev/null
+++ b/qpid/cpp/src/tests/.valgrind.supp
@@ -0,0 +1,179 @@
+{
+ Leak in TCPConnector: https://bugzilla.redhat.com/show_bug.cgi?id=520600
+ Memcheck:Leak
+ fun:_vgrZU_libcZdsoZa_calloc
+ fun:_dl_allocate_tls
+ fun:*
+ fun:*
+ fun:*
+ fun:_ZN4qpid6client12TCPConnector7connectERKSsi
+}
+
+{
+ Leak in TCPConnector: https://bugzilla.redhat.com/show_bug.cgi?id=520600
+ Memcheck:Leak
+ fun:_vgrZU_libcZdsoZa_calloc
+ fun:_dl_allocate_tls
+ fun:*
+ fun:*
+ fun:_ZN4qpid6client12TCPConnector7connectERKSsi
+}
+
+{
+ Reported on FC5 and RHEL5 when md5 sasl libs are installed
+ Memcheck:Leak
+ fun:*
+ fun:_dl_map_object_from_fd
+ fun:_dl_map_object
+ fun:openaux
+ fun:_dl_catch_error
+ fun:_dl_map_object_deps
+ fun:dl_open_worker
+ fun:_dl_catch_error
+ fun:_dl_open
+ fun:dlopen_doit
+ fun:_dl_catch_error
+ fun:_dlerror_run
+ fun:*
+ fun:_sasl_get_plugin
+ fun:_sasl_load_plugins
+ fun:sasl_client_init
+}
+
+{
+ Uninitialised value problem in _dl_relocate (F7, F8)
+ Memcheck:Cond
+ fun:_dl_relocate_object
+ fun:*dl_*
+}
+
+{
+ False "possibly leaked" in boost program_options - global std::string var.
+ Memcheck:Leak
+ fun:_Znwj
+ fun:_ZNSs4_Rep9_S_createEjjRKSaIcE
+ obj:/usr/lib/libstdc++.so.6.0.8
+ fun:_ZNSsC1EPKcRKSaIcE
+ obj:/usr/lib/libboost_program_options.so.1.33.1
+}
+
+{
+ INVESTIGATE
+ Memcheck:Leak
+ fun:calloc
+ fun:_dl_allocate_tls
+ fun:pthread_create@@GLIBC_2.2.5
+ fun:_ZN4qpid6client9Connector4initEv
+ fun:_ZN4qpid6client14ConnectionImpl4openERKSsiS3_S3_S3_
+}
+
+{
+ INVESTIGATE
+ Memcheck:Param
+ write(buf)
+ obj:/lib64/tls/libc-2.3.4.so
+ fun:_ZNK4qpid3sys6Socket5writeEPKvm
+ fun:_ZN4qpid3sys8AsynchIO9writeableERNS0_14DispatchHandleE
+}
+
+{
+ "Conditional jump or move depends on uninitialised value(s)" from Xerces parser
+ Memcheck:Cond
+ fun:_ZN11xercesc_2_717XMLUTF8Transcoder13transcodeFromEPKhjPtjRjPh
+ fun:_ZN11xercesc_2_79XMLReader14xcodeMoreCharsEPtPhj
+ fun:_ZN11xercesc_2_79XMLReader17refreshCharBufferEv
+}
+
+{
+ INVESTIGATE
+ Memcheck:Param
+ socketcall.sendto(msg)
+ fun:send
+ fun:get_mapping
+ fun:__nscd_get_map_ref
+ fun:nscd_gethst_r
+ fun:__nscd_gethostbyname_r
+ fun:gethostbyname_r@@GLIBC_2.2.5
+ fun:gethostbyname
+ fun:_ZNK4qpid3sys6Socket7connectERKSsi
+}
+
+{
+ INVESTIGATE
+ Memcheck:Leak
+ fun:calloc
+ fun:_dl_allocate_tls
+ fun:pthread_create@@GLIBC_2.2.5
+ fun:_ZN4qpid6broker5Timer5startEv
+ fun:_ZN4qpid6broker5TimerC1Ev
+ fun:_ZN4qpid6broker10DtxManagerC1Ev
+ fun:_ZN4qpid6broker6BrokerC1ERKNS1_7OptionsE
+ fun:_ZN4qpid6broker6Broker6createERKNS1_7OptionsE
+ fun:_ZN20ClientSessionFixtureC1Ev
+ fun:_Z14testQueueQueryv
+ fun:_ZN5boost9unit_test9ut_detail17unit_test_monitor8functionEv
+ obj:/usr/lib64/libboost_unit_test_framework.so.1.32.0
+ fun:_ZN5boost17execution_monitor7executeEbi
+ fun:_ZN5boost9unit_test9ut_detail17unit_test_monitor21execute_and_translateEPNS0_9test_caseEMS3_FvvEi
+ fun:_ZN5boost9unit_test9test_case3runEv
+ fun:_ZN5boost9unit_test10test_suite6do_runEv
+ fun:_ZN5boost9unit_test9test_case3runEv
+ fun:main
+}
+
+{
+ INVESTIGATE
+ Memcheck:Leak
+ fun:calloc
+ fun:_dl_allocate_tls
+ fun:pthread_create@@GLIBC_2.2.5
+ fun:_ZN4qpid6client9Connector4initEv
+}
+
+{
+ MICK -- FIX
+ Memcheck:Leak
+ fun:_Znam
+ fun:_ZN4qpid7Options5parseEiPPcRKSsb
+}
+
+{
+ MICK -- FIX
+ Memcheck:Leak
+ fun:malloc
+ fun:strdup
+ fun:_ZN4qpid7Options5parseEiPPcRKSsb
+}
+
+{
+ Known leak in boost.thread 1.33.1. Wildcards for 64/32 bit diffs.
+ Memcheck:Leak
+ fun:*
+ obj:/usr/*/libboost_thread.so.1.33.1
+ fun:_ZN5boost6detail3tss3setEPv
+}
+
+{
+ Shows up on RHEL5: believed benign
+ Memcheck:Cond
+ fun:__strcpy_chk
+ fun:_sasl_load_plugins
+ fun:sasl_client_init
+}
+
+{
+ Seems like a use after delete issue in boost unit_test
+ Memcheck:Addr8
+ fun:_ZN5boost9unit_test14framework_implD1Ev
+ fun:exit
+ fun:(below main)
+}
+
+{
+ Seems like a use after delete issue in boost unit_test
+ Memcheck:Addr4
+ fun:_ZN5boost9unit_test14framework_implD1Ev
+ fun:exit
+ fun:(below main)
+}
+
diff --git a/qpid/cpp/src/tests/AccumulatedAckTest.cpp b/qpid/cpp/src/tests/AccumulatedAckTest.cpp
new file mode 100644
index 0000000000..c736a519d2
--- /dev/null
+++ b/qpid/cpp/src/tests/AccumulatedAckTest.cpp
@@ -0,0 +1,237 @@
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/framing/AccumulatedAck.h"
+#include "unit_test.h"
+#include <iostream>
+#include <list>
+
+using std::list;
+using namespace qpid::framing;
+
+
+namespace qpid {
+namespace tests {
+
+bool covers(const AccumulatedAck& ack, int i)
+{
+ return ack.covers(SequenceNumber(i));
+}
+
+void update(AccumulatedAck& ack, int start, int end)
+{
+ ack.update(SequenceNumber(start), SequenceNumber(end));
+}
+
+QPID_AUTO_TEST_SUITE(AccumulatedAckTestSuite)
+
+QPID_AUTO_TEST_CASE(testGeneral)
+{
+ AccumulatedAck ack(0);
+ ack.clear();
+ update(ack, 3,3);
+ update(ack, 7,7);
+ update(ack, 9,9);
+ update(ack, 1,2);
+ update(ack, 4,5);
+ update(ack, 6,6);
+
+ for(int i = 1; i <= 7; i++) BOOST_CHECK(covers(ack, i));
+ BOOST_CHECK(covers(ack, 9));
+
+ BOOST_CHECK(!covers(ack, 8));
+ BOOST_CHECK(!covers(ack, 10));
+
+ ack.consolidate();
+
+ for(int i = 1; i <= 7; i++) BOOST_CHECK(covers(ack, i));
+ BOOST_CHECK(covers(ack, 9));
+
+ BOOST_CHECK(!covers(ack, 8));
+ BOOST_CHECK(!covers(ack, 10));
+}
+
+QPID_AUTO_TEST_CASE(testCovers)
+{
+ AccumulatedAck ack(5);
+ update(ack, 7, 7);
+ update(ack, 9, 9);
+
+ BOOST_CHECK(covers(ack, 1));
+ BOOST_CHECK(covers(ack, 2));
+ BOOST_CHECK(covers(ack, 3));
+ BOOST_CHECK(covers(ack, 4));
+ BOOST_CHECK(covers(ack, 5));
+ BOOST_CHECK(covers(ack, 7));
+ BOOST_CHECK(covers(ack, 9));
+
+ BOOST_CHECK(!covers(ack, 6));
+ BOOST_CHECK(!covers(ack, 8));
+ BOOST_CHECK(!covers(ack, 10));
+}
+
+QPID_AUTO_TEST_CASE(testUpdateFromCompletionData)
+{
+ AccumulatedAck ack(0);
+ SequenceNumber mark(2);
+ SequenceNumberSet ranges;
+ ranges.addRange(SequenceNumber(5), SequenceNumber(8));
+ ranges.addRange(SequenceNumber(10), SequenceNumber(15));
+ ranges.addRange(SequenceNumber(9), SequenceNumber(9));
+ ranges.addRange(SequenceNumber(3), SequenceNumber(4));
+
+ ack.update(mark, ranges);
+
+ for(int i = 0; i <= 15; i++) {
+ BOOST_CHECK(covers(ack, i));
+ }
+ BOOST_CHECK(!covers(ack, 16));
+ BOOST_CHECK_EQUAL((uint32_t) 15, ack.mark.getValue());
+}
+
+QPID_AUTO_TEST_CASE(testCase1)
+{
+ AccumulatedAck ack(3);
+ update(ack, 1,2);
+ for(int i = 1; i <= 3; i++) BOOST_CHECK(covers(ack, i));
+ BOOST_CHECK(!covers(ack, 4));
+}
+
+QPID_AUTO_TEST_CASE(testCase2)
+{
+ AccumulatedAck ack(3);
+ update(ack, 3,6);
+ for(int i = 1; i <= 6; i++) BOOST_CHECK(covers(ack, i));
+ BOOST_CHECK(!covers(ack, 7));
+}
+
+QPID_AUTO_TEST_CASE(testCase3)
+{
+ AccumulatedAck ack(3);
+ update(ack, 4,6);
+ for(int i = 1; i <= 6; i++) {
+ BOOST_CHECK(covers(ack, i));
+ }
+ BOOST_CHECK(!covers(ack, 7));
+}
+
+QPID_AUTO_TEST_CASE(testCase4)
+{
+ AccumulatedAck ack(3);
+ update(ack, 5,6);
+ for(int i = 1; i <= 6; i++) {
+ if (i == 4) BOOST_CHECK(!covers(ack, i));
+ else BOOST_CHECK(covers(ack, i));
+ }
+ BOOST_CHECK(!covers(ack, 7));
+}
+
+QPID_AUTO_TEST_CASE(testConsolidation1)
+{
+ AccumulatedAck ack(3);
+ update(ack, 7,7);
+ BOOST_CHECK_EQUAL((uint32_t) 3, ack.mark.getValue());
+ BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size());
+
+ update(ack, 8,9);
+ BOOST_CHECK_EQUAL((uint32_t) 3, ack.mark.getValue());
+ BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size());
+
+ update(ack, 1,2);
+ BOOST_CHECK_EQUAL((uint32_t) 3, ack.mark.getValue());
+ BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size());
+
+ update(ack, 4,5);
+ BOOST_CHECK_EQUAL((uint32_t) 5, ack.mark.getValue());
+ BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size());
+
+ update(ack, 6,6);
+ BOOST_CHECK_EQUAL((uint32_t) 9, ack.mark.getValue());
+ BOOST_CHECK_EQUAL((size_t) 0, ack.ranges.size());
+
+ for(int i = 1; i <= 9; i++) BOOST_CHECK(covers(ack, i));
+ BOOST_CHECK(!covers(ack, 10));
+}
+
+QPID_AUTO_TEST_CASE(testConsolidation2)
+{
+ AccumulatedAck ack(0);
+ update(ack, 10,12);
+ BOOST_CHECK_EQUAL((uint32_t) 0, ack.mark.getValue());
+ BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size());
+
+ update(ack, 7,9);
+ BOOST_CHECK_EQUAL((uint32_t) 0, ack.mark.getValue());
+ BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size());
+ BOOST_CHECK_EQUAL((uint32_t) 7, ack.ranges.front().start.getValue());
+ BOOST_CHECK_EQUAL((uint32_t) 12, ack.ranges.front().end.getValue());
+
+ update(ack, 5,7);
+ BOOST_CHECK_EQUAL((uint32_t) 0, ack.mark.getValue());
+ BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size());
+ BOOST_CHECK_EQUAL((uint32_t) 5, ack.ranges.front().start.getValue());
+ BOOST_CHECK_EQUAL((uint32_t) 12, ack.ranges.front().end.getValue());
+
+ update(ack, 3,4);
+ BOOST_CHECK_EQUAL((uint32_t) 0, ack.mark.getValue());
+ BOOST_CHECK_EQUAL((size_t) 1, ack.ranges.size());
+ BOOST_CHECK_EQUAL((uint32_t) 3, ack.ranges.front().start.getValue());
+ BOOST_CHECK_EQUAL((uint32_t) 12, ack.ranges.front().end.getValue());
+
+ update(ack, 1,2);
+ BOOST_CHECK_EQUAL((uint32_t) 12, ack.mark.getValue());
+ BOOST_CHECK_EQUAL((size_t) 0, ack.ranges.size());
+
+ for(int i = 1; i <= 12; i++) BOOST_CHECK(covers(ack, i));
+ BOOST_CHECK(!covers(ack, 13));
+}
+
+QPID_AUTO_TEST_CASE(testConsolidation3)
+{
+ AccumulatedAck ack(0);
+ update(ack, 10,12);
+ update(ack, 6,7);
+ update(ack, 3,4);
+ update(ack, 1,15);
+ BOOST_CHECK_EQUAL((uint32_t) 15, ack.mark.getValue());
+ BOOST_CHECK_EQUAL((size_t) 0, ack.ranges.size());
+}
+
+QPID_AUTO_TEST_CASE(testConsolidation4)
+{
+ AccumulatedAck ack(0);
+ ack.update(SequenceNumber(0), SequenceNumber(2));
+ ack.update(SequenceNumber(5), SequenceNumber(8));
+ ack.update(SequenceNumber(10), SequenceNumber(15));
+ ack.update(SequenceNumber(9), SequenceNumber(9));
+ ack.update(SequenceNumber(3), SequenceNumber(4));
+
+ for(int i = 0; i <= 15; i++) {
+ BOOST_CHECK(covers(ack, i));
+ }
+ BOOST_CHECK(!covers(ack, 16));
+ BOOST_CHECK_EQUAL((uint32_t) 15, ack.mark.getValue());
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/Acl.cpp b/qpid/cpp/src/tests/Acl.cpp
new file mode 100644
index 0000000000..9c3de0de62
--- /dev/null
+++ b/qpid/cpp/src/tests/Acl.cpp
@@ -0,0 +1,166 @@
+/*
+ *
+ * Copyright (c) 2014 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+
+#include "unit_test.h"
+#include "test_tools.h"
+#include "qpid/acl/AclLexer.h"
+#include <boost/assign.hpp>
+
+using namespace std;
+using namespace qpid;
+using namespace qpid::acl;
+using namespace boost::assign;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(AclTestSuite)
+
+#define OBJ_ENUMS(e, s) \
+ BOOST_CHECK_EQUAL(AclHelper::getObjectTypeStr((e)),(s)); \
+ BOOST_CHECK_EQUAL(AclHelper::getObjectType((s)),(e))
+
+QPID_AUTO_TEST_CASE(TestLexerObjectEnums) {
+ BOOST_CHECK_EQUAL(OBJECTSIZE, 7);
+ OBJ_ENUMS(OBJ_QUEUE, "queue");
+ OBJ_ENUMS(OBJ_EXCHANGE, "exchange");
+ OBJ_ENUMS(OBJ_BROKER, "broker");
+ OBJ_ENUMS(OBJ_LINK, "link");
+ OBJ_ENUMS(OBJ_METHOD, "method");
+ OBJ_ENUMS(OBJ_QUERY, "query");
+ OBJ_ENUMS(OBJ_CONNECTION, "connection");
+ int maxLen = 0;
+ for (int i=0; i<acl::OBJECTSIZE; i++) {
+ int thisLen = AclHelper::getObjectTypeStr( ObjectType(i) ).length();
+ if (thisLen > maxLen)
+ maxLen = thisLen;
+ }
+ BOOST_CHECK_EQUAL(maxLen, acl::OBJECTTYPE_STR_WIDTH);
+}
+
+#define ACT_ENUMS(e, s) \
+ BOOST_CHECK_EQUAL(AclHelper::getActionStr((e)),(s)); \
+ BOOST_CHECK_EQUAL(AclHelper::getAction((s)),(e))
+
+QPID_AUTO_TEST_CASE(TestLexerActionEnums) {
+ BOOST_CHECK_EQUAL(ACTIONSIZE, 12);
+ ACT_ENUMS(ACT_CONSUME, "consume");
+ ACT_ENUMS(ACT_PUBLISH, "publish");
+ ACT_ENUMS(ACT_CREATE, "create");
+ ACT_ENUMS(ACT_ACCESS, "access");
+ ACT_ENUMS(ACT_BIND, "bind");
+ ACT_ENUMS(ACT_UNBIND, "unbind");
+ ACT_ENUMS(ACT_DELETE, "delete");
+ ACT_ENUMS(ACT_PURGE, "purge");
+ ACT_ENUMS(ACT_UPDATE, "update");
+ ACT_ENUMS(ACT_MOVE, "move");
+ ACT_ENUMS(ACT_REDIRECT, "redirect");
+ ACT_ENUMS(ACT_REROUTE, "reroute");
+ int maxLen = 0;
+ for (int i=0; i<acl::ACTIONSIZE; i++) {
+ int thisLen = AclHelper::getActionStr( Action(i) ).length();
+ if (thisLen > maxLen)
+ maxLen = thisLen;
+ }
+ BOOST_CHECK_EQUAL(maxLen, acl::ACTION_STR_WIDTH);
+}
+
+#define PROP_ENUMS(e, s) \
+ BOOST_CHECK_EQUAL(AclHelper::getPropertyStr((e)),(s)); \
+ BOOST_CHECK_EQUAL(AclHelper::getProperty((s)),(e))
+
+QPID_AUTO_TEST_CASE(TestLexerPropertyEnums) {
+ BOOST_CHECK_EQUAL(PROPERTYSIZE, 21);
+ PROP_ENUMS(PROP_NAME, "name");
+ PROP_ENUMS(PROP_DURABLE, "durable");
+ PROP_ENUMS(PROP_OWNER, "owner");
+ PROP_ENUMS(PROP_ROUTINGKEY, "routingkey");
+ PROP_ENUMS(PROP_AUTODELETE, "autodelete");
+ PROP_ENUMS(PROP_EXCLUSIVE, "exclusive");
+ PROP_ENUMS(PROP_TYPE, "type");
+ PROP_ENUMS(PROP_ALTERNATE, "alternate");
+ PROP_ENUMS(PROP_QUEUENAME, "queuename");
+ PROP_ENUMS(PROP_EXCHANGENAME, "exchangename");
+ PROP_ENUMS(PROP_SCHEMAPACKAGE, "schemapackage");
+ PROP_ENUMS(PROP_SCHEMACLASS, "schemaclass");
+ PROP_ENUMS(PROP_POLICYTYPE, "policytype");
+ PROP_ENUMS(PROP_PAGING, "paging");
+ PROP_ENUMS(PROP_HOST, "host");
+ PROP_ENUMS(PROP_MAXPAGES, "maxpages");
+ PROP_ENUMS(PROP_MAXPAGEFACTOR, "maxpagefactor");
+ PROP_ENUMS(PROP_MAXQUEUESIZE, "maxqueuesize");
+ PROP_ENUMS(PROP_MAXQUEUECOUNT, "maxqueuecount");
+ PROP_ENUMS(PROP_MAXFILESIZE, "maxfilesize");
+ PROP_ENUMS(PROP_MAXFILECOUNT, "maxfilecount");
+
+}
+
+#define SPECPROP_ENUMS(e, s) \
+ BOOST_CHECK_EQUAL(AclHelper::getPropertyStr((e)),(s)); \
+ BOOST_CHECK_EQUAL(AclHelper::getSpecProperty((s)),(e))
+
+QPID_AUTO_TEST_CASE(TestLexerSpecPropertyEnums) {
+ BOOST_CHECK_EQUAL(SPECPROPSIZE, 27);
+ SPECPROP_ENUMS(SPECPROP_NAME, "name");
+ SPECPROP_ENUMS(SPECPROP_DURABLE, "durable");
+ SPECPROP_ENUMS(SPECPROP_OWNER, "owner");
+ SPECPROP_ENUMS(SPECPROP_ROUTINGKEY, "routingkey");
+ SPECPROP_ENUMS(SPECPROP_AUTODELETE, "autodelete");
+ SPECPROP_ENUMS(SPECPROP_EXCLUSIVE, "exclusive");
+ SPECPROP_ENUMS(SPECPROP_TYPE, "type");
+ SPECPROP_ENUMS(SPECPROP_ALTERNATE, "alternate");
+ SPECPROP_ENUMS(SPECPROP_QUEUENAME, "queuename");
+ SPECPROP_ENUMS(SPECPROP_EXCHANGENAME, "exchangename");
+ SPECPROP_ENUMS(SPECPROP_SCHEMAPACKAGE, "schemapackage");
+ SPECPROP_ENUMS(SPECPROP_SCHEMACLASS, "schemaclass");
+ SPECPROP_ENUMS(SPECPROP_POLICYTYPE, "policytype");
+ SPECPROP_ENUMS(SPECPROP_PAGING, "paging");
+ SPECPROP_ENUMS(SPECPROP_HOST, "host");
+ SPECPROP_ENUMS(SPECPROP_MAXQUEUESIZELOWERLIMIT, "queuemaxsizelowerlimit");
+ SPECPROP_ENUMS(SPECPROP_MAXQUEUESIZEUPPERLIMIT, "queuemaxsizeupperlimit");
+ SPECPROP_ENUMS(SPECPROP_MAXQUEUECOUNTLOWERLIMIT, "queuemaxcountlowerlimit");
+ SPECPROP_ENUMS(SPECPROP_MAXQUEUECOUNTUPPERLIMIT, "queuemaxcountupperlimit");
+ SPECPROP_ENUMS(SPECPROP_MAXFILESIZELOWERLIMIT, "filemaxsizelowerlimit");
+ SPECPROP_ENUMS(SPECPROP_MAXFILESIZEUPPERLIMIT, "filemaxsizeupperlimit");
+ SPECPROP_ENUMS(SPECPROP_MAXFILECOUNTLOWERLIMIT, "filemaxcountlowerlimit");
+ SPECPROP_ENUMS(SPECPROP_MAXFILECOUNTUPPERLIMIT, "filemaxcountupperlimit");
+ SPECPROP_ENUMS(SPECPROP_MAXPAGESLOWERLIMIT, "pageslowerlimit");
+ SPECPROP_ENUMS(SPECPROP_MAXPAGESUPPERLIMIT, "pagesupperlimit");
+ SPECPROP_ENUMS(SPECPROP_MAXPAGEFACTORLOWERLIMIT, "pagefactorlowerlimit");
+ SPECPROP_ENUMS(SPECPROP_MAXPAGEFACTORUPPERLIMIT, "pagefactorupperlimit");
+
+ BOOST_CHECK_EQUAL(AclHelper::getSpecProperty("maxqueuesize"), SPECPROP_MAXQUEUESIZEUPPERLIMIT);
+ BOOST_CHECK_EQUAL(AclHelper::getSpecProperty("maxqueuecount"), SPECPROP_MAXQUEUECOUNTUPPERLIMIT);
+}
+
+#define RESULT_ENUMS(e, s) \
+ BOOST_CHECK_EQUAL(AclHelper::getAclResultStr((e)),(s)); \
+ BOOST_CHECK_EQUAL(AclHelper::getAclResult((s)),(e))
+
+QPID_AUTO_TEST_CASE(TestLexerResultEnums) {
+ BOOST_CHECK_EQUAL(RESULTSIZE, 4);
+ RESULT_ENUMS(ALLOW, "allow");
+ RESULT_ENUMS(ALLOWLOG, "allow-log");
+ RESULT_ENUMS(DENY, "deny");
+ RESULT_ENUMS(DENYLOG, "deny-log");
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/AclHost.cpp b/qpid/cpp/src/tests/AclHost.cpp
new file mode 100644
index 0000000000..7d60c5a63d
--- /dev/null
+++ b/qpid/cpp/src/tests/AclHost.cpp
@@ -0,0 +1,166 @@
+/*
+ *
+ * Copyright (c) 2014 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+
+#include "unit_test.h"
+#include "test_tools.h"
+#include "qpid/AclHost.h"
+#include "qpid/sys/SocketAddress.h"
+#include <boost/assign.hpp>
+
+using namespace std;
+using namespace qpid;
+using namespace boost::assign;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(AclHostTestSuite)
+
+#define ACLURL_CHECK_INVALID(STR) BOOST_CHECK_THROW(AclHost(STR), AclHost::Invalid)
+
+#define SENSE_IP_VERSIONS() \
+ bool haveIPv4(true); \
+ try { \
+ sys::SocketAddress sa("1.1.1.1", ""); \
+ sa.firstAddress(); \
+} catch (qpid::Exception) { \
+ haveIPv4 = false; \
+} \
+ bool haveIPv6(true); \
+ try { \
+ sys::SocketAddress sa("::1", ""); \
+ sa.firstAddress(); \
+} catch (qpid::Exception) { \
+ haveIPv6 = false; \
+} \
+(void) haveIPv4; \
+(void) haveIPv6;
+
+QPID_AUTO_TEST_CASE(TestParseTcpIPv4) {
+ SENSE_IP_VERSIONS();
+ if (haveIPv4) {
+ BOOST_CHECK_EQUAL(AclHost("1.1.1.1").str(), "(1.1.1.1,1.1.1.1)");
+ BOOST_CHECK_EQUAL(AclHost("1.1.1.1,2.2.2.2").str(), "(1.1.1.1,2.2.2.2)");
+ }
+}
+
+QPID_AUTO_TEST_CASE(TestParseTcpIPv6) {
+ SENSE_IP_VERSIONS();
+ if (haveIPv6) {
+ BOOST_CHECK_EQUAL(AclHost("[::1]").str(), "([::1],[::1])");
+ BOOST_CHECK_EQUAL(AclHost("[::1],::5").str(), "([::1],[::5])");
+ }
+}
+
+QPID_AUTO_TEST_CASE(TestParseAll) {
+ SENSE_IP_VERSIONS();
+ if (haveIPv4 || haveIPv6) {
+ BOOST_CHECK_EQUAL(AclHost("").str(), "(all)");
+ }
+}
+
+QPID_AUTO_TEST_CASE(TestInvalidMixedIpFamilies) {
+ SENSE_IP_VERSIONS();
+ if (haveIPv4 && haveIPv6) {
+ ACLURL_CHECK_INVALID("1.1.1.1,[::1]");
+ ACLURL_CHECK_INVALID("[::1],1.1.1.1");
+ }
+}
+
+QPID_AUTO_TEST_CASE(TestMalformedIPv4) {
+ SENSE_IP_VERSIONS();
+ if (haveIPv4) {
+ ACLURL_CHECK_INVALID("1.1.1.1.1");
+ ACLURL_CHECK_INVALID("1.1.1.777");
+ ACLURL_CHECK_INVALID("1.1.1.1abcd");
+ ACLURL_CHECK_INVALID("1.1.1.*");
+ }
+}
+
+QPID_AUTO_TEST_CASE(TestRangeWithInvertedSizeOrder) {
+ SENSE_IP_VERSIONS();
+ if (haveIPv4) {
+ ACLURL_CHECK_INVALID("1.1.1.100,1.1.1.1");
+ }
+ if (haveIPv6) {
+ ACLURL_CHECK_INVALID("[FF::1],[::1]");
+ }
+}
+
+QPID_AUTO_TEST_CASE(TestSingleHostResolvesMultipleAddresses) {
+ SENSE_IP_VERSIONS();
+ AclHost XX("localhost");
+}
+
+QPID_AUTO_TEST_CASE(TestMatchSingleAddresses) {
+ SENSE_IP_VERSIONS();
+ if (haveIPv4) {
+ AclHost host1("1.1.1.1");
+ BOOST_CHECK(host1.match("1.1.1.1") == true);
+ BOOST_CHECK(host1.match("1.2.1.1") == false);
+ }
+ if (haveIPv6) {
+ AclHost host2("FF::1");
+ BOOST_CHECK(host2.match("00FF:0000::1") == true);
+ }
+}
+
+QPID_AUTO_TEST_CASE(TestMatchIPv4Range) {
+ SENSE_IP_VERSIONS();
+ if (haveIPv4) {
+ AclHost host1("192.168.0.0,192.168.255.255");
+ BOOST_CHECK(host1.match("128.1.1.1") == false);
+ BOOST_CHECK(host1.match("192.167.255.255") == false);
+ BOOST_CHECK(host1.match("192.168.0.0") == true);
+ BOOST_CHECK(host1.match("192.168.0.1") == true);
+ BOOST_CHECK(host1.match("192.168.1.0") == true);
+ BOOST_CHECK(host1.match("192.168.255.254") == true);
+ BOOST_CHECK(host1.match("192.168.255.255") == true);
+ BOOST_CHECK(host1.match("192.169.0.0") == false);
+ if (haveIPv6) {
+ BOOST_CHECK(host1.match("::1") == false);
+ }
+ }
+}
+
+QPID_AUTO_TEST_CASE(TestMatchIPv6Range) {
+ SENSE_IP_VERSIONS();
+ if (haveIPv6) {
+ AclHost host1("::10,::1:0");
+ BOOST_CHECK(host1.match("::1") == false);
+ BOOST_CHECK(host1.match("::f") == false);
+ BOOST_CHECK(host1.match("::10") == true);
+ BOOST_CHECK(host1.match("::11") == true);
+ BOOST_CHECK(host1.match("::ffff") == true);
+ BOOST_CHECK(host1.match("::1:0") == true);
+ BOOST_CHECK(host1.match("::1:1") == false);
+ if (haveIPv4) {
+ BOOST_CHECK(host1.match("192.169.0.0") == false);
+ }
+ AclHost host2("[fc00::],[fc00::ff]");
+ BOOST_CHECK(host2.match("fc00::") == true);
+ BOOST_CHECK(host2.match("fc00::1") == true);
+ BOOST_CHECK(host2.match("fc00::ff") == true);
+ BOOST_CHECK(host2.match("fc00::100") == false);
+
+ }
+}
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/Address.cpp b/qpid/cpp/src/tests/Address.cpp
new file mode 100644
index 0000000000..0fd3585958
--- /dev/null
+++ b/qpid/cpp/src/tests/Address.cpp
@@ -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.
+ *
+ */
+#include <iostream>
+#include "qpid/messaging/Address.h"
+#include "qpid/types/Variant.h"
+
+#include "unit_test.h"
+
+using namespace qpid::messaging;
+using namespace qpid::types;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(AddressSuite)
+
+QPID_AUTO_TEST_CASE(testParseNameOnly)
+{
+ Address address("my-topic");
+ BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName());
+}
+
+QPID_AUTO_TEST_CASE(testParseSubject)
+{
+ Address address("my-topic/my-subject");
+ BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName());
+ BOOST_CHECK_EQUAL(std::string("my-subject"), address.getSubject());
+}
+
+QPID_AUTO_TEST_CASE(testParseOptions)
+{
+ Address address("my-topic; {a:bc, x:101, y:'a string'}");
+ BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName());
+
+ BOOST_CHECK_EQUAL(std::string("bc"), address.getOptions()["a"]);
+ BOOST_CHECK_EQUAL(101, static_cast<int>(address.getOptions()["x"]));
+ BOOST_CHECK_EQUAL(std::string("a string"), address.getOptions()["y"]);
+
+ // Test asString() and asInt64() once here
+
+ BOOST_CHECK_EQUAL(std::string("bc"), address.getOptions()["a"].asString());
+ BOOST_CHECK_EQUAL(static_cast<uint16_t>(101), address.getOptions()["x"].asInt64());
+ BOOST_CHECK_EQUAL(std::string("a string"), address.getOptions()["y"].asString());
+}
+
+QPID_AUTO_TEST_CASE(testParseSubjectAndOptions)
+{
+ Address address("my-topic/my-subject; {a:bc, x:101, y:'a string'}");
+ BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName());
+ BOOST_CHECK_EQUAL(std::string("my-subject"), address.getSubject());
+
+ BOOST_CHECK_EQUAL(std::string("bc"), address.getOptions()["a"]);
+ BOOST_CHECK_EQUAL(101, static_cast<int>(address.getOptions()["x"]));
+ BOOST_CHECK_EQUAL(std::string("a string"), address.getOptions()["y"]);
+}
+
+QPID_AUTO_TEST_CASE(testParseNestedOptions)
+{
+ Address address("my-topic; {a:{p:202, q:'another string'}, x:101, y:'a string'}");
+ BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName());
+ BOOST_CHECK_EQUAL(202, static_cast<int>(address.getOptions()["a"].asMap()["p"]));
+ BOOST_CHECK_EQUAL(std::string("another string"), address.getOptions()["a"].asMap()["q"]);
+ BOOST_CHECK_EQUAL(std::string("a string"), address.getOptions()["y"]);
+}
+
+QPID_AUTO_TEST_CASE(testParseOptionsWithList)
+{
+ Address address("my-topic; {a:[202, 'another string'], x:101}");
+ BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName());
+ Variant::List& list = address.getOptions()["a"].asList();
+ Variant::List::const_iterator i = list.begin();
+ BOOST_CHECK(i != list.end());
+ BOOST_CHECK_EQUAL((uint16_t) 202, i->asInt64());
+ BOOST_CHECK(++i != list.end());
+ BOOST_CHECK_EQUAL(std::string("another string"), i->asString());
+ BOOST_CHECK_EQUAL((uint16_t) 101, address.getOptions()["x"].asInt64());
+}
+
+QPID_AUTO_TEST_CASE(testParseOptionsWithEmptyList)
+{
+ Address address("my-topic; {a:[], x:101}");
+ BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName());
+ Variant::List& list = address.getOptions()["a"].asList();
+ BOOST_CHECK_EQUAL(list.size(), (size_t) 0);
+ BOOST_CHECK_EQUAL((uint16_t) 101, address.getOptions()["x"].asInt64());
+}
+
+QPID_AUTO_TEST_CASE(testParseOptionsWithEmptyMap)
+{
+ Address address("my-topic; {a:{}, x:101}");
+ BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName());
+ Variant::Map& map = address.getOptions()["a"].asMap();
+ BOOST_CHECK_EQUAL(map.size(), (size_t) 0);
+ BOOST_CHECK_EQUAL((uint16_t) 101, address.getOptions()["x"].asInt64());
+}
+
+QPID_AUTO_TEST_CASE(testParseQuotedNameAndSubject)
+{
+ Address address("'my topic with / in it'/'my subject with ; in it'");
+ BOOST_CHECK_EQUAL(std::string("my topic with / in it"), address.getName());
+ BOOST_CHECK_EQUAL(std::string("my subject with ; in it"), address.getSubject());
+}
+
+QPID_AUTO_TEST_CASE(testParseOptionsWithEmptyStringAsValue)
+{
+ Address address("my-topic; {a:'', x:101}");
+ BOOST_CHECK_EQUAL(std::string("my-topic"), address.getName());
+ Variant a = address.getOptions()["a"];
+ BOOST_CHECK_EQUAL(VAR_STRING, a.getType());
+ std::string aVal = a;
+ BOOST_CHECK(aVal.size() == 0);
+ BOOST_CHECK_EQUAL((uint16_t) 101, address.getOptions()["x"].asInt64());
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}}
diff --git a/qpid/cpp/src/tests/Array.cpp b/qpid/cpp/src/tests/Array.cpp
new file mode 100644
index 0000000000..8ce7615162
--- /dev/null
+++ b/qpid/cpp/src/tests/Array.cpp
@@ -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.
+ *
+ */
+#include <iostream>
+#include <sstream>
+#include "qpid/framing/Array.h"
+#include "qpid/framing/FieldValue.h"
+
+#include "unit_test.h"
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(ArrayTestSuite)
+
+using namespace qpid::framing;
+
+void populate(std::vector<std::string>& data, int count = 10)
+{
+ for (int i = 0; i < count; i++) {
+ std::stringstream out;
+ out << "item-" << i;
+ data.push_back(out.str());
+ }
+}
+
+QPID_AUTO_TEST_CASE(testEncodeDecode)
+{
+ std::vector<std::string> data;
+ populate(data);
+
+ Array a(data);
+
+ char buff[200];
+ Buffer wbuffer(buff, 200);
+ a.encode(wbuffer);
+
+ Array b;
+ Buffer rbuffer(buff, 200);
+ b.decode(rbuffer);
+ BOOST_CHECK_EQUAL(a, b);
+
+ std::vector<std::string> data2;
+ std::transform(b.begin(), b.end(), std::back_inserter(data2), Array::get<std::string, Array::ValuePtr>);
+ //BOOST_CHECK_EQUAL(data, data2);
+ BOOST_CHECK(data == data2);
+}
+
+QPID_AUTO_TEST_CASE(testArrayAssignment)
+{
+ std::vector<std::string> data;
+ populate(data);
+ Array b;
+ {
+ Array a(data);
+ b = a;
+ BOOST_CHECK_EQUAL(a, b);
+ }
+ std::vector<std::string> data2;
+ std::transform(b.begin(), b.end(), std::back_inserter(data2), Array::get<std::string, Array::ValuePtr>);
+ //BOOST_CHECK_EQUAL(data, data2);
+ BOOST_CHECK(data == data2);
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/AsyncCompletion.cpp b/qpid/cpp/src/tests/AsyncCompletion.cpp
new file mode 100644
index 0000000000..dc43f10156
--- /dev/null
+++ b/qpid/cpp/src/tests/AsyncCompletion.cpp
@@ -0,0 +1,153 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+
+#include "unit_test.h"
+#include "test_tools.h"
+#include "BrokerFixture.h"
+#include "qpid/broker/NullMessageStore.h"
+#include "qpid/sys/BlockingQueue.h"
+#include "qpid/client/AsyncSession.h"
+#include "qpid/sys/Time.h"
+#include "qpid/framing/QueueQueryResult.h"
+#include "qpid/client/TypedResult.h"
+
+using namespace std;
+using namespace qpid;
+using namespace client;
+using namespace framing;
+
+namespace qpid { namespace broker {
+class TransactionContext;
+class PersistableQueue;
+}}
+
+using broker::PersistableMessage;
+using broker::NullMessageStore;
+using broker::TransactionContext;
+using broker::PersistableQueue;
+using sys::TIME_SEC;
+using boost::intrusive_ptr;
+
+/** @file
+ * Unit tests for async completion.
+ * Using a dummy store, verify that the broker indicates async completion of
+ * message enqueues at the correct time.
+ */
+
+namespace qpid {
+namespace tests {
+
+class AsyncCompletionMessageStore : public NullMessageStore {
+ public:
+ sys::BlockingQueue<boost::intrusive_ptr<PersistableMessage> > enqueued;
+
+ AsyncCompletionMessageStore() : NullMessageStore() {}
+ ~AsyncCompletionMessageStore(){}
+
+ void enqueue(TransactionContext*,
+ const boost::intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue& )
+ {
+ enqueued.push(msg);
+ }
+};
+
+QPID_AUTO_TEST_SUITE(AsyncCompletionTestSuite)
+
+/**
+ * Send a sync after a bunch of incomplete messages, verify the sync completes
+ * only when all the messages are complete.
+ */
+QPID_AUTO_TEST_CASE(testWaitTillComplete) {
+ SessionFixture fix;
+ AsyncCompletionMessageStore* store = new AsyncCompletionMessageStore;
+ boost::shared_ptr<qpid::broker::MessageStore> p;
+ p.reset(store);
+ fix.broker->setStore(p);
+ AsyncSession s = fix.session;
+
+ static const int count = 3;
+
+ s.queueDeclare("q", arg::durable=true);
+ Completion transfers[count];
+ for (int i = 0; i < count; ++i) {
+ Message msg(boost::lexical_cast<string>(i), "q");
+ msg.getDeliveryProperties().setDeliveryMode(PERSISTENT);
+ transfers[i] = s.messageTransfer(arg::content=msg);
+ }
+
+ // Get hold of the broker-side messages.
+ typedef vector<intrusive_ptr<PersistableMessage> > BrokerMessages;
+ BrokerMessages enqueued;
+ for (int j = 0; j < count; ++j)
+ enqueued.push_back(store->enqueued.pop(TIME_SEC));
+
+ // Send a sync, make sure it does not complete till all messages are complete.
+ // In reverse order for fun.
+ Completion sync = s.executionSync(arg::sync=true);
+ for (int k = count-1; k >= 0; --k) {
+ BOOST_CHECK(!transfers[k].isComplete()); // Should not be complete yet.
+ BOOST_CHECK(!sync.isComplete()); // Should not be complete yet.
+ enqueued[k]->enqueueComplete();
+ }
+ sync.wait(); // Should complete now, all messages are completed.
+}
+
+/**
+ * Send a sync after all messages are complete, verify it completes immediately.
+ */
+QPID_AUTO_TEST_CASE(testSyncAfterComplete) {
+ SessionFixture fix;
+ AsyncCompletionMessageStore* store = new AsyncCompletionMessageStore;
+ boost::shared_ptr<qpid::broker::MessageStore> p;
+ p.reset(store);
+ fix.broker->setStore(p);
+ AsyncSession s = fix.session;
+
+ static const int count = 3;
+
+ s.queueDeclare("q", arg::durable=true);
+ // Transfer and complete all the messages
+ for (int i = 0; i < count; ++i) {
+ Message msg(boost::lexical_cast<string>(i), "q");
+ msg.getDeliveryProperties().setDeliveryMode(PERSISTENT);
+ Completion transfer = s.messageTransfer(arg::content=msg, arg::sync=true);
+ intrusive_ptr<PersistableMessage> enqueued = store->enqueued.pop(TIME_SEC);
+ enqueued->enqueueComplete();
+ transfer.wait();
+ }
+ // Send a sync, make sure it completes immediately
+ Completion sync = s.executionSync(arg::sync=true);
+ sync.wait(); // Should complete now, all messages are completed.
+}
+
+QPID_AUTO_TEST_CASE(testGetResult) {
+ SessionFixture fix;
+ AsyncSession s = fix.session;
+
+ s.queueDeclare("q", arg::durable=true);
+ TypedResult<QueueQueryResult> tr = s.queueQuery("q");
+ QueueQueryResult qq = tr.get();
+ BOOST_CHECK_EQUAL(qq.getQueue(), "q");
+ BOOST_CHECK_EQUAL(qq.getMessageCount(), 0U);
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/AtomicValue.cpp b/qpid/cpp/src/tests/AtomicValue.cpp
new file mode 100644
index 0000000000..d855d993a7
--- /dev/null
+++ b/qpid/cpp/src/tests/AtomicValue.cpp
@@ -0,0 +1,54 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+
+#include "unit_test.h"
+#include "test_tools.h"
+#include "qpid/sys/AtomicValue.h"
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(AtomicValueTestSuite)
+
+QPID_AUTO_TEST_CASE(test) {
+ qpid::sys::AtomicValue<int> x(0);
+ BOOST_CHECK_EQUAL(++x, 1);
+ BOOST_CHECK_EQUAL(--x,0);
+ BOOST_CHECK_EQUAL(x+=5,5);
+ BOOST_CHECK_EQUAL(x-=10,-5);
+ BOOST_CHECK_EQUAL(x.fetchAndAdd(7), -5);
+ BOOST_CHECK_EQUAL(x.get(),2);
+ BOOST_CHECK_EQUAL(x.fetchAndSub(3), 2);
+ BOOST_CHECK_EQUAL(x.get(),-1);
+
+ BOOST_CHECK_EQUAL(x.valueCompareAndSwap(-1,10), -1);
+ BOOST_CHECK_EQUAL(x.get(), 10);
+ BOOST_CHECK_EQUAL(x.valueCompareAndSwap(5, 6), 10);
+ BOOST_CHECK_EQUAL(x.get(), 10);
+
+ BOOST_CHECK(!x.boolCompareAndSwap(5, 6));
+ BOOST_CHECK_EQUAL(x.get(), 10);
+ BOOST_CHECK(x.boolCompareAndSwap(10, 6));
+ BOOST_CHECK_EQUAL(x.get(), 6);
+}
+
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/Blob.cpp b/qpid/cpp/src/tests/Blob.cpp
new file mode 100644
index 0000000000..9878d92fe4
--- /dev/null
+++ b/qpid/cpp/src/tests/Blob.cpp
@@ -0,0 +1,21 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
diff --git a/qpid/cpp/src/tests/BrokerFixture.h b/qpid/cpp/src/tests/BrokerFixture.h
new file mode 100644
index 0000000000..474b9d747f
--- /dev/null
+++ b/qpid/cpp/src/tests/BrokerFixture.h
@@ -0,0 +1,168 @@
+#ifndef TESTS_BROKERFIXTURE_H
+#define TESTS_BROKERFIXTURE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/BrokerOptions.h"
+#include "qpid/client/Connection.h"
+#include "qpid/client/ConnectionImpl.h"
+#include "qpid/client/Session.h"
+#include "qpid/client/SubscriptionManager.h"
+#include "qpid/client/LocalQueue.h"
+#include "qpid/log/Logger.h"
+#include "qpid/log/Options.h"
+#include "qpid/sys/Thread.h"
+#include <boost/noncopyable.hpp>
+
+namespace qpid {
+namespace tests {
+
+/**
+ * A fixture with an in-process broker.
+ */
+struct BrokerFixture : private boost::noncopyable {
+ typedef qpid::broker::Broker Broker;
+ typedef boost::intrusive_ptr<Broker> BrokerPtr;
+ typedef qpid::broker::BrokerOptions BrokerOptions;
+ typedef std::vector<std::string> Args;
+
+ BrokerPtr broker;
+ BrokerOptions opts;
+ uint16_t port;
+ qpid::sys::Thread brokerThread;
+
+ BrokerFixture(const Args& args=Args(), const BrokerOptions& opts0=BrokerOptions(),
+ bool isExternalPort_=false, uint16_t externalPort_=0) :
+ opts(opts0)
+ {
+ init(args, isExternalPort_, externalPort_);
+ }
+
+ BrokerFixture(const BrokerOptions& opts0,
+ bool isExternalPort_=false, uint16_t externalPort_=0) :
+ opts(opts0)
+ {
+ init(Args(), isExternalPort_, externalPort_);
+ }
+
+ void shutdownBroker() {
+ if (broker) {
+ broker->shutdown();
+ brokerThread.join();
+ broker = BrokerPtr();
+ }
+ }
+
+ ~BrokerFixture() { shutdownBroker(); }
+
+ /** Open a connection to the broker. */
+ void open(qpid::client::Connection& c) {
+ c.open("localhost", getPort());
+ }
+
+ uint16_t getPort() { return port; }
+
+ private:
+ void init(const Args& args, bool isExternalPort=false, uint16_t externalPort=0)
+ {
+ // Keep the tests quiet unless logging env. vars have been set by user.
+ if (!::getenv("QPID_LOG_ENABLE") && !::getenv("QPID_TRACE")) {
+ qpid::log::Options logOpts;
+ logOpts.selectors.clear();
+ logOpts.deselectors.clear();
+ logOpts.selectors.push_back("error+");
+ qpid::log::Logger::instance().configure(logOpts);
+ }
+ // Default options, may be over-ridden when we parse args.
+ opts.port=0;
+ opts.listenInterfaces.push_back("127.0.0.1");
+ opts.workerThreads=1;
+ opts.dataDir="";
+ opts.auth=false;
+
+ // Argument parsing
+ if (args.size() > 0) {
+ std::vector<const char*> argv(args.size());
+ for (size_t i = 0; i<args.size(); ++i)
+ argv[i] = args[i].c_str();
+ Plugin::addOptions(opts);
+ opts.parse(argv.size(), &argv[0]);
+ }
+ broker = Broker::create(opts);
+ // TODO aconway 2007-12-05: At one point BrokerFixture
+ // tests could hang in Connection ctor if the following
+ // line is removed. This may not be an issue anymore.
+ broker->accept();
+ if (isExternalPort) port = externalPort;
+ else port = broker->getPort(qpid::broker::Broker::TCP_TRANSPORT);
+ brokerThread = qpid::sys::Thread(*broker);
+ };
+};
+
+/** Connection that opens in its constructor */
+struct LocalConnection : public qpid::client::Connection {
+ LocalConnection(uint16_t port) { open("localhost", port); }
+ LocalConnection(const qpid::client::ConnectionSettings& s) { open(s); }
+ ~LocalConnection() { close(); }
+};
+
+/** Convenience class to create and open a connection and session
+ * and some related useful objects.
+ */
+template <class ConnectionType=LocalConnection, class SessionType=qpid::client::Session>
+struct ClientT {
+ ConnectionType connection;
+ SessionType session;
+ qpid::client::SubscriptionManager subs;
+ qpid::client::LocalQueue lq;
+ std::string name;
+
+ ClientT(uint16_t port, const std::string& name_=std::string(), int timeout=0)
+ : connection(port), session(connection.newSession(name_,timeout)), subs(session), name(name_) {}
+ ClientT(const qpid::client::ConnectionSettings& settings, const std::string& name_=std::string(), int timeout=0)
+ : connection(settings), session(connection.newSession(name_, timeout)), subs(session), name(name_) {}
+
+ ~ClientT() { close(); }
+ void close() { session.close(); connection.close(); }
+};
+
+typedef ClientT<> Client;
+
+/**
+ * A BrokerFixture and ready-connected BrokerFixture::Client all in one.
+ */
+template <class ConnectionType, class SessionType=qpid::client::Session>
+struct SessionFixtureT : BrokerFixture, ClientT<ConnectionType,SessionType> {
+
+ SessionFixtureT(const BrokerOptions& opts=BrokerOptions()) :
+ BrokerFixture(BrokerFixture::Args(), opts),
+ ClientT<ConnectionType,SessionType>(getPort())
+ {}
+
+};
+
+typedef SessionFixtureT<LocalConnection> SessionFixture;
+
+}} // namespace qpid::tests
+
+#endif /*!TESTS_BROKERFIXTURE_H*/
diff --git a/qpid/cpp/src/tests/BrokerMgmtAgent.cpp b/qpid/cpp/src/tests/BrokerMgmtAgent.cpp
new file mode 100644
index 0000000000..bad7e768a6
--- /dev/null
+++ b/qpid/cpp/src/tests/BrokerMgmtAgent.cpp
@@ -0,0 +1,387 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "unit_test.h"
+#include "MessagingFixture.h"
+#include "qpid/management/Buffer.h"
+#include "qpid/management/ManagementAgent.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/amqp_0_10/Codecs.h"
+#include "qpid/log/Logger.h"
+#include "qpid/log/Options.h"
+
+#include "qmf/org/apache/qpid/broker/mgmt/test/TestObject.h"
+
+#include <iomanip>
+
+
+using qpid::management::Mutex;
+using qpid::management::Manageable;
+using qpid::management::Buffer;
+using namespace qpid::messaging;
+using namespace qpid::types;
+
+
+
+namespace qpid {
+namespace tests {
+
+namespace _qmf = qmf::org::apache::qpid::broker::mgmt::test;
+namespace {
+
+typedef boost::shared_ptr<_qmf::TestObject> TestObjectPtr;
+typedef std::vector<TestObjectPtr> TestObjectVector;
+
+// Instantiates a broker and its internal management agent. Provides
+// factories for constructing Receivers for object indication messages.
+//
+class AgentFixture
+{
+ MessagingFixture *mFix;
+
+ public:
+ AgentFixture( unsigned int pubInterval=10,
+ bool qmfV2=false,
+ qpid::broker::Broker::Options opts = qpid::broker::Broker::Options())
+ {
+ opts.enableMgmt=true;
+ opts.qmf1Support=!qmfV2;
+ opts.qmf2Support=qmfV2;
+ opts.mgmtPubInterval=pubInterval;
+ mFix = new MessagingFixture(opts, true);
+
+ _qmf::TestObject::registerSelf(getBrokerAgent());
+ };
+ ~AgentFixture()
+ {
+ delete mFix;
+ };
+ ::qpid::management::ManagementAgent *getBrokerAgent() { return mFix->broker->getManagementAgent(); }
+ Receiver createV1DataIndRcvr( const std::string package, const std::string klass )
+ {
+ return mFix->session.createReceiver(std::string("kqueue; {create: always, delete: always, "
+ "node: {type: queue, "
+ "x-bindings: [{exchange: qpid.management, "
+ "key: 'console.obj.1.0.")
+ + package + std::string(".") + klass
+ + std::string("'}]}}"));
+ };
+ Receiver createV2DataIndRcvr( const std::string package, const std::string klass )
+ {
+ std::string p(package);
+ std::replace(p.begin(), p.end(), '.', '_');
+ std::string k(klass);
+ std::replace(k.begin(), k.end(), '.', '_');
+
+ return mFix->session.createReceiver(std::string("kqueue; {create: always, delete: always, "
+ "node: {type: queue, "
+ "x-bindings: [{exchange: qmf.default.topic, "
+ "key: 'agent.ind.data.")
+ + p + std::string(".") + k
+ + std::string("'}]}}"));
+ };
+};
+
+
+// A "management object" that supports the TestObject
+//
+class TestManageable : public qpid::management::Manageable
+{
+ management::ManagementObject::shared_ptr mgmtObj;
+ const std::string key;
+ public:
+ TestManageable(management::ManagementAgent *agent, std::string _key)
+ : key(_key)
+ {
+ _qmf::TestObject::shared_ptr tmp(new _qmf::TestObject(agent, this));
+
+ // seed it with some default values...
+ tmp->set_string1(key);
+ tmp->set_bool1(true);
+ qpid::types::Variant::Map vMap;
+ vMap["one"] = qpid::types::Variant(1);
+ vMap["two"] = qpid::types::Variant("two");
+ vMap["three"] = qpid::types::Variant("whatever");
+ tmp->set_map1(vMap);
+
+ mgmtObj = tmp;
+ };
+ ~TestManageable() { mgmtObj.reset(); }
+ management::ManagementObject::shared_ptr GetManagementObject() const { return mgmtObj; };
+ static void validateTestObjectProperties(_qmf::TestObject& to)
+ {
+ // verify the default values are as expected. We don't check 'string1',
+ // as it is the object key, and is unique for each object (no default value).
+ BOOST_CHECK(to.get_bool1() == true);
+ BOOST_CHECK(to.get_map1().size() == 3);
+ qpid::types::Variant::Map mappy = to.get_map1();
+ BOOST_CHECK(1 == (unsigned int)mappy["one"]);
+ BOOST_CHECK(mappy["two"].asString() == std::string("two"));
+ BOOST_CHECK(mappy["three"].asString() == std::string("whatever"));
+ };
+};
+
+
+// decode a V1 Content Indication message
+//
+void decodeV1ObjectUpdates(const Message& inMsg, TestObjectVector& objs, const size_t objLen)
+{
+ const size_t MAX_BUFFER_SIZE=65536;
+ char tmp[MAX_BUFFER_SIZE];
+
+ objs.clear();
+
+ BOOST_CHECK(inMsg.getContent().size() <= MAX_BUFFER_SIZE);
+
+ ::memcpy(tmp, inMsg.getContent().data(), inMsg.getContent().size());
+ Buffer buf(tmp, inMsg.getContent().size());
+
+ while (buf.available() > 8) { // 8 == qmf v1 header size
+ BOOST_CHECK_EQUAL(buf.getOctet(), 'A');
+ BOOST_CHECK_EQUAL(buf.getOctet(), 'M');
+ BOOST_CHECK_EQUAL(buf.getOctet(), '2');
+ BOOST_CHECK_EQUAL(buf.getOctet(), 'c'); // opcode == content indication
+ // @@todo: kag: how do we skip 'i' entries???
+ buf.getLong(); // ignore sequence
+
+ std::string str1; // decode content body as string
+ buf.getRawData(str1, objLen);
+
+ TestObjectPtr fake(new _qmf::TestObject(0,0));
+ fake->readProperties( str1 );
+ objs.push_back(fake);
+ }
+}
+
+
+// decode a V2 Content Indication message
+//
+void decodeV2ObjectUpdates(const qpid::messaging::Message& inMsg, TestObjectVector& objs)
+{
+ objs.clear();
+
+ BOOST_CHECK_EQUAL(inMsg.getContentType(), std::string("amqp/list"));
+
+ const ::qpid::types::Variant::Map& m = inMsg.getProperties();
+ Variant::Map::const_iterator iter = m.find(std::string("qmf.opcode"));
+ BOOST_CHECK(iter != m.end());
+ BOOST_CHECK_EQUAL(iter->second.asString(), std::string("_data_indication"));
+
+ Variant::List vList;
+ ::qpid::amqp_0_10::ListCodec::decode(inMsg.getContent(), vList);
+
+ for (Variant::List::iterator lIter = vList.begin(); lIter != vList.end(); lIter++) {
+ TestObjectPtr fake(new _qmf::TestObject(0,0));
+ fake->readTimestamps(lIter->asMap());
+ fake->mapDecodeValues((lIter->asMap())["_values"].asMap());
+ objs.push_back(fake);
+ }
+}
+}
+
+QPID_AUTO_TEST_SUITE(BrokerMgmtAgent)
+
+// verify that an object that is added to the broker's management database is
+// published correctly. Furthermore, verify that it is published once after
+// it has been deleted.
+//
+QPID_AUTO_TEST_CASE(v1ObjPublish)
+{
+ AgentFixture* fix = new AgentFixture(3);
+ management::ManagementAgent* agent;
+ agent = fix->getBrokerAgent();
+
+ // create a manageable test object
+ TestManageable *tm = new TestManageable(agent, std::string("obj1"));
+ uint32_t objLen = tm->GetManagementObject()->writePropertiesSize();
+
+ Receiver r1 = fix->createV1DataIndRcvr("org.apache.qpid.broker.mgmt.test", "#");
+
+ agent->addObject(tm->GetManagementObject(), 1);
+
+ // wait for the object to be published
+ Message m1;
+ BOOST_CHECK(r1.fetch(m1, Duration::SECOND * 6));
+
+ TestObjectVector objs;
+ decodeV1ObjectUpdates(m1, objs, objLen);
+ BOOST_CHECK(objs.size() > 0);
+
+ for (TestObjectVector::iterator oIter = objs.begin(); oIter != objs.end(); oIter++) {
+
+ TestManageable::validateTestObjectProperties(**oIter);
+
+ qpid::types::Variant::Map mappy;
+ (*oIter)->writeTimestamps(mappy);
+ BOOST_CHECK(0 == mappy["_delete_ts"].asUint64()); // not deleted
+ }
+
+ // destroy the object
+
+ tm->GetManagementObject()->resourceDestroy();
+
+ // wait for the deleted object to be published
+
+ bool isDeleted = false;
+ while (!isDeleted && r1.fetch(m1, Duration::SECOND * 6)) {
+
+ decodeV1ObjectUpdates(m1, objs, objLen);
+ BOOST_CHECK(objs.size() > 0);
+
+ for (TestObjectVector::iterator oIter = objs.begin(); oIter != objs.end(); oIter++) {
+
+ TestManageable::validateTestObjectProperties(**oIter);
+
+ qpid::types::Variant::Map mappy;
+ (*oIter)->writeTimestamps(mappy);
+ if (mappy["_delete_ts"].asUint64() != 0)
+ isDeleted = true;
+ }
+ }
+
+ BOOST_CHECK(isDeleted);
+
+ r1.close();
+ delete fix;
+ delete tm;
+}
+
+// Repeat the previous test, but with V2-based object support
+//
+QPID_AUTO_TEST_CASE(v2ObjPublish)
+{
+ AgentFixture* fix = new AgentFixture(3, true);
+ management::ManagementAgent* agent;
+ agent = fix->getBrokerAgent();
+
+ TestManageable *tm = new TestManageable(agent, std::string("obj2"));
+
+ Receiver r1 = fix->createV2DataIndRcvr(tm->GetManagementObject()->getPackageName(), "#");
+
+ agent->addObject(tm->GetManagementObject(), "testobj-1");
+
+ // wait for the object to be published
+ Message m1;
+ BOOST_CHECK(r1.fetch(m1, Duration::SECOND * 6));
+
+ TestObjectVector objs;
+ decodeV2ObjectUpdates(m1, objs);
+ BOOST_CHECK(objs.size() > 0);
+
+ for (TestObjectVector::iterator oIter = objs.begin(); oIter != objs.end(); oIter++) {
+
+ TestManageable::validateTestObjectProperties(**oIter);
+
+ qpid::types::Variant::Map mappy;
+ (*oIter)->writeTimestamps(mappy);
+ BOOST_CHECK(0 == mappy["_delete_ts"].asUint64());
+ }
+
+ // destroy the object
+
+ tm->GetManagementObject()->resourceDestroy();
+
+ // wait for the deleted object to be published
+
+ bool isDeleted = false;
+ while (!isDeleted && r1.fetch(m1, Duration::SECOND * 6)) {
+
+ decodeV2ObjectUpdates(m1, objs);
+ BOOST_CHECK(objs.size() > 0);
+
+ for (TestObjectVector::iterator oIter = objs.begin(); oIter != objs.end(); oIter++) {
+
+ TestManageable::validateTestObjectProperties(**oIter);
+
+ qpid::types::Variant::Map mappy;
+ (*oIter)->writeTimestamps(mappy);
+ if (mappy["_delete_ts"].asUint64() != 0)
+ isDeleted = true;
+ }
+ }
+
+ BOOST_CHECK(isDeleted);
+
+ r1.close();
+ delete fix;
+ delete tm;
+}
+
+// See QPID-2997
+QPID_AUTO_TEST_CASE(v2RapidRestoreObj)
+{
+ AgentFixture* fix = new AgentFixture(3, true);
+ management::ManagementAgent* agent;
+ agent = fix->getBrokerAgent();
+
+ // two objects, same ObjID
+ TestManageable *tm1 = new TestManageable(agent, std::string("obj2"));
+ TestManageable *tm2 = new TestManageable(agent, std::string("obj2"));
+
+ Receiver r1 = fix->createV2DataIndRcvr(tm1->GetManagementObject()->getPackageName(), "#");
+
+ // add, then immediately delete and re-add a copy of the object
+ agent->addObject(tm1->GetManagementObject(), "testobj-1");
+ tm1->GetManagementObject()->resourceDestroy();
+ agent->addObject(tm2->GetManagementObject(), "testobj-1");
+
+ // expect: a delete notification, then an update notification
+ TestObjectVector objs;
+ bool isDeleted = false;
+ bool isAdvertised = false;
+ size_t count = 0;
+ Message m1;
+ while (r1.fetch(m1, Duration::SECOND * 6)) {
+
+ decodeV2ObjectUpdates(m1, objs);
+ BOOST_CHECK(objs.size() > 0);
+
+ for (TestObjectVector::iterator oIter = objs.begin(); oIter != objs.end(); oIter++) {
+ count++;
+ TestManageable::validateTestObjectProperties(**oIter);
+
+ qpid::types::Variant::Map mappy;
+ (*oIter)->writeTimestamps(mappy);
+ if (mappy["_delete_ts"].asUint64() != 0) {
+ isDeleted = true;
+ BOOST_CHECK(isAdvertised == false); // delete must be first
+ } else {
+ isAdvertised = true;
+ BOOST_CHECK(isDeleted == true); // delete must be first
+ }
+ }
+ }
+
+ BOOST_CHECK(isDeleted);
+ BOOST_CHECK(isAdvertised);
+ BOOST_CHECK(count == 2);
+
+ r1.close();
+ delete fix;
+ delete tm1;
+ delete tm2;
+}
+
+QPID_AUTO_TEST_SUITE_END()
+}
+}
+
+
diff --git a/qpid/cpp/src/tests/BrokerMgmtAgent.xml b/qpid/cpp/src/tests/BrokerMgmtAgent.xml
new file mode 100644
index 0000000000..202b8debf3
--- /dev/null
+++ b/qpid/cpp/src/tests/BrokerMgmtAgent.xml
@@ -0,0 +1,38 @@
+<schema package="org.apache.qpid.broker.mgmt.test">
+
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you under the Apache License, Version 2.0 (the
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+
+ <!--
+ ===============================================================
+ TestObject
+ ===============================================================
+ -->
+ <class name="TestObject">
+
+ A test object defined for the BrokerMgmtAgent unit test.
+
+ <property name="string1" type="lstr" access="RW" index="y"/>
+ <property name="bool1" type="bool" access="RW"/>
+ <property name="map1" type="map" access="RW"/>
+
+ </class>
+
+</schema>
+
diff --git a/qpid/cpp/src/tests/BrokerOptions.cpp b/qpid/cpp/src/tests/BrokerOptions.cpp
new file mode 100644
index 0000000000..b36d96916a
--- /dev/null
+++ b/qpid/cpp/src/tests/BrokerOptions.cpp
@@ -0,0 +1,79 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/** Unit tests for various broker configuration options **/
+
+#include "unit_test.h"
+#include "test_tools.h"
+#include "MessagingFixture.h"
+
+#include "qpid/messaging/Address.h"
+#include "qpid/messaging/Connection.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/messaging/Receiver.h"
+#include "qpid/messaging/Sender.h"
+#include "qpid/messaging/Session.h"
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(BrokerOptionsTestSuite)
+
+using namespace qpid::broker;
+using namespace qpid::messaging;
+using namespace qpid::types;
+using namespace qpid;
+
+QPID_AUTO_TEST_CASE(testDisabledTimestamp)
+{
+ // by default, there should be no timestamp added by the broker
+ MessagingFixture fix;
+
+ Sender sender = fix.session.createSender("test-q; {create:always, delete:sender}");
+ messaging::Message msg("hi");
+ sender.send(msg);
+
+ Receiver receiver = fix.session.createReceiver("test-q");
+ messaging::Message in;
+ BOOST_CHECK(receiver.fetch(in, Duration::IMMEDIATE));
+ Variant::Map props = in.getProperties();
+ BOOST_CHECK(props.find("x-amqp-0-10.timestamp") == props.end());
+}
+
+QPID_AUTO_TEST_CASE(testEnabledTimestamp)
+{
+ // when enabled, the 0.10 timestamp is added by the broker
+ Broker::Options opts;
+ opts.timestampRcvMsgs = true;
+ MessagingFixture fix(opts, true);
+
+ Sender sender = fix.session.createSender("test-q; {create:always, delete:sender}");
+ messaging::Message msg("one");
+ sender.send(msg);
+
+ Receiver receiver = fix.session.createReceiver("test-q");
+ messaging::Message in;
+ BOOST_CHECK(receiver.fetch(in, Duration::IMMEDIATE));
+ Variant::Map props = in.getProperties();
+ BOOST_CHECK(props.find("x-amqp-0-10.timestamp") != props.end());
+ BOOST_CHECK(props["x-amqp-0-10.timestamp"]);
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}}
diff --git a/qpid/cpp/src/tests/CMakeLists.txt b/qpid/cpp/src/tests/CMakeLists.txt
new file mode 100644
index 0000000000..20f98204a4
--- /dev/null
+++ b/qpid/cpp/src/tests/CMakeLists.txt
@@ -0,0 +1,402 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Make sure that everything get built before the tests
+# Need to create a var with all the necessary top level targets
+
+# If we're linking Boost for DLLs, turn that on for the unit test too.
+if (QPID_LINK_BOOST_DYNAMIC)
+ add_definitions(-DBOOST_TEST_DYN_LINK)
+endif (QPID_LINK_BOOST_DYNAMIC)
+
+include_directories( ${CMAKE_CURRENT_SOURCE_DIR} )
+
+# Using the Boost DLLs triggers warning 4275 on Visual Studio
+# (non dll-interface class used as base for dll-interface class).
+# This is ok, so suppress the warning.
+# Also, boost lengthy names trigger warning 4503, decorated name length exceeded
+# and using getenv() triggers insecure CRT warnings which we can silence in the
+# test environment.
+if (MSVC)
+ add_definitions( /wd4275 /wd4503 /D_CRT_SECURE_NO_WARNINGS)
+endif (MSVC)
+
+# Macro to make it easier to remember where the tests are built
+macro(remember_location testname)
+ set (${testname}_LOCATION ${CMAKE_CURRENT_BINARY_DIR}/${testname}${CMAKE_EXECUTABLE_SUFFIX})
+endmacro(remember_location)
+
+# If we're using GCC allow variadic macros (even though they're c99 not c++01)
+if (CMAKE_COMPILER_IS_GNUCXX)
+ add_definitions(-Wno-variadic-macros)
+endif (CMAKE_COMPILER_IS_GNUCXX)
+
+# Windows uses some process-startup calls to ensure that errors, etc. don't
+# result in error boxes being thrown up. Since it's expected that most test
+# runs will be in scripts, the default is to force these outputs to stderr
+# instead of windows. If you want to remove this code, build without the
+# QPID_WINDOWS_DEFAULT_TEST_OUTPUTS ON.
+if (CMAKE_SYSTEM_NAME STREQUAL Windows)
+ option(QPID_WINDOWS_DEFAULT_TEST_OUTPUTS "Use default error-handling on Windows tests" OFF)
+ if (NOT QPID_WINDOWS_DEFAULT_TEST_OUTPUTS)
+ set(platform_test_additions windows/DisableWin32ErrorWindows.cpp)
+ endif (NOT QPID_WINDOWS_DEFAULT_TEST_OUTPUTS)
+endif (CMAKE_SYSTEM_NAME STREQUAL Windows)
+
+# Some generally useful utilities that just happen to be built in the test area
+add_executable (qpid-receive qpid-receive.cpp Statistics.cpp ${platform_test_additions})
+target_link_libraries (qpid-receive qpidmessaging qpidtypes qpidcommon)
+remember_location(qpid-receive)
+
+add_executable (qpid-send qpid-send.cpp Statistics.cpp ${platform_test_additions})
+target_link_libraries (qpid-send qpidmessaging qpidtypes qpidcommon)
+remember_location(qpid-send)
+
+install (TARGETS
+ qpid-receive qpid-send
+ RUNTIME DESTINATION ${QPID_INSTALL_BINDIR})
+
+add_executable (qpid-perftest qpid-perftest.cpp ${platform_test_additions})
+target_link_libraries (qpid-perftest qpidclient qpidcommon ${Boost_PROGRAM_OPTIONS_LIBRARY})
+remember_location(qpid-perftest)
+
+add_executable (qpid-latency-test qpid-latency-test.cpp ${platform_test_additions})
+target_link_libraries (qpid-latency-test qpidclient qpidcommon)
+remember_location(qpid-latency-test)
+
+add_executable (qpid-client-test qpid-client-test.cpp ${platform_test_additions})
+target_link_libraries (qpid-client-test qpidclient qpidcommon)
+remember_location(qpid-client-test)
+
+add_executable (qpid-ping qpid-ping.cpp ${platform_test_additions})
+target_link_libraries (qpid-ping qpidmessaging qpidtypes qpidcommon)
+remember_location(qpid-ping)
+
+add_executable (qpid-topic-listener qpid-topic-listener.cpp ${platform_test_additions})
+target_link_libraries (qpid-topic-listener qpidclient qpidcommon)
+remember_location(qpid-topic-listener)
+
+add_executable (qpid-topic-publisher qpid-topic-publisher.cpp ${platform_test_additions})
+target_link_libraries (qpid-topic-publisher qpidclient qpidcommon)
+remember_location(qpid-topic-publisher)
+
+add_executable (receiver receiver.cpp ${platform_test_additions})
+target_link_libraries (receiver qpidclient qpidcommon)
+remember_location(receiver)
+
+# This is bizarre - using both messaging and client libraries
+add_executable (sender sender.cpp Statistics.cpp ${platform_test_additions})
+target_link_libraries (sender qpidmessaging qpidtypes qpidclient qpidcommon)
+remember_location(sender)
+
+add_executable (qpid-txtest qpid-txtest.cpp ${platform_test_additions})
+target_link_libraries (qpid-txtest qpidclient qpidcommon qpidtypes)
+#qpid_txtest_SOURCES=qpid-txtest.cpp TestOptions.h ConnectionOptions.h
+remember_location(qpid-txtest)
+
+add_executable (qpid-txtest2 qpid-txtest2.cpp ${platform_test_additions})
+target_link_libraries (qpid-txtest2 qpidmessaging qpidtypes qpidcommon)
+remember_location(qpid-txtest2)
+
+install (TARGETS
+ qpid-perftest qpid-latency-test qpid-client-test
+ qpid-ping
+ qpid-topic-listener qpid-topic-publisher receiver sender
+ qpid-txtest qpid-txtest2
+ RUNTIME DESTINATION ${QPID_INSTALL_TESTDIR})
+
+# Only build test code if testing is turned on
+if (BUILD_TESTING)
+
+# Create the environment scripts for tests
+set (abs_srcdir ${CMAKE_CURRENT_SOURCE_DIR})
+set (abs_builddir ${CMAKE_CURRENT_BINARY_DIR})
+set (abs_top_srcdir ${CMAKE_SOURCE_DIR})
+set (abs_top_builddir ${CMAKE_BINARY_DIR})
+set (builddir_lib_suffix "")
+
+if (CMAKE_SYSTEM_NAME STREQUAL Windows)
+ configure_file (${CMAKE_CURRENT_SOURCE_DIR}/test_env.ps1.in
+ ${CMAKE_CURRENT_BINARY_DIR}/test_env.ps1 @ONLY)
+else (CMAKE_SYSTEM_NAME STREQUAL Windows)
+ configure_file (${CMAKE_CURRENT_SOURCE_DIR}/test_env.sh.in
+ ${CMAKE_CURRENT_BINARY_DIR}/test_env.sh @ONLY)
+endif (CMAKE_SYSTEM_NAME STREQUAL Windows)
+
+# Copy qpidd-p0 script to build directory so tests can find it.
+configure_file (${CMAKE_CURRENT_SOURCE_DIR}/qpidd-p0 ${CMAKE_CURRENT_BINARY_DIR} COPYONLY)
+
+if (CMAKE_SYSTEM_NAME STREQUAL Windows)
+ set (ENV{OUTDIR} ${EXECUTABLE_OUTPUT_PATH})
+ set (test_script_suffix ".ps1")
+ set (shell "powershell")
+endif (CMAKE_SYSTEM_NAME STREQUAL Windows)
+
+set(test_wrap ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_test${test_script_suffix} -buildDir ${CMAKE_BINARY_DIR})
+set(python_wrap ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_test${test_script_suffix} -buildDir ${CMAKE_BINARY_DIR} -python)
+
+if (BUILD_TESTING_UNITTESTS)
+
+#
+# Unit test program
+#
+# Unit tests are built as a single program to reduce valgrind overhead
+# when running the tests. If you want to build a subset of the tests run
+# ccmake and set unit_tests_to_build to the set you want to build.
+
+# Like this to work with cmake 2.4 on Unix
+set (qpid_test_boost_libs
+ ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} ${Boost_SYSTEM_LIBRARY})
+
+set(all_unit_tests
+ AccumulatedAckTest
+ Acl
+ AclHost
+ Array
+ AsyncCompletion
+ AtomicValue
+ ClientMessage
+ ClientMessageTest
+ ClientSessionTest
+ DeliveryRecordTest
+ DtxWorkRecordTest
+ exception_test
+ ExchangeTest
+ FieldTable
+ FieldValue
+ FrameDecoder
+ FramingTest
+ HeadersExchangeTest
+ HeaderTest
+ InlineAllocator
+ InlineVector
+ logging
+ ManagementTest
+ MessageReplayTracker
+ MessageTest
+ MessagingLogger
+ MessagingSessionTests
+ PollableCondition
+ ProxyTest
+ QueueDepth
+ QueueFlowLimitTest
+ QueueOptionsTest
+ QueuePolicyTest
+ QueueRegistryTest
+ QueueTest
+ RangeSet
+ RefCounted
+ RetryList
+ Selector
+ SequenceNumberTest
+ SequenceSet
+ SessionState
+ Shlib
+ StringUtils
+ SystemInfo
+ TimerTest
+ TopicExchangeTest
+ TxBufferTest
+ TransactionObserverTest
+ Url
+ Uuid
+ Variant
+ ${xml_tests}
+ )
+
+set(unit_tests_to_build
+ ""
+ CACHE STRING "Which unit tests to build"
+ )
+
+mark_as_advanced(unit_tests_to_build)
+
+# If no unit_test specifically set then use all unit tests
+if (unit_tests_to_build)
+set(actual_unit_tests ${unit_tests_to_build})
+else()
+set(actual_unit_tests ${all_unit_tests})
+endif()
+
+add_executable (unit_test unit_test
+ ${actual_unit_tests} ${platform_test_additions})
+target_link_libraries (unit_test
+ ${qpid_test_boost_libs}
+ qpidmessaging qpidtypes qpidbroker qpidclient qpidcommon)
+set_target_properties (unit_test PROPERTIES COMPILE_DEFINITIONS _IN_QPID_BROKER)
+remember_location(unit_test)
+
+add_test (unit_test ${test_wrap} -boostTest -- ${unit_test_LOCATION})
+
+endif (BUILD_TESTING_UNITTESTS)
+
+add_library (shlibtest MODULE shlibtest.cpp)
+
+if (BUILD_SASL)
+ add_custom_command(
+ OUTPUT sasl_config/qpidd.conf sasl_config/qpidd.sasldb
+ COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/sasl_test_setup.sh)
+
+ add_custom_target(
+ sasl_config ALL
+ DEPENDS sasl_config/qpidd.conf sasl_config/qpidd.sasldb)
+endif (BUILD_SASL)
+
+#
+# Other test programs
+#
+add_executable (echotest echotest.cpp ${platform_test_additions})
+target_link_libraries (echotest qpidclient qpidcommon)
+remember_location(echotest)
+
+add_executable (publish publish.cpp ${platform_test_additions})
+target_link_libraries (publish qpidclient qpidcommon)
+remember_location(publish)
+
+add_executable (consume consume.cpp ${platform_test_additions})
+target_link_libraries (consume qpidclient qpidcommon)
+remember_location(consume)
+
+add_executable (header_test header_test.cpp ${platform_test_additions})
+target_link_libraries (header_test qpidclient qpidcommon)
+remember_location(header_test)
+
+add_executable (declare_queues declare_queues.cpp ${platform_test_additions})
+target_link_libraries (declare_queues qpidclient qpidcommon)
+remember_location(declare_queues)
+
+add_executable (replaying_sender replaying_sender.cpp ${platform_test_additions})
+target_link_libraries (replaying_sender qpidclient qpidcommon)
+remember_location(replaying_sender)
+
+add_executable (resuming_receiver resuming_receiver.cpp ${platform_test_additions})
+target_link_libraries (resuming_receiver qpidclient qpidcommon)
+remember_location(resuming_receiver)
+
+add_executable (txshift txshift.cpp ${platform_test_additions})
+target_link_libraries (txshift qpidclient qpidcommon)
+remember_location(txshift)
+
+add_executable (txjob txjob.cpp ${platform_test_additions})
+target_link_libraries (txjob qpidclient qpidcommon)
+remember_location(txjob)
+
+add_executable (datagen datagen.cpp ${platform_test_additions})
+target_link_libraries (datagen qpidclient qpidcommon)
+remember_location(datagen)
+
+add_executable (msg_group_test msg_group_test.cpp ${platform_test_additions})
+target_link_libraries (msg_group_test qpidmessaging qpidtypes qpidcommon)
+remember_location(msg_group_test)
+
+add_executable (ha_test_max_queues ha_test_max_queues.cpp ${platform_test_additions})
+target_link_libraries (ha_test_max_queues qpidclient qpidcommon)
+remember_location(ha_test_max_queues)
+
+if (BUILD_SASL)
+ add_executable (sasl_version sasl_version.cpp ${platform_test_additions})
+ remember_location(sasl_version)
+endif (BUILD_SASL)
+
+set (python_src ${CMAKE_SOURCE_DIR}/../python)
+if (EXISTS ${python_src})
+ set (python_bld ${CMAKE_CURRENT_BINARY_DIR}/python)
+ # This will not pick up added or deleted python files
+ # In that case you need to rerun CMake
+ file(GLOB_RECURSE python_files ${python_src}/*.py)
+
+ add_custom_command(
+ OUTPUT ${python_bld}
+ DEPENDS ${python_files}
+ COMMAND ${PYTHON_EXECUTABLE}
+ setup.py
+ build --build-base=${python_bld}/build
+ install --prefix=${python_bld} --install-lib=${python_bld} --install-scripts=${python_bld}/commands
+ WORKING_DIRECTORY ${python_src}
+ )
+
+ add_custom_target(
+ python_bld ALL
+ DEPENDS ${python_bld}
+ )
+endif (EXISTS ${python_src})
+
+if (BUILD_SASL)
+ add_test (sasl_fed ${test_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/sasl_fed${test_script_suffix})
+ add_test (sasl_fed_ex_dynamic ${test_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/sasl_fed_ex${test_script_suffix} dynamic)
+ add_test (sasl_fed_ex_link ${test_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/sasl_fed_ex${test_script_suffix} link)
+ add_test (sasl_fed_ex_queue ${test_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/sasl_fed_ex${test_script_suffix} queue)
+ add_test (sasl_fed_ex_route ${test_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/sasl_fed_ex${test_script_suffix} route)
+ add_test (sasl_no_dir ${test_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/sasl_no_dir${test_script_suffix})
+ if (BUILD_SSL)
+ add_test(ssl_test ${test_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/ssl_test${test_script_suffix})
+ endif (BUILD_SSL)
+endif (BUILD_SASL)
+add_test (qpid-client-test ${test_wrap} -startBroker -- ${qpid-client-test_LOCATION})
+add_test (quick_perftest ${test_wrap} -startBroker -- ${qpid-perftest_LOCATION} --summary --count 100)
+add_test (quick_topictest ${test_wrap} -startBroker -- ${CMAKE_CURRENT_SOURCE_DIR}/quick_topictest${test_script_suffix})
+add_test (quick_txtest ${test_wrap} -startBroker -- ${qpid-txtest_LOCATION} --queues 4 --tx-count 10 --quiet)
+add_test (quick_txtest2 ${test_wrap} -startBroker -- ${qpid-txtest2_LOCATION} --queues 4 --tx-count 10 --quiet)
+add_test (msg_group_tests ${test_wrap} -startBroker -- ${CMAKE_CURRENT_SOURCE_DIR}/run_msg_group_tests${test_script_suffix})
+add_test (run_header_test ${test_wrap} -startBroker -- ${CMAKE_CURRENT_SOURCE_DIR}/run_header_test${test_script_suffix})
+add_test (python_tests ${test_wrap} -startBroker -- ${CMAKE_CURRENT_SOURCE_DIR}/python_tests${test_script_suffix})
+if (NOT CMAKE_SYSTEM_NAME STREQUAL Windows)
+ # paged queue not yet implemented for windows
+ add_test (paged_queue_tests ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_paged_queue_tests${test_script_suffix})
+endif (NOT CMAKE_SYSTEM_NAME STREQUAL Windows)
+
+if (BUILD_AMQP)
+ add_test (interop_tests ${python_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/interop_tests.py)
+endif (BUILD_AMQP)
+
+add_test (ha_tests ${python_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/ha_tests.py)
+add_test (qpidd_qmfv2_tests ${python_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/qpidd_qmfv2_tests.py)
+if (BUILD_AMQP)
+ add_test (interlink_tests ${python_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/interlink_tests.py)
+ add_test (idle_timeout_tests ${python_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/idle_timeout_tests.py)
+endif (BUILD_AMQP)
+add_test (swig_python_tests ${test_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/swig_python_tests${test_script_suffix})
+add_test (ipv6_test ${test_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/ipv6_test${test_script_suffix})
+add_test (federation_tests ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_federation_tests${test_script_suffix})
+add_test (federation_sys_tests ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_federation_sys_tests${test_script_suffix})
+add_test (queue_flow_limit_tests
+ ${test_wrap}
+ -startBroker -brokerOptions "--default-flow-stop-threshold=80 --default-flow-resume-threshold=70"
+ -- ${CMAKE_CURRENT_SOURCE_DIR}/run_queue_flow_limit_tests${test_script_suffix})
+if (BUILD_ACL)
+ add_test (acl_tests ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_acl_tests${test_script_suffix})
+endif (BUILD_ACL)
+add_test (cli_tests ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_cli_tests${test_script_suffix})
+add_test (dynamic_log_level_test ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/dynamic_log_level_test${test_script_suffix})
+add_test (dynamic_log_hires_timestamp ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/dynamic_log_hires_timestamp${test_script_suffix})
+if (BUILD_MSSQL)
+ add_test (store_tests ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_store_tests${test_script_suffix} MSSQL)
+endif (BUILD_MSSQL)
+if (BUILD_MSCLFS)
+ add_test (store_tests_clfs ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_store_tests${test_script_suffix} MSSQL-CLFS)
+endif (BUILD_MSCLFS)
+add_test (queue_redirect ${shell} ${CMAKE_CURRENT_SOURCE_DIR}/run_queue_redirect${test_script_suffix})
+
+add_library(test_store MODULE test_store.cpp)
+target_link_libraries (test_store qpidbroker qpidcommon)
+set_target_properties (test_store PROPERTIES PREFIX "" COMPILE_DEFINITIONS _IN_QPID_BROKER)
+
+add_library (dlclose_noop MODULE dlclose_noop.c)
+
+endif (BUILD_TESTING)
diff --git a/qpid/cpp/src/tests/ClientMessage.cpp b/qpid/cpp/src/tests/ClientMessage.cpp
new file mode 100644
index 0000000000..994c46552c
--- /dev/null
+++ b/qpid/cpp/src/tests/ClientMessage.cpp
@@ -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.
+ *
+ */
+#include <iostream>
+#include "qpid/messaging/Message.h"
+
+#include "unit_test.h"
+
+using namespace qpid::messaging;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(ClientMessageSuite)
+
+QPID_AUTO_TEST_CASE(testCopyConstructor)
+{
+ Message m("my-data");
+ m.setSubject("my-subject");
+ m.getProperties()["a"] = "ABC";
+ Message c(m);
+ BOOST_CHECK_EQUAL(m.getContent(), c.getContent());
+ BOOST_CHECK_EQUAL(m.getSubject(), c.getSubject());
+ BOOST_CHECK_EQUAL(m.getProperties()["a"], c.getProperties()["a"]);
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/ClientMessageTest.cpp b/qpid/cpp/src/tests/ClientMessageTest.cpp
new file mode 100644
index 0000000000..f925f1c234
--- /dev/null
+++ b/qpid/cpp/src/tests/ClientMessageTest.cpp
@@ -0,0 +1,51 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+
+/**@file Unit tests for the client::Message class. */
+
+#include "unit_test.h"
+#include "qpid/client/Message.h"
+
+using namespace qpid::client;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(ClientMessageTestSuite)
+
+QPID_AUTO_TEST_CASE(MessageCopyAssign) {
+ // Verify that message has normal copy semantics.
+ Message m("foo");
+ BOOST_CHECK_EQUAL("foo", m.getData());
+ Message c(m);
+ BOOST_CHECK_EQUAL("foo", c.getData());
+ Message a;
+ BOOST_CHECK_EQUAL("", a.getData());
+ a = m;
+ BOOST_CHECK_EQUAL("foo", a.getData());
+ a.setData("a");
+ BOOST_CHECK_EQUAL("a", a.getData());
+ c.setData("c");
+ BOOST_CHECK_EQUAL("c", c.getData());
+ BOOST_CHECK_EQUAL("foo", m.getData());
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/ClientSessionTest.cpp b/qpid/cpp/src/tests/ClientSessionTest.cpp
new file mode 100644
index 0000000000..f35524c0c0
--- /dev/null
+++ b/qpid/cpp/src/tests/ClientSessionTest.cpp
@@ -0,0 +1,663 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "unit_test.h"
+#include "test_tools.h"
+#include "BrokerFixture.h"
+#include "qpid/client/QueueOptions.h"
+#include "qpid/client/MessageListener.h"
+#include "qpid/client/SubscriptionManager.h"
+#include "qpid/client/AsyncSession.h"
+#include "qpid/sys/Monitor.h"
+#include "qpid/sys/Thread.h"
+#include "qpid/sys/Runnable.h"
+#include "qpid/sys/Time.h"
+#include "qpid/client/Session.h"
+#include "qpid/client/Message.h"
+#include "qpid/framing/reply_exceptions.h"
+
+#include <boost/optional.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/bind.hpp>
+#include <boost/ptr_container/ptr_vector.hpp>
+#include <boost/format.hpp>
+
+#include <vector>
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(ClientSessionTest)
+
+using namespace qpid::client;
+using namespace qpid::framing;
+using namespace qpid;
+using qpid::sys::Monitor;
+using qpid::sys::Thread;
+using qpid::sys::TIME_SEC;
+using qpid::broker::BrokerOptions;
+using std::string;
+using std::cout;
+using std::endl;
+
+
+struct DummyListener : public sys::Runnable, public MessageListener {
+ std::vector<Message> messages;
+ string name;
+ uint expected;
+ SubscriptionManager submgr;
+
+ DummyListener(Session& session, const string& n, uint ex) :
+ name(n), expected(ex), submgr(session) {}
+
+ void run()
+ {
+ submgr.subscribe(*this, name);
+ submgr.run();
+ }
+
+ void received(Message& msg)
+ {
+ messages.push_back(msg);
+ if (--expected == 0) {
+ submgr.stop();
+ }
+ }
+};
+
+struct SimpleListener : public MessageListener
+{
+ Monitor lock;
+ std::vector<Message> messages;
+
+ void received(Message& msg)
+ {
+ Monitor::ScopedLock l(lock);
+ messages.push_back(msg);
+ lock.notifyAll();
+ }
+
+ void waitFor(const uint n)
+ {
+ Monitor::ScopedLock l(lock);
+ while (messages.size() < n) {
+ lock.wait();
+ }
+ }
+};
+
+struct ClientSessionFixture : public SessionFixture
+{
+ ClientSessionFixture(const BrokerOptions& opts = BrokerOptions()) : SessionFixture(opts) {
+ session.queueDeclare(arg::queue="my-queue");
+ }
+};
+
+QPID_AUTO_TEST_CASE(testQueueQuery) {
+ ClientSessionFixture fix;
+ fix.session = fix.connection.newSession();
+ fix.session.queueDeclare(arg::queue="q", arg::alternateExchange="amq.fanout",
+ arg::exclusive=true, arg::autoDelete=true);
+ QueueQueryResult result = fix.session.queueQuery("q");
+ BOOST_CHECK_EQUAL(false, result.getDurable());
+ BOOST_CHECK_EQUAL(true, result.getExclusive());
+ BOOST_CHECK_EQUAL("amq.fanout", result.getAlternateExchange());
+}
+
+QPID_AUTO_TEST_CASE(testDispatcher)
+{
+ ClientSessionFixture fix;
+ fix.session =fix.connection.newSession();
+ size_t count = 100;
+ for (size_t i = 0; i < count; ++i)
+ fix.session.messageTransfer(arg::content=Message(boost::lexical_cast<string>(i), "my-queue"));
+ DummyListener listener(fix.session, "my-queue", count);
+ listener.run();
+ BOOST_CHECK_EQUAL(count, listener.messages.size());
+ for (size_t i = 0; i < count; ++i)
+ BOOST_CHECK_EQUAL(boost::lexical_cast<string>(i), listener.messages[i].getData());
+}
+
+QPID_AUTO_TEST_CASE(testDispatcherThread)
+{
+ ClientSessionFixture fix;
+ fix.session =fix.connection.newSession();
+ size_t count = 10;
+ DummyListener listener(fix.session, "my-queue", count);
+ sys::Thread t(listener);
+ for (size_t i = 0; i < count; ++i) {
+ fix.session.messageTransfer(arg::content=Message(boost::lexical_cast<string>(i), "my-queue"));
+ }
+ t.join();
+ BOOST_CHECK_EQUAL(count, listener.messages.size());
+ for (size_t i = 0; i < count; ++i)
+ BOOST_CHECK_EQUAL(boost::lexical_cast<string>(i), listener.messages[i].getData());
+}
+
+QPID_AUTO_TEST_CASE(testUseSuspendedError)
+{
+ ClientSessionFixture fix;
+ fix.session.timeout(60);
+ fix.session.suspend();
+ try {
+ fix.session.exchangeQuery(arg::exchange="amq.fanout");
+ BOOST_FAIL("Expected session suspended exception");
+ } catch(const NotAttachedException&) {}
+}
+
+QPID_AUTO_TEST_CASE(testSendToSelf) {
+ ClientSessionFixture fix;
+ SimpleListener mylistener;
+ fix.session.queueDeclare(arg::queue="myq", arg::exclusive=true, arg::autoDelete=true);
+ fix.subs.subscribe(mylistener, "myq");
+ sys::Thread runner(fix.subs);//start dispatcher thread
+ string data("msg");
+ Message msg(data, "myq");
+ const uint count=10;
+ for (uint i = 0; i < count; ++i) {
+ fix.session.messageTransfer(arg::content=msg);
+ }
+ mylistener.waitFor(count);
+ fix.subs.cancel("myq");
+ fix.subs.stop();
+ runner.join();
+ fix.session.close();
+ BOOST_CHECK_EQUAL(mylistener.messages.size(), count);
+ for (uint j = 0; j < count; ++j) {
+ BOOST_CHECK_EQUAL(mylistener.messages[j].getData(), data);
+ }
+}
+
+QPID_AUTO_TEST_CASE(testLocalQueue) {
+ ClientSessionFixture fix;
+ fix.session.queueDeclare(arg::queue="lq", arg::exclusive=true, arg::autoDelete=true);
+ LocalQueue lq;
+ fix.subs.subscribe(lq, "lq", FlowControl(2, FlowControl::UNLIMITED, false));
+ fix.session.messageTransfer(arg::content=Message("foo0", "lq"));
+ fix.session.messageTransfer(arg::content=Message("foo1", "lq"));
+ fix.session.messageTransfer(arg::content=Message("foo2", "lq"));
+ BOOST_CHECK_EQUAL("foo0", lq.pop().getData());
+ BOOST_CHECK_EQUAL("foo1", lq.pop().getData());
+ BOOST_CHECK(lq.empty()); // Credit exhausted.
+ fix.subs.getSubscription("lq").setFlowControl(FlowControl::unlimited());
+ BOOST_CHECK_EQUAL("foo2", lq.pop().getData());
+}
+
+struct DelayedTransfer : sys::Runnable
+{
+ ClientSessionFixture& fixture;
+
+ DelayedTransfer(ClientSessionFixture& f) : fixture(f) {}
+
+ void run()
+ {
+ qpid::sys::sleep(1);
+ fixture.session.messageTransfer(arg::content=Message("foo2", "getq"));
+ }
+};
+
+QPID_AUTO_TEST_CASE(testGet) {
+ ClientSessionFixture fix;
+ fix.session.queueDeclare(arg::queue="getq", arg::exclusive=true, arg::autoDelete=true);
+ fix.session.messageTransfer(arg::content=Message("foo0", "getq"));
+ fix.session.messageTransfer(arg::content=Message("foo1", "getq"));
+ Message got;
+ BOOST_CHECK(fix.subs.get(got, "getq", TIME_SEC));
+ BOOST_CHECK_EQUAL("foo0", got.getData());
+ BOOST_CHECK(fix.subs.get(got, "getq", TIME_SEC));
+ BOOST_CHECK_EQUAL("foo1", got.getData());
+ BOOST_CHECK(!fix.subs.get(got, "getq"));
+ DelayedTransfer sender(fix);
+ Thread t(sender);
+ //test timed get where message shows up after a short delay
+ BOOST_CHECK(fix.subs.get(got, "getq", 5*TIME_SEC));
+ BOOST_CHECK_EQUAL("foo2", got.getData());
+ t.join();
+}
+
+QPID_AUTO_TEST_CASE(testOpenFailure) {
+ BrokerFixture b;
+ Connection c;
+ string host("unknowable-host");
+ try {
+ c.open(host);
+ } catch (const Exception&) {
+ BOOST_CHECK(!c.isOpen());
+ }
+ b.open(c);
+ BOOST_CHECK(c.isOpen());
+ c.close();
+ BOOST_CHECK(!c.isOpen());
+}
+
+QPID_AUTO_TEST_CASE(testPeriodicExpiration) {
+ BrokerOptions opts;
+ opts.queueCleanInterval = 1*TIME_SEC;
+ opts.queueFlowStopRatio = 0;
+ opts.queueFlowResumeRatio = 0;
+ ClientSessionFixture fix(opts);
+ FieldTable args;
+ args.setInt("qpid.max_count",10);
+ fix.session.queueDeclare(arg::queue="my-queue", arg::exclusive=true, arg::autoDelete=true, arg::arguments=args);
+
+ for (uint i = 0; i < 10; i++) {
+ Message m((boost::format("Message_%1%") % (i+1)).str(), "my-queue");
+ if (i % 2) m.getDeliveryProperties().setTtl(500);
+ fix.session.messageTransfer(arg::content=m);
+ }
+
+ BOOST_CHECK_EQUAL(fix.session.queueQuery(string("my-queue")).getMessageCount(), 10u);
+ qpid::sys::sleep(2);
+ BOOST_CHECK_EQUAL(fix.session.queueQuery(string("my-queue")).getMessageCount(), 5u);
+ fix.session.messageTransfer(arg::content=Message("Message_11", "my-queue"));//ensure policy is also updated
+}
+
+QPID_AUTO_TEST_CASE(testExpirationOnPop) {
+ ClientSessionFixture fix;
+ fix.session.queueDeclare(arg::queue="my-queue", arg::exclusive=true, arg::autoDelete=true);
+
+ for (uint i = 0; i < 10; i++) {
+ Message m((boost::format("Message_%1%") % (i+1)).str(), "my-queue");
+ if (i % 2) m.getDeliveryProperties().setTtl(200);
+ fix.session.messageTransfer(arg::content=m);
+ }
+
+ qpid::sys::usleep(300* 1000);
+
+ for (uint i = 0; i < 10; i++) {
+ if (i % 2) continue;
+ Message m;
+ BOOST_CHECK(fix.subs.get(m, "my-queue", TIME_SEC));
+ BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i+1)).str(), m.getData());
+ }
+}
+
+QPID_AUTO_TEST_CASE(testRelease) {
+ ClientSessionFixture fix;
+
+ const uint count=10;
+ for (uint i = 0; i < count; i++) {
+ Message m((boost::format("Message_%1%") % (i+1)).str(), "my-queue");
+ fix.session.messageTransfer(arg::content=m);
+ }
+
+ fix.subs.setAutoStop(false);
+ fix.subs.start();
+ SubscriptionSettings settings;
+ settings.autoAck = 0;
+
+ SimpleListener l1;
+ Subscription s1 = fix.subs.subscribe(l1, "my-queue", settings);
+ l1.waitFor(count);
+ s1.cancel();
+
+ for (uint i = 0; i < count; i++) {
+ BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i+1)).str(), l1.messages[i].getData());
+ }
+ s1.release(s1.getUnaccepted());
+
+ //check that released messages are redelivered
+ settings.autoAck = 1;
+ SimpleListener l2;
+ Subscription s2 = fix.subs.subscribe(l2, "my-queue", settings);
+ l2.waitFor(count);
+ for (uint i = 0; i < count; i++) {
+ BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i+1)).str(), l2.messages[i].getData());
+ }
+
+ fix.subs.stop();
+ fix.subs.wait();
+ fix.session.close();
+}
+
+QPID_AUTO_TEST_CASE(testCompleteOnAccept) {
+ ClientSessionFixture fix;
+ const uint count = 8;
+ const uint chunk = 4;
+ for (uint i = 0; i < count; i++) {
+ Message m((boost::format("Message_%1%") % (i+1)).str(), "my-queue");
+ fix.session.messageTransfer(arg::content=m);
+ }
+
+ SubscriptionSettings settings;
+ settings.autoAck = 0;
+ settings.completionMode = COMPLETE_ON_ACCEPT;
+ settings.flowControl = FlowControl::messageWindow(chunk);
+
+ LocalQueue q;
+ Subscription s = fix.subs.subscribe(q, "my-queue", settings);
+ fix.session.messageFlush(arg::destination=s.getName());
+ SequenceSet accepted;
+ for (uint i = 0; i < chunk; i++) {
+ Message m;
+ BOOST_CHECK(q.get(m));
+ BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i+1)).str(), m.getData());
+ accepted.add(m.getId());
+ }
+ Message m;
+ BOOST_CHECK(!q.get(m));
+
+ s.accept(accepted);
+ //need to reallocate credit as we have flushed it all out
+ s.setFlowControl(FlowControl::messageWindow(chunk));
+ fix.session.messageFlush(arg::destination=s.getName());
+ accepted.clear();
+
+ for (uint i = chunk; i < count; i++) {
+ Message m;
+ BOOST_CHECK(q.get(m));
+ BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i+1)).str(), m.getData());
+ accepted.add(m.getId());
+ }
+ fix.session.messageAccept(accepted);
+}
+
+namespace
+{
+struct Publisher : qpid::sys::Runnable
+{
+ AsyncSession session;
+ Message message;
+ uint count;
+ Thread thread;
+
+ Publisher(Connection& con, Message m, uint c) : session(con.newSession()), message(m), count(c) {}
+
+ void start()
+ {
+ thread = Thread(*this);
+ }
+
+ void join()
+ {
+ thread.join();
+ }
+
+ void run()
+ {
+ for (uint i = 0; i < count; i++) {
+ session.messageTransfer(arg::content=message);
+ }
+ session.sync();
+ session.close();
+ }
+};
+}
+
+QPID_AUTO_TEST_CASE(testConcurrentSenders)
+{
+ //Ensure concurrent publishing sessions on a connection don't
+ //cause assertions, deadlocks or other undesirables:
+ BrokerFixture fix;
+ Connection connection;
+ ConnectionSettings settings;
+ settings.maxFrameSize = 1024;
+ settings.port = fix.broker->getPort(qpid::broker::Broker::TCP_TRANSPORT);
+ connection.open(settings);
+ AsyncSession session = connection.newSession();
+ Message message(string(512, 'X'));
+
+ boost::ptr_vector<Publisher> publishers;
+ for (size_t i = 0; i < 5; i++) {
+ publishers.push_back(new Publisher(connection, message, 100));
+ }
+ std::for_each(publishers.begin(), publishers.end(), boost::bind(&Publisher::start, _1));
+ std::for_each(publishers.begin(), publishers.end(), boost::bind(&Publisher::join, _1));
+ connection.close();
+}
+
+
+QPID_AUTO_TEST_CASE(testExclusiveSubscribe)
+{
+ ClientSessionFixture fix;
+ fix.session.queueDeclare(arg::queue="myq", arg::exclusive=true, arg::autoDelete=true);
+ SubscriptionSettings settings;
+ settings.exclusive = true;
+ LocalQueue q;
+ fix.subs.subscribe(q, "myq", settings, "first");
+ //attempt to create new subscriber should fail
+ ScopedSuppressLogging sl;
+ BOOST_CHECK_THROW(fix.subs.subscribe(q, "myq", "second"), ResourceLockedException);
+ ;
+
+}
+
+QPID_AUTO_TEST_CASE(testExclusiveBinding) {
+ FieldTable options;
+ options.setString("qpid.exclusive-binding", "anything");
+ ClientSessionFixture fix;
+ fix.session.queueDeclare(arg::queue="queue-1", arg::exclusive=true, arg::autoDelete=true);
+ fix.session.queueDeclare(arg::queue="queue-2", arg::exclusive=true, arg::autoDelete=true);
+ fix.session.exchangeBind(arg::exchange="amq.direct", arg::queue="queue-1", arg::bindingKey="my-key", arg::arguments=options);
+ fix.session.messageTransfer(arg::destination="amq.direct", arg::content=Message("message1", "my-key"));
+ fix.session.exchangeBind(arg::exchange="amq.direct", arg::queue="queue-2", arg::bindingKey="my-key", arg::arguments=options);
+ fix.session.messageTransfer(arg::destination="amq.direct", arg::content=Message("message2", "my-key"));
+
+ Message got;
+ BOOST_CHECK(fix.subs.get(got, "queue-1"));
+ BOOST_CHECK_EQUAL("message1", got.getData());
+ BOOST_CHECK(!fix.subs.get(got, "queue-1"));
+
+ BOOST_CHECK(fix.subs.get(got, "queue-2"));
+ BOOST_CHECK_EQUAL("message2", got.getData());
+ BOOST_CHECK(!fix.subs.get(got, "queue-2"));
+}
+
+QPID_AUTO_TEST_CASE(testResubscribeWithLocalQueue) {
+ ClientSessionFixture fix;
+ fix.session.queueDeclare(arg::queue="some-queue", arg::exclusive=true, arg::autoDelete=true);
+ LocalQueue p, q;
+ fix.subs.subscribe(p, "some-queue");
+ fix.subs.cancel("some-queue");
+ fix.subs.subscribe(q, "some-queue");
+
+ fix.session.messageTransfer(arg::content=Message("some-data", "some-queue"));
+ fix.session.messageFlush(arg::destination="some-queue");
+
+ Message got;
+ BOOST_CHECK(!p.get(got));
+
+ BOOST_CHECK(q.get(got));
+ BOOST_CHECK_EQUAL("some-data", got.getData());
+ BOOST_CHECK(!q.get(got));
+}
+
+QPID_AUTO_TEST_CASE(testReliableDispatch) {
+ ClientSessionFixture fix;
+ std::string queue("a-queue");
+ fix.session.queueDeclare(arg::queue=queue, arg::autoDelete=true);
+
+ ConnectionSettings settings;
+ settings.port = fix.broker->getPort(qpid::broker::Broker::TCP_TRANSPORT);
+
+ Connection c1;
+ c1.open(settings);
+ Session s1 = c1.newSession();
+ SubscriptionManager subs1(s1);
+ LocalQueue q1;
+ subs1.subscribe(q1, queue, FlowControl());//first subscriber has no credit
+
+ Connection c2;
+ c2.open(settings);
+ Session s2 = c2.newSession();
+ SubscriptionManager subs2(s2);
+ LocalQueue q2;
+ subs2.subscribe(q2, queue);//second subscriber has credit
+
+ fix.session.messageTransfer(arg::content=Message("my-message", queue));
+
+ //check that the second consumer gets the message
+ Message got;
+ BOOST_CHECK(q2.get(got, 1*TIME_SEC));
+ BOOST_CHECK_EQUAL("my-message", got.getData());
+
+ c1.close();
+ c2.close();
+}
+
+QPID_AUTO_TEST_CASE(testSessionCloseOnInvalidSession) {
+ Session session;
+ session.close();
+}
+
+QPID_AUTO_TEST_CASE(testLVQVariedSize) {
+ ClientSessionFixture fix;
+ std::string queue("my-lvq");
+ QueueOptions args;
+ args.setOrdering(LVQ_NO_BROWSE);
+ fix.session.queueDeclare(arg::queue=queue, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args);
+
+ std::string key;
+ args.getLVQKey(key);
+
+ for (size_t i = 0; i < 10; i++) {
+ std::ostringstream data;
+ size_t size = 100 - ((i % 10) * 10);
+ data << std::string(size, 'x');
+
+ Message m(data.str(), queue);
+ m.getHeaders().setString(key, "abc");
+ fix.session.messageTransfer(arg::content=m);
+ }
+}
+
+QPID_AUTO_TEST_CASE(testSessionManagerSetFlowControl) {
+ ClientSessionFixture fix;
+ std::string name("dummy");
+ LocalQueue queue;
+ SubscriptionSettings settings;
+ settings.flowControl = FlowControl();
+ fix.session.queueDeclare(arg::queue=name, arg::exclusive=true, arg::autoDelete=true);
+ fix.subs.subscribe(queue, name, settings);
+ fix.session.messageTransfer(arg::content=Message("my-message", name));
+ fix.subs.setFlowControl(name, 1, FlowControl::UNLIMITED, false);
+ fix.session.messageFlush(name);
+ Message got;
+ BOOST_CHECK(queue.get(got, 0));
+ BOOST_CHECK_EQUAL("my-message", got.getData());
+}
+
+QPID_AUTO_TEST_CASE(testGetThenSubscribe) {
+ ClientSessionFixture fix;
+ std::string name("myqueue");
+ fix.session.queueDeclare(arg::queue=name, arg::exclusive=true, arg::autoDelete=true);
+ fix.session.messageTransfer(arg::content=Message("one", name));
+ fix.session.messageTransfer(arg::content=Message("two", name));
+ Message got;
+ BOOST_CHECK(fix.subs.get(got, name));
+ BOOST_CHECK_EQUAL("one", got.getData());
+
+ DummyListener listener(fix.session, name, 1);
+ listener.run();
+ BOOST_CHECK_EQUAL(1u, listener.messages.size());
+ if (!listener.messages.empty()) {
+ BOOST_CHECK_EQUAL("two", listener.messages[0].getData());
+ }
+}
+
+QPID_AUTO_TEST_CASE(testSessionIsValid) {
+ ClientSessionFixture fix;
+ BOOST_CHECK(fix.session.isValid());
+ Session session;
+ BOOST_CHECK(!session.isValid());
+}
+
+QPID_AUTO_TEST_CASE(testExpirationNotAltered) {
+ ClientSessionFixture fix;
+ fix.session.queueDeclare(arg::queue="my-queue", arg::exclusive=true, arg::autoDelete=true);
+
+ Message m("my-message", "my-queue");
+ m.getDeliveryProperties().setTtl(60000);
+ m.getDeliveryProperties().setExpiration(12345);
+ fix.session.messageTransfer(arg::content=m);
+ Message got;
+ BOOST_CHECK(fix.subs.get(got, "my-queue"));
+ BOOST_CHECK_EQUAL("my-message", got.getData());
+ BOOST_CHECK_EQUAL(12345u, got.getDeliveryProperties().getExpiration());
+}
+
+QPID_AUTO_TEST_CASE(testGetConnectionFromSession) {
+ ClientSessionFixture fix;
+ FieldTable options;
+ options.setInt("no-local", 1);
+ fix.session.queueDeclare(arg::queue="a", arg::exclusive=true, arg::autoDelete=true, arg::arguments=options);
+ fix.session.queueDeclare(arg::queue="b", arg::exclusive=true, arg::autoDelete=true);
+
+ Connection c = fix.session.getConnection();
+ Session s = c.newSession();
+ //If this new session was created as expected on the same connection as
+ //fix.session, then the no-local behaviour means that queue 'a'
+ //will not enqueue messages from this new session but queue 'b'
+ //will.
+ s.messageTransfer(arg::content=Message("a", "a"));
+ s.messageTransfer(arg::content=Message("b", "b"));
+
+ Message got;
+ BOOST_CHECK(fix.subs.get(got, "b"));
+ BOOST_CHECK_EQUAL("b", got.getData());
+ BOOST_CHECK(!fix.subs.get(got, "a"));
+}
+
+
+QPID_AUTO_TEST_CASE(testQueueDeleted)
+{
+ ClientSessionFixture fix;
+ fix.session.queueDeclare(arg::queue="my-queue");
+ LocalQueue queue;
+ fix.subs.subscribe(queue, "my-queue");
+
+ ScopedSuppressLogging sl;
+ fix.session.queueDelete(arg::queue="my-queue");
+ BOOST_CHECK_THROW(queue.get(1*qpid::sys::TIME_SEC), qpid::framing::ResourceDeletedException);
+}
+
+QPID_AUTO_TEST_CASE(testTtl)
+{
+ const uint64_t ms = 1000ULL; // convert sec to ms
+ const uint64_t us = 1000ULL * 1000ULL; // convert sec to us
+
+ ClientSessionFixture fix;
+ fix.session.queueDeclare(arg::queue="ttl-test", arg::exclusive=true, arg::autoDelete=true);
+ Message msg1 = Message("AAA", "ttl-test");
+ uint64_t ttl = 2 * ms; // 2 sec
+ msg1.getDeliveryProperties().setTtl(ttl);
+ Connection c = fix.session.getConnection();
+ Session s = c.newSession();
+ s.messageTransfer(arg::content=msg1);
+
+ Message msg2 = Message("BBB", "ttl-test");
+ ttl = 10 * ms; // 10 sec
+ msg2.getDeliveryProperties().setTtl(ttl);
+ s.messageTransfer(arg::content=msg2);
+
+ qpid::sys::usleep(5 * us); // 5 sec
+
+ // Message "AAA" should be expired and never be delivered
+ // Check "BBB" has ttl somewhere between 1 and 5 secs
+ Message got;
+ BOOST_CHECK(fix.subs.get(got, "ttl-test"));
+ BOOST_CHECK_EQUAL("BBB", got.getData());
+ BOOST_CHECK(got.getDeliveryProperties().getTtl() > 1 * ms);
+ BOOST_CHECK(got.getDeliveryProperties().getTtl() < ttl - (5 * ms));
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/ConnectionOptions.h b/qpid/cpp/src/tests/ConnectionOptions.h
new file mode 100644
index 0000000000..fe945e9ddd
--- /dev/null
+++ b/qpid/cpp/src/tests/ConnectionOptions.h
@@ -0,0 +1,62 @@
+#ifndef QPID_CLIENT_CONNECTIONOPTIONS_H
+#define QPID_CLIENT_CONNECTIONOPTIONS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/client/ConnectionSettings.h"
+#include "qpid/Options.h"
+
+namespace qpid {
+
+/**
+ * Options parser for ConnectionOptions.
+ */
+struct ConnectionOptions : public qpid::Options,
+ public qpid::client::ConnectionSettings
+{
+ ConnectionOptions() : qpid::Options("Connection Settings")
+ {
+ using namespace qpid;
+ addOptions()
+ ("broker,b", optValue(host, "HOST"), "Broker host to connect to")
+ ("port,p", optValue(port, "PORT"), "Broker port to connect to")
+ ("protocol,P", optValue(protocol, "tcp|ssl|rdma"), "Protocol to use for broker connection")
+ ("virtualhost,v", optValue(virtualhost, "VHOST"), "virtual host")
+ ("username", optValue(username, "USER"), "user name for broker log in.")
+ ("password", optValue(password, "PASSWORD"), "password for broker log in.")
+ ("mechanism", optValue(mechanism, "MECH"), "SASL mechanism to use when authenticating.")
+ ("locale", optValue(locale, "LOCALE"), "locale to use.")
+ ("max-channels", optValue(maxChannels, "N"), "the maximum number of channels the client requires.")
+ ("heartbeat", optValue(heartbeat, "N"), "Desired heartbeat interval in seconds.")
+ ("max-frame-size", optValue(maxFrameSize, "N"), "the maximum frame size to request.")
+ ("bounds-multiplier", optValue(bounds, "N"),
+ "bound size of write queue (as a multiple of the max frame size).")
+ ("tcp-nodelay", optValue(tcpNoDelay), "Turn on tcp-nodelay")
+ ("service", optValue(service, "SERVICE-NAME"), "SASL service name.")
+ ("min-ssf", optValue(minSsf, "N"), "Minimum acceptable strength for SASL security layer")
+ ("max-ssf", optValue(maxSsf, "N"), "Maximum acceptable strength for SASL security layer");
+ }
+};
+
+} // namespace qpid
+
+#endif /*!QPID_CLIENT_CONNECTIONOPTIONS_H*/
diff --git a/qpid/cpp/src/tests/DeliveryRecordTest.cpp b/qpid/cpp/src/tests/DeliveryRecordTest.cpp
new file mode 100644
index 0000000000..37b3095f81
--- /dev/null
+++ b/qpid/cpp/src/tests/DeliveryRecordTest.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/DeliveryRecord.h"
+#include "qpid/broker/Queue.h"
+#include "unit_test.h"
+#include <iostream>
+#include <memory>
+#include <boost/format.hpp>
+
+using namespace qpid::broker;
+using namespace qpid::sys;
+using namespace qpid::framing;
+using boost::dynamic_pointer_cast;
+using std::list;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(DeliveryRecordTestSuite)
+
+QPID_AUTO_TEST_CASE(testSort)
+{
+ list<SequenceNumber> ids;
+ ids.push_back(SequenceNumber(6));
+ ids.push_back(SequenceNumber(2));
+ ids.push_back(SequenceNumber(4));
+ ids.push_back(SequenceNumber(5));
+ ids.push_back(SequenceNumber(1));
+ ids.push_back(SequenceNumber(3));
+
+ list<DeliveryRecord> records;
+ for (list<SequenceNumber>::iterator i = ids.begin(); i != ids.end(); i++) {
+ DeliveryRecord r(QueueCursor(CONSUMER), framing::SequenceNumber(), SequenceNumber(), Queue::shared_ptr(), "tag", Consumer::shared_ptr(), false, false, false);
+ r.setId(*i);
+ records.push_back(r);
+ }
+ records.sort();
+
+ SequenceNumber expected(0);
+ for (list<DeliveryRecord>::iterator i = records.begin(); i != records.end(); i++) {
+ BOOST_CHECK(i->getId() == ++expected);
+ }
+}
+
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/DispatcherTest.cpp b/qpid/cpp/src/tests/DispatcherTest.cpp
new file mode 100644
index 0000000000..7312fe8d2e
--- /dev/null
+++ b/qpid/cpp/src/tests/DispatcherTest.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/sys/Poller.h"
+#include "qpid/sys/Dispatcher.h"
+#include "qpid/sys/DispatchHandle.h"
+#include "qpid/sys/posix/PrivatePosix.h"
+#include "qpid/sys/Thread.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <signal.h>
+
+#include <iostream>
+#include <boost/bind.hpp>
+
+using namespace std;
+using namespace qpid::sys;
+
+namespace qpid {
+namespace tests {
+
+int writeALot(int fd, const string& s) {
+ int bytesWritten = 0;
+ do {
+ errno = 0;
+ int lastWrite = ::write(fd, s.c_str(), s.size());
+ if ( lastWrite >= 0) {
+ bytesWritten += lastWrite;
+ }
+ } while (errno != EAGAIN);
+ return bytesWritten;
+}
+
+int readALot(int fd) {
+ int bytesRead = 0;
+ char buf[10240];
+
+ do {
+ errno = 0;
+ int lastRead = ::read(fd, buf, sizeof(buf));
+ if ( lastRead >= 0) {
+ bytesRead += lastRead;
+ }
+ } while (errno != EAGAIN);
+ return bytesRead;
+}
+
+int64_t writtenBytes = 0;
+int64_t readBytes = 0;
+
+void writer(DispatchHandle& h, int fd, const string& s) {
+ writtenBytes += writeALot(fd, s);
+ h.rewatch();
+}
+
+void reader(DispatchHandle& h, int fd) {
+ readBytes += readALot(fd);
+ h.rewatch();
+}
+
+void rInterrupt(DispatchHandle&) {
+ cerr << "R";
+}
+
+void wInterrupt(DispatchHandle&) {
+ cerr << "W";
+}
+
+DispatchHandle::Callback rcb = rInterrupt;
+DispatchHandle::Callback wcb = wInterrupt;
+
+DispatchHandleRef *volatile rh = 0;
+DispatchHandleRef *volatile wh = 0;
+
+volatile bool stopWait = false;
+volatile bool phase1finished = false;
+
+timer_t timer;
+
+void stop_handler(int /*signo*/, siginfo_t* /*info*/, void* /*context*/) {
+ stopWait = true;
+}
+
+void timer_handler(int /*signo*/, siginfo_t* /*info*/, void* /*context*/) {
+ static int count = 0;
+ if (count++ < 10) {
+ rh->call(rcb);
+ wh->call(wcb);
+ } else {
+ phase1finished = true;
+ assert(::timer_delete(timer) == 0);
+ }
+}
+
+}} // namespace qpid::tests
+
+using namespace qpid::tests;
+
+int main(int /*argc*/, char** /*argv*/)
+{
+ // Create poller
+ Poller::shared_ptr poller(new Poller);
+
+ // Create dispatcher thread
+ Thread dt(*poller);
+ Thread dt1(*poller);
+ Thread dt2(*poller);
+ Thread dt3(*poller);
+
+ // Setup sender and receiver
+ int sv[2];
+ int rc = ::socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
+ assert(rc >= 0);
+
+ // Set non-blocking
+ rc = ::fcntl(sv[0], F_SETFL, O_NONBLOCK);
+ assert(rc >= 0);
+
+ rc = ::fcntl(sv[1], F_SETFL, O_NONBLOCK);
+ assert(rc >= 0);
+
+ // Make up a large string
+ string testString = "This is only a test ... 1,2,3,4,5,6,7,8,9,10;";
+ for (int i = 0; i < 8; i++)
+ testString += testString;
+
+ IOHandle f0(sv[0]);
+ IOHandle f1(sv[1]);
+
+ rh = new DispatchHandleRef(f0, boost::bind(reader, _1, sv[0]), 0, 0);
+ wh = new DispatchHandleRef(f1, 0, boost::bind(writer, _1, sv[1], testString), 0);
+
+ rh->startWatch(poller);
+ wh->startWatch(poller);
+
+ // Set up a regular itimer interupt
+ // We assume that this thread will handle the signals whilst sleeping
+ // as the Poller threads have signal handling blocked
+
+ // Signal handling
+ struct ::sigaction sa;
+ sa.sa_sigaction = timer_handler;
+ sa.sa_flags = SA_RESTART | SA_SIGINFO;
+ ::sigemptyset(&sa.sa_mask);
+ rc = ::sigaction(SIGRTMIN, &sa,0);
+ assert(rc == 0);
+
+ ::sigevent se;
+ ::memset(&se, 0, sizeof(se)); // Clear to make valgrind happy (this *is* the neatest way to do this portably - sigh)
+ se.sigev_notify = SIGEV_SIGNAL;
+ se.sigev_signo = SIGRTMIN;
+ rc = ::timer_create(CLOCK_REALTIME, &se, &timer);
+ assert(rc == 0);
+
+ itimerspec ts = {
+ /*.it_value = */ {2, 0}, // s, ns
+ /*.it_interval = */ {2, 0}}; // s, ns
+
+ rc = ::timer_settime(timer, 0, &ts, 0);
+ assert(rc == 0);
+
+ // wait
+ while (!phase1finished) {
+ ::sleep(1);
+ }
+
+ // Now test deleting/creating DispatchHandles in tight loop, so that we are likely to still be using the
+ // attached PollerHandles after deleting the DispatchHandle
+ DispatchHandleRef* t = wh;
+ wh = 0;
+ delete t;
+ t = rh;
+ rh = 0;
+ delete t;
+
+ sa.sa_sigaction = stop_handler;
+ rc = ::sigaction(SIGRTMIN, &sa,0);
+ assert(rc == 0);
+
+ itimerspec nts = {
+ /*.it_value = */ {30, 0}, // s, ns
+ /*.it_interval = */ {30, 0}}; // s, ns
+
+ rc = ::timer_create(CLOCK_REALTIME, &se, &timer);
+ assert(rc == 0);
+ rc = ::timer_settime(timer, 0, &nts, 0);
+ assert(rc == 0);
+
+ DispatchHandleRef* rh1;
+ DispatchHandleRef* wh1;
+
+ struct timespec w = {0, 1000000};
+ while (!stopWait) {
+ rh1 = new DispatchHandleRef(f0, boost::bind(reader, _1, sv[0]), 0, 0);
+ wh1 = new DispatchHandleRef(f1, 0, boost::bind(writer, _1, sv[1], testString), 0);
+ rh1->startWatch(poller);
+ wh1->startWatch(poller);
+
+ ::nanosleep(&w, 0);
+
+ delete wh1;
+ delete rh1;
+ }
+
+ rc = ::timer_delete(timer);
+ assert(rc == 0);
+
+ poller->shutdown();
+ dt.join();
+ dt1.join();
+ dt2.join();
+ dt3.join();
+
+ cout << "\nWrote: " << writtenBytes << "\n";
+ cout << "Read: " << readBytes << "\n";
+
+ return 0;
+}
diff --git a/qpid/cpp/src/tests/DtxWorkRecordTest.cpp b/qpid/cpp/src/tests/DtxWorkRecordTest.cpp
new file mode 100644
index 0000000000..bcb3fc14a1
--- /dev/null
+++ b/qpid/cpp/src/tests/DtxWorkRecordTest.cpp
@@ -0,0 +1,193 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/DtxWorkRecord.h"
+#include "unit_test.h"
+#include <iostream>
+#include <vector>
+#include "TxMocks.h"
+
+using namespace qpid::broker;
+using boost::static_pointer_cast;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(DtxWorkRecordTestSuite)
+
+QPID_AUTO_TEST_CASE(testOnePhaseCommit){
+ MockTransactionalStore store;
+ store.expectBegin().expectCommit();
+
+ MockTxOp::shared_ptr opA(new MockTxOp());
+ opA->expectPrepare().expectCommit();
+ MockTxOp::shared_ptr opB(new MockTxOp());
+ opB->expectPrepare().expectCommit();
+
+ boost::intrusive_ptr<DtxBuffer> bufferA(new DtxBuffer());
+ bufferA->enlist(static_pointer_cast<TxOp>(opA));
+ bufferA->markEnded();
+ boost::intrusive_ptr<DtxBuffer> bufferB(new DtxBuffer());
+ bufferB->enlist(static_pointer_cast<TxOp>(opB));
+ bufferB->markEnded();
+
+ DtxWorkRecord work("my-xid", &store);
+ work.add(bufferA);
+ work.add(bufferB);
+
+ work.commit(true);
+
+ store.check();
+ BOOST_CHECK(store.isCommitted());
+ opA->check();
+ opB->check();
+}
+
+QPID_AUTO_TEST_CASE(testFailOnOnePhaseCommit){
+ MockTransactionalStore store;
+ store.expectBegin().expectAbort();
+
+ MockTxOp::shared_ptr opA(new MockTxOp());
+ opA->expectPrepare().expectRollback();
+ MockTxOp::shared_ptr opB(new MockTxOp(true));
+ opB->expectPrepare().expectRollback();
+ MockTxOp::shared_ptr opC(new MockTxOp());
+ opC->expectRollback();
+
+ boost::intrusive_ptr<DtxBuffer> bufferA(new DtxBuffer());
+ bufferA->enlist(static_pointer_cast<TxOp>(opA));
+ bufferA->markEnded();
+ boost::intrusive_ptr<DtxBuffer> bufferB(new DtxBuffer());
+ bufferB->enlist(static_pointer_cast<TxOp>(opB));
+ bufferB->markEnded();
+ boost::intrusive_ptr<DtxBuffer> bufferC(new DtxBuffer());
+ bufferC->enlist(static_pointer_cast<TxOp>(opC));
+ bufferC->markEnded();
+
+ DtxWorkRecord work("my-xid", &store);
+ work.add(bufferA);
+ work.add(bufferB);
+ work.add(bufferC);
+
+ work.commit(true);
+
+ BOOST_CHECK(store.isAborted());
+ store.check();
+
+ opA->check();
+ opB->check();
+ opC->check();
+}
+
+QPID_AUTO_TEST_CASE(testTwoPhaseCommit){
+ MockTransactionalStore store;
+ store.expectBegin2PC().expectPrepare().expectCommit();
+
+ MockTxOp::shared_ptr opA(new MockTxOp());
+ opA->expectPrepare().expectCommit();
+ MockTxOp::shared_ptr opB(new MockTxOp());
+ opB->expectPrepare().expectCommit();
+
+ boost::intrusive_ptr<DtxBuffer> bufferA(new DtxBuffer());
+ bufferA->enlist(static_pointer_cast<TxOp>(opA));
+ bufferA->markEnded();
+ boost::intrusive_ptr<DtxBuffer> bufferB(new DtxBuffer());
+ bufferB->enlist(static_pointer_cast<TxOp>(opB));
+ bufferB->markEnded();
+
+ DtxWorkRecord work("my-xid", &store);
+ work.add(bufferA);
+ work.add(bufferB);
+
+ BOOST_CHECK(work.prepare());
+ BOOST_CHECK(store.isPrepared());
+ work.commit(false);
+ store.check();
+ BOOST_CHECK(store.isCommitted());
+ opA->check();
+ opB->check();
+}
+
+QPID_AUTO_TEST_CASE(testFailOnTwoPhaseCommit){
+ MockTransactionalStore store;
+ store.expectBegin2PC().expectAbort();
+
+ MockTxOp::shared_ptr opA(new MockTxOp());
+ opA->expectPrepare().expectRollback();
+ MockTxOp::shared_ptr opB(new MockTxOp(true));
+ opB->expectPrepare().expectRollback();
+ MockTxOp::shared_ptr opC(new MockTxOp());
+ opC->expectRollback();
+
+ boost::intrusive_ptr<DtxBuffer> bufferA(new DtxBuffer());
+ bufferA->enlist(static_pointer_cast<TxOp>(opA));
+ bufferA->markEnded();
+ boost::intrusive_ptr<DtxBuffer> bufferB(new DtxBuffer());
+ bufferB->enlist(static_pointer_cast<TxOp>(opB));
+ bufferB->markEnded();
+ boost::intrusive_ptr<DtxBuffer> bufferC(new DtxBuffer());
+ bufferC->enlist(static_pointer_cast<TxOp>(opC));
+ bufferC->markEnded();
+
+ DtxWorkRecord work("my-xid", &store);
+ work.add(bufferA);
+ work.add(bufferB);
+ work.add(bufferC);
+
+ BOOST_CHECK(!work.prepare());
+ BOOST_CHECK(store.isAborted());
+ store.check();
+ opA->check();
+ opB->check();
+ opC->check();
+}
+
+QPID_AUTO_TEST_CASE(testRollback){
+ MockTransactionalStore store;
+ store.expectBegin2PC().expectPrepare().expectAbort();
+
+ MockTxOp::shared_ptr opA(new MockTxOp());
+ opA->expectPrepare().expectRollback();
+ MockTxOp::shared_ptr opB(new MockTxOp());
+ opB->expectPrepare().expectRollback();
+
+ boost::intrusive_ptr<DtxBuffer> bufferA(new DtxBuffer());
+ bufferA->enlist(static_pointer_cast<TxOp>(opA));
+ bufferA->markEnded();
+ boost::intrusive_ptr<DtxBuffer> bufferB(new DtxBuffer());
+ bufferB->enlist(static_pointer_cast<TxOp>(opB));
+ bufferB->markEnded();
+
+ DtxWorkRecord work("my-xid", &store);
+ work.add(bufferA);
+ work.add(bufferB);
+
+ BOOST_CHECK(work.prepare());
+ BOOST_CHECK(store.isPrepared());
+ work.rollback();
+ store.check();
+ BOOST_CHECK(store.isAborted());
+ opA->check();
+ opB->check();
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/ExchangeTest.cpp b/qpid/cpp/src/tests/ExchangeTest.cpp
new file mode 100644
index 0000000000..df0684e832
--- /dev/null
+++ b/qpid/cpp/src/tests/ExchangeTest.cpp
@@ -0,0 +1,267 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/Exception.h"
+#include "qpid/broker/Exchange.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/DeliverableMessage.h"
+#include "qpid/broker/DirectExchange.h"
+#include "qpid/broker/ExchangeRegistry.h"
+#include "qpid/broker/FanOutExchange.h"
+#include "qpid/broker/HeadersExchange.h"
+#include "qpid/broker/TopicExchange.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "unit_test.h"
+#include <iostream>
+#include "MessageUtils.h"
+
+using std::string;
+
+using namespace qpid::broker;
+using namespace qpid::framing;
+using namespace qpid::sys;
+using namespace qpid;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(ExchangeTestSuite)
+
+QPID_AUTO_TEST_CASE(testMe)
+{
+ Queue::shared_ptr queue(new Queue("queue", true));
+ Queue::shared_ptr queue2(new Queue("queue2", true));
+
+ TopicExchange topic("topic");
+ topic.bind(queue, "abc", 0);
+ topic.bind(queue2, "abc", 0);
+
+ DirectExchange direct("direct");
+ direct.bind(queue, "abc", 0);
+ direct.bind(queue2, "abc", 0);
+
+ queue.reset();
+ queue2.reset();
+
+ DeliverableMessage msg(MessageUtils::createMessage("exchange", "abc"), 0);
+ topic.route(msg);
+ direct.route(msg);
+}
+
+QPID_AUTO_TEST_CASE(testIsBound)
+{
+ Queue::shared_ptr a(new Queue("a", true));
+ Queue::shared_ptr b(new Queue("b", true));
+ Queue::shared_ptr c(new Queue("c", true));
+ Queue::shared_ptr d(new Queue("d", true));
+
+ string k1("abc");
+ string k2("def");
+ string k3("xyz");
+
+ FanOutExchange fanout("fanout");
+ BOOST_CHECK(fanout.bind(a, "", 0));
+ BOOST_CHECK(fanout.bind(b, "", 0));
+ BOOST_CHECK(fanout.bind(c, "", 0));
+
+ BOOST_CHECK(fanout.isBound(a, 0, 0));
+ BOOST_CHECK(fanout.isBound(b, 0, 0));
+ BOOST_CHECK(fanout.isBound(c, 0, 0));
+ BOOST_CHECK(!fanout.isBound(d, 0, 0));
+
+ DirectExchange direct("direct");
+ BOOST_CHECK(direct.bind(a, k1, 0));
+ BOOST_CHECK(direct.bind(a, k3, 0));
+ BOOST_CHECK(direct.bind(b, k2, 0));
+ BOOST_CHECK(direct.bind(c, k1, 0));
+
+ BOOST_CHECK(direct.isBound(a, 0, 0));
+ BOOST_CHECK(direct.isBound(a, &k1, 0));
+ BOOST_CHECK(direct.isBound(a, &k3, 0));
+ BOOST_CHECK(!direct.isBound(a, &k2, 0));
+ BOOST_CHECK(direct.isBound(b, 0, 0));
+ BOOST_CHECK(direct.isBound(b, &k2, 0));
+ BOOST_CHECK(direct.isBound(c, &k1, 0));
+ BOOST_CHECK(!direct.isBound(d, 0, 0));
+ BOOST_CHECK(!direct.isBound(d, &k1, 0));
+ BOOST_CHECK(!direct.isBound(d, &k2, 0));
+ BOOST_CHECK(!direct.isBound(d, &k3, 0));
+
+ TopicExchange topic("topic");
+ BOOST_CHECK(topic.bind(a, k1, 0));
+ BOOST_CHECK(topic.bind(a, k3, 0));
+ BOOST_CHECK(topic.bind(b, k2, 0));
+ BOOST_CHECK(topic.bind(c, k1, 0));
+
+ BOOST_CHECK(topic.isBound(a, 0, 0));
+ BOOST_CHECK(topic.isBound(a, &k1, 0));
+ BOOST_CHECK(topic.isBound(a, &k3, 0));
+ BOOST_CHECK(!topic.isBound(a, &k2, 0));
+ BOOST_CHECK(topic.isBound(b, 0, 0));
+ BOOST_CHECK(topic.isBound(b, &k2, 0));
+ BOOST_CHECK(topic.isBound(c, &k1, 0));
+ BOOST_CHECK(!topic.isBound(d, 0, 0));
+ BOOST_CHECK(!topic.isBound(d, &k1, 0));
+ BOOST_CHECK(!topic.isBound(d, &k2, 0));
+ BOOST_CHECK(!topic.isBound(d, &k3, 0));
+
+ HeadersExchange headers("headers");
+ FieldTable args1;
+ args1.setString("x-match", "all");
+ args1.setString("a", "A");
+ args1.setInt("b", 1);
+ FieldTable args2;
+ args2.setString("x-match", "any");
+ args2.setString("a", "A");
+ args2.setInt("b", 1);
+ FieldTable args3;
+ args3.setString("x-match", "any");
+ args3.setString("c", "C");
+ args3.setInt("b", 6);
+
+ headers.bind(a, "", &args1);
+ headers.bind(a, "other", &args3);//need to use different binding key to correctly identify second binding
+ headers.bind(b, "", &args2);
+ headers.bind(c, "", &args1);
+
+ BOOST_CHECK(headers.isBound(a, 0, 0));
+ BOOST_CHECK(headers.isBound(a, 0, &args1));
+ BOOST_CHECK(headers.isBound(a, 0, &args3));
+ BOOST_CHECK(!headers.isBound(a, 0, &args2));
+ BOOST_CHECK(headers.isBound(b, 0, 0));
+ BOOST_CHECK(headers.isBound(b, 0, &args2));
+ BOOST_CHECK(headers.isBound(c, 0, &args1));
+ BOOST_CHECK(!headers.isBound(d, 0, 0));
+ BOOST_CHECK(!headers.isBound(d, 0, &args1));
+ BOOST_CHECK(!headers.isBound(d, 0, &args2));
+ BOOST_CHECK(!headers.isBound(d, 0, &args3));
+}
+
+QPID_AUTO_TEST_CASE(testDeleteGetAndRedeclare)
+{
+ ExchangeRegistry exchanges;
+ exchanges.declare("my-exchange", "direct", false, false, FieldTable());
+ exchanges.destroy("my-exchange");
+ try {
+ exchanges.get("my-exchange");
+ } catch (const NotFoundException&) {}
+ std::pair<Exchange::shared_ptr, bool> response = exchanges.declare("my-exchange", "direct", false, false, FieldTable());
+ BOOST_CHECK_EQUAL(string("direct"), response.first->getType());
+}
+
+QPID_AUTO_TEST_CASE(testSequenceOptions)
+{
+ FieldTable args;
+ args.setInt("qpid.msg_sequence",1);
+ char* buff = new char[10000];
+ framing::Buffer buffer(buff,10000);
+ {
+ DirectExchange direct("direct1", false, false, args);
+
+ DeliverableMessage msg1(MessageUtils::createMessage("e", "abc"), 0);
+ DeliverableMessage msg2(MessageUtils::createMessage("e", "abc"), 0);
+ DeliverableMessage msg3(MessageUtils::createMessage("e", "abc"), 0);
+
+ direct.route(msg1);
+ direct.route(msg2);
+ direct.route(msg3);
+
+ BOOST_CHECK_EQUAL(1, msg1.getMessage().getAnnotation("qpid.msg_sequence").asInt64());
+ BOOST_CHECK_EQUAL(2, msg2.getMessage().getAnnotation("qpid.msg_sequence").asInt64());
+ BOOST_CHECK_EQUAL(3, msg3.getMessage().getAnnotation("qpid.msg_sequence").asInt64());
+
+ FanOutExchange fanout("fanout1", false, false, args);
+ HeadersExchange header("headers1", false, false, args);
+ TopicExchange topic ("topic1", false, false, args);
+
+ // check other exchanges, that they preroute
+ DeliverableMessage msg4(MessageUtils::createMessage("e", "abc"), 0);
+ DeliverableMessage msg5(MessageUtils::createMessage("e", "abc"), 0);
+ DeliverableMessage msg6(MessageUtils::createMessage("e", "abc"), 0);
+
+ fanout.route(msg4);
+ BOOST_CHECK_EQUAL(1, msg4.getMessage().getAnnotation("qpid.msg_sequence").asInt64());
+
+ header.route(msg5);
+ BOOST_CHECK_EQUAL(1, msg5.getMessage().getAnnotation("qpid.msg_sequence").asInt64());
+
+ topic.route(msg6);
+ BOOST_CHECK_EQUAL(1, msg6.getMessage().getAnnotation("qpid.msg_sequence").asInt64());
+ direct.encode(buffer);
+ }
+ {
+
+ ExchangeRegistry exchanges;
+ buffer.reset();
+ DirectExchange::shared_ptr exch_dec = Exchange::decode(exchanges, buffer);
+
+ DeliverableMessage msg1(MessageUtils::createMessage("e", "abc"), 0);
+ exch_dec->route(msg1);
+
+ BOOST_CHECK_EQUAL(4, msg1.getMessage().getAnnotation("qpid.msg_sequence").asInt64());
+
+ }
+ delete [] buff;
+}
+
+QPID_AUTO_TEST_CASE(testIVEOption)
+{
+ FieldTable args;
+ args.setInt("qpid.ive",1);
+ DirectExchange direct("direct1", false, false, args);
+ FanOutExchange fanout("fanout1", false, false, args);
+ HeadersExchange header("headers1", false, false, args);
+ TopicExchange topic ("topic1", false, false, args);
+
+ qpid::types::Variant::Map properties;
+ properties["routing-key"] = "abc";
+ properties["a"] = "abc";
+ Message msg1 = MessageUtils::createMessage(properties, "my-message", "direct1");
+ DeliverableMessage dmsg1(msg1, 0);
+
+ FieldTable args2;
+ args2.setString("x-match", "any");
+ args2.setString("a", "abc");
+
+ direct.route(dmsg1);
+ fanout.route(dmsg1);
+ header.route(dmsg1);
+ topic.route(dmsg1);
+ Queue::shared_ptr queue(new Queue("queue", true));
+ Queue::shared_ptr queue1(new Queue("queue1", true));
+ Queue::shared_ptr queue2(new Queue("queue2", true));
+ Queue::shared_ptr queue3(new Queue("queue3", true));
+
+ BOOST_CHECK(direct.bind(queue, "abc", 0));
+ BOOST_CHECK(fanout.bind(queue1, "abc", 0));
+ BOOST_CHECK(header.bind(queue2, "", &args2));
+ BOOST_CHECK(topic.bind(queue3, "abc", 0));
+
+ BOOST_CHECK_EQUAL(1u,queue->getMessageCount());
+ BOOST_CHECK_EQUAL(1u,queue1->getMessageCount());
+ BOOST_CHECK_EQUAL(1u,queue2->getMessageCount());
+ BOOST_CHECK_EQUAL(1u,queue3->getMessageCount());
+
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/FieldTable.cpp b/qpid/cpp/src/tests/FieldTable.cpp
new file mode 100644
index 0000000000..c040f1d433
--- /dev/null
+++ b/qpid/cpp/src/tests/FieldTable.cpp
@@ -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.
+ *
+ */
+#include <iostream>
+#include <algorithm>
+#include "qpid/framing/Array.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/framing/List.h"
+
+#include "unit_test.h"
+
+using namespace qpid::framing;
+
+using std::string;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(FieldTableTestSuite)
+
+QPID_AUTO_TEST_CASE(testMe)
+{
+ FieldTable ft;
+ ft.setString("A", "BCDE");
+ BOOST_CHECK(string("BCDE") == ft.getAsString("A"));
+
+ char buff[100];
+ Buffer wbuffer(buff, 100);
+ wbuffer.put(ft);
+
+ Buffer rbuffer(buff, 100);
+ FieldTable ft2;
+ rbuffer.get(ft2);
+ BOOST_CHECK(string("BCDE") == ft2.getAsString("A"));
+
+}
+
+QPID_AUTO_TEST_CASE(testAssignment)
+{
+ FieldTable a;
+ FieldTable b;
+
+ a.setString("A", "BBBB");
+ a.setInt("B", 1234);
+ b = a;
+ a.setString("A", "CCCC");
+
+ BOOST_CHECK(string("CCCC") == a.getAsString("A"));
+ BOOST_CHECK(string("BBBB") == b.getAsString("A"));
+ BOOST_CHECK_EQUAL(1234, a.getAsInt("B"));
+ BOOST_CHECK_EQUAL(1234, b.getAsInt("B"));
+ BOOST_CHECK(IntegerValue(1234) == *a.get("B"));
+ BOOST_CHECK(IntegerValue(1234) == *b.get("B"));
+
+ FieldTable d;
+ {
+ FieldTable c;
+ c = a;
+
+ std::vector<char> buff(c.encodedSize());
+ Buffer wbuffer(&buff[0], c.encodedSize());
+ wbuffer.put(c);
+
+ Buffer rbuffer(&buff[0], c.encodedSize());
+ rbuffer.get(d);
+ BOOST_CHECK_EQUAL(c, d);
+ BOOST_CHECK(string("CCCC") == c.getAsString("A"));
+ BOOST_CHECK(IntegerValue(1234) == *c.get("B"));
+ }
+ BOOST_CHECK(string("CCCC") == d.getAsString("A"));
+ BOOST_CHECK(IntegerValue(1234) == *d.get("B"));
+}
+
+
+QPID_AUTO_TEST_CASE(testNestedValues)
+{
+ double d = 1.2345;
+ uint32_t u = 101;
+ char buff[1000];
+ {
+ FieldTable a;
+ FieldTable b;
+ std::vector<std::string> items;
+ items.push_back("one");
+ items.push_back("two");
+ Array c(items);
+ List list;
+ list.push_back(List::ValuePtr(new Str16Value("red")));
+ list.push_back(List::ValuePtr(new Unsigned32Value(u)));
+ list.push_back(List::ValuePtr(new Str8Value("yellow")));
+ list.push_back(List::ValuePtr(new DoubleValue(d)));
+
+ a.setString("id", "A");
+ b.setString("id", "B");
+ a.setTable("B", b);
+ a.setArray("C", c);
+ a.set("my-list", FieldTable::ValuePtr(new ListValue(list)));
+
+
+ Buffer wbuffer(buff, 100);
+ wbuffer.put(a);
+ }
+ {
+ Buffer rbuffer(buff, 100);
+ FieldTable a;
+ FieldTable b;
+ Array c;
+ rbuffer.get(a);
+ BOOST_CHECK(string("A") == a.getAsString("id"));
+ a.getTable("B", b);
+ BOOST_CHECK(string("B") == b.getAsString("id"));
+ a.getArray("C", c);
+ std::vector<std::string> items;
+ std::transform(c.begin(), c.end(), std::back_inserter(items), Array::get<std::string, Array::ValuePtr>);
+ BOOST_CHECK((uint) 2 == items.size());
+ BOOST_CHECK(string("one") == items[0]);
+ BOOST_CHECK(string("two") == items[1]);
+
+ List list;
+ BOOST_CHECK(a.get("my-list")->get<List>(list));
+ List::const_iterator i = list.begin();
+ BOOST_CHECK(i != list.end());
+ BOOST_CHECK_EQUAL(std::string("red"), (*i)->get<std::string>());
+
+ i++;
+ BOOST_CHECK(i != list.end());
+ BOOST_CHECK_EQUAL(u, (uint32_t) (*i)->get<int>());
+
+ i++;
+ BOOST_CHECK(i != list.end());
+ BOOST_CHECK_EQUAL(std::string("yellow"), (*i)->get<std::string>());
+
+ i++;
+ BOOST_CHECK(i != list.end());
+ BOOST_CHECK_EQUAL(d, (*i)->get<double>());
+
+ i++;
+ BOOST_CHECK(i == list.end());
+ }
+}
+
+QPID_AUTO_TEST_CASE(testFloatAndDouble)
+{
+ char buff[100];
+ float f = 5.672f;
+ double d = 56.720001;
+ {
+ FieldTable a;
+ a.setString("string", "abc");
+ a.setInt("int", 5672);
+ a.setFloat("float", f);
+ a.setDouble("double", d);
+
+ Buffer wbuffer(buff, 100);
+ wbuffer.put(a);
+ }
+ {
+ Buffer rbuffer(buff, 100);
+ FieldTable a;
+ rbuffer.get(a);
+ BOOST_CHECK_EQUAL(string("abc"), a.getAsString("string"));
+ BOOST_CHECK_EQUAL(5672, a.getAsInt("int"));
+ float f2;
+ BOOST_CHECK(!a.getFloat("string", f2));
+ BOOST_CHECK(!a.getFloat("int", f2));
+ BOOST_CHECK(a.getFloat("float", f2));
+ BOOST_CHECK_EQUAL(f2, f);
+
+ double d2;
+ BOOST_CHECK(!a.getDouble("string", d2));
+ BOOST_CHECK(!a.getDouble("int", d2));
+ BOOST_CHECK(a.getDouble("double", d2));
+ BOOST_CHECK_EQUAL(d2, d);
+ }
+}
+
+QPID_AUTO_TEST_CASE(test64GetAndSetConverts)
+{
+ FieldTable args;
+ args.setInt64("a",100);
+ args.setInt64("b",-(int64_t) ((int64_t) 1<<34));
+
+ args.setUInt64("c",1u);
+ args.setUInt64("d",(uint64_t) ((uint64_t) 1<<34));
+ BOOST_CHECK_EQUAL(1u, args.getAsUInt64("c"));
+ BOOST_CHECK_EQUAL(100u, args.getAsUInt64("a"));
+ BOOST_CHECK_EQUAL(1, args.getAsInt64("c"));
+ BOOST_CHECK_EQUAL(100, args.getAsInt64("a"));
+ BOOST_CHECK_EQUAL(-(int64_t) ((int64_t) 1<<34), args.getAsInt64("b"));
+ BOOST_CHECK_EQUAL((uint64_t) ((uint64_t) 1<<34), args.getAsUInt64("d"));
+ BOOST_CHECK_EQUAL((int64_t) ((int64_t) 1<<34), args.getAsInt64("d"));
+
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/FieldValue.cpp b/qpid/cpp/src/tests/FieldValue.cpp
new file mode 100644
index 0000000000..9cea48e3cf
--- /dev/null
+++ b/qpid/cpp/src/tests/FieldValue.cpp
@@ -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.
+ */
+#include "qpid/framing/FieldValue.h"
+
+#include "unit_test.h"
+#include <boost/test/floating_point_comparison.hpp>
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(FieldValueTestSuite)
+
+using namespace qpid::framing;
+
+Str16Value s("abc");
+IntegerValue i(42);
+FloatValue f((float)42.42);
+DoubleValue df(123.123);
+
+QPID_AUTO_TEST_CASE(testStr16ValueEquals)
+{
+
+ BOOST_CHECK(Str16Value("abc") == s);
+ BOOST_CHECK(Str16Value("foo") != s);
+ BOOST_CHECK(s != i);
+ BOOST_CHECK(s.convertsTo<std::string>() == true);
+ BOOST_CHECK(s.convertsTo<int>() == false);
+ BOOST_CHECK(s.get<std::string>() == "abc");
+ BOOST_CHECK_THROW(s.get<int>(), InvalidConversionException);
+
+}
+
+QPID_AUTO_TEST_CASE(testIntegerValueEquals)
+{
+ BOOST_CHECK(i.get<int>() == 42);
+ BOOST_CHECK(IntegerValue(42) == i);
+ BOOST_CHECK(IntegerValue(5) != i);
+ BOOST_CHECK(i != s);
+ BOOST_CHECK(i.convertsTo<std::string>() == false);
+ BOOST_CHECK(i.convertsTo<float>() == true);
+ BOOST_CHECK(i.convertsTo<int>() == true);
+ BOOST_CHECK_THROW(i.get<std::string>(), InvalidConversionException);
+ BOOST_CHECK_EQUAL(i.get<float>(), 42.0);
+}
+
+QPID_AUTO_TEST_CASE(testFloatValueEquals)
+{
+ BOOST_CHECK(f.convertsTo<float>() == true);
+ BOOST_CHECK(FloatValue((float)42.42) == f);
+ BOOST_CHECK_CLOSE(double(f.get<float>()), 42.42, 0.001);
+ // Check twice, regression test for QPID-6470 where the value was corrupted during get.
+ BOOST_CHECK(FloatValue((float)42.42) == f);
+ BOOST_CHECK_CLOSE(f.get<double>(), 42.42, 0.001);
+
+ // Float to double conversion
+ BOOST_CHECK(f.convertsTo<double>() == true);
+ BOOST_CHECK_CLOSE(f.get<double>(), 42.42, 0.001);
+
+ // Double value
+ BOOST_CHECK(f.convertsTo<float>() == true);
+ BOOST_CHECK(f.convertsTo<double>() == true);
+ BOOST_CHECK_CLOSE(double(df.get<float>()), 123.123, 0.001);
+ BOOST_CHECK_CLOSE(df.get<double>(), 123.123, 0.001);
+
+ // Invalid conversions should fail.
+ BOOST_CHECK(!f.convertsTo<std::string>());
+ BOOST_CHECK(!f.convertsTo<int>());
+ BOOST_CHECK_THROW(f.get<std::string>(), InvalidConversionException);
+ BOOST_CHECK_THROW(f.get<int>(), InvalidConversionException);
+
+ // getFloatingPointValue: check twice, regression test for QPID-6470
+ BOOST_CHECK_CLOSE((double(f.getFloatingPointValue<float,sizeof(float)>())), 42.42, 0.001);
+ BOOST_CHECK_CLOSE((double(f.getFloatingPointValue<float,sizeof(float)>())), 42.42, 0.001);
+ BOOST_CHECK_CLOSE((df.getFloatingPointValue<double,sizeof(double)>()), 123.123, 0.001);
+ // getFloatingPointValue should *not* convert float/double, require exact type.
+ BOOST_CHECK_THROW((f.getFloatingPointValue<double,sizeof(double)>()), InvalidConversionException);
+ BOOST_CHECK_THROW((double(df.getFloatingPointValue<float,sizeof(float)>())), InvalidConversionException);
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/Frame.cpp b/qpid/cpp/src/tests/Frame.cpp
new file mode 100644
index 0000000000..cfcfde04a7
--- /dev/null
+++ b/qpid/cpp/src/tests/Frame.cpp
@@ -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.
+ *
+ */
+#include "qpid/framing/Frame.h"
+
+#include <boost/lexical_cast.hpp>
+#include "unit_test.h"
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(FrameTestSuite)
+
+using namespace std;
+using namespace qpid::framing;
+
+QPID_AUTO_TEST_CASE(testContentBody) {
+ Frame f(42, AMQContentBody("foobar"));
+ AMQBody* body=f.getBody();
+ BOOST_CHECK(dynamic_cast<AMQContentBody*>(body));
+ Buffer b(f.encodedSize();
+ f.encode(b);
+ b.flip();
+ Frame g;
+ g.decode(b);
+ AMQContentBody* content=dynamic_cast<AMQContentBody*>(g.getBody());
+ BOOST_REQUIRE(content);
+ BOOST_CHECK_EQUAL(content->getData(), "foobar");
+}
+
+QPID_AUTO_TEST_CASE(testMethodBody) {
+ FieldTable args;
+ args.setString("foo", "bar");
+ Frame f(
+ 42, QueueDeclareBody(ProtocolVersion(), 1, "q", "altex",
+ true, false, true, false, true, args));
+ BOOST_CHECK_EQUAL(f.getChannel(), 42);
+ Buffer b(f.encodedSize();
+ f.encode(b);
+ b.flip();
+ Frame g;
+ g.decode(b);
+ BOOST_CHECK_EQUAL(f.getChannel(), g.getChannel());
+ QueueDeclareBody* declare=dynamic_cast<QueueDeclareBody*>(g.getBody());
+ BOOST_REQUIRE(declare);
+ BOOST_CHECK_EQUAL(declare->getAlternateExchange(), "altex");
+ BOOST_CHECK_EQUAL(lexical_cast<string>(*f.getBody()), lexical_cast<string>(*g.getBody()));
+}
+
+QPID_AUTO_TEST_CASE(testLoop) {
+ // Run in a loop so heap profiler can spot any allocations.
+ Buffer b(1024);
+ for (int i = 0; i < 100; ++i) {
+ Frame ctor(2, AccessRequestOkBody(ProtocolVersion(), 42));
+ Frame assign(3);
+ assign.body = AccessRequestOkBody(ProtocolVersion(), 42);
+ assign.encode(b);
+ b.flip();
+ Frame g;
+ g.decode(b);
+ BOOST_REQUIRE(dynamic_cast<AccessRequestOkBody*>(g.getBody())->getTicket() == 42);
+ }
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/FrameDecoder.cpp b/qpid/cpp/src/tests/FrameDecoder.cpp
new file mode 100644
index 0000000000..9eeff2a41e
--- /dev/null
+++ b/qpid/cpp/src/tests/FrameDecoder.cpp
@@ -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.
+ *
+ */
+
+#include "unit_test.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/FrameDecoder.h"
+#include "qpid/framing/AMQContentBody.h"
+#include "qpid/framing/Buffer.h"
+#include <string>
+
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(FrameDecoderTest)
+
+using namespace std;
+using namespace qpid::framing;
+
+
+string makeData(int size) {
+ string data;
+ data.resize(size);
+ for (int i =0; i < size; ++i)
+ data[i] = 'a' + (i%26);
+ return data;
+}
+string encodeFrame(string data) {
+ AMQFrame f((AMQContentBody(data)));
+ string encoded;
+ encoded.resize(f.encodedSize());
+ Buffer b(&encoded[0], encoded.size());
+ f.encode(b);
+ return encoded;
+}
+
+string getData(const AMQFrame& frame) {
+ const AMQContentBody* content = dynamic_cast<const AMQContentBody*>(frame.getBody());
+ BOOST_CHECK(content);
+ return content->getData();
+}
+
+QPID_AUTO_TEST_CASE(testByteFragments) {
+ string data = makeData(42);
+ string encoded = encodeFrame(data);
+ FrameDecoder decoder;
+ for (size_t i = 0; i < encoded.size()-1; ++i) {
+ Buffer buf(&encoded[i], 1);
+ BOOST_CHECK(!decoder.decode(buf));
+ }
+ Buffer buf(&encoded[encoded.size()-1], 1);
+ BOOST_CHECK(decoder.decode(buf));
+ BOOST_CHECK_EQUAL(data, getData(decoder.getFrame()));
+}
+
+
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/FramingTest.cpp b/qpid/cpp/src/tests/FramingTest.cpp
new file mode 100644
index 0000000000..2392b6fec4
--- /dev/null
+++ b/qpid/cpp/src/tests/FramingTest.cpp
@@ -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.
+ *
+ */
+#include "qpid/client/Connection.h"
+#include "qpid/client/Connector.h"
+#include "qpid/framing/AMQP_HighestVersion.h"
+#include "qpid/framing/ProtocolVersion.h"
+#include "qpid/framing/all_method_bodies.h"
+#include "qpid/framing/amqp_framing.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/framing/FieldValue.h"
+#include "unit_test.h"
+
+#include <boost/bind.hpp>
+#include <boost/lexical_cast.hpp>
+#include <iostream>
+
+#include <memory>
+#include <sstream>
+#include <typeinfo>
+
+using namespace qpid;
+using namespace qpid::framing;
+using namespace std;
+
+namespace qpid {
+namespace tests {
+
+template <class T>
+std::string tostring(const T& x)
+{
+ std::ostringstream out;
+ out << x;
+ return out.str();
+}
+
+QPID_AUTO_TEST_SUITE(FramingTestSuite)
+
+QPID_AUTO_TEST_CASE(testMessageTransferBody)
+{
+ char buffer[1024];
+ ProtocolVersion version(highestProtocolVersion);
+ Buffer wbuff(buffer, sizeof(buffer));
+ MessageTransferBody in(version, "my-exchange", 1, 1);
+ in.encode(wbuff);
+
+ Buffer rbuff(buffer, sizeof(buffer));
+ MessageTransferBody out(version);
+ out.decode(rbuff);
+ BOOST_CHECK_EQUAL(tostring(in), tostring(out));
+}
+
+QPID_AUTO_TEST_CASE(testConnectionSecureBody)
+{
+ char buffer[1024];
+ ProtocolVersion version(highestProtocolVersion);
+ Buffer wbuff(buffer, sizeof(buffer));
+ std::string s = "security credential";
+ ConnectionSecureBody in(version, s);
+ in.encode(wbuff);
+
+ Buffer rbuff(buffer, sizeof(buffer));
+ ConnectionSecureBody out(version);
+ out.decode(rbuff);
+ BOOST_CHECK_EQUAL(tostring(in), tostring(out));
+}
+
+QPID_AUTO_TEST_CASE(testConnectionRedirectBody)
+{
+ char buffer[1024];
+ ProtocolVersion version(highestProtocolVersion);
+ Buffer wbuff(buffer, sizeof(buffer));
+ std::string a = "hostA";
+ std::string b = "hostB";
+ Array hosts(0x95);
+ hosts.add(boost::shared_ptr<FieldValue>(new Str16Value(a)));
+ hosts.add(boost::shared_ptr<FieldValue>(new Str16Value(b)));
+
+ ConnectionRedirectBody in(version, a, hosts);
+ in.encode(wbuff);
+
+ Buffer rbuff(buffer, sizeof(buffer));
+ ConnectionRedirectBody out(version);
+ out.decode(rbuff);
+ BOOST_CHECK_EQUAL(tostring(in), tostring(out));
+}
+
+QPID_AUTO_TEST_CASE(testQueueDeclareBody)
+{
+ char buffer[1024];
+ ProtocolVersion version(highestProtocolVersion);
+ Buffer wbuff(buffer, sizeof(buffer));
+ QueueDeclareBody in(version, "name", "dlq", true, false, true, false, FieldTable());
+ in.encode(wbuff);
+
+ Buffer rbuff(buffer, sizeof(buffer));
+ QueueDeclareBody out(version);
+ out.decode(rbuff);
+ BOOST_CHECK_EQUAL(tostring(in), tostring(out));
+}
+
+QPID_AUTO_TEST_CASE(testConnectionRedirectBodyFrame)
+{
+ char buffer[1024];
+ ProtocolVersion version(highestProtocolVersion);
+ Buffer wbuff(buffer, sizeof(buffer));
+ std::string a = "hostA";
+ std::string b = "hostB";
+ Array hosts(0x95);
+ hosts.add(boost::shared_ptr<FieldValue>(new Str16Value(a)));
+ hosts.add(boost::shared_ptr<FieldValue>(new Str16Value(b)));
+
+ AMQFrame in((ConnectionRedirectBody(version, a, hosts)));
+ in.setChannel(999);
+ in.encode(wbuff);
+
+ Buffer rbuff(buffer, sizeof(buffer));
+ AMQFrame out;
+ out.decode(rbuff);
+ BOOST_CHECK_EQUAL(tostring(in), tostring(out));
+}
+
+QPID_AUTO_TEST_CASE(testMessageCancelBodyFrame)
+{
+ char buffer[1024];
+ ProtocolVersion version(highestProtocolVersion);
+ Buffer wbuff(buffer, sizeof(buffer));
+ AMQFrame in((MessageCancelBody(version, "tag")));
+ in.setChannel(999);
+ in.encode(wbuff);
+
+ Buffer rbuff(buffer, sizeof(buffer));
+ AMQFrame out;
+ out.decode(rbuff);
+ BOOST_CHECK_EQUAL(tostring(in), tostring(out));
+}
+
+QPID_AUTO_TEST_CASE(badStrings) {
+ char data[(65535 + 2) + (255 + 1)];
+ Buffer b(data, sizeof(data));
+ BOOST_CHECK_THROW(b.putShortString(std::string(256, 'X')),
+ Exception);
+ BOOST_CHECK_THROW(b.putMediumString(std::string(65536, 'X')),
+ Exception);
+ b.putShortString(std::string(255, 'X'));
+ b.putMediumString(std::string(65535, 'X'));
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/HeaderTest.cpp b/qpid/cpp/src/tests/HeaderTest.cpp
new file mode 100644
index 0000000000..4b16f3c793
--- /dev/null
+++ b/qpid/cpp/src/tests/HeaderTest.cpp
@@ -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.
+ *
+ */
+#include <iostream>
+#include "qpid/framing/amqp_framing.h"
+#include "qpid/framing/FieldValue.h"
+#include "unit_test.h"
+
+using namespace qpid::framing;
+using namespace std;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(HeaderTestSuite)
+
+QPID_AUTO_TEST_CASE(testGenericProperties)
+{
+ AMQHeaderBody body;
+ body.get<MessageProperties>(true)->getApplicationHeaders().setString(
+ "A", "BCDE");
+ char buff[100];
+ Buffer wbuffer(buff, 100);
+ body.encode(wbuffer);
+
+ Buffer rbuffer(buff, 100);
+ AMQHeaderBody body2;
+ body2.decode(rbuffer, body.encodedSize());
+ MessageProperties* props =
+ body2.get<MessageProperties>(true);
+ BOOST_CHECK_EQUAL(
+ string("BCDE"),
+ props->getApplicationHeaders().get("A")->get<string>());
+}
+
+QPID_AUTO_TEST_CASE(testMessageProperties)
+{
+ AMQFrame out((AMQHeaderBody()));
+ MessageProperties* props1 =
+ out.castBody<AMQHeaderBody>()->get<MessageProperties>(true);
+
+ props1->setContentLength(42);
+ props1->setMessageId(Uuid(true));
+ props1->setCorrelationId("correlationId");
+ props1->setReplyTo(ReplyTo("ex","key"));
+ props1->setContentType("contentType");
+ props1->setContentEncoding("contentEncoding");
+ props1->setUserId("userId");
+ props1->setAppId("appId");
+
+ char buff[10000];
+ Buffer wbuffer(buff, 10000);
+ out.encode(wbuffer);
+
+ Buffer rbuffer(buff, 10000);
+ AMQFrame in;
+ in.decode(rbuffer);
+ MessageProperties* props2 =
+ in.castBody<AMQHeaderBody>()->get<MessageProperties>(true);
+
+ BOOST_CHECK_EQUAL(props1->getContentLength(), props2->getContentLength());
+ BOOST_CHECK_EQUAL(props1->getMessageId(), props2->getMessageId());
+ BOOST_CHECK_EQUAL(props1->getCorrelationId(), props2->getCorrelationId());
+ BOOST_CHECK_EQUAL(props1->getContentType(), props2->getContentType());
+ BOOST_CHECK_EQUAL(props1->getContentEncoding(), props2->getContentEncoding());
+ BOOST_CHECK_EQUAL(props1->getUserId(), props2->getUserId());
+ BOOST_CHECK_EQUAL(props1->getAppId(), props2->getAppId());
+
+}
+
+QPID_AUTO_TEST_CASE(testDeliveryProperies)
+{
+ AMQFrame out((AMQHeaderBody()));
+ DeliveryProperties* props1 =
+ out.castBody<AMQHeaderBody>()->get<DeliveryProperties>(true);
+
+ props1->setDiscardUnroutable(true);
+ props1->setExchange("foo");
+
+ char buff[10000];
+ Buffer wbuffer(buff, 10000);
+ out.encode(wbuffer);
+
+ Buffer rbuffer(buff, 10000);
+ AMQFrame in;
+ in.decode(rbuffer);
+ DeliveryProperties* props2 =
+ in.castBody<AMQHeaderBody>()->get<DeliveryProperties>(true);
+
+ BOOST_CHECK(props2->getDiscardUnroutable());
+ BOOST_CHECK_EQUAL(string("foo"), props2->getExchange());
+ BOOST_CHECK(!props2->hasTimestamp());
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/HeadersExchangeTest.cpp b/qpid/cpp/src/tests/HeadersExchangeTest.cpp
new file mode 100644
index 0000000000..3e68b84bc3
--- /dev/null
+++ b/qpid/cpp/src/tests/HeadersExchangeTest.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/Exception.h"
+#include "qpid/broker/HeadersExchange.h"
+#include "qpid/broker/Message.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/FieldValue.h"
+#include "MessageUtils.h"
+#include "unit_test.h"
+
+using namespace qpid::broker;
+using namespace qpid::framing;
+using namespace qpid::types;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(HeadersExchangeTestSuite)
+
+QPID_AUTO_TEST_CASE(testMatchAll)
+{
+ FieldTable b;
+ b.setString("x-match", "all");
+ b.setString("foo", "FOO");
+ b.setInt("n", 42);
+
+ Variant::Map m;
+ const int32_t int_n(42);
+ m["foo"] = "FOO";
+ m["n"] = int_n;
+ BOOST_CHECK(HeadersExchange::match(b, MessageUtils::createMessage(m, "", "", true)));
+
+ // Ignore extras.
+ m["extra"] = "x";
+ BOOST_CHECK(HeadersExchange::match(b, MessageUtils::createMessage(m, "", "", true)));
+
+ // Fail mismatch, wrong value.
+ m["foo"] = "NotFoo";
+ BOOST_CHECK(!HeadersExchange::match(b, MessageUtils::createMessage(m, "", "", true)));
+
+ // Fail mismatch, missing value
+ Variant::Map n;
+ n["n"] = int_n;
+ n["extra"] = "x";
+ BOOST_CHECK(!HeadersExchange::match(b, MessageUtils::createMessage(n, "", "", true)));
+}
+
+QPID_AUTO_TEST_CASE(testMatchAny)
+{
+ FieldTable b;
+ b.setString("x-match", "any");
+ b.setString("foo", "FOO");
+ b.setInt("n", 42);
+
+ Variant::Map n;
+ Variant::Map m;
+ m["foo"] = "FOO";
+ BOOST_CHECK(!HeadersExchange::match(b, MessageUtils::createMessage(n, "", "", true)));
+ BOOST_CHECK(HeadersExchange::match(b, MessageUtils::createMessage(m, "", "", true)));
+ const int32_t int_n(42);
+ m["n"] = int_n;
+ BOOST_CHECK(HeadersExchange::match(b, MessageUtils::createMessage(m, "", "", true)));
+}
+
+QPID_AUTO_TEST_CASE(testMatchEmptyValue)
+{
+ FieldTable b;
+ b.setString("x-match", "all");
+ b.set("foo", FieldTable::ValuePtr());
+ b.set("n", FieldTable::ValuePtr());
+ Variant::Map m;
+ BOOST_CHECK(!HeadersExchange::match(b, MessageUtils::createMessage(m, "", "", true)));
+}
+
+QPID_AUTO_TEST_CASE(testMatchEmptyArgs)
+{
+ FieldTable b;
+ Variant::Map m;
+ m["foo"] = "FOO";
+ Message msg = MessageUtils::createMessage(m, "", "", true);
+
+ b.setString("x-match", "all");
+ BOOST_CHECK(HeadersExchange::match(b, msg));
+ b.setString("x-match", "any");
+ BOOST_CHECK(!HeadersExchange::match(b, msg));
+}
+
+
+QPID_AUTO_TEST_CASE(testMatchNoXMatch)
+{
+ FieldTable b;
+ b.setString("foo", "FOO");
+ Variant::Map m;
+ m["foo"] = "FOO";
+ BOOST_CHECK(!HeadersExchange::match(b, MessageUtils::createMessage(m, "", "", true)));
+}
+
+QPID_AUTO_TEST_CASE(testBindNoXMatch)
+{
+ HeadersExchange exchange("test");
+ Queue::shared_ptr queue;
+ std::string key;
+ FieldTable args;
+ try {
+ //just checking this doesn't cause assertion etc
+ exchange.bind(queue, key, &args);
+ } catch(qpid::Exception&) {
+ //expected
+ }
+}
+
+
+QPID_AUTO_TEST_CASE(testMatchSizedIntUint)
+{
+ typedef std::list<Variant::Map> vml;
+
+ const int8_t i8(1);
+ const int16_t i16(1);
+ const int32_t i32(1);
+ const int64_t i64(1);
+ const uint8_t u8(1);
+ const uint16_t u16(1);
+ const uint32_t u32(1);
+ const uint64_t u64(1);
+
+ Variant::Map mi8, mi16, mi32, mi64;
+ Variant::Map mu8, mu16, mu32, mu64;
+
+ mi8["bk"] = i8;
+ mi16["bk"] = i16;
+ mi32["bk"] = i32;
+ mi64["bk"] = i64;
+ mu8["bk"] = u8;
+ mu16["bk"] = u16;
+ mu32["bk"] = u32;
+ mu64["bk"] = u64;
+
+ vml mMap;
+ mMap.push_back(mi8);
+ mMap.push_back(mi16);
+ mMap.push_back(mi32);
+ mMap.push_back(mi64);
+ mMap.push_back(mu8);
+ mMap.push_back(mu16);
+ mMap.push_back(mu32);
+ mMap.push_back(mu64);
+
+ for (vml::iterator bVal=mMap.begin(); bVal!=mMap.end(); ++bVal) {
+ FieldTable b;
+ qpid::amqp_0_10::translate(*bVal, b);
+ b.setString("x-match", "all");
+ for (vml::iterator mVal=mMap.begin(); mVal!=mMap.end(); ++mVal) {
+ BOOST_CHECK(HeadersExchange::match(b, MessageUtils::createMessage(*mVal, "", "", true)));
+ }
+ }
+}
+
+// TODO: Headers exchange match on single
+
+QPID_AUTO_TEST_CASE(testMatchFloatDouble)
+{
+ const double iFloat(1.0);
+ Variant::Map m;
+ m["bk"] = iFloat;
+
+ FieldTable b;
+ qpid::amqp_0_10::translate(m, b);
+ b.setString("x-match", "all");
+ BOOST_CHECK(HeadersExchange::match(b, MessageUtils::createMessage(m, "", "", true)));
+}
+
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/InlineAllocator.cpp b/qpid/cpp/src/tests/InlineAllocator.cpp
new file mode 100644
index 0000000000..a4c4d64cea
--- /dev/null
+++ b/qpid/cpp/src/tests/InlineAllocator.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/InlineAllocator.h"
+#include "unit_test.h"
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(InlineAllocatorTestSuite)
+
+using namespace qpid;
+using namespace std;
+
+QPID_AUTO_TEST_CASE(testAllocate) {
+ InlineAllocator<std::allocator<char>, 2> alloc;
+
+ char* p = alloc.allocate(1);
+ BOOST_CHECK(p == (char*)&alloc);
+ alloc.deallocate(p,1);
+
+ p = alloc.allocate(2);
+ BOOST_CHECK(p == (char*)&alloc);
+ alloc.deallocate(p,2);
+
+ p = alloc.allocate(3);
+ BOOST_CHECK(p != (char*)&alloc);
+ alloc.deallocate(p,3);
+}
+
+QPID_AUTO_TEST_CASE(testAllocateFull) {
+ InlineAllocator<std::allocator<char>, 1> alloc;
+
+ char* p = alloc.allocate(1);
+ BOOST_CHECK(p == (char*)&alloc);
+
+ char* q = alloc.allocate(1);
+ BOOST_CHECK(q != (char*)&alloc);
+
+ alloc.deallocate(p,1);
+ p = alloc.allocate(1);
+ BOOST_CHECK(p == (char*)&alloc);
+
+ alloc.deallocate(p,1);
+ alloc.deallocate(q,1);
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/InlineVector.cpp b/qpid/cpp/src/tests/InlineVector.cpp
new file mode 100644
index 0000000000..ba5165886d
--- /dev/null
+++ b/qpid/cpp/src/tests/InlineVector.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/InlineVector.h"
+#include "unit_test.h"
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(InlineVectorTestSuite)
+
+using namespace qpid;
+using namespace std;
+
+typedef InlineVector<int, 3> Vec;
+
+bool isInline(const Vec& v) {
+ // If nothing, give it the benefit of the doubt;
+ // can't take address of nothing.
+ if (v.size() <= 0)
+ return true;
+ return (const char*)&v <= (const char*)(&v[0]) &&
+ (const char*)(&v[0]) < (const char*)&v+sizeof(v);
+}
+
+QPID_AUTO_TEST_CASE(testCtor) {
+ {
+ Vec v;
+ BOOST_CHECK(isInline(v));
+ BOOST_CHECK(v.empty());
+ }
+ {
+ Vec v(3, 42);
+ BOOST_CHECK(isInline(v));
+ BOOST_CHECK_EQUAL(3u, v.size());
+ BOOST_CHECK_EQUAL(v[0], 42);
+ BOOST_CHECK_EQUAL(v[2], 42);
+
+ Vec u(v);
+ BOOST_CHECK(isInline(u));
+ BOOST_CHECK_EQUAL(3u, u.size());
+ BOOST_CHECK_EQUAL(u[0], 42);
+ BOOST_CHECK_EQUAL(u[2], 42);
+ }
+
+ {
+ Vec v(4, 42);
+
+ BOOST_CHECK_EQUAL(v.size(), 4u);
+ BOOST_CHECK(!isInline(v));
+ Vec u(v);
+ BOOST_CHECK_EQUAL(u.size(), 4u);
+ BOOST_CHECK(!isInline(u));
+ }
+}
+
+QPID_AUTO_TEST_CASE(testInsert) {
+ {
+ Vec v;
+ v.push_back(1);
+ BOOST_CHECK_EQUAL(v.size(), 1u);
+ BOOST_CHECK_EQUAL(v.back(), 1);
+ BOOST_CHECK(isInline(v));
+
+ v.insert(v.begin(), 2);
+ BOOST_CHECK_EQUAL(v.size(), 2u);
+ BOOST_CHECK_EQUAL(v.back(), 1);
+ BOOST_CHECK(isInline(v));
+
+ v.push_back(3);
+ BOOST_CHECK(isInline(v));
+
+ v.push_back(4);
+
+ BOOST_CHECK(!isInline(v));
+ }
+ {
+ Vec v(3,42);
+ v.insert(v.begin(), 9);
+ BOOST_CHECK_EQUAL(v.size(), 4u);
+ BOOST_CHECK(!isInline(v));
+ }
+ {
+ Vec v(3,42);
+ v.insert(v.begin()+1, 9);
+ BOOST_CHECK(!isInline(v));
+ BOOST_CHECK_EQUAL(v.size(), 4u);
+ }
+}
+
+QPID_AUTO_TEST_CASE(testAssign) {
+ Vec v(3,42);
+ Vec u;
+ u = v;
+ BOOST_CHECK(isInline(u));
+ u.push_back(4);
+ BOOST_CHECK(!isInline(u));
+ v = u;
+ BOOST_CHECK(!isInline(v));
+}
+
+QPID_AUTO_TEST_CASE(testResize) {
+ Vec v;
+ v.resize(5);
+ BOOST_CHECK(!isInline(v));
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/ManagementTest.cpp b/qpid/cpp/src/tests/ManagementTest.cpp
new file mode 100644
index 0000000000..98ef591fae
--- /dev/null
+++ b/qpid/cpp/src/tests/ManagementTest.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/management/ManagementObject.h"
+#include "qpid/framing/Buffer.h"
+#include "unit_test.h"
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(ManagementTestSuite)
+
+using namespace qpid::framing;
+using namespace qpid::management;
+
+QPID_AUTO_TEST_CASE(testObjectIdSerializeStream) {
+ std::string text("0-10-4-2500-80000000000()");
+ std::stringstream input(text);
+
+ ObjectId oid(input);
+
+ std::stringstream output;
+ output << oid;
+
+ BOOST_CHECK_EQUAL(text, output.str());
+}
+
+QPID_AUTO_TEST_CASE(testObjectIdSerializeString) {
+ std::string text("0-10-4-2500-80000000000()");
+
+ ObjectId oid(text);
+
+ std::stringstream output;
+ output << oid;
+
+ BOOST_CHECK_EQUAL(text, output.str());
+}
+
+QPID_AUTO_TEST_CASE(testObjectIdEncode) {
+ qpid::types::Variant::Map oidMap;
+
+ ObjectId oid(1, 2, 3, 9999);
+ oid.setV2Key("testkey");
+ oid.setAgentName("myAgent");
+
+ std::stringstream out1;
+ out1 << oid;
+
+ BOOST_CHECK_EQUAL(out1.str(), "1-2-3-myAgent-9999(testkey)");
+}
+
+QPID_AUTO_TEST_CASE(testObjectIdAttach) {
+ AgentAttachment agent;
+ ObjectId oid(&agent, 10, 20);
+ oid.setV2Key("GabbaGabbaHey");
+ oid.setAgentName("MrSmith");
+
+ std::stringstream out1;
+ out1 << oid;
+
+ BOOST_CHECK_EQUAL(out1.str(), "10-20-0-MrSmith-0(GabbaGabbaHey)");
+
+ agent.setBanks(30, 40);
+ std::stringstream out2;
+ out2 << oid;
+
+ BOOST_CHECK_EQUAL(out2.str(), "10-20-30-MrSmith-0(GabbaGabbaHey)");
+}
+
+QPID_AUTO_TEST_CASE(testObjectIdCreate) {
+ ObjectId oid("some-agent-name", "an-object-name");
+
+ BOOST_CHECK_EQUAL(oid.getAgentName(), "some-agent-name");
+ BOOST_CHECK_EQUAL(oid.getV2Key(), "an-object-name");
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/MessageReplayTracker.cpp b/qpid/cpp/src/tests/MessageReplayTracker.cpp
new file mode 100644
index 0000000000..c0778247f0
--- /dev/null
+++ b/qpid/cpp/src/tests/MessageReplayTracker.cpp
@@ -0,0 +1,104 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "unit_test.h"
+#include "BrokerFixture.h"
+#include "qpid/client/MessageReplayTracker.h"
+#include "qpid/sys/Time.h"
+
+#include <boost/format.hpp>
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(MessageReplayTrackerTests)
+
+using namespace qpid::client;
+using namespace qpid::sys;
+using std::string;
+
+class ReplayBufferChecker
+{
+ public:
+
+ ReplayBufferChecker(uint from, uint to) : end(to), i(from) {}
+
+ void operator()(const Message& m)
+ {
+ if (i > end) BOOST_FAIL("Extra message found: " + m.getData());
+ BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i++)).str(), m.getData());
+ }
+ private:
+ const uint end;
+ uint i;
+
+};
+
+QPID_AUTO_TEST_CASE(testReplay)
+{
+ SessionFixture fix;
+ fix.session.queueDeclare(arg::queue="my-queue", arg::exclusive=true, arg::autoDelete=true);
+
+ MessageReplayTracker tracker(10);
+ tracker.init(fix.session);
+ for (uint i = 0; i < 5; i++) {
+ Message message((boost::format("Message_%1%") % (i+1)).str(), "my-queue");
+ tracker.send(message);
+ }
+ ReplayBufferChecker checker(1, 10);
+ tracker.foreach(checker);
+
+ tracker.replay(fix.session);
+ for (uint j = 0; j < 2; j++) {//each message should have been sent twice
+ for (uint i = 0; i < 5; i++) {
+ Message m;
+ BOOST_CHECK(fix.subs.get(m, "my-queue", TIME_SEC));
+ BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i+1)).str(), m.getData());
+ }
+ }
+ Message m;
+ BOOST_CHECK(!fix.subs.get(m, "my-queue"));
+}
+
+QPID_AUTO_TEST_CASE(testCheckCompletion)
+{
+ SessionFixture fix;
+ fix.session.queueDeclare(arg::queue="my-queue", arg::exclusive=true, arg::autoDelete=true);
+
+ MessageReplayTracker tracker(10);
+ tracker.init(fix.session);
+ for (uint i = 0; i < 5; i++) {
+ Message message((boost::format("Message_%1%") % (i+1)).str(), "my-queue");
+ tracker.send(message);
+ }
+ fix.session.sync();//ensures all messages are complete
+ tracker.checkCompletion();
+ tracker.replay(fix.session);
+ Message received;
+ for (uint i = 0; i < 5; i++) {
+ BOOST_CHECK(fix.subs.get(received, "my-queue"));
+ BOOST_CHECK_EQUAL((boost::format("Message_%1%") % (i+1)).str(), received.getData());
+ }
+ BOOST_CHECK(!fix.subs.get(received, "my-queue"));
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/MessageTest.cpp b/qpid/cpp/src/tests/MessageTest.cpp
new file mode 100644
index 0000000000..a6c5157b47
--- /dev/null
+++ b/qpid/cpp/src/tests/MessageTest.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/Message.h"
+#include "qpid/broker/Protocol.h"
+#include "qpid/framing/AMQP_HighestVersion.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/framing/Uuid.h"
+#include "MessageUtils.h"
+
+#include "unit_test.h"
+
+#include <iostream>
+
+using namespace qpid::broker;
+using namespace qpid::framing;
+
+using std::string;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(MessageTestSuite)
+
+QPID_AUTO_TEST_CASE(testEncodeDecode)
+{
+ string exchange = "MyExchange";
+ string routingKey = "MyRoutingKey";
+ uint64_t ttl(60);
+ Uuid messageId(true);
+ string data("abcdefghijklmn");
+
+ qpid::types::Variant::Map properties;
+ properties["routing-key"] = routingKey;
+ properties["ttl"] = ttl;
+ properties["durable"] = true;
+ properties["message-id"] = qpid::types::Uuid(messageId.data());
+ properties["abc"] = "xyz";
+ Message msg = MessageUtils::createMessage(properties, data);
+
+ std::vector<char> bytes(msg.getPersistentContext()->encodedSize());
+ qpid::framing::Buffer buffer(&bytes[0], bytes.size());
+ msg.getPersistentContext()->encode(buffer);
+ buffer.reset();
+ ProtocolRegistry registry(std::set<std::string>(), 0);
+ msg = registry.decode(buffer);
+
+ BOOST_CHECK_EQUAL(routingKey, msg.getRoutingKey());
+ BOOST_CHECK_EQUAL((uint64_t) data.size(), msg.getContent().size());
+ BOOST_CHECK_EQUAL(data, msg.getContent());
+ //BOOST_CHECK_EQUAL(messageId, msg->getProperties<MessageProperties>()->getMessageId());
+ BOOST_CHECK_EQUAL(string("xyz"), msg.getPropertyAsString("abc"));
+ BOOST_CHECK(msg.isPersistent());
+}
+
+QPID_AUTO_TEST_CASE(testMessageProperties)
+{
+ string data("abcdefghijklmn");
+
+ qpid::types::Variant::Map properties;
+ properties["abc"] = "xyz";
+ Message msg = MessageUtils::createMessage(properties, data);
+
+ // Regression test that looking up a property doesn't return a prefix
+ BOOST_CHECK_EQUAL(msg.getProperty("abcdef").getType(), qpid::types::VAR_VOID);
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/MessageUtils.h b/qpid/cpp/src/tests/MessageUtils.h
new file mode 100644
index 0000000000..f05b0d8b20
--- /dev/null
+++ b/qpid/cpp/src/tests/MessageUtils.h
@@ -0,0 +1,117 @@
+#ifndef TESTS_MESSAGEUTILS_H
+#define TESTS_MESSAGEUTILS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/broker/Message.h"
+#include "qpid/broker/amqp_0_10/MessageTransfer.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qpid/framing/Uuid.h"
+#include "qpid/types/Variant.h"
+#include "qpid/amqp_0_10/Codecs.h"
+
+using namespace qpid;
+using namespace broker;
+using namespace framing;
+
+namespace qpid {
+namespace tests {
+
+struct MessageUtils
+{
+ static Message createMessage(const qpid::types::Variant::Map& properties,
+ const std::string& content="",
+ const std::string& destination = "",
+ bool replaceHeaders = false
+ )
+ {
+ boost::intrusive_ptr<broker::amqp_0_10::MessageTransfer> msg(new broker::amqp_0_10::MessageTransfer());
+
+ AMQFrame method(( MessageTransferBody(ProtocolVersion(), destination, 0, 0)));
+ AMQFrame header((AMQHeaderBody()));
+
+ msg->getFrames().append(method);
+ msg->getFrames().append(header);
+ if (content.size()) {
+ msg->getFrames().getHeaders()->get<MessageProperties>(true)->setContentLength(content.size());
+ AMQFrame data((AMQContentBody(content)));
+ msg->getFrames().append(data);
+ }
+ if (!replaceHeaders) {
+ for (qpid::types::Variant::Map::const_iterator i = properties.begin(); i != properties.end(); ++i) {
+ if (i->first == "routing-key" && !i->second.isVoid()) {
+ msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setRoutingKey(i->second);
+ } else if (i->first == "message-id" && !i->second.isVoid()) {
+ qpid::types::Uuid id = i->second;
+ qpid::framing::Uuid id2(id.data());
+ msg->getFrames().getHeaders()->get<MessageProperties>(true)->setMessageId(id2);
+ } else if (i->first == "ttl" && !i->second.isVoid()) {
+ msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setTtl(i->second);
+ } else if (i->first == "priority" && !i->second.isVoid()) {
+ msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setPriority(i->second);
+ } else if (i->first == "durable" && !i->second.isVoid()) {
+ msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setDeliveryMode(i->second.asBool() ? 2 : 1);
+ } else {
+ msg->getFrames().getHeaders()->get<MessageProperties>(true)->getApplicationHeaders().setString(i->first, i->second);
+ }
+ }
+ } else {
+ framing::FieldTable newHeaders;
+ qpid::amqp_0_10::translate(properties, newHeaders);
+ msg->getFrames().getHeaders()->get<MessageProperties>(true)->getApplicationHeaders() = newHeaders;
+ }
+ return Message(msg, msg);
+ }
+
+
+ static Message createMessage(const std::string& exchange="", const std::string& routingKey="",
+ uint64_t ttl = 0, bool durable = false, const Uuid& messageId=Uuid(true),
+ const std::string& content="")
+ {
+ boost::intrusive_ptr<broker::amqp_0_10::MessageTransfer> msg(new broker::amqp_0_10::MessageTransfer());
+
+ AMQFrame method(( MessageTransferBody(ProtocolVersion(), exchange, 0, 0)));
+ AMQFrame header((AMQHeaderBody()));
+
+ msg->getFrames().append(method);
+ msg->getFrames().append(header);
+ MessageProperties* props = msg->getFrames().getHeaders()->get<MessageProperties>(true);
+ props->setContentLength(content.size());
+ props->setMessageId(messageId);
+ msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setRoutingKey(routingKey);
+ if (durable)
+ msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setDeliveryMode(2);
+ if (ttl)
+ msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setTtl(ttl);
+ if (content.size()) {
+ AMQFrame data((AMQContentBody(content)));
+ msg->getFrames().append(data);
+ }
+ if (ttl) msg->computeExpiration();
+ return Message(msg, msg);
+ }
+};
+
+}} // namespace qpid::tests
+
+#endif /*!TESTS_MESSAGEUTILS_H*/
diff --git a/qpid/cpp/src/tests/MessagingFixture.h b/qpid/cpp/src/tests/MessagingFixture.h
new file mode 100644
index 0000000000..165aefeeec
--- /dev/null
+++ b/qpid/cpp/src/tests/MessagingFixture.h
@@ -0,0 +1,352 @@
+#ifndef TESTS_MESSAGINGFIXTURE_H
+#define TESTS_MESSAGINGFIXTURE_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "BrokerFixture.h"
+#include "unit_test.h"
+#include "test_tools.h"
+#include "qpid/client/Connection.h"
+#include "qpid/client/Session.h"
+#include "qpid/framing/Uuid.h"
+#include "qpid/messaging/Address.h"
+#include "qpid/messaging/Connection.h"
+#include "qpid/messaging/Session.h"
+#include "qpid/messaging/Sender.h"
+#include "qpid/messaging/Receiver.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/types/Variant.h"
+
+#include <boost/format.hpp>
+
+namespace qpid {
+namespace tests {
+
+using qpid::types::Variant;
+
+struct BrokerAdmin
+{
+ qpid::client::Connection connection;
+ qpid::client::Session session;
+
+ BrokerAdmin(uint16_t port)
+ {
+ connection.open("localhost", port);
+ session = connection.newSession();
+ }
+
+ void createQueue(const std::string& name)
+ {
+ session.queueDeclare(qpid::client::arg::queue=name);
+ }
+
+ void deleteQueue(const std::string& name)
+ {
+ session.queueDelete(qpid::client::arg::queue=name);
+ }
+
+ void createExchange(const std::string& name, const std::string& type)
+ {
+ session.exchangeDeclare(qpid::client::arg::exchange=name, qpid::client::arg::type=type);
+ }
+
+ void deleteExchange(const std::string& name)
+ {
+ session.exchangeDelete(qpid::client::arg::exchange=name);
+ }
+
+ bool checkQueueExists(const std::string& name)
+ {
+ return session.queueQuery(name).getQueue() == name;
+ }
+
+ bool checkExchangeExists(const std::string& name, std::string& type)
+ {
+ qpid::framing::ExchangeQueryResult result = session.exchangeQuery(name);
+ type = result.getType();
+ return !result.getNotFound();
+ }
+
+ void send(qpid::client::Message& message, const std::string& exchange=std::string())
+ {
+ session.messageTransfer(qpid::client::arg::destination=exchange, qpid::client::arg::content=message);
+ }
+
+ ~BrokerAdmin()
+ {
+ session.close();
+ connection.close();
+ }
+};
+
+struct MessagingFixture : public BrokerFixture
+{
+ messaging::Connection connection;
+ messaging::Session session;
+ BrokerAdmin admin;
+
+ MessagingFixture(const BrokerOptions& opts = BrokerOptions(), bool mgmtEnabled=false) :
+ BrokerFixture(opts, mgmtEnabled),
+ connection(open(broker->getPort(Broker::TCP_TRANSPORT))),
+ session(connection.createSession()),
+ admin(broker->getPort(Broker::TCP_TRANSPORT))
+ {
+ }
+
+ static messaging::Connection open(uint16_t port)
+ {
+ messaging::Connection connection(
+ (boost::format("amqp:tcp:localhost:%1%") % (port)).str());
+ connection.open();
+ return connection;
+
+ }
+
+ /** Open a connection to the broker. */
+ qpid::messaging::Connection newConnection()
+ {
+ qpid::messaging::Connection connection(
+ (boost::format("amqp:tcp:localhost:%1%") % (broker->getPort(qpid::broker::Broker::TCP_TRANSPORT))).str());
+ return connection;
+ }
+
+ void ping(const qpid::messaging::Address& address)
+ {
+ messaging::Receiver r = session.createReceiver(address);
+ messaging::Sender s = session.createSender(address);
+ messaging::Message out(framing::Uuid(true).str());
+ s.send(out);
+ messaging::Message in;
+ BOOST_CHECK(r.fetch(in, 5*messaging::Duration::SECOND));
+ BOOST_CHECK_EQUAL(out.getContent(), in.getContent());
+ r.close();
+ s.close();
+ }
+
+ ~MessagingFixture()
+ {
+ session.close();
+ connection.close();
+ }
+};
+
+struct QueueFixture : MessagingFixture
+{
+ std::string queue;
+
+ QueueFixture(const std::string& name = "test-queue") : queue(name)
+ {
+ admin.createQueue(queue);
+ }
+
+ ~QueueFixture()
+ {
+ admin.deleteQueue(queue);
+ }
+
+};
+
+struct TopicFixture : MessagingFixture
+{
+ std::string topic;
+
+ TopicFixture(const std::string& name = "test-topic", const std::string& type="fanout") : topic(name)
+ {
+ admin.createExchange(topic, type);
+ }
+
+ ~TopicFixture()
+ {
+ admin.deleteExchange(topic);
+ }
+
+};
+
+struct MultiQueueFixture : MessagingFixture
+{
+ typedef std::vector<std::string>::const_iterator const_iterator;
+ std::vector<std::string> queues;
+
+ MultiQueueFixture(const std::vector<std::string>& names = boost::assign::list_of<std::string>("q1")("q2")("q3")) : queues(names)
+ {
+ for (const_iterator i = queues.begin(); i != queues.end(); ++i) {
+ admin.createQueue(*i);
+ }
+ }
+
+ ~MultiQueueFixture()
+ {
+ connection.close();
+ for (const_iterator i = queues.begin(); i != queues.end(); ++i) {
+ admin.deleteQueue(*i);
+ }
+ }
+
+};
+
+inline std::vector<std::string> fetch(messaging::Receiver& receiver, int count, messaging::Duration timeout=messaging::Duration::SECOND*5)
+{
+ std::vector<std::string> data;
+ messaging::Message message;
+ for (int i = 0; i < count && receiver.fetch(message, timeout); i++) {
+ data.push_back(message.getContent());
+ }
+ return data;
+}
+
+
+inline void send(messaging::Sender& sender, uint count = 1, uint start = 1,
+ const std::string& base = "Message")
+{
+ for (uint i = start; i < start + count; ++i) {
+ sender.send(messaging::Message((boost::format("%1%_%2%") % base % i).str()));
+ }
+}
+
+inline void receive(messaging::Receiver& receiver, uint count = 1, uint start = 1,
+ const std::string& base = "Message",
+ messaging::Duration timeout=messaging::Duration::SECOND*5)
+{
+ for (uint i = start; i < start + count; ++i) {
+ BOOST_CHECK_EQUAL(receiver.fetch(timeout).getContent(), (boost::format("%1%_%2%") % base % i).str());
+ }
+}
+
+
+class MethodInvoker
+{
+ public:
+ MethodInvoker(messaging::Session session) :
+ replyTo("#; {create:always, node:{x-declare:{auto-delete:true}}}"),
+ sender(session.createSender("qmf.default.direct/broker")),
+ receiver(session.createReceiver(replyTo)) {}
+
+ void createExchange(const std::string& name, const std::string& type, bool durable=false)
+ {
+ Variant::Map params;
+ params["name"]=name;
+ params["type"]="exchange";
+ params["properties"] = Variant::Map();
+ params["properties"].asMap()["exchange-type"] = type;
+ params["properties"].asMap()["durable"] = durable;
+ methodRequest("create", params);
+ }
+
+ void deleteExchange(const std::string& name)
+ {
+ Variant::Map params;
+ params["name"]=name;
+ params["type"]="exchange";
+ methodRequest("delete", params);
+ }
+
+ void createQueue(const std::string& name, bool durable=false, bool autodelete=false,
+ const Variant::Map& options=Variant::Map())
+ {
+ Variant::Map params;
+ params["name"]=name;
+ params["type"]="queue";
+ params["properties"] = options;
+ params["properties"].asMap()["durable"] = durable;
+ params["properties"].asMap()["auto-delete"] = autodelete;
+ methodRequest("create", params);
+ }
+
+ void deleteQueue(const std::string& name)
+ {
+ Variant::Map params;
+ params["name"]=name;
+ params["type"]="queue";
+ methodRequest("delete", params);
+ }
+
+ void bind(const std::string& exchange, const std::string& queue, const std::string& key,
+ const Variant::Map& options=Variant::Map())
+ {
+ Variant::Map params;
+ params["name"]=(boost::format("%1%/%2%/%3%") % (exchange) % (queue) % (key)).str();
+ params["type"]="binding";
+ params["properties"] = options;
+ methodRequest("create", params);
+ }
+
+ void unbind(const std::string& exchange, const std::string& queue, const std::string& key)
+ {
+ Variant::Map params;
+ params["name"]=(boost::format("%1%/%2%/%3%") % (exchange) % (queue) % (key)).str();
+ params["type"]="binding";
+ methodRequest("delete", params);
+ }
+
+ void methodRequest(
+ const std::string& method,
+ const Variant::Map& inParams, Variant::Map* outParams = 0,
+ const std::string& objectName="org.apache.qpid.broker:broker:amqp-broker")
+ {
+ Variant::Map content;
+ Variant::Map objectId;
+ objectId["_object_name"] = objectName;;
+ content["_object_id"] = objectId;
+ content["_method_name"] = method;
+ content["_arguments"] = inParams;
+
+ messaging::Message request;
+ request.setReplyTo(replyTo);
+ request.getProperties()["x-amqp-0-10.app-id"] = "qmf2";
+ request.getProperties()["qmf.opcode"] = "_method_request";
+ encode(content, request);
+
+ sender.send(request);
+
+ messaging::Message response;
+ if (receiver.fetch(response, messaging::Duration::SECOND*5)) {
+ if (response.getProperties()["x-amqp-0-10.app-id"] == "qmf2") {
+ std::string opcode = response.getProperties()["qmf.opcode"];
+ if (opcode == "_method_response") {
+ if (outParams) {
+ Variant::Map m;
+ decode(response, m);
+ *outParams = m["_arguments"].asMap();
+ }
+ } else if (opcode == "_exception") {
+ Variant::Map m;
+ decode(response, m);
+ throw Exception(QPID_MSG("Error: " << m["_values"]));
+ } else {
+ throw Exception(QPID_MSG("Invalid response received, unexpected opcode: " << opcode));
+ }
+ } else {
+ throw Exception(QPID_MSG("Invalid response received, not a qmfv2 message: app-id="
+ << response.getProperties()["x-amqp-0-10.app-id"]));
+ }
+ } else {
+ throw Exception(QPID_MSG("No response received"));
+ }
+ }
+ private:
+ messaging::Address replyTo;
+ messaging::Sender sender;
+ messaging::Receiver receiver;
+};
+
+}} // namespace qpid::tests
+
+#endif /*!TESTS_MESSAGINGFIXTURE_H*/
diff --git a/qpid/cpp/src/tests/MessagingLogger.cpp b/qpid/cpp/src/tests/MessagingLogger.cpp
new file mode 100644
index 0000000000..195a33db12
--- /dev/null
+++ b/qpid/cpp/src/tests/MessagingLogger.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/log/Statement.h"
+#include "qpid/messaging/Connection.h"
+#include "qpid/messaging/exceptions.h"
+#include "qpid/messaging/Logger.h"
+
+#include <iostream>
+#include <memory>
+#include <stdexcept>
+
+#include <vector>
+
+#include "unit_test.h"
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(MessagingLoggerSuite)
+
+class StringLogger : public qpid::messaging::LoggerOutput {
+ std::string& outString;
+
+ void log(qpid::messaging::Level /*level*/, bool user, const char* /*file*/, int /*line*/, const char* /*function*/, const std::string& message){
+ if (user) outString += "User ";
+ outString += message;
+ }
+
+public:
+ StringLogger(std::string& os) :
+ outString(os)
+ {}
+};
+
+#define SETUP_LOGGING(logger, ...) \
+do {\
+ const char* args[]={"", __VA_ARGS__, 0};\
+ qpid::messaging::Logger::configure((sizeof (args)/sizeof (char*))-1, args);\
+ logOutput.clear();\
+ qpid::messaging::Logger::setOutput(logger);\
+} while (0)
+#define LOG_LEVEL(level)\
+ QPID_LOG(level, #level " level output")
+#define LOG_ALL_LOGGING_LEVELS \
+do { \
+ LOG_LEVEL(trace); \
+ LOG_LEVEL(debug); \
+ LOG_LEVEL(info); \
+ LOG_LEVEL(notice); \
+ LOG_LEVEL(warning); \
+ LOG_LEVEL(critical); \
+} while (0)
+#define LOG_USER_LEVEL(level)\
+ qpid::messaging::Logger::log(qpid::messaging::level, __FILE__, __LINE__, __FUNCTION__, #level " message")
+#define LOG_ALL_USER_LOGGING_LEVELS \
+do { \
+ LOG_USER_LEVEL(trace); \
+ LOG_USER_LEVEL(debug); \
+ LOG_USER_LEVEL(info); \
+ LOG_USER_LEVEL(notice); \
+ LOG_USER_LEVEL(warning); \
+ LOG_USER_LEVEL(critical); \
+} while (0)
+
+std::string logOutput;
+
+QPID_AUTO_TEST_CASE(testLoggerLevels)
+{
+ StringLogger logger(logOutput);
+
+ SETUP_LOGGING(logger, "--log-enable", "debug");
+ LOG_ALL_LOGGING_LEVELS;
+ BOOST_CHECK_EQUAL(logOutput, "debug level output\ncritical level output\n");
+
+ SETUP_LOGGING(logger, "--log-enable", "trace+", "--log-disable", "notice");
+ LOG_ALL_LOGGING_LEVELS;
+ BOOST_CHECK_EQUAL(logOutput, "trace level output\ndebug level output\ninfo level output\nwarning level output\ncritical level output\n");
+
+ SETUP_LOGGING(logger, "--log-enable", "info-");
+ LOG_ALL_LOGGING_LEVELS;
+ BOOST_CHECK_EQUAL(logOutput, "trace level output\ndebug level output\ninfo level output\ncritical level output\n");
+
+ SETUP_LOGGING(logger, "--log-enable", "trace+", "--log-disable", "notice+");
+ LOG_ALL_LOGGING_LEVELS;
+ BOOST_CHECK_EQUAL(logOutput, "trace level output\ndebug level output\ninfo level output\ncritical level output\n");
+}
+
+QPID_AUTO_TEST_CASE(testUserLoggerLevels)
+{
+ StringLogger logger(logOutput);
+
+ SETUP_LOGGING(logger, "--log-enable", "debug");
+ LOG_ALL_USER_LOGGING_LEVELS;
+ BOOST_CHECK_EQUAL(logOutput, "User debug message\nUser critical message\n");
+
+ SETUP_LOGGING(logger, "--log-enable", "trace+", "--log-disable", "notice");
+ LOG_ALL_USER_LOGGING_LEVELS;
+ BOOST_CHECK_EQUAL(logOutput, "User trace message\nUser debug message\nUser info message\nUser warning message\nUser critical message\n");
+
+ SETUP_LOGGING(logger, "--log-enable", "info-");
+ LOG_ALL_USER_LOGGING_LEVELS;
+ BOOST_CHECK_EQUAL(logOutput, "User trace message\nUser debug message\nUser info message\nUser critical message\n");
+
+ SETUP_LOGGING(logger, "--log-enable", "trace+", "--log-disable", "notice+");
+ LOG_ALL_USER_LOGGING_LEVELS;
+ BOOST_CHECK_EQUAL(logOutput, "User trace message\nUser debug message\nUser info message\nUser critical message\n");
+
+ SETUP_LOGGING(logger, "--log-disable", "trace+");
+ LOG_ALL_LOGGING_LEVELS;
+ LOG_ALL_USER_LOGGING_LEVELS;
+ BOOST_CHECK_EQUAL(logOutput, "critical level output\nUser critical message\n");
+}
+
+QPID_AUTO_TEST_CASE(testLoggerUsage)
+{
+ qpid::messaging::Logger::configure(0, 0, "blah");
+ std::string u = qpid::messaging::Logger::usage();
+
+ BOOST_CHECK(!u.empty());
+ BOOST_CHECK( u.find("--blah-log-enable")!=u.npos );
+}
+
+QPID_AUTO_TEST_CASE(testLoggerException)
+{
+ const char* args[]={"", "--blah-log-enable", "illegal", 0};
+ BOOST_CHECK_THROW(qpid::messaging::Logger::configure(3, args, "blah"), qpid::messaging::MessagingException);
+}
+
+QPID_AUTO_TEST_SUITE_END()
+}}
diff --git a/qpid/cpp/src/tests/MessagingSessionTests.cpp b/qpid/cpp/src/tests/MessagingSessionTests.cpp
new file mode 100644
index 0000000000..d01dd69999
--- /dev/null
+++ b/qpid/cpp/src/tests/MessagingSessionTests.cpp
@@ -0,0 +1,1495 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "MessagingFixture.h"
+#include "unit_test.h"
+#include "test_tools.h"
+#include "qpid/messaging/Address.h"
+#include "qpid/messaging/Connection.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/messaging/Receiver.h"
+#include "qpid/messaging/Sender.h"
+#include "qpid/messaging/Session.h"
+#include "qpid/client/Connection.h"
+#include "qpid/client/Session.h"
+#include "qpid/framing/ExchangeQueryResult.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/framing/Uuid.h"
+#include "qpid/sys/Time.h"
+#include <boost/assign.hpp>
+#include <boost/format.hpp>
+#include <string>
+#include <vector>
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(MessagingSessionTests)
+
+using namespace qpid::messaging;
+using namespace qpid::types;
+using namespace qpid;
+using qpid::broker::BrokerOptions;
+using qpid::framing::Uuid;
+
+
+QPID_AUTO_TEST_CASE(testSimpleSendReceive)
+{
+ QueueFixture fix;
+ Sender sender = fix.session.createSender(fix.queue);
+ Message out("test-message");
+ out.setSubject("test-subject");
+ sender.send(out);
+ Receiver receiver = fix.session.createReceiver(fix.queue);
+ Message in = receiver.fetch(Duration::SECOND * 5);
+ fix.session.acknowledge();
+ BOOST_CHECK_EQUAL(in.getContent(), out.getContent());
+ BOOST_CHECK_EQUAL(in.getSubject(), out.getSubject());
+}
+
+QPID_AUTO_TEST_CASE(testSyncSendReceive)
+{
+ QueueFixture fix;
+ Sender sender = fix.session.createSender(fix.queue);
+ Message out("test-message");
+ sender.send(out, true);
+ Receiver receiver = fix.session.createReceiver(fix.queue);
+ Message in = receiver.fetch(Duration::IMMEDIATE);
+ fix.session.acknowledge(true);
+ BOOST_CHECK_EQUAL(in.getContent(), out.getContent());
+}
+
+QPID_AUTO_TEST_CASE(testSendReceiveHeaders)
+{
+ QueueFixture fix;
+ Sender sender = fix.session.createSender(fix.queue);
+ Message out("test-message");
+ for (uint i = 0; i < 10; ++i) {
+ out.getProperties()["a"] = i;
+ out.setProperty("b", i + 100);
+ sender.send(out);
+ }
+ uint8_t v1(255u);
+ int8_t v2(-120);
+ out.getProperties()["c"] = v1;
+ out.getProperties()["d"] = v2;
+ sender.send(out);
+ Receiver receiver = fix.session.createReceiver(fix.queue);
+ Message in;
+ for (uint i = 0; i < 10; ++i) {
+ BOOST_CHECK(receiver.fetch(in, Duration::SECOND * 5));
+ BOOST_CHECK_EQUAL(in.getContent(), out.getContent());
+ BOOST_CHECK_EQUAL(in.getProperties()["a"].asUint32(), i);
+ BOOST_CHECK_EQUAL(in.getProperties()["b"].asUint32(), i + 100);
+ fix.session.acknowledge();
+ }
+ BOOST_CHECK(receiver.fetch(in, Duration::SECOND * 5));
+ Variant& c = in.getProperties()["c"];
+ BOOST_CHECK_EQUAL(c.getType(), VAR_UINT8);
+ BOOST_CHECK_EQUAL(c.asUint8(), v1);
+ Variant& d = in.getProperties()["d"];
+ BOOST_CHECK_EQUAL(d.getType(), VAR_INT8);
+ BOOST_CHECK_EQUAL(d.asInt8(), v2);
+}
+
+QPID_AUTO_TEST_CASE(testSenderError)
+{
+ MessagingFixture fix;
+ ScopedSuppressLogging sl;
+ BOOST_CHECK_THROW(fix.session.createSender("NonExistentAddress"), qpid::messaging::NotFound);
+ fix.session = fix.connection.createSession();
+ BOOST_CHECK_THROW(fix.session.createSender("NonExistentAddress; {create:receiver}"),
+ qpid::messaging::NotFound);
+}
+
+QPID_AUTO_TEST_CASE(testReceiverError)
+{
+ MessagingFixture fix;
+ ScopedSuppressLogging sl;
+ BOOST_CHECK_THROW(fix.session.createReceiver("NonExistentAddress"), qpid::messaging::NotFound);
+ fix.session = fix.connection.createSession();
+ BOOST_CHECK_THROW(fix.session.createReceiver("NonExistentAddress; {create:sender}"),
+ qpid::messaging::NotFound);
+}
+
+QPID_AUTO_TEST_CASE(testSimpleTopic)
+{
+ TopicFixture fix;
+
+ Sender sender = fix.session.createSender(fix.topic);
+ Message msg("one");
+ sender.send(msg);
+ Receiver sub1 = fix.session.createReceiver(fix.topic);
+ sub1.setCapacity(10u);
+ msg.setContent("two");
+ sender.send(msg);
+ Receiver sub2 = fix.session.createReceiver(fix.topic);
+ sub2.setCapacity(10u);
+ msg.setContent("three");
+ sender.send(msg);
+ Receiver sub3 = fix.session.createReceiver(fix.topic);
+ sub3.setCapacity(10u);
+ msg.setContent("four");
+ sender.send(msg);
+ BOOST_CHECK_EQUAL(fetch(sub2, 2), boost::assign::list_of<std::string>("three")("four"));
+ sub2.close();
+
+ msg.setContent("five");
+ sender.send(msg);
+ BOOST_CHECK_EQUAL(fetch(sub1, 4), boost::assign::list_of<std::string>("two")("three")("four")("five"));
+ BOOST_CHECK_EQUAL(fetch(sub3, 2), boost::assign::list_of<std::string>("four")("five"));
+ Message in;
+ BOOST_CHECK(!sub2.fetch(in, Duration::IMMEDIATE));//TODO: or should this raise an error?
+
+
+ //TODO: check pending messages...
+}
+
+QPID_AUTO_TEST_CASE(testNextReceiver)
+{
+ MultiQueueFixture fix;
+
+ for (uint i = 0; i < fix.queues.size(); i++) {
+ Receiver r = fix.session.createReceiver(fix.queues[i]);
+ r.setCapacity(10u);
+ }
+
+ for (uint i = 0; i < fix.queues.size(); i++) {
+ Sender s = fix.session.createSender(fix.queues[i]);
+ Message msg((boost::format("Message_%1%") % (i+1)).str());
+ s.send(msg);
+ }
+
+ for (uint i = 0; i < fix.queues.size(); i++) {
+ Message msg;
+ BOOST_CHECK(fix.session.nextReceiver().fetch(msg, Duration::SECOND));
+ BOOST_CHECK_EQUAL(msg.getContent(), (boost::format("Message_%1%") % (i+1)).str());
+ }
+}
+
+QPID_AUTO_TEST_CASE(testMapMessage)
+{
+ QueueFixture fix;
+ Sender sender = fix.session.createSender(fix.queue);
+ Message out;
+ Variant::Map content;
+ content["abc"] = "def";
+ content["pi"] = 3.14f;
+ Variant utf8("A utf 8 string");
+ utf8.setEncoding("utf8");
+ content["utf8"] = utf8;
+ Variant utf16("\x00\x61\x00\x62\x00\x63");
+ utf16.setEncoding("utf16");
+ content["utf16"] = utf16;
+ encode(content, out);
+ sender.send(out);
+ Receiver receiver = fix.session.createReceiver(fix.queue);
+ Message in = receiver.fetch(5 * Duration::SECOND);
+ Variant::Map view;
+ decode(in, view);
+ BOOST_CHECK_EQUAL(view["abc"].asString(), "def");
+ BOOST_CHECK_EQUAL(view["pi"].asFloat(), 3.14f);
+ BOOST_CHECK_EQUAL(view["utf8"].asString(), utf8.asString());
+ BOOST_CHECK_EQUAL(view["utf8"].getEncoding(), utf8.getEncoding());
+ BOOST_CHECK_EQUAL(view["utf16"].asString(), utf16.asString());
+ BOOST_CHECK_EQUAL(view["utf16"].getEncoding(), utf16.getEncoding());
+ fix.session.acknowledge();
+}
+
+QPID_AUTO_TEST_CASE(testMapMessageWithInitial)
+{
+ QueueFixture fix;
+ Sender sender = fix.session.createSender(fix.queue);
+ Message out;
+ Variant::Map imap;
+ imap["abc"] = "def";
+ imap["pi"] = 3.14f;
+ encode(imap, out);
+ sender.send(out);
+ Receiver receiver = fix.session.createReceiver(fix.queue);
+ Message in = receiver.fetch(5 * Duration::SECOND);
+ Variant::Map view;
+ decode(in, view);
+ BOOST_CHECK_EQUAL(view["abc"].asString(), "def");
+ BOOST_CHECK_EQUAL(view["pi"].asFloat(), 3.14f);
+ fix.session.acknowledge();
+}
+
+QPID_AUTO_TEST_CASE(testListMessage)
+{
+ QueueFixture fix;
+ Sender sender = fix.session.createSender(fix.queue);
+ Message out;
+ Variant::List content;
+ content.push_back(Variant("abc"));
+ content.push_back(Variant(1234));
+ content.push_back(Variant("def"));
+ content.push_back(Variant(56.789));
+ encode(content, out);
+ sender.send(out);
+ Receiver receiver = fix.session.createReceiver(fix.queue);
+ Message in = receiver.fetch(5 * Duration::SECOND);
+ Variant::List view;
+ decode(in, view);
+ BOOST_CHECK_EQUAL(view.size(), content.size());
+ BOOST_CHECK_EQUAL(view.front().asString(), "abc");
+ BOOST_CHECK_EQUAL(view.back().asDouble(), 56.789);
+
+ Variant::List::const_iterator i = view.begin();
+ BOOST_CHECK(i != view.end());
+ BOOST_CHECK_EQUAL(i->asString(), "abc");
+ BOOST_CHECK(++i != view.end());
+ BOOST_CHECK_EQUAL(i->asInt64(), 1234);
+ BOOST_CHECK(++i != view.end());
+ BOOST_CHECK_EQUAL(i->asString(), "def");
+ BOOST_CHECK(++i != view.end());
+ BOOST_CHECK_EQUAL(i->asDouble(), 56.789);
+ BOOST_CHECK(++i == view.end());
+
+ fix.session.acknowledge();
+}
+
+QPID_AUTO_TEST_CASE(testListMessageWithInitial)
+{
+ QueueFixture fix;
+ Sender sender = fix.session.createSender(fix.queue);
+ Message out;
+ Variant::List ilist;
+ ilist.push_back(Variant("abc"));
+ ilist.push_back(Variant(1234));
+ ilist.push_back(Variant("def"));
+ ilist.push_back(Variant(56.789));
+ encode(ilist, out);
+ sender.send(out);
+ Receiver receiver = fix.session.createReceiver(fix.queue);
+ Message in = receiver.fetch(5 * Duration::SECOND);
+ Variant::List view;
+ decode(in, view);
+ BOOST_CHECK_EQUAL(view.size(), ilist.size());
+ BOOST_CHECK_EQUAL(view.front().asString(), "abc");
+ BOOST_CHECK_EQUAL(view.back().asDouble(), 56.789);
+
+ Variant::List::const_iterator i = view.begin();
+ BOOST_CHECK(i != view.end());
+ BOOST_CHECK_EQUAL(i->asString(), "abc");
+ BOOST_CHECK(++i != view.end());
+ BOOST_CHECK_EQUAL(i->asInt64(), 1234);
+ BOOST_CHECK(++i != view.end());
+ BOOST_CHECK_EQUAL(i->asString(), "def");
+ BOOST_CHECK(++i != view.end());
+ BOOST_CHECK_EQUAL(i->asDouble(), 56.789);
+ BOOST_CHECK(++i == view.end());
+
+ fix.session.acknowledge();
+}
+
+QPID_AUTO_TEST_CASE(testReject)
+{
+ QueueFixture fix;
+ Sender sender = fix.session.createSender(fix.queue);
+ Message m1("reject-me");
+ sender.send(m1);
+ Message m2("accept-me");
+ sender.send(m2);
+ Receiver receiver = fix.session.createReceiver(fix.queue);
+ Message in = receiver.fetch(5 * Duration::SECOND);
+ BOOST_CHECK_EQUAL(in.getContent(), m1.getContent());
+ fix.session.reject(in);
+ in = receiver.fetch(5 * Duration::SECOND);
+ BOOST_CHECK_EQUAL(in.getContent(), m2.getContent());
+ fix.session.acknowledge();
+}
+
+QPID_AUTO_TEST_CASE(testAvailable)
+{
+ MultiQueueFixture fix;
+
+ Receiver r1 = fix.session.createReceiver(fix.queues[0]);
+ r1.setCapacity(100);
+
+ Receiver r2 = fix.session.createReceiver(fix.queues[1]);
+ r2.setCapacity(100);
+
+ Sender s1 = fix.session.createSender(fix.queues[0]);
+ Sender s2 = fix.session.createSender(fix.queues[1]);
+
+ for (uint i = 0; i < 10; ++i) {
+ s1.send(Message((boost::format("A_%1%") % (i+1)).str()));
+ }
+ for (uint i = 0; i < 5; ++i) {
+ s2.send(Message((boost::format("B_%1%") % (i+1)).str()));
+ }
+ qpid::sys::sleep(1);//is there any avoid an arbitrary sleep while waiting for messages to be dispatched?
+ for (uint i = 0; i < 5; ++i) {
+ BOOST_CHECK_EQUAL(fix.session.getReceivable(), 15u - 2*i);
+ BOOST_CHECK_EQUAL(r1.getAvailable(), 10u - i);
+ BOOST_CHECK_EQUAL(r1.fetch().getContent(), (boost::format("A_%1%") % (i+1)).str());
+ BOOST_CHECK_EQUAL(r2.getAvailable(), 5u - i);
+ BOOST_CHECK_EQUAL(r2.fetch().getContent(), (boost::format("B_%1%") % (i+1)).str());
+ fix.session.acknowledge();
+ }
+ for (uint i = 5; i < 10; ++i) {
+ BOOST_CHECK_EQUAL(fix.session.getReceivable(), 10u - i);
+ BOOST_CHECK_EQUAL(r1.getAvailable(), 10u - i);
+ BOOST_CHECK_EQUAL(r1.fetch().getContent(), (boost::format("A_%1%") % (i+1)).str());
+ }
+}
+
+QPID_AUTO_TEST_CASE(testUnsettledAcks)
+{
+ QueueFixture fix;
+ Sender sender = fix.session.createSender(fix.queue);
+ for (uint i = 0; i < 10; ++i) {
+ sender.send(Message((boost::format("Message_%1%") % (i+1)).str()));
+ }
+ Receiver receiver = fix.session.createReceiver(fix.queue);
+ for (uint i = 0; i < 10; ++i) {
+ BOOST_CHECK_EQUAL(receiver.fetch().getContent(), (boost::format("Message_%1%") % (i+1)).str());
+ }
+ BOOST_CHECK_EQUAL(fix.session.getUnsettledAcks(), 0u);
+ fix.session.acknowledge();
+ BOOST_CHECK_EQUAL(fix.session.getUnsettledAcks(), 10u);
+ fix.session.sync();
+ BOOST_CHECK_EQUAL(fix.session.getUnsettledAcks(), 0u);
+}
+
+QPID_AUTO_TEST_CASE(testUnsettledSend)
+{
+ QueueFixture fix;
+ Sender sender = fix.session.createSender(fix.queue);
+ send(sender, 10);
+ //Note: this test relies on 'inside knowledge' of the sender
+ //implementation and the fact that the simple test case makes it
+ //possible to predict when completion information will be sent to
+ //the client. TODO: is there a better way of testing this?
+ BOOST_CHECK_EQUAL(sender.getUnsettled(), 10u);
+ fix.session.sync();
+ BOOST_CHECK_EQUAL(sender.getUnsettled(), 0u);
+
+ Receiver receiver = fix.session.createReceiver(fix.queue);
+ receive(receiver, 10);
+ fix.session.acknowledge();
+}
+
+QPID_AUTO_TEST_CASE(testBrowse)
+{
+ QueueFixture fix;
+ Sender sender = fix.session.createSender(fix.queue);
+ send(sender, 10);
+ Receiver browser1 = fix.session.createReceiver(fix.queue + "; {mode:browse}");
+ receive(browser1, 10);
+ Receiver browser2 = fix.session.createReceiver(fix.queue + "; {mode:browse}");
+ receive(browser2, 10);
+ Receiver releaser1 = fix.session.createReceiver(fix.queue);
+ Message m1 = releaser1.fetch(messaging::Duration::SECOND*5);
+ BOOST_CHECK(!m1.getRedelivered());
+ fix.session.release(m1);
+ Receiver releaser2 = fix.session.createReceiver(fix.queue);
+ Message m2 = releaser2.fetch(messaging::Duration::SECOND*5);
+ BOOST_CHECK(m2.getRedelivered());
+ fix.session.release(m2);
+ Receiver consumer = fix.session.createReceiver(fix.queue);
+ receive(consumer, 10);
+ fix.session.acknowledge();
+}
+
+struct QueueCreatePolicyFixture : public MessagingFixture
+{
+ qpid::messaging::Address address;
+
+ QueueCreatePolicyFixture(const std::string& a) : address(a) {}
+
+ void test()
+ {
+ ping(address);
+ BOOST_CHECK(admin.checkQueueExists(address.getName()));
+ }
+
+ ~QueueCreatePolicyFixture()
+ {
+ admin.deleteQueue(address.getName());
+ }
+};
+
+QPID_AUTO_TEST_CASE(testCreatePolicyQueueAlways)
+{
+ QueueCreatePolicyFixture fix("#; {create:always, node:{type:queue}}");
+ fix.test();
+}
+
+QPID_AUTO_TEST_CASE(testCreatePolicyQueueReceiver)
+{
+ QueueCreatePolicyFixture fix("#; {create:receiver, node:{type:queue}}");
+ Receiver r = fix.session.createReceiver(fix.address);
+ fix.test();
+ r.close();
+}
+
+QPID_AUTO_TEST_CASE(testCreatePolicyQueueSender)
+{
+ QueueCreatePolicyFixture fix("#; {create:sender, node:{type:queue}}");
+ Sender s = fix.session.createSender(fix.address);
+ fix.test();
+ s.close();
+}
+
+struct ExchangeCreatePolicyFixture : public MessagingFixture
+{
+ qpid::messaging::Address address;
+ const std::string exchangeType;
+
+ ExchangeCreatePolicyFixture(const std::string& a, const std::string& t) :
+ address(a), exchangeType(t) {}
+
+ void test()
+ {
+ ping(address);
+ std::string actualType;
+ BOOST_CHECK(admin.checkExchangeExists(address.getName(), actualType));
+ BOOST_CHECK_EQUAL(exchangeType, actualType);
+ }
+
+ ~ExchangeCreatePolicyFixture()
+ {
+ admin.deleteExchange(address.getName());
+ }
+};
+
+QPID_AUTO_TEST_CASE(testCreatePolicyTopic)
+{
+ ExchangeCreatePolicyFixture fix("#; {create:always, node:{type:topic}}",
+ "topic");
+ fix.test();
+}
+
+QPID_AUTO_TEST_CASE(testCreatePolicyTopicReceiverFanout)
+{
+ ExchangeCreatePolicyFixture fix("#/my-subject; {create:receiver, node:{type:topic, x-declare:{type:fanout}}}", "fanout");
+ Receiver r = fix.session.createReceiver(fix.address);
+ fix.test();
+ r.close();
+}
+
+QPID_AUTO_TEST_CASE(testCreatePolicyTopicSenderDirect)
+{
+ ExchangeCreatePolicyFixture fix("#/my-subject; {create:sender, node:{type:topic, x-declare:{type:direct}}}", "direct");
+ Sender s = fix.session.createSender(fix.address);
+ fix.test();
+ s.close();
+}
+
+struct DeletePolicyFixture : public MessagingFixture
+{
+ enum Mode {RECEIVER, SENDER, ALWAYS, NEVER};
+
+ std::string getPolicy(Mode mode)
+ {
+ switch (mode) {
+ case SENDER:
+ return "{delete:sender}";
+ case RECEIVER:
+ return "{delete:receiver}";
+ case ALWAYS:
+ return "{delete:always}";
+ case NEVER:
+ return "{delete:never}";
+ }
+ return "";
+ }
+
+ void testAll()
+ {
+ test(RECEIVER);
+ test(SENDER);
+ test(ALWAYS);
+ test(NEVER);
+ }
+
+ virtual ~DeletePolicyFixture() {}
+ virtual void create(const qpid::messaging::Address&) = 0;
+ virtual void destroy(const qpid::messaging::Address&) = 0;
+ virtual bool exists(const qpid::messaging::Address&) = 0;
+
+ void test(Mode mode)
+ {
+ qpid::messaging::Address address("testqueue#; " + getPolicy(mode));
+ create(address);
+
+ Sender s = session.createSender(address);
+ Receiver r = session.createReceiver(address);
+ switch (mode) {
+ case RECEIVER:
+ s.close();
+ BOOST_CHECK(exists(address));
+ r.close();
+ BOOST_CHECK(!exists(address));
+ break;
+ case SENDER:
+ r.close();
+ BOOST_CHECK(exists(address));
+ s.close();
+ BOOST_CHECK(!exists(address));
+ break;
+ case ALWAYS:
+ s.close();
+ BOOST_CHECK(!exists(address));
+ break;
+ case NEVER:
+ r.close();
+ BOOST_CHECK(exists(address));
+ s.close();
+ BOOST_CHECK(exists(address));
+ destroy(address);
+ }
+ }
+};
+
+struct QueueDeletePolicyFixture : DeletePolicyFixture
+{
+ void create(const qpid::messaging::Address& address)
+ {
+ admin.createQueue(address.getName());
+ }
+ void destroy(const qpid::messaging::Address& address)
+ {
+ admin.deleteQueue(address.getName());
+ }
+ bool exists(const qpid::messaging::Address& address)
+ {
+ return admin.checkQueueExists(address.getName());
+ }
+};
+
+struct ExchangeDeletePolicyFixture : DeletePolicyFixture
+{
+ const std::string exchangeType;
+ ExchangeDeletePolicyFixture(const std::string type = "topic") : exchangeType(type) {}
+
+ void create(const qpid::messaging::Address& address)
+ {
+ admin.createExchange(address.getName(), exchangeType);
+ }
+ void destroy(const qpid::messaging::Address& address)
+ {
+ admin.deleteExchange(address.getName());
+ }
+ bool exists(const qpid::messaging::Address& address)
+ {
+ std::string actualType;
+ return admin.checkExchangeExists(address.getName(), actualType) && actualType == exchangeType;
+ }
+};
+
+QPID_AUTO_TEST_CASE(testDeletePolicyQueue)
+{
+ QueueDeletePolicyFixture fix;
+ fix.testAll();
+}
+
+QPID_AUTO_TEST_CASE(testDeletePolicyExchange)
+{
+ ExchangeDeletePolicyFixture fix;
+ fix.testAll();
+}
+
+QPID_AUTO_TEST_CASE(testAssertPolicyQueue)
+{
+ MessagingFixture fix;
+ std::string a1 = "q; {create:always, assert:always, node:{type:queue, durable:false, x-declare:{arguments:{qpid.max-count:100}}}}";
+ Sender s1 = fix.session.createSender(a1);
+ s1.close();
+ Receiver r1 = fix.session.createReceiver(a1);
+ r1.close();
+
+ std::string a2 = "q; {assert:receiver, node:{durable:true, x-declare:{arguments:{qpid.max-count:100}}}}";
+ Sender s2 = fix.session.createSender(a2);
+ s2.close();
+ BOOST_CHECK_THROW(fix.session.createReceiver(a2), qpid::messaging::AssertionFailed);
+
+ std::string a3 = "q; {assert:sender, node:{x-declare:{arguments:{qpid.max-count:99}}}}";
+ BOOST_CHECK_THROW(fix.session.createSender(a3), qpid::messaging::AssertionFailed);
+ Receiver r3 = fix.session.createReceiver(a3);
+ r3.close();
+
+ fix.admin.deleteQueue("q");
+}
+
+QPID_AUTO_TEST_CASE(testAssertExchangeOption)
+{
+ MessagingFixture fix;
+ std::string a1 = "e; {create:always, assert:always, node:{type:topic, x-declare:{type:direct, arguments:{qpid.msg_sequence:True}}}}";
+ Sender s1 = fix.session.createSender(a1);
+ s1.close();
+ Receiver r1 = fix.session.createReceiver(a1);
+ r1.close();
+
+ std::string a2 = "e; {assert:receiver, node:{type:topic, x-declare:{type:fanout, arguments:{qpid.msg_sequence:True}}}}";
+ Sender s2 = fix.session.createSender(a2);
+ s2.close();
+ BOOST_CHECK_THROW(fix.session.createReceiver(a2), qpid::messaging::AssertionFailed);
+
+ std::string a3 = "e; {assert:sender, node:{x-declare:{arguments:{qpid.msg_sequence:False}}}}";
+ BOOST_CHECK_THROW(fix.session.createSender(a3), qpid::messaging::AssertionFailed);
+ Receiver r3 = fix.session.createReceiver(a3);
+ r3.close();
+
+ fix.admin.deleteExchange("e");
+}
+
+QPID_AUTO_TEST_CASE(testGetSender)
+{
+ QueueFixture fix;
+ std::string name = fix.session.createSender(fix.queue).getName();
+ Sender sender = fix.session.getSender(name);
+ BOOST_CHECK_EQUAL(name, sender.getName());
+ Message out(Uuid(true).str());
+ sender.send(out);
+ Message in;
+ BOOST_CHECK(fix.session.createReceiver(fix.queue).fetch(in));
+ BOOST_CHECK_EQUAL(out.getContent(), in.getContent());
+ BOOST_CHECK_THROW(fix.session.getSender("UnknownSender"), qpid::messaging::KeyError);
+}
+
+QPID_AUTO_TEST_CASE(testGetReceiver)
+{
+ QueueFixture fix;
+ std::string name = fix.session.createReceiver(fix.queue).getName();
+ Receiver receiver = fix.session.getReceiver(name);
+ BOOST_CHECK_EQUAL(name, receiver.getName());
+ Message out(Uuid(true).str());
+ fix.session.createSender(fix.queue).send(out);
+ Message in;
+ BOOST_CHECK(receiver.fetch(in));
+ BOOST_CHECK_EQUAL(out.getContent(), in.getContent());
+ BOOST_CHECK_THROW(fix.session.getReceiver("UnknownReceiver"), qpid::messaging::KeyError);
+}
+
+QPID_AUTO_TEST_CASE(testGetSessionFromConnection)
+{
+ QueueFixture fix;
+ fix.connection.createSession("my-session");
+ Session session = fix.connection.getSession("my-session");
+ Message out(Uuid(true).str());
+ session.createSender(fix.queue).send(out);
+ Message in;
+ BOOST_CHECK(session.createReceiver(fix.queue).fetch(in));
+ BOOST_CHECK_EQUAL(out.getContent(), in.getContent());
+ BOOST_CHECK_THROW(fix.connection.getSession("UnknownSession"), qpid::messaging::KeyError);
+}
+
+QPID_AUTO_TEST_CASE(testGetConnectionFromSession)
+{
+ QueueFixture fix;
+ Message out(Uuid(true).str());
+ Sender sender = fix.session.createSender(fix.queue);
+ sender.send(out);
+ Message in;
+ sender.getSession().getConnection().createSession("incoming");
+ BOOST_CHECK(fix.connection.getSession("incoming").createReceiver(fix.queue).fetch(in));
+ BOOST_CHECK_EQUAL(out.getContent(), in.getContent());
+}
+
+QPID_AUTO_TEST_CASE(testTx)
+{
+ QueueFixture fix;
+ Session ssn1 = fix.connection.createTransactionalSession();
+ Session ssn2 = fix.connection.createTransactionalSession();
+ Sender sender1 = ssn1.createSender(fix.queue);
+ Sender sender2 = ssn2.createSender(fix.queue);
+ Receiver receiver1 = ssn1.createReceiver(fix.queue);
+ Receiver receiver2 = ssn2.createReceiver(fix.queue);
+ Message in;
+
+ send(sender1, 5, 1, "A");
+ send(sender2, 5, 1, "B");
+ ssn2.commit();
+ receive(receiver1, 5, 1, "B");//(only those from sender2 should be received)
+ BOOST_CHECK(!receiver1.fetch(in, Duration::IMMEDIATE));//check there are no more messages
+ ssn1.rollback();
+ receive(receiver2, 5, 1, "B");
+ BOOST_CHECK(!receiver2.fetch(in, Duration::IMMEDIATE));//check there are no more messages
+ ssn2.rollback();
+ receive(receiver1, 5, 1, "B");
+ BOOST_CHECK(!receiver1.fetch(in, Duration::IMMEDIATE));//check there are no more messages
+ ssn1.commit();
+ //check neither receiver gets any more messages:
+ BOOST_CHECK(!receiver1.fetch(in, Duration::IMMEDIATE));
+ BOOST_CHECK(!receiver2.fetch(in, Duration::IMMEDIATE));
+}
+
+QPID_AUTO_TEST_CASE(testRelease)
+{
+ QueueFixture fix;
+ Sender sender = fix.session.createSender(fix.queue);
+ Message out("test-message");
+ sender.send(out, true);
+ Receiver receiver = fix.session.createReceiver(fix.queue);
+ Message m1 = receiver.fetch(Duration::IMMEDIATE);
+ fix.session.release(m1);
+ Message m2 = receiver.fetch(Duration::SECOND * 1);
+ BOOST_CHECK_EQUAL(m1.getContent(), out.getContent());
+ BOOST_CHECK_EQUAL(m1.getContent(), m2.getContent());
+ BOOST_CHECK(m2.getRedelivered());
+ fix.session.acknowledge(true);
+}
+
+QPID_AUTO_TEST_CASE(testOptionVerification)
+{
+ MessagingFixture fix;
+ fix.session.createReceiver("my-queue; {create: always, assert: always, delete: always, node: {type: queue, durable: false, x-declare: {arguments: {a: b}}, x-bindings: [{exchange: amq.fanout}]}, link: {name: abc, durable: false, reliability: exactly-once, x-subscribe: {arguments:{a:b}}, x-bindings:[{exchange: amq.fanout}]}, mode: browse}");
+ BOOST_CHECK_THROW(fix.session.createReceiver("my-queue; {invalid-option:blah}"), qpid::messaging::AddressError);
+}
+
+QPID_AUTO_TEST_CASE(testReceiveSpecialProperties)
+{
+ QueueFixture fix;
+
+ qpid::client::Message out;
+ out.getDeliveryProperties().setRoutingKey(fix.queue);
+ out.getMessageProperties().setAppId("my-app-id");
+ out.getMessageProperties().setMessageId(qpid::framing::Uuid(true));
+ out.getMessageProperties().setContentEncoding("my-content-encoding");
+ fix.admin.send(out);
+
+ Receiver receiver = fix.session.createReceiver(fix.queue);
+ Message in = receiver.fetch(Duration::SECOND * 5);
+ BOOST_CHECK_EQUAL(in.getProperties()["x-amqp-0-10.routing-key"].asString(), out.getDeliveryProperties().getRoutingKey());
+ BOOST_CHECK_EQUAL(in.getProperties()["x-amqp-0-10.app-id"].asString(), out.getMessageProperties().getAppId());
+ BOOST_CHECK_EQUAL(in.getProperties()["x-amqp-0-10.content-encoding"].asString(), out.getMessageProperties().getContentEncoding());
+ BOOST_CHECK_EQUAL(in.getMessageId(), out.getMessageProperties().getMessageId().str());
+ fix.session.acknowledge(true);
+}
+
+QPID_AUTO_TEST_CASE(testSendSpecialProperties)
+{
+ QueueFixture fix;
+ Sender sender = fix.session.createSender(fix.queue);
+ Message out("test-message");
+ std::string appId = "my-app-id";
+ std::string contentEncoding = "my-content-encoding";
+ out.getProperties()["x-amqp-0-10.app-id"] = appId;
+ out.getProperties()["x-amqp-0-10.content-encoding"] = contentEncoding;
+ out.setMessageId(qpid::framing::Uuid(true).str());
+ sender.send(out, true);
+
+ qpid::client::LocalQueue q;
+ qpid::client::SubscriptionManager subs(fix.admin.session);
+ qpid::client::Subscription s = subs.subscribe(q, fix.queue);
+ qpid::client::Message in = q.get();
+ s.cancel();
+ fix.admin.session.sync();
+
+ BOOST_CHECK_EQUAL(in.getMessageProperties().getAppId(), appId);
+ BOOST_CHECK_EQUAL(in.getMessageProperties().getContentEncoding(), contentEncoding);
+ BOOST_CHECK_EQUAL(in.getMessageProperties().getMessageId().str(), out.getMessageId());
+}
+
+QPID_AUTO_TEST_CASE(testExclusiveSubscriber)
+{
+ QueueFixture fix;
+ std::string address = (boost::format("%1%; { link: { x-subscribe : { exclusive:true } } }") % fix.queue).str();
+ Receiver receiver = fix.session.createReceiver(address);
+ ScopedSuppressLogging sl;
+ try {
+ fix.session.createReceiver(address);
+ fix.session.sync();
+ BOOST_FAIL("Expected exception.");
+ } catch (const MessagingException& /*e*/) {}
+}
+
+
+QPID_AUTO_TEST_CASE(testExclusiveQueueSubscriberAndBrowser)
+{
+ MessagingFixture fix;
+
+ std::string address = "exclusive-queue; { create: receiver, node : { x-declare : { auto-delete: true, exclusive: true } } }";
+ std::string browseAddress = "exclusive-queue; { mode: browse }";
+
+ Receiver receiver = fix.session.createReceiver(address);
+ fix.session.sync();
+
+ Connection c2 = fix.newConnection();
+ c2.open();
+ Session s2 = c2.createSession();
+
+ BOOST_CHECK_NO_THROW(Receiver browser = s2.createReceiver(browseAddress));
+ c2.close();
+}
+
+
+QPID_AUTO_TEST_CASE(testDeleteQueueWithUnackedMessages)
+{
+ MessagingFixture fix;
+ const uint capacity = 5;
+
+ Sender sender = fix.session.createSender("test.ex;{create:always,node:{type:topic}}");
+ Receiver receiver2 = fix.session.createReceiver("alternate.ex;{create:always,node:{type:topic}}");
+ Receiver receiver1 = fix.session.createReceiver("test.q;{create:always, delete:always,node:{type:queue, x-declare:{alternate-exchange:alternate.ex}},link:{x-bindings:[{exchange:test.ex,queue:test.q,key:#}]}}");
+
+ receiver1.setCapacity(capacity);
+ receiver2.setCapacity(capacity*2);
+
+ Message out("test-message");
+ for (uint i = 0; i < capacity*2; ++i) {
+ sender.send(out);
+ }
+
+ receiver1.close();
+
+ // Make sure all pending messages were sent to the alternate
+ // exchange when the queue was deleted.
+ Message in;
+ for (uint i = 0; i < capacity*2; ++i) {
+ in = receiver2.fetch(Duration::SECOND * 5);
+ BOOST_CHECK_EQUAL(in.getContent(), out.getContent());
+ }
+}
+
+QPID_AUTO_TEST_CASE(testAuthenticatedUsername)
+{
+ MessagingFixture fix;
+ Connection connection = fix.newConnection();
+ connection.setOption("sasl-mechanism", "PLAIN");
+ connection.setOption("username", "test-user");
+ connection.setOption("password", "ignored");
+ connection.open();
+ BOOST_CHECK_EQUAL(connection.getAuthenticatedUsername(), std::string("test-user"));
+}
+
+QPID_AUTO_TEST_CASE(testExceptionOnClosedConnection)
+{
+ MessagingFixture fix;
+ fix.connection.close();
+ BOOST_CHECK_THROW(fix.connection.createSession(), MessagingException);
+ Connection connection("blah");
+ BOOST_CHECK_THROW(connection.createSession(), MessagingException);
+}
+
+QPID_AUTO_TEST_CASE(testAcknowledge)
+{
+ QueueFixture fix;
+ Sender sender = fix.session.createSender(fix.queue);
+ const uint count(20);
+ for (uint i = 0; i < count; ++i) {
+ sender.send(Message((boost::format("Message_%1%") % (i+1)).str()));
+ }
+
+ Session other = fix.connection.createSession();
+ Receiver receiver = other.createReceiver(fix.queue);
+ std::vector<Message> messages;
+ for (uint i = 0; i < count; ++i) {
+ Message msg = receiver.fetch();
+ BOOST_CHECK_EQUAL(msg.getContent(), (boost::format("Message_%1%") % (i+1)).str());
+ messages.push_back(msg);
+ }
+ const uint batch(10); //acknowledge first 10 messages only
+ for (uint i = 0; i < batch; ++i) {
+ other.acknowledge(messages[i]);
+ }
+ messages.clear();
+ other.sync();
+ other.close();
+
+ other = fix.connection.createSession();
+ receiver = other.createReceiver(fix.queue);
+ for (uint i = 0; i < (count-batch); ++i) {
+ Message msg = receiver.fetch();
+ BOOST_CHECK_EQUAL(msg.getContent(), (boost::format("Message_%1%") % (i+1+batch)).str());
+ if (i % 2) other.acknowledge(msg); //acknowledge every other message
+ }
+ other.sync();
+ other.close();
+
+ //check unacknowledged messages are still enqueued
+ other = fix.connection.createSession();
+ receiver = other.createReceiver(fix.queue);
+ for (uint i = 0; i < ((count-batch)/2); ++i) {
+ Message msg = receiver.fetch();
+ BOOST_CHECK_EQUAL(msg.getContent(), (boost::format("Message_%1%") % ((i*2)+1+batch)).str());
+ }
+ other.acknowledge();//acknowledge all messages
+ other.sync();
+ other.close();
+
+ Message m;
+ //check queue is empty
+ BOOST_CHECK(!fix.session.createReceiver(fix.queue).fetch(m, Duration::IMMEDIATE));
+}
+
+QPID_AUTO_TEST_CASE(testQmfCreateAndDelete)
+{
+ MessagingFixture fix(BrokerOptions(), true/*enable management*/);
+ MethodInvoker control(fix.session);
+ control.createQueue("my-queue");
+ control.createExchange("my-exchange", "topic");
+ control.bind("my-exchange", "my-queue", "subject1");
+
+ Sender sender = fix.session.createSender("my-exchange");
+ Receiver receiver = fix.session.createReceiver("my-queue");
+ Message out;
+ out.setSubject("subject1");
+ out.setContent("one");
+ sender.send(out);
+ Message in;
+ BOOST_CHECK(receiver.fetch(in, Duration::SECOND*5));
+ BOOST_CHECK_EQUAL(out.getContent(), in.getContent());
+ control.unbind("my-exchange", "my-queue", "subject1");
+ control.bind("my-exchange", "my-queue", "subject2");
+
+ out.setContent("two");
+ sender.send(out);//should be dropped
+
+ out.setSubject("subject2");
+ out.setContent("three");
+ sender.send(out);//should not be dropped
+
+ BOOST_CHECK(receiver.fetch(in, Duration::SECOND*5));
+ BOOST_CHECK_EQUAL(out.getContent(), in.getContent());
+ BOOST_CHECK(!receiver.fetch(in, Duration::IMMEDIATE));
+ sender.close();
+ receiver.close();
+
+ control.deleteExchange("my-exchange");
+ messaging::Session other = fix.connection.createSession();
+ {
+ ScopedSuppressLogging sl;
+ BOOST_CHECK_THROW(other.createSender("my-exchange"), qpid::messaging::NotFound);
+ }
+ control.deleteQueue("my-queue");
+ other = fix.connection.createSession();
+ {
+ ScopedSuppressLogging sl;
+ BOOST_CHECK_THROW(other.createReceiver("my-queue"), qpid::messaging::NotFound);
+ }
+}
+
+QPID_AUTO_TEST_CASE(testRejectAndCredit)
+{
+ //Ensure credit is restored on completing rejected messages
+ QueueFixture fix;
+ Sender sender = fix.session.createSender(fix.queue);
+ Receiver receiver = fix.session.createReceiver(fix.queue);
+
+ const uint count(10);
+ receiver.setCapacity(count);
+ for (uint i = 0; i < count; i++) {
+ sender.send(Message((boost::format("Message_%1%") % (i+1)).str()));
+ }
+
+ Message in;
+ for (uint i = 0; i < count; ++i) {
+ if (receiver.fetch(in, Duration::SECOND)) {
+ BOOST_CHECK_EQUAL(in.getContent(), (boost::format("Message_%1%") % (i+1)).str());
+ fix.session.reject(in);
+ } else {
+ BOOST_FAIL((boost::format("Message_%1% not received as expected") % (i+1)).str());
+ break;
+ }
+ }
+ //send another batch of messages
+ for (uint i = 0; i < count; i++) {
+ sender.send(Message((boost::format("Message_%1%") % (i+count)).str()));
+ }
+
+ for (uint i = 0; i < count; ++i) {
+ if (receiver.fetch(in, Duration::SECOND)) {
+ BOOST_CHECK_EQUAL(in.getContent(), (boost::format("Message_%1%") % (i+count)).str());
+ } else {
+ BOOST_FAIL((boost::format("Message_%1% not received as expected") % (i+count)).str());
+ break;
+ }
+ }
+ fix.session.acknowledge();
+ receiver.close();
+ sender.close();
+}
+
+QPID_AUTO_TEST_CASE(testTtlForever)
+{
+ QueueFixture fix;
+ Sender sender = fix.session.createSender(fix.queue);
+ Message out("I want to live forever!");
+ out.setTtl(Duration::FOREVER);
+ sender.send(out, true);
+ Receiver receiver = fix.session.createReceiver(fix.queue);
+ Message in = receiver.fetch(Duration::IMMEDIATE);
+ fix.session.acknowledge();
+ BOOST_CHECK_EQUAL(in.getContent(), out.getContent());
+ BOOST_CHECK(in.getTtl() == Duration::FOREVER);
+}
+
+QPID_AUTO_TEST_CASE(testExclusiveTopicSubscriber)
+{
+ TopicFixture fix;
+ std::string address = (boost::format("%1%; { link: { name: 'my-subscription', x-declare: { auto-delete: true, exclusive: true }}}") % fix.topic).str();
+ Sender sender = fix.session.createSender(fix.topic);
+ Receiver receiver1 = fix.session.createReceiver(address);
+ {
+ ScopedSuppressLogging sl;
+ try {
+ fix.session.createReceiver(address);
+ fix.session.sync();
+ BOOST_FAIL("Expected exception.");
+ } catch (const MessagingException& /*e*/) {}
+ }
+}
+
+QPID_AUTO_TEST_CASE(testNonExclusiveSubscriber)
+{
+ TopicFixture fix;
+ std::string address = (boost::format("%1%; {node:{type:topic}, link:{name:'my-subscription', x-declare:{auto-delete:true, exclusive:false}}}") % fix.topic).str();
+ Receiver receiver1 = fix.session.createReceiver(address);
+ Receiver receiver2 = fix.session.createReceiver(address);
+ Sender sender = fix.session.createSender(fix.topic);
+ sender.send(Message("one"), true);
+ Message in = receiver1.fetch(Duration::IMMEDIATE);
+ BOOST_CHECK_EQUAL(in.getContent(), std::string("one"));
+ sender.send(Message("two"), true);
+ in = receiver2.fetch(Duration::IMMEDIATE);
+ BOOST_CHECK_EQUAL(in.getContent(), std::string("two"));
+ fix.session.acknowledge();
+}
+
+QPID_AUTO_TEST_CASE(testAcknowledgeUpTo)
+{
+ QueueFixture fix;
+ Sender sender = fix.session.createSender(fix.queue);
+ const uint count(20);
+ for (uint i = 0; i < count; ++i) {
+ sender.send(Message((boost::format("Message_%1%") % (i+1)).str()));
+ }
+
+ Session other = fix.connection.createSession();
+ Receiver receiver = other.createReceiver(fix.queue);
+ std::vector<Message> messages;
+ for (uint i = 0; i < count; ++i) {
+ Message msg = receiver.fetch();
+ BOOST_CHECK_EQUAL(msg.getContent(), (boost::format("Message_%1%") % (i+1)).str());
+ messages.push_back(msg);
+ }
+ const uint batch = 10;
+ other.acknowledgeUpTo(messages[batch-1]);//acknowledge first 10 messages only
+
+ messages.clear();
+ other.sync();
+ other.close();
+
+ other = fix.connection.createSession();
+ receiver = other.createReceiver(fix.queue);
+ Message msg;
+ for (uint i = 0; i < (count-batch); ++i) {
+ msg = receiver.fetch();
+ BOOST_CHECK_EQUAL(msg.getContent(), (boost::format("Message_%1%") % (i+1+batch)).str());
+ }
+ other.acknowledgeUpTo(msg);
+ other.sync();
+ other.close();
+
+ Message m;
+ //check queue is empty
+ BOOST_CHECK(!fix.session.createReceiver(fix.queue).fetch(m, Duration::IMMEDIATE));
+}
+
+QPID_AUTO_TEST_CASE(testCreateBindingsOnStandardExchange)
+{
+ QueueFixture fix;
+ Sender sender = fix.session.createSender((boost::format("amq.direct; {create:always, node:{type:topic, x-bindings:[{queue:%1%, key:my-subject}]}}") % fix.queue).str());
+ Message out("test-message");
+ out.setSubject("my-subject");
+ sender.send(out);
+ Receiver receiver = fix.session.createReceiver(fix.queue);
+ Message in = receiver.fetch(Duration::SECOND * 5);
+ fix.session.acknowledge();
+ BOOST_CHECK_EQUAL(in.getContent(), out.getContent());
+ BOOST_CHECK_EQUAL(in.getSubject(), out.getSubject());
+}
+
+QPID_AUTO_TEST_CASE(testUnsubscribeOnClose)
+{
+ MessagingFixture fix;
+ Sender sender = fix.session.createSender("my-exchange/my-subject; {create: always, delete:sender, node:{type:topic, x-declare:{alternate-exchange:amq.fanout}}}");
+ Receiver receiver = fix.session.createReceiver("my-exchange/my-subject");
+ Receiver deadletters = fix.session.createReceiver("amq.fanout");
+
+ sender.send(Message("first"));
+ Message in = receiver.fetch(Duration::SECOND);
+ BOOST_CHECK_EQUAL(in.getContent(), std::string("first"));
+ fix.session.acknowledge();
+ receiver.close();
+ sender.send(Message("second"));
+ in = deadletters.fetch(Duration::SECOND);
+ BOOST_CHECK_EQUAL(in.getContent(), std::string("second"));
+ fix.session.acknowledge();
+}
+
+QPID_AUTO_TEST_CASE(testHeadersExchange)
+{
+ MessagingFixture fix;
+ //use both quoted and unquoted values
+ Receiver receiver = fix.session.createReceiver("amq.match; {link:{x-bindings:[{arguments:{x-match:all,qpid.subject:'abc',my-property:abc}}]}}");
+ Sender sender = fix.session.createSender("amq.match");
+ Message out("test-message");
+ out.setSubject("abc");
+ Variant& property = out.getProperties()["my-property"];
+ property = "abc";
+ property.setEncoding("utf8");
+ sender.send(out, true);
+ Message in;
+ if (receiver.fetch(in, Duration::SECOND)) {
+ fix.session.acknowledge();
+ BOOST_CHECK_EQUAL(in.getContent(), out.getContent());
+ } else {
+ BOOST_FAIL("Message did not match as expected!");
+ }
+}
+
+QPID_AUTO_TEST_CASE(testLargeRoutingKey)
+{
+ MessagingFixture fix;
+ std::string address = "amq.direct/" + std::string(300, 'x');//routing/binding key can be at most 225 chars in 0-10
+ BOOST_CHECK_THROW(fix.session.createReceiver(address), qpid::messaging::MessagingException);
+}
+
+QPID_AUTO_TEST_CASE(testAlternateExchangeInLinkDeclare)
+{
+ MessagingFixture fix;
+ Sender s = fix.session.createSender("amq.direct/key");
+ Receiver r1 = fix.session.createReceiver("amq.direct/key;{link:{x-declare:{alternate-exchange:'amq.fanout'}}}");
+ Receiver r2 = fix.session.createReceiver("amq.fanout");
+
+ for (uint i = 0; i < 10; ++i) {
+ s.send(Message((boost::format("Message_%1%") % (i+1)).str()), true);
+ }
+ r1.close();//orphans all messages in subscription queue, which should then be routed through alternate exchange
+ for (uint i = 0; i < 10; ++i) {
+ Message received;
+ BOOST_CHECK(r2.fetch(received, Duration::SECOND));
+ BOOST_CHECK_EQUAL(received.getContent(), (boost::format("Message_%1%") % (i+1)).str());
+ }
+}
+
+QPID_AUTO_TEST_CASE(testBrowseOnly)
+{
+ /* Set up a queue browse-only, and try to receive
+ the same messages twice with two different receivers.
+ This works because the browse-only queue does not
+ allow message acquisition. */
+
+ QueueFixture fix;
+ std::string addr = "q; {create:always, node:{type:queue, durable:false, x-declare:{arguments:{qpid.browse-only:1}}}}";
+ Sender sender = fix.session.createSender(addr);
+ Message out("test-message");
+
+ int count = 10;
+ for ( int i = 0; i < count; ++ i ) {
+ sender.send(out);
+ }
+
+ Message m;
+
+ Receiver receiver_1 = fix.session.createReceiver(addr);
+ for ( int i = 0; i < count; ++ i ) {
+ BOOST_CHECK(receiver_1.fetch(m, Duration::SECOND));
+ }
+
+ Receiver receiver_2 = fix.session.createReceiver(addr);
+ for ( int i = 0; i < count; ++ i ) {
+ BOOST_CHECK(receiver_2.fetch(m, Duration::SECOND));
+ }
+
+ fix.session.acknowledge();
+}
+
+QPID_AUTO_TEST_CASE(testLinkBindingCleanup)
+{
+ MessagingFixture fix;
+
+ Sender sender = fix.session.createSender("test.ex;{create:always,node:{type:topic}}");
+
+ Connection connection = fix.newConnection();
+ connection.open();
+
+ Session session(connection.createSession());
+ Receiver receiver1 = session.createReceiver("test.q;{create:always, node:{type:queue, x-bindings:[{exchange:test.ex,queue:test.q,key:#,arguments:{x-scope:session}}]}}");
+ Receiver receiver2 = fix.session.createReceiver("test.q;{create:never, delete:always}");
+ connection.close();
+
+ sender.send(Message("test-message"), true);
+
+ // The session-scoped binding should be removed when receiver1's network connection is lost
+ Message in;
+ BOOST_CHECK(!receiver2.fetch(in, Duration::IMMEDIATE));
+}
+
+namespace {
+struct Fetcher : public qpid::sys::Runnable {
+ Receiver receiver;
+ Message message;
+ bool result;
+ qpid::messaging::Duration timeout;
+ bool timedOut;
+
+ Fetcher(Receiver r) : receiver(r), result(false), timeout(Duration::SECOND*10), timedOut(false) {}
+ void run()
+ {
+ qpid::sys::AbsTime start(qpid::sys::now());
+ try {
+ result = receiver.fetch(message, timeout);
+ } catch (const MessagingException&) {}
+ qpid::sys::Duration timeTaken(start, qpid::sys::now());
+ timedOut = (uint64_t) timeTaken >= timeout.getMilliseconds() * qpid::sys::TIME_MSEC;
+ }
+};
+}
+
+QPID_AUTO_TEST_CASE(testConcurrentFetch)
+{
+ MessagingFixture fix;
+ Sender sender = fix.session.createSender("my-test-queue;{create:always, node : { x-declare : { auto-delete: true}}}");
+ Receiver receiver = fix.session.createReceiver("my-test-queue");
+ Fetcher fetcher(fix.session.createReceiver("amq.fanout"));
+ qpid::sys::Thread runner(fetcher);
+ Message out("test-message");
+ for (int i = 0; i < 10; i++) {//try several times to make sure
+ sender.send(out, true);
+ //since the message is now on the queue, it should take less than the timeout to actually fetch it
+ qpid::sys::AbsTime start = qpid::sys::AbsTime::now();
+ Message in;
+ BOOST_CHECK(receiver.fetch(in, qpid::messaging::Duration::SECOND*2));
+ qpid::sys::Duration time(start, qpid::sys::AbsTime::now());
+ BOOST_CHECK(time < qpid::sys::TIME_SEC*2);
+ if (time >= qpid::sys::TIME_SEC*2) break;//if we failed, no need to keep testing
+ }
+ fix.session.createSender("amq.fanout").send(out);
+ runner.join();
+ BOOST_CHECK(fetcher.result);
+}
+
+QPID_AUTO_TEST_CASE(testSimpleRequestResponse)
+{
+ QueueFixture fix;
+ //create receiver on temp queue for responses (using shorthand for temp queue)
+ Receiver r1 = fix.session.createReceiver("#");
+ //send request
+ Sender s1 = fix.session.createSender(fix.queue);
+ Message original("test-message");
+ original.setSubject("test-subject");
+ original.setReplyTo(r1.getAddress());
+ s1.send(original);
+
+ //receive request and send response
+ Receiver r2 = fix.session.createReceiver(fix.queue);
+ Message m = r2.fetch(Duration::SECOND * 5);
+ Sender s2 = fix.session.createSender(m.getReplyTo());
+ s2.send(m);
+ m = r1.fetch(Duration::SECOND * 5);
+ fix.session.acknowledge();
+ BOOST_CHECK_EQUAL(m.getContent(), original.getContent());
+ BOOST_CHECK_EQUAL(m.getSubject(), original.getSubject());
+}
+
+QPID_AUTO_TEST_CASE(testSelfDestructQueue)
+{
+ MessagingFixture fix;
+ Session other = fix.connection.createSession();
+ Receiver r1 = other.createReceiver("amq.fanout; {link:{reliability:at-least-once, x-declare:{arguments:{qpid.max_count:10,qpid.policy_type:self-destruct}}}}");
+ Receiver r2 = fix.session.createReceiver("amq.fanout");
+ //send request
+ Sender s = fix.session.createSender("amq.fanout");
+ for (uint i = 0; i < 20; ++i) {
+ s.send(Message((boost::format("MSG_%1%") % (i+1)).str()));
+ }
+ try {
+ ScopedSuppressLogging sl;
+ for (uint i = 0; i < 20; ++i) {
+ r1.fetch(Duration::SECOND);
+ }
+ BOOST_FAIL("Expected exception.");
+ } catch (const qpid::messaging::MessagingException&) {
+ }
+
+ for (uint i = 0; i < 20; ++i) {
+ BOOST_CHECK_EQUAL(r2.fetch(Duration::SECOND).getContent(), (boost::format("MSG_%1%") % (i+1)).str());
+ }
+}
+
+QPID_AUTO_TEST_CASE(testReroutingRingQueue)
+{
+ MessagingFixture fix;
+ Receiver r1 = fix.session.createReceiver("my-queue; {create:always, node:{x-declare:{alternate-exchange:amq.fanout, auto-delete:True, arguments:{qpid.max_count:10,qpid.policy_type:ring}}}}");
+ Receiver r2 = fix.session.createReceiver("amq.fanout");
+
+ Sender s = fix.session.createSender("my-queue");
+ for (uint i = 0; i < 20; ++i) {
+ s.send(Message((boost::format("MSG_%1%") % (i+1)).str()));
+ }
+ for (uint i = 10; i < 20; ++i) {
+ BOOST_CHECK_EQUAL(r1.fetch(Duration::SECOND).getContent(), (boost::format("MSG_%1%") % (i+1)).str());
+ }
+ for (uint i = 0; i < 10; ++i) {
+ BOOST_CHECK_EQUAL(r2.fetch(Duration::SECOND).getContent(), (boost::format("MSG_%1%") % (i+1)).str());
+ }
+}
+
+QPID_AUTO_TEST_CASE(testReleaseOnPriorityQueue)
+{
+ MessagingFixture fix;
+ std::string queue("queue; {create:always, node:{x-declare:{auto-delete:True, arguments:{qpid.priorities:10}}}}");
+ std::string text("my message");
+ Sender sender = fix.session.createSender(queue);
+ sender.send(Message(text));
+ Receiver receiver = fix.session.createReceiver(queue);
+ Message msg;
+ for (uint i = 0; i < 10; ++i) {
+ if (receiver.fetch(msg, Duration::SECOND)) {
+ BOOST_CHECK_EQUAL(msg.getContent(), text);
+ fix.session.release(msg);
+ } else {
+ BOOST_FAIL("Released message not redelivered as expected.");
+ }
+ }
+ fix.session.acknowledge();
+}
+
+QPID_AUTO_TEST_CASE(testRollbackWithFullPrefetch)
+{
+ QueueFixture fix;
+ std::string first("first");
+ std::string second("second");
+ Sender sender = fix.session.createSender(fix.queue);
+ for (uint i = 0; i < 10; ++i) {
+ sender.send(Message((boost::format("MSG_%1%") % (i+1)).str()));
+ }
+ Session txsession = fix.connection.createTransactionalSession();
+ Receiver receiver = txsession.createReceiver(fix.queue);
+ receiver.setCapacity(9);
+ Message msg;
+ for (uint i = 0; i < 10; ++i) {
+ if (receiver.fetch(msg, Duration::SECOND)) {
+ BOOST_CHECK_EQUAL(msg.getContent(), std::string("MSG_1"));
+ txsession.rollback();
+ } else {
+ BOOST_FAIL("Released message not redelivered as expected.");
+ break;
+ }
+ }
+ txsession.acknowledge();
+ txsession.commit();
+}
+
+QPID_AUTO_TEST_CASE(testCloseAndConcurrentFetch)
+{
+ QueueFixture fix;
+ Receiver receiver = fix.session.createReceiver(fix.queue);
+ Fetcher fetcher(receiver);
+ qpid::sys::Thread runner(fetcher);
+ qpid::sys::usleep(500);
+ receiver.close();
+ runner.join();
+ BOOST_CHECK(!fetcher.timedOut);
+}
+
+QPID_AUTO_TEST_CASE(testCloseAndMultipleConcurrentFetches)
+{
+ QueueFixture fix;
+ Receiver receiver = fix.session.createReceiver(fix.queue);
+ Receiver receiver2 = fix.session.createReceiver("amq.fanout");
+ Receiver receiver3 = fix.session.createReceiver("amq.fanout");
+ Fetcher fetcher(receiver);
+ Fetcher fetcher2(receiver2);
+ Fetcher fetcher3(receiver3);
+ qpid::sys::Thread runner(fetcher);
+ qpid::sys::Thread runner2(fetcher2);
+ qpid::sys::Thread runner3(fetcher3);
+ qpid::sys::usleep(500);
+ receiver.close();
+ Message message("Test");
+ fix.session.createSender("amq.fanout").send(message);
+ runner2.join();
+ BOOST_CHECK(fetcher2.result);
+ BOOST_CHECK_EQUAL(fetcher2.message.getContent(), message.getContent());
+ runner3.join();
+ BOOST_CHECK(fetcher3.result);
+ BOOST_CHECK_EQUAL(fetcher3.message.getContent(), message.getContent());
+ runner.join();
+ BOOST_CHECK(!fetcher.timedOut);
+}
+
+QPID_AUTO_TEST_CASE(testSessionCheckError)
+{
+ MessagingFixture fix;
+ Session session = fix.connection.createSession();
+ Sender sender = session.createSender("q; {create:always, node:{x-declare:{auto-delete:True, arguments:{qpid.max_count:1}}}}");
+ ScopedSuppressLogging sl;
+ for (uint i = 0; i < 2; ++i) {
+ sender.send(Message((boost::format("A_%1%") % (i+1)).str()));
+ }
+ try {
+ while (true) session.checkError();
+ } catch (const qpid::types::Exception&) {
+ //this is ok
+ } catch (const qpid::Exception&) {
+ BOOST_FAIL("Wrong exception type thrown");
+ }
+}
+
+QPID_AUTO_TEST_CASE(testImmediateNextReceiver)
+{
+ QueueFixture fix;
+ Sender sender = fix.session.createSender(fix.queue);
+ Message out("test message");
+ sender.send(out);
+ fix.session.createReceiver(fix.queue).setCapacity(1);
+ Receiver next;
+ qpid::sys::AbsTime start = qpid::sys::now();
+ try {
+ while (!fix.session.nextReceiver(next, qpid::messaging::Duration::IMMEDIATE)) {
+ qpid::sys::Duration running(start, qpid::sys::now());
+ if (running > 5*qpid::sys::TIME_SEC) {
+ throw qpid::types::Exception("Timed out spinning on nextReceiver(IMMEDIATE)");
+ }
+ qpid::sys::usleep(1); // for valgrind
+ }
+ Message in;
+ BOOST_CHECK(next.fetch(in, qpid::messaging::Duration::IMMEDIATE));
+ BOOST_CHECK_EQUAL(in.getContent(), out.getContent());
+ next.close();
+ } catch (const std::exception& e) {
+ BOOST_FAIL(e.what());
+ }
+}
+
+QPID_AUTO_TEST_CASE(testImmediateNextReceiverNoMessage)
+{
+ QueueFixture fix;
+ Receiver r = fix.session.createReceiver(fix.queue);
+ r.setCapacity(1);
+ Receiver next;
+ try {
+ BOOST_CHECK(!fix.session.nextReceiver(next, qpid::messaging::Duration::IMMEDIATE));
+ r.close();
+ } catch (const std::exception& e) {
+ BOOST_FAIL(e.what());
+ }
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/MessagingThreadTests.cpp b/qpid/cpp/src/tests/MessagingThreadTests.cpp
new file mode 100644
index 0000000000..48264735b1
--- /dev/null
+++ b/qpid/cpp/src/tests/MessagingThreadTests.cpp
@@ -0,0 +1,144 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "MessagingFixture.h"
+#include "qpid/sys/Runnable.h"
+#include "qpid/sys/Thread.h"
+#include <boost/lexical_cast.hpp>
+
+namespace qpid {
+namespace tests {
+QPID_AUTO_TEST_SUITE(MessagingThreadTests)
+
+using namespace messaging;
+using namespace boost::assign;
+using namespace std;
+
+struct ReceiveThread : public sys::Runnable {
+ Receiver receiver;
+ vector<string> received;
+ string error;
+
+ ReceiveThread(Receiver s) : receiver(s) {}
+ void run() {
+ try {
+ while(true) {
+ Message m = receiver.fetch(Duration::SECOND*5);
+ if (m.getContent() == "END") break;
+ received.push_back(m.getContent());
+ }
+ } catch (const NoMessageAvailable& e) {
+ // Indicates that fetch timed out OR receiver was closed by other thread.
+ if (!receiver.isClosed()) // timeout
+ error = e.what();
+ } catch (const std::exception& e) {
+ error = e.what();
+ }
+ }
+};
+
+struct NextReceiverThread : public sys::Runnable {
+ Session session;
+ vector<string> received;
+ string error;
+
+ NextReceiverThread(Session s) : session(s) {}
+ void run() {
+ try {
+ while(true) {
+ Message m = session.nextReceiver(Duration::SECOND*5).fetch();
+ if (m.getContent() == "END") break;
+ received.push_back(m.getContent());
+ }
+ } catch (const std::exception& e) {
+ error = e.what();
+ }
+ }
+};
+
+
+QPID_AUTO_TEST_CASE(testConcurrentSendReceive) {
+ MessagingFixture fix;
+ Sender s = fix.session.createSender("concurrent;{create:always}");
+ Receiver r = fix.session.createReceiver("concurrent;{create:always,link:{reliability:unreliable}}");
+ ReceiveThread rt(r);
+ sys::Thread thread(rt);
+ const size_t COUNT=100;
+ for (size_t i = 0; i < COUNT; ++i) {
+ s.send(Message());
+ }
+ s.send(Message("END"));
+ thread.join();
+ BOOST_CHECK_EQUAL(rt.error, string());
+ BOOST_CHECK_EQUAL(COUNT, rt.received.size());
+}
+
+QPID_AUTO_TEST_CASE(testCloseBusyReceiver) {
+ MessagingFixture fix;
+ Receiver r = fix.session.createReceiver("closeReceiver;{create:always}");
+ ReceiveThread rt(r);
+ sys::Thread thread(rt);
+ sys::usleep(1000); // Give the receive thread time to block.
+ r.close();
+ thread.join();
+ BOOST_CHECK_EQUAL(rt.error, string());
+
+ // Fetching on closed receiver should fail.
+ Message m;
+ BOOST_CHECK(!r.fetch(m, Duration(0)));
+ BOOST_CHECK_THROW(r.fetch(Duration(0)), NoMessageAvailable);
+}
+
+QPID_AUTO_TEST_CASE(testCloseSessionBusyReceiver) {
+ MessagingFixture fix;
+ Receiver r = fix.session.createReceiver("closeSession;{create:always}");
+ ReceiveThread rt(r);
+ sys::Thread thread(rt);
+ sys::usleep(1000); // Give the receive thread time to block.
+ fix.session.close();
+ thread.join();
+ BOOST_CHECK_EQUAL(rt.error, string());
+
+ // Fetching on closed receiver should fail.
+ Message m;
+ BOOST_CHECK(!r.fetch(m, Duration(0)));
+ BOOST_CHECK_THROW(r.fetch(Duration(0)), NoMessageAvailable);
+}
+
+QPID_AUTO_TEST_CASE(testConcurrentSendNextReceiver) {
+ MessagingFixture fix;
+ Receiver r = fix.session.createReceiver("concurrent;{create:always,link:{reliability:unreliable}}");
+ const size_t COUNT=100;
+ r.setCapacity(COUNT);
+ NextReceiverThread rt(fix.session);
+ sys::Thread thread(rt);
+ sys::usleep(1000); // Give the receive thread time to block.
+ Sender s = fix.session.createSender("concurrent;{create:always}");
+ for (size_t i = 0; i < COUNT; ++i) {
+ s.send(Message());
+ }
+ s.send(Message("END"));
+ thread.join();
+ BOOST_CHECK_EQUAL(rt.error, string());
+ BOOST_CHECK_EQUAL(COUNT, rt.received.size());
+}
+
+QPID_AUTO_TEST_SUITE_END()
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/PollableCondition.cpp b/qpid/cpp/src/tests/PollableCondition.cpp
new file mode 100644
index 0000000000..f9b3c25c93
--- /dev/null
+++ b/qpid/cpp/src/tests/PollableCondition.cpp
@@ -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.
+ *
+ */
+
+#include "test_tools.h"
+#include "unit_test.h"
+#include "qpid/sys/Poller.h"
+#include "qpid/sys/PollableCondition.h"
+#include "qpid/sys/Monitor.h"
+#include "qpid/sys/Time.h"
+#include "qpid/sys/Thread.h"
+#include <boost/bind.hpp>
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(PollableConditionTest)
+
+using namespace qpid::sys;
+
+const Duration SHORT = TIME_SEC/100;
+const Duration LONG = TIME_SEC/10;
+
+class Callback {
+ public:
+ enum Action { NONE, CLEAR };
+
+ Callback() : count(), action(NONE) {}
+
+ void call(PollableCondition& pc) {
+ Mutex::ScopedLock l(lock);
+ ++count;
+ switch(action) {
+ case NONE: break;
+ case CLEAR: pc.clear(); break;
+ }
+ action = NONE;
+ lock.notify();
+ }
+
+ bool isCalling() { Mutex::ScopedLock l(lock); return wait(LONG); }
+
+ bool isNotCalling() { Mutex::ScopedLock l(lock); return !wait(SHORT); }
+
+ bool nextCall(Action a=NONE) {
+ Mutex::ScopedLock l(lock);
+ action = a;
+ return wait(LONG);
+ }
+
+ private:
+ bool wait(Duration timeout) {
+ int n = count;
+ AbsTime deadline(now(), timeout);
+ while (n == count && lock.wait(deadline))
+ ;
+ return n != count;
+ }
+
+ Monitor lock;
+ int count;
+ Action action;
+};
+
+QPID_AUTO_TEST_CASE(testPollableCondition) {
+ boost::shared_ptr<Poller> poller(new Poller());
+ Callback callback;
+ PollableCondition pc(boost::bind(&Callback::call, &callback, _1), poller);
+
+ Thread runner = Thread(*poller);
+
+ BOOST_CHECK(callback.isNotCalling()); // condition is not set.
+
+ pc.set();
+ BOOST_CHECK(callback.isCalling()); // Set.
+ BOOST_CHECK(callback.isCalling()); // Still set.
+
+ callback.nextCall(Callback::CLEAR);
+ BOOST_CHECK(callback.isNotCalling()); // Cleared
+
+ pc.set();
+ BOOST_CHECK(callback.isCalling()); // Set.
+ callback.nextCall(Callback::CLEAR);
+ BOOST_CHECK(callback.isNotCalling()); // Cleared.
+
+ poller->shutdown();
+ runner.join();
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} //namespace qpid::tests
diff --git a/qpid/cpp/src/tests/PollerTest.cpp b/qpid/cpp/src/tests/PollerTest.cpp
new file mode 100644
index 0000000000..5a1d02964c
--- /dev/null
+++ b/qpid/cpp/src/tests/PollerTest.cpp
@@ -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.
+ *
+ */
+
+/**
+ * Use socketpair to test the poller
+ */
+
+#include "qpid/sys/Poller.h"
+#include "qpid/sys/posix/PrivatePosix.h"
+
+#include <string>
+#include <iostream>
+#include <memory>
+#include <exception>
+
+#include <assert.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+using namespace std;
+using namespace qpid::sys;
+
+int writeALot(int fd, const string& s) {
+ int bytesWritten = 0;
+ do {
+ errno = 0;
+ int lastWrite = ::write(fd, s.c_str(), s.size());
+ if ( lastWrite >= 0) {
+ bytesWritten += lastWrite;
+ }
+ } while (errno != EAGAIN);
+ return bytesWritten;
+}
+
+int readALot(int fd) {
+ int bytesRead = 0;
+ char buf[1024];
+
+ do {
+ errno = 0;
+ int lastRead = ::read(fd, buf, sizeof(buf));
+ if ( lastRead >= 0) {
+ bytesRead += lastRead;
+ }
+ } while (errno != EAGAIN);
+ return bytesRead;
+}
+
+void makesocketpair(int (&sv)[2]) {
+ int rc = ::socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
+ assert(rc >= 0);
+
+ // Set non-blocking
+ rc = ::fcntl(sv[0], F_SETFL, O_NONBLOCK);
+ assert(rc >= 0);
+
+ rc = ::fcntl(sv[1], F_SETFL, O_NONBLOCK);
+ assert(rc >= 0);
+}
+
+int main(int /*argc*/, char** /*argv*/)
+{
+ try
+ {
+ int sv[2];
+ makesocketpair(sv);
+
+ // Make up a large string
+ string testString = "This is only a test ... 1,2,3,4,5,6,7,8,9,10;";
+ for (int i = 0; i < 6; i++)
+ testString += testString;
+
+ // Read as much as we can from socket 0
+ int bytesRead = readALot(sv[0]);
+ assert(bytesRead == 0);
+
+ // Write as much as we can to socket 0
+ int bytesWritten = writeALot(sv[0], testString);
+
+ // Read as much as we can from socket 1
+ bytesRead = readALot(sv[1]);
+ assert(bytesRead == bytesWritten);
+
+ auto_ptr<Poller> poller(new Poller);
+
+ IOHandle f0(sv[0]);
+ IOHandle f1(sv[1]);
+
+ PollerHandle h0(f0);
+ PollerHandle h1(f1);
+
+ poller->registerHandle(h0);
+ poller->monitorHandle(h0, Poller::INOUT);
+
+ // h0 should be writable
+ Poller::Event event = poller->wait();
+ assert(event.handle == &h0);
+ assert(event.type == Poller::WRITABLE);
+
+ // Write as much as we can to socket 0
+ bytesWritten = writeALot(sv[0], testString);
+
+ // Wait for 500ms - h0 no longer writable
+ event = poller->wait(500000000);
+ assert(event.handle == 0);
+
+ // Test we can read it all now
+ poller->registerHandle(h1);
+ poller->monitorHandle(h1, Poller::INOUT);
+ event = poller->wait();
+ assert(event.handle == &h1);
+ assert(event.type == Poller::READ_WRITABLE);
+
+ bytesRead = readALot(sv[1]);
+ assert(bytesRead == bytesWritten);
+
+ // Test poller interrupt
+ assert(poller->interrupt(h0) == true);
+ event = poller->wait();
+ assert(event.handle == &h0);
+ assert(event.type == Poller::INTERRUPTED);
+
+ // Test multiple interrupts
+ assert(poller->interrupt(h0) == true);
+ assert(poller->interrupt(h1) == true);
+
+ // Make sure we can interrupt them again
+ assert(poller->interrupt(h0) == true);
+ assert(poller->interrupt(h1) == true);
+
+ // Make sure that they both come out
+ event = poller->wait();
+ assert(event.type == Poller::INTERRUPTED);
+ assert(event.handle == &h0 || event.handle == &h1);
+ if (event.handle == &h0) {
+ event = poller->wait();
+ assert(event.type == Poller::INTERRUPTED);
+ assert(event.handle == &h1);
+ } else {
+ event = poller->wait();
+ assert(event.type == Poller::INTERRUPTED);
+ assert(event.handle == &h0);
+ }
+
+ poller->unmonitorHandle(h1, Poller::INOUT);
+
+ event = poller->wait();
+ assert(event.handle == &h0);
+ assert(event.type == Poller::WRITABLE);
+
+ // We didn't write anything so it should still be writable
+ event = poller->wait();
+ assert(event.handle == &h0);
+ assert(event.type == Poller::WRITABLE);
+
+ poller->unmonitorHandle(h0, Poller::INOUT);
+
+ event = poller->wait(500000000);
+ assert(event.handle == 0);
+
+ poller->unregisterHandle(h1);
+ assert(poller->interrupt(h1) == false);
+
+ // close the other end to force a disconnect
+ ::close(sv[1]);
+
+ // Now make sure that we are readable followed by disconnected
+ // and after that we never return again
+ poller->monitorHandle(h0, Poller::INOUT);
+ event = poller->wait(500000000);
+ assert(event.handle == &h0);
+ assert(event.type == Poller::READABLE);
+ event = poller->wait(500000000);
+ assert(event.handle == &h0);
+ assert(event.type == Poller::DISCONNECTED);
+ event = poller->wait(1500000000);
+ assert(event.handle == 0);
+
+ // Now we're disconnected monitoring should have no effect at all
+ poller->unmonitorHandle(h0, Poller::INOUT);
+ event = poller->wait(1500000000);
+ assert(event.handle == 0);
+
+ poller->unregisterHandle(h0);
+ assert(poller->interrupt(h0) == false);
+
+ // Test shutdown
+ poller->shutdown();
+ event = poller->wait();
+ assert(event.handle == 0);
+ assert(event.type == Poller::SHUTDOWN);
+
+ event = poller->wait();
+ assert(event.handle == 0);
+ assert(event.type == Poller::SHUTDOWN);
+
+ ::close(sv[0]);
+
+ // Test for correct interaction of shutdown and interrupts - need to have new poller
+ // etc. for this
+ makesocketpair(sv);
+
+ auto_ptr<Poller> poller1(new Poller);
+
+ IOHandle f2(sv[0]);
+ IOHandle f3(sv[1]);
+
+ PollerHandle h2(f2);
+ PollerHandle h3(f3);
+
+ poller1->registerHandle(h2);
+ poller1->monitorHandle(h2, Poller::INOUT);
+ event = poller1->wait();
+ assert(event.handle == &h2);
+ assert(event.type == Poller::WRITABLE);
+
+ // Shutdown
+ poller1->shutdown();
+ event = poller1->wait();
+ assert(event.handle == 0);
+ assert(event.type == Poller::SHUTDOWN);
+
+ assert(poller1->interrupt(h2) == true);
+ event = poller1->wait();
+ assert(event.handle == &h2);
+ assert(event.type == Poller::INTERRUPTED);
+ poller1->unmonitorHandle(h2, Poller::INOUT);
+
+ event = poller1->wait();
+ assert(event.handle == 0);
+ assert(event.type == Poller::SHUTDOWN);
+
+ poller1->unregisterHandle(h2);
+ return 0;
+ } catch (exception& e) {
+ cout << "Caught exception " << e.what() << "\n";
+ }
+}
+
+
diff --git a/qpid/cpp/src/tests/ProxyTest.cpp b/qpid/cpp/src/tests/ProxyTest.cpp
new file mode 100644
index 0000000000..a926b28395
--- /dev/null
+++ b/qpid/cpp/src/tests/ProxyTest.cpp
@@ -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.
+ *
+ */
+#include <iostream>
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/AMQMethodBody.h"
+#include "qpid/framing/ExecutionSyncBody.h"
+#include "qpid/framing/Proxy.h"
+
+#include "unit_test.h"
+
+using namespace qpid::framing;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(ProxyTestSuite)
+
+
+QPID_AUTO_TEST_CASE(testScopedSync)
+{
+ struct DummyHandler : FrameHandler
+ {
+ void handle(AMQFrame& f) {
+ AMQMethodBody* m = f.getMethod();
+ BOOST_CHECK(m);
+ BOOST_CHECK(m->isA<ExecutionSyncBody>());
+ BOOST_CHECK(m->isSync());
+ }
+ };
+ DummyHandler f;
+ Proxy p(f);
+ Proxy::ScopedSync s(p);
+ p.send(ExecutionSyncBody(p.getVersion()));
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/Qmf2.cpp b/qpid/cpp/src/tests/Qmf2.cpp
new file mode 100644
index 0000000000..bc263d5c6d
--- /dev/null
+++ b/qpid/cpp/src/tests/Qmf2.cpp
@@ -0,0 +1,422 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <iostream>
+#include "qpid/types/Variant.h"
+#include "qmf/QueryImpl.h"
+#include "qmf/SchemaImpl.h"
+#include "qmf/exceptions.h"
+#include "qpid/messaging/Connection.h"
+#include "qmf/PosixEventNotifierImpl.h"
+#include "qmf/AgentSession.h"
+#include "qmf/AgentSessionImpl.h"
+#include "qmf/ConsoleSession.h"
+#include "qmf/ConsoleSessionImpl.h"
+#include "unit_test.h"
+
+using namespace std;
+using namespace qpid::types;
+using namespace qpid::messaging;
+using namespace qmf;
+
+bool isReadable(int fd)
+{
+ fd_set rfds;
+ struct timeval tv;
+ int nfds, result;
+
+ FD_ZERO(&rfds);
+ FD_SET(fd, &rfds);
+ nfds = fd + 1;
+ tv.tv_sec = 0;
+ tv.tv_usec = 0;
+
+ result = select(nfds, &rfds, NULL, NULL, &tv);
+
+ return result > 0;
+}
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(Qmf2Suite)
+
+QPID_AUTO_TEST_CASE(testQuery)
+{
+ Query query(QUERY_OBJECT, "class_name", "package_name", "[and, [eq, name, [quote, smith]], [lt, age, [quote, 27]]]");
+ Query newQuery(new QueryImpl(QueryImplAccess::get(query).asMap()));
+
+ BOOST_CHECK_EQUAL(newQuery.getTarget(), QUERY_OBJECT);
+ BOOST_CHECK_EQUAL(newQuery.getSchemaId().getName(), "class_name");
+ BOOST_CHECK_EQUAL(newQuery.getSchemaId().getPackageName(), "package_name");
+
+ Variant::List pred(newQuery.getPredicate());
+ BOOST_CHECK_EQUAL(pred.size(), size_t(3));
+
+ Variant::List::iterator iter(pred.begin());
+ BOOST_CHECK_EQUAL(iter->asString(), "and");
+ iter++;
+ BOOST_CHECK_EQUAL(iter->getType(), VAR_LIST);
+ iter++;
+ BOOST_CHECK_EQUAL(iter->getType(), VAR_LIST);
+ iter = iter->asList().begin();
+ BOOST_CHECK_EQUAL(iter->asString(), "lt");
+ iter++;
+ BOOST_CHECK_EQUAL(iter->asString(), "age");
+ iter++;
+ BOOST_CHECK_EQUAL(iter->getType(), VAR_LIST);
+ iter = iter->asList().begin();
+ BOOST_CHECK_EQUAL(iter->asString(), "quote");
+ iter++;
+ BOOST_CHECK_EQUAL(iter->asUint32(), uint32_t(27));
+
+ Query query2(QUERY_OBJECT_ID);
+ Query newQuery2(new QueryImpl(QueryImplAccess::get(query2).asMap()));
+ BOOST_CHECK_EQUAL(newQuery2.getTarget(), QUERY_OBJECT_ID);
+
+ Query query3(QUERY_SCHEMA);
+ Query newQuery3(new QueryImpl(QueryImplAccess::get(query3).asMap()));
+ BOOST_CHECK_EQUAL(newQuery3.getTarget(), QUERY_SCHEMA);
+
+ Query query4(QUERY_SCHEMA_ID);
+ Query newQuery4(new QueryImpl(QueryImplAccess::get(query4).asMap()));
+ BOOST_CHECK_EQUAL(newQuery4.getTarget(), QUERY_SCHEMA_ID);
+
+ DataAddr addr("name", "agent_name", 34);
+ Query query5(addr);
+ Query newQuery5(new QueryImpl(QueryImplAccess::get(query5).asMap()));
+ BOOST_CHECK_EQUAL(newQuery5.getTarget(), QUERY_OBJECT);
+ BOOST_CHECK_EQUAL(newQuery5.getDataAddr().getName(), "name");
+ BOOST_CHECK_EQUAL(newQuery5.getDataAddr().getAgentName(), "agent_name");
+ BOOST_CHECK_EQUAL(newQuery5.getDataAddr().getAgentEpoch(), uint32_t(34));
+}
+
+QPID_AUTO_TEST_CASE(testQueryPredicateErrors)
+{
+ Query query;
+ Variant::Map map;
+
+ BOOST_CHECK_THROW(Query(QUERY_OBJECT, "INVALID"), QmfException);
+ query = Query(QUERY_OBJECT, "[unknown, one, two]");
+ BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException);
+
+ query = Query(QUERY_OBJECT, "[eq, first]");
+ BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException);
+
+ query = Query(QUERY_OBJECT, "[exists]");
+ BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException);
+
+ query = Query(QUERY_OBJECT, "[eq, first, [quote, 1, 2, 3]]]");
+ BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException);
+
+ query = Query(QUERY_OBJECT, "[eq, first, [unexpected, 3]]]");
+ BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException);
+
+ query = Query(QUERY_OBJECT, "[eq, first, {}]]");
+ BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException);
+
+ query = Query(QUERY_OBJECT, "[eq, first, second, third]");
+ BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException);
+
+ query = Query(QUERY_OBJECT, "[and, first, second, third]");
+ BOOST_CHECK_THROW(query.matchesPredicate(map), QmfException);
+}
+
+QPID_AUTO_TEST_CASE(testQueryPredicate)
+{
+ Query query;
+ Variant::Map map;
+
+ map["forty"] = 40;
+ map["fifty"] = 50;
+ map["minus_ten"] = -10;
+ map["pos_float"] = 100.05;
+ map["neg_float"] = -1000.33;
+ map["name"] = "jones";
+ map["bool_t"] = true;
+ map["bool_f"] = false;
+
+ BOOST_CHECK_THROW(Query(QUERY_OBJECT, "INVALID"), QmfException);
+
+ query = Query(QUERY_OBJECT);
+ BOOST_CHECK_EQUAL(query.matchesPredicate(Variant::Map()), true);
+
+ query = Query(QUERY_OBJECT, "[eq, forty, [quote, 40]]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), true);
+
+ query = Query(QUERY_OBJECT, "[eq, forty, [quote, 41]]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), false);
+
+ query = Query(QUERY_OBJECT, "[le, forty, fifty]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), true);
+
+ query = Query(QUERY_OBJECT, "[and, [eq, forty, [quote, 40]], [eq, name, [quote, jones]]]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), true);
+
+ query = Query(QUERY_OBJECT, "[and, [eq, forty, [quote, 40]], [eq, name, [quote, smith]]]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), false);
+
+ query = Query(QUERY_OBJECT, "[or, [eq, forty, [quote, 40]], [eq, name, [quote, smith]]]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), true);
+
+ query = Query(QUERY_OBJECT, "[or, [eq, forty, [quote, 41]], [eq, name, [quote, smith]]]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), false);
+
+ query = Query(QUERY_OBJECT, "[not, [le, forty, [quote, 40]]]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), false);
+
+ query = Query(QUERY_OBJECT, "[le, forty, [quote, 40]]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), true);
+
+ query = Query(QUERY_OBJECT, "[ge, forty, [quote, 40]]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), true);
+
+ query = Query(QUERY_OBJECT, "[lt, forty, [quote, 45]]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), true);
+
+ query = Query(QUERY_OBJECT, "[lt, [quote, 45], forty]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), false);
+
+ query = Query(QUERY_OBJECT, "[gt, forty, [quote, 45]]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), false);
+
+ query = Query(QUERY_OBJECT, "[gt, [quote, 45], forty]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), true);
+
+ query = Query(QUERY_OBJECT, "[eq, bool_t, [quote, True]]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), true);
+
+ query = Query(QUERY_OBJECT, "[eq, bool_t, [quote, False]]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), false);
+
+ query = Query(QUERY_OBJECT, "[eq, bool_f, [quote, True]]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), false);
+
+ query = Query(QUERY_OBJECT, "[eq, bool_f, [quote, False]]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), true);
+
+ query = Query(QUERY_OBJECT, "[eq, minus_ten, [quote, -10]]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), true);
+
+ query = Query(QUERY_OBJECT, "[lt, minus_ten, [quote, -20]]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), false);
+
+ query = Query(QUERY_OBJECT, "[lt, [quote, -20], minus_ten]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), true);
+
+ query = Query(QUERY_OBJECT, "[exists, name]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), true);
+
+ query = Query(QUERY_OBJECT, "[exists, nonexfield]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), false);
+
+ query = Query(QUERY_OBJECT, "[eq, pos_float, [quote, 100.05]]");
+ BOOST_CHECK_EQUAL(query.matchesPredicate(map), true);
+}
+
+QPID_AUTO_TEST_CASE(testSchema)
+{
+ Schema in(SCHEMA_TYPE_DATA, "package", "class");
+ in.addProperty(SchemaProperty("prop1", SCHEMA_DATA_BOOL, "{desc:'Property One'}"));
+ in.addProperty(SchemaProperty("prop2", SCHEMA_DATA_INT, "{desc:'Property Two',unit:'Furlong'}"));
+ in.addProperty(SchemaProperty("prop3", SCHEMA_DATA_STRING, "{desc:'Property Three'}"));
+
+ SchemaMethod method1("method1", "{desc:'Method One'}");
+ method1.addArgument(SchemaProperty("arg1", SCHEMA_DATA_BOOL, "{desc:'Argument One',dir:IN}"));
+ method1.addArgument(SchemaProperty("arg2", SCHEMA_DATA_INT, "{desc:'Argument Two',dir:OUT}"));
+ method1.addArgument(SchemaProperty("arg3", SCHEMA_DATA_FLOAT, "{desc:'Argument Three',dir:INOUT}"));
+ in.addMethod(method1);
+
+ SchemaMethod method2("method2", "{desc:'Method Two'}");
+ method2.addArgument(SchemaProperty("arg21", SCHEMA_DATA_BOOL, "{desc:'Argument One',dir:IN}"));
+ method2.addArgument(SchemaProperty("arg22", SCHEMA_DATA_INT, "{desc:'Argument Two',dir:OUT}"));
+ method2.addArgument(SchemaProperty("arg23", SCHEMA_DATA_FLOAT, "{desc:'Argument Three',dir:INOUT}"));
+ in.addMethod(method2);
+
+ BOOST_CHECK(!in.isFinalized());
+ in.finalize();
+ BOOST_CHECK(in.isFinalized());
+
+ Variant::Map map(SchemaImplAccess::get(in).asMap());
+ Schema out(new SchemaImpl(map));
+
+ BOOST_CHECK(out.isFinalized());
+ BOOST_CHECK_EQUAL(out.getSchemaId().getType(), SCHEMA_TYPE_DATA);
+ BOOST_CHECK_EQUAL(out.getSchemaId().getPackageName(), "package");
+ BOOST_CHECK_EQUAL(out.getSchemaId().getName(), "class");
+ BOOST_CHECK_EQUAL(out.getSchemaId().getHash(), in.getSchemaId().getHash());
+
+ BOOST_CHECK_EQUAL(out.getPropertyCount(), uint32_t(3));
+ SchemaProperty prop;
+
+ prop = out.getProperty(0);
+ BOOST_CHECK_EQUAL(prop.getName(), "prop1");
+ BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_BOOL);
+ BOOST_CHECK_EQUAL(prop.getDesc(), "Property One");
+
+ prop = out.getProperty(1);
+ BOOST_CHECK_EQUAL(prop.getName(), "prop2");
+ BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_INT);
+ BOOST_CHECK_EQUAL(prop.getDesc(), "Property Two");
+ BOOST_CHECK_EQUAL(prop.getUnit(), "Furlong");
+ BOOST_CHECK(!prop.isIndex());
+
+ prop = out.getProperty(2);
+ BOOST_CHECK_EQUAL(prop.getName(), "prop3");
+ BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_STRING);
+ BOOST_CHECK_EQUAL(prop.getDesc(), "Property Three");
+
+ BOOST_CHECK_THROW(out.getProperty(3), QmfException);
+
+ BOOST_CHECK_EQUAL(out.getMethodCount(), uint32_t(2));
+ SchemaMethod method;
+
+ method = out.getMethod(0);
+ BOOST_CHECK_EQUAL(method.getName(), "method1");
+ BOOST_CHECK_EQUAL(method.getDesc(), "Method One");
+ BOOST_CHECK_EQUAL(method.getArgumentCount(), uint32_t(3));
+
+ prop = method.getArgument(0);
+ BOOST_CHECK_EQUAL(prop.getName(), "arg1");
+ BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_BOOL);
+ BOOST_CHECK_EQUAL(prop.getDesc(), "Argument One");
+ BOOST_CHECK_EQUAL(prop.getDirection(), DIR_IN);
+
+ prop = method.getArgument(1);
+ BOOST_CHECK_EQUAL(prop.getName(), "arg2");
+ BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_INT);
+ BOOST_CHECK_EQUAL(prop.getDesc(), "Argument Two");
+ BOOST_CHECK_EQUAL(prop.getDirection(), DIR_OUT);
+
+ prop = method.getArgument(2);
+ BOOST_CHECK_EQUAL(prop.getName(), "arg3");
+ BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_FLOAT);
+ BOOST_CHECK_EQUAL(prop.getDesc(), "Argument Three");
+ BOOST_CHECK_EQUAL(prop.getDirection(), DIR_IN_OUT);
+
+ BOOST_CHECK_THROW(method.getArgument(3), QmfException);
+
+ method = out.getMethod(1);
+ BOOST_CHECK_EQUAL(method.getName(), "method2");
+ BOOST_CHECK_EQUAL(method.getDesc(), "Method Two");
+ BOOST_CHECK_EQUAL(method.getArgumentCount(), uint32_t(3));
+
+ prop = method.getArgument(0);
+ BOOST_CHECK_EQUAL(prop.getName(), "arg21");
+ BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_BOOL);
+ BOOST_CHECK_EQUAL(prop.getDesc(), "Argument One");
+ BOOST_CHECK_EQUAL(prop.getDirection(), DIR_IN);
+
+ prop = method.getArgument(1);
+ BOOST_CHECK_EQUAL(prop.getName(), "arg22");
+ BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_INT);
+ BOOST_CHECK_EQUAL(prop.getDesc(), "Argument Two");
+ BOOST_CHECK_EQUAL(prop.getDirection(), DIR_OUT);
+
+ prop = method.getArgument(2);
+ BOOST_CHECK_EQUAL(prop.getName(), "arg23");
+ BOOST_CHECK_EQUAL(prop.getType(), SCHEMA_DATA_FLOAT);
+ BOOST_CHECK_EQUAL(prop.getDesc(), "Argument Three");
+ BOOST_CHECK_EQUAL(prop.getDirection(), DIR_IN_OUT);
+
+ BOOST_CHECK_THROW(method.getArgument(3), QmfException);
+}
+
+QPID_AUTO_TEST_CASE(testAgentSessionEventListener)
+{
+ Connection connection("localhost");
+ AgentSession session(connection, "");
+ posix::EventNotifier notifier(session);
+
+ AgentSessionImpl& sessionImpl = AgentSessionImplAccess::get(session);
+
+ BOOST_CHECK(sessionImpl.getEventNotifier() != 0);
+}
+
+QPID_AUTO_TEST_CASE(testConsoleSessionEventListener)
+{
+ Connection connection("localhost");
+ ConsoleSession session(connection, "");
+ posix::EventNotifier notifier(session);
+
+ ConsoleSessionImpl& sessionImpl = ConsoleSessionImplAccess::get(session);
+
+ BOOST_CHECK(sessionImpl.getEventNotifier() != 0);
+}
+
+QPID_AUTO_TEST_CASE(testGetHandle)
+{
+ Connection connection("localhost");
+ ConsoleSession session(connection, "");
+ posix::EventNotifier notifier(session);
+
+ BOOST_CHECK(notifier.getHandle() > 0);
+}
+
+QPID_AUTO_TEST_CASE(testSetReadableToFalse)
+{
+ Connection connection("localhost");
+ ConsoleSession session(connection, "");
+ posix::EventNotifier notifier(session);
+ PosixEventNotifierImplAccess::get(notifier).setReadable(false);
+
+ bool readable(isReadable(notifier.getHandle()));
+ BOOST_CHECK(!readable);
+}
+
+QPID_AUTO_TEST_CASE(testSetReadable)
+{
+ Connection connection("localhost");
+ ConsoleSession session(connection, "");
+ posix::EventNotifier notifier(session);
+ PosixEventNotifierImplAccess::get(notifier).setReadable(true);
+
+ bool readable(isReadable(notifier.getHandle()));
+ BOOST_CHECK(readable);
+}
+
+QPID_AUTO_TEST_CASE(testSetReadableMultiple)
+{
+ Connection connection("localhost");
+ ConsoleSession session(connection, "");
+ posix::EventNotifier notifier(session);
+ for (int i = 0; i < 15; i++)
+ PosixEventNotifierImplAccess::get(notifier).setReadable(true);
+ PosixEventNotifierImplAccess::get(notifier).setReadable(false);
+
+ bool readable(isReadable(notifier.getHandle()));
+ BOOST_CHECK(!readable);
+}
+
+QPID_AUTO_TEST_CASE(testDeleteNotifier)
+{
+ Connection connection("localhost");
+ ConsoleSession session(connection, "");
+ ConsoleSessionImpl& sessionImpl = ConsoleSessionImplAccess::get(session);
+ {
+ posix::EventNotifier notifier(session);
+ BOOST_CHECK(sessionImpl.getEventNotifier() != 0);
+ }
+ BOOST_CHECK(sessionImpl.getEventNotifier() == 0);
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/QueueDepth.cpp b/qpid/cpp/src/tests/QueueDepth.cpp
new file mode 100644
index 0000000000..09b221b3a8
--- /dev/null
+++ b/qpid/cpp/src/tests/QueueDepth.cpp
@@ -0,0 +1,105 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/broker/QueueDepth.h"
+
+#include "unit_test.h"
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(QueueDepthTestSuite)
+
+using namespace qpid::broker;
+
+QPID_AUTO_TEST_CASE(testCompare)
+{
+ QueueDepth a(0, 0);
+ QueueDepth b(1, 1);
+ QueueDepth c(2, 2);
+ QueueDepth d(1, 1);
+
+ BOOST_CHECK(a < b);
+ BOOST_CHECK(b < c);
+ BOOST_CHECK(a < c);
+
+ BOOST_CHECK(b > a);
+ BOOST_CHECK(c > b);
+ BOOST_CHECK(c > a);
+
+ BOOST_CHECK(b == d);
+ BOOST_CHECK(d == b);
+ BOOST_CHECK(a != b);
+ BOOST_CHECK(b != a);
+
+ QueueDepth e; e.setCount(1);
+ QueueDepth f; f.setCount(2);
+ BOOST_CHECK(e < f);
+ BOOST_CHECK(f > e);
+
+ QueueDepth g; g.setSize(1);
+ QueueDepth h; h.setSize(2);
+ BOOST_CHECK(g < h);
+ BOOST_CHECK(h > g);
+}
+
+QPID_AUTO_TEST_CASE(testIncrement)
+{
+ QueueDepth a(5, 10);
+ QueueDepth b(3, 6);
+ QueueDepth c(8, 16);
+ a += b;
+ BOOST_CHECK(a == c);
+ BOOST_CHECK_EQUAL(8u, a.getCount());
+ BOOST_CHECK_EQUAL(16u, a.getSize());
+}
+
+QPID_AUTO_TEST_CASE(testDecrement)
+{
+ QueueDepth a(5, 10);
+ QueueDepth b(3, 6);
+ QueueDepth c(2, 4);
+ a -= b;
+ BOOST_CHECK(a == c);
+ BOOST_CHECK_EQUAL(2u, a.getCount());
+ BOOST_CHECK_EQUAL(4u, a.getSize());
+}
+
+QPID_AUTO_TEST_CASE(testAddition)
+{
+ QueueDepth a(5, 10);
+ QueueDepth b(3, 6);
+
+ QueueDepth c = a + b;
+ BOOST_CHECK_EQUAL(8u, c.getCount());
+ BOOST_CHECK_EQUAL(16u, c.getSize());
+}
+
+QPID_AUTO_TEST_CASE(testSubtraction)
+{
+ QueueDepth a(5, 10);
+ QueueDepth b(3, 6);
+
+ QueueDepth c = a - b;
+ BOOST_CHECK_EQUAL(2u, c.getCount());
+ BOOST_CHECK_EQUAL(4u, c.getSize());
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/QueueFlowLimitTest.cpp b/qpid/cpp/src/tests/QueueFlowLimitTest.cpp
new file mode 100644
index 0000000000..b35294922d
--- /dev/null
+++ b/qpid/cpp/src/tests/QueueFlowLimitTest.cpp
@@ -0,0 +1,457 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include <sstream>
+#include <deque>
+#include "unit_test.h"
+#include "test_tools.h"
+
+#include "qpid/broker/QueueFlowLimit.h"
+#include "qpid/broker/QueueSettings.h"
+#include "qpid/sys/Time.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/framing/FieldValue.h"
+#include "MessageUtils.h"
+#include "BrokerFixture.h"
+
+using namespace qpid::broker;
+using namespace qpid::framing;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(QueueFlowLimitTestSuite)
+
+namespace {
+
+class TestFlow : public QueueFlowLimit
+{
+public:
+ TestFlow(uint32_t flowStopCount, uint32_t flowResumeCount,
+ uint64_t flowStopSize, uint64_t flowResumeSize) :
+ QueueFlowLimit("", flowStopCount, flowResumeCount, flowStopSize, flowResumeSize)
+ {}
+ virtual ~TestFlow() {}
+
+ static TestFlow *createTestFlow(const qpid::framing::FieldTable& settings)
+ {
+ FieldTable::ValuePtr v;
+
+ v = settings.get(flowStopCountKey);
+ uint32_t flowStopCount = (v) ? (uint32_t)v->get<int64_t>() : 0;
+ v = settings.get(flowResumeCountKey);
+ uint32_t flowResumeCount = (v) ? (uint32_t)v->get<int64_t>() : 0;
+ v = settings.get(flowStopSizeKey);
+ uint64_t flowStopSize = (v) ? (uint64_t)v->get<int64_t>() : 0;
+ v = settings.get(flowResumeSizeKey);
+ uint64_t flowResumeSize = (v) ? (uint64_t)v->get<int64_t>() : 0;
+
+ return new TestFlow(flowStopCount, flowResumeCount, flowStopSize, flowResumeSize);
+ }
+
+ static boost::shared_ptr<qpid::broker::QueueFlowLimit> getQueueFlowLimit(const qpid::framing::FieldTable& arguments)
+ {
+ QueueSettings settings;
+ settings.populate(arguments, settings.storeSettings);
+ return QueueFlowLimit::createLimit("", settings);
+ }
+};
+
+Message createMessage(uint32_t size)
+{
+ static uint32_t seqNum;
+ //Need to compute what data size is required to make a given
+ //overall size (use one byte of content in test message to ensure
+ //content frame is added)
+ Message test = MessageUtils::createMessage(qpid::types::Variant::Map(), std::string("x"));
+ size_t min = test.getMessageSize() - 1;
+ if (min > size) throw qpid::Exception("Can't create message that small!");
+ Message msg = MessageUtils::createMessage(qpid::types::Variant::Map(), std::string (size - min, 'x'));
+ msg.setSequence(++seqNum);//this doesn't affect message size
+ return msg;
+}
+}
+
+QPID_AUTO_TEST_CASE(testFlowCount)
+{
+ FieldTable args;
+ args.setInt(QueueFlowLimit::flowStopCountKey, 7);
+ args.setInt(QueueFlowLimit::flowResumeCountKey, 5);
+
+ std::auto_ptr<TestFlow> flow(TestFlow::createTestFlow(args));
+
+ BOOST_CHECK_EQUAL((uint32_t) 7, flow->getFlowStopCount());
+ BOOST_CHECK_EQUAL((uint32_t) 5, flow->getFlowResumeCount());
+ BOOST_CHECK_EQUAL((uint32_t) 0, flow->getFlowStopSize());
+ BOOST_CHECK_EQUAL((uint32_t) 0, flow->getFlowResumeSize());
+ BOOST_CHECK(!flow->isFlowControlActive());
+ BOOST_CHECK(flow->monitorFlowControl());
+
+ std::deque<Message> msgs;
+ for (size_t i = 0; i < 6; i++) {
+ msgs.push_back(createMessage(100));
+ flow->enqueued(msgs.back());
+ BOOST_CHECK(!flow->isFlowControlActive());
+ }
+ BOOST_CHECK(!flow->isFlowControlActive()); // 6 on queue
+ msgs.push_back(createMessage(100));
+ flow->enqueued(msgs.back());
+ BOOST_CHECK(!flow->isFlowControlActive()); // 7 on queue
+ msgs.push_back(createMessage(100));
+ flow->enqueued(msgs.back());
+ BOOST_CHECK(flow->isFlowControlActive()); // 8 on queue, ON
+ msgs.push_back(createMessage(100));
+ flow->enqueued(msgs.back());
+ BOOST_CHECK(flow->isFlowControlActive()); // 9 on queue, no change to flow control
+
+ flow->dequeued(msgs.front());
+ msgs.pop_front();
+ BOOST_CHECK(flow->isFlowControlActive()); // 8 on queue
+ flow->dequeued(msgs.front());
+ msgs.pop_front();
+ BOOST_CHECK(flow->isFlowControlActive()); // 7 on queue
+ flow->dequeued(msgs.front());
+ msgs.pop_front();
+ BOOST_CHECK(flow->isFlowControlActive()); // 6 on queue
+ flow->dequeued(msgs.front());
+ msgs.pop_front();
+ BOOST_CHECK(flow->isFlowControlActive()); // 5 on queue, no change
+
+ flow->dequeued(msgs.front());
+ msgs.pop_front();
+ BOOST_CHECK(!flow->isFlowControlActive()); // 4 on queue, OFF
+}
+
+QPID_AUTO_TEST_CASE(testFlowSize)
+{
+ FieldTable args;
+ args.setUInt64(QueueFlowLimit::flowStopSizeKey, 700);
+ args.setUInt64(QueueFlowLimit::flowResumeSizeKey, 460);
+
+ std::auto_ptr<TestFlow> flow(TestFlow::createTestFlow(args));
+
+ BOOST_CHECK_EQUAL((uint32_t) 0, flow->getFlowStopCount());
+ BOOST_CHECK_EQUAL((uint32_t) 0, flow->getFlowResumeCount());
+ BOOST_CHECK_EQUAL((uint32_t) 700, flow->getFlowStopSize());
+ BOOST_CHECK_EQUAL((uint32_t) 460, flow->getFlowResumeSize());
+ BOOST_CHECK(!flow->isFlowControlActive());
+ BOOST_CHECK(flow->monitorFlowControl());
+
+ std::deque<Message> msgs;
+ for (size_t i = 0; i < 6; i++) {
+ msgs.push_back(createMessage(100));
+ flow->enqueued(msgs.back());
+ BOOST_CHECK(!flow->isFlowControlActive());
+ }
+ BOOST_CHECK(!flow->isFlowControlActive()); // 600 on queue
+ BOOST_CHECK_EQUAL(6u, flow->getFlowCount());
+ BOOST_CHECK_EQUAL(600u, flow->getFlowSize());
+
+ Message msg_50 = createMessage(50);
+ flow->enqueued(msg_50);
+ BOOST_CHECK(!flow->isFlowControlActive()); // 650 on queue
+ Message tinyMsg_1 = createMessage(40);
+ flow->enqueued(tinyMsg_1);
+ BOOST_CHECK(!flow->isFlowControlActive()); // 690 on queue
+
+ Message tinyMsg_2 = createMessage(40);
+ flow->enqueued(tinyMsg_2);
+ BOOST_CHECK(flow->isFlowControlActive()); // 730 on queue, ON
+ msgs.push_back(createMessage(100));
+ flow->enqueued(msgs.back());
+ BOOST_CHECK(flow->isFlowControlActive()); // 830 on queue
+ BOOST_CHECK_EQUAL(10u, flow->getFlowCount());
+ BOOST_CHECK_EQUAL(830u, flow->getFlowSize());
+
+ flow->dequeued(msgs.front());
+ msgs.pop_front();
+ BOOST_CHECK(flow->isFlowControlActive()); // 730 on queue
+ flow->dequeued(msgs.front());
+ msgs.pop_front();
+ BOOST_CHECK(flow->isFlowControlActive()); // 630 on queue
+ flow->dequeued(msgs.front());
+ msgs.pop_front();
+ BOOST_CHECK(flow->isFlowControlActive()); // 530 on queue
+
+ flow->dequeued(tinyMsg_1);
+ BOOST_CHECK(flow->isFlowControlActive()); // 490 on queue
+ flow->dequeued(tinyMsg_2);
+ BOOST_CHECK(!flow->isFlowControlActive()); // 450 on queue, OFF
+
+ flow->dequeued(msg_50);
+ BOOST_CHECK(!flow->isFlowControlActive()); // 400 on queue
+ flow->dequeued(msgs.front());
+ msgs.pop_front();
+ BOOST_CHECK(!flow->isFlowControlActive()); // 300 on queue
+ flow->dequeued(msgs.front());
+ msgs.pop_front();
+ BOOST_CHECK(!flow->isFlowControlActive()); // 200 on queue
+ BOOST_CHECK_EQUAL(2u, flow->getFlowCount());
+ BOOST_CHECK_EQUAL(200u, flow->getFlowSize());
+}
+
+QPID_AUTO_TEST_CASE(testFlowArgs)
+{
+ FieldTable args;
+ const uint64_t stop(0x2FFFFFFFFull);
+ const uint64_t resume(0x1FFFFFFFFull);
+ args.setInt(QueueFlowLimit::flowStopCountKey, 30);
+ args.setInt(QueueFlowLimit::flowResumeCountKey, 21);
+ args.setUInt64(QueueFlowLimit::flowStopSizeKey, stop);
+ args.setUInt64(QueueFlowLimit::flowResumeSizeKey, resume);
+
+ std::auto_ptr<TestFlow> flow(TestFlow::createTestFlow(args));
+
+ BOOST_CHECK_EQUAL((uint32_t) 30, flow->getFlowStopCount());
+ BOOST_CHECK_EQUAL((uint32_t) 21, flow->getFlowResumeCount());
+ BOOST_CHECK_EQUAL(stop, flow->getFlowStopSize());
+ BOOST_CHECK_EQUAL(resume, flow->getFlowResumeSize());
+ BOOST_CHECK(!flow->isFlowControlActive());
+ BOOST_CHECK(flow->monitorFlowControl());
+}
+
+
+QPID_AUTO_TEST_CASE(testFlowCombo)
+{
+ FieldTable args;
+ args.setInt(QueueFlowLimit::flowStopCountKey, 10);
+ args.setInt(QueueFlowLimit::flowResumeCountKey, 5);
+ args.setUInt64(QueueFlowLimit::flowStopSizeKey, 2000);
+ args.setUInt64(QueueFlowLimit::flowResumeSizeKey, 1000);
+
+ std::deque<Message> msgs_50;
+ std::deque<Message> msgs_100;
+ std::deque<Message> msgs_500;
+ std::deque<Message> msgs_1000;
+
+ Message msg;
+
+ std::auto_ptr<TestFlow> flow(TestFlow::createTestFlow(args));
+ BOOST_CHECK(!flow->isFlowControlActive()); // count:0 size:0
+
+ // verify flow control comes ON when only count passes its stop point.
+
+ for (size_t i = 0; i < 10; i++) {
+ msgs_100.push_back(createMessage(100));
+ flow->enqueued(msgs_100.back());
+ BOOST_CHECK(!flow->isFlowControlActive());
+ }
+ // count:10 size:1000
+
+ msgs_50.push_back(createMessage(50));
+ flow->enqueued(msgs_50.back()); // count:11 size: 1050 ->ON
+ BOOST_CHECK(flow->isFlowControlActive());
+
+ for (size_t i = 0; i < 6; i++) {
+ flow->dequeued(msgs_100.front());
+ msgs_100.pop_front();
+ BOOST_CHECK(flow->isFlowControlActive());
+ }
+ // count:5 size: 450
+
+ flow->dequeued(msgs_50.front()); // count: 4 size: 400 ->OFF
+ msgs_50.pop_front();
+ BOOST_CHECK(!flow->isFlowControlActive());
+
+ for (size_t i = 0; i < 4; i++) {
+ flow->dequeued(msgs_100.front());
+ msgs_100.pop_front();
+ BOOST_CHECK(!flow->isFlowControlActive());
+ }
+ // count:0 size:0
+
+ // verify flow control comes ON when only size passes its stop point.
+
+ msgs_1000.push_back(createMessage(1000));
+ flow->enqueued(msgs_1000.back()); // count:1 size: 1000
+ BOOST_CHECK(!flow->isFlowControlActive());
+
+ msgs_500.push_back(createMessage(500));
+ flow->enqueued(msgs_500.back()); // count:2 size: 1500
+ BOOST_CHECK(!flow->isFlowControlActive());
+
+ msgs_500.push_back(createMessage(500));
+ flow->enqueued(msgs_500.back()); // count:3 size: 2000
+ BOOST_CHECK(!flow->isFlowControlActive());
+
+ msgs_50.push_back(createMessage(50));
+ flow->enqueued(msgs_50.back()); // count:4 size: 2050 ->ON
+ BOOST_CHECK(flow->isFlowControlActive());
+
+ flow->dequeued(msgs_1000.front()); // count:3 size:1050
+ msgs_1000.pop_front();
+ BOOST_CHECK(flow->isFlowControlActive());
+
+ flow->dequeued(msgs_50.front()); // count:2 size:1000
+ msgs_50.pop_front();
+ BOOST_CHECK(flow->isFlowControlActive());
+
+ flow->dequeued(msgs_500.front()); // count:1 size:500 ->OFF
+ msgs_500.pop_front();
+ BOOST_CHECK(!flow->isFlowControlActive());
+
+ // verify flow control remains ON until both thresholds drop below their
+ // resume point.
+
+ for (size_t i = 0; i < 8; i++) {
+ msgs_100.push_back(createMessage(100));
+ flow->enqueued(msgs_100.back());
+ BOOST_CHECK(!flow->isFlowControlActive());
+ }
+ // count:9 size:1300
+
+ msgs_100.push_back(createMessage(100));
+ flow->enqueued(msgs_100.back()); // count:10 size: 1400
+ BOOST_CHECK(!flow->isFlowControlActive());
+
+ msgs_50.push_back(createMessage(50));
+ flow->enqueued(msgs_50.back()); // count:11 size: 1450 ->ON
+ BOOST_CHECK(flow->isFlowControlActive());
+
+ msgs_1000.push_back(createMessage(1000));
+ flow->enqueued(msgs_1000.back()); // count:12 size: 2450 (both thresholds crossed)
+ BOOST_CHECK(flow->isFlowControlActive());
+
+ // at this point: 9@100 + 1@500 + 1@1000 + 1@50 == 12@2450
+
+ flow->dequeued(msgs_500.front()); // count:11 size:1950
+ msgs_500.pop_front();
+ BOOST_CHECK(flow->isFlowControlActive());
+
+ for (size_t i = 0; i < 9; i++) {
+ flow->dequeued(msgs_100.front());
+ msgs_100.pop_front();
+ BOOST_CHECK(flow->isFlowControlActive());
+ }
+ // count:2 size:1050
+ flow->dequeued(msgs_50.front()); // count:1 size:1000
+ msgs_50.pop_front();
+ BOOST_CHECK(flow->isFlowControlActive()); // still active due to size
+
+ flow->dequeued(msgs_1000.front()); // count:0 size:0 ->OFF
+ msgs_1000.pop_front();
+ BOOST_CHECK(!flow->isFlowControlActive());
+}
+
+
+QPID_AUTO_TEST_CASE(testFlowDefaultArgs)
+{
+ QueueFlowLimit::setDefaults(2950001, // max queue byte count
+ 80, // 80% stop threshold
+ 70); // 70% resume threshold
+ FieldTable args;
+ boost::shared_ptr<QueueFlowLimit> flow = TestFlow::getQueueFlowLimit(args);
+ BOOST_CHECK(flow);
+
+ BOOST_CHECK_EQUAL((uint64_t) 2360001, flow->getFlowStopSize());
+ BOOST_CHECK_EQUAL((uint64_t) 2065000, flow->getFlowResumeSize());
+ BOOST_CHECK_EQUAL( 0u, flow->getFlowStopCount());
+ BOOST_CHECK_EQUAL( 0u, flow->getFlowResumeCount());
+ BOOST_CHECK(!flow->isFlowControlActive());
+ BOOST_CHECK(flow->monitorFlowControl());
+}
+
+
+QPID_AUTO_TEST_CASE(testFlowOverrideArgs)
+{
+ QueueFlowLimit::setDefaults(0, // max queue byte count
+ 80, // 80% stop threshold
+ 70); // 70% resume threshold
+ {
+ FieldTable args;
+ args.setInt(QueueFlowLimit::flowStopCountKey, 35000);
+ args.setInt(QueueFlowLimit::flowResumeCountKey, 30000);
+// args.setInt(QueueFlowLimit::flowStopSizeKey, 0);
+
+ boost::shared_ptr<QueueFlowLimit> flow = TestFlow::getQueueFlowLimit(args);
+ BOOST_CHECK(flow);
+
+ BOOST_CHECK_EQUAL((uint32_t) 35000, flow->getFlowStopCount());
+ BOOST_CHECK_EQUAL((uint32_t) 30000, flow->getFlowResumeCount());
+ BOOST_CHECK_EQUAL((uint64_t) 0, flow->getFlowStopSize());
+ BOOST_CHECK_EQUAL((uint64_t) 0, flow->getFlowResumeSize());
+ BOOST_CHECK(!flow->isFlowControlActive());
+ BOOST_CHECK(flow->monitorFlowControl());
+ }
+ {
+ FieldTable args;
+ args.setInt(QueueFlowLimit::flowStopSizeKey, 350000);
+ args.setInt(QueueFlowLimit::flowResumeSizeKey, 300000);
+
+ boost::shared_ptr<QueueFlowLimit> flow = TestFlow::getQueueFlowLimit(args);
+ BOOST_CHECK(flow);
+
+ BOOST_CHECK_EQUAL((uint32_t) 0, flow->getFlowStopCount());
+ BOOST_CHECK_EQUAL((uint32_t) 0, flow->getFlowResumeCount());
+ BOOST_CHECK_EQUAL((uint64_t) 350000, flow->getFlowStopSize());
+ BOOST_CHECK_EQUAL((uint64_t) 300000, flow->getFlowResumeSize());
+ BOOST_CHECK(!flow->isFlowControlActive());
+ BOOST_CHECK(flow->monitorFlowControl());
+ }
+ {
+ FieldTable args;
+ args.setInt(QueueFlowLimit::flowStopCountKey, 35000);
+ args.setInt(QueueFlowLimit::flowResumeCountKey, 30000);
+ args.setInt(QueueFlowLimit::flowStopSizeKey, 350000);
+ args.setInt(QueueFlowLimit::flowResumeSizeKey, 300000);
+
+ boost::shared_ptr<QueueFlowLimit> flow = TestFlow::getQueueFlowLimit(args);
+ BOOST_CHECK(flow);
+
+ BOOST_CHECK_EQUAL((uint32_t) 35000, flow->getFlowStopCount());
+ BOOST_CHECK_EQUAL((uint32_t) 30000, flow->getFlowResumeCount());
+ BOOST_CHECK_EQUAL((uint64_t) 350000, flow->getFlowStopSize());
+ BOOST_CHECK_EQUAL((uint64_t) 300000, flow->getFlowResumeSize());
+ BOOST_CHECK(!flow->isFlowControlActive());
+ BOOST_CHECK(flow->monitorFlowControl());
+ }
+}
+
+
+QPID_AUTO_TEST_CASE(testFlowOverrideDefaults)
+{
+ QueueFlowLimit::setDefaults(2950001, // max queue byte count
+ 97, // stop threshold
+ 73); // resume threshold
+ FieldTable args;
+ boost::shared_ptr<QueueFlowLimit> flow = TestFlow::getQueueFlowLimit(args);
+ BOOST_CHECK(flow);
+
+ BOOST_CHECK_EQUAL((uint32_t) 2861501, flow->getFlowStopSize());
+ BOOST_CHECK_EQUAL((uint32_t) 2153500, flow->getFlowResumeSize());
+ BOOST_CHECK(!flow->isFlowControlActive());
+ BOOST_CHECK(flow->monitorFlowControl());
+}
+
+
+QPID_AUTO_TEST_CASE(testFlowDisable)
+{
+ {
+ FieldTable args;
+ args.setInt(QueueFlowLimit::flowStopCountKey, 0);
+ args.setInt(QueueFlowLimit::flowStopSizeKey, 0);
+ boost::shared_ptr<QueueFlowLimit> flow = TestFlow::getQueueFlowLimit(args);
+ BOOST_CHECK(!flow);
+ }
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/QueueOptionsTest.cpp b/qpid/cpp/src/tests/QueueOptionsTest.cpp
new file mode 100644
index 0000000000..bdb83d7d22
--- /dev/null
+++ b/qpid/cpp/src/tests/QueueOptionsTest.cpp
@@ -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.
+ *
+ */
+#include <iostream>
+#include "qpid/framing/Array.h"
+#include "qpid/client/QueueOptions.h"
+
+#include "unit_test.h"
+
+using namespace qpid::client;
+
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(QueueOptionsTestSuite)
+
+QPID_AUTO_TEST_CASE(testSizePolicy)
+{
+ QueueOptions ft;
+
+ ft.setSizePolicy(REJECT,1,2);
+
+ BOOST_CHECK(QueueOptions::strREJECT == ft.getAsString(QueueOptions::strTypeKey));
+ BOOST_CHECK(1 == ft.getAsInt(QueueOptions::strMaxSizeKey));
+ BOOST_CHECK(2 == ft.getAsInt(QueueOptions::strMaxCountKey));
+
+ ft.setSizePolicy(FLOW_TO_DISK,0,2);
+ BOOST_CHECK(QueueOptions::strFLOW_TO_DISK == ft.getAsString(QueueOptions::strTypeKey));
+ BOOST_CHECK(1 == ft.getAsInt(QueueOptions::strMaxSizeKey));
+ BOOST_CHECK(2 == ft.getAsInt(QueueOptions::strMaxCountKey));
+
+ ft.setSizePolicy(RING,1,0);
+ BOOST_CHECK(QueueOptions::strRING == ft.getAsString(QueueOptions::strTypeKey));
+
+ ft.setSizePolicy(RING_STRICT,1,0);
+ BOOST_CHECK(QueueOptions::strRING_STRICT == ft.getAsString(QueueOptions::strTypeKey));
+
+ ft.clearSizePolicy();
+ BOOST_CHECK(!ft.isSet(QueueOptions::strTypeKey));
+ BOOST_CHECK(!ft.isSet(QueueOptions::strMaxSizeKey));
+ BOOST_CHECK(!ft.isSet(QueueOptions::strMaxCountKey));
+}
+
+QPID_AUTO_TEST_CASE(testFlags)
+{
+ QueueOptions ft;
+
+ ft.setOrdering(LVQ);
+ BOOST_CHECK(1 == ft.getAsInt(QueueOptions::strLastValueQueue));
+ ft.setOrdering(FIFO);
+ BOOST_CHECK(!ft.isSet(QueueOptions::strLastValueQueue));
+
+}
+
+QPID_AUTO_TEST_CASE(testSetOrdering)
+{
+ //ensure setOrdering(FIFO) works even if not preceded by a call to
+ //setOrdering(LVQ)
+ QueueOptions ft;
+ ft.setOrdering(FIFO);
+ BOOST_CHECK(!ft.isSet(QueueOptions::strLastValueQueue));
+
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/QueuePolicyTest.cpp b/qpid/cpp/src/tests/QueuePolicyTest.cpp
new file mode 100644
index 0000000000..f61c283fd4
--- /dev/null
+++ b/qpid/cpp/src/tests/QueuePolicyTest.cpp
@@ -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.
+ *
+ */
+#include <sstream>
+#include "unit_test.h"
+#include "test_tools.h"
+
+#include "qpid/broker/QueueFlowLimit.h"
+#include "qpid/client/QueueOptions.h"
+#include "qpid/sys/Time.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "BrokerFixture.h"
+
+#include <boost/format.hpp>
+
+using namespace qpid::broker;
+using namespace qpid::client;
+using namespace qpid::framing;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(QueuePolicyTestSuite)
+
+QPID_AUTO_TEST_CASE(testRingPolicyCount)
+{
+ QueueOptions args;
+ args.setSizePolicy(RING, 0, 5);
+
+ SessionFixture f;
+ std::string q("my-ring-queue");
+ f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args);
+ for (int i = 0; i < 10; i++) {
+ f.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q));
+ }
+ client::Message msg;
+ for (int i = 5; i < 10; i++) {
+ BOOST_CHECK(f.subs.get(msg, q, qpid::sys::TIME_SEC));
+ BOOST_CHECK_EQUAL((boost::format("%1%_%2%") % "Message" % (i+1)).str(), msg.getData());
+ }
+ BOOST_CHECK(!f.subs.get(msg, q));
+
+ for (int i = 10; i < 20; i++) {
+ f.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q));
+ }
+ for (int i = 15; i < 20; i++) {
+ BOOST_CHECK(f.subs.get(msg, q, qpid::sys::TIME_SEC));
+ BOOST_CHECK_EQUAL((boost::format("%1%_%2%") % "Message" % (i+1)).str(), msg.getData());
+ }
+ BOOST_CHECK(!f.subs.get(msg, q));
+}
+
+QPID_AUTO_TEST_CASE(testRingPolicySize)
+{
+ //The message size now includes all headers as well as the content
+ //aka body, so compute the amount of data needed to hit a given
+ //overall size
+ std::string q("my-ring-queue");
+ size_t minMessageSize = 25/*minimum size of headers*/ + q.size()/*routing key length*/ + 4/*default exchange, added by broker*/;
+
+ std::string hundredBytes = std::string(100 - minMessageSize, 'h');
+ std::string fourHundredBytes = std::string (400 - minMessageSize, 'f');
+ std::string thousandBytes = std::string(1000 - minMessageSize, 't');
+
+ // Ring queue, 500 bytes maxSize
+
+ QueueOptions args;
+ args.setSizePolicy(RING, 500, 0);
+
+ SessionFixture f;
+ f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args);
+
+ // A. Send messages 0 .. 5, each 100 bytes
+
+ client::Message m(hundredBytes, q);
+
+ for (int i = 0; i < 6; i++) {
+ std::stringstream id;
+ id << i;
+ m.getMessageProperties().setCorrelationId(id.str());
+ f.session.messageTransfer(arg::content=m);
+ }
+
+ // should find 1 .. 5 on the queue, 0 is displaced by 5
+ client::Message msg;
+ for (int i = 1; i < 6; i++) {
+ std::stringstream id;
+ id << i;
+ BOOST_CHECK(f.subs.get(msg, q, qpid::sys::TIME_SEC));
+ BOOST_CHECK_EQUAL(msg.getMessageProperties().getCorrelationId(), id.str());
+ }
+ BOOST_CHECK(!f.subs.get(msg, q));
+
+ // B. Now make sure that one 400 byte message displaces four 100 byte messages
+
+ // Send messages 0 .. 5, each 100 bytes
+ for (int i = 0; i < 6; i++) {
+ client::Message m(hundredBytes, q);
+ std::stringstream id;
+ id << i;
+ m.getMessageProperties().setCorrelationId(id.str());
+ f.session.messageTransfer(arg::content=m);
+ }
+
+ // Now send one 400 byte message
+ client::Message m2(fourHundredBytes, q);
+ m2.getMessageProperties().setCorrelationId("6");
+ f.session.messageTransfer(arg::content=m2);
+
+ // expect to see 5, 6 on the queue
+ for (int i = 5; i < 7; i++) {
+ std::stringstream id;
+ id << i;
+ BOOST_CHECK(f.subs.get(msg, q, qpid::sys::TIME_SEC));
+ BOOST_CHECK_EQUAL(msg.getMessageProperties().getCorrelationId(), id.str());
+ }
+ BOOST_CHECK(!f.subs.get(msg, q));
+
+
+ // C. Try sending a 1000-byte message, should fail - exceeds maxSize of queue
+
+ client::Message m3(thousandBytes, q);
+ m3.getMessageProperties().setCorrelationId("6");
+ try {
+ ScopedSuppressLogging sl;
+ f.session.messageTransfer(arg::content=m3);
+ BOOST_FAIL("Ooops - successfully added a 1000 byte message to a 512 byte ring queue ...");
+ }
+ catch (...) {
+ }
+
+}
+
+
+QPID_AUTO_TEST_CASE(testStrictRingPolicy)
+{
+ QueueOptions args;
+ args.setSizePolicy(RING_STRICT, 0, 5);
+ args.setString("qpid.flow_stop_count", "0");
+
+ SessionFixture f;
+ std::string q("my-ring-queue");
+ f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args);
+ LocalQueue incoming;
+ SubscriptionSettings settings(FlowControl::unlimited());
+ settings.autoAck = 0; // no auto ack.
+ Subscription sub = f.subs.subscribe(incoming, q, settings);
+ for (int i = 0; i < 5; i++) {
+ f.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q));
+ }
+ for (int i = 0; i < 5; i++) {
+ BOOST_CHECK_EQUAL(incoming.pop().getData(), (boost::format("%1%_%2%") % "Message" % (i+1)).str());
+ }
+ try {
+ ScopedSuppressLogging sl; // Suppress messages for expected errors.
+ f.session.messageTransfer(arg::content=client::Message("Message_6", q));
+ BOOST_FAIL("expecting ResourceLimitExceededException.");
+ } catch (const ResourceLimitExceededException&) {}
+}
+
+QPID_AUTO_TEST_CASE(testPolicyWithDtx)
+{
+ QueueOptions args;
+ args.setSizePolicy(REJECT, 0, 5);
+
+ SessionFixture f;
+ std::string q("my-policy-queue");
+ f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args);
+ LocalQueue incoming;
+ SubscriptionSettings settings(FlowControl::unlimited());
+ settings.autoAck = 0; // no auto ack.
+ Subscription sub = f.subs.subscribe(incoming, q, settings);
+ f.session.dtxSelect();
+ Xid tx1(1, "test-dtx-mgr", "tx1");
+ f.session.dtxStart(arg::xid=tx1);
+ for (int i = 0; i < 5; i++) {
+ f.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q));
+ }
+ f.session.dtxEnd(arg::xid=tx1);
+ f.session.dtxCommit(arg::xid=tx1, arg::onePhase=true);
+
+ Xid tx2(1, "test-dtx-mgr", "tx2");
+ f.session.dtxStart(arg::xid=tx2);
+ for (int i = 0; i < 5; i++) {
+ BOOST_CHECK_EQUAL(incoming.pop().getData(), (boost::format("%1%_%2%") % "Message" % (i+1)).str());
+ }
+ SequenceSet accepting=sub.getUnaccepted();
+ f.session.messageAccept(accepting);
+ f.session.dtxEnd(arg::xid=tx2);
+ f.session.dtxPrepare(arg::xid=tx2);
+ f.session.dtxRollback(arg::xid=tx2);
+ f.session.messageRelease(accepting);
+
+ Xid tx3(1, "test-dtx-mgr", "tx3");
+ f.session.dtxStart(arg::xid=tx3);
+ for (int i = 0; i < 5; i++) {
+ incoming.pop();
+ }
+ accepting=sub.getUnaccepted();
+ f.session.messageAccept(accepting);
+ f.session.dtxEnd(arg::xid=tx3);
+ f.session.dtxPrepare(arg::xid=tx3);
+
+ Session other = f.connection.newSession();
+ try {
+ ScopedSuppressLogging sl; // Suppress messages for expected errors.
+ other.messageTransfer(arg::content=client::Message("Message_6", q));
+ BOOST_FAIL("expecting ResourceLimitExceededException.");
+ } catch (const ResourceLimitExceededException&) {}
+
+ f.session.dtxCommit(arg::xid=tx3);
+ //now retry and this time should succeed
+ other = f.connection.newSession();
+ other.messageTransfer(arg::content=client::Message("Message_6", q));
+}
+
+QPID_AUTO_TEST_CASE(testFlowToDiskWithNoStore)
+{
+ //Ensure that with no store loaded, we don't flow to disk but
+ //fallback to rejecting messages
+ QueueOptions args;
+ args.setSizePolicy(FLOW_TO_DISK, 0, 5);
+ // Disable flow control, or else we'll never hit the max limit
+ args.setInt(QueueFlowLimit::flowStopCountKey, 0);
+
+ SessionFixture f;
+ std::string q("my-queue");
+ f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args);
+ LocalQueue incoming;
+ SubscriptionSettings settings(FlowControl::unlimited());
+ settings.autoAck = 0; // no auto ack.
+ Subscription sub = f.subs.subscribe(incoming, q, settings);
+ for (int i = 0; i < 5; i++) {
+ f.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q));
+ }
+ for (int i = 0; i < 5; i++) {
+ BOOST_CHECK_EQUAL(incoming.pop().getData(), (boost::format("%1%_%2%") % "Message" % (i+1)).str());
+ }
+ try {
+ ScopedSuppressLogging sl; // Suppress messages for expected errors.
+ f.session.messageTransfer(arg::content=client::Message("Message_6", q));
+ BOOST_FAIL("expecting ResourceLimitExceededException.");
+ } catch (const ResourceLimitExceededException&) {}
+}
+
+QPID_AUTO_TEST_CASE(testPolicyFailureOnCommit)
+{
+ QueueOptions args;
+ args.setSizePolicy(REJECT, 0, 5);
+
+ SessionFixture f;
+ std::string q("q");
+ f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args);
+ f.session.txSelect();
+ for (int i = 0; i < 10; i++) {
+ f.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q));
+ }
+ ScopedSuppressLogging sl; // Suppress messages for expected errors.
+ BOOST_CHECK_THROW(f.session.txCommit(), InternalErrorException);
+}
+
+QPID_AUTO_TEST_CASE(testCapacityConversion)
+{
+ FieldTable args;
+ args.setString("qpid.max_count", "5");
+ args.setString("qpid.flow_stop_count", "0");
+
+ SessionFixture f;
+ std::string q("q");
+ f.session.queueDeclare(arg::queue=q, arg::exclusive=true, arg::autoDelete=true, arg::arguments=args);
+ for (int i = 0; i < 5; i++) {
+ f.session.messageTransfer(arg::content=client::Message((boost::format("%1%_%2%") % "Message" % (i+1)).str(), q));
+ }
+ try {
+ ScopedSuppressLogging sl; // Suppress messages for expected errors.
+ f.session.messageTransfer(arg::content=client::Message("Message_6", q));
+ BOOST_FAIL("expecting ResourceLimitExceededException.");
+ } catch (const ResourceLimitExceededException&) {}
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/QueueRegistryTest.cpp b/qpid/cpp/src/tests/QueueRegistryTest.cpp
new file mode 100644
index 0000000000..364d66c525
--- /dev/null
+++ b/qpid/cpp/src/tests/QueueRegistryTest.cpp
@@ -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.
+ */
+
+#include "qpid/broker/QueueRegistry.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/QueueSettings.h"
+#include "unit_test.h"
+#include <string>
+
+using namespace qpid::broker;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(QueueRegistryTest)
+
+QPID_AUTO_TEST_CASE(testDeclare)
+{
+ std::string foo("foo");
+ std::string bar("bar");
+ QueueRegistry reg;
+ std::pair<Queue::shared_ptr, bool> qc;
+
+ qc = reg.declare(foo, QueueSettings());
+ Queue::shared_ptr q = qc.first;
+ BOOST_CHECK(q);
+ BOOST_CHECK(qc.second); // New queue
+ BOOST_CHECK_EQUAL(foo, q->getName());
+
+ qc = reg.declare(foo, QueueSettings());
+ BOOST_CHECK_EQUAL(q, qc.first);
+ BOOST_CHECK(!qc.second);
+
+ qc = reg.declare(bar, QueueSettings());
+ q = qc.first;
+ BOOST_CHECK(q);
+ BOOST_CHECK_EQUAL(true, qc.second);
+ BOOST_CHECK_EQUAL(bar, q->getName());
+}
+
+QPID_AUTO_TEST_CASE(testFind)
+{
+ std::string foo("foo");
+ std::string bar("bar");
+ QueueRegistry reg;
+ std::pair<Queue::shared_ptr, bool> qc;
+
+ BOOST_CHECK(reg.find(foo) == 0);
+
+ reg.declare(foo, QueueSettings());
+ reg.declare(bar, QueueSettings());
+ Queue::shared_ptr q = reg.find(bar);
+ BOOST_CHECK(q);
+ BOOST_CHECK_EQUAL(bar, q->getName());
+}
+
+QPID_AUTO_TEST_CASE(testDestroy)
+{
+ std::string foo("foo");
+ QueueRegistry reg;
+ std::pair<Queue::shared_ptr, bool> qc;
+
+ qc = reg.declare(foo, QueueSettings());
+ reg.destroy(foo);
+ // Queue is gone from the registry.
+ BOOST_CHECK(reg.find(foo) == 0);
+ // Queue is not actually destroyed till we drop our reference.
+ BOOST_CHECK_EQUAL(foo, qc.first->getName());
+ // We shoud be the only reference.
+ BOOST_CHECK_EQUAL(1L, qc.first.use_count());
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/QueueTest.cpp b/qpid/cpp/src/tests/QueueTest.cpp
new file mode 100644
index 0000000000..ee9d37e76d
--- /dev/null
+++ b/qpid/cpp/src/tests/QueueTest.cpp
@@ -0,0 +1,629 @@
+ /*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "MessageUtils.h"
+#include "unit_test.h"
+#include "test_tools.h"
+#include "qpid/Exception.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/DeliverableMessage.h"
+#include "qpid/broker/FanOutExchange.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/broker/Deliverable.h"
+#include "qpid/broker/ExchangeRegistry.h"
+#include "qpid/broker/QueueRegistry.h"
+#include "qpid/broker/NullMessageStore.h"
+#include "qpid/framing/DeliveryProperties.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qpid/client/QueueOptions.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/broker/QueueFlowLimit.h"
+#include "qpid/broker/QueueSettings.h"
+#include "qpid/sys/Thread.h"
+#include "qpid/sys/Timer.h"
+
+#include <iostream>
+#include <vector>
+#include <boost/format.hpp>
+#include <boost/lexical_cast.hpp>
+
+using namespace std;
+using boost::intrusive_ptr;
+using namespace qpid;
+using namespace qpid::broker;
+using namespace qpid::client;
+using namespace qpid::framing;
+using namespace qpid::sys;
+
+namespace qpid {
+namespace tests {
+class TestConsumer : public virtual Consumer{
+public:
+ typedef boost::shared_ptr<TestConsumer> shared_ptr;
+
+ QueueCursor lastCursor;
+ Message lastMessage;
+ bool received;
+ TestConsumer(std::string name="test", bool acquire = true) : Consumer(name, acquire ? CONSUMER : BROWSER, ""), received(false) {};
+
+ virtual bool deliver(const QueueCursor& cursor, const Message& message){
+ lastCursor = cursor;
+ lastMessage = message;
+ received = true;
+ return true;
+ };
+ void notify() {}
+ void cancel() {}
+ void acknowledged(const DeliveryRecord&) {}
+ OwnershipToken* getSession() { return 0; }
+};
+
+class FailOnDeliver : public Deliverable
+{
+ Message msg;
+public:
+ FailOnDeliver() : msg(MessageUtils::createMessage()) {}
+ void deliverTo(const boost::shared_ptr<Queue>& queue)
+ {
+ throw Exception(QPID_MSG("Invalid delivery to " << queue->getName()));
+ }
+ Message& getMessage() { return msg; }
+};
+
+QPID_AUTO_TEST_SUITE(QueueTestSuite)
+
+QPID_AUTO_TEST_CASE(testBound){
+ //test the recording of bindings, and use of those to allow a queue to be unbound
+ string key("my-key");
+ FieldTable args;
+
+ Queue::shared_ptr queue(new Queue("my-queue"));
+ ExchangeRegistry exchanges;
+ //establish bindings from exchange->queue and notify the queue as it is bound:
+ Exchange::shared_ptr exchange1 = exchanges.declare("my-exchange-1", "direct").first;
+ exchange1->bind(queue, key, &args);
+ queue->bound(exchange1->getName(), key, args);
+
+ Exchange::shared_ptr exchange2 = exchanges.declare("my-exchange-2", "fanout").first;
+ exchange2->bind(queue, key, &args);
+ queue->bound(exchange2->getName(), key, args);
+
+ Exchange::shared_ptr exchange3 = exchanges.declare("my-exchange-3", "topic").first;
+ exchange3->bind(queue, key, &args);
+ queue->bound(exchange3->getName(), key, args);
+
+ //delete one of the exchanges:
+ exchanges.destroy(exchange2->getName());
+ exchange2.reset();
+
+ //unbind the queue from all exchanges it knows it has been bound to:
+ queue->unbind(exchanges);
+
+ //ensure the remaining exchanges don't still have the queue bound to them:
+ FailOnDeliver deliverable;
+ exchange1->route(deliverable);
+ exchange3->route(deliverable);
+}
+
+QPID_AUTO_TEST_CASE(testLVQ){
+
+ QueueSettings settings;
+ string key="key";
+ settings.lvqKey = key;
+ QueueFactory factory;
+ Queue::shared_ptr q(factory.create("my-queue", settings));
+
+ const char* values[] = { "a", "b", "c", "a"};
+ for (size_t i = 0; i < sizeof(values)/sizeof(values[0]); ++i) {
+ qpid::types::Variant::Map properties;
+ properties[key] = values[i];
+ q->deliver(MessageUtils::createMessage(properties, boost::lexical_cast<string>(i+1)));
+ }
+ BOOST_CHECK_EQUAL(q->getMessageCount(), 3u);
+
+ TestConsumer::shared_ptr c(new TestConsumer("test", true));
+ BOOST_CHECK(q->dispatch(c));
+ BOOST_CHECK_EQUAL(std::string("2"), c->lastMessage.getContent());
+ BOOST_CHECK(q->dispatch(c));
+ BOOST_CHECK_EQUAL(std::string("3"), c->lastMessage.getContent());
+ BOOST_CHECK(q->dispatch(c));
+ BOOST_CHECK_EQUAL(std::string("4"), c->lastMessage.getContent());
+
+
+ const char* values2[] = { "a", "b", "c"};
+ for (size_t i = 0; i < sizeof(values2)/sizeof(values2[0]); ++i) {
+ qpid::types::Variant::Map properties;
+ properties[key] = values[i];
+ q->deliver(MessageUtils::createMessage(properties, boost::lexical_cast<string>(i+5)));
+ }
+ BOOST_CHECK_EQUAL(q->getMessageCount(), 3u);
+
+ BOOST_CHECK(q->dispatch(c));
+ BOOST_CHECK_EQUAL(std::string("5"), c->lastMessage.getContent());
+ BOOST_CHECK(q->dispatch(c));
+ BOOST_CHECK_EQUAL(std::string("6"), c->lastMessage.getContent());
+ BOOST_CHECK(q->dispatch(c));
+ BOOST_CHECK_EQUAL(std::string("7"), c->lastMessage.getContent());
+}
+
+QPID_AUTO_TEST_CASE(testLVQEmptyKey){
+
+ QueueSettings settings;
+ string key="key";
+ settings.lvqKey = key;
+ QueueFactory factory;
+ Queue::shared_ptr q(factory.create("my-queue", settings));
+
+
+ qpid::types::Variant::Map properties;
+ properties["key"] = "a";
+ q->deliver(MessageUtils::createMessage(properties, "one"));
+ properties.clear();
+ q->deliver(MessageUtils::createMessage(properties, "two"));
+ BOOST_CHECK_EQUAL(q->getMessageCount(), 2u);
+}
+
+void addMessagesToQueue(uint count, Queue& queue, uint oddTtl = 200, uint evenTtl = 0)
+{
+ for (uint i = 0; i < count; i++) {
+ Message m = MessageUtils::createMessage("exchange", "key", i % 2 ? oddTtl : evenTtl);
+ queue.deliver(m);
+ }
+}
+
+QPID_AUTO_TEST_CASE(testPurgeExpired) {
+ Queue queue("my-queue");
+ addMessagesToQueue(10, queue);
+ BOOST_CHECK_EQUAL(queue.getMessageCount(), 10u);
+ ::usleep(300*1000);
+ queue.purgeExpired(0);
+ BOOST_CHECK_EQUAL(queue.getMessageCount(), 5u);
+}
+
+QPID_AUTO_TEST_CASE(testQueueCleaner) {
+ boost::shared_ptr<Poller> poller(new Poller);
+ Thread runner(poller.get());
+ Timer timer;
+ QueueRegistry queues;
+ Queue::shared_ptr queue = queues.declare("my-queue", QueueSettings()).first;
+ addMessagesToQueue(10, *queue, 200, 400);
+ BOOST_CHECK_EQUAL(queue->getMessageCount(), 10u);
+
+ QueueCleaner cleaner(queues, poller, &timer);
+ cleaner.start(100 * qpid::sys::TIME_MSEC);
+ ::usleep(300*1000);
+ BOOST_CHECK_EQUAL(queue->getMessageCount(), 5u);
+ ::usleep(300*1000);
+ BOOST_CHECK_EQUAL(queue->getMessageCount(), 0u);
+ poller->shutdown();
+ runner.join();
+}
+namespace {
+int getIntProperty(const Message& message, const std::string& key)
+{
+ qpid::types::Variant v = message.getProperty(key);
+ int i(0);
+ if (!v.isVoid()) i = v;
+ return i;
+}
+// helper for group tests
+void verifyAcquire( Queue::shared_ptr queue,
+ TestConsumer::shared_ptr c,
+ std::deque<QueueCursor>& results,
+ const std::string& expectedGroup,
+ const int expectedId )
+{
+ bool success = queue->dispatch(c);
+ BOOST_CHECK(success);
+ if (success) {
+ results.push_back(c->lastCursor);
+ std::string group = c->lastMessage.getPropertyAsString("GROUP-ID");
+ int id = getIntProperty(c->lastMessage, "MY-ID");
+ BOOST_CHECK_EQUAL( group, expectedGroup );
+ BOOST_CHECK_EQUAL( id, expectedId );
+ }
+}
+
+Message createGroupMessage(int id, const std::string& group)
+{
+ qpid::types::Variant::Map properties;
+ properties["GROUP-ID"] = group;
+ properties["MY-ID"] = id;
+ return MessageUtils::createMessage(properties);
+}
+}
+
+QPID_AUTO_TEST_CASE(testGroupsMultiConsumer) {
+ //
+ // Verify that consumers of grouped messages own the groups once a message is acquired,
+ // and release the groups once all acquired messages have been dequeued or requeued
+ //
+ QueueSettings settings;
+ settings.shareGroups = 1;
+ settings.groupKey = "GROUP-ID";
+ QueueFactory factory;
+ Queue::shared_ptr queue(factory.create("my_queue", settings));
+
+ std::string groups[] = { std::string("a"), std::string("a"), std::string("a"),
+ std::string("b"), std::string("b"), std::string("b"),
+ std::string("c"), std::string("c"), std::string("c") };
+ for (int i = 0; i < 9; ++i) {
+ queue->deliver(createGroupMessage(i, groups[i]));
+ }
+
+ // Queue = a-0, a-1, a-2, b-3, b-4, b-5, c-6, c-7, c-8...
+ // Owners= ---, ---, ---, ---, ---, ---, ---, ---, ---,
+
+ BOOST_CHECK_EQUAL(uint32_t(9), queue->getMessageCount());
+
+ TestConsumer::shared_ptr c1(new TestConsumer("C1"));
+ TestConsumer::shared_ptr c2(new TestConsumer("C2"));
+
+ queue->consume(c1);
+ queue->consume(c2);
+
+ std::deque<QueueCursor> dequeMeC1;
+ std::deque<QueueCursor> dequeMeC2;
+
+
+ verifyAcquire(queue, c1, dequeMeC1, "a", 0 ); // c1 now owns group "a" (acquire a-0)
+ verifyAcquire(queue, c2, dequeMeC2, "b", 3 ); // c2 should now own group "b" (acquire b-3)
+
+ // now let c1 complete the 'a-0' message - this should free the 'a' group
+ queue->dequeue( 0, dequeMeC1.front() );
+ dequeMeC1.pop_front();
+
+ // Queue = a-1, a-2, b-3, b-4, b-5, c-6, c-7, c-8...
+ // Owners= ---, ---, ^C2, ^C2, ^C2, ---, ---, ---
+
+ // now c2 should pick up the next 'a-1', since it is oldest free
+ verifyAcquire(queue, c2, dequeMeC2, "a", 1 ); // c2 should now own groups "a" and "b"
+
+ // Queue = a-1, a-2, b-3, b-4, b-5, c-6, c-7, c-8...
+ // Owners= ^C2, ^C2, ^C2, ^C2, ^C2, ---, ---, ---
+
+ // c1 should only be able to snarf up the first "c" message now...
+ verifyAcquire(queue, c1, dequeMeC1, "c", 6 ); // should skip to the first "c"
+
+ // Queue = a-1, a-2, b-3, b-4, b-5, c-6, c-7, c-8...
+ // Owners= ^C2, ^C2, ^C2, ^C2, ^C2, ^C1, ^C1, ^C1
+
+ // hmmm... what if c2 now dequeues "b-3"? (now only has a-1 acquired)
+ queue->dequeue( 0, dequeMeC2.front() );
+ dequeMeC2.pop_front();
+
+ // Queue = a-1, a-2, b-4, b-5, c-6, c-7, c-8...
+ // Owners= ^C2, ^C2, ---, ---, ^C1, ^C1, ^C1
+
+ // b group is free, c is owned by c1 - c1's next get should grab 'b-4'
+ verifyAcquire(queue, c1, dequeMeC1, "b", 4 );
+
+ // Queue = a-1, a-2, b-4, b-5, c-6, c-7, c-8...
+ // Owners= ^C2, ^C2, ^C1, ^C1, ^C1, ^C1, ^C1
+
+ // c2 can now only grab a-2, and that's all
+ verifyAcquire(queue, c2, dequeMeC2, "a", 2 );
+
+ // now C2 can't get any more, since C1 owns "b" and "c" group...
+ bool gotOne = queue->dispatch(c2);
+ BOOST_CHECK( !gotOne );
+
+ // hmmm... what if c1 now dequeues "c-6"? (now only own's b-4)
+ queue->dequeue( 0, dequeMeC1.front() );
+ dequeMeC1.pop_front();
+
+ // Queue = a-1, a-2, b-4, b-5, c-7, c-8...
+ // Owners= ^C2, ^C2, ^C1, ^C1, ---, ---
+
+ // c2 can now grab c-7
+ verifyAcquire(queue, c2, dequeMeC2, "c", 7 );
+
+ // Queue = a-1, a-2, b-4, b-5, c-7, c-8...
+ // Owners= ^C2, ^C2, ^C1, ^C1, ^C2, ^C2
+
+ // what happens if C-2 "requeues" a-1 and a-2?
+ queue->release( dequeMeC2.front() );
+ dequeMeC2.pop_front();
+ queue->release( dequeMeC2.front() );
+ dequeMeC2.pop_front(); // now just has c-7 acquired
+
+ // Queue = a-1, a-2, b-4, b-5, c-7, c-8...
+ // Owners= ---, ---, ^C1, ^C1, ^C2, ^C2
+
+ // now c1 will grab a-1 and a-2...
+ verifyAcquire(queue, c1, dequeMeC1, "a", 1 );
+ verifyAcquire(queue, c1, dequeMeC1, "a", 2 );
+
+ // Queue = a-1, a-2, b-4, b-5, c-7, c-8...
+ // Owners= ^C1, ^C1, ^C1, ^C1, ^C2, ^C2
+
+ // c2 can now acquire c-8 only
+ verifyAcquire(queue, c2, dequeMeC2, "c", 8 );
+
+ // and c1 can get b-5
+ verifyAcquire(queue, c1, dequeMeC1, "b", 5 );
+
+ // should be no more acquire-able for anyone now:
+ gotOne = queue->dispatch(c1);
+ BOOST_CHECK( !gotOne );
+ gotOne = queue->dispatch(c2);
+ BOOST_CHECK( !gotOne );
+
+ // release all of C1's acquired messages, then cancel C1
+ while (!dequeMeC1.empty()) {
+ queue->release(dequeMeC1.front());
+ dequeMeC1.pop_front();
+ }
+ queue->cancel(c1);
+
+ // Queue = a-1, a-2, b-4, b-5, c-7, c-8...
+ // Owners= ---, ---, ---, ---, ^C2, ^C2
+
+ // b-4, a-1, a-2, b-5 all should be available, right?
+ verifyAcquire(queue, c2, dequeMeC2, "a", 1 );
+
+ while (!dequeMeC2.empty()) {
+ queue->dequeue(0, dequeMeC2.front());
+ dequeMeC2.pop_front();
+ }
+
+ // Queue = a-2, b-4, b-5
+ // Owners= ---, ---, ---
+
+ TestConsumer::shared_ptr c3(new TestConsumer("C3"));
+ queue->consume(c3);
+ std::deque<QueueCursor> dequeMeC3;
+
+ verifyAcquire(queue, c3, dequeMeC3, "a", 2 );
+ verifyAcquire(queue, c2, dequeMeC2, "b", 4 );
+
+ // Queue = a-2, b-4, b-5
+ // Owners= ^C3, ^C2, ^C2
+
+ gotOne = queue->dispatch(c3);
+ BOOST_CHECK( !gotOne );
+
+ verifyAcquire(queue, c2, dequeMeC2, "b", 5 );
+
+ while (!dequeMeC2.empty()) {
+ queue->dequeue(0, dequeMeC2.front());
+ dequeMeC2.pop_front();
+ }
+
+ // Queue = a-2,
+ // Owners= ^C3,
+ queue->deliver(createGroupMessage(9, "a"));
+
+ // Queue = a-2, a-9
+ // Owners= ^C3, ^C3
+
+ gotOne = queue->dispatch(c2);
+ BOOST_CHECK( !gotOne );
+
+ queue->deliver(createGroupMessage(10, "b"));
+
+ // Queue = a-2, a-9, b-10
+ // Owners= ^C3, ^C3, ----
+
+ verifyAcquire(queue, c2, dequeMeC2, "b", 10 );
+ verifyAcquire(queue, c3, dequeMeC3, "a", 9 );
+
+ gotOne = queue->dispatch(c3);
+ BOOST_CHECK( !gotOne );
+
+ queue->cancel(c2);
+ queue->cancel(c3);
+}
+
+
+QPID_AUTO_TEST_CASE(testGroupsMultiConsumerDefaults) {
+ //
+ // Verify that the same default group name is automatically applied to messages that
+ // do not specify a group name.
+ //
+ QueueSettings settings;
+ settings.shareGroups = 1;
+ settings.groupKey = "GROUP-ID";
+ QueueFactory factory;
+ Queue::shared_ptr queue(factory.create("my_queue", settings));
+
+ for (int i = 0; i < 3; ++i) {
+ qpid::types::Variant::Map properties;
+ // no "GROUP-ID" header
+ properties["MY-ID"] = i;
+ queue->deliver(MessageUtils::createMessage(properties));
+ }
+
+ // Queue = 0, 1, 2
+
+ BOOST_CHECK_EQUAL(uint32_t(3), queue->getMessageCount());
+
+ TestConsumer::shared_ptr c1(new TestConsumer("C1"));
+ TestConsumer::shared_ptr c2(new TestConsumer("C2"));
+
+ queue->consume(c1);
+ queue->consume(c2);
+
+ std::deque<QueueCursor> dequeMeC1;
+ std::deque<QueueCursor> dequeMeC2;
+
+ queue->dispatch(c1); // c1 now owns default group (acquired 0)
+ dequeMeC1.push_back(c1->lastCursor);
+ int id = getIntProperty(c1->lastMessage, "MY-ID");
+ BOOST_CHECK_EQUAL( id, 0 );
+
+ bool gotOne = queue->dispatch(c2); // c2 should get nothing
+ BOOST_CHECK( !gotOne );
+
+ queue->dispatch(c1); // c1 now acquires 1
+ dequeMeC1.push_back(c1->lastCursor);
+ id = getIntProperty(c1->lastMessage, "MY-ID");
+ BOOST_CHECK_EQUAL( id, 1 );
+
+ gotOne = queue->dispatch(c2); // c2 should still get nothing
+ BOOST_CHECK( !gotOne );
+
+ while (!dequeMeC1.empty()) {
+ queue->dequeue(0, dequeMeC1.front());
+ dequeMeC1.pop_front();
+ }
+
+ // now default group should be available...
+ queue->dispatch(c2); // c2 now owns default group (acquired 2)
+ id = c2->lastMessage.getProperty("MY-ID");
+ BOOST_CHECK_EQUAL( id, 2 );
+
+ gotOne = queue->dispatch(c1); // c1 should get nothing
+ BOOST_CHECK( !gotOne );
+
+ queue->cancel(c1);
+ queue->cancel(c2);
+}
+
+QPID_AUTO_TEST_CASE(testSetPositionFifo) {
+ Queue::shared_ptr q(new Queue("my-queue", true));
+ BOOST_CHECK_EQUAL(q->getPosition(), SequenceNumber(0));
+ for (int i = 0; i < 10; ++i)
+ q->deliver(MessageUtils::createMessage(qpid::types::Variant::Map(), boost::lexical_cast<string>(i+1)));
+
+ // Verify the front of the queue
+ TestConsumer::shared_ptr c(new TestConsumer("test", false)); // Don't acquire
+ BOOST_CHECK(q->dispatch(c));
+ BOOST_CHECK_EQUAL(1u, c->lastMessage.getSequence()); // Numbered from 1
+ BOOST_CHECK_EQUAL("1", c->lastMessage.getContent());
+
+ // Verify the back of the queue
+ BOOST_CHECK_EQUAL(10u, q->getPosition());
+ BOOST_CHECK_EQUAL(10u, q->getMessageCount());
+
+ // Using setPosition to introduce a gap in sequence numbers.
+ q->setPosition(15);
+ BOOST_CHECK_EQUAL(10u, q->getMessageCount());
+ BOOST_CHECK_EQUAL(15u, q->getPosition());
+ q->deliver(MessageUtils::createMessage(qpid::types::Variant::Map(), "16"));
+
+ q->seek(*c, Queue::MessagePredicate(), 9);
+ BOOST_CHECK(q->dispatch(c));
+ BOOST_CHECK_EQUAL(10u, c->lastMessage.getSequence());
+ BOOST_CHECK_EQUAL("10", c->lastMessage.getContent());
+ BOOST_CHECK(q->dispatch(c));
+ BOOST_CHECK_EQUAL(16u, c->lastMessage.getSequence());
+ BOOST_CHECK_EQUAL("16", c->lastMessage.getContent());
+
+ // Using setPosition to trunkcate the queue
+ q->setPosition(5);
+ BOOST_CHECK_EQUAL(5u, q->getMessageCount());
+ q->deliver(MessageUtils::createMessage(qpid::types::Variant::Map(), "6a"));
+ c = boost::shared_ptr<TestConsumer>(new TestConsumer("test", false)); // Don't acquire
+ q->seek(*c, Queue::MessagePredicate(), 4);
+ BOOST_CHECK(q->dispatch(c));
+ BOOST_CHECK_EQUAL(5u, c->lastMessage.getSequence());
+ BOOST_CHECK_EQUAL("5", c->lastMessage.getContent());
+ BOOST_CHECK(q->dispatch(c));
+ BOOST_CHECK_EQUAL(6u, c->lastMessage.getSequence());
+ BOOST_CHECK_EQUAL("6a", c->lastMessage.getContent());
+ BOOST_CHECK(!q->dispatch(c)); // No more messages.
+}
+
+QPID_AUTO_TEST_CASE(testSetPositionLvq) {
+ QueueSettings settings;
+ string key="key";
+ settings.lvqKey = key;
+ QueueFactory factory;
+ Queue::shared_ptr q(factory.create("my-queue", settings));
+
+ const char* values[] = { "a", "b", "c", "a", "b", "c" };
+ for (size_t i = 0; i < sizeof(values)/sizeof(values[0]); ++i) {
+ qpid::types::Variant::Map properties;
+ properties[key] = values[i];
+ q->deliver(MessageUtils::createMessage(properties, boost::lexical_cast<string>(i+1)));
+ }
+ BOOST_CHECK_EQUAL(3u, q->getMessageCount());
+ // Verify the front of the queue
+ TestConsumer::shared_ptr c(new TestConsumer("test", false)); // Don't acquire
+ BOOST_CHECK(q->dispatch(c));
+ BOOST_CHECK_EQUAL(4u, c->lastMessage.getSequence()); // Numbered from 1
+ BOOST_CHECK_EQUAL("4", c->lastMessage.getContent());
+ // Verify the back of the queue
+ BOOST_CHECK_EQUAL(6u, q->getPosition());
+
+ q->setPosition(5);
+
+ c = boost::shared_ptr<TestConsumer>(new TestConsumer("test", false)); // Don't acquire
+ q->seek(*c, Queue::MessagePredicate(), 4);
+ BOOST_CHECK(q->dispatch(c));
+ BOOST_CHECK_EQUAL(5u, c->lastMessage.getSequence()); // Numbered from 1
+ BOOST_CHECK(!q->dispatch(c));
+}
+
+QPID_AUTO_TEST_CASE(testSetPositionPriority) {
+ QueueSettings settings;
+ settings.priorities = 10;
+ QueueFactory factory;
+ Queue::shared_ptr q(factory.create("my-queue", settings));
+
+ const int priorities[] = { 1, 2, 3, 2, 1, 3 };
+ for (size_t i = 0; i < sizeof(priorities)/sizeof(priorities[0]); ++i) {
+ qpid::types::Variant::Map properties;
+ properties["priority"] = priorities[i];
+ q->deliver(MessageUtils::createMessage(properties, boost::lexical_cast<string>(i+1)));
+ }
+
+ // Truncation removes messages in fifo order, not priority order.
+ q->setPosition(3);
+ TestConsumer::shared_ptr c(new TestConsumer("test", false)); // Browse in priority order
+ BOOST_CHECK(q->dispatch(c));
+ BOOST_CHECK_EQUAL(3u, c->lastMessage.getSequence());
+ BOOST_CHECK(q->dispatch(c));
+ BOOST_CHECK_EQUAL(2u, c->lastMessage.getSequence());
+ BOOST_CHECK(q->dispatch(c));
+ BOOST_CHECK_EQUAL(1u, c->lastMessage.getSequence());
+ BOOST_CHECK(!q->dispatch(c));
+
+ qpid::types::Variant::Map properties;
+ properties["priority"] = 4;
+ q->deliver(MessageUtils::createMessage(properties, "4a"));
+
+ BOOST_CHECK(q->dispatch(c));
+ BOOST_CHECK_EQUAL(4u, c->lastMessage.getSequence());
+ BOOST_CHECK_EQUAL("4a", c->lastMessage.getContent());
+
+ // But consumers see priority order
+ c.reset(new TestConsumer("test", true));
+ BOOST_CHECK(q->dispatch(c));
+ BOOST_CHECK_EQUAL(4u, c->lastMessage.getSequence());
+ BOOST_CHECK_EQUAL("4a", c->lastMessage.getContent());
+ BOOST_CHECK(q->dispatch(c));
+ BOOST_CHECK_EQUAL(3u, c->lastMessage.getSequence());
+ BOOST_CHECK_EQUAL("3", c->lastMessage.getContent());
+ BOOST_CHECK(q->dispatch(c));
+ BOOST_CHECK_EQUAL(2u, c->lastMessage.getSequence());
+ BOOST_CHECK_EQUAL("2", c->lastMessage.getContent());
+ BOOST_CHECK(q->dispatch(c));
+ BOOST_CHECK_EQUAL(1u, c->lastMessage.getSequence());
+ BOOST_CHECK_EQUAL("1", c->lastMessage.getContent());
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/README.txt b/qpid/cpp/src/tests/README.txt
new file mode 100644
index 0000000000..8eaa5bbd25
--- /dev/null
+++ b/qpid/cpp/src/tests/README.txt
@@ -0,0 +1,37 @@
+= Running Qpid C++ tests =
+
+General philosophy is that "make test" run all tests by default, but
+developers can run tests selectively as explained below.
+
+== Unit Tests ==
+
+Unit tests use the boost test framework, and are compiled to the programd
+unit_test
+
+There are several options to control how test results are displayed, see
+ http://www.boost.org/doc/libs/1_35_0/libs/test/doc/components/utf/parameters/index.html
+
+== System Tests ==
+
+System tests are executables or scripts. You can run executable tests directly
+as well as via "make test" or "ctest". Some tests require environment settings
+which are set by src/tests/test_env.sh on Unix or by src/tests/test_env.ps1 on
+Windows.
+
+./python_tests: runs ../python/run_tests. This is the main set of
+system tests for the broker.
+
+Other C++ client test executables and scripts under client/test are
+system tests for the client.
+
+== Running selected tests ==
+
+The make target "make test" simply runs the command "ctest". Running ctest
+directly gives you additional options, e.g.
+
+ ctest -R <regexp> -VV
+
+This runs tests with names matching the regular expression <regexp> and will
+print the full output of the tests rather than just listing which tests pass or
+fail.
+
diff --git a/qpid/cpp/src/tests/RangeSet.cpp b/qpid/cpp/src/tests/RangeSet.cpp
new file mode 100644
index 0000000000..285f432bf7
--- /dev/null
+++ b/qpid/cpp/src/tests/RangeSet.cpp
@@ -0,0 +1,154 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+
+#include "unit_test.h"
+#include "test_tools.h"
+#include "qpid/RangeSet.h"
+
+using namespace std;
+using namespace qpid;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(RangeSetTestSuite)
+
+typedef qpid::Range<int> TR; // Test Range
+typedef RangeSet<int> TRSet;
+
+QPID_AUTO_TEST_CASE(testEmptyRange) {
+ TR r;
+ BOOST_CHECK_EQUAL(r, TR(0,0));
+ BOOST_CHECK(r.empty());
+ BOOST_CHECK(!r.contains(0));
+}
+
+QPID_AUTO_TEST_CASE(testRangeSetAddPoint) {
+ TRSet r;
+ BOOST_CHECK(r.empty());
+ r += 3;
+ BOOST_CHECK_MESSAGE(r.contains(3), r);
+ BOOST_CHECK_MESSAGE(r.contains(TR(3,4)), r);
+ BOOST_CHECK(!r.empty());
+ r += 5;
+ BOOST_CHECK_MESSAGE(r.contains(5), r);
+ BOOST_CHECK_MESSAGE(r.contains(TR(5,6)), r);
+ BOOST_CHECK_MESSAGE(!r.contains(TR(3,6)), r);
+ r += 4;
+ BOOST_CHECK_MESSAGE(r.contains(TR(3,6)), r);
+}
+
+QPID_AUTO_TEST_CASE(testRangeSetAddRange) {
+ TRSet r;
+ r += TR(0,3);
+ BOOST_CHECK(r.contains(TR(0,3)));
+ BOOST_CHECK(r.contiguous());
+ r += TR(4,6);
+ BOOST_CHECK(!r.contiguous());
+ BOOST_CHECK_MESSAGE(r.contains(TR(4,6)), r);
+ r += 3;
+ BOOST_CHECK_MESSAGE(r.contains(TR(0,6)), r);
+ BOOST_CHECK(r.front() == 0);
+ BOOST_CHECK(r.back() == 6);
+
+ // Merging additions
+ r = TRSet(0,3)+TR(5,6);
+ TRSet e(0,6);
+ BOOST_CHECK_EQUAL(r + TR(3,5), e);
+ BOOST_CHECK(e.contiguous());
+ r = TRSet(0,5)+TR(10,15)+TR(20,25)+TR(30,35)+TR(40,45);
+ BOOST_CHECK_EQUAL(r + TR(11,37), TRSet(0,5)+TR(11,37)+TR(40,45));
+}
+
+QPID_AUTO_TEST_CASE(testRangeSetAddSet) {
+ TRSet r;
+ TRSet s = TRSet(0,3)+TR(5,10);
+ r += s;
+ BOOST_CHECK_EQUAL(r,s);
+ r += TRSet(3,5) + TR(7,12) + 15;
+ BOOST_CHECK_EQUAL(r, TRSet(0,12) + 15);
+
+ r.clear();
+ BOOST_CHECK(r.empty());
+ r += TR::makeClosed(6,10);
+ BOOST_CHECK_EQUAL(r, TRSet(6,11));
+ r += TRSet(2,6)+8;
+ BOOST_CHECK_EQUAL(r, TRSet(2,11));
+}
+
+QPID_AUTO_TEST_CASE(testRangeSetIterate) {
+ TRSet r = TRSet(1,3)+TR(4,7)+TR(10,11);
+ std::vector<int> actual;
+ std::copy(r.begin(), r.end(), std::back_inserter(actual));
+ std::vector<int> expect = boost::assign::list_of(1)(2)(4)(5)(6)(10);
+ BOOST_CHECK_EQUAL(expect, actual);
+}
+
+QPID_AUTO_TEST_CASE(testRangeSetRemove) {
+ // points
+ BOOST_CHECK_EQUAL(TRSet(0,5)-3, TRSet(0,3)+TR(4,5));
+ BOOST_CHECK_EQUAL(TRSet(1,5)-5, TRSet(1,5));
+ BOOST_CHECK_EQUAL(TRSet(1,5)-0, TRSet(1,5));
+
+ TRSet r(TRSet(0,5)+TR(10,15)+TR(20,25));
+
+ // TRs
+ BOOST_CHECK_EQUAL(r-TR(0,5), TRSet(10,15)+TR(20,25));
+ BOOST_CHECK_EQUAL(r-TR(10,15), TRSet(0,5)+TR(20,25));
+ BOOST_CHECK_EQUAL(r-TR(20,25), TRSet(0,5)+TR(10,15));
+
+ BOOST_CHECK_EQUAL(r-TR(-5, 30), TRSet());
+
+ BOOST_CHECK_EQUAL(r-TR(-5, 7), TRSet(10,15)+TR(20,25));
+ BOOST_CHECK_EQUAL(r-TR(8,19), TRSet(0,5)+TR(20,25));
+ BOOST_CHECK_EQUAL(r-TR(17,30), TRSet(0,5)+TR(10,15));
+
+ BOOST_CHECK_EQUAL(r-TR(-5, 5), TRSet(10,15)+TR(20,25));
+ BOOST_CHECK_EQUAL(r-TR(10,19), TRSet(0,5)+TR(20,25));
+ BOOST_CHECK_EQUAL(r-TR(18,25), TRSet(0,5)+TR(10,15));
+ BOOST_CHECK_EQUAL(r-TR(23,25), TRSet(0,5)+TR(10,15)+TR(20,23));
+
+ BOOST_CHECK_EQUAL(r-TR(-3, 3), TRSet(3,5)+TR(10,15)+TR(20,25));
+ BOOST_CHECK_EQUAL(r-TR(3, 7), TRSet(0,2)+TR(10,15)+TR(20,25));
+ BOOST_CHECK_EQUAL(r-TR(3, 12), TRSet(0,3)+TR(12,15)+TR(20,25));
+ BOOST_CHECK_EQUAL(r-TR(3, 22), TRSet(12,15)+TR(22,25));
+ BOOST_CHECK_EQUAL(r-TR(12, 22), TRSet(0,5)+TR(10,11)+TR(22,25));
+
+ // Sets
+ BOOST_CHECK_EQUAL(r-(TRSet(-1,6)+TR(11,14)+TR(23,25)),
+ TRSet(10,11)+TR(14,15)+TR(20,23));
+ // Split the ranges
+ BOOST_CHECK_EQUAL(r-(TRSet(2,3)+TR(11,13)+TR(21,23)),
+ TRSet(0,2)+TR(4,5)+
+ TR(10,11)+TR(14,15)+
+ TR(20,21)+TR(23,25));
+ // Truncate the ranges
+ BOOST_CHECK_EQUAL(r-(TRSet(0,3)+TR(13,15)+TR(19,23)),
+ TRSet(3,5)+TR(10,13)+TR(20,23));
+ // Remove multiple ranges with truncation
+ BOOST_CHECK_EQUAL(r-(TRSet(3,23)), TRSet(0,3)+TR(23,25));
+ // Remove multiple ranges in middle
+ TRSet r2 = TRSet(0,5)+TR(10,15)+TR(20,25)+TR(30,35);
+ BOOST_CHECK_EQUAL(r2-TRSet(11,24),
+ TRSet(0,5)+TR(10,11)+TR(24,25)+TR(30,35));
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/RefCounted.cpp b/qpid/cpp/src/tests/RefCounted.cpp
new file mode 100644
index 0000000000..3ac3895322
--- /dev/null
+++ b/qpid/cpp/src/tests/RefCounted.cpp
@@ -0,0 +1,55 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/RefCounted.h"
+#include <boost/intrusive_ptr.hpp>
+
+#include "unit_test.h"
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(RefCountedTestSuiteTestSuite)
+
+using boost::intrusive_ptr;
+using namespace std;
+using namespace qpid;
+
+struct CountMe : public RefCounted {
+ static int instances;
+ CountMe() { ++instances; }
+ ~CountMe() { --instances; }
+};
+
+int CountMe::instances=0;
+
+QPID_AUTO_TEST_CASE(testRefCounted) {
+ BOOST_CHECK_EQUAL(0, CountMe::instances);
+ intrusive_ptr<CountMe> p(new CountMe());
+ BOOST_CHECK_EQUAL(1, CountMe::instances);
+ intrusive_ptr<CountMe> q(p);
+ BOOST_CHECK_EQUAL(1, CountMe::instances);
+ q=0;
+ BOOST_CHECK_EQUAL(1, CountMe::instances);
+ p=0;
+ BOOST_CHECK_EQUAL(0, CountMe::instances);
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/RetryList.cpp b/qpid/cpp/src/tests/RetryList.cpp
new file mode 100644
index 0000000000..50cd5edfe8
--- /dev/null
+++ b/qpid/cpp/src/tests/RetryList.cpp
@@ -0,0 +1,111 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+
+#include "unit_test.h"
+#include "test_tools.h"
+#include "qpid/broker/RetryList.h"
+
+using namespace qpid;
+using namespace qpid::broker;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(RetryListTestSuite)
+
+struct RetryListFixture
+{
+ RetryList list;
+ std::vector<Url> urls;
+ std::vector<Address> expected;
+
+ void addUrl(const std::string& s)
+ {
+ urls.push_back(Url(s));
+ }
+
+ void addExpectation(const std::string& host, uint16_t port)
+ {
+ expected.push_back(Address("tcp", host, port));
+ }
+
+ void check()
+ {
+ list.reset(urls);
+ for (int t = 0; t < 2; t++) {
+ Address next;
+ for (std::vector<Address>::const_iterator i = expected.begin(); i != expected.end(); ++i) {
+ BOOST_CHECK(list.next(next));
+ BOOST_CHECK_EQUAL(i->host, next.host);
+ BOOST_CHECK_EQUAL(i->port, next.port);
+ }
+ BOOST_CHECK(!list.next(next));
+ }
+ }
+};
+
+QPID_AUTO_TEST_CASE(testWithSingleAddress)
+{
+ RetryListFixture test;
+ test.addUrl("amqp:host:5673");
+ test.addExpectation("host", 5673);
+ test.check();
+}
+
+QPID_AUTO_TEST_CASE(testWithSingleUrlOfMultipleAddresses)
+{
+ RetryListFixture test;
+ test.addUrl("amqp:host1,host2:2222,tcp:host3:5673,host4:1");
+
+ test.addExpectation("host1", 5672);
+ test.addExpectation("host2", 2222);
+ test.addExpectation("host3", 5673);
+ test.addExpectation("host4", 1);
+
+ test.check();
+}
+
+QPID_AUTO_TEST_CASE(testWithMultipleUrlsOfMultipleAddresses)
+{
+ RetryListFixture test;
+ test.addUrl("amqp:my-host");
+ test.addUrl("amqp:host1:6666,host2:2222,tcp:host3:5673,host4:1");
+ test.addUrl("amqp:host5,host6:2222,tcp:host7:5673");
+
+ test.addExpectation("my-host", 5672);
+ test.addExpectation("host1", 6666);
+ test.addExpectation("host2", 2222);
+ test.addExpectation("host3", 5673);
+ test.addExpectation("host4", 1);
+ test.addExpectation("host5", 5672);
+ test.addExpectation("host6", 2222);
+ test.addExpectation("host7", 5673);
+
+ test.check();
+}
+
+QPID_AUTO_TEST_CASE(testEmptyList)
+{
+ RetryListFixture test;
+ test.check();
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/Selector.cpp b/qpid/cpp/src/tests/Selector.cpp
new file mode 100644
index 0000000000..73af1a9623
--- /dev/null
+++ b/qpid/cpp/src/tests/Selector.cpp
@@ -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.
+ *
+ */
+
+#include "qpid/broker/SelectorToken.h"
+#include "qpid/broker/Selector.h"
+#include "qpid/broker/SelectorValue.h"
+
+#include "unit_test.h"
+
+#include <string>
+#include <map>
+#include <boost/ptr_container/ptr_vector.hpp>
+
+using std::string;
+using std::map;
+using std::vector;
+
+namespace qb = qpid::broker;
+
+using qpid::broker::Token;
+using qpid::broker::TokenType;
+using qpid::broker::Tokeniser;
+using qpid::broker::tokenise;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(SelectorSuite)
+
+typedef bool (*TokeniseF)(string::const_iterator&,string::const_iterator&,Token&);
+
+bool tokeniseEos(std::string::const_iterator& s, std::string::const_iterator& e, Token& tok)
+{
+ Token t1;
+ std::string::const_iterator t = s;
+ bool r = tokenise(t, e, t1);
+ if (r && (t1.type==qb::T_EOS)) {tok = t1; s = t; return true;}
+ return false;
+}
+
+bool tokeniseParens(std::string::const_iterator& s, std::string::const_iterator& e, Token& tok)
+{
+ Token t1;
+ std::string::const_iterator t = s;
+ bool r = tokenise(t, e, t1);
+ if (r && (t1.type==qb::T_LPAREN || t1.type==qb::T_RPAREN)) {tok = t1; s = t; return true;}
+ return false;
+}
+
+bool tokeniseOperator(std::string::const_iterator& s, std::string::const_iterator& e, Token& tok)
+{
+ Token t1;
+ std::string::const_iterator t = s;
+ bool r = tokenise(t, e, t1);
+ if (r && (t1.type>=qb::T_PLUS && t1.type<=qb::T_GREQ)) {tok = t1; s = t; return true;}
+ return false;
+}
+
+bool tokeniseString(std::string::const_iterator& s, std::string::const_iterator& e, Token& tok)
+{
+ Token t1;
+ std::string::const_iterator t = s;
+ bool r = tokenise(t, e, t1);
+ if (r && (t1.type==qb::T_STRING)) {tok = t1; s = t; return true;}
+ return false;
+}
+
+bool tokeniseIdentifier(std::string::const_iterator& s, std::string::const_iterator& e, Token& tok)
+{
+ Token t1;
+ std::string::const_iterator t = s;
+ bool r = tokenise(t, e, t1);
+ if (r && (t1.type==qb::T_IDENTIFIER)) {tok = t1; s = t; return true;}
+ return false;
+}
+
+bool tokeniseReservedWord(std::string::const_iterator& s, std::string::const_iterator& e, Token& tok)
+{
+ std::string::const_iterator t = s;
+ Token t1;
+ if (tokenise(t, e, t1)) {
+ switch (t1.type) {
+ case qb::T_AND:
+ case qb::T_BETWEEN:
+ case qb::T_ESCAPE:
+ case qb::T_FALSE:
+ case qb::T_IN:
+ case qb::T_IS:
+ case qb::T_LIKE:
+ case qb::T_NOT:
+ case qb::T_NULL:
+ case qb::T_OR:
+ case qb::T_TRUE:
+ tok = t1;
+ s = t;
+ return true;
+ default:
+ break;
+ }
+ }
+ return false;
+}
+
+bool tokeniseNumeric(std::string::const_iterator& s, std::string::const_iterator& e, Token& tok)
+{
+ Token t1;
+ std::string::const_iterator t = s;
+ bool r = tokenise(t, e, t1);
+ if (r && (t1.type==qb::T_NUMERIC_EXACT || t1.type==qb::T_NUMERIC_APPROX)) {tok = t1; s = t; return true;}
+ return false;
+}
+
+
+void verifyTokeniserSuccess(TokeniseF t, const char* ss, TokenType tt, const char* tv, const char* fs) {
+ Token tok;
+ string s(ss);
+ string::const_iterator sb = s.begin();
+ string::const_iterator se = s.end();
+ BOOST_CHECK(t(sb, se, tok));
+ BOOST_CHECK_EQUAL(tok, Token(tt, tv));
+ BOOST_CHECK_EQUAL(string(sb, se), fs);
+}
+
+void verifyTokeniserFail(TokeniseF t, const char* c) {
+ Token tok;
+ string s(c);
+ string::const_iterator sb = s.begin();
+ string::const_iterator se = s.end();
+ BOOST_CHECK(!t(sb, se, tok));
+ BOOST_CHECK_EQUAL(string(sb, se), c);
+}
+
+QPID_AUTO_TEST_CASE(tokeniseSuccess)
+{
+ verifyTokeniserSuccess(&tokenise, "", qb::T_EOS, "", "");
+ verifyTokeniserSuccess(&tokenise, " ", qb::T_EOS, "", "");
+ verifyTokeniserSuccess(&tokenise, "null_123+blah", qb::T_IDENTIFIER, "null_123", "+blah");
+ verifyTokeniserSuccess(&tokenise, "\"null-123\"+blah", qb::T_IDENTIFIER, "null-123", "+blah");
+ verifyTokeniserSuccess(&tokenise, "\"This is an \"\"odd!\"\" identifier\"+blah", qb::T_IDENTIFIER, "This is an \"odd!\" identifier", "+blah");
+ verifyTokeniserSuccess(&tokenise, "null+blah", qb::T_NULL, "null", "+blah");
+ verifyTokeniserSuccess(&tokenise, "null+blah", qb::T_NULL, "null", "+blah");
+ verifyTokeniserSuccess(&tokenise, "Is nOt null", qb::T_IS, "Is", " nOt null");
+ verifyTokeniserSuccess(&tokenise, "nOt null", qb::T_NOT, "nOt", " null");
+ verifyTokeniserSuccess(&tokenise, "Is nOt null", qb::T_IS, "Is", " nOt null");
+ verifyTokeniserSuccess(&tokenise, "'Hello World'", qb::T_STRING, "Hello World", "");
+ verifyTokeniserSuccess(&tokenise, "'Hello World''s end'a bit more", qb::T_STRING, "Hello World's end", "a bit more");
+ verifyTokeniserSuccess(&tokenise, "=blah", qb::T_EQUAL, "=", "blah");
+ verifyTokeniserSuccess(&tokenise, "<> Identifier", qb::T_NEQ, "<>", " Identifier");
+ verifyTokeniserSuccess(&tokenise, "(a and b) not c", qb::T_LPAREN, "(", "a and b) not c");
+ verifyTokeniserSuccess(&tokenise, ") not c", qb::T_RPAREN, ")", " not c");
+ verifyTokeniserSuccess(&tokenise, "019kill", qb::T_NUMERIC_EXACT, "019", "kill");
+ verifyTokeniserSuccess(&tokenise, "0kill", qb::T_NUMERIC_EXACT, "0", "kill");
+ verifyTokeniserSuccess(&tokenise, "0.kill", qb::T_NUMERIC_APPROX, "0.", "kill");
+ verifyTokeniserSuccess(&tokenise, "3.1415=pi", qb::T_NUMERIC_APPROX, "3.1415", "=pi");
+ verifyTokeniserSuccess(&tokenise, ".25.kill", qb::T_NUMERIC_APPROX, ".25", ".kill");
+ verifyTokeniserSuccess(&tokenise, "2e5.kill", qb::T_NUMERIC_APPROX, "2e5", ".kill");
+ verifyTokeniserSuccess(&tokenise, "3.e50easy to kill", qb::T_NUMERIC_APPROX, "3.e50", "easy to kill");
+ verifyTokeniserSuccess(&tokenise, "34.25e+50easy to kill", qb::T_NUMERIC_APPROX, "34.25e+50", "easy to kill");
+ verifyTokeniserSuccess(&tokenise, "34.e-50easy to kill", qb::T_NUMERIC_APPROX, "34.e-50", "easy to kill");
+}
+
+QPID_AUTO_TEST_CASE(tokeniseFailure)
+{
+ verifyTokeniserFail(&tokeniseEos, "hb23");
+ verifyTokeniserFail(&tokeniseIdentifier, "123");
+ verifyTokeniserFail(&tokeniseIdentifier, "'Embedded 123'");
+ verifyTokeniserFail(&tokeniseReservedWord, "1.2e5");
+ verifyTokeniserFail(&tokeniseReservedWord, "'Stringy thing'");
+ verifyTokeniserFail(&tokeniseReservedWord, "oR_andsomething");
+ verifyTokeniserFail(&tokeniseString, "'Embedded 123");
+ verifyTokeniserFail(&tokeniseString, "'This isn''t fair");
+ verifyTokeniserFail(&tokeniseOperator, "123");
+ verifyTokeniserFail(&tokeniseOperator, "'Stringy thing'");
+ verifyTokeniserFail(&tokeniseOperator, "NoT");
+ verifyTokeniserFail(&tokeniseOperator, "(a and b)");
+ verifyTokeniserFail(&tokeniseOperator, ")");
+ verifyTokeniserFail(&tokeniseParens, "=");
+ verifyTokeniserFail(&tokeniseParens, "what ho!");
+ verifyTokeniserFail(&tokeniseNumeric, "kill");
+ verifyTokeniserFail(&tokeniseNumeric, "e3");
+ verifyTokeniserFail(&tokeniseNumeric, "1.e.5");
+ verifyTokeniserFail(&tokeniseNumeric, ".e5");
+ verifyTokeniserFail(&tokeniseNumeric, "34e");
+ verifyTokeniserFail(&tokeniseNumeric, ".3e+");
+ verifyTokeniserFail(&tokeniseNumeric, ".3e-.");
+}
+
+QPID_AUTO_TEST_CASE(tokenString)
+{
+
+ string exp(" a =b");
+ string::const_iterator s = exp.begin();
+ string::const_iterator e = exp.end();
+ Tokeniser t(s, e);
+
+ BOOST_CHECK_EQUAL(t.nextToken(), Token(qb::T_IDENTIFIER, "a"));
+ BOOST_CHECK_EQUAL(t.nextToken(), Token(qb::T_EQUAL, "="));
+ BOOST_CHECK_EQUAL(t.nextToken(), Token(qb::T_IDENTIFIER, "b"));
+ BOOST_CHECK_EQUAL(t.nextToken(), Token(qb::T_EOS, ""));
+
+ exp = " not 'hello kitty''s friend' = Is null ";
+ s = exp.begin();
+ e = exp.end();
+ Tokeniser u(s, e);
+
+ BOOST_CHECK_EQUAL(u.nextToken(), Token(qb::T_NOT, "not"));
+ BOOST_CHECK_EQUAL(u.nextToken(), Token(qb::T_STRING, "hello kitty's friend"));
+ BOOST_CHECK_EQUAL(u.nextToken(), Token(qb::T_EQUAL, "="));
+ BOOST_CHECK_EQUAL(u.nextToken(), Token(qb::T_IS, "Is"));
+ BOOST_CHECK_EQUAL(u.nextToken(), Token(qb::T_NULL, "null"));
+ BOOST_CHECK_EQUAL(u.nextToken(), Token(qb::T_EOS, ""));
+ BOOST_CHECK_EQUAL(u.nextToken(), Token(qb::T_EOS, ""));
+
+ u.returnTokens(3);
+ BOOST_CHECK_EQUAL(u.nextToken(), Token(qb::T_IS, "Is"));
+ BOOST_CHECK_EQUAL(u.nextToken(), Token(qb::T_NULL, "null"));
+ BOOST_CHECK_EQUAL(u.nextToken(), Token(qb::T_EOS, ""));
+ BOOST_CHECK_EQUAL(u.nextToken(), Token(qb::T_EOS, ""));
+
+ exp = "(a+6)*7.5/1e6";
+ s = exp.begin();
+ e = exp.end();
+ Tokeniser v(s, e);
+
+ BOOST_CHECK_EQUAL(v.nextToken(), Token(qb::T_LPAREN, "("));
+ BOOST_CHECK_EQUAL(v.nextToken(), Token(qb::T_IDENTIFIER, "a"));
+ BOOST_CHECK_EQUAL(v.nextToken(), Token(qb::T_PLUS, "+"));
+ BOOST_CHECK_EQUAL(v.nextToken(), Token(qb::T_NUMERIC_EXACT, "6"));
+ BOOST_CHECK_EQUAL(v.nextToken(), Token(qb::T_RPAREN, ")"));
+ BOOST_CHECK_EQUAL(v.nextToken(), Token(qb::T_MULT, "*"));
+ BOOST_CHECK_EQUAL(v.nextToken(), Token(qb::T_NUMERIC_APPROX, "7.5"));
+ BOOST_CHECK_EQUAL(v.nextToken(), Token(qb::T_DIV, "/"));
+ BOOST_CHECK_EQUAL(v.nextToken(), Token(qb::T_NUMERIC_APPROX, "1e6"));
+}
+
+QPID_AUTO_TEST_CASE(parseStringFail)
+{
+ BOOST_CHECK_THROW(qb::Selector e("hello world"), std::range_error);
+ BOOST_CHECK_THROW(qb::Selector e("hello ^ world"), std::range_error);
+ BOOST_CHECK_THROW(qb::Selector e("A is null not"), std::range_error);
+ BOOST_CHECK_THROW(qb::Selector e("A is null or not"), std::range_error);
+ BOOST_CHECK_THROW(qb::Selector e("A is null or and"), std::range_error);
+ BOOST_CHECK_THROW(qb::Selector e("A is null and (B='hello out there'"), std::range_error);
+ BOOST_CHECK_THROW(qb::Selector e("in='hello kitty'"), std::range_error);
+ BOOST_CHECK_THROW(qb::Selector e("A like 234"), std::range_error);
+ BOOST_CHECK_THROW(qb::Selector e("A not 234 escape"), std::range_error);
+ BOOST_CHECK_THROW(qb::Selector e("A not like 'eclecti_' escape 'happy'"), std::range_error);
+ BOOST_CHECK_THROW(qb::Selector e("A not like 'eclecti_' escape happy"), std::range_error);
+ BOOST_CHECK_THROW(qb::Selector e("A not like 'eclecti_' escape '%'"), std::range_error);
+ BOOST_CHECK_THROW(qb::Selector e("A BETWEEN AND 'true'"), std::range_error);
+ BOOST_CHECK_THROW(qb::Selector e("A NOT BETWEEN 34 OR 3.9"), std::range_error);
+ BOOST_CHECK_THROW(qb::Selector e("A IN ()"), std::range_error);
+ BOOST_CHECK_THROW(qb::Selector e("A NOT IN ()"), std::range_error);
+ BOOST_CHECK_THROW(qb::Selector e("A IN 'hello', 'there', 1, true, (1-17))"), std::range_error);
+ BOOST_CHECK_THROW(qb::Selector e("A IN ('hello', 'there' 1, true, (1-17))"), std::range_error);
+}
+
+QPID_AUTO_TEST_CASE(parseString)
+{
+ BOOST_CHECK_NO_THROW(qb::Selector e("'Daft' is not null"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("42 is null"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("A is not null"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("A is null"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("A = C"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("A <> C"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("A='hello kitty'"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("A<>'hello kitty'"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("A=B"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("A<>B"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("A='hello kitty' OR B='Bye, bye cruel world'"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("B='hello kitty' AnD A='Bye, bye cruel world'"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("A is null or A='Bye, bye cruel world'"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("Z is null OR A is not null and A<>'Bye, bye cruel world'"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("(Z is null OR A is not null) and A<>'Bye, bye cruel world'"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("NOT C is not null OR C is null"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("Not A='' or B=z"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("Not A=17 or B=5.6"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("A<>17 and B=5.6e17"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("A LIKE 'excep%ional'"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("B NOT LIKE 'excep%ional'"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("A LIKE 'excep%ional' EScape '\'"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("A BETWEEN 13 AND 'true'"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("A NOT BETWEEN 100 AND 3.9"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("true"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("-354"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("-(X or Y)"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("-687 or 567"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("(354.6)"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("A is null and 'hello out there'"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("17/4>4"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("17/4>+4"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("17/4>-4"));
+ BOOST_CHECK_NO_THROW(qb::Selector e("A IN ('hello', 'there', 1 , true, (1-17))"));
+}
+
+class TestSelectorEnv : public qpid::broker::SelectorEnv {
+ mutable map<string, qb::Value> values;
+ boost::ptr_vector<string> strings;
+ static const qb::Value EMPTY;
+
+ const qb::Value& value(const string& v) const {
+ const qb::Value& r = values.find(v)!=values.end() ? values[v] : EMPTY;
+ return r;
+ }
+
+public:
+ void set(const string& id, const char* value) {
+ strings.push_back(new string(value));
+ values[id] = strings[strings.size()-1];
+ }
+
+ void set(const string& id, const qb::Value& value) {
+ if (value.type==qb::Value::T_STRING) {
+ strings.push_back(new string(*value.s));
+ values[id] = strings[strings.size()-1];
+ } else {
+ values[id] = value;
+ }
+ }
+};
+
+const qb::Value TestSelectorEnv::EMPTY;
+
+QPID_AUTO_TEST_CASE(simpleEval)
+{
+ TestSelectorEnv env;
+ env.set("A", "Bye, bye cruel world");
+ env.set("B", "hello kitty");
+
+ BOOST_CHECK(qb::Selector("").eval(env));
+ BOOST_CHECK(qb::Selector(" ").eval(env));
+ BOOST_CHECK(qb::Selector("A is not null").eval(env));
+ BOOST_CHECK(!qb::Selector("A is null").eval(env));
+ BOOST_CHECK(!qb::Selector("A = C").eval(env));
+ BOOST_CHECK(!qb::Selector("A <> C").eval(env));
+ BOOST_CHECK(!qb::Selector("C is not null").eval(env));
+ BOOST_CHECK(qb::Selector("C is null").eval(env));
+ BOOST_CHECK(qb::Selector("A='Bye, bye cruel world'").eval(env));
+ BOOST_CHECK(!qb::Selector("A<>'Bye, bye cruel world'").eval(env));
+ BOOST_CHECK(!qb::Selector("A='hello kitty'").eval(env));
+ BOOST_CHECK(qb::Selector("A<>'hello kitty'").eval(env));
+ BOOST_CHECK(!qb::Selector("A=B").eval(env));
+ BOOST_CHECK(qb::Selector("A<>B").eval(env));
+ BOOST_CHECK(!qb::Selector("A='hello kitty' OR B='Bye, bye cruel world'").eval(env));
+ BOOST_CHECK(qb::Selector("B='hello kitty' OR A='Bye, bye cruel world'").eval(env));
+ BOOST_CHECK(qb::Selector("B='hello kitty' AnD A='Bye, bye cruel world'").eval(env));
+ BOOST_CHECK(!qb::Selector("B='hello kitty' AnD B='Bye, bye cruel world'").eval(env));
+ BOOST_CHECK(qb::Selector("A is null or A='Bye, bye cruel world'").eval(env));
+ BOOST_CHECK(qb::Selector("Z is null OR A is not null and A<>'Bye, bye cruel world'").eval(env));
+ BOOST_CHECK(!qb::Selector("(Z is null OR A is not null) and A<>'Bye, bye cruel world'").eval(env));
+ BOOST_CHECK(qb::Selector("NOT C is not null OR C is null").eval(env));
+ BOOST_CHECK(qb::Selector("Not A='' or B=z").eval(env));
+ BOOST_CHECK(qb::Selector("Not A=17 or B=5.6").eval(env));
+ BOOST_CHECK(!qb::Selector("A<>17 and B=5.6e17").eval(env));
+ BOOST_CHECK(!qb::Selector("C=D").eval(env));
+ BOOST_CHECK(qb::Selector("13 is not null").eval(env));
+ BOOST_CHECK(!qb::Selector("'boo!' is null").eval(env));
+ BOOST_CHECK(qb::Selector("A LIKE '%cru_l%'").eval(env));
+ BOOST_CHECK(qb::Selector("'_%%_hello.th_re%' LIKE 'z_%.%z_%z%' escape 'z'").eval(env));
+ BOOST_CHECK(qb::Selector("A NOT LIKE 'z_%.%z_%z%' escape 'z'").eval(env));
+ BOOST_CHECK(qb::Selector("'{}[]<>,.!\"$%^&*()_-+=?/|\\' LIKE '{}[]<>,.!\"$z%^&*()z_-+=?/|\\' escape 'z'").eval(env));
+}
+
+QPID_AUTO_TEST_CASE(numericEval)
+{
+ TestSelectorEnv env;
+ env.set("A", 42.0);
+ env.set("B", 39);
+
+ BOOST_CHECK(qb::Selector("A>B").eval(env));
+ BOOST_CHECK(qb::Selector("A=42").eval(env));
+ BOOST_CHECK(qb::Selector("B=39.0").eval(env));
+ BOOST_CHECK(qb::Selector("Not A=17 or B=5.6").eval(env));
+ BOOST_CHECK(!qb::Selector("A<>17 and B=5.6e17").eval(env));
+ BOOST_CHECK(qb::Selector("3 BETWEEN -17 and 98.5").eval(env));
+ BOOST_CHECK(qb::Selector("A BETWEEN B and 98.5").eval(env));
+ BOOST_CHECK(!qb::Selector("B NOT BETWEEN 35 AND 100").eval(env));
+ BOOST_CHECK(!qb::Selector("A BETWEEN B and 40").eval(env));
+ BOOST_CHECK(!qb::Selector("A BETWEEN C and 40").eval(env));
+ BOOST_CHECK(!qb::Selector("A BETWEEN 45 and C").eval(env));
+ BOOST_CHECK(qb::Selector("(A BETWEEN 40 and C) IS NULL").eval(env));
+ BOOST_CHECK(qb::Selector("(A BETWEEN C and 45) IS NULL").eval(env));
+ BOOST_CHECK(qb::Selector("17/4=4").eval(env));
+ BOOST_CHECK(!qb::Selector("A/0=0").eval(env));
+ BOOST_CHECK(qb::Selector("A*B+19<A*(B+19)").eval(env));
+ BOOST_CHECK(qb::Selector("-A=0-A").eval(env));
+}
+
+QPID_AUTO_TEST_CASE(comparisonEval)
+{
+ TestSelectorEnv env;
+
+ BOOST_CHECK(!qb::Selector("17 > 19.0").eval(env));
+ BOOST_CHECK(!qb::Selector("'hello' > 19.0").eval(env));
+ BOOST_CHECK(!qb::Selector("'hello' < 19.0").eval(env));
+ BOOST_CHECK(!qb::Selector("'hello' = 19.0").eval(env));
+ BOOST_CHECK(!qb::Selector("'hello'>42 and 'hello'<42 and 'hello'=42 and 'hello'<>42").eval(env));
+ BOOST_CHECK(qb::Selector("20 >= 19.0 and 20 > 19").eval(env));
+ BOOST_CHECK(qb::Selector("42 <= 42.0 and 37.0 >= 37").eval(env));
+ BOOST_CHECK(qb::Selector("(A IN ('hello', 'there', 1 , true, (1-17))) IS NULL").eval(env));
+ BOOST_CHECK(qb::Selector("'hello' IN ('hello', 'there', 1 , true, (1-17))").eval(env));
+ BOOST_CHECK(qb::Selector("TRUE IN ('hello', 'there', 1 , true, (1-17))").eval(env));
+ BOOST_CHECK(qb::Selector("-16 IN ('hello', 'there', 1 , true, (1-17))").eval(env));
+ BOOST_CHECK(!qb::Selector("'hell' IN ('hello', 'there', 1 , true, (1-17))").eval(env));
+ BOOST_CHECK(qb::Selector("('hell' IN ('hello', 'there', 1 , true, (1-17), A)) IS NULL").eval(env));
+}
+
+QPID_AUTO_TEST_CASE(NullEval)
+{
+ TestSelectorEnv env;
+
+ BOOST_CHECK(qb::Selector("P > 19.0 or (P is null)").eval(env));
+ BOOST_CHECK(qb::Selector("P is null or P=''").eval(env));
+ BOOST_CHECK(!qb::Selector("P=Q").eval(env));
+ BOOST_CHECK(!qb::Selector("not P=Q").eval(env));
+ BOOST_CHECK(!qb::Selector("not P=Q and not P=Q").eval(env));
+ BOOST_CHECK(!qb::Selector("P=Q or not P=Q").eval(env));
+ BOOST_CHECK(!qb::Selector("P > 19.0 or P <= 19.0").eval(env));
+ BOOST_CHECK(qb::Selector("P > 19.0 or 17 <= 19.0").eval(env));
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}}
diff --git a/qpid/cpp/src/tests/SequenceNumberTest.cpp b/qpid/cpp/src/tests/SequenceNumberTest.cpp
new file mode 100644
index 0000000000..f3c934e3ca
--- /dev/null
+++ b/qpid/cpp/src/tests/SequenceNumberTest.cpp
@@ -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.
+ *
+ */
+
+#include "unit_test.h"
+#include <iostream>
+#include "qpid/framing/SequenceNumber.h"
+#include "qpid/framing/SequenceNumberSet.h"
+
+using namespace qpid::framing;
+
+namespace qpid {
+namespace tests {
+
+void checkDifference(SequenceNumber& a, SequenceNumber& b, int gap)
+{
+ BOOST_CHECK_EQUAL(gap, a - b);
+ BOOST_CHECK_EQUAL(-gap, b - a);
+
+ //increment until b wraps around
+ for (int i = 0; i < (gap + 2); i++, ++a, ++b) {
+ BOOST_CHECK_EQUAL(gap, a - b);
+ }
+ //keep incrementing until a also wraps around
+ for (int i = 0; i < (gap + 2); i++, ++a, ++b) {
+ BOOST_CHECK_EQUAL(gap, a - b);
+ }
+ //let b catch up and overtake
+ for (int i = 0; i < (gap*2); i++, ++b) {
+ BOOST_CHECK_EQUAL(gap - i, a - b);
+ BOOST_CHECK_EQUAL(i - gap, b - a);
+ }
+}
+
+void checkComparison(SequenceNumber& a, SequenceNumber& b, int gap)
+{
+ //increment until b wraps around
+ for (int i = 0; i < (gap + 2); i++) {
+ BOOST_CHECK(++a < ++b);//test prefix
+ }
+ //keep incrementing until a also wraps around
+ for (int i = 0; i < (gap + 2); i++) {
+ BOOST_CHECK(a++ < b++);//test postfix
+ }
+ //let a 'catch up'
+ for (int i = 0; i < gap; i++) {
+ a++;
+ }
+ BOOST_CHECK(a == b);
+ BOOST_CHECK(++a > b);
+}
+
+
+QPID_AUTO_TEST_SUITE(SequenceNumberTestSuite)
+
+QPID_AUTO_TEST_CASE(testIncrementPostfix)
+{
+ SequenceNumber a;
+ SequenceNumber b;
+ BOOST_CHECK(!(a > b));
+ BOOST_CHECK(!(b < a));
+ BOOST_CHECK(a == b);
+
+ SequenceNumber c = a++;
+ BOOST_CHECK(a > b);
+ BOOST_CHECK(b < a);
+ BOOST_CHECK(a != b);
+ BOOST_CHECK(c < a);
+ BOOST_CHECK(a != c);
+
+ b++;
+ BOOST_CHECK(!(a > b));
+ BOOST_CHECK(!(b < a));
+ BOOST_CHECK(a == b);
+ BOOST_CHECK(c < b);
+ BOOST_CHECK(b != c);
+}
+
+QPID_AUTO_TEST_CASE(testIncrementPrefix)
+{
+ SequenceNumber a;
+ SequenceNumber b;
+ BOOST_CHECK(!(a > b));
+ BOOST_CHECK(!(b < a));
+ BOOST_CHECK(a == b);
+
+ SequenceNumber c = ++a;
+ BOOST_CHECK(a > b);
+ BOOST_CHECK(b < a);
+ BOOST_CHECK(a != b);
+ BOOST_CHECK(a == c);
+
+ ++b;
+ BOOST_CHECK(!(a > b));
+ BOOST_CHECK(!(b < a));
+ BOOST_CHECK(a == b);
+}
+
+QPID_AUTO_TEST_CASE(testWrapAround)
+{
+ const uint32_t max = 0xFFFFFFFF;
+ SequenceNumber a(max - 10);
+ SequenceNumber b(max - 5);
+ checkComparison(a, b, 5);
+
+ const uint32_t max_signed = 0x7FFFFFFF;
+ SequenceNumber c(max_signed - 10);
+ SequenceNumber d(max_signed - 5);
+ checkComparison(c, d, 5);
+}
+
+QPID_AUTO_TEST_CASE(testCondense)
+{
+ SequenceNumberSet set;
+ for (uint i = 0; i < 6; i++) {
+ set.push_back(SequenceNumber(i));
+ }
+ set.push_back(SequenceNumber(7));
+ for (uint i = 9; i < 13; i++) {
+ set.push_back(SequenceNumber(i));
+ }
+ set.push_back(SequenceNumber(13));
+ SequenceNumberSet actual = set.condense();
+
+ SequenceNumberSet expected;
+ expected.addRange(SequenceNumber(0), SequenceNumber(5));
+ expected.addRange(SequenceNumber(7), SequenceNumber(7));
+ expected.addRange(SequenceNumber(9), SequenceNumber(13));
+ BOOST_CHECK_EQUAL(expected, actual);
+}
+
+QPID_AUTO_TEST_CASE(testCondenseSingleRange)
+{
+ SequenceNumberSet set;
+ for (uint i = 0; i < 6; i++) {
+ set.push_back(SequenceNumber(i));
+ }
+ SequenceNumberSet actual = set.condense();
+
+ SequenceNumberSet expected;
+ expected.addRange(SequenceNumber(0), SequenceNumber(5));
+ BOOST_CHECK_EQUAL(expected, actual);
+}
+
+QPID_AUTO_TEST_CASE(testCondenseSingleItem)
+{
+ SequenceNumberSet set;
+ set.push_back(SequenceNumber(1));
+ SequenceNumberSet actual = set.condense();
+
+ SequenceNumberSet expected;
+ expected.addRange(SequenceNumber(1), SequenceNumber(1));
+ BOOST_CHECK_EQUAL(expected, actual);
+}
+
+QPID_AUTO_TEST_CASE(testDifference)
+{
+ SequenceNumber a;
+ SequenceNumber b;
+
+ for (int i = 0; i < 10; i++, ++a) {
+ BOOST_CHECK_EQUAL(i, a - b);
+ BOOST_CHECK_EQUAL(-i, b - a);
+ }
+
+ b = a;
+
+ for (int i = 0; i < 10; i++, ++b) {
+ BOOST_CHECK_EQUAL(-i, a - b);
+ BOOST_CHECK_EQUAL(i, b - a);
+ }
+}
+
+QPID_AUTO_TEST_CASE(testDifferenceWithWrapAround1)
+{
+ const uint32_t max = 0xFFFFFFFF;
+ SequenceNumber a(max - 5);
+ SequenceNumber b(max - 10);
+ checkDifference(a, b, 5);
+}
+
+QPID_AUTO_TEST_CASE(testDifferenceWithWrapAround2)
+{
+ const uint32_t max_signed = 0x7FFFFFFF;
+ SequenceNumber c(max_signed - 5);
+ SequenceNumber d(max_signed - 10);
+ checkDifference(c, d, 5);
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/SequenceSet.cpp b/qpid/cpp/src/tests/SequenceSet.cpp
new file mode 100644
index 0000000000..bc0a8ea509
--- /dev/null
+++ b/qpid/cpp/src/tests/SequenceSet.cpp
@@ -0,0 +1,187 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/framing/SequenceSet.h"
+#include "unit_test.h"
+#include <list>
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(SequenceSetTestSuite)
+
+using namespace qpid::framing;
+
+struct RangeExpectations
+{
+ typedef std::pair<SequenceNumber, SequenceNumber> Range;
+ typedef std::list<Range> Ranges;
+
+ Ranges ranges;
+
+ RangeExpectations& expect(const SequenceNumber& start, const SequenceNumber& end) {
+ ranges.push_back(Range(start, end));
+ return *this;
+ }
+
+ void operator()(const SequenceNumber& start, const SequenceNumber& end) {
+ BOOST_CHECK(!ranges.empty());
+ if (!ranges.empty()) {
+ BOOST_CHECK_EQUAL(start, ranges.front().first);
+ BOOST_CHECK_EQUAL(end, ranges.front().second);
+ ranges.pop_front();
+ }
+ }
+
+ void check(SequenceSet& set) {
+ set.for_each(*this);
+ BOOST_CHECK(ranges.empty());
+ }
+};
+
+QPID_AUTO_TEST_CASE(testAdd) {
+ SequenceSet s;
+ s.add(2);
+ s.add(8,8);
+ s.add(3,5);
+
+ for (uint32_t i = 0; i <= 1; i++)
+ BOOST_CHECK(!s.contains(i));
+
+ for (uint32_t i = 2; i <= 5; i++)
+ BOOST_CHECK(s.contains(i));
+
+ for (uint32_t i = 6; i <= 7; i++)
+ BOOST_CHECK(!s.contains(i));
+
+ BOOST_CHECK(s.contains(8));
+
+ for (uint32_t i = 9; i <= 10; i++)
+ BOOST_CHECK(!s.contains(i));
+
+ RangeExpectations().expect(2, 5).expect(8, 8).check(s);
+
+ SequenceSet t;
+ t.add(6, 10);
+ t.add(s);
+
+ for (uint32_t i = 0; i <= 1; i++)
+ BOOST_CHECK(!t.contains(i));
+
+ for (uint32_t i = 2; i <= 10; i++)
+ BOOST_CHECK_MESSAGE(t.contains(i), t << " contains " << i);
+
+ RangeExpectations().expect(2, 10).check(t);
+}
+
+QPID_AUTO_TEST_CASE(testAdd2) {
+ SequenceSet s;
+ s.add(7,6);
+ s.add(4,4);
+ s.add(3,10);
+ s.add(2);
+ RangeExpectations().expect(2, 10).check(s);
+}
+
+QPID_AUTO_TEST_CASE(testRemove) {
+ SequenceSet s;
+ SequenceSet t;
+ s.add(0, 10);
+ t.add(0, 10);
+
+ s.remove(7);
+ s.remove(3, 5);
+ s.remove(9, 10);
+
+ t.remove(s);
+
+ for (uint32_t i = 0; i <= 2; i++) {
+ BOOST_CHECK(s.contains(i));
+ BOOST_CHECK(!t.contains(i));
+ }
+
+ for (uint32_t i = 3; i <= 5; i++) {
+ BOOST_CHECK(!s.contains(i));
+ BOOST_CHECK(t.contains(i));
+ }
+
+ BOOST_CHECK(s.contains(6));
+ BOOST_CHECK(!t.contains(6));
+
+ BOOST_CHECK(!s.contains(7));
+ BOOST_CHECK(t.contains(7));
+
+ BOOST_CHECK(s.contains(8));
+ BOOST_CHECK(!t.contains(8));
+
+ for (uint32_t i = 9; i <= 10; i++) {
+ BOOST_CHECK(!s.contains(i));
+ BOOST_CHECK(t.contains(i));
+ }
+
+ RangeExpectations().expect(0, 2).expect(6, 6).expect(8, 8).check(s);
+ RangeExpectations().expect(3, 5).expect(7, 7).expect(9, 10).check(t);
+}
+
+
+QPID_AUTO_TEST_CASE(testOutOfOrderRemove) {
+
+ SequenceSet s(2, 20);
+
+ // test remove from middle:
+ s.remove(7);
+ RangeExpectations().expect(2, 6).expect(8, 20).check(s);
+ s.remove(14);
+ RangeExpectations().expect(2, 6).expect(8, 13).expect(15, 20).check(s);
+
+ // remove from front of subrange:
+ s.remove(8, 8);
+ RangeExpectations().expect(2, 6).expect(9, 13).expect(15, 20).check(s);
+
+ // remove from tail of subrange:
+ s.remove(6);
+ RangeExpectations().expect(2, 5).expect(9, 13).expect(15, 20).check(s);
+
+ // remove across subrange:
+ s.remove(13, 15);
+ RangeExpectations().expect(2, 5).expect(9, 12).expect(16, 20).check(s);
+
+ // remove within subrange:
+ s.remove(6, 8);
+ RangeExpectations().expect(2, 5).expect(9, 12).expect(16, 20).check(s);
+
+ // remove overlap subrange tail:
+ s.remove(11, 15);
+ RangeExpectations().expect(2, 5).expect(9, 10).expect(16, 20).check(s);
+
+ // remove overlap subrange head:
+ s.remove(14, 17);
+ RangeExpectations().expect(2, 5).expect(9, 10).expect(18, 20).check(s);
+
+ // remove overlap sequence tail:
+ s.remove(20, 22);
+ RangeExpectations().expect(2, 5).expect(9, 10).expect(18, 19).check(s);
+
+ // remove overlap sequence head:
+ s.remove(1, 3);
+ RangeExpectations().expect(4, 5).expect(9, 10).expect(18, 19).check(s);
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/SessionState.cpp b/qpid/cpp/src/tests/SessionState.cpp
new file mode 100644
index 0000000000..1cf3415484
--- /dev/null
+++ b/qpid/cpp/src/tests/SessionState.cpp
@@ -0,0 +1,303 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "unit_test.h"
+
+#include "qpid/SessionState.h"
+#include "qpid/Exception.h"
+#include "qpid/framing/MessageTransferBody.h"
+#include "qpid/framing/SessionFlushBody.h"
+
+#include <boost/bind.hpp>
+#include <algorithm>
+#include <functional>
+#include <numeric>
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(SessionStateTestSuite)
+
+using namespace std;
+using namespace qpid::framing;
+
+// ================================================================
+// Utility functions.
+
+// Apply f to [begin, end) and accumulate the result
+template <class Iter, class T, class F>
+T applyAccumulate(Iter begin, Iter end, T seed, const F& f) {
+ return std::accumulate(begin, end, seed, boost::bind(std::plus<T>(), _1, boost::bind(f, _2)));
+}
+
+// Create a frame with a one-char string.
+AMQFrame& frame(char s) {
+ static AMQFrame frame((AMQContentBody(string(&s, 1))));
+ return frame;
+}
+
+// Simple string representation of a frame.
+string str(const AMQFrame& f) {
+ if (f.getMethod()) return "C"; // Command or Control
+ const AMQContentBody* c = dynamic_cast<const AMQContentBody*>(f.getBody());
+ if (c) return c->getData(); // Return data for content frames.
+ return "H"; // Must be a header.
+}
+// Make a string from a range of frames.
+string str(const boost::iterator_range<vector<AMQFrame>::const_iterator>& frames) {
+ string (*strFrame)(const AMQFrame&) = str;
+ return applyAccumulate(frames.begin(), frames.end(), string(), ptr_fun(strFrame));
+}
+// Make a transfer command frame.
+AMQFrame transferFrame(bool hasContent) {
+ AMQFrame t((MessageTransferBody()));
+ t.setFirstFrame(true);
+ t.setLastFrame(true);
+ t.setFirstSegment(true);
+ t.setLastSegment(!hasContent);
+ return t;
+}
+// Make a content frame
+AMQFrame contentFrame(string content, bool isLast=true) {
+ AMQFrame f((AMQContentBody(content)));
+ f.setFirstFrame(true);
+ f.setLastFrame(true);
+ f.setFirstSegment(false);
+ f.setLastSegment(isLast);
+ return f;
+}
+AMQFrame contentFrameChar(char content, bool isLast=true) {
+ return contentFrame(string(1, content), isLast);
+}
+
+// Send frame & return size of frame.
+size_t send(qpid::SessionState& s, const AMQFrame& f) { s.senderRecord(f); return f.encodedSize(); }
+// Send transfer command with no content.
+size_t transfer0(qpid::SessionState& s) { return send(s, transferFrame(false)); }
+// Send transfer frame with single content frame.
+size_t transfer1(qpid::SessionState& s, string content) {
+ return send(s,transferFrame(true)) + send(s,contentFrame(content));
+}
+size_t transfer1Char(qpid::SessionState& s, char content) {
+ return transfer1(s, string(1,content));
+}
+
+// Send transfer frame with multiple single-byte content frames.
+size_t transferN(qpid::SessionState& s, string content) {
+ size_t size=send(s, transferFrame(!content.empty()));
+ if (!content.empty()) {
+ char last = content[content.size()-1];
+ content.resize(content.size()-1);
+ size += applyAccumulate(content.begin(), content.end(), 0,
+ boost::bind(&send, boost::ref(s),
+ boost::bind(contentFrameChar, _1, false)));
+ size += send(s, contentFrameChar(last, true));
+ }
+ return size;
+}
+
+// Send multiple transfers with single-byte content.
+size_t transfers(qpid::SessionState& s, string content) {
+ return applyAccumulate(content.begin(), content.end(), 0,
+ boost::bind(transfer1Char, boost::ref(s), _1));
+}
+
+size_t contentFrameSize(size_t n=1) { return AMQFrame(( AMQContentBody())).encodedSize() + n; }
+size_t transferFrameSize() { return AMQFrame((MessageTransferBody())).encodedSize(); }
+
+// ==== qpid::SessionState test classes
+
+using qpid::SessionId;
+using qpid::SessionPoint;
+
+
+QPID_AUTO_TEST_CASE(testSendGetReplyList) {
+ qpid::SessionState s;
+ s.setTimeout(1);
+ s.senderGetCommandPoint();
+ transfer1(s, "abc");
+ transfers(s, "def");
+ transferN(s, "xyz");
+ BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(0,0))),"CabcCdCeCfCxyz");
+ // Ignore controls.
+ s.senderRecord(AMQFrame(new SessionFlushBody()));
+ BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(2,0))),"CeCfCxyz");
+}
+
+QPID_AUTO_TEST_CASE(testNeedFlush) {
+ qpid::SessionState::Configuration c;
+ // sync after 2 1-byte transfers or equivalent bytes.
+ c.replayFlushLimit = 2*(transferFrameSize()+contentFrameSize());
+ qpid::SessionState s(SessionId(), c);
+ s.setTimeout(1);
+ s.senderGetCommandPoint();
+ transfers(s, "a");
+ BOOST_CHECK(!s.senderNeedFlush());
+ transfers(s, "b");
+ BOOST_CHECK(s.senderNeedFlush());
+ s.senderRecordFlush();
+ BOOST_CHECK(!s.senderNeedFlush());
+ transfers(s, "c");
+ BOOST_CHECK(!s.senderNeedFlush());
+ transfers(s, "d");
+ BOOST_CHECK(s.senderNeedFlush());
+ BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint())), "CaCbCcCd");
+}
+
+QPID_AUTO_TEST_CASE(testPeerConfirmed) {
+ qpid::SessionState::Configuration c;
+ // sync after 2 1-byte transfers or equivalent bytes.
+ c.replayFlushLimit = 2*(transferFrameSize()+contentFrameSize());
+ qpid::SessionState s(SessionId(), c);
+ s.setTimeout(1);
+ s.senderGetCommandPoint();
+ transfers(s, "ab");
+ BOOST_CHECK(s.senderNeedFlush());
+ transfers(s, "cd");
+ BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(0,0))), "CaCbCcCd");
+ s.senderConfirmed(SessionPoint(3));
+ BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(3,0))), "Cd");
+ BOOST_CHECK(!s.senderNeedFlush());
+
+ // Multi-frame transfer.
+ transfer1(s, "efg");
+ transfers(s, "xy");
+ BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(3,0))), "CdCefgCxCy");
+ BOOST_CHECK(s.senderNeedFlush());
+
+ s.senderConfirmed(SessionPoint(4));
+ BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(4,0))), "CefgCxCy");
+ BOOST_CHECK(s.senderNeedFlush());
+
+ s.senderConfirmed(SessionPoint(5));
+ BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(5,0))), "CxCy");
+ BOOST_CHECK(s.senderNeedFlush());
+
+ s.senderConfirmed(SessionPoint(6));
+ BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(6,0))), "Cy");
+ BOOST_CHECK(!s.senderNeedFlush());
+}
+
+QPID_AUTO_TEST_CASE(testPeerCompleted) {
+ qpid::SessionState s;
+ s.setTimeout(1);
+ s.senderGetCommandPoint();
+ // Completion implies confirmation
+ transfers(s, "abc");
+ BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(0,0))), "CaCbCc");
+ SequenceSet set(SequenceSet() + 0 + 1);
+ s.senderCompleted(set);
+ BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(2,0))), "Cc");
+
+ transfers(s, "def");
+ // We dont do out-of-order confirmation, so this will only confirm up to 3:
+ set = SequenceSet(SequenceSet() + 2 + 3 + 5);
+ s.senderCompleted(set);
+ BOOST_CHECK_EQUAL(str(s.senderExpected(SessionPoint(4,0))), "CeCf");
+}
+
+QPID_AUTO_TEST_CASE(testReceive) {
+ // Advance expected/received correctly
+ qpid::SessionState s;
+ s.receiverSetCommandPoint(SessionPoint());
+ BOOST_CHECK_EQUAL(s.receiverGetExpected(), SessionPoint(0));
+ BOOST_CHECK_EQUAL(s.receiverGetReceived(), SessionPoint(0));
+
+ BOOST_CHECK(s.receiverRecord(transferFrame(false)));
+ BOOST_CHECK_EQUAL(s.receiverGetExpected(), SessionPoint(1));
+ BOOST_CHECK_EQUAL(s.receiverGetReceived(), SessionPoint(1));
+
+ BOOST_CHECK(s.receiverRecord(transferFrame(true)));
+ SessionPoint point = SessionPoint(1, transferFrameSize());
+ BOOST_CHECK_EQUAL(s.receiverGetExpected(), point);
+ BOOST_CHECK_EQUAL(s.receiverGetReceived(), point);
+ BOOST_CHECK(s.receiverRecord(contentFrame("", false)));
+ point.offset += contentFrameSize(0);
+ BOOST_CHECK_EQUAL(s.receiverGetExpected(), point);
+ BOOST_CHECK_EQUAL(s.receiverGetReceived(), point);
+ BOOST_CHECK(s.receiverRecord(contentFrame("", true)));
+ BOOST_CHECK_EQUAL(s.receiverGetExpected(), SessionPoint(2));
+ BOOST_CHECK_EQUAL(s.receiverGetReceived(), SessionPoint(2));
+
+ // Idempotence barrier, rewind expected & receive some duplicates.
+ s.receiverSetCommandPoint(SessionPoint(1));
+ BOOST_CHECK(!s.receiverRecord(transferFrame(false)));
+ BOOST_CHECK_EQUAL(s.receiverGetExpected(), SessionPoint(2));
+ BOOST_CHECK_EQUAL(s.receiverGetReceived(), SessionPoint(2));
+ BOOST_CHECK(s.receiverRecord(transferFrame(false)));
+ BOOST_CHECK_EQUAL(s.receiverGetExpected(), SessionPoint(3));
+ BOOST_CHECK_EQUAL(s.receiverGetReceived(), SessionPoint(3));
+}
+
+QPID_AUTO_TEST_CASE(testCompleted) {
+ // completed & unknownCompleted
+ qpid::SessionState s;
+ s.receiverSetCommandPoint(SessionPoint());
+ s.receiverRecord(transferFrame(false));
+ s.receiverRecord(transferFrame(false));
+ s.receiverRecord(transferFrame(false));
+ s.receiverCompleted(1);
+ BOOST_CHECK_EQUAL(s.receiverGetUnknownComplete(), SequenceSet(SequenceSet()+1));
+ s.receiverCompleted(0);
+ BOOST_CHECK_EQUAL(s.receiverGetUnknownComplete(),
+ SequenceSet(SequenceSet() + qpid::Range<SequenceNumber>(0,2)));
+ s.receiverKnownCompleted(SequenceSet(SequenceSet()+1));
+ BOOST_CHECK_EQUAL(s.receiverGetUnknownComplete(), SequenceSet(SequenceSet()+2));
+ // TODO aconway 2008-04-30: missing tests for known-completed.
+}
+
+QPID_AUTO_TEST_CASE(testNeedKnownCompleted) {
+ size_t flushInterval= 2*(transferFrameSize()+contentFrameSize())+1;
+ qpid::SessionState::Configuration c(flushInterval);
+ qpid::SessionState s(qpid::SessionId(), c);
+ s.senderGetCommandPoint();
+ transfers(s, "a");
+ SequenceSet set(SequenceSet() + 0);
+ s.senderCompleted(set);
+ BOOST_CHECK(!s.senderNeedKnownCompleted());
+
+ transfers(s, "b");
+ set += 1;
+ s.senderCompleted(set);
+ BOOST_CHECK(!s.senderNeedKnownCompleted());
+
+ transfers(s, "c");
+ set += 2;
+ s.senderCompleted(set);
+ BOOST_CHECK(s.senderNeedKnownCompleted());
+ s.senderRecordKnownCompleted();
+ BOOST_CHECK(!s.senderNeedKnownCompleted());
+
+ transfers(s, "de");
+ set += 3;
+ set += 4;
+ s.senderCompleted(set);
+ BOOST_CHECK(!s.senderNeedKnownCompleted());
+
+ transfers(s, "f");
+ set += 2;
+ s.senderCompleted(set);
+ BOOST_CHECK(s.senderNeedKnownCompleted());
+ s.senderRecordKnownCompleted();
+ BOOST_CHECK(!s.senderNeedKnownCompleted());
+}
+
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/Shlib.cpp b/qpid/cpp/src/tests/Shlib.cpp
new file mode 100644
index 0000000000..7f01323e3c
--- /dev/null
+++ b/qpid/cpp/src/tests/Shlib.cpp
@@ -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.
+ *
+ */
+
+#include "test_tools.h"
+#include "config.h"
+#include "qpid/sys/Shlib.h"
+#include "qpid/Exception.h"
+
+#include "unit_test.h"
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(ShlibTestSuite)
+
+using namespace qpid::sys;
+typedef void (*CallMe)(int*);
+
+
+QPID_AUTO_TEST_CASE(testShlib) {
+ Shlib sh("./" QPID_SHLIB_PREFIX "shlibtest" QPID_SHLIB_POSTFIX QPID_SHLIB_SUFFIX);
+ // Double cast to avoid ISO warning.
+ CallMe callMe=sh.getSymbol<CallMe>("callMe");
+ BOOST_REQUIRE(callMe != 0);
+ int unloaded=0;
+ callMe(&unloaded);
+ sh.unload();
+ BOOST_CHECK_EQUAL(42, unloaded);
+ try {
+ sh.getSymbol("callMe");
+ BOOST_FAIL("Expected exception");
+ }
+ catch (const qpid::Exception&) {}
+}
+
+QPID_AUTO_TEST_CASE(testAutoShlib) {
+ int unloaded = 0;
+ {
+ AutoShlib sh("./" QPID_SHLIB_PREFIX "shlibtest" QPID_SHLIB_POSTFIX QPID_SHLIB_SUFFIX);
+ CallMe callMe=sh.getSymbol<CallMe>("callMe");
+ BOOST_REQUIRE(callMe != 0);
+ callMe(&unloaded);
+ }
+ BOOST_CHECK_EQUAL(42, unloaded);
+}
+
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/Statistics.cpp b/qpid/cpp/src/tests/Statistics.cpp
new file mode 100644
index 0000000000..7cacde8b74
--- /dev/null
+++ b/qpid/cpp/src/tests/Statistics.cpp
@@ -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.
+ *
+ */
+#include "Statistics.h"
+#include <qpid/messaging/Message.h>
+#include <ostream>
+#include <iomanip>
+
+namespace qpid {
+namespace tests {
+
+using namespace std;
+
+Statistic::~Statistic() {}
+
+Throughput::Throughput() : messages(0), started(false) {}
+
+void Throughput::message(const messaging::Message&) {
+ ++messages;
+ if (!started) {
+ start = sys::now();
+ started = true;
+ }
+}
+
+void Throughput::header(ostream& o) const {
+ o << "tp(m/s)";
+}
+
+void Throughput::report(ostream& o) const {
+ double elapsed(int64_t(sys::Duration(start, sys::now()))/double(sys::TIME_SEC));
+ o << fixed << setprecision(0) << messages/elapsed;
+}
+
+ThroughputAndLatency::ThroughputAndLatency() :
+ total(0),
+ min(numeric_limits<double>::max()),
+ max(numeric_limits<double>::min()),
+ samples(0)
+{}
+
+const std::string TS = "ts";
+
+void ThroughputAndLatency::message(const messaging::Message& m) {
+ Throughput::message(m);
+ types::Variant::Map::const_iterator i = m.getProperties().find(TS);
+ if (i != m.getProperties().end()) {
+ ++samples;
+ int64_t start(i->second.asInt64());
+ int64_t end(sys::Duration::FromEpoch());
+ double latency = double(end - start)/sys::TIME_MSEC;
+ if (latency > 0) {
+ total += latency;
+ if (latency < min) min = latency;
+ if (latency > max) max = latency;
+ }
+ }
+}
+
+void ThroughputAndLatency::header(ostream& o) const {
+ Throughput::header(o);
+ o << '\t' << "l-min" << '\t' << "l-max" << '\t' << "l-avg";
+}
+
+void ThroughputAndLatency::report(ostream& o) const {
+ Throughput::report(o);
+ if (samples) {
+ o << fixed << setprecision(2)
+ << '\t' << min << '\t' << max << '\t' << total/samples;
+ }
+}
+
+ReporterBase::ReporterBase(ostream& o, int batch, bool wantHeader)
+ : batchSize(batch), batchCount(0), headerPrinted(!wantHeader), out(o)
+{}
+
+ReporterBase::~ReporterBase() {}
+
+/** Count message in the statistics */
+void ReporterBase::message(const messaging::Message& m) {
+ if (!overall.get()) overall = create();
+ overall->message(m);
+ if (batchSize) {
+ if (!batch.get()) batch = create();
+ batch->message(m);
+ if (++batchCount == batchSize) {
+ header();
+ batch->report(out);
+ out << endl;
+ batch = create();
+ batchCount = 0;
+ }
+ }
+}
+
+/** Print overall report. */
+void ReporterBase::report() {
+ if (!overall.get()) overall = create();
+ header();
+ overall->report(out);
+ out << endl;
+}
+
+void ReporterBase::header() {
+ if (!headerPrinted) {
+ if (!overall.get()) overall = create();
+ overall->header(out);
+ out << endl;
+ headerPrinted = true;
+ }
+}
+
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/Statistics.h b/qpid/cpp/src/tests/Statistics.h
new file mode 100644
index 0000000000..091046a17f
--- /dev/null
+++ b/qpid/cpp/src/tests/Statistics.h
@@ -0,0 +1,111 @@
+#ifndef TESTS_STATISTICS_H
+#define TESTS_STATISTICS_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+
+#include <qpid/sys/Time.h>
+#include <limits>
+#include <iosfwd>
+#include <memory>
+
+namespace qpid {
+
+namespace messaging {
+class Message;
+}
+
+namespace tests {
+
+class Statistic {
+ public:
+ virtual ~Statistic();
+ virtual void message(const messaging::Message&) = 0;
+ virtual void report(std::ostream&) const = 0;
+ virtual void header(std::ostream&) const = 0;
+};
+
+class Throughput : public Statistic {
+ public:
+ Throughput();
+ virtual void message(const messaging::Message&);
+ virtual void report(std::ostream&) const;
+ virtual void header(std::ostream&) const;
+
+ protected:
+ int messages;
+
+ private:
+ bool started;
+ sys::AbsTime start;
+};
+
+class ThroughputAndLatency : public Throughput {
+ public:
+ ThroughputAndLatency();
+ virtual void message(const messaging::Message&);
+ virtual void report(std::ostream&) const;
+ virtual void header(std::ostream&) const;
+
+ private:
+ double total, min, max; // Milliseconds
+ int samples;
+};
+
+/** Report batch and overall statistics */
+class ReporterBase {
+ public:
+ virtual ~ReporterBase();
+
+ /** Count message in the statistics */
+ void message(const messaging::Message& m);
+
+ /** Print overall report. */
+ void report();
+
+ protected:
+ ReporterBase(std::ostream& o, int batchSize, bool wantHeader);
+ virtual std::auto_ptr<Statistic> create() = 0;
+
+ private:
+ void header();
+ void report(const Statistic& s);
+ std::auto_ptr<Statistic> overall;
+ std::auto_ptr<Statistic> batch;
+ int batchSize, batchCount;
+ bool stopped, headerPrinted;
+ std::ostream& out;
+};
+
+template <class Stats> class Reporter : public ReporterBase {
+ public:
+ Reporter(std::ostream& o, int batchSize, bool wantHeader)
+ : ReporterBase(o, batchSize, wantHeader) {}
+
+ virtual std::auto_ptr<Statistic> create() {
+ return std::auto_ptr<Statistic>(new Stats);
+ }
+};
+
+}} // namespace qpid::tests
+
+#endif /*!TESTS_STATISTICS_H*/
diff --git a/qpid/cpp/src/tests/StringUtils.cpp b/qpid/cpp/src/tests/StringUtils.cpp
new file mode 100644
index 0000000000..c50287a4f4
--- /dev/null
+++ b/qpid/cpp/src/tests/StringUtils.cpp
@@ -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.
+ *
+ */
+#include <iostream>
+#include "qpid/StringUtils.h"
+
+#include "unit_test.h"
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(StringUtilsTestSuite)
+
+using std::string;
+
+QPID_AUTO_TEST_CASE(testSplit_general)
+{
+ std::vector<std::string> results = split("a bbbb, car,d123,,,e", ", ");
+ BOOST_CHECK_EQUAL(5u, results.size());
+ BOOST_CHECK_EQUAL(string("a"), results[0]);
+ BOOST_CHECK_EQUAL(string("bbbb"), results[1]);
+ BOOST_CHECK_EQUAL(string("car"), results[2]);
+ BOOST_CHECK_EQUAL(string("d123"), results[3]);
+ BOOST_CHECK_EQUAL(string("e"), results[4]);
+}
+
+QPID_AUTO_TEST_CASE(testSplit_noDelims)
+{
+ std::vector<std::string> results = split("abc", ", ");
+ BOOST_CHECK_EQUAL(1u, results.size());
+ BOOST_CHECK_EQUAL(string("abc"), results[0]);
+}
+
+QPID_AUTO_TEST_CASE(testSplit_delimAtEnd)
+{
+ std::vector<std::string> results = split("abc def,,", ", ");
+ BOOST_CHECK_EQUAL(2u, results.size());
+ BOOST_CHECK_EQUAL(string("abc"), results[0]);
+ BOOST_CHECK_EQUAL(string("def"), results[1]);
+}
+
+QPID_AUTO_TEST_CASE(testSplit_delimAtStart)
+{
+ std::vector<std::string> results = split(",,abc def", ", ");
+ BOOST_CHECK_EQUAL(2u, results.size());
+ BOOST_CHECK_EQUAL(string("abc"), results[0]);
+ BOOST_CHECK_EQUAL(string("def"), results[1]);
+}
+
+QPID_AUTO_TEST_CASE(testSplit_onlyDelims)
+{
+ std::vector<std::string> results = split(",, , ", ", ");
+ BOOST_CHECK_EQUAL(0u, results.size());
+}
+
+QPID_AUTO_TEST_CASE(testSplit_empty)
+{
+ std::vector<std::string> results = split("", ", ");
+ BOOST_CHECK_EQUAL(0u, results.size());
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}}
diff --git a/qpid/cpp/src/tests/SystemInfo.cpp b/qpid/cpp/src/tests/SystemInfo.cpp
new file mode 100644
index 0000000000..34f1ac408e
--- /dev/null
+++ b/qpid/cpp/src/tests/SystemInfo.cpp
@@ -0,0 +1,36 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+
+#include "unit_test.h"
+#include "test_tools.h"
+#include "qpid/sys/SystemInfo.h"
+#include <boost/assign.hpp>
+
+using namespace std;
+using namespace qpid::sys;
+using namespace boost::assign;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(SystemInfoTestSuite)
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/TestMessageStore.h b/qpid/cpp/src/tests/TestMessageStore.h
new file mode 100644
index 0000000000..0b63bc9c15
--- /dev/null
+++ b/qpid/cpp/src/tests/TestMessageStore.h
@@ -0,0 +1,63 @@
+#ifndef _tests_TestMessageStore_h
+#define _tests_TestMessageStore_h
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/broker/NullMessageStore.h"
+#include <vector>
+
+using namespace qpid;
+using namespace qpid::broker;
+using namespace qpid::framing;
+
+namespace qpid {
+namespace tests {
+
+typedef std::pair<std::string, boost::intrusive_ptr<PersistableMessage> > msg_queue_pair;
+
+class TestMessageStore : public NullMessageStore
+{
+ public:
+ std::vector<boost::intrusive_ptr<PersistableMessage> > dequeued;
+ std::vector<msg_queue_pair> enqueued;
+
+ void dequeue(TransactionContext*,
+ const boost::intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue& /*queue*/)
+ {
+ dequeued.push_back(msg);
+ }
+
+ void enqueue(TransactionContext*,
+ const boost::intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue& queue)
+ {
+ msg->enqueueComplete();
+ enqueued.push_back(msg_queue_pair(queue.getName(), msg));
+ }
+
+ TestMessageStore() : NullMessageStore() {}
+ ~TestMessageStore(){}
+};
+
+}} // namespace qpid::tests
+
+#endif
diff --git a/qpid/cpp/src/tests/TestOptions.h b/qpid/cpp/src/tests/TestOptions.h
new file mode 100644
index 0000000000..f8da0f59cf
--- /dev/null
+++ b/qpid/cpp/src/tests/TestOptions.h
@@ -0,0 +1,79 @@
+#ifndef _TestOptions_
+#define _TestOptions_
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/Options.h"
+#include "qpid/log/Options.h"
+#include "qpid/Url.h"
+#include "qpid/log/Logger.h"
+#include "qpid/client/Connection.h"
+#include "ConnectionOptions.h"
+
+#include <iostream>
+#include <exception>
+
+namespace qpid {
+
+struct TestOptions : public qpid::Options
+{
+ TestOptions(const std::string& helpText_=std::string(),
+ const std::string& argv0=std::string())
+ : Options("Test Options"), help(false), log(argv0), helpText(helpText_)
+ {
+ addOptions()
+ ("help", optValue(help), "print this usage statement");
+ add(con);
+ add(log);
+ }
+
+ /** As well as parsing, throw help message if requested. */
+ void parse(int argc, char** argv) {
+ try {
+ qpid::Options::parse(argc, argv);
+ } catch (const std::exception& e) {
+ std::ostringstream msg;
+ msg << *this << std::endl << std::endl << e.what() << std::endl;
+ throw qpid::Options::Exception(msg.str());
+ }
+ qpid::log::Logger::instance().configure(log);
+ if (help) {
+ std::ostringstream msg;
+ msg << *this << std::endl << std::endl << helpText << std::endl;
+ throw qpid::Options::Exception(msg.str());
+ }
+ }
+
+ /** Open a connection using option values */
+ void open(qpid::client::Connection& connection) {
+ connection.open(con);
+ }
+
+
+ bool help;
+ ConnectionOptions con;
+ qpid::log::Options log;
+ std::string helpText;
+};
+
+}
+
+#endif
diff --git a/qpid/cpp/src/tests/TimerTest.cpp b/qpid/cpp/src/tests/TimerTest.cpp
new file mode 100644
index 0000000000..d28eeeffc1
--- /dev/null
+++ b/qpid/cpp/src/tests/TimerTest.cpp
@@ -0,0 +1,176 @@
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+#include "qpid/sys/Timer.h"
+#include "qpid/sys/Monitor.h"
+#include "qpid/Options.h"
+#include "unit_test.h"
+#include <math.h>
+#include <iostream>
+#include <memory>
+#include <boost/format.hpp>
+#include <boost/lexical_cast.hpp>
+
+using namespace qpid::sys;
+using boost::intrusive_ptr;
+using boost::dynamic_pointer_cast;
+
+namespace qpid {
+namespace tests {
+
+class Counter
+{
+ Mutex lock;
+ uint counter;
+ public:
+ Counter() : counter(0) {}
+ uint next()
+ {
+ Mutex::ScopedLock l(lock);
+ return ++counter;
+ }
+};
+
+class TestTask : public TimerTask
+{
+ const AbsTime start;
+ const Duration expected;
+ AbsTime end;
+ bool fired;
+ uint position;
+ Monitor monitor;
+ Counter& counter;
+
+ public:
+ TestTask(Duration timeout, Counter& _counter)
+ : TimerTask(timeout, "Test"), start(now()), expected(timeout), end(start), fired(false), counter(_counter) {}
+
+ void fire()
+ {
+ Monitor::ScopedLock l(monitor);
+ fired = true;
+ position = counter.next();
+ end = now();
+ monitor.notify();
+ }
+
+ void check(uint expected_position, uint64_t tolerance = 500 * TIME_MSEC)
+ {
+ Monitor::ScopedLock l(monitor);
+ BOOST_CHECK(fired);
+ BOOST_CHECK_EQUAL(expected_position, position);
+ Duration actual(start, end);
+#ifdef _MSC_VER
+ uint64_t difference = _abs64(expected - actual);
+#elif defined(_WIN32)
+ uint64_t difference = labs(expected - actual);
+#elif defined(__SUNPRO_CC) || defined (__IBMCPP__)
+ uint64_t difference = llabs(expected - actual);
+#else
+ uint64_t difference = abs(expected - actual);
+#endif
+ std::string msg(boost::lexical_cast<std::string>(boost::format("tolerance = %1%, difference = %2%") % tolerance % difference));
+ BOOST_CHECK_MESSAGE(difference < tolerance, msg);
+ }
+
+ void wait(Duration d)
+ {
+ Monitor::ScopedLock l(monitor);
+ monitor.wait(AbsTime(now(), d));
+ }
+};
+
+class DummyRunner : public Runnable
+{
+ public:
+ void run() {}
+};
+
+QPID_AUTO_TEST_SUITE(TimerTestSuite)
+
+QPID_AUTO_TEST_CASE(testGeneral)
+{
+ Counter counter;
+ Timer timer;
+ intrusive_ptr<TestTask> task1(new TestTask(Duration(3 * TIME_SEC), counter));
+ intrusive_ptr<TestTask> task2(new TestTask(Duration(1 * TIME_SEC), counter));
+ intrusive_ptr<TestTask> task3(new TestTask(Duration(4 * TIME_SEC), counter));
+ intrusive_ptr<TestTask> task4(new TestTask(Duration(2 * TIME_SEC), counter));
+
+ timer.add(task1);
+ timer.add(task2);
+ timer.add(task3);
+ timer.add(task4);
+
+ dynamic_pointer_cast<TestTask>(task3)->wait(Duration(6 * TIME_SEC));
+
+ dynamic_pointer_cast<TestTask>(task1)->check(3);
+ dynamic_pointer_cast<TestTask>(task2)->check(1);
+ dynamic_pointer_cast<TestTask>(task3)->check(4);
+ dynamic_pointer_cast<TestTask>(task4)->check(2);
+}
+
+std::string toString(Duration d) { return boost::lexical_cast<std::string>(d); }
+Duration fromString(const std::string& str) { return boost::lexical_cast<Duration>(str); }
+
+QPID_AUTO_TEST_CASE(testOstreamInOut) {
+ std::string empty;
+ BOOST_CHECK_EQUAL(toString(Duration(int64_t(TIME_SEC))), "1s");
+ BOOST_CHECK_EQUAL(toString(Duration(int64_t(TIME_SEC*123.4))), "123.4s");
+ BOOST_CHECK_EQUAL(toString(Duration(int64_t(TIME_MSEC*123.4))), "123.4ms");
+ BOOST_CHECK_EQUAL(toString(Duration(int64_t(TIME_USEC*123.4))), "123.4us");
+ BOOST_CHECK_EQUAL(toString(Duration(int64_t(TIME_NSEC*123))), "123ns");
+
+ BOOST_CHECK_EQUAL(fromString("123.4"), Duration(int64_t(TIME_SEC*123.4)));
+ BOOST_CHECK_EQUAL(fromString("123.4s"), Duration(int64_t(TIME_SEC*123.4)));
+ BOOST_CHECK_EQUAL(fromString("123ms"), Duration(int64_t(TIME_MSEC*123)));
+ BOOST_CHECK_EQUAL(fromString("123us"), Duration(int64_t(TIME_USEC*123)));
+ BOOST_CHECK_EQUAL(fromString("123ns"), Duration(int64_t(TIME_NSEC*123)));
+
+ Duration d = 0;
+ std::istringstream i;
+ std::string s;
+
+ i.str("123x");
+ i >> d;
+ BOOST_CHECK(i.fail());
+ BOOST_CHECK_EQUAL(d, 0);
+ BOOST_CHECK_EQUAL(i.str(), "123x");
+
+ i.str("xxx");
+ i >> d;
+ BOOST_CHECK(i.fail());
+ BOOST_CHECK_EQUAL(d, 0);
+ BOOST_CHECK_EQUAL(i.str(), "xxx");
+}
+
+QPID_AUTO_TEST_CASE(testOptionParse) {
+ Options opts;
+ Duration interval;
+ opts.addOptions()("interval", optValue(interval, "I"), "blah");
+ const char *args[] = { "fakeexe", "--interval", "123.4" };
+ opts.parse(sizeof(args)/sizeof(args[0]), args);
+ BOOST_CHECK_EQUAL(interval, Duration(int64_t(TIME_SEC*123.4)));
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/TopicExchangeTest.cpp b/qpid/cpp/src/tests/TopicExchangeTest.cpp
new file mode 100644
index 0000000000..d57951ea3f
--- /dev/null
+++ b/qpid/cpp/src/tests/TopicExchangeTest.cpp
@@ -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.
+ */
+#include "qpid/broker/TopicKeyNode.h"
+#include "qpid/broker/TopicExchange.h"
+#include "unit_test.h"
+#include "test_tools.h"
+
+using namespace qpid::broker;
+using namespace std;
+
+
+namespace qpid {
+namespace broker {
+
+// Class for exercising the pattern match code in the TopicExchange
+class TopicExchange::TopicExchangeTester {
+
+public:
+ typedef std::vector<std::string> BindingVec;
+ typedef TopicKeyNode<TopicExchange::BindingKey> TestBindingNode;
+
+private:
+ // binding node iterator that collects all routes that are bound
+ class TestFinder : public TestBindingNode::TreeIterator {
+ public:
+ TestFinder(BindingVec& m) : bv(m) {};
+ ~TestFinder() {};
+ bool visit(TestBindingNode& node) {
+ if (!node.bindings.bindingVector.empty())
+ bv.push_back(node.routePattern);
+ return true;
+ }
+
+ BindingVec& bv;
+ };
+
+public:
+ TopicExchangeTester() {};
+ ~TopicExchangeTester() {};
+ bool addBindingKey(const std::string& bKey) {
+ string routingPattern = normalize(bKey);
+ BindingKey *bk = bindingTree.add(routingPattern);
+ if (bk) {
+ // push a dummy binding to mark this node as "non-leaf"
+ bk->bindingVector.push_back(Binding::shared_ptr());
+ return true;
+ }
+ return false;
+ }
+
+ bool removeBindingKey(const std::string& bKey){
+ string routingPattern = normalize(bKey);
+ BindingKey *bk = bindingTree.get(routingPattern);
+ if (bk) {
+ bk->bindingVector.pop_back();
+ if (bk->bindingVector.empty()) {
+ // no more bindings - remove this node
+ bindingTree.remove(routingPattern);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ void findMatches(const std::string& rKey, BindingVec& matches) {
+ TestFinder testFinder(matches);
+ bindingTree.iterateMatch( rKey, testFinder );
+ }
+
+ void getAll(BindingVec& bindings) {
+ TestFinder testFinder(bindings);
+ bindingTree.iterateAll( testFinder );
+ }
+
+private:
+ TestBindingNode bindingTree;
+};
+} // namespace broker
+
+
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(TopicExchangeTestSuite)
+
+#define CHECK_NORMALIZED(expect, pattern) BOOST_CHECK_EQUAL(expect, TopicExchange::normalize(pattern));
+
+namespace {
+ // return the count of bindings that match 'pattern'
+ int match(TopicExchange::TopicExchangeTester &tt,
+ const std::string& pattern)
+ {
+ TopicExchange::TopicExchangeTester::BindingVec bv;
+ tt.findMatches(pattern, bv);
+ return int(bv.size());
+ }
+
+ // return true if expected contains exactly all bindings that match
+ // against pattern.
+ bool compare(TopicExchange::TopicExchangeTester& tt,
+ const std::string& pattern,
+ const TopicExchange::TopicExchangeTester::BindingVec& expected)
+ {
+ TopicExchange::TopicExchangeTester::BindingVec bv;
+ tt.findMatches(pattern, bv);
+ if (expected.size() != bv.size()) {
+ // std::cout << "match failed 1 f=[" << bv << "]" << std::endl;
+ // std::cout << "match failed 1 e=[" << expected << "]" << std::endl;
+ return false;
+ }
+ TopicExchange::TopicExchangeTester::BindingVec::const_iterator i;
+ for (i = expected.begin(); i != expected.end(); i++) {
+ TopicExchange::TopicExchangeTester::BindingVec::iterator j;
+ for (j = bv.begin(); j != bv.end(); j++) {
+ // std::cout << "matched [" << *j << "]" << std::endl;
+ if (*i == *j) break;
+ }
+ if (j == bv.end()) {
+ // std::cout << "match failed 2 [" << bv << "]" << std::endl;
+ return false;
+ }
+ }
+ return true;
+ }
+}
+
+
+QPID_AUTO_TEST_CASE(testNormalize)
+{
+ CHECK_NORMALIZED("", "");
+ CHECK_NORMALIZED("a.b.c", "a.b.c");
+ CHECK_NORMALIZED("a.*.c", "a.*.c");
+ CHECK_NORMALIZED("#", "#");
+ CHECK_NORMALIZED("#", "#.#.#.#");
+ CHECK_NORMALIZED("*.*.*.#", "#.*.#.*.#.#.*");
+ CHECK_NORMALIZED("a.*.*.*.#", "a.*.#.*.#.*.#");
+ CHECK_NORMALIZED("a.*.*.*.#", "a.*.#.*.#.*");
+ CHECK_NORMALIZED("*.*.*.#", "*.#.#.*.*.#");
+}
+
+QPID_AUTO_TEST_CASE(testPlain)
+{
+ TopicExchange::TopicExchangeTester tt;
+ string pattern("ab.cd.e");
+
+ BOOST_CHECK(tt.addBindingKey(pattern));
+ BOOST_CHECK_EQUAL(1, match(tt, "ab.cd.e"));
+ BOOST_CHECK_EQUAL(0, match(tt, "abx.cd.e"));
+ BOOST_CHECK_EQUAL(0, match(tt, "ab.cd"));
+ BOOST_CHECK_EQUAL(0, match(tt, "ab.cd..e."));
+ BOOST_CHECK_EQUAL(0, match(tt, "ab.cd.e."));
+ BOOST_CHECK_EQUAL(0, match(tt, ".ab.cd.e"));
+ BOOST_CHECK(tt.removeBindingKey(pattern));
+
+ pattern = "";
+ BOOST_CHECK(tt.addBindingKey(pattern));
+ BOOST_CHECK_EQUAL(1, match(tt, ""));
+ BOOST_CHECK(tt.removeBindingKey(pattern));
+
+ pattern = ".";
+ BOOST_CHECK(tt.addBindingKey(pattern));
+ BOOST_CHECK_EQUAL(1, match(tt, "."));
+ BOOST_CHECK(tt.removeBindingKey(pattern));
+}
+
+
+QPID_AUTO_TEST_CASE(testStar)
+{
+ TopicExchange::TopicExchangeTester tt;
+ string pattern("a.*.b");
+ BOOST_CHECK(tt.addBindingKey(pattern));
+ BOOST_CHECK_EQUAL(1, match(tt, "a.xx.b"));
+ BOOST_CHECK_EQUAL(0, match(tt, "a.b"));
+ BOOST_CHECK(tt.removeBindingKey(pattern));
+
+ pattern = "*.x";
+ BOOST_CHECK(tt.addBindingKey(pattern));
+ BOOST_CHECK_EQUAL(1, match(tt, "y.x"));
+ BOOST_CHECK_EQUAL(1, match(tt, ".x"));
+ BOOST_CHECK_EQUAL(0, match(tt, "x"));
+ BOOST_CHECK(tt.removeBindingKey(pattern));
+
+ pattern = "x.x.*";
+ BOOST_CHECK(tt.addBindingKey(pattern));
+ BOOST_CHECK_EQUAL(1, match(tt, "x.x.y"));
+ BOOST_CHECK_EQUAL(1, match(tt, "x.x."));
+ BOOST_CHECK_EQUAL(0, match(tt, "x.x"));
+ BOOST_CHECK_EQUAL(0, match(tt, "q.x.y"));
+ BOOST_CHECK(tt.removeBindingKey(pattern));
+}
+
+QPID_AUTO_TEST_CASE(testHash)
+{
+ TopicExchange::TopicExchangeTester tt;
+ string pattern("a.#.b");
+ BOOST_CHECK(tt.addBindingKey(pattern));
+ BOOST_CHECK_EQUAL(1, match(tt, "a.b"));
+ BOOST_CHECK_EQUAL(1, match(tt, "a.x.b"));
+ BOOST_CHECK_EQUAL(1, match(tt, "a..x.y.zz.b"));
+ BOOST_CHECK_EQUAL(0, match(tt, "a.b."));
+ BOOST_CHECK_EQUAL(0, match(tt, "q.x.b"));
+ BOOST_CHECK(tt.removeBindingKey(pattern));
+
+ pattern = "a.#";
+ BOOST_CHECK(tt.addBindingKey(pattern));
+ BOOST_CHECK_EQUAL(1, match(tt, "a"));
+ BOOST_CHECK_EQUAL(1, match(tt, "a.b"));
+ BOOST_CHECK_EQUAL(1, match(tt, "a.b.c"));
+ BOOST_CHECK(tt.removeBindingKey(pattern));
+
+ pattern = "#.a";
+ BOOST_CHECK(tt.addBindingKey(pattern));
+ BOOST_CHECK_EQUAL(1, match(tt, "a"));
+ BOOST_CHECK_EQUAL(1, match(tt, "x.y.a"));
+ BOOST_CHECK(tt.removeBindingKey(pattern));
+
+ pattern = "a.#.b.#.c";
+ BOOST_CHECK(tt.addBindingKey(pattern));
+ BOOST_CHECK_EQUAL(1, match(tt, "a.b.c"));
+ BOOST_CHECK_EQUAL(1, match(tt, "a.x.b.y.c"));
+ BOOST_CHECK_EQUAL(1, match(tt, "a.x.x.b.y.y.c"));
+ BOOST_CHECK(tt.removeBindingKey(pattern));
+}
+
+QPID_AUTO_TEST_CASE(testMixed)
+{
+ TopicExchange::TopicExchangeTester tt;
+ string pattern("*.x.#.y");
+ BOOST_CHECK(tt.addBindingKey(pattern));
+ BOOST_CHECK_EQUAL(1, match(tt, "a.x.y"));
+ BOOST_CHECK_EQUAL(1, match(tt, "a.x.p.qq.y"));
+ BOOST_CHECK_EQUAL(0, match(tt, "a.a.x.y"));
+ BOOST_CHECK_EQUAL(0, match(tt, "aa.x.b.c"));
+ BOOST_CHECK(tt.removeBindingKey(pattern));
+
+ pattern = "a.#.b.*";
+ BOOST_CHECK(tt.addBindingKey(pattern));
+ BOOST_CHECK_EQUAL(1, match(tt, "a.b.x"));
+ BOOST_CHECK_EQUAL(1, match(tt, "a.x.x.x.b.x"));
+ BOOST_CHECK(tt.removeBindingKey(pattern));
+
+ pattern = "*.*.*.#";
+ BOOST_CHECK(tt.addBindingKey(pattern));
+ BOOST_CHECK_EQUAL(1, match(tt, "x.y.z"));
+ BOOST_CHECK_EQUAL(1, match(tt, "x.y.z.a.b.c"));
+ BOOST_CHECK_EQUAL(0, match(tt, "x.y"));
+ BOOST_CHECK_EQUAL(0, match(tt, "x"));
+ BOOST_CHECK(tt.removeBindingKey(pattern));
+}
+
+
+QPID_AUTO_TEST_CASE(testMultiple)
+{
+ TopicExchange::TopicExchangeTester tt;
+ const std::string bindings[] =
+ { "a", "b",
+ "a.b", "b.c",
+ "a.b.c.d", "b.c.d.e",
+ "a.*", "a.#", "a.*.#",
+ "#.b", "*.b", "*.#.b",
+ "a.*.b", "a.#.b", "a.*.#.b",
+ "*.b.*", "#.b.#",
+ };
+ const size_t nBindings = sizeof(bindings)/sizeof(bindings[0]);
+
+ // setup bindings
+ for (size_t idx = 0; idx < nBindings; idx++) {
+ BOOST_CHECK(tt.addBindingKey(bindings[idx]));
+ }
+
+ {
+ // read all bindings, and verify all are present
+ TopicExchange::TopicExchangeTester::BindingVec b;
+ tt.getAll(b);
+ BOOST_CHECK_EQUAL(b.size(), nBindings);
+ for (size_t idx = 0; idx < nBindings; idx++) {
+ bool found = false;
+ for (TopicExchange::TopicExchangeTester::BindingVec::iterator i = b.begin();
+ i != b.end(); i++) {
+ if (*i == bindings[idx]) {
+ found = true;
+ break;
+ }
+ }
+ BOOST_CHECK(found);
+ }
+ }
+
+ { // test match on pattern "a"
+ const std::string matches[] = { "a", "a.#" };
+ const size_t nMatches = 2;
+ TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches);
+ BOOST_CHECK(compare(tt, "a", expected));
+ }
+
+ { // test match on pattern "a.z"
+ const std::string matches[] = { "a.*", "a.#", "a.*.#" };
+ const size_t nMatches = 3;
+ TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches);
+ BOOST_CHECK(compare(tt, "a.z", expected));
+ }
+
+ { // test match on pattern "a.b"
+ const std::string matches[] = {
+ "a.b", "a.*", "a.#", "a.*.#",
+ "#.b", "#.b.#", "*.#.b", "*.b",
+ "a.#.b"
+ };
+ const size_t nMatches = 9;
+ TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches);
+ BOOST_CHECK(compare(tt, "a.b", expected));
+ }
+
+ { // test match on pattern "a.c.c.b"
+
+ const std::string matches[] = {
+ "#.b", "#.b.#", "*.#.b", "a.#.b",
+ "a.#", "a.*.#.b", "a.*.#"
+ };
+ const size_t nMatches = 7;
+ TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches);
+ BOOST_CHECK(compare(tt, "a.c.c.b", expected));
+ }
+
+ { // test match on pattern "a.b.c"
+
+ const std::string matches[] = {
+ "#.b.#", "*.b.*", "a.#", "a.*.#"
+ };
+ const size_t nMatches = 4;
+ TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches);
+ BOOST_CHECK(compare(tt, "a.b.c", expected));
+ }
+
+ { // test match on pattern "b"
+
+ const std::string matches[] = {
+ "#.b", "#.b.#", "b"
+ };
+ const size_t nMatches = 3;
+ TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches);
+ BOOST_CHECK(compare(tt, "b", expected));
+ }
+
+ { // test match on pattern "x.b"
+
+ const std::string matches[] = {
+ "#.b", "#.b.#", "*.#.b", "*.b"
+ };
+ const size_t nMatches = 4;
+ TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches);
+ BOOST_CHECK(compare(tt, "x.b", expected));
+ }
+
+ { // test match on pattern "x.y.z.b"
+
+ const std::string matches[] = {
+ "#.b", "#.b.#", "*.#.b"
+ };
+ const size_t nMatches = 3;
+ TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches);
+ BOOST_CHECK(compare(tt, "x.y.z.b", expected));
+ }
+
+ { // test match on pattern "x.y.z.b.a.b.c"
+
+ const std::string matches[] = {
+ "#.b.#", "#.b.#"
+ };
+ const size_t nMatches = 2;
+ TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches);
+ BOOST_CHECK(compare(tt, "x.y.z.b.a.b.c", expected));
+ }
+
+ { // test match on pattern "a.b.c.d"
+
+ const std::string matches[] = {
+ "#.b.#", "a.#", "a.*.#", "a.b.c.d",
+ };
+ const size_t nMatches = 4;
+ TopicExchange::TopicExchangeTester::BindingVec expected(matches, matches + nMatches);
+ BOOST_CHECK(compare(tt, "a.b.c.d", expected));
+ }
+
+ // cleanup bindings
+ for (size_t idx = 0; idx < nBindings; idx++) {
+ BOOST_CHECK(tt.removeBindingKey(bindings[idx]));
+ }
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/TransactionObserverTest.cpp b/qpid/cpp/src/tests/TransactionObserverTest.cpp
new file mode 100644
index 0000000000..80ef494c21
--- /dev/null
+++ b/qpid/cpp/src/tests/TransactionObserverTest.cpp
@@ -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.
+ *
+ */
+
+#include "unit_test.h"
+#include "test_tools.h"
+#include "MessagingFixture.h"
+#include "qpid/broker/BrokerObserver.h"
+#include "qpid/broker/TransactionObserver.h"
+#include "qpid/broker/TxBuffer.h"
+#include "qpid/broker/Queue.h"
+#include "qpid/ha/types.h"
+
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/lexical_cast.hpp>
+#include <iostream>
+#include <vector>
+
+namespace qpid {
+namespace tests {
+
+using framing::SequenceSet;
+using messaging::Message;
+using boost::shared_ptr;
+
+using namespace boost::assign;
+using namespace boost;
+using namespace broker;
+using namespace std;
+using namespace messaging;
+using namespace types;
+
+QPID_AUTO_TEST_SUITE(TransactionalObserverTest)
+
+Message msg(string content) { return Message(content); }
+
+struct MockTransactionObserver : public TransactionObserver {
+ bool prep;
+ vector<string> events;
+
+ MockTransactionObserver(bool prep_=true) : prep(prep_) {}
+
+ void record(const string& e) { events.push_back(e); }
+
+ void enqueue(const shared_ptr<Queue>& q, const broker::Message& m) {
+ record("enqueue "+q->getName()+" "+m.getContent());
+ }
+ void dequeue(const Queue::shared_ptr& q, SequenceNumber p, SequenceNumber r) {
+ record("dequeue "+q->getName()+" "+
+ lexical_cast<string>(p)+" "+lexical_cast<string>(r));
+ }
+ bool prepare() { record("prepare"); return prep; }
+ void commit() { record("commit"); }
+ void rollback() {record("rollback"); }
+};
+
+struct MockBrokerObserver : public BrokerObserver {
+ bool prep;
+ shared_ptr<MockTransactionObserver> tx;
+
+ MockBrokerObserver(bool prep_=true) : prep(prep_) {}
+
+ void startTx(const intrusive_ptr<TxBuffer>& buffer) {
+ if (!tx) { // Don't overwrite first tx with automatically started second tx.
+ tx.reset(new MockTransactionObserver(prep));
+ buffer->setObserver(tx);
+ }
+ }
+};
+
+Session simpleTxTransaction(MessagingFixture& fix) {
+ fix.session.createSender("q1;{create:always}").send(msg("foo")); // Not in TX
+ // Transaction with 1 enqueue and 1 dequeue.
+ Session txSession = fix.connection.createTransactionalSession();
+ BOOST_CHECK_EQUAL("foo", txSession.createReceiver("q1").fetch().getContent());
+ txSession.acknowledge();
+ txSession.createSender("q2;{create:always}").send(msg("bar"));
+ return txSession;
+}
+
+QPID_AUTO_TEST_CASE(testTxCommit) {
+ MessagingFixture fix;
+ shared_ptr<MockBrokerObserver> brokerObserver(new MockBrokerObserver);
+ fix.broker->getBrokerObservers().add(brokerObserver);
+ Session txSession = simpleTxTransaction(fix);
+ txSession.commit();
+ // Note on ordering: observers see enqueues as they happen, but dequeues just
+ // before prepare.
+ BOOST_CHECK_EQUAL(
+ list_of<string>("enqueue q2 bar")("dequeue q1 1 0")("prepare")("commit"),
+ brokerObserver->tx->events
+ );
+}
+
+QPID_AUTO_TEST_CASE(testTxFail) {
+ MessagingFixture fix;
+ shared_ptr<MockBrokerObserver> brokerObserver(new MockBrokerObserver(false));
+ fix.broker->getBrokerObservers().add(brokerObserver);
+ Session txSession = simpleTxTransaction(fix);
+ try {
+ ScopedSuppressLogging sl; // Suppress messages for expected error.
+ txSession.commit();
+ BOOST_FAIL("Expected exception");
+ } catch(...) {}
+
+ BOOST_CHECK_EQUAL(
+ list_of<string>("enqueue q2 bar")("dequeue q1 1 0")("prepare")("rollback"),
+ brokerObserver->tx->events
+ );
+}
+
+QPID_AUTO_TEST_CASE(testTxRollback) {
+ MessagingFixture fix;
+ shared_ptr<MockBrokerObserver> brokerObserver(new MockBrokerObserver(false));
+ fix.broker->getBrokerObservers().add(brokerObserver);
+ Session txSession = simpleTxTransaction(fix);
+ txSession.rollback();
+ // Note: The dequeue does not appear here. This is because TxAccepts
+ // (i.e. dequeues) are not enlisted until SemanticState::commit and are
+ // never enlisted if the transaction is rolled back.
+ BOOST_CHECK_EQUAL(
+ list_of<string>("enqueue q2 bar")("rollback"),
+ brokerObserver->tx->events
+ );
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/TxBufferTest.cpp b/qpid/cpp/src/tests/TxBufferTest.cpp
new file mode 100644
index 0000000000..3f052d213e
--- /dev/null
+++ b/qpid/cpp/src/tests/TxBufferTest.cpp
@@ -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.
+ *
+ */
+#include "qpid/broker/TxBuffer.h"
+#include "unit_test.h"
+#include "test_tools.h"
+#include <iostream>
+#include <vector>
+#include "TxMocks.h"
+
+using namespace qpid::broker;
+using boost::static_pointer_cast;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(TxBufferTestSuite)
+
+QPID_AUTO_TEST_CASE(testCommitLocal)
+{
+ MockTransactionalStore store;
+ store.expectBegin().expectCommit();
+
+ MockTxOp::shared_ptr opA(new MockTxOp());
+ opA->expectPrepare().expectCommit();
+ MockTxOp::shared_ptr opB(new MockTxOp());
+ opB->expectPrepare().expectPrepare().expectCommit().expectCommit();//opB enlisted twice to test relative order
+ MockTxOp::shared_ptr opC(new MockTxOp());
+ opC->expectPrepare().expectCommit();
+
+ TxBuffer buffer;
+ buffer.enlist(static_pointer_cast<TxOp>(opA));
+ buffer.enlist(static_pointer_cast<TxOp>(opB));
+ buffer.enlist(static_pointer_cast<TxOp>(opB));//opB enlisted twice
+ buffer.enlist(static_pointer_cast<TxOp>(opC));
+
+ buffer.startCommit(&store);
+ buffer.endCommit(&store);
+ store.check();
+ BOOST_CHECK(store.isCommitted());
+ opA->check();
+ opB->check();
+ opC->check();
+}
+
+QPID_AUTO_TEST_CASE(testFailOnCommitLocal)
+{
+ MockTransactionalStore store;
+ store.expectBegin().expectAbort();
+
+ MockTxOp::shared_ptr opA(new MockTxOp());
+ opA->expectPrepare().expectRollback();
+ MockTxOp::shared_ptr opB(new MockTxOp(true));
+ opB->expectPrepare().expectRollback();
+ MockTxOp::shared_ptr opC(new MockTxOp());//will never get prepare as b will fail
+ opC->expectRollback();
+
+ TxBuffer buffer;
+ buffer.enlist(static_pointer_cast<TxOp>(opA));
+ buffer.enlist(static_pointer_cast<TxOp>(opB));
+ buffer.enlist(static_pointer_cast<TxOp>(opC));
+
+ try {
+ ScopedSuppressLogging sl; // Suppress messages for expected error.
+ buffer.startCommit(&store);
+ buffer.endCommit(&store);
+ BOOST_FAIL("Expected exception");
+ } catch (...) {}
+ BOOST_CHECK(store.isAborted());
+ store.check();
+ opA->check();
+ opB->check();
+ opC->check();
+}
+
+QPID_AUTO_TEST_CASE(testPrepare)
+{
+ MockTxOp::shared_ptr opA(new MockTxOp());
+ opA->expectPrepare();
+ MockTxOp::shared_ptr opB(new MockTxOp());
+ opB->expectPrepare();
+ MockTxOp::shared_ptr opC(new MockTxOp());
+ opC->expectPrepare();
+
+ TxBuffer buffer;
+ buffer.enlist(static_pointer_cast<TxOp>(opA));
+ buffer.enlist(static_pointer_cast<TxOp>(opB));
+ buffer.enlist(static_pointer_cast<TxOp>(opC));
+
+ BOOST_CHECK(buffer.prepare(0));
+ opA->check();
+ opB->check();
+ opC->check();
+}
+
+QPID_AUTO_TEST_CASE(testFailOnPrepare)
+{
+ MockTxOp::shared_ptr opA(new MockTxOp());
+ opA->expectPrepare();
+ MockTxOp::shared_ptr opB(new MockTxOp(true));
+ opB->expectPrepare();
+ MockTxOp::shared_ptr opC(new MockTxOp());//will never get prepare as b will fail
+
+ TxBuffer buffer;
+ buffer.enlist(static_pointer_cast<TxOp>(opA));
+ buffer.enlist(static_pointer_cast<TxOp>(opB));
+ buffer.enlist(static_pointer_cast<TxOp>(opC));
+
+ BOOST_CHECK(!buffer.prepare(0));
+ opA->check();
+ opB->check();
+ opC->check();
+}
+
+QPID_AUTO_TEST_CASE(testRollback)
+{
+ MockTxOp::shared_ptr opA(new MockTxOp());
+ opA->expectRollback();
+ MockTxOp::shared_ptr opB(new MockTxOp(true));
+ opB->expectRollback();
+ MockTxOp::shared_ptr opC(new MockTxOp());
+ opC->expectRollback();
+
+ TxBuffer buffer;
+ buffer.enlist(static_pointer_cast<TxOp>(opA));
+ buffer.enlist(static_pointer_cast<TxOp>(opB));
+ buffer.enlist(static_pointer_cast<TxOp>(opC));
+
+ buffer.rollback();
+ opA->check();
+ opB->check();
+ opC->check();
+}
+
+QPID_AUTO_TEST_CASE(testBufferIsClearedAfterRollback)
+{
+ MockTxOp::shared_ptr opA(new MockTxOp());
+ opA->expectRollback();
+ MockTxOp::shared_ptr opB(new MockTxOp());
+ opB->expectRollback();
+
+ TxBuffer buffer;
+ buffer.enlist(static_pointer_cast<TxOp>(opA));
+ buffer.enlist(static_pointer_cast<TxOp>(opB));
+
+ buffer.rollback();
+ buffer.commit();//second call should not reach ops
+ opA->check();
+ opB->check();
+}
+
+QPID_AUTO_TEST_CASE(testBufferIsClearedAfterCommit)
+{
+ MockTxOp::shared_ptr opA(new MockTxOp());
+ opA->expectCommit();
+ MockTxOp::shared_ptr opB(new MockTxOp());
+ opB->expectCommit();
+
+ TxBuffer buffer;
+ buffer.enlist(static_pointer_cast<TxOp>(opA));
+ buffer.enlist(static_pointer_cast<TxOp>(opB));
+
+ buffer.commit();
+ buffer.rollback();//second call should not reach ops
+ opA->check();
+ opB->check();
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/TxMocks.h b/qpid/cpp/src/tests/TxMocks.h
new file mode 100644
index 0000000000..8b54e7484b
--- /dev/null
+++ b/qpid/cpp/src/tests/TxMocks.h
@@ -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.
+ *
+ */
+#ifndef _tests_TxMocks_h
+#define _tests_TxMocks_h
+
+
+#include "qpid/Exception.h"
+#include "qpid/Msg.h"
+#include "qpid/broker/TransactionalStore.h"
+#include "qpid/broker/TxOp.h"
+#include <iostream>
+#include <vector>
+
+using namespace qpid::broker;
+using boost::static_pointer_cast;
+using std::string;
+
+namespace qpid {
+namespace tests {
+
+template <class T> void assertEqualVector(std::vector<T>& expected, std::vector<T>& actual){
+ unsigned int i = 0;
+ while(i < expected.size() && i < actual.size()){
+ BOOST_CHECK_EQUAL(expected[i], actual[i]);
+ i++;
+ }
+ if (i < expected.size()) {
+ throw qpid::Exception(QPID_MSG("Missing " << expected[i]));
+ } else if (i < actual.size()) {
+ throw qpid::Exception(QPID_MSG("Extra " << actual[i]));
+ }
+ BOOST_CHECK_EQUAL(expected.size(), actual.size());
+}
+
+class TxOpConstants{
+protected:
+ const string PREPARE;
+ const string COMMIT;
+ const string ROLLBACK;
+
+ TxOpConstants() : PREPARE("PREPARE"), COMMIT("COMMIT"), ROLLBACK("ROLLBACK") {}
+};
+
+class MockTxOp : public TxOp, public TxOpConstants{
+ std::vector<string> expected;
+ std::vector<string> actual;
+ bool failOnPrepare;
+ string debugName;
+public:
+ typedef boost::shared_ptr<MockTxOp> shared_ptr;
+
+ MockTxOp() : failOnPrepare(false) {}
+ MockTxOp(bool _failOnPrepare) : failOnPrepare(_failOnPrepare) {}
+
+ void setDebugName(string name){
+ debugName = name;
+ }
+
+ void printExpected(){
+ std::cout << std::endl << "MockTxOp[" << debugName << "] expects: ";
+ for (std::vector<string>::iterator i = expected.begin(); i < expected.end(); i++) {
+ if(i != expected.begin()) std::cout << ", ";
+ std::cout << *i;
+ }
+ std::cout << std::endl;
+ }
+
+ void printActual(){
+ std::cout << std::endl << "MockTxOp[" << debugName << "] actual: ";
+ for (std::vector<string>::iterator i = actual.begin(); i < actual.end(); i++) {
+ if(i != actual.begin()) std::cout << ", ";
+ std::cout << *i;
+ }
+ std::cout << std::endl;
+ }
+
+ bool prepare(TransactionContext*) throw(){
+ actual.push_back(PREPARE);
+ return !failOnPrepare;
+ }
+ void commit() throw(){
+ actual.push_back(COMMIT);
+ }
+ void rollback() throw(){
+ if(!debugName.empty()) std::cout << std::endl << "MockTxOp[" << debugName << "]::rollback()" << std::endl;
+ actual.push_back(ROLLBACK);
+ }
+
+ void callObserver(const boost::shared_ptr<TransactionObserver>&) {}
+
+ MockTxOp& expectPrepare(){
+ expected.push_back(PREPARE);
+ return *this;
+ }
+ MockTxOp& expectCommit(){
+ expected.push_back(COMMIT);
+ return *this;
+ }
+ MockTxOp& expectRollback(){
+ expected.push_back(ROLLBACK);
+ return *this;
+ }
+ void check(){
+ assertEqualVector(expected, actual);
+ }
+
+ ~MockTxOp(){}
+};
+
+class MockTransactionalStore : public TransactionalStore{
+ const string BEGIN;
+ const string BEGIN2PC;
+ const string PREPARE;
+ const string COMMIT;
+ const string ABORT;
+ std::vector<string> expected;
+ std::vector<string> actual;
+
+ enum states {OPEN = 1, PREPARED = 2, COMMITTED = 3, ABORTED = 4};
+ int state;
+
+ class TestTransactionContext : public TPCTransactionContext{
+ MockTransactionalStore* store;
+ public:
+ TestTransactionContext(MockTransactionalStore* _store) : store(_store) {}
+ void prepare(){
+ if(!store->isOpen()) throw "txn already completed";
+ store->state = PREPARED;
+ }
+
+ void commit(){
+ if(!store->isOpen() && !store->isPrepared()) throw "txn already completed";
+ store->state = COMMITTED;
+ }
+
+ void abort(){
+ if(!store->isOpen() && !store->isPrepared()) throw "txn already completed";
+ store->state = ABORTED;
+ }
+ ~TestTransactionContext(){}
+ };
+
+public:
+ MockTransactionalStore() :
+ BEGIN("BEGIN"), BEGIN2PC("BEGIN2PC"), PREPARE("PREPARE"), COMMIT("COMMIT"), ABORT("ABORT"), state(OPEN){}
+
+ void collectPreparedXids(std::set<std::string>&)
+ {
+ throw "Operation not supported";
+ }
+
+ std::auto_ptr<TPCTransactionContext> begin(const std::string&){
+ actual.push_back(BEGIN2PC);
+ std::auto_ptr<TPCTransactionContext> txn(new TestTransactionContext(this));
+ return txn;
+ }
+ std::auto_ptr<TransactionContext> begin(){
+ actual.push_back(BEGIN);
+ std::auto_ptr<TransactionContext> txn(new TestTransactionContext(this));
+ return txn;
+ }
+ void prepare(TPCTransactionContext& ctxt){
+ actual.push_back(PREPARE);
+ dynamic_cast<TestTransactionContext&>(ctxt).prepare();
+ }
+ void commit(TransactionContext& ctxt){
+ actual.push_back(COMMIT);
+ dynamic_cast<TestTransactionContext&>(ctxt).commit();
+ }
+ void abort(TransactionContext& ctxt){
+ actual.push_back(ABORT);
+ dynamic_cast<TestTransactionContext&>(ctxt).abort();
+ }
+ MockTransactionalStore& expectBegin(){
+ expected.push_back(BEGIN);
+ return *this;
+ }
+ MockTransactionalStore& expectBegin2PC(){
+ expected.push_back(BEGIN2PC);
+ return *this;
+ }
+ MockTransactionalStore& expectPrepare(){
+ expected.push_back(PREPARE);
+ return *this;
+ }
+ MockTransactionalStore& expectCommit(){
+ expected.push_back(COMMIT);
+ return *this;
+ }
+ MockTransactionalStore& expectAbort(){
+ expected.push_back(ABORT);
+ return *this;
+ }
+ void check(){
+ assertEqualVector(expected, actual);
+ }
+
+ bool isPrepared(){
+ return state == PREPARED;
+ }
+
+ bool isCommitted(){
+ return state == COMMITTED;
+ }
+
+ bool isAborted(){
+ return state == ABORTED;
+ }
+
+ bool isOpen() const{
+ return state == OPEN;
+ }
+ ~MockTransactionalStore(){}
+};
+
+}} // namespace qpid::tests
+
+#endif
diff --git a/qpid/cpp/src/tests/Url.cpp b/qpid/cpp/src/tests/Url.cpp
new file mode 100644
index 0000000000..b30de682bc
--- /dev/null
+++ b/qpid/cpp/src/tests/Url.cpp
@@ -0,0 +1,116 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+
+#include "unit_test.h"
+#include "test_tools.h"
+#include "qpid/Url.h"
+#include <boost/assign.hpp>
+
+using namespace std;
+using namespace qpid;
+using namespace boost::assign;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(UrlTestSuite)
+
+#define URL_CHECK_STR(STR) BOOST_CHECK_EQUAL(Url(STR).str(), STR)
+#define URL_CHECK_INVALID(STR) BOOST_CHECK_THROW(Url(STR), Url::Invalid)
+
+QPID_AUTO_TEST_CASE(TestParseTcp) {
+ URL_CHECK_STR("amqp:tcp:host:42");
+ URL_CHECK_STR("amqp:tcp:host-._~%ff%23:42"); // unreserved chars and pct encoded hex.
+ // Check defaults
+ BOOST_CHECK_EQUAL(Url("amqp:host:42").str(), "amqp:tcp:host:42");
+ BOOST_CHECK_EQUAL(Url("amqp:tcp:host").str(), "amqp:tcp:host:5672");
+ BOOST_CHECK_EQUAL(Url("host").str(), "amqp:tcp:host:5672");
+}
+
+QPID_AUTO_TEST_CASE(TestParseInvalid) {
+ //host is required:
+ URL_CHECK_INVALID("amqp:tcp:");
+ URL_CHECK_INVALID("amqp:");
+ URL_CHECK_INVALID("amqp::42");
+ URL_CHECK_INVALID("");
+
+ // Port must be numeric
+ URL_CHECK_INVALID("host:badPort");
+}
+
+QPID_AUTO_TEST_CASE(TestParseXyz) {
+ Url::addProtocol("xyz");
+ URL_CHECK_STR("amqp:xyz:host:123");
+ BOOST_CHECK_EQUAL(Url("xyz:host").str(), "amqp:xyz:host:5672");
+}
+
+QPID_AUTO_TEST_CASE(TestParseTricky) {
+ BOOST_CHECK_EQUAL(Url("amqp").str(), "amqp:tcp:amqp:5672");
+ BOOST_CHECK_EQUAL(Url("amqp:tcp").str(), "amqp:tcp:tcp:5672");
+ // These are ambiguous parses and arguably not the best result
+ BOOST_CHECK_EQUAL(Url("amqp:876").str(), "amqp:tcp:876:5672");
+ BOOST_CHECK_EQUAL(Url("tcp:567").str(), "amqp:tcp:567:5672");
+}
+
+QPID_AUTO_TEST_CASE(TestParseIPv6) {
+ Url u1("[::]");
+ BOOST_CHECK_EQUAL(u1[0].host, "::");
+ BOOST_CHECK_EQUAL(u1[0].port, 5672);
+ Url u2("[::1]");
+ BOOST_CHECK_EQUAL(u2[0].host, "::1");
+ BOOST_CHECK_EQUAL(u2[0].port, 5672);
+ Url u3("[::127.0.0.1]");
+ BOOST_CHECK_EQUAL(u3[0].host, "::127.0.0.1");
+ BOOST_CHECK_EQUAL(u3[0].port, 5672);
+ Url u4("[2002::222:68ff:fe0b:e61a]");
+ BOOST_CHECK_EQUAL(u4[0].host, "2002::222:68ff:fe0b:e61a");
+ BOOST_CHECK_EQUAL(u4[0].port, 5672);
+ Url u5("[2002::222:68ff:fe0b:e61a]:123");
+ BOOST_CHECK_EQUAL(u5[0].host, "2002::222:68ff:fe0b:e61a");
+ BOOST_CHECK_EQUAL(u5[0].port, 123);
+}
+
+QPID_AUTO_TEST_CASE(TestParseMultiAddress) {
+ Url::addProtocol("xyz");
+ URL_CHECK_STR("amqp:tcp:host:0,xyz:foo:123,tcp:foo:0,xyz:bar:1");
+ URL_CHECK_STR("amqp:xyz:foo:222,tcp:foo:0");
+ URL_CHECK_INVALID("amqp:tcp:h:0,");
+ URL_CHECK_INVALID(",amqp:tcp:h");
+}
+
+QPID_AUTO_TEST_CASE(TestParseUserPass) {
+ URL_CHECK_STR("amqp:user/pass@tcp:host:123");
+ URL_CHECK_STR("amqp:user@tcp:host:123");
+ BOOST_CHECK_EQUAL(Url("user/pass@host").str(), "amqp:user/pass@tcp:host:5672");
+ BOOST_CHECK_EQUAL(Url("user@host").str(), "amqp:user@tcp:host:5672");
+
+ Url u("user/pass@host");
+ BOOST_CHECK_EQUAL(u.getUser(), "user");
+ BOOST_CHECK_EQUAL(u.getPass(), "pass");
+ Url v("foo@host");
+ BOOST_CHECK_EQUAL(v.getUser(), "foo");
+ BOOST_CHECK_EQUAL(v.getPass(), "");
+ u = v;
+ BOOST_CHECK_EQUAL(u.getUser(), "foo");
+ BOOST_CHECK_EQUAL(u.getPass(), "");
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/Uuid.cpp b/qpid/cpp/src/tests/Uuid.cpp
new file mode 100644
index 0000000000..f9a67d9db0
--- /dev/null
+++ b/qpid/cpp/src/tests/Uuid.cpp
@@ -0,0 +1,150 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "qpid/framing/Uuid.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/types/Uuid.h"
+
+#include "unit_test.h"
+
+#include <set>
+
+#include <boost/array.hpp>
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(UuidTestSuite)
+
+using namespace std;
+using namespace qpid::framing;
+
+struct UniqueSet : public std::set<Uuid> {
+ void operator()(const Uuid& uuid) {
+ BOOST_REQUIRE(find(uuid) == end());
+ insert(uuid);
+ }
+};
+
+QPID_AUTO_TEST_CASE(testUuidCtor) {
+ // Uniqueness
+ boost::array<Uuid,1000> uuids;
+ for_each(uuids.begin(), uuids.end(), mem_fun_ref(&Uuid::generate));
+ UniqueSet unique;
+ for_each(uuids.begin(), uuids.end(), unique);
+}
+
+boost::array<uint8_t, 16> sample = {{0x1b, 0x4e, 0x28, 0xba, 0x2f, 0xa1, 0x11, 0x02, 0x88, 0x3f, 0xb9, 0xa7, 0x61, 0xbd, 0xe3, 0xfb}};
+const string sampleStr("1b4e28ba-2fa1-1102-883f-b9a761bde3fb");
+const string zeroStr("00000000-0000-0000-0000-000000000000");
+const string badUuid1("1b4e28ba-2fa1-11d2-883f-b9761bde3fb");
+const string badUuid2("1b4e28ba-2fa1-11d23883f-b9761dbde3fb");
+
+QPID_AUTO_TEST_CASE(testUuidIstream) {
+ Uuid uuid;
+ istringstream in(sampleStr);
+ in >> uuid;
+ BOOST_CHECK(!in.fail());
+ BOOST_CHECK(::memcmp(uuid.data(), sample.data(), uuid.size())==0);
+
+ istringstream is(zeroStr);
+ Uuid zero;
+ is >> zero;
+ BOOST_CHECK(!is.fail());
+ BOOST_CHECK_EQUAL(zero, Uuid());
+}
+
+QPID_AUTO_TEST_CASE(testUuidOstream) {
+ Uuid uuid(sample.c_array());
+ ostringstream out;
+ out << uuid;
+ BOOST_CHECK(out.good());
+ BOOST_CHECK_EQUAL(out.str(), sampleStr);
+
+ ostringstream os;
+ os << Uuid();
+ BOOST_CHECK(out.good());
+ BOOST_CHECK_EQUAL(os.str(), zeroStr);
+}
+
+QPID_AUTO_TEST_CASE(testBadUuidIstream) {
+ Uuid a;
+ istringstream is(badUuid1);
+ is >> a;
+ BOOST_CHECK(!is.good());
+ istringstream is2(badUuid2);
+ is2 >> a;
+ BOOST_CHECK(!is2.good());
+}
+
+QPID_AUTO_TEST_CASE(testUuidIOstream) {
+ Uuid a(true), b(true);
+ ostringstream os;
+ os << a << endl << b;
+ Uuid aa, bb;
+ istringstream is(os.str());
+ is >> aa >> ws >> bb;
+ BOOST_CHECK(os.good());
+ BOOST_CHECK_EQUAL(a, aa);
+ BOOST_CHECK_EQUAL(b, bb);
+}
+
+QPID_AUTO_TEST_CASE(testUuidEncodeDecode) {
+ std::vector<char> buff(Uuid::size());
+ Buffer wbuf(&buff[0], Uuid::size());
+ Uuid uuid(sample.c_array());
+ uuid.encode(wbuf);
+
+ Buffer rbuf(&buff[0], Uuid::size());
+ Uuid decoded;
+ decoded.decode(rbuf);
+ BOOST_CHECK_EQUAL(string(sample.begin(), sample.end()),
+ string(decoded.data(), decoded.data()+decoded.size()));
+}
+
+QPID_AUTO_TEST_CASE(testTypesUuid)
+{
+ //tests for the Uuid class in the types namespace (introduced
+ //to avoid pulling in dependencies from framing)
+ types::Uuid a;
+ types::Uuid b(true);
+ types::Uuid c(true);
+ types::Uuid d(b);
+ types::Uuid e;
+ e = c;
+
+ BOOST_CHECK(!a);
+ BOOST_CHECK(b);
+
+ BOOST_CHECK(a != b);
+ BOOST_CHECK(b != c);
+
+ BOOST_CHECK_EQUAL(b, d);
+ BOOST_CHECK_EQUAL(c, e);
+
+ ostringstream out;
+ out << b;
+ istringstream in(out.str());
+ in >> a;
+ BOOST_CHECK(!in.fail());
+ BOOST_CHECK_EQUAL(a, b);
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/Variant.cpp b/qpid/cpp/src/tests/Variant.cpp
new file mode 100644
index 0000000000..5ae7fc89eb
--- /dev/null
+++ b/qpid/cpp/src/tests/Variant.cpp
@@ -0,0 +1,834 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "unit_test.h"
+#include "qpid/types/Variant.h"
+#include "qpid/amqp_0_10/Codecs.h"
+#include <boost/assign.hpp>
+#include <iostream>
+
+using namespace qpid::types;
+using namespace qpid::amqp_0_10;
+using boost::assign::list_of;
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(VariantSuite)
+
+QPID_AUTO_TEST_CASE(testConversions)
+{
+ Variant value;
+
+ //string to float/double
+ value = "1.5";
+ BOOST_CHECK_EQUAL((float) 1.5, value.asFloat());
+ BOOST_CHECK_EQUAL((double) 1.5, value.asDouble());
+
+ //float to string or double
+ value = 1.5f;
+ BOOST_CHECK_EQUAL((float) 1.5, value.asFloat());
+ BOOST_CHECK_EQUAL((double) 1.5, value.asDouble());
+ BOOST_CHECK_EQUAL(std::string("1.5"), value.asString());
+
+ //double to string (conversion to float not valid)
+ value = 1.5;
+ BOOST_CHECK_THROW(value.asFloat(), InvalidConversion);
+ BOOST_CHECK_EQUAL((double) 1.5, value.asDouble());
+ BOOST_CHECK_EQUAL(std::string("1.5"), value.asString());
+
+ //uint8 to larger unsigned ints and string
+ value = (uint8_t) 7;
+ BOOST_CHECK_EQUAL((uint8_t) 7, value.asUint8());
+ BOOST_CHECK_EQUAL((uint16_t) 7, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 7, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 7, value.asUint64());
+ BOOST_CHECK_EQUAL(std::string("7"), value.asString());
+
+ value = (uint16_t) 8;
+ BOOST_CHECK_EQUAL(std::string("8"), value.asString());
+ value = (uint32_t) 9;
+ BOOST_CHECK_EQUAL(std::string("9"), value.asString());
+
+ //uint32 to larger unsigned ints and string
+ value = (uint32_t) 9999999;
+ BOOST_CHECK_EQUAL((uint32_t) 9999999, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 9999999, value.asUint64());
+ BOOST_CHECK_EQUAL(std::string("9999999"), value.asString());
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint16(), InvalidConversion);
+
+ value = "true";
+ BOOST_CHECK(value.asBool());
+ value = "false";
+ BOOST_CHECK(!value.asBool());
+ value = "1";
+ BOOST_CHECK(value.asBool());
+ value = "0";
+ BOOST_CHECK(!value.asBool());
+ value = "other";
+ BOOST_CHECK_THROW(value.asBool(), InvalidConversion);
+}
+
+QPID_AUTO_TEST_CASE(testConversionsFromString)
+{
+ Variant value;
+ value = "5";
+ BOOST_CHECK_EQUAL(5, value.asInt16());
+ BOOST_CHECK_EQUAL(5u, value.asUint16());
+
+ value = "-5";
+ BOOST_CHECK_EQUAL(-5, value.asInt16());
+ BOOST_CHECK_THROW(value.asUint16(), InvalidConversion);
+
+ value = "18446744073709551615";
+ BOOST_CHECK_EQUAL(18446744073709551615ull, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt64(), InvalidConversion);
+
+ value = "9223372036854775808";
+ BOOST_CHECK_EQUAL(9223372036854775808ull, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt64(), InvalidConversion);
+
+ value = "-9223372036854775809";
+ BOOST_CHECK_THROW(value.asUint64(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt64(), InvalidConversion);
+
+ value = "2147483648";
+ BOOST_CHECK_EQUAL(2147483648ul, value.asUint32());
+ BOOST_CHECK_THROW(value.asInt32(), InvalidConversion);
+
+ value = "-2147483649";
+ BOOST_CHECK_THROW(value.asUint32(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt32(), InvalidConversion);
+
+ value = "32768";
+ BOOST_CHECK_EQUAL(32768u, value.asUint16());
+ BOOST_CHECK_THROW(value.asInt16(), InvalidConversion);
+
+ value = "-32769";
+ BOOST_CHECK_THROW(value.asUint16(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt16(), InvalidConversion);
+
+ value = "-2.5";
+ BOOST_CHECK_EQUAL(-2.5, value.asFloat());
+
+ value = "-0.875432e10";
+ BOOST_CHECK_EQUAL(-0.875432e10, value.asDouble());
+
+ value = "-0";
+ BOOST_CHECK_EQUAL(0, value.asInt16());
+ BOOST_CHECK_EQUAL(0u, value.asUint16());
+
+ value = "-Blah";
+ BOOST_CHECK_THROW(value.asUint16(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt16(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint32(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt32(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint64(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt64(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asFloat(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asDouble(), InvalidConversion);
+
+ value = "-000";
+ BOOST_CHECK_EQUAL(0, value.asInt16());
+ BOOST_CHECK_EQUAL(0u, value.asUint16());
+
+ value = "-0010";
+ BOOST_CHECK_EQUAL(-10, value.asInt16());
+ BOOST_CHECK_THROW(value.asUint16(), InvalidConversion);
+}
+
+QPID_AUTO_TEST_CASE(testSizeConversionsUint)
+{
+ Variant value;
+
+ //uint8 (in 7 bits) to other uints, ints
+ value = (uint8_t) 7;
+ BOOST_CHECK_EQUAL((uint8_t) 7, value.asUint8());
+ BOOST_CHECK_EQUAL((uint16_t) 7, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 7, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 7, value.asUint64());
+ BOOST_CHECK_EQUAL((int8_t) 7, value.asInt8());
+ BOOST_CHECK_EQUAL((int16_t) 7, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) 7, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 7, value.asInt64());
+
+ //uint8 (in 8 bits) to other uints, ints
+ value = (uint8_t) 200;
+ BOOST_CHECK_EQUAL((uint8_t) 200, value.asUint8());
+ BOOST_CHECK_EQUAL((uint16_t) 200, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 200, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 200, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int16_t) 200, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) 200, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 200, value.asInt64());
+
+
+
+ //uint16 (in 7 bits) to other uints, ints
+ value = (uint16_t) 120;
+ BOOST_CHECK_EQUAL((uint8_t) 120, value.asUint8());
+ BOOST_CHECK_EQUAL((uint16_t) 120, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 120, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 120, value.asUint64());
+ BOOST_CHECK_EQUAL((int8_t) 120, value.asInt8());
+ BOOST_CHECK_EQUAL((int16_t) 120, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) 120, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 120, value.asInt64());
+
+ //uint16 (more than 7 bits) to other uints, ints
+ value = (uint16_t) 240;
+ BOOST_CHECK_EQUAL((uint8_t) 240, value.asUint8());
+ BOOST_CHECK_EQUAL((uint16_t) 240, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 240, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 240, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int16_t) 240, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) 240, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 240, value.asInt64());
+
+ //uint16 (more than 8 bits) to other uints, ints
+ value = (uint16_t) 1000;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((uint16_t) 1000, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 1000, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 1000, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int16_t) 1000, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) 1000, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 1000, value.asInt64());
+
+ //uint16 (more than 15 bits) to other uints, ints
+ value = (uint16_t) 32770;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((uint16_t) 32770, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 32770, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 32770, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt16(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int32_t) 32770, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 32770, value.asInt64());
+
+
+
+ //uint32 (in 7 bits) to other uints, ints
+ value = (uint32_t) 120;
+ BOOST_CHECK_EQUAL((uint8_t) 120, value.asUint8());
+ BOOST_CHECK_EQUAL((uint16_t) 120, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 120, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 120, value.asUint64());
+ BOOST_CHECK_EQUAL((int8_t) 120, value.asInt8());
+ BOOST_CHECK_EQUAL((int16_t) 120, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) 120, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 120, value.asInt64());
+
+ //uint32 (more than 7 bits) to other uints, ints
+ value = (uint32_t) 240;
+ BOOST_CHECK_EQUAL((uint8_t) 240, value.asUint8());
+ BOOST_CHECK_EQUAL((uint16_t) 240, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 240, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 240, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int16_t) 240, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) 240, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 240, value.asInt64());
+
+ //uint32 (more than 8 bits) to other uints, ints
+ value = (uint32_t) 1000;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((uint16_t) 1000, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 1000, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 1000, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int16_t) 1000, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) 1000, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 1000, value.asInt64());
+
+ //uint32 (more than 15 bits) to other uints, ints
+ value = (uint32_t) 32770;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((uint16_t) 32770, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 32770, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 32770, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt16(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int32_t) 32770, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 32770, value.asInt64());
+
+ //uint32 (more than 16 bits) to other uints, ints
+ value = (uint32_t) 66000;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint16(), InvalidConversion);
+ BOOST_CHECK_EQUAL((uint32_t) 66000, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 66000, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt16(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int32_t) 66000, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 66000, value.asInt64());
+
+
+
+ //uint64 (in 7 bits) to other uints, ints
+ value = (uint64_t) 120;
+ BOOST_CHECK_EQUAL((uint8_t) 120, value.asUint8());
+ BOOST_CHECK_EQUAL((uint16_t) 120, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 120, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 120, value.asUint64());
+ BOOST_CHECK_EQUAL((int8_t) 120, value.asInt8());
+ BOOST_CHECK_EQUAL((int16_t) 120, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) 120, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 120, value.asInt64());
+
+ //uint64 (more than 7 bits) to other uints, ints
+ value = (uint64_t) 240;
+ BOOST_CHECK_EQUAL((uint8_t) 240, value.asUint8());
+ BOOST_CHECK_EQUAL((uint16_t) 240, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 240, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 240, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int16_t) 240, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) 240, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 240, value.asInt64());
+
+ //uint64 (more than 8 bits) to other uints, ints
+ value = (uint64_t) 1000;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((uint16_t) 1000, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 1000, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 1000, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int16_t) 1000, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) 1000, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 1000, value.asInt64());
+
+ //uint64 (more than 15 bits) to other uints, ints
+ value = (uint64_t) 32770;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((uint16_t) 32770, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 32770, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 32770, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt16(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int32_t) 32770, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 32770, value.asInt64());
+
+ //uint64 (more than 16 bits) to other uints, ints
+ value = (uint64_t) 66000;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint16(), InvalidConversion);
+ BOOST_CHECK_EQUAL((uint32_t) 66000, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 66000, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt16(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int32_t) 66000, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 66000, value.asInt64());
+
+ //uint64 (more than 31 bits) to other uints, ints
+ value = (uint64_t) 3000000000ul;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint16(), InvalidConversion);
+ BOOST_CHECK_EQUAL((uint32_t) 3000000000ul, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 3000000000ul, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt16(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt32(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int64_t) 3000000000ul, value.asInt64());
+
+ //uint64 (more than 32 bits) to other uints, ints
+ value = (uint64_t) 7000000000ull;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint16(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint32(), InvalidConversion);
+ BOOST_CHECK_EQUAL((uint64_t) 7000000000ull, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt16(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt32(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int64_t) 7000000000ull, value.asInt64());
+
+ //uint64 (more than 63 bits) to other uints, ints
+ value = (uint64_t) 0x8000000000000000ull;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint16(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint32(), InvalidConversion);
+ BOOST_CHECK_EQUAL((uint64_t) 0x8000000000000000ull, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt16(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt32(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt64(), InvalidConversion);
+}
+
+QPID_AUTO_TEST_CASE(testSizeConversionsInt)
+{
+ Variant value;
+
+ //int8 (positive in 7 bits)
+ value = (int8_t) 100;
+ BOOST_CHECK_EQUAL((uint8_t) 100, value.asUint8());
+ BOOST_CHECK_EQUAL((uint16_t) 100, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 100, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 100, value.asUint64());
+ BOOST_CHECK_EQUAL((int8_t) 100, value.asInt8());
+ BOOST_CHECK_EQUAL((int16_t) 100, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) 100, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 100, value.asInt64());
+
+ //int8 (negative)
+ value = (int8_t) -100;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint16(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint32(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint64(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int8_t) -100, value.asInt8());
+ BOOST_CHECK_EQUAL((int16_t) -100, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) -100, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) -100, value.asInt64());
+
+
+
+ //int16 (positive in 7 bits)
+ value = (int16_t) 100;
+ BOOST_CHECK_EQUAL((uint8_t) 100, value.asUint8());
+ BOOST_CHECK_EQUAL((uint16_t) 100, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 100, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 100, value.asUint64());
+ BOOST_CHECK_EQUAL((int8_t) 100, value.asInt8());
+ BOOST_CHECK_EQUAL((int16_t) 100, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) 100, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 100, value.asInt64());
+
+ //int16 (positive in 8 bits)
+ value = (int16_t) 200;
+ BOOST_CHECK_EQUAL((uint8_t) 200, value.asUint8());
+ BOOST_CHECK_EQUAL((uint16_t) 200, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 200, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 200, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int16_t) 200, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) 200, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 200, value.asInt64());
+
+ //int16 (positive in more than 8 bits)
+ value = (int16_t) 1000;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((uint16_t) 1000, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 1000, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 1000, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int16_t) 1000, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) 1000, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 1000, value.asInt64());
+
+ //int16 (negative in 7 bits)
+ value = (int16_t) -100;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint16(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint32(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint64(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int8_t) -100, value.asInt8());
+ BOOST_CHECK_EQUAL((int16_t) -100, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) -100, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) -100, value.asInt64());
+
+ //int16 (negative in more than 7 bits)
+ value = (int16_t) -1000;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint16(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint32(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint64(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int16_t) -1000, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) -1000, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) -1000, value.asInt64());
+
+
+
+ //int32 (positive in 7 bits)
+ value = (int32_t) 100;
+ BOOST_CHECK_EQUAL((uint8_t) 100, value.asUint8());
+ BOOST_CHECK_EQUAL((uint16_t) 100, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 100, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 100, value.asUint64());
+ BOOST_CHECK_EQUAL((int8_t) 100, value.asInt8());
+ BOOST_CHECK_EQUAL((int16_t) 100, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) 100, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 100, value.asInt64());
+
+ //int32 (positive in 8 bits)
+ value = (int32_t) 200;
+ BOOST_CHECK_EQUAL((uint8_t) 200, value.asUint8());
+ BOOST_CHECK_EQUAL((uint16_t) 200, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 200, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 200, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int16_t) 200, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) 200, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 200, value.asInt64());
+
+ //int32 (positive in more than 8 bits)
+ value = (int32_t) 1000;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((uint16_t) 1000, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 1000, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 1000, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int16_t) 1000, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) 1000, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 1000, value.asInt64());
+
+ //int32 (positive in more than 15 bits)
+ value = (int32_t) 40000;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((uint16_t) 40000, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 40000, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 40000, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt16(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int32_t) 40000, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 40000, value.asInt64());
+
+ //int32 (negative in 7 bits)
+ value = (int32_t) -100;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint16(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint32(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint64(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int8_t) -100, value.asInt8());
+ BOOST_CHECK_EQUAL((int16_t) -100, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) -100, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) -100, value.asInt64());
+
+ //int32 (negative in more than 7 bits)
+ value = (int32_t) -1000;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint16(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint32(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint64(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int16_t) -1000, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) -1000, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) -1000, value.asInt64());
+
+ //int32 (negative in more than 15 bits)
+ value = (int32_t) -40000;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint16(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint32(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint64(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt16(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int32_t) -40000, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) -40000, value.asInt64());
+
+
+
+ //int64 (positive in 7 bits)
+ value = (int64_t) 100;
+ BOOST_CHECK_EQUAL((uint8_t) 100, value.asUint8());
+ BOOST_CHECK_EQUAL((uint16_t) 100, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 100, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 100, value.asUint64());
+ BOOST_CHECK_EQUAL((int8_t) 100, value.asInt8());
+ BOOST_CHECK_EQUAL((int16_t) 100, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) 100, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 100, value.asInt64());
+
+ //int64 (positive in 8 bits)
+ value = (int64_t) 200;
+ BOOST_CHECK_EQUAL((uint8_t) 200, value.asUint8());
+ BOOST_CHECK_EQUAL((uint16_t) 200, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 200, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 200, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int16_t) 200, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) 200, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 200, value.asInt64());
+
+ //int64 (positive in more than 8 bits)
+ value = (int64_t) 1000;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((uint16_t) 1000, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 1000, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 1000, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int16_t) 1000, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) 1000, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 1000, value.asInt64());
+
+ //int64 (positive in more than 15 bits)
+ value = (int64_t) 40000;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((uint16_t) 40000, value.asUint16());
+ BOOST_CHECK_EQUAL((uint32_t) 40000, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 40000, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt16(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int32_t) 40000, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) 40000, value.asInt64());
+
+ //int64 (positive in more than 31 bits)
+ value = (int64_t) 3000000000ll;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint16(), InvalidConversion);
+ BOOST_CHECK_EQUAL((uint32_t) 3000000000ll, value.asUint32());
+ BOOST_CHECK_EQUAL((uint64_t) 3000000000ll, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt16(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt32(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int64_t) 3000000000ll, value.asInt64());
+
+ //int64 (positive in more than 32 bits)
+ value = (int64_t) 5000000000ll;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint16(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint32(), InvalidConversion);
+ BOOST_CHECK_EQUAL((uint64_t) 5000000000ll, value.asUint64());
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt16(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt32(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int64_t) 5000000000ll, value.asInt64());
+
+ //int64 (negative in 7 bits)
+ value = (int64_t) -100;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint16(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint32(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint64(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int8_t) -100, value.asInt8());
+ BOOST_CHECK_EQUAL((int16_t) -100, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) -100, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) -100, value.asInt64());
+
+ //int64 (negative in more than 7 bits)
+ value = (int64_t) -1000;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint16(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint32(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint64(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int16_t) -1000, value.asInt16());
+ BOOST_CHECK_EQUAL((int32_t) -1000, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) -1000, value.asInt64());
+
+ //int64 (negative in more than 15 bits)
+ value = (int64_t) -40000;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint16(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint32(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint64(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt16(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int32_t) -40000, value.asInt32());
+ BOOST_CHECK_EQUAL((int64_t) -40000, value.asInt64());
+
+ //int64 (negative in more than 31 bits)
+ value = (int64_t) -3000000000ll;
+ BOOST_CHECK_THROW(value.asUint8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint16(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint32(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asUint64(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt8(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt16(), InvalidConversion);
+ BOOST_CHECK_THROW(value.asInt32(), InvalidConversion);
+ BOOST_CHECK_EQUAL((int64_t) -3000000000ll, value.asInt64());
+}
+
+QPID_AUTO_TEST_CASE(testAssignment)
+{
+ Variant value("abc");
+ Variant other = value;
+ BOOST_CHECK_EQUAL(VAR_STRING, value.getType());
+ BOOST_CHECK_EQUAL(other.getType(), value.getType());
+ BOOST_CHECK_EQUAL(other.asString(), value.asString());
+
+ const uint32_t i(1000);
+ value = i;
+ BOOST_CHECK_EQUAL(VAR_UINT32, value.getType());
+ BOOST_CHECK_EQUAL(VAR_STRING, other.getType());
+}
+
+QPID_AUTO_TEST_CASE(testList)
+{
+ const std::string s("abc");
+ const float f(9.876f);
+ const int16_t x(1000);
+
+ Variant value = Variant::List();
+ value.asList().push_back(Variant(s));
+ value.asList().push_back(Variant(f));
+ value.asList().push_back(Variant(x));
+ BOOST_CHECK_EQUAL(3u, value.asList().size());
+ Variant::List::const_iterator i = value.asList().begin();
+
+ BOOST_CHECK(i != value.asList().end());
+ BOOST_CHECK_EQUAL(VAR_STRING, i->getType());
+ BOOST_CHECK_EQUAL(s, i->asString());
+ i++;
+
+ BOOST_CHECK(i != value.asList().end());
+ BOOST_CHECK_EQUAL(VAR_FLOAT, i->getType());
+ BOOST_CHECK_EQUAL(f, i->asFloat());
+ i++;
+
+ BOOST_CHECK(i != value.asList().end());
+ BOOST_CHECK_EQUAL(VAR_INT16, i->getType());
+ BOOST_CHECK_EQUAL(x, i->asInt16());
+ i++;
+
+ BOOST_CHECK(i == value.asList().end());
+}
+
+QPID_AUTO_TEST_CASE(testMap)
+{
+ const std::string red("red");
+ const float pi(3.14f);
+ const int16_t x(1000);
+ const Uuid u(true);
+
+ Variant value = Variant::Map();
+ value.asMap()["colour"] = red;
+ value.asMap()["pi"] = pi;
+ value.asMap()["my-key"] = x;
+ value.asMap()["id"] = u;
+ BOOST_CHECK_EQUAL(4u, value.asMap().size());
+
+ BOOST_CHECK_EQUAL(VAR_STRING, value.asMap()["colour"].getType());
+ BOOST_CHECK_EQUAL(red, value.asMap()["colour"].asString());
+
+ BOOST_CHECK_EQUAL(VAR_FLOAT, value.asMap()["pi"].getType());
+ BOOST_CHECK_EQUAL(pi, value.asMap()["pi"].asFloat());
+
+ BOOST_CHECK_EQUAL(VAR_INT16, value.asMap()["my-key"].getType());
+ BOOST_CHECK_EQUAL(x, value.asMap()["my-key"].asInt16());
+
+ BOOST_CHECK_EQUAL(VAR_UUID, value.asMap()["id"].getType());
+ BOOST_CHECK_EQUAL(u, value.asMap()["id"].asUuid());
+
+ value.asMap()["my-key"] = "now it's a string";
+ BOOST_CHECK_EQUAL(VAR_STRING, value.asMap()["my-key"].getType());
+ BOOST_CHECK_EQUAL(std::string("now it's a string"), value.asMap()["my-key"].asString());
+}
+
+QPID_AUTO_TEST_CASE(testIsEqualTo)
+{
+ BOOST_CHECK_EQUAL(Variant("abc"), Variant("abc"));
+ BOOST_CHECK_EQUAL(Variant(1234), Variant(1234));
+
+ Variant a = Variant::Map();
+ a.asMap()["colour"] = "red";
+ a.asMap()["pi"] = 3.14f;
+ a.asMap()["my-key"] = 1234;
+ Variant b = Variant::Map();
+ b.asMap()["colour"] = "red";
+ b.asMap()["pi"] = 3.14f;
+ b.asMap()["my-key"] = 1234;
+ BOOST_CHECK_EQUAL(a, b);
+}
+
+QPID_AUTO_TEST_CASE(testEncoding)
+{
+ Variant a("abc");
+ a.setEncoding("utf8");
+ Variant b = a;
+ Variant map = Variant::Map();
+ map.asMap()["a"] = a;
+ map.asMap()["b"] = b;
+ BOOST_CHECK_EQUAL(a.getEncoding(), std::string("utf8"));
+ BOOST_CHECK_EQUAL(a.getEncoding(), b.getEncoding());
+ BOOST_CHECK_EQUAL(a.getEncoding(), map.asMap()["a"].getEncoding());
+ BOOST_CHECK_EQUAL(b.getEncoding(), map.asMap()["b"].getEncoding());
+ BOOST_CHECK_EQUAL(map.asMap()["a"].getEncoding(), map.asMap()["b"].getEncoding());
+}
+
+QPID_AUTO_TEST_CASE(testBufferEncoding)
+{
+ Variant a("abc");
+ a.setEncoding("utf8");
+ std::string buffer;
+
+ Variant::Map inMap, outMap;
+ inMap["a"] = a;
+
+ MapCodec::encode(inMap, buffer);
+ MapCodec::decode(buffer, outMap);
+ BOOST_CHECK_EQUAL(inMap, outMap);
+
+ inMap["b"] = Variant(std::string(65535, 'X'));
+ inMap["b"].setEncoding("utf16");
+ MapCodec::encode(inMap, buffer);
+ MapCodec::decode(buffer, outMap);
+ BOOST_CHECK_EQUAL(inMap, outMap);
+
+ inMap["fail"] = Variant(std::string(65536, 'X'));
+ inMap["fail"].setEncoding("utf16");
+ BOOST_CHECK_THROW(MapCodec::encode(inMap, buffer), std::exception);
+}
+
+QPID_AUTO_TEST_CASE(parse)
+{
+ Variant a;
+ a.parse("What a fine mess");
+ BOOST_CHECK(a.getType()==types::VAR_STRING);
+ a.parse("true");
+ BOOST_CHECK(a.getType()==types::VAR_BOOL);
+ a.parse("FalsE");
+ BOOST_CHECK(a.getType()==types::VAR_BOOL);
+ a.parse("3.1415926");
+ BOOST_CHECK(a.getType()==types::VAR_DOUBLE);
+ a.parse("-7.2e-15");
+ BOOST_CHECK(a.getType()==types::VAR_DOUBLE);
+ a.parse("9223372036854775807");
+ BOOST_CHECK(a.getType()==types::VAR_INT64);
+ a.parse("9223372036854775808");
+ BOOST_CHECK(a.getType()==types::VAR_UINT64);
+ a.parse("-9223372036854775807");
+ BOOST_CHECK(a.getType()==types::VAR_INT64);
+ a.parse("-9223372036854775808");
+ BOOST_CHECK(a.getType()==types::VAR_DOUBLE);
+ a.parse("18446744073709551615");
+ BOOST_CHECK(a.getType()==types::VAR_UINT64);
+ a.parse("18446744073709551616");
+ BOOST_CHECK(a.getType()==types::VAR_DOUBLE);
+}
+
+QPID_AUTO_TEST_CASE(described)
+{
+ Variant a;
+ BOOST_CHECK(!a.isDescribed());
+ a.getDescriptors().push_back("foo");
+ BOOST_CHECK(a.isDescribed());
+ BOOST_CHECK_EQUAL(a.getDescriptors().size(), 1U);
+ BOOST_CHECK_EQUAL(a.getDescriptors().front(), Variant("foo"));
+ a = 42;
+ BOOST_CHECK(a.isDescribed());
+ BOOST_CHECK_EQUAL(a.getDescriptors().size(), 1U);
+ BOOST_CHECK_EQUAL(a.getDescriptors().front(), Variant("foo"));
+ a.getDescriptors().push_back(33);
+ BOOST_CHECK_EQUAL(a.getDescriptors().size(), 2U);
+ BOOST_CHECK_EQUAL(a.getDescriptors().front(), Variant("foo"));
+ BOOST_CHECK_EQUAL(*(++a.getDescriptors().begin()), Variant(33));
+ a.getDescriptors().clear();
+ BOOST_CHECK(!a.isDescribed());
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/XmlClientSessionTest.cpp b/qpid/cpp/src/tests/XmlClientSessionTest.cpp
new file mode 100644
index 0000000000..bfa6ed096b
--- /dev/null
+++ b/qpid/cpp/src/tests/XmlClientSessionTest.cpp
@@ -0,0 +1,301 @@
+/*
+ *
+ * Licensed to the Apachef Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "unit_test.h"
+#include "test_tools.h"
+#include "BrokerFixture.h"
+#include "qpid/sys/Shlib.h"
+#include "qpid/sys/Monitor.h"
+#include "qpid/sys/Thread.h"
+#include "qpid/sys/Runnable.h"
+#include "qpid/client/Message.h"
+#include "qpid/framing/reply_exceptions.h"
+#include "qpid/client/Connection.h"
+#include "qpid/client/SubscriptionManager.h"
+#include "qpid/client/LocalQueue.h"
+#include "qpid/client/Session.h"
+#include "qpid/client/SubscriptionManager.h"
+
+#include <boost/optional.hpp>
+#include <boost/lexical_cast.hpp>
+
+#include <vector>
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(XmlClientSessionTest)
+
+struct XmlFixture {
+ XmlFixture() {
+ qpid::sys::Shlib shlib(getLibPath("XML_LIB"));
+ }
+ ~XmlFixture() {}
+};
+
+using namespace qpid::client;
+using namespace qpid::client::arg;
+using namespace qpid::framing;
+using namespace qpid;
+
+using qpid::sys::Monitor;
+using std::string;
+using std::cout;
+using std::endl;
+
+
+class SubscribedLocalQueue : public LocalQueue {
+ private:
+ SubscriptionManager& subscriptions;
+ public:
+ SubscribedLocalQueue(SubscriptionManager& subs) : subscriptions(subs) {}
+ Message get () { return pop(); }
+ Message get (sys::Duration timeout) { return pop(timeout); }
+ virtual ~SubscribedLocalQueue() {}
+};
+
+
+struct SimpleListener : public MessageListener
+{
+ Monitor lock;
+ std::vector<Message> messages;
+
+ void received(Message& msg)
+ {
+ Monitor::ScopedLock l(lock);
+ messages.push_back(msg);
+ lock.notifyAll();
+ }
+
+ void waitFor(const uint n)
+ {
+ Monitor::ScopedLock l(lock);
+ while (messages.size() < n) {
+ lock.wait();
+ }
+ }
+};
+
+struct ClientSessionFixture : public SessionFixture
+{
+ void declareSubscribe(const string& q="odd_blue",
+ const string& dest="xml")
+ {
+ session.queueDeclare(queue=q);
+ session.messageSubscribe(queue=q, destination=dest, acquireMode=1);
+ session.messageFlow(destination=dest, unit=0, value=0xFFFFFFFF);//messages
+ session.messageFlow(destination=dest, unit=1, value=0xFFFFFFFF);//bytes
+ }
+};
+
+// ########### START HERE ####################################
+
+QPID_FIXTURE_TEST_CASE(testXmlBinding, XmlFixture) {
+ ClientSessionFixture f;
+
+ SubscriptionManager subscriptions(f.session);
+ SubscribedLocalQueue localQueue(subscriptions);
+
+ f.session.exchangeDeclare(qpid::client::arg::exchange="xml", qpid::client::arg::type="xml");
+ f.session.queueDeclare(qpid::client::arg::queue="odd_blue");
+ subscriptions.subscribe(localQueue, "odd_blue");
+
+ FieldTable binding;
+ binding.setString("xquery", "declare variable $color external;"
+ "(./message/id mod 2 = 1) and ($color = 'blue')");
+ f.session.exchangeBind(qpid::client::arg::exchange="xml", qpid::client::arg::queue="odd_blue", qpid::client::arg::bindingKey="query_name", qpid::client::arg::arguments=binding);
+
+ Message message;
+ message.getDeliveryProperties().setRoutingKey("query_name");
+
+ message.getHeaders().setString("color", "blue");
+ string m = "<message><id>1</id></message>";
+ message.setData(m);
+
+ f.session.messageTransfer(qpid::client::arg::content=message, qpid::client::arg::destination="xml");
+
+ Message m2 = localQueue.get(1*qpid::sys::TIME_SEC);
+ BOOST_CHECK_EQUAL(m, m2.getData());
+}
+
+/**
+ * Ensure that multiple queues can be bound using the same routing key
+ */
+QPID_FIXTURE_TEST_CASE(testXMLBindMultipleQueues, XmlFixture) {
+ ClientSessionFixture f;
+
+
+ f.session.exchangeDeclare(arg::exchange="xml", arg::type="xml");
+ f.session.queueDeclare(arg::queue="blue", arg::exclusive=true, arg::autoDelete=true);
+ f.session.queueDeclare(arg::queue="red", arg::exclusive=true, arg::autoDelete=true);
+
+ FieldTable blue;
+ blue.setString("xquery", "./colour = 'blue'");
+ f.session.exchangeBind(arg::exchange="xml", arg::queue="blue", arg::bindingKey="by-colour", arg::arguments=blue);
+ FieldTable red;
+ red.setString("xquery", "./colour = 'red'");
+ f.session.exchangeBind(arg::exchange="xml", arg::queue="red", arg::bindingKey="by-colour", arg::arguments=red);
+
+ Message sent1("<colour>blue</colour>", "by-colour");
+ f.session.messageTransfer(arg::content=sent1, arg::destination="xml");
+
+ Message sent2("<colour>red</colour>", "by-colour");
+ f.session.messageTransfer(arg::content=sent2, arg::destination="xml");
+
+ Message received;
+ BOOST_CHECK(f.subs.get(received, "blue"));
+ BOOST_CHECK_EQUAL(sent1.getData(), received.getData());
+ BOOST_CHECK(f.subs.get(received, "red"));
+ BOOST_CHECK_EQUAL(sent2.getData(), received.getData());
+}
+
+//### Test: Bad XML does not kill the server - and does not even
+// raise an exception, the content is not required to be XML.
+
+QPID_FIXTURE_TEST_CASE(testXMLSendBadXML, XmlFixture) {
+ ClientSessionFixture f;
+
+ f.session.exchangeDeclare(arg::exchange="xml", arg::type="xml");
+ f.session.queueDeclare(arg::queue="blue", arg::exclusive=true, arg::autoDelete=true)\
+ ;
+ f.session.queueDeclare(arg::queue="red", arg::exclusive=true, arg::autoDelete=true);
+
+ FieldTable blue;
+ blue.setString("xquery", "./colour = 'blue'");
+ f.session.exchangeBind(arg::exchange="xml", arg::queue="blue", arg::bindingKey="by-c\
+olour", arg::arguments=blue);
+ FieldTable red;
+ red.setString("xquery", "./colour = 'red'");
+ f.session.exchangeBind(arg::exchange="xml", arg::queue="red", arg::bindingKey="by-co\
+lour", arg::arguments=red);
+
+ Message sent1("<>colour>blue</colour>", "by-colour");
+ f.session.messageTransfer(arg::content=sent1, arg::destination="xml");
+
+ BOOST_CHECK_EQUAL(1, 1);
+}
+
+
+//### Test: Bad XQuery does not kill the server, but does raise an exception
+
+QPID_FIXTURE_TEST_CASE(testXMLBadXQuery, XmlFixture) {
+ ClientSessionFixture f;
+
+ f.session.exchangeDeclare(arg::exchange="xml", arg::type="xml");
+ f.session.queueDeclare(arg::queue="blue", arg::exclusive=true, arg::autoDelete=true)\
+ ;
+
+ try {
+ ScopedSuppressLogging sl; // Supress logging of error messages for expected error.
+ FieldTable blue;
+ blue.setString("xquery", "./colour $=! 'blue'");
+ f.session.exchangeBind(arg::exchange="xml", arg::queue="blue", arg::bindingKey="by-c\
+olour", arg::arguments=blue);
+ }
+ catch (const InternalErrorException& e) {
+ return;
+ }
+ BOOST_ERROR("A bad XQuery must raise an exception when used in an XML Binding.");
+
+}
+
+
+//### Test: double, string, and integer field values can all be bound to queries
+
+QPID_FIXTURE_TEST_CASE(testXmlBindingUntyped, XmlFixture) {
+ ClientSessionFixture f;
+
+ SubscriptionManager subscriptions(f.session);
+ SubscribedLocalQueue localQueue(subscriptions);
+
+ f.session.exchangeDeclare(qpid::client::arg::exchange="xml", qpid::client::arg::type="xml");
+ f.session.queueDeclare(qpid::client::arg::queue="odd_blue");
+ subscriptions.subscribe(localQueue, "odd_blue");
+
+ FieldTable binding;
+ binding.setString("xquery",
+ "declare variable $s external;"
+ "declare variable $i external;"
+ "declare variable $d external;"
+ "$s = 'string' and $i = 1 and $d < 1");
+ f.session.exchangeBind(qpid::client::arg::exchange="xml", qpid::client::arg::queue="odd_blue", qpid::client::arg::bindingKey="query_name", qpid::client::arg::arguments=binding);
+
+ Message message;
+ message.getDeliveryProperties().setRoutingKey("query_name");
+
+ message.getHeaders().setString("s", "string");
+ message.getHeaders().setInt("i", 1);
+ message.getHeaders().setDouble("d", 0.5);
+ string m = "<message>Hi, Mom!</message>";
+ message.setData(m);
+
+ f.session.messageTransfer(qpid::client::arg::content=message, qpid::client::arg::destination="xml");
+
+ Message m2 = localQueue.get(1*qpid::sys::TIME_SEC);
+ BOOST_CHECK_EQUAL(m, m2.getData());
+}
+
+
+//### Test: double, string, and integer field values can all be bound to queries
+
+QPID_FIXTURE_TEST_CASE(testXmlBindingTyped, XmlFixture) {
+ ClientSessionFixture f;
+
+ SubscriptionManager subscriptions(f.session);
+ SubscribedLocalQueue localQueue(subscriptions);
+
+ f.session.exchangeDeclare(qpid::client::arg::exchange="xml", qpid::client::arg::type="xml");
+ f.session.queueDeclare(qpid::client::arg::queue="odd_blue");
+ subscriptions.subscribe(localQueue, "odd_blue");
+
+ FieldTable binding;
+ binding.setString("xquery",
+ "declare variable $s as xs:string external;"
+ "declare variable $i as xs:integer external;"
+ "declare variable $d external;" // XQilla bug when declaring xs:float, xs:double types? Fine if untyped, acts as float.
+ "$s = 'string' and $i = 1 and $d < 1");
+ f.session.exchangeBind(qpid::client::arg::exchange="xml", qpid::client::arg::queue="odd_blue", qpid::client::arg::bindingKey="query_name", qpid::client::arg::arguments=binding);
+
+ Message message;
+ message.getDeliveryProperties().setRoutingKey("query_name");
+
+ message.getHeaders().setString("s", "string");
+ message.getHeaders().setInt("i", 1);
+ message.getHeaders().setDouble("d", 0.5);
+ string m = "<message>Hi, Mom!</message>";
+ message.setData(m);
+
+ f.session.messageTransfer(qpid::client::arg::content=message, qpid::client::arg::destination="xml");
+
+ Message m2 = localQueue.get(1*qpid::sys::TIME_SEC);
+ BOOST_CHECK_EQUAL(m, m2.getData());
+}
+
+
+//### Test: Each session can provide its own definition for a query name
+
+
+
+//### Test: Bindings persist, surviving broker restart
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/acl.py b/qpid/cpp/src/tests/acl.py
new file mode 100755
index 0000000000..2838bd3f83
--- /dev/null
+++ b/qpid/cpp/src/tests/acl.py
@@ -0,0 +1,3959 @@
+#!/usr/bin/env python
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 sys
+import qpid
+from qpid.util import connect
+from qpid.connection import Connection
+from qpid.datatypes import uuid4
+from qpid.testlib import TestBase010
+from qmf.console import Session
+from qpid.datatypes import Message
+import qpid.messaging
+from qpidtoollibs import BrokerAgent
+
+class ACLFile:
+ def __init__(self, policy='data_dir/policy.acl'):
+ self.f = open(policy,'w')
+
+ def write(self,line):
+ self.f.write(line)
+
+ def close(self):
+ self.f.close()
+
+class ACLTests(TestBase010):
+
+ # required for testing QMF methods
+ def get_messaging_connection(self, user, passwd):
+ parms = {'username':user, 'password':passwd, 'sasl_mechanisms':'PLAIN'}
+ brokerurl="%s:%s" %(self.broker.host, self.broker.port)
+ connection = qpid.messaging.Connection(brokerurl, **parms)
+ connection.open()
+ return connection
+
+ # For connection limit tests this function
+ # throws if the connection won't start
+ # returns a connection that the caller can close if he likes.
+ def get_connection(self, user, passwd):
+ socket = connect(self.broker.host, self.broker.port)
+ connection = Connection (sock=socket, username=user, password=passwd,
+ mechanism="PLAIN")
+ connection.start()
+ return connection
+
+ def get_session(self, user, passwd):
+ socket = connect(self.broker.host, self.broker.port)
+ connection = Connection (sock=socket, username=user, password=passwd,
+ mechanism="PLAIN")
+ connection.start()
+ return connection.session(str(uuid4()))
+
+ def port_i(self):
+ return int(self.defines["port-i"])
+
+ def port_u(self):
+ return int(self.defines["port-u"])
+
+ def port_q(self):
+ return int(self.defines["port-q"])
+
+ def get_session_by_port(self, user, passwd, byPort):
+ socket = connect(self.broker.host, byPort)
+ connection = Connection (sock=socket, username=user, password=passwd,
+ mechanism="PLAIN")
+ connection.start()
+ return connection.session(str(uuid4()))
+
+ def reload_acl(self):
+ result = None
+ try:
+ self.broker_access.reloadAclFile()
+ except Exception, e:
+ result = str(e)
+ return result
+
+ def acl_lookup(self, userName, action, aclObj, aclObjName, propMap):
+ result = {}
+ try:
+ result = self.broker_access.acl_lookup(userName, action, aclObj, aclObjName, propMap)
+ except Exception, e:
+ result['text'] = str(e)
+ result['result'] = str(e)
+ return result
+
+ def acl_lookupPublish(self, userName, exchange, key):
+ result = {}
+ try:
+ result = self.broker_access.acl_lookupPublish(userName, exchange, key)
+ except Exception, e:
+ result['text'] = str(e)
+ result['result'] = str(e)
+ return result
+
+ def get_acl_file(self):
+ return ACLFile(self.config.defines.get("policy-file", "data_dir/policy.acl"))
+
+ def setUp(self):
+ aclf = self.get_acl_file()
+ aclf.write('acl allow all all\n')
+ aclf.close()
+ TestBase010.setUp(self)
+ self.startBrokerAccess()
+ self.reload_acl()
+
+ def tearDown(self):
+ aclf = self.get_acl_file()
+ aclf.write('acl allow all all\n')
+ aclf.close()
+ self.reload_acl()
+ TestBase010.tearDown(self)
+
+
+ def Lookup(self, userName, action, aclObj, aclObjName, propMap, expectedResult):
+ result = self.acl_lookup(userName, action, aclObj, aclObjName, propMap)
+ if (result['result'] != expectedResult):
+ suffix = ', [ERROR: Expected= ' + expectedResult
+ if (result['result'] is None):
+ suffix = suffix + ', Exception= ' + result['text'] + ']'
+ else:
+ suffix = suffix + ', Actual= ' + result['result'] + ']'
+ self.fail('Lookup: name=' + userName + ', action=' + action + ', aclObj=' + aclObj + ', aclObjName=' + aclObjName + ', propertyMap=' + str(propMap) + suffix)
+
+
+ def LookupPublish(self, userName, exchName, keyName, expectedResult):
+ result = self.acl_lookupPublish(userName, exchName, keyName)
+ if (result['result'] != expectedResult):
+ suffix = ', [ERROR: Expected= ' + expectedResult
+ if (result['result'] is None):
+ suffix = suffix + ', Exception= ' + result['text'] + ']'
+ else:
+ suffix = suffix + ', Actual= ' + result['result'] + ']'
+ self.fail('LookupPublish: name=' + userName + ', exchange=' + exchName + ', key=' + keyName + suffix)
+
+ def AllBut(self, allList, removeList):
+ tmpList = allList[:]
+ for item in removeList:
+ try:
+ tmpList.remove(item)
+ except Exception, e:
+ self.fail("ERROR in AllBut() \nallList = %s \nremoveList = %s \nerror = %s " \
+ % (allList, removeList, e))
+ return tmpList
+
+ #=====================================
+ # ACL general tests
+ #=====================================
+
+ def test_deny_mode(self):
+ """
+ Test the deny all mode
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl allow anonymous all all\n')
+ aclf.write('acl allow bob@QPID create queue\n')
+ aclf.write('acl deny all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('bob','bob')
+ try:
+ session.queue_declare(queue="deny_queue")
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request");
+ self.fail("Error during queue create request");
+
+ try:
+ session.exchange_bind(exchange="amq.direct", queue="deny_queue", binding_key="routing_key")
+ self.fail("ACL should deny queue bind request");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+
+ def test_allow_mode(self):
+ """
+ Test the allow all mode
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID bind exchange\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('bob','bob')
+ try:
+ session.queue_declare(queue="allow_queue")
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request");
+ self.fail("Error during queue create request");
+
+ try:
+ session.exchange_bind(exchange="amq.direct", queue="allow_queue", binding_key="routing_key")
+ self.fail("ACL should deny queue bind request");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+
+
+ def test_allow_mode_with_specfic_allow_override(self):
+ """
+ Specific allow overrides a general deny
+ """
+ aclf = self.get_acl_file()
+ aclf.write('group admins bob@QPID joe@QPID \n')
+ aclf.write('acl allow bob@QPID create queue \n')
+ aclf.write('acl deny admins create queue \n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('bob','bob')
+
+ try:
+ session.queue_declare(queue='zed')
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow create queue request");
+
+
+ #=====================================
+ # ACL file format tests
+ #=====================================
+
+ def test_empty_groups(self):
+ """
+ Test empty groups
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl group\n')
+ aclf.write('acl group admins bob@QPID joe@QPID\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result.find("Insufficient tokens for acl definition",0,len(result)) == -1):
+ self.fail("ACL Reader should reject the acl file due to empty group name")
+
+ def test_illegal_acl_formats(self):
+ """
+ Test illegal acl formats
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl group admins bob@QPID joe@QPID\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result.find("Unknown ACL permission",0,len(result)) == -1):
+ self.fail(result)
+
+ def test_illegal_extension_lines(self):
+ """
+ Test illegal extension lines
+ """
+
+ aclf = self.get_acl_file()
+ aclf.write('group admins bob@QPID \n')
+ aclf.write(' \ \n')
+ aclf.write('joe@QPID \n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result.find("contains an illegal extension",0,len(result)) == -1):
+ self.fail(result)
+
+ if (result.find("Non-continuation line must start with \"group\" or \"acl\"",0,len(result)) == -1):
+ self.fail(result)
+
+ def test_illegal_extension_lines(self):
+ """
+ Test proper extention lines
+ """
+ aclf = self.get_acl_file()
+ aclf.write('group test1 joe@EXAMPLE.com \\ \n') # should be allowed
+ aclf.write(' jack@EXAMPLE.com \\ \n') # should be allowed
+ aclf.write('jill@TEST.COM \\ \n') # should be allowed
+ aclf.write('host/123.example.com@TEST.COM\n') # should be allowed
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ def test_nested_groups(self):
+ """
+ Test nested groups
+ """
+
+ aclf = self.get_acl_file()
+ aclf.write('group user-consume martin@QPID ted@QPID\n')
+ aclf.write('group group2 kim@QPID user-consume rob@QPID \n')
+ aclf.write('acl allow anonymous all all \n')
+ aclf.write('acl allow group2 create queue \n')
+ aclf.write('acl deny all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('rob','rob')
+ try:
+ session.queue_declare(queue="rob_queue")
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request");
+ self.fail("Error during queue create request");
+
+
+
+ def test_user_realm(self):
+ """
+ Test a user defined without a realm
+ Ex. group admin rajith
+ Note: a user name without a realm is interpreted as a group name
+ """
+ aclf = self.get_acl_file()
+ aclf.write('group admin bob\n') # shouldn't be allowed
+ aclf.write('acl deny admin bind exchange\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result.find("not defined yet.",0,len(result)) == -1):
+ self.fail(result)
+
+ def test_allowed_chars_for_username(self):
+ """
+ Test a user defined without a realm
+ Ex. group admin rajith
+ """
+ aclf = self.get_acl_file()
+ aclf.write('group test1 joe@EXAMPLE.com\n') # should be allowed
+ aclf.write('group test2 jack_123-jill@EXAMPLE.com\n') # should be allowed
+ aclf.write('group test4 host/somemachine.example.com@EXAMPLE.COM\n') # should be allowed
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ aclf = self.get_acl_file()
+ aclf.write('group test1 joe$H@EXAMPLE.com\n') # shouldn't be allowed
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result.find("Username \"joe$H@EXAMPLE.com\" contains illegal characters",0,len(result)) == -1):
+ self.fail(result)
+
+ #=====================================
+ # ACL validation tests
+ #=====================================
+
+ def test_illegal_queue_policy(self):
+ """
+ Test illegal queue policy
+ """
+
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 exclusive=true policytype=ding\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "ding is not a valid value for 'policytype', possible values are one of"
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+ def test_illegal_queuemaxsize_upper_limit_spec(self):
+ """
+ Test illegal queue policy
+ """
+ #
+ # Use maxqueuesize
+ #
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 maxqueuesize=-1\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "-1 is not a valid value for 'queuemaxsizeupperlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 maxqueuesize=9223372036854775808\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "9223372036854775808 is not a valid value for 'queuemaxsizeupperlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+ #
+ # Use queuemaxsizeupperlimit
+ #
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 queuemaxsizeupperlimit=-1\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "-1 is not a valid value for 'queuemaxsizeupperlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 queuemaxsizeupperlimit=9223372036854775808\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "9223372036854775808 is not a valid value for 'queuemaxsizeupperlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+
+
+ def test_illegal_queuemaxcount_upper_limit_spec(self):
+ """
+ Test illegal queue policy
+ """
+ #
+ # Use maxqueuecount
+ #
+
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 maxqueuecount=-1\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "-1 is not a valid value for 'queuemaxcountupperlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 maxqueuecount=9223372036854775808\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "9223372036854775808 is not a valid value for 'queuemaxcountupperlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+ #
+ # use maxqueuecountupperlimit
+ #
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 queuemaxcountupperlimit=-1\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "-1 is not a valid value for 'queuemaxcountupperlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 queuemaxcountupperlimit=9223372036854775808\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "9223372036854775808 is not a valid value for 'queuemaxcountupperlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+
+ def test_illegal_queuemaxsize_lower_limit_spec(self):
+ """
+ Test illegal queue policy
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 queuemaxsizelowerlimit=-1\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "-1 is not a valid value for 'queuemaxsizelowerlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 queuemaxsizelowerlimit=9223372036854775808\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "9223372036854775808 is not a valid value for 'queuemaxsizelowerlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+
+
+ def test_illegal_queuemaxcount_lower_limit_spec(self):
+ """
+ Test illegal queue policy
+ """
+
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 queuemaxcountlowerlimit=-1\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "-1 is not a valid value for 'queuemaxcountlowerlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 queuemaxcountlowerlimit=9223372036854775808\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "9223372036854775808 is not a valid value for 'queuemaxcountlowerlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+
+ def test_illegal_filemaxsize_upper_limit_spec(self):
+ """
+ Test illegal file policy
+ """
+ #
+ # Use filemaxsizeupperlimit
+ #
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 filemaxsizeupperlimit=-1\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "-1 is not a valid value for 'filemaxsizeupperlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 filemaxsizeupperlimit=9223372036854775808\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "9223372036854775808 is not a valid value for 'filemaxsizeupperlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+
+
+ def test_illegal_filemaxcount_upper_limit_spec(self):
+ """
+ Test illegal file policy
+ """
+ #
+ # use maxfilecountupperlimit
+ #
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 filemaxcountupperlimit=-1\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "-1 is not a valid value for 'filemaxcountupperlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 filemaxcountupperlimit=9223372036854775808\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "9223372036854775808 is not a valid value for 'filemaxcountupperlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+
+ def test_illegal_filemaxsize_lower_limit_spec(self):
+ """
+ Test illegal file policy
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 filemaxsizelowerlimit=-1\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "-1 is not a valid value for 'filemaxsizelowerlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 filemaxsizelowerlimit=9223372036854775808\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "9223372036854775808 is not a valid value for 'filemaxsizelowerlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+
+
+ def test_illegal_filemaxcount_lower_limit_spec(self):
+ """
+ Test illegal file policy
+ """
+
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 filemaxcountlowerlimit=-1\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "-1 is not a valid value for 'filemaxcountlowerlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 filemaxcountlowerlimit=9223372036854775808\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "9223372036854775808 is not a valid value for 'filemaxcountlowerlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+
+ def test_illegal_pages_lower_limit_spec(self):
+ """
+ Test illegal paged queue policy
+ """
+
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 pageslowerlimit=-1\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "-1 is not a valid value for 'pageslowerlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 pageslowerlimit=9223372036854775808\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "9223372036854775808 is not a valid value for 'pageslowerlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+
+ def test_illegal_pages_upper_limit_spec(self):
+ """
+ Test illegal paged queue policy
+ """
+
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 pagesupperlimit=-1\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "-1 is not a valid value for 'pagesupperlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 pagesupperlimit=9223372036854775808\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "9223372036854775808 is not a valid value for 'pagesupperlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+
+ def test_illegal_pagefactor_lower_limit_spec(self):
+ """
+ Test illegal paged queue policy
+ """
+
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 pagefactorlowerlimit=-1\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "-1 is not a valid value for 'pagefactorlowerlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 pagefactorlowerlimit=9223372036854775808\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "9223372036854775808 is not a valid value for 'pagefactorlowerlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+
+ def test_illegal_pagefactor_upper_limit_spec(self):
+ """
+ Test illegal paged queue policy
+ """
+
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 pagefactorupperlimit=-1\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "-1 is not a valid value for 'pagefactorupperlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=q2 pagefactorupperlimit=9223372036854775808\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ expected = "9223372036854775808 is not a valid value for 'pagefactorupperlimit', " \
+ "values should be between 0 and 9223372036854775807";
+ if (result.find(expected) == -1):
+ self.fail(result)
+
+
+ #=====================================
+ # ACL queue tests
+ #=====================================
+
+ def test_queue_allow_mode(self):
+ """
+ Test cases for queue acl in allow mode
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID access queue name=q1\n')
+ aclf.write('acl deny bob@QPID create queue name=q1 durable=true\n')
+ aclf.write('acl deny bob@QPID create queue name=q2 exclusive=true policytype=ring\n')
+ aclf.write('acl deny bob@QPID access queue name=q3\n')
+ aclf.write('acl deny bob@QPID delete queue name=q4\n')
+ aclf.write('acl deny bob@QPID create queue name=q5 maxqueuesize=1000 maxqueuecount=100\n')
+ aclf.write('acl deny bob@QPID create queue name=q6 paging=true\n')
+ aclf.write('acl deny bob@QPID purge queue name=q7\n')
+ aclf.write('acl deny bob@QPID move queue name=q7\n')
+ aclf.write('acl deny bob@QPID move queue name=q8 queuename=q7\n')
+ aclf.write('acl deny bob@QPID redirect queue name=q7\n')
+ aclf.write('acl deny bob@QPID redirect queue name=q8 queuename=q7\n')
+ aclf.write('acl deny bob@QPID reroute queue name=q7\n')
+ aclf.write('acl deny bob@QPID reroute queue name=q8 exchangename=amq.fanout\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('bob','bob')
+
+ try:
+ session.queue_declare(queue="q1", durable=True)
+ self.fail("ACL should deny queue create request with name=q1 durable=true");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.queue_declare(queue="q1", durable=True, passive=True)
+ self.fail("ACL should deny queue passive declare request with name=q1 durable=true");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.policy_type"] = "ring"
+ session.queue_declare(queue="q2", exclusive=True, arguments=queue_options)
+ self.fail("ACL should deny queue create request with name=q2 exclusive=true qpid.policy_type=ring");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.policy_type"] = "ring_strict"
+ session.queue_declare(queue="q2", exclusive=True, arguments=queue_options)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request with name=q2 exclusive=true qpid.policy_type=ring_strict");
+
+ try:
+ queue_options = {}
+ queue_options["qpid.max_count"] = 200
+ queue_options["qpid.max_size"] = 500
+ session.queue_declare(queue="q5", exclusive=True, arguments=queue_options)
+ self.fail("ACL should deny queue create request with name=q2, qpid.max_size=500 and qpid.max_count=200");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.paging"] = True
+ session.queue_declare(queue="q6", arguments=queue_options)
+ self.fail("ACL should deny queue create request with name=q6, qpid.paging=True");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.max_count"] = 200
+ queue_options["qpid.max_size"] = 100
+ session.queue_declare(queue="q2", exclusive=True, arguments=queue_options)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request with name=q2, qpid.max_size=100 and qpid.max_count=200 ");
+ try:
+ session.queue_declare(queue="q3", exclusive=True)
+ session.queue_declare(queue="q4", durable=True)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request for q3 and q4 with any parameter");
+
+ try:
+ session.queue_query(queue="q3")
+ self.fail("ACL should deny queue query request for q3");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ # some queues needs to be created for testing purge / move / reroute / redirect
+ session.queue_declare(queue="q7")
+ session.queue_declare(queue="q8")
+ session.queue_declare(queue="q9")
+ try:
+ session.queue_purge(queue="q7")
+ self.fail("ACL should deny queue purge request for q7");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.queue_purge(queue="q8")
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue purge request for q8");
+
+ # as we use QMF methods, it is easier to use BrokerAgent from messaging.connection and not use session object as above
+ broker_agent = BrokerAgent(self.get_messaging_connection('bob','bob'))
+
+ try:
+ broker_agent.queueMoveMessages("q7", "q8", 0)
+ self.fail("ACL should deny queue move request from q7 to q8");
+ except Exception, e:
+ self.assertTrue("'error_code': 7" in e.args[0])
+ broker_agent = BrokerAgent(self.get_messaging_connection('bob','bob'))
+
+ try:
+ broker_agent.queueMoveMessages("q8", "q9", 0)
+ except Exception, e:
+ if ("'error_code': 7" in e.args[0]):
+ self.fail("ACL should allow queue move request from q8 to q9");
+
+ try:
+ broker_agent.queueMoveMessages("q9", "q8", 0)
+ except Exception, e:
+ if ("'error_code': 7" in e.args[0]):
+ self.fail("ACL should allow queue move request from q9 to q8");
+
+ try:
+ broker_agent.Redirect("q7", "q8")
+ self.fail("ACL should deny queue redirect request from q7 to q8");
+ except Exception, e:
+ self.assertTrue("'error_code': 7" in e.args[0])
+ broker_agent = BrokerAgent(self.get_messaging_connection('bob','bob'))
+
+ try:
+ broker_agent.Redirect("q8", "q9")
+ except Exception, e:
+ if ("'error_code': 7" in e.args[0]):
+ self.fail("ACL should allow queue redirect request from q8 to q9");
+
+ try:
+ broker_agent.Redirect("q9", "q8")
+ except Exception, e:
+ if ("'error_code': 7" in e.args[0]):
+ self.fail("ACL should allow queue redirect request from q9 to q8");
+
+ try:
+ broker_agent.getQueue('q7').reroute(0, False, "amq.fanout")
+ self.fail("ACL should deny queue reroute request from q7 to amq.fanout");
+ except Exception, e:
+ self.assertTrue("'error_code': 7" in e.args[0])
+ broker_agent = BrokerAgent(self.get_messaging_connection('bob','bob'))
+
+ try:
+ broker_agent.getQueue('q8').reroute(0, False, "amq.fanout")
+ self.fail("ACL should deny queue reroute request from q8 to amq.fanout");
+ except Exception, e:
+ self.assertTrue("'error_code': 7" in e.args[0])
+ broker_agent = BrokerAgent(self.get_messaging_connection('bob','bob'))
+
+ try:
+ broker_agent.getQueue('q8').reroute(0, False, "amq.direct")
+ except Exception, e:
+ if ("'error_code': 7" in e.args[0]):
+ self.fail("ACL should allow queue reroute request from q8 to amq.direct");
+
+ try:
+ broker_agent.getQueue('q9').reroute(0, False, "amq.fanout")
+ except Exception, e:
+ if ("'error_code': 7" in e.args[0]):
+ self.fail("ACL should allow queue reroute request from q9 to amq.fanout");
+
+ try:
+ session.queue_delete(queue="q4")
+ self.fail("ACL should deny queue delete request for q4");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.queue_delete(queue="q3")
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue delete request for q3");
+
+
+ def test_queue_deny_mode(self):
+ """
+ Test cases for queue acl in deny mode
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl allow bob@QPID access queue name=q1\n')
+ aclf.write('acl allow bob@QPID create queue name=q1 durable=true\n')
+ aclf.write('acl allow bob@QPID create queue name=q2 exclusive=true policytype=ring\n')
+ aclf.write('acl allow bob@QPID access queue name=q3\n')
+ aclf.write('acl allow bob@QPID purge queue name=q3\n')
+ aclf.write('acl allow bob@QPID create queue name=q3\n')
+ aclf.write('acl allow bob@QPID create queue name=q4\n')
+ aclf.write('acl allow bob@QPID delete queue name=q4\n')
+ aclf.write('acl allow bob@QPID create queue name=q5 maxqueuesize=1000 maxqueuecount=100\n')
+ aclf.write('acl allow bob@QPID create queue name=q6 queuemaxsizelowerlimit=50 queuemaxsizeupperlimit=100 queuemaxcountlowerlimit=50 queuemaxcountupperlimit=100\n')
+ aclf.write('acl allow bob@QPID create queue name=q7 policytype=self-destruct\n')
+ aclf.write('acl allow anonymous all all\n')
+ aclf.write('acl deny all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('bob','bob')
+
+ try:
+ session.queue_declare(queue="q1", durable=True)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request with name=q1 durable=true");
+
+ try:
+ session.queue_declare(queue="q1", durable=True, passive=True)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue passive declare request with name=q1 durable=true passive=true");
+
+ try:
+ session.queue_declare(queue="q1", durable=False, passive=False)
+ self.fail("ACL should deny queue create request with name=q1 durable=true passive=false");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.queue_declare(queue="q2", exclusive=False)
+ self.fail("ACL should deny queue create request with name=q2 exclusive=false");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.max_count"] = 200
+ queue_options["qpid.max_size"] = 500
+ session.queue_declare(queue="q5", arguments=queue_options)
+ self.fail("ACL should deny queue create request with name=q5 maxqueuesize=500 maxqueuecount=200");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.max_count"] = 100
+ queue_options["qpid.max_size"] = 500
+ session.queue_declare(queue="q5", arguments=queue_options)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request with name=q5 maxqueuesize=500 maxqueuecount=200");
+
+ try:
+ queue_options = {}
+ queue_options["qpid.max_count"] = 49
+ queue_options["qpid.max_size"] = 100
+ session.queue_declare(queue="q6", arguments=queue_options)
+ self.fail("ACL should deny queue create request with name=q6 maxqueuesize=100 maxqueuecount=49");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.max_count"] = 101
+ queue_options["qpid.max_size"] = 100
+ session.queue_declare(queue="q6", arguments=queue_options)
+ self.fail("ACL should allow queue create request with name=q6 maxqueuesize=100 maxqueuecount=101");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.max_count"] = 100
+ queue_options["qpid.max_size"] = 49
+ session.queue_declare(queue="q6", arguments=queue_options)
+ self.fail("ACL should deny queue create request with name=q6 maxqueuesize=49 maxqueuecount=100");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.max_count"] = 100
+ queue_options["qpid.max_size"] =101
+ session.queue_declare(queue="q6", arguments=queue_options)
+ self.fail("ACL should deny queue create request with name=q6 maxqueuesize=101 maxqueuecount=100");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.max_count"] = 50
+ queue_options["qpid.max_size"] = 50
+ session.queue_declare(queue="q6", arguments=queue_options)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request with name=q6 maxqueuesize=50 maxqueuecount=50");
+
+ try:
+ queue_options = {}
+ queue_options["qpid.policy_type"] = "ring"
+ session.queue_declare(queue="q2", exclusive=True, arguments=queue_options)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request for q2 with exclusive=true policytype=ring");
+
+ try:
+ session.queue_declare(queue="q7", arguments={"qpid.policy_type": "ring"})
+ self.fail("ACL should not allow queue create request for q7 with policytype=ring");
+ except qpid.session.SessionException, e:
+ session = self.get_session('bob','bob')
+
+ try:
+ session.queue_declare(queue="q7", arguments={"qpid.policy_type": "self-destruct"})
+ except qpid.session.SessionException, e:
+ self.fail("ACL should allow queue create request for q7 with policytype=self-destruct");
+
+ try:
+ session.queue_declare(queue="q3")
+ session.queue_declare(queue="q4")
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request for q3 and q4");
+
+ try:
+ session.queue_query(queue="q4")
+ self.fail("ACL should deny queue query request for q4");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.queue_purge(queue="q4")
+ self.fail("ACL should deny queue purge request for q4");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.queue_purge(queue="q3")
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue purge request for q3");
+
+ try:
+ session.queue_query(queue="q3")
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue query request for q3");
+
+ try:
+ session.queue_delete(queue="q3")
+ self.fail("ACL should deny queue delete request for q3");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.queue_delete(queue="q4")
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue delete request for q4");
+
+ #=====================================
+ # ACL paged tests
+ #=====================================
+
+ def test_paged_allow_mode(self):
+ """
+ Test cases for paged acl in allow mode
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID create queue name=qf1 pageslowerlimit=1000\n')
+ aclf.write('acl deny bob@QPID create queue name=qf2 pagesupperlimit=100\n')
+ aclf.write('acl deny bob@QPID create queue name=qf3 pagefactorlowerlimit=10\n')
+ aclf.write('acl deny bob@QPID create queue name=qf4 pagefactorupperlimit=1\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.paging"] = True
+ queue_options["qpid.max_pages_loaded"] = 500
+ session.queue_declare(queue="qf1", arguments=queue_options)
+ self.fail("ACL should deny queue create request with name=qf1, qpid.paging=True, qpid.max_pages_loaded=500");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.paging"] = True
+ queue_options["qpid.max_pages_loaded"] = 500
+ session.queue_declare(queue="qf2", arguments=queue_options)
+ self.fail("ACL should deny queue create request with name=qf2, qpid.paging=True, qpid.max_pages_loaded=500");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.paging"] = True
+ queue_options["qpid.page_factor"] = 5
+ session.queue_declare(queue="qf3", arguments=queue_options)
+ self.fail("ACL should deny queue create request with name=qf3, qpid.paging=True, qpid.page_factor=5");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.paging"] = True
+ queue_options["qpid.page_factor"] = 5
+ session.queue_declare(queue="qf4", arguments=queue_options)
+ self.fail("ACL should deny queue create request with name=qf4, qpid.paging=True, qpid.page_factor=5");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+
+ def test_paged_deny_mode(self):
+ """
+ Test cases for paged acl in deny mode
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl allow bob@QPID create queue name=qf1 pageslowerlimit=100 pagesupperlimit=1000\n')
+ aclf.write('acl allow bob@QPID create queue name=qf2 pagefactorlowerlimit=1 pagefactorupperlimit=10\n')
+ aclf.write('acl allow anonymous all all\n')
+ aclf.write('acl deny all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.paging"] = True
+ queue_options["qpid.max_pages_loaded"] = 500
+ session.queue_declare(queue="qf1", arguments=queue_options)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request with name=qf1, qpid.paging=True, qpid.max_pages_loaded=500");
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.paging"] = True
+ queue_options["qpid.page_factor"] = 5
+ session.queue_declare(queue="qf2", arguments=queue_options)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request with name=qf2, qpid.paging=True, qpid.page_factor=5");
+ session = self.get_session('bob','bob')
+
+
+ #=====================================
+ # ACL file tests
+ #=====================================
+
+ def test_file_allow_mode(self):
+ """
+ Test cases for file acl in allow mode
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID access queue name=qf1\n')
+ aclf.write('acl deny bob@QPID create queue name=qf1 durable=true\n')
+ aclf.write('acl deny bob@QPID create queue name=qf2 exclusive=true policytype=ring\n')
+ aclf.write('acl deny bob@QPID access queue name=qf3\n')
+ aclf.write('acl deny bob@QPID purge queue name=qf3\n')
+ aclf.write('acl deny bob@QPID delete queue name=qf4\n')
+ aclf.write('acl deny bob@QPID create queue name=qf5 filemaxsizeupperlimit=1000 filemaxcountupperlimit=100\n')
+ aclf.write('acl deny bob@QPID create queue name=ABCDE queuemaxsizelowerlimit=900000 queuemaxsizeupperlimit=1024000 queuemaxcountlowerlimit=900 queuemaxcountupperlimit=2000 filemaxsizelowerlimit=0 filemaxsizeupperlimit=32 filemaxcountlowerlimit=0 filemaxcountupperlimit=4 policytype=ring durable=false autodelete=true\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('bob','bob')
+
+ self.Lookup("bob@QPID", "create", "queue", "ABCDE", {"durable":"false",
+ "autodelete":"true",
+ "exclusive":"false",
+ "alternate":"",
+ "policytype":"ring",
+ "maxqueuesize":"1024000",
+ "maxqueuecount":"1000",
+ "maxfilesize":"0",
+ "maxfilecount":"0" }, "deny")
+
+ try:
+ queue_options = {}
+ queue_options["qpid.file_count"] = 200
+ queue_options["qpid.file_size"] = 500
+ session.queue_declare(queue="qf5", exclusive=True, durable=True, arguments=queue_options)
+ self.fail("ACL should deny queue create request with name=qf5, qpid.file_size=500 and qpid.file_count=200");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.file_count"] = 200
+ queue_options["qpid.file_size"] = 100
+ session.queue_declare(queue="qf2", exclusive=True, arguments=queue_options)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request with name=qf2, qpid.file_size=100 and qpid.file_count=200 ");
+
+
+ def test_file_deny_mode(self):
+ """
+ Test cases for queue acl in deny mode
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl allow bob@QPID access queue name=qfd1\n')
+ aclf.write('acl allow bob@QPID create queue name=qfd1 durable=true\n')
+ aclf.write('acl allow bob@QPID create queue name=qfd2 exclusive=true policytype=ring\n')
+ aclf.write('acl allow bob@QPID access queue name=qfd3\n')
+ aclf.write('acl allow bob@QPID purge queue name=qfd3\n')
+ aclf.write('acl allow bob@QPID create queue name=qfd3\n')
+ aclf.write('acl allow bob@QPID create queue name=qfd4\n')
+ aclf.write('acl allow bob@QPID delete queue name=qfd4\n')
+ aclf.write('acl allow bob@QPID create queue name=qfd5 filemaxsizeupperlimit=1000 filemaxcountupperlimit=100\n')
+ aclf.write('acl allow bob@QPID create queue name=qfd6 filemaxsizelowerlimit=50 filemaxsizeupperlimit=100 filemaxcountlowerlimit=50 filemaxcountupperlimit=100\n')
+ aclf.write('acl allow bob@QPID create queue name=ABCDE queuemaxsizelowerlimit=900000 queuemaxsizeupperlimit=1024000 queuemaxcountlowerlimit=900 queuemaxcountupperlimit=2000 filemaxsizelowerlimit=0 filemaxsizeupperlimit=32 filemaxcountlowerlimit=0 filemaxcountupperlimit=4 policytype=ring durable=false autodelete=true\n')
+ aclf.write('acl allow bob@QPID create queue name=FGHIJ queuemaxsizelowerlimit=900000 queuemaxsizeupperlimit=1024000 queuemaxcountlowerlimit=900 queuemaxcountupperlimit=2000 filemaxsizelowerlimit=2 filemaxsizeupperlimit=32 filemaxcountlowerlimit=0 filemaxcountupperlimit=4 policytype=ring durable=false autodelete=true\n')
+ aclf.write('acl allow bob@QPID create queue name=KLMNO queuemaxsizelowerlimit=900000 queuemaxsizeupperlimit=1024000 queuemaxcountlowerlimit=900 queuemaxcountupperlimit=2000 filemaxsizelowerlimit=0 filemaxsizeupperlimit=0 filemaxcountlowerlimit=0 filemaxcountupperlimit=4 policytype=ring durable=false autodelete=true\n')
+ aclf.write('acl allow anonymous all all\n')
+ aclf.write('acl deny all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('bob','bob')
+
+ self.Lookup("bob@QPID", "create", "queue", "ABCDE", {"durable":"false",
+ "autodelete":"true",
+ "exclusive":"false",
+ "alternate":"",
+ "policytype":"ring",
+ "maxqueuesize":"1024000",
+ "maxqueuecount":"1000",
+ "maxfilesize":"0",
+ "maxfilecount":"0" }, "allow")
+
+ self.Lookup("bob@QPID", "create", "queue", "FGHIJ", {"durable":"false",
+ "autodelete":"true",
+ "exclusive":"false",
+ "alternate":"",
+ "policytype":"ring",
+ "maxqueuesize":"1024000",
+ "maxqueuecount":"1000",
+ "maxfilesize":"1",
+ "maxfilecount":"0" }, "deny")
+
+ self.Lookup("bob@QPID", "create", "queue", "FGHIJ", {"durable":"false",
+ "autodelete":"true",
+ "exclusive":"false",
+ "alternate":"",
+ "policytype":"ring",
+ "maxqueuesize":"1024000",
+ "maxqueuecount":"1000",
+ "maxfilesize":"2",
+ "maxfilecount":"0" }, "allow")
+
+ self.Lookup("bob@QPID", "create", "queue", "FGHIJ", {"durable":"false",
+ "autodelete":"true",
+ "exclusive":"false",
+ "alternate":"",
+ "policytype":"ring",
+ "maxqueuesize":"1024000",
+ "maxqueuecount":"1000",
+ "maxfilesize":"32",
+ "maxfilecount":"0" }, "allow")
+
+ self.Lookup("bob@QPID", "create", "queue", "FGHIJ", {"durable":"false",
+ "autodelete":"true",
+ "exclusive":"false",
+ "alternate":"",
+ "policytype":"ring",
+ "maxqueuesize":"1024000",
+ "maxqueuecount":"1000",
+ "maxfilesize":"33",
+ "maxfilecount":"0" }, "deny")
+
+ self.Lookup("bob@QPID", "create", "queue", "KLMNO", {"durable":"false",
+ "autodelete":"true",
+ "exclusive":"false",
+ "alternate":"",
+ "policytype":"ring",
+ "maxqueuesize":"1024000",
+ "maxqueuecount":"1000",
+ "maxfilesize":"0",
+ "maxfilecount":"0" }, "allow")
+
+ self.Lookup("bob@QPID", "create", "queue", "KLMNO", {"durable":"false",
+ "autodelete":"true",
+ "exclusive":"false",
+ "alternate":"",
+ "policytype":"ring",
+ "maxqueuesize":"1024000",
+ "maxqueuecount":"1000",
+ "maxfilesize":"17",
+ "maxfilecount":"0" }, "allow")
+
+ self.Lookup("bob@QPID", "create", "queue", "KLMNO", {"durable":"false",
+ "autodelete":"true",
+ "exclusive":"false",
+ "alternate":"",
+ "policytype":"ring",
+ "maxqueuesize":"1024000",
+ "maxqueuecount":"1000",
+ "maxfilesize":"33",
+ "maxfilecount":"0" }, "allow")
+
+ try:
+ session.queue_declare(queue="qfd1", durable=True)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request with name=qfd1 durable=true");
+
+ try:
+ session.queue_declare(queue="qfd1", durable=True, passive=True)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue passive declare request with name=qfd1 durable=true passive=true");
+
+ try:
+ session.queue_declare(queue="qfd1", durable=False, passive=False)
+ self.fail("ACL should deny queue create request with name=qfd1 durable=true passive=false");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.queue_declare(queue="qfd2", exclusive=False)
+ self.fail("ACL should deny queue create request with name=qfd2 exclusive=false");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.file_count"] = 200
+ queue_options["qpid.file_size"] = 500
+ session.queue_declare(queue="qfd5", durable=True, arguments=queue_options)
+ self.fail("ACL should deny queue create request with name=qfd5 filemaxsizeupperlimit=500 filemaxcountupperlimit=200");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.file_count"] = 100
+ queue_options["qpid.file_size"] = 500
+ session.queue_declare(queue="qfd5", durable=True, arguments=queue_options)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request with name=qfd5 filemaxsizeupperlimit=500 filemaxcountupperlimit=200");
+
+ try:
+ queue_options = {}
+ queue_options["qpid.file_count"] = 49
+ queue_options["qpid.file_size"] = 100
+ session.queue_declare(queue="qfd6", durable=True, arguments=queue_options)
+ self.fail("ACL should deny queue create request with name=qfd6 filemaxsizeupperlimit=100 filemaxcountupperlimit=49");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.file_count"] = 101
+ queue_options["qpid.file_size"] = 100
+ session.queue_declare(queue="qfd6", durable=True, arguments=queue_options)
+ self.fail("ACL should allow queue create request with name=qfd6 filemaxsizeupperlimit=100 filemaxcountupperlimit=101");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.file_count"] = 100
+ queue_options["qpid.file_size"] = 49
+ session.queue_declare(queue="qfd6", durable=True, arguments=queue_options)
+ self.fail("ACL should deny queue create request with name=qfd6 filemaxsizeupperlimit=49 filemaxcountupperlimit=100");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.file_count"] = 100
+ queue_options["qpid.file_size"] =101
+ session.queue_declare(queue="qfd6", durable=True, arguments=queue_options)
+ self.fail("ACL should deny queue create request with name=qfd6 filemaxsizeupperlimit=101 filemaxcountupperlimit=100");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.file_count"] = 50
+ queue_options["qpid.file_size"] = 50
+ session.queue_declare(queue="qfd6", durable=True, arguments=queue_options)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request with name=qfd6 filemaxsizeupperlimit=50 filemaxcountupperlimit=50");
+
+
+ #=====================================
+ # ACL exchange tests
+ #=====================================
+
+ def test_exchange_acl_allow_mode(self):
+ session = self.get_session('bob','bob')
+ session.queue_declare(queue="baz")
+
+ """
+ Test cases for exchange acl in allow mode
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID access exchange name=testEx\n')
+ aclf.write('acl deny bob@QPID create exchange name=testEx durable=true\n')
+ aclf.write('acl deny bob@QPID create exchange name=ex1 type=direct\n')
+ aclf.write('acl deny bob@QPID access exchange name=myEx queuename=q1 routingkey=rk1.*\n')
+ aclf.write('acl deny bob@QPID bind exchange name=myEx queuename=q1 routingkey=rk1\n')
+ aclf.write('acl deny bob@QPID unbind exchange name=myEx queuename=q1 routingkey=rk1\n')
+ aclf.write('acl deny bob@QPID delete exchange name=myEx\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('bob','bob')
+ session.queue_declare(queue='q1')
+ session.queue_declare(queue='q2')
+ session.exchange_declare(exchange='myEx', type='direct')
+
+ try:
+ session.exchange_declare(exchange='testEx', durable=True)
+ self.fail("ACL should deny exchange create request with name=testEx durable=true");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.exchange_declare(exchange='testEx', durable=True, passive=True)
+ self.fail("ACL should deny passive exchange declare request with name=testEx durable=true passive=true");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.exchange_declare(exchange='testEx', type='direct', durable=False)
+ except qpid.session.SessionException, e:
+ print e
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow exchange create request for testEx with any parameter other than durable=true");
+
+ try:
+ session.exchange_declare(exchange='ex1', type='direct')
+ self.fail("ACL should deny exchange create request with name=ex1 type=direct");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.exchange_declare(exchange='myXml', type='direct')
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow exchange create request for myXml with any parameter");
+
+ try:
+ session.exchange_query(name='myEx')
+ self.fail("ACL should deny exchange query request for myEx");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.exchange_bound(exchange='myEx', queue='q1', binding_key='rk1.*')
+ self.fail("ACL should deny exchange bound request for myEx with queuename=q1 and routing_key='rk1.*' ");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.exchange_query(name='amq.topic')
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow exchange query request for exchange='amq.topic'");
+
+ try:
+ session.exchange_bound(exchange='myEx', queue='q1', binding_key='rk2.*')
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow exchange bound request for myEx with queuename=q1 and binding_key='rk2.*'");
+
+ try:
+ session.exchange_bind(exchange='myEx', queue='q1', binding_key='rk1')
+ self.fail("ACL should deny exchange bind request with exchange='myEx' queuename='q1' bindingkey='rk1'");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.exchange_bind(exchange='myEx', queue='q1', binding_key='x')
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow exchange bind request for exchange='myEx', queue='q1', binding_key='x'");
+
+ try:
+ session.exchange_bind(exchange='myEx', queue='q2', binding_key='rk1')
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow exchange bind request for exchange='myEx', queue='q2', binding_key='rk1'");
+
+ try:
+ session.exchange_unbind(exchange='myEx', queue='q1', binding_key='rk1')
+ self.fail("ACL should deny exchange unbind request with exchange='myEx' queuename='q1' bindingkey='rk1'");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.exchange_unbind(exchange='myEx', queue='q1', binding_key='x')
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow exchange unbind request for exchange='myEx', queue='q1', binding_key='x'");
+
+ try:
+ session.exchange_unbind(exchange='myEx', queue='q2', binding_key='rk1')
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow exchange unbind request for exchange='myEx', queue='q2', binding_key='rk1'");
+
+ try:
+ session.exchange_delete(exchange='myEx')
+ self.fail("ACL should deny exchange delete request for myEx");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.exchange_delete(exchange='myXml')
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow exchange delete request for myXml");
+
+
+ def test_exchange_acl_deny_mode(self):
+ session = self.get_session('bob','bob')
+ session.queue_declare(queue='bar')
+
+ """
+ Test cases for exchange acl in deny mode
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl allow bob@QPID create exchange name=myEx durable=true\n')
+ aclf.write('acl allow bob@QPID bind exchange name=amq.topic queuename=bar routingkey=foo.*\n')
+ aclf.write('acl allow bob@QPID unbind exchange name=amq.topic queuename=bar routingkey=foo.*\n')
+ aclf.write('acl allow bob@QPID access exchange name=myEx queuename=q1 routingkey=rk1.*\n')
+ aclf.write('acl allow bob@QPID delete exchange name=myEx\n')
+ aclf.write('acl allow anonymous all all\n')
+ aclf.write('acl deny all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('bob','bob')
+
+ try:
+ session.exchange_declare(exchange='myEx', type='direct', durable=True, passive=False)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow exchange create request for myEx with durable=true and passive=false");
+
+ try:
+ session.exchange_declare(exchange='myEx', type='direct', durable=False)
+ self.fail("ACL should deny exchange create request with name=myEx durable=false");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.exchange_bind(exchange='amq.topic', queue='bar', binding_key='foo.bar')
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow exchange bind request for exchange='amq.topic', queue='bar', binding_key='foor.bar'");
+
+ try:
+ session.exchange_bind(exchange='amq.topic', queue='baz', binding_key='foo.bar')
+ self.fail("ACL should deny exchange bind request for exchange='amq.topic', queue='baz', binding_key='foo.bar'");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.exchange_bind(exchange='amq.topic', queue='bar', binding_key='fooz.bar')
+ self.fail("ACL should deny exchange bind request for exchange='amq.topic', queue='bar', binding_key='fooz.bar'");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.exchange_unbind(exchange='amq.topic', queue='bar', binding_key='foo.bar')
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow exchange unbind request for exchange='amq.topic', queue='bar', binding_key='foor.bar'");
+ try:
+ session.exchange_unbind(exchange='amq.topic', queue='baz', binding_key='foo.bar')
+ self.fail("ACL should deny exchange unbind request for exchange='amq.topic', queue='baz', binding_key='foo.bar'");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.exchange_unbind(exchange='amq.topic', queue='bar', binding_key='fooz.bar')
+ self.fail("ACL should deny exchange unbind request for exchange='amq.topic', queue='bar', binding_key='fooz.bar'");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.exchange_query(name='amq.topic')
+ self.fail("ACL should deny exchange query request for amq.topic");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.exchange_bound(exchange='myEx', queue='q1', binding_key='rk2.*')
+ self.fail("ACL should deny exchange bound request for amq.topic with queuename=q1 and routing_key='rk2.*' ");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.exchange_query(name='myEx')
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow exchange query request for exchange='myEx'");
+
+ try:
+ session.exchange_bound(exchange='myEx', queue='q1', binding_key='rk1.*')
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow exchange bound request for myEx with queuename=q1 and binding_key='rk1.*'");
+
+ try:
+ session.exchange_delete(exchange='myEx')
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow exchange delete request for myEx");
+
+ def test_create_and_delete_exchange_via_qmf(self):
+ """
+ Test acl is enforced when creating/deleting via QMF
+ methods. Note that in order to be able to send the QMF methods
+ and receive the responses a significant amount of permissions
+ need to be enabled (TODO: can the set below be narrowed down
+ at all?)
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl allow bob@QPID create exchange\n')
+ aclf.write('acl allow admin@QPID delete exchange\n')
+ aclf.write('acl allow all access exchange\n')
+ aclf.write('acl allow all bind exchange\n')
+ aclf.write('acl allow all create queue\n')
+ aclf.write('acl allow all access queue\n')
+ aclf.write('acl allow all delete queue\n')
+ aclf.write('acl allow all consume queue\n')
+ aclf.write('acl allow all access method\n')
+ aclf.write('acl deny all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ bob = BrokerAdmin(self.config.broker, "bob", "bob")
+ bob.create_exchange("my-exchange") #should pass
+ #cleanup by deleting exchange
+ try:
+ bob.delete_exchange("my-exchange") #should fail
+ self.fail("ACL should deny exchange delete request for my-exchange");
+ except Exception, e:
+ self.assertEqual(7,e.args[0]["error_code"])
+ assert e.args[0]["error_text"].find("unauthorized-access") == 0
+ admin = BrokerAdmin(self.config.broker, "admin", "admin")
+ admin.delete_exchange("my-exchange") #should pass
+
+ anonymous = BrokerAdmin(self.config.broker)
+ try:
+ anonymous.create_exchange("another-exchange") #should fail
+ self.fail("ACL should deny exchange create request for another-exchange");
+ except Exception, e:
+ self.assertEqual(7,e.args[0]["error_code"])
+ assert e.args[0]["error_text"].find("unauthorized-access") == 0
+
+ def test_qmf_query(self):
+ aclf = self.get_acl_file()
+ aclf.write('acl allow all access exchange\n')
+ aclf.write('acl allow all bind exchange\n')
+ aclf.write('acl allow all create queue\n')
+ aclf.write('acl allow all access queue\n')
+ aclf.write('acl allow all delete queue\n')
+ aclf.write('acl allow all consume queue\n')
+ aclf.write('acl allow all access method\n')
+ aclf.write('acl allow bob@QPID access query name=org.apache.qpid.broker:queue:q1\n')
+ aclf.write('acl allow bob@QPID access query schemaclass=exchange\n')
+ aclf.write('acl deny all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ bob = BrokerAdmin(self.config.broker, "bob", "bob")
+
+ try:
+ bob.query(object_name="org.apache.qpid.broker:queue:q1")
+ except Exception, e:
+ if ("unauthorized-access:" in e.args[0]):
+ self.fail("ACL should allow queue QMF query for q1");
+
+ try:
+ bob.query(object_name="org.apache.qpid.broker:queue:q2")
+ self.fail("ACL should deny queue QMF query for q2");
+ except Exception, e:
+ self.assertTrue("unauthorized-access:" in e.args[0])
+ bob = BrokerAdmin(self.config.broker, "bob", "bob")
+
+ try:
+ bob.query(class_name="binding")
+ self.fail("ACL should deny class binding QMF query");
+ except Exception, e:
+ self.assertTrue("unauthorized-access:" in e.args[0])
+ bob = BrokerAdmin(self.config.broker, "bob", "bob")
+
+ try:
+ bob.query(class_name="exchange")
+ except Exception, e:
+ if ("unauthorized-access:" in e.args[0]):
+ self.fail("ACL should allow class exchange QMF query");
+
+
+ #=====================================
+ # ACL consume tests
+ #=====================================
+
+ def test_consume_allow_mode(self):
+ """
+ Test cases for consume in allow mode
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID consume queue name=q1\n')
+ aclf.write('acl deny bob@QPID consume queue name=q2\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('bob','bob')
+
+
+ try:
+ session.queue_declare(queue='q1')
+ session.queue_declare(queue='q2')
+ session.queue_declare(queue='q3')
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow create queue request");
+
+ try:
+ session.message_subscribe(queue='q1', destination='myq1')
+ self.fail("ACL should deny subscription for queue='q1'");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.message_subscribe(queue='q2', destination='myq1')
+ self.fail("ACL should deny subscription for queue='q2'");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.message_subscribe(queue='q3', destination='myq3')
+ session.message_cancel(destination='myq3')
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow subscription for q3");
+
+
+ def test_consume_deny_mode(self):
+ """
+ Test cases for consume in allow mode
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl allow bob@QPID consume queue name=q1\n')
+ aclf.write('acl allow bob@QPID consume queue name=q2\n')
+ aclf.write('acl allow bob@QPID create queue\n')
+ aclf.write('acl allow anonymous all\n')
+ aclf.write('acl deny all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('bob','bob')
+
+
+ try:
+ session.queue_declare(queue='q1')
+ session.queue_declare(queue='q2')
+ session.queue_declare(queue='q3')
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow create queue request");
+
+ try:
+ session.message_subscribe(queue='q1', destination='myq1')
+ session.message_subscribe(queue='q2', destination='myq2')
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow subscription for q1 and q2");
+
+ try:
+ session.message_subscribe(queue='q3', destination='myq3')
+ self.fail("ACL should deny subscription for queue='q3'");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+
+ #=====================================
+ # ACL publish tests
+ #=====================================
+
+ def test_publish_acl_allow_mode(self):
+ """
+ Test various publish acl
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl deny bob@QPID publish exchange name=amq.direct routingkey=rk1\n')
+ aclf.write('acl deny bob@QPID publish exchange name=amq.topic\n')
+ aclf.write('acl deny bob@QPID publish exchange name=myEx routingkey=rk2\n')
+ aclf.write("acl deny bob@QPID publish exchange name=amq.default routingkey=restricted\n")
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('bob','bob')
+
+ props = session.delivery_properties(routing_key="rk1")
+
+ try:
+ session.message_transfer(destination="amq.direct", message=Message(props,"Test"))
+ self.fail("ACL should deny message transfer to name=amq.direct routingkey=rk1");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.message_transfer(destination="amq.topic", message=Message(props,"Test"))
+ self.fail("ACL should deny message transfer to name=amq.topic");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.exchange_declare(exchange='myEx', type='direct', durable=False)
+ session.message_transfer(destination="myEx", message=Message(props,"Test"))
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow message transfer to exchange myEx with routing key rk1");
+
+
+ props = session.delivery_properties(routing_key="rk2")
+ try:
+ session.message_transfer(destination="amq.direct", message=Message(props,"Test"))
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow message transfer to exchange amq.direct with routing key rk2");
+
+ self.LookupPublish("bob@QPID", "", "restricted", "deny")
+ self.LookupPublish("bob@QPID", "", "another", "allow")
+ self.LookupPublish("joe@QPID", "", "restricted", "allow")
+
+
+ def test_publish_acl_deny_mode(self):
+ """
+ Test various publish acl
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl allow bob@QPID publish exchange name=amq.direct routingkey=rk1\n')
+ aclf.write('acl allow bob@QPID publish exchange name=amq.topic\n')
+ aclf.write('acl allow bob@QPID publish exchange name=myEx routingkey=rk2\n')
+ aclf.write('acl allow bob@QPID create exchange\n')
+ aclf.write("acl allow bob@QPID publish exchange name=amq.default routingkey=unrestricted\n")
+ aclf.write('acl allow anonymous all all \n')
+ aclf.write('acl deny all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('bob','bob')
+
+ props = session.delivery_properties(routing_key="rk2")
+
+ try:
+ session.message_transfer(destination="amq.direct", message=Message(props,"Test"))
+ self.fail("ACL should deny message transfer to name=amq.direct routingkey=rk2");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.message_transfer(destination="amq.topic", message=Message(props,"Test"))
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow message transfer to exchange amq.topic with any routing key");
+
+ try:
+ session.exchange_declare(exchange='myEx', type='direct', durable=False)
+ session.message_transfer(destination="myEx", message=Message(props,"Test"))
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow message transfer to exchange myEx with routing key=rk2");
+
+ props = session.delivery_properties(routing_key="rk1")
+
+ try:
+ session.message_transfer(destination="myEx", message=Message(props,"Test"))
+ self.fail("ACL should deny message transfer to name=myEx routingkey=rk1");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.message_transfer(destination="amq.direct", message=Message(props,"Test"))
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow message transfer to exchange amq.direct with routing key rk1");
+
+ self.LookupPublish("bob@QPID", "", "unrestricted", "allow")
+ self.LookupPublish("bob@QPID", "", "another", "deny")
+ self.LookupPublish("joe@QPID", "", "unrestricted", "deny")
+
+
+ #=====================================
+ # ACL broker configuration tests
+ #=====================================
+
+ def test_broker_timestamp_config(self):
+ """
+ Test ACL control of the broker timestamp configuration
+ """
+ aclf = self.get_acl_file()
+ # enable lots of stuff to allow QMF to work
+ aclf.write('acl allow all create exchange\n')
+ aclf.write('acl allow all access exchange\n')
+ aclf.write('acl allow all bind exchange\n')
+ aclf.write('acl allow all publish exchange\n')
+ aclf.write('acl allow all create queue\n')
+ aclf.write('acl allow all access queue\n')
+ aclf.write('acl allow all delete queue\n')
+ aclf.write('acl allow all consume queue\n')
+ aclf.write('acl allow all access method\n')
+ # this should let bob access the timestamp configuration
+ aclf.write('acl allow bob@QPID access broker\n')
+ aclf.write('acl allow bob@QPID update broker\n')
+ aclf.write('acl allow admin@QPID all all\n')
+ aclf.write('acl deny all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ ts = None
+ bob = BrokerAdmin(self.config.broker, "bob", "bob")
+ ts = bob.get_timestamp_cfg() #should work
+ bob.set_timestamp_cfg(ts); #should work
+
+ obo = BrokerAdmin(self.config.broker, "obo", "obo")
+ try:
+ ts = obo.get_timestamp_cfg() #should fail
+ failed = False
+ except Exception, e:
+ failed = True
+ self.assertEqual(7,e.args[0]["error_code"])
+ assert e.args[0]["error_text"].find("unauthorized-access") == 0
+ assert(failed)
+
+ try:
+ obo.set_timestamp_cfg(ts) #should fail
+ failed = False
+ except Exception, e:
+ failed = True
+ self.assertEqual(7,e.args[0]["error_code"])
+ assert e.args[0]["error_text"].find("unauthorized-access") == 0
+ assert(failed)
+
+ admin = BrokerAdmin(self.config.broker, "admin", "admin")
+ ts = admin.get_timestamp_cfg() #should pass
+ admin.set_timestamp_cfg(ts) #should pass
+
+
+
+ #=====================================
+ # QMF Functional tests
+ #=====================================
+
+ def test_qmf_functional_tests(self):
+ """
+ Test using QMF method hooks into ACL logic
+ """
+ aclf = self.get_acl_file()
+ aclf.write('group admins moe@COMPANY.COM \\\n')
+ aclf.write(' larry@COMPANY.COM \\\n')
+ aclf.write(' curly@COMPANY.COM \\\n')
+ aclf.write(' shemp@COMPANY.COM\n')
+ aclf.write('group auditors aaudit@COMPANY.COM baudit@COMPANY.COM caudit@COMPANY.COM \\\n')
+ aclf.write(' daudit@COMPANY.COM eaduit@COMPANY.COM eaudit@COMPANY.COM\n')
+ aclf.write('group tatunghosts tatung01@COMPANY.COM \\\n')
+ aclf.write(' tatung02/x86.build.company.com@COMPANY.COM \\\n')
+ aclf.write(' tatung03/x86.build.company.com@COMPANY.COM \\\n')
+ aclf.write(' tatung04/x86.build.company.com@COMPANY.COM \n')
+ aclf.write('group publishusers publish@COMPANY.COM x-pubs@COMPANY.COM\n')
+ aclf.write('acl allow-log admins all all\n')
+ aclf.write('# begin hack alert: allow anonymous to access the lookup debug functions\n')
+ aclf.write('acl allow-log anonymous create queue\n')
+ aclf.write('acl allow-log anonymous all exchange name=qmf.*\n')
+ aclf.write('acl allow-log anonymous all exchange name=amq.direct\n')
+ aclf.write('acl allow-log anonymous all exchange name=qpid.management\n')
+ aclf.write('acl allow-log anonymous access method name=*\n')
+ aclf.write('# end hack alert\n')
+ aclf.write('acl allow-log auditors all exchange name=company.topic routingkey=private.audit.*\n')
+ aclf.write('acl allow-log tatunghosts publish exchange name=company.topic routingkey=tatung.*\n')
+ aclf.write('acl allow-log tatunghosts publish exchange name=company.direct routingkey=tatung-service-queue\n')
+ aclf.write('acl allow-log publishusers create queue\n')
+ aclf.write('acl allow-log publishusers publish exchange name=qpid.management routingkey=broker\n')
+ aclf.write('acl allow-log publishusers publish exchange name=qmf.default.topic routingkey=*\n')
+ aclf.write('acl allow-log publishusers publish exchange name=qmf.default.direct routingkey=*\n')
+ aclf.write('acl allow-log all bind exchange name=company.topic routingkey=tatung.*\n')
+ aclf.write('acl allow-log all bind exchange name=company.direct routingkey=tatung-service-queue\n')
+ aclf.write('acl allow-log all consume queue\n')
+ aclf.write('acl allow-log all access exchange\n')
+ aclf.write('acl allow-log all access queue\n')
+ aclf.write('acl allow-log all create queue name=tmp.* durable=false autodelete=true exclusive=true policytype=ring\n')
+ aclf.write('acl allow mrQ create queue queuemaxsizelowerlimit=100 queuemaxsizeupperlimit=200 queuemaxcountlowerlimit=300 queuemaxcountupperlimit=400\n')
+ aclf.write('acl deny-log all all\n')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ #
+ # define some group lists
+ #
+ g_admins = ['moe@COMPANY.COM', \
+ 'larry@COMPANY.COM', \
+ 'curly@COMPANY.COM', \
+ 'shemp@COMPANY.COM']
+
+ g_auditors = [ 'aaudit@COMPANY.COM','baudit@COMPANY.COM','caudit@COMPANY.COM', \
+ 'daudit@COMPANY.COM','eaduit@COMPANY.COM','eaudit@COMPANY.COM']
+
+ g_tatunghosts = ['tatung01@COMPANY.COM', \
+ 'tatung02/x86.build.company.com@COMPANY.COM', \
+ 'tatung03/x86.build.company.com@COMPANY.COM', \
+ 'tatung04/x86.build.company.com@COMPANY.COM']
+
+ g_publishusers = ['publish@COMPANY.COM', 'x-pubs@COMPANY.COM']
+
+ g_public = ['jpublic@COMPANY.COM', 'me@yahoo.com']
+
+ g_all = g_admins + g_auditors + g_tatunghosts + g_publishusers + g_public
+
+ action_all = ['consume','publish','create','access','bind','unbind','delete','purge','update']
+
+ #
+ # Run some tests verifying against users who are in and who are out of given groups.
+ #
+
+ for u in g_admins:
+ self.Lookup(u, "create", "queue", "anything", {"durable":"true"}, "allow-log")
+ uInTest = g_auditors + g_admins
+ uOutTest = self.AllBut(g_all, uInTest)
+
+ for u in uInTest:
+ self.LookupPublish(u, "company.topic", "private.audit.This", "allow-log")
+
+ for u in uInTest:
+ for a in ['bind', 'unbind', 'access', 'publish']:
+ self.Lookup(u, a, "exchange", "company.topic", {"routingkey":"private.audit.This"}, "allow-log")
+
+ for u in uOutTest:
+ self.LookupPublish(u, "company.topic", "private.audit.This", "deny-log")
+ self.Lookup(u, "bind", "exchange", "company.topic", {"routingkey":"private.audit.This"}, "deny-log")
+
+ uInTest = g_admins + g_tatunghosts
+ uOutTest = self.AllBut(g_all, uInTest)
+
+ for u in uInTest:
+ self.LookupPublish(u, "company.topic", "tatung.this2", "allow-log")
+ self.LookupPublish(u, "company.direct", "tatung-service-queue", "allow-log")
+
+ for u in uOutTest:
+ self.LookupPublish(u, "company.topic", "tatung.this2", "deny-log")
+ self.LookupPublish(u, "company.direct", "tatung-service-queue", "deny-log")
+
+ for u in uOutTest:
+ for a in ["bind", "access"]:
+ self.Lookup(u, a, "exchange", "company.topic", {"routingkey":"tatung.this2"}, "allow-log")
+ self.Lookup(u, a, "exchange", "company.direct", {"routingkey":"tatung-service-queue"}, "allow-log")
+
+ uInTest = g_admins + g_publishusers
+ uOutTest = self.AllBut(g_all, uInTest)
+
+ for u in uInTest:
+ self.LookupPublish(u, "qpid.management", "broker", "allow-log")
+ self.LookupPublish(u, "qmf.default.topic", "this3", "allow-log")
+ self.LookupPublish(u, "qmf.default.direct", "this4", "allow-log")
+
+ for u in uOutTest:
+ self.LookupPublish(u, "qpid.management", "broker", "deny-log")
+ self.LookupPublish(u, "qmf.default.topic", "this3", "deny-log")
+ self.LookupPublish(u, "qmf.default.direct", "this4", "deny-log")
+
+ for u in uOutTest:
+ for a in ["bind"]:
+ self.Lookup(u, a, "exchange", "qpid.management", {"routingkey":"broker"}, "deny-log")
+ self.Lookup(u, a, "exchange", "qmf.default.topic", {"routingkey":"this3"}, "deny-log")
+ self.Lookup(u, a, "exchange", "qmf.default.direct", {"routingkey":"this4"}, "deny-log")
+ for a in ["access"]:
+ self.Lookup(u, a, "exchange", "qpid.management", {"routingkey":"broker"}, "allow-log")
+ self.Lookup(u, a, "exchange", "qmf.default.topic", {"routingkey":"this3"}, "allow-log")
+ self.Lookup(u, a, "exchange", "qmf.default.direct", {"routingkey":"this4"}, "allow-log")
+
+ # Test against queue size limits
+
+ self.Lookup('mrQ', 'create', 'queue', 'abc', {"maxqueuesize":"150", "maxqueuecount":"350"}, "allow")
+ self.Lookup('mrQ', 'create', 'queue', 'def', {"maxqueuesize":"99", "maxqueuecount":"350"}, "deny")
+ self.Lookup('mrQ', 'create', 'queue', 'uvw', {"maxqueuesize":"201", "maxqueuecount":"350"}, "deny")
+ self.Lookup('mrQ', 'create', 'queue', 'xyz', {"maxqueuesize":"150", "maxqueuecount":"299"}, "deny")
+ self.Lookup('mrQ', 'create', 'queue', '', {"maxqueuesize":"150", "maxqueuecount":"401"}, "deny")
+ self.Lookup('mrQ', 'create', 'queue', '', {"maxqueuesize":"0", "maxqueuecount":"401"}, "deny")
+ self.Lookup('mrQ', 'create', 'queue', '', {"maxqueuesize":"150", "maxqueuecount":"0" }, "deny")
+
+
+ #=====================================
+ # Routingkey lookup using Topic Exchange tests
+ #=====================================
+
+ def test_topic_exchange_publish_tests(self):
+ """
+ Test using QMF method hooks into ACL logic
+ """
+ aclf = self.get_acl_file()
+ aclf.write('# begin hack alert: allow anonymous to access the lookup debug functions\n')
+ aclf.write('acl allow-log anonymous create queue\n')
+ aclf.write('acl allow-log anonymous all exchange name=qmf.*\n')
+ aclf.write('acl allow-log anonymous all exchange name=amq.direct\n')
+ aclf.write('acl allow-log anonymous all exchange name=qpid.management\n')
+ aclf.write('acl allow-log anonymous access method name=*\n')
+ aclf.write('# end hack alert\n')
+ aclf.write('acl allow-log uPlain1@COMPANY publish exchange name=X routingkey=ab.cd.e\n')
+ aclf.write('acl allow-log uPlain2@COMPANY publish exchange name=X routingkey=.\n')
+ aclf.write('acl allow-log uStar1@COMPANY publish exchange name=X routingkey=a.*.b\n')
+ aclf.write('acl allow-log uStar2@COMPANY publish exchange name=X routingkey=*.x\n')
+ aclf.write('acl allow-log uStar3@COMPANY publish exchange name=X routingkey=x.x.*\n')
+ aclf.write('acl allow-log uHash1@COMPANY publish exchange name=X routingkey=a.#.b\n')
+ aclf.write('acl allow-log uHash2@COMPANY publish exchange name=X routingkey=a.#\n')
+ aclf.write('acl allow-log uHash3@COMPANY publish exchange name=X routingkey=#.a\n')
+ aclf.write('acl allow-log uHash4@COMPANY publish exchange name=X routingkey=a.#.b.#.c\n')
+ aclf.write('acl allow-log uMixed1@COMPANY publish exchange name=X routingkey=*.x.#.y\n')
+ aclf.write('acl allow-log uMixed2@COMPANY publish exchange name=X routingkey=a.#.b.*\n')
+ aclf.write('acl allow-log uMixed3@COMPANY publish exchange name=X routingkey=*.*.*.#\n')
+
+ aclf.write('acl allow-log all publish exchange name=X routingkey=MN.OP.Q\n')
+ aclf.write('acl allow-log all publish exchange name=X routingkey=M.*.N\n')
+ aclf.write('acl allow-log all publish exchange name=X routingkey=M.#.N\n')
+ aclf.write('acl allow-log all publish exchange name=X routingkey=*.M.#.N\n')
+
+ aclf.write('acl deny-log all all\n')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ # aclKey: "ab.cd.e"
+ self.LookupPublish("uPlain1@COMPANY", "X", "ab.cd.e", "allow-log")
+ self.LookupPublish("uPlain1@COMPANY", "X", "abx.cd.e", "deny-log")
+ self.LookupPublish("uPlain1@COMPANY", "X", "ab.cd", "deny-log")
+ self.LookupPublish("uPlain1@COMPANY", "X", "ab.cd..e.", "deny-log")
+ self.LookupPublish("uPlain1@COMPANY", "X", "ab.cd.e.", "deny-log")
+ self.LookupPublish("uPlain1@COMPANY", "X", ".ab.cd.e", "deny-log")
+ # aclKey: "."
+ self.LookupPublish("uPlain2@COMPANY", "X", ".", "allow-log")
+
+ # aclKey: "a.*.b"
+ self.LookupPublish("uStar1@COMPANY", "X", "a.xx.b", "allow-log")
+ self.LookupPublish("uStar1@COMPANY", "X", "a.b", "deny-log")
+ # aclKey: "*.x"
+ self.LookupPublish("uStar2@COMPANY", "X", "y.x", "allow-log")
+ self.LookupPublish("uStar2@COMPANY", "X", ".x", "allow-log")
+ self.LookupPublish("uStar2@COMPANY", "X", "x", "deny-log")
+ # aclKey: "x.x.*"
+ self.LookupPublish("uStar3@COMPANY", "X", "x.x.y", "allow-log")
+ self.LookupPublish("uStar3@COMPANY", "X", "x.x.", "allow-log")
+ self.LookupPublish("uStar3@COMPANY", "X", "x.x", "deny-log")
+ self.LookupPublish("uStar3@COMPANY", "X", "q.x.y", "deny-log")
+
+ # aclKey: "a.#.b"
+ self.LookupPublish("uHash1@COMPANY", "X", "a.b", "allow-log")
+ self.LookupPublish("uHash1@COMPANY", "X", "a.x.b", "allow-log")
+ self.LookupPublish("uHash1@COMPANY", "X", "a..x.y.zz.b", "allow-log")
+ self.LookupPublish("uHash1@COMPANY", "X", "a.b.", "deny-log")
+ self.LookupPublish("uHash1@COMPANY", "X", "q.x.b", "deny-log")
+
+ # aclKey: "a.#"
+ self.LookupPublish("uHash2@COMPANY", "X", "a", "allow-log")
+ self.LookupPublish("uHash2@COMPANY", "X", "a.b", "allow-log")
+ self.LookupPublish("uHash2@COMPANY", "X", "a.b.c", "allow-log")
+
+ # aclKey: "#.a"
+ self.LookupPublish("uHash3@COMPANY", "X", "a", "allow-log")
+ self.LookupPublish("uHash3@COMPANY", "X", "x.y.a", "allow-log")
+
+ # aclKey: "a.#.b.#.c"
+ self.LookupPublish("uHash4@COMPANY", "X", "a.b.c", "allow-log")
+ self.LookupPublish("uHash4@COMPANY", "X", "a.x.b.y.c", "allow-log")
+ self.LookupPublish("uHash4@COMPANY", "X", "a.x.x.b.y.y.c", "allow-log")
+
+ # aclKey: "*.x.#.y"
+ self.LookupPublish("uMixed1@COMPANY", "X", "a.x.y", "allow-log")
+ self.LookupPublish("uMixed1@COMPANY", "X", "a.x.p.qq.y", "allow-log")
+ self.LookupPublish("uMixed1@COMPANY", "X", "a.a.x.y", "deny-log")
+ self.LookupPublish("uMixed1@COMPANY", "X", "aa.x.b.c", "deny-log")
+
+ # aclKey: "a.#.b.*"
+ self.LookupPublish("uMixed2@COMPANY", "X", "a.b.x", "allow-log")
+ self.LookupPublish("uMixed2@COMPANY", "X", "a.x.x.x.b.x", "allow-log")
+
+ # aclKey: "*.*.*.#"
+ self.LookupPublish("uMixed3@COMPANY", "X", "x.y.z", "allow-log")
+ self.LookupPublish("uMixed3@COMPANY", "X", "x.y.z.a.b.c", "allow-log")
+ self.LookupPublish("uMixed3@COMPANY", "X", "x.y", "deny-log")
+ self.LookupPublish("uMixed3@COMPANY", "X", "x", "deny-log")
+
+ # Repeat the keys with wildcard user spec
+ self.LookupPublish("uPlain1@COMPANY", "X", "MN.OP.Q", "allow-log")
+ self.LookupPublish("uStar1@COMPANY" , "X", "M.xx.N", "allow-log")
+ self.LookupPublish("uHash1@COMPANY" , "X", "M.N", "allow-log")
+ self.LookupPublish("uHash1@COMPANY" , "X", "M.x.N", "allow-log")
+ self.LookupPublish("uHash1@COMPANY" , "X", "M..x.y.zz.N", "allow-log")
+ self.LookupPublish("uMixed1@COMPANY", "X", "a.M.N", "allow-log")
+ self.LookupPublish("uMixed1@COMPANY", "X", "a.M.p.qq.N", "allow-log")
+
+ self.LookupPublish("dev@QPID", "X", "MN.OP.Q", "allow-log")
+ self.LookupPublish("dev@QPID", "X", "M.xx.N", "allow-log")
+ self.LookupPublish("dev@QPID", "X", "M.N", "allow-log")
+ self.LookupPublish("dev@QPID", "X", "M.x.N", "allow-log")
+ self.LookupPublish("dev@QPID", "X", "M..x.y.zz.N", "allow-log")
+ self.LookupPublish("dev@QPID", "X", "a.M.N", "allow-log")
+ self.LookupPublish("dev@QPID", "X", "a.M.p.qq.N", "allow-log")
+
+ def test_topic_exchange_other_tests(self):
+ """
+ Test using QMF method hooks into ACL logic
+ """
+ action_list = ['access','bind','unbind']
+
+ aclf = self.get_acl_file()
+ aclf.write('# begin hack alert: allow anonymous to access the lookup debug functions\n')
+ aclf.write('acl allow-log anonymous create queue\n')
+ aclf.write('acl allow-log anonymous all exchange name=qmf.*\n')
+ aclf.write('acl allow-log anonymous all exchange name=amq.direct\n')
+ aclf.write('acl allow-log anonymous all exchange name=qpid.management\n')
+ aclf.write('acl allow-log anonymous access method name=*\n')
+ aclf.write('# end hack alert\n')
+ for action in action_list:
+ aclf.write('acl allow-log uPlain1@COMPANY ' + action + ' exchange name=X routingkey=ab.cd.e\n')
+ aclf.write('acl allow-log uPlain2@COMPANY ' + action + ' exchange name=X routingkey=.\n')
+ aclf.write('acl allow-log uStar1@COMPANY ' + action + ' exchange name=X routingkey=a.*.b\n')
+ aclf.write('acl allow-log uStar2@COMPANY ' + action + ' exchange name=X routingkey=*.x\n')
+ aclf.write('acl allow-log uStar3@COMPANY ' + action + ' exchange name=X routingkey=x.x.*\n')
+ aclf.write('acl allow-log uHash1@COMPANY ' + action + ' exchange name=X routingkey=a.#.b\n')
+ aclf.write('acl allow-log uHash2@COMPANY ' + action + ' exchange name=X routingkey=a.#\n')
+ aclf.write('acl allow-log uHash3@COMPANY ' + action + ' exchange name=X routingkey=#.a\n')
+ aclf.write('acl allow-log uHash4@COMPANY ' + action + ' exchange name=X routingkey=a.#.b.#.c\n')
+ aclf.write('acl allow-log uMixed1@COMPANY ' + action + ' exchange name=X routingkey=*.x.#.y\n')
+ aclf.write('acl allow-log uMixed2@COMPANY ' + action + ' exchange name=X routingkey=a.#.b.*\n')
+ aclf.write('acl allow-log uMixed3@COMPANY ' + action + ' exchange name=X routingkey=*.*.*.#\n')
+
+ aclf.write('acl allow-log all ' + action + ' exchange name=X routingkey=MN.OP.Q\n')
+ aclf.write('acl allow-log all ' + action + ' exchange name=X routingkey=M.*.N\n')
+ aclf.write('acl allow-log all ' + action + ' exchange name=X routingkey=M.#.N\n')
+ aclf.write('acl allow-log all ' + action + ' exchange name=X routingkey=*.M.#.N\n')
+
+ aclf.write('acl deny-log all all\n')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ for action in action_list:
+ # aclKey: "ab.cd.e"
+ self.Lookup("uPlain1@COMPANY", action, "exchange", "X", {"routingkey":"ab.cd.e"}, "allow-log")
+ self.Lookup("uPlain1@COMPANY", action, "exchange", "X", {"routingkey":"ab.cd.e"}, "allow-log")
+
+ self.Lookup("uPlain1@COMPANY", action, "exchange", "X", {"routingkey":"ab.cd.e"}, "allow-log")
+ self.Lookup("uPlain1@COMPANY", action, "exchange", "X", {"routingkey":"abx.cd.e"}, "deny-log")
+ self.Lookup("uPlain1@COMPANY", action, "exchange", "X", {"routingkey":"ab.cd"}, "deny-log")
+ self.Lookup("uPlain1@COMPANY", action, "exchange", "X", {"routingkey":"ab.cd..e."}, "deny-log")
+ self.Lookup("uPlain1@COMPANY", action, "exchange", "X", {"routingkey":"ab.cd.e."}, "deny-log")
+ self.Lookup("uPlain1@COMPANY", action, "exchange", "X", {"routingkey":".ab.cd.e"}, "deny-log")
+ # aclKey: "."
+ self.Lookup("uPlain2@COMPANY", action, "exchange", "X", {"routingkey":"."}, "allow-log")
+
+ # aclKey: "a.*.b"
+ self.Lookup("uStar1@COMPANY", action, "exchange", "X", {"routingkey":"a.xx.b"}, "allow-log")
+ self.Lookup("uStar1@COMPANY", action, "exchange", "X", {"routingkey":"a.b"}, "deny-log")
+ # aclKey: "*.x"
+ self.Lookup("uStar2@COMPANY", action, "exchange", "X", {"routingkey":"y.x"}, "allow-log")
+ self.Lookup("uStar2@COMPANY", action, "exchange", "X", {"routingkey":".x"}, "allow-log")
+ self.Lookup("uStar2@COMPANY", action, "exchange", "X", {"routingkey":"x"}, "deny-log")
+ # aclKey: "x.x.*"
+ self.Lookup("uStar3@COMPANY", action, "exchange", "X", {"routingkey":"x.x.y"}, "allow-log")
+ self.Lookup("uStar3@COMPANY", action, "exchange", "X", {"routingkey":"x.x."}, "allow-log")
+ self.Lookup("uStar3@COMPANY", action, "exchange", "X", {"routingkey":"x.x"}, "deny-log")
+ self.Lookup("uStar3@COMPANY", action, "exchange", "X", {"routingkey":"q.x.y"}, "deny-log")
+
+ # aclKey: "a.#.b"
+ self.Lookup("uHash1@COMPANY", action, "exchange", "X", {"routingkey":"a.b"}, "allow-log")
+ self.Lookup("uHash1@COMPANY", action, "exchange", "X", {"routingkey":"a.x.b"}, "allow-log")
+ self.Lookup("uHash1@COMPANY", action, "exchange", "X", {"routingkey":"a..x.y.zz.b"}, "allow-log")
+ self.Lookup("uHash1@COMPANY", action, "exchange", "X", {"routingkey":"a.b."}, "deny-log")
+ self.Lookup("uHash1@COMPANY", action, "exchange", "X", {"routingkey":"q.x.b"}, "deny-log")
+
+ # aclKey: "a.#"
+ self.Lookup("uHash2@COMPANY", action, "exchange", "X", {"routingkey":"a"}, "allow-log")
+ self.Lookup("uHash2@COMPANY", action, "exchange", "X", {"routingkey":"a.b"}, "allow-log")
+ self.Lookup("uHash2@COMPANY", action, "exchange", "X", {"routingkey":"a.b.c"}, "allow-log")
+
+ # aclKey: "#.a"
+ self.Lookup("uHash3@COMPANY", action, "exchange", "X", {"routingkey":"a"}, "allow-log")
+ self.Lookup("uHash3@COMPANY", action, "exchange", "X", {"routingkey":"x.y.a"}, "allow-log")
+
+ # aclKey: "a.#.b.#.c"
+ self.Lookup("uHash4@COMPANY", action, "exchange", "X", {"routingkey":"a.b.c"}, "allow-log")
+ self.Lookup("uHash4@COMPANY", action, "exchange", "X", {"routingkey":"a.x.b.y.c"}, "allow-log")
+ self.Lookup("uHash4@COMPANY", action, "exchange", "X", {"routingkey":"a.x.x.b.y.y.c"}, "allow-log")
+
+ # aclKey: "*.x.#.y"
+ self.Lookup("uMixed1@COMPANY", action, "exchange", "X", {"routingkey":"a.x.y"}, "allow-log")
+ self.Lookup("uMixed1@COMPANY", action, "exchange", "X", {"routingkey":"a.x.p.qq.y"}, "allow-log")
+ self.Lookup("uMixed1@COMPANY", action, "exchange", "X", {"routingkey":"a.a.x.y"}, "deny-log")
+ self.Lookup("uMixed1@COMPANY", action, "exchange", "X", {"routingkey":"aa.x.b.c"}, "deny-log")
+
+ # aclKey: "a.#.b.*"
+ self.Lookup("uMixed2@COMPANY", action, "exchange", "X", {"routingkey":"a.b.x"}, "allow-log")
+ self.Lookup("uMixed2@COMPANY", action, "exchange", "X", {"routingkey":"a.x.x.x.b.x"}, "allow-log")
+
+ # aclKey: "*.*.*.#"
+ self.Lookup("uMixed3@COMPANY", action, "exchange", "X", {"routingkey":"x.y.z"}, "allow-log")
+ self.Lookup("uMixed3@COMPANY", action, "exchange", "X", {"routingkey":"x.y.z.a.b.c"}, "allow-log")
+ self.Lookup("uMixed3@COMPANY", action, "exchange", "X", {"routingkey":"x.y"}, "deny-log")
+ self.Lookup("uMixed3@COMPANY", action, "exchange", "X", {"routingkey":"x"}, "deny-log")
+
+ # Repeat the keys with wildcard user spec
+ self.Lookup("uPlain1@COMPANY", action, "exchange", "X", {"routingkey":"MN.OP.Q"}, "allow-log")
+ self.Lookup("uStar1@COMPANY" , action, "exchange", "X", {"routingkey":"M.xx.N"}, "allow-log")
+ self.Lookup("uHash1@COMPANY" , action, "exchange", "X", {"routingkey":"M.N"}, "allow-log")
+ self.Lookup("uHash1@COMPANY" , action, "exchange", "X", {"routingkey":"M.x.N"}, "allow-log")
+ self.Lookup("uHash1@COMPANY" , action, "exchange", "X", {"routingkey":"M..x.y.zz.N"}, "allow-log")
+ self.Lookup("uMixed1@COMPANY", action, "exchange", "X", {"routingkey":"a.M.N"}, "allow-log")
+ self.Lookup("uMixed1@COMPANY", action, "exchange", "X", {"routingkey":"a.M.p.qq.N"}, "allow-log")
+
+ self.Lookup("dev@QPID", action, "exchange", "X", {"routingkey": "MN.OP.Q"}, "allow-log")
+ self.Lookup("dev@QPID", action, "exchange", "X", {"routingkey": "M.xx.N"}, "allow-log")
+ self.Lookup("dev@QPID", action, "exchange", "X", {"routingkey": "M.N"}, "allow-log")
+ self.Lookup("dev@QPID", action, "exchange", "X", {"routingkey": "M.x.N"}, "allow-log")
+ self.Lookup("dev@QPID", action, "exchange", "X", {"routingkey": "M..x.y.zz.N"}, "allow-log")
+ self.Lookup("dev@QPID", action, "exchange", "X", {"routingkey": "a.M.N"}, "allow-log")
+ self.Lookup("dev@QPID", action, "exchange", "X", {"routingkey": "a.M.p.qq.N"}, "allow-log")
+
+ #=====================================
+ # Connection limits
+ #=====================================
+
+ def test_connection_limits_cli_sets_all(self):
+
+ try:
+ sessiona1 = self.get_session_by_port('alice','alice', self.port_u())
+ sessiona2 = self.get_session_by_port('alice','alice', self.port_u())
+ except Exception, e:
+ self.fail("Could not create two connections for user alice: " + str(e))
+
+ # Third session should fail
+ try:
+ sessiona3 = self.get_session_by_port('alice','alice', self.port_u())
+ self.fail("Should not be able to create third connection for user alice")
+ except Exception, e:
+ result = None
+
+
+
+ def test_connection_limits_by_named_user(self):
+ """
+ Test ACL control connection limits
+ """
+ aclf = self.get_acl_file()
+ aclf.write('quota connections 2 aliceCL@QPID bobCL@QPID\n')
+ aclf.write('quota connections 0 evildude@QPID\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ # By username should be able to connect twice per user
+ try:
+ conna1 = self.get_connection('aliceCL','aliceCL')
+ conna2 = self.get_connection('aliceCL','aliceCL')
+ except Exception, e:
+ self.fail("Could not create two connections for user aliceCL: " + str(e))
+
+ # Third session should fail
+ try:
+ conna3 = self.get_connection('aliceCL','aliceCL')
+ self.fail("Should not be able to create third connection for user aliceCL")
+ except Exception, e:
+ result = None
+
+ # Disconnecting should allow another session.
+ conna1.close()
+ try:
+ conna3 = self.get_connection('aliceCL','aliceCL')
+ except Exception, e:
+ self.fail("Could not recreate second connection for user aliceCL: " + str(e))
+
+ # By username should be able to connect twice per user
+ try:
+ connb1 = self.get_connection('bobCL','bobCL')
+ connb2 = self.get_connection('bobCL','bobCL')
+ except Exception, e:
+ self.fail("Could not create two connections for user bobCL: " + str(e))
+
+ # Third session should fail
+ try:
+ connb3 = self.get_connection('bobCL','bobCL')
+ self.fail("Should not be able to create third connection for user bobCL")
+ except Exception, e:
+ result = None
+
+
+ # User with quota of 0 is denied
+ try:
+ conne1 = self.get_connection('evildude','evildude')
+ self.fail("Should not be able to create a connection for user evildude")
+ except Exception, e:
+ result = None
+
+
+ # User not named in quotas is denied
+ try:
+ connc1 = self.get_connection('charlie','charlie')
+ self.fail("Should not be able to create a connection for user charlie")
+ except Exception, e:
+ result = None
+
+ # Clean up the connections
+ conna2.close()
+ conna3.close()
+ connb1.close()
+ connb2.close()
+
+
+
+ def test_connection_limits_by_unnamed_all(self):
+ """
+ Test ACL control connection limits
+ """
+ aclf = self.get_acl_file()
+ aclf.write('quota connections 2 aliceUA@QPID bobUA@QPID\n')
+ aclf.write('quota connections 1 all\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ # By username should be able to connect twice per user
+ try:
+ connectiona1 = self.get_connection('aliceUA','alice')
+ connectiona2 = self.get_connection('aliceUA','alice')
+ except Exception, e:
+ self.fail("Could not create two connections for user alice: " + str(e))
+
+ # Third connection should fail
+ try:
+ connectiona3 = self.get_connection('aliceUA','alice')
+ self.fail("Should not be able to create third connection for user alice")
+ except Exception, e:
+ result = None
+
+ # By username should be able to connect twice per user
+ try:
+ connectionb1 = self.get_connection('bobUA','bob')
+ connectionb2 = self.get_connection('bobUA','bob')
+ except Exception, e:
+ self.fail("Could not create two connections for user bob: " + str(e))
+
+ # Third connection should fail
+ try:
+ connectionb3 = self.get_connection('bobUA','bob')
+ self.fail("Should not be able to create third connection for user bob")
+ except Exception, e:
+ result = None
+
+ # User not named in quotas gets 'all' quota
+ try:
+ connectionc1 = self.get_connection('charlieUA','charlie')
+ except Exception, e:
+ self.fail("Could not create one connection for user charlie: " + str(e))
+
+ # Next connection should fail
+ try:
+ connectionc2 = self.get_connection('charlieUA','charlie')
+ self.fail("Should not be able to create second connection for user charlie")
+ except Exception, e:
+ result = None
+
+ # Clean up the connections
+ connectiona1.close()
+ connectiona2.close()
+ connectionb1.close()
+ connectionb2.close()
+ connectionc1.close()
+
+
+ def test_connection_limits_by_group(self):
+ """
+ Test ACL control connection limits
+ """
+ aclf = self.get_acl_file()
+ aclf.write('group stooges moeGR@QPID larryGR@QPID curlyGR@QPID\n')
+ aclf.write('quota connections 2 aliceGR@QPID bobGR@QPID\n')
+ aclf.write('quota connections 2 stooges charlieGR@QPID\n')
+ aclf.write('# user and groups may be overwritten. Should use last value\n')
+ aclf.write('quota connections 3 bobGR@QPID stooges\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ # Alice gets 2
+ try:
+ connectiona1 = self.get_connection('aliceGR','alice')
+ connectiona2 = self.get_connection('aliceGR','alice')
+ except Exception, e:
+ self.fail("Could not create two connections for user alice: " + str(e))
+
+ # Third connection should fail
+ try:
+ connectiona3 = self.get_connection('aliceGR','alice')
+ self.fail("Should not be able to create third connection for user alice")
+ except Exception, e:
+ result = None
+
+ # Bob gets 3
+ try:
+ connectionb1 = self.get_connection('bobGR','bob')
+ connectionb2 = self.get_connection('bobGR','bob')
+ connectionb3 = self.get_connection('bobGR','bob')
+ except Exception, e:
+ self.fail("Could not create three connections for user bob: " + str(e))
+
+ # Fourth connection should fail
+ try:
+ connectionb4 = self.get_connection('bobGR','bob')
+ self.fail("Should not be able to create fourth connection for user bob")
+ except Exception, e:
+ result = None
+
+ # Moe gets 3
+ try:
+ connectionm1 = self.get_connection('moeGR','moe')
+ connectionm2 = self.get_connection('moeGR','moe')
+ connectionm3 = self.get_connection('moeGR','moe')
+ except Exception, e:
+ self.fail("Could not create three connections for user moe: " + str(e))
+
+ # Fourth connection should fail
+ try:
+ connectionb4 = self.get_connection('moeGR','moe')
+ self.fail("Should not be able to create fourth connection for user ,pe")
+ except Exception, e:
+ result = None
+
+ # User not named in quotas is denied
+ try:
+ connections1 = self.get_connection('shempGR','shemp')
+ self.fail("Should not be able to create a connection for user shemp")
+ except Exception, e:
+ result = None
+
+ # Clean up the connections
+ connectiona1.close()
+ connectiona2.close()
+ connectionb1.close()
+ connectionb2.close()
+ connectionb3.close()
+ connectionm1.close()
+ connectionm2.close()
+ connectionm3.close()
+
+
+ def test_connection_limits_by_ip_address(self):
+ """
+ Test ACL control connection limits by ip address
+ """
+ # By IP address should be able to connect twice per client address
+ try:
+ sessionb1 = self.get_session_by_port('alice','alice', self.port_i())
+ sessionb2 = self.get_session_by_port('bob','bob', self.port_i())
+ except Exception, e:
+ self.fail("Could not create two connections for client address: " + str(e))
+
+ # Third session should fail
+ try:
+ sessionb3 = self.get_session_by_port('charlie','charlie', self.port_i())
+ self.fail("Should not be able to create third connection for client address")
+ except Exception, e:
+ result = None
+
+ sessionb1.close()
+ sessionb2.close()
+
+ #=====================================
+ # User name substitution
+ #=====================================
+
+ def test_user_name_substitution(self):
+ """
+ Test name substitution internals, limits, and edge cases.
+ """
+ aclf = self.get_acl_file()
+ aclf.write('# begin hack alert: allow anonymous to access the lookup debug functions\n')
+ aclf.write('acl allow-log anonymous create queue\n')
+ aclf.write('acl allow-log anonymous all exchange name=qmf.*\n')
+ aclf.write('acl allow-log anonymous all exchange name=amq.direct\n')
+ aclf.write('acl allow-log anonymous all exchange name=qpid.management\n')
+ aclf.write('acl allow-log anonymous access method name=*\n')
+ aclf.write('# end hack alert\n')
+ aclf.write('acl allow all create queue name=tmp-${userdomain}\n')
+ aclf.write('acl allow all create queue name=${userdomain}-tmp\n')
+ aclf.write('acl allow all create queue name=tmp-${userdomain}-tmp\n')
+ aclf.write('acl allow all create queue name=tmp-${userdomain}-tmp-${userdomain}\n')
+ aclf.write('acl allow all create queue name=temp0-${userdomain}\n')
+ aclf.write('acl allow all access queue name=temp0-${userdomain}\n')
+ aclf.write('acl allow all purge queue name=temp0-${userdomain}\n')
+ aclf.write('acl allow all consume queue name=temp0-${userdomain}\n')
+ aclf.write('acl allow all delete queue name=temp0-${userdomain}\n')
+ aclf.write('acl allow all create exchange name=temp0-${userdomain}\n')
+ aclf.write('acl allow all access exchange name=temp0-${userdomain}\n')
+ aclf.write('acl allow all bind exchange name=temp0-${userdomain}\n')
+ aclf.write('acl allow all unbind exchange name=temp0-${userdomain}\n')
+ aclf.write('acl allow all delete exchange name=temp0-${userdomain}\n')
+ aclf.write('acl allow all publish exchange name=temp0-${userdomain}\n')
+
+ aclf.write('acl allow all publish exchange name=X routingkey=${userdomain}.cd.e\n')
+ aclf.write('acl allow all publish exchange name=X routingkey=a.*.${userdomain}\n')
+ aclf.write('acl allow all publish exchange name=X routingkey=b.#.${userdomain}\n')
+ aclf.write('acl allow all publish exchange name=X routingkey=*.${userdomain}.#.y\n')
+
+ aclf.write('acl allow all create queue name=user-${user}\n')
+ aclf.write('acl allow all publish exchange name=U routingkey=${user}.cd.e\n')
+ aclf.write('acl allow all publish exchange name=U routingkey=a.*.${user}\n')
+ aclf.write('acl allow all publish exchange name=U routingkey=b.#.${user}\n')
+ aclf.write('acl allow all publish exchange name=U routingkey=*.${user}.#.y\n')
+
+ aclf.write('acl allow all create queue name=domain-${domain}\n')
+ aclf.write('acl allow all publish exchange name=D routingkey=${domain}.cd.e\n')
+ aclf.write('acl allow all publish exchange name=D routingkey=a.*.${domain}\n')
+ aclf.write('acl allow all publish exchange name=D routingkey=b.#.${domain}\n')
+ aclf.write('acl allow all publish exchange name=D routingkey=*.${domain}.#.y\n')
+
+ # Resolving ${user}_${domain} into ${userdomain} works for everything but routing keys
+ aclf.write('acl allow all create queue name=mixed-OK-${user}_${domain}\n')
+ # For routing keys ${user}_${domain} will be parsed into ${userdomain}.
+ # Routing keys not be found when the rule specifies ${user}_${domain}.
+ aclf.write('acl allow all publish exchange name=NOGO routingkey=${user}_${domain}.cd.e\n')
+ # This works since it is does not conflict with ${userdomain}
+ aclf.write('acl allow all publish exchange name=OK routingkey=${user}___${domain}.cd.e\n')
+
+ aclf.write('acl deny-log all all\n')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ self.Lookup("alice@QPID", "create", "queue", "tmp-alice_QPID", {}, "allow")
+ self.Lookup("bob@QPID", "create", "queue", "bob_QPID-tmp", {}, "allow")
+ self.Lookup("charlie@QPID", "create", "queue", "tmp-charlie_QPID-tmp", {}, "allow")
+ self.Lookup("dave@QPID", "create", "queue", "tmp-dave_QPID-tmp-dave_QPID", {}, "allow")
+ self.Lookup("ed@BIG.COM", "create", "queue", "tmp-ed_BIG_COM", {}, "allow")
+ self.Lookup("c.e.r@BIG.GER.COM", "create", "queue", "tmp-c_e_r_BIG_GER_COM", {}, "allow")
+ self.Lookup("c@", "create", "queue", "tmp-c_", {}, "allow")
+ self.Lookup("someuser", "create", "queue", "tmp-someuser", {}, "allow")
+
+ self.Lookup("alice@QPID", "create", "queue", "tmp-${user}", {}, "deny-log")
+
+ self.Lookup("bob@QPID", "create", "exchange", "temp0-bob_QPID", {}, "allow")
+ self.Lookup("bob@QPID", "access", "exchange", "temp0-bob_QPID", {}, "allow")
+ self.Lookup("bob@QPID", "bind", "exchange", "temp0-bob_QPID", {}, "allow")
+ self.Lookup("bob@QPID", "unbind", "exchange", "temp0-bob_QPID", {}, "allow")
+ self.Lookup("bob@QPID", "delete", "exchange", "temp0-bob_QPID", {}, "allow")
+ self.LookupPublish("bob@QPID", "temp0-bob_QPID", "x", "allow")
+
+ self.Lookup("bob@QPID", "create", "queue", "temp0-bob_QPID", {}, "allow")
+ self.Lookup("bob@QPID", "access", "queue", "temp0-bob_QPID", {}, "allow")
+ self.Lookup("bob@QPID", "purge", "queue", "temp0-bob_QPID", {}, "allow")
+ self.Lookup("bob@QPID", "consume", "queue", "temp0-bob_QPID", {}, "allow")
+ self.Lookup("bob@QPID", "delete", "queue", "temp0-bob_QPID", {}, "allow")
+
+ self.Lookup("alice@QPID", "access", "queue", "temp0-bob_QPID", {}, "deny-log")
+
+ # aclKey: "${userdomain}.cd.e"
+ self.LookupPublish("uPlain1@COMPANY", "X", "uPlain1_COMPANY.cd.e", "allow")
+ # aclKey: "a.*.${userdomain}"
+ self.LookupPublish("uStar1@COMPANY", "X", "a.xx.uStar1_COMPANY", "allow")
+ self.LookupPublish("uStar1@COMPANY", "X", "a.b", "deny-log")
+ # aclKey: "b.#.${userdomain}"
+ self.LookupPublish("uHash1@COMPANY", "X", "b.uHash1_COMPANY", "allow")
+ self.LookupPublish("uHash1@COMPANY", "X", "b.x.uHash1_COMPANY", "allow")
+ self.LookupPublish("uHash1@COMPANY", "X", "b..x.y.zz.uHash1_COMPANY", "allow")
+ self.LookupPublish("uHash1@COMPANY", "X", "b.uHash1_COMPANY.", "deny-log")
+ self.LookupPublish("uHash1@COMPANY", "X", "q.x.uHash1_COMPANY", "deny-log")
+ # aclKey: "*.${userdomain}.#.y"
+ self.LookupPublish("uMixed1@COMPANY", "X", "a.uMixed1_COMPANY.y", "allow")
+ self.LookupPublish("uMixed1@COMPANY", "X", "a.uMixed1_COMPANY.p.qq.y", "allow")
+ self.LookupPublish("uMixed1@COMPANY", "X", "a.a.uMixed1_COMPANY.y", "deny-log")
+ self.LookupPublish("uMixed1@COMPANY", "X", "aa.uMixed1_COMPANY.b.c", "deny-log")
+ self.LookupPublish("uMixed1@COMPANY.COM", "X", "a.uMixed1_COMPANY_COM.y", "allow")
+
+
+ self.Lookup("bob@QPID", "create", "queue", "user-bob", {}, "allow")
+ # aclKey: "${user}.cd.e"
+ self.LookupPublish("uPlain1@COMPANY", "U", "uPlain1.cd.e", "allow")
+ # aclKey: "a.*.${user}"
+ self.LookupPublish("uStar1@COMPANY", "U", "a.xx.uStar1", "allow")
+ self.LookupPublish("uStar1@COMPANY", "U", "a.b", "deny-log")
+ # aclKey: "b.#.${user}"
+ self.LookupPublish("uHash1@COMPANY", "U", "b.uHash1", "allow")
+ self.LookupPublish("uHash1@COMPANY", "U", "b.x.uHash1", "allow")
+ self.LookupPublish("uHash1@COMPANY", "U", "b..x.y.zz.uHash1", "allow")
+ self.LookupPublish("uHash1@COMPANY", "U", "b.uHash1.", "deny-log")
+ self.LookupPublish("uHash1@COMPANY", "U", "q.x.uHash1", "deny-log")
+ # aclKey: "*.${user}.#.y"
+ self.LookupPublish("uMixed1@COMPANY", "U", "a.uMixed1.y", "allow")
+ self.LookupPublish("uMixed1@COMPANY", "U", "a.uMixed1.p.qq.y", "allow")
+ self.LookupPublish("uMixed1@COMPANY", "U", "a.a.uMixed1.y", "deny-log")
+ self.LookupPublish("uMixed1@COMPANY", "U", "aa.uMixed1.b.c", "deny-log")
+ self.LookupPublish("uMixed1@COMPANY.COM", "U", "a.uMixed1.y", "allow")
+
+
+ self.Lookup("bob@QPID", "create", "queue", "domain-QPID", {}, "allow")
+ # aclKey: "${domain}.cd.e"
+ self.LookupPublish("uPlain1@COMPANY", "D", "COMPANY.cd.e", "allow")
+ # aclKey: "a.*.${domain}"
+ self.LookupPublish("uStar1@COMPANY", "D", "a.xx.COMPANY", "allow")
+ self.LookupPublish("uStar1@COMPANY", "D", "a.b", "deny-log")
+ # aclKey: "b.#.${domain}"
+ self.LookupPublish("uHash1@COMPANY", "D", "b.COMPANY", "allow")
+ self.LookupPublish("uHash1@COMPANY", "D", "b.x.COMPANY", "allow")
+ self.LookupPublish("uHash1@COMPANY", "D", "b..x.y.zz.COMPANY", "allow")
+ self.LookupPublish("uHash1@COMPANY", "D", "b.COMPANY.", "deny-log")
+ self.LookupPublish("uHash1@COMPANY", "D", "q.x.COMPANY", "deny-log")
+ # aclKey: "*.${domain}.#.y"
+ self.LookupPublish("uMixed1@COMPANY", "D", "a.COMPANY.y", "allow")
+ self.LookupPublish("uMixed1@COMPANY", "D", "a.COMPANY.p.qq.y", "allow")
+ self.LookupPublish("uMixed1@COMPANY", "D", "a.a.COMPANY.y", "deny-log")
+ self.LookupPublish("uMixed1@COMPANY", "D", "aa.COMPANY.b.c", "deny-log")
+ self.LookupPublish("uMixed1@COMPANY.COM", "D", "a.COMPANY_COM.y", "allow")
+
+ self.Lookup("uPlain1@COMPANY", "create", "queue", "mixed-OK-uPlain1_COMPANY", {}, "allow")
+ self.LookupPublish("uPlain1@COMPANY", "NOGO", "uPlain1_COMPANY.cd.e", "deny-log")
+ self.LookupPublish("uPlain1@COMPANY", "OK", "uPlain1___COMPANY.cd.e", "allow")
+
+
+ #=====================================
+ # User name substitution details
+ #=====================================
+ # User name substitution allows for three flavors of keyword in the Acl file.
+ # Given a user name of bob.user@QPID.COM the keywords are normalized and resolve as follows:
+ # ${userdomain} - bob_user_QPID_COM
+ # ${user} - bob_user
+ # ${domain} - QPID_COM
+ #
+ # The following substitution tests are very similar but differ in the flavor of keyword used
+ # in the rules. The tests results using the different keywords differ slightly in how permissive
+ # the rules become.
+ # ${userdomain} limits access to one authenticated user
+ # ${user} limits access to a user name regardless of user's domain
+ # ${domain} limits access to a domain regardless of user name
+ #
+
+ def test_user_name_substitution_userdomain(self):
+ """
+ Test a setup where users can create, bind, and publish to a main exchange and queue.
+ Allow access to a single alternate exchange and queue.
+ """
+ aclf = self.get_acl_file()
+ aclf.write('# begin hack alert: allow anonymous to access the lookup debug functions\n')
+ aclf.write('acl allow-log anonymous create queue\n')
+ aclf.write('acl allow-log anonymous all exchange name=qmf.*\n')
+ aclf.write('acl allow-log anonymous all exchange name=amq.direct\n')
+ aclf.write('acl allow-log anonymous all exchange name=qpid.management\n')
+ aclf.write('acl allow-log anonymous access method name=*\n')
+ aclf.write('# end hack alert\n')
+ # Create primary queue and exchange:
+ # allow predefined alternate
+ # deny any other alternate
+ # allow no alternate
+ aclf.write('acl allow all create queue name=${userdomain}-work alternate=${userdomain}-work2\n')
+ aclf.write('acl deny all create queue name=${userdomain}-work alternate=*\n')
+ aclf.write('acl allow all create queue name=${userdomain}-work\n')
+ aclf.write('acl allow all create exchange name=${userdomain}-work alternate=${userdomain}-work2\n')
+ aclf.write('acl deny all create exchange name=${userdomain}-work alternate=*\n')
+ aclf.write('acl allow all create exchange name=${userdomain}-work\n')
+ # Create backup queue and exchange
+ # Deny any alternate
+ aclf.write('acl deny all create queue name=${userdomain}-work2 alternate=*\n')
+ aclf.write('acl allow all create queue name=${userdomain}-work2\n')
+ aclf.write('acl deny all create exchange name=${userdomain}-work2 alternate=*\n')
+ aclf.write('acl allow all create exchange name=${userdomain}-work2\n')
+ # Bind/unbind primary exchange
+ # Use only predefined routingkey and queuename
+ aclf.write('acl allow all bind exchange name=${userdomain}-work routingkey=${userdomain} queuename=${userdomain}-work\n')
+ aclf.write('acl allow all unbind exchange name=${userdomain}-work routingkey=${userdomain} queuename=${userdomain}-work\n')
+ # Bind/unbind backup exchange
+ # Use only predefined routingkey and queuename
+ aclf.write('acl allow all bind exchange name=${userdomain}-work2 routingkey=${userdomain} queuename=${userdomain}-work2\n')
+ aclf.write('acl allow all unbind exchange name=${userdomain}-work2 routingkey=${userdomain} queuename=${userdomain}-work2\n')
+ # Access primary exchange
+ # Use only predefined routingkey and queuename
+ aclf.write('acl allow all access exchange name=${userdomain}-work routingkey=${userdomain} queuename=${userdomain}-work\n')
+ # Access backup exchange
+ # Use only predefined routingkey and queuename
+ aclf.write('acl allow all access exchange name=${userdomain}-work2 routingkey=${userdomain} queuename=${userdomain}-work2\n')
+ # Publish primary exchange
+ # Use only predefined routingkey
+ aclf.write('acl allow all publish exchange name=${userdomain}-work routingkey=${userdomain}\n')
+ # Publish backup exchange
+ # Use only predefined routingkey
+ aclf.write('acl allow all publish exchange name=${userdomain}-work2 routingkey=${userdomain}\n')
+ # deny mode
+ aclf.write('acl deny all all\n')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ # create queues
+ self.Lookup("bob@QPID", "create", "queue", "bob_QPID-work", {}, "allow")
+ self.Lookup("bob@QPID", "create", "queue", "bob_QPID-work2", {}, "allow")
+ self.Lookup("bob@QPID", "create", "queue", "joe_QPID-work", {}, "deny")
+ self.Lookup("bob@QPID", "create", "queue", "joe_QPID-work2", {}, "deny")
+ self.Lookup("bob@QPID", "create", "queue", "bob_QPID-work3", {}, "deny")
+ self.Lookup("bob@QPID", "create", "queue", "bob_QPID-work", {"alternate":"bob_QPID-work2"}, "allow")
+ self.Lookup("bob@QPID", "create", "queue", "bob_QPID-work", {"alternate":"joe_QPID-work2"}, "deny")
+ self.Lookup("bob@QPID", "create", "queue", "bob_QPID-work2", {"alternate":"someexchange"}, "deny")
+ # create exchanges
+ self.Lookup("bob@QPID", "create", "exchange", "bob_QPID-work", {}, "allow")
+ self.Lookup("bob@QPID", "create", "exchange", "bob_QPID-work2",{}, "allow")
+ self.Lookup("bob@QPID", "create", "exchange", "joe_QPID-work", {}, "deny")
+ self.Lookup("bob@QPID", "create", "exchange", "joe_QPID-work2",{}, "deny")
+ self.Lookup("bob@QPID", "create", "exchange", "bob_QPID-work3",{}, "deny")
+ self.Lookup("bob@QPID", "create", "exchange", "bob_QPID-work", {"alternate":"bob_QPID-work2"}, "allow")
+ self.Lookup("bob@QPID", "create", "exchange", "bob_QPID-work2",{"alternate":"someexchange"}, "deny")
+ # bind/unbind/access
+ self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work", {}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work", {"routingkey":"bob_QPID"}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work", { "queuename":"bob_QPID-work"}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work"}, "allow")
+ self.Lookup("bob@QPID", "bind", "exchange", "joe_QPID-work", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work"}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work", {"routingkey":"joe_QPID", "queuename":"bob_QPID-work"}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work", {"routingkey":"bob_QPID", "queuename":"joe_QPID-work"}, "deny")
+
+ self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work2", {}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work2", {"routingkey":"bob_QPID"}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work2", { "queuename":"bob_QPID-work2"}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work2", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work2"}, "allow")
+ self.Lookup("bob@QPID", "bind", "exchange", "joe_QPID-work2", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work2"}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work2", {"routingkey":"joe_QPID", "queuename":"bob_QPID-work2"}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "bob_QPID-work2", {"routingkey":"bob_QPID", "queuename":"joe_QPID-work2"}, "deny")
+
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work", {}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work", {"routingkey":"bob_QPID"}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work", { "queuename":"bob_QPID-work"}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work"}, "allow")
+ self.Lookup("bob@QPID", "unbind", "exchange", "joe_QPID-work", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work"}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work", {"routingkey":"joe_QPID", "queuename":"bob_QPID-work"}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work", {"routingkey":"bob_QPID", "queuename":"joe_QPID-work"}, "deny")
+
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work2", {}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work2", {"routingkey":"bob_QPID"}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work2", { "queuename":"bob_QPID-work2"}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work2", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work2"}, "allow")
+ self.Lookup("bob@QPID", "unbind", "exchange", "joe_QPID-work2", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work2"}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work2", {"routingkey":"joe_QPID", "queuename":"bob_QPID-work2"}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob_QPID-work2", {"routingkey":"bob_QPID", "queuename":"joe_QPID-work2"}, "deny")
+
+ self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work", {}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work", {"routingkey":"bob_QPID"}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work", { "queuename":"bob_QPID-work"}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work"}, "allow")
+ self.Lookup("bob@QPID", "access", "exchange", "joe_QPID-work", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work"}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work", {"routingkey":"joe_QPID", "queuename":"bob_QPID-work"}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work", {"routingkey":"bob_QPID", "queuename":"joe_QPID-work"}, "deny")
+
+ self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work2", {}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work2", {"routingkey":"bob_QPID"}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work2", { "queuename":"bob_QPID-work2"}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work2", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work2"}, "allow")
+ self.Lookup("bob@QPID", "access", "exchange", "joe_QPID-work2", {"routingkey":"bob_QPID", "queuename":"bob_QPID-work2"}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work2", {"routingkey":"joe_QPID", "queuename":"bob_QPID-work2"}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "bob_QPID-work2", {"routingkey":"bob_QPID", "queuename":"joe_QPID-work2"}, "deny")
+ # publish
+ self.LookupPublish("bob@QPID", "bob_QPID-work", "bob_QPID", "allow")
+ self.LookupPublish("bob@QPID", "bob_QPID-work2", "bob_QPID", "allow")
+ self.LookupPublish("bob@QPID", "joe_QPID-work", "bob_QPID", "deny")
+ self.LookupPublish("bob@QPID", "joe_QPID-work2", "bob_QPID", "deny")
+ self.LookupPublish("bob@QPID", "bob_QPID-work", "joe_QPID", "deny")
+ self.LookupPublish("bob@QPID", "bob_QPID-work2", "joe_QPID", "deny")
+
+
+ def test_user_name_substitution_user(self):
+ """
+ Test a setup where users can create, bind, and publish to a main exchange and queue.
+ Allow access to a single backup exchange and queue.
+ """
+ aclf = self.get_acl_file()
+ aclf.write('# begin hack alert: allow anonymous to access the lookup debug functions\n')
+ aclf.write('acl allow-log anonymous create queue\n')
+ aclf.write('acl allow-log anonymous all exchange name=qmf.*\n')
+ aclf.write('acl allow-log anonymous all exchange name=amq.direct\n')
+ aclf.write('acl allow-log anonymous all exchange name=qpid.management\n')
+ aclf.write('acl allow-log anonymous access method name=*\n')
+ aclf.write('# end hack alert\n')
+ # Create primary queue and exchange
+ # allow predefined alternate
+ # deny any other alternate
+ # allow no alternate
+ aclf.write('acl allow all create queue name=${user}-work alternate=${user}-work2\n')
+ aclf.write('acl deny all create queue name=${user}-work alternate=*\n')
+ aclf.write('acl allow all create queue name=${user}-work\n')
+ aclf.write('acl allow all create exchange name=${user}-work alternate=${user}-work2\n')
+ aclf.write('acl deny all create exchange name=${user}-work alternate=*\n')
+ aclf.write('acl allow all create exchange name=${user}-work\n')
+ # Create backup queue and exchange
+ # Deny any alternate
+ aclf.write('acl deny all create queue name=${user}-work2 alternate=*\n')
+ aclf.write('acl allow all create queue name=${user}-work2\n')
+ aclf.write('acl deny all create exchange name=${user}-work2 alternate=*\n')
+ aclf.write('acl allow all create exchange name=${user}-work2\n')
+ # Bind/unbind primary exchange
+ # Use only predefined routingkey and queuename
+ aclf.write('acl allow all bind exchange name=${user}-work routingkey=${user} queuename=${user}-work\n')
+ aclf.write('acl allow all unbind exchange name=${user}-work routingkey=${user} queuename=${user}-work\n')
+ # Bind/unbind backup exchange
+ # Use only predefined routingkey and queuename
+ aclf.write('acl allow all bind exchange name=${user}-work2 routingkey=${user} queuename=${user}-work2\n')
+ aclf.write('acl allow all unbind exchange name=${user}-work2 routingkey=${user} queuename=${user}-work2\n')
+ # Access primary exchange
+ # Use only predefined routingkey and queuename
+ aclf.write('acl allow all access exchange name=${user}-work routingkey=${user} queuename=${user}-work\n')
+ # Access backup exchange
+ # Use only predefined routingkey and queuename
+ aclf.write('acl allow all access exchange name=${user}-work2 routingkey=${user} queuename=${user}-work2\n')
+ # Publish primary exchange
+ # Use only predefined routingkey
+ aclf.write('acl allow all publish exchange name=${user}-work routingkey=${user}\n')
+ # Publish backup exchange
+ # Use only predefined routingkey
+ aclf.write('acl allow all publish exchange name=${user}-work2 routingkey=${user}\n')
+ # deny mode
+ aclf.write('acl deny all all\n')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ # create queues
+ self.Lookup("bob@QPID", "create", "queue", "bob-work", {}, "allow")
+ self.Lookup("bob@QPID", "create", "queue", "bob-work2", {}, "allow")
+ self.Lookup("bob@QPID", "create", "queue", "joe-work", {}, "deny")
+ self.Lookup("bob@QPID", "create", "queue", "joe-work2", {}, "deny")
+ self.Lookup("bob@QPID", "create", "queue", "bob-work3", {}, "deny")
+ self.Lookup("bob@QPID", "create", "queue", "bob-work", {"alternate":"bob-work2"}, "allow")
+ self.Lookup("bob@QPID", "create", "queue", "bob-work", {"alternate":"joe-work2"}, "deny")
+ self.Lookup("bob@QPID", "create", "queue", "bob-work2", {"alternate":"someexchange"},"deny")
+ # create exchanges
+ self.Lookup("bob@QPID", "create", "exchange", "bob-work", {}, "allow")
+ self.Lookup("bob@QPID", "create", "exchange", "bob-work2",{}, "allow")
+ self.Lookup("bob@QPID", "create", "exchange", "joe-work", {}, "deny")
+ self.Lookup("bob@QPID", "create", "exchange", "joe-work2",{}, "deny")
+ self.Lookup("bob@QPID", "create", "exchange", "bob-work3",{}, "deny")
+ self.Lookup("bob@QPID", "create", "exchange", "bob-work", {"alternate":"bob-work2"}, "allow")
+ self.Lookup("bob@QPID", "create", "exchange", "bob-work2",{"alternate":"someexchange"},"deny")
+ # bind/unbind/access
+ self.Lookup("bob@QPID", "bind", "exchange", "bob-work", {}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "bob-work", {"routingkey":"bob"}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "bob-work", { "queuename":"bob-work"}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "bob-work", {"routingkey":"bob", "queuename":"bob-work"}, "allow")
+ self.Lookup("bob@QPID", "bind", "exchange", "joe-work", {"routingkey":"bob", "queuename":"bob-work"}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "bob-work", {"routingkey":"joe", "queuename":"bob-work"}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "bob-work", {"routingkey":"bob", "queuename":"joe-work"}, "deny")
+
+ self.Lookup("bob@QPID", "bind", "exchange", "bob-work2", {}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "bob-work2", {"routingkey":"bob"}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "bob-work2", { "queuename":"bob-work2"}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "bob-work2", {"routingkey":"bob", "queuename":"bob-work2"}, "allow")
+ self.Lookup("bob@QPID", "bind", "exchange", "joe-work2", {"routingkey":"bob", "queuename":"bob-work2"}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "bob-work2", {"routingkey":"joe", "queuename":"bob-work2"}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "bob-work2", {"routingkey":"bob", "queuename":"joe-work2"}, "deny")
+
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob-work", {}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob-work", {"routingkey":"bob"}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob-work", { "queuename":"bob-work"}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob-work", {"routingkey":"bob", "queuename":"bob-work"}, "allow")
+ self.Lookup("bob@QPID", "unbind", "exchange", "joe-work", {"routingkey":"bob", "queuename":"bob-work"}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob-work", {"routingkey":"joe", "queuename":"bob-work"}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob-work", {"routingkey":"bob", "queuename":"joe-work"}, "deny")
+
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob-work2", {}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob-work2", {"routingkey":"bob"}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob-work2", { "queuename":"bob-work2"}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob-work2", {"routingkey":"bob", "queuename":"bob-work2"}, "allow")
+ self.Lookup("bob@QPID", "unbind", "exchange", "joe-work2", {"routingkey":"bob", "queuename":"bob-work2"}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob-work2", {"routingkey":"joe", "queuename":"bob-work2"}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "bob-work2", {"routingkey":"bob", "queuename":"joe-work2"}, "deny")
+
+ self.Lookup("bob@QPID", "access", "exchange", "bob-work", {}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "bob-work", {"routingkey":"bob"}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "bob-work", { "queuename":"bob-work"}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "bob-work", {"routingkey":"bob", "queuename":"bob-work"}, "allow")
+ self.Lookup("bob@QPID", "access", "exchange", "joe-work", {"routingkey":"bob", "queuename":"bob-work"}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "bob-work", {"routingkey":"joe", "queuename":"bob-work"}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "bob-work", {"routingkey":"bob", "queuename":"joe-work"}, "deny")
+
+ self.Lookup("bob@QPID", "access", "exchange", "bob-work2", {}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "bob-work2", {"routingkey":"bob"}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "bob-work2", { "queuename":"bob-work2"}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "bob-work2", {"routingkey":"bob", "queuename":"bob-work2"}, "allow")
+ self.Lookup("bob@QPID", "access", "exchange", "joe-work2", {"routingkey":"bob", "queuename":"bob-work2"}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "bob-work2", {"routingkey":"joe", "queuename":"bob-work2"}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "bob-work2", {"routingkey":"bob", "queuename":"joe-work2"}, "deny")
+ # publish
+ self.LookupPublish("bob@QPID", "bob-work", "bob", "allow")
+ self.LookupPublish("bob@QPID", "bob-work2", "bob", "allow")
+ self.LookupPublish("bob@QPID", "joe-work", "bob", "deny")
+ self.LookupPublish("bob@QPID", "joe-work2", "bob", "deny")
+ self.LookupPublish("bob@QPID", "bob-work", "joe", "deny")
+ self.LookupPublish("bob@QPID", "bob-work2", "joe", "deny")
+
+
+ def test_user_name_substitution_domain(self):
+ """
+ Test a setup where users can create, bind, and publish to a main exchange and queue.
+ Allow access to a single backup exchange and queue.
+ """
+ aclf = self.get_acl_file()
+ aclf.write('# begin hack alert: allow anonymous to access the lookup debug functions\n')
+ aclf.write('acl allow-log anonymous create queue\n')
+ aclf.write('acl allow-log anonymous all exchange name=qmf.*\n')
+ aclf.write('acl allow-log anonymous all exchange name=amq.direct\n')
+ aclf.write('acl allow-log anonymous all exchange name=qpid.management\n')
+ aclf.write('acl allow-log anonymous access method name=*\n')
+ aclf.write('# end hack alert\n')
+ # Create primary queue and exchange
+ # allow predefined alternate
+ # deny any other alternate
+ # allow no alternate
+ aclf.write('acl allow all create queue name=${domain}-work alternate=${domain}-work2\n')
+ aclf.write('acl deny all create queue name=${domain}-work alternate=*\n')
+ aclf.write('acl allow all create queue name=${domain}-work\n')
+ aclf.write('acl allow all create exchange name=${domain}-work alternate=${domain}-work2\n')
+ aclf.write('acl deny all create exchange name=${domain}-work alternate=*\n')
+ aclf.write('acl allow all create exchange name=${domain}-work\n')
+ # Create backup queue and exchange
+ # Deny any alternate
+ aclf.write('acl deny all create queue name=${domain}-work2 alternate=*\n')
+ aclf.write('acl allow all create queue name=${domain}-work2\n')
+ aclf.write('acl deny all create exchange name=${domain}-work2 alternate=*\n')
+ aclf.write('acl allow all create exchange name=${domain}-work2\n')
+ # Bind/unbind primary exchange
+ # Use only predefined routingkey and queuename
+ aclf.write('acl allow all bind exchange name=${domain}-work routingkey=${domain} queuename=${domain}-work\n')
+ aclf.write('acl allow all unbind exchange name=${domain}-work routingkey=${domain} queuename=${domain}-work\n')
+ # Bind/unbind backup exchange
+ # Use only predefined routingkey and queuename
+ aclf.write('acl allow all bind exchange name=${domain}-work2 routingkey=${domain} queuename=${domain}-work2\n')
+ aclf.write('acl allow all unbind exchange name=${domain}-work2 routingkey=${domain} queuename=${domain}-work2\n')
+ # Access primary exchange
+ # Use only predefined routingkey and queuename
+ aclf.write('acl allow all access exchange name=${domain}-work routingkey=${domain} queuename=${domain}-work\n')
+ # Access backup exchange
+ # Use only predefined routingkey and queuename
+ aclf.write('acl allow all access exchange name=${domain}-work2 routingkey=${domain} queuename=${domain}-work2\n')
+ # Publish primary exchange
+ # Use only predefined routingkey
+ aclf.write('acl allow all publish exchange name=${domain}-work routingkey=${domain}\n')
+ # Publish backup exchange
+ # Use only predefined routingkey
+ aclf.write('acl allow all publish exchange name=${domain}-work2 routingkey=${domain}\n')
+ # deny mode
+ aclf.write('acl deny all all\n')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ # create queues
+ self.Lookup("bob@QPID", "create", "queue", "QPID-work", {}, "allow")
+ self.Lookup("bob@QPID", "create", "queue", "QPID-work2", {}, "allow")
+ self.Lookup("bob@QPID", "create", "queue", "QPID-work3", {}, "deny")
+ self.Lookup("bob@QPID", "create", "queue", "QPID-work", {"alternate":"QPID-work2"}, "allow")
+ self.Lookup("bob@QPID", "create", "queue", "QPID-work", {"alternate":"bob_QPID-work2"},"deny")
+ self.Lookup("bob@QPID", "create", "queue", "QPID-work", {"alternate":"joe_QPID-work2"},"deny")
+ self.Lookup("bob@QPID", "create", "queue", "QPID-work2", {"alternate":"someexchange"}, "deny")
+ # create exchanges
+ self.Lookup("bob@QPID", "create", "exchange", "QPID-work", {}, "allow")
+ self.Lookup("bob@QPID", "create", "exchange", "QPID-work2",{}, "allow")
+ self.Lookup("bob@QPID", "create", "exchange", "QPID-work3",{}, "deny")
+ self.Lookup("bob@QPID", "create", "exchange", "QPID-work", {"alternate":"QPID-work2"}, "allow")
+ self.Lookup("bob@QPID", "create", "exchange", "QPID-work2",{"alternate":"someexchange"}, "deny")
+ # bind/unbind/access
+ self.Lookup("bob@QPID", "bind", "exchange", "QPID-work", {}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "QPID-work", {"routingkey":"QPID"}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "QPID-work", { "queuename":"QPID-work"}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "QPID-work", {"routingkey":"QPID", "queuename":"QPID-work"}, "allow")
+
+ self.Lookup("bob@QPID", "bind", "exchange", "QPID-work2", {}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "QPID-work2", {"routingkey":"QPID"}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "QPID-work2", { "queuename":"QPID-work2"}, "deny")
+ self.Lookup("bob@QPID", "bind", "exchange", "QPID-work2", {"routingkey":"QPID", "queuename":"QPID-work2"}, "allow")
+
+ self.Lookup("bob@QPID", "unbind", "exchange", "QPID-work", {}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "QPID-work", {"routingkey":"QPID"}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "QPID-work", { "queuename":"QPID-work"}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "QPID-work", {"routingkey":"QPID", "queuename":"QPID-work"}, "allow")
+
+ self.Lookup("bob@QPID", "unbind", "exchange", "QPID-work2", {}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "QPID-work2", {"routingkey":"QPID"}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "QPID-work2", { "queuename":"QPID-work2"}, "deny")
+ self.Lookup("bob@QPID", "unbind", "exchange", "QPID-work2", {"routingkey":"QPID", "queuename":"QPID-work2"}, "allow")
+
+ self.Lookup("bob@QPID", "access", "exchange", "QPID-work", {}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "QPID-work", {"routingkey":"QPID"}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "QPID-work", { "queuename":"QPID-work"}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "QPID-work", {"routingkey":"QPID", "queuename":"QPID-work"}, "allow")
+
+ self.Lookup("bob@QPID", "access", "exchange", "QPID-work2", {}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "QPID-work2", {"routingkey":"QPID"}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "QPID-work2", { "queuename":"QPID-work2"}, "deny")
+ self.Lookup("bob@QPID", "access", "exchange", "QPID-work2", {"routingkey":"QPID", "queuename":"QPID-work2"}, "allow")
+ # publish
+ self.LookupPublish("bob@QPID", "QPID-work", "QPID", "allow")
+ self.LookupPublish("bob@QPID", "QPID-work2", "QPID", "allow")
+ self.LookupPublish("joe@QPID", "QPID-work", "QPID", "allow")
+ self.LookupPublish("joe@QPID", "QPID-work2", "QPID", "allow")
+
+ #=====================================
+ # Queue per-user quota
+ #=====================================
+
+ def queue_quota(self, user, passwd, count, byPort=None):
+ """ Helper method to:
+ - create a number of queues (should succeed)
+ - create too many queues (should fail)
+ - create another queue after deleting a queue (should succeed)
+ """
+
+ try:
+ if byPort:
+ session = self.get_session_by_port(user, passwd, byPort)
+ else:
+ session = self.get_session(user, passwd)
+ except Exception, e:
+ self.fail("Unexpected error creating session for %s: %s" % (user, str(e)))
+
+ # Should be able to create count queues per user
+ try:
+ for i in range(count):
+ session.queue_declare(queue="%s%d" % (user, i))
+ except Exception, e:
+ self.fail("Could not create %s for %s: %s" % ("%s%d" % (user, i), user, str(e)))
+
+ # next queue should fail
+ try:
+ session.queue_declare(queue="%s%d" % (user, count))
+ self.fail("Should not be able to create another queue for user %s" % user)
+ except Exception, e:
+ if byPort:
+ session = self.get_session_by_port(user, passwd, byPort)
+ else:
+ session = self.get_session(user, passwd)
+
+ if count > 0:
+ # Deleting a queue should allow another queue.
+ session.queue_delete(queue="%s0" % user)
+ try:
+ session.queue_declare(queue="%s%d" % (user, count))
+ except Exception, e:
+ self.fail("Could not recreate additional queue for user %s: %s " % (user, str(e)))
+
+ # Clean up
+ for i in range(1, count+1):
+ session.queue_delete(queue="%s%d" % (user, i))
+ try:
+ session.close()
+ except Exception, e:
+ pass
+
+ def test_queue_per_named_user_quota(self):
+ """
+ Test ACL queue counting limits per named user.
+ """
+ aclf = self.get_acl_file()
+ aclf.write('quota queues 2 ted@QPID carrol@QPID\n')
+ aclf.write('quota queues 1 edward@QPID\n')
+ aclf.write('quota queues 0 mick@QPID\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ # named users should be able to create specified number of queues
+ self.queue_quota("ted", 'ted', 2)
+ self.queue_quota("carrol", 'carrol', 2)
+ self.queue_quota("edward", 'edward', 1)
+
+ # User with quota of 0 is denied
+ self.queue_quota("mick", 'mick', 0)
+
+ # User not named in quotas is denied
+ self.queue_quota("dan", 'dan', 0)
+
+ def test_queue_per_user_quota(self):
+ """
+ Test ACL queue counting limits.
+ port_q has a limit of 2
+ """
+ # bob should be able to create two queues
+ self.queue_quota("bob", 'bob', 2, self.port_q())
+
+ # alice should be able to create two queues
+ self.queue_quota("alice", 'alice', 2, self.port_q())
+
+
+ def test_queue_limits_by_unnamed_all(self):
+ """
+ Test ACL control queue limits
+ """
+ aclf = self.get_acl_file()
+ aclf.write('quota queues 2 aliceQUA@QPID bobQUA@QPID\n')
+ aclf.write('quota queues 1 all\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ # By username should be able to connect twice per user
+ self.queue_quota('aliceQUA', 'alice', 2)
+ self.queue_quota('bobQUA', 'bob', 2)
+
+ # User not named in quotas gets 'all' quota
+ self.queue_quota('charlieQUA', 'charlie', 1)
+
+
+ def test_queue_limits_by_group(self):
+ """
+ Test ACL control queue limits
+ """
+ aclf = self.get_acl_file()
+ aclf.write('group hobbits frodoGR@QPID samGR@QPID merryGR@QPID\n')
+ aclf.write('quota queues 2 gandalfGR@QPID aragornGR@QPID\n')
+ aclf.write('quota queues 2 hobbits rosieGR@QPID\n')
+ aclf.write('# user and groups may be overwritten. Should use last value\n')
+ aclf.write('quota queues 3 aragornGR@QPID hobbits\n')
+ aclf.write('acl allow all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ # gandalf gets 2
+ self.queue_quota('gandalfGR', 'gandalf', 2)
+
+ # aragorn gets 3
+ self.queue_quota('aragornGR', 'aragorn', 3)
+
+ # frodo gets 3
+ self.queue_quota('frodoGR', 'frodo', 3)
+
+ # User not named in quotas is denied
+ self.queue_quota('bilboGR', 'bilbo', 0)
+
+ def test_queue_delete_with_properties(self):
+ """
+ Test cases for queue delete with properties
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl allow bob@QPID access queue\n')
+ aclf.write('acl allow bob@QPID access exchange\n')
+ aclf.write('acl allow bob@QPID create queue name=qdaq1 durable=true\n')
+ aclf.write('acl allow bob@QPID create queue name=qdaq2 exclusive=true\n')
+ aclf.write('acl allow bob@QPID create queue name=qdaq3 policytype=ring\n')
+ aclf.write('acl allow bob@QPID create queue name=qdaq4 durable=false\n')
+ aclf.write('acl allow bob@QPID create queue name=qdaq5 exclusive=false\n')
+ aclf.write('acl allow bob@QPID create queue name=qdaq6 policytype=reject\n')
+ aclf.write('acl allow bob@QPID create queue name=qdaq7 autodelete=true\n')
+ aclf.write('acl allow bob@QPID create queue name=qdaq8 autodelete=false\n')
+ aclf.write('acl allow bob@QPID create queue name=qdaq9\n')
+ aclf.write('acl allow bob@QPID create exchange name=qdae9\n')
+ aclf.write('acl deny bob@QPID delete queue name=qdaq1 durable=true\n')
+ aclf.write('acl deny bob@QPID delete queue name=qdaq2 exclusive=true\n')
+ aclf.write('acl deny bob@QPID delete queue name=qdaq3 policytype=ring\n')
+ aclf.write('acl deny bob@QPID delete queue name=qdaq4 durable=false\n')
+ aclf.write('acl deny bob@QPID delete queue name=qdaq5 exclusive=false\n')
+ aclf.write('acl deny bob@QPID delete queue name=qdaq6 policytype=reject\n')
+ aclf.write('acl deny bob@QPID delete queue name=qdaq7 autodelete=true\n')
+ aclf.write('acl deny bob@QPID delete queue name=qdaq8 autodelete=false\n')
+ aclf.write('acl deny bob@QPID delete queue name=qdaq9 alternate=qdaq9a\n')
+ aclf.write('acl allow all access queue\n')
+ aclf.write('acl allow anonymous all all\n')
+ aclf.write('acl deny all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('bob','bob')
+
+ try:
+ session.queue_declare(queue="qdaq1", durable=True)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request with name=qdaq1 durable=true");
+
+ try:
+ session.queue_delete(queue="qdaq1")
+ self.fail("ACL should deny queue delete request for qdaq1");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.queue_declare(queue="qdaq2", exclusive=True)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request with name=qdaq2 exclusive=true");
+
+ try:
+ session.queue_delete(queue="qdaq2")
+ self.fail("ACL should deny queue delete request for qdaq2");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.policy_type"] = "ring"
+ session.queue_declare(queue="qdaq3", arguments=queue_options)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request for qdaq3 with policytype=ring");
+
+ try:
+ session.queue_delete(queue="qdaq3")
+ self.fail("ACL should deny queue delete request for qdaq3");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.queue_declare(queue="qdaq4", durable=False)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request with name=qdaq4 durable=false");
+
+ try:
+ session.queue_delete(queue="qdaq4")
+ self.fail("ACL should deny queue delete request for qdaq4");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.queue_declare(queue="qdaq5", exclusive=False)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request with name=qdaq5 exclusive=false");
+
+ try:
+ session.queue_delete(queue="qdaq5")
+ self.fail("ACL should deny queue delete request for qdaq5");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ queue_options = {}
+ queue_options["qpid.policy_type"] = "reject"
+ session.queue_declare(queue="qdaq6", arguments=queue_options)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request for qdaq6 with policytype=reject");
+
+ try:
+ session.queue_delete(queue="qdaq6")
+ self.fail("ACL should deny queue delete request for qdaq6");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.queue_declare(queue="qdaq7", auto_delete=True)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request with name=qdaq7 autodelete=true");
+
+ try:
+ session.queue_delete(queue="qdaq7")
+ self.fail("ACL should deny queue delete request for qdaq7");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.queue_declare(queue="qdaq8", auto_delete=False)
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request with name=qdaq8 autodelete=false");
+
+ try:
+ session.queue_delete(queue="qdaq8")
+ self.fail("ACL should deny queue delete request for qdaq8");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.exchange_declare(exchange='qdae9', type='direct')
+ except qpid.session.SessionException, e:
+ self.fail("ACL should allow exchange create request with name=qdae9");
+
+ try:
+ session.queue_declare(queue="qdaq9", alternate_exchange="qdae9")
+ except qpid.session.SessionException, e:
+ if (403 == e.args[0].error_code):
+ self.fail("ACL should allow queue create request with name=qdaq9 alternate=qdaq9a");
+
+ try:
+ session.queue_delete(queue="qdaq9")
+ self.fail("ACL should deny queue delete request for qdaq9");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+
+ def test_exchange_delete_with_properties(self):
+ """
+ Test cases for exchange delete with properties
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl allow bob@QPID access exchange\n')
+ aclf.write('acl allow bob@QPID create exchange\n')
+ aclf.write('acl deny bob@QPID delete exchange name=edae1 durable=true\n')
+ aclf.write('acl deny bob@QPID delete exchange name=edae2 alternate=edae2a\n')
+ aclf.write('acl deny bob@QPID delete exchange type=direct\n')
+ aclf.write('acl allow bob@QPID delete exchange type=headers\n')
+ aclf.write('acl allow anonymous all all\n')
+ aclf.write('acl deny all all')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('bob','bob')
+
+ try:
+ session.exchange_declare(exchange='edae1', type='direct', durable=True)
+ except qpid.session.SessionException, e:
+ self.fail("ACL should allow exchange create request with name=edae1");
+
+ try:
+ session.exchange_delete(exchange="edae1")
+ self.fail("ACL should deny exchange delete request for edae1");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.exchange_declare(exchange='edae2a', type='direct')
+ except qpid.session.SessionException, e:
+ self.fail("ACL should allow exchange create request with name=edae2a");
+
+ try:
+ session.exchange_declare(exchange='edae2', type='direct', alternate_exchange='edae2a')
+ except qpid.session.SessionException, e:
+ self.fail("ACL should allow exchange create request with name=edae2");
+
+ try:
+ session.exchange_delete(exchange="edae2")
+ self.fail("ACL should deny exchange delete request for edae2");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.exchange_declare(exchange='edae3d', type='direct')
+ except qpid.session.SessionException, e:
+ self.fail("ACL should allow exchange create request with name=edae3d");
+
+ try:
+ session.exchange_declare(exchange='edae3h', type='headers')
+ except qpid.session.SessionException, e:
+ self.fail("ACL should allow exchange create request with name=eda3h");
+
+ try:
+ session.exchange_delete(exchange="edae3d")
+ self.fail("ACL should deny exchange delete request for edae3d");
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ session = self.get_session('bob','bob')
+
+ try:
+ session.exchange_delete(exchange="edae3h")
+ except qpid.session.SessionException, e:
+ self.assertEqual(403,e.args[0].error_code)
+ self.fail("ACL should allow exchange delete request for edae3h");
+
+ #=====================================
+ # 'create connection' tests
+ #=====================================
+# def test_connect_mode_file_rejects_two_defaults(self):
+# """
+# Should reject a file with two connect mode statements
+# """
+# aclf = self.get_acl_file()
+# aclf.write('acl allow all create connection host=all\n')
+# aclf.write('acl allow all create connection host=all\n')
+# aclf.close()
+#
+# result = self.reload_acl()
+# if (result):
+# pass
+# else:
+# self.fail(result)
+
+ def test_connect_mode_accepts_host_spec_formats(self):
+ """
+ Should accept host specs of various forms
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl allow bob@QPID create connection host=all\n')
+ aclf.write('acl allow bob@QPID create connection host=1.1.1.1\n')
+ aclf.write('acl allow bob@QPID create connection host=1.1.1.1,2.2.2.2\n')
+ aclf.write('acl allow bob@QPID create connection host=localhost\n')
+ aclf.write('acl allow all all\n')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ def test_connect_mode_allow_all_mode(self):
+ """
+ Should allow one 'all', 'all'
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl allow all create connection host=all\n')
+ aclf.write('acl allow all all\n')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('bob','bob')
+
+
+ def test_connect_mode_allow_all_localhost(self):
+ """
+ Should allow 'all' 'localhost'
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl allow all create connection host=localhost\n')
+ aclf.write('acl deny all create connection host=all\n')
+ aclf.write('acl allow all all\n')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('bob','bob')
+
+
+ def test_connect_mode_global_deny(self):
+ """
+ Should allow 'all' 'localhost'
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl allow all create connection host=localhost\n')
+ aclf.write('acl deny all create connection host=all\n')
+ aclf.write('acl allow all all\n')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('bob','bob')
+
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"127.0.0.1"}, "allow")
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"127.0.0.2"}, "deny")
+
+
+ def test_connect_mode_global_range(self):
+ """
+ Should allow 'all' 'localhost'
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl allow all create connection host=10.0.0.0,10.255.255.255\n')
+ aclf.write('acl allow all create connection host=localhost\n')
+ aclf.write('acl deny all create connection host=all\n')
+ aclf.write('acl allow all all\n')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('bob','bob')
+
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"0.0.0.0"}, "deny")
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"9.255.255.255"}, "deny")
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"10.0.0.0"}, "allow")
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"10.255.255.255"}, "allow")
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"11.0.0.0"}, "deny")
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"255.255.255.255"},"deny")
+
+
+ def test_connect_mode_nested_ranges(self):
+ """
+ Tests nested ranges for single user
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl deny-log bob@QPID create connection host=10.0.1.0,10.0.1.255\n')
+ aclf.write('acl allow-log bob@QPID create connection host=10.0.0.0,10.255.255.255\n')
+ aclf.write('acl deny-log bob@QPID create connection host=all\n')
+ aclf.write('acl allow all create connection host=localhost\n')
+ aclf.write('acl allow all all\n')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('bob','bob')
+
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"0.0.0.0"}, "deny-log")
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"9.255.255.255"}, "deny-log")
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"10.0.0.0"}, "allow-log")
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"10.0.0.255"}, "allow-log")
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"10.0.1.0"}, "deny-log")
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"10.0.1.255"}, "deny-log")
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"10.0.2.0"}, "allow-log")
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"10.255.255.255"}, "allow-log")
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"11.0.0.0"}, "deny-log")
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"255.255.255.255"},"deny-log")
+
+
+ def test_connect_mode_user_ranges(self):
+ """
+ Two user ranges should not interfere with each other
+ """
+ aclf = self.get_acl_file()
+ aclf.write('acl allow-log bob@QPID create connection host=10.0.0.0,10.255.255.255\n')
+ aclf.write('acl deny-log bob@QPID create connection host=all\n')
+ aclf.write('acl allow-log cat@QPID create connection host=192.168.0.0,192.168.255.255\n')
+ aclf.write('acl deny-log cat@QPID create connection host=all\n')
+ aclf.write('acl allow all create connection host=localhost\n')
+ aclf.write('acl allow all all\n')
+ aclf.close()
+
+ result = self.reload_acl()
+ if (result):
+ self.fail(result)
+
+ session = self.get_session('bob','bob')
+
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"0.0.0.0"}, "deny-log")
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"9.255.255.255"}, "deny-log")
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"10.0.0.0"}, "allow-log")
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"10.255.255.255"}, "allow-log")
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"11.0.0.0"}, "deny-log")
+ self.Lookup("bob@QPID", "create", "connection", "", {"host":"255.255.255.255"},"deny-log")
+ self.Lookup("cat@QPID", "create", "connection", "", {"host":"0.0.0.0"}, "deny-log")
+ self.Lookup("cat@QPID", "create", "connection", "", {"host":"192.167.255.255"},"deny-log")
+ self.Lookup("cat@QPID", "create", "connection", "", {"host":"192.168.0.0"}, "allow-log")
+ self.Lookup("cat@QPID", "create", "connection", "", {"host":"192.168.255.255"},"allow-log")
+ self.Lookup("cat@QPID", "create", "connection", "", {"host":"192.169.0.0"}, "deny-log")
+ self.Lookup("cat@QPID", "create", "connection", "", {"host":"255.255.255.255"},"deny-log")
+
+
+class BrokerAdmin:
+ def __init__(self, broker, username=None, password=None):
+ self.connection = qpid.messaging.Connection(broker)
+ if username:
+ self.connection.username = username
+ self.connection.password = password
+ self.connection.sasl_mechanisms = "PLAIN"
+ self.connection.open()
+ self.session = self.connection.session()
+ self.sender = self.session.sender("qmf.default.direct/broker")
+ self.reply_to = "responses-#; {create:always}"
+ self.receiver = self.session.receiver(self.reply_to)
+
+ def invoke(self, method, arguments):
+ content = {
+ "_object_id": {"_object_name": "org.apache.qpid.broker:broker:amqp-broker"},
+ "_method_name": method,
+ "_arguments": arguments
+ }
+ request = qpid.messaging.Message(reply_to=self.reply_to, content=content)
+ request.properties["x-amqp-0-10.app-id"] = "qmf2"
+ request.properties["qmf.opcode"] = "_method_request"
+ self.sender.send(request)
+ response = self.receiver.fetch()
+ self.session.acknowledge()
+ if response.properties['x-amqp-0-10.app-id'] == 'qmf2':
+ if response.properties['qmf.opcode'] == '_method_response':
+ return response.content['_arguments']
+ elif response.properties['qmf.opcode'] == '_exception':
+ raise Exception(response.content['_values'])
+ else: raise Exception("Invalid response received, unexpected opcode: %s" % response.properties['qmf.opcode'])
+ else: raise Exception("Invalid response received, not a qmfv2 method: %s" % response.properties['x-amqp-0-10.app-id'])
+
+ def query(self, object_name=None, class_name=None):
+ content = { "_what": "OBJECT" }
+ if object_name is not None:
+ content["_object_id"] = {"_object_name": object_name }
+ if class_name is not None:
+ content["_schema_id"] = {"_class_name": class_name }
+ request = qpid.messaging.Message(reply_to=self.reply_to, content=content)
+ request.properties["x-amqp-0-10.app-id"] = "qmf2"
+ request.properties["qmf.opcode"] = "_query_request"
+ self.sender.send(request)
+ response = self.receiver.fetch()
+ self.session.acknowledge()
+ if response.properties['x-amqp-0-10.app-id'] == 'qmf2':
+ if response.properties['qmf.opcode'] == '_query_response':
+ return
+ elif response.properties['qmf.opcode'] == '_exception':
+ raise Exception(response.content['_values'])
+ else: raise Exception("Invalid response received, unexpected opcode: %s" % response.properties['qmf.opcode'])
+ else: raise Exception("Invalid response received, not a qmfv2 method: %s" % response.properties['x-amqp-0-10.app-id'])
+
+ def create_exchange(self, name, exchange_type=None, options={}):
+ properties = options
+ if exchange_type: properties["exchange_type"] = exchange_type
+ self.invoke("create", {"type": "exchange", "name":name, "properties":properties})
+
+ def create_queue(self, name, properties={}):
+ self.invoke("create", {"type": "queue", "name":name, "properties":properties})
+
+ def delete_exchange(self, name):
+ self.invoke("delete", {"type": "exchange", "name":name})
+
+ def delete_queue(self, name):
+ self.invoke("delete", {"type": "queue", "name":name})
+
+ def get_timestamp_cfg(self):
+ return self.invoke("getTimestampConfig", {})
+
+ def set_timestamp_cfg(self, receive):
+ return self.invoke("getTimestampConfig", {"receive":receive})
diff --git a/qpid/cpp/src/tests/ais_test.cpp b/qpid/cpp/src/tests/ais_test.cpp
new file mode 100644
index 0000000000..00c61242e4
--- /dev/null
+++ b/qpid/cpp/src/tests/ais_test.cpp
@@ -0,0 +1,23 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// Defines test_main function to link with actual unit test code.
+#define BOOST_AUTO_TEST_MAIN // Boost 1.33
+#define BOOST_TEST_MAIN
+#include "unit_test.h"
+
diff --git a/qpid/cpp/src/tests/allhosts b/qpid/cpp/src/tests/allhosts
new file mode 100755
index 0000000000..07bc04fff5
--- /dev/null
+++ b/qpid/cpp/src/tests/allhosts
@@ -0,0 +1,79 @@
+#!/usr/bin/env 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.
+#
+
+usage() {
+ echo "Usage: $0 [options] command.
+Run a command on each host in \$HOSTS.
+Options:
+ -l USER passed to ssh - run as USER.
+ -t passed to ssh - create a terminal.
+ -b run in background, wait for commands to complete.
+ -d run in background, don't wait for commands to complete.
+ -s SECONDS sleep between starting commands.
+ -q don't print banner lines for each host.
+ -o SUFFIX log output of each command to <host>.SUFFIX
+ -X passed to ssh - forward X connection.
+"
+ exit 1
+}
+
+while getopts "tl:bs:dqo:X" opt; do
+ case $opt in
+ l) SSHOPTS="-l$OPTARG $SSHOPTS" ;;
+ t) SSHOPTS="-t $SSHOPTS" ;;
+ b) BACKGROUND=1 ;;
+ d) BACKGROUND=1; DISOWN=1 ;;
+ s) SLEEP="sleep $OPTARG" ;;
+ q) NOBANNER=1 ;;
+ o) SUFFIX=$OPTARG ;;
+ X) SSHOPTS="-X $SSHOPTS" ;;
+ *) usage;;
+ esac
+done
+shift `expr $OPTIND - 1`
+test "$*" || usage;
+
+OK_FILE=`mktemp` # Will be deleted if anything goes wrong.
+trap "rm -f $OK_FILE" EXIT
+
+do_ssh() {
+ h=$1; shift
+ if test $SUFFIX ; then ssh $SSHOPTS $h "$@" &> $h.$SUFFIX
+ else ssh $SSHOPTS $h "$@"; fi || rm -rf $OK_FILE;
+}
+
+for h in $HOSTS ; do
+ test "$NOBANNER" || echo "== ssh $SSHOPTS $h $@ =="
+ if [ "$BACKGROUND" = 1 ]; then
+ do_ssh $h "$@" &
+ CHILDREN="$! $CHILDREN"
+ else
+ do_ssh $h "$@"
+ fi
+ $SLEEP
+done
+
+if [ "$DISOWN" = 1 ]; then
+ for c in $CHILDREN; do disown $c; done
+else
+ wait
+fi
+
+test -f $OK_FILE
diff --git a/qpid/cpp/src/tests/assertions.py b/qpid/cpp/src/tests/assertions.py
new file mode 100644
index 0000000000..930afd124d
--- /dev/null
+++ b/qpid/cpp/src/tests/assertions.py
@@ -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.
+#
+
+from qpid.tests.messaging.implementation import *
+from qpid.tests.messaging import VersionTest
+
+class AssertionTests (VersionTest):
+ """
+ Tests for assertions with qpidd
+ """
+ def test_queues_alternate_exchange1(self):
+ name = str(uuid4())
+ self.ssn.sender("%s; {create:always, node:{properties:{alternate-exchange:amq.fanout}}}" % name)
+ self.ssn.sender("%s; {assert:always, node:{properties:{alternate-exchange:amq.fanout}}}" % name)
+ try:
+ self.ssn.sender("%s; {assert:always, node:{properties:{alternate-exchange:amq.topic}}}" % name)
+ assert False, "Expected assertion to fail on alternate-exchange"
+ except AssertionFailed: None
+ except MessagingError: None
+
+ def test_queues_alternate_exchange2(self):
+ name = str(uuid4())
+ self.ssn.sender("%s; {create:always, node:{x-declare:{alternate-exchange:amq.fanout}}}" % name)
+ self.ssn.sender("%s; {assert:always, node:{x-declare:{alternate-exchange:amq.fanout}}}" % name)
+ try:
+ self.ssn.sender("%s; {assert:always, node:{x-declare:{alternate-exchange:amq.topic}}}" % name)
+ assert False, "Expected assertion to fail on alternate-exchange"
+ except AssertionFailed: None
+ except MessagingError: None
+
+ def test_queue_type(self):
+ name = str(uuid4())
+ self.ssn.sender("%s; {create:always}" % name)
+ self.ssn.sender("%s; {assert:always, node:{type:queue}}" % name)
+ try:
+ self.ssn.sender("%s; {assert:always, node:{type:topic}}" % name)
+ assert False, "Expected assertion to fail on type"
+ except AssertionFailed: None
+ except MessagingError: None
+
+ def test_queue_not_durable(self):
+ name = str(uuid4())
+ self.ssn.sender("%s; {create:always}" % name)
+ self.ssn.sender("%s; {assert:always, node:{durable:False}}" % name)
+ try:
+ self.ssn.sender("%s; {assert:always, node:{durable:True}}" % name)
+ assert False, "Expected assertion to fail on durability"
+ except AssertionFailed: None
+ except MessagingError: None
+
+ def test_queue_is_durable(self):
+ name = str(uuid4())
+ self.ssn.sender("%s; {create:always, node:{durable:True}}" % name)
+ self.ssn.sender("%s; {assert:always, node:{durable:True}}" % name)
+
+ def test_queue_is_autodelete(self):
+ name = str(uuid4())
+ self.ssn.sender("%s; {create:always, node:{x-declare:{auto-delete:True}}}" % name)
+ self.ssn.sender("%s; {assert:always, node:{x-declare:{auto-delete:True}}}" % name)
+ try:
+ self.ssn.sender("%s; {assert:always, node:{x-declare:{auto-delete:False}}}" % name)
+ assert False, "Expected assertion to fail for auto-delete"
+ except AssertionFailed: None
+ except MessagingError: None
+
+ def do_test_queue_options(self, name):
+ self.ssn.sender("%s; {create:always, node:{x-declare:{arguments:{foo:bar,'qpid.last_value_queue_key':abc}}}}" % name)
+ self.ssn.sender("%s; {assert:always, node:{x-declare:{arguments:{'qpid.last_value_queue_key':abc}}}}" % name)
+ try:
+ self.ssn.sender("%s; {assert:always, node:{x-declare:{arguments:{foo:bar}}}}" % name)
+ assert False, "Expected assertion to fail on unrecognised option"
+ except AssertionFailed: None
+ except MessagingError: None
+ try:
+ self.ssn.sender("%s; {assert:always, node:{x-declare:{arguments:{'qpid.max_count':10}}}}" % name)
+ assert False, "Expected assertion to fail on unspecified option"
+ except AssertionFailed: None
+ except MessagingError: None
+ try:
+ self.ssn.sender("%s; {assert:always, node:{x-declare:{arguments:{'qpid.last_value_key':xyz}}}}" % name)
+ assert False, "Expected assertion to fail on option with different value"
+ except AssertionFailed: None
+ except MessagingError: None
+
+ def test_queue_options(self):
+ self.do_test_queue_options(str(uuid4()))
+
+ def test_queue_options_from_0_10(self):
+ name = str(uuid4())
+ self.do_test_queue_options(name)
+ ssn_0_10 = self.create_connection("amqp0-10", True).session()
+ ssn_0_10.sender("%s; {assert:always, node:{x-declare:{arguments:{'qpid.last_value_queue_key':abc}}}}" % name)
+ try:
+ ssn_0_10.sender("%s; {assert:always, node:{x-declare:{arguments:{'qpid.last_value_key':xyz}}}}" % name)
+ assert False, "Expected assertion to fail on option with different value"
+ except AssertionFailed: None
+ except MessagingError: None
+
+
+ def test_exchanges_alternate_exchange1(self):
+ name = str(uuid4())
+ self.ssn.sender("%s; {create:always, node:{type:topic, properties:{alternate-exchange:amq.fanout}}}" % name)
+ self.ssn.sender("%s; {assert:always, node:{type:topic, properties:{alternate-exchange:amq.fanout}}}" % name)
+ try:
+ self.ssn.sender("%s; {assert:always, node:{properties:{alternate-exchange:amq.topic}}}" % name)
+ assert False, "Expected assertion to fail on alternate-exchange"
+ except AssertionFailed: None
+ except MessagingError: None
+
+ def test_exchanges_alternate_exchange2(self):
+ name = str(uuid4())
+ self.ssn.sender("%s; {create:always, node:{type:topic, x-declare:{alternate-exchange:amq.fanout}}}" % name)
+ self.ssn.sender("%s; {assert:always, node:{type:topic, x-declare:{alternate-exchange:amq.fanout}}}" % name)
+ try:
+ self.ssn.sender("%s; {assert:always, node:{x-declare:{alternate-exchange:amq.topic}}}" % name)
+ assert False, "Expected assertion to fail on alternate-exchange"
+ except AssertionFailed: None
+ except MessagingError: None
+
+ def test_exchange_type(self):
+ name = str(uuid4())
+ self.ssn.sender("%s; {create:always, node:{type:topic}}" % name)
+ self.ssn.sender("%s; {assert:always, node:{type:topic}}" % name)
+ try:
+ self.ssn.sender("%s; {assert:always, node:{type:queue}}" % name)
+ assert False, "Expected assertion to fail on type"
+ except AssertionFailed: None
+ except MessagingError: None
+
+ def test_exchange_durability(self):
+ name = str(uuid4())
+ self.ssn.sender("%s; {create:always, node:{type:topic}}" % name)
+ self.ssn.sender("%s; {assert:always, node:{durable:False}}" % name)
+ try:
+ self.ssn.sender("%s; {assert:always, node:{durable:True}}" % name)
+ assert False, "Expected assertion to fail on durability"
+ except AssertionFailed: None
+ except MessagingError: None
+
+ def test_exchange_is_autodelete(self):
+ name = str(uuid4())
+ self.ssn.sender("%s; {create:always, node:{type:topic, x-declare:{auto-delete:True}}}" % name)
+ self.ssn.sender("%s; {assert:always, node:{x-declare:{auto-delete:True}}}" % name)
+ try:
+ self.ssn.sender("%s; {assert:always, node:{x-declare:{auto-delete:False}}}" % name)
+ assert False, "Expected assertion to fail for auto-delete"
+ except AssertionFailed: None
+ except MessagingError: None
+
+ def test_exchange_options(self):
+ name = str(uuid4())
+ self.ssn.sender("%s; {create:always, node:{type:topic, x-declare:{arguments:{foo:bar,'qpid.msg_sequence':True}}}}" % name)
+ self.ssn.sender("%s; {assert:always, node:{x-declare:{arguments:{'qpid.msg_sequence':True}}}}" % name)
+ try:
+ self.ssn.sender("%s; {assert:always, node:{x-declare:{arguments:{foo:bar}}}}" % name)
+ assert False, "Expected assertion to fail on unrecognised option"
+ except AssertionFailed: None
+ except MessagingError: None
+ try:
+ self.ssn.sender("%s; {assert:always, node:{x-declare:{arguments:{'qpid.ive':True}}}}" % name)
+ assert False, "Expected assertion to fail on unspecified option"
+ except AssertionFailed: None
+ except MessagingError: None
+
+ def test_queue_autodelete_timeout(self):
+ name = str(uuid4())
+ # create subscription queue with 0-10 to be sure of name
+ ssn_0_10 = self.create_connection("amqp0-10", True).session()
+ ssn_0_10.receiver("amq.direct; {link:{name:%s,timeout:30}}" % name)
+ self.ssn.sender("%s; {assert:always, node:{x-declare:{arguments: {qpid.auto_delete_timeout: 30}}}}" % name)
+ ssn_0_10_other = self.create_connection("amqp0-10", True).session()
+ ssn_0_10_other.sender("%s; {assert:always, node:{x-declare:{arguments: {qpid.auto_delete_timeout: 30}}}}" % name)
+ try:
+ self.ssn.sender("%s; {assert:always, node:{x-declare:{arguments: {qpid.auto_delete_timeout: 60}}}}" % name)
+ ssn_0_10_other.sender("%s; {assert:always, node:{x-declare:{arguments: {qpid.auto_delete_timeout: 60}}}}" % name)
+ assert False, "Expected assertion to fail for auto_delete_timeout"
+ except AssertionFailed: None
+ except MessagingError: None
diff --git a/qpid/cpp/src/tests/background.ps1 b/qpid/cpp/src/tests/background.ps1
new file mode 100644
index 0000000000..36e9e4e6e9
--- /dev/null
+++ b/qpid/cpp/src/tests/background.ps1
@@ -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.
+#
+
+# Run a PowerShell scriptblock in a background process.
+param(
+ [scriptblock] $script # scriptblock to run
+)
+
+# break out of the script on any errors
+trap { break }
+
+# In order to pass a scriptblock to another powershell instance, it must
+# be encoded to pass through the command line.
+$encodedScript = [convert]::ToBase64String(
+ [Text.Encoding]::Unicode.GetBytes([string] $script))
+
+#$p = new-object System.Diagnostics.Process
+$si = new-object System.Diagnostics.ProcessStartInfo
+$si.WorkingDirectory = $pwd
+$si.FileName = (get-command powershell.exe).Definition
+$si.Arguments = "-encodedCommand $encodedScript"
+
+###### debugging setup
+#$si.CreateNoWindow = $true
+# UseShellExecute false required for RedirectStandard(Error, Output)
+#$si.UseShellExecute = $false
+#$si.RedirectStandardError = $true
+#$si.RedirectStandardOutput = $true
+######
+$si.UseShellExecute = $true
+
+##### Debugging, instead of the plain Start() above.
+#$output = [io.File]::AppendText("start.out")
+#$error = [io.File]::AppendText("start.err")
+$p = [System.Diagnostics.Process]::Start($si)
+#$output.WriteLine($p.StandardOutput.ReadToEnd())
+#$error.WriteLine($p.StandardError.ReadToEnd())
+#$p.WaitForExit()
+#$output.Close()
diff --git a/qpid/cpp/src/tests/brokertest.py b/qpid/cpp/src/tests/brokertest.py
new file mode 100644
index 0000000000..6fae88092b
--- /dev/null
+++ b/qpid/cpp/src/tests/brokertest.py
@@ -0,0 +1,752 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Support library for tests that start multiple brokers, e.g. HA or federation
+
+import os, signal, string, tempfile, subprocess, socket, threading, time, imp, re
+import qpid, traceback, signal
+from qpid import connection, util
+from qpid.compat import format_exc
+from unittest import TestCase
+from copy import copy
+from threading import Thread, Lock, Condition
+from logging import getLogger
+from qpidtoollibs import BrokerAgent
+
+# NOTE: Always import native client qpid.messaging, import swigged client
+# qpid_messaging if possible. qpid_messaing is set to None if not available.
+#
+# qm is set to qpid_messaging if it is available, qpid.messaging if not.
+# Use qm.X to specify names from the default messaging module.
+#
+# Set environment variable QPID_PY_NO_SWIG=1 to prevent qpid_messaging from loading.
+#
+# BrokerTest can be configured to determine which protocol is used by default:
+#
+# -DPROTOCOL="amqpX": Use protocol "amqpX". Defaults to amqp1.0 if available.
+#
+# The configured defaults can be over-ridden on BrokerTest.connect and some
+# other methods by specifying native=True|False and protocol="amqpX"
+#
+
+import qpid.messaging
+qm = qpid.messaging
+qpid_messaging = None
+
+def env_has_log_config():
+ """True if there are qpid log configuratoin settings in the environment."""
+ return "QPID_LOG_ENABLE" in os.environ or "QPID_TRACE" in os.environ
+
+if not os.environ.get("QPID_PY_NO_SWIG"):
+ try:
+ import qpid_messaging
+ from qpid.datatypes import uuid4
+ qm = qpid_messaging
+ # Silence warnings from swigged messaging library unless enabled in environment.
+ if not env_has_log_config():
+ qm.Logger.configure(["--log-enable=error"])
+ except ImportError:
+ print "Cannot load python SWIG bindings, falling back to native qpid.messaging."
+
+log = getLogger("brokertest")
+
+# Values for expected outcome of process at end of test
+EXPECT_EXIT_OK=1 # Expect to exit with 0 status before end of test.
+EXPECT_EXIT_FAIL=2 # Expect to exit with non-0 status before end of test.
+EXPECT_RUNNING=3 # Expect to still be running at end of test
+EXPECT_UNKNOWN=4 # No expectation, don't check exit status.
+
+def find_exe(program):
+ """Find an executable in the system PATH"""
+ def is_exe(fpath):
+ return os.path.isfile(fpath) and os.access(fpath, os.X_OK)
+ mydir, name = os.path.split(program)
+ if mydir:
+ if is_exe(program): return program
+ else:
+ for path in os.environ["PATH"].split(os.pathsep):
+ exe_file = os.path.join(path, program)
+ if is_exe(exe_file): return exe_file
+ return None
+
+def is_running(pid):
+ try:
+ os.kill(pid, 0)
+ return True
+ except:
+ return False
+
+class BadProcessStatus(Exception):
+ pass
+
+def error_line(filename, n=1):
+ """Get the last n line(s) of filename for error messages"""
+ result = []
+ try:
+ f = open(filename)
+ try:
+ for l in f:
+ if len(result) == n: result.pop(0)
+ result.append(" "+l)
+ finally:
+ f.close()
+ except: return ""
+ return ":\n" + "".join(result)
+
+def retry(function, timeout=10, delay=.001, max_delay=1):
+ """Call function until it returns a true value or timeout expires.
+ Double the delay for each retry up to max_delay.
+ Returns what function returns if true, None if timeout expires."""
+ deadline = time.time() + timeout
+ ret = None
+ while True:
+ ret = function()
+ if ret: return ret
+ remaining = deadline - time.time()
+ if remaining <= 0: return False
+ delay = min(delay, remaining)
+ time.sleep(delay)
+ delay = min(delay*2, max_delay)
+
+class AtomicCounter:
+ def __init__(self):
+ self.count = 0
+ self.lock = Lock()
+
+ def next(self):
+ self.lock.acquire();
+ ret = self.count
+ self.count += 1
+ self.lock.release();
+ return ret
+
+_popen_id = AtomicCounter() # Popen identifier for use in output file names.
+
+# Constants for file descriptor arguments to Popen
+FILE = "FILE" # Write to file named after process
+from subprocess import PIPE, STDOUT
+
+class Popen(subprocess.Popen):
+ """
+ Can set and verify expectation of process status at end of test.
+ Dumps command line, stdout, stderr to data dir for debugging.
+ """
+
+ def __init__(self, cmd, expect=EXPECT_EXIT_OK, stdin=None, stdout=FILE, stderr=FILE):
+ """Run cmd (should be a list of program and arguments)
+ expect - if set verify expectation at end of test.
+ stdout, stderr - can have the same values as for subprocess.Popen as well as
+ FILE (the default) which means write to a file named after the process.
+ stdin - like subprocess.Popen but defauts to PIPE
+ """
+ self._clean = False
+ self._clean_lock = Lock()
+ assert find_exe(cmd[0]), "executable not found: "+cmd[0]
+ if type(cmd) is type(""): cmd = [cmd] # Make it a list.
+ self.cmd = [ str(x) for x in cmd ]
+ self.expect = expect
+ self.id = _popen_id.next()
+ self.pname = "%s-%d" % (os.path.split(self.cmd[0])[1], self.id)
+ if stdout == FILE: stdout = open(self.outfile("out"), "w")
+ if stderr == FILE: stderr = open(self.outfile("err"), "w")
+ subprocess.Popen.__init__(self, self.cmd, bufsize=0, executable=None,
+ stdin=stdin, stdout=stdout, stderr=stderr)
+ f = open(self.outfile("cmd"), "w")
+ try: f.write("%s\n%d"%(self.cmd_str(), self.pid))
+ finally: f.close()
+ log.debug("Started process %s: %s" % (self.pname, " ".join(self.cmd)))
+
+ def __repr__(self): return "Popen<%s>"%(self.pname)
+
+ def outfile(self, ext): return "%s.%s" % (self.pname, ext)
+
+ def unexpected(self,msg):
+ err = error_line(self.outfile("err")) or error_line(self.outfile("out"))
+ raise BadProcessStatus("%s %s%s" % (self.pname, msg, err))
+
+ def teardown(self): # Clean up at end of test.
+ if self.expect == EXPECT_UNKNOWN:
+ try: self.kill() # Just make sure its dead
+ except: pass
+ elif self.expect == EXPECT_RUNNING:
+ if self.poll() != None:
+ self.unexpected("expected running, exit code %d" % self.returncode)
+ else:
+ try:
+ self.kill()
+ except Exception,e:
+ self.unexpected("exception from kill: %s" % str(e))
+ else:
+ retry(lambda: self.poll() is not None)
+ if self.returncode is None: # Still haven't stopped
+ self.kill()
+ self.unexpected("still running")
+ elif self.expect == EXPECT_EXIT_OK and self.returncode != 0:
+ self.unexpected("exit code %d" % self.returncode)
+ elif self.expect == EXPECT_EXIT_FAIL and self.returncode == 0:
+ self.unexpected("expected error")
+ self.wait()
+
+
+ def communicate(self, input=None):
+ ret = subprocess.Popen.communicate(self, input)
+ self._cleanup()
+ return ret
+
+ def is_running(self): return self.poll() is None
+
+ def assert_running(self):
+ if not self.is_running(): self.unexpected("Exit code %d" % self.returncode)
+
+ def wait(self):
+ ret = subprocess.Popen.wait(self)
+ self._cleanup()
+ return ret
+
+ def assert_exit_ok(self):
+ if self.wait() != 0: self.unexpected("Exit code %d" % self.returncode)
+
+ def terminate(self):
+ try: subprocess.Popen.terminate(self)
+ except AttributeError: # No terminate method
+ try:
+ os.kill( self.pid , signal.SIGTERM)
+ except AttributeError: # no os.kill, using taskkill.. (Windows only)
+ os.popen('TASKKILL /PID ' +str(self.pid) + ' /F')
+ self.wait()
+
+ def kill(self):
+ # Set to EXPECT_UNKNOWN, EXPECT_EXIT_FAIL creates a race condition
+ # if the process exits normally concurrent with the call to kill.
+ self.expect = EXPECT_UNKNOWN
+ try: subprocess.Popen.kill(self)
+ except AttributeError: # No terminate method
+ try:
+ os.kill( self.pid , signal.SIGKILL)
+ except AttributeError: # no os.kill, using taskkill.. (Windows only)
+ os.popen('TASKKILL /PID ' +str(self.pid) + ' /F')
+ self.wait()
+
+ def _cleanup(self):
+ """Clean up after a dead process"""
+ self._clean_lock.acquire()
+ if not self._clean:
+ self._clean = True
+ try: self.stdin.close()
+ except: pass
+ try: self.stdout.close()
+ except: pass
+ try: self.stderr.close()
+ except: pass
+ self._clean_lock.release()
+
+ def cmd_str(self): return " ".join([str(s) for s in self.cmd])
+
+
+def checkenv(name):
+ value = os.getenv(name)
+ if not value: raise Exception("Environment variable %s is not set" % name)
+ return value
+
+def find_in_file(str, filename):
+ if not os.path.exists(filename): return False
+ f = open(filename)
+ try: return str in f.read()
+ finally: f.close()
+
+class Broker(Popen):
+ "A broker process. Takes care of start, stop and logging."
+ _broker_count = 0
+ _log_count = 0
+
+ def __repr__(self): return "<Broker:%s:%d>"%(self.log, self.port())
+
+ def get_log(self):
+ return os.path.abspath(self.log)
+
+ def __init__(self, test, args=[], test_store=False, name=None, expect=EXPECT_RUNNING, port=0, wait=None, show_cmd=False):
+ """Start a broker daemon. name determines the data-dir and log
+ file names."""
+
+ self.test = test
+ self._port=port
+ args = copy(args)
+ if BrokerTest.amqp_lib: args += ["--load-module", BrokerTest.amqp_lib]
+ if BrokerTest.store_lib and not test_store:
+ args += ['--load-module', BrokerTest.store_lib]
+ if BrokerTest.sql_store_lib:
+ args += ['--load-module', BrokerTest.sql_store_lib]
+ args += ['--catalog', BrokerTest.sql_catalog]
+ if BrokerTest.sql_clfs_store_lib:
+ args += ['--load-module', BrokerTest.sql_clfs_store_lib]
+ args += ['--catalog', BrokerTest.sql_catalog]
+ cmd = [BrokerTest.qpidd_exec, "--port", port, "--interface", "127.0.0.1", "--no-module-dir"] + args
+ if not "--auth" in args: cmd.append("--auth=no")
+ if wait != None:
+ cmd += ["--wait", str(wait)]
+ if name: self.name = name
+ else:
+ self.name = "broker%d" % Broker._broker_count
+ Broker._broker_count += 1
+
+ self.log = "%03d:%s.log" % (Broker._log_count, self.name)
+ self.store_log = "%03d:%s.store.log" % (Broker._log_count, self.name)
+ Broker._log_count += 1
+
+ cmd += ["--log-to-file", self.log]
+ cmd += ["--log-to-stderr=no"]
+
+ # Add default --log-enable arguments unless args already has --log arguments.
+ if not env_has_log_config() and not [l for l in args if l.startswith("--log")]:
+ args += ["--log-enable=info+"]
+
+ if test_store: cmd += ["--load-module", BrokerTest.test_store_lib,
+ "--test-store-events", self.store_log]
+
+ self.datadir = os.path.abspath(self.name)
+ cmd += ["--data-dir", self.datadir]
+ if show_cmd: print cmd
+ Popen.__init__(self, cmd, expect, stdout=PIPE)
+ test.teardown_add(self)
+ self._host = "127.0.0.1"
+ self._agent = None
+
+ log.debug("Started broker %s" % self)
+
+ def host(self): return self._host
+
+ def port(self):
+ # Read port from broker process stdout if not already read.
+ if (self._port == 0):
+ try: self._port = int(self.stdout.readline())
+ except ValueError, e:
+ raise Exception("Can't get port for broker %s (%s)%s: %s" %
+ (self.name, self.pname, error_line(self.log,5), e))
+ return self._port
+
+ def unexpected(self,msg):
+ raise BadProcessStatus("%s: %s (%s)" % (msg, self.name, self.pname))
+
+ def connect(self, timeout=5, native=False, **kwargs):
+ """New API connection to the broker.
+ @param native if True force use of the native qpid.messaging client
+ even if swig client is available.
+ """
+ if native: connection_class = qpid.messaging.Connection
+ else:
+ connection_class = qm.Connection
+ if (self.test.protocol and qm == qpid_messaging):
+ kwargs.setdefault("protocol", self.test.protocol)
+ return connection_class.establish(self.host_port(), timeout=timeout, **kwargs)
+
+ @property
+ def agent(self, **kwargs):
+ """Return a BrokerAgent for this broker"""
+ if not self._agent: self._agent = BrokerAgent(self.connect(**kwargs))
+ return self._agent
+
+
+ def declare_queue(self, queue):
+ self.agent.addQueue(queue)
+
+ def _prep_sender(self, queue, durable, xprops):
+ s = queue + "; {create:always, node:{durable:" + str(durable)
+ if xprops != None: s += ", x-declare:{" + xprops + "}"
+ return s + "}}"
+
+ def send_message(self, queue, message, durable=True, xprops=None, session=None):
+ if session == None:
+ s = self.connect().session()
+ else:
+ s = session
+ s.sender(self._prep_sender(queue, durable, xprops)).send(message)
+ if session == None:
+ s.connection.close()
+
+ def send_messages(self, queue, messages, durable=True, xprops=None, session=None):
+ if session == None:
+ s = self.connect().session()
+ else:
+ s = session
+ sender = s.sender(self._prep_sender(queue, durable, xprops))
+ for m in messages: sender.send(m)
+ if session == None:
+ s.connection.close()
+
+ def get_message(self, queue):
+ s = self.connect().session()
+ m = s.receiver(queue+"; {create:always}", capacity=1).fetch(timeout=1)
+ s.acknowledge()
+ s.connection.close()
+ return m
+
+ def get_messages(self, queue, n):
+ s = self.connect().session()
+ receiver = s.receiver(queue+"; {create:always}", capacity=n)
+ m = [receiver.fetch(timeout=1) for i in range(n)]
+ s.acknowledge()
+ s.connection.close()
+ return m
+
+ def host_port(self): return "%s:%s" % (self.host(), self.port())
+
+ def ready(self, timeout=10, **kwargs):
+ """Wait till broker is ready to serve clients"""
+ deadline = time.time()+timeout
+ while True:
+ try:
+ c = self.connect(timeout=timeout, **kwargs)
+ try:
+ c.session()
+ return # All good
+ finally: c.close()
+ except Exception,e: # Retry up to timeout
+ if time.time() > deadline:
+ raise RethrownException(
+ "Broker %s not responding: (%s)%s"%(
+ self.name,e,error_line(self.log, 5)))
+
+ def assert_log_clean(self, ignore=None):
+ log = open(self.get_log())
+ try:
+ error = re.compile("] error|] critical")
+ if ignore: ignore = re.compile(ignore)
+ else: ignore = re.compile("\000") # Won't match anything
+ for line in log.readlines():
+ assert not error.search(line) or ignore.search(line), "Errors in log file %s: %s"%(log, line)
+ finally: log.close()
+
+def receiver_iter(receiver, timeout=0):
+ """Make an iterator out of a receiver. Returns messages till Empty is raised."""
+ try:
+ while True:
+ yield receiver.fetch(timeout=timeout)
+ except qm.Empty:
+ pass
+
+def browse(session, queue, timeout=0, transform=lambda m: m.content):
+ """Return a list with the contents of each message on queue."""
+ r = session.receiver("%s;{mode:browse}"%(queue))
+ r.capacity = 100
+ try:
+ return [transform(m) for m in receiver_iter(r, timeout)]
+ finally:
+ r.close()
+
+def assert_browse(session, queue, expect_contents, timeout=0, transform=lambda m: m.content, msg=None):
+ """Assert that the contents of messages on queue (as retrieved
+ using session and timeout) exactly match the strings in
+ expect_contents"""
+ if msg is None: msg = "browse '%s' failed" % queue
+ actual_contents = browse(session, queue, timeout, transform=transform)
+ if msg: msg = "%s: %r != %r"%(msg, expect_contents, actual_contents)
+ assert expect_contents == actual_contents, msg
+
+def assert_browse_retry(session, queue, expect_contents, timeout=1, delay=.001, transform=lambda m:m.content, msg="browse failed"):
+ """Wait up to timeout for contents of queue to match expect_contents"""
+ test = lambda: browse(session, queue, 0, transform=transform) == expect_contents
+ retry(test, timeout, delay)
+ actual_contents = browse(session, queue, 0, transform=transform)
+ if msg: msg = "%s: %r != %r"%(msg, expect_contents, actual_contents)
+ assert expect_contents == actual_contents, msg
+
+class BrokerTest(TestCase):
+ """
+ Tracks processes started by test and kills at end of test.
+ Provides a well-known working directory for each test.
+ """
+
+ def __init__(self, *args, **kwargs):
+ self.longMessage = True # Enable long messages for assert*(..., msg=xxx)
+ TestCase.__init__(self, *args, **kwargs)
+
+ # Environment settings.
+ qpidd_exec = os.path.abspath(checkenv("QPIDD_EXEC"))
+ ha_lib = os.getenv("HA_LIB")
+ xml_lib = os.getenv("XML_LIB")
+ amqp_lib = os.getenv("AMQP_LIB")
+ qpid_config_exec = os.getenv("QPID_CONFIG_EXEC")
+ qpid_route_exec = os.getenv("QPID_ROUTE_EXEC")
+ receiver_exec = os.getenv("RECEIVER_EXEC")
+ sender_exec = os.getenv("SENDER_EXEC")
+ sql_store_lib = os.getenv("STORE_SQL_LIB")
+ sql_clfs_store_lib = os.getenv("STORE_SQL_CLFS_LIB")
+ sql_catalog = os.getenv("STORE_CATALOG")
+ store_lib = os.getenv("STORE_LIB")
+ test_store_lib = os.getenv("TEST_STORE_LIB")
+ rootdir = os.getcwd()
+
+ try:
+ import proton
+ PN_VERSION = (proton.VERSION_MAJOR, proton.VERSION_MINOR)
+ except ImportError:
+ # proton not on path, can't determine version
+ PN_VERSION = (0, 0)
+ except AttributeError:
+ # prior to 0.8 proton did not expose version info
+ PN_VERSION = (0, 7)
+
+ PN_TX_VERSION = (0, 9)
+
+ amqp_tx_supported = PN_VERSION >= PN_TX_VERSION
+
+ @classmethod
+ def amqp_tx_warning(cls):
+ if not cls.amqp_tx_supported:
+ if cls.PN_VERSION == (0, 0):
+ print "WARNING: Cannot test transactions over AMQP 1.0, proton not on path so version could not be determined"
+ elif cls.PN_VERSION == (0, 7):
+ print "WARNING: Cannot test transactions over AMQP 1.0, proton version is 0.7 or less, %s.%s required" % cls.PN_TX_VERSION
+ else:
+ print "WARNING: Cannot test transactions over AMQP 1.0, proton version %s.%s < %s.%s" % (cls.PN_VERSION + cls.PN_TX_VERSION)
+ return False
+ return True
+
+ def configure(self, config): self.config=config
+
+ def setUp(self):
+ defs = self.config.defines
+ outdir = defs.get("OUTDIR") or "brokertest.tmp"
+ self.dir = os.path.join(self.rootdir, outdir, self.id())
+ os.makedirs(self.dir)
+ os.chdir(self.dir)
+ self.teardown_list = [] # things to tear down at end of test
+ if qpid_messaging and self.amqp_lib: default_protocol="amqp1.0"
+ else: default_protocol="amqp0-10"
+ self.protocol = defs.get("PROTOCOL") or default_protocol
+ self.tx_protocol = self.protocol
+ if not self.amqp_tx_supported: self.tx_protocol = "amqp0-10"
+
+ def tearDown(self):
+ err = []
+ self.teardown_list.reverse() # Tear down in reverse order
+ for p in self.teardown_list:
+ log.debug("Tearing down %s", p)
+ try:
+ # Call the first of the methods that is available on p.
+ for m in ["teardown", "close"]:
+ a = getattr(p, m, None)
+ if a: a(); break
+ else: raise Exception("Don't know how to tear down %s", p)
+ except Exception, e:
+ if m != "close": # Ignore connection close errors.
+ err.append("%s: %s"%(e.__class__.__name__, str(e)))
+ self.teardown_list = [] # reset in case more processes start
+ os.chdir(self.rootdir)
+ if err: raise Exception("Unexpected process status:\n "+"\n ".join(err))
+
+ def teardown_add(self, thing):
+ """Call thing.teardown() or thing.close() at end of test"""
+ self.teardown_list.append(thing)
+
+ def popen(self, cmd, expect=EXPECT_EXIT_OK, stdin=None, stdout=FILE, stderr=FILE):
+ """Start a process that will be killed at end of test, in the test dir."""
+ os.chdir(self.dir)
+ p = Popen(cmd, expect, stdin=stdin, stdout=stdout, stderr=stderr)
+ self.teardown_add(p)
+ return p
+
+ def broker(self, args=[], name=None, expect=EXPECT_RUNNING, wait=True, port=0, show_cmd=False, **kw):
+ """Create and return a broker ready for use"""
+ b = Broker(self, args=args, name=name, expect=expect, port=port, show_cmd=show_cmd, **kw)
+ if (wait):
+ try: b.ready()
+ except Exception, e:
+ raise RethrownException("Failed to start broker %s(%s): %s" % (b.name, b.log, e))
+ return b
+
+ def check_output(self, args, stdin=None):
+ p = self.popen(args, stdout=PIPE, stderr=STDOUT)
+ out = p.communicate(stdin)
+ if p.returncode != 0:
+ raise Exception("%s exit code %s, output:\n%s" % (args, p.returncode, out[0]))
+ return out[0]
+
+ def browse(self, *args, **kwargs): browse(*args, **kwargs)
+ def assert_browse(self, *args, **kwargs): assert_browse(*args, **kwargs)
+ def assert_browse_retry(self, *args, **kwargs): assert_browse_retry(*args, **kwargs)
+
+ def protocol_option(self, connection_options=""):
+ if "protocol" in connection_options: return connection_options
+ else: return ",".join(filter(None, [connection_options,"protocol:'%s'"%self.protocol]))
+
+
+def join(thread, timeout=30):
+ thread.join(timeout)
+ if thread.isAlive(): raise Exception("Timed out joining thread %s"%thread)
+
+class RethrownException(Exception):
+ """Captures the stack trace of the current exception to be thrown later"""
+ def __init__(self, msg=""):
+ Exception.__init__(self, msg+"\n"+format_exc())
+
+class StoppableThread(Thread):
+ """
+ Base class for threads that do something in a loop and periodically check
+ to see if they have been stopped.
+ """
+ def __init__(self):
+ self.stopped = False
+ self.error = None
+ Thread.__init__(self)
+
+ def stop(self):
+ self.stopped = True
+ join(self)
+ if self.error: raise self.error
+
+# Options for a client that wants to reconnect automatically.
+RECONNECT_OPTIONS="reconnect:true,reconnect-timeout:10,reconnect-urls-replace:true"
+
+class NumberedSender(Thread):
+ """
+ Thread to run a sender client and send numbered messages until stopped.
+ """
+
+ def __init__(self, broker, max_depth=None, queue="test-queue",
+ connection_options=RECONNECT_OPTIONS,
+ failover_updates=False, url=None, args=[]):
+ """
+ max_depth: enable flow control, ensure sent - received <= max_depth.
+ Requires self.notify_received(n) to be called each time messages are received.
+ """
+ Thread.__init__(self)
+ cmd = ["qpid-send",
+ "--broker", url or broker.host_port(),
+ "--address", "%s;{create:always}"%queue,
+ "--connection-options", "{%s}"%(broker.test.protocol_option(connection_options)),
+ "--content-stdin"
+ ] + args
+ if failover_updates: cmd += ["--failover-updates"]
+ self.sender = broker.test.popen(
+ cmd, expect=EXPECT_RUNNING, stdin=PIPE)
+ self.condition = Condition()
+ self.max = max_depth
+ self.received = 0
+ self.stopped = False
+ self.error = None
+ self.queue = queue
+
+ def write_message(self, n):
+ self.sender.stdin.write(str(n)+"\n")
+ self.sender.stdin.flush()
+
+ def run(self):
+ try:
+ self.sent = 0
+ while not self.stopped:
+ self.sender.assert_running()
+ if self.max:
+ self.condition.acquire()
+ while not self.stopped and self.sent - self.received > self.max:
+ self.condition.wait()
+ self.condition.release()
+ self.write_message(self.sent)
+ self.sent += 1
+ except Exception, e:
+ self.error = RethrownException(
+ "%s: (%s)%s"%(self.sender.pname,e,
+ error_line(self.sender.outfile("err"))))
+
+
+ def notify_received(self, count):
+ """Called by receiver to enable flow control. count = messages received so far."""
+ self.condition.acquire()
+ self.received = count
+ self.condition.notify()
+ self.condition.release()
+
+ def stop(self):
+ self.condition.acquire()
+ try:
+ self.stopped = True
+ self.condition.notify()
+ finally: self.condition.release()
+ join(self)
+ self.write_message(-1) # end-of-messages marker.
+ if self.error: raise self.error
+
+class NumberedReceiver(Thread):
+ """
+ Thread to run a receiver client and verify it receives
+ sequentially numbered messages.
+ """
+ def __init__(self, broker, sender=None, queue="test-queue",
+ connection_options=RECONNECT_OPTIONS,
+ failover_updates=False, url=None, args=[]):
+ """
+ sender: enable flow control. Call sender.received(n) for each message received.
+ """
+ Thread.__init__(self)
+ self.test = broker.test
+ cmd = ["qpid-receive",
+ "--broker", url or broker.host_port(),
+ "--address", "%s;{create:always}"%queue,
+ "--connection-options", "{%s}"%(broker.test.protocol_option(connection_options)),
+ "--forever"
+ ]
+ if failover_updates: cmd += [ "--failover-updates" ]
+ cmd += args
+ self.receiver = self.test.popen(
+ cmd, expect=EXPECT_RUNNING, stdout=PIPE)
+ self.lock = Lock()
+ self.error = None
+ self.sender = sender
+ self.received = 0
+ self.queue = queue
+
+ def read_message(self):
+ n = int(self.receiver.stdout.readline())
+ return n
+
+ def run(self):
+ try:
+ m = self.read_message()
+ while m != -1:
+ self.receiver.assert_running()
+ assert m <= self.received, "%s missing message %s>%s"%(self.queue, m, self.received)
+ if (m == self.received): # Ignore duplicates
+ self.received += 1
+ if self.sender:
+ self.sender.notify_received(self.received)
+ m = self.read_message()
+ except Exception, e:
+ self.error = RethrownException(
+ "%s: (%s)%s"%(self.receiver.pname,e,
+ error_line(self.receiver.outfile("err"))))
+
+ def check(self):
+ """Raise an exception if there has been an error"""
+ if self.error: raise self.error
+
+ def stop(self):
+ """Returns when termination message is received"""
+ join(self)
+ self.check()
+
+def import_script(path):
+ """
+ Import executable script at path as a module.
+ Requires some trickery as scripts are not in standard module format
+ """
+ f = open(path)
+ try:
+ name=os.path.split(path)[1].replace("-","_")
+ return imp.load_module(name, f, path, ("", "r", imp.PY_SOURCE))
+ finally: f.close()
diff --git a/qpid/cpp/src/tests/cli_tests.py b/qpid/cpp/src/tests/cli_tests.py
new file mode 100755
index 0000000000..eee9bc648c
--- /dev/null
+++ b/qpid/cpp/src/tests/cli_tests.py
@@ -0,0 +1,477 @@
+#!/usr/bin/env python
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 sys
+import os
+import imp
+from qpid.testlib import TestBase010
+from qpid.datatypes import Message
+from qpid.queue import Empty
+from time import sleep
+
+def import_script(path):
+ """
+ Import executable script at path as a module.
+ Requires some trickery as scripts are not in standard module format
+ """
+ f = open(path)
+ try:
+ name=os.path.split(path)[1].replace("-","_")
+ return imp.load_module(name, f, path, ("", "r", imp.PY_SOURCE))
+ finally: f.close()
+
+def checkenv(name):
+ value = os.getenv(name)
+ if not value: raise Exception("Environment variable %s is not set" % name)
+ return value
+
+class CliTests(TestBase010):
+
+ def remote_host(self):
+ return self.defines.get("remote-host", "localhost")
+
+ def remote_port(self):
+ return int(self.defines["remote-port"])
+
+ def cli_dir(self):
+ return self.defines["cli-dir"]
+
+ def makeQueue(self, qname, arguments, api=False):
+ if api:
+ ret = self.qpid_config_api(" add queue " + qname + " " + arguments)
+ else:
+ ret = os.system(self.qpid_config_command(" add queue " + qname + " " + arguments))
+
+ self.assertEqual(ret, 0)
+ queue = self.broker_access.getQueue(qname)
+ if queue:
+ return queue
+ assert False
+
+ def test_queue_params(self):
+ self.startBrokerAccess()
+ queue1 = self.makeQueue("test_queue_params1", "--limit-policy none")
+ queue2 = self.makeQueue("test_queue_params2", "--limit-policy reject")
+ queue3 = self.makeQueue("test_queue_params3", "--limit-policy ring")
+
+ LIMIT = "qpid.policy_type"
+ assert LIMIT not in queue1.arguments
+ self.assertEqual(queue2.arguments[LIMIT], "reject")
+ self.assertEqual(queue3.arguments[LIMIT], "ring")
+
+ queue4 = self.makeQueue("test_queue_params4", "--lvq-key lkey")
+
+ LVQKEY = "qpid.last_value_queue_key"
+
+ assert LVQKEY not in queue3.arguments
+ assert LVQKEY in queue4.arguments
+ assert queue4.arguments[LVQKEY] == "lkey"
+
+ def test_queue_params_api(self):
+ self.startBrokerAccess()
+ queue1 = self.makeQueue("test_queue_params_api1", "--limit-policy none", True)
+ queue2 = self.makeQueue("test_queue_params_api2", "--limit-policy reject", True)
+ queue3 = self.makeQueue("test_queue_params_api3", "--limit-policy ring", True)
+
+ LIMIT = "qpid.policy_type"
+ assert LIMIT not in queue1.arguments
+ self.assertEqual(queue2.arguments[LIMIT], "reject")
+ self.assertEqual(queue3.arguments[LIMIT], "ring")
+
+ queue4 = self.makeQueue("test_queue_params_api4", "--lvq-key lkey")
+
+ LVQKEY = "qpid.last_value_queue_key"
+
+ assert LVQKEY not in queue3.arguments
+ assert LVQKEY in queue4.arguments
+ assert queue4.arguments[LVQKEY] == "lkey"
+
+
+ def test_qpid_config(self):
+ self.startBrokerAccess();
+ qname = "test_qpid_config"
+
+ ret = os.system(self.qpid_config_command(" add queue " + qname))
+ self.assertEqual(ret, 0)
+ queues = self.broker_access.getAllQueues()
+ found = False
+ for queue in queues:
+ if queue.name == qname:
+ self.assertEqual(queue.durable, False)
+ found = True
+ self.assertEqual(found, True)
+
+ ret = os.system(self.qpid_config_command(" del queue " + qname))
+ self.assertEqual(ret, 0)
+ queues = self.broker_access.getAllQueues()
+ found = False
+ for queue in queues:
+ if queue.name == qname:
+ found = True
+ self.assertEqual(found, False)
+
+ def test_qpid_config_del_nonempty_queue(self):
+ self.startBrokerAccess();
+ qname = "test_qpid_config_del"
+
+ ret = os.system(self.qpid_config_command(" add queue " + qname))
+ self.assertEqual(ret, 0)
+ queues = self.broker_access.getAllQueues()
+ found = False
+ for queue in queues:
+ if queue.name == qname:
+ self.assertEqual(queue.durable, False)
+ found = True
+ self.assertEqual(found, True)
+
+ self.startBrokerAccess()
+
+ sess = self.broker_conn.session()
+ tx = sess.sender(qname)
+ tx.send("MESSAGE")
+
+ ret = os.system(self.qpid_config_command(" del queue " + qname))
+ queues = self.broker_access.getAllQueues()
+ found = False
+ for queue in queues:
+ if queue.name == qname:
+ found = True
+ self.assertEqual(found, True)
+
+ ret = os.system(self.qpid_config_command(" del queue " + qname + " --force"))
+ self.assertEqual(ret, 0)
+ queues = self.broker_access.getAllQueues()
+ found = False
+ for queue in queues:
+ if queue.name == qname:
+ found = True
+ self.assertEqual(found, False)
+
+
+ def test_qpid_config_api(self):
+ self.startBrokerAccess();
+ qname = "test_qpid_config_api"
+
+ ret = self.qpid_config_api(" add queue " + qname)
+ self.assertEqual(ret, 0)
+ queues = self.broker_access.getAllQueues()
+ found = False
+ for queue in queues:
+ if queue.name == qname:
+ self.assertEqual(queue.durable, False)
+ found = True
+ self.assertEqual(found, True)
+
+ ret = self.qpid_config_api(" del queue " + qname)
+ self.assertEqual(ret, 0)
+ queues = self.broker_access.getAllQueues()
+ found = False
+ for queue in queues:
+ if queue.name == qname:
+ found = True
+ self.assertEqual(found, False)
+
+
+ def test_qpid_config_sasl_plain_expect_succeed(self):
+ self.startBrokerAccess();
+ qname = "test_qpid_config_sasl_plain_expect_succeed"
+ cmd = " --sasl-mechanism PLAIN -b guest/guest@localhost:"+str(self.broker.port) + " add queue " + qname
+ ret = self.qpid_config_api(cmd)
+ self.assertEqual(ret, 0)
+
+ def test_qpid_config_sasl_plain_expect_fail(self):
+ """Fails because no user name and password is supplied"""
+ self.startBrokerAccess();
+ qname = "test_qpid_config_sasl_plain_expect_fail"
+ cmd = " --sasl-mechanism PLAIN -b localhost:"+str(self.broker.port) + " add queue " + qname
+ ret = self.qpid_config_api(cmd)
+ assert ret != 0
+
+ # helpers for some of the test methods
+ def helper_find_exchange(self, xchgname, typ, expected=True):
+ xchgs = self.broker_access.getAllExchanges()
+ found = False
+ for xchg in xchgs:
+ if xchg.name == xchgname:
+ if typ:
+ self.assertEqual(xchg.type, typ)
+ found = True
+ self.assertEqual(found, expected)
+
+ def helper_create_exchange(self, xchgname, typ="direct", opts=""):
+ foo = self.qpid_config_command(opts + " add exchange " + typ + " " + xchgname)
+ # print foo
+ ret = os.system(foo)
+ self.assertEqual(ret, 0)
+ self.helper_find_exchange(xchgname, typ, True)
+
+ def helper_destroy_exchange(self, xchgname):
+ foo = self.qpid_config_command(" del exchange " + xchgname)
+ # print foo
+ ret = os.system(foo)
+ self.assertEqual(ret, 0)
+ self.helper_find_exchange(xchgname, False, expected=False)
+
+ def helper_find_queue(self, qname, expected=True):
+ queues = self.broker_access.getAllQueues()
+ found = False
+ for queue in queues:
+ if queue.name == qname:
+ self.assertEqual(queue.durable, False)
+ found = True
+ self.assertEqual(found, expected)
+
+ def helper_create_queue(self, qname):
+ foo = self.qpid_config_command(" add queue " + qname)
+ # print foo
+ ret = os.system(foo)
+ self.assertEqual(ret, 0)
+ self.helper_find_queue(qname, True)
+
+ def helper_destroy_queue(self, qname):
+ foo = self.qpid_config_command(" del queue " + qname)
+ # print foo
+ ret = os.system(foo)
+ self.assertEqual(ret, 0)
+ self.helper_find_queue(qname, False)
+
+ # test the bind-queue-to-header-exchange functionality
+ def test_qpid_config_headers(self):
+ self.startBrokerAccess();
+ qname = "test_qpid_config"
+ xchgname = "test_xchg"
+
+ # first create a header xchg
+ self.helper_create_exchange(xchgname, typ="headers")
+
+ # create the queue
+ self.helper_create_queue(qname)
+
+ # now bind the queue to the xchg
+ foo = self.qpid_config_command(" bind " + xchgname + " " + qname +
+ " key all foo=bar baz=quux")
+ # print foo
+ ret = os.system(foo)
+ self.assertEqual(ret, 0)
+
+ # he likes it, mikey. Ok, now tear it all down. first the binding
+ ret = os.system(self.qpid_config_command(" unbind " + xchgname + " " + qname +
+ " key"))
+ self.assertEqual(ret, 0)
+
+ # then the queue
+ self.helper_destroy_queue(qname)
+
+ # then the exchange
+ self.helper_destroy_exchange(xchgname)
+
+
+ def test_qpid_config_xml(self):
+ self.startBrokerAccess();
+ qname = "test_qpid_config"
+ xchgname = "test_xchg"
+
+ # first create a header xchg
+ self.helper_create_exchange(xchgname, typ="xml")
+
+ # create the queue
+ self.helper_create_queue(qname)
+
+ # now bind the queue to the xchg
+ foo = self.qpid_config_command("-f test.xquery bind " + xchgname + " " + qname)
+ # print foo
+ ret = os.system(foo)
+ self.assertEqual(ret, 0)
+
+ # he likes it, mikey. Ok, now tear it all down. first the binding
+ ret = os.system(self.qpid_config_command(" unbind " + xchgname + " " + qname +
+ " key"))
+ self.assertEqual(ret, 0)
+
+ # then the queue
+ self.helper_destroy_queue(qname)
+
+ # then the exchange
+ self.helper_destroy_exchange(xchgname)
+
+ def test_qpid_config_durable(self):
+ self.startBrokerAccess();
+ qname = "test_qpid_config"
+
+ ret = os.system(self.qpid_config_command(" add queue --durable " + qname))
+ self.assertEqual(ret, 0)
+ queues = self.broker_access.getAllQueues()
+ found = False
+ for queue in queues:
+ if queue.name == qname:
+ self.assertEqual(queue.durable, True)
+ found = True
+ self.assertEqual(found, True)
+
+ ret = os.system(self.qpid_config_command(" del queue " + qname))
+ self.assertEqual(ret, 0)
+ queues = self.broker_access.getAllQueues()
+ found = False
+ for queue in queues:
+ if queue.name == qname:
+ found = True
+ self.assertEqual(found, False)
+
+ def test_qpid_config_altex(self):
+ self.startBrokerAccess();
+ exName = "testalt"
+ qName = "testqalt"
+ altName = "amq.direct"
+
+ ret = os.system(self.qpid_config_command(" add exchange topic %s --alternate-exchange=%s" % (exName, altName)))
+ self.assertEqual(ret, 0)
+
+ exchanges = self.broker_access.getAllExchanges()
+ found = False
+ for exchange in exchanges:
+ if exchange.name == altName:
+ self.assertEqual(exchange.altExchange, None)
+
+ if exchange.name == exName:
+ found = True
+ if not exchange.altExchange:
+ self.fail("Alternate exchange not set")
+ self.assertEqual(exchange.altExchange, altName)
+ self.assertEqual(found, True)
+
+ ret = os.system(self.qpid_config_command(" add queue %s --alternate-exchange=%s" % (qName, altName)))
+ self.assertEqual(ret, 0)
+
+ ret = os.system(self.qpid_config_command(" queues"))
+ self.assertEqual(ret, 0)
+
+ queues = self.broker_access.getAllQueues()
+ found = False
+ for queue in queues:
+ if queue.name == qName:
+ found = True
+ if not queue.altExchange:
+ self.fail("Alternate exchange not set")
+ self.assertEqual(queue.altExchange, altName)
+ self.assertEqual(found, True)
+
+ def test_qpid_config_list_queues_arguments(self):
+ """
+ Test to verify that when the type of a policy limit is
+ actually a string (though still a valid value), it does not
+ upset qpid-config
+ """
+ self.startBrokerAccess();
+
+ names = ["queue_capacity%s" % (i) for i in range(1, 6)]
+ for name in names:
+ self.session.queue_declare(queue=name, exclusive=True,
+ arguments={'qpid.max_count' : str(i), 'qpid.max_size': '100'})
+
+ output = os.popen(self.qpid_config_command(" queues")).readlines()
+ queues = [line.split()[0] for line in output[2:len(output)]] #ignore first two lines (header)
+
+ for name in names:
+ assert name in queues, "%s not in %s" % (name, queues)
+
+ def test_qpid_route(self):
+ self.startBrokerAccess();
+
+ command = self.cli_dir() + "/qpid-route dynamic add guest/guest@localhost:%d %s:%d amq.topic" %\
+ (self.broker.port, self.remote_host(), self.remote_port())
+ ret = os.system(command)
+ self.assertEqual(ret, 0)
+
+ links = self.broker_access.getAllLinks()
+ found = False
+ for link in links:
+ if link.port == self.remote_port():
+ found = True
+ self.assertEqual(found, True)
+
+ def test_qpid_route_api(self):
+ self.startBrokerAccess();
+
+ ret = self.qpid_route_api("dynamic add "
+ + "guest/guest@localhost:"+str(self.broker.port) + " "
+ + str(self.remote_host())+":"+str(self.remote_port()) + " "
+ +"amq.direct")
+
+ self.assertEqual(ret, 0)
+
+ links = self.broker_access.getAllLinks()
+ found = False
+ for link in links:
+ if link.port == self.remote_port():
+ found = True
+ self.assertEqual(found, True)
+
+
+ def test_qpid_route_api(self):
+ self.startBrokerAccess();
+
+ ret = self.qpid_route_api("dynamic add "
+ + " --client-sasl-mechanism PLAIN "
+ + "guest/guest@localhost:"+str(self.broker.port) + " "
+ + str(self.remote_host())+":"+str(self.remote_port()) + " "
+ +"amq.direct")
+
+ self.assertEqual(ret, 0)
+
+ links = self.broker_access.getAllLinks()
+ found = False
+ for link in links:
+ if link.port == self.remote_port():
+ found = True
+ self.assertEqual(found, True)
+
+ def test_qpid_route_api_expect_fail(self):
+ self.startBrokerAccess();
+
+ ret = self.qpid_route_api("dynamic add "
+ + " --client-sasl-mechanism PLAIN "
+ + "localhost:"+str(self.broker.port) + " "
+ + str(self.remote_host())+":"+str(self.remote_port()) + " "
+ +"amq.direct")
+ assert ret != 0
+
+
+ def getProperty(self, msg, name):
+ for h in msg.headers:
+ if hasattr(h, name): return getattr(h, name)
+ return None
+
+ def getAppHeader(self, msg, name):
+ headers = self.getProperty(msg, "application_headers")
+ if headers:
+ return headers[name]
+ return None
+
+ def qpid_config_command(self, arg = ""):
+ return self.cli_dir() + "/qpid-config -b localhost:%d" % self.broker.port + " " + arg
+
+ def qpid_config_api(self, arg = ""):
+ script = import_script(checkenv("QPID_CONFIG_EXEC"))
+ broker = ["-b", "localhost:"+str(self.broker.port)]
+ return script.main(broker + arg.split())
+
+ def qpid_route_api(self, arg = ""):
+ script = import_script(checkenv("QPID_ROUTE_EXEC"))
+ return script.main(arg.split())
diff --git a/qpid/cpp/src/tests/config.null b/qpid/cpp/src/tests/config.null
new file mode 100644
index 0000000000..e2f355768b
--- /dev/null
+++ b/qpid/cpp/src/tests/config.null
@@ -0,0 +1,21 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Deliberately empty configuration file for tests.
+
diff --git a/qpid/cpp/src/tests/consume.cpp b/qpid/cpp/src/tests/consume.cpp
new file mode 100644
index 0000000000..69110d151f
--- /dev/null
+++ b/qpid/cpp/src/tests/consume.cpp
@@ -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.
+ *
+ */
+
+#include <algorithm>
+#include <iostream>
+#include <memory>
+#include <sstream>
+#include <vector>
+
+#include "TestOptions.h"
+#include "qpid/client/Connection.h"
+#include "qpid/client/Message.h"
+#include "qpid/client/Session.h"
+#include "qpid/client/SubscriptionManager.h"
+
+using namespace qpid;
+using namespace qpid::client;
+using namespace qpid::sys;
+using namespace std;
+
+namespace qpid {
+namespace tests {
+
+typedef vector<string> StringSet;
+
+struct Args : public qpid::TestOptions {
+ uint count;
+ uint ack;
+ string queue;
+ bool declare;
+ bool summary;
+ bool print;
+ bool durable;
+
+ Args() : count(1000), ack(0), queue("publish-consume"),
+ declare(false), summary(false), print(false)
+ {
+ addOptions()
+ ("count", optValue(count, "N"), "number of messages to publish")
+ ("ack-frequency", optValue(ack, "N"), "ack every N messages (0 means use no-ack mode)")
+ ("queue", optValue(queue, "<queue name>"), "queue to consume from")
+ ("declare", optValue(declare), "declare the queue")
+ ("durable", optValue(durable), "declare the queue durable, use with declare")
+ ("print-data", optValue(print), "Print the recieved data at info level")
+ ("s,summary", optValue(summary), "Print undecorated rate.");
+ }
+};
+
+Args opts;
+
+struct Client
+{
+ Connection connection;
+ Session session;
+
+ Client()
+ {
+ opts.open(connection);
+ session = connection.newSession();
+ }
+
+ void consume()
+ {
+ if (opts.declare)
+ session.queueDeclare(arg::queue=opts.queue, arg::durable=opts.durable);
+ SubscriptionManager subs(session);
+ LocalQueue lq;
+ SubscriptionSettings settings;
+ settings.acceptMode = opts.ack > 0 ? ACCEPT_MODE_EXPLICIT : ACCEPT_MODE_NONE;
+ settings.flowControl = FlowControl(opts.count, SubscriptionManager::UNLIMITED,false);
+ Subscription sub = subs.subscribe(lq, opts.queue, settings);
+ Message msg;
+ AbsTime begin=now();
+ for (size_t i = 0; i < opts.count; ++i) {
+ msg=lq.pop();
+ QPID_LOG(info, "Received: " << msg.getMessageProperties().getCorrelationId());
+ if (opts.print) QPID_LOG(info, "Data: " << msg.getData());
+ }
+ if (opts.ack != 0)
+ sub.accept(sub.getUnaccepted()); // Cumulative ack for final batch.
+ AbsTime end=now();
+ double secs(double(Duration(begin,end))/TIME_SEC);
+ if (opts.summary) cout << opts.count/secs << endl;
+ else cout << "Time: " << secs << "s Rate: " << opts.count/secs << endl;
+ }
+
+ ~Client()
+ {
+ try{
+ session.close();
+ connection.close();
+ } catch(const exception& e) {
+ cout << e.what() << endl;
+ }
+ }
+};
+
+}} // namespace qpid::tests
+
+using namespace qpid::tests;
+
+int main(int argc, char** argv)
+{
+ try {
+ opts.parse(argc, argv);
+ Client client;
+ client.consume();
+ return 0;
+ } catch(const exception& e) {
+ cout << e.what() << endl;
+ }
+ return 1;
+}
diff --git a/qpid/cpp/src/tests/datagen.cpp b/qpid/cpp/src/tests/datagen.cpp
new file mode 100644
index 0000000000..acbc07d63c
--- /dev/null
+++ b/qpid/cpp/src/tests/datagen.cpp
@@ -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.
+ *
+ */
+
+#include <exception>
+#include <iostream>
+#include <stdlib.h>
+#include <time.h>
+#include "qpid/Options.h"
+
+namespace qpid {
+namespace tests {
+
+struct Args : public qpid::Options
+{
+ uint count;
+ uint minSize;
+ uint maxSize;
+ uint minChar;
+ uint maxChar;
+ bool help;
+
+ Args() : qpid::Options("Random data generator"),
+ count(1), minSize(8), maxSize(4096),
+ minChar(32), maxChar(126),//safely printable ascii chars
+ help(false)
+ {
+ addOptions()
+ ("count", qpid::optValue(count, "N"), "number of data strings to generate")
+ ("min-size", qpid::optValue(minSize, "N"), "minimum size of data string")
+ ("max-size", qpid::optValue(maxSize, "N"), "maximum size of data string")
+ ("min-char", qpid::optValue(minChar, "N"), "minimum char value used in data string")
+ ("max-char", qpid::optValue(maxChar, "N"), "maximum char value used in data string")
+ ("help", qpid::optValue(help), "print this usage statement");
+ }
+
+ bool parse(int argc, char** argv) {
+ try {
+ qpid::Options::parse(argc, argv);
+ if (maxSize < minSize) throw qpid::Options::Exception("max-size must be greater than min-size");
+ if (maxChar < minChar) throw qpid::Options::Exception("max-char must be greater than min-char");
+
+ if (help) {
+ std::cerr << *this << std::endl << std::endl;
+ } else {
+ return true;
+ }
+ } catch (const std::exception& e) {
+ std::cerr << *this << std::endl << std::endl << e.what() << std::endl;
+ }
+ return false;
+ }
+
+};
+
+uint random(uint min, uint max)
+{
+ return (rand() % (max-min+1)) + min;
+}
+
+std::string generateData(uint size, uint min, uint max)
+{
+ std::string data;
+ for (uint i = 0; i < size; i++) {
+ data += (char) random(min, max);
+ }
+ return data;
+}
+
+}} // namespace qpid::tests
+
+using namespace qpid::tests;
+
+int main(int argc, char** argv)
+{
+ Args opts;
+ if (opts.parse(argc, argv)) {
+ srand(time(0));
+ for (uint i = 0; i < opts.count; i++) {
+ std::cout << generateData(random(opts.minSize, opts.maxSize), opts.minChar, opts.maxChar) << std::endl;
+ }
+ return 0;
+ } else {
+ return 1;
+ }
+}
diff --git a/qpid/cpp/src/tests/declare_queues.cpp b/qpid/cpp/src/tests/declare_queues.cpp
new file mode 100644
index 0000000000..bf85b9c04b
--- /dev/null
+++ b/qpid/cpp/src/tests/declare_queues.cpp
@@ -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.
+ *
+ */
+
+#include <qpid/client/FailoverManager.h>
+#include <qpid/client/Session.h>
+#include <qpid/sys/Time.h>
+#include <qpid/Exception.h>
+
+#include <cstdlib>
+#include <iostream>
+#include <sstream>
+
+using namespace qpid::client;
+
+using namespace std;
+
+int
+main(int argc, char ** argv)
+{
+ ConnectionSettings settings;
+ if ( argc != 6 )
+ {
+ cerr << "Usage: declare_queues host port durability queue_name_prefix n_queues\n";
+ return 1;
+ }
+
+ settings.host = argv[1];
+ settings.port = atoi(argv[2]);
+ int durability = atoi(argv[3]);
+ char const * queue_name_prefix = argv[4];
+ int n_queues = atoi(argv[5]);
+
+ FailoverManager connection(settings);
+
+ int max_fail = 13;
+ for ( int i = 0; i < n_queues; ++ i ) {
+ stringstream queue_name;
+ queue_name << queue_name_prefix << '_' << i;
+
+ bool queue_created = false;
+ int failure_count;
+
+ // Any non-transport failure is Bad.
+ try
+ {
+ while ( ! queue_created ) {
+ Session session = connection.connect().newSession();
+ // TransportFailures aren't too bad -- they might happen because
+ // we are doing a cluster failover test. But if we get too many,
+ // we will still bug out.
+ failure_count = 0;
+ try {
+ if ( durability )
+ session.queueDeclare(arg::queue=queue_name.str(), arg::durable=true);
+ else
+ session.queueDeclare(arg::queue=queue_name.str());
+ queue_created = true;
+ cout << "declare_queues: Created queue " << queue_name.str() << endl;
+ }
+ catch ( const qpid::TransportFailure& ) {
+ if ( ++ failure_count >= max_fail ) {
+ cerr << "declare_queues failed: too many transport errors.\n";
+ cerr << " host: " << settings.host
+ << " port: " << settings.port << endl;
+ return 1;
+ }
+ qpid::sys::sleep ( 1 );
+ }
+ }
+ }
+ catch ( const exception & error) {
+ cerr << "declare_queues failed:" << error.what() << endl;
+ cerr << " host: " << settings.host
+ << " port: " << settings.port << endl;
+ return 1;
+ }
+ }
+}
+
+
+
+
+
diff --git a/qpid/cpp/src/tests/dlclose_noop.c b/qpid/cpp/src/tests/dlclose_noop.c
new file mode 100644
index 0000000000..b78cf486d8
--- /dev/null
+++ b/qpid/cpp/src/tests/dlclose_noop.c
@@ -0,0 +1,30 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+/*
+ * Loaded via LD_PRELOAD this will turn dlclose into a no-op.
+ *
+ * Allows valgrind to generate useful reports from programs that
+ * dynamically unload libraries before exit, such as CppUnit's
+ * DllPlugInTester.
+ *
+ */
+
+#include <stdio.h>
+void* dlclose(void* handle) { return 0; }
+
diff --git a/qpid/cpp/src/tests/dynamic_log_hires_timestamp b/qpid/cpp/src/tests/dynamic_log_hires_timestamp
new file mode 100755
index 0000000000..75034f9902
--- /dev/null
+++ b/qpid/cpp/src/tests/dynamic_log_hires_timestamp
@@ -0,0 +1,75 @@
+#!/usr/bin/env 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.
+#
+
+# Run a simple test to verify dynamic log highres timestamp changes
+source ./test_env.sh
+test -d $PYTHON_DIR || { echo "Skipping python tests, no python dir."; exit 0; }
+
+LOG_FILE=hires_test.log
+trap cleanup EXIT
+
+cleanup() {
+ test -n "$PORT" && $QPIDD_EXEC --no-module-dir --quit --port $PORT
+}
+
+error() {
+ echo $*;
+ exit 1;
+}
+
+rm -rf $LOG_FILE
+PORT=$($QPIDD_EXEC --auth=no --no-module-dir --daemon --port=0 --interface 127.0.0.1 --log-to-file $LOG_FILE) || error "Could not start broker"
+
+echo Broker for log highres timestamp test started on $PORT, pid is $($QPIDD_EXEC --no-module-dir --check --port $PORT)
+
+$srcdir/qpid-ctrl -b localhost:$PORT setLogLevel level='debug+:Broker' > /dev/null
+$srcdir/qpid-ctrl -b localhost:$PORT echo sequence=1 body=LOWRES > /dev/null
+$srcdir/qpid-ctrl -b localhost:$PORT setLogHiresTimestamp logHires='true' > /dev/null
+$srcdir/qpid-ctrl -b localhost:$PORT echo sequence=2 body=HI_RES > /dev/null
+$srcdir/qpid-ctrl -b localhost:$PORT setLogHiresTimestamp logHires='false' > /dev/null
+$srcdir/qpid-ctrl -b localhost:$PORT echo sequence=3 body=LOWRES > /dev/null
+
+# Expect 3 log entries with 'echo' in them
+if [[ $(grep echo $LOG_FILE | wc -l) -ne 3 ]]; then
+ cat $LOG_FILE
+ error "Log content error - expected 3 echo log entries"
+fi
+
+# Lines 1 and 3 are length X
+# Line 2 is length X+10 because of timestamp addition
+LEN1=$(grep echo $LOG_FILE | grep \(1 | wc -m)
+LEN2=$(grep echo $LOG_FILE | grep \(2 | wc -m)
+LEN3=$(grep echo $LOG_FILE | grep \(3 | wc -m)
+EXPECTED_LEN2=$(( $LEN1 + 10 ))
+
+if [ $LEN1 -ne $LEN3 ]; then
+ cat $LOG_FILE
+ error "Log content error - expected echo 3 to be same line length as echo 1"
+fi
+
+if [ $LEN2 -ne $EXPECTED_LEN2 ]; then
+ cat $LOG_FILE
+ error "Log content error - expected echo 2 to be 10 characters longer than echo 1"
+fi
+
+rm -rf $LOG_FILE
+echo OK
+
diff --git a/qpid/cpp/src/tests/dynamic_log_level_test b/qpid/cpp/src/tests/dynamic_log_level_test
new file mode 100755
index 0000000000..f8fd7a8dd8
--- /dev/null
+++ b/qpid/cpp/src/tests/dynamic_log_level_test
@@ -0,0 +1,90 @@
+#!/usr/bin/env 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.
+#
+
+# Run a simple test to verify dynamic log level changes
+source ./test_env.sh
+test -d $PYTHON_DIR || { echo "Skipping python tests, no python dir."; exit 0; }
+
+LOG_FILE=log_test.log
+trap cleanup EXIT
+
+cleanup() {
+ test -n "$PORT" && $QPIDD_EXEC --no-module-dir --quit --port $PORT
+}
+
+error() {
+ echo $*;
+ exit 1;
+}
+
+checklog() {
+ if [[ $(grep echo $LOG_FILE | wc -l) -ne $1 ]]; then
+ cat $LOG_FILE
+ error "Log contents not as expected - " $2
+ fi
+}
+
+rm -rf $LOG_FILE
+PORT=$($QPIDD_EXEC --auth=no --no-module-dir --daemon --port=0 --interface 127.0.0.1 --log-to-file $LOG_FILE) || error "Could not start broker"
+
+echo Broker for log level test started on $PORT, pid is $($QPIDD_EXEC --no-module-dir --check --port $PORT)
+
+# Set level to notice+ and send an echo request
+# The 'echo' in the log is hidden since it is at debug level.
+$srcdir/qpid-ctrl -b localhost:$PORT setLogLevel level='notice+' > /dev/null
+$srcdir/qpid-ctrl -b localhost:$PORT echo sequence=1 body=HIDDEN > /dev/null
+checklog 0 "Step 1 Expected no echo log entries"
+
+# Next, enable all Broker logs at debug and higher levels and send another echo
+# This 'echo' should be in the log.
+$srcdir/qpid-ctrl -b localhost:$PORT setLogLevel level='debug+:Broker' > /dev/null
+$srcdir/qpid-ctrl -b localhost:$PORT echo sequence=2 body=VISIBLE > /dev/null
+checklog 1 "Step 2 Expected one echo log entry"
+
+# Now turn on Broker debug messages but specifically disable ManagementMethod logs
+# The 'echo' should be hidden.
+$srcdir/qpid-ctrl -b localhost:$PORT setLogLevel level='debug+:Broker !debug+:broker::Broker::ManagementMethod' > /dev/null
+$srcdir/qpid-ctrl -b localhost:$PORT echo sequence=3 body=HIDDEN > /dev/null
+checklog 1 "Step 3 Expected one echo log entry"
+
+# Verify that the management get returns what was just set
+$srcdir/qpid-ctrl -b localhost:$PORT getLogLevel > dynamic_log_level.tmp
+if [[ $(grep 'level=debug+:Broker,!debug+:broker::Broker::ManagementMethod' dynamic_log_level.tmp | wc -l) -ne 1 ]]; then
+ error "Step 4 getLogLevel returned unexpected value: " `cat dynamic_log_level.tmp`
+fi
+rm -rf dynamic_log_level.tmp
+
+cleanup
+
+# Start another broker with --log-disable settings and make sure the management string receives them
+rm -rf $LOG_FILE
+PORT=$($QPIDD_EXEC --auth=no --no-module-dir --daemon --port=0 --interface 127.0.0.1 --log-to-file $LOG_FILE --log-enable debug:foo --log-disable debug:bar) || error "Could not start broker"
+echo Broker for log level test started on $PORT, pid is $($QPIDD_EXEC --no-module-dir --check --port $PORT)
+
+$srcdir/qpid-ctrl -b localhost:$PORT getLogLevel > dynamic_log_level.tmp
+if [[ $(grep 'level=debug:foo,!debug:bar' dynamic_log_level.tmp | wc -l) -ne 1 ]]; then
+ error "Step 5 getLogLevel returned unexpected value: " `cat dynamic_log_level.tmp`
+fi
+rm -rf dynamic_log_level.tmp
+
+rm -rf $LOG_FILE
+echo OK
+
diff --git a/qpid/cpp/src/tests/echotest.cpp b/qpid/cpp/src/tests/echotest.cpp
new file mode 100644
index 0000000000..7c30989098
--- /dev/null
+++ b/qpid/cpp/src/tests/echotest.cpp
@@ -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.
+ *
+ */
+
+#include <qpid/client/Connection.h>
+#include <qpid/client/SubscriptionManager.h>
+#include <qpid/client/AsyncSession.h>
+#include <qpid/client/Message.h>
+#include <qpid/client/MessageListener.h>
+#include <qpid/sys/Time.h>
+#include <qpid/Options.h>
+
+#include <iostream>
+
+using namespace qpid::client;
+using namespace qpid::framing;
+using namespace qpid::sys;
+using namespace std;
+
+namespace qpid {
+namespace tests {
+
+struct Args : public qpid::Options,
+ public qpid::client::ConnectionSettings
+{
+ bool help;
+ uint count;
+ uint size;
+ bool summary;
+
+ Args() : qpid::Options("Simple latency test optins"), help(false), count(20), size(0), summary()
+ {
+ using namespace qpid;
+ addOptions()
+ ("help", optValue(help), "Print this usage statement")
+ ("count", optValue(count, "N"), "Number of messages to send")
+ ("size", optValue(count, "N"), "Size of messages")
+ ("broker,b", optValue(host, "HOST"), "Broker host to connect to")
+ ("port,p", optValue(port, "PORT"), "Broker port to connect to")
+ ("username", optValue(username, "USER"), "user name for broker log in.")
+ ("password", optValue(password, "PASSWORD"), "password for broker log in.")
+ ("mechanism", optValue(mechanism, "MECH"), "SASL mechanism to use when authenticating.")
+ ("tcp-nodelay", optValue(tcpNoDelay), "Turn on tcp-nodelay")
+ ("s,summary", optValue(summary), "Print only average latency.");
+ }
+};
+
+uint64_t current_time()
+{
+ return Duration::FromEpoch();
+}
+
+class Listener : public MessageListener
+{
+ private:
+ Session session;
+ SubscriptionManager subscriptions;
+ uint counter;
+ const uint limit;
+ std::string queue;
+ Message request;
+ double total, min, max;
+ bool summary;
+
+ public:
+ Listener(Session& session, uint limit, bool summary);
+ void start(uint size);
+ void received(Message& message);
+};
+
+Listener::Listener(Session& s, uint l, bool summary_) :
+ session(s), subscriptions(s), counter(0), limit(l),
+ queue(session.getId().getName()), total(),
+ min(std::numeric_limits<double>::max()), max(), summary(summary_)
+{}
+
+void Listener::start(uint size)
+{
+ session.queueDeclare(arg::queue=queue, arg::exclusive=true, arg::autoDelete=true);
+ request.getDeliveryProperties().setRoutingKey(queue);
+ subscriptions.subscribe(*this, queue, SubscriptionSettings(FlowControl::unlimited(), ACCEPT_MODE_NONE));
+
+ request.getDeliveryProperties().setTimestamp(current_time());
+ if (size) request.setData(std::string(size, 'X'));
+ async(session).messageTransfer(arg::content=request);
+ subscriptions.run();
+}
+
+void Listener::received(Message& response)
+{
+ //extract timestamp and compute latency:
+ uint64_t sentAt = response.getDeliveryProperties().getTimestamp();
+ uint64_t receivedAt = current_time();
+
+ double latency = ((double) (receivedAt - sentAt)) / TIME_MSEC;
+ if (!summary) cout << "Latency: " << latency << "ms" << endl;
+ min = std::min(latency, min);
+ max = std::max(latency, max);
+ total += latency;
+
+ if (++counter < limit) {
+ request.getDeliveryProperties().setTimestamp(current_time());
+ async(session).messageTransfer(arg::content=request);
+ } else {
+ subscriptions.cancel(queue);
+ if (summary) cout << min << "\t" << max << "\t" << total/limit << endl;
+ else cout << "min: " << min << " max: " << max << " average: " << total/limit << endl;
+ }
+}
+
+}} // namespace qpid::tests
+
+using namespace qpid::tests;
+
+int main(int argc, char** argv)
+{
+ Args opts;
+ opts.parse(argc, argv);
+
+ if (opts.help) {
+ std::cout << opts << std::endl;
+ return 0;
+ }
+
+ Connection connection;
+ try {
+ connection.open(opts);
+ Session session = connection.newSession();
+ Listener listener(session, opts.count, opts.summary);
+ listener.start(opts.size);
+
+ connection.close();
+ return 0;
+ } catch(const std::exception& error) {
+ std::cout << error.what() << std::endl;
+ }
+ return 1;
+}
+
+
diff --git a/qpid/cpp/src/tests/exception_test.cpp b/qpid/cpp/src/tests/exception_test.cpp
new file mode 100644
index 0000000000..3e844b4e58
--- /dev/null
+++ b/qpid/cpp/src/tests/exception_test.cpp
@@ -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.
+ *
+ */
+
+#include "unit_test.h"
+#include "test_tools.h"
+#include "BrokerFixture.h"
+#include "qpid/client/SubscriptionManager.h"
+#include "qpid/client/MessageListener.h"
+#include "qpid/sys/Runnable.h"
+#include "qpid/sys/Thread.h"
+#include "qpid/framing/reply_exceptions.h"
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(exception_test)
+
+// FIXME aconway 2008-06-12: need to update our exception handling to
+// 0-10 handling and extend this test to provoke all the exceptional
+// conditions we know of and verify the correct exception is thrown.
+
+using namespace std;
+using namespace qpid;
+using namespace sys;
+using namespace client;
+using namespace framing;
+
+using qpid::broker::Broker;
+using boost::bind;
+using boost::function;
+
+template <class Ex>
+struct Catcher : public Runnable {
+ function<void ()> f;
+ bool caught;
+ Thread thread;
+
+ Catcher(function<void ()> f_) : f(f_), caught(false), thread(this) {}
+ ~Catcher() { join(); }
+
+ void run() {
+ try {
+ ScopedSuppressLogging sl; // Suppress messages for expected errors.
+ f();
+ }
+ catch(const Ex& e) {
+ caught=true;
+ BOOST_MESSAGE(string("Caught expected exception: ")+e.what()+"["+typeid(e).name()+"]");
+ }
+ catch(const std::exception& e) {
+ BOOST_ERROR(string("Bad exception: ")+e.what()+"["+typeid(e).name()+"] expected: "+typeid(Ex).name());
+ }
+ catch(...) {
+ BOOST_ERROR(string("Bad exception: unknown"));
+ }
+ }
+
+ bool join() {
+ if (thread) {
+ thread.join();
+ thread=Thread();
+ }
+ return caught;
+ }
+};
+
+QPID_AUTO_TEST_CASE(TestSessionBusy) {
+ SessionFixture f;
+ try {
+ ScopedSuppressLogging sl; // Suppress messages for expected errors.
+ f.connection.newSession(f.session.getId().getName());
+ BOOST_FAIL("Expected SessionBusyException for " << f.session.getId().getName());
+ } catch (const SessionBusyException&) {} // FIXME aconway 2008-09-22: client is not throwing correct exception.
+}
+
+QPID_AUTO_TEST_CASE(DisconnectedPop) {
+ SessionFixture fix;
+ fix.session.queueDeclare(arg::queue="q");
+ fix.subs.subscribe(fix.lq, "q");
+ Catcher<TransportFailure> pop(bind(&LocalQueue::pop, &fix.lq, sys::TIME_SEC));
+ fix.shutdownBroker();
+ BOOST_CHECK(pop.join());
+}
+
+QPID_AUTO_TEST_CASE(DisconnectedListen) {
+ SessionFixture fix;
+ struct NullListener : public MessageListener {
+ void received(Message&) { BOOST_FAIL("Unexpected message"); }
+ } l;
+ fix.session.queueDeclare(arg::queue="q");
+ fix.subs.subscribe(l, "q");
+
+ Catcher<TransportFailure> runner(bind(&SubscriptionManager::run, boost::ref(fix.subs)));
+ fix.shutdownBroker();
+ runner.join();
+ BOOST_CHECK_THROW(fix.session.queueDeclare(arg::queue="x"), TransportFailure);
+}
+
+QPID_AUTO_TEST_CASE(NoSuchQueueTest) {
+ SessionFixture fix;
+ ScopedSuppressLogging sl; // Suppress messages for expected errors.
+ BOOST_CHECK_THROW(fix.subs.subscribe(fix.lq, "no such queue"), NotFoundException);
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/failing-amqp0-10-python-tests b/qpid/cpp/src/tests/failing-amqp0-10-python-tests
new file mode 100644
index 0000000000..afa263217f
--- /dev/null
+++ b/qpid/cpp/src/tests/failing-amqp0-10-python-tests
@@ -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.
+#
+
+#The following four tests fail the because pure python client excludes
+#the node type for queues from the reply-to address, weheras the swigged
+#client does not (as that prevents it resolving the node on every send)
+qpid.tests.messaging.message.MessageEchoTests.testReplyTo
+qpid.tests.messaging.message.MessageEchoTests.testReplyToQueue
+qpid.tests.messaging.message.MessageEchoTests.testReplyToQueueSubject
+qpid.tests.messaging.message.MessageEchoTests.testProperties
+
+# The following test fails because the swig client throws an error
+# when creating a sender with a node name that is ambiguous and no
+# type specified. By contrast the pure python client defaults to the
+# queue in this case.
+qpid_tests.broker_0_10.new_api.GeneralTests.test_node_disambiguation_2
diff --git a/qpid/cpp/src/tests/failing-amqp1.0-python-tests b/qpid/cpp/src/tests/failing-amqp1.0-python-tests
new file mode 100644
index 0000000000..e76d05d7be
--- /dev/null
+++ b/qpid/cpp/src/tests/failing-amqp1.0-python-tests
@@ -0,0 +1,25 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+qpid_tests.broker_0_10.new_api.GeneralTests.test_qpid_3481_acquired_to_alt_exchange_2_consumers
+qpid_tests.broker_0_10.new_api.GeneralTests.test_qpid_3481_acquired_to_alt_exchange
+qpid_tests.broker_0_10.new_api.GeneralTests.test_nolocal_rerouted
+qpid_tests.broker_0_10.new_api.GeneralTests.test_ambiguous_delete_1
+qpid_tests.broker_0_10.new_api.GeneralTests.test_ambiguous_delete_2
+qpid_tests.broker_0_10.new_api.GeneralTests.test_node_disambiguation_2
diff --git a/qpid/cpp/src/tests/fanout_perftest b/qpid/cpp/src/tests/fanout_perftest
new file mode 100755
index 0000000000..168994d372
--- /dev/null
+++ b/qpid/cpp/src/tests/fanout_perftest
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+exec `dirname $0`/run_perftest 10000 --mode fanout --npubs 16 --nsubs 16 --size 64
diff --git a/qpid/cpp/src/tests/federated_topic_test b/qpid/cpp/src/tests/federated_topic_test
new file mode 100755
index 0000000000..2d31f9af5a
--- /dev/null
+++ b/qpid/cpp/src/tests/federated_topic_test
@@ -0,0 +1,128 @@
+#!/bin/sh
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Run the topic test on a federated setup
+
+# Clean up old log files
+rm -f subscriber_*.log
+
+# Defaults values
+SUBSCRIBERS=2
+MESSAGES=1000
+BATCHES=1
+VERBOSE=1
+
+while getopts "s:m:b:" opt ; do
+ case $opt in
+ s) SUBSCRIBERS=$OPTARG ;;
+ m) MESSAGES=$OPTARG ;;
+ b) BATCHES=$OPTARG ;;
+ ?)
+ echo "Usage: %0 [-s <subscribers>] [-m <messages.] [-b <batches>]"
+ exit 1
+ ;;
+ esac
+done
+
+source ./test_env.sh
+
+trap stop_brokers EXIT
+
+start_broker() {
+ $QPIDD_EXEC --daemon --port 0 --interface 127.0.0.1 --no-module-dir --no-data-dir --auth no > qpidd.port
+}
+
+start_brokers() {
+ start_broker
+ PORT_A=`cat qpidd.port`
+ start_broker
+ PORT_B=`cat qpidd.port`
+ start_broker
+ PORT_C=`cat qpidd.port`
+}
+
+stop_brokers() {
+ for p in $PORT_A $PORT_B $PORT_C; do
+ $QPIDD_EXEC --no-module-dir -q --port $p
+ done
+}
+
+subscribe() {
+ #which broker should we connect to?
+ if (( $1 % 2 )); then
+ MY_PORT=$PORT_C;
+ else
+ MY_PORT=$PORT_A;
+ fi
+
+ echo Subscriber $1 connecting on $MY_PORT
+ LOG="subscriber_$1.log"
+ ./qpid-topic-listener -p $MY_PORT > $LOG 2>&1 && rm -f $LOG
+}
+
+publish() {
+ ./qpid-topic-publisher --messages $MESSAGES --batches $BATCHES --subscribers $SUBSCRIBERS -p $PORT_A
+}
+
+setup_routes() {
+ BROKER_A="daffodil:$PORT_A"
+ BROKER_B="daffodil:$PORT_B"
+ BROKER_C="daffodil:$PORT_C"
+ if (($VERBOSE)); then
+ echo "Establishing routes for topic..."
+ fi
+ $QPID_ROUTE_EXEC route add $BROKER_B $BROKER_A amq.topic topic_control B B
+ $QPID_ROUTE_EXEC route add $BROKER_C $BROKER_B amq.topic topic_control C C
+ if (($VERBOSE)); then
+ echo "linked A->B->C"
+ fi
+ $QPID_ROUTE_EXEC route add $BROKER_B $BROKER_C amq.topic topic_control B B
+ $QPID_ROUTE_EXEC route add $BROKER_A $BROKER_B amq.topic topic_control A A
+ if (($VERBOSE)); then
+ echo "linked C->B->A"
+ echo "Establishing routes for response queue..."
+ fi
+
+ $QPID_ROUTE_EXEC route add $BROKER_B $BROKER_C amq.direct response B B
+ $QPID_ROUTE_EXEC route add $BROKER_A $BROKER_B amq.direct response A A
+ if (($VERBOSE)); then
+ echo "linked C->B->A"
+ for b in $BROKER_A $BROKER_B $BROKER_C; do
+ echo "Routes for $b"
+ $QPID_ROUTE_EXEC route list $b
+ done
+ fi
+}
+
+if test -d ${PYTHON_DIR} ; then
+ start_brokers
+ if (($VERBOSE)); then
+ echo "Running federated topic test against brokers on ports $PORT_A $PORT_B $PORT_C"
+ fi
+
+ for ((i=$SUBSCRIBERS ; i--; )); do
+ subscribe $i &
+ done
+
+ setup_routes
+
+ publish || exit 1
+fi
diff --git a/qpid/cpp/src/tests/federation.py b/qpid/cpp/src/tests/federation.py
new file mode 100755
index 0000000000..f5b62019e5
--- /dev/null
+++ b/qpid/cpp/src/tests/federation.py
@@ -0,0 +1,2793 @@
+#!/usr/bin/env python
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 sys
+from qpid.testlib import TestBase010
+from qpid.datatypes import Message
+from qpid.queue import Empty
+from qpid.util import URL
+import qpid.messaging
+from time import sleep, time
+
+
+class _FedBroker(object):
+ """
+ A proxy object for a remote broker. Contains connection and management
+ state.
+ """
+ def __init__(self, host, port,
+ conn=None, session=None, qmf_broker=None):
+ self.host = host
+ self.port = port
+ self.url = "%s:%d" % (host, port)
+ self.client_conn = None
+ self.client_session = None
+ self.qmf_broker = None
+ self.qmf_object = None
+ if conn is not None:
+ self.client_conn = conn
+ if session is not None:
+ self.client_session = session
+ if qmf_broker is not None:
+ self.qmf_broker = qmf_broker
+
+
+class FederationTests(TestBase010):
+
+ def remote_host(self):
+ return self.defines.get("remote-host", "localhost")
+
+ def remote_port(self):
+ return int(self.defines["remote-port"])
+
+ def verify_cleanup(self):
+ attempts = 0
+ total = len(self.qmf.getObjects(_class="bridge")) + len(self.qmf.getObjects(_class="link"))
+ while total > 0:
+ attempts += 1
+ if attempts >= 10:
+ self.fail("Bridges and links didn't clean up")
+ return
+ sleep(1)
+ total = len(self.qmf.getObjects(_class="bridge")) + len(self.qmf.getObjects(_class="link"))
+
+ def _setup_brokers(self):
+ ports = [self.remote_port()]
+ extra = self.defines.get("extra-brokers")
+ if extra:
+ for p in extra.split():
+ ports.append(int(p))
+
+ # broker[0] has already been set up.
+ self._brokers = [_FedBroker(self.broker.host,
+ self.broker.port,
+ self.conn,
+ self.session,
+ self.qmf_broker)]
+ self._brokers[0].qmf_object = self.qmf.getObjects(_class="broker")[0]
+
+ # setup remaining brokers
+ for _p in ports:
+ _b = _FedBroker(self.remote_host(), _p)
+ _b.client_conn = self.connect(host=self.remote_host(), port=_p)
+ _b.client_session = _b.client_conn.session("Fed_client_session_" + str(_p))
+ _b.qmf_broker = self.qmf.addBroker(_b.url)
+ for _bo in self.qmf.getObjects(_class="broker"):
+ if _bo.getBroker().getUrl() == _b.qmf_broker.getUrl():
+ _b.qmf_object = _bo
+ break
+ self._brokers.append(_b)
+
+ # add a new-style messaging connection to each broker
+ for _b in self._brokers:
+ _b.connection = qpid.messaging.Connection(_b.url)
+ _b.connection.open()
+
+ def _teardown_brokers(self):
+ """ Un-does _setup_brokers()
+ """
+ # broker[0] is configured at test setup, so it must remain configured
+ for _b in self._brokers[1:]:
+ self.qmf.delBroker(_b.qmf_broker)
+ if not _b.client_session.error():
+ _b.client_session.close(timeout=10)
+ _b.client_conn.close(timeout=10)
+ _b.connection.close()
+
+ def test_bridge_create_and_close(self):
+ self.startQmf();
+ qmf = self.qmf
+
+ broker = qmf.getObjects(_class="broker")[0]
+ result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0, result)
+
+ link = qmf.getObjects(_class="link")[0]
+ result = link.bridge(False, "amq.direct", "amq.direct", "my-key", "",
+ "", False, False, False, 0, 0)
+ self.assertEqual(result.status, 0, result)
+
+ bridge = qmf.getObjects(_class="bridge")[0]
+ result = bridge.close()
+ self.assertEqual(result.status, 0, result)
+
+ result = link.close()
+ self.assertEqual(result.status, 0, result)
+
+ self.verify_cleanup()
+
+ def test_pull_from_exchange(self):
+ """ This test uses an alternative method to manage links and bridges
+ via the broker object.
+ """
+ session = self.session
+
+ self.startQmf()
+ qmf = self.qmf
+ broker = qmf.getObjects(_class="broker")[0]
+
+ # create link
+ link_args = {"host":self.remote_host(), "port":self.remote_port(), "durable":False,
+ "authMechanism":"PLAIN", "username":"guest", "password":"guest",
+ "transport":"tcp"}
+ result = broker.create("link", "test-link-1", link_args, False)
+ self.assertEqual(result.status, 0, result)
+ link = qmf.getObjects(_class="link")[0]
+
+ # create bridge
+ bridge_args = {"link":"test-link-1", "src":"amq.direct", "dest":"amq.fanout",
+ "key":"my-key"}
+ result = broker.create("bridge", "test-bridge-1", bridge_args, False);
+ self.assertEqual(result.status, 0, result)
+ bridge = qmf.getObjects(_class="bridge")[0]
+
+ #setup queue to receive messages from local broker
+ session.queue_declare(queue="fed1", exclusive=True, auto_delete=True)
+ session.exchange_bind(queue="fed1", exchange="amq.fanout")
+ self.subscribe(queue="fed1", destination="f1")
+ queue = session.incoming("f1")
+ sleep(6)
+
+ #send messages to remote broker and confirm it is routed to local broker
+ r_conn = self.connect(host=self.remote_host(), port=self.remote_port())
+ r_session = r_conn.session("test_pull_from_exchange")
+
+ for i in range(1, 11):
+ dp = r_session.delivery_properties(routing_key="my-key")
+ r_session.message_transfer(destination="amq.direct", message=Message(dp, "Message %d" % i))
+
+ for i in range(1, 11):
+ msg = queue.get(timeout=5)
+ self.assertEqual("Message %d" % i, msg.body)
+ try:
+ extra = queue.get(timeout=1)
+ self.fail("Got unexpected message in queue: " + extra.body)
+ except Empty: None
+
+
+ result = broker.delete("bridge", "test-bridge-1", {})
+ self.assertEqual(result.status, 0, result)
+
+ result = broker.delete("link", "test-link-1", {})
+ self.assertEqual(result.status, 0, result)
+
+ self.verify_cleanup()
+
+ def test_push_to_exchange(self):
+ session = self.session
+
+ self.startQmf()
+ qmf = self.qmf
+ broker = qmf.getObjects(_class="broker")[0]
+ result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0, result)
+
+ link = qmf.getObjects(_class="link")[0]
+ result = link.bridge(False, "amq.direct", "amq.fanout", "my-key", "", "", False, True, False, 0, 0)
+ self.assertEqual(result.status, 0, result)
+
+ bridge = qmf.getObjects(_class="bridge")[0]
+
+ #setup queue to receive messages from remote broker
+ r_conn = self.connect(host=self.remote_host(), port=self.remote_port())
+ r_session = r_conn.session("test_push_to_exchange")
+ r_session.queue_declare(queue="fed1", exclusive=True, auto_delete=True)
+ r_session.exchange_bind(queue="fed1", exchange="amq.fanout")
+ self.subscribe(session=r_session, queue="fed1", destination="f1")
+ queue = r_session.incoming("f1")
+ sleep(6)
+
+ #send messages to local broker and confirm it is routed to remote broker
+ for i in range(1, 11):
+ dp = session.delivery_properties(routing_key="my-key")
+ session.message_transfer(destination="amq.direct", message=Message(dp, "Message %d" % i))
+
+ for i in range(1, 11):
+ msg = queue.get(timeout=5)
+ self.assertEqual("Message %d" % i, msg.body)
+ try:
+ extra = queue.get(timeout=1)
+ self.fail("Got unexpected message in queue: " + extra.body)
+ except Empty: None
+
+ result = bridge.close()
+ self.assertEqual(result.status, 0, result)
+ result = link.close()
+ self.assertEqual(result.status, 0, result)
+
+ self.verify_cleanup()
+
+ def test_pull_from_queue(self):
+ session = self.session
+
+ #setup queue on remote broker and add some messages
+ r_conn = self.connect(host=self.remote_host(), port=self.remote_port())
+ r_session = r_conn.session("test_pull_from_queue")
+ r_session.queue_declare(queue="my-bridge-queue", auto_delete=True)
+ for i in range(1, 6):
+ dp = r_session.delivery_properties(routing_key="my-bridge-queue")
+ r_session.message_transfer(message=Message(dp, "Message %d" % i))
+
+ #setup queue to receive messages from local broker
+ session.queue_declare(queue="fed1", exclusive=True, auto_delete=True)
+ session.exchange_bind(queue="fed1", exchange="amq.fanout")
+ self.subscribe(queue="fed1", destination="f1")
+ queue = session.incoming("f1")
+
+ self.startQmf()
+ qmf = self.qmf
+ broker = qmf.getObjects(_class="broker")[0]
+ result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0, result)
+
+ link = qmf.getObjects(_class="link")[0]
+ result = link.bridge(False, "my-bridge-queue", "amq.fanout", "my-key", "", "", True, False, False, 1, 0)
+ self.assertEqual(result.status, 0, result)
+
+ bridge = qmf.getObjects(_class="bridge")[0]
+ sleep(3)
+
+ #add some more messages (i.e. after bridge was created)
+ for i in range(6, 11):
+ dp = r_session.delivery_properties(routing_key="my-bridge-queue")
+ r_session.message_transfer(message=Message(dp, "Message %d" % i))
+
+ for i in range(1, 11):
+ try:
+ msg = queue.get(timeout=5)
+ self.assertEqual("Message %d" % i, msg.body)
+ except Empty:
+ self.fail("Failed to find expected message containing 'Message %d'" % i)
+ try:
+ extra = queue.get(timeout=1)
+ self.fail("Got unexpected message in queue: " + extra.body)
+ except Empty: None
+
+ result = bridge.close()
+ self.assertEqual(result.status, 0, result)
+ result = link.close()
+ self.assertEqual(result.status, 0, result)
+
+ self.verify_cleanup()
+
+ def test_pull_from_queue_recovery(self):
+ session = self.session
+
+ #setup queue on remote broker and add some messages
+ r_conn = self.connect(host=self.remote_host(), port=self.remote_port())
+ r_session = r_conn.session("test_pull_from_queue_recovery")
+ # disable auto-delete otherwise the detach of the fed session may
+ # delete the queue right after this test re-creates it.
+ r_session.queue_declare(queue="my-bridge-queue", auto_delete=False)
+ for i in range(1, 6):
+ dp = r_session.delivery_properties(routing_key="my-bridge-queue")
+ r_session.message_transfer(message=Message(dp, "Message %d" % i))
+
+ #setup queue to receive messages from local broker
+ session.queue_declare(queue="fed1", exclusive=True, auto_delete=True)
+ session.exchange_bind(queue="fed1", exchange="amq.fanout")
+ self.subscribe(queue="fed1", destination="f1")
+ queue = session.incoming("f1")
+
+ self.startQmf()
+ qmf = self.qmf
+ broker = qmf.getObjects(_class="broker")[0]
+ result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0, result)
+
+ link = qmf.getObjects(_class="link")[0]
+ result = link.bridge(False, "my-bridge-queue", "amq.fanout", "my-key", "", "", True, False, False, 1, 0)
+ self.assertEqual(result.status, 0, result)
+
+ bridge = qmf.getObjects(_class="bridge")[0]
+ sleep(5)
+
+ #recreate the remote bridge queue to invalidate the bridge session
+ r_session.queue_delete (queue="my-bridge-queue", if_empty=False, if_unused=False)
+ r_session.queue_declare(queue="my-bridge-queue", auto_delete=False)
+
+ #add some more messages (i.e. after bridge was created)
+ for i in range(6, 11):
+ dp = r_session.delivery_properties(routing_key="my-bridge-queue")
+ r_session.message_transfer(message=Message(dp, "Message %d" % i))
+
+ for i in range(1, 11):
+ try:
+ msg = queue.get(timeout=5)
+ self.assertEqual("Message %d" % i, msg.body)
+ except Empty:
+ self.fail("Failed to find expected message containing 'Message %d'" % i)
+ try:
+ extra = queue.get(timeout=1)
+ self.fail("Got unexpected message in queue: " + extra.body)
+ except Empty: None
+
+ result = bridge.close()
+ self.assertEqual(result.status, 0)
+ result = link.close()
+ self.assertEqual(result.status, 0)
+ self.verify_cleanup()
+ r_session.queue_delete (queue="my-bridge-queue", if_empty=False, if_unused=False)
+
+ def test_tracing_automatic(self):
+ remoteUrl = "%s:%d" % (self.remote_host(), self.remote_port())
+ self.startQmf()
+ l_broker = self.qmf_broker
+ r_broker = self.qmf.addBroker(remoteUrl)
+
+ l_brokerObj = self.qmf.getObjects(_class="broker", _broker=l_broker)[0]
+ r_brokerObj = self.qmf.getObjects(_class="broker", _broker=r_broker)[0]
+
+ l_res = l_brokerObj.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp")
+ r_res = r_brokerObj.connect(self.broker.host, self.broker.port, False, "PLAIN", "guest", "guest", "tcp")
+
+ self.assertEqual(l_res.status, 0)
+ self.assertEqual(r_res.status, 0)
+
+ l_link = self.qmf.getObjects(_class="link", _broker=l_broker)[0]
+ r_link = self.qmf.getObjects(_class="link", _broker=r_broker)[0]
+
+ l_res = l_link.bridge(False, "amq.direct", "amq.direct", "key", "", "", False, False, False, 0, 0)
+ r_res = r_link.bridge(False, "amq.direct", "amq.direct", "key", "", "", False, False, False, 0, 0)
+
+ self.assertEqual(l_res.status, 0)
+ self.assertEqual(r_res.status, 0)
+
+ count = 0
+ while l_link.state != "Operational" or r_link.state != "Operational":
+ count += 1
+ if count > 10:
+ self.fail("Fed links didn't become operational after 10 seconds")
+ sleep(1)
+ l_link = self.qmf.getObjects(_class="link", _broker=l_broker)[0]
+ r_link = self.qmf.getObjects(_class="link", _broker=r_broker)[0]
+ sleep(3)
+
+ #setup queue to receive messages from local broker
+ session = self.session
+ session.queue_declare(queue="fed1", exclusive=True, auto_delete=True)
+ session.exchange_bind(queue="fed1", exchange="amq.direct", binding_key="key")
+ self.subscribe(queue="fed1", destination="f1")
+ queue = session.incoming("f1")
+
+ #setup queue on remote broker and add some messages
+ r_conn = self.connect(host=self.remote_host(), port=self.remote_port())
+ r_session = r_conn.session("test_trace")
+ for i in range(1, 11):
+ dp = r_session.delivery_properties(routing_key="key")
+ r_session.message_transfer(destination="amq.direct", message=Message(dp, "Message %d" % i))
+
+ for i in range(1, 11):
+ try:
+ msg = queue.get(timeout=5)
+ mp = msg.get("message_properties").application_headers
+ self.assertEqual(mp.__class__, dict)
+ self.assertEqual(mp['x-qpid.trace'], 'REMOTE') # check that the federation-tag override works
+ self.assertEqual("Message %d" % i, msg.body)
+ except Empty:
+ self.fail("Failed to find expected message containing 'Message %d'" % i)
+ try:
+ extra = queue.get(timeout=1)
+ self.fail("Got unexpected message in queue: " + extra.body)
+ except Empty: None
+
+ def test_tracing(self):
+ session = self.session
+
+ self.startQmf()
+ qmf = self.qmf
+ broker = qmf.getObjects(_class="broker")[0]
+ result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ link = qmf.getObjects(_class="link")[0]
+ result = link.bridge(False, "amq.direct", "amq.fanout", "my-key", "my-bridge-id",
+ "exclude-me,also-exclude-me", False, False, False, 0, 0)
+ self.assertEqual(result.status, 0)
+ bridge = qmf.getObjects(_class="bridge")[0]
+
+ #setup queue to receive messages from local broker
+ session.queue_declare(queue="fed1", exclusive=True, auto_delete=True)
+ session.exchange_bind(queue="fed1", exchange="amq.fanout")
+ self.subscribe(queue="fed1", destination="f1")
+ queue = session.incoming("f1")
+ sleep(6)
+
+ #send messages to remote broker and confirm it is routed to local broker
+ r_conn = self.connect(host=self.remote_host(), port=self.remote_port())
+ r_session = r_conn.session("test_tracing")
+
+ trace = [None, "exclude-me", "a,exclude-me,b", "also-exclude-me,c", "dont-exclude-me"]
+ body = ["yes", "first-bad", "second-bad", "third-bad", "yes"]
+ for b, t in zip(body, trace):
+ headers = {}
+ if (t): headers["x-qpid.trace"]=t
+ dp = r_session.delivery_properties(routing_key="my-key", ttl=1000*60*5)
+ mp = r_session.message_properties(application_headers=headers)
+ r_session.message_transfer(destination="amq.direct", message=Message(dp, mp, b))
+
+ for e in ["my-bridge-id", "dont-exclude-me,my-bridge-id"]:
+ msg = queue.get(timeout=5)
+ self.assertEqual("yes", msg.body)
+ self.assertEqual(e, self.getAppHeader(msg, "x-qpid.trace"))
+ assert(msg.get("delivery_properties").ttl > 0)
+ assert(msg.get("delivery_properties").ttl < 1000*60*50)
+
+ try:
+ extra = queue.get(timeout=1)
+ self.fail("Got unexpected message in queue: " + extra.body)
+ except Empty: None
+
+ result = bridge.close()
+ self.assertEqual(result.status, 0)
+ result = link.close()
+ self.assertEqual(result.status, 0)
+
+ self.verify_cleanup()
+
+ def test_dynamic_fanout(self):
+ session = self.session
+ r_conn = self.connect(host=self.remote_host(), port=self.remote_port())
+ r_session = r_conn.session("test_dynamic_fanout")
+
+ session.exchange_declare(exchange="fed.fanout", type="fanout")
+ r_session.exchange_declare(exchange="fed.fanout", type="fanout")
+
+ self.startQmf()
+ qmf = self.qmf
+ broker = qmf.getObjects(_class="broker")[0]
+ result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ link = qmf.getObjects(_class="link")[0]
+ result = link.bridge(False, "fed.fanout", "fed.fanout", "", "", "", False, False, True, 0, 0)
+ self.assertEqual(result.status, 0)
+ bridge = qmf.getObjects(_class="bridge")[0]
+ sleep(5)
+
+ session.queue_declare(queue="fed1", exclusive=True, auto_delete=True)
+ session.exchange_bind(queue="fed1", exchange="fed.fanout")
+ self.subscribe(queue="fed1", destination="f1")
+ queue = session.incoming("f1")
+
+ for i in range(1, 11):
+ dp = r_session.delivery_properties()
+ r_session.message_transfer(destination="fed.fanout", message=Message(dp, "Message %d" % i))
+
+ for i in range(1, 11):
+ msg = queue.get(timeout=5)
+ self.assertEqual("Message %d" % i, msg.body)
+ try:
+ extra = queue.get(timeout=1)
+ self.fail("Got unexpected message in queue: " + extra.body)
+ except Empty: None
+
+ result = bridge.close()
+ self.assertEqual(result.status, 0)
+ result = link.close()
+ self.assertEqual(result.status, 0)
+
+ self.verify_cleanup()
+
+
+ def test_dynamic_direct(self):
+ session = self.session
+ r_conn = self.connect(host=self.remote_host(), port=self.remote_port())
+ r_session = r_conn.session("test_dynamic_direct")
+
+ session.exchange_declare(exchange="fed.direct", type="direct")
+ r_session.exchange_declare(exchange="fed.direct", type="direct")
+
+ self.startQmf()
+ qmf = self.qmf
+ broker = qmf.getObjects(_class="broker")[0]
+ result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ link = qmf.getObjects(_class="link")[0]
+ result = link.bridge(False, "fed.direct", "fed.direct", "", "", "", False, False, True, 0, 0)
+ self.assertEqual(result.status, 0)
+ bridge = qmf.getObjects(_class="bridge")[0]
+ sleep(5)
+
+ session.queue_declare(queue="fed1", exclusive=True, auto_delete=True)
+ session.exchange_bind(queue="fed1", exchange="fed.direct", binding_key="fd-key")
+ self.subscribe(queue="fed1", destination="f1")
+ queue = session.incoming("f1")
+
+ for i in range(1, 11):
+ dp = r_session.delivery_properties(routing_key="fd-key")
+ r_session.message_transfer(destination="fed.direct", message=Message(dp, "Message %d" % i))
+
+ for i in range(1, 11):
+ msg = queue.get(timeout=5)
+ self.assertEqual("Message %d" % i, msg.body)
+ try:
+ extra = queue.get(timeout=1)
+ self.fail("Got unexpected message in queue: " + extra.body)
+ except Empty: None
+
+ result = bridge.close()
+ self.assertEqual(result.status, 0)
+ result = link.close()
+ self.assertEqual(result.status, 0)
+
+ self.verify_cleanup()
+
+ def test_dynamic_topic(self):
+ session = self.session
+ r_conn = self.connect(host=self.remote_host(), port=self.remote_port())
+ r_session = r_conn.session("test_dynamic_topic")
+
+ session.exchange_declare(exchange="fed.topic", type="topic")
+ r_session.exchange_declare(exchange="fed.topic", type="topic")
+
+ self.startQmf()
+ qmf = self.qmf
+ broker = qmf.getObjects(_class="broker")[0]
+ result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ link = qmf.getObjects(_class="link")[0]
+ result = link.bridge(False, "fed.topic", "fed.topic", "", "", "", False, False, True, 0, 0)
+ self.assertEqual(result.status, 0)
+ bridge = qmf.getObjects(_class="bridge")[0]
+ sleep(5)
+
+ session.queue_declare(queue="fed1", exclusive=True, auto_delete=True)
+ session.exchange_bind(queue="fed1", exchange="fed.topic", binding_key="ft-key.#")
+ self.subscribe(queue="fed1", destination="f1")
+ queue = session.incoming("f1")
+
+ for i in range(1, 11):
+ dp = r_session.delivery_properties(routing_key="ft-key.one.two")
+ r_session.message_transfer(destination="fed.topic", message=Message(dp, "Message %d" % i))
+
+ for i in range(1, 11):
+ msg = queue.get(timeout=5)
+ self.assertEqual("Message %d" % i, msg.body)
+ try:
+ extra = queue.get(timeout=1)
+ self.fail("Got unexpected message in queue: " + extra.body)
+ except Empty: None
+
+ result = bridge.close()
+ self.assertEqual(result.status, 0)
+ result = link.close()
+ self.assertEqual(result.status, 0)
+
+ self.verify_cleanup()
+
+ def test_dynamic_topic_reorigin(self):
+ session = self.session
+ r_conn = self.connect(host=self.remote_host(), port=self.remote_port())
+ r_session = r_conn.session("test_dynamic_topic_reorigin")
+
+ session.exchange_declare(exchange="fed.topic_reorigin", type="topic")
+ r_session.exchange_declare(exchange="fed.topic_reorigin", type="topic")
+
+ session.exchange_declare(exchange="fed.topic_reorigin_2", type="topic")
+ r_session.exchange_declare(exchange="fed.topic_reorigin_2", type="topic")
+
+ self.startQmf()
+ qmf = self.qmf
+ broker = qmf.getObjects(_class="broker")[0]
+ result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ session.queue_declare(queue="fed2", exclusive=True, auto_delete=True)
+ session.exchange_bind(queue="fed2", exchange="fed.topic_reorigin_2", binding_key="ft-key.one.#")
+ self.subscribe(queue="fed2", destination="f2")
+ queue2 = session.incoming("f2")
+
+ link = qmf.getObjects(_class="link")[0]
+ result = link.bridge(False, "fed.topic_reorigin", "fed.topic_reorigin", "", "", "", False, False, True, 0, 0)
+ self.assertEqual(result.status, 0)
+ result = link.bridge(False, "fed.topic_reorigin_2", "fed.topic_reorigin_2", "", "", "", False, False, True, 0, 0)
+ self.assertEqual(result.status, 0)
+
+ bridge = qmf.getObjects(_class="bridge")[0]
+ bridge2 = qmf.getObjects(_class="bridge")[1]
+ sleep(5)
+
+ session.queue_declare(queue="fed1", exclusive=True, auto_delete=True)
+ session.exchange_bind(queue="fed1", exchange="fed.topic_reorigin", binding_key="ft-key.#")
+ self.subscribe(queue="fed1", destination="f1")
+ queue = session.incoming("f1")
+
+ for i in range(1, 11):
+ dp = r_session.delivery_properties(routing_key="ft-key.one.two")
+ r_session.message_transfer(destination="fed.topic_reorigin", message=Message(dp, "Message %d" % i))
+
+ for i in range(1, 11):
+ msg = queue.get(timeout=5)
+ self.assertEqual("Message %d" % i, msg.body)
+ try:
+ extra = queue.get(timeout=1)
+ self.fail("Got unexpected message in queue: " + extra.body)
+ except Empty: None
+
+ result = bridge.close()
+ self.assertEqual(result.status, 0)
+ result = bridge2.close()
+ self.assertEqual(result.status, 0)
+
+ # extra check: verify we don't leak bridge objects - keep the link
+ # around and verify the bridge count has gone to zero
+
+ attempts = 0
+ bridgeCount = len(qmf.getObjects(_class="bridge"))
+ while bridgeCount > 0:
+ attempts += 1
+ if attempts >= 5:
+ self.fail("Bridges didn't clean up")
+ return
+ sleep(1)
+ bridgeCount = len(qmf.getObjects(_class="bridge"))
+
+ result = link.close()
+ self.assertEqual(result.status, 0)
+
+ self.verify_cleanup()
+
+ def test_dynamic_direct_reorigin(self):
+ session = self.session
+ r_conn = self.connect(host=self.remote_host(), port=self.remote_port())
+ r_session = r_conn.session("test_dynamic_direct_reorigin")
+
+ session.exchange_declare(exchange="fed.direct_reorigin", type="direct")
+ r_session.exchange_declare(exchange="fed.direct_reorigin", type="direct")
+
+ session.exchange_declare(exchange="fed.direct_reorigin_2", type="direct")
+ r_session.exchange_declare(exchange="fed.direct_reorigin_2", type="direct")
+
+ self.startQmf()
+ qmf = self.qmf
+ broker = qmf.getObjects(_class="broker")[0]
+ result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ session.queue_declare(queue="fed2", exclusive=True, auto_delete=True)
+ session.exchange_bind(queue="fed2", exchange="fed.direct_reorigin_2", binding_key="ft-key.two")
+ self.subscribe(queue="fed2", destination="f2")
+ queue2 = session.incoming("f2")
+
+ link = qmf.getObjects(_class="link")[0]
+ result = link.bridge(False, "fed.direct_reorigin", "fed.direct_reorigin", "", "", "", False, False, True, 0, 0)
+ self.assertEqual(result.status, 0)
+ result = link.bridge(False, "fed.direct_reorigin_2", "fed.direct_reorigin_2", "", "", "", False, False, True, 0, 0)
+ self.assertEqual(result.status, 0)
+
+ bridge = qmf.getObjects(_class="bridge")[0]
+ bridge2 = qmf.getObjects(_class="bridge")[1]
+ sleep(5)
+
+ session.queue_declare(queue="fed1", exclusive=True, auto_delete=True)
+ session.exchange_bind(queue="fed1", exchange="fed.direct_reorigin", binding_key="ft-key.one")
+ self.subscribe(queue="fed1", destination="f1")
+ queue = session.incoming("f1")
+
+ for i in range(1, 11):
+ dp = r_session.delivery_properties(routing_key="ft-key.one")
+ r_session.message_transfer(destination="fed.direct_reorigin", message=Message(dp, "Message %d" % i))
+
+ for i in range(1, 11):
+ msg = queue.get(timeout=5)
+ self.assertEqual("Message %d" % i, msg.body)
+ try:
+ extra = queue.get(timeout=1)
+ self.fail("Got unexpected message in queue: " + extra.body)
+ except Empty: None
+
+ result = bridge.close()
+ self.assertEqual(result.status, 0)
+
+ # Extra test: don't explicitly close() bridge2. When the link is closed,
+ # it should clean up bridge2 automagically. verify_cleanup() will detect
+ # if bridge2 isn't cleaned up and will fail the test.
+ #
+ #result = bridge2.close()
+ #self.assertEqual(result.status, 0)
+ result = link.close()
+ self.assertEqual(result.status, 0)
+
+ self.verify_cleanup()
+
+ def test_dynamic_headers_any(self):
+ self.do_test_dynamic_headers('any')
+
+ def test_dynamic_headers_all(self):
+ self.do_test_dynamic_headers('all')
+
+
+ def do_test_dynamic_headers(self, match_mode):
+ session = self.session
+ r_conn = self.connect(host=self.remote_host(), port=self.remote_port())
+ r_session = r_conn.session("test_dynamic_headers_%s" % match_mode)
+
+ session.exchange_declare(exchange="fed.headers", type="headers")
+ r_session.exchange_declare(exchange="fed.headers", type="headers")
+
+ self.startQmf()
+ qmf = self.qmf
+
+ broker = qmf.getObjects(_class="broker")[0]
+ result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ link = qmf.getObjects(_class="link")[0]
+ result = link.bridge(False, "fed.headers", "fed.headers", "", "", "", False, False, True, 0, 0)
+ self.assertEqual(result.status, 0)
+ bridge = qmf.getObjects(_class="bridge")[0]
+ sleep(5)
+
+ session.queue_declare(queue="fed1", exclusive=True, auto_delete=True)
+ session.exchange_bind(queue="fed1", exchange="fed.headers", binding_key="key1", arguments={'x-match':match_mode, 'class':'first'})
+ self.subscribe(queue="fed1", destination="f1")
+ queue = session.incoming("f1")
+
+ props = r_session.message_properties(application_headers={'class':'first'})
+ for i in range(1, 11):
+ r_session.message_transfer(destination="fed.headers", message=Message(props, "Message %d" % i))
+
+ for i in range(1, 11):
+ msg = queue.get(timeout=5)
+ content = msg.body
+ self.assertEqual("Message %d" % i, msg.body)
+ try:
+ extra = queue.get(timeout=1)
+ self.fail("Got unexpected message in queue: " + extra.body)
+ except Empty: None
+
+ result = bridge.close()
+ self.assertEqual(result.status, 0)
+ result = link.close()
+ self.assertEqual(result.status, 0)
+
+ self.verify_cleanup()
+
+ def test_dynamic_headers_reorigin(self):
+ session = self.session
+ r_conn = self.connect(host=self.remote_host(), port=self.remote_port())
+ r_session = r_conn.session("test_dynamic_headers_reorigin")
+
+ session.exchange_declare(exchange="fed.headers_reorigin", type="headers")
+ r_session.exchange_declare(exchange="fed.headers_reorigin", type="headers")
+
+ session.exchange_declare(exchange="fed.headers_reorigin_2", type="headers")
+ r_session.exchange_declare(exchange="fed.headers_reorigin_2", type="headers")
+
+ self.startQmf()
+ qmf = self.qmf
+ broker = qmf.getObjects(_class="broker")[0]
+ result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ session.queue_declare(queue="fed2", exclusive=True, auto_delete=True)
+ session.exchange_bind(queue="fed2", exchange="fed.headers_reorigin_2", binding_key="key2", arguments={'x-match':'any', 'class':'second'})
+ self.subscribe(queue="fed2", destination="f2")
+ queue2 = session.incoming("f2")
+
+ link = qmf.getObjects(_class="link")[0]
+ result = link.bridge(False, "fed.headers_reorigin", "fed.headers_reorigin", "", "", "", False, False, True, 0, 0)
+ self.assertEqual(result.status, 0)
+ result = link.bridge(False, "fed.headers_reorigin_2", "fed.headers_reorigin_2", "", "", "", False, False, True, 0, 0)
+ self.assertEqual(result.status, 0)
+
+ bridge = qmf.getObjects(_class="bridge")[0]
+ bridge2 = qmf.getObjects(_class="bridge")[1]
+ sleep(5)
+
+ session.queue_declare(queue="fed1", exclusive=True, auto_delete=True)
+ session.exchange_bind(queue="fed1", exchange="fed.headers_reorigin", binding_key="key1", arguments={'x-match':'any', 'class':'first'})
+ self.subscribe(queue="fed1", destination="f1")
+ queue = session.incoming("f1")
+
+ props = r_session.message_properties(application_headers={'class':'first'})
+ for i in range(1, 11):
+ r_session.message_transfer(destination="fed.headers_reorigin", message=Message(props, "Message %d" % i))
+
+ for i in range(1, 11):
+ msg = queue.get(timeout=5)
+ self.assertEqual("Message %d" % i, msg.body)
+ try:
+ extra = queue.get(timeout=1)
+ self.fail("Got unexpected message in queue: " + extra.body)
+ except Empty: None
+
+ result = bridge.close()
+ self.assertEqual(result.status, 0)
+
+ # Extra test: don't explicitly close() bridge2. When the link is closed,
+ # it should clean up bridge2 automagically. verify_cleanup() will detect
+ # if bridge2 isn't cleaned up and will fail the test.
+ #
+ #result = bridge2.close()
+ #self.assertEqual(result.status, 0)
+ result = link.close()
+ self.assertEqual(result.status, 0)
+
+ self.verify_cleanup()
+
+ def test_dynamic_headers_unbind(self):
+ session = self.session
+ r_conn = self.connect(host=self.remote_host(), port=self.remote_port())
+ r_session = r_conn.session("test_dynamic_headers_unbind")
+
+ session.exchange_declare(exchange="fed.headers_unbind", type="headers")
+ r_session.exchange_declare(exchange="fed.headers_unbind", type="headers")
+
+ self.startQmf()
+ qmf = self.qmf
+
+ broker = qmf.getObjects(_class="broker")[0]
+ result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ link = qmf.getObjects(_class="link")[0]
+ result = link.bridge(False, "fed.headers_unbind", "fed.headers_unbind", "", "", "", False, False, True, 0, 0)
+ self.assertEqual(result.status, 0)
+ bridge = qmf.getObjects(_class="bridge")[0]
+ sleep(5)
+
+ session.queue_declare(queue="fed1", exclusive=True, auto_delete=True)
+ queue = qmf.getObjects(_class="queue", name="fed1")[0]
+ queue.update()
+ self.assertEqual(queue.bindingCount, 1,
+ "bindings not accounted for (expected 1, got %d)" % queue.bindingCount)
+
+ session.exchange_bind(queue="fed1", exchange="fed.headers_unbind", binding_key="key1", arguments={'x-match':'any', 'class':'first'})
+ queue.update()
+ self.assertEqual(queue.bindingCount, 2,
+ "bindings not accounted for (expected 2, got %d)" % queue.bindingCount)
+
+ session.exchange_unbind(queue="fed1", exchange="fed.headers_unbind", binding_key="key1")
+ queue.update()
+ self.assertEqual(queue.bindingCount, 1,
+ "bindings not accounted for (expected 1, got %d)" % queue.bindingCount)
+
+ result = bridge.close()
+ self.assertEqual(result.status, 0)
+ result = link.close()
+ self.assertEqual(result.status, 0)
+
+ self.verify_cleanup()
+
+
+ def test_dynamic_headers_xml(self):
+ session = self.session
+ r_conn = self.connect(host=self.remote_host(), port=self.remote_port())
+ r_session = r_conn.session("test_dynamic_headers_xml")
+
+ session.exchange_declare(exchange="fed.xml", type="xml")
+ r_session.exchange_declare(exchange="fed.xml", type="xml")
+
+ self.startQmf()
+ qmf = self.qmf
+
+ broker = qmf.getObjects(_class="broker")[0]
+ result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ link = qmf.getObjects(_class="link")[0]
+ result = link.bridge(False, "fed.xml", "fed.xml", "", "", "", False, False, True, 0, 0)
+
+ self.assertEqual(result.status, 0)
+ bridge = qmf.getObjects(_class="bridge")[0]
+ sleep(5)
+
+ session.queue_declare(queue="fed1", exclusive=True, auto_delete=True)
+ session.exchange_bind(queue="fed1", exchange="fed.xml", binding_key="key1", arguments={'xquery':'true()'})
+ self.subscribe(queue="fed1", destination="f1")
+ queue = session.incoming("f1")
+
+ props = r_session.delivery_properties(routing_key="key1")
+ for i in range(1, 11):
+ r_session.message_transfer(destination="fed.xml", message=Message(props, "Message %d" % i))
+
+ for i in range(1, 11):
+ msg = queue.get(timeout=5)
+ content = msg.body
+ self.assertEqual("Message %d" % i, msg.body)
+ try:
+ extra = queue.get(timeout=1)
+ self.fail("Got unexpected message in queue: " + extra.body)
+ except Empty: None
+
+ result = bridge.close()
+ self.assertEqual(result.status, 0)
+ result = link.close()
+ self.assertEqual(result.status, 0)
+
+ self.verify_cleanup()
+
+ def test_dynamic_headers_reorigin_xml(self):
+ session = self.session
+ r_conn = self.connect(host=self.remote_host(), port=self.remote_port())
+ r_session = r_conn.session("test_dynamic_headers_reorigin_xml")
+
+ session.exchange_declare(exchange="fed.xml_reorigin", type="xml")
+ r_session.exchange_declare(exchange="fed.xml_reorigin", type="xml")
+
+ session.exchange_declare(exchange="fed.xml_reorigin_2", type="xml")
+ r_session.exchange_declare(exchange="fed.xml_reorigin_2", type="xml")
+
+ self.startQmf()
+ qmf = self.qmf
+ broker = qmf.getObjects(_class="broker")[0]
+ result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ session.queue_declare(queue="fed2", exclusive=True, auto_delete=True)
+ session.exchange_bind(queue="fed2", exchange="fed.xml_reorigin_2", binding_key="key2", arguments={'xquery':'true()'})
+ self.subscribe(queue="fed2", destination="f2")
+ queue2 = session.incoming("f2")
+
+ link = qmf.getObjects(_class="link")[0]
+ result = link.bridge(False, "fed.xml_reorigin", "fed.xml_reorigin", "", "", "", False, False, True, 0, 0)
+
+ self.assertEqual(result.status, 0)
+ result = link.bridge(False, "fed.xml_reorigin_2", "fed.xml_reorigin_2", "", "", "", False, False, True, 0, 0)
+ self.assertEqual(result.status, 0)
+
+ bridge = qmf.getObjects(_class="bridge")[0]
+ bridge2 = qmf.getObjects(_class="bridge")[1]
+ sleep(5)
+
+ foo=qmf.getObjects(_class="link")
+ session.queue_declare(queue="fed1", exclusive=True, auto_delete=True)
+ session.exchange_bind(queue="fed1", exchange="fed.xml_reorigin", binding_key="key1", arguments={'xquery':'true()'})
+ self.subscribe(queue="fed1", destination="f1")
+ queue = session.incoming("f1")
+
+ props = r_session.delivery_properties(routing_key="key1")
+ for i in range(1, 11):
+ r_session.message_transfer(destination="fed.xml_reorigin", message=Message(props, "Message %d" % i))
+
+ for i in range(1, 11):
+ msg = queue.get(timeout=5)
+ self.assertEqual("Message %d" % i, msg.body)
+ try:
+ extra = queue.get(timeout=1)
+ self.fail("Got unexpected message in queue: " + extra.body)
+ except Empty: None
+
+ result = bridge.close()
+ self.assertEqual(result.status, 0)
+
+ # Extra test: don't explicitly close() bridge2. When the link is closed,
+ # it should clean up bridge2 automagically. verify_cleanup() will detect
+ # if bridge2 isn't cleaned up and will fail the test.
+ #
+ #result = bridge2.close()
+ #self.assertEqual(result.status, 0)
+ result = link.close()
+ self.assertEqual(result.status, 0)
+
+ self.verify_cleanup()
+
+ def test_dynamic_headers_unbind_xml(self):
+ session = self.session
+ r_conn = self.connect(host=self.remote_host(), port=self.remote_port())
+ r_session = r_conn.session("test_dynamic_xml_unbind")
+
+ session.exchange_declare(exchange="fed.xml_unbind", type="xml")
+ r_session.exchange_declare(exchange="fed.xml_unbind", type="xml")
+
+ self.startQmf()
+ qmf = self.qmf
+
+ broker = qmf.getObjects(_class="broker")[0]
+ result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ link = qmf.getObjects(_class="link")[0]
+ result = link.bridge(False, "fed.xml_unbind", "fed.xml_unbind", "", "", "", False, False, True, 0, 0)
+
+ self.assertEqual(result.status, 0)
+ bridge = qmf.getObjects(_class="bridge")[0]
+ sleep(5)
+
+ session.queue_declare(queue="fed1", exclusive=True, auto_delete=True)
+ queue = qmf.getObjects(_class="queue", name="fed1")[0]
+ queue.update()
+ self.assertEqual(queue.bindingCount, 1,
+ "bindings not accounted for (expected 1, got %d)" % queue.bindingCount)
+
+ session.exchange_bind(queue="fed1", exchange="fed.xml_unbind", binding_key="key1", arguments={'xquery':'true()'})
+ queue.update()
+ self.assertEqual(queue.bindingCount, 2,
+ "bindings not accounted for (expected 2, got %d)" % queue.bindingCount)
+
+ session.exchange_unbind(queue="fed1", exchange="fed.xml_unbind", binding_key="key1")
+ queue.update()
+ self.assertEqual(queue.bindingCount, 1,
+ "bindings not accounted for (expected 1, got %d)" % queue.bindingCount)
+
+ result = bridge.close()
+ self.assertEqual(result.status, 0)
+ result = link.close()
+ self.assertEqual(result.status, 0)
+
+ self.verify_cleanup()
+
+
+ def test_dynamic_topic_nodup(self):
+ """Verify that a message whose routing key matches more than one
+ binding does not get duplicated to the same queue.
+ """
+ session = self.session
+ r_conn = self.connect(host=self.remote_host(), port=self.remote_port())
+ r_session = r_conn.session("test_dynamic_topic_nodup")
+
+ session.exchange_declare(exchange="fed.topic", type="topic")
+ r_session.exchange_declare(exchange="fed.topic", type="topic")
+
+ self.startQmf()
+ qmf = self.qmf
+ broker = qmf.getObjects(_class="broker")[0]
+ result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ link = qmf.getObjects(_class="link")[0]
+ result = link.bridge(False, "fed.topic", "fed.topic", "", "", "", False, False, True, 0, 0)
+ self.assertEqual(result.status, 0)
+ bridge = qmf.getObjects(_class="bridge")[0]
+ sleep(5)
+
+ session.queue_declare(queue="fed1", exclusive=True, auto_delete=True)
+ session.exchange_bind(queue="fed1", exchange="fed.topic", binding_key="red.*")
+ session.exchange_bind(queue="fed1", exchange="fed.topic", binding_key="*.herring")
+
+ self.subscribe(queue="fed1", destination="f1")
+ queue = session.incoming("f1")
+
+ for i in range(1, 11):
+ dp = r_session.delivery_properties(routing_key="red.herring")
+ r_session.message_transfer(destination="fed.topic", message=Message(dp, "Message %d" % i))
+
+ for i in range(1, 11):
+ msg = queue.get(timeout=5)
+ self.assertEqual("Message %d" % i, msg.body)
+ try:
+ extra = queue.get(timeout=1)
+ self.fail("Got unexpected message in queue: " + extra.body)
+ except Empty: None
+
+ result = bridge.close()
+ self.assertEqual(result.status, 0)
+ result = link.close()
+ self.assertEqual(result.status, 0)
+
+ self.verify_cleanup()
+
+
+ def test_dynamic_direct_route_prop(self):
+ """ Set up a tree of uni-directional routes across the direct exchange.
+ Bind the same key to the same queues on the leaf nodes. Verify a
+ message sent with the routing key transverses the tree an arrives at
+ each leaf. Remove one leaf's queue, and verify that messages still
+ reach the other leaf.
+
+ Route Topology:
+
+ +---> B2 queue:"test-queue", binding key:"spudboy"
+ B0 --> B1 --+
+ +---> B3 queue:"test-queue", binding key:"spudboy"
+ """
+ session = self.session
+
+ # create the federation
+
+ self.startQmf()
+ qmf = self.qmf
+
+ self._setup_brokers()
+
+ # create direct exchange on each broker, and retrieve the corresponding
+ # management object for that exchange
+
+ exchanges=[]
+ for _b in self._brokers:
+ _b.client_session.exchange_declare(exchange="fedX.direct", type="direct")
+ self.assertEqual(_b.client_session.exchange_query(name="fedX.direct").type,
+ "direct", "exchange_declare failed!")
+ # pull the exchange out of qmf...
+ retries = 0
+ my_exchange = None
+ while my_exchange is None:
+ objs = qmf.getObjects(_broker=_b.qmf_broker, _class="exchange")
+ for ooo in objs:
+ if ooo.name == "fedX.direct":
+ my_exchange = ooo
+ break
+ if my_exchange is None:
+ retries += 1
+ self.failIfEqual(retries, 10,
+ "QMF failed to find new exchange!")
+ sleep(1)
+ exchanges.append(my_exchange)
+
+ self.assertEqual(len(exchanges), len(self._brokers), "Exchange creation failed!")
+
+ # connect B0 --> B1
+ result = self._brokers[1].qmf_object.connect(self._brokers[0].host,
+ self._brokers[0].port,
+ False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ # connect B1 --> B2
+ result = self._brokers[2].qmf_object.connect(self._brokers[1].host,
+ self._brokers[1].port,
+ False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ # connect B1 --> B3
+ result = self._brokers[3].qmf_object.connect(self._brokers[1].host,
+ self._brokers[1].port,
+ False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ # for each link, bridge the "fedX.direct" exchanges:
+
+ for _l in qmf.getObjects(_class="link"):
+ # print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.getBroker())))
+ result = _l.bridge(False, # durable
+ "fedX.direct", # src
+ "fedX.direct", # dst
+ "", # key
+ "", # tag
+ "", # excludes
+ False, # srcIsQueue
+ False, # srcIsLocal
+ True, # dynamic
+ 0, # sync
+ 0) # credit
+ self.assertEqual(result.status, 0)
+
+ # wait for the inter-broker links to become operational
+ retries = 0
+ operational = False
+ while not operational:
+ operational = True
+ for _l in qmf.getObjects(_class="link"):
+ #print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.state)))
+ if _l.state != "Operational":
+ operational = False
+ if not operational:
+ retries += 1
+ self.failIfEqual(retries, 10,
+ "inter-broker links failed to become operational.")
+ sleep(1)
+
+ # @todo - There is no way to determine when the bridge objects become
+ # active. Hopefully, this is long enough!
+ sleep(6)
+
+ # create a queue on B2, bound to "spudboy"
+ self._brokers[2].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True)
+ self._brokers[2].client_session.exchange_bind(queue="fedX1", exchange="fedX.direct", binding_key="spudboy")
+
+ # create a queue on B3, bound to "spudboy"
+ self._brokers[3].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True)
+ self._brokers[3].client_session.exchange_bind(queue="fedX1", exchange="fedX.direct", binding_key="spudboy")
+
+ # subscribe to messages arriving on B2's queue
+ self.subscribe(self._brokers[2].client_session, queue="fedX1", destination="f1")
+ queue_2 = self._brokers[2].client_session.incoming("f1")
+
+ # subscribe to messages arriving on B3's queue
+ self.subscribe(self._brokers[3].client_session, queue="fedX1", destination="f1")
+ queue_3 = self._brokers[3].client_session.incoming("f1")
+
+ # wait until the binding key has propagated to each broker (twice at
+ # broker B1). Work backwards from binding brokers.
+
+ binding_counts = [1, 2, 1, 1]
+ self.assertEqual(len(binding_counts), len(exchanges), "Update Test!")
+ for i in range(3,-1,-1):
+ retries = 0
+ exchanges[i].update()
+ while exchanges[i].bindingCount < binding_counts[i]:
+ retries += 1
+ self.failIfEqual(retries, 10,
+ "binding failed to propagate to broker %d"
+ % i)
+ sleep(3)
+ exchanges[i].update()
+
+ # send 10 msgs from B0
+ for i in range(1, 11):
+ dp = self._brokers[0].client_session.delivery_properties(routing_key="spudboy")
+ self._brokers[0].client_session.message_transfer(destination="fedX.direct", message=Message(dp, "Message_drp %d" % i))
+
+ # wait for 10 messages to be forwarded from B0->B1,
+ # 10 messages from B1->B2,
+ # and 10 messages from B1->B3
+ retries = 0
+ for ex in exchanges:
+ ex.update()
+ while (exchanges[0].msgReceives != 10 or exchanges[0].msgRoutes != 10 or
+ exchanges[1].msgReceives != 10 or exchanges[1].msgRoutes != 20 or
+ exchanges[2].msgReceives != 10 or exchanges[2].msgRoutes != 10 or
+ exchanges[3].msgReceives != 10 or exchanges[3].msgRoutes != 10):
+ retries += 1
+ self.failIfEqual(retries, 10,
+ "federation failed to route msgs %d:%d %d:%d %d:%d %d:%d"
+ % (exchanges[0].msgReceives,
+ exchanges[0].msgRoutes,
+ exchanges[1].msgReceives,
+ exchanges[1].msgRoutes,
+ exchanges[2].msgReceives,
+ exchanges[2].msgRoutes,
+ exchanges[3].msgReceives,
+ exchanges[3].msgRoutes))
+ sleep(1)
+ for ex in exchanges:
+ ex.update()
+
+ # get exactly 10 msgs on B2 and B3
+ for i in range(1, 11):
+ msg = queue_2.get(timeout=5)
+ self.assertEqual("Message_drp %d" % i, msg.body)
+ msg = queue_3.get(timeout=5)
+ self.assertEqual("Message_drp %d" % i, msg.body)
+
+ try:
+ extra = queue_2.get(timeout=1)
+ self.fail("Got unexpected message in queue_2: " + extra.body)
+ except Empty: None
+
+ try:
+ extra = queue_3.get(timeout=1)
+ self.fail("Got unexpected message in queue_3: " + extra.body)
+ except Empty: None
+
+
+ # tear down the queue on B2
+ self._brokers[2].client_session.exchange_unbind(queue="fedX1", exchange="fedX.direct", binding_key="spudboy")
+ self._brokers[2].client_session.message_cancel(destination="f1")
+ self._brokers[2].client_session.queue_delete(queue="fedX1")
+
+ # @todo - restore code when QPID-2499 fixed!!
+ sleep(6)
+ # wait for the binding count on B1 to drop from 2 to 1
+ retries = 0
+ exchanges[1].update()
+ while exchanges[1].bindingCount != 1:
+ retries += 1
+ self.failIfEqual(retries, 10,
+ "unbinding failed to propagate to broker B1: %d"
+ % exchanges[1].bindingCount)
+ sleep(1)
+ exchanges[1].update()
+
+ # send 10 msgs from B0
+ for i in range(11, 21):
+ dp = self._brokers[0].client_session.delivery_properties(routing_key="spudboy")
+ self._brokers[0].client_session.message_transfer(destination="fedX.direct", message=Message(dp, "Message_drp %d" % i))
+
+ # verify messages are forwarded to B3 only
+ retries = 0
+ for ex in exchanges:
+ ex.update()
+ while (exchanges[0].msgReceives != 20 or exchanges[0].msgRoutes != 20 or
+ exchanges[1].msgReceives != 20 or exchanges[1].msgRoutes != 30 or
+ exchanges[2].msgReceives != 10 or exchanges[2].msgRoutes != 10 or
+ exchanges[3].msgReceives != 20 or exchanges[3].msgRoutes != 20):
+ retries += 1
+ self.failIfEqual(retries, 10,
+ "federation failed to route more msgs %d:%d %d:%d %d:%d %d:%d"
+ % (exchanges[0].msgReceives,
+ exchanges[0].msgRoutes,
+ exchanges[1].msgReceives,
+ exchanges[1].msgRoutes,
+ exchanges[2].msgReceives,
+ exchanges[2].msgRoutes,
+ exchanges[3].msgReceives,
+ exchanges[3].msgRoutes))
+ sleep(1)
+ for ex in exchanges:
+ ex.update()
+
+ # get exactly 10 msgs on B3 only
+ for i in range(11, 21):
+ msg = queue_3.get(timeout=5)
+ self.assertEqual("Message_drp %d" % i, msg.body)
+
+ try:
+ extra = queue_3.get(timeout=1)
+ self.fail("Got unexpected message in queue_3: " + extra.body)
+ except Empty: None
+
+ # cleanup
+
+ self._brokers[3].client_session.exchange_unbind(queue="fedX1", exchange="fedX.direct", binding_key="spudboy")
+ self._brokers[3].client_session.message_cancel(destination="f1")
+ self._brokers[3].client_session.queue_delete(queue="fedX1")
+
+ for _b in qmf.getObjects(_class="bridge"):
+ result = _b.close()
+ self.assertEqual(result.status, 0)
+
+ for _l in qmf.getObjects(_class="link"):
+ result = _l.close()
+ self.assertEqual(result.status, 0)
+
+ for _b in self._brokers:
+ _b.client_session.exchange_delete(exchange="fedX.direct")
+
+ self._teardown_brokers()
+
+ self.verify_cleanup()
+
+ def test_dynamic_topic_route_prop(self):
+ """ Set up a tree of uni-directional routes across a topic exchange.
+ Bind the same key to the same queues on the leaf nodes. Verify a
+ message sent with the routing key transverses the tree an arrives at
+ each leaf. Remove one leaf's queue, and verify that messages still
+ reach the other leaf.
+
+ Route Topology:
+
+ +---> B2 queue:"test-queue", binding key:"spud.*"
+ B0 --> B1 --+
+ +---> B3 queue:"test-queue", binding key:"spud.*"
+ """
+ session = self.session
+
+ # create the federation
+
+ self.startQmf()
+ qmf = self.qmf
+
+ self._setup_brokers()
+
+ # create exchange on each broker, and retrieve the corresponding
+ # management object for that exchange
+
+ exchanges=[]
+ for _b in self._brokers:
+ _b.client_session.exchange_declare(exchange="fedX.topic", type="topic")
+ self.assertEqual(_b.client_session.exchange_query(name="fedX.topic").type,
+ "topic", "exchange_declare failed!")
+ # pull the exchange out of qmf...
+ retries = 0
+ my_exchange = None
+ while my_exchange is None:
+ objs = qmf.getObjects(_broker=_b.qmf_broker, _class="exchange")
+ for ooo in objs:
+ if ooo.name == "fedX.topic":
+ my_exchange = ooo
+ break
+ if my_exchange is None:
+ retries += 1
+ self.failIfEqual(retries, 10,
+ "QMF failed to find new exchange!")
+ sleep(1)
+ exchanges.append(my_exchange)
+
+ self.assertEqual(len(exchanges), len(self._brokers), "Exchange creation failed!")
+
+ # connect B0 --> B1
+ result = self._brokers[1].qmf_object.connect(self._brokers[0].host,
+ self._brokers[0].port,
+ False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ # connect B1 --> B2
+ result = self._brokers[2].qmf_object.connect(self._brokers[1].host,
+ self._brokers[1].port,
+ False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ # connect B1 --> B3
+ result = self._brokers[3].qmf_object.connect(self._brokers[1].host,
+ self._brokers[1].port,
+ False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ # for each link, bridge the "fedX.topic" exchanges:
+
+ for _l in qmf.getObjects(_class="link"):
+ # print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.getBroker())))
+ result = _l.bridge(False, # durable
+ "fedX.topic", # src
+ "fedX.topic", # dst
+ "", # key
+ "", # tag
+ "", # excludes
+ False, # srcIsQueue
+ False, # srcIsLocal
+ True, # dynamic
+ 0, # sync
+ 0) # credit
+ self.assertEqual(result.status, 0)
+
+ # wait for the inter-broker links to become operational
+ retries = 0
+ operational = False
+ while not operational:
+ operational = True
+ for _l in qmf.getObjects(_class="link"):
+ #print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.state)))
+ if _l.state != "Operational":
+ operational = False
+ if not operational:
+ retries += 1
+ self.failIfEqual(retries, 10,
+ "inter-broker links failed to become operational.")
+ sleep(1)
+
+ # @todo - There is no way to determine when the bridge objects become
+ # active.
+ sleep(6)
+
+ # create a queue on B2, bound to "spudboy"
+ self._brokers[2].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True)
+ self._brokers[2].client_session.exchange_bind(queue="fedX1", exchange="fedX.topic", binding_key="spud.*")
+
+ # create a queue on B3, bound to "spudboy"
+ self._brokers[3].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True)
+ self._brokers[3].client_session.exchange_bind(queue="fedX1", exchange="fedX.topic", binding_key="spud.*")
+
+ # subscribe to messages arriving on B2's queue
+ self.subscribe(self._brokers[2].client_session, queue="fedX1", destination="f1")
+ queue_2 = self._brokers[2].client_session.incoming("f1")
+
+ # subscribe to messages arriving on B3's queue
+ self.subscribe(self._brokers[3].client_session, queue="fedX1", destination="f1")
+ queue_3 = self._brokers[3].client_session.incoming("f1")
+
+ # wait until the binding key has propagated to each broker (twice at
+ # broker B1). Work backwards from binding brokers.
+
+ binding_counts = [1, 2, 1, 1]
+ self.assertEqual(len(binding_counts), len(exchanges), "Update Test!")
+ for i in range(3,-1,-1):
+ retries = 0
+ exchanges[i].update()
+ while exchanges[i].bindingCount < binding_counts[i]:
+ retries += 1
+ self.failIfEqual(retries, 10,
+ "binding failed to propagate to broker %d"
+ % i)
+ sleep(3)
+ exchanges[i].update()
+
+ # send 10 msgs from B0
+ for i in range(1, 11):
+ dp = self._brokers[0].client_session.delivery_properties(routing_key="spud.boy")
+ self._brokers[0].client_session.message_transfer(destination="fedX.topic", message=Message(dp, "Message_trp %d" % i))
+
+ # wait for 10 messages to be forwarded from B0->B1,
+ # 10 messages from B1->B2,
+ # and 10 messages from B1->B3
+ retries = 0
+ for ex in exchanges:
+ ex.update()
+ while (exchanges[0].msgReceives != 10 or exchanges[0].msgRoutes != 10 or
+ exchanges[1].msgReceives != 10 or exchanges[1].msgRoutes != 20 or
+ exchanges[2].msgReceives != 10 or exchanges[2].msgRoutes != 10 or
+ exchanges[3].msgReceives != 10 or exchanges[3].msgRoutes != 10):
+ retries += 1
+ self.failIfEqual(retries, 10,
+ "federation failed to route msgs %d:%d %d:%d %d:%d %d:%d"
+ % (exchanges[0].msgReceives,
+ exchanges[0].msgRoutes,
+ exchanges[1].msgReceives,
+ exchanges[1].msgRoutes,
+ exchanges[2].msgReceives,
+ exchanges[2].msgRoutes,
+ exchanges[3].msgReceives,
+ exchanges[3].msgRoutes))
+ sleep(1)
+ for ex in exchanges:
+ ex.update()
+
+ # get exactly 10 msgs on B2 and B3
+ for i in range(1, 11):
+ msg = queue_2.get(timeout=5)
+ self.assertEqual("Message_trp %d" % i, msg.body)
+ msg = queue_3.get(timeout=5)
+ self.assertEqual("Message_trp %d" % i, msg.body)
+
+ try:
+ extra = queue_2.get(timeout=1)
+ self.fail("Got unexpected message in queue_2: " + extra.body)
+ except Empty: None
+
+ try:
+ extra = queue_3.get(timeout=1)
+ self.fail("Got unexpected message in queue_3: " + extra.body)
+ except Empty: None
+
+ # tear down the queue on B2
+ self._brokers[2].client_session.exchange_unbind(queue="fedX1", exchange="fedX.topic", binding_key="spud.*")
+ self._brokers[2].client_session.message_cancel(destination="f1")
+ self._brokers[2].client_session.queue_delete(queue="fedX1")
+
+ # wait for the binding count on B1 to drop from 2 to 1
+ retries = 0
+ exchanges[1].update()
+ while exchanges[1].bindingCount != 1:
+ retries += 1
+ self.failIfEqual(retries, 10,
+ "unbinding failed to propagate to broker B1: %d"
+ % exchanges[1].bindingCount)
+ sleep(1)
+ exchanges[1].update()
+
+ # send 10 msgs from B0
+ for i in range(11, 21):
+ dp = self._brokers[0].client_session.delivery_properties(routing_key="spud.boy")
+ self._brokers[0].client_session.message_transfer(destination="fedX.topic", message=Message(dp, "Message_trp %d" % i))
+
+ # verify messages are forwarded to B3 only
+ retries = 0
+ for ex in exchanges:
+ ex.update()
+ while (exchanges[0].msgReceives != 20 or exchanges[0].msgRoutes != 20 or
+ exchanges[1].msgReceives != 20 or exchanges[1].msgRoutes != 30 or
+ exchanges[2].msgReceives != 10 or exchanges[2].msgRoutes != 10 or
+ exchanges[3].msgReceives != 20 or exchanges[3].msgRoutes != 20):
+ retries += 1
+ self.failIfEqual(retries, 10,
+ "federation failed to route more msgs %d:%d %d:%d %d:%d %d:%d"
+ % (exchanges[0].msgReceives,
+ exchanges[0].msgRoutes,
+ exchanges[1].msgReceives,
+ exchanges[1].msgRoutes,
+ exchanges[2].msgReceives,
+ exchanges[2].msgRoutes,
+ exchanges[3].msgReceives,
+ exchanges[3].msgRoutes))
+ sleep(1)
+ for ex in exchanges:
+ ex.update()
+
+ # get exactly 10 msgs on B3 only
+ for i in range(11, 21):
+ msg = queue_3.get(timeout=5)
+ self.assertEqual("Message_trp %d" % i, msg.body)
+
+ try:
+ extra = queue_3.get(timeout=1)
+ self.fail("Got unexpected message in queue_3: " + extra.body)
+ except Empty: None
+
+ # cleanup
+
+ self._brokers[3].client_session.exchange_unbind(queue="fedX1", exchange="fedX.topic", binding_key="spud.*")
+ self._brokers[3].client_session.message_cancel(destination="f1")
+ self._brokers[3].client_session.queue_delete(queue="fedX1")
+
+ for _b in qmf.getObjects(_class="bridge"):
+ result = _b.close()
+ self.assertEqual(result.status, 0)
+
+ for _l in qmf.getObjects(_class="link"):
+ result = _l.close()
+ self.assertEqual(result.status, 0)
+
+ for _b in self._brokers:
+ _b.client_session.exchange_delete(exchange="fedX.topic")
+
+ self._teardown_brokers()
+
+ self.verify_cleanup()
+
+
+ def test_dynamic_fanout_route_prop(self):
+ """ Set up a tree of uni-directional routes across a fanout exchange.
+ Bind the same key to the same queues on the leaf nodes. Verify a
+ message sent with the routing key transverses the tree an arrives at
+ each leaf. Remove one leaf's queue, and verify that messages still
+ reach the other leaf.
+
+ Route Topology:
+
+ +---> B2 queue:"test-queue", binding key:"spud.*"
+ B0 --> B1 --+
+ +---> B3 queue:"test-queue", binding key:"spud.*"
+ """
+ session = self.session
+
+ # create the federation
+
+ self.startQmf()
+ qmf = self.qmf
+
+ self._setup_brokers()
+
+ # create fanout exchange on each broker, and retrieve the corresponding
+ # management object for that exchange
+
+ exchanges=[]
+ for _b in self._brokers:
+ _b.client_session.exchange_declare(exchange="fedX.fanout", type="fanout")
+ self.assertEqual(_b.client_session.exchange_query(name="fedX.fanout").type,
+ "fanout", "exchange_declare failed!")
+ # pull the exchange out of qmf...
+ retries = 0
+ my_exchange = None
+ while my_exchange is None:
+ objs = qmf.getObjects(_broker=_b.qmf_broker, _class="exchange")
+ for ooo in objs:
+ if ooo.name == "fedX.fanout":
+ my_exchange = ooo
+ break
+ if my_exchange is None:
+ retries += 1
+ self.failIfEqual(retries, 10,
+ "QMF failed to find new exchange!")
+ sleep(1)
+ exchanges.append(my_exchange)
+
+ self.assertEqual(len(exchanges), len(self._brokers), "Exchange creation failed!")
+
+ # connect B0 --> B1
+ result = self._brokers[1].qmf_object.connect(self._brokers[0].host,
+ self._brokers[0].port,
+ False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ # connect B1 --> B2
+ result = self._brokers[2].qmf_object.connect(self._brokers[1].host,
+ self._brokers[1].port,
+ False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ # connect B1 --> B3
+ result = self._brokers[3].qmf_object.connect(self._brokers[1].host,
+ self._brokers[1].port,
+ False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ # for each link, bridge the "fedX.fanout" exchanges:
+
+ for _l in qmf.getObjects(_class="link"):
+ # print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.getBroker())))
+ result = _l.bridge(False, # durable
+ "fedX.fanout", # src
+ "fedX.fanout", # dst
+ "", # key
+ "", # tag
+ "", # excludes
+ False, # srcIsQueue
+ False, # srcIsLocal
+ True, # dynamic
+ 0, # sync
+ 0) # credit
+ self.assertEqual(result.status, 0)
+
+ # wait for the inter-broker links to become operational
+ retries = 0
+ operational = False
+ while not operational:
+ operational = True
+ for _l in qmf.getObjects(_class="link"):
+ # print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.state)))
+ if _l.state != "Operational":
+ operational = False
+ if not operational:
+ retries += 1
+ self.failIfEqual(retries, 10,
+ "inter-broker links failed to become operational.")
+ sleep(1)
+
+ # @todo - There is no way to determine when the bridge objects become
+ # active.
+ sleep(6)
+
+ # create a queue on B2, bound to the exchange
+ self._brokers[2].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True)
+ self._brokers[2].client_session.exchange_bind(queue="fedX1", exchange="fedX.fanout")
+
+ # create a queue on B3, bound to the exchange
+ self._brokers[3].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True)
+ self._brokers[3].client_session.exchange_bind(queue="fedX1", exchange="fedX.fanout")
+
+ # subscribe to messages arriving on B2's queue
+ self.subscribe(self._brokers[2].client_session, queue="fedX1", destination="f1")
+ queue_2 = self._brokers[2].client_session.incoming("f1")
+
+ # subscribe to messages arriving on B3's queue
+ self.subscribe(self._brokers[3].client_session, queue="fedX1", destination="f1")
+ queue_3 = self._brokers[3].client_session.incoming("f1")
+
+ # wait until the binding key has propagated to each broker (twice at
+ # broker B1). Work backwards from binding brokers.
+
+ binding_counts = [1, 2, 1, 1]
+ self.assertEqual(len(binding_counts), len(exchanges), "Update Test!")
+ for i in range(3,-1,-1):
+ retries = 0
+ exchanges[i].update()
+ while exchanges[i].bindingCount < binding_counts[i]:
+ retries += 1
+ self.failIfEqual(retries, 10,
+ "binding failed to propagate to broker %d"
+ % i)
+ sleep(3)
+ exchanges[i].update()
+
+ # send 10 msgs from B0
+ for i in range(1, 11):
+ dp = self._brokers[0].client_session.delivery_properties()
+ self._brokers[0].client_session.message_transfer(destination="fedX.fanout", message=Message(dp, "Message_frp %d" % i))
+
+ # wait for 10 messages to be forwarded from B0->B1,
+ # 10 messages from B1->B2,
+ # and 10 messages from B1->B3
+ retries = 0
+ for ex in exchanges:
+ ex.update()
+ while (exchanges[0].msgReceives != 10 or exchanges[0].msgRoutes != 10 or
+ exchanges[1].msgReceives != 10 or exchanges[1].msgRoutes != 20 or
+ exchanges[2].msgReceives != 10 or exchanges[2].msgRoutes != 10 or
+ exchanges[3].msgReceives != 10 or exchanges[3].msgRoutes != 10):
+ retries += 1
+ self.failIfEqual(retries, 10,
+ "federation failed to route msgs %d:%d %d:%d %d:%d %d:%d"
+ % (exchanges[0].msgReceives,
+ exchanges[0].msgRoutes,
+ exchanges[1].msgReceives,
+ exchanges[1].msgRoutes,
+ exchanges[2].msgReceives,
+ exchanges[2].msgRoutes,
+ exchanges[3].msgReceives,
+ exchanges[3].msgRoutes))
+ sleep(1)
+ for ex in exchanges:
+ ex.update()
+
+ # get exactly 10 msgs on B2 and B3
+ for i in range(1, 11):
+ msg = queue_2.get(timeout=5)
+ self.assertEqual("Message_frp %d" % i, msg.body)
+ msg = queue_3.get(timeout=5)
+ self.assertEqual("Message_frp %d" % i, msg.body)
+
+ try:
+ extra = queue_2.get(timeout=1)
+ self.fail("Got unexpected message in queue_2: " + extra.body)
+ except Empty: None
+
+ try:
+ extra = queue_3.get(timeout=1)
+ self.fail("Got unexpected message in queue_3: " + extra.body)
+ except Empty: None
+
+ # tear down the queue on B2
+ self._brokers[2].client_session.exchange_unbind(queue="fedX1", exchange="fedX.fanout")
+ self._brokers[2].client_session.message_cancel(destination="f1")
+ self._brokers[2].client_session.queue_delete(queue="fedX1")
+
+ # wait for the binding count on B1 to drop from 2 to 1
+ retries = 0
+ exchanges[1].update()
+ while exchanges[1].bindingCount != 1:
+ retries += 1
+ self.failIfEqual(retries, 10,
+ "unbinding failed to propagate to broker B1: %d"
+ % exchanges[1].bindingCount)
+ sleep(1)
+ exchanges[1].update()
+
+ # send 10 msgs from B0
+ for i in range(11, 21):
+ dp = self._brokers[0].client_session.delivery_properties()
+ self._brokers[0].client_session.message_transfer(destination="fedX.fanout", message=Message(dp, "Message_frp %d" % i))
+
+ # verify messages are forwarded to B3 only
+ retries = 0
+ for ex in exchanges:
+ ex.update()
+ while (exchanges[0].msgReceives != 20 or exchanges[0].msgRoutes != 20 or
+ exchanges[1].msgReceives != 20 or exchanges[1].msgRoutes != 30 or
+ exchanges[2].msgReceives != 10 or exchanges[2].msgRoutes != 10 or
+ exchanges[3].msgReceives != 20 or exchanges[3].msgRoutes != 20):
+ retries += 1
+ self.failIfEqual(retries, 10,
+ "federation failed to route more msgs %d:%d %d:%d %d:%d %d:%d"
+ % (exchanges[0].msgReceives,
+ exchanges[0].msgRoutes,
+ exchanges[1].msgReceives,
+ exchanges[1].msgRoutes,
+ exchanges[2].msgReceives,
+ exchanges[2].msgRoutes,
+ exchanges[3].msgReceives,
+ exchanges[3].msgRoutes))
+ sleep(1)
+ for ex in exchanges:
+ ex.update()
+
+ # get exactly 10 msgs on B3 only
+ for i in range(11, 21):
+ msg = queue_3.get(timeout=5)
+ self.assertEqual("Message_frp %d" % i, msg.body)
+
+ try:
+ extra = queue_3.get(timeout=1)
+ self.fail("Got unexpected message in queue_3: " + extra.body)
+ except Empty: None
+
+ # cleanup
+
+ self._brokers[3].client_session.exchange_unbind(queue="fedX1", exchange="fedX.fanout")
+ self._brokers[3].client_session.message_cancel(destination="f1")
+ self._brokers[3].client_session.queue_delete(queue="fedX1")
+
+ for _b in qmf.getObjects(_class="bridge"):
+ result = _b.close()
+ self.assertEqual(result.status, 0)
+
+ for _l in qmf.getObjects(_class="link"):
+ result = _l.close()
+ self.assertEqual(result.status, 0)
+
+ for _b in self._brokers:
+ _b.client_session.exchange_delete(exchange="fedX.fanout")
+
+ self._teardown_brokers()
+
+ self.verify_cleanup()
+
+
+ def getProperty(self, msg, name):
+ for h in msg.headers:
+ if hasattr(h, name): return getattr(h, name)
+ return None
+
+ def getAppHeader(self, msg, name):
+ headers = self.getProperty(msg, "application_headers")
+ if headers:
+ return headers[name]
+ return None
+
+ def test_dynamic_topic_bounce(self):
+ """ Bounce the connection between federated Topic Exchanges.
+ """
+ class Params:
+ def exchange_type(self): return "topic"
+ def bind_queue(self, ssn, qname, ename):
+ ssn.exchange_bind(queue=qname, exchange=ename,
+ binding_key="spud.*")
+ def unbind_queue(self, ssn, qname, ename):
+ ssn.exchange_unbind(queue=qname, exchange=ename, binding_key="spud.*")
+ def delivery_properties(self, ssn):
+ return ssn.delivery_properties(routing_key="spud.boy")
+
+ self.generic_dynamic_bounce_test(Params())
+
+ def test_dynamic_direct_bounce(self):
+ """ Bounce the connection between federated Direct Exchanges.
+ """
+ class Params:
+ def exchange_type(self): return "direct"
+ def bind_queue(self, ssn, qname, ename):
+ ssn.exchange_bind(queue=qname, exchange=ename, binding_key="spud")
+ def unbind_queue(self, ssn, qname, ename):
+ ssn.exchange_unbind(queue=qname, exchange=ename, binding_key="spud")
+ def delivery_properties(self, ssn):
+ return ssn.delivery_properties(routing_key="spud")
+ self.generic_dynamic_bounce_test(Params())
+
+ def test_dynamic_fanout_bounce(self):
+ """ Bounce the connection between federated Fanout Exchanges.
+ """
+ class Params:
+ def exchange_type(self): return "fanout"
+ def bind_queue(self, ssn, qname, ename):
+ ssn.exchange_bind(queue=qname, exchange=ename)
+ def unbind_queue(self, ssn, qname, ename):
+ ssn.exchange_unbind(queue=qname, exchange=ename)
+ def delivery_properties(self, ssn):
+ return ssn.delivery_properties(routing_key="spud")
+ self.generic_dynamic_bounce_test(Params())
+
+ def test_dynamic_headers_bounce(self):
+ """ Bounce the connection between federated Headers Exchanges.
+ """
+ class Params:
+ def exchange_type(self): return "headers"
+ def bind_queue(self, ssn, qname, ename):
+ ssn.exchange_bind(queue=qname, exchange=ename,
+ binding_key="spud", arguments={'x-match':'any', 'class':'first'})
+ def unbind_queue(self, ssn, qname, ename):
+ ssn.exchange_unbind(queue=qname, exchange=ename, binding_key="spud")
+ def delivery_properties(self, ssn):
+ return ssn.message_properties(application_headers={'class':'first'})
+ ## @todo KAG - re-enable once federation bugs with headers exchanges
+ ## are fixed.
+ #self.generic_dynamic_bounce_test(Params())
+ return
+
+
+ def generic_dynamic_bounce_test(self, params):
+ """ Verify that a federated broker can maintain a binding to a local
+ queue using the same key as a remote binding. Destroy and reconnect
+ the federation link, and verify routes are restored correctly.
+ See QPID-3170.
+ Topology:
+
+ Queue1 <---"Key"---B0<==[Federated Exchange]==>B1---"Key"--->Queue2
+ """
+ session = self.session
+
+ # create the federation
+
+ self.startQmf()
+ qmf = self.qmf
+
+ self._setup_brokers()
+
+ # create exchange on each broker, and retrieve the corresponding
+ # management object for that exchange
+
+ exchanges=[]
+ for _b in self._brokers[0:2]:
+ _b.client_session.exchange_declare(exchange="fedX", type=params.exchange_type())
+ self.assertEqual(_b.client_session.exchange_query(name="fedX").type,
+ params.exchange_type(), "exchange_declare failed!")
+ # pull the exchange out of qmf...
+ retries = 0
+ my_exchange = None
+ timeout = time() + 10
+ while my_exchange is None and time() <= timeout:
+ objs = qmf.getObjects(_broker=_b.qmf_broker, _class="exchange")
+ for ooo in objs:
+ if ooo.name == "fedX":
+ my_exchange = ooo
+ break
+ if my_exchange is None:
+ self.fail("QMF failed to find new exchange!")
+ exchanges.append(my_exchange)
+
+ #
+ # on each broker, create a local queue bound to the exchange with the
+ # same key value.
+ #
+
+ self._brokers[0].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True)
+ params.bind_queue(self._brokers[0].client_session, "fedX1", "fedX")
+ self.subscribe(self._brokers[0].client_session, queue="fedX1", destination="f1")
+ queue_0 = self._brokers[0].client_session.incoming("f1")
+
+ self._brokers[1].client_session.queue_declare(queue="fedX1", exclusive=True, auto_delete=True)
+ params.bind_queue(self._brokers[1].client_session, "fedX1", "fedX")
+ self.subscribe(self._brokers[1].client_session, queue="fedX1", destination="f1")
+ queue_1 = self._brokers[1].client_session.incoming("f1")
+
+ # now federate the two brokers
+
+ # connect B0 --> B1
+ result = self._brokers[1].qmf_object.connect(self._brokers[0].host,
+ self._brokers[0].port,
+ False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ # connect B1 --> B0
+ result = self._brokers[0].qmf_object.connect(self._brokers[1].host,
+ self._brokers[1].port,
+ False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ # for each link, bridge the "fedX" exchanges:
+
+ for _l in qmf.getObjects(_class="link"):
+ # print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.getBroker())))
+ result = _l.bridge(False, # durable
+ "fedX", # src
+ "fedX", # dst
+ "", # key
+ "", # tag
+ "", # excludes
+ False, # srcIsQueue
+ False, # srcIsLocal
+ True, # dynamic
+ 0, # sync
+ 0) # credit
+ self.assertEqual(result.status, 0)
+
+ # wait for all the inter-broker links to become operational
+ operational = False
+ timeout = time() + 10
+ while not operational and time() <= timeout:
+ operational = True
+ for _l in qmf.getObjects(_class="link"):
+ #print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.state)))
+ if _l.state != "Operational":
+ operational = False
+ self.failUnless(operational, "inter-broker links failed to become operational.")
+
+ # @todo - There is no way to determine when the bridge objects become
+ # active.
+
+ # wait until the binding key has propagated to each broker - each
+ # broker should see 2 bindings (1 local, 1 remote)
+
+ binding_counts = [2, 2]
+ self.assertEqual(len(binding_counts), len(exchanges), "Update Test!")
+ for i in range(2):
+ exchanges[i].update()
+ timeout = time() + 10
+ while exchanges[i].bindingCount < binding_counts[i] and time() <= timeout:
+ exchanges[i].update()
+ self.failUnless(exchanges[i].bindingCount == binding_counts[i])
+
+ # send 10 msgs to B0
+ for i in range(1, 11):
+ # dp = self._brokers[0].client_session.delivery_properties(routing_key=params.routing_key())
+ dp = params.delivery_properties(self._brokers[0].client_session)
+ self._brokers[0].client_session.message_transfer(destination="fedX", message=Message(dp, "Message_trp %d" % i))
+
+ # get exactly 10 msgs on B0's local queue and B1's queue
+ for i in range(1, 11):
+ try:
+ msg = queue_0.get(timeout=5)
+ self.assertEqual("Message_trp %d" % i, msg.body)
+ msg = queue_1.get(timeout=5)
+ self.assertEqual("Message_trp %d" % i, msg.body)
+ except Empty:
+ self.fail("Only got %d msgs - expected 10" % i)
+ try:
+ extra = queue_0.get(timeout=1)
+ self.fail("Got unexpected message in queue_0: " + extra.body)
+ except Empty: None
+
+ try:
+ extra = queue_1.get(timeout=1)
+ self.fail("Got unexpected message in queue_1: " + extra.body)
+ except Empty: None
+
+ #
+ # Tear down the bridges between the two exchanges, then wait
+ # for the bindings to be cleaned up
+ #
+
+ for _b in qmf.getObjects(_class="bridge"):
+ result = _b.close()
+ self.assertEqual(result.status, 0)
+
+ binding_counts = [1, 1]
+ self.assertEqual(len(binding_counts), len(exchanges), "Update Test!")
+ for i in range(2):
+ exchanges[i].update()
+ timeout = time() + 10
+ while exchanges[i].bindingCount != binding_counts[i] and time() <= timeout:
+ exchanges[i].update()
+ self.failUnless(exchanges[i].bindingCount == binding_counts[i])
+
+ #
+ # restore the bridges between the two exchanges, and wait for the
+ # bindings to propagate.
+ #
+
+ for _l in qmf.getObjects(_class="link"):
+ # print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.getBroker())))
+ result = _l.bridge(False, # durable
+ "fedX", # src
+ "fedX", # dst
+ "", # key
+ "", # tag
+ "", # excludes
+ False, # srcIsQueue
+ False, # srcIsLocal
+ True, # dynamic
+ 0, # sync
+ 0) # credit
+ self.assertEqual(result.status, 0)
+
+ binding_counts = [2, 2]
+ self.assertEqual(len(binding_counts), len(exchanges), "Update Test!")
+ for i in range(2):
+ exchanges[i].update()
+ timeout = time() + 10
+ while exchanges[i].bindingCount != binding_counts[i] and time() <= timeout:
+ exchanges[i].update()
+ self.failUnless(exchanges[i].bindingCount == binding_counts[i])
+
+ #
+ # verify traffic flows correctly
+ #
+
+ for i in range(1, 11):
+ #dp = self._brokers[1].client_session.delivery_properties(routing_key=params.routing_key())
+ dp = params.delivery_properties(self._brokers[1].client_session)
+ self._brokers[1].client_session.message_transfer(destination="fedX", message=Message(dp, "Message_trp %d" % i))
+
+ # get exactly 10 msgs on B0's queue and B1's queue
+ for i in range(1, 11):
+ try:
+ msg = queue_0.get(timeout=5)
+ self.assertEqual("Message_trp %d" % i, msg.body)
+ msg = queue_1.get(timeout=5)
+ self.assertEqual("Message_trp %d" % i, msg.body)
+ except Empty:
+ self.fail("Only got %d msgs - expected 10" % i)
+ try:
+ extra = queue_0.get(timeout=1)
+ self.fail("Got unexpected message in queue_0: " + extra.body)
+ except Empty: None
+
+ try:
+ extra = queue_1.get(timeout=1)
+ self.fail("Got unexpected message in queue_1: " + extra.body)
+ except Empty: None
+
+
+ #
+ # cleanup
+ #
+ params.unbind_queue(self._brokers[0].client_session, "fedX1", "fedX")
+ self._brokers[0].client_session.message_cancel(destination="f1")
+ self._brokers[0].client_session.queue_delete(queue="fedX1")
+
+ params.unbind_queue(self._brokers[1].client_session, "fedX1", "fedX")
+ self._brokers[1].client_session.message_cancel(destination="f1")
+ self._brokers[1].client_session.queue_delete(queue="fedX1")
+
+ for _b in qmf.getObjects(_class="bridge"):
+ result = _b.close()
+ self.assertEqual(result.status, 0)
+
+ for _l in qmf.getObjects(_class="link"):
+ result = _l.close()
+ self.assertEqual(result.status, 0)
+
+ for _b in self._brokers[0:2]:
+ _b.client_session.exchange_delete(exchange="fedX")
+
+ self._teardown_brokers()
+
+ self.verify_cleanup()
+
+
+ def test_multilink_direct(self):
+ """ Verify that two distinct links can be created between federated
+ brokers.
+ """
+ self.startQmf()
+ qmf = self.qmf
+ self._setup_brokers()
+ src_broker = self._brokers[0]
+ dst_broker = self._brokers[1]
+
+ # create a direct exchange on each broker
+ for _b in [src_broker, dst_broker]:
+ _b.client_session.exchange_declare(exchange="fedX.direct", type="direct")
+ self.assertEqual(_b.client_session.exchange_query(name="fedX.direct").type,
+ "direct", "exchange_declare failed!")
+
+ # create destination queues
+ for _q in [("HiQ", "high"), ("MedQ", "medium"), ("LoQ", "low")]:
+ dst_broker.client_session.queue_declare(queue=_q[0], auto_delete=True)
+ dst_broker.client_session.exchange_bind(queue=_q[0], exchange="fedX.direct", binding_key=_q[1])
+
+ # create two connections, one for high priority traffic
+ for _q in ["HiPri", "Traffic"]:
+ result = dst_broker.qmf_object.create("link", _q,
+ {"host":src_broker.host,
+ "port":src_broker.port},
+ False)
+ self.assertEqual(result.status, 0);
+
+ links = qmf.getObjects(_broker=dst_broker.qmf_broker, _class="link")
+ for _l in links:
+ if _l.name == "HiPri":
+ hi_link = _l
+ elif _l.name == "Traffic":
+ data_link = _l
+ else:
+ self.fail("Unexpected Link found: " + _l.name)
+
+ # now create a route for messages sent with key "high" to use the
+ # hi_link
+ result = dst_broker.qmf_object.create("bridge", "HiPriBridge",
+ {"link":hi_link.name,
+ "src":"fedX.direct",
+ "dest":"fedX.direct",
+ "key":"high"}, False)
+ self.assertEqual(result.status, 0);
+
+
+ # create routes for the "medium" and "low" links to use the normal
+ # data_link
+ for _b in [("MediumBridge", "medium"), ("LowBridge", "low")]:
+ result = dst_broker.qmf_object.create("bridge", _b[0],
+ {"link":data_link.name,
+ "src":"fedX.direct",
+ "dest":"fedX.direct",
+ "key":_b[1]}, False)
+ self.assertEqual(result.status, 0);
+
+ # now wait for the links to become operational
+ for _l in [hi_link, data_link]:
+ expire_time = time() + 30
+ while _l.state != "Operational" and time() < expire_time:
+ _l.update()
+ self.assertEqual(_l.state, "Operational", "Link failed to become operational")
+
+ # verify each link uses a different connection
+ self.assertNotEqual(hi_link.connectionRef, data_link.connectionRef,
+ "Different links using the same connection")
+
+ hi_conn = qmf.getObjects(_broker=dst_broker.qmf_broker,
+ _objectId=hi_link.connectionRef)[0]
+ data_conn = qmf.getObjects(_broker=dst_broker.qmf_broker,
+ _objectId=data_link.connectionRef)[0]
+
+
+ # send hi data, verify only goes over hi link
+
+ r_ssn = dst_broker.connection.session()
+ hi_receiver = r_ssn.receiver("HiQ");
+ med_receiver = r_ssn.receiver("MedQ");
+ low_receiver = r_ssn.receiver("LoQ");
+
+ for _c in [hi_conn, data_conn]:
+ _c.update()
+ self.assertEqual(_c.msgsToClient, 0, "Unexpected messages received")
+
+ s_ssn = src_broker.connection.session()
+ hi_sender = s_ssn.sender("fedX.direct/high")
+ med_sender = s_ssn.sender("fedX.direct/medium")
+ low_sender = s_ssn.sender("fedX.direct/low")
+
+ try:
+ hi_sender.send(qpid.messaging.Message(content="hi priority"))
+ msg = hi_receiver.fetch(timeout=10)
+ r_ssn.acknowledge()
+ self.assertEqual(msg.content, "hi priority");
+ except:
+ self.fail("Hi Pri message failure")
+
+ hi_conn.update()
+ data_conn.update()
+ self.assertEqual(hi_conn.msgsToClient, 1, "Expected 1 hi pri message")
+ self.assertEqual(data_conn.msgsToClient, 0, "Expected 0 data messages")
+
+ # send low and medium, verify it does not go over hi link
+
+ try:
+ med_sender.send(qpid.messaging.Message(content="medium priority"))
+ msg = med_receiver.fetch(timeout=10)
+ r_ssn.acknowledge()
+ self.assertEqual(msg.content, "medium priority");
+ except:
+ self.fail("Medium Pri message failure")
+
+ hi_conn.update()
+ data_conn.update()
+ self.assertEqual(hi_conn.msgsToClient, 1, "Expected 1 hi pri message")
+ self.assertEqual(data_conn.msgsToClient, 1, "Expected 1 data message")
+
+ try:
+ low_sender.send(qpid.messaging.Message(content="low priority"))
+ msg = low_receiver.fetch(timeout=10)
+ r_ssn.acknowledge()
+ self.assertEqual(msg.content, "low priority");
+ except:
+ self.fail("Low Pri message failure")
+
+ hi_conn.update()
+ data_conn.update()
+ self.assertEqual(hi_conn.msgsToClient, 1, "Expected 1 hi pri message")
+ self.assertEqual(data_conn.msgsToClient, 2, "Expected 2 data message")
+
+ # cleanup
+
+ for _b in qmf.getObjects(_broker=dst_broker.qmf_broker,_class="bridge"):
+ result = _b.close()
+ self.assertEqual(result.status, 0)
+
+ for _l in qmf.getObjects(_broker=dst_broker.qmf_broker,_class="link"):
+ result = _l.close()
+ self.assertEqual(result.status, 0)
+
+ for _q in [("HiQ", "high"), ("MedQ", "medium"), ("LoQ", "low")]:
+ dst_broker.client_session.exchange_unbind(queue=_q[0], exchange="fedX.direct", binding_key=_q[1])
+ dst_broker.client_session.queue_delete(queue=_q[0])
+
+ for _b in [src_broker, dst_broker]:
+ _b.client_session.exchange_delete(exchange="fedX.direct")
+
+ self._teardown_brokers()
+
+ self.verify_cleanup()
+
+
+ def test_multilink_shared_queue(self):
+ """ Verify that two distinct links can be created between federated
+ brokers.
+ """
+ self.startQmf()
+ qmf = self.qmf
+ self._setup_brokers()
+ src_broker = self._brokers[0]
+ dst_broker = self._brokers[1]
+
+ # create a topic exchange on the destination broker
+ dst_broker.client_session.exchange_declare(exchange="fedX.topic", type="topic")
+ self.assertEqual(dst_broker.client_session.exchange_query(name="fedX.topic").type,
+ "topic", "exchange_declare failed!")
+
+ # create a destination queue
+ dst_broker.client_session.queue_declare(queue="destQ", auto_delete=True)
+ dst_broker.client_session.exchange_bind(queue="destQ", exchange="fedX.topic", binding_key="srcQ")
+
+ # create a single source queue
+ src_broker.client_session.queue_declare(queue="srcQ", auto_delete=True)
+
+ # create two connections
+ for _q in ["Link1", "Link2"]:
+ result = dst_broker.qmf_object.create("link", _q,
+ {"host":src_broker.host,
+ "port":src_broker.port},
+ False)
+ self.assertEqual(result.status, 0);
+
+ links = qmf.getObjects(_broker=dst_broker.qmf_broker, _class="link")
+ self.assertEqual(len(links), 2)
+
+ # now create two "parallel" queue routes from the source queue to the
+ # destination exchange.
+ result = dst_broker.qmf_object.create("bridge", "Bridge1",
+ {"link":"Link1",
+ "src":"srcQ",
+ "dest":"fedX.topic",
+ "srcIsQueue": True},
+ False)
+ self.assertEqual(result.status, 0);
+ result = dst_broker.qmf_object.create("bridge", "Bridge2",
+ {"link":"Link2",
+ "src":"srcQ",
+ "dest":"fedX.topic",
+ "srcIsQueue": True},
+ False)
+ self.assertEqual(result.status, 0);
+
+
+ # now wait for the links to become operational
+ for _l in links:
+ expire_time = time() + 30
+ while _l.state != "Operational" and time() < expire_time:
+ _l.update()
+ self.assertEqual(_l.state, "Operational", "Link failed to become operational")
+
+ # verify each link uses a different connection
+ self.assertNotEqual(links[0].connectionRef, links[1].connectionRef,
+ "Different links using the same connection")
+
+ conn1 = qmf.getObjects(_broker=dst_broker.qmf_broker,
+ _objectId=links[0].connectionRef)[0]
+ conn2 = qmf.getObjects(_broker=dst_broker.qmf_broker,
+ _objectId=links[1].connectionRef)[0]
+
+ # verify messages sent to the queue are pulled by each connection
+
+ r_ssn = dst_broker.connection.session()
+ receiver = r_ssn.receiver("destQ");
+
+ for _c in [conn1, conn2]:
+ _c.update()
+ self.assertEqual(_c.msgsToClient, 0, "Unexpected messages received")
+
+ s_ssn = src_broker.connection.session()
+ sender = s_ssn.sender("srcQ")
+
+ try:
+ for x in range(5):
+ sender.send(qpid.messaging.Message(content="hello"))
+ for x in range(5):
+ msg = receiver.fetch(timeout=10)
+ self.assertEqual(msg.content, "hello");
+ r_ssn.acknowledge()
+ except:
+ self.fail("Message failure")
+
+ # expect messages to be split over each connection.
+ conn1.update()
+ conn2.update()
+ self.assertNotEqual(conn1.msgsToClient, 0, "No messages sent")
+ self.assertNotEqual(conn2.msgsToClient, 0, "No messages sent")
+ self.assertEqual(conn2.msgsToClient + conn1.msgsToClient, 5,
+ "Expected 5 messages total")
+
+ for _b in qmf.getObjects(_broker=dst_broker.qmf_broker,_class="bridge"):
+ result = _b.close()
+ self.assertEqual(result.status, 0)
+
+ for _l in qmf.getObjects(_broker=dst_broker.qmf_broker,_class="link"):
+ result = _l.close()
+ self.assertEqual(result.status, 0)
+
+ dst_broker.client_session.exchange_unbind(queue="destQ", exchange="fedX.topic", binding_key="srcQ")
+ dst_broker.client_session.exchange_delete(exchange="fedX.topic")
+
+ self._teardown_brokers()
+
+ self.verify_cleanup()
+
+
+ def test_dynamic_direct_shared_queue(self):
+ """
+ Route Topology:
+
+ +<--- B1
+ B0 <---+<--- B2
+ +<--- B3
+ """
+ session = self.session
+
+ # create the federation
+
+ self.startQmf()
+ qmf = self.qmf
+
+ self._setup_brokers()
+
+ # create direct exchange on each broker, and retrieve the corresponding
+ # management object for that exchange
+
+ exchanges=[]
+ for _b in self._brokers:
+ _b.client_session.exchange_declare(exchange="fedX.direct", type="direct")
+ self.assertEqual(_b.client_session.exchange_query(name="fedX.direct").type,
+ "direct", "exchange_declare failed!")
+ # pull the exchange out of qmf...
+ retries = 0
+ my_exchange = None
+ while my_exchange is None:
+ objs = qmf.getObjects(_broker=_b.qmf_broker, _class="exchange")
+ for ooo in objs:
+ if ooo.name == "fedX.direct":
+ my_exchange = ooo
+ break
+ if my_exchange is None:
+ retries += 1
+ self.failIfEqual(retries, 10,
+ "QMF failed to find new exchange!")
+ sleep(1)
+ exchanges.append(my_exchange)
+
+ self.assertEqual(len(exchanges), len(self._brokers), "Exchange creation failed!")
+
+ # Create 2 links per each source broker (1,2,3) to the downstream
+ # broker 0:
+ for _b in range(1,4):
+ for _l in ["dynamic", "queue"]:
+ result = self._brokers[0].qmf_object.create( "link",
+ "Link-%d-%s" % (_b, _l),
+ {"host":self._brokers[_b].host,
+ "port":self._brokers[_b].port}, False)
+ self.assertEqual(result.status, 0)
+
+ # create queue on source brokers for use by the dynamic route
+ self._brokers[_b].client_session.queue_declare(queue="fedSrcQ", exclusive=False, auto_delete=True)
+
+ for _l in range(1,4):
+ # for each dynamic link, create a dynamic bridge for the "fedX.direct"
+ # exchanges, using the fedSrcQ on each upstream source broker
+ result = self._brokers[0].qmf_object.create("bridge",
+ "Bridge-%d-dynamic" % _l,
+ {"link":"Link-%d-dynamic" % _l,
+ "src":"fedX.direct",
+ "dest":"fedX.direct",
+ "dynamic":True,
+ "queue":"fedSrcQ"}, False)
+ self.assertEqual(result.status, 0)
+
+ # create a queue route that shares the queue used by the dynamic route
+ result = self._brokers[0].qmf_object.create("bridge",
+ "Bridge-%d-queue" % _l,
+ {"link":"Link-%d-queue" % _l,
+ "src":"fedSrcQ",
+ "dest":"fedX.direct",
+ "srcIsQueue":True}, False)
+ self.assertEqual(result.status, 0)
+
+
+ # wait for the inter-broker links to become operational
+ retries = 0
+ operational = False
+ while not operational:
+ operational = True
+ for _l in qmf.getObjects(_class="link"):
+ #print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.state)))
+ if _l.state != "Operational":
+ operational = False
+ if not operational:
+ retries += 1
+ self.failIfEqual(retries, 10,
+ "inter-broker links failed to become operational.")
+ sleep(1)
+
+ # @todo - There is no way to determine when the bridge objects become
+ # active. Hopefully, this is long enough!
+ sleep(6)
+
+ # create a queue on B0, bound to "spudboy"
+ self._brokers[0].client_session.queue_declare(queue="DestQ", exclusive=True, auto_delete=True)
+ self._brokers[0].client_session.exchange_bind(queue="DestQ", exchange="fedX.direct", binding_key="spudboy")
+
+ # subscribe to messages arriving on B2's queue
+ self.subscribe(self._brokers[0].client_session, queue="DestQ", destination="f1")
+ queue = self._brokers[0].client_session.incoming("f1")
+
+ # wait until the binding key has propagated to each broker
+
+ binding_counts = [1, 1, 1, 1]
+ self.assertEqual(len(binding_counts), len(exchanges), "Update Test!")
+ for i in range(3,-1,-1):
+ retries = 0
+ exchanges[i].update()
+ while exchanges[i].bindingCount < binding_counts[i]:
+ retries += 1
+ self.failIfEqual(retries, 10,
+ "binding failed to propagate to broker %d"
+ % i)
+ sleep(3)
+ exchanges[i].update()
+
+ for _b in range(1,4):
+ # send 3 msgs from each source broker
+ for i in range(3):
+ dp = self._brokers[_b].client_session.delivery_properties(routing_key="spudboy")
+ self._brokers[_b].client_session.message_transfer(destination="fedX.direct", message=Message(dp, "Message_drp %d" % i))
+
+ # get exactly 9 (3 per broker) on B0
+ for i in range(9):
+ msg = queue.get(timeout=5)
+
+ try:
+ extra = queue.get(timeout=1)
+ self.fail("Got unexpected message in queue: " + extra.body)
+ except Empty: None
+
+ # verify that messages went across every link
+ for _l in qmf.getObjects(_broker=self._brokers[0].qmf_broker,
+ _class="link"):
+ for _c in qmf.getObjects(_broker=self._brokers[0].qmf_broker,
+ _objectId=_l.connectionRef):
+ self.assertNotEqual(_c.msgsToClient, 0, "Messages did not pass over link as expected.")
+
+ # cleanup
+
+ self._brokers[0].client_session.exchange_unbind(queue="DestQ", exchange="fedX.direct", binding_key="spudboy")
+ self._brokers[0].client_session.message_cancel(destination="f1")
+ self._brokers[0].client_session.queue_delete(queue="DestQ")
+
+ for _b in qmf.getObjects(_class="bridge"):
+ result = _b.close()
+ self.assertEqual(result.status, 0)
+
+ for _l in qmf.getObjects(_class="link"):
+ result = _l.close()
+ self.assertEqual(result.status, 0)
+
+ for _b in self._brokers:
+ _b.client_session.exchange_delete(exchange="fedX.direct")
+
+ self._teardown_brokers()
+
+ self.verify_cleanup()
+
+ def test_dynamic_bounce_unbinds_named_queue(self):
+ """ Verify that a propagated binding is removed when the connection is
+ bounced
+ """
+ session = self.session
+
+ # create the federation
+
+ self.startQmf()
+ qmf = self.qmf
+
+ self._setup_brokers()
+
+ # create exchange on each broker, and retrieve the corresponding
+ # management object for that exchange
+
+ exchanges=[]
+ for _b in self._brokers[0:2]:
+ _b.client_session.exchange_declare(exchange="fedX", type="direct")
+ self.assertEqual(_b.client_session.exchange_query(name="fedX").type,
+ "direct", "exchange_declare failed!")
+ # pull the exchange out of qmf...
+ retries = 0
+ my_exchange = None
+ timeout = time() + 10
+ while my_exchange is None and time() <= timeout:
+ objs = qmf.getObjects(_broker=_b.qmf_broker, _class="exchange")
+ for ooo in objs:
+ if ooo.name == "fedX":
+ my_exchange = ooo
+ break
+ if my_exchange is None:
+ self.fail("QMF failed to find new exchange!")
+ exchanges.append(my_exchange)
+
+ # on the destination broker, create a binding for propagation
+ self._brokers[0].client_session.queue_declare(queue="fedDstQ")
+ self._brokers[0].client_session.exchange_bind(queue="fedDstQ", exchange="fedX", binding_key="spud")
+
+ # on the source broker, create a bridge queue
+ self._brokers[1].client_session.queue_declare(queue="fedSrcQ")
+
+ # connect B1 --> B0
+ result = self._brokers[0].qmf_object.create( "link",
+ "Link-dynamic",
+ {"host":self._brokers[1].host,
+ "port":self._brokers[1].port}, False)
+ self.assertEqual(result.status, 0)
+
+ # bridge the "fedX" exchange:
+ result = self._brokers[0].qmf_object.create("bridge",
+ "Bridge-dynamic",
+ {"link":"Link-dynamic",
+ "src":"fedX",
+ "dest":"fedX",
+ "dynamic":True,
+ "queue":"fedSrcQ"}, False)
+ self.assertEqual(result.status, 0)
+
+ # wait for the inter-broker links to become operational
+ operational = False
+ timeout = time() + 10
+ while not operational and time() <= timeout:
+ operational = True
+ for _l in qmf.getObjects(_class="link"):
+ #print("Link=%s:%s %s" % (_l.host, _l.port, str(_l.state)))
+ if _l.state != "Operational":
+ operational = False
+ self.failUnless(operational, "inter-broker links failed to become operational.")
+
+ # wait until the binding key has propagated to the src broker
+ exchanges[1].update()
+ timeout = time() + 10
+ while exchanges[1].bindingCount < 1 and time() <= timeout:
+ exchanges[1].update()
+ self.failUnless(exchanges[1].bindingCount == 1)
+
+ #
+ # Tear down the bridges between the two exchanges, then wait
+ # for the bindings to be cleaned up
+ #
+ for _b in qmf.getObjects(_class="bridge"):
+ result = _b.close()
+ self.assertEqual(result.status, 0)
+ exchanges[1].update()
+ timeout = time() + 10
+ while exchanges[1].bindingCount != 0 and time() <= timeout:
+ exchanges[1].update()
+ self.failUnless(exchanges[1].bindingCount == 0)
+
+ self._brokers[1].client_session.queue_delete(queue="fedSrcQ")
+
+ for _b in qmf.getObjects(_class="bridge"):
+ result = _b.close()
+ self.assertEqual(result.status, 0)
+
+ for _l in qmf.getObjects(_class="link"):
+ result = _l.close()
+ self.assertEqual(result.status, 0)
+
+ for _b in self._brokers[0:2]:
+ _b.client_session.exchange_delete(exchange="fedX")
+
+ self._teardown_brokers()
+
+ self.verify_cleanup()
+
+ def test_credit(self):
+ """ Test a federation link configured to use explict acks and a credit
+ limit
+ """
+ session = self.session
+
+ # setup queue on remote broker and add some messages
+ r_conn = self.connect(host=self.remote_host(), port=self.remote_port())
+ r_session = r_conn.session("test_credit")
+ r_session.queue_declare(queue="my-bridge-queue", auto_delete=True)
+
+ #setup queue to receive messages from local broker
+ session.queue_declare(queue="fed1", exclusive=True, auto_delete=True)
+ session.exchange_bind(queue="fed1", exchange="amq.fanout")
+ self.subscribe(queue="fed1", destination="f1")
+ queue = session.incoming("f1")
+
+ self.startQmf()
+ qmf = self.qmf
+ broker = qmf.getObjects(_class="broker")[0]
+ result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0, result)
+
+ link = qmf.getObjects(_class="link")[0]
+
+ # now wait for Link to go operational
+ retries = 0
+ operational = False
+ while not operational:
+ link.update()
+ if link.state == "Operational":
+ operational = True;
+ if not operational:
+ retries += 1
+ self.failIfEqual(retries, 10,
+ "inter-broker links failed to become operational.")
+ sleep(1)
+
+ # create the subscription
+ result = link.bridge(False, "my-bridge-queue", "amq.fanout", "my-key",
+ "", "", True, False, False,
+ 3, # explicit ack, with sync every 3 msgs
+ 7) # msg credit
+ self.assertEqual(result.status, 0, result)
+ bridge = qmf.getObjects(_class="bridge")[0]
+
+ # generate enough traffic to trigger flow control and syncs
+ for i in range(1000):
+ dp = r_session.delivery_properties(routing_key="my-bridge-queue")
+ r_session.message_transfer(message=Message(dp, "Message %d" % i))
+
+ for i in range(1000):
+ try:
+ msg = queue.get(timeout=5)
+ self.assertEqual("Message %d" % i, msg.body)
+ except Empty:
+ self.fail("Failed to find expected message containing 'Message %d'" % i)
+ try:
+ extra = queue.get(timeout=1)
+ self.fail("Got unexpected message in queue: " + extra.body)
+ except Empty: None
+
+ result = bridge.close()
+ self.assertEqual(result.status, 0, result)
+ result = link.close()
+ self.assertEqual(result.status, 0, result)
+
+ r_session.close()
+ r_conn.close()
+
+ self.verify_cleanup()
+
diff --git a/qpid/cpp/src/tests/federation_sys.py b/qpid/cpp/src/tests/federation_sys.py
new file mode 100755
index 0000000000..be9613bb9f
--- /dev/null
+++ b/qpid/cpp/src/tests/federation_sys.py
@@ -0,0 +1,977 @@
+#!/usr/bin/env python
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+from inspect import stack
+from qpid import messaging
+from qpid.messaging import Message
+from qpid.messaging.exceptions import Empty
+from qpid.testlib import TestBase010
+from random import randint
+from sys import stdout
+from time import sleep
+
+
+class Enum(object):
+ def __init__(self, **entries):
+ self.__dict__.update(entries)
+ def __repr__(self):
+ args = ['%s=%s' % (k, repr(v)) for (k,v) in vars(self).items()]
+ return 'Enum(%s)' % ', '.join(args)
+
+
+class QmfTestBase010(TestBase010):
+
+ _brokers = []
+ _links = []
+ _bridges = []
+ _alt_exch_ops = Enum(none=0, create=1, delete=2)
+
+ class _Broker(object):
+ """
+ This broker proxy object holds the Qmf proxy to a broker of known address as well as the QMF broker
+ object, connection and sessions to the broker.
+ """
+ def __init__(self, url):
+ self.url = url # format: "host:port"
+ url_parts = url.split(':')
+ self.host = url_parts[0]
+ self.port = int(url_parts[1])
+ self.qmf_broker = None
+ self.connection = messaging.Connection.establish(self.url)
+ self.sessions = []
+ def __str__(self):
+ return "_Broker %s:%s (%d open sessions)" % (self.host, self.port, len(self.sessions))
+ def destroy(self, qmf = None):
+ if qmf is not None:
+ qmf.delBroker(self.qmf_broker.getBroker())
+ for session in self.sessions:
+ try: # Session may have been closed by broker error
+ session.close()
+ except Exception, e: print "WARNING: %s: Unable to close session %s (%s): %s %s" % (self, session, hex(id(session)), type(e), e)
+ try: # Connection may have been closed by broker error
+ self.connection.close()
+ except Exception, e: print "WARNING: %s: Unable to close connection %s (%s): %s %s" % (self, self.connection, hex(id(self.connection)), type(e), e)
+ def session(self, name, transactional_flag = False):
+ session = self.connection.session(name, transactional_flag)
+ self.sessions.append(session)
+ return session
+
+ def setUp(self):
+ """
+ Called one before each test starts
+ """
+ TestBase010.setUp(self)
+ self.startQmf();
+
+ def tearDown(self):
+ """
+ Called once after each test competes. Close all Qmf objects (bridges, links and brokers)
+ """
+ while len(self._bridges):
+ self._bridges.pop().close()
+ while len(self._links):
+ self._links.pop().close()
+ while len(self._brokers):
+ b = self._brokers.pop()
+ if len(self._brokers) <= 1:
+ b.destroy(None)
+ else:
+ b.destroy(self.qmf)
+ TestBase010.tearDown(self)
+ self.qmf.close()
+
+ #--- General test utility functions
+
+ def _get_name(self):
+ """
+ Return the name of method which called this method stripped of "test_" prefix. Used for naming
+ queues and exchanges on a per-test basis.
+ """
+ return stack()[1][3][5:]
+
+ def _get_broker_port(self, key):
+ """
+ Get the port of a broker defined in the environment using -D<key>=portno
+ """
+ return int(self.defines[key])
+
+ def _get_send_address(self, exch_name, queue_name):
+ """
+ Get an address to which to send messages based on the exchange name and queue name, but taking into account
+ that the exchange name may be "" (the default exchange), in whcih case the format changes slightly.
+ """
+ if len(exch_name) == 0: # Default exchange
+ return queue_name
+ return "%s/%s" % (exch_name, queue_name)
+
+ def _get_broker(self, broker_port_key):
+ """
+ Read the port numbers for pre-started brokers from the environment using keys, then find or create and return
+ the Qmf broker proxy for the appropriate broker
+ """
+ port = self._get_broker_port(broker_port_key)
+ return self._find_create_broker("localhost:%s" % port)
+ ################
+ def _get_msg_subject(self, topic_key):
+ """
+ Return an appropriate subject for sending a message to a known topic. Return None if there is no topic.
+ """
+ if len(topic_key) == 0: return None
+ if "*" in topic_key: return topic_key.replace("*", "test")
+ if "#" in topic_key: return topic_key.replace("#", "multipart.test")
+ return topic_key
+
+ def _send_msgs(self, session_name, broker, addr, msg_count, msg_content = "Message_%03d", topic_key = "",
+ msg_durable_flag = False, enq_txn_size = 0):
+ """
+ Send messages to a broker using address addr
+ """
+ send_session = broker.session(session_name, transactional_flag = enq_txn_size > 0)
+ sender = send_session.sender(addr)
+ txn_cnt = 0
+ for i in range(0, msg_count):
+ sender.send(Message(msg_content % (i + 1), subject = self._get_msg_subject(topic_key), durable = msg_durable_flag))
+ if enq_txn_size > 0:
+ txn_cnt += 1
+ if txn_cnt >= enq_txn_size:
+ send_session.commit()
+ txn_cnt = 0
+ if enq_txn_size > 0 and txn_cnt > 0:
+ send_session.commit()
+ sender.close()
+ send_session.close()
+
+ def _receive_msgs(self, session_name, broker, addr, msg_count, msg_content = "Message_%03d", deq_txn_size = 0,
+ timeout = 0):
+ """
+ Receive messages from a broker
+ """
+ receive_session = broker.session(session_name, transactional_flag = deq_txn_size > 0)
+ receiver = receive_session.receiver(addr)
+ txn_cnt = 0
+ for i in range(0, msg_count):
+ try:
+ msg = receiver.fetch(timeout = timeout)
+ if deq_txn_size > 0:
+ txn_cnt += 1
+ if txn_cnt >= deq_txn_size:
+ receive_session.commit()
+ txn_cnt = 0
+ receive_session.acknowledge()
+ except Empty:
+ if deq_txn_size > 0: receive_session.rollback()
+ receiver.close()
+ receive_session.close()
+ if i == 0:
+ self.fail("Broker %s queue \"%s\" is empty" % (broker.qmf_broker.getBroker().getUrl(), addr))
+ else:
+ self.fail("Unable to receive message %d from broker %s queue \"%s\"" % (i, broker.qmf_broker.getBroker().getUrl(), addr))
+ if msg.content != msg_content % (i + 1):
+ receiver.close()
+ receive_session.close()
+ self.fail("Unexpected message \"%s\", was expecting \"%s\"." % (msg.content, msg_content % (i + 1)))
+ try:
+ msg = receiver.fetch(timeout = 0)
+ if deq_txn_size > 0: receive_session.rollback()
+ receiver.close()
+ receive_session.close()
+ self.fail("Extra message \"%s\" found on broker %s address \"%s\"" % (msg.content, broker.qmf_broker.getBroker().getUrl(), addr))
+ except Empty:
+ pass
+ if deq_txn_size > 0 and txn_cnt > 0:
+ receive_session.commit()
+ receiver.close()
+ receive_session.close()
+
+ #--- QMF-specific utility functions
+
+ def _get_qmf_property(self, props, key):
+ """
+ Get the value of a named property key kj from a property list [(k0, v0), (k1, v1), ... (kn, vn)].
+ """
+ for k,v in props:
+ if k.name == key:
+ return v
+ return None
+
+ def _check_qmf_return(self, method_result):
+ """
+ Check the result of a Qmf-defined method call
+ """
+ self.assertTrue(method_result.status == 0, method_result.text)
+
+ def _check_optional_qmf_property(self, qmf_broker, type, qmf_object, key, expected_val, obj_ref_flag):
+ """
+ Optional Qmf properties don't show up in the properties list when they are not specified. Checks for
+ these property types involve searching the properties list and making sure it is present or not as
+ expected.
+ """
+ val = self._get_qmf_property(qmf_object.getProperties(), key)
+ if val is None:
+ if len(expected_val) > 0:
+ self.fail("%s %s exists, but has does not have %s property. Expected value: \"%s\"" %
+ (type, qmf_object.name, key, expected_val))
+ else:
+ if len(expected_val) > 0:
+ if obj_ref_flag:
+ obj = self.qmf.getObjects(_objectId = val, _broker = qmf_broker.getBroker())
+ self.assertEqual(len(obj), 1, "More than one object with the same objectId: %s" % obj)
+ val = obj[0].name
+ self.assertEqual(val, expected_val, "%s %s exists, but has incorrect %s property. Found \"%s\", expected \"%s\"" %
+ (type, qmf_object.name, key, val, expected_val))
+ else:
+ self.fail("%s %s exists, but has an unexpected %s property \"%s\" set." % (type, qmf_object.name, key, val))
+
+ #--- Find/create Qmf broker objects
+
+ def _find_qmf_broker(self, url):
+ """
+ Find the Qmf broker object for the given broker URL. The broker must have been previously added to Qmf through
+ addBroker()
+ """
+ for b in self.qmf.getObjects(_class="broker"):
+ if b.getBroker().getUrl() == url:
+ return b
+ return None
+
+ def _find_create_broker(self, url):
+ """
+ Find a running broker through Qmf. If it does not exist, add it (assuming the broker is already running).
+ """
+ broker = self._Broker(url)
+ self._brokers.append(broker)
+ if self.qmf is not None:
+ qmf_broker = self._find_qmf_broker(broker.url)
+ if qmf_broker is None:
+ self.qmf.addBroker(broker.url)
+ broker.qmf_broker = self._find_qmf_broker(broker.url)
+ else:
+ broker.qmf_broker = qmf_broker
+ return broker
+
+ #--- Find/create/delete exchanges
+
+ def _find_qmf_exchange(self, qmf_broker, name, type, alternate, durable, auto_delete):
+ """
+ Find Qmf exchange object
+ """
+ for e in self.qmf.getObjects(_class="exchange", _broker = qmf_broker.getBroker()):
+ if e.name == name:
+ if len(name) == 0 or (len(name) >= 4 and name[:4] == "amq."): return e # skip checks for special exchanges
+ self.assertEqual(e.type, type,
+ "Exchange \"%s\" exists, but is of unexpected type %s; expected type %s." %
+ (name, e.type, type))
+ self._check_optional_qmf_property(qmf_broker, "Exchange", e, "altExchange", alternate, True)
+ self.assertEqual(e.durable, durable,
+ "Exchange \"%s\" exists, but has incorrect durability. Found durable=%s, expected durable=%s" %
+ (name, e.durable, durable))
+ self.assertEqual(e.autoDelete, auto_delete,
+ "Exchange \"%s\" exists, but has incorrect auto-delete property. Found %s, expected %s" %
+ (name, e.autoDelete, auto_delete))
+ return e
+ return None
+
+ def _find_create_qmf_exchange(self, qmf_broker, name, type, alternate, durable, auto_delete, args):
+ """
+ Find Qmf exchange object if exchange exists, create exchange and return its Qmf object if not
+ """
+ e = self._find_qmf_exchange(qmf_broker, name, type, alternate, durable, auto_delete)
+ if e is not None: return e
+ # Does not exist, so create it
+ props = dict({"exchange-type": type, "type": type, "durable": durable, "auto-delete": auto_delete, "alternate-exchange": alternate}, **args)
+ self._check_qmf_return(qmf_broker.create(type="exchange", name=name, properties=props, strict=True))
+ e = self._find_qmf_exchange(qmf_broker, name, type, alternate, durable, auto_delete)
+ self.assertNotEqual(e, None, "Creation of exchange %s on broker %s failed" % (name, qmf_broker.getBroker().getUrl()))
+ return e
+
+ def _find_delete_qmf_exchange(self, qmf_broker, name, type, alternate, durable, auto_delete):
+ """
+ Find and delete Qmf exchange object if it exists
+ """
+ e = self._find_qmf_exchange(qmf_broker, name, type, alternate, durable, auto_delete)
+ if e is not None and not auto_delete:
+ self._check_qmf_return(qmf_broker.delete(type="exchange", name=name, options={}))
+
+ #--- Find/create/delete queues
+
+ def _find_qmf_queue(self, qmf_broker, name, alternate_exchange, durable, exclusive, auto_delete):
+ """
+ Find a Qmf queue object
+ """
+ for q in self.qmf.getObjects(_class="queue", _broker = qmf_broker.getBroker()):
+ if q.name == name:
+ self._check_optional_qmf_property(qmf_broker, "Queue", q, "altExchange", alternate_exchange, True)
+ self.assertEqual(q.durable, durable,
+ "Queue \"%s\" exists, but has incorrect durable property. Found %s, expected %s" %
+ (name, q.durable, durable))
+ self.assertEqual(q.exclusive, exclusive,
+ "Queue \"%s\" exists, but has incorrect exclusive property. Found %s, expected %s" %
+ (name, q.exclusive, exclusive))
+ self.assertEqual(q.autoDelete, auto_delete,
+ "Queue \"%s\" exists, but has incorrect auto-delete property. Found %s, expected %s" %
+ (name, q.autoDelete, auto_delete))
+ return q
+ return None
+
+ def _find_create_qmf_queue(self, qmf_broker, name, alternate_exchange, durable, exclusive, auto_delete, args):
+ """
+ Find Qmf queue object if queue exists, create queue and return its Qmf object if not
+ """
+ q = self._find_qmf_queue(qmf_broker, name, alternate_exchange, durable, exclusive, auto_delete)
+ if q is not None: return q
+ # Queue does not exist, so create it
+ props = dict({"durable": durable, "auto-delete": auto_delete, "exclusive": exclusive, "alternate-exchange": alternate_exchange}, **args)
+ self._check_qmf_return(qmf_broker.create(type="queue", name=name, properties=props, strict=True))
+ q = self._find_qmf_queue(qmf_broker, name, alternate_exchange, durable, exclusive, auto_delete)
+ self.assertNotEqual(q, None, "Creation of queue %s on broker %s failed" % (name, qmf_broker.getBroker().getUrl()))
+ return q
+
+ def _find_delete_qmf_queue(self, qmf_broker, name, alternate_exchange, durable, exclusive, auto_delete, args):
+ """
+ Find and delete Qmf queue object if it exists
+ """
+ q = self._find_qmf_queue(qmf_broker, name, alternate_exchange, durable, exclusive, auto_delete)
+ if q is not None and not auto_delete:
+ self._check_qmf_return(qmf_broker.delete(type="queue", name=name, options={}))
+
+ #--- Find/create/delete bindings (between an exchange and a queue)
+
+ def _find_qmf_binding(self, qmf_broker, qmf_exchange, qmf_queue, binding_key, binding_args):
+ """
+ Find a Qmf binding object
+ """
+ for b in self.qmf.getObjects(_class="binding", _broker = qmf_broker.getBroker()):
+ if b.exchangeRef == qmf_exchange.getObjectId() and b.queueRef == qmf_queue.getObjectId():
+ if qmf_exchange.type != "fanout": # Fanout ignores the binding key, and always returns "" as the key
+ self.assertEqual(b.bindingKey, binding_key,
+ "Binding between exchange %s and queue %s exists, but has mismatching binding key: Found %s, expected %s." %
+ (qmf_exchange.name, qmf_queue.name, b.bindingKey, binding_key))
+ self.assertEqual(b.arguments, binding_args,
+ "Binding between exchange %s and queue %s exists, but has mismatching arguments: Found %s, expected %s" %
+ (qmf_exchange.name, qmf_queue.name, b.arguments, binding_args))
+ return b
+ return None
+
+ def _find_create_qmf_binding(self, qmf_broker, qmf_exchange, qmf_queue, binding_key, binding_args):
+ """
+ Find Qmf binding object if it exists, create binding and return its Qmf object if not
+ """
+ b = self._find_qmf_binding(qmf_broker, qmf_exchange, qmf_queue, binding_key, binding_args)
+ if b is not None: return b
+ # Does not exist, so create it
+ self._check_qmf_return(qmf_broker.create(type="binding", name="%s/%s/%s" % (qmf_exchange.name, qmf_queue.name, binding_key), properties=binding_args, strict=True))
+ b = self._find_qmf_binding(qmf_broker, qmf_exchange, qmf_queue, binding_key, binding_args)
+ self.assertNotEqual(b, None, "Creation of binding between exchange %s and queue %s with key %s failed" %
+ (qmf_exchange.name, qmf_queue.name, binding_key))
+ return b
+
+ def _find_delete_qmf_binding(self, qmf_broker, qmf_exchange, qmf_queue, binding_key, binding_args):
+ """
+ Find and delete Qmf binding object if it exists
+ """
+ b = self._find_qmf_binding(qmf_broker, qmf_exchange, qmf_queue, binding_key, binding_args)
+ if b is not None:
+ if len(qmf_exchange.name) > 0: # not default exchange
+ self._check_qmf_return(qmf_broker.delete(type="binding", name="%s/%s/%s" % (qmf_exchange.name, qmf_queue.name, binding_key), options={}))
+
+ #--- Find/create a link
+
+ def _find_qmf_link(self, qmf_from_broker_proxy, host, port):
+ """
+ Find a Qmf link object
+ """
+ for l in self.qmf.getObjects(_class="link", _broker=qmf_from_broker_proxy):
+ if l.host == host and l.port == port:
+ return l
+ return None
+
+ def _find_create_qmf_link(self, qmf_from_broker, qmf_to_broker_proxy, link_durable_flag, auth_mechanism, user_id,
+ password, transport, pause_interval, link_ready_timeout):
+ """
+ Find a Qmf link object if it exists, create it and return its Qmf link object if not
+ """
+ to_broker_host = qmf_to_broker_proxy.host
+ to_broker_port = qmf_to_broker_proxy.port
+ l = self._find_qmf_link(qmf_from_broker.getBroker(), to_broker_host, to_broker_port)
+ if l is not None: return l
+ # Does not exist, so create it
+ self._check_qmf_return(qmf_from_broker.connect(to_broker_host, to_broker_port, link_durable_flag, auth_mechanism, user_id, password, transport))
+ l = self._find_qmf_link(qmf_from_broker.getBroker(), to_broker_host, to_broker_port)
+ self.assertNotEqual(l, None, "Creation of link from broker %s to broker %s failed" %
+ (qmf_from_broker.getBroker().getUrl(), qmf_to_broker_proxy.getUrl()))
+ self._wait_for_link(l, pause_interval, link_ready_timeout)
+ return l
+
+ def _wait_for_link(self, link, pause_interval, link_ready_timeout):
+ """
+ Wait for link to become active (state=Operational)
+ """
+ tot_time = 0
+ link.update()
+ while link.state != "Operational" and tot_time < link_ready_timeout:
+ sleep(pause_interval)
+ tot_time += pause_interval
+ link.update()
+ self.assertEqual(link.state, "Operational", "Timeout: Link not operational, state=%s" % link.state)
+
+ #--- Find/create a bridge
+
+ def _find_qmf_bridge(self, qmf_broker_proxy, qmf_link, source, destination, key):
+ """
+ Find a Qmf link object
+ """
+ for b in self.qmf.getObjects(_class="bridge", _broker=qmf_broker_proxy):
+ if b.linkRef == qmf_link.getObjectId() and b.src == source and b.dest == destination and b.key == key:
+ return b
+ return None
+
+ def _find_create_qmf_bridge(self, qmf_broker_proxy, qmf_link, queue_name, exch_name, topic_key,
+ queue_route_type_flag, bridge_durable_flag):
+ """
+ Find a Qmf bridge object if it exists, create it and return its Qmf object if not
+ """
+ if queue_route_type_flag:
+ src = queue_name
+ dest = exch_name
+ key = ""
+ else:
+ src = exch_name
+ dest = exch_name
+ if len(topic_key) > 0:
+ key = topic_key
+ else:
+ key = queue_name
+ b = self._find_qmf_bridge(qmf_broker_proxy, qmf_link, src, dest, key)
+ if b is not None:
+ return b
+ # Does not exist, so create it
+ self._check_qmf_return(qmf_link.bridge(bridge_durable_flag, src, dest, key, "", "", queue_route_type_flag, False, False, 1, 0))
+ b = self._find_qmf_bridge(qmf_broker_proxy, qmf_link, src, dest, key)
+ self.assertNotEqual(b, None, "Bridge creation failed: src=%s dest=%s key=%s" % (src, dest, key))
+ return b
+
+ def _wait_for_bridge(self, bridge, src_broker, dest_broker, exch_name, queue_name, topic_key, pause_interval,
+ bridge_ready_timeout):
+ """
+ Wait for bridge to become active by sending messages over the bridge at 1 sec intervals until they are
+ observed at the destination.
+ """
+ tot_time = 0
+ active = False
+ send_session = src_broker.session("tx")
+ sender = send_session.sender(self._get_send_address(exch_name, queue_name))
+ src_receive_session = src_broker.session("src_rx")
+ src_receiver = src_receive_session.receiver(queue_name)
+ dest_receive_session = dest_broker.session("dest_rx")
+ dest_receiver = dest_receive_session.receiver(queue_name)
+ while not active and tot_time < bridge_ready_timeout:
+ sender.send(Message("xyz123", subject = self._get_msg_subject(topic_key)))
+ try:
+ src_receiver.fetch(timeout = 0)
+ src_receive_session.acknowledge()
+ # Keep receiving msgs, as several may have accumulated
+ while True:
+ dest_receiver.fetch(timeout = 0)
+ dest_receive_session.acknowledge()
+ sleep(1)
+ active = True
+ except Empty:
+ sleep(pause_interval)
+ tot_time += pause_interval
+ dest_receiver.close()
+ dest_receive_session.close()
+ src_receiver.close()
+ src_receive_session.close()
+ sender.close()
+ send_session.close()
+ self.assertTrue(active, "Bridge failed to become active after %ds: %s" % (bridge_ready_timeout, bridge))
+
+ #--- Find/create/delete utility functions
+
+ def _create_and_bind(self, qmf_broker, exchange_args, queue_args, binding_args):
+ """
+ Create a binding between a named exchange and queue on a broker
+ """
+ e = self._find_create_qmf_exchange(qmf_broker, **exchange_args)
+ q = self._find_create_qmf_queue(qmf_broker, **queue_args)
+ return self._find_create_qmf_binding(qmf_broker, e, q, **binding_args)
+
+ def _check_alt_exchange(self, qmf_broker, alt_exch_name, alt_exch_type, alt_exch_op):
+ """
+ Check for existence of alternate exchange. Return the Qmf exchange proxy object for the alternate exchange
+ """
+ if len(alt_exch_name) == 0: return None
+ if alt_exch_op == _alt_exch_ops.create:
+ return self._find_create_qmf_exchange(qmf_broker=qmf_broker, name=alt_exch_name, type=alt_exch_type,
+ alternate="", durable=False, auto_delete=False, args={})
+ if alt_exch_op == _alt_exch_ops.delete:
+ return self._find_delete_qmf_exchange(qmf_broker=qmf_broker, name=alt_exch_name, type=alt_exch_type,
+ alternate="", durable=False, auto_delete=False)
+ return self._find_qmf_exchange(qmf_broker=qmf_broker, name=alt_exchange_name, type=alt_exchange_type,
+ alternate="", durable=False, auto_delete=False)
+
+ def _delete_queue_binding(self, qmf_broker, exchange_args, queue_args, binding_args):
+ """
+ Delete a queue and the binding between it and the exchange
+ """
+ e = self._find_qmf_exchange(qmf_broker, exchange_args["name"], exchange_args["type"], exchange_args["alternate"], exchange_args["durable"], exchange_args["auto_delete"])
+ q = self._find_qmf_queue(qmf_broker, queue_args["name"], queue_args["alternate_exchange"], queue_args["durable"], queue_args["exclusive"], queue_args["auto_delete"])
+ self._find_delete_qmf_binding(qmf_broker, e, q, **binding_args)
+ self._find_delete_qmf_queue(qmf_broker, **queue_args)
+
+ def _create_route(self, queue_route_type_flag, src_broker, dest_broker, exch_name, queue_name, topic_key,
+ link_durable_flag, bridge_durable_flag, auth_mechanism, user_id, password, transport,
+ pause_interval = 1, link_ready_timeout = 20, bridge_ready_timeout = 20):
+ """
+ Create a route from a source broker to a destination broker
+ """
+ l = self._find_create_qmf_link(dest_broker.qmf_broker, src_broker.qmf_broker.getBroker(), link_durable_flag,
+ auth_mechanism, user_id, password, transport, pause_interval, link_ready_timeout)
+ self._links.append(l)
+ b = self._find_create_qmf_bridge(dest_broker.qmf_broker.getBroker(), l, queue_name, exch_name, topic_key,
+ queue_route_type_flag, bridge_durable_flag)
+ self._bridges.append(b)
+ self._wait_for_bridge(b, src_broker, dest_broker, exch_name, queue_name, topic_key, pause_interval, bridge_ready_timeout)
+
+ # Parameterized test - entry point for tests
+
+ def _do_test(self,
+ test_name, # Name of test
+ exch_name = "amq.direct", # Remote exchange name
+ exch_type = "direct", # Remote exchange type
+ exch_alt_exch = "", # Remote exchange alternate exchange
+ exch_alt_exch_type = "direct", # Remote exchange alternate exchange type
+ exch_durable_flag = False, # Remote exchange durability
+ exch_auto_delete_flag = False, # Remote exchange auto-delete property
+ exch_x_args = {}, # Remote exchange args
+ queue_alt_exch = "", # Remote queue alternate exchange
+ queue_alt_exch_type = "direct", # Remote queue alternate exchange type
+ queue_durable_flag = False, # Remote queue durability
+ queue_exclusive_flag = False, # Remote queue exclusive property
+ queue_auto_delete_flag = False, # Remote queue auto-delete property
+ queue_x_args = {}, # Remote queue args
+ binding_durable_flag = False, # Remote binding durability
+ binding_x_args = {}, # Remote binding args
+ topic_key = "", # Binding key For remote topic exchanges only
+ msg_count = 10, # Number of messages to send
+ msg_durable_flag = False, # Message durability
+ link_durable_flag = False, # Route link durability
+ bridge_durable_flag = False, # Route bridge durability
+ queue_route_type_flag = False, # Route type: false = bridge route, true = queue route
+ enq_txn_size = 0, # Enqueue transaction size, 0 = no transactions
+ deq_txn_size = 0, # Dequeue transaction size, 0 = no transactions
+ alt_exch_op = _alt_exch_ops.create,# Op on alt exch [create (ensure present), delete (ensure not present), none (neither create nor delete)]
+ auth_mechanism = "", # Authorization mechanism for linked broker
+ user_id = "", # User ID for authorization on linked broker
+ password = "", # Password for authorization on linked broker
+ transport = "tcp" # Transport for route to linked broker
+ ):
+ """
+ Parameterized federation test. Sets up a federated link between a source broker and a destination broker and
+ checks that messages correctly pass over the link to the destination. Where appropriate (non-queue-routes), also
+ checks for the presence of messages on the source broker.
+
+ In these tests, the concept is to create a LOCAL broker, then create a link to a REMOTE broker using federation.
+ In other words, the messages sent to the LOCAL broker will be replicated on the REMOTE broker, and tests are
+ performed on the REMOTE broker to check that the required messages are present. In the case of regular routes,
+ the LOCAL broker will also retain the messages, and a similar test is performed on this broker.
+
+ TODO: There are several items to improve here:
+ 1. _do_test() is rather general. Rather create a version for each exchange type and test the exchange/queue
+ interaction in more detail based on the exchange type
+ 2. Add a headers and an xml exchange type
+ 3. Restructure the tests to start and stop brokers directly rather than relying on previously
+ started brokers. Then persistence can be checked by stopping and restarting the brokers. In particular,
+ test the persistence of links and bridges, both of which take a persistence flag.
+ 4. Test the behavior of the alternate exchanges when messages are sourced through a link. Also check behavior
+ when the alternate exchange is not present or is deleted after the reference is made.
+ 5. Test special queue types (eg LVQ)
+ """
+ local_broker = self._get_broker("local-port")
+ remote_broker = self._get_broker("remote-port")
+
+ # Check alternate exchanges exist (and create them if not) on both local and remote brokers
+ self._check_alt_exchange(local_broker.qmf_broker, exch_alt_exch, exch_alt_exch_type, alt_exch_op)
+ self._check_alt_exchange(local_broker.qmf_broker, queue_alt_exch, queue_alt_exch_type, alt_exch_op)
+ self._check_alt_exchange(remote_broker.qmf_broker, exch_alt_exch, exch_alt_exch_type, alt_exch_op)
+ self._check_alt_exchange(remote_broker.qmf_broker, queue_alt_exch, queue_alt_exch_type, alt_exch_op)
+
+ queue_name = "queue_%s" % test_name
+ exchange_args = {"name": exch_name, "type": exch_type, "alternate": exch_alt_exch,
+ "durable": exch_durable_flag, "auto_delete": exch_auto_delete_flag, "args": exch_x_args}
+ queue_args = {"name": queue_name, "alternate_exchange": queue_alt_exch, "durable": queue_durable_flag,
+ "exclusive": queue_exclusive_flag, "auto_delete": queue_auto_delete_flag, "args": queue_x_args}
+ binding_args = {"binding_args": binding_x_args}
+ if exch_type == "topic":
+ self.assertTrue(len(topic_key) > 0, "Topic exchange selected, but no topic key was set.")
+ binding_args["binding_key"] = topic_key
+ elif exch_type == "direct":
+ binding_args["binding_key"] = queue_name
+ else:
+ binding_args["binding_key"] = ""
+ self._create_and_bind(qmf_broker=local_broker.qmf_broker, exchange_args=exchange_args, queue_args=queue_args, binding_args=binding_args)
+ self._create_and_bind(qmf_broker=remote_broker.qmf_broker, exchange_args=exchange_args, queue_args=queue_args, binding_args=binding_args)
+ self._create_route(queue_route_type_flag, local_broker, remote_broker, exch_name, queue_name, topic_key,
+ link_durable_flag, bridge_durable_flag, auth_mechanism, user_id, password, transport)
+
+ self._send_msgs("send_session", local_broker, addr = self._get_send_address(exch_name, queue_name),
+ msg_count = msg_count, topic_key = topic_key, msg_durable_flag = msg_durable_flag, enq_txn_size = enq_txn_size)
+ if not queue_route_type_flag:
+ self._receive_msgs("local_receive_session", local_broker, addr = queue_name, msg_count = msg_count, deq_txn_size = deq_txn_size)
+ self._receive_msgs("remote_receive_session", remote_broker, addr = queue_name, msg_count = msg_count, deq_txn_size = deq_txn_size, timeout = 5)
+
+ # Clean up
+ self._delete_queue_binding(qmf_broker=local_broker.qmf_broker, exchange_args=exchange_args, queue_args=queue_args, binding_args=binding_args)
+ self._delete_queue_binding(qmf_broker=remote_broker.qmf_broker, exchange_args=exchange_args, queue_args=queue_args, binding_args=binding_args)
+
+class A_ShortTests(QmfTestBase010):
+
+ def test_route_defaultExch(self):
+ self._do_test(self._get_name())
+
+ def test_queueRoute_defaultExch(self):
+ self._do_test(self._get_name(), queue_route_type_flag=True)
+
+
+class A_LongTests(QmfTestBase010):
+
+ def test_route_amqDirectExch(self):
+ self._do_test(self._get_name(), exch_name="amq.direct")
+
+ def test_queueRoute_amqDirectExch(self):
+ self._do_test(self._get_name(), exch_name="amq.direct", queue_route_type_flag=True)
+
+
+ def test_route_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange")
+
+ def test_queueRoute_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True)
+
+
+ def test_route_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout")
+
+ def test_queueRoute_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True)
+
+
+ def test_route_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#")
+
+ def test_queueRoute_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True)
+
+
+class B_ShortTransactionTests(QmfTestBase010):
+
+ def test_txEnq01_route_defaultExch(self):
+ self._do_test(self._get_name(), enq_txn_size=1)
+
+ def test_txEnq01_queueRoute_defaultExch(self):
+ self._do_test(self._get_name(), queue_route_type_flag=True, enq_txn_size=1)
+
+ def test_txEnq01_txDeq01_route_defaultExch(self):
+ self._do_test(self._get_name(), enq_txn_size=1, deq_txn_size=1)
+
+ def test_txEnq01_txDeq01_queueRoute_defaultExch(self):
+ self._do_test(self._get_name(), queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1)
+
+
+class B_LongTransactionTests(QmfTestBase010):
+
+ def test_txEnq10_route_defaultExch(self):
+ self._do_test(self._get_name(), enq_txn_size=10, msg_count = 103)
+
+ def test_txEnq10_queueRoute_defaultExch(self):
+ self._do_test(self._get_name(), queue_route_type_flag=True, enq_txn_size=10, msg_count = 103)
+
+
+
+
+ def test_txEnq01_route_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange", enq_txn_size=1)
+
+ def test_txEnq01_queueRoute_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True, enq_txn_size=1)
+
+ def test_txEnq10_route_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange", enq_txn_size=10, msg_count = 103)
+
+ def test_txEnq10_queueRoute_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True, enq_txn_size=10, msg_count = 103)
+
+ def test_txEnq01_txDeq01_route_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange", enq_txn_size=1, deq_txn_size=1)
+
+ def test_txEnq01_txDeq01_queueRoute_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange", queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1)
+
+
+ def test_txEnq01_route_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", enq_txn_size=1)
+
+ def test_txEnq01_queueRoute_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True, enq_txn_size=1)
+
+ def test_txEnq10_route_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", enq_txn_size=10, msg_count = 103)
+
+ def test_txEnq10_queueRoute_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True, enq_txn_size=10, msg_count = 103)
+
+ def test_txEnq01_txDeq01_route_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", enq_txn_size=1, deq_txn_size=1)
+
+ def test_txEnq01_txDeq01_queueRoute_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1)
+
+
+ def test_txEnq01_route_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", enq_txn_size=1)
+
+ def test_txEnq01_queueRoute_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True, enq_txn_size=1)
+
+ def test_txEnq10_route_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", enq_txn_size=10, msg_count = 103)
+
+ def test_txEnq10_queueRoute_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True, enq_txn_size=10, msg_count = 103)
+
+ def test_txEnq01_txDeq01_route_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", enq_txn_size=1, deq_txn_size=1)
+
+ def test_txEnq01_txDeq01_queueRoute_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1)
+
+
+class E_ShortPersistenceTests(QmfTestBase010):
+
+ def test_route_durQueue_defaultExch(self):
+ self._do_test(self._get_name(), queue_durable_flag=True)
+
+ def test_route_durMsg_durQueue_defaultExch(self):
+ self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True)
+
+ def test_queueRoute_durQueue_defaultExch(self):
+ self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True)
+
+ def test_queueRoute_durMsg_durQueue_defaultExch(self):
+ self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True)
+
+
+class E_LongPersistenceTests(QmfTestBase010):
+
+
+ def test_route_durQueue_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True)
+
+ def test_route_durMsg_durQueue_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True)
+
+ def test_queueRoute_durQueue_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True)
+
+ def test_queueRoute_durMsg_durQueue_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True)
+
+
+ def test_route_durQueue_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True)
+
+ def test_route_durMsg_durQueue_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True)
+
+ def test_queueRoute_durQueue_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True)
+
+ def test_queueRoute_durMsg_durQueue_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True)
+
+
+ def test_route_durQueue_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True)
+
+ def test_route_durMsg_durQueue_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True)
+
+ def test_queueRoute_durQueue_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True)
+
+ def test_queueRoute_durMsg_durQueue_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True)
+
+
+class F_ShortPersistenceTransactionTests(QmfTestBase010):
+
+ def test_txEnq01_route_durQueue_defaultExch(self):
+ self._do_test(self._get_name(), queue_durable_flag=True, enq_txn_size=1)
+
+ def test_txEnq01_route_durMsg_durQueue_defaultExch(self):
+ self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1)
+
+ def test_txEnq01_queueRoute_durQueue_defaultExch(self):
+ self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1)
+
+ def test_txEnq01_queueRoute_durMsg_durQueue_defaultExch(self):
+ self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1)
+
+ def test_txEnq01_txDeq01_route_durQueue_defaultExch(self):
+ self._do_test(self._get_name(), queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1)
+
+ def test_txEnq01_txDeq01_route_durMsg_durQueue_defaultExch(self):
+ self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1)
+
+ def test_txEnq01_txDeq01_queueRoute_durQueue_defaultExch(self):
+ self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1)
+
+ def test_txEnq01_txDeq01_queueRoute_durMsg_durQueue_defaultExch(self):
+ self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1)
+
+
+class F_LongPersistenceTransactionTests(QmfTestBase010):
+
+ def test_txEnq10_route_durQueue_defaultExch(self):
+ self._do_test(self._get_name(), queue_durable_flag=True, enq_txn_size=10, msg_count = 103)
+
+ def test_txEnq10_route_durMsg_durQueue_defaultExch(self):
+ self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103)
+
+ def test_txEnq10_queueRoute_durQueue_defaultExch(self):
+ self._do_test(self._get_name(), queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103)
+
+ def test_txEnq10_queueRoute_durMsg_durQueue_defaultExch(self):
+ self._do_test(self._get_name(), msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103)
+
+
+
+
+ def test_txEnq01_route_durQueue_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, enq_txn_size=1)
+
+ def test_txEnq01_route_durMsg_durQueue_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1)
+
+ def test_txEnq01_queueRoute_durQueue_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1)
+
+ def test_txEnq01_queueRoute_durMsg_durQueue_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1)
+
+ def test_txEnq10_route_durQueue_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, enq_txn_size=10, msg_count = 103)
+
+ def test_txEnq10_route_durMsg_durQueue_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103)
+
+ def test_txEnq10_queueRoute_durQueue_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103)
+
+ def test_txEnq10_queueRoute_durMsg_durQueue_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103)
+
+ def test_txEnq01_txDeq01_route_durQueue_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1)
+
+ def test_txEnq01_txDeq01_route_durMsg_durQueue_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1)
+
+ def test_txEnq01_txDeq01_queueRoute_durQueue_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1)
+
+ def test_txEnq01_txDeq01_queueRoute_durMsg_durQueue_directExch(self):
+ self._do_test(self._get_name(), exch_name="testDirectExchange", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1)
+
+
+ def test_txEnq01_route_durQueue_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, enq_txn_size=1)
+
+ def test_txEnq01_route_durMsg_durQueue_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1)
+
+ def test_txEnq01_queueRoute_durQueue_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1)
+
+ def test_txEnq01_queueRoute_durMsg_durQueue_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1)
+
+ def test_txEnq10_route_durQueue_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, enq_txn_size=10, msg_count = 103)
+
+ def test_txEnq10_route_durMsg_durQueue_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103)
+
+ def test_txEnq10_queueRoute_durQueue_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103)
+
+ def test_txEnq10_queueRoute_durMsg_durQueue_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103)
+
+ def test_txEnq01_txDeq01_route_durQueue_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1)
+
+ def test_txEnq01_txDeq01_route_durMsg_durQueue_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1)
+
+ def test_txEnq01_txDeq01_queueRoute_durQueue_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1)
+
+ def test_txEnq01_txDeq01_queueRoute_durMsg_durQueue_fanoutExch(self):
+ self._do_test(self._get_name(), exch_name="testFanoutExchange", exch_type="fanout", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1)
+
+
+ def test_txEnq01_route_durQueue_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, enq_txn_size=1)
+
+ def test_txEnq01_route_durMsg_durQueue_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1)
+
+ def test_txEnq01_queueRoute_durQueue_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1)
+
+ def test_txEnq01_queueRoute_durMsg_durQueue_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1)
+
+ def test_txEnq10_route_durQueue_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, enq_txn_size=10, msg_count = 103)
+
+ def test_txEnq10_route_durMsg_durQueue_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=10, msg_count = 103)
+
+ def test_txEnq10_queueRoute_durQueue_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103)
+
+ def test_txEnq10_queueRoute_durMsg_durQueue_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=10, msg_count = 103)
+
+ def test_txEnq01_txDeq01_route_durQueue_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1)
+
+ def test_txEnq01_txDeq01_route_durMsg_durQueue_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, enq_txn_size=1, deq_txn_size=1)
+
+ def test_txEnq01_txDeq01_queueRoute_durQueue_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1)
+
+ def test_txEnq01_txDeq01_queueRoute_durMsg_durQueue_topicExch(self):
+ self._do_test(self._get_name(), exch_name="testTopicExchange", exch_type="topic", topic_key=self._get_name()+".#", msg_durable_flag=True, queue_durable_flag=True, queue_route_type_flag=True, enq_txn_size=1, deq_txn_size=1)
+
+
diff --git a/qpid/cpp/src/tests/find_prog.ps1 b/qpid/cpp/src/tests/find_prog.ps1
new file mode 100644
index 0000000000..5c482debbf
--- /dev/null
+++ b/qpid/cpp/src/tests/find_prog.ps1
@@ -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.
+#
+
+# Locate the subdirectory where the specified program resides; the program
+# must have a directory and a file name, even if the directory is .
+param(
+ [string] $prog # program to look for somewhere below cwd
+)
+
+$dir = Split-Path $prog
+$exe = Split-Path $prog -leaf
+$sub = ""
+$subs = "Debug","Release","MinSizeRel","RelWithDebInfo"
+foreach ($try in $subs) {
+ $prog = "$dir\$try\$exe"
+ if (Test-Path $prog) {
+ $sub = $try
+ break
+ }
+}
diff --git a/qpid/cpp/src/tests/ha_test.py b/qpid/cpp/src/tests/ha_test.py
new file mode 100755
index 0000000000..82ca808cb1
--- /dev/null
+++ b/qpid/cpp/src/tests/ha_test.py
@@ -0,0 +1,403 @@
+#!/usr/bin/env python
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 os, signal, sys, time, imp, re, subprocess, glob, random, logging, shutil, math, unittest, random
+import traceback
+from brokertest import *
+from threading import Thread, Lock, Condition
+from logging import getLogger, WARN, ERROR, DEBUG, INFO
+from qpidtoollibs import BrokerAgent
+from qpid.harness import Skipped
+
+log = getLogger(__name__)
+
+class LogLevel:
+ """
+ Temporarily change the log settings on the root logger.
+ Used to suppress expected WARN messages from the python client.
+ """
+ def __init__(self, level):
+ self.save_level = getLogger().getEffectiveLevel()
+ getLogger().setLevel(level)
+
+ def restore(self):
+ getLogger().setLevel(self.save_level)
+
+class QmfAgent(object):
+ """Access to a QMF broker agent."""
+ def __init__(self, address, **kwargs):
+ self._connection = qm.Connection.establish(
+ address, client_properties={"qpid.ha-admin":1}, **kwargs)
+ self._agent = BrokerAgent(self._connection)
+
+ def queues(self):
+ return [q.values['name'] for q in self._agent.getAllQueues()]
+
+ def repsub_queue(self, sub):
+ """If QMF subscription sub is a replicating subscription return
+ the name of the replicated queue, else return None"""
+ session = self.getSession(sub.sessionRef)
+ if not session: return None
+ m = re.search("qpid.ha-q:(.*)\.", session.name)
+ return m and m.group(1)
+
+ def repsub_queues(self):
+ """Return queue names for all replicating subscriptions"""
+ return filter(None, [self.repsub_queue(s) for s in self.getAllSubscriptions()])
+
+ def tx_queues(self):
+ """Return names of all tx-queues"""
+ return [q for q in self.queues() if q.startswith("qpid.ha-tx")]
+
+ def __getattr__(self, name):
+ a = getattr(self._agent, name)
+ return a
+
+class Credentials(object):
+ """SASL credentials: username, password, and mechanism"""
+ def __init__(self, username, password, mechanism):
+ (self.username, self.password, self.mechanism) = (username, password, mechanism)
+
+ def __str__(self): return "Credentials%s"%(self.tuple(),)
+
+ def tuple(self): return (self.username, self.password, self.mechanism)
+
+ def add_user(self, url): return "%s/%s@%s"%(self.username, self.password, url)
+
+class HaPort:
+ """Many HA tests need to allocate a broker port dynamically and then kill
+ and restart a broker on that same port multiple times. qpidd --port=0 only
+ ensures the port for the initial broker process, subsequent brokers re-using
+ the same port may fail with "address already in use".
+
+ HaPort binds and listens to the port and returns a file descriptor to pass
+ to qpidd --socket-fd. It holds on to the port untill the end of the test so
+ the broker can restart multiple times.
+ """
+
+ def __init__(self, test, port=0):
+ """Bind and listen to port. port=0 allocates a port dynamically.
+ self.port is the allocated port, self.fileno is the file descriptor for
+ qpid --socket-fd."""
+
+ self.test = test
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.socket.bind(("", port))
+ self.socket.listen(5)
+ self.port = self.socket.getsockname()[1]
+ self.fileno = self.socket.fileno()
+ self.stopped = False
+ test.teardown_add(self) # Stop during test.tearDown
+
+ def teardown(self): # Called in tearDown
+ if not self.stopped:
+ self.stopped = True
+ self.socket.shutdown(socket.SHUT_RDWR)
+ self.socket.close()
+
+ def __str__(self): return "HaPort<port:%s, fileno:%s>"%(self.port, self.fileno)
+
+
+class HaBroker(Broker):
+ """Start a broker with HA enabled
+ @param client_cred: (user, password, mechanism) for admin clients started by the HaBroker.
+ """
+
+ heartbeat=5
+
+ def __init__(self, test, ha_port=None, args=[], brokers_url=None, ha_cluster=True,
+ ha_replicate="all", client_credentials=None, **kwargs):
+ assert BrokerTest.ha_lib, "Cannot locate HA plug-in"
+ ha_port = ha_port or HaPort(test)
+ args = copy(args)
+ args += ["--load-module", BrokerTest.ha_lib,
+ # Non-standard settings for faster tests.
+ "--link-maintenance-interval=0.1",
+ "--ha-cluster=%s"%ha_cluster]
+ # Add default --log-enable arguments unless args already has --log arguments.
+ if not env_has_log_config() and not [l for l in args if l.startswith("--log")]:
+ args += ["--log-enable=info+", "--log-enable=debug+:ha::"]
+ if not [h for h in args if h.startswith("--link-heartbeat-interval")]:
+ args += ["--link-heartbeat-interval=%s"%(HaBroker.heartbeat)]
+
+ if ha_replicate is not None:
+ args += [ "--ha-replicate=%s"%ha_replicate ]
+ if brokers_url: args += [ "--ha-brokers-url", brokers_url ]
+ # Set up default ACL
+ acl=os.path.join(os.getcwd(), "unrestricted.acl")
+ if not os.path.exists(acl):
+ aclf=file(acl,"w")
+ aclf.write("""
+acl allow all all
+ """)
+ aclf.close()
+ if not "--acl-file" in args:
+ args += [ "--acl-file", acl, ]
+ args += ["--socket-fd=%s"%ha_port.fileno, "--listen-disable=tcp"]
+ self._agent = None
+ self.client_credentials = client_credentials
+ self.ha_port = ha_port
+ Broker.__init__(self, test, args, port=ha_port.port, **kwargs)
+
+ # Do some static setup to locate the qpid-config and qpid-ha tools.
+ @property
+ def qpid_ha_script(self):
+ if not hasattr(self, "_qpid_ha_script"):
+ qpid_ha_exec = os.getenv("QPID_HA_EXEC")
+ if not qpid_ha_exec or not os.path.isfile(qpid_ha_exec):
+ raise Skipped("qpid-ha not available")
+ self._qpid_ha_script = import_script(qpid_ha_exec)
+ return self._qpid_ha_script
+
+ def __repr__(self): return "<HaBroker:%s:%d>"%(self.log, self.port())
+
+ def qpid_ha(self, args):
+ if not self.qpid_ha_script:
+ raise Skipped("qpid-ha not available")
+ try:
+ cred = self.client_credentials
+ url = self.host_port()
+ if cred:
+ url =cred.add_user(url)
+ args = args + ["--sasl-mechanism", cred.mechanism]
+ self.qpid_ha_script.main_except(["", "-b", url]+args)
+ except Exception, e:
+ raise Exception("Error in qpid_ha -b %s %s: %s"%(url, args,e))
+
+ def promote(self): self.ready(); self.qpid_ha(["promote", "--cluster-manager"])
+ def replicate(self, from_broker, queue): self.qpid_ha(["replicate", from_broker, queue])
+ @property
+ def agent(self):
+ if not self._agent:
+ cred = self.client_credentials
+ if cred:
+ self._agent = QmfAgent(cred.add_user(self.host_port()), sasl_mechanisms=cred.mechanism)
+ else:
+ self._agent = QmfAgent(self.host_port())
+ return self._agent
+
+ def qmf(self):
+ hb = self.agent.getHaBroker()
+ hb.update()
+ return hb
+
+ def ha_status(self): return self.qmf().status
+
+ def wait_status(self, status, timeout=10):
+
+ def try_get_status():
+ self._status = "<unknown>"
+ try:
+ self._status = self.ha_status()
+ except qm.ConnectionError, e:
+ # Record the error but don't raise, the broker may not be up yet.
+ self._status = "%s: %s" % (type(e).__name__, e)
+ return self._status == status;
+ assert retry(try_get_status, timeout=timeout), "%s expected=%r, actual=%r"%(
+ self, status, self._status)
+
+ def wait_queue(self, queue, timeout=10, msg="wait_queue"):
+ """ Wait for queue to be visible via QMF"""
+ agent = self.agent
+ assert retry(lambda: agent.getQueue(queue) is not None, timeout=timeout), \
+ "%s queue %s not present" % (msg, queue)
+
+ def wait_no_queue(self, queue, timeout=10, msg="wait_no_queue"):
+ """ Wait for queue to be invisible via QMF"""
+ agent = self.agent
+ assert retry(lambda: agent.getQueue(queue) is None, timeout=timeout), "%s: queue %s still present"%(msg,queue)
+
+ def qpid_config(self, args):
+ qpid_config_exec = os.getenv("QPID_CONFIG_EXEC")
+ if not qpid_config_exec or not os.path.isfile(qpid_config_exec):
+ raise Skipped("qpid-config not available")
+ assert subprocess.call(
+ [qpid_config_exec, "--broker", self.host_port()]+args, stdout=1, stderr=subprocess.STDOUT
+ ) == 0, "qpid-config failed"
+
+ def config_replicate(self, from_broker, queue):
+ self.qpid_config(["add", "queue", "--start-replica", from_broker, queue])
+
+ def config_declare(self, queue, replication):
+ self.qpid_config(["add", "queue", queue, "--replicate", replication])
+
+ def connect_admin(self, **kwargs):
+ cred = self.client_credentials
+ if cred:
+ return Broker.connect(
+ self, client_properties={"qpid.ha-admin":1},
+ username=cred.username, password=cred.password, sasl_mechanisms=cred.mechanism,
+ **kwargs)
+ else:
+ return Broker.connect(self, client_properties={"qpid.ha-admin":1}, **kwargs)
+
+ def wait_address(self, address):
+ """Wait for address to become valid on the broker."""
+ c = self.connect_admin()
+ try: wait_address(c, address)
+ finally: c.close()
+
+ wait_backup = wait_address
+
+ def browse(self, queue, timeout=0, transform=lambda m: m.content):
+ c = self.connect_admin()
+ try:
+ return browse(c.session(), queue, timeout, transform)
+ finally: c.close()
+
+ def assert_browse_backup(self, queue, expected, **kwargs):
+ """Combines wait_backup and assert_browse_retry."""
+ c = self.connect_admin()
+ try:
+ wait_address(c, queue)
+ assert_browse_retry(c.session(), queue, expected, **kwargs)
+ finally: c.close()
+
+ assert_browse = assert_browse_backup
+
+ def assert_connect_fail(self):
+ try:
+ self.connect()
+ self.test.fail("Expected qm.ConnectionError")
+ except qm.ConnectionError: pass
+
+ def try_connect(self):
+ try: return self.connect()
+ except qm.ConnectionError: return None
+
+ def ready(self, *args, **kwargs):
+ if not 'client_properties' in kwargs: kwargs['client_properties'] = {}
+ kwargs['client_properties']['qpid.ha-admin'] = True
+ return Broker.ready(self, *args, **kwargs)
+
+ def kill(self, final=True):
+ if final: self.ha_port.teardown()
+ self._agent = None
+ return Broker.kill(self)
+
+
+class HaCluster(object):
+ _cluster_count = 0
+
+ def __init__(self, test, n, promote=True, wait=True, args=[], s_args=[], **kwargs):
+ """Start a cluster of n brokers.
+
+ @test: The test being run
+ @n: start n brokers
+ @promote: promote self[0] to primary
+ @wait: wait for primary active and backups ready. Ignored if promote=False
+ @args: args for all brokers in the cluster.
+ @s_args: args for specific brokers: s_args[i] for broker i.
+ """
+ self.test = test
+ self.args = copy(args)
+ self.s_args = copy(s_args)
+ self.kwargs = kwargs
+ self._ports = [HaPort(test) for i in xrange(n)]
+ self._set_url()
+ self._brokers = []
+ self.id = HaCluster._cluster_count
+ self.broker_id = 0
+ HaCluster._cluster_count += 1
+ for i in xrange(n): self.start()
+ if promote:
+ self[0].promote()
+ if wait:
+ self[0].wait_status("active")
+ for b in self[1:]: b.wait_status("ready")
+
+ def next_name(self):
+ name="cluster%s-%s"%(self.id, self.broker_id)
+ self.broker_id += 1
+ return name
+
+ def _ha_broker(self, i, name):
+ args = self.args
+ if i < len(self.s_args): args += self.s_args[i]
+ ha_port = self._ports[i]
+ b = HaBroker(ha_port.test, ha_port, brokers_url=self.url, name=name,
+ args=args, **self.kwargs)
+ b.ready(timeout=10)
+ return b
+
+ def start(self):
+ """Start a new broker in the cluster"""
+ i = len(self)
+ assert i <= len(self._ports)
+ if i == len(self._ports): # Adding new broker after cluster init
+ self._ports.append(HaPort(self.test))
+ self._set_url()
+ b = self._ha_broker(i, self.next_name())
+ self._brokers.append(b)
+ return b
+
+ def _set_url(self):
+ self.url = ",".join("127.0.0.1:%s"%(p.port) for p in self._ports)
+
+ def connect(self, i, **kwargs):
+ """Connect with reconnect_urls"""
+ c = self[i].connect(reconnect=True, reconnect_urls=self.url.split(","), **kwargs)
+ self.test.teardown_add(c) # Clean up
+ return c
+
+ def kill(self, i, promote_next=True, final=True):
+ """Kill broker i, promote broker i+1"""
+ self[i].kill(final=final)
+ if promote_next: self[(i+1) % len(self)].promote()
+
+ def restart(self, i):
+ """Start a broker with the same port, name and data directory. It will get
+ a separate log file: foo.n.log"""
+ if self._ports[i].stopped: raise Exception("Restart after final kill: %s"%(self))
+ b = self._brokers[i]
+ self._brokers[i] = self._ha_broker(i, b.name)
+ self._brokers[i].ready()
+
+ def bounce(self, i, promote_next=True):
+ """Stop and restart a broker in a cluster."""
+ if (len(self) == 1):
+ self.kill(i, promote_next=False, final=False)
+ self.restart(i)
+ self[i].ready()
+ if promote_next: self[i].promote()
+ else:
+ self.kill(i, promote_next, final=False)
+ self.restart(i)
+
+ # Behave like a list of brokers.
+ def __len__(self): return len(self._brokers)
+ def __getitem__(self,index): return self._brokers[index]
+ def __iter__(self): return self._brokers.__iter__()
+
+
+def wait_address(connection, address):
+ """Wait for an address to become valid."""
+ assert retry(lambda: valid_address(connection, address)), "Timed out waiting for address %s"%(address)
+
+def valid_address(connection, address):
+ """Test if an address is valid"""
+ try:
+ s = connection.session().receiver(address)
+ s.session.close()
+ return True
+ except qm.NotFound:
+ return False
+
+
diff --git a/qpid/cpp/src/tests/ha_test_max_queues.cpp b/qpid/cpp/src/tests/ha_test_max_queues.cpp
new file mode 100644
index 0000000000..fcce4c3151
--- /dev/null
+++ b/qpid/cpp/src/tests/ha_test_max_queues.cpp
@@ -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.
+ *
+ */
+
+#include <qpid/client/Connection.h>
+#include <qpid/client/Session.h>
+#include <qpid/client/AsyncSession.h>
+#include <qpid/Url.h>
+#include <qpid/framing/reply_exceptions.h>
+#include <sstream>
+
+using namespace qpid::client;
+using namespace std;
+
+int main(int argc, char** argv) {
+ if (argc != 2) {
+ cerr << "Expecing URL of broker as argument" << endl;
+ exit(1);
+ }
+ try {
+ // We need to create a large number of queues quickly, so we
+ // use the old API for it's asynchronous commands.
+ // The qpid::messaging API does not allow async queue creation.
+ //
+ Connection c;
+ c.open(qpid::Url(argv[1]));
+ AsyncSession s = async(c.newSession());
+ // Generate too many queues, make sure we get an exception.
+ for (uint64_t i = 0; i < 100000; ++i) {
+ ostringstream os;
+ os << "q" << i;
+ string q = os.str();
+ s.queueDeclare(q, arg::sync=false);
+ if (i && i % 1000 == 0) {
+ s.sync(); // Check for exceptions.
+ cout << "Declared " << q << endl;
+ }
+ }
+ cout << "Expected resource-limit-exceeded exception" << endl;
+ return 1;
+ }
+ catch (const qpid::framing::ResourceLimitExceededException& e) {
+ cout << "Resource limit exceeded: " << e.what() << endl;
+ return 0;
+ }
+ catch (const std::exception& e) {
+ cout << "Error: " << e.what() << endl;
+ return 1;
+ }
+}
diff --git a/qpid/cpp/src/tests/ha_tests.py b/qpid/cpp/src/tests/ha_tests.py
new file mode 100755
index 0000000000..2ee2e291e2
--- /dev/null
+++ b/qpid/cpp/src/tests/ha_tests.py
@@ -0,0 +1,1634 @@
+#!/usr/bin/env python
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 os, signal, sys, time, imp, re, subprocess, glob, random, logging, shutil, math, unittest
+import traceback
+from qpid.datatypes import uuid4, UUID
+from brokertest import *
+from ha_test import *
+from threading import Thread, Lock, Condition
+from logging import getLogger, WARN, ERROR, DEBUG, INFO
+from qpidtoollibs import BrokerAgent, EventHelper
+
+log = getLogger(__name__)
+
+class HaBrokerTest(BrokerTest):
+ """Base class for HA broker tests"""
+
+class ReplicationTests(HaBrokerTest):
+ """Correctness tests for HA replication."""
+
+ def test_replication(self):
+ """Test basic replication of configuration and messages before and
+ after backup has connected"""
+
+ def setup(prefix, primary):
+ """Create config, send messages on the primary p"""
+ a = primary.agent
+
+ def queue(name, replicate):
+ a.addQueue(name, options={'qpid.replicate':replicate})
+ return name
+
+ def exchange(name, replicate, bindq, key):
+ a.addExchange("fanout", name, options={'qpid.replicate':replicate})
+ a.bind(name, bindq, key)
+ return name
+
+ # Test replication of messages
+ p = primary.connect().session()
+ s = p.sender(queue(prefix+"q1", "all"))
+ for m in ["a", "b", "1"]: s.send(qm.Message(m))
+ # Test replication of dequeue
+ self.assertEqual(p.receiver(prefix+"q1").fetch(timeout=0).content, "a")
+ p.acknowledge()
+
+ p.sender(queue(prefix+"q2", "configuration")).send(qm.Message("2"))
+ p.sender(queue(prefix+"q3", "none")).send(qm.Message("3"))
+ p.sender(exchange(prefix+"e1", "all", prefix+"q1", "key1")).send(qm.Message("4"))
+ p.sender(exchange(prefix+"e2", "configuration", prefix+"q2", "key2")).send(qm.Message("5"))
+ # Test unbind
+ p.sender(queue(prefix+"q4", "all")).send(qm.Message("6"))
+ s3 = p.sender(exchange(prefix+"e4", "all", prefix+"q4", "key4"))
+ s3.send(qm.Message("7"))
+ a.unbind(prefix+"e4", prefix+"q4", "key4")
+ p.sender(prefix+"e4").send(qm.Message("drop1")) # Should be dropped
+
+ # Test replication of deletes
+ queue(prefix+"dq", "all")
+ exchange(prefix+"de", "all", prefix+"dq", "")
+ a.delQueue(prefix+"dq")
+ a.delExchange(prefix+"de")
+
+ # Need a marker so we can wait till sync is done.
+ queue(prefix+"x", "configuration")
+
+ def verify(b, prefix, p):
+ """Verify setup was replicated to backup b"""
+ # Wait for configuration to replicate.
+ wait_address(b.connection, prefix+"x");
+ self.assert_browse_retry(b, prefix+"q1", ["b", "1", "4"])
+
+ self.assertEqual(p.receiver(prefix+"q1").fetch(timeout=0).content, "b")
+ p.acknowledge()
+ self.assert_browse_retry(b, prefix+"q1", ["1", "4"])
+
+ self.assert_browse_retry(b, prefix+"q2", []) # configuration only
+ assert not valid_address(b.connection, prefix+"q3")
+
+ # Verify exchange with replicate=all
+ b.sender(prefix+"e1/key1").send(qm.Message(prefix+"e1"))
+ self.assert_browse_retry(b, prefix+"q1", ["1", "4", prefix+"e1"])
+
+ # Verify exchange with replicate=configuration
+ b.sender(prefix+"e2/key2").send(qm.Message(prefix+"e2"))
+ self.assert_browse_retry(b, prefix+"q2", [prefix+"e2"])
+
+ b.sender(prefix+"e4/key4").send(qm.Message("drop2")) # Verify unbind.
+ self.assert_browse_retry(b, prefix+"q4", ["6","7"])
+
+ # Verify deletes
+ assert not valid_address(b.connection, prefix+"dq")
+ assert not valid_address(b.connection, prefix+"de")
+
+ l = LogLevel(ERROR) # Hide expected WARNING log messages from failover.
+ try:
+ cluster = HaCluster(self, 2)
+ primary = cluster[0]
+ backup = cluster[1]
+
+ # Send messages before re-starting the backup, test catch-up replication.
+ cluster.kill(1, promote_next=False, final=False)
+ setup("1", primary)
+ cluster.restart(1)
+
+ # Send messages after re-starting the backup, to test steady-state replication.
+ setup("2", primary)
+
+ p = primary.connect().session()
+
+ # Verify the data on the backup
+ b = backup.connect_admin().session()
+ verify(b, "1", p)
+ verify(b, "2", p)
+ # Test a series of messages, enqueue all then dequeue all.
+ primary.agent.addQueue("foo")
+ s = p.sender("foo")
+ wait_address(b.connection, "foo")
+ msgs = [str(i) for i in range(10)]
+ for m in msgs: s.send(qm.Message(m))
+ self.assert_browse_retry(p, "foo", msgs)
+ self.assert_browse_retry(b, "foo", msgs)
+ r = p.receiver("foo")
+ for m in msgs: self.assertEqual(m, r.fetch(timeout=0).content)
+ p.acknowledge()
+ self.assert_browse_retry(p, "foo", [])
+ self.assert_browse_retry(b, "foo", [])
+
+ # Another series, this time verify each dequeue individually.
+ for m in msgs: s.send(qm.Message(m))
+ self.assert_browse_retry(p, "foo", msgs)
+ self.assert_browse_retry(b, "foo", msgs)
+ for i in range(len(msgs)):
+ self.assertEqual(msgs[i], r.fetch(timeout=0).content)
+ p.acknowledge()
+ self.assert_browse_retry(p, "foo", msgs[i+1:])
+ self.assert_browse_retry(b, "foo", msgs[i+1:])
+ finally: l.restore()
+
+ def test_sync(self):
+ primary = HaBroker(self, name="primary")
+ primary.promote()
+ p = primary.connect().session()
+ s = p.sender("q;{create:always}")
+ for m in [str(i) for i in range(0,10)]: s.send(m)
+ s.sync()
+ backup1 = HaBroker(self, name="backup1", brokers_url=primary.host_port())
+ for m in [str(i) for i in range(10,20)]: s.send(m)
+ s.sync()
+ backup2 = HaBroker(self, name="backup2", brokers_url=primary.host_port())
+ for m in [str(i) for i in range(20,30)]: s.send(m)
+ s.sync()
+
+ msgs = [str(i) for i in range(30)]
+ b1 = backup1.connect_admin().session()
+ backup1.assert_browse_backup("q", msgs)
+ backup2.assert_browse_backup("q", msgs)
+
+ def test_send_receive(self):
+ """Verify sequence numbers of messages sent by qpid-send"""
+ l = LogLevel(ERROR) # Hide expected WARNING log messages from failover.
+ try:
+ brokers = HaCluster(self, 3)
+ sender = self.popen(
+ ["qpid-send",
+ "--broker", brokers[0].host_port(),
+ "--address", "q;{create:always}",
+ "--messages=1000",
+ "--content-string=x",
+ "--connection-options={%s}"%self.protocol_option()
+ ])
+ receiver = self.popen(
+ ["qpid-receive",
+ "--broker", brokers[0].host_port(),
+ "--address", "q;{create:always}",
+ "--messages=990",
+ "--timeout=10",
+ "--connection-options={%s}"%self.protocol_option()
+ ])
+ self.assertEqual(sender.wait(), 0)
+ self.assertEqual(receiver.wait(), 0)
+ expect = [long(i) for i in range(991, 1001)]
+ sn = lambda m: m.properties["sn"]
+ brokers[1].assert_browse_backup("q", expect, transform=sn)
+ brokers[2].assert_browse_backup("q", expect, transform=sn)
+ finally: l.restore()
+
+ def test_failover_python(self):
+ """Verify that backups rejects connections and that fail-over works in python client"""
+ l = LogLevel(ERROR) # Hide expected WARNING log messages from failover.
+ try:
+ primary = HaBroker(self, name="primary")
+ primary.promote()
+ backup = HaBroker(self, name="backup", brokers_url=primary.host_port())
+ # Check that backup rejects normal connections
+ try:
+ backup.connect().session()
+ self.fail("Expected connection to backup to fail")
+ except qm.ConnectionError: pass
+ # Check that admin connections are allowed to backup.
+ backup.connect_admin().close()
+
+ # Test discovery: should connect to primary after reject by backup
+ c = backup.connect(reconnect_urls=[primary.host_port(), backup.host_port()],
+ reconnect=True)
+ s = c.session()
+ sender = s.sender("q;{create:always}")
+ sender.send("foo", sync=True)
+ s.sync()
+ primary.kill()
+ assert retry(lambda: not is_running(primary.pid))
+ backup.promote()
+ sender.send("bar")
+ self.assert_browse_retry(s, "q", ["foo", "bar"])
+ c.close()
+ finally: l.restore()
+
+
+ def test_heartbeat_python(self):
+ """Verify that a python client with a heartbeat specified disconnects
+ from a stalled broker and does not hang indefinitely."""
+
+ broker = Broker(self)
+ broker_addr = broker.host_port()
+
+ # Case 1: Connect before stalling the broker, use the connection after stalling.
+ c = qm.Connection(broker_addr, heartbeat=1)
+ c.open()
+ os.kill(broker.pid, signal.SIGSTOP) # Stall the broker
+
+ def make_sender(): c.session().sender("foo")
+ self.assertRaises(qm.ConnectionError, make_sender)
+
+ # Case 2: Connect to a stalled broker
+ c = qm.Connection(broker_addr, heartbeat=1)
+ self.assertRaises(qm.ConnectionError, c.open)
+
+ # Case 3: Re-connect to a stalled broker.
+ broker2 = Broker(self)
+ c = qm.Connection(broker2.host_port(), heartbeat=1, reconnect_limit=1,
+ reconnect=True, reconnect_urls=[broker_addr],
+ reconnect_log=False) # Hide expected warnings
+ c.open()
+ broker2.kill() # Cause re-connection to broker
+ self.assertRaises(qm.ConnectionError, make_sender)
+
+ def test_failover_cpp(self):
+ """Verify that failover works in the C++ client."""
+ cluster = HaCluster(self, 2)
+ cluster[0].connect().session().sender("q;{create:always}")
+ cluster[1].wait_backup("q")
+ # FIXME aconway 2014-02-21: using 0-10, there is a failover problem with 1.0
+ sender = NumberedSender(cluster[0], url=cluster.url, queue="q",
+ connection_options="reconnect:true,protocol:'amqp0-10'")
+ receiver = NumberedReceiver(cluster[0], url=cluster.url, queue="q",
+ connection_options="reconnect:true,protocol:'amqp0-10'")
+ receiver.start()
+ sender.start()
+ assert retry(lambda: receiver.received > 10) # Wait for some messages to get thru
+ cluster.kill(0)
+ n = receiver.received
+ assert retry(lambda: receiver.received > n + 10) # Verify we are still going
+ sender.stop()
+ receiver.stop()
+
+ def test_backup_failover(self):
+ """Verify that a backup broker fails over and recovers queue state"""
+ brokers = HaCluster(self, 3)
+ brokers[0].connect().session().sender("q;{create:always}").send("a")
+ brokers.kill(0)
+ brokers[1].connect().session().sender("q").send("b")
+ brokers[2].assert_browse_backup("q", ["a","b"])
+ s = brokers[1].connect().session()
+ self.assertEqual("a", s.receiver("q").fetch().content)
+ s.acknowledge()
+ brokers[2].assert_browse_backup("q", ["b"])
+
+ def test_empty_backup_failover(self):
+ """Verify that a new primary becomes active with no queues.
+ Regression test for QPID-5430"""
+ brokers = HaCluster(self, 3)
+ brokers.kill(0)
+ brokers[1].wait_status("active")
+
+ def test_qpid_config_replication(self):
+ """Set up replication via qpid-config"""
+ brokers = HaCluster(self,2)
+ brokers[0].config_declare("q","all")
+ brokers[0].connect().session().sender("q").send("foo")
+ brokers[1].assert_browse_backup("q", ["foo"])
+
+ def test_standalone_queue_replica(self):
+ """Test replication of individual queues outside of cluster mode"""
+ primary = HaBroker(self, name="primary", ha_cluster=False,
+ args=["--ha-queue-replication=yes"]);
+ pc = primary.connect()
+ ps = pc.session().sender("q;{create:always}")
+ pr = pc.session().receiver("q;{create:always}")
+ backup = HaBroker(self, name="backup", ha_cluster=False,
+ args=["--ha-queue-replication=yes"])
+ bs = backup.connect().session()
+ br = bs.receiver("q;{create:always}")
+
+ def srange(*args): return [str(i) for i in xrange(*args)]
+
+ for m in srange(3): ps.send(m)
+ # Set up replication with qpid-ha
+ backup.replicate(primary.host_port(), "q")
+ backup.assert_browse_backup("q", srange(3))
+ for m in srange(3,6): ps.send(str(m))
+ backup.assert_browse_backup("q", srange(6))
+ self.assertEqual("0", pr.fetch().content)
+ pr.session.acknowledge()
+ backup.assert_browse_backup("q", srange(1,6))
+
+ # Set up replication with qpid-config
+ ps2 = pc.session().sender("q2;{create:always}")
+ backup.config_replicate(primary.host_port(), "q2");
+ ps2.send("x")
+ backup.assert_browse_backup("q2", ["x"])
+
+
+ def test_standalone_queue_replica_failover(self):
+ """Test individual queue replication from a cluster to a standalone
+ backup broker, verify it fails over."""
+ l = LogLevel(ERROR) # Hide expected WARNING log messages from failover.
+ try:
+ cluster = HaCluster(self, 2)
+ primary = cluster[0]
+ pc = cluster.connect(0)
+ ps = pc.session().sender("q;{create:always}")
+ pr = pc.session().receiver("q;{create:always}")
+ backup = HaBroker(self, name="backup", ha_cluster=False,
+ args=["--ha-queue-replication=yes"])
+ br = backup.connect().session().receiver("q;{create:always}")
+ backup.replicate(cluster.url, "q")
+ ps.send("a")
+ ps.sync()
+ backup.assert_browse_backup("q", ["a"])
+ cluster.bounce(0)
+ backup.assert_browse_backup("q", ["a"])
+ ps.send("b")
+ backup.assert_browse_backup("q", ["a", "b"])
+ cluster[0].wait_status("ready")
+ cluster.bounce(1)
+ # FIXME aconway 2014-02-20: pr does not fail over with 1.0/swig
+ if qm == qpid_messaging:
+ print "WARNING: Skipping SWIG client failover bug"
+ return
+ self.assertEqual("a", pr.fetch().content)
+ pr.session.acknowledge()
+ backup.assert_browse_backup("q", ["b"])
+ pc.close()
+ br.close()
+ finally: l.restore()
+
+ def test_lvq(self):
+ """Verify that we replicate to an LVQ correctly"""
+ cluster = HaCluster(self, 2)
+ s = cluster[0].connect().session().sender("lvq; {create:always, node:{x-declare:{arguments:{'qpid.last_value_queue_key':lvq-key}}}}")
+
+ def send(key,value,expect):
+ s.send(qm.Message(content=value,properties={"lvq-key":key}))
+ cluster[1].assert_browse_backup("lvq", expect)
+
+ send("a", "a-1", ["a-1"])
+ send("b", "b-1", ["a-1", "b-1"])
+ send("a", "a-2", ["b-1", "a-2"])
+ send("a", "a-3", ["b-1", "a-3"])
+ send("c", "c-1", ["b-1", "a-3", "c-1"])
+ send("c", "c-2", ["b-1", "a-3", "c-2"])
+ send("b", "b-2", ["a-3", "c-2", "b-2"])
+ send("c", "c-3", ["a-3", "b-2", "c-3"])
+ send("d", "d-1", ["a-3", "b-2", "c-3", "d-1"])
+
+ def test_ring(self):
+ """Test replication with the ring queue policy"""
+ """Verify that we replicate to an LVQ correctly"""
+ cluster = HaCluster(self, 2)
+ s = cluster[0].connect().session().sender("q; {create:always, node:{x-declare:{arguments:{'qpid.policy_type':ring, 'qpid.max_count':5}}}}")
+ for i in range(10): s.send(qm.Message(str(i)))
+ cluster[1].assert_browse_backup("q", [str(i) for i in range(5,10)])
+
+ def test_reject(self):
+ """Test replication with the reject queue policy"""
+ cluster = HaCluster(self, 2)
+ primary, backup = cluster
+ s = primary.connect().session().sender("q; {create:always, node:{x-declare:{arguments:{'qpid.policy_type':reject, 'qpid.max_count':5}}}}")
+ try:
+ for i in range(10): s.send(qm.Message(str(i)), sync=False)
+ except qm.LinkError: pass
+ backup.assert_browse_backup("q", [str(i) for i in range(0,5)])
+ try: s.session.connection.close()
+ except: pass # Expect exception from broken session
+
+ def test_priority(self):
+ """Verify priority queues replicate correctly"""
+ cluster = HaCluster(self, 2)
+ session = cluster[0].connect().session()
+ s = session.sender("priority-queue; {create:always, node:{x-declare:{arguments:{'qpid.priorities':10}}}}")
+ priorities = [8,9,5,1,2,2,3,4,9,7,8,9,9,2]
+ for p in priorities: s.send(qm.Message(priority=p))
+ # Can't use browse_backup as browser sees messages in delivery order not priority.
+ cluster[1].wait_backup("priority-queue")
+ r = cluster[1].connect_admin().session().receiver("priority-queue")
+ received = [r.fetch().priority for i in priorities]
+ self.assertEqual(sorted(priorities, reverse=True), received)
+
+ def test_priority_fairshare(self):
+ """Verify priority queues replicate correctly"""
+ cluster = HaCluster(self, 2)
+ primary, backup = cluster
+ session = primary.connect().session()
+ levels = 8
+ priorities = [4,5,3,7,8,8,2,8,2,8,8,16,6,6,6,6,6,6,8,3,5,8,3,5,5,3,3,8,8,3,7,3,7,7,7,8,8,8,2,3]
+ limits={7:0,6:4,5:3,4:2,3:2,2:2,1:2}
+ limit_policy = ",".join(["'qpid.fairshare':5"] + ["'qpid.fairshare-%s':%s"%(i[0],i[1]) for i in limits.iteritems()])
+ s = session.sender("priority-queue; {create:always, node:{x-declare:{arguments:{'qpid.priorities':%s, %s}}}}"%(levels,limit_policy))
+ messages = [qm.Message(content=str(uuid4()), priority = p) for p in priorities]
+ for m in messages: s.send(m)
+ backup.wait_backup(s.target)
+ r = backup.connect_admin().session().receiver("priority-queue")
+ received = [r.fetch().content for i in priorities]
+ sort = sorted(messages, key=lambda m: priority_level(m.priority, levels), reverse=True)
+ fair = [m.content for m in fairshare(sort, lambda l: limits.get(l,0), levels)]
+ self.assertEqual(received, fair)
+
+ def test_priority_ring(self):
+ cluster = HaCluster(self, 2)
+ primary, backup = cluster
+ s = primary.connect().session().sender("q; {create:always, node:{x-declare:{arguments:{'qpid.policy_type':ring, 'qpid.max_count':5, 'qpid.priorities':10}}}}")
+ priorities = [8,9,5,1,2,2,3,4,9,7,8,9,9,2]
+ for p in priorities: s.send(qm.Message(priority=p))
+ expect = sorted(priorities,reverse=True)[0:5]
+ primary.assert_browse("q", expect, transform=lambda m: m.priority)
+ backup.assert_browse_backup("q", expect, transform=lambda m: m.priority)
+
+ def test_backup_acquired(self):
+ """Verify that acquired messages are backed up, for all queue types."""
+ class Test:
+ def __init__(self, queue, arguments, expect):
+ self.queue = queue
+ self.address = "%s;{create:always,node:{x-declare:{arguments:{%s}}}}"%(
+ self.queue, ",".join(arguments))
+ self.expect = [str(i) for i in expect]
+
+ def send(self, connection):
+ """Send messages, then acquire one but don't acknowledge"""
+ s = connection.session()
+ for m in range(10): s.sender(self.address).send(str(m))
+ s.receiver(self.address).fetch()
+
+ def verify(self, brokertest, backup):
+ backup.assert_browse_backup(self.queue, self.expect, msg=self.queue)
+
+ tests = [
+ Test("plain",[],range(10)),
+ Test("ring", ["'qpid.policy_type':ring", "'qpid.max_count':5"], range(5,10)),
+ Test("priority",["'qpid.priorities':10"], range(10)),
+ Test("fairshare", ["'qpid.priorities':10,'qpid.fairshare':5"], range(10)),
+ Test("lvq", ["'qpid.last_value_queue_key':lvq-key"], [9])
+ ]
+
+ cluster = HaCluster(self, 3)
+ cluster.kill(2, final=False) # restart after messages are sent to test catch-up
+
+ c = cluster[0].connect()
+ for t in tests: t.send(c) # Send messages, leave one unacknowledged.
+
+ cluster.restart(2)
+ cluster[2].wait_status("ready")
+
+ # Verify acquired message was replicated
+ for t in tests: t.verify(self, cluster[1])
+ for t in tests: t.verify(self, cluster[2])
+
+ def test_replicate_default(self):
+ """Make sure we don't replicate if ha-replicate is unspecified or none"""
+ cluster1 = HaCluster(self, 2, ha_replicate=None)
+ cluster1[1].wait_status("ready")
+ c1 = cluster1[0].connect().session().sender("q;{create:always}")
+ cluster2 = HaCluster(self, 2, ha_replicate="none")
+ cluster2[1].wait_status("ready")
+ cluster2[0].connect().session().sender("q;{create:always}")
+ time.sleep(.1) # Give replication a chance.
+ # Expect queues not to be found
+ self.assertRaises(qm.NotFound, cluster1[1].connect_admin().session().receiver, "q")
+ self.assertRaises(qm.NotFound, cluster2[1].connect_admin().session().receiver, "q")
+
+ def test_replicate_binding(self):
+ """Verify that binding replication can be disabled"""
+ cluster = HaCluster(self, 2)
+ primary, backup = cluster[0], cluster[1]
+ ps = primary.connect().session()
+ a = primary.agent
+ a.addExchange("fanout", "ex")
+ a.addQueue("q")
+ a.bind("ex", "q", options={'qpid.replicate':'none'})
+ backup.wait_backup("q")
+
+ primary.kill()
+ assert retry(lambda: not is_running(primary.pid)) # Wait for primary to die
+ backup.promote()
+ bs = backup.connect_admin().session()
+ bs.sender("ex").send(qm.Message("msg"))
+ self.assert_browse_retry(bs, "q", [])
+
+ def test_invalid_replication(self):
+ """Verify that we reject an attempt to declare a queue with invalid replication value."""
+ cluster = HaCluster(self, 1, ha_replicate="all")
+ self.assertRaises(Exception, cluster[0].connect().session().sender,
+ "q;{create:always, node:{x-declare:{arguments:{'qpid.replicate':XXinvalidXX}}}}")
+
+ def test_exclusive_queue(self):
+ """Ensure that we can back-up exclusive queues, i.e. the replicating
+ subscriptions are exempt from the exclusivity"""
+ cluster = HaCluster(self, 2)
+ def test(addr):
+ c = cluster[0].connect()
+ q = addr.split(";")[0]
+ r = c.session().receiver(addr)
+ self.assertRaises(qm.LinkError, c.session().receiver, addr)
+ s = c.session().sender(q).send(q)
+ cluster[1].assert_browse_backup(q, [q])
+ test("excl_queue;{create:always, node:{x-declare:{exclusive:True}}}")
+ if qm == qpid.messaging: # FIXME aconway 2014-02-20: swig client no exclusive subscribe
+ test("excl_sub;{create:always, link:{x-subscribe:{exclusive:True}}}");
+
+ def test_auto_delete_exclusive(self):
+ """Verify that we ignore auto-delete, exclusive, non-auto-delete-timeout queues"""
+ cluster = HaCluster(self, 2)
+ s0 = cluster[0].connect().session()
+ s0.receiver("exad;{create:always,node:{x-declare:{exclusive:True,auto-delete:True}}}")
+ s0.receiver("ex;{create:always,node:{x-declare:{exclusive:True}}}")
+ ad = s0.receiver("ad;{create:always,node:{x-declare:{auto-delete:True}}}")
+ s0.receiver("time;{create:always,node:{x-declare:{exclusive:True,auto-delete:True,arguments:{'qpid.auto_delete_timeout':1}}}}")
+ s0.receiver("q;{create:always}")
+
+ s1 = cluster[1].connect_admin().session()
+ cluster[1].wait_backup("q")
+ assert not valid_address(s1.connection, "exad")
+ assert valid_address(s1.connection, "ex")
+ assert valid_address(s1.connection, "ad")
+ assert valid_address(s1.connection, "time")
+
+ # Verify that auto-delete queues are not kept alive by
+ # replicating subscriptions
+ ad.close()
+ s0.sync()
+ assert not valid_address(s0.connection, "ad")
+
+ def test_broker_info(self):
+ """Check that broker information is correctly published via management"""
+ cluster = HaCluster(self, 3)
+
+ def ha_broker(broker):
+ ha_broker = broker.agent.getHaBroker();
+ ha_broker.update()
+ return ha_broker
+
+ for broker in cluster: # Make sure HA system-id matches broker's
+ self.assertEqual(ha_broker(broker).systemId, UUID(broker.agent.getBroker().systemRef))
+
+ # Check that all brokers have the same membership as the cluster
+ def check_ids(broker):
+ cluster_ids = set([ ha_broker(b).systemId for b in cluster])
+ broker_ids = set([m["system-id"] for m in ha_broker(broker).members])
+ assert retry(lambda: cluster_ids == broker_ids, 1), "%s != %s on %s"%(cluster_ids, broker_ids, broker)
+
+ for broker in cluster: check_ids(broker)
+
+ # Add a new broker, check it is updated everywhere
+ b = cluster.start()
+ for broker in cluster: check_ids(broker)
+
+ def test_auth(self):
+ """Verify that authentication does not interfere with replication."""
+ # TODO aconway 2012-07-09: generate test sasl config portably for cmake
+ sasl_config=os.path.join(self.rootdir, "sasl_config")
+ if not os.path.exists(sasl_config):
+ print "WARNING: Skipping test, SASL test configuration %s not found."%sasl_config
+ return
+ acl=os.path.join(os.getcwd(), "policy.acl")
+ aclf=file(acl,"w")
+ # Minimum set of privileges required for the HA user.
+ aclf.write("""
+# HA user
+acl allow zag@QPID access queue
+acl allow zag@QPID create queue
+acl allow zag@QPID consume queue
+acl allow zag@QPID delete queue
+acl allow zag@QPID access exchange
+acl allow zag@QPID create exchange
+acl allow zag@QPID bind exchange
+acl allow zag@QPID publish exchange
+acl allow zag@QPID delete exchange
+acl allow zag@QPID access method
+acl allow zag@QPID create link
+acl allow zag@QPID access query
+# Normal user
+acl allow zig@QPID all all
+acl deny all all
+ """)
+ aclf.close()
+ cluster = HaCluster(
+ self, 2,
+ args=["--auth", "yes", "--sasl-config", sasl_config,
+ "--acl-file", acl,
+ "--ha-username=zag", "--ha-password=zag", "--ha-mechanism=PLAIN"
+ ],
+ client_credentials=Credentials("zag", "zag", "PLAIN"))
+ c = cluster[0].connect(username="zig", password="zig")
+ s0 = c.session();
+ a = cluster[0].agent
+ a.addQueue("q")
+ a.addExchange("fanout", "ex")
+ a.bind("ex", "q", "")
+ s0.sender("ex").send("foo");
+
+ # Transactions should be done over the tx_protocol
+ c = cluster[0].connect(protocol=self.tx_protocol, username="zig", password="zig")
+ s1 = c.session(transactional=True)
+ s1.sender("ex").send("foo-tx");
+ cluster[1].assert_browse_backup("q", ["foo"])
+ s1.commit()
+ cluster[1].assert_browse_backup("q", ["foo", "foo-tx"])
+
+ def test_alternate_exchange(self):
+ """Verify that alternate-exchange on exchanges and queues is propagated
+ to new members of a cluster. """
+ cluster = HaCluster(self, 2)
+ s = cluster[0].connect().session()
+ # altex exchange: acts as alternate exchange
+ a = cluster[0].agent
+ a.addExchange("fanout", "altex")
+ # altq queue bound to altex, collect re-routed messages.
+ a.addQueue("altq")
+ a.bind("altex", "altq", "")
+ # ex exchange with alternate-exchange altex and no queues bound
+ a.addExchange("direct", "ex", {"alternate-exchange":"altex"})
+ # create queue q with alternate-exchange altex
+ a.addQueue("q", {"alternate-exchange":"altex"})
+ # create a bunch of exchanges to ensure we don't clean up prematurely if the
+ # response comes in multiple fragments.
+ for i in xrange(200): s.sender("ex.%s;{create:always,node:{type:topic}}"%i)
+
+ def verify(broker):
+ c = broker.connect()
+ s = c.session()
+ # Verify unmatched message goes to ex's alternate.
+ s.sender("ex").send("foo")
+ altq = s.receiver("altq")
+ self.assertEqual("foo", altq.fetch(timeout=0).content)
+ s.acknowledge()
+ # Verify rejected message goes to q's alternate.
+ s.sender("q").send("bar")
+ msg = s.receiver("q").fetch(timeout=0)
+ self.assertEqual("bar", msg.content)
+ s.acknowledge(msg, qm.Disposition(qm.REJECTED)) # Reject the message
+ self.assertEqual("bar", altq.fetch(timeout=0).content)
+ s.acknowledge()
+ s.sync() # Make sure backups are caught-up.
+ c.close()
+
+ # Sanity check: alternate exchanges on original broker
+ verify(cluster[0])
+ a = cluster[0].agent
+ # Altex is in use as an alternate exchange, we should get an exception
+ self.assertRaises(Exception, a.delExchange, "altex")
+ # Check backup that was connected during setup.
+ def wait(broker):
+ broker.wait_status("ready")
+ for a in ["q", "ex", "altq", "altex"]:
+ broker.wait_backup(a)
+ wait(cluster[1])
+ cluster.bounce(0)
+ verify(cluster[1])
+
+ # Check a newly started backup.
+ cluster.start()
+ wait(cluster[2])
+ cluster.bounce(1)
+ verify(cluster[2])
+
+ # Check that alt-exchange in-use count is replicated
+ a = cluster[2].agent
+ self.assertRaises(Exception, a.delExchange, "altex")
+ a.delQueue("q")
+ self.assertRaises(Exception, a.delExchange, "altex")
+ a.delExchange("ex")
+ a.delExchange("altex")
+
+ def test_priority_reroute(self):
+ """Regression test for QPID-4262, rerouting messages from a priority queue
+ to itself causes a crash"""
+ cluster = HaCluster(self, 2)
+ primary = cluster[0]
+ session = primary.connect().session()
+ a = primary.agent
+ a.addQueue("pq", {'qpid.priorities':10})
+ a.bind("amq.fanout", "pq")
+ s = session.sender("pq")
+ for m in xrange(100): s.send(qm.Message(str(m), priority=m%10))
+ pq = QmfAgent(primary.host_port()).getQueue("pq")
+ pq.reroute(request=0, useAltExchange=False, exchange="amq.fanout")
+ # Verify that consuming is in priority order
+ expect = [str(10*i+p) for p in xrange(9,-1,-1) for i in xrange(0,10) ]
+ actual = [m.content for m in primary.get_messages("pq", 100)]
+ self.assertEqual(expect, actual)
+
+ def test_delete_missing_response(self):
+ """Check that a backup correctly deletes leftover queues and exchanges that are
+ missing from the initial reponse set."""
+ # This test is a bit contrived, we set up the situation on backup brokers
+ # and then promote one.
+ cluster = HaCluster(self, 2, promote=False)
+
+ # cluster[0] Will be the primary
+ s = cluster[0].connect_admin().session()
+ s.sender("q1;{create:always}")
+ s.sender("e1;{create:always, node:{type:topic}}")
+
+ # cluster[1] will be the backup, has extra queues/exchanges
+ xdecl = "x-declare:{arguments:{'qpid.replicate':'all'}}"
+ node = "node:{%s}"%(xdecl)
+ s = cluster[1].connect_admin().session()
+ s.sender("q1;{create:always, %s}"%(node))
+ s.sender("q2;{create:always, %s}"%(node))
+ s.sender("e1;{create:always, node:{type:topic, %s}}"%(xdecl))
+ s.sender("e2;{create:always, node:{type:topic, %s}}"%(xdecl))
+ for a in ["q1", "q2", "e1", "e2"]: cluster[1].wait_backup(a)
+
+ cluster[0].promote()
+ # Verify the backup deletes the surplus queue and exchange
+ cluster[1].wait_status("ready")
+ s = cluster[1].connect_admin().session()
+ self.assertRaises(qm.NotFound, s.receiver, ("q2"));
+ self.assertRaises(qm.NotFound, s.receiver, ("e2"));
+
+
+ def test_delete_qpid_4285(self):
+ """Regression test for QPID-4285: on deleting a queue it gets stuck in a
+ partially deleted state and causes replication errors."""
+ cluster = HaCluster(self,2)
+ s = cluster[0].connect().session()
+ s.receiver("q;{create:always}")
+ cluster[1].wait_backup("q")
+ cluster.kill(0) # Make the backup take over.
+ s = cluster[1].connect().session()
+ cluster[1].agent.delQueue("q") # Delete q on new primary
+ self.assertRaises(qm.NotFound, s.receiver, "q")
+ assert not cluster[1].agent.getQueue("q") # Should not be in QMF
+
+ def test_auto_delete_failover(self):
+ """Test auto-delete queues. Verify that:
+ - queues auto-deleted on the primary are deleted on the backup.
+ - auto-delete queues with/without timeout are deleted after a failover correctly
+ - auto-delete queues never used (subscribe to) to are not deleted
+ - messages are correctly routed to the alternate exchange.
+ """
+ cluster = HaCluster(self, 3)
+ s = cluster[0].connect().session()
+ a = cluster[0].agent
+
+ def setup(q, timeout=None):
+ # Create alternate exchange, auto-delete queue and queue bound to alt. ex.
+ a.addExchange("fanout", q+"-altex")
+ args = {"auto-delete":True, "alternate-exchange":q+"-altex"}
+ if timeout is not None: args['qpid.auto_delete_timeout'] = timeout
+ a.addQueue(q, args)
+ a.addQueue(q+"-altq")
+ a.bind("%s-altex"%q, "%s-altq"%q)
+
+ for args in [["q1"],["q2",0],["q3",1],["q4"],["q5"]]: setup(*args)
+ receivers = []
+ for i in xrange(1,5): # Don't use q5
+ q = "q%s"%i
+ receivers.append(s.receiver(q)) # Subscribe
+ qs = s.sender(q); qs.send(q); qs.close() # Send q name as message
+
+ receivers[3].close() # Trigger auto-delete for q4
+ for b in cluster[1:3]: b.wait_no_queue("q4") # Verify deleted on backups
+
+ cluster[0].kill(final=False) # Kill primary
+ cluster[2].promote()
+ cluster.restart(0)
+ cluster[2].wait_queue("q3") # Not yet auto-deleted, 1 sec timeout.
+ for b in cluster:
+ for q in ["q%s"%i for i in xrange(1,5)]:
+ b.wait_no_queue(q,timeout=2, msg=str(b)) # auto-deleted
+ b.assert_browse_backup("%s-altq"%q, [q]) # Routed to alternate
+ cluster[2].wait_queue("q5") # Not auto-deleted, never subscribed
+ cluster[2].connect().session().receiver("q5").close()
+ cluster[2].wait_no_queue("q5")
+
+ def test_auto_delete_close(self):
+ """Verify auto-delete queues are deleted on backup if auto-deleted
+ on primary"""
+ cluster=HaCluster(self, 2)
+
+ # Create altex to use as alternate exchange, with altq bound to it
+ a = cluster[0].agent
+ a.addExchange("fanout", "altex")
+ a.addQueue("altq", {"auto-delete":True})
+ a.bind("altex", "altq")
+
+ p = cluster[0].connect().session()
+ r = p.receiver("adq1;{create:always,node:{x-declare:{auto-delete:True,alternate-exchange:'altex'}}}")
+ s = p.sender("adq1")
+ for m in ["aa","bb","cc"]: s.send(m)
+ s.close()
+ cluster[1].wait_queue("adq1")
+ r.close() # trigger auto-delete of adq1
+ cluster[1].wait_no_queue("adq1")
+ cluster[1].assert_browse_backup("altq", ["aa","bb","cc"])
+
+ def test_expired(self):
+ """Regression test for QPID-4379: HA does not properly handle expired messages"""
+ # Race between messages expiring and HA replicating consumer.
+ cluster = HaCluster(self, 2)
+ s = cluster[0].connect().session().sender("q;{create:always}", capacity=2)
+ def send_ttl_messages():
+ for i in xrange(100): s.send(qm.Message(str(i), ttl=0.001))
+ send_ttl_messages()
+ cluster.start()
+ send_ttl_messages()
+
+ def test_missed_recreate(self):
+ """If a queue or exchange is destroyed and one with the same name re-created
+ while a backup is disconnected, the backup should also delete/recreate
+ the object when it re-connects"""
+ cluster = HaCluster(self, 3)
+ sn = cluster[0].connect().session()
+ # Create a queue with messages
+ s = sn.sender("qq;{create:always}")
+ msgs = [str(i) for i in xrange(3)]
+ for m in msgs: s.send(m)
+ cluster[1].assert_browse_backup("qq", msgs)
+ cluster[2].assert_browse_backup("qq", msgs)
+ # Set up an exchange with a binding.
+ a = cluster[0].agent
+ a.addExchange("fanout", "xx")
+ a.addQueue("xxq")
+ a.bind("xx", "xxq", "xxq")
+ cluster[1].wait_address("xx")
+ self.assertEqual(cluster[1].agent.getExchange("xx").values["bindingCount"], 1)
+ cluster[2].wait_address("xx")
+ self.assertEqual(cluster[2].agent.getExchange("xx").values["bindingCount"], 1)
+
+ # Simulate the race by re-creating the objects before promoting the new primary
+ cluster.kill(0, promote_next=False)
+ xdecl = "x-declare:{arguments:{'qpid.replicate':'all'}}"
+ node = "node:{%s}"%(xdecl)
+ sn = cluster[1].connect_admin().session()
+ a = cluster[1].agent
+ a.delQueue("qq", if_empty=False)
+ s = sn.sender("qq;{create:always, %s}"%(node))
+ s.send("foo")
+ a.delExchange("xx")
+ sn.sender("xx;{create:always,node:{type:topic,%s}}"%(xdecl))
+ cluster[1].promote()
+ cluster[1].wait_status("active")
+ # Verify we are not still using the old objects on cluster[2]
+ cluster[2].assert_browse_backup("qq", ["foo"])
+ cluster[2].wait_address("xx")
+ self.assertEqual(cluster[2].agent.getExchange("xx").values["bindingCount"], 0)
+
+ def test_resource_limit_bug(self):
+ """QPID-5666 Regression test: Incorrect resource limit exception for queue creation."""
+ cluster = HaCluster(self, 3)
+ qs = ["q%s"%i for i in xrange(10)]
+ a = cluster[0].agent
+ a.addQueue("q")
+ cluster[1].wait_backup("q")
+ cluster.kill(0)
+ cluster[1].promote()
+ cluster[1].wait_status("active")
+ a = cluster[1].agent
+ a.delQueue("q")
+ a.addQueue("q")
+
+def fairshare(msgs, limit, levels):
+ """
+ Generator to return prioritised messages in expected order for a given fairshare limit
+ """
+ count = 0
+ last_priority = None
+ postponed = []
+ while msgs or postponed:
+ if not msgs:
+ msgs = postponed
+ count = 0
+ last_priority = None
+ postponed = [ ]
+ msg = msgs.pop(0)
+ if last_priority and priority_level(msg.priority, levels) == last_priority:
+ count += 1
+ else:
+ last_priority = priority_level(msg.priority, levels)
+ count = 1
+ l = limit(last_priority)
+ if (l and count > l):
+ postponed.append(msg)
+ else:
+ yield msg
+ return
+
+def priority_level(value, levels):
+ """
+ Method to determine which of a distinct number of priority levels
+ a given value falls into.
+ """
+ offset = 5-math.ceil(levels/2.0)
+ return min(max(value - offset, 0), levels-1)
+
+class LongTests(HaBrokerTest):
+ """Tests that can run for a long time if -DDURATION=<minutes> is set"""
+
+ def duration(self):
+ d = self.config.defines.get("DURATION")
+ if d: return float(d)*60
+ else: return 3 # Default is to be quick
+
+ def test_failover_send_receive(self):
+ """Test failover with continuous send-receive"""
+ brokers = HaCluster(self, 3)
+
+ # Start sender and receiver threads
+ n = 10
+ senders = [
+ NumberedSender(
+ brokers[0], url=brokers.url,max_depth=50,
+ queue="test%s"%(i), args=["--capacity=10"]) for i in xrange(n)]
+
+ receivers = [
+ NumberedReceiver(
+ brokers[0], url=brokers.url, sender=senders[i],
+ queue="test%s"%(i), args=["--capacity=10"]) for i in xrange(n)]
+
+ for r in receivers: r.start()
+ for s in senders: s.start()
+
+ def wait_passed(r, n):
+ """Wait for receiver r to pass n"""
+ def check():
+ r.check() # Verify no exceptions
+ return r.received > n + 100
+ assert retry(check), "Stalled %s waiting for %s, sent %s"%(
+ r.queue, n, [s for s in senders if s.queue==r.queue][0].sent)
+
+ for r in receivers: wait_passed(r, 0)
+
+ # Kill and restart brokers in a cycle:
+ endtime = time.time() + self.duration()
+ i = 0
+ primary = 0
+ try:
+ try:
+ while time.time() < endtime or i < 3: # At least 3 iterations
+ # Precondition: All 3 brokers running,
+ # primary = index of promoted primary
+ # one or two backups are running,
+ for s in senders: s.sender.assert_running()
+ for r in receivers: r.receiver.assert_running()
+ checkpoint = [ r.received+10 for r in receivers ]
+ victim = random.choice([0,1,2,primary]) # Give the primary a better chance.
+ if victim == primary:
+ # Don't kill primary till it is active and the next
+ # backup is ready, otherwise we can lose messages.
+ brokers[victim].wait_status("active")
+ next = (victim+1)%3
+ brokers[next].wait_status("ready")
+ brokers.bounce(victim) # Next one is promoted
+ primary = next
+ else:
+ brokers.bounce(victim, promote_next=False)
+
+ # Make sure we are not stalled
+ map(wait_passed, receivers, checkpoint)
+ # Run another checkpoint to ensure things work in this configuration
+ checkpoint = [ r.received+10 for r in receivers ]
+ map(wait_passed, receivers, checkpoint)
+ i += 1
+ except:
+ traceback.print_exc()
+ raise
+ finally:
+ for s in senders: s.stop()
+ for r in receivers: r.stop()
+ dead = filter(lambda b: not b.is_running(), brokers)
+ if dead: raise Exception("Brokers not running: %s"%dead)
+
+ def test_tx_send_receive(self):
+ brokers = HaCluster(self, 3)
+ sender = self.popen(
+ ["qpid-send",
+ "--broker", brokers[0].host_port(),
+ "--address", "q;{create:always}",
+ "--messages=1000",
+ "--tx=10",
+ "--connection-options={protocol:%s}" % self.tx_protocol
+ ])
+ receiver = self.popen(
+ ["qpid-receive",
+ "--broker", brokers[0].host_port(),
+ "--address", "q;{create:always}",
+ "--messages=990",
+ "--timeout=10",
+ "--tx=10",
+ "--connection-options={protocol:%s}" % self.tx_protocol
+ ])
+ self.assertEqual(sender.wait(), 0)
+ self.assertEqual(receiver.wait(), 0)
+ expect = [long(i) for i in range(991, 1001)]
+ sn = lambda m: m.properties["sn"]
+ brokers[0].assert_browse("q", expect, transform=sn)
+ brokers[1].assert_browse_backup("q", expect, transform=sn)
+ brokers[2].assert_browse_backup("q", expect, transform=sn)
+
+
+ def test_qmf_order(self):
+ """QPID 4402: HA QMF events can be out of order.
+ This test mimics the test described in the JIRA. Two threads repeatedly
+ declare the same auto-delete queue and close their connection.
+ """
+ broker = Broker(self)
+ class Receiver(Thread):
+ def __init__(self, qname):
+ Thread.__init__(self)
+ self.qname = qname
+ self.stopped = False
+
+ def run(self):
+ while not self.stopped:
+ self.connection = broker.connect()
+ try:
+ self.connection.session().receiver(
+ self.qname+";{create:always,node:{x-declare:{auto-delete:True}}}")
+ except qm.NotFound: pass # Can occur occasionally, not an error.
+ try: self.connection.close()
+ except: pass
+
+ class QmfObject(object):
+ """Track existance of an object and validate QMF events"""
+ def __init__(self, type_name, name_field, name):
+ self.type_name, self.name_field, self.name = type_name, name_field, name
+ self.exists = False
+
+ def qmf_event(self, event):
+ content = event.content[0]
+ event_type = content['_schema_id']['_class_name']
+ values = content['_values']
+ if event_type == self.type_name+"Declare" and values[self.name_field] == self.name:
+ disp = values['disp']
+ log.debug("Event %s: disp=%s exists=%s"%(
+ event_type, values['disp'], self.exists))
+ if self.exists: assert values['disp'] == 'existing'
+ else: assert values['disp'] == 'created'
+ self.exists = True
+ elif event_type == self.type_name+"Delete" and values[self.name_field] == self.name:
+ log.debug("Event %s: exists=%s"%(event_type, self.exists))
+ assert self.exists
+ self.exists = False
+
+ # Verify order of QMF events.
+ helper = EventHelper()
+ r = broker.connect().session().receiver(helper.eventAddress())
+ threads = [Receiver("qq"), Receiver("qq")]
+ for t in threads: t.start()
+ queue = QmfObject("queue", "qName", "qq")
+ finish = time.time() + self.duration()
+ try:
+ while time.time() < finish:
+ queue.qmf_event(r.fetch())
+ finally:
+ for t in threads: t.stopped = True; t.join()
+
+ def test_max_queues(self):
+ """Verify that we behave properly if we try to exceed the max number
+ of replicated queues - currently limited by the max number of channels
+ in the replication link"""
+ # This test is very slow (3 mins), skip it unless duration() > 1 minute.
+ if self.duration() < 60: return
+ # This test is written in C++ for speed, it takes a long time
+ # to create 64k queues in python. See ha_test_max_queues.cpp.
+ cluster = HaCluster(self, 2)
+ test = self.popen(["ha_test_max_queues", cluster[0].host_port()])
+ self.assertEqual(test.wait(), 0)
+
+class RecoveryTests(HaBrokerTest):
+ """Tests for recovery after a failure."""
+
+ def test_queue_hold(self):
+ """Verify that the broker holds queues without sufficient backup,
+ i.e. does not complete messages sent to those queues."""
+
+ l = LogLevel(ERROR) # Hide expected WARNING log messages from failover.
+ try:
+ # We don't want backups to time out for this test, set long timeout.
+ cluster = HaCluster(self, 4, args=["--ha-backup-timeout=120"]);
+ # Wait for the primary to be ready
+ cluster[0].wait_status("active")
+ for b in cluster[1:4]: b.wait_status("ready")
+ # Create a queue before the failure.
+ # FIXME aconway 2014-02-20: SWIG client doesn't respect sync=False
+ s1 = cluster.connect(0, native=True).session().sender("q1;{create:always}")
+ for b in cluster: b.wait_backup("q1")
+ for i in xrange(10): s1.send(str(i), timeout=0.1)
+
+ # Kill primary and 2 backups
+ cluster[3].wait_status("ready")
+ for i in [0,1,2]: cluster.kill(i, promote_next=False, final=False)
+ cluster[3].promote() # New primary, backups will be 1 and 2
+ cluster[3].wait_status("recovering")
+
+ def assertSyncTimeout(s):
+ self.assertRaises(qpid.messaging.Timeout, s.sync, timeout=.01)
+
+ # Create a queue after the failure
+ # FIXME aconway 2014-02-20: SWIG client doesn't respect sync=False
+ s2 = cluster.connect(3, native=True).session().sender("q2;{create:always}")
+
+ # Verify that messages sent are not completed
+ for i in xrange(10,20):
+ s1.send(str(i), sync=False, timeout=0.1);
+ s2.send(str(i), sync=False, timeout=0.1)
+
+ assertSyncTimeout(s1)
+ self.assertEqual(s1.unsettled(), 10)
+ assertSyncTimeout(s2)
+ self.assertEqual(s2.unsettled(), 10)
+
+ # Verify we can receive even if sending is on hold:
+ cluster[3].assert_browse("q1", [str(i) for i in range(10)])
+
+ # Restart backups, verify queues are released only when both backups are up
+ cluster.restart(1)
+ assertSyncTimeout(s1)
+ self.assertEqual(s1.unsettled(), 10)
+ assertSyncTimeout(s2)
+ self.assertEqual(s2.unsettled(), 10)
+ cluster.restart(2)
+ cluster.restart(0)
+
+ # Verify everything is up to date and active
+ def settled(sender): sender.sync(timeout=1); return sender.unsettled() == 0;
+ assert retry(lambda: settled(s1)), "Unsetttled=%s"%(s1.unsettled())
+ assert retry(lambda: settled(s2)), "Unsetttled=%s"%(s2.unsettled())
+ cluster[1].assert_browse_backup("q1", [str(i) for i in range(10)+range(10,20)])
+ cluster[1].assert_browse_backup("q2", [str(i) for i in range(10,20)])
+ cluster[3].wait_status("active"),
+ s1.session.connection.close()
+ s2.session.connection.close()
+ finally: l.restore()
+
+ def test_expected_backup_timeout(self):
+ """Verify that we time-out expected backups and release held queues
+ after a configured interval. Verify backup is demoted to catch-up,
+ but can still rejoin.
+ """
+ cluster = HaCluster(self, 3, args=["--ha-backup-timeout=0.5"]);
+ for i in [0,1]: cluster.kill(i, False)
+ cluster[2].promote() # New primary, expected backup will be 1
+ # Should not go active till the expected backup connects or times out.
+ cluster[2].wait_status("recovering")
+ # Messages should be held till expected backup times out
+ ss = cluster[2].connect().session()
+ s = ss.sender("q;{create:always}")
+ s.send("foo", sync=False)
+ self.assertEqual(s.unsettled(), 1) # Verify message not settled immediately.
+ s.sync(timeout=1) # And settled after timeout.
+ cluster[2].wait_status("active")
+
+ def test_join_ready_cluster(self):
+ """If we join a cluster where the primary is dead, the new primary is
+ not yet promoted and there are ready backups then we should refuse
+ promotion so that one of the ready backups can be chosen."""
+ cluster = HaCluster(self, 2)
+ cluster[0].wait_status("active")
+ cluster[1].wait_status("ready")
+ cluster.bounce(0, promote_next=False)
+ self.assertRaises(Exception, cluster[0].promote)
+ os.kill(cluster[1].pid, signal.SIGSTOP) # Test for timeout if unresponsive.
+ cluster.bounce(0, promote_next=False)
+ cluster[0].promote()
+
+ def test_stalled_backup(self):
+ """Make sure that a stalled backup broker does not stall the primary"""
+ cluster = HaCluster(self, 3, args=["--link-heartbeat-interval=1"])
+ os.kill(cluster[1].pid, signal.SIGSTOP)
+ s = cluster[0].connect().session()
+ s.sender("q;{create:always}").send("x")
+ self.assertEqual("x", s.receiver("q").fetch(0).content)
+
+class StoreTests(HaBrokerTest):
+ """Test for HA with persistence."""
+
+ def check_skip(self):
+ if not BrokerTest.store_lib:
+ print "WARNING: skipping HA+store tests, no store lib found."
+ return not BrokerTest.store_lib
+
+ def test_store_recovery(self):
+ """Verify basic store and recover functionality"""
+ if self.check_skip(): return
+ cluster = HaCluster(self, 1)
+ sn = cluster[0].connect().session()
+ # Create queue qq, exchange exx and binding between them
+ s = sn.sender("qq;{create:always,node:{durable:true}}")
+ sk = sn.sender("exx/k;{create:always,node:{type:topic, durable:true, x-declare:{type:'direct'}}}")
+ cluster[0].agent.bind("exx", "qq", "k")
+ for m in ["foo", "bar", "baz"]: s.send(qm.Message(m, durable=True))
+ r = cluster[0].connect().session().receiver("qq")
+ self.assertEqual(r.fetch().content, "foo")
+ r.session.acknowledge()
+ # Sending this message is a hack to flush the dequeue operation on qq.
+ s.send(qm.Message("flush", durable=True))
+
+ def verify(broker, x_count):
+ sn = broker.connect().session()
+ assert_browse(sn, "qq", [ "bar", "baz", "flush" ]+ (x_count)*["x"])
+ sn.sender("exx/k").send(qm.Message("x", durable=True))
+ assert_browse(sn, "qq", [ "bar", "baz", "flush" ]+ (x_count+1)*["x"])
+
+ verify(cluster[0], 0) # Sanity check
+ cluster.bounce(0)
+ cluster[0].wait_status("active")
+ verify(cluster[0], 1) # Loaded from store
+ cluster.start()
+ cluster[1].wait_status("ready")
+ cluster.kill(0)
+ cluster[1].wait_status("active")
+ verify(cluster[1], 2)
+ cluster.bounce(1, promote_next=False)
+ cluster[1].promote()
+ cluster[1].wait_status("active")
+ verify(cluster[1], 3)
+
+ def test_catchup_store(self):
+ """Verify that a backup erases queue data from store recovery before
+ doing catch-up from the primary."""
+ if self.check_skip(): return
+ cluster = HaCluster(self, 2)
+ sn = cluster[0].connect(heartbeat=HaBroker.heartbeat).session()
+ s1 = sn.sender("q1;{create:always,node:{durable:true}}")
+ for m in ["foo","bar"]: s1.send(qm.Message(m, durable=True))
+ s2 = sn.sender("q2;{create:always,node:{durable:true}}")
+ sk2 = sn.sender("ex/k2;{create:always,node:{type:topic, durable:true, x-declare:{type:'direct'}}}")
+ cluster[0].agent.bind("ex", "q2", "k2")
+ sk2.send(qm.Message("hello", durable=True))
+ # Wait for backup to catch up.
+ cluster[1].assert_browse_backup("q1", ["foo","bar"])
+ cluster[1].assert_browse_backup("q2", ["hello"])
+ # Make changes that the backup doesn't see
+ cluster.kill(1, promote_next=False, final=False)
+ r1 = cluster[0].connect(heartbeat=HaBroker.heartbeat).session().receiver("q1")
+ for m in ["foo", "bar"]: self.assertEqual(r1.fetch().content, m)
+ r1.session.acknowledge()
+ for m in ["x","y","z"]: s1.send(qm.Message(m, durable=True))
+ cluster[0].agent.unbind("ex", "q2", "k2")
+ cluster[0].agent.bind("ex", "q1", "k1")
+ # Restart both brokers from store to get inconsistent sequence numbering.
+ cluster.bounce(0, promote_next=False)
+ cluster[0].promote()
+ cluster[0].wait_status("active")
+ cluster.restart(1)
+ cluster[1].wait_status("ready")
+
+ # Verify state
+ cluster[0].assert_browse("q1", ["x","y","z"])
+ cluster[1].assert_browse_backup("q1", ["x","y","z"])
+
+ sn = cluster[0].connect(heartbeat=HaBroker.heartbeat).session()
+ sn.sender("ex/k1").send("boo")
+ cluster[0].assert_browse_backup("q1", ["x","y","z", "boo"])
+ cluster[1].assert_browse_backup("q1", ["x","y","z", "boo"])
+ sn.sender("ex/k2").send("hoo") # q2 was unbound so this should be dropped.
+ sn.sender("q2").send("end") # mark the end of the queue for assert_browse
+ cluster[0].assert_browse("q2", ["hello", "end"])
+ cluster[1].assert_browse_backup("q2", ["hello", "end"])
+
+def open_read(name):
+ try:
+ f = open(name)
+ return f.read()
+ finally: f.close()
+
+class TransactionTests(HaBrokerTest):
+
+ def tx_simple_setup(self, cluster, broker=0):
+ """Start a transaction, remove messages from queue a, add messages to queue b"""
+ c = cluster.connect(broker, protocol=self.tx_protocol)
+ # Send messages to a, no transaction.
+ sa = c.session().sender("a;{create:always,node:{durable:true}}")
+ tx_msgs = ["x","y","z"]
+ for m in tx_msgs: sa.send(qm.Message(content=m, durable=True))
+ sa.close()
+
+ # Receive messages from a, in transaction.
+ tx = c.session(transactional=True)
+ txr = tx.receiver("a")
+ tx_msgs2 = [txr.fetch(1).content for i in xrange(3)]
+ self.assertEqual(tx_msgs, tx_msgs2)
+
+ # Send messages to b, transactional, mixed with non-transactional.
+ sb = c.session().sender("b;{create:always,node:{durable:true}}")
+ txs = tx.sender("b")
+ msgs = [str(i) for i in xrange(3)]
+ for tx_m,m in zip(tx_msgs2, msgs):
+ txs.send(tx_m);
+ sb.send(m)
+ sb.close()
+ return tx
+
+ def tx_subscriptions(self, broker):
+ """Return list of queue names for tx subscriptions"""
+ return [q for q in broker.agent.repsub_queues()
+ if q.startswith("qpid.ha-tx")]
+
+ def test_tx_simple_commit(self):
+ cluster = HaCluster(self, 2, test_store=True, wait=True)
+ tx = self.tx_simple_setup(cluster)
+ tx.sync()
+ tx_queues = cluster[0].agent.tx_queues()
+
+ # NOTE: backup does not process transactional dequeues until prepare
+ cluster[1].assert_browse_backup("a", ["x","y","z"])
+ cluster[1].assert_browse_backup("b", ['0', '1', '2'])
+
+ tx.acknowledge()
+ tx.commit()
+ tx.sync()
+ tx.close()
+
+ for b in cluster:
+ self.assert_simple_commit_outcome(b, tx_queues)
+
+ # Verify non-tx dequeue is replicated correctly
+ c = cluster.connect(0, protocol=self.tx_protocol)
+ r = c.session().receiver("b")
+ ri = receiver_iter(r, timeout=1)
+ self.assertEqual(['0', '1', '2', 'x', 'y', 'z'], [m.content for m in ri])
+ r.session.acknowledge()
+ for b in cluster: b.assert_browse_backup("b", [], msg=b)
+ c.close()
+ tx.connection.close()
+
+
+ def check_enq_deq(self, cluster, queue, expect):
+ for b in cluster:
+ q = b.agent.getQueue(queue)
+ self.assertEqual(
+ (b.name,)+expect,
+ (b.name, q.msgTotalEnqueues, q.msgTotalDequeues, q.msgTxnEnqueues, q.msgTxnDequeues))
+
+ def test_tx_enq_notx_deq(self):
+ """Verify that a non-tx dequeue of a tx enqueue is replicated correctly"""
+ cluster = HaCluster(self, 2, test_store=True)
+ c = cluster.connect(0, protocol=self.tx_protocol)
+
+ tx = c.session(transactional=True)
+ c.session().sender("qq;{create:always}").send("m1")
+ tx.sender("qq;{create:always}").send("tx")
+ tx.commit()
+ tx.close()
+ c.session().sender("qq;{create:always}").send("m2")
+ self.check_enq_deq(cluster, 'qq', (3, 0, 1, 0))
+
+ notx = c.session()
+ self.assertEqual(['m1', 'tx', 'm2'], [m.content for m in receiver_iter(notx.receiver('qq'))])
+ notx.acknowledge()
+ self.check_enq_deq(cluster, 'qq', (3, 3, 1, 0))
+ for b in cluster: b.assert_browse_backup('qq', [], msg=b)
+ for b in cluster: self.assert_tx_clean(b)
+
+ def test_tx_enq_notx_deq_qpid_send(self):
+ """Verify that a non-tx dequeue of a tx enqueue is replicated correctly"""
+ cluster = HaCluster(self, 2, test_store=True)
+
+ self.popen(
+ ['qpid-send', '-a', 'qq;{create:always}', '-b', cluster[0].host_port(), '--tx=1',
+ '--content-string=foo']
+ ).assert_exit_ok()
+ for b in cluster: b.assert_browse_backup('qq', ['foo'], msg=b)
+ self.check_enq_deq(cluster, 'qq', (1, 0, 1, 0))
+
+ self.popen(['qpid-receive', '-a', 'qq', '-b', cluster[0].host_port()]).assert_exit_ok()
+ self.check_enq_deq(cluster, 'qq', (1, 1, 1, 0))
+ for b in cluster: b.assert_browse_backup('qq', [], msg=b)
+ for b in cluster: self.assert_tx_clean(b)
+
+ def assert_tx_clean(self, b):
+ """Verify that there are no transaction artifacts
+ (exchanges, queues, subscriptions) on b."""
+ class FunctionCache: # Call a function and cache the result.
+ def __init__(self, f): self.f, self.value = f, None
+ def __call__(self): self.value = self.f(); return self.value
+
+ txq= FunctionCache(b.agent.tx_queues)
+ assert retry(lambda: not txq()), "%s: unexpected %s"%(b, txq.value)
+ txsub = FunctionCache(lambda: self.tx_subscriptions(b))
+ assert retry(lambda: not txsub()), "%s: unexpected %s"%(b, txsub.value)
+ # TODO aconway 2013-10-15: TX exchanges don't show up in management.
+
+ def assert_simple_commit_outcome(self, b, tx_queues):
+ b.assert_browse_backup("a", [], msg=b)
+ b.assert_browse_backup("b", ['0', '1', '2', 'x', 'y', 'z'], msg=b)
+ # Check for expected actions on the store
+ expect = """<enqueue a x>
+<enqueue a y>
+<enqueue a z>
+<begin tx 1>
+<dequeue a x tx=1>
+<dequeue a y tx=1>
+<dequeue a z tx=1>
+<commit tx=1>
+"""
+ self.assertEqual(expect, open_read(b.store_log), msg=b)
+ self.assert_tx_clean(b)
+
+ def test_tx_simple_rollback(self):
+ cluster = HaCluster(self, 2, test_store=True)
+ tx = self.tx_simple_setup(cluster)
+ tx.sync()
+ tx_queues = cluster[0].agent.tx_queues()
+ tx.acknowledge()
+ tx.rollback()
+ tx.close() # For clean test.
+ for b in cluster: self.assert_simple_rollback_outcome(b, tx_queues)
+ tx.connection.close()
+
+ def assert_simple_rollback_outcome(self, b, tx_queues):
+ b.assert_browse_backup("a", ["x","y","z"], msg=b)
+ b.assert_browse_backup("b", ['0', '1', '2'], msg=b)
+ # Check for expected actions on the store
+ expect = """<enqueue a x>
+<enqueue a y>
+<enqueue a z>
+"""
+ self.assertEqual(open_read(b.store_log), expect, msg=b)
+ self.assert_tx_clean(b)
+
+ def test_tx_simple_failure(self):
+ """Verify we throw TransactionAborted if there is a store error during a transaction"""
+ cluster = HaCluster(self, 3, test_store=True)
+ tx = self.tx_simple_setup(cluster)
+ tx.sync()
+ tx_queues = cluster[0].agent.tx_queues()
+ tx.acknowledge()
+ l = LogLevel(ERROR) # Hide expected WARNING log messages from failover.
+ try:
+ cluster.bounce(0) # Should cause roll-back
+ tx.connection.session() # Wait for reconnect
+ for b in cluster: self.assert_simple_rollback_outcome(b, tx_queues)
+ self.assertRaises(qm.TransactionAborted, tx.sync)
+ self.assertRaises(qm.TransactionAborted, tx.commit)
+ try: tx.connection.close()
+ except qm.TransactionAborted: pass # Occasionally get exception on close.
+ for b in cluster: self.assert_simple_rollback_outcome(b, tx_queues)
+ finally: l.restore()
+
+ def test_tx_simple_failover(self):
+ """Verify we throw TransactionAborted if there is a fail-over during a transaction"""
+ cluster = HaCluster(self, 3, test_store=True)
+ tx = self.tx_simple_setup(cluster)
+ tx.sync()
+ tx_queues = cluster[0].agent.tx_queues()
+ tx.acknowledge()
+ l = LogLevel(ERROR) # Hide expected WARNING log messages from failover.
+ try:
+ cluster.bounce(0) # Should cause roll-back
+ tx.connection.session() # Wait for reconnect
+ for b in cluster: self.assert_simple_rollback_outcome(b, tx_queues)
+ self.assertRaises(qm.TransactionAborted, tx.sync)
+ self.assertRaises(qm.TransactionAborted, tx.commit)
+ try: tx.connection.close()
+ except qm.TransactionAborted: pass # Occasionally get exception on close.
+ for b in cluster: self.assert_simple_rollback_outcome(b, tx_queues)
+ finally: l.restore()
+
+ def test_tx_unknown_failover(self):
+ """Verify we throw TransactionUnknown if there is a failure during commit"""
+ cluster = HaCluster(self, 3, test_store=True)
+ tx = self.tx_simple_setup(cluster)
+ tx.sync()
+ tx_queues = cluster[0].agent.tx_queues()
+ tx.acknowledge()
+ l = LogLevel(ERROR) # Hide expected WARNING log messages from failover.
+ try:
+ os.kill(cluster[2].pid, signal.SIGSTOP) # Delay prepare response
+ class CommitThread(Thread):
+ def run(self):
+ try: tx.commit()
+ except Exception, e:
+ self.error = e
+ t = CommitThread()
+ t.start() # Commit in progress
+ t.join(timeout=0.01)
+ self.assertTrue(t.isAlive())
+ cluster.bounce(0)
+ os.kill(cluster[2].pid, signal.SIGCONT)
+ t.join()
+ try: raise t.error
+ except qm.TransactionUnknown: pass
+ for b in cluster: self.assert_tx_clean(b)
+ try: tx.connection.close()
+ except qm.TransactionUnknown: pass # Occasionally get exception on close.
+ finally: l.restore()
+
+ def test_tx_no_backups(self):
+ """Test the special case of a TX where there are no backups"""
+
+ # Test commit
+ cluster = HaCluster(self, 1, test_store=True)
+ tx = self.tx_simple_setup(cluster)
+ tx.acknowledge()
+ tx.commit()
+ tx.sync()
+ tx_queues = cluster[0].agent.tx_queues()
+ tx.close()
+ self.assert_simple_commit_outcome(cluster[0], tx_queues)
+
+ # Test rollback
+ cluster = HaCluster(self, 1, test_store=True)
+ tx = self.tx_simple_setup(cluster)
+ tx.sync()
+ tx_queues = cluster[0].agent.tx_queues()
+ tx.acknowledge()
+ tx.rollback()
+ tx.sync()
+ tx.close()
+ self.assert_simple_rollback_outcome(cluster[0], tx_queues)
+
+ def test_tx_backup_fail(self):
+ cluster = HaCluster(self, 2, test_store=True, s_args=[[],["--test-store-name=bang"]])
+ c = cluster[0].connect(protocol=self.tx_protocol)
+ tx = c.session(transactional=True)
+ s = tx.sender("q;{create:always,node:{durable:true}}")
+ for m in ["foo","TEST_STORE_DO bang: throw","bar"]: s.send(qm.Message(m, durable=True))
+ def commit_sync(): tx.commit(); tx.sync()
+ self.assertRaises(qm.TransactionAborted, commit_sync)
+ for b in cluster: b.assert_browse_backup("q", [])
+ self.assertEqual(open_read(cluster[0].store_log), "<begin tx 1>\n<enqueue q foo tx=1>\n<enqueue q TEST_STORE_DO bang: throw tx=1>\n<enqueue q bar tx=1>\n<abort tx=1>\n")
+ self.assertEqual(open_read(cluster[1].store_log), "<begin tx 1>\n<enqueue q foo tx=1>\n<enqueue q TEST_STORE_DO bang: throw tx=1>\n<abort tx=1>\n")
+
+ def test_tx_join_leave(self):
+ """Test cluster members joining/leaving cluster.
+ Also check that tx-queues are cleaned up at end of transaction."""
+
+ cluster = HaCluster(self, 3)
+
+ # Leaving
+ tx = cluster[0].connect(protocol=self.tx_protocol).session(transactional=True)
+ s = tx.sender("q;{create:always}")
+ s.send("a", sync=True)
+ self.assertEqual([1,1,1], [len(b.agent.tx_queues()) for b in cluster])
+ cluster[1].kill(final=False)
+ s.send("b")
+ tx.commit()
+ tx.connection.close()
+ for b in [cluster[0],cluster[2]]:
+ self.assert_tx_clean(b)
+ b.assert_browse_backup("q", ["a","b"], msg=b)
+ # Joining
+ tx = cluster[0].connect(protocol=self.tx_protocol).session(transactional=True)
+ s = tx.sender("q;{create:always}")
+ s.send("foo")
+ cluster.restart(1) # Not a part of the current transaction.
+ tx.commit()
+ tx.connection.close()
+ for b in cluster: self.assert_tx_clean(b)
+ # The new member is not in the tx but receives the results normal replication.
+ for b in cluster: b.assert_browse_backup("q", ["a", "b", "foo"], msg=b)
+
+ def test_tx_block_threads(self):
+ """Verify that TXs blocked in commit don't deadlock."""
+ cluster = HaCluster(self, 2, args=["--worker-threads=2"], test_store=True)
+ n = 10 # Number of concurrent transactions
+ sessions = [cluster.connect(0, protocol=self.tx_protocol).session(transactional=True) for i in xrange(n)]
+ # Have the store delay the response for 10s
+ for s in sessions:
+ sn = s.sender("qq;{create:always,node:{durable:true}}")
+ sn.send(qm.Message("foo", durable=True))
+ self.assertEqual(n, len(cluster[1].agent.tx_queues()))
+ threads = [ Thread(target=s.commit) for s in sessions]
+ for t in threads: t.start()
+ cluster[0].ready(timeout=1) # Check for deadlock
+ for b in cluster: b.assert_browse_backup('qq', ['foo']*n)
+ for t in threads: t.join()
+ for s in sessions: s.connection.close()
+
+ def test_other_tx_tests(self):
+ try:
+ import qpid_tests.broker_0_10
+ except ImportError:
+ raise Skipped("Tests not found")
+ cluster = HaCluster(self, 3)
+ if "QPID_PORT" in os.environ: del os.environ["QPID_PORT"]
+ self.popen(["qpid-txtest2", "--broker", cluster[0].host_port()]).assert_exit_ok()
+ print
+ self.popen(["qpid-python-test",
+ "-m", "qpid_tests.broker_0_10",
+ "-m", "qpid_tests.broker_1_0",
+ "-b", "localhost:%s"%(cluster[0].port()),
+ "*.tx.*"], stdout=None, stderr=None).assert_exit_ok()
+
+if __name__ == "__main__":
+ qpid_ha_exec = os.getenv("QPID_HA_EXEC")
+ if qpid_ha_exec and os.path.isfile(qpid_ha_exec):
+ BrokerTest.amqp_tx_warning()
+ outdir = "ha_tests.tmp"
+ shutil.rmtree(outdir, True)
+ os.execvp("qpid-python-test",
+ ["qpid-python-test", "-m", "ha_tests", "-DOUTDIR=%s"%outdir]
+ + sys.argv[1:])
+ else:
+ print "Skipping ha_tests, qpid-ha not available"
+
+
diff --git a/qpid/cpp/src/tests/header_test.cpp b/qpid/cpp/src/tests/header_test.cpp
new file mode 100644
index 0000000000..c36b4f3bc3
--- /dev/null
+++ b/qpid/cpp/src/tests/header_test.cpp
@@ -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.
+ *
+ */
+
+#include <iostream>
+
+#include "TestOptions.h"
+#include "qpid/client/Connection.h"
+#include "qpid/client/Message.h"
+#include "qpid/client/Session.h"
+#include "qpid/client/SubscriptionManager.h"
+
+using namespace qpid;
+using namespace qpid::client;
+using namespace std;
+
+int main(int argc, char** argv)
+{
+ TestOptions opts;
+ try {
+ opts.parse(argc, argv);
+ Connection connection;
+ connection.open(opts.con);
+ Session session = connection.newSession();
+ std::string q("header_interop_test_queue");
+ session.queueDeclare(arg::queue=q);
+ double pi = 3.14159265;
+ float e = 2.71828f;
+ Message msg("", q);
+ msg.getMessageProperties().getApplicationHeaders().setDouble("pi", pi);
+ msg.getMessageProperties().getApplicationHeaders().setFloat("e", e);
+ session.messageTransfer(arg::content=msg);
+
+ session.close();
+ connection.close();
+
+ return 0;
+ } catch(const exception& e) {
+ cout << e.what() << endl;
+ }
+ return 1;
+}
diff --git a/qpid/cpp/src/tests/header_test.py b/qpid/cpp/src/tests/header_test.py
new file mode 100755
index 0000000000..d5a2c16c01
--- /dev/null
+++ b/qpid/cpp/src/tests/header_test.py
@@ -0,0 +1,86 @@
+#!/usr/bin/env python
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 qpid
+import sys
+import os
+from qpid.util import connect
+from qpid.connection import Connection
+from qpid.datatypes import Message, RangedSet, uuid4
+from qpid.queue import Empty
+from math import fabs
+
+def getApplicationHeaders(msg):
+ for h in msg.headers:
+ if hasattr(h, 'application_headers'): return getattr(h, 'application_headers')
+ return None
+
+# Set parameters for login
+
+host="127.0.0.1"
+port=5672
+user="guest"
+password="guest"
+
+if len(sys.argv) > 1 :
+ host=sys.argv[1]
+if len(sys.argv) > 2 :
+ port=int(sys.argv[2])
+
+# Create a connection.
+socket = connect(host, port)
+connection = Connection (sock=socket)
+connection.start()
+session = connection.session(str(uuid4()))
+
+q = "header_interop_test_queue"
+session.queue_declare(queue=q)
+
+session.message_subscribe(queue=q, destination="received")
+queue = session.incoming("received")
+queue.start()
+
+msg = queue.get(timeout=10)
+pi = 3.14159265
+e = 2.71828
+
+headers = getApplicationHeaders(msg)
+pi_ = headers["pi"]
+e_ = headers["e"]
+session.close(timeout=10)
+
+failed = False
+
+if pi != pi_:
+ print "got incorrect value for pi: ", pi_, " expected:", pi
+ failed = True
+
+if fabs(e - e_) > 0.0001:
+ print "got incorrect value for e: ", e_, " expected:", e
+ failed = True
+
+if failed:
+ sys.exit(1)
+else:
+ print "Correct header values received."
+ sys.exit(0)
+
+
+
diff --git a/qpid/cpp/src/tests/headers_federation.py b/qpid/cpp/src/tests/headers_federation.py
new file mode 100644
index 0000000000..60cff1da54
--- /dev/null
+++ b/qpid/cpp/src/tests/headers_federation.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 sys
+from qpid.testlib import TestBase010
+from qpid.datatypes import Message
+from qpid.queue import Empty
+from time import sleep
+
+class HeadersFederationTests(TestBase010):
+
+ def remote_host(self):
+ return self.defines.get("remote-host", "localhost")
+
+ def remote_port(self):
+ return int(self.defines["remote-port"])
+
+ def verify_cleanup(self):
+ attempts = 0
+ total = len(self.qmf.getObjects(_class="bridge")) + len(self.qmf.getObjects(_class="link"))
+ while total > 0:
+ attempts += 1
+ if attempts >= 10:
+ self.fail("Bridges and links didn't clean up")
+ return
+ sleep(1)
+ total = len(self.qmf.getObjects(_class="bridge")) + len(self.qmf.getObjects(_class="link"))
+
+ def test_dynamic_headers_unbind(self):
+ session = self.session
+ r_conn = self.connect(host=self.remote_host(), port=self.remote_port())
+ r_session = r_conn.session("test_dynamic_headers_unbind")
+
+ session.exchange_declare(exchange="fed.headers_unbind", type="headers")
+ r_session.exchange_declare(exchange="fed.headers_unbind", type="headers")
+
+ self.startQmf()
+ qmf = self.qmf
+
+ broker = qmf.getObjects(_class="broker")[0]
+ result = broker.connect(self.remote_host(), self.remote_port(), False, "PLAIN", "guest", "guest", "tcp")
+ self.assertEqual(result.status, 0)
+
+ link = qmf.getObjects(_class="link")[0]
+ result = link.bridge(False, "fed.headers_unbind", "fed.headers_unbind", "", "", "", False, False, True, 0)
+ self.assertEqual(result.status, 0)
+ bridge = qmf.getObjects(_class="bridge")[0]
+ sleep(5)
+
+ session.queue_declare(queue="fed1", exclusive=True, auto_delete=True)
+ queue = qmf.getObjects(_class="queue", name="fed1")[0]
+ queue.update()
+ self.assertEqual(queue.bindingCount, 1,
+ "bindings not accounted for (expected 1, got %d)" % queue.bindingCount)
+
+ session.exchange_bind(queue="fed1", exchange="fed.headers_unbind", binding_key="key1", arguments={'x-match':'any', 'class':'first'})
+ queue.update()
+ self.assertEqual(queue.bindingCount, 2,
+ "bindings not accounted for (expected 2, got %d)" % queue.bindingCount)
+
+ session.exchange_unbind(queue="fed1", exchange="fed.headers_unbind", binding_key="key1")
+ queue.update()
+ self.assertEqual(queue.bindingCount, 1,
+ "bindings not accounted for (expected 1, got %d)" % queue.bindingCount)
+
+ result = bridge.close()
+ self.assertEqual(result.status, 0)
+ result = link.close()
+ self.assertEqual(result.status, 0)
+
+ self.verify_cleanup()
+
+ def getProperty(self, msg, name):
+ for h in msg.headers:
+ if hasattr(h, name): return getattr(h, name)
+ return None
+
+ def getAppHeader(self, msg, name):
+ headers = self.getProperty(msg, "application_headers")
+ if headers:
+ return headers[name]
+ return None
diff --git a/qpid/cpp/src/tests/idle_timeout_tests.py b/qpid/cpp/src/tests/idle_timeout_tests.py
new file mode 100755
index 0000000000..22a107a110
--- /dev/null
+++ b/qpid/cpp/src/tests/idle_timeout_tests.py
@@ -0,0 +1,95 @@
+#!/usr/bin/env python
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 os
+import shutil
+import signal
+import sys
+
+from brokertest import *
+
+class AmqpIdleTimeoutTest(BrokerTest):
+ """
+ Test AMQP 1.0 idle-timeout support
+ """
+ def setUp(self):
+ BrokerTest.setUp(self)
+ if not BrokerTest.amqp_lib:
+ raise Skipped("AMQP 1.0 library not found")
+ if qm != qpid_messaging:
+ raise Skipped("AMQP 1.0 client not found")
+ self._broker = self.broker()
+
+ def test_client_timeout(self):
+ """Ensure that the client disconnects should the broker stop
+ responding.
+ """
+ conn = self._broker.connect(native=False, timeout=None,
+ protocol="amqp1.0", heartbeat=1)
+ self.assertTrue(conn.isOpen())
+ # should disconnect within 2 seconds of broker stop
+ deadline = time.time() + 8
+ os.kill(self._broker.pid, signal.SIGSTOP)
+ while time.time() < deadline:
+ if not conn.isOpen():
+ break;
+ self.assertTrue(not conn.isOpen())
+ os.kill(self._broker.pid, signal.SIGCONT)
+
+
+ def test_broker_timeout(self):
+ """By default, the broker will adopt the same timeout as the client
+ (mimics the 0-10 timeout behavior). Verify the broker disconnects
+ unresponsive clients.
+ """
+
+ count = len(self._broker.agent.getAllConnections())
+
+ # Create a new connection to the broker:
+ receiver_cmd = ["qpid-receive",
+ "--broker", self._broker.host_port(),
+ "--address=amq.fanout",
+ "--connection-options={protocol:amqp1.0, heartbeat:1}",
+ "--forever"]
+ receiver = self.popen(receiver_cmd, stdout=PIPE, stderr=PIPE,
+ expect=EXPECT_UNKNOWN)
+ start = time.time()
+ deadline = time.time() + 10
+ while time.time() < deadline:
+ if count < len(self._broker.agent.getAllConnections()):
+ break;
+ self.assertTrue(count < len(self._broker.agent.getAllConnections()))
+
+ # now 'hang' the client, the broker should disconnect
+ start = time.time()
+ os.kill(receiver.pid, signal.SIGSTOP)
+ deadline = time.time() + 10
+ while time.time() < deadline:
+ if count == len(self._broker.agent.getAllConnections()):
+ break;
+ self.assertEqual(count, len(self._broker.agent.getAllConnections()))
+ os.kill(receiver.pid, signal.SIGCONT)
+ receiver.teardown()
+
+
+if __name__ == "__main__":
+ shutil.rmtree("brokertest.tmp", True)
+ os.execvp("qpid-python-test",
+ ["qpid-python-test", "-m", "idle_timeout_tests"] + sys.argv[1:])
diff --git a/qpid/cpp/src/tests/install_env.sh.in b/qpid/cpp/src/tests/install_env.sh.in
new file mode 100644
index 0000000000..d29a23930d
--- /dev/null
+++ b/qpid/cpp/src/tests/install_env.sh.in
@@ -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.
+#
+
+absdir() { echo `cd $1 && pwd`; }
+
+prefix=`absdir @prefix@`
+export QPID_INSTALL_PREFIX=$prefix
+export PATH=$prefix/bin:$prefix/sbin:$prefix/libexec/qpid/tests:$PATH
+export LD_LIBRARY_PATH=$prefix/lib:$LD_LIBRARY_PATH
+export PYTHONPATH=$prefix/lib/python2.6/site-packages:$PYTHONPATH
diff --git a/qpid/cpp/src/tests/interlink_tests.py b/qpid/cpp/src/tests/interlink_tests.py
new file mode 100755
index 0000000000..3eec2422f1
--- /dev/null
+++ b/qpid/cpp/src/tests/interlink_tests.py
@@ -0,0 +1,336 @@
+#!/usr/bin/env python
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 os, signal, sys, time, imp, re, subprocess, glob, random, logging, shutil, math, unittest, random
+import traceback
+from qpid.messaging import Message, SessionError, NotFound, ConnectionError, ReceiverError, Connection, Timeout, Disposition, REJECTED, Empty
+from brokertest import *
+from ha_test import HaPort
+from threading import Thread, Lock, Condition
+from logging import getLogger, WARN, ERROR, DEBUG, INFO
+from qpidtoollibs import BrokerObject
+
+class Domain(BrokerObject):
+ def __init__(self, broker, values):
+ BrokerObject.__init__(self, broker, values)
+
+class Config:
+ def __init__(self, broker, address="q;{create:always}", version="amqp1.0"):
+ self.url = broker.host_port()
+ self.address = address
+ self.version = version
+
+ def __str__(self):
+ return "url: %s, address: %s, version: %s" % (self.url, self.address, self.version)
+
+class AmqpBrokerTest(BrokerTest):
+ """
+ Tests using AMQP 1.0 support
+ """
+ def setUp(self):
+ BrokerTest.setUp(self)
+ self.port_holder = HaPort(self)
+ self.broker = self.amqp_broker(port_holder=self.port_holder)
+ self.default_config = Config(self.broker)
+ self.agent = self.broker.agent
+
+ def sender(self, config, reply_to=None):
+ cmd = ["qpid-send",
+ "--broker", config.url,
+ "--address", config.address,
+ "--connection-options", "{protocol:%s}" % config.version,
+ "--content-stdin", "--send-eos=1"
+ ]
+ if reply_to:
+ cmd.append( "--reply-to=%s" % reply_to)
+ return self.popen(cmd, stdin=PIPE)
+
+ def receiver(self, config):
+ cmd = ["qpid-receive",
+ "--broker", config.url,
+ "--address", config.address,
+ "--connection-options", "{protocol:%r}" % config.version,
+ "--timeout=10"
+ ]
+ return self.popen(cmd, stdout=PIPE)
+
+ def ready_receiver(self, config):
+ # NOTE: some tests core dump when run with SWIG binding over proton
+ # version<=0.6. This is fixed on proton 0.7.
+ def use_native():
+ pv=os.environ.get("QPID_PROTON_VERSION")
+ return pv and [int(n) for n in pv.split(".")] <= [0,6]
+ s = self.broker.connect(native=use_native()).session()
+ r = s.receiver("readyq; {create:always}")
+ cmd = ["qpid-receive",
+ "--broker", config.url,
+ "--address", config.address,
+ "--connection-options", "{protocol:%r}" % config.version,
+ "--timeout=10", "--ready-address=readyq;{create:always}"
+ ]
+ result = self.popen(cmd, stdout=PIPE)
+ r.fetch(timeout=1) # wait until receiver is actually ready
+ s.acknowledge()
+ r.close()
+ s.close()
+ return result
+
+ def send_and_receive(self, send_config=None, recv_config=None, count=1000, reply_to=None, wait_for_receiver=False, debug=False):
+ if debug:
+ print "sender config is %s" % (send_config or self.default_config)
+ print "receiver config is %s" % (recv_config or self.default_config)
+ sender = self.sender(send_config or self.default_config, reply_to)
+ sender._set_cloexec_flag(sender.stdin) #required for older python, see http://bugs.python.org/issue4112
+ if wait_for_receiver:
+ receiver = self.ready_receiver(recv_config or self.default_config)
+ else:
+ receiver = self.receiver(recv_config or self.default_config)
+
+ messages = ["message-%s" % (i+1) for i in range(count)]
+ for m in messages:
+ sender.stdin.write(m + "\n")
+ sender.stdin.flush()
+ sender.stdin.close()
+ if debug:
+ c = send_config or self.default_config
+ print "sent %s messages to %s sn %s" % (len(messages), c.address, c.url)
+
+ if debug:
+ c = recv_config or self.default_config
+ print "reading messages from %s sn %s" % (c.address, c.url)
+ for m in messages:
+ l = receiver.stdout.readline().rstrip()
+ if debug:
+ print l
+ assert m == l, (m, l)
+
+ sender.wait()
+ receiver.wait()
+
+ def test_simple(self):
+ self.send_and_receive()
+
+ def test_translate1(self):
+ self.send_and_receive(recv_config=Config(self.broker, version="amqp0-10"))
+
+ def test_translate2(self):
+ self.send_and_receive(send_config=Config(self.broker, version="amqp0-10"))
+
+ def test_translate_with_large_routingkey(self):
+ self.send_and_receive(send_config=Config(self.broker, address="amq.topic/a.%s" % ("x" * 256), version="amqp1.0"), recv_config=Config(self.broker, address="amq.topic/a.*", version="amqp0-10"), wait_for_receiver=True)
+
+ def send_and_receive_empty(self, send_config=None, recv_config=None):
+ sconfig = send_config or self.default_config
+ rconfig = recv_config or self.default_config
+ send_cmd = ["qpid-send",
+ "--broker", sconfig.url,
+ "--address=%s" % sconfig.address,
+ "--connection-options={protocol:%s}" % sconfig.version,
+ "--content-size=0",
+ "--messages=1",
+ "-P", "my-header=abc"
+ ]
+ sender = self.popen(send_cmd)
+ sender.wait()
+ receive_cmd = ["qpid-receive",
+ "--broker", rconfig.url,
+ "--address=%s" % rconfig.address,
+ "--connection-options={protocol:%s}" % rconfig.version,
+ "--messages=1",
+ "--print-content=false", "--print-headers=true"
+ ]
+ receiver = self.popen(receive_cmd, stdout=PIPE)
+ l = receiver.stdout.read()
+ assert "my-header:abc" in l
+ receiver.wait()
+
+ def test_translate_empty_1(self):
+ self.send_and_receive_empty(recv_config=Config(self.broker, version="amqp0-10"))
+
+ def test_translate_empty_2(self):
+ self.send_and_receive_empty(send_config=Config(self.broker, version="amqp0-10"))
+
+ def request_response(self, reply_to, send_config=None, request_config=None, response_config=None, count=1000, wait_for_receiver=False):
+ rconfig = request_config or self.default_config
+ echo_cmd = ["qpid-receive",
+ "--broker", rconfig.url,
+ "--address=%s" % rconfig.address,
+ "--connection-options={protocol:%s}" % rconfig.version,
+ "--timeout=10", "--print-content=false", "--print-headers=false"
+ ]
+ requests = self.popen(echo_cmd)
+ self.send_and_receive(send_config, response_config, count, reply_to=reply_to, wait_for_receiver=wait_for_receiver)
+ requests.wait()
+
+ def request_response_local(self, request_address, response_address, wait_for_receiver=False, request_version="amqp1.0", echo_version="amqp1.0"):
+ self.request_response(response_address, send_config=Config(self.broker, address=request_address, version=request_version), request_config=Config(self.broker, address=request_address, version=echo_version), response_config=Config(self.broker, address=response_address, version=request_version), wait_for_receiver=wait_for_receiver)
+
+ def test_request_reponse_queue(self):
+ self.agent.create("queue", "q1")
+ self.agent.create("queue", "q2")
+ self.request_response_local("q1", "q2")
+
+ def test_request_reponse_queue_translated1(self):
+ self.agent.create("queue", "q1")
+ self.agent.create("queue", "q2")
+ self.request_response_local("q1", "q2", request_version="amqp0-10", echo_version="amqp1.0")
+
+ def test_request_reponse_queue_translated2(self):
+ self.agent.create("queue", "q1")
+ self.agent.create("queue", "q2")
+ self.request_response_local("q1", "q2", request_version="amqp1.0", echo_version="amqp0-10")
+
+ def test_request_reponse_exchange(self):
+ self.agent.create("queue", "q1")
+ self.request_response_local("q1", "amq.fanout", wait_for_receiver=True)
+
+ def test_request_reponse_exchange_translated1(self):
+ self.agent.create("queue", "q1")
+ self.request_response_local("q1", "amq.fanout", wait_for_receiver=True, request_version="amqp0-10", echo_version="amqp1.0")
+
+ def test_request_reponse_exchange_translated2(self):
+ self.agent.create("queue", "q1")
+ self.request_response_local("q1", "amq.fanout", wait_for_receiver=True, request_version="amqp1.0", echo_version="amqp0-10")
+
+ def test_request_reponse_exchange_with_subject(self):
+ self.agent.create("queue", "q1")
+ self.request_response_local("q1", "amq.topic/abc; {node:{type:topic}}", wait_for_receiver=True)
+
+ def test_request_reponse_exchange_with_subject_translated1(self):
+ self.agent.create("queue", "q1")
+ self.request_response_local("q1", "amq.topic/abc; {node:{type:topic}}", wait_for_receiver=True, request_version="amqp0-10", echo_version="amqp1.0")
+
+ def test_request_reponse_exchange_with_subject_translated2(self):
+ self.agent.create("queue", "q1")
+ self.request_response_local("q1", "amq.topic/abc; {node:{type:topic}}", wait_for_receiver=True, request_version="amqp1.0", echo_version="amqp0-10")
+
+ def test_domain(self):
+ brokerB = self.amqp_broker()
+ self.agent.create("domain", "BrokerB", {"url":brokerB.host_port()})
+ domains = self.agent._getAllBrokerObjects(Domain)
+ assert len(domains) == 1
+ assert domains[0].name == "BrokerB"
+
+ def incoming_link(self, mechanism):
+ brokerB = self.amqp_broker()
+ agentB = brokerB.agent
+ self.agent.create("queue", "q")
+ agentB.create("queue", "q")
+ self.agent.create("domain", "BrokerB", {"url":brokerB.host_port(), "sasl_mechanisms":mechanism})
+ self.agent.create("incoming", "Link1", {"domain":"BrokerB","source":"q","target":"q"})
+ #send to brokerB, receive from brokerA
+ self.send_and_receive(send_config=Config(brokerB))
+
+ def test_incoming_link_anonymous(self):
+ self.incoming_link("ANONYMOUS")
+
+ def test_incoming_link_nosasl(self):
+ self.incoming_link("NONE")
+
+ def test_outgoing_link(self):
+ brokerB = self.amqp_broker()
+ agentB = brokerB.agent
+ self.agent.create("queue", "q")
+ agentB.create("queue", "q")
+ self.agent.create("domain", "BrokerB", {"url":brokerB.host_port(), "sasl_mechanisms":"NONE"})
+ self.agent.create("outgoing", "Link1", {"domain":"BrokerB","source":"q","target":"q"})
+ #send to brokerA, receive from brokerB
+ self.send_and_receive(recv_config=Config(brokerB))
+
+ def test_relay(self):
+ brokerB = self.amqp_broker()
+ agentB = brokerB.agent
+ agentB.create("queue", "q")
+ self.agent.create("domain", "BrokerB", {"url":brokerB.host_port(), "sasl_mechanisms":"NONE"})
+ #send to q on broker B through brokerA
+ self.send_and_receive(send_config=Config(self.broker, address="q@BrokerB"), recv_config=Config(brokerB))
+
+ def test_reconnect(self):
+ receiver_cmd = ["qpid-receive",
+ "--broker", self.broker.host_port(),
+ "--address=amq.fanout",
+ "--connection-options={protocol:amqp1.0, reconnect:True,container_id:receiver}",
+ "--timeout=10", "--print-content=true", "--print-headers=false"
+ ]
+ receiver = self.popen(receiver_cmd, stdout=PIPE)
+
+ sender_cmd = ["qpid-send",
+ "--broker", self.broker.host_port(),
+ "--address=amq.fanout",
+ "--connection-options={protocol:amqp1.0,reconnect:True,container_id:sender}",
+ "--content-stdin", "--send-eos=1"
+ ]
+ sender = self.popen(sender_cmd, stdin=PIPE)
+ sender._set_cloexec_flag(sender.stdin) #required for older python, see http://bugs.python.org/issue4112
+
+
+ batch1 = ["message-%s" % (i+1) for i in range(10000)]
+ for m in batch1:
+ sender.stdin.write(m + "\n")
+ sender.stdin.flush()
+
+ self.broker.kill()
+ self.broker = self.amqp_broker(port_holder=self.port_holder)
+
+ batch2 = ["message-%s" % (i+1) for i in range(10000, 20000)]
+ for m in batch2:
+ sender.stdin.write(m + "\n")
+ sender.stdin.flush()
+
+ sender.stdin.close()
+
+ last = None
+ m = receiver.stdout.readline().rstrip()
+ while len(m):
+ last = m
+ m = receiver.stdout.readline().rstrip()
+ assert last == "message-20000", (last)
+
+ """ Create and return a broker with AMQP 1.0 support """
+ def amqp_broker(self):
+ assert BrokerTest.amqp_lib, "Cannot locate AMQP 1.0 plug-in"
+ self.port_holder = HaPort(self) #reserve port
+ args = ["--load-module", BrokerTest.amqp_lib,
+ "--socket-fd=%s" % self.port_holder.fileno,
+ "--listen-disable=tcp",
+ "--log-enable=trace+:Protocol",
+ "--log-enable=info+"]
+ return BrokerTest.broker(self, args, port=self.port_holder.port)
+
+ def amqp_broker(self, port_holder=None):
+ assert BrokerTest.amqp_lib, "Cannot locate AMQP 1.0 plug-in"
+ if port_holder:
+ args = ["--load-module", BrokerTest.amqp_lib,
+ "--socket-fd=%s" % port_holder.fileno,
+ "--listen-disable=tcp",
+ "--log-enable=trace+:Protocol",
+ "--log-enable=info+"]
+ return BrokerTest.broker(self, args, port=port_holder.port)
+ else:
+ args = ["--load-module", BrokerTest.amqp_lib,
+ "--log-enable=trace+:Protocol",
+ "--log-enable=info+"]
+ return BrokerTest.broker(self, args)
+
+
+if __name__ == "__main__":
+ shutil.rmtree("brokertest.tmp", True)
+ os.execvp("qpid-python-test",
+ ["qpid-python-test", "-m", "interlink_tests"] + sys.argv[1:])
diff --git a/qpid/cpp/src/tests/interop_tests.py b/qpid/cpp/src/tests/interop_tests.py
new file mode 100755
index 0000000000..f76b9f634b
--- /dev/null
+++ b/qpid/cpp/src/tests/interop_tests.py
@@ -0,0 +1,220 @@
+#!/usr/bin/env python
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+"""
+A set of tests that can be run against a foreign AMQP 1.0 broker.
+
+RUNNING WITH A FOREIGN BROKER:
+
+1. Start the broker
+2. Create persistent queues named: interop-a interop-b interop-q tx-1 tx-2
+3. Export the environment variable QPID_INTEROP_URL with the URL to connect to your broker
+ in the form [user[:password]@]host[:port]
+4. From the build directory run this test:
+ ctest -VV -R interop_tests
+
+If QPID_INTEROP_URL is not set, a qpidd broker will be started for the test.
+"""
+
+import os, sys, shutil, subprocess
+import qpid_messaging as qm
+from brokertest import *
+
+URL='QPID_INTEROP_URL'
+
+class InteropTest(BrokerTest):
+
+ def setUp(self):
+ super(InteropTest, self).setUp()
+ self.url = os.environ[URL]
+ self.connect_opts = ['--broker', self.url, '--connection-options', '{protocol:amqp1.0}']
+
+ def connect(self, **kwargs):
+ """Python connection to interop URL"""
+ c = qm.Connection.establish(self.url, protocol='amqp1.0', **kwargs)
+ self.teardown_add(c)
+ return c
+
+ def drain(self, queue, connection=None):
+ """
+ Drain a queue to make sure it is empty. Throw away the messages.
+ """
+ c = connection or self.connect()
+ r = c.session().receiver(queue)
+ try:
+ while True:
+ r.fetch(timeout=0)
+ r.session.acknowledge()
+ except qm.Empty:
+ pass
+ r.close()
+
+ def clear_queue(self, queue, connection=None, properties=None, durable=False):
+ """
+ Make empty queue, prefix with self.id(). Create if needed, drain if needed
+ @return queue name.
+ """
+ queue = "interop-%s" % queue
+ c = connection or self.connect()
+ props = {'create':'always'}
+ if durable: props['node'] = {'durable':True}
+ if properties: props.update(properties)
+ self.drain("%s;%s" % (queue, props), c)
+ return queue
+
+
+class SimpleTest(InteropTest):
+ """Simple test to check the broker is responding."""
+
+ def test_send_receive_python(self):
+ c = self.connect()
+ q = self.clear_queue('q', c)
+ s = c.session()
+ s.sender(q).send('foo')
+ self.assertEqual('foo', s.receiver(q).fetch().content)
+
+ def test_send_receive_cpp(self):
+ q = self.clear_queue('q')
+ args = ['-b', self.url, '-a', q]
+ self.check_output(['qpid-send', '--content-string=cpp_foo'] + args)
+ self.assertEqual('cpp_foo', self.check_output(['qpid-receive'] + args).strip())
+
+
+class PythonTxTest(InteropTest):
+
+ def tx_simple_setup(self):
+ """Start a transaction, remove messages from queue a, add messages to queue b"""
+ c = self.connect()
+ qa, qb = self.clear_queue('a', c, durable=True), self.clear_queue('b', c, durable=True)
+
+ # Send messages to a, no transaction.
+ sa = c.session().sender(qa+";{create:always,node:{durable:true}}")
+ tx_msgs = ['x', 'y', 'z']
+ for m in tx_msgs: sa.send(qm.Message(content=m, durable=True))
+
+ # Receive messages from a, in transaction.
+ tx = c.session(transactional=True)
+ txr = tx.receiver(qa)
+ self.assertEqual(tx_msgs, [txr.fetch(1).content for i in xrange(3)])
+ tx.acknowledge()
+
+ # Send messages to b, transactional, mixed with non-transactional.
+ sb = c.session().sender(qb+";{create:always,node:{durable:true}}")
+ txs = tx.sender(qb)
+ msgs = [str(i) for i in xrange(3)]
+ for tx_m, m in zip(tx_msgs, msgs):
+ txs.send(tx_m);
+ sb.send(m)
+ tx.sync()
+ return tx, qa, qb
+
+ def test_tx_simple_commit(self):
+ tx, qa, qb = self.tx_simple_setup()
+ s = self.connect().session()
+ assert_browse(s, qa, [])
+ assert_browse(s, qb, ['0', '1', '2'])
+ tx.commit()
+ assert_browse(s, qa, [])
+ assert_browse(s, qb, ['0', '1', '2', 'x', 'y', 'z'])
+
+ def test_tx_simple_rollback(self):
+ tx, qa, qb = self.tx_simple_setup()
+ s = self.connect().session()
+ assert_browse(s, qa, [])
+ assert_browse(s, qb, ['0', '1', '2'])
+ tx.rollback()
+ assert_browse(s, qa, ['x', 'y', 'z'])
+ assert_browse(s, qb, ['0', '1', '2'])
+
+ def test_tx_sequence(self):
+ tx = self.connect().session(transactional=True)
+ notx = self.connect().session()
+ q = self.clear_queue('q', tx.connection, durable=True)
+ s = tx.sender(q)
+ r = tx.receiver(q)
+ s.send('a')
+ tx.commit()
+ assert_browse(notx, q, ['a'])
+ s.send('b')
+ tx.commit()
+ assert_browse(notx, q, ['a', 'b'])
+ self.assertEqual('a', r.fetch().content)
+ tx.acknowledge();
+ tx.commit()
+ assert_browse(notx, q, ['b'])
+ s.send('z')
+ tx.rollback()
+ assert_browse(notx, q, ['b'])
+ self.assertEqual('b', r.fetch().content)
+ tx.acknowledge();
+ tx.rollback()
+ assert_browse(notx, q, ['b'])
+
+
+class CppTxTest(InteropTest):
+
+ def test_txtest2(self):
+ self.popen(["qpid-txtest2"] + self.connect_opts).assert_exit_ok()
+
+ def test_send_receive(self):
+ q = self.clear_queue('q', durable=True)
+ sender = self.popen(["qpid-send",
+ "--address", q,
+ "--messages=100",
+ "--tx=10",
+ "--durable=yes"] + self.connect_opts)
+ receiver = self.popen(["qpid-receive",
+ "--address", q,
+ "--messages=90",
+ "--timeout=10",
+ "--tx=10"] + self.connect_opts)
+ sender.assert_exit_ok()
+ receiver.assert_exit_ok()
+ expect = [long(i) for i in range(91, 101)]
+ sn = lambda m: m.properties["sn"]
+ assert_browse(self.connect().session(), q, expect, transform=sn)
+
+
+if __name__ == "__main__":
+ if not BrokerTest.amqp_tx_supported:
+ BrokerTest.amqp_tx_warning()
+ print "Skipping interop_tests"
+ sys.exit(0)
+ outdir = "interop_tests.tmp"
+ shutil.rmtree(outdir, True)
+ cmd = ["qpid-python-test", "-m", "interop_tests", "-DOUTDIR=%s"%outdir] + sys.argv[1:]
+ if "QPID_PORT" in os.environ: del os.environ["QPID_PORT"]
+ if os.environ.get(URL):
+ os.execvp(cmd[0], cmd)
+ else:
+ dir = os.getcwd()
+ class StartBroker(BrokerTest):
+ def start_qpidd(self): pass
+ test = StartBroker('start_qpidd')
+ class Config:
+ def __init__(self):
+ self.defines = { 'OUTDIR': outdir }
+ test.configure(Config())
+ test.setUp()
+ os.environ[URL] = test.broker().host_port()
+ os.chdir(dir)
+ p = subprocess.Popen(cmd)
+ status = p.wait()
+ test.tearDown()
+ sys.exit(status)
diff --git a/qpid/cpp/src/tests/ipv6_test b/qpid/cpp/src/tests/ipv6_test
new file mode 100755
index 0000000000..4ac5f95fba
--- /dev/null
+++ b/qpid/cpp/src/tests/ipv6_test
@@ -0,0 +1,120 @@
+#!/usr/bin/env 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.
+#
+
+# Check whether we have any globally configured IPv6 addresses
+# - if not then we can't run the tests because ipv6 lookups won't
+# work within the qpid code. This is a deliberate feature to avoid
+# getting addresses that can't be routed by the machine.
+
+if ip -f inet6 -o addr | cut -f 9 -s -d' ' | grep global > /dev/null ; then
+ echo "IPv6 addresses configured continuing"
+else
+ echo "No global IPv6 addresses configured - skipping test"
+ exit 0
+fi
+
+
+# Run a simple test over IPv6
+source $QPID_TEST_COMMON
+
+CONFIG=$(dirname $0)/config.null
+TEST_HOSTNAME=::1
+COUNT=10
+
+trap cleanup EXIT
+
+error() { echo $*; exit 1; }
+
+# Don't need --no-module-dir or --no-data-dir as they are set as env vars in test_env.sh
+COMMON_OPTS="--interface [::1] --daemon --auth no --config $CONFIG"
+
+# Record all broker ports started
+unset PORTS
+declare -a PORTS
+
+# Start new brokers:
+# $1 must be integer
+# $2 = extra opts
+# Append used ports to PORTS variable
+start_brokers() {
+ local -a ports
+ for (( i=0; $i<$1; i++)) do
+ ports[$i]=$($QPIDD_EXEC --port 0 $COMMON_OPTS $2)
+ done
+ PORTS=( ${PORTS[@]} ${ports[@]} )
+}
+
+stop_brokers() {
+ for port in "${PORTS[@]}";
+ do
+ $QPIDD_EXEC -qp $port
+ done
+ PORTS=()
+}
+
+cleanup() {
+ stop_brokers
+}
+
+start_brokers 1
+PORT=${PORTS[0]}
+echo "Started IPv6 smoke perftest on broker port $PORT"
+
+## Test connection via connection settings
+./qpid-perftest --count ${COUNT} --port ${PORT} -b $TEST_HOSTNAME --summary
+
+## Test connection with a URL
+URL="amqp:[$TEST_HOSTNAME]:$PORT"
+
+./qpid-send -b $URL --content-string=hello -a "foo;{create:always}"
+MSG=`./qpid-receive -b $URL -a "foo;{create:always}" --messages 1`
+test "$MSG" = "hello" || { echo "receive failed '$MSG' != 'hello'"; exit 1; }
+
+stop_brokers
+
+# Federation smoke test follows
+
+# Start 2 brokers
+
+# In a distribution, the python tools will be absent.
+ensure_python_tests
+
+start_brokers 2
+echo "Started Federated brokers on ports ${PORTS[*]}"
+# Make broker urls
+BROKER0="[::1]:${PORTS[0]}"
+BROKER1="[::1]:${PORTS[1]}"
+TEST_QUEUE=ipv6-fed-test
+
+$QPID_CONFIG_EXEC -b $BROKER0 add queue $TEST_QUEUE
+$QPID_CONFIG_EXEC -b $BROKER1 add queue $TEST_QUEUE
+$QPID_ROUTE_EXEC dynamic add $BROKER1 $BROKER0 amq.direct
+$QPID_CONFIG_EXEC -b $BROKER1 bind amq.direct $TEST_QUEUE $TEST_QUEUE
+$QPID_ROUTE_EXEC route map $BROKER1
+
+./datagen --count 100 | tee rdata-in |
+ ./qpid-send -b amqp:$BROKER0 -a amq.direct/$TEST_QUEUE --content-stdin
+./qpid-receive -b amqp:$BROKER1 -a $TEST_QUEUE --print-content yes -m 0 > rdata-out
+
+cmp rdata-in rdata-out || { echo "Federated data over IPv6 does not compare"; exit 1; }
+
+stop_brokers
+rm rdata-in rdata-out
diff --git a/qpid/cpp/src/tests/legacystore/.valgrind.supp b/qpid/cpp/src/tests/legacystore/.valgrind.supp
new file mode 100644
index 0000000000..5c1c5377bf
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/.valgrind.supp
@@ -0,0 +1,35 @@
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:_Znwm
+ fun:_ZNSs4_Rep9_S_createEmmRKSaIcE
+ fun:_ZNSs12_S_constructIPKcEEPcT_S3_RKSaIcESt20forward_iterator_tag
+ fun:_ZNSsC1EPKcRKSaIcE
+}
+
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:_Znwm
+ fun:_ZNSs4_Rep9_S_createEmmRKSaIcE
+ fun:_ZNSs4_Rep8_M_cloneERKSaIcEm
+ fun:_ZNSs7reserveEm
+}
+
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:_Znwm
+ fun:_ZNSs4_Rep9_S_createEmmRKSaIcE
+ fun:_ZNSs9_M_mutateEmmm
+ fun:_ZNSs15_M_replace_safeEmmPKcm
+}
+
+{
+ <insert_a_suppression_name_here>
+ Memcheck:Leak
+ fun:_Znwm
+ fun:_ZNSs4_Rep9_S_createEmmRKSaIcE
+ fun:_ZNSsC1IPcEET_S1_RKSaIcE
+}
+
diff --git a/qpid/cpp/src/tests/legacystore/.valgrindrc b/qpid/cpp/src/tests/legacystore/.valgrindrc
new file mode 100644
index 0000000000..4aba7661de
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/.valgrindrc
@@ -0,0 +1,7 @@
+--gen-suppressions=all
+--leak-check=full
+--demangle=yes
+--suppressions=.valgrind.supp
+--num-callers=25
+--trace-children=yes
+
diff --git a/qpid/cpp/src/tests/legacystore/CMakeLists.txt b/qpid/cpp/src/tests/legacystore/CMakeLists.txt
new file mode 100644
index 0000000000..5527f23255
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/CMakeLists.txt
@@ -0,0 +1,133 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+if(BUILD_LEGACYSTORE AND BUILD_TESTING)
+
+message(STATUS "Building legacystore tests")
+
+# If we're linking Boost for DLLs, turn that on for the tests too.
+if (QPID_LINK_BOOST_DYNAMIC)
+ add_definitions(-DBOOST_TEST_DYN_LINK)
+endif (QPID_LINK_BOOST_DYNAMIC)
+
+include_directories( ${CMAKE_CURRENT_SOURCE_DIR} )
+
+set(test_wrap ${shell} ${CMAKE_SOURCE_DIR}/src/tests/run_test${test_script_suffix} -buildDir=${CMAKE_BINARY_DIR})
+
+if (BUILD_TESTING_UNITTESTS)
+
+# Like this to work with cmake 2.4 on Unix
+set (qpid_test_boost_libs
+ ${Boost_UNIT_TEST_FRAMEWORK_LIBRARY} ${Boost_SYSTEM_LIBRARY})
+
+# Journal tests
+MACRO (define_journal_test mainSourceFile)
+if ("${ARGV1}" STREQUAL "LONG")
+ set (testname "journal_long_${mainSourceFile}")
+else ()
+ set (testname "journal_${mainSourceFile}")
+endif ()
+add_executable (${testname}
+ jrnl/${mainSourceFile}
+ unit_test
+ ${platform_test_additions})
+target_link_libraries (${testname}
+ ${qpid_test_boost_libs}
+ ${clock_gettime_LIB} legacystore_shared)
+if ("${ARGV1}" STREQUAL "LONG")
+ set_target_properties(${testname} PROPERTIES COMPILE_DEFINITIONS LONG_TEST)
+endif ()
+remember_location(${testname})
+add_test (${testname} ${test_wrap} -boostTest -- ${${testname}_LOCATION})
+unset (testname)
+ENDMACRO (define_journal_test)
+
+define_journal_test (_ut_time_ns)
+define_journal_test (_ut_jexception)
+define_journal_test (_ut_jerrno)
+define_journal_test (_ut_rec_hdr)
+define_journal_test (_ut_jinf)
+define_journal_test (_ut_jdir)
+define_journal_test (_ut_enq_map)
+define_journal_test (_ut_txn_map)
+define_journal_test (_ut_lpmgr)
+define_journal_test (_st_basic)
+define_journal_test (_st_basic_txn)
+define_journal_test (_st_read)
+define_journal_test (_st_read_txn)
+define_journal_test (_st_auto_expand)
+define_journal_test (_ut_lpmgr LONG)
+define_journal_test (_st_basic LONG)
+define_journal_test (_st_read LONG)
+
+add_executable (jtt__ut
+ jrnl/jtt/_ut_data_src.cpp
+ jrnl/jtt/_ut_jrnl_init_params.cpp
+ jrnl/jtt/_ut_read_arg.cpp
+ jrnl/jtt/_ut_jrnl_instance.cpp
+ jrnl/jtt/_ut_test_case.cpp
+ jrnl/jtt/_ut_test_case_result.cpp
+ jrnl/jtt/_ut_test_case_result_agregation.cpp
+ jrnl/jtt/_ut_test_case_set.cpp
+ jrnl/jtt/args.cpp
+ jrnl/jtt/data_src.cpp
+ jrnl/jtt/jrnl_init_params.cpp
+ jrnl/jtt/jrnl_instance.cpp
+ jrnl/jtt/read_arg.cpp
+ jrnl/jtt/test_case.cpp
+ jrnl/jtt/test_case_set.cpp
+ jrnl/jtt/test_case_result.cpp
+ jrnl/jtt/test_case_result_agregation.cpp
+ unit_test.cpp)
+
+target_link_libraries (jtt__ut
+ ${qpid_test_boost_libs}
+ ${Boost_PROGRAM_OPTIONS_LIBRARY}
+ ${clock_gettime_LIB} legacystore_shared)
+
+add_test(journal_jtt_ut ${test_wrap} -boostTest -workingDir=${CMAKE_CURRENT_SOURCE_DIR}/jrnl/jtt -- ${CMAKE_CURRENT_BINARY_DIR}/jtt__ut)
+
+endif (BUILD_TESTING_UNITTESTS)
+
+#
+# Other test programs
+#
+
+add_executable(jtt
+ jrnl/jtt/args.cpp
+ jrnl/jtt/data_src.cpp
+ jrnl/jtt/jrnl_init_params.cpp
+ jrnl/jtt/jrnl_instance.cpp
+ jrnl/jtt/main.cpp
+ jrnl/jtt/read_arg.cpp
+ jrnl/jtt/test_case.cpp
+ jrnl/jtt/test_case_result.cpp
+ jrnl/jtt/test_case_result_agregation.cpp
+ jrnl/jtt/test_case_set.cpp
+ jrnl/jtt/test_mgr.cpp)
+
+target_link_libraries (jtt
+ ${Boost_PROGRAM_OPTIONS_LIBRARY}
+ ${clock_gettime_LIB} legacystore_shared)
+
+add_test(journal_jtt ${CMAKE_CURRENT_BINARY_DIR}/jtt -c ${CMAKE_CURRENT_SOURCE_DIR}/jrnl/jtt/jtt.csv)
+
+add_test (legacystore_python_tests ${test_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/run_python_tests${test_script_suffix})
+
+endif (BUILD_LEGACYSTORE AND BUILD_TESTING)
diff --git a/qpid/cpp/src/tests/legacystore/MessageUtils.h b/qpid/cpp/src/tests/legacystore/MessageUtils.h
new file mode 100644
index 0000000000..cd23244293
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/MessageUtils.h
@@ -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.
+ *
+ */
+
+#include <qpid/broker/Message.h>
+#include <qpid/broker/Queue.h>
+#include <qpid/broker/amqp_0_10/MessageTransfer.h>
+#include <qpid/framing/AMQFrame.h>
+#include <qpid/framing/all_method_bodies.h>
+#include <qpid/framing/Uuid.h>
+
+using namespace qpid::broker;
+using namespace qpid::framing;
+
+struct MessageUtils
+{
+ static Message createMessage(const std::string& exchange, const std::string& routingKey,
+ const Uuid& messageId=Uuid(), const bool durable = false,
+ const uint64_t contentSize = 0, const std::string& correlationId = std::string())
+ {
+ boost::intrusive_ptr<qpid::broker::amqp_0_10::MessageTransfer> msg(new qpid::broker::amqp_0_10::MessageTransfer());
+
+ AMQFrame method(( MessageTransferBody(ProtocolVersion(), exchange, 0, 0)));
+ AMQFrame header((AMQHeaderBody()));
+
+ msg->getFrames().append(method);
+ msg->getFrames().append(header);
+ MessageProperties* props = msg->getFrames().getHeaders()->get<MessageProperties>(true);
+ props->setContentLength(contentSize);
+ props->setMessageId(messageId);
+ props->setCorrelationId(correlationId);
+ msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setRoutingKey(routingKey);
+ if (durable)
+ msg->getFrames().getHeaders()->get<DeliveryProperties>(true)->setDeliveryMode(PERSISTENT);
+ return Message(msg, msg);
+ }
+
+ static void addContent(Message msg, const std::string& data)
+ {
+ AMQFrame content((AMQContentBody(data)));
+ qpid::broker::amqp_0_10::MessageTransfer::get(msg).getFrames().append(content);
+ }
+
+ struct MessageRetriever : public Consumer
+ {
+ MessageRetriever(Queue& q) : Consumer("test", CONSUMER), queue(q) {};
+
+ bool deliver(const QueueCursor& c, const Message& m)
+ {
+ message = m;
+ cursor = c;
+ return true;
+ };
+ void notify() {}
+ void cancel() {}
+ void acknowledged(const DeliveryRecord&) {}
+ OwnershipToken* getSession() { return 0; }
+
+ const Queue& queue;
+ Message message;
+ QueueCursor cursor;
+ };
+
+ static Message get(Queue& queue, QueueCursor* cursor = 0)
+ {
+ boost::shared_ptr<MessageRetriever> consumer(new MessageRetriever(queue));
+ if (!queue.dispatch(consumer))throw qpid::Exception("No message found!");
+ if (cursor) *cursor = consumer->cursor;
+ return consumer->message;
+ }
+
+ static Uuid getMessageId(const Message& message)
+ {
+ return qpid::broker::amqp_0_10::MessageTransfer::get(message).getProperties<MessageProperties>()->getMessageId();
+ }
+
+ static std::string getCorrelationId(const Message& message)
+ {
+ return qpid::broker::amqp_0_10::MessageTransfer::get(message).getProperties<MessageProperties>()->getCorrelationId();
+ }
+
+ static void deliver(Message& msg, FrameHandler& h, uint16_t framesize)
+ {
+ qpid::broker::amqp_0_10::MessageTransfer::get(msg).sendHeader(h, framesize, false, 0, qpid::types::Variant::Map());
+ qpid::broker::amqp_0_10::MessageTransfer::get(msg).sendContent(h, framesize);
+ }
+
+};
diff --git a/qpid/cpp/src/tests/legacystore/TestFramework.cpp b/qpid/cpp/src/tests/legacystore/TestFramework.cpp
new file mode 100644
index 0000000000..2f7faf7682
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/TestFramework.cpp
@@ -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.
+ *
+ */
+
+// Defines broker to be used by tests
+
+#include "unit_test.h"
+#include "TestFramework.h"
+#include "qpid/broker/Broker.h"
+
+#include <iostream>
+
+//BOOST_GLOBAL_FIXTURE( testBroker )
diff --git a/qpid/cpp/src/tests/legacystore/TestFramework.h b/qpid/cpp/src/tests/legacystore/TestFramework.h
new file mode 100644
index 0000000000..f3066db602
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/TestFramework.h
@@ -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.
+ *
+ */
+
+// Defines broker to be used by tests
+
+#include "unit_test.h"
+
+#include <qpid/broker/Broker.h>
+
+namespace {
+ // test broker
+ qpid::broker::Broker::Options opts;
+ qpid::broker::Broker br(opts);
+/*
+ struct testBroker {
+ testBroker() {}
+ ~testBroker() {}
+ };*/
+}
diff --git a/qpid/cpp/src/tests/legacystore/clean.sh b/qpid/cpp/src/tests/legacystore/clean.sh
new file mode 100644
index 0000000000..838f246232
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/clean.sh
@@ -0,0 +1,32 @@
+#!/usr/bin/env 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.
+#
+
+# This script cleans up any previous database and journal files, and should
+# be run prior to the store system tests, as these are prone to crashing or
+# hanging under some circumstances if the database is old or inconsistent.
+
+if [ -d ${TMP_DATA_DIR} ]; then
+ rm -rf ${TMP_DATA_DIR}
+fi
+if [ -d ${TMP_PYTHON_TEST_DIR} ]; then
+ rm -rf ${TMP_PYTHON_TEST_DIR}
+fi
+rm -f ${abs_srcdir}/*.vglog*
diff --git a/qpid/cpp/src/tests/legacystore/federation/Makefile.am b/qpid/cpp/src/tests/legacystore/federation/Makefile.am
new file mode 100644
index 0000000000..c48e861a65
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/federation/Makefile.am
@@ -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.
+#
+
+
+abs_srcdir=@abs_srcdir@
+
+TMP_DATA_DIR=$(abs_srcdir)/../tmp_data_dir
+
+TESTS = \
+ run_federation_sys_tests
+
+LONG_TESTS = \
+ run_long_federation_sys_tests
+
+EXTRA_DIST = \
+ federation_tests_env.sh \
+ run_federation_sys_tests \
+ run_long_federation_sys_tests
+
+TESTS_ENVIRONMENT = \
+ QPID_DIR=$(QPID_DIR) \
+ QPID_BLD=$(QPID_BLD) \
+ TMP_DATA_DIR=$(TMP_DATA_DIR) \
+ abs_srcdir=$(abs_srcdir)
+
+check-long: all
+ $(MAKE) check TESTS="$(LONG_TESTS)" SUBDIRS=.
+
+# END
+
diff --git a/qpid/cpp/src/tests/legacystore/federation/federation_tests_env.sh b/qpid/cpp/src/tests/legacystore/federation/federation_tests_env.sh
new file mode 100755
index 0000000000..bf75056444
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/federation/federation_tests_env.sh
@@ -0,0 +1,313 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+
+# --- Function definitions ---
+
+func_check_required_env ()
+#-------------------------
+# Check that EITHER:
+# QPID_DIR is set (for running against svn QPID)
+# OR
+# QPID_PREFIX is set (for running against installed QPID
+# Will exit with error code 1 if neither of these is defined.
+# Params: None
+# Returns: 0 if env vars ok, 1 otherwise
+{
+ if test -z "${QPID_DIR}" -a -z "${QPID_PREFIX}"; then
+ # Try to find qpidd in the normal installed location
+ if test -x /usr/sbin/qpidd; then
+ QPID_PREFIX=/usr
+ else
+ echo "ERROR: Could not find installed Qpid"
+ echo "Either of the following must be set in the environment for this script to run:"
+ echo " QPID_DIR for running against a Qpid svn build"
+ echo " QPID_PREFIX for running against an installed Qpid"
+ return 1
+ fi
+ fi
+ return 0
+}
+
+
+func_check_clustering ()
+#-----------------------
+# Check openAIS/corosync is running and user has correct privileges
+# Params: None
+# Returns: 0 if openAIS/corosync is running, 1 otherwise
+# Sets env var COROSYNC to 1 if corosync is running, not set otherwise
+{
+ # Check either aisexec or corosync is running as root
+ cluster_prog=`ps -u root | grep 'aisexec\|corosync'`
+ test -n "$cluster_prog" || NODAEMON="Neither aisexec nor corosync is running as root"
+ if test -z "$NODAEMON"; then
+ # Test for corosync running
+ echo $cluster_prog | grep "aisexec" > /dev/null || COROSYNC=1
+ if test -n "$COROSYNC"; then
+ # Corosync auth test
+ user=`whoami`
+ ls /etc/corosync/uidgid.d | grep $user > /dev/null || NOAUTH="You are not authorized to use corosync."
+ else
+ # OpenAis auth test
+ id -nG | grep '\<ais\>' >/dev/null || NOAUTH="You are not a member of the ais group."
+ fi
+ fi
+
+ if test -n "$NODAEMON" -o -n "$NOAUTH"; then
+ cat <<EOF
+
+ ========== WARNING: NOT RUNNING CLUSTER TESTS ============
+
+ Cluster tests will not be run because:
+
+ $NODAEMON
+ $NOAUTH
+
+ ==========================================================
+
+EOF
+ return 1
+ fi
+ CLUSTERING_ENABLED=1
+ return 0
+}
+
+
+func_check_qpid_python ()
+#------------------------
+# Check that Qpid python environment is ok
+# Params: None
+# Returns: 0 if Python environment is ok; 1 otherwise
+{
+ if ! python -c "import qpid" ; then
+ cat <<EOF
+
+ =========== WARNING: PYTHON TESTS DISABLED ==============
+
+ Unable to load python qpid module - skipping python tests.
+
+ PYTHONPATH=${PYTHONPATH}
+
+ ===========================================================
+
+EOF
+ return 1
+ fi
+ return 0
+}
+
+func_set_python_env()
+#--------------------
+# Set up the python path
+# Params: None
+# Returns: Nothing
+{
+ if test "${QPID_DIR}" -a -d "${QPID_DIR}" ; then
+ QPID_PYTHON=${QPID_DIR}/python
+ QPID_TOOLS=${QPID_DIR}/tools/src/py
+ QMF_LIB=${QPID_DIR}/extras/qmf/src/py
+ export PYTHONPATH=${QPID_PYTHON}:${QMF_LIB}:${QPID_TOOLS}:$PYTHONPATH
+ fi
+}
+
+func_set_env ()
+#--------------
+# Set up the environment based on value of ${QPID_DIR}: if ${QPID_DIR} exists, assume a svn checkout,
+# otherwise set up for an installed or prefix test.
+# Params: None
+# Returns: Nothing
+{
+ if test "${QPID_DIR}" -a -d "${QPID_DIR}" ; then
+ # QPID_DIR is defined for source tree builds by the --with-qpid-checkout configure option.
+ # QPID_BLD is defined as the build directory, either $QPID_DIR/cpp or separately specified with
+ # the --with-qpid-build option for VPATH builds.
+
+ # Check QPID_BLD is also set
+ if test -z ${QPID_BLD}; then
+ QPID_BLD="${QPID_DIR}/cpp"
+ fi
+ source $QPID_BLD/src/tests/test_env.sh
+# CPP_CLUSTER_EXEC="${QPID_BLD}/src/tests/cluster_test"
+# PYTHON_CLUSTER_EXEC="${QPID_DIR}/cpp/src/tests/$PYTHON_TESTNAME"
+ FEDERATION_SYS_TESTS_FAIL="${QPID_DIR}/cpp/src/tests/federation_sys_tests.fail"
+ if test -z ${STORE_LIB}; then
+ STORE_LIB="../../lib/.libs/msgstore.so"
+ fi
+# export STORE_ENABLE=1
+ else
+ # Set up the environment based on value of ${QPID_PREFIX} for testing against an installed qpid
+ # Alternatively, make sure ${QPID_BIN_DIR}, ${QPID_SBIN_DIR}, ${QPID_LIB_DIR} and ${QPID_LIBEXEC_DIR} are set for
+ # the installed location.
+ if test "${QPID_PREFIX}" -a -d "${QPID_PREFIX}" ; then
+ QPID_BIN_DIR=${QPID_PREFIX}/bin
+ QPID_SBIN_DIR=${QPID_PREFIX}/sbin
+ QPID_LIB_DIR=${QPID_PREFIX}/lib
+ QPID_LIBEXEC_DIR=${QPID_PREFIX}/libexec
+ export PATH="$QPID_BIN_DIR:$QPID_SBIN_DIR:$QPID_LIBEXEC_DIR/qpid/tests:$PATH"
+ fi
+
+ # These four env vars must be set prior to calling this script
+ func_checkpaths QPID_BIN_DIR QPID_SBIN_DIR QPID_LIB_DIR QPID_LIBEXEC_DIR
+
+ # Paths and dirs
+ export PYTHON_DIR="${QPID_BIN_DIR}"
+ export PYTHONPATH="${QPID_LIB_DIR}/python:${QPID_LIBEXEC_DIR}/qpid/tests:${QPID_LIB_DIR}/python2.4:${QPID_LIB_DIR}/python2.4/site-packages:${PYTHONPATH}"
+ # Libraries
+ export CLUSTER_LIB="${QPID_LIB_DIR}/qpid/daemon/cluster.so"
+ export ACL_LIB="${QPID_LIB_DIR}/qpid/daemon/acl.so"
+ export TEST_STORE_LIB="${QPID_LIB_DIR}/qpid/tests/test_store.so"
+
+ # Executables
+# CPP_CLUSTER_EXEC="${QPID_LIBEXEC_DIR}/qpid/tests/cluster_test"
+# PYTHON_CLUSTER_EXEC="${QPID_LIBEXEC_DIR}/qpid/tests/$PYTHON_TESTNAME"
+ export QPIDD_EXEC="${QPID_SBIN_DIR}/qpidd"
+ export QPID_CONFIG_EXEC="${QPID_BIN_DIR}/qpid-config"
+ export QPID_ROUTE_EXEC="${QPID_BIN_DIR}/qpid-route"
+ export QPID_CLUSTER_EXEC="${QPID_BIN_DIR}/qpid-cluster"
+# export RECEIVER_EXEC="${QPID_LIBEXEC_DIR}/qpid/tests/receiver"
+# export SENDER_EXEC="${QPID_LIBEXEC_DIR}/qpid/tests/sender"
+ export QPID_PYTHON_TEST="${QPID_BIN_DIR}/qpid-python-test"
+
+ # Data
+ FEDERATION_SYS_TESTS_FAIL="${QPID_LIBEXEC_DIR}/qpid/tests/federation_sys_tests.fail"
+ fi
+}
+
+
+func_mk_data_dir ()
+#------------------
+# Create a data dir at ${TMP_DATA_DIR} if not present, clear it otherwise.
+# Set TMP_DATA_DIR if it is not set.
+# Params: None
+# Returns: Nothing
+{
+ if test -z "${TMP_DATA_DIR}"; then
+ TMP_DATA_DIR=/tmp/federation_sys_tests
+ echo "TMP_DATA_DIR not set; using ${TMP_DATA_DIR}"
+ fi
+
+ # Delete old cluster test dirs if they exist
+ if test -d "${TMP_DATA_DIR}" ; then
+ rm -rf "${TMP_DATA_DIR}/cluster"
+ fi
+ mkdir -p "${TMP_DATA_DIR}/cluster"
+ export TMP_DATA_DIR
+}
+
+
+func_checkvar ()
+#---------------
+# Check that an environment var is set (ie non-zero length)
+# Params: $1 - env var to be checked
+# Returns: 0 = env var is set (ie non-zero length)
+# 1 = env var is not set
+{
+ local loc_VAR=$1
+ if test -z ${!loc_VAR}; then
+ echo "WARNING: environment variable ${loc_VAR} not set."
+ return 1
+ fi
+ return 0
+}
+
+
+func_checkpaths ()
+#-----------------
+# Check a list of paths (each can contain ':'-separated sub-list) is set and valid (ie each path exists as a dir)
+# Params: $@ - List of path env vars to be checked
+# Returns: Nothing
+{
+ local loc_PATHS=$@
+ for path in ${loc_PATHS}; do
+ func_checkvar ${path}
+ if test $? == 0; then
+ local temp_IFS=${IFS}
+ IFS=":"
+ local pl=${!path}
+ for p in ${pl[@]}; do
+ if test ! -d ${p}; then
+ echo "WARNING: Directory ${p} in var ${path} not found."
+ fi
+ done
+ IFS=${temp_IFS}
+ fi
+ done
+}
+
+
+func_checklibs ()
+#----------------
+# Check that a list of libs is set and valid (ie each lib exists as an executable file)
+# Params: $@ - List of lib values to be checked
+# Returns: Nothing
+{
+ local loc_LIBS=$@
+ for lib in ${loc_LIBS[@]}; do
+ func_checkvar ${lib}
+ if test $? == 0; then
+ if test ! -x ${!lib}; then
+ echo "WARNING: Library ${lib}=${!lib} not found."
+ fi
+ fi
+ done
+}
+
+
+func_checkexecs ()
+#-----------------
+# Check that a list of executable is set and valid (ie each exec exists as an executable file)
+# Params: $@ - List of exec values to be checked
+# Returns: Nothing
+{
+ local loc_EXECS=$@
+ for exec in ${loc_EXECS[@]}; do
+ func_checkvar ${exec}
+ if test $? == 0; then
+ if test ! -x ${!exec}; then
+ echo "WARNING: Executable ${exec}=${!exec} not found or is not executable."
+ fi
+ fi
+ done
+}
+
+
+#--- Start of script ---
+
+func_set_python_env
+func_check_required_env || exit 1 # Cannot run, exit with error
+func_check_qpid_python || exit 1 # Cannot run, exit with error
+func_check_clustering # Warning
+
+PYTHON_TESTNAME=federation_sys.py
+func_set_env
+func_mk_data_dir
+
+# Check expected environment vars are set
+func_checkpaths PYTHON_DIR PYTHONPATH TMP_DATA_DIR
+func_checklibs CLUSTER_LIB STORE_LIB
+func_checkexecs QPIDD_EXEC QPID_CONFIG_EXEC QPID_ROUTE_EXEC QPID_PYTHON_TEST
+
+FAILING_PYTHON_TESTS="${abs_srcdir}/../failing_python_tests.txt"
+if test -z $1; then
+ FEDERATION_SYS_TEST="${QPID_PYTHON_TEST} -m cluster_tests -I ${FEDERATION_SYS_TESTS_FAIL}"
+else
+ FEDERATION_SYS_TEST="${QPID_PYTHON_TEST} -m cluster_tests -I ${FEDERATION_SYS_TESTS_FAIL} cluster_tests.LongTests.*"
+ LONG_TEST=1
+fi
+
diff --git a/qpid/cpp/src/tests/legacystore/federation/run_federation_sys_tests b/qpid/cpp/src/tests/legacystore/federation/run_federation_sys_tests
new file mode 100755
index 0000000000..776f009c05
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/federation/run_federation_sys_tests
@@ -0,0 +1,96 @@
+#!/usr/bin/env 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.
+#
+
+
+# Run the federation tests.
+source ${abs_srcdir}/federation_tests_env.sh
+
+MODULENAME=federation_sys
+
+# Test for long test
+if [[ "$1" == "LONG_TEST" ]]; then
+ USE_LONG_TEST=1
+ shift # get rid of this param so it is not treated as a test name
+fi
+
+trap stop_brokers INT TERM QUIT
+
+MODULES="--load-module ${STORE_LIB} --jfile-size 12 --num-jfiles 4"
+CLUSTER_MODULE="--load-module ${CLUSTER_LIB} "
+if [ -z ${USE_LONG_TEST} ]; then
+ SKIPTESTS="-i federation_sys.A_Long* -i federation_sys.B_Long* -i federation_sys.E_Long* -i federation_sys.F_Long*"
+fi
+if [ -z ${CLUSTERING_ENABLED} ]; then
+ SKIPTESTS="${SKIPTESTS} -i federation_sys.C_* -i federation_sys.D_* -i federation_sys.G_* -i federation_sys.H_*"
+elif [ -z ${USE_LONG_TEST} ]; then
+ SKIPTESTS="${SKIPTESTS} -i federation_sys.C_Long* -i federation_sys.D_Long* -i federation_sys.G_Long* -i federation_sys.H_Long*"
+fi
+
+start_brokers() {
+ clean_or_create_dir() {
+ if [ -n "$1" -a -d $1 ]; then
+ rm -rf $1/*
+ else
+ mkdir -p $1
+ fi
+ }
+ start_broker() {
+ clean_or_create_dir $1
+ ${QPIDD_EXEC} --daemon --port 0 --auth no --data-dir $1 $2 > qpidd.port
+ PORT=`cat qpidd.port`
+ eval "$3=${PORT}"
+ }
+ start_broker ${TMP_DATA_DIR}/local "${MODULES} --log-enable info+ --log-to-file ${TMP_DATA_DIR}/qpidd.log.local" LOCAL_PORT
+ start_broker ${TMP_DATA_DIR}/remote "${MODULES} --log-enable info+ --log-to-file ${TMP_DATA_DIR}/qpidd.log.remote" REMOTE_PORT
+ if [ -n "$CLUSTERING_ENABLED" ]; then
+ start_broker ${TMP_DATA_DIR}/cluster/c1.1 "${MODULES} ${CLUSTER_MODULE} --cluster-name test-cluster-1 --log-enable info+ --log-to-file ${TMP_DATA_DIR}/qpidd.log.cluster1.1" CLUSTER_C1_1
+ start_broker ${TMP_DATA_DIR}/cluster/c1.2 "${MODULES} ${CLUSTER_MODULE} --cluster-name test-cluster-1 --log-enable info+ --log-to-file ${TMP_DATA_DIR}/qpidd.log.cluster1.2" CLUSTER_C1_2
+ start_broker ${TMP_DATA_DIR}/cluster/c2.1 "${MODULES} ${CLUSTER_MODULE} --cluster-name test-cluster-2 --log-enable info+ --log-to-file ${TMP_DATA_DIR}/qpidd.log.cluster2.1" CLUSTER_C2_1
+ start_broker ${TMP_DATA_DIR}/cluster/c2.2 "${MODULES} ${CLUSTER_MODULE} --cluster-name test-cluster-2 --log-enable info+ --log-to-file ${TMP_DATA_DIR}/qpidd.log.cluster2.2" CLUSTER_C2_2
+ fi
+ rm qpidd.port
+}
+
+stop_brokers() {
+ ${QPIDD_EXEC} -q --port ${LOCAL_PORT}
+ ${QPIDD_EXEC} -q --port ${REMOTE_PORT}
+ if [ -n "${CLUSTERING_ENABLED}" ]; then
+ ${QPID_CLUSTER_EXEC} --all-stop --force localhost:${CLUSTER_C1_1}
+ ${QPID_CLUSTER_EXEC} --all-stop --force localhost:${CLUSTER_C2_1}
+ fi
+}
+
+if test -d ${PYTHON_DIR} ; then
+ start_brokers
+ if [ -z ${CLUSTERING_ENABLED} ]; then
+ echo "Running federation tests using brokers on local port ${LOCAL_PORT}, remote port ${REMOTE_PORT} (NOTE: clustering is DISABLED)"
+ else
+ echo "Running federation tests using brokers on local port ${LOCAL_PORT}, remote port ${REMOTE_PORT}, local cluster nodes ${CLUSTER_C1_1} ${CLUSTER_C1_2}, remote cluster nodes ${CLUSTER_C2_1} ${CLUSTER_C2_2}"
+ fi
+ if [ -z ${USE_LONG_TEST} ]; then
+ echo "NOTE: To run a full set of federation system tests, use \"make check-long\"."
+ fi
+ ${QPID_PYTHON_TEST} -m ${MODULENAME} ${SKIPTESTS} -b localhost:$REMOTE_PORT -Dlocal-port=$LOCAL_PORT -Dremote-port=$REMOTE_PORT -Dlocal-cluster-ports="$CLUSTER_C1_1 $CLUSTER_C1_2" -Dremote-cluster-ports="$CLUSTER_C2_1 $CLUSTER_C2_2" $@
+ RETCODE=$?
+ stop_brokers
+ if test x$RETCODE != x0; then
+ echo "FAIL federation tests"; exit 1;
+ fi
+fi
diff --git a/qpid/cpp/src/tests/legacystore/federation/run_long_federation_sys_tests b/qpid/cpp/src/tests/legacystore/federation/run_long_federation_sys_tests
new file mode 100755
index 0000000000..012c8d8f18
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/federation/run_long_federation_sys_tests
@@ -0,0 +1,24 @@
+#!/usr/bin/env 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.
+#
+
+
+# Run the federation system tests (long version).
+
+./run_federation_sys_tests LONG_TEST $@
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_st_auto_expand.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_st_auto_expand.cpp
new file mode 100644
index 0000000000..fb5c1f1742
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/_st_auto_expand.cpp
@@ -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.
+ *
+ */
+
+#include "../unit_test.h"
+#include <cmath>
+#include <iostream>
+#include "qpid/legacystore/jrnl/jcntl.h"
+
+using namespace boost::unit_test;
+using namespace mrg::journal;
+using namespace std;
+
+QPID_AUTO_TEST_SUITE(journal_auto_expand)
+
+const string test_filename("_st_auto_expand");
+
+#include "_st_helper_fns.h"
+
+// === Test suite ===
+
+QPID_AUTO_TEST_CASE(no_ae_threshold)
+{
+ string test_name = get_test_name(test_filename, "no_ae_threshold");
+ try
+ {
+ string msg;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_DEFAULT_JFILES, false, 0, DEFAULT_JFSIZE_SBLKS);
+ unsigned m;
+
+ // Fill journal to just below threshold
+ u_int32_t t = num_msgs_to_threshold(NUM_DEFAULT_JFILES,
+ DEFAULT_JFSIZE_SBLKS * JRNL_SBLK_SIZE, LARGE_MSG_REC_SIZE_DBLKS);
+ for (m=0; m<t; m++)
+ enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false);
+ // This enqueue should exceed the threshold
+ BOOST_CHECK_EQUAL(jc.get_enq_cnt(), t);
+ enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false, RHM_IORES_ENQCAPTHRESH);
+ BOOST_CHECK_EQUAL(jc.get_enq_cnt(), t);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(no_ae_threshold_dequeue_some)
+{
+ string test_name = get_test_name(test_filename, "no_ae_threshold_dequeue_some");
+ try
+ {
+ string msg;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_DEFAULT_JFILES, false, 0, DEFAULT_JFSIZE_SBLKS);
+ unsigned m;
+
+ // Fill journal to just below threshold
+ u_int32_t t = num_msgs_to_threshold(NUM_DEFAULT_JFILES,
+ DEFAULT_JFSIZE_SBLKS * JRNL_SBLK_SIZE, LARGE_MSG_REC_SIZE_DBLKS);
+ for (m=0; m<t; m++)
+ enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false);
+ // This enqueue should exceed the threshold
+ BOOST_CHECK_EQUAL(jc.get_enq_cnt(), t);
+ enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false, RHM_IORES_ENQCAPTHRESH);
+ BOOST_CHECK_EQUAL(jc.get_enq_cnt(), t);
+
+ // Dequeue 25 msgs
+ #define NUM_MSGS_DEQ 25
+ for (m=0; m<NUM_MSGS_DEQ; m++)
+ deq_msg(jc, m, m+t);
+ BOOST_CHECK_EQUAL(jc.get_enq_cnt(), u_int32_t(t-NUM_MSGS_DEQ));
+
+ // Check we can still enqueue and dequeue
+ for (m=t+NUM_MSGS_DEQ; m<t+NUM_MSGS_DEQ+NUM_MSGS; m++)
+ enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false);
+ for (m=t+NUM_MSGS_DEQ; m<t+NUM_MSGS_DEQ+NUM_MSGS; m++)
+ deq_msg(jc, m, m+NUM_MSGS_DEQ+NUM_MSGS);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(no_ae_threshold_dequeue_all)
+{
+ string test_name = get_test_name(test_filename, "no_ae_threshold_dequeue_all");
+ try
+ {
+ string msg;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_DEFAULT_JFILES, false, 0, DEFAULT_JFSIZE_SBLKS);
+ unsigned m;
+
+ // Fill journal to just below threshold
+ u_int32_t t = num_msgs_to_threshold(NUM_DEFAULT_JFILES,
+ DEFAULT_JFSIZE_SBLKS * JRNL_SBLK_SIZE, LARGE_MSG_REC_SIZE_DBLKS);
+ for (m=0; m<t; m++)
+ enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false);
+ // This enqueue should exceed the threshold
+ BOOST_CHECK_EQUAL(jc.get_enq_cnt(), t);
+ enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false, RHM_IORES_ENQCAPTHRESH);
+ BOOST_CHECK_EQUAL(jc.get_enq_cnt(), t);
+
+ // Dequeue all msgs
+ for (m=0; m<t; m++)
+ deq_msg(jc, m, m+t);
+ BOOST_CHECK_EQUAL(jc.get_enq_cnt(), u_int32_t(0));
+
+ // Check we can still enqueue and dequeue
+ for (m=2*t; m<2*t + NUM_MSGS; m++)
+ enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false);
+ for (m=2*t; m<2*t + NUM_MSGS; m++)
+ deq_msg(jc, m, m+2*t+NUM_MSGS);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_SUITE_END()
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_st_basic.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_st_basic.cpp
new file mode 100644
index 0000000000..4aa6d2e29f
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/_st_basic.cpp
@@ -0,0 +1,558 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "../unit_test.h"
+#include <cmath>
+#include <iostream>
+#include "qpid/legacystore/jrnl/jcntl.h"
+
+using namespace boost::unit_test;
+using namespace mrg::journal;
+using namespace std;
+
+QPID_AUTO_TEST_SUITE(journal_basic)
+
+const string test_filename("_st_basic");
+
+#include "_st_helper_fns.h"
+
+// === Test suite ===
+
+#ifndef LONG_TEST
+/*
+ * ==============================================
+ * NORMAL TESTS
+ * This section contains normal "make check" tests
+ * for building/packaging. These are built when
+ * LONG_TEST is _not_ defined.
+ * ==============================================
+ */
+
+QPID_AUTO_TEST_CASE(instantiation)
+{
+ string test_name = get_test_name(test_filename, "instantiation");
+ try
+ {
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ BOOST_CHECK_EQUAL(jc.is_ready(), false);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(initialization)
+{
+ string test_name = get_test_name(test_filename, "initialization");
+ try
+ {
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ BOOST_CHECK_EQUAL(jc.is_ready(), false);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ BOOST_CHECK_EQUAL(jc.is_ready(), true);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(enqueue_dequeue_block)
+{
+ string test_name = get_test_name(test_filename, "enqueue_dequeue_block");
+ try
+ {
+ string msg;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ for (int m=0; m<NUM_MSGS; m++)
+ BOOST_CHECK_EQUAL(enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false), u_int64_t(m));
+ for (int m=0; m<NUM_MSGS; m++)
+ deq_msg(jc, m, m+NUM_MSGS);
+
+ // Again...
+ for (int m=2*NUM_MSGS; m<3*NUM_MSGS; m++)
+ BOOST_CHECK_EQUAL(enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false), u_int64_t(m));
+ for (int m=2*NUM_MSGS; m<3*NUM_MSGS; m++)
+ deq_msg(jc, m, m+3*NUM_MSGS);
+
+ // Disjoint rids
+ for (int m=10*NUM_MSGS; m<11*NUM_MSGS; m++)
+ BOOST_CHECK_EQUAL(enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false), u_int64_t(m));
+ for (int m=10*NUM_MSGS; m<11*NUM_MSGS; m++)
+ deq_msg(jc, m, m+11*NUM_MSGS);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(enqueue_dequeue_interleaved)
+{
+ string test_name = get_test_name(test_filename, "enqueue_dequeue_interleaved");
+ try
+ {
+ string msg;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ for (int m=0; m<2*NUM_MSGS; m+=2)
+ {
+ BOOST_CHECK_EQUAL(enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false), u_int64_t(m));
+ deq_msg(jc, m, m+1);
+ }
+
+ // Again...
+ for (int m=2*NUM_MSGS; m<4*NUM_MSGS; m+=2)
+ {
+ BOOST_CHECK_EQUAL(enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false), u_int64_t(m));
+ deq_msg(jc, m, m+1);
+ }
+
+ // Disjoint rids
+ for (int m=10*NUM_MSGS; m<12*NUM_MSGS; m+=2)
+ {
+ BOOST_CHECK_EQUAL(enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false), u_int64_t(m));
+ deq_msg(jc, m, m+1);
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(enqueue_dequeue_interleaved_file_rollover)
+{
+ string test_name = get_test_name(test_filename, "enqueue_dequeue_interleaved_file_rollover");
+ try
+ {
+ string msg;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ unsigned n = num_msgs_to_full(NUM_TEST_JFILES, TEST_JFSIZE_SBLKS * JRNL_SBLK_SIZE,
+ 16*MSG_REC_SIZE_DBLKS, true);
+ for (unsigned m=0; m<3*2*n; m+=2) // overwrite files 3 times
+ {
+ enq_msg(jc, m, create_msg(msg, m, 16*MSG_SIZE), false);
+ deq_msg(jc, m, m+1);
+ }
+ jc.stop(true);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(empty_recover)
+{
+ string test_name = get_test_name(test_filename, "empty_recover");
+ try
+ {
+ {
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ BOOST_CHECK_EQUAL(jc.is_ready(), false);
+ BOOST_CHECK_EQUAL(jc.is_read_only(), false);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ BOOST_CHECK_EQUAL(jc.is_ready(), true);
+ BOOST_CHECK_EQUAL(jc.is_read_only(), false);
+ }
+ {
+ u_int64_t hrid;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ BOOST_CHECK_EQUAL(jc.is_ready(), false);
+ BOOST_CHECK_EQUAL(jc.is_read_only(), false);
+ jc.recover(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS, 0, hrid);
+ BOOST_CHECK_EQUAL(jc.is_ready(), true);
+ BOOST_CHECK_EQUAL(jc.is_read_only(), true);
+ BOOST_CHECK_EQUAL(hrid, u_int64_t(0));
+ }
+ {
+ u_int64_t hrid;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ BOOST_CHECK_EQUAL(jc.is_ready(), false);
+ BOOST_CHECK_EQUAL(jc.is_read_only(), false);
+ jc.recover(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS, 0, hrid);
+ BOOST_CHECK_EQUAL(jc.is_ready(), true);
+ BOOST_CHECK_EQUAL(jc.is_read_only(), true);
+ BOOST_CHECK_EQUAL(hrid, u_int64_t(0));
+ jc.recover_complete();
+ BOOST_CHECK_EQUAL(jc.is_ready(), true);
+ BOOST_CHECK_EQUAL(jc.is_read_only(), false);
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(enqueue_recover_dequeue_block)
+{
+ string test_name = get_test_name(test_filename, "enqueue_recover_dequeue_block");
+ try
+ {
+ {
+ string msg;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ for (int m=0; m<NUM_MSGS; m++)
+ enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false);
+ }
+ {
+ u_int64_t hrid;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.recover(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS, 0, hrid);
+ BOOST_CHECK_EQUAL(hrid, u_int64_t(NUM_MSGS - 1));
+ jc.recover_complete();
+ for (int m=0; m<NUM_MSGS; m++)
+ deq_msg(jc, m, m+NUM_MSGS);
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(enqueue_recover_dequeue_interleaved)
+{
+ string test_name = get_test_name(test_filename, "enqueue_recover_dequeue_interleaved");
+ try
+ {
+ string msg;
+ u_int64_t hrid;
+
+ for (int m=0; m<2*NUM_MSGS; m+=2)
+ {
+ {
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ if (m == 0)
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS); // First time only
+ else
+ {
+ jc.recover(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS, 0, hrid);
+ BOOST_CHECK_EQUAL(hrid, u_int64_t(m - 1));
+ jc.recover_complete();
+ }
+ enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false);
+ }
+ {
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.recover(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS, 0, hrid);
+ BOOST_CHECK_EQUAL(hrid, u_int64_t(m));
+ jc.recover_complete();
+ deq_msg(jc, m, m+1);
+ }
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(header_flags)
+{
+ string test_name = get_test_name(test_filename, "header_flags");
+ try
+ {
+ {
+ string msg;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ // Transient msgs - should not recover
+ for (int m=0; m<NUM_MSGS; m++)
+ enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), true);
+ // Persistent msgs
+ for (int m=NUM_MSGS; m<NUM_MSGS*2; m++)
+ enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false);
+ // Transient extern msgs - should not recover
+ for (int m=NUM_MSGS*2; m<NUM_MSGS*3; m++)
+ enq_extern_msg(jc, m, MSG_SIZE, true);
+ // Persistnet extern msgs
+ for (int m=NUM_MSGS*3; m<NUM_MSGS*4; m++)
+ enq_extern_msg(jc, m, MSG_SIZE, false);
+ }
+ {
+ string msg;
+ u_int64_t hrid;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.recover(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS, 0, hrid);
+ // Recover non-transient msgs
+ for (int m=NUM_MSGS; m<NUM_MSGS*2; m++)
+ {
+ string rmsg;
+ string xid;
+ bool transientFlag;
+ bool externalFlag;
+
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag);
+ BOOST_CHECK_MESSAGE(transientFlag == false, "Transient message recovered.");
+ BOOST_CHECK_MESSAGE(externalFlag == false, "External flag incorrect.");
+ BOOST_CHECK_MESSAGE(create_msg(msg, m, MSG_SIZE).compare(rmsg) == 0,
+ "Non-transient message corrupt during recover.");
+ }
+ // Recover non-transient extern msgs
+ for (int m=NUM_MSGS*3; m<NUM_MSGS*4; m++)
+ {
+ string rmsg;
+ string xid;
+ bool transientFlag;
+ bool externalFlag;
+
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag);
+ BOOST_CHECK_MESSAGE(transientFlag == false, "Transient message recovered.");
+ BOOST_CHECK_MESSAGE(externalFlag == true, "External flag incorrect.");
+ BOOST_CHECK_MESSAGE(rmsg.size() == 0, "External message returned non-zero size.");
+ }
+ jc.recover_complete();
+ // Read recovered non-transient msgs
+ for (int m=NUM_MSGS; m<NUM_MSGS*2; m++)
+ {
+ string rmsg;
+ string xid;
+ bool transientFlag;
+ bool externalFlag;
+
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag);
+ BOOST_CHECK_MESSAGE(transientFlag == false, "Transient message recovered.");
+ BOOST_CHECK_MESSAGE(externalFlag == false, "External flag incorrect.");
+ BOOST_CHECK_MESSAGE(create_msg(msg, m, MSG_SIZE).compare(rmsg) == 0,
+ "Non-transient message corrupt during recover.");
+ }
+ // Read recovered non-transient extern msgs
+ for (int m=NUM_MSGS*3; m<NUM_MSGS*4; m++)
+ {
+ string rmsg;
+ string xid;
+ bool transientFlag;
+ bool externalFlag;
+
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag);
+ BOOST_CHECK_MESSAGE(transientFlag == false, "Transient message recovered.");
+ BOOST_CHECK_MESSAGE(externalFlag == true, "External flag incorrect.");
+ BOOST_CHECK_MESSAGE(rmsg.size() == 0, "External message returned non-zero size.");
+ }
+ // Dequeue recovered messages
+ for (int m=NUM_MSGS; m<NUM_MSGS*2; m++)
+ deq_msg(jc, m, m+3*NUM_MSGS);
+ for (int m=NUM_MSGS*3; m<NUM_MSGS*4; m++)
+ deq_msg(jc, m, m+2*NUM_MSGS);
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(double_dequeue)
+{
+ string test_name = get_test_name(test_filename, "double_dequeue");
+ try
+ {
+ string msg;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ enq_msg(jc, 0, create_msg(msg, 0, MSG_SIZE), false);
+ deq_msg(jc, 0, 1);
+ try{ deq_msg(jc, 0, 2); BOOST_ERROR("Did not throw exception on second dequeue."); }
+ catch (const jexception& e){ BOOST_CHECK_EQUAL(e.err_code(), jerrno::JERR_WMGR_DEQRIDNOTENQ); }
+ enq_msg(jc, 2, create_msg(msg, 1, MSG_SIZE), false);
+ deq_msg(jc, 2, 3);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+#else
+/*
+ * ==============================================
+ * LONG TESTS
+ * This section contains long tests and soak tests,
+ * and are run using target check-long (ie "make
+ * check-long"). These are built when LONG_TEST is
+ * defined.
+ * ==============================================
+ */
+
+QPID_AUTO_TEST_CASE(journal_overflow)
+{
+ string test_name = get_test_name(test_filename, "journal_overflow");
+ try
+ {
+ string msg;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_DEFAULT_JFILES, false, 0, DEFAULT_JFSIZE_SBLKS);
+ unsigned m;
+
+ // Fill journal to just below threshold
+ u_int32_t t = num_msgs_to_threshold(NUM_DEFAULT_JFILES, DEFAULT_JFSIZE_SBLKS * JRNL_SBLK_SIZE,
+ MSG_REC_SIZE_DBLKS);
+ u_int32_t d = num_dequeues_rem(NUM_DEFAULT_JFILES, DEFAULT_JFSIZE_SBLKS * JRNL_SBLK_SIZE);
+ for (m=0; m<t; m++)
+ enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false);
+ // This enqueue should exceed the threshold
+ enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false, RHM_IORES_ENQCAPTHRESH);
+
+ // Dequeue as many msgs as possible except first
+ for (m=1; m<=d; m++)
+ deq_msg(jc, m, m+t);
+ deq_msg(jc, d+1, d+2, RHM_IORES_FULL);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(file_cycle_block)
+{
+ string test_name = get_test_name(test_filename, "file_cycle_block");
+ try
+ {
+ string msg;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_DEFAULT_JFILES, false, 0, DEFAULT_JFSIZE_SBLKS);
+
+ // 5 cycles of enqueue/dequeue blocks of half threshold exception size
+ u_int32_t t = num_msgs_to_threshold(NUM_DEFAULT_JFILES, DEFAULT_JFSIZE_SBLKS * JRNL_SBLK_SIZE,
+ LARGE_MSG_REC_SIZE_DBLKS)/2;
+ for (unsigned i=0; i<5; i++)
+ {
+ for (unsigned m=2*i*t; m<(2*i+1)*t; m++)
+ enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false);
+ for (unsigned m=2*i*t; m<(2*i+1)*t; m++)
+ deq_msg(jc, m, m+t);
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(file_cycle_interleaved)
+{
+ string test_name = get_test_name(test_filename, "file_cycle_interleaved");
+ try
+ {
+ string msg;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_DEFAULT_JFILES, false, 0, DEFAULT_JFSIZE_SBLKS);
+
+ // 5 cycles of enqueue/dequeue blocks of half threshold exception size
+ u_int32_t t = num_msgs_to_threshold(NUM_DEFAULT_JFILES, DEFAULT_JFSIZE_SBLKS * JRNL_SBLK_SIZE,
+ LARGE_MSG_REC_SIZE_DBLKS)/2;
+ for (unsigned m=0; m<5*2*t; m+=2)
+ {
+ enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false);
+ deq_msg(jc, m, m+1);
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(recover_file_cycle_block)
+{
+ string test_name = get_test_name(test_filename, "recover_file_cycle_block");
+ try
+ {
+ string msg;
+ u_int64_t hrid;
+
+ // 5 cycles of enqueue/dequeue blocks of half threshold exception size
+ u_int32_t t = num_msgs_to_threshold(NUM_DEFAULT_JFILES, DEFAULT_JFSIZE_SBLKS * JRNL_SBLK_SIZE,
+ LARGE_MSG_REC_SIZE_DBLKS)/2;
+ for (unsigned i=0; i<5; i++)
+ {
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ if (i)
+ {
+ jc.recover(NUM_DEFAULT_JFILES, false, 0, DEFAULT_JFSIZE_SBLKS, 0, hrid);
+ BOOST_CHECK_EQUAL(hrid, u_int64_t(2*i*t - 1));
+ jc.recover_complete();
+ }
+ else
+ jc.initialize(NUM_DEFAULT_JFILES, false, 0, DEFAULT_JFSIZE_SBLKS);
+
+ for (unsigned m=2*i*t; m<(2*i+1)*t; m++)
+ enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false);
+ for (unsigned m=2*i*t; m<(2*i+1)*t; m++)
+ deq_msg(jc, m, m+t);
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(recover_file_cycle_interleaved)
+{
+ string test_name = get_test_name(test_filename, "recover_file_cycle_interleaved");
+ try
+ {
+ string msg;
+ u_int64_t hrid;
+
+ // 5 cycles of enqueue/dequeue blocks of half threshold exception size
+ u_int32_t t = num_msgs_to_threshold(NUM_DEFAULT_JFILES, DEFAULT_JFSIZE_SBLKS * JRNL_SBLK_SIZE,
+ LARGE_MSG_REC_SIZE_DBLKS)/2;
+ for (unsigned i=0; i<5; i++)
+ {
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ if (i)
+ {
+ jc.recover(NUM_DEFAULT_JFILES, false, 0, DEFAULT_JFSIZE_SBLKS, 0, hrid);
+ BOOST_CHECK_EQUAL(hrid, u_int64_t(2*i*t - 1));
+ jc.recover_complete();
+ }
+ else
+ jc.initialize(NUM_DEFAULT_JFILES, false, 0, DEFAULT_JFSIZE_SBLKS);
+
+ for (unsigned m=2*i*t; m<2*(i+1)*t; m+=2)
+ {
+ enq_msg(jc, m, create_msg(msg, m, LARGE_MSG_SIZE), false);
+ deq_msg(jc, m, m+1);
+ }
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+#endif
+
+QPID_AUTO_TEST_SUITE_END()
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_st_basic_txn.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_st_basic_txn.cpp
new file mode 100644
index 0000000000..aa2d31c2ae
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/_st_basic_txn.cpp
@@ -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.
+ *
+ */
+
+#include "../unit_test.h"
+#include <cmath>
+#include <iostream>
+#include "qpid/legacystore/jrnl/jcntl.h"
+
+using namespace boost::unit_test;
+using namespace mrg::journal;
+using namespace std;
+
+QPID_AUTO_TEST_SUITE(journal_basic_txn)
+
+const string test_filename("_st_basic_txn");
+
+#include "_st_helper_fns.h"
+
+// === Test suite ===
+
+QPID_AUTO_TEST_CASE(enqueue_commit_dequeue_block)
+{
+ string test_name = get_test_name(test_filename, "enqueue_commit_dequeue_block");
+ try
+ {
+ string msg;
+ string xid;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ create_xid(xid, 0, XID_SIZE);
+ for (int m=0; m<NUM_MSGS; m++)
+ BOOST_CHECK_EQUAL(enq_txn_msg(jc, m, create_msg(msg, m, MSG_SIZE), xid, false), u_int64_t(m));
+ txn_commit(jc, NUM_MSGS, xid);
+ for (int m=0; m<NUM_MSGS; m++)
+ deq_msg(jc, m, m+NUM_MSGS+1);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(enqueue_abort_dequeue_block)
+{
+ string test_name = get_test_name(test_filename, "enqueue_abort_dequeue_block");
+ try
+ {
+ string msg;
+ string xid;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ create_xid(xid, 0, XID_SIZE);
+ for (int m=0; m<NUM_MSGS; m++)
+ BOOST_CHECK_EQUAL(enq_txn_msg(jc, m, create_msg(msg, m, MSG_SIZE), xid, false), u_int64_t(m));
+ txn_abort(jc, NUM_MSGS, xid);
+ for (int m=0; m<NUM_MSGS; m++)
+ {
+ try
+ {
+ deq_msg(jc, m, m+NUM_MSGS+1);
+ BOOST_ERROR("Expected dequeue to fail with exception JERR_WMGR_DEQRIDNOTENQ.");
+ }
+ catch (const jexception& e) { if (e.err_code() != jerrno::JERR_WMGR_DEQRIDNOTENQ) throw; }
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(enqueue_commit_dequeue_interleaved)
+{
+ string test_name = get_test_name(test_filename, "enqueue_commit_dequeue_interleaved");
+ try
+ {
+ string msg;
+ string xid;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ for (int m=0; m<NUM_MSGS; m++)
+ {
+ create_xid(xid, m, XID_SIZE);
+ BOOST_CHECK_EQUAL(enq_txn_msg(jc, 3*m, create_msg(msg, m, MSG_SIZE), xid, false), u_int64_t(3*m));
+ txn_commit(jc, 3*m+1, xid);
+ deq_msg(jc, 3*m, 3*m+2);
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(enqueue_abort_dequeue_interleaved)
+{
+ string test_name = get_test_name(test_filename, "enqueue_abort_dequeue_interleaved");
+ try
+ {
+ string msg;
+ string xid;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ for (int m=0; m<NUM_MSGS; m++)
+ {
+ create_xid(xid, m, XID_SIZE);
+ BOOST_CHECK_EQUAL(enq_txn_msg(jc, 3*m, create_msg(msg, m, MSG_SIZE), xid, false), u_int64_t(3*m));
+ txn_abort(jc, 3*m+1, xid);
+ try
+ {
+ deq_msg(jc, 2*m, 2*m+2);
+ BOOST_ERROR("Expected dequeue to fail with exception JERR_WMGR_DEQRIDNOTENQ.");
+ }
+ catch (const jexception& e) { if (e.err_code() != jerrno::JERR_WMGR_DEQRIDNOTENQ) throw; }
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(enqueue_dequeue_commit_block)
+{
+ string test_name = get_test_name(test_filename, "enqueue_dequeue_commit_block");
+ try
+ {
+ string msg;
+ string xid;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ create_xid(xid, 0, XID_SIZE);
+ for (int m=0; m<NUM_MSGS; m++)
+ BOOST_CHECK_EQUAL(enq_txn_msg(jc, m, create_msg(msg, m, MSG_SIZE), xid, false), u_int64_t(m));
+ BOOST_CHECK_EQUAL(jc.get_enq_cnt(), u_int32_t(0));
+ for (int m=0; m<NUM_MSGS; m++)
+ deq_txn_msg(jc, m, m+NUM_MSGS, xid);
+ BOOST_CHECK_EQUAL(jc.get_enq_cnt(), u_int32_t(0));
+ txn_commit(jc, 2*NUM_MSGS, xid);
+ BOOST_CHECK_EQUAL(jc.get_enq_cnt(), u_int32_t(0));
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(enqueue_dequeue_abort_block)
+{
+ string test_name = get_test_name(test_filename, "enqueue_dequeue_abort_block");
+ try
+ {
+ string msg;
+ string xid;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ create_xid(xid, 0, XID_SIZE);
+ for (int m=0; m<NUM_MSGS; m++)
+ BOOST_CHECK_EQUAL(enq_txn_msg(jc, m, create_msg(msg, m, MSG_SIZE), xid, false), u_int64_t(m));
+ BOOST_CHECK_EQUAL(jc.get_enq_cnt(), u_int32_t(0));
+ for (int m=0; m<NUM_MSGS; m++)
+ deq_txn_msg(jc, m, m+NUM_MSGS, xid);
+ BOOST_CHECK_EQUAL(jc.get_enq_cnt(), u_int32_t(0));
+ txn_abort(jc, 2*NUM_MSGS, xid);
+ BOOST_CHECK_EQUAL(jc.get_enq_cnt(), u_int32_t(0));
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(enqueue_dequeue_commit_interleaved)
+{
+ string test_name = get_test_name(test_filename, "enqueue_dequeue_commit_interleaved");
+ try
+ {
+ string msg;
+ string xid;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ for (int m=0; m<NUM_MSGS; m++)
+ {
+ create_xid(xid, m, XID_SIZE);
+ BOOST_CHECK_EQUAL(enq_txn_msg(jc, 3*m, create_msg(msg, m, MSG_SIZE), xid, false), u_int64_t(3*m));
+ deq_txn_msg(jc, 3*m, 3*m+1, xid);
+ txn_commit(jc, 3*m+2, xid);
+ BOOST_CHECK_EQUAL(jc.get_enq_cnt(), u_int32_t(0));
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(enqueue_dequeue_abort_interleaved)
+{
+ string test_name = get_test_name(test_filename, "enqueue_dequeue_abort_interleaved");
+ try
+ {
+ string msg;
+ string xid;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ for (int m=0; m<NUM_MSGS; m++)
+ {
+ create_xid(xid, m, XID_SIZE);
+ BOOST_CHECK_EQUAL(enq_txn_msg(jc, 3*m, create_msg(msg, m, MSG_SIZE), xid, false), u_int64_t(3*m));
+ deq_txn_msg(jc, 3*m, 3*m+1, xid);
+ txn_abort(jc, 3*m+2, xid);
+ BOOST_CHECK_EQUAL(jc.get_enq_cnt(), u_int32_t(0));
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_SUITE_END()
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_st_helper_fns.h b/qpid/cpp/src/tests/legacystore/jrnl/_st_helper_fns.h
new file mode 100644
index 0000000000..923065dd11
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/_st_helper_fns.h
@@ -0,0 +1,882 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+// NOTE: This file is included in _st_*.cpp files inside the QPID_AUTO_TEST_SUITE()
+// definition.
+
+#define MAX_AIO_SLEEPS 500
+#define AIO_SLEEP_TIME 1000
+#define NUM_TEST_JFILES 4
+#define NUM_DEFAULT_JFILES 8
+#define JRNL_DEFAULT_FSIZE 24 // Multiples of JRNL_RMGR_PAGE_SIZE
+#define TEST_JFSIZE_SBLKS 128
+#define DEFAULT_JFSIZE_SBLKS (JRNL_DEFAULT_FSIZE * JRNL_RMGR_PAGE_SIZE)
+#define NUM_MSGS 5
+#define MSG_REC_SIZE_DBLKS 2
+#define MSG_SIZE (MSG_REC_SIZE_DBLKS * JRNL_DBLK_SIZE) - sizeof(enq_hdr) - sizeof(rec_tail)
+#define LARGE_MSG_REC_SIZE_DBLKS (JRNL_SBLK_SIZE * JRNL_RMGR_PAGE_SIZE)
+#define LARGE_MSG_SIZE (LARGE_MSG_REC_SIZE_DBLKS * JRNL_DBLK_SIZE) - sizeof(enq_hdr) - sizeof(rec_tail)
+#define XID_SIZE 64
+
+#define XLARGE_MSG_RATIO (1.0 * LARGE_MSG_REC_SIZE / JRNL_DBLK_SIZE / JRNL_SBLK_SIZE / JRNL_RMGR_PAGE_SIZE)
+#define XLARGE_MSG_THRESHOLD (int)(JRNL_DEFAULT_FSIZE * NUM_DEFAULT_JFILES * JRNL_ENQ_THRESHOLD / 100 / LARGE_MSG_RATIO)
+
+#define NUM_JFILES 4
+#define JFSIZE_SBLKS 128
+
+const char* tdp = getenv("TMP_DATA_DIR");
+const string test_dir(tdp && strlen(tdp) > 0 ? string(tdp) + "/" + test_filename : "/var/tmp/jrnl_test");
+
+class test_dtok : public data_tok
+{
+private:
+ bool flag;
+public:
+ test_dtok() : data_tok(), flag(false) {}
+ virtual ~test_dtok() {}
+ bool done() { if (flag || _wstate == NONE) return true; else { flag = true; return false; } }
+};
+
+class test_jrnl_cb : public aio_callback {
+ virtual void wr_aio_cb(std::vector<data_tok*>& dtokl)
+ {
+ for (std::vector<data_tok*>::const_iterator i=dtokl.begin(); i!=dtokl.end(); i++)
+ {
+ test_dtok* dtp = static_cast<test_dtok*>(*i);
+ if (dtp->done())
+ delete dtp;
+ }
+ }
+ virtual void rd_aio_cb(std::vector<u_int16_t>& /*pil*/) {}
+};
+
+class test_jrnl : public jcntl
+{
+test_jrnl_cb* cb;
+
+public:
+ test_jrnl(const std::string& jid, const std::string& jdir, const std::string& base_filename, test_jrnl_cb& cb0) :
+ jcntl(jid, jdir, base_filename),
+ cb(&cb0) {}
+ virtual ~test_jrnl() {}
+ void initialize(const u_int16_t num_jfiles, const bool ae, const u_int16_t ae_max_jfiles,
+ const u_int32_t jfsize_sblks)
+ {
+ jcntl::initialize(num_jfiles, ae, ae_max_jfiles, jfsize_sblks, JRNL_WMGR_DEF_PAGES, JRNL_WMGR_DEF_PAGE_SIZE,
+ cb);
+ _jdir.create_dir();
+ }
+ void recover(const u_int16_t num_jfiles, const bool ae, const u_int16_t ae_max_jfiles, const u_int32_t jfsize_sblks,
+ vector<string>* txn_list, u_int64_t& highest_rid)
+ { jcntl::recover(num_jfiles, ae, ae_max_jfiles, jfsize_sblks, JRNL_WMGR_DEF_PAGES, JRNL_WMGR_DEF_PAGE_SIZE, cb,
+ txn_list, highest_rid); }
+};
+
+/*
+* This class is for testing recover functionality by maintaining an internal lfid-pfid map, then creating physical
+* journal file stubs (just the fhdr section of the journal) and jinf file. This allows the recover functionality (which
+* analyzes these components to determine recover order).
+*
+* First set up a map or "blueprint" of what the journal should look like for recovery, then have the class create the
+* physical files. The jinf object under test then reads and analyzes the created journal, and it's analysis is checked
+* against what is expected.
+*
+* General usage pattern:
+* 1. Create instance of lfid_pfid_map.
+* 2. Call lfid_pfid_map::journal_create() to simulate initial journal creation.
+* 3. (optional) Call lfid_pfid_map::journal_insert() one or more times to simulate the addition of journal files.
+* 4. Call lfid_pfid_map::write_journal() to create dummy journal files (files containing only file headers)
+* 5. Create and initialize the jinf object under test
+* 6. Call jinf::analyze() to determine the pfid order - and thus also first and last lids
+* 7. Call lfid_pfid_map::check_analysis() to check the conclusions of the analysis
+* 8. Call lfid_pfid_map::destroy_journal() to delete the journal files and reset the lfid_pfid_map object.
+* 9. (optional) Back to step 2 for more tests
+*
+* See the individual methods below for more details.
+*/
+class lfid_pfid_map
+{
+ public:
+ typedef pair<u_int16_t, file_hdr> lppair; // Used for loading the map
+ typedef multimap<u_int16_t, file_hdr> lpmap; // Stores the journal "plan" before it is created on-disk
+ typedef lpmap::const_iterator lpmap_citr; // General purpose iterator
+ typedef pair<lpmap_citr, lpmap_citr> lpmap_range; // Range of values returned by multimap's equal_range() fn
+
+ private:
+ string _jid; // Journal id
+ string _base_filename; // Base filename
+ lpmap _map; // Stores the journal "blueprint" before it is created on-disk
+ u_int16_t _num_used_files; // number of files which contain jorunals
+ u_int16_t _oldest_lfid; // lfid where owi flips; always 0 if !_full
+ u_int16_t _last_pfid; // last pfid (ie last file added)
+
+ public:
+ lfid_pfid_map(const string& jid, const string& base_filename) :
+ _jid(jid), _base_filename(base_filename), _num_used_files(0), _oldest_lfid(0), _last_pfid(0)
+ {}
+ virtual ~lfid_pfid_map() {}
+
+ // Mainly used for debugging
+ void print()
+ {
+ int cnt = 0;
+ for (lpmap_citr i=_map.begin(); i!=_map.end(); i++, cnt++)
+ {
+ const file_hdr fh = i->second;
+ cout << " " << cnt << ": owi=" << (fh.get_owi()?"t":"f") << hex << " frid=0x" << fh._rid;
+ cout << " pfid=0x" << fh._pfid << " lfid=0x" << fh._lfid << " fro=0x" << fh._fro << dec << endl;
+ }
+ }
+
+ std::size_t size()
+ {
+ return _map.size();
+ }
+
+ /*
+ * Method journal_create(): Used to simulate the initial creation of a journal before file insertions
+ * take place.
+ *
+ * num_jfiles: The initial journal file count.
+ * num_used_jfiles: If this number is less than num_jfiles, it indicates a clean journal that has not yet
+ * completed its first rotation, and some files are empty (ie all null). The first
+ * num_used_jfiles will contain file headers, the remainder will be blank.
+ * oldest_lfid: The lfid (==pfid, see note 1 below) at which the owi flag flips. During normal operation,
+ * each time the journal rotates back to file 0, a flag (called the overwrite indicator or owi)
+ * is flipped. This flag is saved in the file header. During recovery, if scanning from logical
+ * file 0 upwards, the file at which this flag reverses from its value in file 0 is the file
+ * that was to have been overwritten next, and is thus the "oldest" file. Recovery analysis must
+ * start with this file. oldest_lfid sets the file at which this flag will flip value for the
+ * simulated recovery analysis. Note that this will be ignored if num_used_jfiles < num_jfiles,
+ * as it is not possible for an overwrite to have occurred if not all the files have been used.
+ * first_owi: Sets the value of the owi flag in file 0. If set to false, then the flip will be found with
+ * a true flag (and visa versa).
+ *
+ * NOTES:
+ * 1. By definition, the lfids and pfids coincide for a journal containing no inserted files. Thus pfid == lfid
+ * for all journals created after using initial_journal_create() alone.
+ * 2. By definition, if a journal is not full (num_used_jfiles < num_jfiles), then all owi flags for those files
+ * that are used must be the same. It is not possible for an overwrite situation to arise if a journal is not
+ * full.
+ * 3. This function acts on map _map only, and does not create any test files. Call write_journal() to do that.
+ * 4. This function must be called on a clean test object or on one where the previous test data has been
+ * cleared by calling journal_destroy(). Running this function more than once on existing data will
+ * result in invalid journals which cannot be recovered.
+ */
+ void journal_create(const u_int16_t num_jfiles, // Total number of files
+ const u_int16_t num_used_jfiles, // Number of used files, rest empty at end
+ const u_int16_t oldest_lfid = 0, // Fid where owi reverses
+ const u_int16_t bad_lfid = 0, // Fid where owi reverses again (must be > oldest_lifd),
+ // used for testing bad owi detection
+ const bool first_owi = false) // Value of first owi flag (ie pfid=0)
+ {
+ const bool full = num_used_jfiles == num_jfiles;
+ bool owi = first_owi;
+ _oldest_lfid = full ? oldest_lfid : 0;
+ for (u_int16_t lfid = 0; lfid < num_jfiles; lfid++)
+ {
+ const u_int16_t pfid = lfid;
+ file_hdr fh;
+ if (pfid < num_used_jfiles)
+ {
+ _num_used_files = num_used_jfiles;
+ /*
+ * Invert the owi flag from its current value (initially given by first_owi param) only if:
+ * 1. The journal is full (ie all files are used)
+ * AND
+ * 2. oldest_lfid param is non-zero (this is default, but lfid 0 being inverted is logically
+ * inconsistent with first_owi parameter being present)
+ * AND
+ * 3. Either:
+ * * current lfid == oldest_lfid (ie we are preparing the oldest lfid)
+ * OR
+ * * current lfid == bad_lfid AND bad_lfid > oldest (ie we are past the oldest and preparing the
+ * bad lfid)
+ */
+ if (full && oldest_lfid > 0 &&
+ (lfid == oldest_lfid || (bad_lfid > oldest_lfid && lfid == bad_lfid)))
+ owi = !owi;
+ const u_int64_t frid = u_int64_t(random());
+ init_fhdr(fh, frid, pfid, lfid, owi);
+ }
+ _map.insert(lppair(lfid, fh));
+ }
+ }
+
+ /*
+ * Method journal_insert(): Used to simulate the insertion of journal files into an existing journal.
+ *
+ * after_lfid: The logical file id (lfid) after which the new file is to be inserted.
+ * num_files: The number of files to be inserted.
+ * adjust_lids: Flag indicating that the lids of files _following_ the inserted files are to be adjusted upwards
+ * by the number of inserted files. Not doing so simulates a recovery immediately after insertion
+ * but before the following files are overwritten with their new lids. If this is set false, then:
+ * a) after_lfid MUST be the most recent file (_oldest_lfid-1 ie last lfid before owi changes).
+ * b) This call must be the last insert call.
+ *
+ * NOTES:
+ * 1. It is not possible to insert before lfid/pfid 0; thus these are always coincidental. This operation is
+ * logically equivalent to inserting after the last lfid, which is possible.
+ * 2. It is not possible to insert into a journal that is not full. Doing so will result in an unrecoverable
+ * journal (one that is logically inconsistent that can never occur in reality).
+ * 3. If a journal is stopped/interrupted immediately after a file insertion, there could be duplicate lids in
+ * play at recovery, as the following file lids in their headers are only overwritten when the file is
+ * eventually written to during normal operation. The owi flags, however, are used to determine which of the
+ * ambiguous lids are the inserted files.
+ * 4. This function acts on map _map only, and does not create any test files. Call write_journal() to do that.
+ */
+ void journal_insert(const u_int16_t after_lfid, // Insert files after this lfid
+ const u_int16_t num_files = 1, // Number of files to insert
+ const bool adjust_lids = true) // Adjust lids following inserted files
+ {
+ if (num_files == 0) return;
+ _num_used_files += num_files;
+ const u_int16_t num_jfiles_before_append = _map.size();
+ lpmap_citr i = _map.find(after_lfid);
+ if (i == _map.end()) BOOST_FAIL("Unable to find lfid=" << after_lfid << " in map.");
+ const file_hdr fh_before = (*i).second;
+
+ // Move overlapping lids (if req'd)
+ if (adjust_lids && after_lfid < num_jfiles_before_append - 1)
+ {
+ for (u_int16_t lfid = num_jfiles_before_append - 1; lfid > after_lfid; lfid--)
+ {
+ lpmap_citr itr = _map.find(lfid);
+ if (itr == _map.end()) BOOST_FAIL("Unable to find lfid=" << after_lfid << " in map.");
+ file_hdr fh = itr->second;
+ _map.erase(lfid);
+ fh._lfid += num_files;
+ if (lfid == _oldest_lfid)
+ _oldest_lfid += num_files;
+ _map.insert(lppair(fh._lfid, fh));
+ }
+ }
+
+ // Add new file headers
+ u_int16_t pfid = num_jfiles_before_append;
+ u_int16_t lfid = after_lfid + 1;
+ while (pfid < num_jfiles_before_append + num_files)
+ {
+ const u_int64_t frid = u_int64_t(random());
+ const size_t fro = 0x200;
+ const file_hdr fh(RHM_JDAT_FILE_MAGIC, RHM_JDAT_VERSION, frid, pfid, lfid, fro, fh_before.get_owi(),
+ true);
+ _map.insert(lppair(lfid, fh));
+ _last_pfid = pfid;
+ pfid++;
+ lfid++;
+ }
+ }
+
+ /*
+ * Get the list of pfids in the map in order of lfid. The pfids are appended to the supplied vector. Only
+ * as many headers as are in the map are appended.
+ * NOTE: will clear any contents from supplied vector before appending pfid list.
+ */
+ void get_pfid_list(vector<u_int16_t>& pfid_list)
+ {
+ pfid_list.clear();
+ for (lpmap_citr i = _map.begin(); i != _map.end(); i++)
+ pfid_list.push_back(i->second._pfid);
+ }
+
+ /*
+ * Get the list of lfids in the map. The lfids are appended to the supplied vector in the order they appear
+ * in the map (which is not necessarily the natural or sorted order).
+ * NOTE: will clear any contents from supplied vector before appending lfid list.
+ */
+ void get_lfid_list(vector<u_int16_t>& lfid_list)
+ {
+ lfid_list.clear();
+ lfid_list.assign(_map.size(), 0);
+ for (lpmap_citr i = _map.begin(); i != _map.end(); i++)
+ lfid_list[i->second._pfid] = i->first;
+ }
+
+ /*
+ * Method check_analysis(): Used to check the result of the test jinf object analysis by comparing the pfid order
+ * array it produces against the internal map.
+ *
+ * ji: A ref to the jinf object under test.
+ */
+ void check_analysis(jinf& ji) // jinf object under test after analyze() has been called
+ {
+ BOOST_CHECK_EQUAL(ji.get_first_pfid(), get_first_pfid());
+ BOOST_CHECK_EQUAL(ji.get_last_pfid(), get_last_pfid());
+
+ jinf::pfid_list& pfidl = ji.get_pfid_list();
+ const u_int16_t num_jfiles = _map.size();
+ const bool all_used = _num_used_files == num_jfiles;
+ BOOST_CHECK_EQUAL(pfidl.size(), _num_used_files);
+
+ const u_int16_t lfid_start = all_used ? _oldest_lfid : 0;
+ // Because a simulated failure would leave lfid dups in map and last_fid would not exist in map in this
+ // case, we must find lfid_stop via pfid instead. Search for pfid == num_files.
+ lpmap_citr itr = _map.begin();
+ while (itr != _map.end() && itr->second._pfid != _num_used_files - 1) itr++;
+ if (itr == _map.end())
+ BOOST_FAIL("check(): Unable to find pfid=" << (_num_used_files - 1) << " in map.");
+ const u_int16_t lfid_stop = itr->second._lfid;
+
+ std::size_t fidl_index = 0;
+ for (u_int16_t lfid_cnt = lfid_start; lfid_cnt < lfid_stop; lfid_cnt++, fidl_index++)
+ {
+ const u_int16_t lfid = lfid_cnt % num_jfiles;
+ lpmap_citr itr = _map.find(lfid);
+ if (itr == _map.end())
+ BOOST_FAIL("check(): Unable to find lfid=" << lfid << " in map.");
+ BOOST_CHECK_EQUAL(itr->second._pfid, pfidl[fidl_index]);
+ }
+ }
+
+ /*
+ * Method get_pfid(): Look up a pfid from a known lfid.
+ */
+ u_int16_t get_pfid(const u_int16_t lfid, const bool initial_owi = false)
+ {
+ switch (_map.count(lfid))
+ {
+ case 1:
+ return _map.find(lfid)->second._pfid;
+ case 2:
+ for (lpmap_citr itr = _map.lower_bound(lfid); itr != _map.upper_bound(lfid); itr++)
+ {
+ if (itr->second.get_owi() != initial_owi)
+ return itr->second._pfid;
+ }
+ default:;
+ }
+ BOOST_FAIL("get_pfid(): lfid=" << lfid << " not found in map.");
+ return 0xffff;
+ }
+
+ /*
+ * Method get_first_pfid(): Look up the first (oldest, or next-to-be-overwritten) pfid in the analysis sequence.
+ */
+ u_int16_t get_first_pfid()
+ {
+ return get_pfid(_oldest_lfid);
+ }
+
+ /*
+ * Method get_last_pfid(): Look up the last (newest, or most recently written) pfid in the analysis sequence.
+ */
+ u_int16_t get_last_pfid()
+ {
+ u_int16_t flfid = 0;
+ if (_num_used_files == _map.size()) // journal full?
+ {
+ if (_oldest_lfid)
+ {
+ // if failed insert, cycle past duplicate lids
+ while (_map.count(_oldest_lfid) == 2)
+ _oldest_lfid++;
+ while (_map.find(_oldest_lfid) != _map.end() && _map.find(_oldest_lfid)->second.get_owi() == false)
+ _oldest_lfid++;
+ flfid = _oldest_lfid - 1;
+ }
+ else
+ flfid = _map.size() - 1;
+ }
+ else
+ flfid = _num_used_files - 1;
+ return get_pfid(flfid, true);
+ }
+
+ /*
+ * Method write_journal(): Used to create the dummy journal files from the built-up map created by calling
+ * initial_journal_create() and optionally journal_append() one or more times. Since the jinf object reads the
+ * jinf file and the file headers only, the create object creates a dummy journal file containing only a file
+ * header (512 bytes each) and a single jinf file which contains the journal metadata required for recovery
+ * analysis.
+ */
+ void write_journal(const bool ae, const u_int16_t ae_max_jfiles, const u_int32_t fsize_sblks = JFSIZE_SBLKS)
+ {
+ create_jinf(ae, ae_max_jfiles);
+ u_int16_t pfid = 0;
+ for (lpmap_citr itr = _map.begin(); itr != _map.end(); itr++, pfid++)
+ {
+ if (itr->second._pfid == 0 && itr->second._magic == 0) // empty header, use pfid counter instead
+ create_journal_file(pfid, itr->second, _base_filename, fsize_sblks);
+ else
+ create_journal_file(itr->second._pfid, itr->second, _base_filename, fsize_sblks);
+ }
+ }
+
+ /*
+ * Method destroy_journal(): Destroy the files created by create_journal() and reset the lfid_pfid_map test
+ * object. A new test may be started using the same lfid_pfid_map test object once this call has been made.
+ */
+ void destroy_journal()
+ {
+ for (u_int16_t pfid = 0; pfid < _map.size(); pfid++)
+ {
+ string fn = create_journal_filename(pfid, _base_filename);
+ BOOST_WARN_MESSAGE(::unlink(fn.c_str()) == 0, "destroy_journal(): Failed to remove file " << fn);
+ }
+ clean_journal_info_file(_base_filename);
+ _map.clear();
+ _num_used_files = 0;
+ _oldest_lfid = 0;
+ _last_pfid = 0;
+ }
+
+ /*
+ * Method create_new_jinf(): This static call creates a default jinf file only. This is used to test the read
+ * constructor of a jinf test object which reads a jinf file at instantiation.
+ */
+ static void create_new_jinf(const string jid, const string base_filename, const bool ae)
+ {
+ if (jdir::exists(test_dir))
+ jdir::delete_dir(test_dir);
+ create_jinf(NUM_JFILES, ae, (ae ? 5 * NUM_JFILES : 0), jid, base_filename);
+ }
+
+ /*
+ * Method clean_journal_info_file(): This static method deletes only a jinf file without harming any other
+ * journal file or its directory. This is used to clear those tests which rely only on the existence of a
+ * jinf file.
+ */
+ static void clean_journal_info_file(const string base_filename)
+ {
+ stringstream fn;
+ fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION;
+ BOOST_WARN_MESSAGE(::unlink(fn.str().c_str()) == 0, "clean_journal_info_file(): Failed to remove file " <<
+ fn.str());
+ }
+
+ static string create_journal_filename(const u_int16_t pfid, const string base_filename)
+ {
+ stringstream fn;
+ fn << test_dir << "/" << base_filename << ".";
+ fn << setfill('0') << hex << setw(4) << pfid << "." << JRNL_DATA_EXTENSION;
+ return fn.str();
+ }
+
+ private:
+ static void init_fhdr(file_hdr& fh,
+ const u_int64_t frid,
+ const u_int16_t pfid,
+ const u_int16_t lfid,
+ const bool owi,
+ const bool no_enq = false)
+ {
+ fh._magic = RHM_JDAT_FILE_MAGIC;
+ fh._version = RHM_JDAT_VERSION;
+#if defined(JRNL_BIG_ENDIAN)
+ fh._eflag = RHM_BENDIAN_FLAG;
+#else
+ fh._eflag = RHM_LENDIAN_FLAG;
+#endif
+ fh._uflag = owi ? rec_hdr::HDR_OVERWRITE_INDICATOR_MASK : 0;
+ fh._rid = frid;
+ fh._pfid = pfid;
+ fh._lfid = lfid;
+ fh._fro = no_enq ? 0 : 0x200;
+ timespec ts;
+ ::clock_gettime(CLOCK_REALTIME, &ts);
+ fh._ts_sec = ts.tv_sec;
+ fh._ts_nsec = ts.tv_nsec;
+ }
+
+ void create_jinf(const bool ae, const u_int16_t ae_max_jfiles)
+ {
+ if (jdir::exists(test_dir))
+ jdir::delete_dir(test_dir);
+ create_jinf(_map.size(), ae, ae_max_jfiles, _jid, _base_filename);
+ }
+
+ static void create_jinf(u_int16_t num_files, const bool ae, const u_int16_t ae_max_jfiles, const string jid,
+ const string base_filename)
+ {
+ jdir::create_dir(test_dir); // Check test dir exists; create it if not
+ timespec ts;
+ ::clock_gettime(CLOCK_REALTIME, &ts);
+ jinf ji(jid, test_dir, base_filename, num_files, ae, ae_max_jfiles, JFSIZE_SBLKS, JRNL_WMGR_DEF_PAGE_SIZE,
+ JRNL_WMGR_DEF_PAGES, ts);
+ ji.write();
+ }
+
+ static void create_journal_file(const u_int16_t pfid,
+ const file_hdr& fh,
+ const string base_filename,
+ const u_int32_t fsize_sblks = JFSIZE_SBLKS,
+ const char fill_char = 0)
+ {
+ const std::string filename = create_journal_filename(pfid, base_filename);
+ ofstream of(filename.c_str(), ofstream::out | ofstream::trunc);
+ if (!of.good())
+ BOOST_FAIL("Unable to open test journal file \"" << filename << "\" for writing.");
+
+ write_file_header(filename, of, fh, fill_char);
+ write_file_body(of, fsize_sblks, fill_char);
+
+ of.close();
+ if (of.fail() || of.bad())
+ BOOST_FAIL("Error closing test journal file \"" << filename << "\".");
+ }
+
+ static void write_file_header(const std::string& filename,
+ ofstream& of,
+ const file_hdr& fh,
+ const char fill_char)
+ {
+ // write file header
+ u_int32_t cnt = sizeof(file_hdr);
+ of.write((const char*)&fh, cnt);
+ if (of.fail() || of.bad())
+ BOOST_FAIL("Error writing file header to test journal file \"" << filename << "\".");
+
+ // fill remaining sblk with fill char
+ while (cnt++ < JRNL_DBLK_SIZE * JRNL_SBLK_SIZE)
+ {
+ of.put(fill_char);
+ if (of.fail() || of.bad())
+ BOOST_FAIL("Error writing filler to test journal file \"" << filename << "\".");
+ }
+ }
+
+ static void write_file_body(ofstream& of, const u_int32_t fsize_sblks, const char fill_char)
+ {
+ if (fsize_sblks > 1)
+ {
+ std::vector<char> sblk_buffer(JRNL_DBLK_SIZE * JRNL_SBLK_SIZE, fill_char);
+ u_int32_t fwritten_sblks = 0; // hdr
+ while (fwritten_sblks++ < fsize_sblks)
+ of.write(&sblk_buffer[0], JRNL_DBLK_SIZE * JRNL_SBLK_SIZE);
+ }
+ }
+};
+
+const string
+get_test_name(const string& file, const string& test_name)
+{
+ cout << test_filename << "." << test_name << ": " << flush;
+ return file + "." + test_name;
+}
+
+bool
+check_iores(const string& ctxt, const iores ret, const iores exp_ret, test_dtok* dtp)
+{
+ if (ret != exp_ret)
+ {
+ delete dtp;
+ BOOST_FAIL(ctxt << ": Expected " << iores_str(exp_ret) << "; got " << iores_str(ret));
+ }
+ return false;
+}
+
+bool
+handle_jcntl_response(const iores res, jcntl& jc, unsigned& aio_sleep_cnt, const std::string& ctxt, const iores exp_ret,
+ test_dtok* dtp)
+{
+ if (res == RHM_IORES_PAGE_AIOWAIT)
+ {
+ if (++aio_sleep_cnt <= MAX_AIO_SLEEPS)
+ {
+ jc.get_wr_events(0); // *** GEV2
+ usleep(AIO_SLEEP_TIME);
+ }
+ else
+ return check_iores(ctxt, res, exp_ret, dtp);
+ }
+ else
+ return check_iores(ctxt, res, exp_ret, dtp);
+ return true;
+}
+
+u_int64_t
+enq_msg(jcntl& jc,
+ const u_int64_t rid,
+ const string& msg,
+ const bool transient,
+ const iores exp_ret = RHM_IORES_SUCCESS)
+{
+ ostringstream ctxt;
+ ctxt << "enq_msg(" << rid << ")";
+ test_dtok* dtp = new test_dtok;
+ BOOST_CHECK_MESSAGE(dtp != 0, "Data token allocation failed (dtp == 0).");
+ dtp->set_rid(rid);
+ dtp->set_external_rid(true);
+ try
+ {
+ iores res = jc.enqueue_data_record(msg.c_str(), msg.size(), msg.size(), dtp, transient);
+ check_iores(ctxt.str(), res, exp_ret, dtp);
+ u_int64_t dtok_rid = dtp->rid();
+ if (dtp->done()) delete dtp;
+ return dtok_rid;
+ }
+ catch (exception& e) { delete dtp; throw; }
+}
+
+u_int64_t
+enq_extern_msg(jcntl& jc, const u_int64_t rid, const std::size_t msg_size, const bool transient,
+ const iores exp_ret = RHM_IORES_SUCCESS)
+{
+ ostringstream ctxt;
+ ctxt << "enq_extern_msg(" << rid << ")";
+ test_dtok* dtp = new test_dtok;
+ BOOST_CHECK_MESSAGE(dtp != 0, "Data token allocation failed (dtp == 0).");
+ dtp->set_rid(rid);
+ dtp->set_external_rid(true);
+ try
+ {
+ iores res = jc.enqueue_extern_data_record(msg_size, dtp, transient);
+ check_iores(ctxt.str(), res, exp_ret, dtp);
+ u_int64_t dtok_rid = dtp->rid();
+ if (dtp->done()) delete dtp;
+ return dtok_rid;
+ }
+ catch (exception& e) { delete dtp; throw; }
+}
+
+u_int64_t
+enq_txn_msg(jcntl& jc, const u_int64_t rid, const string& msg, const string& xid, const bool transient,
+ const iores exp_ret = RHM_IORES_SUCCESS)
+{
+ ostringstream ctxt;
+ ctxt << "enq_txn_msg(" << rid << ")";
+ test_dtok* dtp = new test_dtok;
+ BOOST_CHECK_MESSAGE(dtp != 0, "Data token allocation failed (dtp == 0).");
+ dtp->set_rid(rid);
+ dtp->set_external_rid(true);
+ try
+ {
+ iores res = jc.enqueue_txn_data_record(msg.c_str(), msg.size(), msg.size(), dtp, xid,
+ transient);
+ check_iores(ctxt.str(), res, exp_ret, dtp);
+ u_int64_t dtok_rid = dtp->rid();
+ if (dtp->done()) delete dtp;
+ return dtok_rid;
+ }
+ catch (exception& e) { delete dtp; throw; }
+}
+
+u_int64_t
+enq_extern_txn_msg(jcntl& jc, const u_int64_t rid, const std::size_t msg_size, const string& xid, const bool transient,
+ const iores exp_ret = RHM_IORES_SUCCESS)
+{
+ ostringstream ctxt;
+ ctxt << "enq_extern_txn_msg(" << rid << ")";
+ test_dtok* dtp = new test_dtok;
+ BOOST_CHECK_MESSAGE(dtp != 0, "Data token allocation failed (dtp == 0).");
+ dtp->set_rid(rid);
+ dtp->set_external_rid(true);
+ try
+ {
+ iores res = jc.enqueue_extern_txn_data_record(msg_size, dtp, xid, transient);
+ check_iores(ctxt.str(), res, exp_ret, dtp);
+ u_int64_t dtok_rid = dtp->rid();
+ if (dtp->done()) delete dtp;
+ return dtok_rid;
+ }
+ catch (exception& e) { delete dtp; throw; }
+}
+
+u_int64_t
+deq_msg(jcntl& jc, const u_int64_t drid, const u_int64_t rid, const iores exp_ret = RHM_IORES_SUCCESS)
+{
+ ostringstream ctxt;
+ ctxt << "deq_msg(" << drid << ")";
+ test_dtok* dtp = new test_dtok;
+ BOOST_CHECK_MESSAGE(dtp != 0, "Data token allocation failed (dtp == 0).");
+ dtp->set_rid(rid);
+ dtp->set_dequeue_rid(drid);
+ dtp->set_external_rid(true);
+ dtp->set_wstate(data_tok::ENQ);
+ try
+ {
+ iores res = jc.dequeue_data_record(dtp);
+ check_iores(ctxt.str(), res, exp_ret, dtp);
+ u_int64_t dtok_rid = dtp->rid();
+ if (dtp->done()) delete dtp;
+ return dtok_rid;
+ }
+ catch (exception& e) { delete dtp; throw; }
+}
+
+u_int64_t
+deq_txn_msg(jcntl& jc, const u_int64_t drid, const u_int64_t rid, const string& xid,
+ const iores exp_ret = RHM_IORES_SUCCESS)
+{
+ ostringstream ctxt;
+ ctxt << "deq_txn_msg(" << drid << ")";
+ test_dtok* dtp = new test_dtok;
+ BOOST_CHECK_MESSAGE(dtp != 0, "Data token allocation failed (dtp == 0).");
+ dtp->set_rid(rid);
+ dtp->set_dequeue_rid(drid);
+ dtp->set_external_rid(true);
+ dtp->set_wstate(data_tok::ENQ);
+ try
+ {
+ iores res = jc.dequeue_txn_data_record(dtp, xid);
+ check_iores(ctxt.str(), res, exp_ret, dtp);
+ u_int64_t dtok_rid = dtp->rid();
+ if (dtp->done()) delete dtp;
+ return dtok_rid;
+ }
+ catch (exception& e) { delete dtp; throw; }
+}
+
+u_int64_t
+txn_abort(jcntl& jc, const u_int64_t rid, const string& xid, const iores exp_ret = RHM_IORES_SUCCESS)
+{
+ test_dtok* dtp = new test_dtok;
+ BOOST_CHECK_MESSAGE(dtp != 0, "Data token allocation failed (dtp == 0).");
+ dtp->set_rid(rid);
+ dtp->set_external_rid(true);
+ try
+ {
+ iores res = jc.txn_abort(dtp, xid);
+ check_iores("txn_abort", res, exp_ret, dtp);
+ u_int64_t dtok_rid = dtp->rid();
+ if (dtp->done()) delete dtp;
+ return dtok_rid;
+ }
+ catch (exception& e) { delete dtp; throw; }
+}
+
+u_int64_t
+txn_commit(jcntl& jc, const u_int64_t rid, const string& xid, const iores exp_ret = RHM_IORES_SUCCESS)
+{
+ test_dtok* dtp = new test_dtok;
+ BOOST_CHECK_MESSAGE(dtp != 0, "Data token allocation failed (dtp == 0).");
+ dtp->set_rid(rid);
+ dtp->set_external_rid(true);
+ try
+ {
+ iores res = jc.txn_commit(dtp, xid);
+ check_iores("txn_commit", res, exp_ret, dtp);
+ u_int64_t dtok_rid = dtp->rid();
+ if (dtp->done()) delete dtp;
+ return dtok_rid;
+ }
+ catch (exception& e) { delete dtp; throw; }
+}
+
+void
+read_msg(jcntl& jc, string& msg, string& xid, bool& transient, bool& external, const iores exp_ret = RHM_IORES_SUCCESS)
+{
+ void* mp = 0;
+ std::size_t msize = 0;
+ void* xp = 0;
+ std::size_t xsize = 0;
+ test_dtok* dtp = new test_dtok;
+ BOOST_CHECK_MESSAGE(dtp != 0, "Data token allocation failed (dtp == 0).");
+ dtp->set_wstate(data_tok::ENQ);
+
+ unsigned aio_sleep_cnt = 0;
+ try
+ {
+ iores res = jc.read_data_record(&mp, msize, &xp, xsize, transient, external, dtp);
+ while (handle_jcntl_response(res, jc, aio_sleep_cnt, "read_msg", exp_ret, dtp))
+ res = jc.read_data_record(&mp, msize, &xp, xsize, transient, external, dtp);
+ }
+ catch (exception& e) { delete dtp; throw; }
+
+ if (mp)
+ msg.assign((char*)mp, msize);
+ if (xp)
+ {
+ xid.assign((char*)xp, xsize);
+ std::free(xp);
+ xp = 0;
+ }
+ else if (mp)
+ {
+ std::free(mp);
+ mp = 0;
+ }
+ delete dtp;
+}
+
+/*
+ * Returns the number of messages of size msg_rec_size_dblks that will fit into an empty journal with or without
+ * corresponding dequeues (controlled by include_deq) without a threshold - ie until the journal is full. Assumes
+ * that dequeue records fit into one dblk.
+ */
+u_int32_t
+num_msgs_to_full(const u_int16_t num_files, const u_int32_t file_size_dblks, const u_int32_t msg_rec_size_dblks,
+ bool include_deq)
+{
+ u_int32_t rec_size_dblks = msg_rec_size_dblks;
+ if (include_deq)
+ rec_size_dblks++;
+ return u_int32_t(::floor(1.0 * num_files * file_size_dblks / rec_size_dblks));
+}
+
+/*
+ * Returns the number of messages of size msg_rec_size_dblks that will fit into an empty journal before the enqueue
+ * threshold of JRNL_ENQ_THRESHOLD (%).
+ */
+u_int32_t
+num_msgs_to_threshold(const u_int16_t num_files, const u_int32_t file_size_dblks, const u_int32_t msg_rec_size_dblks)
+{
+ return u_int32_t(::floor(1.0 * num_files * file_size_dblks * JRNL_ENQ_THRESHOLD / msg_rec_size_dblks / 100));
+}
+
+/*
+ * Returns the amount of space reserved in dblks (== num dequeues assuming dequeue size of 1 dblk) for the enqueue
+ * threshold of JRNL_ENQ_THRESHOLD (%).
+ */
+u_int32_t
+num_dequeues_rem(const u_int16_t num_files, const u_int32_t file_size_dblks)
+{
+ /*
+ * Fraction of journal remaining after threshold is used --------------+
+ * Total no. dblks in journal ------+ |
+ * | |
+ * +------------+------------+ +-----------------+---------------------+
+ */
+ return u_int32_t(::ceil(num_files * file_size_dblks * (1.0 - (1.0 * JRNL_ENQ_THRESHOLD / 100))));
+}
+
+string&
+create_msg(string& s, const int msg_num, const int len)
+{
+ ostringstream oss;
+ oss << "MSG_" << setfill('0') << setw(6) << msg_num << "_";
+ for (int i=12; i<=len; i++)
+ oss << (char)('0' + i%10);
+ s.assign(oss.str());
+ return s;
+}
+
+string&
+create_xid(string& s, const int msg_num, const int len)
+{
+ ostringstream oss;
+ oss << "XID_" << setfill('0') << setw(6) << msg_num << "_";
+ for (int i=11; i<len; i++)
+ oss << (char)('a' + i%26);
+ s.assign(oss.str());
+ return s;
+}
+
+long
+get_seed()
+{
+ timespec ts;
+ if (::clock_gettime(CLOCK_REALTIME, &ts))
+ BOOST_FAIL("Unable to read clock to generate seed.");
+ long tenths = ts.tv_nsec / 100000000;
+ return long(10 * ts.tv_sec + tenths); // time in tenths of a second
+}
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_st_read.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_st_read.cpp
new file mode 100644
index 0000000000..ff2c39e14c
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/_st_read.cpp
@@ -0,0 +1,460 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "../unit_test.h"
+#include <cmath>
+#include <iostream>
+#include "qpid/legacystore/jrnl/jcntl.h"
+
+using namespace boost::unit_test;
+using namespace mrg::journal;
+using namespace std;
+
+QPID_AUTO_TEST_SUITE(journal_read)
+
+const string test_filename("_st_read");
+
+#include "_st_helper_fns.h"
+
+// === Test suite ===
+
+#ifndef LONG_TEST
+/*
+ * ==============================================
+ * NORMAL TESTS
+ * This section contains normal "make check" tests
+ * for building/packaging. These are built when
+ * LONG_TEST is _not_ defined.
+ * ==============================================
+ */
+
+QPID_AUTO_TEST_CASE(empty_read)
+{
+ string test_name = get_test_name(test_filename, "empty_read");
+ try
+ {
+ string msg;
+ string rmsg;
+ string xid;
+ bool transientFlag;
+ bool externalFlag;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(enqueue_read_dequeue_block)
+{
+ string test_name = get_test_name(test_filename, "enqueue_read_dequeue_block");
+ try
+ {
+ string msg;
+ string rmsg;
+ string xid;
+ bool transientFlag;
+ bool externalFlag;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ for (int m=0; m<NUM_MSGS; m++)
+ enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false);
+ jc.flush();
+ for (int m=0; m<NUM_MSGS; m++)
+ {
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag);
+ BOOST_CHECK_EQUAL(create_msg(msg, m, MSG_SIZE), rmsg);
+ BOOST_CHECK_EQUAL(xid.size(), std::size_t(0));
+ BOOST_CHECK_EQUAL(transientFlag, false);
+ BOOST_CHECK_EQUAL(externalFlag, false);
+ }
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY);
+ for (int m=0; m<NUM_MSGS; m++)
+ deq_msg(jc, m, m+NUM_MSGS);
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(enqueue_read_dequeue_interleaved)
+{
+ string test_name = get_test_name(test_filename, "enqueue_read_dequeue_interleaved");
+ try
+ {
+ string msg;
+ string rmsg;
+ string xid;
+ bool transientFlag;
+ bool externalFlag;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ for (int m=0; m<500*NUM_MSGS; m+=2)
+ {
+ enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false);
+ jc.flush();
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag);
+ BOOST_CHECK_EQUAL(create_msg(msg, m, MSG_SIZE), rmsg);
+ BOOST_CHECK_EQUAL(xid.size(), std::size_t(0));
+ BOOST_CHECK_EQUAL(transientFlag, false);
+ BOOST_CHECK_EQUAL(externalFlag, false);
+ deq_msg(jc, m, m+1);
+ jc.flush();
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY);
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(enqueue_recovered_read_dequeue)
+{
+ string test_name = get_test_name(test_filename, "enqueue_recovered_read_dequeue");
+ try
+ {
+ {
+ string msg;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ for (int m=0; m<NUM_MSGS; m++)
+ enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false);
+ }
+ {
+ string msg;
+ u_int64_t hrid;
+ string rmsg;
+ string xid;
+ bool transientFlag;
+ bool externalFlag;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.recover(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS, 0, hrid);
+ BOOST_CHECK_EQUAL(hrid, u_int64_t(NUM_MSGS - 1));
+ jc.recover_complete();
+ for (int m=0; m<NUM_MSGS; m++)
+ {
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag);
+ BOOST_CHECK_EQUAL(create_msg(msg, m, MSG_SIZE), rmsg);
+ BOOST_CHECK_EQUAL(xid.size(), std::size_t(0));
+ BOOST_CHECK_EQUAL(transientFlag, false);
+ BOOST_CHECK_EQUAL(externalFlag, false);
+ }
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY);
+ for (int m=0; m<NUM_MSGS; m++)
+ deq_msg(jc, m, m+NUM_MSGS);
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY);
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(multi_page_enqueue_recovered_read_dequeue_block)
+{
+ string test_name = get_test_name(test_filename, "multi_page_enqueue_recovered_read_dequeue_block");
+ try
+ {
+ {
+ string msg;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(2*NUM_TEST_JFILES, false, 0, 10*TEST_JFSIZE_SBLKS);
+ for (int m=0; m<NUM_MSGS*125; m++)
+ enq_msg(jc, m, create_msg(msg, m, 16*MSG_SIZE), false);
+ }
+ {
+ string msg;
+ u_int64_t hrid;
+ string rmsg;
+ string xid;
+ bool transientFlag;
+ bool externalFlag;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.recover(2*NUM_TEST_JFILES, false, 0, 10*TEST_JFSIZE_SBLKS, 0, hrid);
+ BOOST_CHECK_EQUAL(hrid, u_int64_t(NUM_MSGS*125 - 1));
+ jc.recover_complete();
+ for (int m=0; m<NUM_MSGS*125; m++)
+ {
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag);
+ BOOST_CHECK_EQUAL(create_msg(msg, m, 16*MSG_SIZE), rmsg);
+ BOOST_CHECK_EQUAL(xid.size(), std::size_t(0));
+ BOOST_CHECK_EQUAL(transientFlag, false);
+ BOOST_CHECK_EQUAL(externalFlag, false);
+ }
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY);
+ for (int m=0; m<NUM_MSGS*125; m++)
+ deq_msg(jc, m, m+NUM_MSGS*125);
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY);
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(enqueue_recover_read_recovered_read_dequeue_block)
+{
+ string test_name = get_test_name(test_filename, "enqueue_recover_read_recovered_read_dequeue_block");
+ try
+ {
+ {
+ string msg;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ for (int m=0; m<NUM_MSGS; m++)
+ enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false);
+ }
+ {
+ string msg;
+ u_int64_t hrid;
+ string rmsg;
+ string xid;
+ bool transientFlag;
+ bool externalFlag;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.recover(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS, 0, hrid);
+ BOOST_CHECK_EQUAL(hrid, u_int64_t(NUM_MSGS - 1));
+ for (int m=0; m<NUM_MSGS; m++)
+ {
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag);
+ BOOST_CHECK_EQUAL(create_msg(msg, m, MSG_SIZE), rmsg);
+ BOOST_CHECK_EQUAL(xid.size(), std::size_t(0));
+ BOOST_CHECK_EQUAL(transientFlag, false);
+ BOOST_CHECK_EQUAL(externalFlag, false);
+ }
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY);
+ }
+ {
+ string msg;
+ u_int64_t hrid;
+ string rmsg;
+ string xid;
+ bool transientFlag;
+ bool externalFlag;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.recover(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS, 0, hrid);
+ BOOST_CHECK_EQUAL(hrid, u_int64_t(NUM_MSGS - 1));
+ for (int m=0; m<NUM_MSGS; m++)
+ {
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag);
+ BOOST_CHECK_EQUAL(create_msg(msg, m, MSG_SIZE), rmsg);
+ BOOST_CHECK_EQUAL(xid.size(), std::size_t(0));
+ BOOST_CHECK_EQUAL(transientFlag, false);
+ BOOST_CHECK_EQUAL(externalFlag, false);
+ }
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY);
+ jc.recover_complete();
+ for (int m=0; m<NUM_MSGS; m++)
+ {
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag);
+ BOOST_CHECK_EQUAL(create_msg(msg, m, MSG_SIZE), rmsg);
+ BOOST_CHECK_EQUAL(xid.size(), std::size_t(0));
+ BOOST_CHECK_EQUAL(transientFlag, false);
+ BOOST_CHECK_EQUAL(externalFlag, false);
+ }
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY);
+ for (int m=0; m<NUM_MSGS; m++)
+ deq_msg(jc, m, m+NUM_MSGS);
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY);
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(delayed_read)
+{
+ string test_name = get_test_name(test_filename, "delayed_read");
+ try
+ {
+ string msg;
+ string rmsg;
+ string xid;
+ bool transientFlag;
+ bool externalFlag;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ unsigned m;
+ for (m=0; m<2*NUM_MSGS; m+=2)
+ {
+ enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false);
+ deq_msg(jc, m, m+1);
+ }
+ enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false);
+ jc.flush();
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag);
+ BOOST_CHECK_EQUAL(msg, rmsg);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(cache_cycled_delayed_read)
+{
+ string test_name = get_test_name(test_filename, "cache_cycled_delayed_read");
+ try
+ {
+ string msg;
+ string rmsg;
+ string xid;
+ bool transientFlag;
+ bool externalFlag;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ unsigned m;
+ unsigned read_buffer_size_dblks = JRNL_RMGR_PAGES * JRNL_RMGR_PAGE_SIZE * JRNL_SBLK_SIZE;
+ unsigned n = num_msgs_to_full(1, read_buffer_size_dblks, 16*MSG_REC_SIZE_DBLKS, true);
+ for (m=0; m<2*2*n + 20; m+=2) // fill read buffer twice + 10 msgs
+ {
+ enq_msg(jc, m, create_msg(msg, m, 16*MSG_SIZE), false);
+ deq_msg(jc, m, m+1);
+ }
+ enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false);
+ jc.flush();
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag);
+ BOOST_CHECK_EQUAL(msg, rmsg);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+#else
+/*
+ * ==============================================
+ * LONG TESTS
+ * This section contains long tests and soak tests,
+ * and are run using target check-long (ie "make
+ * check-long"). These are built when LONG_TEST is
+ * defined.
+ * ==============================================
+ */
+
+QPID_AUTO_TEST_CASE(multi_page_enqueue_read_dequeue_block)
+{
+ string test_name = get_test_name(test_filename, "multi_page_enqueue_read_dequeue_block");
+ try
+ {
+ string msg;
+ string rmsg;
+ string xid;
+ bool transientFlag;
+ bool externalFlag;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(2*NUM_TEST_JFILES, false, 0, 10*TEST_JFSIZE_SBLKS);
+ for (int i=0; i<10; i++)
+ {
+ for (int m=0; m<NUM_MSGS*125; m++)
+ enq_msg(jc, m, create_msg(msg, m, 16*MSG_SIZE), false);
+ jc.flush();
+ for (int m=0; m<NUM_MSGS*125; m++)
+ {
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag);
+ BOOST_CHECK_EQUAL(create_msg(msg, m, 16*MSG_SIZE), rmsg);
+ BOOST_CHECK_EQUAL(xid.size(), std::size_t(0));
+ BOOST_CHECK_EQUAL(transientFlag, false);
+ BOOST_CHECK_EQUAL(externalFlag, false);
+ }
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY);
+ for (int m=0; m<NUM_MSGS*125; m++)
+ deq_msg(jc, m, m+NUM_MSGS*125);
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag, RHM_IORES_EMPTY);
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(increasing_interval_delayed_read)
+{
+ string test_name = get_test_name(test_filename, "increasing_interval_delayed_read");
+ try
+ {
+ string msg;
+ string rmsg;
+ string xid;
+ bool transientFlag;
+ bool externalFlag;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ unsigned read_buffer_size_dblks = JRNL_RMGR_PAGES * JRNL_RMGR_PAGE_SIZE * JRNL_SBLK_SIZE;
+ unsigned n = num_msgs_to_full(1, read_buffer_size_dblks, MSG_REC_SIZE_DBLKS, true);
+ unsigned m = 0;
+
+ // Validate read pipeline
+ enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false);
+ jc.flush();
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag);
+ deq_msg(jc, m, m+1);
+ m += 2;
+
+ // repeat the following multiple times...
+ for (int i=0; i<10; i++)
+ {
+ // Invalidate read pipeline with large write
+ unsigned t = m + (i*n) + 25;
+ for (; m<t; m+=2)
+ {
+ enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false);
+ deq_msg(jc, m, m+1);
+ }
+
+ // Revalidate read pipeline
+ enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false);
+ jc.flush();
+ read_msg(jc, rmsg, xid, transientFlag, externalFlag);
+ BOOST_CHECK_EQUAL(msg, rmsg);
+ deq_msg(jc, m, m+1);
+ m += 2;
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+#endif
+
+QPID_AUTO_TEST_SUITE_END()
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_st_read_txn.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_st_read_txn.cpp
new file mode 100644
index 0000000000..621777d8d3
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/_st_read_txn.cpp
@@ -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.
+ *
+ */
+
+#include "../unit_test.h"
+#include <cmath>
+#include <iostream>
+#include "qpid/legacystore/jrnl/jcntl.h"
+
+using namespace boost::unit_test;
+using namespace mrg::journal;
+using namespace std;
+
+QPID_AUTO_TEST_SUITE(journal_read_txn)
+
+const string test_filename("_st_read_txn");
+
+#include "_st_helper_fns.h"
+
+// === Test suite ===
+
+QPID_AUTO_TEST_CASE(tx_enqueue_commit_block)
+{
+ string test_name = get_test_name(test_filename, "tx_enqueue_commit_block");
+ try
+ {
+ string msg;
+ string xid;
+ string rmsg;
+ string rxid;
+ bool transientFlag;
+ bool externalFlag;
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ create_xid(xid, 0, XID_SIZE);
+ for (int m=0; m<NUM_MSGS; m++)
+ enq_txn_msg(jc, m, create_msg(msg, m, MSG_SIZE), xid, false);
+ jc.flush();
+ read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_TXPENDING);
+ txn_commit(jc, NUM_MSGS, xid);
+ jc.flush();
+ for (int m=0; m<NUM_MSGS; m++)
+ {
+ read_msg(jc, rmsg, rxid, transientFlag, externalFlag);
+ BOOST_CHECK_EQUAL(create_msg(msg, m, MSG_SIZE), rmsg);
+ BOOST_CHECK_EQUAL(rxid, xid);
+ BOOST_CHECK_EQUAL(transientFlag, false);
+ BOOST_CHECK_EQUAL(externalFlag, false);
+ }
+ read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_EMPTY);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(tx_enqueue_commit_interleaved)
+{
+ string test_name = get_test_name(test_filename, "tx_enqueue_commit_interleaved");
+ try
+ {
+ string msg;
+ string xid;
+ string rmsg;
+ string rxid;
+ bool transientFlag;
+ bool externalFlag;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ for (int m=0; m<NUM_MSGS; m++)
+ {
+ create_xid(xid, 2*m, XID_SIZE);
+ enq_txn_msg(jc, 2*m, create_msg(msg, 2*m, MSG_SIZE), xid, false);
+ jc.flush();
+ read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_TXPENDING);
+ txn_commit(jc, 2*m+1, xid);
+ jc.flush();
+ read_msg(jc, rmsg, rxid, transientFlag, externalFlag);
+ BOOST_CHECK_EQUAL(create_msg(msg, 2*m, MSG_SIZE), rmsg);
+ BOOST_CHECK_EQUAL(rxid, xid);
+ BOOST_CHECK_EQUAL(transientFlag, false);
+ BOOST_CHECK_EQUAL(externalFlag, false);
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(tx_enqueue_abort_block)
+{
+ string test_name = get_test_name(test_filename, "tx_enqueue_abort_block");
+ try
+ {
+ string msg;
+ string xid;
+ string rmsg;
+ string rxid;
+ bool transientFlag;
+ bool externalFlag;
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ create_xid(xid, 1, XID_SIZE);
+ for (int m=0; m<NUM_MSGS; m++)
+ enq_txn_msg(jc, m, create_msg(msg, m, MSG_SIZE), xid, false);
+ jc.flush();
+ read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_TXPENDING);
+ txn_abort(jc, NUM_MSGS, xid);
+ jc.flush();
+ read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_EMPTY);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(tx_enqueue_abort_interleaved)
+{
+ string test_name = get_test_name(test_filename, "tx_enqueue_abort_interleaved");
+ try
+ {
+ string msg;
+ string xid;
+ string rmsg;
+ string rxid;
+ bool transientFlag;
+ bool externalFlag;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ for (int m=0; m<NUM_MSGS; m++)
+ {
+ create_xid(xid, 2*m, XID_SIZE);
+ enq_txn_msg(jc, 2*m, create_msg(msg, 2*m, MSG_SIZE), xid, false);
+ jc.flush();
+ read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_TXPENDING);
+ txn_abort(jc, 2*m+1, xid);
+ jc.flush();
+ read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_EMPTY);
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(tx_enqueue_commit_dequeue_block)
+{
+ string test_name = get_test_name(test_filename, "tx_enqueue_commit_dequeue_block");
+ try
+ {
+ string msg;
+ string xid;
+ string rmsg;
+ string rxid;
+ bool transientFlag;
+ bool externalFlag;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ create_xid(xid, 2, XID_SIZE);
+ for (int m=0; m<NUM_MSGS; m++)
+ enq_txn_msg(jc, m, create_msg(msg, m, MSG_SIZE), xid, false);
+ txn_commit(jc, NUM_MSGS, xid);
+ for (int m=0; m<NUM_MSGS; m++)
+ deq_msg(jc, m, m+NUM_MSGS+1);
+ jc.flush();
+ read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_EMPTY);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(tx_enqueue_commit_dequeue_interleaved)
+{
+ string test_name = get_test_name(test_filename, "tx_enqueue_commit_dequeue_interleaved");
+ try
+ {
+ string msg;
+ string xid;
+ string rmsg;
+ string rxid;
+ bool transientFlag;
+ bool externalFlag;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ for (int m=0; m<NUM_MSGS; m++)
+ {
+ create_xid(xid, 3*m, XID_SIZE);
+ enq_txn_msg(jc, 3*m, create_msg(msg, m, MSG_SIZE), xid, false);
+ txn_commit(jc, 3*m+1, xid);
+ deq_msg(jc, 3*m, 3*m+2);
+ jc.flush();
+ read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_EMPTY);
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(enqueue_tx_dequeue_commit_block)
+{
+ string test_name = get_test_name(test_filename, "enqueue_tx_dequeue_commit_block");
+ try
+ {
+ string msg;
+ string xid;
+ string rmsg;
+ string rxid;
+ bool transientFlag;
+ bool externalFlag;
+
+ create_xid(xid, 3, XID_SIZE);
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ for (int m=0; m<NUM_MSGS; m++)
+ enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false);
+ for (int m=0; m<NUM_MSGS; m++)
+ deq_txn_msg(jc, m, m+NUM_MSGS, xid);
+ jc.flush();
+ read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_TXPENDING);
+ txn_commit(jc, 2*NUM_MSGS, xid);
+ jc.flush();
+ read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_EMPTY);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(enqueue_tx_dequeue_commit_interleaved)
+{
+ string test_name = get_test_name(test_filename, "enqueue_tx_dequeue_commit_interleaved");
+ try
+ {
+ string msg;
+ string xid;
+ string rmsg;
+ string rxid;
+ bool transientFlag;
+ bool externalFlag;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ for (int m=0; m<NUM_MSGS; m++)
+ {
+ enq_msg(jc, 3*m, create_msg(msg, 3*m, MSG_SIZE), false);
+ create_xid(xid, 3*m, XID_SIZE);
+ deq_txn_msg(jc, 3*m, 3*m+1, xid);
+ jc.flush();
+ read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_TXPENDING);
+ txn_commit(jc, 3*m+2, xid);
+ jc.flush();
+ read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_EMPTY);
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(enqueue_tx_dequeue_abort_block)
+{
+ string test_name = get_test_name(test_filename, "enqueue_tx_dequeue_abort_block");
+ try
+ {
+ string msg;
+ string xid;
+ string rmsg;
+ string rxid;
+ bool transientFlag;
+ bool externalFlag;
+
+ create_xid(xid, 4, XID_SIZE);
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ for (int m=0; m<NUM_MSGS; m++)
+ enq_msg(jc, m, create_msg(msg, m, MSG_SIZE), false);
+ for (int m=0; m<NUM_MSGS; m++)
+ deq_txn_msg(jc, m, m+NUM_MSGS, xid);
+ jc.flush();
+ read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_TXPENDING);
+ txn_abort(jc, 2*NUM_MSGS, xid);
+ jc.flush();
+ for (int m=0; m<NUM_MSGS; m++)
+ {
+ read_msg(jc, rmsg, rxid, transientFlag, externalFlag);
+ BOOST_CHECK_EQUAL(create_msg(msg, m, MSG_SIZE), rmsg);
+ BOOST_CHECK_EQUAL(rxid.length(), std::size_t(0));
+ BOOST_CHECK_EQUAL(transientFlag, false);
+ BOOST_CHECK_EQUAL(externalFlag, false);
+ }
+ read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_EMPTY);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(enqueue_tx_dequeue_abort_interleaved)
+{
+ string test_name = get_test_name(test_filename, "enqueue_tx_dequeue_abort_interleaved");
+ try
+ {
+ string msg;
+ string xid;
+ string rmsg;
+ string rxid;
+ bool transientFlag;
+ bool externalFlag;
+
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ jc.initialize(NUM_TEST_JFILES, false, 0, TEST_JFSIZE_SBLKS);
+ for (int m=0; m<NUM_MSGS; m++)
+ {
+ enq_msg(jc, 3*m, create_msg(msg, 3*m, MSG_SIZE), false);
+ create_xid(xid, 3*m, XID_SIZE);
+ deq_txn_msg(jc, 3*m, 3*m+1, xid);
+ jc.flush();
+ read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_TXPENDING);
+ txn_abort(jc, 3*m+2, xid);
+ jc.flush();
+ read_msg(jc, rmsg, rxid, transientFlag, externalFlag);
+ read_msg(jc, rmsg, rxid, transientFlag, externalFlag, RHM_IORES_EMPTY);
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_SUITE_END()
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_ut_enq_map.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_ut_enq_map.cpp
new file mode 100644
index 0000000000..f05dcb824c
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/_ut_enq_map.cpp
@@ -0,0 +1,320 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "../unit_test.h"
+
+#include <iostream>
+#include "qpid/legacystore/jrnl/enq_map.h"
+#include "qpid/legacystore/jrnl/jerrno.h"
+
+using namespace boost::unit_test;
+using namespace mrg::journal;
+using namespace std;
+
+QPID_AUTO_TEST_SUITE(enq_map_suite)
+
+const string test_filename("_ut_enq_map");
+
+QPID_AUTO_TEST_CASE(constructor)
+{
+ cout << test_filename << ".constructor: " << flush;
+ enq_map e1;
+ BOOST_CHECK(e1.empty());
+ BOOST_CHECK_EQUAL(e1.size(), u_int32_t(0));
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(insert_get)
+{
+ cout << test_filename << ".insert_get: " << flush;
+ u_int16_t pfid;
+ u_int64_t rid;
+ u_int16_t pfid_start = 0x2000U;
+ u_int64_t rid_begin = 0xffffffff00000000ULL;
+ u_int64_t rid_end = 0xffffffff00000200ULL;
+
+ // insert with no dups
+ u_int64_t rid_incr_1 = 4ULL;
+ enq_map e2;
+ e2.set_num_jfiles(pfid_start + (rid_end - rid_begin)/rid_incr_1);
+ for (rid = rid_begin, pfid = pfid_start; rid < rid_end; rid += rid_incr_1, pfid++)
+ BOOST_CHECK_EQUAL(e2.insert_pfid(rid, pfid), enq_map::EMAP_OK);
+ BOOST_CHECK(!e2.empty());
+ BOOST_CHECK_EQUAL(e2.size(), u_int32_t(128));
+
+ // get
+ u_int64_t rid_incr_2 = 6ULL;
+ for (u_int64_t rid = rid_begin; rid < rid_end; rid += rid_incr_2)
+ {
+ BOOST_CHECK_EQUAL(e2.is_enqueued(rid), (rid%rid_incr_1 ? false : true));
+ u_int16_t exp_pfid = pfid_start + (u_int16_t)((rid - rid_begin)/rid_incr_1);
+ int16_t ret_fid = e2.get_pfid(rid);
+ if (ret_fid < enq_map::EMAP_OK) // fail
+ {
+ BOOST_CHECK_EQUAL(ret_fid, enq_map::EMAP_RID_NOT_FOUND);
+ BOOST_CHECK(rid%rid_incr_1);
+ }
+ else
+ {
+ BOOST_CHECK_EQUAL(ret_fid, exp_pfid);
+ BOOST_CHECK(rid%rid_incr_1 == 0);
+ }
+ if ((rid + rid_incr_2)%(8 * rid_incr_2) == 0)
+ pfid++;
+ }
+
+ // insert with dups
+ for (rid = rid_begin, pfid = pfid_start; rid < rid_end; rid += rid_incr_2, pfid++)
+ {
+ int16_t res = e2.insert_pfid(rid, pfid);
+ if (res < enq_map::EMAP_OK) // fail
+ {
+ BOOST_CHECK_EQUAL(res, enq_map::EMAP_DUP_RID);
+ BOOST_CHECK(rid%rid_incr_1 == 0);
+ }
+ else
+ BOOST_CHECK(rid%rid_incr_1);
+ }
+ BOOST_CHECK_EQUAL(e2.size(), u_int32_t(171));
+ e2.clear();
+ BOOST_CHECK(e2.empty());
+ BOOST_CHECK_EQUAL(e2.size(), u_int32_t(0));
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(get_remove)
+{
+ cout << test_filename << ".get_remove: " << flush;
+ u_int16_t pfid;
+ u_int64_t rid;
+ u_int16_t pfid_start = 0x3000U;
+ u_int64_t rid_begin = 0xeeeeeeee00000000ULL;
+ u_int64_t rid_end = 0xeeeeeeee00000200ULL;
+
+ u_int64_t rid_incr_1 = 4ULL;
+ u_int64_t num_incr_1 = (rid_end - rid_begin)/rid_incr_1;
+ enq_map e3;
+ e3.set_num_jfiles(pfid_start + (rid_end - rid_begin)/rid_incr_1);
+ for (rid = rid_begin, pfid = pfid_start; rid < rid_end; rid += rid_incr_1, pfid++)
+ BOOST_CHECK_EQUAL(e3.insert_pfid(rid, pfid), enq_map::EMAP_OK);
+ BOOST_CHECK_EQUAL(e3.size(), num_incr_1);
+
+ u_int64_t rid_incr_2 = 6ULL;
+ for (rid = rid_begin, pfid = pfid_start; rid < rid_end; rid += rid_incr_2, pfid++)
+ {
+ u_int16_t exp_pfid = pfid_start + (u_int16_t)((rid - rid_begin)/rid_incr_1);
+ int16_t ret_fid = e3.get_remove_pfid(rid);
+ if (ret_fid < enq_map::EMAP_OK) // fail
+ {
+ BOOST_CHECK_EQUAL(ret_fid, enq_map::EMAP_RID_NOT_FOUND);
+ BOOST_CHECK(rid%rid_incr_1);
+ }
+ else
+ {
+ BOOST_CHECK_EQUAL(ret_fid, exp_pfid);
+ BOOST_CHECK(rid%rid_incr_1 == 0);
+ }
+ }
+ BOOST_CHECK_EQUAL(e3.size(), u_int32_t(85));
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(lock)
+{
+ cout << test_filename << ".lock: " << flush;
+ u_int16_t pfid;
+ u_int64_t rid;
+ u_int16_t pfid_start = 0x4000U;
+ u_int64_t rid_begin = 0xdddddddd00000000ULL;
+ u_int64_t rid_end = 0xdddddddd00000200ULL;
+
+ // insert, every second entry is locked
+ u_int64_t rid_incr_1 = 4ULL;
+ u_int64_t num_incr_1 = (rid_end - rid_begin)/rid_incr_1;
+ bool locked = false;
+ enq_map e4;
+ e4.set_num_jfiles(pfid_start + (rid_end - rid_begin)/rid_incr_1);
+ for (rid = rid_begin, pfid = pfid_start; rid < rid_end; rid += rid_incr_1, pfid++)
+ {
+ BOOST_CHECK_EQUAL(e4.insert_pfid(rid, pfid, locked), enq_map::EMAP_OK);
+ locked = !locked;
+ }
+ BOOST_CHECK_EQUAL(e4.size(), num_incr_1);
+
+ // unlock and lock non-existent rids
+ int16_t res = e4.lock(1ULL);
+ if (res < enq_map::EMAP_OK)
+ BOOST_CHECK_EQUAL(res, enq_map::EMAP_RID_NOT_FOUND);
+ else
+ BOOST_ERROR("Failed to detect locking non-existent rid.");
+ res = e4.unlock(2ULL);
+ if (res < enq_map::EMAP_OK)
+ BOOST_CHECK_EQUAL(res, enq_map::EMAP_RID_NOT_FOUND);
+ else
+ BOOST_ERROR("Failed to detect unlocking non-existent rid.");
+
+ // get / unlock
+ for (u_int64_t rid = rid_begin; rid < rid_end; rid += rid_incr_1)
+ {
+ int16_t fid = e4.get_pfid(rid);
+ if (fid < enq_map::EMAP_OK) // fail
+ {
+ BOOST_CHECK_EQUAL(fid, enq_map::EMAP_LOCKED);
+ BOOST_CHECK(rid%(2*rid_incr_1));
+ // unlock, read, then relock
+ BOOST_CHECK_EQUAL(e4.unlock(rid), enq_map::EMAP_OK);
+ BOOST_CHECK(e4.get_pfid(rid) >= enq_map::EMAP_OK);
+ BOOST_CHECK_EQUAL(e4.lock(rid), enq_map::EMAP_OK);
+ fid = e4.get_pfid(rid);
+ if (fid < enq_map::EMAP_OK) // fail
+ BOOST_CHECK_EQUAL(fid, enq_map::EMAP_LOCKED);
+ else
+ BOOST_ERROR("Failed to prevent getting locked record");
+ }
+ }
+
+ // remove all; if locked, use with txn_flag true; should ignore all locked records
+ for (u_int64_t rid = rid_begin; rid < rid_end; rid += rid_incr_1)
+ BOOST_CHECK(e4.get_remove_pfid(rid, true) >= enq_map::EMAP_OK);
+ BOOST_CHECK(e4.empty());
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(lists)
+{
+ cout << test_filename << ".lists: " << flush;
+ u_int16_t pfid;
+ u_int64_t rid;
+ u_int16_t pfid_start = 0x5000UL;
+ u_int64_t rid_begin = 0xdddddddd00000000ULL;
+ u_int64_t rid_end = 0xdddddddd00000200ULL;
+
+ // insert, every second entry is locked
+ u_int64_t rid_incr_1 = 4ULL;
+ u_int64_t num_incr_1 = (rid_end - rid_begin)/rid_incr_1;
+ vector<u_int64_t> rid_list;
+ vector<u_int16_t> pfid_list;
+ enq_map e5;
+ e5.set_num_jfiles(pfid_start + (rid_end - rid_begin)/rid_incr_1);
+ for (rid = rid_begin, pfid = pfid_start; rid < rid_end; rid += rid_incr_1, pfid++)
+ {
+ BOOST_CHECK_EQUAL(e5.insert_pfid(rid, pfid), enq_map::EMAP_OK);
+ rid_list.push_back(rid);
+ pfid_list.push_back(pfid);
+ }
+ BOOST_CHECK_EQUAL(e5.size(), num_incr_1);
+ BOOST_CHECK_EQUAL(rid_list.size(), num_incr_1);
+ BOOST_CHECK_EQUAL(pfid_list.size(), num_incr_1);
+
+ vector<u_int64_t> ret_rid_list;
+ e5.rid_list(ret_rid_list);
+ BOOST_CHECK_EQUAL(ret_rid_list.size(), num_incr_1);
+ for (unsigned i=0; i<ret_rid_list.size(); i++)
+ BOOST_CHECK_EQUAL(rid_list[i], ret_rid_list[i]);
+
+ vector<u_int16_t> ret_pfid_list;
+ e5.pfid_list(ret_pfid_list);
+ BOOST_CHECK_EQUAL(ret_pfid_list.size(), num_incr_1);
+ for (unsigned i=0; i<ret_pfid_list.size(); i++)
+ BOOST_CHECK_EQUAL(pfid_list[i], ret_pfid_list[i]);
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(enq_count)
+{
+ cout << test_filename << ".enq_count: " << flush;
+
+ enq_map e6;
+
+ // Check the allocation and cleanup as the file size is set both up and down
+ e6.set_num_jfiles(24);
+ e6.set_num_jfiles(0);
+ e6.set_num_jfiles(100);
+ e6.set_num_jfiles(4);
+
+ // Add 100 enqueues to file 1, check that the counts match
+ for (u_int16_t pfid=0; pfid<4; pfid++)
+ BOOST_CHECK_EQUAL(e6.get_enq_cnt(pfid), u_int32_t(0));
+ for (u_int64_t rid=0; rid<100; rid++)
+ BOOST_CHECK_EQUAL(e6.insert_pfid(rid, 1), enq_map::EMAP_OK);
+ for (u_int16_t pfid=0; pfid<4; pfid++)
+ {
+ if (pfid == 1)
+ BOOST_CHECK_EQUAL(e6.get_enq_cnt(pfid), u_int32_t(100));
+ else
+ BOOST_CHECK_EQUAL(e6.get_enq_cnt(pfid), u_int32_t(0));
+ }
+
+ // Now remove 10 from file 1, check that the counts match
+ for (u_int64_t rid=0; rid<100; rid+=10)
+ //e6.Xget_remove_pfid(rid);
+ BOOST_CHECK(e6.get_remove_pfid(rid) >= enq_map::EMAP_OK);
+ for (u_int16_t pfid=0; pfid<4; pfid++)
+ {
+ if (pfid == 1)
+ BOOST_CHECK_EQUAL(e6.get_enq_cnt(pfid), u_int32_t(90));
+ else
+ BOOST_CHECK_EQUAL(e6.get_enq_cnt(pfid), u_int32_t(0));
+ }
+
+ // Now resize the file up and make sure the count in file 1 still exists
+ e6.set_num_jfiles(8);
+ for (u_int16_t pfid=0; pfid<8; pfid++)
+ {
+ if (pfid == 1)
+ BOOST_CHECK_EQUAL(e6.get_enq_cnt(pfid), u_int32_t(90));
+ else
+ BOOST_CHECK_EQUAL(e6.get_enq_cnt(pfid), u_int32_t(0));
+ }
+
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(stress)
+{
+ cout << test_filename << ".stress: " << flush;
+ u_int64_t rid;
+ u_int64_t rid_cnt;
+ u_int64_t rid_begin = 0xffffffff00000000ULL;
+ u_int64_t num_rid = 10;
+
+ enq_map e7;
+ e7.set_num_jfiles(rid_begin + num_rid);
+
+ // insert even rids with no dups
+ for (rid = rid_begin, rid_cnt = u_int64_t(0); rid_cnt < num_rid; rid += 2ULL, rid_cnt++)
+ BOOST_CHECK_EQUAL(e7.insert_pfid(rid, u_int16_t(0)), enq_map::EMAP_OK);
+ BOOST_CHECK_EQUAL(e7.size(), num_rid);
+
+ // insert odd rids with no dups
+ for (rid = rid_begin + 1, rid_cnt = u_int64_t(0); rid_cnt < num_rid; rid += 2ULL, rid_cnt++)
+ BOOST_CHECK_EQUAL(e7.insert_pfid(rid, u_int16_t(0)), enq_map::EMAP_OK);
+ BOOST_CHECK_EQUAL(e7.size(), num_rid * 2);
+
+ // remove even rids
+ for (rid = rid_begin, rid_cnt = u_int64_t(0); rid_cnt < num_rid; rid += 2ULL, rid_cnt++)
+ BOOST_CHECK(e7.get_remove_pfid(rid) >= enq_map::EMAP_OK);
+ BOOST_CHECK_EQUAL(e7.size(), num_rid);
+
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_SUITE_END()
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_ut_jdir.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_ut_jdir.cpp
new file mode 100644
index 0000000000..b55d5ff8ef
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/_ut_jdir.cpp
@@ -0,0 +1,416 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "../unit_test.h"
+
+#include <cerrno>
+#include <cstring>
+#include <dirent.h>
+#include <fstream>
+#include <iomanip>
+#include <iostream>
+#include "qpid/legacystore/jrnl/file_hdr.h"
+#include "qpid/legacystore/jrnl/jcfg.h"
+#include "qpid/legacystore/jrnl/jdir.h"
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include "qpid/legacystore/jrnl/jexception.h"
+#include <sys/stat.h>
+
+#define NUM_JFILES 4
+#define JFSIZE_SBLKS 128
+
+#define ERRORSTR(e) std::strerror(e) << " (" << e << ")"
+#define NUM_CLEAR_OPS 20
+
+using namespace boost::unit_test;
+using namespace mrg::journal;
+using namespace std;
+
+QPID_AUTO_TEST_SUITE(jdir_suite)
+
+const string test_filename("_ut_jdir");
+const char* tdp = getenv("TMP_DATA_DIR");
+const string test_dir(tdp && strlen(tdp) > 0 ? string(tdp) + "/_ut_jdir" : "/var/tmp/_ut_jdir");
+
+// === Helper functions ===
+
+void create_file(const char* filename, mode_t fmode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
+{
+ ofstream of(filename, ofstream::out | ofstream::trunc);
+ if (!of.good())
+ BOOST_FAIL("Unable to open file " << filename << " for writing.");
+ of.write(filename, std::strlen(filename));
+ of.close();
+ ::chmod(filename, fmode);
+}
+
+void create_file(const string filename, mode_t fmode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
+{
+ create_file(filename.c_str(), fmode);
+}
+
+void create_jdat_file(const char* dirname, const char* base_filename, u_int32_t fid,
+ u_int64_t first_rid)
+{
+ stringstream fn;
+ fn << dirname << "/" << base_filename << ".";
+ fn << setfill('0') << hex << setw(4) << fid << ".jdat";
+ file_hdr fh(RHM_JDAT_FILE_MAGIC, RHM_JDAT_VERSION, 0, first_rid, fid, 0x200, true);
+ ofstream of(fn.str().c_str(), ofstream::out | ofstream::trunc);
+ if (!of.good())
+ BOOST_FAIL("Unable to open journal data file " << fn.str() << " for writing.");
+ of.write((const char*)&fh, sizeof(file_hdr));
+ of.close();
+}
+
+void create_jinf_file(const char* dirname, const char* base_filename)
+{
+ timespec ts;
+ ::clock_gettime(CLOCK_REALTIME, &ts);
+ jinf ji("test journal id", dirname, base_filename, NUM_JFILES, false, 0, JFSIZE_SBLKS,
+ JRNL_WMGR_DEF_PAGE_SIZE, JRNL_WMGR_DEF_PAGES, ts);
+ ji.write();
+}
+
+void create_jrnl_fileset(const char* dirname, const char* base_filename)
+{
+ create_jinf_file(dirname, base_filename);
+ for (u_int32_t fid = 0; fid < NUM_JFILES; fid++)
+ {
+ u_int64_t rid = 0x12340000 + (fid * 0x25);
+ create_jdat_file(dirname, base_filename, fid, rid);
+ }
+}
+
+unsigned count_dir_contents(const char* dirname, bool incl_files, bool incl_dirs = true)
+{
+ struct dirent* entry;
+ struct stat s;
+ unsigned file_cnt = 0;
+ unsigned dir_cnt = 0;
+ unsigned other_cnt = 0;
+ DIR* dir = ::opendir(dirname);
+ if (!dir)
+ BOOST_FAIL("Unable to open directory " << dirname);
+ while ((entry = ::readdir(dir)) != NULL)
+ {
+ // Ignore . and ..
+ if (std::strcmp(entry->d_name, ".") != 0 && std::strcmp(entry->d_name, "..") != 0)
+ {
+ stringstream fn;
+ fn << dirname << "/" << entry->d_name;
+ if (::stat(fn.str().c_str(), &s))
+ BOOST_FAIL("Unable to stat dir entry " << entry->d_name << "; err=" <<
+ ERRORSTR(errno));
+ if (S_ISREG(s.st_mode))
+ file_cnt++;
+ else if (S_ISDIR(s.st_mode))
+ dir_cnt++;
+ else
+ other_cnt++;
+ }
+ }
+ ::closedir(dir);
+ if (incl_files)
+ {
+ if (incl_dirs)
+ return file_cnt + dir_cnt;
+ return file_cnt;
+ }
+ else if (incl_dirs)
+ return dir_cnt;
+ return other_cnt;
+}
+
+void check_dir_contents(const char* dirname, const char* base_filename, unsigned num_subdirs,
+ bool jrnl_present)
+{
+ if (jdir::is_dir(dirname))
+ {
+ // Subdir count
+ BOOST_CHECK_EQUAL(count_dir_contents(dirname, false, true), num_subdirs);
+
+ // Journal file count
+ unsigned num_jrnl_files = jrnl_present ? NUM_JFILES + 1 : 0;
+ BOOST_CHECK_EQUAL(count_dir_contents(dirname, true, false), num_jrnl_files);
+
+ // Check journal files are present
+ if (jrnl_present)
+ try { jdir::verify_dir(dirname, base_filename); }
+ catch(const jexception& e) { BOOST_ERROR(e); }
+ for (unsigned subdir_num = 1; subdir_num <= num_subdirs; subdir_num++)
+ {
+ stringstream subdir_name;
+ subdir_name << dirname << "/_" << base_filename << ".bak.";
+ subdir_name << hex << setfill('0') << setw(4) << subdir_num;
+ try { jdir::verify_dir(subdir_name.str().c_str(), base_filename); }
+ catch(const jexception& e) { BOOST_ERROR(e); }
+ }
+ }
+ else
+ BOOST_ERROR(dirname << " is not a directory");
+}
+
+void check_dir_not_existing(const char* dirname)
+{
+ if (jdir::exists(dirname) && jdir::is_dir(dirname))
+ jdir::delete_dir(dirname);
+ if (jdir::exists(dirname))
+ BOOST_FAIL("Unable to remove directory " << dirname);
+}
+
+void check_dir_not_existing(const string dirname)
+{
+ check_dir_not_existing(dirname.c_str());
+}
+
+// === Test suite ===
+
+QPID_AUTO_TEST_CASE(constructor)
+{
+ cout << test_filename << ".constructor: " << flush;
+ string dir(test_dir + "/A/B/C/D/E/F");
+ string bfn("test_base");
+ jdir dir1(dir, bfn);
+ BOOST_CHECK(dir1.dirname().compare(dir) == 0);
+ BOOST_CHECK(dir1.base_filename().compare(bfn) == 0);
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(create_delete_dir)
+{
+ cout << test_filename << ".create_delete_dir: " << flush;
+ // Use instance
+ string dir_A(test_dir + "/A");
+ string dir_Ats(test_dir + "/A/"); // trailing '/'
+ check_dir_not_existing(test_dir + "/A");
+ jdir dir1(dir_A, "test_base");
+ dir1.create_dir();
+ // check all combos of jdir::exists and jdir::is_dir()
+ BOOST_CHECK(jdir::exists(dir_A));
+ BOOST_CHECK(jdir::exists(dir_Ats));
+ BOOST_CHECK(jdir::exists(dir_A.c_str()));
+ BOOST_CHECK(jdir::exists(dir_Ats.c_str()));
+ BOOST_CHECK(jdir::is_dir(dir_A));
+ BOOST_CHECK(jdir::is_dir(dir_Ats));
+ BOOST_CHECK(jdir::is_dir(dir_Ats.c_str()));
+ BOOST_CHECK(jdir::is_dir(dir_Ats.c_str()));
+ // do it a second time when dir exists
+ dir1.create_dir();
+ BOOST_CHECK(jdir::is_dir(dir_A));
+ dir1.delete_dir();
+ BOOST_CHECK(!jdir::exists(dir_A));
+
+ // Use static fn
+ check_dir_not_existing(test_dir + "/B");
+ jdir::create_dir(test_dir + "/B");
+ BOOST_CHECK(jdir::is_dir(test_dir + "/B"));
+ jdir::create_dir(test_dir + "/B");
+ BOOST_CHECK(jdir::is_dir(test_dir + "/B"));
+ jdir::delete_dir(test_dir + "/B");
+ BOOST_CHECK(!jdir::exists(test_dir + "/B"));
+
+ // Non-empty dirs
+ check_dir_not_existing(test_dir + "/C");
+ jdir::create_dir(test_dir + "/C");
+ BOOST_CHECK(jdir::is_dir(test_dir + "/C"));
+ create_file(test_dir + "/C/test_file_1.txt"); // mode 644 (default)
+ create_file(test_dir + "/C/test_file_2.txt", S_IRWXU | S_IRWXG | S_IRWXO); // mode 777
+ create_file(test_dir + "/C/test_file_3.txt", S_IRUSR | S_IRGRP | S_IROTH); // mode 444 (read-only)
+ create_file(test_dir + "/C/test_file_4.txt", 0); // mode 000 (no permissions)
+ BOOST_CHECK(jdir::is_dir(test_dir + "/C"));
+ jdir::create_dir(test_dir + "/C");
+ BOOST_CHECK(jdir::is_dir(test_dir + "/C"));
+ jdir::delete_dir(test_dir + "/C");
+ BOOST_CHECK(!jdir::exists(test_dir + "/C"));
+
+ // Check non-existent dirs fail
+ check_dir_not_existing(test_dir + "/D");
+ try
+ {
+ jdir::is_dir(test_dir + "/D");
+ BOOST_ERROR("jdir::is_dir() failed to throw jexeption for non-existent directory.");
+ }
+ catch(const jexception& e)
+ {
+ BOOST_CHECK_EQUAL(e.err_code(), jerrno::JERR_JDIR_STAT);
+ }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(create_delete_dir_recursive)
+{
+ cout << test_filename << ".create_delete_dir_recursive: " << flush;
+ // Use instances
+ check_dir_not_existing(test_dir + "/E");
+ jdir dir1(test_dir + "/E/F/G/H", "test_base");
+ dir1.create_dir();
+ BOOST_CHECK(jdir::is_dir(test_dir + "/E/F/G/H"));
+ dir1.delete_dir();
+ BOOST_CHECK(!jdir::exists(test_dir + "/E/F/G/H")); // only H deleted, E/F/G remain
+ BOOST_CHECK(jdir::exists(test_dir + "/E/F/G"));
+ jdir::delete_dir(test_dir + "/E"); // delete remaining dirs
+ BOOST_CHECK(!jdir::exists(test_dir + "/E"));
+
+ check_dir_not_existing(test_dir + "/F");
+ jdir dir2(test_dir + "/F/G/H/I/", "test_base"); // trailing '/'
+ dir2.create_dir();
+ BOOST_CHECK(jdir::is_dir(test_dir + "/F/G/H/I/"));
+ dir2.delete_dir();
+ BOOST_CHECK(!jdir::exists(test_dir + "/F/G/H/I/"));
+ BOOST_CHECK(jdir::exists(test_dir + "/F/G/H/"));
+ jdir::delete_dir(test_dir + "/F");
+ BOOST_CHECK(!jdir::exists(test_dir + "/F"));
+
+ check_dir_not_existing(test_dir + "/G");
+ jdir dir3(test_dir + "/G/H//I//J", "test_base"); // extra '/' in path
+ dir3.create_dir();
+ BOOST_CHECK(jdir::is_dir(test_dir + "/G/H//I//J"));
+ dir3.delete_dir();
+ BOOST_CHECK(!jdir::exists(test_dir + "/G/H//I//J"));
+ BOOST_CHECK(jdir::exists(test_dir + "/G/H//I"));
+ jdir::delete_dir(test_dir + "/F");
+ BOOST_CHECK(!jdir::exists(test_dir + "/F"));
+
+ // Use static fn
+ check_dir_not_existing(test_dir + "/H");
+ jdir::create_dir(test_dir + "/H/I/J/K");
+ BOOST_CHECK(jdir::is_dir(test_dir + "/H/I/J/K"));
+ jdir::delete_dir(test_dir + "/H/I/J/K");
+ BOOST_CHECK(!jdir::exists(test_dir + "/H/I/J/K")); // only J deleted, H/I/J remain
+ BOOST_CHECK(jdir::exists(test_dir + "/H/I/J"));
+ jdir::delete_dir(test_dir + "/H");
+ BOOST_CHECK(!jdir::exists(test_dir + "/H"));
+
+ check_dir_not_existing(test_dir + "/I");
+ jdir::create_dir(test_dir + "/I/J/K/L/"); // trailing '/'
+ BOOST_CHECK(jdir::is_dir(test_dir + "/I/J/K/L/"));
+ jdir::delete_dir(test_dir + "/I/J/K/L/");
+ BOOST_CHECK(!jdir::exists(test_dir + "/I/J/K/L/"));
+ BOOST_CHECK(jdir::exists(test_dir + "/I/J/K/"));
+ jdir::delete_dir(test_dir + "/I");
+ BOOST_CHECK(!jdir::exists(test_dir + "/I"));
+
+ check_dir_not_existing(test_dir + "//J");
+ jdir::create_dir(test_dir + "//J//K//L//M"); // extra '/' in path
+ BOOST_CHECK(jdir::is_dir(test_dir + "//J//K//L//M"));
+ jdir::delete_dir(test_dir + "//J//K//L//M");
+ BOOST_CHECK(!jdir::exists(test_dir + "//J//K//L//M"));
+ BOOST_CHECK(jdir::exists(test_dir + "//J//K//L"));
+ jdir::delete_dir(test_dir + "//J");
+ BOOST_CHECK(!jdir::exists(test_dir + "//J"));
+
+ // Non-empty dirs
+ check_dir_not_existing(test_dir + "/K");
+ jdir::create_dir(test_dir + "/K/L/M1/N1");
+ jdir::create_dir(test_dir + "/K/L/M1/N2");
+ jdir::create_dir(test_dir + "/K/L/M1/N3");
+ jdir::create_dir(test_dir + "/K/L/M1/N4");
+ create_file(test_dir + "/K/L/M1/N4/test_file_1.txt"); // mode 644 (default)
+ create_file(test_dir + "/K/L/M1/N4/test_file_2.txt", S_IRWXU | S_IRWXG | S_IRWXO); // mode 777
+ create_file(test_dir + "/K/L/M1/N4/test_file_3.txt", S_IRUSR | S_IRGRP | S_IROTH); // mode 444
+ create_file(test_dir + "/K/L/M1/N4/test_file_4.txt", 0); // mode 000 (no permissions)
+ jdir::create_dir(test_dir + "/K/L/M2");
+ jdir::create_dir(test_dir + "/K/L/M3/N5");
+ jdir::create_dir(test_dir + "/K/L/M3/N6");
+ BOOST_CHECK(jdir::is_dir(test_dir + "/K/L/M1/N1"));
+ BOOST_CHECK(jdir::is_dir(test_dir + "/K/L/M1/N2"));
+ BOOST_CHECK(jdir::is_dir(test_dir + "/K/L/M1/N3"));
+ BOOST_CHECK(jdir::is_dir(test_dir + "/K/L/M1/N4"));
+ BOOST_CHECK(jdir::is_dir(test_dir + "/K/L/M2"));
+ BOOST_CHECK(jdir::is_dir(test_dir + "/K/L/M3/N5"));
+ BOOST_CHECK(jdir::is_dir(test_dir + "/K/L/M3/N6"));
+ jdir::delete_dir(test_dir + "/K");
+ BOOST_CHECK(!jdir::exists(test_dir + "/K"));
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(clear_verify_dir)
+{
+ cout << test_filename << ".clear_verify_dir: " << flush;
+ // Use instances
+ const char* jrnl_dir = "/var/tmp/test_dir_1";
+ const char* bfn = "test_base";
+ check_dir_not_existing(jrnl_dir);
+ jdir test_dir_1(jrnl_dir, bfn);
+ test_dir_1.create_dir();
+ BOOST_CHECK(jdir::is_dir(jrnl_dir));
+ // add journal files, check they exist, then clear them
+ unsigned cnt = 0;
+ while (cnt < NUM_CLEAR_OPS)
+ {
+ create_jrnl_fileset(jrnl_dir, bfn);
+ check_dir_contents(jrnl_dir, bfn, cnt, true);
+ test_dir_1.clear_dir();
+ check_dir_contents(jrnl_dir, bfn, ++cnt, false);
+ }
+ // clean up
+ test_dir_1.delete_dir();
+ BOOST_CHECK(!jdir::exists(jrnl_dir));
+
+ // Non-existent dir with auto-create true
+ jrnl_dir = "/var/tmp/test_dir_2";
+ check_dir_not_existing(jrnl_dir);
+ jdir test_dir_2(jrnl_dir, bfn);
+ // clear dir
+ test_dir_2.clear_dir(); // create flag is true by default
+ check_dir_contents(jrnl_dir, bfn, 0, false);
+ // clear empty dir, should not create subdir
+ test_dir_2.clear_dir(); // create flag is true by default
+ check_dir_contents(jrnl_dir, bfn, 0, false);
+ // clean up
+ test_dir_2.delete_dir();
+ BOOST_CHECK(!jdir::exists(jrnl_dir));
+
+ // non-existent dir with auto-create false
+ jrnl_dir = "/var/tmp/test_dir_3";
+ check_dir_not_existing(jrnl_dir);
+ jdir test_dir_3(jrnl_dir, bfn);
+ try
+ {
+ test_dir_3.clear_dir(false);
+ BOOST_ERROR("jdir::clear_dir(flase) failed to throw jexeption for non-existent directory.");
+ }
+ catch(const jexception& e)
+ {
+ BOOST_CHECK_EQUAL(e.err_code(), jerrno::JERR_JDIR_OPENDIR);
+ }
+
+ // Use static fn
+ jrnl_dir = "/var/tmp/test_dir_4";
+ check_dir_not_existing(jrnl_dir);
+ jdir::clear_dir(jrnl_dir, bfn); // should create dir if it does not exist
+ // add journal files, check they exist, then clear them
+ cnt = 0;
+ while (cnt < NUM_CLEAR_OPS)
+ {
+ create_jrnl_fileset(jrnl_dir, bfn);
+ check_dir_contents(jrnl_dir, bfn, cnt, true);
+ jdir::clear_dir(jrnl_dir, bfn);
+ check_dir_contents(jrnl_dir, bfn, ++cnt, false);
+ }
+ // clean up
+ jdir::delete_dir(jrnl_dir);
+ BOOST_CHECK(!jdir::exists(jrnl_dir));
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_SUITE_END()
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_ut_jerrno.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_ut_jerrno.cpp
new file mode 100644
index 0000000000..1ae1138355
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/_ut_jerrno.cpp
@@ -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.
+ *
+ */
+
+#include "../unit_test.h"
+
+#include <cstring>
+#include <iostream>
+#include "qpid/legacystore/jrnl/jerrno.h"
+
+using namespace boost::unit_test;
+using namespace mrg::journal;
+using namespace std;
+
+QPID_AUTO_TEST_SUITE(jerrno_suite)
+using namespace mrg::journal;
+
+const string test_filename("_ut_jerrno");
+
+QPID_AUTO_TEST_CASE(jerrno_val)
+{
+ cout << test_filename << ".jerrno_val: " << flush;
+ const char* m = "JERR__MALLOC";
+ string malloc_msg = string(jerrno::err_msg(jerrno::JERR__MALLOC));
+ BOOST_CHECK(malloc_msg.substr(0, std::strlen(m)).compare(m) == 0);
+ BOOST_CHECK(std::strcmp(jerrno::err_msg(0), "<Unknown error code>") == 0);
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_SUITE_END()
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_ut_jexception.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_ut_jexception.cpp
new file mode 100644
index 0000000000..8b9e876aa6
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/_ut_jexception.cpp
@@ -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.
+ *
+ */
+
+#include "../unit_test.h"
+
+#include <cstring>
+#include <iostream>
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include "qpid/legacystore/jrnl/jexception.h"
+
+using namespace boost::unit_test;
+using namespace mrg::journal;
+using namespace std;
+
+QPID_AUTO_TEST_SUITE(jexception_suite)
+
+const string test_filename("_ut_jexception");
+
+// === Helper functions ===
+
+void throw_exception(const jexception& e, std::size_t what_len, std::size_t ai_len,
+ std::size_t tc_len, std::size_t tf_len)
+{
+ try { throw e; }
+ catch (const jexception& e)
+ {
+ BOOST_CHECK_EQUAL(std::strlen(e.what()), what_len);
+ BOOST_CHECK_EQUAL(e.additional_info().size(), ai_len);
+ BOOST_CHECK_EQUAL(e.throwing_class().size(), tc_len);
+ BOOST_CHECK_EQUAL(e.throwing_fn().size(), tf_len);
+ }
+}
+
+void throw_exception(const jexception& e, std::size_t what_len, std::size_t ai_len)
+{
+ throw_exception(e, what_len, ai_len, 0, 0);
+}
+
+void throw_exception(const jexception& e, std::size_t what_len, std::size_t tc_len,
+ std::size_t tf_len)
+{
+ throw_exception(e, what_len, 0, tc_len, tf_len);
+}
+
+// === Test suite ===
+
+QPID_AUTO_TEST_CASE(constructor_1)
+{
+ cout << test_filename << ".constructor_1: " << flush;
+ try
+ {
+ jexception e1;
+ BOOST_CHECK_EQUAL(e1.err_code(), (u_int32_t)0);
+ BOOST_CHECK(e1.additional_info().size() == 0);
+ BOOST_CHECK(e1.throwing_class().size() == 0);
+ BOOST_CHECK(e1.throwing_fn().size() == 0);
+ BOOST_CHECK(std::strlen(e1.what()) > 0);
+ throw e1;
+ }
+ catch (const jexception& e)
+ {
+ BOOST_CHECK_EQUAL(e.err_code(), (u_int32_t)0);
+ BOOST_CHECK(e.additional_info().size() == 0);
+ BOOST_CHECK(e.throwing_class().size() == 0);
+ BOOST_CHECK(e.throwing_fn().size() == 0);
+ BOOST_CHECK(std::strlen(e.what()) > 0);
+ }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(constructor_2)
+{
+ cout << test_filename << ".constructor_2: " << flush;
+ const u_int32_t err_code = 2;
+ try
+ {
+ jexception e2(err_code);
+ BOOST_CHECK_EQUAL(e2.err_code(), err_code);
+ BOOST_CHECK(e2.additional_info().size() == 0);
+ BOOST_CHECK(e2.throwing_class().size() == 0);
+ BOOST_CHECK(e2.throwing_fn().size() == 0);
+ BOOST_CHECK(std::strlen(e2.what()) > 0);
+ throw e2;
+ }
+ catch (const jexception& e)
+ {
+ BOOST_CHECK_EQUAL(e.err_code(), err_code);
+ BOOST_CHECK(e.additional_info().size() == 0);
+ BOOST_CHECK(e.throwing_class().size() == 0);
+ BOOST_CHECK(e.throwing_fn().size() == 0);
+ BOOST_CHECK(std::strlen(e.what()) > 0);
+ }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(constructor_3a)
+{
+ cout << test_filename << ".constructor_3a: " << flush;
+ const char* err_msg = "exception3";
+ try
+ {
+ jexception e3(err_msg);
+ BOOST_CHECK_EQUAL(e3.err_code(), (u_int32_t)0);
+ BOOST_CHECK(e3.additional_info().compare(err_msg) == 0);
+ BOOST_CHECK(e3.throwing_class().size() == 0);
+ BOOST_CHECK(e3.throwing_fn().size() == 0);
+ BOOST_CHECK(std::strlen(e3.what()) > 0);
+ throw e3;
+ }
+ catch (const jexception& e)
+ {
+ BOOST_CHECK_EQUAL(e.err_code(), (u_int32_t)0);
+ BOOST_CHECK(e.additional_info().compare(err_msg) == 0);
+ BOOST_CHECK(e.throwing_class().size() == 0);
+ BOOST_CHECK(e.throwing_fn().size() == 0);
+ BOOST_CHECK(std::strlen(e.what()) > 0);
+ }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(constructor_3b)
+{
+ cout << test_filename << ".constructor_3b: " << flush;
+ const string err_msg("exception3");
+ try
+ {
+ jexception e3(err_msg);
+ BOOST_CHECK_EQUAL(e3.err_code(), (u_int32_t)0);
+ BOOST_CHECK(e3.additional_info().compare(err_msg) == 0);
+ BOOST_CHECK(e3.throwing_class().size() == 0);
+ BOOST_CHECK(e3.throwing_fn().size() == 0);
+ BOOST_CHECK(std::strlen(e3.what()) > 0);
+ throw e3;
+ }
+ catch (const jexception& e)
+ {
+ BOOST_CHECK_EQUAL(e.err_code(), (u_int32_t)0);
+ BOOST_CHECK(e.additional_info().compare(err_msg) == 0);
+ BOOST_CHECK(e.throwing_class().size() == 0);
+ BOOST_CHECK(e.throwing_fn().size() == 0);
+ BOOST_CHECK(std::strlen(e.what()) > 0);
+ }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(constructor_4a)
+{
+ cout << test_filename << ".constructor_4a: " << flush;
+ const u_int32_t err_code = 4;
+ const char* err_msg = "exception4";
+ try
+ {
+ jexception e4(err_code, err_msg);
+ BOOST_CHECK_EQUAL(e4.err_code(), err_code);
+ BOOST_CHECK(e4.additional_info().compare(err_msg) == 0);
+ BOOST_CHECK(e4.throwing_class().size() == 0);
+ BOOST_CHECK(e4.throwing_fn().size() == 0);
+ BOOST_CHECK(std::strlen(e4.what()) > 0);
+ throw e4;
+ }
+ catch (const jexception& e)
+ {
+ BOOST_CHECK_EQUAL(e.err_code(), err_code);
+ BOOST_CHECK(e.additional_info().compare(err_msg) == 0);
+ BOOST_CHECK(e.throwing_class().size() == 0);
+ BOOST_CHECK(e.throwing_fn().size() == 0);
+ BOOST_CHECK(std::strlen(e.what()) > 0);
+ }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(constructor_4b)
+{
+ cout << test_filename << ".constructor_4b: " << flush;
+ const u_int32_t err_code = 4;
+ const string err_msg("exception4");
+ try
+ {
+ jexception e4(err_code, err_msg);
+ BOOST_CHECK_EQUAL(e4.err_code(), err_code);
+ BOOST_CHECK(e4.additional_info().compare(err_msg) == 0);
+ BOOST_CHECK(e4.throwing_class().size() == 0);
+ BOOST_CHECK(e4.throwing_fn().size() == 0);
+ BOOST_CHECK(std::strlen(e4.what()) > 0);
+ throw e4;
+ }
+ catch (const jexception& e)
+ {
+ BOOST_CHECK_EQUAL(e.err_code(), err_code);
+ BOOST_CHECK(e.additional_info().compare(err_msg) == 0);
+ BOOST_CHECK(e.throwing_class().size() == 0);
+ BOOST_CHECK(e.throwing_fn().size() == 0);
+ BOOST_CHECK(std::strlen(e.what()) > 0);
+ }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(constructor_5a)
+{
+ cout << test_filename << ".constructor_5a: " << flush;
+ const u_int32_t err_code = 5;
+ const char* err_class = "class5";
+ const char* err_fn = "fn5";
+ try
+ {
+ jexception e5(err_code, err_class, err_fn);
+ BOOST_CHECK_EQUAL(e5.err_code(), err_code);
+ BOOST_CHECK(e5.additional_info().size() == 0);
+ BOOST_CHECK(e5.throwing_class().compare(err_class) == 0);
+ BOOST_CHECK(e5.throwing_fn().compare(err_fn) == 0);
+ BOOST_CHECK(std::strlen(e5.what()) > 0);
+ throw e5;
+ }
+ catch (const jexception& e)
+ {
+ BOOST_CHECK_EQUAL(e.err_code(), err_code);
+ BOOST_CHECK(e.additional_info().size() == 0);
+ BOOST_CHECK(e.throwing_class().compare(err_class) == 0);
+ BOOST_CHECK(e.throwing_fn().compare(err_fn) == 0);
+ BOOST_CHECK(std::strlen(e.what()) > 0);
+ }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(constructor_5b)
+{
+ cout << test_filename << ".constructor_5b: " << flush;
+ const u_int32_t err_code = 5;
+ const string err_class("class5");
+ const string err_fn("fn5");
+ try
+ {
+ jexception e5(err_code, err_class, err_fn);
+ BOOST_CHECK_EQUAL(e5.err_code(), err_code);
+ BOOST_CHECK(e5.additional_info().size() == 0);
+ BOOST_CHECK(e5.throwing_class().compare(err_class) == 0);
+ BOOST_CHECK(e5.throwing_fn().compare(err_fn) == 0);
+ BOOST_CHECK(std::strlen(e5.what()) > 0);
+ throw e5;
+ }
+ catch (const jexception& e)
+ {
+ BOOST_CHECK_EQUAL(e.err_code(), err_code);
+ BOOST_CHECK(e.additional_info().size() == 0);
+ BOOST_CHECK(e.throwing_class().compare(err_class) == 0);
+ BOOST_CHECK(e.throwing_fn().compare(err_fn) == 0);
+ BOOST_CHECK(std::strlen(e.what()) > 0);
+ }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(constructor_6a)
+{
+ cout << test_filename << ".constructor_6a: " << flush;
+ const u_int32_t err_code = 6;
+ const char* err_msg = "exception6";
+ const char* err_class = "class6";
+ const char* err_fn = "fn6";
+ try
+ {
+ jexception e6(err_code, err_msg, err_class, err_fn);
+ BOOST_CHECK_EQUAL(e6.err_code(), err_code);
+ BOOST_CHECK(e6.additional_info().compare(err_msg) == 0);
+ BOOST_CHECK(e6.throwing_class().compare(err_class) == 0);
+ BOOST_CHECK(e6.throwing_fn().compare(err_fn) == 0);
+ BOOST_CHECK(std::strlen(e6.what()) > 0);
+ throw e6;
+ }
+ catch (const jexception& e)
+ {
+ BOOST_CHECK_EQUAL(e.err_code(), err_code);
+ BOOST_CHECK(e.additional_info().compare(err_msg) == 0);
+ BOOST_CHECK(e.throwing_class().compare(err_class) == 0);
+ BOOST_CHECK(e.throwing_fn().compare(err_fn) == 0);
+ BOOST_CHECK(std::strlen(e.what()) > 0);
+ }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(constructor_6b)
+{
+ cout << test_filename << ".constructor_6b: " << flush;
+ const u_int32_t err_code = 6;
+ const string err_msg("exception6");
+ const string err_class("class6");
+ const string err_fn("fn6");
+ try
+ {
+ jexception e6(err_code, err_msg, err_class, err_fn);
+ BOOST_CHECK_EQUAL(e6.err_code(), err_code);
+ BOOST_CHECK(e6.additional_info().compare(err_msg) == 0);
+ BOOST_CHECK(e6.throwing_class().compare(err_class) == 0);
+ BOOST_CHECK(e6.throwing_fn().compare(err_fn) == 0);
+ BOOST_CHECK(std::strlen(e6.what()) > 0);
+ throw e6;
+ }
+ catch (const jexception& e)
+ {
+ BOOST_CHECK_EQUAL(e.err_code(), err_code);
+ BOOST_CHECK(e.additional_info().compare(err_msg) == 0);
+ BOOST_CHECK(e.throwing_class().compare(err_class) == 0);
+ BOOST_CHECK(e.throwing_fn().compare(err_fn) == 0);
+ BOOST_CHECK(std::strlen(e.what()) > 0);
+ }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(msg_scope)
+{
+ cout << test_filename << ".msg_scope: " << flush;
+ try
+ {
+ // These will go out of scope as soon as jexception is thrown...
+ const string msg("Error message");
+ const string cls("class");
+ const string fn("function");
+ throw jexception(100, msg, cls, fn);
+ }
+ catch (const jexception& e)
+ {
+ stringstream ss;
+ ss << e;
+ BOOST_CHECK(ss.str().size() > 0);
+ }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_SUITE_END()
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_ut_jinf.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_ut_jinf.cpp
new file mode 100644
index 0000000000..f239139306
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/_ut_jinf.cpp
@@ -0,0 +1,402 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "../unit_test.h"
+
+#include <cmath>
+#include <iostream>
+#include "qpid/legacystore/jrnl/jcntl.h"
+
+using namespace boost::unit_test;
+using namespace mrg::journal;
+using namespace std;
+
+QPID_AUTO_TEST_SUITE(jinf_suite)
+
+const string test_filename("_ut_jinf");
+
+#include "_st_helper_fns.h"
+
+timespec ts;
+
+QPID_AUTO_TEST_CASE(write_constructor)
+{
+ string test_name = get_test_name(test_filename, "write_constructor");
+ const string jid = test_name + "_jid";
+ const string base_filename = test_name + "_bfn";
+ jdir::create_dir(test_dir); // Check test dir exists; create it if not
+ ::clock_gettime(CLOCK_REALTIME, &ts);
+ jinf ji(jid, test_dir, base_filename, NUM_JFILES, false, 0, JFSIZE_SBLKS, JRNL_WMGR_DEF_PAGE_SIZE, JRNL_WMGR_DEF_PAGES, ts);
+ BOOST_CHECK_EQUAL(ji.jver(), RHM_JDAT_VERSION);
+ BOOST_CHECK(ji.jid().compare(jid) == 0);
+ BOOST_CHECK(ji.jdir().compare(test_dir) == 0);
+ BOOST_CHECK(ji.base_filename().compare(base_filename) == 0);
+ const timespec this_ts = ji.ts();
+ BOOST_CHECK_EQUAL(this_ts.tv_sec, ts.tv_sec);
+ BOOST_CHECK_EQUAL(this_ts.tv_nsec, ts.tv_nsec);
+ BOOST_CHECK_EQUAL(ji.num_jfiles(), u_int16_t(NUM_JFILES));
+ BOOST_CHECK_EQUAL(ji.is_ae(), false);
+ BOOST_CHECK_EQUAL(ji.ae_max_jfiles(), u_int16_t(0));
+ BOOST_CHECK_EQUAL(ji.jfsize_sblks(), u_int32_t(JFSIZE_SBLKS));
+ BOOST_CHECK_EQUAL(ji.sblk_size_dblks(), u_int16_t(JRNL_SBLK_SIZE));
+ BOOST_CHECK_EQUAL(ji.dblk_size(), u_int32_t(JRNL_DBLK_SIZE));
+ BOOST_CHECK_EQUAL(ji.wcache_pgsize_sblks(), u_int32_t(JRNL_WMGR_DEF_PAGE_SIZE));
+ BOOST_CHECK_EQUAL(ji.wcache_num_pages(), u_int16_t(JRNL_WMGR_DEF_PAGES));
+ BOOST_CHECK_EQUAL(ji.rcache_pgsize_sblks(), u_int32_t(JRNL_RMGR_PAGE_SIZE));
+ BOOST_CHECK_EQUAL(ji.rcache_num_pages(), u_int16_t(JRNL_RMGR_PAGES));
+ ji.write();
+ cout << "done" << endl;
+}
+
+QPID_AUTO_TEST_CASE(read_constructor)
+{
+ string test_name = get_test_name(test_filename, "read_constructor");
+ const string jid = test_name + "_jid";
+ const string base_filename = test_name + "_bfn";
+ lfid_pfid_map::create_new_jinf(jid, base_filename, false);
+
+ stringstream fn;
+ fn << test_dir << "/" <<base_filename << "." << JRNL_INFO_EXTENSION;
+ jinf ji(fn.str(), false);
+ BOOST_CHECK_EQUAL(ji.jver(), RHM_JDAT_VERSION);
+ BOOST_CHECK(ji.jid().compare(jid) == 0);
+ BOOST_CHECK(ji.jdir().compare(test_dir) == 0);
+ BOOST_CHECK(ji.base_filename().compare(base_filename) == 0);
+// const timespec this_ts = ji.ts();
+// BOOST_CHECK_EQUAL(this_ts.tv_sec, ts.tv_sec);
+// BOOST_CHECK_EQUAL(this_ts.tv_nsec, ts.tv_nsec);
+ BOOST_CHECK_EQUAL(ji.num_jfiles(), u_int16_t(NUM_JFILES));
+ BOOST_CHECK_EQUAL(ji.is_ae(), false);
+ BOOST_CHECK_EQUAL(ji.ae_max_jfiles(), u_int16_t(0));
+ BOOST_CHECK_EQUAL(ji.jfsize_sblks(), u_int32_t(JFSIZE_SBLKS));
+ BOOST_CHECK_EQUAL(ji.sblk_size_dblks(), u_int16_t(JRNL_SBLK_SIZE));
+ BOOST_CHECK_EQUAL(ji.dblk_size(), u_int32_t(JRNL_DBLK_SIZE));
+ BOOST_CHECK_EQUAL(ji.wcache_pgsize_sblks(), u_int32_t(JRNL_WMGR_DEF_PAGE_SIZE));
+ BOOST_CHECK_EQUAL(ji.wcache_num_pages(), u_int16_t(JRNL_WMGR_DEF_PAGES));
+ BOOST_CHECK_EQUAL(ji.rcache_pgsize_sblks(), u_int32_t(JRNL_RMGR_PAGE_SIZE));
+ BOOST_CHECK_EQUAL(ji.rcache_num_pages(), u_int16_t(JRNL_RMGR_PAGES));
+
+ cout << "done" << endl;
+}
+
+QPID_AUTO_TEST_CASE(set_functions)
+{
+ string test_name = get_test_name(test_filename, "set_functions");
+ const string jid = test_name + "_jid";
+ const string base_filename = test_name + "_bfn";
+ lfid_pfid_map::create_new_jinf(jid, base_filename, false);
+
+ stringstream fn;
+ fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION;
+ jinf ji(fn.str(), false);
+
+ ji.set_jdir("abc123");
+ BOOST_CHECK(ji.jdir().compare("abc123") == 0);
+ ji.set_jdir(test_dir);
+ BOOST_CHECK(ji.jdir().compare(test_dir) == 0);
+ ji.incr_num_jfiles();
+ BOOST_CHECK_EQUAL(ji.num_jfiles(), u_int16_t(NUM_JFILES+1));
+ ji.incr_num_jfiles();
+ BOOST_CHECK_EQUAL(ji.num_jfiles(), u_int16_t(NUM_JFILES+2));
+
+ lfid_pfid_map::clean_journal_info_file(test_dir);
+ cout << "done" << endl;
+}
+
+QPID_AUTO_TEST_CASE(validate)
+{
+ string test_name = get_test_name(test_filename, "validate");
+ const string jid = test_name + "_jid";
+ const string base_filename = test_name + "_bfn";
+ lfid_pfid_map::create_new_jinf(jid, base_filename, false);
+
+ stringstream fn;
+ fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION;
+ jinf ji(fn.str(), true);
+ // TODO: Check validation picks up conflict, but need to be friend to jinf to do it
+
+ lfid_pfid_map::clean_journal_info_file(test_dir);
+ cout << "done" << endl;
+}
+
+QPID_AUTO_TEST_CASE(analyze_empty_journal)
+{
+ string test_name = get_test_name(test_filename, "analyze_empty_journal");
+ const string jid = test_name + "_jid";
+ const string base_filename = test_name + "_bfn";
+ jdir::create_dir(test_dir); // Check test dir exists; create it if not
+
+ lfid_pfid_map m(jid, base_filename);
+ m.journal_create(NUM_JFILES, 0, 0);
+ m.write_journal(false, 0);
+
+ stringstream fn;
+ fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION;
+ jinf ji(fn.str(), false);
+ try { ji.analyze(); }
+ catch (const jexception& e)
+ {
+ if (e.err_code() != jerrno::JERR_JINF_JDATEMPTY)
+ BOOST_ERROR("Failed to throw expected exception jerrno::JERR_JINF_JDATEMPTY");
+ }
+
+ m.destroy_journal();
+ cout << "done" << endl;
+}
+
+QPID_AUTO_TEST_CASE(analyze_part_full_journal)
+{
+ string test_name = get_test_name(test_filename, "analyze_part_full_journal");
+ const string jid = test_name + "_jid";
+ const string base_filename = test_name + "_bfn";
+ lfid_pfid_map m(jid, base_filename);
+ for (u_int16_t num_files = 1; num_files < NUM_JFILES; num_files++)
+ {
+ m.journal_create(NUM_JFILES, num_files, 0);
+ m.write_journal(false, 0);
+
+ stringstream fn;
+ fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION;
+ jinf ji(fn.str(), false);
+ ji.analyze();
+ m.check_analysis(ji);
+
+ m.destroy_journal();
+ }
+ cout << "done" << endl;
+}
+
+QPID_AUTO_TEST_CASE(analyze_full_journal)
+{
+ string test_name = get_test_name(test_filename, "analyze_full_journal");
+ const string jid = test_name + "_jid";
+ const string base_filename = test_name + "_bfn";
+ lfid_pfid_map m(jid, base_filename);
+ for (u_int16_t file_num = 0; file_num < NUM_JFILES; file_num++)
+ {
+ m.journal_create(NUM_JFILES, NUM_JFILES, file_num);
+ m.write_journal(false, 0);
+
+ stringstream fn;
+ fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION;
+ jinf ji(fn.str(), false);
+ ji.analyze();
+ m.check_analysis(ji);
+
+ m.destroy_journal();
+ }
+ cout << "done" << endl;
+}
+
+QPID_AUTO_TEST_CASE(analyze_single_appended_journal)
+{
+ string test_name = get_test_name(test_filename, "analyze_single_appended_journal");
+ const string jid = test_name + "_jid";
+ const string base_filename = test_name + "_bfn";
+ lfid_pfid_map m(jid, base_filename);
+ for (u_int16_t oldest_lid = 0; oldest_lid < NUM_JFILES; oldest_lid++)
+ for (u_int16_t after_lid = 0; after_lid < NUM_JFILES; after_lid++)
+ for (u_int16_t num_files = 1; num_files <= 5; num_files++)
+ {
+ m.journal_create(NUM_JFILES, NUM_JFILES, oldest_lid);
+ m.journal_insert(after_lid, num_files);
+ m.write_journal(true, 16);
+
+ stringstream fn;
+ fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION;
+ jinf ji(fn.str(), false);
+ ji.analyze();
+ m.check_analysis(ji);
+
+ m.destroy_journal();
+ }
+ cout << "done" << endl;
+}
+
+QPID_AUTO_TEST_CASE(analyze_multi_appended_journal)
+{
+ string test_name = get_test_name(test_filename, "analyze_multi_appended_journal");
+ const string jid = test_name + "_jid";
+ const string base_filename = test_name + "_bfn";
+ lfid_pfid_map m(jid, base_filename);
+ ::srand48(1);
+
+ for (u_int16_t num_appends = 1; num_appends <= 2*NUM_JFILES; num_appends++)
+ {
+ const u_int16_t oldest_lid = u_int16_t(NUM_JFILES * ::drand48());
+ m.journal_create(NUM_JFILES, NUM_JFILES, oldest_lid);
+ for (u_int16_t a = 0; a < num_appends; a++)
+ {
+ const u_int16_t num_files = u_int16_t(1 + (NUM_JFILES * ::drand48()));
+ const u_int16_t after_lid = u_int16_t(m.size() * ::drand48());
+ m.journal_insert(after_lid, num_files);
+ }
+ m.write_journal(true, 24);
+
+ stringstream fn;
+ fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION;
+ jinf ji(fn.str(), false);
+ ji.analyze();
+ m.check_analysis(ji);
+
+ m.destroy_journal();
+ }
+
+ cout << "done" << endl;
+}
+
+QPID_AUTO_TEST_CASE(analyze_multi_appended_then_failed_journal)
+{
+ string test_name = get_test_name(test_filename, "analyze_multi_appended_then_failed_journal");
+ const string jid = test_name + "_jid";
+ const string base_filename = test_name + "_bfn";
+ lfid_pfid_map m(jid, base_filename);
+ ::srand48(1);
+
+ // As this test relies on repeatable but random sequences, use many iterations for coverage
+ for (int c = 1; c <= 100; c++)
+ {
+ for (u_int16_t num_appends = 1; num_appends <= 2*NUM_JFILES; num_appends++)
+ {
+ u_int16_t oldest_lid = u_int16_t(NUM_JFILES * ::drand48());
+ m.journal_create(NUM_JFILES, NUM_JFILES, oldest_lid);
+ for (u_int16_t a = 0; a < num_appends-1; a++)
+ {
+ const u_int16_t num_files = u_int16_t(1 + (NUM_JFILES * ::drand48()));
+ const u_int16_t after_lid = u_int16_t(m.size() * ::drand48());
+ m.journal_insert(after_lid, num_files);
+ if (after_lid < oldest_lid)
+ oldest_lid += num_files;
+ }
+ const u_int16_t num_files = u_int16_t(1 + (NUM_JFILES * ::drand48()));
+ const u_int16_t after_lid = oldest_lid == 0 ? m.size() - 1 : oldest_lid - 1;
+ m.journal_insert(after_lid, num_files, false);
+ m.write_journal(true, 32);
+
+ stringstream fn;
+ fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION;
+ jinf ji(fn.str(), false);
+ ji.analyze();
+ m.check_analysis(ji);
+
+ m.destroy_journal();
+ }
+ }
+
+ cout << "done" << endl;
+}
+
+QPID_AUTO_TEST_CASE(analyze_inconsistent_jdat_file_size_in_journal)
+{
+ string test_name = get_test_name(test_filename, "analyze_inconsistent_jdat_file_size_in_journal");
+ const string jid = test_name + "_jid";
+ const string base_filename = test_name + "_bfn";
+ lfid_pfid_map m(jid, base_filename);
+ ::srand48(1);
+
+ for (u_int16_t pfid = 1; pfid < NUM_JFILES; pfid++)
+ {
+ m.journal_create(NUM_JFILES, NUM_JFILES, 0);
+ m.write_journal(false, 0);
+
+ const std::string filename = m.create_journal_filename(pfid, base_filename);
+ std::ofstream of(filename.c_str(), ofstream::out | ofstream::app);
+ if (!of.good())
+ BOOST_FAIL("Unable to open test journal file \"" << filename << "\" for writing.");
+ std::size_t expand_size = std::size_t(10 * JRNL_DBLK_SIZE * JRNL_SBLK_SIZE * ::drand48());
+ std::vector<char> sblk_buffer(expand_size, 0);
+ of.write(&sblk_buffer[0], expand_size);
+ of.close();
+
+ stringstream fn;
+ fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION;
+ jinf ji(fn.str(), false);
+ try
+ {
+ ji.analyze();
+ BOOST_FAIL("Failed to detect irregular journal file size in file \"" << filename << "\"");
+ }
+ catch (const jexception& e) {} // ignore - expected
+
+ m.destroy_journal();
+ }
+ cout << "done" << endl;
+}
+
+QPID_AUTO_TEST_CASE(analyze_owi_in_non_ae_journal)
+{
+ string test_name = get_test_name(test_filename, "analyze_owi_in_non_ae_journal");
+ const string jid = test_name + "_jid";
+ const string base_filename = test_name + "_bfn";
+ lfid_pfid_map m(jid, base_filename);
+ for (u_int16_t oldest_file = 1; oldest_file < NUM_DEFAULT_JFILES-1; oldest_file++)
+ {
+ for (u_int16_t bad_owi_file = oldest_file + 1; bad_owi_file < NUM_DEFAULT_JFILES; bad_owi_file++)
+ {
+ m.journal_create(NUM_DEFAULT_JFILES, NUM_DEFAULT_JFILES, oldest_file, bad_owi_file);
+ m.write_journal(false, 0);
+
+ stringstream fn;
+ fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION;
+ jinf ji(fn.str(), false);
+ try
+ {
+ ji.analyze();
+ BOOST_FAIL("Failed to detect irregular OWI flag in non-ae journal file \"" << fn.str() << "\"");
+ }
+ catch (const jexception& e) {} // ignore - expected
+
+ m.destroy_journal();
+ }
+ }
+ cout << "done" << endl;
+}
+
+QPID_AUTO_TEST_CASE(analyze_owi_in_ae_min_size_journal)
+{
+ string test_name = get_test_name(test_filename, "analyze_owi_in_ae_min_size_journal");
+ const string jid = test_name + "_jid";
+ const string base_filename = test_name + "_bfn";
+ lfid_pfid_map m(jid, base_filename);
+ for (u_int16_t oldest_file = 1; oldest_file < NUM_JFILES-1; oldest_file++)
+ {
+ for (u_int16_t bad_owi_file = oldest_file + 1; bad_owi_file < NUM_JFILES; bad_owi_file++)
+ {
+ m.journal_create(NUM_JFILES, NUM_JFILES, oldest_file, bad_owi_file);
+ m.write_journal(true, 16);
+
+ stringstream fn;
+ fn << test_dir << "/" << base_filename << "." << JRNL_INFO_EXTENSION;
+ jinf ji(fn.str(), false);
+ try
+ {
+ ji.analyze();
+ BOOST_FAIL("Failed to detect irregular OWI flag in min-sized ae journal file \"" << fn.str() << "\"");
+ }
+ catch (const jexception& e) {} // ignore - expected
+
+ m.destroy_journal();
+ }
+ }
+ cout << "done" << endl;
+}
+
+QPID_AUTO_TEST_SUITE_END()
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_ut_lpmgr.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_ut_lpmgr.cpp
new file mode 100644
index 0000000000..2dc20ffa7c
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/_ut_lpmgr.cpp
@@ -0,0 +1,886 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "../unit_test.h"
+#include <cmath>
+#include <iostream>
+#include "qpid/legacystore/jrnl/jcntl.h"
+#include "qpid/legacystore/jrnl/lpmgr.h"
+
+using namespace boost::unit_test;
+using namespace mrg::journal;
+using namespace std;
+
+QPID_AUTO_TEST_SUITE(arr_cnt_suite)
+
+const string test_filename("_ut_lpmgr");
+
+#include "_st_helper_fns.h"
+
+// === Helper functions and definitions ===
+
+typedef vector<u_int16_t> flist;
+typedef flist::const_iterator flist_citr;
+
+class lpmgr_test_helper
+{
+ lpmgr_test_helper() {}
+ virtual ~lpmgr_test_helper() {}
+
+public:
+ static void check_pfids_lfids(const lpmgr& lm, const u_int16_t pfids[], const u_int16_t lfids[],
+ const size_t pfid_lfid_size)
+ {
+ vector<u_int16_t> res;
+ lm.get_pfid_list(res);
+ vectors_equal(lm, pfids, pfid_lfid_size, res, true);
+ lm.get_lfid_list(res);
+ vectors_equal(lm, lfids, pfid_lfid_size, res, false);
+ }
+
+ static void check_pfids_lfids(const lpmgr& lm, const flist& pfids, const flist lfids)
+ {
+ vector<u_int16_t> res;
+ lm.get_pfid_list(res);
+ vectors_equal(lm, pfids, res, true);
+ lm.get_lfid_list(res);
+ vectors_equal(lm, lfids, res, false);
+ }
+
+ static void check_linear_pfids_lfids(const lpmgr& lm, const size_t pfid_lfid_size)
+ {
+ vector<u_int16_t> res;
+ lm.get_pfid_list(res);
+ linear_vectors_equal(lm, pfid_lfid_size, res, true);
+ lm.get_lfid_list(res);
+ linear_vectors_equal(lm, pfid_lfid_size, res, false);
+ }
+
+ static void rcvdat_init(rcvdat& rd, const u_int16_t num_jfiles, const bool ae, const u_int16_t ae_max_jfiles,
+ const u_int16_t pfids[])
+ {
+ rd.reset(num_jfiles, ae, ae_max_jfiles);
+ load_vector(pfids, num_jfiles, rd._fid_list);
+ rd._jempty = false;
+ rd._lfid = pfids[num_jfiles - 1];
+ rd._eo = 100 * JRNL_DBLK_SIZE * JRNL_SBLK_SIZE;
+ }
+
+ static void rcvdat_init(rcvdat& rd, const flist& pfidl, const bool ae, const u_int16_t ae_max_jfiles)
+ {
+ const u_int16_t num_jfiles = pfidl.size();
+ rd.reset(num_jfiles, ae, ae_max_jfiles);
+ load_vector(pfidl, rd._fid_list);
+ rd._jempty = false;
+ rd._lfid = pfidl[num_jfiles - 1];
+ rd._eo = 100 * JRNL_DBLK_SIZE * JRNL_SBLK_SIZE;
+ }
+
+ static void initialize(lpmgr& lm, test_jrnl& jc, const u_int16_t num_jfiles, const bool ae,
+ const u_int16_t ae_max_jfiles)
+ {
+ lm.initialize(num_jfiles, ae, ae_max_jfiles, &jc, &jc.new_fcntl);
+ BOOST_CHECK_EQUAL(lm.is_init(), true);
+ BOOST_CHECK_EQUAL(lm.is_ae(), ae);
+ BOOST_CHECK_EQUAL(lm.ae_max_jfiles(), ae_max_jfiles);
+ BOOST_CHECK_EQUAL(lm.num_jfiles(), num_jfiles);
+ if (num_jfiles)
+ check_linear_pfids_lfids(lm, num_jfiles);
+ else
+ BOOST_CHECK_EQUAL(lm.get_fcntlp(0), (void*)0);
+ }
+
+ // version which sets up the lfid_pfid_map for later manipulation by insert tests
+ static void initialize(lfid_pfid_map& lfm, lpmgr& lm, test_jrnl& jc, const u_int16_t num_jfiles, const bool ae,
+ const u_int16_t ae_max_jfiles)
+ {
+ lfm.journal_create(num_jfiles, num_jfiles);
+ initialize(lm, jc, num_jfiles, ae, ae_max_jfiles);
+ }
+
+ static void prepare_recover(lfid_pfid_map& lfm, const u_int16_t size)
+ {
+ if (size < 4) BOOST_FAIL("prepare_recover(): size parameter (" << size << ") too small.");
+ lfm.journal_create(4, 4); // initial journal of size 4
+ u_int16_t s = 4; // cumulative size
+ while (s < size)
+ {
+ const u_int16_t ins_posn = u_int16_t(s * ::drand48()); // this insert posn
+ if (3.0 * ::drand48() > 1.0 || size - s < 2) // 2:1 chance of single insert when >= 2 still to insert
+ {
+ lfm.journal_insert(ins_posn); // single insert
+ s++;
+ }
+ else
+ {
+ // multiple insert, either 2 - 5
+ const u_int16_t max_ins_size = size - s >5 ? 5 : size - s;
+ const u_int16_t ins_size = 2 + u_int16_t((max_ins_size - 2) * ::drand48()); // this insert size
+ lfm.journal_insert(ins_posn, ins_size);
+ s += ins_size;
+ }
+ }
+ }
+
+ static void recover(lfid_pfid_map& lfm, lpmgr& lm, test_jrnl& jc, const bool ae, const u_int16_t ae_max_jfiles)
+ {
+ flist pfidl;
+ flist lfidl;
+ rcvdat rd;
+ const u_int16_t num_jfiles = lfm.size();
+
+ lfm.get_pfid_list(pfidl);
+ lfm.get_lfid_list(lfidl);
+ lm.finalize(); // clear all file handles before erasing old journal files
+ lfm.write_journal(ae, ae_max_jfiles, JFSIZE_SBLKS);
+
+ lpmgr_test_helper::rcvdat_init(rd, pfidl, ae, ae_max_jfiles);
+ lm.recover(rd, &jc, &jc.new_fcntl);
+ BOOST_CHECK_EQUAL(lm.is_init(), true);
+ BOOST_CHECK_EQUAL(lm.is_ae(), ae);
+ BOOST_CHECK_EQUAL(lm.ae_max_jfiles(), ae_max_jfiles);
+ BOOST_CHECK_EQUAL(lm.num_jfiles(), num_jfiles);
+ if (num_jfiles)
+ check_pfids_lfids(lm, pfidl, lfidl);
+ else
+ BOOST_CHECK_EQUAL(lm.get_fcntlp(0), (void*)0);
+ }
+
+ static void finalize(lpmgr& lm)
+ {
+ lm.finalize();
+ BOOST_CHECK_EQUAL(lm.is_init(), false);
+ BOOST_CHECK_EQUAL(lm.is_ae(), false);
+ BOOST_CHECK_EQUAL(lm.ae_max_jfiles(), u_int16_t(0));
+ BOOST_CHECK_EQUAL(lm.num_jfiles(), u_int16_t(0));
+ BOOST_CHECK_EQUAL(lm.get_fcntlp(0), (void*)0);
+ vector<u_int16_t> res;
+ lm.get_pfid_list(res);
+ BOOST_CHECK_EQUAL(res.size(), u_int16_t(0));
+ lm.get_lfid_list(res);
+ BOOST_CHECK_EQUAL(res.size(), u_int16_t(0));
+ }
+
+ static void insert(lfid_pfid_map& lfm, lpmgr& lm, test_jrnl& jc, const u_int16_t after_lfid, const u_int16_t incr = 1)
+ {
+ flist pfidl;
+ flist lfidl;
+ const u_int16_t num_jfiles = lm.num_jfiles();
+ lfm.journal_insert(after_lfid, incr);
+ lfm.get_pfid_list(pfidl);
+ lfm.get_lfid_list(lfidl);
+ lm.insert(after_lfid, &jc, &jc.new_fcntl, incr);
+ BOOST_CHECK_EQUAL(lm.num_jfiles(), num_jfiles + incr);
+ lpmgr_test_helper::check_pfids_lfids(lm, pfidl, lfidl);
+ }
+
+ static void check_ae_max_jfiles(lpmgr& lm, const u_int16_t num_jfiles, const u_int16_t ae_max_jfiles)
+ {
+ bool legal = ae_max_jfiles > num_jfiles || ae_max_jfiles == 0;
+
+ lm.set_ae(false);
+ BOOST_CHECK(!lm.is_ae());
+ if (legal)
+ {
+ lm.set_ae_max_jfiles(ae_max_jfiles);
+ BOOST_CHECK_EQUAL(lm.ae_max_jfiles(), ae_max_jfiles);
+ lm.set_ae(true);
+ BOOST_CHECK(lm.is_ae());
+ BOOST_CHECK_EQUAL(lm.ae_jfiles_rem(), ae_max_jfiles
+ ? ae_max_jfiles - num_jfiles
+ : JRNL_MAX_NUM_FILES - num_jfiles);
+ }
+ else
+ {
+ lm.set_ae_max_jfiles(ae_max_jfiles);
+ BOOST_CHECK_EQUAL(lm.ae_max_jfiles(), ae_max_jfiles);
+ try
+ {
+ lm.set_ae(true); // should raise exception
+ BOOST_ERROR("Auto-expand enabled with out-of-range ae_max_jfiles");
+ }
+ catch (const jexception& e) { BOOST_CHECK_EQUAL(e.err_code(), jerrno::JERR_LFMGR_BADAEFNUMLIM); }
+ BOOST_CHECK(!lm.is_ae());
+ BOOST_CHECK_EQUAL(lm.ae_jfiles_rem(), 0);
+ }
+ BOOST_CHECK_EQUAL(lm.ae_max_jfiles(), ae_max_jfiles);
+ }
+
+ static void check_multiple_initialization_recover(lfid_pfid_map& lfm, test_jrnl& jc,
+ const u_int16_t num_jfiles_arr[][2], const bool init_flag_0, const bool finalize_flag,
+ const bool init_flag_1)
+ {
+ unsigned i_njf = 0;
+ while (num_jfiles_arr[i_njf][0] && num_jfiles_arr[i_njf][1]) // cycle through each entry in num_jfiles_arr
+ {
+ for (unsigned i1_njf = 0; i1_njf <= 1; i1_njf++) // cycle through the two numbers in each entry of num_jfiles_arr
+ {
+ const u_int16_t num_jfiles_0 = num_jfiles_arr[i_njf][i1_njf == 0]; // first number in pair
+ const u_int16_t num_jfiles_1 = num_jfiles_arr[i_njf][i1_njf != 0]; // second number in pair
+
+ for (unsigned i_ae = 0; i_ae < 4; i_ae++) // cycle through combinations of enabling AE
+ {
+ const bool ae_0 = i_ae & 0x1; // first bit: enable AE on first init
+ const bool ae_1 = i_ae & 0x2; // second bit: enable AE on second init
+ for (unsigned i_aemjf = 0; i_aemjf < 4; i_aemjf++) // cycle through combinations of enabling/disabling ae limit
+ {
+ const u_int16_t ae_max_jfiles_0 = i_aemjf & 0x1 ? 3 * num_jfiles_0 : 0; // max ae files, 0 = disable max
+ const u_int16_t ae_max_jfiles_1 = i_aemjf & 0x2 ? 4 * num_jfiles_1 : 0; // max ae files, 0 = disable max
+
+ lpmgr lm; // DUT
+
+ if (init_flag_0)
+ initialize(lm, jc, num_jfiles_0, ae_0, ae_max_jfiles_0);
+ else
+ {
+ prepare_recover(lfm, num_jfiles_0);
+ recover(lfm, lm, jc, ae_1, ae_max_jfiles_0);
+ lfm.destroy_journal();
+ }
+
+ if (finalize_flag) finalize(lm);
+
+ if (init_flag_1)
+ initialize(lm, jc, num_jfiles_1, ae_1, ae_max_jfiles_1);
+ else
+ {
+ prepare_recover(lfm, num_jfiles_1);
+ recover(lfm, lm, jc, ae_1, ae_max_jfiles_1);
+ lfm.destroy_journal();
+ }
+ }
+ }
+ }
+ i_njf++;
+ }
+ }
+
+ static void check_insert(lfid_pfid_map& lfm, lpmgr& lm, test_jrnl& jc, const u_int16_t after_lfid,
+ const u_int16_t incr = 1)
+ {
+ const u_int16_t num_jfiles = lm.num_jfiles();
+ const u_int16_t ae_max_jfiles = lm.ae_max_jfiles();
+ const u_int16_t effective_ae_max_jfiles = ae_max_jfiles ? ae_max_jfiles : JRNL_MAX_NUM_FILES;
+ BOOST_CHECK_EQUAL(lm.ae_jfiles_rem(), effective_ae_max_jfiles - num_jfiles);
+ bool legal = lm.is_ae() && num_jfiles + incr <= effective_ae_max_jfiles;
+ if (legal)
+ {
+ insert(lfm, lm, jc, after_lfid, incr);
+ BOOST_CHECK_EQUAL(lm.num_jfiles(), num_jfiles + incr);
+ BOOST_CHECK_EQUAL(lm.ae_jfiles_rem(), effective_ae_max_jfiles - num_jfiles - incr);
+ }
+ else
+ {
+ try
+ {
+ insert(lfm, lm, jc, after_lfid, incr);
+ if (lm.is_ae())
+ BOOST_ERROR("lpmgr::insert() succeeded and exceeded limit");
+ else
+ BOOST_ERROR("lpmgr::insert() succeeded with auto-expand disabled");
+ }
+ catch (const jexception& e)
+ {
+ if (lm.is_ae())
+ BOOST_CHECK_EQUAL(e.err_code(), jerrno::JERR_LFMGR_AEFNUMLIMIT);
+ else
+ BOOST_CHECK_EQUAL(e.err_code(), jerrno::JERR_LFMGR_AEDISABLED);
+ }
+ BOOST_CHECK_EQUAL(lm.num_jfiles(), num_jfiles);
+ BOOST_CHECK_EQUAL(lm.ae_jfiles_rem(), effective_ae_max_jfiles - num_jfiles);
+ }
+ }
+
+ static void check_limit(lfid_pfid_map& lfm, test_jrnl& jc, const bool ae, const u_int16_t num_jfiles,
+ const u_int16_t ae_max_jfiles)
+ {
+ lpmgr lm;
+
+ for (unsigned i = 0; i < 2; i++)
+ {
+ if (i)
+ initialize(lfm, lm, jc, num_jfiles, ae, ae_max_jfiles);
+ else
+ {
+ prepare_recover(lfm, num_jfiles);
+ recover(lfm, lm, jc, ae, ae_max_jfiles);
+ }
+
+ // use up all available files
+ unsigned j = ae_max_jfiles ? ae_max_jfiles : JRNL_MAX_NUM_FILES;
+ while (ae && j > num_jfiles)
+ {
+ const u_int16_t posn = static_cast<u_int16_t>((lm.num_jfiles() - 1) * ::drand48());
+ const u_int16_t incr = 1 + static_cast<u_int16_t>((lm.ae_jfiles_rem() > 4
+ ? 3 : lm.ae_jfiles_rem() - 1) * ::drand48());
+ check_insert(lfm, lm, jc, posn, incr);
+ j -= incr;
+ }
+ // these should be over the limit or illegal
+ check_insert(lfm, lm, jc, 0);
+ check_insert(lfm, lm, jc, 2, 2);
+ lfm.destroy_journal();
+ }
+ }
+
+private:
+ static void load_vector(const u_int16_t a[], const size_t n, flist& v)
+ {
+ for (size_t i = 0; i < n; i++)
+ v.push_back(a[i]);
+ }
+
+ static void load_vector(const flist& a, flist& b)
+ {
+ for (flist_citr i = a.begin(); i < a.end(); i++)
+ b.push_back(*i);
+ }
+
+ static void vectors_equal(const lpmgr& lm, const u_int16_t a[], const size_t n, const flist& b,
+ const bool pfid_check)
+ {
+ BOOST_CHECK_EQUAL(n, b.size());
+ for (size_t i = 0; i < n; i++)
+ {
+ BOOST_CHECK_EQUAL(a[i], b[i]);
+ fcntl* fp = lm.get_fcntlp(i);
+ BOOST_CHECK_MESSAGE(fp != (void*)0, "Unexpected void pointer returned by lpmgr::get_fcntlp()");
+ if (fp) BOOST_CHECK_EQUAL(pfid_check ? fp->pfid() : fp->lfid(), pfid_check ? a[i] : i);
+ }
+ }
+
+ static void vectors_equal(const lpmgr& lm, const flist& a, const flist& b, const bool pfid_check)
+ {
+ BOOST_CHECK_EQUAL(a.size(), b.size());
+ for (size_t i = 0; i < a.size(); i++)
+ {
+ BOOST_CHECK_EQUAL(a[i], b[i]);
+ fcntl* fp = lm.get_fcntlp(i);
+ BOOST_CHECK_MESSAGE(fp != (void*)0, "Unexpected void pointer returned by lpmgr::get_fcntlp()");
+ if (fp) BOOST_CHECK_EQUAL(pfid_check ? fp->pfid() : fp->lfid(), pfid_check ? a[i] : i);
+ }
+ }
+
+ static void linear_vectors_equal(const lpmgr& lm, const size_t n, const flist& f, const bool pfid_check)
+ {
+ BOOST_CHECK_EQUAL(n, f.size());
+ for (size_t i = 0; i < n; i++)
+ {
+ BOOST_CHECK_EQUAL(i, f[i]);
+ fcntl* fp = lm.get_fcntlp(i);
+ BOOST_CHECK_MESSAGE(fp != (void*)0, "Unexpected void pointer returned by lpmgr::get_fcntlp()");
+ if (fp) BOOST_CHECK_EQUAL(pfid_check ? fp->pfid() : fp->lfid(), i);
+ }
+ }
+};
+
+// === Tests ===
+
+#ifndef LONG_TEST
+/*
+ * ==============================================
+ * NORMAL TESTS
+ * This section contains normal "make check" tests
+ * for building/packaging. These are built when
+ * LONG_TEST is _not_ defined.
+ * ==============================================
+ */
+
+/*
+ * Check that after construction, the fcntl array _fcntl_arr is empty and the is_init() function returns false.
+ */
+QPID_AUTO_TEST_CASE(default_constructor)
+{
+ string test_name = get_test_name(test_filename, "default_constructor");
+ try
+ {
+ lpmgr lm;
+ BOOST_CHECK_EQUAL(lm.is_init(), false);
+ BOOST_CHECK_EQUAL(lm.is_ae(), false);
+ BOOST_CHECK_EQUAL(lm.ae_max_jfiles(), u_int16_t(0));
+ BOOST_CHECK_EQUAL(lm.num_jfiles(), u_int16_t(0));
+ BOOST_CHECK_EQUAL(lm.get_fcntlp(0), (void*)0);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "done" << endl;
+}
+
+/*
+ * Check that initialize() correctly creates an ordered fcntl array _fcntl_arr.
+ */
+QPID_AUTO_TEST_CASE(initialize)
+{
+ string test_name = get_test_name(test_filename, "initialize");
+ const u_int16_t num_jfiles = 8;
+ try
+ {
+ jdir::create_dir(test_dir); // Check test dir exists; create it if not
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ {
+ lpmgr lm;
+ lpmgr_test_helper::initialize(lm, jc, num_jfiles, false, 0);
+ }
+ {
+ lpmgr lm;
+ lpmgr_test_helper::initialize(lm, jc, num_jfiles, true, 0);
+ }
+ {
+ lpmgr lm;
+ lpmgr_test_helper::initialize(lm, jc, num_jfiles, true, 5 * num_jfiles);
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "done" << endl;
+}
+
+/*
+ * Check that recover() correctly sets up the specified pfid list order.
+ */
+QPID_AUTO_TEST_CASE(recover)
+{
+ string test_name = get_test_name(test_filename, "recover");
+ ::srand48(1); // init random gen for repeatable tests when using lpmgr_test_helper::prepare_recover()
+ try
+ {
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ lfid_pfid_map lfm(test_name, test_name);
+
+ {
+ lpmgr lm;
+ lpmgr_test_helper::prepare_recover(lfm, 8);
+ lpmgr_test_helper::recover(lfm, lm, jc, false, 0);
+ lfm.destroy_journal();
+ }
+ {
+ lpmgr lm;
+ lpmgr_test_helper::prepare_recover(lfm, 8);
+ lpmgr_test_helper::recover(lfm, lm, jc, true, 0);
+ lfm.destroy_journal();
+ }
+ {
+ lpmgr lm;
+ lpmgr_test_helper::prepare_recover(lfm, 8);
+ lpmgr_test_helper::recover(lfm, lm, jc, true, 5 * lfm.size());
+ lfm.destroy_journal();
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "done" << endl;
+}
+
+/*
+ * Check that finalize() after an initialize() empties _fcntl_arr and that afterwards is_init() returns false.
+ */
+QPID_AUTO_TEST_CASE(initialize_finalize)
+{
+ string test_name = get_test_name(test_filename, "initialize_finalize");
+ const u_int16_t num_jfiles = 8;
+ try
+ {
+ jdir::create_dir(test_dir); // Check test dir exists; create it if not
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ {
+ lpmgr lm;
+ lpmgr_test_helper::initialize(lm, jc, num_jfiles, false, 0);
+ lpmgr_test_helper::finalize(lm);
+ }
+ {
+ lpmgr lm;
+ lpmgr_test_helper::initialize(lm, jc, num_jfiles, true, 0);
+ lpmgr_test_helper::finalize(lm);
+ }
+ {
+ lpmgr lm;
+ lpmgr_test_helper::initialize(lm, jc, num_jfiles, true, 5 * num_jfiles);
+ lpmgr_test_helper::finalize(lm);
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "done" << endl;
+}
+
+/*
+ * Check that finalize() after a recover() empties _fcntl_arr and that afterwards is_init() returns false.
+ */
+QPID_AUTO_TEST_CASE(recover_finalize)
+{
+ string test_name = get_test_name(test_filename, "recover_finalize");
+ const u_int16_t num_jfiles = 8;
+ ::srand48(1); // init random gen for repeatable tests when using lpmgr_test_helper::prepare_recover()
+ try
+ {
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ lfid_pfid_map lfm(test_name, test_name);
+
+ {
+ lpmgr lm;
+ lpmgr_test_helper::prepare_recover(lfm, num_jfiles);
+ lpmgr_test_helper::recover(lfm, lm, jc, false, 0);
+ lpmgr_test_helper::finalize(lm);
+ lfm.destroy_journal();
+ }
+ {
+ lpmgr lm;
+ lpmgr_test_helper::prepare_recover(lfm, num_jfiles);
+ lpmgr_test_helper::recover(lfm, lm, jc, true, 0);
+ lpmgr_test_helper::finalize(lm);
+ lfm.destroy_journal();
+ }
+ {
+ lpmgr lm;
+ lpmgr_test_helper::prepare_recover(lfm, num_jfiles);
+ lpmgr_test_helper::recover(lfm, lm, jc, true, 5 * lfm.size());
+ lpmgr_test_helper::finalize(lm);
+ lfm.destroy_journal();
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "done" << endl;
+}
+
+/*
+ * Check that 0 and/or null and other extreme/boundary parameters behave as expected.
+ */
+QPID_AUTO_TEST_CASE(zero_null_params)
+{
+ string test_name = get_test_name(test_filename, "zero_null_params");
+ const u_int16_t num_jfiles = 8;
+ try
+ {
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ lfid_pfid_map lfm(test_name, test_name);
+ lpmgr lm;
+ lpmgr_test_helper::initialize(lfm, lm, jc, num_jfiles, true, 0);
+
+ // Check that inserting 0 files works ok
+ lpmgr_test_helper::insert(lfm, lm, jc, 0, 0);
+ lpmgr_test_helper::insert(lfm, lm, jc, 2, 0);
+ lpmgr_test_helper::insert(lfm, lm, jc, num_jfiles - 1, 0);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "done" << endl;
+}
+
+/*
+ * Check that initialize()/recover() works correctly after a previous initialize()/recover() with/without an intervening
+ * finalize().
+ */
+QPID_AUTO_TEST_CASE(multiple_initialization_recover)
+{
+ string test_name = get_test_name(test_filename, "multiple_initialization_recover");
+ ::srand48(1); // init random gen for repeatable tests when using lpmgr_test_helper::prepare_recover()
+
+ // Set combinations of value pairs to be used for number of journal files in first and second init
+ u_int16_t num_jfiles_arr[][2] = {{8, 12}, {4, 7}, {0, 0}}; // end with zeros
+ try
+ {
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ lfid_pfid_map lfm(test_name, test_name);
+ for (unsigned p = 0; p < 8; p++)
+ {
+ const bool i_0 = p & 0x01; // first bit
+ const bool i_1 = p & 0x02; // second bit
+ const bool f = p & 0x04; // third bit
+ lpmgr_test_helper::check_multiple_initialization_recover(lfm, jc, num_jfiles_arr, i_0, f, i_1);
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "done" << endl;
+}
+
+/*
+ * Check that insert() works correctly after initialize() and shifts the pfid sequence beyond the insert point correctly:
+ *
+ * The following sequence is tested:
+ * initialize 4 pfids=[0,1,2,3] lfids=[0,1,2,3]
+ * insert 1 after lfid 0 pfids=[0,4,1,2,3] lfids=[0,2,3,4,1]
+ * insert 2 after lfid 2 pfids=[0,4,1,5,6,2,3] lfids=[0,2,5,6,1,3,4]
+ * insert 1 after lfid 6 pfids=[0,4,1,5,6,2,3,7] lfids=[0,2,5,6,1,3,4,7]
+ * issert 1 after lfid 3 pfids=[0,4,1,5,8,6,2,3,7] lfids=[0,2,6,7,1,3,5,8,4]
+ */
+QPID_AUTO_TEST_CASE(initialize_insert)
+{
+ string test_name = get_test_name(test_filename, "initialize_insert");
+ const u_int16_t initial_num_jfiles = 8;
+ try
+ {
+ jdir::create_dir(test_dir); // Check test dir exists; create it if not
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ lfid_pfid_map lfm(test_name, test_name);
+ lpmgr lm;
+ lpmgr_test_helper::initialize(lfm, lm, jc, initial_num_jfiles, true, 0);
+
+ lpmgr_test_helper::insert(lfm, lm, jc, 0);
+ lpmgr_test_helper::insert(lfm, lm, jc, 2, 2);
+ lpmgr_test_helper::insert(lfm, lm, jc, 6);
+ lpmgr_test_helper::insert(lfm, lm, jc, 3);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "done" << endl;
+}
+
+/*
+ * Check that insert() works correctly after recover() and shifts the pfid sequence beyond the insert point correctly:
+ *
+ * The following sequence is tested:
+ * recover 4 pfids=[0,2,3,1] lfids=[0,3,1,2]
+ * insert 1 after lfid 0 pfids=[0,4,2,3,1] lfids=[0,4,2,3,1]
+ * insert 2 after lfid 2 pfids=[0,4,2,5,6,3,1] lfids=[0,6,2,5,1,3,4]
+ * insert 1 after lfid 6 pfids=[0,4,2,5,6,3,1,7] lfids=[0,6,2,5,1,3,4,7]
+ * issert 1 after lfid 3 pfids=[0,4,2,5,8,6,3,1,7] lfids=[0,7,2,6,1,3,5,8,4]
+ */
+QPID_AUTO_TEST_CASE(recover_insert)
+{
+ string test_name = get_test_name(test_filename, "recover_insert");
+ const u_int16_t initial_num_jfiles = 4;
+ ::srand48(1); // init random gen for repeatable tests when using lpmgr_test_helper::prepare_recover()
+ try
+ {
+ jdir::create_dir(test_dir); // Check test dir exists; create it if not
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ lfid_pfid_map lfm(test_name, test_name);
+ lpmgr lm;
+ lpmgr_test_helper::prepare_recover(lfm, initial_num_jfiles);
+ lpmgr_test_helper::recover(lfm, lm, jc, true, 0);
+
+ lpmgr_test_helper::insert(lfm, lm, jc, 0);
+ lpmgr_test_helper::insert(lfm, lm, jc, 2, 2);
+ lpmgr_test_helper::insert(lfm, lm, jc, 6);
+ lpmgr_test_helper::insert(lfm, lm, jc, 3);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "done" << endl;
+}
+
+/*
+ * Check that illegal ae parameter combinations are caught and result in an exception being thrown.
+ */
+QPID_AUTO_TEST_CASE(ae_parameters)
+{
+ string test_name = get_test_name(test_filename, "ae_parameters");
+ ::srand48(1); // init random gen for repeatable tests when using lpmgr_test_helper::prepare_recover()
+ try
+ {
+ jdir::create_dir(test_dir); // Check test dir exists; create it if not
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ lfid_pfid_map lfm(test_name, test_name);
+ const u_int16_t num_jfiles = 8;
+ lpmgr lm;
+
+ for (unsigned i = 0; i < 2; i++)
+ {
+ if (i)
+ lpmgr_test_helper::initialize(lfm, lm, jc, num_jfiles, false, 0);
+ else
+ {
+ lpmgr_test_helper::prepare_recover(lfm, num_jfiles);
+ lpmgr_test_helper::recover(lfm, lm, jc, false, 0);
+ }
+
+ lpmgr_test_helper::check_ae_max_jfiles(lm, num_jfiles, num_jfiles - 2);
+ lpmgr_test_helper::check_ae_max_jfiles(lm, num_jfiles, 0);
+ lpmgr_test_helper::check_ae_max_jfiles(lm, num_jfiles, 2 * num_jfiles);
+ lpmgr_test_helper::check_ae_max_jfiles(lm, num_jfiles, num_jfiles);
+ lfm.destroy_journal();
+ }
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "done" << endl;
+}
+
+/*
+ * Check that initialized or recovered journals with auto-expand disabled will not allow either inserts or appends.
+ */
+QPID_AUTO_TEST_CASE(ae_disabled)
+{
+ string test_name = get_test_name(test_filename, "ae_disabled");
+ ::srand48(1); // init random gen for repeatable tests when using lpmgr_test_helper::prepare_recover()
+ try
+ {
+ jdir::create_dir(test_dir); // Check test dir exists; create it if not
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ lfid_pfid_map lfm(test_name, test_name);
+ lpmgr_test_helper::check_limit(lfm, jc, false, 8, 0);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "done" << endl;
+}
+
+/*
+ * Check that initialized or recovered journals with auto-expand enabled and a file limit set will enforce the correct
+ * limits on inserts and appends.
+ */
+QPID_AUTO_TEST_CASE(ae_enabled_limit)
+{
+ string test_name = get_test_name(test_filename, "ae_enabled_limit");
+ ::srand48(1); // init random gen for repeatable tests when using lpmgr_test_helper::prepare_recover()
+ try
+ {
+ jdir::create_dir(test_dir); // Check test dir exists; create it if not
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ lfid_pfid_map lfm(test_name, test_name);
+ lpmgr_test_helper::check_limit(lfm, jc, true, 8, 32);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "done" << endl;
+}
+
+/*
+ * Check that initialized or recovered journals with auto-expand enabled and no file limit set (0) will allow inserts and
+ * appends up to the file limit JRNL_MAX_NUM_FILES.
+ */
+QPID_AUTO_TEST_CASE(ae_enabled_unlimited)
+{
+ string test_name = get_test_name(test_filename, "ae_enabled_unlimited");
+ ::srand48(1); // init random gen for repeatable tests when using lpmgr_test_helper::prepare_recover()
+ try
+ {
+ jdir::create_dir(test_dir); // Check test dir exists; create it if not
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ lfid_pfid_map lfm(test_name, test_name);
+ lpmgr_test_helper::check_limit(lfm, jc, true, 8, 0);
+ }
+ catch(const exception& e) { BOOST_FAIL(e.what()); }
+ cout << "done" << endl;
+}
+
+#else
+/*
+ * ==============================================
+ * LONG TESTS
+ * This section contains long tests and soak tests,
+ * and are run using target check-long (ie "make
+ * check-long"). These are built when LONG_TEST is
+ * defined.
+ * ==============================================
+ */
+
+/*
+ * Tests randomized combinations of initialization/recovery, initial size, number, size and location of inserts.
+ *
+ * To reproduce a specific test, comment out the get_seed() statement and uncomment the literal below, adjusting the seed
+ * value to that required.
+ */
+QPID_AUTO_TEST_CASE(randomized_tests)
+{
+ string test_name = get_test_name(test_filename, "randomized_tests");
+ const long seed = get_seed();
+ // const long seed = 0x2d9b69d32;
+ cout << "seed=0x" << hex << seed << dec << " " << flush;
+ ::srand48(seed);
+
+ lfid_pfid_map lfm(test_name, test_name);
+ flist pfidl;
+ flist lfidl;
+ rcvdat rd;
+ u_int16_t curr_ae_max_jfiles = 0;
+ jdir::create_dir(test_dir); // Check test dir exists; create it if not
+
+ for (int test_num = 0; test_num < 250; test_num++)
+ {
+ test_jrnl_cb cb;
+ test_jrnl jc(test_name, test_dir, test_name, cb);
+ lpmgr lm;
+ // 50% chance of recovery except first run and if there is still ae space left
+ const bool recover_flag = test_num > 0 &&
+ curr_ae_max_jfiles > lfm.size() &&
+ 2.0 * ::drand48() < 1.0;
+ if (recover_flag)
+ {
+ // Recover from previous iteration
+ lfm.get_pfid_list(pfidl);
+ lfm.get_lfid_list(lfidl);
+ lfm.write_journal(true, curr_ae_max_jfiles, JFSIZE_SBLKS);
+ lpmgr_test_helper::rcvdat_init(rd, pfidl, true, curr_ae_max_jfiles);
+ lm.recover(rd, &jc, &jc.new_fcntl);
+ lpmgr_test_helper::check_pfids_lfids(lm, pfidl, lfidl);
+ }
+ else
+ {
+ // Initialize from scratch
+ const u_int16_t num_jfiles = 4 + u_int16_t(21.0 * ::drand48()); // size: 4 - 25 files
+ curr_ae_max_jfiles = u_int16_t(4 * num_jfiles * ::drand48()); // size: 0 - 100 files
+ if (curr_ae_max_jfiles > JRNL_MAX_NUM_FILES) curr_ae_max_jfiles = JRNL_MAX_NUM_FILES;
+ else if (curr_ae_max_jfiles <= num_jfiles) curr_ae_max_jfiles = 0;
+ lfm.destroy_journal();
+ lfm.journal_create(num_jfiles, num_jfiles);
+ lfm.get_pfid_list(pfidl);
+ lfm.get_lfid_list(lfidl);
+ lm.initialize(num_jfiles, true, curr_ae_max_jfiles, &jc, &jc.new_fcntl);
+ lpmgr_test_helper::check_linear_pfids_lfids(lm, num_jfiles);
+ }
+
+ // Loop to insert pfids
+ const int num_inserts = 1 + int(lfm.size() * ::drand48());
+ for (int i = 0; i < num_inserts; i++)
+ {
+ const u_int16_t size = lm.num_jfiles();
+ const u_int16_t after_lfid = u_int16_t(1.0 * size * ::drand48());
+ const u_int16_t num_jfiles = 1 + u_int16_t(4.0 * ::drand48());
+ const bool legal = lm.ae_max_jfiles()
+ ? size + num_jfiles <= lm.ae_max_jfiles()
+ : size + num_jfiles <= JRNL_MAX_NUM_FILES;
+ if (legal)
+ {
+ lfm.journal_insert(after_lfid, num_jfiles);
+ lfm.get_pfid_list(pfidl);
+ lfm.get_lfid_list(lfidl);
+
+ lm.insert(after_lfid, &jc, &jc.new_fcntl, num_jfiles);
+ lpmgr_test_helper::check_pfids_lfids(lm, pfidl, lfidl);
+ }
+ else
+ {
+ try
+ {
+ lm.insert(after_lfid, &jc, &jc.new_fcntl, num_jfiles);
+ BOOST_FAIL("lpmgr::insert() succeeded and exceeded limit");
+ }
+ catch (const jexception& e)
+ {
+ BOOST_CHECK_EQUAL(e.err_code(), jerrno::JERR_LFMGR_AEFNUMLIMIT);
+ break; // no more inserts...
+ }
+ }
+ }
+ lm.finalize();
+ BOOST_CHECK_EQUAL(lm.is_init(), false);
+ BOOST_CHECK_EQUAL(lm.num_jfiles(), u_int16_t(0));
+ BOOST_CHECK_EQUAL(lm.get_fcntlp(0), (void*)0);
+ }
+ cout << "done" << endl;
+}
+
+#endif
+
+QPID_AUTO_TEST_SUITE_END()
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_ut_rec_hdr.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_ut_rec_hdr.cpp
new file mode 100644
index 0000000000..099e576bbd
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/_ut_rec_hdr.cpp
@@ -0,0 +1,438 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "../unit_test.h"
+
+#include <ctime>
+#include <iostream>
+#include "qpid/legacystore/jrnl/deq_hdr.h"
+#include "qpid/legacystore/jrnl/enq_hdr.h"
+#include "qpid/legacystore/jrnl/file_hdr.h"
+#include "qpid/legacystore/jrnl/jcfg.h"
+#include "qpid/legacystore/jrnl/rec_tail.h"
+#include "qpid/legacystore/jrnl/txn_hdr.h"
+
+using namespace boost::unit_test;
+using namespace mrg::journal;
+using namespace std;
+
+QPID_AUTO_TEST_SUITE(rec_hdr_suite)
+
+const string test_filename("_ut_rec_hdr");
+
+QPID_AUTO_TEST_CASE(hdr_class)
+{
+ cout << test_filename << ".hdr_class: " << flush;
+ rec_hdr h1;
+ BOOST_CHECK_EQUAL(h1._magic, 0UL);
+ BOOST_CHECK_EQUAL(h1._version, 0);
+ BOOST_CHECK_EQUAL(h1._eflag, 0);
+ BOOST_CHECK_EQUAL(h1._uflag, 0);
+ BOOST_CHECK_EQUAL(h1._rid, 0ULL);
+ BOOST_CHECK(!h1.get_owi());
+
+ const u_int32_t magic = 0x89abcdefUL;
+ const u_int16_t uflag = 0x5537;
+ const u_int8_t version = 0xef;
+ const u_int64_t rid = 0x123456789abcdef0ULL;
+ const bool owi = true;
+
+ rec_hdr h2(magic, version, rid, owi);
+ BOOST_CHECK_EQUAL(h2._magic, magic);
+ BOOST_CHECK_EQUAL(h2._version, version);
+#ifdef JRNL_LITTLE_ENDIAN
+ BOOST_CHECK_EQUAL(h2._eflag, RHM_LENDIAN_FLAG);
+#else
+ BOOST_CHECK_EQUAL(h2._eflag, RHM_BENDIAN_FLAG);
+#endif
+ BOOST_CHECK_EQUAL(h2._uflag, (const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK);
+ BOOST_CHECK_EQUAL(h2._rid, rid);
+ BOOST_CHECK_EQUAL(h2.get_owi(), owi);
+ h2._uflag = uflag;
+ BOOST_CHECK(h2.get_owi());
+ h2.set_owi(true);
+ BOOST_CHECK(h2.get_owi());
+ BOOST_CHECK_EQUAL(h2._uflag, uflag);
+ h2.set_owi(false);
+ BOOST_CHECK(!h2.get_owi());
+ BOOST_CHECK_EQUAL(h2._uflag, (uflag & ~(const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK));
+ h2.set_owi(true);
+ BOOST_CHECK(h2.get_owi());
+ BOOST_CHECK_EQUAL(h2._uflag, uflag);
+
+ h1.hdr_copy(h2);
+ BOOST_CHECK_EQUAL(h1._magic, magic);
+ BOOST_CHECK_EQUAL(h1._version, version);
+#ifdef JRNL_LITTLE_ENDIAN
+ BOOST_CHECK_EQUAL(h1._eflag, RHM_LENDIAN_FLAG);
+#else
+ BOOST_CHECK_EQUAL(h1._eflag, RHM_BENDIAN_FLAG);
+#endif
+ BOOST_CHECK_EQUAL(h1._uflag, uflag);
+ BOOST_CHECK_EQUAL(h1._rid, rid);
+ BOOST_CHECK(h1.get_owi());
+ BOOST_CHECK_EQUAL(h1._uflag, uflag);
+
+ h1.reset();
+ BOOST_CHECK_EQUAL(h1._magic, 0UL);
+ BOOST_CHECK_EQUAL(h1._version, 0);
+ BOOST_CHECK_EQUAL(h1._eflag, 0);
+ BOOST_CHECK_EQUAL(h1._uflag, 0);
+ BOOST_CHECK_EQUAL(h1._rid, 0ULL);
+ BOOST_CHECK(!h1.get_owi());
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(rec_tail_class)
+{
+ cout << test_filename << ".rec_tail_class: " << flush;
+ const u_int32_t magic = 0xfedcba98;
+ const u_int64_t rid = 0xfedcba9876543210ULL;
+ const u_int32_t xmagic = ~magic;
+
+ {
+ rec_tail rt1;
+ BOOST_CHECK_EQUAL(rt1._xmagic, 0xffffffffUL);
+ BOOST_CHECK_EQUAL(rt1._rid, 0ULL);
+ }
+
+ {
+ rec_tail rt2(magic, rid);
+ BOOST_CHECK_EQUAL(rt2._xmagic, magic);
+ BOOST_CHECK_EQUAL(rt2._rid, rid);
+ }
+
+ {
+ rec_hdr h(magic, RHM_JDAT_VERSION, rid, true);
+ rec_tail rt3(h);
+ BOOST_CHECK_EQUAL(rt3._xmagic, xmagic);
+ BOOST_CHECK_EQUAL(rt3._rid, rid);
+ }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(file_hdr_class)
+{
+ cout << test_filename << ".file_hdr_class: " << flush;
+ const u_int32_t magic = 0xfedcba98UL;
+ const u_int8_t version = 0xa5;
+ const u_int16_t uflag = 0x5537;
+ const u_int64_t rid = 0xfedcba9876543210ULL;
+ const u_int16_t pfid = 0xfedcU;
+ const u_int16_t lfid = 0xf0e1U;
+#ifdef JRNL_32_BIT
+ const std::size_t fro = 0xfedcba98UL;
+#else
+ const std::size_t fro = 0xfedcba9876543210ULL;
+#endif
+ timespec ts;
+ const bool owi = true;
+
+ {
+ file_hdr fh1;
+ BOOST_CHECK_EQUAL(fh1._magic, 0UL);
+ BOOST_CHECK_EQUAL(fh1._version, 0);
+ BOOST_CHECK_EQUAL(fh1._eflag, 0);
+ BOOST_CHECK_EQUAL(fh1._uflag, 0);
+ BOOST_CHECK_EQUAL(fh1._rid, 0ULL);
+ BOOST_CHECK_EQUAL(fh1._pfid, 0UL);
+ BOOST_CHECK_EQUAL(fh1._lfid, 0U);
+ BOOST_CHECK_EQUAL(fh1._fro, std::size_t(0));
+ BOOST_CHECK_EQUAL(fh1._ts_sec, std::time_t(0));
+ BOOST_CHECK_EQUAL(fh1._ts_nsec, u_int32_t(0));
+ BOOST_CHECK(!fh1.get_owi());
+ }
+
+ {
+ file_hdr fh2(magic, version, rid, pfid, lfid, fro, owi, false);
+ BOOST_CHECK_EQUAL(fh2._magic, magic);
+ BOOST_CHECK_EQUAL(fh2._version, version);
+#ifdef JRNL_LITTLE_ENDIAN
+ BOOST_CHECK_EQUAL(fh2._eflag, RHM_LENDIAN_FLAG);
+#else
+ BOOST_CHECK_EQUAL(fh2._eflag, RHM_BENDIAN_FLAG);
+#endif
+ BOOST_CHECK_EQUAL(fh2._uflag, (const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK);
+ BOOST_CHECK_EQUAL(fh2._rid, rid);
+ BOOST_CHECK_EQUAL(fh2._pfid, pfid );
+ BOOST_CHECK_EQUAL(fh2._lfid, lfid);
+ BOOST_CHECK_EQUAL(fh2._fro, fro);
+ BOOST_CHECK_EQUAL(fh2._ts_sec, std::time_t(0));
+ BOOST_CHECK_EQUAL(fh2._ts_nsec, u_int32_t(0));
+ ::clock_gettime(CLOCK_REALTIME, &ts);
+ fh2.set_time(ts);
+ BOOST_CHECK_EQUAL(fh2._ts_sec, ts.tv_sec);
+ BOOST_CHECK_EQUAL(fh2._ts_nsec, u_int32_t(ts.tv_nsec));
+ BOOST_CHECK(fh2.get_owi());
+
+ fh2._uflag = uflag;
+ BOOST_CHECK(fh2.get_owi());
+
+ fh2.set_owi(false);
+ BOOST_CHECK(!fh2.get_owi());
+ BOOST_CHECK_EQUAL(fh2._uflag,
+ (uflag & ~(const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK));
+
+ fh2.set_owi(true);
+ BOOST_CHECK(fh2.get_owi());
+ BOOST_CHECK_EQUAL(fh2._uflag, uflag);
+ }
+
+ {
+ file_hdr fh3(magic, version, rid, pfid, lfid, fro, owi, true);
+ BOOST_CHECK_EQUAL(fh3._magic, magic);
+ BOOST_CHECK_EQUAL(fh3._version, version);
+#ifdef JRNL_LITTLE_ENDIAN
+ BOOST_CHECK_EQUAL(fh3._eflag, RHM_LENDIAN_FLAG);
+#else
+ BOOST_CHECK_EQUAL(fh3._eflag, RHM_BENDIAN_FLAG);
+#endif
+ BOOST_CHECK_EQUAL(fh3._uflag, (const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK);
+ BOOST_CHECK_EQUAL(fh3._rid, rid);
+ BOOST_CHECK_EQUAL(fh3._pfid, pfid);
+ BOOST_CHECK_EQUAL(fh3._lfid, lfid);
+ BOOST_CHECK_EQUAL(fh3._fro, fro);
+ BOOST_CHECK(fh3._ts_sec - ts.tv_sec <= 1); // No more than 1 sec difference
+ }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(enq_hdr_class)
+{
+ cout << test_filename << ".enq_hdr_class: " << flush;
+ const u_int32_t magic = 0xfedcba98UL;
+ const u_int8_t version = 0xa5;
+ const u_int64_t rid = 0xfedcba9876543210ULL;
+ const u_int16_t uflag = 0x5537;
+#ifdef JRNL_32_BIT
+ const std::size_t xidsize = 0xfedcba98UL;
+ const std::size_t dsize = 0x76543210UL;
+#else
+ const std::size_t xidsize = 0xfedcba9876543210ULL;
+ const std::size_t dsize = 0x76543210fedcba98ULL;
+#endif
+ const bool owi = true;
+
+ {
+ enq_hdr eh1;
+ BOOST_CHECK_EQUAL(eh1._magic, 0UL);
+ BOOST_CHECK_EQUAL(eh1._version, 0);
+ BOOST_CHECK_EQUAL(eh1._eflag, 0);
+ BOOST_CHECK_EQUAL(eh1._uflag, 0);
+ BOOST_CHECK_EQUAL(eh1._rid, 0ULL);
+ BOOST_CHECK_EQUAL(eh1._xidsize, std::size_t(0));
+ BOOST_CHECK_EQUAL(eh1._dsize, std::size_t(0));
+ BOOST_CHECK(!eh1.get_owi());
+ }
+
+ {
+ enq_hdr eh2(magic, version, rid, xidsize, dsize, owi, false);
+ BOOST_CHECK_EQUAL(eh2._magic, magic);
+ BOOST_CHECK_EQUAL(eh2._version, version);
+#ifdef JRNL_LITTLE_ENDIAN
+ BOOST_CHECK_EQUAL(eh2._eflag, RHM_LENDIAN_FLAG);
+#else
+ BOOST_CHECK_EQUAL(eh2._eflag, RHM_BENDIAN_FLAG);
+#endif
+ BOOST_CHECK_EQUAL(eh2._uflag, (const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK);
+ BOOST_CHECK_EQUAL(eh2._rid, rid);
+ BOOST_CHECK_EQUAL(eh2._xidsize, xidsize);
+ BOOST_CHECK_EQUAL(eh2._dsize, dsize);
+ BOOST_CHECK(eh2.get_owi());
+ BOOST_CHECK(!eh2.is_transient());
+ BOOST_CHECK(!eh2.is_external());
+
+ eh2._uflag = uflag;
+ BOOST_CHECK(eh2.get_owi());
+ BOOST_CHECK(eh2.is_transient());
+ BOOST_CHECK(eh2.is_external());
+
+ eh2.set_owi(false);
+ BOOST_CHECK(!eh2.get_owi());
+ BOOST_CHECK(eh2.is_transient());
+ BOOST_CHECK(eh2.is_external());
+ BOOST_CHECK_EQUAL(eh2._uflag,
+ (uflag & ~(const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK));
+
+ eh2.set_owi(true);
+ BOOST_CHECK(eh2.get_owi());
+ BOOST_CHECK(eh2.is_transient());
+ BOOST_CHECK(eh2.is_external());
+ BOOST_CHECK_EQUAL(eh2._uflag, uflag);
+
+ eh2.set_transient(false);
+ BOOST_CHECK(eh2.get_owi());
+ BOOST_CHECK(!eh2.is_transient());
+ BOOST_CHECK(eh2.is_external());
+ BOOST_CHECK_EQUAL(eh2._uflag, uflag & ~(const u_int16_t)enq_hdr::ENQ_HDR_TRANSIENT_MASK);
+
+ eh2.set_transient(true);
+ BOOST_CHECK(eh2.get_owi());
+ BOOST_CHECK(eh2.is_transient());
+ BOOST_CHECK(eh2.is_external());
+ BOOST_CHECK_EQUAL(eh2._uflag, uflag);
+
+ eh2.set_external(false);
+ BOOST_CHECK(eh2.get_owi());
+ BOOST_CHECK(eh2.is_transient());
+ BOOST_CHECK(!eh2.is_external());
+ BOOST_CHECK_EQUAL(eh2._uflag, uflag & ~(const u_int16_t)enq_hdr::ENQ_HDR_EXTERNAL_MASK);
+
+ eh2.set_external(true);
+ BOOST_CHECK(eh2.get_owi());
+ BOOST_CHECK(eh2.is_transient());
+ BOOST_CHECK(eh2.is_external());
+ BOOST_CHECK_EQUAL(eh2._uflag, uflag);
+ }
+
+ {
+ enq_hdr eh3(magic, version, rid, xidsize, dsize, owi, true);
+ BOOST_CHECK_EQUAL(eh3._magic, magic);
+ BOOST_CHECK_EQUAL(eh3._version, version);
+#ifdef JRNL_LITTLE_ENDIAN
+ BOOST_CHECK_EQUAL(eh3._eflag, RHM_LENDIAN_FLAG);
+#else
+ BOOST_CHECK_EQUAL(eh3._eflag, RHM_BENDIAN_FLAG);
+#endif
+ BOOST_CHECK_EQUAL(eh3._uflag, (const u_int16_t)enq_hdr::ENQ_HDR_TRANSIENT_MASK |
+ (const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK);
+ BOOST_CHECK_EQUAL(eh3._rid, rid);
+ BOOST_CHECK_EQUAL(eh3._xidsize, xidsize);
+ BOOST_CHECK_EQUAL(eh3._dsize, dsize);
+ BOOST_CHECK(eh3.get_owi());
+ BOOST_CHECK(eh3.is_transient());
+ BOOST_CHECK(!eh3.is_external());
+ }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(deq_hdr_class)
+{
+ cout << test_filename << ".deq_hdr_class: " << flush;
+ const u_int32_t magic = 0xfedcba98UL;
+ const u_int8_t version = 0xa5;
+ const u_int16_t uflag = 0x5537;
+ const u_int64_t rid = 0xfedcba9876543210ULL;
+ const u_int64_t drid = 0x76543210fedcba98ULL;
+#ifdef JRNL_32_BIT
+ const std::size_t xidsize = 0xfedcba98UL;
+#else
+ const std::size_t xidsize = 0xfedcba9876543210ULL;
+#endif
+ const bool owi = true;
+
+ {
+ deq_hdr dh1;
+ BOOST_CHECK_EQUAL(dh1._magic, 0UL);
+ BOOST_CHECK_EQUAL(dh1._version, 0);
+ BOOST_CHECK_EQUAL(dh1._eflag, 0);
+ BOOST_CHECK_EQUAL(dh1._uflag, 0);
+ BOOST_CHECK_EQUAL(dh1._rid, 0ULL);
+ BOOST_CHECK_EQUAL(dh1._deq_rid, 0ULL);
+ BOOST_CHECK_EQUAL(dh1._xidsize, std::size_t(0));
+ BOOST_CHECK(!dh1.get_owi());
+ }
+
+ {
+ deq_hdr dh2(magic, version, rid, drid, xidsize, owi);
+ BOOST_CHECK_EQUAL(dh2._magic, magic);
+ BOOST_CHECK_EQUAL(dh2._version, version);
+#ifdef JRNL_LITTLE_ENDIAN
+ BOOST_CHECK_EQUAL(dh2._eflag, RHM_LENDIAN_FLAG);
+#else
+ BOOST_CHECK_EQUAL(dh2._eflag, RHM_BENDIAN_FLAG);
+#endif
+ BOOST_CHECK_EQUAL(dh2._uflag, (const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK);
+ BOOST_CHECK_EQUAL(dh2._rid, rid);
+ BOOST_CHECK_EQUAL(dh2._deq_rid, drid);
+ BOOST_CHECK_EQUAL(dh2._xidsize, xidsize);
+ BOOST_CHECK(dh2.get_owi());
+
+ dh2._uflag = uflag;
+ BOOST_CHECK(dh2.get_owi());
+
+ dh2.set_owi(false);
+ BOOST_CHECK(!dh2.get_owi());
+ BOOST_CHECK_EQUAL(dh2._uflag,
+ (uflag & ~(const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK));
+
+ dh2.set_owi(true);
+ BOOST_CHECK(dh2.get_owi());
+ BOOST_CHECK_EQUAL(dh2._uflag, uflag);
+ }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(txn_hdr_class)
+{
+ cout << test_filename << ".txn_hdr_class: " << flush;
+ const u_int32_t magic = 0xfedcba98UL;
+ const u_int8_t version = 0xa5;
+ const u_int16_t uflag = 0x5537;
+ const u_int64_t rid = 0xfedcba9876543210ULL;
+#ifdef JRNL_32_BIT
+ const std::size_t xidsize = 0xfedcba98UL;
+#else
+ const std::size_t xidsize = 0xfedcba9876543210ULL;
+#endif
+ const bool owi = true;
+
+ {
+ txn_hdr th1;
+ BOOST_CHECK_EQUAL(th1._magic, 0UL);
+ BOOST_CHECK_EQUAL(th1._version, 0);
+ BOOST_CHECK_EQUAL(th1._eflag, 0);
+ BOOST_CHECK_EQUAL(th1._uflag, 0);
+ BOOST_CHECK_EQUAL(th1._rid, 0ULL);
+ BOOST_CHECK_EQUAL(th1._xidsize, std::size_t(0));
+ BOOST_CHECK(!th1.get_owi());
+ }
+
+ {
+ txn_hdr th2(magic, version, rid, xidsize, owi);
+ BOOST_CHECK_EQUAL(th2._magic, magic);
+ BOOST_CHECK_EQUAL(th2._version, version);
+#ifdef JRNL_LITTLE_ENDIAN
+ BOOST_CHECK_EQUAL(th2._eflag, RHM_LENDIAN_FLAG);
+#else
+ BOOST_CHECK_EQUAL(th2._eflag, RHM_BENDIAN_FLAG);
+#endif
+ BOOST_CHECK_EQUAL(th2._uflag, (const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK);
+ BOOST_CHECK_EQUAL(th2._rid, rid);
+ BOOST_CHECK_EQUAL(th2._xidsize, xidsize);
+ BOOST_CHECK(th2.get_owi());
+
+ th2._uflag = uflag;
+ BOOST_CHECK(th2.get_owi());
+
+ th2.set_owi(false);
+ BOOST_CHECK(!th2.get_owi());
+ BOOST_CHECK_EQUAL(th2._uflag,
+ (uflag & ~(const u_int16_t)rec_hdr::HDR_OVERWRITE_INDICATOR_MASK));
+
+ th2.set_owi(true);
+ BOOST_CHECK(th2.get_owi());
+ BOOST_CHECK_EQUAL(th2._uflag, uflag);
+ }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_SUITE_END()
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_ut_time_ns.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_ut_time_ns.cpp
new file mode 100644
index 0000000000..f1b53bb97b
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/_ut_time_ns.cpp
@@ -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.
+ *
+ */
+
+#include "../unit_test.h"
+
+#include <ctime>
+#include <iostream>
+#include "qpid/legacystore/jrnl/time_ns.h"
+
+using namespace boost::unit_test;
+using namespace mrg::journal;
+using namespace std;
+
+QPID_AUTO_TEST_SUITE(time_ns_suite)
+
+const string test_filename("_ut_time_ns");
+
+QPID_AUTO_TEST_CASE(constructors)
+{
+ cout << test_filename << ".constructors: " << flush;
+ const std::time_t sec = 123;
+ const long nsec = 123456789;
+
+ time_ns t1;
+ BOOST_CHECK_EQUAL(t1.tv_sec, 0);
+ BOOST_CHECK_EQUAL(t1.tv_nsec, 0);
+ BOOST_CHECK_EQUAL(t1.is_zero(), true);
+ time_ns t2(sec, nsec);
+ BOOST_CHECK_EQUAL(t2.tv_sec, sec);
+ BOOST_CHECK_EQUAL(t2.tv_nsec, nsec);
+ BOOST_CHECK_EQUAL(t2.is_zero(), false);
+ time_ns t3(t1);
+ BOOST_CHECK_EQUAL(t3.tv_sec, 0);
+ BOOST_CHECK_EQUAL(t3.tv_nsec, 0);
+ BOOST_CHECK_EQUAL(t3.is_zero(), true);
+ time_ns t4(t2);
+ BOOST_CHECK_EQUAL(t4.tv_sec, sec);
+ BOOST_CHECK_EQUAL(t4.tv_nsec, nsec);
+ BOOST_CHECK_EQUAL(t4.is_zero(), false);
+ t4.set_zero();
+ BOOST_CHECK_EQUAL(t4.tv_sec, 0);
+ BOOST_CHECK_EQUAL(t4.tv_nsec, 0);
+ BOOST_CHECK_EQUAL(t4.is_zero(), true);
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(operators)
+{
+ cout << test_filename << ".operators: " << flush;
+ const std::time_t sec1 = 123;
+ const long nsec1 = 123456789;
+ const std::time_t sec2 = 1;
+ const long nsec2 = 999999999;
+ const std::time_t sec_sum = sec1 + sec2 + 1;
+ const long nsec_sum = nsec1 + nsec2 - 1000000000;
+ const std::time_t sec_1_minus_2 = sec1 - sec2 - 1;
+ const long nsec_1_minus_2 = nsec1 - nsec2 + 1000000000;
+ const std::time_t sec_2_minus_1 = sec2 - sec1;
+ const long nsec_2_minus_1 = nsec2 - nsec1;
+ time_ns z;
+ time_ns t1(sec1, nsec1);
+ time_ns t2(sec2, nsec2);
+
+ time_ns t3 = z;
+ BOOST_CHECK_EQUAL(t3.tv_sec, 0);
+ BOOST_CHECK_EQUAL(t3.tv_nsec, 0);
+ BOOST_CHECK_EQUAL(t3 == z, true);
+ BOOST_CHECK_EQUAL(t3 != z, false);
+ BOOST_CHECK_EQUAL(t3 > z, false);
+ BOOST_CHECK_EQUAL(t3 >= z, true);
+ BOOST_CHECK_EQUAL(t3 < z, false);
+ BOOST_CHECK_EQUAL(t3 <= z, true);
+
+ t3 = t1;
+ BOOST_CHECK_EQUAL(t3.tv_sec, sec1);
+ BOOST_CHECK_EQUAL(t3.tv_nsec, nsec1);
+ BOOST_CHECK_EQUAL(t3 == t1, true);
+ BOOST_CHECK_EQUAL(t3 != t1, false);
+ BOOST_CHECK_EQUAL(t3 > t1, false);
+ BOOST_CHECK_EQUAL(t3 >= t1, true);
+ BOOST_CHECK_EQUAL(t3 < t1, false);
+ BOOST_CHECK_EQUAL(t3 <= t1, true);
+
+ t3 += z;
+ BOOST_CHECK_EQUAL(t3.tv_sec, sec1);
+ BOOST_CHECK_EQUAL(t3.tv_nsec, nsec1);
+
+ t3 = t2;
+ BOOST_CHECK_EQUAL(t3.tv_sec, sec2);
+ BOOST_CHECK_EQUAL(t3.tv_nsec, nsec2);
+ BOOST_CHECK_EQUAL(t3 == t2, true);
+ BOOST_CHECK_EQUAL(t3 != t2, false);
+ BOOST_CHECK_EQUAL(t3 > t2, false);
+ BOOST_CHECK_EQUAL(t3 >= t2, true);
+ BOOST_CHECK_EQUAL(t3 < t2, false);
+ BOOST_CHECK_EQUAL(t3 <= t2, true);
+
+ t3 += z;
+ BOOST_CHECK_EQUAL(t3.tv_sec, sec2);
+ BOOST_CHECK_EQUAL(t3.tv_nsec, nsec2);
+
+ t3 = t1;
+ t3 += t2;
+ BOOST_CHECK_EQUAL(t3.tv_sec, sec_sum);
+ BOOST_CHECK_EQUAL(t3.tv_nsec, nsec_sum);
+
+ t3 = t1;
+ t3 -= t2;
+ BOOST_CHECK_EQUAL(t3.tv_sec, sec_1_minus_2);
+ BOOST_CHECK_EQUAL(t3.tv_nsec, nsec_1_minus_2);
+
+ t3 = t2;
+ t3 -= t1;
+ BOOST_CHECK_EQUAL(t3.tv_sec, sec_2_minus_1);
+ BOOST_CHECK_EQUAL(t3.tv_nsec, nsec_2_minus_1);
+
+ t3 = t1 + t2;
+ BOOST_CHECK_EQUAL(t3.tv_sec, sec_sum);
+ BOOST_CHECK_EQUAL(t3.tv_nsec, nsec_sum);
+
+ t3 = t1 - t2;
+ BOOST_CHECK_EQUAL(t3.tv_sec, sec_1_minus_2);
+ BOOST_CHECK_EQUAL(t3.tv_nsec, nsec_1_minus_2);
+
+ t3 = t2 - t1;
+ BOOST_CHECK_EQUAL(t3.tv_sec, sec_2_minus_1);
+ BOOST_CHECK_EQUAL(t3.tv_nsec, nsec_2_minus_1);
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(str)
+{
+ cout << test_filename << ".str: " << flush;
+ time_ns t1(123, 123456789);
+ BOOST_CHECK_EQUAL(t1.str(), "123.123457");
+ BOOST_CHECK_EQUAL(t1.str(9), "123.123456789");
+ BOOST_CHECK_EQUAL(t1.str(0), "123");
+ time_ns t2(1, 1);
+ BOOST_CHECK_EQUAL(t2.str(9), "1.000000001");
+ time_ns t3(-12, 345);
+ BOOST_CHECK_EQUAL(t3.str(9), "-11.999999655");
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_SUITE_END()
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/_ut_txn_map.cpp b/qpid/cpp/src/tests/legacystore/jrnl/_ut_txn_map.cpp
new file mode 100644
index 0000000000..595ce0f6c6
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/_ut_txn_map.cpp
@@ -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.
+ *
+ */
+
+#include "../unit_test.h"
+
+#include <iomanip>
+#include <iostream>
+#include "qpid/legacystore/jrnl/txn_map.h"
+#include <sstream>
+
+using namespace boost::unit_test;
+using namespace mrg::journal;
+using namespace std;
+
+QPID_AUTO_TEST_SUITE(txn_map_suite)
+
+const string test_filename("_ut_txn_map");
+
+// === Helper functions ===
+
+const string make_xid(u_int64_t rid)
+{
+ stringstream ss;
+ ss << "XID-" << setfill('0') << setw(16) << hex << rid;
+ ss << "-0123456789abcdef";
+ return ss.str();
+}
+
+void check_td_equal(txn_data& td1, txn_data& td2)
+{
+ BOOST_CHECK_EQUAL(td1._rid, td2._rid);
+ BOOST_CHECK_EQUAL(td1._drid, td2._drid);
+ BOOST_CHECK_EQUAL(td1._pfid, td2._pfid);
+ BOOST_CHECK_EQUAL(td1._enq_flag, td2._enq_flag);
+ BOOST_CHECK_EQUAL(td1._aio_compl, td2._aio_compl);
+}
+
+// === Test suite ===
+
+QPID_AUTO_TEST_CASE(constructor)
+{
+ cout << test_filename << ".constructor: " << flush;
+ const u_int64_t rid = 0x123456789abcdef0ULL;
+ const u_int64_t drid = 0xfedcba9876543210ULL;
+ const u_int16_t pfid = 0xfedcU;
+ const bool enq_flag = true;
+ txn_data td(rid, drid, pfid, enq_flag);
+ BOOST_CHECK_EQUAL(td._rid, rid);
+ BOOST_CHECK_EQUAL(td._drid, drid);
+ BOOST_CHECK_EQUAL(td._pfid, pfid);
+ BOOST_CHECK_EQUAL(td._enq_flag, enq_flag);
+ BOOST_CHECK_EQUAL(td._aio_compl, false);
+
+ txn_map t1;
+ BOOST_CHECK(t1.empty());
+ BOOST_CHECK_EQUAL(t1.size(), u_int32_t(0));
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(insert_get)
+{
+ cout << test_filename << ".insert_get: " << flush;
+ u_int16_t fid;
+ u_int64_t rid;
+ u_int16_t pfid_start = 0x2000U;
+ u_int64_t rid_begin = 0xffffffff00000000ULL;
+ u_int64_t rid_end = 0xffffffff00000200ULL;
+
+ // insert with no dups
+ u_int64_t rid_incr_1 = 4ULL;
+ txn_map t2;
+ t2.set_num_jfiles(pfid_start + (rid_end - rid_begin)/rid_incr_1);
+ for (rid = rid_begin, fid = pfid_start; rid < rid_end; rid += rid_incr_1, fid++)
+ t2.insert_txn_data(make_xid(rid), txn_data(rid, ~rid, fid, false));
+ BOOST_CHECK(!t2.empty());
+ BOOST_CHECK_EQUAL(t2.size(), u_int32_t(128));
+
+ // get
+ u_int64_t rid_incr_2 = 6ULL;
+ for (u_int64_t rid = rid_begin; rid < rid_end; rid += rid_incr_2)
+ {
+ string xid = make_xid(rid);
+ BOOST_CHECK_EQUAL(t2.in_map(xid), (rid%rid_incr_1 ? false : true));
+ }
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_SUITE_END()
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/chk_jdata b/qpid/cpp/src/tests/legacystore/jrnl/chk_jdata
new file mode 100755
index 0000000000..2ac87d91b9
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/chk_jdata
@@ -0,0 +1,32 @@
+#!/usr/bin/env 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.
+#
+
+
+JRNL_BLK_SIZE=512 # Block size in bytes
+JRNL_PAGE_SIZE=256 # Journal page size in blocks
+JRNL_FILE_SIZE=12 # Journal file size in pages
+let END_OFFSET=${JRNL_BLK_SIZE}*${JRNL_PAGE_SIZE}*${JRNL_FILE_SIZE}
+for f in jdata/test.*.jdat; do
+ echo $f
+ hexdump -C -n 1024 $f
+ hexdump -C -s ${END_OFFSET} $f
+ echo "============"
+done
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/cp_rtest_jrnl b/qpid/cpp/src/tests/legacystore/jrnl/cp_rtest_jrnl
new file mode 100755
index 0000000000..e21f991788
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/cp_rtest_jrnl
@@ -0,0 +1,59 @@
+#!/usr/bin/env 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.
+#
+
+JDATA_DIR=jdata
+TAR_DIR=rd_test_jrnls
+
+function get_filename
+{
+ local prefix=$1
+ local file_num=$2
+ local suffix=$3
+
+ if (( file_num < 10 )); then
+ local num="000${file_num}"
+ elif (( file_num < 100 )); then
+ local num="00${file_num}"
+ elif (( file_num < 1000 )); then
+ local num="0${file_num}"
+ else
+ local num="${file_num}"
+ fi
+ FILENAME=${prefix}${num}${suffix}
+ return 0
+}
+
+if (( $# != 1 )); then
+ echo "Incorrect args, expected 1 arg (usage: \"prep <testnum>\")"
+ exit
+fi
+
+get_filename "t" $1 ".tar.gz"
+if [[ -d ${JDATA_DIR} ]]; then
+ rm -rf ${JDATA_DIR}/*
+else
+ mkdir -p ${JDATA_DIR}
+fi
+if [[ -f "${TAR_DIR}/${FILENAME}" ]]; then
+ tar -C ${JDATA_DIR} -xzf "${TAR_DIR}/${FILENAME}"
+else
+ echo "Error: file \"${TAR_DIR}/${FILENAME}\" not found."
+fi
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jhexdump b/qpid/cpp/src/tests/legacystore/jrnl/jhexdump
new file mode 100755
index 0000000000..b013914441
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jhexdump
@@ -0,0 +1,41 @@
+#!/usr/bin/env 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 "$1" ]; then
+ echo "No directory specified."
+ exit
+fi
+
+JDIR=$1
+echo "Target directory: ${JDIR}"
+
+rm -f j*.txt
+
+if [ -d "${JDIR}" ]; then
+ n=0
+ for f in "${JDIR}"/*.jdat; do
+ echo "$f -> j$n.txt"
+ hexdump -C "$f" > j$n.txt
+ (( n += 1 ))
+ done
+else
+ echo "This directory does not exist."
+fi
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_data_src.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_data_src.cpp
new file mode 100644
index 0000000000..e4656ef83f
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_data_src.cpp
@@ -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.
+ *
+ */
+
+#include "../../unit_test.h"
+#include <cstddef>
+#include "data_src.h"
+#include <iomanip>
+#include <iostream>
+
+using namespace boost::unit_test;
+using namespace mrg::jtt;
+using namespace std;
+
+QPID_AUTO_TEST_SUITE(jtt_data_src)
+
+const string test_filename("_ut_data_src");
+
+long
+get_seed()
+{
+ timespec ts;
+ if (::clock_gettime(CLOCK_REALTIME, &ts))
+ BOOST_FAIL("Unable to read clock to generate seed.");
+ long tenths = ts.tv_nsec / 100000000;
+ return long(10 * ts.tv_sec + tenths); // time in tenths of a second
+}
+
+#ifndef LONG_TEST
+/*
+ * ==============================================
+ * NORMAL TESTS
+ * This section contains normal "make check" tests
+ * for building/packaging. These are built when
+ * LONG_TEST is _not_ defined.
+ * ==============================================
+ */
+
+QPID_AUTO_TEST_CASE(data)
+{
+ cout << test_filename << ".data: " << flush;
+ BOOST_CHECK(data_src::max_dsize > 0);
+ for (std::size_t i=0; i<1024; i++)
+ {
+ const char* dp = data_src::get_data(i);
+ BOOST_CHECK_EQUAL(*dp, static_cast<char>('0' + ((i + 1) % 10)));
+ }
+ for (std::size_t i=data_src::max_dsize-1024; i<data_src::max_dsize; i++)
+ {
+ const char* dp = data_src::get_data(i);
+ BOOST_CHECK_EQUAL(*dp, static_cast<char>('0' + ((i + 1) % 10)));
+ }
+ const char* dp1 = data_src::get_data(data_src::max_dsize);
+ BOOST_CHECK_EQUAL(dp1,(char*) 0);
+ const char* dp2 = data_src::get_data(data_src::max_dsize + 0x1000);
+ BOOST_CHECK_EQUAL(dp2, (char*)0);
+ cout << "ok" << endl;
+}
+
+// There is a long version of this test in _ut_long_data_src.cpp
+QPID_AUTO_TEST_CASE(xid_data_xid)
+{
+ const std::size_t num = 64;
+ cout << test_filename << ".xid_data_xid: " << flush;
+ BOOST_CHECK_EQUAL(data_src::get_xid(1), "0");
+ BOOST_CHECK_EQUAL(data_src::get_xid(2), "01");
+ BOOST_CHECK_EQUAL(data_src::get_xid(3), "002");
+ BOOST_CHECK_EQUAL(data_src::get_xid(4), "0003");
+ BOOST_CHECK_EQUAL(data_src::get_xid(5), "00004");
+ BOOST_CHECK_EQUAL(data_src::get_xid(6), "000005");
+ BOOST_CHECK_EQUAL(data_src::get_xid(7), "0000006");
+ BOOST_CHECK_EQUAL(data_src::get_xid(8), "00000007");
+ BOOST_CHECK_EQUAL(data_src::get_xid(9), "xid:00008");
+ BOOST_CHECK_EQUAL(data_src::get_xid(10), "xid:000009");
+ BOOST_CHECK_EQUAL(data_src::get_xid(11), "xid:0000010");
+ BOOST_CHECK_EQUAL(data_src::get_xid(12), "xid:00000011");
+ BOOST_CHECK_EQUAL(data_src::get_xid(13), "xid:00000012:");
+ BOOST_CHECK_EQUAL(data_src::get_xid(14), "xid:00000013:n");
+ BOOST_CHECK_EQUAL(data_src::get_xid(15), "xid:00000014:no");
+ std::size_t i = 15;
+ for (; i<num; i++)
+ {
+ string xid(data_src::get_xid(i));
+
+ ostringstream oss;
+ oss << setfill('0') << "xid:" << setw(8) << i << ":";
+
+ BOOST_CHECK_EQUAL(xid.size(), i);
+ BOOST_CHECK_EQUAL(xid.substr(0, 13), oss.str());
+ BOOST_CHECK_EQUAL(xid[13], 'n');
+ BOOST_CHECK_EQUAL(xid[i-1], (char)('a' + ((i-1)%26)));
+ }
+ for (std::size_t j=data_src::max_xsize-num; j<data_src::max_xsize; j++,i++)
+ {
+ string xid(data_src::get_xid(j));
+
+ ostringstream oss;
+ oss << setfill('0') << "xid:" << setw(8) << i << ":";
+
+ BOOST_CHECK_EQUAL(xid.size(), j);
+ BOOST_CHECK_EQUAL(xid.substr(0, 13), oss.str());
+ BOOST_CHECK_EQUAL(xid[13], 'n');
+ BOOST_CHECK_EQUAL(xid[j-1], (char)('a' + ((j-1)%26)));
+ }
+ cout << "ok" << endl;
+}
+
+#else
+/*
+ * ==============================================
+ * LONG TESTS
+ * This section contains long tests and soak tests,
+ * and are run using target check-long (ie "make
+ * check-long"). These are built when LONG_TEST is
+ * defined.
+ * ==============================================
+ */
+
+/*
+ * To reproduce a specific test, comment out the get_seed() statement and uncomment the literal below, adjusting the seed
+ * value to that required.
+ */
+QPID_AUTO_TEST_CASE(xid_data_xid)
+{
+ const long seed = get_seed();
+ // const long seed = 0x2d9b69d32;
+ ::srand48(seed);
+
+ const std::size_t num = 1024;
+ cout << test_filename << ".xid_data_xid seed=0x" << std::hex << seed << std::dec << ": " << flush;
+ BOOST_CHECK_EQUAL(data_src::get_xid(1), "0");
+ BOOST_CHECK_EQUAL(data_src::get_xid(2), "01");
+ BOOST_CHECK_EQUAL(data_src::get_xid(3), "002");
+ BOOST_CHECK_EQUAL(data_src::get_xid(4), "0003");
+ BOOST_CHECK_EQUAL(data_src::get_xid(5), "00004");
+ BOOST_CHECK_EQUAL(data_src::get_xid(6), "000005");
+ BOOST_CHECK_EQUAL(data_src::get_xid(7), "0000006");
+ BOOST_CHECK_EQUAL(data_src::get_xid(8), "00000007");
+ BOOST_CHECK_EQUAL(data_src::get_xid(9), "xid:00008");
+ BOOST_CHECK_EQUAL(data_src::get_xid(10), "xid:000009");
+ BOOST_CHECK_EQUAL(data_src::get_xid(11), "xid:0000010");
+ BOOST_CHECK_EQUAL(data_src::get_xid(12), "xid:00000011");
+ BOOST_CHECK_EQUAL(data_src::get_xid(13), "xid:00000012:");
+ BOOST_CHECK_EQUAL(data_src::get_xid(14), "xid:00000013:n");
+ BOOST_CHECK_EQUAL(data_src::get_xid(15), "xid:00000014:no");
+ std::size_t i = 15;
+ for (; i<num; i++)
+ {
+ string xid(data_src::get_xid(i));
+
+ ostringstream oss;
+ oss << setfill('0') << "xid:" << setw(8) << i << ":";
+
+ BOOST_CHECK_EQUAL(xid.size(), i);
+ BOOST_CHECK_EQUAL(xid.substr(0, 13), oss.str());
+ BOOST_CHECK_EQUAL(xid[13], 'n');
+ BOOST_CHECK_EQUAL(xid[i-1], (char)('a' + ((i-1)%26)));
+ }
+ for (std::size_t j=data_src::max_xsize-num; j<data_src::max_xsize; j++,i++)
+ {
+ string xid(data_src::get_xid(j));
+
+ ostringstream oss;
+ oss << setfill('0') << "xid:" << setw(8) << i << ":";
+
+ BOOST_CHECK_EQUAL(xid.size(), j);
+ BOOST_CHECK_EQUAL(xid.substr(0, 13), oss.str());
+ BOOST_CHECK_EQUAL(xid[13], 'n');
+ BOOST_CHECK_EQUAL(xid[j-1], (char)('a' + ((j-1)%26)));
+ }
+ std::srand(seed);
+ for (int cnt=0; cnt<1000; cnt++,i++)
+ {
+ std::size_t k = 1 + ::lrand48() % (data_src::max_xsize - 1);
+ string xid(data_src::get_xid(k));
+
+ ostringstream oss;
+ oss << setfill('0') << "xid:" << setw(8) << i << ":";
+
+ BOOST_CHECK_EQUAL(xid.size(), k);
+ BOOST_CHECK_EQUAL(xid.substr(0, 13), oss.str());
+ BOOST_CHECK_EQUAL(xid[13], 'n');
+ BOOST_CHECK_EQUAL(xid[k-1], (char)('a' + ((k-1)%26)));
+ }
+ cout << "ok" << endl;
+}
+
+#endif
+
+QPID_AUTO_TEST_SUITE_END()
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_jrnl_init_params.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_jrnl_init_params.cpp
new file mode 100644
index 0000000000..9fefe25105
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_jrnl_init_params.cpp
@@ -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.
+ *
+ */
+
+#include "../../unit_test.h"
+#include "jrnl_init_params.h"
+#include <iostream>
+
+using namespace boost::unit_test;
+using namespace mrg::jtt;
+using namespace std;
+
+QPID_AUTO_TEST_SUITE(jtt_jrnl_init_params)
+
+const string test_filename("_ut_jrnl_init_params");
+
+QPID_AUTO_TEST_CASE(constructor)
+{
+ cout << test_filename << ".constructor: " << flush;
+ const string jid = "jid";
+ const string jdir = "jdir";
+ const string bfn = "base filename";
+ const u_int16_t num_jfiles = 123;
+ const bool ae = false;
+ const u_int16_t ae_max_jfiles = 456;
+ const u_int32_t jfsize_sblks = 789;
+ jrnl_init_params jip(jid, jdir, bfn, num_jfiles, ae, ae_max_jfiles, jfsize_sblks);
+ BOOST_CHECK_EQUAL(jip.jid(), jid);
+ BOOST_CHECK_EQUAL(jip.jdir(), jdir);
+ BOOST_CHECK_EQUAL(jip.base_filename(), bfn);
+ BOOST_CHECK_EQUAL(jip.num_jfiles(), num_jfiles);
+ BOOST_CHECK_EQUAL(jip.is_ae(), ae);
+ BOOST_CHECK_EQUAL(jip.ae_max_jfiles(), ae_max_jfiles);
+ BOOST_CHECK_EQUAL(jip.jfsize_sblks(), jfsize_sblks);
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(copy_constructor_1)
+{
+ cout << test_filename << ".copy_constructor_1: " << flush;
+ const string jid = "jid";
+ const string jdir = "jdir";
+ const string bfn = "base filename";
+ const u_int16_t num_jfiles = 123;
+ const bool ae = false;
+ const u_int16_t ae_max_jfiles = 456;
+ const u_int32_t jfsize_sblks = 789;
+ jrnl_init_params jip1(jid, jdir, bfn, num_jfiles, ae, ae_max_jfiles, jfsize_sblks);
+ jrnl_init_params jip2(jip1);
+ BOOST_CHECK_EQUAL(jip2.jid(), jid);
+ BOOST_CHECK_EQUAL(jip2.jdir(), jdir);
+ BOOST_CHECK_EQUAL(jip2.base_filename(), bfn);
+ BOOST_CHECK_EQUAL(jip2.num_jfiles(), num_jfiles);
+ BOOST_CHECK_EQUAL(jip2.is_ae(), ae);
+ BOOST_CHECK_EQUAL(jip2.ae_max_jfiles(), ae_max_jfiles);
+ BOOST_CHECK_EQUAL(jip2.jfsize_sblks(), jfsize_sblks);
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(copy_constructor_2)
+{
+ cout << test_filename << ".copy_constructor_2: " << flush;
+ const string jid = "jid";
+ const string jdir = "jdir";
+ const string bfn = "base filename";
+ const u_int16_t num_jfiles = 123;
+ const bool ae = false;
+ const u_int16_t ae_max_jfiles = 456;
+ const u_int32_t jfsize_sblks = 789;
+ jrnl_init_params::shared_ptr p(new jrnl_init_params(jid, jdir, bfn, num_jfiles, ae, ae_max_jfiles, jfsize_sblks));
+ jrnl_init_params jip2(p.get());
+ BOOST_CHECK_EQUAL(jip2.jid(), jid);
+ BOOST_CHECK_EQUAL(jip2.jdir(), jdir);
+ BOOST_CHECK_EQUAL(jip2.base_filename(), bfn);
+ BOOST_CHECK_EQUAL(jip2.num_jfiles(), num_jfiles);
+ BOOST_CHECK_EQUAL(jip2.is_ae(), ae);
+ BOOST_CHECK_EQUAL(jip2.ae_max_jfiles(), ae_max_jfiles);
+ BOOST_CHECK_EQUAL(jip2.jfsize_sblks(), jfsize_sblks);
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_jrnl_instance.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_jrnl_instance.cpp
new file mode 100644
index 0000000000..12f1c542d6
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_jrnl_instance.cpp
@@ -0,0 +1,178 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "../../unit_test.h"
+
+#include <iostream>
+#include "jrnl_init_params.h"
+#include "jrnl_instance.h"
+#include "qpid/legacystore/jrnl/jdir.h"
+#include "qpid/legacystore/jrnl/jerrno.h"
+
+using namespace boost::unit_test;
+using namespace mrg::journal;
+using namespace mrg::jtt;
+using namespace std;
+
+QPID_AUTO_TEST_SUITE(jtt_jrnl_instance)
+
+const string test_filename("_ut_jrnl_instance");
+const char* tdp = getenv("TMP_DATA_DIR");
+const string test_dir(tdp && strlen(tdp) > 0 ? tdp : "/var/tmp/JttTest");
+
+QPID_AUTO_TEST_CASE(constructor_1)
+{
+ cout << test_filename << ".constructor_1: " << flush;
+ const string jid = "jid1";
+ const string jdir = test_dir + "/test1";
+ const string bfn = "test";
+ const u_int16_t num_jfiles = 20;
+ const bool ae = false;
+ const u_int16_t ae_max_jfiles = 45;
+ const u_int32_t jfsize_sblks = 128;
+
+ args a("a1");
+ using mrg::jtt::test_case;
+ test_case::shared_ptr p(new test_case(1, 0, 0, 0, false, 0, 0, test_case::JTT_PERSISTNET, test_case::JDL_INTERNAL,
+ "t1"));
+ jrnl_instance ji(jid, jdir, bfn, num_jfiles, ae, ae_max_jfiles, jfsize_sblks);
+ ji.init_tc(p, &a);
+ ji.run_tc();
+ ji.tc_wait_compl();
+ try { jdir::verify_dir(jdir, bfn); }
+ catch (const jexception& e) { BOOST_ERROR(e.what()); }
+ jdir::delete_dir(jdir);
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(constructor_2)
+{
+ cout << test_filename << ".constructor_2: " << flush;
+ const string jid = "jid2";
+ const string jdir = test_dir + "/test2";
+ const string bfn = "test";
+ const u_int16_t num_jfiles = 20;
+ const bool ae = false;
+ const u_int16_t ae_max_jfiles = 45;
+ const u_int32_t jfsize_sblks = 128;
+
+ args a("a2");
+ using mrg::jtt::test_case;
+ test_case::shared_ptr p(new test_case(2, 0, 0, 0, false, 0, 0, test_case::JTT_PERSISTNET, test_case::JDL_INTERNAL,
+ "t2"));
+ jrnl_init_params::shared_ptr jpp(new jrnl_init_params(jid, jdir, bfn, num_jfiles, ae, ae_max_jfiles, jfsize_sblks));
+ jrnl_instance ji(jpp);
+ ji.init_tc(p, &a);
+ ji.run_tc();
+ ji.tc_wait_compl();
+ try { jdir::verify_dir(jdir, bfn); }
+ catch (const jexception& e) { BOOST_ERROR(e.what()); }
+ jdir::delete_dir(jdir);
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(constructor_3)
+{
+ cout << test_filename << ".constructor_3: " << flush;
+ const string jid = "jid3";
+ const string jdir = test_dir + "/test3";
+ const string bfn = "test";
+ const u_int16_t num_jfiles = 20;
+ const bool ae = false;
+ const u_int16_t ae_max_jfiles = 45;
+ const u_int32_t jfsize_sblks = 128;
+
+ args a("a3");
+ using mrg::jtt::test_case;
+ test_case::shared_ptr p(new test_case(3, 0, 0, 0, false, 0, 0, test_case::JTT_PERSISTNET, test_case::JDL_INTERNAL,
+ "t3"));
+ jrnl_init_params::shared_ptr jpp(new jrnl_init_params(jid, jdir, bfn, num_jfiles, ae, ae_max_jfiles, jfsize_sblks));
+ jrnl_instance ji(jpp);
+ ji.init_tc(p, &a);
+ ji.run_tc();
+ ji.tc_wait_compl();
+ try { jdir::verify_dir(jdir, bfn); }
+ catch (const jexception& e) { BOOST_ERROR(e.what()); }
+ jdir::delete_dir(jdir);
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(recover)
+{
+ cout << test_filename << ".recover: " << flush;
+ const string jid = "jid5";
+ const string jdir = test_dir + "/test5";
+ const string bfn = "test";
+ const u_int16_t num_jfiles = 20;
+ const bool ae = false;
+ const u_int16_t ae_max_jfiles = 0;
+ const u_int32_t jfsize_sblks = 128;
+
+ args a("a4");
+ using mrg::jtt::test_case;
+ test_case::shared_ptr p(new test_case(5, 0, 0, 0, false, 0, 0, test_case::JTT_PERSISTNET, test_case::JDL_INTERNAL,
+ "t5"));
+ jrnl_init_params::shared_ptr jpp(new jrnl_init_params(jid, jdir, bfn, num_jfiles, ae, ae_max_jfiles, jfsize_sblks));
+ jrnl_instance ji(jpp);
+ ji.init_tc(p, &a);
+ ji.run_tc();
+ ji.tc_wait_compl();
+ try { jdir::verify_dir(jdir, bfn); }
+ catch (const jexception& e) { BOOST_ERROR(e.what()); }
+ a.recover_mode = true;
+ ji.init_tc(p, &a);
+ ji.run_tc();
+ ji.tc_wait_compl();
+ try { jdir::verify_dir(jdir, bfn); }
+ catch (const jexception& e) { BOOST_ERROR(e.what()); }
+ jdir::delete_dir(jdir);
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(recover_no_files)
+{
+ cout << test_filename << ".recover_no_files: " << flush;
+ const string jid = "jid6";
+ const string jdir = test_dir + "/test6";
+ const string bfn = "test";
+ const u_int16_t num_jfiles = 20;
+ const bool ae = false;
+ const u_int16_t ae_max_jfiles = 0;
+ const u_int32_t jfsize_sblks = 128;
+
+ args a("a5");
+ a.recover_mode = true;
+ using mrg::jtt::test_case;
+ test_case::shared_ptr p(new test_case(6, 0, 0, 0, false, 0, 0, test_case::JTT_PERSISTNET, test_case::JDL_INTERNAL,
+ "t6"));
+ jrnl_init_params::shared_ptr jpp(new jrnl_init_params(jid, jdir, bfn, num_jfiles, ae, ae_max_jfiles, jfsize_sblks));
+ jrnl_instance ji(jpp);
+ ji.init_tc(p, &a);
+ ji.run_tc();
+ ji.tc_wait_compl();
+ try { jdir::verify_dir(jdir, bfn); }
+ catch (const jexception& e) { BOOST_ERROR(e.what()); }
+ jdir::delete_dir(jdir);
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_read_arg.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_read_arg.cpp
new file mode 100644
index 0000000000..0d2025270d
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_read_arg.cpp
@@ -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.
+ *
+ */
+
+#include "../../unit_test.h"
+#include <boost/test/unit_test_log.hpp>
+#include "read_arg.h"
+#include <iostream>
+
+#include <boost/program_options.hpp>
+namespace po = boost::program_options;
+using namespace mrg::jtt;
+using namespace boost::unit_test;
+using namespace std;
+
+QPID_AUTO_TEST_SUITE(jtt_read_arg)
+
+const string test_filename("_ut_read_arg");
+
+QPID_AUTO_TEST_CASE(constructor)
+{
+ cout << test_filename << ".constructor: " << flush;
+ read_arg ra1;
+ BOOST_CHECK_EQUAL(ra1.val(), read_arg::NONE);
+ BOOST_CHECK_EQUAL(ra1.str(), "NONE");
+ read_arg ra2(read_arg::NONE);
+ BOOST_CHECK_EQUAL(ra2.val(), read_arg::NONE);
+ BOOST_CHECK_EQUAL(ra2.str(), "NONE");
+ read_arg ra3(read_arg::ALL);
+ BOOST_CHECK_EQUAL(ra3.val(), read_arg::ALL);
+ BOOST_CHECK_EQUAL(ra3.str(), "ALL");
+ read_arg ra4(read_arg::RANDOM);
+ BOOST_CHECK_EQUAL(ra4.val(), read_arg::RANDOM);
+ BOOST_CHECK_EQUAL(ra4.str(), "RANDOM");
+ read_arg ra5(read_arg::LAZYLOAD);
+ BOOST_CHECK_EQUAL(ra5.val(), read_arg::LAZYLOAD);
+ BOOST_CHECK_EQUAL(ra5.str(), "LAZYLOAD");
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(set_val)
+{
+ cout << test_filename << ".set_val: " << flush;
+ read_arg ra;
+ BOOST_CHECK_EQUAL(ra.val(), read_arg::NONE);
+ BOOST_CHECK_EQUAL(ra.str(), "NONE");
+ ra.set_val(read_arg::ALL);
+ BOOST_CHECK_EQUAL(ra.val(), read_arg::ALL);
+ BOOST_CHECK_EQUAL(ra.str(), "ALL");
+ ra.set_val(read_arg::RANDOM);
+ BOOST_CHECK_EQUAL(ra.val(), read_arg::RANDOM);
+ BOOST_CHECK_EQUAL(ra.str(), "RANDOM");
+ ra.set_val(read_arg::LAZYLOAD);
+ BOOST_CHECK_EQUAL(ra.val(), read_arg::LAZYLOAD);
+ BOOST_CHECK_EQUAL(ra.str(), "LAZYLOAD");
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(parse)
+{
+ cout << test_filename << ".parse: " << flush;
+ read_arg ra;
+ ra.parse("LAZYLOAD");
+ BOOST_CHECK_EQUAL(ra.val(), read_arg::LAZYLOAD);
+ BOOST_CHECK_EQUAL(ra.str(), "LAZYLOAD");
+ ra.parse("ALL");
+ BOOST_CHECK_EQUAL(ra.val(), read_arg::ALL);
+ BOOST_CHECK_EQUAL(ra.str(), "ALL");
+ BOOST_CHECK_THROW(ra.parse(""), po::invalid_option_value)
+ BOOST_CHECK_EQUAL(ra.val(), read_arg::ALL);
+ BOOST_CHECK_EQUAL(ra.str(), "ALL");
+ BOOST_CHECK_THROW(ra.parse("abc123"), po::invalid_option_value)
+ BOOST_CHECK_EQUAL(ra.val(), read_arg::ALL);
+ BOOST_CHECK_EQUAL(ra.str(), "ALL");
+ ra.parse("NONE");
+ BOOST_CHECK_EQUAL(ra.val(), read_arg::NONE);
+ BOOST_CHECK_EQUAL(ra.str(), "NONE");
+ ra.parse("RANDOM");
+ BOOST_CHECK_EQUAL(ra.val(), read_arg::RANDOM);
+ BOOST_CHECK_EQUAL(ra.str(), "RANDOM");
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(istream_)
+{
+ cout << test_filename << ".istream_: " << flush;
+ read_arg ra;
+ istringstream ss1("LAZYLOAD", ios::in);
+ ss1 >> ra;
+ BOOST_CHECK_EQUAL(ra.val(), read_arg::LAZYLOAD);
+ BOOST_CHECK_EQUAL(ra.str(), "LAZYLOAD");
+ istringstream ss2("ALL", ios::in);
+ ss2 >> ra;
+ BOOST_CHECK_EQUAL(ra.val(), read_arg::ALL);
+ BOOST_CHECK_EQUAL(ra.str(), "ALL");
+ istringstream ss3("NONE", ios::in);
+ ss3 >> ra;
+ BOOST_CHECK_EQUAL(ra.val(), read_arg::NONE);
+ BOOST_CHECK_EQUAL(ra.str(), "NONE");
+ istringstream ss4("RANDOM", ios::in);
+ ss4 >> ra;
+ BOOST_CHECK_EQUAL(ra.val(), read_arg::RANDOM);
+ BOOST_CHECK_EQUAL(ra.str(), "RANDOM");
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(ostream_)
+{
+ cout << test_filename << ".ostream_: " << flush;
+ ostringstream s1;
+ read_arg ra(read_arg::LAZYLOAD);
+ s1 << ra;
+ BOOST_CHECK_EQUAL(s1.str(), "LAZYLOAD");
+ ra.set_val(read_arg::ALL);
+ ostringstream s2;
+ s2 << ra;
+ BOOST_CHECK_EQUAL(s2.str(), "ALL");
+ ra.set_val(read_arg::NONE);
+ ostringstream s3;
+ s3 << ra;
+ BOOST_CHECK_EQUAL(s3.str(), "NONE");
+ ra.set_val(read_arg::RANDOM);
+ ostringstream s4;
+ s4 << ra;
+ BOOST_CHECK_EQUAL(s4.str(), "RANDOM");
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_SUITE_END()
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case.cpp
new file mode 100644
index 0000000000..3a7d0f951c
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case.cpp
@@ -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.
+ *
+ */
+
+#include "../../unit_test.h"
+#include <cstddef>
+#include <iomanip>
+#include <iostream>
+#include "test_case.h"
+#include "test_case_result.h"
+
+using namespace boost::unit_test;
+using namespace mrg::jtt;
+using namespace std;
+
+QPID_AUTO_TEST_SUITE(jtt_test_case)
+
+const string test_filename("_ut_test_case");
+
+QPID_AUTO_TEST_CASE(constructor)
+{
+ cout << test_filename << ".constructor: " << flush;
+ const unsigned test_case_num = 0x12345;
+ const u_int32_t num_msgs = 0x100;
+ const std::size_t min_data_size = 0x1000;
+ const std::size_t max_data_size = 0;
+ const bool auto_deq = true;
+ const std::size_t min_xid_size = 0x200;
+ const std::size_t max_xid_size = 0x200;
+ using mrg::jtt::test_case;
+ const test_case::transient_t transient = test_case::JTT_PERSISTNET;
+ const test_case::external_t external = test_case::JDL_INTERNAL;
+ const string comment = "This is a test";
+
+ test_case tc(test_case_num, num_msgs, min_data_size, max_data_size, auto_deq,
+ min_xid_size, max_xid_size, transient, external, comment);
+ BOOST_CHECK_EQUAL(tc.test_case_num(), test_case_num);
+ BOOST_CHECK_EQUAL(tc.num_msgs(), num_msgs);
+ BOOST_CHECK_EQUAL(tc.min_data_size(), min_data_size);
+ BOOST_CHECK_EQUAL(tc.max_data_size(), max_data_size);
+ BOOST_CHECK_EQUAL(tc.auto_deq(), auto_deq);
+ BOOST_CHECK_EQUAL(tc.min_xid_size(), min_xid_size);
+ BOOST_CHECK_EQUAL(tc.max_xid_size(), max_xid_size);
+ BOOST_CHECK_EQUAL(tc.transient(), transient);
+ BOOST_CHECK_EQUAL(tc.external(), external);
+ BOOST_CHECK_EQUAL(tc.comment(), comment);
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(results)
+{
+ cout << test_filename << ".results: " << flush;
+ const unsigned test_case_num = 0x12345;
+ const u_int32_t num_msgs = 0x100;
+ const std::size_t min_data_size = 0x1000;
+ const std::size_t max_data_size = 0;
+ const bool auto_deq = true;
+ const std::size_t min_xid_size = 0x200;
+ const std::size_t max_xid_size = 0x200;
+ using mrg::jtt::test_case;
+ const test_case::transient_t transient = test_case::JTT_PERSISTNET;
+ const test_case::external_t external = test_case::JDL_INTERNAL;
+ const string comment = "This is a test";
+ const unsigned num_results = 20;
+
+ test_case tc(test_case_num, num_msgs, min_data_size, max_data_size, auto_deq,
+ min_xid_size, max_xid_size, transient, external, comment);
+ for (unsigned i=0; i<num_results; i++)
+ {
+ ostringstream oss;
+ oss << "JID_" << setfill('0') << setw(2) << i;
+ test_case_result::shared_ptr p(new test_case_result(oss.str()));
+ tc.add_result(p);
+ }
+ BOOST_CHECK_EQUAL(tc.num_results(), num_results);
+ test_case_result_agregation ave = tc.average();
+ unsigned i=0;
+ for (test_case_result_agregation::tcrp_list_citr j=ave.rlist_begin(); j!=ave.rlist_end();
+ i++,j++)
+ {
+ ostringstream oss;
+ oss << "JID_" << setfill('0') << setw(2) << i;
+ BOOST_CHECK_EQUAL((*j)->jid(), oss.str());
+ }
+ for (unsigned i=0; i<num_results; i++)
+ {
+ ostringstream oss;
+ oss << "JID_" << setfill('0') << setw(2) << i;
+ BOOST_CHECK_EQUAL(ave[i]->jid(), oss.str());
+ }
+ tc.clear();
+ BOOST_CHECK_EQUAL(tc.num_results(), unsigned(0));
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_SUITE_END()
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_result.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_result.cpp
new file mode 100644
index 0000000000..dd83dbee69
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_result.cpp
@@ -0,0 +1,206 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "../../unit_test.h"
+
+#include <iostream>
+#include "qpid/legacystore/jrnl/jexception.h"
+#include "test_case_result.h"
+
+using namespace boost::unit_test;
+using namespace mrg::journal;
+using namespace mrg::jtt;
+using namespace std;
+
+QPID_AUTO_TEST_SUITE(jtt_test_case_result)
+
+const string test_filename("_ut_test_case_result");
+
+QPID_AUTO_TEST_CASE(constructor)
+{
+ cout << test_filename << ".constructor: " << flush;
+ const string jid("journal id 1");
+ test_case_result tcr(jid);
+ BOOST_CHECK_EQUAL(tcr.jid(), jid);
+ BOOST_CHECK_EQUAL(tcr.exception(), false);
+ BOOST_CHECK_EQUAL(tcr.exception_count(), 0U);
+ const time_ns& ts1 = tcr.start_time();
+ BOOST_CHECK(ts1.is_zero());
+ const time_ns& ts2 = tcr.stop_time();
+ BOOST_CHECK(ts2.is_zero());
+ const time_ns& ts3 = tcr.test_time();
+ BOOST_CHECK(ts3.is_zero());
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(start_stop)
+{
+ cout << test_filename << ".start_stop: " << flush;
+ const string jid("journal id 2");
+ test_case_result tcr(jid);
+ BOOST_CHECK_EQUAL(tcr.exception(), false);
+ BOOST_CHECK_EQUAL(tcr.exception_count(), 0U);
+ const time_ns& ts1 = tcr.start_time();
+ BOOST_CHECK(ts1.is_zero());
+ const time_ns& ts2 = tcr.stop_time();
+ BOOST_CHECK(ts2.is_zero());
+ const time_ns& ts3 = tcr.test_time();
+ BOOST_CHECK(ts3.is_zero());
+
+ tcr.set_start_time();
+ BOOST_CHECK_EQUAL(tcr.exception(), false);
+ BOOST_CHECK_EQUAL(tcr.exception_count(), 0U);
+ const time_ns& ts4 = tcr.start_time();
+ BOOST_CHECK(!ts4.is_zero());
+ const time_ns& ts5 = tcr.stop_time();
+ BOOST_CHECK(ts5.is_zero());
+ const time_ns& ts6 = tcr.test_time();
+ BOOST_CHECK(ts6.is_zero());
+
+ ::usleep(1100000); // 1.1 sec in microseconds
+ tcr.set_stop_time();
+ BOOST_CHECK_EQUAL(tcr.exception(), false);
+ BOOST_CHECK_EQUAL(tcr.exception_count(), 0U);
+ const time_ns& ts7 = tcr.stop_time();
+ BOOST_CHECK(!ts7.is_zero());
+ const time_ns& ts8 = tcr.test_time();
+ BOOST_CHECK(ts8.tv_sec == 1);
+ BOOST_CHECK(ts8.tv_nsec > 100000000); // 0.1 sec in nanoseconds
+ BOOST_CHECK(ts8.tv_nsec < 200000000); // 0.2 sec in nanoseconds
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(start_exception_stop_1)
+{
+ cout << test_filename << ".start_exception_stop_1: " << flush;
+ const string jid("journal id 3");
+ test_case_result tcr(jid);
+ const u_int32_t err_code = 0x321;
+ const string err_msg = "exception message";
+ const jexception e(err_code, err_msg);
+ tcr.set_start_time();
+ ::usleep(1100000); // 1.1 sec in microseconds
+ tcr.add_exception(e);
+ BOOST_CHECK_EQUAL(tcr.exception(), true);
+ BOOST_CHECK_EQUAL(tcr.exception_count(), 1U);
+ BOOST_CHECK_EQUAL(tcr[0], e.what());
+ const time_ns& ts1 = tcr.stop_time();
+ BOOST_CHECK(!ts1.is_zero());
+ const time_ns& ts2 = tcr.test_time();
+ BOOST_CHECK(ts2.tv_sec == 1);
+ BOOST_CHECK(ts2.tv_nsec > 100000000); // 0.1 sec in nanoseconds
+ BOOST_CHECK(ts2.tv_nsec < 200000000); // 0.2 sec in nanoseconds
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(start_exception_stop_2)
+{
+ cout << test_filename << ".start_exception_stop_2: " << flush;
+ const string jid("journal id 4");
+ test_case_result tcr(jid);
+ const string err_msg = "exception message";
+ tcr.set_start_time();
+ ::usleep(1100000); // 1.1 sec in microseconds
+ tcr.add_exception(err_msg);
+ BOOST_CHECK_EQUAL(tcr.exception(), true);
+ BOOST_CHECK_EQUAL(tcr.exception_count(), 1U);
+ BOOST_CHECK_EQUAL(tcr[0], err_msg);
+ const time_ns& ts1 = tcr.stop_time();
+ BOOST_CHECK(!ts1.is_zero());
+ const time_ns& ts2 = tcr.test_time();
+ BOOST_CHECK(ts2.tv_sec == 1);
+ BOOST_CHECK(ts2.tv_nsec > 100000000); // 0.1 sec in nanoseconds
+ BOOST_CHECK(ts2.tv_nsec < 200000000); // 0.2 sec in nanoseconds
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(start_exception_stop_3)
+{
+ cout << test_filename << ".start_exception_stop_3: " << flush;
+ const string jid("journal id 5");
+ test_case_result tcr(jid);
+ const char* err_msg = "exception message";
+ tcr.set_start_time();
+ ::usleep(1100000); // 1.1 sec in microseconds
+ tcr.add_exception(err_msg);
+ BOOST_CHECK_EQUAL(tcr.exception(), true);
+ BOOST_CHECK_EQUAL(tcr.exception_count(), 1U);
+ BOOST_CHECK_EQUAL(tcr[0], err_msg);
+ const time_ns& ts1 = tcr.stop_time();
+ BOOST_CHECK(!ts1.is_zero());
+ const time_ns& ts2 = tcr.test_time();
+ BOOST_CHECK(ts2.tv_sec == 1);
+ BOOST_CHECK(ts2.tv_nsec > 100000000); // 0.1 sec in nanoseconds
+ BOOST_CHECK(ts2.tv_nsec < 200000000); // 0.2 sec in nanoseconds
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(start_exception)
+{
+ cout << test_filename << ".start_exception: " << flush;
+ const string jid("journal id 6");
+ test_case_result tcr(jid);
+ u_int32_t err_code = 0x654;
+ const string err_msg = "exception message";
+ const jexception e(err_code, err_msg);
+ tcr.set_start_time();
+ ::usleep(1100000); // 1.1 sec in microseconds
+ tcr.add_exception(e, false);
+ BOOST_CHECK_EQUAL(tcr.exception(), true);
+ BOOST_CHECK_EQUAL(tcr.exception_count(), 1U);
+ BOOST_CHECK_EQUAL(tcr[0], e.what());
+ const time_ns& ts1 = tcr.stop_time();
+ BOOST_CHECK(ts1.is_zero());
+ const time_ns& ts2 = tcr.test_time();
+ BOOST_CHECK(ts2.is_zero());
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(counters)
+{
+ cout << test_filename << ".counters: " << flush;
+ const u_int32_t num_enq = 125;
+ const u_int32_t num_deq = 64;
+ const u_int32_t num_read = 22;
+ const string jid("journal id 7");
+ test_case_result tcr(jid);
+ BOOST_CHECK_EQUAL(tcr.num_enq(), u_int32_t(0));
+ BOOST_CHECK_EQUAL(tcr.num_deq(), u_int32_t(0));
+ BOOST_CHECK_EQUAL(tcr.num_read(), u_int32_t(0));
+ for (unsigned i=0; i<num_enq; i++)
+ tcr.incr_num_enq();
+ BOOST_CHECK_EQUAL(tcr.num_enq(), num_enq);
+ BOOST_CHECK_EQUAL(tcr.num_deq(), u_int32_t(0));
+ BOOST_CHECK_EQUAL(tcr.num_read(), u_int32_t(0));
+ for (unsigned j=0; j<num_deq; j++)
+ tcr.incr_num_deq();
+ BOOST_CHECK_EQUAL(tcr.num_enq(), num_enq);
+ BOOST_CHECK_EQUAL(tcr.num_deq(), num_deq);
+ BOOST_CHECK_EQUAL(tcr.num_read(), u_int32_t(0));
+ for (unsigned k=0; k<num_read; k++)
+ tcr.incr_num_read();
+ BOOST_CHECK_EQUAL(tcr.num_enq(), num_enq);
+ BOOST_CHECK_EQUAL(tcr.num_deq(), num_deq);
+ BOOST_CHECK_EQUAL(tcr.num_read(), num_read);
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_SUITE_END()
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_result_agregation.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_result_agregation.cpp
new file mode 100644
index 0000000000..aa01bf833d
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_result_agregation.cpp
@@ -0,0 +1,178 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "../../unit_test.h"
+#include <ctime>
+#include <iostream>
+#include "test_case_result_agregation.h"
+
+using namespace boost::unit_test;
+using namespace mrg::journal;
+using namespace mrg::jtt;
+using namespace std;
+
+QPID_AUTO_TEST_SUITE(jtt_test_case_result_agregation)
+
+const string test_filename("_ut_test_case_result_agregation");
+
+// === Helper functions ===
+
+void check_agregate(const test_case_result_agregation& tcra, const u_int32_t num_enq,
+ const u_int32_t num_deq, const u_int32_t num_reads, const u_int32_t num_results,
+ const u_int32_t num_exceptions, const std::time_t secs, const long nsec)
+{
+ BOOST_CHECK_EQUAL(tcra.num_enq(), num_enq);
+ BOOST_CHECK_EQUAL(tcra.num_deq(), num_deq);
+ BOOST_CHECK_EQUAL(tcra.num_read(), num_reads);
+ BOOST_CHECK_EQUAL(tcra.num_results(), num_results);
+ BOOST_CHECK_EQUAL(tcra.exception_count(), num_exceptions);
+ BOOST_CHECK_EQUAL(tcra.exception(), num_exceptions > 0);
+ const time_ns& ts1 = tcra.test_time();
+ BOOST_CHECK_EQUAL(ts1.tv_sec, secs);
+ BOOST_CHECK_EQUAL(ts1.tv_nsec, nsec);
+}
+
+test_case_result::shared_ptr make_result(const string& jid, const u_int32_t num_enq,
+ const u_int32_t num_deq, const u_int32_t num_reads, const std::time_t secs, const long nsec)
+{
+ test_case_result::shared_ptr tcrp(new test_case_result(jid));
+ for (unsigned i=0; i<num_enq; i++)
+ tcrp->incr_num_enq();
+ for (unsigned i=0; i<num_deq; i++)
+ tcrp->incr_num_deq();
+ for (unsigned i=0; i<num_reads; i++)
+ tcrp->incr_num_read();
+ time_ns ts(secs, nsec);
+ tcrp->set_test_time(ts);
+ return tcrp;
+}
+
+// === Test suite ===
+
+QPID_AUTO_TEST_CASE(constructor_1)
+{
+ cout << test_filename << ".constructor_1: " << flush;
+ test_case_result_agregation tcra;
+ BOOST_CHECK_EQUAL(tcra.tc_average_mode(), true);
+ BOOST_CHECK_EQUAL(tcra.jid(), "Average");
+ BOOST_CHECK_EQUAL(tcra.exception(), false);
+ BOOST_CHECK_EQUAL(tcra.exception_count(), 0U);
+ const time_ns& ts1 = tcra.start_time();
+ BOOST_CHECK(ts1.is_zero());
+ const time_ns& ts2 = tcra.stop_time();
+ BOOST_CHECK(ts2.is_zero());
+ const time_ns& ts3 = tcra.test_time();
+ BOOST_CHECK(ts3.is_zero());
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(constructor_2)
+{
+ cout << test_filename << ".constructor_2: " << flush;
+ string jid("journal id");
+ test_case_result_agregation tcra(jid);
+ BOOST_CHECK_EQUAL(tcra.tc_average_mode(), false);
+ BOOST_CHECK_EQUAL(tcra.jid(), jid);
+ BOOST_CHECK_EQUAL(tcra.exception(), false);
+ BOOST_CHECK_EQUAL(tcra.exception_count(), 0U);
+ const time_ns& ts1 = tcra.start_time();
+ BOOST_CHECK(ts1.is_zero());
+ const time_ns& ts2 = tcra.stop_time();
+ BOOST_CHECK(ts2.is_zero());
+ const time_ns& ts3 = tcra.test_time();
+ BOOST_CHECK(ts3.is_zero());
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(add_test_case)
+{
+ cout << test_filename << ".add_test_case: " << flush;
+ string jid("jid1");
+ test_case_result::shared_ptr tcrp1 = make_result("jid1", 10, 10, 0, 1, 101010101L);
+ test_case_result::shared_ptr tcrp2 = make_result("jid1", 25, 0, 35, 10, 20202020L);
+ test_case_result::shared_ptr tcrp3 = make_result("jid1", 0, 15, 5, 2, 555555555L);
+ test_case_result::shared_ptr tcrp4 = make_result("jid2", 100, 100, 100, 100, 323232324L);
+ test_case_result::shared_ptr tcrp5 = make_result("jid1", 5, 0, 0, 0, 100L);
+ tcrp5->add_exception(string("error 1"), false);
+ test_case_result::shared_ptr tcrp6 = make_result("jid3", 0, 5, 0, 0, 100L);
+ jexception e(0x123, "exception 2");
+ tcrp6->add_exception(e, false);
+ test_case_result::shared_ptr tcrp7 = make_result("jid1", 0, 0, 0, 0, 0L);
+ test_case_result::shared_ptr tcrp8 = make_result("jid1", 200, 100, 300, 12, 323232224L);
+
+ test_case_result_agregation tcra(jid);
+ check_agregate(tcra, 0, 0, 0, 0, 0, 0, 0L);
+ tcra.add_test_result(tcrp1);
+ check_agregate(tcra, 10, 10, 0, 1, 0, 1, 101010101L);
+ tcra.add_test_result(tcrp2);
+ check_agregate(tcra, 35, 10, 35, 2, 0, 11, 121212121L);
+ tcra.add_test_result(tcrp3);
+ check_agregate(tcra, 35, 25, 40, 3, 0, 13, 676767676L);
+ tcra.add_test_result(tcrp4);
+ check_agregate(tcra, 35, 25, 40, 3, 0, 13, 676767676L);
+ tcra.add_test_result(tcrp5);
+ check_agregate(tcra, 40, 25, 40, 4, 1, 13, 676767776L);
+ tcra.add_test_result(tcrp6);
+ check_agregate(tcra, 40, 25, 40, 4, 1, 13, 676767776L);
+ tcra.add_test_result(tcrp7);
+ check_agregate(tcra, 40, 25, 40, 5, 1, 13, 676767776L);
+ tcra.add_test_result(tcrp8);
+ check_agregate(tcra, 240, 125, 340, 6, 1, 26, 0L);
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(add_test_case_average)
+{
+ cout << test_filename << ".add_test_case_average: " << flush;
+ test_case_result::shared_ptr tcrp1 = make_result("jid1", 10, 10, 0, 1, 101010101L);
+ test_case_result::shared_ptr tcrp2 = make_result("jid2", 25, 0, 35, 10, 20202020L);
+ test_case_result::shared_ptr tcrp3 = make_result("jid3", 0, 15, 5, 2, 555555555L);
+ test_case_result::shared_ptr tcrp4 = make_result("jid4", 100, 100, 100, 100, 323232324L);
+ test_case_result::shared_ptr tcrp5 = make_result("jid5", 5, 0, 0, 0, 100L);
+ tcrp5->add_exception(string("error 1"), false);
+ test_case_result::shared_ptr tcrp6 = make_result("jid6", 0, 5, 0, 0, 100L);
+ jexception e(0x123, "exception 2");
+ tcrp6->add_exception(e, false);
+ test_case_result::shared_ptr tcrp7 = make_result("jid7", 0, 0, 0, 0, 0L);
+ test_case_result::shared_ptr tcrp8 = make_result("jid8", 200, 100, 300, 12, 222222022L);
+
+ test_case_result_agregation tcra;
+ check_agregate(tcra, 0, 0, 0, 0, 0, 0, 0L);
+ tcra.add_test_result(tcrp1);
+ check_agregate(tcra, 10, 10, 0, 1, 0, 1, 101010101L);
+ tcra.add_test_result(tcrp2);
+ check_agregate(tcra, 35, 10, 35, 2, 0, 11, 121212121L);
+ tcra.add_test_result(tcrp3);
+ check_agregate(tcra, 35, 25, 40, 3, 0, 13, 676767676L);
+ tcra.add_test_result(tcrp4);
+ check_agregate(tcra, 135, 125, 140, 4, 0, 114, 0L);
+ tcra.add_test_result(tcrp5);
+ check_agregate(tcra, 140, 125, 140, 5, 1, 114, 100L);
+ tcra.add_test_result(tcrp6);
+ check_agregate(tcra, 140, 130, 140, 6, 2, 114, 200L);
+ tcra.add_test_result(tcrp7);
+ check_agregate(tcra, 140, 130, 140, 7, 2, 114, 200L);
+ tcra.add_test_result(tcrp8);
+ check_agregate(tcra, 340, 230, 440, 8, 2, 126, 222222222L);
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_SUITE_END()
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_set.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_set.cpp
new file mode 100644
index 0000000000..adbdf6884b
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_set.cpp
@@ -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.
+ *
+ */
+
+#include "../../unit_test.h"
+#include <cstddef>
+#include <iostream>
+#include <sys/stat.h>
+#include "test_case.h"
+#include "test_case_set.h"
+
+using namespace boost::unit_test;
+using namespace mrg::jtt;
+using namespace std;
+
+QPID_AUTO_TEST_SUITE(jtt_test_case_set)
+
+const string csv_file("_ut_test_case_set.csv");
+const string test_filename("_ut_test_case_set");
+
+// === Helper functions ===
+
+bool check_csv_file(const char* filename)
+{
+ struct stat s;
+ if (::stat(filename, &s))
+ return false;
+ if (S_ISREG(s.st_mode))
+ return true;
+ return false;
+}
+
+// === Test suite ===
+
+QPID_AUTO_TEST_CASE(constructor)
+{
+ cout << test_filename << ".constructor: " << flush;
+ test_case_set tcs;
+ BOOST_CHECK(tcs.empty());
+ BOOST_CHECK_EQUAL(tcs.size(), unsigned(0));
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(append_1)
+{
+ cout << test_filename << ".append_1: " << flush;
+ const unsigned test_case_num = 0x12345;
+ const u_int32_t num_msgs = 0x100;
+ const std::size_t min_data_size = 0x1000;
+ const std::size_t max_data_size = 0;
+ const bool auto_deq = true;
+ const std::size_t min_xid_size = 0x200;
+ const std::size_t max_xid_size = 0x200;
+ using mrg::jtt::test_case;
+ const test_case::transient_t transient = test_case::JTT_PERSISTNET;
+ const test_case::external_t external = test_case::JDL_INTERNAL;
+ const string comment = "This is a test";
+
+ test_case_set tcs;
+ tcs.append(test_case_num, num_msgs, min_data_size, max_data_size, auto_deq, min_xid_size,
+ max_xid_size, transient, external, comment);
+ BOOST_CHECK(!tcs.empty());
+ BOOST_CHECK_EQUAL(tcs.size(), unsigned(1));
+ test_case::shared_ptr tcp = tcs[0];
+ BOOST_CHECK_EQUAL(tcp->test_case_num(), test_case_num);
+ BOOST_CHECK_EQUAL(tcp->num_msgs(), num_msgs);
+ BOOST_CHECK_EQUAL(tcp->min_data_size(), min_data_size);
+ BOOST_CHECK_EQUAL(tcp->max_data_size(), max_data_size);
+ BOOST_CHECK_EQUAL(tcp->min_xid_size(), min_xid_size);
+ BOOST_CHECK_EQUAL(tcp->max_xid_size(), max_xid_size);
+ BOOST_CHECK_EQUAL(tcp->transient(), transient);
+ BOOST_CHECK_EQUAL(tcp->external(), external);
+ BOOST_CHECK_EQUAL(tcp->comment(), comment);
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(append_2)
+{
+ cout << test_filename << ".append_2: " << flush;
+ const unsigned test_case_num = 0x12345;
+ const u_int32_t num_msgs = 0x100;
+ const std::size_t min_data_size = 0x1000;
+ const std::size_t max_data_size = 0;
+ const bool auto_deq = true;
+ const std::size_t min_xid_size = 0x200;
+ const std::size_t max_xid_size = 0x200;
+ using mrg::jtt::test_case;
+ const test_case::transient_t transient = test_case::JTT_PERSISTNET;
+ const test_case::external_t external = test_case::JDL_INTERNAL;
+ const string comment = "This is a test";
+
+ test_case::shared_ptr tcp(new test_case(test_case_num, num_msgs, min_data_size, max_data_size,
+ auto_deq, min_xid_size, max_xid_size, transient, external, comment));
+ test_case_set tcs;
+ tcs.append(tcp);
+ BOOST_CHECK(!tcs.empty());
+ BOOST_CHECK_EQUAL(tcs.size(), unsigned(1));
+ tcp = tcs[0];
+ BOOST_CHECK_EQUAL(tcp->test_case_num(), test_case_num);
+ BOOST_CHECK_EQUAL(tcp->num_msgs(), num_msgs);
+ BOOST_CHECK_EQUAL(tcp->min_data_size(), min_data_size);
+ BOOST_CHECK_EQUAL(tcp->max_data_size(), max_data_size);
+ BOOST_CHECK_EQUAL(tcp->min_xid_size(), min_xid_size);
+ BOOST_CHECK_EQUAL(tcp->max_xid_size(), max_xid_size);
+ BOOST_CHECK_EQUAL(tcp->transient(), transient);
+ BOOST_CHECK_EQUAL(tcp->external(), external);
+ BOOST_CHECK_EQUAL(tcp->comment(), comment);
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_CASE(append_from_csv)
+{
+ cout << test_filename << ".append_from_csv: " << flush;
+ test_case_set tcs;
+ BOOST_REQUIRE_MESSAGE(check_csv_file(csv_file.c_str()), "Test CSV file \"" << csv_file <<
+ "\" is missing.");
+ tcs.append_from_csv(csv_file, false);
+ BOOST_CHECK(!tcs.empty());
+ BOOST_CHECK_EQUAL(tcs.size(), unsigned(44));
+ BOOST_CHECK_EQUAL(tcs.ignored(), unsigned(0));
+ tcs.clear();
+ BOOST_CHECK(tcs.empty());
+ tcs.append_from_csv(csv_file, true);
+ BOOST_CHECK(!tcs.empty());
+ BOOST_CHECK_EQUAL(tcs.size(), unsigned(18));
+ BOOST_CHECK_EQUAL(tcs.ignored(), unsigned(26));
+ cout << "ok" << endl;
+}
+
+QPID_AUTO_TEST_SUITE_END()
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_set.csv b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_set.csv
new file mode 100644
index 0000000000..f886186275
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/_ut_test_case_set.csv
@@ -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.
+#
+
+,,,,,,,"Msg size",,"Xid size",,,,,"enq-size",,"deq-size",,"txn-size",,
+"Col. 0","1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20"
+"Test #","tf","pf","amn","mn incr","#msgs","ms incr","Min","Max","Min","Max","auto-deq","transient","extern","bytes","dblks","bytes","dblks","bytes","dblks","comment"
+,,,,,,,,,,,,,,,,,,,,
+"Initialize only",,,,,,,,,,,,,,,,,,,,
+0,"L",0,0,0,0,0,0,0,0,0,FALSE,FALSE,FALSE,44,1,0,0,0,0,"No messages - journal creation/initialization only"
+,,,,,,,,,,,,,,,,,,,,
+"Simple message combinations of persistent/deq transientueued/non-dequeued, transactional/non-transactional",,,,,,,,,,,,,,,,,,,,
+1,"L",1,1,0,1,0,10,10,0,0,FALSE,FALSE,FALSE,54,1,0,0,0,0,"1 * 10-byte message"
+2,"L",1,10,0,10,0,10,10,0,0,FALSE,FALSE,FALSE,54,1,0,0,0,0,"10 * 10-byte message"
+3,"L",1,1,0,1,0,10,10,0,0,FALSE,TRUE,FALSE,54,1,0,0,0,0,"1 * 10-byte message [transient]"
+4,"L",1,10,0,10,0,10,10,0,0,FALSE,TRUE,FALSE,54,1,0,0,0,0,"10 * 10-byte message [transient]"
+5,"L",1,1,0,1,0,10,10,10,10,FALSE,FALSE,FALSE,64,1,0,0,0,0,"1 * 10-byte message [txn]"
+6,"L",1,10,0,10,0,10,10,10,10,FALSE,FALSE,FALSE,64,1,0,0,0,0,"10 * 10-byte message [txn]"
+7,"L",1,1,0,1,0,10,10,10,10,FALSE,TRUE,FALSE,64,1,0,0,0,0,"1 * 10-byte message [txn transient]"
+8,"L",1,10,0,10,0,10,10,10,10,FALSE,TRUE,FALSE,64,1,0,0,0,0,"10 * 10-byte message [txn transient]"
+9,"L",1,1,0,1,0,10,10,0,0,TRUE,FALSE,FALSE,54,1,32,1,0,0,"1 * 10-byte message [deq]"
+10,"L",1,10,0,10,0,10,10,0,0,TRUE,FALSE,FALSE,54,1,32,1,0,0,"10 * 10-byte message [deq]"
+11,"L",1,1,0,1,0,10,10,0,0,TRUE,TRUE,FALSE,54,1,32,1,0,0,"1 * 10-byte message [deq transient]"
+12,"L",1,10,0,10,0,10,10,0,0,TRUE,TRUE,FALSE,54,1,32,1,0,0,"10 * 10-byte message [deq transient]"
+13,"L",1,1,0,1,0,10,10,10,10,TRUE,FALSE,FALSE,64,1,54,1,46,1,"1 * 10-byte message [deq txn]"
+14,"L",1,10,0,10,0,10,10,10,10,TRUE,FALSE,FALSE,64,1,54,1,46,1,"10 * 10-byte message [deq txn]"
+15,"L",1,1,0,1,0,10,10,10,10,TRUE,TRUE,FALSE,64,1,54,1,46,1,"1 * 10-byte message [txn deq transient]"
+16,"L",1,10,0,10,0,10,10,10,10,TRUE,TRUE,FALSE,64,1,54,1,46,1,"10 * 10-byte message [txn deq transient]"
+17,"L",1,1,0,1,0,10,10,0,0,FALSE,FALSE,TRUE,54,1,0,0,0,0,"1 * 10-byte message [extern]"
+18,"L",1,10,0,10,0,10,10,0,0,FALSE,FALSE,TRUE,54,1,0,0,0,0,"10 * 10-byte message [extern]"
+19,"L",1,1,0,1,0,10,10,0,0,FALSE,TRUE,TRUE,54,1,0,0,0,0,"1 * 10-byte message [transient extern]"
+20,"L",1,10,0,10,0,10,10,0,0,FALSE,TRUE,TRUE,54,1,0,0,0,0,"10 * 10-byte message [transient extern]"
+21,"L",1,1,0,1,0,10,10,10,10,FALSE,FALSE,TRUE,64,1,0,0,0,0,"1 * 10-byte message [txn extern]"
+22,"L",1,10,0,10,0,10,10,10,10,FALSE,FALSE,TRUE,64,1,0,0,0,0,"10 * 10-byte message [txn extern]"
+23,"L",1,1,0,1,0,10,10,10,10,FALSE,TRUE,TRUE,64,1,0,0,0,0,"1 * 10-byte message [txn transient extern]"
+24,"L",1,10,0,10,0,10,10,10,10,FALSE,TRUE,TRUE,64,1,0,0,0,0,"10 * 10-byte message [txn transient extern]"
+25,"L",1,1,0,1,0,10,10,0,0,TRUE,FALSE,TRUE,54,1,32,1,0,0,"1 * 10-byte message [deq extern]"
+26,"L",1,10,0,10,0,10,10,0,0,TRUE,FALSE,TRUE,54,1,32,1,0,0,"10 * 10-byte message [deq extern]"
+27,"L",1,1,0,1,0,10,10,0,0,TRUE,TRUE,TRUE,54,1,32,1,0,0,"1 * 10-byte message [deq transient extern]"
+28,"L",1,10,0,10,0,10,10,0,0,TRUE,TRUE,TRUE,54,1,32,1,0,0,"10 * 10-byte message [deq transient extern]"
+29,"L",1,1,0,1,0,10,10,10,10,TRUE,FALSE,TRUE,64,1,54,1,46,1,"1 * 10-byte message [deq txn extern]"
+30,"L",1,10,0,10,0,10,10,10,10,TRUE,FALSE,TRUE,64,1,54,1,46,1,"10 * 10-byte message [deq txn extern]"
+31,"L",1,1,0,1,0,10,10,10,10,TRUE,TRUE,TRUE,64,1,54,1,46,1,"1 * 10-byte message [txn deq transient extern]"
+32,"L",1,10,0,10,0,10,10,10,10,TRUE,TRUE,TRUE,64,1,54,1,46,1,"10 * 10-byte message [txn deq transient extern]"
+,,,,,,,,,,,,,,,,,,,,
+"High volume tests of random message lengths - RHM_WRONLY req'd for auto-dequeue == FALSE",,,,,,,,,,,,,,,,,,,,
+33,"M",1,5000000,0,5000000,0,0,100,1,100,FALSE,RANDOM,RANDOM,244,2,0,0,0,0,"100 bytes xid max + 100 bytes data max [txn]"
+34,"M",3,3000000,0,3000000,0,0,300,1,300,FALSE,RANDOM,RANDOM,644,6,0,0,0,0,"300 bytes xid max + 300 bytes data max [txn]"
+35,"M",10,1600000,0,1600000,0,0,1000,1,1000,FALSE,RANDOM,RANDOM,2044,16,0,0,0,0,"1000 bytes xid max + 1000 bytes data max [txn]"
+36,"M",30,600000,0,600000,0,0,3000,1,3000,FALSE,RANDOM,RANDOM,6044,48,0,0,0,0,"3000 bytes xid max + 3000 bytes data max [txn]"
+37,"M",100,200000,0,200000,0,0,10000,1,10000,FALSE,RANDOM,RANDOM,20044,157,0,0,0,0,"10000 bytes xid max + 10000 bytes data max [txn]"
+38,"M",300,60000,0,60000,0,0,30000,1,30000,FALSE,RANDOM,RANDOM,60044,470,0,0,0,0,"30000 bytes xid max + 30000 bytes data max [txn]"
+39,"M",1000,20000,0,20000,0,0,100000,1,100000,FALSE,RANDOM,RANDOM,200044,1563,0,0,0,0,"100000 bytes xid max + 100000 bytes data max [txn]"
+,,,,,,,,,,,,,,,,,,,,
+"STANDARD PERFORMANCE BENCHMARK: 10,000,000 writes, data=212b (2 dblks)",,,,,,,,,,,,,,,,,,,,
+40,"M",1,10000000,0,10000000,0,212,212,0,0,FALSE,FALSE,FALSE,256,2,0,0,0,0,"212 bytes data (2 dblks enq)"
+41,"M",1,10000000,0,10000000,0,148,148,64,64,FALSE,FALSE,FALSE,256,2,0,0,0,0,"148 bytes data + 64 bytes xid (2 dblks enq)"
+42,"M",1,10000000,0,10000000,0,212,212,0,0,TRUE,FALSE,FALSE,256,2,32,1,0,0,"212 bytes data (2 dblks enq + 1 dblk deq)"
+43,"M",1,10000000,0,10000000,0,148,148,64,64,TRUE,FALSE,FALSE,256,2,108,1,100,1,"148 bytes data + 64 bytes xid (2 dblks enq + 1 dblks deq + 1 dblks txn)"
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/args.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/args.cpp
new file mode 100644
index 0000000000..0f041c380e
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/args.cpp
@@ -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.
+ *
+ */
+
+#include "args.h"
+
+#include <cstddef>
+#include <iostream>
+
+namespace po = boost::program_options;
+
+namespace mrg
+{
+namespace jtt
+{
+
+args::args(std::string opt_title):
+ _options_descr(opt_title),
+ format_chk(false),
+ keep_jrnls(false),
+ lld_rd_num(10),
+ lld_skip_num(100),
+ num_jrnls(1),
+ pause_secs(0),
+ randomize(false),
+ read_mode(),
+ read_prob(50),
+ recover_mode(false),
+ repeat_flag(false),
+ reuse_instance(false),
+ seed(0)
+{
+ _options_descr.add_options()
+ ("csv-file,c",
+ po::value<std::string>(&test_case_csv_file_name)->default_value("jtt.csv"),
+ "CSV file containing test cases.")
+
+ ("format-chk",
+ po::value<bool>(&format_chk)->zero_tokens(),
+ "Check the format of each journal file.")
+
+ ("help,h", "This help message.")
+
+ ("jrnl-dir",
+ po::value<std::string>(&journal_dir)->default_value("/var/tmp/jtt"),
+ "Directory in which journal files will be placed.")
+
+ ("keep-jrnls",
+ po::value<bool>(&keep_jrnls)->zero_tokens(),
+ "Keep all test journals.")
+
+ ("lld-rd-num",
+ po::value<unsigned>(&lld_rd_num)->default_value(10),
+ "Number of consecutive messages to read after only dequeueing lld-skip-num "
+ "messages during lazy-loading. Ignored if read-mode is not set to LAZYLOAD.")
+
+ ("lld-skip-num",
+ po::value<unsigned>(&lld_skip_num)->default_value(100),
+ "Number of consecutive messages to dequeue only (without reading) prior to "
+ "reading lld-rd-num messages. Ignored if read-mode is not set to LAZYLOAD.")
+
+ ("num-jrnls",
+ po::value<unsigned>(&num_jrnls)->default_value(1),
+ "Number of simultaneous journal instances to test.")
+
+ ("pause",
+ po::value<unsigned>(&pause_secs)->default_value(0),
+ "Pause in seconds between test cases (allows disk to catch up).")
+
+ ("randomize",
+ po::value<bool>(&randomize)->zero_tokens(),
+ "Randomize the order of the tests.")
+
+ ("read-mode",
+ po::value<read_arg>(&read_mode)->default_value(read_arg::NONE),
+ read_arg::descr().c_str())
+
+ ("read-prob",
+ po::value<unsigned>(&read_prob)->default_value(50),
+ "Read probability (percent) for each message when read-mode is set to RANDOM.")
+
+ ("recover-mode",
+ po::value<bool>(&recover_mode)->zero_tokens(),
+ "Recover journal from the previous test for each test case.")
+
+ ("repeat",
+ po::value<bool>(&repeat_flag)->zero_tokens(),
+ "Repeat all test cases indefinitely.")
+
+ ("reuse-instance",
+ po::value<bool>(&reuse_instance)->zero_tokens(),
+ "Reuse journal instance for all test cases.")
+
+ ("seed",
+ po::value<unsigned>(&seed)->default_value(0),
+ "Seed for use in random number generator.")
+
+ ("analyzer",
+ po::value<std::string>(&jfile_analyzer)->default_value("./file_chk.py"),
+ "Journal file analyzer program to use when the --format-chk option is used, ignored otherwise.")
+
+ ;
+}
+
+bool
+args::parse(int argc, char** argv) // return true if error, false if ok
+{
+ try
+ {
+ po::store(po::parse_command_line(argc, argv, _options_descr), _vmap);
+ po::notify(_vmap);
+ }
+ catch (const std::exception& e)
+ {
+ std::cout << "ERROR: " << e.what() << std::endl;
+ return usage();
+ }
+ if (_vmap.count("help"))
+ return usage();
+ if (num_jrnls == 0)
+ {
+ std::cout << "ERROR: num-jrnls must be 1 or more." << std::endl;
+ return usage();
+ }
+ if (read_prob > 100) // read_prob is unsigned, so no need to check < 0
+ {
+ std::cout << "ERROR: read-prob must be between 0 and 100 inclusive." << std::endl;
+ return usage();
+ }
+ if (repeat_flag && keep_jrnls)
+ {
+ std::string resp;
+ std::cout << "WARNING: repeat and keep-jrnls: Monitor disk usage as test journals will"
+ " accumulate." << std::endl;
+ std::cout << "Continue? <y/n> ";
+ std::cin >> resp;
+ if (resp.size() == 1)
+ {
+ if (resp[0] != 'y' && resp[0] != 'Y')
+ return true;
+ }
+ else if (resp.size() == 3) // any combo of lower- and upper-case
+ {
+ if (resp[0] != 'y' && resp[0] != 'Y')
+ return true;
+ if (resp[1] != 'e' && resp[1] != 'E')
+ return true;
+ if (resp[2] != 's' && resp[2] != 'S')
+ return true;
+ }
+ else
+ return true;
+ }
+ return false;
+}
+
+bool
+args::usage() const
+{
+ std::cout << _options_descr << std::endl;
+ return true;
+}
+
+void
+args::print_args() const
+{
+ std::cout << "Number of journals: " << num_jrnls << std::endl;
+ std::cout << "Read mode: " << read_mode << std::endl;
+ if (read_mode.val() == read_arg::RANDOM)
+ std::cout << "Read probability: " << read_prob << " %" << std::endl;
+ if (read_mode.val() == read_arg::LAZYLOAD)
+ {
+ std::cout << "Lazy-load skips: " << lld_skip_num << std::endl;
+ std::cout << "Lazy-load reads: " << lld_rd_num << std::endl;
+ }
+ if (pause_secs)
+ std::cout << "Pause between test cases: " << pause_secs << " sec." << std::endl;
+ if (seed)
+ std::cout << "Randomize seed: " << seed << std::endl;
+ print_flags();
+}
+
+void
+args::print_flags() const
+{
+ if (format_chk || keep_jrnls || randomize || recover_mode || repeat_flag ||
+ reuse_instance)
+ {
+ std::cout << "Flag options:";
+ // TODO: Get flag args and their strings directly from _options_descr.
+ if (format_chk)
+ std::cout << " format-chk";
+ if (keep_jrnls)
+ std::cout << " keep-jrnls";
+ if (randomize)
+ std::cout << " randomize";
+ if (recover_mode)
+ std::cout << " recover-mode";
+ if (repeat_flag)
+ std::cout << " repeat-flag";
+ if (reuse_instance)
+ std::cout << " reuse-instance";
+ std::cout << std::endl;
+ }
+ std::cout << std::endl;
+}
+
+} // namespace jtt
+} // namespace mrg
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/args.h b/qpid/cpp/src/tests/legacystore/jrnl/jtt/args.h
new file mode 100644
index 0000000000..b6f7fb4a79
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/args.h
@@ -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.
+ *
+ */
+
+#ifndef mrg_jtt_args_hpp
+#define mrg_jtt_args_hpp
+
+#include <boost/program_options.hpp>
+#include "read_arg.h"
+
+namespace mrg
+{
+namespace jtt
+{
+
+ struct args
+ {
+ boost::program_options::options_description _options_descr;
+ boost::program_options::variables_map _vmap;
+
+ // Add args here
+ std::string jfile_analyzer;
+ std::string test_case_csv_file_name;
+ std::string journal_dir;
+ bool format_chk;
+ bool keep_jrnls;
+ unsigned lld_rd_num;
+ unsigned lld_skip_num;
+ unsigned num_jrnls;
+ unsigned pause_secs;
+ bool randomize;
+ read_arg read_mode;
+ unsigned read_prob;
+ bool recover_mode;
+ bool repeat_flag;
+ bool reuse_instance;
+ unsigned seed;
+
+ args(std::string opt_title);
+ bool parse(int argc, char** argv); // return true if error, false if ok
+ bool usage() const; // return true
+ void print_args() const;
+ void print_flags() const;
+ };
+
+} // namespace jtt
+} // namespace mrg
+
+#endif // ifndef mrg_jtt_args_hpp
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/data_src.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/data_src.cpp
new file mode 100644
index 0000000000..3530e0b223
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/data_src.cpp
@@ -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.
+ *
+ */
+
+#include "data_src.h"
+
+#include <cstddef>
+#include <iomanip>
+#include <sstream>
+
+namespace mrg
+{
+namespace jtt
+{
+
+char data_src::_data_src[data_src::max_dsize];
+char data_src::_xid_src[data_src::max_xsize];
+bool data_src::_initialized = data_src::__init();
+u_int64_t data_src::_xid_cnt = 0ULL;
+mrg::journal::smutex data_src::_sm;
+
+data_src::data_src()
+{}
+
+bool
+data_src::__init()
+{
+ for (unsigned i=0; i<max_dsize; i++)
+ _data_src[i] = '0' + ((i + 1) % 10); // 123456789012345...
+ for (unsigned j=0; j<max_xsize; j++)
+ _xid_src[j] = 'a' + (j % 26); // abc...xyzabc...
+ return true;
+}
+
+const char*
+data_src::get_data(const std::size_t offs)
+{
+ if (offs >= max_dsize) return 0;
+ return _data_src + offs;
+}
+
+std::string
+data_src::get_xid(const std::size_t xid_size)
+{
+ if (xid_size == 0)
+ return "";
+ std::ostringstream oss;
+ oss << std::setfill('0');
+ if (xid_size < 9)
+ oss << std::setw(xid_size) << get_xid_cnt();
+ else if (xid_size < 13)
+ oss << "xid:" << std::setw(xid_size - 4) << get_xid_cnt();
+ else
+ {
+ oss << "xid:" << std::setw(8) << get_xid_cnt() << ":";
+ oss.write(get_xid_content(13), xid_size - 13);
+ }
+ return oss.str();
+}
+
+const char*
+data_src::get_xid_content(const std::size_t offs)
+{
+ if (offs >= max_xsize) return 0;
+ return _xid_src + offs;
+}
+
+} // namespace jtt
+} // namespace mrg
+
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/data_src.h b/qpid/cpp/src/tests/legacystore/jrnl/jtt/data_src.h
new file mode 100644
index 0000000000..66dc613787
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/data_src.h
@@ -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.
+ *
+ */
+
+#ifndef mrg_jtt_data_src_hpp
+#define mrg_jtt_data_src_hpp
+
+#include <cstddef>
+#include "qpid/legacystore/jrnl/slock.h"
+#include "qpid/legacystore/jrnl/smutex.h"
+#include <pthread.h>
+#include <string>
+#include <sys/types.h>
+
+#define DATA_SIZE 1024 * 1024
+#define XID_SIZE 1024 * 1024
+
+namespace mrg
+{
+namespace jtt
+{
+ class data_src
+ {
+ public:
+ static const std::size_t max_dsize = DATA_SIZE;
+ static const std::size_t max_xsize = XID_SIZE;
+
+ private:
+ static char _data_src[];
+ static char _xid_src[];
+ static u_int64_t _xid_cnt;
+ static bool _initialized;
+ static mrg::journal::smutex _sm;
+
+ public:
+ static const char* get_data(const std::size_t offs);
+ static std::string get_xid(const std::size_t xid_size);
+
+ private:
+ data_src();
+ static u_int64_t get_xid_cnt() { mrg::journal::slock s(_sm); return _xid_cnt++; }
+ static const char* get_xid_content(const std::size_t offs);
+ static bool __init();
+ };
+
+} // namespace jtt
+} // namespace mrg
+
+#endif // ifndef mrg_jtt_data_src_hpp
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/jfile_chk.py b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jfile_chk.py
new file mode 100755
index 0000000000..36ef511f5c
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jfile_chk.py
@@ -0,0 +1,838 @@
+#!/usr/bin/env python
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 sys
+import getopt
+import string
+import xml.parsers.expat
+from struct import unpack, calcsize
+from time import gmtime, strftime
+
+dblk_size = 128
+sblk_size = 4 * dblk_size
+jfsize = None
+hdr_ver = 1
+
+TEST_NUM_COL = 0
+NUM_MSGS_COL = 5
+MIN_MSG_SIZE_COL = 7
+MAX_MSG_SIZE_COL = 8
+MIN_XID_SIZE_COL = 9
+MAX_XID_SIZE_COL = 10
+AUTO_DEQ_COL = 11
+TRANSIENT_COL = 12
+EXTERN_COL = 13
+COMMENT_COL = 20
+
+owi_mask = 0x01
+transient_mask = 0x10
+extern_mask = 0x20
+
+printchars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ '
+
+
+
+#== global functions ===========================================================
+
+def load(f, klass):
+ args = load_args(f, klass)
+ subclass = klass.discriminate(args)
+ result = subclass(*args)
+ if subclass != klass:
+ result.init(f, *load_args(f, subclass))
+ result.skip(f)
+ return result;
+
+def load_args(f, klass):
+ size = calcsize(klass.format)
+ foffs = f.tell(),
+ bin = f.read(size)
+ if len(bin) != size:
+ raise Exception("end of file")
+ return foffs + unpack(klass.format, bin)
+
+def size_blks(size, blk_size):
+ return (size + blk_size - 1)/blk_size
+
+def rem_in_blk(f, blk_size):
+ foffs = f.tell()
+ return (size_blks(f.tell(), blk_size) * blk_size) - foffs;
+
+def file_full(f):
+ return f.tell() >= jfsize
+
+def isprintable(s):
+ return s.strip(printchars) == ''
+
+def print_xid(xidsize, xid):
+ if xid == None:
+ if xidsize > 0:
+ raise Exception('Inconsistent XID size: xidsize=%d, xid=None' % xidsize)
+ return ''
+ if isprintable(xid):
+ xidstr = split_str(xid)
+ else:
+ xidstr = hex_split_str(xid)
+ if xidsize != len(xid):
+ raise Exception('Inconsistent XID size: xidsize=%d, xid(%d)=\"%s\"' % (xidsize, len(xid), xidstr))
+ return 'xid(%d)=\"%s\" ' % (xidsize, xidstr)
+
+def print_data(dsize, data):
+ if data == None:
+ return ''
+ if isprintable(data):
+ datastr = split_str(data)
+ else:
+ datastr = hex_split_str(data)
+ if dsize != len(data):
+ raise Exception('Inconsistent data size: dsize=%d, data(%d)=\"%s\"' % (dsize, len(data), datastr))
+ return 'data(%d)=\"%s\" ' % (dsize, datastr)
+
+def hex_split_str(s, split_size = 50):
+ if len(s) <= split_size:
+ return hex_str(s, 0, len(s))
+ if len(s) > split_size + 25:
+ return hex_str(s, 0, 10) + ' ... ' + hex_str(s, 55, 65) + ' ... ' + hex_str(s, len(s)-10, len(s))
+ return hex_str(s, 0, 10) + ' ... ' + hex_str(s, len(s)-10, len(s))
+
+def hex_str(s, b, e):
+ o = ''
+ for i in range(b, e):
+ if isprintable(s[i]):
+ o += s[i]
+ else:
+ o += '\\%02x' % ord(s[i])
+ return o
+
+def split_str(s, split_size = 50):
+ if len(s) < split_size:
+ return s
+ return s[:25] + ' ... ' + s[-25:]
+
+def inv_str(s):
+ si = ''
+ for i in range(0,len(s)):
+ si += chr(~ord(s[i]) & 0xff)
+ return si
+
+def load_file_data(f, size, data):
+ if size == 0:
+ return (data, True)
+ if data == None:
+ loaded = 0
+ else:
+ loaded = len(data)
+ foverflow = f.tell() + size - loaded > jfsize
+ if foverflow:
+ rsize = jfsize - f.tell()
+ else:
+ rsize = size - loaded
+ bin = f.read(rsize)
+ if data == None:
+ data = unpack('%ds' % (rsize), bin)[0]
+ else:
+ data = data + unpack('%ds' % (rsize), bin)[0]
+ return (data, not foverflow)
+
+def exit(code, qflag):
+ if code != 0 or not qflag:
+ print out.getvalue()
+ out.close()
+ sys.exit(code)
+
+#== class Sizeable =============================================================
+
+class Sizeable:
+
+ def size(self):
+ classes = [self.__class__]
+
+ size = 0
+ while classes:
+ cls = classes.pop()
+ if hasattr(cls, "format"):
+ size += calcsize(cls.format)
+ classes.extend(cls.__bases__)
+
+ return size
+
+
+#== class Hdr ==================================================================
+
+class Hdr(Sizeable):
+
+ format = '=4sBBHQ'
+
+ def discriminate(args):
+ return CLASSES.get(args[1][-1], Hdr)
+ discriminate = staticmethod(discriminate)
+
+ def __init__(self, foffs, magic, ver, end, flags, rid):
+ self.foffs = foffs
+ self.magic = magic
+ self.ver = ver
+ self.end = end
+ self.flags = flags
+ self.rid = rid
+ if self.magic[-1] not in ['0x00', 'a', 'c', 'd', 'e', 'f', 'x']:
+ error = 3
+
+ def __str__(self):
+ if self.empty():
+ return '0x%08x: <empty>' % (self.foffs)
+ if self.magic[-1] == 'x':
+ return '0x%08x: [\"%s\"]' % (self.foffs, self.magic)
+ if self.magic[-1] in ['a', 'c', 'd', 'e', 'f', 'x']:
+ return '0x%08x: [\"%s\" v=%d e=%d f=0x%04x rid=0x%x]' % (self.foffs, self.magic, self.ver, self.end, self.flags, self.rid)
+ return '0x%08x: <error, unknown magic \"%s\" (possible overwrite boundary?)>' % (self.foffs, self.magic)
+
+ def empty(self):
+ return self.magic == '\x00'*4
+
+ def owi(self):
+ return self.flags & owi_mask != 0
+
+ def skip(self, f):
+ f.read(rem_in_blk(f, dblk_size))
+
+ def check(self):
+ if self.empty() or self.magic[:3] != 'RHM' or self.magic[3] not in ['a', 'c', 'd', 'e', 'f', 'x']:
+ return True
+ if self.ver != hdr_ver and self.magic[-1] != 'x':
+ raise Exception('%s: Invalid header version: found %d, expected %d.' % (self, self.ver, hdr_ver))
+ return False
+
+
+#== class FileHdr ==============================================================
+
+class FileHdr(Hdr):
+
+ format = '=2H4x3Q'
+
+ def init(self, f, foffs, fid, lid, fro, time_sec, time_ns):
+ self.fid = fid
+ self.lid = lid
+ self.fro = fro
+ self.time_sec = time_sec
+ self.time_ns = time_ns
+
+ def __str__(self):
+ return '%s fid=%d lid=%d fro=0x%08x t=%s' % (Hdr.__str__(self), self.fid, self.lid, self.fro, self.timestamp_str())
+
+ def skip(self, f):
+ f.read(rem_in_blk(f, sblk_size))
+
+ def timestamp(self):
+ return (self.time_sec, self.time_ns)
+
+ def timestamp_str(self):
+ ts = gmtime(self.time_sec)
+ fstr = '%%a %%b %%d %%H:%%M:%%S.%09d %%Y' % (self.time_ns)
+ return strftime(fstr, ts)
+
+
+#== class DeqHdr ===============================================================
+
+class DeqHdr(Hdr):
+
+ format = '=QQ'
+
+ def init(self, f, foffs, deq_rid, xidsize):
+ self.deq_rid = deq_rid
+ self.xidsize = xidsize
+ self.xid = None
+ self.deq_tail = None
+ self.xid_complete = False
+ self.tail_complete = False
+ self.tail_bin = None
+ self.tail_offs = 0
+ self.load(f)
+
+ def load(self, f):
+ if self.xidsize == 0:
+ self.xid_complete = True
+ self.tail_complete = True
+ else:
+ if not self.xid_complete:
+ ret = load_file_data(f, self.xidsize, self.xid)
+ self.xid = ret[0]
+ self.xid_complete = ret[1]
+ if self.xid_complete and not self.tail_complete:
+ ret = load_file_data(f, calcsize(RecTail.format), self.tail_bin)
+ self.tail_bin = ret[0]
+ if ret[1]:
+ self.enq_tail = RecTail(self.tail_offs, *unpack(RecTail.format, self.tail_bin))
+ if self.enq_tail.magic_inv != inv_str(self.magic) or self.enq_tail.rid != self.rid:
+ print " > %s" % self
+ raise Exception('Invalid dequeue record tail (magic=%s; rid=%d) at 0x%08x' % (self.enq_tail, self.enq_tail.rid, self.enq_tail.foffs))
+ self.enq_tail.skip(f)
+ self.tail_complete = ret[1]
+ return self.complete()
+
+ def complete(self):
+ return self.xid_complete and self.tail_complete
+
+ def __str__(self):
+ return '%s %sdrid=0x%x' % (Hdr.__str__(self), print_xid(self.xidsize, self.xid), self.deq_rid)
+
+
+#== class TxnHdr ===============================================================
+
+class TxnHdr(Hdr):
+
+ format = '=Q'
+
+ def init(self, f, foffs, xidsize):
+ self.xidsize = xidsize
+ self.xid = None
+ self.tx_tail = None
+ self.xid_complete = False
+ self.tail_complete = False
+ self.tail_bin = None
+ self.tail_offs = 0
+ self.load(f)
+
+ def load(self, f):
+ if not self.xid_complete:
+ ret = load_file_data(f, self.xidsize, self.xid)
+ self.xid = ret[0]
+ self.xid_complete = ret[1]
+ if self.xid_complete and not self.tail_complete:
+ ret = load_file_data(f, calcsize(RecTail.format), self.tail_bin)
+ self.tail_bin = ret[0]
+ if ret[1]:
+ self.enq_tail = RecTail(self.tail_offs, *unpack(RecTail.format, self.tail_bin))
+ if self.enq_tail.magic_inv != inv_str(self.magic) or self.enq_tail.rid != self.rid:
+ print " > %s" % self
+ raise Exception('Invalid transaction record tail (magic=%s; rid=%d) at 0x%08x' % (self.enq_tail, self.enq_tail.rid, self.enq_tail.foffs))
+ self.enq_tail.skip(f)
+ self.tail_complete = ret[1]
+ return self.complete()
+
+ def complete(self):
+ return self.xid_complete and self.tail_complete
+
+ def __str__(self):
+ return '%s %s' % (Hdr.__str__(self), print_xid(self.xidsize, self.xid))
+
+
+#== class RecTail ==============================================================
+
+class RecTail(Sizeable):
+
+ format = '=4sQ'
+
+ def __init__(self, foffs, magic_inv, rid):
+ self.foffs = foffs
+ self.magic_inv = magic_inv
+ self.rid = rid
+
+ def __str__(self):
+ magic = inv_str(self.magic_inv)
+ return '[\"%s\" rid=0x%x]' % (magic, self.rid)
+
+ def skip(self, f):
+ f.read(rem_in_blk(f, dblk_size))
+
+
+#== class EnqRec ===============================================================
+
+class EnqRec(Hdr):
+
+ format = '=QQ'
+
+ def init(self, f, foffs, xidsize, dsize):
+ self.xidsize = xidsize
+ self.dsize = dsize
+ self.transient = self.flags & transient_mask > 0
+ self.extern = self.flags & extern_mask > 0
+ self.xid = None
+ self.data = None
+ self.enq_tail = None
+ self.xid_complete = False
+ self.data_complete = False
+ self.tail_complete = False
+ self.tail_bin = None
+ self.tail_offs = 0
+ self.load(f)
+
+ def load(self, f):
+ if not self.xid_complete:
+ ret = load_file_data(f, self.xidsize, self.xid)
+ self.xid = ret[0]
+ self.xid_complete = ret[1]
+ if self.xid_complete and not self.data_complete:
+ if self.extern:
+ self.data_complete = True
+ else:
+ ret = load_file_data(f, self.dsize, self.data)
+ self.data = ret[0]
+ self.data_complete = ret[1]
+ if self.data_complete and not self.tail_complete:
+ ret = load_file_data(f, calcsize(RecTail.format), self.tail_bin)
+ self.tail_bin = ret[0]
+ if ret[1]:
+ self.enq_tail = RecTail(self.tail_offs, *unpack(RecTail.format, self.tail_bin))
+ if self.enq_tail.magic_inv != inv_str(self.magic) or self.enq_tail.rid != self.rid:
+ print " > %s" % self
+ raise Exception('Invalid enqueue record tail (magic=%s; rid=%d) at 0x%08x' % (self.enq_tail, self.enq_tail.rid, self.enq_tail.foffs))
+ self.enq_tail.skip(f)
+ self.tail_complete = ret[1]
+ return self.complete()
+
+ def complete(self):
+ return self.xid_complete and self.data_complete and self.tail_complete
+
+ def print_flags(self):
+ s = ''
+ if self.transient:
+ s = '*TRANSIENT'
+ if self.extern:
+ if len(s) > 0:
+ s += ',EXTERNAL'
+ else:
+ s = '*EXTERNAL'
+ if len(s) > 0:
+ s += '*'
+ return s
+
+ def __str__(self):
+ return '%s %s%s %s %s' % (Hdr.__str__(self), print_xid(self.xidsize, self.xid), print_data(self.dsize, self.data), self.enq_tail, self.print_flags())
+
+
+#== class Main =================================================================
+
+class Main:
+ def __init__(self, argv):
+ self.bfn = None
+ self.csvfn = None
+ self.jdir = None
+ self.aflag = False
+ self.hflag = False
+ self.qflag = False
+ self.tnum = None
+ self.num_jfiles = None
+ self.num_msgs = None
+ self.msg_len = None
+ self.auto_deq = None
+ self.xid_len = None
+ self.transient = None
+ self.extern = None
+
+ self.file_start = 0
+ self.file_num = 0
+ self.fro = 0x200
+ self.emap = {}
+ self.tmap = {}
+ self.rec_cnt = 0
+ self.msg_cnt = 0
+ self.txn_msg_cnt = 0
+ self.fhdr = None
+ self.f = None
+ self.first_rec = False
+ self.last_file = False
+ self.last_rid = -1
+ self.fhdr_owi_at_msg_start = None
+
+ self.proc_args(argv)
+ self.proc_csv()
+ self.read_jinf()
+
+ def run(self):
+ try:
+ start_info = self.analyze_files()
+ stop = self.advance_file(*start_info)
+ except Exception:
+ print 'WARNING: All journal files are empty.'
+ if self.num_msgs > 0:
+ raise Exception('All journal files are empty, but %d msgs expectd.' % self.num_msgs)
+ else:
+ stop = True
+ while not stop:
+ warn = ''
+ if file_full(self.f):
+ stop = self.advance_file()
+ if stop:
+ break
+ hdr = load(self.f, Hdr)
+ if hdr.empty():
+ stop = True;
+ break
+ if hdr.check():
+ stop = True;
+ else:
+ self.rec_cnt += 1
+ self.fhdr_owi_at_msg_start = self.fhdr.owi()
+ if self.first_rec:
+ if self.fhdr.fro != hdr.foffs:
+ raise Exception('File header first record offset mismatch: fro=0x%08x; rec_offs=0x%08x' % (self.fhdr.fro, hdr.foffs))
+ else:
+ if not self.qflag: print ' * fro ok: 0x%08x' % self.fhdr.fro
+ self.first_rec = False
+ if isinstance(hdr, EnqRec) and not stop:
+ while not hdr.complete():
+ stop = self.advance_file()
+ if stop:
+ break
+ hdr.load(self.f)
+ if self.extern != None:
+ if hdr.extern:
+ if hdr.data != None:
+ raise Exception('Message data found on external record')
+ else:
+ if self.msg_len > 0 and len(hdr.data) != self.msg_len:
+ raise Exception('Message length (%d) incorrect; expected %d' % (len(hdr.data), self.msg_len))
+ else:
+ if self.msg_len > 0 and len(hdr.data) != self.msg_len:
+ raise Exception('Message length (%d) incorrect; expected %d' % (len(hdr.data), self.msg_len))
+ if self.xid_len > 0 and len(hdr.xid) != self.xid_len:
+ print ' ERROR: XID length (%d) incorrect; expected %d' % (len(hdr.xid), self.xid_len)
+ sys.exit(1)
+ #raise Exception('XID length (%d) incorrect; expected %d' % (len(hdr.xid), self.xid_len))
+ if self.transient != None:
+ if self.transient:
+ if not hdr.transient:
+ raise Exception('Expected transient record, found persistent')
+ else:
+ if hdr.transient:
+ raise Exception('Expected persistent record, found transient')
+ stop = not self.check_owi(hdr)
+ if stop:
+ warn = ' (WARNING: OWI mismatch - could be overwrite boundary.)'
+ else:
+ self.msg_cnt += 1
+ if self.aflag or self.auto_deq:
+ if hdr.xid == None:
+ self.emap[hdr.rid] = (self.fhdr.fid, hdr, False)
+ else:
+ self.txn_msg_cnt += 1
+ if hdr.xid in self.tmap:
+ self.tmap[hdr.xid].append((self.fhdr.fid, hdr)) #Append tuple to existing list
+ else:
+ self.tmap[hdr.xid] = [(self.fhdr.fid, hdr)] # Create new list
+ elif isinstance(hdr, DeqHdr) and not stop:
+ while not hdr.complete():
+ stop = self.advance_file()
+ if stop:
+ break
+ hdr.load(self.f)
+ stop = not self.check_owi(hdr)
+ if stop:
+ warn = ' (WARNING: OWI mismatch - could be overwrite boundary.)'
+ else:
+ if self.auto_deq != None:
+ if not self.auto_deq:
+ warn = ' WARNING: Dequeue record rid=%d found in non-dequeue test - ignoring.' % hdr.rid
+ if self.aflag or self.auto_deq:
+ if hdr.xid == None:
+ if hdr.deq_rid in self.emap:
+ if self.emap[hdr.deq_rid][2]:
+ warn = ' (WARNING: dequeue rid 0x%x dequeues locked enqueue record 0x%x)' % (hdr.rid, hdr.deq_rid)
+ del self.emap[hdr.deq_rid]
+ else:
+ warn = ' (WARNING: rid being dequeued 0x%x not found in enqueued records)' % hdr.deq_rid
+ else:
+ if hdr.deq_rid in self.emap:
+ t = self.emap[hdr.deq_rid]
+ self.emap[hdr.deq_rid] = (t[0], t[1], True) # Lock enq record
+ if hdr.xid in self.tmap:
+ self.tmap[hdr.xid].append((self.fhdr.fid, hdr)) #Append to existing list
+ else:
+ self.tmap[hdr.xid] = [(self.fhdr.fid, hdr)] # Create new list
+ elif isinstance(hdr, TxnHdr) and not stop:
+ while not hdr.complete():
+ stop = self.advance_file()
+ if stop:
+ break
+ hdr.load(self.f)
+ stop = not self.check_owi(hdr)
+ if stop:
+ warn = ' (WARNING: OWI mismatch - could be overwrite boundary.)'
+ else:
+ if hdr.xid in self.tmap:
+ mismatched_rids = []
+ if hdr.magic[-1] == 'c': # commit
+ for rec in self.tmap[hdr.xid]:
+ if isinstance(rec[1], EnqRec):
+ self.emap[rec[1].rid] = (rec[0], rec[1], False) # Transfer enq to emap
+ elif isinstance(rec[1], DeqHdr):
+ if rec[1].deq_rid in self.emap:
+ del self.emap[rec[1].deq_rid] # Delete from emap
+ else:
+ mismatched_rids.append('0x%x' % rec[1].deq_rid)
+ else:
+ raise Exception('Unknown header found in txn map: %s' % rec[1])
+ elif hdr.magic[-1] == 'a': # abort
+ for rec in self.tmap[hdr.xid]:
+ if isinstance(rec[1], DeqHdr):
+ if rec[1].deq_rid in self.emap:
+ t = self.emap[rec[1].deq_rid]
+ self.emap[rec[1].deq_rid] = (t[0], t[1], False) # Unlock enq record
+ del self.tmap[hdr.xid]
+ if len(mismatched_rids) > 0:
+ warn = ' (WARNING: transactional dequeues not found in enqueue map; rids=%s)' % mismatched_rids
+ else:
+ warn = ' (WARNING: %s not found in transaction map)' % print_xid(len(hdr.xid), hdr.xid)
+ if not self.qflag: print ' > %s%s' % (hdr, warn)
+ if not stop:
+ stop = (self.last_file and hdr.check()) or hdr.empty() or self.fhdr.empty()
+
+ def analyze_files(self):
+ fname = ''
+ fnum = -1
+ rid = -1
+ fro = -1
+ tss = ''
+ if not self.qflag: print 'Analyzing journal files:'
+ owi_found = False
+ for i in range(0, self.num_jfiles):
+ jfn = self.jdir + '/' + self.bfn + '.%04d.jdat' % i
+ f = open(jfn)
+ fhdr = load(f, Hdr)
+ if fhdr.empty():
+ if not self.qflag:
+ print ' %s: file empty' % jfn
+ break
+ if i == 0:
+ init_owi = fhdr.owi()
+ fname = jfn
+ fnum = i
+ rid = fhdr.rid
+ fro = fhdr.fro
+ tss = fhdr.timestamp_str()
+ elif fhdr.owi() != init_owi and not owi_found:
+ fname = jfn
+ fnum = i
+ rid = fhdr.rid
+ fro = fhdr.fro
+ tss = fhdr.timestamp_str()
+ owi_found = True
+ if not self.qflag:
+ print ' %s: owi=%s rid=0x%x, fro=0x%08x ts=%s' % (jfn, fhdr.owi(), fhdr.rid, fhdr.fro, fhdr.timestamp_str())
+ if fnum < 0 or rid < 0 or fro < 0:
+ raise Exception('All journal files empty')
+ if not self.qflag: print ' Oldest complete file: %s: rid=%d, fro=0x%08x ts=%s' % (fname, rid, fro, tss)
+ return (fnum, rid, fro)
+
+ def advance_file(self, *start_info):
+ seek_flag = False
+ if len(start_info) == 3:
+ self.file_start = self.file_num = start_info[0]
+ self.fro = start_info[2]
+ seek_flag = True
+ if self.f != None and file_full(self.f):
+ self.file_num = self.incr_fnum()
+ if self.file_num == self.file_start:
+ return True
+ if self.file_start == 0:
+ self.last_file = self.file_num == self.num_jfiles - 1
+ else:
+ self.last_file = self.file_num == self.file_start - 1
+ if self.file_num < 0 or self.file_num >= self.num_jfiles:
+ raise Exception('Bad file number %d' % self.file_num)
+ jfn = self.jdir + '/' + self.bfn + '.%04d.jdat' % self.file_num
+ self.f = open(jfn)
+ self.fhdr = load(self.f, Hdr)
+ if seek_flag and self.f.tell() != self.fro:
+ self.f.seek(self.fro)
+ self.first_rec = True
+ if not self.qflag: print jfn, ": ", self.fhdr
+ return False
+
+ def incr_fnum(self):
+ self.file_num += 1
+ if self.file_num >= self.num_jfiles:
+ self.file_num = 0;
+ return self.file_num
+
+ def check_owi(self, hdr):
+ return self.fhdr_owi_at_msg_start == hdr.owi()
+
+ def check_rid(self, hdr):
+ if self.last_rid != -1 and hdr.rid <= self.last_rid:
+ return False
+ self.last_rid = hdr.rid
+ return True
+
+ def read_jinf(self):
+ filename = self.jdir + '/' + self.bfn + '.jinf'
+ try:
+ f = open(filename, 'r')
+ except IOError:
+ print 'ERROR: Unable to open jinf file %s' % filename
+ sys.exit(1)
+ p = xml.parsers.expat.ParserCreate()
+ p.StartElementHandler = self.handleStartElement
+ p.CharacterDataHandler = self.handleCharData
+ p.EndElementHandler = self.handleEndElement
+ p.ParseFile(f)
+ if self.num_jfiles == None:
+ print 'ERROR: number_jrnl_files not found in jinf file "%s"!' % filename
+ if jfsize == None:
+ print 'ERROR: jrnl_file_size_sblks not found in jinf file "%s"!' % filename
+ if self.num_jfiles == None or jfsize == None:
+ sys.exit(1)
+
+ def handleStartElement(self, name, attrs):
+ global jfsize
+ if name == 'number_jrnl_files':
+ self.num_jfiles = int(attrs['value'])
+ if name == 'jrnl_file_size_sblks':
+ jfsize = (int(attrs['value']) + 1) * sblk_size
+
+ def handleCharData(self, data): pass
+
+ def handleEndElement(self, name): pass
+
+ def proc_csv(self):
+ if self.csvfn != None and self.tnum != None:
+ tparams = self.get_test(self.csvfn, self.tnum)
+ if tparams == None:
+ print 'ERROR: Test %d not found in CSV file "%s"' % (self.tnum, self.csvfn)
+ sys.exit(1)
+ self.num_msgs = tparams['num_msgs']
+ if tparams['min_size'] == tparams['max_size']:
+ self.msg_len = tparams['max_size']
+ else:
+ self.msg_len = 0
+ self.auto_deq = tparams['auto_deq']
+ if tparams['xid_min_size'] == tparams['xid_max_size']:
+ self.xid_len = tparams['xid_max_size']
+ else:
+ self.xid_len = 0
+ self.transient = tparams['transient']
+ self.extern = tparams['extern']
+
+ def get_test(self, filename, tnum):
+ try:
+ f=open(filename, 'r')
+ except IOError:
+ print 'ERROR: Unable to open CSV file "%s"' % filename
+ sys.exit(1)
+ for l in f:
+ sl = l.strip().split(',')
+ if len(sl[0]) > 0 and sl[0][0] != '"':
+ try:
+ if (int(sl[TEST_NUM_COL]) == tnum):
+ return { 'num_msgs':int(sl[NUM_MSGS_COL]),
+ 'min_size':int(sl[MIN_MSG_SIZE_COL]),
+ 'max_size':int(sl[MAX_MSG_SIZE_COL]),
+ 'auto_deq':not (sl[AUTO_DEQ_COL] == 'FALSE' or sl[AUTO_DEQ_COL] == '0'),
+ 'xid_min_size':int(sl[MIN_XID_SIZE_COL]),
+ 'xid_max_size':int(sl[MAX_XID_SIZE_COL]),
+ 'transient':not (sl[TRANSIENT_COL] == 'FALSE' or sl[TRANSIENT_COL] == '0'),
+ 'extern':not (sl[EXTERN_COL] == 'FALSE' or sl[EXTERN_COL] == '0'),
+ 'comment':sl[COMMENT_COL] }
+ except Exception:
+ pass
+ return None
+
+ def proc_args(self, argv):
+ try:
+ opts, args = getopt.getopt(sys.argv[1:], "ab:c:d:hqt:", ["analyse", "base-filename=", "csv-filename=", "dir=", "help", "quiet", "test-num="])
+ except getopt.GetoptError:
+ self.usage()
+ sys.exit(2)
+ for o, a in opts:
+ if o in ("-h", "--help"):
+ self.usage()
+ sys.exit()
+ if o in ("-a", "--analyze"):
+ self.aflag = True
+ if o in ("-b", "--base-filename"):
+ self.bfn = a
+ if o in ("-c", "--csv-filename"):
+ self.csvfn = a
+ if o in ("-d", "--dir"):
+ self.jdir = a
+ if o in ("-q", "--quiet"):
+ self.qflag = True
+ if o in ("-t", "--test-num"):
+ if not a.isdigit():
+ print 'ERROR: Illegal test-num argument. Must be a non-negative number'
+ sys.exit(2)
+ self.tnum = int(a)
+ if self.bfn == None or self.jdir == None:
+ print 'ERROR: Missing requred args.'
+ self.usage()
+ sys.exit(2)
+ if self.tnum != None and self.csvfn == None:
+ print 'ERROR: Test number specified, but not CSV file'
+ self.usage()
+ sys.exit(2)
+
+ def usage(self):
+ print 'Usage: %s opts' % sys.argv[0]
+ print ' where opts are in either short or long format (*=req\'d):'
+ print ' -a --analyze Analyze enqueue/dequeue records'
+ print ' -b --base-filename [string] * Base filename for journal files'
+ print ' -c --csv-filename [string] CSV filename containing test parameters'
+ print ' -d --dir [string] * Journal directory containing journal files'
+ print ' -h --help Print help'
+ print ' -q --quiet Quiet (reduced output)'
+ print ' -t --test-num [int] Test number from CSV file - only valid if CSV file named'
+
+ def report(self):
+ if not self.qflag:
+ print
+ print ' === REPORT ===='
+ if self.num_msgs > 0 and self.msg_cnt != self.num_msgs:
+ print 'WARNING: Found %d messages; %d expected.' % (self.msg_cnt, self.num_msgs)
+ if len(self.emap) > 0:
+ print
+ print 'Remaining enqueued records (sorted by rid): '
+ keys = sorted(self.emap.keys())
+ for k in keys:
+ if self.emap[k][2] == True: # locked
+ locked = ' (locked)'
+ else:
+ locked = ''
+ print " fid=%d %s%s" % (self.emap[k][0], self.emap[k][1], locked)
+ print 'WARNING: Enqueue-Dequeue mismatch, %d enqueued records remain.' % len(self.emap)
+ if len(self.tmap) > 0:
+ txn_rec_cnt = 0
+ print
+ print 'Remaining transactions: '
+ for t in self.tmap:
+ print_xid(len(t), t)
+ for r in self.tmap[t]:
+ print " fid=%d %s" % (r[0], r[1])
+ print " Total: %d records for xid %s" % (len(self.tmap[t]), t)
+ txn_rec_cnt += len(self.tmap[t])
+ print 'WARNING: Incomplete transactions, %d xids remain containing %d records.' % (len(self.tmap), txn_rec_cnt)
+ print '%d enqueues, %d journal records processed.' % (self.msg_cnt, self.rec_cnt)
+
+
+#===============================================================================
+
+CLASSES = {
+ "a": TxnHdr,
+ "c": TxnHdr,
+ "d": DeqHdr,
+ "e": EnqRec,
+ "f": FileHdr
+}
+
+m = Main(sys.argv)
+m.run()
+m.report()
+
+sys.exit(None)
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_init_params.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_init_params.cpp
new file mode 100644
index 0000000000..1bc04110af
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_init_params.cpp
@@ -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.
+ *
+ */
+
+#include "jrnl_init_params.h"
+
+namespace mrg
+{
+namespace jtt
+{
+
+jrnl_init_params::jrnl_init_params(const std::string& jid, const std::string& jdir, const std::string& base_filename,
+ const u_int16_t num_jfiles, const bool ae, const u_int16_t ae_max_jfiles, const u_int32_t jfsize_sblks,
+ const u_int16_t wcache_num_pages, const u_int32_t wcache_pgsize_sblks):
+ _jid(jid),
+ _jdir(jdir),
+ _base_filename(base_filename),
+ _num_jfiles(num_jfiles),
+ _ae(ae),
+ _ae_max_jfiles(ae_max_jfiles),
+ _jfsize_sblks(jfsize_sblks),
+ _wcache_num_pages(wcache_num_pages),
+ _wcache_pgsize_sblks(wcache_pgsize_sblks)
+{}
+
+jrnl_init_params::jrnl_init_params(const jrnl_init_params& jp):
+ _jid(jp._jid),
+ _jdir(jp._jdir),
+ _base_filename(jp._base_filename),
+ _num_jfiles(jp._num_jfiles),
+ _ae(jp._ae),
+ _ae_max_jfiles(jp._ae_max_jfiles),
+ _jfsize_sblks(jp._jfsize_sblks),
+ _wcache_num_pages(jp._wcache_num_pages),
+ _wcache_pgsize_sblks(jp._wcache_pgsize_sblks)
+{}
+
+jrnl_init_params::jrnl_init_params(const jrnl_init_params* const jp_ptr):
+ _jid(jp_ptr->_jid),
+ _jdir(jp_ptr->_jdir),
+ _base_filename(jp_ptr->_base_filename),
+ _num_jfiles(jp_ptr->_num_jfiles),
+ _ae(jp_ptr->_ae),
+ _ae_max_jfiles(jp_ptr->_ae_max_jfiles),
+ _jfsize_sblks(jp_ptr->_jfsize_sblks),
+ _wcache_num_pages(jp_ptr->_wcache_num_pages),
+ _wcache_pgsize_sblks(jp_ptr->_wcache_pgsize_sblks)
+{}
+
+// static initializers
+
+const u_int16_t jrnl_init_params::def_num_jfiles = 8;
+const bool jrnl_init_params::def_ae = false;
+const u_int16_t jrnl_init_params::def_ae_max_jfiles = 0;
+const u_int32_t jrnl_init_params::def_jfsize_sblks = 0xc00;
+const u_int16_t jrnl_init_params::def_wcache_num_pages = 32;
+const u_int32_t jrnl_init_params::def_wcache_pgsize_sblks = 64;
+
+} // namespace jtt
+} // namespace mrg
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_init_params.h b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_init_params.h
new file mode 100644
index 0000000000..ece87f8e03
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_init_params.h
@@ -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.
+ *
+ */
+
+#ifndef mrg_jtt_jrnl_init_params_hpp
+#define mrg_jtt_jrnl_init_params_hpp
+
+#include <boost/shared_ptr.hpp>
+#include <string>
+#include <sys/types.h>
+
+namespace mrg
+{
+namespace jtt
+{
+
+ class jrnl_init_params
+ {
+ public:
+ static const u_int16_t def_num_jfiles;
+ static const bool def_ae;
+ static const u_int16_t def_ae_max_jfiles;
+ static const u_int32_t def_jfsize_sblks;
+ static const u_int16_t def_wcache_num_pages;
+ static const u_int32_t def_wcache_pgsize_sblks;
+
+ typedef boost::shared_ptr<jrnl_init_params> shared_ptr;
+
+ private:
+ std::string _jid;
+ std::string _jdir;
+ std::string _base_filename;
+ u_int16_t _num_jfiles;
+ bool _ae;
+ u_int16_t _ae_max_jfiles;
+ u_int32_t _jfsize_sblks;
+ u_int16_t _wcache_num_pages;
+ u_int32_t _wcache_pgsize_sblks;
+
+ public:
+ jrnl_init_params(const std::string& jid, const std::string& jdir, const std::string& base_filename,
+ const u_int16_t num_jfiles = def_num_jfiles, const bool ae = def_ae,
+ const u_int16_t ae_max_jfiles = def_ae_max_jfiles, const u_int32_t jfsize_sblks = def_jfsize_sblks,
+ const u_int16_t wcache_num_pages = def_wcache_num_pages,
+ const u_int32_t wcache_pgsize_sblks = def_wcache_pgsize_sblks);
+ jrnl_init_params(const jrnl_init_params& jp);
+ jrnl_init_params(const jrnl_init_params* const jp_ptr);
+
+ inline const std::string& jid() const { return _jid; }
+ inline const std::string& jdir() const { return _jdir; }
+ inline const std::string& base_filename() const { return _base_filename; }
+ inline u_int16_t num_jfiles() const { return _num_jfiles; }
+ inline bool is_ae() const { return _ae; }
+ inline u_int16_t ae_max_jfiles() const { return _ae_max_jfiles; }
+ inline u_int32_t jfsize_sblks() const { return _jfsize_sblks; }
+ inline u_int16_t wcache_num_pages() const { return _wcache_num_pages; }
+ inline u_int32_t wcache_pgsize_sblks() const { return _wcache_pgsize_sblks; }
+ };
+
+} // namespace jtt
+} // namespace mrg
+
+#endif // ifndef mrg_jtt_jrnl_init_params_hpp
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_instance.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_instance.cpp
new file mode 100644
index 0000000000..339dc1b52c
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_instance.cpp
@@ -0,0 +1,439 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "jrnl_instance.h"
+
+#include <cstdlib>
+#include "data_src.h"
+#include "qpid/legacystore/jrnl/data_tok.h"
+#include "qpid/legacystore/jrnl/jerrno.h"
+#include "test_case_result.h"
+
+#define MAX_WR_WAIT 10 // in ms
+#define MAX_RD_WAIT 100 // in ms
+#define MAX_ENQCAPTHRESH_CNT 1000 // 10s if MAX_WR_WAIT is 10 ms
+
+namespace mrg
+{
+namespace jtt
+{
+
+jrnl_instance::jrnl_instance(const std::string& jid, const std::string& jdir, const std::string& base_filename,
+ const u_int16_t num_jfiles, const bool ae, const u_int16_t ae_max_jfiles, const u_int32_t jfsize_sblks,
+ const u_int16_t wcache_num_pages, const u_int32_t wcache_pgsize_sblks):
+ mrg::journal::jcntl(jid, jdir, base_filename),
+ _jpp(new jrnl_init_params(jid, jdir, base_filename, num_jfiles, ae, ae_max_jfiles, jfsize_sblks,
+ wcache_num_pages, wcache_pgsize_sblks)),
+ _args_ptr(0),
+ _dtok_master_enq_list(),
+ _dtok_master_txn_list(),
+ _dtok_rd_list(),
+ _dtok_deq_list(),
+ _rd_aio_cv(_rd_aio_mutex),
+ _wr_full_cv(_wr_full_mutex),
+ _rd_list_cv(_rd_list_mutex),
+ _deq_list_cv(_deq_list_mutex),
+ _tcp(),
+ _tcrp()
+{}
+
+jrnl_instance::jrnl_instance(const jrnl_init_params::shared_ptr& p):
+ mrg::journal::jcntl(p->jid(), p->jdir(), p->base_filename()),
+ _jpp(p),
+ _args_ptr(0),
+ _dtok_master_enq_list(),
+ _dtok_master_txn_list(),
+ _dtok_rd_list(),
+ _dtok_deq_list(),
+ _rd_aio_cv(_rd_aio_mutex),
+ _wr_full_cv(_wr_full_mutex),
+ _rd_list_cv(_rd_list_mutex),
+ _deq_list_cv(_deq_list_mutex),
+ _tcp(),
+ _tcrp()
+{}
+
+jrnl_instance::~jrnl_instance() {}
+
+
+void
+jrnl_instance::init_tc(test_case::shared_ptr& tcp, const args* const args_ptr) throw ()
+{
+ test_case_result::shared_ptr p(new test_case_result(_jpp->jid()));
+ _tcrp = p;
+ _args_ptr = args_ptr;
+ try
+ {
+ _tcp = tcp;
+ _dtok_master_enq_list.clear();
+ _dtok_master_txn_list.clear();
+ _dtok_rd_list.clear();
+ _dtok_deq_list.clear();
+
+ if (_args_ptr->recover_mode)
+ {
+ try
+ {
+ u_int64_t highest_rid;
+ recover(_jpp->num_jfiles(), _jpp->is_ae(), _jpp->ae_max_jfiles(), _jpp->jfsize_sblks(),
+ _jpp->wcache_num_pages(), _jpp->wcache_pgsize_sblks(), this,
+ 0, highest_rid);
+ recover_complete();
+ }
+ catch (const mrg::journal::jexception& e)
+ {
+ if (e.err_code() == mrg::journal::jerrno::JERR_JDIR_STAT)
+ initialize(_jpp->num_jfiles(), _jpp->is_ae(), _jpp->ae_max_jfiles(), _jpp->jfsize_sblks(),
+ _jpp->wcache_num_pages(), _jpp->wcache_pgsize_sblks(), this);
+ else
+ throw;
+ }
+ }
+ else
+ initialize(_jpp->num_jfiles(), _jpp->is_ae(), _jpp->ae_max_jfiles(), _jpp->jfsize_sblks(),
+ _jpp->wcache_num_pages(), _jpp->wcache_pgsize_sblks(), this);
+ }
+ catch (const mrg::journal::jexception& e) { _tcrp->add_exception(e); }
+ catch (const std::exception& e) { _tcrp->add_exception(e.what()); }
+ catch (...) { _tcrp->add_exception("Unknown exception"); }
+}
+
+void
+jrnl_instance::run_tc() throw ()
+{
+ _tcrp->set_start_time();
+ ::pthread_create(&_enq_thread, 0, run_enq, this);
+ ::pthread_create(&_read_thread, 0, run_read, this);
+ ::pthread_create(&_deq_thread, 0, run_deq, this);
+}
+
+void
+jrnl_instance::tc_wait_compl() throw ()
+{
+ try
+ {
+ ::pthread_join(_deq_thread, 0);
+ ::pthread_join(_read_thread, 0);
+ ::pthread_join(_enq_thread, 0);
+ stop(true);
+ }
+ catch (const mrg::journal::jexception& e) { _tcrp->add_exception(e); panic(); }
+ catch (const std::exception& e) { _tcrp->add_exception(e.what()); panic(); }
+ catch (...) { _tcrp->add_exception("Unknown exception"); panic(); }
+ _lpmgr.finalize();
+ _tcrp->set_stop_time();
+ _tcp->add_result(_tcrp);
+}
+
+void
+jrnl_instance::run_enq() throw ()
+{
+ try
+ {
+ unsigned sleep_cnt = 0U;
+ while(_tcrp->num_enq() < _tcp->num_msgs() && !_tcrp->exception())
+ {
+ dtok_ptr p(new mrg::journal::data_tok);
+ _dtok_master_enq_list.push_back(p);
+ const char* msgp = data_src::get_data(_tcrp->num_enq() % 10);
+ const std::size_t msg_size = _tcp->this_data_size();
+ const std::size_t xid_size = _tcp->this_xid_size();
+ const std::string xid(data_src::get_xid(xid_size));
+ const bool external = _tcp->this_external();
+ const bool transient = _tcp->this_transience();
+ mrg::journal::iores res;
+ if (xid_size)
+ {
+ if (external)
+ res = enqueue_extern_txn_data_record(msg_size, p.get(), xid, transient);
+ else
+ res = enqueue_txn_data_record(msgp, msg_size, msg_size, p.get(), xid,
+ transient);
+ }
+ else
+ {
+ if (external)
+ res = enqueue_extern_data_record(msg_size, p.get(), transient);
+ else
+ res = enqueue_data_record(msgp, msg_size, msg_size, p.get(), transient);
+ }
+ switch (res)
+ {
+ case mrg::journal::RHM_IORES_SUCCESS:
+ sleep_cnt = 0U;
+ _tcrp->incr_num_enq();
+ if (p->has_xid() && !_tcp->auto_deq())
+ commit(p.get());
+ break;
+ case mrg::journal::RHM_IORES_ENQCAPTHRESH:
+ if (++sleep_cnt > MAX_ENQCAPTHRESH_CNT)
+ {
+ _tcrp->add_exception("Timeout waiting for RHM_IORES_ENQCAPTHRESH to clear.");
+ panic();
+ }
+ else if (get_wr_events(0) == 0) // *** GEV2
+ {
+ mrg::journal::slock sl(_wr_full_mutex);
+ _wr_full_cv.waitintvl(MAX_WR_WAIT * 1000000); // MAX_WR_WAIT in ms
+ }
+ break;
+ default:
+ std::ostringstream oss;
+ oss << "ERROR: enqueue operation in journal \"" << _jid << "\" returned ";
+ oss << mrg::journal::iores_str(res) << ".";
+ _tcrp->add_exception(oss.str());
+ }
+ }
+ flush(true);
+ }
+ catch (const mrg::journal::jexception& e) { _tcrp->add_exception(e); panic(); }
+ catch (const std::exception& e) { _tcrp->add_exception(e.what()); panic(); }
+ catch (...) { _tcrp->add_exception("Unknown exception"); panic(); }
+}
+
+void
+jrnl_instance::run_read() throw ()
+{
+ try
+ {
+ read_arg::read_mode_t rd_mode = _args_ptr->read_mode.val();
+ if (rd_mode != read_arg::NONE)
+ {
+ while (_tcrp->num_rproc() < _tcp->num_msgs() && !_tcrp->exception())
+ {
+ journal::data_tok* dtokp = 0;
+ {
+ mrg::journal::slock sl(_rd_list_mutex);
+ if (_dtok_rd_list.empty())
+ _rd_list_cv.wait();
+ if (!_dtok_rd_list.empty())
+ {
+ dtokp = _dtok_rd_list.front();
+ _dtok_rd_list.pop_front();
+ }
+ }
+ if (dtokp)
+ {
+ _tcrp->incr_num_rproc();
+
+ bool do_read = true;
+ if (rd_mode == read_arg::RANDOM)
+ do_read = 1.0 * std::rand() / RAND_MAX < _args_ptr->read_prob / 100.0;
+ else if (rd_mode == read_arg::LAZYLOAD)
+ do_read = _tcrp->num_rproc() >= _args_ptr->lld_skip_num &&
+ _tcrp->num_read() < _args_ptr->lld_rd_num;
+ bool read_compl = false;
+ while (do_read && !read_compl && !_tcrp->exception())
+ {
+ void* dptr = 0;
+ std::size_t dsize = 0;
+ void* xptr = 0;
+ std::size_t xsize = 0;
+ bool tr = false;
+ bool ext = false;
+ mrg::journal::iores res = read_data_record(&dptr, dsize, &xptr, xsize, tr,
+ ext, dtokp);
+ switch (res)
+ {
+ case mrg::journal::RHM_IORES_SUCCESS:
+ {
+ mrg::journal::slock sl(_deq_list_mutex);
+ _dtok_deq_list.push_back(dtokp);
+ _deq_list_cv.broadcast();
+ }
+ read_compl = true;
+ _tcrp->incr_num_read();
+
+ // clean up
+ if (xsize)
+ std::free(xptr);
+ else if (dsize)
+ std::free(dptr);
+ dptr = 0;
+ xptr = 0;
+ break;
+ case mrg::journal::RHM_IORES_PAGE_AIOWAIT:
+ if (get_rd_events(0) == 0)
+ {
+ mrg::journal::slock sl(_rd_aio_mutex);
+ _rd_aio_cv.waitintvl(MAX_RD_WAIT * 1000000); // MAX_RD_WAIT in ms
+ }
+ break;
+ default:
+ std::ostringstream oss;
+ oss << "ERROR: read operation in journal \"" << _jid;
+ oss << "\" returned " << mrg::journal::iores_str(res) << ".";
+ _tcrp->add_exception(oss.str());
+ {
+ mrg::journal::slock sl(_deq_list_mutex);
+ _deq_list_cv.broadcast(); // wake up deq thread
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (const mrg::journal::jexception& e) { _tcrp->add_exception(e); panic(); }
+ catch (const std::exception& e) { _tcrp->add_exception(e.what()); panic(); }
+ catch (...) { _tcrp->add_exception("Unknown exception"); panic(); }
+}
+
+void
+jrnl_instance::run_deq() throw ()
+{
+ try
+ {
+ if (_tcp->auto_deq())
+ {
+ while(_tcrp->num_deq() < _tcp->num_msgs() && !_tcrp->exception())
+ {
+ journal::data_tok* dtokp = 0;
+ {
+ mrg::journal::slock sl(_deq_list_mutex);
+ if (_dtok_deq_list.empty())
+ _deq_list_cv.wait();
+ if (!_dtok_deq_list.empty())
+ {
+ dtokp = _dtok_deq_list.front();
+ _dtok_deq_list.pop_front();
+ }
+ }
+ if (dtokp)
+ {
+ mrg::journal::iores res;
+ if (dtokp->has_xid())
+ res = dequeue_txn_data_record(dtokp, dtokp->xid());
+ else
+ res = dequeue_data_record(dtokp);
+ if (res == mrg::journal::RHM_IORES_SUCCESS)
+ {
+ _tcrp->incr_num_deq();
+ commit(dtokp);
+ }
+ else
+ {
+ std::ostringstream oss;
+ oss << "ERROR: dequeue operation in journal \"" << _jid;
+ oss << "\" returned " << mrg::journal::iores_str(res) << ".";
+ _tcrp->add_exception(oss.str());
+ }
+ }
+ }
+ flush(true);
+ }
+ }
+ catch (const mrg::journal::jexception& e) { _tcrp->add_exception(e); panic(); }
+ catch (const std::exception& e) { _tcrp->add_exception(e.what()); panic(); }
+ catch (...) { _tcrp->add_exception("Unknown exception"); panic(); }
+}
+
+void
+jrnl_instance::abort(const mrg::journal::data_tok* dtokp)
+{
+ txn(dtokp, false);
+}
+
+void
+jrnl_instance::commit(const mrg::journal::data_tok* dtokp)
+{
+ txn(dtokp, true);
+}
+
+void
+jrnl_instance::txn(const mrg::journal::data_tok* dtokp, const bool commit)
+{
+ if (dtokp->has_xid())
+ {
+ mrg::journal::data_tok* p = prep_txn_dtok(dtokp);
+ mrg::journal::iores res = commit ? txn_commit(p, p->xid()) : txn_abort(p, p->xid());
+ if (res != mrg::journal::RHM_IORES_SUCCESS)
+ {
+ std::ostringstream oss;
+ oss << "ERROR: " << (commit ? "commit" : "abort") << " operation in journal \"";
+ oss << _jid << "\" returned " << mrg::journal::iores_str(res) << ".";
+ _tcrp->add_exception(oss.str());
+ }
+ }
+}
+
+mrg::journal::data_tok*
+jrnl_instance::prep_txn_dtok(const mrg::journal::data_tok* dtokp)
+{
+ dtok_ptr p(new mrg::journal::data_tok);
+ _dtok_master_txn_list.push_back(p);
+ p->set_xid(dtokp->xid());
+ return p.get();
+}
+
+void
+jrnl_instance::panic()
+{
+ // In the event of a panic or exception condition, release all waiting CVs
+ _rd_aio_cv.broadcast();
+ _wr_full_cv.broadcast();
+ _rd_list_cv.broadcast();
+ _deq_list_cv.broadcast();
+}
+
+// AIO callbacks
+
+void
+jrnl_instance::wr_aio_cb(std::vector<journal::data_tok*>& dtokl)
+{
+ for (std::vector<journal::data_tok*>::const_iterator i=dtokl.begin(); i!=dtokl.end(); i++)
+ {
+ if ((*i)->wstate() == journal::data_tok::ENQ || (*i)->wstate() == journal::data_tok::DEQ)
+ {
+ journal::data_tok* dtokp = *i;
+ if (dtokp->wstate() == journal::data_tok::ENQ)
+ {
+ if (_args_ptr->read_mode.val() == read_arg::NONE)
+ {
+ mrg::journal::slock sl(_deq_list_mutex);
+ _dtok_deq_list.push_back(dtokp);
+ _deq_list_cv.broadcast();
+ }
+ else
+ {
+ mrg::journal::slock sl(_rd_list_mutex);
+ _dtok_rd_list.push_back(dtokp);
+ _rd_list_cv.broadcast();
+ }
+ }
+ else // DEQ
+ {
+ mrg::journal::slock sl(_wr_full_mutex);
+ _wr_full_cv.broadcast();
+ }
+ }
+ }
+}
+
+void
+jrnl_instance::rd_aio_cb(std::vector<u_int16_t>& /*pil*/)
+{
+ mrg::journal::slock sl(_rd_aio_mutex);
+ _rd_aio_cv.broadcast();
+}
+
+} // namespace jtt
+} // namespace mrg
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_instance.h b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_instance.h
new file mode 100644
index 0000000000..5003f39b24
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jrnl_instance.h
@@ -0,0 +1,121 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#ifndef mrg_jtt_jrnl_instance_hpp
+#define mrg_jtt_jrnl_instance_hpp
+
+#include "args.h"
+#include "jrnl_init_params.h"
+#include "test_case.h"
+
+#include <boost/shared_ptr.hpp>
+#include "qpid/legacystore/jrnl/cvar.h"
+#include "qpid/legacystore/jrnl/data_tok.h"
+#include "qpid/legacystore/jrnl/jcntl.h"
+#include "qpid/legacystore/jrnl/slock.h"
+#include "qpid/legacystore/jrnl/smutex.h"
+#include <list>
+#include <vector>
+
+namespace mrg
+{
+namespace jtt
+{
+
+ class jrnl_instance : public mrg::journal::jcntl, public virtual mrg::journal::aio_callback
+ {
+ public:
+ typedef boost::shared_ptr<jrnl_instance> shared_ptr;
+ typedef boost::shared_ptr<journal::data_tok> dtok_ptr;
+
+ private:
+ jrnl_init_params::shared_ptr _jpp;
+ const args* _args_ptr;
+ std::vector<dtok_ptr> _dtok_master_enq_list;
+ std::vector<dtok_ptr> _dtok_master_txn_list;
+ std::list<journal::data_tok*> _dtok_rd_list;
+ std::list<journal::data_tok*> _dtok_deq_list;
+ mrg::journal::smutex _rd_aio_mutex; ///< Mutex for read aio wait conditions
+ mrg::journal::cvar _rd_aio_cv; ///< Condition var for read aio wait conditions
+ mrg::journal::smutex _wr_full_mutex; ///< Mutex for write full conditions
+ mrg::journal::cvar _wr_full_cv; ///< Condition var for write full conditions
+ mrg::journal::smutex _rd_list_mutex; ///< Mutex for _dtok_rd_list
+ mrg::journal::cvar _rd_list_cv; ///< Condition var for _dtok_rd_list
+ mrg::journal::smutex _deq_list_mutex; ///< Mutex for _dtok_deq_list
+ mrg::journal::cvar _deq_list_cv; ///< Condition var for _dtok_deq_list
+ pthread_t _enq_thread;
+ pthread_t _deq_thread;
+ pthread_t _read_thread;
+ test_case::shared_ptr _tcp;
+ test_case_result::shared_ptr _tcrp;
+
+ public:
+ jrnl_instance(const std::string& jid, const std::string& jdir,
+ const std::string& base_filename,
+ const u_int16_t num_jfiles = jrnl_init_params::def_num_jfiles,
+ const bool ae = jrnl_init_params::def_ae,
+ const u_int16_t ae_max_jfiles = jrnl_init_params::def_ae_max_jfiles,
+ const u_int32_t jfsize_sblks = jrnl_init_params::def_jfsize_sblks,
+ const u_int16_t wcache_num_pages = jrnl_init_params::def_wcache_num_pages,
+ const u_int32_t wcache_pgsize_sblks = jrnl_init_params::def_wcache_pgsize_sblks);
+ jrnl_instance(const jrnl_init_params::shared_ptr& params);
+ virtual ~jrnl_instance();
+
+ inline const jrnl_init_params::shared_ptr& params() const { return _jpp; }
+ inline const std::string& jid() const { return _jpp->jid(); }
+
+ void init_tc(test_case::shared_ptr& tcp, const args* const args_ptr) throw ();
+ void run_tc() throw ();
+ void tc_wait_compl() throw ();
+
+ // AIO callbacks
+ virtual void wr_aio_cb(std::vector<journal::data_tok*>& dtokl);
+ virtual void rd_aio_cb(std::vector<u_int16_t>& pil);
+
+ private:
+ void run_enq() throw ();
+ inline static void* run_enq(void* p)
+ { static_cast<jrnl_instance*>(p)->run_enq(); return 0; }
+
+ void run_read() throw ();
+ inline static void* run_read(void* p)
+ { static_cast<jrnl_instance*>(p)->run_read(); return 0; }
+
+ void run_deq() throw ();
+ inline static void* run_deq(void* p)
+ { static_cast<jrnl_instance*>(p)->run_deq(); return 0; }
+
+ void abort(const mrg::journal::data_tok* dtokp);
+ void commit(const mrg::journal::data_tok* dtokp);
+ void txn(const mrg::journal::data_tok* dtokp, const bool commit);
+ mrg::journal::data_tok* prep_txn_dtok(const mrg::journal::data_tok* dtokp);
+
+ void panic();
+
+// // static callbacks
+// static void aio_rd_callback(jcntl* journal, std::vector<u_int16_t>& pil);
+// static void aio_wr_callback(jcntl* journal, std::vector<journal::data_tok*>& dtokl);
+ };
+
+} // namespace jtt
+} // namespace mrg
+
+#endif // ifndef mrg_jtt_jrnl_instance_hpp
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/jtt.csv b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jtt.csv
new file mode 100644
index 0000000000..df523e3f97
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/jtt.csv
@@ -0,0 +1,234 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+,,,,,,,"Msg size",,"Xid size",,,,,"enq-size",,"deq-size",,"txn-size",,
+"Test #","tf","pf","amn","mn incr","#msgs","ms incr","Min","Max","Min","Max","auto-deq","transient","extern","bytes","dblks","bytes","dblks","bytes","dblks","comment"
+,,,,,,,,,,,,,,,,,,,,
+"Initialize only",,,,,,,,,,,,,,,,,,,,
+0,"L",0,0,0,0,0,0,0,0,0,FALSE,FALSE,FALSE,44,1,0,0,0,0,"No messages - journal creation/initialization only"
+,,,,,,,,,,,,,,,,,,,,
+"Simple message combinations of persistent/deq transientueued/non-dequeued, transactional/non-transactional",,,,,,,,,,,,,,,,,,,,
+1,"L",1,1,0,1,0,10,10,0,0,FALSE,FALSE,FALSE,54,1,0,0,0,0,"1 * 10-byte message"
+2,"L",1,10,0,10,0,10,10,0,0,FALSE,FALSE,FALSE,54,1,0,0,0,0,"10 * 10-byte message"
+3,"L",1,1,0,1,0,10,10,0,0,FALSE,TRUE,FALSE,54,1,0,0,0,0,"1 * 10-byte message [transient]"
+4,"L",1,10,0,10,0,10,10,0,0,FALSE,TRUE,FALSE,54,1,0,0,0,0,"10 * 10-byte message [transient]"
+5,"L",1,1,0,1,0,10,10,10,10,FALSE,FALSE,FALSE,64,1,0,0,0,0,"1 * 10-byte message [txn]"
+6,"L",1,10,0,10,0,10,10,10,10,FALSE,FALSE,FALSE,64,1,0,0,0,0,"10 * 10-byte message [txn]"
+7,"L",1,1,0,1,0,10,10,10,10,FALSE,TRUE,FALSE,64,1,0,0,0,0,"1 * 10-byte message [txn transient]"
+8,"L",1,10,0,10,0,10,10,10,10,FALSE,TRUE,FALSE,64,1,0,0,0,0,"10 * 10-byte message [txn transient]"
+9,"L",1,1,0,1,0,10,10,0,0,TRUE,FALSE,FALSE,54,1,32,1,0,0,"1 * 10-byte message [deq]"
+10,"L",1,10,0,10,0,10,10,0,0,TRUE,FALSE,FALSE,54,1,32,1,0,0,"10 * 10-byte message [deq]"
+11,"L",1,1,0,1,0,10,10,0,0,TRUE,TRUE,FALSE,54,1,32,1,0,0,"1 * 10-byte message [deq transient]"
+12,"L",1,10,0,10,0,10,10,0,0,TRUE,TRUE,FALSE,54,1,32,1,0,0,"10 * 10-byte message [deq transient]"
+13,"L",1,1,0,1,0,10,10,10,10,TRUE,FALSE,FALSE,64,1,54,1,46,1,"1 * 10-byte message [deq txn]"
+14,"L",1,10,0,10,0,10,10,10,10,TRUE,FALSE,FALSE,64,1,54,1,46,1,"10 * 10-byte message [deq txn]"
+15,"L",1,1,0,1,0,10,10,10,10,TRUE,TRUE,FALSE,64,1,54,1,46,1,"1 * 10-byte message [txn deq transient]"
+16,"L",1,10,0,10,0,10,10,10,10,TRUE,TRUE,FALSE,64,1,54,1,46,1,"10 * 10-byte message [txn deq transient]"
+17,"L",1,1,0,1,0,10,10,0,0,FALSE,FALSE,TRUE,54,1,0,0,0,0,"1 * 10-byte message [extern]"
+18,"L",1,10,0,10,0,10,10,0,0,FALSE,FALSE,TRUE,54,1,0,0,0,0,"10 * 10-byte message [extern]"
+19,"L",1,1,0,1,0,10,10,0,0,FALSE,TRUE,TRUE,54,1,0,0,0,0,"1 * 10-byte message [transient extern]"
+20,"L",1,10,0,10,0,10,10,0,0,FALSE,TRUE,TRUE,54,1,0,0,0,0,"10 * 10-byte message [transient extern]"
+21,"L",1,1,0,1,0,10,10,10,10,FALSE,FALSE,TRUE,64,1,0,0,0,0,"1 * 10-byte message [txn extern]"
+22,"L",1,10,0,10,0,10,10,10,10,FALSE,FALSE,TRUE,64,1,0,0,0,0,"10 * 10-byte message [txn extern]"
+23,"L",1,1,0,1,0,10,10,10,10,FALSE,TRUE,TRUE,64,1,0,0,0,0,"1 * 10-byte message [txn transient extern]"
+24,"L",1,10,0,10,0,10,10,10,10,FALSE,TRUE,TRUE,64,1,0,0,0,0,"10 * 10-byte message [txn transient extern]"
+25,"L",1,1,0,1,0,10,10,0,0,TRUE,FALSE,TRUE,54,1,32,1,0,0,"1 * 10-byte message [deq extern]"
+26,"L",1,10,0,10,0,10,10,0,0,TRUE,FALSE,TRUE,54,1,32,1,0,0,"10 * 10-byte message [deq extern]"
+27,"L",1,1,0,1,0,10,10,0,0,TRUE,TRUE,TRUE,54,1,32,1,0,0,"1 * 10-byte message [deq transient extern]"
+28,"L",1,10,0,10,0,10,10,0,0,TRUE,TRUE,TRUE,54,1,32,1,0,0,"10 * 10-byte message [deq transient extern]"
+29,"L",1,1,0,1,0,10,10,10,10,TRUE,FALSE,TRUE,64,1,54,1,46,1,"1 * 10-byte message [deq txn extern]"
+30,"L",1,10,0,10,0,10,10,10,10,TRUE,FALSE,TRUE,64,1,54,1,46,1,"10 * 10-byte message [deq txn extern]"
+31,"L",1,1,0,1,0,10,10,10,10,TRUE,TRUE,TRUE,64,1,54,1,46,1,"1 * 10-byte message [txn deq transient extern]"
+32,"L",1,10,0,10,0,10,10,10,10,TRUE,TRUE,TRUE,64,1,54,1,46,1,"10 * 10-byte message [txn deq transient extern]"
+,,,,,,,,,,,,,,,,,,,,
+"Transition from one d-block to two per message",,,,,,,,,,,,,,,,,,,,
+33,"L",1,10,0,10,0,84,84,0,0,FALSE,FALSE,FALSE,128,1,0,0,0,0,"1 dblk exact fit"
+34,"L",1,10,0,10,1,85,85,0,0,FALSE,FALSE,FALSE,129,2,0,0,0,0,"1 dblk + 1 byte"
+35,"L",1,10,0,10,0,58,58,26,26,FALSE,FALSE,FALSE,128,1,0,0,0,0,"1 dblk exact fit [txn]"
+36,"L",1,10,0,10,1,59,59,26,26,FALSE,FALSE,FALSE,129,2,0,0,0,0,"1 dblk + 1 byte [txn]"
+,,,,,,,,,,,,,,,,,,,,
+"Transition from one s-block to two per message",,,,,,,,,,,,,,,,,,,,
+37,"L",1,10,0,10,0,468,468,0,0,FALSE,FALSE,FALSE,512,4,0,0,0,0,"1 sblk exact fit"
+38,"L",1,10,0,10,1,469,469,0,0,FALSE,FALSE,FALSE,513,5,0,0,0,0,"1 sblk + 1 byte"
+39,"L",1,10,0,10,0,442,442,26,26,FALSE,FALSE,FALSE,512,4,0,0,0,0,"1 sblk exact fit [txn]"
+40,"L",1,10,0,10,1,443,443,26,26,FALSE,FALSE,FALSE,513,5,0,0,0,0,"1 sblk + 1 byte [txn]"
+,,,,,,,,,,,,,,,,,,,,
+"Transition from first page to second",,,,,,,,,,,,,,,,,,,,
+41,"L",1,8,0,8,0,4052,4052,0,0,FALSE,FALSE,FALSE,4096,32,0,0,0,0,"1/8 page"
+42,"L",1,8,1,9,0,4052,4052,0,0,FALSE,FALSE,FALSE,4096,32,0,0,0,0,"1/8 page"
+43,"L",1,8,0,8,1,4053,4053,0,0,FALSE,FALSE,FALSE,4097,33,0,0,0,0,"1/8 page + 1 byte"
+44,"L",1,8,0,8,0,3796,3796,256,256,FALSE,FALSE,FALSE,4096,32,0,0,0,0,"1/8 page [txn]"
+45,"L",1,8,1,9,0,3796,3796,256,256,FALSE,FALSE,FALSE,4096,32,0,0,0,0,"1/8 page [txn]"
+46,"L",1,8,0,8,1,3797,3797,256,256,FALSE,FALSE,FALSE,4097,33,0,0,0,0,"1/8 page + 1 byte [txn]"
+47,"L",1,8,0,8,0,3924,3924,0,0,TRUE,FALSE,FALSE,3968,31,32,1,0,0,"1/8 page incl deq [deq]"
+48,"L",1,8,1,9,0,3924,3924,0,0,TRUE,FALSE,FALSE,3968,31,32,1,0,0,"1/8 page incl deq [deq]"
+49,"L",1,8,0,8,1,3925,3925,0,0,TRUE,FALSE,FALSE,3969,32,32,1,0,0,"1/8 page incl deq + 1 byte [deq]"
+50,"L",1,8,0,8,0,3028,3028,256,256,TRUE,FALSE,FALSE,3328,26,300,3,292,3,"1/8 page incl deq & txn [deq txn]"
+51,"L",1,8,1,9,0,3028,3028,256,256,TRUE,FALSE,FALSE,3328,26,300,3,292,3,"1/8 page incl deq & txn [deq txn]"
+52,"L",1,8,0,8,1,3029,3029,256,256,TRUE,FALSE,FALSE,3329,27,300,3,292,3,"1/8 page incl deq & txn + 1 byte [deq txn]"
+,,,,,,,,,,,,,,,,,,,,
+"Page cache rollover (from page 32 back to page 0)",,,,,,,,,,,,,,,,,,,,
+53,"L",1,32,0,32,0,32724,32724,0,0,FALSE,FALSE,FALSE,32768,256,0,0,0,0,"1 page"
+54,"L",1,32,1,33,0,32724,32724,0,0,FALSE,FALSE,FALSE,32768,256,0,0,0,0,"1 page"
+55,"L",1,32,0,32,1,32725,32725,0,0,FALSE,FALSE,FALSE,32769,257,0,0,0,0,"1 page + 1 byte"
+56,"L",1.5,22,0,22,0,49108,49108,0,0,FALSE,FALSE,FALSE,49152,384,0,0,0,0,"1.5 pages"
+57,"L",1,32,0,32,0,32468,32468,256,256,FALSE,FALSE,FALSE,32768,256,0,0,0,0,"1 page [txn]"
+58,"L",1,32,1,33,0,32468,32468,256,256,FALSE,FALSE,FALSE,32768,256,0,0,0,0,"1 page [txn]"
+59,"L",1,32,0,32,1,32469,32469,256,256,FALSE,FALSE,FALSE,32769,257,0,0,0,0,"1 page + 1 byte [txn]"
+60,"L",1.5,22,0,22,0,48852,48852,256,256,FALSE,FALSE,FALSE,49152,384,0,0,0,0,"1.5 pages [txn]"
+61,"L",1,32,0,32,0,32596,32596,0,0,TRUE,FALSE,FALSE,32640,255,32,1,0,0,"1 page incl deq [deq]"
+62,"L",1,32,1,33,0,32596,32596,0,0,TRUE,FALSE,FALSE,32640,255,32,1,0,0,"1 page incl deq [deq]"
+63,"L",1,32,0,32,1,32597,32597,0,0,TRUE,FALSE,FALSE,32641,256,32,1,0,0,"1 page incl deq + 1 byte [deq]"
+64,"L",1.5,22,0,22,0,48980,48980,0,0,TRUE,FALSE,FALSE,49024,383,32,1,0,0,"1.5 pages incl deq [deq]"
+65,"L",1,32,0,32,0,31700,31700,256,256,TRUE,FALSE,FALSE,32000,250,300,3,292,3,"1 page incl deq & txn [deq txn]"
+66,"L",1,32,1,33,0,31700,31700,256,256,TRUE,FALSE,FALSE,32000,250,300,3,292,3,"1 page incl deq & txn [deq txn]"
+67,"L",1,32,0,32,1,31701,31701,256,256,TRUE,FALSE,FALSE,32001,251,300,3,292,3,"1 page incl deq & txn + 1 byte [deq txn]"
+68,"L",1.5,22,0,22,0,48084,48084,256,256,TRUE,FALSE,FALSE,48384,378,300,3,292,3,"1.5 pages incl deq & txn [deq txn]"
+,,,,,,,,,,,,,,,,,,,,
+"File transition (from file 0000 to 0001)",,,,,,,,,,,,,,,,,,,,
+69,"L",1,48,0,48,0,32724,32724,0,0,FALSE,FALSE,FALSE,32768,256,0,0,0,0,"1 page"
+70,"L",1,48,1,49,0,32724,32724,0,0,FALSE,FALSE,FALSE,32768,256,0,0,0,0,"1 page"
+71,"L",1,48,0,48,1,32725,32725,0,0,FALSE,FALSE,FALSE,32769,257,0,0,0,0,"1 page + 1 byte"
+72,"L",2.5,20,0,20,0,81876,81876,0,0,FALSE,FALSE,FALSE,81920,640,0,0,0,0,"2.5 pages"
+73,"L",1,48,0,48,0,32468,32468,256,256,FALSE,FALSE,FALSE,32768,256,0,0,0,0,"1 page [txn]"
+74,"L",1,48,1,49,0,32468,32468,256,256,FALSE,FALSE,FALSE,32768,256,0,0,0,0,"1 page [txn]"
+75,"L",1,48,0,48,1,32469,32469,256,256,FALSE,FALSE,FALSE,32769,257,0,0,0,0,"1 page + 1 byte [txn]"
+76,"L",2.5,20,0,20,0,81620,81620,256,256,FALSE,FALSE,FALSE,81920,640,0,0,0,0,"2.5 pages [txn]"
+77,"L",1,48,0,48,0,32596,32596,0,0,TRUE,FALSE,FALSE,32640,255,32,1,0,0,"1 page incl deq [deq]"
+78,"L",1,48,1,49,0,32596,32596,0,0,TRUE,FALSE,FALSE,32640,255,32,1,0,0,"1 page incl deq [deq]"
+79,"L",1,48,0,48,1,32597,32597,0,0,TRUE,FALSE,FALSE,32641,256,32,1,0,0,"1 page incl deq + 1 byte [deq]"
+80,"L",2.5,20,0,20,0,81748,81748,0,0,TRUE,FALSE,FALSE,81792,639,32,1,0,0,"2.5 pages incl deq [deq]"
+81,"L",1,48,0,48,0,31700,31700,256,256,TRUE,FALSE,FALSE,32000,250,300,3,292,3,"1 page incl deq & txn [deq txn]"
+82,"L",1,48,1,49,0,31700,31700,256,256,TRUE,FALSE,FALSE,32000,250,300,3,292,3,"1 page incl deq & txn [deq txn]"
+83,"L",1,48,0,48,1,31701,31701,256,256,TRUE,FALSE,FALSE,32001,251,300,3,292,3,"1 page incl deq & txn + 1 byte [deq txn]"
+84,"L",2.5,20,0,20,0,80852,80852,256,256,TRUE,FALSE,FALSE,81152,634,300,3,292,3,"2.5 pages incl deq & txn [deq txn]"
+,,,,,,,,,,,,,,,,,,,,
+"File rollover (from file 0007 to 0000) - RHM_WRONLY req'd for auto-dequeue == FALSE",,,,,,,,,,,,,,,,,,,,
+85,"L",0.5,16,0,16,0,786260,786260,0,0,TRUE,FALSE,FALSE,786304,6143,32,1,0,0,"24 pages incl deq = ½ file [deq]"
+86,"L",0.5,16,1,17,0,786260,786260,0,0,TRUE,FALSE,FALSE,786304,6143,32,1,0,0,"24 pages incl deq = ½ file [deq]"
+87,"L",0.5,16,0,16,1,786261,786261,0,0,TRUE,FALSE,FALSE,786305,6144,32,1,0,0,"24 pages incl deq + 1 byte [deq]"
+88,"L",0.5,16,0,16,0,785364,785364,256,256,TRUE,FALSE,FALSE,785664,6138,300,3,292,3,"24 pages incl deq & txn = ½ file [deq txn]"
+89,"L",0.5,16,1,17,0,785364,785364,256,256,TRUE,FALSE,FALSE,785664,6138,300,3,292,3,"24 pages incl deq & txn = ½ file [deq txn]"
+90,"L",0.5,16,0,16,1,785365,785365,256,256,TRUE,FALSE,FALSE,785665,6139,300,3,292,3,"24 pages incl deq & txn + 1 byte [deq txn]"
+91,"L",0.25,32,0,32,0,786260,786260,0,0,TRUE,FALSE,FALSE,786304,6143,32,1,0,0,"24 pages incl deq = ½ file [deq]"
+92,"L",0.25,32,1,33,0,786260,786260,0,0,TRUE,FALSE,FALSE,786304,6143,32,1,0,0,"24 pages incl deq = ½ file [deq]"
+93,"L",0.25,32,0,32,1,786261,786261,0,0,TRUE,FALSE,FALSE,786305,6144,32,1,0,0,"24 pages incl deq + 1 byte [deq]"
+94,"L",0.25,32,0,32,0,785364,785364,256,256,TRUE,FALSE,FALSE,785664,6138,300,3,292,3,"24 pages incl deq & txn = ½ file [deq txn]"
+95,"L",0.25,32,1,33,0,785364,785364,256,256,TRUE,FALSE,FALSE,785664,6138,300,3,292,3,"24 pages incl deq & txn = ½ file [deq txn]"
+96,"L",0.25,32,0,32,1,785365,785365,256,256,TRUE,FALSE,FALSE,785665,6139,300,3,292,3,"24 pages incl deq & txn + 1 byte [deq txn]"
+,,,,,,,,,,,,,,,,,,,,
+"Multi-page messages (large messages) - tests various paths in encoder.",,,,,,,,,,,,,,,,,,,,
+97,"L",1,16,0,16,0,32724,32724,0,0,FALSE,FALSE,FALSE,32768,256,0,0,0,0,"data 1 page"
+98,"L",1,16,0,16,1,32725,32725,0,0,FALSE,FALSE,FALSE,32769,257,0,0,0,0,"data 1 page + 1 byte (tail split; 1 byte over page boundary)"
+99,"L",1,16,0,16,11,32735,32735,0,0,FALSE,FALSE,FALSE,32779,257,0,0,0,0,"data 1 page + 11 bytes (tail split; 11 bytes over page boundary)"
+100,"L",1,16,0,16,12,32736,32736,0,0,FALSE,FALSE,FALSE,32780,257,0,0,0,0,"data 1 page + 12 bytes (tail separated exactly onto next page)"
+101,"L",1,16,0,16,13,32737,32737,0,0,FALSE,FALSE,FALSE,32781,257,0,0,0,0,"data 1 page + 13 bytes (data split; 1 byte over page boundary)"
+102,"L",1,16,0,16,0,32468,32468,256,256,FALSE,FALSE,FALSE,32768,256,0,0,0,0,"data 1 page [txn]"
+103,"L",1,16,0,16,1,32469,32469,256,256,FALSE,FALSE,FALSE,32769,257,0,0,0,0,"data 1 page + 1 byte (tail split; 1 byte over page boundary) [txn]"
+104,"L",1,16,0,16,11,32479,32479,256,256,FALSE,FALSE,FALSE,32779,257,0,0,0,0,"data 1 page + 11 bytes (tail split; 11 bytes over page boundary) [txn]"
+105,"L",1,16,0,16,12,32480,32480,256,256,FALSE,FALSE,FALSE,32780,257,0,0,0,0,"data 1 page + 12 bytes (tail separated exactly onto next page) [txn]"
+106,"L",1,16,0,16,13,32481,32481,256,256,FALSE,FALSE,FALSE,32781,257,0,0,0,0,"data 1 page + 13 bytes (data split; 1 byte over page boundary) [txn]"
+107,"L",2,16,0,16,0,65492,65492,0,0,FALSE,FALSE,FALSE,65536,512,0,0,0,0,"data 2 pages"
+108,"L",2,16,0,16,1,65493,65493,0,0,FALSE,FALSE,FALSE,65537,513,0,0,0,0,"data 2 pages + 1 byte (tail split; 1 byte over page boundary)"
+109,"L",2,16,0,16,11,65503,65503,0,0,FALSE,FALSE,FALSE,65547,513,0,0,0,0,"data 2 pages + 11 bytes (tail split; 11 bytes over page boundary)"
+110,"L",2,16,0,16,12,65504,65504,0,0,FALSE,FALSE,FALSE,65548,513,0,0,0,0,"data 2 pages + 12 bytes (tail separated exactly onto next page)"
+111,"L",2,16,0,16,13,65505,65505,0,0,FALSE,FALSE,FALSE,65549,513,0,0,0,0,"data 2 pages + 13 bytes (data split; 1 byte over page boundary)"
+112,"L",2,16,0,16,0,65236,65236,256,256,FALSE,FALSE,FALSE,65536,512,0,0,0,0,"data 2 pages [txn]"
+113,"L",2,16,0,16,1,65237,65237,256,256,FALSE,FALSE,FALSE,65537,513,0,0,0,0,"data 2 pages + 1 byte (tail split; 1 byte over page boundary) [txn]"
+114,"L",2,16,0,16,11,65247,65247,256,256,FALSE,FALSE,FALSE,65547,513,0,0,0,0,"data 2 pages + 11 bytes (tail split; 11 bytes over page boundary) [txn]"
+115,"L",2,16,0,16,12,65248,65248,256,256,FALSE,FALSE,FALSE,65548,513,0,0,0,0,"data 2 pages + 12 bytes (tail separated exactly onto next page) [txn]"
+116,"L",2,16,0,16,13,65249,65249,256,256,FALSE,FALSE,FALSE,65549,513,0,0,0,0,"data 2 pages + 13 bytes (data split; 1 byte over page boundary) [txn]"
+117,"L",4,16,0,16,0,131028,131028,0,0,FALSE,FALSE,FALSE,131072,1024,0,0,0,0,"data 4 pages"
+118,"L",4,16,0,16,1,131029,131029,0,0,FALSE,FALSE,FALSE,131073,1025,0,0,0,0,"data 4 pages + 1 byte (tail split; 1 byte over page boundary)"
+119,"L",4,16,0,16,11,131039,131039,0,0,FALSE,FALSE,FALSE,131083,1025,0,0,0,0,"data 4 pages + 11 bytes (tail split; 11 bytes over page boundary)"
+120,"L",4,16,0,16,12,131040,131040,0,0,FALSE,FALSE,FALSE,131084,1025,0,0,0,0,"data 4 pages + 12 bytes (tail separated exactly onto next page)"
+121,"L",4,16,0,16,13,131041,131041,0,0,FALSE,FALSE,FALSE,131085,1025,0,0,0,0,"data 4 pages + 13 bytes (data split; 1 byte over page boundary)"
+122,"L",4,16,0,16,0,130772,130772,256,256,FALSE,FALSE,FALSE,131072,1024,0,0,0,0,"data 4 pages [txn]"
+123,"L",4,16,0,16,1,130773,130773,256,256,FALSE,FALSE,FALSE,131073,1025,0,0,0,0,"data 4 pages + 1 byte (tail split; 1 byte over page boundary) [txn]"
+124,"L",4,16,0,16,11,130783,130783,256,256,FALSE,FALSE,FALSE,131083,1025,0,0,0,0,"data 4 pages + 11 bytes (tail split; 11 bytes over page boundary) [txn]"
+125,"L",4,16,0,16,12,130784,130784,256,256,FALSE,FALSE,FALSE,131084,1025,0,0,0,0,"data 4 pages + 12 bytes (tail separated exactly onto next page) [txn]"
+126,"L",4,16,0,16,13,130785,130785,256,256,FALSE,FALSE,FALSE,131085,1025,0,0,0,0,"data 4 pages + 13 bytes (data split; 1 byte over page boundary) [txn]"
+127,"L",3.5,16,0,16,0,114644,114644,0,0,FALSE,FALSE,FALSE,114688,896,0,0,0,0,"data 3.5 pages"
+128,"L",3.5,16,0,16,1,114645,114645,0,0,FALSE,FALSE,FALSE,114689,897,0,0,0,0,"data 3.5 pages + 1 byte"
+129,"L",3.5,16,0,16,0,114388,114388,256,256,FALSE,FALSE,FALSE,114688,896,0,0,0,0,"data 3.5 pages [txn]"
+130,"L",3.5,16,0,16,1,114389,114389,256,256,FALSE,FALSE,FALSE,114689,897,0,0,0,0,"data 3.5 pages + 1 byte [txn]"
+131,"L",1,16,0,16,-1,10,10,32735,32735,FALSE,FALSE,FALSE,32789,257,0,0,0,0,"xid 1 page – 1 byte; data 10 bytes (exact fit) [txn]"
+132,"L",1,16,0,16,0,10,10,32736,32736,FALSE,FALSE,FALSE,32790,257,0,0,0,0,"xid 1 page; data 10 bytes (exact fit) [txn]"
+133,"L",1,16,0,16,1,10,10,32737,32737,FALSE,FALSE,FALSE,32791,257,0,0,0,0,"xid 1 page + 1 byte; data 10 bytes (exact fit) [txn]"
+134,"L",2,16,0,16,-1,10,10,65503,65503,FALSE,FALSE,FALSE,65557,513,0,0,0,0,"xid 2 pages – 1 byte; data 10 bytes (exact fit) [txn]"
+135,"L",2,16,0,16,0,10,10,65504,65504,FALSE,FALSE,FALSE,65558,513,0,0,0,0,"xid 2 pages; data 10 bytes (exact fit) [txn]"
+136,"L",2,16,0,16,1,10,10,65505,65505,FALSE,FALSE,FALSE,65559,513,0,0,0,0,"xid 2 pages + 1 byte; data 10 bytes (exact fit) [txn]"
+137,"L",4,16,0,16,-1,10,10,131039,131039,FALSE,FALSE,FALSE,131093,1025,0,0,0,0,"xid 4 pages – 1 byte; data 10 bytes (exact fit) [txn]"
+138,"L",4,16,0,16,0,10,10,131040,131040,FALSE,FALSE,FALSE,131094,1025,0,0,0,0,"xid 4 pages; data 10 bytes (exact fit) [txn]"
+139,"L",4,16,0,16,1,10,10,131041,131041,FALSE,FALSE,FALSE,131095,1025,0,0,0,0,"xid 4 pages + 1 byte; data 10 bytes (exact fit) [txn]"
+140,"L",3.5,16,0,16,0,10,10,114656,114656,FALSE,FALSE,FALSE,114710,897,0,0,0,0,"xid 3.5 pages; data 10 bytes (exact fit) [txn]"
+141,"L",3.5,16,0,16,1,10,10,114657,114657,FALSE,FALSE,FALSE,114711,897,0,0,0,0,"xid 3.5 pages + 1 byte; data 10 bytes (exact fit) [txn]"
+142,"L",1,16,0,16,-1,10,10,32735,32735,TRUE,FALSE,FALSE,32789,257,32779,257,32771,257,"xid 1 page – 1 byte for enq rec; data 10 bytes (exact fit) [deq, txn]"
+143,"L",1,16,0,16,0,10,10,32736,32736,TRUE,FALSE,FALSE,32790,257,32780,257,32772,257,"xid 1 page for enq rec; data 10 bytes (exact fit) [deq, txn]"
+144,"L",1,16,0,16,1,10,10,32737,32737,TRUE,FALSE,FALSE,32791,257,32781,257,32773,257,"xid 1 page + 1 byte for enq rec; data 10 bytes (exact fit) [deq, txn]"
+145,"L",2,16,0,16,-1,10,10,65503,65503,TRUE,FALSE,FALSE,65557,513,65547,513,65539,513,"xid 2 pages – 1 byte for enq rec; data 10 bytes (exact fit) [deq, txn]"
+146,"L",2,16,0,16,0,10,10,65504,65504,TRUE,FALSE,FALSE,65558,513,65548,513,65540,513,"xid 2 pages for enq rec; data 10 bytes (exact fit) [deq, txn]"
+147,"L",2,16,0,16,1,10,10,65505,65505,TRUE,FALSE,FALSE,65559,513,65549,513,65541,513,"xid 2 pages + 1 byte for enq rec; data 10 bytes (exact fit) [deq, txn]"
+148,"L",4,16,0,16,-1,10,10,131039,131039,TRUE,FALSE,FALSE,131093,1025,131083,1025,131075,1025,"xid 4 pages – 1 byte for enq rec; data 10 bytes (exact fit) [deq, txn]"
+149,"L",4,16,0,16,0,10,10,131040,131040,TRUE,FALSE,FALSE,131094,1025,131084,1025,131076,1025,"xid 4 pages for enq rec; data 10 bytes (exact fit) [deq, txn]"
+150,"L",4,16,0,16,1,10,10,131041,131041,TRUE,FALSE,FALSE,131095,1025,131085,1025,131077,1025,"xid 4 pages + 1 byte for enq rec; data 10 bytes (exact fit) [deq, txn]"
+151,"L",3.5,16,0,16,0,10,10,114656,114656,TRUE,FALSE,FALSE,114710,897,114700,897,114692,897,"xid 3.5 pages for enq rec; data 10 bytes (exact fit) [deq, txn]"
+152,"L",3.5,16,0,16,1,10,10,114657,114657,TRUE,FALSE,FALSE,114711,897,114701,897,114693,897,"xid 3.5 pages + 1 byte for enq rec; data 10 bytes (exact fit) [deq, txn]"
+153,"L",1,16,0,16,-1,10,10,32735,32735,TRUE,FALSE,FALSE,32789,257,32779,257,32771,257,"xid 1 page – 1 byte for deq rec; data 10 bytes (exact fit) [deq, txn]"
+154,"L",1,16,0,16,0,10,10,32736,32736,TRUE,FALSE,FALSE,32790,257,32780,257,32772,257,"xid 1 page for deq rec; data 10 bytes (exact fit) [deq, txn]"
+155,"L",1,16,0,16,1,10,10,32737,32737,TRUE,FALSE,FALSE,32791,257,32781,257,32773,257,"xid 1 page + 1 byte for deq rec; data 10 bytes (exact fit) [deq, txn]"
+156,"L",2,16,0,16,-1,10,10,65503,65503,TRUE,FALSE,FALSE,65557,513,65547,513,65539,513,"xid 2 pages – 1 byte for deq rec; data 10 bytes (exact fit) [deq, txn]"
+157,"L",2,16,0,16,0,10,10,65504,65504,TRUE,FALSE,FALSE,65558,513,65548,513,65540,513,"xid 2 pages for deq rec; data 10 bytes (exact fit) [deq, txn]"
+158,"L",2,16,0,16,1,10,10,65505,65505,TRUE,FALSE,FALSE,65559,513,65549,513,65541,513,"xid 2 pages + 1 byte for deq rec; data 10 bytes (exact fit) [deq, txn]"
+159,"L",4,16,0,16,-1,10,10,131039,131039,TRUE,FALSE,FALSE,131093,1025,131083,1025,131075,1025,"xid 4 pages – 1 byte for deq rec; data 10 bytes (exact fit) [deq, txn]"
+160,"L",4,16,0,16,0,10,10,131040,131040,TRUE,FALSE,FALSE,131094,1025,131084,1025,131076,1025,"xid 4 pages for deq rec; data 10 bytes (exact fit) [deq, txn]"
+161,"L",4,16,0,16,1,10,10,131041,131041,TRUE,FALSE,FALSE,131095,1025,131085,1025,131077,1025,"xid 4 pages + 1 byte for deq rec; data 10 bytes (exact fit) [deq, txn]"
+162,"L",3.5,16,0,16,0,10,10,114656,114656,TRUE,FALSE,FALSE,114710,897,114700,897,114692,897,"xid 3.5 pages for deq rec; data 10 bytes (exact fit) [deq, txn]"
+163,"L",3.5,16,0,16,1,10,10,114657,114657,TRUE,FALSE,FALSE,114711,897,114701,897,114693,897,"xid 3.5 pages + 1 byte for deq rec; data 10 bytes (exact fit) [deq, txn]"
+164,"L",1,16,0,16,-1,10,10,32743,32743,TRUE,FALSE,FALSE,32797,257,32787,257,32779,257,"xid 1 page – 1 byte for txn rec; data 10 bytes (exact fit) [deq, txn]"
+165,"L",1,16,0,16,0,10,10,32744,32744,TRUE,FALSE,FALSE,32798,257,32788,257,32780,257,"xid 1 page for txn rec; data 10 bytes (exact fit) [deq, txn]"
+166,"L",1,16,0,16,1,10,10,32745,32745,TRUE,FALSE,FALSE,32799,257,32789,257,32781,257,"xid 1 page + 1 byte for txn rec; data 10 bytes (exact fit) [deq, txn]"
+167,"L",2,16,0,16,-1,10,10,65511,65511,TRUE,FALSE,FALSE,65565,513,65555,513,65547,513,"xid 2 pages – 1 byte for txn rec; data 10 bytes (exact fit) [deq, txn]"
+168,"L",2,16,0,16,0,10,10,65512,65512,TRUE,FALSE,FALSE,65566,513,65556,513,65548,513,"xid 2 pages for txn rec; data 10 bytes (exact fit) [deq, txn]"
+169,"L",2,16,0,16,1,10,10,65513,65513,TRUE,FALSE,FALSE,65567,513,65557,513,65549,513,"xid 2 pages + 1 byte for txn rec; data 10 bytes (exact fit) [deq, txn]"
+170,"L",4,16,0,16,-1,10,10,131047,131047,TRUE,FALSE,FALSE,131101,1025,131091,1025,131083,1025,"xid 4 pages – 1 byte for txn rec; data 10 bytes (exact fit) [deq, txn]"
+171,"L",4,16,0,16,0,10,10,131048,131048,TRUE,FALSE,FALSE,131102,1025,131092,1025,131084,1025,"xid 4 pages for txn rec; data 10 bytes (exact fit) [deq, txn]"
+172,"L",4,16,0,16,1,10,10,131049,131049,TRUE,FALSE,FALSE,131103,1025,131093,1025,131085,1025,"xid 4 pages + 1 byte for txn rec; data 10 bytes (exact fit) [deq, txn]"
+173,"L",3.5,16,0,16,0,10,10,114664,114664,TRUE,FALSE,FALSE,114718,897,114708,897,114700,897,"xid 3.5 pages for txn rec; data 10 bytes (exact fit) [deq, txn]"
+174,"L",3.5,16,0,16,1,10,10,114665,114665,TRUE,FALSE,FALSE,114719,897,114709,897,114701,897,"xid 3.5 pages + 1 byte for txn rec; data 10 bytes (exact fit) [deq, txn]"
+,,,,,,,,,,,,,,,,,,,,
+"High volume tests of random message lengths - RHM_WRONLY req'd for auto-dequeue == FALSE",,,,,,,,,,,,,,,,,,,,
+#175,"M",1,5000000,0,5000000,0,0,84,0,0,TRUE,FALSE,FALSE,128,1,32,1,0,0,"1 dblk max [deq]"
+#176,"M",3,3000000,0,3000000,0,0,340,0,0,TRUE,FALSE,FALSE,384,3,32,1,0,0,"3 dblks max [deq]"
+#177,"M",10,1600000,0,1600000,0,0,1236,0,0,TRUE,FALSE,FALSE,1280,10,32,1,0,0,"10 dblks max [deq]"
+#178,"M",30,600000,0,600000,0,0,3796,0,0,TRUE,FALSE,FALSE,3840,30,32,1,0,0,"30 dblks max [deq]"
+#179,"M",100,200000,0,200000,0,0,12756,0,0,TRUE,FALSE,FALSE,12800,100,32,1,0,0,"100 dblks max [deq]"
+#180,"M",300,60000,0,60000,0,0,38356,0,0,TRUE,FALSE,FALSE,38400,300,32,1,0,0,"300 dblks max [deq]"
+#181,"M",1000,20000,0,20000,0,0,127956,0,0,TRUE,FALSE,FALSE,128000,1000,32,1,0,0,"1000 dblks max [deq]"
+#182,"M",1,5000000,0,5000000,0,0,100,1,100,TRUE,FALSE,FALSE,244,2,144,2,136,2,"100 bytes xid max + 100 bytes data max [deq txn]"
+#183,"M",3,3000000,0,3000000,0,0,300,1,300,TRUE,FALSE,FALSE,644,6,344,3,336,3,"300 bytes xid max + 300 bytes data max [deq txn]"
+#184,"M",10,1600000,0,1600000,0,0,1000,1,1000,TRUE,FALSE,FALSE,2044,16,1044,9,1036,9,"1000 bytes xid max + 1000 bytes data max [deq txn]"
+#185,"M",30,600000,0,600000,0,0,3000,1,3000,TRUE,FALSE,FALSE,6044,48,3044,24,3036,24,"3000 bytes xid max + 3000 bytes data max [deq txn]"
+#186,"M",100,200000,0,200000,0,0,10000,1,10000,TRUE,FALSE,FALSE,20044,157,10044,79,10036,79,"10000 bytes xid max + 10000 bytes data max [deq txn]"
+#187,"M",300,60000,0,60000,0,0,30000,1,30000,TRUE,FALSE,FALSE,60044,470,30044,235,30036,235,"30000 bytes xid max + 30000 bytes data max [deq txn]"
+#188,"M",1000,20000,0,20000,0,0,100000,1,100000,TRUE,FALSE,FALSE,200044,1563,100044,782,100036,782,"100000 bytes xid max + 100000 bytes data max [deq txn]"
+,,,,,,,,,,,,,,,,,,,,
+"STANDARD PERFORMANCE BENCHMARK: 10,000,000 writes, data=212b (2 dblks)",,,,,,,,,,,,,,,,,,,,
+#189,"M",1,10000000,0,10000000,0,212,212,0,0,TRUE,FALSE,FALSE,256,2,32,1,0,0,"212 bytes data (2 dblks enq + 1 dblk deq)"
+#190,"M",1,10000000,0,10000000,0,148,148,64,64,TRUE,FALSE,FALSE,256,2,108,1,100,1,"148 bytes data + 64 bytes xid (2 dblks enq + 1 dblks deq + 1 dblks txn)"
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/main.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/main.cpp
new file mode 100644
index 0000000000..c8a4642b1c
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/main.cpp
@@ -0,0 +1,57 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "test_mgr.h"
+
+#include "args.h"
+#include <csignal>
+#include <iostream>
+
+#define PACKAGE_NAME "Journal Test Tool"
+#define VERSION "0.1"
+
+namespace po = boost::program_options;
+
+int main(int argc, char** argv)
+{
+ std::signal(SIGINT, mrg::jtt::test_mgr::signal_handler);
+ std::signal(SIGTERM, mrg::jtt::test_mgr::signal_handler);
+
+ std::cout << PACKAGE_NAME << " v." << VERSION << std::endl;
+
+ std::ostringstream oss;
+ oss << PACKAGE_NAME << " options";
+ mrg::jtt::args args(oss.str());
+ if (args.parse(argc, argv)) return 1;
+
+ try
+ {
+ mrg::jtt::test_mgr tm(args);
+ tm.run();
+ if (tm.error()) return 2; // One or more tests threw exceptions
+ }
+ catch (const std::exception& e)
+ {
+ std::cerr << e.what() << std::endl;
+ return 3;
+ }
+ return 0;
+}
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/read_arg.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/read_arg.cpp
new file mode 100644
index 0000000000..94a07c7005
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/read_arg.cpp
@@ -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.
+ *
+ */
+
+#include "read_arg.h"
+
+#include <cassert>
+#include <boost/program_options.hpp>
+namespace po = boost::program_options;
+
+namespace mrg
+{
+namespace jtt
+{
+std::map<std::string, read_arg::read_mode_t> read_arg::_map;
+std::string read_arg::_description;
+const bool read_arg::init = __init();
+
+// static init fn
+bool
+read_arg::__init()
+{
+ // Set string versions of each enum option here
+ _map["NONE"] = NONE;
+ _map["ALL"] = ALL;
+ _map["RANDOM"] = RANDOM;
+ _map["LAZYLOAD"] = LAZYLOAD;
+ _description = "Determines if and when messages will be read prior to dequeueing. "
+ "Values: (NONE | ALL | RANDOM | LAZYLOAD)";
+ return true;
+}
+
+void
+read_arg::parse(const std::string& str)
+{
+ std::map<std::string, read_arg::read_mode_t>::const_iterator i = _map.find(str);
+ if (i == _map.end())
+ throw po::invalid_option_value(str);
+ _rm = i->second;
+}
+
+// static fn
+const std::string&
+read_arg::str(const read_mode_t rm)
+{
+ std::map<std::string, read_mode_t>::const_iterator i = _map.begin();
+ while (i->second != rm && i != _map.end()) i++;
+ assert(i != _map.end());
+ return i->first;
+}
+
+// static fn
+const std::string&
+read_arg::descr()
+{
+ return _description;
+}
+
+std::ostream&
+operator<<(std::ostream& os, const read_arg& ra)
+{
+ os << ra.str();
+ return os;
+}
+
+std::istream&
+operator>>(std::istream& is, read_arg& ra)
+{
+ std::string s;
+ is >> s;
+ ra.parse(s);
+ return is;
+}
+
+} // namespace jtt
+} // namespace mrg
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/read_arg.h b/qpid/cpp/src/tests/legacystore/jrnl/jtt/read_arg.h
new file mode 100644
index 0000000000..a8fd6f198e
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/read_arg.h
@@ -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.
+ *
+ */
+
+#ifndef mrg_jtt_read_arg_hpp
+#define mrg_jtt_read_arg_hpp
+
+#include <string>
+#include <map>
+
+namespace mrg
+{
+namespace jtt
+{
+
+class read_arg
+{
+ public:
+ enum read_mode_t { NONE, ALL, RANDOM, LAZYLOAD};
+ private:
+ static std::map<std::string, read_mode_t> _map;
+ static std::string _description;
+ static const bool init;
+ static bool __init();
+ read_mode_t _rm;
+ public:
+ inline read_arg() : _rm(NONE) {}
+ inline read_arg(read_mode_t rm) : _rm(rm) {}
+
+ inline read_mode_t val() const { return _rm; }
+ inline void set_val(const read_mode_t rm) { _rm = rm; }
+ void parse(const std::string& str);
+
+ inline const std::string& str() const { return str(_rm); }
+ static const std::string& str(const read_mode_t rm);
+ static const std::string& descr();
+
+ friend std::ostream& operator<<(std::ostream& os, const read_arg& ra);
+ friend std::istream& operator>>(std::istream& is, read_arg& ra);
+};
+
+} // namespace jtt
+} // namespace mrg
+
+#endif // ifndef mrg_jtt_read_arg_hpp
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case.cpp
new file mode 100644
index 0000000000..e06e053504
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case.cpp
@@ -0,0 +1,179 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "test_case.h"
+
+#include <cstdlib>
+#include <iomanip>
+#include <sstream>
+
+namespace mrg
+{
+namespace jtt
+{
+
+test_case::test_case(const unsigned test_case_num, const u_int32_t num_msgs,
+ const std::size_t min_data_size, const std::size_t max_data_size, const bool auto_deq,
+ const std::size_t min_xid_size, const std::size_t max_xid_size, const transient_t transient,
+ const external_t external, const std::string& comment):
+ _test_case_num(test_case_num),
+ _num_msgs(num_msgs),
+ _min_data_size(min_data_size),
+ _max_data_size(max_data_size),
+ _auto_dequeue(auto_deq),
+ _min_xid_size(min_xid_size),
+ _max_xid_size(max_xid_size),
+ _transient(transient),
+ _external(external),
+ _comment(comment),
+ _result_average(),
+ _result_jmap()
+{}
+
+test_case::~test_case()
+{}
+
+std::size_t
+test_case::this_data_size() const
+{
+ if (_min_data_size == _max_data_size)
+ return _max_data_size;
+ std::size_t size_diff = _max_data_size - _min_data_size;
+ return _min_data_size + std::size_t(1.0 * std::rand() * size_diff/(RAND_MAX + 1.0));
+}
+
+std::size_t
+test_case::this_xid_size() const
+{
+ // TODO: rework when probabilities are introduced. Assume 50% if _min_xid_size = 0
+ if (_max_xid_size == 0)
+ return std::size_t(0);
+ if (_min_xid_size == 0)
+ {
+ if (1.0 * std::rand() / RAND_MAX < 0.5)
+ return std::size_t(0);
+ }
+ std::size_t size_diff = _max_xid_size - _min_xid_size;
+ return _min_xid_size + std::size_t(1.0 * std::rand() * size_diff/(RAND_MAX + 1.0));
+}
+
+bool
+test_case::this_transience() const
+{
+ // TODO: rework when probabilities are introduced. Assume 50% if JTT_RANDOM
+ if (_transient == JTT_TRANSIENT)
+ return false;
+ if (_transient == JTT_PERSISTNET)
+ return true;
+ return 1.0 * std::rand() / RAND_MAX < 0.5;
+}
+
+bool
+test_case::this_external() const
+{
+ // TODO: rework when probabilities are introduced. Assume 50% if JDL_RANDOM
+ if (_external == JDL_INTERNAL)
+ return false;
+ if (_external == JDL_EXTERNAL)
+ return true;
+ return 1.0 * std::rand() / RAND_MAX < 0.5;
+}
+
+void
+test_case::add_result(test_case_result::shared_ptr& tcrp)
+{
+ _result_average.add_test_result(tcrp);
+ res_map_citr ari = _result_jmap.find(tcrp->jid());
+ if (ari == _result_jmap.end())
+ {
+ test_case_result_agregation::shared_ptr p(new test_case_result_agregation(tcrp->jid()));
+ p->add_test_result(tcrp);
+ _result_jmap.insert(res_map_pair(tcrp->jid(), p));
+ }
+ else
+ ari->second->add_test_result(tcrp);
+}
+
+void
+test_case::set_fmt_chk_res(const bool res, const std::string& jid)
+{
+ _result_average.set_fmt_chk_res(res);
+ res_map_citr ari = _result_jmap.find(jid);
+ if (ari != _result_jmap.end())
+ ari->second->set_fmt_chk_res(res);
+}
+
+const test_case_result::shared_ptr
+test_case::jmap_last(std::string& jid) const
+{
+ res_map_citr i = _result_jmap.find(jid);
+ if (i == _result_jmap.end())
+ return test_case_result::shared_ptr();
+ u_int32_t num_res = (*i).second->num_results();
+ if (num_res)
+ return (*(*i).second)[num_res - 1];
+ return test_case_result::shared_ptr();
+}
+
+void
+test_case::clear()
+{
+ _result_average.clear();
+ _result_jmap.clear();
+}
+
+const std::string
+test_case::str() const
+{
+ std::ostringstream oss;
+ oss << "Test Parameters: Test case no. " << _test_case_num << ":" << std::endl;
+ oss << " Comment: " << _comment << std::endl;
+ oss << " Number of messages: " << _num_msgs << std::endl;
+ oss << " Data size: " << _min_data_size;
+ if (_min_data_size == _max_data_size)
+ oss << " bytes (fixed)" << std::endl;
+ else
+ oss << " - " << _max_data_size << " bytes" << std::endl;
+ oss << " XID size: " << _min_xid_size;
+ if (_min_xid_size == _max_xid_size)
+ oss << " bytes (fixed)" << std::endl;
+ else
+ oss << " - " << _max_xid_size << " bytes" << std::endl;
+ oss << " Auto-dequeue: " << (_auto_dequeue ? "true" : "false") << std::endl;
+ oss << " Persistence: ";
+ switch (_transient)
+ {
+ case JTT_TRANSIENT: oss << "TRANSIENT" << std::endl; break;
+ case JTT_PERSISTNET: oss << "PERSISTNET" << std::endl; break;
+ case JTT_RANDOM: oss << "RANDOM" << std::endl; break;
+ }
+ oss << " Message Data: ";
+ switch (_external)
+ {
+ case JDL_INTERNAL: oss << "INTERNAL"; break;
+ case JDL_EXTERNAL: oss << "EXTERNAL"; break;
+ case JDL_RANDOM: oss << "RANDOM"; break;
+ }
+ return oss.str();
+}
+
+} // namespace jtt
+} // namespace mrg
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case.h b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case.h
new file mode 100644
index 0000000000..f72dd05f0c
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case.h
@@ -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.
+ *
+ */
+
+#ifndef mrg_jtt_test_case_hpp
+#define mrg_jtt_test_case_hpp
+
+#include <boost/shared_ptr.hpp>
+#include <cstddef>
+#include <map>
+#include "test_case_result.h"
+#include "test_case_result_agregation.h"
+#include <vector>
+
+namespace mrg
+{
+namespace jtt
+{
+
+ class test_case
+ {
+ public:
+ enum transient_type { JTT_TRANSIENT = 0, JTT_PERSISTNET, JTT_RANDOM };
+ typedef transient_type transient_t;
+
+ enum data_location { JDL_INTERNAL = 0, JDL_EXTERNAL, JDL_RANDOM };
+ typedef data_location external_t;
+
+ typedef boost::shared_ptr<test_case> shared_ptr;
+
+ typedef std::map<std::string, test_case_result_agregation::shared_ptr> res_map;
+ typedef std::pair<std::string, test_case_result_agregation::shared_ptr> res_map_pair;
+ typedef res_map::const_iterator res_map_citr;
+
+ private:
+ unsigned _test_case_num;
+ u_int32_t _num_msgs;
+ std::size_t _min_data_size;
+ std::size_t _max_data_size;
+ bool _auto_dequeue;
+ // TODO: add probability of transaction to these params
+ std::size_t _min_xid_size;
+ std::size_t _max_xid_size;
+ // TODO: change these enums (transient_t & external_t) to probabilities
+ transient_t _transient;
+ external_t _external;
+ std::string _comment;
+
+ test_case_result_agregation _result_average; // overall average result
+ res_map _result_jmap; // map of per-journal averages
+
+ public:
+ test_case(const unsigned test_case_num, const u_int32_t num_msgs,
+ const std::size_t min_data_size, const std::size_t max_data_size,
+ const bool auto_deq, const std::size_t min_xid_size,
+ const std::size_t max_xid_size, const transient_t transient,
+ const external_t external, const std::string& comment);
+ virtual ~test_case();
+
+ inline unsigned test_case_num() const { return _test_case_num; }
+ inline u_int32_t num_msgs() const { return _num_msgs; }
+ inline std::size_t min_data_size() const { return _min_data_size; }
+ inline std::size_t max_data_size() const { return _max_data_size; }
+ std::size_t this_data_size() const;
+ inline bool auto_deq() const { return _auto_dequeue; }
+ inline std::size_t min_xid_size() const { return _min_xid_size; }
+ inline std::size_t max_xid_size() const { return _max_xid_size; }
+ std::size_t this_xid_size() const;
+ inline transient_t transient() const { return _transient; }
+ bool this_transience() const;
+ inline external_t external() const { return _external; }
+ bool this_external() const;
+ inline const std::string& comment() const { return _comment; }
+
+ void add_result(test_case_result::shared_ptr& p);
+ void set_fmt_chk_res(const bool res, const std::string& jid);
+
+ inline const test_case_result_agregation& average() const { return _result_average; }
+ inline u_int32_t num_results() const { return _result_average.num_results(); }
+ inline unsigned num_jrnls() const { return _result_jmap.size(); }
+ inline res_map_citr jrnl_average(std::string& jid) const { return _result_jmap.find(jid); }
+ inline res_map_citr jmap_begin() const { return _result_jmap.begin(); }
+ inline res_map_citr jmap_end() const { return _result_jmap.end(); }
+ const test_case_result::shared_ptr jmap_last(std::string& jid) const;
+
+ void clear();
+ const std::string str() const;
+ };
+
+} // namespace jtt
+} // namespace mrg
+
+#endif // ifndef mrg_jtt_test_case_hpp
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result.cpp
new file mode 100644
index 0000000000..2f88f265a5
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result.cpp
@@ -0,0 +1,201 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "test_case_result.h"
+
+#include <iomanip>
+#include <sstream>
+
+namespace mrg
+{
+namespace jtt
+{
+
+test_case_result::test_case_result(const std::string& jid):
+ _jid(jid),
+ _num_enq(0),
+ _num_deq(0),
+ _num_read(0),
+ _num_rproc(0),
+ _start_time(),
+ _stop_time(),
+ _stopped(false),
+ _test_time(),
+ _exception_list()
+{}
+
+test_case_result::~test_case_result()
+{}
+
+const std::string
+test_case_result::test_time_str() const
+{
+ return _test_time.str(9);
+}
+
+void
+test_case_result::add_exception(const journal::jexception& e, const bool set_stop_time_flag)
+{
+ if (!_stopped && set_stop_time_flag)
+ {
+ set_stop_time();
+ _stopped = true;
+ }
+ _exception_list.push_back(e.what());
+}
+
+void
+test_case_result::add_exception(const std::string& err_str, const bool set_stop_time_flag)
+{
+ if (!_stopped && set_stop_time_flag)
+ {
+ set_stop_time();
+ _stopped = true;
+ }
+ _exception_list.push_back(err_str);
+}
+
+void
+test_case_result::add_exception(const char* err_str, const bool set_stop_time_flag)
+{
+ if (!_stopped && set_stop_time_flag)
+ {
+ set_stop_time();
+ _stopped = true;
+ }
+ _exception_list.push_back(err_str);
+}
+
+void
+test_case_result::clear()
+{
+ _num_enq = 0;
+ _num_deq = 0;
+ _num_read = 0;
+ _start_time.set_zero();
+ _stop_time.set_zero();
+ _test_time.set_zero();
+ _exception_list.clear();
+}
+
+const std::string
+test_case_result::str(const bool summary) const
+{
+ std::ostringstream oss;
+ if (summary)
+ {
+ oss << _jid << ":";
+ oss << str_summary();
+ if (_exception_list.size())
+ oss << "; fail: " << _exception_list[0] << std::endl;
+ else
+ oss << "; ok" << std::endl;
+ }
+ else
+ {
+ oss << "--- Journal instance: jid=\"" << _jid << "\" ---" << std::endl;
+ oss << str_full();
+ if (_exception_list.size())
+ oss << " exception/error:" << _exception_list[0] << std::endl;
+ }
+ return oss.str();
+}
+
+const std::string
+test_case_result::str_full() const
+{
+ const double t = _test_time.tv_sec + (_test_time.tv_nsec/1e9);
+ const bool no_exception = _exception_list.empty();
+ std::ostringstream oss;
+ oss.setf(std::ios::fixed, std::ios::floatfield);
+ oss.precision(2);
+ if (no_exception)
+ {
+ oss.precision(6);
+ oss << " total test time: " << t << "s" << std::endl;
+ }
+ oss.precision(3);
+ oss << " total number enqueues: " << _num_enq;
+ if (no_exception)
+ oss << " (" << (_num_enq / t) << " enq/sec)";
+ oss << std::endl;
+ oss << " total number dequeues: " << _num_deq;
+ if (no_exception)
+ oss << " (" << (_num_deq / t) << " deq/sec)";
+ oss << std::endl;
+ oss << "total write operations: " << (_num_enq + _num_deq);
+ if (no_exception)
+ oss << " (" << ((_num_enq + _num_deq) / t) << " wrops/sec)";
+ oss << std::endl;
+ oss << " total number reads: " << _num_read;
+ if (no_exception)
+ oss << " (" << (_num_read / t) << " rd/sec)";
+ oss << std::endl;
+ oss << " total operations: " << (_num_enq + _num_deq + _num_read);
+ if (no_exception)
+ oss << " (" << ((_num_enq + _num_deq + _num_read) / t) << " ops/sec)";
+ oss << std::endl;
+ oss << " overall result: " << (no_exception ? "PASS" : "*** FAIL ***") << std::endl;
+ return oss.str();
+}
+
+const std::string
+test_case_result::str_summary() const
+{
+ const double t = _test_time.tv_sec + (_test_time.tv_nsec/1e9);
+ const bool no_exception = _exception_list.empty();
+ std::ostringstream oss;
+ oss.setf(std::ios::fixed, std::ios::floatfield);
+ if (no_exception)
+ {
+ oss.precision(6);
+ oss << " t=" << t << "s;";
+ }
+ else
+ oss << " exception";
+ oss.precision(3);
+ oss << " enq=" << _num_enq;
+ if (no_exception)
+ oss << " (" << (_num_enq / t) << ")";
+ oss << "; deq=" << _num_deq;
+ if (no_exception)
+ oss << " (" << (_num_deq / t) << ")";
+ oss << "; wr=" << (_num_enq + _num_deq);
+ if (no_exception)
+ oss << " (" << ((_num_enq + _num_deq) / t) << ")";
+ oss << "; rd=" << _num_read;
+ if (no_exception)
+ oss << " (" << (_num_read / t) << ")";
+ oss << "; tot=" << (_num_enq + _num_deq + _num_read);
+ if (no_exception)
+ oss << " (" << ((_num_enq + _num_deq + _num_read) / t) << ")";
+ return oss.str();
+}
+
+void
+test_case_result::calc_test_time()
+{
+ if (!_start_time.is_zero() && _stop_time >= _start_time)
+ _test_time = _stop_time - _start_time;
+}
+
+} // namespace jtt
+} // namespace mrg
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result.h b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result.h
new file mode 100644
index 0000000000..d15f9d021d
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result.h
@@ -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.
+ *
+ */
+
+#ifndef mrg_jtt_test_case_result_hpp
+#define mrg_jtt_test_case_result_hpp
+
+#include <boost/shared_ptr.hpp>
+#include <deque>
+#include "qpid/legacystore/jrnl/jexception.h"
+#include "qpid/legacystore/jrnl/time_ns.h"
+#include <string>
+
+namespace mrg
+{
+namespace jtt
+{
+
+ class test_case_result
+ {
+ public:
+ typedef boost::shared_ptr<test_case_result> shared_ptr;
+
+ typedef std::deque<std::string> elist;
+ typedef elist::const_iterator elist_citr;
+
+ protected:
+ std::string _jid;
+ u_int32_t _num_enq;
+ u_int32_t _num_deq;
+ u_int32_t _num_read; // Messages actually read
+ u_int32_t _num_rproc; // Messages handled by read thread (not all are read)
+ journal::time_ns _start_time;
+ journal::time_ns _stop_time;
+ bool _stopped;
+ journal::time_ns _test_time;
+ elist _exception_list;
+
+ public:
+ test_case_result(const std::string& jid);
+ virtual ~test_case_result();
+
+ inline const std::string& jid() const { return _jid; }
+ inline u_int32_t num_enq() const { return _num_enq; }
+ inline u_int32_t incr_num_enq() { return ++_num_enq; }
+ inline u_int32_t num_deq() const { return _num_deq; }
+ inline u_int32_t incr_num_deq() { return ++_num_deq; }
+ inline u_int32_t num_read() const { return _num_read; }
+ inline u_int32_t incr_num_read() { return ++_num_read; }
+ inline u_int32_t num_rproc() const { return _num_rproc; }
+ inline u_int32_t incr_num_rproc() { return ++_num_rproc; }
+
+ inline const journal::time_ns& start_time() const { return _start_time; }
+ inline void set_start_time() { ::clock_gettime(CLOCK_REALTIME, &_start_time); }
+ inline const journal::time_ns& stop_time() const { return _stop_time; }
+ inline void set_stop_time()
+ { ::clock_gettime(CLOCK_REALTIME, &_stop_time); calc_test_time(); }
+ inline void set_test_time(const journal::time_ns& ts) { _test_time = ts; }
+ inline const journal::time_ns& test_time() const { return _test_time; }
+ const std::string test_time_str() const;
+
+ void add_exception(const journal::jexception& e, const bool set_stop_time_flag = true);
+ void add_exception(const std::string& err_str, const bool set_stop_time_flag = true);
+ void add_exception(const char* err_str, const bool set_stop_time_flag = true);
+ inline bool exception() const { return _exception_list.size() > 0; }
+ inline unsigned exception_count() const { return _exception_list.size(); }
+ inline elist_citr begin() { return _exception_list.begin(); }
+ inline elist_citr end() { return _exception_list.end(); }
+ inline const std::string& operator[](unsigned i) { return _exception_list[i]; }
+
+ void clear();
+ const std::string str(const bool summary) const;
+
+ protected:
+ const std::string str_full() const;
+ const std::string str_summary() const;
+ void calc_test_time();
+ };
+
+} // namespace jtt
+} // namespace mrg
+
+#endif // ifndef mrg_jtt_test_case_result_hpp
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result_agregation.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result_agregation.cpp
new file mode 100644
index 0000000000..da439e71e8
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result_agregation.cpp
@@ -0,0 +1,185 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "test_case_result_agregation.h"
+
+#include <iomanip>
+#include <sstream>
+
+namespace mrg
+{
+namespace jtt
+{
+
+test_case_result_agregation::test_case_result_agregation():
+ test_case_result("Average"),
+ _tc_average(true),
+ _fmt_chk_done(false),
+ _fmt_chk_err(false),
+ _res_list()
+{
+}
+
+test_case_result_agregation::test_case_result_agregation(const std::string& jid):
+ test_case_result(jid),
+ _tc_average(false),
+ _fmt_chk_done(false),
+ _fmt_chk_err(false),
+ _res_list()
+{}
+
+test_case_result_agregation::~test_case_result_agregation()
+{}
+
+void
+test_case_result_agregation::add_test_result(const test_case_result::shared_ptr& tcrp)
+{
+ if (_tc_average || _jid.compare(tcrp->jid()) == 0)
+ {
+ _num_enq += tcrp->num_enq();
+ _num_deq += tcrp->num_deq();
+ _num_read += tcrp->num_read();
+ add_test_time(tcrp->test_time());
+ _exception_list.insert(_exception_list.end(), tcrp->begin(), tcrp->end());
+ _res_list.push_back(tcrp);
+ }
+}
+
+bool
+test_case_result_agregation::exception() const
+{
+ for (tcrp_list_citr i = _res_list.begin(); i < _res_list.end(); i++)
+ if ((*i)->exception())
+ return true;
+ return false;
+}
+
+unsigned
+test_case_result_agregation::exception_count() const
+{
+ unsigned cnt = 0;
+ for (tcrp_list_citr i = _res_list.begin(); i < _res_list.end(); i++)
+ cnt += (*i)->exception_count();
+ return cnt;
+}
+
+void
+test_case_result_agregation::clear()
+{
+ test_case_result::clear();
+ _res_list.clear();
+}
+
+const std::string
+test_case_result_agregation::str(const bool last_only, const bool summary) const
+{
+ std::ostringstream oss;
+ if (last_only)
+ oss << " " << _res_list.at(_res_list.size()-1)->str(summary);
+ else
+ {
+ for (tcrp_list_citr i=_res_list.begin(); i!=_res_list.end(); i++)
+ oss << " " << (*i)->str(summary);
+ }
+ if (_res_list.size() > 1)
+ oss << " " << (summary ? str_summary(last_only) : str_full(last_only));
+ return oss.str();
+}
+
+const std::string
+test_case_result_agregation::str_full(const bool /*last_only*/) const
+{
+ std::ostringstream oss;
+ oss.precision(2);
+ if (_tc_average)
+ oss << "Average across all journal instances:" << std::endl;
+ else
+ oss << "Average for jid=\"" << _jid << "\":" << std::endl;
+ oss << " total number results: " << _res_list.size() << std::endl;
+ oss << " number exceptions: " << _exception_list.size() << " (" <<
+ (100.0 * _res_list.size() / _exception_list.size()) << "%)" << std::endl;
+
+ oss << test_case_result::str_full();
+
+ if (_exception_list.size())
+ {
+ unsigned n = 0;
+ oss << "List of exceptions/errors:" << std::endl;
+ for (elist_citr i = _exception_list.begin(); i != _exception_list.end(); i++, n++)
+ oss << " " << n << ". " << (*i) << std::endl;
+ }
+
+ if (!_tc_average && _res_list.size() > 1)
+ {
+ oss << "Individual results:" << std::endl;
+ for (tcrp_list_citr i=_res_list.begin(); i!=_res_list.end(); i++)
+ oss << " " << (*i)->str(false) << std::endl;
+ oss << std::endl;
+ }
+
+ return oss.str();
+}
+
+const std::string
+test_case_result_agregation::str_summary(const bool /*last_only*/) const
+{
+ std::ostringstream oss;
+ if (_tc_average)
+ oss << "overall average [" << _res_list.size() << "]:";
+ else
+ oss << "average (" << _res_list.size() << "):";
+
+ oss << test_case_result::str_summary();
+ if (_fmt_chk_done)
+ oss << " fmt-chk=" << (_fmt_chk_err ? "fail" : "ok");
+
+ if (_exception_list.size())
+ {
+ if (_tc_average)
+ oss << " fail: " << _exception_list.size() << " exception"
+ << (_exception_list.size()>1?"s":"") << std::endl;
+ else
+ {
+ if (_exception_list.size() == 1)
+ oss << " fail: " << *_exception_list.begin() << std::endl;
+ else
+ {
+ oss << std::endl;
+ unsigned n = 0;
+ for (elist_citr i = _exception_list.begin(); i != _exception_list.end(); i++, n++)
+ oss << " " << n << ". " << (*i) << std::endl;
+ }
+ }
+ }
+ else
+ oss << " ok" << std::endl;
+ return oss.str();
+}
+
+const journal::time_ns&
+test_case_result_agregation::add_test_time(const journal::time_ns& t)
+{
+ _test_time += t;
+ return _test_time;
+}
+
+} // namespace jtt
+} // namespace mrg
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result_agregation.h b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result_agregation.h
new file mode 100644
index 0000000000..0b3998176c
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_result_agregation.h
@@ -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.
+ *
+ */
+
+#ifndef mrg_jtt_test_case_result_agregation_hpp
+#define mrg_jtt_test_case_result_agregation_hpp
+
+#include "test_case_result.h"
+
+#include <iostream>
+#include <vector>
+
+namespace mrg
+{
+namespace jtt
+{
+
+ class test_case_result_agregation : public test_case_result
+ {
+ public:
+ typedef boost::shared_ptr<test_case_result_agregation> shared_ptr;
+
+ typedef std::vector<test_case_result::shared_ptr> tcrp_list;
+ typedef tcrp_list::const_iterator tcrp_list_citr;
+
+ private:
+ bool _tc_average;
+ bool _fmt_chk_done;
+ bool _fmt_chk_err;
+ tcrp_list _res_list;
+
+ public:
+ test_case_result_agregation(); // used for average across jrnl instances
+ test_case_result_agregation(const std::string& jid);
+ virtual ~test_case_result_agregation();
+
+ void add_test_result(const test_case_result::shared_ptr& tcrp);
+
+ inline bool tc_average_mode() const { return _tc_average; }
+ inline bool fmt_chk_done() const { return _fmt_chk_done; }
+ inline bool fmt_chk_res() const { return _fmt_chk_err; }
+ inline void set_fmt_chk_res(const bool err)
+ { _fmt_chk_done = true; _fmt_chk_err |= err; if (err) add_exception("Journal format error"); }
+ inline u_int32_t num_results() const { return _res_list.size(); }
+ inline tcrp_list_citr rlist_begin() const { return _res_list.begin(); }
+ inline tcrp_list_citr rlist_end() const { return _res_list.end(); }
+ inline const test_case_result::shared_ptr& operator[](unsigned i) const
+ { return _res_list[i]; }
+ bool exception() const;
+ unsigned exception_count() const;
+
+ void clear();
+ const std::string str(const bool last_only, const bool summary) const;
+
+ private:
+ const std::string str_full(const bool last_only) const;
+ const std::string str_summary(const bool last_only) const;
+ const journal::time_ns& add_test_time(const journal::time_ns& t);
+ };
+
+} // namespace jtt
+} // namespace mrg
+
+#endif // ifndef mrg_jtt_test_case_result_agregation_hpp
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_set.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_set.cpp
new file mode 100644
index 0000000000..b818d6c7ae
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_set.cpp
@@ -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.
+ *
+ */
+
+#include "test_case_set.h"
+
+#include <cstdlib>
+#include <fstream>
+#include <iostream>
+
+namespace mrg
+{
+namespace jtt
+{
+
+test_case_set::test_case_set():
+ _tc_list(),
+ _csv_ignored(0)
+{}
+
+test_case_set::test_case_set(const std::string& csv_filename, const bool recover_mode,
+ const csv_map& cols):
+ _tc_list(),
+ _csv_ignored(0)
+{
+ append_from_csv(csv_filename, recover_mode, cols);
+}
+
+test_case_set::~test_case_set()
+{}
+
+void
+test_case_set::append(const unsigned test_case_num, const u_int32_t num_msgs,
+ const std::size_t min_data_size, const std::size_t max_data_size, const bool auto_deq,
+ const std::size_t min_xid_size, const std::size_t max_xid_size,
+ const test_case::transient_t transient, const test_case::external_t external,
+ const std::string& comment)
+{
+ test_case::shared_ptr tcp(new test_case(test_case_num, num_msgs, min_data_size,
+ max_data_size, auto_deq, min_xid_size, max_xid_size, transient, external, comment));
+ append(tcp);
+}
+
+
+#define CSV_BUFF_SIZE 2048
+void
+test_case_set::append_from_csv(const std::string& csv_filename, const bool recover_mode,
+ const csv_map& cols)
+{
+ char buff[CSV_BUFF_SIZE];
+ std::ifstream ifs(csv_filename.c_str());
+ while (ifs.good())
+ {
+ ifs.getline(buff, (std::streamsize)CSV_BUFF_SIZE);
+ if (ifs.gcount())
+ {
+ test_case::shared_ptr tcp = get_tc_from_csv(buff, cols);
+ if (tcp.get())
+ {
+ if (!recover_mode || tcp->auto_deq())
+ append(tcp);
+ else
+ _csv_ignored++;
+ }
+ }
+ }
+}
+
+test_case::shared_ptr
+test_case_set::get_tc_from_csv(const std::string& csv_line, const csv_map& cols)
+{
+ unsigned test_case_num = 0;
+ u_int32_t num_msgs = 0;
+ std::size_t min_data_size = 0;
+ std::size_t max_data_size = 0;
+ bool auto_deq = false;
+ std::size_t min_xid_size = 0;
+ std::size_t max_xid_size = 0;
+ test_case::transient_t transient = test_case::JTT_TRANSIENT;
+ test_case::external_t external = test_case::JDL_INTERNAL;
+ std::string comment;
+
+ csv_tok t(csv_line);
+ unsigned col_num = 0;
+ for (csv_tok_citr t_itr = t.begin(); t_itr != t.end(); ++t_itr, ++col_num)
+ {
+ const std::string& tok = *t_itr;
+ csv_map_citr m_citr = cols.find(col_num);
+ if (m_citr != cols.end())
+ {
+ switch (m_citr->second)
+ {
+ case CSV_TC_NUM:
+ if (!tok.size() || tok[0] < '0' || tok[0] > '9')
+ return test_case::shared_ptr();
+ test_case_num = unsigned(std::atol(tok.c_str()));
+ break;
+ case CSV_TC_NUM_MSGS: num_msgs = u_int32_t(std::atol(tok.c_str())); break;
+ case CSV_TC_MIN_DATA_SIZE: min_data_size = std::size_t(std::atol(tok.c_str())); break;
+ case CSV_TC_MAX_DATA_SIZE: max_data_size = std::size_t(std::atol(tok.c_str())); break;
+ case CSV_TC_AUTO_DEQ:
+ if (tok == "TRUE" || tok == "1")
+ auto_deq = true;
+ break;
+ case CSV_TC_MIN_XID_SIZE: min_xid_size = std::size_t(std::atol(tok.c_str())); break;
+ case CSV_TC_MAX_XID_SIZE: max_xid_size = std::size_t(std::atol(tok.c_str())); break;
+ case CSV_TC_TRANSIENT:
+ if (tok == "TRUE" || tok == "1")
+ transient = test_case::JTT_PERSISTNET;
+ else if (tok == "RANDOM" || tok == "-1")
+ transient = test_case::JTT_RANDOM;
+ break;
+ case CSV_TC_EXTERNAL:
+ if (tok == "TRUE" || tok == "1")
+ external = test_case::JDL_EXTERNAL;
+ else if (tok == "RANDOM" || tok == "-1")
+ external = test_case::JDL_RANDOM;
+ break;
+ case CSV_TC_COMMENT: comment = *t_itr; break;
+ }
+ }
+ }
+ if (col_num)
+ return test_case::shared_ptr(new test_case(test_case_num, num_msgs, min_data_size,
+ max_data_size, auto_deq, min_xid_size, max_xid_size, transient, external, comment));
+ else
+ return test_case::shared_ptr();
+}
+
+// Static member initializations
+// This csv_map is for use on the standard spreadsheet-derived test case csv files.
+test_case_set::csv_map test_case_set::std_csv_map;
+const bool test_case_set::_map_init = __init();
+
+bool
+test_case_set::__init()
+{
+ std_csv_map.insert(test_case_set::csv_pair(0, test_case_set::CSV_TC_NUM));
+ std_csv_map.insert(test_case_set::csv_pair(5, test_case_set::CSV_TC_NUM_MSGS));
+ std_csv_map.insert(test_case_set::csv_pair(7, test_case_set::CSV_TC_MIN_DATA_SIZE));
+ std_csv_map.insert(test_case_set::csv_pair(8, test_case_set::CSV_TC_MAX_DATA_SIZE));
+ std_csv_map.insert(test_case_set::csv_pair(11, test_case_set::CSV_TC_AUTO_DEQ));
+ std_csv_map.insert(test_case_set::csv_pair(9, test_case_set::CSV_TC_MIN_XID_SIZE));
+ std_csv_map.insert(test_case_set::csv_pair(10, test_case_set::CSV_TC_MAX_XID_SIZE));
+ std_csv_map.insert(test_case_set::csv_pair(12, test_case_set::CSV_TC_TRANSIENT));
+ std_csv_map.insert(test_case_set::csv_pair(13, test_case_set::CSV_TC_EXTERNAL));
+ std_csv_map.insert(test_case_set::csv_pair(20, test_case_set::CSV_TC_COMMENT));
+ return true;
+}
+
+} // namespace jtt
+} // namespace mrg
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_set.h b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_set.h
new file mode 100644
index 0000000000..94a1ee3172
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_case_set.h
@@ -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.
+ *
+ */
+
+#ifndef mrg_jtt_test_case_set_hpp
+#define mrg_jtt_test_case_set_hpp
+
+#include "test_case.h"
+
+#include <cstddef>
+#include <boost/tokenizer.hpp>
+#include <map>
+#include <vector>
+
+namespace mrg
+{
+namespace jtt
+{
+
+ class test_case_set
+ {
+ public:
+ enum csv_col_enum {
+ CSV_TC_NUM = 0,
+ CSV_TC_NUM_MSGS,
+ CSV_TC_MIN_DATA_SIZE,
+ CSV_TC_MAX_DATA_SIZE,
+ CSV_TC_AUTO_DEQ,
+ CSV_TC_MIN_XID_SIZE,
+ CSV_TC_MAX_XID_SIZE,
+ CSV_TC_TRANSIENT,
+ CSV_TC_EXTERNAL,
+ CSV_TC_COMMENT };
+ typedef std::pair<unsigned, csv_col_enum> csv_pair;
+ typedef std::map<unsigned, csv_col_enum> csv_map;
+ typedef csv_map::const_iterator csv_map_citr;
+ static csv_map std_csv_map;
+
+ typedef std::vector<test_case::shared_ptr> tcl;
+ typedef tcl::iterator tcl_itr;
+ typedef tcl::const_iterator tcl_citr;
+
+ typedef boost::tokenizer<boost::escaped_list_separator<char> > csv_tok;
+ typedef csv_tok::const_iterator csv_tok_citr;
+
+ private:
+ tcl _tc_list;
+ static const bool _map_init;
+ unsigned _csv_ignored;
+
+ public:
+ test_case_set();
+ test_case_set(const std::string& csv_filename, const bool recover_mode,
+ const csv_map& cols = std_csv_map);
+ virtual ~test_case_set();
+
+ inline unsigned size() const { return _tc_list.size(); }
+ inline unsigned ignored() const { return _csv_ignored; }
+ inline bool empty() const { return _tc_list.empty(); }
+
+ inline void append(const test_case::shared_ptr& tc) { _tc_list.push_back(tc); }
+ void append(const unsigned test_case_num, const u_int32_t num_msgs,
+ const std::size_t min_data_size, const std::size_t max_data_size,
+ const bool auto_deq, const std::size_t min_xid_size,
+ const std::size_t max_xid_size, const test_case::transient_t transient,
+ const test_case::external_t external, const std::string& comment);
+ void append_from_csv(const std::string& csv_filename, const bool recover_mode,
+ const csv_map& cols = std_csv_map);
+ inline tcl_itr begin() { return _tc_list.begin(); }
+ inline tcl_itr end() { return _tc_list.end(); }
+ inline const test_case::shared_ptr& operator[](unsigned i) { return _tc_list[i]; }
+ inline void clear() { _tc_list.clear(); }
+
+ private:
+ test_case::shared_ptr get_tc_from_csv(const std::string& csv_line, const csv_map& cols);
+ static bool __init();
+ };
+
+} // namespace jtt
+} // namespace mrg
+
+#endif // ifndef mrg_jtt_test_case_set_hpp
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_mgr.cpp b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_mgr.cpp
new file mode 100644
index 0000000000..de0b5dbfb9
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_mgr.cpp
@@ -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.
+ *
+ */
+
+#include "test_mgr.h"
+
+#include <cstdlib>
+#include <iostream>
+#include <sys/stat.h>
+#include "test_case_set.h"
+
+namespace mrg
+{
+namespace jtt
+{
+
+test_mgr::test_mgr(args& args):
+ _ji_list(),
+ _args(args),
+ _err_flag(false),
+ _random_fn_ptr(random_fn)
+{
+ if (_args.seed)
+ std::srand(_args.seed);
+}
+
+test_mgr::~test_mgr()
+{}
+
+void
+test_mgr::run()
+{
+ // TODO: complete tidy-up of non-summary (verbose) results, then pull through
+ // a command-line summary to control this.
+ // Idea: --summary: prints short results afterwards
+ // --verbose: prints long version as test progresses
+ // defualt: none of these, similar to current summary = true version.
+ const bool summary = true;
+
+ std::cout << "CSV file: \"" << _args.test_case_csv_file_name << "\"";
+ test_case_set tcs(_args.test_case_csv_file_name, _args.recover_mode);
+
+ if (tcs.size())
+ {
+ std::cout << " (found " << tcs.size() << " test case" << (tcs.size() != 1 ? "s" : "") <<
+ ")" << std::endl;
+ if (tcs.ignored())
+ std::cout << "WARNING: " << tcs.ignored() << " test cases were ignored. (All test "
+ "cases without auto-dequeue are ignored when recover-mode is selected.)" <<
+ std::endl;
+ _args.print_args();
+ }
+ else if(tcs.ignored())
+ {
+ std::cout << " WARNING: All " << tcs.ignored() << " test case(s) were ignored. (All test "
+ "cases without auto-dequeue are ignored when recover-mode is selected.)" <<
+ std::endl;
+ }
+ else
+ std::cout << " (WARNING: This CSV file is empty or does not exist.)" << std::endl;
+
+ do
+ {
+ unsigned u = 0;
+ if (_args.randomize)
+ random_shuffle(tcs.begin(), tcs.end(), _random_fn_ptr);
+ for (test_case_set::tcl_itr tci = tcs.begin(); tci != tcs.end(); tci++, u++)
+ {
+ if (summary)
+ std::cout << "Test case " << (*tci)->test_case_num() << ": \"" <<
+ (*tci)->comment() << "\"" << std::endl;
+ else
+ std::cout << (*tci)->str() << std::endl;
+ if (!_args.reuse_instance || _ji_list.empty())
+ initialize_jrnls();
+ for (ji_list_citr jii=_ji_list.begin(); jii!=_ji_list.end(); jii++)
+ (*jii)->init_tc(*tci, &_args);
+ for (ji_list_citr jii=_ji_list.begin(); jii!=_ji_list.end(); jii++)
+ (*jii)->run_tc();
+ for (ji_list_citr jii=_ji_list.begin(); jii!=_ji_list.end(); jii++)
+ (*jii)->tc_wait_compl();
+
+ if (_args.format_chk)
+ {
+ for (ji_list_citr jii=_ji_list.begin(); jii!=_ji_list.end(); jii++)
+ {
+ jrnl_init_params::shared_ptr jpp = (*jii)->params();
+ std::string ja = _args.jfile_analyzer;
+ if (ja.empty()) ja = "./jfile_chk.py";
+ if (!exists(ja))
+ {
+ std::ostringstream oss;
+ oss << "ERROR: Validation program \"" << ja << "\" does not exist" << std::endl;
+ throw std::runtime_error(oss.str());
+ }
+ std::ostringstream oss;
+ oss << ja << " -b " << jpp->base_filename();
+ // TODO: When jfile_check.py can handle previously recovered journals for
+ // specific tests, then remove this exclusion.
+ if (!_args.recover_mode)
+ {
+ oss << " -c " << _args.test_case_csv_file_name;
+ oss << " -t " << (*tci)->test_case_num();
+ }
+ oss << " -q " << jpp->jdir();
+ bool res = system(oss.str().c_str()) != 0;
+ (*tci)->set_fmt_chk_res(res, jpp->jid());
+ if (res) _err_flag = true;
+ }
+ }
+
+ if (!_args.recover_mode && !_args.keep_jrnls)
+ for (ji_list_citr jii=_ji_list.begin(); jii!=_ji_list.end(); jii++)
+ try { mrg::journal::jdir::delete_dir((*jii)->jrnl_dir()); }
+ catch (...) {} // TODO - work out exception strategy for failure here...
+
+ print_results(*tci, summary);
+ if ((*tci)->average().exception())
+ _err_flag = true;
+ if (_abort || (!_args.repeat_flag && _signal))
+ break;
+ if (_args.pause_secs && tci != tcs.end())
+ ::usleep(_args.pause_secs * 1000000);
+ }
+ }
+ while (_args.repeat_flag && !_signal);
+}
+
+// static fn:
+void
+test_mgr::signal_handler(int sig)
+{
+ if (_signal)
+ _abort = true;
+ _signal = sig;
+ std::cout << std::endl;
+ std::cout << "********************************" << std::endl;
+ std::cout << "Caught signal " << sig << std::endl;
+ if (_abort)
+ std::cout << "Aborting..." << std::endl;
+ else
+ std::cout << "Completing current test cycle..." << std::endl;
+ std::cout << "********************************" << std::endl << std::endl;
+}
+
+bool
+test_mgr::exists(std::string fname)
+{
+ struct stat s;
+ if (::stat(fname.c_str(), &s))
+ {
+ if (errno == ENOENT) // No such dir or file
+ return false;
+ // Throw for any other condition
+ std::ostringstream oss;
+ oss << "ERROR: test_mgr::exists(): file=\"" << fname << "\": " << FORMAT_SYSERR(errno);
+ throw std::runtime_error(oss.str());
+ }
+ return true;
+}
+
+void
+test_mgr::initialize_jrnls()
+{
+ _ji_list.clear();
+ for (unsigned i=0; i<_args.num_jrnls; i++)
+ {
+ std::ostringstream jid;
+ jid << std::hex << std::setfill('0');
+ jid << "test_" << std::setw(4) << std::hex << i;
+ std::ostringstream jdir;
+ jdir << _args.journal_dir << "/" << jid.str();
+ jrnl_init_params::shared_ptr jpp(new jrnl_init_params(jid.str(), jdir.str(), jid.str()));
+ jrnl_instance::shared_ptr jip(new jrnl_instance(jpp));
+ _ji_list.push_back(jip);
+ }
+}
+
+void
+test_mgr::print_results(test_case::shared_ptr tcp, const bool summary)
+{
+ if (!summary)
+ std::cout << " === Results ===" << std::endl;
+
+// TODO - the reporting is broken when --repeat is used. The following commented-out
+// section was an attempt to fix it, but there are too many side-effects.
+// for (test_case::res_map_citr i=tcp->jmap_begin(); i!=tcp->jmap_end(); i++)
+// std::cout << (*i).second->str(summary, summary);
+// if (tcp->num_jrnls() > 1)
+ std::cout << tcp->average().str(false, summary);
+
+ if (!summary)
+ std::cout << std::endl;
+}
+
+// static instances
+volatile sig_atomic_t test_mgr::_signal = 0;
+volatile bool test_mgr::_abort = false;
+
+} // namespace jtt
+} // namespace mrg
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_mgr.h b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_mgr.h
new file mode 100644
index 0000000000..e608ac6280
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/jtt/test_mgr.h
@@ -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.
+ *
+ */
+
+#ifndef mrg_jtt_test_mgr_hpp
+#define mrg_jtt_test_mgr_hpp
+
+#include "args.h"
+#include <csignal>
+#include <cstdlib>
+#include "jrnl_instance.h"
+
+namespace mrg
+{
+namespace jtt
+{
+ class test_mgr
+ {
+ public:
+ typedef std::vector<jrnl_instance::shared_ptr> ji_list;
+ typedef ji_list::iterator ji_list_itr;
+ typedef ji_list::const_iterator ji_list_citr;
+
+ private:
+ ji_list _ji_list;
+ args& _args;
+ bool _err_flag;
+ ptrdiff_t (*_random_fn_ptr)(const ptrdiff_t i);
+ static volatile std::sig_atomic_t _signal;
+ static volatile bool _abort;
+
+ public:
+ test_mgr(args& args);
+ virtual ~test_mgr();
+ void run();
+ inline bool error() const { return _err_flag; }
+
+ static void signal_handler(int signal);
+
+ private:
+ static bool exists(std::string file_name);
+ void initialize_jrnls();
+ void print_results(test_case::shared_ptr tcp, const bool summary);
+ inline static ptrdiff_t random_fn(const ptrdiff_t i)
+ { return static_cast<ptrdiff_t>(1.0 * i * std::rand() / RAND_MAX); }
+ };
+
+} // namespace jtt
+} // namespace mrg
+
+#endif // ifndef mrg_jtt_test_mgr_hpp
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/prof b/qpid/cpp/src/tests/legacystore/jrnl/prof
new file mode 100755
index 0000000000..2abe7baa4a
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/prof
@@ -0,0 +1,32 @@
+#!/usr/bin/env 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.
+#
+
+mkdir -p profile
+opcontrol --setup --no-vmlinux --separate=library
+opcontrol --start
+# -- Do stuff here --
+./jtest wtests.csv 264
+# -- End of stuff --
+opcontrol --stop
+opcontrol --dump
+opcontrol --shutdown
+opreport -l ./jtest
+opannotate --source --output-dir=profile ./jtest
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/run-journal-tests b/qpid/cpp/src/tests/legacystore/jrnl/run-journal-tests
new file mode 100755
index 0000000000..e169e39c60
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/run-journal-tests
@@ -0,0 +1,47 @@
+#!/usr/bin/env 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 test x${TMP_DATA_DIR} == x; then
+ export TMP_DATA_DIR=/tmp
+fi
+fail=0
+num_jrnls=3
+
+# Run jtt using default test set
+echo
+echo "===== Mode 1: New journal instance, no recover ====="
+jtt/jtt --analyzer ../../tools/store_chk --jrnl-dir ${TMP_DATA_DIR} --csv jtt/jtt.csv --format-chk --num-jrnls ${num_jrnls} || fail=1
+rm -rf ${TMP_DATA_DIR}/test_0*
+echo
+echo "===== Mode 2: Re-use journal instance, no recover ====="
+jtt/jtt --analyzer ../../tools/store_chk --jrnl-dir ${TMP_DATA_DIR} --csv jtt/jtt.csv --reuse-instance --format-chk --num-jrnls ${num_jrnls} || fail=1
+rm -rf ${TMP_DATA_DIR}/test_0*
+echo
+echo "===== Mode 3: New journal instance, recover previous test journal ====="
+jtt/jtt --analyzer ../../tools/store_chk --jrnl-dir ${TMP_DATA_DIR} --csv jtt/jtt.csv --recover-mode --format-chk --num-jrnls ${num_jrnls} || fail=1
+rm -rf ${TMP_DATA_DIR}/test_0*
+echo
+echo "===== Mode 4: Re-use journal instance, recover previous test journal ====="
+jtt/jtt --analyzer ../../tools/store_chk --jrnl-dir ${TMP_DATA_DIR} --csv jtt/jtt.csv --reuse-instance --recover-mode --format-chk --num-jrnls ${num_jrnls} || fail=1
+rm -rf ${TMP_DATA_DIR}/test_0*
+echo
+
+exit $fail
diff --git a/qpid/cpp/src/tests/legacystore/jrnl/tests.ods b/qpid/cpp/src/tests/legacystore/jrnl/tests.ods
new file mode 100644
index 0000000000..d900374321
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/jrnl/tests.ods
Binary files differ
diff --git a/qpid/cpp/src/tests/legacystore/persistence.py b/qpid/cpp/src/tests/legacystore/persistence.py
new file mode 100644
index 0000000000..c4ab712f14
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/persistence.py
@@ -0,0 +1,574 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 sys, re, traceback, socket
+from getopt import getopt, GetoptError
+
+from qpid.connection import Connection
+from qpid.util import connect
+from qpid.datatypes import Message, RangedSet
+from qpid.queue import Empty
+from qpid.session import SessionException
+from qpid.testlib import TestBase010
+from time import sleep
+
+class PersistenceTest(TestBase010):
+
+ XA_RBROLLBACK = 1
+ XA_RBTIMEOUT = 2
+ XA_OK = 0
+
+ def createMessage(self, **kwargs):
+ session = self.session
+ dp = {}
+ dp['delivery_mode'] = 2
+ mp = {}
+ for k, v in kwargs.iteritems():
+ if k in ['routing_key', 'delivery_mode']: dp[k] = v
+ if k in ['message_id', 'correlation_id', 'application_headers']: mp[k] = v
+ args = []
+ args.append(session.delivery_properties(**dp))
+ if len(mp):
+ args.append(session.message_properties(**mp))
+ if kwargs.has_key('body'): args.append(kwargs['body'])
+ return Message(*args)
+
+ def phase1(self):
+ session = self.session
+
+ session.queue_declare(queue="queue-a", durable=True)
+ session.queue_declare(queue="queue-b", durable=True)
+ session.exchange_bind(queue="queue-a", exchange="amq.direct", binding_key="a")
+ session.exchange_bind(queue="queue-b", exchange="amq.direct", binding_key="b")
+
+ session.message_transfer(destination="amq.direct",
+ message=self.createMessage(routing_key="a", correlation_id="Msg0001", body="A_Message1"))
+ session.message_transfer(destination="amq.direct",
+ message=self.createMessage(routing_key="b", correlation_id="Msg0002", body="B_Message1"))
+
+# session.queue_declare(queue="lvq-test", durable=True, arguments={"qpid.last_value_queue":True})
+# session.message_transfer(message=self.createMessage(routing_key="lvq-test", application_headers={"qpid.LVQ_key":"B"}, body="B1"))
+# session.message_transfer(message=self.createMessage(routing_key="lvq-test", application_headers={"qpid.LVQ_key":"A"}, body="A1"))
+# session.message_transfer(message=self.createMessage(routing_key="lvq-test", application_headers={"qpid.LVQ_key":"A"}, body="A2"))
+# session.message_transfer(message=self.createMessage(routing_key="lvq-test", application_headers={"qpid.LVQ_key":"B"}, body="B2"))
+# session.message_transfer(message=self.createMessage(routing_key="lvq-test", application_headers={"qpid.LVQ_key":"B"}, body="B3"))
+# session.message_transfer(message=self.createMessage(routing_key="lvq-test", application_headers={"qpid.LVQ_key":"C"}, body="C1"))
+
+
+
+ def phase2(self):
+ session = self.session
+
+ #check queues exists
+ session.queue_declare(queue="queue-a", durable=True, passive=True)
+ session.queue_declare(queue="queue-b", durable=True, passive=True)
+
+ #check they are still bound to amq.direct correctly
+ responses = []
+ responses.append(session.exchange_bound(queue="queue-a", exchange="amq.direct", binding_key="a"))
+ responses.append(session.exchange_bound(queue="queue-b", exchange="amq.direct", binding_key="b"))
+ for r in responses:
+ self.assert_(not r.exchange_not_found)
+ self.assert_(not r.queue_not_found)
+ self.assert_(not r.key_not_matched)
+
+
+ #check expected messages are there
+ self.assertMessageOnQueue("queue-a", "Msg0001", "A_Message1")
+ self.assertMessageOnQueue("queue-b", "Msg0002", "B_Message1")
+
+ self.assertEmptyQueue("queue-a")
+ self.assertEmptyQueue("queue-b")
+
+ session.queue_declare(queue="queue-c", durable=True)
+
+ #send a message to a topic such that it reaches all queues
+ session.exchange_bind(queue="queue-a", exchange="amq.topic", binding_key="abc")
+ session.exchange_bind(queue="queue-b", exchange="amq.topic", binding_key="abc")
+ session.exchange_bind(queue="queue-c", exchange="amq.topic", binding_key="abc")
+
+ session.message_transfer(destination="amq.topic",
+ message=self.createMessage(routing_key="abc", correlation_id="Msg0003", body="AB_Message2"))
+
+# #check LVQ exists and has exepected messages:
+# session.queue_declare(queue="lvq-test", durable=True, passive=True)
+# session.message_subscribe(destination="lvq", queue="lvq-test")
+# lvq = session.incoming("lvq")
+# lvq.start()
+# accepted = RangedSet()
+# for m in ["A2", "B3", "C1"]:
+# msg = lvq.get(timeout=1)
+# self.assertEquals(m, msg.body)
+# accepted.add(msg.id)
+# try:
+# extra = lvq.get(timeout=1)
+# self.fail("lvq-test not empty, contains: " + extra.body)
+# except Empty: None
+# #publish some more messages while subscriber is active (no replacement):
+# session.message_transfer(message=self.createMessage(routing_key="lvq-test", application_headers={"qpid.LVQ_key":"C"}, body="C2"))
+# session.message_transfer(message=self.createMessage(routing_key="lvq-test", application_headers={"qpid.LVQ_key":"C"}, body="C3"))
+# session.message_transfer(message=self.createMessage(routing_key="lvq-test", application_headers={"qpid.LVQ_key":"A"}, body="A3"))
+# session.message_transfer(message=self.createMessage(routing_key="lvq-test", application_headers={"qpid.LVQ_key":"A"}, body="A4"))
+# session.message_transfer(message=self.createMessage(routing_key="lvq-test", application_headers={"qpid.LVQ_key":"C"}, body="C4"))
+# #check that accepting replaced messages is safe
+# session.message_accept(accepted)
+
+
+ def phase3(self):
+ session = self.session
+
+# #lvq recovery validation
+# session.queue_declare(queue="lvq-test", durable=True, passive=True)
+# session.message_subscribe(destination="lvq", queue="lvq-test")
+# lvq = session.incoming("lvq")
+# lvq.start()
+# accepted = RangedSet()
+# lvq.start()
+# for m in ["C4", "A4"]:
+# msg = lvq.get(timeout=1)
+# self.assertEquals(m, msg.body)
+# accepted.add(msg.id)
+# session.message_accept(accepted)
+# try:
+# extra = lvq.get(timeout=1)
+# self.fail("lvq-test not empty, contains: " + extra.body)
+# except Empty: None
+# session.message_cancel(destination="lvq")
+# session.queue_delete(queue="lvq-test")
+
+
+ #check queues exists
+ session.queue_declare(queue="queue-a", durable=True, passive=True)
+ session.queue_declare(queue="queue-b", durable=True, passive=True)
+ session.queue_declare(queue="queue-c", durable=True, passive=True)
+
+ session.tx_select()
+ #check expected messages are there
+ self.assertMessageOnQueue("queue-a", "Msg0003", "AB_Message2")
+ self.assertMessageOnQueue("queue-b", "Msg0003", "AB_Message2")
+ self.assertMessageOnQueue("queue-c", "Msg0003", "AB_Message2")
+
+ self.assertEmptyQueue("queue-a")
+ self.assertEmptyQueue("queue-b")
+ self.assertEmptyQueue("queue-c")
+
+ #note: default bindings must be restored for this to work
+ session.message_transfer(message=self.createMessage(
+ routing_key="queue-a", correlation_id="Msg0004", body="A_Message3"))
+ session.message_transfer(message=self.createMessage(
+ routing_key="queue-a", correlation_id="Msg0005", body="A_Message4"))
+ session.message_transfer(message=self.createMessage(
+ routing_key="queue-a", correlation_id="Msg0006", body="A_Message5"))
+
+ session.tx_commit()
+
+
+ #delete a queue
+ session.queue_delete(queue="queue-c")
+
+ session.message_subscribe(destination="ctag", queue="queue-a", accept_mode=0)
+ session.message_flow(destination="ctag", unit=0, value=0xFFFFFFFF)
+ session.message_flow(destination="ctag", unit=1, value=0xFFFFFFFF)
+ included = session.incoming("ctag")
+ msg1 = included.get(timeout=1)
+ self.assertExpectedContent(msg1, "Msg0004", "A_Message3")
+ msg2 = included.get(timeout=1)
+ self.assertExpectedContent(msg2, "Msg0005", "A_Message4")
+ msg3 = included.get(timeout=1)
+ self.assertExpectedContent(msg3, "Msg0006", "A_Message5")
+ self.ack(msg1, msg2, msg3)
+
+ session.message_transfer(destination="amq.direct", message=self.createMessage(
+ routing_key="queue-b", correlation_id="Msg0007", body="B_Message3"))
+
+ session.tx_rollback()
+
+
+ def phase4(self):
+ session = self.session
+
+ #check queues exists
+ session.queue_declare(queue="queue-a", durable=True, passive=True)
+ session.queue_declare(queue="queue-b", durable=True, passive=True)
+
+ self.assertMessageOnQueue("queue-a", "Msg0004", "A_Message3")
+ self.assertMessageOnQueue("queue-a", "Msg0005", "A_Message4")
+ self.assertMessageOnQueue("queue-a", "Msg0006", "A_Message5")
+
+ self.assertEmptyQueue("queue-a")
+ self.assertEmptyQueue("queue-b")
+
+ #check this queue doesn't exist
+ try:
+ session.queue_declare(queue="queue-c", durable=True, passive=True)
+ raise Exception("Expected queue-c to have been deleted")
+ except SessionException, e:
+ self.assertEquals(404, e.args[0].error_code)
+
+ def phase5(self):
+
+ session = self.session
+ queues = ["queue-a1", "queue-a2", "queue-b1", "queue-b2", "queue-c1", "queue-c2", "queue-d1", "queue-d2"]
+
+ for q in queues:
+ session.queue_declare(queue=q, durable=True)
+ session.queue_purge(queue=q)
+
+ session.message_transfer(message=self.createMessage(
+ routing_key="queue-a1", correlation_id="MsgA", body="MessageA"))
+ session.message_transfer(message=self.createMessage(
+ routing_key="queue-b1", correlation_id="MsgB", body="MessageB"))
+ session.message_transfer(message=self.createMessage(
+ routing_key="queue-c1", correlation_id="MsgC", body="MessageC"))
+ session.message_transfer(message=self.createMessage(
+ routing_key="queue-d1", correlation_id="MsgD", body="MessageD"))
+
+ session.dtx_select()
+ txa = self.xid('a')
+ txb = self.xid('b')
+ txc = self.xid('c')
+ txd = self.xid('d')
+
+ self.txswap("queue-a1", "queue-a2", txa)
+ self.txswap("queue-b1", "queue-b2", txb)
+ self.txswap("queue-c1", "queue-c2", txc)
+ self.txswap("queue-d1", "queue-d2", txd)
+
+ #no queue should have any messages accessible
+ for q in queues:
+ self.assertEqual(0, session.queue_query(queue=q).message_count, "Bad count for %s" % (q))
+
+ self.assertEqual(self.XA_OK, session.dtx_commit(xid=txa, one_phase=True).status)
+ self.assertEqual(self.XA_OK, session.dtx_rollback(xid=txb).status)
+ self.assertEqual(self.XA_OK, session.dtx_prepare(xid=txc).status)
+ self.assertEqual(self.XA_OK, session.dtx_prepare(xid=txd).status)
+
+ #further checks
+ not_empty = ["queue-a2", "queue-b1"]
+ for q in queues:
+ if q in not_empty:
+ self.assertEqual(1, session.queue_query(queue=q).message_count, "Bad count for %s" % (q))
+ else:
+ self.assertEqual(0, session.queue_query(queue=q).message_count, "Bad count for %s" % (q))
+
+
+ def phase6(self):
+ session = self.session
+
+ #check prepared transaction are reported correctly by recover
+ txc = self.xid('c')
+ txd = self.xid('d')
+
+ xids = session.dtx_recover().in_doubt
+ ids = [x.global_id for x in xids] #TODO: come up with nicer way to test these
+
+ if txc.global_id not in ids:
+ self.fail("Recovered xids not as expected. missing: %s" % (txc))
+ if txd.global_id not in ids:
+ self.fail("Recovered xids not as expected. missing: %s" % (txd))
+ self.assertEqual(2, len(xids))
+
+
+ queues = ["queue-a1", "queue-a2", "queue-b1", "queue-b2", "queue-c1", "queue-c2", "queue-d1", "queue-d2"]
+ not_empty = ["queue-a2", "queue-b1"]
+
+ #re-check
+ not_empty = ["queue-a2", "queue-b1"]
+ for q in queues:
+ if q in not_empty:
+ self.assertEqual(1, session.queue_query(queue=q).message_count, "Bad count for %s" % (q))
+ else:
+ self.assertEqual(0, session.queue_query(queue=q).message_count, "Bad count for %s" % (q))
+
+ #complete the prepared transactions
+ self.assertEqual(self.XA_OK, session.dtx_commit(xid=txc).status)
+ self.assertEqual(self.XA_OK, session.dtx_rollback(xid=txd).status)
+ not_empty.append("queue-c2")
+ not_empty.append("queue-d1")
+
+ for q in queues:
+ if q in not_empty:
+ self.assertEqual(1, session.queue_query(queue=q).message_count)
+ else:
+ self.assertEqual(0, session.queue_query(queue=q).message_count)
+
+ def phase7(self):
+ session = self.session
+ session.synchronous = False
+
+ # check xids from phase 6 are gone
+ txc = self.xid('c')
+ txd = self.xid('d')
+
+ xids = session.dtx_recover().in_doubt
+ ids = [x.global_id for x in xids] #TODO: come up with nicer way to test these
+
+ if txc.global_id in ids:
+ self.fail("Xid still present : %s" % (txc))
+ if txd.global_id in ids:
+ self.fail("Xid still present : %s" % (txc))
+ self.assertEqual(0, len(xids))
+
+ #test deletion of queue after publish
+ #create queue
+ session.queue_declare(queue = "q", auto_delete=True, durable=True)
+
+ #send message
+ for i in range(1, 10):
+ session.message_transfer(message=self.createMessage(routing_key = "q", body = "my-message"))
+
+ session.synchronous = True
+ #explicitly delete queue
+ session.queue_delete(queue = "q")
+
+ #test acking of message from auto-deleted queue
+ #create queue
+ session.queue_declare(queue = "q", auto_delete=True, durable=True)
+
+ #send message
+ session.message_transfer(message=self.createMessage(routing_key = "q", body = "my-message"))
+
+ #create consumer
+ session.message_subscribe(queue = "q", destination = "a", accept_mode=0, acquire_mode=0)
+ session.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "a")
+ session.message_flow(unit = 0, value = 10, destination = "a")
+ queue = session.incoming("a")
+
+ #consume the message, cancel subscription (triggering auto-delete), then ack it
+ msg = queue.get(timeout = 5)
+ session.message_cancel(destination = "a")
+ self.ack(msg)
+
+ #test implicit deletion of bindings when queue is deleted
+ session.queue_declare(queue = "durable-subscriber-queue", exclusive=True, durable=True)
+ session.exchange_bind(exchange="amq.topic", queue="durable-subscriber-queue", binding_key="xyz")
+ session.message_transfer(destination= "amq.topic", message=self.createMessage(routing_key = "xyz", body = "my-message"))
+ session.queue_delete(queue = "durable-subscriber-queue")
+
+ #test unbind:
+ #create a series of bindings to a queue
+ session.queue_declare(queue = "binding-test-queue", durable=True)
+ session.exchange_bind(exchange="amq.direct", queue="binding-test-queue", binding_key="abc")
+ session.exchange_bind(exchange="amq.direct", queue="binding-test-queue", binding_key="pqr")
+ session.exchange_bind(exchange="amq.direct", queue="binding-test-queue", binding_key="xyz")
+ session.exchange_bind(exchange="amq.match", queue="binding-test-queue", binding_key="a", arguments={"x-match":"all", "p":"a"})
+ session.exchange_bind(exchange="amq.match", queue="binding-test-queue", binding_key="b", arguments={"x-match":"all", "p":"b"})
+ session.exchange_bind(exchange="amq.match", queue="binding-test-queue", binding_key="c", arguments={"x-match":"all", "p":"c"})
+ #then restart broker...
+
+
+ def phase8(self):
+ session = self.session
+
+ #continue testing unbind:
+ #send messages to the queue via each of the bindings
+ for k in ["abc", "pqr", "xyz"]:
+ data = "first %s" % (k)
+ session.message_transfer(destination= "amq.direct", message=self.createMessage(routing_key=k, body=data))
+ for a in [{"p":"a"}, {"p":"b"}, {"p":"c"}]:
+ data = "first %s" % (a["p"])
+ session.message_transfer(destination="amq.match", message=self.createMessage(application_headers=a, body=data))
+ #unbind some bindings (using final 0-10 semantics)
+ session.exchange_unbind(exchange="amq.direct", queue="binding-test-queue", binding_key="pqr")
+ session.exchange_unbind(exchange="amq.match", queue="binding-test-queue", binding_key="b")
+ #send messages again
+ for k in ["abc", "pqr", "xyz"]:
+ data = "second %s" % (k)
+ session.message_transfer(destination= "amq.direct", message=self.createMessage(routing_key=k, body=data))
+ for a in [{"p":"a"}, {"p":"b"}, {"p":"c"}]:
+ data = "second %s" % (a["p"])
+ session.message_transfer(destination="amq.match", message=self.createMessage(application_headers=a, body=data))
+
+ #check that only the correct messages are received
+ expected = []
+ for k in ["abc", "pqr", "xyz"]:
+ expected.append("first %s" % (k))
+ for a in [{"p":"a"}, {"p":"b"}, {"p":"c"}]:
+ expected.append("first %s" % (a["p"]))
+ for k in ["abc", "xyz"]:
+ expected.append("second %s" % (k))
+ for a in [{"p":"a"}, {"p":"c"}]:
+ expected.append("second %s" % (a["p"]))
+
+ session.message_subscribe(queue = "binding-test-queue", destination = "binding-test")
+ session.message_flow(unit = 1, value = 0xFFFFFFFF, destination = "binding-test")
+ session.message_flow(unit = 0, value = 10, destination = "binding-test")
+ queue = session.incoming("binding-test")
+
+ while len(expected):
+ msg = queue.get(timeout=1)
+ if msg.body not in expected:
+ self.fail("Missing message: %s" % msg.body)
+ expected.remove(msg.body)
+ try:
+ msg = queue.get(timeout=1)
+ self.fail("Got extra message: %s" % msg.body)
+ except Empty: pass
+
+
+
+ session.queue_declare(queue = "durable-subscriber-queue", exclusive=True, durable=True)
+ session.exchange_bind(exchange="amq.topic", queue="durable-subscriber-queue", binding_key="xyz")
+ session.message_transfer(destination= "amq.topic", message=self.createMessage(routing_key = "xyz", body = "my-message"))
+ session.queue_delete(queue = "durable-subscriber-queue")
+
+
+ def xid(self, txid, branchqual = ''):
+ return self.session.xid(format=0, global_id=txid, branch_id=branchqual)
+
+ def txswap(self, src, dest, tx):
+ self.assertEqual(self.XA_OK, self.session.dtx_start(xid=tx).status)
+ self.session.message_subscribe(destination="temp-swap", queue=src, accept_mode=0)
+ self.session.message_flow(destination="temp-swap", unit=0, value=1)
+ self.session.message_flow(destination="temp-swap", unit=1, value=0xFFFFFFFF)
+ msg = self.session.incoming("temp-swap").get(timeout=1)
+ self.session.message_cancel(destination="temp-swap")
+ self.session.message_transfer(message=self.createMessage(routing_key=dest, correlation_id=self.getProperty(msg, 'correlation_id'),
+ body=msg.body))
+ self.ack(msg)
+ self.assertEqual(self.XA_OK, self.session.dtx_end(xid=tx).status)
+
+ def assertEmptyQueue(self, name):
+ self.assertEqual(0, self.session.queue_query(queue=name).message_count)
+
+ def assertConnectionException(self, expectedCode, message):
+ self.assertEqual("connection", message.method.klass.name)
+ self.assertEqual("close", message.method.name)
+ self.assertEqual(expectedCode, message.reply_code)
+
+ def assertExpectedMethod(self, reply, klass, method):
+ self.assertEqual(klass, reply.method.klass.name)
+ self.assertEqual(method, reply.method.name)
+
+ def assertExpectedContent(self, msg, id, body):
+ self.assertEqual(id, self.getProperty(msg, 'correlation_id'))
+ self.assertEqual(body, msg.body)
+ return msg
+
+ def getProperty(self, msg, name):
+ for h in msg.headers:
+ if hasattr(h, name): return getattr(h, name)
+ return None
+
+ def ack(self, *msgs):
+ session = self.session
+ set = RangedSet()
+ for m in msgs:
+ set.add(m.id)
+ #TODO: tidy up completion
+ session.receiver._completed.add(m.id)
+ session.message_accept(set)
+ session.channel.session_completed(session.receiver._completed)
+
+ def assertExpectedGetResult(self, id, body):
+ return self.assertExpectedContent(session.incoming("incoming-gets").get(timeout=1), id, body)
+
+ def assertEqual(self, expected, actual, msg=''):
+ if expected != actual: raise Exception("%s expected: %s actual: %s" % (msg, expected, actual))
+
+ def assertMessageOnQueue(self, queue, id, body):
+ self.session.message_subscribe(destination="incoming-gets", queue=queue, accept_mode=0)
+ self.session.message_flow(destination="incoming-gets", unit=0, value=1)
+ self.session.message_flow(destination="incoming-gets", unit=1, value=0xFFFFFFFF)
+ msg = self.session.incoming("incoming-gets").get(timeout=1)
+ self.assertExpectedContent(msg, id, body)
+ self.ack(msg)
+ self.session.message_cancel(destination="incoming-gets")
+
+
+ def __init__(self):
+ TestBase010.__init__(self, "run")
+ self.setBroker("localhost")
+ self.errata = []
+
+ def connect(self):
+ """ Connects to the broker """
+ self.conn = Connection(connect(self.host, self.port))
+ self.conn.start(timeout=10)
+ self.session = self.conn.session("test-session", timeout=10)
+
+ def run(self, args=sys.argv[1:]):
+ try:
+ opts, extra = getopt(args, "r:s:e:b:p:h", ["retry=", "spec=", "errata=", "broker=", "phase=", "help"])
+ except GetoptError, e:
+ self._die(str(e))
+ phase = 0
+ retry = 0;
+ for opt, value in opts:
+ if opt in ("-h", "--help"): self._die()
+ if opt in ("-s", "--spec"): self.spec = value
+ if opt in ("-e", "--errata"): self.errata.append(value)
+ if opt in ("-b", "--broker"): self.setBroker(value)
+ if opt in ("-p", "--phase"): phase = int(value)
+ if opt in ("-r", "--retry"): retry = int(value)
+
+ if not phase: self._die("please specify the phase to run")
+ phase = "phase%d" % phase
+ self.connect()
+
+ try:
+ getattr(self, phase)()
+ print phase, "succeeded"
+ res = True;
+ except Exception, e:
+ print phase, "failed: ", e
+ traceback.print_exc()
+ res = False
+
+
+ if not self.session.error(): self.session.close(timeout=10)
+ self.conn.close(timeout=10)
+
+ # Crude fix to wait for thread in client to exit after return from session_close()
+ # Reduces occurrences of "Unhandled exception in thread" messages after each test
+ import time
+ time.sleep(1)
+
+ return res
+
+
+ def setBroker(self, broker):
+ rex = re.compile(r"""
+ # [ <user> [ / <password> ] @] <host> [ :<port> ]
+ ^ (?: ([^/]*) (?: / ([^@]*) )? @)? ([^:]+) (?: :([0-9]+))?$""", re.X)
+ match = rex.match(broker)
+ if not match: self._die("'%s' is not a valid broker" % (broker))
+ self.user, self.password, self.host, self.port = match.groups()
+ self.port = int(default(self.port, 5672))
+ self.user = default(self.user, "guest")
+ self.password = default(self.password, "guest")
+
+ def _die(self, message = None):
+ if message: print message
+ print """
+Options:
+ -h/--help : this message
+ -s/--spec <spec.xml> : file containing amqp XML spec
+ -p/--phase : test phase to run
+ -b/--broker [<user>[/<password>]@]<host>[:<port>] : broker to connect to
+ """
+ sys.exit(1)
+
+def default(value, default):
+ if (value == None): return default
+ else: return value
+
+if __name__ == "__main__":
+ test = PersistenceTest()
+ if not test.run(): sys.exit(1)
diff --git a/qpid/cpp/src/tests/legacystore/python_tests/__init__.py b/qpid/cpp/src/tests/legacystore/python_tests/__init__.py
new file mode 100644
index 0000000000..ebb9da8670
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/python_tests/__init__.py
@@ -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.
+#
+
+# Do not delete - marks this directory as a python package.
+
+from client_persistence import *
+from resize import *
+
diff --git a/qpid/cpp/src/tests/legacystore/python_tests/client_persistence.py b/qpid/cpp/src/tests/legacystore/python_tests/client_persistence.py
new file mode 100644
index 0000000000..37c12601be
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/python_tests/client_persistence.py
@@ -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.
+#
+
+import os
+
+from brokertest import EXPECT_EXIT_OK
+from store_test import StoreTest, Qmf, store_args
+from qpid.messaging import *
+
+import qpid.messaging, brokertest
+brokertest.qm = qpid.messaging # FIXME aconway 2014-04-04: Tests fail with SWIG client.
+
+class ExchangeQueueTests(StoreTest):
+ """
+ Simple tests of the broker exchange and queue types
+ """
+
+ def test_direct_exchange(self):
+ """Test Direct exchange."""
+ broker = self.broker(store_args(), name="test_direct_exchange", expect=EXPECT_EXIT_OK)
+ msg1 = Message("A_Message1", durable=True, correlation_id="Msg0001")
+ msg2 = Message("B_Message1", durable=True, correlation_id="Msg0002")
+ broker.send_message("a", msg1)
+ broker.send_message("b", msg2)
+ broker.terminate()
+
+ broker = self.broker(store_args(), name="test_direct_exchange")
+ self.check_message(broker, "a", msg1, True)
+ self.check_message(broker, "b", msg2, True)
+
+ def test_topic_exchange(self):
+ """Test Topic exchange."""
+ broker = self.broker(store_args(), name="test_topic_exchange", expect=EXPECT_EXIT_OK)
+ ssn = broker.connect().session()
+ snd1 = ssn.sender("abc/key1; {create:always, node:{type:topic, durable:True}}")
+ snd2 = ssn.sender("abc/key2; {create:always, node:{type:topic, durable:True}}")
+ ssn.receiver("a; {create:always, link:{x-bindings:[{exchange:abc, key:key1}]}, node:{durable:True}}")
+ ssn.receiver("b; {create:always, link:{x-bindings:[{exchange:abc, key:key1}]}, node:{durable:True}}")
+ ssn.receiver("c; {create:always, link:{x-bindings:[{exchange:abc, key:key1}, "
+ "{exchange:abc, key: key2}]}, node:{durable:True}}")
+ ssn.receiver("d; {create:always, link:{x-bindings:[{exchange:abc, key:key2}]}, node:{durable:True}}")
+ ssn.receiver("e; {create:always, link:{x-bindings:[{exchange:abc, key:key2}]}, node:{durable:True}}")
+ msg1 = Message("Message1", durable=True, correlation_id="Msg0003")
+ snd1.send(msg1)
+ msg2 = Message("Message2", durable=True, correlation_id="Msg0004")
+ snd2.send(msg2)
+ broker.terminate()
+
+ broker = self.broker(store_args(), name="test_topic_exchange")
+ self.check_message(broker, "a", msg1, True)
+ self.check_message(broker, "b", msg1, True)
+ self.check_messages(broker, "c", [msg1, msg2], True)
+ self.check_message(broker, "d", msg2, True)
+ self.check_message(broker, "e", msg2, True)
+
+
+ def test_legacy_lvq(self):
+ """Test legacy LVQ."""
+ broker = self.broker(store_args(), name="test_lvq", expect=EXPECT_EXIT_OK)
+ ma1 = Message("A1", durable=True, correlation_id="Msg0005", properties={"qpid.LVQ_key":"A"})
+ ma2 = Message("A2", durable=True, correlation_id="Msg0006", properties={"qpid.LVQ_key":"A"})
+ mb1 = Message("B1", durable=True, correlation_id="Msg0007", properties={"qpid.LVQ_key":"B"})
+ mb2 = Message("B2", durable=True, correlation_id="Msg0008", properties={"qpid.LVQ_key":"B"})
+ mb3 = Message("B3", durable=True, correlation_id="Msg0009", properties={"qpid.LVQ_key":"B"})
+ mc1 = Message("C1", durable=True, correlation_id="Msg0010", properties={"qpid.LVQ_key":"C"})
+ broker.send_messages("lvq-test", [mb1, ma1, ma2, mb2, mb3, mc1],
+ xprops="arguments:{\"qpid.last_value_queue\":True}")
+ broker.terminate()
+
+ broker = self.broker(store_args(), name="test_lvq", expect=EXPECT_EXIT_OK)
+ ssn = self.check_messages(broker, "lvq-test", [ma2, mb3, mc1], empty=True, ack=False)
+ # Add more messages while subscriber is active (no replacement):
+ ma3 = Message("A3", durable=True, correlation_id="Msg0011", properties={"qpid.LVQ_key":"A"})
+ ma4 = Message("A4", durable=True, correlation_id="Msg0012", properties={"qpid.LVQ_key":"A"})
+ mc2 = Message("C2", durable=True, correlation_id="Msg0013", properties={"qpid.LVQ_key":"C"})
+ mc3 = Message("C3", durable=True, correlation_id="Msg0014", properties={"qpid.LVQ_key":"C"})
+ mc4 = Message("C4", durable=True, correlation_id="Msg0015", properties={"qpid.LVQ_key":"C"})
+ broker.send_messages("lvq-test", [mc2, mc3, ma3, ma4, mc4], session=ssn)
+ ssn.acknowledge()
+ broker.terminate()
+
+ broker = self.broker(store_args(), name="test_lvq")
+ self.check_messages(broker, "lvq-test", [ma4, mc4], True)
+
+
+ def test_fanout_exchange(self):
+ """Test Fanout Exchange"""
+ broker = self.broker(store_args(), name="test_fanout_exchange", expect=EXPECT_EXIT_OK)
+ ssn = broker.connect().session()
+ snd = ssn.sender("TestFanoutExchange; {create: always, node: {type: topic, x-declare: {type: fanout}}}")
+ ssn.receiver("TestFanoutExchange; {link: {name: \"q1\", durable: True, reliability:at-least-once}}")
+ ssn.receiver("TestFanoutExchange; {link: {name: \"q2\", durable: True, reliability:at-least-once}}")
+ ssn.receiver("TestFanoutExchange; {link: {name: \"q3\", durable: True, reliability:at-least-once}}")
+ msg1 = Message("Msg1", durable=True, correlation_id="Msg0001")
+ snd.send(msg1)
+ msg2 = Message("Msg2", durable=True, correlation_id="Msg0002")
+ snd.send(msg2)
+ broker.terminate()
+
+ broker = self.broker(store_args(), name="test_fanout_exchange")
+ self.check_messages(broker, "q1", [msg1, msg2], True)
+ self.check_messages(broker, "q2", [msg1, msg2], True)
+ self.check_messages(broker, "q3", [msg1, msg2], True)
+
+
+ def test_message_reject(self):
+ broker = self.broker(store_args(), name="test_message_reject", expect=EXPECT_EXIT_OK)
+ ssn = broker.connect().session()
+ snd = ssn.sender("tmr; {create:always, node:{type:queue, durable:True}}")
+ rcv = ssn.receiver("tmr; {create:always, node:{type:queue, durable:True}}")
+ m1 = Message("test_message_reject", durable=True, correlation_id="Msg0001")
+ snd.send(m1)
+ m2 = rcv.fetch()
+ ssn.acknowledge(message=m2, disposition=Disposition(REJECTED))
+ broker.terminate()
+
+ broker = self.broker(store_args(), name="test_message_reject")
+ qmf = Qmf(broker)
+ assert qmf.queue_message_count("tmr") == 0
+
+
+ def test_route(self):
+ """ Test the recovery of a route (link and bridge objects."""
+ broker = self.broker(store_args(), name="test_route", expect=EXPECT_EXIT_OK)
+ qmf = Qmf(broker)
+ qmf_broker_obj = qmf.get_objects("broker")[0]
+
+ # create a "link"
+ link_args = {"host":"a.fake.host.com", "port":9999, "durable":True,
+ "authMechanism":"PLAIN", "username":"guest", "password":"guest",
+ "transport":"tcp"}
+ result = qmf_broker_obj.create("link", "test-link", link_args, False)
+ self.assertEqual(result.status, 0, result)
+ link = qmf.get_objects("link")[0]
+
+ # create bridge
+ bridge_args = {"link":"test-link", "src":"amq.direct", "dest":"amq.fanout",
+ "key":"my-key", "durable":True}
+ result = qmf_broker_obj.create("bridge", "test-bridge", bridge_args, False);
+ self.assertEqual(result.status, 0, result)
+ bridge = qmf.get_objects("bridge")[0]
+
+ broker.terminate()
+
+ # recover the link and bridge
+ broker = self.broker(store_args(), name="test_route")
+ qmf = Qmf(broker)
+ qmf_broker_obj = qmf.get_objects("broker")[0]
+ self.assertEqual(len(qmf.get_objects("link")), 1)
+ self.assertEqual(len(qmf.get_objects("bridge")), 1)
+
+
+
+class AlternateExchangePropertyTests(StoreTest):
+ """
+ Test the persistence of the Alternate Exchange property for exchanges and queues.
+ """
+
+ def test_exchange(self):
+ """Exchange alternate exchange property persistence test"""
+ broker = self.broker(store_args(), name="test_exchange", expect=EXPECT_EXIT_OK)
+ qmf = Qmf(broker)
+ qmf.add_exchange("altExch", "direct", durable=True) # Serves as alternate exchange instance
+ qmf.add_exchange("testExch", "direct", durable=True, alt_exchange_name="altExch")
+ qmf.close()
+ broker.terminate()
+
+ broker = self.broker(store_args(), name="test_exchange")
+ qmf = Qmf(broker)
+ try:
+ qmf.add_exchange("altExch", "direct", passive=True)
+ except Exception, error:
+ self.fail("Alternate exchange (\"altExch\") instance not recovered: %s" % error)
+ try:
+ qmf.add_exchange("testExch", "direct", passive=True)
+ except Exception, error:
+ self.fail("Test exchange (\"testExch\") instance not recovered: %s" % error)
+ self.assertTrue(qmf.query_exchange("testExch", alt_exchange_name = "altExch"),
+ "Alternate exchange property not found or is incorrect on exchange \"testExch\".")
+ qmf.close()
+
+ def test_queue(self):
+ """Queue alternate exchange property persistexchangeNamece test"""
+ broker = self.broker(store_args(), name="test_queue", expect=EXPECT_EXIT_OK)
+ qmf = Qmf(broker)
+ qmf.add_exchange("altExch", "direct", durable=True) # Serves as alternate exchange instance
+ qmf.add_queue("testQueue", durable=True, alt_exchange_name="altExch")
+ qmf.close()
+ broker.terminate()
+
+ broker = self.broker(store_args(), name="test_queue")
+ qmf = Qmf(broker)
+ try:
+ qmf.add_exchange("altExch", "direct", passive=True)
+ except Exception, error:
+ self.fail("Alternate exchange (\"altExch\") instance not recovered: %s" % error)
+ try:
+ qmf.add_queue("testQueue", passive=True)
+ except Exception, error:
+ self.fail("Test queue (\"testQueue\") instance not recovered: %s" % error)
+ self.assertTrue(qmf.query_queue("testQueue", alt_exchange_name = "altExch"),
+ "Alternate exchange property not found or is incorrect on queue \"testQueue\".")
+ qmf.close()
+
+
+class RedeliveredTests(StoreTest):
+ """
+ Test the behavior of the redelivered flag in the context of persistence
+ """
+
+ def test_broker_recovery(self):
+ """Test that the redelivered flag is set on messages after recovery of broker"""
+ broker = self.broker(store_args(), name="test_broker_recovery", expect=EXPECT_EXIT_OK)
+ msg_content = "xyz"*100
+ msg = Message(msg_content, durable=True)
+ broker.send_message("testQueue", msg)
+ broker.terminate()
+
+ broker = self.broker(store_args(), name="test_broker_recovery")
+ rcv_msg = broker.get_message("testQueue")
+ self.assertEqual(msg_content, rcv_msg.content)
+ self.assertTrue(rcv_msg.redelivered)
+
diff --git a/qpid/cpp/src/tests/legacystore/python_tests/resize.py b/qpid/cpp/src/tests/legacystore/python_tests/resize.py
new file mode 100644
index 0000000000..e719b755da
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/python_tests/resize.py
@@ -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.
+#
+
+import glob
+import os
+import subprocess
+
+from brokertest import EXPECT_EXIT_OK
+from qpid.datatypes import uuid4
+from store_test import StoreTest, store_args
+from qpid.messaging import Message
+
+import qpid.messaging, brokertest
+brokertest.qm = qpid.messaging # TODO aconway 2014-04-04: Tests fail with SWIG client.
+
+class ResizeTest(StoreTest):
+
+ resize_tool = os.getenv("QPID_STORE_RESIZE_TOOL", "qpid-store-resize")
+ print resize_tool
+ def _resize_store(self, store_dir, queue_name, resize_num_files, resize_file_size, exp_fail):
+ for f in glob.glob(os.path.join(store_dir, "*")):
+ final_store_dir = os.path.join(f, queue_name)
+ p = subprocess.Popen([self.resize_tool, final_store_dir, "--num-jfiles", str(resize_num_files),
+ "--jfile-size-pgs", str(resize_file_size), "--quiet"], stdout = subprocess.PIPE,
+ stderr = subprocess.STDOUT)
+ res = p.wait()
+ err_found = False
+ try:
+ for l in p.stdout:
+ if exp_fail:
+ err_found = True
+ print "[Expected error]:",
+ print l,
+ finally:
+ p.stdout.close()
+ return res
+
+ def _resize_test(self, queue_name, num_msgs, msg_size, resize_num_files, resize_file_size, init_num_files = 8,
+ init_file_size = 24, exp_fail = False, wait_time = None):
+ # Using a sender will force the creation of an empty persistent queue which is needed for some tests
+ broker = self.broker(store_args(), name="broker", expect=EXPECT_EXIT_OK, wait=wait_time)
+ ssn = broker.connect().session()
+ snd = ssn.sender("%s; {create:always, node:{durable:True}}" % queue_name)
+
+ msgs = []
+ for index in range(0, num_msgs):
+ msg = Message(self.make_message(index, msg_size), durable=True, id=uuid4(), correlation_id="msg-%04d"%index)
+ msgs.append(msg)
+ snd.send(msg)
+ broker.terminate()
+
+ res = self._resize_store(os.path.join(self.dir, "broker", "rhm", "jrnl"), queue_name, resize_num_files,
+ resize_file_size, exp_fail)
+ if res != 0:
+ if exp_fail:
+ return
+ self.fail("ERROR: Resize operation failed with return code %d" % res)
+ elif exp_fail:
+ self.fail("ERROR: Resize operation succeeded, but a failure was expected")
+
+ broker = self.broker(store_args(), name="broker")
+ self.check_messages(broker, queue_name, msgs, True)
+
+ # TODO: Check the physical files to check number and size are as expected.
+
+
+class SimpleTest(ResizeTest):
+ """
+ Simple tests of the resize utility for resizing a journal to larger and smaller sizes.
+ """
+
+ def test_empty_store_same(self):
+ self._resize_test(queue_name = "empty_store_same",
+ num_msgs = 0, msg_size = 0,
+ init_num_files = 8, init_file_size = 24,
+ resize_num_files = 8, resize_file_size = 24)
+
+ def test_empty_store_up(self):
+ self._resize_test(queue_name = "empty_store_up",
+ num_msgs = 0, msg_size = 0,
+ init_num_files = 8, init_file_size = 24,
+ resize_num_files = 16, resize_file_size = 48)
+
+ def test_empty_store_down(self):
+ self._resize_test(queue_name = "empty_store_down",
+ num_msgs = 0, msg_size = 0,
+ init_num_files = 8, init_file_size = 24,
+ resize_num_files = 6, resize_file_size = 12)
+
+# TODO: Put into long tests, make sure there is > 128GB free disk space
+# def test_empty_store_max(self):
+# self._resize_test(queue_name = "empty_store_max",
+# num_msgs = 0, msg_size = 0,
+# init_num_files = 8, init_file_size = 24,
+# resize_num_files = 64, resize_file_size = 32768,
+# wait_time = 120)
+
+ def test_empty_store_min(self):
+ self._resize_test(queue_name = "empty_store_min",
+ num_msgs = 0, msg_size = 0,
+ init_num_files = 8, init_file_size = 24,
+ resize_num_files = 4, resize_file_size = 1)
+
+ def test_basic_up(self):
+ self._resize_test(queue_name = "basic_up",
+ num_msgs = 100, msg_size = 10000,
+ init_num_files = 8, init_file_size = 24,
+ resize_num_files = 16, resize_file_size = 48)
+
+ def test_basic_down(self):
+ self._resize_test(queue_name = "basic_down",
+ num_msgs = 100, msg_size = 10000,
+ init_num_files = 8, init_file_size = 24,
+ resize_num_files = 4, resize_file_size = 15)
+
+ def test_basic_low(self):
+ self._resize_test(queue_name = "basic_low",
+ num_msgs = 100, msg_size = 10000,
+ init_num_files = 8, init_file_size = 24,
+ resize_num_files = 4, resize_file_size = 4,
+ exp_fail = True)
+
+ def test_basic_under(self):
+ self._resize_test(queue_name = "basic_under",
+ num_msgs = 100, msg_size = 10000,
+ init_num_files = 8, init_file_size = 24,
+ resize_num_files = 4, resize_file_size = 3,
+ exp_fail = True)
+
+ def test_very_large_msg_up(self):
+ self._resize_test(queue_name = "very_large_msg_up",
+ num_msgs = 4, msg_size = 2000000,
+ init_num_files = 8, init_file_size = 24,
+ resize_num_files = 16, resize_file_size = 48)
+
+ def test_very_large_msg_down(self):
+ self._resize_test(queue_name = "very_large_msg_down",
+ num_msgs = 4, msg_size = 2000000,
+ init_num_files = 16, init_file_size = 64,
+ resize_num_files = 16, resize_file_size = 48)
+
+ def test_very_large_msg_low(self):
+ self._resize_test(queue_name = "very_large_msg_low",
+ num_msgs = 4, msg_size = 2000000,
+ init_num_files = 8, init_file_size = 24,
+ resize_num_files = 7, resize_file_size = 20,
+ exp_fail = True)
+
+ def test_very_large_msg_under(self):
+ self._resize_test(queue_name = "very_large_msg_under",
+ num_msgs = 4, msg_size = 2000000,
+ init_num_files = 8, init_file_size = 24,
+ resize_num_files = 6, resize_file_size = 8,
+ exp_fail = True)
diff --git a/qpid/cpp/src/tests/legacystore/python_tests/store_test.py b/qpid/cpp/src/tests/legacystore/python_tests/store_test.py
new file mode 100644
index 0000000000..cc846aefd4
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/python_tests/store_test.py
@@ -0,0 +1,417 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 re
+from brokertest import BrokerTest
+from qpid.messaging import Empty
+from qmf.console import Session
+
+import qpid.messaging, brokertest
+brokertest.qm = qpid.messaging # TODO aconway 2014-04-04: Tests fail with SWIG client.
+
+
+def store_args(store_dir = None):
+ """Return the broker args necessary to load the async store"""
+ assert BrokerTest.store_lib
+ if store_dir == None:
+ return []
+ return ["--store-dir", store_dir]
+
+class Qmf:
+ """
+ QMF functions not yet available in the new QMF API. Remove this and replace with new API when it becomes available.
+ """
+ def __init__(self, broker):
+ self.__session = Session()
+ self.__broker = self.__session.addBroker("amqp://localhost:%d"%broker.port())
+
+ def add_exchange(self, exchange_name, exchange_type, alt_exchange_name=None, passive=False, durable=False,
+ arguments = None):
+ """Add a new exchange"""
+ amqp_session = self.__broker.getAmqpSession()
+ if arguments == None:
+ arguments = {}
+ if alt_exchange_name:
+ amqp_session.exchange_declare(exchange=exchange_name, type=exchange_type,
+ alternate_exchange=alt_exchange_name, passive=passive, durable=durable,
+ arguments=arguments)
+ else:
+ amqp_session.exchange_declare(exchange=exchange_name, type=exchange_type, passive=passive, durable=durable,
+ arguments=arguments)
+
+ def add_queue(self, queue_name, alt_exchange_name=None, passive=False, durable=False, arguments = None):
+ """Add a new queue"""
+ amqp_session = self.__broker.getAmqpSession()
+ if arguments == None:
+ arguments = {}
+ if alt_exchange_name:
+ amqp_session.queue_declare(queue_name, alternate_exchange=alt_exchange_name, passive=passive,
+ durable=durable, arguments=arguments)
+ else:
+ amqp_session.queue_declare(queue_name, passive=passive, durable=durable, arguments=arguments)
+
+ def delete_queue(self, queue_name):
+ """Delete an existing queue"""
+ amqp_session = self.__broker.getAmqpSession()
+ amqp_session.queue_delete(queue_name)
+
+ def _query(self, name, _class, package, alt_exchange_name=None):
+ """Qmf query function which can optionally look for the presence of an alternate exchange name"""
+ try:
+ obj_list = self.__session.getObjects(_class=_class, _package=package)
+ found = False
+ for obj in obj_list:
+ if obj.name == name:
+ found = True
+ if alt_exchange_name != None:
+ alt_exch_list = self.__session.getObjects(_objectId=obj.altExchange)
+ if len(alt_exch_list) == 0 or alt_exch_list[0].name != alt_exchange_name:
+ return False
+ break
+ return found
+ except Exception:
+ return False
+
+
+ def query_exchange(self, exchange_name, alt_exchange_name=None):
+ """Test for the presence of an exchange, and optionally whether it has an alternate exchange set to a known
+ value."""
+ return self._query(exchange_name, "exchange", "org.apache.qpid.broker", alt_exchange_name)
+
+ def query_queue(self, queue_name, alt_exchange_name=None):
+ """Test for the presence of an exchange, and optionally whether it has an alternate exchange set to a known
+ value."""
+ return self._query(queue_name, "queue", "org.apache.qpid.broker", alt_exchange_name)
+
+ def queue_message_count(self, queue_name):
+ """Query the number of messages on a queue"""
+ queue_list = self.__session.getObjects(_class="queue", _name=queue_name)
+ if len(queue_list):
+ return queue_list[0].msgDepth
+
+ def queue_empty(self, queue_name):
+ """Check if a queue is empty (has no messages waiting)"""
+ return self.queue_message_count(queue_name) == 0
+
+ def get_objects(self, target_class, target_package="org.apache.qpid.broker"):
+ return self.__session.getObjects(_class=target_class, _package=target_package)
+
+
+ def close(self):
+ self.__session.delBroker(self.__broker)
+ self.__session = None
+
+
+class StoreTest(BrokerTest):
+ """
+ This subclass of BrokerTest adds some convenience test/check functions
+ """
+
+ def _chk_empty(self, queue, receiver):
+ """Check if a queue is empty (has no more messages)"""
+ try:
+ msg = receiver.fetch(timeout=0)
+ self.assert_(False, "Queue \"%s\" not empty: found message: %s" % (queue, msg))
+ except Empty:
+ pass
+
+ @staticmethod
+ def make_message(msg_count, msg_size):
+ """Make message content. Format: 'abcdef....' followed by 'msg-NNNN', where NNNN is the message count"""
+ msg = "msg-%04d" % msg_count
+ msg_len = len(msg)
+ buff = ""
+ if msg_size != None and msg_size > msg_len:
+ for index in range(0, msg_size - msg_len):
+ if index == msg_size - msg_len - 1:
+ buff += "-"
+ else:
+ buff += chr(ord('a') + (index % 26))
+ return buff + msg
+
+ # Functions for formatting address strings
+
+ @staticmethod
+ def _fmt_csv(string_list, list_braces = None):
+ """Format a list using comma-separation. Braces are optionally added."""
+ if len(string_list) == 0:
+ return ""
+ first = True
+ str_ = ""
+ if list_braces != None:
+ str_ += list_braces[0]
+ for string in string_list:
+ if string != None:
+ if first:
+ first = False
+ else:
+ str_ += ", "
+ str_ += string
+ if list_braces != None:
+ str_ += list_braces[1]
+ return str_
+
+ def _fmt_map(self, string_list):
+ """Format a map {l1, l2, l3, ...} from a string list. Each item in the list must be a formatted map
+ element('key:val')."""
+ return self._fmt_csv(string_list, list_braces="{}")
+
+ def _fmt_list(self, string_list):
+ """Format a list [l1, l2, l3, ...] from a string list."""
+ return self._fmt_csv(string_list, list_braces="[]")
+
+ def addr_fmt(self, node_name, **kwargs):
+ """Generic AMQP to new address formatter. Takes common (but not all) AMQP options and formats an address
+ string."""
+ # Get keyword args
+ node_subject = kwargs.get("node_subject")
+ create_policy = kwargs.get("create_policy")
+ delete_policy = kwargs.get("delete_policy")
+ assert_policy = kwargs.get("assert_policy")
+ mode = kwargs.get("mode")
+ link = kwargs.get("link", False)
+ link_name = kwargs.get("link_name")
+ node_type = kwargs.get("node_type")
+ durable = kwargs.get("durable", False)
+ link_reliability = kwargs.get("link_reliability")
+ x_declare_list = kwargs.get("x_declare_list", [])
+ x_bindings_list = kwargs.get("x_bindings_list", [])
+ x_subscribe_list = kwargs.get("x_subscribe_list", [])
+
+ node_flag = not link and (node_type != None or durable or len(x_declare_list) > 0 or len(x_bindings_list) > 0)
+ link_flag = link and (link_name != None or durable or link_reliability != None or len(x_declare_list) > 0 or
+ len(x_bindings_list) > 0 or len(x_subscribe_list) > 0)
+ assert not (node_flag and link_flag)
+
+ opt_str_list = []
+ if create_policy != None:
+ opt_str_list.append("create: %s" % create_policy)
+ if delete_policy != None:
+ opt_str_list.append("delete: %s" % delete_policy)
+ if assert_policy != None:
+ opt_str_list.append("assert: %s" % assert_policy)
+ if mode != None:
+ opt_str_list.append("mode: %s" % mode)
+ if node_flag or link_flag:
+ node_str_list = []
+ if link_name != None:
+ node_str_list.append("name: \"%s\"" % link_name)
+ if node_type != None:
+ node_str_list.append("type: %s" % node_type)
+ if durable:
+ node_str_list.append("durable: True")
+ if link_reliability != None:
+ node_str_list.append("reliability: %s" % link_reliability)
+ if len(x_declare_list) > 0:
+ node_str_list.append("x-declare: %s" % self._fmt_map(x_declare_list))
+ if len(x_bindings_list) > 0:
+ node_str_list.append("x-bindings: %s" % self._fmt_list(x_bindings_list))
+ if len(x_subscribe_list) > 0:
+ node_str_list.append("x-subscribe: %s" % self._fmt_map(x_subscribe_list))
+ if node_flag:
+ opt_str_list.append("node: %s" % self._fmt_map(node_str_list))
+ else:
+ opt_str_list.append("link: %s" % self._fmt_map(node_str_list))
+ addr_str = node_name
+ if node_subject != None:
+ addr_str += "/%s" % node_subject
+ if len(opt_str_list) > 0:
+ addr_str += "; %s" % self._fmt_map(opt_str_list)
+ return addr_str
+
+ def snd_addr(self, node_name, **kwargs):
+ """ Create a send (node) address"""
+ # Get keyword args
+ topic = kwargs.get("topic")
+ topic_flag = kwargs.get("topic_flag", False)
+ auto_create = kwargs.get("auto_create", True)
+ auto_delete = kwargs.get("auto_delete", False)
+ durable = kwargs.get("durable", False)
+ exclusive = kwargs.get("exclusive", False)
+ ftd_count = kwargs.get("ftd_count")
+ ftd_size = kwargs.get("ftd_size")
+ policy = kwargs.get("policy", "flow-to-disk")
+ exchage_type = kwargs.get("exchage_type")
+
+ create_policy = None
+ if auto_create:
+ create_policy = "always"
+ delete_policy = None
+ if auto_delete:
+ delete_policy = "always"
+ node_type = None
+ if topic != None or topic_flag:
+ node_type = "topic"
+ x_declare_list = ["\"exclusive\": %s" % exclusive]
+ if ftd_count != None or ftd_size != None:
+ queue_policy = ["\'qpid.policy_type\': %s" % policy]
+ if ftd_count:
+ queue_policy.append("\'qpid.max_count\': %d" % ftd_count)
+ if ftd_size:
+ queue_policy.append("\'qpid.max_size\': %d" % ftd_size)
+ x_declare_list.append("arguments: %s" % self._fmt_map(queue_policy))
+ if exchage_type != None:
+ x_declare_list.append("type: %s" % exchage_type)
+
+ return self.addr_fmt(node_name, topic=topic, create_policy=create_policy, delete_policy=delete_policy,
+ node_type=node_type, durable=durable, x_declare_list=x_declare_list)
+
+ def rcv_addr(self, node_name, **kwargs):
+ """ Create a receive (link) address"""
+ # Get keyword args
+ auto_create = kwargs.get("auto_create", True)
+ auto_delete = kwargs.get("auto_delete", False)
+ link_name = kwargs.get("link_name")
+ durable = kwargs.get("durable", False)
+ browse = kwargs.get("browse", False)
+ exclusive = kwargs.get("exclusive", False)
+ binding_list = kwargs.get("binding_list", [])
+ ftd_count = kwargs.get("ftd_count")
+ ftd_size = kwargs.get("ftd_size")
+ policy = kwargs.get("policy", "flow-to-disk")
+
+ create_policy = None
+ if auto_create:
+ create_policy = "always"
+ delete_policy = None
+ if auto_delete:
+ delete_policy = "always"
+ mode = None
+ if browse:
+ mode = "browse"
+ x_declare_list = ["\"exclusive\": %s" % exclusive]
+ if ftd_count != None or ftd_size != None:
+ queue_policy = ["\'qpid.policy_type\': %s" % policy]
+ if ftd_count:
+ queue_policy.append("\'qpid.max_count\': %d" % ftd_count)
+ if ftd_size:
+ queue_policy.append("\'qpid.max_size\': %d" % ftd_size)
+ x_declare_list.append("arguments: %s" % self._fmt_map(queue_policy))
+ x_bindings_list = []
+ for binding in binding_list:
+ x_bindings_list.append("{exchange: %s, key: %s}" % binding)
+ if durable: reliability = 'at-least-once'
+ else: reliability = None
+ return self.addr_fmt(node_name, create_policy=create_policy, delete_policy=delete_policy, mode=mode, link=True,
+ link_name=link_name, durable=durable, x_declare_list=x_declare_list,
+ x_bindings_list=x_bindings_list, link_reliability=reliability)
+
+ def check_message(self, broker, queue, exp_msg, transactional=False, empty=False, ack=True, browse=False):
+ """Check that a message is on a queue by dequeuing it and comparing it to the expected message"""
+ return self.check_messages(broker, queue, [exp_msg], transactional, empty, ack, browse)
+
+ def check_messages(self, broker, queue, exp_msg_list, transactional=False, empty=False, ack=True, browse=False,
+ emtpy_flag=False):
+ """Check that messages is on a queue by dequeuing them and comparing them to the expected messages"""
+ if emtpy_flag:
+ num_msgs = 0
+ else:
+ num_msgs = len(exp_msg_list)
+ ssn = broker.connect().session(transactional=transactional)
+ rcvr = ssn.receiver(self.rcv_addr(queue, browse=browse), capacity=num_msgs)
+ if num_msgs > 0:
+ try:
+ recieved_msg_list = [rcvr.fetch(timeout=0) for i in range(num_msgs)]
+ except Empty:
+ self.assert_(False, "Queue \"%s\" is empty, unable to retrieve expected message %d." % (queue, i))
+ for i in range(0, len(recieved_msg_list)):
+ self.assertEqual(recieved_msg_list[i].content, exp_msg_list[i].content)
+ self.assertEqual(recieved_msg_list[i].correlation_id, exp_msg_list[i].correlation_id)
+ if empty:
+ self._chk_empty(queue, rcvr)
+ if ack:
+ ssn.acknowledge()
+ if transactional:
+ ssn.commit()
+ ssn.connection.close()
+ else:
+ if transactional:
+ ssn.commit()
+ return ssn
+
+
+ # Functions for finding strings in the broker log file (or other files)
+
+ @staticmethod
+ def _read_file(file_name):
+ """Returns the content of file named file_name as a string"""
+ file_handle = file(file_name)
+ try:
+ return file_handle.read()
+ finally:
+ file_handle.close()
+
+ def _get_hits(self, broker, search):
+ """Find all occurrences of the search in the broker log (eliminating possible duplicates from msgs on multiple
+ queues)"""
+ # TODO: Use sets when RHEL-4 is no longer supported
+ hits = []
+ for hit in search.findall(self._read_file(broker.log)):
+ if hit not in hits:
+ hits.append(hit)
+ return hits
+
+ def _reconsile_hits(self, broker, ftd_msgs, release_hits):
+ """Remove entries from list release_hits if they match the message id in ftd_msgs. Check for remaining
+ release_hits."""
+ for msg in ftd_msgs:
+ found = False
+ for hit in release_hits:
+ if str(msg.id) in hit:
+ release_hits.remove(hit)
+ #print "Found %s in %s" % (msg.id, broker.log)
+ found = True
+ break
+ if not found:
+ self.assert_(False, "Unable to locate released message %s in log %s" % (msg.id, broker.log))
+ if len(release_hits) > 0:
+ err = "Messages were unexpectedly released in log %s:\n" % broker.log
+ for hit in release_hits:
+ err += " %s\n" % hit
+ self.assert_(False, err)
+
+ def check_msg_release(self, broker, ftd_msgs):
+ """ Check for 'Content released' messages in broker log for messages in ftd_msgs"""
+ hits = self._get_hits(broker, re.compile("debug Message id=\"[0-9a-f-]{36}\"; pid=0x[0-9a-f]+: "
+ "Content released$", re.MULTILINE))
+ self._reconsile_hits(broker, ftd_msgs, hits)
+
+ def check_msg_release_on_commit(self, broker, ftd_msgs):
+ """ Check for 'Content released on commit' messages in broker log for messages in ftd_msgs"""
+ hits = self._get_hits(broker, re.compile("debug Message id=\"[0-9a-f-]{36}\"; pid=0x[0-9a-f]+: "
+ "Content released on commit$", re.MULTILINE))
+ self._reconsile_hits(broker, ftd_msgs, hits)
+
+ def check_msg_release_on_recover(self, broker, ftd_msgs):
+ """ Check for 'Content released after recovery' messages in broker log for messages in ftd_msgs"""
+ hits = self._get_hits(broker, re.compile("debug Message id=\"[0-9a-f-]{36}\"; pid=0x[0-9a-f]+: "
+ "Content released after recovery$", re.MULTILINE))
+ self._reconsile_hits(broker, ftd_msgs, hits)
+
+ def check_msg_block(self, broker, ftd_msgs):
+ """Check for 'Content release blocked' messages in broker log for messages in ftd_msgs"""
+ hits = self._get_hits(broker, re.compile("debug Message id=\"[0-9a-f-]{36}\"; pid=0x[0-9a-f]+: "
+ "Content release blocked$", re.MULTILINE))
+ self._reconsile_hits(broker, ftd_msgs, hits)
+
+ def check_msg_block_on_commit(self, broker, ftd_msgs):
+ """Check for 'Content release blocked' messages in broker log for messages in ftd_msgs"""
+ hits = self._get_hits(broker, re.compile("debug Message id=\"[0-9a-f-]{36}\"; pid=0x[0-9a-f]+: "
+ "Content release blocked on commit$", re.MULTILINE))
+ self._reconsile_hits(broker, ftd_msgs, hits)
diff --git a/qpid/cpp/src/tests/legacystore/run_long_python_tests b/qpid/cpp/src/tests/legacystore/run_long_python_tests
new file mode 100644
index 0000000000..be6380302c
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/run_long_python_tests
@@ -0,0 +1,21 @@
+#!/usr/bin/env 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.
+#
+
+./run_python_tests LONG_TEST
diff --git a/qpid/cpp/src/tests/legacystore/run_python_tests b/qpid/cpp/src/tests/legacystore/run_python_tests
new file mode 100755
index 0000000000..c1d04a28a1
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/run_python_tests
@@ -0,0 +1,43 @@
+#!/usr/bin/env 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.
+#
+
+source $QPID_TEST_COMMON
+
+ensure_python_tests
+
+#Add our directory to the python path
+export PYTHONPATH=$srcdir/legacystore:$PYTHONPATH
+
+MODULENAME=python_tests
+
+echo "Running Python tests in module ${MODULENAME}..."
+
+QPID_PORT=${QPID_PORT:-5672}
+FAILING=${FAILING:-/dev/null}
+PYTHON_TESTS=${PYTHON_TESTS:-$*}
+
+OUTDIR=${MODULENAME}.tmp
+rm -rf $OUTDIR
+
+# To debug a test, add the following options to the end of the following line:
+# -v DEBUG -c qpid.messaging.io.ops [*.testName]
+${QPID_PYTHON_TEST} -m ${MODULENAME} -I $FAILING -DOUTDIR=$OUTDIR \
+ $PYTHON_TEST || exit 1
+
diff --git a/qpid/cpp/src/tests/legacystore/run_short_python_tests b/qpid/cpp/src/tests/legacystore/run_short_python_tests
new file mode 100644
index 0000000000..9b9e7c59be
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/run_short_python_tests
@@ -0,0 +1,21 @@
+#!/usr/bin/env 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.
+#
+
+./run_python_tests SHORT_TEST
diff --git a/qpid/cpp/src/tests/legacystore/system_test.sh b/qpid/cpp/src/tests/legacystore/system_test.sh
new file mode 100644
index 0000000000..52cecbce8a
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/system_test.sh
@@ -0,0 +1,51 @@
+#!/usr/bin/env 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.
+#
+
+error() { echo $*; exit 1; }
+
+# Make sure $QPID_DIR contains what we need.
+if ! test -d "$QPID_DIR" ; then
+ echo "WARNING: QPID_DIR is not set skipping system tests."
+ exit
+fi
+STORE_LIB=../lib/.libs/msgstore.so
+
+xml_spec=$QPID_DIR/specs/amqp.0-10-qpid-errata.stripped.xml
+test -f $xml_spec || error "$xml_spec not found: invalid \$QPID_DIR ?"
+export PYTHONPATH=$QPID_DIR/python:$QPID_DIR/extras/qmf/src/py:$QPID_DIR/tools/src/py
+
+echo "Using directory $TMP_DATA_DIR"
+
+fail=0
+
+# Run the tests with a given set of flags
+BROKER_OPTS="--no-module-dir --load-module=$STORE_LIB --data-dir=$TMP_DATA_DIR --auth=no --wcache-page-size 16"
+run_tests() {
+ for p in `seq 1 8`; do
+ $abs_srcdir/start_broker "$@" ${BROKER_OPTS} || { echo "FAIL broker start"; return 1; }
+ python "$abs_srcdir/persistence.py" -s "$xml_spec" -b localhost:`cat qpidd.port` -p $p -r 3 || fail=1;
+ $abs_srcdir/stop_broker
+ done
+}
+
+run_tests || fail=1
+
+exit $fail
diff --git a/qpid/cpp/src/tests/legacystore/unit_test.cpp b/qpid/cpp/src/tests/legacystore/unit_test.cpp
new file mode 100644
index 0000000000..add80a6f91
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/unit_test.cpp
@@ -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.
+ *
+ */
+
+
+// Defines test_main function to link with actual unit test code.
+#define BOOST_AUTO_TEST_MAIN // Boost 1.33
+#define BOOST_TEST_MAIN
+
+#include "unit_test.h"
+
diff --git a/qpid/cpp/src/tests/legacystore/unit_test.h b/qpid/cpp/src/tests/legacystore/unit_test.h
new file mode 100644
index 0000000000..16b6ae2ffb
--- /dev/null
+++ b/qpid/cpp/src/tests/legacystore/unit_test.h
@@ -0,0 +1,69 @@
+#ifndef QPIPD_TEST_UNIT_TEST_H_
+#define QPIPD_TEST_UNIT_TEST_H_
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+
+// Workaround so we can build against boost 1.32, 1.33 and boost 1.34.
+// Remove when we no longer need to support 1.32 or 1.33.
+
+#include <boost/version.hpp>
+
+#if (BOOST_VERSION < 103400) // v.1.33 and earlier
+# include <boost/test/auto_unit_test.hpp>
+#else // v.1.34 and later
+# include <boost/test/unit_test.hpp>
+#endif
+
+// Keep the test function for compilation but do not not register it.
+// TODO aconway 2008-04-23: better workaround for expected failures.
+// The following causes the test testUpdateTxState not to run at all.
+# define QPID_AUTO_TEST_CASE_EXPECTED_FAILURES(test_name,n) \
+ namespace { struct test_name { void test_method(); }; } \
+ void test_name::test_method()
+// The following runs the test testUpdateTxState, but it fails.
+/*#define QPID_AUTO_TEST_CASE_EXPECTED_FAILURES(test_name,n) \
+ namespace { struct test_name { void test_method(); }; } \
+ BOOST_AUTO_TEST_CASE(name)*/
+
+#if (BOOST_VERSION < 103300) // v.1.32 and earlier
+
+# define QPID_AUTO_TEST_SUITE(name)
+# define QPID_AUTO_TEST_CASE(name) BOOST_AUTO_UNIT_TEST(name)
+# define QPID_AUTO_TEST_SUITE_END()
+
+#elif (BOOST_VERSION < 103400) // v.1.33
+
+// Note the trailing ';'
+# define QPID_AUTO_TEST_SUITE(name) BOOST_AUTO_TEST_SUITE(name);
+# define QPID_AUTO_TEST_CASE(name) BOOST_AUTO_TEST_CASE(name)
+# define QPID_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END();
+
+#else // v.1.34 and later
+
+# define QPID_AUTO_TEST_SUITE(name) BOOST_AUTO_TEST_SUITE(name)
+# define QPID_AUTO_TEST_CASE(name) BOOST_AUTO_TEST_CASE(name)
+# define QPID_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()
+
+#endif
+
+#endif /*!QPIPD_TEST_UNIT_TEST_H_*/
diff --git a/qpid/cpp/src/tests/linearstore/CMakeLists.txt b/qpid/cpp/src/tests/linearstore/CMakeLists.txt
new file mode 100644
index 0000000000..bf6c164818
--- /dev/null
+++ b/qpid/cpp/src/tests/linearstore/CMakeLists.txt
@@ -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.
+#
+
+if(BUILD_LINEARSTORE AND BUILD_TESTING)
+
+message(STATUS "Building linearstore tests")
+
+set(test_wrap ${shell} ${CMAKE_SOURCE_DIR}/src/tests/run_test${test_script_suffix} -buildDir=${CMAKE_BINARY_DIR})
+
+add_test (linearstore_python_tests ${test_wrap} -- ${CMAKE_CURRENT_SOURCE_DIR}/run_python_tests${test_script_suffix})
+
+endif (BUILD_LINEARSTORE AND BUILD_TESTING)
+
diff --git a/qpid/cpp/src/tests/linearstore/linearstoredirsetup.sh b/qpid/cpp/src/tests/linearstore/linearstoredirsetup.sh
new file mode 100755
index 0000000000..ef39767e9b
--- /dev/null
+++ b/qpid/cpp/src/tests/linearstore/linearstoredirsetup.sh
@@ -0,0 +1,55 @@
+#!/usr/bin/env 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.
+#
+
+# This script sets up a test directory which contains both
+# recoverable and non-recoverable files and directories for
+# the empty file pool (EFP).
+
+# NOTE: The following is based on typical development tree paths, not installed paths
+
+BASE_DIR=${HOME}/RedHat
+STORE_DIR=${BASE_DIR}
+PYTHON_TOOLS_DIR=${BASE_DIR}/qpid/tools/src/linearstore
+export PYTHONPATH=${BASE_DIR}/qpid/python:${BASE_DIR}/qpid/extras/qmf/src/py:${BASE_DIR}/qpid/tools/src/py
+
+# Remove old dirs (if present)
+rm -rf ${STORE_DIR}/qls
+rm -rf ${STORE_DIR}/p002
+rm ${STORE_DIR}/p004
+
+# Create new dir tree and links
+mkdir ${STORE_DIR}/p002_ext
+touch ${STORE_DIR}/p004_ext
+mkdir ${STORE_DIR}/qls
+mkdir ${STORE_DIR}/qls/p001
+touch ${STORE_DIR}/qls/p003
+ln -s ${STORE_DIR}/p002_ext ${STORE_DIR}/qls/p002
+ln -s ${STORE_DIR}/p004_ext ${STORE_DIR}/qls/p004
+
+# Populate efp dirs with empty files
+${PYTHON_TOOLS_DIR}/efptool.py $STORE_DIR/qls/ -a -p 1 -s 2048 -n 25
+${PYTHON_TOOLS_DIR}/efptool.py $STORE_DIR/qls/ -a -p 1 -s 512 -n 25
+${PYTHON_TOOLS_DIR}/efptool.py $STORE_DIR/qls/ -a -p 2 -s 2048 -n 25
+
+# Show the result for information
+${LINEARSTOREDIR}/tools/src/py/linearstore/efptool.py $STORE_DIR/qls/ -l
+tree -la $STORE_DIR/qls
+
diff --git a/qpid/cpp/src/tests/linearstore/python_tests/__init__.py b/qpid/cpp/src/tests/linearstore/python_tests/__init__.py
new file mode 100644
index 0000000000..1e59d403e4
--- /dev/null
+++ b/qpid/cpp/src/tests/linearstore/python_tests/__init__.py
@@ -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.
+#
+
+# Do not delete - marks this directory as a python package.
+
+from client_persistence import *
+
diff --git a/qpid/cpp/src/tests/linearstore/python_tests/client_persistence.py b/qpid/cpp/src/tests/linearstore/python_tests/client_persistence.py
new file mode 100644
index 0000000000..9ff9480c4c
--- /dev/null
+++ b/qpid/cpp/src/tests/linearstore/python_tests/client_persistence.py
@@ -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.
+#
+
+import os
+
+from brokertest import EXPECT_EXIT_OK
+from store_test import StoreTest, Qmf, store_args
+from qpid.messaging import *
+
+import qpid.messaging, brokertest
+brokertest.qm = qpid.messaging # FIXME aconway 2014-04-04: Tests fail with SWIG client.
+
+class ExchangeQueueTests(StoreTest):
+ """
+ Simple tests of the broker exchange and queue types
+ """
+
+ def test_direct_exchange(self):
+ """Test Direct exchange."""
+ broker = self.broker(store_args(), name="test_direct_exchange", expect=EXPECT_EXIT_OK)
+ msg1 = Message("A_Message1", durable=True, correlation_id="Msg0001")
+ msg2 = Message("B_Message1", durable=True, correlation_id="Msg0002")
+ broker.send_message("a", msg1)
+ broker.send_message("b", msg2)
+ broker.terminate()
+
+ broker = self.broker(store_args(), name="test_direct_exchange")
+ self.check_message(broker, "a", msg1, True)
+ self.check_message(broker, "b", msg2, True)
+
+ def test_topic_exchange(self):
+ """Test Topic exchange."""
+ broker = self.broker(store_args(), name="test_topic_exchange", expect=EXPECT_EXIT_OK)
+ ssn = broker.connect().session()
+ snd1 = ssn.sender("abc/key1; {create:always, node:{type:topic, durable:True}}")
+ snd2 = ssn.sender("abc/key2; {create:always, node:{type:topic, durable:True}}")
+ ssn.receiver("a; {create:always, link:{x-bindings:[{exchange:abc, key:key1}]}, node:{durable:True}}")
+ ssn.receiver("b; {create:always, link:{x-bindings:[{exchange:abc, key:key1}]}, node:{durable:True}}")
+ ssn.receiver("c; {create:always, link:{x-bindings:[{exchange:abc, key:key1}, "
+ "{exchange:abc, key: key2}]}, node:{durable:True}}")
+ ssn.receiver("d; {create:always, link:{x-bindings:[{exchange:abc, key:key2}]}, node:{durable:True}}")
+ ssn.receiver("e; {create:always, link:{x-bindings:[{exchange:abc, key:key2}]}, node:{durable:True}}")
+ msg1 = Message("Message1", durable=True, correlation_id="Msg0003")
+ snd1.send(msg1)
+ msg2 = Message("Message2", durable=True, correlation_id="Msg0004")
+ snd2.send(msg2)
+ broker.terminate()
+
+ broker = self.broker(store_args(), name="test_topic_exchange")
+ self.check_message(broker, "a", msg1, True)
+ self.check_message(broker, "b", msg1, True)
+ self.check_messages(broker, "c", [msg1, msg2], True)
+ self.check_message(broker, "d", msg2, True)
+ self.check_message(broker, "e", msg2, True)
+
+
+ def test_legacy_lvq(self):
+ """Test legacy LVQ."""
+ broker = self.broker(store_args(), name="test_lvq", expect=EXPECT_EXIT_OK)
+ ma1 = Message("A1", durable=True, correlation_id="Msg0005", properties={"qpid.LVQ_key":"A"})
+ ma2 = Message("A2", durable=True, correlation_id="Msg0006", properties={"qpid.LVQ_key":"A"})
+ mb1 = Message("B1", durable=True, correlation_id="Msg0007", properties={"qpid.LVQ_key":"B"})
+ mb2 = Message("B2", durable=True, correlation_id="Msg0008", properties={"qpid.LVQ_key":"B"})
+ mb3 = Message("B3", durable=True, correlation_id="Msg0009", properties={"qpid.LVQ_key":"B"})
+ mc1 = Message("C1", durable=True, correlation_id="Msg0010", properties={"qpid.LVQ_key":"C"})
+ broker.send_messages("lvq-test", [mb1, ma1, ma2, mb2, mb3, mc1],
+ xprops="arguments:{\"qpid.last_value_queue\":True}")
+ broker.terminate()
+
+ broker = self.broker(store_args(), name="test_lvq", expect=EXPECT_EXIT_OK)
+ ssn = self.check_messages(broker, "lvq-test", [ma2, mb3, mc1], empty=True, ack=False)
+ # Add more messages while subscriber is active (no replacement):
+ ma3 = Message("A3", durable=True, correlation_id="Msg0011", properties={"qpid.LVQ_key":"A"})
+ ma4 = Message("A4", durable=True, correlation_id="Msg0012", properties={"qpid.LVQ_key":"A"})
+ mc2 = Message("C2", durable=True, correlation_id="Msg0013", properties={"qpid.LVQ_key":"C"})
+ mc3 = Message("C3", durable=True, correlation_id="Msg0014", properties={"qpid.LVQ_key":"C"})
+ mc4 = Message("C4", durable=True, correlation_id="Msg0015", properties={"qpid.LVQ_key":"C"})
+ broker.send_messages("lvq-test", [mc2, mc3, ma3, ma4, mc4], session=ssn)
+ ssn.acknowledge()
+ broker.terminate()
+
+ broker = self.broker(store_args(), name="test_lvq")
+ self.check_messages(broker, "lvq-test", [ma4, mc4], True)
+
+
+ def test_fanout_exchange(self):
+ """Test Fanout Exchange"""
+ broker = self.broker(store_args(), name="test_fanout_exchange", expect=EXPECT_EXIT_OK)
+ ssn = broker.connect().session()
+ snd = ssn.sender("TestFanoutExchange; {create: always, node: {type: topic, x-declare: {type: fanout}}}")
+ ssn.receiver("TestFanoutExchange; {link: {name: \"q1\", durable: True, reliability:at-least-once}}")
+ ssn.receiver("TestFanoutExchange; {link: {name: \"q2\", durable: True, reliability:at-least-once}}")
+ ssn.receiver("TestFanoutExchange; {link: {name: \"q3\", durable: True, reliability:at-least-once}}")
+ msg1 = Message("Msg1", durable=True, correlation_id="Msg0001")
+ snd.send(msg1)
+ msg2 = Message("Msg2", durable=True, correlation_id="Msg0002")
+ snd.send(msg2)
+ broker.terminate()
+
+ broker = self.broker(store_args(), name="test_fanout_exchange")
+ self.check_messages(broker, "q1", [msg1, msg2], True)
+ self.check_messages(broker, "q2", [msg1, msg2], True)
+ self.check_messages(broker, "q3", [msg1, msg2], True)
+
+
+ def test_message_reject(self):
+ broker = self.broker(store_args(), name="test_message_reject", expect=EXPECT_EXIT_OK)
+ ssn = broker.connect().session()
+ snd = ssn.sender("tmr; {create:always, node:{type:queue, durable:True}}")
+ rcv = ssn.receiver("tmr; {create:always, node:{type:queue, durable:True}}")
+ m1 = Message("test_message_reject", durable=True, correlation_id="Msg0001")
+ snd.send(m1)
+ m2 = rcv.fetch()
+ ssn.acknowledge(message=m2, disposition=Disposition(REJECTED))
+ broker.terminate()
+
+ broker = self.broker(store_args(), name="test_message_reject")
+ qmf = Qmf(broker)
+ assert qmf.queue_message_count("tmr") == 0
+
+
+ def test_route(self):
+ """ Test the recovery of a route (link and bridge objects."""
+ broker = self.broker(store_args(), name="test_route", expect=EXPECT_EXIT_OK)
+ qmf = Qmf(broker)
+ qmf_broker_obj = qmf.get_objects("broker")[0]
+
+ # create a "link"
+ link_args = {"host":"a.fake.host.com", "port":9999, "durable":True,
+ "authMechanism":"PLAIN", "username":"guest", "password":"guest",
+ "transport":"tcp"}
+ result = qmf_broker_obj.create("link", "test-link", link_args, False)
+ self.assertEqual(result.status, 0, result)
+ link = qmf.get_objects("link")[0]
+
+ # create bridge
+ bridge_args = {"link":"test-link", "src":"amq.direct", "dest":"amq.fanout",
+ "key":"my-key", "durable":True}
+ result = qmf_broker_obj.create("bridge", "test-bridge", bridge_args, False);
+ self.assertEqual(result.status, 0, result)
+ bridge = qmf.get_objects("bridge")[0]
+
+ broker.terminate()
+
+ # recover the link and bridge
+ broker = self.broker(store_args(), name="test_route")
+ qmf = Qmf(broker)
+ qmf_broker_obj = qmf.get_objects("broker")[0]
+ self.assertEqual(len(qmf.get_objects("link")), 1)
+ self.assertEqual(len(qmf.get_objects("bridge")), 1)
+
+
+
+class AlternateExchangePropertyTests(StoreTest):
+ """
+ Test the persistence of the Alternate Exchange property for exchanges and queues.
+ """
+
+ def test_exchange(self):
+ """Exchange alternate exchange property persistence test"""
+ broker = self.broker(store_args(), name="test_exchange", expect=EXPECT_EXIT_OK)
+ qmf = Qmf(broker)
+ qmf.add_exchange("altExch", "direct", durable=True) # Serves as alternate exchange instance
+ qmf.add_exchange("testExch", "direct", durable=True, alt_exchange_name="altExch")
+ qmf.close()
+ broker.terminate()
+
+ broker = self.broker(store_args(), name="test_exchange")
+ qmf = Qmf(broker)
+ try:
+ qmf.add_exchange("altExch", "direct", passive=True)
+ except Exception, error:
+ self.fail("Alternate exchange (\"altExch\") instance not recovered: %s" % error)
+ try:
+ qmf.add_exchange("testExch", "direct", passive=True)
+ except Exception, error:
+ self.fail("Test exchange (\"testExch\") instance not recovered: %s" % error)
+ self.assertTrue(qmf.query_exchange("testExch", alt_exchange_name = "altExch"),
+ "Alternate exchange property not found or is incorrect on exchange \"testExch\".")
+ qmf.close()
+
+ def test_queue(self):
+ """Queue alternate exchange property persistexchangeNamece test"""
+ broker = self.broker(store_args(), name="test_queue", expect=EXPECT_EXIT_OK)
+ qmf = Qmf(broker)
+ qmf.add_exchange("altExch", "direct", durable=True) # Serves as alternate exchange instance
+ qmf.add_queue("testQueue", durable=True, alt_exchange_name="altExch")
+ qmf.close()
+ broker.terminate()
+
+ broker = self.broker(store_args(), name="test_queue")
+ qmf = Qmf(broker)
+ try:
+ qmf.add_exchange("altExch", "direct", passive=True)
+ except Exception, error:
+ self.fail("Alternate exchange (\"altExch\") instance not recovered: %s" % error)
+ try:
+ qmf.add_queue("testQueue", passive=True)
+ except Exception, error:
+ self.fail("Test queue (\"testQueue\") instance not recovered: %s" % error)
+ self.assertTrue(qmf.query_queue("testQueue", alt_exchange_name = "altExch"),
+ "Alternate exchange property not found or is incorrect on queue \"testQueue\".")
+ qmf.close()
+
+
+class RedeliveredTests(StoreTest):
+ """
+ Test the behavior of the redelivered flag in the context of persistence
+ """
+
+ def test_broker_recovery(self):
+ """Test that the redelivered flag is set on messages after recovery of broker"""
+ broker = self.broker(store_args(), name="test_broker_recovery", expect=EXPECT_EXIT_OK)
+ msg_content = "xyz"*100
+ msg = Message(msg_content, durable=True)
+ broker.send_message("testQueue", msg)
+ broker.terminate()
+
+ broker = self.broker(store_args(), name="test_broker_recovery")
+ rcv_msg = broker.get_message("testQueue")
+ self.assertEqual(msg_content, rcv_msg.content)
+ self.assertTrue(rcv_msg.redelivered)
+
diff --git a/qpid/cpp/src/tests/linearstore/python_tests/store_test.py b/qpid/cpp/src/tests/linearstore/python_tests/store_test.py
new file mode 100644
index 0000000000..cc846aefd4
--- /dev/null
+++ b/qpid/cpp/src/tests/linearstore/python_tests/store_test.py
@@ -0,0 +1,417 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 re
+from brokertest import BrokerTest
+from qpid.messaging import Empty
+from qmf.console import Session
+
+import qpid.messaging, brokertest
+brokertest.qm = qpid.messaging # TODO aconway 2014-04-04: Tests fail with SWIG client.
+
+
+def store_args(store_dir = None):
+ """Return the broker args necessary to load the async store"""
+ assert BrokerTest.store_lib
+ if store_dir == None:
+ return []
+ return ["--store-dir", store_dir]
+
+class Qmf:
+ """
+ QMF functions not yet available in the new QMF API. Remove this and replace with new API when it becomes available.
+ """
+ def __init__(self, broker):
+ self.__session = Session()
+ self.__broker = self.__session.addBroker("amqp://localhost:%d"%broker.port())
+
+ def add_exchange(self, exchange_name, exchange_type, alt_exchange_name=None, passive=False, durable=False,
+ arguments = None):
+ """Add a new exchange"""
+ amqp_session = self.__broker.getAmqpSession()
+ if arguments == None:
+ arguments = {}
+ if alt_exchange_name:
+ amqp_session.exchange_declare(exchange=exchange_name, type=exchange_type,
+ alternate_exchange=alt_exchange_name, passive=passive, durable=durable,
+ arguments=arguments)
+ else:
+ amqp_session.exchange_declare(exchange=exchange_name, type=exchange_type, passive=passive, durable=durable,
+ arguments=arguments)
+
+ def add_queue(self, queue_name, alt_exchange_name=None, passive=False, durable=False, arguments = None):
+ """Add a new queue"""
+ amqp_session = self.__broker.getAmqpSession()
+ if arguments == None:
+ arguments = {}
+ if alt_exchange_name:
+ amqp_session.queue_declare(queue_name, alternate_exchange=alt_exchange_name, passive=passive,
+ durable=durable, arguments=arguments)
+ else:
+ amqp_session.queue_declare(queue_name, passive=passive, durable=durable, arguments=arguments)
+
+ def delete_queue(self, queue_name):
+ """Delete an existing queue"""
+ amqp_session = self.__broker.getAmqpSession()
+ amqp_session.queue_delete(queue_name)
+
+ def _query(self, name, _class, package, alt_exchange_name=None):
+ """Qmf query function which can optionally look for the presence of an alternate exchange name"""
+ try:
+ obj_list = self.__session.getObjects(_class=_class, _package=package)
+ found = False
+ for obj in obj_list:
+ if obj.name == name:
+ found = True
+ if alt_exchange_name != None:
+ alt_exch_list = self.__session.getObjects(_objectId=obj.altExchange)
+ if len(alt_exch_list) == 0 or alt_exch_list[0].name != alt_exchange_name:
+ return False
+ break
+ return found
+ except Exception:
+ return False
+
+
+ def query_exchange(self, exchange_name, alt_exchange_name=None):
+ """Test for the presence of an exchange, and optionally whether it has an alternate exchange set to a known
+ value."""
+ return self._query(exchange_name, "exchange", "org.apache.qpid.broker", alt_exchange_name)
+
+ def query_queue(self, queue_name, alt_exchange_name=None):
+ """Test for the presence of an exchange, and optionally whether it has an alternate exchange set to a known
+ value."""
+ return self._query(queue_name, "queue", "org.apache.qpid.broker", alt_exchange_name)
+
+ def queue_message_count(self, queue_name):
+ """Query the number of messages on a queue"""
+ queue_list = self.__session.getObjects(_class="queue", _name=queue_name)
+ if len(queue_list):
+ return queue_list[0].msgDepth
+
+ def queue_empty(self, queue_name):
+ """Check if a queue is empty (has no messages waiting)"""
+ return self.queue_message_count(queue_name) == 0
+
+ def get_objects(self, target_class, target_package="org.apache.qpid.broker"):
+ return self.__session.getObjects(_class=target_class, _package=target_package)
+
+
+ def close(self):
+ self.__session.delBroker(self.__broker)
+ self.__session = None
+
+
+class StoreTest(BrokerTest):
+ """
+ This subclass of BrokerTest adds some convenience test/check functions
+ """
+
+ def _chk_empty(self, queue, receiver):
+ """Check if a queue is empty (has no more messages)"""
+ try:
+ msg = receiver.fetch(timeout=0)
+ self.assert_(False, "Queue \"%s\" not empty: found message: %s" % (queue, msg))
+ except Empty:
+ pass
+
+ @staticmethod
+ def make_message(msg_count, msg_size):
+ """Make message content. Format: 'abcdef....' followed by 'msg-NNNN', where NNNN is the message count"""
+ msg = "msg-%04d" % msg_count
+ msg_len = len(msg)
+ buff = ""
+ if msg_size != None and msg_size > msg_len:
+ for index in range(0, msg_size - msg_len):
+ if index == msg_size - msg_len - 1:
+ buff += "-"
+ else:
+ buff += chr(ord('a') + (index % 26))
+ return buff + msg
+
+ # Functions for formatting address strings
+
+ @staticmethod
+ def _fmt_csv(string_list, list_braces = None):
+ """Format a list using comma-separation. Braces are optionally added."""
+ if len(string_list) == 0:
+ return ""
+ first = True
+ str_ = ""
+ if list_braces != None:
+ str_ += list_braces[0]
+ for string in string_list:
+ if string != None:
+ if first:
+ first = False
+ else:
+ str_ += ", "
+ str_ += string
+ if list_braces != None:
+ str_ += list_braces[1]
+ return str_
+
+ def _fmt_map(self, string_list):
+ """Format a map {l1, l2, l3, ...} from a string list. Each item in the list must be a formatted map
+ element('key:val')."""
+ return self._fmt_csv(string_list, list_braces="{}")
+
+ def _fmt_list(self, string_list):
+ """Format a list [l1, l2, l3, ...] from a string list."""
+ return self._fmt_csv(string_list, list_braces="[]")
+
+ def addr_fmt(self, node_name, **kwargs):
+ """Generic AMQP to new address formatter. Takes common (but not all) AMQP options and formats an address
+ string."""
+ # Get keyword args
+ node_subject = kwargs.get("node_subject")
+ create_policy = kwargs.get("create_policy")
+ delete_policy = kwargs.get("delete_policy")
+ assert_policy = kwargs.get("assert_policy")
+ mode = kwargs.get("mode")
+ link = kwargs.get("link", False)
+ link_name = kwargs.get("link_name")
+ node_type = kwargs.get("node_type")
+ durable = kwargs.get("durable", False)
+ link_reliability = kwargs.get("link_reliability")
+ x_declare_list = kwargs.get("x_declare_list", [])
+ x_bindings_list = kwargs.get("x_bindings_list", [])
+ x_subscribe_list = kwargs.get("x_subscribe_list", [])
+
+ node_flag = not link and (node_type != None or durable or len(x_declare_list) > 0 or len(x_bindings_list) > 0)
+ link_flag = link and (link_name != None or durable or link_reliability != None or len(x_declare_list) > 0 or
+ len(x_bindings_list) > 0 or len(x_subscribe_list) > 0)
+ assert not (node_flag and link_flag)
+
+ opt_str_list = []
+ if create_policy != None:
+ opt_str_list.append("create: %s" % create_policy)
+ if delete_policy != None:
+ opt_str_list.append("delete: %s" % delete_policy)
+ if assert_policy != None:
+ opt_str_list.append("assert: %s" % assert_policy)
+ if mode != None:
+ opt_str_list.append("mode: %s" % mode)
+ if node_flag or link_flag:
+ node_str_list = []
+ if link_name != None:
+ node_str_list.append("name: \"%s\"" % link_name)
+ if node_type != None:
+ node_str_list.append("type: %s" % node_type)
+ if durable:
+ node_str_list.append("durable: True")
+ if link_reliability != None:
+ node_str_list.append("reliability: %s" % link_reliability)
+ if len(x_declare_list) > 0:
+ node_str_list.append("x-declare: %s" % self._fmt_map(x_declare_list))
+ if len(x_bindings_list) > 0:
+ node_str_list.append("x-bindings: %s" % self._fmt_list(x_bindings_list))
+ if len(x_subscribe_list) > 0:
+ node_str_list.append("x-subscribe: %s" % self._fmt_map(x_subscribe_list))
+ if node_flag:
+ opt_str_list.append("node: %s" % self._fmt_map(node_str_list))
+ else:
+ opt_str_list.append("link: %s" % self._fmt_map(node_str_list))
+ addr_str = node_name
+ if node_subject != None:
+ addr_str += "/%s" % node_subject
+ if len(opt_str_list) > 0:
+ addr_str += "; %s" % self._fmt_map(opt_str_list)
+ return addr_str
+
+ def snd_addr(self, node_name, **kwargs):
+ """ Create a send (node) address"""
+ # Get keyword args
+ topic = kwargs.get("topic")
+ topic_flag = kwargs.get("topic_flag", False)
+ auto_create = kwargs.get("auto_create", True)
+ auto_delete = kwargs.get("auto_delete", False)
+ durable = kwargs.get("durable", False)
+ exclusive = kwargs.get("exclusive", False)
+ ftd_count = kwargs.get("ftd_count")
+ ftd_size = kwargs.get("ftd_size")
+ policy = kwargs.get("policy", "flow-to-disk")
+ exchage_type = kwargs.get("exchage_type")
+
+ create_policy = None
+ if auto_create:
+ create_policy = "always"
+ delete_policy = None
+ if auto_delete:
+ delete_policy = "always"
+ node_type = None
+ if topic != None or topic_flag:
+ node_type = "topic"
+ x_declare_list = ["\"exclusive\": %s" % exclusive]
+ if ftd_count != None or ftd_size != None:
+ queue_policy = ["\'qpid.policy_type\': %s" % policy]
+ if ftd_count:
+ queue_policy.append("\'qpid.max_count\': %d" % ftd_count)
+ if ftd_size:
+ queue_policy.append("\'qpid.max_size\': %d" % ftd_size)
+ x_declare_list.append("arguments: %s" % self._fmt_map(queue_policy))
+ if exchage_type != None:
+ x_declare_list.append("type: %s" % exchage_type)
+
+ return self.addr_fmt(node_name, topic=topic, create_policy=create_policy, delete_policy=delete_policy,
+ node_type=node_type, durable=durable, x_declare_list=x_declare_list)
+
+ def rcv_addr(self, node_name, **kwargs):
+ """ Create a receive (link) address"""
+ # Get keyword args
+ auto_create = kwargs.get("auto_create", True)
+ auto_delete = kwargs.get("auto_delete", False)
+ link_name = kwargs.get("link_name")
+ durable = kwargs.get("durable", False)
+ browse = kwargs.get("browse", False)
+ exclusive = kwargs.get("exclusive", False)
+ binding_list = kwargs.get("binding_list", [])
+ ftd_count = kwargs.get("ftd_count")
+ ftd_size = kwargs.get("ftd_size")
+ policy = kwargs.get("policy", "flow-to-disk")
+
+ create_policy = None
+ if auto_create:
+ create_policy = "always"
+ delete_policy = None
+ if auto_delete:
+ delete_policy = "always"
+ mode = None
+ if browse:
+ mode = "browse"
+ x_declare_list = ["\"exclusive\": %s" % exclusive]
+ if ftd_count != None or ftd_size != None:
+ queue_policy = ["\'qpid.policy_type\': %s" % policy]
+ if ftd_count:
+ queue_policy.append("\'qpid.max_count\': %d" % ftd_count)
+ if ftd_size:
+ queue_policy.append("\'qpid.max_size\': %d" % ftd_size)
+ x_declare_list.append("arguments: %s" % self._fmt_map(queue_policy))
+ x_bindings_list = []
+ for binding in binding_list:
+ x_bindings_list.append("{exchange: %s, key: %s}" % binding)
+ if durable: reliability = 'at-least-once'
+ else: reliability = None
+ return self.addr_fmt(node_name, create_policy=create_policy, delete_policy=delete_policy, mode=mode, link=True,
+ link_name=link_name, durable=durable, x_declare_list=x_declare_list,
+ x_bindings_list=x_bindings_list, link_reliability=reliability)
+
+ def check_message(self, broker, queue, exp_msg, transactional=False, empty=False, ack=True, browse=False):
+ """Check that a message is on a queue by dequeuing it and comparing it to the expected message"""
+ return self.check_messages(broker, queue, [exp_msg], transactional, empty, ack, browse)
+
+ def check_messages(self, broker, queue, exp_msg_list, transactional=False, empty=False, ack=True, browse=False,
+ emtpy_flag=False):
+ """Check that messages is on a queue by dequeuing them and comparing them to the expected messages"""
+ if emtpy_flag:
+ num_msgs = 0
+ else:
+ num_msgs = len(exp_msg_list)
+ ssn = broker.connect().session(transactional=transactional)
+ rcvr = ssn.receiver(self.rcv_addr(queue, browse=browse), capacity=num_msgs)
+ if num_msgs > 0:
+ try:
+ recieved_msg_list = [rcvr.fetch(timeout=0) for i in range(num_msgs)]
+ except Empty:
+ self.assert_(False, "Queue \"%s\" is empty, unable to retrieve expected message %d." % (queue, i))
+ for i in range(0, len(recieved_msg_list)):
+ self.assertEqual(recieved_msg_list[i].content, exp_msg_list[i].content)
+ self.assertEqual(recieved_msg_list[i].correlation_id, exp_msg_list[i].correlation_id)
+ if empty:
+ self._chk_empty(queue, rcvr)
+ if ack:
+ ssn.acknowledge()
+ if transactional:
+ ssn.commit()
+ ssn.connection.close()
+ else:
+ if transactional:
+ ssn.commit()
+ return ssn
+
+
+ # Functions for finding strings in the broker log file (or other files)
+
+ @staticmethod
+ def _read_file(file_name):
+ """Returns the content of file named file_name as a string"""
+ file_handle = file(file_name)
+ try:
+ return file_handle.read()
+ finally:
+ file_handle.close()
+
+ def _get_hits(self, broker, search):
+ """Find all occurrences of the search in the broker log (eliminating possible duplicates from msgs on multiple
+ queues)"""
+ # TODO: Use sets when RHEL-4 is no longer supported
+ hits = []
+ for hit in search.findall(self._read_file(broker.log)):
+ if hit not in hits:
+ hits.append(hit)
+ return hits
+
+ def _reconsile_hits(self, broker, ftd_msgs, release_hits):
+ """Remove entries from list release_hits if they match the message id in ftd_msgs. Check for remaining
+ release_hits."""
+ for msg in ftd_msgs:
+ found = False
+ for hit in release_hits:
+ if str(msg.id) in hit:
+ release_hits.remove(hit)
+ #print "Found %s in %s" % (msg.id, broker.log)
+ found = True
+ break
+ if not found:
+ self.assert_(False, "Unable to locate released message %s in log %s" % (msg.id, broker.log))
+ if len(release_hits) > 0:
+ err = "Messages were unexpectedly released in log %s:\n" % broker.log
+ for hit in release_hits:
+ err += " %s\n" % hit
+ self.assert_(False, err)
+
+ def check_msg_release(self, broker, ftd_msgs):
+ """ Check for 'Content released' messages in broker log for messages in ftd_msgs"""
+ hits = self._get_hits(broker, re.compile("debug Message id=\"[0-9a-f-]{36}\"; pid=0x[0-9a-f]+: "
+ "Content released$", re.MULTILINE))
+ self._reconsile_hits(broker, ftd_msgs, hits)
+
+ def check_msg_release_on_commit(self, broker, ftd_msgs):
+ """ Check for 'Content released on commit' messages in broker log for messages in ftd_msgs"""
+ hits = self._get_hits(broker, re.compile("debug Message id=\"[0-9a-f-]{36}\"; pid=0x[0-9a-f]+: "
+ "Content released on commit$", re.MULTILINE))
+ self._reconsile_hits(broker, ftd_msgs, hits)
+
+ def check_msg_release_on_recover(self, broker, ftd_msgs):
+ """ Check for 'Content released after recovery' messages in broker log for messages in ftd_msgs"""
+ hits = self._get_hits(broker, re.compile("debug Message id=\"[0-9a-f-]{36}\"; pid=0x[0-9a-f]+: "
+ "Content released after recovery$", re.MULTILINE))
+ self._reconsile_hits(broker, ftd_msgs, hits)
+
+ def check_msg_block(self, broker, ftd_msgs):
+ """Check for 'Content release blocked' messages in broker log for messages in ftd_msgs"""
+ hits = self._get_hits(broker, re.compile("debug Message id=\"[0-9a-f-]{36}\"; pid=0x[0-9a-f]+: "
+ "Content release blocked$", re.MULTILINE))
+ self._reconsile_hits(broker, ftd_msgs, hits)
+
+ def check_msg_block_on_commit(self, broker, ftd_msgs):
+ """Check for 'Content release blocked' messages in broker log for messages in ftd_msgs"""
+ hits = self._get_hits(broker, re.compile("debug Message id=\"[0-9a-f-]{36}\"; pid=0x[0-9a-f]+: "
+ "Content release blocked on commit$", re.MULTILINE))
+ self._reconsile_hits(broker, ftd_msgs, hits)
diff --git a/qpid/cpp/src/tests/linearstore/run_long_python_tests b/qpid/cpp/src/tests/linearstore/run_long_python_tests
new file mode 100644
index 0000000000..be6380302c
--- /dev/null
+++ b/qpid/cpp/src/tests/linearstore/run_long_python_tests
@@ -0,0 +1,21 @@
+#!/usr/bin/env 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.
+#
+
+./run_python_tests LONG_TEST
diff --git a/qpid/cpp/src/tests/linearstore/run_python_tests b/qpid/cpp/src/tests/linearstore/run_python_tests
new file mode 100755
index 0000000000..4ff212a71c
--- /dev/null
+++ b/qpid/cpp/src/tests/linearstore/run_python_tests
@@ -0,0 +1,42 @@
+#!/usr/bin/env 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.
+#
+
+source ${QPID_TEST_COMMON}
+
+ensure_python_tests
+
+#Add our directory to the python path
+export PYTHONPATH=$srcdir/linearstore:${PYTHONPATH}
+
+MODULENAME=python_tests
+
+echo "Running Python tests in module ${MODULENAME}..."
+
+QPID_PORT=${QPID_PORT:-5672}
+FAILING=${FAILING:-/dev/null}
+PYTHON_TESTS=${PYTHON_TESTS:-$*}
+
+OUTDIR=${MODULENAME}.tmp
+rm -rf ${OUTDIR}
+
+# To debug a test, add the following options to the end of the following line:
+# -v DEBUG -c qpid.messaging.io.ops [*.testName]
+${QPID_PYTHON_TEST} -m ${MODULENAME} -I ${FAILING} -DOUTDIR=${OUTDIR} ${PYTHON_TEST} || exit 1
+
diff --git a/qpid/cpp/src/tests/linearstore/run_short_python_tests b/qpid/cpp/src/tests/linearstore/run_short_python_tests
new file mode 100644
index 0000000000..9b9e7c59be
--- /dev/null
+++ b/qpid/cpp/src/tests/linearstore/run_short_python_tests
@@ -0,0 +1,21 @@
+#!/usr/bin/env 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.
+#
+
+./run_python_tests SHORT_TEST
diff --git a/qpid/cpp/src/tests/linearstore/tx-test-soak.sh b/qpid/cpp/src/tests/linearstore/tx-test-soak.sh
new file mode 100755
index 0000000000..7d5581961f
--- /dev/null
+++ b/qpid/cpp/src/tests/linearstore/tx-test-soak.sh
@@ -0,0 +1,275 @@
+#! /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.
+#
+
+# tx-test-soak
+#
+# Basic test methodology:
+# 1. Start broker
+# 2. Run qpid-txtest against broker using randomly generated parameters
+# 3. After some time, kill the broker using SIGKILL
+# 4. Restart broker, recover messages
+# 5. Run qpid-txtest against broker in check mode, which checks that all expected messages are present.
+# 6. Wash, rinse, repeat... The number of runs is determined by ${NUM_RUNS}
+
+# NOTE: The following is based on typical development tree paths, not installed paths
+
+NUM_RUNS=1000
+BASE_DIR=${HOME}/RedHat
+CMAKE_BUILD_DIR=${BASE_DIR}/q.cm
+
+# Infrequently adjusted
+RESULT_BASE_DIR_PREFIX=${BASE_DIR}/results.tx-test-soak
+RECOVER_TIME_PER_QUEUE=1
+STORE_MODULE="linearstore.so"
+BROKER_LOG_LEVEL="info+"
+BROKER_MANAGEMENT="no" # "no" or "yes"
+TRUNCATE_INTERVAL=10
+MAX_DISK_PERC_USED=90
+
+# Constants (don't adjust these)
+export BASE_DIR
+RELATIVE_BASE_DIR=`python -c "import os,os.path; print os.path.relpath(os.environ['BASE_DIR'], os.environ['PWD'])"`
+export PYTHONPATH=${BASE_DIR}/qpid/python:${BASE_DIR}/qpid/extras/qmf/src/py:${BASE_DIR}/qpid/tools/src/py
+LOG_FILE_NAME=log.txt
+QPIDD_FN=qpidd
+QPIDD=${CMAKE_BUILD_DIR}/src/${QPIDD_FN}
+TXTEST_FN=qpid-txtest
+TXTEST=${CMAKE_BUILD_DIR}/src/tests/${TXTEST_FN}
+ANALYZE_FN=qpid_qls_analyze.py
+ANALYZE=${BASE_DIR}/qpid/tools/src/py/${ANALYZE_FN}
+ANALYZE_ARGS="--efp --show-recs --stats"
+QPIDD_BASE_ARGS="--load-module ${STORE_MODULE} -m ${BROKER_MANAGEMENT} --auth no --default-flow-stop-threshold 0 --default-flow-resume-threshold 0 --default-queue-limit 0 --store-dir ${BASE_DIR} --log-enable ${BROKER_LOG_LEVEL} --log-to-stderr no --log-to-stdout no"
+TXTEST_INIT_STR="--init yes --transfer no --check no"
+TXTEST_RUN_STR="--init no --transfer yes --check no"
+TXTEST_CHK_STR="--init no --transfer no --check yes"
+SUCCESS_MSG="All expected messages were retrieved."
+TIMESTAMP_FORMAT="+%Y-%m-%d_%H:%M:%S"
+ANSI_RED="\e[1;31m"
+ANSI_NONE="\e[0m"
+DEFAULT_EFP_DIR=2048k
+DEFAULT_EFP_SIZE=2101248
+SIG_KILL=-9
+SIG_TERM=-15
+
+# Creates a random number into the variable named in string $1 in the range [$2..$3] (both inclusive).
+# $1: variable name as string to which random value is assigned
+# $2: minimum inclusive range of random number
+# $3: maximum inclusive range of random number
+get_random() {
+ eval $1=`python -S -c "import random; print random.randint($2,$3)"`
+}
+
+# Uses anon-uniform distribution to set a random message size.
+# Most messages must be small (0 - 1k), but we need a few medium (10k) and large (100k) ones also.
+# Sets message size into var ${MSG_SIZE}
+set_message_size() {
+ local key=0
+ get_random "key" 1 10
+ if (( "${key}" == "10" )); then # 1 out of 10 - very large
+ get_random "MSG_SIZE" 100000 1000000
+ FILE_SIZE_MULTIPLIER=3
+ elif (( "${key}" >= "8" )); then # 2 out of 10 - large
+ get_random "MSG_SIZE" 10000 100000
+ FILE_SIZE_MULTIPLIER=2
+ elif (( "${key}" >= "6" )); then # 2 out of 10 - medium
+ get_random "MSG_SIZE" 1000 10000
+ FILE_SIZE_MULTIPLIER=1
+ else # 5 out of 10 - small
+ get_random "MSG_SIZE" 10 1000
+ FILE_SIZE_MULTIPLIER=1
+ fi
+}
+
+# Start or restart broker
+# $1: Log suffix: either "A" or "B". If "A", broker is started with truncation, otherwise broker is restarted with recovery.
+# $2: Truncate flag - only used if Log suffix is "A": if true, then truncate store
+# The PID of the broker is returned in ${QPIDD_PID}
+start_broker() {
+ local truncate_val
+ local truncate_str
+ if [[ "$1" == "A" ]]; then
+ if [[ $2 == true ]]; then
+ truncate_val="yes"
+ truncate_str="(Store truncated)"
+ if [[ -e ${BASE_DIR}/qls/p001/efp/${DEFAULT_EFP_DIR} ]]; then
+ for f in ${BASE_DIR}/qls/p001/efp/${DEFAULT_EFP_DIR}/*; do
+ local filesize=`stat -c%s "${f}"`
+ if (( ${filesize} != ${DEFAULT_EFP_SIZE} )); then
+ rm ${f}
+ fi
+ done
+ fi
+ else
+ truncate_val="no"
+ fi
+ else
+ truncate_val="no"
+ fi
+ echo "${QPIDD} ${QPIDD_BASE_ARGS} --truncate ${truncate_val} --log-to-file ${RESULT_DIR}/qpidd.$1.log &" > ${RESULT_DIR}/qpidd.$1.cmd
+ ${QPIDD} ${QPIDD_BASE_ARGS} --truncate ${truncate_val} --log-to-file ${RESULT_DIR}/qpidd.$1.log &
+ QPIDD_PID=$!
+ echo "Broker PID=${QPIDD_PID} ${truncate_str}" | tee -a ${LOG_FILE}
+}
+
+# Start or evaluate results of transaction test client
+# $1: Log suffix flag: either "A" or "B". If "A", client is started in test mode, otherwise client evaluates recovery.
+start_tx_test() {
+ local tx_test_params="--messages-per-tx ${MSGS_PER_TX} --tx-count 1000000 --total-messages ${TOT_MSGS} --size ${MSG_SIZE} --queues ${NUM_QUEUES}"
+ if [[ "$1" == "A" ]]; then
+ # Run in background
+ echo "${TXTEST##*/} parameters: ${tx_test_params}" | tee -a ${LOG_FILE}
+ echo "${TXTEST} ${tx_test_params} ${TXTEST_INIT_STR} &> ${RESULT_DIR}/txtest.$1.log" > ${RESULT_DIR}/txtest.$1.cmd
+ ${TXTEST} ${tx_test_params} ${TXTEST_INIT_STR} &> ${RESULT_DIR}/txtest.$1.log
+ echo "${TXTEST} ${tx_test_params} ${TXTEST_RUN_STR} &> ${RESULT_DIR}/txtest.$1.log &" >> ${RESULT_DIR}/txtest.$1.cmd
+ ${TXTEST} ${tx_test_params} ${TXTEST_RUN_STR} &> ${RESULT_DIR}/txtest.$1.log &
+ else
+ # Run in foreground
+ #echo "${TXTEST##*/} ${tx_test_params} ${TXTEST_CHK_STR}" | tee -a ${LOG_FILE}
+ echo "${TXTEST} ${tx_test_params} ${TXTEST_CHK_STR} &> ${RESULT_DIR}/txtest.$1.log" > ${RESULT_DIR}/txtest.$1.cmd
+ ${TXTEST} ${tx_test_params} ${TXTEST_CHK_STR} &> ${RESULT_DIR}/txtest.$1.log
+ fi
+}
+
+# Search for the presence of core.* files, move them into the current result directory and run gdb against them.
+# No params
+process_core_files() {
+ ls core.* &> /dev/null
+ if (( "$?" == "0" )); then
+ for cf in core.*; do
+ gdb --batch --quiet -ex "thread apply all bt" -ex "quit" ${QPIDD} ${cf} &> ${RESULT_DIR}/${cf##*/}.gdb.txt
+ gdb --batch --quiet -ex "thread apply all bt full" -ex "quit" ${QPIDD} ${cf} &> ${RESULT_DIR}/${cf##*/}.gdb-full.txt
+ cat ${RESULT_DIR}/${cf##*/}.gdb.txt
+ mv ${cf} ${RESULT_DIR}/
+ echo "Core file ${cf##*/} found and recovered"
+ done
+ fi
+}
+
+# Kill a process quietly
+# $1: Signal
+# $2: PID
+kill_process() {
+ kill ${1} ${2} &>> ${LOG_FILE}
+ wait ${2} &>> ${LOG_FILE}
+}
+
+# Check that test can run: No other copy of qpidd running, enough disk space
+check_ready_to_run() {
+ # Check no copy of qpidd is running
+ PID=`pgrep ${QPIDD_FN}`
+ if [[ "$?" == "0" ]]; then
+ echo "ERROR: qpidd running as pid ${PID}"
+ exit 1
+ fi
+ # Check disk is < 90% full
+ local perc_full=`df -h ${HOME} | tail -1 | awk '{print substr($5,0, length($5)-1)}'`
+ if (( ${perc_full} >= ${MAX_DISK_PERC_USED} )); then
+ echo "ERROR: Disk is too close to full (${perc_full}%)"
+ exit 2
+ fi
+}
+
+# Analyze store files
+# $1: Log suffix flag: either "A" or "B". If "A", client is started in test mode, otherwise client evaluates recovery.
+analyze_store() {
+ ${ANALYZE} ${ANALYZE_ARGS} ${BASE_DIR}/qls &> ${RESULT_DIR}/qls_analysis.$1.log
+ echo >> ${RESULT_DIR}/qls_analysis.$1.log
+ echo "----------------------------------------------------------" >> ${RESULT_DIR}/qls_analysis.$1.log
+ echo "With transactional reconsiliation:" >> ${RESULT_DIR}/qls_analysis.$1.log
+ echo >> ${RESULT_DIR}/qls_analysis.$1.log
+ ${ANALYZE} ${ANALYZE_ARGS} --txn ${BASE_DIR}/qls &>> ${RESULT_DIR}/qls_analysis.$1.log
+}
+
+ulimit -c unlimited # Allow core files to be created
+
+RESULT_BASE_DIR_SUFFIX=`date "${TIMESTAMP_FORMAT}"`
+RESULT_BASE_DIR="${RESULT_BASE_DIR_PREFIX}.${RESULT_BASE_DIR_SUFFIX}"
+LOG_FILE=${RESULT_BASE_DIR}/${LOG_FILE_NAME}
+if [[ -n "${RESULT_BASE_DIR}" ]]; then
+ rm -rf ${RESULT_BASE_DIR}
+fi
+
+mkdir -p ${RESULT_BASE_DIR}
+for rn in `seq ${NUM_RUNS}`; do
+ # === Prepare result dir, check ready to run test, set run vars ===
+ RESULT_DIR=${RESULT_BASE_DIR}/run_${rn}
+ mkdir -p ${RESULT_DIR}
+ check_ready_to_run
+ if (( (${rn} - 1) % ${TRUNCATE_INTERVAL} == 0 )) || [[ -n ${ERROR_FLAG} ]]; then
+ TRUNCATE_FLAG=true
+ else
+ TRUNCATE_FLAG=false
+ fi
+ set_message_size
+ get_random "MSGS_PER_TX" 1 20
+ get_random "TOT_MSGS" 100 1000
+ get_random "NUM_QUEUES" 2 15
+ MIN_RUNTIME=$(( 20 * ${FILE_SIZE_MULTIPLIER} ))
+ MAX_RUNTIME=$(( 120 * ${FILE_SIZE_MULTIPLIER} ))
+ get_random "RUN_TIME" ${MIN_RUNTIME} ${MAX_RUNTIME}
+ RECOVER_TIME=$(( ${NUM_QUEUES} * ${RECOVER_TIME_PER_QUEUE} * ${FILE_SIZE_MULTIPLIER} ))
+ echo "Run ${rn} of ${NUM_RUNS} ==============" | tee -a ${LOG_FILE}
+
+ # === PART A: Initial run of qpid-txtest ===
+ start_broker "A" ${TRUNCATE_FLAG}
+ sleep ${RECOVER_TIME} # Need a way to test if broker has started here
+ start_tx_test "A"
+ echo "Running for ${RUN_TIME} secs..." | tee -a ${LOG_FILE}
+ sleep ${RUN_TIME}
+ kill_process ${SIG_KILL} ${QPIDD_PID}
+ sleep 2
+ analyze_store "A"
+ tar -czf ${RESULT_DIR}/qls_A.tar.gz ${RELATIVE_BASE_DIR}/qls
+
+ # === PART B: Recovery and check ===
+ start_broker "B"
+ echo "Recover time=${RECOVER_TIME} secs..." | tee -a ${LOG_FILE}
+ sleep ${RECOVER_TIME} # Need a way to test if broker has started here
+ start_tx_test "B"
+ sleep 1
+ kill_process ${SIG_TERM} ${QPIDD_PID}
+ sleep 2
+ PID=`pgrep ${QPIDD_FN}`
+ if [[ "$?" == "0" ]]; then
+ kill_process ${SIG_KILL} ${PID}
+ sleep 2
+ fi
+ analyze_store "B"
+ tar -czf ${RESULT_DIR}/qls_B.tar.gz ${RELATIVE_BASE_DIR}/qls
+
+ # === Check for errors, cores and exceptions in logs ===
+ grep -Hn "jexception" ${RESULT_DIR}/qpidd.A.log | tee -a ${LOG_FILE}
+ grep -Hn "jexception" ${RESULT_DIR}/qpidd.B.log | tee -a ${LOG_FILE}
+ grep -Hn "Traceback (most recent call last):" ${RESULT_DIR}/qls_analysis.A.log | tee -a ${LOG_FILE}
+ grep -Hn "Traceback (most recent call last):" ${RESULT_DIR}/qls_analysis.B.log | tee -a ${LOG_FILE}
+ grep "${SUCCESS_MSG}" ${RESULT_DIR}/txtest.B.log &> /dev/null
+ if [[ "$?" != "0" ]]; then
+ echo "ERROR in run ${rn}" >> ${LOG_FILE}
+ echo -e "${ANSI_RED}ERROR${ANSI_NONE} in run ${rn}"
+ ERROR_FLAG=true
+ else
+ unset ERROR_FLAG
+ fi
+ sleep 2
+ process_core_files
+ echo | tee -a ${LOG_FILE}
+done
+
diff --git a/qpid/cpp/src/tests/logging.cpp b/qpid/cpp/src/tests/logging.cpp
new file mode 100644
index 0000000000..32cd09d73d
--- /dev/null
+++ b/qpid/cpp/src/tests/logging.cpp
@@ -0,0 +1,512 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include "test_tools.h"
+#include "qpid/log/Logger.h"
+#include "qpid/log/Options.h"
+#include "qpid/log/OstreamOutput.h"
+#include "qpid/memory.h"
+#include "qpid/Options.h"
+#if defined (_WIN32)
+# include "qpid/log/windows/SinkOptions.h"
+#else
+# include "qpid/log/posix/SinkOptions.h"
+#endif
+
+#include <boost/test/floating_point_comparison.hpp>
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/format.hpp>
+#include "unit_test.h"
+
+#include <exception>
+#include <fstream>
+#include <time.h>
+
+
+namespace qpid {
+namespace tests {
+
+QPID_AUTO_TEST_SUITE(loggingTestSuite)
+
+using namespace std;
+using namespace qpid::log;
+using boost::ends_with;
+using boost::contains;
+using boost::format;
+
+QPID_AUTO_TEST_CASE(testStatementInit) {
+ Statement s=QPID_LOG_STATEMENT_INIT(debug); int line=__LINE__;
+ BOOST_CHECK(!s.enabled);
+ BOOST_CHECK_EQUAL(string(__FILE__), s.file);
+ BOOST_CHECK_EQUAL(line, s.line);
+ BOOST_CHECK_EQUAL(debug, s.level);
+}
+
+
+QPID_AUTO_TEST_CASE(testSelector_enable) {
+ Selector s;
+ // Simple enable
+ s.enable(debug,"foo");
+ BOOST_CHECK(s.isEnabled(debug,"foo"));
+ BOOST_CHECK(!s.isEnabled(error,"foo"));
+ BOOST_CHECK(!s.isEnabled(error,"bar"));
+
+ // Substring match
+ BOOST_CHECK(s.isEnabled(debug, "bazfoobar"));
+ BOOST_CHECK(!s.isEnabled(debug, "bazbar"));
+
+ // Different levels for different substrings.
+ s.enable(info, "bar");
+ BOOST_CHECK(s.isEnabled(debug, "foobar"));
+ BOOST_CHECK(s.isEnabled(info, "foobar"));
+ BOOST_CHECK(!s.isEnabled(debug, "bar"));
+ BOOST_CHECK(!s.isEnabled(info, "foo"));
+
+ // Enable-strings
+ s.enable("notice:blob");
+ BOOST_CHECK(s.isEnabled(notice, "blob"));
+ s.enable("error+:oops");
+ BOOST_CHECK(s.isEnabled(error, "oops"));
+ BOOST_CHECK(s.isEnabled(critical, "oops"));
+}
+
+QPID_AUTO_TEST_CASE(testSelector_disable) {
+ Selector s;
+ // Simple enable/disable
+ s.enable(trace,"foo");
+ BOOST_CHECK(s.isEnabled(trace,"foo"));
+ BOOST_CHECK(!s.isDisabled(trace,"foo"));
+ s.disable(trace,"foo");
+ BOOST_CHECK(s.isEnabled(trace,"foo"));
+ BOOST_CHECK(s.isDisabled(trace,"foo"));
+}
+
+QPID_AUTO_TEST_CASE(testStatementEnabled) {
+ // Verify that the singleton enables and disables static
+ // log statements.
+ Logger& l = Logger::instance();
+ ScopedSuppressLogging ls(l);
+ l.select(Selector(debug));
+ static Statement s=QPID_LOG_STATEMENT_INIT(debug);
+ BOOST_CHECK(!s.enabled);
+ static Statement::Initializer init(s);
+ BOOST_CHECK(s.enabled);
+
+ static Statement s2=QPID_LOG_STATEMENT_INIT(warning);
+ static Statement::Initializer init2(s2);
+ BOOST_CHECK(!s2.enabled);
+
+ l.select(Selector(warning));
+ BOOST_CHECK(!s.enabled);
+ BOOST_CHECK(s2.enabled);
+}
+
+struct TestOutput : public Logger::Output {
+ vector<string> msg;
+ vector<Statement> stmt;
+
+ TestOutput(Logger& l) {
+ l.output(std::auto_ptr<Logger::Output>(this));
+ }
+
+ void log(const Statement& s, const string& m) {
+ msg.push_back(m);
+ stmt.push_back(s);
+ }
+ string last() { return msg.back(); }
+};
+
+using boost::assign::list_of;
+
+QPID_AUTO_TEST_CASE(testLoggerOutput) {
+ Logger l;
+ l.clear();
+ l.select(Selector(debug));
+ Statement s=QPID_LOG_STATEMENT_INIT(debug);
+
+ TestOutput* out=new TestOutput(l);
+
+ // Verify message is output.
+ l.log(s, "foo");
+ vector<string> expect=list_of("foo\n");
+ BOOST_CHECK_EQUAL(expect, out->msg);
+
+ // Verify multiple outputs
+ TestOutput* out2=new TestOutput(l);
+ l.log(Statement(), "baz");
+ expect.push_back("baz\n");
+ BOOST_CHECK_EQUAL(expect, out->msg);
+ expect.erase(expect.begin());
+ BOOST_CHECK_EQUAL(expect, out2->msg);
+}
+
+QPID_AUTO_TEST_CASE(testMacro) {
+ Logger& l=Logger::instance();
+ ScopedSuppressLogging ls(l);
+ l.select(Selector(info));
+ TestOutput* out=new TestOutput(l);
+ QPID_LOG(info, "foo");
+ vector<string> expect=list_of("foo\n");
+ BOOST_CHECK_EQUAL(expect, out->msg);
+ BOOST_CHECK_EQUAL(__FILE__, out->stmt.front().file);
+
+ // Not enabled:
+ QPID_LOG(debug, "bar");
+ BOOST_CHECK_EQUAL(expect, out->msg);
+
+ QPID_LOG(info, 42 << " bingo");
+ expect.push_back("42 bingo\n");
+ BOOST_CHECK_EQUAL(expect, out->msg);
+}
+
+QPID_AUTO_TEST_CASE(testLoggerFormat) {
+ Logger& l = Logger::instance();
+ ScopedSuppressLogging ls(l);
+ l.select(Selector(critical));
+ TestOutput* out=new TestOutput(l);
+
+ l.format(Logger::FILE);
+ QPID_LOG(critical, "foo");
+ BOOST_CHECK_EQUAL(out->last(), string(__FILE__)+": foo\n");
+
+ l.format(Logger::FILE|Logger::LINE);
+ QPID_LOG(critical, "foo");
+ BOOST_CHECK_EQUAL(out->last().find(__FILE__), 0u);
+
+ l.format(Logger::FUNCTION);
+ QPID_LOG(critical, "foo");
+ BOOST_CHECK( ends_with( out->last(), ": foo\n"));
+ string name = out->last().substr(0, out->last().length() - 6);
+ BOOST_CHECK( contains( string(BOOST_CURRENT_FUNCTION), name));
+
+ l.format(Logger::LEVEL);
+ QPID_LOG(critical, "foo");
+ BOOST_CHECK_EQUAL("critical foo\n", out->last());
+}
+
+QPID_AUTO_TEST_CASE(testOstreamOutput) {
+ Logger& l=Logger::instance();
+ ScopedSuppressLogging ls(l);
+ l.select(Selector(error));
+ ostringstream os;
+ l.output(qpid::make_auto_ptr<Logger::Output>(new OstreamOutput(os)));
+ QPID_LOG(error, "foo");
+ QPID_LOG(error, "bar");
+ QPID_LOG(error, "baz");
+ BOOST_CHECK_EQUAL("foo\nbar\nbaz\n", os.str());
+}
+
+#if 0 // This test requires manual intervention. Normally disabled.
+QPID_AUTO_TEST_CASE(testSyslogOutput) {
+ Logger& l=Logger::instance();
+ Logger::StateSaver ls(l);
+ l.clear();
+ l.select(Selector(info));
+ l.syslog("qpid_test");
+ QPID_LOG(info, "Testing QPID");
+ BOOST_ERROR("Manually verify that /var/log/messages contains a recent line 'Testing QPID'");
+}
+#endif // 0
+
+int count() {
+ static int n = 0;
+ return n++;
+}
+
+int loggedCount() {
+ static int n = 0;
+ QPID_LOG(debug, "counting: " << n);
+ return n++;
+}
+
+
+using namespace qpid::sys;
+
+// Measure CPU time.
+clock_t timeLoop(int times, int (*fp)()) {
+ clock_t start=clock();
+ while (times-- > 0)
+ (*fp)();
+ return clock() - start;
+}
+
+// Overhead test disabled because it consumes a ton of CPU and takes
+// forever under valgrind. Not friendly for regular test runs.
+//
+#if 0
+QPID_AUTO_TEST_CASE(testOverhead) {
+ // Ensure that the ratio of CPU time for an incrementing loop
+ // with and without disabled log statements is in acceptable limits.
+ //
+ int times=100000000;
+ clock_t noLog=timeLoop(times, count);
+ clock_t withLog=timeLoop(times, loggedCount);
+ double ratio=double(withLog)/double(noLog);
+
+ // NB: in initial tests the ratio was consistently below 1.5,
+ // 2.5 is reasonable and should avoid spurios failures
+ // due to machine load.
+ //
+ BOOST_CHECK_SMALL(ratio, 2.5);
+}
+#endif // 0
+
+Statement statement(
+ Level level, const char* file="", int line=0, const char* fn=0)
+{
+ Statement s={0, file, line, fn, level, ::qpid::log::unspecified};
+ return s;
+}
+
+
+#define ARGC(argv) (sizeof(argv)/sizeof(char*))
+
+QPID_AUTO_TEST_CASE(testOptionsParse) {
+ const char* argv[]={
+ 0,
+ "--log-enable", "error+:foo",
+ "--log-enable", "debug:bar",
+ "--log-enable", "info",
+ "--log-disable", "error+:foo",
+ "--log-disable", "debug:bar",
+ "--log-disable", "info",
+ "--log-to-stderr", "no",
+ "--log-to-file", "logout",
+ "--log-level", "yes",
+ "--log-source", "1",
+ "--log-thread", "true",
+ "--log-function", "YES"
+ };
+ qpid::log::Options opts("");
+#ifdef _WIN32
+ qpid::log::windows::SinkOptions sinks("test");
+#else
+ qpid::log::posix::SinkOptions sinks("test");
+#endif
+ opts.parse(ARGC(argv), const_cast<char**>(argv));
+ sinks = *opts.sinkOptions;
+ vector<string> expect=list_of("error+:foo")("debug:bar")("info");
+ BOOST_CHECK_EQUAL(expect, opts.selectors);
+ BOOST_CHECK_EQUAL(expect, opts.deselectors);
+ BOOST_CHECK(!sinks.logToStderr);
+ BOOST_CHECK(!sinks.logToStdout);
+ BOOST_CHECK(sinks.logFile == "logout");
+ BOOST_CHECK(opts.level);
+ BOOST_CHECK(opts.source);
+ BOOST_CHECK(opts.function);
+ BOOST_CHECK(opts.thread);
+}
+
+QPID_AUTO_TEST_CASE(testOptionsDefault) {
+ qpid::log::Options opts("");
+#ifdef _WIN32
+ qpid::log::windows::SinkOptions sinks("test");
+#else
+ qpid::log::posix::SinkOptions sinks("test");
+#endif
+ sinks = *opts.sinkOptions;
+ BOOST_CHECK(sinks.logToStderr);
+ BOOST_CHECK(!sinks.logToStdout);
+ BOOST_CHECK(sinks.logFile.length() == 0);
+ vector<string> expect=list_of("notice+");
+ BOOST_CHECK_EQUAL(expect, opts.selectors);
+ BOOST_CHECK(opts.time && opts.level);
+ BOOST_CHECK(!(opts.source || opts.function || opts.thread));
+}
+
+QPID_AUTO_TEST_CASE(testSelectorFromOptions) {
+ const char* argv[]={
+ 0,
+ "--log-enable", "error+:foo",
+ "--log-enable", "debug:bar",
+ "--log-enable", "info"
+ };
+ qpid::log::Options opts("");
+ opts.parse(ARGC(argv), const_cast<char**>(argv));
+ vector<string> expect=list_of("error+:foo")("debug:bar")("info");
+ BOOST_CHECK_EQUAL(expect, opts.selectors);
+ Selector s(opts);
+ BOOST_CHECK(!s.isEnabled(warning, "x"));
+ BOOST_CHECK(!s.isEnabled(debug, "x"));
+ BOOST_CHECK(s.isEnabled(debug, "bar"));
+ BOOST_CHECK(s.isEnabled(error, "foo"));
+ BOOST_CHECK(s.isEnabled(critical, "foo"));
+}
+
+QPID_AUTO_TEST_CASE(testDeselectorFromOptions) {
+ const char* argv[]={
+ 0,
+ "--log-disable", "error-:foo",
+ "--log-disable", "debug:bar",
+ "--log-disable", "info"
+ };
+ qpid::log::Options opts("");
+ opts.parse(ARGC(argv), const_cast<char**>(argv));
+ vector<string> expect=list_of("error-:foo")("debug:bar")("info");
+ BOOST_CHECK_EQUAL(expect, opts.deselectors);
+ Selector s(opts);
+ BOOST_CHECK(!s.isDisabled(warning, "x"));
+ BOOST_CHECK(!s.isDisabled(debug, "x"));
+ BOOST_CHECK(s.isDisabled(debug, "bar"));
+ BOOST_CHECK(s.isDisabled(trace, "foo"));
+ BOOST_CHECK(s.isDisabled(debug, "foo"));
+ BOOST_CHECK(s.isDisabled(info, "foo"));
+ BOOST_CHECK(s.isDisabled(notice, "foo"));
+ BOOST_CHECK(s.isDisabled(warning, "foo"));
+ BOOST_CHECK(s.isDisabled(error, "foo"));
+ BOOST_CHECK(!s.isDisabled(critical, "foo"));
+}
+
+QPID_AUTO_TEST_CASE(testMultiConflictingSelectorFromOptions) {
+ const char* argv[]={
+ 0,
+ "--log-enable", "trace+:foo",
+ "--log-disable", "error-:foo",
+ "--log-enable", "debug:bar",
+ "--log-disable", "debug:bar",
+ "--log-enable", "info",
+ "--log-disable", "info",
+ "--log-enable", "debug+:Model",
+ "--log-disable", "info-:Model"
+ };
+ qpid::log::Options opts("");
+ opts.parse(ARGC(argv), const_cast<char**>(argv));
+ Selector s(opts);
+ BOOST_CHECK(!s.isEnabled(warning, "x", log::broker));
+ BOOST_CHECK(!s.isEnabled(debug, "x", log::broker));
+ BOOST_CHECK(!s.isEnabled(trace, "foo", log::broker));
+ BOOST_CHECK(!s.isEnabled(debug, "foo", log::broker));
+ BOOST_CHECK(!s.isEnabled(info, "foo", log::broker));
+ BOOST_CHECK(!s.isEnabled(notice, "foo", log::broker));
+ BOOST_CHECK(!s.isEnabled(warning, "foo", log::broker));
+ BOOST_CHECK(!s.isEnabled(error, "foo", log::broker));
+ BOOST_CHECK(s.isEnabled(critical, "foo", log::broker));
+ BOOST_CHECK(!s.isEnabled(debug, "bar", log::model));
+ BOOST_CHECK(!s.isEnabled(trace, "zaz", log::model));
+ BOOST_CHECK(!s.isEnabled(debug, "zaz", log::model));
+ BOOST_CHECK(!s.isEnabled(info, "zaz", log::model));
+ BOOST_CHECK(s.isEnabled(notice, "zaz", log::model));
+ BOOST_CHECK(s.isEnabled(warning, "zaz", log::model));
+ BOOST_CHECK(s.isEnabled(error, "zaz", log::model));
+ BOOST_CHECK(s.isEnabled(critical, "zaz", log::model));
+}
+
+QPID_AUTO_TEST_CASE(testLoggerStateure) {
+ Logger& l=Logger::instance();
+ ScopedSuppressLogging ls(l);
+ qpid::log::Options opts("test");
+ const char* argv[]={
+ 0,
+ "--log-time", "no",
+ "--log-source", "yes",
+ "--log-to-stderr", "no",
+ "--log-to-file", "logging.tmp",
+ "--log-enable", "critical"
+ };
+ opts.parse(ARGC(argv), const_cast<char**>(argv));
+ l.configure(opts);
+ QPID_LOG_CAT(critical, test, "foo"); int srcline=__LINE__;
+ ifstream log("logging.tmp");
+ string line;
+ getline(log, line);
+ string expect=(format("[Test] critical %s:%d: foo")%__FILE__%srcline).str();
+ BOOST_CHECK_EQUAL(expect, line);
+ log.close();
+ unlink("logging.tmp");
+}
+
+QPID_AUTO_TEST_CASE(testQuoteNonPrintable) {
+ Logger& l=Logger::instance();
+ ScopedSuppressLogging ls(l);
+ qpid::log::Options opts("test");
+ opts.time=false;
+#ifdef _WIN32
+ qpid::log::windows::SinkOptions *sinks =
+ dynamic_cast<qpid::log::windows::SinkOptions *>(opts.sinkOptions.get());
+#else
+ qpid::log::posix::SinkOptions *sinks =
+ dynamic_cast<qpid::log::posix::SinkOptions *>(opts.sinkOptions.get());
+#endif
+ sinks->logToStderr = false;
+ sinks->logFile = "logging.tmp";
+ l.configure(opts);
+
+ char s[] = "null\0tab\tspace newline\nret\r\x80\x99\xff";
+ string str(s, sizeof(s));
+ QPID_LOG_CAT(critical, test, str);
+ ifstream log("logging.tmp");
+ string line;
+ getline(log, line, '\0');
+ string expect="[Test] critical null\\x00tab\tspace newline\nret\r\\x80\\x99\\xFF\\x00\n";
+ BOOST_CHECK_EQUAL(expect, line);
+ log.close();
+ unlink("logging.tmp");
+}
+
+QPID_AUTO_TEST_CASE(testSelectorElements) {
+ SelectorElement s("debug");
+ BOOST_CHECK_EQUAL(s.levelStr, "debug");
+ BOOST_CHECK_EQUAL(s.patternStr, "");
+ BOOST_CHECK_EQUAL(s.level, debug);
+ BOOST_CHECK(!s.isDisable);
+ BOOST_CHECK(!s.isCategory);
+ BOOST_CHECK(!s.isLevelAndAbove);
+ BOOST_CHECK(!s.isLevelAndBelow);
+
+ SelectorElement t("debug:Broker");
+ BOOST_CHECK_EQUAL(t.levelStr, "debug");
+ BOOST_CHECK_EQUAL(t.patternStr, "Broker");
+ BOOST_CHECK_EQUAL(t.level, debug);
+ BOOST_CHECK_EQUAL(t.category, broker);
+ BOOST_CHECK(!t.isDisable);
+ BOOST_CHECK(t.isCategory);
+ BOOST_CHECK(!t.isLevelAndAbove);
+ BOOST_CHECK(!t.isLevelAndBelow);
+
+ SelectorElement u("info+:qmf::");
+ BOOST_CHECK_EQUAL(u.levelStr, "info");
+ BOOST_CHECK_EQUAL(u.patternStr, "qmf::");
+ BOOST_CHECK_EQUAL(u.level, info);
+ BOOST_CHECK(!u.isDisable);
+ BOOST_CHECK(!u.isCategory);
+ BOOST_CHECK(u.isLevelAndAbove);
+ BOOST_CHECK(!u.isLevelAndBelow);
+
+ SelectorElement v("critical-");
+ BOOST_CHECK_EQUAL(v.levelStr, "critical");
+ BOOST_CHECK_EQUAL(v.patternStr, "");
+ BOOST_CHECK_EQUAL(v.level, critical);
+ BOOST_CHECK(!v.isDisable);
+ BOOST_CHECK(!v.isCategory);
+ BOOST_CHECK(!v.isLevelAndAbove);
+ BOOST_CHECK(v.isLevelAndBelow);
+
+ SelectorElement w("!warning-:Management");
+ BOOST_CHECK_EQUAL(w.levelStr, "warning");
+ BOOST_CHECK_EQUAL(w.patternStr, "Management");
+ BOOST_CHECK_EQUAL(w.level, warning);
+ BOOST_CHECK_EQUAL(w.category, management);
+ BOOST_CHECK(w.isDisable);
+ BOOST_CHECK(w.isCategory);
+ BOOST_CHECK(!w.isLevelAndAbove);
+ BOOST_CHECK(w.isLevelAndBelow);
+}
+
+QPID_AUTO_TEST_SUITE_END()
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/misc.py b/qpid/cpp/src/tests/misc.py
new file mode 100644
index 0000000000..257fb9e754
--- /dev/null
+++ b/qpid/cpp/src/tests/misc.py
@@ -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.
+#
+
+from qpid.tests.messaging.implementation import *
+from qpid.tests.messaging import VersionTest
+
+class MiscellaneousTests (VersionTest):
+ """
+ Tests for various aspects of qpidd behaviour
+ """
+ def test_exclusive(self):
+ con = self.create_connection("amqp1.0", True)
+ rcv = con.session().receiver("q; {create:always, node:{properties:{exclusive:True,auto-delete:True}}}")
+
+ other = self.create_connection("amqp1.0", True)
+ try:
+ #can send to the queue
+ snd = other.session().sender("q")
+
+ #can browse the queue
+ browser = other.session().receiver("q; {mode:browse}")
+
+ #can't consume from the queue
+ try:
+ consumer = other.session().receiver("q")
+ assert False, ("Should not be able to consume from exclusively owned queue")
+ except LinkError, e: None
+ try:
+ exclusive = other.session().receiver("q; {create: always, node:{properties:{exclusive:True}}}")
+ assert False, ("Should not be able to consume exclusively from exclusively owned queue")
+ except LinkError, e: None
+ finally:
+ rcv.close()
+ con.close()
+ other.close()
+
+class AutoDeleteExchangeTests(VersionTest):
+ def init_test(self, exchange_type="topic"):
+ rcv = self.ssn.receiver("my-topic; {create:always, node:{type:topic, properties:{'exchange-type':%s, 'auto-delete':True}}}" % exchange_type)
+ snd = self.ssn.sender("my-topic")
+ #send some messages
+ msgs = [Message(content=c) for c in ['a','b','c','d']]
+ for m in msgs: snd.send(m)
+
+ #verify receipt
+ for expected in msgs:
+ msg = rcv.fetch(0)
+ assert msg.content == expected.content
+ self.ssn.acknowledge(msg)
+ return (rcv, snd)
+
+ def on_rcv_detach_test(self, exchange_type="topic"):
+ rcv, snd = self.init_test(exchange_type)
+ rcv.close()
+ #verify exchange is still there
+ snd.send(Message(content="will be dropped"))
+ snd.close()
+ #now verify it is no longer there
+ try:
+ self.ssn.sender("my-topic")
+ assert False, "Attempt to send to deleted exchange should fail"
+ except MessagingError: None
+
+ def on_snd_detach_test(self, exchange_type="topic"):
+ rcv, snd = self.init_test(exchange_type)
+ snd.close()
+ #verify exchange is still there
+ snd = self.ssn.sender("my-topic")
+ snd.send(Message(content="will be dropped"))
+ snd.close()
+ rcv.close()
+ #now verify it is no longer there
+ try:
+ self.ssn.sender("my-topic")
+ assert False, "Attempt to send to deleted exchange should fail"
+ except MessagingError: None
+
+ def test_autodelete_fanout_exchange_on_rcv_detach(self):
+ self.on_rcv_detach_test("fanout")
+
+ def test_autodelete_fanout_exchange_on_snd_detach(self):
+ self.on_snd_detach_test("fanout")
+
+ def test_autodelete_direct_exchange_on_rcv_detach(self):
+ self.on_rcv_detach_test("direct")
+
+ def test_autodelete_direct_exchange_on_snd_detach(self):
+ self.on_snd_detach_test("direct")
+
+ def test_autodelete_topic_exchange_on_rcv_detach(self):
+ self.on_rcv_detach_test("topic")
+
+ def test_autodelete_topic_exchange_on_snd_detach(self):
+ self.on_snd_detach_test("topic")
+
+ def test_autodelete_headers_exchange_on_rcv_detach(self):
+ self.on_rcv_detach_test("headers")
+
+ def test_autodelete_headers_exchange_on_snd_detach(self):
+ self.on_snd_detach_test("headers")
+
+
+
diff --git a/qpid/cpp/src/tests/msg_group_test.cpp b/qpid/cpp/src/tests/msg_group_test.cpp
new file mode 100644
index 0000000000..ca87197ff3
--- /dev/null
+++ b/qpid/cpp/src/tests/msg_group_test.cpp
@@ -0,0 +1,641 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <qpid/messaging/Address.h>
+#include <qpid/messaging/Connection.h>
+#include <qpid/messaging/Receiver.h>
+#include <qpid/messaging/Sender.h>
+#include <qpid/messaging/Session.h>
+#include <qpid/messaging/Message.h>
+#include <qpid/messaging/FailoverUpdates.h>
+#include <qpid/Options.h>
+#include <qpid/log/Logger.h>
+#include <qpid/log/Options.h>
+#include "qpid/log/Statement.h"
+#include "qpid/sys/Time.h"
+#include "qpid/sys/Runnable.h"
+#include "qpid/sys/Thread.h"
+#include "qpid/sys/SystemInfo.h"
+
+#include <iostream>
+#include <memory>
+#include <stdlib.h>
+
+using namespace qpid::messaging;
+using namespace qpid::types;
+using namespace std;
+
+namespace qpid {
+namespace tests {
+
+struct Options : public qpid::Options
+{
+ bool help;
+ std::string url;
+ std::string address;
+ std::string connectionOptions;
+ uint messages;
+ uint capacity;
+ uint ackFrequency;
+ bool failoverUpdates;
+ qpid::log::Options log;
+ uint senders;
+ uint receivers;
+ uint groupSize;
+ bool printReport;
+ std::string groupKey;
+ bool durable;
+ bool allowDuplicates;
+ bool randomizeSize;
+ bool stickyConsumer;
+ uint timeout;
+ uint interleave;
+ std::string prefix;
+ uint sendRate;
+
+ Options(const std::string& argv0=std::string())
+ : qpid::Options("Options"),
+ help(false),
+ url("amqp:tcp:127.0.0.1"),
+ messages(10000),
+ capacity(1000),
+ ackFrequency(100),
+ failoverUpdates(false),
+ log(argv0),
+ senders(2),
+ receivers(2),
+ groupSize(10),
+ printReport(false),
+ groupKey("qpid.no_group"),
+ durable(false),
+ allowDuplicates(false),
+ randomizeSize(false),
+ stickyConsumer(false),
+ timeout(10),
+ interleave(1),
+ sendRate(0)
+ {
+ addOptions()
+ ("ack-frequency", qpid::optValue(ackFrequency, "N"), "Ack frequency (0 implies none of the messages will get accepted)")
+ ("address,a", qpid::optValue(address, "ADDRESS"), "address to send and receive from")
+ ("allow-duplicates", qpid::optValue(allowDuplicates), "Ignore the delivery of duplicated messages")
+ ("broker,b", qpid::optValue(url, "URL"), "url of broker to connect to")
+ ("capacity", qpid::optValue(capacity, "N"), "Pre-fetch window (0 implies no pre-fetch)")
+ ("connection-options", qpid::optValue(connectionOptions, "OPTIONS"), "options for the connection")
+ ("durable", qpid::optValue(durable, "yes|no"), "Mark messages as durable.")
+ ("failover-updates", qpid::optValue(failoverUpdates), "Listen for membership updates distributed via amq.failover")
+ ("group-key", qpid::optValue(groupKey, "KEY"), "Key of the message header containing the group identifier.")
+ ("group-prefix", qpid::optValue(prefix, "STRING"), "Add 'prefix' to the start of all generated group identifiers.")
+ ("group-size", qpid::optValue(groupSize, "N"), "Number of messages per a group.")
+ ("interleave", qpid::optValue(interleave, "N"), "Simultaineously interleave messages from N different groups.")
+ ("messages,m", qpid::optValue(messages, "N"), "Number of messages to send per each sender.")
+ ("receivers,r", qpid::optValue(receivers, "N"), "Number of message consumers.")
+ ("randomize-group-size", qpid::optValue(randomizeSize), "Randomize the number of messages per group to [1...group-size].")
+ ("send-rate", qpid::optValue(sendRate,"N"), "Send at rate of N messages/second. 0 means send as fast as possible.")
+ ("senders,s", qpid::optValue(senders, "N"), "Number of message producers.")
+ ("sticky-consumers", qpid::optValue(stickyConsumer), "If set, verify that all messages in a group are consumed by the same client [TBD].")
+ ("timeout", qpid::optValue(timeout, "N"), "Fail with a stall error should all consumers remain idle for timeout seconds.")
+ ("print-report", qpid::optValue(printReport), "Dump message group statistics to stdout.")
+ ("help", qpid::optValue(help), "print this usage statement");
+ add(log);
+ //("check-redelivered", qpid::optValue(checkRedelivered), "Fails with exception if a duplicate is not marked as redelivered (only relevant when ignore-duplicates is selected)")
+ //("tx", qpid::optValue(tx, "N"), "batch size for transactions (0 implies transaction are not used)")
+ //("rollback-frequency", qpid::optValue(rollbackFrequency, "N"), "rollback frequency (0 implies no transaction will be rolledback)")
+ }
+
+ bool parse(int argc, char** argv)
+ {
+ try {
+ qpid::Options::parse(argc, argv);
+ if (address.empty()) throw qpid::Exception("Address must be specified!");
+ if (senders == 0 && receivers == 0) throw qpid::Exception("No senders and No receivers?");
+ if (messages == 0) throw qpid::Exception("The message count cannot be zero.");
+ qpid::log::Logger::instance().configure(log);
+ if (help) {
+ std::cout << *this << std::endl << std::endl
+ << "Verifies the behavior of grouped messages." << std::endl;
+ return false;
+ } else {
+ return true;
+ }
+ } catch (const std::exception& e) {
+ std::cerr << *this << std::endl << std::endl << e.what() << std::endl;
+ return false;
+ }
+ }
+};
+
+const string EOS("eos");
+const string SN("sn");
+
+
+// class that monitors group state across all publishers and consumers. tracks the next
+// expected sequence for each group, and total messages consumed.
+class GroupChecker
+{
+ qpid::sys::Mutex lock;
+
+ uint consumerCt;
+ uint producerCt;
+ uint totalMsgs;
+ uint totalMsgsConsumed;
+ uint totalMsgsPublished;
+ bool allowDuplicates;
+ uint duplicateMsgs;
+
+ typedef std::map<std::string, uint> SequenceMap;
+ SequenceMap sequenceMap;
+
+ // Statistics - for each group, store the names of all clients that consumed messages
+ // from that group, and the number of messages consumed per client.
+ typedef std::map<std::string, uint> ClientCounter;
+ typedef std::map<std::string, ClientCounter> GroupStatistics;
+ GroupStatistics statistics;
+
+public:
+
+ GroupChecker( uint messages, uint consumers, uint producers, bool d) :
+ consumerCt(consumers), producerCt(producers),
+ totalMsgs(0), totalMsgsConsumed(0), totalMsgsPublished(0), allowDuplicates(d),
+ duplicateMsgs(0)
+ {
+ // if consumering only - we a draining a queue of 'messages' queued messages.
+ if (producerCt != 0) {
+ totalMsgs = producers * messages;
+ } else {
+ totalMsgs = messages;
+ }
+ }
+
+ bool checkSequence( const std::string& groupId,
+ uint sequence, const std::string& client )
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+
+ QPID_LOG(debug, "Client " << client << " has received " << groupId << ":" << sequence);
+
+ GroupStatistics::iterator gs = statistics.find(groupId);
+ if (gs == statistics.end()) {
+ statistics[groupId][client] = 1;
+ } else {
+ gs->second[client]++;
+ }
+ // now verify
+ SequenceMap::iterator s = sequenceMap.find(groupId);
+ if (s == sequenceMap.end()) {
+ QPID_LOG(debug, "Client " << client << " thinks this is the first message from group " << groupId << ":" << sequence);
+ // if duplication allowed, it is possible that the last msg(s) of an old sequence are redelivered on reconnect.
+ // in this case, set the sequence from the first msg.
+ sequenceMap[groupId] = (allowDuplicates) ? sequence : 0;
+ s = sequenceMap.find(groupId);
+ } else if (sequence < s->second) {
+ duplicateMsgs++;
+ QPID_LOG(debug, "Client " << client << " thinks this message is a duplicate! " << groupId << ":" << sequence);
+ return allowDuplicates;
+ }
+ totalMsgsConsumed++;
+ return sequence == s->second++;
+ }
+
+ void sendingSequence( const std::string& groupId,
+ uint sequence, bool eos,
+ const std::string& client )
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ ++totalMsgsPublished;
+
+ QPID_LOG(debug, "Client " << client << " sending " << groupId << ":" << sequence <<
+ ((eos) ? " (last)" : ""));
+ }
+
+ bool eraseGroup( const std::string& groupId, const std::string& name )
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ QPID_LOG(debug, "Deleting group " << groupId << " (by client " << name << ")");
+ return sequenceMap.erase( groupId ) == 1;
+ }
+
+ uint getNextExpectedSequence( const std::string& groupId )
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ return sequenceMap[groupId];
+ }
+
+ bool allMsgsPublished() // true when done publishing msgs
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ return (producerCt == 0 || totalMsgsPublished >= totalMsgs);
+ }
+
+ bool allMsgsConsumed() // true when done consuming msgs
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ return (consumerCt == 0 ||
+ (totalMsgsConsumed >= totalMsgs && sequenceMap.size() == 0));
+ }
+
+ uint getTotalMessages()
+ {
+ return totalMsgs;
+ }
+
+ uint getConsumedTotal()
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ return totalMsgsConsumed;
+ }
+
+ uint getPublishedTotal()
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ return totalMsgsPublished;
+ }
+
+ ostream& print(ostream& out)
+ {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ out << "Total Published: " << totalMsgsPublished << ", Total Consumed: " << totalMsgsConsumed <<
+ ", Duplicates detected: " << duplicateMsgs << std::endl;
+ out << "Total Groups: " << statistics.size() << std::endl;
+ unsigned long consumers = 0;
+ for (GroupStatistics::iterator gs = statistics.begin(); gs != statistics.end(); ++gs) {
+ out << " GroupId: " << gs->first;
+ consumers += gs->second.size(); // # of consumers that processed this group
+ if (gs->second.size() == 1)
+ out << " completely consumed by a single client." << std::endl;
+ else
+ out << " consumed by " << gs->second.size() << " different clients." << std::endl;
+
+ for (ClientCounter::iterator cc = gs->second.begin(); cc != gs->second.end(); ++cc) {
+ out << " Client: " << cc->first << " consumed " << cc->second << " messages from the group." << std::endl;
+ }
+ }
+ out << "Average # of consumers per group: " << ((statistics.size() != 0) ? (double(consumers)/statistics.size()) : 0) << std::endl;
+ return out;
+ }
+};
+
+
+namespace {
+ // rand() is not thread safe. Create a singleton obj to hold a lock while calling
+ // rand() so it can be called safely by multiple concurrent clients.
+ class Randomizer {
+ qpid::sys::Mutex lock;
+ public:
+ uint operator()(uint max) {
+ qpid::sys::Mutex::ScopedLock l(lock);
+ return (rand() % max) + 1;
+ }
+ };
+
+ static Randomizer randomizer;
+}
+
+
+// tag each generated message with a group identifer
+//
+class GroupGenerator {
+
+ const std::string groupPrefix;
+ const uint groupSize;
+ const bool randomizeSize;
+ const uint interleave;
+
+ uint groupSuffix;
+ uint total;
+
+ struct GroupState {
+ std::string id;
+ const uint size;
+ uint count;
+ GroupState( const std::string& i, const uint s )
+ : id(i), size(s), count(0) {}
+ };
+ typedef std::list<GroupState> GroupList;
+ GroupList groups;
+ GroupList::iterator current;
+
+ // add a new group identifier to the list
+ void newGroup() {
+ std::ostringstream groupId(groupPrefix, ios_base::out|ios_base::ate);
+ groupId << std::string(":") << groupSuffix++;
+ uint size = (randomizeSize) ? randomizer(groupSize) : groupSize;
+ QPID_LOG(trace, "New group: GROUPID=[" << groupId.str() << "] size=" << size << " this=" << this);
+ GroupState group( groupId.str(), size );
+ groups.push_back( group );
+ }
+
+public:
+ GroupGenerator( const std::string& prefix,
+ const uint t,
+ const uint size,
+ const bool randomize,
+ const uint i)
+ : groupPrefix(prefix), groupSize(size),
+ randomizeSize(randomize), interleave(i), groupSuffix(0), total(t)
+ {
+ QPID_LOG(trace, "New group generator: PREFIX=[" << prefix << "] total=" << total << " size=" << size << " rand=" << randomize << " interleave=" << interleave << " this=" << this);
+ for (uint i = 0; i < 1 || i < interleave; ++i) {
+ newGroup();
+ }
+ current = groups.begin();
+ }
+
+ bool genGroup(std::string& groupId, uint& seq, bool& eos)
+ {
+ if (!total) return false;
+ --total;
+ if (current == groups.end())
+ current = groups.begin();
+ groupId = current->id;
+ seq = current->count++;
+ if (current->count == current->size) {
+ QPID_LOG(trace, "Last msg for " << current->id << ", " << current->count << " this=" << this);
+ eos = true;
+ if (total >= interleave) { // need a new group to replace this one
+ newGroup();
+ groups.erase(current++);
+ } else ++current;
+ } else {
+ ++current;
+ eos = total < interleave; // mark eos on the last message of each group
+ }
+ QPID_LOG(trace, "SENDING GROUPID=[" << groupId << "] seq=" << seq << " eos=" << eos << " this=" << this);
+ return true;
+ }
+};
+
+
+
+class Client : public qpid::sys::Runnable
+{
+public:
+ typedef boost::shared_ptr<Client> shared_ptr;
+ enum State {ACTIVE, DONE, FAILURE};
+ Client( const std::string& n, const Options& o ) : name(n), opts(o), state(ACTIVE), stopped(false) {}
+ virtual ~Client() {}
+ State getState() { return state; }
+ void testFailed( const std::string& reason ) { state = FAILURE; error << "Client '" << name << "' failed: " << reason; }
+ void clientDone() { if (state == ACTIVE) state = DONE; }
+ qpid::sys::Thread& getThread() { return thread; }
+ const std::string getErrorMsg() { return error.str(); }
+ void stop() {stopped = true;}
+ const std::string& getName() { return name; }
+
+protected:
+ const std::string name;
+ const Options& opts;
+ qpid::sys::Thread thread;
+ ostringstream error;
+ State state;
+ bool stopped;
+};
+
+
+class Consumer : public Client
+{
+ GroupChecker& checker;
+
+public:
+ Consumer(const std::string& n, const Options& o, GroupChecker& c ) : Client(n, o), checker(c) {};
+ virtual ~Consumer() {};
+
+ void run()
+ {
+ Connection connection;
+ try {
+ connection = Connection(opts.url, opts.connectionOptions);
+ connection.open();
+ std::auto_ptr<FailoverUpdates> updates(opts.failoverUpdates ? new FailoverUpdates(connection) : 0);
+ Session session = connection.createSession();
+ Receiver receiver = session.createReceiver(opts.address);
+ receiver.setCapacity(opts.capacity);
+ Message msg;
+ uint count = 0;
+
+ while (!stopped) {
+ if (receiver.fetch(msg, Duration::SECOND)) { // msg retrieved
+ qpid::types::Variant::Map& properties = msg.getProperties();
+ std::string groupId = properties[opts.groupKey];
+ uint groupSeq = properties[SN];
+ bool eof = properties[EOS];
+
+ QPID_LOG(trace, "RECVING GROUPID=[" << groupId << "] seq=" << groupSeq << " eos=" << eof << " name=" << name);
+
+ qpid::sys::usleep(10);
+
+ if (!checker.checkSequence( groupId, groupSeq, name )) {
+ ostringstream msg;
+ msg << "Check sequence failed. Group=" << groupId << " rcvd seq=" << groupSeq << " expected=" << checker.getNextExpectedSequence( groupId );
+ testFailed( msg.str() );
+ break;
+ } else if (eof) {
+ if (!checker.eraseGroup( groupId, name )) {
+ ostringstream msg;
+ msg << "Erase group failed. Group=" << groupId << " rcvd seq=" << groupSeq;
+ testFailed( msg.str() );
+ break;
+ }
+ }
+
+ ++count;
+ if (opts.ackFrequency && (count % opts.ackFrequency == 0)) {
+ session.acknowledge();
+ }
+ // Clear out message properties & content for next iteration.
+ msg = Message(); // TODO aconway 2010-12-01: should be done by fetch
+ } else if (checker.allMsgsConsumed()) // timed out, nothing else to do?
+ break;
+ }
+ session.acknowledge();
+ session.close();
+ connection.close();
+ } catch(const std::exception& error) {
+ ostringstream msg;
+ msg << "consumer error: " << error.what();
+ testFailed( msg.str() );
+ connection.close();
+ }
+ clientDone();
+ QPID_LOG(trace, "Consuming client " << name << " completed.");
+ }
+};
+
+
+
+class Producer : public Client
+{
+ GroupChecker& checker;
+ GroupGenerator generator;
+
+public:
+ Producer(const std::string& n, const Options& o, GroupChecker& c)
+ : Client(n, o), checker(c),
+ generator( n, o.messages, o.groupSize, o.randomizeSize, o.interleave )
+ {};
+ virtual ~Producer() {};
+
+ void run()
+ {
+ Connection connection;
+ try {
+ connection = Connection(opts.url, opts.connectionOptions);
+ connection.open();
+ std::auto_ptr<FailoverUpdates> updates(opts.failoverUpdates ? new FailoverUpdates(connection) : 0);
+ Session session = connection.createSession();
+ Sender sender = session.createSender(opts.address);
+ if (opts.capacity) sender.setCapacity(opts.capacity);
+ Message msg;
+ msg.setDurable(opts.durable);
+ std::string groupId;
+ uint seq;
+ bool eos;
+ uint sent = 0;
+
+ qpid::sys::AbsTime start = qpid::sys::now();
+ int64_t interval = 0;
+ if (opts.sendRate) interval = qpid::sys::TIME_SEC/opts.sendRate;
+
+ while (!stopped && generator.genGroup(groupId, seq, eos)) {
+ msg.getProperties()[opts.groupKey] = groupId;
+ msg.getProperties()[SN] = seq;
+ msg.getProperties()[EOS] = eos;
+ checker.sendingSequence( groupId, seq, eos, name );
+
+ sender.send(msg);
+ ++sent;
+
+ if (opts.sendRate) {
+ qpid::sys::AbsTime waitTill(start, sent*interval);
+ int64_t delay = qpid::sys::Duration(qpid::sys::now(), waitTill);
+ if (delay > 0) qpid::sys::usleep(delay/qpid::sys::TIME_USEC);
+ }
+ }
+ session.sync();
+ session.close();
+ connection.close();
+ } catch(const std::exception& error) {
+ ostringstream msg;
+ msg << "producer '" << name << "' error: " << error.what();
+ testFailed(msg.str());
+ connection.close();
+ }
+ clientDone();
+ QPID_LOG(trace, "Producing client " << name << " completed.");
+ }
+};
+
+
+}} // namespace qpid::tests
+
+using namespace qpid::tests;
+
+int main(int argc, char ** argv)
+{
+ int status = 0;
+ try {
+ Options opts;
+ if (opts.parse(argc, argv)) {
+
+ GroupChecker state( opts.messages,
+ opts.receivers,
+ opts.senders,
+ opts.allowDuplicates);
+ std::vector<Client::shared_ptr> clients;
+
+ if (opts.randomizeSize) srand((unsigned int)qpid::sys::SystemInfo::getProcessId());
+
+ // fire off the producers && consumers
+ for (size_t j = 0; j < opts.senders; ++j) {
+ ostringstream name;
+ name << opts.prefix << "P_" << j;
+ clients.push_back(Client::shared_ptr(new Producer( name.str(), opts, state )));
+ clients.back()->getThread() = qpid::sys::Thread(*clients.back());
+ }
+ for (size_t j = 0; j < opts.receivers; ++j) {
+ ostringstream name;
+ name << opts.prefix << "C_" << j;
+ clients.push_back(Client::shared_ptr(new Consumer( name.str(), opts, state )));
+ clients.back()->getThread() = qpid::sys::Thread(*clients.back());
+ }
+
+ // wait for all pubs/subs to finish.... or for consumers to fail or stall.
+ uint stalledTime = 0;
+ bool clientFailed = false;
+ while (!clientFailed && (!state.allMsgsPublished() || !state.allMsgsConsumed())) {
+ uint lastCount;
+
+ lastCount = state.getConsumedTotal();
+ qpid::sys::usleep( 1000000 );
+
+ // check each client for failures
+ for (std::vector<Client::shared_ptr>::iterator i = clients.begin();
+ i != clients.end(); ++i) {
+ QPID_LOG(debug, "Client " << (*i)->getName() << " state=" << (*i)->getState());
+ if ((*i)->getState() == Client::FAILURE) {
+ QPID_LOG(error, argv[0] << ": test failed with client error: " << (*i)->getErrorMsg());
+ clientFailed = true;
+ break; // exit test.
+ }
+ }
+
+ // check for stalled consumers
+ if (!clientFailed && !state.allMsgsConsumed()) {
+ if (lastCount == state.getConsumedTotal()) {
+ if (++stalledTime >= opts.timeout) {
+ clientFailed = true;
+ break; // exit test
+ }
+ } else {
+ stalledTime = 0;
+ }
+ }
+ QPID_LOG(debug, "Consumed to date = " << state.getConsumedTotal() <<
+ " Published to date = " << state.getPublishedTotal() <<
+ " total=" << state.getTotalMessages());
+ }
+
+ if (clientFailed) {
+ if (stalledTime >= opts.timeout) {
+ QPID_LOG(error, argv[0] << ": test failed due to stalled consumer." );
+ status = 2;
+ } else {
+ status = 1;
+ }
+ }
+
+ // Wait for started threads.
+ for (std::vector<Client::shared_ptr>::iterator i = clients.begin();
+ i != clients.end(); ++i) {
+ (*i)->stop();
+ (*i)->getThread().join();
+ }
+
+ if (opts.printReport && !status) state.print(std::cout);
+ } else status = 4;
+ } catch(const std::exception& error) {
+ QPID_LOG(error, argv[0] << ": " << error.what());
+ status = 3;
+ }
+ QPID_LOG(trace, "TEST DONE [" << status << "]");
+
+ return status;
+}
diff --git a/qpid/cpp/src/tests/multiq_perftest b/qpid/cpp/src/tests/multiq_perftest
new file mode 100755
index 0000000000..9673dd2e6d
--- /dev/null
+++ b/qpid/cpp/src/tests/multiq_perftest
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+exec `dirname $0`/run_perftest 10000 --mode shared --qt 16
diff --git a/qpid/cpp/src/tests/perfdist b/qpid/cpp/src/tests/perfdist
new file mode 100755
index 0000000000..4049b410ff
--- /dev/null
+++ b/qpid/cpp/src/tests/perfdist
@@ -0,0 +1,87 @@
+#!/usr/bin/env 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.
+#
+
+
+#
+# Distributed perftest.
+# Runs perftest clients on multiple hosts using ssh.
+#
+
+set -e
+usage() {
+cat <<EOF
+usage: $0 <perftest-args> -- <client-hosts ...> [ --- <broker hosts...> ]
+Client & broker hosts can also be set in env vars CLIENTS and BROKERS.
+
+Run perftest clients on the client hosts against brokers on the broker
+hosts Clients are assigned to client hosts round robin: publishers
+first, then subscribers. If there are multiple brokers (for cluster
+tests) clients connect to them round robin.
+
+Broker hosts can be listed with -b in perftest-args or after ---
+at the end of the arguments.
+
+Error: $*
+EOF
+exit 1
+}
+
+TESTDIR=${TESTDIR:-$PWD} # Absolute path to test exes on all hosts.
+
+collect() { eval $COLLECT=\""\$$COLLECT $*"\"; }
+NPUBS=1
+NSUBS=1
+COLLECT=ARGS
+while test $# -gt 0; do
+ case $1 in
+ --publish|--subscribe|--setup|--control) usage "Don't pass perftest action flags: $1" ;;
+ --npubs) collect $1 $2; NPUBS=$2; shift 2 ;;
+ --nsubs) collect $1 $2; NSUBS=$2; shift 2 ;;
+ -s|--summary) collect $1; QUIET=yes; shift 1 ;;
+ -b|--broker) BROKERS="$BROKERS $2"; shift 2;;
+ --) COLLECT=CLIENTARG; shift ;;
+ ---) COLLECT=BROKERARG; shift;;
+ *) collect $1; shift ;;
+ esac
+done
+
+CLIENTS=${CLIENTARG:-$CLIENTS}
+if [ -z "$CLIENTS" ]; then usage "No client hosts listed after --"; fi
+BROKERS=${BROKERARG:-$BROKERS}
+if [ -z "$BROKERS" ]; then usage "No brokers specified"; fi
+
+PERFTEST="$TESTDIR/perftest $ARGS"
+
+CLIENTS=($CLIENTS)
+BROKERS=($BROKERS)
+start() {
+ CLIENT=${CLIENTS[i % ${#CLIENTS[*]}]}
+ BROKER=${BROKERS[i % ${#BROKERS[*]}]}
+ ARGS="$* --broker $BROKER"
+ cmd="ssh -n $CLIENT $PERFTEST $ARGS"
+ test -z "$QUIET" && echo "Client $i: $cmd"
+ $cmd &
+}
+
+$PERFTEST --setup -b ${BROKERS[0]}
+for (( i=0 ; i < $NPUBS ; ++i)); do start --publish; done
+for (( ; i < $NPUBS+$NSUBS ; ++i)); do start --subscribe; done
+$PERFTEST --control -b ${BROKERS[0]}
diff --git a/qpid/cpp/src/tests/ping_broker b/qpid/cpp/src/tests/ping_broker
new file mode 100755
index 0000000000..bdf48f3358
--- /dev/null
+++ b/qpid/cpp/src/tests/ping_broker
@@ -0,0 +1,134 @@
+#!/usr/bin/env python
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 os
+from optparse import OptionParser, OptionGroup
+import sys
+import locale
+import socket
+import re
+from qpid.messaging import Connection
+
+home = os.environ.get("QPID_TOOLS_HOME", os.path.normpath("/usr/share/qpid-tools"))
+sys.path.append(os.path.join(home, "python"))
+
+from qpidtoollibs import BrokerAgent
+from qpidtoollibs import Display, Header, Sorter, YN, Commas, TimeLong
+
+
+class Config:
+ def __init__(self):
+ self._host = "localhost"
+ self._connTimeout = 10
+
+config = Config()
+conn_options = {}
+
+def OptionsAndArguments(argv):
+ """ Set global variables for options, return arguments """
+
+ global config
+ global conn_options
+
+ usage = "%prog [options]"
+
+ parser = OptionParser(usage=usage)
+
+ parser.add_option("-b", "--broker", action="store", type="string", default="localhost", metavar="<url>",
+ help="URL of the broker to query")
+ parser.add_option("-t", "--timeout", action="store", type="int", default=10, metavar="<secs>",
+ help="Maximum time to wait for broker connection (in seconds)")
+ parser.add_option("--sasl-mechanism", action="store", type="string", metavar="<mech>",
+ help="SASL mechanism for authentication (e.g. EXTERNAL, ANONYMOUS, PLAIN, CRAM-MD5, DIGEST-MD5, GSSAPI). SASL automatically picks the most secure available mechanism - use this option to override.")
+ parser.add_option("--ssl-certificate", action="store", type="string", metavar="<cert>", help="Client SSL certificate (PEM Format)")
+ parser.add_option("--ssl-key", action="store", type="string", metavar="<key>", help="Client SSL private key (PEM Format)")
+ parser.add_option("--ssl-trustfile", action="store", type="string", metavar="<CA>", help="List of trusted CAs (PEM Format)")
+ parser.add_option("--ssl-skip-hostname-check", action="store_true",
+ help="Do not validate hostname in peer certificate")
+ parser.add_option("--ha-admin", action="store_true", help="Allow connection to a HA backup broker.")
+
+ opts, args = parser.parse_args(args=argv)
+
+ config._host = opts.broker
+ config._connTimeout = opts.timeout
+
+ if opts.sasl_mechanism:
+ conn_options['sasl_mechanisms'] = opts.sasl_mechanism
+ if opts.ssl_certificate:
+ conn_options['ssl_certfile'] = opts.ssl_certificate
+ if opts.ssl_key:
+ conn_options['ssl_key'] = opts.ssl_key
+ if opts.ssl_trustfile:
+ conn_options['ssl_trustfile'] = opts.ssl_trustfile
+ if opts.ssl_skip_hostname_check:
+ conn_options['ssl_skip_hostname_check'] = True
+ if opts.ha_admin:
+ conn_options['client_properties'] = {'qpid.ha-admin' : 1}
+ return args
+
+class BrokerManager:
+ def __init__(self):
+ self.brokerName = None
+ self.connection = None
+ self.broker = None
+ self.cluster = None
+
+ def SetBroker(self, brokerUrl):
+ self.url = brokerUrl
+ self.connection = Connection.establish(self.url, **conn_options)
+ self.broker = BrokerAgent(self.connection)
+
+ def Disconnect(self):
+ """ Release any allocated brokers. Ignore any failures as the tool is
+ shutting down.
+ """
+ try:
+ connection.close()
+ except:
+ pass
+
+ def Ping(self, args):
+ for sequence in range(10):
+ result = self.broker.echo(sequence, "ECHO BODY")
+ if result['sequence'] != sequence:
+ raise Exception("Invalid Sequence")
+
+
+def main(argv=None):
+
+ args = OptionsAndArguments(argv)
+ bm = BrokerManager()
+
+ try:
+ bm.SetBroker(config._host)
+ bm.Ping(args)
+ bm.Disconnect()
+ return 0
+ except KeyboardInterrupt:
+ print
+ except Exception,e:
+ print "Failed: %s - %s" % (e.__class__.__name__, e)
+
+ bm.Disconnect() # try to deallocate brokers
+ return 1
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/qpid/cpp/src/tests/policies.py b/qpid/cpp/src/tests/policies.py
new file mode 100644
index 0000000000..ec0191f91e
--- /dev/null
+++ b/qpid/cpp/src/tests/policies.py
@@ -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.
+#
+
+from qpid.tests.messaging.implementation import *
+from qpid.tests.messaging import VersionTest
+
+class Mgmt:
+ """
+ Simple QMF management utility (qpidtoollibs uses
+ qpid.messaging.Message rather than swigged version)
+ """
+ def __init__(self, conn):
+ self.conn = conn
+ self.sess = self.conn.session()
+ self.reply_to = "qmf.default.topic/direct.%s;{node:{type:topic}, link:{x-declare:{auto-delete:True,exclusive:True}}}" % \
+ str(uuid4())
+ self.reply_rx = self.sess.receiver(self.reply_to)
+ self.reply_rx.capacity = 10
+ self.tx = self.sess.sender("qmf.default.direct/broker")
+ self.next_correlator = 1
+
+ def list(self, class_name):
+ props = {'method' : 'request',
+ 'qmf.opcode' : '_query_request',
+ 'x-amqp-0-10.app-id' : 'qmf2'}
+ correlator = str(self.next_correlator)
+ self.next_correlator += 1
+
+ content = {'_what' : 'OBJECT',
+ '_schema_id' : {'_class_name' : class_name.lower()}}
+
+ message = Message(content, reply_to=self.reply_to, correlation_id=correlator,
+ properties=props, subject="broker")
+ self.tx.send(message)
+
+
+ response = self.reply_rx.fetch(10)
+ if response.properties['qmf.opcode'] != '_query_response':
+ raise Exception("bad response")
+ items = []
+ done = False
+ while not done:
+ for item in response.content:
+ items.append(item['_values'])
+ if 'partial' in response.properties:
+ response = self.reply_rx.fetch(10)
+ else:
+ done = True
+ self.sess.acknowledge()
+ return items
+
+ def do_qmf_method(self, method, arguments, addr="org.apache.qpid.broker:broker:amqp-broker", timeout=10):
+ props = {'method' : 'request',
+ 'qmf.opcode' : '_method_request',
+ 'x-amqp-0-10.app-id' : 'qmf2'}
+ correlator = str(self.next_correlator)
+ self.next_correlator += 1
+
+ content = {'_object_id' : {'_object_name' : addr},
+ '_method_name' : method,
+ '_arguments' : arguments}
+
+ message = Message(content, reply_to=self.reply_to, correlation_id=correlator,
+ properties=props, subject="broker")
+ self.tx.send(message)
+ response = self.reply_rx.fetch(timeout)
+ self.sess.acknowledge()
+ if response.properties['qmf.opcode'] == '_exception':
+ raise Exception("Exception from Agent: %r" % response.content['_values'])
+ if response.properties['qmf.opcode'] != '_method_response':
+ raise Exception("bad response: %r" % response.properties)
+ return response.content['_arguments']
+
+ def create(self, _type, name, properties={}):
+ return self.do_qmf_method('create', {'type': _type, 'name': name, 'properties': properties})
+
+ def delete(self, _type, name):
+ return self.do_qmf_method('delete', {'type': _type, 'name': name})
+
+
+class PoliciesTests (VersionTest):
+ """
+ Tests for node policies with qpidd
+ """
+
+ def do_simple_queue_test(self, pattern, name, properties={}, autodeleted=True):
+ mgmt = self.create_connection("amqp0-10", True)
+ agent = Mgmt(mgmt)
+ agent.create('QueuePolicy', pattern, properties)
+ try:
+ snd = self.ssn.sender(name)
+ msgs = [Message(content=s, subject = s) for s in ['a','b','c','d']]
+ for m in msgs: snd.send(m)
+ snd.close()
+
+ for expected in msgs:
+ rcv = self.ssn.receiver(name)
+ msg = rcv.fetch(0)
+ assert msg.content == expected.content, (msg.content, expected.content)
+ self.ssn.acknowledge()
+ rcv.close() #close after each message to ensure queue isn't deleted with messages in it
+ self.ssn.close()
+ self.conn.close()
+
+ matched = [q for q in agent.list("Queue") if q['name'] == name]
+ if autodeleted:
+ # ensure that queue is no longer there (as empty and unused)
+ assert len(matched) == 0, (matched)
+ else:
+ # ensure that queue is still there though empty and unused
+ assert len(matched) == 1, (matched)
+ finally:
+ agent.delete('QueuePolicy', pattern)
+ mgmt.close()
+
+ def test_queue(self):
+ self.do_simple_queue_test("queue-*", "queue-1")
+
+ def test_queue_not_autodeleted(self):
+ self.do_simple_queue_test("permanent-queue-*", "permanent-queue-1", {'auto-delete':False}, False)
+
+ def test_queue_manual_delete(self):
+ self.do_simple_queue_test("permanent-queue-*", "permanent-queue-1", {'qpid.lifetime-policy':'manual'}, False)
+
+ def test_queue_delete_if_unused_and_empty(self):
+ self.do_simple_queue_test("queue-*", "queue-1", {'qpid.lifetime-policy':'delete-if-unused-and-empty'}, True)
+
+ def do_simple_topic_test(self, pattern, name, properties={}, autodeleted=True):
+ mgmt = self.create_connection("amqp0-10", True)
+ agent = Mgmt(mgmt)
+ agent.create('TopicPolicy', pattern, properties)
+ try:
+ snd = self.ssn.sender(name)
+ rcv1 = self.ssn.receiver(name)
+ rcv2 = self.ssn.receiver(name)
+
+ msgs = [Message(content=s, subject = s) for s in ['a','b','c','d']]
+ for m in msgs: snd.send(m)
+
+ for rcv in [rcv1, rcv2]:
+ for expected in msgs:
+ msg = rcv.fetch(0)
+ assert msg.content == expected.content, (msg.content, expected.content)
+ self.ssn.acknowledge()
+ rcv1.close()
+ rcv2.close()
+ snd.close()
+
+ matched = [e for e in agent.list("Exchange") if e['name'] == name]
+ if autodeleted:
+ # ensure that exchange is no longer there (as it is now unused)
+ assert len(matched) == 0, (matched)
+ else:
+ # ensure that exchange has not been autodeleted in spite of being unused
+ assert len(matched) == 1, (matched)
+ finally:
+ agent.delete('TopicPolicy', pattern)
+ mgmt.close()
+
+ def test_topic(self):
+ self.do_simple_topic_test('fanout-*', 'fanout-1', {'exchange-type':'fanout'})
+
+ def test_topic_not_autodelete(self):
+ self.do_simple_topic_test('permanent-fanout-*', 'permanent-fanout-1', {'exchange-type':'fanout', 'auto-delete':False}, False)
+
+ def test_topic_manual_delete(self):
+ self.do_simple_topic_test('permanent-fanout-*', 'permanent-fanout-1', {'exchange-type':'fanout', 'qpid.lifetime-policy':'manual'}, False)
+
+ def test_topic_delete_if_unused(self):
+ self.do_simple_topic_test('fanout-*', 'fanout-1', {'exchange-type':'fanout', 'qpid.lifetime-policy':'delete-if-unused'}, True)
+
+ def test_mgmt(self):
+ mgmt = self.create_connection("amqp0-10", True)
+ agent = Mgmt(mgmt)
+ agent.create('QueuePolicy', 'queue-*')
+ agent.create('QueuePolicy', 'alt.queue.*')
+ agent.create('TopicPolicy', 'topic-*')
+ try:
+ queues = [q['name'] for q in agent.list("QueuePolicy")]
+ topics = [t['name'] for t in agent.list("TopicPolicy")]
+ assert 'queue-*' in queues, (queues)
+ assert 'alt.queue.*' in queues, (queues)
+
+ try:
+ agent.delete('TopicPolicy', 'queue-*')
+ assert False, ('Deletion of policy using wrong type should fail')
+ except: None
+
+ finally:
+ agent.delete('QueuePolicy', 'queue-*')
+ agent.delete('QueuePolicy', 'alt.queue.*')
+ agent.delete('TopicPolicy', 'topic-*')
+ mgmt.close()
diff --git a/qpid/cpp/src/tests/policy.acl b/qpid/cpp/src/tests/policy.acl
new file mode 100644
index 0000000000..4c13ac75c1
--- /dev/null
+++ b/qpid/cpp/src/tests/policy.acl
@@ -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.
+#
+
+acl allow all all
diff --git a/qpid/cpp/src/tests/publish.cpp b/qpid/cpp/src/tests/publish.cpp
new file mode 100644
index 0000000000..3f456e7588
--- /dev/null
+++ b/qpid/cpp/src/tests/publish.cpp
@@ -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.
+ *
+ */
+
+#include <algorithm>
+#include <iostream>
+#include <memory>
+#include <sstream>
+#include <vector>
+
+#include "TestOptions.h"
+#include "qpid/client/Connection.h"
+#include "qpid/client/Message.h"
+#include "qpid/client/AsyncSession.h"
+#include "qpid/client/SubscriptionManager.h"
+
+using namespace qpid;
+using namespace qpid::client;
+using namespace qpid::sys;
+using namespace std;
+
+namespace qpid {
+namespace tests {
+
+typedef vector<string> StringSet;
+
+struct Args : public qpid::TestOptions {
+ uint size;
+ uint count;
+ bool durable;
+ string destination;
+ string routingKey;
+ bool summary;
+ bool id;
+
+ Args() : size(256), count(1000), durable(true), routingKey("publish-consume"), summary(false), id(false) {
+ addOptions()
+ ("size", optValue(size, "N"), "message size")
+ ("count", optValue(count, "N"), "number of messages to publish")
+ ("durable", optValue(durable, "yes|no"), "use durable messages")
+ ("destination", optValue(destination, "<exchange name>"), "destination to publish to")
+ ("routing-key", optValue(routingKey, "<key>"), "routing key to publish with")
+ ("summary,s", optValue(summary), "Output only the rate.")
+ ("id", optValue(id), "Add unique correlation ID");
+ }
+};
+
+Args opts;
+
+struct Client
+{
+ Connection connection;
+ AsyncSession session;
+
+ Client()
+ {
+ opts.open(connection);
+ session = connection.newSession();
+ }
+
+ // Cheap hex calculation, avoid expensive ostrstream and string
+ // creation to generate correlation ids in message loop.
+ char hex(char i) { return i<10 ? '0'+i : 'A'+i-10; }
+ void hex(char i, string& s) {
+ s[0]=hex(i>>24); s[1]=hex(i>>16); s[2]=hex(i>>8); s[3]=i;
+ }
+
+ void publish()
+ {
+ AbsTime begin=now();
+ Message msg(string(opts.size, 'X'), opts.routingKey);
+ string correlationId = "0000";
+ if (opts.durable)
+ msg.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT);
+
+ for (uint i = 0; i < opts.count; i++) {
+ if (opts.id) {
+ hex(i+1, correlationId);
+ msg.getMessageProperties().setCorrelationId(correlationId);
+ }
+ session.messageTransfer(arg::destination=opts.destination,
+ arg::content=msg,
+ arg::acceptMode=1);
+ }
+ session.sync();
+ AbsTime end=now();
+ double secs(double(Duration(begin,end))/TIME_SEC);
+ if (opts.summary) cout << opts.count/secs << endl;
+ else cout << "Time: " << secs << "s Rate: " << opts.count/secs << endl;
+ }
+
+ ~Client()
+ {
+ try{
+ session.close();
+ connection.close();
+ } catch(const exception& e) {
+ cout << e.what() << endl;
+ }
+ }
+};
+
+}} // namespace qpid::tests
+
+using namespace qpid::tests;
+
+int main(int argc, char** argv)
+{
+ try {
+ opts.parse(argc, argv);
+ Client client;
+ client.publish();
+ return 0;
+ } catch(const exception& e) {
+ cout << e.what() << endl;
+ }
+ return 1;
+}
diff --git a/qpid/cpp/src/tests/python_tests b/qpid/cpp/src/tests/python_tests
new file mode 100755
index 0000000000..a36839a43c
--- /dev/null
+++ b/qpid/cpp/src/tests/python_tests
@@ -0,0 +1,34 @@
+#!/usr/bin/env 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.
+#
+
+# Run the python tests.
+source $QPID_TEST_COMMON
+ensure_python_tests
+QPID_PORT=${QPID_PORT:-5672}
+PYTHON_TESTS=${PYTHON_TESTS:-$*}
+FAILING=${FAILING:-/dev/null}
+
+if [ ! -d $QPID_TESTS ]; then
+ echo "SKIPPED python tests: test code not found"
+ exit 0
+fi
+
+python $QPID_PYTHON_TEST -m qpid_tests.broker_0_10 -m qpid.tests -b localhost:$QPID_PORT -I $FAILING $PYTHON_TESTS || exit 1
diff --git a/qpid/cpp/src/tests/python_tests.ps1 b/qpid/cpp/src/tests/python_tests.ps1
new file mode 100644
index 0000000000..f7caa8f75a
--- /dev/null
+++ b/qpid/cpp/src/tests/python_tests.ps1
@@ -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.
+#
+
+# Run the python tests; intended to be run by run_test.ps1 which sets up
+# QPID_PORT
+$srcdir = Split-Path $myInvocation.InvocationName
+$PYTHON_DIR = "$srcdir\..\..\..\python"
+if (!(Test-Path $PYTHON_DIR -pathType Container)) {
+ "Skipping python tests as python libs not found"
+ exit 1
+}
+
+. .\test_env.ps1
+
+if (Test-Path env:FAILING) {
+ $fails = "-I $env:FAILING"
+}
+if (Test-Path env:PYTHON_TESTS) {
+ $tests = "$env:PYTHON_TESTS"
+}
+else {
+ $tests = "$args"
+}
+
+python $PYTHON_DIR/qpid-python-test -m qpid_tests.broker_0_10 -m qpid.tests -b localhost:$env:QPID_PORT $fails $tests
+exit $LASTEXITCODE
diff --git a/qpid/cpp/src/tests/qpid-analyze-trace b/qpid/cpp/src/tests/qpid-analyze-trace
new file mode 100755
index 0000000000..009fbc441c
--- /dev/null
+++ b/qpid/cpp/src/tests/qpid-analyze-trace
@@ -0,0 +1,258 @@
+#!/usr/bin/env python
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 sys
+from datetime import datetime
+from optparse import OptionParser
+
+# Version of this tool software
+MAJOR_VERSION = 1
+MINOR_VERSION = 1
+# === Version history ===
+# 2011-11-16 1.1: Bugfixs:
+# QPID-3623 - Incorrect handling of transactions
+# QPID-3624 - Replace argparse lib with optparse so tool can be used on Python 2.6.
+# 2011-11-07 1.0: Initial checkin
+# QPID-3579: Initial version checked in
+
+
+# AMQP 0-10 commands - these increment the command counter
+EXEC_COMMANDS = ["ExecutionSync", "ExecutionResult", "ExecutionException", "MessageTransfer", "MessageAccept",
+ "MessageReject", "MessageRelease", "MessageAcquire", "MessageResume", "MessageSubscribe",
+ "MessageCancel", "MessageSetFlowMode", "MessageFlow", "MessageFlush", "MessageStop", "TxSelect",
+ "TxCommit", "TxRollback", "DtxSelect", "DtxStart", "DtxEnd", "DtxCommit", "DtxForget", "DtxGetTimeout",
+ "DtxPrepare", "DtxRecover", "DtxRollback", "DtxSetTimeout", "ExchangeDeclare", "ExchangeDelete",
+ "ExchangeQuery", "ExchangeBind", "ExchangeUnbind", "ExchangeBound", "QueueDeclare", "QueueDelete",
+ "QueuePurge", "QueueQuery", "FileQos", "FileQosOk", "FileConsume", "FileConsumeOk", "FileCancel",
+ "FileOpen", "FileOpenOk", "FileStage", "FilePublish", "FileReturn", "FileDeliver", "FileAck",
+ "FileReject", "StreamQos", "StreamQosOk", "StreamConsume", "StreamConsumeOk", "StreamCancel",
+ "StreamPublish", "StreamReturn", "StreamDeliver"]
+HEADER_STR = " -line ----------timestamp -----------connection ssn recv send- txn-- operation---------->"
+
+PROGRESS_LINES_PER_DOT = 100000
+
+class LogLevel:
+ CRITICAL = (1, "critical")
+ ERROR = (2, "error")
+ WARNING = (3, "warning")
+ NOTICE = (4, "notice")
+ INFO = (5, "info")
+ DEBUG = (6, "debug")
+ TRACE = (7, "trace")
+ @staticmethod
+ def get_level(level):
+ if level == LogLevel.CRITICAL[1]: return LogLevel.CRITICAL
+ if level == LogLevel.ERROR[1]: return LogLevel.ERROR
+ if level == LogLevel.WARNING[1]: return LogLevel.WARNING
+ if level == LogLevel.NOTICE[1]: return LogLevel.NOTICE
+ if level == LogLevel.INFO[1]: return LogLevel.INFO
+ if level == LogLevel.DEBUG[1]: return LogLevel.DEBUG
+ if level == LogLevel.TRACE[1]: return LogLevel.TRACE
+ raise Exception("Unknown log level: %s" % level)
+
+class LogLine:
+ def __init__(self, line_no, line):
+ self.line_no = line_no
+ self.timestamp = datetime.strptime(line[:19], "%Y-%m-%d %H:%M:%S")
+ self.level = LogLevel.get_level(line[20:].split(" ")[0])
+ self.line = line[21 + len(self.level[1]):].strip()
+ self.cmd_cnt = None
+ self.txn_cnt = None
+ def __str__(self):
+ if self.contains("RECV"): cnt_str = "R"
+ else: cnt_str = " S"
+ if self.cmd_cnt is not None: cnt_str += str(self.cmd_cnt)
+ set_index = self.find("{")
+ header_index = self.find("header")
+ content_index = self.find("content")
+ if self.txn_cnt is None:
+ txn_cnt_str = ""
+ else:
+ txn_cnt_str = "T%d" % self.txn_cnt
+ if header_index != -1 and header_index < set_index: op_str = " + " + self.line[header_index:self.line.rfind("]")]
+ elif content_index != -1 and set_index == -1: op_str = " + " + self.line[content_index:self.line.rfind("]")]
+ else: op_str = self.line[set_index+1:self.line.rfind("}")]
+ return " %7d %19s %22s %3d %-10s %-5s %s" % (self.line_no, self.timestamp.isoformat(" "),
+ self.get_identifier_remote_addr(), self.get_channel(),
+ cnt_str, txn_cnt_str, op_str)
+ def contains(self, string):
+ return self.line.find(string) != -1
+ def find(self, string):
+ return self.line.find(string)
+ def get_channel(self):
+ return int(self.get_named_value("channel"))
+ def get_identifier(self):
+ return self.line.partition("[")[2].partition("]")[0]
+ def get_identifier_remote_addr(self):
+ return self.get_identifier().partition("-")[2]
+ def get_named_value(self, name):
+ return self.line.partition("%s=" % name)[2].partition(";")[0]
+ def get_msg_accept_range(self):
+ str_nums = self.get_named_value("transfers").strip(" {[]}").split(",")
+ return range(int(str_nums[0]), int(str_nums[1]) + 1)
+ def is_log_level(self, level):
+ if self.level is None: return None
+ return level[0] == self.level[0]
+ def is_frame(self):
+ return self.contains("Frame[")
+
+class ConnectionProperty:
+ def __init__(self, line):
+ self.addr = line.get_identifier_remote_addr()
+ self.channel = line.get_channel()
+ self.ops = [line]
+ def add_op(self, line):
+ self.ops.append(line)
+
+class Connection(ConnectionProperty):
+ def __init__(self, line):
+ ConnectionProperty.__init__(self, line)
+ self.session_list = [] # Keeps session creation order
+ self.session_dict = {} # For looking up by channel no.
+ def __str__(self):
+ return "Connection %s (ops=%d; sessions=%d):" % (self.addr, len(self.ops), len(self.session_dict))
+ def add_session(self, session):
+ self.session_list.append(session)
+ self.session_dict[session.channel] = session
+ def get_session(self, channel):
+ return self.session_dict[channel]
+
+class Session(ConnectionProperty):
+ def __init__(self, line):
+ ConnectionProperty.__init__(self, line)
+ self.name = line.get_named_value("name")
+ self.send_cnt = 0
+ self.recv_cnt = 0
+ self.txn_flag = False
+ self.txn_cnt = 0
+ self.recv_cmds = {} # For looking up by cmd no
+ self.send_cmds = {} # For looking up by cmd no
+ def __str__(self):
+ if self.txn_flag:
+ return " + Session %d (name=%s send-cmds=%d recv-cmds=%d txns=%d):" % (self.channel, self.name,
+ self.send_cnt, self.recv_cnt,
+ self.txn_cnt)
+ return " + Session %d (name=%s send-cmds=%d recv-cmds=%d non-txn):" % (self.channel, self.name, self.send_cnt,
+ self.recv_cnt)
+ def incr_recv_cnt(self, line):
+ self.recv_cmds[self.recv_cnt] = line
+ self.recv_cnt += 1
+ def incr_send_cnt(self, line):
+ self.send_cmds[self.send_cnt] = line
+ self.send_cnt += 1
+ def set_send_txn_cnt(self, cmd):
+ self.send_cmds[cmd].txn_cnt = self.txn_cnt
+
+class TraceAnalysis:
+ def __init__(self):
+ self.connection_list = [] # Keeps connection creation order
+ self.connection_dict = {} # For looking up by connection address
+ parser = OptionParser(usage="%prog [options] trace-file", version="%%prog %d.%d" % (MAJOR_VERSION, MINOR_VERSION),
+ description="A tool to structure and display Qpid broker trace logs.")
+ parser.add_option("--connection-summary", action="store_true", default=False, dest="connection_summary",
+ help="Hide connection details, provide one-line summary")
+ parser.add_option("--session-summary", action="store_true", default=False, dest="session_summary",
+ help="Hide session details, provide one-line summary")
+ parser.add_option("--summary", "-s", action="store_true", default=False, dest="summary",
+ help="Hide both connection and session details. Equivalent to --connection-summary and"
+ "--session-summary")
+ self.opts, self.args = parser.parse_args()
+ if len(self.args) == 0: raise Exception("Missing trace-file argument")
+ def analyze_trace(self):
+ lcnt = 0
+ print "Reading trace file %s:" % self.args[0]
+ log_file = open(self.args[0], "r")
+ try:
+ for fline in log_file:
+ lcnt += 1
+ try:
+ lline = LogLine(lcnt, fline)
+ if lline.is_log_level(LogLevel.TRACE) and lline.is_frame():
+ if lline.contains("{ConnectionStartBody"):
+ conn = Connection(lline)
+ self.connection_list.append(conn)
+ self.connection_dict[conn.addr] = conn
+ elif lline.contains("{Connection"):
+ self.connection_dict[lline.get_identifier_remote_addr()].add_op(lline)
+ elif lline.contains("{SessionAttachBody"):
+ ssn = Session(lline)
+ self.connection_dict[ssn.addr].add_session(ssn)
+ else:
+ ssn = self.connection_dict[lline.get_identifier_remote_addr()].get_session(lline.get_channel())
+ ssn.add_op(lline)
+ if lline.line[lline.find("{") + 1 : lline.find("Body")] in EXEC_COMMANDS:
+ if lline.contains("RECV"):
+ lline.cmd_cnt = ssn.recv_cnt
+ if ssn.txn_flag:
+ if lline.contains("MessageAcceptBody"):
+ lline.txn_cnt = ssn.txn_cnt
+ for cmd in lline.get_msg_accept_range():
+ ssn.set_send_txn_cnt(cmd)
+ if lline.contains("MessageTransferBody"): lline.txn_cnt = ssn.txn_cnt
+ ssn.incr_recv_cnt(lline)
+ elif lline.contains("SEND") or lline.contains("SENT"):
+ lline.cmd_cnt = ssn.send_cnt
+ ssn.incr_send_cnt(lline)
+ # TODO: This treatment will probably break down for DTX
+ if lline.contains("xSelectBody"):
+ ssn.txn_flag = True
+ elif lline.contains("xCommitBody") or lline.contains("xRollbackBody"):
+ lline.txn_cnt = ssn.txn_cnt
+ ssn.txn_cnt += 1
+ except KeyboardInterrupt, e: raise e
+ except: pass
+ if (lcnt + 1) % PROGRESS_LINES_PER_DOT == 0:
+ sys.stdout.write(".")
+ sys.stdout.flush()
+ finally: log_file.close()
+ if lcnt > PROGRESS_LINES_PER_DOT: print
+ print "Read and analyzed", lcnt, "lines."
+ def print_analysis(self):
+ if len(self.connection_list) > 0:
+ for c in self.connection_list:
+ print
+ print c
+ if not self.opts.connection_summary and not self.opts.summary:
+ print HEADER_STR
+ for o in c.ops:
+ print o
+ for s in c.session_list:
+ print s
+ if not self.opts.session_summary and not self.opts.summary:
+ print HEADER_STR
+ for o in s.ops:
+ print o
+ else:
+ print "No trace-level entries found in log."
+
+def check_python_version(major, minor, micro):
+ if sys.version_info < (major, minor, micro):
+ print "Incorrect Python version: %s found; >= %d.%d.%d needed." % (sys.version.split()[0], major, minor, micro)
+ sys.exit(-1)
+
+# === Main program ===
+
+if __name__ == '__main__':
+ check_python_version(2, 4, 0)
+ t = TraceAnalysis()
+ t.analyze_trace()
+ t.print_analysis()
+ \ No newline at end of file
diff --git a/qpid/cpp/src/tests/qpid-build-rinstall b/qpid/cpp/src/tests/qpid-build-rinstall
new file mode 100755
index 0000000000..beff7dffba
--- /dev/null
+++ b/qpid/cpp/src/tests/qpid-build-rinstall
@@ -0,0 +1,28 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under onemake
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Run "make install"" locally then copy the install tree to each of $HOSTS
+# Must be run in a configured qpid build directory.
+#
+test -f config.status || { echo "Not in a configured build directory."; usage; }
+. src/tests/install_env.sh
+set -ex
+make && make -j1 install
+rsynchosts $QPID_INSTALL_PREFIX
diff --git a/qpid/cpp/src/tests/qpid-client-test.cpp b/qpid/cpp/src/tests/qpid-client-test.cpp
new file mode 100644
index 0000000000..9198324f93
--- /dev/null
+++ b/qpid/cpp/src/tests/qpid-client-test.cpp
@@ -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.
+ *
+ */
+
+/**
+ * This file provides a simple test (and example) of basic
+ * functionality including declaring an exchange and a queue, binding
+ * these together, publishing a message and receiving that message
+ * asynchronously.
+ */
+
+#include <iostream>
+
+#include "TestOptions.h"
+#include "qpid/client/Connection.h"
+#include "qpid/client/Message.h"
+#include "qpid/client/Session.h"
+#include "qpid/client/SubscriptionManager.h"
+
+
+using namespace qpid;
+using namespace qpid::client;
+using namespace qpid::framing;
+using std::string;
+
+namespace qpid {
+namespace tests {
+
+struct Args : public TestOptions {
+ uint msgSize;
+ bool verbose;
+
+ Args() : TestOptions("Simple test of Qpid c++ client; sends and receives a single message."), msgSize(26), verbose(false)
+ {
+ addOptions()
+ ("size", optValue(msgSize, "N"), "message size")
+ ("verbose", optValue(verbose), "print out some status messages");
+ }
+};
+
+const std::string chars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
+
+std::string generateData(uint size)
+{
+ if (size < chars.length()) {
+ return chars.substr(0, size);
+ }
+ std::string data;
+ for (uint i = 0; i < (size / chars.length()); i++) {
+ data += chars;
+ }
+ data += chars.substr(0, size % chars.length());
+ return data;
+}
+
+void print(const std::string& text, const Message& msg)
+{
+ std::cout << text;
+ if (msg.getData().size() > 16) {
+ std::cout << msg.getData().substr(0, 16) << "...";
+ } else {
+ std::cout << msg.getData();
+ }
+ std::cout << std::endl;
+}
+
+}} // namespace qpid::tests
+
+using namespace qpid::tests;
+
+int main(int argc, char** argv)
+{
+ try {
+ Args opts;
+ opts.parse(argc, argv);
+
+ //Connect to the broker:
+ Connection connection;
+ opts.open(connection);
+ if (opts.verbose) std::cout << "Opened connection." << std::endl;
+
+ //Create and open a session on the connection through which
+ //most functionality is exposed:
+ Session session = connection.newSession();
+ if (opts.verbose) std::cout << "Opened session." << std::endl;
+
+
+ //'declare' the exchange and the queue, which will create them
+ //as they don't exist
+ session.exchangeDeclare(arg::exchange="MyExchange", arg::type="direct");
+ if (opts.verbose) std::cout << "Declared exchange." << std::endl;
+ session.queueDeclare(arg::queue="MyQueue", arg::autoDelete=true, arg::exclusive=true);
+ if (opts.verbose) std::cout << "Declared queue." << std::endl;
+
+ //now bind the queue to the exchange
+ session.exchangeBind(arg::exchange="MyExchange", arg::queue="MyQueue", arg::bindingKey="MyKey");
+ if (opts.verbose) std::cout << "Bound queue to exchange." << std::endl;
+
+ //create and send a message to the exchange using the routing
+ //key we bound our queue with:
+ Message msgOut(generateData(opts.msgSize));
+ msgOut.getDeliveryProperties().setRoutingKey("MyKey");
+ session.messageTransfer(arg::destination="MyExchange", arg::content=msgOut, arg::acceptMode=1);
+ if (opts.verbose) print("Published message: ", msgOut);
+
+ // Using the SubscriptionManager, get the message from the queue.
+ SubscriptionManager subs(session);
+ Message msgIn = subs.get("MyQueue");
+ if (msgIn.getData() == msgOut.getData())
+ if (opts.verbose) std::cout << "Received the exepected message." << std::endl;
+
+ //close the session & connection
+ session.close();
+ if (opts.verbose) std::cout << "Closed session." << std::endl;
+ connection.close();
+ if (opts.verbose) std::cout << "Closed connection." << std::endl;
+ return 0;
+ } catch(const std::exception& e) {
+ std::cout << e.what() << std::endl;
+ }
+ return 1;
+}
diff --git a/qpid/cpp/src/tests/qpid-cluster-benchmark b/qpid/cpp/src/tests/qpid-cluster-benchmark
new file mode 100755
index 0000000000..f20ac6ac30
--- /dev/null
+++ b/qpid/cpp/src/tests/qpid-cluster-benchmark
@@ -0,0 +1,64 @@
+#!/bin/sh
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Benchmark script for comparing cluster performance.
+
+# Default options
+MESSAGES="-m 10000"
+REPEAT="--repeat 10"
+QUEUES="-q 6"
+SENDERS="-s 3"
+RECEIVERS="-r 3"
+BROKERS= # Local broker
+CLIENT_HOSTS= # No ssh, all clients are local
+# Connection options
+TCP_NODELAY=false
+RECONNECT=true
+HEARTBEAT=1
+
+while getopts "m:f:n:b:q:s:r:c:h:i:txyv-" opt; do
+ case $opt in
+ b) BROKERS="-b $OPTARG";;
+ c) CLIENT_HOSTS="-c $OPTARG";;
+ h) HEARTBEAT=$OPTARG;;
+ i) RECONNECT=$OPTARG;;
+ m) MESSAGES="-m $OPTARG";;
+ n) REPEAT="--repeat $OPTARG";;
+ q) QUEUES="-q $OPTARG";;
+ r) RECEIVERS="-r $OPTARG";;
+ s) SENDERS="-s $OPTARG";;
+ t) TCP_NODELAY=true;;
+ v) OPTS="--verbose";;
+ x) SAVE_RECEIVED="--save-received";;
+ y) NO_DELETE="--no-delete";;
+ -) break ;;
+ *) echo "Unknown option"; exit 1;;
+ esac
+done
+shift $(($OPTIND-1))
+
+CONNECTION_OPTIONS="--connection-options {tcp-nodelay:$TCP_NODELAY,reconnect:$RECONNECT,heartbeat:$HEARTBEAT}"
+
+BROKER=$(echo $BROKERS | sed s/,.*//)
+run_test() { echo $*; shift; "$@"; echo; echo; echo; }
+
+OPTS="$OPTS $REPEAT $BROKERS --summarize $QUEUES $SENDERS $RECEIVERS $MESSAGES $CLIENT_HOSTS $SAVE_RECEIVED $CONNECTION_OPTIONS $NO_DELETE"
+
+run_test "Benchmark:" qpid-cpp-benchmark $OPTS "$@"
diff --git a/qpid/cpp/src/tests/qpid-cpp-benchmark b/qpid/cpp/src/tests/qpid-cpp-benchmark
new file mode 100755
index 0000000000..2d5ec711fe
--- /dev/null
+++ b/qpid/cpp/src/tests/qpid-cpp-benchmark
@@ -0,0 +1,363 @@
+#!/usr/bin/env python
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 optparse, time, re, os
+
+try:
+ import qpid_messaging as qm
+except ImportError:
+ qpid_messaging = None
+ import qpid.messaging as qm
+
+from threading import Thread
+from subprocess import Popen, PIPE, STDOUT
+
+op = optparse.OptionParser(usage="usage: %prog [options]",
+ description="simple performance benchmarks")
+op.add_option("-b", "--broker", default=[], action="append", type="str",
+ help="url of broker(s) to connect to, round robin on multiple brokers")
+op.add_option("-c", "--client-host", default=[], action="append", type="str",
+ help="host(s) to run clients on via ssh, round robin on mulple hosts")
+op.add_option("-q", "--queues", default=1, type="int", metavar="N",
+ help="create N queues (default %default)")
+op.add_option("-s", "--senders", default=1, type="int", metavar="N",
+ help="start N senders per queue (default %default)")
+op.add_option("-r", "--receivers", default=1, type="int", metavar="N",
+ help="start N receivers per queue (default %default)")
+op.add_option("-m", "--messages", default=100000, type="int", metavar="N",
+ help="send N messages per sender (default %default)")
+op.add_option("--queue-name", default="benchmark", metavar="NAME",
+ help="base name for queues (default %default)")
+op.add_option("--send-rate", default=0, metavar="N",
+ help="send rate limited to N messages/second, 0 means no limit (default %default)")
+op.add_option("--receive-rate", default=0, metavar="N",
+ help="receive rate limited to N messages/second, 0 means no limit (default %default)")
+op.add_option("--content-size", default=1024, type="int", metavar="BYTES",
+ help="message size in bytes (default %default)")
+op.add_option("--ack-frequency", default=100, metavar="N", type="int",
+ help="receiver ack's every N messages, 0 means unconfirmed (default %default)")
+op.add_option("--tx", default=0, metavar="N", type="int",
+ help="Transaction batch size, 0 means no transactions")
+op.add_option("--no-report-header", dest="report_header", default=True,
+ action="store_false", help="don't print header on report")
+op.add_option("--summarize", default=False, action="store_true",
+ help="print summary statistics for multiple senders/receivers: total throughput, average latency")
+op.add_option("--repeat", default=1, metavar="N", help="repeat N times", type="int")
+op.add_option("--send-option", default=[], action="append", type="str",
+ help="Additional option for sending addresses")
+op.add_option("--receive-option", default=[], action="append", type="str",
+ help="Additional option for receiving addresses")
+op.add_option("--create-option", default=[], action="append", type="str",
+ help="Additional option for creating addresses")
+op.add_option("--send-arg", default=[], action="append", type="str",
+ help="Additional argument for qpid-send")
+op.add_option("--receive-arg", default=[], action="append", type="str",
+ help="Additional argument for qpid-receive")
+op.add_option("--no-timestamp", dest="timestamp", default=True,
+ action="store_false", help="don't add a timestamp, no latency results")
+op.add_option("--sequence", dest="sequence", default=False,
+ action="store_true", help="add a sequence number to each message")
+op.add_option("--connection-options", type="str",
+ help="Connection options for senders & receivers")
+op.add_option("--durable", default=False, action="store_true",
+ help="Use durable queues and messages")
+op.add_option("-t", "--timeout", default=1.0, type="float", metavar="SECONDS",
+ help="Timeout for fetch operations (default %default)")
+op.add_option("--save-received", default=False, action="store_true",
+ help="Save received message content to files <queuename>-receiver-<n>.msg")
+op.add_option("--verbose", default=False, action="store_true",
+ help="Show commands executed")
+op.add_option("--fill-drain", default=False, action="store_true",
+ help="First fill the queues, then drain them")
+op.add_option("--qpid-send-path", default="", type="str", metavar="PATH",
+ help="path to qpid-send binary")
+op.add_option("--qpid-receive-path", default="", type="str", metavar="PATH",
+ help="path to qpid-receive binary")
+
+single_quote_re = re.compile("'")
+def posix_quote(string):
+ """ Quote a string for use as an argument in a posix shell"""
+ return "'" + single_quote_re.sub("\\'", string) + "'";
+
+def ssh_command(host, command):
+ """ Convert command into an ssh command on host with quoting"""
+ return ["ssh", host] + [posix_quote(arg) for arg in command]
+
+class Clients:
+ def __init__(self): self.clients=[]
+
+ def add(self, client):
+ self.clients.append(client)
+ return client
+
+ def kill(self):
+ for c in self.clients:
+ try: c.kill()
+ except: pass
+
+class PopenCommand(Popen):
+ """Like Popen but you can query for the command"""
+ def __init__(self, command, *args, **kwargs):
+ self.command = command
+ Popen.__init__(self, command, *args, **kwargs)
+
+clients = Clients()
+
+def start_receive(queue, index, opts, ready_queue, broker, host):
+ address_opts=opts.receive_option
+ if opts.durable: address_opts += ["node:{durable:true}"]
+ address="%s;{%s}"%(queue,",".join(address_opts))
+ msg_total=opts.senders*opts.messages
+ messages = msg_total/opts.receivers;
+ if (index < msg_total%opts.receivers): messages += 1
+ if (messages == 0): return None
+ command = [os.path.join(opts.qpid_receive_path, "qpid-receive"),
+ "-b", broker,
+ "-a", address,
+ "-m", str(messages),
+ "--forever",
+ "--print-content=no",
+ "--receive-rate", str(opts.receive_rate),
+ "--report-total",
+ "--ack-frequency", str(opts.ack_frequency),
+ "--ready-address", "%s;{create:always}"%ready_queue,
+ "--report-header=no",
+ "--tx=%s" % opts.tx
+ ]
+ if opts.save_received:
+ command += ["--save-content=%s-receiver-%s.msg"%(queue,index)]
+ command += opts.receive_arg
+ if opts.connection_options:
+ command += ["--connection-options",opts.connection_options]
+ if host: command = ssh_command(host, command)
+ if opts.verbose: print "Receiver: ", command
+ return clients.add(PopenCommand(command, stdout=PIPE, stderr=PIPE))
+
+def start_send(queue, opts, broker, host):
+ address="%s;{%s}"%(queue,",".join(opts.send_option + ["create:always"]))
+ command = [os.path.join(opts.qpid_send_path, "qpid-send"),
+ "-b", broker,
+ "-a", address,
+ "--messages", str(opts.messages),
+ "--content-size", str(opts.content_size),
+ "--send-rate", str(opts.send_rate),
+ "--report-total",
+ "--report-header=no",
+ "--timestamp=%s"%(opts.timestamp and "yes" or "no"),
+ "--sequence=%s"%(opts.sequence and "yes" or "no"),
+ "--durable=%d" % opts.durable,
+ "--tx=%s" % opts.tx
+ ]
+ command += opts.send_arg
+ if opts.connection_options:
+ command += ["--connection-options",opts.connection_options]
+ if host: command = ssh_command(host, command)
+ if opts.verbose: print "Sender: ", command
+ return clients.add(PopenCommand(command, stdout=PIPE, stderr=PIPE))
+
+def error_msg(out, err):
+ return ("\n[stdout]\n%s\n[stderr]\n%s[end]"%(out, err))
+
+def first_line(p):
+ out,err=p.communicate()
+ if p.returncode != 0:
+ raise Exception("Process exit %d: %s"%(p.returncode, error_msg(out,err)))
+ return out.split("\n")[0]
+
+def connect(broker, opts):
+ if opts.connection_options:
+ copts = dict([kv.strip().split(":") for kv in opts.connection_options.strip("{}").split(",")])
+ else:
+ copts = {}
+ return qm.Connection.establish(broker, **copts)
+
+def drain(queue, session, opts):
+ """
+ Drain a queue to make sure it is empty. Throw away the messages.
+ """
+ if opts.verbose: print "Draining", queue
+ r = session.receiver(queue, capacity=1000)
+ n = 0
+ try:
+ while True:
+ # FIXME aconway 2014-11-21: activemq broker does not respect the drain flag
+ # so fetch on an empty queue will hang forever, use get with timeout instead.
+ # r.fetch(timeout=0)
+ m = qm.Message()
+ r.get(timeout=opts.timeout)
+ n += 1
+ if n % 500 == 0: r.session.acknowledge()
+ r.session.acknowledge()
+ except qm.Empty:
+ pass
+ r.close()
+ if opts.verbose: print "Drained", queue, n
+
+def clear_queues(queues, brokers, opts):
+ c = connect(brokers[0], opts)
+ for q in queues:
+ s = c.session()
+ need_drain = False
+ try:
+ s.sender("%s;{delete:always}"%(q)).close()
+ if opts.verbose: print "Deleted", q
+ except qm.NotFound:
+ s = c.session()
+ except qm.AddressError:
+ need_drain = True # AMQP 1.0 does not support delete, drain instead.
+ s = c.session()
+ address_opts = ["create:always"]
+ if opts.durable: address_opts += ["node:{durable:true}"]
+ address = "%s;{%s}"%(q, ",".join(opts.create_option + address_opts))
+ if opts.verbose: print "Declaring", address
+ s.sender(address)
+ if need_drain: drain(q, s, opts)
+ c.close()
+
+def print_header(timestamp):
+ if timestamp: latency_header="\tl-min\tl-max\tl-avg\ttotal-tp"
+ else: latency_header=""
+ print "send-tp\trecv-tp%s"%latency_header
+
+def parse(parser, lines): # Parse sender/receiver output
+ return [map(lambda p: p[0](p[1]), zip(parser,line.split())) for line in lines]
+
+def parse_senders(senders):
+ return parse([int],[first_line(p) for p in senders])
+
+def parse_receivers(receivers):
+ return parse([int,float,float,float],[first_line(p) for p in receivers if p])
+
+def print_data(send_stats, recv_stats, total_tp):
+ for send,recv in map(None, send_stats, recv_stats):
+ line=""
+ if send: line += "%d"%send[0]
+ if recv:
+ line += "\t%d"%recv[0]
+ if len(recv) == 4: line += "\t%.2f\t%.2f\t%.2f"%tuple(recv[1:])
+ if total_tp is not None:
+ line += "\t%d"%total_tp
+ total_tp = None
+ print line
+
+def print_summary(send_stats, recv_stats, total_tp):
+ def avg(s): sum(s) / len(s)
+ send_tp = sum([l[0] for l in send_stats])
+ recv_tp = sum([l[0] for l in recv_stats])
+ summary = "%d\t%d"%(send_tp, recv_tp)
+ if recv_stats and len(recv_stats[0]) == 4:
+ l_min = sum(l[1] for l in recv_stats)/len(recv_stats)
+ l_max = sum(l[2] for l in recv_stats)/len(recv_stats)
+ l_avg = sum(l[3] for l in recv_stats)/len(recv_stats)
+ summary += "\t%.2f\t%.2f\t%.2f"%(l_min, l_max, l_avg)
+ summary += "\t%d"%total_tp
+ print summary
+
+
+class ReadyReceiver:
+ """A receiver for ready messages"""
+ def __init__(self, queue, broker, opts):
+ self.connection = connect(broker, opts)
+ self.receiver = self.connection.session().receiver(queue)
+ self.receiver.session.sync()
+ self.timeout=opts.timeout
+
+ def wait(self, receivers):
+ try:
+ for i in receivers: self.receiver.fetch(self.timeout)
+ self.receiver.session.acknowledge()
+ self.connection.close()
+ except qm.Empty:
+ for r in receivers:
+ if (r.poll() is not None):
+ out,err=r.communicate()
+ raise Exception("Receiver error: %s\n%s" %
+ (" ".join(r.command), error_msg(out,err)))
+ raise Exception("Timed out waiting for receivers to be ready")
+
+def flatten(l):
+ return sum(map(lambda s: re.split(re.compile("\s*,\s*|\s+"), s), l), [])
+
+class RoundRobin:
+ def __init__(self,items):
+ self.items = items
+ self.index = 0
+
+ def next(self):
+ if not self.items: return None
+ ret = self.items[self.index]
+ self.index = (self.index+1)%len(self.items)
+ return ret
+
+def main():
+ opts, args = op.parse_args()
+ opts.client_host = flatten(opts.client_host)
+ if not opts.broker:
+ if opts.client_host:
+ raise Exception("--broker must be specified if --client_host is.")
+ opts.broker = ["127.0.0.1"] # Deafult to local broker
+ opts.broker = flatten(opts.broker)
+ brokers = RoundRobin(opts.broker)
+ client_hosts = RoundRobin(opts.client_host)
+ send_out = ""
+ receive_out = ""
+ ready_queue="%s-ready"%(opts.queue_name)
+ queues = ["%s-%s"%(opts.queue_name, i) for i in xrange(opts.queues)]
+ try:
+ for i in xrange(opts.repeat):
+ clear_queues(queues+[ready_queue], opts.broker, opts)
+ ready_receiver = ReadyReceiver(ready_queue, opts.broker[0], opts)
+
+ def start_receivers():
+ return [ start_receive(q, j, opts, ready_queue, brokers.next(), client_hosts.next())
+ for q in queues for j in xrange(opts.receivers) ]
+
+
+ def start_senders():
+ return [ start_send(q, opts,brokers.next(), client_hosts.next())
+ for q in queues for j in xrange(opts.senders) ]
+
+ if opts.report_header and i == 0: print_header(opts.timestamp)
+
+ if opts.fill_drain:
+ # First fill the queues, then drain them
+ start = time.time()
+ senders = start_senders()
+ for p in senders: p.wait()
+ receivers = start_receivers()
+ for p in receivers: p.wait()
+ else:
+ # Run senders and receivers in parallel
+ receivers = start_receivers()
+ ready_receiver.wait(filter(None, receivers)) # Wait for receivers ready
+ start = time.time()
+ senders = start_senders()
+ for p in senders + receivers: p.wait()
+
+ total_sent = opts.queues * opts.senders * opts.messages
+ total_tp = total_sent / (time.time()-start)
+ send_stats=parse_senders(senders)
+ recv_stats=parse_receivers(receivers)
+ if opts.summarize: print_summary(send_stats, recv_stats, total_tp)
+ else: print_data(send_stats, recv_stats, total_tp)
+ finally: clients.kill() # No strays
+
+if __name__ == "__main__": main()
+
diff --git a/qpid/cpp/src/tests/qpid-ctrl b/qpid/cpp/src/tests/qpid-ctrl
new file mode 100755
index 0000000000..4246c57898
--- /dev/null
+++ b/qpid/cpp/src/tests/qpid-ctrl
@@ -0,0 +1,120 @@
+#!/usr/bin/env python
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 optparse
+from qpid.messaging import *
+from qpid.util import URL
+from qpid.log import enable, DEBUG, WARN
+
+def nameval(st):
+ idx = st.find("=")
+ if idx >= 0:
+ name = st[0:idx]
+ value = st[idx+1:]
+ else:
+ name = st
+ value = None
+ return name, value
+
+def list_map_entries(m):
+ r = ""
+ for t in m:
+ r += "%s=%s " % (t, m[t])
+ return r
+
+def get_qmfv2_result(m):
+ if m.properties['x-amqp-0-10.app-id'] == 'qmf2':
+ if m.properties['qmf.opcode'] == '_method_response':
+ return m.content['_arguments']
+ elif m.properties['qmf.opcode'] == '_exception':
+ raise Exception("Error: %s" % list_map_entries(m.content['_values']))
+ else: raise Exception("Invalid response received, unexpected opcode: %s" % m)
+ else: raise Exception("Invalid response received, not a qmfv2 method: %s" % m)
+
+
+parser = optparse.OptionParser(usage="usage: %prog [options] COMMAND ...",
+ description="Invoke the specified command.")
+parser.add_option("-b", "--broker", default="localhost",
+ help="connect to specified BROKER (default %default)")
+parser.add_option("-c", "--class", dest="qmfclass", default="broker",
+ help="class of object on which command is being invoked (default %default)")
+parser.add_option("-p", "--package", default="org.apache.qpid.broker",
+ help="package of object on which command is being invoked (default %default)")
+parser.add_option("-i", "--id", default="amqp-broker",
+ help="identifier of object on which command is being invoked (default %default)")
+parser.add_option("-a", "--address", default="qmf.default.direct/broker",
+ help="address to send commands to (default %default)")
+parser.add_option("-t", "--timeout", type="float", default=5,
+ help="timeout in seconds to wait for response before exiting (default %default)")
+parser.add_option("-v", dest="verbose", action="store_true",
+ help="enable logging")
+
+opts, args = parser.parse_args()
+
+if opts.verbose:
+ enable("qpid", DEBUG)
+else:
+ enable("qpid", WARN)
+
+if args:
+ command = args.pop(0)
+else:
+ parser.error("command is required")
+
+
+conn = Connection(opts.broker)
+try:
+ conn.open()
+ ssn = conn.session()
+ snd = ssn.sender(opts.address)
+ reply_to = "qmf.default.direct/%s; {node: {type: topic}}" % str(uuid4())
+ rcv = ssn.receiver(reply_to)
+
+ object_name = "%s:%s:%s" % (opts.package, opts.qmfclass, opts.id)
+ method_name = command
+ arguments = {}
+ for a in args:
+ name, val = nameval(a)
+ if val[0] == '{' or val[0] == '[':
+ arguments[name] = eval(val)
+ else:
+ arguments[name] = val
+ content = {
+ "_object_id": {"_object_name": object_name},
+ "_method_name": method_name,
+ "_arguments": arguments
+ }
+ msg = Message(reply_to=reply_to, content=content)
+ msg.properties["x-amqp-0-10.app-id"] = "qmf2"
+ msg.properties["qmf.opcode"] = "_method_request"
+ snd.send(msg)
+
+ try:
+ print list_map_entries(get_qmfv2_result(rcv.fetch(timeout=opts.timeout)))
+ except Empty:
+ print "No response received!"
+ except Exception, e:
+ print e
+except ReceiverError, e:
+ print e
+except KeyboardInterrupt:
+ pass
+
+conn.close()
diff --git a/qpid/cpp/src/tests/qpid-latency-test.cpp b/qpid/cpp/src/tests/qpid-latency-test.cpp
new file mode 100644
index 0000000000..a03963467b
--- /dev/null
+++ b/qpid/cpp/src/tests/qpid-latency-test.cpp
@@ -0,0 +1,480 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+
+#include <algorithm>
+#include <limits>
+#include <iostream>
+#include <memory>
+#include <sstream>
+#include <vector>
+
+#include "TestOptions.h"
+#include "qpid/sys/Thread.h"
+#include "qpid/client/Connection.h"
+#include "qpid/client/Message.h"
+#include "qpid/client/AsyncSession.h"
+#include "qpid/client/SubscriptionManager.h"
+#include "qpid/sys/Time.h"
+
+using namespace qpid;
+using namespace qpid::client;
+using namespace qpid::sys;
+using std::string;
+
+namespace qpid {
+namespace tests {
+
+typedef std::vector<std::string> StringSet;
+
+struct Args : public qpid::TestOptions {
+ uint size;
+ uint count;
+ uint rate;
+ bool sync;
+ uint reportFrequency;
+ uint timeLimit;
+ uint concurrentConnections;
+ uint prefetch;
+ uint ack;
+ bool cumulative;
+ bool csv;
+ bool durable;
+ string base;
+ bool singleConnect;
+
+ Args() : size(256), count(1000), rate(0), reportFrequency(1000),
+ timeLimit(0), concurrentConnections(1),
+ prefetch(100), ack(0),
+ durable(false), base("latency-test"), singleConnect(false)
+
+ {
+ addOptions()
+
+ ("size", optValue(size, "N"), "message size")
+ ("concurrentTests", optValue(concurrentConnections, "N"), "number of concurrent test setups, will create another publisher,\
+ subcriber, queue, and connections")
+ ("single-connection", optValue(singleConnect, "yes|no"), "Use one connection for multiple sessions.")
+ ("count", optValue(count, "N"), "number of messages to send")
+ ("rate", optValue(rate, "N"), "target message rate (causes count to be ignored)")
+ ("sync", optValue(sync), "send messages synchronously")
+ ("report-frequency", optValue(reportFrequency, "N"),
+ "number of milliseconds to wait between reports (ignored unless rate specified)")
+ ("time-limit", optValue(timeLimit, "N"),
+ "test duration, in seconds")
+ ("prefetch", optValue(prefetch, "N"), "prefetch count (0 implies no flow control, and no acking)")
+ ("ack", optValue(ack, "N"), "Ack frequency in messages (defaults to half the prefetch value)")
+ ("durable", optValue(durable, "yes|no"), "use durable messages")
+ ("csv", optValue(csv), "print stats in csv format (rate,min,max,avg)")
+ ("cumulative", optValue(cumulative), "cumulative stats in csv format")
+ ("queue-base-name", optValue(base, "<name>"), "base name for queues");
+ }
+};
+
+const std::string chars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
+
+Args opts;
+double c_min, c_avg, c_max;
+Connection globalConnection;
+
+uint64_t current_time()
+{
+ return Duration::FromEpoch();
+}
+
+struct Stats
+{
+ Mutex lock;
+ uint count;
+ double minLatency;
+ double maxLatency;
+ double totalLatency;
+
+ Stats();
+ void update(double l);
+ void print();
+ void reset();
+};
+
+class Client : public Runnable
+{
+protected:
+ Connection* connection;
+ Connection localConnection;
+ AsyncSession session;
+ Thread thread;
+ string queue;
+
+public:
+ Client(const string& q);
+ virtual ~Client();
+
+ void start();
+ void join();
+ void run();
+ virtual void test() = 0;
+};
+
+class Receiver : public Client, public MessageListener
+{
+ SubscriptionManager mgr;
+ uint count;
+ Stats& stats;
+
+public:
+ Receiver(const string& queue, Stats& stats);
+ void test();
+ void received(Message& msg);
+ Stats getStats();
+ uint getCount() { return count; }
+ void stop() { mgr.stop(); mgr.cancel(queue); }
+};
+
+
+class Sender : public Client
+{
+ string generateData(uint size);
+ void sendByRate();
+ void sendByCount();
+ Receiver& receiver;
+ const string data;
+
+public:
+ Sender(const string& queue, Receiver& receiver);
+ void test();
+};
+
+
+class Test
+{
+ const string queue;
+ Stats stats;
+ Receiver receiver;
+ Sender sender;
+ AbsTime begin;
+
+public:
+ Test(const string& q) : queue(q), receiver(queue, stats), sender(queue, receiver), begin(now()) {}
+ void start();
+ void join();
+ void report();
+};
+
+
+Client::Client(const string& q) : queue(q)
+{
+ if (opts.singleConnect){
+ connection = &globalConnection;
+ if (!globalConnection.isOpen()) opts.open(globalConnection);
+ }else{
+ connection = &localConnection;
+ opts.open(localConnection);
+ }
+ session = connection->newSession();
+}
+
+void Client::start()
+{
+ thread = Thread(this);
+}
+
+void Client::join()
+{
+ thread.join();
+}
+
+void Client::run()
+{
+ try{
+ test();
+ } catch(const std::exception& e) {
+ std::cout << "Error in receiver: " << e.what() << std::endl;
+ }
+}
+
+Client::~Client()
+{
+ try{
+ session.close();
+ connection->close();
+ } catch(const std::exception& e) {
+ std::cout << "Error in receiver: " << e.what() << std::endl;
+ }
+}
+
+Receiver::Receiver(const string& q, Stats& s) : Client(q), mgr(session), count(0), stats(s)
+{
+ session.queueDeclare(arg::queue=queue, arg::durable=opts.durable, arg::autoDelete=true);
+ uint msgCount = session.queueQuery(arg::queue=queue).get().getMessageCount();
+ if (msgCount) {
+ std::cout << "Warning: found " << msgCount << " msgs on " << queue << ". Purging..." << std::endl;
+ session.queuePurge(arg::queue=queue);
+ session.sync();
+ }
+ SubscriptionSettings settings;
+ if (opts.prefetch) {
+ settings.autoAck = (opts.ack ? opts.ack : (opts.prefetch / 2));
+ settings.flowControl = FlowControl::messageWindow(opts.prefetch);
+ } else {
+ settings.acceptMode = ACCEPT_MODE_NONE;
+ settings.flowControl = FlowControl::unlimited();
+ }
+ mgr.subscribe(*this, queue, settings);
+}
+
+void Receiver::test()
+{
+ mgr.run();
+ mgr.cancel(queue);
+}
+
+void Receiver::received(Message& msg)
+{
+ ++count;
+ uint64_t receivedAt = current_time();
+ uint64_t sentAt = msg.getDeliveryProperties().getTimestamp();
+
+ stats.update(((double) (receivedAt - sentAt)) / TIME_MSEC);
+
+ if (!opts.rate && count >= opts.count) {
+ mgr.stop();
+ }
+}
+
+void Stats::update(double latency)
+{
+ Mutex::ScopedLock l(lock);
+ count++;
+ minLatency = std::min(minLatency, latency);
+ maxLatency = std::max(maxLatency, latency);
+ totalLatency += latency;
+}
+
+Stats::Stats() : count(0), minLatency(std::numeric_limits<double>::max()), maxLatency(0), totalLatency(0) {}
+
+void Stats::print()
+{
+ static bool already_have_stats = false;
+ uint value;
+
+ if (opts.rate)
+ value = opts.rate;
+ else
+ value = opts.count;
+ Mutex::ScopedLock l(lock);
+ double aux_avg = (totalLatency / count);
+ if (!opts.cumulative) {
+ if (!opts.csv) {
+ if (count) {
+ std::cout << "Latency(ms): min=" << minLatency << ", max=" <<
+ maxLatency << ", avg=" << aux_avg;
+ } else {
+ std::cout << "Stalled: no samples for interval";
+ }
+ } else {
+ if (count) {
+ std::cout << value << "," << minLatency << "," << maxLatency <<
+ "," << aux_avg;
+ } else {
+ std::cout << value << "," << minLatency << "," << maxLatency <<
+ ", Stalled";
+ }
+ }
+ } else {
+ if (count) {
+ if (already_have_stats) {
+ c_avg = (c_min + aux_avg) / 2;
+ if (c_min > minLatency) c_min = minLatency;
+ if (c_max < maxLatency) c_max = maxLatency;
+ } else {
+ c_avg = aux_avg;
+ c_min = minLatency;
+ c_max = maxLatency;
+ already_have_stats = true;
+ }
+ std::cout << value << "," << c_min << "," << c_max <<
+ "," << c_avg;
+ } else {
+ std::cout << "Stalled: no samples for interval";
+ }
+ }
+}
+
+void Stats::reset()
+{
+ Mutex::ScopedLock l(lock);
+ count = 0;
+ totalLatency = maxLatency = 0;
+ minLatency = std::numeric_limits<double>::max();
+}
+
+Sender::Sender(const string& q, Receiver& receiver) : Client(q), receiver(receiver), data(generateData(opts.size)) {}
+
+void Sender::test()
+{
+ if (opts.rate) sendByRate();
+ else sendByCount();
+}
+
+void Sender::sendByCount()
+{
+ Message msg(data, queue);
+ if (opts.durable) {
+ msg.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT);
+ }
+
+ for (uint i = 0; i < opts.count; i++) {
+ uint64_t sentAt(current_time());
+ msg.getDeliveryProperties().setTimestamp(sentAt);
+ async(session).messageTransfer(arg::content=msg, arg::acceptMode=1);
+ if (opts.sync) session.sync();
+ }
+ session.sync();
+}
+
+void Sender::sendByRate()
+{
+ Message msg(data, queue);
+ if (opts.durable) {
+ msg.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT);
+ }
+ uint64_t interval = TIME_SEC/opts.rate;
+ int64_t timeLimit = opts.timeLimit * TIME_SEC;
+ uint64_t sent = 0;
+ AbsTime start = now();
+ AbsTime last = start;
+ while (true) {
+ AbsTime sentAt=now();
+ msg.getDeliveryProperties().setTimestamp(Duration::FromEpoch());
+ async(session).messageTransfer(arg::content=msg, arg::acceptMode=1);
+ if (opts.sync) session.sync();
+ ++sent;
+ if (Duration(last, sentAt) > (opts.reportFrequency*TIME_MSEC)) {
+ Duration t(start, now());
+ //check rate actually achieved thus far
+ if (t/TIME_SEC) {
+ uint actualRate = sent / (t/TIME_SEC);
+ //report inability to stay within 1% of desired rate
+ if (actualRate < opts.rate && opts.rate - actualRate > opts.rate/100) {
+ std::cerr << "WARNING: Desired send rate: " << opts.rate << ", actual send rate: " << actualRate << std::endl;
+ }
+ }
+ last = sentAt;
+ }
+
+ AbsTime waitTill(start, sent*interval);
+ Duration delay(sentAt, waitTill);
+ if (delay > 0)
+ sys::usleep(delay / TIME_USEC);
+ if (timeLimit != 0 && Duration(start, now()) > timeLimit) {
+ session.sync();
+ receiver.stop();
+ break;
+ }
+ }
+}
+
+string Sender::generateData(uint size)
+{
+ if (size < chars.length()) {
+ return chars.substr(0, size);
+ }
+ std::string data;
+ for (uint i = 0; i < (size / chars.length()); i++) {
+ data += chars;
+ }
+ data += chars.substr(0, size % chars.length());
+ return data;
+}
+
+
+void Test::start()
+{
+ receiver.start();
+ begin = AbsTime(now());
+ sender.start();
+}
+
+void Test::join()
+{
+ sender.join();
+ receiver.join();
+ AbsTime end = now();
+ Duration time(begin, end);
+ double msecs(time / TIME_MSEC);
+ if (!opts.csv) {
+ std::cout << "Sent " << receiver.getCount() << " msgs through " << queue
+ << " in " << msecs << "ms (" << (receiver.getCount() * 1000 / msecs) << " msgs/s) ";
+ }
+ stats.print();
+ std::cout << std::endl;
+}
+
+void Test::report()
+{
+ stats.print();
+ std::cout << std::endl;
+ stats.reset();
+}
+
+}} // namespace qpid::tests
+
+using namespace qpid::tests;
+
+int main(int argc, char** argv)
+{
+ try {
+ opts.parse(argc, argv);
+ if (opts.cumulative)
+ opts.csv = true;
+
+ Connection localConnection;
+ AsyncSession session;
+
+ boost::ptr_vector<Test> tests(opts.concurrentConnections);
+ for (uint i = 0; i < opts.concurrentConnections; i++) {
+ std::ostringstream out;
+ out << opts.base << "-" << (i+1);
+ tests.push_back(new Test(out.str()));
+ }
+ for (boost::ptr_vector<Test>::iterator i = tests.begin(); i != tests.end(); i++) {
+ i->start();
+ }
+ if (opts.rate && !opts.timeLimit) {
+ while (true) {
+ qpid::sys::usleep(opts.reportFrequency * 1000);
+ //print latency report:
+ for (boost::ptr_vector<Test>::iterator i = tests.begin(); i != tests.end(); i++) {
+ i->report();
+ }
+ }
+ } else {
+ for (boost::ptr_vector<Test>::iterator i = tests.begin(); i != tests.end(); i++) {
+ i->join();
+ }
+ }
+
+ return 0;
+ } catch(const std::exception& e) {
+ std::cout << e.what() << std::endl;
+ }
+ return 1;
+}
diff --git a/qpid/cpp/src/tests/qpid-perftest.cpp b/qpid/cpp/src/tests/qpid-perftest.cpp
new file mode 100644
index 0000000000..7b9738772c
--- /dev/null
+++ b/qpid/cpp/src/tests/qpid-perftest.cpp
@@ -0,0 +1,760 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "TestOptions.h"
+#include "qpid/OptionsTemplates.h"
+#include "qpid/client/AsyncSession.h"
+#include "qpid/client/SubscriptionManager.h"
+#include "qpid/client/Connection.h"
+#include "qpid/client/Completion.h"
+#include "qpid/client/Message.h"
+#include "qpid/framing/FieldTable.h"
+#include "qpid/sys/Time.h"
+#include "qpid/sys/Thread.h"
+
+#include <boost/lexical_cast.hpp>
+#include <boost/bind.hpp>
+#include <boost/function.hpp>
+#include <boost/ptr_container/ptr_vector.hpp>
+
+#include <iostream>
+#include <sstream>
+#include <numeric>
+#include <algorithm>
+#include <math.h>
+
+
+using namespace std;
+using namespace qpid;
+using namespace client;
+using namespace sys;
+using boost::lexical_cast;
+using boost::bind;
+
+namespace qpid {
+namespace tests {
+
+enum Mode { SHARED, FANOUT, TOPIC };
+const char* modeNames[] = { "shared", "fanout", "topic" };
+
+// istream/ostream ops so Options can read/display Mode.
+istream& operator>>(istream& in, Mode& mode) {
+ string s;
+ in >> s;
+ int i = find(modeNames, modeNames+3, s) - modeNames;
+ if (i >= 3) throw Exception("Invalid mode: "+s);
+ mode = Mode(i);
+ return in;
+}
+
+ostream& operator<<(ostream& out, Mode mode) {
+ return out << modeNames[mode];
+}
+
+struct Opts : public TestOptions {
+
+ // Actions
+ bool setup, control, publish, subscribe;
+
+ // Queue policy
+ uint32_t queueMaxCount;
+ uint64_t queueMaxSize;
+ std::string baseName;
+ bool queueDurable;
+
+ // Publisher
+ size_t pubs;
+ size_t count ;
+ size_t size;
+ size_t headers;
+ bool confirm;
+ bool durable;
+ bool uniqueData;
+ bool syncPub;
+
+ // Subscriber
+ size_t subs;
+ size_t ack;
+
+ // General
+ size_t qt;
+ bool singleConnect;
+ size_t iterations;
+ Mode mode;
+ bool summary;
+ uint32_t intervalSub;
+ uint32_t intervalPub;
+ size_t tx;
+ size_t txPub;
+ size_t txSub;
+ bool commitAsync;
+
+ static const std::string helpText;
+
+ Opts() :
+ TestOptions(helpText),
+ setup(false), control(false), publish(false), subscribe(false), baseName("qpid-perftest"),
+ pubs(1), count(500000), size(1024), headers(0), confirm(true), durable(false), uniqueData(false), syncPub(false),
+ subs(1), ack(0),
+ qt(1),singleConnect(false), iterations(1), mode(SHARED), summary(false),
+ intervalSub(0), intervalPub(0), tx(0), txPub(0), txSub(0), commitAsync(false)
+ {
+ addOptions()
+ ("setup", optValue(setup), "Create shared queues.")
+ ("control", optValue(control), "Run test, print report.")
+ ("publish", optValue(publish), "Publish messages.")
+ ("subscribe", optValue(subscribe), "Subscribe for messages.")
+
+ ("mode", optValue(mode, "shared|fanout|topic"), "Test mode."
+ "\nshared: --qt queues, --npubs publishers and --nsubs subscribers per queue.\n"
+ "\nfanout: --npubs publishers, --nsubs subscribers, fanout exchange."
+ "\ntopic: --qt topics, --npubs publishers and --nsubs subscribers per topic.\n")
+
+ ("npubs", optValue(pubs, "N"), "Create N publishers.")
+ ("count", optValue(count, "N"), "Each publisher sends N messages.")
+ ("size", optValue(size, "BYTES"), "Size of messages in bytes.")
+ ("headers", optValue(headers, "N"), "Number of headers to add to each message.")
+ ("pub-confirm", optValue(confirm, "yes|no"), "Publisher use confirm-mode.")
+ ("durable", optValue(durable, "yes|no"), "Publish messages as durable.")
+ ("unique-data", optValue(uniqueData, "yes|no"), "Make data for each message unique.")
+ ("sync-publish", optValue(syncPub, "yes|no"), "Wait for confirmation of each message before sending the next one.")
+
+ ("nsubs", optValue(subs, "N"), "Create N subscribers.")
+ ("sub-ack", optValue(ack, "N"), "N>0: Subscriber acks batches of N.\n"
+ "N==0: Subscriber uses unconfirmed mode")
+
+ ("qt", optValue(qt, "N"), "Create N queues or topics.")
+ ("single-connection", optValue(singleConnect, "yes|no"), "Use one connection for multiple sessions.")
+
+ ("iterations", optValue(iterations, "N"), "Desired number of iterations of the test.")
+ ("summary,s", optValue(summary), "Summary output: pubs/sec subs/sec transfers/sec Mbytes/sec")
+
+ ("queue-max-count", optValue(queueMaxCount, "N"), "queue policy: count to trigger 'flow to disk'")
+ ("queue-max-size", optValue(queueMaxSize, "N"), "queue policy: accumulated size to trigger 'flow to disk'")
+ ("base-name", optValue(baseName, "NAME"), "base name used for queues or topics")
+ ("queue-durable", optValue(queueDurable, "N"), "Make queue durable (implied if durable set)")
+
+ ("interval_sub", optValue(intervalSub, "ms"), ">=0 delay between msg consume")
+ ("interval_pub", optValue(intervalPub, "ms"), ">=0 delay between msg publish")
+
+ ("tx", optValue(tx, "N"), "if non-zero, the transaction batch size for publishing and consuming")
+ ("pub-tx", optValue(txPub, "N"), "if non-zero, the transaction batch size for publishing")
+ ("async-commit", optValue(commitAsync, "yes|no"), "Don't wait for completion of commit")
+ ("sub-tx", optValue(txSub, "N"), "if non-zero, the transaction batch size for consuming");
+ }
+
+ // Computed values
+ size_t totalPubs;
+ size_t totalSubs;
+ size_t transfers;
+ size_t subQuota;
+
+ void parse(int argc, char** argv) {
+ TestOptions::parse(argc, argv);
+ switch (mode) {
+ case SHARED:
+ if (count % subs) {
+ count += subs - (count % subs);
+ cout << "WARNING: Adjusted --count to " << count
+ << " the next multiple of --nsubs" << endl;
+ }
+ totalPubs = pubs*qt;
+ totalSubs = subs*qt;
+ subQuota = (pubs*count)/subs;
+ break;
+ case FANOUT:
+ if (qt != 1) cerr << "WARNING: Fanout mode, ignoring --qt="
+ << qt << endl;
+ qt=1;
+ totalPubs = pubs;
+ totalSubs = subs;
+ subQuota = totalPubs*count;
+ break;
+ case TOPIC:
+ totalPubs = pubs*qt;
+ totalSubs = subs*qt;
+ subQuota = pubs*count;
+ break;
+ }
+ transfers=(totalPubs*count) + (totalSubs*subQuota);
+ if (tx) {
+ if (txPub) {
+ cerr << "WARNING: Using overriden tx value for publishers: " << txPub << std::endl;
+ } else {
+ txPub = tx;
+ }
+ if (txSub) {
+ cerr << "WARNING: Using overriden tx value for subscribers: " << txSub << std::endl;
+ } else {
+ txSub = tx;
+ }
+ }
+ }
+};
+
+const std::string Opts::helpText=
+"There are two ways to use qpid-perftest: single process or multi-process.\n\n"
+"If none of the --setup, --publish, --subscribe or --control options\n"
+"are given qpid-perftest will run a single-process test.\n"
+"For a multi-process test first run:\n"
+" qpid-perftest --setup <other options>\n"
+"and wait for it to complete. The remaining process should run concurrently::\n"
+"Run --npubs times: qpid-perftest --publish <other options>\n"
+"Run --nsubs times: qpid-perftest --subscribe <other options>\n"
+"Run once: qpid-perftest --control <other options>\n"
+"Note the <other options> must be identical for all processes.\n";
+
+Opts opts;
+Connection globalConnection;
+
+std::string fqn(const std::string& name)
+{
+ ostringstream fqn;
+ fqn << opts.baseName << "_" << name;
+ return fqn.str();
+}
+
+struct Client : public Runnable {
+ Connection* connection;
+ Connection localConnection;
+ AsyncSession session;
+ Thread thread;
+
+ Client() {
+ if (opts.singleConnect){
+ connection = &globalConnection;
+ if (!globalConnection.isOpen()) opts.open(globalConnection);
+ }else{
+ connection = &localConnection;
+ opts.open(localConnection);
+ }
+ session = connection->newSession();
+ }
+
+ ~Client() {
+ try {
+ if (connection->isOpen()) {
+ session.close();
+ connection->close();
+ }
+ } catch (const std::exception& e) {
+ std::cerr << "Error in shutdown: " << e.what() << std::endl;
+ }
+ }
+};
+
+struct Setup : public Client {
+
+ void queueInit(string name, bool durable=false, const framing::FieldTable& settings=framing::FieldTable()) {
+ session.queueDeclare(arg::queue=name, arg::durable=durable, arg::arguments=settings);
+ session.queuePurge(arg::queue=name);
+ session.sync();
+ }
+
+ void run() {
+ queueInit(fqn("pub_start"));
+ queueInit(fqn("pub_done"));
+ queueInit(fqn("sub_ready"));
+ queueInit(fqn("sub_done"));
+ if (opts.iterations > 1) queueInit(fqn("sub_iteration"));
+ if (opts.mode==SHARED) {
+ framing::FieldTable settings;//queue policy settings
+ settings.setInt("qpid.max_count", opts.queueMaxCount);
+ settings.setInt("qpid.max_size", opts.queueMaxSize);
+ for (size_t i = 0; i < opts.qt; ++i) {
+ ostringstream qname;
+ qname << opts.baseName << i;
+ queueInit(qname.str(), opts.durable || opts.queueDurable, settings);
+ }
+ }
+ }
+};
+
+void expect(string actual, string expect) {
+ if (expect != actual)
+ throw Exception("Expecting "+expect+" but received "+actual);
+
+}
+
+double secs(Duration d) { return double(d)/TIME_SEC; }
+double secs(AbsTime start, AbsTime finish) {
+ return secs(Duration(start,finish));
+}
+
+
+// Collect rates & print stats.
+class Stats {
+ vector<double> values;
+ double sum;
+
+ public:
+ Stats() : sum(0) {}
+
+ // Functor to collect rates.
+ void operator()(const string& data) {
+ try {
+ double d=lexical_cast<double>(data);
+ values.push_back(d);
+ sum += d;
+ } catch (const std::exception&) {
+ throw Exception("Bad report: "+data);
+ }
+ }
+
+ double mean() const {
+ return sum/values.size();
+ }
+
+ double stdev() const {
+ if (values.size() <= 1) return 0;
+ double avg = mean();
+ double ssq = 0;
+ for (vector<double>::const_iterator i = values.begin();
+ i != values.end(); ++i) {
+ double x=*i;
+ x -= avg;
+ ssq += x*x;
+ }
+ return sqrt(ssq/(values.size()-1));
+ }
+
+ ostream& print(ostream& out) {
+ ostream_iterator<double> o(out, "\n");
+ copy(values.begin(), values.end(), o);
+ out << "Average: " << mean();
+ if (values.size() > 1)
+ out << " (std.dev. " << stdev() << ")";
+ return out << endl;
+ }
+};
+
+
+// Manage control queues, collect and print reports.
+struct Controller : public Client {
+
+ SubscriptionManager subs;
+
+ Controller() : subs(session) {}
+
+ /** Process messages from queue by applying a functor. */
+ void process(size_t n, string queue,
+ boost::function<void (const string&)> msgFn)
+ {
+ if (!opts.summary)
+ cout << "Processing " << n << " messages from "
+ << queue << " " << flush;
+ LocalQueue lq;
+ subs.setFlowControl(n, SubscriptionManager::UNLIMITED, false);
+ subs.subscribe(lq, queue);
+ for (size_t i = 0; i < n; ++i) {
+ if (!opts.summary) cout << "." << flush;
+ msgFn(lq.pop().getData());
+ }
+ if (!opts.summary) cout << " done." << endl;
+ }
+
+ void process(size_t n, LocalQueue lq, string queue,
+ boost::function<void (const string&)> msgFn)
+ {
+ session.messageFlow(queue, 0, n);
+ if (!opts.summary)
+ cout << "Processing " << n << " messages from "
+ << queue << " " << flush;
+ for (size_t i = 0; i < n; ++i) {
+ if (!opts.summary) cout << "." << flush;
+ msgFn(lq.pop().getData());
+ }
+ if (!opts.summary) cout << " done." << endl;
+ }
+
+ void send(size_t n, string queue, string data) {
+ if (!opts.summary)
+ cout << "Sending " << data << " " << n << " times to " << queue
+ << endl;
+ Message msg(data, queue);
+ for (size_t i = 0; i < n; ++i)
+ session.messageTransfer(arg::content=msg, arg::acceptMode=1);
+ }
+
+ void run() { // Controller
+ try {
+ // Wait for subscribers to be ready.
+ process(opts.totalSubs, fqn("sub_ready"), boost::bind(expect, _1, "ready"));
+
+ LocalQueue pubDone;
+ LocalQueue subDone;
+ subs.setFlowControl(0, SubscriptionManager::UNLIMITED, false);
+ subs.subscribe(pubDone, fqn("pub_done"));
+ subs.subscribe(subDone, fqn("sub_done"));
+
+ double txrateTotal(0);
+ double mbytesTotal(0);
+ double pubRateTotal(0);
+ double subRateTotal(0);
+
+ for (size_t j = 0; j < opts.iterations; ++j) {
+ AbsTime start=now();
+ send(opts.totalPubs, fqn("pub_start"), "start"); // Start publishers
+ if (j) {
+ send(opts.totalSubs, fqn("sub_iteration"), "next"); // Start subscribers on next iteration
+ }
+
+ Stats pubRates;
+ Stats subRates;
+
+ process(opts.totalPubs, pubDone, fqn("pub_done"), boost::ref(pubRates));
+ process(opts.totalSubs, subDone, fqn("sub_done"), boost::ref(subRates));
+
+ AbsTime end=now();
+ double time=secs(start, end);
+ if (time <= 0.0) {
+ throw Exception("ERROR: Test completed in zero seconds. Try again with a larger message count.");
+ }
+ double txrate=opts.transfers/time;
+ double mbytes=(txrate*opts.size)/(1024*1024);
+
+ if (!opts.summary) {
+ cout << endl << "Total " << opts.transfers << " transfers of "
+ << opts.size << " bytes in "
+ << time << " seconds." << endl;
+ cout << endl << "Publish transfers/sec: " << endl;
+ pubRates.print(cout);
+ cout << endl << "Subscribe transfers/sec: " << endl;
+ subRates.print(cout);
+ cout << endl
+ << "Total transfers/sec: " << txrate << endl
+ << "Total Mbytes/sec: " << mbytes << endl;
+ }
+ else {
+ cout << pubRates.mean() << "\t"
+ << subRates.mean() << "\t"
+ << txrate << "\t"
+ << mbytes << endl;
+ }
+
+ txrateTotal += txrate;
+ mbytesTotal += mbytes;
+ pubRateTotal += pubRates.mean();
+ subRateTotal += subRates.mean();
+ }
+ if (opts.iterations > 1) {
+ cout << "Averages: "<< endl
+ << (pubRateTotal / opts.iterations) << "\t"
+ << (subRateTotal / opts.iterations) << "\t"
+ << (txrateTotal / opts.iterations) << "\t"
+ << (mbytesTotal / opts.iterations) << endl;
+ }
+ }
+ catch (const std::exception& e) {
+ cout << "Controller exception: " << e.what() << endl;
+ }
+ }
+};
+
+
+struct PublishThread : public Client {
+ string destination;
+ string routingKey;
+
+ PublishThread() {};
+
+ PublishThread(string key, string dest=string()) {
+ destination=dest;
+ routingKey=key;
+ }
+
+ void run() { // Publisher
+ try {
+ string data;
+ size_t offset(0);
+ if (opts.uniqueData) {
+ offset = 5;
+ data += "data:";//marker (requested for latency testing tool scripts)
+ data += string(sizeof(size_t), 'X');//space for seq no
+ data += session.getId().str();
+ if (opts.size > data.size()) {
+ data += string(opts.size - data.size(), 'X');
+ } else if(opts.size < data.size()) {
+ cout << "WARNING: Increased --size to " << data.size()
+ << " to honour --unique-data" << endl;
+ }
+ } else {
+ size_t msgSize=max(opts.size, sizeof(size_t));
+ data = string(msgSize, 'X');
+ }
+
+ Message msg(data, routingKey);
+ if (opts.durable)
+ msg.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT);
+ if (opts.headers) {
+ for (size_t i = 0; i < opts.headers; ++i) {
+ std::stringstream h;
+ h << "hdr" << i;
+ msg.getMessageProperties().getApplicationHeaders().setString(h.str(), h.str());
+ }
+ }
+
+ if (opts.txPub){
+ session.txSelect();
+ }
+ SubscriptionManager subs(session);
+ LocalQueue lq;
+ subs.setFlowControl(0, SubscriptionManager::UNLIMITED, false);
+ Subscription cs = subs.subscribe(lq, fqn("pub_start"));
+
+ for (size_t j = 0; j < opts.iterations; ++j) {
+ cs.grantMessageCredit(1);
+ expect(lq.pop().getData(), "start");
+ AbsTime start=now();
+ for (size_t i=0; i<opts.count; i++) {
+ // Stamp the iteration into the message data, avoid
+ // any heap allocation.
+ const_cast<std::string&>(msg.getData()).replace(offset, sizeof(size_t),
+ reinterpret_cast<const char*>(&i), sizeof(size_t));
+ if (opts.syncPub) {
+ sync(session).messageTransfer(
+ arg::destination=destination,
+ arg::content=msg,
+ arg::acceptMode=1);
+ } else {
+ session.messageTransfer(
+ arg::destination=destination,
+ arg::content=msg,
+ arg::acceptMode=1);
+ }
+ if (opts.txPub && ((i+1) % opts.txPub == 0)){
+ if (opts.commitAsync){
+ session.txCommit();
+ } else {
+ sync(session).txCommit();
+ }
+ }
+ if (opts.intervalPub)
+ qpid::sys::usleep(opts.intervalPub*1000);
+ }
+ if (opts.confirm) session.sync();
+ AbsTime end=now();
+ double time=secs(start,end);
+ if (time <= 0.0) {
+ throw Exception("ERROR: Test completed in zero seconds. Try again with a larger message count.");
+ }
+
+ // Send result to controller.
+ Message report(lexical_cast<string>(opts.count/time), fqn("pub_done"));
+ session.messageTransfer(arg::content=report, arg::acceptMode=1);
+ if (opts.txPub){
+ sync(session).txCommit();
+ }
+ }
+ session.close();
+ }
+ catch (const std::exception& e) {
+ cout << "PublishThread exception: " << e.what() << endl;
+ }
+ }
+};
+
+struct SubscribeThread : public Client {
+
+ string queue;
+
+ SubscribeThread() {}
+
+ SubscribeThread(string q) { queue = q; }
+
+ SubscribeThread(string key, string ex) {
+ queue=session.getId().str(); // Unique name.
+ session.queueDeclare(arg::queue=queue,
+ arg::exclusive=true,
+ arg::autoDelete=true,
+ arg::durable=opts.durable);
+ session.exchangeBind(arg::queue=queue,
+ arg::exchange=ex,
+ arg::bindingKey=key);
+ }
+
+ void verify(bool cond, const char* test, uint32_t expect, uint32_t actual) {
+ if (!cond) {
+ Message error(
+ QPID_MSG("Sequence error: expected n" << test << expect << " but got " << actual),
+ "sub_done");
+ session.messageTransfer(arg::content=error, arg::acceptMode=1);
+ throw Exception(error.getData());
+ }
+ }
+
+ void run() { // Subscribe
+ try {
+ if (opts.txSub) sync(session).txSelect();
+ SubscriptionManager subs(session);
+ SubscriptionSettings settings;
+ settings.autoAck = opts.txSub ? opts.txSub : opts.ack;
+ settings.acceptMode = (opts.txSub || opts.ack ? ACCEPT_MODE_EXPLICIT : ACCEPT_MODE_NONE);
+ settings.flowControl = FlowControl::messageCredit(opts.subQuota);
+ LocalQueue lq;
+ Subscription subscription = subs.subscribe(lq, queue, settings);
+ // Notify controller we are ready.
+ session.messageTransfer(arg::content=Message("ready", fqn("sub_ready")), arg::acceptMode=1);
+ if (opts.txSub) {
+ if (opts.commitAsync) session.txCommit();
+ else sync(session).txCommit();
+ }
+
+ LocalQueue iterationControl;
+ if (opts.iterations > 1) {
+ subs.subscribe(iterationControl, fqn("sub_iteration"), SubscriptionSettings(FlowControl::messageCredit(0)));
+ }
+
+ for (size_t j = 0; j < opts.iterations; ++j) {
+ if (j > 0) {
+ //need to wait here until all subs are done
+ session.messageFlow(fqn("sub_iteration"), 0, 1);
+ iterationControl.pop();
+
+ //need to allocate some more credit for subscription
+ session.messageFlow(queue, 0, opts.subQuota);
+ }
+ Message msg;
+ AbsTime start=now();
+ size_t expect=0;
+ for (size_t i = 0; i < opts.subQuota; ++i) {
+ msg=lq.pop();
+ if (opts.txSub && ((i+1) % opts.txSub == 0)) {
+ if (opts.commitAsync) session.txCommit();
+ else sync(session).txCommit();
+ }
+ if (opts.intervalSub)
+ qpid::sys::usleep(opts.intervalSub*1000);
+ // TODO aconway 2007-11-23: check message order for.
+ // multiple publishers. Need an array of counters,
+ // one per publisher and a publisher ID in the
+ // message. Careful not to introduce a lot of overhead
+ // here, e.g. no std::map, std::string etc.
+ //
+ // For now verify order only for a single publisher.
+ size_t offset = opts.uniqueData ? 5 /*marker is 'data:'*/ : 0;
+ size_t n;
+ memcpy (&n, reinterpret_cast<const char*>(msg.getData().data() + offset),
+ sizeof(n));
+ if (opts.pubs == 1) {
+ if (opts.subs == 1 || opts.mode == FANOUT) verify(n==expect, "==", expect, n);
+ else verify(n>=expect, ">=", expect, n);
+ expect = n+1;
+ }
+ }
+ if (opts.txSub || opts.ack)
+ subscription.accept(subscription.getUnaccepted());
+ if (opts.txSub) {
+ if (opts.commitAsync) session.txCommit();
+ else sync(session).txCommit();
+ }
+ AbsTime end=now();
+
+ // Report to publisher.
+ Message result(lexical_cast<string>(opts.subQuota/secs(start,end)),
+ fqn("sub_done"));
+ session.messageTransfer(arg::content=result, arg::acceptMode=1);
+ if (opts.txSub) sync(session).txCommit();
+ }
+ session.close();
+ }
+ catch (const std::exception& e) {
+ cout << "SubscribeThread exception: " << e.what() << endl;
+ }
+ }
+};
+
+}
+
+template po::value_semantic* create_value(tests::Mode& val, const std::string& arg);
+
+} // namespace qpid::tests
+
+using namespace qpid::tests;
+
+int main(int argc, char** argv) {
+ int exitCode = 0;
+ boost::ptr_vector<Client> subs(opts.subs);
+ boost::ptr_vector<Client> pubs(opts.pubs);
+
+ try {
+ opts.parse(argc, argv);
+
+ string exchange;
+ switch (opts.mode) {
+ case FANOUT: exchange="amq.fanout"; break;
+ case TOPIC: exchange="amq.topic"; break;
+ case SHARED: break;
+ }
+
+ bool singleProcess=
+ (!opts.setup && !opts.control && !opts.publish && !opts.subscribe);
+ if (singleProcess)
+ opts.setup = opts.control = opts.publish = opts.subscribe = true;
+
+ if (opts.setup) Setup().run(); // Set up queues
+
+ // Start pubs/subs for each queue/topic.
+ for (size_t i = 0; i < opts.qt; ++i) {
+ ostringstream key;
+ key << opts.baseName << i; // Queue or topic name.
+ if (opts.publish) {
+ size_t n = singleProcess ? opts.pubs : 1;
+ for (size_t j = 0; j < n; ++j) {
+ pubs.push_back(new PublishThread(key.str(), exchange));
+ pubs.back().thread=Thread(pubs.back());
+ }
+ }
+ if (opts.subscribe) {
+ size_t n = singleProcess ? opts.subs : 1;
+ for (size_t j = 0; j < n; ++j) {
+ if (opts.mode==SHARED)
+ subs.push_back(new SubscribeThread(key.str()));
+ else
+ subs.push_back(new SubscribeThread(key.str(),exchange));
+ subs.back().thread=Thread(subs.back());
+ }
+ }
+ }
+
+ if (opts.control) Controller().run();
+ }
+ catch (const std::exception& e) {
+ cout << endl << e.what() << endl;
+ exitCode = 1;
+ }
+
+ // Wait for started threads.
+ if (opts.publish) {
+ for (boost::ptr_vector<Client>::iterator i=pubs.begin();
+ i != pubs.end();
+ ++i)
+ i->thread.join();
+ }
+
+ if (opts.subscribe) {
+ for (boost::ptr_vector<Client>::iterator i=subs.begin();
+ i != subs.end();
+ ++i)
+ i->thread.join();
+ }
+ return exitCode;
+}
diff --git a/qpid/cpp/src/tests/qpid-ping.cpp b/qpid/cpp/src/tests/qpid-ping.cpp
new file mode 100644
index 0000000000..40e6a0f671
--- /dev/null
+++ b/qpid/cpp/src/tests/qpid-ping.cpp
@@ -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.
+ *
+
+ */
+
+#include <qpid/messaging/Address.h>
+#include <qpid/messaging/Connection.h>
+#include "qpid/messaging/Duration.h"
+#include <qpid/messaging/Message.h>
+#include <qpid/messaging/Sender.h>
+#include <qpid/messaging/Receiver.h>
+#include <qpid/messaging/Session.h>
+#include <qpid/Msg.h>
+#include <qpid/Options.h>
+#include <qpid/types/Uuid.h>
+#include <string>
+#include <iostream>
+
+using namespace std;
+using namespace qpid::messaging;
+using qpid::types::Uuid;
+
+namespace {
+
+struct PingOptions : public qpid::Options {
+ string url;
+ string address;
+ string message;
+ string connectionOptions;
+ double timeout; // Timeout in seconds.
+ bool quiet; // No output
+
+ PingOptions() :
+ url("127.0.0.1"),
+ address(Uuid(true).str()+";{create:always}"),
+ message(Uuid(true).str()),
+ timeout(1),
+ quiet(false)
+ {
+ using qpid::optValue;
+ addOptions()
+ ("broker,b", qpid::optValue(url, "URL"), "url of broker to connect to.")
+ ("address,a", qpid::optValue(address, "ADDRESS"), "address to use.")
+ ("message,m", optValue(message, "MESSAGE"), "message text to send.")
+ ("connection-options", optValue(connectionOptions, "OPTIONS"), "options for the connection.")
+ ("timeout,t", optValue(timeout, "SECONDS"), "Max time to wait.")
+ ("quiet,q", optValue(quiet), "Don't print anything to stderr/stdout.");
+ }
+};
+
+} // namespace
+
+int main(int argc, char** argv) {
+ Connection connection;
+ try {
+ PingOptions opts;
+ opts.parse(argc, argv);
+ connection = Connection(opts.url, opts.connectionOptions);
+ connection.open();
+ if (!opts.quiet) cout << "Opened connection." << endl;
+ Session s = connection.createSession();
+ s.createSender(opts.address).send(Message(opts.message));
+ if (!opts.quiet) cout << "Sent message." << endl;
+ Message m = s.createReceiver(opts.address).
+ fetch(Duration(uint64_t(opts.timeout*1000)));
+ if (m.getContent() != opts.message)
+ throw qpid::Exception(qpid::Msg() << "Expected " << opts.message
+ << " but received " << m.getContent());
+ if (!opts.quiet) cout << "Received message." << endl;
+ connection.close();
+ return 0;
+ } catch (const exception& e) {
+ cerr << "Error: " << e.what() << endl;
+ connection.close();
+ return 1;
+ }
+}
diff --git a/qpid/cpp/src/tests/qpid-receive.cpp b/qpid/cpp/src/tests/qpid-receive.cpp
new file mode 100644
index 0000000000..a71fd11fa7
--- /dev/null
+++ b/qpid/cpp/src/tests/qpid-receive.cpp
@@ -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.
+ *
+ */
+
+#include <qpid/messaging/Address.h>
+#include <qpid/messaging/Connection.h>
+#include <qpid/messaging/Receiver.h>
+#include <qpid/messaging/Sender.h>
+#include <qpid/messaging/Session.h>
+#include <qpid/messaging/Message.h>
+#include <qpid/messaging/FailoverUpdates.h>
+#include <qpid/Options.h>
+#include <qpid/log/Logger.h>
+#include <qpid/log/Options.h>
+#include "qpid/sys/Time.h"
+#include "TestOptions.h"
+#include "Statistics.h"
+
+#include <iostream>
+#include <memory>
+
+using namespace qpid::messaging;
+using namespace qpid::types;
+using namespace std;
+
+namespace qpid {
+namespace tests {
+
+struct Options : public qpid::Options
+{
+ bool help;
+ std::string url;
+ std::string address;
+ std::string connectionOptions;
+ int64_t timeout;
+ bool forever;
+ uint messages;
+ bool ignoreDuplicates;
+ bool verifySequence;
+ bool checkRedelivered;
+ uint capacity;
+ uint ackFrequency;
+ uint tx;
+ uint rollbackFrequency;
+ bool printContent;
+ bool printContentObjectType;
+ bool printHeaders;
+ bool failoverUpdates;
+ qpid::log::Options log;
+ bool reportTotal;
+ uint reportEvery;
+ bool reportHeader;
+ string readyAddress;
+ uint receiveRate;
+ std::string replyto;
+ bool noReplies;
+
+ Options(const std::string& argv0=std::string())
+ : qpid::Options("Options"),
+ help(false),
+ url("127.0.0.1"),
+ timeout(0),
+ forever(false),
+ messages(0),
+ ignoreDuplicates(false),
+ verifySequence(false),
+ checkRedelivered(false),
+ capacity(1000),
+ ackFrequency(100),
+ tx(0),
+ rollbackFrequency(0),
+ printContent(true),
+ printContentObjectType(false),
+ printHeaders(false),
+ failoverUpdates(false),
+ log(argv0),
+ reportTotal(false),
+ reportEvery(0),
+ reportHeader(true),
+ receiveRate(0),
+ noReplies(false)
+ {
+ addOptions()
+ ("broker,b", qpid::optValue(url, "URL"), "url of broker to connect to")
+ ("address,a", qpid::optValue(address, "ADDRESS"), "address to receive from")
+ ("connection-options", qpid::optValue(connectionOptions, "OPTIONS"), "options for the connection")
+ ("timeout", qpid::optValue(timeout, "TIMEOUT"), "timeout in seconds to wait before exiting")
+ ("forever,f", qpid::optValue(forever), "ignore timeout and wait forever")
+ ("messages,m", qpid::optValue(messages, "N"), "Number of messages to receive; 0 means receive indefinitely")
+ ("ignore-duplicates", qpid::optValue(ignoreDuplicates), "Detect and ignore duplicates (by checking 'sn' header)")
+ ("verify-sequence", qpid::optValue(verifySequence), "Verify there are no gaps in the message sequence (by checking 'sn' header)")
+ ("check-redelivered", qpid::optValue(checkRedelivered), "Fails with exception if a duplicate is not marked as redelivered (only relevant when ignore-duplicates is selected)")
+ ("capacity", qpid::optValue(capacity, "N"), "Pre-fetch window (0 implies no pre-fetch)")
+ ("ack-frequency", qpid::optValue(ackFrequency, "N"), "Ack frequency (0 implies none of the messages will get accepted)")
+ ("tx", qpid::optValue(tx, "N"), "batch size for transactions (0 implies transaction are not used)")
+ ("rollback-frequency", qpid::optValue(rollbackFrequency, "N"), "rollback frequency (0 implies no transaction will be rolledback)")
+ ("print-content", qpid::optValue(printContent, "yes|no"), "print out message content")
+ ("print-object-type", qpid::optValue(printContentObjectType, "yes|no"), "print a description of the content's object type if relevant")
+ ("print-headers", qpid::optValue(printHeaders, "yes|no"), "print out message headers")
+ ("failover-updates", qpid::optValue(failoverUpdates), "Listen for membership updates distributed via amq.failover")
+ ("report-total", qpid::optValue(reportTotal), "Report total throughput and latency statistics")
+ ("report-every", qpid::optValue(reportEvery,"N"), "Report throughput and latency statistics every N messages.")
+ ("report-header", qpid::optValue(reportHeader, "yes|no"), "Headers on report.")
+ ("ready-address", qpid::optValue(readyAddress, "ADDRESS"), "send a message to this address when ready to receive")
+ ("receive-rate", qpid::optValue(receiveRate,"N"), "Receive at rate of N messages/second. 0 means receive as fast as possible.")
+ ("reply-to", qpid::optValue(replyto, "REPLY-TO"), "specify reply-to address on response messages")
+ ("ignore-reply-to", qpid::optValue(noReplies), "Do not send replies even if reply-to is set")
+ ("help", qpid::optValue(help), "print this usage statement");
+ add(log);
+ }
+
+ Duration getTimeout()
+ {
+ if (forever) return Duration::FOREVER;
+ else return Duration::SECOND*timeout;
+
+ }
+ bool parse(int argc, char** argv)
+ {
+ try {
+ qpid::Options::parse(argc, argv);
+ if (address.empty()) throw qpid::Exception("Address must be specified!");
+ qpid::log::Logger::instance().configure(log);
+ if (help) {
+ std::cout << *this << std::endl << std::endl
+ << "Drains messages from the specified address" << std::endl;
+ return false;
+ } else {
+ return true;
+ }
+ } catch (const std::exception& e) {
+ std::cerr << *this << std::endl << std::endl << e.what() << std::endl;
+ return false;
+ }
+ }
+};
+
+const string EOS("eos");
+const string SN("sn");
+
+/** Check for duplicate or dropped messages by sequence number */
+class SequenceTracker
+{
+ public:
+ SequenceTracker(const Options& o) : opts(o), lastSn(0) {}
+
+ /** Return true if the message should be procesed, false if it should be ignored. */
+ bool track(Message& message) {
+ if (!(opts.verifySequence || opts.ignoreDuplicates))
+ return true; // Not checking sequence numbers.
+ uint sn = message.getProperties()[SN];
+ bool duplicate = (sn <= lastSn);
+ bool dropped = (sn > lastSn+1);
+ if (opts.verifySequence && dropped)
+ throw Exception(QPID_MSG("Gap in sequence numbers " << lastSn << "-" << sn));
+ bool ignore = duplicate && opts.ignoreDuplicates;
+ if (ignore && opts.checkRedelivered && !message.getRedelivered())
+ throw qpid::Exception("duplicate sequence number received, message not marked as redelivered!");
+ if (!duplicate) lastSn = sn;
+ return !ignore;
+ }
+
+ private:
+ const Options& opts;
+ uint lastSn;
+};
+
+}} // namespace qpid::tests
+
+using namespace qpid::tests;
+
+int main(int argc, char ** argv)
+{
+ Connection connection;
+ try {
+ Options opts;
+ if (opts.parse(argc, argv)) {
+ connection = Connection(opts.url, opts.connectionOptions);
+ connection.open();
+ std::auto_ptr<FailoverUpdates> updates(opts.failoverUpdates ? new FailoverUpdates(connection) : 0);
+ Session session = opts.tx ? connection.createTransactionalSession() : connection.createSession();
+ Receiver receiver = session.createReceiver(opts.address);
+ receiver.setCapacity(std::min(opts.capacity, opts.messages));
+ Message msg;
+ uint count = 0;
+ uint txCount = 0;
+ SequenceTracker sequenceTracker(opts);
+ Duration timeout = opts.getTimeout();
+ bool done = false;
+ Reporter<ThroughputAndLatency> reporter(std::cout, opts.reportEvery, opts.reportHeader);
+ if (!opts.readyAddress.empty()) {
+ session.createSender(opts.readyAddress).send(msg);
+ if (opts.tx)
+ session.commit();
+ }
+ // For receive rate calculation
+ qpid::sys::AbsTime start = qpid::sys::now();
+ int64_t interval = 0;
+ if (opts.receiveRate) interval = qpid::sys::TIME_SEC/opts.receiveRate;
+
+ std::map<std::string,Sender> replyTo;
+
+ while (!done && receiver.fetch(msg, timeout)) {
+ reporter.message(msg);
+ if (sequenceTracker.track(msg)) {
+ if (msg.getContent() == EOS) {
+ done = true;
+ } else {
+ ++count;
+ if (opts.printHeaders) {
+ if (msg.getSubject().size()) std::cout << "Subject: " << msg.getSubject() << std::endl;
+ if (msg.getReplyTo()) std::cout << "ReplyTo: " << msg.getReplyTo() << std::endl;
+ if (msg.getMessageId().size()) std::cout << "MessageId: " << msg.getMessageId() << std::endl;
+ if (msg.getCorrelationId().size()) std::cout << "CorrelationId: " << msg.getCorrelationId() << std::endl;
+ if (msg.getUserId().size()) std::cout << "UserId: " << msg.getUserId() << std::endl;
+ if (msg.getTtl().getMilliseconds()) std::cout << "TTL: " << msg.getTtl().getMilliseconds() << std::endl;
+ if (msg.getPriority()) std::cout << "Priority: " << ((uint) msg.getPriority()) << std::endl;
+ if (msg.getDurable()) std::cout << "Durable: true" << std::endl;
+ if (msg.getRedelivered()) std::cout << "Redelivered: true" << std::endl;
+ std::cout << "Properties: " << msg.getProperties() << std::endl;
+ if (msg.getContentType().size()) std::cout << "ContentType: " << msg.getContentType() << std::endl;
+ std::cout << std::endl;
+ }
+ if (opts.printContent) {
+ if (!msg.getContentObject().isVoid()) {
+ if (opts.printContentObjectType) {
+ std::cout << "[Object: " << getTypeName(msg.getContentObject().getType()) << "]" << std::endl;
+ }
+ std::cout << msg.getContentObject() << std::endl;
+ } else {
+ std::cout << msg.getContent() << std::endl;
+ }
+ }
+ if (opts.messages && count >= opts.messages) done = true;
+ }
+ }
+ if (opts.tx && (count % opts.tx == 0)) {
+ if (opts.rollbackFrequency && (++txCount % opts.rollbackFrequency == 0)) {
+ session.rollback();
+ } else {
+ session.commit();
+ }
+ } else if (opts.ackFrequency && (count % opts.ackFrequency == 0)) {
+ session.acknowledge();
+ }
+ if (msg.getReplyTo() && !opts.noReplies) { // Echo message back to reply-to address.
+ Sender& s = replyTo[msg.getReplyTo().str()];
+ if (s.isNull()) {
+ s = session.createSender(msg.getReplyTo());
+ s.setCapacity(opts.capacity);
+ replyTo[msg.getReplyTo().str()] = s;
+ }
+ msg.setReplyTo(Address(opts.replyto));
+ s.send(msg);
+ }
+ if (opts.receiveRate) {
+ qpid::sys::AbsTime waitTill(start, count*interval);
+ int64_t delay = qpid::sys::Duration(qpid::sys::now(), waitTill);
+ if (delay > 0) qpid::sys::usleep(delay/qpid::sys::TIME_USEC);
+ }
+ }
+ if (opts.reportTotal) reporter.report();
+ if (opts.tx) {
+ if (opts.rollbackFrequency && (++txCount % opts.rollbackFrequency == 0)) {
+ session.rollback();
+ } else {
+ session.commit();
+ }
+ } else if (opts.ackFrequency) {
+ session.acknowledge();
+ }
+ session.close();
+ connection.close();
+ return 0;
+ }
+ return 1;
+ } catch(const std::exception& error) {
+ std::cerr << "qpid-receive: " << error.what() << std::endl;
+ connection.close();
+ return 1;
+ }
+}
diff --git a/qpid/cpp/src/tests/qpid-send.cpp b/qpid/cpp/src/tests/qpid-send.cpp
new file mode 100644
index 0000000000..bc0ad78601
--- /dev/null
+++ b/qpid/cpp/src/tests/qpid-send.cpp
@@ -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.
+ *
+ */
+
+#include <qpid/messaging/Address.h>
+#include <qpid/messaging/Connection.h>
+#include <qpid/messaging/Message.h>
+#include <qpid/messaging/Sender.h>
+#include <qpid/messaging/Receiver.h>
+#include <qpid/messaging/Session.h>
+#include <qpid/messaging/FailoverUpdates.h>
+#include <qpid/sys/Time.h>
+#include <qpid/sys/Monitor.h>
+#include <qpid/sys/SystemInfo.h>
+#include "TestOptions.h"
+#include "Statistics.h"
+
+#include <fstream>
+#include <iostream>
+#include <memory>
+
+using std::string;
+using std::ios_base;
+
+using qpid::messaging::Address;
+using qpid::messaging::Connection;
+using qpid::messaging::Duration;
+using qpid::messaging::FailoverUpdates;
+using qpid::messaging::Message;
+using qpid::messaging::Receiver;
+using qpid::messaging::Session;
+using qpid::messaging::Sender;
+using qpid::types::Exception;
+using qpid::types::Uuid;
+using qpid::types::Variant;
+
+namespace qpid {
+namespace tests {
+
+typedef std::vector<std::string> string_vector;
+
+struct Options : public qpid::Options
+{
+ bool help;
+ std::string url;
+ std::string connectionOptions;
+ std::string address;
+ uint messages;
+ std::string id;
+ std::string replyto;
+ uint sendEos;
+ bool durable;
+ uint ttl;
+ uint priority;
+ std::string userid;
+ std::string correlationid;
+ string_vector properties;
+ string_vector entries;
+ std::string contentString;
+ uint contentSize;
+ bool contentStdin;
+ uint tx;
+ uint rollbackFrequency;
+ uint capacity;
+ bool failoverUpdates;
+ qpid::log::Options log;
+ bool reportTotal;
+ uint reportEvery;
+ bool reportHeader;
+ uint sendRate;
+ bool sequence;
+ bool timestamp;
+ std::string groupKey;
+ std::string groupPrefix;
+ uint groupSize;
+ bool groupRandSize;
+ uint groupInterleave;
+
+ Options(const std::string& argv0=std::string())
+ : qpid::Options("Options"),
+ help(false),
+ url("127.0.0.1"),
+ messages(1),
+ sendEos(0),
+ durable(false),
+ ttl(0),
+ priority(0),
+ contentString(),
+ contentSize(0),
+ contentStdin(false),
+ tx(0),
+ rollbackFrequency(0),
+ capacity(1000),
+ failoverUpdates(false),
+ log(argv0),
+ reportTotal(false),
+ reportEvery(0),
+ reportHeader(true),
+ sendRate(0),
+ sequence(true),
+ timestamp(true),
+ groupPrefix("GROUP-"),
+ groupSize(10),
+ groupRandSize(false),
+ groupInterleave(1)
+ {
+ addOptions()
+ ("broker,b", qpid::optValue(url, "URL"), "url of broker to connect to")
+ ("address,a", qpid::optValue(address, "ADDRESS"), "address to send to")
+ ("connection-options", qpid::optValue(connectionOptions, "OPTIONS"), "options for the connection")
+ ("messages,m", qpid::optValue(messages, "N"), "stop after N messages have been sent, 0 means no limit")
+ ("id,i", qpid::optValue(id, "ID"), "use the supplied id instead of generating one")
+ ("reply-to", qpid::optValue(replyto, "REPLY-TO"), "specify reply-to address")
+ ("send-eos", qpid::optValue(sendEos, "N"), "Send N EOS messages to mark end of input")
+ ("durable", qpid::optValue(durable, "yes|no"), "Mark messages as durable.")
+ ("ttl", qpid::optValue(ttl, "msecs"), "Time-to-live for messages, in milliseconds")
+ ("priority", qpid::optValue(priority, "PRIORITY"), "Priority for messages (higher value implies higher priority)")
+ ("property,P", qpid::optValue(properties, "NAME=VALUE"), "specify message property")
+ ("correlation-id", qpid::optValue(correlationid, "ID"), "correlation-id for message")
+ ("user-id", qpid::optValue(userid, "USERID"), "userid for message")
+ ("content-string", qpid::optValue(contentString, "CONTENT"), "use CONTENT as message content")
+ ("content-size", qpid::optValue(contentSize, "N"), "create an N-byte message content")
+ ("content-map,M", qpid::optValue(entries, "NAME=VALUE"), "specify entry for map content")
+ ("content-stdin", qpid::optValue(contentStdin), "read message content from stdin, one line per message")
+ ("capacity", qpid::optValue(capacity, "N"), "size of the senders outgoing message queue")
+ ("tx", qpid::optValue(tx, "N"), "batch size for transactions (0 implies transaction are not used)")
+ ("rollback-frequency", qpid::optValue(rollbackFrequency, "N"), "rollback frequency (0 implies no transaction will be rolledback)")
+ ("failover-updates", qpid::optValue(failoverUpdates), "Listen for membership updates distributed via amq.failover")
+ ("report-total", qpid::optValue(reportTotal), "Report total throughput statistics")
+ ("report-every", qpid::optValue(reportEvery,"N"), "Report throughput statistics every N messages")
+ ("report-header", qpid::optValue(reportHeader, "yes|no"), "Headers on report.")
+ ("send-rate", qpid::optValue(sendRate,"N"), "Send at rate of N messages/second. 0 means send as fast as possible.")
+ ("sequence", qpid::optValue(sequence, "yes|no"), "Add a sequence number messages property (required for duplicate/lost message detection)")
+ ("timestamp", qpid::optValue(timestamp, "yes|no"), "Add a time stamp messages property (required for latency measurement)")
+ ("group-key", qpid::optValue(groupKey, "KEY"), "Generate groups of messages using message header 'KEY' to hold the group identifier")
+ ("group-prefix", qpid::optValue(groupPrefix, "STRING"), "Generate group identifers with 'STRING' prefix (if group-key specified)")
+ ("group-size", qpid::optValue(groupSize, "N"), "Number of messages per a group (if group-key specified)")
+ ("group-randomize-size", qpid::optValue(groupRandSize), "Randomize the number of messages per group to [1...group-size] (if group-key specified)")
+ ("group-interleave", qpid::optValue(groupInterleave, "N"), "Simultaineously interleave messages from N different groups (if group-key specified)")
+ ("help", qpid::optValue(help), "print this usage statement");
+ add(log);
+ }
+
+ bool parse(int argc, char** argv)
+ {
+ try {
+ qpid::Options::parse(argc, argv);
+ if (address.empty()) throw qpid::Exception("Address must be specified!");
+ qpid::log::Logger::instance().configure(log);
+ if (help) {
+ std::cout << *this << std::endl << std::endl
+ << "Sends messages to the specified address" << std::endl;
+ return false;
+ } else {
+ return true;
+ }
+ } catch (const std::exception& e) {
+ std::cerr << *this << std::endl << std::endl << e.what() << std::endl;
+ return false;
+ }
+ }
+
+ static bool nameval(const std::string& in, std::string& name, std::string& value)
+ {
+ std::string::size_type i = in.find("=");
+ if (i == std::string::npos) {
+ name = in;
+ return false;
+ } else {
+ name = in.substr(0, i);
+ if (i+1 < in.size()) {
+ value = in.substr(i+1);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ static void setProperty(Message& message, const std::string& property)
+ {
+ std::string name;
+ std::string value;
+ if (nameval(property, name, value)) {
+ message.getProperties()[name].parse(value);
+ } else {
+ message.getProperties()[name] = Variant();
+ }
+ }
+
+ void setProperties(Message& message) const
+ {
+ for (string_vector::const_iterator i = properties.begin(); i != properties.end(); ++i) {
+ setProperty(message, *i);
+ }
+ }
+
+ void setEntries(Variant::Map& content) const
+ {
+ for (string_vector::const_iterator i = entries.begin(); i != entries.end(); ++i) {
+ std::string name;
+ std::string value;
+ if (nameval(*i, name, value)) {
+ content[name] = value;
+ } else {
+ content[name] = Variant();
+ }
+ }
+ }
+};
+
+const string EOS("eos");
+const string SN("sn");
+const string TS("ts");
+
+class ContentGenerator {
+ public:
+ virtual ~ContentGenerator() {}
+ virtual bool setContent(Message& msg) = 0;
+ void setContentObject(Message& msg, const std::string& content, const std::string& encoding=std::string("utf8"))
+ {
+ Variant& obj = msg.getContentObject();
+ obj = content;
+ obj.setEncoding(encoding);
+ }
+};
+
+
+class GetlineContentGenerator : public ContentGenerator {
+ public:
+ virtual bool setContent(Message& msg) {
+ string content;
+ bool got = !!getline(std::cin, content);
+ if (got) {
+ setContentObject(msg, content);
+ }
+ return got;
+ }
+};
+
+class FixedContentGenerator : public ContentGenerator {
+ public:
+ FixedContentGenerator(const string& s) : content(s) {}
+ virtual bool setContent(Message& msg) {
+ setContentObject(msg, content);
+ return true;
+ }
+ private:
+ std::string content;
+};
+
+class MapContentGenerator : public ContentGenerator {
+ public:
+ MapContentGenerator(const Options& opt) : opts(opt) {}
+ virtual bool setContent(Message& msg) {
+ msg.getContentObject() = qpid::types::Variant::Map();
+ opts.setEntries(msg.getContentObject().asMap());
+ return true;
+ }
+ private:
+ const Options& opts;
+};
+
+// tag each generated message with a group identifer
+//
+class GroupGenerator {
+ public:
+ GroupGenerator(const std::string& key,
+ const std::string& prefix,
+ const uint size,
+ const bool randomize,
+ const uint interleave)
+ : groupKey(key), groupPrefix(prefix), groupSize(size),
+ randomizeSize(randomize), groupSuffix(0)
+ {
+ if (randomize) srand((unsigned int)qpid::sys::SystemInfo::getProcessId());
+
+ for (uint i = 0; i < 1 || i < interleave; ++i) {
+ newGroup();
+ }
+ current = groups.begin();
+ }
+
+ void setGroupInfo(Message &msg)
+ {
+ if (current == groups.end())
+ current = groups.begin();
+ msg.getProperties()[groupKey] = current->id;
+ // std::cout << "SENDING GROUPID=[" << current->id << "]" << std::endl;
+ if (++(current->count) == current->size) {
+ newGroup();
+ groups.erase(current++);
+ } else
+ ++current;
+ }
+
+ private:
+ const std::string& groupKey;
+ const std::string& groupPrefix;
+ const uint groupSize;
+ const bool randomizeSize;
+
+ uint groupSuffix;
+
+ struct GroupState {
+ std::string id;
+ const uint size;
+ uint count;
+ GroupState( const std::string& i, const uint s )
+ : id(i), size(s), count(0) {}
+ };
+ typedef std::list<GroupState> GroupList;
+ GroupList groups;
+ GroupList::iterator current;
+
+ void newGroup() {
+ std::ostringstream groupId(groupPrefix, ios_base::out|ios_base::ate);
+ groupId << groupSuffix++;
+ uint size = (randomizeSize) ? (rand() % groupSize) + 1 : groupSize;
+ // std::cout << "New group: GROUPID=[" << groupId.str() << "] size=" << size << std::endl;
+ GroupState group( groupId.str(), size );
+ groups.push_back( group );
+ }
+};
+
+}} // namespace qpid::tests
+
+using qpid::tests::Options;
+using qpid::tests::Reporter;
+using qpid::tests::Throughput;
+using qpid::tests::ContentGenerator;
+using qpid::tests::GroupGenerator;
+using qpid::tests::GetlineContentGenerator;
+using qpid::tests::MapContentGenerator;
+using qpid::tests::FixedContentGenerator;
+using qpid::tests::SN;
+using qpid::tests::TS;
+using qpid::tests::EOS;
+
+int main(int argc, char ** argv)
+{
+ Connection connection;
+ try {
+ Options opts;
+ if (opts.parse(argc, argv)) {
+ connection = Connection(opts.url, opts.connectionOptions);
+ connection.open();
+ std::auto_ptr<FailoverUpdates> updates(opts.failoverUpdates ? new FailoverUpdates(connection) : 0);
+ Session session = opts.tx ? connection.createTransactionalSession() : connection.createSession();
+ Sender sender = session.createSender(opts.address);
+ if (opts.capacity) sender.setCapacity(opts.capacity);
+ Message msg;
+ msg.setDurable(opts.durable);
+ if (opts.ttl) {
+ msg.setTtl(Duration(opts.ttl));
+ }
+ if (opts.priority) {
+ msg.setPriority(opts.priority);
+ }
+ if (!opts.replyto.empty()) {
+ msg.setReplyTo(Address(opts.replyto));
+ }
+ if (!opts.userid.empty()) msg.setUserId(opts.userid);
+ if (!opts.id.empty()) msg.setMessageId(opts.id);
+ if (!opts.correlationid.empty()) msg.setCorrelationId(opts.correlationid);
+ opts.setProperties(msg);
+ uint sent = 0;
+ uint txCount = 0;
+ Reporter<Throughput> reporter(std::cout, opts.reportEvery, opts.reportHeader);
+
+ std::auto_ptr<ContentGenerator> contentGen;
+ if (opts.contentStdin) {
+ opts.messages = 0; // Don't limit # messages sent.
+ contentGen.reset(new GetlineContentGenerator);
+ }
+ else if (opts.entries.size() > 0)
+ contentGen.reset(new MapContentGenerator(opts));
+ else if (opts.contentSize > 0)
+ contentGen.reset(new FixedContentGenerator(string(opts.contentSize, 'X')));
+ else
+ contentGen.reset(new FixedContentGenerator(opts.contentString));
+
+ std::auto_ptr<GroupGenerator> groupGen;
+ if (!opts.groupKey.empty())
+ groupGen.reset(new GroupGenerator(opts.groupKey,
+ opts.groupPrefix,
+ opts.groupSize,
+ opts.groupRandSize,
+ opts.groupInterleave));
+
+ qpid::sys::AbsTime start = qpid::sys::now();
+ int64_t interval = 0;
+ if (opts.sendRate) interval = qpid::sys::TIME_SEC/opts.sendRate;
+
+ while (contentGen->setContent(msg)) {
+ ++sent;
+ if (opts.sequence)
+ msg.getProperties()[SN] = sent;
+ if (groupGen.get())
+ groupGen->setGroupInfo(msg);
+
+ if (opts.timestamp)
+ msg.getProperties()[TS] = int64_t(
+ qpid::sys::Duration::FromEpoch());
+ sender.send(msg);
+ reporter.message(msg);
+
+ if (opts.tx && (sent % opts.tx == 0)) {
+ if (opts.rollbackFrequency &&
+ (++txCount % opts.rollbackFrequency == 0))
+ session.rollback();
+ else
+ session.commit();
+ }
+ if (opts.messages && sent >= opts.messages) break;
+
+ if (opts.sendRate) {
+ qpid::sys::AbsTime waitTill(start, sent*interval);
+ int64_t delay = qpid::sys::Duration(qpid::sys::now(), waitTill);
+ if (delay > 0) qpid::sys::usleep(delay/qpid::sys::TIME_USEC);
+ }
+ }
+ if (opts.reportTotal) reporter.report();
+ for (uint i = opts.sendEos; i > 0; --i) {
+ if (opts.sequence)
+ msg.getProperties()[SN] = ++sent;
+ msg.setContent(EOS); //TODO: add in ability to send digest or similar
+ sender.send(msg);
+ }
+ if (opts.tx) {
+ if (opts.rollbackFrequency && (++txCount % opts.rollbackFrequency == 0)) {
+ session.rollback();
+ } else {
+ session.commit();
+ }
+ }
+ session.sync();
+ session.close();
+ connection.close();
+ return 0;
+ }
+ return 1;
+ } catch(const std::exception& error) {
+ std::cerr << "qpid-send: " << error.what() << std::endl;
+ connection.close();
+ return 1;
+ }
+}
diff --git a/qpid/cpp/src/tests/qpid-src-rinstall b/qpid/cpp/src/tests/qpid-src-rinstall
new file mode 100755
index 0000000000..c7473e9197
--- /dev/null
+++ b/qpid/cpp/src/tests/qpid-src-rinstall
@@ -0,0 +1,31 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under onemake
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Copy the source tree and run "make install" on each of $HOSTS
+# Must be run in a configured qpid build directory.
+
+absdir() { echo `cd $1 && pwd`; }
+
+test -f config.status || { echo "Not in a configured build directory."; }
+CONFIGURE=`./config.status -V | grep '^configured by' | sed 's/^configured by \([^,]*\),.*$/\1/'`
+CONFIG_OPTIONS=`./config.status -V | grep 'with options' | sed 's/^.*with options "\([^"]*\)".*$/\1/'`
+set -ex
+rsynchosts `absdir $(dirname $CONFIGURE)/..` # Copy cpp srcdir and siblings.
+allhosts -bo rbuild.log "mkdir -p $PWD && cd $PWD && { test -f config.status || $CONFIGURE $CONFIG_OPTIONS; } && make && make -j1 install"
diff --git a/qpid/cpp/src/tests/qpid-stream.cpp b/qpid/cpp/src/tests/qpid-stream.cpp
new file mode 100644
index 0000000000..f02a484750
--- /dev/null
+++ b/qpid/cpp/src/tests/qpid-stream.cpp
@@ -0,0 +1,193 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <qpid/messaging/Connection.h>
+#include <qpid/messaging/Message.h>
+#include <qpid/messaging/Receiver.h>
+#include <qpid/messaging/Sender.h>
+#include <qpid/messaging/Session.h>
+#include <qpid/sys/Runnable.h>
+#include <qpid/sys/Thread.h>
+#include <qpid/sys/Time.h>
+#include <qpid/Options.h>
+#include <iostream>
+#include <string>
+
+using namespace qpid::messaging;
+using namespace qpid::types;
+
+namespace qpid {
+namespace tests {
+
+struct Args : public qpid::Options
+{
+ std::string url;
+ std::string address;
+ uint size;
+ uint rate;
+ bool durable;
+ uint receiverCapacity;
+ uint senderCapacity;
+ uint ackFrequency;
+
+ Args() :
+ url("amqp:tcp:127.0.0.1:5672"),
+ address("test-queue"),
+ size(512),
+ rate(1000),
+ durable(false),
+ receiverCapacity(0),
+ senderCapacity(0),
+ ackFrequency(1)
+ {
+ addOptions()
+ ("url", qpid::optValue(url, "URL"), "Url to connect to.")
+ ("address", qpid::optValue(address, "ADDRESS"), "Address to stream messages through.")
+ ("size", qpid::optValue(size, "bytes"), "Message size in bytes (content only, not headers).")
+ ("rate", qpid::optValue(rate, "msgs/sec"), "Rate at which to stream messages.")
+ ("durable", qpid::optValue(durable, "true|false"), "Mark messages as durable.")
+ ("sender-capacity", qpid::optValue(senderCapacity, "N"), "Credit window (0 implies infinite window)")
+ ("receiver-capacity", qpid::optValue(receiverCapacity, "N"), "Credit window (0 implies infinite window)")
+ ("ack-frequency", qpid::optValue(ackFrequency, "N"),
+ "Ack frequency (0 implies none of the messages will get accepted)");
+ }
+};
+
+Args opts;
+
+const std::string TS = "ts";
+
+uint64_t timestamp(const qpid::sys::AbsTime& time)
+{
+ qpid::sys::Duration t(qpid::sys::EPOCH, time);
+ return t;
+}
+
+struct Client : qpid::sys::Runnable
+{
+ virtual ~Client() {}
+ virtual void doWork(Session&) = 0;
+
+ void run()
+ {
+ Connection connection(opts.url);
+ try {
+ connection.open();
+ Session session = connection.createSession();
+ doWork(session);
+ session.close();
+ connection.close();
+ } catch(const std::exception& error) {
+ std::cout << error.what() << std::endl;
+ connection.close();
+ }
+ }
+
+ qpid::sys::Thread thread;
+
+ void start() { thread = qpid::sys::Thread(this); }
+ void join() { thread.join(); }
+};
+
+struct Publish : Client
+{
+ void doWork(Session& session)
+ {
+ Sender sender = session.createSender(opts.address);
+ if (opts.senderCapacity) sender.setCapacity(opts.senderCapacity);
+ Message msg(std::string(opts.size, 'X'));
+ uint64_t interval = qpid::sys::TIME_SEC / opts.rate;
+ uint64_t sent = 0, missedRate = 0;
+ qpid::sys::AbsTime start = qpid::sys::now();
+ while (true) {
+ qpid::sys::AbsTime sentAt = qpid::sys::now();
+ msg.getProperties()[TS] = timestamp(sentAt);
+ sender.send(msg);
+ ++sent;
+ qpid::sys::AbsTime waitTill(start, sent*interval);
+ qpid::sys::Duration delay(sentAt, waitTill);
+ if (delay < 0) {
+ ++missedRate;
+ } else {
+ qpid::sys::usleep(delay / qpid::sys::TIME_USEC);
+ }
+ }
+ }
+};
+
+struct Consume : Client
+{
+ void doWork(Session& session)
+ {
+ Message msg;
+ uint64_t received = 0;
+ double minLatency = std::numeric_limits<double>::max();
+ double maxLatency = 0;
+ double totalLatency = 0;
+ Receiver receiver = session.createReceiver(opts.address);
+ if (opts.receiverCapacity) receiver.setCapacity(opts.receiverCapacity);
+ while (receiver.fetch(msg)) {
+ ++received;
+ if (opts.ackFrequency && (received % opts.ackFrequency == 0)) {
+ session.acknowledge();
+ }
+ //calculate latency
+ uint64_t receivedAt = timestamp(qpid::sys::now());
+ uint64_t sentAt = msg.getProperties()[TS].asUint64();
+ double latency = ((double) (receivedAt - sentAt)) / qpid::sys::TIME_MSEC;
+
+ //update avg, min & max
+ minLatency = std::min(minLatency, latency);
+ maxLatency = std::max(maxLatency, latency);
+ totalLatency += latency;
+
+ if (received % opts.rate == 0) {
+ std::cout << "count=" << received
+ << ", avg=" << (totalLatency/received)
+ << ", min=" << minLatency
+ << ", max=" << maxLatency << std::endl;
+ }
+ }
+ }
+};
+
+}} // namespace qpid::tests
+
+using namespace qpid::tests;
+
+int main(int argc, char** argv)
+{
+ try {
+ opts.parse(argc, argv);
+ Publish publish;
+ Consume consume;
+ publish.start();
+ consume.start();
+ consume.join();
+ publish.join();
+ return 0;
+ } catch(const std::exception& error) {
+ std::cout << error.what() << std::endl;
+ }
+ return 1;
+}
+
+
diff --git a/qpid/cpp/src/tests/qpid-topic-listener.cpp b/qpid/cpp/src/tests/qpid-topic-listener.cpp
new file mode 100644
index 0000000000..c42e76d760
--- /dev/null
+++ b/qpid/cpp/src/tests/qpid-topic-listener.cpp
@@ -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.
+ *
+ */
+
+/**
+ * This file provides one half of a test and example of a pub-sub
+ * style of interaction. See qpid-topic-publisher.cpp for the other half,
+ * in which the logic for publishing is defined.
+ *
+ * This file contains the listener logic. A listener will subscribe to
+ * a logical 'topic'. It will count the number of messages it receives
+ * and the time elapsed between the first one and the last one. It
+ * recognises two types of 'special' message that tell it to (a) send
+ * a report containing this information, (b) shutdown (i.e. stop
+ * listening).
+ */
+
+#include "TestOptions.h"
+#include "qpid/client/Connection.h"
+#include "qpid/client/MessageListener.h"
+#include "qpid/client/Session.h"
+#include "qpid/client/AsyncSession.h"
+#include "qpid/client/SubscriptionManager.h"
+#include "qpid/sys/SystemInfo.h"
+#include "qpid/sys/Time.h"
+#include "qpid/framing/FieldValue.h"
+#include <iostream>
+#include <sstream>
+
+using namespace qpid;
+using namespace qpid::client;
+using namespace qpid::sys;
+using namespace qpid::framing;
+using namespace std;
+
+namespace qpid {
+namespace tests {
+
+/**
+ * A message listener implementation in which the runtime logic is
+ * defined.
+ */
+class Listener : public MessageListener{
+ Session session;
+ SubscriptionManager& mgr;
+ const string responseQueue;
+ const bool transactional;
+ bool init;
+ int count;
+ AbsTime start;
+
+ void shutdown();
+ void report();
+public:
+ Listener(const Session& session, SubscriptionManager& mgr, const string& reponseQueue, bool tx);
+ virtual void received(Message& msg);
+ Subscription subscription;
+};
+
+/**
+ * A utility class for managing the options passed in.
+ */
+struct Args : public qpid::TestOptions {
+ int ack;
+ bool transactional;
+ bool durable;
+ int prefetch;
+ string statusqueue;
+
+ Args() : ack(0), transactional(false), durable(false), prefetch(0) {
+ addOptions()
+ ("ack", optValue(ack, "MODE"), "Ack frequency in messages (defaults to half the prefetch value)")
+ ("transactional", optValue(transactional), "Use transactions")
+ ("durable", optValue(durable), "subscribers should use durable queues")
+ ("prefetch", optValue(prefetch, "N"), "prefetch count (0 implies no flow control, and no acking)")
+ ("status-queue", optValue(statusqueue, "QUEUE-NAME"), "Message queue to put status messages on");
+ }
+};
+
+Listener::Listener(const Session& s, SubscriptionManager& m, const string& _responseq, bool tx) :
+ session(s), mgr(m), responseQueue(_responseq), transactional(tx), init(false), count(0){}
+
+void Listener::received(Message& message){
+ if(!init){
+ start = now();
+ count = 0;
+ init = true;
+ cout << "Batch started." << endl;
+ }
+ string type = message.getHeaders().getAsString("TYPE");
+
+ if(string("TERMINATION_REQUEST") == type){
+ shutdown();
+ }else if(string("REPORT_REQUEST") == type){
+ subscription.accept(subscription.getUnaccepted()); // Accept everything upto this point
+ cout <<"Batch ended, sending report." << endl;
+ //send a report:
+ report();
+ init = false;
+ }else if (++count % 1000 == 0){
+ cout <<"Received " << count << " messages." << endl;
+ }
+}
+
+void Listener::shutdown(){
+ mgr.stop();
+}
+
+void Listener::report(){
+ AbsTime finish = now();
+ Duration time(start, finish);
+ stringstream reportstr;
+ reportstr << "Received " << count << " messages in "
+ << time/TIME_MSEC << " ms.";
+ Message msg(reportstr.str(), responseQueue);
+ msg.getHeaders().setString("TYPE", "REPORT");
+ session.messageTransfer(arg::destination="amq.direct", arg::content=msg, arg::acceptMode=1);
+ if(transactional){
+ sync(session).txCommit();
+ }
+}
+
+}} // namespace qpid::tests
+
+using namespace qpid::tests;
+
+/**
+ * The main routine creates a Listener instance and sets it up to
+ * consume from a private queue bound to the exchange with the
+ * appropriate topic name.
+ */
+int main(int argc, char** argv){
+ try{
+ Args args;
+ args.parse(argc, argv);
+ if(args.help)
+ cout << args << endl;
+ else {
+ Connection connection;
+ args.open(connection);
+ AsyncSession session = connection.newSession();
+
+ //declare exchange, queue and bind them:
+ session.queueDeclare(arg::queue="response");
+ std::string control = "control_" + session.getId().str();
+ if (args.durable) {
+ session.queueDeclare(arg::queue=control, arg::durable=true);
+ } else {
+ session.queueDeclare(arg::queue=control, arg::exclusive=true, arg::autoDelete=true);
+ }
+ session.exchangeBind(arg::exchange="amq.topic", arg::queue=control, arg::bindingKey="topic_control");
+
+ //set up listener
+ SubscriptionManager mgr(session);
+ Listener listener(session, mgr, "response", args.transactional);
+ SubscriptionSettings settings;
+ if (args.prefetch) {
+ settings.autoAck = (args.ack ? args.ack : (args.prefetch / 2));
+ settings.flowControl = FlowControl::messageCredit(args.prefetch);
+ } else {
+ settings.acceptMode = ACCEPT_MODE_NONE;
+ settings.flowControl = FlowControl::unlimited();
+ }
+ listener.subscription = mgr.subscribe(listener, control, settings);
+ session.sync();
+
+ if( args.statusqueue.length() > 0 ) {
+ stringstream msg_str;
+ msg_str << "qpid-topic-listener: " << qpid::sys::SystemInfo::getProcessId();
+ session.messageTransfer(arg::content=Message(msg_str.str(), args.statusqueue));
+ cout << "Ready status put on queue '" << args.statusqueue << "'" << endl;
+ }
+
+ if (args.transactional) {
+ session.txSelect();
+ }
+
+ cout << "qpid-topic-listener: listening..." << endl;
+ mgr.run();
+ if (args.durable) {
+ session.queueDelete(arg::queue=control);
+ }
+ session.close();
+ cout << "closing connection" << endl;
+ connection.close();
+ }
+ return 0;
+ } catch (const std::exception& error) {
+ cout << "qpid-topic-listener: " << error.what() << endl;
+ }
+ return 1;
+}
diff --git a/qpid/cpp/src/tests/qpid-topic-publisher.cpp b/qpid/cpp/src/tests/qpid-topic-publisher.cpp
new file mode 100644
index 0000000000..f9107b90d0
--- /dev/null
+++ b/qpid/cpp/src/tests/qpid-topic-publisher.cpp
@@ -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.
+ *
+ */
+
+/**
+ * This file provides one half of a test and example of a pub-sub
+ * style of interaction. See qpid-topic-listener.cpp for the other half, in
+ * which the logic for subscribers is defined.
+ *
+ * This file contains the publisher logic. The publisher will send a
+ * number of messages to the exchange with the appropriate routing key
+ * for the logical 'topic'. Once it has done this it will then send a
+ * request that each subscriber report back with the number of message
+ * it has received and the time that elapsed between receiving the
+ * first one and receiving the report request. Once the expected
+ * number of reports are received, it sends out a request that each
+ * subscriber shutdown.
+ */
+
+#include "TestOptions.h"
+#include "qpid/client/Connection.h"
+#include "qpid/client/MessageListener.h"
+#include "qpid/client/AsyncSession.h"
+#include "qpid/client/SubscriptionManager.h"
+#include "qpid/sys/Monitor.h"
+#include "qpid/sys/Time.h"
+#include <cstdlib>
+#include <iostream>
+
+using namespace qpid;
+using namespace qpid::client;
+using namespace qpid::sys;
+using namespace std;
+
+namespace qpid {
+namespace tests {
+
+/**
+ * The publishing logic is defined in this class. It implements
+ * message listener and can therfore be used to receive messages sent
+ * back by the subscribers.
+ */
+class Publisher {
+ AsyncSession session;
+ SubscriptionManager mgr;
+ LocalQueue queue;
+ const string controlTopic;
+ const bool transactional;
+ const bool durable;
+
+ string generateData(int size);
+
+public:
+ Publisher(const AsyncSession& session, const string& controlTopic, bool tx, bool durable);
+ int64_t publish(int msgs, int listeners, int size);
+ void terminate();
+};
+
+/**
+ * A utility class for managing the options passed in to the test
+ */
+struct Args : public TestOptions {
+ int messages;
+ int subscribers;
+ bool transactional;
+ bool durable;
+ int batches;
+ int delay;
+ int size;
+ string statusqueue;
+
+ Args() : messages(1000), subscribers(1),
+ transactional(false), durable(false),
+ batches(1), delay(0), size(256)
+ {
+ addOptions()
+ ("messages", optValue(messages, "N"), "how many messages to send")
+ ("subscribers", optValue(subscribers, "N"), "how many subscribers to expect reports from")
+ ("transactional", optValue(transactional), "client should use transactions")
+ ("durable", optValue(durable), "messages should be durable")
+ ("batches", optValue(batches, "N"), "how many batches to run")
+ ("delay", optValue(delay, "SECONDS"), "Causes a delay between each batch")
+ ("size", optValue(size, "BYTES"), "size of the published messages")
+ ("status-queue", optValue(statusqueue, "QUEUE-NAME"), "Message queue to read status messages from");
+ }
+};
+
+Publisher::Publisher(const AsyncSession& _session, const string& _controlTopic, bool tx, bool d) :
+ session(_session), mgr(session), controlTopic(_controlTopic), transactional(tx), durable(d)
+{
+ mgr.subscribe(queue, "response");
+}
+
+int64_t Publisher::publish(int msgs, int listeners, int size){
+ Message msg(generateData(size), controlTopic);
+ if (durable) {
+ msg.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT);
+ }
+ AbsTime start = now();
+
+ for(int i = 0; i < msgs; i++){
+ session.messageTransfer(arg::content=msg, arg::destination="amq.topic", arg::acceptMode=1);
+ }
+ //send report request
+ Message reportRequest("", controlTopic);
+ reportRequest.getHeaders().setString("TYPE", "REPORT_REQUEST");
+ session.messageTransfer(arg::content=reportRequest, arg::destination="amq.topic", arg::acceptMode=1);
+ if(transactional){
+ sync(session).txCommit();
+ }
+ //wait for a response from each listener (TODO, could log these)
+ for (int i = 0; i < listeners; i++) {
+ Message report = queue.pop();
+ }
+
+ if(transactional){
+ sync(session).txCommit();
+ }
+
+ AbsTime finish = now();
+ return Duration(start, finish);
+}
+
+string Publisher::generateData(int size){
+ string data;
+ for(int i = 0; i < size; i++){
+ data += ('A' + (i / 26));
+ }
+ return data;
+}
+
+void Publisher::terminate(){
+ //send termination request
+ Message terminationRequest("", controlTopic);
+ terminationRequest.getHeaders().setString("TYPE", "TERMINATION_REQUEST");
+ session.messageTransfer(arg::content=terminationRequest, arg::destination="amq.topic", arg::acceptMode=1);
+ if(transactional){
+ session.txCommit();
+ }
+}
+
+}} // namespace qpid::tests
+
+using namespace qpid::tests;
+
+int main(int argc, char** argv) {
+ try{
+ Args args;
+ args.parse(argc, argv);
+ if(args.help)
+ cout << args << endl;
+ else {
+ Connection connection;
+ args.open(connection);
+ AsyncSession session = connection.newSession();
+
+ // If status-queue is defined, wait for all expected listeners to join in before we start
+ if( args.statusqueue.length() > 0 ) {
+ cout << "Waiting for " << args.subscribers << " listeners..." << endl;
+ SubscriptionManager statusSubs(session);
+ LocalQueue statusQ;
+ statusSubs.subscribe(statusQ, args.statusqueue);
+ for (int i = 0; i < args.subscribers; i++) {
+ Message m = statusQ.get();
+ if( m.getData().find("topic_listener: ", 0) == 0 ) {
+ cout << "Listener " << (i+1) << " of " << args.subscribers
+ << " is ready (pid " << m.getData().substr(16, m.getData().length() - 16)
+ << ")" << endl;
+ } else {
+ throw Exception(QPID_MSG("Unexpected message received on status queue: " << m.getData()));
+ }
+ }
+ }
+
+ if (args.transactional) {
+ session.txSelect();
+ }
+ session.queueDeclare(arg::queue="response");
+ session.exchangeBind(arg::exchange="amq.direct", arg::queue="response", arg::bindingKey="response");
+
+ Publisher publisher(session, "topic_control", args.transactional, args.durable);
+
+ int batchSize(args.batches);
+ int64_t max(0);
+ int64_t min(0);
+ int64_t sum(0);
+ for(int i = 0; i < batchSize; i++){
+ if(i > 0 && args.delay) qpid::sys::sleep(args.delay);
+ int64_t msecs =
+ publisher.publish(args.messages,
+ args.subscribers,
+ args.size) / TIME_MSEC;
+ if(!max || msecs > max) max = msecs;
+ if(!min || msecs < min) min = msecs;
+ sum += msecs;
+ cout << "Completed " << (i+1) << " of " << batchSize
+ << " in " << msecs << "ms" << endl;
+ }
+ publisher.terminate();
+ int64_t avg = sum / batchSize;
+ if(batchSize > 1){
+ cout << batchSize << " batches completed. avg=" << avg <<
+ ", max=" << max << ", min=" << min << endl;
+ }
+ session.close();
+ connection.close();
+ }
+ return 0;
+ }catch(exception& error) {
+ cout << error.what() << endl;
+ }
+ return 1;
+}
diff --git a/qpid/cpp/src/tests/qpid-txtest.cpp b/qpid/cpp/src/tests/qpid-txtest.cpp
new file mode 100644
index 0000000000..59ab905af7
--- /dev/null
+++ b/qpid/cpp/src/tests/qpid-txtest.cpp
@@ -0,0 +1,342 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <algorithm>
+#include <iomanip>
+#include <iostream>
+#include <memory>
+#include <sstream>
+#include <vector>
+
+#include "TestOptions.h"
+#include "qpid/client/Connection.h"
+#include "qpid/client/Message.h"
+#include "qpid/client/AsyncSession.h"
+#include "qpid/client/SubscriptionManager.h"
+#include "qpid/framing/Array.h"
+#include "qpid/framing/Buffer.h"
+#include "qpid/framing/FieldValue.h"
+#include "qpid/framing/Uuid.h"
+#include "qpid/sys/Thread.h"
+
+using namespace qpid;
+using namespace qpid::client;
+using namespace qpid::sys;
+using std::string;
+
+namespace qpid {
+namespace tests {
+
+typedef std::vector<std::string> StringSet;
+
+struct Args : public qpid::TestOptions {
+ bool init, transfer, check;//actions
+ uint size;
+ bool durable;
+ uint queues;
+ string base;
+ uint msgsPerTx;
+ uint txCount;
+ uint totalMsgCount;
+ bool dtx;
+ bool quiet;
+
+ Args() : init(true), transfer(true), check(true),
+ size(256), durable(true), queues(2),
+ base("tx-test"), msgsPerTx(1), txCount(1), totalMsgCount(10),
+ dtx(false), quiet(false)
+ {
+ addOptions()
+
+ ("init", optValue(init, "yes|no"), "Declare queues and populate one with the initial set of messages.")
+ ("transfer", optValue(transfer, "yes|no"), "'Move' messages from one queue to another using transactions to ensure no message loss.")
+ ("check", optValue(check, "yes|no"), "Check that the initial messages are all still available.")
+ ("size", optValue(size, "N"), "message size")
+ ("durable", optValue(durable, "yes|no"), "use durable messages")
+ ("queues", optValue(queues, "N"), "number of queues")
+ ("queue-base-name", optValue(base, "<name>"), "base name for queues")
+ ("messages-per-tx", optValue(msgsPerTx, "N"), "number of messages transferred per transaction")
+ ("tx-count", optValue(txCount, "N"), "number of transactions per 'agent'")
+ ("total-messages", optValue(totalMsgCount, "N"), "total number of messages in 'circulation'")
+ ("dtx", optValue(dtx, "yes|no"), "use distributed transactions")
+ ("quiet", optValue(quiet), "reduce output from test");
+ }
+};
+
+const std::string chars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
+
+std::string generateData(uint size)
+{
+ if (size < chars.length()) {
+ return chars.substr(0, size);
+ }
+ std::string data;
+ for (uint i = 0; i < (size / chars.length()); i++) {
+ data += chars;
+ }
+ data += chars.substr(0, size % chars.length());
+ return data;
+}
+
+void generateSet(const std::string& base, uint count, StringSet& collection)
+{
+ for (uint i = 0; i < count; i++) {
+ std::ostringstream out;
+ out << base << "-" << (i+1);
+ collection.push_back(out.str());
+ }
+}
+
+Args opts;
+
+struct Client
+{
+ Connection connection;
+ AsyncSession session;
+
+ Client()
+ {
+ opts.open(connection);
+ session = connection.newSession();
+ }
+
+ ~Client()
+ {
+ try{
+ session.close();
+ connection.close();
+ } catch(const std::exception& e) {
+ std::cout << e.what() << std::endl;
+ }
+ }
+};
+
+struct Transfer : public Client, public Runnable
+{
+ std::string src;
+ std::string dest;
+ Thread thread;
+ framing::Xid xid;
+
+ Transfer(const std::string& to, const std::string& from) : src(to), dest(from), xid(0x4c414e47, "", from) {}
+
+ void run()
+ {
+ try {
+
+ if (opts.dtx) session.dtxSelect();
+ else session.txSelect();
+ SubscriptionManager subs(session);
+
+ LocalQueue lq;
+ SubscriptionSettings settings(FlowControl::messageWindow(opts.msgsPerTx));
+ settings.autoAck = 0; // Disabled
+ Subscription sub = subs.subscribe(lq, src, settings);
+
+ for (uint t = 0; t < opts.txCount; t++) {
+ Message in;
+ Message out("", dest);
+ if (opts.dtx) {
+ setNewXid(xid);
+ session.dtxStart(arg::xid=xid);
+ }
+ for (uint m = 0; m < opts.msgsPerTx; m++) {
+ in = lq.pop();
+ std::string& data = in.getData();
+ if (data.size() != opts.size) {
+ std::ostringstream oss;
+ oss << "Message size incorrect: size=" << in.getData().size() << "; expected " << opts.size;
+ throw std::runtime_error(oss.str());
+ }
+ out.setData(data);
+ out.getMessageProperties().setCorrelationId(in.getMessageProperties().getCorrelationId());
+ out.getDeliveryProperties().setDeliveryMode(in.getDeliveryProperties().getDeliveryMode());
+ session.messageTransfer(arg::content=out, arg::acceptMode=1);
+ }
+ sub.accept(sub.getUnaccepted());
+ if (opts.dtx) {
+ session.dtxEnd(arg::xid=xid);
+ session.dtxPrepare(arg::xid=xid);
+ session.dtxCommit(arg::xid=xid);
+ } else {
+ session.txCommit();
+ }
+ session.sync();
+ }
+ } catch(const std::exception& e) {
+ std::cout << "Transfer interrupted: " << e.what() << std::endl;
+ }
+ }
+
+ void setNewXid(framing::Xid& xid) {
+ framing::Uuid uuid(true);
+ xid.setGlobalId(uuid.str());
+ }
+};
+
+struct Controller : public Client
+{
+ StringSet ids;
+ StringSet queues;
+
+ Controller()
+ {
+ generateSet(opts.base, opts.queues, queues);
+ generateSet("msg", opts.totalMsgCount, ids);
+ }
+
+ void init()
+ {
+ //declare queues
+ for (StringSet::iterator i = queues.begin(); i != queues.end(); i++) {
+ session.queueDeclare(arg::queue=*i, arg::durable=opts.durable);
+ session.sync();
+ }
+
+ Message msg(generateData(opts.size), *queues.begin());
+ if (opts.durable) {
+ msg.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT);
+ }
+
+ //publish messages
+ for (StringSet::iterator i = ids.begin(); i != ids.end(); i++) {
+ msg.getMessageProperties().setCorrelationId(*i);
+ session.messageTransfer(arg::content=msg, arg::acceptMode=1);
+ }
+ }
+
+ void transfer()
+ {
+ boost::ptr_vector<Transfer> agents(opts.queues);
+ //launch transfer agents
+ for (StringSet::iterator i = queues.begin(); i != queues.end(); i++) {
+ StringSet::iterator next = i + 1;
+ if (next == queues.end()) next = queues.begin();
+
+ if (!opts.quiet) std::cout << "Transfering from " << *i << " to " << *next << std::endl;
+ agents.push_back(new Transfer(*i, *next));
+ agents.back().thread = Thread(agents.back());
+ }
+
+ for (boost::ptr_vector<Transfer>::iterator i = agents.begin(); i != agents.end(); i++) {
+ i->thread.join();
+ }
+ }
+
+ int check()
+ {
+ SubscriptionManager subs(session);
+
+ // Recover DTX transactions (if any)
+ if (opts.dtx) {
+ framing::DtxRecoverResult dtxRes = session.dtxRecover().get();
+ const framing::Array& xidArr = dtxRes.getInDoubt();
+ std::vector<std::string> inDoubtXids(xidArr.size());
+ std::transform(xidArr.begin(), xidArr.end(), inDoubtXids.begin(), framing::Array::get<std::string, framing::Array::ValuePtr>);
+
+ if (inDoubtXids.size()) {
+ if (!opts.quiet) std::cout << "Recovering DTX in-doubt transaction(s):" << std::endl;
+ framing::StructHelper decoder;
+ framing::Xid xid;
+ // abort even, commit odd transactions
+ for (unsigned i = 0; i < inDoubtXids.size(); i++) {
+ decoder.decode(xid, inDoubtXids[i]);
+ if (!opts.quiet) std::cout << (i%2 ? " * aborting " : " * committing ");
+ xid.print(std::cout);
+ std::cout << std::endl;
+ if (i%2) {
+ session.dtxRollback(arg::xid=xid);
+ } else {
+ session.dtxCommit(arg::xid=xid);
+ }
+ }
+ }
+ }
+
+ StringSet drained;
+ //drain each queue and verify the correct set of messages are available
+ for (StringSet::iterator i = queues.begin(); i != queues.end(); i++) {
+ //subscribe, allocate credit and flushn
+ LocalQueue lq;
+ SubscriptionSettings settings(FlowControl::unlimited(), ACCEPT_MODE_NONE);
+ subs.subscribe(lq, *i, settings);
+ session.messageFlush(arg::destination=*i);
+ session.sync();
+
+ uint count(0);
+ while (!lq.empty()) {
+ Message m = lq.pop();
+ //add correlation ids of received messages to drained
+ drained.push_back(m.getMessageProperties().getCorrelationId());
+ ++count;
+ }
+ if (!opts.quiet) std::cout << "Drained " << count << " messages from " << *i << std::endl;
+ }
+
+ sort(ids.begin(), ids.end());
+ sort(drained.begin(), drained.end());
+
+ //check that drained == ids
+ StringSet missing;
+ set_difference(ids.begin(), ids.end(), drained.begin(), drained.end(), back_inserter(missing));
+
+ StringSet extra;
+ set_difference(drained.begin(), drained.end(), ids.begin(), ids.end(), back_inserter(extra));
+
+ if (missing.empty() && extra.empty()) {
+ std::cout << "All expected messages were retrieved." << std::endl;
+ return 0;
+ } else {
+ if (!missing.empty()) {
+ std::cout << "The following ids were missing:" << std::endl;
+ for (StringSet::iterator i = missing.begin(); i != missing.end(); i++) {
+ std::cout << " '" << *i << "'" << std::endl;
+ }
+ }
+ if (!extra.empty()) {
+ std::cout << "The following extra ids were encountered:" << std::endl;
+ for (StringSet::iterator i = extra.begin(); i != extra.end(); i++) {
+ std::cout << " '" << *i << "'" << std::endl;
+ }
+ }
+ return 1;
+ }
+ }
+};
+
+}} // namespace qpid::tests
+
+using namespace qpid::tests;
+
+int main(int argc, char** argv)
+{
+ try {
+ opts.parse(argc, argv);
+ Controller controller;
+ if (opts.init) controller.init();
+ if (opts.transfer) controller.transfer();
+ if (opts.check) return controller.check();
+ return 0;
+ } catch(const std::exception& e) {
+ std::cout << e.what() << std::endl;
+ }
+ return 2;
+}
diff --git a/qpid/cpp/src/tests/qpid-txtest2.cpp b/qpid/cpp/src/tests/qpid-txtest2.cpp
new file mode 100644
index 0000000000..58c48f9a8d
--- /dev/null
+++ b/qpid/cpp/src/tests/qpid-txtest2.cpp
@@ -0,0 +1,363 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <algorithm>
+#include <iomanip>
+#include <iostream>
+#include <memory>
+#include <sstream>
+#include <vector>
+
+#include "qpid/messaging/Connection.h"
+#include "qpid/messaging/Duration.h"
+#include "qpid/messaging/Message.h"
+#include "qpid/messaging/Receiver.h"
+#include "qpid/messaging/Sender.h"
+#include "qpid/messaging/Session.h"
+#include <qpid/Options.h>
+#include <qpid/log/Logger.h>
+#include <qpid/log/Options.h>
+#include "qpid/sys/Runnable.h"
+#include "qpid/sys/Thread.h"
+
+using namespace qpid::messaging;
+using namespace qpid::sys;
+
+namespace qpid {
+namespace tests {
+
+typedef std::vector<std::string> StringSet;
+
+struct Options : public qpid::Options {
+ bool help;
+ bool init, transfer, check;//actions
+ uint size;
+ bool durable;
+ uint queues;
+ std::string base;
+ uint msgsPerTx;
+ uint txCount;
+ uint totalMsgCount;
+ bool dtx;
+ uint capacity;
+ std::string url;
+ std::string connectionOptions;
+ qpid::log::Options log;
+ uint port;
+ bool quiet;
+ double fetchTimeout;
+
+ Options() : help(false), init(true), transfer(true), check(true),
+ size(256), durable(true), queues(2),
+ base("tx"), msgsPerTx(1), txCount(5), totalMsgCount(10),
+ capacity(1000), url("localhost"), port(0), quiet(false), fetchTimeout(5)
+ {
+ addOptions()
+ ("init", qpid::optValue(init, "yes|no"), "Declare queues and populate one with the initial set of messages.")
+ ("transfer", qpid::optValue(transfer, "yes|no"), "'Move' messages from one queue to another using transactions to ensure no message loss.")
+ ("check", qpid::optValue(check, "yes|no"), "Check that the initial messages are all still available.")
+ ("size", qpid::optValue(size, "N"), "message size")
+ ("durable", qpid::optValue(durable, "yes|no"), "use durable messages")
+ ("queues", qpid::optValue(queues, "N"), "number of queues")
+ ("queue-base-name", qpid::optValue(base, "<name>"), "base name for queues")
+ ("messages-per-tx", qpid::optValue(msgsPerTx, "N"), "number of messages transferred per transaction")
+ ("tx-count", qpid::optValue(txCount, "N"), "number of transactions per 'agent'")
+ ("total-messages", qpid::optValue(totalMsgCount, "N"), "total number of messages in 'circulation'")
+ ("capacity", qpid::optValue(capacity, "N"), "Pre-fetch window (0 implies no pre-fetch)")
+ ("broker,b", qpid::optValue(url, "URL"), "url of broker to connect to")
+ ("connection-options", qpid::optValue(connectionOptions, "OPTIONS"), "options for the connection")
+ ("port,p", qpid::optValue(port, "PORT"), "(for test compatibility only, use broker option instead)")
+ ("quiet", qpid::optValue(quiet), "reduce output from test")
+ ("fetch-timeout", qpid::optValue(fetchTimeout, "SECONDS"), "Timeout for transactional fetch")
+ ("help", qpid::optValue(help), "print this usage statement");
+ add(log);
+ }
+
+ bool parse(int argc, char** argv)
+ {
+ try {
+ qpid::Options::parse(argc, argv);
+ if (port) {
+ if (url == "localhost") {
+ std::stringstream u;
+ u << url << ":" << port;
+ url = u.str();
+ } else {
+ std::cerr << *this << std::endl << std::endl
+ << "--port and --broker should not be specified together; specify full url in --broker option" << std::endl;
+ return false;
+ }
+
+ }
+ qpid::log::Logger::instance().configure(log);
+ if (help) {
+ std::cout << *this << std::endl << std::endl
+ << "Transactionally moves messages between queues" << std::endl;
+ return false;
+ }
+ if (totalMsgCount < msgsPerTx) {
+ totalMsgCount = msgsPerTx; // Must have at least msgsPerTx total messages.
+ }
+ return true;
+ } catch (const std::exception& e) {
+ std::cerr << *this << std::endl << std::endl << e.what() << std::endl;
+ return false;
+ }
+ }
+};
+
+const std::string chars("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
+
+std::string generateData(uint size)
+{
+ if (size < chars.length()) {
+ return chars.substr(0, size);
+ }
+ std::string data;
+ for (uint i = 0; i < (size / chars.length()); i++) {
+ data += chars;
+ }
+ data += chars.substr(0, size % chars.length());
+ return data;
+}
+
+void generateSet(const std::string& base, uint count, StringSet& collection)
+{
+ for (uint i = 0; i < count; i++) {
+ std::ostringstream digits;
+ digits << count;
+ std::ostringstream out;
+ out << base << "-" << std::setw(digits.str().size()) << std::setfill('0') << (i+1);
+ collection.push_back(out.str());
+ }
+}
+
+struct Client
+{
+ const Options& opts;
+ Connection connection;
+ Session session;
+
+ Client(const Options& o, bool transactional=false) : opts(o), connection(opts.url, opts.connectionOptions)
+ {
+ connection.open();
+ session = transactional ? connection.createTransactionalSession() : connection.createSession();
+ }
+
+ virtual ~Client()
+ {
+ try {
+ session.sync();
+ session.close();
+ connection.close();
+ } catch(const std::exception& e) {
+ std::cout << "Client shutdown: " << e.what() << std::endl;
+ }
+ }
+};
+
+struct TransactionalClient : Client
+{
+ TransactionalClient(const Options& o) : Client(o, true) {}
+ virtual ~TransactionalClient() {}
+};
+
+struct Transfer : public TransactionalClient, public Runnable
+{
+ const std::string target;
+ const std::string source;
+ Thread thread;
+ bool failed;
+
+ Transfer(const std::string& to, const std::string& from, const Options& opts) : TransactionalClient(opts), target(to), source(from), failed(false) {}
+
+ void run()
+ {
+ try {
+
+ Sender sender(session.createSender(target));
+ Receiver receiver(session.createReceiver(source));
+ receiver.setCapacity(opts.capacity);
+ for (uint t = 0; t < opts.txCount;) {
+ std::ostringstream id;
+ id << source << ">" << target << ":" << t+1;
+ try {
+ for (uint m = 0; m < opts.msgsPerTx; m++) {
+ Message msg = receiver.fetch(Duration::SECOND*uint64_t(opts.fetchTimeout));
+ if (msg.getContentSize() != opts.size) {
+ std::ostringstream oss;
+ oss << "Message size incorrect: size=" << msg.getContentSize() << "; expected " << opts.size;
+ throw std::runtime_error(oss.str());
+ }
+ sender.send(msg);
+ }
+ session.commit();
+ t++;
+ if (!opts.quiet) std::cout << "Transaction " << id.str() << " of " << opts.txCount << " committed successfully" << std::endl;
+ } catch (const TransactionAborted&) {
+ std::cout << "Transaction " << id.str() << " of " << opts.txCount << " was aborted and will be retried" << std::endl;
+ session = connection.createTransactionalSession();
+ sender = session.createSender(target);
+ receiver = session.createReceiver(source);
+ receiver.setCapacity(opts.capacity);
+ }
+ }
+ sender.close();
+ receiver.close();
+ } catch(const std::exception& e) {
+ failed = true;
+ QPID_LOG(error, "Transfer " << source << " to " << target << " interrupted: " << e.what());
+ }
+ }
+};
+
+namespace {
+const std::string CREATE_DURABLE("; {create:always, node:{durable:True}}");
+const std::string CREATE_NON_DURABLE("; {create:always}");
+}
+
+struct Controller : public Client
+{
+ StringSet ids;
+ StringSet queues;
+
+ Controller(const Options& opts) : Client(opts)
+ {
+ generateSet(opts.base, opts.queues, queues);
+ generateSet("msg", opts.totalMsgCount, ids);
+ }
+
+ void init()
+ {
+ Message msg(generateData(opts.size));
+ msg.setDurable(opts.durable);
+
+ for (StringSet::iterator i = queues.begin(); i != queues.end(); i++) {
+ std::string address = *i + (opts.durable ? CREATE_DURABLE : CREATE_NON_DURABLE);
+
+ // Clear out any garbage on queues.
+ Receiver receiver = session.createReceiver(address);
+ Message rmsg;
+ uint count(0);
+ while (receiver.fetch(rmsg, Duration::IMMEDIATE)) ++count;
+ session.acknowledge();
+ receiver.close();
+ if (!opts.quiet) std::cout << "Cleaned up " << count << " messages from " << *i << std::endl;
+
+ Sender sender = session.createSender(address);
+ if (i == queues.begin()) {
+ for (StringSet::iterator i = ids.begin(); i != ids.end(); i++) {
+ msg.setCorrelationId(*i);
+ sender.send(msg);
+ }
+ }
+ sender.close();
+ }
+ }
+
+ void transfer()
+ {
+ boost::ptr_vector<Transfer> agents(opts.queues);
+ //launch transfer agents
+ for (StringSet::iterator i = queues.begin(); i != queues.end(); i++) {
+ StringSet::iterator next = i + 1;
+ if (next == queues.end()) next = queues.begin();
+
+ if (!opts.quiet) std::cout << "Transfering from " << *i << " to " << *next << std::endl;
+ agents.push_back(new Transfer(*i, *next, opts));
+ agents.back().thread = Thread(agents.back());
+ }
+
+ for (boost::ptr_vector<Transfer>::iterator i = agents.begin(); i != agents.end(); i++)
+ i->thread.join();
+ for (boost::ptr_vector<Transfer>::iterator i = agents.begin(); i != agents.end(); i++)
+ if (i->failed)
+ throw std::runtime_error("Transfer agents failed");
+ }
+
+ int check()
+ {
+ StringSet drained;
+ //drain each queue and verify the correct set of messages are available
+ for (StringSet::iterator i = queues.begin(); i != queues.end(); i++) {
+ Receiver receiver = session.createReceiver(*i);
+ uint count(0);
+ Message msg;
+ while (receiver.fetch(msg, Duration::IMMEDIATE)) {
+ //add correlation ids of received messages to drained
+ drained.push_back(msg.getCorrelationId());
+ ++count;
+ }
+ session.acknowledge();
+ receiver.close();
+ if (!opts.quiet) std::cout << "Drained " << count << " messages from " << *i << std::endl;
+ }
+ sort(ids.begin(), ids.end());
+ sort(drained.begin(), drained.end());
+
+ //check that drained == ids
+ StringSet missing;
+ set_difference(ids.begin(), ids.end(), drained.begin(), drained.end(), back_inserter(missing));
+
+ StringSet extra;
+ set_difference(drained.begin(), drained.end(), ids.begin(), ids.end(), back_inserter(extra));
+
+ if (missing.empty() && extra.empty()) {
+ std::cout << "All expected messages were retrieved." << std::endl;
+ return 0;
+ } else {
+ if (!missing.empty()) {
+ std::cout << "The following ids were missing:" << std::endl;
+ for (StringSet::iterator i = missing.begin(); i != missing.end(); i++) {
+ std::cout << " '" << *i << "'" << std::endl;
+ }
+ }
+ if (!extra.empty()) {
+ std::cout << "The following extra ids were encountered:" << std::endl;
+ for (StringSet::iterator i = extra.begin(); i != extra.end(); i++) {
+ std::cout << " '" << *i << "'" << std::endl;
+ }
+ }
+ return 1;
+ }
+ }
+};
+}} // namespace qpid::tests
+
+using namespace qpid::tests;
+
+int main(int argc, char** argv)
+{
+ try {
+ Options opts;
+ if (opts.parse(argc, argv)) {
+ Controller controller(opts);
+ if (opts.init) controller.init();
+ if (opts.transfer) controller.transfer();
+ if (opts.check) return controller.check();
+ return 0;
+ }
+ return 1;
+ } catch(const std::exception& e) {
+ std::cerr << argv[0] << ": " << e.what() << std::endl;
+ }
+ return 2;
+}
diff --git a/qpid/cpp/src/tests/qpidd-empty.conf b/qpid/cpp/src/tests/qpidd-empty.conf
new file mode 100644
index 0000000000..cf3f19fba0
--- /dev/null
+++ b/qpid/cpp/src/tests/qpidd-empty.conf
@@ -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.
+#
+
+# An empty configuration file.
+# Used when running tests to avoid picking up configuration
+# installed in the default place.
diff --git a/qpid/cpp/src/tests/qpidd-p0 b/qpid/cpp/src/tests/qpidd-p0
new file mode 100755
index 0000000000..1f7807afd2
--- /dev/null
+++ b/qpid/cpp/src/tests/qpidd-p0
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Wrapper script to allocate a port and fork a broker to listen on it.
+#
+# Instead of this:
+# qpidd --port 0 <qpidd-args...>
+# do this:
+# qpidd-p0 <qpidd-args...>
+#
+# The port is bound by python code, and then handed over to the broker via the
+# --socket-fd option. This avoids problems with the qpidd --port 0 option which
+# ocassional fails with an "address in use" error. It's not clear why --port 0
+# doesn't work, it may be to do with the way qpidd binds a port to multiple
+# addresses on a multi-homed host.
+#
+
+import subprocess, socket, time, os, sys
+
+s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+s.bind(("", 0))
+s.listen(5)
+port = s.getsockname()[1]
+print port
+sys.stdout.flush()
+if len(sys.argv) > 1:
+ cmd = sys.argv[1:] + ["--socket-fd", str(s.fileno()), "--listen-disable=tcp"]
+ os.execvp(sys.argv[1], cmd)
diff --git a/qpid/cpp/src/tests/qpidd_qmfv2_tests.py b/qpid/cpp/src/tests/qpidd_qmfv2_tests.py
new file mode 100755
index 0000000000..2b45cb6eea
--- /dev/null
+++ b/qpid/cpp/src/tests/qpidd_qmfv2_tests.py
@@ -0,0 +1,278 @@
+#!/usr/bin/env python
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Runs QMF tests against a broker running with QMFv1 disabled. This forces the
+# broker to use QMFv2 only. This is necessary as if there is a bug in V2, some
+# V1 operations may hide that (esp. asynchonous notifications)
+
+
+import sys, shutil, os
+from time import sleep
+from brokertest import *
+from qpid.messaging import Message
+try: import qmf.console
+except: print "Cannot import module qmf.console, skipping tests"; exit(0);
+
+import qpid.messaging, brokertest
+brokertest.qm = qpid.messaging # TODO aconway 2014-04-04: Tests fail with SWIG client.
+
+class ConsoleTest(BrokerTest):
+ """
+ Test QMFv2 support using the qmf.console library.
+ """
+ PUB_INTERVAL=1
+
+ def setUp(self):
+ BrokerTest.setUp(self)
+
+ def _startBroker(self, QMFv1=False ):
+ self._broker_is_v1 = QMFv1
+ if self._broker_is_v1:
+ args = ["--mgmt-qmf1=yes", "--mgmt-qmf2=no"]
+ else:
+ args = ["--mgmt-qmf1=no", "--mgmt-qmf2=yes"]
+
+ args.append("--mgmt-pub-interval=%d" % self.PUB_INTERVAL)
+ self.broker = BrokerTest.broker(self, args)
+
+
+ def _myStartQmf(self, broker, console=None):
+ # I manually set up the QMF session here rather than call the startQmf
+ # method from BrokerTest as I can guarantee the console library is used
+ # (assuming BrokerTest's implementation of startQmf could change)
+ self.qmf_session = qmf.console.Session(console)
+ self.qmf_broker = self.qmf_session.addBroker("%s:%s" % (broker.host(),
+ broker.port()))
+
+ def _create_queue( self, q_name, args={} ):
+ broker = self.qmf_session.getObjects(_class="broker")[0]
+ result = broker.create("queue", q_name, args, False)
+ self.assertEqual(result.status, 0, result)
+
+
+ def _test_method_call(self):
+ """ Verify method calls work, and check the behavior of getObjects()
+ call
+ """
+ self._myStartQmf( self.broker )
+ self._create_queue( "fleabag", {"auto-delete":True} )
+
+ qObj = None
+ queues = self.qmf_session.getObjects(_class="queue")
+ for q in queues:
+ if q.name == "fleabag":
+ qObj = q
+ break
+ self.assertNotEqual(qObj, None, "Failed to get queue object")
+ #print qObj
+
+ def _test_unsolicited_updates(self):
+ """ Verify that the Console callbacks work
+ """
+
+ class Handler(qmf.console.Console):
+ def __init__(self):
+ self.v1_oids = 0
+ self.v1_events = 0
+ self.v2_oids = 0
+ self.v2_events = 0
+ self.broker_info = []
+ self.broker_conn = []
+ self.newpackage = []
+ self.newclass = []
+ self.agents = []
+ self.events = []
+ self.updates = {} # holds the objects by OID
+ self.heartbeats = []
+
+ def brokerInfo(self, broker):
+ #print "brokerInfo:", broker
+ self.broker_info.append(broker)
+ def brokerConnected(self, broker):
+ #print "brokerConnected:", broker
+ self.broker_conn.append(broker)
+ def newPackage(self, name):
+ #print "newPackage:", name
+ self.newpackage.append(name)
+ def newClass(self, kind, classKey):
+ #print "newClass:", kind, classKey
+ self.newclass.append( (kind, classKey) )
+ def newAgent(self, agent):
+ #print "newAgent:", agent
+ self.agents.append( agent )
+ def event(self, broker, event):
+ #print "EVENT %s" % event
+ self.events.append(event)
+ if event.isV2:
+ self.v2_events += 1
+ else:
+ self.v1_events += 1
+
+ def heartbeat(self, agent, timestamp):
+ #print "Heartbeat %s" % agent
+ self.heartbeats.append( (agent, timestamp) )
+
+ # generic handler for objectProps and objectStats
+ def _handle_obj_update(self, record):
+ oid = record.getObjectId()
+ if oid.isV2:
+ self.v2_oids += 1
+ else:
+ self.v1_oids += 1
+
+ if oid not in self.updates:
+ self.updates[oid] = record
+ else:
+ self.updates[oid].mergeUpdate( record )
+
+ def objectProps(self, broker, record):
+ assert len(record.getProperties()), "objectProps() invoked with no properties?"
+ self._handle_obj_update(record)
+
+ def objectStats(self, broker, record):
+ assert len(record.getStatistics()), "objectStats() invoked with no properties?"
+ self._handle_obj_update(record)
+
+ handler = Handler()
+ self._myStartQmf( self.broker, handler )
+ # this should force objectProps, queueDeclare Event callbacks
+ self._create_queue( "fleabag", {"auto-delete":True} )
+ # this should force objectStats callback
+ self.broker.send_message( "fleabag", Message("Hi") )
+ # and we should get a few heartbeats
+ sleep(self.PUB_INTERVAL)
+ self.broker.send_message( "fleabag", Message("Hi") )
+ sleep(self.PUB_INTERVAL)
+ self.broker.send_message( "fleabag", Message("Hi") )
+ sleep(self.PUB_INTERVAL * 2)
+
+ assert handler.broker_info, "No BrokerInfo callbacks received"
+ assert handler.broker_conn, "No BrokerConnected callbacks received"
+ assert handler.newpackage, "No NewPackage callbacks received"
+ assert handler.newclass, "No NewClass callbacks received"
+ assert handler.agents, "No NewAgent callbacks received"
+ assert handler.events, "No event callbacks received"
+ assert handler.updates, "No updates received"
+ assert handler.heartbeats, "No heartbeat callbacks received"
+
+ # now verify updates for queue "fleabag" were received, and the
+ # msgDepth statistic is correct
+
+ msgs = 0
+ for o in handler.updates.itervalues():
+ key = o.getClassKey()
+ if key and key.getClassName() == "queue" and o.name == "fleabag":
+ assert o.msgDepth, "No update to msgDepth statistic!"
+ msgs = o.msgDepth
+ break
+ assert msgs == 3, "msgDepth statistics not accurate!"
+
+ # verify that the published objects were of the correct QMF version
+ if self._broker_is_v1:
+ assert handler.v1_oids and handler.v2_oids == 0, "QMFv2 updates received while in V1-only mode!"
+ assert handler.v1_events and handler.v2_events == 0, "QMFv2 events received while in V1-only mode!"
+ else:
+ assert handler.v2_oids and handler.v1_oids == 0, "QMFv1 updates received while in V2-only mode!"
+ assert handler.v2_events and handler.v1_events == 0, "QMFv1 events received while in V2-only mode!"
+
+ def _test_async_method(self):
+ class Handler (qmf.console.Console):
+ def __init__(self):
+ self.cv = Condition()
+ self.xmtList = {}
+ self.rcvList = {}
+
+ def methodResponse(self, broker, seq, response):
+ self.cv.acquire()
+ try:
+ self.rcvList[seq] = response
+ finally:
+ self.cv.release()
+
+ def request(self, broker, count):
+ self.count = count
+ for idx in range(count):
+ self.cv.acquire()
+ try:
+ seq = broker.echo(idx, "Echo Message", _async = True)
+ self.xmtList[seq] = idx
+ finally:
+ self.cv.release()
+
+ def check(self):
+ if self.count != len(self.xmtList):
+ return "fail (attempted send=%d, actual sent=%d)" % (self.count, len(self.xmtList))
+ lost = 0
+ mismatched = 0
+ for seq in self.xmtList:
+ value = self.xmtList[seq]
+ if seq in self.rcvList:
+ result = self.rcvList.pop(seq)
+ if result.sequence != value:
+ mismatched += 1
+ else:
+ lost += 1
+ spurious = len(self.rcvList)
+ if lost == 0 and mismatched == 0 and spurious == 0:
+ return "pass"
+ else:
+ return "fail (lost=%d, mismatch=%d, spurious=%d)" % (lost, mismatched, spurious)
+
+ handler = Handler()
+ self._myStartQmf(self.broker, handler)
+ broker = self.qmf_session.getObjects(_class="broker")[0]
+ handler.request(broker, 20)
+ sleep(1)
+ self.assertEqual(handler.check(), "pass")
+
+ def test_method_call(self):
+ self._startBroker()
+ self._test_method_call()
+
+ def test_unsolicited_updates(self):
+ self._startBroker()
+ self._test_unsolicited_updates()
+
+ def test_async_method(self):
+ self._startBroker()
+ self._test_async_method()
+
+ # For now, include "QMFv1 only" tests. Once QMFv1 is deprecated, these can
+ # be removed
+
+ def test_method_call_v1(self):
+ self._startBroker(QMFv1=True)
+ self._test_method_call()
+
+ def test_unsolicited_updates_v1(self):
+ self._startBroker(QMFv1=True)
+ self._test_unsolicited_updates()
+
+ def test_async_method_v1(self):
+ self._startBroker(QMFv1=True)
+ self._test_async_method()
+
+
+
+if __name__ == "__main__":
+ shutil.rmtree("brokertest.tmp", True)
+ os.execvp("qpid-python-test",
+ ["qpid-python-test", "-m", "qpidd_qmfv2_tests"] + sys.argv[1:])
+
diff --git a/qpid/cpp/src/tests/queue_flow_limit_tests.py b/qpid/cpp/src/tests/queue_flow_limit_tests.py
new file mode 100644
index 0000000000..83e31e979b
--- /dev/null
+++ b/qpid/cpp/src/tests/queue_flow_limit_tests.py
@@ -0,0 +1,376 @@
+#!/usr/bin/env python
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 sys
+from qpid.testlib import TestBase010
+from qpid.messaging import Connection
+from threading import Thread
+from time import sleep, time
+from os import environ, popen
+
+class QueueFlowLimitTests(TestBase010):
+
+ _timeout = 100
+
+ def __getattr__(self, name):
+ if name == "assertGreater":
+ return lambda a, b: self.failUnless(a > b)
+ else:
+ raise AttributeError
+
+ def _create_queue(self, name,
+ stop_count=None, resume_count=None,
+ stop_size=None, resume_size=None,
+ max_size=None, max_count=None):
+ """ Create a queue with the given flow settings via the queue.declare
+ command.
+ """
+ args={}
+ if (stop_count is not None):
+ args["qpid.flow_stop_count"] = stop_count;
+ if (resume_count is not None):
+ args["qpid.flow_resume_count"] = resume_count;
+ if (stop_size is not None):
+ args["qpid.flow_stop_size"] = stop_size;
+ if (resume_size is not None):
+ args["qpid.flow_resume_size"] = resume_size;
+ if (max_size is not None):
+ args["qpid.max_size"] = max_size;
+ if (max_count is not None):
+ args["qpid.max_count"] = max_count;
+
+ broker = self.qmf.getObjects(_class="broker")[0]
+ rc = broker.create( "queue", name, args, True )
+ self.assertEqual(rc.status, 0, rc)
+
+ qs = self.qmf.getObjects(_class="queue")
+ for i in qs:
+ if i.name == name:
+ # verify flow settings
+ if (stop_count is not None):
+ self.assertEqual(i.arguments.get("qpid.flow_stop_count"), stop_count)
+ if (resume_count is not None):
+ self.assertEqual(i.arguments.get("qpid.flow_resume_count"), resume_count)
+ if (stop_size is not None):
+ self.assertEqual(i.arguments.get("qpid.flow_stop_size"), stop_size)
+ if (resume_size is not None):
+ self.assertEqual(i.arguments.get("qpid.flow_resume_size"), resume_size)
+ if (max_size is not None):
+ self.assertEqual(i.arguments.get("qpid.max_size"), max_size)
+ if (max_count is not None):
+ self.assertEqual(i.arguments.get("qpid.max_count"), max_count)
+ self.failIf(i.flowStopped)
+ return i.getObjectId()
+ self.fail("Unable to create queue '%s'" % name)
+ return None
+
+
+ def _delete_queue(self, name):
+ """ Delete a named queue
+ """
+ broker = self.qmf.getObjects(_class="broker")[0]
+ rc = broker.delete( "queue", name, {} )
+ self.assertEqual(rc.status, 0, rc)
+
+
+ def _start_qpid_send(self, queue, count, content="X", capacity=100):
+ """ Use the qpid-send client to generate traffic to a queue.
+ """
+ command = "qpid-send" + \
+ " -b" + " %s:%s" % (self.broker.host, self.broker.port) \
+ + " -a " + str(queue) \
+ + " --messages " + str(count) \
+ + " --content-string " + str(content) \
+ + " --capacity " + str(capacity)
+ return popen(command)
+
+ def _start_qpid_receive(self, queue, count, timeout=5):
+ """ Use the qpid-receive client to consume from a queue.
+ Note well: prints one line of text to stdout for each consumed msg.
+ """
+ command = "qpid-receive" + \
+ " -b " + "%s:%s" % (self.broker.host, self.broker.port) \
+ + " -a " + str(queue) \
+ + " --messages " + str(count) \
+ + " --timeout " + str(timeout) \
+ + " --print-content yes"
+ return popen(command)
+
+ def test_qpid_config_cmd(self):
+ """ Test the qpid-config command's ability to configure a queue's flow
+ control thresholds.
+ """
+ tool = environ.get("QPID_CONFIG_EXEC")
+ if tool:
+ command = tool + \
+ " --broker=%s:%s " % (self.broker.host, self.broker.port) \
+ + "add queue test01 --flow-stop-count=999" \
+ + " --flow-resume-count=55 --flow-stop-size=5000000" \
+ + " --flow-resume-size=100000"
+ cmd = popen(command)
+ rc = cmd.close()
+ self.assertEqual(rc, None)
+
+ # now verify the settings
+ self.startQmf();
+ qs = self.qmf.getObjects(_class="queue")
+ for i in qs:
+ if i.name == "test01":
+ self.assertEqual(i.arguments.get("qpid.flow_stop_count"), 999)
+ self.assertEqual(i.arguments.get("qpid.flow_resume_count"), 55)
+ self.assertEqual(i.arguments.get("qpid.flow_stop_size"), 5000000)
+ self.assertEqual(i.arguments.get("qpid.flow_resume_size"), 100000)
+ self.failIf(i.flowStopped)
+ break;
+ self.assertEqual(i.name, "test01")
+ self._delete_queue("test01")
+
+
+ def test_flow_count(self):
+ """ Create a queue with count-based flow limit. Spawn several
+ producers which will exceed the limit. Verify limit exceeded. Consume
+ all messages. Verify flow control released.
+ """
+ self.startQmf();
+ oid = self._create_queue("test-q", stop_count=373, resume_count=229)
+ self.assertEqual(self.qmf.getObjects(_objectId=oid)[0].flowStoppedCount, 0)
+
+ sndr1 = self._start_qpid_send("test-q", count=1213, content="XXX", capacity=50);
+ sndr2 = self._start_qpid_send("test-q", count=797, content="Y", capacity=13);
+ sndr3 = self._start_qpid_send("test-q", count=331, content="ZZZZZ", capacity=149);
+ totalMsgs = 1213 + 797 + 331
+
+ # wait until flow control is active
+ deadline = time() + self._timeout
+ while (not self.qmf.getObjects(_objectId=oid)[0].flowStopped) and \
+ time() < deadline:
+ pass
+ self.failUnless(self.qmf.getObjects(_objectId=oid)[0].flowStopped)
+ depth = self.qmf.getObjects(_objectId=oid)[0].msgDepth
+ self.assertGreater(depth, 373)
+
+ # now wait until the enqueues stop happening - ensure that
+ # not all msgs have been sent (senders are blocked)
+ sleep(1)
+ newDepth = self.qmf.getObjects(_objectId=oid)[0].msgDepth
+ while depth != newDepth:
+ depth = newDepth;
+ sleep(1)
+ newDepth = self.qmf.getObjects(_objectId=oid)[0].msgDepth
+ self.assertGreater(totalMsgs, depth)
+
+ # drain the queue
+ rcvr = self._start_qpid_receive("test-q",
+ count=totalMsgs)
+ count = 0;
+ x = rcvr.readline() # prints a line for each received msg
+ while x:
+ count += 1;
+ x = rcvr.readline()
+
+ sndr1.close();
+ sndr2.close();
+ sndr3.close();
+ rcvr.close();
+
+ self.assertEqual(count, totalMsgs)
+ self.failIf(self.qmf.getObjects(_objectId=oid)[0].flowStopped)
+ self.failUnless(self.qmf.getObjects(_objectId=oid)[0].flowStoppedCount)
+
+ self._delete_queue("test-q")
+
+
+ def test_flow_size(self):
+ """ Create a queue with size-based flow limit. Spawn several
+ producers which will exceed the limit. Verify limit exceeded. Consume
+ all messages. Verify flow control released.
+ """
+ self.startQmf();
+ oid = self._create_queue("test-q", stop_size=351133, resume_size=251143)
+
+ sndr1 = self._start_qpid_send("test-q", count=1699, content="X"*439, capacity=53);
+ sndr2 = self._start_qpid_send("test-q", count=1129, content="Y"*631, capacity=13);
+ sndr3 = self._start_qpid_send("test-q", count=881, content="Z"*823, capacity=149);
+ totalMsgs = 1699 + 1129 + 881
+
+ # wait until flow control is active
+ deadline = time() + self._timeout
+ while (not self.qmf.getObjects(_objectId=oid)[0].flowStopped) and \
+ time() < deadline:
+ pass
+ self.failUnless(self.qmf.getObjects(_objectId=oid)[0].flowStopped)
+ self.assertGreater(self.qmf.getObjects(_objectId=oid)[0].byteDepth, 351133)
+
+ # now wait until the enqueues stop happening - ensure that
+ # not all msgs have been sent (senders are blocked)
+ depth = self.qmf.getObjects(_objectId=oid)[0].msgDepth
+ sleep(1)
+ newDepth = self.qmf.getObjects(_objectId=oid)[0].msgDepth
+ while depth != newDepth:
+ depth = newDepth;
+ sleep(1)
+ newDepth = self.qmf.getObjects(_objectId=oid)[0].msgDepth
+ self.assertGreater(totalMsgs, depth)
+
+ # drain the queue
+ rcvr = self._start_qpid_receive("test-q",
+ count=totalMsgs)
+ count = 0;
+ x = rcvr.readline() # prints a line for each received msg
+ while x:
+ count += 1;
+ x = rcvr.readline()
+
+ sndr1.close();
+ sndr2.close();
+ sndr3.close();
+ rcvr.close();
+
+ self.assertEqual(count, totalMsgs)
+ self.failIf(self.qmf.getObjects(_objectId=oid)[0].flowStopped)
+
+ self._delete_queue("test-q")
+
+
+ def verify_limit(self, testq):
+ """ run a limit check against the testq object
+ """
+
+ testq.mgmt = self.qmf.getObjects(_objectId=testq.oid)[0]
+
+ # fill up the queue, waiting until flow control is active
+ sndr1 = self._start_qpid_send(testq.mgmt.name, count=testq.sendCount, content=testq.content)
+ deadline = time() + self._timeout
+ while (not testq.mgmt.flowStopped) and time() < deadline:
+ testq.mgmt.update()
+
+ self.failUnless(testq.verifyStopped())
+
+ # now consume enough messages to drop below the flow resume point, and
+ # verify flow control is released.
+ rcvr = self._start_qpid_receive(testq.mgmt.name, count=testq.consumeCount)
+ rcvr.readlines() # prints a line for each received msg
+ rcvr.close();
+
+ # we should now be below the resume threshold
+ self.failUnless(testq.verifyResumed())
+
+ self._delete_queue(testq.mgmt.name)
+ sndr1.close();
+
+
+ def test_default_flow_count(self):
+ """ Create a queue with count-based size limit, and verify the computed
+ thresholds using the broker's default ratios.
+ """
+ class TestQ:
+ def __init__(self, oid):
+ # Use the broker-wide default flow thresholds of 80%/70% (see
+ # run_queue_flow_limit_tests) to base the thresholds off the
+ # queue's max_count configuration parameter
+ # max_count == 1000 -> stop == 800, resume == 700
+ self.oid = oid
+ self.sendCount = 1000
+ self.consumeCount = 301 # (send - resume) + 1 to reenable flow
+ self.content = "X"
+ self.mgmt = None
+ def verifyStopped(self):
+ self.mgmt.update()
+ return self.mgmt.flowStopped and (self.mgmt.msgDepth > 800)
+ def verifyResumed(self):
+ self.mgmt.update()
+ return (not self.mgmt.flowStopped) and (self.mgmt.msgDepth < 700)
+
+ self.startQmf();
+ oid = self._create_queue("test-X", max_count=1000)
+ self.verify_limit(TestQ(oid))
+
+
+ def test_default_flow_size(self):
+ """ Create a queue with byte-based size limit, and verify the computed
+ thresholds using the broker's default ratios.
+ """
+ class TestQ:
+ def __init__(self, oid):
+ # Use the broker-wide default flow thresholds of 80%/70% (see
+ # run_queue_flow_limit_tests) to base the thresholds off the
+ # queue's max_count configuration parameter
+ # max_size == 10000 -> stop == 8000 bytes, resume == 7000 bytes
+ self.oid = oid
+ self.sendCount = 2000
+ self.consumeCount = 601 # (send - resume) + 1 to reenable flow
+ self.content = "XXXXX" # 5 bytes per message sent.
+ self.mgmt = None
+ def verifyStopped(self):
+ self.mgmt.update()
+ return self.mgmt.flowStopped and (self.mgmt.byteDepth > 8000)
+ def verifyResumed(self):
+ self.mgmt.update()
+ return (not self.mgmt.flowStopped) and (self.mgmt.byteDepth < 7000)
+
+ self.startQmf();
+ oid = self._create_queue("test-Y", max_size=10000)
+ self.verify_limit(TestQ(oid))
+
+
+ def test_blocked_queue_delete(self):
+ """ Verify that blocked senders are unblocked when a queue that is flow
+ controlled is deleted.
+ """
+
+ class BlockedSender(Thread):
+ def __init__(self, tester, queue, count, capacity=10):
+ self.tester = tester
+ self.queue = queue
+ self.count = count
+ self.capacity = capacity
+ Thread.__init__(self)
+ self.done = False
+ self.start()
+ def run(self):
+ # spawn qpid-send
+ p = self.tester._start_qpid_send(self.queue,
+ self.count,
+ self.capacity)
+ p.close() # waits for qpid-send to complete
+ self.done = True
+
+ self.startQmf();
+ oid = self._create_queue("kill-q", stop_size=10, resume_size=2)
+ q = self.qmf.getObjects(_objectId=oid)[0]
+ self.failIf(q.flowStopped)
+
+ sender = BlockedSender(self, "kill-q", count=100)
+ # wait for flow control
+ deadline = time() + self._timeout
+ while (not q.flowStopped) and time() < deadline:
+ q.update()
+
+ self.failUnless(q.flowStopped)
+ self.failIf(sender.done) # sender blocked
+
+ self._delete_queue("kill-q")
+ sender.join(5)
+ self.failIf(sender.isAlive())
+ self.failUnless(sender.done)
+
+
+
+
diff --git a/qpid/cpp/src/tests/queue_redirect.py b/qpid/cpp/src/tests/queue_redirect.py
new file mode 100644
index 0000000000..8a7b4c244b
--- /dev/null
+++ b/qpid/cpp/src/tests/queue_redirect.py
@@ -0,0 +1,317 @@
+#!/usr/bin/env python
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 sys
+import qpid
+from qpid.util import connect
+from qpid.connection import Connection
+from qpid.datatypes import uuid4
+from qpid.testlib import TestBase010
+from qmf.console import Session
+from qpid.datatypes import Message
+import qpid.messaging
+from time import sleep
+from os import environ, popen
+
+class ACLFile:
+ def __init__(self, policy='data_dir/policy.acl'):
+ self.f = open(policy,'w')
+
+ def write(self,line):
+ self.f.write(line)
+
+ def close(self):
+ self.f.close()
+
+class QueueredirectTests(TestBase010):
+
+ def get_session(self, user, passwd):
+ socket = connect(self.broker.host, self.broker.port)
+ connection = Connection (sock=socket, username=user, password=passwd,
+ mechanism="PLAIN")
+ connection.start()
+ return connection.session(str(uuid4()))
+
+ def reload_acl(self):
+ result = None
+ try:
+ self.broker_access.reloadAclFile()
+ except Exception, e:
+ result = str(e)
+ return result
+
+ def get_acl_file(self):
+ return ACLFile(self.config.defines.get("policy-file", "data_dir/policy.acl"))
+
+ def setUp(self):
+ aclf = self.get_acl_file()
+ aclf.write('acl allow all all\n')
+ aclf.close()
+ TestBase010.setUp(self)
+ self.startBrokerAccess()
+ self.reload_acl()
+
+ def tearDown(self):
+ aclf = self.get_acl_file()
+ aclf.write('acl allow all all\n')
+ aclf.close()
+ self.reload_acl()
+ TestBase010.tearDown(self)
+
+
+ def redirect(self, srcQueue, tgtQueue, expectPass, failMessage):
+ try:
+ result = {}
+ result = self.broker_access.Redirect(srcQueue, tgtQueue)
+ if not expectPass:
+ self.fail("src:" + srcQueue + ", tgt:" + tgtQueue + " - " + failMessage)
+ except Exception, e:
+ if expectPass:
+ self.fail("src:" + srcQueue + ", tgt:" + tgtQueue + " - " + failMessage)
+
+ def create_queue(self, session, name, autoDelete):
+ try:
+ session.queue_declare(queue=name, auto_delete=autoDelete)
+ except Exception, e:
+ self.fail("Should allow create queue " + name)
+
+ def _start_qpid_send(self, queue, count, content="X", capacity=100):
+ """ Use the qpid-send client to generate traffic to a queue.
+ """
+ command = "qpid-send" + \
+ " -b" + " %s:%s" % (self.broker.host, self.broker.port) \
+ + " -a " + str(queue) \
+ + " --messages " + str(count) \
+ + " --content-string " + str(content) \
+ + " --capacity " + str(capacity)
+ return popen(command)
+
+ def _start_qpid_receive(self, queue, count, timeout=5):
+ """ Use the qpid-receive client to consume from a queue.
+ Note well: prints one line of text to stdout for each consumed msg.
+ """
+ command = "qpid-receive" + \
+ " -b " + "%s:%s" % (self.broker.host, self.broker.port) \
+ + " -a " + str(queue) \
+ + " --messages " + str(count) \
+ + " --timeout " + str(timeout) \
+ + " --print-content yes"
+ return popen(command)
+
+
+
+ #=====================================
+ # QT queue tests
+ #=====================================
+
+ def test_010_deny_backing_up_a_nonexistant_queue(self):
+ session = self.get_session('bob','bob')
+ self.redirect("A010", "A010", False, "Should not allow redirect to non-existent queue A010")
+ session.close()
+
+ def test_020_deny_destroy_redirect(self):
+ session = self.get_session('bob','bob')
+ self.create_queue(session, "A020", False)
+ self.redirect("A020", "", False, "Should not allow destroying redirect")
+ session.close()
+
+ def test_030_deny_redirecting_to_nonexistent_queue(self):
+ session = self.get_session('bob','bob')
+ self.create_queue(session, "A030", False)
+ self.redirect("A030", "Axxx", False, "Should not allow redirect with non-existent queue Axxx")
+ session.close()
+
+ def test_040_deny_queue_redirecting_to_itself(self):
+ session = self.get_session('bob','bob')
+ self.create_queue(session, "A040", False)
+ self.redirect("A040", "A040", False, "Should not allow redirect with itself")
+ session.close()
+
+ def test_050_deny_redirecting_autodelete_queue(self):
+ session = self.get_session('bob','bob')
+ self.create_queue(session, "A050", True)
+ self.create_queue(session, "B050", False)
+ self.redirect("A050", "B050", False, "Should not allow redirect with autodelete source queue")
+ self.redirect("B050", "A050", False, "Should not allow redirect with autodelete target queue")
+ session.close()
+
+ def test_100_create_redirect_queue_pair(self):
+ session = self.get_session('bob','bob')
+ self.create_queue(session, "A100", False)
+ self.create_queue(session, "B100", False)
+ self.redirect("A100", "B100", True, "Should allow redirect")
+ session.close()
+
+ def test_110_deny_adding_second_redirect_to_queue(self):
+ session = self.get_session('bob','bob')
+ self.create_queue(session, "A110", False)
+ self.create_queue(session, "B110", False)
+ self.create_queue(session, "C110", False)
+ self.redirect("A110", "B110", True, "Should allow redirect")
+ self.redirect("A110", "C110", False, "Should deny second redirect")
+ self.redirect("C110", "B110", False, "Should deny second redirect")
+ session.close()
+
+ def test_120_verify_redirect_to_target(self):
+ session = self.get_session('bob','bob')
+ self.create_queue(session, "A120", False)
+ self.create_queue(session, "B120", False)
+
+ # Send messages to original queue
+ sndr1 = self._start_qpid_send("A120", count=5, content="A120-before-rebind");
+ sndr1.close()
+
+ # redirect
+ self.redirect("A120", "B120", True, "Should allow redirect")
+
+ # Send messages to original queue
+ sndr2 = self._start_qpid_send("A120", count=3, content="A120-after-rebind");
+ sndr2.close()
+
+ # drain the queue
+ rcvr = self._start_qpid_receive("A120",
+ count=5)
+ count = 0;
+ x = rcvr.readline() # prints a line for each received msg
+ while x:
+# print "Read from A120 " + x
+ count += 1;
+ x = rcvr.readline()
+
+ self.assertEqual(count, 5)
+
+ # drain the queue
+ rcvrB = self._start_qpid_receive("B120",
+ count=3)
+ count = 0;
+ x = rcvrB.readline() # prints a line for each received msg
+ while x:
+# print "Read from B120 " + x
+ count += 1;
+ x = rcvrB.readline()
+
+ self.assertEqual(count, 3)
+
+ ###session.close()
+
+ def test_140_verify_redirect_to_source(self):
+ session = self.get_session('bob','bob')
+ self.create_queue(session, "A140", False)
+ self.create_queue(session, "B140", False)
+
+ # Send messages to target queue - these go onto B
+ sndr1 = self._start_qpid_send("B140", count=5, content="B140-before-rebind");
+ sndr1.close()
+
+ # redirect
+ self.redirect("A140", "B140", True, "Should allow redirect")
+
+ # Send messages to target queue - these go onto A
+ sndr2 = self._start_qpid_send("B140", count=3, content="B140-after-rebind");
+ sndr2.close()
+
+ # drain the queue
+ rcvr = self._start_qpid_receive("B140", count=5)
+ count = 0;
+ x = rcvr.readline() # prints a line for each received msg
+ while x:
+ # print "Read from B140 " + x
+ count += 1;
+ x = rcvr.readline()
+
+ self.assertEqual(count, 5)
+
+ # drain the queue
+ rcvrB = self._start_qpid_receive("A140", count=3)
+ count = 0;
+ x = rcvrB.readline() # prints a line for each received msg
+ while x:
+ # print "Read from A140 " + x
+ count += 1;
+ x = rcvrB.readline()
+
+ self.assertEqual(count, 3)
+
+ ###session.close()
+
+ def test_150_queue_deletion_destroys_redirect(self):
+ session = self.get_session('bob','bob')
+ self.create_queue(session, "A150", False)
+ self.create_queue(session, "B150", False)
+ self.create_queue(session, "C150", False)
+
+ # redirect
+ self.redirect("A150", "B150", True, "Should allow redirect")
+
+ self.redirect("A150", "C150", False, "A is already redirected")
+
+ alice = BrokerAdmin(self.config.broker, "bob", "bob")
+ alice.delete_queue("B150") #should pass
+
+ self.redirect("A150", "C150", True, "Should allow redirect")
+ session.close()
+
+##############################################################################################
+class BrokerAdmin:
+ def __init__(self, broker, username=None, password=None):
+ self.connection = qpid.messaging.Connection(broker)
+ if username:
+ self.connection.username = username
+ self.connection.password = password
+ self.connection.sasl_mechanisms = "PLAIN"
+ self.connection.open()
+ self.session = self.connection.session()
+ self.sender = self.session.sender("qmf.default.direct/broker")
+ self.reply_to = "responses-#; {create:always}"
+ self.receiver = self.session.receiver(self.reply_to)
+
+ def invoke(self, method, arguments):
+ content = {
+ "_object_id": {"_object_name": "org.apache.qpid.broker:broker:amqp-broker"},
+ "_method_name": method,
+ "_arguments": arguments
+ }
+ request = qpid.messaging.Message(reply_to=self.reply_to, content=content)
+ request.properties["x-amqp-0-10.app-id"] = "qmf2"
+ request.properties["qmf.opcode"] = "_method_request"
+ self.sender.send(request)
+ response = self.receiver.fetch()
+ self.session.acknowledge()
+ if response.properties['x-amqp-0-10.app-id'] == 'qmf2':
+ if response.properties['qmf.opcode'] == '_method_response':
+ return response.content['_arguments']
+ elif response.properties['qmf.opcode'] == '_exception':
+ raise Exception(response.content['_values'])
+ else: raise Exception("Invalid response received, unexpected opcode: %s" % response.properties['qmf.opcode'])
+ else: raise Exception("Invalid response received, not a qmfv2 method: %s" % response.properties['x-amqp-0-10.app-id'])
+
+ def create_exchange(self, name, exchange_type=None, options={}):
+ properties = options
+ if exchange_type: properties["exchange_type"] = exchange_type
+ self.invoke("create", {"type": "exchange", "name":name, "properties":properties})
+
+ def create_queue(self, name, properties={}):
+ self.invoke("create", {"type": "queue", "name":name, "properties":properties})
+
+ def delete_exchange(self, name):
+ self.invoke("delete", {"type": "exchange", "name":name})
+
+ def delete_queue(self, name):
+ self.invoke("delete", {"type": "queue", "name":name})
diff --git a/qpid/cpp/src/tests/quick_perftest b/qpid/cpp/src/tests/quick_perftest
new file mode 100755
index 0000000000..698af60324
--- /dev/null
+++ b/qpid/cpp/src/tests/quick_perftest
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+exec `dirname $0`/run_test ./qpid-perftest --summary --count 100
diff --git a/qpid/cpp/src/tests/quick_topictest b/qpid/cpp/src/tests/quick_topictest
new file mode 100755
index 0000000000..e44ec0f477
--- /dev/null
+++ b/qpid/cpp/src/tests/quick_topictest
@@ -0,0 +1,30 @@
+#!/usr/bin/env 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.
+#
+
+
+# Quick and quiet topic test for make check.
+test -z "$srcdir" && srcdir=`dirname $0`
+$srcdir/topictest -s2 -m2 -b1 > topictest.log 2>&1 || {
+ echo $0 FAILED:
+ cat topictest.log
+ exit 1
+}
+rm topictest.log
diff --git a/qpid/cpp/src/tests/quick_topictest.ps1 b/qpid/cpp/src/tests/quick_topictest.ps1
new file mode 100644
index 0000000000..8f5b2caff7
--- /dev/null
+++ b/qpid/cpp/src/tests/quick_topictest.ps1
@@ -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.
+#
+
+# Quick and quiet topic test for make check.
+[string]$me = $myInvocation.InvocationName
+$srcdir = Split-Path $me
+Invoke-Expression "$srcdir\topictest.ps1 -subscribers 2 -messages 2 -batches 1" > topictest.log 2>&1
+if (!$?) {
+ "$me FAILED:"
+ cat topictest.log
+ exit 1
+}
+Remove-Item topictest.log
+exit 0
diff --git a/qpid/cpp/src/tests/quick_txtest b/qpid/cpp/src/tests/quick_txtest
new file mode 100755
index 0000000000..77e8556f1d
--- /dev/null
+++ b/qpid/cpp/src/tests/quick_txtest
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+exec `dirname $0`/run_test ./qpid-txtest --queues 4 --tx-count 10 --quiet
diff --git a/qpid/cpp/src/tests/receiver.cpp b/qpid/cpp/src/tests/receiver.cpp
new file mode 100644
index 0000000000..f1b462d6e4
--- /dev/null
+++ b/qpid/cpp/src/tests/receiver.cpp
@@ -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.
+ *
+ */
+
+#include <qpid/client/FailoverManager.h>
+#include <qpid/client/Session.h>
+#include <qpid/client/Message.h>
+#include <qpid/client/SubscriptionManager.h>
+#include <qpid/client/SubscriptionSettings.h>
+#include "TestOptions.h"
+
+#include <iostream>
+#include <fstream>
+
+
+using namespace qpid;
+using namespace qpid::client;
+using namespace qpid::framing;
+
+using namespace std;
+
+namespace qpid {
+namespace tests {
+
+struct Args : public qpid::TestOptions
+{
+ string queue;
+ uint messages;
+ bool ignoreDuplicates;
+ uint creditWindow;
+ uint ackFrequency;
+ bool browse;
+
+ Args() : queue("test-queue"), messages(0), ignoreDuplicates(false), creditWindow(0), ackFrequency(1), browse(false)
+ {
+ addOptions()
+ ("queue", qpid::optValue(queue, "QUEUE NAME"), "Queue from which to request messages")
+ ("messages", qpid::optValue(messages, "N"), "Number of messages to receive; 0 means receive indefinitely")
+ ("ignore-duplicates", qpid::optValue(ignoreDuplicates), "Detect and ignore duplicates (by checking 'sn' header)")
+ ("credit-window", qpid::optValue(creditWindow, "N"), "Credit window (0 implies infinite window)")
+ ("ack-frequency", qpid::optValue(ackFrequency, "N"), "Ack frequency (0 implies none of the messages will get accepted)")
+ ("browse", qpid::optValue(browse), "Browse rather than consuming");
+ }
+};
+
+const string EOS("eos");
+const string SN("sn");
+
+class Receiver : public MessageListener, public FailoverManager::Command
+{
+ public:
+ Receiver(const string& queue, uint messages, bool ignoreDuplicates, uint creditWindow, uint ackFrequency, bool browse);
+ void received(Message& message);
+ void execute(AsyncSession& session, bool isRetry);
+ private:
+ const string queue;
+ const uint count;
+ const bool skipDups;
+ SubscriptionSettings settings;
+ Subscription subscription;
+ uint processed;
+ uint lastSn;
+
+ bool isDuplicate(Message& message);
+};
+
+Receiver::Receiver(const string& q, uint messages, bool ignoreDuplicates, uint creditWindow, uint ackFrequency, bool browse) :
+ queue(q), count(messages), skipDups(ignoreDuplicates), processed(0), lastSn(0)
+{
+ if (browse) settings.acquireMode = ACQUIRE_MODE_NOT_ACQUIRED;
+ if (creditWindow) settings.flowControl = FlowControl::messageWindow(creditWindow);
+ settings.autoAck = ackFrequency;
+}
+
+void Receiver::received(Message& message)
+{
+ if (!(skipDups && isDuplicate(message))) {
+ bool eos = message.getData() == EOS;
+ if (!eos) std::cout << message.getData() << std::endl;
+ if (eos || ++processed == count) subscription.cancel();
+ }
+}
+
+bool Receiver::isDuplicate(Message& message)
+{
+ uint sn = message.getHeaders().getAsInt(SN);
+ if (lastSn < sn) {
+ lastSn = sn;
+ return false;
+ } else {
+ return true;
+ }
+}
+
+void Receiver::execute(AsyncSession& session, bool /*isRetry*/)
+{
+ SubscriptionManager subs(session);
+ subscription = subs.subscribe(*this, queue, settings);
+ subs.run();
+ if (settings.autoAck) {
+ subscription.accept(subscription.getUnaccepted());
+ }
+}
+
+}} // namespace qpid::tests
+
+using namespace qpid::tests;
+
+int main(int argc, char ** argv)
+{
+ Args opts;
+ try {
+ opts.parse(argc, argv);
+ FailoverManager connection(opts.con);
+ Receiver receiver(opts.queue, opts.messages, opts.ignoreDuplicates, opts.creditWindow, opts.ackFrequency, opts.browse);
+ connection.execute(receiver);
+ connection.close();
+ return 0;
+ } catch(const std::exception& error) {
+ std::cerr << "Failure: " << error.what() << std::endl;
+ }
+ return 1;
+}
diff --git a/qpid/cpp/src/tests/reject_release.py b/qpid/cpp/src/tests/reject_release.py
new file mode 100644
index 0000000000..d072b8aa78
--- /dev/null
+++ b/qpid/cpp/src/tests/reject_release.py
@@ -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.
+#
+
+from qpid.tests.messaging.implementation import *
+from qpid.tests.messaging import VersionTest
+
+class RejectReleaseTests (VersionTest):
+ """
+ Tests for reject and release with qpidd
+ """
+ def test_reject(self):
+ name = str(uuid4())
+ snd = self.ssn.sender("%s; {create:always, node:{properties:{alternate-exchange:amq.fanout}}}" % name)
+ rcv = self.ssn.receiver(name)
+ rcv2 = self.ssn.receiver("amq.fanout")
+
+ msgs = [Message(content=s, subject = s) for s in ['a','b','c','d']]
+
+ for m in msgs: snd.send(m)
+
+ for expected in msgs:
+ msg = rcv.fetch(0)
+ assert msg.content == expected.content
+ self.ssn.reject(msg)
+
+ for expected in msgs:
+ msg = rcv2.fetch(0)
+ assert msg.content == expected.content
+
+ def test_release(self):
+ snd = self.ssn.sender("#")
+ rcv = self.ssn.receiver(snd.target)
+
+ msgs = [Message(content=s, subject = s) for s in ['a','b','c','d']]
+
+ for m in msgs: snd.send(m)
+
+ msg = rcv.fetch(0)
+ assert msg.content == "a"
+ self.ssn.release(msg)
+
+ msg = rcv.fetch(0)
+ assert msg.content == "a"
+ self.ssn.acknowledge(msg)
+
+ msg = rcv.fetch(0)
+ assert msg.content == "b"
+ self.ssn.release(msg)
+
diff --git a/qpid/cpp/src/tests/replaying_sender.cpp b/qpid/cpp/src/tests/replaying_sender.cpp
new file mode 100644
index 0000000000..a5549bfdf2
--- /dev/null
+++ b/qpid/cpp/src/tests/replaying_sender.cpp
@@ -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.
+ *
+ */
+
+#include <qpid/client/FailoverManager.h>
+#include <qpid/client/Session.h>
+#include <qpid/client/AsyncSession.h>
+#include <qpid/client/Message.h>
+#include <qpid/client/MessageReplayTracker.h>
+#include <qpid/Exception.h>
+
+#include <iostream>
+#include <sstream>
+
+using namespace qpid;
+using namespace qpid::client;
+using namespace qpid::framing;
+
+using namespace std;
+
+namespace qpid {
+namespace tests {
+
+class Sender : public FailoverManager::Command
+{
+ public:
+ Sender(const std::string& queue, uint count, uint reportFreq);
+ void execute(AsyncSession& session, bool isRetry);
+ uint getSent();
+
+ void setVerbosity ( int v ) { verbosity = v; }
+ void setPersistence ( int p ) { persistence = p; }
+
+ private:
+ MessageReplayTracker sender;
+ const uint count;
+ uint sent;
+ const uint reportFrequency;
+ Message message;
+ int verbosity;
+ int persistence;
+ string queueName;
+};
+
+Sender::Sender(const std::string& queue, uint count_, uint reportFreq )
+ : sender(10),
+ count(count_),
+ sent(0),
+ reportFrequency(reportFreq),
+ verbosity(0),
+ persistence(0),
+ queueName ( queue )
+{
+ message.getDeliveryProperties().setRoutingKey(queueName.c_str());
+}
+
+const string SN("sn");
+
+void Sender::execute(AsyncSession& session, bool isRetry)
+{
+ if (verbosity > 0)
+ std::cout << "replaying_sender " << (isRetry ? "first " : "re-") << "connect." << endl;
+ if (isRetry) sender.replay(session);
+ else sender.init(session);
+ while (sent < count) {
+ stringstream message_data;
+ message_data << ++sent;
+ message.setData(message_data.str());
+ message.getHeaders().setInt(SN, sent);
+ if ( persistence )
+ message.getDeliveryProperties().setDeliveryMode(PERSISTENT);
+
+ sender.send(message);
+ if (count > reportFrequency && !(sent % reportFrequency)) {
+ if ( verbosity > 0 )
+ std::cout << "Sender sent "
+ << sent
+ << " of "
+ << count
+ << " on queue "
+ << queueName
+ << std::endl;
+ }
+ }
+ message.setData("That's all, folks!");
+ sender.send(message);
+
+ if ( verbosity > 0 )
+ std::cout << "SENDER COMPLETED\n";
+}
+
+uint Sender::getSent()
+{
+ return sent;
+}
+
+}} // namespace qpid::tests
+
+using namespace qpid::tests;
+
+int main(int argc, char ** argv)
+{
+ ConnectionSettings settings;
+
+ if ( argc != 8 )
+ {
+ std::cerr << "Usage: replaying_sender host port n_messages report_frequency verbosity persistence queue_name\n";
+ return 1;
+ }
+
+ settings.host = argv[1];
+ settings.port = atoi(argv[2]);
+ int n_messages = atoi(argv[3]);
+ int reportFrequency = atoi(argv[4]);
+ int verbosity = atoi(argv[5]);
+ int persistence = atoi(argv[6]);
+ char * queue_name = argv[7];
+
+ FailoverManager connection(settings);
+ Sender sender(queue_name, n_messages, reportFrequency );
+ sender.setVerbosity ( verbosity );
+ sender.setPersistence ( persistence );
+ try {
+ connection.execute ( sender );
+ if ( verbosity > 0 )
+ {
+ std::cout << "Sender finished. Sent "
+ << sender.getSent()
+ << " messages on queue "
+ << queue_name
+ << endl;
+ }
+ connection.close();
+ return 0;
+ }
+ catch(const std::exception& error)
+ {
+ cerr << "Sender (host: "
+ << settings.host
+ << " port: "
+ << settings.port
+ << " ) "
+ << " Failed: "
+ << error.what()
+ << std::endl;
+ }
+ return 1;
+}
diff --git a/qpid/cpp/src/tests/resuming_receiver.cpp b/qpid/cpp/src/tests/resuming_receiver.cpp
new file mode 100644
index 0000000000..2e22a7c572
--- /dev/null
+++ b/qpid/cpp/src/tests/resuming_receiver.cpp
@@ -0,0 +1,193 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <qpid/client/FailoverManager.h>
+#include <qpid/client/Session.h>
+#include <qpid/client/Message.h>
+#include <qpid/client/SubscriptionManager.h>
+
+#include <iostream>
+#include <fstream>
+
+using namespace qpid;
+using namespace qpid::client;
+using namespace qpid::framing;
+
+using namespace std;
+
+
+
+namespace qpid {
+namespace tests {
+
+class Listener : public MessageListener,
+ public FailoverManager::Command,
+ public FailoverManager::ReconnectionStrategy
+{
+ public:
+ Listener ( int report_frequency = 1000,
+ int verbosity = 0,
+ char const * queue_name = "message_queue" );
+ void received(Message& message);
+ void execute(AsyncSession& session, bool isRetry);
+ void check();
+ void editUrlList(vector<Url>& urls);
+ private:
+ Subscription subscription;
+ uint count;
+ vector<int> received_twice;
+ uint lastSn;
+ bool gaps;
+ uint reportFrequency;
+ int verbosity;
+ bool done;
+ string queueName;
+};
+
+
+Listener::Listener ( int freq, int verbosity, char const * name )
+ : count(0),
+ lastSn(0),
+ gaps(false),
+ reportFrequency(freq),
+ verbosity(verbosity),
+ done(false),
+ queueName ( name )
+{}
+
+const std::string SN("sn");
+
+void Listener::received(Message & message)
+{
+ if (message.getData() == "That's all, folks!")
+ {
+ done = true;
+ if(verbosity > 0 )
+ {
+ cout << "Shutting down listener for "
+ << message.getDestination() << endl;
+
+ cout << "Listener received "
+ << count
+ << " messages ("
+ << received_twice.size()
+ << " received_twice)"
+ << endl;
+
+ }
+ subscription.cancel();
+ if ( verbosity > 0 )
+ cout << "LISTENER COMPLETED\n";
+
+ if ( ! gaps ) {
+ cout << "no gaps were detected\n";
+ cout << received_twice.size() << " messages were received twice.\n";
+ }
+ else {
+ cout << "gaps detected\n";
+ for ( unsigned int i = 0; i < received_twice.size(); ++ i )
+ cout << "received_twice "
+ << received_twice[i]
+ << endl;
+ }
+ } else {
+ uint sn = message.getHeaders().getAsInt(SN);
+ if (lastSn < sn) {
+ if (sn - lastSn > 1) {
+ cerr << "Error: gap in sequence between " << lastSn << " and " << sn << endl;
+ gaps = true;
+ }
+ lastSn = sn;
+ ++count;
+ if ( ! ( count % reportFrequency ) ) {
+ if ( verbosity > 0 )
+ cout << "Listener has received "
+ << count
+ << " messages on queue "
+ << queueName
+ << endl;
+ }
+ } else {
+ received_twice.push_back ( sn );
+ }
+ }
+}
+
+void Listener::check()
+{
+ if (gaps) throw Exception("Detected gaps in sequence; messages appear to have been lost.");
+}
+
+void Listener::execute(AsyncSession& session, bool isRetry) {
+ if (verbosity > 0)
+ cout << "resuming_receiver " << (isRetry ? "first " : "re-") << "connect." << endl;
+ if (!done) {
+ SubscriptionManager subs(session);
+ subscription = subs.subscribe(*this, queueName);
+ subs.run();
+ }
+}
+
+void Listener::editUrlList(vector<Url>& urls)
+{
+ /**
+ * A more realistic algorithm would be to search through the list
+ * for prefered hosts and ensure they come first in the list.
+ */
+ if (urls.size() > 1) rotate(urls.begin(), urls.begin() + 1, urls.end());
+}
+
+}} // namespace qpid::tests
+
+using namespace qpid::tests;
+
+int main(int argc, char ** argv)
+{
+ ConnectionSettings settings;
+
+ if ( argc != 6 )
+ {
+ cerr << "Usage: resuming_receiver host port report_frequency verbosity queue_name\n";
+ return 1;
+ }
+
+ settings.host = argv[1];
+ settings.port = atoi(argv[2]);
+ int reportFrequency = atoi(argv[3]);
+ int verbosity = atoi(argv[4]);
+ char * queue_name = argv[5];
+
+ Listener listener ( reportFrequency, verbosity, queue_name );
+ FailoverManager connection(settings, &listener);
+
+ try {
+ connection.execute(listener);
+ connection.close();
+ listener.check();
+ return 0;
+ } catch(const exception& error) {
+ cerr << "Receiver failed: " << error.what() << endl;
+ }
+ return 1;
+}
+
+
+
diff --git a/qpid/cpp/src/tests/ring_queue_test b/qpid/cpp/src/tests/ring_queue_test
new file mode 100755
index 0000000000..410ea30eb8
--- /dev/null
+++ b/qpid/cpp/src/tests/ring_queue_test
@@ -0,0 +1,174 @@
+#!/usr/bin/env 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.
+#
+
+# Test script for validating the behaviour of a ring queue
+
+QUEUE_NAME=ring-queue
+LIMIT=100
+DURABLE=0
+MESSAGES=10000
+SENDERS=1
+RECEIVERS=1
+CONCURRENT=0
+BROKER_URL="-b ${QPID_BROKER:-localhost}:${QPID_PORT:-5672}"
+
+setup() {
+ if [[ $DURABLE -gt 0 ]]; then
+ EXTRA_ARGS=" --durable"
+ fi
+ qpid-config $BROKER_URL add queue $QUEUE_NAME --max-queue-count $LIMIT --limit-policy ring $EXTRA_ARGS
+}
+
+send() {
+ datagen --count $MESSAGES | tee sender_${QUEUE_NAME}_${1} | sender --durable $DURABLE --routing-key $QUEUE_NAME
+}
+
+receive() {
+ #TODO: allow a variety of receiver options to be passed in (ack-frequency, credit-window etc)
+ receiver --queue $QUEUE_NAME > receiver_${QUEUE_NAME}_${1}
+}
+
+cleanup() {
+ rm -f sender_${QUEUE_NAME}_* receiver_${QUEUE_NAME}_*
+ qpid-config $BROKER_URL del queue $QUEUE_NAME --force
+}
+
+log() {
+ echo $1
+}
+
+fail() {
+ echo $1
+ FAILED=1
+}
+
+validate() {
+ if [[ $RECEIVERS -eq 0 ]]; then
+ #queue should have $LIMIT messages on it, but need to send an eos also
+ sender --routing-key $QUEUE_NAME --send-eos 1 < /dev/null
+ received=$(receiver --queue $QUEUE_NAME --browse | wc -l)
+ if [[ received -eq $(( $LIMIT - 1)) ]]; then
+ log "queue contains $LIMIT messages as expected"
+ else
+ fail "queue does not contain the expected $LIMIT messages (received $received)"
+ fi
+ elif [[ $CONCURRENT -eq 0 ]]; then
+ #sum of length of all output files should be equal to $LIMIT - $RECEIVERS (1 eos message each)
+ received=$(cat receiver_${QUEUE_NAME}_* | wc -l)
+ expected=$(( $LIMIT - $RECEIVERS ))
+ if [[ $received -eq $expected ]]; then
+ log "received $LIMIT messages as expected"
+ else
+ fail "received $received messages, expected $expected"
+ fi
+ #if there were only a single sender and receiver (executed serially) we can check the
+ #actual received contents
+ if [[ $RECEIVERS -eq 1 ]] && [[ $SENDERS -eq 1 ]]; then
+ tail -n $(($LIMIT - 1)) sender_${QUEUE_NAME}_1 | diff - receiver_${QUEUE_NAME}_1 || FAILED=1
+ if [[ $FAILED -eq 1 ]]; then
+ fail "did not receive expected messages"
+ else
+ log "received messages matched expectations"
+ fi
+ fi
+ else
+ #multiple receivers, concurrent with senders; ring queue functionality cannot be validated in this case
+ if [[ $(cat receiver_${QUEUE_NAME}_* | wc -l) -le $(cat sender_${QUEUE_NAME}_* | wc -l) ]]; then
+ log "sent at least as many messages as were received"
+ else
+ #Note: if any receiver was browsing, this would be valid (would need to add 'sort | uniq')
+ # to pipeline above
+ fail "received more messages than were sent"
+ fi
+ fi
+
+ if [[ $FAILED ]]; then
+ echo $(basename $0): FAILED
+ exit 1
+ else
+ cleanup
+ fi
+}
+
+run_test() {
+ if [[ $CONCURRENT -eq 0 ]]; then
+ echo "Starting $SENDERS senders followed by $RECEIVERS receivers "
+ else
+ echo "Starting $SENDERS senders concurrently with $RECEIVERS receivers"
+ fi
+ for ((i=1; i <= $SENDERS; i++)); do
+ send $i &
+ sender_pids[$i]=$!
+ done
+ if [[ $CONCURRENT -eq 0 ]] && [[ $RECEIVERS -gt 0 ]]; then
+ wait
+ sender --routing-key $QUEUE_NAME --send-eos $RECEIVERS < /dev/null
+ fi
+ for ((i=1; i <= $RECEIVERS; i++)); do
+ receive $i &
+ done
+ if [[ $CONCURRENT -gt 0 ]]; then
+ for ((i=1; i <= $SENDERS; i++)); do
+ wait ${sender_pids[$i]}
+ done
+ sender --routing-key $QUEUE_NAME --send-eos $RECEIVERS < /dev/null
+ fi
+ wait
+}
+
+usage() {
+ cat <<EOF
+$(basename $0): Test script for validating the behaviour of a ring queue
+
+Options:
+ -q <queue> the name of the queue to use
+ -s <senders> the number of senders to start
+ -r <receivers> the number of receivers to start
+ -l <limit> the limit for the ring queue
+ -m <messages> the number of messages to send
+ -c if specified, receivers will run concurrently with senders
+ -d if specified the queue and messages will be durable
+EOF
+ exit 1
+}
+
+while getopts "s:r:m:u:dch" opt ; do
+ case $opt in
+ q) QUEUE_NAME=$OPTARG ;;
+ l) LIMIT=$OPTARG ;;
+ s) SENDERS=$OPTARG ;;
+ r) RECEIVERS=$OPTARG ;;
+ m) MESSAGES=$OPTARG ;;
+ d) DURABLE=1 ;;
+ c) CONCURRENT=1 ;;
+ h) usage;;
+ ?) usage;;
+ esac
+done
+
+if [[ $SENDERS -gt 0 ]]; then
+ setup
+ run_test
+ validate
+else
+ echo "Nothing can be done if there are no senders"
+fi
+
diff --git a/qpid/cpp/src/tests/rsynchosts b/qpid/cpp/src/tests/rsynchosts
new file mode 100755
index 0000000000..10e1081f76
--- /dev/null
+++ b/qpid/cpp/src/tests/rsynchosts
@@ -0,0 +1,57 @@
+#!/usr/bin/env bash
+#
+# Licensed to the Apache Software Foundation (ASF) under onemake
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+absdir() { echo `cd $1 && pwd`; }
+
+abspath() {
+ if test -d "$1"; then absdir "$1";
+ else echo $(absdir $(dirname "$1"))/$(basename "$1")
+ fi
+}
+
+usage() {
+ echo "Usage: $(basename $0) [-l user] file [file...]
+Synchronize the contents of each file or directory to the same absolute path on
+each host in \$HOSTS.
+"
+ exit 1
+}
+
+while getopts "l:" opt; do
+ case $opt in
+ l) RSYNC_USER="$OPTARG@" ;;
+ *) usage ;;
+ esac
+done
+shift `expr $OPTIND - 1`
+
+test "$*" || usage
+
+for f in $*; do FILES="$FILES $(abspath $f)" || exit 1; done
+
+OK_FILE=`mktemp` # Will be deleted if anything goes wrong.
+trap "rm -f $OK_FILE" EXIT
+
+for h in $HOSTS; do
+ rsync -vaRO --delete $FILES $RSYNC_USER$h:/ || { echo "rsync to $h failed"; rm -f $OK_FILE; } &
+done
+wait
+test -f $OK_FILE
+
diff --git a/qpid/cpp/src/tests/run_acl_tests b/qpid/cpp/src/tests/run_acl_tests
new file mode 100755
index 0000000000..4bb9e7aa5d
--- /dev/null
+++ b/qpid/cpp/src/tests/run_acl_tests
@@ -0,0 +1,166 @@
+#!/usr/bin/env 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.
+#
+
+# Run the acl tests. $srcdir is set by the Makefile.
+source ./test_env.sh
+DATA_DIR=`pwd`/data_dir
+DATA_DIRI=`pwd`/data_diri
+DATA_DIRU=`pwd`/data_diru
+DATA_DIRQ=`pwd`/data_dirq
+
+trap stop_brokers INT TERM QUIT
+
+start_brokers() {
+ ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-module-dir --data-dir $DATA_DIR --acl-file policy.acl --auth no --log-enable trace+:acl --log-to-file local.log > qpidd.port
+ LOCAL_PORT=`cat qpidd.port`
+ ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-module-dir --data-dir $DATA_DIRI --acl-file policy.acl --auth no --connection-limit-per-ip 2 --log-to-file locali.log > qpiddi.port
+ LOCAL_PORTI=`cat qpiddi.port`
+ ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-module-dir --data-dir $DATA_DIRU --acl-file policy.acl --auth no --connection-limit-per-user 2 --log-to-file localu.log > qpiddu.port
+ LOCAL_PORTU=`cat qpiddu.port`
+ ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-module-dir --data-dir $DATA_DIRQ --acl-file policy.acl --auth no --max-queues-per-user 2 --log-to-file localq.log > qpiddq.port
+ LOCAL_PORTQ=`cat qpiddq.port`
+}
+
+start_noacl_noauth_brokers() {
+ ../qpidd --daemon --port 0 --no-module-dir --data-dir $DATA_DIR --auth no --log-to-file local.log > qpidd.port
+ LOCAL_PORT=`cat qpidd.port`
+ ../qpidd --daemon --port 0 --no-module-dir --data-dir $DATA_DIRI --auth no --log-to-file locali.log > qpiddi.port
+ LOCAL_PORTI=`cat qpiddi.port`
+ ../qpidd --daemon --port 0 --no-module-dir --data-dir $DATA_DIRU --auth no --log-to-file localu.log > qpiddu.port
+ LOCAL_PORTU=`cat qpiddu.port`
+ ../qpidd --daemon --port 0 --no-module-dir --data-dir $DATA_DIRQ --auth no --log-to-file localq.log > qpiddq.port
+ LOCAL_PORTQ=`cat qpiddq.port`
+}
+
+start_noacl_auth_brokers() {
+ sasl_config_file=$builddir/sasl_config
+ if [ ! -f $sasl_config_file ] ; then
+ echo Creating sasl database
+ . $srcdir/sasl_test_setup.sh
+ fi
+ ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-module-dir --data-dir $DATA_DIR --auth yes --sasl-config=$sasl_config_file --log-to-file local.log > qpidd.port
+ LOCAL_PORT=`cat qpidd.port`
+ ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-module-dir --data-dir $DATA_DIRI --auth yes --sasl-config=$sasl_config_file --log-to-file locali.log > qpiddi.port
+ LOCAL_PORTI=`cat qpiddi.port`
+ ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-module-dir --data-dir $DATA_DIRU --auth yes --sasl-config=$sasl_config_file --log-to-file localu.log > qpiddu.port
+ LOCAL_PORTU=`cat qpiddu.port`
+ ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-module-dir --data-dir $DATA_DIRQ --auth yes --sasl-config=$sasl_config_file --log-to-file localq.log > qpiddq.port
+ LOCAL_PORTQ=`cat qpiddq.port`
+}
+
+stop_brokers() {
+ $QPIDD_EXEC --no-module-dir -q --port $LOCAL_PORT
+ $QPIDD_EXEC --no-module-dir -q --port $LOCAL_PORTI
+ $QPIDD_EXEC --no-module-dir -q --port $LOCAL_PORTU
+ $QPIDD_EXEC --no-module-dir -q --port $LOCAL_PORTQ
+}
+
+delete_directories() {
+ rm -rf $DATA_DIR
+ rm -rf $DATA_DIRI
+ rm -rf $DATA_DIRU
+ rm -rf $DATA_DIRQ
+}
+
+delete_logfiles() {
+ rm -rf local.log
+ rm -rf locali.log
+ rm -rf localu.log
+ rm -rf localq.log
+}
+
+create_directories() {
+ mkdir -p $DATA_DIR
+ mkdir -p $DATA_DIRI
+ mkdir -p $DATA_DIRU
+ mkdir -p $DATA_DIRQ
+}
+
+populate_directories() {
+ cp $srcdir/policy.acl $DATA_DIR
+ cp $srcdir/policy.acl $DATA_DIRI
+ cp $srcdir/policy.acl $DATA_DIRU
+ cp $srcdir/policy.acl $DATA_DIRQ
+}
+
+test_loading_acl_from_absolute_path(){
+ POLICY_FILE=$srcdir/policy.acl
+ rm -f temp.log
+ PORT=`../qpidd --daemon --port 0 --interface 127.0.0.1 --no-module-dir --no-data-dir --auth no --acl-file $POLICY_FILE -t --log-to-file temp.log 2>/dev/null`
+ ACL_FILE=`grep "notice ACL: Read file" temp.log | sed 's/^.*Read file //'`
+ $QPIDD_EXEC --no-module-dir -q --port $PORT
+ if test "$ACL_FILE" != "\"$POLICY_FILE\""; then
+ echo "unable to load policy file from an absolute path";
+ return 1;
+ fi
+ rm temp.log
+}
+
+test_noacl_deny_create_link() {
+ delete_logfiles
+ start_noacl_noauth_brokers
+ echo "Running no-acl, no-auth tests using brokers on ports $LOCAL_PORT, $LOCAL_PORTI, $LOCAL_PORTU, and $LOCAL_PORTQ"
+ $QPID_CONFIG_EXEC -a localhost:$LOCAL_PORT add exchange topic fed.topic
+ $QPID_CONFIG_EXEC -a localhost:$LOCAL_PORTI add exchange topic fed.topic
+ $QPID_ROUTE_EXEC dynamic add localhost:$LOCAL_PORT localhost:$LOCAL_PORTI fed.topic 2>/dev/null
+ sleep 2
+ stop_brokers
+ grep -q "must specify ACL create link rules" local.log
+ if [ $? -eq 0 ]
+ then
+ echo "Test fail - Broker with auth=no should have allowed link creation";
+ return 1;
+ fi
+
+ delete_logfiles
+ start_noacl_auth_brokers
+ echo "Running no-acl, auth tests using brokers on ports $LOCAL_PORT, $LOCAL_PORTI, $LOCAL_PORTU, and $LOCAL_PORTQ"
+ $QPID_CONFIG_EXEC -a localhost:$LOCAL_PORT add exchange topic fed.topic
+ $QPID_CONFIG_EXEC -a localhost:$LOCAL_PORTI add exchange topic fed.topic
+ $QPID_ROUTE_EXEC dynamic add localhost:$LOCAL_PORT localhost:$LOCAL_PORTI fed.topic 2>/dev/null
+ sleep 2
+ stop_brokers
+ grep -q "must specify ACL create link rules" local.log
+ if [ $? -ne 0 ]
+ then
+ echo "Test fail - Broker with no ACL and --auth=yes file did not deny link creation";
+ return 1;
+ fi
+}
+
+if test -d ${PYTHON_DIR} ; then
+ # run acl.py test file
+ delete_directories
+ create_directories
+ populate_directories
+ delete_logfiles
+ start_brokers
+ echo "Running acl tests using brokers on ports $LOCAL_PORT, $LOCAL_PORTI, $LOCAL_PORTU, and $LOCAL_PORTQ"
+ $QPID_PYTHON_TEST -b localhost:$LOCAL_PORT -m acl -Dport-i=$LOCAL_PORTI -Dport-u=$LOCAL_PORTU -Dport-q=$LOCAL_PORTQ || EXITCODE=1
+ stop_brokers || EXITCODE=1
+ #
+ test_loading_acl_from_absolute_path || EXITCODE=1
+ #
+ test_noacl_deny_create_link || EXITCODE=1
+ delete_directories
+ exit $EXITCODE
+fi
+
diff --git a/qpid/cpp/src/tests/run_acl_tests.ps1 b/qpid/cpp/src/tests/run_acl_tests.ps1
new file mode 100644
index 0000000000..8279d87e54
--- /dev/null
+++ b/qpid/cpp/src/tests/run_acl_tests.ps1
@@ -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.
+#
+
+# Run the acl tests.
+
+$srcdir = Split-Path $myInvocation.InvocationName
+. .\test_env.ps1
+if (!(Test-Path $PYTHON_DIR -pathType Container)) {
+ "Skipping acl tests as python libs not found"
+ exit 1
+}
+
+$Global:BROKER_EXE = ""
+
+Function start_broker($acl_options)
+{
+ # Test runs from the tests directory but the broker executable is one level
+ # up, and most likely in a subdirectory from there based on what build type.
+ # Look around for it before trying to start it.
+ . $srcdir\find_prog.ps1 ..\qpidd.exe
+ if (!(Test-Path $prog)) {
+ "Cannot locate qpidd.exe"
+ exit 1
+ }
+ $Global:BROKER_EXE = $prog
+ if (Test-Path qpidd.port) {
+ Remove-Item qpidd.port
+ }
+ $cmdline = "$prog --auth=no --no-module-dir --port=0 --log-to-file qpidd.log $acl_options | foreach { set-content qpidd.port `$_ }"
+ $cmdblock = $executioncontext.invokecommand.NewScriptBlock($cmdline)
+ . $srcdir\background.ps1 $cmdblock
+ # Wait for the broker to start
+ $wait_time = 0
+ while (!(Test-Path qpidd.port) -and ($wait_time -lt 30)) {
+ Start-Sleep 2
+ $wait_time += 2
+ }
+ if (!(Test-Path qpidd.port)) {
+ "Timeout waiting for broker to start"
+ exit 1
+ }
+ set-item -path env:BROKER_PORT -value (get-content -path qpidd.port -totalcount 1)
+}
+
+Function stop_broker
+{
+ "Stopping $Global:BROKER_EXE"
+ Invoke-Expression "$Global:BROKER_EXE --no-module-dir -q --port $env:BROKER_PORT" | Write-Output
+ Remove-Item qpidd.port
+}
+
+$DATA_DIR = [IO.Directory]::GetCurrentDirectory() + "\data_dir"
+Remove-Item $DATA_DIR -recurse
+New-Item $DATA_DIR -type directory
+Copy-Item $srcdir\policy.acl $DATA_DIR
+start_broker("--data-dir $DATA_DIR --acl-file policy.acl")
+"Running acl tests using broker on port $env:BROKER_PORT"
+Invoke-Expression "python $PYTHON_DIR/qpid-python-test -m acl -b localhost:$env:BROKER_PORT" | Out-Default
+$RETCODE=$LASTEXITCODE
+stop_broker
+
+# Now try reading the acl file from an absolute path.
+Remove-Item qpidd.log
+$policy_full_path = "$srcdir\policy.acl"
+start_broker("--no-data-dir --acl-file $policy_full_path")
+#test_loading_acl_from_absolute_path(){
+# POLICY_FILE=$srcdir/policy.acl
+# rm -f temp.log
+# PORT=`../qpidd --daemon --port 0 --no-module-dir --no-data-dir --auth no --load-module $ACL_LIB --acl-file $POLICY_FILE -t --log-to-file temp.log 2>/dev/null`
+# ACL_FILE=`grep "notice Read ACL file" temp.log | sed 's/^.*Read ACL file //'`
+# $QPIDD_EXEC --no-module-dir -q --port $PORT
+# if test "$ACL_FILE" != "\"$POLICY_FILE\""; then
+# echo "unable to load policy file from an absolute path";
+# return 1;
+# fi
+# rm temp.log
+#}
+#
+# test_loading_acl_from_absolute_path || EXITCODE=1
+# rm -rf $DATA_DIR
+# exit $EXITCODE
+stop_broker
+exit $RETCODE
diff --git a/qpid/cpp/src/tests/run_cli_tests b/qpid/cpp/src/tests/run_cli_tests
new file mode 100755
index 0000000000..1db99001a4
--- /dev/null
+++ b/qpid/cpp/src/tests/run_cli_tests
@@ -0,0 +1,81 @@
+#!/usr/bin/env 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.
+#
+
+# Run the cli-utility tests.
+
+source ./test_env.sh
+CLI_DIR=$PYTHON_COMMANDS
+
+trap stop_brokers INT TERM QUIT
+
+# helper function to create test.xquery in the current directory, so
+# that the python test program can find it. yes, it leaves a turd.
+create_test_xquery() {
+ cat <<EOF > ./test.xquery
+ let \$w := ./weather
+ return \$w/station = 'Raleigh-Durham International Airport (KRDU)'
+ and \$w/temperature_f > 50
+ and \$w/temperature_f - \$w/dewpoint > 5
+ and \$w/wind_speed_mph > 7
+ and \$w/wind_speed_mph < 20
+EOF
+}
+
+start_brokers() {
+ # if the xml lib is present, use it. if not, disable any tests which
+ # look like they're xml related.
+ # if we start supporting xml on windows, it will need something similar
+ # here
+ if [ -f ../xml.so ] ; then
+ xargs="--load-module ../xml.so"
+ if [ ! -f test.xquery ] ; then
+ create_test_xquery
+ fi
+ targs=""
+ else
+ echo "Ignoring XML tests"
+ xargs=""
+ targs="--ignore=*xml*"
+ fi
+
+ ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-data-dir --no-module-dir --mgmt-publish no --auth no $xargs > qpidd.port
+ LOCAL_PORT=`cat qpidd.port`
+ ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-data-dir --no-module-dir --mgmt-publish no --auth no $xargs > qpidd.port
+ REMOTE_PORT=`cat qpidd.port`
+}
+
+stop_brokers() {
+ $QPIDD_EXEC --no-module-dir -q --port $LOCAL_PORT
+ $QPIDD_EXEC --no-module-dir -q --port $REMOTE_PORT
+}
+
+if test -d ${PYTHON_DIR} ; then
+ start_brokers
+ echo "Running CLI tests using brokers on ports $LOCAL_PORT $REMOTE_PORT"
+ PYTHON_TESTS=${PYTHON_TESTS:-$*}
+ $QPID_PYTHON_TEST -m cli_tests -b localhost:$LOCAL_PORT -Dremote-port=$REMOTE_PORT -Dcli-dir=$CLI_DIR $targs $PYTHON_TESTS $@
+ RETCODE=$?
+ stop_brokers
+ if test x$RETCODE != x0; then
+ echo "FAIL CLI tests"; exit 1;
+ fi
+fi
+
diff --git a/qpid/cpp/src/tests/run_federation_sys_tests b/qpid/cpp/src/tests/run_federation_sys_tests
new file mode 100755
index 0000000000..f5f1ae44d3
--- /dev/null
+++ b/qpid/cpp/src/tests/run_federation_sys_tests
@@ -0,0 +1,71 @@
+#!/usr/bin/env 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.
+#
+
+# Run the federation system tests.
+
+source ./test_env.sh
+
+MODULENAME=federation_sys
+
+# Test for long test
+if [[ "$1" == "LONG_TEST" ]]; then
+ USE_LONG_TEST=1
+ shift # get rid of this param so it is not treated as a test name
+fi
+
+trap stop_brokers INT TERM QUIT
+
+SKIPTESTS="-i federation_sys.E_* -i federation_sys.F_* -i federation_sys.G_* -i federation_sys.H_*"
+if [ -z ${USE_LONG_TEST} ]; then
+ SKIPTESTS="-i federation_sys.A_Long* -i federation_sys.B_Long* ${SKIPTESTS}"
+fi
+echo "WARNING: Tests using persistence will be ignored."
+SKIPTESTS="${SKIPTESTS} -i federation_sys.C_* -i federation_sys.D_*"
+
+start_brokers() {
+ start_broker() {
+ ${QPIDD_EXEC} --daemon --port 0 --interface 127.0.0.1 --auth no --no-data-dir $1 > qpidd.port
+ PORT=`cat qpidd.port`
+ eval "$2=${PORT}"
+ }
+ start_broker "" LOCAL_PORT
+ start_broker "" REMOTE_PORT
+ rm qpidd.port
+}
+
+stop_brokers() {
+ ${QPIDD_EXEC} -q --port ${LOCAL_PORT}
+ ${QPIDD_EXEC} -q --port ${REMOTE_PORT}
+}
+
+if test -d ${PYTHON_DIR} ; then
+ start_brokers
+ echo "Running federation tests using brokers on local port ${LOCAL_PORT}, remote port ${REMOTE_PORT} (NOTE: clustering is DISABLED)"
+ if [ -z ${USE_LONG_TEST} ]; then
+ echo "NOTE: To run a full set of federation system tests, use \"make check-long\". To test with persistence, run the store version of this script."
+ fi
+ ${QPID_PYTHON_TEST} -m ${MODULENAME} ${SKIPTESTS} -b localhost:${REMOTE_PORT} -Dlocal-port=${LOCAL_PORT} -Dremote-port=${REMOTE_PORT} $@
+ RETCODE=$?
+ stop_brokers
+ if test x${RETCODE} != x0; then
+ echo "FAIL federation tests"; exit 1;
+ fi
+fi
diff --git a/qpid/cpp/src/tests/run_federation_tests b/qpid/cpp/src/tests/run_federation_tests
new file mode 100755
index 0000000000..8cadd3702f
--- /dev/null
+++ b/qpid/cpp/src/tests/run_federation_tests
@@ -0,0 +1,61 @@
+#!/usr/bin/env 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.
+#
+
+# Run the federation tests.
+
+source ./test_env.sh
+#set -x
+trap stop_brokers INT TERM QUIT
+
+if [ -f ../xml.so ] ; then
+ MODULES="--load-module xml" # Load the XML exchange and run XML exchange federation tests
+ SKIPTESTS=
+else
+ MODULES="--no-module-dir"
+ SKIPTESTS='-i *_xml' # note: single quotes prevent expansion of *
+fi
+
+QPIDD_CMD="../qpidd --daemon --port 0 --interface 127.0.0.1 --no-data-dir $MODULES --auth no --log-enable=info+ --log-enable=debug+:Bridge --log-to-file"
+start_brokers() {
+ rm -f fed_local.log fed_remote.log fed_b1.log fed_b2.log
+ LOCAL_PORT=$($QPIDD_CMD fed_local.log --federation-tag LOCAL)
+ REMOTE_PORT=$($QPIDD_CMD fed_remote.log --federation-tag REMOTE)
+ REMOTE_B1=$($QPIDD_CMD fed_b1.log --federation-tag B1)
+ REMOTE_B2=$($QPIDD_CMD fed_b2.log --federation-tag B2)
+}
+
+stop_brokers() {
+ $QPIDD_EXEC $MODULES -q --port $LOCAL_PORT
+ $QPIDD_EXEC $MODULES -q --port $REMOTE_PORT
+ $QPIDD_EXEC $MODULES -q --port $REMOTE_B1
+ $QPIDD_EXEC $MODULES -q --port $REMOTE_B2
+}
+
+if test -d ${PYTHON_DIR} ; then
+ start_brokers
+ echo "Running federation tests using brokers on ports $LOCAL_PORT $REMOTE_PORT $REMOTE_B1 $REMOTE_B2"
+ $QPID_PYTHON_TEST -m federation ${SKIPTESTS} -b localhost:$LOCAL_PORT -Dremote-port=$REMOTE_PORT -Dextra-brokers="$REMOTE_B1 $REMOTE_B2" $@
+ RETCODE=$?
+ stop_brokers
+ if test x$RETCODE != x0; then
+ echo "FAIL federation tests"; exit 1;
+ fi
+fi
diff --git a/qpid/cpp/src/tests/run_federation_tests.ps1 b/qpid/cpp/src/tests/run_federation_tests.ps1
new file mode 100644
index 0000000000..803b3eef6f
--- /dev/null
+++ b/qpid/cpp/src/tests/run_federation_tests.ps1
@@ -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.
+#
+
+# Run the federation tests.
+
+$srcdir = Split-Path $myInvocation.InvocationName
+$PYTHON_DIR = "$srcdir\..\..\..\python"
+if (!(Test-Path $PYTHON_DIR -pathType Container)) {
+ "Skipping federation tests as python libs not found"
+ exit 1
+}
+
+. .\test_env.ps1
+
+# Test runs from the tests directory but the broker executable is one level
+# up, and most likely in a subdirectory from there based on what build type.
+# Look around for it before trying to start it.
+$subs = "Debug","Release","MinSizeRel","RelWithDebInfo"
+foreach ($sub in $subs) {
+ $prog = "..\$sub\qpidd.exe"
+ if (Test-Path $prog) {
+ break
+ }
+}
+if (!(Test-Path $prog)) {
+ "Cannot locate qpidd.exe"
+ exit 1
+}
+$cmdline = "$prog --auth=no --no-module-dir --no-data-dir --port=0 --ssl-port=0 --log-to-file qpidd.log $args | foreach { set-content qpidd.port `$_ }"
+$cmdblock = $executioncontext.invokecommand.NewScriptBlock($cmdline)
+
+function start_brokers {
+ # Start 2 brokers, saving the port numbers in LOCAL_PORT, REMOTE_PORT.
+ . $srcdir\background.ps1 $cmdblock
+ while (!(Test-Path qpidd.port)) {
+ Start-Sleep 2
+ }
+ set-item -path env:LOCAL_PORT -value (get-content -path qpidd.port -totalcount 1)
+ Remove-Item qpidd.port
+ . $srcdir\background.ps1 $cmdblock
+ while (!(Test-Path qpidd.port)) {
+ Start-Sleep 2
+ }
+ set-item -path env:REMOTE_PORT -value (get-content -path qpidd.port -totalcount 1)
+}
+
+function stop_brokers {
+ Invoke-Expression "$prog -q --port $env:LOCAL_PORT" | Out-Default
+ Invoke-Expression "$prog -q --port $env:REMOTE_PORT" | Out-Default
+}
+
+trap {
+ &stop_brokers
+ break
+}
+
+&start_brokers
+"Running federation tests using brokers on ports $env:LOCAL_PORT $env:REMOTE_PORT"
+$env:PYTHONPATH="$srcdir;$PYTHON_DIR;$PYTHON_TEST_DIR;$env:PYTHONPATH;$QMF_LIB"
+$tests = "*"
+Invoke-Expression "python $PYTHON_DIR/qpid-python-test -m federation -b localhost:$env:LOCAL_PORT -Dremote-port=$env:REMOTE_PORT $tests" | Out-Default
+$RETCODE=$LASTEXITCODE
+&stop_brokers
+if ($RETCODE -ne 0) {
+ "FAIL federation tests"
+ exit 1
+}
diff --git a/qpid/cpp/src/tests/run_ha_tests b/qpid/cpp/src/tests/run_ha_tests
new file mode 100755
index 0000000000..bb60bea076
--- /dev/null
+++ b/qpid/cpp/src/tests/run_ha_tests
@@ -0,0 +1,29 @@
+#!/usr/bin/env 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.
+#
+
+
+# Make sure the python tools are available. They will be if we are building in
+# a checkoug, they may not be in a distribution.
+test -d $PYTHON_COMMANDS -a -x $PYTHON_COMMANDS/qpid-ha -a -x $PYTHON_COMMANDS/qpid-config || { echo "Skipping HA tests, qpid-ha or qpid-config not available."; exit 0; }
+
+srcdir=`dirname $0`
+$srcdir/ha_tests.py
+
diff --git a/qpid/cpp/src/tests/run_header_test b/qpid/cpp/src/tests/run_header_test
new file mode 100755
index 0000000000..d1edcf6831
--- /dev/null
+++ b/qpid/cpp/src/tests/run_header_test
@@ -0,0 +1,31 @@
+#!/usr/bin/env 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.
+#
+
+# Simple test of encode/decode of a double in application headers
+# TODO: this should be expanded to cover a wider set of types and go
+# in both directions
+
+source $QPID_TEST_COMMON
+
+ensure_python_tests
+
+./header_test -p $QPID_PORT
+$srcdir/header_test.py "localhost" $QPID_PORT
diff --git a/qpid/cpp/src/tests/run_header_test.ps1 b/qpid/cpp/src/tests/run_header_test.ps1
new file mode 100644
index 0000000000..344fac9cf9
--- /dev/null
+++ b/qpid/cpp/src/tests/run_header_test.ps1
@@ -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.
+#
+
+# Simple test of encode/decode of a double in application headers
+# TODO: this should be expanded to cover a wider set of types and go
+# in both directions
+
+$srcdir = Split-Path $myInvocation.InvocationName
+$PYTHON_DIR = "$srcdir\..\..\..\python"
+if (!(Test-Path $PYTHON_DIR -pathType Container)) {
+ "Skipping header test as python libs not found"
+ exit 0
+}
+
+. .\test_env.ps1
+
+if (Test-Path qpidd.port) {
+ set-item -path env:QPID_PORT -value (get-content -path qpidd.port -totalcount 1)
+}
+
+# Test runs from the tests directory but the test executables are in a
+# subdirectory based on the build type. Look around for it before trying
+# to start it.
+. $srcdir\find_prog.ps1 .\header_test.exe
+if (!(Test-Path $prog)) {
+ "Cannot locate header_test.exe"
+ exit 1
+}
+
+Invoke-Expression "$prog -p $env:QPID_PORT" | Write-Output
+Invoke-Expression "python $srcdir/header_test.py localhost $env:QPID_PORT" | Write-Output
+exit $LASTEXITCODE
diff --git a/qpid/cpp/src/tests/run_headers_federation_tests b/qpid/cpp/src/tests/run_headers_federation_tests
new file mode 100644
index 0000000000..afbbf144ee
--- /dev/null
+++ b/qpid/cpp/src/tests/run_headers_federation_tests
@@ -0,0 +1,49 @@
+#!/bin/sh
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+# Run the federation tests for the Headers Exchange.
+
+source ./test_env.sh
+
+trap stop_brokers INT TERM QUIT
+
+start_brokers() {
+ ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-data-dir --no-module-dir --auth no > qpidd.port
+ LOCAL_PORT=`cat qpidd.port`
+ ../qpidd --daemon --port 0 --interface 127.0.0.1 --no-data-dir --no-module-dir --auth no > qpidd.port
+ REMOTE_PORT=`cat qpidd.port`
+}
+
+stop_brokers() {
+ $QPIDD_EXEC --no-module-dir -q --port $LOCAL_PORT
+ $QPIDD_EXEC --no-module-dir -q --port $REMOTE_PORT
+}
+
+if test -d ${PYTHON_DIR} ; then
+ start_brokers
+ echo "Running HeadersExchange federation tests using brokers on ports $LOCAL_PORT $REMOTE_PORT"
+ $QPID_PYTHON_TEST -m headers_federation -b localhost:$LOCAL_PORT -Dremote-port=$REMOTE_PORT $@
+ RETCODE=$?
+ stop_brokers
+ if test x$RETCODE != x0; then
+ echo "FAIL federation tests"; exit 1;
+ fi
+fi
diff --git a/qpid/cpp/src/tests/run_interlink_tests b/qpid/cpp/src/tests/run_interlink_tests
new file mode 100755
index 0000000000..71482fa7fd
--- /dev/null
+++ b/qpid/cpp/src/tests/run_interlink_tests
@@ -0,0 +1,26 @@
+#!/usr/bin/env 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.
+#
+
+test -e "$AMQP_LIB" || { echo "Skipping AMQP 1.0 based tests; AMQP 1.0 support not available."; exit 0; }
+
+srcdir=`dirname $0`
+$srcdir/interlink_tests.py
+
diff --git a/qpid/cpp/src/tests/run_long_federation_sys_tests b/qpid/cpp/src/tests/run_long_federation_sys_tests
new file mode 100644
index 0000000000..c2b4e02d81
--- /dev/null
+++ b/qpid/cpp/src/tests/run_long_federation_sys_tests
@@ -0,0 +1,24 @@
+#!/usr/bin/env 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.
+#
+
+# Run the federation system tests (long version).
+
+./run_federation_sys_tests LONG_TEST $@
diff --git a/qpid/cpp/src/tests/run_msg_group_tests b/qpid/cpp/src/tests/run_msg_group_tests
new file mode 100755
index 0000000000..ee479c23c7
--- /dev/null
+++ b/qpid/cpp/src/tests/run_msg_group_tests
@@ -0,0 +1,62 @@
+#!/usr/bin/env 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.
+#
+#script to run a sequence of message group queue tests via make
+
+source $QPID_TEST_COMMON
+
+ensure_python_tests
+
+QUEUE_NAME="group-queue"
+GROUP_KEY="My-Group-Id"
+
+BROKER_URL="${QPID_BROKER:-localhost}:${QPID_PORT:-5672}"
+
+run_test() {
+ "$@"
+}
+
+##set -x
+
+declare -i i=0
+declare -a tests
+tests=("qpid-config -b $BROKER_URL add queue $QUEUE_NAME --group-header=${GROUP_KEY} --shared-groups"
+ "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 103 --group-size 13 --receivers 2 --senders 3 --capacity 3 --ack-frequency 7 --randomize-group-size --interleave 3"
+ "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 103 --group-size 13 --receivers 2 --senders 3 --capacity 7 --ack-frequency 7 --randomize-group-size"
+ "qpid-config -b $BROKER_URL add queue ${QUEUE_NAME}-two --group-header=${GROUP_KEY} --shared-groups"
+ "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 103 --group-size 13 --receivers 2 --senders 3 --capacity 7 --ack-frequency 3 --randomize-group-size"
+ "msg_group_test -b $BROKER_URL -a ${QUEUE_NAME}-two --group-key $GROUP_KEY --messages 103 --group-size 13 --receivers 2 --senders 3 --capacity 3 --ack-frequency 7 --randomize-group-size --interleave 5"
+ "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 59 --group-size 5 --receivers 2 --senders 3 --capacity 1 --ack-frequency 3 --randomize-group-size"
+ "qpid-config -b $BROKER_URL del queue ${QUEUE_NAME}-two --force"
+ "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 59 --group-size 3 --receivers 2 --senders 3 --capacity 1 --ack-frequency 1 --randomize-group-size"
+ "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 211 --group-size 13 --receivers 2 --senders 3 --capacity 47 --ack-frequency 79 --interleave 53"
+ "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 10000 --group-size 1 --receivers 0 --senders 1"
+ "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 10000 --receivers 5 --senders 0"
+ "qpid-config -b $BROKER_URL del queue $QUEUE_NAME --force")
+
+while [ -n "${tests[i]}" ]; do
+ run_test ${tests[i]}
+ RETCODE=$?
+ if test x$RETCODE != x0; then
+ echo "FAILED message group test. Failed command: \"${tests[i]}\"";
+ exit 1;
+ fi
+ i+=1
+done
diff --git a/qpid/cpp/src/tests/run_msg_group_tests.ps1 b/qpid/cpp/src/tests/run_msg_group_tests.ps1
new file mode 100644
index 0000000000..e9cee0a5a0
--- /dev/null
+++ b/qpid/cpp/src/tests/run_msg_group_tests.ps1
@@ -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.
+#
+
+# Simple test of encode/decode of a double in application headers
+# TODO: this should be expanded to cover a wider set of types and go
+# in both directions
+
+$srcdir = Split-Path $myInvocation.InvocationName
+$PYTHON_DIR = "$srcdir\..\..\..\python"
+if (!(Test-Path $PYTHON_DIR -pathType Container)) {
+ "Skipping msg_group test as python libs not found"
+ exit 0
+}
+
+. .\test_env.ps1
+
+if (Test-Path qpidd.port) {
+ set-item -path env:QPID_PORT -value (get-content -path qpidd.port -totalcount 1)
+}
+
+# Test runs from the tests directory but the test executables are in a
+# subdirectory based on the build type. Look around for it before trying
+# to start it.
+. $srcdir\find_prog.ps1 .\msg_group_test.exe
+if (!(Test-Path $prog)) {
+ "Cannot locate msg_group_test.exe"
+ exit 1
+}
+
+$QUEUE_NAME="group-queue"
+$GROUP_KEY="My-Group-Id"
+$BROKER_URL="localhost:$env:QPID_PORT"
+
+$tests=@("python $QPID_CONFIG_EXEC -b $BROKER_URL add queue $QUEUE_NAME --group-header=${GROUP_KEY} --shared-groups",
+ "$prog -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 103 --group-size 13 --receivers 2 --senders 3 --capacity 3 --ack-frequency 7 --randomize-group-size --interleave 3",
+ "$prog -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 103 --group-size 13 --receivers 2 --senders 3 --capacity 7 --ack-frequency 7 --randomize-group-size",
+ "python $QPID_CONFIG_EXEC -b $BROKER_URL add queue ${QUEUE_NAME}-two --group-header=${GROUP_KEY} --shared-groups",
+ "$prog -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 103 --group-size 13 --receivers 2 --senders 3 --capacity 7 --ack-frequency 3 --randomize-group-size",
+ "$prog -b $BROKER_URL -a ${QUEUE_NAME}-two --group-key $GROUP_KEY --messages 103 --group-size 13 --receivers 2 --senders 3 --capacity 3 --ack-frequency 7 --randomize-group-size --interleave 5",
+ "$prog -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 59 --group-size 5 --receivers 2 --senders 3 --capacity 1 --ack-frequency 3 --randomize-group-size",
+ "python $QPID_CONFIG_EXEC -b $BROKER_URL del queue ${QUEUE_NAME}-two --force",
+ "$prog -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 59 --group-size 3 --receivers 2 --senders 3 --capacity 1 --ack-frequency 1 --randomize-group-size",
+ "$prog -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 211 --group-size 13 --receivers 2 --senders 3 --capacity 47 --ack-frequency 79 --interleave 53",
+ "$prog -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 10000 --group-size 1 --receivers 0 --senders 1",
+ "$prog -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 10000 --receivers 5 --senders 0",
+ "python $QPID_CONFIG_EXEC -b $BROKER_URL del queue $QUEUE_NAME --force")
+
+foreach ($cmd in $tests)
+{
+ Invoke-Expression "$cmd" | Write-Output
+ $ret = $LASTEXITCODE
+ if ($ret -ne 0) {Write-Host "FAILED message group test. Failed command: $cmd"
+ break}
+}
+exit $ret
diff --git a/qpid/cpp/src/tests/run_msg_group_tests_soak b/qpid/cpp/src/tests/run_msg_group_tests_soak
new file mode 100755
index 0000000000..d87ca16c88
--- /dev/null
+++ b/qpid/cpp/src/tests/run_msg_group_tests_soak
@@ -0,0 +1,63 @@
+#!/usr/bin/env 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.
+#
+#script to run a sequence of long-running message group tests via make
+
+#setup path to find qpid-config and msg_group_test test progs
+source ./test_env.sh
+test -d $PYTHON_DIR || { echo "Skipping message group tests, no python dir."; exit 0; }
+
+export PATH=$PWD:$srcdir:$PYTHON_COMMANDS:$PATH
+
+#set port to connect to via env var
+test -s qpidd.port && QPID_PORT=`cat qpidd.port`
+
+#trap cleanup INT TERM QUIT
+
+QUEUE_NAME="group-queue"
+GROUP_KEY="My-Group-Id"
+
+BROKER_URL="${QPID_BROKER:-localhost}:${QPID_PORT:-5672}"
+
+run_test() {
+ $@
+}
+
+##set -x
+
+declare -i i=0
+declare -a tests
+tests=("qpid-config -b $BROKER_URL add queue $QUEUE_NAME --group-header=${GROUP_KEY} --shared-groups"
+ "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 10007 --receivers 3 --senders 5 --group-size 211 --randomize-group-size --capacity 47 --ack-frequency 97"
+ "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 10007 --receivers 3 --senders 5 --group-size 211 --randomize-group-size --capacity 79 --ack-frequency 79"
+ "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 10007 --receivers 3 --senders 5 --group-size 211 --randomize-group-size --capacity 97 --ack-frequency 47"
+ "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 40000 --receivers 0 --senders 5 --group-size 13 --randomize-group-size"
+ "msg_group_test -b $BROKER_URL -a $QUEUE_NAME --group-key $GROUP_KEY --messages 200000 --receivers 3 --senders 0 --capacity 23 --ack-frequency 7"
+ "qpid-config -b $BROKER_URL del queue $QUEUE_NAME --force")
+
+while [ -n "${tests[i]}" ]; do
+ run_test ${tests[i]}
+ RETCODE=$?
+ if test x$RETCODE != x0; then
+ echo "FAILED message group test. Failed command: \"${tests[i]}\"";
+ exit 1;
+ fi
+ i+=1
+done
diff --git a/qpid/cpp/src/tests/run_paged_queue_tests b/qpid/cpp/src/tests/run_paged_queue_tests
new file mode 100755
index 0000000000..2c1e3ae614
--- /dev/null
+++ b/qpid/cpp/src/tests/run_paged_queue_tests
@@ -0,0 +1,50 @@
+#!/usr/bin/env 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.
+#
+
+#setup path to find qpid-config and sender/receiver test progs
+source ./test_env.sh
+trap stop_broker INT TERM QUIT
+
+export PATH=$PWD:$srcdir:$PYTHON_COMMANDS:$PATH
+
+start_broker() {
+ QPID_PORT=$($QPIDD_EXEC --daemon --port 0 --interface 127.0.0.1 --no-data-dir --paging-dir=$PWD/pqtest_data $MODULES --auth no) || { echo "Could not start broker"; exit 1; }
+}
+
+stop_broker() {
+ $QPIDD_EXEC -q --port $QPID_PORT
+}
+
+test_single_page() {
+ msgcount=1000
+ qpid-send --messages $msgcount --content-size 1024 --broker "localhost:$QPID_PORT" --address "onepage; {create: always, node:{x-declare:{arguments:{'qpid.paging':True,'qpid.max_pages_loaded':1}}}}"
+ received=$(qpid-receive --address onepage --broker "localhost:$QPID_PORT" --messages $msgcount | wc -l)
+ if [[ $received -ne $msgcount ]]; then
+ echo "single page test failed: received $received messages, expected $msgcount"
+ exit 1
+ fi
+}
+
+start_broker
+test_single_page
+qpid-cpp-benchmark --broker "localhost:$QPID_PORT" --create-option "node:{x-declare:{arguments:{'qpid.paging':True,'qpid.max_size':0,'qpid.max_count':0,'qpid.flow_stop_size':0,'qpid.flow_resume_size':0,'qpid.flow_stop_count':0,'qpid.flow_resume_count':0}}}"
+qpid-cpp-benchmark --broker "localhost:$QPID_PORT" --create-option "node:{x-declare:{arguments:{'qpid.paging':True,'qpid.max_size':0,'qpid.max_count':0,'qpid.flow_stop_size':0,'qpid.flow_resume_size':0,'qpid.flow_stop_count':0,'qpid.flow_resume_count':0}}}" --fill-drain
+stop_broker
diff --git a/qpid/cpp/src/tests/run_perftest b/qpid/cpp/src/tests/run_perftest
new file mode 100755
index 0000000000..2fadc6cc62
--- /dev/null
+++ b/qpid/cpp/src/tests/run_perftest
@@ -0,0 +1,28 @@
+#!/usr/bin/env 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.
+#
+
+# Args: count [qpid-perftest options...]
+# Run a qpid-perftest with count multiplied.
+#
+MULTIPLIER=3
+COUNT=`expr $1 \* $MULTIPLIER`
+shift
+exec `dirname $0`/run_test ./qpid-perftest --summary --count $COUNT "$@"
diff --git a/qpid/cpp/src/tests/run_queue_flow_limit_tests b/qpid/cpp/src/tests/run_queue_flow_limit_tests
new file mode 100755
index 0000000000..55b3e5d4c5
--- /dev/null
+++ b/qpid/cpp/src/tests/run_queue_flow_limit_tests
@@ -0,0 +1,27 @@
+#!/usr/bin/env 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.
+#
+
+source $QPID_TEST_COMMON
+
+ensure_python_tests
+
+# Run tests against Queue producer flow control.
+$QPID_PYTHON_TEST -m queue_flow_limit_tests $SKIPTESTS -b localhost:$QPID_PORT
diff --git a/qpid/cpp/src/tests/run_queue_redirect b/qpid/cpp/src/tests/run_queue_redirect
new file mode 100755
index 0000000000..3a0ae5118a
--- /dev/null
+++ b/qpid/cpp/src/tests/run_queue_redirect
@@ -0,0 +1,56 @@
+#!/usr/bin/env 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.
+#
+
+# Run the queue redirect. $srcdir is set by the Makefile.
+source ./test_env.sh
+DATA_DIR=`pwd`/data_dir
+
+trap stop_brokers INT TERM QUIT
+
+start_brokers() {
+ $QPIDD_EXEC --daemon \
+ --port 0 --interface 127.0.0.1 \
+ --no-module-dir \
+ --data-dir $DATA_DIR \
+ --acl-file policy.acl \
+ --auth no \
+ --log-to-file queue_redirect.log \
+ --log-enable info+ \
+ --log-enable trace+:Model \
+ --log-enable trace+ > qpidd.port
+ LOCAL_PORT=`cat qpidd.port`
+}
+
+stop_brokers() {
+ $QPIDD_EXEC --no-module-dir -q --port $LOCAL_PORT
+}
+
+if test -d ${PYTHON_DIR} ; then
+ rm -f queue_redirect.log
+ rm -rf $DATA_DIR
+ mkdir -p $DATA_DIR
+ cp $srcdir/policy.acl $DATA_DIR
+ start_brokers
+ echo "Running queue redirect tests using broker on port $LOCAL_PORT"
+ $QPID_PYTHON_TEST -b localhost:$LOCAL_PORT -m queue_redirect
+ stop_brokers || EXITCODE=1
+ exit $EXITCODE
+fi
diff --git a/qpid/cpp/src/tests/run_ring_queue_test b/qpid/cpp/src/tests/run_ring_queue_test
new file mode 100755
index 0000000000..69497f9872
--- /dev/null
+++ b/qpid/cpp/src/tests/run_ring_queue_test
@@ -0,0 +1,36 @@
+#!/usr/bin/env 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.
+#
+#script to run a sequence of ring queue tests via make
+
+#setup path to find qpid-config and sender/receiver test progs
+source ./test_env.sh
+
+export PATH=$PWD:$srcdir:$PYTHON_COMMANDS:$PATH
+
+#set port to connect to via env var
+test -s qpidd.port && QPID_PORT=`cat qpidd.port`
+export QPID_PORT
+
+ring_queue_test -c -s 4 -r 4
+ring_queue_test -s 4 -r 0
+ring_queue_test -s 1 -r 1
+
+
diff --git a/qpid/cpp/src/tests/run_store_tests.ps1 b/qpid/cpp/src/tests/run_store_tests.ps1
new file mode 100644
index 0000000000..0683892393
--- /dev/null
+++ b/qpid/cpp/src/tests/run_store_tests.ps1
@@ -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.
+#
+
+# Run the store tests.
+# There are two sets of tests:
+# 1. A subset of the normal broker python tests, dtx and persistence, but
+# run again with the desired store loaded.
+# 2. store.py, which tests recovering things across broker restarts.
+
+$test_store = $args[0]
+if ($test_store -ne "MSSQL" -and $test_store -ne "MSSQL-CLFS") {
+ "Invalid store test type $test_store - must be MSSQL or MSSQL-CLFS"
+ exit 1
+}
+
+$srcdir = Split-Path $myInvocation.InvocationName
+
+. .\test_env.ps1
+
+if (!(Test-Path $PYTHON_DIR -pathType Container)) {
+ "Skipping store tests as python libs not found"
+ exit 1
+}
+
+# Test runs from the tests directory but the broker executable is one level
+# up, and most likely in a subdirectory from there based on what build type.
+# Look around for it before trying to start it.
+$subs = "Debug","Release","MinSizeRel","RelWithDebInfo"
+foreach ($sub in $subs) {
+ $prog = "..\$sub\qpidd.exe"
+ if (Test-Path $prog) {
+ break
+ }
+}
+if (!(Test-Path $prog)) {
+ "Cannot locate qpidd.exe"
+ exit 1
+}
+
+# The store to test is the same build type as the broker.
+$store_dir = "..\qpid\store\$sub"
+if (!([string]::Compare($sub, "Debug", $True))) {
+ $suffix = "d"
+}
+
+$stamp = Get-Date -format %dMMMyyyy_HHmmss
+$env:STORE_LIB="$store_dir\store$suffix.dll"
+if ($test_store -eq "MSSQL") {
+ $test_store_module="$store_dir\mssql_store$suffix.dll"
+ $env:STORE_SQL_LIB=$test_store_module
+ $env:STORE_CATALOG="store_recovery_sql_test_$stamp"
+ $cat1="store_sql_test_$stamp"
+ $out = "sql_store_test_$stamp"
+}
+else {
+ $test_store_module="$store_dir\msclfs_store$suffix.dll"
+ $env:STORE_SQL_CLFS_LIB=$test_store_module
+ $env:STORE_CATALOG="store_recovery_clfs_test_$stamp"
+ $cat1="store_clfs_test_$stamp"
+ $out = "clfs_store_test_$stamp"
+}
+
+$FAILCODE = 0
+
+# Test 1... re-run some of the regular python broker tests against a broker
+# with the store module loaded.
+$cmdline = "$prog --auth=no --port=0 --log-to-file qpidd-store.log --no-module-dir --load-module $env:STORE_LIB --load-module $test_store_module --catalog $cat1 | foreach { set-content qpidd-store.port `$_ }"
+$cmdblock = $executioncontext.invokecommand.NewScriptBlock($cmdline)
+. $srcdir\background.ps1 $cmdblock
+
+$wait_time = 0
+while (!(Test-Path qpidd-store.port) -and ($wait_time -lt 90)) {
+ Start-Sleep 2
+ $wait_time += 2
+}
+if (!(Test-Path qpidd-store.port)) {
+ "Time out waiting for broker to start"
+ exit 1
+}
+set-item -path env:QPID_PORT -value (get-content -path qpidd-store.port -totalcount 1)
+Remove-Item qpidd-store.port
+
+$PYTHON_TEST_DIR = "$srcdir\..\..\..\tests\src\py\qpid_tests\broker_0_10"
+$env:PYTHONPATH="$PYTHON_TEST_DIR;$srcdir;$env:PYTHONPATH"
+python $PYTHON_DIR/qpid-python-test -m dtx -m persistence -b localhost:$env:QPID_PORT $fails $tests
+$RETCODE=$LASTEXITCODE
+if ($RETCODE -ne 0) {
+ $FAILCODE = 1
+}
+
+# Piping the output makes the script wait for qpidd to finish.
+Invoke-Expression "$prog --quit --port $env:QPID_PORT" | Write-Output
+
+
+# Test 2... store.py starts/stops/restarts its own brokers
+
+$tests = "*"
+$env:QPIDD_EXEC="$prog"
+$env:STORE_LIB="$store_dir\store$suffix.dll"
+if ($test_store -eq "MSSQL") {
+ $env:STORE_SQL_LIB="$store_dir\mssql_store$suffix.dll"
+ $env:STORE_CATALOG="store_recovery_sql_test_$stamp"
+ $out = "sql_store_test_$stamp"
+}
+else {
+ $env:STORE_SQL_CLFS_LIB="$store_dir\msclfs_store$suffix.dll"
+ $env:STORE_CATALOG="store_recovery_clfs_test_$stamp"
+ $out = "clfs_store_test_$stamp"
+}
+Invoke-Expression "python $PYTHON_DIR/qpid-python-test -m store -D OUTDIR=$out $tests" | Out-Default
+$RETCODE=$LASTEXITCODE
+if ($RETCODE -ne 0) {
+ "FAIL $test_store store tests"
+ $FAILCODE = 1
+}
+exit $FAILCODE
diff --git a/qpid/cpp/src/tests/run_test b/qpid/cpp/src/tests/run_test
new file mode 100755
index 0000000000..8e397b3458
--- /dev/null
+++ b/qpid/cpp/src/tests/run_test
@@ -0,0 +1,191 @@
+#!/usr/bin/env bash
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+#
+# Set up environment and run a test executable or script.
+#
+# Output nothing if test passes, show the output if it fails and
+# leave output in <test>.log for examination.
+#
+# If qpidd.port exists and is not empty run test with QPID_PORT=`cat qpidd.port`
+#
+# If $VALGRIND if is set run under valgrind. If there are valgrind
+# erros show valgrind output, also leave it in <test>.valgrind for
+# examination.
+#
+
+wrapper="Qpid Test Wrapper"
+function usage {
+ echo "Usage:"
+ echo " -workingDir DIR"
+ echo " -buildDir DIR"
+ echo " -sourceDir DIR"
+ echo " -python - run python script"
+ echo " -boostTest - run boost unit test"
+ echo " -xml - XML output from tests"
+ echo " -startBroker - start/stop broker before/after test"
+ echo " -brokerOptions - use these extra options when starting broker"
+ echo " -help - print this message"
+ echo " -- - This is required to separate the wrapped command"
+ echo " from the test parameters"
+}
+
+function illegal_option {
+ echo "$wrapper: $1 is not an accepted option"
+ usage >&2
+}
+
+function no_command {
+ echo "$wrapper: No wrapped command specified"
+ usage >&2
+}
+
+function ignored_argument {
+ echo "Ignored argument: $1" >&2
+}
+
+working_dir='.'
+
+while true; do
+case "$1" in
+ --) shift; break ;;
+ # Split up any parameters expressed as -blah=foo
+ # and process them next time round the loop
+ -*=*) option=${1%%=*}; param=${1#*=}
+ shift;
+ set -- "$option" "$param" "$@" ;;
+ -workingDir) working_dir=$2; shift 2 ;;
+ -buildDir) build_dir=$2; shift 2 ;;
+ -sourceDir) source_dir=$2; shift 2 ;;
+ -python) run_python=yes; shift ;;
+ -boostTest) boost_test=yes; shift ;;
+ -xml) xml_output=yes; shift ;;
+ -startBroker) start_broker=yes; shift ;;
+ -brokerOptions) qpidd_extra_options=$2; shift 2 ;;
+ -help) usage; exit 0; ;;
+ -*) illegal_option "$1"; exit 1; ;;
+ '') no_command; exit 1; ;;
+ *) ignored_argument "$1"; shift; ;;
+esac
+done
+
+program=$1
+shift
+
+logfilebase=$(pwd -P)/$(basename $program)
+source $build_dir/src/tests/test_env.sh || (echo "Error: Couldn't read test_env.sh (build settings)" ; exit 1)
+source $srcdir/vg_check
+
+# Allow environment to dictate if we output xml test results
+if [ -n "$QPID_XML_TEST_OUTPUT" ] ; then
+ xml_output=yes
+fi
+
+# Use VALGRIND_OPTS="--gen-suppressions=all" to generated suppressions
+VALGRIND_OPTS="$VALGRIND_OPTS
+--leak-check=full
+--demangle=yes
+--suppressions=$srcdir/.valgrind.supp
+--num-callers=25
+"
+
+# Set up environment for running a Qpid test
+if [ -n "$start_broker" ] ; then
+ qpidd_command="$QPIDD_EXEC --auth=no --no-module-dir --daemon --port=0 --interface 127.0.0.1 --log-to-file $logfilebase-qpidd.log $qpidd_extra_options"
+ if [ -n "$VALGRIND" ] ; then
+ if [ -n "$xml_output" ] ; then
+ QPID_PORT=$($VALGRIND $VALGRIND_OPTS --xml=yes --xml-file=$logfilebase-qpidd-vg.xml -- $qpidd_command)
+ else
+ QPID_PORT=$($VALGRIND $VALGRIND_OPTS --log-file=$logfilebase-qpidd.vglog -- $qpidd_command)
+ fi
+ else
+ QPID_PORT=$($qpidd_command)
+ fi
+elif [ -r qpidd.port ]; then
+ QPID_PORT=$(cat qpidd.port)
+fi
+export QPID_PORT
+QPID_LOG_TO_FILE="$logfilebase.log"
+export QPID_LOG_TO_FILE
+
+# Export variables from makefile.
+export srcdir
+
+if [ -n "$VALGRIND" ] ; then
+ if [ -n "$xml_output" ] ; then
+ valgrind_command="$VALGRIND $VALGRIND_OPTS --xml=yes --xml-file=$logfilebase-vg.xml --"
+ else
+ VG_LOG="$logfilebase.vglog"
+ rm -f $VG_LOG*
+ valgrind_command="$VALGRIND $VALGRIND_OPTS --log-file=$VG_LOG --"
+ fi
+fi
+
+ERROR=0
+if [ -n "$run_python" -a -n "$PYTHON" ] ; then
+ (cd $working_dir; $PYTHON $program "$@") || ERROR=1
+elif [ ! -x $program ] ; then
+ echo "Cannot execute $program"
+ ERROR=1
+elif file $program | grep -q ELF; then
+ if [ -n "$boost_test" ] ; then
+ # Set boost unit test environment
+ if [ -n "$xml_output" ] ; then
+ export BOOST_TEST_SHOW_PROGRESS=no
+ export BOOST_TEST_OUTPUT_FORMAT=XML
+ export BOOST_TEST_LOG_LEVEL=test_suite
+ export BOOST_TEST_REPORT_LEVEL=no
+ (cd $working_dir; $valgrind_command $program "$@") > $logfilebase-unittest.xml || ERROR=1
+ else
+ (cd $working_dir; $valgrind_command $program "$@") || ERROR=1
+ fi
+ else
+ # This is a real executable, valgrind it if required
+ # Hide output unless there's an error.
+ (cd $working_dir; $valgrind_command $program "$@" 2>&1) || ERROR=1
+ fi
+ if [ -n "$VG_LOG" ] ; then
+ vg_check $VG_LOG* || ERROR=1
+ fi
+else
+ (cd $working_dir; $program "$@") || ERROR=1
+fi
+
+# Check log
+if [ -r $QPID_LOG_TO_FILE ]; then
+egrep 'warning\|error\|critical' $QPID_LOG_TO_FILE && {
+ echo "WARNING: Suspicious log entries in $QPID_LOG_TO_FILE, above."
+}
+fi
+
+if [ -n "$start_broker" ] ; then
+ $QPIDD_EXEC --no-module-dir --quit || ERROR=1
+
+ # Check qpidd.log.
+ egrep 'warning\|error\|critical' $logfilebase-qpidd.log && {
+ echo "WARNING: Suspicious broker log entries in qpidd.log, above."
+ }
+
+ # Check valgrind log.
+ if [ -n "$VALGRIND" -a -z "$xml_output" ] ; then
+ vg_check $logfilebase-qpidd.vglog || ERROR=1
+ fi
+fi
+exit $ERROR
diff --git a/qpid/cpp/src/tests/run_test.ps1 b/qpid/cpp/src/tests/run_test.ps1
new file mode 100644
index 0000000000..ff103e4556
--- /dev/null
+++ b/qpid/cpp/src/tests/run_test.ps1
@@ -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.
+#
+
+param(
+ [string]$workingDir = $pwd,
+ [string]$buildDir = $(throw "-buildDir is required"),
+ [string]$sourceDir,
+ [switch]$python = $false,
+ [switch]$boostTest = $false,
+ [switch]$xml,
+ [switch]$startBroker = $false,
+ [string]$brokerOptions,
+ [switch]$help,
+ [Parameter(Mandatory=$true, ValueFromRemainingArguments=$true, Position=0)]
+ [String[]]$rest
+ )
+
+if ([string]::IsNullOrEmpty($sourceDir)) {
+ $sourceDir = Split-Path $myInvocation.InvocationName
+}
+
+if ([string]::IsNullOrEmpty($xml)) {
+ $xml = Test-Path variable:global:QPID_XML_TEST_OUTPUT
+}
+
+# Set up environment and run a test executable or script.
+. .\test_env.ps1
+
+if ($rest[0] -eq $null) {
+ "No wrapped command specified"
+ exit 1
+}
+# The test exe is probably not in the current binary dir - it's usually
+# placed in a subdirectory based on the configuration built in Visual Studio.
+# So check around to see where it is - when located, set the QPID_LIB_DIR
+# and PATH to look in the corresponding configuration off the src directory,
+# one level up.
+$prog = $rest[0]
+$logfilebase = [System.IO.Path]::GetFileNameWithoutExtension($prog)
+$logfilebase = "$pwd\\$logfilebase"
+# Qpid client lib sees QPID_LOG_TO_FILE; acts like using --log-to-file on
+# command line.
+$env:QPID_LOG_TO_FILE = "$logfilebase.log"
+$is_script = $prog -match ".ps1$"
+if (($is_script -or $python) -and !(Test-Path "$prog")) {
+ "$prog does not exist"
+ exit 1
+}
+if (!$is_script -and !(Test-Path "$prog")) {
+ . $sourceDir\find_prog.ps1 $prog
+ $rest[0] = $prog
+ $env:QPID_LIB_DIR = "..\$sub"
+}
+
+# Set up environment for running a Qpid test. If a broker should be started,
+# do that, else check for a saved port number to use.
+if ($startBroker) {
+ $broker = new-object System.Diagnostics.ProcessStartInfo
+ $broker.WorkingDirectory = $pwd
+ $broker.UseShellExecute = $false
+ $broker.CreateNoWindow = $true
+ $broker.RedirectStandardOutput = $true
+ $broker.FileName = $env:QPIDD_EXEC
+ $broker.Arguments = "--auth=no --no-module-dir --port=0 --interface 127.0.0.1 --log-to-file $logfilebase-qpidd.log $brokerOptions"
+ $broker_process = [System.Diagnostics.Process]::Start($broker)
+ $env:QPID_PORT = $broker_process.StandardOutput.ReadLine()
+}
+else {
+ # If qpidd.port exists and is not empty run test with QPID_PORT set.
+ if (Test-Path qpidd.port) {
+ set-item -path env:QPID_PORT -value (get-content -path qpidd.port -totalcount 1)
+ }
+}
+
+# Now start the real test.
+if ($python) {
+ $to_run = $PYTHON_EXE
+ $skip_args0 = $false
+ $outputfile = ""
+}
+elseif ($boostTest) {
+ if ($xml) {
+ $env:BOOST_TEST_SHOW_PROGRESS=no
+ $env:BOOST_TEST_OUTPUT_FORMAT=XML
+ $env:BOOST_TEST_LOG_LEVEL=test_suite
+ $env:BOOST_TEST_REPORT_LEVEL=no
+ $to_run = $rest[0]
+ $skip_args0 = $true
+ $outputfile = "$logfilebase-unittest.xml"
+ }
+ else {
+ $to_run = $rest[0]
+ $skip_args0 = $true
+ $outputfile = ""
+ }
+}
+else {
+ # Non-boost executable or powershell script
+ $outputfile = ""
+ if ($is_script) {
+ $to_run = (get-command powershell.exe).Definition
+ $skip_args0 = $false
+ }
+ else {
+ $to_run = $rest[0]
+ $skip_args0 = $true
+ }
+}
+
+if ($skip_args0) {
+ $arglist = $rest[1..($rest.length-1)]
+}
+else {
+ $arglist = $rest
+}
+
+if ($outputfile -eq "") {
+ $p = Start-Process -FilePath $to_run -ArgumentList $arglist -NoNewWindow -PassThru
+ $line = ""
+}
+else {
+ $p = Start-Process -FilePath $to_run -ArgumentList $arglist -NoNewWindow -RedirectStandardOutput $outputfile -PassThru
+}
+Wait-Process -InputObject $p
+$status = $p.ExitCode
+
+if (Test-Path $env:QPID_LOG_TO_FILE) {
+ $problems = Select-String -Path $env:QPID_LOG_TO_FILE -pattern " error ", " warning ", " critical "
+ if ($problems -ne $null) {
+ "WARNING: suspicious log entries in $env:QPID_LOG_TO_FILE:\n$problems"
+ $status = 1
+ }
+}
+
+# If a broker was started, stop it.
+if ($startBroker) {
+ & $env:QPIDD_EXEC --no-module-dir --quit
+ # Check qpid log for problems
+ $problems = Select-String -Path $logfilebase-qpidd.log -pattern " error ", " warning ", " critical "
+ if ($problems -ne $null) {
+ "WARNING: suspicious log entries in $logfilebase-qpidd.log:\n$problems"
+ $status = 1
+ }
+}
+
+exit $status
diff --git a/qpid/cpp/src/tests/sasl_fed b/qpid/cpp/src/tests/sasl_fed
new file mode 100755
index 0000000000..38ef43f56f
--- /dev/null
+++ b/qpid/cpp/src/tests/sasl_fed
@@ -0,0 +1,169 @@
+#!/usr/bin/env 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.
+#
+
+# This minimum value corresponds to sasl version 2.1.22
+minimum_sasl_version=131350
+
+sasl_version=`$QPID_TEST_EXEC_DIR/sasl_version`
+
+# This test is necessary becasue this sasl version is the first one that permits
+# redirection of the sasl config file path.
+if [ "$sasl_version" -lt "$minimum_sasl_version" ]; then
+ echo "sasl_fed: must have sasl version 2.1.22 or greater. ( Integer value: $minimum_sasl_version ) Version is: $sasl_version"
+ exit 0
+fi
+
+# In a distribution, the python tools will be absent.
+if [ ! -f $QPID_CONFIG_EXEC ] || [ ! -f $QPID_ROUTE_EXEC ] ; then
+ echo "python tools absent - skipping sasl_fed."
+ exit 0
+fi
+
+
+sasl_config_file=$QPID_TEST_EXEC_DIR/sasl_config
+
+my_random_number=$RANDOM
+tmp_root=/tmp/sasl_fed_$my_random_number
+mkdir -p $tmp_root
+
+# create ACL file to allow links
+echo acl allow all all > $tmp_root/sasl_fed.acl
+
+
+#--------------------------------------------------
+#echo " Starting broker 1"
+#--------------------------------------------------
+$QPIDD_EXEC \
+ -p 0 --interface 127.0.0.1 \
+ --data-dir $tmp_root/data_1 \
+ --auth=yes \
+ --mgmt-enable=yes \
+ --log-enable info+ \
+ --log-source yes \
+ --log-to-file $tmp_root/qpidd_1.log \
+ --sasl-config=$sasl_config_file \
+ --acl-file $tmp_root/sasl_fed.acl \
+ -d > $tmp_root/broker_1_port
+
+broker_1_port=`cat $tmp_root/broker_1_port`
+
+
+#--------------------------------------------------
+#echo " Starting broker 2"
+#--------------------------------------------------
+$QPIDD_EXEC \
+ -p 0 --interface 127.0.0.1 \
+ --data-dir $tmp_root/data_2 \
+ --auth=yes \
+ --mgmt-enable=yes \
+ --log-enable info+ \
+ --log-source yes \
+ --log-to-file $tmp_root/qpidd_2.log \
+ --sasl-config=$sasl_config_file \
+ --acl-file $tmp_root/sasl_fed.acl \
+ -d > $tmp_root/broker_2_port
+
+broker_2_port=`cat $tmp_root/broker_2_port`
+
+sleep 2
+
+# I am not randomizing these names, because the test creates its own brokers.
+QUEUE_NAME=sasl_fed_queue
+ROUTING_KEY=sasl_fed_queue
+EXCHANGE_NAME=sasl_fedex
+
+#--------------------------------------------------
+#echo " add exchanges"
+#--------------------------------------------------
+$QPID_CONFIG_EXEC -b localhost:$broker_1_port add exchange direct $EXCHANGE_NAME
+$QPID_CONFIG_EXEC -b localhost:$broker_2_port add exchange direct $EXCHANGE_NAME
+
+
+#--------------------------------------------------
+#echo " add queues"
+#--------------------------------------------------
+$QPID_CONFIG_EXEC -b localhost:$broker_1_port add queue $QUEUE_NAME
+$QPID_CONFIG_EXEC -b localhost:$broker_2_port add queue $QUEUE_NAME
+
+sleep 5
+
+#--------------------------------------------------
+#echo " create bindings"
+#--------------------------------------------------
+$QPID_CONFIG_EXEC -b localhost:$broker_1_port bind $EXCHANGE_NAME $QUEUE_NAME $ROUTING_KEY
+$QPID_CONFIG_EXEC -b localhost:$broker_2_port bind $EXCHANGE_NAME $QUEUE_NAME $ROUTING_KEY
+
+sleep 5
+
+
+#--------------------------------------------------
+#echo " qpid-route route add"
+#--------------------------------------------------
+$QPID_ROUTE_EXEC route add zag/zag@localhost:$broker_2_port zag/zag@localhost:$broker_1_port $EXCHANGE_NAME $ROUTING_KEY "" "" DIGEST-MD5
+
+sleep 5
+
+
+n_messages=100
+#--------------------------------------------------
+#echo " Sending 100 messages to $broker_1_port "
+#--------------------------------------------------
+$QPID_TEST_EXEC_DIR/datagen --count $n_messages | $SENDER_EXEC --mechanism DIGEST-MD5 --username zag --password zag --exchange $EXCHANGE_NAME --routing-key $ROUTING_KEY --port $broker_1_port
+
+sleep 5
+
+#--------------------------------------------------
+#echo " Examine Broker $broker_1_port"
+#--------------------------------------------------
+broker_1_message_count=`$PYTHON_COMMANDS/qpid-stat -q -b localhost:$broker_1_port | grep sasl_fed_queue | awk '{print $2}'`
+#echo " "
+
+#--------------------------------------------------
+#echo " Examine Broker $broker_2_port"
+#--------------------------------------------------
+broker_2_message_count=`$PYTHON_COMMANDS/qpid-stat -q -b localhost:$broker_2_port | grep sasl_fed_queue | awk '{print $2}'`
+#echo " "
+
+#--------------------------------------------------
+#echo " Asking brokers to quit."
+#--------------------------------------------------
+$QPIDD_EXEC --port $broker_1_port --quit
+$QPIDD_EXEC --port $broker_2_port --quit
+
+
+#--------------------------------------------------
+#echo "Removing temporary directory $tmp_root"
+#--------------------------------------------------
+rm -rf $tmp_root
+
+if [ "$broker_2_message_count" -eq "$n_messages" ]; then
+ # echo "good: |$broker_2_message_count| == |$n_messages|"
+ exit 0
+else
+ # echo "not ideal: |$broker_1_message_count| != |$n_messages|"
+ exit 1
+fi
+
+
+
+
+
+
diff --git a/qpid/cpp/src/tests/sasl_fed_ex b/qpid/cpp/src/tests/sasl_fed_ex
new file mode 100755
index 0000000000..e2ee37ba39
--- /dev/null
+++ b/qpid/cpp/src/tests/sasl_fed_ex
@@ -0,0 +1,283 @@
+#!/usr/bin/env 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.
+#
+
+#===============================================================================
+# These tests create federated links between two brokers using SASL security.
+# The SASL mechanism used is EXTERNAL, which is satisfied by SSL
+# transport-layer security.
+#===============================================================================
+
+source $QPID_TEST_COMMON
+
+ensure_python_tests
+
+script_name=`basename $0`
+
+if [ $# -lt 1 ] || [ $# -gt 2 ]
+then
+ echo
+ # These are the four different ways of creating links ( or routes+links )
+ # that the qpid-route command provides.
+ echo "Usage: ${script_name} dynamic|link|queue|route"
+ echo
+ exit 1
+fi
+
+qpid_route_method=$1
+
+# Debugging print. --------------------------
+debug=
+function print {
+ if [ "$debug" ]; then
+ echo "${script_name}: $1"
+ fi
+}
+
+print "=========== start sasl_fed_ex $* ============"
+
+
+
+# This minimum value corresponds to sasl version 2.1.22
+minimum_sasl_version=131350
+
+sasl_version=`$QPID_TEST_EXEC_DIR/sasl_version`
+
+# This test is necessary because this sasl version is the first one that permits
+# redirection of the sasl config file path.
+if [ "$sasl_version" -lt "$minimum_sasl_version" ]; then
+ echo "sasl_fed: must have sasl version 2.1.22 or greater. ( Integer value: $minimum_sasl_version ) Version is: $sasl_version"
+ exit 0
+fi
+
+CERT_DIR=`pwd`/test_cert_db
+CERT_PW_FILE=`pwd`/cert.password
+TEST_HOSTNAME=127.0.0.1
+
+create_certs() {
+ #create certificate and key databases with single, simple, self-signed certificate in it
+ mkdir ${CERT_DIR}
+ certutil -N -d ${CERT_DIR} -f ${CERT_PW_FILE}
+ certutil -S -d ${CERT_DIR} -n ${TEST_HOSTNAME} -s "CN=${TEST_HOSTNAME}" -t "CT,," -x -f ${CERT_PW_FILE} -z /bin/sh 2> /dev/null
+}
+
+delete_certs() {
+ if [[ -e ${CERT_DIR} ]] ; then
+ print "removing cert dir ${CERT_DIR}"
+ rm -rf ${CERT_DIR}
+ fi
+}
+
+
+CERTUTIL=$(type -p certutil)
+if [[ !(-x $CERTUTIL) ]] ; then
+ echo "No certutil, skipping ssl test";
+ exit 0;
+fi
+
+delete_certs
+create_certs 2> /dev/null
+if [ ! $? ]; then
+ error "Could not create test certificate"
+ exit 1
+fi
+
+sasl_config_dir=$QPID_TEST_EXEC_DIR/sasl_config
+
+tmp_root=$QPID_TEST_EXEC_DIR/sasl_fed_ex_temp
+print "results dir is ${tmp_root}"
+rm -rf ${tmp_root}
+mkdir -p $tmp_root
+
+SRC_SSL_PORT=6667
+DST_SSL_PORT=6666
+
+SRC_SSL_PORT_2=6668
+DST_SSL_PORT_2=6669
+
+SRC_TCP_PORT=5801
+DST_TCP_PORT=5807
+
+SRC_TCP_PORT_2=5802
+DST_TCP_PORT_2=5803
+
+export QPID_SSL_CERT_NAME=${TEST_HOSTNAME}
+
+export QPID_NO_MODULE_DIR=1
+export QPID_SSL_CERT_DB=${CERT_DIR}
+export QPID_SSL_CERT_PASSWORD_FILE=${CERT_PW_FILE}
+export QPID_SSL_CERT_NAME=${TEST_HOSTNAME}
+
+
+
+#######################################
+# Understanding this Plumbing
+#######################################
+# 1. when you establish the route with qpid-route,
+# here is the best termiology to use:
+#
+# qpid-route route add DST SRC
+#
+# 2. DST will connect to SRC through the ssl port of SRC.
+#
+# 3. sender client connects to the tcp port of SRC.
+#
+# 4. sender specifies mechanism ANONYMOUS.
+#
+# 5. DST pulls messages off the temp queue on SRC to itself.
+#
+
+COMMON_BROKER_OPTIONS=" \
+ --ssl-sasl-no-dict \
+ --sasl-config=$sasl_config_dir \
+ --ssl-require-client-authentication \
+ --auth yes \
+ --ssl-cert-db $CERT_DIR \
+ --ssl-cert-password-file $CERT_PW_FILE \
+ --ssl-cert-name $TEST_HOSTNAME \
+ --no-data-dir \
+ --no-module-dir \
+ --mgmt-enable=yes \
+ --log-enable info+ \
+ --log-source yes \
+ --daemon "
+
+
+function start_brokers {
+ # vanilla brokers --------------------------------
+ print "Starting SRC broker"
+ $QPIDD_EXEC \
+ --port=${SRC_TCP_PORT} \
+ --ssl-port ${SRC_SSL_PORT} \
+ ${COMMON_BROKER_OPTIONS} \
+ --log-to-file $tmp_root/qpidd_src.log 2> /dev/null
+
+ broker_ports[0]=${SRC_TCP_PORT}
+
+ print "Starting DST broker"
+ $QPIDD_EXEC \
+ --port=${DST_TCP_PORT} \
+ --ssl-port ${DST_SSL_PORT} \
+ ${COMMON_BROKER_OPTIONS} \
+ --log-to-file $tmp_root/qpidd_dst.log 2> /dev/null
+
+ broker_ports[1]=${DST_TCP_PORT}
+}
+
+function halt_brokers {
+ n_brokers=${#broker_ports[@]}
+ print "Halting ${n_brokers} brokers."
+ for i in $(seq 0 $((${n_brokers} - 1)))
+ do
+ halt_port=${broker_ports[$i]}
+ print "Halting broker $i on port ${halt_port}"
+ $QPIDD_EXEC --port ${halt_port} --quit
+ done
+
+}
+
+
+start_brokers
+
+
+# I am not randomizing these names, because this test creates its own brokers.
+QUEUE_NAME=sasl_fed_queue
+ROUTING_KEY=sasl_fed_queue
+EXCHANGE_NAME=sasl_fedex
+
+
+print "add exchanges"
+$QPID_CONFIG_EXEC -b localhost:${SRC_TCP_PORT} add exchange direct $EXCHANGE_NAME
+$QPID_CONFIG_EXEC -b localhost:${DST_TCP_PORT} add exchange direct $EXCHANGE_NAME
+
+
+print "add queues"
+$QPID_CONFIG_EXEC -b localhost:${SRC_TCP_PORT} add queue $QUEUE_NAME
+$QPID_CONFIG_EXEC -b localhost:${DST_TCP_PORT} add queue $QUEUE_NAME
+
+
+print "create bindings"
+$QPID_CONFIG_EXEC -b localhost:${SRC_TCP_PORT} bind $EXCHANGE_NAME $QUEUE_NAME $ROUTING_KEY
+$QPID_CONFIG_EXEC -b localhost:${DST_TCP_PORT} bind $EXCHANGE_NAME $QUEUE_NAME $ROUTING_KEY
+
+
+#
+# NOTE: The SRC broker *must* be referred to as $TEST_HOSTNAME, and not as "localhost".
+# It must be referred to by the exact string given as the Common Name (CN) in the cert,
+# which was created in the function create_certs, above.
+
+
+
+#----------------------------------------------------------------
+# Use qpid-route to create the link, or the link+route, depending
+# on which of its several methods was requested.
+#----------------------------------------------------------------
+if [ ${qpid_route_method} == "dynamic" ]; then
+ print "dynamic add"
+ $QPID_ROUTE_EXEC -t ssl dynamic add localhost:${DST_TCP_PORT} $TEST_HOSTNAME:${SRC_SSL_PORT} $EXCHANGE_NAME "" "" EXTERNAL
+elif [ ${qpid_route_method} == "link" ]; then
+ print "link add"
+ $QPID_ROUTE_EXEC -t ssl link add localhost:${DST_TCP_PORT} $TEST_HOSTNAME:${SRC_SSL_PORT} EXTERNAL
+elif [ ${qpid_route_method} == "queue" ]; then
+ print "queue add"
+ $QPID_ROUTE_EXEC -t ssl queue add localhost:${DST_TCP_PORT} $TEST_HOSTNAME:${SRC_SSL_PORT} $EXCHANGE_NAME $ROUTING_KEY EXTERNAL
+elif [ ${qpid_route_method} == "route" ]; then
+ print "route add"
+ $QPID_ROUTE_EXEC -t ssl route add localhost:${DST_TCP_PORT} $TEST_HOSTNAME:${SRC_SSL_PORT} $EXCHANGE_NAME $ROUTING_KEY "" "" EXTERNAL
+else
+ echo "unknown method: |${qpid_route_method}|"
+ echo " choices are: dynamic|link|queue|route "
+ halt_brokers
+ exit 1
+fi
+
+
+# I don't know how to avoid this sleep yet. It has to come after route-creation
+# to avoid false negatives.
+sleep 5
+
+# Look only at the transport field, which should be "ssl".
+print "check the link"
+link_status=$($QPID_ROUTE_EXEC link list localhost:${DST_TCP_PORT} | tail -1 | awk '{print $3}')
+
+halt_brokers
+
+sleep 1
+
+if [ ! ${link_status} ]; then
+ print "link_status is empty"
+ print "result: fail"
+ exit 2
+fi
+
+if [ ${link_status} == "ssl" ]; then
+ print "result: good"
+ # Only remove the tmp_root on success, to permit debugging.
+ print "Removing temporary directory $tmp_root"
+ rm -rf $tmp_root
+ exit 0
+fi
+
+print "link_status has a bad value: ${link_status}"
+print "result: fail"
+exit 3
+
+
+
diff --git a/qpid/cpp/src/tests/sasl_no_dir b/qpid/cpp/src/tests/sasl_no_dir
new file mode 100755
index 0000000000..b2f5d1668e
--- /dev/null
+++ b/qpid/cpp/src/tests/sasl_no_dir
@@ -0,0 +1,106 @@
+#!/usr/bin/env 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.
+#
+
+script_name=`basename $0`
+
+# This minimum value corresponds to sasl version 2.1.22
+minimum_sasl_version=131350
+
+sasl_version=$($QPID_TEST_EXEC_DIR/sasl_version)
+
+# This test is necessary because this sasl version is the first one that permits
+# redirection of the sasl config file path.
+if [ "$sasl_version" -lt "$minimum_sasl_version" ]; then
+ echo "sasl_fed: must have sasl version 2.1.22 or greater. ( Integer value: $minimum_sasl_version ) Version is: $sasl_version"
+ exit 0
+fi
+
+
+sasl_config_dir=$QPID_TEST_EXEC_DIR/sasl_config
+
+
+# Debugging print. --------------------------
+debug=
+function print {
+ if [ "$debug" ]; then
+ echo "${script_name}: $1"
+ fi
+}
+
+
+my_random_number=$RANDOM
+tmp_root=/tmp/sasl_fed_$my_random_number
+mkdir -p $tmp_root
+
+
+LOG_FILE=$tmp_root/qpidd.log
+
+# If you want to see this test fail, just comment out this 'mv' command.
+print "Moving sasl configuration dir."
+mv ${sasl_config_dir} ${sasl_config_dir}-
+
+
+#--------------------------------------------------
+print " Starting broker"
+#--------------------------------------------------
+$QPIDD_EXEC \
+ -p 0 --interface 127.0.0.1 \
+ --no-data-dir \
+ --auth=yes \
+ --mgmt-enable=yes \
+ --log-enable info+ \
+ --log-source yes \
+ --log-to-file ${LOG_FILE} \
+ --sasl-config=$sasl_config_dir \
+ -d 2> /dev/null 1> $tmp_root/broker_port
+
+
+
+# If it works right, the output will look something like this: ( two lines long )
+# Daemon startup failed: SASL: sasl_set_path failed: no such directory: /home/mick/trunk/qpid/cpp/src/tests/sasl_config (qpid/broker/SaslAuthenticator.cpp:112)
+# 2011-10-13 14:07:00 critical qpidd.cpp:83: Unexpected error: Daemon startup failed: SASL: sasl_set_path failed: no such directory: /home/mick/trunk/qpid/cpp/src/tests/sasl_config (qpid/broker/SaslAuthenticator.cpp:112)
+
+result=`cat ${LOG_FILE} | grep "sasl_set_path failed: no such directory" | wc -l `
+
+#--------------------------------------------------
+print "Restore the Sasl config dir to its original place."
+#--------------------------------------------------
+mv ${sasl_config_dir}- ${sasl_config_dir}
+
+if [ "2" -eq ${result} ]; then
+ print "result: success"
+ rm -rf $tmp_root
+ exit 0
+fi
+
+
+# If this test fails, the broker is still alive.
+# Kill it.
+broker_port=`cat $tmp_root/broker_port`
+#--------------------------------------------------
+print "Asking broker to quit."
+#--------------------------------------------------
+$QPIDD_EXEC --port $broker_port --quit
+
+rm -rf $tmp_root
+
+print "result: fail"
+exit 1
diff --git a/qpid/cpp/src/tests/sasl_test_setup.sh b/qpid/cpp/src/tests/sasl_test_setup.sh
new file mode 100755
index 0000000000..d41efbe6e5
--- /dev/null
+++ b/qpid/cpp/src/tests/sasl_test_setup.sh
@@ -0,0 +1,42 @@
+#!/usr/bin/env 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.
+#
+source ./test_env.sh
+
+test -x $SASL_PW || { echo Skipping SASL test, saslpasswd2 not found; exit 0; }
+
+mkdir -p sasl_config
+
+# Create configuration file.
+cat > sasl_config/qpidd.conf <<EOF
+pwcheck_method: auxprop
+auxprop_plugin: sasldb
+sasldb_path: $PWD/sasl_config/qpidd.sasldb
+sql_select: dummy select
+mech_list: ANONYMOUS PLAIN DIGEST-MD5 EXTERNAL CRAM-MD5
+EOF
+
+# Populate temporary sasl db.
+SASLTEST_DB=./sasl_config/qpidd.sasldb
+rm -f $SASLTEST_DB
+echo guest | $SASL_PW -c -p -f $SASLTEST_DB -u QPID guest
+echo zig | $SASL_PW -c -p -f $SASLTEST_DB -u QPID zig
+echo zag | $SASL_PW -c -p -f $SASLTEST_DB -u QPID zag
+
diff --git a/qpid/cpp/src/tests/sasl_version.cpp b/qpid/cpp/src/tests/sasl_version.cpp
new file mode 100644
index 0000000000..db3efe4181
--- /dev/null
+++ b/qpid/cpp/src/tests/sasl_version.cpp
@@ -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.
+ *
+ */
+
+#include <iostream>
+
+#include "sasl/sasl.h"
+
+
+/*
+ Some tests need to distinguish between different versions of
+ SASL. This encodes and outputs the version number as an integer
+ for easy use in testing scripts.
+*/
+
+int
+main ( )
+{
+ // I assume that these are 8-bit quantities....
+ int sasl_version = (SASL_VERSION_MAJOR << 16) +
+ (SASL_VERSION_MINOR << 8) +
+ SASL_VERSION_STEP;
+
+ std::cout << sasl_version << std::endl;
+
+ return 0;
+}
+
+
+
+
diff --git a/qpid/cpp/src/tests/sender.cpp b/qpid/cpp/src/tests/sender.cpp
new file mode 100644
index 0000000000..063b5e87dc
--- /dev/null
+++ b/qpid/cpp/src/tests/sender.cpp
@@ -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.
+ *
+ */
+
+#include <qpid/client/FailoverManager.h>
+#include <qpid/client/Session.h>
+#include <qpid/client/AsyncSession.h>
+#include <qpid/client/Message.h>
+#include <qpid/client/MessageReplayTracker.h>
+#include <qpid/client/QueueOptions.h>
+#include <qpid/Exception.h>
+#include "TestOptions.h"
+
+#include "qpid/messaging/Message.h" // Only for Statistics
+#include "Statistics.h"
+
+#include <fstream>
+#include <iostream>
+
+using namespace qpid;
+using namespace qpid::client;
+using namespace qpid::framing;
+
+using namespace std;
+
+namespace qpid {
+namespace tests {
+
+struct Args : public qpid::TestOptions
+{
+ string destination;
+ string key;
+ uint sendEos;
+ bool durable;
+ uint ttl;
+ string lvqMatchValue;
+ string lvqMatchFile;
+ bool reportTotal;
+ int reportEvery;
+ bool reportHeader;
+
+ Args() :
+ key("test-queue"), sendEos(0), durable(false), ttl(0),
+ reportTotal(false),
+ reportEvery(0),
+ reportHeader(true)
+ {
+ addOptions()
+ ("exchange", qpid::optValue(destination, "EXCHANGE"), "Exchange to send messages to")
+ ("routing-key", qpid::optValue(key, "KEY"), "Routing key to add to messages")
+ ("send-eos", qpid::optValue(sendEos, "N"), "Send N EOS messages to mark end of input")
+ ("durable", qpid::optValue(durable, "true|false"), "Mark messages as durable.")
+ ("ttl", qpid::optValue(ttl, "msecs"), "Time-to-live for messages, in milliseconds")
+ ("lvq-match-value", qpid::optValue(lvqMatchValue, "KEY"), "The value to set for the LVQ match key property")
+ ("lvq-match-file", qpid::optValue(lvqMatchFile, "FILE"), "A file containing values to set for the LVQ match key property")
+ ("report-total", qpid::optValue(reportTotal), "Report total throughput statistics")
+ ("report-every", qpid::optValue(reportEvery,"N"), "Report throughput statistics every N messages")
+ ("report-header", qpid::optValue(reportHeader, "yes|no"), "Headers on report.")
+ ;
+ }
+};
+
+const string EOS("eos");
+
+class Sender : public FailoverManager::Command
+{
+ public:
+ Sender(Reporter<Throughput>& reporter, const std::string& destination, const std::string& key, uint sendEos, bool durable, uint ttl,
+ const std::string& lvqMatchValue, const std::string& lvqMatchFile);
+ void execute(AsyncSession& session, bool isRetry);
+
+ private:
+ Reporter<Throughput>& reporter;
+ messaging::Message dummyMessage;
+ const std::string destination;
+ MessageReplayTracker sender;
+ Message message;
+ const uint sendEos;
+ uint sent;
+ std::ifstream lvqMatchValues;
+};
+
+Sender::Sender(Reporter<Throughput>& rep, const std::string& dest, const std::string& key, uint eos, bool durable, uint ttl, const std::string& lvqMatchValue, const std::string& lvqMatchFile) :
+ reporter(rep), destination(dest), sender(10), message("", key), sendEos(eos), sent(0) , lvqMatchValues(lvqMatchFile.c_str())
+{
+ if (durable){
+ message.getDeliveryProperties().setDeliveryMode(framing::PERSISTENT);
+ }
+
+ if (ttl) {
+ message.getDeliveryProperties().setTtl(ttl);
+ }
+
+ if (!lvqMatchValue.empty()) {
+ message.getHeaders().setString(QueueOptions::strLVQMatchProperty, lvqMatchValue);
+ }
+}
+
+void Sender::execute(AsyncSession& session, bool isRetry)
+{
+ if (isRetry) sender.replay(session);
+ else sender.init(session);
+ string data;
+ while (getline(std::cin, data)) {
+ message.setData(data);
+ //message.getHeaders().setInt("SN", ++sent);
+ string matchKey;
+ if (lvqMatchValues && getline(lvqMatchValues, matchKey)) {
+ message.getHeaders().setString(QueueOptions::strLVQMatchProperty, matchKey);
+ }
+ reporter.message(dummyMessage); // For statistics
+ sender.send(message, destination);
+ }
+ for (uint i = sendEos; i > 0; --i) {
+ message.setData(EOS);
+ sender.send(message, destination);
+ }
+}
+
+}} // namespace qpid::tests
+
+using namespace qpid::tests;
+
+int main(int argc, char ** argv)
+{
+ Args opts;
+ try {
+ opts.parse(argc, argv);
+ Reporter<Throughput> reporter(std::cout, opts.reportEvery, opts.reportHeader);
+ FailoverManager connection(opts.con);
+ Sender sender(reporter, opts.destination, opts.key, opts.sendEos, opts.durable, opts.ttl, opts.lvqMatchValue, opts.lvqMatchFile);
+ connection.execute(sender);
+ connection.close();
+ if (opts.reportTotal) reporter.report();
+ return 0;
+ } catch(const std::exception& error) {
+ std::cout << "Failed: " << error.what() << std::endl;
+ }
+ return 1;
+}
diff --git a/qpid/cpp/src/tests/shared_perftest b/qpid/cpp/src/tests/shared_perftest
new file mode 100755
index 0000000000..709ffd56b5
--- /dev/null
+++ b/qpid/cpp/src/tests/shared_perftest
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+exec `dirname $0`/run_perftest 100000 --mode shared --npubs 16 --nsubs 16
diff --git a/qpid/cpp/src/tests/shlibtest.cpp b/qpid/cpp/src/tests/shlibtest.cpp
new file mode 100644
index 0000000000..5655eb7e64
--- /dev/null
+++ b/qpid/cpp/src/tests/shlibtest.cpp
@@ -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.
+ *
+ */
+
+namespace qpid {
+namespace tests {
+
+int* loaderData = 0;
+extern "C"
+#ifdef WIN32
+__declspec(dllexport)
+#endif
+void callMe(int *i) { loaderData=i; }
+
+struct OnUnload { ~OnUnload() { *loaderData=42; } };
+OnUnload unloader; // For destructor.
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/ssl_test b/qpid/cpp/src/tests/ssl_test
new file mode 100755
index 0000000000..d681059495
--- /dev/null
+++ b/qpid/cpp/src/tests/ssl_test
@@ -0,0 +1,331 @@
+#!/usr/bin/env 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.
+#
+
+# Run a simple test over SSL
+
+#set -x
+
+CONFIG=$(dirname $0)/config.null
+TEST_CERT_DIR=`pwd`/test_cert_dir
+CERT_DB=${TEST_CERT_DIR}/test_cert_db
+CERT_PW_FILE=`pwd`/cert.password
+TEST_HOSTNAME=127.0.0.1
+TEST_CLIENT_CERT=rumplestiltskin
+CA_PEM_FILE=${TEST_CERT_DIR}/ca_cert.pem
+OTHER_CA_CERT_DB=${TEST_CERT_DIR}/x_ca_cert_db
+OTHER_CA_PEM_FILE=${TEST_CERT_DIR}/other_ca_cert.pem
+PY_PING_BROKER=${QPID_TEST_SRC_DIR}/ping_broker
+COUNT=10
+
+if [[ -a $AMQP_LIB ]] ; then
+ MODULES="--load-module $AMQP_LIB"
+fi
+
+trap cleanup EXIT
+
+error() { echo $*; exit 1; }
+
+# create the test certificate database
+# $1 = string used as Subject in server's certificate
+# $2 = string used as SubjectAlternateName (SAN) in server's certificate
+create_certs() {
+
+ local CERT_SUBJECT=${1:-"CN=${TEST_HOSTNAME},O=MyCo,ST=Massachusetts,C=US"}
+ local CERT_SAN=${2:-"*.server.com"}
+
+ mkdir -p ${TEST_CERT_DIR}
+ rm -rf ${TEST_CERT_DIR}/*
+
+ # Set Up a CA with a self-signed Certificate
+ #
+ mkdir -p ${CERT_DB}
+ certutil -N -d ${CERT_DB} -f ${CERT_PW_FILE}
+ certutil -S -d ${CERT_DB} -n "Test-CA" -s "CN=Test-CA,O=MyCo,ST=Massachusetts,C=US" -t "CT,," -x -f ${CERT_PW_FILE} -z /bin/sh >/dev/null 2>&1
+ certutil -L -d ${CERT_DB} -n "Test-CA" -a -o ${CERT_DB}/rootca.crt -f ${CERT_PW_FILE}
+ #certutil -L -d ${CERT_DB} -f ${CERT_PW_FILE}
+
+ # create server certificate signed by Test-CA
+ #
+ certutil -R -d ${CERT_DB} -s "${CERT_SUBJECT}" -o ${TEST_CERT_DIR}/server.req -f ${CERT_PW_FILE} -z /bin/sh > /dev/null 2>&1
+ certutil -C -d ${CERT_DB} -c "Test-CA" -8 "${CERT_SAN}" -i ${TEST_CERT_DIR}/server.req -o ${TEST_CERT_DIR}/server.crt -f ${CERT_PW_FILE} -m ${RANDOM}
+ certutil -A -d ${CERT_DB} -n ${TEST_HOSTNAME} -i ${TEST_CERT_DIR}/server.crt -t "Pu,,"
+
+ # create a certificate to identify the client
+ #
+ certutil -R -d ${CERT_DB} -s "CN=${TEST_CLIENT_CERT}" -o ${TEST_CERT_DIR}/client.req -f ${CERT_PW_FILE} -z /bin/sh > /dev/null 2>&1
+ certutil -C -d ${CERT_DB} -c "Test-CA" -8 "*.client.com" -i ${TEST_CERT_DIR}/client.req -o ${TEST_CERT_DIR}/client.crt -f ${CERT_PW_FILE} -m ${RANDOM}
+ certutil -A -d ${CERT_DB} -n ${TEST_CLIENT_CERT} -i ${TEST_CERT_DIR}/client.crt -t "Pu,,"
+ ###
+ #certutil -N -d ${SERVER_CERT_DIR} -f ${CERT_PW_FILE}
+ #certutil -S -d ${SERVER_CERT_DIR} -n ${TEST_HOSTNAME} -s "CN=${TEST_HOSTNAME}" -t "CT,," -x -f ${CERT_PW_FILE} -z /usr/bin/certutil
+ #certutil -S -d ${SERVER_CERT_DIR} -n ${TEST_CLIENT_CERT} -s "CN=${TEST_CLIENT_CERT}" -t "CT,," -x -f ${CERT_PW_FILE} -z /usr/bin/certutil
+
+ # Set up a separate DB with its own CA for testing failure to validate scenario
+ #
+ mkdir -p ${OTHER_CA_CERT_DB}
+ certutil -N -d ${OTHER_CA_CERT_DB} -f ${CERT_PW_FILE}
+ certutil -S -d ${OTHER_CA_CERT_DB} -n "Other-Test-CA" -s "CN=Another Test CA,O=MyCo,ST=Massachusetts,C=US" -t "CT,," -x -f ${CERT_PW_FILE} -z /bin/sh >/dev/null 2>&1
+ certutil -L -d ${OTHER_CA_CERT_DB} -n "Other-Test-CA" -a -o ${OTHER_CA_CERT_DB}/rootca.crt -f ${CERT_PW_FILE}
+ #certutil -L -d ${OTHER_CA_CERT_DB} -f ${CERT_PW_FILE}
+}
+
+delete_certs() {
+ if [[ -e ${TEST_CERT_DIR} ]] ; then
+ rm -rf ${TEST_CERT_DIR}
+ fi
+}
+
+# Don't need --no-module-dir or --no-data-dir as they are set as env vars in test_env.sh
+COMMON_OPTS="--daemon --config $CONFIG --ssl-cert-db $CERT_DB --ssl-cert-password-file $CERT_PW_FILE --ssl-cert-name $TEST_HOSTNAME"
+
+# Start new brokers:
+# $1 must be integer
+# $2 = extra opts
+# Append used ports to PORTS variable
+start_brokers() {
+ local -a ports
+ for (( i=0; $i<$1; i++)) do
+ ports[$i]=$($QPIDD_EXEC --port 0 --interface 127.0.0.1 $COMMON_OPTS $2) || error "Could not start broker $i"
+ done
+ PORTS=( ${PORTS[@]} ${ports[@]} )
+}
+
+# Stop single broker:
+# $1 is number of broker to stop (0 based)
+stop_broker() {
+ $QPIDD_EXEC -qp ${PORTS[$1]}
+
+ # Remove from ports array
+ unset PORTS[$1]
+}
+
+stop_brokers() {
+ for port in "${PORTS[@]}";
+ do
+ $QPIDD_EXEC -qp $port
+ done
+ PORTS=()
+}
+
+pick_port() {
+ # We need a fixed port to set --cluster-url. Use qpidd to pick a free port.
+ PICK=`../qpidd --no-module-dir --listen-disable ssl -dp0`
+ ../qpidd --no-module-dir -qp $PICK
+ echo $PICK
+}
+
+cleanup() {
+ stop_brokers
+ delete_certs
+ rm -f ${CERT_PW_FILE}
+}
+
+start_ssl_broker() {
+ start_brokers 1 "--transport ssl --ssl-port 0 --require-encryption --auth no $MODULES"
+}
+
+start_ssl_mux_broker() {
+ ../qpidd $COMMON_OPTS --port $1 --ssl-port $1 --auth no
+ PORTS=( ${PORTS[@]} $1 )
+}
+
+sasl_config_dir=$QPID_TEST_EXEC_DIR/sasl_config
+
+start_authenticating_broker() {
+ start_brokers 1 "--transport ssl --ssl-port 0 --require-encryption --ssl-sasl-no-dict --ssl-require-client-authentication --auth yes --sasl-config=${sasl_config_dir} $MODULES"
+}
+
+ssl_cluster_broker() { # $1 = port
+ start_brokers 1 "--ssl-port $1 --auth no --load-module $CLUSTER_LIB --cluster-name ssl_test.$HOSTNAME.$$ --cluster-url amqp:ssl:$TEST_HOSTNAME:$1"
+
+ # Wait for broker to be ready
+ qpid-ping -Pssl -b $TEST_HOSTNAME:$1 -q || { echo "Cannot connect to broker on $1"; exit 1; }
+}
+
+CERTUTIL=$(type -p certutil)
+if [[ !(-x $CERTUTIL) ]] ; then
+ echo "No certutil, skipping ssl test";
+ exit 0;
+fi
+
+if [[ !(-e ${CERT_PW_FILE}) ]] ; then
+ echo password > ${CERT_PW_FILE}
+fi
+delete_certs
+create_certs || error "Could not create test certificate database"
+
+start_ssl_broker
+PORT=${PORTS[0]}
+echo "Running SSL test on port $PORT"
+export QPID_NO_MODULE_DIR=1
+export QPID_SSL_CERT_DB=${CERT_DB}
+export QPID_SSL_CERT_PASSWORD_FILE=${CERT_PW_FILE}
+
+## Test connection via connection settings
+./qpid-perftest --count ${COUNT} --port ${PORT} -P ssl -b $TEST_HOSTNAME --summary
+
+## Test connection with a URL
+URL=amqp:ssl:$TEST_HOSTNAME:$PORT
+./qpid-send -b $URL --content-string=hello -a "foo;{create:always}"
+MSG=`./qpid-receive -b $URL -a "foo;{create:always}" --messages 1`
+test "$MSG" = "hello" || { echo "receive failed '$MSG' != 'hello'"; exit 1; }
+
+if [[ -a $AMQP_LIB ]] ; then
+ echo "Testing ssl over AMQP 1.0"
+ ./qpid-send --connection-options '{protocol:amqp1.0}' -b $URL --content-string=hello -a "foo;{create:always}"
+ MSG=`./qpid-receive --connection-options '{protocol:amqp1.0}' -b $URL -a "foo;{create:always}" --messages 1`
+ test "$MSG" = "hello" || { echo "receive failed for AMQP 1.0 '$MSG' != 'hello'"; exit 1; }
+fi
+
+## Test connection with a combination of URL and connection options (in messaging API)
+URL=$TEST_HOSTNAME:$PORT
+./qpid-send -b $URL --connection-options '{transport:ssl,heartbeat:2}' --content-string='hello again' -a "foo;{create:always}"
+MSG=`./qpid-receive -b $URL --connection-options '{transport:ssl,heartbeat:2}' -a "foo;{create:always}" --messages 1`
+test "$MSG" = "hello again" || { echo "receive failed '$MSG' != 'hello again'"; exit 1; }
+
+## Test using the Python client
+if test -d $PYTHON_DIR; then
+ echo "Testing Non-Authenticating with Python Client..."
+ URL=amqps://$TEST_HOSTNAME:$PORT
+ if `$PY_PING_BROKER -b $URL`; then echo " Passed"; else { echo " Failed"; exit 1; }; fi
+else
+ echo "Skipping python part of ssl_test, no python dir."
+fi
+
+#### Client Authentication tests
+
+start_authenticating_broker
+PORT2=${PORTS[1]}
+echo "Running SSL client authentication test on port $PORT2"
+URL=amqp:ssl:$TEST_HOSTNAME:$PORT2
+
+## See if you can set the SSL cert-name for the connection
+./qpid-send -b $URL --connection-options "{ssl-cert-name: $TEST_CLIENT_CERT }" --content-string=hello -a "bar;{create:always}"
+MSG2=`./qpid-receive -b $URL --connection-options "{ssl-cert-name: $TEST_CLIENT_CERT }" -a "bar;{create:always}" --messages 1`
+test "$MSG2" = "hello" || { echo "receive failed '$MSG2' != 'hello'"; exit 1; }
+
+## Make sure that connect fails with an invalid SSL cert-name
+./qpid-send -b $URL --connection-options "{ssl-cert-name: pignose }" --content-string=hello -a "baz;{create:always}" 2>/dev/null 1>/dev/null
+MSG3=`./qpid-receive -b $URL --connection-options "{ssl-cert-name: pignose }" -a "baz;{create:always}" --messages 1 2>/dev/null`
+test "$MSG3" = "" || { echo "receive succeeded without valid ssl cert '$MSG3' != ''"; exit 1; }
+
+stop_brokers
+
+# Test ssl muxed with plain TCP on the same connection
+
+# Test a specified port number - since tcp/ssl are the same port don't need to specify --transport ssl
+PORT=`pick_port`
+start_ssl_mux_broker $PORT || error "Could not start broker"
+echo "Running SSL/TCP mux test on fixed port $PORT"
+
+## Test connection via connection settings
+./qpid-perftest --count ${COUNT} --port ${PORT} -P ssl -b $TEST_HOSTNAME --summary || error "SSL connection failed!"
+./qpid-perftest --count ${COUNT} --port ${PORT} -P tcp -b $TEST_HOSTNAME --summary || error "TCP connection failed!"
+
+# Test a broker chosen port - since ssl chooses port need to use --transport ssl here
+start_ssl_broker
+PORT=${PORTS[0]}
+echo "Running SSL/TCP mux test on random port $PORT"
+
+## Test connection via connection settings
+./qpid-perftest --count ${COUNT} --port ${PORT} -P ssl -b $TEST_HOSTNAME --summary || error "SSL connection failed!"
+./qpid-perftest --count ${COUNT} --port ${PORT} -P tcp -b $TEST_HOSTNAME --summary || error "TCP connection failed!"
+
+stop_brokers
+
+### Additional tests that require 'openssl' and 'pk12util' to be installed (optional)
+
+PK12UTIL=$(type -p pk12util)
+if [[ !(-x $PK12UTIL) ]] ; then
+ echo >&2 "'pk12util' command not available, skipping remaining tests"
+ exit 0
+fi
+
+OPENSSL=$(type -p openssl)
+if [[ !(-x $OPENSSL) ]] ; then
+ echo >&2 "'openssl' command not available, skipping remaining tests"
+ exit 0
+fi
+
+if test -d $PYTHON_DIR; then
+## verify python version > 2.5 (only 2.6+ does certificate checking)
+ PY_VERSION=$(python -c "import sys; print hex(sys.hexversion)")
+ if (( PY_VERSION < 0x02060000 )); then
+ echo >&2 "Detected python version < 2.6 - skipping certificate verification tests"
+ exit 0
+ fi
+
+ echo "Testing Certificate validation and Authentication with the Python Client..."
+
+# extract the CA's certificate as a PEM file
+ get_ca_certs() {
+ $PK12UTIL -o ${TEST_CERT_DIR}/CA_pk12.out -d ${CERT_DB} -n "Test-CA" -w ${CERT_PW_FILE} -k ${CERT_PW_FILE} > /dev/null
+ $OPENSSL pkcs12 -in ${TEST_CERT_DIR}/CA_pk12.out -out ${CA_PEM_FILE} -nokeys -passin file:${CERT_PW_FILE} >/dev/null
+ $PK12UTIL -o ${TEST_CERT_DIR}/other_CA_pk12.out -d ${OTHER_CA_CERT_DB} -n "Other-Test-CA" -w ${CERT_PW_FILE} -k ${CERT_PW_FILE} > /dev/null
+ $OPENSSL pkcs12 -in ${TEST_CERT_DIR}/other_CA_pk12.out -out ${OTHER_CA_PEM_FILE} -nokeys -passin file:${CERT_PW_FILE} >/dev/null
+ }
+
+ get_ca_certs || error "Could not extract CA certificates as PEM files"
+ start_ssl_broker
+ PORT=${PORTS[0]}
+ URL=amqps://$TEST_HOSTNAME:$PORT
+# verify the python client can authenticate the broker using the CA
+ if `${PY_PING_BROKER} -b $URL --ssl-trustfile=${CA_PEM_FILE}`; then echo " Passed"; else { echo " Failed"; exit 1; }; fi
+# verify the python client fails to authenticate the broker when using the other CA
+ if `${PY_PING_BROKER} -b $URL --ssl-trustfile=${OTHER_CA_PEM_FILE} > /dev/null 2>&1`; then { echo " Failed"; exit 1; }; else echo " Passed"; fi
+ stop_brokers
+
+# create a certificate without matching TEST_HOSTNAME, should fail to verify
+
+ create_certs "O=MyCo" "*.${TEST_HOSTNAME}.com" || error "Could not create server test certificate"
+ get_ca_certs || error "Could not extract CA certificates as PEM files"
+ start_ssl_broker
+ PORT=${PORTS[0]}
+ URL=amqps://$TEST_HOSTNAME:$PORT
+ if `${PY_PING_BROKER} -b $URL --ssl-trustfile=${CA_PEM_FILE} > /dev/null 2>&1`; then { echo " Failed"; exit 1; }; else echo " Passed"; fi
+# but disabling the check for the hostname should pass
+ if `${PY_PING_BROKER} -b $URL --ssl-trustfile=${CA_PEM_FILE} --ssl-skip-hostname-check`; then echo " Passed"; else { echo " Failed"; exit 1; }; fi
+ stop_brokers
+
+# test SubjectAltName parsing
+
+ if (( PY_VERSION >= 0x02070300 )); then
+ # python 2.7.3+ supports SubjectAltName extraction
+ # create a certificate with TEST_HOSTNAME only in SAN, should verify OK
+ create_certs "O=MyCo" "*.foo.com,${TEST_HOSTNAME},*xyz.com" || error "Could not create server test certificate"
+ get_ca_certs || error "Could not extract CA certificates as PEM files"
+ start_ssl_broker
+ PORT=${PORTS[0]}
+ URL=amqps://$TEST_HOSTNAME:$PORT
+ if `${PY_PING_BROKER} -b $URL --ssl-trustfile=${CA_PEM_FILE}`; then echo " Passed"; else { echo " Failed"; exit 1; }; fi
+ stop_brokers
+
+ create_certs "O=MyCo" "*${TEST_HOSTNAME}" || error "Could not create server test certificate"
+ get_ca_certs || error "Could not extract CA certificates as PEM files"
+ start_ssl_broker
+ PORT=${PORTS[0]}
+ URL=amqps://$TEST_HOSTNAME:$PORT
+ if `${PY_PING_BROKER} -b $URL --ssl-trustfile=${CA_PEM_FILE}`; then echo " Passed"; else { echo " Failed"; exit 1; }; fi
+ stop_brokers
+ fi
+
+fi
+
diff --git a/qpid/cpp/src/tests/store.py b/qpid/cpp/src/tests/store.py
new file mode 100755
index 0000000000..5c1934dded
--- /dev/null
+++ b/qpid/cpp/src/tests/store.py
@@ -0,0 +1,214 @@
+#!/usr/bin/env python
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT 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 errno, os, time
+from brokertest import *
+from qpid import compat, session
+from qpid.util import connect
+from qpid.connection import Connection
+from qpid.datatypes import Message, uuid4
+from qpid.queue import Empty
+
+class StoreTests(BrokerTest):
+
+ XA_RBROLLBACK = 1
+ XA_RBTIMEOUT = 2
+ XA_OK = 0
+ tx_counter = 0
+
+ def configure(self, config):
+ self.config = config
+ self.defines = self.config.defines
+ BrokerTest.configure(self, config)
+
+ def setup_connection(self):
+ socket = connect(self._broker.host(), self._broker.port())
+ return Connection(sock=socket)
+
+ def setup_session(self):
+ self.conn.start()
+ return self.conn.session(str(uuid4()))
+
+ def start_session(self):
+ self.conn = self.setup_connection()
+ self.ssn = self.setup_session()
+
+ def setUp(self):
+ BrokerTest.setUp(self)
+ self._broker = self.broker()
+ self.start_session()
+
+ def cycle_broker(self):
+ # tearDown resets working dir; change it back after.
+ d = os.getcwd()
+ BrokerTest.tearDown(self)
+ os.chdir(d)
+ self._broker = None
+ self._broker = self.broker()
+ self.conn = self.setup_connection()
+ self.ssn = self.setup_session()
+
+ def xid(self, txid):
+ StoreTests.tx_counter += 1
+ branchqual = "v%s" % StoreTests.tx_counter
+ return self.ssn.xid(format=0, global_id=txid, branch_id=branchqual)
+
+ def testDurableExchange(self):
+ try:
+ self.ssn.exchange_delete(exchange="DE1")
+ except:
+ # restart the session busted from the exception
+ self.start_session()
+
+ self.ssn.exchange_declare(exchange="DE1", type="direct", durable=True)
+ response = self.ssn.exchange_query(name="DE1")
+ self.assert_(response.durable)
+ self.assert_(not response.not_found)
+
+ # Cycle the broker and make sure the exchange recovers
+ self.cycle_broker()
+ response = self.ssn.exchange_query(name="DE1")
+ self.assert_(response.durable)
+ self.assert_(not response.not_found)
+
+ self.ssn.exchange_delete(exchange="DE1")
+
+ def testDurableQueues(self):
+ try:
+ self.ssn.queue_delete(queue="DQ1")
+ except:
+ self.start_session()
+
+ self.ssn.queue_declare(queue="DQ1", durable=True)
+ response = self.ssn.queue_query(queue="DQ1")
+ self.assertEqual("DQ1", response.queue)
+ self.assert_(response.durable)
+
+ # Cycle the broker and make sure the queue recovers
+ self.cycle_broker()
+ response = self.ssn.queue_query(queue="DQ1")
+ self.assertEqual("DQ1", response.queue)
+ self.assert_(response.durable)
+
+ self.ssn.queue_delete(queue="DQ1")
+
+ def testDurableBindings(self):
+ try:
+ self.ssn.exchange_unbind(queue="DB_Q1", exchange="DB_E1", binding_key="K1")
+ except:
+ self.start_session()
+ try:
+ self.ssn.exchange_delete(exchange="DB_E1")
+ except:
+ self.start_session()
+ try:
+ self.ssn.queue_delete(queue="DB_Q1")
+ except:
+ self.start_session()
+
+ self.ssn.queue_declare(queue="DB_Q1", durable=True)
+ self.ssn.exchange_declare(exchange="DB_E1", type="direct", durable=True)
+ self.ssn.exchange_bind(exchange="DB_E1", queue="DB_Q1", binding_key="K1")
+
+ # Queue up 2 messages, one with non-zero body, one with zero-length.
+ # 2 = delivery_mode.persistent
+ dp = self.ssn.delivery_properties(routing_key="DB_Q1", delivery_mode=2)
+ self.ssn.message_transfer(message=Message(dp, "normal message"))
+ self.ssn.message_transfer(message=Message(dp, ""))
+
+ # Cycle the broker and make sure the binding recovers
+ self.cycle_broker()
+ response = self.ssn.exchange_bound(exchange="DB_E1", queue="DB_Q1", binding_key="K1")
+ self.assert_(not response.exchange_not_found)
+ self.assert_(not response.queue_not_found)
+ self.assert_(not response.queue_not_matched)
+ self.assert_(not response.key_not_matched)
+
+ # Are the messages still there?
+ self.ssn.message_subscribe(destination="msgs", queue="DB_Q1", accept_mode=1, acquire_mode=0)
+ self.ssn.message_flow(unit = 1, value = 0xFFFFFFFFL, destination = "msgs")
+ self.ssn.message_flow(unit = 0, value = 10, destination = "msgs")
+ message_arrivals = self.ssn.incoming("msgs")
+ try:
+ message_arrivals.get(timeout=1)
+ message_arrivals.get(timeout=1)
+ except Empty:
+ assert False, 'Durable message(s) not recovered'
+
+ self.ssn.exchange_unbind(queue="DB_Q1", exchange="DB_E1", binding_key="K1")
+ self.ssn.exchange_delete(exchange="DB_E1")
+ self.ssn.queue_delete(queue="DB_Q1")
+
+ def testDtxRecoverPrepared(self):
+ try:
+ self.ssn.exchange_unbind(queue="Dtx_Q", exchange="Dtx_E", binding_key="Dtx")
+ except:
+ self.start_session()
+ try:
+ self.ssn.exchange_delete(exchange="Dtx_E")
+ except:
+ self.start_session()
+ try:
+ self.ssn.queue_delete(queue="Dtx_Q")
+ except:
+ self.start_session()
+
+ self.ssn.queue_declare(queue="Dtx_Q", auto_delete=False, durable=True)
+ self.ssn.exchange_declare(exchange="Dtx_E", type="direct", durable=True)
+ self.ssn.exchange_bind(exchange="Dtx_E", queue="Dtx_Q", binding_key="Dtx")
+ txid = self.xid("DtxRecoverPrepared")
+ self.ssn.dtx_select()
+ self.ssn.dtx_start(xid=txid)
+ # 2 = delivery_mode.persistent
+ dp = self.ssn.delivery_properties(routing_key="Dtx_Q", delivery_mode=2)
+ self.ssn.message_transfer(message=Message(dp, "transactional message"))
+ self.ssn.dtx_end(xid=txid)
+ self.assertEqual(self.XA_OK, self.ssn.dtx_prepare(xid=txid).status)
+ # Cycle the broker and make sure the xid is there, the message is not
+ # queued.
+ self.cycle_broker()
+ # The txid should be recovered and in doubt
+ xids = self.ssn.dtx_recover().in_doubt
+ xid_matched = False
+ for x in xids:
+ self.assertEqual(txid.format, x.format)
+ self.assertEqual(txid.global_id, x.global_id)
+ self.assertEqual(txid.branch_id, x.branch_id)
+ xid_matched = True
+ self.assert_(xid_matched)
+ self.ssn.message_subscribe(destination="dtx_msgs", queue="Dtx_Q", accept_mode=1, acquire_mode=0)
+ self.ssn.message_flow(unit = 1, value = 0xFFFFFFFFL, destination = "dtx_msgs")
+ self.ssn.message_flow(unit = 0, value = 10, destination = "dtx_msgs")
+ message_arrivals = self.ssn.incoming("dtx_msgs")
+ try:
+ message_arrivals.get(timeout=1)
+ assert False, 'Message present in queue before commit'
+ except Empty: pass
+ self.ssn.dtx_select()
+ self.assertEqual(self.XA_OK, self.ssn.dtx_commit(xid=txid, one_phase=False).status)
+ try:
+ msg = message_arrivals.get(timeout=1)
+ self.assertEqual("transactional message", msg.body)
+ except Empty:
+ assert False, 'Message should be present after dtx commit but is not'
+
+ self.ssn.exchange_unbind(queue="Dtx_Q", exchange="Dtx_E", binding_key="Dtx")
+ self.ssn.exchange_delete(exchange="Dtx_E")
+ self.ssn.queue_delete(queue="Dtx_Q")
diff --git a/qpid/cpp/src/tests/swig_python_tests b/qpid/cpp/src/tests/swig_python_tests
new file mode 100755
index 0000000000..40c35ac0fa
--- /dev/null
+++ b/qpid/cpp/src/tests/swig_python_tests
@@ -0,0 +1,66 @@
+#!/usr/bin/env 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.
+#
+
+# Run the python tests.
+source $QPID_TEST_COMMON
+
+ensure_python_tests
+
+trap stop_broker INT TERM QUIT
+
+if [[ -a $AMQP_LIB ]] ; then
+ echo "Found AMQP support: $AMQP_LIB"
+ MODULES="--load-module $AMQP_LIB"
+fi
+
+fail() {
+ echo "FAIL swigged python tests: $1"; exit 1;
+}
+skip() {
+ echo "SKIPPED swigged python tests: $1"; exit 0;
+}
+
+start_broker() {
+ rm -f swig_python_tests.log
+ QPID_PORT=$($QPIDD_EXEC --daemon --port 0 --interface 127.0.0.1 --no-data-dir $MODULES --auth no --log-to-file swig_python_tests.log) || fail "Could not start broker"
+}
+
+stop_broker() {
+ $QPIDD_EXEC -q --port $QPID_PORT
+}
+
+test -f $PYTHONSWIGMODULE || skip "no swigged python client"
+test -d $QPID_TESTS || skip "test code not found"
+
+start_broker
+echo "Running swigged python tests using broker on port $QPID_PORT"
+
+export PYTHONPATH=$PYTHONPATH:$PYTHONPATH_SWIG
+export QPID_USE_SWIG_CLIENT=1
+$QPID_PYTHON_TEST -m qpid.tests.messaging.message -m qpid_tests.broker_0_10.priority -m qpid_tests.broker_0_10.lvq -m qpid_tests.broker_0_10.new_api -b localhost:$QPID_PORT -I $srcdir/failing-amqp0-10-python-tests $* || FAILED=1
+if [[ -a $AMQP_LIB ]] ; then
+ $QPID_PYTHON_TEST --define="protocol_version=amqp1.0" -m qpid_tests.broker_1_0 -m qpid_tests.broker_0_10.new_api -m assertions -m reject_release -m misc -m policies -b localhost:$QPID_PORT -I $srcdir/failing-amqp1.0-python-tests $* || FAILED=1
+fi
+stop_broker
+if [[ $FAILED -eq 1 ]]; then
+ fail ""
+fi
+
diff --git a/qpid/cpp/src/tests/test.xquery b/qpid/cpp/src/tests/test.xquery
new file mode 100644
index 0000000000..4cfe3af02d
--- /dev/null
+++ b/qpid/cpp/src/tests/test.xquery
@@ -0,0 +1,6 @@
+ let $w := ./weather
+ return $w/station = 'Raleigh-Durham International Airport (KRDU)'
+ and $w/temperature_f > 50
+ and $w/temperature_f - $w/dewpoint > 5
+ and $w/wind_speed_mph > 7
+ and $w/wind_speed_mph < 20
diff --git a/qpid/cpp/src/tests/test_env.ps1.in b/qpid/cpp/src/tests/test_env.ps1.in
new file mode 100644
index 0000000000..94834a4b5e
--- /dev/null
+++ b/qpid/cpp/src/tests/test_env.ps1.in
@@ -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.
+#
+
+# Environment variables substituted by configure/cmake.
+$abs_srcdir="@abs_srcdir@"
+$abs_builddir="@abs_builddir@"
+$top_srcdir="@abs_top_srcdir@"
+$top_builddir="@abs_top_builddir@"
+$moduledir="$top_builddir\src@builddir_lib_suffix@"
+$testmoduledir="$builddir@builddir_lib_suffix@"
+$BOOST_LIBRARYDIR="@BOOST_LIBRARYDIR@"
+
+# Python paths and directories
+$PYTHON_EXE="@PYTHON_EXECUTABLE@"
+$PYTHON_DIR="$builddir\python"
+$QPID_PYTHON_TEST="$PYTHON_DIR\commands\qpid-python-test"
+if ( !(Test-Path "$PYTHON_DIR") -and (Test-Path "$top_srcdir\..\python")) {
+ $PYTHON_DIR="$top_srcdir\..\python"
+ $QPID_PYTHON_TEST="$PYTHON_DIR\qpid-python-test"
+}
+$QPID_TESTS="$top_srcdir\..\tests"
+$QPID_TESTS_PY="$QPID_TESTS\src\py"
+$QPID_TOOLS="$top_srcdir\..\tools"
+$QPID_TOOLS_LIBS="$QPID_TOOLS\src\py"
+$QMF_LIB="$top_srcdir\..\extras\qmf\src\py"
+$PYTHON_COMMANDS="$QPID_TOOLS\src\py"
+$env:PYTHONPATH="$srcdir;$PYTHON_DIR;$PYTHON_COMMANDS;$QPID_TESTS_PY;$QPID_TOOLS_LIBS;$QMF_LIB;$env:PYTHONPATH"
+$QPID_CONFIG_EXEC="$PYTHON_COMMANDS\qpid-config"
+$QPID_ROUTE_EXEC="$PYTHON_COMMANDS\qpid-route"
+$QPID_HA_TOOL_EXEC="$PYTHON_COMMANDS\qpid-ha-tool"
+
+# Executables
+$env:QPIDD_EXEC="$top_builddir\src\@CMAKE_BUILD_TYPE@\qpidd.exe"
+$env:QPID_WATCHDOG_EXEC="$top_builddir\src\qpidd_watchdog"
+
+# Test executables
+$QPID_TEST_EXEC_DIR="$builddir\@CMAKE_BUILD_TYPE@"
+$RECEIVER_EXEC="$QPID_TEST_EXEC_DIR\receiver"
+$SENDER_EXEC="$QPID_TEST_EXEC_DIR\sender"
+
+# Path
+$env:PATH="$top_builddir\src\@CMAKE_BUILD_TYPE@;$builddir\@CMAKE_BUILD_TYPE@;$srcdir;$PYTHON_COMMANDS;$QPID_TEST_EXEC_DIR;@BOOST_LIBRARYDIR@;$env:PATH"
+
+# Modules
+$env:TEST_STORE_LIB="$testmoduledir\test_store.so"
+
+#exportmodule() { test -f $moduledir/$2 && eval "export $1=$moduledir/$2"; }
+#exportmodule ACL_LIB acl.so
+#exportmodule CLUSTER_LIB cluster.so
+#exportmodule SSLCONNECTOR_LIB sslconnector.so
+#exportmodule SSL_LIB ssl.so
+#exportmodule WATCHDOG_LIB watchdog.so
+#exportmodule XML_LIB xml.so
+
+# Qpid options
+$env:QPID_NO_MODULE_DIR="1" # Don't accidentally load installed modules
+$env:QPID_DATA_DIR= # Default to no data dir, not ~/.qpidd
+
+# Options for boost test framework
+$env:BOOST_TEST_SHOW_PROGRESS="yes"
+$env:BOOST_TEST_CATCH_SYSTEM_ERRORS="no"
diff --git a/qpid/cpp/src/tests/test_env.sh.in b/qpid/cpp/src/tests/test_env.sh.in
new file mode 100644
index 0000000000..1c4c117e4b
--- /dev/null
+++ b/qpid/cpp/src/tests/test_env.sh.in
@@ -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.
+#
+
+absdir() { echo `cd $1 && pwd`; }
+
+# Environment variables substituted by cmake.
+export srcdir=`absdir @abs_srcdir@`
+export builddir=`absdir @abs_builddir@`
+export top_srcdir=`absdir @abs_top_srcdir@`
+export top_builddir=`absdir @abs_top_builddir@`
+export moduledir=$top_builddir/src@builddir_lib_suffix@
+export pythonswigdir=$top_builddir/bindings/qpid/python/
+export pythonswiglibdir=$top_builddir/bindings/qpid/python@builddir_lib_suffix@
+export testmoduledir=$builddir@builddir_lib_suffix@
+export QPID_INSTALL_PREFIX=@prefix@
+
+# Tools substituted by cmake
+enable_valgrind=${enable_valgrind-@ENABLE_VALGRIND@}
+if [ "$enable_valgrind" = "ON" ] ; then
+ export VALGRIND=@VALGRIND_EXECUTABLE@
+fi
+export SASL_PW=@SASLPASSWD2_EXECUTABLE@
+export PYTHON=@PYTHON_EXECUTABLE@
+
+# Python paths and directories
+export PYTHON_DIR=$builddir/python
+export QPID_PYTHON_TEST=$PYTHON_DIR/commands/qpid-python-test
+if [ ! -d $PYTHON_DIR -a -d $top_srcdir/../python ]; then
+ export PYTHON_DIR=$top_srcdir/../python
+ export QPID_PYTHON_TEST=$PYTHON_DIR/qpid-python-test
+fi
+export QPID_TESTS=$top_srcdir/../tests
+export QPID_TESTS_PY=$QPID_TESTS/src/py
+export QPID_TOOLS=$top_srcdir/../tools
+export QMF_LIB=$top_srcdir/../extras/qmf/src/py
+export PYTHON_COMMANDS=$QPID_TOOLS/src/py
+export PYTHONPATH_SWIG=$pythonswigdir:$pythonswiglibdir
+export PYTHONPATH=$srcdir:$PYTHON_DIR:$PYTHON_COMMANDS:$QPID_TESTS_PY:$QMF_LIB:$PYTHONPATH_SWIG:$PYTHONPATH
+export QPID_CONFIG_EXEC=$PYTHON_COMMANDS/qpid-config
+export QPID_ROUTE_EXEC=$PYTHON_COMMANDS/qpid-route
+export QPID_HA_EXEC=$PYTHON_COMMANDS/qpid-ha
+export PYTHONSWIGMODULE=$pythonswigdir/qpid_messaging.py
+# Executables
+export QPIDD_EXEC=$top_builddir/src/qpidd
+
+# Test executables
+export QPID_TEST_EXEC_DIR=$builddir
+export QPID_TEST_SRC_DIR=$srcdir
+export RECEIVER_EXEC=$QPID_TEST_EXEC_DIR/receiver
+export SENDER_EXEC=$QPID_TEST_EXEC_DIR/sender
+
+# Path
+export PATH=$top_builddir/src:$builddir:$srcdir:$PYTHON_COMMANDS:$QPID_TEST_EXEC_DIR:$PYTHON_DIR/commands:$PATH
+
+# Modules
+export TEST_STORE_LIB=$testmoduledir/test_store.so
+
+exportmodule() { test -f $moduledir/$2 && eval "export $1=$moduledir/$2"; }
+exportmodule HA_LIB ha.so
+exportmodule XML_LIB xml.so
+test "$STORE_LIB" || exportmodule STORE_LIB linearstore.so
+test "$STORE_LIB" || exportmodule STORE_LIB legacystore.so
+exportmodule AMQP_LIB amqp.so
+
+# Qpid options
+export QPID_NO_MODULE_DIR=1 # Don't accidentally load installed modules
+export QPID_DATA_DIR=
+export QPID_CONFIG=$srcdir/qpidd-empty.conf
+
+# Use temporary directory if $HOME does not exist
+if [ ! -e "$HOME" ]; then
+ export QPID_DATA_DIR=/tmp/qpid
+ export QPID_PID_DIR=/tmp/qpid
+fi
+
+# Options for boost test framework
+test -z "$BOOST_TEST_SHOW_PROGRESS" && export BOOST_TEST_SHOW_PROGRESS=yes
+test -z "$BOOST_TEST_CATCH_SYSTEM_ERRORS" && export BOOST_TEST_CATCH_SYSTEM_ERRORS=no
+
+# Source this for useful common testing functions
+export QPID_TEST_COMMON=$srcdir/test_env_common.sh
+
+# Proton configuration
+export QPID_PROTON_VERSION=@Proton_VERSION@
diff --git a/qpid/cpp/src/tests/test_env_common.sh b/qpid/cpp/src/tests/test_env_common.sh
new file mode 100644
index 0000000000..348664ca76
--- /dev/null
+++ b/qpid/cpp/src/tests/test_env_common.sh
@@ -0,0 +1,28 @@
+#!/usr/bin/env 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.
+#
+
+# Ensure that we have python testing tools available
+function ensure_python_tests {
+ if [ ! -d ${PYTHON_DIR} ] ; then
+ echo "Python test code not found: skipping python based test"
+ exit 0;
+ fi
+}
+
diff --git a/qpid/cpp/src/tests/test_store.cpp b/qpid/cpp/src/tests/test_store.cpp
new file mode 100644
index 0000000000..14aee7b648
--- /dev/null
+++ b/qpid/cpp/src/tests/test_store.cpp
@@ -0,0 +1,339 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+
+/**@file
+ *
+ * Message store for tests, with two roles:
+ *
+ * 1. Dump store events to a text file that can be compared to expected event
+ * sequence
+ *
+ * 2. Emulate hard-to-recreate conditions such as asynchronous completion delays
+ * or store errors.
+ *
+ * Messages with specially formatted contents trigger various actions.
+ * See class Action below for available actions and message format..
+ *
+ */
+
+#include "qpid/broker/NullMessageStore.h"
+#include "qpid/broker/Broker.h"
+#include "qpid/broker/amqp_0_10/MessageTransfer.h"
+#include "qpid/framing/AMQFrame.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/Thread.h"
+#include "qpid/Plugin.h"
+#include "qpid/Options.h"
+#include "qpid/RefCounted.h"
+#include "qpid/Msg.h"
+#include <boost/cast.hpp>
+#include <boost/lexical_cast.hpp>
+#include <memory>
+#include <ostream>
+#include <fstream>
+#include <sstream>
+
+using namespace std;
+using namespace boost;
+using namespace qpid;
+using namespace qpid::broker;
+using namespace qpid::sys;
+
+namespace qpid {
+namespace tests {
+
+namespace {
+
+bool startswith(const string& s, const string& prefix) {
+ return s.compare(0, prefix.size(), prefix) == 0;
+}
+
+void split(const string& s, vector<string>& result, const char* sep=" \t\n") {
+ size_t i = s.find_first_not_of(sep);
+ while (i != string::npos) {
+ size_t j = s.find_first_of(sep, i);
+ if (j == string::npos) {
+ result.push_back(s.substr(i));
+ break;
+ }
+ result.push_back(s.substr(i, j-i));
+ i = s.find_first_not_of(sep, j);
+ }
+}
+
+}
+
+/**
+ * Action message format is TEST_STORE_DO [<name>...]:<action> [<args>...]
+ *
+ * A list of store <name> can be included so the action only executes on one of
+ * the named stores. This is useful in a cluster setting where the same message
+ * is replicated to all broker's stores but should only trigger an action on
+ * specific ones. If no <name> is given, execute on any store.
+ *
+ */
+class Action {
+ public:
+ /** Available actions */
+ enum ActionEnum {
+ NONE,
+ THROW, ///< Throw an exception from enqueue
+ DELAY, ///< Delay completion, takes an ID string to complete.
+ COMPLETE, ///< Complete a previously delayed message, takes ID
+
+ N_ACTIONS // Count of actions, must be last
+ };
+
+ string name;
+ ActionEnum index;
+ vector<string> storeNames, args;
+
+ Action(const string& s) {
+ index = NONE;
+ if (!startswith(s, PREFIX)) return;
+ size_t colon = s.find_first_of(":");
+ if (colon == string::npos) return;
+ assert(colon >= PREFIX.size());
+ split(s.substr(PREFIX.size(), colon-PREFIX.size()), storeNames);
+ split(s.substr(colon+1), args);
+ if (args.empty()) return;
+ for (size_t i = 0; i < N_ACTIONS; ++i) {
+ if (args[0] == ACTION_NAMES[i]) {
+ name = args[0];
+ index = ActionEnum(i);
+ args.erase(args.begin());
+ break;
+ }
+ }
+ }
+
+ bool executeIn(const string& storeName) {
+ return storeNames.empty() ||
+ find(storeNames.begin(), storeNames.end(), storeName) !=storeNames.end();
+ }
+
+ private:
+ static string PREFIX;
+ static const char* ACTION_NAMES[N_ACTIONS];
+};
+
+string Action::PREFIX("TEST_STORE_DO");
+
+const char* Action::ACTION_NAMES[] = { "none", "throw", "delay", "complete" };
+
+
+struct TestStoreOptions : public Options {
+
+ string name;
+ string dump;
+ string events;
+
+ TestStoreOptions() : Options("Test Store Options") {
+ addOptions()
+ ("test-store-name", optValue(name, "NAME"),
+ "Name of test store instance.")
+ ("test-store-dump", optValue(dump, "FILE"),
+ "File to dump enqueued messages.")
+ ("test-store-events", optValue(events, "FILE"),
+ "File to log events, 1 line per event.")
+ ;
+ }
+};
+
+
+class TestStore : public NullMessageStore {
+ public:
+ TestStore(const TestStoreOptions& opts, Broker& broker_)
+ : options(opts), name(opts.name), broker(broker_)
+ {
+ QPID_LOG(info, "TestStore name=" << name
+ << " dump=" << options.dump
+ << " events=" << options.events)
+
+ if (!options.dump.empty())
+ dump.reset(new ofstream(options.dump.c_str()));
+ if (!options.events.empty())
+ events.reset(new ofstream(options.events.c_str()));
+ }
+
+ ~TestStore() {
+ for_each(threads.begin(), threads.end(), boost::bind(&Thread::join, _1));
+ }
+
+ // Dummy transaction context.
+ struct TxContext : public TPCTransactionContext {
+ static int nextId;
+ string id;
+ TxContext() : id(lexical_cast<string>(nextId++)) {}
+ TxContext(string xid) : id(xid) {}
+ };
+
+ static string getId(const TransactionContext& tx) {
+ const TxContext* tc = dynamic_cast<const TxContext*>(&tx);
+ assert(tc);
+ return tc->id;
+ }
+
+
+ bool isNull() const { return false; }
+
+ void log(const string& msg) {
+ QPID_LOG(info, "test_store: " << msg);
+ if (events.get()) *events << msg << endl << std::flush;
+ }
+
+ auto_ptr<TransactionContext> begin() {
+ auto_ptr<TxContext> tx(new TxContext());
+ log(Msg() << "<begin tx " << tx->id << ">");
+ return auto_ptr<TransactionContext>(tx);
+ }
+
+ auto_ptr<TPCTransactionContext> begin(const std::string& xid) {
+ auto_ptr<TxContext> tx(new TxContext(xid));
+ log(Msg() << "<begin tx " << tx->id << ">");
+ return auto_ptr<TPCTransactionContext>(tx);
+ }
+
+ string getContent(const intrusive_ptr<PersistableMessage>& msg) {
+ intrusive_ptr<broker::Message::Encoding> enc(
+ dynamic_pointer_cast<broker::Message::Encoding>(msg));
+ return enc->getContent();
+ }
+
+ void enqueue(TransactionContext* tx,
+ const boost::intrusive_ptr<PersistableMessage>& pmsg,
+ const PersistableQueue& queue)
+ {
+ ostringstream o;
+ string data = getContent(pmsg);
+ o << "<enqueue " << queue.getName() << " " << data;
+ if (tx) o << " tx=" << getId(*tx);
+ o << ">";
+ log(o.str());
+
+ // Dump the message if there is a dump file.
+ if (dump.get()) {
+ *dump << "Message(" << data.size() << "): " << data << endl;
+ }
+ string logPrefix = "TestStore "+name+": ";
+ Action action(data);
+ bool doComplete = true;
+ if (action.index && action.executeIn(name)) {
+ switch (action.index) {
+
+ case Action::THROW:
+ throw Exception(logPrefix + data);
+ break;
+
+ case Action::DELAY: {
+ if (action.args.empty()) {
+ QPID_LOG(error, logPrefix << "async-id needs argument: " << data);
+ break;
+ }
+ asyncIds[action.args[0]] = pmsg;
+ QPID_LOG(debug, logPrefix << "delayed completion " << action.args[0]);
+ doComplete = false;
+ break;
+ }
+
+ case Action::COMPLETE: {
+ if (action.args.empty()) {
+ QPID_LOG(error, logPrefix << "complete-id needs argument: " << data);
+ break;
+ }
+ AsyncIds::iterator i = asyncIds.find(action.args[0]);
+ if (i != asyncIds.end()) {
+ i->second->enqueueComplete();
+ QPID_LOG(debug, logPrefix << "completed " << action.args[0]);
+ asyncIds.erase(i);
+ } else {
+ QPID_LOG(info, logPrefix << "not found for completion " << action.args[0]);
+ }
+ break;
+ }
+
+ default:
+ QPID_LOG(error, logPrefix << "unknown action: " << data);
+ }
+ }
+ if (doComplete) pmsg->enqueueComplete();
+ }
+
+ void dequeue(TransactionContext* tx,
+ const boost::intrusive_ptr<PersistableMessage>& msg,
+ const PersistableQueue& queue)
+ {
+ QPID_LOG(debug, "TestStore dequeue " << queue.getName());
+ ostringstream o;
+ o<< "<dequeue " << queue.getName() << " " << getContent(msg);
+ if (tx) o << " tx=" << getId(*tx);
+ o << ">";
+ log(o.str());
+ }
+
+ void prepare(TPCTransactionContext& txn) {
+ log(Msg() << "<prepare tx=" << getId(txn) << ">");
+ }
+
+ void commit(TransactionContext& txn) {
+ log(Msg() << "<commit tx=" << getId(txn) << ">");
+ }
+
+ void abort(TransactionContext& txn) {
+ log(Msg() << "<abort tx=" << getId(txn) << ">");
+ }
+
+
+ private:
+ typedef map<string, boost::intrusive_ptr<PersistableMessage> > AsyncIds;
+
+ TestStoreOptions options;
+ string name;
+ Broker& broker;
+ vector<Thread> threads;
+ std::auto_ptr<ofstream> dump;
+ std::auto_ptr<ofstream> events;
+ AsyncIds asyncIds;
+};
+
+int TestStore::TxContext::nextId(1);
+
+struct TestStorePlugin : public Plugin {
+
+ TestStoreOptions options;
+
+ Options* getOptions() { return &options; }
+
+ void earlyInitialize (Plugin::Target& target)
+ {
+ Broker* broker = dynamic_cast<Broker*>(&target);
+ if (!broker) return;
+ boost::shared_ptr<MessageStore> p(new TestStore(options, *broker));
+ broker->setStore (p);
+ }
+
+ void initialize(qpid::Plugin::Target&) {}
+};
+
+static TestStorePlugin pluginInstance;
+
+}} // namespace qpid::tests
diff --git a/qpid/cpp/src/tests/test_tools.h b/qpid/cpp/src/tests/test_tools.h
new file mode 100644
index 0000000000..d006246299
--- /dev/null
+++ b/qpid/cpp/src/tests/test_tools.h
@@ -0,0 +1,106 @@
+#ifndef TEST_TOOLS_H
+#define TEST_TOOLS_H
+
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+#include "qpid/log/Logger.h"
+
+#include <limits.h> // Include before boost/test headers.
+#include <boost/test/test_tools.hpp>
+#include <boost/assign/list_of.hpp>
+#include <vector>
+#include <set>
+#include <ostream>
+#include <sstream>
+#include <exception>
+#include <stdexcept>
+
+// Print a sequence
+template <class T> std::ostream& seqPrint(std::ostream& o, const T& seq) {
+ std::copy(seq.begin(), seq.end(), std::ostream_iterator<typename T::value_type>(o, " "));
+ return o;
+}
+
+// Compare sequences
+template <class T, class U>
+bool seqEqual(const T& a, const U& b) {
+ typename T::const_iterator i = a.begin();
+ typename U::const_iterator j = b.begin();
+ while (i != a.end() && j != b.end() && *i == *j) { ++i; ++j; }
+ return (i == a.end()) && (j == b.end());
+}
+
+// ostream and == operators so we can compare vectors and sets with
+// boost::assign::list_of with BOOST_CHECK_EQUALS
+namespace std { // In namespace std so boost can find them.
+
+template <class T>
+ostream& operator<<(ostream& o, const vector<T>& v) { return seqPrint(o, v); }
+
+template <class T>
+ostream& operator<<(ostream& o, const set<T>& v) { return seqPrint(o, v); }
+
+template <class T>
+ostream& operator<<(ostream& o, const boost::assign_detail::generic_list<T>& l) { return seqPrint(o, l); }
+
+template <class T>
+bool operator == (const vector<T>& a, const boost::assign_detail::generic_list<T>& b) { return seqEqual(a, b); }
+
+template <class T>
+bool operator == (const boost::assign_detail::generic_list<T>& b, const vector<T>& a) { return seqEqual(a, b); }
+
+template <class T>
+bool operator == (const set<T>& a, const boost::assign_detail::generic_list<T>& b) { return seqEqual(a, b); }
+
+template <class T>
+bool operator == (const boost::assign_detail::generic_list<T>& b, const set<T>& a) { return seqEqual(a, b); }
+}
+
+namespace qpid {
+namespace tests {
+
+/** Check if types of two objects (as given by typeinfo::name()) match. */
+#define BOOST_CHECK_TYPEID_EQUAL(a,b) BOOST_CHECK_EQUAL(typeid(a).name(),typeid(b).name())
+
+/**
+ * Supress all logging in a scope, restore to previous configuration in destructor.
+ */
+struct ScopedSuppressLogging {
+ typedef qpid::log::Logger Logger;
+ ScopedSuppressLogging(Logger& l=Logger::instance()) : logger(l), opts(l.getOptions()) { l.clear(); }
+ ~ScopedSuppressLogging() { logger.configure(opts); }
+ Logger& logger;
+ qpid::log::Options opts;
+};
+
+inline std::string getLibPath(const char* envName, const char* defaultPath = 0) {
+ const char* p = std::getenv(envName);
+ if (p != 0)
+ return p;
+ if (defaultPath == 0) {
+ std::ostringstream msg;
+ msg << "Environment variable " << envName << " not set.";
+ throw std::runtime_error(msg.str());
+ }
+ return defaultPath;
+}
+
+}} // namespace qpid::tests
+
+#endif /*!TEST_TOOLS_H*/
+
diff --git a/qpid/cpp/src/tests/topic_perftest b/qpid/cpp/src/tests/topic_perftest
new file mode 100755
index 0000000000..04e1cdcffb
--- /dev/null
+++ b/qpid/cpp/src/tests/topic_perftest
@@ -0,0 +1,22 @@
+#!/usr/bin/env bash
+
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+exec `dirname $0`/run_perftest 10000 --mode topic --qt 16
diff --git a/qpid/cpp/src/tests/topictest b/qpid/cpp/src/tests/topictest
new file mode 100755
index 0000000000..f4c6e7187d
--- /dev/null
+++ b/qpid/cpp/src/tests/topictest
@@ -0,0 +1,61 @@
+#!/usr/bin/env 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.
+#
+
+# Run the C++ topic test
+
+# Clean up old log files
+rm -f subscriber_*.log
+
+# Defaults values
+SUBSCRIBERS=10
+MESSAGES=2000
+BATCHES=10
+
+while getopts "s:m:b:h:t" opt ; do
+ case $opt in
+ s) SUBSCRIBERS=$OPTARG ;;
+ m) MESSAGES=$OPTARG ;;
+ b) BATCHES=$OPTARG ;;
+ h) HOST=-h$OPTARG ;;
+ t) TRANSACTIONAL="--transactional --durable" ;;
+ ?)
+ echo "Usage: %0 [-s <subscribers>] [-m <messages.] [-b <batches>]"
+ exit 1
+ ;;
+ esac
+done
+
+subscribe() {
+ echo Start subscriber $1
+ LOG="subscriber_$1.log"
+ ./qpid-topic-listener $TRANSACTIONAL > $LOG 2>&1 && rm -f $LOG
+}
+
+publish() {
+ ./qpid-topic-publisher --messages $MESSAGES --batches $BATCHES --subscribers $SUBSCRIBERS $HOST $TRANSACTIONAL
+}
+
+for ((i=$SUBSCRIBERS ; i--; )); do
+ subscribe $i &
+done
+# FIXME aconway 2007-03-27: Hack around startup race. Fix topic test.
+sleep 2
+publish 2>&1 || exit 1
diff --git a/qpid/cpp/src/tests/topictest.ps1 b/qpid/cpp/src/tests/topictest.ps1
new file mode 100644
index 0000000000..f15b2d452c
--- /dev/null
+++ b/qpid/cpp/src/tests/topictest.ps1
@@ -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.
+#
+
+# Parameters with default values: s (subscribers) m (messages) b (batches)
+# h (host) t (false; use transactions)
+param (
+ [int]$subscribers = 10,
+ [int]$message_count = 2000,
+ [int]$batches = 10,
+ [string]$broker,
+ [switch] $t # transactional
+)
+
+# Run the C++ topic test
+[string]$me = $myInvocation.InvocationName
+$srcdir = Split-Path $me
+#$srcdir = Split-Path $myInvocation.InvocationName
+
+# Clean up old log files
+Get-Item subscriber_*.log | Remove-Item
+
+if ($t) {
+ $transactional = "--transactional --durable"
+}
+
+# Find which subdir the exes are in
+. $srcdir\find_prog.ps1 .\qpid-topic-listener.exe
+
+function subscribe {
+ param ([int]$num, [string]$sub)
+ "Start subscriber $num"
+ $LOG = "subscriber_$num.log"
+ $cmdline = ".\$sub\qpid-topic-listener $transactional > $LOG 2>&1
+ if (`$LastExitCode -ne 0) { Remove-Item $LOG }"
+ $cmdblock = $executioncontext.invokecommand.NewScriptBlock($cmdline)
+ . $srcdir\background.ps1 $cmdblock
+}
+
+function publish {
+ param ([string]$sub)
+ Invoke-Expression ".\$sub\qpid-topic-publisher --messages $message_count --batches $batches --subscribers $subscribers $host $transactional" 2>&1
+}
+
+if ($broker.length) {
+ $broker = "-h$broker"
+}
+
+$i = $subscribers
+while ($i -gt 0) {
+ subscribe $i $sub
+ $i--
+}
+
+# FIXME aconway 2007-03-27: Hack around startup race. Fix topic test.
+Start-Sleep 2
+publish $sub
+exit $LastExitCode
diff --git a/qpid/cpp/src/tests/txjob.cpp b/qpid/cpp/src/tests/txjob.cpp
new file mode 100644
index 0000000000..29394c3415
--- /dev/null
+++ b/qpid/cpp/src/tests/txjob.cpp
@@ -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.
+ *
+ */
+
+#include <iostream>
+#include <boost/bind.hpp>
+#include <boost/ptr_container/ptr_vector.hpp>
+
+#include "TestOptions.h"
+#include "qpid/client/AsyncSession.h"
+#include "qpid/client/FailoverManager.h"
+#include "qpid/client/Message.h"
+#include "qpid/client/SubscriptionManager.h"
+#include "qpid/sys/Thread.h"
+
+using namespace qpid::client;
+using namespace qpid::sys;
+
+namespace qpid {
+namespace tests {
+
+struct Args : public qpid::TestOptions
+{
+ std::string workQueue;
+ std::string source;
+ std::string dest;
+ uint messages;
+ uint jobs;
+ bool quit;
+ bool declareQueues;
+
+ Args() : workQueue("txshift-control"), source("txshift-1"), dest("txshift-2"), messages(0), jobs(0),
+ quit(false), declareQueues(false)
+ {
+ addOptions()
+ ("messages", qpid::optValue(messages, "N"), "Number of messages to shift")
+ ("jobs", qpid::optValue(jobs, "N"), "Number of shift jobs to request")
+ ("source", qpid::optValue(source, "QUEUE NAME"), "source queue from which messages will be shifted")
+ ("dest", qpid::optValue(dest, "QUEUE NAME"), "dest queue to which messages will be shifted")
+ ("work-queue", qpid::optValue(workQueue, "QUEUE NAME"), "work queue from which to take instructions")
+ ("add-quit", qpid::optValue(quit), "add a 'quit' instruction to the queue (after any other jobs)")
+ ("declare-queues", qpid::optValue(declareQueues), "issue a declare for all queues");
+ }
+};
+
+}} // namespace qpid::tests
+
+using namespace qpid::tests;
+
+//TODO: might be nice to make this capable of failover as well at some
+//point; for now its just for the setup phase.
+int main(int argc, char** argv)
+{
+ Args opts;
+ try {
+ opts.parse(argc, argv);
+ Connection connection;
+ connection.open(opts.con);
+ Session session = connection.newSession();
+ if (opts.declareQueues) {
+ session.queueDeclare(arg::queue=opts.workQueue);
+ session.queueDeclare(arg::queue=opts.source);
+ session.queueDeclare(arg::queue=opts.dest);
+ }
+ for (uint i = 0; i < opts.jobs; ++i) {
+ Message job("transfer", opts.workQueue);
+ job.getHeaders().setString("src", opts.source);
+ job.getHeaders().setString("dest", opts.dest);
+ job.getHeaders().setInt("count", opts.messages);
+ async(session).messageTransfer(arg::content=job);
+ }
+
+ if (opts.quit) {
+ async(session).messageTransfer(arg::content=Message("quit", opts.workQueue));
+ }
+
+ session.sync();
+ session.close();
+
+ return 0;
+ } catch(const std::exception& e) {
+ std::cout << e.what() << std::endl;
+ return 1;
+ }
+}
diff --git a/qpid/cpp/src/tests/txshift.cpp b/qpid/cpp/src/tests/txshift.cpp
new file mode 100644
index 0000000000..6ec28c7233
--- /dev/null
+++ b/qpid/cpp/src/tests/txshift.cpp
@@ -0,0 +1,193 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <iostream>
+#include <boost/bind.hpp>
+#include <boost/ptr_container/ptr_vector.hpp>
+
+#include "TestOptions.h"
+#include "qpid/client/AsyncSession.h"
+#include "qpid/client/FailoverManager.h"
+#include "qpid/client/Message.h"
+#include "qpid/client/SubscriptionManager.h"
+#include "qpid/log/Statement.h"
+#include "qpid/sys/Thread.h"
+
+using namespace qpid::client;
+using namespace qpid::sys;
+
+namespace qpid {
+namespace tests {
+
+struct Args : public qpid::TestOptions
+{
+ std::string workQueue;
+ uint workers;
+
+ Args() : workQueue("txshift-control"), workers(1)
+ {
+ addOptions()
+ ("workers", qpid::optValue(workers, "N"), "Number of separate worker sessions to start")
+ ("work-queue", qpid::optValue(workQueue, "NAME"), "work queue from which to take instructions");
+ }
+};
+
+struct Transfer : MessageListener
+{
+ std::string control;
+ std::string source;
+ std::string destination;
+ uint expected;
+ uint transfered;
+ SubscriptionSettings controlSettings;
+ Subscription controlSubscription;
+ SubscriptionSettings sourceSettings;
+ Subscription sourceSubscription;
+
+ Transfer(const std::string control_) : control(control_), expected(0), transfered(0) {}
+
+ void subscribeToSource(SubscriptionManager manager)
+ {
+ sourceSettings.autoAck = 0;//will accept once at the end of the batch
+ sourceSettings.flowControl = FlowControl::messageCredit(expected);
+ sourceSubscription = manager.subscribe(*this, source, sourceSettings);
+ QPID_LOG(info, "Subscribed to source: " << source << " expecting: " << expected);
+ }
+
+ void subscribeToControl(SubscriptionManager manager)
+ {
+ controlSettings.flowControl = FlowControl::messageCredit(1);
+ controlSubscription = manager.subscribe(*this, control, controlSettings);
+ QPID_LOG(info, "Subscribed to job queue");
+ }
+
+ void received(Message& message)
+ {
+ QPID_LOG(debug, "received: " << message.getData() << " for " << message.getDestination());
+ if (message.getDestination() == source) {
+ receivedFromSource(message);
+ } else if (message.getDestination() == control) {
+ receivedFromControl(message);
+ } else {
+ QPID_LOG(error, "Unexpected message: " << message.getData() << " to " << message.getDestination());
+ }
+ }
+
+ void receivedFromSource(Message& message)
+ {
+ QPID_LOG(debug, "transfering " << (transfered+1) << " of " << expected);
+ message.getDeliveryProperties().setRoutingKey(destination);
+ async(sourceSubscription.getSession()).messageTransfer(arg::content=message);
+ if (++transfered == expected) {
+ QPID_LOG(info, "completed job: " << transfered << " messages shifted from " <<
+ source << " to " << destination);
+ sourceSubscription.accept(sourceSubscription.getUnaccepted());
+ sourceSubscription.getSession().txCommit();
+ sourceSubscription.cancel();
+ //grant credit to allow broker to send us another control message
+ controlSubscription.grantMessageCredit(1);
+ }
+ }
+
+ void receivedFromControl(Message& message)
+ {
+ if (message.getData() == "transfer") {
+ source = message.getHeaders().getAsString("src");
+ destination = message.getHeaders().getAsString("dest");
+ expected = message.getHeaders().getAsInt("count");
+ transfered = 0;
+ QPID_LOG(info, "received transfer request: " << expected << " messages to be shifted from " <<
+ source << " to " << destination);
+ subscribeToSource(controlSubscription.getSubscriptionManager());
+ } else if (message.getData() == "quit") {
+ QPID_LOG(info, "received quit request");
+ controlSubscription.cancel();
+ } else {
+ std::cerr << "Rejecting invalid message: " << message.getData() << std::endl;
+ controlSubscription.getSession().messageReject(SequenceSet(message.getId()));
+ }
+ }
+
+};
+
+struct Worker : FailoverManager::Command, Runnable
+{
+ FailoverManager& connection;
+ Transfer transfer;
+ Thread runner;
+
+ Worker(FailoverManager& c, const std::string& controlQueue) : connection(c), transfer(controlQueue) {}
+
+ void run()
+ {
+ connection.execute(*this);
+ }
+
+ void start()
+ {
+ runner = Thread(this);
+ }
+
+ void join()
+ {
+ runner.join();
+ }
+
+ void execute(AsyncSession& session, bool isRetry)
+ {
+ if (isRetry) QPID_LOG(info, "Retrying...");
+ session.txSelect();
+ SubscriptionManager subs(session);
+ transfer.subscribeToControl(subs);
+ subs.run();
+ session.txCommit();//commit accept of control messages
+ }
+};
+
+}} // namespace qpid::tests
+
+using namespace qpid::tests;
+
+int main(int argc, char** argv)
+{
+ Args opts;
+ try {
+ opts.parse(argc, argv);
+ FailoverManager connection(opts.con);
+ connection.connect();
+ if (opts.workers == 1) {
+ Worker worker(connection, opts.workQueue);
+ worker.run();
+ } else {
+ boost::ptr_vector<Worker> workers;
+ for (uint i = 0; i < opts.workers; i++) {
+ workers.push_back(new Worker(connection, opts.workQueue));
+ }
+ std::for_each(workers.begin(), workers.end(), boost::bind(&Worker::start, _1));
+ std::for_each(workers.begin(), workers.end(), boost::bind(&Worker::join, _1));
+ }
+
+ return 0;
+ } catch(const std::exception& e) {
+ std::cout << e.what() << std::endl;
+ return 1;
+ }
+}
diff --git a/qpid/cpp/src/tests/unit_test.cpp b/qpid/cpp/src/tests/unit_test.cpp
new file mode 100644
index 0000000000..00c61242e4
--- /dev/null
+++ b/qpid/cpp/src/tests/unit_test.cpp
@@ -0,0 +1,23 @@
+/*
+ *
+ * Copyright (c) 2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+// Defines test_main function to link with actual unit test code.
+#define BOOST_AUTO_TEST_MAIN // Boost 1.33
+#define BOOST_TEST_MAIN
+#include "unit_test.h"
+
diff --git a/qpid/cpp/src/tests/unit_test.h b/qpid/cpp/src/tests/unit_test.h
new file mode 100644
index 0000000000..a11df2ff04
--- /dev/null
+++ b/qpid/cpp/src/tests/unit_test.h
@@ -0,0 +1,74 @@
+#ifndef QPIPD_TEST_UNIT_TEST_H_
+#define QPIPD_TEST_UNIT_TEST_H_
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+// Workaround so we can build against boost 1.33 and boost 1.34.
+// Remove when we no longer need to support 1.33.
+//
+#include <boost/version.hpp>
+#include <limits.h> // Must be inclued beofre boost/test headers.
+
+// #include the correct header file.
+//
+#if (BOOST_VERSION < 103400)
+# include <boost/test/auto_unit_test.hpp>
+#else
+# include <boost/test/unit_test.hpp>
+#endif // BOOST_VERSION
+
+// Workarounds for BOOST_AUTO_TEST_CASE|SUITE|SUITE_END
+//
+#if (BOOST_VERSION < 103300)
+
+# define QPID_AUTO_TEST_SUITE(name)
+# define QPID_AUTO_TEST_CASE(name) BOOST_AUTO_UNIT_TEST(name)
+# define QPID_AUTO_TEST_SUITE_END()
+
+#elif (BOOST_VERSION < 103400)
+// Note the trailing ';'
+# define QPID_AUTO_TEST_SUITE(name) BOOST_AUTO_TEST_SUITE(name);
+# define QPID_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END();
+
+#endif // Workarounds for BOOST_AUTO_TEST_CASE|SUITE|SUITE_END
+
+//
+// Default definitions for latest version of boost.
+//
+
+#ifndef QPID_AUTO_TEST_SUITE
+# define QPID_AUTO_TEST_SUITE(name) BOOST_AUTO_TEST_SUITE(name)
+#endif
+
+#ifndef QPID_AUTO_TEST_CASE
+# define QPID_AUTO_TEST_CASE(name) BOOST_AUTO_TEST_CASE(name)
+#endif
+
+#ifndef QPID_FIXTURE_TEST_CASE
+# define QPID_FIXTURE_TEST_CASE(name, f) BOOST_FIXTURE_TEST_CASE(name, f)
+#endif
+
+#ifndef QPID_AUTO_TEST_SUITE_END
+# define QPID_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()
+#endif
+
+#endif // !QPIPD_TEST_UNIT_TEST_H_
diff --git a/qpid/cpp/src/tests/vg_check b/qpid/cpp/src/tests/vg_check
new file mode 100644
index 0000000000..462f4cb5e4
--- /dev/null
+++ b/qpid/cpp/src/tests/vg_check
@@ -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.
+#
+
+# Check for valgrind errors. Sourced by test scripts.
+
+vg_failed() {
+ echo "Valgrind error log in $VG_LOG." 1>&2
+ cat $VG_LOG 1>&2
+ echo $1 1>&2
+ exit 1
+}
+
+vg_check()
+{
+ test -z "$1" || VG_LOG=$1
+ test -f $VG_LOG || vg_failed Valgrind log file $VG_LOG missing.
+ # Ensure there is an ERROR SUMMARY line.
+ grep -E '^==[0-9]+== ERROR SUMMARY:' $VG_LOG > /dev/null || \
+ vg_failed "No valgrind ERROR SUMMARY line in $VG_LOG."
+ # Ensure that the number of errors is 0.
+ grep -E '^==[0-9]+== ERROR SUMMARY: [^0]' $VG_LOG > /dev/null && \
+ vg_failed "Valgrind reported errors in $VG_LOG; see above."
+ # Check for leaks.
+ grep -E '^==[0-9]+== +.* lost: [^0]' $VG_LOG && \
+ vg_failed "Found memory leaks (see log file, $VG_LOG); see above."
+ true
+}
diff --git a/qpid/cpp/src/tests/windows/DisableWin32ErrorWindows.cpp b/qpid/cpp/src/tests/windows/DisableWin32ErrorWindows.cpp
new file mode 100644
index 0000000000..14f1e46606
--- /dev/null
+++ b/qpid/cpp/src/tests/windows/DisableWin32ErrorWindows.cpp
@@ -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.
+ *
+ */
+
+// This file intends to prevent Windows from throwing up error boxes and
+// offering to debug when serious errors happen. The errors are displayed
+// on stderr instead. The purpose of this is to allow the tests to proceed
+// scripted and catch the text for logging. If this behavior is desired,
+// include this file with the executable being built. If the default
+// behaviors are desired, don't include this file in the build.
+
+#if defined(_MSC_VER)
+#include <crtdbg.h>
+#endif
+#include <windows.h>
+#include <iostream>
+
+namespace qpid {
+namespace tests {
+namespace windows {
+
+// Instead of popping up a window for exceptions, just print something out
+LONG _stdcall UnhandledExceptionFilter (PEXCEPTION_POINTERS pExceptionInfo)
+{
+ DWORD dwExceptionCode = pExceptionInfo->ExceptionRecord->ExceptionCode;
+
+ if (dwExceptionCode == EXCEPTION_ACCESS_VIOLATION)
+ std::cerr << "\nERROR: ACCESS VIOLATION\n" << std::endl;
+ else
+ std::cerr << "\nERROR: UNHANDLED EXCEPTION\n" << std::endl;
+
+ return EXCEPTION_EXECUTE_HANDLER;
+}
+
+struct redirect_errors_to_stderr {
+ redirect_errors_to_stderr ();
+};
+
+static redirect_errors_to_stderr block;
+
+redirect_errors_to_stderr::redirect_errors_to_stderr()
+{
+#if defined(_MSC_VER)
+ _CrtSetReportMode (_CRT_WARN, _CRTDBG_MODE_FILE);
+ _CrtSetReportFile (_CRT_WARN, _CRTDBG_FILE_STDERR);
+ _CrtSetReportMode (_CRT_ERROR, _CRTDBG_MODE_FILE);
+ _CrtSetReportFile (_CRT_ERROR, _CRTDBG_FILE_STDERR);
+ _CrtSetReportMode (_CRT_ASSERT, _CRTDBG_MODE_FILE);
+ _CrtSetReportFile (_CRT_ASSERT, _CRTDBG_FILE_STDERR);
+#endif
+
+ // Prevent the system from displaying the critical-error-handler
+ // and can't-open-file message boxes.
+ SetErrorMode(SEM_FAILCRITICALERRORS);
+ SetErrorMode(SEM_NOOPENFILEERRORBOX);
+
+ // And this will catch all unhandled exceptions.
+ SetUnhandledExceptionFilter (&UnhandledExceptionFilter);
+}
+
+}}} // namespace
diff --git a/qpid/cpp/src/versions.cmake b/qpid/cpp/src/versions.cmake
new file mode 100644
index 0000000000..ac20e9ed7f
--- /dev/null
+++ b/qpid/cpp/src/versions.cmake
@@ -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.
+#
+
+# Library Version Information (CURRENT.REVISION.AGE):
+#
+# CURRENT => API/ABI version. Bump this if the interface changes
+# REVISION => Version of underlying implementation.
+# Bump if implementation changes but API/ABI doesn't
+# AGE => Number of API/ABI versions this is backward compatible with
+
+set (qmf_version 1.0.0)
+set (qmf2_version 1.0.0)
+set (qmfconsole_version 2.0.0)
+set (qmfengine_version 1.1.0)
+set (qpidbroker_version 2.0.0)
+set (qpidclient_version 2.0.0)
+set (qpidcommon_version 2.0.0)
+set (qpidmessaging_version 2.0.0)
+set (qpidtypes_version 1.0.0)
+set (rdmawrap_version 2.0.0)
+set (sslcommon_version 2.0.0)
+set (legacystore_version 1.0.0)
+
+string(REGEX MATCH "[0-9]*" qmf_version_major ${qmf_version})
+string(REGEX MATCH "[0-9]*" qmf2_version_major ${qmf2_version})
+string(REGEX MATCH "[0-9]*" qmfconsole_version_major ${qmfconsole_version})
+string(REGEX MATCH "[0-9]*" qmfengine_version_major ${qmfengine_version})
+string(REGEX MATCH "[0-9]*" qpidbroker_version_major ${qpidbroker_version})
+string(REGEX MATCH "[0-9]*" qpidclient_version_major ${qpidclient_version})
+string(REGEX MATCH "[0-9]*" qpidcommon_version_major ${qpidcommon_version})
+string(REGEX MATCH "[0-9]*" qpidmessaging_version_major ${qpidmessaging_version})
+string(REGEX MATCH "[0-9]*" qpidtypes_version_major ${qpidtypes_version})
+string(REGEX MATCH "[0-9]*" rdmawrap_version_major ${rdmawrap_version})
+string(REGEX MATCH "[0-9]*" sslcommon_version_major ${sslcommon_version})
+string(REGEX MATCH "[0-9]*" legacystore_version_major ${legacystore_version})
diff --git a/qpid/cpp/src/windows/QpiddBroker.cpp b/qpid/cpp/src/windows/QpiddBroker.cpp
new file mode 100644
index 0000000000..b383b7d6c7
--- /dev/null
+++ b/qpid/cpp/src/windows/QpiddBroker.cpp
@@ -0,0 +1,512 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "config.h"
+#include "qpidd.h"
+#include "SCM.h"
+#include "qpid/Exception.h"
+#include "qpid/Options.h"
+#include "qpid/Plugin.h"
+#include "qpid/sys/IntegerTypes.h"
+#include "qpid/sys/windows/check.h"
+#include "qpid/sys/Thread.h"
+#include "qpid/broker/Broker.h"
+
+#include <iostream>
+#include <string>
+#include <vector>
+#include <windows.h>
+
+namespace {
+ // This will accept args from the command line; augmented with service args.
+ std::vector<std::string> cmdline_args;
+}
+
+namespace qpid {
+namespace broker {
+
+BootstrapOptions::BootstrapOptions(const char* argv0)
+ : qpid::Options("Options"),
+ common("", QPIDD_CONF_FILE, QPIDC_CONF_FILE),
+ module(QPIDD_MODULE_DIR),
+ log(argv0)
+{
+ add(common);
+ add(module);
+ add(log);
+}
+
+void BootstrapOptions::usage() const {
+ std::cout << "Usage: qpidd [OPTIONS]" << std::endl << std::endl << *this << std::endl;
+}
+
+// Local functions to set and get the pid via a LockFile.
+namespace {
+
+const std::string TCP = "tcp";
+
+// ShutdownEvent maintains an event that can be used to ask the broker
+// to stop. Analogous to sending SIGTERM/SIGINT to the posix broker.
+// The signal() method signals the event.
+class ShutdownEvent {
+ public:
+ ShutdownEvent(int port);
+ ~ShutdownEvent();
+
+ void create();
+ void open();
+ void signal();
+
+ private:
+ std::string eventName;
+
+ protected:
+ HANDLE event;
+};
+
+class ShutdownHandler : public ShutdownEvent, public qpid::sys::Runnable {
+ public:
+ ShutdownHandler(int port, const boost::intrusive_ptr<Broker>& b)
+ : ShutdownEvent(port) { broker = b; }
+
+ private:
+ virtual void run(); // Inherited from Runnable
+ boost::intrusive_ptr<Broker> broker;
+};
+
+ShutdownEvent::ShutdownEvent(int port) : event(NULL) {
+ std::ostringstream name;
+ name << "qpidd_" << port << std::ends;
+ eventName = name.str();
+}
+
+void ShutdownEvent::create() {
+ // Auto-reset event in case multiple processes try to signal a
+ // broker that doesn't respond for some reason. Initially not signaled.
+ event = ::CreateEvent(NULL, false, false, eventName.c_str());
+ QPID_WINDOWS_CHECK_NULL(event);
+}
+
+void ShutdownEvent::open() {
+ // TODO: Might need to search Global\\ name if unadorned name fails
+ event = ::OpenEvent(EVENT_MODIFY_STATE, false, eventName.c_str());
+ QPID_WINDOWS_CHECK_NULL(event);
+}
+
+ShutdownEvent::~ShutdownEvent() {
+ ::CloseHandle(event);
+ event = NULL;
+}
+
+void ShutdownEvent::signal() {
+ QPID_WINDOWS_CHECK_NOT(::SetEvent(event), 0);
+}
+
+
+void ShutdownHandler::run() {
+ if (event == NULL)
+ return;
+ ::WaitForSingleObject(event, INFINITE);
+ if (broker.get()) {
+ broker->shutdown();
+ broker = 0; // Release the broker reference
+ }
+}
+
+// Console control handler to properly handle ctl-c.
+int ourPort;
+BOOL CtrlHandler(DWORD ctl)
+{
+ ShutdownEvent shutter(ourPort); // We have to have set up the port before interrupting
+ shutter.open();
+ shutter.signal();
+ return ((ctl == CTRL_C_EVENT || ctl == CTRL_CLOSE_EVENT) ? TRUE : FALSE);
+}
+
+template <typename T>
+class NamedSharedMemory {
+ std::string name;
+ HANDLE memory;
+ T* data;
+
+public:
+ NamedSharedMemory(const std::string&);
+ ~NamedSharedMemory();
+
+ T& create();
+ T& get();
+};
+
+template <typename T>
+NamedSharedMemory<T>::NamedSharedMemory(const std::string& n) :
+ name(n),
+ memory(NULL),
+ data(0)
+{}
+
+template <typename T>
+NamedSharedMemory<T>::~NamedSharedMemory() {
+ if (data)
+ ::UnmapViewOfFile(data);
+ if (memory != NULL)
+ ::CloseHandle(memory);
+}
+
+template <typename T>
+T& NamedSharedMemory<T>::create() {
+ assert(memory == NULL);
+
+ // Create named shared memory file
+ memory = ::CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, sizeof(T), name.c_str());
+ QPID_WINDOWS_CHECK_NULL(memory);
+
+ // Map file into memory
+ data = static_cast<T*>(::MapViewOfFile(memory, FILE_MAP_WRITE, 0, 0, 0));
+ QPID_WINDOWS_CHECK_NULL(data);
+
+ return *data;
+}
+
+template <typename T>
+T& NamedSharedMemory<T>::get() {
+ if (memory == NULL) {
+ // TODO: Might need to search Global\\ name if unadorned name fails
+ memory = ::OpenFileMapping(FILE_MAP_WRITE, FALSE, name.c_str());
+ QPID_WINDOWS_CHECK_NULL(memory);
+
+ data = static_cast<T*>(::MapViewOfFile(memory, FILE_MAP_WRITE, 0, 0, 0));
+ QPID_WINDOWS_CHECK_NULL(data);
+ }
+
+ return *data;
+}
+
+std::string brokerInfoName(uint16_t port)
+{
+ std::ostringstream path;
+ path << "qpidd_info_" << port;
+ return path.str();
+}
+
+struct BrokerInfo {
+ DWORD pid;
+};
+
+// Service-related items. Only involved when running the broker as a Windows
+// service.
+
+const std::string svcName = "qpidd";
+SERVICE_STATUS svcStatus;
+SERVICE_STATUS_HANDLE svcStatusHandle = 0;
+
+// This function is only called when the broker is run as a Windows
+// service. It receives control requests from Windows.
+VOID WINAPI SvcCtrlHandler(DWORD control)
+{
+ switch(control) {
+ case SERVICE_CONTROL_STOP:
+ svcStatus.dwCurrentState = SERVICE_STOP_PENDING;
+ svcStatus.dwControlsAccepted = 0;
+ svcStatus.dwCheckPoint = 1;
+ svcStatus.dwWaitHint = 5000; // 5 secs.
+ ::SetServiceStatus(svcStatusHandle, &svcStatus);
+ CtrlHandler(CTRL_C_EVENT);
+ break;
+
+ case SERVICE_CONTROL_INTERROGATE:
+ break;
+
+ default:
+ break;
+ }
+}
+
+VOID WINAPI ServiceMain(DWORD argc, LPTSTR *argv)
+{
+ // The arguments can come from 2 places. Args set with the executable
+ // name when the service is installed come through main() and are now
+ // in cmdline_args. Arguments set in StartService come into argc/argv
+ // above; if they are set, argv[0] is the service name. Make command
+ // line args first; StartService args come later and can override
+ // command line args.
+ int all_argc = argc + cmdline_args.size();
+ if (argc == 0 && !cmdline_args.empty())
+ ++all_argc; // No StartService args, so need to add prog name argv[0]
+ const char **all_argv = new const char *[all_argc];
+ if (all_argc > 0) {
+ int i = 0;
+ all_argv[i++] = argc > 0 ? argv[0] : svcName.c_str();
+ for (size_t j = 0; j < cmdline_args.size(); ++j)
+ all_argv[i++] = cmdline_args[j].c_str();
+ for (DWORD k = 1; k < argc; ++k)
+ all_argv[i++] = argv[k];
+ }
+
+ ::memset(&svcStatus, 0, sizeof(svcStatus));
+ svcStatusHandle = ::RegisterServiceCtrlHandler(svcName.c_str(),
+ SvcCtrlHandler);
+ svcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+ svcStatus.dwCheckPoint = 1;
+ svcStatus.dwWaitHint = 10000; // 10 secs.
+ svcStatus.dwCurrentState = SERVICE_START_PENDING;
+ ::SetServiceStatus(svcStatusHandle, &svcStatus);
+ // QpiddBroker class resets state to running.
+ svcStatus.dwWin32ExitCode = run_broker(all_argc,
+ const_cast<char**>(all_argv),
+ true);
+ svcStatus.dwCurrentState = SERVICE_STOPPED;
+ svcStatus.dwCheckPoint = 0;
+ svcStatus.dwWaitHint = 0;
+ ::SetServiceStatus(svcStatusHandle, &svcStatus);
+}
+
+} // namespace
+
+
+struct ProcessControlOptions : public qpid::Options {
+ bool quit;
+ bool check;
+ std::string transport;
+
+ ProcessControlOptions()
+ : qpid::Options("Process control options"),
+ quit(false),
+ check(false),
+ transport(TCP)
+ {
+ addOptions()
+ ("check,c", qpid::optValue(check), "Prints the broker's process ID to stdout and returns 0 if the broker is running, otherwise returns 1")
+ ("transport", qpid::optValue(transport, "TRANSPORT"), "The transport for which to return the port")
+ ("quit,q", qpid::optValue(quit), "Tells the broker to shut down");
+ }
+};
+
+struct ServiceOptions : public qpid::Options {
+ bool install;
+ bool start;
+ bool stop;
+ bool uninstall;
+ bool daemon;
+ std::string startType;
+ std::string startArgs;
+ std::string account;
+ std::string password;
+ std::string depends;
+
+ ServiceOptions()
+ : qpid::Options("Service options"),
+ install(false),
+ start(false),
+ stop(false),
+ uninstall(false),
+ daemon(false),
+ startType("demand"),
+ startArgs(""),
+ account("NT AUTHORITY\\LocalService"),
+ password(""),
+ depends("")
+ {
+ addOptions()
+ ("install", qpid::optValue(install), "Install as service")
+ ("start-type", qpid::optValue(startType, "auto|demand|disabled"), "Service start type\nApplied at install time only.")
+ ("arguments", qpid::optValue(startArgs, "COMMAND LINE ARGS"), "Arguments to pass when service auto-starts")
+ ("account", qpid::optValue(account, "(LocalService)"), "Account to run as, default is LocalService\nApplied at install time only.")
+ ("password", qpid::optValue(password, "PASSWORD"), "Account password, if needed\nApplied at install time only.")
+ ("depends", qpid::optValue(depends, "(comma delimited list)"), "Names of services that must start before this service\nApplied at install time only.")
+ ("start", qpid::optValue(start), "Start the service.")
+ ("stop", qpid::optValue(stop), "Stop the service.")
+ ("uninstall", qpid::optValue(uninstall), "Uninstall the service.");
+ }
+};
+
+struct QpiddWindowsOptions : public QpiddOptionsPrivate {
+ ProcessControlOptions control;
+ ServiceOptions service;
+ QpiddWindowsOptions(QpiddOptions *parent) : QpiddOptionsPrivate(parent) {
+ parent->add(service);
+ parent->add(control);
+ }
+};
+
+QpiddOptions::QpiddOptions(const char* argv0)
+ : qpid::Options("Options"),
+ common("", QPIDD_CONF_FILE, QPIDC_CONF_FILE),
+ module(QPIDD_MODULE_DIR),
+ log(argv0)
+{
+ add(common);
+ add(module);
+ add(broker);
+ add(log);
+
+ platform.reset(new QpiddWindowsOptions(this));
+ qpid::Plugin::addOptions(*this);
+}
+
+void QpiddOptions::usage() const {
+ std::cout << "Usage: qpidd [OPTIONS]" << std::endl << std::endl
+ << *this << std::endl;
+}
+
+int QpiddBroker::execute (QpiddOptions *options) {
+
+ // If running as a service, bump the status checkpoint to let SCM know
+ // we're still making progress.
+ if (svcStatusHandle != 0) {
+ svcStatus.dwCheckPoint++;
+ ::SetServiceStatus(svcStatusHandle, &svcStatus);
+ }
+
+ // Options that affect a running daemon.
+ QpiddWindowsOptions *myOptions =
+ reinterpret_cast<QpiddWindowsOptions *>(options->platform.get());
+ if (myOptions == 0)
+ throw qpid::Exception("Internal error obtaining platform options");
+
+ if (myOptions->service.install) {
+ // Handle start type
+ DWORD startType;
+ if (myOptions->service.startType.compare("demand") == 0)
+ startType = SERVICE_DEMAND_START;
+ else if (myOptions->service.startType.compare("auto") == 0)
+ startType = SERVICE_AUTO_START;
+ else if (myOptions->service.startType.compare("disabled") == 0)
+ startType = SERVICE_DISABLED;
+ else if (!myOptions->service.startType.empty())
+ throw qpid::Exception("Invalid service start type: " +
+ myOptions->service.startType);
+
+ // Install service and exit
+ qpid::windows::SCM manager;
+ manager.install(svcName,
+ "Apache Qpid Message Broker",
+ myOptions->service.startArgs,
+ startType,
+ myOptions->service.account,
+ myOptions->service.password,
+ myOptions->service.depends);
+ return 0;
+ }
+
+ if (myOptions->service.start) {
+ qpid::windows::SCM manager;
+ manager.start(svcName);
+ return 0;
+ }
+
+ if (myOptions->service.stop) {
+ qpid::windows::SCM manager;
+ manager.stop(svcName);
+ return 0;
+ }
+
+ if (myOptions->service.uninstall) {
+ qpid::windows::SCM manager;
+ manager.uninstall(svcName);
+ return 0;
+ }
+
+ if (myOptions->control.check || myOptions->control.quit) {
+ // Relies on port number being set via --port or QPID_PORT env variable.
+ NamedSharedMemory<BrokerInfo> info(brokerInfoName(options->broker.port));
+ int pid = info.get().pid;
+ if (pid < 0)
+ return 1;
+ if (myOptions->control.check)
+ std::cout << pid << std::endl;
+ if (myOptions->control.quit) {
+ ShutdownEvent shutter(options->broker.port);
+ shutter.open();
+ shutter.signal();
+ HANDLE brokerHandle = ::OpenProcess(SYNCHRONIZE, false, pid);
+ QPID_WINDOWS_CHECK_NULL(brokerHandle);
+ ::WaitForSingleObject(brokerHandle, INFINITE);
+ ::CloseHandle(brokerHandle);
+ }
+ return 0;
+ }
+
+ boost::intrusive_ptr<Broker> brokerPtr(new Broker(options->broker));
+
+ // Need the correct port number to use in the pid file name.
+ if (options->broker.port == 0)
+ options->broker.port = brokerPtr->getPort(myOptions->control.transport);
+
+ BrokerInfo info;
+ info.pid = ::GetCurrentProcessId();
+
+ NamedSharedMemory<BrokerInfo> sharedInfo(brokerInfoName(options->broker.port));
+ sharedInfo.create() = info;
+
+ // Allow the broker to receive a shutdown request via a qpidd --quit
+ // command. Note that when the broker is run as a service this operation
+ // should not be allowed.
+ ourPort = options->broker.port;
+ ShutdownHandler waitShut(ourPort, brokerPtr);
+ waitShut.create();
+ qpid::sys::Thread waitThr(waitShut); // Wait for shutdown event
+ ::SetConsoleCtrlHandler((PHANDLER_ROUTINE)CtrlHandler, TRUE);
+ brokerPtr->accept();
+ std::cout << options->broker.port << std::endl;
+
+ // If running as a service, tell SCM we're up. There's still a chance
+ // that store recovery will drag out the time before the broker actually
+ // responds to requests, but integrating that mechanism with the SCM
+ // updating is probably more work than it's worth.
+ if (svcStatusHandle != 0) {
+ svcStatus.dwCheckPoint = 0;
+ svcStatus.dwWaitHint = 0;
+ svcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
+ svcStatus.dwCurrentState = SERVICE_RUNNING;
+ ::SetServiceStatus(svcStatusHandle, &svcStatus);
+ }
+
+ brokerPtr->run();
+ waitShut.signal(); // In case we shut down some other way
+ waitThr.join();
+ return 0;
+}
+
+}} // namespace qpid::broker
+
+int main(int argc, char* argv[])
+{
+ // If started as a service, notify the SCM we're up. Else just run.
+ // If as a service, StartServiceControlDispatcher doesn't return until
+ // the service is stopped.
+ SERVICE_TABLE_ENTRY dispatchTable[] =
+ {
+ { "", (LPSERVICE_MAIN_FUNCTION)qpid::broker::ServiceMain },
+ { NULL, NULL }
+ };
+ // Copy any command line args to be available in case we're started
+ // as a service. Pick these back up in ServiceMain.
+ for (int i = 1; i < argc; ++i)
+ cmdline_args.push_back(argv[i]);
+
+ if (!StartServiceCtrlDispatcher(dispatchTable)) {
+ DWORD err = ::GetLastError();
+ if (err == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) // Run as console
+ return qpid::broker::run_broker(argc, argv);
+ throw QPID_WINDOWS_ERROR(err);
+ }
+ return 0;
+}
diff --git a/qpid/cpp/src/windows/SCM.cpp b/qpid/cpp/src/windows/SCM.cpp
new file mode 100644
index 0000000000..2eeb143427
--- /dev/null
+++ b/qpid/cpp/src/windows/SCM.cpp
@@ -0,0 +1,332 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include "qpid/log/Statement.h"
+#include "qpid/sys/windows/check.h"
+#include "SCM.h"
+
+#pragma comment(lib, "advapi32.lib")
+
+namespace qpid {
+namespace windows {
+
+namespace {
+
+// Container that will close a SC_HANDLE upon destruction.
+class AutoServiceHandle {
+public:
+ AutoServiceHandle(SC_HANDLE h_ = NULL) : h(h_) {}
+ ~AutoServiceHandle() { if (h != NULL) ::CloseServiceHandle(h); }
+ void release() { h = NULL; }
+ void reset(SC_HANDLE newHandle)
+ {
+ if (h != NULL)
+ ::CloseServiceHandle(h);
+ h = newHandle;
+ }
+ operator SC_HANDLE() const { return h; }
+
+private:
+ SC_HANDLE h;
+};
+
+}
+
+SCM::SCM() : scmHandle(NULL)
+{
+}
+
+SCM::~SCM()
+{
+ if (NULL != scmHandle)
+ ::CloseServiceHandle(scmHandle);
+}
+
+/**
+ * Install this executable as a service
+ */
+void SCM::install(const string& serviceName,
+ const string& serviceDesc,
+ const string& args,
+ DWORD startType,
+ const string& account,
+ const string& password,
+ const string& depends)
+{
+ // Handle dependent service name list; Windows wants a set of nul-separated
+ // names ending with a double nul.
+ string depends2 = depends;
+ if (!depends2.empty()) {
+ // CDL to null delimiter w/ trailing double null
+ size_t p = 0;
+ while ((p = depends2.find_first_of( ',', p)) != string::npos)
+ depends2.replace(p, 1, 1, '\0');
+ depends2.push_back('\0');
+ depends2.push_back('\0');
+ }
+
+#if 0
+ // I'm nervous about adding a user/password check here. Is this a
+ // potential attack vector, letting users check passwords without
+ // control? -Steve Huston, Feb 24, 2011
+
+ // Validate account, password
+ HANDLE hToken = NULL;
+ bool logStatus = false;
+ if (!account.empty() && !password.empty() &&
+ !(logStatus = ::LogonUserA(account.c_str(),
+ "",
+ password.c_str(),
+ LOGON32_LOGON_NETWORK,
+ LOGON32_PROVIDER_DEFAULT,
+ &hToken ) != 0))
+ std::cout << "warning: supplied account & password failed with LogonUser." << std::endl;
+ if (logStatus)
+ ::CloseHandle(hToken);
+#endif
+
+ // Get fully qualified .exe name
+ char myPath[MAX_PATH];
+ DWORD myPathLength = ::GetModuleFileName(NULL, myPath, MAX_PATH);
+ QPID_WINDOWS_CHECK_NOT(myPathLength, 0);
+ string imagePath(myPath, myPathLength);
+ if (!args.empty())
+ imagePath += " " + args;
+
+ // Ensure there's a handle to the SCM database.
+ openSvcManager();
+
+ // Create the service
+ SC_HANDLE svcHandle;
+ svcHandle = ::CreateService(scmHandle, // SCM database
+ serviceName.c_str(), // name of service
+ serviceDesc.c_str(), // name to display
+ SERVICE_ALL_ACCESS, // desired access
+ SERVICE_WIN32_OWN_PROCESS, // service type
+ startType, // start type
+ SERVICE_ERROR_NORMAL, // error cntrl type
+ imagePath.c_str(), // path to service's binary w/ optional arguments
+ NULL, // no load ordering group
+ NULL, // no tag identifier
+ depends2.empty() ? NULL : depends2.c_str(),
+ account.empty() ? NULL : account.c_str(), // account name, or NULL for LocalSystem
+ password.empty() ? NULL : password.c_str()); // password, or NULL for none
+ QPID_WINDOWS_CHECK_NULL(svcHandle);
+ ::CloseServiceHandle(svcHandle);
+ QPID_LOG(info, "Service installed successfully");
+}
+
+/**
+ *
+ */
+void SCM::uninstall(const string& serviceName)
+{
+ // Ensure there's a handle to the SCM database.
+ openSvcManager();
+ AutoServiceHandle svc(::OpenService(scmHandle,
+ serviceName.c_str(),
+ DELETE));
+ QPID_WINDOWS_CHECK_NULL((SC_HANDLE)svc);
+ QPID_WINDOWS_CHECK_NOT(::DeleteService(svc), 0);
+ QPID_LOG(info, "Service deleted successfully.");
+}
+
+/**
+ * Attempt to start the service.
+ */
+void SCM::start(const string& serviceName)
+{
+ // Ensure we have a handle to the SCM database.
+ openSvcManager();
+
+ // Get a handle to the service.
+ AutoServiceHandle svc(::OpenService(scmHandle,
+ serviceName.c_str(),
+ SERVICE_ALL_ACCESS));
+ QPID_WINDOWS_CHECK_NULL(svc);
+
+ // Check the status in case the service is not stopped.
+ DWORD state = waitForStateChangeFrom(svc, SERVICE_STOP_PENDING);
+ if (state == SERVICE_STOP_PENDING)
+ throw qpid::Exception("Timed out waiting for running service to stop.");
+
+ // Attempt to start the service.
+ QPID_WINDOWS_CHECK_NOT(::StartService(svc, 0, NULL), 0);
+
+ QPID_LOG(info, "Service start pending...");
+
+ // Check the status until the service is no longer start pending.
+ state = waitForStateChangeFrom(svc, SERVICE_START_PENDING);
+ // Determine whether the service is running.
+ if (state == SERVICE_RUNNING) {
+ QPID_LOG(info, "Service started successfully");
+ }
+ else {
+ throw qpid::Exception(QPID_MSG("Service not yet running; state now " << state));
+ }
+}
+
+/**
+ *
+ */
+void SCM::stop(const string& serviceName)
+{
+ // Ensure a handle to the SCM database.
+ openSvcManager();
+
+ // Get a handle to the service.
+ AutoServiceHandle svc(::OpenService(scmHandle,
+ serviceName.c_str(),
+ SERVICE_STOP | SERVICE_QUERY_STATUS |
+ SERVICE_ENUMERATE_DEPENDENTS));
+ QPID_WINDOWS_CHECK_NULL(svc);
+
+ // Make sure the service is not already stopped; if it's stop-pending,
+ // wait for it to finalize.
+ DWORD state = waitForStateChangeFrom(svc, SERVICE_STOP_PENDING);
+ if (state == SERVICE_STOPPED) {
+ QPID_LOG(info, "Service is already stopped");
+ return;
+ }
+
+ // If the service is running, dependencies must be stopped first.
+ std::auto_ptr<ENUM_SERVICE_STATUS> deps;
+ DWORD numDeps = getDependentServices(svc, deps);
+ for (DWORD i = 0; i < numDeps; i++)
+ stop(deps.get()[i].lpServiceName);
+
+ // Dependents stopped; send a stop code to the service.
+ SERVICE_STATUS_PROCESS ssp;
+ if (!::ControlService(svc, SERVICE_CONTROL_STOP, (LPSERVICE_STATUS)&ssp))
+ throw qpid::Exception(QPID_MSG("Stopping " << serviceName << ": " <<
+ qpid::sys::strError(::GetLastError())));
+
+ // Wait for the service to stop.
+ state = waitForStateChangeFrom(svc, SERVICE_STOP_PENDING);
+ if (state == SERVICE_STOPPED)
+ QPID_LOG(info, QPID_MSG("Service " << serviceName <<
+ " stopped successfully."));
+}
+
+/**
+ *
+ */
+void SCM::openSvcManager()
+{
+ if (NULL != scmHandle)
+ return;
+
+ scmHandle = ::OpenSCManager(NULL, // local computer
+ NULL, // ServicesActive database
+ SC_MANAGER_ALL_ACCESS); // Rights
+ QPID_WINDOWS_CHECK_NULL(scmHandle);
+}
+
+DWORD SCM::waitForStateChangeFrom(SC_HANDLE svc, DWORD originalState)
+{
+ SERVICE_STATUS_PROCESS ssStatus;
+ DWORD bytesNeeded;
+ DWORD waitTime;
+ if (!::QueryServiceStatusEx(svc, // handle to service
+ SC_STATUS_PROCESS_INFO, // information level
+ (LPBYTE)&ssStatus, // address of structure
+ sizeof(ssStatus), // size of structure
+ &bytesNeeded)) // size needed if buffer is too small
+ throw QPID_WINDOWS_ERROR(::GetLastError());
+
+ // Save the tick count and initial checkpoint.
+ DWORD startTickCount = ::GetTickCount();
+ DWORD oldCheckPoint = ssStatus.dwCheckPoint;
+
+ // Wait for the service to change out of the noted state.
+ while (ssStatus.dwCurrentState == originalState) {
+ // Do not wait longer than the wait hint. A good interval is
+ // one-tenth of the wait hint but not less than 1 second
+ // and not more than 10 seconds.
+ waitTime = ssStatus.dwWaitHint / 10;
+ if (waitTime < 1000)
+ waitTime = 1000;
+ else if (waitTime > 10000)
+ waitTime = 10000;
+
+ ::Sleep(waitTime);
+
+ // Check the status until the service is no longer stop pending.
+ if (!::QueryServiceStatusEx(svc,
+ SC_STATUS_PROCESS_INFO,
+ (LPBYTE) &ssStatus,
+ sizeof(ssStatus),
+ &bytesNeeded))
+ throw QPID_WINDOWS_ERROR(::GetLastError());
+
+ if (ssStatus.dwCheckPoint > oldCheckPoint) {
+ // Continue to wait and check.
+ startTickCount = ::GetTickCount();
+ oldCheckPoint = ssStatus.dwCheckPoint;
+ } else {
+ if ((::GetTickCount() - startTickCount) > ssStatus.dwWaitHint)
+ break;
+ }
+ }
+ return ssStatus.dwCurrentState;
+}
+
+/**
+ * Get the services that depend on @arg svc. All dependent service info
+ * is returned in an array of ENUM_SERVICE_STATUS structures via @arg deps.
+ *
+ * @retval The number of dependent services.
+ */
+DWORD SCM::getDependentServices(SC_HANDLE svc,
+ std::auto_ptr<ENUM_SERVICE_STATUS>& deps)
+{
+ DWORD bytesNeeded;
+ DWORD numEntries;
+
+ // Pass a zero-length buffer to get the required buffer size.
+ if (::EnumDependentServices(svc,
+ SERVICE_ACTIVE,
+ 0,
+ 0,
+ &bytesNeeded,
+ &numEntries)) {
+ // If the Enum call succeeds, then there are no dependent
+ // services, so do nothing.
+ return 0;
+ }
+
+ if (::GetLastError() != ERROR_MORE_DATA)
+ throw QPID_WINDOWS_ERROR((::GetLastError()));
+
+ // Allocate a buffer for the dependencies.
+ deps.reset((LPENUM_SERVICE_STATUS)(new char[bytesNeeded]));
+ // Enumerate the dependencies.
+ if (!::EnumDependentServices(svc,
+ SERVICE_ACTIVE,
+ deps.get(),
+ bytesNeeded,
+ &bytesNeeded,
+ &numEntries))
+ throw QPID_WINDOWS_ERROR((::GetLastError()));
+ return numEntries;
+}
+
+} } // namespace qpid::windows
diff --git a/qpid/cpp/src/windows/SCM.h b/qpid/cpp/src/windows/SCM.h
new file mode 100644
index 0000000000..bdc73bc210
--- /dev/null
+++ b/qpid/cpp/src/windows/SCM.h
@@ -0,0 +1,109 @@
+#ifndef WINDOWS_SCM_H
+#define WINDOWS_SCM_H
+
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+#include <memory>
+#include <string>
+using std::string;
+
+#ifdef UNICODE
+#undef UNICODE
+#endif
+
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+
+#include <windows.h>
+
+namespace qpid {
+namespace windows {
+
+/**
+ * @class SCM
+ *
+ * Access the Windows Service Control Manager.
+ */
+class SCM
+{
+public:
+ SCM();
+ ~SCM();
+
+ /**
+ * Install this executable as a service
+ *
+ * @param serviceName The name of the service
+ * @param serviceDesc Description of the service's purpose
+ * @param args The argument list to pass into the service
+ * @param startType The start type: SERVICE_DEMAND_START,
+ * SERVICE_AUTO_START, SERVICE_DISABLED
+ * @param account If not empty, the account name to install this
+ * service under
+ * @param password If not empty, the account password to install this
+ * service with
+ * @param depends If not empty, a comma delimited list of services
+ * that must start before this one
+ */
+ void install(const string& serviceName,
+ const string& serviceDesc,
+ const string& args,
+ DWORD startType = SERVICE_DEMAND_START,
+ const string& account = "NT AUTHORITY\\LocalSystem",
+ const string& password = "",
+ const string& depends = "");
+
+ /**
+ * Uninstall this executable as a service
+ *
+ * @param serviceName the name of the service
+ */
+ void uninstall(const string& serviceName);
+
+ /**
+ * Start the specified service
+ *
+ * @param serviceName the name of the service
+ */
+ void start(const string& serviceName);
+
+ /**
+ * Stop the specified service
+ *
+ * @param serviceName the name of the service
+ */
+ void stop(const string &serviceName);
+
+private:
+ SC_HANDLE scmHandle;
+
+ void openSvcManager();
+ DWORD waitForStateChangeFrom(SC_HANDLE svc, DWORD originalState);
+ DWORD getDependentServices(SC_HANDLE svc,
+ std::auto_ptr<ENUM_SERVICE_STATUS>& deps);
+
+};
+
+}} // namespace qpid::windows
+
+#endif /* #ifndef WINDOWS_SCM_H */
diff --git a/qpid/cpp/src/windows/resources/qpid-icon.ico b/qpid/cpp/src/windows/resources/qpid-icon.ico
new file mode 100644
index 0000000000..112f5d8f1f
--- /dev/null
+++ b/qpid/cpp/src/windows/resources/qpid-icon.ico
Binary files differ
diff --git a/qpid/cpp/src/windows/resources/template-resource.rc b/qpid/cpp/src/windows/resources/template-resource.rc
new file mode 100644
index 0000000000..8ca0a90890
--- /dev/null
+++ b/qpid/cpp/src/windows/resources/template-resource.rc
@@ -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.
+//
+
+#include "version-resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "windows.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (U.S.) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+#ifdef _WIN32
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+#endif //_WIN32
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "version-resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""afxres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION ${winverFileVersionBinary}
+ PRODUCTVERSION ${winverProductVersionBinary}
+ FILEFLAGSMASK 0x17L
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x4L
+ FILETYPE 0x2L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904b0"
+ BEGIN
+ VALUE "FileDescription", "${winverFileDescription}"
+ VALUE "FileVersion", "${winverFileVersionString}"
+ VALUE "LegalCopyright", "${winverLegalCopyright}"
+ VALUE "InternalName", "${winverInternalName}"
+ VALUE "OriginalFilename", "${winverOriginalFilename}"
+ VALUE "ProductName", "${winverProductName}"
+ VALUE "ProductVersion", "${winverProductVersionString}"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
+
+// Icon with lowest ID value placed first to ensure application icon
+// remains consistent on all systems.
+IDI_ICON1 ICON "qpid-icon.ico"
+
+#endif // English (U.S.) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/qpid/cpp/src/windows/resources/version-resource.h b/qpid/cpp/src/windows/resources/version-resource.h
new file mode 100644
index 0000000000..bf942abbaf
--- /dev/null
+++ b/qpid/cpp/src/windows/resources/version-resource.h
@@ -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.
+//
+
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Preserved for common usage by any Qpid exe/dll.
+
+#define IDI_ICON1 101
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 104
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif