summaryrefslogtreecommitdiff
path: root/qpid/java/systests/src/main/java/org
diff options
context:
space:
mode:
Diffstat (limited to 'qpid/java/systests/src/main/java/org')
-rw-r--r--qpid/java/systests/src/main/java/org/apache/mina/transport/vmpipe/support/VmPipeIdleStatusChecker.java125
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/client/AMQQueueDeferredOrderingTest.java145
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/client/AMQTestConnection_0_10.java34
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/client/DispatcherTest.java238
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/client/MessageListenerMultiConsumerImmediatePrefetch.java44
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/client/MessageListenerMultiConsumerTest.java253
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/client/MessageListenerTest.java261
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/client/MultipleJCAProviderRegistrationTest.java82
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/client/ResetMessageListenerTest.java229
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/client/SessionCreateTest.java63
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/client/message/AMQPEncodedMapMessageTest.java283
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/client/message/NonQpidObjectMessage.java236
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/client/ssl/SSLTest.java181
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/ManagementActorLoggingTest.java536
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageConnectionStatisticsTest.java102
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageStatisticsConfigurationTest.java177
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageStatisticsDeliveryTest.java110
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageStatisticsReportingTest.java90
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageStatisticsTest.java233
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageStatisticsTestCase.java128
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/BrokerStartupTest.java149
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/configuration/ServerConfigurationFileTest.java89
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/exchange/MessagingTestConfigProperties.java308
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/exchange/ReturnUnroutableMandatoryMessageTest.java309
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/failover/FailoverMethodTest.java254
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/failover/MessageDisappearWithIOExceptionTest.java338
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/failure/HeapExhaustion.java236
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AbstractTestLogging.java448
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AccessControlLoggingTest.java174
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AlertingTest.java202
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/logging/BindingLoggingTest.java271
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/logging/BrokerLoggingTest.java1003
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ChannelLoggingTest.java313
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ConnectionLoggingTest.java192
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/logging/DerbyMessageStoreLoggingTest.java574
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/logging/DurableQueueLoggingTest.java307
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ExchangeLoggingTest.java217
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ManagementLoggingTest.java305
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/logging/MemoryMessageStoreLoggingTest.java186
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/logging/QueueLoggingTest.java183
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/logging/SubscriptionLoggingTest.java458
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/logging/TransientQueueLoggingTest.java30
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/logging/VirtualHostLoggingTest.java134
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/persistent/NoLocalAfterRecoveryTest.java246
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/queue/ConflationQueueTest.java435
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/queue/DeepQueueConsumeWithSelector.java159
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/queue/ModelTest.java343
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/queue/MultipleTransactedBatchProducerTest.java246
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/queue/PersistentTestManual.java276
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/queue/PriorityTest.java212
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/queue/ProducerFlowControlTest.java496
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/queue/QueueDepthWithSelectorTest.java176
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/queue/SubscriptionTestHelper.java294
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/queue/TimeToLiveTest.java370
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/AbstractACLTestCase.java285
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExhaustiveACLTest.java195
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLFileTest.java184
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLJMXTest.java244
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLTest.java37
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalAdminACLTest.java186
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/SimpleACLTest.java644
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/security/firewall/FirewallConfigTest.java298
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/store/PersistentStoreTest.java193
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/store/SlowMessageStore.java321
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/util/AveragedRun.java66
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/util/RunStats.java57
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/server/util/TimedRun.java52
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/systest/GlobalQueuesTest.java223
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/systest/GlobalTopicsTest.java36
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/systest/MergeConfigurationTest.java124
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/systest/SubscriptionTest.java146
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/systest/TestingBaseCase.java241
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/systest/TopicTest.java85
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/client/CancelTest.java100
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/client/DupsOkTest.java167
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/client/FlowControlTest.java213
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserAutoAckTest.java528
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserClientAckTest.java34
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserDupsOkTest.java31
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserNoAckTest.java33
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserPreAckTest.java32
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserTransactedTest.java31
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/client/RollbackOrderTest.java194
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/client/destination/AddressBasedDestinationTest.java1072
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/client/failover/FailoverTest.java389
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/client/message/JMSDestinationTest.java373
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/client/message/MessageToStringTest.java257
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/client/message/ObjectMessageTest.java141
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/client/message/SelectorTest.java310
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/client/queue/LVQTest.java84
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/client/queue/QueuePolicyTest.java110
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/client/timeouts/SyncWaitDelayTest.java112
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/client/timeouts/SyncWaitTimeoutDelayTest.java72
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/AMQPPublisher.java54
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/Assertion.java39
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/AssertionBase.java66
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/BrokerLifecycleAware.java70
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/CauseFailure.java42
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/CauseFailureUserPrompt.java65
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/Circuit.java109
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/CircuitEnd.java91
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/CircuitEndBase.java152
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/DropInTest.java51
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/ExceptionMonitor.java205
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkBaseCase.java301
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkClientBaseCase.java31
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkTestContext.java48
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/LocalAMQPCircuitFactory.java168
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/LocalCircuitFactory.java316
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/MessageIdentityVector.java167
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/MessageMonitor.java105
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/MessagingTestConfigProperties.java685
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/NotApplicableAssertion.java112
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/Publisher.java74
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/Receiver.java92
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/TestCaseVector.java88
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/TestClientDetails.java86
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/TestUtils.java192
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchFailureException.java45
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchThread.java124
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchronizer.java69
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/LocalClockSynchronizer.java73
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockReference.java165
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockSynchronizer.java463
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedCircuitImpl.java469
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedPublisherImpl.java95
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedReceiverImpl.java95
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/TestClientCircuitEnd.java324
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/Coordinator.java539
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/DistributedTestDecorator.java166
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/FanOutTestDecorator.java244
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/InteropTestDecorator.java209
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/OptOutTestCase.java69
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/TestClient.java499
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/TestClientControlledTest.java108
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/listeners/XMLTestListener.java409
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalAMQPPublisherImpl.java133
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalCircuitImpl.java306
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalPublisherImpl.java170
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalReceiverImpl.java144
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/package.html43
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/AMQPFeatureDecorator.java96
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/CauseFailureDecorator.java95
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/CauseFailureInVM.java70
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/InVMBrokerDecorator.java136
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/BaseCircuitFactory.java136
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/CircuitFactory.java102
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/FanOutCircuitFactory.java198
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/InteropCircuitFactory.java152
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/testcases/FailoverTest.java119
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/testcases/ImmediateMessageTest.java303
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/testcases/MandatoryMessageTest.java321
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/testcases/RollbackTest.java132
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/testcases/TTLTest.java154
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/Acknowledge2ConsumersTest.java193
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/AcknowledgeAfterFailoverOnMessageTest.java429
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/AcknowledgeAfterFailoverTest.java317
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/AcknowledgeOnMessageTest.java226
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/AcknowledgeTest.java179
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/FailoverBeforeConsumingRecoverTest.java40
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/QuickAcking.java148
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/RecoverTest.java450
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/BytesMessageTest.java284
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/FieldTableMessageTest.java163
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/InvalidDestinationTest.java104
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/LargeMessageTest.java190
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/MapMessageTest.java1271
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/MultipleConnectionTest.java218
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/ObjectMessageTest.java278
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/PropertyValueTest.java408
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/PubSubTwoConnectionTest.java75
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/ReceiveTest.java82
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/SessionStartTest.java115
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/TextMessageTest.java248
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/close/CloseTest.java58
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/AMQConnectionTest.java405
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/AMQSessionTest.java110
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/DynamicQueueExchangeCreateTest.java90
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseOkTest.java239
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseTest.java394
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/channelclose/CloseWithBlockingReceiveTest.java81
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/connection/CloseAfterConnectionFailureTest.java124
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/connection/ConnectionCloseTest.java111
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/connection/ConnectionStartTest.java158
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/connection/ConnectionTest.java301
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/connection/ExceptionListenerTest.java62
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/Client.java143
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/CombinedTest.java69
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/Service.java95
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/ServiceCreator.java112
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/SpecialQueue.java46
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/message/ObjectMessageTest.java335
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/protocol/AMQProtocolSessionTest.java106
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/temporaryqueue/TemporaryQueueTest.java258
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/close/CloseBeforeAckTest.java142
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/close/JavaServerCloseRaceConditionTest.java119
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/close/MessageRequeueTest.java373
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/close/TopicPublisherCloseTest.java69
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/close/VerifyAckingOkDuringClose.java160
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ct/DurableSubscriberTest.java506
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/message/JMSPropertiesTest.java204
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/message/StreamMessageTest.java162
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/message/UTF8En4
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/message/UTF8Jp4
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/message/UTF8Test.java111
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/publish/DirtyTransactedPublishTest.java403
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java844
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/TopicPublisherTest.java76
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/TopicSessionTest.java453
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/CommitRollbackTest.java572
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactedTest.java348
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutConfigurationTest.java82
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutDisabledTest.java72
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutTest.java335
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutTestCase.java253
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/xa/AbstractXATestCase.java132
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/xa/FaultTest.java409
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/xa/QueueTest.java657
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/unit/xa/TopicTest.java1711
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/utils/ConversationFactory.java476
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/utils/FailoverBaseCase.java115
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/utils/JMXTestUtils.java435
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidBrokerTestCase.java1363
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidClientConnection.java289
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidClientConnectionHelper.java295
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/utils/ReflectionUtils.java228
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/utils/ReflectionUtilsException.java44
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/test/utils/protocol/TestIoSession.java104
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/util/ClasspathScanner.java234
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitor.java238
-rw-r--r--qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitorTest.java275
231 files changed, 52779 insertions, 0 deletions
diff --git a/qpid/java/systests/src/main/java/org/apache/mina/transport/vmpipe/support/VmPipeIdleStatusChecker.java b/qpid/java/systests/src/main/java/org/apache/mina/transport/vmpipe/support/VmPipeIdleStatusChecker.java
new file mode 100644
index 0000000000..5323ad28bf
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/mina/transport/vmpipe/support/VmPipeIdleStatusChecker.java
@@ -0,0 +1,125 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.mina.transport.vmpipe.support;
+
+import org.apache.mina.common.IdleStatus;
+
+import java.util.HashMap;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * This file is a patch to override MINA, because of the IdentityHashMap bug. Workaround to be supplied in MINA 1.0.7.
+ * This patched file will be removed once upgraded onto a newer MINA.
+ *
+ * Dectects idle sessions and fires <tt>sessionIdle</tt> events to them.
+ *
+ * @author The Apache Directory Project (mina-dev@directory.apache.org)
+ */
+public class VmPipeIdleStatusChecker
+{
+ private static final VmPipeIdleStatusChecker INSTANCE = new VmPipeIdleStatusChecker();
+
+ public static VmPipeIdleStatusChecker getInstance()
+ {
+ return INSTANCE;
+ }
+
+ private final Map sessions = new HashMap(); // will use as a set
+
+ private final Worker worker = new Worker();
+
+ private VmPipeIdleStatusChecker()
+ {
+ worker.start();
+ }
+
+ public void addSession(VmPipeSessionImpl session)
+ {
+ synchronized (sessions)
+ {
+ sessions.put(session, session);
+ }
+ }
+
+ private class Worker extends Thread
+ {
+ private Worker()
+ {
+ super("VmPipeIdleStatusChecker");
+ setDaemon(true);
+ }
+
+ public void run()
+ {
+ for (;;)
+ {
+ try
+ {
+ Thread.sleep(1000);
+ }
+ catch (InterruptedException e)
+ { }
+
+ long currentTime = System.currentTimeMillis();
+
+ synchronized (sessions)
+ {
+ Iterator it = sessions.keySet().iterator();
+ while (it.hasNext())
+ {
+ VmPipeSessionImpl session = (VmPipeSessionImpl) it.next();
+ if (!session.isConnected())
+ {
+ it.remove();
+ }
+ else
+ {
+ notifyIdleSession(session, currentTime);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void notifyIdleSession(VmPipeSessionImpl session, long currentTime)
+ {
+ notifyIdleSession0(session, currentTime, session.getIdleTimeInMillis(IdleStatus.BOTH_IDLE), IdleStatus.BOTH_IDLE,
+ Math.max(session.getLastIoTime(), session.getLastIdleTime(IdleStatus.BOTH_IDLE)));
+ notifyIdleSession0(session, currentTime, session.getIdleTimeInMillis(IdleStatus.READER_IDLE), IdleStatus.READER_IDLE,
+ Math.max(session.getLastReadTime(), session.getLastIdleTime(IdleStatus.READER_IDLE)));
+ notifyIdleSession0(session, currentTime, session.getIdleTimeInMillis(IdleStatus.WRITER_IDLE), IdleStatus.WRITER_IDLE,
+ Math.max(session.getLastWriteTime(), session.getLastIdleTime(IdleStatus.WRITER_IDLE)));
+ }
+
+ private void notifyIdleSession0(VmPipeSessionImpl session, long currentTime, long idleTime, IdleStatus status,
+ long lastIoTime)
+ {
+ if ((idleTime > 0) && (lastIoTime != 0) && ((currentTime - lastIoTime) >= idleTime))
+ {
+ session.increaseIdleCount(status);
+ session.getFilterChain().fireSessionIdle(session, status);
+ }
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/client/AMQQueueDeferredOrderingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/client/AMQQueueDeferredOrderingTest.java
new file mode 100644
index 0000000000..ca10126aa7
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/client/AMQQueueDeferredOrderingTest.java
@@ -0,0 +1,145 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.client;
+
+import javax.jms.Connection;
+import javax.jms.Session;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.TextMessage;
+
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.client.transport.TransportConnection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AMQQueueDeferredOrderingTest extends QpidBrokerTestCase
+{
+
+ private static final int NUM_MESSAGES = 1000;
+
+ private Connection con;
+ private Session session;
+ private AMQQueue queue;
+ private MessageConsumer consumer;
+
+ private static final Logger _logger = LoggerFactory.getLogger(AMQQueueDeferredOrderingTest.class);
+
+ private ASyncProducer producerThread;
+
+ private class ASyncProducer extends Thread
+ {
+
+ private MessageProducer producer;
+ private final Logger _logger = LoggerFactory.getLogger(ASyncProducer.class);
+ private Session session;
+ private int start;
+ private int end;
+
+ public ASyncProducer(AMQQueue q, int start, int end) throws Exception
+ {
+ this.session = con.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ this._logger.info("Create Consumer of Q1");
+ this.producer = this.session.createProducer(q);
+ this.start = start;
+ this.end = end;
+ }
+
+ public void run()
+ {
+ try
+ {
+ this._logger.info("Starting to send messages");
+ for (int i = start; i < end && !interrupted(); i++)
+ {
+ producer.send(session.createTextMessage(Integer.toString(i)));
+ }
+ this._logger.info("Sent " + (end - start) + " messages");
+ }
+ catch (JMSException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ _logger.info("Create Connection");
+ con = getConnection();
+ _logger.info("Create Session");
+ session = con.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ _logger.info("Create Q");
+ queue = new AMQQueue(new AMQShortString("amq.direct"), new AMQShortString("Q"), new AMQShortString("Q"),
+ false, true);
+ _logger.info("Create Consumer of Q");
+ consumer = session.createConsumer(queue);
+ _logger.info("Start Connection");
+ con.start();
+ }
+
+ public void testPausedOrder() throws Exception
+ {
+
+ // Setup initial messages
+ _logger.info("Creating first producer thread");
+ producerThread = new ASyncProducer(queue, 0, NUM_MESSAGES / 2);
+ producerThread.start();
+ // Wait for them to be done
+ producerThread.join();
+
+ // Setup second set of messages to produce while we consume
+ _logger.info("Creating second producer thread");
+ producerThread = new ASyncProducer(queue, NUM_MESSAGES / 2, NUM_MESSAGES);
+ producerThread.start();
+
+ // Start consuming and checking they're in order
+ _logger.info("Consuming messages");
+ for (int i = 0; i < NUM_MESSAGES; i++)
+ {
+ Message msg = consumer.receive(3000);
+ assertNotNull("Message should not be null", msg);
+ assertTrue("Message should be a text message", msg instanceof TextMessage);
+ assertEquals("Message content does not match expected", Integer.toString(i), ((TextMessage) msg).getText());
+ }
+ }
+
+ protected void tearDown() throws Exception
+ {
+ _logger.info("Interuptting producer thread");
+ producerThread.interrupt();
+ _logger.info("Closing connection");
+ con.close();
+
+ super.tearDown();
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(AMQQueueDeferredOrderingTest.class);
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/client/AMQTestConnection_0_10.java b/qpid/java/systests/src/main/java/org/apache/qpid/client/AMQTestConnection_0_10.java
new file mode 100644
index 0000000000..09a03a17a0
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/client/AMQTestConnection_0_10.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.qpid.client;
+
+import org.apache.qpid.transport.Connection;
+
+public class AMQTestConnection_0_10 extends AMQConnection
+{
+ public AMQTestConnection_0_10(String url) throws Exception
+ {
+ super(url);
+ }
+
+ public Connection getConnection()
+ {
+ return((AMQConnectionDelegate_0_10)_delegate).getQpidConnection();
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/client/DispatcherTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/client/DispatcherTest.java
new file mode 100644
index 0000000000..a8a23c2c41
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/client/DispatcherTest.java
@@ -0,0 +1,238 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.client;
+
+import java.util.Hashtable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.jms.Connection;
+import javax.jms.ConnectionFactory;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageListener;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.naming.Context;
+import javax.naming.spi.InitialContextFactory;
+
+import org.apache.qpid.client.transport.TransportConnection;
+import org.apache.qpid.jndi.PropertiesFileInitialContextFactory;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * QPID-293 Setting MessageListener after connection has started can cause messages to be "lost" on a internal delivery queue
+ * <p/>
+ * The message delivery process:
+ * Mina puts a message on _queue in AMQSession and the dispatcher thread take()s
+ * from here and dispatches to the _consumers. If the _consumer doesn't have a message listener set at connection start
+ * then messages are stored on _synchronousQueue (which needs to be > 1 to pass JMS TCK as multiple consumers on a
+ * session can run in any order and a synchronous put/poll will block the dispatcher).
+ * <p/>
+ * When setting the message listener later the _synchronousQueue is just poll()'ed and the first message delivered
+ * the remaining messages will be left on the queue and lost, subsequent messages on the session will arrive first.
+ */
+public class DispatcherTest extends QpidBrokerTestCase
+{
+ private static final Logger _logger = LoggerFactory.getLogger(DispatcherTest.class);
+
+ Context _context;
+
+ private static final int MSG_COUNT = 6;
+ private int _receivedCount = 0;
+ private int _receivedCountWhileStopped = 0;
+ private Connection _clientConnection, _producerConnection;
+ private MessageConsumer _consumer;
+ MessageProducer _producer;
+ Session _clientSession, _producerSession;
+
+ private final CountDownLatch _allFirstMessagesSent = new CountDownLatch(1); // all messages Sent Lock
+ private final CountDownLatch _allSecondMessagesSent = new CountDownLatch(1); // all messages Sent Lock
+
+ private volatile boolean _connectionStopped = false;
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ // Create Client 1
+ _clientConnection = getConnection();
+
+ _clientSession = _clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ Queue queue = _clientSession.createQueue(this.getClass().getName());
+ _consumer = _clientSession.createConsumer(queue);
+
+ // Create Producer
+ _producerConnection = getConnection();
+
+ _producerConnection.start();
+
+ _producerSession = _producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ _producer = _producerSession.createProducer(queue);
+
+ for (int msg = 0; msg < MSG_COUNT; msg++)
+ {
+ _producer.send(_producerSession.createTextMessage("Message " + msg));
+ }
+ }
+
+ protected void tearDown() throws Exception
+ {
+
+ _clientConnection.close();
+
+ _producerConnection.close();
+ super.tearDown();
+ }
+
+ public void testAsynchronousRecieve()
+ {
+ _logger.info("Test Start");
+
+ assertTrue(!((AMQConnection) _clientConnection).started());
+
+ // Set default Message Listener
+ try
+ {
+ _consumer.setMessageListener(new MessageListener()
+ {
+ public void onMessage(Message message)
+ {
+ _logger.info("Client 1 ML 1 Received Message(" + _receivedCount + "):" + message);
+
+ _receivedCount++;
+
+ if (_receivedCount == MSG_COUNT)
+ {
+ _allFirstMessagesSent.countDown();
+ }
+
+ if (_connectionStopped)
+ {
+ _logger.info("Running with Message:" + _receivedCount);
+ }
+
+ if (_connectionStopped && (_allFirstMessagesSent.getCount() == 0))
+ {
+ _receivedCountWhileStopped++;
+ }
+
+ if (_allFirstMessagesSent.getCount() == 0)
+ {
+ if (_receivedCount == (MSG_COUNT * 2))
+ {
+ _allSecondMessagesSent.countDown();
+ }
+ }
+ }
+ });
+
+ assertTrue("Connecion should not be started", !((AMQConnection) _clientConnection).started());
+ _clientConnection.start();
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Error Setting Default ML on consumer1");
+ }
+
+ try
+ {
+ _allFirstMessagesSent.await(1000, TimeUnit.MILLISECONDS);
+ }
+ catch (InterruptedException e)
+ {
+ // do nothing
+ }
+
+ try
+ {
+ assertTrue("Connecion should be started", ((AMQConnection) _clientConnection).started());
+ _clientConnection.stop();
+ _connectionStopped = true;
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Error stopping connection");
+ }
+
+ try
+ {
+ _logger.error("Send additional messages");
+
+ for (int msg = 0; msg < MSG_COUNT; msg++)
+ {
+ _producer.send(_producerSession.createTextMessage("Message " + msg));
+ }
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Unable to send additional messages", e);
+ }
+
+ try
+ {
+ Thread.sleep(1000);
+ }
+ catch (InterruptedException e)
+ {
+ // ignore
+ }
+
+ try
+ {
+ _logger.info("Restarting connection");
+
+ _connectionStopped = false;
+ _clientConnection.start();
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Error Setting Better ML on consumer1", e);
+ }
+
+ _logger.info("Waiting upto 2 seconds for messages");
+
+ try
+ {
+ _allSecondMessagesSent.await(1000, TimeUnit.MILLISECONDS);
+ }
+ catch (InterruptedException e)
+ {
+ // do nothing
+ }
+
+ assertEquals("Messages not received correctly", 0, _allFirstMessagesSent.getCount());
+ assertEquals("Messages not received correctly", 0, _allSecondMessagesSent.getCount());
+ assertEquals("Client didn't get all messages", MSG_COUNT * 2, _receivedCount);
+ assertEquals("Messages received while stopped is not 0", 0, _receivedCountWhileStopped);
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(DispatcherTest.class);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/client/MessageListenerMultiConsumerImmediatePrefetch.java b/qpid/java/systests/src/main/java/org/apache/qpid/client/MessageListenerMultiConsumerImmediatePrefetch.java
new file mode 100644
index 0000000000..7461f6c200
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/client/MessageListenerMultiConsumerImmediatePrefetch.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.client;
+
+/**
+ * QPID-293 Setting MessageListener after connection has started can cause messages to be "lost" on a internal delivery
+ * queue <p/> The message delivery process: Mina puts a message on _queue in AMQSession and the dispatcher thread
+ * take()s from here and dispatches to the _consumers. If the _consumer1 doesn't have a message listener set at
+ * connection start then messages are stored on _synchronousQueue (which needs to be > 1 to pass JMS TCK as multiple
+ * consumers on a session can run in any order and a synchronous put/poll will block the dispatcher). <p/> When setting
+ * the message listener later the _synchronousQueue is just poll()'ed and the first message delivered the remaining
+ * messages will be left on the queue and lost, subsequent messages on the session will arrive first.
+ */
+public class MessageListenerMultiConsumerImmediatePrefetch extends MessageListenerMultiConsumerTest
+{
+ protected void setUp() throws Exception
+ {
+ System.setProperty(AMQSession.IMMEDIATE_PREFETCH, "true");
+ super.setUp();
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(MessageListenerMultiConsumerImmediatePrefetch.class);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/client/MessageListenerMultiConsumerTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/client/MessageListenerMultiConsumerTest.java
new file mode 100644
index 0000000000..ca83b99120
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/client/MessageListenerMultiConsumerTest.java
@@ -0,0 +1,253 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.client;
+
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.Connection;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageListener;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.naming.Context;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.UUID;
+
+/**
+ * QPID-293 Setting MessageListener after connection has started can cause messages to be "lost" on a internal delivery
+ * queue <p/> The message delivery process: Mina puts a message on _queue in AMQSession and the dispatcher thread
+ * take()s from here and dispatches to the _consumers. If the _consumer1 doesn't have a message listener set at
+ * connection start then messages are stored on _synchronousQueue (which needs to be > 1 to pass JMS TCK as multiple
+ * consumers on a session can run in any order and a synchronous put/poll will block the dispatcher). <p/> When setting
+ * the message listener later the _synchronousQueue is just poll()'ed and the first message delivered the remaining
+ * messages will be left on the queue and lost, subsequent messages on the session will arrive first.
+ */
+public class MessageListenerMultiConsumerTest extends QpidBrokerTestCase
+{
+ private static final Logger _logger = LoggerFactory.getLogger(MessageListenerMultiConsumerTest.class);
+
+ Context _context;
+
+ private static final int MSG_COUNT = 6;
+ private int receivedCount1 = 0;
+ private int receivedCount2 = 0;
+ private Connection _clientConnection;
+ private MessageConsumer _consumer1;
+ private MessageConsumer _consumer2;
+ private Session _clientSession1;
+ private Queue _queue;
+ private final CountDownLatch _allMessagesSent = new CountDownLatch(2); // all messages Sent Lock
+ private static final String QUEUE_NAME = "queue" + UUID.randomUUID().toString();
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ // Create Client 1
+ _clientConnection = getConnection("guest", "guest");
+
+ _clientConnection.start();
+
+ _clientSession1 = _clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ _queue =_clientSession1.createQueue(QUEUE_NAME);
+
+ _consumer1 = _clientSession1.createConsumer(_queue);
+
+ // Create Client 2
+ Session clientSession2 = _clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ _consumer2 = clientSession2.createConsumer(_queue);
+
+ // Create Producer
+ Connection producerConnection = getConnection("guest", "guest");
+
+ producerConnection.start();
+
+ Session producerSession = producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ MessageProducer producer = producerSession.createProducer(_queue);
+
+ for (int msg = 0; msg < MSG_COUNT; msg++)
+ {
+ producer.send(producerSession.createTextMessage("Message " + msg));
+ }
+
+ producerConnection.close();
+
+ }
+
+ protected void tearDown() throws Exception
+ {
+ _clientConnection.close();
+ super.tearDown();
+ }
+
+ public void testRecieveInterleaved() throws Exception
+ {
+ int msg = 0;
+ int MAX_LOOPS = MSG_COUNT * 2;
+ for (int loops = 0; (msg < MSG_COUNT) || (loops < MAX_LOOPS); loops++)
+ {
+
+ if (_consumer1.receive(1000) != null)
+ {
+ msg++;
+ }
+
+ if (_consumer2.receive(1000) != null)
+ {
+ msg++;
+ }
+ }
+
+ assertEquals("Not all messages received.", MSG_COUNT, msg);
+ }
+
+ public void testAsynchronousRecieve() throws Exception
+ {
+ _consumer1.setMessageListener(new MessageListener()
+ {
+ public void onMessage(Message message)
+ {
+ _logger.info("Client 1 Received Message(" + receivedCount1 + "):" + message);
+
+ receivedCount1++;
+
+ if (receivedCount1 == (MSG_COUNT / 2))
+ {
+ _allMessagesSent.countDown();
+ }
+
+ }
+ });
+
+ _consumer2.setMessageListener(new MessageListener()
+ {
+ public void onMessage(Message message)
+ {
+ _logger.info("Client 2 Received Message(" + receivedCount2 + "):" + message);
+
+ receivedCount2++;
+ if (receivedCount2 == (MSG_COUNT / 2))
+ {
+ _allMessagesSent.countDown();
+ }
+ }
+ });
+
+ _logger.info("Waiting upto 2 seconds for messages");
+
+ try
+ {
+ _allMessagesSent.await(4000, TimeUnit.MILLISECONDS);
+ }
+ catch (InterruptedException e)
+ {
+ // do nothing
+ }
+
+ assertEquals(MSG_COUNT, receivedCount1 + receivedCount2);
+ }
+
+ public void testRecieveC2Only() throws Exception
+ {
+ if (
+ !Boolean.parseBoolean(
+ System.getProperties().getProperty(AMQSession.IMMEDIATE_PREFETCH,
+ AMQSession.IMMEDIATE_PREFETCH_DEFAULT)))
+ {
+ _logger.info("Performing Receive only on C2");
+ for (int msg = 0; msg < MSG_COUNT; msg++)
+ {
+ assertTrue(MSG_COUNT + " msg should be received. Only received:" + msg, _consumer2.receive(1000) != null);
+ }
+ }
+ }
+
+ public void testRecieveBoth() throws Exception
+ {
+ if (
+ !Boolean.parseBoolean(
+ System.getProperties().getProperty(AMQSession.IMMEDIATE_PREFETCH,
+ AMQSession.IMMEDIATE_PREFETCH_DEFAULT)))
+ {
+ _logger.info("Performing Receive only with two consumers on one session ");
+
+ //Create a new consumer on session one that we don't use
+ _clientSession1.createConsumer(_queue);
+
+ int msg;
+ for (msg = 0; msg < (MSG_COUNT / 2); msg++)
+ {
+
+ // Attempt to receive up to half the messages
+ // The other half may have gone to the consumer above
+ final Message message = _consumer1.receive(1000);
+ if(message == null)
+ {
+ break;
+ }
+
+ }
+
+ _consumer1.close();
+ // This will close the unused consumer above.
+ _clientSession1.close();
+
+
+ // msg will now have recorded the number received on session 1
+ // attempt to retrieve the rest on session 2
+ for (; msg < MSG_COUNT ; msg++)
+ {
+ assertTrue("Failed at msg id" + msg, _consumer2.receive(1000) != null);
+ }
+
+ }
+ else
+ {
+ _logger.info("Performing Receive only on both C1 and C2");
+
+ for (int msg = 0; msg < (MSG_COUNT / 2); msg++)
+ {
+
+ assertTrue(_consumer1.receive(3000) != null);
+ }
+
+ for (int msg = 0; msg < (MSG_COUNT / 2); msg++)
+ {
+ assertTrue(_consumer2.receive(3000) != null);
+ }
+ }
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(MessageListenerMultiConsumerTest.class);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/client/MessageListenerTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/client/MessageListenerTest.java
new file mode 100644
index 0000000000..e4d1c72208
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/client/MessageListenerTest.java
@@ -0,0 +1,261 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.client;
+
+import org.apache.qpid.server.configuration.ServerConfiguration;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.util.LogMonitor;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.Connection;
+import javax.jms.ExceptionListener;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageListener;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.naming.Context;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * QPID-293 Setting MessageListener after connection has started can cause messages to be "lost" on a internal delivery
+ * queue <p/> The message delivery process: Mina puts a message on _queue in AMQSession and the dispatcher thread
+ * take()s from here and dispatches to the _consumers. If the _consumer doesn't have a message listener set at
+ * connection start then messages are stored on _synchronousQueue (which needs to be > 1 to pass JMS TCK as multiple
+ * consumers on a session can run in any order and a synchronous put/poll will block the dispatcher). <p/> When setting
+ * the message listener later the _synchronousQueue is just poll()'ed and the first message delivered the remaining
+ * messages will be left on the queue and lost, subsequent messages on the session will arrive first.
+ */
+public class MessageListenerTest extends QpidBrokerTestCase implements MessageListener, ExceptionListener
+{
+ private static final Logger _logger = LoggerFactory.getLogger(MessageListenerTest.class);
+
+ Context _context;
+
+ private static final int MSG_COUNT = 5;
+ private int _receivedCount = 0;
+ private int _errorCount = 0;
+ private MessageConsumer _consumer;
+ private Connection _clientConnection;
+ private CountDownLatch _awaitMessages = new CountDownLatch(MSG_COUNT);
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ // Create Client
+ _clientConnection = getConnection("guest", "guest");
+
+ _clientConnection.start();
+
+ Session clientSession = _clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ Queue queue =clientSession.createQueue("message-listener-test-queue");
+
+ _consumer = clientSession.createConsumer(queue);
+
+ // Create Producer
+
+ Connection producerConnection = getConnection("guest", "guest");
+
+ producerConnection.start();
+
+ Session producerSession = producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ MessageProducer producer = producerSession.createProducer(queue);
+
+ for (int msg = 0; msg < MSG_COUNT; msg++)
+ {
+ producer.send(producerSession.createTextMessage("Message " + msg));
+ }
+
+ producerConnection.close();
+
+ }
+
+ protected void tearDown() throws Exception
+ {
+ if (_clientConnection != null)
+ {
+ _clientConnection.close();
+ }
+ super.tearDown();
+ }
+
+ public void testSynchronousReceive() throws Exception
+ {
+ for (int msg = 0; msg < MSG_COUNT; msg++)
+ {
+ assertTrue(_consumer.receive(2000) != null);
+ }
+ }
+
+ public void testSynchronousReceiveNoWait() throws Exception
+ {
+ for (int msg = 0; msg < MSG_COUNT; msg++)
+ {
+ assertTrue("Failed to receive message " + msg, _consumer.receiveNoWait() != null);
+ }
+ }
+
+ public void testAsynchronousReceive() throws Exception
+ {
+ _consumer.setMessageListener(this);
+
+ _logger.info("Waiting 3 seconds for messages");
+
+ try
+ {
+ _awaitMessages.await(3000, TimeUnit.MILLISECONDS);
+ }
+ catch (InterruptedException e)
+ {
+ // do nothing
+ }
+ // Should have received all async messages
+ assertEquals(MSG_COUNT, _receivedCount);
+
+ }
+
+ public void testReceiveThenUseMessageListener() throws Exception
+ {
+ _logger.error("Test disabled as initial receive is not called first");
+ // Perform initial receive to start connection
+ assertTrue(_consumer.receive(2000) != null);
+ _receivedCount++;
+
+ // Sleep to ensure remaining 4 msgs end up on _synchronousQueue
+ Thread.sleep(1000);
+
+ // Set the message listener and wait for the messages to come in.
+ _consumer.setMessageListener(this);
+
+ _logger.info("Waiting 3 seconds for messages");
+
+ try
+ {
+ _awaitMessages.await(3000, TimeUnit.MILLISECONDS);
+ }
+ catch (InterruptedException e)
+ {
+ // do nothing
+ }
+ // Should have received all async messages
+ assertEquals(MSG_COUNT, _receivedCount);
+
+ _clientConnection.close();
+
+ Connection conn = getConnection("guest", "guest");
+ Session clientSession = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ Queue queue = clientSession.createQueue("message-listener-test-queue");
+ MessageConsumer cons = clientSession.createConsumer(queue);
+ conn.start();
+
+ // check that the messages were actually dequeued
+ assertTrue(cons.receive(2000) == null);
+ }
+
+ /**
+ * Tests the case where the message listener throws an java.lang.Error.
+ *
+ */
+ public void testMessageListenerThrowsError() throws Exception
+ {
+ final String javaLangErrorMessageText = "MessageListener failed with java.lang.Error";
+ _clientConnection.setExceptionListener(this);
+
+ _awaitMessages = new CountDownLatch(1);
+
+ _consumer.setMessageListener(new MessageListener()
+ {
+ public void onMessage(Message message)
+ {
+ try
+ {
+ _logger.debug("onMessage called");
+ _receivedCount++;
+
+
+ throw new Error(javaLangErrorMessageText);
+ }
+ finally
+ {
+ _awaitMessages.countDown();
+ }
+ }
+ });
+
+
+ _logger.info("Waiting 3 seconds for message");
+ _awaitMessages.await(3000, TimeUnit.MILLISECONDS);
+
+ assertEquals("onMessage should have been called", 1, _receivedCount);
+ assertEquals("onException should NOT have been called", 0, _errorCount);
+
+ // Check that Error has been written to the application log.
+
+ LogMonitor _monitor = new LogMonitor(_outputFile);
+ assertTrue("The expected message not written to log file.",
+ _monitor.waitForMessage(javaLangErrorMessageText, LOGMONITOR_TIMEOUT));
+
+ if (_clientConnection != null)
+ {
+ try
+ {
+ _clientConnection.close();
+ }
+ catch (JMSException e)
+ {
+ // Ignore connection close errors for this test.
+ }
+ finally
+ {
+ _clientConnection = null;
+ }
+ }
+ }
+
+ public void onMessage(Message message)
+ {
+ _logger.info("Received Message(" + _receivedCount + "):" + message);
+
+ _receivedCount++;
+ _awaitMessages.countDown();
+ }
+
+ @Override
+ public void onException(JMSException e)
+ {
+ _logger.info("Exception received", e);
+ _errorCount++;
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(MessageListenerTest.class);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/client/MultipleJCAProviderRegistrationTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/client/MultipleJCAProviderRegistrationTest.java
new file mode 100644
index 0000000000..29b4dd82a7
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/client/MultipleJCAProviderRegistrationTest.java
@@ -0,0 +1,82 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.client;
+
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.server.registry.ConfigurationFileApplicationRegistry;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.client.transport.TransportConnection;
+
+import java.io.File;
+import java.security.Provider;
+import java.security.Security;
+import java.util.List;
+import java.util.LinkedList;
+
+/**
+ * QPID-1394 : Test to ensure that the client can register their custom JCAProviders after the broker to ensure that
+ * the Qpid custom authentication SASL plugins are used.
+ */
+public class MultipleJCAProviderRegistrationTest extends QpidBrokerTestCase
+{
+
+ public void setUp() throws Exception
+ {
+ _broker = VM;
+
+ super.setUp();
+ }
+
+ public void test() throws Exception
+ {
+ // Get the providers before connection
+ Provider[] providers = Security.getProviders();
+
+ // Force the client to load the providers
+ getConnection();
+
+ Provider[] afterConnectionCreation = Security.getProviders();
+
+ // Find the additions
+ List additions = new LinkedList();
+ for (Provider afterCreation : afterConnectionCreation)
+ {
+ boolean found = false;
+ for (Provider provider : providers)
+ {
+ if (provider == afterCreation)
+ {
+ found=true;
+ break;
+ }
+ }
+
+ // Record added registies
+ if (!found)
+ {
+ additions.add(afterCreation);
+ }
+ }
+
+ assertTrue("Client did not register any providers", additions.size() > 0);
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/client/ResetMessageListenerTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/client/ResetMessageListenerTest.java
new file mode 100644
index 0000000000..303da29389
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/client/ResetMessageListenerTest.java
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.client;
+
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageListener;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+import javax.naming.Context;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * QPID-293 Setting MessageListener after connection has started can cause messages to be "lost" on a internal delivery
+ * queue <p/> The message delivery process: Mina puts a message on _queue in AMQSession and the dispatcher thread
+ * take()s from here and dispatches to the _consumers. If the _consumer1 doesn't have a message listener set at
+ * connection start then messages are stored on _synchronousQueue (which needs to be > 1 to pass JMS TCK as multiple
+ * consumers on a session can run in any order and a synchronous put/poll will block the dispatcher). <p/> When setting
+ * the message listener later the _synchronousQueue is just poll()'ed and the first message delivered the remaining
+ * messages will be left on the queue and lost, subsequent messages on the session will arrive first.
+ */
+public class ResetMessageListenerTest extends QpidBrokerTestCase
+{
+ private static final Logger _logger = LoggerFactory.getLogger(ResetMessageListenerTest.class);
+
+ Context _context;
+
+ private static final int MSG_COUNT = 6;
+ private Connection _clientConnection, _producerConnection;
+ private MessageConsumer _consumer1;
+ MessageProducer _producer;
+ Session _clientSession, _producerSession;
+
+ private final CountDownLatch _allFirstMessagesSent = new CountDownLatch(MSG_COUNT); // all messages Sent Lock
+ private final CountDownLatch _allSecondMessagesSent = new CountDownLatch(MSG_COUNT); // all messages Sent Lock
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ _clientConnection = getConnection("guest", "guest");
+ _clientConnection.start();
+ // Create Client 1
+
+ _clientSession = _clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ Queue queue = _clientSession.createQueue("reset-message-listener-test-queue");
+
+ _consumer1 = _clientSession.createConsumer(queue);
+
+ // Create Producer
+ _producerConnection = getConnection("guest", "guest");
+
+ _producerConnection.start();
+
+ _producerSession = _producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ _producer = _producerSession.createProducer(queue);
+
+ TextMessage m = _producerSession.createTextMessage();
+ m.setStringProperty("rank", "first");
+ for (int msg = 0; msg < MSG_COUNT; msg++)
+ {
+ m.setText("Message " + msg);
+ _producer.send(m);
+ }
+ }
+
+ protected void tearDown() throws Exception
+ {
+ _clientConnection.close();
+
+ super.tearDown();
+ }
+
+ public void testAsynchronousRecieve()
+ {
+
+ _logger.info("Test Start");
+
+ try
+ {
+ _consumer1.setMessageListener(new MessageListener()
+ {
+ public void onMessage(Message message)
+ {
+ try
+ {
+ if (message.getStringProperty("rank").equals("first"))
+ {
+ _allFirstMessagesSent.countDown();
+ }
+ }
+ catch (JMSException e)
+ {
+ e.printStackTrace();
+ fail("error receiving message");
+ }
+ }
+ });
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Error Setting Default ML on consumer1");
+ }
+ try
+ {
+ assertTrue("Did not receive all first batch of messages",
+ _allFirstMessagesSent.await(1000, TimeUnit.MILLISECONDS));
+ _logger.info("Received first batch of messages");
+ }
+ catch (InterruptedException e)
+ {
+ // do nothing
+ }
+
+ try
+ {
+ _clientConnection.stop();
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Error stopping connection");
+ }
+
+ _logger.info("Reset Message Listener ");
+ try
+ {
+ _consumer1.setMessageListener(new MessageListener()
+ {
+ public void onMessage(Message message)
+ {
+ try
+ {
+ if (message.getStringProperty("rank").equals("first"))
+ {
+ // Something ugly will happen, it'll probably kill the dispatcher
+ fail("All first set of messages should have been received");
+ }
+ else
+ {
+ _allSecondMessagesSent.countDown();
+ }
+ }
+ catch (JMSException e)
+ {
+ e.printStackTrace();
+ // Something ugly will happen, it'll probably kill the dispatcher
+ fail("error receiving message");
+ }
+ }
+ });
+
+ _clientConnection.start();
+ }
+ catch (javax.jms.IllegalStateException e)
+ {
+ _logger.error("Connection not stopped while setting ML", e);
+ fail("Unable to change message listener:" + e.getCause());
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Error Setting Better ML on consumer1", e);
+ }
+
+ try
+ {
+ _logger.info("Send additional messages");
+ TextMessage m = _producerSession.createTextMessage();
+ m.setStringProperty("rank", "second");
+ for (int msg = 0; msg < MSG_COUNT; msg++)
+ {
+ m.setText("Message " + msg);
+ _producer.send(m);
+ }
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Unable to send additional messages", e);
+ }
+
+ _logger.info("Waiting for messages");
+
+ try
+ {
+ assertTrue(_allSecondMessagesSent.await(1000, TimeUnit.MILLISECONDS));
+ }
+ catch (InterruptedException e)
+ {
+ // do nothing
+ }
+ assertEquals("First batch of messages not received correctly", 0, _allFirstMessagesSent.getCount());
+ assertEquals("Second batch of messages not received correctly", 0, _allSecondMessagesSent.getCount());
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(ResetMessageListenerTest.class);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/client/SessionCreateTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/client/SessionCreateTest.java
new file mode 100644
index 0000000000..15900a17fe
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/client/SessionCreateTest.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.client;
+
+import javax.jms.Connection;
+import javax.jms.Session;
+import javax.naming.Context;
+
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+/**
+ * Class to check that session creation on a connection has no accidental limit
+ */
+public class SessionCreateTest extends QpidBrokerTestCase
+{
+ private static final Logger _logger = LoggerFactory.getLogger(MessageListenerTest.class);
+
+ Context _context;
+
+ private Connection _clientConnection;
+ protected int maxSessions = 65555;
+
+ public void testSessionCreationLimit() throws Exception
+ {
+ // Create Client
+ _clientConnection = getConnection("guest", "guest");
+
+ _clientConnection.start();
+
+ for (int i=0; i < maxSessions; i++)
+ {
+ Session sess = _clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ assertNotNull(sess);
+ sess.close();
+ System.out.println("created session: " + i);
+ }
+
+ _clientConnection.close();
+
+ }
+
+} \ No newline at end of file
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/client/message/AMQPEncodedMapMessageTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/client/message/AMQPEncodedMapMessageTest.java
new file mode 100644
index 0000000000..bf96dae02e
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/client/message/AMQPEncodedMapMessageTest.java
@@ -0,0 +1,283 @@
+package org.apache.qpid.client.message;
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.MapMessage;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.message.AMQPEncodedMapMessage;
+import org.apache.qpid.client.message.AbstractJMSMessage;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.ProtocolVersion;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+
+public class AMQPEncodedMapMessageTest extends QpidBrokerTestCase
+{
+ private Connection _connection;
+ private Session _session;
+ MessageConsumer _consumer;
+ MessageProducer _producer;
+ UUID myUUID = UUID.randomUUID();
+
+ public void setUp() throws Exception
+ {
+ super.setUp();
+
+ //Create Connection
+ _connection = getConnection();
+
+ //Create Session
+ _session = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ //Create Queue
+ String queueName = getTestQueueName();
+ Queue queue = _session.createQueue(queueName);
+
+ //Create Consumer
+ _consumer = _session.createConsumer(queue);
+
+ //Create Producer
+ _producer = _session.createProducer(queue);
+
+ _connection.start();
+ }
+
+ public void testEmptyMessage() throws JMSException
+ {
+ MapMessage m = _session.createMapMessage();
+ _producer.send(m);
+ AMQPEncodedMapMessage msg = (AMQPEncodedMapMessage)_consumer.receive(RECEIVE_TIMEOUT);
+ assertNotNull("Message was not received on time",msg);
+ assertEquals("Message content-type is incorrect",
+ AMQPEncodedMapMessage.MIME_TYPE,
+ ((AbstractJMSMessage)msg).getContentType());
+
+ assertEquals("Message content should be an empty map",
+ Collections.EMPTY_MAP,
+ ((AMQPEncodedMapMessage)msg).getMap());
+ }
+
+ public void testNullMessage() throws JMSException
+ {
+ MapMessage m = _session.createMapMessage();
+ ((AMQPEncodedMapMessage)m).setMap(null);
+ _producer.send(m);
+ AMQPEncodedMapMessage msg = (AMQPEncodedMapMessage)_consumer.receive(RECEIVE_TIMEOUT);
+ assertNotNull("Message was not received on time",msg);
+ assertEquals("Message content-type is incorrect",
+ AMQPEncodedMapMessage.MIME_TYPE,
+ ((AbstractJMSMessage)msg).getContentType());
+
+ assertEquals("Message content should be null",
+ null,
+ ((AMQPEncodedMapMessage)msg).getMap());
+
+ }
+
+ public void testMessageWithContent() throws JMSException
+ {
+ MapMessage m = _session.createMapMessage();
+ m.setBoolean("Boolean", true);
+ m.setByte("Byte", (byte)5);
+ byte[] bytes = new byte[]{(byte)5,(byte)8};
+ m.setBytes("Bytes", bytes);
+ m.setChar("Char", 'X');
+ m.setDouble("Double", 56.84);
+ m.setFloat("Float", Integer.MAX_VALUE + 5000);
+ m.setInt("Int", Integer.MAX_VALUE - 5000);
+ m.setShort("Short", (short)58);
+ m.setString("String", "Hello");
+ m.setObject("uuid", myUUID);
+ _producer.send(m);
+
+ AMQPEncodedMapMessage msg = (AMQPEncodedMapMessage)_consumer.receive(RECEIVE_TIMEOUT);
+ assertNotNull("Message was not received on time",msg);
+ assertEquals("Message content-type is incorrect",
+ AMQPEncodedMapMessage.MIME_TYPE,
+ ((AbstractJMSMessage)msg).getContentType());
+
+ assertEquals(true,m.getBoolean("Boolean"));
+ assertEquals((byte)5,m.getByte("Byte"));
+ byte[] bytesRcv = m.getBytes("Bytes");
+ assertNotNull("Byte array is null",bytesRcv);
+ assertEquals((byte)5,bytesRcv[0]);
+ assertEquals((byte)8,bytesRcv[1]);
+ assertEquals('X',m.getChar("Char"));
+ assertEquals(56.84,m.getDouble("Double"));
+ //assertEquals(Integer.MAX_VALUE + 5000,m.getFloat("Float"));
+ assertEquals(Integer.MAX_VALUE - 5000,m.getInt("Int"));
+ assertEquals((short)58,m.getShort("Short"));
+ assertEquals("Hello",m.getString("String"));
+ assertEquals(myUUID,(UUID)m.getObject("uuid"));
+ }
+
+
+ public void testMessageWithListEntries() throws JMSException
+ {
+ MapMessage m = _session.createMapMessage();
+
+ List<Integer> myList = getList();
+
+ m.setObject("List", myList);
+
+ List<UUID> uuidList = new ArrayList<UUID>();
+ uuidList.add(myUUID);
+ m.setObject("uuid-list", uuidList);
+ _producer.send(m);
+
+ AMQPEncodedMapMessage msg = (AMQPEncodedMapMessage)_consumer.receive(RECEIVE_TIMEOUT);
+ assertNotNull("Message was not received on time",msg);
+ assertEquals("Message content-type is incorrect",
+ AMQPEncodedMapMessage.MIME_TYPE,
+ ((AbstractJMSMessage)msg).getContentType());
+
+ List<Integer> list = (List<Integer>)msg.getObject("List");
+ assertNotNull("List not received",list);
+ Collections.sort(list);
+ int i = 1;
+ for (Integer j: list)
+ {
+ assertEquals(i,j.intValue());
+ i++;
+ }
+
+ List<UUID> list2 = (List<UUID>)msg.getObject("uuid-list");
+ assertNotNull("UUID List not received",list2);
+ assertEquals(myUUID,list2.get(0));
+ }
+
+ public void testMessageWithMapEntries() throws JMSException
+ {
+ MapMessage m = _session.createMapMessage();
+
+ Map<String,String> myMap = getMap();
+ m.setObject("Map", myMap);
+
+ Map<String,UUID> uuidMap = new HashMap<String,UUID>();
+ uuidMap.put("uuid", myUUID);
+ m.setObject("uuid-map", uuidMap);
+
+ _producer.send(m);
+
+ AMQPEncodedMapMessage msg = (AMQPEncodedMapMessage)_consumer.receive(RECEIVE_TIMEOUT);
+ assertNotNull("Message was not received on time",msg);
+ assertEquals("Message content-type is incorrect",
+ AMQPEncodedMapMessage.MIME_TYPE,
+ ((AbstractJMSMessage)msg).getContentType());
+
+ Map<String,String> map = (Map<String,String>)msg.getObject("Map");
+ assertNotNull("Map not received",map);
+ for (int i=1; i <4; i++ )
+ {
+ assertEquals("String" + i,map.get("Key" + i));
+ i++;
+ }
+
+ Map<String,UUID> map2 = (Map<String,UUID>)msg.getObject("uuid-map");
+ assertNotNull("Map not received",map2);
+ assertEquals(myUUID,map2.get("uuid"));
+ }
+
+ public void testMessageWithNestedListsAndMaps() throws JMSException
+ {
+ MapMessage m = _session.createMapMessage();
+
+ Map<String,Object> myMap = new HashMap<String,Object>();
+ myMap.put("map", getMap());
+ myMap.put("list", getList());
+
+ m.setObject("Map", myMap);
+ _producer.send(m);
+
+ AMQPEncodedMapMessage msg = (AMQPEncodedMapMessage)_consumer.receive(RECEIVE_TIMEOUT);
+ assertNotNull("Message was not received on time",msg);
+ assertEquals("Message content-type is incorrect",
+ AMQPEncodedMapMessage.MIME_TYPE,
+ ((AbstractJMSMessage)msg).getContentType());
+
+ Map<String,Object> mainMap = (Map<String,Object>)msg.getObject("Map");
+ assertNotNull("Main Map not received",mainMap);
+
+ Map<String,String> map = (Map<String,String>)mainMap.get("map");
+ assertNotNull("Nested Map not received",map);
+ for (int i=1; i <4; i++ )
+ {
+ assertEquals("String" + i,map.get("Key" + i));
+ i++;
+ }
+
+ List<Integer> list = (List<Integer>)mainMap.get("list");
+ assertNotNull("Nested List not received",list);
+ Collections.sort(list);
+
+ int i = 1;
+ for (Integer j: list)
+ {
+ assertEquals(i,j.intValue());
+ i++;
+ }
+ }
+
+ private List<Integer> getList()
+ {
+ List<Integer> myList = new ArrayList<Integer>();
+ myList.add(1);
+ myList.add(2);
+ myList.add(3);
+
+ return myList;
+ }
+
+ private Map<String,String> getMap()
+ {
+ Map<String,String> myMap = new HashMap<String,String>();
+ myMap.put("Key1","String1");
+ myMap.put("Key2","String2");
+ myMap.put("Key3","String3");
+
+ return myMap;
+ }
+
+ public void tearDown() throws Exception
+ {
+ //clean up
+ _connection.close();
+
+ super.tearDown();
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/client/message/NonQpidObjectMessage.java b/qpid/java/systests/src/main/java/org/apache/qpid/client/message/NonQpidObjectMessage.java
new file mode 100644
index 0000000000..857adaf82c
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/client/message/NonQpidObjectMessage.java
@@ -0,0 +1,236 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.client.message;
+
+import java.io.Serializable;
+import java.util.Enumeration;
+
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.ObjectMessage;
+import javax.jms.Session;
+
+public class NonQpidObjectMessage implements ObjectMessage {
+
+ private ObjectMessage _realMessage;
+ private String _contentString;
+
+ /**
+ * Allows us to construct a JMS message which
+ * does not inherit from the Qpid message superclasses
+ * and expand our unit testing of MessageConverter et al
+ * @param session
+ */
+ public NonQpidObjectMessage(Session session) throws JMSException
+ {
+ _realMessage = session.createObjectMessage();
+ }
+
+ public String getJMSMessageID() throws JMSException {
+ return _realMessage.getJMSMessageID();
+ }
+
+ public void setJMSMessageID(String string) throws JMSException {
+ _realMessage.setJMSMessageID(string);
+ }
+
+ public long getJMSTimestamp() throws JMSException {
+ return _realMessage.getJMSTimestamp();
+ }
+
+ public void setJMSTimestamp(long l) throws JMSException {
+ _realMessage.setJMSTimestamp(l);
+ }
+
+ public byte[] getJMSCorrelationIDAsBytes() throws JMSException {
+ return _realMessage.getJMSCorrelationIDAsBytes();
+ }
+
+ public void setJMSCorrelationIDAsBytes(byte[] bytes) throws JMSException {
+ _realMessage.setJMSCorrelationIDAsBytes(bytes);
+ }
+
+ public void setJMSCorrelationID(String string) throws JMSException {
+ _realMessage.setJMSCorrelationID(string);
+ }
+
+ public String getJMSCorrelationID() throws JMSException {
+ return _realMessage.getJMSCorrelationID();
+ }
+
+ public Destination getJMSReplyTo() throws JMSException {
+ return _realMessage.getJMSReplyTo();
+ }
+
+ public void setJMSReplyTo(Destination destination) throws JMSException {
+ _realMessage.setJMSReplyTo(destination);
+ }
+
+ public Destination getJMSDestination() throws JMSException {
+ return _realMessage.getJMSDestination();
+ }
+
+ public void setJMSDestination(Destination destination) throws JMSException {
+ _realMessage.setJMSDestination(destination);
+ }
+
+ public int getJMSDeliveryMode() throws JMSException {
+ return _realMessage.getJMSDeliveryMode();
+ }
+
+ public void setJMSDeliveryMode(int i) throws JMSException {
+ _realMessage.setJMSDeliveryMode(i);
+ }
+
+ public boolean getJMSRedelivered() throws JMSException {
+ return _realMessage.getJMSRedelivered();
+ }
+
+ public void setJMSRedelivered(boolean b) throws JMSException {
+ _realMessage.setJMSRedelivered(b);
+ }
+
+ public String getJMSType() throws JMSException {
+ return _realMessage.getJMSType();
+ }
+
+ public void setJMSType(String string) throws JMSException {
+ _realMessage.setJMSType(string);
+ }
+
+ public long getJMSExpiration() throws JMSException {
+ return _realMessage.getJMSExpiration();
+ }
+
+ public void setJMSExpiration(long l) throws JMSException {
+ _realMessage.setJMSExpiration(l);
+ }
+
+ public int getJMSPriority() throws JMSException {
+ return _realMessage.getJMSPriority();
+ }
+
+ public void setJMSPriority(int i) throws JMSException {
+ _realMessage.setJMSPriority(i);
+ }
+
+ public void clearProperties() throws JMSException {
+ _realMessage.clearProperties();
+ }
+
+ public boolean propertyExists(String string) throws JMSException {
+ return _realMessage.propertyExists(string);
+ }
+
+ public boolean getBooleanProperty(String string) throws JMSException {
+ return _realMessage.getBooleanProperty(string);
+ }
+
+ public byte getByteProperty(String string) throws JMSException {
+ return _realMessage.getByteProperty(string);
+ }
+
+ public short getShortProperty(String string) throws JMSException {
+ return _realMessage.getShortProperty(string);
+ }
+
+ public int getIntProperty(String string) throws JMSException {
+ return _realMessage.getIntProperty(string);
+ }
+
+ public long getLongProperty(String string) throws JMSException {
+ return _realMessage.getLongProperty(string);
+ }
+
+ public float getFloatProperty(String string) throws JMSException {
+ return _realMessage.getFloatProperty(string);
+ }
+
+ public double getDoubleProperty(String string) throws JMSException {
+ return _realMessage.getDoubleProperty(string);
+ }
+
+ public String getStringProperty(String string) throws JMSException {
+ return _realMessage.getStringProperty(string);
+ }
+
+ public Object getObjectProperty(String string) throws JMSException {
+ return _realMessage.getObjectProperty(string);
+ }
+
+ public Enumeration getPropertyNames() throws JMSException {
+ return _realMessage.getPropertyNames();
+ }
+
+ public void setBooleanProperty(String string, boolean b) throws JMSException {
+ _realMessage.setBooleanProperty(string,b);
+ }
+
+ public void setByteProperty(String string, byte b) throws JMSException {
+ _realMessage.setByteProperty(string,b);
+ }
+
+ public void setShortProperty(String string, short i) throws JMSException {
+ _realMessage.setShortProperty(string,i);
+ }
+
+ public void setIntProperty(String string, int i) throws JMSException {
+ _realMessage.setIntProperty(string,i);
+ }
+
+ public void setLongProperty(String string, long l) throws JMSException {
+ _realMessage.setLongProperty(string,l);
+ }
+
+ public void setFloatProperty(String string, float v) throws JMSException {
+ _realMessage.setFloatProperty(string,v);
+ }
+
+ public void setDoubleProperty(String string, double v) throws JMSException {
+ _realMessage.setDoubleProperty(string,v);
+ }
+
+ public void setStringProperty(String string, String string1) throws JMSException {
+ _realMessage.setStringProperty(string,string1);
+ }
+
+ public void setObjectProperty(String string, Object object) throws JMSException {
+ _realMessage.setObjectProperty(string,object);
+ }
+
+ public void acknowledge() throws JMSException {
+ _realMessage.acknowledge();
+ }
+
+ public void clearBody() throws JMSException {
+ _realMessage.clearBody();
+ }
+
+ public void setObject(Serializable serializable) throws JMSException {
+ if (serializable instanceof String)
+ {
+ _contentString = (String)serializable;
+ }
+ }
+
+ public Serializable getObject() throws JMSException {
+ return _contentString; }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/client/ssl/SSLTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/client/ssl/SSLTest.java
new file mode 100644
index 0000000000..8cdf12eaa4
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/client/ssl/SSLTest.java
@@ -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.
+ *
+ */
+package org.apache.qpid.client.ssl;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import javax.jms.Session;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQTestConnection_0_10;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.transport.Connection;
+
+public class SSLTest extends QpidBrokerTestCase
+{
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ System.setProperty("javax.net.debug", "ssl");
+ super.setUp();
+ }
+
+ @Override
+ protected void tearDown() throws Exception
+ {
+ System.setProperty("javax.net.debug", "");
+ super.tearDown();
+ }
+
+ public void testCreateSSLContextFromConnectionURLParams()
+ {
+ if (Boolean.getBoolean("profile.use_ssl"))
+ {
+ String url = "amqp://guest:guest@test/?brokerlist='tcp://localhost:%s" +
+ "?ssl='true'&ssl_verify_hostname='true'" +
+ "&key_store='%s'&key_store_password='%s'" +
+ "&trust_store='%s'&trust_store_password='%s'" +
+ "'";
+
+ String keyStore = System.getProperty("javax.net.ssl.keyStore");
+ String keyStorePass = System.getProperty("javax.net.ssl.keyStorePassword");
+ String trustStore = System.getProperty("javax.net.ssl.trustStore");
+ String trustStorePass = System.getProperty("javax.net.ssl.trustStorePassword");
+
+ url = String.format(url,System.getProperty("test.port.ssl"),
+ keyStore,keyStorePass,trustStore,trustStorePass);
+
+ // temporarily set the trust/key store jvm args to something else
+ // to ensure we only read from the connection URL param.
+ System.setProperty("javax.net.ssl.trustStore","fessgsdgd");
+ System.setProperty("javax.net.ssl.trustStorePassword","fessgsdgd");
+ System.setProperty("javax.net.ssl.keyStore","fessgsdgd");
+ System.setProperty("javax.net.ssl.keyStorePassword","fessgsdgd");
+ try
+ {
+ AMQConnection con = new AMQConnection(url);
+ Session ssn = con.createSession(false,Session.AUTO_ACKNOWLEDGE);
+ }
+ catch (Exception e)
+ {
+ fail("SSL Connection should be successful");
+ }
+ finally
+ {
+ System.setProperty("javax.net.ssl.trustStore",trustStore);
+ System.setProperty("javax.net.ssl.trustStorePassword",trustStorePass);
+ System.setProperty("javax.net.ssl.keyStore",keyStore);
+ System.setProperty("javax.net.ssl.keyStorePassword",keyStorePass);
+ }
+ }
+ }
+
+ public void testMultipleCertsInSingleStore() throws Exception
+ {
+ if (Boolean.getBoolean("profile.use_ssl"))
+ {
+ String url = "amqp://guest:guest@test/?brokerlist='tcp://localhost:" +
+ System.getProperty("test.port.ssl") +
+ "?ssl='true'&ssl_cert_alias='app1''";
+
+ AMQTestConnection_0_10 con = new AMQTestConnection_0_10(url);
+ Connection transportCon = con.getConnection();
+ String userID = transportCon.getSecurityLayer().getUserID();
+ assertEquals("The correct certificate was not choosen","app1@acme.org",userID);
+ con.close();
+
+ url = "amqp://guest:guest@test/?brokerlist='tcp://localhost:" +
+ System.getProperty("test.port.ssl") +
+ "?ssl='true'&ssl_cert_alias='app2''";
+
+ con = new AMQTestConnection_0_10(url);
+ transportCon = con.getConnection();
+ userID = transportCon.getSecurityLayer().getUserID();
+ assertEquals("The correct certificate was not choosen","app2@acme.org",userID);
+ con.close();
+ }
+ }
+
+ public void testVerifyHostName()
+ {
+ if (Boolean.getBoolean("profile.use_ssl"))
+ {
+ String url = "amqp://guest:guest@test/?brokerlist='tcp://127.0.0.1:" +
+ System.getProperty("test.port.ssl") +
+ "?ssl='true'&ssl_verify_hostname='true''";
+
+ try
+ {
+ AMQConnection con = new AMQConnection(url);
+ fail("Hostname verification failed. No exception was thrown");
+ }
+ catch (Exception e)
+ {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ e.printStackTrace(new PrintStream(bout));
+ String strace = bout.toString();
+ assertTrue("Correct exception not thrown",strace.contains("SSL hostname verification failed"));
+ }
+
+ }
+ }
+
+ public void testVerifyLocalHost()
+ {
+ if (Boolean.getBoolean("profile.use_ssl"))
+ {
+ String url = "amqp://guest:guest@test/?brokerlist='tcp://localhost:" +
+ System.getProperty("test.port.ssl") +
+ "?ssl='true'&ssl_verify_hostname='true''";
+
+ try
+ {
+ AMQConnection con = new AMQConnection(url);
+ }
+ catch (Exception e)
+ {
+ fail("Hostname verification should succeed");
+ }
+ }
+ }
+
+ public void testVerifyLocalHostLocalDomain()
+ {
+ if (Boolean.getBoolean("profile.use_ssl"))
+ {
+ String url = "amqp://guest:guest@test/?brokerlist='tcp://localhost.localdomain:" +
+ System.getProperty("test.port.ssl") +
+ "?ssl='true'&ssl_verify_hostname='true''";
+
+ try
+ {
+ AMQConnection con = new AMQConnection(url);
+ }
+ catch (Exception e)
+ {
+ fail("Hostname verification should succeed");
+ }
+
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/ManagementActorLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/ManagementActorLoggingTest.java
new file mode 100644
index 0000000000..19657ef396
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/ManagementActorLoggingTest.java
@@ -0,0 +1,536 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.management.jmx;
+
+import org.apache.qpid.management.common.mbeans.ManagedBroker;
+import org.apache.qpid.management.common.mbeans.ManagedConnection;
+import org.apache.qpid.management.common.mbeans.ManagedExchange;
+import org.apache.qpid.server.logging.AbstractTestLogging;
+import org.apache.qpid.server.logging.subjects.AbstractTestLogSubject;
+import org.apache.qpid.test.utils.JMXTestUtils;
+
+import javax.jms.Connection;
+import javax.jms.ExceptionListener;
+import javax.jms.JMSException;
+import javax.management.JMException;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test class to test if any change in the broker JMX code is affesting the management console
+ * There are some hardcoding of management feature names and parameter names to create a customized
+ * look in the console.
+ */
+public class ManagementActorLoggingTest extends AbstractTestLogging
+{
+ private JMXTestUtils _jmxUtils;
+ private boolean _closed = false;
+ private static final String USER = "admin";
+
+ @Override
+ public void setUp() throws Exception
+ {
+ _jmxUtils = new JMXTestUtils(this, USER, USER);
+ _jmxUtils.setUp();
+ super.setUp();
+ _jmxUtils.open();
+ }
+
+ @Override
+ public void tearDown() throws Exception
+ {
+ if(!_closed)
+ {
+ _jmxUtils.close();
+ }
+ super.tearDown();
+ }
+
+ /**
+ * Description:
+ * When a JMX Management connection is made then this will be logged out.
+ *
+ * Input:
+ *
+ * 1. Running Broker
+ * 2. Connect Management client via JMX
+ * Output:
+ *
+ * <date> MNG-1007 : Open <user>
+ *
+ * Validation Steps:
+ * 1. The MNG ID is correct
+ * 2. The user is correct
+ *
+ * On connection close a MNG-1008 is expected
+ *
+ * * <date> MNG-1008 : Close
+ *
+ * Validation Steps:
+ * 1. The MNG ID is correct
+ *
+ * @throws java.io.IOException - if there is a problem reseting the log monitor
+ */
+ public void testJMXManagementConsoleConnection() throws IOException
+ {
+ List<String> results = waitAndFindMatches("MNG-1007");
+
+ assertEquals("Unexpected Management Connection count", 1, results.size());
+
+ String log = getLogMessage(results, 0);
+
+ validateMessageID("MNG-1007", log);
+
+ assertTrue("User not in log message:" + log, log.endsWith(USER));
+ // Extract the id from the log string
+ // MESSAGE [mng:1(rmi://169.24.29.116)] MNG-1007 : Open : User admin
+ int connectionID = Integer.parseInt(fromActor(getLog(results.get(0))).charAt(4) + "");
+
+ results = findMatches("MNG-1008");
+
+ assertEquals("Unexpected Management Connection close count", 0, results.size());
+
+ _jmxUtils.close();
+ _closed = true;
+
+ results = waitAndFindMatches("MNG-1008");
+
+ assertEquals("Unexpected Management Connection count", 1, results.size());
+
+ assertEquals("Close does not have same id as open,", connectionID,
+ Integer.parseInt(fromActor(getLog(results.get(0))).charAt(4) + ""));
+ }
+
+ /**
+ * Description:
+ * When a connected client has its connection closed via the Management Console this will be logged as a CON-1002 message.
+ * Input:
+ *
+ * 1. Running Broker
+ * 2. Connected Client
+ * 3. Connection is closed via Management Console
+ * Output:
+ *
+ * <date> CON-1002 : Close
+ *
+ * Validation Steps:
+ * 4. The CON ID is correct
+ * 5. This must be the last CON message for the Connection
+ * 6. It must be preceded by a CON-1001 for this Connection
+ *
+ * @throws Exception - {@see ManagedConnection.closeConnection and #getConnection}
+ * @throws java.io.IOException - if there is a problem reseting the log monitor
+ */
+ public void testConnectionCloseViaManagement() throws IOException, Exception
+ {
+ //Create a connection to the broker
+ Connection connection = getConnection();
+
+ // Monitor the connection for an exception being thrown
+ // this should be a DisconnectionException but it is not this tests
+ // job to valiate that. Only use the exception as a synchronisation
+ // to check the log file for the Close message
+ final CountDownLatch exceptionReceived = new CountDownLatch(1);
+ connection.setExceptionListener(new ExceptionListener()
+ {
+ public void onException(JMSException e)
+ {
+ //Failover being attempted.
+ exceptionReceived.countDown();
+ }
+ });
+
+ //Remove the connection close from any 0-10 connections
+ _monitor.reset();
+
+ // Get a managedConnection
+ ManagedConnection mangedConnection = _jmxUtils.getManagedObject(ManagedConnection.class, "org.apache.qpid:type=VirtualHost.Connection,*");
+
+ //Close the connection
+ mangedConnection.closeConnection();
+
+ //Wait for the connection to close
+ assertTrue("Timed out waiting for conneciton to report close",
+ exceptionReceived.await(2, TimeUnit.SECONDS));
+
+ //Validate results
+ List<String> results = waitAndFindMatches("CON-1002");
+
+ assertEquals("Unexpected Connection Close count", 1, results.size());
+ }
+
+ /**
+ * Description:
+ * Exchange creation is possible from the Management Console.
+ * When an exchanged is created in this way then a EXH-1001 create message
+ * is expected to be logged.
+ * Input:
+ *
+ * 1. Running broker
+ * 2. Connected Management Console
+ * 3. Exchange Created via Management Console
+ * Output:
+ *
+ * EXH-1001 : Create : [Durable] Type:<value> Name:<value>
+ *
+ * Validation Steps:
+ * 4. The EXH ID is correct
+ * 5. The correct tags are present in the message based on the create options
+ *
+ * @throws java.io.IOException - if there is a problem reseting the log monitor
+ * @throws javax.management.JMException - {@see #createQueue and ManagedExchange.deleteQueue}
+ */
+ public void testCreateExchangeDirectTransientViaManagementConsole() throws IOException, JMException
+ {
+ _monitor.reset();
+
+ _jmxUtils.createExchange("test", getName(), "direct", false);
+
+ // Validate
+
+ //1 - ID is correct
+ List<String> results = waitAndFindMatches("EXH-1001");
+
+ assertEquals("More than one exchange creation found", 1, results.size());
+
+ String log = getLogMessage(results, 0);
+
+ // Validate correct exchange name
+ assertTrue("Incorrect exchange name created:" + log, log.endsWith(getName()));
+
+ // Validate it was a management actor.
+ String actor = fromActor(log);
+ assertTrue("Actor is not a manangement actor:" + actor, actor.startsWith("mng"));
+ }
+
+ public void testCreateExchangeTopicTransientViaManagementConsole() throws IOException, JMException
+ {
+ //Remove any previous exchange declares
+ _monitor.reset();
+
+ _jmxUtils.createExchange("test", getName(), "topic", false);
+
+ // Validate
+
+ //1 - ID is correct
+ List<String> results = waitAndFindMatches("EXH-1001");
+
+ assertEquals("More than one exchange creation found", 1, results.size());
+
+ String log = getLogMessage(results, 0);
+
+ // Validate correct exchange name
+ assertTrue("Incorrect exchange name created:" + log, log.endsWith(getName()));
+
+ // Validate it was a management actor.
+ String actor = fromActor(log);
+ assertTrue("Actor is not a manangement actor:" + actor, actor.startsWith("mng"));
+
+ }
+
+ public void testCreateExchangeFanoutTransientViaManagementConsole() throws IOException, JMException
+ {
+ //Remove any previous exchange declares
+ _monitor.reset();
+
+ _jmxUtils.createExchange("test", getName(), "fanout", false);
+
+ // Validate
+
+ //1 - ID is correct
+ List<String> results = waitAndFindMatches("EXH-1001");
+
+ assertEquals("More than one exchange creation found", 1, results.size());
+
+ String log = getLogMessage(results, 0);
+
+ // Validate correct exchange name
+ assertTrue("Incorrect exchange name created:" + log, log.endsWith(getName()));
+
+ // Validate it was a management actor.
+ String actor = fromActor(log);
+ assertTrue("Actor is not a manangement actor:" + actor, actor.startsWith("mng"));
+
+ }
+
+ public void testCreateExchangeHeadersTransientViaManagementConsole() throws IOException, JMException
+ {
+ //Remove any previous exchange declares
+ _monitor.reset();
+
+ _jmxUtils.createExchange("test", getName(), "headers", false);
+
+ // Validate
+
+ //1 - ID is correct
+ List<String> results = waitAndFindMatches("EXH-1001");
+
+ assertEquals("More than one exchange creation found", 1, results.size());
+
+ String log = getLogMessage(results, 0);
+
+ // Validate correct exchange name
+ assertTrue("Incorrect exchange name created:" + log, log.endsWith(getName()));
+
+ // Validate it was a management actor.
+ String actor = fromActor(log);
+ assertTrue("Actor is not a manangement actor:" + actor, actor.startsWith("mng"));
+
+ }
+
+ /**
+ * Description:
+ * Queue creation is possible from the Management Console. When a queue is created in this way then a QUE-1001 create message is expected to be logged.
+ * Input:
+ *
+ * 1. Running broker
+ * 2. Connected Management Console
+ * 3. Queue Created via Management Console
+ * Output:
+ *
+ * <date> QUE-1001 : Create : Transient Owner:<name>
+ *
+ * Validation Steps:
+ * 4. The QUE ID is correct
+ * 5. The correct tags are present in the message based on the create options
+ *
+ * @throws java.io.IOException - if there is a problem reseting the log monitor
+ * @throws javax.management.JMException - {@see #createQueue and ManagedExchange.deleteQueue}
+ */
+ public void testCreateQueueTransientViaManagementConsole() throws IOException, JMException
+ {
+ //Remove any previous queue declares
+ _monitor.reset();
+
+ _jmxUtils.createQueue("test", getName(), null, false);
+
+ // Validate
+
+ List<String> results = waitAndFindMatches("QUE-1001");
+
+ assertEquals("More than one queue creation found", 1, results.size());
+
+ String log = getLogMessage(results, 0);
+
+ // Validate correct queue name
+ String subject = fromSubject(log);
+ assertEquals("Incorrect queue name created", getName(), AbstractTestLogSubject.getSlice("qu", subject));
+
+ // Validate it was a management actor.
+ String actor = fromActor(log);
+ assertTrue("Actor is not a manangement actor:" + actor, actor.startsWith("mng"));
+ }
+
+ /**
+ * Description:
+ * The ManagementConsole can be used to delete a queue. When this is done a QUE-1002 Deleted message must be logged.
+ * Input:
+ *
+ * 1. Running Broker
+ * 2. Queue created on the broker with no subscribers
+ * 3. Management Console connected
+ * 4. Queue is deleted via Management Console
+ * Output:
+ *
+ * <date> QUE-1002 : Deleted
+ *
+ * Validation Steps:
+ * 5. The QUE ID is correct
+ *
+ * @throws java.io.IOException - if there is a problem reseting the log monitor
+ * @throws javax.management.JMException - {@see #createQueue and ManagedExchange.deleteQueue}
+ */
+ public void testQueueDeleteViaManagementConsole() throws IOException, JMException
+ {
+ //Remove any previous queue declares
+ _monitor.reset();
+
+ _jmxUtils.createQueue("test", getName(), null, false);
+
+ ManagedBroker managedBroker = _jmxUtils.getManagedBroker("test");
+
+ managedBroker.deleteQueue(getName());
+
+ List<String> results = waitAndFindMatches("QUE-1002");
+
+ assertEquals("More than one queue deletion found", 1, results.size());
+
+ String log = getLog(results.get(0));
+
+ // Validate correct binding
+ String subject = fromSubject(log);
+ assertEquals("Incorrect queue named in delete", getName(), AbstractTestLogSubject.getSlice("qu", subject));
+
+ // Validate it was a management actor.
+ String actor = fromActor(log);
+ assertTrue("Actor is not a manangement actor:" + actor, actor.startsWith("mng"));
+
+ }
+
+ /**
+ * Description:
+ * The binding of a Queue and an Exchange is done via a Binding. When this Binding is created via the Management Console a BND-1001 Create message will be logged.
+ * Input:
+ *
+ * 1. Running Broker
+ * 2. Connected Management Console
+ * 3. Use Management Console to perform binding
+ * Output:
+ *
+ * <date> BND-1001 : Create
+ *
+ * Validation Steps:
+ * 4. The BND ID is correct
+ * 5. This will be the first message for the given binding
+ *
+ * @throws java.io.IOException - if there is a problem reseting the log monitor
+ * @throws javax.management.JMException - {@see #createQueue and ManagedExchange.createNewBinding}
+ */
+ public void testBindingCreateOnDirectViaManagementConsole() throws IOException, JMException
+ {
+ //Remove any previous queue declares
+ _monitor.reset();
+
+ _jmxUtils.createQueue("test", getName(), null, false);
+
+ ManagedExchange managedExchange = _jmxUtils.getManagedExchange("amq.direct");
+
+ managedExchange.createNewBinding(getName(), getName());
+
+ List<String> results = waitAndFindMatches("BND-1001");
+
+ assertEquals("More than one bind creation found", 1, results.size());
+
+ String log = getLogMessage(results, 0);
+
+ // Validate correct binding
+ String subject = fromSubject(log);
+ assertEquals("Incorrect queue named in create", getName(), AbstractTestLogSubject.getSlice("qu", subject));
+ assertEquals("Incorrect routing key in create", getName(), AbstractTestLogSubject.getSlice("rk", subject));
+
+ // Validate it was a management actor.
+ String actor = fromActor(log);
+ assertTrue("Actor is not a manangement actor:" + actor, actor.startsWith("mng"));
+ }
+
+ public void testBindingCreateOnTopicViaManagementConsole() throws IOException, JMException
+ {
+ //Remove any previous queue declares
+ _monitor.reset();
+
+ _jmxUtils.createQueue("test", getName(), null, false);
+
+ ManagedExchange managedExchange = _jmxUtils.getManagedExchange("amq.topic");
+
+ managedExchange.createNewBinding(getName(), getName());
+
+ List<String> results = waitAndFindMatches("BND-1001");
+
+ assertEquals("More than one bind creation found", 1, results.size());
+
+ String log = getLogMessage(results, 0);
+
+ // Validate correct binding
+ String subject = fromSubject(log);
+ assertEquals("Incorrect queue named in create", getName(), AbstractTestLogSubject.getSlice("qu", subject));
+ assertEquals("Incorrect routing key in create", getName(), AbstractTestLogSubject.getSlice("rk", subject));
+
+ // Validate it was a management actor.
+ String actor = fromActor(log);
+ assertTrue("Actor is not a manangement actor:" + actor, actor.startsWith("mng"));
+ }
+
+ public void testBindingCreateOnFanoutViaManagementConsole() throws IOException, JMException
+ {
+ //Remove any previous queue declares
+ _monitor.reset();
+
+ _jmxUtils.createQueue("test", getName(), null, false);
+
+ ManagedExchange managedExchange = _jmxUtils.getManagedExchange("amq.fanout");
+
+ managedExchange.createNewBinding(getName(), getName());
+
+ List<String> results = waitAndFindMatches("BND-1001");
+
+ assertEquals("More than one bind creation found", 1, results.size());
+
+ String log = getLogMessage(results, 0);
+
+ // Validate correct binding
+ String subject = fromSubject(log);
+ assertEquals("Incorrect queue named in create", getName(), AbstractTestLogSubject.getSlice("qu", subject));
+ assertEquals("Incorrect routing key in create", getName(), AbstractTestLogSubject.getSlice("rk", subject));
+
+ // Validate it was a management actor.
+ String actor = fromActor(log);
+ assertTrue("Actor is not a manangement actor:" + actor, actor.startsWith("mng"));
+ }
+
+ /**
+ * Description:
+ * Bindings can be deleted so that a queue can be rebound with a different set of values. This can be performed via the Management Console
+ * Input:
+ *
+ * 1. Running Broker
+ * 2. Management Console connected
+ * 3. Management Console is used to perform unbind.
+ * Output:
+ *
+ * <date> BND-1002 : Deleted
+ *
+ * Validation Steps:
+ * 4. The BND ID is correct
+ * 5. There must have been a BND-1001 Create message first.
+ * 6. This will be the last message for the given binding
+ *
+ * @throws java.io.IOException - if there is a problem reseting the log monitor or an issue with the JMX Connection
+ * @throws javax.management.JMException - {@see #createExchange and ManagedBroker.unregisterExchange}
+ */
+ public void testUnRegisterExchangeViaManagementConsole() throws IOException, JMException
+ {
+ //Remove any previous queue declares
+ _monitor.reset();
+
+ _jmxUtils.createExchange("test", getName(), "direct", false);
+
+ ManagedBroker managedBroker = _jmxUtils.getManagedBroker("test");
+
+ managedBroker.unregisterExchange(getName());
+
+ List<String> results = waitAndFindMatches("EXH-1002");
+
+ assertEquals("More than one exchange deletion found", 1, results.size());
+
+ String log = getLog(results.get(0));
+
+ // Validate correct binding
+ String subject = fromSubject(log);
+ assertEquals("Incorrect exchange named in delete", "direct/" + getName(), AbstractTestLogSubject.getSlice("ex", subject));
+
+ // Validate it was a management actor.
+ String actor = fromActor(log);
+ assertTrue("Actor is not a manangement actor:" + actor, actor.startsWith("mng"));
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageConnectionStatisticsTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageConnectionStatisticsTest.java
new file mode 100644
index 0000000000..9839c6e475
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageConnectionStatisticsTest.java
@@ -0,0 +1,102 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.management.jmx;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jms.Connection;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.management.common.mbeans.ManagedBroker;
+import org.apache.qpid.management.common.mbeans.ManagedConnection;
+
+/**
+ * Test enabling generation of message statistics on a per-connection basis.
+ */
+public class MessageConnectionStatisticsTest extends MessageStatisticsTestCase
+{
+ public void configureStatistics() throws Exception
+ {
+ // no statistics generation configured
+ }
+
+ /**
+ * Test statistics on a single connection
+ */
+ public void testEnablingStatisticsPerConnection() throws Exception
+ {
+ ManagedBroker vhost = _jmxUtils.getManagedBroker("test");
+
+ sendUsing(_test, 5, 200);
+ Thread.sleep(1000);
+
+ List<String> addresses = new ArrayList<String>();
+ for (ManagedConnection mc : _jmxUtils.getManagedConnections("test"))
+ {
+ assertEquals("Incorrect connection total", 0, mc.getTotalMessagesReceived());
+ assertEquals("Incorrect connection data", 0, mc.getTotalDataReceived());
+ assertFalse("Connection statistics should not be enabled", mc.isStatisticsEnabled());
+
+ addresses.add(mc.getRemoteAddress());
+ }
+ assertEquals("Incorrect vhost total", 0, vhost.getTotalMessagesReceived());
+ assertEquals("Incorrect vhost data", 0, vhost.getTotalDataReceived());
+
+ Connection test = new AMQConnection(_brokerUrl, USER, USER, "clientid", "test");
+ test.start();
+ for (ManagedConnection mc : _jmxUtils.getManagedConnections("test"))
+ {
+ if (addresses.contains(mc.getRemoteAddress()))
+ {
+ continue;
+ }
+ mc.setStatisticsEnabled(true);
+ assertEquals("Incorrect connection total", 0, mc.getTotalMessagesReceived());
+ assertEquals("Incorrect connection data", 0, mc.getTotalDataReceived());
+ }
+
+ sendUsing(test, 5, 200);
+ sendUsing(_test, 5, 200);
+ Thread.sleep(1000);
+
+ for (ManagedConnection mc : _jmxUtils.getManagedConnections("test"))
+ {
+ if (addresses.contains(mc.getRemoteAddress()))
+ {
+ assertEquals("Incorrect connection total", 0, mc.getTotalMessagesReceived());
+ assertEquals("Incorrect connection data", 0, mc.getTotalDataReceived());
+ assertFalse("Connection statistics should not be enabled", mc.isStatisticsEnabled());
+ }
+ else
+ {
+ assertEquals("Incorrect connection total", 5, mc.getTotalMessagesReceived());
+ assertEquals("Incorrect connection data", 1000, mc.getTotalDataReceived());
+ assertTrue("Connection statistics should be enabled", mc.isStatisticsEnabled());
+ }
+ }
+ assertEquals("Incorrect vhost total", 0, vhost.getTotalMessagesReceived());
+ assertEquals("Incorrect vhost data", 0, vhost.getTotalDataReceived());
+ assertFalse("Vhost statistics should not be enabled", vhost.isStatisticsEnabled());
+
+ test.close();
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageStatisticsConfigurationTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageStatisticsConfigurationTest.java
new file mode 100644
index 0000000000..df8c6e74cd
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageStatisticsConfigurationTest.java
@@ -0,0 +1,177 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.management.jmx;
+
+import org.apache.qpid.management.common.mbeans.ManagedBroker;
+import org.apache.qpid.management.common.mbeans.ManagedConnection;
+
+/**
+ * Test enabling generation of message statistics on a per-connection basis.
+ */
+public class MessageStatisticsConfigurationTest extends MessageStatisticsTestCase
+{
+ public void configureStatistics() throws Exception
+ {
+ setConfigurationProperty("statistics.generation.broker", Boolean.toString(getName().contains("Broker")));
+ setConfigurationProperty("statistics.generation.virtualhosts", Boolean.toString(getName().contains("Virtualhost")));
+ setConfigurationProperty("statistics.generation.connections", Boolean.toString(getName().contains("Connection")));
+ }
+
+ /**
+ * Just broker statistics.
+ */
+ public void testGenerateBrokerStatistics() throws Exception
+ {
+ sendUsing(_test, 5, 200);
+ Thread.sleep(1000);
+
+ for (ManagedConnection mc : _jmxUtils.getManagedConnections("test"))
+ {
+ assertEquals("Incorrect connection total", 0, mc.getTotalMessagesReceived());
+ assertEquals("Incorrect connection data", 0, mc.getTotalDataReceived());
+ assertFalse("Connection statistics should not be enabled", mc.isStatisticsEnabled());
+ }
+
+ ManagedBroker vhost = _jmxUtils.getManagedBroker("test");
+ assertEquals("Incorrect vhost data", 0, vhost.getTotalMessagesReceived());
+ assertEquals("Incorrect vhost data", 0, vhost.getTotalDataReceived());
+ assertFalse("Vhost statistics should not be enabled", vhost.isStatisticsEnabled());
+
+ if (!_broker.equals(VM))
+ {
+ assertEquals("Incorrect server total messages", 5, _jmxUtils.getServerInformation().getTotalMessagesReceived());
+ assertEquals("Incorrect server total data", 1000, _jmxUtils.getServerInformation().getTotalDataReceived());
+ assertTrue("Server statistics should be enabled", _jmxUtils.getServerInformation().isStatisticsEnabled());
+ }
+ }
+
+ /**
+ * Just virtualhost statistics.
+ */
+ public void testGenerateVirtualhostStatistics() throws Exception
+ {
+ sendUsing(_test, 5, 200);
+ Thread.sleep(1000);
+
+ for (ManagedConnection mc : _jmxUtils.getManagedConnections("test"))
+ {
+ assertEquals("Incorrect connection total", 0, mc.getTotalMessagesReceived());
+ assertEquals("Incorrect connection data", 0, mc.getTotalDataReceived());
+ assertFalse("Connection statistics should not be enabled", mc.isStatisticsEnabled());
+ }
+
+ ManagedBroker vhost = _jmxUtils.getManagedBroker("test");
+ assertEquals("Incorrect vhost data", 5, vhost.getTotalMessagesReceived());
+ assertEquals("Incorrect vhost data", 1000, vhost.getTotalDataReceived());
+ assertTrue("Vhost statistics should be enabled", vhost.isStatisticsEnabled());
+
+ if (!_broker.equals(VM))
+ {
+ assertEquals("Incorrect server total messages", 0, _jmxUtils.getServerInformation().getTotalMessagesReceived());
+ assertEquals("Incorrect server total data", 0, _jmxUtils.getServerInformation().getTotalDataReceived());
+ assertFalse("Server statistics should not be enabled", _jmxUtils.getServerInformation().isStatisticsEnabled());
+ }
+ }
+
+ /**
+ * Just connection statistics.
+ */
+ public void testGenerateConnectionStatistics() throws Exception
+ {
+ sendUsing(_test, 5, 200);
+ Thread.sleep(1000);
+
+ for (ManagedConnection mc : _jmxUtils.getManagedConnections("test"))
+ {
+ assertEquals("Incorrect connection total", 5, mc.getTotalMessagesReceived());
+ assertEquals("Incorrect connection data", 1000, mc.getTotalDataReceived());
+ assertTrue("Connection statistics should be enabled", mc.isStatisticsEnabled());
+ }
+
+ ManagedBroker vhost = _jmxUtils.getManagedBroker("test");
+ assertEquals("Incorrect vhost data", 0, vhost.getTotalMessagesReceived());
+ assertEquals("Incorrect vhost data", 0, vhost.getTotalDataReceived());
+ assertFalse("Vhost statistics should not be enabled", vhost.isStatisticsEnabled());
+
+ if (!_broker.equals(VM))
+ {
+ assertEquals("Incorrect server total messages", 0, _jmxUtils.getServerInformation().getTotalMessagesReceived());
+ assertEquals("Incorrect server total data", 0, _jmxUtils.getServerInformation().getTotalDataReceived());
+ assertFalse("Server statistics should not be enabled", _jmxUtils.getServerInformation().isStatisticsEnabled());
+ }
+ }
+
+ /**
+ * Both broker and virtualhost statistics.
+ */
+ public void testGenerateBrokerAndVirtualhostStatistics() throws Exception
+ {
+ sendUsing(_test, 5, 200);
+ Thread.sleep(1000);
+
+ for (ManagedConnection mc : _jmxUtils.getManagedConnections("test"))
+ {
+ assertEquals("Incorrect connection total", 0, mc.getTotalMessagesReceived());
+ assertEquals("Incorrect connection data", 0, mc.getTotalDataReceived());
+ assertFalse("Connection statistics should not be enabled", mc.isStatisticsEnabled());
+ }
+
+ ManagedBroker vhost = _jmxUtils.getManagedBroker("test");
+ assertEquals("Incorrect vhost data", 5, vhost.getTotalMessagesReceived());
+ assertEquals("Incorrect vhost data", 1000, vhost.getTotalDataReceived());
+ assertTrue("Vhost statistics should be enabled", vhost.isStatisticsEnabled());
+
+ if (!_broker.equals(VM))
+ {
+ assertEquals("Incorrect server total messages", 5, _jmxUtils.getServerInformation().getTotalMessagesReceived());
+ assertEquals("Incorrect server total data", 1000, _jmxUtils.getServerInformation().getTotalDataReceived());
+ assertTrue("Server statistics should be enabled", _jmxUtils.getServerInformation().isStatisticsEnabled());
+ }
+ }
+
+ /**
+ * Broker, virtualhost and connection statistics.
+ */
+ public void testGenerateBrokerVirtualhostAndConnectionStatistics() throws Exception
+ {
+ sendUsing(_test, 5, 200);
+ Thread.sleep(1000);
+
+ for (ManagedConnection mc : _jmxUtils.getManagedConnections("test"))
+ {
+ assertEquals("Incorrect connection total", 5, mc.getTotalMessagesReceived());
+ assertEquals("Incorrect connection data", 1000, mc.getTotalDataReceived());
+ assertTrue("Connection statistics should be enabled", mc.isStatisticsEnabled());
+ }
+
+ ManagedBroker vhost = _jmxUtils.getManagedBroker("test");
+ assertEquals("Incorrect vhost data", 5, vhost.getTotalMessagesReceived());
+ assertEquals("Incorrect vhost data", 1000, vhost.getTotalDataReceived());
+ assertTrue("Vhost statistics should be enabled", vhost.isStatisticsEnabled());
+
+ if (!_broker.equals(VM))
+ {
+ assertEquals("Incorrect server total messages", 5, _jmxUtils.getServerInformation().getTotalMessagesReceived());
+ assertEquals("Incorrect server total data", 1000, _jmxUtils.getServerInformation().getTotalDataReceived());
+ assertTrue("Server statistics should be enabled", _jmxUtils.getServerInformation().isStatisticsEnabled());
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageStatisticsDeliveryTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageStatisticsDeliveryTest.java
new file mode 100644
index 0000000000..e657856d0e
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageStatisticsDeliveryTest.java
@@ -0,0 +1,110 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.management.jmx;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jms.Connection;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.Session;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.management.common.mbeans.ManagedBroker;
+import org.apache.qpid.management.common.mbeans.ManagedConnection;
+
+/**
+ * Test statistics for delivery and receipt.
+ */
+public class MessageStatisticsDeliveryTest extends MessageStatisticsTestCase
+{
+ public void configureStatistics() throws Exception
+ {
+ setConfigurationProperty("statistics.generation.broker", "true");
+ setConfigurationProperty("statistics.generation.virtualhosts", "true");
+ setConfigurationProperty("statistics.generation.connections", "true");
+ }
+
+ public void testDeliveryAndReceiptStatistics() throws Exception
+ {
+ ManagedBroker vhost = _jmxUtils.getManagedBroker("test");
+
+ sendUsing(_test, 5, 200);
+ Thread.sleep(1000);
+
+ List<String> addresses = new ArrayList<String>();
+ for (ManagedConnection mc : _jmxUtils.getManagedConnections("test"))
+ {
+ assertEquals("Incorrect connection delivery total", 0, mc.getTotalMessagesDelivered());
+ assertEquals("Incorrect connection delivery data", 0, mc.getTotalDataDelivered());
+ assertEquals("Incorrect connection receipt total", 5, mc.getTotalMessagesReceived());
+ assertEquals("Incorrect connection receipt data", 1000, mc.getTotalDataReceived());
+
+ addresses.add(mc.getRemoteAddress());
+ }
+
+ assertEquals("Incorrect vhost delivery total", 0, vhost.getTotalMessagesDelivered());
+ assertEquals("Incorrect vhost delivery data", 0, vhost.getTotalDataDelivered());
+ assertEquals("Incorrect vhost receipt total", 5, vhost.getTotalMessagesReceived());
+ assertEquals("Incorrect vhost receipt data", 1000, vhost.getTotalDataReceived());
+
+ Connection test = new AMQConnection(_brokerUrl, USER, USER, "clientid", "test");
+ test.start();
+ receiveUsing(test, 5);
+
+ for (ManagedConnection mc : _jmxUtils.getManagedConnections("test"))
+ {
+ if (addresses.contains(mc.getRemoteAddress()))
+ {
+ assertEquals("Incorrect connection delivery total", 0, mc.getTotalMessagesDelivered());
+ assertEquals("Incorrect connection delivery data", 0, mc.getTotalDataDelivered());
+ assertEquals("Incorrect connection receipt total", 5, mc.getTotalMessagesReceived());
+ assertEquals("Incorrect connection receipt data", 1000, mc.getTotalDataReceived());
+ }
+ else
+ {
+ assertEquals("Incorrect connection delivery total", 5, mc.getTotalMessagesDelivered());
+ assertEquals("Incorrect connection delivery data", 1000, mc.getTotalDataDelivered());
+ assertEquals("Incorrect connection receipt total", 0, mc.getTotalMessagesReceived());
+ assertEquals("Incorrect connection receipt data", 0, mc.getTotalDataReceived());
+ }
+ }
+ assertEquals("Incorrect vhost delivery total", 5, vhost.getTotalMessagesDelivered());
+ assertEquals("Incorrect vhost delivery data", 1000, vhost.getTotalDataDelivered());
+ assertEquals("Incorrect vhost receipt total", 5, vhost.getTotalMessagesReceived());
+ assertEquals("Incorrect vhost receipt data", 1000, vhost.getTotalDataReceived());
+
+ test.close();
+ }
+
+ protected void receiveUsing(Connection con, int number) throws Exception
+ {
+ Session session = con.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ createQueue(session);
+ MessageConsumer consumer = session.createConsumer(_queue);
+ for (int i = 0; i < number; i++)
+ {
+ Message msg = consumer.receive(100);
+ assertNotNull("Message " + i + " was not received", msg);
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageStatisticsReportingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageStatisticsReportingTest.java
new file mode 100644
index 0000000000..180440c0d6
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageStatisticsReportingTest.java
@@ -0,0 +1,90 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.management.jmx;
+
+import java.util.List;
+
+import org.apache.qpid.util.LogMonitor;
+
+/**
+ * Test generation of message statistics reporting.
+ */
+public class MessageStatisticsReportingTest extends MessageStatisticsTestCase
+{
+ protected LogMonitor _monitor;
+
+ public void configureStatistics() throws Exception
+ {
+ setConfigurationProperty("statistics.generation.broker", "true");
+ setConfigurationProperty("statistics.generation.virtualhosts", "true");
+
+ if (getName().equals("testEnabledStatisticsReporting"))
+ {
+ setConfigurationProperty("statistics.reporting.period", "10");
+ }
+
+ _monitor = new LogMonitor(_outputFile);
+ }
+
+ /**
+ * Test enabling reporting.
+ */
+ public void testEnabledStatisticsReporting() throws Exception
+ {
+ sendUsing(_test, 10, 100);
+ sendUsing(_dev, 20, 100);
+ sendUsing(_local, 15, 100);
+
+ Thread.sleep(10 * 1000); // 15s
+
+ List<String> brokerStatsData = _monitor.findMatches("BRK-1008");
+ List<String> brokerStatsMessages = _monitor.findMatches("BRK-1009");
+ List<String> vhostStatsData = _monitor.findMatches("VHT-1003");
+ List<String> vhostStatsMessages = _monitor.findMatches("VHT-1004");
+
+ assertEquals("Incorrect number of broker data stats log messages", 2, brokerStatsData.size());
+ assertEquals("Incorrect number of broker message stats log messages", 2, brokerStatsMessages.size());
+ assertEquals("Incorrect number of virtualhost data stats log messages", 6, vhostStatsData.size());
+ assertEquals("Incorrect number of virtualhost message stats log messages", 6, vhostStatsMessages.size());
+ }
+
+ /**
+ * Test not enabling reporting.
+ */
+ public void testNotEnabledStatisticsReporting() throws Exception
+ {
+ sendUsing(_test, 10, 100);
+ sendUsing(_dev, 20, 100);
+ sendUsing(_local, 15, 100);
+
+ Thread.sleep(10 * 1000); // 15s
+
+ List<String> brokerStatsData = _monitor.findMatches("BRK-1008");
+ List<String> brokerStatsMessages = _monitor.findMatches("BRK-1009");
+ List<String> vhostStatsData = _monitor.findMatches("VHT-1003");
+ List<String> vhostStatsMessages = _monitor.findMatches("VHT-1004");
+
+ assertEquals("Incorrect number of broker data stats log messages", 0, brokerStatsData.size());
+ assertEquals("Incorrect number of broker message stats log messages", 0, brokerStatsMessages.size());
+ assertEquals("Incorrect number of virtualhost data stats log messages", 0, vhostStatsData.size());
+ assertEquals("Incorrect number of virtualhost message stats log messages", 0, vhostStatsMessages.size());
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageStatisticsTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageStatisticsTest.java
new file mode 100644
index 0000000000..50ca51b18a
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageStatisticsTest.java
@@ -0,0 +1,233 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.management.jmx;
+
+import javax.jms.Connection;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.management.common.mbeans.ManagedBroker;
+import org.apache.qpid.management.common.mbeans.ManagedConnection;
+
+/**
+ * Test generation of message statistics.
+ */
+public class MessageStatisticsTest extends MessageStatisticsTestCase
+{
+ public void configureStatistics() throws Exception
+ {
+ setConfigurationProperty("statistics.generation.broker", "true");
+ setConfigurationProperty("statistics.generation.virtualhosts", "true");
+ setConfigurationProperty("statistics.generation.connections", "true");
+ }
+
+ /**
+ * Test message totals.
+ */
+ public void testMessageTotals() throws Exception
+ {
+ sendUsing(_test, 10, 100);
+ sendUsing(_dev, 20, 100);
+ sendUsing(_local, 5, 100);
+ sendUsing(_local, 5, 100);
+ sendUsing(_local, 5, 100);
+ Thread.sleep(2000);
+
+ ManagedBroker test = _jmxUtils.getManagedBroker("test");
+ ManagedBroker dev = _jmxUtils.getManagedBroker("development");
+ ManagedBroker local = _jmxUtils.getManagedBroker("localhost");
+
+ if (!isBroker010())
+ {
+ long total = 0;
+ long data = 0;
+ for (ManagedConnection mc : _jmxUtils.getAllManagedConnections())
+ {
+ total += mc.getTotalMessagesReceived();
+ data += mc.getTotalDataReceived();
+ }
+ assertEquals("Incorrect connection total", 45, total);
+ assertEquals("Incorrect connection data", 4500, data);
+ }
+ if (!_broker.equals(VM))
+ {
+ assertEquals("Incorrect server total", 45, _jmxUtils.getServerInformation().getTotalMessagesReceived());
+ assertEquals("Incorrect server data", 4500, _jmxUtils.getServerInformation().getTotalDataReceived());
+ }
+
+ if (!isBroker010())
+ {
+ long testTotal = 0;
+ long testData = 0;
+ for (ManagedConnection mc : _jmxUtils.getManagedConnections("test"))
+ {
+ testTotal += mc.getTotalMessagesReceived();
+ testData += mc.getTotalDataReceived();
+ }
+ assertEquals("Incorrect test connection total", 10, testTotal);
+ assertEquals("Incorrect test connection data", 1000, testData);
+ }
+ assertEquals("Incorrect test vhost total", 10, test.getTotalMessagesReceived());
+ assertEquals("Incorrect test vhost data", 1000, test.getTotalDataReceived());
+
+ if (!isBroker010())
+ {
+ long devTotal = 0;
+ long devData = 0;
+ for (ManagedConnection mc : _jmxUtils.getManagedConnections("development"))
+ {
+ devTotal += mc.getTotalMessagesReceived();
+ devData += mc.getTotalDataReceived();
+ }
+ assertEquals("Incorrect test connection total", 20, devTotal);
+ assertEquals("Incorrect test connection data", 2000, devData);
+ }
+ assertEquals("Incorrect development total", 20, dev.getTotalMessagesReceived());
+ assertEquals("Incorrect development data", 2000, dev.getTotalDataReceived());
+
+ if (!isBroker010())
+ {
+ long localTotal = 0;
+ long localData = 0;
+ for (ManagedConnection mc : _jmxUtils.getManagedConnections("localhost"))
+ {
+ localTotal += mc.getTotalMessagesReceived();
+ localData += mc.getTotalDataReceived();
+ }
+ assertEquals("Incorrect test connection total", 15, localTotal);
+ assertEquals("Incorrect test connection data", 1500, localData);
+ }
+ assertEquals("Incorrect localhost total", 15, local.getTotalMessagesReceived());
+ assertEquals("Incorrect localhost data", 1500, local.getTotalDataReceived());
+ }
+
+ /**
+ * Test message totals when a connection is closed.
+ */
+ public void testMessageTotalsWithClosedConnections() throws Exception
+ {
+ Connection temp = new AMQConnection(_brokerUrl, USER, USER, "clientid", "test");
+ temp.start();
+
+ sendUsing(_test, 10, 100);
+ sendUsing(temp, 10, 100);
+ sendUsing(_test, 10, 100);
+ Thread.sleep(2000);
+
+ temp.close();
+
+ ManagedBroker test = _jmxUtils.getManagedBroker("test");
+
+ if (!isBroker010())
+ {
+ long total = 0;
+ long data = 0;
+ for (ManagedConnection mc : _jmxUtils.getAllManagedConnections())
+ {
+ total += mc.getTotalMessagesReceived();
+ data += mc.getTotalDataReceived();
+ }
+ assertEquals("Incorrect active connection total", 20, total);
+ assertEquals("Incorrect active connection data", 2000, data);
+ }
+ if (!_broker.equals(VM))
+ {
+ assertEquals("Incorrect server total", 30, _jmxUtils.getServerInformation().getTotalMessagesReceived());
+ assertEquals("Incorrect server data", 3000, _jmxUtils.getServerInformation().getTotalDataReceived());
+ }
+
+ if (!isBroker010())
+ {
+ long testTotal = 0;
+ long testData = 0;
+ for (ManagedConnection mc : _jmxUtils.getManagedConnections("test"))
+ {
+ testTotal += mc.getTotalMessagesReceived();
+ testData += mc.getTotalDataReceived();
+ }
+ assertEquals("Incorrect test active connection total", 20, testTotal);
+ assertEquals("Incorrect test active connection data", 20 * 100, testData);
+ }
+ assertEquals("Incorrect test vhost total", 30, test.getTotalMessagesReceived());
+ assertEquals("Incorrect test vhost data", 30 * 100, test.getTotalDataReceived());
+ }
+
+ /**
+ * Test message peak rate generation.
+ */
+ public void testMessagePeakRates() throws Exception
+ {
+ sendUsing(_test, 2, 10);
+ Thread.sleep(10000);
+ sendUsing(_dev, 4, 10);
+ Thread.sleep(10000);
+
+ ManagedBroker test = _jmxUtils.getManagedBroker("test");
+ ManagedBroker dev = _jmxUtils.getManagedBroker("development");
+
+ assertApprox("Incorrect test vhost peak messages", 0.2d, 1.0d, test.getPeakMessageReceiptRate());
+ assertApprox("Incorrect test vhost peak data", 0.2d, 10.0d, test.getPeakDataReceiptRate());
+ assertApprox("Incorrect dev vhost peak messages", 0.2d, 2.0d, dev.getPeakMessageReceiptRate());
+ assertApprox("Incorrect dev vhost peak data", 0.2d, 20.0d, dev.getPeakDataReceiptRate());
+
+ if (!_broker.equals(VM))
+ {
+ assertApprox("Incorrect server peak messages", 0.2d, 2.0d, _jmxUtils.getServerInformation().getPeakMessageReceiptRate());
+ assertApprox("Incorrect server peak data", 0.2d, 20.0d, _jmxUtils.getServerInformation().getPeakDataReceiptRate());
+ }
+ }
+
+ /**
+ * Test message totals when a vhost has its statistics reset
+ */
+ public void testMessageTotalVhostReset() throws Exception
+ {
+ sendUsing(_test, 10, 10);
+ sendUsing(_dev, 10, 10);
+ Thread.sleep(2000);
+
+ ManagedBroker test = _jmxUtils.getManagedBroker("test");
+ ManagedBroker dev = _jmxUtils.getManagedBroker("development");
+
+ assertEquals("Incorrect test vhost total messages", 10, test.getTotalMessagesReceived());
+ assertEquals("Incorrect test vhost total data", 100, test.getTotalDataReceived());
+ assertEquals("Incorrect dev vhost total messages", 10, dev.getTotalMessagesReceived());
+ assertEquals("Incorrect dev vhost total data", 100, dev.getTotalDataReceived());
+
+ if (!_broker.equals(VM))
+ {
+ assertEquals("Incorrect server total messages", 20, _jmxUtils.getServerInformation().getTotalMessagesReceived());
+ assertEquals("Incorrect server total data", 200, _jmxUtils.getServerInformation().getTotalDataReceived());
+ }
+
+ test.resetStatistics();
+
+ assertEquals("Incorrect test vhost total messages", 0, test.getTotalMessagesReceived());
+ assertEquals("Incorrect test vhost total data", 0, test.getTotalDataReceived());
+ assertEquals("Incorrect dev vhost total messages", 10, dev.getTotalMessagesReceived());
+ assertEquals("Incorrect dev vhost total data", 100, dev.getTotalDataReceived());
+
+ if (!_broker.equals(VM))
+ {
+ assertEquals("Incorrect server total messages", 20, _jmxUtils.getServerInformation().getTotalMessagesReceived());
+ assertEquals("Incorrect server total data", 200, _jmxUtils.getServerInformation().getTotalDataReceived());
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageStatisticsTestCase.java b/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageStatisticsTestCase.java
new file mode 100644
index 0000000000..a5b3aa283c
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/management/jmx/MessageStatisticsTestCase.java
@@ -0,0 +1,128 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.management.jmx;
+
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.test.utils.JMXTestUtils;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+/**
+ * Test generation of message statistics.
+ */
+public abstract class MessageStatisticsTestCase extends QpidBrokerTestCase
+{
+ protected static final String USER = "admin";
+
+ protected JMXTestUtils _jmxUtils;
+ protected Connection _test, _dev, _local;
+ protected String _queueName = "statistics";
+ protected Destination _queue;
+ protected String _brokerUrl;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ _jmxUtils = new JMXTestUtils(this, USER, USER);
+ _jmxUtils.setUp();
+
+ configureStatistics();
+
+ super.setUp();
+
+ _brokerUrl = getBroker().toString();
+ _test = new AMQConnection(_brokerUrl, USER, USER, "clientid", "test");
+ _dev = new AMQConnection(_brokerUrl, USER, USER, "clientid", "development");
+ _local = new AMQConnection(_brokerUrl, USER, USER, "clientid", "localhost");
+
+ _test.start();
+ _dev.start();
+ _local.start();
+
+ _jmxUtils.open();
+ }
+
+ protected void createQueue(Session session) throws AMQException, JMSException
+ {
+ _queue = new AMQQueue(ExchangeDefaults.DIRECT_EXCHANGE_NAME, _queueName);
+ if (!((AMQSession<?,?>) session).isQueueBound((AMQDestination) _queue))
+ {
+ ((AMQSession<?,?>) session).createQueue(new AMQShortString(_queueName), false, true, false, null);
+ ((AMQSession<?,?>) session).declareAndBind((AMQDestination) new AMQQueue(ExchangeDefaults.DIRECT_EXCHANGE_NAME, _queueName));
+ }
+ }
+
+
+ @Override
+ public void tearDown() throws Exception
+ {
+ _jmxUtils.close();
+
+ _test.close();
+ _dev.close();
+ _local.close();
+
+ super.tearDown();
+ }
+
+ /**
+ * Configure statistics generation properties on the broker.
+ */
+ public abstract void configureStatistics() throws Exception;
+
+ protected void sendUsing(Connection con, int number, int size) throws Exception
+ {
+ Session session = con.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ createQueue(session);
+ MessageProducer producer = session.createProducer(_queue);
+ String content = new String(new byte[size]);
+ TextMessage msg = session.createTextMessage(content);
+ for (int i = 0; i < number; i++)
+ {
+ producer.send(msg);
+ }
+ }
+
+ /**
+ * Asserts that the actual value is within the expected value plus or
+ * minus the given error.
+ */
+ public void assertApprox(String message, double error, double expected, double actual)
+ {
+ double min = expected * (1.0d - error);
+ double max = expected * (1.0d + error);
+ String assertion = String.format("%s: expected %f +/- %d%%, actual %f",
+ message, expected, (int) (error * 100.0d), actual);
+ assertTrue(assertion, actual > min && actual < max);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/BrokerStartupTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/BrokerStartupTest.java
new file mode 100644
index 0000000000..f9227c53ba
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/BrokerStartupTest.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server;
+
+import org.apache.qpid.server.logging.AbstractTestLogging;
+import org.apache.qpid.util.LogMonitor;
+import org.apache.log4j.Logger;
+import org.apache.log4j.Level;
+
+import java.util.List;
+
+import junit.framework.AssertionFailedError;
+
+import javax.jms.Connection;
+import javax.jms.Session;
+import javax.jms.Queue;
+
+/**
+ * Series of tests to validate the external Java broker starts up as expected.
+ */
+public class BrokerStartupTest extends AbstractTestLogging
+{
+ public void setUp() throws Exception
+ {
+ // We either do this here or have a null check in tearDown.
+ // As when this test is run against profiles other than java it will NPE
+ _monitor = new LogMonitor(_outputFile);
+ //We explicitly do not call super.setUp as starting up the broker is
+ //part of the test case.
+ }
+
+
+ /**
+ * Description:
+ * Test that providing an invalid broker logging configuration file does not
+ * cause the broker to enable DEBUG logging that will seriously impair
+ * performance
+ * Input:
+ * -l value that does not exist
+ * <p/>
+ * Output:
+ * <p/>
+ * No DEBUG output
+ * <p/>
+ * Validation Steps:
+ * <p/>
+ * 1. Start the broker and verify no DEBUG output exists
+ *
+ * @throws Exception caused by broker startup
+ */
+ public void testInvalidLog4jConfigurationFile() throws Exception
+ {
+ // This logging startup code only occurs when you run a Java broker,
+ // that broker must be started via Main so not an InVM broker.
+ if (isJavaBroker() && isExternalBroker())
+ {
+ //Remove test Log4j config from the commandline
+ _broker = _broker.substring(0, _broker.indexOf("-l"));
+
+ // Add an invalid value
+ _broker += " -l invalid";
+
+ // The broker has a built in default log4j configuration set up
+ // so if the the broker cannot load the -l value it will use default
+ // use this default. Test that this is correctly loaded, by
+ // including -Dlog4j.debug so we can validate.
+ setBrokerEnvironment("QPID_OPTS", "-Dlog4j.debug");
+
+ // Disable all client logging so we can test for broker DEBUG only.
+ setLoggerLevel(Logger.getRootLogger(), Level.WARN);
+ setLoggerLevel(Logger.getLogger("qpid.protocol"), Level.WARN);
+ setLoggerLevel(Logger.getLogger("org.apache.qpid"), Level.WARN);
+
+ // Set the broker to use info level logging, which is the qpid-server
+ // default. Rather than debug which is the test default.
+ setBrokerOnlySystemProperty("amqj.server.logging.level", "info");
+ // Set the logging defaults to info for this test.
+ setBrokerOnlySystemProperty("amqj.logging.level", "info");
+ setBrokerOnlySystemProperty("root.logging.level", "info");
+
+ startBroker();
+
+ assertEquals("Log4j could not load desired configruation.",
+ 0, findMatches("log4j:ERROR Could not read configuration file from URL").size());
+
+ assertEquals("Logging did not error as expected",
+ 1, waitAndFindMatches("Logging configuration error: unable to read file ").size());
+
+
+ // Perfom some action on the broker to ensure that we hit the DEBUG
+ // messages that we know are there. Though the current xml parsing
+ // will generate a LOT of DEBUG on startup.
+ Connection connection = getConnection();
+
+ Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ Queue queue = session.createQueue(getTestQueueName());
+ session.createConsumer(queue).close();
+
+ int COUNT = 10;
+ sendMessage(session, queue, COUNT);
+
+ assertEquals(COUNT,drainQueue(queue));
+
+ List<String> results = waitAndFindMatches("DEBUG");
+ try
+ {
+ // Validation
+
+ assertEquals("DEBUG messages should not be logged", 0, results.size());
+ }
+ catch (AssertionFailedError afe)
+ {
+ System.err.println("Log Dump:");
+ for (String log : results)
+ {
+ System.err.println(log);
+ }
+
+ if (results.size() == 0)
+ {
+ System.err.println("Monitored file contents:");
+ System.err.println(_monitor.readFile());
+ }
+
+ throw afe;
+ }
+ }
+ }
+
+} \ No newline at end of file
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/configuration/ServerConfigurationFileTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/configuration/ServerConfigurationFileTest.java
new file mode 100644
index 0000000000..d4c550bc08
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/configuration/ServerConfigurationFileTest.java
@@ -0,0 +1,89 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.configuration;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+/**
+ * This system test ensures that when loading our default system-test
+ * configuration file the configuration is correctly loaded.
+ *
+ * All configuration values should be set in the systest config file so that
+ * the ability to load them can be validated.
+ */
+public class ServerConfigurationFileTest extends QpidBrokerTestCase
+{
+ ServerConfiguration _serverConfig;
+
+ public void setUp() throws ConfigurationException
+ {
+ if (!_configFile.exists())
+ {
+ fail("Unable to test without config file:" + _configFile);
+ }
+
+ saveTestConfiguration();
+ saveTestVirtualhosts();
+
+ _serverConfig = new ServerConfiguration(_configFile);
+ }
+
+ /**
+ * This helper method ensures that when we attempt to read a value that is
+ * set in the configuration file we do actualy read a value and not
+ * simply get a defaulted value from the ServerConfiguration.get*() methods.
+ *
+ * @param property the propert to test
+ */
+ private void validatePropertyDefinedInFile(String property)
+ {
+ //Verify that we are not just picking up the the default value from the getBoolean
+ assertNotNull("The value set in the configuration file is not being read for property:" + property,
+ _serverConfig.getConfig().getProperty(property));
+ }
+
+ public void testProtectIOEnabled() throws ConfigurationException
+ {
+ validatePropertyDefinedInFile(ServerConfiguration.CONNECTOR_PROTECTIO_ENABLED);
+ }
+
+ public void testProtectIOReadBufferLimitSize() throws ConfigurationException
+ {
+ validatePropertyDefinedInFile(ServerConfiguration.CONNECTOR_PROTECTIO_READ_BUFFER_LIMIT_SIZE);
+ }
+
+ public void testProtectIOWriteBufferLimitSize() throws ConfigurationException
+ {
+ validatePropertyDefinedInFile(ServerConfiguration.CONNECTOR_PROTECTIO_WRITE_BUFFER_LIMIT_SIZE);
+ }
+
+ public void testStatusUpdates() throws ConfigurationException
+ {
+ validatePropertyDefinedInFile(ServerConfiguration.STATUS_UPDATES);
+ }
+
+ public void testLocale() throws ConfigurationException
+ {
+ validatePropertyDefinedInFile(ServerConfiguration.ADVANCED_LOCALE);
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/exchange/MessagingTestConfigProperties.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/exchange/MessagingTestConfigProperties.java
new file mode 100644
index 0000000000..2d89d319d7
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/exchange/MessagingTestConfigProperties.java
@@ -0,0 +1,308 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.exchange;
+
+import org.apache.qpid.jms.Session;
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+/**
+ * MessagingTestConfigProperties defines a set of property names and default values for specifying a messaging topology,
+ * and test parameters for running a messaging test over that topology. A Properties object holding some of these
+ * properties, superimposed onto the defaults, is used to establish test topologies and control test behaviour.
+ *
+ * <p/>A complete list of the parameters, default values and comments on their usage is provided here:
+ *
+ * <p/><table><caption>Parameters</caption>
+ * <tr><th> Parameter <th> Default <th> Comments
+ * <tr><td> messageSize <td> 0 <td> Message size in bytes. Not including any headers.
+ * <tr><td> destinationName <td> ping <td> The root name to use to generate destination names to ping.
+ * <tr><td> persistent <td> false <td> Determines whether peristent delivery is used.
+ * <tr><td> transacted <td> false <td> Determines whether messages are sent/received in transactions.
+ * <tr><td> broker <td> tcp://localhost:5672 <td> Determines the broker to connect to.
+ * <tr><td> virtualHost <td> test <td> Determines the virtual host to send all ping over.
+ * <tr><td> rate <td> 0 <td> The maximum rate (in hertz) to send messages at. 0 means no limit.
+ * <tr><td> verbose <td> false <td> The verbose flag for debugging. Prints to console on every message.
+ * <tr><td> pubsub <td> false <td> Whether to ping topics or queues. Uses p2p by default.
+ * <tr><td> username <td> guest <td> The username to access the broker with.
+ * <tr><td> password <td> guest <td> The password to access the broker with.
+ * <tr><td> selector <td> null <td> Not used. Defines a message selector to filter pings with.
+ * <tr><td> destinationCount <td> 1 <td> The number of receivers listening to the pings.
+ * <tr><td> timeout <td> 30000 <td> In milliseconds. The timeout to stop waiting for replies.
+ * <tr><td> commitBatchSize <td> 1 <td> The number of messages per transaction in transactional mode.
+ * <tr><td> uniqueDests <td> true <td> Whether each receiver only listens to one ping destination or all.
+ * <tr><td> durableDests <td> false <td> Whether or not durable destinations are used.
+ * <tr><td> ackMode <td> AUTO_ACK <td> The message acknowledgement mode. Possible values are:
+ * 0 - SESSION_TRANSACTED
+ * 1 - AUTO_ACKNOWLEDGE
+ * 2 - CLIENT_ACKNOWLEDGE
+ * 3 - DUPS_OK_ACKNOWLEDGE
+ * 257 - NO_ACKNOWLEDGE
+ * 258 - PRE_ACKNOWLEDGE
+ * <tr><td> maxPending <td> 0 <td> The maximum size in bytes, of messages sent but not yet received.
+ * Limits the volume of messages currently buffered on the client
+ * or broker. Can help scale test clients by limiting amount of buffered
+ * data to avoid out of memory errors.
+ * </table>
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Provide the names and defaults of all test parameters.
+ * </table>
+ */
+public class MessagingTestConfigProperties
+{
+ // ====================== Connection Properties ==================================
+
+ /** Holds the name of the default connection configuration. */
+ public static final String CONNECTION_NAME = "broker";
+
+ /** Holds the name of the property to get the initial context factory name from. */
+ public static final String INITIAL_CONTEXT_FACTORY_PROPNAME = "java.naming.factory.initial";
+
+ /** Defines the class to use as the initial context factory by default. */
+ public static final String INITIAL_CONTEXT_FACTORY_DEFAULT = "org.apache.qpid.jndi.PropertiesFileInitialContextFactory";
+
+ /** Holds the name of the default connection factory configuration property. */
+ public static final String CONNECTION_PROPNAME = "connectionfactory.broker";
+
+ /** Defeins the default connection configuration. */
+ public static final String CONNECTION_DEFAULT = "amqp://guest:guest@clientid/?brokerlist='vm://:1'";
+
+ /** Holds the name of the property to get the test broker url from. */
+ public static final String BROKER_PROPNAME = "qpid.test.broker";
+
+ /** Holds the default broker url for the test. */
+ public static final String BROKER_DEFAULT = "vm://:1";
+
+ /** Holds the name of the property to get the test broker virtual path. */
+ public static final String VIRTUAL_HOST_PROPNAME = "virtualHost";
+
+ /** Holds the default virtual path for the test. */
+ public static final String VIRTUAL_HOST_DEFAULT = "";
+
+ /** Holds the name of the property to get the broker access username from. */
+ public static final String USERNAME_PROPNAME = "username";
+
+ /** Holds the default broker log on username. */
+ public static final String USERNAME_DEFAULT = "guest";
+
+ /** Holds the name of the property to get the broker access password from. */
+ public static final String PASSWORD_PROPNAME = "password";
+
+ /** Holds the default broker log on password. */
+ public static final String PASSWORD_DEFAULT = "guest";
+
+ // ====================== Messaging Topology Properties ==========================
+
+ /** Holds the name of the property to get the bind publisher procuder flag from. */
+ public static final String PUBLISHER_PRODUCER_BIND_PROPNAME = "publisherProducerBind";
+
+ /** Holds the default value of the publisher producer flag. */
+ public static final boolean PUBLISHER_PRODUCER_BIND_DEFAULT = true;
+
+ /** Holds the name of the property to get the bind publisher procuder flag from. */
+ public static final String PUBLISHER_CONSUMER_BIND_PROPNAME = "publisherConsumerBind";
+
+ /** Holds the default value of the publisher consumer flag. */
+ public static final boolean PUBLISHER_CONSUMER_BIND_DEFAULT = false;
+
+ /** Holds the name of the property to get the bind receiver procuder flag from. */
+ public static final String RECEIVER_PRODUCER_BIND_PROPNAME = "receiverProducerBind";
+
+ /** Holds the default value of the receiver producer flag. */
+ public static final boolean RECEIVER_PRODUCER_BIND_DEFAULT = false;
+
+ /** Holds the name of the property to get the bind receiver procuder flag from. */
+ public static final String RECEIVER_CONSUMER_BIND_PROPNAME = "receiverConsumerBind";
+
+ /** Holds the default value of the receiver consumer flag. */
+ public static final boolean RECEIVER_CONSUMER_BIND_DEFAULT = true;
+
+ /** Holds the name of the property to get the destination name root from. */
+ public static final String SEND_DESTINATION_NAME_ROOT_PROPNAME = "sendDestinationRoot";
+
+ /** Holds the root of the name of the default destination to send to. */
+ public static final String SEND_DESTINATION_NAME_ROOT_DEFAULT = "sendTo";
+
+ /** Holds the name of the property to get the destination name root from. */
+ public static final String RECEIVE_DESTINATION_NAME_ROOT_PROPNAME = "receiveDestinationRoot";
+
+ /** Holds the root of the name of the default destination to send to. */
+ public static final String RECEIVE_DESTINATION_NAME_ROOT_DEFAULT = "receiveFrom";
+
+ /** Holds the name of the proeprty to get the destination count from. */
+ public static final String DESTINATION_COUNT_PROPNAME = "destinationCount";
+
+ /** Defines the default number of destinations to ping. */
+ public static final int DESTINATION_COUNT_DEFAULT = 1;
+
+ /** Holds the name of the property to get the p2p or pub/sub messaging mode from. */
+ public static final String PUBSUB_PROPNAME = "pubsub";
+
+ /** Holds the pub/sub mode default, true means ping a topic, false means ping a queue. */
+ public static final boolean PUBSUB_DEFAULT = false;
+
+ // ====================== JMS Options and Flags =================================
+
+ /** Holds the name of the property to get the test delivery mode from. */
+ public static final String PERSISTENT_MODE_PROPNAME = "persistent";
+
+ /** Holds the message delivery mode to use for the test. */
+ public static final boolean PERSISTENT_MODE_DEFAULT = false;
+
+ /** Holds the name of the property to get the test transactional mode from. */
+ public static final String TRANSACTED_PROPNAME = "transacted";
+
+ /** Holds the transactional mode to use for the test. */
+ public static final boolean TRANSACTED_DEFAULT = false;
+
+ /** Holds the name of the property to set the no local flag from. */
+ public static final String NO_LOCAL_PROPNAME = "noLocal";
+
+ /** Defines the default value of the no local flag to use when consuming messages. */
+ public static final boolean NO_LOCAL_DEFAULT = false;
+
+ /** Holds the name of the property to get the message acknowledgement mode from. */
+ public static final String ACK_MODE_PROPNAME = "ackMode";
+
+ /** Defines the default message acknowledgement mode. */
+ public static final int ACK_MODE_DEFAULT = Session.AUTO_ACKNOWLEDGE;
+
+ /** Holds the name of the property to get the durable subscriptions flag from, when doing pub/sub messaging. */
+ public static final String DURABLE_SUBSCRIPTION_PROPNAME = "durableSubscription";
+
+ /** Defines the default value of the durable subscriptions flag. */
+ public static final boolean DURABLE_SUBSCRIPTION_DEFAULT = false;
+
+ // ====================== Qpid Options and Flags ================================
+
+ /** Holds the name of the property to set the exclusive flag from. */
+ public static final String EXCLUSIVE_PROPNAME = "exclusive";
+
+ /** Defines the default value of the exclusive flag to use when consuming messages. */
+ public static final boolean EXCLUSIVE_DEFAULT = false;
+
+ /** Holds the name of the property to set the immediate flag from. */
+ public static final String IMMEDIATE_PROPNAME = "immediate";
+
+ /** Defines the default value of the immediate flag to use when sending messages. */
+ public static final boolean IMMEDIATE_DEFAULT = false;
+
+ /** Holds the name of the property to set the mandatory flag from. */
+ public static final String MANDATORY_PROPNAME = "mandatory";
+
+ /** Defines the default value of the mandatory flag to use when sending messages. */
+ public static final boolean MANDATORY_DEFAULT = false;
+
+ /** Holds the name of the property to get the durable destinations flag from. */
+ public static final String DURABLE_DESTS_PROPNAME = "durableDests";
+
+ /** Default value for the durable destinations flag. */
+ public static final boolean DURABLE_DESTS_DEFAULT = false;
+
+ /** Holds the name of the proeprty to set the prefetch size from. */
+ public static final String PREFECTH_PROPNAME = "prefetch";
+
+ /** Defines the default prefetch size to use when consuming messages. */
+ public static final int PREFETCH_DEFAULT = 100;
+
+ // ====================== Common Test Parameters ================================
+
+ /** Holds the name of the property to get the test message size from. */
+ public static final String MESSAGE_SIZE_PROPNAME = "messageSize";
+
+ /** Used to set up a default message size. */
+ public static final int MESSAGE_SIZE_DEAFULT = 0;
+
+ /** Holds the name of the property to get the message rate from. */
+ public static final String RATE_PROPNAME = "rate";
+
+ /** Defines the default rate (in pings per second) to send pings at. 0 means as fast as possible, no restriction. */
+ public static final int RATE_DEFAULT = 0;
+
+ /** Holds the name of the proeprty to get the. */
+ public static final String SELECTOR_PROPNAME = "selector";
+
+ /** Holds the default message selector. */
+ public static final String SELECTOR_DEFAULT = "";
+
+ /** Holds the name of the property to get the waiting timeout for response messages. */
+ public static final String TIMEOUT_PROPNAME = "timeout";
+
+ /** Default time to wait before assuming that a ping has timed out. */
+ public static final long TIMEOUT_DEFAULT = 30000;
+
+ /** Holds the name of the property to get the commit batch size from. */
+ public static final String TX_BATCH_SIZE_PROPNAME = "commitBatchSize";
+
+ /** Defines the default number of pings to send in each transaction when running transactionally. */
+ public static final int TX_BATCH_SIZE_DEFAULT = 1;
+
+ /** Holds the name of the property to set the maximum amount of pending message data for a producer to hold. */
+ public static final String MAX_PENDING_PROPNAME = "maxPending";
+
+ /** Defines the default maximum quantity of pending message data to allow producers to hold. */
+ public static final int MAX_PENDING_DEFAULT = 0;
+
+ /** Holds the name of the property to get the verbose mode proeprty from. */
+ public static final String VERBOSE_PROPNAME = "verbose";
+
+ /** Holds the default verbose mode. */
+ public static final boolean VERBOSE_DEFAULT = false;
+
+ /** Holds the default configuration properties. */
+ public static ParsedProperties defaults = new ParsedProperties();
+
+ static
+ {
+ defaults.setPropertyIfNull(INITIAL_CONTEXT_FACTORY_PROPNAME, INITIAL_CONTEXT_FACTORY_DEFAULT);
+ defaults.setPropertyIfNull(CONNECTION_PROPNAME, CONNECTION_DEFAULT);
+ defaults.setPropertyIfNull(MESSAGE_SIZE_PROPNAME, MESSAGE_SIZE_DEAFULT);
+ defaults.setPropertyIfNull(PUBLISHER_PRODUCER_BIND_PROPNAME, PUBLISHER_PRODUCER_BIND_DEFAULT);
+ defaults.setPropertyIfNull(PUBLISHER_CONSUMER_BIND_PROPNAME, PUBLISHER_CONSUMER_BIND_DEFAULT);
+ defaults.setPropertyIfNull(RECEIVER_PRODUCER_BIND_PROPNAME, RECEIVER_PRODUCER_BIND_DEFAULT);
+ defaults.setPropertyIfNull(RECEIVER_CONSUMER_BIND_PROPNAME, RECEIVER_CONSUMER_BIND_DEFAULT);
+ defaults.setPropertyIfNull(SEND_DESTINATION_NAME_ROOT_PROPNAME, SEND_DESTINATION_NAME_ROOT_DEFAULT);
+ defaults.setPropertyIfNull(RECEIVE_DESTINATION_NAME_ROOT_PROPNAME, RECEIVE_DESTINATION_NAME_ROOT_DEFAULT);
+ defaults.setPropertyIfNull(PERSISTENT_MODE_PROPNAME, PERSISTENT_MODE_DEFAULT);
+ defaults.setPropertyIfNull(TRANSACTED_PROPNAME, TRANSACTED_DEFAULT);
+ defaults.setPropertyIfNull(BROKER_PROPNAME, BROKER_DEFAULT);
+ defaults.setPropertyIfNull(VIRTUAL_HOST_PROPNAME, VIRTUAL_HOST_DEFAULT);
+ defaults.setPropertyIfNull(RATE_PROPNAME, RATE_DEFAULT);
+ defaults.setPropertyIfNull(VERBOSE_PROPNAME, VERBOSE_DEFAULT);
+ defaults.setPropertyIfNull(PUBSUB_PROPNAME, PUBSUB_DEFAULT);
+ defaults.setPropertyIfNull(USERNAME_PROPNAME, USERNAME_DEFAULT);
+ defaults.setPropertyIfNull(PASSWORD_PROPNAME, PASSWORD_DEFAULT);
+ defaults.setPropertyIfNull(SELECTOR_PROPNAME, SELECTOR_DEFAULT);
+ defaults.setPropertyIfNull(DESTINATION_COUNT_PROPNAME, DESTINATION_COUNT_DEFAULT);
+ defaults.setPropertyIfNull(TIMEOUT_PROPNAME, TIMEOUT_DEFAULT);
+ defaults.setPropertyIfNull(TX_BATCH_SIZE_PROPNAME, TX_BATCH_SIZE_DEFAULT);
+ defaults.setPropertyIfNull(DURABLE_DESTS_PROPNAME, DURABLE_DESTS_DEFAULT);
+ defaults.setPropertyIfNull(ACK_MODE_PROPNAME, ACK_MODE_DEFAULT);
+ defaults.setPropertyIfNull(DURABLE_SUBSCRIPTION_PROPNAME, DURABLE_SUBSCRIPTION_DEFAULT);
+ defaults.setPropertyIfNull(MAX_PENDING_PROPNAME, MAX_PENDING_DEFAULT);
+ defaults.setPropertyIfNull(PREFECTH_PROPNAME, PREFETCH_DEFAULT);
+ defaults.setPropertyIfNull(NO_LOCAL_PROPNAME, NO_LOCAL_DEFAULT);
+ defaults.setPropertyIfNull(EXCLUSIVE_PROPNAME, EXCLUSIVE_DEFAULT);
+ defaults.setPropertyIfNull(IMMEDIATE_PROPNAME, IMMEDIATE_DEFAULT);
+ defaults.setPropertyIfNull(MANDATORY_PROPNAME, MANDATORY_DEFAULT);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/exchange/ReturnUnroutableMandatoryMessageTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/exchange/ReturnUnroutableMandatoryMessageTest.java
new file mode 100644
index 0000000000..4b4fbd711b
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/exchange/ReturnUnroutableMandatoryMessageTest.java
@@ -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.
+ *
+ */
+
+package org.apache.qpid.server.exchange;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.jms.Connection;
+import javax.jms.ExceptionListener;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.client.AMQHeadersExchange;
+import org.apache.qpid.client.AMQNoRouteException;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.AMQTopic;
+import org.apache.qpid.configuration.ClientProperties;
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.url.AMQBindingURL;
+import org.apache.qpid.url.BindingURL;
+
+public class ReturnUnroutableMandatoryMessageTest extends QpidBrokerTestCase implements ExceptionListener
+{
+ private static final Logger _logger = Logger.getLogger(ReturnUnroutableMandatoryMessageTest.class);
+
+ private final List<Message> _bouncedMessageList = Collections.synchronizedList(new ArrayList<Message>());
+
+ static
+ {
+ String workdir = System.getProperty("QPID_WORK");
+ if (workdir == null || workdir.equals(""))
+ {
+ String tempdir = System.getProperty("java.io.tmpdir");
+ System.out.println("QPID_WORK not set using tmp directory: " + tempdir);
+ System.setProperty("QPID_WORK", tempdir);
+ }
+ }
+
+ /**
+ * Tests that mandatory message which are not routable are returned to the producer
+ *
+ * @throws Exception
+ */
+ public void testReturnUnroutableMandatoryMessage_HEADERS() throws Exception
+ {
+ _bouncedMessageList.clear();
+ MessageConsumer consumer = null;
+ AMQSession producerSession = null;
+ AMQHeadersExchange queue = null;
+ Connection con=null, con2 = null;
+ try
+ {
+ con = getConnection();
+
+ AMQSession consumerSession = (AMQSession) con.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+
+ queue = new AMQHeadersExchange(new AMQBindingURL(ExchangeDefaults.HEADERS_EXCHANGE_CLASS + "://" + ExchangeDefaults.HEADERS_EXCHANGE_NAME + "/test/queue1?" + BindingURL.OPTION_ROUTING_KEY + "='F0000=1'"));
+ FieldTable ft = new FieldTable();
+ ft.setString("F1000", "1");
+ consumer = consumerSession.createConsumer(queue, Integer.parseInt(ClientProperties.MAX_PREFETCH_DEFAULT), Integer.parseInt(ClientProperties.MAX_PREFETCH_DEFAULT) /2 , false, false, (String) null, ft);
+
+ //force synch to ensure the consumer has resulted in a bound queue
+ //((AMQSession) consumerSession).declareExchangeSynch(ExchangeDefaults.HEADERS_EXCHANGE_NAME, ExchangeDefaults.HEADERS_EXCHANGE_CLASS);
+ // This is the default now
+
+ con2 = getConnection();
+
+ con2.setExceptionListener(this);
+ producerSession = (AMQSession) con2.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+
+ // Need to start the "producer" connection in order to receive bounced messages
+ _logger.info("Starting producer connection");
+ con2.start();
+ }
+ catch (JMSException jmse)
+ {
+ fail(jmse.getMessage());
+ }
+
+ try
+ {
+ MessageProducer nonMandatoryProducer = producerSession.createProducer(queue, false, false);
+ MessageProducer mandatoryProducer = producerSession.createProducer(queue);
+
+ // First test - should neither be bounced nor routed
+ _logger.info("Sending non-routable non-mandatory message");
+ TextMessage msg1 = producerSession.createTextMessage("msg1");
+ nonMandatoryProducer.send(msg1);
+
+ // Second test - should be bounced
+ _logger.info("Sending non-routable mandatory message");
+ TextMessage msg2 = producerSession.createTextMessage("msg2");
+ mandatoryProducer.send(msg2);
+
+ // Third test - should be routed
+ _logger.info("Sending routable message");
+ TextMessage msg3 = producerSession.createTextMessage("msg3");
+ msg3.setStringProperty("F1000", "1");
+ mandatoryProducer.send(msg3);
+
+ _logger.info("Starting consumer connection");
+ con.start();
+ TextMessage tm = (TextMessage) consumer.receive(1000L);
+
+ assertTrue("No message routed to receiver", tm != null);
+ assertTrue("Wrong message routed to receiver: " + tm.getText(), "msg3".equals(tm.getText()));
+
+ try
+ {
+ Thread.sleep(1000L);
+ }
+ catch (InterruptedException e)
+ {
+ ;
+ }
+
+ assertTrue("Wrong number of messages bounced (expect 1): " + _bouncedMessageList.size(), _bouncedMessageList.size() == 1);
+ Message m = _bouncedMessageList.get(0);
+ assertTrue("Wrong message bounced: " + m.toString(), m.toString().contains("msg2"));
+ }
+ catch (JMSException jmse)
+ {
+
+ }
+ con.close();
+ con2.close();
+
+ }
+
+ public void testReturnUnroutableMandatoryMessage_QUEUE() throws Exception
+ {
+ _bouncedMessageList.clear();
+ Connection con = getConnection();
+
+ AMQSession consumerSession = (AMQSession) con.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+
+ AMQQueue valid_queue = new AMQQueue(ExchangeDefaults.DIRECT_EXCHANGE_CLASS, "testReturnUnroutableMandatoryMessage_QUEUE");
+ AMQQueue invalid_queue = new AMQQueue(ExchangeDefaults.DIRECT_EXCHANGE_CLASS, "testReturnUnroutableMandatoryMessage_QUEUE_INVALID");
+ MessageConsumer consumer = consumerSession.createConsumer(valid_queue);
+
+ //force synch to ensure the consumer has resulted in a bound queue
+ //((AMQSession) consumerSession).declareExchangeSynch(ExchangeDefaults.HEADERS_EXCHANGE_NAME, ExchangeDefaults.HEADERS_EXCHANGE_CLASS);
+ // This is the default now
+
+ Connection con2 = getConnection();
+
+ con2.setExceptionListener(this);
+ AMQSession producerSession = (AMQSession) con2.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+
+ // Need to start the "producer" connection in order to receive bounced messages
+ _logger.info("Starting producer connection");
+ con2.start();
+
+ MessageProducer nonMandatoryProducer = producerSession.createProducer(valid_queue, false, false);
+ MessageProducer mandatoryProducer = producerSession.createProducer(invalid_queue);
+
+ // First test - should be routed
+ _logger.info("Sending non-mandatory message");
+ TextMessage msg1 = producerSession.createTextMessage("msg1");
+ nonMandatoryProducer.send(msg1);
+
+ // Second test - should be bounced
+ _logger.info("Sending non-routable mandatory message");
+ TextMessage msg2 = producerSession.createTextMessage("msg2");
+ mandatoryProducer.send(msg2);
+
+ _logger.info("Starting consumer connection");
+ con.start();
+ TextMessage tm = (TextMessage) consumer.receive(1000L);
+
+ assertTrue("No message routed to receiver", tm != null);
+ assertTrue("Wrong message routed to receiver: " + tm.getText(), "msg1".equals(tm.getText()));
+
+ try
+ {
+ Thread.sleep(1000L);
+ }
+ catch (InterruptedException e)
+ {
+ ;
+ }
+
+ assertTrue("Wrong number of messages bounced (expect 1): " + _bouncedMessageList.size(), _bouncedMessageList.size() == 1);
+ Message m = _bouncedMessageList.get(0);
+ assertTrue("Wrong message bounced: " + m.toString(), m.toString().contains("msg2"));
+
+ con.close();
+ con2.close();
+ }
+
+ public void testReturnUnroutableMandatoryMessage_TOPIC() throws Exception
+ {
+ _bouncedMessageList.clear();
+ Connection con = getConnection();
+
+ AMQSession consumerSession = (AMQSession) con.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+
+ AMQTopic valid_topic = new AMQTopic(ExchangeDefaults.TOPIC_EXCHANGE_CLASS, "test.Return.Unroutable.Mandatory.Message.TOPIC");
+ AMQTopic invalid_topic = new AMQTopic(ExchangeDefaults.TOPIC_EXCHANGE_CLASS, "test.Return.Unroutable.Mandatory.Message.TOPIC.invalid");
+ MessageConsumer consumer = consumerSession.createConsumer(valid_topic);
+
+ //force synch to ensure the consumer has resulted in a bound queue
+ //((AMQSession) consumerSession).declareExchangeSynch(ExchangeDefaults.HEADERS_EXCHANGE_NAME, ExchangeDefaults.HEADERS_EXCHANGE_CLASS);
+ // This is the default now
+
+ Connection con2 = getConnection();
+
+ con2.setExceptionListener(this);
+ AMQSession producerSession = (AMQSession) con2.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+
+ // Need to start the "producer" connection in order to receive bounced messages
+ _logger.info("Starting producer connection");
+ con2.start();
+
+ MessageProducer nonMandatoryProducer = producerSession.createProducer(valid_topic, false, false);
+ MessageProducer mandatoryProducer = producerSession.createProducer(invalid_topic);
+
+ // First test - should be routed
+ _logger.info("Sending non-mandatory message");
+ TextMessage msg1 = producerSession.createTextMessage("msg1");
+ nonMandatoryProducer.send(msg1);
+
+ // Second test - should be bounced
+ _logger.info("Sending non-routable mandatory message");
+ TextMessage msg2 = producerSession.createTextMessage("msg2");
+ mandatoryProducer.send(msg2);
+
+ _logger.info("Starting consumer connection");
+ con.start();
+ TextMessage tm = (TextMessage) consumer.receive(1000L);
+
+ assertTrue("No message routed to receiver", tm != null);
+ assertTrue("Wrong message routed to receiver: " + tm.getText(), "msg1".equals(tm.getText()));
+
+ try
+ {
+ Thread.sleep(1000L);
+ }
+ catch (InterruptedException e)
+ {
+ ;
+ }
+
+ assertEquals("Wrong number of messages bounced: ", 1, _bouncedMessageList.size());
+ Message m = _bouncedMessageList.get(0);
+ assertTrue("Wrong message bounced: " + m.toString(), m.toString().contains("msg2"));
+
+ con.close();
+ con2.close();
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(ReturnUnroutableMandatoryMessageTest.class);
+ }
+
+ public void onException(JMSException jmsException)
+ {
+
+ Exception linkedException = null;
+ try
+ {
+ linkedException = jmsException.getLinkedException();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
+ }
+ if (linkedException instanceof AMQNoRouteException)
+ {
+ AMQNoRouteException noRoute = (AMQNoRouteException) linkedException;
+ Message bounced = (Message) noRoute.getUndeliveredMessage();
+ _bouncedMessageList.add(bounced);
+ _logger.info("Caught expected NoRouteException");
+ }
+ else
+ {
+ _logger.warn("Caught exception on producer: ", jmsException);
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/failover/FailoverMethodTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/failover/FailoverMethodTest.java
new file mode 100644
index 0000000000..ec222ff03d
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/failover/FailoverMethodTest.java
@@ -0,0 +1,254 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.failover;
+
+import junit.framework.TestCase;
+
+import org.apache.qpid.AMQDisconnectedException;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQConnectionURL;
+import org.apache.qpid.client.transport.TransportConnection;
+import org.apache.qpid.client.vmbroker.AMQVMBrokerCreationException;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.util.InternalBrokerBaseCase;
+import org.apache.qpid.url.URLSyntaxException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.ExceptionListener;
+import javax.jms.JMSException;
+import java.util.concurrent.CountDownLatch;
+
+public class FailoverMethodTest extends InternalBrokerBaseCase implements ExceptionListener
+{
+ private CountDownLatch _failoverComplete = new CountDownLatch(1);
+ protected static final Logger _logger = LoggerFactory.getLogger(FailoverMethodTest.class);
+
+ @Override
+ public void createBroker() throws Exception
+ {
+ super.createBroker();
+ TransportConnection.createVMBroker(ApplicationRegistry.DEFAULT_INSTANCE);
+ }
+
+ @Override
+ public void stopBroker()
+ {
+ TransportConnection.killVMBroker(ApplicationRegistry.DEFAULT_INSTANCE);
+ super.stopBroker();
+ }
+
+ /**
+ * Test that the round robin method has the correct delays.
+ * The first connection to vm://:1 will work but the localhost connection should fail but the duration it takes
+ * to report the failure is what is being tested.
+ *
+ * @throws URLSyntaxException
+ * @throws InterruptedException
+ * @throws JMSException
+ */
+ public void testFailoverRoundRobinDelay() throws URLSyntaxException, InterruptedException, JMSException
+ {
+ //note: The VM broker has no connect delay and the default 1 retry
+ // while the tcp:localhost broker has 3 retries with a 2s connect delay
+ String connectionString = "amqp://guest:guest@/test?brokerlist=" +
+ "'vm://:" + ApplicationRegistry.DEFAULT_INSTANCE +
+ ";tcp://localhost:5670?connectdelay='2000',retries='3''";
+
+ AMQConnectionURL url = new AMQConnectionURL(connectionString);
+
+ try
+ {
+ long start = System.currentTimeMillis();
+ AMQConnection connection = new AMQConnection(url, null);
+
+ connection.setExceptionListener(this);
+
+ stopBroker();
+
+ _failoverComplete.await();
+
+ long end = System.currentTimeMillis();
+
+ long duration = (end - start);
+
+ //Failover should take more that 6 seconds.
+ // 3 Retires
+ // so VM Broker NoDelay 0 (Connect) NoDelay 0
+ // then TCP NoDelay 0 Delay 1 Delay 2 Delay 3
+ // so 3 delays of 2s in total for connection
+ // as this is a tcp connection it will take 1second per connection to fail
+ // so max time is 6seconds of delay plus 4 seconds of TCP Delay + 1 second of runtime. == 11 seconds
+
+ // Ensure we actually had the delay
+ assertTrue("Failover took less than 6 seconds", duration > 6000);
+
+ // Ensure we don't have delays before initial connection and reconnection.
+ // We allow 1 second for initial connection and failover logic on top of 6s of sleep.
+ assertTrue("Failover took more than 11 seconds:(" + duration + ")", duration < 11000);
+ }
+ catch (AMQException e)
+ {
+ fail(e.getMessage());
+ }
+ }
+
+ public void testFailoverSingleDelay() throws URLSyntaxException, AMQVMBrokerCreationException,
+ InterruptedException, JMSException
+ {
+ String connectionString = "amqp://guest:guest@/test?brokerlist='vm://:1?connectdelay='2000',retries='3''";
+
+ AMQConnectionURL url = new AMQConnectionURL(connectionString);
+
+ try
+ {
+ long start = System.currentTimeMillis();
+ AMQConnection connection = new AMQConnection(url, null);
+
+ connection.setExceptionListener(this);
+
+ stopBroker();
+
+ _failoverComplete.await();
+
+ long end = System.currentTimeMillis();
+
+ long duration = (end - start);
+
+ //Failover should take more that 6 seconds.
+ // 3 Retires
+ // so NoDelay 0 (Connect) NoDelay 0 Delay 1 Delay 2 Delay 3
+ // so 3 delays of 2s in total for connection
+ // so max time is 6 seconds of delay + 1 second of runtime. == 7 seconds
+
+ // Ensure we actually had the delay
+ assertTrue("Failover took less than 6 seconds", duration > 6000);
+
+ // Ensure we don't have delays before initial connection and reconnection.
+ // We allow 1 second for initial connection and failover logic on top of 6s of sleep.
+ assertTrue("Failover took more than 7 seconds:(" + duration + ")", duration < 7000);
+ }
+ catch (AMQException e)
+ {
+ fail(e.getMessage());
+ }
+ }
+
+ public void onException(JMSException e)
+ {
+ if (e.getLinkedException() instanceof AMQDisconnectedException)
+ {
+ _logger.debug("Received AMQDisconnectedException");
+ _failoverComplete.countDown();
+ }
+ }
+
+ /**
+ * Test that setting 'nofailover' as the failover policy does not result in
+ * delays or connection attempts when the initial connection is lost.
+ *
+ * Test validates that there is a connection delay as required on initial
+ * connection.
+ *
+ * @throws URLSyntaxException
+ * @throws AMQVMBrokerCreationException
+ * @throws InterruptedException
+ * @throws JMSException
+ */
+ public void testNoFailover() throws URLSyntaxException, AMQVMBrokerCreationException,
+ InterruptedException, JMSException
+ {
+ int CONNECT_DELAY = 2000;
+ String connectionString = "amqp://guest:guest@/test?brokerlist='vm://:1?connectdelay='" + CONNECT_DELAY + "'," +
+ "retries='3'',failover='nofailover'";
+
+ AMQConnectionURL url = new AMQConnectionURL(connectionString);
+
+ try
+ {
+ //Kill initial broker
+ stopBroker();
+
+ //Create a thread to start the broker asynchronously
+ Thread brokerStart = new Thread(new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ //Wait before starting broker
+ // The wait should allow atleast 1 retries to fail before broker is ready
+ Thread.sleep(750);
+ createBroker();
+ }
+ catch (Exception e)
+ {
+ System.err.println(e.getMessage());
+ e.printStackTrace();
+ }
+ }
+ });
+
+
+ brokerStart.start();
+ long start = System.currentTimeMillis();
+
+
+ //Start the connection so it will use the retries
+ AMQConnection connection = new AMQConnection(url, null);
+
+ long end = System.currentTimeMillis();
+
+ long duration = (end - start);
+
+ // Check that we actually had a delay had a delay in connection
+ assertTrue("Initial connection should be longer than 1 delay : " + CONNECT_DELAY + " <:(" + duration + ")", duration > CONNECT_DELAY);
+
+
+ connection.setExceptionListener(this);
+
+ //Ensure we collect the brokerStart thread
+ brokerStart.join();
+
+ start = System.currentTimeMillis();
+
+ //Kill connection
+ stopBroker();
+
+ _failoverComplete.await();
+
+ end = System.currentTimeMillis();
+
+ duration = (end - start);
+
+ // Notification of the connection failure should be very quick as we are denying the ability to failover.
+ // It may not be as quick for Java profile tests so lets just make sure it is less than the connectiondelay
+ // Occasionally it takes 1s so we have to set CONNECT_DELAY to be higher to take that in to account.
+ assertTrue("Notification of the connection failure took was : " + CONNECT_DELAY + " >:(" + duration + ")", duration < CONNECT_DELAY);
+ }
+ catch (AMQException e)
+ {
+ fail(e.getMessage());
+ }
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/failover/MessageDisappearWithIOExceptionTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/failover/MessageDisappearWithIOExceptionTest.java
new file mode 100644
index 0000000000..4c2758241e
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/failover/MessageDisappearWithIOExceptionTest.java
@@ -0,0 +1,338 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.failover;
+
+import org.apache.mina.common.WriteTimeoutException;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+import org.apache.qpid.jms.ConnectionListener;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.test.utils.FailoverBaseCase;
+import org.apache.qpid.AMQConnectionClosedException;
+
+import javax.jms.Destination;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test case based on user reported error.
+ *
+ * Summary:
+ * A user has reported message loss from their application. On bouncing of
+ * the broker the 'lost' messages are delivered to the broker.
+ *
+ * Note:
+ * The client was using Spring so that may influence the situation.
+ *
+ * Issue:
+ * The log files show 7 instances of the following which result in 7
+ * missing messages.
+ *
+ * The client log files show:
+ *
+ * The broker log file show:
+ *
+ *
+ * 7 missing messages have delivery tags 5-11. Which says that they are
+ * sequentially the next message from the broker.
+ *
+ * The only way for the 'without a handler' log to occur is if the consumer
+ * has been removed from the look up table of the dispatcher.
+ * And the only way for the 'null message' log to occur on the broker is is
+ * if the message does not exist in the unacked-map
+ *
+ * The consumer is only removed from the list during session
+ * closure and failover.
+ *
+ * If the session was closed then the broker would requeue the unacked
+ * messages so the potential exists to have an empty map but the broker
+ * will not send a message out after the unacked map has been cleared.
+ *
+ * When failover occurs the _consumer map is cleared and the consumers are
+ * resubscribed. This is down without first stopping any existing
+ * dispatcher so there exists the potential to receive a message after
+ * the _consumer map has been cleared which is how the 'without a handler'
+ * log statement occurs.
+ *
+ * Scenario:
+ *
+ * Looking over logs the sequence that best fits the events is as follows:
+ * - Something causes Mina to be delayed causing the WriteTimoutException.
+ * - This exception is recevied by AMQProtocolHandler#exceptionCaught
+ * - As the WriteTimeoutException is an IOException this will cause
+ * sessionClosed to be called to start failover.
+ * + This is potentially the issues here. All IOExceptions are treated
+ * as connection failure events.
+ * - Failover Runs
+ * + Failover assumes that the previous connection has been closed.
+ * + Failover binds the existing objects (AMQConnection/Session) to the
+ * new connection objects.
+ * - Everything is reported as being successfully failed over.
+ * However, what is neglected is that the original connection has not
+ * been closed.
+ * + So what occurs is that the broker sends a message to the consumer on
+ * the original connection, as it was not notified of the client
+ * failing over.
+ * As the client failover reuses the original AMQSession and Dispatcher
+ * the new messages the broker sends to the old consumer arrives at the
+ * client and is processed by the same AMQSession and Dispatcher.
+ * However, as the failover process cleared the _consumer map and
+ * resubscribe the consumers the Dispatcher does not recognise the
+ * delivery tag and so logs the 'without a handler' message.
+ * - The Dispatcher then attempts to reject the message, however,
+ * + The AMQSession/Dispatcher pair have been swapped to using a new Mina
+ * ProtocolSession as part of the failover process so the reject is
+ * sent down the second connection. The broker receives the Reject
+ * request but as the Message was sent on a different connection the
+ * unacknowledgemap is empty and a 'message is null' log message
+ * produced.
+ *
+ * Test Strategy:
+ *
+ * It should be easy to demonstrate if we can send an IOException to
+ * AMQProtocolHandler#exceptionCaught and then try sending a message.
+ *
+ * The current unknowns here are the type of consumers that are in use.
+ * If it was an exclusive queue(Durable Subscription) then why did the
+ * resubscribe not fail.
+ *
+ * If it was not exclusive then why did the messages not round robin?
+ */
+public class MessageDisappearWithIOExceptionTest extends FailoverBaseCase implements ConnectionListener
+{
+ private CountDownLatch _failoverOccured = new CountDownLatch(1);
+ AMQConnection _connection;
+ Session _session;
+ Queue _queue;
+ MessageConsumer _consumer;
+
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ stopBroker(getFailingPort());
+
+ }
+
+ /**
+ * Test Summary:
+ *
+ * Create a queue consumer and send 10 messages to the broker.
+ *
+ * Consume the first message.
+ * This will pull the rest into the prefetch
+ *
+ * Send an IOException to the MinaProtocolHandler.
+ *
+ * This will force failover to occur.
+ *
+ * 9 messages would normally be expected but it is expected that none will
+ * arrive. As they are still in the prefetch of the first session.
+ *
+ * To free the messages we need to close all connections.
+ * - Simply doing connection.close() and retesting will not be enough as
+ * the original connection's IO layer will still exist and is nolonger
+ * connected to the connection object as a result of failover.
+ *
+ * - Test will need to retain a reference to the original connection IO so
+ * that it can be closed releasing the messages to validate that the
+ * messages have indeed been 'lost' on that sesssion.
+ */
+ public void test() throws Exception
+ {
+ initialiseConnection();
+
+ // Create Producer
+ // Send 10 messages
+ List<Message> messages = sendNumberedBytesMessage(_session, _queue, 10);
+
+ // Consume first messasge
+ Message received = _consumer.receive(2000);
+
+ // Verify received messages
+ assertNotNull("First message not received.", received);
+ assertEquals("Incorrect message Received",
+ messages.remove(0).getIntProperty("count"),
+ received.getIntProperty("count"));
+
+ // When the Exception is received by the underlying IO layer it will
+ // initiate failover. The first step of which is to ensure that the
+ // existing conection is closed. So in this situation the connection
+ // will be flushed casuing the above ACK to be sent to the broker.
+ //
+ // That said:
+ // when the socket close is detected on the server it will rise up the
+ // Mina filter chain and interrupt processing.
+ // this has been raised as QPID-2138
+ _session.createConsumer(_session.createTemporaryQueue()).close();
+
+ //Retain IO Layer
+ AMQProtocolSession protocolSession = _connection.getProtocolHandler().getProtocolSession();
+
+ // Send IO Exception - causing failover
+ _connection.getProtocolHandler().
+ exception(new WriteTimeoutException("WriteTimeoutException to cause failover."));
+
+ // Verify Failover occured through ConnectionListener
+ assertTrue("Failover did not occur",
+ _failoverOccured.await(4000, TimeUnit.MILLISECONDS));
+
+ /***********************************/
+ // This verifies that the bug has been resolved
+
+ // Attempt to consume again. Expect 9 messages
+ for (int count = 1; count < 10; count++)
+ {
+ received = _consumer.receive(2000);
+ assertNotNull("Expected message not received:" + count, received);
+ assertEquals(messages.remove(0).getIntProperty("count"),
+ received.getIntProperty("count"));
+ }
+
+ //Verify there are no more messages
+ received = _consumer.receive(1000);
+ assertNull("Message receieved when there should be none:" + received,
+ received);
+
+// /***********************************/
+// // This verifies that the bug exists
+//
+// // Attempt to consume remaining 9 messages.. Expecting NONE.
+// // receiving just one message should fail so no need to fail 9 times
+// received = _consumer.receive(1000);
+// assertNull("Message receieved when it should be null:" + received, received);
+//
+//// //Close the Connection which you would assume would free the messages
+//// _connection.close();
+////
+//// // Reconnect
+//// initialiseConnection();
+////
+//// // We should still be unable to receive messages
+//// received = _consumer.receive(1000);
+//// assertNull("Message receieved when it should be null:" + received, received);
+////
+//// _connection.close();
+//
+// // Close original IO layer. Expecting messages to be released
+// protocolSession.closeProtocolSession();
+//
+// // Reconnect and all should be good.
+//// initialiseConnection();
+//
+// // Attempt to consume again. Expect 9 messages
+// for (int count = 1; count < 10; count++)
+// {
+// received = _consumer.receive(2000);
+// assertNotNull("Expected message not received:" + count, received);
+// assertEquals(messages.remove(0).getIntProperty("count"),
+// received.getIntProperty("count"));
+// }
+//
+// //Verify there are no more messages
+// received = _consumer.receive(1000);
+// assertNull("Message receieved when there should be none:" + received,
+// received);
+ }
+
+ private void initialiseConnection()
+ throws Exception
+ {
+ //Create Connection using the default connection URL. i.e. not the Failover URL that would be used by default
+ _connection = (AMQConnection) getConnectionFactory("default").createConnection("guest", "guest");
+ // The default connection does not have any retries configured so
+ // Allow this connection to retry so that we can block on the failover.
+ // The alternative would be to use the getConnection() default. However,
+ // this would add additional complexity in the logging as a second
+ // broker is defined in that url. We do not need it for this test.
+ _connection.getFailoverPolicy().getCurrentMethod().setRetries(1);
+ _connection.setConnectionListener(this);
+
+ _session = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ _queue = _session.createQueue(getTestQueueName());
+
+ // Create Consumer
+ _consumer = _session.createConsumer(_queue);
+
+ //Start connection
+ _connection.start();
+ }
+
+ /** QpidTestCase back port to this release */
+
+ // modified from QTC as sendMessage is not testable.
+ // - should be renamed sendBlankBytesMessage
+ // - should be renamed sendNumberedBytesMessage
+ public List<Message> sendNumberedBytesMessage(Session session, Destination destination,
+ int count) throws Exception
+ {
+ List<Message> messages = new ArrayList<Message>(count);
+
+ MessageProducer producer = session.createProducer(destination);
+
+ for (int i = 0; i < count; i++)
+ {
+ Message next = session.createMessage();
+
+ next.setIntProperty("count", i);
+
+ producer.send(next);
+
+ messages.add(next);
+ }
+
+ producer.close();
+ return messages;
+ }
+
+ public void bytesSent(long count)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void bytesReceived(long count)
+ {
+ }
+
+ public boolean preFailover(boolean redirect)
+ {
+ //Allow failover to occur
+ return true;
+ }
+
+ public boolean preResubscribe()
+ {
+ //Allow failover to occur
+ return true;
+ }
+
+ public void failoverComplete()
+ {
+ _failoverOccured.countDown();
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/failure/HeapExhaustion.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/failure/HeapExhaustion.java
new file mode 100644
index 0000000000..22a1b119fa
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/failure/HeapExhaustion.java
@@ -0,0 +1,236 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.server.failure;
+
+import junit.framework.TestCase;
+import org.apache.qpid.test.utils.QpidClientConnectionHelper;
+import org.apache.qpid.client.failover.FailoverException;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.log4j.Logger;
+
+import javax.jms.JMSException;
+import javax.jms.DeliveryMode;
+import java.io.IOException;
+
+
+/** Test Case provided by client Non-functional Test NF101: heap exhaustion behaviour */
+public class HeapExhaustion extends TestCase
+{
+ private static final Logger _logger = Logger.getLogger(HeapExhaustion.class);
+
+ protected QpidClientConnectionHelper conn;
+ protected final String BROKER = "localhost";
+ protected final String vhost = "/test";
+ protected final String queue = "direct://amq.direct//queue";
+
+ protected String hundredK;
+ protected String megabyte;
+
+ protected String generatePayloadOfSize(Integer numBytes)
+ {
+ return new String(new byte[numBytes]);
+ }
+
+ protected void setUp() throws Exception
+ {
+ conn = new QpidClientConnectionHelper(BROKER);
+ conn.setVirtualHost(vhost);
+
+ try
+ {
+ conn.connect();
+ } catch (JMSException e)
+ {
+ e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
+ }
+ // clear queue
+ _logger.debug("setup: clearing test queue");
+ conn.consume(queue, 2000);
+
+ hundredK = generatePayloadOfSize(1024 * 100);
+ megabyte = generatePayloadOfSize(1024 * 1024);
+ }
+
+ protected void tearDown() throws Exception
+ {
+ conn.disconnect();
+ }
+
+
+ /**
+ * PUT at maximum rate (although we commit after each PUT) until failure
+ *
+ * @throws Exception on error
+ */
+ public void testUntilFailureTransient() throws Exception
+ {
+ int copies = 0;
+ int total = 0;
+ String payload = hundredK;
+ int size = payload.getBytes().length;
+ while (true)
+ {
+ conn.put(queue, payload, 1, DeliveryMode.NON_PERSISTENT);
+ copies++;
+ total += size;
+ System.out.println("put copy " + copies + " OK for total bytes: " + total);
+ }
+ }
+
+ /**
+ * PUT at lower rate (5 per second) until failure
+ *
+ * @throws Exception on error
+ */
+ public void testUntilFailureWithDelaysTransient() throws Exception
+ {
+ int copies = 0;
+ int total = 0;
+ String payload = hundredK;
+ int size = payload.getBytes().length;
+ while (true)
+ {
+ conn.put(queue, payload, 1, DeliveryMode.NON_PERSISTENT);
+ copies++;
+ total += size;
+ System.out.println("put copy " + copies + " OK for total bytes: " + total);
+ Thread.sleep(200);
+ }
+ }
+
+ public static void noDelay()
+ {
+ HeapExhaustion he = new HeapExhaustion();
+
+ try
+ {
+ he.setUp();
+ }
+ catch (Exception e)
+ {
+ _logger.info("Unable to connect");
+ System.exit(0);
+ }
+
+ try
+ {
+ _logger.info("Running testUntilFailure");
+ try
+ {
+ he.testUntilFailureTransient();
+ }
+ catch (FailoverException fe)
+ {
+ _logger.error("Caught failover:" + fe);
+ }
+ _logger.info("Finishing Connection ");
+
+ try
+ {
+ he.tearDown();
+ }
+ catch (JMSException jmse)
+ {
+ if (((AMQException) jmse.getLinkedException()).getErrorCode() == AMQConstant.REQUEST_TIMEOUT)
+ {
+ _logger.info("Successful test of testUntilFailure");
+ }
+ else
+ {
+ _logger.error("Test Failed due to:" + jmse);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.error("Test Failed due to:" + e);
+ }
+ }
+
+ public static void withDelay()
+ {
+ HeapExhaustion he = new HeapExhaustion();
+
+ try
+ {
+ he.setUp();
+ }
+ catch (Exception e)
+ {
+ _logger.info("Unable to connect");
+ System.exit(0);
+ }
+
+ try
+ {
+ _logger.info("Running testUntilFailure");
+ try
+ {
+ he.testUntilFailureWithDelaysTransient();
+ }
+ catch (FailoverException fe)
+ {
+ _logger.error("Caught failover:" + fe);
+ }
+ _logger.info("Finishing Connection ");
+
+ try
+ {
+ he.tearDown();
+ }
+ catch (JMSException jmse)
+ {
+ if (((AMQException) jmse.getLinkedException()).getErrorCode() == AMQConstant.REQUEST_TIMEOUT)
+ {
+ _logger.info("Successful test of testUntilFailure");
+ }
+ else
+ {
+ _logger.error("Test Failed due to:" + jmse);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.error("Test Failed due to:" + e);
+ }
+ }
+
+ public static void main(String args[])
+ {
+ noDelay();
+
+
+ try
+ {
+ System.out.println("Restart failed broker now to retest broker with delays in send.");
+ System.in.read();
+ }
+ catch (IOException e)
+ {
+ _logger.info("Continuing");
+ }
+
+ withDelay();
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AbstractTestLogging.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AbstractTestLogging.java
new file mode 100644
index 0000000000..f56f428f0b
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AbstractTestLogging.java
@@ -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.
+ *
+ */
+package org.apache.qpid.server.logging;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.qpid.server.configuration.ServerConfiguration;
+import org.apache.qpid.server.logging.subjects.AbstractTestLogSubject;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.util.InternalBrokerBaseCase;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.util.LogMonitor;
+
+/**
+ * Abstract superclass for logging test set up and utility methods.
+ *
+ * So named to prevent it being selected itself as a test to run by the test suite.
+ */
+public class AbstractTestLogging extends QpidBrokerTestCase
+{
+ public static final long DEFAULT_LOG_WAIT = 2000;
+ public static final String TEST_LOG_PREFIX = "MESSAGE";
+ protected LogMonitor _monitor;
+
+ InternalBrokerBaseCase _configLoader;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ setLogMessagePrefix();
+
+ super.setUp();
+ _monitor = new LogMonitor(_outputFile);
+ }
+
+ protected ServerConfiguration getServerConfig() throws ConfigurationException
+ {
+ ServerConfiguration _serverConfiguration;
+ if (isExternalBroker())
+ {
+ _serverConfiguration = new ServerConfiguration(_configFile)
+ {
+ @Override
+ public void initialise() throws ConfigurationException
+ {
+ //Overriding initialise to only setup the vhosts and not
+ //perform the ConfigurationPlugin setup, removing need for
+ //an ApplicationRegistry to be loaded.
+ setupVirtualHosts(getConfig());
+ }
+ };
+ _serverConfiguration.initialise();
+ }
+ else
+ {
+ _serverConfiguration = ApplicationRegistry.getInstance().getConfiguration();
+ }
+
+ return _serverConfiguration;
+ }
+
+ protected void setLogMessagePrefix()
+ {
+ //set the message prefix to facilitate scraping from the munged test output.
+ setSystemProperty("qpid.logging.prefix", TEST_LOG_PREFIX);
+ }
+
+ @Override
+ public void tearDown() throws Exception
+ {
+ _monitor.close();
+ if (isExternalBroker() && _configLoader != null)
+ {
+ _configLoader.tearDown();
+ }
+ super.tearDown();
+ }
+
+ /**
+ * assert that the requested log message has not occured
+ *
+ * @param log
+ *
+ * @throws IOException
+ */
+ public void assertLoggingNotYetOccured(String log) throws IOException
+ {
+ // Ensure the alert has not occured yet
+ assertEquals("Message has already occured:" + log, 0,
+ findMatches(log).size());
+ }
+
+ protected void validateMessageID(String id, String log)
+ {
+ assertEquals("Incorrect message", id, getMessageID(log));
+ }
+
+ protected String getMessageID(String log)
+ {
+ String message = fromMessage(log);
+
+ return message.substring(0, message.indexOf(" "));
+ }
+
+ /**
+ * Return the first channel id from the log string
+ * ' ch;X' if there is no channel id return -1.
+ *
+ * @param log the log string to search.
+ *
+ * @return channel id or -1 if no channel id exists.
+ */
+ protected int getChannelID(String log)
+ {
+ int start = log.indexOf("ch:") + 3;
+
+ // If we do a check for ] as the boundary we will get cases where log
+ // is presented with the bounding. If we don't match a ] then we can use
+ // the end of the string as the boundary.
+ int end = log.indexOf("]", start);
+ if (end == -1)
+ {
+ end = log.length();
+ }
+
+ try
+ {
+ return Integer.parseInt(log.substring(start, end));
+ }
+ catch (Exception e)
+ {
+ return -1;
+ }
+ }
+
+ protected String fromMessage(String log)
+ {
+ int startSubject = log.indexOf("]") + 1;
+ int start = log.indexOf("]", startSubject) + 1;
+
+ // If we don't have a subject then the second indexOf will return 0
+ // in which case we can use the end of the actor as the index.
+ if (start == 0)
+ {
+ start = startSubject;
+ }
+
+ return log.substring(start).trim();
+ }
+
+ /**
+ * Extract the Subject from the Log Message.
+ *
+ * The subject is the second block inclosed in brackets '[ ]'.
+ *
+ * If there is no Subject or the second block of brackets '[ ]' cannot be
+ * identified then an empty String ("") is returned.
+ *
+ * The brackets '[ ]' are not included in the returned String.
+ *
+ * @param log The log message to process
+ *
+ * @return the Subject string or the empty string ("") if the subject can't be identified.
+ */
+ protected String fromSubject(String log)
+ {
+ int start = log.indexOf("[") + 1;
+ // Take the second index
+ start = log.indexOf("[", start) + 1;
+
+ // There may not be a subject so in that case return nothing.
+ if (start == 0)
+ {
+ return "";
+ }
+
+ int end = log.indexOf("]", start);
+ try
+ {
+ return log.substring(start, end);
+ }
+ catch (IndexOutOfBoundsException iobe)
+ {
+ return "";
+ }
+ }
+
+ /**
+ * Extract the actor segment from the log message.
+ * The Actor segment is the first section enclosed in '[ ]'.
+ *
+ * No analysis is performed to ensure that the first '[ ]' section of the
+ * given log is really an Actor segment.
+ *
+ * The brackets '[ ]' are not included in the returned String.
+ *
+ * @param log the Log Message
+ *
+ * @return the Actor segment or "" if unable to locate '[ ]' section
+ */
+ protected String fromActor(String log)
+ {
+ int start = log.indexOf("[") + 1;
+ int end = log.indexOf("]", start);
+ try
+ {
+ return log.substring(start, end).trim();
+ }
+ catch (IndexOutOfBoundsException iobe)
+ {
+ return "";
+ }
+ }
+
+ /**
+ * Return the message String from the given message section
+ *
+ * @param log the Message Section
+ *
+ * @return the Message String.
+ */
+ protected String getMessageString(String log)
+ {
+ // Remove the Log ID from the returned String
+ int start = log.indexOf(":") + 1;
+
+ return log.substring(start).trim();
+ }
+
+ /**
+ * Given our log message extract the connection ID:
+ *
+ * The log string will contain the connectionID identified by 'con:'
+ *
+ * So extract the value shown here by X:
+ *
+ * 'con:X('
+ *
+ * Extract the value between the ':' and '(' and process it as an Integer
+ *
+ * If we are unable to find the right index or process the substring as an
+ * Integer then return -1.
+ *
+ * @param log the log String to process
+ *
+ * @return the connection ID or -1.
+ */
+ protected int getConnectionID(String log)
+ {
+ int conIDStart = log.indexOf("con:") + 4;
+ int conIDEnd = log.indexOf("(", conIDStart);
+ try
+ {
+ return Integer.parseInt(log.substring(conIDStart, conIDEnd));
+ }
+ catch (Exception e)
+ {
+ return -1;
+ }
+ }
+
+ /**
+ * Extract the log entry from the raw log line which will contain other
+ * log4j formatting.
+ *
+ * This formatting may impead our testing process so extract the log message
+ * as we know it to be formatted.
+ *
+ * This starts with the string MESSAGE
+ *
+ * @param rawLog the raw log
+ *
+ * @return the log we are expecting to be printed without the log4j prefixes
+ */
+ protected String getLog(String rawLog)
+ {
+ int start = rawLog.indexOf(TEST_LOG_PREFIX);
+ return rawLog.substring(start);
+ }
+
+ /**
+ * Extract the log entry from the result set. Positions are 0-based.
+ *
+ * @param results list of log message results to extract from
+ * @param position position in the list of the message to extract
+ * @return the message string
+ */
+ protected String getLogMessage(List<String> results, int position)
+ {
+ return getLog(results.get(position));
+ }
+
+ /**
+ * Extract the nth-from-last log entry from the result set.
+ *
+ * @param results list of log message results to extract from
+ * @param positionFromEnd position from end of the message list to extract (eg 0 for last)
+ * @return the message string
+ */
+ protected String getLogMessageFromEnd(List<String> results, int positionFromEnd)
+ {
+ int resultSize = results.size();
+ return getLogMessage(results, resultSize - 1 - positionFromEnd);
+ }
+
+ protected List<String> findMatches(String toFind) throws IOException
+ {
+ return _monitor.findMatches(toFind);
+ }
+
+ protected List<String> waitAndFindMatches(String toFind) throws IOException
+ {
+ return waitAndFindMatches(toFind, DEFAULT_LOG_WAIT);
+ }
+
+ protected List<String> waitAndFindMatches(String toFind, long wait) throws IOException
+ {
+ return _monitor.waitAndFindMatches(toFind, wait);
+ }
+
+ public boolean waitForMessage(String message) throws FileNotFoundException, IOException
+ {
+ return waitForMessage(message, DEFAULT_LOG_WAIT);
+ }
+
+ public boolean waitForMessage(String message, long wait) throws FileNotFoundException, IOException
+ {
+ return _monitor.waitForMessage(message, wait, true);
+ }
+
+ /**
+ * Given a list of messages that have been pulled out of a log file
+ * Process the results splitting the log statements in to lists based on the
+ * actor's connection ID.
+ *
+ * So for each log entry extract the Connecition ID from the Actor of the log
+ *
+ * Then use that as a key to a HashMap storing the list of log messages for
+ * that connection.
+ *
+ * @param logMessages The list of mixed connection log messages
+ *
+ * @return Map indexed by connection id to a list of log messages just for that connection.
+ */
+ protected HashMap<Integer, List<String>> splitResultsOnConnectionID(List<String> logMessages)
+ {
+ HashMap<Integer, List<String>> connectionSplitList = new HashMap<Integer, List<String>>();
+
+ for (String log : logMessages)
+ {
+ // Get the connectionID from the Actor in the Message Log.
+ int cID = getConnectionID(fromActor(getLog(log)));
+
+ List<String> connectionData = connectionSplitList.get(cID);
+
+ // Create the initial List if we don't have one already
+ if (connectionData == null)
+ {
+ connectionData = new LinkedList<String>();
+ connectionSplitList.put(cID, connectionData);
+ }
+
+ // Store the log
+ connectionData.add(log);
+ }
+
+ return connectionSplitList;
+ }
+
+ /**
+ * Filter the give result set by the specficifed virtualhost.
+ * This is done using the getSlice to identify the virtualhost (vh) in the
+ * log message
+ *
+ * @param results full list of logs
+ * @param virtualHostName the virtualhostName to filter on
+ *
+ * @return the list of messages only for that virtualhost
+ */
+ protected List<String> filterResultsByVirtualHost(List<String> results, String virtualHostName)
+ {
+ List<String> filteredResults = new LinkedList<String>();
+ Iterator<String> iterator = results.iterator();
+
+ while (iterator.hasNext())
+ {
+ String log = iterator.next();
+
+ if (AbstractTestLogSubject.getSlice("vh", log).equals(virtualHostName))
+ {
+ filteredResults.add(log);
+ }
+ }
+
+ return filteredResults;
+ }
+
+ /**
+ * Dump the log results.
+ */
+ protected void dumpLogs(List<String> results) throws IOException
+ {
+ dumpLogs(results, null);
+ }
+
+ /**
+ * Dump the log results or if there are none, the contents of the
+ * monitored log file if the monitor is non-null.
+ */
+ protected void dumpLogs(List<String> results, LogMonitor monitor) throws IOException
+ {
+ System.err.println("Log Dump:");
+ for (String log : results)
+ {
+ System.err.println(log);
+ }
+
+ if (results.isEmpty() && monitor != null)
+ {
+ System.err.println("Monitored file contents:");
+ System.err.println(monitor.readFile());
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AccessControlLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AccessControlLoggingTest.java
new file mode 100644
index 0000000000..2629e82831
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AccessControlLoggingTest.java
@@ -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.
+ */
+package org.apache.qpid.server.logging;
+
+import java.io.File;
+import java.util.List;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Session;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.protocol.AMQConstant;
+
+/**
+ * ACL version 2/3 file testing to verify that ACL actor logging works correctly.
+ *
+ * This suite of tests validate that the AccessControl messages occur correctly
+ * and according to the following format:
+ *
+ * <pre>
+ * ACL-1001 : Allowed Operation Object {PROPERTIES}
+ * ACL-1002 : Denied Operation Object {PROPERTIES}
+ * </pre>
+ */
+public class AccessControlLoggingTest extends AbstractTestLogging
+{
+ private static final String ACL_LOG_PREFIX = "ACL-";
+ private static final String USER = "client";
+ private static final String PASS = "guest";
+
+ public void setUp() throws Exception
+ {
+ setConfigurationProperty("virtualhosts.virtualhost.test.security.aclv2",
+ QpidHome + File.separator + "etc" + File.separator + "test-logging.txt");
+
+ super.setUp();
+ }
+
+ /** FIXME This comes from SimpleACLTest and makes me suspicious. */
+ @Override
+ public void tearDown() throws Exception
+ {
+ try
+ {
+ super.tearDown();
+ }
+ catch (JMSException e)
+ {
+ //we're throwing this away as it can happen in this test as the state manager remembers exceptions
+ //that we provoked with authentication failures, where the test passes - we can ignore on con close
+ }
+ }
+
+ /**
+ * Test that {@code allow} ACL entries do not log anything.
+ */
+ public void testAllow() throws Exception
+ {
+ Connection conn = getConnection(USER, PASS);
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ conn.start();
+ ((AMQSession<?, ?>) sess).createQueue(new AMQShortString("allow"), false, false, false);
+
+ List<String> matches = findMatches(ACL_LOG_PREFIX);
+
+ assertTrue("Should be no ACL log messages", matches.isEmpty());
+ }
+
+ /**
+ * Test that {@code allow-log} ACL entries log correctly.
+ */
+ public void testAllowLog() throws Exception
+ {
+ Connection conn = getConnection(USER, PASS);
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ conn.start();
+ ((AMQSession<?, ?>) sess).createQueue(new AMQShortString("allow-log"), false, false, false);
+
+ List<String> matches = findMatches(ACL_LOG_PREFIX);
+
+ assertEquals("Should only be one ACL log message", 1, matches.size());
+
+ String log = getLogMessage(matches, 0);
+ String actor = fromActor(log);
+ String subject = fromSubject(log);
+ String message = getMessageString(fromMessage(log));
+
+ validateMessageID(ACL_LOG_PREFIX + 1001, log);
+
+ assertTrue("Actor should contain the user identity", actor.contains(USER));
+ assertTrue("Subject should be empty", subject.length() == 0);
+ assertTrue("Message should start with 'Allowed'", message.startsWith("Allowed"));
+ assertTrue("Message should contain 'Create Queue'", message.contains("Create Queue"));
+ assertTrue("Message should have contained the queue name", message.contains("allow-log"));
+ }
+
+ /**
+ * Test that {@code deny-log} ACL entries log correctly.
+ */
+ public void testDenyLog() throws Exception
+ {
+ Connection conn = getConnection(USER, PASS);
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ conn.start();
+ try {
+ ((AMQSession<?, ?>) sess).createQueue(new AMQShortString("deny-log"), false, false, false);
+ fail("Should have denied queue creation");
+ }
+ catch (AMQException amqe)
+ {
+ // Denied, so exception thrown
+ assertEquals("Expected ACCESS_REFUSED error code", AMQConstant.ACCESS_REFUSED, amqe.getErrorCode());
+ }
+
+ List<String> matches = findMatches(ACL_LOG_PREFIX);
+
+ assertEquals("Should only be one ACL log message", 1, matches.size());
+
+ String log = getLogMessage(matches, 0);
+ String actor = fromActor(log);
+ String subject = fromSubject(log);
+ String message = getMessageString(fromMessage(log));
+
+ validateMessageID(ACL_LOG_PREFIX + 1002, log);
+
+ assertTrue("Actor should contain the user identity", actor.contains(USER));
+ assertTrue("Subject should be empty", subject.length() == 0);
+ assertTrue("Message should start with 'Denied'", message.startsWith("Denied"));
+ assertTrue("Message should contain 'Create Queue'", message.contains("Create Queue"));
+ assertTrue("Message should have contained the queue name", message.contains("deny-log"));
+ }
+
+ /**
+ * Test that {@code deny} ACL entries do not log anything.
+ */
+ public void testDeny() throws Exception
+ {
+ Connection conn = getConnection(USER, PASS);
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ conn.start();
+ try {
+ ((AMQSession<?, ?>) sess).createQueue(new AMQShortString("deny"), false, false, false);
+ fail("Should have denied queue creation");
+ }
+ catch (AMQException amqe)
+ {
+ // Denied, so exception thrown
+ assertEquals("Expected ACCESS_REFUSED error code", AMQConstant.ACCESS_REFUSED, amqe.getErrorCode());
+ }
+
+ List<String> matches = findMatches(ACL_LOG_PREFIX);
+
+ assertTrue("Should be no ACL log messages", matches.isEmpty());
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AlertingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AlertingTest.java
new file mode 100644
index 0000000000..05aaf16af1
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/AlertingTest.java
@@ -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.
+*
+*/
+package org.apache.qpid.server.logging;
+
+import javax.jms.Connection;
+import javax.jms.Queue;
+import javax.jms.Session;
+
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.server.configuration.ServerConfiguration;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.util.FileUtils;
+
+public class AlertingTest extends AbstractTestLogging
+{
+ private String VIRTUALHOST = "test";
+ private Session _session;
+ private Connection _connection;
+ private Queue _destination;
+ private int _numMessages;
+
+ private static final int ALERT_LOG_WAIT_PERIOD = 5000;
+ private static final String MESSAGE_COUNT_ALERT = "MESSAGE_COUNT_ALERT";
+
+ public void setUp() throws Exception
+ {
+ // Update the configuration to make our virtualhost Persistent.
+ makeVirtualHostPersistent(VIRTUALHOST);
+ setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".housekeeping.expiredMessageCheckPeriod", "5000");
+
+ _numMessages = 50;
+
+ // Then we do the normal setup stuff like starting the broker, getting a connection etc.
+ super.setUp();
+
+ setupConnection();
+ }
+
+ @Override
+ public void tearDown() throws Exception
+ {
+ // Ensure queue is clean for next run.
+ drainQueue(_destination);
+ super.tearDown();
+ }
+
+
+ /**
+ * Create a new connection and ensure taht our destination queue is created
+ * and bound.
+ *
+ * Note that the tests here that restart the broker rely on persistence.
+ * However, the queue creation here is transient. So the queue will not be
+ * rebound on restart. Hence the consumer creation here rather than just the
+ * once.
+ *
+ * The persistent messages will recreate the queue but not bind it (as it
+ * was not a durable queue) However, the consumer creation here will ensure
+ * that the queue is correctly bound and can receive new messages.
+ *
+ * @throws Exception
+ */
+ private void setupConnection()
+ throws Exception
+ {
+ _connection = getConnection();
+ _session = _connection.createSession(true, Session.SESSION_TRANSACTED);
+ _destination = _session.createQueue(getTestQueueName());
+
+ // Consumer is only used to actually create the destination
+ _session.createConsumer(_destination).close();
+ }
+
+ /**
+ * Checks the log file for MESSAGE_COUNT_ALERT, fails() the test if it's not found and
+ * places the entire contents in the message to help debug cruise control failures.
+ *
+ * @throws Exception
+ */
+ private void wasAlertFired() throws Exception
+ {
+ if (!waitForMessage(MESSAGE_COUNT_ALERT, ALERT_LOG_WAIT_PERIOD))
+ {
+ StringBuffer message = new StringBuffer("Could not find 'MESSAGE_COUNT_ALERT' in log file: " + _monitor.getMonitoredFile().getAbsolutePath());
+ message.append("\n");
+
+ // Add the current contents of the log file to test output
+ message.append(_monitor.readFile());
+
+ // Write the test config file to test output
+ message.append("Server configuration overrides in use:\n");
+ message.append(FileUtils.readFileAsString(getTestConfigFile()));
+
+ message.append("\nVirtualhost maxMessageCount:\n");
+ ServerConfiguration config = new ServerConfiguration(_configFile);
+ config.initialise();
+ message.append(config.getVirtualHostConfig(VIRTUALHOST).getMaximumMessageCount());
+
+ fail(message.toString());
+ }
+ }
+
+ public void testAlertingReallyWorks() throws Exception
+ {
+ // Send 5 messages, make sure that the alert was fired properly.
+ sendMessage(_session, _destination, _numMessages + 1);
+ _session.commit();
+ wasAlertFired();
+ }
+
+ public void testAlertingReallyWorksWithRestart() throws Exception
+ {
+ sendMessage(_session, _destination, _numMessages + 1);
+ _session.commit();
+ _connection.close();
+ stopBroker();
+
+ // Rest the monitoring clearing the current output file.
+ _monitor.reset();
+ startBroker();
+ wasAlertFired();
+ }
+
+ /**
+ * Test that if the alert value is change from the previous value we can
+ * still get alerts.
+ *
+ * Test sends two messages to the broker then restarts the broker with new
+ * configuration.
+ *
+ * If the test is running inVM the test validates that the new configuration
+ * has been applied.
+ *
+ * Validates that we only have two messages on the queue and then sends
+ * enough messages to trigger the alert.
+ *
+ * The alert is then validate.
+ *
+ *
+ * @throws Exception
+ */
+ public void testAlertingReallyWorksWithChanges() throws Exception
+ {
+ // send some messages and nuke the logs
+ sendMessage(_session, _destination, 2);
+ _session.commit();
+ // To prevent any failover/retry/connection dropped errors
+ _connection.close();
+
+ stopBroker();
+
+ _monitor.reset();
+
+ // Change max message count to 5, start broker and make sure that that's triggered at the right time
+ setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".queues.maximumMessageCount", "5");
+
+ startBroker();
+
+ if (!isExternalBroker())
+ {
+ assertEquals("Alert Max Msg Count is not correct", 5, ApplicationRegistry.getInstance().getVirtualHostRegistry().
+ getVirtualHost(VIRTUALHOST).getQueueRegistry().getQueue(new AMQShortString(_destination.getQueueName())).
+ getMaximumMessageCount());
+ }
+
+ setupConnection();
+
+ // Validate the queue depth is as expected
+ long messageCount = ((AMQSession<?, ?>) _session).getQueueDepth((AMQDestination) _destination);
+ assertEquals("Broker has invalid message count for test", 2, messageCount);
+
+ // Ensure the alert has not occured yet
+ assertLoggingNotYetOccured(MESSAGE_COUNT_ALERT);
+
+ // Trigger the new value
+ sendMessage(_session, _destination, 3);
+ _session.commit();
+
+ // Validate that the alert occured.
+ wasAlertFired();
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/BindingLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/BindingLoggingTest.java
new file mode 100644
index 0000000000..97914f84a5
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/BindingLoggingTest.java
@@ -0,0 +1,271 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.logging;
+
+import org.apache.qpid.server.logging.subjects.AbstractTestLogSubject;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.Topic;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Binding
+ *
+ * The Binding test suite validates that the follow log messages as specified in the Functional Specification.
+ *
+ * This suite of tests validate that the Binding messages occur correctly and according to the following format:
+ *
+ * BND-1001 : Create [: Arguments : <key=value>]
+ * BND-1002 : Deleted
+ */
+public class BindingLoggingTest extends AbstractTestLogging
+{
+
+ static final String BND_PREFIX = "BND-";
+
+ Connection _connection;
+ Session _session;
+ Queue _queue;
+ Topic _topic;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ //Ignore broker startup messages
+ _monitor.reset();
+
+ _connection = getConnection();
+
+ _session = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ _queue = _session.createQueue(getName());
+ _topic = (Topic) getInitialContext().lookup(TOPIC);
+ }
+
+ private void validateLogMessage(String log, String messageID, String message, String exchange, String rkey, String queueName)
+ {
+ validateMessageID(messageID, log);
+
+ String subject = fromSubject(log);
+
+ assertEquals("Queue not correct.", queueName,
+ AbstractTestLogSubject.getSlice("qu", subject));
+ assertEquals("Routing Key not correct.", rkey,
+ AbstractTestLogSubject.getSlice("rk", subject));
+ assertEquals("Virtualhost not correct.", "/test",
+ AbstractTestLogSubject.getSlice("vh", subject));
+ assertEquals("Exchange not correct.", exchange,
+ AbstractTestLogSubject.getSlice("ex", subject));
+
+ assertEquals("Log Message not as expected", message, getMessageString(fromMessage(log)));
+ }
+
+ /**
+ * testBindingCreate
+ *
+ * Description:
+ * The binding of a Queue and an Exchange is done via a Binding. When this Binding is created a BND-1001 Create message will be logged.
+ * Input:
+ *
+ * 1. Running Broker
+ * 2. New Client requests that a Queue is bound to a new exchange.
+ * Output:
+ *
+ * <date> BND-1001 : Create : Arguments : {x-filter-jms-selector=}
+ *
+ * Validation Steps:
+ * 3. The BND ID is correct
+ * 4. This will be the first message for the given binding
+ */
+ public void testBindingCreate() throws JMSException, IOException
+ {
+ _session.createConsumer(_queue).close();
+
+ List<String> results = waitAndFindMatches(BND_PREFIX);
+
+ // We will have two binds as we bind all queues to the default exchange
+ assertEquals("Result set larger than expected.", 2, results.size());
+
+ String exchange = "direct/<<default>>";
+ String messageID = "BND-1001";
+ String message = "Create";
+ String queueName = _queue.getQueueName();
+
+ validateLogMessage(getLogMessage(results, 0), messageID, message, exchange, queueName, queueName);
+
+ exchange = "direct/amq.direct";
+ message = "Create : Arguments : {x-filter-jms-selector=}";
+ validateLogMessage(getLogMessage(results, 1), messageID, message, exchange, queueName, queueName);
+ }
+
+ /**
+ * Description:
+ * A Binding can be made with a set of arguments. When this occurs we logged the key,value pairs as part of the Binding log message. When the subscriber with a JMS Selector consumes from an exclusive queue such as a topic. The binding is made with the JMS Selector as an argument.
+ * Input:
+ *
+ * 1. Running Broker
+ * 2. Java Client consumes from a topic with a JMS selector.
+ * Output:
+ *
+ * <date> BND-1001 : Create : Arguments : {x-filter-jms-selector=<value>}
+ *
+ * Validation Steps:
+ * 3. The BND ID is correct
+ * 4. The JMS Selector argument is present in the message
+ * 5. This will be the first message for the given binding
+ */
+ public void testBindingCreateWithArguments() throws JMSException, IOException
+ {
+ final String SELECTOR = "Selector='True'";
+
+ _session.createDurableSubscriber(_topic, getName(), SELECTOR, false).close();
+
+ List<String> results = waitAndFindMatches(BND_PREFIX);
+
+ // We will have two binds as we bind all queues to the default exchange
+ assertEquals("Result set larger than expected.", 2, results.size());
+
+ //Verify the first entry is the default binding
+ String messageID = "BND-1001";
+ String message = "Create";
+
+ validateLogMessage(getLogMessage(results, 0), messageID, message,
+ "direct/<<default>>", "clientid:" + getName(), "clientid:" + getName());
+
+ //Default binding will be without the selector
+ assertTrue("JMSSelector identified in binding:"+message, !message.contains("jms-selector"));
+
+ // Perform full testing on the second non default binding
+ message = getMessageString(fromMessage(getLogMessage(results, 1)));
+
+ validateLogMessage(getLogMessage(results, 1), messageID, message,
+ "topic/amq.topic", "topic", "clientid:" + getName());
+
+ assertTrue("JMSSelector not identified in binding:"+message, message.contains("jms-selector"));
+ assertTrue("Selector not part of binding.:"+message, message.contains(SELECTOR));
+
+ }
+
+ /**
+ * Description:
+ * Bindings can be deleted so that a queue can be rebound with a different set of values.
+ * Input:
+ *
+ * 1. Running Broker
+ * 2. AMQP UnBind Request is made
+ * Output:
+ *
+ * <date> BND-1002 : Deleted
+ *
+ * Validation Steps:
+ * 3. The BND ID is correct
+ * 4. There must have been a BND-1001 Create message first.
+ * 5. This will be the last message for the given binding
+ */
+ public void testBindingDelete() throws JMSException, IOException
+ {
+ //Closing a consumer on a temporary queue will cause it to autodelete
+ // and so unbind.
+ _session.createConsumer(_session.createTemporaryQueue()).close();
+
+ if(isBroker010())
+ {
+ //auto-delete is at session close for 0-10
+ _session.close();
+ }
+
+ //wait for the deletion messages to be logged
+ waitForMessage("BND-1002");
+
+ //gather all the BND messages
+ List<String> results = waitAndFindMatches(BND_PREFIX);
+
+ // We will have two binds as we bind all queues to the default exchange
+ assertEquals("Result not as expected." + results, 4, results.size());
+
+
+ String messageID = "BND-1001";
+ String message = "Create";
+
+ String log = getLogMessage(results, 0);
+ validateMessageID(messageID, log);
+ assertEquals("Log Message not as expected", message, getMessageString(fromMessage(log)));
+
+ log = getLogMessage(results, 1);
+ validateMessageID(messageID, log);
+ assertEquals("Log Message not as expected", message, getMessageString(fromMessage(log)));
+
+
+ String DEFAULT = "direct/<<default>>";
+ String DIRECT = "direct/amq.direct";
+
+ messageID = "BND-1002";
+ message = "Deleted";
+
+ log = getLogMessage(results, 2);
+ validateMessageID(messageID, log);
+
+ String subject = fromSubject(log);
+
+ validateBindingDeleteArguments(subject, "/test");
+
+ boolean defaultFirst = DEFAULT.equals(AbstractTestLogSubject.getSlice("ex", subject));
+ boolean directFirst = DIRECT.equals(AbstractTestLogSubject.getSlice("ex", subject));
+
+ assertEquals("Log Message not as expected", message, getMessageString(fromMessage(log)));
+
+ log = getLogMessage(results, 3);
+
+ validateMessageID(messageID, log);
+
+ subject = fromSubject(log);
+
+ validateBindingDeleteArguments(subject, "/test");
+
+ if (!defaultFirst)
+ {
+ assertEquals(DEFAULT, AbstractTestLogSubject.getSlice("ex", subject));
+ assertTrue("First Exchange Log was not a direct exchange delete",directFirst);
+ }
+ else
+ {
+ assertEquals(DIRECT, AbstractTestLogSubject.getSlice("ex", subject));
+ assertTrue("First Exchange Log was not a default exchange delete",defaultFirst);
+ }
+
+ assertEquals("Log Message not as expected", message, getMessageString(fromMessage(log)));
+ }
+
+ private void validateBindingDeleteArguments(String subject, String vhostName)
+ {
+ String routingKey = AbstractTestLogSubject.getSlice("rk", subject);
+
+ assertTrue("Routing Key does not start with TempQueue:"+routingKey,
+ routingKey.startsWith("TempQueue"));
+ assertEquals("Virtualhost not correct.", vhostName,
+ AbstractTestLogSubject.getSlice("vh", subject));
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/BrokerLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/BrokerLoggingTest.java
new file mode 100644
index 0000000000..8fd2c085c3
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/BrokerLoggingTest.java
@@ -0,0 +1,1003 @@
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+package org.apache.qpid.server.logging;
+
+import junit.framework.AssertionFailedError;
+import org.apache.qpid.server.Main;
+import org.apache.qpid.transport.ConnectionException;
+import org.apache.qpid.util.LogMonitor;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.Socket;
+import java.util.List;
+
+/**
+ * Broker Test Suite
+ *
+ * The Broker test suite validates that the follow log messages as specified in the Functional Specification.
+ *
+ * BRK-1001 : Startup : Version: <Version> Build: <Build>
+ * BRK-1002 : Starting : Listening on <Transport> port <Port>
+ * BRK-1003 : Shuting down : <Transport> port <Port>
+ * BRK-1004 : Ready
+ * BRK-1005 : Stopped
+ * BRK-1006 : Using configuration : <path>
+ * BRK-1007 : Using logging configuration : <path>
+ *
+ * These messages should only occur during startup. The tests need to verify the order of messages. In the case of the BRK-1002 and BRK-1003 the respective ports should only be available between the two log messages.
+ */
+public class BrokerLoggingTest extends AbstractTestLogging
+{
+ private static final String BRK_LOG_PREFIX = "BRK-";
+
+ public void setUp() throws Exception
+ {
+ setLogMessagePrefix();
+
+ // We either do this here or have a null check in tearDown.
+ // As when this test is run against profiles other than java it will NPE
+ _monitor = new LogMonitor(_outputFile);
+ //We explicitly do not call super.setUp as starting up the broker is
+ //part of the test case.
+ }
+
+ /**
+ * Description:
+ * On startup the broker must report the active configuration file. The
+ * logging system must output this so that we can know what configuration
+ * is being used for this broker instance.
+ *
+ * Input:
+ * The value of -c specified on the command line.
+ * Output:
+ * <date> MESSAGE BRK-1006 : Using configuration : <config file>
+ * Constraints:
+ * This MUST BE the first BRK log message.
+ *
+ * Validation Steps:
+ * 1. This is first BRK log message.
+ * 2. The BRK ID is correct
+ * 3. The config file is the full path to the file specified on
+ * the commandline.
+ *
+ * @throws Exception caused by broker startup
+ */
+ public void testBrokerStartupConfiguration() throws Exception
+ {
+ String TESTID="BRK-1006";
+
+ // This logging startup code only occurs when you run a Java broker,
+ // that broker must be started via Main so not an InVM broker.
+ if (isJavaBroker() && isExternalBroker())
+ {
+ startBroker();
+
+ // Now we can create the monitor as _outputFile will now be defined
+ _monitor = new LogMonitor(_outputFile);
+
+
+ String configFilePath = _configFile.toString();
+
+ // Ensure we wait for TESTID to be logged
+ waitAndFindMatches(TESTID);
+
+ List<String> results = waitAndFindMatches(BRK_LOG_PREFIX);
+ try
+ {
+ // Validation
+
+ assertTrue("BRKer message not logged", results.size() > 0);
+
+ String log = getLogMessage(results, 0);
+
+ //1
+ validateMessageID(TESTID, log);
+
+ //2
+ results = findMatches(TESTID);
+ assertEquals("More than one configuration message found.",
+ 1, results.size());
+
+ //3
+ assertTrue("Config file details not correctly logged",
+ log.endsWith(configFilePath));
+ }
+ catch (AssertionFailedError afe)
+ {
+ dumpLogs(results, _monitor);
+
+ throw afe;
+ }
+ }
+ }
+
+ /**
+ * Description:
+ * On startup the broker must report correctly report the log4j file in use. This is important as it can help diagnose why logging messages are not being reported.
+ * Input:
+ * No custom -l value should be provided on the command line so that the default value is correctly reported.
+ * Output:
+ *
+ * <date> MESSAGE BRK-1007 : Using logging configuration : <$QPID_HOME>/etc/log4j.xml
+ *
+ * Validation Steps:
+ *
+ * 1. The BRK ID is correct
+ * 2. This occurs before the BRK-1001 startup message.
+ * 3. The log4j file is the full path to the file specified on the commandline.
+ *
+ * @throws Exception caused by broker startup
+ */
+ public void testBrokerStartupDefaultLog4j() throws Exception
+ {
+ // This logging startup code only occurs when you run a Java broker,
+ // that broker must be started via Main so not an InVM broker.
+ if (isJavaBroker() && isExternalBroker())
+ {
+ String TESTID = "BRK-1007";
+
+ //Remove test Log4j config from the commandline
+ _broker = _broker.substring(0, _broker.indexOf("-l"));
+
+ startBroker();
+
+ // Now we can create the monitor as _outputFile will now be defined
+ _monitor = new LogMonitor(_outputFile);
+
+ // Ensure broker has fully started up.
+ getConnection();
+
+ // Ensure we wait for TESTID to be logged
+ waitAndFindMatches(TESTID);
+
+ List<String> results = waitAndFindMatches(BRK_LOG_PREFIX);
+ try
+ {
+ // Validation
+
+ assertTrue("BRKer message not logged", results.size() > 0);
+
+ boolean validation = false;
+ for (String rawLog : results)
+ {
+ // We don't care about messages after we have our log config
+ if (validation)
+ {
+ break;
+ }
+
+ String log = getLog(rawLog);
+
+ // Ensure we do not have a BRK-1001 message before
+ if (!getMessageID(log).equals(TESTID))
+ {
+ assertFalse(getMessageID(log).equals("BRK-1001"));
+ continue;
+ }
+
+ //1
+ validateMessageID(TESTID, log);
+
+ //2
+ //There will be 1 copy of this startup message (via SystemOut)
+ assertEquals("Unexpected log4j configuration message count.",
+ 1, findMatches(TESTID).size());
+
+ //3
+ String defaultLog4j = _configFile.getParent() + "/" + Main.DEFAULT_LOG_CONFIG_FILENAME;
+ assertTrue("Log4j file(" + defaultLog4j + ") details not correctly logged:" + getMessageString(log),
+ getMessageString(log).endsWith(defaultLog4j));
+
+ validation = true;
+ }
+
+ assertTrue("Validation not performed: " + TESTID + " not logged", validation);
+ }
+ catch (AssertionFailedError afe)
+ {
+ dumpLogs(results, _monitor);
+
+ throw afe;
+ }
+ }
+ }
+
+ /**
+ * Description:
+ * On startup the broker must report correctly report the log4j file in use. This is important as it can help diagnose why logging messages are not being reported. The broker must also be capable of correctly recognising the command line property to specify the custom logging configuration.
+ * Input:
+ * The value of -l specified on the command line.
+ * Output:
+ *
+ * <date> MESSAGE BRK-1007 : Using logging configuration : <log4j file>
+ *
+ * Validation Steps:
+ *
+ * 1. The BRK ID is correct
+ * 2. This should occur before the BRK-1001 : Startup message
+ * 3. The log4j file is the full path to the file specified on the commandline.
+ *
+ * @throws Exception caused by broker startup
+ */
+ public void testBrokerStartupCustomLog4j() throws Exception
+ {
+ // This logging startup code only occurs when you run a Java broker,
+ // that broker must be started via Main so not an InVM broker.
+ if (isJavaBroker() && isExternalBroker())
+ {
+ // Get custom -l value used during testing for the broker startup
+ String customLog4j = _broker.substring(_broker.indexOf("-l") + 2);
+
+ String TESTID = "BRK-1007";
+
+ startBroker();
+
+ // Now we can create the monitor as _outputFile will now be defined
+ _monitor = new LogMonitor(_outputFile);
+
+
+ // Ensure broker has fully started up.
+ getConnection();
+
+ // Ensure we wait for TESTID to be logged
+ waitAndFindMatches(TESTID);
+
+ List<String> results = waitAndFindMatches(BRK_LOG_PREFIX);
+ try
+ {
+ // Validation
+
+ assertTrue("BRKer message not logged", results.size() > 0);
+
+ boolean validation = false;
+ for (String rawLog : results)
+ {
+ // We don't care about messages after we have our log config
+ if (validation)
+ {
+ break;
+ }
+ String log = getLog(rawLog);
+
+ // Ensure we do not have a BRK-1001 message before
+ if (!getMessageID(log).equals(TESTID))
+ {
+ assertFalse(getMessageID(log).equals("BRK-1001"));
+ continue;
+ }
+
+ //1
+ validateMessageID(TESTID, log);
+
+ //2
+ //There will be 1 copy of this startup message (via SystemOut)
+ assertEquals("Unexpected log4j configuration message count.",
+ 1, findMatches(TESTID).size());
+
+ //3
+ assertTrue("Log4j file details not correctly logged:" + getMessageString(log),
+ getMessageString(log).endsWith(customLog4j));
+
+ validation = true;
+ }
+
+ assertTrue("Validation not performed: " + TESTID + " not logged", validation);
+ }
+ catch (AssertionFailedError afe)
+ {
+ dumpLogs(results, _monitor);
+
+ throw afe;
+ }
+ }
+ }
+
+ /**
+ * Description: On startup the broker reports the broker version number and svn build revision. This information is retrieved from the resource 'qpidversion.properties' which is located via the classloader.
+ * Input: The 'qpidversion.properties' file located on the classpath.
+ * Output:
+ *
+ * <date> MESSAGE BRK-1001 : Startup : qpid Version: 0.6 Build: 767150
+ *
+ * Validation Steps:
+ *
+ * 1. The BRK ID is correct
+ * 2. This occurs before any BRK-1002 listening messages are reported.
+ *
+ * @throws Exception caused by broker startup
+ */
+ public void testBrokerStartupStartup() throws Exception
+ {
+ // This logging startup code only occurs when you run a Java broker,
+ // that broker must be started via Main so not an InVM broker.
+ if (isJavaBroker() && isExternalBroker())
+ {
+ String TESTID = "BRK-1001";
+
+ startBroker();
+
+ // Now we can create the monitor as _outputFile will now be defined
+ _monitor = new LogMonitor(_outputFile);
+
+ // Ensure we wait for TESTID to be logged
+ waitAndFindMatches(TESTID);
+
+ // Retrieve all BRK- log messages so we can check for an erroneous
+ // BRK-1002 message.
+ List<String> results = findMatches(BRK_LOG_PREFIX);
+
+ try
+ {
+ // Validation
+
+ assertTrue("BRKer message not logged", results.size() > 0);
+
+ boolean validation = false;
+ for (String rawLog : results)
+ {
+ if (validation)
+ {
+ //Stop checking once we have got to our startup test
+ break;
+ }
+ String log = getLog(rawLog);
+
+ // Ensure we do not have a BRK-1002 message
+ if (!getMessageID(log).equals(TESTID))
+ {
+ assertFalse(getMessageID(log).equals("BRK-1002"));
+ continue;
+ }
+
+ //1
+ validateMessageID(TESTID, log);
+
+ //2
+ //There will be 2 copies of the startup message (one via SystemOut, and one via Log4J)
+ assertEquals("Unexpected startup message count",
+ 2, findMatches(TESTID).size());
+
+ validation = true;
+ }
+
+ assertTrue("Validation not performed: " + TESTID + " not logged", validation);
+ }
+ catch (AssertionFailedError afe)
+ {
+ dumpLogs(results, _monitor);
+
+ throw afe;
+ }
+ }
+ }
+
+ /**
+ * Description:
+ * On startup the broker may listen on a number of ports and protocols. Each of these must be reported as they are made available.
+ * Input:
+ * The default configuration with no SSL
+ * Output:
+ *
+ * <date> MESSAGE BRK-1002 : Starting : Listening on TCP port 5672
+ *
+ * Constraints:
+ * Additional broker configuration will occur between the Startup(BRK-1001) and Starting(BRK-1002) messages depending on what VirtualHosts are configured.
+ * Validation Steps:
+ *
+ * 1. The BRK ID is correct
+ * 2. This occurs after the BRK-1001 startup message
+ * 3. Using the default configuration a single BRK-1002 will be printed showing values TCP / 5672
+ *
+ * @throws Exception caused by broker startup
+ */
+ public void testBrokerStartupListeningTCPDefault() throws Exception
+ {
+ // This logging startup code only occurs when you run a Java broker,
+ // that broker must be started via Main so not an InVM broker.
+ if (isJavaBroker() && isExternalBroker())
+ {
+ String TESTID = "BRK-1002";
+
+ startBroker();
+
+ // Now we can create the monitor as _outputFile will now be defined
+ _monitor = new LogMonitor(_outputFile);
+
+ // Ensure broker has fully started up.
+ getConnection();
+
+ // Ensure we wait for TESTID to be logged
+ waitAndFindMatches(TESTID);
+
+ // Retrieve all BRK- log messages so we can check for an erroneous
+ // BRK-1002 message.
+ List<String> results = findMatches(BRK_LOG_PREFIX);
+ try
+ {
+ // Validation
+
+ assertTrue("BRKer message not logged", results.size() > 0);
+
+ boolean validation = false;
+ boolean foundBRK1001 = false;
+ for (String rawLog : results)
+ {
+ String log = getLog(rawLog);
+
+ // Ensure we do not have a BRK-1002 message
+ if (!getMessageID(log).equals(TESTID))
+ {
+ if (getMessageID(log).equals("BRK-1001"))
+ {
+ foundBRK1001 = true;
+ }
+ continue;
+ }
+
+ assertTrue("BRK-1001 not logged before this message", foundBRK1001);
+
+ //1
+ validateMessageID(TESTID, log);
+
+ //2
+ //There will be 2 copies of the startup message (one via SystemOut, and one via Log4J)
+ assertEquals("Unexpected listen message count",
+ 2, findMatches(TESTID).size());
+
+ //3
+ String message = getMessageString(log);
+ assertTrue("Expected Listen log not correct" + message,
+ message.endsWith("Listening on TCP port " + getPort()));
+
+ validation = true;
+ }
+
+ assertTrue("Validation not performed: " + TESTID + " not logged", validation);
+ }
+ catch (AssertionFailedError afe)
+ {
+ dumpLogs(results, _monitor);
+
+ throw afe;
+ }
+ }
+ }
+
+ /**
+ * Description:
+ * On startup the broker may listen on a number of ports and protocols. Each of these must be reported as they are made available.
+ * Input:
+ * The default configuration with SSL enabled
+ * Output:
+ *
+ * <date> MESSAGE BRK-1002 : Starting : Listening on TCP port 5672
+ * <date> MESSAGE BRK-1002 : Starting : Listening on TCP/SSL port 8672
+ *
+ * Constraints:
+ * Additional broker configuration will occur between the Startup(BRK-1001) and Starting(BRK-1002) messages depending on what VirtualHosts are configured.
+ * Validation Steps:
+ *
+ * 1. The BRK ID is correct
+ * 2. This occurs after the BRK-1001 startup message
+ * 3. With SSL enabled in the configuration two BRK-1002 will be printed (order is not specified)
+ * 1. One showing values TCP / 5672
+ * 2. One showing values TCP/SSL / 5672
+ *
+ * @throws Exception caused by broker startup
+ */
+ public void testBrokerStartupListeningTCPSSL() throws Exception
+ {
+ // This logging startup code only occurs when you run a Java broker,
+ // that broker must be started via Main so not an InVM broker.
+ if (isJavaBroker() && isExternalBroker())
+ {
+ String TESTID = "BRK-1002";
+
+ // Enable SSL on the connection
+ setConfigurationProperty("connector.ssl.enabled", "true");
+ setConfigurationProperty("connector.ssl.sslOnly", "false");
+ setConfigurationProperty("connector.ssl.keyStorePath", getConfigurationStringProperty("management.ssl.keyStorePath"));
+ setConfigurationProperty("connector.ssl.keyStorePassword", getConfigurationStringProperty("management.ssl.keyStorePassword"));
+
+ Integer sslPort = Integer.parseInt(getConfigurationStringProperty("connector.sslport"));
+
+ startBroker();
+
+ // Now we can create the monitor as _outputFile will now be defined
+ _monitor = new LogMonitor(_outputFile);
+
+ // Ensure broker has fully started up.
+ getConnection();
+
+ // Ensure we wait for TESTID to be logged
+ waitAndFindMatches(TESTID);
+
+ // Retrieve all BRK- log messages so we can check for an erroneous
+ // BRK-1002 message.
+ List<String> results = findMatches(BRK_LOG_PREFIX);
+ try
+ {
+ // Validation
+
+ assertTrue("BRKer message not logged", results.size() > 0);
+
+ boolean validation = false;
+ boolean foundBRK1001 = false;
+ for (String rawLog : results)
+ {
+ String log = getLog(rawLog);
+
+ // Ensure we do not have a BRK-1002 message
+ if (!getMessageID(log).equals(TESTID))
+ {
+ if (getMessageID(log).equals("BRK-1001"))
+ {
+ foundBRK1001 = true;
+ }
+ continue;
+ }
+
+ assertTrue("BRK-1001 not logged before this message", foundBRK1001);
+
+ //1
+ validateMessageID(TESTID, log);
+
+ //2
+ //There will be 4 copies of the startup message (two via SystemOut, and two via Log4J)
+ List<String> listenMessages = findMatches(TESTID);
+ assertEquals("Four listen messages should be found.",
+ 4, listenMessages .size());
+
+ //3
+ //Check the first
+ String message = getMessageString(getLog(listenMessages .get(0)));
+ assertTrue("Expected Listen log not correct" + message,
+ message.endsWith("Listening on TCP port " + getPort()));
+
+ // Check the third, ssl listen.
+ message = getMessageString(getLog(listenMessages .get(2)));
+ assertTrue("Expected Listen log not correct" + message,
+ message.endsWith("Listening on TCP/SSL port " + sslPort));
+
+ //4 Test ports open
+ testSocketOpen(getPort());
+ testSocketOpen(sslPort);
+
+ validation = true;
+ }
+
+ assertTrue("Validation not performed: " + TESTID + " not logged", validation);
+ }
+ catch (AssertionFailedError afe)
+ {
+ dumpLogs(results, _monitor);
+
+ throw afe;
+ }
+ }
+ }
+
+ /**
+ * Description:
+ * The final message the broker will print when it has performed all initialisation and listener startups will be to log the BRK-1004 Ready message
+ * Input:
+ * No input, all successful broker startups will show BRK-1004 messages.
+ * Output:
+ *
+ * 2009-07-09 15:50:20 +0100 MESSAGE BRK-1004 : Qpid Broker Ready
+ *
+ * Validation Steps:
+ *
+ * 1. The BRK ID is correct
+ * 2. This occurs after the BRK-1001 startup message
+ * 3. This must be the last message the broker prints after startup. Currently, if there is no further interaction with the broker then there should be no more logging.
+ *
+ * @throws Exception caused by broker startup
+ */
+ public void testBrokerStartupReady() throws Exception
+ {
+ // This logging startup code only occurs when you run a Java broker,
+ // that broker must be started via Main so not an InVM broker.
+ if (isJavaBroker() && isExternalBroker())
+ {
+ String TESTID = "BRK-1004";
+
+ startBroker();
+
+ //Ensure the broker has fully started up.
+ getConnection();
+ // Ensure we wait for TESTID to be logged
+ waitAndFindMatches(TESTID);
+
+ // Retrieve all BRK- log messages so we can check for an erroneous
+ // BRK-1001 message.
+ List<String> results = findMatches(BRK_LOG_PREFIX);
+ try
+ {
+ // Validation
+
+ assertTrue("BRKer message not logged", results.size() > 0);
+
+ boolean validationComplete = false;
+ boolean foundBRK1001 = false;
+
+ for (int i=0; i < results.size(); i++)
+ {
+ String rawLog = results.get(i);
+ String log = getLog(rawLog);
+
+ // Ensure we do not have a BRK-1001 message
+ if (!getMessageID(log).equals(TESTID))
+ {
+ if (getMessageID(log).equals("BRK-1001"))
+ {
+ foundBRK1001 = true;
+ }
+ continue;
+ }
+
+ assertTrue("BRK-1001 not logged before this message", foundBRK1001);
+
+ //1
+ validateMessageID(TESTID, log);
+
+ //2
+ assertEquals("Ready message not present", "Qpid Broker Ready", getMessageString(log));
+
+ //There will be 2 copies of the startup message (one via SystemOut, and one via Log4J)
+ assertEquals("Unexpected ready message count",
+ 2, findMatches(TESTID).size());
+ assertEquals("The ready messages should have been the last 2 messages", results.size() - 2, i);
+
+ validationComplete = true;
+ break;
+ }
+
+ assertTrue("Validation not performed: " + TESTID + " not logged", validationComplete);
+ }
+ catch (AssertionFailedError afe)
+ {
+ dumpLogs(results, _monitor);
+
+ throw afe;
+ }
+ }
+ }
+
+ /**
+ * Description:
+ * On startup the broker may listen on a number of ports and protocols. Each of these must then report a shutting down message as they stop listening.
+ * Input:
+ * The default configuration with no SSL
+ * Output:
+ *
+ * <date> MESSAGE BRK-1003 : Shutting down : TCP port 5672
+ *
+ * Validation Steps:
+ *
+ * 1. The BRK ID is correct
+ * 2. Only TCP is reported with the default configuration with no SSL.
+ * 3. The default port is correct
+ * 4. The port is not accessible after this message
+ *
+ * @throws Exception caused by broker startup
+ */
+ public void testBrokerShutdownListeningTCPDefault() throws Exception
+ {
+ // This logging startup code only occurs when you run a Java broker,
+ // that broker must be started via Main so not an InVM broker.
+ if (isJavaBroker() && isExternalBroker())
+ {
+ String TESTID = "BRK-1003";
+
+ startBroker();
+
+ // Now we can create the monitor as _outputFile will now be defined
+ _monitor = new LogMonitor(_outputFile);
+
+ stopBroker();
+
+ //Give broker time to shutdown and flush log
+ checkSocketClosed(getPort());
+
+ List<String> results = waitAndFindMatches(BRK_LOG_PREFIX);
+ try
+ {
+ // Validation
+
+ assertTrue("BRKer message not logged", results.size() > 0);
+
+ boolean validation = false;
+ boolean foundBRK1001 = false;
+ for (String rawLog : results)
+ {
+ String log = getLog(rawLog);
+
+ // Ensure we do not have a BRK-1002 message
+ if (!getMessageID(log).equals(TESTID))
+ {
+ if (getMessageID(log).equals("BRK-1001"))
+ {
+ foundBRK1001 = true;
+ }
+ continue;
+ }
+
+ assertTrue("BRK-1001 not logged before this message", foundBRK1001);
+
+ //1
+ validateMessageID(TESTID, log);
+
+ //2
+ assertEquals("More than one listen message found.",
+ 1, findMatches(TESTID).size());
+
+ //3
+ String message = getMessageString(log);
+ assertTrue("Expected shutdown log not correct" + message,
+ message.endsWith("TCP port " + getPort()));
+
+ //4
+ checkSocketClosed(getPort());
+
+ validation = true;
+ }
+
+ assertTrue("Validation not performed: " + TESTID + " not logged", validation);
+ }
+ catch (AssertionFailedError afe)
+ {
+ dumpLogs(results, _monitor);
+
+ throw afe;
+ }
+ }
+ }
+
+ /**
+ * Description:
+ * On startup the broker may listen on a number of ports and protocols. Each of these must be reported as they are made available.
+ * Input:
+ * The default configuration with SSL enabled
+ * Output:
+ *
+ * <date> MESSAGE BRK-1002 : Starting : Listening on TCP port 5672
+ * <date> MESSAGE BRK-1002 : Starting : Listening on TCP/SSL port 8672
+ *
+ * Constraints:
+ * Additional broker configuration will occur between the Startup(BRK-1001) and Starting(BRK-1002) messages depending on what VirtualHosts are configured.
+ * Validation Steps:
+ *
+ * 1. The BRK ID is correct
+ * 2. This occurs after the BRK-1001 startup message
+ * 3. With SSL enabled in the configuration two BRK-1002 will be printed (order is not specified)
+ * 1. One showing values TCP / 5672
+ * 2. One showing values TCP/SSL / 5672
+ *
+ * @throws Exception caused by broker startup
+ */
+ public void testBrokerShutdownListeningTCPSSL() throws Exception
+ {
+ // This logging startup code only occurs when you run a Java broker,
+ // that broker must be started via Main so not an InVM broker.
+ if (isJavaBroker() && isExternalBroker())
+ {
+ String TESTID = "BRK-1003";
+
+ // Enable SSL on the connection
+ setConfigurationProperty("connector.ssl.enabled", "true");
+ setConfigurationProperty("connector.ssl.keyStorePath", getConfigurationStringProperty("management.ssl.keyStorePath"));
+ setConfigurationProperty("connector.ssl.keyStorePassword", getConfigurationStringProperty("management.ssl.keyStorePassword"));
+
+ Integer sslPort = Integer.parseInt(getConfigurationStringProperty("connector.sslport"));
+
+ startBroker();
+
+ // Now we can create the monitor as _outputFile will now be defined
+ _monitor = new LogMonitor(_outputFile);
+
+
+// //Clear any startup messages as we don't need them for validation
+// _monitor.reset();
+ //Stop the broker to get the log messages for testing
+ stopBroker();
+
+ //Give broker time to shutdown and flush log
+ checkSocketClosed(getPort());
+
+ List<String> results = waitAndFindMatches(TESTID);
+ try
+ {
+ // Validation
+
+ assertTrue(TESTID + " messages not logged", results.size() > 0);
+
+ String log = getLog(results.get(0));
+
+ //1
+ validateMessageID(TESTID, log);
+
+ //2
+ List<String> listenMessages = findMatches(TESTID);
+ assertEquals("Two shutdown messages should be found.",
+ 2, listenMessages.size());
+
+ //3
+ String message = getMessageString(getLog(listenMessages.get(0)));
+ assertTrue("Expected shutdown log not correct" + message,
+ message.endsWith("TCP port " + getPort()));
+
+ // Check second, ssl, listen.
+ message = getMessageString(getLog(listenMessages.get(1)));
+ assertTrue("Expected shutdown log not correct" + message,
+ message.endsWith("TCP/SSL port " + sslPort));
+
+ //4
+ //Test Port closed
+ checkSocketClosed(getPort());
+ //Test SSL Port closed
+ checkSocketClosed(sslPort);
+ }
+ catch (AssertionFailedError afe)
+ {
+ dumpLogs(results, _monitor);
+
+ throw afe;
+ }
+ }
+ }
+
+ /**
+ * Description:
+ * Input:
+ * No input, all clean broker shutdowns will show BRK-1005 messages.
+ * Output:
+ *
+ * <date> MESSAGE BRK-1005 : Stopped
+ *
+ * Constraints:
+ * This is the LAST message the broker will log.
+ * Validation Steps:
+ *
+ * 1. The BRK ID is correct
+ * 2. This is the last message the broker will log.
+ *
+ * @throws Exception caused by broker startup
+ */
+ public void testBrokerShutdownStopped() throws Exception
+ {
+ // This logging startup code only occurs when you run a Java broker,
+ // that broker must be started via Main so not an InVM broker.
+ if (isJavaBroker() && isExternalBroker())
+ {
+ String TESTID = "BRK-1005";
+
+ startBroker();
+
+ // Now we can create the monitor as _outputFile will now be defined
+ _monitor = new LogMonitor(_outputFile);
+
+ getConnection().close();
+
+ stopBroker();
+
+ // Ensure the broker has shutdown before retreving results
+ checkSocketClosed(getPort());
+
+ waitAndFindMatches(TESTID);
+
+ List<String> results = waitAndFindMatches(BRK_LOG_PREFIX);
+ try
+ {
+ // Validation
+
+ assertTrue("BRKer message not logged", results.size() > 0);
+
+ boolean validation = false;
+ for (String rawLog : results)
+ {
+ assertFalse("More broker log statements present after ready message", validation);
+ String log = getLog(rawLog);
+
+ // Ignore all logs until we get to the test id.
+ if (!getMessageID(log).equals(TESTID))
+ {
+ continue;
+ }
+
+ //1
+ validateMessageID(TESTID, log);
+
+ //2
+ assertEquals("More than one ready message found.",
+ 1, findMatches(TESTID).size());
+
+ //3
+ assertEquals("Stopped message not present", "Stopped", getMessageString(log));
+
+ validation = true;
+ }
+
+ assertTrue("Validation not performed: " + TESTID + " not logged", validation);
+ }
+ catch (AssertionFailedError afe)
+ {
+ dumpLogs(results, _monitor);
+
+ throw afe;
+ }
+ }
+ }
+
+ /**
+ * Test that a socket on the given port is closed.
+ *
+ * Does this by attempting to connect to the port and expecting a
+ * ConnectionRefused IOException or a ConnectionException
+ *
+ * @param port the port number
+ */
+ private void checkSocketClosed(int port)
+ {
+ try
+ {
+ Socket socket = new Socket((String) null, port);
+ fail("Socket not closed on port:" + port);
+ }
+ catch (ConnectionException e)
+ {
+ //normal path
+ }
+ catch (IOException e)
+ {
+ if (!e.getMessage().equals("Connection refused"))
+ {
+ fail("Socket not closed on port:" + port + ":" + e.getMessage());
+ // Keep stack trace for diagnosis.
+ e.printStackTrace(System.err);
+ }
+ }
+ }
+
+ /**
+ * Test that a socket on the given port is open.
+ *
+ * Does this by attempting to connect to the port and expecting a
+ * The connection to succeed.
+ * It then closes the socket and expects that to work cleanly.
+ *
+ * @param port the port number
+ */
+ private void testSocketOpen(int port)
+ {
+ try
+ {
+ Socket socket = new Socket((String) null, port);
+ socket.close();
+ }
+ catch (IOException e)
+ {
+ fail("Unable to open and close socket to port:" + port
+ + ". Due to:" + e.getMessage());
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ChannelLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ChannelLoggingTest.java
new file mode 100644
index 0000000000..02d0d6f334
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ChannelLoggingTest.java
@@ -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.
+ *
+ */
+package org.apache.qpid.server.logging;
+
+import org.apache.qpid.client.AMQConnection;
+
+import javax.jms.Connection;
+import javax.jms.MessageConsumer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import java.util.List;
+
+public class ChannelLoggingTest extends AbstractTestLogging
+{
+ private static final String CHANNEL_PREFIX = "CHN-";
+
+ // No explicit startup configuration is required for this test
+ // so no setUp() method
+
+ /**
+ * Description:
+ * When a new Channel (JMS Session) is created this will be logged as a CHN-1001 Create message. The messages will contain the prefetch details about this new Channel.
+ * Input:
+ *
+ * 1. Running Broker
+ * 2. New JMS Session/Channel creation
+ *
+ * Output:
+ * <date> CHN-1001 : Create
+ * <date> CHN-1004 : Prefetch Size (bytes) {0,number} : Count {1,number}
+ *
+ * Validation Steps:
+ * 1. The CHN ID is correct
+ * 2. The prefetch value matches that defined by the requesting client.
+ *
+ * @throws Exception - if an error occurs
+ */
+ public void testChannelCreate() throws Exception
+ {
+ assertLoggingNotYetOccured(CHANNEL_PREFIX);
+
+ Connection connection = getConnection();
+
+ int PREFETCH = 12;
+
+ // Test that calling session.close gives us the expected output
+ ((AMQConnection)connection).createSession(false, Session.AUTO_ACKNOWLEDGE,PREFETCH);
+
+ // Wait to ensure that the CHN-1001 message is logged
+ waitForMessage("CHN-1001");
+
+ List<String> results = findMatches("CHN-1001");
+
+ // Validation
+ assertEquals("CHN-1001 messages not logged", 1, results.size());
+
+ String log = getLogMessage(results, 0);
+ // MESSAGE [con:0(guest@anonymous(3273383)/test)/ch:1] CHN-1001 : Create
+ validateMessageID("CHN-1001", log);
+ assertEquals("Incorrect Channel in actor:"+fromActor(log), isBroker010()? 0 : 1, getChannelID(fromActor(log)));
+
+ if (isBroker08())
+ {
+ // Wait to ensure that the CHN-1004 message is logged
+ waitForMessage("CHN-1004");
+
+ results = findMatches("CHN-1004");
+
+ // Validation
+ assertEquals("CHN-1004 messages not logged", 1, results.size());
+ log = getLogMessage(results, 0);
+ // MESSAGE [con:0(guest@anonymous(3273383)/test)/ch:1] CHN-1004 : Prefetch Size (bytes) {0,number} : Count {1,number}
+ validateMessageID("CHN-1004", log);
+ assertEquals("Incorrect Channel in actor:"+fromActor(log), isBroker010()? 0 : 1, getChannelID(fromActor(log)));
+ assertTrue("Prefetch Count not correct",getMessageString(fromMessage(log)).endsWith("Count "+PREFETCH));
+ }
+
+ connection.close();
+ }
+
+ /**
+ * Description:
+ * The Java Broker implements consumer flow control for all ack modes except
+ * No-Ack. When a client connects the session's flow is initially set to
+ * Stopped. Verify this message appears
+ *
+ * Input:
+ * 1. Running broker
+ * 2. Create consumer
+ * Output:
+ *
+ * <date> CHN-1002 : Flow Stopped
+ *
+ * Validation Steps:
+ * 4. The CHN ID is correct
+ *
+ * @throws Exception - if an error occurs
+ */
+
+ public void testChannelStartsFlowStopped() throws Exception
+ {
+ assertLoggingNotYetOccured(CHANNEL_PREFIX);
+
+ Connection connection = getConnection();
+
+ // Create a session to fill up
+ Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ Queue queue = (Queue) getInitialContext().lookup(QUEUE);
+ MessageConsumer consumer = session.createConsumer(queue);
+
+ connection.start();
+
+ // Wait to ensure that the CHN-1002 message is logged
+ waitForMessage("CHN-1002");
+
+ List<String> results = findMatches(CHANNEL_PREFIX);
+
+ assertTrue("No CHN messages logged", results.size() > 0);
+
+ // The last channel message should be:
+ //
+ // INFO - MESSAGE [con:0(guest@anonymous(4205299)/test)/ch:1] [con:0(guest@anonymous(4205299)/test)/ch:1] CHN-1002 : Flow Stopped
+
+ // Verify the last channel message is stopped
+ validateChannelStart(results, false);
+ }
+
+ private void validateChannelStart(List<String> results, boolean flowStarted)
+ {
+ String log = getLogMessageFromEnd(results, 0);
+
+ String flow = flowStarted ? "Started" : "Stopped";
+ validateMessageID("CHN-1002", log);
+ assertEquals("Message should be Flow " + flow, "Flow " + flow, getMessageString(fromMessage(log)));
+ }
+
+ /**
+ * Description:
+ * The Java Broker implements consumer flow control for all ack modes except
+ * No-Ack. When the client first attempts to receive a message then the Flow
+ * status of the Session is set to Started.
+ *
+ * Input:
+ * 1. Running broker
+ * 2. Create a consumer
+ * 3. Attempt to receive a message
+ * Output:
+ *
+ * <date> CHN-1002 : Flow Started
+ *
+ * Validation Steps:
+ * 4. The CHN ID is correct
+ *
+ * @throws Exception - if an error occurs
+ */
+
+ public void testChannelStartConsumerFlowStarted() throws Exception
+ {
+ assertLoggingNotYetOccured(CHANNEL_PREFIX);
+
+ Connection connection = getConnection();
+
+ // Create a session to fill up
+ Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ Queue queue = (Queue) getInitialContext().lookup(QUEUE);
+ MessageConsumer consumer = session.createConsumer(queue);
+
+ connection.start();
+
+ //Call receive to send the Flow On message
+ consumer.receiveNoWait();
+
+ //Wait for up to 2 seconds for message to appear
+ // ignore response as we will use the findMatches afterwards just
+ // incase it did take more than 2 seconds to log.
+ _monitor.waitForMessage(CHANNEL_PREFIX, 2000);
+
+ // Wait to ensure that the CHN-1002 message is logged
+ waitForMessage("CHN-1002");
+
+ List<String> results = findMatches(CHANNEL_PREFIX);
+
+ assertTrue("No CHN messages logged", results.size() > 0);
+
+ // The last two channel messages(before the close) should be:
+ //
+ // INFO [qpid.message] MESSAGE [con:1(guest@/127.0.0.1:49869/test)/ch:1] [con:1(guest@/127.0.0.1:49869/test)/ch:1] CHN-1002 : Flow Stopped
+ // INFO [qpid.message] MESSAGE [con:1(guest@/127.0.0.1:49869/test)/ch:1] [con:1(guest@/127.0.0.1:49869/test)/ch:1] CHN-1002 : Flow Started
+
+ // Verify the last channel msg is Started.
+ validateChannelStart(results, true);
+ }
+
+ /**
+ * Description:
+ * When the client gracefully closes the Connection then a CHN-1003 Close
+ * message will be issued. This must be the last message logged for this
+ * Channel.
+ * Input:
+ * 1. Running Broker
+ * 2. Connected Client
+ * 3. Client then requests that the Connection is closed
+ * Output:
+ *
+ * <date> CHN-1003 : Close
+ *
+ * Validation Steps:
+ * 4. The MST ID is correct
+ * 5. This must be the last message logged for this Channel.
+ *
+ * @throws Exception - if an error occurs
+ */
+ public void testChannelCloseViaConnectionClose() throws Exception
+ {
+ assertLoggingNotYetOccured(CHANNEL_PREFIX);
+
+ Connection connection = getConnection();
+
+ // Create a session
+ connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ // Close the connection to verify the created session closing is logged.
+ connection.close();
+
+ // Wait to ensure that the CHN-1003 message is logged
+ waitForMessage("CHN-1003");
+
+ List<String> results = findMatches(CHANNEL_PREFIX);
+
+ assertTrue("No CHN messages logged", results.size() > 0);
+
+ // The last two channel messages should be:
+ //
+ // INFO - MESSAGE [con:0(guest@anonymous(4205299)/test)/ch:1] [con:0(guest@anonymous(4205299)/test)/ch:1] CHN-1002 : Flow On
+
+ // Verify
+ validateChannelClose(results);
+ }
+
+ /**
+ * Description:
+ * When the client gracefully closes the Connection then a CHN-1003 Close
+ * message will be issued. This must be the last message logged for this
+ * Channel.
+ * Input:
+ * 1. Running Broker
+ * 2. Connected Client
+ * 3. Client then requests that the Channel is closed
+ * Output:
+ *
+ * <date> CHN-1003 : Close
+ *
+ * Validation Steps:
+ * 4. The MST ID is correct
+ * 5. This must be the last message logged for this Channel.
+ *
+ * @throws Exception - if an error occurs
+ */
+ public void testChannelCloseViaChannelClose() throws Exception
+ {
+ assertLoggingNotYetOccured(CHANNEL_PREFIX);
+
+ Connection connection = getConnection();
+
+ // Create a session and then close it
+ Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ waitForMessage("CHN-1001");
+
+ // Wait to ensure that the CHN-1003 message is logged
+ session.close();
+ waitForMessage("CHN-1003");
+
+ List<String> results = findMatches(CHANNEL_PREFIX);
+
+ assertTrue("No CHN messages logged", results.size() > 0);
+
+ // Verify
+ validateChannelClose(results);
+ }
+
+ private void validateChannelClose(List<String> results)
+ {
+ String open = getLogMessage(results, 0);
+ String close = getLogMessageFromEnd(results, 0);
+
+ validateMessageID("CHN-1001", open);
+ validateMessageID("CHN-1003", close);
+ assertEquals("Message should be Close", "Close", getMessageString(fromMessage(close)));
+ assertEquals("Incorrect Channel ID closed", isBroker010()? 0 : 1, getChannelID(fromSubject(close)));
+ assertEquals("Channel IDs should be the same", getChannelID(fromActor(open)), getChannelID(fromSubject(close)));
+ assertEquals("Connection IDs should be the same", getConnectionID(fromActor(open)), getConnectionID(fromSubject(close)));
+ }
+} \ No newline at end of file
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ConnectionLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ConnectionLoggingTest.java
new file mode 100644
index 0000000000..d28429aa39
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ConnectionLoggingTest.java
@@ -0,0 +1,192 @@
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+package org.apache.qpid.server.logging;
+
+import javax.jms.Connection;
+import java.util.List;
+import java.util.HashMap;
+import java.util.TreeSet;
+
+public class ConnectionLoggingTest extends AbstractTestLogging
+{
+ private static final String CONNECTION_PREFIX = "CON-";
+
+ // No explicit startup configuration is required for this test
+ // so no setUp() method
+
+ /**
+ * Description:
+ * When a new connection is made to the broker this must be logged.
+ *
+ * Input:
+ * 1. Running Broker
+ * 2. Connecting client
+ * Output:
+ * <date> CON-1001 : Open : Client ID {0}[ : Protocol Version : {1}] <version>
+ *
+ * Validation Steps:
+ * 1. The CON ID is correct
+ * 2. This is the first CON message for that Connection
+ *
+ * @throws Exception - if an error occurs
+ */
+ public void testConnectionOpen() throws Exception
+ {
+ assertLoggingNotYetOccured(CONNECTION_PREFIX);
+
+ Connection connection = getConnection();
+ String clientid = connection.getClientID();
+
+ // Wait until opened
+ waitForMessage("CON-1001");
+
+ // Close the conneciton
+ connection.close();
+
+ // Wait to ensure that the desired message is logged
+ waitForMessage("CON-1002");
+
+ List<String> results = waitAndFindMatches("CON-1001");
+
+ // Validation
+ // We should have at least three messages when running InVM but when running External
+ // we will get 0-10 negotiation on con:0 whcih may close at some random point
+ // MESSAGE [con:0(/127.0.0.1:46926)] CON-1001 : Open
+ // MESSAGE [con:0(/127.0.0.1:46926)] CON-1001 : Open : Protocol Version : 0-10
+ // MESSAGE [con:1(/127.0.0.1:46927)] CON-1001 : Open
+ // MESSAGE [con:1(/127.0.0.1:46927)] CON-1001 : Open : Protocol Version : 0-9
+ // MESSAGE [con:0(/127.0.0.1:46926)] CON-1002 : Close
+ // MESSAGE [con:1(/127.0.0.1:46927)] CON-1001 : Open : Client ID : clientid : Protocol Version : 0-9
+
+ //So check how many connections we have in the result set and extract the last one.
+ // When running InVM we will have con:0 and externally con:1
+
+ HashMap<Integer, List<String>> connectionData = splitResultsOnConnectionID(results);
+
+ // Get the last Integer from keySet of the ConnectionData
+ int connectionID = new TreeSet<Integer>(connectionData.keySet()).last();
+
+ //Use just the data from the last connection for the test
+ results = connectionData.get(connectionID);
+
+ // If we are running inVM or with 0-10 we will get three open messagse
+ // if running externally with 0-8/0-9 we will also have open and close messages from the failed 0-10 negotiation
+ assertTrue("CON messages not logged:" + results.size(), results.size() >= 3);
+
+ String log = getLogMessage(results, 0);
+ // MESSAGE [con:1(/127.0.0.1:52540)] CON-1001 : Open
+ //1 & 2
+ validateMessageID("CON-1001",log);
+
+ // validate the last three CON- messages.
+ // This is because when running externally we may also have logged the failed
+ // 0-10 negotiation messages if using 0-8/0-9 on the broker.
+
+ // 3 - Assert the options are correct
+ // MESSAGE [con:1(/127.0.0.1:52540)] CON-1001 : Open : Client ID : clientid : Protocol Version : 0-9
+ validateConnectionOpen(results, 0, true, true, clientid);
+
+ // MESSAGE [con:1(/127.0.0.1:52540)] CON-1001 : Open : Protocol Version : 0-9
+ validateConnectionOpen(results, 1, true, false, null);
+
+ validateConnectionOpen(results, 2, false, false, null);
+ }
+
+ private void validateConnectionOpen(List<String> results, int positionFromEnd,
+ boolean protocolVersionPresent, boolean clientIdOptionPresent, String clientIdValue)
+ {
+ String log = getLogMessageFromEnd(results, positionFromEnd);
+
+ validateMessageID("CON-1001",log);
+
+ assertEquals("unexpected Client ID option state", clientIdOptionPresent, fromMessage(log).contains("Client ID :"));
+
+ if(clientIdOptionPresent && clientIdValue != null)
+ {
+ assertTrue("Client ID value is not present: " + clientIdValue, fromMessage(log).contains(clientIdValue));
+ }
+
+ assertEquals("unexpected Protocol Version option state",
+ protocolVersionPresent, fromMessage(log).contains("Protocol Version :"));
+ //fixme there is no way currently to find out the negotiated protocol version
+ // The delegate is the versioned class ((AMQConnection)connection)._delegate
+ }
+
+ /**
+ * Description:
+ * When a connected client closes the connection this will be logged as a CON-1002 message.
+ * Input:
+ *
+ * 1. Running Broker
+ * 2. Connected Client
+ * Output:
+ *
+ * <date> CON-1002 : Close
+ *
+ * Validation Steps:
+ * 3. The CON ID is correct
+ * 4. This must be the last CON message for the Connection
+ * 5. It must be preceded by a CON-1001 for this Connection
+ */
+ public void testConnectionClose() throws Exception
+ {
+ assertLoggingNotYetOccured(CONNECTION_PREFIX);
+
+ Connection connection = getConnection();
+
+ // Wait until opened
+ waitForMessage("CON-1001");
+
+ // Close the conneciton
+ connection.close();
+
+ // Wait to ensure that the desired message is logged
+ waitForMessage("CON-1002");
+
+ List<String> results = findMatches(CONNECTION_PREFIX);
+
+ // Validation
+
+ // We should have at least four messages
+ assertTrue("CON messages not logged:" + results.size(), results.size() >= 4);
+
+ // Validate Close message occurs
+ String log = getLogMessageFromEnd(results, 0);
+ validateMessageID("CON-1002",log);
+ assertTrue("Message does not end with close:" + log, log.endsWith("Close"));
+
+ // Extract connection ID to validate there is a CON-1001 messasge for it
+ int closeConnectionID = getConnectionID(fromSubject(log));
+ assertTrue("Could not find connection id in CLOSE", closeConnectionID != -1);
+
+ //Previous log message should be the open
+ log = getLogMessageFromEnd(results, 1);
+ // MESSAGE [con:1(/127.0.0.1:52540)] CON-1001 : Open : Client ID : clientid : Protocol Version : 0-9
+ validateMessageID("CON-1001",log);
+
+ // Extract connection ID to validate it matches the CON-1002 messasge
+ int openConnectionID = getConnectionID(fromActor(log));
+ assertTrue("Could not find connection id in OPEN", openConnectionID != -1);
+
+ // Check connection ids match
+ assertEquals("Connection IDs do not match", closeConnectionID, openConnectionID);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/DerbyMessageStoreLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/DerbyMessageStoreLoggingTest.java
new file mode 100644
index 0000000000..16c529316a
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/DerbyMessageStoreLoggingTest.java
@@ -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.
+ *
+ */
+package org.apache.qpid.server.logging;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.qpid.server.configuration.ServerConfiguration;
+import org.apache.qpid.server.logging.subjects.AbstractTestLogSubject;
+
+import javax.jms.Connection;
+import javax.jms.Queue;
+import javax.jms.Session;
+import java.util.List;
+import java.io.File;
+
+/**
+ * The MessageStore test suite validates that the follow log messages as
+ * specified in the Functional Specification.
+ *
+ * This suite of tests validate that the MessageStore messages occur correctly
+ * and according to the following format:
+ *
+ * MST-1001 : Created : <name>
+ * MST-1003 : Closed
+ *
+ * NOTE: Only for Persistent Stores
+ * MST-1002 : Store location : <path>
+ * MST-1004 : Recovery Start [: <queue.name>]
+ * MST-1005 : Recovered <count> messages for queue <queue.name>
+ * MST-1006 : Recovery Complete [: <queue.name>]
+ */
+public class DerbyMessageStoreLoggingTest extends MemoryMessageStoreLoggingTest
+{
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ // MemoryMessageStoreLoggingTest setUp itself does not call super.setUp
+ //We call super.setUp but this will not start the broker as that is
+ //part of the test case.
+
+ // Load the default configuration file to get the list of defined vhosts
+ ServerConfiguration configuration = new ServerConfiguration(new File(_configFile.getParent() + "/config.xml"));
+ configuration.initialise();
+ List<String> vhosts = configuration.getConfig().getList("virtualhosts.virtualhost.name");
+
+ // Make them all persistent i.e. Use DerbyMessageStore and
+ // test that it logs correctly.
+ for (String vhost : vhosts)
+ {
+ makeVirtualHostPersistent(vhost);
+ }
+ }
+
+ /**
+ * Description:
+ * Persistent MessageStores will require space on disk to persist the data.
+ * This value will be logged on startup after the MessageStore has been
+ * created.
+ * Input:
+ * Default configuration
+ * Output:
+ *
+ * <date> MST-1002 : Store location : <path>
+ *
+ * Validation Steps:
+ *
+ * 1. The MST ID is correct
+ * 2. This must occur after MST-1001
+ */
+ public void testMessageStoreStoreLocation() throws Exception
+ {
+ assertLoggingNotYetOccured(MESSAGES_STORE_PREFIX);
+
+ startBroker();
+
+ List<String> results = waitAndFindMatches(MESSAGES_STORE_PREFIX);
+
+ // Validation
+
+ assertTrue("MST messages not logged", results.size() > 0);
+
+ // Load VirtualHost list from file.
+ ServerConfiguration configuration = new ServerConfiguration(_configFile);
+ configuration.initialise();
+ List<String> vhosts = configuration.getConfig().getList("virtualhosts.virtualhost.name");
+
+ //Validate each vhost logs a creation
+ results = waitAndFindMatches("MST-1002");
+
+ assertEquals("Each vhost did not close its store.", vhosts.size(), results.size());
+
+ for (int index = 0; index < results.size(); index++)
+ {
+ String result = getLogMessage(results, index);;
+
+ // getSlize will return extract the vhost from vh(/test) -> '/test'
+ // so remove the '/' to get the name
+ String vhostName = AbstractTestLogSubject.getSlice("vh", result).substring(1);
+
+ // To get the store class used in the configuration we need to know
+ // the virtualhost name, found above. AND
+ // the index that the virtualhost is within the configuration.
+ // we can retrive that from the vhosts list previously extracted.
+ String fullStoreName = configuration.getConfig().getString("virtualhosts.virtualhost(" + vhosts.indexOf(vhostName) + ")." + vhostName + ".store.class");
+
+ // Get the Simple class name from the expected class name of o.a.q.s.s.MMS
+ String storeName = fullStoreName.substring(fullStoreName.lastIndexOf(".") + 1);
+
+ assertTrue("MST-1002 does not contain a store path" + getMessageString(result),
+ getMessageString(result).length() > 0);
+
+ assertEquals("The store name does not match expected value",
+ storeName, AbstractTestLogSubject.getSlice("ms", fromSubject(result)));
+ }
+ }
+
+ /**
+ * Description:
+ * Persistent message stores may have state on disk that they must recover
+ * during startup. As the MessageStore starts up it will report that it is
+ * about to start the recovery process by logging MST-1004. This message
+ * will always be logged for persistent MessageStores. If there is no data
+ * to recover then there will be no subsequent recovery messages.
+ * Input:
+ * Default persistent configuration
+ * Output:
+ * <date> MST-1004 : Recovery Start
+ *
+ * Validation Steps:
+ *
+ * 1. The MST ID is correct
+ * 2. The MessageStore must have first logged a creation event.
+ */
+ public void testMessageStoreRecoveryStart() throws Exception
+ {
+ assertLoggingNotYetOccured(MESSAGES_STORE_PREFIX);
+
+ startBroker();
+
+ List<String> results = waitAndFindMatches(MESSAGES_STORE_PREFIX);
+
+ // Validation
+
+ assertTrue("MST messages not logged", results.size() > 0);
+
+ // Load VirtualHost list from file.
+ ServerConfiguration configuration = new ServerConfiguration(_configFile);
+ configuration.initialise();
+ List<String> vhosts = configuration.getConfig().getList("virtualhosts.virtualhost.name");
+
+ //Validate each vhost logs a creation
+ results = waitAndFindMatches("MST-1004");
+
+ assertTrue("Each vhost did not close its store.", vhosts.size() <= results.size());
+
+ for (int index = 0; index < results.size(); index++)
+ {
+ String result = getLogMessage(results, index);;
+
+ if (getMessageString(result).contains("Recovery Start :"))
+ {
+ //Don't test queue start recoveries
+ continue;
+ }
+
+ // getSlize will return extract the vhost from vh(/test) -> '/test'
+ // so remove the '/' to get the name
+ String vhostName = AbstractTestLogSubject.getSlice("vh", result).substring(1);
+
+ // To get the store class used in the configuration we need to know
+ // the virtualhost name, found above. AND
+ // the index that the virtualhost is within the configuration.
+ // we can retrive that from the vhosts list previously extracted.
+ String fullStoreName = configuration.getConfig().getString("virtualhosts.virtualhost(" + vhosts.indexOf(vhostName) + ")." + vhostName + ".store.class");
+
+ // Get the Simple class name from the expected class name of o.a.q.s.s.MMS
+ String storeName = fullStoreName.substring(fullStoreName.lastIndexOf(".") + 1);
+
+ assertEquals("MST-1004 does have expected message", "Recovery Start",
+ getMessageString(result));
+
+ assertEquals("The store name does not match expected value",
+ storeName, AbstractTestLogSubject.getSlice("ms", fromSubject(result)));
+ }
+ }
+
+ /**
+ * Description:
+ * Once all persistent queues have been recovered and the MessageStore has completed all recovery it must logged that the recovery process has completed.
+ * Input:
+ * Default persistent configuration
+ * Output:
+ *
+ * <date> MST-1006 : Recovery Complete
+ *
+ * Validation Steps:
+ *
+ * 1. The MST ID is correct
+ * 2. This is the last message from the MessageStore during startup.
+ * 3. This must be proceeded by a MST-1006 Recovery Start.
+ */
+ public void testMessageStoreRecoveryComplete() throws Exception
+ {
+ assertLoggingNotYetOccured(MESSAGES_STORE_PREFIX);
+
+ startBroker();
+
+ List<String> results = waitAndFindMatches(MESSAGES_STORE_PREFIX);
+
+ // Validation
+
+ assertTrue("MST messages not logged", results.size() > 0);
+
+ // Load VirtualHost list from file.
+ ServerConfiguration configuration = new ServerConfiguration(_configFile);
+ configuration.initialise();
+ List<String> vhosts = configuration.getConfig().getList("virtualhosts.virtualhost.name");
+
+ //Validate each vhost logs a creation
+ results = waitAndFindMatches("MST-1006");
+
+ assertTrue("Each vhost did not close its store.", vhosts.size() <= results.size());
+
+ for (int index = 0; index < results.size(); index++)
+ {
+ String result = getLogMessage(results, index);
+
+ if (getMessageString(result).contains("Recovery Complete :"))
+ {
+ //Don't test queue start recoveries
+ continue;
+ }
+
+ // getSlize will return extract the vhost from vh(/test) -> '/test'
+ // so remove the '/' to get the name
+ String vhostName = AbstractTestLogSubject.getSlice("vh", result).substring(1);
+
+ // To get the store class used in the configuration we need to know
+ // the virtualhost name, found above. AND
+ // the index that the virtualhost is within the configuration.
+ // we can retrive that from the vhosts list previously extracted.
+ String fullStoreName = configuration.getConfig().getString("virtualhosts.virtualhost(" + vhosts.indexOf(vhostName) + ")." + vhostName + ".store.class");
+
+ // Get the Simple class name from the expected class name of o.a.q.s.s.MMS
+ String storeName = fullStoreName.substring(fullStoreName.lastIndexOf(".") + 1);
+
+ assertEquals("MST-1006 does have expected message", "Recovery Complete",
+ getMessageString(result));
+
+ assertEquals("The store name does not match expected value",
+ storeName, AbstractTestLogSubject.getSlice("ms", fromSubject(result)));
+ }
+ }
+
+ /**
+ * Description:
+ * A persistent MessageStore may have data to recover from disk. The message store will use MST-1004 to report the start of recovery for a specific queue that it has previously persisted.
+ * Input:
+ * Default persistent configuration
+ * Output:
+ *
+ * <date> MST-1004 : Recovery Start : <queue.name>
+ *
+ * Validation Steps:
+ *
+ * 1. The MST ID is correct
+ * 2. This must occur after the recovery start MST-1004 has been logged.
+ */
+ public void testMessageStoreQueueRecoveryStart() throws Exception
+ {
+ assertLoggingNotYetOccured(MESSAGES_STORE_PREFIX);
+
+ startBroker();
+
+ List<String> results = waitAndFindMatches(MESSAGES_STORE_PREFIX);
+
+ // Validation
+
+ assertTrue("MST messages not logged", results.size() > 0);
+
+ // Load VirtualHost list from file.
+ ServerConfiguration configuration = new ServerConfiguration(_configFile);
+ configuration.initialise();
+ List<String> vhosts = configuration.getConfig().getList("virtualhosts.virtualhost.name");
+
+ //Validate each vhost logs a creation
+ results = waitAndFindMatches("MST-1004 : Recovery Start :");
+
+ // We are only looking for the default queue defined in local host being
+ // recovered. If other tests have made queues in test then we want to
+ // exclude them here.
+ results = filterResultsByVirtualHost(results, "/localhost");
+
+ assertEquals("Recovered test queue not found.", 1, results.size());
+
+ String result = getLogMessage(results, 0);
+
+ // getSlize will return extract the vhost from vh(/test) -> '/test'
+ // so remove the '/' to get the name
+ String vhostName = AbstractTestLogSubject.getSlice("vh", result).substring(1);
+
+ // To get the store class used in the configuration we need to know
+ // the virtualhost name, found above. AND
+ // the index that the virtualhost is within the configuration.
+ // we can retrive that from the vhosts list previously extracted.
+ String fullStoreName = configuration.getConfig().getString("virtualhosts.virtualhost(" + vhosts.indexOf(vhostName) + ")." + vhostName + ".store.class");
+
+ // Get the Simple class name from the expected class name of o.a.q.s.s.MMS
+ String storeName = fullStoreName.substring(fullStoreName.lastIndexOf(".") + 1);
+
+ assertTrue("MST-1006 does end with queue 'test-queue':" + getMessageString(result),
+ getMessageString(result).endsWith("test-queue"));
+
+ assertEquals("The store name does not match expected value",
+ storeName, AbstractTestLogSubject.getSlice("ms", fromSubject(result)));
+
+ }
+
+ /**
+ * Description:
+ * After the queue has been recovered the store will log that recovery has been completed. The MessageStore must not report further status about the recovery of this queue after this message. In addition every MST-1004 queue recovery start message must be matched with a MST-1006 recovery complete.
+ * Input:
+ * Default persistent configuration
+ * Output:
+ *
+ * <date> MST-1006 : Recovery Complete : <queue.name>
+ *
+ * Validation Steps:
+ *
+ * 1. The MST ID is correct
+ * 2. This must occur after the queue recovery start MST-1004 has been logged.
+ * 3. The queue.name is non-empty
+ * 4. The queue.name correlates with a previous recovery start
+ */
+ public void testMessageStoreQueueRecoveryComplete() throws Exception
+ {
+ assertLoggingNotYetOccured(MESSAGES_STORE_PREFIX);
+
+ startBroker();
+
+ List<String> results = waitAndFindMatches(MESSAGES_STORE_PREFIX);
+
+ // Validation
+
+ assertTrue("MST messages not logged", results.size() > 0);
+
+ // Load VirtualHost list from file.
+ ServerConfiguration configuration = new ServerConfiguration(_configFile);
+ configuration.initialise();
+ List<String> vhosts = configuration.getConfig().getList("virtualhosts.virtualhost.name");
+
+ //Validate each vhost logs a creation
+ results = waitAndFindMatches("MST-1006 : Recovery Complete :");
+
+ // We are only looking for the default queue defined in local host being
+ // recovered. If other tests have made queues in test then we want to
+ // exclude them here.
+ results = filterResultsByVirtualHost(results, "/localhost");
+
+ assertEquals("Recovered test queue not found.", 1, results.size());
+
+ String result = getLogMessage(results, 0);
+
+ // getSlize will return extract the vhost from vh(/test) -> '/test'
+ // so remove the '/' to get the name
+ String vhostName = AbstractTestLogSubject.getSlice("vh", result).substring(1);
+
+ // To get the store class used in the configuration we need to know
+ // the virtualhost name, found above. AND
+ // the index that the virtualhost is within the configuration.
+ // we can retrive that from the vhosts list previously extracted.
+ String fullStoreName = configuration.getConfig().getString("virtualhosts.virtualhost(" + vhosts.indexOf(vhostName) + ")." + vhostName + ".store.class");
+
+ // Get the Simple class name from the expected class name of o.a.q.s.s.MMS
+ String storeName = fullStoreName.substring(fullStoreName.lastIndexOf(".") + 1);
+
+ assertTrue("MST-1006 does end with queue 'test-queue':" + getMessageString(result),
+ getMessageString(result).endsWith("test-queue"));
+
+ assertEquals("The store name does not match expected value",
+ storeName, AbstractTestLogSubject.getSlice("ms", fromSubject(result)));
+
+ results = findMatches("MST-1004 : Recovery Start : test-queue");
+
+ assertEquals("MST-1004 for test-queue not found", 1, results.size());
+ }
+
+ /**
+ * Description:
+ * A persistent queue must be persisted so that on recovery it can be restored independently of any messages that may be stored on it. This test verifies that the MessageStore will log that it has recovered 0 messages for persistent queues that do not have any messages.
+ * Input:
+ *
+ * 1. Default persistent configuration
+ * 2. Persistent queue with no messages enqueued
+ * Output:
+ *
+ * <date> MST-1005 : Recovered 0 messages for queue <queue.name>
+ *
+ * Validation Steps:
+ * 3. The MST ID is correct
+ * 4. This must occur after the queue recovery start MST-1004 has been logged.
+ * 5. The count is 0
+ * 6. 'messages' is correctly printed
+ * 7. The queue.name is non-empty
+ */
+ public void testMessageStoreQueueRecoveryCountEmpty() throws Exception
+ {
+ assertLoggingNotYetOccured(MESSAGES_STORE_PREFIX);
+
+ String queueName = getTestQueueName();
+
+ startBroker();
+ Connection connetion = getConnection();
+ Session session = connetion.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ Queue queue = session.createQueue("direct://amq.direct/" + queueName + "/" + queueName + "?durable='true'");
+
+ session.createConsumer(queue).close();
+
+ // Stop the broker so that we can test recovery
+ stopBroker();
+
+ int COUNT = 0;
+ testDurableRecoveryCount(COUNT, queueName);
+ }
+
+ /**
+ * Description:
+ * On recovery all the persistent messages that are stored on disk must be returned to the queue. MST-1005 will report the number of messages that have been recovered from disk.
+ * Input:
+ *
+ * 1. Default persistent configuration
+ * 2. Persistent queue with multiple messages enqueued
+ * Output:
+ *
+ * <date> MST-1005 : Recovered <count> messages for queue <queue.name>
+ *
+ * Validation Steps:
+ * 3. The MST ID is correct
+ * 4. This must occur after the queue recovery start MST-1004 has been logged.
+ * 5. The count is > 1
+ * 6. 'messages' is correctly printed
+ * 7. The queue.name is non-empty
+ */
+ public void testMessageStoreQueueRecoveryCountPlural() throws Exception
+ {
+ assertLoggingNotYetOccured(MESSAGES_STORE_PREFIX);
+
+ String queueName = getTestQueueName();
+
+ int COUNT = 10;
+
+ testDurableRecoveryCount(COUNT, queueName);
+ }
+
+ /**
+ * Send a set number of messages to a new durable queue, as specified. Then
+ * restart the broker and validate that they are restored.
+ *
+ * @param COUNT - the count to send
+ * @param queueName - the new queue name
+ * @throws Exception - if a problem occured.
+ */
+ private void testDurableRecoveryCount(int COUNT, String queueName) throws Exception
+ {
+ startBroker();
+ Connection connetion = getConnection();
+ Session session = connetion.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ Queue queue = session.createQueue("direct://amq.direct/" + queueName + "/" + queueName + "?durable='true'");
+
+ session.createConsumer(queue).close();
+
+ sendMessage(session, queue, COUNT);
+ try
+ {
+ connetion.close();
+
+ stopBroker();
+
+ // Clear our monitor
+ _monitor.reset();
+
+ startBroker();
+
+ List<String> results = waitAndFindMatches(MESSAGES_STORE_PREFIX);
+
+ // Validation
+
+ assertTrue("MST messages not logged", results.size() > 0);
+
+ // Load VirtualHost list from file.
+ ServerConfiguration configuration = new ServerConfiguration(_configFile);
+ configuration.initialise();
+ List<String> vhosts = configuration.getConfig().getList("virtualhosts.virtualhost.name");
+
+ //Validate each vhost logs a creation
+ results = waitAndFindMatches("MST-1004 : Recovery Start : " + queueName);
+
+ assertEquals("Recovered test queue not found.", 1, results.size());
+
+ String result = getLogMessage(results, 0);
+
+ validateMessageID("MST-1004", result);
+
+ assertTrue("MST-1004 does end with queue '" + queueName + "':" + getMessageString(result),
+ getMessageString(result).endsWith(queueName));
+
+ results = waitAndFindMatches("MST-1005");
+
+ assertTrue("Insufficient MST-1005 logged.", results.size()>0);
+
+ result = null;
+
+ // If the first message is not our queue the second one will be
+ for(String resultEntry : results)
+ {
+ // Look for first match and set that to result
+ if (resultEntry.contains(queueName))
+ {
+ result = getLog(resultEntry);
+ break;
+ }
+ }
+
+ assertNotNull("MST-1005 entry for queue:" + queueName + ". Not found", result);
+
+ // getSlize will return extract the vhost from vh(/test) -> '/test'
+ // so remove the '/' to get the name
+ String vhostName = AbstractTestLogSubject.getSlice("vh", result).substring(1);
+
+ // To get the store class used in the configuration we need to know
+ // the virtualhost name, found above. AND
+ // the index that the virtualhost is within the configuration.
+ // we can retrive that from the vhosts list previously extracted.
+ String fullStoreName = configuration.getConfig().getString("virtualhosts.virtualhost(" + vhosts.indexOf(vhostName) + ")." + vhostName + ".store.class");
+
+ // Get the Simple class name from the expected class name of o.a.q.s.s.MMS
+ String storeName = fullStoreName.substring(fullStoreName.lastIndexOf(".") + 1);
+
+ assertTrue("MST-1005 does end with queue 'test-queue':" + getMessageString(result),
+ getMessageString(result).endsWith(queueName));
+
+ assertTrue("MST-1005 does end show correct count:" + getMessageString(result),
+ getMessageString(result).contains("Recovered " + COUNT + " messages"));
+
+ assertEquals("The store name does not match expected value",
+ storeName, AbstractTestLogSubject.getSlice("ms", fromSubject(result)));
+ }
+ finally
+ {
+ //Ensure we attempt to drain the queue.
+ assertEquals("Unable to drain queue", COUNT, drainQueue(queue));
+ }
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/DurableQueueLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/DurableQueueLoggingTest.java
new file mode 100644
index 0000000000..32adc49521
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/DurableQueueLoggingTest.java
@@ -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.
+ *
+ */
+package org.apache.qpid.server.logging;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.framing.AMQShortString;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.naming.NamingException;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The Queue test suite validates that the follow log messages as specified in
+ * the Functional Specification.
+ *
+ * This suite of tests validate that the Queue messages occur correctly and
+ * according to the following format:
+ *
+ * QUE-1001 : Create : [AutoDelete] [Durable|Transient] [Priority:<levels>] [Owner:<name>]
+ */
+public class DurableQueueLoggingTest extends AbstractTestLogging
+{
+ protected String DURABLE = "Durable";
+ protected String TRANSIENT = "Transient";
+ protected boolean _durable;
+
+ protected Connection _connection;
+ protected Session _session;
+ private static final String QUEUE_PREFIX = "QUE-";
+ private static int PRIORITIES = 6;
+
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ //Ensure we only have logs from our test
+ _monitor.reset();
+
+ _connection = getConnection();
+ _session = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ _durable = true;
+ }
+
+ /**
+ * Description:
+ * When a simple transient queue is created then a QUE-1001 create message
+ * is expected to be logged.
+ * Input:
+ * 1. Running broker
+ * 2. Persistent Queue is created from a client
+ * Output:
+ *
+ * <date> QUE-1001 : Create : Owner: '<name>' Durable
+ *
+ * Validation Steps:
+ * 3. The QUE ID is correct
+ * 4. The Durable tag is present in the message
+ * 5. The Owner is as expected
+ *
+ * @throws javax.jms.JMSException
+ * @throws javax.naming.NamingException
+ * @throws java.io.IOException
+ */
+ public void testQueueCreateDurableExclusive() throws NamingException, JMSException, IOException
+ {
+ String queueName= getTestQueueName();
+ // To force a queue Creation Event we need to create a consumer.
+ Queue queue = (Queue) _session.createQueue("direct://amq.direct/" + queueName + "/" + queueName + "?durable='" + _durable + "'&exclusive='true'");
+
+ _session.createConsumer(queue);
+
+ List<String> results = waitForMesssage();
+
+ String clientID = _connection.getClientID();
+ assertNotNull("clientID should not be null", clientID);
+
+ validateQueueProperties(results, false, false, clientID);
+ }
+
+ /**
+ * Description:
+ * When a simple transient queue is created then a QUE-1001 create message
+ * is expected to be logged.
+ * Input:
+ * 1. Running broker
+ * 2. Persistent Queue is created from a client
+ * Output:
+ *
+ * <date> QUE-1001 : Create : Owner: '<name>' Durable
+ *
+ * Validation Steps:
+ * 3. The QUE ID is correct
+ * 4. The Durable tag is present in the message
+ * 5. The Owner is as expected
+ *
+ * @throws javax.jms.JMSException
+ * @throws javax.naming.NamingException
+ * @throws java.io.IOException
+ */
+ public void testQueueCreateDurable() throws NamingException, JMSException, IOException
+ {
+ String queueName = getTestQueueName();
+
+ // To force a queue Creation Event we need to create a consumer.
+ Queue queue = (Queue) _session.createQueue("direct://amq.direct/" + queueName + "/" + queueName + "?durable='" + _durable + "'");
+
+ _session.createConsumer(queue);
+
+ List<String> results = waitForMesssage();
+
+ validateQueueProperties(results, false, false, null);
+ }
+
+ /**
+ * Description:
+ * When a simple transient queue is created then a QUE-1001 create message
+ * is expected to be logged.
+ * Input:
+ * 1. Running broker
+ * 2. AutoDelete Persistent Queue is created from a client
+ * Output:
+ *
+ * <date> QUE-1001 : Create : Owner: '<name>' AutoDelete Durable
+ *
+ * Validation Steps:
+ * 3. The QUE ID is correct
+ * 4. The Durable tag is present in the message
+ * 5. The Owner is as expected
+ * 6. The AutoDelete tag is present in the message
+ *
+ * @throws javax.jms.JMSException
+ * @throws javax.naming.NamingException
+ * @throws java.io.IOException
+ */
+ public void testQueueCreatePersistentAutoDelete() throws NamingException, JMSException, IOException
+ {
+ String queueName = getTestQueueName();
+ // To force a queue Creation Event we need to create a consumer.
+ Queue queue = (Queue) _session.createQueue("direct://amq.direct/"+queueName+"/"+queueName+"?durable='"+_durable+"'&autodelete='true'");
+
+ _session.createConsumer(queue);
+
+ List<String> results = waitForMesssage();
+
+ validateQueueProperties(results, false, true, null);
+ }
+
+ /**
+ * Description:
+ * When a simple transient queue is created then a QUE-1001 create message
+ * is expected to be logged.
+ * Input:
+ * 1. Running broker
+ * 2. Persistent Queue is created from a client
+ * Output:
+ *
+ * <date> QUE-1001 : Create : Owner: '<name>' Durable Priority:<levels>
+ *
+ * Validation Steps:
+ * 3. The QUE ID is correct
+ * 4. The Durable tag is present in the message
+ * 5. The Owner is as expected
+ * 6. The Priority level is correctly set
+ *
+ * @throws javax.jms.JMSException
+ * @throws javax.naming.NamingException
+ * @throws java.io.IOException
+ */
+ public void testCreateQueuePersistentPriority() throws NamingException, JMSException, IOException, AMQException
+ {
+ // To Create a Priority queue we need to use AMQSession specific code
+ final Map<String, Object> arguments = new HashMap<String, Object>();
+ arguments.put("x-qpid-priorities", PRIORITIES);
+ // Need to create a queue that does not exist so use test name
+ final String queueName = getTestQueueName();
+ ((AMQSession) _session).createQueue(new AMQShortString(queueName), false, _durable, false, arguments);
+
+ Queue queue = (Queue) _session.createQueue("direct://amq.direct/"+queueName+"/"+queueName+"?durable='"+_durable+"'&autodelete='false'");
+
+
+ //Need to create a Consumer to ensure that the log has had time to write
+ // as the above Create is Asynchronous
+ _session.createConsumer(queue);
+
+ List<String> results = waitForMesssage();
+
+ // Only 1 Queue message should hav been logged
+ assertEquals("Result set size not as expected", 1, results.size());
+
+ validateQueueProperties(results, true, false, null);
+ }
+
+ /**
+ * Description:
+ * When a simple transient queue is created then a QUE-1001 create message
+ * is expected to be logged.
+ * Input:
+ * 1. Running broker
+ * 2. AutoDelete Persistent Queue is created from a client
+ * Output:
+ *
+ * <date> QUE-1001 : Create : Owner: '<name>' Durable Priority:<levels>
+ *
+ * Validation Steps:
+ * 3. The QUE ID is correct
+ * 4. The Durable tag is present in the message
+ * 5. The Owner is as expected
+ * 6. The AutoDelete tag is present in the message
+ * 7. The Priority level is correctly set
+ *
+ * @throws javax.jms.JMSException
+ * @throws javax.naming.NamingException
+ * @throws java.io.IOException
+ */
+ public void testCreateQueuePersistentAutoDeletePriority() throws NamingException, JMSException, IOException, AMQException
+ {
+ // To Create a Priority queue we need to use AMQSession specific code
+ final Map<String, Object> arguments = new HashMap<String, Object>();
+ arguments.put("x-qpid-priorities", PRIORITIES);
+ // Need to create a queue that does not exist so use test name
+ final String queueName = getTestQueueName() + "-autoDeletePriority";
+ ((AMQSession) _session).createQueue(new AMQShortString(queueName), true, _durable, false, arguments);
+
+ Queue queue = (Queue) _session.createQueue("direct://amq.direct/"+queueName+"/"+queueName+"?durable='"+_durable+"'&autodelete='true'");
+
+
+ //Need to create a Consumer to ensure that the log has had time to write
+ // as the above Create is Asynchronous
+ _session.createConsumer(queue);
+
+ List<String> results = waitForMesssage();
+
+ validateQueueProperties(results, true, true, null);
+ }
+
+ private List<String> waitForMesssage() throws IOException
+ {
+ // Validation
+ // Ensure we have received the QUE log msg.
+ waitForMessage("QUE-1001");
+
+ List<String> results = findMatches(QUEUE_PREFIX);
+
+ // Only 1 Queue message should hav been logged
+ assertEquals("Result set size not as expected", 1, results.size());
+
+ return results;
+ }
+
+ public void validateQueueProperties(List<String> results, boolean hasPriority, boolean hasAutodelete, String clientID)
+ {
+ String log = getLogMessage(results, 0);
+
+ // Message Should be a QUE-1001
+ validateMessageID("QUE-1001", log);
+
+ // Queue is Durable
+ assertEquals(DURABLE + " keyword not correct in log entry",
+ _durable, fromMessage(log).contains(DURABLE));
+
+ assertEquals(TRANSIENT + " keyword not correct in log entry.",
+ !_durable, fromMessage(log).contains(TRANSIENT));
+
+ // Queue is Priority
+ assertEquals("Unexpected priority status:" + fromMessage(log), hasPriority,
+ fromMessage(log).contains("Priority: " + PRIORITIES));
+
+ // Queue is AutoDelete
+ assertEquals("Unexpected AutoDelete status:" + fromMessage(log), hasAutodelete,
+ fromMessage(log).contains("AutoDelete"));
+
+ if(clientID != null)
+ {
+ assertTrue("Queue does not have correct owner value:" + fromMessage(log),
+ fromMessage(log).contains("Owner: " + clientID));
+ }
+ else
+ {
+ assertFalse("Queue should not contain Owner tag:" + fromMessage(log),
+ fromMessage(log).contains("Owner"));
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ExchangeLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ExchangeLoggingTest.java
new file mode 100644
index 0000000000..1e48f34f99
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ExchangeLoggingTest.java
@@ -0,0 +1,217 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.logging;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQSession_0_10;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.ExchangeDeleteBody;
+import org.apache.qpid.framing.ExchangeDeleteOkBody;
+import org.apache.qpid.framing.amqp_8_0.MethodRegistry_8_0;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Queue;
+import javax.jms.Session;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Exchange
+ *
+ * The Exchange test suite validates that the follow log messages as specified in the Functional Specification.
+ *
+ * This suite of tests validate that the Exchange messages occur correctly and according to the following format:
+ *
+ * EXH-1001 : Create : [Durable] Type:<value> Name:<value>
+ * EXH-1002 : Deleted
+ */
+public class ExchangeLoggingTest extends AbstractTestLogging
+{
+
+ static final String EXH_PREFIX = "EXH-";
+
+ Connection _connection;
+ Session _session;
+ Queue _queue;
+ String _name;
+ String _type;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+
+ _connection = getConnection();
+
+ _session = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ _type = "direct";
+ _name = getTestQueueName()+ "-exchange";
+
+ _queue = _session.createQueue(_type + "://" + _name + "/queue/queue");
+
+ }
+
+ /**
+ * Description:
+ * When a durable exchange is created an EXH-1001 message is logged with the Durable tag. This will be the first message from this exchange.
+ * Input:
+ *
+ * 1. Running broker
+ * 2. Client requests a durable exchange be created.
+ * Output:
+ *
+ * <date> EXH-1001 : Create : Durable Type:<value> Name:<value>
+ *
+ * Validation Steps:
+ * 3. The EXH ID is correct
+ * 4. The Durable tag is present in the message
+ */
+
+ public void testExchangeCreateDurable() throws JMSException, IOException
+ {
+ // The client cannot create durable exchanges lets just look at the
+ // ones the broker creates at startup.
+
+ // They should all be durable
+
+ // Ensure we have received the EXH log msg.
+ waitForMessage("EXH-1001");
+
+ List<String> results = findMatches(EXH_PREFIX);
+
+ assertTrue("No Results found for Exchange.", results.size()>0);
+
+ validateExchangeCreate(results, true, false);
+ }
+
+ /**
+ * Description:
+ * When an exchange is created an EXH-1001 message is logged. This will be the first message from this exchange.
+ * Input:
+ *
+ * 1. Running broker
+ * 2. Client requests an exchange be created.
+ * Output:
+ *
+ * <date> EXH-1001 : Create : Type:<value> Name:<value>
+ *
+ * Validation Steps:
+ * 3. The EXH ID is correct
+ */
+ public void testExchangeCreate() throws JMSException, IOException
+ {
+ //Ignore broker startup messages
+ _monitor.reset();
+
+ _session.createConsumer(_queue);
+ // Ensure we have received the EXH log msg.
+ waitForMessage("EXH-1001");
+
+ List<String> results = findMatches(EXH_PREFIX);
+
+ assertEquals("Result set larger than expected.", 1, results.size());
+
+ validateExchangeCreate(results, false, true);
+ }
+
+ private void validateExchangeCreate(List<String> results, boolean durable, boolean checkNameAndType)
+ {
+ String log = getLogMessage(results, 0);
+ String message = getMessageString(fromMessage(log));
+
+ validateMessageID("EXH-1001", log);
+
+ assertTrue("Log Message does not start with create:" + message,
+ message.startsWith("Create"));
+
+ assertEquals("Unexpected Durable state:" + message, durable,
+ message.contains("Durable"));
+
+ if(checkNameAndType)
+ {
+ assertTrue("Log Message does not contain Type:" + message,
+ message.contains("Type: " + _type));
+ assertTrue("Log Message does not contain Name:" + message,
+ message.contains("Name: " + _name));
+ }
+ }
+
+ /**
+ * Description:
+ * An Exchange can be deleted through an AMQP ExchangeDelete method. When this is successful an EXH-1002 Delete message will be logged. This will be the last message from this exchange.
+ * Input:
+ *
+ * 1. Running broker
+ * 2. A new Exchange has been created
+ * 3. Client requests that the new exchange be deleted.
+ * Output:
+ *
+ * <date> EXH-1002 : Deleted
+ *
+ * Validation Steps:
+ * 4. The EXH ID is correct
+ * 5. There is a corresponding EXH-1001 Create message logged.
+ */
+ public void testExchangeDelete() throws Exception, IOException
+ {
+ //Ignore broker startup messages
+ _monitor.reset();
+
+ //create the exchange by creating a consumer
+ _session.createConsumer(_queue);
+
+ //now delete the exchange
+ if(isBroker010())
+ {
+ ((AMQSession_0_10) _session).sendExchangeDelete(_name, false);
+ }
+ else
+ {
+ MethodRegistry_8_0 registry = new MethodRegistry_8_0();
+
+ ExchangeDeleteBody body = registry.createExchangeDeleteBody(0, new AMQShortString(_name), false, true);
+
+ AMQFrame exchangeDeclare = body.generateFrame(0);
+
+ ((AMQConnection) _connection).getProtocolHandler().syncWrite(exchangeDeclare, ExchangeDeleteOkBody.class);
+ }
+
+ //Wait and ensure we get our last EXH-1002 msg
+ waitForMessage("EXH-1002");
+
+ List<String> results = findMatches(EXH_PREFIX);
+
+ assertEquals("Result set larger than expected.", 2, results.size());
+
+ validateExchangeCreate(results, false, false);
+
+ String log = getLogMessage(results, 1);
+ validateMessageID("EXH-1002", log);
+
+ String message = getMessageString(fromMessage(log));
+ assertEquals("Log Message not as expected", "Deleted", message);
+
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ManagementLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ManagementLoggingTest.java
new file mode 100644
index 0000000000..595c0d5f35
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/ManagementLoggingTest.java
@@ -0,0 +1,305 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.logging;
+
+import junit.framework.AssertionFailedError;
+
+import org.apache.qpid.util.LogMonitor;
+
+import java.util.List;
+import java.io.File;
+
+/**
+ * Management Console Test Suite
+ *
+ * The Management Console test suite validates that the follow log messages as specified in the Functional Specification.
+ *
+ * This suite of tests validate that the management console messages occur correctly and according to the following format:
+ *
+ * MNG-1001 : Startup
+ * MNG-1002 : Starting : <service> : Listening on port <Port>
+ * MNG-1003 : Shutting down : <service> : port <Port>
+ * MNG-1004 : Ready
+ * MNG-1005 : Stopped
+ * MNG-1006 : Using SSL Keystore : <path>
+ */
+public class ManagementLoggingTest extends AbstractTestLogging
+{
+ private static final String MNG_PREFIX = "MNG-";
+
+ public void setUp() throws Exception
+ {
+ setLogMessagePrefix();
+
+ // We either do this here or have a null check in tearDown.
+ // As when this test is run against profiles other than java it will NPE
+ _monitor = new LogMonitor(_outputFile);
+ //We explicitly do not call super.setUp as starting up the broker is
+ //part of the test case.
+
+ }
+
+ /**
+ * Description:
+ * Using the startup configuration validate that the management startup
+ * message is logged correctly.
+ * Input:
+ * Standard configuration with management enabled
+ * Output:
+ *
+ * <date> MNG-1001 : Startup
+ *
+ * Constraints:
+ * This is the FIRST message logged by MNG
+ * Validation Steps:
+ *
+ * 1. The BRK ID is correct
+ * 2. This is the FIRST message logged by MNG
+ */
+ public void testManagementStartupEnabled() throws Exception
+ {
+ // This test only works on external java brokers due to the fact that
+ // Management is disabled on InVM brokers.
+ if (isJavaBroker() && isExternalBroker())
+ {
+ startBrokerAndCreateMonitor(true, false);
+
+ // Ensure we have received the MNG log msg.
+ waitForMessage("MNG-1001");
+
+ List<String> results = findMatches(MNG_PREFIX);
+
+ try
+ {
+ // Validation
+
+ assertTrue("MNGer message not logged", results.size() > 0);
+
+ String log = getLogMessage(results, 0);
+
+ //1
+ validateMessageID("MNG-1001", log);
+
+ //2
+ //There will be 2 copies of the startup message (one via SystemOut, and one via Log4J)
+ results = findMatches("MNG-1001");
+ assertEquals("Unexpected startup message count.",
+ 2, results.size());
+
+ //3
+ assertEquals("Startup log message is not 'Startup'.", "Startup",
+ getMessageString(log));
+ }
+ catch (AssertionFailedError afe)
+ {
+ dumpLogs(results, _monitor);
+ throw afe;
+ }
+ }
+ }
+
+ /**
+ * Description:
+ * Verify that when management is disabled in the configuration file the
+ * startup message is not logged.
+ * Input:
+ * Standard configuration with management disabled
+ * Output:
+ * NO MNG messages
+ * Validation Steps:
+ *
+ * 1. Validate that no MNG messages are produced.
+ */
+ public void testManagementStartupDisabled() throws Exception
+ {
+ // This test only works on external java brokers due to the fact that
+ // Management is disabled on InVM brokers.
+ if (isJavaBroker() && isExternalBroker())
+ {
+ startBrokerAndCreateMonitor(false, false);
+
+ List<String> results = findMatches(MNG_PREFIX);
+ try
+ {
+ // Validation
+
+ assertEquals("MNGer messages logged", 0, results.size());
+ }
+ catch (AssertionFailedError afe)
+ {
+ dumpLogs(results, _monitor);
+ throw afe;
+ }
+ }
+ }
+
+ /**
+ * The two MNG-1002 messages are logged at the same time so lets test them
+ * at the same time.
+ *
+ * Description:
+ * Using the default configuration validate that the RMI Registry socket is
+ * correctly reported as being opened
+ *
+ * Input:
+ * The default configuration file
+ * Output:
+ *
+ * <date> MESSAGE MNG-1002 : Starting : RMI Registry : Listening on port 8999
+ *
+ * Constraints:
+ * The RMI ConnectorServer and Registry log messages do not have a prescribed order
+ * Validation Steps:
+ *
+ * 1. The MNG ID is correct
+ * 2. The specified port is the correct '8999'
+ *
+ * Description:
+ * Using the default configuration validate that the RMI ConnectorServer
+ * socket is correctly reported as being opened
+ *
+ * Input:
+ * The default configuration file
+ * Output:
+ *
+ * <date> MESSAGE MNG-1002 : Starting : RMI ConnectorServer : Listening on port 9099
+ *
+ * Constraints:
+ * The RMI ConnectorServer and Registry log messages do not have a prescribed order
+ * Validation Steps:
+ *
+ * 1. The MNG ID is correct
+ * 2. The specified port is the correct '9099'
+ */
+ public void testManagementStartupRMIEntries() throws Exception
+ {
+ // This test only works on external java brokers due to the fact that
+ // Management is disabled on InVM brokers.
+ if (isJavaBroker() && isExternalBroker())
+ {
+ startBrokerAndCreateMonitor(true, false);
+
+ List<String> results = waitAndFindMatches("MNG-1002");
+ try
+ {
+ // Validation
+
+ //There will be 4 startup messages (two via SystemOut, and two via Log4J)
+ assertEquals("Unexpected MNG-1002 message count", 4, results.size());
+
+ String log = getLogMessage(results, 0);
+
+ //1
+ validateMessageID("MNG-1002", log);
+
+ //Check the RMI Registry port is as expected
+ int mPort = getPort() + (DEFAULT_MANAGEMENT_PORT - DEFAULT_PORT);
+ assertTrue("RMI Registry port not as expected(" + mPort + ").:" + getMessageString(log),
+ getMessageString(log).endsWith(String.valueOf(mPort)));
+
+ log = getLogMessage(results, 2);
+
+ //1
+ validateMessageID("MNG-1002", log);
+
+ // We expect the RMI Registry port (the defined 'management port') to be
+ // 100 lower than the JMX RMIConnector Server Port (the actual JMX server)
+ int jmxPort = mPort + 100;
+ assertTrue("JMX RMIConnectorServer port not as expected(" + jmxPort + ").:" + getMessageString(log),
+ getMessageString(log).endsWith(String.valueOf(jmxPort)));
+ }
+ catch (AssertionFailedError afe)
+ {
+ dumpLogs(results, _monitor);
+ throw afe;
+ }
+ }
+ }
+
+ /**
+ * Description:
+ * Using the default configuration with SSL enabled for the management port the SSL Keystore path should be reported via MNG-1006
+ * Input:
+ * Management SSL enabled default configuration.
+ * Output:
+ *
+ * <date> MESSAGE MNG-1006 : Using SSL Keystore : test_resources/ssl/keystore.jks
+ *
+ * Validation Steps:
+ *
+ * 1. The MNG ID is correct
+ * 2. The keystore path is as specified in the configuration
+ */
+ public void testManagementStartupSSLKeystore() throws Exception
+ {
+ // This test only works on external java brokers due to the fact that
+ // Management is disabled on InVM brokers.
+ if (isJavaBroker() && isExternalBroker())
+ {
+ startBrokerAndCreateMonitor(true, true);
+
+ List<String> results = waitAndFindMatches("MNG-1006");
+ try
+ {
+ // Validation
+
+ assertTrue("MNGer message not logged", results.size() > 0);
+
+ String log = getLogMessage(results, 0);
+
+ //1
+ validateMessageID("MNG-1006", log);
+
+ // Validate we only have two MNG-1002 (one via stdout, one via log4j)
+ results = findMatches("MNG-1006");
+ assertEquals("Upexpected SSL Keystore message count",
+ 2, results.size());
+
+ // Validate the keystore path is as expected
+ assertTrue("SSL Keystore entry expected.:" + getMessageString(log),
+ getMessageString(log).endsWith(new File(getConfigurationStringProperty("management.ssl.keyStorePath")).getName()));
+ }
+ catch (AssertionFailedError afe)
+ {
+ dumpLogs(results, _monitor);
+ throw afe;
+ }
+ }
+
+ }
+
+ private void startBrokerAndCreateMonitor(boolean managementEnabled, boolean useManagementSSL) throws Exception
+ {
+ //Ensure management is on
+ setConfigurationProperty("management.enabled", String.valueOf(managementEnabled));
+
+ if(useManagementSSL)
+ {
+ // This test requires we have an ssl connection
+ setConfigurationProperty("management.ssl.enabled", "true");
+ }
+
+ startBroker();
+
+ // Now we can create the monitor as _outputFile will now be defined
+ _monitor = new LogMonitor(_outputFile);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/MemoryMessageStoreLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/MemoryMessageStoreLoggingTest.java
new file mode 100644
index 0000000000..34d9e1f057
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/MemoryMessageStoreLoggingTest.java
@@ -0,0 +1,186 @@
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+package org.apache.qpid.server.logging;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.qpid.server.configuration.ServerConfiguration;
+import org.apache.qpid.server.logging.subjects.AbstractTestLogSubject;
+import org.apache.qpid.util.LogMonitor;
+
+/**
+ * The MessageStore test suite validates that the follow log messages as
+ * specified in the Functional Specification.
+ *
+ * This suite of tests validate that the MessageStore messages occur correctly
+ * and according to the following format:
+ *
+ * MST-1001 : Created : <name>
+ * MST-1003 : Closed
+ *
+ * NOTE: Only for Persistent Stores
+ * MST-1002 : Store location : <path>
+ * MST-1004 : Recovery Start [: <queue.name>]
+ * MST-1005 : Recovered <count> messages for queue <queue.name>
+ * MST-1006 : Recovery Complete [: <queue.name>]
+ */
+public class MemoryMessageStoreLoggingTest extends AbstractTestLogging
+{
+ protected static final String MESSAGES_STORE_PREFIX = "MST-";
+
+ public void setUp() throws Exception
+ {
+ //We explicitly do not call super.setUp as starting up the broker is
+ //part of the test case.
+ // So we have to make the new Log Monitor here
+
+ _monitor = new LogMonitor(_outputFile);
+ }
+
+ /**
+ * Description:
+ * During Virtualhost startup a MessageStore will be created. The first MST
+ * message that must be logged is the MST-1001 MessageStore creation.
+ * Input:
+ * Default configuration
+ * Output:
+ * <date> MST-1001 : Created : <name>
+ *
+ * Validation Steps:
+ *
+ * 1. The MST ID is correct
+ * 2. The <name> is the correct MessageStore type as specified in the Default configuration
+ *
+ * @throws Exception caused by broker startup
+ */
+ public void testMessageStoreCreation() throws Exception
+ {
+ assertLoggingNotYetOccured(MESSAGES_STORE_PREFIX);
+
+ super.setUp();
+
+ List<String> results = waitAndFindMatches(MESSAGES_STORE_PREFIX);
+
+ // Validation
+
+ assertTrue("MST messages not logged", results.size() > 0);
+
+ String log = getLogMessage(results, 0);
+ //1
+ assertEquals("MST-1001 is not the first MST message", "MST-1001", getMessageID(fromMessage(log)));
+
+ //Validate each vhost logs a creation
+ results = waitAndFindMatches("MST-1001");
+
+ // Load VirtualHost list from file.
+ List<String> vhosts = Arrays.asList(getServerConfig().getVirtualHosts());
+
+ assertEquals("Each vhost did not create a store.", vhosts.size(), results.size());
+
+ for (int index = 0; index < results.size(); index++)
+ {
+ String result = getLogMessage(results, index);
+
+ // getSlice will return extract the vhost from vh(/test) -> '/test'
+ // so remove the '/' to get the name
+ String vhostName = AbstractTestLogSubject.getSlice("vh", result).substring(1);
+
+ // Get the store class used in the configuration for the virtualhost.
+ String fullStoreName = getServerConfig().getVirtualHostConfig(vhostName).getMessageStoreClass();
+
+ // Get the Simple class name from the expected class name of o.a.q.s.s.MMS
+ String storeName = fullStoreName.substring(fullStoreName.lastIndexOf(".") + 1);
+
+ assertTrue("MST-1001 does not contains correct store name:"
+ + storeName + ":" + result, getMessageString(result).endsWith(storeName));
+
+ assertEquals("The store name does not match expected value",
+ storeName, AbstractTestLogSubject.getSlice("ms", fromSubject(result)));
+ }
+ }
+
+ /**
+ * Description:
+ * During shutdown the MessageStore will also cleanly close. When this has
+ * completed a MST-1003 closed message will be logged. No further messages
+ * from this MessageStore will be logged after this message.
+ *
+ * Input:
+ * Default configuration
+ * Output:
+ * <date> MST-1003 : Closed
+ *
+ * Validation Steps:
+ *
+ * 1. The MST ID is correct
+ * 2. This is teh last log message from this MessageStore
+ *
+ * @throws Exception caused by broker startup
+ */
+ public void testMessageStoreClose() throws Exception
+ {
+ assertLoggingNotYetOccured(MESSAGES_STORE_PREFIX);
+
+ super.setUp();
+
+ //Stop the broker so we get the close messages.
+ stopBroker();
+
+ List<String> results = waitAndFindMatches(MESSAGES_STORE_PREFIX);
+
+ // Validation
+
+ assertTrue("MST messages not logged", results.size() > 0);
+
+ // Load VirtualHost list from file.
+ ServerConfiguration configuration = new ServerConfiguration(_configFile);
+ configuration.initialise();
+ List<String> vhosts = Arrays.asList(configuration.getVirtualHosts());
+
+ //Validate each vhost logs a creation
+ results = waitAndFindMatches("MST-1003");
+
+ assertEquals("Each vhost did not close its store.", vhosts.size(), results.size());
+
+ for (int index = 0; index < results.size(); index++)
+ {
+ String result = getLogMessage(results, index);
+
+ // getSlice will return extract the vhost from vh(/test) -> '/test'
+ // so remove the '/' to get the name
+ String vhostName = AbstractTestLogSubject.getSlice("vh", result).substring(1);
+
+ // Get the store class used in the configuration for the virtualhost.
+ String fullStoreName = configuration.getVirtualHostConfig(vhostName).getMessageStoreClass();
+
+ // Get the Simple class name from the expected class name of o.a.q.s.s.MMS
+ String storeName = fullStoreName.substring(fullStoreName.lastIndexOf(".") + 1);
+
+ assertEquals("MST-1003 does not close:",
+ "Closed", getMessageString(result));
+
+ assertEquals("The store name does not match expected value",
+ storeName, AbstractTestLogSubject.getSlice("ms", fromSubject(result)));
+ }
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/QueueLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/QueueLoggingTest.java
new file mode 100644
index 0000000000..b8a42c0ab3
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/QueueLoggingTest.java
@@ -0,0 +1,183 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.logging;
+
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.failover.FailoverException;
+import org.apache.qpid.server.logging.subjects.AbstractTestLogSubject;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.AMQException;
+
+import javax.jms.Connection;
+import javax.jms.Session;
+import javax.jms.Queue;
+import javax.jms.JMSException;
+import javax.naming.NamingException;
+import java.util.List;
+import java.io.IOException;
+
+/**
+ * The Queue test suite validates that the follow log messages as specified in
+ * the Functional Specification.
+ *
+ * This suite of tests validate that the Queue messages occur correctly and
+ * according to the following format:
+ *
+ * QUE-1002 : Deleted
+ */
+public class QueueLoggingTest extends AbstractTestLogging
+{
+ protected Connection _connection;
+ protected Session _session;
+ private static final String QUEUE_PREFIX = "QUE-";
+
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ //Remove broker startup logging messages
+ _monitor.reset();
+
+ _connection = getConnection();
+ _session = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ }
+
+ /**
+ * Description:
+ * An explict QueueDelete request must result in a QUE-1002 Deleted message
+ * being logged. This can be done via an explict AMQP QueueDelete method.
+ * Input:
+ *
+ * 1. Running Broker
+ * 2. Queue created on the broker with no subscribers
+ * 3. Client requests the queue be deleted via a QueueDelete
+ * Output:
+ *
+ * <date> QUE-1002 : Deleted
+ *
+ * Validation Steps:
+ *
+ * 4. The QUE ID is correct
+ *
+ * @throws java.io.IOException
+ * @throws javax.jms.JMSException
+ * @throws javax.naming.NamingException
+ */
+ public void testQueueDelete() throws NamingException, JMSException, IOException, FailoverException, AMQException
+ {
+ // To force a queue Creation Event we need to create a consumer.
+ Queue queue = _session.createQueue(getTestQueueName());
+
+ _session.createConsumer(queue);
+
+ // Delete Queue
+ ((AMQSession)_session).sendQueueDelete(new AMQShortString(queue.getQueueName()));
+
+ //Perform a synchronous action to ensure that the above log will be on disk
+ _session.close();
+
+ // Validation
+ //Ensure that we wait for the QUE log message
+ waitAndFindMatches("QUE-1002");
+
+ List<String> results = findMatches(QUEUE_PREFIX);
+
+ // Only 1 Queue message should hav been logged
+ assertEquals("Result set size not as expected", 2, results.size());
+
+ String log = getLogMessage(results, 0);
+
+ // Message Should be a QUE-1001
+ validateMessageID("QUE-1001", log);
+
+ String createdQueueName = AbstractTestLogSubject.getSlice("qu", fromSubject(log));
+
+ log = getLogMessage(results, 1);
+ // Message Should be a QUE-1002
+ validateMessageID("QUE-1002", log);
+
+ assertEquals("Log Message is incorrect ", "Deleted", getMessageString(fromMessage(log)));
+
+ assertEquals("Queue Delete not for created queue:", createdQueueName,
+ AbstractTestLogSubject.getSlice("qu", fromSubject(log)));
+ }
+
+
+ /**
+ * Description:
+ * An explict QueueDelete request must result in a QUE-1002 Deleted message
+ * being logged. This can be done via an explict AMQP QueueDelete method.
+ * Input:
+ *
+ * 1. Running Broker
+ * 2. Queue created on the broker with no subscribers
+ * 3. Client creates a temporary queue then disconnects
+ * Output:
+ *
+ * <date> QUE-1002 : Deleted
+ *
+ * Validation Steps:
+ *
+ * 4. The QUE ID is correct
+ *
+ * @throws java.io.IOException
+ * @throws javax.jms.JMSException
+ * @throws javax.naming.NamingException
+ */
+ public void testQueueAutoDelete() throws NamingException, JMSException, IOException
+ {
+ // Create a temporary queue so that when we consume from it and
+ // then close the consumer it will be autoDeleted.
+ _session.createConsumer(_session.createTemporaryQueue()).close();
+
+ if(isBroker010())
+ {
+ //auto-delete is at session close for 0-10
+ _session.close();
+ }
+
+ // Validation
+ //Ensure that we wait for the QUE log message
+ waitAndFindMatches("QUE-1002");
+
+ List<String> results = findMatches(QUEUE_PREFIX);
+
+ // Only 1 Queue message should hav been logged
+ assertEquals("Result set size not as expected", 2, results.size());
+
+ String log = getLogMessage(results, 0);
+
+ // Message Should be a QUE-1001
+ validateMessageID("QUE-1001", log);
+
+ String createdQueueName = AbstractTestLogSubject.getSlice("qu", fromSubject(log));
+
+ log = getLogMessage(results, 1);
+ // Message Should be a QUE-1002
+ validateMessageID("QUE-1002", log);
+
+ assertEquals("Log Message is incorrect ", "Deleted", getMessageString(fromMessage(log)));
+
+ assertEquals("Queue Delete not for created queue:", createdQueueName,
+ AbstractTestLogSubject.getSlice("qu", fromSubject(log)));
+
+ }
+
+} \ No newline at end of file
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/SubscriptionLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/SubscriptionLoggingTest.java
new file mode 100644
index 0000000000..6e156f091e
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/SubscriptionLoggingTest.java
@@ -0,0 +1,458 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.logging;
+
+import junit.framework.AssertionFailedError;
+import org.apache.qpid.client.AMQConnection;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.MessageConsumer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.Topic;
+import javax.jms.Message;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Subscription
+ *
+ * The Subscription test suite validates that the follow log messages as specified in the Functional Specification.
+ *
+ * This suite of tests validate that the Subscription messages occur correctly and according to the following format:
+ *
+ * SUB-1001 : Create : [Durable] [Arguments : <key=value>]
+ * SUB-1002 : Close
+ * SUB-1003 : State : <state>
+ */
+public class SubscriptionLoggingTest extends AbstractTestLogging
+{
+ static final String SUB_PREFIX = "SUB-";
+
+ Connection _connection;
+ Session _session;
+ Queue _queue;
+ Topic _topic;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ //Remove broker startup logging messages
+ _monitor.reset();
+
+ _connection = getConnection();
+
+ _session = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ _queue = (Queue) getInitialContext().lookup(QUEUE);
+ _topic = (Topic) getInitialContext().lookup(TOPIC);
+ }
+
+ /**
+ * Description:
+ * When a Subscription is created it will be logged. This test validates that Subscribing to a transient queue is correctly logged.
+ * Input:
+ *
+ * 1. Running Broker
+ * 2. Create a new Subscription to a transient queue/topic.
+ * Output: 6
+ *
+ * <date> SUB-1001 : Create
+ *
+ * Validation Steps:
+ * 3. The SUB ID is correct
+ *
+ * @throws java.io.IOException - if there is a problem getting the matches
+ * @throws javax.jms.JMSException - if there is a problem creating the consumer
+ */
+ public void testSubscriptionCreate() throws JMSException, IOException
+ {
+ _session.createConsumer(_queue);
+
+ //Validate
+
+ //Ensure that we wait for the SUB log message
+ waitAndFindMatches("SUB-1001");
+
+ List<String> results = findMatches(SUB_PREFIX);
+
+ assertEquals("Result set larger than expected.", 1, results.size());
+
+ String log = getLogMessage(results, 0);
+
+ validateMessageID("SUB-1001", log);
+
+ assertEquals("Log Message not as expected", "Create", getMessageString(fromMessage(log)));
+ }
+
+ /**
+ * Description:
+ * The creation of a Durable Subscription, such as a JMS DurableTopicSubscriber will result in an extra Durable tag being included in the Create log message
+ * Input:
+ *
+ * 1. Running Broker
+ * 2. Creation of a JMS DurableTopicSubiber
+ * Output:
+ *
+ * <date> SUB-1001 : Create : Durable
+ *
+ * Validation Steps:
+ * 3. The SUB ID is correct
+ * 4. The Durable tag is present in the message
+ * NOTE: A Subscription is not Durable, the queue it consumes from is.
+ *
+ * @throws java.io.IOException - if there is a problem getting the matches
+ * @throws javax.jms.JMSException - if there is a problem creating the consumer
+ */
+ public void testSubscriptionCreateDurable() throws JMSException, IOException
+ {
+ _session.createDurableSubscriber(_topic, getName());
+
+ //Validate
+ //Ensure that we wait for the SUB log message
+ waitAndFindMatches("SUB-1001");
+
+ List<String> results = findMatches(SUB_PREFIX);
+
+ assertEquals("Result set not as expected.", 1, results.size());
+
+ String log = getLogMessage(results, 0);
+
+ validateMessageID("SUB-1001", log);
+
+ String message = getMessageString(fromMessage(log));
+ assertTrue("Durable not on log message:" + message, message.contains("Durable"));
+ }
+
+ /**
+ * Description:
+ * The creation of a QueueBrowser will provides a number arguments and so should form part of the SUB-1001 Create message.
+ * Input:
+ *
+ * 1. Running Broker
+ * 2. Java Client creates a QueueBroweser
+ * Output:
+ *
+ * <date> SUB-1001 : Create : Arguments : <key=value>
+ *
+ * Validation Steps:
+ * 3. The SUB ID is correct
+ * 4. The Arguments are present in the message
+ * 5. Arguments keys include AutoClose and Browser.
+ *
+ * @throws java.io.IOException - if there is a problem getting the matches
+ * @throws javax.jms.JMSException - if there is a problem creating the consumer
+ */
+ public void testSubscriptionCreateQueueBrowser() throws JMSException, IOException
+ {
+ _session.createBrowser(_queue);
+
+ //Validate
+ //Ensure that we wait for the SUB log message
+ waitAndFindMatches("SUB-1001");
+
+ List<String> results = findMatches(SUB_PREFIX);
+
+ assertEquals("Result set larger than expected.", 2, results.size());
+
+ String log = getLogMessage(results, 0);
+
+ validateMessageID("SUB-1001", log);
+
+ String message = getMessageString(fromMessage(log));
+ assertTrue("Browser not on log message:" + message, message.contains("Browser"));
+ if(!isBroker010())
+ {
+ assertTrue("AutoClose not on log message:" + message, message.contains("AutoClose"));
+ }
+
+ // Beacause it is an auto close and we have no messages on the queue we
+ // will get a close message
+ log = getLogMessage(results, 1);
+ validateMessageID("SUB-1002", log);
+
+ }
+
+ /**
+ * Description:
+ * The creation of a Subscriber with a JMS Selector will result in the Argument field being populated. These argument key/value pairs are then shown in the log message.
+ * Input:
+ *
+ * 1. Running Broker
+ * 2. Subscriber created with a JMS Selector.
+ * Output:
+ *
+ * <date> SUB-1001 : Create : Arguments : <key=value>
+ *
+ * Validation Steps:
+ * 3. The SUB ID is correct
+ * 4. Argument tag is present in the message
+ *
+ * @throws java.io.IOException - if there is a problem getting the matches
+ * @throws javax.jms.JMSException - if there is a problem creating the consumer
+ */
+ public void testSubscriptionCreateWithArguments() throws JMSException, IOException
+ {
+ final String SELECTOR = "Selector='True'";
+ _session.createConsumer(_queue, SELECTOR);
+
+ //Validate
+
+ //Ensure that we wait for the SUB log message
+ waitAndFindMatches("SUB-1001");
+
+ List<String> results = findMatches(SUB_PREFIX);
+
+ assertEquals("Result set larger than expected.", 1, results.size());
+
+ String log = getLogMessage(results, 0);
+
+ validateMessageID("SUB-1001", log);
+
+ String message = getMessageString(fromMessage(log));
+ assertTrue("Selector not on log message:" + message, message.contains(SELECTOR));
+ }
+
+ /**
+ * Description:
+ * The final combination of SUB-1001 Create messages involves the creation of a Durable Subscription that also contains a set of Arguments, such as those provided via a JMS Selector.
+ * Input:
+ *
+ * 1. Running Broker
+ * 2. Java Client creates a Durable Subscription with Selector
+ * Output:
+ *
+ * <date> SUB-1001 : Create : Durable Arguments : <key=value>
+ *
+ * Validation Steps:
+ * 3. The SUB ID is correct
+ * 4. The tag Durable is present in the message
+ * 5. The Arguments are present in the message
+ *
+ * @throws java.io.IOException - if there is a problem getting the matches
+ * @throws javax.jms.JMSException - if there is a problem creating the consumer
+ */
+ public void testSubscriptionCreateDurableWithArguments() throws JMSException, IOException
+ {
+ final String SELECTOR = "Selector='True'";
+ _session.createDurableSubscriber(_topic, getName(), SELECTOR, false);
+
+ //Validate
+
+ //Ensure that we wait for the SUB log message
+ waitAndFindMatches("SUB-1001");
+
+ List<String> results = findMatches(SUB_PREFIX);
+
+ assertEquals("Result set larger than expected.", 1, results.size());
+
+ String log = getLogMessage(results, 0);
+
+ validateMessageID("SUB-1001", log);
+
+ String message = getMessageString(fromMessage(log));
+ assertTrue("Durable not on log message:" + message, message.contains("Durable"));
+ assertTrue("Selector not on log message:" + message, message.contains(SELECTOR));
+ }
+
+ /**
+ * Description:
+ * When a Subscription is closed it will log this so that it can be correlated with the Create.
+ * Input:
+ *
+ * 1. Running Broker
+ * 2. Client with a subscription.
+ * 3. The subscription is then closed.
+ * Output:
+ *
+ * <date> SUB-1002 : Close
+ *
+ * Validation Steps:
+ * 1. The SUB ID is correct
+ * 2. There must be a SUB-1001 Create message preceding this message
+ * 3. This must be the last message from the given Subscription
+ *
+ * @throws java.io.IOException - if there is a problem getting the matches
+ * @throws javax.jms.JMSException - if there is a problem creating the consumer
+ */
+ public void testSubscriptionClose() throws JMSException, IOException
+ {
+ _session.createConsumer(_queue).close();
+
+ //Validate
+ //Ensure that we wait for the SUB log message
+ waitAndFindMatches("SUB-1002");
+
+ List<String> results = findMatches(SUB_PREFIX);
+
+ //3
+ assertEquals("Result set larger than expected.", 2, results.size());
+
+ // 2
+ String log = getLogMessage(results, 0);
+ validateMessageID("SUB-1001", log);
+ // 1
+ log = getLogMessage(results, 1);
+ validateMessageID("SUB-1002", log);
+
+ String message = getMessageString(fromMessage(log));
+ assertEquals("Log message is not close", "Close", message);
+
+ }
+
+ /**
+ * Description:
+ * When a Subscription fills its prefetch it will become suspended. This
+ * will be logged as a SUB-1003 message.
+ * Input:
+ *
+ * 1. Running broker
+ * 2. Message Producer to put more data on the queue than the client's prefetch
+ * 3. Client that ensures that its prefetch becomes full
+ * Output:
+ *
+ * <date> SUB-1003 : State : <state>
+ *
+ * Validation Steps:
+ * 1. The SUB ID is correct
+ * 2. The state is correct
+ *
+ * @throws java.io.IOException - if there is a problem getting the matches
+ * @throws javax.jms.JMSException - if there is a problem creating the consumer
+ */
+ public void testSubscriptionSuspend() throws Exception, IOException
+ {
+ //Close session with large prefetch
+ _connection.createSession(false, Session.AUTO_ACKNOWLEDGE).close();
+
+ int PREFETCH = 15;
+
+ //Create new session with small prefetch
+ _session = ((AMQConnection) _connection).createSession(true, Session.SESSION_TRANSACTED, PREFETCH);
+
+ MessageConsumer consumer = _session.createConsumer(_queue);
+
+ _connection.start();
+
+ //Start the dispatcher & Unflow the channel.
+ consumer.receiveNoWait();
+
+ //Fill the prefetch and two extra so that our receive bellow allows the
+ // subscription to become active
+ // Previously we set this to 17 so that it would return to a suspended
+ // state. However, testing has shown that the state change can occur
+ // sufficiently quickly that logging does not occur consistently enough
+ // for testing.
+ int SEND_COUNT = 16;
+ sendMessage(_session, _queue, SEND_COUNT);
+ _session.commit();
+ // Retreive the first message, and start the flow of messages
+ Message msg = consumer.receive(1000);
+ assertNotNull("First message not retreived", msg);
+ _session.commit();
+
+ // Drain the queue to ensure there is time for the ACTIVE log message
+ // Check that we can received all the messages
+ int receivedCount = 0;
+ while (msg != null)
+ {
+ receivedCount++;
+ msg = consumer.receive(1000);
+ _session.commit();
+ }
+
+ //Validate we received all the messages
+ assertEquals("Not all sent messages received.", SEND_COUNT, receivedCount);
+
+ // Fill the queue again to suspend the consumer
+ sendMessage(_session, _queue, SEND_COUNT);
+ _session.commit();
+
+ //Validate
+ List<String> results = waitAndFindMatches("SUB-1003");
+
+ try
+ {
+ // Validation expects three messages.
+ // The Actor can be any one of the following depending on the exactly what is going on on the broker.
+ // Ideally we would test that we can get all of them but setting up
+ // the timing to do this in a consistent way is not benefitial.
+ // Ensuring the State is as expected is sufficient.
+// INFO - MESSAGE [vh(/test)/qu(example.queue)] [sub:6(qu(example.queue))] SUB-1003 : State :
+// INFO - MESSAGE [con:6(guest@anonymous(26562441)/test)/ch:3] [sub:6(qu(example.queue))] SUB-1003 : State :
+// INFO - MESSAGE [sub:6(vh(test)/qu(example.queue))] [sub:6(qu(example.queue))] SUB-1003 : State :
+
+ assertEquals("Result set not expected size:", 3, results.size());
+
+ // Validate Initial Suspension
+ String expectedState = "SUSPENDED";
+ String log = getLogMessage(results, 0);
+ validateSubscriptionState(log, expectedState);
+
+ // After being suspended the subscription should become active.
+ expectedState = "ACTIVE";
+ log = getLogMessage(results, 1);
+ validateSubscriptionState(log, expectedState);
+
+ // Validate that it was re-suspended
+ expectedState = "SUSPENDED";
+ log = getLogMessage(results, 2);
+ validateSubscriptionState(log, expectedState);
+ // We only need validate the state.
+ }
+ catch (AssertionFailedError afe)
+ {
+ System.err.println("Log Dump:");
+ for (String log : results)
+ {
+ System.err.println(log);
+ }
+ throw afe;
+ }
+ _connection.close();
+
+ //Ensure the queue is drained before the test ends
+ drainQueue(_queue);
+
+ }
+
+ /**
+ * Validate that the given log statement is a well formatted SUB-1003
+ * message. That means the ID and expected state are correct.
+ *
+ * @param log the log to test
+ * @param expectedState the state that should be logged.
+ */
+ private void validateSubscriptionState(String log, String expectedState)
+ {
+ validateMessageID("SUB-1003", log);
+ String logMessage = getMessageString(fromMessage(log));
+ assertTrue("Log Message does not start with 'State'" + logMessage,
+ logMessage.startsWith("State"));
+
+ assertTrue("Log Message does not have expected State of '"
+ + expectedState + "'" + logMessage,
+ logMessage.endsWith(expectedState));
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/TransientQueueLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/TransientQueueLoggingTest.java
new file mode 100644
index 0000000000..29f74c5818
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/TransientQueueLoggingTest.java
@@ -0,0 +1,30 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.logging;
+
+public class TransientQueueLoggingTest extends DurableQueueLoggingTest
+{
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ _durable = false;
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/VirtualHostLoggingTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/VirtualHostLoggingTest.java
new file mode 100644
index 0000000000..a23e40ecce
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/logging/VirtualHostLoggingTest.java
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+
+package org.apache.qpid.server.logging;
+
+import java.util.Arrays;
+import java.util.List;
+
+import junit.framework.AssertionFailedError;
+
+import org.apache.qpid.server.configuration.ServerConfiguration;
+
+/**
+ * Virtualhost Test Cases
+ * The virtualhost test suite validates that the follow log messages as specified in the Functional Specification.
+ * <p/>
+ * This suite of tests validate that the management console messages occur correctly and according to the following format:
+ * <p/>
+ * VHT-1001 : Created : <name>
+ * VHT-1002 : Work directory : <path>
+ * VHT-1003 : Closed
+ */
+public class VirtualHostLoggingTest extends AbstractTestLogging
+{
+ private static final String VHT_PREFIX = "VHT-";
+
+ /**
+ * Description:
+ * Testing can be performed using the default configuration. The goal is to validate that for each virtualhost defined in the configuration file a VHT-1001 Created message is provided.
+ * Input:
+ * The default configuration file
+ * Output:
+ * <p/>
+ * <date> VHT-1001 : Created : <name>
+ * Validation Steps:
+ * <p/>
+ * The VHT ID is correct
+ * A VHT-1001 is printed for each virtualhost defined in the configuration file.
+ * This must be the first message for the specified virtualhost.
+ *
+ * @throws Exception caused by broker startup
+ */
+ public void testVirtualhostCreation() throws Exception
+ {
+ //Wait for the correct VHT message to arrive.
+ waitForMessage(VHT_PREFIX + "1001");
+
+ //Validate each vhost logs a creation
+ List<String> results = findMatches(VHT_PREFIX + "1001");
+
+ try
+ {
+ List<String> vhosts = Arrays.asList(getServerConfig().getVirtualHosts());
+
+ assertEquals("Each vhost did not create a store.", vhosts.size(), results.size());
+
+ for (int index = 0; index < results.size(); index++)
+ {
+ // Retrieve the vhostname from the log entry message 'Created : <vhostname>'
+ String result = getLogMessage(results, index);
+ String vhostName = getMessageString(fromMessage(result)).split(" ")[2];
+
+ assertTrue("Virtualhost named in log not found in config file:" + vhostName + ":" + vhosts, vhosts.contains(vhostName));
+ }
+ }
+ catch (AssertionFailedError afe)
+ {
+ dumpLogs(results, _monitor);
+
+ throw afe;
+ }
+ }
+
+ /**
+ * Description:
+ * Testing can be performed using the default configuration. During broker shutdown a VHT-1002 Closed message will be printed for each of the configured virtualhosts. For every virtualhost that was started a close must be logged. After the close message has been printed no further logging will be performed by this virtualhost.
+ * Input:
+ * The default configuration file
+ * Output:
+ * <p/>
+ * <date> VHT-1002 : Closed
+ * Validation Steps:
+ * <p/>
+ * The VHT ID is correct
+ * This is the last VHT message for the given virtualhost.
+ *
+ * @throws Exception caused by broker startup
+ */
+ public void testVirtualhostClosure() throws Exception
+ {
+ stopBroker();
+
+ // Wait for the correct VHT message to arrive.
+ waitForMessage(VHT_PREFIX + "1002");
+
+ // Validate each vhost logs a closure
+ List<String> results = findMatches(VHT_PREFIX + "1002");
+
+ try
+ {
+ // Load VirtualHost list from file.
+ ServerConfiguration configuration = new ServerConfiguration(_configFile);
+ configuration.initialise();
+ List<String> vhosts = Arrays.asList(configuration.getVirtualHosts());
+
+ assertEquals("Each vhost did not close their store.", vhosts.size(), results.size());
+ }
+ catch (AssertionFailedError afe)
+ {
+ dumpLogs(results, _monitor);
+
+ throw afe;
+ }
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/persistent/NoLocalAfterRecoveryTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/persistent/NoLocalAfterRecoveryTest.java
new file mode 100644
index 0000000000..a5aec3edce
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/persistent/NoLocalAfterRecoveryTest.java
@@ -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.
+ *
+ */
+package org.apache.qpid.server.persistent;
+
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.jms.ConnectionListener;
+import org.apache.qpid.jms.BrokerDetails;
+import org.apache.qpid.jms.ConnectionURL;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.registry.ConfigurationFileApplicationRegistry;
+import org.apache.qpid.server.store.DerbyMessageStore;
+import org.apache.commons.configuration.XMLConfiguration;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.Session;
+import javax.jms.Topic;
+import javax.jms.TopicSubscriber;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import java.util.concurrent.CountDownLatch;
+import java.io.File;
+
+/**
+ * QPID-1813 : We do not store the client id with a message so on store restart
+ * that information is lost and we are unable to perform no local checks.
+ *
+ * QPID-1813 highlights the lack of testing here as the broker will NPE as it
+ * assumes that the client id of the publisher will always exist
+ */
+public class NoLocalAfterRecoveryTest extends QpidBrokerTestCase implements ConnectionListener
+{
+ protected final String MY_TOPIC_SUBSCRIPTION_NAME = this.getName();
+ protected static final int SEND_COUNT = 10;
+ private CountDownLatch _failoverComplete = new CountDownLatch(1);
+
+ protected ConnectionURL _connectionURL;
+
+ @Override
+ protected void setUp() throws Exception
+ {
+
+ XMLConfiguration configuration = new XMLConfiguration(_configFile);
+ configuration.setProperty("virtualhosts.virtualhost.test.store.class", "org.apache.qpid.server.store.DerbyMessageStore");
+ configuration.setProperty("virtualhosts.virtualhost.test.store."+ DerbyMessageStore.ENVIRONMENT_PATH_PROPERTY,
+ System.getProperty("QPID_WORK", System.getProperty("java.io.tmpdir")) + File.separator + "derbyDB-NoLocalAfterRecoveryTest");
+
+ File tmpFile = File.createTempFile("configFile", "test");
+ tmpFile.deleteOnExit();
+ configuration.save(tmpFile);
+
+ _configFile = tmpFile;
+ _connectionURL = getConnectionURL();
+
+ BrokerDetails details = _connectionURL.getBrokerDetails(0);
+
+ // Due to the problem with SingleServer delaying on all connection
+ // attempts. So using a high retry value.
+ if (_broker.equals(VM))
+ {
+ // Local testing suggests InVM restart takes under a second
+ details.setProperty(BrokerDetails.OPTIONS_RETRY, "5");
+ details.setProperty(BrokerDetails.OPTIONS_CONNECT_DELAY, "200");
+ }
+ else
+ {
+ // This will attempt to failover for 3 seconds.
+ // Local testing suggests failover takes 2 seconds
+ details.setProperty(BrokerDetails.OPTIONS_RETRY, "10");
+ details.setProperty(BrokerDetails.OPTIONS_CONNECT_DELAY, "500");
+ }
+
+ super.setUp();
+ }
+
+ public void test() throws Exception
+ {
+
+ Connection connection = getConnection(_connectionURL);
+ Session session = connection.createSession(true, Session.SESSION_TRANSACTED);
+
+ Topic topic = (Topic) getInitialContext().lookup("topic");
+
+ TopicSubscriber noLocalSubscriber = session.
+ createDurableSubscriber(topic, MY_TOPIC_SUBSCRIPTION_NAME + "-NoLocal",
+ null, true);
+
+ TopicSubscriber normalSubscriber = session.
+ createDurableSubscriber(topic, MY_TOPIC_SUBSCRIPTION_NAME + "-Normal",
+ null, false);
+
+ List<Message> sent = sendMessage(session, topic, SEND_COUNT);
+
+ session.commit();
+
+ assertEquals("Incorrect number of messages sent",
+ SEND_COUNT, sent.size());
+
+
+ // Check messages can be received as expected.
+ connection.start();
+
+ assertTrue("No Local Subscriber is not a no-local subscriber",
+ noLocalSubscriber.getNoLocal());
+
+ assertFalse("Normal Subscriber is a no-local subscriber",
+ normalSubscriber.getNoLocal());
+
+
+ List<Message> received = receiveMessage(noLocalSubscriber, SEND_COUNT);
+ assertEquals("No Local Subscriber Received messages", 0, received.size());
+
+ received = receiveMessage(normalSubscriber, SEND_COUNT);
+ assertEquals("Normal Subscriber Received no messages",
+ SEND_COUNT, received.size());
+
+
+ ((AMQConnection)connection).setConnectionListener(this);
+
+ restartBroker();
+
+
+ //Await
+ if (!_failoverComplete.await(4000L, TimeUnit.MILLISECONDS))
+ {
+ fail("Failover Failed to compelete");
+ }
+
+ session.rollback();
+
+ //Failover will restablish our clients
+ assertTrue("No Local Subscriber is not a no-local subscriber",
+ noLocalSubscriber.getNoLocal());
+
+ assertFalse("Normal Subscriber is a no-local subscriber",
+ normalSubscriber.getNoLocal());
+
+
+ // NOTE : here that the NO-local subscriber actually now gets ALL the
+ // messages as the connection has failed and they are consuming on a
+ // different connnection to the one that was published on.
+ received = receiveMessage(noLocalSubscriber, SEND_COUNT);
+ assertEquals("No Local Subscriber Received messages", SEND_COUNT, received.size());
+
+ received = receiveMessage(normalSubscriber, SEND_COUNT);
+ assertEquals("Normal Subscriber Received no messages",
+ SEND_COUNT, received.size());
+
+ //leave the store in a clean state.
+ session.commit();
+ }
+
+ protected List<Message> assertReceiveMessage(MessageConsumer messageConsumer,
+ int count) throws JMSException
+ {
+
+ List<Message> receivedMessages = new ArrayList<Message>(count);
+ for (int i = 0; i < count; i++)
+ {
+ Message received = messageConsumer.receive(1000);
+
+ if (received != null)
+ {
+ receivedMessages.add(received);
+ }
+ else
+ {
+ fail("Only "
+ + receivedMessages.size() + "/" + count + " received.");
+ }
+ }
+
+ return receivedMessages;
+ }
+
+ protected List<Message> receiveMessage(MessageConsumer messageConsumer,
+ int count) throws JMSException
+ {
+
+ List<Message> receivedMessages = new ArrayList<Message>(count);
+ for (int i = 0; i < count; i++)
+ {
+ Message received = messageConsumer.receive(1000);
+
+ if (received != null)
+ {
+ receivedMessages.add(received);
+ }
+ else
+ {
+ break;
+ }
+ }
+
+ return receivedMessages;
+ }
+
+ public void bytesSent(long count)
+ {
+
+ }
+
+ public void bytesReceived(long count)
+ {
+
+ }
+
+ public boolean preFailover(boolean redirect)
+ {
+ return true;
+ }
+
+ public boolean preResubscribe()
+ {
+ return true;
+ }
+
+ public void failoverComplete()
+ {
+ _failoverComplete.countDown();
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/ConflationQueueTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/ConflationQueueTest.java
new file mode 100644
index 0000000000..ae7be6f7f4
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/ConflationQueueTest.java
@@ -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.
+ *
+ */
+
+package org.apache.qpid.server.queue;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.url.AMQBindingURL;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ConflationQueueTest extends QpidBrokerTestCase
+{
+ private static final int TIMEOUT = 1500;
+
+
+ private static final Logger _logger = Logger.getLogger(ConflationQueueTest.class);
+
+
+
+ protected final String VHOST = "/test";
+ protected final String QUEUE = "ConflationQueue";
+
+ private static final int MSG_COUNT = 400;
+
+ private Connection producerConnection;
+ private MessageProducer producer;
+ private Session producerSession;
+ private Queue queue;
+ private Connection consumerConnection;
+ private Session consumerSession;
+
+
+ private MessageConsumer consumer;
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ producerConnection = getConnection();
+ producerSession = producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ producerConnection.start();
+
+
+ }
+
+ protected void tearDown() throws Exception
+ {
+ producerConnection.close();
+ consumerConnection.close();
+ super.tearDown();
+ }
+
+ public void testConflation() throws Exception
+ {
+ consumerConnection = getConnection();
+ consumerSession = consumerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+
+ final Map<String,Object> arguments = new HashMap<String, Object>();
+ arguments.put("qpid.last_value_queue_key","key");
+ ((AMQSession) producerSession).createQueue(new AMQShortString(QUEUE), false, true, false, arguments);
+ queue = new org.apache.qpid.client.AMQQueue("amq.direct",QUEUE);
+ ((AMQSession) producerSession).declareAndBind((AMQDestination)queue);
+ producer = producerSession.createProducer(queue);
+
+ for (int msg = 0; msg < MSG_COUNT; msg++)
+ {
+ producer.send(nextMessage(msg, producerSession));
+ }
+
+ producer.close();
+ producerSession.close();
+ producerConnection.close();
+
+ consumer = consumerSession.createConsumer(queue);
+ consumerConnection.start();
+ Message received;
+
+ List<Message> messages = new ArrayList<Message>();
+ while((received = consumer.receive(1000))!=null)
+ {
+ messages.add(received);
+ }
+
+ assertEquals("Unexpected number of messages received",10,messages.size());
+
+ for(int i = 0 ; i < 10; i++)
+ {
+ Message msg = messages.get(i);
+ assertEquals("Unexpected message number received", MSG_COUNT - 10 + i, msg.getIntProperty("msg"));
+ }
+
+
+ }
+
+
+ public void testConflationWithRelease() throws Exception
+ {
+ consumerConnection = getConnection();
+ consumerSession = consumerConnection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+
+
+ final Map<String,Object> arguments = new HashMap<String, Object>();
+ arguments.put("qpid.last_value_queue_key","key");
+ ((AMQSession) producerSession).createQueue(new AMQShortString(QUEUE), false, true, false, arguments);
+ queue = new org.apache.qpid.client.AMQQueue("amq.direct",QUEUE);
+ ((AMQSession) producerSession).declareAndBind((AMQDestination)queue);
+ producer = producerSession.createProducer(queue);
+
+ for (int msg = 0; msg < MSG_COUNT/2; msg++)
+ {
+ producer.send(nextMessage(msg, producerSession));
+
+ }
+
+ // HACK to do something synchronous
+ ((AMQSession)producerSession).sync();
+
+ consumer = consumerSession.createConsumer(queue);
+ consumerConnection.start();
+ Message received;
+ List<Message> messages = new ArrayList<Message>();
+ while((received = consumer.receive(1000))!=null)
+ {
+ messages.add(received);
+ }
+
+ assertEquals("Unexpected number of messages received",10,messages.size());
+
+ for(int i = 0 ; i < 10; i++)
+ {
+ Message msg = messages.get(i);
+ assertEquals("Unexpected message number received", MSG_COUNT/2 - 10 + i, msg.getIntProperty("msg"));
+ }
+
+ consumerSession.close();
+ consumerConnection.close();
+
+
+ consumerConnection = getConnection();
+ consumerSession = consumerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+
+ for (int msg = MSG_COUNT/2; msg < MSG_COUNT; msg++)
+ {
+ producer.send(nextMessage(msg, producerSession));
+ }
+
+
+ // HACK to do something synchronous
+ ((AMQSession)producerSession).sync();
+
+ consumer = consumerSession.createConsumer(queue);
+ consumerConnection.start();
+
+ messages = new ArrayList<Message>();
+ while((received = consumer.receive(1000))!=null)
+ {
+ messages.add(received);
+ }
+
+ assertEquals("Unexpected number of messages received",10,messages.size());
+
+ for(int i = 0 ; i < 10; i++)
+ {
+ Message msg = messages.get(i);
+ assertEquals("Unexpected message number received", MSG_COUNT - 10 + i, msg.getIntProperty("msg"));
+ }
+
+ }
+
+
+
+ public void testConflationWithReleaseAfterNewPublish() throws Exception
+ {
+ consumerConnection = getConnection();
+ consumerSession = consumerConnection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+
+
+ final Map<String,Object> arguments = new HashMap<String, Object>();
+ arguments.put("qpid.last_value_queue_key","key");
+ ((AMQSession) producerSession).createQueue(new AMQShortString(QUEUE), false, true, false, arguments);
+ queue = new org.apache.qpid.client.AMQQueue("amq.direct",QUEUE);
+ ((AMQSession) producerSession).declareAndBind((AMQDestination)queue);
+ producer = producerSession.createProducer(queue);
+
+ for (int msg = 0; msg < MSG_COUNT/2; msg++)
+ {
+ producer.send(nextMessage(msg, producerSession));
+ }
+
+ // HACK to do something synchronous
+ ((AMQSession)producerSession).sync();
+
+ consumer = consumerSession.createConsumer(queue);
+ consumerConnection.start();
+ Message received;
+ List<Message> messages = new ArrayList<Message>();
+ while((received = consumer.receive(1000))!=null)
+ {
+ messages.add(received);
+ }
+
+ assertEquals("Unexpected number of messages received",10,messages.size());
+
+ for(int i = 0 ; i < 10; i++)
+ {
+ Message msg = messages.get(i);
+ assertEquals("Unexpected message number received", MSG_COUNT/2 - 10 + i, msg.getIntProperty("msg"));
+ }
+
+ consumer.close();
+
+ for (int msg = MSG_COUNT/2; msg < MSG_COUNT; msg++)
+ {
+ producer.send(nextMessage(msg, producerSession));
+ }
+
+ // HACK to do something synchronous
+ ((AMQSession)producerSession).sync();
+
+
+ // this causes the "old" messages to be released
+ consumerSession.close();
+ consumerConnection.close();
+
+
+ consumerConnection = getConnection();
+ consumerSession = consumerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+
+
+ consumer = consumerSession.createConsumer(queue);
+ consumerConnection.start();
+
+ messages = new ArrayList<Message>();
+ while((received = consumer.receive(1000))!=null)
+ {
+ messages.add(received);
+ }
+
+ assertEquals("Unexpected number of messages received",10,messages.size());
+
+ for(int i = 0 ; i < 10; i++)
+ {
+ Message msg = messages.get(i);
+ assertEquals("Unexpected message number received", MSG_COUNT - 10 + i, msg.getIntProperty("msg"));
+ }
+
+ }
+
+ public void testConflationBrowser() throws Exception
+ {
+ consumerConnection = getConnection();
+ consumerSession = consumerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+
+ final Map<String,Object> arguments = new HashMap<String, Object>();
+ arguments.put("qpid.last_value_queue_key","key");
+ ((AMQSession) producerSession).createQueue(new AMQShortString(QUEUE), false, true, false, arguments);
+ queue = new org.apache.qpid.client.AMQQueue("amq.direct",QUEUE);
+ ((AMQSession) producerSession).declareAndBind((AMQDestination)queue);
+ producer = producerSession.createProducer(queue);
+
+ for (int msg = 0; msg < MSG_COUNT; msg++)
+ {
+ producer.send(nextMessage(msg, producerSession));
+
+ }
+
+ ((AMQSession)producerSession).sync();
+
+ AMQBindingURL url = new AMQBindingURL("direct://amq.direct//"+QUEUE+"?browse='true'&durable='true'");
+ AMQQueue browseQueue = new AMQQueue(url);
+
+ consumer = consumerSession.createConsumer(browseQueue);
+ consumerConnection.start();
+ Message received;
+ List<Message> messages = new ArrayList<Message>();
+ while((received = consumer.receive(1000))!=null)
+ {
+ messages.add(received);
+ }
+
+ assertEquals("Unexpected number of messages received",10,messages.size());
+
+ for(int i = 0 ; i < 10; i++)
+ {
+ Message msg = messages.get(i);
+ assertEquals("Unexpected message number received", MSG_COUNT - 10 + i, msg.getIntProperty("msg"));
+ }
+
+ messages.clear();
+
+ producer.send(nextMessage(MSG_COUNT, producerSession));
+
+ ((AMQSession)producerSession).sync();
+
+ while((received = consumer.receive(1000))!=null)
+ {
+ messages.add(received);
+ }
+ assertEquals("Unexpected number of messages received",1,messages.size());
+ assertEquals("Unexpected message number received", MSG_COUNT, messages.get(0).getIntProperty("msg"));
+
+
+ producer.close();
+ producerSession.close();
+ producerConnection.close();
+
+
+
+ }
+
+
+ public void testConflation2Browsers() throws Exception
+ {
+ consumerConnection = getConnection();
+ consumerSession = consumerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+
+ final Map<String,Object> arguments = new HashMap<String, Object>();
+ arguments.put("qpid.last_value_queue_key","key");
+ ((AMQSession) producerSession).createQueue(new AMQShortString(QUEUE), false, true, false, arguments);
+ queue = new org.apache.qpid.client.AMQQueue("amq.direct",QUEUE);
+ ((AMQSession) producerSession).declareAndBind((AMQDestination)queue);
+ producer = producerSession.createProducer(queue);
+
+ for (int msg = 0; msg < MSG_COUNT; msg++)
+ {
+ producer.send(nextMessage(msg, producerSession));
+
+ }
+
+ ((AMQSession)producerSession).sync();
+
+ AMQBindingURL url = new AMQBindingURL("direct://amq.direct//"+QUEUE+"?browse='true'&durable='true'");
+ AMQQueue browseQueue = new AMQQueue(url);
+
+ consumer = consumerSession.createConsumer(browseQueue);
+ MessageConsumer consumer2 = consumerSession.createConsumer(browseQueue);
+ consumerConnection.start();
+ List<Message> messages = new ArrayList<Message>();
+ List<Message> messages2 = new ArrayList<Message>();
+ Message received = consumer.receive(1000);
+ Message received2 = consumer2.receive(1000);
+
+ while(received!=null || received2!=null)
+ {
+ if(received != null)
+ {
+ messages.add(received);
+ }
+ if(received2 != null)
+ {
+ messages2.add(received2);
+ }
+
+
+ received = consumer.receive(1000);
+ received2 = consumer2.receive(1000);
+
+ }
+
+ assertEquals("Unexpected number of messages received on first browser",10,messages.size());
+ assertEquals("Unexpected number of messages received on second browser",10,messages2.size());
+
+ for(int i = 0 ; i < 10; i++)
+ {
+ Message msg = messages.get(i);
+ assertEquals("Unexpected message number received on first browser", MSG_COUNT - 10 + i, msg.getIntProperty("msg"));
+ msg = messages2.get(i);
+ assertEquals("Unexpected message number received on second browser", MSG_COUNT - 10 + i, msg.getIntProperty("msg"));
+ }
+
+
+ producer.close();
+ producerSession.close();
+ producerConnection.close();
+
+
+
+ }
+
+
+
+ private Message nextMessage(int msg, Session producerSession) throws JMSException
+ {
+ Message send = producerSession.createTextMessage("Message: " + msg);
+
+ send.setStringProperty("key", String.valueOf(msg % 10));
+ send.setIntProperty("msg", msg);
+
+ return send;
+ }
+
+
+}
+
+
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/DeepQueueConsumeWithSelector.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/DeepQueueConsumeWithSelector.java
new file mode 100644
index 0000000000..4ac0d2c4d6
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/DeepQueueConsumeWithSelector.java
@@ -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.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.client.AMQConnection;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageListener;
+import javax.jms.Queue;
+import javax.jms.Session;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Test DeapQueueConsumerWithSelector
+ * Summary:
+ * Prior to M4 the broker had a different queue model which pre-processed the
+ * messages on the queue for any connecting subscription that had a selector.
+ *
+ * If the queue had a lot of data then this may take a long time to process
+ * to such an extent that the subscription creation may time out. During this
+ * pre-process phase the virtualhost would be come unresposive.
+ *
+ * Our solution was to allow the timeout to be adjusted QPID-1119, which allowed
+ * the subscription to connect but did not address the unresponsiveness.
+ *
+ * The new queue model introduced in M4 resolved this.
+ *
+ * This test is to validate that the new queueing model does indeed remove the
+ * long pre-processing phase and allow immediate subscription so that there is
+ * no unresponsive period.
+ *
+ * Test Strategy:
+ *
+ * Add 100k messages to the queue with a numberic header property that will
+ * allow later subscribers to use as in a selector.
+ *
+ * Connect the subscriber and time how long it takes to connect.
+ *
+ * Finally consume all the messages from the queue to clean up.
+ */
+public class DeepQueueConsumeWithSelector extends QpidBrokerTestCase implements MessageListener
+{
+
+ private static final int MESSAGE_COUNT = 10000;
+ private static final int BATCH_SIZE = MESSAGE_COUNT / 10;
+
+ private CountDownLatch _receviedLatch = new CountDownLatch(MESSAGE_COUNT);
+
+ protected long SYNC_WRITE_TIMEOUT = 120000L;
+
+
+ public void setUp() throws Exception
+ {
+ //Set the syncWrite timeout to be just larger than the delay on the commitTran.
+ setSystemProperty("amqj.default_syncwrite_timeout", String.valueOf(SYNC_WRITE_TIMEOUT));
+
+ super.setUp();
+ }
+
+ public void test() throws Exception
+ {
+ // Create Connection
+ Connection connection = getConnection();
+ Session session = ((AMQConnection)connection).createSession(true, Session.SESSION_TRANSACTED, 100000);
+
+ Queue queue = (Queue) getInitialContext().lookup("queue");
+
+ // Validate that the destination exists
+ session.createConsumer(queue).close();
+
+ // Send Messages
+ sendMessage(session, queue, MESSAGE_COUNT, BATCH_SIZE);
+
+ session.close();
+
+ session = ((AMQConnection) connection).createSession(false, Session.AUTO_ACKNOWLEDGE);//, 100000);
+
+
+ // Setup Selector to perform a few calculations which will slow it down
+ String selector = "((\"" + INDEX + "\" % 1) = 0) AND ('" + INDEX + "' IS NOT NULL) AND ('" + INDEX + "' <> -1)";
+
+ // Setup timing
+ long start = System.nanoTime();
+
+ System.err.println("Create Consumer");
+ // Connect Consumer
+ MessageConsumer consumer = session.createConsumer(queue, selector);
+ consumer.setMessageListener(this);
+
+ // Validate timing details
+ long end = System.nanoTime();
+
+ System.err.println("Subscription time took:" + (end - start));
+
+ // Consume Messages
+ connection.start();
+
+
+
+ assertTrue("Messages took to long to be received :"+_receviedLatch.getCount(),
+ _receviedLatch.await(SYNC_WRITE_TIMEOUT, TimeUnit.MILLISECONDS ));
+
+ }
+
+ @Override
+ public Message createNextMessage(Session session, int msgCount) throws JMSException
+ {
+ Message message = super.createNextMessage(session,msgCount);
+
+ if ((msgCount % BATCH_SIZE) == 0 )
+ {
+ System.err.println("Sent:"+msgCount);
+ }
+
+ return message;
+ }
+
+ public void onMessage(Message message)
+ {
+ _receviedLatch.countDown();
+ int msgCount = 0;
+ try
+ {
+ msgCount = message.getIntProperty(INDEX);
+ }
+ catch (JMSException e)
+ {
+ //ignore
+ }
+ if ((msgCount % BATCH_SIZE) == 0 )
+ {
+ System.err.println("Received:"+msgCount);
+ }
+
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/ModelTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/ModelTest.java
new file mode 100644
index 0000000000..e3fd042560
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/ModelTest.java
@@ -0,0 +1,343 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.management.common.mbeans.ManagedBroker;
+import org.apache.qpid.management.common.mbeans.ManagedQueue;
+import org.apache.qpid.test.utils.JMXTestUtils;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Session;
+import javax.management.JMException;
+import javax.management.MBeanException;
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.UndeclaredThrowableException;
+
+/**
+ * This Test validates the Queue Model on the broker.
+ * Currently it has some basic queue creation / deletion tests.
+ * However, it should be expanded to include other tests that relate to the
+ * model. i.e.
+ *
+ * The Create and Delete tests should ensure that the requisite logging is
+ * performed.
+ *
+ * Additions to this suite would be to complete testing of creations, validating
+ * fields such as owner/exclusive, autodelete and priority are correctly set.
+ *
+ * Currently this test uses the JMX interface to validate that the queue has
+ * been declared as expected so these tests cannot run against a CPP broker.
+ *
+ *
+ * Tests should ensure that they clean up after themselves.
+ * e,g. Durable queue creation test should perform a queue delete.
+ */
+public class ModelTest extends QpidBrokerTestCase
+{
+
+ private static final String USER = "admin";
+ private JMXTestUtils _jmxUtils;
+ private static final String VIRTUALHOST_NAME = "test";
+
+ @Override
+ public void setUp() throws Exception
+ {
+ // Create a JMX Helper
+ _jmxUtils = new JMXTestUtils(this, USER, USER);
+ _jmxUtils.setUp();
+ super.setUp();
+
+ // Open the JMX Connection
+ _jmxUtils.open();
+ }
+
+ @Override
+ public void tearDown() throws Exception
+ {
+ // Close the JMX Connection
+ _jmxUtils.close();
+ super.tearDown();
+ }
+
+ /**
+ * Test that an exclusive transient queue can be created via AMQP.
+ *
+ * @throws Exception On unexpected error
+ */
+ public void testExclusiveQueueCreationTransientViaAMQP() throws Exception
+ {
+ Connection connection = getConnection();
+
+ String queueName = getTestQueueName();
+ boolean durable = false;
+ boolean autoDelete = false;
+ boolean exclusive = true;
+
+ createViaAMQPandValidateViaJMX(connection, queueName, durable,
+ autoDelete, exclusive);
+ }
+
+
+
+ /**
+ * Test that a transient queue can be created via AMQP.
+ *
+ * @throws Exception On unexpected error
+ */
+ public void testQueueCreationTransientViaAMQP() throws Exception
+ {
+ Connection connection = getConnection();
+
+ String queueName = getTestQueueName();
+ boolean durable = false;
+ boolean autoDelete = false;
+ boolean exclusive = true;
+
+ createViaAMQPandValidateViaJMX(connection, queueName, durable,
+ autoDelete, exclusive);
+ }
+
+ /**
+ * Test that a durable exclusive queue can be created via AMQP.
+ *
+ * @throws Exception On unexpected error
+ */
+
+ public void testExclusiveQueueCreationDurableViaAMQP() throws Exception
+ {
+ Connection connection = getConnection();
+
+ String queueName = getTestQueueName();
+ boolean durable = true;
+ boolean autoDelete = false;
+ boolean exclusive = true;
+
+ createViaAMQPandValidateViaJMX(connection, queueName, durable,
+ autoDelete, exclusive);
+
+ // Clean up
+ ManagedBroker managedBroker =
+ _jmxUtils.getManagedBroker(VIRTUALHOST_NAME);
+ managedBroker.deleteQueue(queueName);
+ }
+
+ /**
+ * Test that a durable queue can be created via AMQP.
+ *
+ * @throws Exception On unexpected error
+ */
+
+ public void testQueueCreationDurableViaAMQP() throws Exception
+ {
+ Connection connection = getConnection();
+
+ String queueName = getTestQueueName();
+ boolean durable = true;
+ boolean autoDelete = false;
+ boolean exclusive = false;
+
+ createViaAMQPandValidateViaJMX(connection, queueName, durable,
+ autoDelete, exclusive);
+
+ // Clean up
+ ManagedBroker managedBroker =
+ _jmxUtils.getManagedBroker(VIRTUALHOST_NAME);
+ managedBroker.deleteQueue(queueName);
+ }
+
+
+ /**
+ * Test that a transient queue can be created via JMX.
+ *
+ * @throws IOException if there is a problem via the JMX connection
+ * @throws javax.management.JMException if there is a problem with the JMX command
+ */
+ public void testCreationTransientViaJMX() throws IOException, JMException
+ {
+ String name = getName();
+ String owner = null;
+ boolean durable = false;
+
+ createViaJMXandValidateViaJMX(name, owner, durable);
+ }
+
+ /**
+ * Test that a durable queue can be created via JMX.
+ *
+ * @throws IOException if there is a problem via the JMX connection
+ * @throws javax.management.JMException if there is a problem with the JMX command
+ */
+ public void testCreationDurableViaJMX() throws IOException, JMException
+ {
+ String name = getName();
+ String owner = null;
+ boolean durable = true;
+
+ createViaJMXandValidateViaJMX(name, owner, durable);
+
+ // Clean up
+ ManagedBroker managedBroker =
+ _jmxUtils.getManagedBroker(VIRTUALHOST_NAME);
+ managedBroker.deleteQueue(name);
+ }
+
+ /**
+ * Test that a transient queue can be deleted via JMX.
+ *
+ * @throws IOException if there is a problem via the JMX connection
+ * @throws javax.management.JMException if there is a problem with the JMX command
+ */
+ public void testDeletionTransientViaJMX() throws IOException, JMException
+ {
+ String name = getName();
+
+ _jmxUtils.createQueue(VIRTUALHOST_NAME, name, null, false);
+
+ ManagedBroker managedBroker = _jmxUtils.
+ getManagedBroker(VIRTUALHOST_NAME);
+
+ try
+ {
+ managedBroker.deleteQueue(name);
+ }
+ catch (UndeclaredThrowableException e)
+ {
+ fail(((MBeanException) ((InvocationTargetException)
+ e.getUndeclaredThrowable()).getTargetException()).getTargetException().getMessage());
+ }
+ }
+
+ /**
+ * Test that a durable queue can be created via JMX.
+ *
+ * @throws IOException if there is a problem via the JMX connection
+ * @throws javax.management.JMException if there is a problem with the JMX command
+ */
+ public void testDeletionDurableViaJMX() throws IOException, JMException
+ {
+ String name = getName();
+
+ _jmxUtils.createQueue(VIRTUALHOST_NAME, name, null, true);
+
+ ManagedBroker managedBroker = _jmxUtils.
+ getManagedBroker(VIRTUALHOST_NAME);
+
+ try
+ {
+ managedBroker.deleteQueue(name);
+ }
+ catch (UndeclaredThrowableException e)
+ {
+ fail(((MBeanException) ((InvocationTargetException)
+ e.getUndeclaredThrowable()).getTargetException()).getTargetException().getMessage());
+ }
+ }
+
+ /*
+ * Helper Methods
+ */
+
+ /**
+ * Using the provided JMS Connection create a queue using the AMQP extension
+ * with the given properties and then validate it was created correctly via
+ * the JMX Connection
+ *
+ * @param connection Qpid JMS Connection
+ * @param queueName String the desired QueueName
+ * @param durable boolean if the queue should be durable
+ * @param autoDelete boolean if the queue is an autoDelete queue
+ * @param exclusive boolean if the queue is exclusive
+ *
+ * @throws AMQException if there is a problem with the createQueue call
+ * @throws JMException if there is a problem with the JMX validatation
+ * @throws IOException if there is a problem with the JMX connection
+ * @throws JMSException if there is a problem creating the JMS Session
+ */
+ private void createViaAMQPandValidateViaJMX(Connection connection,
+ String queueName,
+ boolean durable,
+ boolean autoDelete,
+ boolean exclusive)
+ throws AMQException, JMException, IOException, JMSException
+ {
+ AMQSession session = (AMQSession) connection.createSession(false,
+ Session.AUTO_ACKNOWLEDGE);
+
+ session.createQueue(new AMQShortString(queueName),
+ autoDelete, durable, exclusive);
+
+ validateQueueViaJMX(queueName, exclusive ? connection.getClientID() : null, durable, autoDelete);
+ }
+
+ /**
+ * Use the JMX Helper to create a queue with the given properties and then
+ * validate it was created correctly via the JMX Connection
+ *
+ * @param queueName String the desired QueueName
+ * @param owner String the owner value that should be set
+ * @param durable boolean if the queue should be durable
+ * @param autoDelete boolean if the queue is an autoDelete queue
+ *
+ * @throws JMException if there is a problem with the JMX validatation
+ * @throws IOException if there is a problem with the JMX connection
+ */
+ private void createViaJMXandValidateViaJMX(String queueName, String owner,
+ boolean durable)
+ throws JMException, IOException
+ {
+ _jmxUtils.createQueue(VIRTUALHOST_NAME, queueName, owner, durable);
+
+ validateQueueViaJMX(queueName, owner, durable, false);
+ }
+
+ /**
+ * Validate that a queue with the given properties exists on the broker
+ *
+ * @param queueName String the desired QueueName
+ * @param owner String the owner value that should be set
+ * @param durable boolean if the queue should be durable
+ * @param autoDelete boolean if the queue is an autoDelete queue
+ *
+ * @throws JMException if there is a problem with the JMX validatation
+ * @throws IOException if there is a problem with the JMX connection
+ */
+ private void validateQueueViaJMX(String queueName, String owner, boolean durable, boolean autoDelete)
+ throws JMException, IOException
+ {
+ ManagedQueue managedQueue = _jmxUtils.
+ getManagedObject(ManagedQueue.class,
+ _jmxUtils.getQueueObjectName(VIRTUALHOST_NAME,
+ queueName));
+
+ assertEquals(queueName, managedQueue.getName());
+ assertEquals(String.valueOf(owner), managedQueue.getOwner());
+ assertEquals(durable, managedQueue.isDurable());
+ assertEquals(autoDelete, managedQueue.isAutoDelete());
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/MultipleTransactedBatchProducerTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/MultipleTransactedBatchProducerTest.java
new file mode 100644
index 0000000000..460270e188
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/MultipleTransactedBatchProducerTest.java
@@ -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.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageListener;
+import javax.jms.Session;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+public class MultipleTransactedBatchProducerTest extends QpidBrokerTestCase
+{
+ private static final Logger _logger = Logger.getLogger(MultipleTransactedBatchProducerTest.class);
+
+ private static final int MESSAGE_COUNT = 1000;
+ private static final int BATCH_SIZE = 50;
+ private static final int NUM_PRODUCERS = 2;
+ private static final int NUM_CONSUMERS = 3;
+ private static final Random RANDOM = new Random();
+
+ private CountDownLatch _receivedLatch;
+ private String _queueName;
+
+ private volatile String _failMsg;
+
+ public void setUp() throws Exception
+ {
+ //debug level logging often makes this test pass artificially, turn the level down to info.
+ setSystemProperty("amqj.server.logging.level", "INFO");
+ _receivedLatch = new CountDownLatch(MESSAGE_COUNT * NUM_PRODUCERS);
+ setConfigurationProperty("management.enabled", "true");
+ super.setUp();
+ _queueName = getTestQueueName();
+ _failMsg = null;
+ }
+
+ /**
+ * When there are multiple producers submitting batches of messages to a given
+ * queue using transacted sessions, it is highly probable that concurrent
+ * enqueue() activity will occur and attempt delivery of their message to the
+ * same subscription. In this scenario it is likely that one of the attempts
+ * will succeed and the other will result in use of the deliverAsync() method
+ * to start a queue Runner and ensure delivery of the message.
+ *
+ * A defect within the processQueue() method used by the Runner would mean that
+ * delivery of these messages may not occur, should the Runner stop before all
+ * messages have been processed. Such a defect was discovered and found to be
+ * most visible when Selectors are used such that one and only one subscription
+ * can/will accept any given message, but multiple subscriptions are present,
+ * and one of the earlier subscriptions receives more messages than the others.
+ *
+ * This test is to validate that the processQueue() method is able to correctly
+ * deliver all of the messages present for asynchronous delivery to subscriptions,
+ * by utilising multiple batch transacted producers to create the scenario and
+ * ensure all messages are received by a consumer.
+ */
+ public void testMultipleBatchedProducersWithMultipleConsumersUsingSelectors() throws Exception
+ {
+ String selector1 = ("(\"" + _queueName +"\" % " + NUM_CONSUMERS + ") = 0");
+ String selector2 = ("(\"" + _queueName +"\" % " + NUM_CONSUMERS + ") = 1");
+ String selector3 = ("(\"" + _queueName +"\" % " + NUM_CONSUMERS + ") = 2");
+
+ //create consumers
+ Connection conn1 = getConnection();
+ conn1.setExceptionListener(new ExceptionHandler("conn1"));
+ Session sess1 = conn1.createSession(true, Session.SESSION_TRANSACTED);
+ MessageConsumer cons1 = sess1.createConsumer(sess1.createQueue(_queueName), selector1);
+ cons1.setMessageListener(new Cons(sess1,"consumer1"));
+
+ Connection conn2 = getConnection();
+ conn2.setExceptionListener(new ExceptionHandler("conn2"));
+ Session sess2 = conn2.createSession(true, Session.SESSION_TRANSACTED);
+ MessageConsumer cons2 = sess2.createConsumer(sess2.createQueue(_queueName), selector2);
+ cons2.setMessageListener(new Cons(sess2,"consumer2"));
+
+ Connection conn3 = getConnection();
+ conn3.setExceptionListener(new ExceptionHandler("conn3"));
+ Session sess3 = conn3.createSession(true, Session.SESSION_TRANSACTED);
+ MessageConsumer cons3 = sess3.createConsumer(sess3.createQueue(_queueName), selector3);
+ cons3.setMessageListener(new Cons(sess3,"consumer3"));
+
+ conn1.start();
+ conn2.start();
+ conn3.start();
+
+ //create producers
+ Connection connA = getConnection();
+ connA.setExceptionListener(new ExceptionHandler("connA"));
+ Connection connB = getConnection();
+ connB.setExceptionListener(new ExceptionHandler("connB"));
+ Thread producer1 = new Thread(new ProducerThread(connA, _queueName, "producer1"));
+ Thread producer2 = new Thread(new ProducerThread(connB, _queueName, "producer2"));
+
+ producer1.start();
+ Thread.sleep(10);
+ producer2.start();
+
+ //await delivery of the messages
+ boolean result = _receivedLatch.await(75, TimeUnit.SECONDS);
+
+ assertNull("Test failed because: " + String.valueOf(_failMsg), _failMsg);
+ assertTrue("Some of the messages were not all recieved in the given timeframe, remaining count was: "+_receivedLatch.getCount(),
+ result);
+
+ }
+
+ @Override
+ public Message createNextMessage(Session session, int msgCount) throws JMSException
+ {
+ Message message = super.createNextMessage(session,msgCount);
+
+ //bias at least 50% of the messages to the first consumers selector because
+ //the issue presents itself primarily when an earlier subscription completes
+ //delivery after the later subscriptions
+ int val;
+ if (msgCount % 2 == 0)
+ {
+ val = 0;
+ }
+ else
+ {
+ val = RANDOM.nextInt(Integer.MAX_VALUE);
+ }
+
+ message.setIntProperty(_queueName, val);
+
+ return message;
+ }
+
+ private class Cons implements MessageListener
+ {
+ private Session _sess;
+ private String _desc;
+
+ public Cons(Session sess, String desc)
+ {
+ _sess = sess;
+ _desc = desc;
+ }
+
+ public void onMessage(Message message)
+ {
+ _receivedLatch.countDown();
+ int msgCount = 0;
+ int msgID = 0;
+ try
+ {
+ msgCount = message.getIntProperty(INDEX);
+ msgID = message.getIntProperty(_queueName);
+ }
+ catch (JMSException e)
+ {
+ _logger.error(_desc + " received exception: " + e.getMessage(), e);
+ failAsyncTest(e.getMessage());
+ }
+
+ _logger.info("Consumer received message:"+ msgCount + " with ID: " + msgID);
+
+ try
+ {
+ _sess.commit();
+ }
+ catch (JMSException e)
+ {
+ _logger.error(_desc + " received exception: " + e.getMessage(), e);
+ failAsyncTest(e.getMessage());
+ }
+ }
+ }
+
+ private class ProducerThread implements Runnable
+ {
+ private Connection _conn;
+ private String _dest;
+ private String _desc;
+
+ public ProducerThread(Connection conn, String dest, String desc)
+ {
+ _conn = conn;
+ _dest = dest;
+ _desc = desc;
+ }
+
+ public void run()
+ {
+ try
+ {
+ Session session = _conn.createSession(true, Session.SESSION_TRANSACTED);
+ sendMessage(session, session.createQueue(_dest), MESSAGE_COUNT, BATCH_SIZE);
+ }
+ catch (Exception e)
+ {
+ _logger.error(_desc + " received exception: " + e.getMessage(), e);
+ failAsyncTest(e.getMessage());
+ }
+ }
+ }
+
+ private class ExceptionHandler implements javax.jms.ExceptionListener
+ {
+ private String _desc;
+
+ public ExceptionHandler(String description)
+ {
+ _desc = description;
+ }
+
+ public void onException(JMSException e)
+ {
+ _logger.error(_desc + " received exception: " + e.getMessage(), e);
+ failAsyncTest(e.getMessage());
+ }
+ }
+
+ private void failAsyncTest(String msg)
+ {
+ _logger.error("Failing test because: " + msg);
+ _failMsg = msg;
+ }
+} \ No newline at end of file
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/PersistentTestManual.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/PersistentTestManual.java
new file mode 100644
index 0000000000..c4e744573f
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/PersistentTestManual.java
@@ -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.
+ *
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQChannelClosedException;
+import org.apache.qpid.AMQConnectionClosedException;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.url.URLSyntaxException;
+import org.apache.qpid.util.CommandLineParser;
+
+import javax.jms.JMSException;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+import java.io.IOException;
+import java.util.Properties;
+
+public class PersistentTestManual
+{
+ private static final Logger _logger = Logger.getLogger(PersistentTestManual.class);
+
+
+ private static final String QUEUE = "direct://amq.direct//PersistentTest-Queue2?durable='true',exclusive='true'";
+
+ protected AMQConnection _connection;
+
+ protected Session _session;
+
+ protected Queue _queue;
+ private Properties properties;
+
+ private String _brokerDetails;
+ private String _username;
+ private String _password;
+ private String _virtualpath;
+
+ public PersistentTestManual(Properties overrides)
+ {
+ properties = new Properties(defaults);
+ properties.putAll(overrides);
+
+ _brokerDetails = properties.getProperty(BROKER_PROPNAME);
+ _username = properties.getProperty(USERNAME_PROPNAME);
+ _password = properties.getProperty(PASSWORD_PROPNAME);
+ _virtualpath = properties.getProperty(VIRTUAL_HOST_PROPNAME);
+
+ createConnection();
+ }
+
+ protected void createConnection()
+ {
+ try
+ {
+ _connection = new AMQConnection(_brokerDetails, _username, _password, "PersistentTest", _virtualpath);
+
+ _session = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ _connection.start();
+ }
+ catch (Exception e)
+ {
+ _logger.error("Unable to create test class due to:" + e.getMessage(), e);
+ System.exit(0);
+ }
+ }
+
+ public void test() throws AMQException, URLSyntaxException
+ {
+
+ //Create the Durable Queue
+ try
+ {
+ _session.createConsumer(_session.createQueue(QUEUE)).close();
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Unable to create Queue due to:" + e.getMessage(), e);
+ System.exit(0);
+ }
+
+ try
+ {
+ if (testQueue())
+ {
+ // close connection
+ _connection.close();
+ // wait
+ System.out.println("Restart Broker Now");
+ try
+ {
+ System.in.read();
+ }
+ catch (IOException e)
+ {
+ //
+ }
+ finally
+ {
+ System.out.println("Continuing....");
+ }
+
+ //Test queue is still there.
+ AMQConnection connection = new AMQConnection(_brokerDetails, _username, _password, "DifferentClientID", _virtualpath);
+
+ AMQSession session = (AMQSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ try
+ {
+ session.createConsumer(session.createQueue(QUEUE));
+ _logger.error("Create consumer succeeded." +
+ " This shouldn't be allowed as this means the queue didn't exist when it should");
+
+ connection.close();
+
+ exit();
+ }
+ catch (JMSException e)
+ {
+ try
+ {
+ connection.close();
+ }
+ catch (JMSException cce)
+ {
+ if (cce.getLinkedException() instanceof AMQConnectionClosedException)
+ {
+ _logger.error("Channel Close Bug still present QPID-432, should see an 'Error closing session'");
+ }
+ else
+ {
+ exit(cce);
+ }
+ }
+
+ if (e.getLinkedException() instanceof AMQChannelClosedException)
+ {
+ _logger.info("AMQChannelClosedException received as expected");
+ }
+ else
+ {
+ exit(e);
+ }
+ }
+ }
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Unable to test Queue due to:" + e.getMessage(), e);
+ System.exit(0);
+ }
+ }
+
+ private void exit(JMSException e)
+ {
+ _logger.error("JMSException received:" + e.getMessage());
+ e.printStackTrace();
+ exit();
+ }
+
+ private void exit()
+ {
+ try
+ {
+ _connection.close();
+ }
+ catch (JMSException e)
+ {
+ //
+ }
+ System.exit(0);
+ }
+
+ private boolean testQueue() throws JMSException
+ {
+ String TEST_TEXT = "init";
+
+ //Create a new session to send producer
+ Session session = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ Queue q = session.createQueue(QUEUE);
+ MessageProducer producer = session.createProducer(q);
+
+ producer.send(session.createTextMessage(TEST_TEXT));
+
+ //create a new consumer on the original session
+ TextMessage m = (TextMessage) _session.createConsumer(q).receive();
+
+
+ if ((m != null) && m.getText().equals(TEST_TEXT))
+ {
+ return true;
+ }
+ else
+ {
+ _logger.error("Incorrect values returned from Queue Test:" + m);
+ System.exit(0);
+ return false;
+ }
+ }
+
+ /** Holds the name of the property to get the test broker url from. */
+ public static final String BROKER_PROPNAME = "broker";
+
+ /** Holds the default broker url for the test. */
+ public static final String BROKER_DEFAULT = "tcp://localhost:5672";
+
+ /** Holds the name of the property to get the test broker virtual path. */
+ public static final String VIRTUAL_HOST_PROPNAME = "virtualHost";
+
+ /** Holds the default virtual path for the test. */
+ public static final String VIRTUAL_HOST_DEFAULT = "";
+
+ /** Holds the name of the property to get the broker access username from. */
+ public static final String USERNAME_PROPNAME = "username";
+
+ /** Holds the default broker log on username. */
+ public static final String USERNAME_DEFAULT = "guest";
+
+ /** Holds the name of the property to get the broker access password from. */
+ public static final String PASSWORD_PROPNAME = "password";
+
+ /** Holds the default broker log on password. */
+ public static final String PASSWORD_DEFAULT = "guest";
+
+ /** Holds the default configuration properties. */
+ public static Properties defaults = new Properties();
+
+ static
+ {
+ defaults.setProperty(BROKER_PROPNAME, BROKER_DEFAULT);
+ defaults.setProperty(USERNAME_PROPNAME, USERNAME_DEFAULT);
+ defaults.setProperty(PASSWORD_PROPNAME, PASSWORD_DEFAULT);
+ defaults.setProperty(VIRTUAL_HOST_PROPNAME, VIRTUAL_HOST_DEFAULT);
+ }
+
+ public static void main(String[] args)
+ {
+ PersistentTestManual test;
+
+ Properties options =
+ CommandLineParser.processCommandLine(args, new CommandLineParser(new String[][]{}), System.getProperties());
+
+ test = new PersistentTestManual(options);
+ try
+ {
+ test.test();
+ System.out.println("Test was successfull.");
+ }
+ catch (Exception e)
+ {
+ _logger.error("Unable to test due to:" + e.getMessage(), e);
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/PriorityTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/PriorityTest.java
new file mode 100644
index 0000000000..6203e8a194
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/PriorityTest.java
@@ -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.
+*
+*/
+package org.apache.qpid.server.queue;
+
+import junit.framework.TestCase;
+import junit.framework.Assert;
+import org.apache.log4j.Logger;
+import org.apache.qpid.client.transport.TransportConnection;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.jndi.PropertiesFileInitialContextFactory;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.url.URLSyntaxException;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+
+import javax.jms.*;
+import javax.naming.NamingException;
+import javax.naming.Context;
+import javax.naming.spi.InitialContextFactory;
+import java.util.Hashtable;
+import java.util.HashMap;
+import java.util.Map;
+
+public class PriorityTest extends QpidBrokerTestCase
+{
+ private static final int TIMEOUT = 1500;
+
+
+ private static final Logger _logger = Logger.getLogger(PriorityTest.class);
+
+ protected final String QUEUE = "PriorityQueue";
+
+ private static final int MSG_COUNT = 50;
+
+ private Connection producerConnection;
+ private MessageProducer producer;
+ private Session producerSession;
+ private Queue queue;
+ private Connection consumerConnection;
+ private Session consumerSession;
+
+
+ private MessageConsumer consumer;
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ producerConnection = getConnection();
+ producerSession = producerConnection.createSession(true, Session.AUTO_ACKNOWLEDGE);
+
+ producerConnection.start();
+
+ consumerConnection = getConnection();
+ consumerSession = consumerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ }
+
+ protected void tearDown() throws Exception
+ {
+ producerConnection.close();
+ consumerConnection.close();
+ super.tearDown();
+ }
+
+ public void testPriority() throws JMSException, NamingException, AMQException
+ {
+ final Map<String,Object> arguments = new HashMap<String, Object>();
+ arguments.put("x-qpid-priorities",10);
+ ((AMQSession) producerSession).createQueue(new AMQShortString(QUEUE), true, false, false, arguments);
+ queue = (Queue) producerSession.createQueue("direct://amq.direct/"+QUEUE+"/"+QUEUE+"?durable='false'&autodelete='true'");
+
+ ((AMQSession) producerSession).declareAndBind((AMQDestination)queue);
+ producer = producerSession.createProducer(queue);
+
+ for (int msg = 0; msg < MSG_COUNT; msg++)
+ {
+ producer.setPriority(msg % 10);
+ producer.send(nextMessage(msg, false, producerSession, producer));
+ }
+ producerSession.commit();
+ producer.close();
+ producerSession.close();
+ producerConnection.close();
+
+ consumer = consumerSession.createConsumer(queue);
+ consumerConnection.start();
+ Message received;
+ int receivedCount = 0;
+ Message previous = null;
+ int messageCount = 0;
+ while((received = consumer.receive(1000))!=null)
+ {
+ messageCount++;
+ if(previous != null)
+ {
+ assertTrue("Messages arrived in unexpected order " + messageCount + " " + previous.getIntProperty("msg") + " " + received.getIntProperty("msg") + " " + previous.getJMSPriority() + " " + received.getJMSPriority(), (previous.getJMSPriority() > received.getJMSPriority()) || ((previous.getJMSPriority() == received.getJMSPriority()) && previous.getIntProperty("msg") < received.getIntProperty("msg")) );
+ }
+
+ previous = received;
+ receivedCount++;
+ }
+
+ assertEquals("Incorrect number of message received", 50, receivedCount);
+ }
+
+ public void testOddOrdering() throws AMQException, JMSException
+ {
+ final Map<String,Object> arguments = new HashMap<String, Object>();
+ arguments.put("x-qpid-priorities",3);
+ ((AMQSession) producerSession).createQueue(new AMQShortString(QUEUE), true, false, false, arguments);
+ queue = producerSession.createQueue("direct://amq.direct/"+QUEUE+"/"+QUEUE+"?durable='false'&autodelete='true'");
+
+ ((AMQSession) producerSession).declareAndBind((AMQDestination)queue);
+ producer = producerSession.createProducer(queue);
+
+ // In order ABC
+ producer.setPriority(9);
+ producer.send(nextMessage(1, false, producerSession, producer));
+ producer.setPriority(4);
+ producer.send(nextMessage(2, false, producerSession, producer));
+ producer.setPriority(1);
+ producer.send(nextMessage(3, false, producerSession, producer));
+
+ // Out of order BAC
+ producer.setPriority(4);
+ producer.send(nextMessage(4, false, producerSession, producer));
+ producer.setPriority(9);
+ producer.send(nextMessage(5, false, producerSession, producer));
+ producer.setPriority(1);
+ producer.send(nextMessage(6, false, producerSession, producer));
+
+ // Out of order BCA
+ producer.setPriority(4);
+ producer.send(nextMessage(7, false, producerSession, producer));
+ producer.setPriority(1);
+ producer.send(nextMessage(8, false, producerSession, producer));
+ producer.setPriority(9);
+ producer.send(nextMessage(9, false, producerSession, producer));
+
+ // Reverse order CBA
+ producer.setPriority(1);
+ producer.send(nextMessage(10, false, producerSession, producer));
+ producer.setPriority(4);
+ producer.send(nextMessage(11, false, producerSession, producer));
+ producer.setPriority(9);
+ producer.send(nextMessage(12, false, producerSession, producer));
+ producerSession.commit();
+
+ consumer = consumerSession.createConsumer(queue);
+ consumerConnection.start();
+
+ Message msg = consumer.receive(TIMEOUT);
+ assertEquals(1, msg.getIntProperty("msg"));
+ msg = consumer.receive(TIMEOUT);
+ assertEquals(5, msg.getIntProperty("msg"));
+ msg = consumer.receive(TIMEOUT);
+ assertEquals(9, msg.getIntProperty("msg"));
+ msg = consumer.receive(TIMEOUT);
+ assertEquals(12, msg.getIntProperty("msg"));
+
+ msg = consumer.receive(TIMEOUT);
+ assertEquals(2, msg.getIntProperty("msg"));
+ msg = consumer.receive(TIMEOUT);
+ assertEquals(4, msg.getIntProperty("msg"));
+ msg = consumer.receive(TIMEOUT);
+ assertEquals(7, msg.getIntProperty("msg"));
+ msg = consumer.receive(TIMEOUT);
+ assertEquals(11, msg.getIntProperty("msg"));
+
+ msg = consumer.receive(TIMEOUT);
+ assertEquals(3, msg.getIntProperty("msg"));
+ msg = consumer.receive(TIMEOUT);
+ assertEquals(6, msg.getIntProperty("msg"));
+ msg = consumer.receive(TIMEOUT);
+ assertEquals(8, msg.getIntProperty("msg"));
+ msg = consumer.receive(TIMEOUT);
+ assertEquals(10, msg.getIntProperty("msg"));
+ }
+
+ private Message nextMessage(int msg, boolean first, Session producerSession, MessageProducer producer) throws JMSException
+ {
+ Message send = producerSession.createTextMessage("Message: " + msg);
+ send.setIntProperty("msg", msg);
+
+ return send;
+ }
+
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/ProducerFlowControlTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/ProducerFlowControlTest.java
new file mode 100644
index 0000000000..f78b327209
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/ProducerFlowControlTest.java
@@ -0,0 +1,496 @@
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+package org.apache.qpid.server.queue;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.management.common.mbeans.ManagedQueue;
+import org.apache.qpid.server.logging.AbstractTestLogging;
+import org.apache.qpid.test.utils.JMXTestUtils;
+import org.apache.qpid.framing.AMQShortString;
+
+import javax.jms.*;
+import javax.naming.NamingException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.io.IOException;
+
+public class ProducerFlowControlTest extends AbstractTestLogging
+{
+ private static final int TIMEOUT = 10000;
+
+ private static final Logger _logger = Logger.getLogger(ProducerFlowControlTest.class);
+
+ private Connection producerConnection;
+ private MessageProducer producer;
+ private Session producerSession;
+ private Queue queue;
+ private Connection consumerConnection;
+ private Session consumerSession;
+
+ private MessageConsumer consumer;
+ private final AtomicInteger _sentMessages = new AtomicInteger();
+
+ private JMXTestUtils _jmxUtils;
+ private boolean _jmxUtilConnected;
+ private static final String USER = "admin";
+
+ public void setUp() throws Exception
+ {
+ _jmxUtils = new JMXTestUtils(this, USER , USER);
+ _jmxUtils.setUp();
+ _jmxUtilConnected=false;
+ super.setUp();
+
+ _monitor.reset();
+
+ producerConnection = getConnection();
+ producerSession = producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ producerConnection.start();
+
+ consumerConnection = getConnection();
+ consumerSession = consumerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ }
+
+ public void tearDown() throws Exception
+ {
+ if(_jmxUtilConnected)
+ {
+ try
+ {
+ _jmxUtils.close();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ producerConnection.close();
+ consumerConnection.close();
+ super.tearDown();
+ }
+
+ public void testCapacityExceededCausesBlock()
+ throws JMSException, NamingException, AMQException, InterruptedException
+ {
+ String queueName = getTestQueueName();
+
+ final Map<String,Object> arguments = new HashMap<String, Object>();
+ arguments.put("x-qpid-capacity",1000);
+ arguments.put("x-qpid-flow-resume-capacity",800);
+ ((AMQSession) producerSession).createQueue(new AMQShortString(queueName), true, false, false, arguments);
+ queue = producerSession.createQueue("direct://amq.direct/"+queueName+"/"+queueName+"?durable='false'&autodelete='true'");
+ ((AMQSession) producerSession).declareAndBind((AMQDestination)queue);
+ producer = producerSession.createProducer(queue);
+
+ _sentMessages.set(0);
+
+
+ // try to send 5 messages (should block after 4)
+ sendMessagesAsync(producer, producerSession, 5, 50L);
+
+ Thread.sleep(5000);
+
+ assertEquals("Incorrect number of message sent before blocking", 4, _sentMessages.get());
+
+ consumer = consumerSession.createConsumer(queue);
+ consumerConnection.start();
+
+
+ consumer.receive();
+
+ Thread.sleep(1000);
+
+ assertEquals("Message incorrectly sent after one message received", 4, _sentMessages.get());
+
+
+ consumer.receive();
+
+ Thread.sleep(1000);
+
+ assertEquals("Message not sent after two messages received", 5, _sentMessages.get());
+
+ }
+
+ public void testBrokerLogMessages()
+ throws JMSException, NamingException, AMQException, InterruptedException, IOException
+ {
+ String queueName = getTestQueueName();
+
+ final Map<String,Object> arguments = new HashMap<String, Object>();
+ arguments.put("x-qpid-capacity",1000);
+ arguments.put("x-qpid-flow-resume-capacity",800);
+ ((AMQSession) producerSession).createQueue(new AMQShortString(queueName), true, false, false, arguments);
+ queue = producerSession.createQueue("direct://amq.direct/"+queueName+"/"+queueName+"?durable='false'&autodelete='true'");
+ ((AMQSession) producerSession).declareAndBind((AMQDestination)queue);
+ producer = producerSession.createProducer(queue);
+
+ _sentMessages.set(0);
+
+
+ // try to send 5 messages (should block after 4)
+ sendMessagesAsync(producer, producerSession, 5, 50L);
+
+ Thread.sleep(5000);
+ List<String> results = waitAndFindMatches("QUE-1003");
+
+ assertEquals("Did not find correct number of QUE-1003 queue overfull messages", 1, results.size());
+
+ consumer = consumerSession.createConsumer(queue);
+ consumerConnection.start();
+
+
+ while(consumer.receive(1000) != null);
+
+ results = waitAndFindMatches("QUE-1004");
+
+ assertEquals("Did not find correct number of UNDERFULL queue underfull messages", 1, results.size());
+
+
+
+ }
+
+
+ public void testClientLogMessages()
+ throws JMSException, NamingException, AMQException, InterruptedException, IOException
+ {
+ String queueName = getTestQueueName();
+
+ setTestClientSystemProperty("qpid.flow_control_wait_failure","3000");
+ setTestClientSystemProperty("qpid.flow_control_wait_notify_period","1000");
+
+ Session session = producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+
+ final Map<String,Object> arguments = new HashMap<String, Object>();
+ arguments.put("x-qpid-capacity",1000);
+ arguments.put("x-qpid-flow-resume-capacity",800);
+ ((AMQSession) session).createQueue(new AMQShortString(queueName), true, false, false, arguments);
+ queue = producerSession.createQueue("direct://amq.direct/"+queueName+"/"+queueName+"?durable='false'&autodelete='true'");
+ ((AMQSession) session).declareAndBind((AMQDestination)queue);
+ producer = session.createProducer(queue);
+
+ _sentMessages.set(0);
+
+
+ // try to send 5 messages (should block after 4)
+ MessageSender sender = sendMessagesAsync(producer, producerSession, 5, 50L);
+
+ Thread.sleep(TIMEOUT);
+ List<String> results = waitAndFindMatches("Message send delayed by", TIMEOUT);
+ assertTrue("No delay messages logged by client",results.size()!=0);
+ results = findMatches("Message send failed due to timeout waiting on broker enforced flow control");
+ assertEquals("Incorrect number of send failure messages logged by client",1,results.size());
+
+
+
+ }
+
+
+ public void testFlowControlOnCapacityResumeEqual()
+ throws JMSException, NamingException, AMQException, InterruptedException
+ {
+ String queueName = getTestQueueName();
+
+ final Map<String,Object> arguments = new HashMap<String, Object>();
+ arguments.put("x-qpid-capacity",1000);
+ arguments.put("x-qpid-flow-resume-capacity",1000);
+ ((AMQSession) producerSession).createQueue(new AMQShortString(queueName), true, false, false, arguments);
+ queue = producerSession.createQueue("direct://amq.direct/"+queueName+"/"+queueName+"?durable='false'&autodelete='true'");
+ ((AMQSession) producerSession).declareAndBind((AMQDestination)queue);
+ producer = producerSession.createProducer(queue);
+
+ _sentMessages.set(0);
+
+ // try to send 5 messages (should block after 4)
+ sendMessagesAsync(producer, producerSession, 5, 50L);
+
+ Thread.sleep(5000);
+
+ assertEquals("Incorrect number of message sent before blocking", 4, _sentMessages.get());
+
+ consumer = consumerSession.createConsumer(queue);
+ consumerConnection.start();
+
+
+ consumer.receive();
+
+ Thread.sleep(1000);
+
+ assertEquals("Message incorrectly sent after one message received", 5, _sentMessages.get());
+
+
+ }
+
+
+ public void testFlowControlSoak()
+ throws Exception, NamingException, AMQException, InterruptedException
+ {
+ String queueName = getTestQueueName();
+
+ _sentMessages.set(0);
+ final int numProducers = 10;
+ final int numMessages = 100;
+
+ final Map<String,Object> arguments = new HashMap<String, Object>();
+ arguments.put("x-qpid-capacity",6000);
+ arguments.put("x-qpid-flow-resume-capacity",3000);
+
+ ((AMQSession) consumerSession).createQueue(new AMQShortString(queueName), false, false, false, arguments);
+
+ queue = producerSession.createQueue("direct://amq.direct/"+queueName+"/"+queueName+"?durable='false'&autodelete='false'");
+ ((AMQSession) consumerSession).declareAndBind((AMQDestination)queue);
+ consumerConnection.start();
+
+ Connection[] producers = new Connection[numProducers];
+ for(int i = 0 ; i < numProducers; i ++)
+ {
+
+ producers[i] = getConnection();
+ producers[i].start();
+ Session session = producers[i].createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ MessageProducer myproducer = session.createProducer(queue);
+ MessageSender sender = sendMessagesAsync(myproducer, session, numMessages, 50L);
+ }
+
+ consumer = consumerSession.createConsumer(queue);
+ consumerConnection.start();
+
+ for(int j = 0; j < numProducers * numMessages; j++)
+ {
+
+ Message msg = consumer.receive(5000);
+ Thread.sleep(50L);
+ assertNotNull("Message not received("+j+"), sent: "+_sentMessages.get(), msg);
+
+ }
+
+
+
+ Message msg = consumer.receive(500);
+ assertNull("extra message received", msg);
+
+
+ for(int i = 0; i < numProducers; i++)
+ {
+ producers[i].close();
+ }
+
+ }
+
+
+
+ public void testSendTimeout()
+ throws JMSException, NamingException, AMQException, InterruptedException
+ {
+ String queueName = getTestQueueName();
+
+ setTestClientSystemProperty("qpid.flow_control_wait_failure","3000");
+ Session session = producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+
+ final Map<String,Object> arguments = new HashMap<String, Object>();
+ arguments.put("x-qpid-capacity",1000);
+ arguments.put("x-qpid-flow-resume-capacity",800);
+ ((AMQSession) session).createQueue(new AMQShortString(queueName), true, false, false, arguments);
+ queue = producerSession.createQueue("direct://amq.direct/"+queueName+"/"+queueName+"?durable='false'&autodelete='true'");
+ ((AMQSession) session).declareAndBind((AMQDestination)queue);
+ producer = session.createProducer(queue);
+
+ _sentMessages.set(0);
+
+
+ // try to send 5 messages (should block after 4)
+ MessageSender sender = sendMessagesAsync(producer, producerSession, 5, 50L);
+
+ Thread.sleep(10000);
+
+ Exception e = sender.getException();
+
+ assertNotNull("No timeout exception on sending", e);
+
+ }
+
+
+ public void testFlowControlAttributeModificationViaJMX()
+ throws JMSException, NamingException, AMQException, InterruptedException, Exception
+ {
+ _jmxUtils.open();
+ _jmxUtilConnected = true;
+
+ String queueName = getTestQueueName();
+
+ //create queue
+ final Map<String,Object> arguments = new HashMap<String, Object>();
+ arguments.put("x-qpid-capacity",0);
+ arguments.put("x-qpid-flow-resume-capacity",0);
+ ((AMQSession) producerSession).createQueue(new AMQShortString(queueName), true, false, false, arguments);
+
+ queue = producerSession.createQueue("direct://amq.direct/"+queueName+"/"+queueName+"?durable='false'&autodelete='true'");
+
+ ((AMQSession) producerSession).declareAndBind((AMQDestination)queue);
+ producer = producerSession.createProducer(queue);
+
+ Thread.sleep(1000);
+
+ //Create a JMX MBean proxy for the queue
+ ManagedQueue queueMBean = _jmxUtils.getManagedObject(ManagedQueue.class, _jmxUtils.getQueueObjectName("test", queueName));
+ assertNotNull(queueMBean);
+
+ //check current attribute values are 0 as expected
+ assertTrue("Capacity was not the expected value", queueMBean.getCapacity() == 0L);
+ assertTrue("FlowResumeCapacity was not the expected value", queueMBean.getFlowResumeCapacity() == 0L);
+
+ //set new values that will cause flow control to be active, and the queue to become overfull after 1 message is sent
+ queueMBean.setCapacity(250L);
+ queueMBean.setFlowResumeCapacity(250L);
+ assertTrue("Capacity was not the expected value", queueMBean.getCapacity() == 250L);
+ assertTrue("FlowResumeCapacity was not the expected value", queueMBean.getFlowResumeCapacity() == 250L);
+ assertFalse("Queue should not be overfull", queueMBean.isFlowOverfull());
+
+ // try to send 2 messages (should block after 1)
+ _sentMessages.set(0);
+ sendMessagesAsync(producer, producerSession, 2, 50L);
+
+ Thread.sleep(2000);
+
+ //check only 1 message was sent, and queue is overfull
+ assertEquals("Incorrect number of message sent before blocking", 1, _sentMessages.get());
+ assertTrue("Queue should be overfull", queueMBean.isFlowOverfull());
+
+ //raise the attribute values, causing the queue to become underfull and allow the second message to be sent.
+ queueMBean.setCapacity(300L);
+ queueMBean.setFlowResumeCapacity(300L);
+
+ Thread.sleep(2000);
+
+ //check second message was sent, and caused the queue to become overfull again
+ assertEquals("Second message was not sent after lifting FlowResumeCapacity", 2, _sentMessages.get());
+ assertTrue("Queue should be overfull", queueMBean.isFlowOverfull());
+
+ //raise capacity above queue depth, check queue remains overfull as FlowResumeCapacity still exceeded
+ queueMBean.setCapacity(700L);
+ assertTrue("Queue should be overfull", queueMBean.isFlowOverfull());
+
+ //receive a message, check queue becomes underfull
+
+ consumer = consumerSession.createConsumer(queue);
+ consumerConnection.start();
+
+ consumer.receive();
+
+ //perform a synchronous op on the connection
+ ((AMQSession) consumerSession).sync();
+
+ assertFalse("Queue should not be overfull", queueMBean.isFlowOverfull());
+
+ consumer.receive();
+ }
+
+ private MessageSender sendMessagesAsync(final MessageProducer producer,
+ final Session producerSession,
+ final int numMessages,
+ long sleepPeriod)
+ {
+ MessageSender sender = new MessageSender(producer, producerSession, numMessages,sleepPeriod);
+ new Thread(sender).start();
+ return sender;
+ }
+
+ private void sendMessages(MessageProducer producer, Session producerSession, int numMessages, long sleepPeriod)
+ throws JMSException
+ {
+
+ for (int msg = 0; msg < numMessages; msg++)
+ {
+ producer.send(nextMessage(msg, producerSession));
+ _sentMessages.incrementAndGet();
+
+
+ try
+ {
+ ((AMQSession)producerSession).sync();
+ }
+ catch (AMQException e)
+ {
+ e.printStackTrace();
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ private static final byte[] BYTE_300 = new byte[300];
+
+
+ private Message nextMessage(int msg, Session producerSession) throws JMSException
+ {
+ BytesMessage send = producerSession.createBytesMessage();
+ send.writeBytes(BYTE_300);
+ send.setIntProperty("msg", msg);
+
+ return send;
+ }
+
+
+ private class MessageSender implements Runnable
+ {
+ private final MessageProducer _producer;
+ private final Session _producerSession;
+ private final int _numMessages;
+
+
+
+ private JMSException _exception;
+ private long _sleepPeriod;
+
+ public MessageSender(MessageProducer producer, Session producerSession, int numMessages, long sleepPeriod)
+ {
+ _producer = producer;
+ _producerSession = producerSession;
+ _numMessages = numMessages;
+ _sleepPeriod = sleepPeriod;
+ }
+
+ public void run()
+ {
+ try
+ {
+ sendMessages(_producer, _producerSession, _numMessages, _sleepPeriod);
+ }
+ catch (JMSException e)
+ {
+ _exception = e;
+ }
+ }
+
+ public JMSException getException()
+ {
+ return _exception;
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/QueueDepthWithSelectorTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/QueueDepthWithSelectorTest.java
new file mode 100644
index 0000000000..74f50e8659
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/QueueDepthWithSelectorTest.java
@@ -0,0 +1,176 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.server.queue;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+/**
+ * Test Case to ensure that messages are correctly returned.
+ * This includes checking:
+ * - The message is returned.
+ * - The broker doesn't leak memory.
+ * - The broker's state is correct after test.
+ */
+public class QueueDepthWithSelectorTest extends QpidBrokerTestCase
+{
+ protected final String VHOST = "test";
+ protected final String QUEUE = this.getClass().getName();
+
+ protected Connection _clientConnection;
+ protected Connection _producerConnection;
+ private Session _clientSession;
+ protected Session _producerSession;
+ protected MessageProducer _producer;
+ private MessageConsumer _consumer;
+
+ protected static int MSG_COUNT = 50;
+
+ protected Message[] _messages = new Message[MSG_COUNT];
+
+ protected Queue _queue;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+
+ _messages = new Message[MSG_COUNT];
+ _queue = getTestQueue();
+
+ //Create Producer
+ _producerConnection = getConnection();
+ _producerConnection.start();
+ _producerSession = _producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ _producer = _producerSession.createProducer(_queue);
+
+ // Create consumer
+ _clientConnection = getConnection();
+ _clientConnection.start();
+ _clientSession = _clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ _consumer = _clientSession.createConsumer(_queue, "key = 23");
+ }
+
+ public void test() throws Exception
+ {
+ //Send messages
+ _logger.info("Starting to send messages");
+ for (int msg = 0; msg < MSG_COUNT; msg++)
+ {
+ _producer.send(nextMessage(msg));
+ }
+ _logger.info("Closing connection");
+ //Close the connection.. .giving the broker time to clean up its state.
+ _producerConnection.close();
+
+ //Verify we get all the messages.
+ _logger.info("Verifying messages");
+ verifyAllMessagesRecevied(50);
+ verifyBrokerState(0);
+
+ //Close the connection.. .giving the broker time to clean up its state.
+ _clientConnection.close();
+
+ //Verify Broker state
+ _logger.info("Verifying broker state");
+ verifyBrokerState(0);
+ }
+
+ protected void verifyBrokerState(int expectedDepth)
+ {
+ try
+ {
+ Connection connection = getConnection();
+ Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ Thread.sleep(2000);
+ long queueDepth = ((AMQSession) session).getQueueDepth((AMQDestination) _queue);
+ assertEquals("Session reports Queue depth not as expected", expectedDepth, queueDepth);
+
+ connection.close();
+ }
+ catch (InterruptedException e)
+ {
+ fail(e.getMessage());
+ }
+ catch (AMQException e)
+ {
+ fail(e.getMessage());
+ }
+ catch (Exception e)
+ {
+ fail(e.getMessage());
+ }
+ }
+
+ protected void verifyAllMessagesRecevied(int expectedDepth) throws Exception
+ {
+ boolean[] msgIdRecevied = new boolean[MSG_COUNT];
+
+ for (int i = 0; i < expectedDepth; i++)
+ {
+ _messages[i] = _consumer.receive(1000);
+ assertNotNull("should have received a message but didn't", _messages[i]);
+ }
+
+ //Check received messages
+ int msgId = 0;
+ for (Message msg : _messages)
+ {
+ assertNotNull("Message should not be null", msg);
+ assertEquals("msgId was wrong", msgId, msg.getIntProperty("ID"));
+ assertFalse("Already received msg id " + msgId, msgIdRecevied[msgId]);
+ msgIdRecevied[msgId] = true;
+ msgId++;
+ }
+
+ //Check all received
+ for (msgId = 0; msgId < expectedDepth; msgId++)
+ {
+ assertTrue("Message " + msgId + " not received.", msgIdRecevied[msgId]);
+ }
+ }
+
+ /**
+ * Get the next message putting the given count into the intProperties as ID.
+ *
+ * @param msgNo the message count to store as ID.
+ * @throws JMSException
+ */
+ protected Message nextMessage(int msgNo) throws JMSException
+ {
+ Message send = _producerSession.createTextMessage("MessageReturnTest");
+ send.setIntProperty("ID", msgNo);
+ send.setIntProperty("key", 23);
+ return send;
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/SubscriptionTestHelper.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/SubscriptionTestHelper.java
new file mode 100644
index 0000000000..1152797dbf
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/SubscriptionTestHelper.java
@@ -0,0 +1,294 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.queue;
+
+import org.apache.qpid.server.AMQChannel;
+import org.apache.qpid.server.logging.LogActor;
+import org.apache.qpid.server.subscription.Subscription;
+import org.apache.qpid.framing.AMQShortString;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+
+public class SubscriptionTestHelper implements Subscription
+{
+ private final List<QueueEntry> messages;
+ private final Object key;
+ private boolean isSuspended;
+ private AMQQueue.Context _queueContext;
+
+ public SubscriptionTestHelper(Object key)
+ {
+ this(key, new ArrayList<QueueEntry>());
+ }
+
+ public SubscriptionTestHelper(final Object key, final boolean isSuspended)
+ {
+ this(key);
+ setSuspended(isSuspended);
+ }
+
+ SubscriptionTestHelper(Object key, List<QueueEntry> messages)
+ {
+ this.key = key;
+ this.messages = messages;
+ }
+
+ List<QueueEntry> getMessages()
+ {
+ return messages;
+ }
+
+ public void setQueue(AMQQueue queue, boolean exclusive)
+ {
+
+ }
+
+ public void setNoLocal(boolean noLocal)
+ {
+
+ }
+
+ public void send(QueueEntry msg)
+ {
+ messages.add(msg);
+ }
+
+ public void setSuspended(boolean suspended)
+ {
+ isSuspended = suspended;
+ }
+
+ public boolean isSuspended()
+ {
+ return isSuspended;
+ }
+
+ public boolean wouldSuspend(QueueEntry msg)
+ {
+ return isSuspended;
+ }
+
+ public void addToResendQueue(QueueEntry msg)
+ {
+ //no-op
+ }
+
+ public void getSendLock()
+ {
+ return;
+ }
+
+ public void releaseSendLock()
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void resend(final QueueEntry entry)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void onDequeue(final QueueEntry queueEntry)
+ {
+
+ }
+
+ public void restoreCredit(QueueEntry queueEntry)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void setStateListener(final StateListener listener)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public State getState()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public AMQQueue.Context getQueueContext()
+ {
+ return _queueContext;
+ }
+
+ public void setQueueContext(AMQQueue.Context queueContext)
+ {
+ _queueContext = queueContext;
+ }
+
+ public boolean setLastSeenEntry(QueueEntry expected, QueueEntry newValue)
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public AMQChannel getChannel()
+ {
+ return null;
+ }
+
+ public void start()
+ {
+ //no-op
+ }
+
+ public AMQShortString getConsumerTag()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public long getSubscriptionID()
+ {
+ return 0; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isActive()
+ {
+ return false; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void confirmAutoClose()
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void set(String key, Object value)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public Object get(String key)
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public LogActor getLogActor()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean isTransient()
+ {
+ return false;
+ }
+
+ public AMQQueue getQueue()
+ {
+ return null;
+ }
+
+ public QueueEntry.SubscriptionAcquiredState getOwningState()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public QueueEntry.SubscriptionAssignedState getAssignedState()
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void queueDeleted(AMQQueue queue)
+ {
+ }
+
+ public boolean filtersMessages()
+ {
+ return false;
+ }
+
+ public boolean hasInterest(QueueEntry msg)
+ {
+ return true;
+ }
+
+ public boolean isAutoClose()
+ {
+ return false;
+ }
+
+ public Queue<QueueEntry> getPreDeliveryQueue()
+ {
+ return null;
+ }
+
+ public Queue<QueueEntry> getResendQueue()
+ {
+ return null;
+ }
+
+ public Queue<QueueEntry> getNextQueue(Queue<QueueEntry> messages)
+ {
+ return messages;
+ }
+
+ public void enqueueForPreDelivery(QueueEntry msg, boolean deliverFirst)
+ {
+ //no-op
+ }
+
+ public void close()
+ {
+ //no-op
+ }
+
+ public boolean isClosed()
+ {
+ return false;
+ }
+
+ public boolean acquires()
+ {
+ return true;
+ }
+
+ public boolean seesRequeues()
+ {
+ return true;
+ }
+
+ public boolean isBrowser()
+ {
+ return false;
+ }
+
+ public int hashCode()
+ {
+ return key.hashCode();
+ }
+
+ public boolean equals(Object o)
+ {
+ return o instanceof SubscriptionTestHelper && ((SubscriptionTestHelper) o).key.equals(key);
+ }
+
+ public String toString()
+ {
+ return key.toString();
+ }
+
+ public boolean isSessionTransactional()
+ {
+ return false;
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/TimeToLiveTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/TimeToLiveTest.java
new file mode 100644
index 0000000000..abb0781536
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/queue/TimeToLiveTest.java
@@ -0,0 +1,370 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.server.queue;
+
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.TopicSubscriber;
+
+import junit.framework.Assert;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.AMQTopic;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+public class TimeToLiveTest extends QpidBrokerTestCase
+{
+ private static final Logger _logger = Logger.getLogger(TimeToLiveTest.class);
+
+ protected final String QUEUE = "TimeToLiveQueue";
+
+ private final long TIME_TO_LIVE = 100L;
+
+ private static final int MSG_COUNT = 50;
+ private static final long SERVER_TTL_TIMEOUT = 60000L;
+
+ public void testPassiveTTL() throws Exception
+ {
+ //Create Client 1
+ Connection clientConnection = getConnection();
+
+ Session clientSession = clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ Queue queue = clientSession.createQueue(QUEUE);
+
+ // Create then close the consumer so the queue is actually created
+ // Closing it then reopening it ensures that the consumer shouldn't get messages
+ // which should have expired and allows a shorter sleep period. See QPID-1418
+
+ MessageConsumer consumer = clientSession.createConsumer(queue);
+ consumer.close();
+
+ //Create Producer
+ Connection producerConnection = getConnection();
+
+ producerConnection.start();
+
+ // Move to a Transacted session to ensure that all messages have been delivered to broker before
+ // we start waiting for TTL
+ Session producerSession = producerConnection.createSession(true, Session.SESSION_TRANSACTED);
+
+ MessageProducer producer = producerSession.createProducer(queue);
+
+ //Set TTL
+ int msg = 0;
+ producer.send(nextMessage(String.valueOf(msg), true, producerSession, producer));
+
+ producer.setTimeToLive(TIME_TO_LIVE);
+
+ for (; msg < MSG_COUNT - 2; msg++)
+ {
+ producer.send(nextMessage(String.valueOf(msg), false, producerSession, producer));
+ }
+
+ //Reset TTL
+ producer.setTimeToLive(0L);
+ producer.send(nextMessage(String.valueOf(msg), false, producerSession, producer));
+
+ producerSession.commit();
+
+ consumer = clientSession.createConsumer(queue);
+
+ // Ensure we sleep the required amount of time.
+ ReentrantLock waitLock = new ReentrantLock();
+ Condition wait = waitLock.newCondition();
+ final long MILLIS = 1000000L;
+
+ long waitTime = TIME_TO_LIVE * MILLIS;
+ while (waitTime > 0)
+ {
+ try
+ {
+ waitLock.lock();
+
+ waitTime = wait.awaitNanos(waitTime);
+ }
+ catch (InterruptedException e)
+ {
+ //Stop if we are interrupted
+ fail(e.getMessage());
+ }
+ finally
+ {
+ waitLock.unlock();
+ }
+
+ }
+
+ clientConnection.start();
+
+ //Receive Message 0
+ // Set 5s receive time for messages we expect to receive.
+ Message receivedFirst = consumer.receive(5000);
+ Message receivedSecond = consumer.receive(5000);
+ Message receivedThird = consumer.receive(1000);
+
+ // Log the messages to help diagnosis incase of failure
+ _logger.info("First:"+receivedFirst);
+ _logger.info("Second:"+receivedSecond);
+ _logger.info("Third:"+receivedThird);
+
+ // Only first and last messages sent should survive expiry
+ Assert.assertNull("More messages received", receivedThird);
+
+ Assert.assertNotNull("First message not received", receivedFirst);
+ Assert.assertTrue("First message doesn't have first set.", receivedFirst.getBooleanProperty("first"));
+ Assert.assertEquals("First message has incorrect TTL.", 0L, receivedFirst.getLongProperty("TTL"));
+
+ Assert.assertNotNull("Final message not received", receivedSecond);
+ Assert.assertFalse("Final message has first set.", receivedSecond.getBooleanProperty("first"));
+ Assert.assertEquals("Final message has incorrect TTL.", 0L, receivedSecond.getLongProperty("TTL"));
+
+ clientConnection.close();
+
+ producerConnection.close();
+ }
+
+ private Message nextMessage(String msg, boolean first, Session producerSession, MessageProducer producer) throws JMSException
+ {
+ Message send = producerSession.createTextMessage("Message " + msg);
+ send.setBooleanProperty("first", first);
+ send.setStringProperty("testprop", "TimeToLiveTest");
+ send.setLongProperty("TTL", producer.getTimeToLive());
+ return send;
+ }
+
+
+ /**
+ * Tests the expired messages get actively deleted even on queues which have no consumers
+ * @throws Exception
+ */
+ public void testActiveTTL() throws Exception
+ {
+ Connection producerConnection = getConnection();
+ AMQSession producerSession = (AMQSession) producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ Queue queue = producerSession.createTemporaryQueue();
+ producerSession.declareAndBind((AMQDestination) queue);
+ MessageProducer producer = producerSession.createProducer(queue);
+ producer.setTimeToLive(1000L);
+
+ // send Messages
+ for(int i = 0; i < MSG_COUNT; i++)
+ {
+ producer.send(producerSession.createTextMessage("Message: "+i));
+ }
+ long failureTime = System.currentTimeMillis() + 2 * SERVER_TTL_TIMEOUT;
+
+ // check Queue depth for up to TIMEOUT seconds after the Queue Depth hasn't changed for 100ms.
+ long messageCount = MSG_COUNT;
+ long lastPass;
+
+ do
+ {
+ lastPass = messageCount;
+ Thread.sleep(100);
+ messageCount = producerSession.getQueueDepth((AMQDestination) queue);
+
+ // If we have received messages in the last loop then extend the timeout time.
+ // if we get messages stuck that are not expiring then the failureTime will occur
+ // failing the test. This will help with the scenario when the broker does not
+ // have enough CPU cycles to process the TTLs.
+ if (lastPass != messageCount)
+ {
+ failureTime = System.currentTimeMillis() + 2 * SERVER_TTL_TIMEOUT;
+ }
+ }
+ while(messageCount > 0L && System.currentTimeMillis() < failureTime);
+
+ assertEquals("Messages not automatically expired: ", 0L, messageCount);
+
+ producer.close();
+ producerSession.close();
+ producerConnection.close();
+ }
+
+ public void testPassiveTTLwithDurableSubscription() throws Exception
+ {
+ //Create Client 1
+ Connection clientConnection = getConnection();
+
+ Session clientSession = clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ // Create and close the durable subscriber
+ AMQTopic topic = new AMQTopic((AMQConnection) clientConnection, getTestQueueName());
+ TopicSubscriber durableSubscriber = clientSession.createDurableSubscriber(topic, getTestQueueName(),"testprop='TimeToLiveTest'", false);
+ durableSubscriber.close();
+
+ //Create Producer
+ Connection producerConnection = getConnection();
+
+ producerConnection.start();
+
+ // Move to a Transacted session to ensure that all messages have been delivered to broker before
+ // we start waiting for TTL
+ Session producerSession = producerConnection.createSession(true, Session.SESSION_TRANSACTED);
+
+ MessageProducer producer = producerSession.createProducer(topic);
+
+ //Set TTL
+ int msg = 0;
+ producer.send(nextMessage(String.valueOf(msg), true, producerSession, producer));
+
+ producer.setTimeToLive(TIME_TO_LIVE);
+
+ for (; msg < MSG_COUNT - 2; msg++)
+ {
+ producer.send(nextMessage(String.valueOf(msg), false, producerSession, producer));
+ }
+
+ //Reset TTL
+ producer.setTimeToLive(0L);
+ producer.send(nextMessage(String.valueOf(msg), false, producerSession, producer));
+
+ producerSession.commit();
+
+ //resubscribe
+ durableSubscriber = clientSession.createDurableSubscriber(topic, getTestQueueName(),"testprop='TimeToLiveTest'", false);
+
+ // Ensure we sleep the required amount of time.
+ ReentrantLock waitLock = new ReentrantLock();
+ Condition wait = waitLock.newCondition();
+ final long MILLIS = 1000000L;
+
+ long waitTime = TIME_TO_LIVE * MILLIS;
+ while (waitTime > 0)
+ {
+ try
+ {
+ waitLock.lock();
+
+ waitTime = wait.awaitNanos(waitTime);
+ }
+ catch (InterruptedException e)
+ {
+ //Stop if we are interrupted
+ fail(e.getMessage());
+ }
+ finally
+ {
+ waitLock.unlock();
+ }
+
+ }
+
+ clientConnection.start();
+
+ //Receive Message 0
+ // Set 5s receive time for messages we expect to receive.
+ Message receivedFirst = durableSubscriber.receive(5000);
+ Message receivedSecond = durableSubscriber.receive(5000);
+ Message receivedThird = durableSubscriber.receive(1000);
+
+ // Log the messages to help diagnosis incase of failure
+ _logger.info("First:"+receivedFirst);
+ _logger.info("Second:"+receivedSecond);
+ _logger.info("Third:"+receivedThird);
+
+ // Only first and last messages sent should survive expiry
+ Assert.assertNull("More messages received", receivedThird);
+
+ Assert.assertNotNull("First message not received", receivedFirst);
+ Assert.assertTrue("First message doesn't have first set.", receivedFirst.getBooleanProperty("first"));
+ Assert.assertEquals("First message has incorrect TTL.", 0L, receivedFirst.getLongProperty("TTL"));
+
+ Assert.assertNotNull("Final message not received", receivedSecond);
+ Assert.assertFalse("Final message has first set.", receivedSecond.getBooleanProperty("first"));
+ Assert.assertEquals("Final message has incorrect TTL.", 0L, receivedSecond.getLongProperty("TTL"));
+
+ clientSession.unsubscribe(getTestQueueName());
+ clientConnection.close();
+
+ producerConnection.close();
+ }
+
+ public void testActiveTTLwithDurableSubscription() throws Exception
+ {
+ //Create Client 1
+ Connection clientConnection = getConnection();
+ Session clientSession = clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ // Create and close the durable subscriber
+ AMQTopic topic = new AMQTopic((AMQConnection) clientConnection, getTestQueueName());
+ TopicSubscriber durableSubscriber = clientSession.createDurableSubscriber(topic, "MyDurableTTLSubscription","testprop='TimeToLiveTest'", false);
+ durableSubscriber.close();
+
+ //Create Producer
+ Connection producerConnection = getConnection();
+ AMQSession producerSession = (AMQSession) producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ MessageProducer producer = producerSession.createProducer(topic);
+ producer.setTimeToLive(1000L);
+
+ // send Messages
+ for(int i = 0; i < MSG_COUNT; i++)
+ {
+ producer.send(producerSession.createTextMessage("Message: "+i));
+ }
+ long failureTime = System.currentTimeMillis() + 2 * SERVER_TTL_TIMEOUT;
+
+ // check Queue depth for up to TIMEOUT seconds after the Queue Depth hasn't changed for 100ms.
+ long messageCount = MSG_COUNT;
+ long lastPass;
+ AMQQueue subcriptionQueue = new AMQQueue("amq.topic","clientid" + ":" + "MyDurableTTLSubscription");
+ do
+ {
+ lastPass = messageCount;
+ Thread.sleep(100);
+ messageCount = producerSession.getQueueDepth((AMQDestination) subcriptionQueue);
+
+ // If we have received messages in the last loop then extend the timeout time.
+ // if we get messages stuck that are not expiring then the failureTime will occur
+ // failing the test. This will help with the scenario when the broker does not
+ // have enough CPU cycles to process the TTLs.
+ if (lastPass != messageCount)
+ {
+ failureTime = System.currentTimeMillis() + 2 * SERVER_TTL_TIMEOUT;
+ }
+ }
+ while(messageCount > 0L && System.currentTimeMillis() < failureTime);
+
+ assertEquals("Messages not automatically expired: ", 0L, messageCount);
+
+ producer.close();
+ producerSession.close();
+ producerConnection.close();
+
+ clientSession.unsubscribe("MyDurableTTLSubscription");
+ clientSession.close();
+ clientConnection.close();
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/AbstractACLTestCase.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/AbstractACLTestCase.java
new file mode 100644
index 0000000000..f845ff1214
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/AbstractACLTestCase.java
@@ -0,0 +1,285 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.qpid.server.security.acl;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.jms.Connection;
+import javax.jms.ExceptionListener;
+import javax.jms.JMSException;
+import javax.naming.NamingException;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.lang.StringUtils;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQConnectionURL;
+import org.apache.qpid.jms.ConnectionListener;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.url.URLSyntaxException;
+
+/**
+ * Abstract test case for ACLs.
+ *
+ * This base class contains convenience methods to mange ACL files and implements a mechanism that allows each
+ * test method to run its own setup code before the broker starts.
+ *
+ * TODO move the pre broker-startup setup method invocation code to {@link QpidBrokerTestCase}
+ *
+ * @see SimpleACLTest
+ * @see ExternalACLTest
+ * @see ExternalACLFileTest
+ * @see ExternalACLJMXTest
+ * @see ExternalAdminACLTest
+ * @see ExhaustiveACLTest
+ */
+public abstract class AbstractACLTestCase extends QpidBrokerTestCase implements ConnectionListener
+{
+ /** Used to synchronise {@link #tearDown()} when exceptions are thrown */
+ protected CountDownLatch _exceptionReceived;
+
+ /** Override this to return the name of the configuration XML file. */
+ public String getConfig()
+ {
+ return "config-systests-acl.xml";
+ }
+
+ /** Override this to setup external ACL files for virtual hosts. */
+ public List<String> getHostList()
+ {
+ return Collections.emptyList();
+ }
+
+ /**
+ * This setup method checks {@link #getConfig()} and {@link #getHostList()} to initialise the broker with specific
+ * ACL configurations and then runs an optional per-test setup method, which is simply a method with the same name
+ * as the test, but starting with {@code setUp} rather than {@code test}.
+ *
+ * @see #setUpACLFile(String)
+ * @see org.apache.qpid.test.utils.QpidBrokerTestCase#setUp()
+ */
+ @Override
+ public void setUp() throws Exception
+ {
+ if (QpidHome == null)
+ {
+ fail("QPID_HOME not set");
+ }
+
+ // Initialise ACLs.
+ _configFile = new File(QpidHome, "etc" + File.separator + getConfig());
+
+ // Initialise ACL files
+ for (String virtualHost : getHostList())
+ {
+ setUpACLFile(virtualHost);
+ }
+
+ // run test specific setup
+ String testSetup = StringUtils.replace(getName(), "test", "setUp");
+ try
+ {
+ Method setup = getClass().getDeclaredMethod(testSetup);
+ setup.invoke(this);
+ }
+ catch (NoSuchMethodException e)
+ {
+ // Ignore
+ }
+ catch (InvocationTargetException e)
+ {
+ throw (Exception) e.getTargetException();
+ }
+
+ super.setUp();
+ }
+
+ @Override
+ public void tearDown() throws Exception
+ {
+ try
+ {
+ super.tearDown();
+ }
+ catch (JMSException e)
+ {
+ //we're throwing this away as it can happen in this test as the state manager remembers exceptions
+ //that we provoked with authentication failures, where the test passes - we can ignore on con close
+ }
+ }
+
+ /**
+ * Configures specific ACL files for a virtual host.
+ *
+ * This method checks for ACL files that exist on the filesystem. If dynamically generatyed ACL files are required in a test,
+ * then it is easier to use the {@code setUp} prefix on a method to generate the ACL file. In order, this method looks
+ * for three files:
+ * <ol>
+ * <li><em>virtualhost</em>-<em>class</em>-<em>test</em>.txt
+ * <li><em>virtualhost</em>-<em>class</em>.txt
+ * <li><em>virtualhost</em>-default.txt
+ * </ol>
+ * The <em>class</em> and <em>test</em> parts are the test class and method names respectively, with the word {@code test}
+ * removed and the rest of the text converted to lowercase. For example, the test class and method named
+ * {@code org.apache.qpid.test.AccessExampleTest#testExampleMethod} on the {@code testhost} virtualhost would use
+ * one of the following files:
+ * <ol>
+ * <li>testhost-accessexample-examplemethod.txt
+ * <li>testhost-accessexample.txt
+ * <li>testhost-default.txt
+ * </ol>
+ * These files should be copied to the <em>${QPID_HOME}/etc</em> directory when the test is run.
+ *
+ * @see #writeACLFile(String, String...)
+ */
+ public void setUpACLFile(String virtualHost) throws IOException, ConfigurationException
+ {
+ String path = QpidHome + File.separator + "etc";
+ String className = StringUtils.substringBeforeLast(getClass().getSimpleName().toLowerCase(), "test");
+ String testName = StringUtils.substringAfter(getName(), "test").toLowerCase();
+
+ File aclFile = new File(path, virtualHost + "-" + className + "-" + testName + ".txt");
+ if (!aclFile.exists())
+ {
+ aclFile = new File(path, virtualHost + "-" + className + ".txt");
+ if (!aclFile.exists())
+ {
+ aclFile = new File(path, virtualHost + "-" + "default.txt");
+ }
+ }
+
+ // Set the ACL file configuration property
+ if (virtualHost.equals("global"))
+ {
+ setConfigurationProperty("security.aclv2", aclFile.getAbsolutePath());
+ }
+ else
+ {
+ setConfigurationProperty("virtualhosts.virtualhost." + virtualHost + ".security.aclv2", aclFile.getAbsolutePath());
+ }
+ }
+
+ public void writeACLFile(String vhost, String...rules) throws ConfigurationException, IOException
+ {
+ File aclFile = File.createTempFile(getClass().getSimpleName(), getName());
+ aclFile.deleteOnExit();
+
+ if ("global".equals(vhost))
+ {
+ setConfigurationProperty("security.aclv2", aclFile.getAbsolutePath());
+ }
+ else
+ {
+ setConfigurationProperty("virtualhosts.virtualhost." + vhost + ".security.aclv2", aclFile.getAbsolutePath());
+ }
+
+ PrintWriter out = new PrintWriter(new FileWriter(aclFile));
+ out.println(String.format("# %s", _testName));
+ for (String line : rules)
+ {
+ out.println(line);
+ }
+ out.close();
+ }
+
+ /**
+ * Creates a connection to the broker, and sets a connection listener to prevent failover and an exception listener
+ * with a {@link CountDownLatch} to synchronise in the {@link #check403Exception(Throwable)} method and allow the
+ * {@link #tearDown()} method to complete properly.
+ */
+ public Connection getConnection(String vhost, String username, String password) throws NamingException, JMSException, URLSyntaxException
+ {
+ AMQConnection connection = (AMQConnection) getConnection(createConnectionURL(vhost, username, password));
+
+ //Prevent Failover
+ connection.setConnectionListener(this);
+
+ //QPID-2081: use a latch to sync on exception causing connection close, to work
+ //around the connection close race during tearDown() causing sporadic failures
+ _exceptionReceived = new CountDownLatch(1);
+
+ connection.setExceptionListener(new ExceptionListener()
+ {
+ public void onException(JMSException e)
+ {
+ _exceptionReceived.countDown();
+ }
+ });
+
+ return (Connection) connection;
+ }
+
+ // Connection Listener Interface - Used here to block failover
+
+ public void bytesSent(long count)
+ {
+ }
+
+ public void bytesReceived(long count)
+ {
+ }
+
+ public boolean preFailover(boolean redirect)
+ {
+ //Prevent failover.
+ return false;
+ }
+
+ public boolean preResubscribe()
+ {
+ return false;
+ }
+
+ public void failoverComplete()
+ {
+ }
+
+ /**
+ * Convenience method to build an {@link AMQConnectionURL} with the right parameters.
+ */
+ public AMQConnectionURL createConnectionURL(String vhost, String username, String password) throws URLSyntaxException
+ {
+ String url = "amqp://" + username + ":" + password + "@clientid/" + vhost + "?brokerlist='" + getBroker() + "?retries='0''";
+ return new AMQConnectionURL(url);
+ }
+
+ /**
+ * Convenience method to validate a JMS exception with a linked {@link AMQConstant#ACCESS_REFUSED} 403 error code exception.
+ */
+ public void check403Exception(Throwable t) throws Exception
+ {
+ assertNotNull("There was no linked exception", t);
+ assertTrue("Wrong linked exception type", t instanceof AMQException);
+ assertEquals("Incorrect error code received", 403, ((AMQException) t).getErrorCode().getCode());
+
+ //use the latch to ensure the control thread waits long enough for the exception thread
+ //to have done enough to mark the connection closed before teardown commences
+ assertTrue("Timed out waiting for conneciton to report close", _exceptionReceived.await(2, TimeUnit.SECONDS));
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExhaustiveACLTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExhaustiveACLTest.java
new file mode 100644
index 0000000000..1b2c98d30a
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExhaustiveACLTest.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.qpid.server.security.acl;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.jms.Connection;
+import javax.jms.Session;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.protocol.AMQConstant;
+
+/**
+ * ACL version 2/3 file testing to verify that ACL entries control queue creation with specific properties.
+ *
+ * Tests have their own ACL files that setup specific permissions, and then try to create queues with every possible combination
+ * of properties to show that rule matching works correctly. For example, a rule that specified {@code autodelete="true"} for
+ * queues with {@link name="temp.true.*"} as well should not affect queues that have names that do not match, or queues that
+ * are not autodelete, or both. Also checks that ACL entries only affect the specified users and virtual hosts.
+ */
+public class ExhaustiveACLTest extends AbstractACLTestCase
+{
+ @Override
+ public String getConfig()
+ {
+ return "config-systests-aclv2.xml";
+ }
+
+ @Override
+ public List<String> getHostList()
+ {
+ return Arrays.asList("test", "test2");
+ }
+
+ /**
+ * Creates a queue.
+ *
+ * Connects to the broker as a particular user and create the named queue on a virtual host, with the provided
+ * parameters. Uses a new {@link Connection} and {@link Session} and closes them afterwards.
+ */
+ private void createQueue(String vhost, String user, String name, boolean autoDelete, boolean durable) throws Exception
+ {
+ Connection conn = getConnection(vhost, user, "guest");
+ Session sess = conn.createSession(true, Session.SESSION_TRANSACTED);
+ conn.start();
+ ((AMQSession<?, ?>) sess).createQueue(new AMQShortString(name), autoDelete, durable, false);
+ sess.commit();
+ conn.close();
+ }
+
+ /**
+ * Calls {@link #createQueue(String, String, String, boolean, boolean)} with the provided parameters and checks that
+ * no exceptions were thrown.
+ */
+ private void createQueueSuccess(String vhost, String user, String name, boolean autoDelete, boolean durable) throws Exception
+ {
+ try
+ {
+ createQueue(vhost, user, name, autoDelete, durable);
+ }
+ catch (AMQException e)
+ {
+ fail(String.format("Create queue should have worked for \"%s\" for user %s@%s, autoDelete=%s, durable=%s",
+ name, user, vhost, Boolean.toString(autoDelete), Boolean.toString(durable)));
+ }
+ }
+
+ /**
+ * Calls {@link #createQueue(String, String, String, boolean, boolean)} with the provided parameters and checks that
+ * the exception thrown was an {@link AMQConstant#ACCESS_REFUSED} or 403 error code.
+ */
+ private void createQueueFailure(String vhost, String user, String name, boolean autoDelete, boolean durable) throws Exception
+ {
+ try
+ {
+ createQueue(vhost, user, name, autoDelete, durable);
+ fail(String.format("Create queue should have failed for \"%s\" for user %s@%s, autoDelete=%s, durable=%s",
+ name, user, vhost, Boolean.toString(autoDelete), Boolean.toString(durable)));
+ }
+ catch (AMQException e)
+ {
+ assertEquals("Should be an ACCESS_REFUSED error", 403, e.getErrorCode().getCode());
+ }
+ }
+
+ public void setUpAuthoriseCreateQueueAutodelete() throws Exception
+ {
+ writeACLFile("test",
+ "acl allow client access virtualhost",
+ "acl allow server access virtualhost",
+ "acl allow client create queue name=\"temp.true.*\" autodelete=true",
+ "acl allow client create queue name=\"temp.false.*\" autodelete=false",
+ "acl deny client create queue",
+ "acl allow client delete queue",
+ "acl deny all create queue"
+ );
+ }
+
+ /**
+ * Test creation of temporary queues, with the autodelete property set to true.
+ */
+ public void testAuthoriseCreateQueueAutodelete() throws Exception
+ {
+ createQueueSuccess("test", "client", "temp.true.00", true, false);
+ createQueueSuccess("test", "client", "temp.true.01", true, false);
+ createQueueSuccess("test", "client", "temp.true.02", true, true);
+ createQueueSuccess("test", "client", "temp.false.03", false, false);
+ createQueueSuccess("test", "client", "temp.false.04", false, false);
+ createQueueSuccess("test", "client", "temp.false.05", false, true);
+ createQueueFailure("test", "client", "temp.true.06", false, false);
+ createQueueFailure("test", "client", "temp.false.07", true, false);
+ createQueueFailure("test", "server", "temp.true.08", true, false);
+ createQueueFailure("test", "client", "temp.other.09", false, false);
+ createQueueSuccess("test2", "guest", "temp.true.01", false, false);
+ createQueueSuccess("test2", "guest", "temp.false.02", true, false);
+ createQueueSuccess("test2", "guest", "temp.true.03", true, false);
+ createQueueSuccess("test2", "guest", "temp.false.04", false, false);
+ createQueueSuccess("test2", "guest", "temp.other.05", false, false);
+ }
+
+ public void setUpAuthoriseCreateQueue() throws Exception
+ {
+ writeACLFile("test",
+ "acl allow client access virtualhost",
+ "acl allow server access virtualhost",
+ "acl allow client create queue name=\"create.*\""
+ );
+ }
+
+ /**
+ * Tests creation of named queues.
+ *
+ * If a named queue is specified
+ */
+ public void testAuthoriseCreateQueue() throws Exception
+ {
+ createQueueSuccess("test", "client", "create.00", true, true);
+ createQueueSuccess("test", "client", "create.01", true, false);
+ createQueueSuccess("test", "client", "create.02", false, true);
+ createQueueSuccess("test", "client", "create.03", true, false);
+ createQueueFailure("test", "server", "create.04", true, true);
+ createQueueFailure("test", "server", "create.05", true, false);
+ createQueueFailure("test", "server", "create.06", false, true);
+ createQueueFailure("test", "server", "create.07", true, false);
+ createQueueSuccess("test2", "guest", "create.00", true, true);
+ createQueueSuccess("test2", "guest", "create.01", true, false);
+ createQueueSuccess("test2", "guest", "create.02", false, true);
+ createQueueSuccess("test2", "guest", "create.03", true, false);
+ }
+
+ public void setUpAuthoriseCreateQueueBoth() throws Exception
+ {
+ writeACLFile("test",
+ "acl allow all access virtualhost",
+ "acl allow client create queue name=\"create.*\"",
+ "acl allow all create queue temporary=true"
+ );
+ }
+
+ /**
+ * Tests creation of named queues.
+ *
+ * If a named queue is specified
+ */
+ public void testAuthoriseCreateQueueBoth() throws Exception
+ {
+ createQueueSuccess("test", "client", "create.00", true, false);
+ createQueueSuccess("test", "client", "create.01", false, false);
+ createQueueFailure("test", "server", "create.02", false, false);
+ createQueueFailure("test", "guest", "create.03", false, false);
+ createQueueSuccess("test", "client", "tmp.00", true, false);
+ createQueueSuccess("test", "server", "tmp.01", true, false);
+ createQueueSuccess("test", "guest", "tmp.02", true, false);
+ createQueueSuccess("test2", "guest", "create.02", false, false);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLFileTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLFileTest.java
new file mode 100644
index 0000000000..1d08015669
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLFileTest.java
@@ -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.
+ */
+package org.apache.qpid.server.security.acl;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.jms.Connection;
+import javax.jms.Session;
+
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.framing.AMQShortString;
+
+/**
+ * Tests that ACL version 2/3 files following the specification work correctly.
+ *
+ * ACL lines that are identical in meaning apart from differences allowed by the specification, such as whitespace or case
+ * of tokens are set up for numbered queues and the queues are then created to show that the meaning is correctly parsed by
+ * the plugin.
+ *
+ * TODO move this to the access-control plugin unit tests instead
+ */
+public class ExternalACLFileTest extends AbstractACLTestCase
+{
+ @Override
+ public String getConfig()
+ {
+ return "config-systests-aclv2.xml";
+ }
+
+ @Override
+ public List<String> getHostList()
+ {
+ return Arrays.asList("test");
+ }
+
+ private void createQueuePrefixList(String prefix, int count)
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ conn.start();
+
+ //Create n queues
+ for (int n = 0; n < count; n++)
+ {
+ AMQShortString queueName = new AMQShortString(String.format("%s.%03d", prefix, n));
+ ((AMQSession<?, ?>) sess).createQueue(queueName, false, false, false);
+ }
+
+ conn.close();
+ }
+ catch (Exception e)
+ {
+ fail("Test failed due to:" + e.getMessage());
+ }
+ }
+
+ private void createQueueNameList(String...queueNames)
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ conn.start();
+
+ //Create all queues
+ for (String queueName : queueNames)
+ {
+ ((AMQSession<?, ?>) sess).createQueue(new AMQShortString(queueName), false, false, false);
+ }
+
+ conn.close();
+ }
+ catch (Exception e)
+ {
+ fail("Test failed due to:" + e.getMessage());
+ }
+ }
+
+ public void setUpCreateQueueMixedCase() throws Exception
+ {
+ writeACLFile(
+ "test",
+ "acl allow client create queue name=mixed.000",
+ "ACL ALLOW client CREATE QUEUE NAME=mixed.001",
+ "Acl Allow client Create Queue Name=mixed.002",
+ "aCL aLLOW client cREATE qUEUE nAME=mixed.003",
+ "aCl AlLoW client cReAtE qUeUe NaMe=mixed.004"
+ );
+ }
+
+ public void testCreateQueueMixedCase()
+ {
+ createQueuePrefixList("mixed", 5);
+ }
+
+ public void setUpCreateQueueContinuation() throws Exception
+ {
+ writeACLFile(
+ "test",
+ "acl allow client create queue name=continuation.000",
+ "acl allow client create queue \\",
+ " name=continuation.001",
+ "acl allow client \\",
+ " create queue \\",
+ " name=continuation.002",
+ "acl allow \\",
+ " client \\",
+ " create queue \\",
+ " name=continuation.003",
+ "acl \\",
+ " allow \\",
+ " client \\",
+ " create queue \\",
+ " name=continuation.004"
+ );
+ }
+
+ public void testCreateQueueContinuation()
+ {
+ createQueuePrefixList("continuation", 5);
+ }
+
+ public void setUpCreateQueueWhitespace() throws Exception
+ {
+ writeACLFile(
+ "test",
+ "acl allow client create queue name=whitespace.000",
+ "acl\tallow\tclient\tcreate\tqueue\tname=whitespace.001",
+ "acl allow client create queue name = whitespace.002",
+ "acl\tallow\tclient\tcreate\tqueue\tname\t=\twhitespace.003",
+ "acl allow\t\tclient\t \tcreate\t\t queue\t \t name \t =\t \twhitespace.004"
+ );
+ }
+
+ public void testCreateQueueWhitespace()
+ {
+ createQueuePrefixList("whitespace", 5);
+ }
+
+ public void setUpCreateQueueQuoting() throws Exception
+ {
+ writeACLFile(
+ "test",
+ "acl allow client create queue name='quoting.ABC.000'",
+ "acl allow client create queue name='quoting.*.000'",
+ "acl allow client create queue name='quoting.#.000'",
+ "acl allow client create queue name='quoting. .000'",
+ "acl allow client create queue name='quoting.!@$%.000'"
+ );
+ }
+
+ public void testCreateQueueQuoting()
+ {
+ createQueueNameList(
+ "quoting.ABC.000",
+ "quoting.*.000",
+ "quoting.#.000",
+ "quoting. .000",
+ "quoting.!@$%.000"
+ );
+ }
+}
+
+
+
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLJMXTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLJMXTest.java
new file mode 100644
index 0000000000..b823690002
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLJMXTest.java
@@ -0,0 +1,244 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.qpid.server.security.acl;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.qpid.AMQConnectionClosedException;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQSecurityException;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.test.utils.JMXTestUtils;
+
+/**
+ * Tests that ACL entries that apply to AMQP objects also apply when those objects are accessed via JMX.
+ */
+public class ExternalACLJMXTest extends AbstractACLTestCase
+{
+ private JMXTestUtils _jmx;
+
+ private static final String QUEUE_NAME = "kipper";
+ private static final String EXCHANGE_NAME = "amq.kipper";
+
+ @Override
+ public String getConfig()
+ {
+ return "config-systests-aclv2.xml";
+ }
+
+ @Override
+ public List<String> getHostList()
+ {
+ return Arrays.asList("test");
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ _jmx = new JMXTestUtils(this, "admin", "admin");
+ _jmx.setUp();
+ super.setUp();
+ _jmx.open();
+ }
+
+ @Override
+ public void tearDown() throws Exception
+ {
+ _jmx.close();
+ super.tearDown();
+ }
+
+ // test-externalacljmx.txt
+ // create queue owner=client # success
+ public void testCreateClientQueueSuccess() throws Exception
+ {
+ //Queue Parameters
+ String queueOwner = "client";
+
+ _jmx.createQueue("test", QUEUE_NAME, queueOwner, true);
+ }
+
+ // test-externalacljmx.txt
+ // create queue owner=client # failure
+ public void testCreateServerQueueFailure() throws Exception
+ {
+ //Queue Parameters
+ String queueOwner = "server";
+
+ try
+ {
+ _jmx.createQueue("test", QUEUE_NAME, queueOwner, true);
+
+ fail("Queue create should fail");
+ }
+ catch (Exception e)
+ {
+ assertNotNull("Cause is not set", e.getCause());
+ assertEquals("Cause message incorrect",
+ "org.apache.qpid.AMQSecurityException: Permission denied: queue-name 'kipper' [error code 403: access refused]", e.getCause().getMessage());
+ }
+ }
+
+ // no create queue acl in file # failure
+ public void testCreateQueueFailure() throws Exception
+ {
+ //Queue Parameters
+ String queueOwner = "guest";
+
+ try
+ {
+ _jmx.createQueue("test", QUEUE_NAME, queueOwner, true);
+
+ fail("Queue create should fail");
+ }
+ catch (Exception e)
+ {
+ assertNotNull("Cause is not set", e.getCause());
+ assertEquals("Cause message incorrect",
+ "org.apache.qpid.AMQSecurityException: Permission denied: queue-name 'kipper' [error code 403: access refused]", e.getCause().getMessage());
+ }
+ }
+
+ // test-externalacljmx.txt
+ // allow create exchange name=amq.kipper.success
+ public void testCreateExchangeSuccess() throws Exception
+ {
+ _jmx.createExchange("test", EXCHANGE_NAME + ".success", "direct", true);
+ }
+
+ // test-externalacljmx.txt
+ // deny create exchange name=amq.kipper.failure
+ public void testCreateExchangeFailure() throws Exception
+ {
+ try
+ {
+ _jmx.createExchange("test", EXCHANGE_NAME + ".failure", "direct", true);
+
+ fail("Exchange create should fail");
+ }
+ catch (Exception e)
+ {
+ assertNotNull("Cause is not set", e.getCause());
+ assertEquals("Cause message incorrect",
+ "org.apache.qpid.AMQSecurityException: Permission denied: exchange-name 'amq.kipper.failure' [error code 403: access refused]", e.getCause().getMessage());
+ }
+ }
+
+ // test-externalacljmx.txt
+ // allow create exchange name=amq.kipper.success
+ // allow delete exchange name=amq.kipper.success
+ public void testDeleteExchangeSuccess() throws Exception
+ {
+ _jmx.createExchange("test", EXCHANGE_NAME + ".success", "direct", true);
+ _jmx.unregisterExchange("test", EXCHANGE_NAME + ".success");
+ }
+
+ // test-externalacljmx-deleteexchangefailure.txt
+ // allow create exchange name=amq.kipper.delete
+ // deny delete exchange name=amq.kipper.delete
+ public void testDeleteExchangeFailure() throws Exception
+ {
+ _jmx.createExchange("test", EXCHANGE_NAME + ".delete", "direct", true);
+ try
+ {
+ _jmx.unregisterExchange("test", EXCHANGE_NAME + ".delete");
+
+ fail("Exchange delete should fail");
+ }
+ catch (Exception e)
+ {
+ assertNotNull("Cause is not set", e.getCause());
+ assertEquals("Cause message incorrect",
+ "org.apache.qpid.AMQSecurityException: Permission denied [error code 403: access refused]", e.getCause().getMessage());
+ }
+ }
+
+ /**
+ * admin user has JMX right but not AMQP
+ */
+ public void setUpCreateQueueJMXRights() throws Exception
+ {
+ writeACLFile("test",
+ "ACL ALLOW admin EXECUTE METHOD component=\"VirtualHost.VirtualHostManager\" name=\"createNewQueue\"",
+ "ACL DENY admin CREATE QUEUE");
+ }
+
+ public void testCreateQueueJMXRights() throws Exception
+ {
+ try
+ {
+ _jmx.createQueue("test", QUEUE_NAME, "admin", true);
+
+ fail("Queue create should fail");
+ }
+ catch (Exception e)
+ {
+ assertNotNull("Cause is not set", e.getCause());
+ assertEquals("Cause message incorrect",
+ "org.apache.qpid.AMQSecurityException: Permission denied: queue-name 'kipper' [error code 403: access refused]", e.getCause().getMessage());
+ }
+ }
+
+ /**
+ * admin user has AMQP right but not JMX
+ */
+ public void setUpCreateQueueAMQPRights() throws Exception
+ {
+ writeACLFile("test",
+ "ACL DENY admin EXECUTE METHOD component=\"VirtualHost.VirtualHostManager\" name=\"createNewQueue\"",
+ "ACL ALLOW admin CREATE QUEUE");
+ }
+
+ public void testCreateQueueAMQPRights() throws Exception
+ {
+ try
+ {
+ _jmx.createQueue("test", QUEUE_NAME, "admin", true);
+
+ fail("Queue create should fail");
+ }
+ catch (Exception e)
+ {
+ assertEquals("Cause message incorrect", "Permission denied: Execute createNewQueue", e.getMessage());
+ }
+ }
+
+ /**
+ * admin has both JMX and AMQP rights
+ */
+ public void setUpCreateQueueJMXAMQPRights() throws Exception
+ {
+ writeACLFile("test",
+ "ACL ALLOW admin EXECUTE METHOD component=\"VirtualHost.VirtualHostManager\" name=\"createNewQueue\"",
+ "ACL ALLOW admin CREATE QUEUE");
+ }
+
+ public void testCreateQueueJMXAMQPRights() throws Exception
+ {
+ try
+ {
+ _jmx.createQueue("test", QUEUE_NAME, "admin", true);
+ }
+ catch (Exception e)
+ {
+ fail("Queue create should succeed: " + e.getCause().getMessage());
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLTest.java
new file mode 100644
index 0000000000..4603cc1862
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalACLTest.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.qpid.server.security.acl;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class ExternalACLTest extends SimpleACLTest
+{
+ @Override
+ public String getConfig()
+ {
+ return "config-systests-aclv2.xml";
+ }
+
+ @Override
+ public List<String> getHostList()
+ {
+ return Arrays.asList("test", "test2");
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalAdminACLTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalAdminACLTest.java
new file mode 100644
index 0000000000..290cbfdc14
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/ExternalAdminACLTest.java
@@ -0,0 +1,186 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.server.security.acl;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.qpid.server.logging.management.LoggingManagementMBean;
+import org.apache.qpid.test.utils.JMXTestUtils;
+
+/**
+ * Tests that ACLs can be applied to mangement operations that do not correspond to a specific AMQP object.
+ *
+ * Theses tests use the logging component, exposed as the {@link LoggingManagementMBean}, to get and set properties.
+ */
+public class ExternalAdminACLTest extends AbstractACLTestCase
+{
+ private static final String CATEGORY_PRIORITY = "LogManMBeanTest.category.priority";
+ private static final String CATEGORY_LEVEL = "LogManMBeanTest.category.level";
+ private static final String LOGGER_LEVEL = "LogManMBeanTest.logger.level";
+
+ private static final String NEWLINE = System.getProperty("line.separator");
+
+ private JMXTestUtils _jmx;
+ private File _testConfigFile;
+
+ @Override
+ public String getConfig()
+ {
+ return "config-systests-aclv2.xml";
+ }
+
+ @Override
+ public List<String> getHostList()
+ {
+ return Arrays.asList("global");
+ }
+
+ @Override
+ public void setUp() throws Exception
+ {
+ _testConfigFile = createTempTestLog4JConfig();
+
+ _jmx = new JMXTestUtils(this, "admin", "admin");
+ _jmx.setUp();
+ super.setUp();
+ _jmx.open();
+ }
+
+ @Override
+ public void tearDown() throws Exception
+ {
+ _jmx.close();
+ super.tearDown();
+ }
+
+ private File createTempTestLog4JConfig()
+ {
+ File tmpFile = null;
+ try
+ {
+ tmpFile = File.createTempFile("LogManMBeanTestLog4jConfig", ".tmp");
+ tmpFile.deleteOnExit();
+
+ FileWriter fstream = new FileWriter(tmpFile);
+ BufferedWriter writer = new BufferedWriter(fstream);
+
+ writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"+NEWLINE);
+ writer.write("<!DOCTYPE log4j:configuration SYSTEM \"log4j.dtd\">"+NEWLINE);
+
+ writer.write("<log4j:configuration xmlns:log4j=\"http://jakarta.apache.org/log4j/\" debug=\"null\" " +
+ "threshold=\"null\">"+NEWLINE);
+
+ writer.write(" <appender class=\"org.apache.log4j.ConsoleAppender\" name=\"STDOUT\">"+NEWLINE);
+ writer.write(" <layout class=\"org.apache.log4j.PatternLayout\">"+NEWLINE);
+ writer.write(" <param name=\"ConversionPattern\" value=\"%d %-5p [%t] %C{2} (%F:%L) - %m%n\"/>"+NEWLINE);
+ writer.write(" </layout>"+NEWLINE);
+ writer.write(" </appender>"+NEWLINE);
+
+ //Example of a 'category' with a 'priority'
+ writer.write(" <category additivity=\"true\" name=\"" + CATEGORY_PRIORITY +"\">"+NEWLINE);
+ writer.write(" <priority value=\"info\"/>"+NEWLINE);
+ writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE);
+ writer.write(" </category>"+NEWLINE);
+
+ //Example of a 'category' with a 'level'
+ writer.write(" <category additivity=\"true\" name=\"" + CATEGORY_LEVEL +"\">"+NEWLINE);
+ writer.write(" <level value=\"warn\"/>"+NEWLINE);
+ writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE);
+ writer.write(" </category>"+NEWLINE);
+
+ //Example of a 'logger' with a 'level'
+ writer.write(" <logger additivity=\"true\" name=\"" + LOGGER_LEVEL + "\">"+NEWLINE);
+ writer.write(" <level value=\"error\"/>"+NEWLINE);
+ writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE);
+ writer.write(" </logger>"+NEWLINE);
+
+ //'root' logger
+ writer.write(" <root>"+NEWLINE);
+ writer.write(" <priority value=\"info\"/>"+NEWLINE);
+ writer.write(" <appender-ref ref=\"STDOUT\"/>"+NEWLINE);
+ writer.write(" </root>"+NEWLINE);
+
+ writer.write("</log4j:configuration>"+NEWLINE);
+
+ writer.flush();
+ writer.close();
+ }
+ catch (IOException e)
+ {
+ fail("Unable to create temporary test log4j configuration");
+ }
+
+ return tmpFile;
+ }
+
+ public void testGetAllLoggerLevels() throws Exception
+ {
+ String[] levels = _jmx.getAvailableLoggerLevels();
+ for (int i = 0; i < levels.length; i++)
+ {
+ System.out.println(levels[i]);
+ }
+ assertEquals("Got incorrect number of log levels", 9, levels.length);
+ }
+
+ public void testGetAllLoggerLevelsDenied() throws Exception
+ {
+ try
+ {
+ _jmx.getAvailableLoggerLevels();
+ fail("Got list of log levels");
+ }
+ catch (Exception e)
+ {
+ // Exception throws
+ e.printStackTrace();
+ assertEquals("Permission denied: Access getAvailableLoggerLevels", e.getMessage());
+ }
+ }
+
+ public void testChangeLoggerLevel() throws Exception
+ {
+ String oldLevel = _jmx.getRuntimeRootLoggerLevel();
+ System.out.println("old level = " + oldLevel);
+ _jmx.setRuntimeRootLoggerLevel("DEBUG");
+ String newLevel = _jmx.getRuntimeRootLoggerLevel();
+ System.out.println("new level = " + newLevel);
+ assertEquals("Logging level was not changed", "DEBUG", newLevel);
+ }
+
+ public void testChangeLoggerLevelDenied() throws Exception
+ {
+ try
+ {
+ _jmx.setRuntimeRootLoggerLevel("DEBUG");
+ fail("Logging level was changed");
+ }
+ catch (Exception e)
+ {
+ assertEquals("Permission denied: Update setRuntimeRootLoggerLevel", e.getMessage());
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/SimpleACLTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/SimpleACLTest.java
new file mode 100644
index 0000000000..a50817e659
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/acl/SimpleACLTest.java
@@ -0,0 +1,644 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.qpid.server.security.acl;
+
+import java.io.IOException;
+
+import javax.jms.Connection;
+import javax.jms.DeliveryMode;
+import javax.jms.IllegalStateException;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+import javax.jms.Topic;
+import javax.jms.TopicSubscriber;
+import javax.naming.NamingException;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.url.URLSyntaxException;
+
+/**
+ * Basic access control list tests.
+ *
+ * These tests require an access control security plugin to be configured in the broker, and carry out various broker
+ * operations that will succeed or fail depending on the user and virtual host. See the {@code config-systests-acl-setup.xml}
+ * configuration file for the SimpleXML version of the ACLs used by the Java broker only, or the various {@code .txt}
+ * files in the system tests directory for the external version 3 ACL files used by both the Java and C++ brokers.
+ * <p>
+ * This class can be extended and the {@link #getConfig()} method overridden to run the same tests with a different type
+ * of access control mechanism. Extension classes should differ only in the configuration file used, but extra tests can be
+ * added that are specific to a particular configuration.
+ * <p>
+ * The tests perform basic AMQP operations like creating queues or excahnges and publishing and consuming messages, using
+ * JMS to contact the broker.
+ *
+ * @see ExternalACLTest
+ */
+public class SimpleACLTest extends AbstractACLTestCase
+{
+ public void testAccessAuthorizedSuccess() throws AMQException, URLSyntaxException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+ Session sess = conn.createSession(true, Session.SESSION_TRANSACTED);
+ conn.start();
+
+ //Do something to show connection is active.
+ sess.rollback();
+
+ conn.close();
+ }
+ catch (Exception e)
+ {
+ fail("Connection was not created due to:" + e);
+ }
+ }
+
+ public void testAccessVhostAuthorisedGuestSuccess() throws IOException, Exception
+ {
+ //The 'guest' user has no access to the 'test' vhost, as tested below in testAccessNoRights(), and so
+ //is unable to perform actions such as connecting (and by extension, creating a queue, and consuming
+ //from a queue etc). In order to test the vhost-wide 'access' ACL right, the 'guest' user has been given
+ //this right in the 'test2' vhost.
+
+ try
+ {
+ //get a connection to the 'test2' vhost using the guest user and perform various actions.
+ Connection conn = getConnection("test2", "guest", "guest");
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ conn.start();
+
+ //create Queues and consumers for each
+ Queue namedQueue = sess.createQueue("vhostAccessCreatedQueue" + getTestQueueName());
+ Queue tempQueue = sess.createTemporaryQueue();
+ MessageConsumer consumer = sess.createConsumer(namedQueue);
+ MessageConsumer tempConsumer = sess.createConsumer(tempQueue);
+
+ //send a message to each queue (also causing an exchange declare)
+ MessageProducer sender = ((AMQSession<?, ?>) sess).createProducer(null);
+ ((org.apache.qpid.jms.MessageProducer) sender).send(namedQueue, sess.createTextMessage("test"),
+ DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true);
+ ((org.apache.qpid.jms.MessageProducer) sender).send(tempQueue, sess.createTextMessage("test"),
+ DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true);
+
+ //consume the messages from the queues
+ consumer.receive(2000);
+ tempConsumer.receive(2000);
+
+ conn.close();
+ }
+ catch (Exception e)
+ {
+ fail("Test failed due to:" + e.getMessage());
+ }
+ }
+
+ public void testAccessNoRightsFailure() throws Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "guest", "guest");
+ Session sess = conn.createSession(true, Session.SESSION_TRANSACTED);
+ conn.start();
+ sess.rollback();
+
+ fail("Connection was created.");
+ }
+ catch (JMSException e)
+ {
+ // JMSException -> linkedException -> cause = AMQException (403 or 320)
+ Exception linkedException = e.getLinkedException();
+ assertNotNull("There was no linked exception", linkedException);
+ Throwable cause = linkedException.getCause();
+ assertNotNull("Cause was null", cause);
+ assertTrue("Wrong linked exception type", cause instanceof AMQException);
+ AMQConstant errorCode = isBroker010() ? AMQConstant.CONTEXT_IN_USE : AMQConstant.ACCESS_REFUSED;
+ assertEquals("Incorrect error code received", errorCode, ((AMQException) cause).getErrorCode());
+ }
+ }
+
+ public void testClientDeleteQueueSuccess() throws Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+ Session sess = conn.createSession(true, Session.SESSION_TRANSACTED);
+ conn.start();
+
+ // create kipper
+ Topic kipper = sess.createTopic("kipper");
+ TopicSubscriber subscriber = sess.createDurableSubscriber(kipper, "kipper");
+
+ subscriber.close();
+ sess.unsubscribe("kipper");
+
+ //Do something to show connection is active.
+ sess.rollback();
+ conn.close();
+ }
+ catch (Exception e)
+ {
+ fail("Test failed due to:" + e.getMessage());
+ }
+ }
+
+ public void testServerDeleteQueueFailure() throws Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "server", "guest");
+ Session sess = conn.createSession(true, Session.SESSION_TRANSACTED);
+ conn.start();
+
+ // create kipper
+ Topic kipper = sess.createTopic("kipper");
+ TopicSubscriber subscriber = sess.createDurableSubscriber(kipper, "kipper");
+
+ subscriber.close();
+ sess.unsubscribe("kipper");
+
+ //Do something to show connection is active.
+ sess.rollback();
+ conn.close();
+ }
+ catch (JMSException e)
+ {
+ // JMSException -> linedException = AMQException.403
+ check403Exception(e.getLinkedException());
+ }
+ }
+
+ public void testClientConsumeFromTempQueueSuccess() throws AMQException, URLSyntaxException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ sess.createConsumer(sess.createTemporaryQueue());
+
+ conn.close();
+ }
+ catch (Exception e)
+ {
+ fail("Test failed due to:" + e.getMessage());
+ }
+ }
+
+ public void testClientConsumeFromNamedQueueFailure() throws NamingException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ sess.createConsumer(sess.createQueue("IllegalQueue"));
+
+ fail("Test failed as consumer was created.");
+ }
+ catch (JMSException e)
+ {
+ check403Exception(e.getLinkedException());
+ }
+ }
+
+ public void testClientCreateTemporaryQueueSuccess() throws JMSException, URLSyntaxException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ //Create Temporary Queue - can't use the createTempQueue as QueueName is null.
+ ((AMQSession<?, ?>) sess).createQueue(new AMQShortString("doesnt_matter_as_autodelete_means_tmp"),
+ true, false, false);
+
+ conn.close();
+ }
+ catch (Exception e)
+ {
+ fail("Test failed due to:" + e.getMessage());
+ }
+ }
+
+ public void testClientCreateNamedQueueFailure() throws NamingException, JMSException, AMQException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ //Create a Named Queue
+ ((AMQSession<?, ?>) sess).createQueue(new AMQShortString("IllegalQueue"), false, false, false);
+
+ fail("Test failed as Queue creation succeded.");
+ //conn will be automatically closed
+ }
+ catch (AMQException e)
+ {
+ check403Exception(e);
+ }
+ }
+
+ public void testClientPublishUsingTransactionSuccess() throws AMQException, URLSyntaxException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+
+ Session sess = conn.createSession(true, Session.SESSION_TRANSACTED);
+
+ conn.start();
+
+ MessageProducer sender = sess.createProducer(sess.createQueue("example.RequestQueue"));
+
+ sender.send(sess.createTextMessage("test"));
+
+ //Send the message using a transaction as this will allow us to retrieve any errors that occur on the broker.
+ sess.commit();
+
+ conn.close();
+ }
+ catch (Exception e)
+ {
+ fail("Test publish failed:" + e);
+ }
+ }
+
+ public void testClientPublishValidQueueSuccess() throws AMQException, URLSyntaxException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ MessageProducer sender = ((AMQSession<?, ?>) sess).createProducer(null);
+
+ Queue queue = sess.createQueue("example.RequestQueue");
+
+ // Send a message that we will wait to be sent, this should give the broker time to process the msg
+ // before we finish this test. Message is set !immed !mand as the queue is invalid so want to test ACLs not
+ // queue existence.
+ ((org.apache.qpid.jms.MessageProducer) sender).send(queue, sess.createTextMessage("test"),
+ DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true);
+
+ conn.close();
+ }
+ catch (Exception e)
+ {
+ fail("Test publish failed:" + e);
+ }
+ }
+
+ public void testClientPublishInvalidQueueSuccess() throws AMQException, URLSyntaxException, JMSException, NamingException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+
+ Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ MessageProducer sender = ((AMQSession<?, ?>) session).createProducer(null);
+
+ Queue queue = session.createQueue("Invalid");
+
+ // Send a message that we will wait to be sent, this should give the broker time to close the connection
+ // before we finish this test. Message is set !immed !mand as the queue is invalid so want to test ACLs not
+ // queue existence.
+ ((org.apache.qpid.jms.MessageProducer) sender).send(queue, session.createTextMessage("test"),
+ DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true);
+
+ // Test the connection with a valid consumer
+ // This may fail as the session may be closed before the queue or the consumer created.
+ Queue temp = session.createTemporaryQueue();
+
+ session.createConsumer(temp).close();
+
+ //Connection should now be closed and will throw the exception caused by the above send
+ conn.close();
+
+ fail("Close is not expected to succeed.");
+ }
+ catch (IllegalStateException e)
+ {
+ _logger.info("QPID-2345: Session became closed and we got that error rather than the authentication error.");
+ }
+ catch (JMSException e)
+ {
+ check403Exception(e.getLinkedException());
+ }
+ }
+
+ public void testServerConsumeFromNamedQueueValid() throws AMQException, URLSyntaxException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "server", "guest");
+
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ sess.createConsumer(sess.createQueue("example.RequestQueue"));
+
+ conn.close();
+ }
+ catch (Exception e)
+ {
+ fail("Test failed due to:" + e.getMessage());
+ }
+ }
+
+ public void testServerConsumeFromNamedQueueInvalid() throws AMQException, URLSyntaxException, NamingException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "client", "guest");
+
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ sess.createConsumer(sess.createQueue("Invalid"));
+
+ fail("Test failed as consumer was created.");
+ }
+ catch (JMSException e)
+ {
+ check403Exception(e.getLinkedException());
+ }
+ }
+
+ public void testServerConsumeFromTemporaryQueue() throws AMQException, URLSyntaxException, NamingException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "server", "guest");
+
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ sess.createConsumer(sess.createTemporaryQueue());
+
+ fail("Test failed as consumer was created.");
+ }
+ catch (JMSException e)
+ {
+ check403Exception(e.getLinkedException());
+ }
+ }
+
+ public void testServerCreateNamedQueueValid() throws JMSException, URLSyntaxException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "server", "guest");
+
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ //Create Temporary Queue
+ ((AMQSession<?, ?>) sess).createQueue(new AMQShortString("example.RequestQueue"), false, false, false);
+
+ conn.close();
+ }
+ catch (Exception e)
+ {
+ fail("Test failed due to:" + e.getMessage());
+ }
+ }
+
+ public void testServerCreateNamedQueueInvalid() throws JMSException, URLSyntaxException, AMQException, NamingException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "server", "guest");
+
+ Session sess = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ //Create a Named Queue
+ ((AMQSession<?, ?>) sess).createQueue(new AMQShortString("IllegalQueue"), false, false, false);
+
+ fail("Test failed as creation succeded.");
+ }
+ catch (Exception e)
+ {
+ check403Exception(e);
+ }
+ }
+
+ public void testServerCreateTemporaryQueueInvalid() throws NamingException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "server", "guest");
+ Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ session.createTemporaryQueue();
+
+ fail("Test failed as creation succeded.");
+ }
+ catch (JMSException e)
+ {
+ check403Exception(e.getLinkedException());
+ }
+ }
+
+ public void testServerCreateAutoDeleteQueueInvalid() throws NamingException, JMSException, AMQException, Exception
+ {
+ try
+ {
+ Connection connection = getConnection("test", "server", "guest");
+
+ Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ connection.start();
+
+ ((AMQSession<?, ?>) session).createQueue(new AMQShortString("again_ensure_auto_delete_queue_for_temporary"),
+ true, false, false);
+
+ fail("Test failed as creation succeded.");
+ }
+ catch (Exception e)
+ {
+ check403Exception(e);
+ }
+ }
+
+ /**
+ * This test uses both the cilent and sender to validate that the Server is able to publish to a temporary queue.
+ * The reason the client must be involved is that the Server is unable to create its own Temporary Queues.
+ *
+ * @throws AMQException
+ * @throws URLSyntaxException
+ * @throws JMSException
+ */
+ public void testServerPublishUsingTransactionSuccess() throws AMQException, URLSyntaxException, JMSException, NamingException, Exception
+ {
+ //Set up the Server
+ Connection serverConnection = getConnection("test", "server", "guest");
+
+ Session serverSession = serverConnection.createSession(true, Session.SESSION_TRANSACTED);
+
+ Queue requestQueue = serverSession.createQueue("example.RequestQueue");
+
+ MessageConsumer server = serverSession.createConsumer(requestQueue);
+
+ serverConnection.start();
+
+ //Set up the consumer
+ Connection clientConnection = getConnection("test", "client", "guest");
+
+ //Send a test mesage
+ Session clientSession = clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ Queue responseQueue = clientSession.createTemporaryQueue();
+
+ MessageConsumer clientResponse = clientSession.createConsumer(responseQueue);
+
+ clientConnection.start();
+
+ Message request = clientSession.createTextMessage("Request");
+
+ assertNotNull("Response Queue is null", responseQueue);
+
+ request.setJMSReplyTo(responseQueue);
+
+ clientSession.createProducer(requestQueue).send(request);
+
+ try
+ {
+ Message msg = null;
+
+ msg = server.receive(2000);
+
+ while (msg != null && !((TextMessage) msg).getText().equals("Request"))
+ {
+ msg = server.receive(2000);
+ }
+
+ assertNotNull("Message not received", msg);
+
+ assertNotNull("Reply-To is Null", msg.getJMSReplyTo());
+
+ MessageProducer sender = serverSession.createProducer(msg.getJMSReplyTo());
+
+ sender.send(serverSession.createTextMessage("Response"));
+
+ //Send the message using a transaction as this will allow us to retrieve any errors that occur on the broker.
+ serverSession.commit();
+
+ //Ensure Response is received.
+ Message clientResponseMsg = clientResponse.receive(2000);
+ assertNotNull("Client did not receive response message,", clientResponseMsg);
+ assertEquals("Incorrect message received", "Response", ((TextMessage) clientResponseMsg).getText());
+
+ }
+ catch (Exception e)
+ {
+ fail("Test publish failed:" + e);
+ }
+ finally
+ {
+ try
+ {
+ serverConnection.close();
+ }
+ finally
+ {
+ clientConnection.close();
+ }
+ }
+ }
+
+ public void testServerPublishInvalidQueueSuccess() throws AMQException, URLSyntaxException, JMSException, NamingException, Exception
+ {
+ try
+ {
+ Connection conn = getConnection("test", "server", "guest");
+
+ ((AMQConnection) conn).setConnectionListener(this);
+
+ Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ conn.start();
+
+ MessageProducer sender = ((AMQSession<?, ?>) session).createProducer(null);
+
+ Queue queue = session.createQueue("Invalid");
+
+ // Send a message that we will wait to be sent, this should give the broker time to close the connection
+ // before we finish this test. Message is set !immed !mand as the queue is invalid so want to test ACLs not
+ // queue existence.
+ ((org.apache.qpid.jms.MessageProducer) sender).send(queue, session.createTextMessage("test"),
+ DeliveryMode.NON_PERSISTENT, 0, 0L, false, false, true);
+
+ // Test the connection with a valid consumer
+ // This may not work as the session may be closed before the queue or consumer creation can occur.
+ // The correct JMSexception with linked error will only occur when the close method is recevied whilst in
+ // the failover safe block
+ session.createConsumer(session.createQueue("example.RequestQueue")).close();
+
+ //Connection should now be closed and will throw the exception caused by the above send
+ conn.close();
+
+ fail("Close is not expected to succeed.");
+ }
+ catch (IllegalStateException e)
+ {
+ _logger.info("QPID-2345: Session became closed and we got that error rather than the authentication error.");
+ }
+ catch (JMSException e)
+ {
+ check403Exception(e.getLinkedException());
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/security/firewall/FirewallConfigTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/firewall/FirewallConfigTest.java
new file mode 100644
index 0000000000..f40e95885d
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/security/firewall/FirewallConfigTest.java
@@ -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.
+ */
+package org.apache.qpid.server.security.firewall;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+
+import org.apache.qpid.client.AMQConnectionURL;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+public class FirewallConfigTest extends QpidBrokerTestCase
+{
+ private File _tmpConfig, _tmpVirtualhosts;
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ // do setup
+ final String QPID_HOME = System.getProperty("QPID_HOME");
+
+ if (QPID_HOME == null)
+ {
+ fail("QPID_HOME not set");
+ }
+
+ // Setup initial config file.
+ _configFile = new File(QPID_HOME, "etc/config-systests-firewall.xml");
+
+ // Setup temporary config file
+ _tmpConfig = File.createTempFile("config-systests-firewall", ".xml");
+ setSystemProperty("QPID_FIREWALL_CONFIG_SETTINGS", _tmpConfig.getAbsolutePath());
+ _tmpConfig.deleteOnExit();
+
+ // Setup temporary virtualhosts file
+ _tmpVirtualhosts = File.createTempFile("virtualhosts-systests-firewall", ".xml");
+ setSystemProperty("QPID_FIREWALL_VIRTUALHOSTS_SETTINGS", _tmpVirtualhosts.getAbsolutePath());
+ _tmpVirtualhosts.deleteOnExit();
+ }
+
+ private void writeFirewallFile(boolean allow, boolean inVhost) throws IOException
+ {
+ FileWriter out = new FileWriter(inVhost ? _tmpVirtualhosts : _tmpConfig);
+ String ipAddr = "127.0.0.1"; // FIXME: get this from InetAddress.getLocalHost().getAddress() ?
+ if (inVhost)
+ {
+ out.write("<virtualhosts><virtualhost><test>");
+ }
+ else
+ {
+ out.write("<broker>");
+ }
+ out.write("<security><firewall>");
+ out.write("<rule access=\""+((allow) ? "allow" : "deny")+"\" network=\""+ipAddr +"\"/>");
+ out.write("</firewall></security>");
+ if (inVhost)
+ {
+ out.write("</test></virtualhost></virtualhosts>");
+ }
+ else
+ {
+ out.write("</broker>");
+ }
+ out.close();
+ }
+
+ public void testVhostAllowBrokerDeny() throws Exception
+ {
+ if (_broker.equals(VM))
+ {
+ //No point running this test with an InVM broker as the
+ //firewall plugin only functions for TCP connections.
+ return;
+ }
+
+ _configFile = new File(System.getProperty("QPID_HOME"), "etc/config-systests-firewall-2.xml");
+
+ super.setUp();
+
+ Connection conn = null;
+ try
+ {
+ //Try to get a connection to the 'test2' vhost
+ //This is expected to succeed as it is allowed at the vhost level
+ conn = getConnection(new AMQConnectionURL("amqp://guest:guest@clientid/test2?brokerlist='" + getBroker() + "'"));
+ }
+ catch (JMSException e)
+ {
+ e.getLinkedException().printStackTrace();
+ fail("The connection was expected to succeed: " + e.getMessage());
+ }
+
+ conn = null;
+ try
+ {
+ //Try to get a connection to the 'test' vhost
+ //This is expected to fail as it is denied at the broker level
+ conn = getConnection();
+ fail("We expected the connection to fail");
+ }
+ catch (JMSException e)
+ {
+ //ignore
+ }
+ }
+
+ public void testVhostDenyBrokerAllow() throws Exception
+ {
+ if (_broker.equals(VM))
+ {
+ //No point running this test with an InVM broker as the
+ //firewall plugin only functions for TCP connections.
+ return;
+ }
+
+ _configFile = new File(System.getProperty("QPID_HOME"), "etc/config-systests-firewall-3.xml");
+
+ super.setUp();
+
+ Connection conn = null;
+ try
+ {
+ //Try to get a connection to the 'test2' vhost
+ //This is expected to fail as it is denied at the vhost level
+ conn = getConnection(new AMQConnectionURL("amqp://guest:guest@clientid/test2?brokerlist='" + getBroker() + "'"));
+ fail("The connection was expected to fail");
+ }
+ catch (JMSException e)
+ {
+ //ignore
+ }
+
+ conn = null;
+ try
+ {
+ //Try to get a connection to the 'test' vhost
+ //This is expected to succeed as it is allowed at the broker level
+ conn = getConnection();
+ }
+ catch (JMSException e)
+ {
+ e.getLinkedException().printStackTrace();
+ fail("The connection was expected to succeed: " + e.getMessage());
+ }
+ }
+
+ public void testDenyOnRestart() throws Exception
+ {
+ testDeny(false, new Runnable() {
+
+ public void run()
+ {
+ try
+ {
+ restartBroker();
+ } catch (Exception e)
+ {
+ fail(e.getMessage());
+ }
+ }
+ });
+ }
+
+ public void testDenyOnRestartInVhost() throws Exception
+ {
+ testDeny(true, new Runnable() {
+
+ public void run()
+ {
+ try
+ {
+ restartBroker();
+ } catch (Exception e)
+ {
+ fail(e.getMessage());
+ }
+ }
+ });
+ }
+
+ public void testAllowOnReloadInVhost() throws Exception
+ {
+ testFirewall(false, true, new Runnable() {
+
+ public void run()
+ {
+ try
+ {
+ reloadBrokerSecurityConfig();
+ } catch (Exception e)
+ {
+ fail(e.getMessage());
+ }
+ }
+ });
+ }
+
+ public void testDenyOnReload() throws Exception
+ {
+ testDeny(false, new Runnable() {
+
+ public void run()
+ {
+ try
+ {
+ reloadBrokerSecurityConfig();
+ } catch (Exception e)
+ {
+ fail(e.getMessage());
+ }
+ }
+ }
+ );
+ }
+
+ public void testDenyOnReloadInVhost() throws Exception
+ {
+ testDeny(true, new Runnable() {
+
+ public void run()
+ {
+ try
+ {
+ reloadBrokerSecurityConfig();
+ } catch (Exception e)
+ {
+ fail(e.getMessage());
+ }
+ }
+ }
+ );
+
+ }
+
+ private void testDeny(boolean inVhost, Runnable restartOrReload) throws Exception
+ {
+ testFirewall(true, inVhost, restartOrReload);
+ }
+
+ /*
+ * Check we can get a connection
+ */
+ private boolean checkConnection() throws Exception
+ {
+ Exception exception = null;
+ Connection conn = null;
+ try
+ {
+ conn = getConnection();
+ }
+ catch (JMSException e)
+ {
+ exception = e;
+ }
+
+ return conn != null;
+ }
+
+ private void testFirewall(boolean initial, boolean inVhost, Runnable restartOrReload) throws Exception
+ {
+ if (_broker.equals(VM))
+ {
+ // No point running this test in a vm broker
+ return;
+ }
+
+ writeFirewallFile(initial, inVhost);
+ setConfigurationProperty("management.enabled", String.valueOf(true));
+ super.setUp();
+
+ assertEquals("Initial connection check failed", initial, checkConnection());
+
+ // Reload changed firewall file after restart or reload
+ writeFirewallFile(!initial, inVhost);
+ restartOrReload.run();
+
+ assertEquals("Second connection check failed", !initial, checkConnection());
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/store/PersistentStoreTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/store/PersistentStoreTest.java
new file mode 100644
index 0000000000..bf9d0e0f7b
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/store/PersistentStoreTest.java
@@ -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.
+ *
+ */
+
+package org.apache.qpid.server.store;
+
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class PersistentStoreTest extends QpidBrokerTestCase
+{
+
+ private static final int NUM_MESSAGES = 100;
+ private Connection _con;
+ private Session _session;
+ private Queue _destination;
+ private MessageConsumer _consumer;
+
+ public void setUp() throws Exception, JMSException
+ {
+ super.setUp();
+ _con = getConnection();
+ _con.start();
+ _session = _con.createSession(true, Session.SESSION_TRANSACTED);
+ _destination = _session.createQueue(getTestQueueName());
+ _consumer = _session.createConsumer(_destination);
+ _consumer.close();
+
+ sendMessage(_session, _destination, NUM_MESSAGES);
+ _session.commit();
+ }
+
+ /** Checks that a new consumer on a new connection can get NUM_MESSAGES from _destination */
+ private void checkMessages() throws Exception, JMSException
+ {
+ _con = getConnection();
+ _session = _con.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ _con.start();
+ _consumer = _session.createConsumer(_destination);
+ for (int i = 1; i <= NUM_MESSAGES; i++)
+ {
+ Message msg = _consumer.receive(RECEIVE_TIMEOUT);
+ assertNotNull("Message " + i + " not received", msg);
+ assertEquals("Did not receive the expected message", i, msg.getIntProperty(INDEX));
+ }
+
+ Message msg = _consumer.receive(100);
+ if(msg != null)
+ {
+ fail("No more messages should be received, but received additional message with index: " + msg.getIntProperty(INDEX));
+ }
+ }
+
+// /**
+// * starts the server, sends 100 messages, restarts the server and gets 100 messages back
+// * the test formerly referred to as BDB-Qpid-1
+// * @throws Exception
+// */
+// public void testStartStop() throws Exception
+// {
+// restartBroker(); -- Not Currently a gracefull restart so not BDB-Qpid-1
+// checkMessages();
+// }
+
+ /**
+ * starts the server, sends 100 messages, nukes then starts the server and gets 100 messages back
+ * the test formerly referred to as BDB-Qpid-2
+ *
+ * @throws Exception
+ */
+ public void testForcibleStartStop() throws Exception
+ {
+ restartBroker();
+ checkMessages();
+ }
+
+// /**
+// * starts the server, sends 100 committed messages, 5 uncommited ones,
+// * restarts the server and gets 100 messages back
+// * the test formerly referred to as BDB-Qpid-5
+// * @throws Exception
+// */
+// public void testStartStopMidTransaction() throws Exception
+// {
+// sendMessage(_session, _destination, 5);
+// restartBroker(); -- Not Currently a gracefull restart so not BDB-Qpid-1
+// checkMessages();
+// }
+
+ /**
+ * starts the server, sends 100 committed messages, 5 uncommited ones,
+ * nukes and starts the server and gets 100 messages back
+ * the test formerly referred to as BDB-Qpid-6
+ *
+ * @throws Exception
+ */
+ public void testForcibleStartStopMidTransaction() throws Exception
+ {
+ sendMessage(_session, _destination, 5);
+ //sync to ensure that the above messages have reached the broker
+ ((AMQSession) _session).sync();
+ restartBroker();
+ checkMessages();
+ }
+
+ /**
+ * starts the server, sends 100 committed messages, 5 uncommited ones,
+ * restarts the client and gets 100 messages back.
+ * the test formerly referred to as BDB-Qpid-7
+ *
+ * FIXME: is this a PersistentStoreTest? Seems more like a transaction test to me.. aidan
+ *
+ * @throws Exception
+ */
+ public void testClientDeathMidTransaction() throws Exception
+ {
+ sendMessage(_session, _destination, 5);
+ _con.close();
+ checkMessages();
+ }
+
+// /**
+// * starts the server, sends 50 committed messages, copies $QPID_WORK to a new location,
+// * sends 10 messages, stops the server, nukes the store, restores the copy, starts the server
+// * checks that we get the first 50 back.
+// */
+// public void testHotBackup()
+// {
+// -- removing as this will leave 100msgs on a queue
+// }
+
+ /**
+ * This test requires that we can send messages without commiting.
+ * QTC always commits the messages sent via sendMessages.
+ *
+ * @param session the session to use for sending
+ * @param destination where to send them to
+ * @param count no. of messages to send
+ *
+ * @return the sent messges
+ *
+ * @throws Exception
+ */
+ @Override
+ public List<Message> sendMessage(Session session, Destination destination,
+ int count) throws Exception
+ {
+ List<Message> messages = new ArrayList<Message>(count);
+
+ MessageProducer producer = session.createProducer(destination);
+
+ for (int i = 1;i <= (count); i++)
+ {
+ Message next = createNextMessage(session, i);
+
+ producer.send(next);
+
+ messages.add(next);
+ }
+
+ return messages;
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/store/SlowMessageStore.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/store/SlowMessageStore.java
new file mode 100644
index 0000000000..a5c38e7e33
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/store/SlowMessageStore.java
@@ -0,0 +1,321 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.store;
+
+import org.apache.commons.configuration.Configuration;
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQStoreException;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.server.queue.AMQQueue;
+import org.apache.qpid.server.message.ServerMessage;
+import org.apache.qpid.server.logging.LogSubject;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.nio.ByteBuffer;
+
+public class SlowMessageStore implements MessageStore
+{
+ private static final Logger _logger = Logger.getLogger(SlowMessageStore.class);
+ private static final String DELAYS = "delays";
+ private HashMap<String, Long> _preDelays = new HashMap<String, Long>();
+ private HashMap<String, Long> _postDelays = new HashMap<String, Long>();
+ private long _defaultDelay = 0L;
+ private MessageStore _realStore = new MemoryMessageStore();
+ private static final String PRE = "pre";
+ private static final String POST = "post";
+ private String DEFAULT_DELAY = "default";
+
+ // ***** MessageStore Interface.
+
+ public void configureConfigStore(String name,
+ ConfigurationRecoveryHandler recoveryHandler,
+ Configuration config,
+ LogSubject logSubject) throws Exception
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+
+ _logger.info("Starting SlowMessageStore on Virtualhost:" + name);
+ Configuration delays = config.subset(DELAYS);
+
+ configureDelays(delays);
+
+ String messageStoreClass = config.getString("realStore");
+
+ if (delays.containsKey(DEFAULT_DELAY))
+ {
+ _defaultDelay = delays.getLong(DEFAULT_DELAY);
+ }
+
+ if (messageStoreClass != null)
+ {
+ Class clazz = Class.forName(messageStoreClass);
+
+ Object o = clazz.newInstance();
+
+ if (!(o instanceof MessageStore))
+ {
+ throw new ClassCastException("Message store class must implement " + MessageStore.class + ". Class " + clazz +
+ " does not.");
+ }
+ _realStore = (MessageStore) o;
+ _realStore.configureConfigStore(name, recoveryHandler, config, logSubject);
+ }
+ else
+ {
+ _realStore.configureConfigStore(name, recoveryHandler, config, logSubject);
+ }
+ }
+
+ private void configureDelays(Configuration config)
+ {
+ Iterator delays = config.getKeys();
+
+ while (delays.hasNext())
+ {
+ String key = (String) delays.next();
+ if (key.endsWith(PRE))
+ {
+ _preDelays.put(key.substring(0, key.length() - PRE.length() - 1), config.getLong(key));
+ }
+ else if (key.endsWith(POST))
+ {
+ _postDelays.put(key.substring(0, key.length() - POST.length() - 1), config.getLong(key));
+ }
+ }
+ }
+
+ private void doPostDelay(String method)
+ {
+ long delay = lookupDelay(_postDelays, method);
+ doDelay(delay);
+ }
+
+ private void doPreDelay(String method)
+ {
+ long delay = lookupDelay(_preDelays, method);
+ doDelay(delay);
+ }
+
+ private long lookupDelay(HashMap<String, Long> delays, String method)
+ {
+ Long delay = delays.get(method);
+ return (delay == null) ? _defaultDelay : delay;
+ }
+
+ private void doDelay(long delay)
+ {
+ if (delay > 0)
+ {
+ long start = System.nanoTime();
+ try
+ {
+
+ Thread.sleep(delay);
+ }
+ catch (InterruptedException e)
+ {
+ _logger.warn("Interrupted : " + e);
+ }
+
+ long slept = (System.nanoTime() - start) / 1000000;
+
+ if (slept >= delay)
+ {
+ _logger.info("Done sleep for:" + slept+":"+delay);
+ }
+ else
+ {
+ _logger.info("Only sleep for:" + slept + " re-sleeping");
+ doDelay(delay - slept);
+ }
+ }
+ }
+
+
+ public void configureMessageStore(String name,
+ MessageStoreRecoveryHandler recoveryHandler,
+ Configuration config,
+ LogSubject logSubject) throws Exception
+ {
+ _realStore.configureMessageStore(name, recoveryHandler, config, logSubject);
+ }
+
+ public void close() throws Exception
+ {
+ doPreDelay("close");
+ _realStore.close();
+ doPostDelay("close");
+ }
+
+ public <M extends StorableMessageMetaData> StoredMessage<M> addMessage(M metaData)
+ {
+ return _realStore.addMessage(metaData);
+ }
+
+
+ public void createExchange(Exchange exchange) throws AMQStoreException
+ {
+ doPreDelay("createExchange");
+ _realStore.createExchange(exchange);
+ doPostDelay("createExchange");
+ }
+
+ public void removeExchange(Exchange exchange) throws AMQStoreException
+ {
+ doPreDelay("removeExchange");
+ _realStore.removeExchange(exchange);
+ doPostDelay("removeExchange");
+ }
+
+ public void bindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQStoreException
+ {
+ doPreDelay("bindQueue");
+ _realStore.bindQueue(exchange, routingKey, queue, args);
+ doPostDelay("bindQueue");
+ }
+
+ public void unbindQueue(Exchange exchange, AMQShortString routingKey, AMQQueue queue, FieldTable args) throws AMQStoreException
+ {
+ doPreDelay("unbindQueue");
+ _realStore.unbindQueue(exchange, routingKey, queue, args);
+ doPostDelay("unbindQueue");
+ }
+
+ public void createQueue(AMQQueue queue) throws AMQStoreException
+ {
+ createQueue(queue, null);
+ }
+
+ public void createQueue(AMQQueue queue, FieldTable arguments) throws AMQStoreException
+ {
+ doPreDelay("createQueue");
+ _realStore.createQueue(queue, arguments);
+ doPostDelay("createQueue");
+ }
+
+ public void removeQueue(AMQQueue queue) throws AMQStoreException
+ {
+ doPreDelay("removeQueue");
+ _realStore.removeQueue(queue);
+ doPostDelay("removeQueue");
+ }
+
+ public void configureTransactionLog(String name,
+ TransactionLogRecoveryHandler recoveryHandler,
+ Configuration storeConfiguration, LogSubject logSubject)
+ throws Exception
+ {
+ _realStore.configureTransactionLog(name, recoveryHandler, storeConfiguration, logSubject);
+ }
+
+ public Transaction newTransaction()
+ {
+ doPreDelay("beginTran");
+ Transaction txn = new SlowTransaction(_realStore.newTransaction());
+ doPostDelay("beginTran");
+ return txn;
+ }
+
+
+ public boolean isPersistent()
+ {
+ return _realStore.isPersistent();
+ }
+
+ public void storeMessageHeader(Long messageNumber, ServerMessage message)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void storeContent(Long messageNumber, long offset, ByteBuffer body)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public ServerMessage getMessage(Long messageNumber)
+ {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ private class SlowTransaction implements Transaction
+ {
+ private final Transaction _underlying;
+
+ private SlowTransaction(Transaction underlying)
+ {
+ _underlying = underlying;
+ }
+
+ public void enqueueMessage(TransactionLogResource queue, Long messageId)
+ throws AMQStoreException
+ {
+ doPreDelay("enqueueMessage");
+ _underlying.enqueueMessage(queue, messageId);
+ doPostDelay("enqueueMessage");
+ }
+
+ public void dequeueMessage(TransactionLogResource queue, Long messageId)
+ throws AMQStoreException
+ {
+ doPreDelay("dequeueMessage");
+ _underlying.dequeueMessage(queue, messageId);
+ doPostDelay("dequeueMessage");
+ }
+
+ public void commitTran()
+ throws AMQStoreException
+ {
+ doPreDelay("commitTran");
+ _underlying.commitTran();
+ doPostDelay("commitTran");
+ }
+
+ public StoreFuture commitTranAsync()
+ throws AMQStoreException
+ {
+ doPreDelay("commitTran");
+ StoreFuture future = _underlying.commitTranAsync();
+ doPostDelay("commitTran");
+ return future;
+ }
+
+ public void abortTran()
+ throws AMQStoreException
+ {
+ doPreDelay("abortTran");
+ _underlying.abortTran();
+ doPostDelay("abortTran");
+ }
+ }
+
+ public void updateQueue(AMQQueue queue) throws AMQStoreException
+ {
+ doPreDelay("updateQueue");
+ _realStore.updateQueue(queue);
+ doPostDelay("updateQueue");
+ }
+
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/util/AveragedRun.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/util/AveragedRun.java
new file mode 100644
index 0000000000..1d17985ab5
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/util/AveragedRun.java
@@ -0,0 +1,66 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.util;
+
+import org.apache.qpid.server.util.TimedRun;
+
+import java.util.concurrent.Callable;
+import java.util.Collection;
+
+public class AveragedRun implements Callable<RunStats>
+{
+ private final RunStats stats = new RunStats();
+ private final TimedRun test;
+ private final int iterations;
+
+ public AveragedRun(TimedRun test, int iterations)
+ {
+ this.test = test;
+ this.iterations = iterations;
+ }
+
+ public RunStats call() throws Exception
+ {
+ for (int i = 0; i < iterations; i++)
+ {
+ stats.record(test.call());
+ }
+ return stats;
+ }
+
+ public void run() throws Exception
+ {
+ System.out.println(test + ": " + call());
+ }
+
+ public String toString()
+ {
+ return test.toString();
+ }
+
+ static void run(Collection<AveragedRun> tests) throws Exception
+ {
+ for(AveragedRun test : tests)
+ {
+ test.run();
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/util/RunStats.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/util/RunStats.java
new file mode 100644
index 0000000000..ec67fc68b3
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/util/RunStats.java
@@ -0,0 +1,57 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.util;
+
+public class RunStats
+{
+ private long min = Long.MAX_VALUE;
+ private long max;
+ private long total;
+ private int count;
+
+ public void record(long time)
+ {
+ max = Math.max(time, max);
+ min = Math.min(time, min);
+ total += time;
+ count++;
+ }
+
+ public long getMin()
+ {
+ return min;
+ }
+
+ public long getMax()
+ {
+ return max;
+ }
+
+ public long getAverage()
+ {
+ return total / count;
+ }
+
+ public String toString()
+ {
+ return "avg=" + getAverage() + ", min=" + min + ", max=" + max;
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/server/util/TimedRun.java b/qpid/java/systests/src/main/java/org/apache/qpid/server/util/TimedRun.java
new file mode 100644
index 0000000000..1291380311
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/server/util/TimedRun.java
@@ -0,0 +1,52 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.server.util;
+
+import java.util.concurrent.Callable;
+
+public abstract class TimedRun implements Callable<Long>
+{
+ private final String description;
+
+ public TimedRun(String description)
+ {
+ this.description = description;
+ }
+
+ public Long call() throws Exception
+ {
+ setup();
+ long start = System.currentTimeMillis();
+ run();
+ long stop = System.currentTimeMillis();
+ teardown();
+ return stop - start;
+ }
+
+ public String toString()
+ {
+ return description;
+ }
+
+ protected void setup() throws Exception{}
+ protected void teardown() throws Exception{}
+ protected abstract void run() throws Exception;
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/GlobalQueuesTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/GlobalQueuesTest.java
new file mode 100644
index 0000000000..9ff143daf3
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/GlobalQueuesTest.java
@@ -0,0 +1,223 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.systest;
+
+import org.apache.commons.configuration.ConfigurationException;
+
+import javax.jms.Session;
+import javax.naming.NamingException;
+import java.io.IOException;
+
+/**
+ * QPID-1447 : Add slow consumer detection and disconnection.
+ *
+ * Slow consumers should on a topic should expect to receive a
+ * 506 : Resource Error if the hit a predefined threshold.
+ */
+public class GlobalQueuesTest extends TestingBaseCase
+{
+
+ protected String CONFIG_SECTION = ".queues";
+
+ /**
+ * Queue Configuration
+
+ <slow-consumer-detection>
+ <!-- The depth before which the policy will be applied-->
+ <depth>4235264</depth>
+
+ <!-- The message age before which the policy will be applied-->
+ <messageAge>600000</messageAge>
+
+ <!-- The number of message before which the policy will be applied-->
+ <messageCount>50</messageCount>
+
+ <!-- Policies configuration -->
+ <policy>
+ <name>TopicDelete</name>
+ <topicDelete>
+ <delete-persistent/>
+ </topicDelete>
+ </policy>
+ </slow-consumer-detection>
+
+ */
+
+
+ /**
+ * VirtualHost Plugin Configuration
+
+ <slow-consumer-detection>
+ <delay>1</delay>
+ <timeunit>MINUTES</timeunit>
+ </slow-consumer-detection>
+
+ */
+
+ public void setConfig(String property, String value, boolean deleteDurable) throws NamingException, IOException, ConfigurationException
+ {
+ setProperty(CONFIG_SECTION + ".slow-consumer-detection." +
+ "policy.name", "TopicDelete");
+
+ setProperty(CONFIG_SECTION + ".slow-consumer-detection." +
+ property, value);
+
+ if (deleteDurable)
+ {
+ setProperty(CONFIG_SECTION + ".slow-consumer-detection." +
+ "policy.topicdelete.delete-persistent", "");
+ }
+ }
+
+ /**
+ * Test that setting messageCount takes affect on topics
+ *
+ * We send 10 messages and disconnect at 9
+ *
+ * @throws Exception
+ */
+ public void testTopicConsumerMessageCount() throws Exception
+ {
+ MAX_QUEUE_MESSAGE_COUNT = 10;
+
+ setConfig("messageCount", String.valueOf(MAX_QUEUE_MESSAGE_COUNT - 1), false);
+
+ //Start the broker
+ startBroker();
+
+ topicConsumer(Session.AUTO_ACKNOWLEDGE, false);
+ }
+
+ /**
+ * Test that setting depth has an effect on topics
+ *
+ * Sets the message size for the test
+ * Sets the depth to be 9 * the depth
+ * Ensure that sending 10 messages causes the disconnection
+ *
+ * @throws Exception
+ */
+ public void testTopicConsumerMessageSize() throws Exception
+ {
+ MAX_QUEUE_MESSAGE_COUNT = 10;
+
+ setConfig("depth", String.valueOf(MESSAGE_SIZE * 9), false);
+
+ //Start the broker
+ startBroker();
+
+ setMessageSize(MESSAGE_SIZE);
+
+ topicConsumer(Session.AUTO_ACKNOWLEDGE, false);
+ }
+
+ /**
+ * Test that setting messageAge has an effect on topics
+ *
+ * Sets the messageAge to be half the disconnection wait timeout
+ * Send 10 messages and then ensure that we get disconnected as we will
+ * wait for the full timeout.
+ *
+ * @throws Exception
+ */
+ public void testTopicConsumerMessageAge() throws Exception
+ {
+ MAX_QUEUE_MESSAGE_COUNT = 10;
+
+ setConfig("messageAge", String.valueOf(DISCONNECTION_WAIT / 2), false);
+
+ //Start the broker
+ startBroker();
+
+ topicConsumer(Session.AUTO_ACKNOWLEDGE, false);
+ }
+
+ /**
+ * Test that setting messageCount takes affect on a durable Consumer
+ *
+ * Ensure we set the delete-persistent option
+ *
+ * We send 10 messages and disconnect at 9
+ *
+ * @throws Exception
+ */
+
+ public void testTopicDurableConsumerMessageCount() throws Exception
+ {
+ MAX_QUEUE_MESSAGE_COUNT = 10;
+
+ setConfig("messageCount", String.valueOf(MAX_QUEUE_MESSAGE_COUNT - 1), true);
+
+ //Start the broker
+ startBroker();
+
+ topicConsumer(Session.AUTO_ACKNOWLEDGE, true);
+ }
+
+ /**
+ * Test that setting depth has an effect on durable consumer topics
+ *
+ * Ensure we set the delete-persistent option
+ *
+ * Sets the message size for the test
+ * Sets the depth to be 9 * the depth
+ * Ensure that sending 10 messages causes the disconnection
+ *
+ * @throws Exception
+ */
+ public void testTopicDurableConsumerMessageSize() throws Exception
+ {
+ MAX_QUEUE_MESSAGE_COUNT = 10;
+
+ setConfig("depth", String.valueOf(MESSAGE_SIZE * 9), true);
+
+ //Start the broker
+ startBroker();
+
+ setMessageSize(MESSAGE_SIZE);
+
+ topicConsumer(Session.AUTO_ACKNOWLEDGE, true);
+ }
+
+ /**
+ * Test that setting messageAge has an effect on topics
+ *
+ * Ensure we set the delete-persistent option
+ *
+ * Sets the messageAge to be 1/5 the disconnection wait timeout (or 1sec)
+ * Send 10 messages and then ensure that we get disconnected as we will
+ * wait for the full timeout.
+ *
+ * @throws Exception
+ */
+ public void testTopicDurableConsumerMessageAge() throws Exception
+ {
+ MAX_QUEUE_MESSAGE_COUNT = 10;
+
+ setConfig("messageAge", String.valueOf(DISCONNECTION_WAIT / 5), true);
+
+ //Start the broker
+ startBroker();
+
+ topicConsumer(Session.AUTO_ACKNOWLEDGE, true);
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/GlobalTopicsTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/GlobalTopicsTest.java
new file mode 100644
index 0000000000..aff5d1b1b8
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/GlobalTopicsTest.java
@@ -0,0 +1,36 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.systest;
+
+import org.apache.commons.configuration.ConfigurationException;
+
+import javax.naming.NamingException;
+import java.io.IOException;
+
+public class GlobalTopicsTest extends GlobalQueuesTest
+{
+ @Override
+ public void setUp() throws Exception
+ {
+ CONFIG_SECTION = ".topics";
+ super.setUp();
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/MergeConfigurationTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/MergeConfigurationTest.java
new file mode 100644
index 0000000000..e4efac60f8
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/MergeConfigurationTest.java
@@ -0,0 +1,124 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.systest;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.qpid.AMQChannelClosedException;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQSession_0_10;
+import org.apache.qpid.jms.ConnectionListener;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.ExceptionListener;
+import javax.jms.JMSException;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import javax.jms.Topic;
+import javax.naming.NamingException;
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class MergeConfigurationTest extends TestingBaseCase
+{
+
+ protected int topicCount = 0;
+
+
+ public void configureTopic(String topic, int msgCount) throws NamingException, IOException, ConfigurationException
+ {
+
+ setProperty(".topics.topic("+topicCount+").name", topic);
+ setProperty(".topics.topic("+topicCount+").slow-consumer-detection.messageCount", String.valueOf(msgCount));
+ setProperty(".topics.topic("+topicCount+").slow-consumer-detection.policy.name", "TopicDelete");
+ topicCount++;
+ }
+
+
+ /**
+ * Test that setting messageCount takes affect on topics
+ *
+ * We send 10 messages and disconnect at 9
+ *
+ * @throws Exception
+ */
+ public void testTopicConsumerMessageCount() throws Exception
+ {
+ MAX_QUEUE_MESSAGE_COUNT = 10;
+
+ configureTopic(getName(), (MAX_QUEUE_MESSAGE_COUNT * 4) - 1);
+
+ //Configure topic as a subscription
+ setProperty(".topics.topic("+topicCount+").subscriptionName", "clientid:"+getTestQueueName());
+ configureTopic(getName(), (MAX_QUEUE_MESSAGE_COUNT - 1));
+
+
+
+ //Start the broker
+ startBroker();
+
+ topicConsumer(Session.AUTO_ACKNOWLEDGE, true);
+ }
+
+
+//
+// public void testMerge() throws ConfigurationException, AMQException
+// {
+//
+// AMQQueue queue = AMQQueueFactory.createAMQQueueImpl(new AMQShortString(getName()+":stockSubscription"), false, new AMQShortString("testowner"),
+// false, false, _virtualHost, null);
+//
+// _virtualHost.getQueueRegistry().registerQueue(queue);
+// Exchange defaultExchange = _virtualHost.getExchangeRegistry().getDefaultExchange();
+// _virtualHost.getBindingFactory().addBinding(getName(), queue, defaultExchange, null);
+//
+//
+// Exchange topicExchange = _virtualHost.getExchangeRegistry().getExchange(ExchangeDefaults.TOPIC_EXCHANGE_NAME);
+// _virtualHost.getBindingFactory().addBinding("stocks.nyse.orcl", queue, topicExchange, null);
+//
+// TopicConfig config = queue.getConfiguration().getConfiguration(TopicConfig.class.getName());
+//
+// assertNotNull("Queue should have topic configuration bound to it.", config);
+// assertEquals("Configuration name not correct", getName() + ":stockSubscription", config.getSubscriptionName());
+//
+// ConfigurationPlugin scdConfig = queue.getConfiguration().getConfiguration(SlowConsumerDetectionQueueConfiguration.class.getName());
+// if (scdConfig instanceof org.apache.qpid.server.configuration.plugin.SlowConsumerDetectionQueueConfiguration)
+// {
+// System.err.println("********************** scd is a SlowConsumerDetectionQueueConfiguration.");
+// }
+// else
+// {
+// System.err.println("********************** Test SCD "+SlowConsumerDetectionQueueConfiguration.class.getClassLoader());
+// System.err.println("********************** Broker SCD "+scdConfig.getClass().getClassLoader());
+// System.err.println("********************** Broker SCD "+scdConfig.getClass().isAssignableFrom(SlowConsumerDetectionQueueConfiguration.class));
+// System.err.println("********************** is a "+scdConfig.getClass());
+// }
+//
+// assertNotNull("Queue should have scd configuration bound to it.", scdConfig);
+// assertEquals("MessageCount is not correct", 10 , ((SlowConsumerDetectionQueueConfiguration)scdConfig).getMessageCount());
+// assertEquals("Policy is not correct", TopicDeletePolicy.class.getName() , ((SlowConsumerDetectionQueueConfiguration)scdConfig).getPolicy().getClass().getName());
+// }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/SubscriptionTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/SubscriptionTest.java
new file mode 100644
index 0000000000..9e9375fd44
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/SubscriptionTest.java
@@ -0,0 +1,146 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.systest;
+
+import org.apache.commons.configuration.ConfigurationException;
+
+import javax.jms.Session;
+import javax.naming.NamingException;
+import java.io.IOException;
+
+/**
+ * Test SCD when configured with Subscription details.
+ *
+ * We run the subscription based tests here to validate that the
+ * subscriptionname value is correctly associated with the subscription.
+ *
+ *
+ */
+public class SubscriptionTest extends TestingBaseCase
+{
+ private int _count=0;
+ protected String CONFIG_SECTION = ".topics.topic";
+
+ /**
+ * Add configuration for the queue that relates just to this test.
+ * We use the getTestQueueName() as our subscription. To ensure the
+ * config sections do not overlap we identify each section with a _count
+ * value.
+ *
+ * This would allow each test to configure more than one section.
+ *
+ * @param property to set
+ * @param value the value to set
+ * @param deleteDurable should deleteDurable be set.
+ * @throws NamingException
+ * @throws IOException
+ * @throws ConfigurationException
+ */
+ public void setConfig(String property, String value, boolean deleteDurable) throws NamingException, IOException, ConfigurationException
+ {
+ setProperty(CONFIG_SECTION + "("+_count+").subscriptionName", "clientid:"+getTestQueueName());
+
+ setProperty(CONFIG_SECTION + "("+_count+").slow-consumer-detection." +
+ "policy.name", "TopicDelete");
+
+ setProperty(CONFIG_SECTION + "("+_count+").slow-consumer-detection." +
+ property, value);
+
+ if (deleteDurable)
+ {
+ setProperty(CONFIG_SECTION + "("+_count+").slow-consumer-detection." +
+ "policy.topicdelete.delete-persistent", "");
+ }
+ _count++;
+ }
+
+
+ /**
+ * Test that setting messageCount takes affect on a durable Consumer
+ *
+ * Ensure we set the delete-persistent option
+ *
+ * We send 10 messages and disconnect at 9
+ *
+ * @throws Exception
+ */
+
+ public void testTopicDurableConsumerMessageCount() throws Exception
+ {
+ MAX_QUEUE_MESSAGE_COUNT = 10;
+
+ setConfig("messageCount", String.valueOf(MAX_QUEUE_MESSAGE_COUNT - 1), true);
+
+ //Start the broker
+ startBroker();
+
+ topicConsumer(Session.AUTO_ACKNOWLEDGE, true);
+ }
+
+ /**
+ * Test that setting depth has an effect on durable consumer topics
+ *
+ * Ensure we set the delete-persistent option
+ *
+ * Sets the message size for the test
+ * Sets the depth to be 9 * the depth
+ * Ensure that sending 10 messages causes the disconnection
+ *
+ * @throws Exception
+ */
+ public void testTopicDurableConsumerMessageSize() throws Exception
+ {
+ MAX_QUEUE_MESSAGE_COUNT = 10;
+
+ setConfig("depth", String.valueOf(MESSAGE_SIZE * 9), true);
+
+ //Start the broker
+ startBroker();
+
+ setMessageSize(MESSAGE_SIZE);
+
+ topicConsumer(Session.AUTO_ACKNOWLEDGE, true);
+ }
+
+ /**
+ * Test that setting messageAge has an effect on topics
+ *
+ * Ensure we set the delete-persistent option
+ *
+ * Sets the messageAge to be 1/5 the disconnection wait timeout (or 1sec)
+ * Send 10 messages and then ensure that we get disconnected as we will
+ * wait for the full timeout.
+ *
+ * @throws Exception
+ */
+ public void testTopicDurableConsumerMessageAge() throws Exception
+ {
+ MAX_QUEUE_MESSAGE_COUNT = 10;
+
+ setConfig("messageAge", String.valueOf(DISCONNECTION_WAIT / 5), true);
+
+ //Start the broker
+ startBroker();
+
+ topicConsumer(Session.AUTO_ACKNOWLEDGE, true);
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/TestingBaseCase.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/TestingBaseCase.java
new file mode 100644
index 0000000000..08a7b7a6e5
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/TestingBaseCase.java
@@ -0,0 +1,241 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.systest;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.qpid.AMQChannelClosedException;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQSession_0_10;
+import org.apache.qpid.jms.ConnectionListener;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.ExceptionListener;
+import javax.jms.JMSException;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import javax.jms.Topic;
+import javax.naming.NamingException;
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class TestingBaseCase extends QpidBrokerTestCase implements ExceptionListener, ConnectionListener
+{
+
+ Topic _destination;
+ protected CountDownLatch _disconnectionLatch = new CountDownLatch(1);
+ protected int MAX_QUEUE_MESSAGE_COUNT;
+ protected int MESSAGE_SIZE = DEFAULT_MESSAGE_SIZE;
+
+ private Thread _publisher;
+ protected static final long DISCONNECTION_WAIT = 5;
+ protected Exception _publisherError = null;
+ protected JMSException _connectionException = null;
+ private static final long JOIN_WAIT = 5000;
+
+ @Override
+ public void setUp() throws Exception
+ {
+
+ setConfigurationProperty("virtualhosts.virtualhost."
+ + getConnectionURL().getVirtualHost().substring(1) +
+ ".slow-consumer-detection.delay", "1");
+
+ setConfigurationProperty("virtualhosts.virtualhost."
+ + getConnectionURL().getVirtualHost().substring(1) +
+ ".slow-consumer-detection.timeunit", "SECONDS");
+
+ }
+
+
+ protected void setProperty(String property, String value) throws NamingException, IOException, ConfigurationException
+ {
+ setConfigurationProperty("virtualhosts.virtualhost." +
+ getConnectionURL().getVirtualHost().substring(1) +
+ property, value);
+ }
+
+
+ /**
+ * Create and start an asynchrounous publisher that will send MAX_QUEUE_MESSAGE_COUNT
+ * messages to the provided destination. Messages are sent in a new connection
+ * on a transaction. Any error is captured and the test is signalled to exit.
+ *
+ * @param destination
+ */
+ private void startPublisher(final Destination destination)
+ {
+ _publisher = new Thread(new Runnable()
+ {
+
+ public void run()
+ {
+ try
+ {
+ Connection connection = getConnection();
+ Session session = connection.createSession(true, Session.SESSION_TRANSACTED);
+
+ MessageProducer publisher = session.createProducer(destination);
+
+ for (int count = 0; count < MAX_QUEUE_MESSAGE_COUNT; count++)
+ {
+ publisher.send(createNextMessage(session, count));
+ session.commit();
+ }
+ }
+ catch (Exception e)
+ {
+ _publisherError = e;
+ _disconnectionLatch.countDown();
+ }
+ }
+ });
+
+ _publisher.start();
+ }
+
+
+
+ /**
+ * Perform the Main test of a topic Consumer with the given AckMode.
+ *
+ * Test creates a new connection and sets up the connection to prevent
+ * failover
+ *
+ * A new consumer is connected and started so that it will prefetch msgs.
+ *
+ * An asynchrounous publisher is started to fill the broker with messages.
+ *
+ * We then wait to be notified of the disconnection via the ExceptionListener
+ *
+ * 0-10 does not have the same notification paths but sync() apparently should
+ * give us the exception, currently it doesn't, so the test is excluded from 0-10
+ *
+ * We should ensure that this test has the same path for all protocol versions.
+ *
+ * Clients should not have to modify their code based on the protocol in use.
+ *
+ * @param ackMode @see javax.jms.Session
+ *
+ * @throws Exception
+ */
+ protected void topicConsumer(int ackMode, boolean durable) throws Exception
+ {
+ Connection connection = getConnection();
+
+ connection.setExceptionListener(this);
+
+ Session session = connection.createSession(ackMode == Session.SESSION_TRANSACTED, ackMode);
+
+ _destination = session.createTopic(getName());
+
+ MessageConsumer consumer;
+
+ if (durable)
+ {
+ consumer = session.createDurableSubscriber(_destination, getTestQueueName());
+ }
+ else
+ {
+ consumer = session.createConsumer(_destination);
+ }
+
+ connection.start();
+
+ // Start the consumer pre-fetching
+ // Don't care about response as we will fill the broker up with messages
+ // after this point and ensure that the client is disconnected at the
+ // right point.
+ consumer.receiveNoWait();
+ startPublisher(_destination);
+
+ boolean disconnected = _disconnectionLatch.await(DISCONNECTION_WAIT, TimeUnit.SECONDS);
+
+ assertTrue("Client was not disconnected", disconnected);
+ assertTrue("Client was not disconnected.", _connectionException != null);
+
+ Exception linked = _connectionException.getLinkedException();
+
+ _publisher.join(JOIN_WAIT);
+
+ assertFalse("Publisher still running", _publisher.isAlive());
+
+ //Validate publishing occurred ok
+ if (_publisherError != null)
+ {
+ throw _publisherError;
+ }
+
+ // NOTE these exceptions will need to be modeled so that they are not
+ // 0-8 specific. e.g. JMSSessionClosedException
+
+ assertNotNull("No error received onException listener.", _connectionException);
+
+ assertNotNull("No linked exception set on:" + _connectionException.getMessage(), linked);
+
+ assertTrue("Incorrect linked exception received.", linked instanceof AMQException);
+
+ AMQException amqException = (AMQException) linked;
+
+ assertEquals("Channel was not closed with correct code.", AMQConstant.RESOURCE_ERROR, amqException.getErrorCode());
+ }
+
+
+ // Exception Listener
+
+ public void onException(JMSException e)
+ {
+ _connectionException = e;
+
+ e.printStackTrace();
+
+ _disconnectionLatch.countDown();
+ }
+
+ /// Connection Listener
+
+ public void bytesSent(long count)
+ {
+ }
+
+ public void bytesReceived(long count)
+ {
+ }
+
+ public boolean preFailover(boolean redirect)
+ {
+ // Prevent Failover
+ return false;
+ }
+
+ public boolean preResubscribe()
+ {
+ return false;
+ }
+
+ public void failoverComplete()
+ {
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/systest/TopicTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/systest/TopicTest.java
new file mode 100644
index 0000000000..09c849cfde
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/systest/TopicTest.java
@@ -0,0 +1,85 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.systest;
+
+import org.apache.commons.configuration.ConfigurationException;
+
+import javax.naming.NamingException;
+import java.io.IOException;
+
+/**
+ * This Topic test extends the Global queue test so it will run all the topic
+ * and subscription tests.
+ *
+ * We redefine the CONFIG_SECTION here so that the configuration is written
+ * against a topic element.
+ *
+ * To complete the migration to testing 'topic' elements we also override
+ * the setConfig to use the test name as the topic name.
+ *
+ */
+public class TopicTest extends GlobalQueuesTest
+{
+ private int _count=0;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ CONFIG_SECTION = ".topics.topic";
+ super.setUp();
+ }
+
+ /**
+ * Add configuration for the queue that relates just to this test.
+ * We use the getTestQueueName() as our subscription. To ensure the
+ * config sections do not overlap we identify each section with a _count
+ * value.
+ *
+ * This would allow each test to configure more than one section.
+ *
+ * @param property to set
+ * @param value the value to set
+ * @param deleteDurable should deleteDurable be set.
+ * @throws NamingException
+ * @throws IOException
+ * @throws ConfigurationException
+ */
+ @Override
+ public void setConfig(String property, String value, boolean deleteDurable) throws NamingException, IOException, ConfigurationException
+ {
+ setProperty(CONFIG_SECTION + "("+_count+").name", getName());
+
+ setProperty(CONFIG_SECTION + "("+_count+").slow-consumer-detection." +
+ "policy.name", "TopicDelete");
+
+ setProperty(CONFIG_SECTION + "("+_count+").slow-consumer-detection." +
+ property, value);
+
+ if (deleteDurable)
+ {
+ setProperty(CONFIG_SECTION + "("+_count+").slow-consumer-detection." +
+ "policy.topicdelete.delete-persistent", "");
+ }
+ _count++;
+ }
+
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/CancelTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/CancelTest.java
new file mode 100644
index 0000000000..13a9dd73b8
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/CancelTest.java
@@ -0,0 +1,100 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.test.client;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import javax.jms.Connection;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.QueueBrowser;
+import javax.jms.Session;
+import java.util.Enumeration;
+
+public class CancelTest extends QpidBrokerTestCase
+{
+ private static final Logger _logger = Logger.getLogger(CancelTest.class);
+
+ private Connection _clientConnection;
+ private Session _clientSession;
+ private Queue _queue;
+
+ public void setUp() throws Exception
+ {
+
+ super.setUp();
+
+ _queue = (Queue) getInitialContext().lookup("queue");
+
+ //Create Client
+ _clientConnection = getConnection();
+
+ _clientConnection.start();
+
+ _clientSession = _clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ //Ensure _queue is created
+ _clientSession.createConsumer(_queue).close();
+ }
+
+ /**
+ * Simply
+ * This test originally did not assert anything but was just checking
+ * that a message could be browsed and consumed without throwing an exception.
+ * It now checks that at least a message is browsed and that a message is received.
+ */
+ public void test() throws Exception
+ {
+ Connection producerConnection = getConnection();
+
+ producerConnection.start();
+
+ Session producerSession = producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ MessageProducer producer = producerSession.createProducer(_queue);
+ producer.send(producerSession.createTextMessage());
+ producerConnection.close();
+
+
+ QueueBrowser browser = _clientSession.createBrowser(_queue);
+ Enumeration e = browser.getEnumeration();
+
+ assertTrue(e.hasMoreElements());
+
+ int i = 0;
+ while (e.hasMoreElements())
+ {
+ e.nextElement();
+ if(++i > 1)
+ {
+ fail("Two many elemnts to browse!");
+ }
+ }
+
+ browser.close();
+
+ MessageConsumer consumer = _clientSession.createConsumer(_queue);
+ assertNotNull( consumer.receive() );
+ consumer.close();
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/DupsOkTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/DupsOkTest.java
new file mode 100644
index 0000000000..a94d975a32
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/DupsOkTest.java
@@ -0,0 +1,167 @@
+package org.apache.qpid.test.client;
+
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQSession;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageListener;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+
+public class DupsOkTest extends QpidBrokerTestCase
+{
+
+ private Queue _queue;
+ private static final int MSG_COUNT = 100;
+ private CountDownLatch _awaitCompletion = new CountDownLatch(1);
+
+ public void setUp() throws Exception
+ {
+ super.setUp();
+
+ _queue = (Queue) getInitialContext().lookup("queue");
+
+
+ //Declare the queue
+ Connection consumerConnection = getConnection();
+ consumerConnection.createSession(false,Session.AUTO_ACKNOWLEDGE).createConsumer(_queue).close();
+
+ //Create Producer put some messages on the queue
+ Connection producerConnection = getConnection();
+
+ producerConnection.start();
+
+ Session producerSession = producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ MessageProducer producer = producerSession.createProducer(_queue);
+
+ for (int count = 1; count <= MSG_COUNT; count++)
+ {
+ Message msg = producerSession.createTextMessage("Message " + count);
+ msg.setIntProperty("count", count);
+ producer.send(msg);
+ }
+
+ producerConnection.close();
+ }
+
+ /**
+ * This test sends x messages and receives them with an async consumer.
+ * Waits for all messages to be received or for 60 s
+ * and checks whether the queue is empty.
+ *
+ * @throws Exception
+ */
+ public void testDupsOK() throws Exception
+ {
+ //Create Client
+ Connection clientConnection = getConnection();
+
+ final Session clientSession = clientConnection.createSession(false, Session.DUPS_OK_ACKNOWLEDGE);
+
+ MessageConsumer consumer = clientSession.createConsumer(_queue);
+
+ assertEquals("The queue should have msgs at start", MSG_COUNT, ((AMQSession) clientSession).getQueueDepth((AMQDestination) _queue));
+
+ clientConnection.start();
+
+ consumer.setMessageListener(new MessageListener()
+ {
+ int _msgCount = 0;
+
+ public void onMessage(Message message)
+ {
+ _msgCount++;
+ if (message == null)
+ {
+ fail("Should not get null messages");
+ }
+
+ if (message instanceof TextMessage)
+ {
+ try
+ {
+ if (message.getIntProperty("count") == MSG_COUNT)
+ {
+ try
+ {
+ if(_msgCount != MSG_COUNT)
+ {
+ assertEquals("Wrong number of messages seen.", MSG_COUNT, _msgCount);
+ }
+ }
+ finally
+ {
+ //This is the last message so release test.
+ _awaitCompletion.countDown();
+ }
+ }
+ }
+ catch (JMSException e)
+ {
+ fail("Unable to get int property 'count'");
+ }
+ }
+ else
+ {
+ fail("Got wrong message type");
+ }
+ }
+ });
+
+ try
+ {
+ if (!_awaitCompletion.await(120, TimeUnit.SECONDS))
+ {
+ fail("Test did not complete in 120 seconds");
+ }
+ }
+ catch (InterruptedException e)
+ {
+ fail("Unable to wait for test completion");
+ throw e;
+ }
+
+ //Close consumer to give broker time to process in bound Acks. As The main thread will be released while
+ // before the dispatcher has sent the ack back to the broker.
+ consumer.close();
+
+ clientSession.close();
+
+ final Session clientSession2 = clientConnection.createSession(false, Session.DUPS_OK_ACKNOWLEDGE);
+
+ assertEquals("The queue should have 0 msgs left", 0, ((AMQSession) clientSession2).getQueueDepth((AMQDestination) _queue));
+
+ clientConnection.close();
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/FlowControlTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/FlowControlTest.java
new file mode 100644
index 0000000000..e1f639afb6
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/FlowControlTest.java
@@ -0,0 +1,213 @@
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+package org.apache.qpid.test.client;
+
+import org.apache.qpid.client.AMQSession_0_8;
+import org.apache.qpid.client.message.AbstractJMSMessage;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.log4j.Logger;
+
+import javax.jms.*;
+
+public class FlowControlTest extends QpidBrokerTestCase
+{
+ private static final Logger _logger = Logger.getLogger(FlowControlTest.class);
+
+ private Connection _clientConnection;
+ private Session _clientSession;
+ private Queue _queue;
+
+ /**
+ * Simply
+ *
+ * @throws Exception
+ */
+ public void testBasicBytesFlowControl() throws Exception
+ {
+ _queue = (Queue) getInitialContext().lookup("queue");
+
+ //Create Client
+ _clientConnection = getConnection();
+
+ _clientConnection.start();
+
+ _clientSession = _clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ //Ensure _queue is created
+ _clientSession.createConsumer(_queue).close();
+
+ Connection producerConnection = getConnection();
+
+ producerConnection.start();
+
+ Session producerSession = producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ MessageProducer producer = producerSession.createProducer(_queue);
+
+ BytesMessage m1 = producerSession.createBytesMessage();
+ m1.writeBytes(new byte[128]);
+ m1.setIntProperty("msg", 1);
+ producer.send(m1);
+ BytesMessage m2 = producerSession.createBytesMessage();
+ m2.writeBytes(new byte[128]);
+ m2.setIntProperty("msg", 2);
+ producer.send(m2);
+ BytesMessage m3 = producerSession.createBytesMessage();
+ m3.writeBytes(new byte[256]);
+ m3.setIntProperty("msg", 3);
+ producer.send(m3);
+
+ producer.close();
+ producerSession.close();
+ producerConnection.close();
+
+ Connection consumerConnection = getConnection();
+ Session consumerSession = consumerConnection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+ ((AMQSession_0_8) consumerSession).setPrefetchLimits(0, 256);
+ MessageConsumer recv = consumerSession.createConsumer(_queue);
+ consumerConnection.start();
+
+ Message r1 = recv.receive(RECEIVE_TIMEOUT);
+ assertNotNull("First message not received", r1);
+ assertEquals("Messages in wrong order", 1, r1.getIntProperty("msg"));
+
+ Message r2 = recv.receive(RECEIVE_TIMEOUT);
+ assertNotNull("Second message not received", r2);
+ assertEquals("Messages in wrong order", 2, r2.getIntProperty("msg"));
+
+ Message r3 = recv.receive(RECEIVE_TIMEOUT);
+ assertNull("Third message incorrectly delivered", r3);
+
+ ((AbstractJMSMessage)r1).acknowledgeThis();
+
+ r3 = recv.receive(RECEIVE_TIMEOUT);
+ assertNull("Third message incorrectly delivered", r3);
+
+ ((AbstractJMSMessage)r2).acknowledgeThis();
+
+ r3 = recv.receive(RECEIVE_TIMEOUT);
+ assertNotNull("Third message not received", r3);
+ assertEquals("Messages in wrong order", 3, r3.getIntProperty("msg"));
+
+ ((AbstractJMSMessage)r3).acknowledgeThis();
+ consumerConnection.close();
+ }
+
+ public void testTwoConsumersBytesFlowControl() throws Exception
+ {
+ _queue = (Queue) getInitialContext().lookup("queue");
+
+ //Create Client
+ _clientConnection = getConnection();
+
+ _clientConnection.start();
+
+ _clientSession = _clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ //Ensure _queue is created
+ _clientSession.createConsumer(_queue).close();
+
+ Connection producerConnection = getConnection();
+
+ producerConnection.start();
+
+ Session producerSession = producerConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ MessageProducer producer = producerSession.createProducer(_queue);
+
+ BytesMessage m1 = producerSession.createBytesMessage();
+ m1.writeBytes(new byte[128]);
+ m1.setIntProperty("msg", 1);
+ producer.send(m1);
+ BytesMessage m2 = producerSession.createBytesMessage();
+ m2.writeBytes(new byte[256]);
+ m2.setIntProperty("msg", 2);
+ producer.send(m2);
+ BytesMessage m3 = producerSession.createBytesMessage();
+ m3.writeBytes(new byte[128]);
+ m3.setIntProperty("msg", 3);
+ producer.send(m3);
+
+ producer.close();
+ producerSession.close();
+ producerConnection.close();
+
+ Connection consumerConnection = getConnection();
+ Session consumerSession1 = consumerConnection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+ ((AMQSession_0_8) consumerSession1).setPrefetchLimits(0, 256);
+ MessageConsumer recv1 = consumerSession1.createConsumer(_queue);
+
+ consumerConnection.start();
+
+ Message r1 = recv1.receive(RECEIVE_TIMEOUT);
+ assertNotNull("First message not received", r1);
+ assertEquals("Messages in wrong order", 1, r1.getIntProperty("msg"));
+
+ Message r2 = recv1.receive(RECEIVE_TIMEOUT);
+ assertNull("Second message incorrectly delivered", r2);
+
+ Session consumerSession2 = consumerConnection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+ ((AMQSession_0_8) consumerSession2).setPrefetchLimits(0, 256);
+ MessageConsumer recv2 = consumerSession2.createConsumer(_queue);
+
+ r2 = recv2.receive(RECEIVE_TIMEOUT);
+ assertNotNull("Second message not received", r2);
+ assertEquals("Messages in wrong order", 2, r2.getIntProperty("msg"));
+
+ Message r3 = recv2.receive(RECEIVE_TIMEOUT);
+ assertNull("Third message incorrectly delivered", r3);
+
+ r3 = recv1.receive(RECEIVE_TIMEOUT);
+ assertNotNull("Third message not received", r3);
+ assertEquals("Messages in wrong order", 3, r3.getIntProperty("msg"));
+
+ r2.acknowledge();
+ r3.acknowledge();
+ recv1.close();
+ recv2.close();
+ consumerSession1.close();
+ consumerSession2.close();
+ consumerConnection.close();
+
+ }
+
+ public static void main(String args[]) throws Throwable
+ {
+ FlowControlTest test = new FlowControlTest();
+
+ int run = 0;
+ while (true)
+ {
+ System.err.println("Test Run:" + ++run);
+ Thread.sleep(1000);
+ try
+ {
+ test.startBroker();
+ test.testBasicBytesFlowControl();
+
+ Thread.sleep(1000);
+ }
+ finally
+ {
+ test.stopBroker();
+ }
+ }
+ }
+}
+
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserAutoAckTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserAutoAckTest.java
new file mode 100644
index 0000000000..97d825177c
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserAutoAckTest.java
@@ -0,0 +1,528 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.test.client;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.test.utils.FailoverBaseCase;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.QueueBrowser;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+import javax.naming.NamingException;
+import java.util.Enumeration;
+import java.util.Random;
+
+public class QueueBrowserAutoAckTest extends FailoverBaseCase
+{
+ protected Connection _clientConnection;
+ protected Session _clientSession;
+ protected Queue _queue;
+ protected static final String MESSAGE_ID_PROPERTY = "MessageIDProperty";
+ protected boolean CLUSTERED = Boolean.getBoolean("profile.clustered");
+
+ public void setUp() throws Exception
+ {
+ super.setUp();
+
+ //Create Client
+ _clientConnection = getConnection();
+ _clientConnection.start();
+
+ setupSession();
+
+ _queue = _clientSession.createQueue(getTestQueueName());
+ _clientSession.createConsumer(_queue).close();
+
+ //Ensure there are no messages on the queue to start with.
+ checkQueueDepth(0);
+ }
+
+ protected void setupSession() throws Exception
+ {
+ _clientSession = _clientConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ }
+
+ public void tearDown() throws Exception
+ {
+ if (_clientConnection != null)
+ {
+ _clientConnection.close();
+ }
+
+ super.tearDown();
+ }
+
+ protected void sendMessages(int num) throws JMSException
+ {
+ Connection producerConnection = null;
+ try
+ {
+ producerConnection = getConnection();
+ }
+ catch (Exception e)
+ {
+ fail("Unable to lookup connection in JNDI.");
+ }
+
+ sendMessages(producerConnection, num);
+ }
+
+ protected void sendMessages(String connection, int num) throws JMSException
+ {
+ Connection producerConnection = null;
+ try
+ {
+ producerConnection = getConnectionFactory(connection).createConnection("guest", "guest");
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("Unable to lookup connection in JNDI.");
+ }
+ sendMessages(producerConnection, num);
+ }
+
+
+ protected void sendMessages(Connection producerConnection, int messageSendCount) throws JMSException
+ {
+ producerConnection.start();
+
+ Session producerSession = producerConnection.createSession(true, Session.AUTO_ACKNOWLEDGE);
+
+ //Ensure _queue is created
+ producerSession.createConsumer(_queue).close();
+
+ MessageProducer producer = producerSession.createProducer(_queue);
+
+ for (int messsageID = 0; messsageID < messageSendCount; messsageID++)
+ {
+ TextMessage textMsg = producerSession.createTextMessage("Message " + messsageID);
+ textMsg.setIntProperty(MESSAGE_ID_PROPERTY, messsageID);
+ producer.send(textMsg);
+ }
+ producerSession.commit();
+
+ producerConnection.close();
+ }
+
+ /**
+ * Using the Protocol getQueueDepth method ensure that the correct number of messages are on the queue.
+ *
+ * Also uses a QueueBrowser as a second method of validating the message count on the queue.
+ *
+ * @param expectedDepth The expected Queue depth
+ * @throws JMSException on error
+ */
+ protected void checkQueueDepth(int expectedDepth) throws JMSException
+ {
+
+ // create QueueBrowser
+ _logger.info("Creating Queue Browser");
+
+ QueueBrowser queueBrowser = _clientSession.createBrowser(_queue);
+
+ // check for messages
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Checking for " + expectedDepth + " messages with QueueBrowser");
+ }
+
+ //Check what the session believes the queue count to be.
+ long queueDepth = 0;
+
+ try
+ {
+ queueDepth = ((AMQSession) _clientSession).getQueueDepth((AMQDestination) _queue);
+ }
+ catch (AMQException e)
+ {
+ }
+
+ assertEquals("Session reports Queue expectedDepth not as expected", expectedDepth, queueDepth);
+
+
+
+ // Browse the queue to get a second opinion
+ int msgCount = 0;
+ Enumeration msgs = queueBrowser.getEnumeration();
+
+ while (msgs.hasMoreElements())
+ {
+ msgs.nextElement();
+ msgCount++;
+ }
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("Found " + msgCount + " messages total in browser");
+ }
+
+ // check to see if all messages found
+ assertEquals("Browser did not find all messages", expectedDepth, msgCount);
+
+ //Close browser
+ queueBrowser.close();
+ }
+
+ protected void closeBrowserBeforeAfterGetNext(int messageCount) throws JMSException
+ {
+ QueueBrowser queueBrowser = _clientSession.createBrowser(_queue);
+
+ Enumeration msgs = queueBrowser.getEnumeration();
+
+ int msgCount = 0;
+
+ while (msgs.hasMoreElements() && msgCount < messageCount)
+ {
+ msgs.nextElement();
+ msgCount++;
+ }
+
+ try
+ {
+ queueBrowser.close();
+ }
+ catch (JMSException e)
+ {
+ fail("Close should happen without error:" + e.getMessage());
+ }
+ }
+
+ /**
+ * This method checks that multiple calls to getEnumeration() on a queueBrowser provide the same behaviour.
+ *
+ * @param sentMessages The number of messages sent
+ * @param browserEnumerationCount The number of times to call getEnumeration()
+ * @throws JMSException
+ */
+ protected void checkMultipleGetEnum(int sentMessages, int browserEnumerationCount) throws JMSException
+ {
+ QueueBrowser queueBrowser = _clientSession.createBrowser(_queue);
+
+ for (int count = 0; count < browserEnumerationCount; count++)
+ {
+ _logger.info("Checking getEnumeration:" + count);
+ Enumeration msgs = queueBrowser.getEnumeration();
+
+ int msgCount = 0;
+
+ while (msgs.hasMoreElements())
+ {
+ msgs.nextElement();
+ msgCount++;
+ }
+
+ // Verify that the browser can see all the messages sent.
+ assertEquals(sentMessages, msgCount);
+ }
+
+ try
+ {
+ queueBrowser.close();
+ }
+ catch (JMSException e)
+ {
+ fail("Close should happen without error:" + e.getMessage());
+ }
+ }
+
+ protected void checkOverlappingMultipleGetEnum(int expectedMessages, int browserEnumerationCount) throws JMSException
+ {
+ checkOverlappingMultipleGetEnum(expectedMessages, browserEnumerationCount, null);
+ }
+
+ protected void checkOverlappingMultipleGetEnum(int expectedMessages, int browserEnumerationCount, String selector) throws JMSException
+ {
+ QueueBrowser queueBrowser = selector == null ?
+ _clientSession.createBrowser(_queue) : _clientSession.createBrowser(_queue);
+// _clientSession.createBrowser(_queue) : _clientSession.createBrowser(_queue, selector);
+
+ Enumeration[] msgs = new Enumeration[browserEnumerationCount];
+ int[] msgCount = new int[browserEnumerationCount];
+
+ //create Enums
+ for (int count = 0; count < browserEnumerationCount; count++)
+ {
+ msgs[count] = queueBrowser.getEnumeration();
+ }
+
+ //interleave reads
+ for (int cnt = 0; cnt < expectedMessages; cnt++)
+ {
+ for (int count = 0; count < browserEnumerationCount; count++)
+ {
+ if (msgs[count].hasMoreElements())
+ {
+ msgs[count].nextElement();
+ msgCount[count]++;
+ }
+ }
+ }
+
+ //validate all browsers get right message count.
+ for (int count = 0; count < browserEnumerationCount; count++)
+ {
+ assertEquals(msgCount[count], expectedMessages);
+ }
+
+ try
+ {
+ queueBrowser.close();
+ }
+ catch (JMSException e)
+ {
+ fail("Close should happen without error:" + e.getMessage());
+ }
+ }
+
+ protected void validate(int messages) throws JMSException
+ {
+ //Create a new connection to validate message content
+ Connection connection = null;
+
+ try
+ {
+ connection = getConnection();
+ }
+ catch (Exception e)
+ {
+ fail("Unable to make validation connection");
+ }
+
+ Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ connection.start();
+
+ MessageConsumer consumer = session.createConsumer(_queue);
+
+ _logger.info("Verify messages are still on the queue");
+
+ Message tempMsg;
+
+ for (int msgCount = 0; msgCount < messages; msgCount++)
+ {
+ tempMsg = (TextMessage) consumer.receive(RECEIVE_TIMEOUT);
+ if (tempMsg == null)
+ {
+ fail("Message " + msgCount + " not retrieved from queue");
+ }
+ }
+
+ //Close this new connection
+ connection.close();
+
+ _logger.info("All messages recevied from queue");
+
+ //ensure no message left.
+ checkQueueDepth(0);
+ }
+
+ protected void checkQueueDepthWithSelectors(int totalMessages, int clients) throws JMSException
+ {
+
+ String selector = MESSAGE_ID_PROPERTY + " % " + clients;
+
+ checkOverlappingMultipleGetEnum(totalMessages / clients, clients, selector);
+ }
+
+
+ /**
+ * This tests you can browse an empty queue, see QPID-785
+ *
+ * @throws Exception
+ */
+ public void testBrowsingEmptyQueue() throws Exception
+ {
+ checkQueueDepth(0);
+ }
+
+ /*
+ * Test Messages Remain on Queue
+ * Create a queu and send messages to it. Browse them and then receive them all to verify they were still there
+ *
+ */
+ public void testQueueBrowserMsgsRemainOnQueue() throws Exception
+ {
+ int messages = 10;
+
+ sendMessages(messages);
+
+ checkQueueDepth(messages);
+
+ validate(messages);
+ }
+
+
+ public void testClosingBrowserMidReceiving() throws NamingException, JMSException
+ {
+ int messages = 100;
+
+ sendMessages(messages);
+
+ checkQueueDepth(messages);
+
+ closeBrowserBeforeAfterGetNext(10);
+
+ validate(messages);
+ }
+
+ /**
+ * This tests that multiple getEnumerations on a QueueBrowser return the required number of messages.
+ * @throws NamingException
+ * @throws JMSException
+ */
+ public void testMultipleGetEnum() throws NamingException, JMSException
+ {
+ int messages = 10;
+
+ sendMessages(messages);
+
+ checkQueueDepth(messages);
+
+ checkMultipleGetEnum(messages, 5);
+
+ validate(messages);
+ }
+
+ public void testMultipleOverlappingGetEnum() throws NamingException, JMSException
+ {
+ int messages = 25;
+
+ sendMessages(messages);
+
+ checkQueueDepth(messages);
+
+ checkOverlappingMultipleGetEnum(messages, 5);
+
+ validate(messages);
+ }
+
+
+ public void testBrowsingWithSelector() throws JMSException
+ {
+ int messages = 40;
+
+ sendMessages(messages);
+
+ checkQueueDepth(messages);
+
+ for (int clients = 2; clients <= 10; clients++)
+ {
+ checkQueueDepthWithSelectors(messages, clients);
+ }
+
+ validate(messages);
+ }
+
+ /**
+ * Testing that a QueueBrowser doesn't actually consume messages from a broker when it fails over.
+ * @throws JMSException
+ */
+ public void testFailoverWithQueueBrowser() throws JMSException
+ {
+ int messages = 5;
+
+ sendMessages("connection1", messages);
+ if (!CLUSTERED)
+ {
+ sendMessages("connection2", messages);
+ }
+
+ checkQueueDepth(messages);
+
+ _logger.info("Creating Queue Browser");
+ QueueBrowser queueBrowser = _clientSession.createBrowser(_queue);
+
+ long queueDepth = 0;
+
+ try
+ {
+ queueDepth = ((AMQSession) _clientSession).getQueueDepth((AMQDestination) _queue);
+ }
+ catch (AMQException e)
+ {
+ fail("Caught exception getting queue depth: " + e.getMessage());
+ }
+
+ assertEquals("Session reports Queue depth not as expected", messages, queueDepth);
+
+ int msgCount = 0;
+ int failPoint = 0;
+
+ failPoint = new Random().nextInt(messages) + 1;
+
+ Enumeration msgs = queueBrowser.getEnumeration();
+ while (msgs.hasMoreElements())
+ {
+ msgs.nextElement();
+ msgCount++;
+
+ if (msgCount == failPoint)
+ {
+ failBroker(getFailingPort());
+ }
+ }
+
+ assertTrue("We should get atleast " + messages + " msgs.", msgCount >= messages);
+
+ if (_logger.isDebugEnabled())
+ {
+ _logger.debug("QBAAT Found " + msgCount + " messages total in browser");
+ }
+
+ //Close browser
+ queueBrowser.close();
+
+ _logger.info("Closed Queue Browser, validating messages on broker.");
+
+ //Validate all messages still on Broker
+ validate(messages);
+ }
+
+ public void testFailoverAsQueueBrowserCreated() throws JMSException
+ {
+ // The IoServiceListenerSupport seems to get stuck in with a managedSession that isn't closing when requested.
+ // So it hangs waiting for the session.
+ int messages = 50;
+
+ sendMessages("connection1", messages);
+ if (!CLUSTERED)
+ {
+ sendMessages("connection2", messages);
+ }
+
+ failBroker(getFailingPort());
+
+ checkQueueDepth(messages);
+
+ //Validate all messages still on Broker 1
+ validate(messages);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserClientAckTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserClientAckTest.java
new file mode 100644
index 0000000000..f30b8043ad
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserClientAckTest.java
@@ -0,0 +1,34 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.client;
+
+import javax.jms.Session;
+
+public class QueueBrowserClientAckTest extends QueueBrowserAutoAckTest
+{
+
+
+ protected void setupSession() throws Exception
+ {
+ _clientSession = _clientConnection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserDupsOkTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserDupsOkTest.java
new file mode 100644
index 0000000000..b19809b8f2
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserDupsOkTest.java
@@ -0,0 +1,31 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.client;
+
+import javax.jms.Session;
+
+public class QueueBrowserDupsOkTest extends QueueBrowserAutoAckTest
+{
+ protected void setupSession() throws Exception
+ {
+ _clientSession = _clientConnection.createSession(false, Session.DUPS_OK_ACKNOWLEDGE);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserNoAckTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserNoAckTest.java
new file mode 100644
index 0000000000..c97343464c
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserNoAckTest.java
@@ -0,0 +1,33 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.client;
+
+import org.apache.qpid.client.AMQSession;
+
+
+public class QueueBrowserNoAckTest extends QueueBrowserAutoAckTest
+{
+
+ protected void setupSession() throws Exception
+ {
+ _clientSession = _clientConnection.createSession(false, AMQSession.NO_ACKNOWLEDGE);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserPreAckTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserPreAckTest.java
new file mode 100644
index 0000000000..bb1c0d3698
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserPreAckTest.java
@@ -0,0 +1,32 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.client;
+
+import org.apache.qpid.client.AMQSession;
+
+public class QueueBrowserPreAckTest extends QueueBrowserAutoAckTest
+{
+
+ protected void setupSession() throws Exception
+ {
+ _clientSession = _clientConnection.createSession(false, AMQSession.PRE_ACKNOWLEDGE);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserTransactedTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserTransactedTest.java
new file mode 100644
index 0000000000..d79788f017
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/QueueBrowserTransactedTest.java
@@ -0,0 +1,31 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.client;
+
+import javax.jms.Session;
+
+public class QueueBrowserTransactedTest extends QueueBrowserAutoAckTest
+{
+ protected void setupSession() throws Exception
+ {
+ _clientSession = _clientConnection.createSession(true, Session.SESSION_TRANSACTED);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/RollbackOrderTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/RollbackOrderTest.java
new file mode 100644
index 0000000000..b944f2ddd2
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/RollbackOrderTest.java
@@ -0,0 +1,194 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.client;
+
+import org.apache.qpid.test.utils.*;
+import javax.jms.*;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import junit.framework.ComparisonFailure;
+import junit.framework.AssertionFailedError;
+
+/**
+ * RollbackOrderTest, QPID-1864, QPID-1871
+ *
+ * Description:
+ *
+ * The problem that this test is exposing is that the dispatcher used to be capable
+ * of holding on to a message when stopped. This ment that when the rollback was
+ * called and the dispatcher stopped it may have hold of a message. So after all
+ * the local queues(preDeliveryQueue, SynchronousQueue, PostDeliveryTagQueue)
+ * have been cleared the client still had a single message, the one the
+ * dispatcher was holding on to.
+ *
+ * As a result the TxRollback operation would run and then release the dispatcher.
+ * Whilst the dispatcher would then proceed to reject the message it was holiding
+ * the Broker would already have resent that message so the rejection would silently
+ * fail.
+ *
+ * And the client would receieve that single message 'early', depending on the
+ * number of messages already recevied when rollback was called.
+ *
+ *
+ * Aims:
+ *
+ * The tests puts 50 messages on to the queue.
+ *
+ * The test then tries to cause the dispatcher to stop whilst it is in the process
+ * of moving a message from the preDeliveryQueue to a consumers sychronousQueue.
+ *
+ * To exercise this path we have 50 message flowing to the client to give the
+ * dispatcher a bit of work to do moving messages.
+ *
+ * Then we loop - 10 times
+ * - Validating that the first message received is always message 1.
+ * - Receive a few more so that there are a few messages to reject.
+ * - call rollback, to try and catch the dispatcher mid process.
+ *
+ * Outcome:
+ *
+ * The hope is that we catch the dispatcher mid process and cause a BasicReject
+ * to fail. Which will be indicated in the log but will also cause that failed
+ * rejected message to be the next to be delivered which will not be message 1
+ * as expected.
+ *
+ * We are testing a race condition here but we can check through the log file if
+ * the race condition occured. However, performing that check will only validate
+ * the problem exists and will not be suitable as part of a system test.
+ *
+ */
+public class RollbackOrderTest extends QpidBrokerTestCase
+{
+
+ private Connection _connection;
+ private Queue _queue;
+ private Session _session;
+ private MessageConsumer _consumer;
+
+ @Override public void setUp() throws Exception
+ {
+ super.setUp();
+ _connection = getConnection();
+
+ _session = _connection.createSession(true, Session.SESSION_TRANSACTED);
+ _queue = _session.createQueue(getTestQueueName());
+ _consumer = _session.createConsumer(_queue);
+
+ //Send more messages so it is more likely that the dispatcher is
+ // processing on rollback.
+ sendMessage(_session, _queue, 50);
+ _session.commit();
+
+ }
+
+ public void testOrderingAfterRollback() throws Exception
+ {
+ //Start the session now so we
+ _connection.start();
+
+ for (int i = 0; i < 20; i++)
+ {
+ Message msg = _consumer.receive();
+ assertEquals("Incorrect Message Received", 0, msg.getIntProperty(INDEX));
+
+ // Pull additional messages through so we have some reject work to do
+ for (int m=0; m < 5 ; m++)
+ {
+ _consumer.receive();
+ }
+
+ System.err.println("ROT-Rollback");
+ _logger.warn("ROT-Rollback");
+ _session.rollback();
+ }
+ }
+
+ public void testOrderingAfterRollbackOnMessage() throws Exception
+ {
+ final CountDownLatch count= new CountDownLatch(20);
+ final Exception exceptions[] = new Exception[20];
+ final AtomicBoolean failed = new AtomicBoolean(false);
+
+ _consumer.setMessageListener(new MessageListener()
+ {
+
+ public void onMessage(Message message)
+ {
+
+ Message msg = message;
+ try
+ {
+ count.countDown();
+ assertEquals("Incorrect Message Received", 0, msg.getIntProperty(INDEX));
+
+ _session.rollback();
+ }
+ catch (JMSException e)
+ {
+ System.out.println("Error:" + e.getMessage());
+ exceptions[(int)count.getCount()] = e;
+ }
+ catch (AssertionFailedError cf)
+ {
+ // End Test if Equality test fails
+ while (count.getCount() != 0)
+ {
+ count.countDown();
+ }
+
+ System.out.println("Error:" + cf.getMessage());
+ System.err.println(cf.getMessage());
+ cf.printStackTrace();
+ failed.set(true);
+ }
+ }
+ });
+ //Start the session now so we
+ _connection.start();
+
+ count.await();
+
+ for (Exception e : exceptions)
+ {
+ if (e != null)
+ {
+ System.err.println(e.getMessage());
+ e.printStackTrace();
+ failed.set(true);
+ }
+ }
+
+// _consumer.close();
+ _connection.close();
+
+ assertFalse("Exceptions thrown during test run, Check Std.err.", failed.get());
+ }
+
+ @Override public void tearDown() throws Exception
+ {
+
+ drainQueue(_queue);
+
+ super.tearDown();
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/destination/AddressBasedDestinationTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/destination/AddressBasedDestinationTest.java
new file mode 100644
index 0000000000..fb389c5345
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/destination/AddressBasedDestinationTest.java
@@ -0,0 +1,1072 @@
+package org.apache.qpid.test.client.destination;
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.QueueReceiver;
+import javax.jms.QueueSession;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+import javax.jms.Topic;
+import javax.jms.TopicSession;
+import javax.jms.TopicSubscriber;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+
+import org.apache.qpid.client.AMQAnyDestination;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.AMQSession_0_10;
+import org.apache.qpid.client.messaging.address.Node.ExchangeNode;
+import org.apache.qpid.client.messaging.address.Node.QueueNode;
+import org.apache.qpid.jndi.PropertiesFileInitialContextFactory;
+import org.apache.qpid.messaging.Address;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AddressBasedDestinationTest extends QpidBrokerTestCase
+{
+ private static final Logger _logger = LoggerFactory.getLogger(AddressBasedDestinationTest.class);
+ private Connection _connection;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ _connection = getConnection() ;
+ _connection.start();
+ }
+
+ @Override
+ public void tearDown() throws Exception
+ {
+ _connection.close();
+ super.tearDown();
+ }
+
+ public void testCreateOptions() throws Exception
+ {
+ Session jmsSession = _connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
+ MessageProducer prod;
+ MessageConsumer cons;
+
+ // default (create never, assert never) -------------------
+ // create never --------------------------------------------
+ String addr1 = "ADDR:testQueue1";
+ AMQDestination dest = new AMQAnyDestination(addr1);
+ try
+ {
+ cons = jmsSession.createConsumer(dest);
+ }
+ catch(JMSException e)
+ {
+ assertTrue(e.getMessage().contains("The name 'testQueue1' supplied in the address " +
+ "doesn't resolve to an exchange or a queue"));
+ }
+
+ try
+ {
+ prod = jmsSession.createProducer(dest);
+ }
+ catch(JMSException e)
+ {
+ assertTrue(e.getCause().getCause().getMessage().contains("The name 'testQueue1' supplied in the address " +
+ "doesn't resolve to an exchange or a queue"));
+ }
+
+ assertFalse("Queue should not be created",(
+ (AMQSession_0_10)jmsSession).isQueueExist(dest, (QueueNode)dest.getSourceNode() ,true));
+
+
+ // create always -------------------------------------------
+ addr1 = "ADDR:testQueue1; { create: always }";
+ dest = new AMQAnyDestination(addr1);
+ cons = jmsSession.createConsumer(dest);
+
+ assertTrue("Queue not created as expected",(
+ (AMQSession_0_10)jmsSession).isQueueExist(dest,(QueueNode)dest.getSourceNode(), true));
+ assertTrue("Queue not bound as expected",(
+ (AMQSession_0_10)jmsSession).isQueueBound("",
+ dest.getAddressName(),dest.getAddressName(), dest.getSourceNode().getDeclareArgs()));
+
+ // create receiver -----------------------------------------
+ addr1 = "ADDR:testQueue2; { create: receiver }";
+ dest = new AMQAnyDestination(addr1);
+ try
+ {
+ prod = jmsSession.createProducer(dest);
+ }
+ catch(JMSException e)
+ {
+ assertTrue(e.getCause().getCause().getMessage().contains("The name 'testQueue2' supplied in the address " +
+ "doesn't resolve to an exchange or a queue"));
+ }
+
+ assertFalse("Queue should not be created",(
+ (AMQSession_0_10)jmsSession).isQueueExist(dest,(QueueNode)dest.getSourceNode(), true));
+
+
+ cons = jmsSession.createConsumer(dest);
+
+ assertTrue("Queue not created as expected",(
+ (AMQSession_0_10)jmsSession).isQueueExist(dest,(QueueNode)dest.getSourceNode(), true));
+ assertTrue("Queue not bound as expected",(
+ (AMQSession_0_10)jmsSession).isQueueBound("",
+ dest.getAddressName(),dest.getAddressName(), dest.getSourceNode().getDeclareArgs()));
+
+ // create never --------------------------------------------
+ addr1 = "ADDR:testQueue3; { create: never }";
+ dest = new AMQAnyDestination(addr1);
+ try
+ {
+ cons = jmsSession.createConsumer(dest);
+ }
+ catch(JMSException e)
+ {
+ assertTrue(e.getMessage().contains("The name 'testQueue3' supplied in the address " +
+ "doesn't resolve to an exchange or a queue"));
+ }
+
+ try
+ {
+ prod = jmsSession.createProducer(dest);
+ }
+ catch(JMSException e)
+ {
+ assertTrue(e.getCause().getCause().getMessage().contains("The name 'testQueue3' supplied in the address " +
+ "doesn't resolve to an exchange or a queue"));
+ }
+
+ assertFalse("Queue should not be created",(
+ (AMQSession_0_10)jmsSession).isQueueExist(dest,(QueueNode)dest.getSourceNode(), true));
+
+ // create sender ------------------------------------------
+ addr1 = "ADDR:testQueue3; { create: sender }";
+ dest = new AMQAnyDestination(addr1);
+
+ try
+ {
+ cons = jmsSession.createConsumer(dest);
+ }
+ catch(JMSException e)
+ {
+ assertTrue(e.getMessage().contains("The name 'testQueue3' supplied in the address " +
+ "doesn't resolve to an exchange or a queue"));
+ }
+ assertFalse("Queue should not be created",(
+ (AMQSession_0_10)jmsSession).isQueueExist(dest,(QueueNode)dest.getSourceNode(), true));
+
+ prod = jmsSession.createProducer(dest);
+ assertTrue("Queue not created as expected",(
+ (AMQSession_0_10)jmsSession).isQueueExist(dest,(QueueNode)dest.getSourceNode(), true));
+ assertTrue("Queue not bound as expected",(
+ (AMQSession_0_10)jmsSession).isQueueBound("",
+ dest.getAddressName(),dest.getAddressName(), dest.getSourceNode().getDeclareArgs()));
+
+ }
+
+ public void testCreateQueue() throws Exception
+ {
+ Session jmsSession = _connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
+
+ String addr = "ADDR:my-queue/hello; " +
+ "{" +
+ "create: always, " +
+ "node: " +
+ "{" +
+ "durable: true ," +
+ "x-declare: " +
+ "{" +
+ "auto-delete: true," +
+ "arguments: {" +
+ "'qpid.max_size': 1000," +
+ "'qpid.max_count': 100" +
+ "}" +
+ "}, " +
+ "x-bindings: [{exchange : 'amq.direct', key : test}, " +
+ "{exchange : 'amq.fanout'}," +
+ "{exchange: 'amq.match', arguments: {x-match: any, dep: sales, loc: CA}}," +
+ "{exchange : 'amq.topic', key : 'a.#'}" +
+ "]," +
+
+ "}" +
+ "}";
+ AMQDestination dest = new AMQAnyDestination(addr);
+ MessageConsumer cons = jmsSession.createConsumer(dest);
+
+ assertTrue("Queue not created as expected",(
+ (AMQSession_0_10)jmsSession).isQueueExist(dest,(QueueNode)dest.getSourceNode(), true));
+
+ assertTrue("Queue not bound as expected",(
+ (AMQSession_0_10)jmsSession).isQueueBound("",
+ dest.getAddressName(),dest.getAddressName(), null));
+
+ assertTrue("Queue not bound as expected",(
+ (AMQSession_0_10)jmsSession).isQueueBound("amq.direct",
+ dest.getAddressName(),"test", null));
+
+ assertTrue("Queue not bound as expected",(
+ (AMQSession_0_10)jmsSession).isQueueBound("amq.fanout",
+ dest.getAddressName(),null, null));
+
+ assertTrue("Queue not bound as expected",(
+ (AMQSession_0_10)jmsSession).isQueueBound("amq.topic",
+ dest.getAddressName(),"a.#", null));
+
+ Map<String,Object> args = new HashMap<String,Object>();
+ args.put("x-match","any");
+ args.put("dep","sales");
+ args.put("loc","CA");
+ assertTrue("Queue not bound as expected",(
+ (AMQSession_0_10)jmsSession).isQueueBound("amq.match",
+ dest.getAddressName(),null, args));
+
+ }
+
+ public void testCreateExchange() throws Exception
+ {
+ Session jmsSession = _connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
+
+ String addr = "ADDR:my-exchange/hello; " +
+ "{ " +
+ "create: always, " +
+ "node: " +
+ "{" +
+ "type: topic, " +
+ "x-declare: " +
+ "{ " +
+ "type:direct, " +
+ "auto-delete: true, " +
+ "arguments: {" +
+ "'qpid.msg_sequence': 1, " +
+ "'qpid.ive': 1" +
+ "}" +
+ "}" +
+ "}" +
+ "}";
+
+ AMQDestination dest = new AMQAnyDestination(addr);
+ MessageConsumer cons = jmsSession.createConsumer(dest);
+
+ assertTrue("Exchange not created as expected",(
+ (AMQSession_0_10)jmsSession).isExchangeExist(dest, (ExchangeNode)dest.getTargetNode() , true));
+
+ // The existence of the queue is implicitly tested here
+ assertTrue("Queue not bound as expected",(
+ (AMQSession_0_10)jmsSession).isQueueBound("my-exchange",
+ dest.getQueueName(),"hello", Collections.<String, Object>emptyMap()));
+
+ // The client should be able to query and verify the existence of my-exchange (QPID-2774)
+ dest = new AMQAnyDestination("ADDR:my-exchange; {create: never}");
+ cons = jmsSession.createConsumer(dest);
+ }
+
+ public void checkQueueForBindings(Session jmsSession, AMQDestination dest,String headersBinding) throws Exception
+ {
+ assertTrue("Queue not created as expected",(
+ (AMQSession_0_10)jmsSession).isQueueExist(dest,(QueueNode)dest.getSourceNode(), true));
+
+ assertTrue("Queue not bound as expected",(
+ (AMQSession_0_10)jmsSession).isQueueBound("",
+ dest.getAddressName(),dest.getAddressName(), null));
+
+ assertTrue("Queue not bound as expected",(
+ (AMQSession_0_10)jmsSession).isQueueBound("amq.direct",
+ dest.getAddressName(),"test", null));
+
+ assertTrue("Queue not bound as expected",(
+ (AMQSession_0_10)jmsSession).isQueueBound("amq.topic",
+ dest.getAddressName(),"a.#", null));
+
+ Address a = Address.parse(headersBinding);
+ assertTrue("Queue not bound as expected",(
+ (AMQSession_0_10)jmsSession).isQueueBound("amq.match",
+ dest.getAddressName(),null, a.getOptions()));
+ }
+
+ /**
+ * Test goal: Verifies that a producer and consumer creation triggers the correct
+ * behavior for x-bindings specified in node props.
+ */
+ public void testBindQueueWithArgs() throws Exception
+ {
+
+ Session jmsSession = _connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
+ String headersBinding = "{exchange: 'amq.match', arguments: {x-match: any, dep: sales, loc: CA}}";
+
+ String addr = "node: " +
+ "{" +
+ "durable: true ," +
+ "x-declare: " +
+ "{ " +
+ "auto-delete: true," +
+ "arguments: {'qpid.max_count': 100}" +
+ "}, " +
+ "x-bindings: [{exchange : 'amq.direct', key : test}, " +
+ "{exchange : 'amq.topic', key : 'a.#'}," +
+ headersBinding +
+ "]" +
+ "}" +
+ "}";
+
+
+ AMQDestination dest1 = new AMQAnyDestination("ADDR:my-queue/hello; {create: receiver, " +addr);
+ MessageConsumer cons = jmsSession.createConsumer(dest1);
+ checkQueueForBindings(jmsSession,dest1,headersBinding);
+
+ AMQDestination dest2 = new AMQAnyDestination("ADDR:my-queue2/hello; {create: sender, " +addr);
+ MessageProducer prod = jmsSession.createProducer(dest2);
+ checkQueueForBindings(jmsSession,dest2,headersBinding);
+ }
+
+ /**
+ * Test goal: Verifies the capacity property in address string is handled properly.
+ * Test strategy:
+ * Creates a destination with capacity 10.
+ * Creates consumer with client ack.
+ * Sends 15 messages to the queue, tries to receive 10.
+ * Tries to receive the 11th message and checks if its null.
+ *
+ * Since capacity is 10 and we haven't acked any messages,
+ * we should not have received the 11th.
+ *
+ * Acks the 10th message and verifies we receive the rest of the msgs.
+ */
+ public void testCapacity() throws Exception
+ {
+ verifyCapacity("ADDR:my-queue; {create: always, link:{capacity: 10}}");
+ }
+
+ public void testSourceAndTargetCapacity() throws Exception
+ {
+ verifyCapacity("ADDR:my-queue; {create: always, link:{capacity: {source:10, target:15} }}");
+ }
+
+ private void verifyCapacity(String address) throws Exception
+ {
+ if (!isCppBroker())
+ {
+ _logger.info("Not C++ broker, exiting test");
+ return;
+ }
+
+ Session jmsSession = _connection.createSession(false,Session.CLIENT_ACKNOWLEDGE);
+
+ AMQDestination dest = new AMQAnyDestination(address);
+ MessageConsumer cons = jmsSession.createConsumer(dest);
+ MessageProducer prod = jmsSession.createProducer(dest);
+
+ for (int i=0; i< 15; i++)
+ {
+ prod.send(jmsSession.createTextMessage("msg" + i) );
+ }
+
+ for (int i=0; i< 9; i++)
+ {
+ cons.receive();
+ }
+ Message msg = cons.receive(RECEIVE_TIMEOUT);
+ assertNotNull("Should have received the 10th message",msg);
+ assertNull("Shouldn't have received the 11th message as capacity is 10",cons.receive(RECEIVE_TIMEOUT));
+ msg.acknowledge();
+ for (int i=11; i<16; i++)
+ {
+ assertNotNull("Should have received the " + i + "th message as we acked the last 10",cons.receive(RECEIVE_TIMEOUT));
+ }
+ }
+
+ /**
+ * Test goal: Verifies if the new address format based destinations
+ * can be specified and loaded correctly from the properties file.
+ *
+ */
+ public void testLoadingFromPropertiesFile() throws Exception
+ {
+ Hashtable<String,String> map = new Hashtable<String,String>();
+ map.put("destination.myQueue1", "ADDR:my-queue/hello; {create: always, node: " +
+ "{x-declare: {auto-delete: true, arguments : {'qpid.max_size': 1000}}}}");
+
+ map.put("destination.myQueue2", "ADDR:my-queue2; { create: receiver }");
+
+ map.put("destination.myQueue3", "BURL:direct://amq.direct/my-queue3?routingkey='test'");
+
+ PropertiesFileInitialContextFactory props = new PropertiesFileInitialContextFactory();
+ Context ctx = props.getInitialContext(map);
+
+ AMQDestination dest1 = (AMQDestination)ctx.lookup("myQueue1");
+ AMQDestination dest2 = (AMQDestination)ctx.lookup("myQueue2");
+ AMQDestination dest3 = (AMQDestination)ctx.lookup("myQueue3");
+
+ Session jmsSession = _connection.createSession(false,Session.CLIENT_ACKNOWLEDGE);
+ MessageConsumer cons1 = jmsSession.createConsumer(dest1);
+ MessageConsumer cons2 = jmsSession.createConsumer(dest2);
+ MessageConsumer cons3 = jmsSession.createConsumer(dest3);
+
+ assertTrue("Destination1 was not created as expected",(
+ (AMQSession_0_10)jmsSession).isQueueExist(dest1,(QueueNode)dest1.getSourceNode(), true));
+
+ assertTrue("Destination1 was not bound as expected",(
+ (AMQSession_0_10)jmsSession).isQueueBound("",
+ dest1.getAddressName(),dest1.getAddressName(), null));
+
+ assertTrue("Destination2 was not created as expected",(
+ (AMQSession_0_10)jmsSession).isQueueExist(dest2,(QueueNode)dest2.getSourceNode(), true));
+
+ assertTrue("Destination2 was not bound as expected",(
+ (AMQSession_0_10)jmsSession).isQueueBound("",
+ dest2.getAddressName(),dest2.getAddressName(), null));
+
+ MessageProducer producer = jmsSession.createProducer(dest3);
+ producer.send(jmsSession.createTextMessage("Hello"));
+ TextMessage msg = (TextMessage)cons3.receive(1000);
+ assertEquals("Destination3 was not created as expected.",msg.getText(),"Hello");
+ }
+
+ /**
+ * Test goal: Verifies the subject can be overridden using "qpid.subject" message property.
+ * Test strategy: Creates and address with a default subject "topic1"
+ * Creates a message with "qpid.subject"="topic2" and sends it.
+ * Verifies that the message goes to "topic2" instead of "topic1".
+ */
+ public void testOverridingSubject() throws Exception
+ {
+ Session jmsSession = _connection.createSession(false,Session.CLIENT_ACKNOWLEDGE);
+
+ AMQDestination topic1 = new AMQAnyDestination("ADDR:amq.topic/topic1; {link:{name: queue1}}");
+
+ MessageProducer prod = jmsSession.createProducer(topic1);
+
+ Message m = jmsSession.createTextMessage("Hello");
+ m.setStringProperty("qpid.subject", "topic2");
+
+ MessageConsumer consForTopic1 = jmsSession.createConsumer(topic1);
+ MessageConsumer consForTopic2 = jmsSession.createConsumer(new AMQAnyDestination("ADDR:amq.topic/topic2; {link:{name: queue2}}"));
+
+ prod.send(m);
+ Message msg = consForTopic1.receive(1000);
+ assertNull("message shouldn't have been sent to topic1",msg);
+
+ msg = consForTopic2.receive(1000);
+ assertNotNull("message should have been sent to topic2",msg);
+
+ }
+
+ /**
+ * Test goal: Verifies that session.createQueue method
+ * works as expected both with the new and old addressing scheme.
+ */
+ public void testSessionCreateQueue() throws Exception
+ {
+ Session ssn = _connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
+
+ // Using the BURL method
+ Destination queue = ssn.createQueue("my-queue");
+ MessageProducer prod = ssn.createProducer(queue);
+ MessageConsumer cons = ssn.createConsumer(queue);
+ assertTrue("my-queue was not created as expected",(
+ (AMQSession_0_10)ssn).isQueueBound("amq.direct",
+ "my-queue","my-queue", null));
+
+ prod.send(ssn.createTextMessage("test"));
+ assertNotNull("consumer should receive a message",cons.receive(1000));
+ cons.close();
+
+ // Using the ADDR method
+ // default case
+ queue = ssn.createQueue("ADDR:my-queue2");
+ try
+ {
+ prod = ssn.createProducer(queue);
+ fail("The client should throw an exception, since there is no queue present in the broker");
+ }
+ catch(Exception e)
+ {
+ String s = "The name 'my-queue2' supplied in the address " +
+ "doesn't resolve to an exchange or a queue";
+ assertEquals(s,e.getCause().getCause().getMessage());
+ }
+
+ // explicit create case
+ queue = ssn.createQueue("ADDR:my-queue2; {create: sender}");
+ prod = ssn.createProducer(queue);
+ cons = ssn.createConsumer(queue);
+ assertTrue("my-queue2 was not created as expected",(
+ (AMQSession_0_10)ssn).isQueueBound("",
+ "my-queue2","my-queue2", null));
+
+ prod.send(ssn.createTextMessage("test"));
+ assertNotNull("consumer should receive a message",cons.receive(1000));
+ cons.close();
+
+ // Using the ADDR method to create a more complicated queue
+ String addr = "ADDR:amq.direct/x512; {create: receiver, " +
+ "link : {name : 'MY.RESP.QUEUE', " +
+ "x-declare : { auto-delete: true, exclusive: true, " +
+ "arguments : {'qpid.max_size': 1000, 'qpid.policy_type': ring} } } }";
+ queue = ssn.createQueue(addr);
+
+ prod = ssn.createProducer(queue);
+ cons = ssn.createConsumer(queue);
+ assertTrue("MY.RESP.QUEUE was not created as expected",(
+ (AMQSession_0_10)ssn).isQueueBound("amq.direct",
+ "MY.RESP.QUEUE","x512", null));
+ cons.close();
+ }
+
+ /**
+ * Test goal: Verifies that session.creatTopic method
+ * works as expected both with the new and old addressing scheme.
+ */
+ public void testSessionCreateTopic() throws Exception
+ {
+ Session ssn = _connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
+
+ // Using the BURL method
+ Topic topic = ssn.createTopic("ACME");
+ MessageProducer prod = ssn.createProducer(topic);
+ MessageConsumer cons = ssn.createConsumer(topic);
+
+ prod.send(ssn.createTextMessage("test"));
+ assertNotNull("consumer should receive a message",cons.receive(1000));
+ cons.close();
+
+ // Using the ADDR method
+ topic = ssn.createTopic("ADDR:ACME");
+ prod = ssn.createProducer(topic);
+ cons = ssn.createConsumer(topic);
+
+ prod.send(ssn.createTextMessage("test"));
+ assertNotNull("consumer should receive a message",cons.receive(1000));
+ cons.close();
+
+ String addr = "ADDR:vehicles/bus; " +
+ "{ " +
+ "create: always, " +
+ "node: " +
+ "{" +
+ "type: topic, " +
+ "x-declare: " +
+ "{ " +
+ "type:direct, " +
+ "auto-delete: true, " +
+ "arguments: {" +
+ "'qpid.msg_sequence': 1, " +
+ "'qpid.ive': 1" +
+ "}" +
+ "}" +
+ "}, " +
+ "link: {name : my-topic, " +
+ "x-bindings: [{exchange : 'vehicles', key : car}, " +
+ "{exchange : 'vehicles', key : van}]" +
+ "}" +
+ "}";
+
+ // Using the ADDR method to create a more complicated topic
+ topic = ssn.createTopic(addr);
+ prod = ssn.createProducer(topic);
+ cons = ssn.createConsumer(topic);
+
+ assertTrue("The queue was not bound to vehicle exchange using bus as the binding key",(
+ (AMQSession_0_10)ssn).isQueueBound("vehicles",
+ "my-topic","bus", null));
+
+ assertTrue("The queue was not bound to vehicle exchange using car as the binding key",(
+ (AMQSession_0_10)ssn).isQueueBound("vehicles",
+ "my-topic","car", null));
+
+ assertTrue("The queue was not bound to vehicle exchange using van as the binding key",(
+ (AMQSession_0_10)ssn).isQueueBound("vehicles",
+ "my-topic","van", null));
+
+ Message msg = ssn.createTextMessage("test");
+ msg.setStringProperty("qpid.subject", "van");
+ prod.send(msg);
+ assertNotNull("consumer should receive a message",cons.receive(1000));
+ cons.close();
+ }
+
+ /**
+ * Test Goal : Verify the default subjects used for each exchange type.
+ * The default for amq.topic is "#" and for the rest it's ""
+ */
+ public void testDefaultSubjects() throws Exception
+ {
+ Session ssn = _connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
+
+ MessageConsumer queueCons = ssn.createConsumer(new AMQAnyDestination("ADDR:amq.direct"));
+ MessageConsumer topicCons = ssn.createConsumer(new AMQAnyDestination("ADDR:amq.topic"));
+
+ MessageProducer queueProducer = ssn.createProducer(new AMQAnyDestination("ADDR:amq.direct"));
+ MessageProducer topicProducer1 = ssn.createProducer(new AMQAnyDestination("ADDR:amq.topic/usa.weather"));
+ MessageProducer topicProducer2 = ssn.createProducer(new AMQAnyDestination("ADDR:amq.topic/sales"));
+
+ queueProducer.send(ssn.createBytesMessage());
+ assertNotNull("The consumer subscribed to amq.direct " +
+ "with empty binding key should have received the message ",queueCons.receive(1000));
+
+ topicProducer1.send(ssn.createTextMessage("25c"));
+ assertEquals("The consumer subscribed to amq.topic " +
+ "with '#' binding key should have received the message ",
+ ((TextMessage)topicCons.receive(1000)).getText(),"25c");
+
+ topicProducer2.send(ssn.createTextMessage("1000"));
+ assertEquals("The consumer subscribed to amq.topic " +
+ "with '#' binding key should have received the message ",
+ ((TextMessage)topicCons.receive(1000)).getText(),"1000");
+ }
+
+ /**
+ * Test Goal : Verify that 'mode : browse' works as expected using a regular consumer.
+ * This indirectly tests ring queues as well.
+ */
+ public void testBrowseMode() throws Exception
+ {
+
+ Session ssn = _connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
+
+ String addr = "ADDR:my-ring-queue; {create: always, mode: browse, " +
+ "node: {x-bindings: [{exchange : 'amq.direct', key : test}], " +
+ "x-declare:{arguments : {'qpid.policy_type':ring, 'qpid.max_count':2}}}}";
+
+ Destination dest = ssn.createQueue(addr);
+ MessageConsumer browseCons = ssn.createConsumer(dest);
+ MessageProducer prod = ssn.createProducer(ssn.createQueue("ADDR:amq.direct/test"));
+
+ prod.send(ssn.createTextMessage("Test1"));
+ prod.send(ssn.createTextMessage("Test2"));
+
+ TextMessage msg = (TextMessage)browseCons.receive(1000);
+ assertEquals("Didn't receive the first message",msg.getText(),"Test1");
+
+ msg = (TextMessage)browseCons.receive(1000);
+ assertEquals("Didn't receive the first message",msg.getText(),"Test2");
+
+ browseCons.close();
+ prod.send(ssn.createTextMessage("Test3"));
+ browseCons = ssn.createConsumer(dest);
+
+ msg = (TextMessage)browseCons.receive(1000);
+ assertEquals("Should receive the second message again",msg.getText(),"Test2");
+
+ msg = (TextMessage)browseCons.receive(1000);
+ assertEquals("Should receive the third message since it's a ring queue",msg.getText(),"Test3");
+
+ assertNull("Should not receive anymore messages",browseCons.receive(500));
+ }
+
+ /**
+ * Test Goal : When the same destination is used when creating two consumers,
+ * If the type == topic, verify that unique subscription queues are created,
+ * unless subscription queue has a name.
+ *
+ * If the type == queue, same queue should be shared.
+ */
+ public void testSubscriptionForSameDestination() throws Exception
+ {
+ Session ssn = _connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
+ Destination dest = ssn.createTopic("ADDR:amq.topic/foo");
+ MessageConsumer consumer1 = ssn.createConsumer(dest);
+ MessageConsumer consumer2 = ssn.createConsumer(dest);
+ MessageProducer prod = ssn.createProducer(dest);
+
+ prod.send(ssn.createTextMessage("A"));
+ TextMessage m = (TextMessage)consumer1.receive(1000);
+ assertEquals("Consumer1 should recieve message A",m.getText(),"A");
+ m = (TextMessage)consumer2.receive(1000);
+ assertEquals("Consumer2 should recieve message A",m.getText(),"A");
+
+ consumer1.close();
+ consumer2.close();
+
+ dest = ssn.createTopic("ADDR:amq.topic/foo; { link: {name: my-queue}}");
+ consumer1 = ssn.createConsumer(dest);
+ try
+ {
+ consumer2 = ssn.createConsumer(dest);
+ fail("An exception should be thrown as 'my-queue' already have an exclusive subscriber");
+ }
+ catch(Exception e)
+ {
+ }
+ _connection.close();
+
+ _connection = getConnection() ;
+ _connection.start();
+ ssn = _connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
+ dest = ssn.createTopic("ADDR:my_queue; {create: always}");
+ consumer1 = ssn.createConsumer(dest);
+ consumer2 = ssn.createConsumer(dest);
+ prod = ssn.createProducer(dest);
+
+ prod.send(ssn.createTextMessage("A"));
+ Message m1 = consumer1.receive(1000);
+ Message m2 = consumer2.receive(1000);
+
+ if (m1 != null)
+ {
+ assertNull("Only one consumer should receive the message",m2);
+ }
+ else
+ {
+ assertNotNull("Only one consumer should receive the message",m2);
+ }
+ }
+
+ public void testXBindingsWithoutExchangeName() throws Exception
+ {
+ Session ssn = _connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
+ String addr = "ADDR:MRKT; " +
+ "{" +
+ "create: receiver," +
+ "node : {type: topic, x-declare: {type: topic} }," +
+ "link:{" +
+ "name: my-topic," +
+ "x-bindings:[{key:'NYSE.#'},{key:'NASDAQ.#'},{key:'CNTL.#'}]" +
+ "}" +
+ "}";
+
+ // Using the ADDR method to create a more complicated topic
+ MessageConsumer cons = ssn.createConsumer(new AMQAnyDestination(addr));
+
+ assertTrue("The queue was not bound to MRKT exchange using NYSE.# as the binding key",(
+ (AMQSession_0_10)ssn).isQueueBound("MRKT",
+ "my-topic","NYSE.#", null));
+
+ assertTrue("The queue was not bound to MRKT exchange using NASDAQ.# as the binding key",(
+ (AMQSession_0_10)ssn).isQueueBound("MRKT",
+ "my-topic","NASDAQ.#", null));
+
+ assertTrue("The queue was not bound to MRKT exchange using CNTL.# as the binding key",(
+ (AMQSession_0_10)ssn).isQueueBound("MRKT",
+ "my-topic","CNTL.#", null));
+
+ MessageProducer prod = ssn.createProducer(ssn.createTopic(addr));
+ Message msg = ssn.createTextMessage("test");
+ msg.setStringProperty("qpid.subject", "NASDAQ.ABCD");
+ prod.send(msg);
+ assertNotNull("consumer should receive a message",cons.receive(1000));
+ cons.close();
+ }
+
+ public void testXSubscribeOverrides() throws Exception
+ {
+ Session ssn = _connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
+ String str = "ADDR:my_queue; {create:always,link: {x-subscribes:{exclusive: true, arguments: {a:b,x:y}}}}";
+ Destination dest = ssn.createTopic(str);
+ MessageConsumer consumer1 = ssn.createConsumer(dest);
+ try
+ {
+ MessageConsumer consumer2 = ssn.createConsumer(dest);
+ fail("An exception should be thrown as 'my-queue' already have an exclusive subscriber");
+ }
+ catch(Exception e)
+ {
+ }
+ }
+
+ public void testQueueReceiversAndTopicSubscriber() throws Exception
+ {
+ Queue queue = new AMQAnyDestination("ADDR:my-queue; {create: always}");
+ Topic topic = new AMQAnyDestination("ADDR:amq.topic/test");
+
+ QueueSession qSession = ((AMQConnection)_connection).createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
+ QueueReceiver receiver = qSession.createReceiver(queue);
+
+ TopicSession tSession = ((AMQConnection)_connection).createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
+ TopicSubscriber sub = tSession.createSubscriber(topic);
+
+ Session ssn = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ MessageProducer prod1 = ssn.createProducer(ssn.createQueue("ADDR:my-queue"));
+ prod1.send(ssn.createTextMessage("test1"));
+
+ MessageProducer prod2 = ssn.createProducer(ssn.createTopic("ADDR:amq.topic/test"));
+ prod2.send(ssn.createTextMessage("test2"));
+
+ Message msg1 = receiver.receive();
+ assertNotNull(msg1);
+ assertEquals("test1",((TextMessage)msg1).getText());
+
+ Message msg2 = sub.receive();
+ assertNotNull(msg2);
+ assertEquals("test2",((TextMessage)msg2).getText());
+ }
+
+ public void testDurableSubscriber() throws Exception
+ {
+ Session ssn = _connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
+
+ Properties props = new Properties();
+ props.setProperty("java.naming.factory.initial", "org.apache.qpid.jndi.PropertiesFileInitialContextFactory");
+ props.setProperty("destination.address1", "ADDR:amq.topic");
+ props.setProperty("destination.address2", "ADDR:amq.direct/test");
+ String addrStr = "ADDR:amq.topic/test; {link:{name: my-topic," +
+ "x-bindings:[{key:'NYSE.#'},{key:'NASDAQ.#'},{key:'CNTL.#'}]}}";
+ props.setProperty("destination.address3", addrStr);
+ props.setProperty("topic.address4", "hello.world");
+ addrStr = "ADDR:my_queue; {create:always,link: {x-subscribes:{exclusive: true, arguments: {a:b,x:y}}}}";
+ props.setProperty("destination.address5", addrStr);
+
+ Context ctx = new InitialContext(props);
+
+ for (int i=1; i < 5; i++)
+ {
+ Topic topic = (Topic) ctx.lookup("address"+i);
+ createDurableSubscriber(ctx,ssn,"address"+i,topic);
+ }
+
+ Topic topic = ssn.createTopic("ADDR:news.us");
+ createDurableSubscriber(ctx,ssn,"my-dest",topic);
+
+ Topic namedQueue = (Topic) ctx.lookup("address5");
+ try
+ {
+ createDurableSubscriber(ctx,ssn,"my-queue",namedQueue);
+ fail("Exception should be thrown. Durable subscribers cannot be created for Queues");
+ }
+ catch(JMSException e)
+ {
+ assertEquals("Durable subscribers can only be created for Topics",
+ e.getMessage());
+ }
+ }
+
+ private void createDurableSubscriber(Context ctx,Session ssn,String destName,Topic topic) throws Exception
+ {
+ MessageConsumer cons = ssn.createDurableSubscriber(topic, destName);
+ MessageProducer prod = ssn.createProducer(topic);
+
+ Message m = ssn.createTextMessage(destName);
+ prod.send(m);
+ Message msg = cons.receive(1000);
+ assertNotNull(msg);
+ assertEquals(destName,((TextMessage)msg).getText());
+ ssn.unsubscribe(destName);
+ }
+
+ public void testDeleteOptions() throws Exception
+ {
+ Session jmsSession = _connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
+ MessageConsumer cons;
+
+ // default (create never, assert never) -------------------
+ // create never --------------------------------------------
+ String addr1 = "ADDR:testQueue1;{create: always, delete: always}";
+ AMQDestination dest = new AMQAnyDestination(addr1);
+ try
+ {
+ cons = jmsSession.createConsumer(dest);
+ cons.close();
+ }
+ catch(JMSException e)
+ {
+ fail("Exception should not be thrown. Exception thrown is : " + e);
+ }
+
+ assertFalse("Queue not deleted as expected",(
+ (AMQSession_0_10)jmsSession).isQueueExist(dest,(QueueNode)dest.getSourceNode(), true));
+
+
+ String addr2 = "ADDR:testQueue2;{create: always, delete: receiver}";
+ dest = new AMQAnyDestination(addr2);
+ try
+ {
+ cons = jmsSession.createConsumer(dest);
+ cons.close();
+ }
+ catch(JMSException e)
+ {
+ fail("Exception should not be thrown. Exception thrown is : " + e);
+ }
+
+ assertFalse("Queue not deleted as expected",(
+ (AMQSession_0_10)jmsSession).isQueueExist(dest,(QueueNode)dest.getSourceNode(), true));
+
+
+ String addr3 = "ADDR:testQueue3;{create: always, delete: sender}";
+ dest = new AMQAnyDestination(addr3);
+ try
+ {
+ cons = jmsSession.createConsumer(dest);
+ MessageProducer prod = jmsSession.createProducer(dest);
+ prod.close();
+ }
+ catch(JMSException e)
+ {
+ fail("Exception should not be thrown. Exception thrown is : " + e);
+ }
+
+ assertFalse("Queue not deleted as expected",(
+ (AMQSession_0_10)jmsSession).isQueueExist(dest,(QueueNode)dest.getSourceNode(), true));
+
+
+ }
+
+ /**
+ * Test Goals : 1. Test if the client sets the correct accept mode for unreliable
+ * and at-least-once.
+ * 2. Test default reliability modes for Queues and Topics.
+ * 3. Test if an exception is thrown if exactly-once is used.
+ * 4. Test if an exception is thrown if at-least-once is used with topics.
+ *
+ * Test Strategy: For goal #1 & #2
+ * For unreliable and at-least-once the test tries to receives messages
+ * in client_ack mode but does not ack the messages.
+ * It will then close the session, recreate a new session
+ * and will then try to verify the queue depth.
+ * For unreliable the messages should have been taken off the queue.
+ * For at-least-once the messages should be put back onto the queue.
+ *
+ */
+
+ public void testReliabilityOptions() throws Exception
+ {
+ String addr1 = "ADDR:testQueue1;{create: always, delete : receiver, link : {reliability : unreliable}}";
+ acceptModeTest(addr1,0);
+
+ String addr2 = "ADDR:testQueue2;{create: always, delete : receiver, link : {reliability : at-least-once}}";
+ acceptModeTest(addr2,2);
+
+ // Default accept-mode for topics
+ acceptModeTest("ADDR:amq.topic/test",0);
+
+ // Default accept-mode for queues
+ acceptModeTest("ADDR:testQueue1;{create: always}",2);
+
+ String addr3 = "ADDR:testQueue2;{create: always, delete : receiver, link : {reliability : exactly-once}}";
+ try
+ {
+ AMQAnyDestination dest = new AMQAnyDestination(addr3);
+ fail("An exception should be thrown indicating it's an unsupported type");
+ }
+ catch(Exception e)
+ {
+ assertTrue(e.getCause().getMessage().contains("The reliability mode 'exactly-once' is not yet supported"));
+ }
+
+ String addr4 = "ADDR:amq.topic/test;{link : {reliability : at-least-once}}";
+ try
+ {
+ AMQAnyDestination dest = new AMQAnyDestination(addr4);
+ Session ssn = _connection.createSession(false,Session.CLIENT_ACKNOWLEDGE);
+ MessageConsumer cons = ssn.createConsumer(dest);
+ fail("An exception should be thrown indicating it's an unsupported combination");
+ }
+ catch(Exception e)
+ {
+ assertTrue(e.getCause().getMessage().contains("AT-LEAST-ONCE is not yet supported for Topics"));
+ }
+ }
+
+ private void acceptModeTest(String address, int expectedQueueDepth) throws Exception
+ {
+ Session ssn = _connection.createSession(false,Session.CLIENT_ACKNOWLEDGE);
+ MessageConsumer cons;
+ MessageProducer prod;
+
+ AMQDestination dest = new AMQAnyDestination(address);
+ cons = ssn.createConsumer(dest);
+ prod = ssn.createProducer(dest);
+
+ for (int i=0; i < expectedQueueDepth; i++)
+ {
+ prod.send(ssn.createTextMessage("Msg" + i));
+ }
+
+ for (int i=0; i < expectedQueueDepth; i++)
+ {
+ Message msg = cons.receive(1000);
+ assertNotNull(msg);
+ assertEquals("Msg" + i,((TextMessage)msg).getText());
+ }
+
+ ssn.close();
+ ssn = _connection.createSession(false,Session.CLIENT_ACKNOWLEDGE);
+ long queueDepth = ((AMQSession) ssn).getQueueDepth(dest);
+ assertEquals(expectedQueueDepth,queueDepth);
+ cons.close();
+ prod.close();
+ }
+
+ public void testDestinationOnSend() throws Exception
+ {
+ Session ssn = _connection.createSession(false,Session.CLIENT_ACKNOWLEDGE);
+ MessageConsumer cons = ssn.createConsumer(ssn.createTopic("amq.topic/test"));
+ MessageProducer prod = ssn.createProducer(null);
+
+ Queue queue = ssn.createQueue("amq.topic/test");
+ prod.send(queue,ssn.createTextMessage("A"));
+
+ Message msg = cons.receive(1000);
+ assertNotNull(msg);
+ assertEquals("A",((TextMessage)msg).getText());
+ prod.close();
+ cons.close();
+ }
+
+ public void testReplyToWithNamelessExchange() throws Exception
+ {
+ System.setProperty("qpid.declare_exchanges","false");
+ replyToTest("ADDR:my-queue;{create: always}");
+ System.setProperty("qpid.declare_exchanges","true");
+ }
+
+ public void testReplyToWithCustomExchange() throws Exception
+ {
+ replyToTest("ADDR:hello;{create:always,node:{type:topic}}");
+ }
+
+ private void replyToTest(String replyTo) throws Exception
+ {
+ Session session = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ Destination replyToDest = AMQDestination.createDestination(replyTo);
+ MessageConsumer replyToCons = session.createConsumer(replyToDest);
+
+ Destination dest = session.createQueue("amq.direct/test");
+
+ MessageConsumer cons = session.createConsumer(dest);
+ MessageProducer prod = session.createProducer(dest);
+ Message m = session.createTextMessage("test");
+ m.setJMSReplyTo(replyToDest);
+ prod.send(m);
+
+ Message msg = cons.receive();
+ MessageProducer prodR = session.createProducer(msg.getJMSReplyTo());
+ prodR.send(session.createTextMessage("x"));
+
+ Message m1 = replyToCons.receive();
+ assertNotNull("The reply to consumer should have received the messsage",m1);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/failover/FailoverTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/failover/FailoverTest.java
new file mode 100644
index 0000000000..fcbab273e5
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/failover/FailoverTest.java
@@ -0,0 +1,389 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.test.client.failover;
+
+import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+import javax.naming.NamingException;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.jms.BrokerDetails;
+import org.apache.qpid.jms.ConnectionListener;
+import org.apache.qpid.jms.ConnectionURL;
+import org.apache.qpid.test.utils.FailoverBaseCase;
+
+public class FailoverTest extends FailoverBaseCase implements ConnectionListener
+{
+ private static final Logger _logger = Logger.getLogger(FailoverTest.class);
+
+ private static final String QUEUE = "queue";
+ private static final int DEFAULT_NUM_MESSAGES = 10;
+ private static final int DEFAULT_SEED = 20080921;
+ protected int numMessages = 0;
+ protected Connection connection;
+ private Session producerSession;
+ private Queue queue;
+ private MessageProducer producer;
+ private Session consumerSession;
+ private MessageConsumer consumer;
+
+ private CountDownLatch failoverComplete;
+ private boolean CLUSTERED = Boolean.getBoolean("profile.clustered");
+ private int seed;
+ private Random rand;
+ private int _currentPort = getFailingPort();
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ numMessages = Integer.getInteger("profile.failoverMsgCount",DEFAULT_NUM_MESSAGES);
+ seed = Integer.getInteger("profile.failoverRandomSeed",DEFAULT_SEED);
+ rand = new Random(seed);
+
+ connection = getConnection();
+ ((AMQConnection) connection).setConnectionListener(this);
+ connection.start();
+ failoverComplete = new CountDownLatch(1);
+ }
+
+ protected void init(boolean transacted, int mode) throws JMSException, NamingException
+ {
+ consumerSession = connection.createSession(transacted, mode);
+ queue = consumerSession.createQueue(getName()+System.currentTimeMillis());
+ consumer = consumerSession.createConsumer(queue);
+
+ producerSession = connection.createSession(transacted, mode);
+ producer = producerSession.createProducer(queue);
+ }
+
+ @Override
+ public void tearDown() throws Exception
+ {
+ try
+ {
+ connection.close();
+ }
+ catch (Exception e)
+ {
+
+ }
+
+ super.tearDown();
+ }
+
+ private void consumeMessages(int startIndex,int endIndex, boolean transacted) throws JMSException
+ {
+ Message msg;
+ _logger.debug("**************** Receive (Start: " + startIndex + ", End:" + endIndex + ")***********************");
+
+ for (int i = startIndex; i < endIndex; i++)
+ {
+ msg = consumer.receive(1000);
+ assertNotNull("Message " + i + " was null!", msg);
+
+ _logger.debug("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+ _logger.debug("Received : " + ((TextMessage) msg).getText());
+ _logger.debug("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+
+ assertEquals("Invalid message order","message " + i, ((TextMessage) msg).getText());
+
+ }
+ _logger.debug("***********************************************************");
+
+ if (transacted)
+ {
+ consumerSession.commit();
+ }
+ }
+
+ private void sendMessages(int startIndex,int endIndex, boolean transacted) throws JMSException
+ {
+ _logger.debug("**************** Send (Start: " + startIndex + ", End:" + endIndex + ")***********************");
+
+ for (int i = startIndex; i < endIndex; i++)
+ {
+ producer.send(producerSession.createTextMessage("message " + i));
+
+ _logger.debug("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+ _logger.debug("Sending message"+i);
+ _logger.debug("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
+ }
+
+ _logger.debug("***********************************************************");
+
+ if (transacted)
+ {
+ producerSession.commit();
+ }
+ }
+
+ public void testP2PFailover() throws Exception
+ {
+ testP2PFailover(numMessages, true,true, false);
+ }
+
+ public void testP2PFailoverWithMessagesLeftToConsumeAndProduce() throws Exception
+ {
+ if (CLUSTERED)
+ {
+ testP2PFailover(numMessages, false,false, false);
+ }
+ }
+
+ public void testP2PFailoverWithMessagesLeftToConsume() throws Exception
+ {
+ if (CLUSTERED)
+ {
+ testP2PFailover(numMessages, false,true, false);
+ }
+ }
+
+ public void testP2PFailoverTransacted() throws Exception
+ {
+ testP2PFailover(numMessages, true,true, false);
+ }
+
+ public void testP2PFailoverTransactedWithMessagesLeftToConsumeAndProduce() throws Exception
+ {
+ // Currently the cluster does not support transactions that span a failover
+ if (CLUSTERED)
+ {
+ testP2PFailover(numMessages, false,false, false);
+ }
+ }
+
+ private void testP2PFailover(int totalMessages, boolean consumeAll, boolean produceAll , boolean transacted) throws JMSException, NamingException
+ {
+ init(transacted, Session.AUTO_ACKNOWLEDGE);
+ runP2PFailover(totalMessages,consumeAll, produceAll , transacted);
+ }
+
+ protected void runP2PFailover(int totalMessages, boolean consumeAll, boolean produceAll , boolean transacted) throws JMSException, NamingException
+ {
+ Message msg = null;
+ int toProduce = totalMessages;
+
+ _logger.debug("===================================================================");
+ _logger.debug("Total messages used for the test " + totalMessages + " messages");
+ _logger.debug("===================================================================");
+
+ if (!produceAll)
+ {
+ toProduce = totalMessages - rand.nextInt(totalMessages);
+ }
+
+ _logger.debug("==================");
+ _logger.debug("Sending " + toProduce + " messages");
+ _logger.debug("==================");
+
+ sendMessages(0,toProduce, transacted);
+
+ // Consume some messages
+ int toConsume = toProduce;
+ if (!consumeAll)
+ {
+ toConsume = toProduce - rand.nextInt(toProduce);
+ }
+
+ consumeMessages(0,toConsume, transacted);
+
+ _logger.debug("==================");
+ _logger.debug("Consuming " + toConsume + " messages");
+ _logger.debug("==================");
+
+ _logger.info("Failing over");
+
+ causeFailure(_currentPort, DEFAULT_FAILOVER_TIME);
+
+ // Check that you produce and consume the rest of messages.
+ _logger.debug("==================");
+ _logger.debug("Sending " + (totalMessages-toProduce) + " messages");
+ _logger.debug("==================");
+
+ sendMessages(toProduce,totalMessages, transacted);
+ consumeMessages(toConsume,totalMessages, transacted);
+
+ _logger.debug("==================");
+ _logger.debug("Consuming " + (totalMessages-toConsume) + " messages");
+ _logger.debug("==================");
+ }
+
+ private void causeFailure(int port, long delay)
+ {
+
+ failBroker(port);
+
+ _logger.info("Awaiting Failover completion");
+ try
+ {
+ if (!failoverComplete.await(delay, TimeUnit.MILLISECONDS))
+ {
+ fail("failover did not complete");
+ }
+ }
+ catch (InterruptedException e)
+ {
+ //evil ignore IE.
+ }
+ }
+
+ public void testClientAckFailover() throws Exception
+ {
+ init(false, Session.CLIENT_ACKNOWLEDGE);
+ sendMessages(0,1, false);
+ Message msg = consumer.receive();
+ assertNotNull("Expected msgs not received", msg);
+
+ causeFailure(getFailingPort(), DEFAULT_FAILOVER_TIME);
+
+ Exception failure = null;
+ try
+ {
+ msg.acknowledge();
+ }
+ catch (Exception e)
+ {
+ failure = e;
+ }
+ assertNotNull("Exception should be thrown", failure);
+ }
+
+ /**
+ * The client used to have a fixed timeout of 4 minutes after which failover would no longer work.
+ * Check that this code has not regressed
+ *
+ * @throws Exception if something unexpected occurs in the test.
+ */
+
+ public void test4MinuteFailover() throws Exception
+ {
+ ConnectionURL connectionURL = getConnectionFactory().getConnectionURL();
+
+ int RETRIES = 4;
+ int DELAY = 60000;
+
+ //Set up a long delay on and large number of retries
+ BrokerDetails details = connectionURL.getBrokerDetails(1);
+ details.setProperty(BrokerDetails.OPTIONS_RETRY, String.valueOf(RETRIES));
+ details.setProperty(BrokerDetails.OPTIONS_CONNECT_DELAY, String.valueOf(DELAY));
+
+ connection = new AMQConnection(connectionURL, null);
+
+ ((AMQConnection) connection).setConnectionListener(this);
+
+ //Start the connection
+ connection.start();
+
+ long FAILOVER_DELAY = ((long)RETRIES * (long)DELAY);
+
+ // Use Nano seconds as it is more accurate for comparision.
+ long failTime = System.nanoTime() + FAILOVER_DELAY * 1000000;
+
+ //Fail the first broker
+ causeFailure(getFailingPort(), FAILOVER_DELAY + DEFAULT_FAILOVER_TIME);
+
+ //Reconnection should occur
+ assertTrue("Failover did not take long enough", System.nanoTime() > failTime);
+ }
+
+
+ /**
+ * The idea is to run a failover test in a loop by failing over
+ * to the other broker each time.
+ */
+ public void testFailoverInALoop() throws Exception
+ {
+ if (!CLUSTERED)
+ {
+ return;
+ }
+
+ int iterations = Integer.getInteger("profile.failoverIterations",0);
+ boolean useAltPort = false;
+ int altPort = FAILING_PORT;
+ int stdPort = DEFAULT_PORT;
+ init(false, Session.AUTO_ACKNOWLEDGE);
+ for (int i=0; i < iterations; i++)
+ {
+ _logger.debug("===================================================================");
+ _logger.debug("Failover In a loop : iteration number " + i);
+ _logger.debug("===================================================================");
+
+ runP2PFailover(numMessages, false,false, false);
+ startBroker(_currentPort);
+ if (useAltPort)
+ {
+ _currentPort = altPort;
+ useAltPort = false;
+ }
+ else
+ {
+ _currentPort = stdPort;
+ useAltPort = true;
+ }
+
+ }
+ //To prevent any failover logic being initiated when we shutdown the brokers.
+ connection.close();
+
+ // Shutdown the brokers
+ stopBroker(altPort);
+ stopBroker(stdPort);
+
+ }
+
+ public void bytesSent(long count)
+ {
+ }
+
+ public void bytesReceived(long count)
+ {
+ }
+
+ public boolean preFailover(boolean redirect)
+ {
+ return true;
+ }
+
+ public boolean preResubscribe()
+ {
+ return true;
+ }
+
+ public void failoverComplete()
+ {
+ failoverComplete.countDown();
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/message/JMSDestinationTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/message/JMSDestinationTest.java
new file mode 100644
index 0000000000..a7efe4922b
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/message/JMSDestinationTest.java
@@ -0,0 +1,373 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.client.message;
+
+import org.apache.qpid.configuration.ClientProperties;
+import org.apache.qpid.client.AMQAnyDestination;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQTopic;
+import org.apache.qpid.client.CustomJMSXProperty;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.management.common.mbeans.ManagedQueue;
+import org.apache.qpid.test.utils.JMXTestUtils;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageListener;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.Topic;
+import javax.management.openmbean.CompositeDataSupport;
+import javax.management.openmbean.TabularData;
+import java.nio.BufferOverflowException;
+import java.util.Iterator;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * From the API Docs getJMSDestination:
+ *
+ * When a message is received, its JMSDestination value must be equivalent to
+ * the value assigned when it was sent.
+ */
+public class JMSDestinationTest extends QpidBrokerTestCase
+{
+
+ private Connection _connection;
+ private Session _session;
+
+ private static final String USER = "admin";
+ private CountDownLatch _receiveMessage;
+ private Message _message;
+
+ public void setUp() throws Exception
+ {
+ //Ensure JMX management is enabled for MovedToQueue test
+ setConfigurationProperty("management.enabled", "true");
+
+ super.setUp();
+
+ _connection = getConnection();
+
+ _session = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ }
+
+ /**
+ * Test a message sent to a queue comes back with JMSDestination queue
+ *
+ * @throws Exception
+ */
+ public void testQueue() throws Exception
+ {
+
+ Queue queue = _session.createQueue(getTestQueueName());
+
+ MessageConsumer consumer = _session.createConsumer(queue);
+
+ sendMessage(_session, queue, 1);
+
+ _connection.start();
+
+ Message message = consumer.receive(10000);
+
+ assertNotNull("Message should not be null", message);
+
+ Destination destination = message.getJMSDestination();
+
+ assertNotNull("JMSDestination should not be null", destination);
+
+ assertEquals("Incorrect Destination type", queue.getClass(), destination.getClass());
+ }
+
+ /**
+ * Test a message sent to a topic comes back with JMSDestination topic
+ *
+ * @throws Exception
+ */
+ public void testTopic() throws Exception
+ {
+
+ Topic topic = _session.createTopic(getTestQueueName() + "Topic");
+
+ MessageConsumer consumer = _session.createConsumer(topic);
+
+ sendMessage(_session, topic, 1);
+
+ _connection.start();
+
+ Message message = consumer.receive(10000);
+
+ assertNotNull("Message should not be null", message);
+
+ Destination destination = message.getJMSDestination();
+
+ assertNotNull("JMSDestination should not be null", destination);
+
+ assertEquals("Incorrect Destination type", topic.getClass(), destination.getClass());
+ }
+
+ /**
+ * Test a message sent to a topic then moved on the broker
+ * comes back with JMSDestination queue.
+ *
+ * i.e. The client is not just setting the value to be the same as the
+ * current consumer destination.
+ *
+ * This test can only be run against the Java broker as it uses JMX to move
+ * messages between queues.
+ *
+ * @throws Exception
+ */
+ public void testMovedToQueue() throws Exception
+ {
+ // Setup JMXUtils
+ JMXTestUtils jmxUtils = new JMXTestUtils(this, USER, USER);
+ jmxUtils.setUp();
+ // Open the JMX Connection
+ jmxUtils.open();
+ try
+ {
+
+ Queue queue = _session.createQueue(getTestQueueName());
+
+ _session.createConsumer(queue).close();
+
+ sendMessage(_session, queue, 1);
+
+ Topic topic = _session.createTopic(getTestQueueName() + "Topic");
+
+ MessageConsumer consumer = _session.createConsumer(topic);
+
+ // Use Management to move message.
+
+ ManagedQueue managedQueue = jmxUtils.
+ getManagedObject(ManagedQueue.class,
+ jmxUtils.getQueueObjectName(getConnectionFactory().getVirtualPath().substring(1),
+ getTestQueueName()));
+
+ // Find the first message on the queue
+ TabularData data = managedQueue.viewMessages(1L, 2L);
+
+ Iterator values = data.values().iterator();
+ assertTrue("No Messages found via JMX", values.hasNext());
+
+ // Get its message ID
+ Long msgID = (Long) ((CompositeDataSupport) values.next()).get("AMQ MessageId");
+
+ // Start the connection and consume message that has been moved to the
+ // queue
+ _connection.start();
+
+ Message message = consumer.receive(1000);
+
+ //Validate we don't have a message on the queue before we start
+ assertNull("Message should be null", message);
+
+ // Move it to from the topic to the queue
+ managedQueue.moveMessages(msgID, msgID, ((AMQTopic) topic).getQueueName());
+
+ // Retrieve the newly moved message
+ message = consumer.receive(1000);
+
+ assertNotNull("Message should not be null", message);
+
+ Destination destination = message.getJMSDestination();
+
+ assertNotNull("JMSDestination should not be null", destination);
+
+ assertEquals("Incorrect Destination type", queue.getClass(), destination.getClass());
+
+ }
+ finally
+ {
+ jmxUtils.close();
+ }
+
+ }
+
+ /**
+ * Test a message sent to a queue comes back with JMSDestination queue
+ * when received via a message listener
+ *
+ * @throws Exception
+ */
+ public void testQueueAsync() throws Exception
+ {
+
+ Queue queue = _session.createQueue(getTestQueueName());
+
+ MessageConsumer consumer = _session.createConsumer(queue);
+
+ sendMessage(_session, queue, 1);
+
+ _connection.start();
+
+ _message = null;
+ _receiveMessage = new CountDownLatch(1);
+
+ consumer.setMessageListener(new MessageListener()
+ {
+ public void onMessage(Message message)
+ {
+ _message = message;
+ _receiveMessage.countDown();
+ }
+ });
+
+ assertTrue("Timed out waiting for message to be received ", _receiveMessage.await(1, TimeUnit.SECONDS));
+
+ assertNotNull("Message should not be null", _message);
+
+ Destination destination = _message.getJMSDestination();
+
+ assertNotNull("JMSDestination should not be null", destination);
+
+ assertEquals("Incorrect Destination type", queue.getClass(), destination.getClass());
+ }
+
+ /**
+ * Test a message received without the JMS_QPID_DESTTYPE can be resent
+ * and correctly have the property set.
+ *
+ * To do this we need to create a 0-10 connection and send a message
+ * which is then received by a 0-8/9 client.
+ *
+ * @throws Exception
+ */
+ public void testReceiveResend() throws Exception
+ {
+ // Create a 0-10 Connection and send message
+ setSystemProperty(ClientProperties.AMQP_VERSION, "0-10");
+
+ Connection connection010 = getConnection();
+
+ Session session010 = connection010.createSession(true, Session.SESSION_TRANSACTED);
+
+ // Create queue for testing
+ Queue queue = session010.createQueue(getTestQueueName());
+
+ // Ensure queue exists
+ session010.createConsumer(queue).close();
+
+ sendMessage(session010, queue, 1);
+
+ // Close the 010 connection
+ connection010.close();
+
+ // Create a 0-8 Connection and receive message
+ setSystemProperty(ClientProperties.AMQP_VERSION, "0-8");
+
+ Connection connection08 = getConnection();
+
+ Session session08 = connection08.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ MessageConsumer consumer = session08.createConsumer(queue);
+
+ connection08.start();
+
+ Message message = consumer.receive(1000);
+
+ assertNotNull("Didn't receive 0-10 message.", message);
+
+ // Validate that JMS_QPID_DESTTYPE is not set
+ try
+ {
+ message.getIntProperty(CustomJMSXProperty.JMS_QPID_DESTTYPE.toString());
+ fail("JMS_QPID_DESTTYPE should not be set, so should throw NumberFormatException");
+ }
+ catch (NumberFormatException nfe)
+ {
+
+ }
+
+ // Resend message back to queue and validate that
+ // a) getJMSDestination works without the JMS_QPID_DESTTYPE
+ // b) we can actually send without a BufferOverFlow.
+
+ MessageProducer producer = session08.createProducer(queue);
+
+ try
+ {
+ producer.send(message);
+ }
+ catch (BufferOverflowException bofe)
+ {
+ // Print the stack trace so we can validate where the execption occured.
+ bofe.printStackTrace();
+ fail("BufferOverflowException thrown during send");
+ }
+
+ message = consumer.receive(1000);
+
+ assertNotNull("Didn't receive recent 0-8 message.", message);
+
+ // Validate that JMS_QPID_DESTTYPE is not set
+ assertEquals("JMS_QPID_DESTTYPE should be set to a Queue", AMQDestination.QUEUE_TYPE,
+ message.getIntProperty(CustomJMSXProperty.JMS_QPID_DESTTYPE.toString()));
+
+ }
+
+ /**
+ * Send a message to a custom exchange and then verify
+ * the message received has the proper destination set
+ *
+ * @throws Exception
+ */
+ public void testGetDestinationWithCustomExchange() throws Exception
+ {
+
+ AMQDestination dest = new AMQAnyDestination(new AMQShortString("my-exchange"),
+ new AMQShortString("direct"),
+ new AMQShortString("test"),
+ false,
+ false,
+ new AMQShortString("test"),
+ false,
+ new AMQShortString[]{new AMQShortString("test")});
+
+ // to force the creation of my-exchange.
+ sendMessage(_session, dest, 1);
+
+ MessageProducer prod = _session.createProducer(dest);
+
+ MessageConsumer consumer = _session.createConsumer(dest);
+
+ _connection.start();
+
+ sendMessage(_session, dest, 1);
+
+ Message message = consumer.receive(10000);
+
+ assertNotNull("Message should not be null", message);
+
+ Destination destination = message.getJMSDestination();
+
+ assertNotNull("JMSDestination should not be null", destination);
+
+ assertEquals("Incorrect Destination name", "my-exchange", dest.getExchangeName().asString());
+ assertEquals("Incorrect Destination type", "direct", dest.getExchangeClass().asString());
+ assertEquals("Incorrect Routing Key", "test", dest.getRoutingKey().asString());
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/message/MessageToStringTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/message/MessageToStringTest.java
new file mode 100644
index 0000000000..1071861d47
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/message/MessageToStringTest.java
@@ -0,0 +1,257 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.client.message;
+
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.framing.AMQShortString;
+
+import javax.jms.BytesMessage;
+import javax.jms.Connection;
+import javax.jms.ConnectionFactory;
+import javax.jms.JMSException;
+import javax.jms.MapMessage;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.ObjectMessage;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.StreamMessage;
+import javax.jms.TextMessage;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutput;
+import java.io.ObjectOutputStream;
+import java.util.UUID;
+
+public class MessageToStringTest extends QpidBrokerTestCase
+{
+ private Connection _connection;
+ private Session _session;
+ private Queue _queue;
+ MessageConsumer _consumer;
+ private static final String BYTE_TEST = "MapByteTest";
+
+ public void setUp() throws Exception
+ {
+ super.setUp();
+
+ //Create Producer put some messages on the queue
+ _connection = getConnection();
+
+ //Create Consumer
+ _session = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ String queueName = getTestQueueName();
+
+ //Create Queue
+ ((AMQSession) _session).createQueue(new AMQShortString(queueName), true, false, false);
+ _queue = _session.createQueue("direct://amq.direct/"+queueName+"/"+queueName+"?durable='false'&autodelete='true'");
+
+
+ _consumer = _session.createConsumer(_queue);
+
+ _connection.start();
+ }
+
+ public void tearDown() throws Exception
+ {
+ //clean up
+ _connection.close();
+
+ super.tearDown();
+ }
+
+ public void testBytesMessage() throws JMSException
+ {
+ //Create Sample Message using UUIDs
+ UUID test = UUID.randomUUID();
+
+ BytesMessage testMessage = _session.createBytesMessage();
+
+ //Convert UUID into bytes for transit
+ byte[] testBytes = test.toString().getBytes();
+
+ testMessage.writeBytes(testBytes);
+
+ sendAndTest(testMessage, testBytes);
+ }
+
+ public void testMapMessage() throws JMSException, IOException
+ {
+ //Create Sample Message using UUIDs
+ UUID test = UUID.randomUUID();
+
+ MapMessage testMessage = _session.createMapMessage();
+
+ byte[] testBytes = convertToBytes(test);
+
+ testMessage.setBytes(BYTE_TEST, testBytes);
+
+ sendAndTest(testMessage, testBytes);
+ }
+
+ public void testObjectMessage() throws JMSException
+ {
+ MessageProducer producer = _session.createProducer(_queue);
+
+ //Create Sample Message using UUIDs
+ UUID test = UUID.randomUUID();
+
+ Message testMessage = _session.createObjectMessage(test);
+
+ sendAndTest(testMessage, test);
+ }
+
+ public void testStreamMessage() throws JMSException, IOException
+ {
+ //Create Sample Message using UUIDs
+ UUID test = UUID.randomUUID();
+
+ StreamMessage testMessage = _session.createStreamMessage();
+
+ byte[] testBytes = convertToBytes(test);
+
+ testMessage.writeBytes(testBytes);
+
+ sendAndTest(testMessage, testBytes);
+ }
+
+ public void testTextMessage() throws JMSException, IOException
+ {
+ //Create Sample Message using UUIDs
+ UUID test = UUID.randomUUID();
+
+ TextMessage testMessage = _session.createTextMessage();
+
+ String stringValue = String.valueOf(test);
+ byte[] testBytes = stringValue.getBytes();
+
+ testMessage.setText(stringValue);
+
+ sendAndTest(testMessage, testBytes);
+ }
+
+ //***************** Helpers
+
+ private void sendAndTest(Message message, Object testBytes) throws JMSException
+ {
+ MessageProducer producer = _session.createProducer(_queue);
+
+ producer.send(message);
+
+ Message receivedMessage = _consumer.receive(1000);
+
+ assertNotNull("Message was not received.", receivedMessage);
+
+ //Ensure that to calling toString doesn't error and that doing this doesn't break next tests.
+ assertNotNull("Message returned null from toString", receivedMessage.toString());
+
+ byte[] byteResults;
+ UUID result;
+
+ try
+ {
+ if (receivedMessage instanceof ObjectMessage)
+ {
+ result = (UUID) ((ObjectMessage) receivedMessage).getObject();
+ assertEquals("UUIDs were not equal", testBytes, result);
+ }
+ else
+ {
+ byteResults = getBytes(receivedMessage, ((byte[]) testBytes).length);
+ assertBytesEquals("UUIDs were not equal", (byte[]) testBytes, byteResults);
+ }
+ }
+ catch (Exception e)
+ {
+ fail(e.getMessage());
+ }
+
+ }
+
+ private void assertBytesEquals(String message, byte[] expected, byte[] actual)
+ {
+ if (expected.length == actual.length)
+ {
+ int index = 0;
+ boolean failed = false;
+ for (byte b : expected)
+ {
+ if (actual[index++] != b)
+ {
+ failed = true;
+ break;
+ }
+ }
+
+ if (!failed)
+ {
+ return;
+ }
+
+ }
+
+ fail(message);
+ }
+
+ private byte[] getBytes(Message receivedMessage, int testBytesLength) throws JMSException
+ {
+ byte[] byteResults = new byte[testBytesLength];
+
+ if (receivedMessage instanceof BytesMessage)
+ {
+ assertEquals(testBytesLength, ((BytesMessage) receivedMessage).readBytes(byteResults));
+ }
+ else if (receivedMessage instanceof StreamMessage)
+ {
+ assertEquals(testBytesLength, ((StreamMessage) receivedMessage).readBytes(byteResults));
+ }
+ else if (receivedMessage instanceof MapMessage)
+ {
+ byteResults = ((MapMessage) receivedMessage).getBytes(BYTE_TEST);
+ assertEquals(testBytesLength, byteResults.length);
+ }
+ else if (receivedMessage instanceof TextMessage)
+ {
+ byteResults = ((TextMessage) receivedMessage).getText().getBytes();
+ assertEquals(testBytesLength, byteResults.length);
+ }
+
+
+ return byteResults;
+ }
+
+ private byte[] convertToBytes(UUID test) throws IOException
+ {
+ //Convert UUID into bytes for transit
+ ObjectOutput out;
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ out = new ObjectOutputStream(bos);
+ out.writeObject(test);
+ out.close();
+
+ return bos.toByteArray();
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/message/ObjectMessageTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/message/ObjectMessageTest.java
new file mode 100644
index 0000000000..147a03be0c
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/message/ObjectMessageTest.java
@@ -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.
+ *
+ */
+package org.apache.qpid.test.client.message;
+
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.framing.AMQShortString;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.ObjectMessage;
+import javax.jms.Queue;
+import javax.jms.Session;
+import java.util.UUID;
+
+public class ObjectMessageTest extends QpidBrokerTestCase
+{
+ private Connection _connection;
+ private Session _session;
+ MessageConsumer _consumer;
+ MessageProducer _producer;
+
+ public void setUp() throws Exception
+ {
+ super.setUp();
+
+ //Create Connection
+ _connection = getConnection();
+
+
+ //Create Session
+ _session = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ //Create Queue
+ String queueName = getTestQueueName();
+ ((AMQSession) _session).createQueue(new AMQShortString(queueName), true, false, false);
+ Queue queue = _session.createQueue("direct://amq.direct/"+queueName+"/"+queueName+"?durable='false'&autodelete='true'");
+
+ //Create Consumer
+ _consumer = _session.createConsumer(queue);
+
+ //Create Producer
+ _producer = _session.createProducer(queue);
+
+ _connection.start();
+ }
+
+ public void tearDown() throws Exception
+ {
+ //clean up
+ _connection.close();
+
+ super.tearDown();
+ }
+
+ public void testGetAndSend() throws JMSException
+ {
+ //Create Sample Message using UUIDs
+ UUID test = UUID.randomUUID();
+
+ ObjectMessage testMessage = _session.createObjectMessage(test);
+
+ Object o = testMessage.getObject();
+
+ assertNotNull("Object was null", o);
+
+ sendAndTest(testMessage, test);
+ }
+
+ public void testSend() throws JMSException
+ {
+ //Create Sample Message using UUIDs
+ UUID test = UUID.randomUUID();
+
+ ObjectMessage testMessage = _session.createObjectMessage(test);
+
+ sendAndTest(testMessage, test);
+ }
+
+ public void testTostringAndSend() throws JMSException
+ {
+ //Create Sample Message using UUIDs
+ UUID test = UUID.randomUUID();
+
+ ObjectMessage testMessage = _session.createObjectMessage(test);
+
+ assertNotNull("Object was null", testMessage.toString());
+
+ sendAndTest(testMessage, test);
+ }
+
+ public void testSendNull() throws JMSException
+ {
+
+ ObjectMessage testMessage = _session.createObjectMessage(null);
+
+ assertNotNull("Object was null", testMessage.toString());
+
+ sendAndTest(testMessage, null);
+ }
+
+ //***************** Helpers
+
+ private void sendAndTest(ObjectMessage message, Object sent) throws JMSException
+ {
+ _producer.send(message);
+
+ ObjectMessage receivedMessage = (ObjectMessage) _consumer.receive(1000);
+
+ assertNotNull("Message was not received.", receivedMessage);
+
+ UUID result = (UUID) receivedMessage.getObject();
+
+ assertEquals("First read: UUIDs were not equal", sent, result);
+
+ result = (UUID) receivedMessage.getObject();
+
+ assertEquals("Second read: UUIDs were not equal", sent, result);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/message/SelectorTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/message/SelectorTest.java
new file mode 100644
index 0000000000..49a608190d
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/message/SelectorTest.java
@@ -0,0 +1,310 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.client.message;
+
+import java.util.concurrent.CountDownLatch;
+
+import javax.jms.DeliveryMode;
+import javax.jms.InvalidSelectorException;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageListener;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+
+import junit.framework.Assert;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.BasicMessageProducer;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SelectorTest extends QpidBrokerTestCase implements MessageListener
+{
+ private static final Logger _logger = LoggerFactory.getLogger(SelectorTest.class);
+
+ private AMQConnection _connection;
+ private AMQDestination _destination;
+ private int count;
+ public String _connectionString = "vm://:1";
+ private static final String INVALID_SELECTOR = "Cost LIKE 5";
+ CountDownLatch _responseLatch = new CountDownLatch(1);
+
+ private static final String BAD_MATHS_SELECTOR = " 1 % 5";
+
+ private static final long RECIEVE_TIMEOUT = 1000;
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ init((AMQConnection) getConnection("guest", "guest"));
+ }
+
+ private void init(AMQConnection connection) throws JMSException
+ {
+ init(connection, new AMQQueue(connection, getTestQueueName(), true));
+ }
+
+ private void init(AMQConnection connection, AMQDestination destination) throws JMSException
+ {
+ _connection = connection;
+ _destination = destination;
+ connection.start();
+ }
+
+ public void onMessage(Message message)
+ {
+ count++;
+ _logger.info("Got Message:" + message);
+ _responseLatch.countDown();
+ }
+
+ public void testUsingOnMessage() throws Exception
+ {
+ String selector = "Cost = 2 AND \"property-with-hyphen\" = 'wibble'";
+ // selector = "JMSType = Special AND Cost = 2 AND AMQMessageID > 0 AND JMSDeliveryMode=" + DeliveryMode.NON_PERSISTENT;
+
+ Session session = (AMQSession) _connection.createSession(false, AMQSession.NO_ACKNOWLEDGE);
+ // _session.createConsumer(destination).setMessageListener(this);
+ session.createConsumer(_destination, selector).setMessageListener(this);
+
+ try
+ {
+ Message msg = session.createTextMessage("Message");
+ msg.setJMSPriority(1);
+ msg.setIntProperty("Cost", 2);
+ msg.setStringProperty("property-with-hyphen", "wibble");
+ msg.setJMSType("Special");
+
+ _logger.info("Sending Message:" + msg);
+
+ ((BasicMessageProducer) session.createProducer(_destination)).send(msg, DeliveryMode.NON_PERSISTENT);
+ _logger.info("Message sent, waiting for response...");
+
+ _responseLatch.await();
+
+ if (count > 0)
+ {
+ _logger.info("Got message");
+ }
+
+ if (count == 0)
+ {
+ fail("Did not get message!");
+ // throw new RuntimeException("Did not get message!");
+ }
+ }
+ catch (JMSException e)
+ {
+ _logger.debug("JMS:" + e.getClass().getSimpleName() + ":" + e.getMessage());
+ if (!(e instanceof InvalidSelectorException))
+ {
+ fail("Wrong exception:" + e.getMessage());
+ }
+ else
+ {
+ System.out.println("SUCCESS!!");
+ }
+ }
+ catch (InterruptedException e)
+ {
+ _logger.debug("IE :" + e.getClass().getSimpleName() + ":" + e.getMessage());
+ }
+
+ }
+
+ public void testUnparsableSelectors() throws Exception
+ {
+ AMQSession session = (AMQSession) _connection.createSession(false, AMQSession.NO_ACKNOWLEDGE);
+ boolean caught = false;
+
+ //Try Creating a Browser
+ try
+ {
+ session.createBrowser(session.createQueue("Ping"), INVALID_SELECTOR);
+ }
+ catch (JMSException e)
+ {
+ _logger.debug("JMS:" + e.getClass().getSimpleName() + ":" + e.getMessage());
+ if (!(e instanceof InvalidSelectorException))
+ {
+ fail("Wrong exception:" + e.getMessage());
+ }
+ caught = true;
+ }
+ assertTrue("No exception thrown!", caught);
+ caught = false;
+
+ //Try Creating a Consumer
+ try
+ {
+ session.createConsumer(session.createQueue("Ping"), INVALID_SELECTOR);
+ }
+ catch (JMSException e)
+ {
+ _logger.debug("JMS:" + e.getClass().getSimpleName() + ":" + e.getMessage());
+ if (!(e instanceof InvalidSelectorException))
+ {
+ fail("Wrong exception:" + e.getMessage());
+ }
+ caught = true;
+ }
+ assertTrue("No exception thrown!", caught);
+ caught = false;
+
+ //Try Creating a Receiever
+ try
+ {
+ session.createReceiver(session.createQueue("Ping"), INVALID_SELECTOR);
+ }
+ catch (JMSException e)
+ {
+ _logger.debug("JMS:" + e.getClass().getSimpleName() + ":" + e.getMessage());
+ if (!(e instanceof InvalidSelectorException))
+ {
+ fail("Wrong exception:" + e.getMessage());
+ }
+ caught = true;
+ }
+ assertTrue("No exception thrown!", caught);
+ caught = false;
+
+ try
+ {
+ session.createReceiver(session.createQueue("Ping"), BAD_MATHS_SELECTOR);
+ }
+ catch (JMSException e)
+ {
+ _logger.debug("JMS:" + e.getClass().getSimpleName() + ":" + e.getMessage());
+ if (!(e instanceof InvalidSelectorException))
+ {
+ fail("Wrong exception:" + e.getMessage());
+ }
+ caught = true;
+ }
+ assertTrue("No exception thrown!", caught);
+ caught = false;
+
+ }
+
+ public void testRuntimeSelectorError() throws JMSException
+ {
+ Session session = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ MessageConsumer consumer = session.createConsumer(_destination , "testproperty % 5 = 1");
+ MessageProducer producer = session.createProducer(_destination);
+ Message sentMsg = session.createTextMessage();
+
+ sentMsg.setIntProperty("testproperty", 1); // 1 % 5
+ producer.send(sentMsg);
+ Message recvd = consumer.receive(RECIEVE_TIMEOUT);
+ assertNotNull(recvd);
+
+ sentMsg.setStringProperty("testproperty", "hello"); // "hello" % 5 makes no sense
+ producer.send(sentMsg);
+ try
+ {
+ recvd = consumer.receive(RECIEVE_TIMEOUT);
+ assertNull(recvd);
+ }
+ catch (Exception e)
+ {
+
+ }
+ assertTrue("Connection should be closed", _connection.isClosed());
+ }
+
+ public void testSelectorWithJMSMessageID() throws Exception
+ {
+ Session session = _connection.createSession(true, Session.SESSION_TRANSACTED);
+
+ MessageProducer prod = session.createProducer(_destination);
+ MessageConsumer consumer = session.createConsumer(_destination,"JMSMessageID IS NOT NULL");
+
+ for (int i=0; i<2; i++)
+ {
+ Message msg = session.createTextMessage("Msg" + String.valueOf(i));
+ prod.send(msg);
+ }
+ session.commit();
+
+ Message msg1 = consumer.receive(1000);
+ Message msg2 = consumer.receive(1000);
+
+ Assert.assertNotNull("Msg1 should not be null", msg1);
+ Assert.assertNotNull("Msg2 should not be null", msg2);
+
+ session.commit();
+
+ prod.setDisableMessageID(true);
+
+ for (int i=0; i<2; i++)
+ {
+ Message msg = session.createTextMessage("Msg" + String.valueOf(i));
+ prod.send(msg);
+ }
+
+ session.commit();
+ Message msg3 = consumer.receive(1000);
+ Assert.assertNull("Msg3 should be null", msg3);
+ session.commit();
+ consumer = session.createConsumer(_destination,"JMSMessageID IS NULL");
+
+ Message msg4 = consumer.receive(1000);
+ Message msg5 = consumer.receive(1000);
+ session.commit();
+ Assert.assertNotNull("Msg4 should not be null", msg4);
+ Assert.assertNotNull("Msg5 should not be null", msg5);
+ }
+
+ public static void main(String[] argv) throws Exception
+ {
+ SelectorTest test = new SelectorTest();
+ test._connectionString = (argv.length == 0) ? "localhost:3000" : argv[0];
+
+ try
+ {
+ while (true)
+ {
+ if (test._connectionString.contains("vm://:1"))
+ {
+ test.setUp();
+ }
+ test.testUsingOnMessage();
+
+ if (test._connectionString.contains("vm://:1"))
+ {
+ test.tearDown();
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ System.err.println(e.getMessage());
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/queue/LVQTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/queue/LVQTest.java
new file mode 100644
index 0000000000..14fbd1deb6
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/queue/LVQTest.java
@@ -0,0 +1,84 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.client.queue;
+
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+
+import org.apache.qpid.test.client.destination.AddressBasedDestinationTest;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class LVQTest extends QpidBrokerTestCase
+{
+ private static final Logger _logger = LoggerFactory.getLogger(LVQTest.class);
+ private Connection _connection;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ _connection = getConnection() ;
+ _connection.start();
+ }
+
+ @Override
+ public void tearDown() throws Exception
+ {
+ _connection.close();
+ super.tearDown();
+ }
+
+ public void testLVQQueue() throws Exception
+ {
+ String addr = "ADDR:my-lvq-queue; {create: always, " +
+ "node: {x-bindings: [{exchange : 'amq.direct', key : test}], " +
+ "x-declare:{arguments : {'qpid.last_value_queue':1}}}}";
+
+ Session ssn = _connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
+
+ Destination dest = ssn.createQueue(addr);
+ MessageConsumer consumer = ssn.createConsumer(dest);
+ MessageProducer prod = ssn.createProducer(ssn.createQueue("ADDR:amq.direct/test"));
+
+ for (int i=0; i<40; i++)
+ {
+ Message msg = ssn.createTextMessage(String.valueOf(i));
+ msg.setStringProperty("qpid.LVQ_key", String.valueOf(i%10));
+ prod.send(msg);
+ }
+
+ for (int i=0; i<10; i++)
+ {
+ TextMessage msg = (TextMessage)consumer.receive(500);
+ assertEquals("The last value is not reflected","3" + i,msg.getText());
+ }
+
+ assertNull("There should not be anymore messages",consumer.receive(500));
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/queue/QueuePolicyTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/queue/QueuePolicyTest.java
new file mode 100644
index 0000000000..e3557efd97
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/queue/QueuePolicyTest.java
@@ -0,0 +1,110 @@
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+package org.apache.qpid.test.client.queue;
+
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class QueuePolicyTest extends QpidBrokerTestCase
+{
+ private static final Logger _logger = LoggerFactory.getLogger(QueuePolicyTest.class);
+ private Connection _connection;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ _connection = getConnection() ;
+ _connection.start();
+ }
+
+ @Override
+ public void tearDown() throws Exception
+ {
+ _connection.close();
+ super.tearDown();
+ }
+
+ public void testRejectPolicy() throws Exception
+ {
+ String addr = "ADDR:queue; {create: always, " +
+ "node: {x-bindings: [{exchange : 'amq.direct', key : test}], " +
+ "x-declare:{ arguments : {'qpid.max_count':5} }}}";
+
+ Session ssn = _connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
+
+ Destination dest = ssn.createQueue(addr);
+ MessageConsumer consumer = ssn.createConsumer(dest);
+ MessageProducer prod = ssn.createProducer(ssn.createQueue("ADDR:amq.direct/test"));
+
+ for (int i=0; i<6; i++)
+ {
+ prod.send(ssn.createMessage());
+ }
+
+ try
+ {
+ prod.send(ssn.createMessage());
+ ((AMQSession)ssn).sync();
+ fail("The client did not receive an exception after exceeding the queue limit");
+ }
+ catch (AMQException e)
+ {
+ assertTrue("The correct error code is not set",e.getErrorCode().toString().contains("506"));
+ }
+ }
+
+ public void testRingPolicy() throws Exception
+ {
+ Session ssn = _connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
+
+ String addr = "ADDR:my-ring-queue; {create: always, " +
+ "node: {x-bindings: [{exchange : 'amq.direct', key : test}], " +
+ "x-declare:{arguments : {'qpid.policy_type':ring, 'qpid.max_count':2} }}}";
+
+ Destination dest = ssn.createQueue(addr);
+ MessageConsumer consumer = ssn.createConsumer(dest);
+ MessageProducer prod = ssn.createProducer(ssn.createQueue("ADDR:amq.direct/test"));
+
+ prod.send(ssn.createTextMessage("Test1"));
+ prod.send(ssn.createTextMessage("Test2"));
+ prod.send(ssn.createTextMessage("Test3"));
+
+ TextMessage msg = (TextMessage)consumer.receive(1000);
+ assertEquals("The consumer should receive the msg with body='Test2'",msg.getText(),"Test2");
+
+ msg = (TextMessage)consumer.receive(1000);
+ assertEquals("The consumer should receive the msg with body='Test3'",msg.getText(),"Test3");
+
+ prod.send(ssn.createTextMessage("Test4"));
+ assertEquals("The consumer should receive the msg with body='Test4'",msg.getText(),"Test3");
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/timeouts/SyncWaitDelayTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/timeouts/SyncWaitDelayTest.java
new file mode 100644
index 0000000000..85565a33b0
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/timeouts/SyncWaitDelayTest.java
@@ -0,0 +1,112 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.client.timeouts;
+
+import java.io.File;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+
+import org.apache.commons.configuration.XMLConfiguration;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.registry.ConfigurationFileApplicationRegistry;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This tests that when the commit takes a long time(due to POST_COMMIT_DELAY) that the commit does not timeout
+ * This test must be run in conjunction with SyncWaiteTimeoutDelay or be run with POST_COMMIT_DELAY > 30s to ensure
+ * that the default value is being replaced.
+ */
+public class SyncWaitDelayTest extends QpidBrokerTestCase
+{
+ protected static final Logger _logger = LoggerFactory.getLogger(SyncWaitDelayTest.class);
+
+ private String VIRTUALHOST = "test";
+ protected long POST_COMMIT_DELAY = 1000L;
+ protected long SYNC_WRITE_TIMEOUT = POST_COMMIT_DELAY + 1000;
+
+ protected Connection _connection;
+ protected Session _session;
+ protected Queue _queue;
+ protected MessageConsumer _consumer;
+
+ public void setUp() throws Exception
+ {
+
+ setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST+".store.class", "org.apache.qpid.server.store.SlowMessageStore");
+ setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST+".store.delays.commitTran.post", String.valueOf(POST_COMMIT_DELAY));
+ setConfigurationProperty("management.enabled", "false");
+
+
+ super.setUp();
+
+ //Set the syncWrite timeout to be just larger than the delay on the commitTran.
+ setSystemProperty("amqj.default_syncwrite_timeout", String.valueOf(SYNC_WRITE_TIMEOUT));
+
+ _connection = getConnection();
+
+ //Create Queue
+ _queue = (Queue) getInitialContext().lookup("queue");
+
+ //Create Consumer
+ _session = _connection.createSession(true, Session.SESSION_TRANSACTED);
+
+ //Ensure Queue exists
+ _session.createConsumer(_queue).close();
+ }
+
+
+ public void test() throws JMSException
+ {
+ MessageProducer producer = _session.createProducer(_queue);
+
+ Message message = _session.createTextMessage("Message");
+
+ producer.send(message);
+
+ long start = System.nanoTime();
+
+ _logger.info("Calling Commit");
+
+ try
+ {
+ _session.commit();
+ long end = System.nanoTime();
+ long time = (end - start);
+ // As we are using Nano time ensure to multiply up the millis.
+ assertTrue("Commit was quickier than the built in delay:" + time, time > 1000000L * POST_COMMIT_DELAY);
+ assertFalse("Commit was slower than the built in default", time > 1000000L * 1000 * 30);
+ }
+ catch (JMSException e)
+ {
+ fail(e.getMessage());
+ }
+
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/client/timeouts/SyncWaitTimeoutDelayTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/timeouts/SyncWaitTimeoutDelayTest.java
new file mode 100644
index 0000000000..1a23eee8ab
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/client/timeouts/SyncWaitTimeoutDelayTest.java
@@ -0,0 +1,72 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.client.timeouts;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQTimeoutException;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageProducer;
+
+/** This tests that when the syncWrite timeout is set that it will timeout on that time rather than the default 30s. */
+public class SyncWaitTimeoutDelayTest extends SyncWaitDelayTest
+{
+ protected static final Logger _logger = Logger.getLogger(SyncWaitTimeoutDelayTest.class);
+
+ public void setUp() throws Exception
+ {
+ POST_COMMIT_DELAY = 1000L;
+
+ //Set the syncWrite timeout to be less than the COMMIT Delay so we can validate that it is being applied
+ SYNC_WRITE_TIMEOUT = 500L;
+
+ super.setUp();
+ }
+
+ @Override
+ public void test() throws JMSException
+ {
+ MessageProducer producer = _session.createProducer(_queue);
+
+ Message message = _session.createTextMessage("Message");
+
+ producer.send(message);
+
+ _logger.info("Calling Commit");
+
+ long start = System.nanoTime();
+ try
+ {
+ _session.commit();
+ fail("Commit occured even though syncWait timeout is shorter than delay in commit");
+ }
+ catch (JMSException e)
+ {
+ assertTrue("Wrong exception type received.", e.getLinkedException() instanceof AMQTimeoutException);
+ assertTrue("Wrong message received on exception.", e.getMessage().startsWith("Failed to commit"));
+ // As we are using Nano time ensure to multiply up the millis.
+ assertTrue("Timeout was more than 30s default", (System.nanoTime() - start) < (1000000L * 1000 * 30));
+ }
+
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/AMQPPublisher.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/AMQPPublisher.java
new file mode 100644
index 0000000000..13465741bd
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/AMQPPublisher.java
@@ -0,0 +1,54 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+/**
+ * An AMQPPublisher represents the status of the publishing side of a test circuit that exposes AMQP specific features.
+ * Its provides additional assertions not available through the plain JMS {@link Publisher} interface.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities
+ * <tr><td> Provide assertion that the publishers received a no consumers error code on every message.
+ * <tr><td> Provide assertion that the publishers received a no route error code on every message.
+ * </table>
+ */
+public interface AMQPPublisher extends Publisher
+{
+ /**
+ * Provides an assertion that the publisher got a no consumers exception on every message.
+ *
+ * @param testProps The test configuration properties.
+ *
+ * @return An assertion that the publisher got a no consumers exception on every message.
+ */
+ Assertion noConsumersAssertion(ParsedProperties testProps);
+
+ /**
+ * Provides an assertion that the publisher got a no rout exception on every message.
+ *
+ * @param testProps The test configuration properties.
+ *
+ * @return An assertion that the publisher got a no rout exception on every message.
+ */
+ Assertion noRouteAssertion(ParsedProperties testProps);
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/Assertion.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/Assertion.java
new file mode 100644
index 0000000000..60d54f1f6f
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/Assertion.java
@@ -0,0 +1,39 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+/**
+ * Assertion models an assertion on a test {@link Circuit}.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities
+ * <tr><td> Indicate whether or not the assertion passes when applied.
+ * </table>
+ */
+public interface Assertion
+{
+ /**
+ * Applies the assertion.
+ *
+ * @return <tt>true</tt> if the assertion passes, <tt>false</tt> if it fails.
+ */
+ public boolean apply();
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/AssertionBase.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/AssertionBase.java
new file mode 100644
index 0000000000..0bb4911d4c
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/AssertionBase.java
@@ -0,0 +1,66 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * AssertionBase is a base class for implenmenting assertions. It provides a mechanism to store error messages, and
+ * report all error messages when its {@link #toString()} method is called.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Collect error messages.
+ * </table>
+ */
+public abstract class AssertionBase implements Assertion
+{
+ /** Holds the error messages. */
+ List<String> errors = new LinkedList<String>();
+
+ /**
+ * Adds an error message to the assertion.
+ *
+ * @param error An error message to add to the assertion.
+ */
+ public void addError(String error)
+ {
+ errors.add(error);
+ }
+
+ /**
+ * Prints all of the error messages in the assertion into a string.
+ *
+ * @return All of the error messages in the assertion as a string.
+ */
+ public String toString()
+ {
+ String result = "";
+
+ for (String error : errors)
+ {
+ result += error;
+ }
+
+ return result;
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/BrokerLifecycleAware.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/BrokerLifecycleAware.java
new file mode 100644
index 0000000000..41614f92fc
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/BrokerLifecycleAware.java
@@ -0,0 +1,70 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+/**
+ * BrokerLifecycleAware is an awareness interface implemented by test cases that can run control the life-cycle of
+ * the brokers on which they run. Its purpose is to expose additional instrumentation of brokers during testing, that
+ * enables tests to use an automated failure mechanism to simulate broker failures, and to re-start failed brokers.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Indicate whether or not a test case is using an in-vm broker.
+ * <tr><td> Track which in-vm broker is currently in use.
+ * <tr><td> Accept setting of a failure mechanism. <td> {@link CauseFailure}.
+ * </table>
+ *
+ * @todo Need to think about how to present the brokers through this interface. Thinking numbering the available
+ * brokers from 1 will do. Then can kill 1 and assume failing onto 2. Restart 1 and kill 2 and fail back onto
+ * 1 again?
+ */
+public interface BrokerLifecycleAware
+{
+ public void setInVmBrokers();
+
+ /**
+ * Indicates whether or not a test case is using in-vm brokers.
+ *
+ * @return <tt>true</tt> if the test is using in-vm brokers, <tt>false</tt> otherwise.
+ */
+ public boolean usingInVmBroker();
+
+ /**
+ * Sets the currently live in-vm broker.
+ *
+ * @param i The currently live in-vm broker.
+ */
+ public void setLiveBroker(int i);
+
+ /**
+ * Reports the currently live in-vm broker.
+ *
+ * @return The currently live in-vm broker.
+ */
+ public int getLiveBroker();
+
+ /**
+ * Accepts a failure mechanism.
+ *
+ * @param failureMechanism The failure mechanism.
+ */
+ public void setFailureMechanism(CauseFailure failureMechanism);
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/CauseFailure.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/CauseFailure.java
new file mode 100644
index 0000000000..9bdd5a72c5
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/CauseFailure.java
@@ -0,0 +1,42 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+/**
+ * CauseFailure provides a method to cause a failure in a messaging broker, usually used in conjunction with fail-over
+ * or other failure mode testing. In some cases failures may be automated, for example by shutting down an in-vm broker,
+ * or by sending a special control signal to a broker over a network connection. In other cases, it may be preferable
+ * to ask a user interactively to cause a failure scenario, in which case an implementation may display a prompt or
+ * dialog box asking for notification once the failure has been caused. The purpose of this interface is to abstract
+ * the exact cause and nature of a failure out of failure test cases.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities
+ * <tr><td> Cause messaging broker failure.
+ * </table>
+ */
+public interface CauseFailure
+{
+ /**
+ * Causes the active message broker to fail.
+ */
+ void causeFailure();
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/CauseFailureUserPrompt.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/CauseFailureUserPrompt.java
new file mode 100644
index 0000000000..889df4ad07
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/CauseFailureUserPrompt.java
@@ -0,0 +1,65 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+import org.apache.qpid.test.framework.CauseFailure;
+
+import java.io.IOException;
+
+/**
+ * Causes a message broker failure by interactively prompting the user to cause it.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Cause messaging broker failure.
+ * </table>
+ */
+public class CauseFailureUserPrompt implements CauseFailure
+{
+ /**
+ * Causes the active message broker to fail.
+ */
+ public void causeFailure()
+ {
+ waitForUser("Cause a broker failure now, then press Return.");
+ }
+
+ /**
+ * Outputs a prompt to the console and waits for the user to press return.
+ *
+ * @param prompt The prompt to display on the console.
+ */
+ private void waitForUser(String prompt)
+ {
+ System.out.println(prompt);
+
+ try
+ {
+ System.in.read();
+ }
+ catch (IOException e)
+ {
+ // Ignored.
+ }
+
+ System.out.println("Continuing.");
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/Circuit.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/Circuit.java
new file mode 100644
index 0000000000..4f9ab1a273
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/Circuit.java
@@ -0,0 +1,109 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+import java.util.List;
+
+/**
+ * A Circuit is the basic test unit against which test cases are to be written. A circuit consists of two 'ends', an
+ * instigating 'publisher' end and a more passive 'receivers' end.
+ *
+ * <p/>Once created, the life-cycle of a circuit may be controlled by {@link #start()}ing it, or {@link #close()}ing it.
+ * Once started, the circuit is ready to send messages over. Once closed the circuit can no longer be used.
+ *
+ * <p/>The state of the circuit may be taken with the {@link #check()} method, and asserted against by the
+ * {@link #applyAssertions(java.util.List)} method.
+ *
+ * <p/>There is a default test procedure which may be performed against the circuit. The outline of this procedure is:
+ *
+ * <p/><pre>
+ * Start the circuit.
+ * Send test messages.
+ * Request a status report.
+ * Assert conditions on the publishing end of the circuit.
+ * Assert conditions on the receiving end of the circuit.
+ * Close the circuit.
+ * Pass with no failed assertions or fail with a list of failed assertions.
+ * </pre>
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities
+ * <tr><td> Supply the publishing and receiving ends of a test messaging circuit.
+ * <tr><td> Start the circuit running.
+ * <tr><td> Close the circuit down.
+ * <tr><td> Take a reading of the circuits state.
+ * <tr><td> Apply assertions against the circuits state.
+ * <tr><td> Send test messages over the circuit.
+ * <tr><td> Perform the default test procedue on the circuit.
+ * </table>
+ */
+public interface Circuit
+{
+ /**
+ * Gets the interface on the publishing end of the circuit.
+ *
+ * @return The publishing end of the circuit.
+ */
+ public Publisher getPublisher();
+
+ /**
+ * Gets the interface on the receiving end of the circuit.
+ *
+ * @return The receiving end of the circuit.
+ */
+ public Receiver getReceiver();
+
+ /**
+ * Connects and starts the circuit. After this method is called the circuit is ready to send messages.
+ */
+ public void start();
+
+ /**
+ * Checks the test circuit. The effect of this is to gather the circuits state, for both ends of the circuit,
+ * into a report, against which assertions may be checked.
+ */
+ public void check();
+
+ /**
+ * Closes the circuit. All associated resources are closed.
+ */
+ public void close();
+
+ /**
+ * Applied a list of assertions against the test circuit. The {@link #check()} method should be called before doing
+ * this, to ensure that the circuit has gathered its state into a report to assert against.
+ *
+ * @param assertions The list of assertions to apply to the circuit.
+ *
+ * @return Any assertions that failed.
+ */
+ public List<Assertion> applyAssertions(List<Assertion> assertions);
+
+ /**
+ * Runs the default test procedure against the circuit, and checks that all of the specified assertions hold.
+ *
+ * @param numMessages The number of messages to send using the default test procedure.
+ * @param assertions The list of assertions to apply.
+ *
+ * @return Any assertions that failed.
+ */
+ public List<Assertion> test(int numMessages, List<Assertion> assertions);
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/CircuitEnd.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/CircuitEnd.java
new file mode 100644
index 0000000000..824edd7022
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/CircuitEnd.java
@@ -0,0 +1,91 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+import javax.jms.*;
+
+/**
+ * A CircuitEnd is a pair consisting of one message producer and one message consumer, that represents one end of a
+ * test circuit. It is a standard unit of connectivity allowing a full-duplex conversation to be held, provided both
+ * the consumer and producer are instantiated and configured.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities
+ * <tr><td> Provide a message producer for sending messages.
+ * <tr><td> Provide a message consumer for receiving messages.
+ * </table>
+ *
+ * @todo Update the {@link org.apache.qpid.test.utils.ConversationFactory} so that it accepts these as the basic conversation
+ * connection units.
+ */
+public interface CircuitEnd
+{
+ /**
+ * Gets the message producer at this circuit end point.
+ *
+ * @return The message producer at with this circuit end point.
+ */
+ public MessageProducer getProducer();
+
+ /**
+ * Gets the message consumer at this circuit end point.
+ *
+ * @return The message consumer at this circuit end point.
+ */
+ public MessageConsumer getConsumer();
+
+ /**
+ * Send the specified message over the producer at this end point.
+ *
+ * @param message The message to send.
+ *
+ * @throws JMSException Any JMS exception occuring during the send is allowed to fall through.
+ */
+ public void send(Message message) throws JMSException;
+
+ /**
+ * Gets the JMS Session associated with this circuit end point.
+ *
+ * @return The JMS Session associated with this circuit end point.
+ */
+ public Session getSession();
+
+ /**
+ * Closes the message producers and consumers and the sessions, associated with this circuit end point.
+ *
+ * @throws JMSException Any JMSExceptions occurring during the close are allowed to fall through.
+ */
+ public void close() throws JMSException;
+
+ /**
+ * Returns the message monitor for reporting on received messages on this circuit end.
+ *
+ * @return The message monitor for this circuit end.
+ */
+ public MessageMonitor getMessageMonitor();
+
+ /**
+ * Returns the exception monitor for reporting on exceptions received on this circuit end.
+ *
+ * @return The exception monitor for this circuit end.
+ */
+ public ExceptionMonitor getExceptionMonitor();
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/CircuitEndBase.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/CircuitEndBase.java
new file mode 100644
index 0000000000..d5a33514df
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/CircuitEndBase.java
@@ -0,0 +1,152 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+import javax.jms.*;
+
+/**
+ * A CircuitEndBase is a pair consisting of one message producer and one message consumer, that represents one end of a
+ * test circuit. It is a standard unit of connectivity allowing a full-duplex conversation to be held, provided both
+ * the consumer and producer are instantiated and configured.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities
+ * <tr><td> Provide a message producer for sending messages.
+ * <tr><td> Provide a message consumer for receiving messages.
+ * </table>
+ */
+public class CircuitEndBase implements CircuitEnd
+{
+ /** Holds the single message producer. */
+ MessageProducer producer;
+
+ /** Holds the single message consumer. */
+ MessageConsumer consumer;
+
+ /** Holds the controlSession for the circuit end. */
+ Session session;
+
+ /** Holds the message monitor for the circuit end. */
+ MessageMonitor messageMonitor;
+
+ /** Holds the exception monitor for the circuit end. */
+ ExceptionMonitor exceptionMonitor;
+
+ /**
+ * Creates a circuit end point on the specified producer, consumer and controlSession. Monitors are also configured
+ * for messages and exceptions received by the circuit end.
+ *
+ * @param producer The message producer for the circuit end point.
+ * @param consumer The message consumer for the circuit end point.
+ * @param session The controlSession for the circuit end point.
+ * @param messageMonitor The monitor to notify of all messages received by the circuit end.
+ * @param exceptionMonitor The monitor to notify of all exceptions received by the circuit end.
+ */
+ public CircuitEndBase(MessageProducer producer, MessageConsumer consumer, Session session, MessageMonitor messageMonitor,
+ ExceptionMonitor exceptionMonitor)
+ {
+ this.producer = producer;
+ this.consumer = consumer;
+ this.session = session;
+
+ this.messageMonitor = messageMonitor;
+ this.exceptionMonitor = exceptionMonitor;
+ }
+
+ /**
+ * Gets the message producer at this circuit end point.
+ *
+ * @return The message producer at with this circuit end point.
+ */
+ public MessageProducer getProducer()
+ {
+ return producer;
+ }
+
+ /**
+ * Gets the message consumer at this circuit end point.
+ *
+ * @return The message consumer at this circuit end point.
+ */
+ public MessageConsumer getConsumer()
+ {
+ return consumer;
+ }
+
+ /**
+ * Send the specified message over the producer at this end point.
+ *
+ * @param message The message to send.
+ * @throws javax.jms.JMSException Any JMS exception occuring during the send is allowed to fall through.
+ */
+ public void send(Message message) throws JMSException
+ {
+ producer.send(message);
+ }
+
+ /**
+ * Gets the JMS Session associated with this circuit end point.
+ *
+ * @return The JMS Session associated with this circuit end point.
+ */
+ public Session getSession()
+ {
+ return session;
+ }
+
+ /**
+ * Closes the message producers and consumers and the sessions, associated with this circuit end point.
+ *
+ * @throws javax.jms.JMSException Any JMSExceptions occurring during the close are allowed to fall through.
+ */
+ public void close() throws JMSException
+ {
+ if (producer != null)
+ {
+ producer.close();
+ }
+
+ if (consumer != null)
+ {
+ consumer.close();
+ }
+ }
+
+ /**
+ * Returns the message monitor for reporting on received messages on this circuit end.
+ *
+ * @return The message monitor for this circuit end.
+ */
+ public MessageMonitor getMessageMonitor()
+ {
+ return messageMonitor;
+ }
+
+ /**
+ * Returns the exception monitor for reporting on exceptions received on this circuit end.
+ *
+ * @return The exception monitor for this circuit end.
+ */
+ public ExceptionMonitor getExceptionMonitor()
+ {
+ return exceptionMonitor;
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/DropInTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/DropInTest.java
new file mode 100644
index 0000000000..78b5a72c1f
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/DropInTest.java
@@ -0,0 +1,51 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+
+/**
+ * A DropIn test is a test case that can accept late joining test clients into a running test. This can be usefull,
+ * for interactive experimentation.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities
+ * <tr><td> Accept late joining test clients.
+ * </table>
+ */
+public interface DropInTest
+{
+ /**
+ * Should accept a late joining client into a running test case. The client will be enlisted with a control message
+ * with the 'CONTROL_TYPE' field set to the value 'LATEJOIN'. It should also provide values for the fields:
+ *
+ * <p/><table>
+ * <tr><td> CLIENT_NAME <td> A unique name for the new client.
+ * <tr><td> CLIENT_PRIVATE_CONTROL_KEY <td> The key for the route on which the client receives its control messages.
+ * </table>
+ *
+ * @param message The late joiners join message.
+ *
+ * @throws JMSException Any JMS Exception are allowed to fall through, indicating that the join failed.
+ */
+ public void lateJoin(Message message) throws JMSException;
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/ExceptionMonitor.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/ExceptionMonitor.java
new file mode 100644
index 0000000000..7d06aba1c0
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/ExceptionMonitor.java
@@ -0,0 +1,205 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+import org.apache.log4j.Logger;
+
+import javax.jms.ExceptionListener;
+import javax.jms.JMSException;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An exception monitor, listens for JMS exception on a connection or consumer. It record all exceptions that it receives
+ * and provides methods to test the number and type of exceptions received.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Record all exceptions received.
+ * </table>
+ */
+public class ExceptionMonitor implements ExceptionListener
+{
+ /** Used for debugging. */
+ private final Logger log = Logger.getLogger(ExceptionMonitor.class);
+
+ /** Holds the received exceptions. */
+ List<Exception> exceptions = new ArrayList<Exception>();
+
+ /**
+ * Receives incoming exceptions.
+ *
+ * @param e The exception to record.
+ */
+ public synchronized void onException(JMSException e)
+ {
+ log.debug("public void onException(JMSException e): called", e);
+
+ exceptions.add(e);
+ }
+
+ /**
+ * Checks that no exceptions have been received.
+ *
+ * @return <tt>true</tt> if no exceptions have been received, <tt>false</tt> otherwise.
+ */
+ public synchronized boolean assertNoExceptions()
+ {
+ return exceptions.isEmpty();
+ }
+
+ /**
+ * Checks that exactly one exception has been received.
+ *
+ * @return <tt>true</tt> if exactly one exception been received, <tt>false</tt> otherwise.
+ */
+ public synchronized boolean assertOneJMSException()
+ {
+ return exceptions.size() == 1;
+ }
+
+ /**
+ * Checks that exactly one exception, with a linked cause of the specified type, has been received.
+ *
+ * @param aClass The type of the linked cause.
+ *
+ * @return <tt>true</tt> if exactly one exception, with a linked cause of the specified type, been received,
+ * <tt>false</tt> otherwise.
+ */
+ public synchronized boolean assertOneJMSExceptionWithLinkedCause(Class aClass)
+ {
+ if (exceptions.size() == 1)
+ {
+ Exception e = exceptions.get(0);
+
+ if (e instanceof JMSException)
+ {
+ JMSException jmse = (JMSException) e;
+
+ Exception linkedCause = jmse.getLinkedException();
+
+ if ((linkedCause != null) && aClass.isInstance(linkedCause))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks that at least one exception of the the specified type, has been received.
+ *
+ * @param exceptionClass The type of the exception.
+ *
+ * @return <tt>true</tt> if at least one exception of the specified type has been received, <tt>false</tt> otherwise.
+ */
+ public synchronized boolean assertExceptionOfType(Class exceptionClass)
+ {
+ // Start by assuming that the exception has no been received.
+ boolean passed = false;
+
+ // Scan all the exceptions for a match.
+ for (Exception e : exceptions)
+ {
+ if (exceptionClass.isInstance(e))
+ {
+ passed = true;
+
+ break;
+ }
+ }
+
+ return passed;
+ }
+
+ /**
+ * Reports the number of exceptions held by this monitor.
+ *
+ * @return The number of exceptions held by this monitor.
+ */
+ public synchronized int size()
+ {
+ return exceptions.size();
+ }
+
+ /**
+ * Clears the record of received exceptions.
+ */
+ public synchronized void reset()
+ {
+ exceptions = new ArrayList<Exception>();
+ }
+
+ /**
+ * Provides a dump of the stack traces of all exceptions that this exception monitor was notified of. Mainly
+ * use for debugging/test failure reporting purposes.
+ *
+ * @return A string containing a dump of the stack traces of all exceptions.
+ */
+ public synchronized String toString()
+ {
+ String result = "ExceptionMonitor: holds " + exceptions.size() + " exceptions.\n\n";
+
+ for (Exception ex : exceptions)
+ {
+ result += getStackTrace(ex) + "\n";
+ }
+
+ return result;
+ }
+
+ /**
+ * Prints an exception stack trace into a string.
+ *
+ * @param t The throwable to get the stack trace from.
+ *
+ * @return A string containing the throwables stack trace.
+ */
+ public static String getStackTrace(Throwable t)
+ {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw, true);
+ t.printStackTrace(pw);
+ pw.flush();
+ sw.flush();
+
+ return sw.toString();
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkBaseCase.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkBaseCase.java
new file mode 100644
index 0000000000..f866cd572f
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkBaseCase.java
@@ -0,0 +1,301 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.NDC;
+
+import org.apache.qpid.test.framework.BrokerLifecycleAware;
+import org.apache.qpid.test.framework.sequencers.CircuitFactory;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import org.apache.qpid.junit.extensions.SetupTaskAware;
+import org.apache.qpid.junit.extensions.SetupTaskHandler;
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+import org.apache.qpid.junit.extensions.util.TestContextProperties;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * FrameworkBaseCase provides a starting point for writing test cases against the test framework. Its main purpose is
+ * to provide some convenience methods for testing.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Create and clean up in-vm brokers on every test case.
+ * <tr><td> Produce lists of assertions from assertion creation calls.
+ * <tr><td> Produce JUnit failures from assertion failures.
+ * <tr><td> Convert failed assertions to error messages.
+ * </table>
+ */
+public class FrameworkBaseCase extends QpidBrokerTestCase implements FrameworkTestContext, SetupTaskAware,
+ BrokerLifecycleAware
+{
+ /** Used for debugging purposes. */
+ private static final Logger log = Logger.getLogger(FrameworkBaseCase.class);
+
+ private CircuitFactory circuitFactory = new LocalAMQPCircuitFactory();
+
+ private ParsedProperties testProps;
+
+ private SetupTaskHandler taskHandler = new SetupTaskHandler();
+
+ private boolean isUsingInVM;
+
+ private CauseFailure failureMechanism = new CauseFailureUserPrompt();
+
+ /**
+ * Creates a new test case with the specified name.
+ *
+ * @param name The test case name.
+ */
+ public FrameworkBaseCase(String name)
+ {
+ super(name);
+ }
+
+ /** Holds the test sequencer to create and run test circuits with. */ /**
+ * Returns the test case sequencer that provides test circuit, and test sequence implementations. The sequencer
+ * that this base case returns by default is suitable for running a test circuit with both circuit ends colocated
+ * on the same JVM.
+ *
+ * @return The test case sequencer.
+ */
+ protected CircuitFactory getCircuitFactory()
+ {
+ return circuitFactory;
+ }
+
+ /**
+ * Overrides the default test circuit factory. Test decorators can use this to supply distributed test sequencers or
+ * other test circuit factory specializations.
+ *
+ * @param circuitFactory The new test circuit factory.
+ */
+ public void setCircuitFactory(CircuitFactory circuitFactory)
+ {
+ this.circuitFactory = circuitFactory;
+ }
+
+ /**
+ * Reports the current test case name.
+ *
+ * @return The current test case name.
+ */
+ public TestCaseVector getTestCaseVector()
+ {
+ return new TestCaseVector(this.getName(), 0);
+ }
+
+ /**
+ * Reports the current test case parameters.
+ *
+ * @return The current test case parameters.
+ */
+ public MessagingTestConfigProperties getTestParameters()
+ {
+ return new MessagingTestConfigProperties(testProps);
+ }
+
+ /**
+ * Creates a list of assertions.
+ *
+ * @param asserts The assertions to compile in a list.
+ *
+ * @return A list of assertions.
+ */
+ protected List<Assertion> assertionList(Assertion... asserts)
+ {
+ List<Assertion> result = new ArrayList<Assertion>();
+
+ for (Assertion assertion : asserts)
+ {
+ result.add(assertion);
+ }
+
+ return result;
+ }
+
+ /**
+ * Generates a JUnit assertion exception (failure) if any assertions are passed into this method, also concatenating
+ * all of the error messages in the assertions together to form an error message to diagnose the test failure with.
+ *
+ * @param asserts The list of failed assertions.
+ */
+ protected static void assertNoFailures(List<Assertion> asserts)
+ {
+ log.debug("protected void assertNoFailures(List<Assertion> asserts = " + asserts + "): called");
+
+ // Check if there are no assertion failures, and return without doing anything if so.
+ if ((asserts == null) || asserts.isEmpty())
+ {
+ return;
+ }
+
+ // Compile all of the assertion failure messages together.
+ String errorMessage = assertionsToString(asserts);
+
+ // Fail with the error message from all of the assertions.
+ fail(errorMessage);
+ }
+
+ /**
+ * Converts a list of failed assertions into an error message.
+ *
+ * @param asserts The failed assertions.
+ *
+ * @return The error message.
+ */
+ protected static String assertionsToString(List<Assertion> asserts)
+ {
+ String errorMessage = "";
+
+ for (Assertion assertion : asserts)
+ {
+ errorMessage += assertion.toString() + "\n";
+ }
+
+ return errorMessage;
+ }
+
+ /**
+ * Ensures that the in-vm broker is created and initialized.
+ *
+ * @throws Exception Any exceptions allowed to fall through and fail the test.
+ */
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ NDC.push(getName());
+
+ testProps = TestContextProperties.getInstance(MessagingTestConfigProperties.defaults);
+ }
+
+ /**
+ * Ensures that the in-vm broker is cleaned up after each test run.
+ */
+ protected void tearDown()
+ {
+ NDC.pop();
+
+ // Process all optional tear down tasks. This may include in-vm broker clean up, if a decorator has added it.
+ taskHandler.runTearDownTasks();
+ }
+
+ /**
+ * Adds the specified task to the tests setup.
+ *
+ * @param task The task to add to the tests setup.
+ */
+ public void chainSetupTask(Runnable task)
+ {
+ taskHandler.chainSetupTask(task);
+ }
+
+ /**
+ * Adds the specified task to the tests tear down.
+ *
+ * @param task The task to add to the tests tear down.
+ */
+ public void chainTearDownTask(Runnable task)
+ {
+ taskHandler.chainTearDownTask(task);
+ }
+
+ /**
+ * Should provide a translation from the junit method name of a test to its test case name as known to the test
+ * clients that will run the test. The purpose of this is to convert the JUnit method name into the correct test
+ * case name to place into the test invite. For example the method "testP2P" might map onto the interop test case
+ * name "TC2_BasicP2P".
+ *
+ * @param methodName The name of the JUnit test method.
+ *
+ * @return The name of the corresponding interop test case.
+ */
+ public String getTestCaseNameForTestMethod(String methodName)
+ {
+ return methodName;
+ }
+
+ public void setInVmBrokers()
+ {
+ isUsingInVM = true;
+ }
+
+ /**
+ * Indicates whether or not a test case is using in-vm brokers.
+ *
+ * @return <tt>true</tt> if the test is using in-vm brokers, <tt>false</tt> otherwise.
+ */
+ public boolean usingInVmBroker()
+ {
+ return isUsingInVM;
+ }
+
+ /**
+ * Sets the currently live in-vm broker.
+ *
+ * @param i The currently live in-vm broker.
+ */
+ public void setLiveBroker(int i)
+ { }
+
+ /**
+ * Reports the currently live in-vm broker.
+ *
+ * @return The currently live in-vm broker.
+ */
+ public int getLiveBroker()
+ {
+ return 0;
+ }
+
+ /**
+ * Accepts a failure mechanism.
+ *
+ * @param failureMechanism The failure mechanism.
+ */
+ public void setFailureMechanism(CauseFailure failureMechanism)
+ {
+ this.failureMechanism = failureMechanism;
+ }
+
+ protected ParsedProperties getTestProps()
+ {
+ return testProps;
+ }
+
+ protected void setTestProps(ParsedProperties testProps)
+ {
+ this.testProps = testProps;
+ }
+
+ protected SetupTaskHandler getTaskHandler()
+ {
+ return taskHandler;
+ }
+
+ protected CauseFailure getFailureMechanism()
+ {
+ return failureMechanism;
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkClientBaseCase.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkClientBaseCase.java
new file mode 100644
index 0000000000..2322955253
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkClientBaseCase.java
@@ -0,0 +1,31 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+/**
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td>
+ * </table>
+ */
+public class FrameworkClientBaseCase
+{
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkTestContext.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkTestContext.java
new file mode 100644
index 0000000000..9a4668e86f
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/FrameworkTestContext.java
@@ -0,0 +1,48 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+/**
+ * A FrameworkTestContext provides context information to test code about the current test case being run; its name, its
+ * parameters.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Provide the name of the current test case.
+ * <tr><td> Provide the test parameters.
+ * </table>
+ */
+public interface FrameworkTestContext
+{
+ /**
+ * Reports the current test case name.
+ *
+ * @return The current test case name.
+ */
+ TestCaseVector getTestCaseVector();
+
+ /**
+ * Reports the current test case parameters.
+ *
+ * @return The current test case parameters.
+ */
+ MessagingTestConfigProperties getTestParameters();
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/LocalAMQPCircuitFactory.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/LocalAMQPCircuitFactory.java
new file mode 100644
index 0000000000..4c8f301d1c
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/LocalAMQPCircuitFactory.java
@@ -0,0 +1,168 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.test.framework.localcircuit.LocalAMQPPublisherImpl;
+import org.apache.qpid.test.framework.localcircuit.LocalPublisherImpl;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+import javax.jms.*;
+
+/**
+ * LocalAMQPCircuitFactory is a test sequencer that creates test circuits with publishing and receiving ends rooted
+ * on the same JVM, allowing AMQP/Qpid specific options to be applied to the circuit.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Provide a standard test procedure over a test circuit.
+ * <tr><td> Construct test circuits appropriate to a tests context.
+ * <tr><td> Construct test circuits the support AMQP specific options.
+ * </table>
+ */
+public class LocalAMQPCircuitFactory extends LocalCircuitFactory
+{
+ /** Used for debugging. */
+ private static final Logger log = Logger.getLogger(LocalAMQPCircuitFactory.class);
+
+ /**
+ * Builds a circuit end suitable for the publishing side of a test circuit, from standard test parameters.
+ *
+ * @param connection The connection to build the circuit end on.
+ * @param testProps The test parameters to configure the circuit end construction.
+ * @param uniqueId A unique number to being numbering destinations from, to make this circuit unique.
+ *
+ * @return A circuit end suitable for the publishing side of a test circuit.
+ *
+ * @throws javax.jms.JMSException Any underlying JMSExceptions are allowed to fall through and fail the creation.
+ */
+ public CircuitEndBase createPublisherCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId)
+ throws JMSException
+ {
+ log.debug(
+ "public CircuitEndBase createPublisherCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId = "
+ + uniqueId + "): called");
+
+ // Cast the test properties into a typed interface for convenience.
+ MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps);
+
+ Session session = connection.createSession(props.getPublisherTransacted(), props.getAckMode());
+
+ Destination destination =
+ props.getPubsub() ? session.createTopic(props.getSendDestinationNameRoot() + "_" + uniqueId)
+ : session.createQueue(props.getSendDestinationNameRoot() + "_" + uniqueId);
+
+ MessageProducer producer =
+ props.getPublisherProducerBind()
+ ? ((props.getImmediate() || props.getMandatory())
+ ? ((AMQSession) session).createProducer(destination, props.getMandatory(), props.getImmediate())
+ : session.createProducer(destination)) : null;
+
+ MessageConsumer consumer =
+ props.getPublisherConsumerBind()
+ ? session.createConsumer(session.createQueue(props.getReceiveDestinationNameRoot() + "_" + uniqueId)) : null;
+
+ MessageMonitor messageMonitor = new MessageMonitor();
+
+ if (consumer != null)
+ {
+ consumer.setMessageListener(messageMonitor);
+ }
+
+ ExceptionMonitor exceptionMonitor = new ExceptionMonitor();
+ connection.setExceptionListener(exceptionMonitor);
+
+ if (!props.getPublisherConsumerActive() && (consumer != null))
+ {
+ consumer.close();
+ }
+
+ return new CircuitEndBase(producer, consumer, session, messageMonitor, exceptionMonitor);
+ }
+
+ /**
+ * Builds a circuit end suitable for the receiving side of a test circuit, from standard test parameters.
+ *
+ * @param connection The connection to build the circuit end on.
+ * @param testProps The test parameters to configure the circuit end construction.
+ * @param uniqueId A unique number to being numbering destinations from, to make this circuit unique.
+ *
+ * @return A circuit end suitable for the receiving side of a test circuit.
+ *
+ * @throws JMSException Any underlying JMSExceptions are allowed to fall through and fail the creation.
+ */
+ public CircuitEndBase createReceiverCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId)
+ throws JMSException
+ {
+ log.debug(
+ "public CircuitEndBase createReceiverCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId = "
+ + uniqueId + "): called");
+
+ // Cast the test properties into a typed interface for convenience.
+ MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps);
+
+ Session session = connection.createSession(props.getPublisherTransacted(), props.getAckMode());
+
+ MessageProducer producer =
+ props.getReceiverProducerBind()
+ ? session.createProducer(session.createQueue(props.getReceiveDestinationNameRoot() + "_" + uniqueId)) : null;
+
+ Destination destination =
+ props.getPubsub() ? session.createTopic(props.getSendDestinationNameRoot() + "_" + uniqueId)
+ : session.createQueue(props.getSendDestinationNameRoot() + "_" + uniqueId);
+
+ MessageConsumer consumer =
+ props.getReceiverConsumerBind()
+ ? ((props.getDurableSubscription() && props.getPubsub())
+ ? session.createDurableSubscriber((Topic) destination, "testsub") : session.createConsumer(destination))
+ : null;
+
+ MessageMonitor messageMonitor = new MessageMonitor();
+
+ if (consumer != null)
+ {
+ consumer.setMessageListener(messageMonitor);
+ }
+
+ if (!props.getReceiverConsumerActive() && (consumer != null))
+ {
+ consumer.close();
+ }
+
+ return new CircuitEndBase(producer, consumer, session, messageMonitor, null);
+ }
+
+ /**
+ * Creates a local {@link Publisher} from a {@link CircuitEnd}. The publisher implementation provides AMQP
+ * specific assertion methods, for testing beyond JMS.
+ *
+ * @param publisherEnd The publishing circuit end.
+ *
+ * @return A {@link Receiver}.
+ */
+ protected LocalPublisherImpl createPublisherFromCircuitEnd(CircuitEndBase publisherEnd)
+ {
+ return new LocalAMQPPublisherImpl(publisherEnd);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/LocalCircuitFactory.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/LocalCircuitFactory.java
new file mode 100644
index 0000000000..ec70759cf7
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/LocalCircuitFactory.java
@@ -0,0 +1,316 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.test.framework.localcircuit.LocalCircuitImpl;
+import org.apache.qpid.test.framework.localcircuit.LocalPublisherImpl;
+import org.apache.qpid.test.framework.localcircuit.LocalReceiverImpl;
+import org.apache.qpid.test.framework.sequencers.CircuitFactory;
+import org.apache.qpid.test.utils.ConversationFactory;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+import javax.jms.*;
+
+import java.util.List;
+import java.util.Properties;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * LocalCircuitFactory is a circuit factory that creates test circuits with publishing and receiving ends rooted
+ * on the same JVM. The ends of the circuit are presented as {@link Publisher} and {@link Receiver} interfaces, which
+ * in turn provide methods to apply assertions to the circuit. The creation of the circuit ends, and the presentation
+ * of the ends as publisher/receiver interfaces, are designed to be overriden, so that circuits and assertions that
+ * use messaging features not available in JMS can be written. This provides an extension point for writing tests
+ * against proprietary features of JMS implementations.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Provide a standard test procedure over a test circuit.
+ * <tr><td> Construct test circuits appropriate to a tests context.
+ * </table>
+ */
+public class LocalCircuitFactory implements CircuitFactory
+{
+ /** Used for debugging. */
+ private static final Logger log = Logger.getLogger(LocalCircuitFactory.class);
+
+ /** Used to create unique destination names for each test. */
+ protected static AtomicLong uniqueDestsId = new AtomicLong();
+
+ /**
+ * Holds a test coordinating conversation with the test clients. This should consist of assigning the test roles,
+ * begining the test and gathering the test reports from the participants.
+ *
+ * @param testCircuit The test circuit.
+ * @param assertions The list of assertions to apply to the test circuit.
+ * @param testProperties The test case definition.
+ */
+ public void sequenceTest(Circuit testCircuit, List<Assertion> assertions, Properties testProperties)
+ {
+ if (testCircuit != null)
+ {
+ FrameworkBaseCase.assertNoFailures(testCircuit.test(1, assertions));
+ }
+ }
+
+ /**
+ * Creates a test circuit for the test, configered by the test parameters specified.
+ *
+ * @param testProperties The test parameters.
+ *
+ * @return A test circuit.
+ */
+ public Circuit createCircuit(Connection connection, ParsedProperties testProperties)
+ {
+ Circuit result;
+
+ // Cast the test properties into a typed interface for convenience.
+ MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProperties);
+
+ // Create a standard publisher/receivers test client pair on a shared connection, individual sessions.
+ try
+ {
+ // Get a unique offset to append to destination names to make them unique to the connection.
+ long uniqueId = uniqueDestsId.incrementAndGet();
+
+ // Add the connection exception listener to assert on exception conditions with.
+ // ExceptionMonitor exceptionMonitor = new ExceptionMonitor();
+ // connection.setExceptionListener(exceptionMonitor);
+
+ // Set up the publisher.
+ CircuitEndBase publisherEnd = createPublisherCircuitEnd(connection, props, uniqueId);
+
+ // Set up the receiver.
+ CircuitEndBase receiverEnd = createReceiverCircuitEnd(connection, props, uniqueId);
+
+ // Start listening for incoming messages.
+ connection.start();
+
+ // Package everything up.
+ LocalPublisherImpl publisher = createPublisherFromCircuitEnd(publisherEnd);
+ LocalReceiverImpl receiver = createReceiverFromCircuitEnd(receiverEnd);
+
+ result = new LocalCircuitImpl(testProperties, publisher, receiver, connection, publisher.getExceptionMonitor());
+ }
+ catch (JMSException e)
+ {
+ throw new RuntimeException("Could not create publisher/receivers pair due to a JMSException.", e);
+ }
+
+ return result;
+ }
+
+ /**
+ * Creates a local {@link Receiver} from a {@link CircuitEnd}. Sub-classes may override this to provide more
+ * specialized receivers if necessary.
+ *
+ * @param receiverEnd The receiving circuit end.
+ *
+ * @return A {@link Receiver}.
+ */
+ protected LocalReceiverImpl createReceiverFromCircuitEnd(CircuitEndBase receiverEnd)
+ {
+ return new LocalReceiverImpl(receiverEnd);
+ }
+
+ /**
+ * Creates a local {@link Publisher} from a {@link CircuitEnd}. Sub-classes may override this to provide more
+ * specialized receivers if necessary.
+ *
+ * @param publisherEnd The publishing circuit end.
+ *
+ * @return A {@link Receiver}.
+ */
+ protected LocalPublisherImpl createPublisherFromCircuitEnd(CircuitEndBase publisherEnd)
+ {
+ return new LocalPublisherImpl(publisherEnd);
+ }
+
+ /**
+ * Builds a circuit end suitable for the publishing side of a test circuit, from standard test parameters.
+ *
+ * @param connection The connection to build the circuit end on.
+ * @param testProps The test parameters to configure the circuit end construction.
+ * @param uniqueId A unique number to being numbering destinations from, to make this circuit unique.
+ *
+ * @return A circuit end suitable for the publishing side of a test circuit.
+ *
+ * @throws JMSException Any underlying JMSExceptions are allowed to fall through and fail the creation.
+ */
+ public CircuitEndBase createPublisherCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId)
+ throws JMSException
+ {
+ log.debug(
+ "public CircuitEndBase createPublisherCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId = "
+ + uniqueId + "): called");
+
+ // Cast the test properties into a typed interface for convenience.
+ MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps);
+
+ // Check that the test properties do not contain AMQP/Qpid specific settings, and fail if they do.
+ if (props.getImmediate() || props.getMandatory())
+ {
+ throw new RuntimeException(
+ "Cannot create a pure JMS circuit as the test properties require AMQP specific options.");
+ }
+
+ Session session = connection.createSession(props.getPublisherTransacted(), props.getAckMode());
+
+ Destination destination =
+ props.getPubsub() ? session.createTopic(props.getSendDestinationNameRoot() + "_" + uniqueId)
+ : session.createQueue(props.getSendDestinationNameRoot() + "_" + uniqueId);
+
+ MessageProducer producer = props.getPublisherProducerBind() ? session.createProducer(destination) : null;
+
+ MessageConsumer consumer =
+ props.getPublisherConsumerBind()
+ ? session.createConsumer(session.createQueue(props.getReceiveDestinationNameRoot() + "_" + uniqueId)) : null;
+
+ MessageMonitor messageMonitor = new MessageMonitor();
+
+ if (consumer != null)
+ {
+ consumer.setMessageListener(messageMonitor);
+ }
+
+ ExceptionMonitor exceptionMonitor = new ExceptionMonitor();
+ connection.setExceptionListener(exceptionMonitor);
+
+ if (!props.getPublisherConsumerActive() && (consumer != null))
+ {
+ consumer.close();
+ }
+
+ return new CircuitEndBase(producer, consumer, session, messageMonitor, exceptionMonitor);
+ }
+
+ /**
+ * Builds a circuit end suitable for the receiving side of a test circuit, from standard test parameters.
+ *
+ * @param connection The connection to build the circuit end on.
+ * @param testProps The test parameters to configure the circuit end construction.
+ * @param uniqueId A unique number to being numbering destinations from, to make this circuit unique.
+ *
+ * @return A circuit end suitable for the receiving side of a test circuit.
+ *
+ * @throws JMSException Any underlying JMSExceptions are allowed to fall through and fail the creation.
+ */
+ public CircuitEndBase createReceiverCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId)
+ throws JMSException
+ {
+ log.debug(
+ "public CircuitEndBase createReceiverCircuitEnd(Connection connection, ParsedProperties testProps, long uniqueId = "
+ + uniqueId + "): called");
+
+ // Cast the test properties into a typed interface for convenience.
+ MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps);
+
+ // Check that the test properties do not contain AMQP/Qpid specific settings, and fail if they do.
+ if (props.getImmediate() || props.getMandatory())
+ {
+ throw new RuntimeException(
+ "Cannot create a pure JMS circuit as the test properties require AMQP specific options.");
+ }
+
+ Session session = connection.createSession(props.getPublisherTransacted(), props.getAckMode());
+
+ MessageProducer producer =
+ props.getReceiverProducerBind()
+ ? session.createProducer(session.createQueue(props.getReceiveDestinationNameRoot() + "_" + uniqueId)) : null;
+
+ Destination destination =
+ props.getPubsub() ? session.createTopic(props.getSendDestinationNameRoot() + "_" + uniqueId)
+ : session.createQueue(props.getSendDestinationNameRoot() + "_" + uniqueId);
+
+ MessageConsumer consumer =
+ props.getReceiverConsumerBind()
+ ? ((props.getDurableSubscription() && props.getPubsub())
+ ? session.createDurableSubscriber((Topic) destination, "testsub") : session.createConsumer(destination))
+ : null;
+
+ MessageMonitor messageMonitor = new MessageMonitor();
+
+ if (consumer != null)
+ {
+ consumer.setMessageListener(messageMonitor);
+ }
+
+ if (!props.getReceiverConsumerActive() && (consumer != null))
+ {
+ consumer.close();
+ }
+
+ return new CircuitEndBase(producer, consumer, session, messageMonitor, null);
+ }
+
+ /**
+ * Sets the sender test client to coordinate the test with.
+ *
+ * @param sender The contact details of the sending client in the test.
+ */
+ public void setSender(TestClientDetails sender)
+ {
+ throw new RuntimeException("Not implemented.");
+ }
+
+ /**
+ * Sets the receiving test client to coordinate the test with.
+ *
+ * @param receiver The contact details of the sending client in the test.
+ */
+ public void setReceiver(TestClientDetails receiver)
+ {
+ throw new RuntimeException("Not implemented.");
+ }
+
+ /**
+ * Supplies the sending test client.
+ *
+ * @return The sending test client.
+ */
+ public TestClientDetails getSender()
+ {
+ throw new RuntimeException("Not implemented.");
+ }
+
+ /**
+ * Supplies the receiving test client.
+ *
+ * @return The receiving test client.
+ */
+ public List<TestClientDetails> getReceivers()
+ {
+ throw new RuntimeException("Not implemented.");
+ }
+
+ /**
+ * Accepts the conversation factory over which to hold the test coordinating conversation.
+ *
+ * @param conversationFactory The conversation factory to coordinate the test over.
+ */
+ public void setConversationFactory(ConversationFactory conversationFactory)
+ {
+ throw new RuntimeException("Not implemented.");
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/MessageIdentityVector.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/MessageIdentityVector.java
new file mode 100644
index 0000000000..397c4e9fbd
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/MessageIdentityVector.java
@@ -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.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+/**
+ * MessageIdentityVector provides a message identification scheme, that matches individual messages with test cases.
+ * Test messages are being sent by a number of test clients, sending messages over a set of routes, and being received
+ * by another set of test clients. Each test is itself, being run within a test cycle, of which there could be many. It
+ * is the job of the test coordinator to request and receive reports from the available test clients, on what has been
+ * sent, what has been received, and what errors may have occurred, and to reconcile this information against the
+ * assertions being applied by the test case. In order to be able to figure out which messages belong to which test,
+ * there needs to be an identification scheme, that the coordinator can use to correlate messages in senders and
+ * receiver reports. Every message sent in a test can be associated with this information.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Identify a test case, a handling client id, a circuit end within the client, and a test cycle number.
+ * </table>
+ */
+public class MessageIdentityVector
+{
+ /** Holds the test case vector component of the message identity vector. */
+ private TestCaseVector testCaseVector;
+
+ /** The unique client id. */
+ private String clientId;
+
+ /** The unique circuit end number within the client id. */
+ private int circuitEndId;
+
+ /**
+ * Creates a new identity vector for test messages.
+ *
+ * @param testCase The name of the test case generating the messages.
+ * @param clientId The unique id of the client implementing a circuit end that is handling the messages.
+ * @param circuitEndId The unique id number of the circuit end within the client.
+ * @param testCycleNumber The cycle iteration number of the test case.
+ */
+ public MessageIdentityVector(String testCase, String clientId, int circuitEndId, int testCycleNumber)
+ {
+ this.testCaseVector = new TestCaseVector(testCase, testCycleNumber);
+ this.clientId = clientId;
+ this.circuitEndId = circuitEndId;
+ }
+
+ /**
+ * Reports the test case vector component of the message identity vector.
+ *
+ * @return The test case vector component of the message identity vector.
+ */
+ public TestCaseVector getTestCaseVector()
+ {
+ return testCaseVector;
+ }
+
+ /**
+ * Reports the name of the test case.
+ *
+ * @return The name of the test case.
+ */
+ public String getTestCase()
+ {
+ return testCaseVector.getTestCase();
+ }
+
+ /**
+ * Reports the test iteration cycle number within the test case.
+ *
+ * @return The test iteration cycle number within the test case.
+ */
+ public int getTestCycleNumber()
+ {
+ return testCaseVector.getTestCycleNumber();
+ }
+
+ /**
+ * Resports the client id.
+ *
+ * @return The client id.
+ */
+ public String getClientId()
+ {
+ return clientId;
+ }
+
+ /**
+ * Reports the circuit end number within the test client.
+ *
+ * @return The circuit end number within the test client.
+ */
+ public int getCircuitEndId()
+ {
+ return circuitEndId;
+ }
+
+ /**
+ * Compares this identity vector with another for equality. All fields must match.
+ *
+ * @param o The identity vector to compare with.
+ *
+ * @return <tt>true</tt> if the identity vector is identical to this one by all fields, <tt>false</tt> otherwise.
+ */
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+
+ if ((o == null) || (getClass() != o.getClass()))
+ {
+ return false;
+ }
+
+ MessageIdentityVector that = (MessageIdentityVector) o;
+
+ if (circuitEndId != that.circuitEndId)
+ {
+ return false;
+ }
+
+ if ((clientId != null) ? (!clientId.equals(that.clientId)) : (that.clientId != null))
+ {
+ return false;
+ }
+
+ if ((testCaseVector != null) ? (!testCaseVector.equals(that.testCaseVector)) : (that.testCaseVector != null))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Computes a hash code for this identity vector based on all fields.
+ *
+ * @return A hash code for this identity vector based on all fields.
+ */
+ public int hashCode()
+ {
+ int result;
+ result = ((testCaseVector != null) ? testCaseVector.hashCode() : 0);
+ result = (31 * result) + ((clientId != null) ? clientId.hashCode() : 0);
+ result = (31 * result) + circuitEndId;
+
+ return result;
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/MessageMonitor.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/MessageMonitor.java
new file mode 100644
index 0000000000..3fac969369
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/MessageMonitor.java
@@ -0,0 +1,105 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+import org.apache.log4j.Logger;
+
+import javax.jms.Message;
+import javax.jms.MessageListener;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * MessageMonitor is used to record information about messages received. This will provide methods to check various
+ * properties, such as the type, number and content of messages received in order to verify the correct behaviour of
+ * tests.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Count incoming messages.
+ * <tr><td> Record time ellapsed since the arrival of the first message.
+ * <tr><td> Reset all counts and timings.
+ * </table>
+ */
+public class MessageMonitor implements MessageListener
+{
+ /** Used for debugging. */
+ private final Logger log = Logger.getLogger(MessageMonitor.class);
+
+ /** Holds the count of messages received since the last query. */
+ protected AtomicInteger numMessages = new AtomicInteger();
+
+ /** Holds the time of arrival of the first message. */
+ protected Long firstMessageTime = null;
+
+ /**
+ * Handles received messages. Does Nothing.
+ *
+ * @param message The message. Ignored.
+ */
+ public void onMessage(Message message)
+ {
+ // log.debug("public void onMessage(Message message): called");
+
+ numMessages.getAndIncrement();
+ }
+
+ /**
+ * Gets the count of messages.
+ *
+ * @return The count of messages.
+ */
+ public int getNumMessage()
+ {
+ if (firstMessageTime == null)
+ {
+ firstMessageTime = System.nanoTime();
+ }
+
+ return numMessages.get();
+ }
+
+ /**
+ * Gets the time elapsed since the first message arrived, in nanos, or zero if no messages have arrived yet.
+ *
+ * @return The time elapsed since the first message arrived, in nanos, or zero if no messages have arrived yet.
+ */
+ public long getTime()
+ {
+ if (firstMessageTime != null)
+ {
+ return System.nanoTime() - firstMessageTime;
+ }
+ else
+ {
+ return 0L;
+ }
+ }
+
+ /**
+ * Resets the message count and timer to zero.
+ */
+ public void reset()
+ {
+ numMessages.set(0);
+ firstMessageTime = null;
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/MessagingTestConfigProperties.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/MessagingTestConfigProperties.java
new file mode 100644
index 0000000000..6d72402018
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/MessagingTestConfigProperties.java
@@ -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.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+import javax.jms.Session;
+
+import java.util.Properties;
+
+/**
+ * MessagingTestConfigProperties defines a set of property names and default values for specifying a messaging topology,
+ * and test parameters for running a messaging test over that topology. A Properties object holding some of these
+ * properties, superimposed onto the defaults, is used to establish test topologies and control test behaviour.
+ *
+ * <p/>A complete list of the parameters, default values and comments on their usage is provided here:
+ *
+ * <p/><table><caption>Parameters</caption>
+ * <tr><th> Parameter <th> Default <th> Comments
+ * <tr><td> messageSize <td> 0 <td> Message size in bytes. Not including any headers.
+ * <tr><td> destinationName <td> ping <td> The root name to use to generate destination names to ping.
+ * <tr><td> persistent <td> false <td> Determines whether peristent delivery is used.
+ * <tr><td> transacted <td> false <td> Determines whether messages are sent/received in transactions.
+ * <tr><td> broker <td> tcp://localhost:5672 <td> Determines the broker to connect to.
+ * <tr><td> virtualHost <td> test <td> Determines the virtual host to send all ping over.
+ * <tr><td> rate <td> 0 <td> The maximum rate (in hertz) to send messages at. 0 means no limit.
+ * <tr><td> verbose <td> false <td> The verbose flag for debugging. Prints to console on every message.
+ * <tr><td> pubsub <td> false <td> Whether to ping topics or queues. Uses p2p by default.
+ * <tr><td> username <td> guest <td> The username to access the broker with.
+ * <tr><td> password <td> guest <td> The password to access the broker with.
+ * <tr><td> selector <td> null <td> Not used. Defines a message selector to filter pings with.
+ * <tr><td> destinationCount <td> 1 <td> The number of receivers listening to the pings.
+ * <tr><td> timeout <td> 30000 <td> In milliseconds. The timeout to stop waiting for replies.
+ * <tr><td> commitBatchSize <td> 1 <td> The number of messages per transaction in transactional mode.
+ * <tr><td> uniqueDests <td> true <td> Whether each receivers only listens to one ping destination or all.
+ * <tr><td> durableDests <td> false <td> Whether or not durable destinations are used.
+ * <tr><td> ackMode <td> AUTO_ACK <td> The message acknowledgement mode. Possible values are:
+ * 0 - SESSION_TRANSACTED
+ * 1 - AUTO_ACKNOWLEDGE
+ * 2 - CLIENT_ACKNOWLEDGE
+ * 3 - DUPS_OK_ACKNOWLEDGE
+ * 257 - NO_ACKNOWLEDGE
+ * 258 - PRE_ACKNOWLEDGE
+ * <tr><td> maxPending <td> 0 <td> The maximum size in bytes, of messages sent but not yet received.
+ * Limits the volume of messages currently buffered on the client
+ * or broker. Can help scale test clients by limiting amount of buffered
+ * data to avoid out of memory errors.
+ * </table>
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Provide the names and defaults of all test parameters.
+ * </table>
+ *
+ * @todo Put a type-safe wrapper around these properties, but continue to store the parameters as properties. This is
+ * simply to ensure that it is a simple matter to serialize/deserialize string/string pairs onto messages.
+ */
+public class MessagingTestConfigProperties extends ParsedProperties
+{
+ // ====================== Connection Properties ==================================
+
+ /** Holds the name of the default connection configuration. */
+ public static final String CONNECTION_NAME = "broker";
+
+ /** Holds the name of the property to get the initial context factory name from. */
+ public static final String INITIAL_CONTEXT_FACTORY_PROPNAME = "java.naming.factory.initial";
+
+ /** Defines the class to use as the initial context factory by default. */
+ public static final String INITIAL_CONTEXT_FACTORY_DEFAULT = "org.apache.qpid.jndi.PropertiesFileInitialContextFactory";
+
+ /** Holds the name of the property to get the test broker url from. */
+ public static final String BROKER_PROPNAME = "qpid.test.broker";
+
+ /** Holds the default broker url for the test. */
+ public static final String BROKER_DEFAULT = "vm://:1";
+
+ /** Holds the name of the property to get the test broker virtual path. */
+ public static final String VIRTUAL_HOST_PROPNAME = "virtualHost";
+
+ /** Holds the default virtual path for the test. */
+ public static final String VIRTUAL_HOST_DEFAULT = "test";
+
+ /** Holds the name of the property to get the broker access username from. */
+ public static final String USERNAME_PROPNAME = "username";
+
+ /** Holds the default broker log on username. */
+ public static final String USERNAME_DEFAULT = "guest";
+
+ /** Holds the name of the property to get the broker access password from. */
+ public static final String PASSWORD_PROPNAME = "password";
+
+ /** Holds the default broker log on password. */
+ public static final String PASSWORD_DEFAULT = "guest";
+
+ // ====================== Messaging Topology Properties ==========================
+
+ /** Holds the name of the property to get the bind publisher procuder flag from. */
+ public static final String PUBLISHER_PRODUCER_BIND_PROPNAME = "publisherProducerBind";
+
+ /** Holds the default value of the publisher producer flag. */
+ public static final boolean PUBLISHER_PRODUCER_BIND_DEFAULT = true;
+
+ /** Holds the name of the property to get the bind publisher procuder flag from. */
+ public static final String PUBLISHER_CONSUMER_BIND_PROPNAME = "publisherConsumerBind";
+
+ /** Holds the default value of the publisher consumer flag. */
+ public static final boolean PUBLISHER_CONSUMER_BIND_DEFAULT = false;
+
+ /** Holds the name of the property to get the bind receivers procuder flag from. */
+ public static final String RECEIVER_PRODUCER_BIND_PROPNAME = "receiverProducerBind";
+
+ /** Holds the default value of the receivers producer flag. */
+ public static final boolean RECEIVER_PRODUCER_BIND_DEFAULT = false;
+
+ /** Holds the name of the property to get the bind receivers procuder flag from. */
+ public static final String RECEIVER_CONSUMER_BIND_PROPNAME = "receiverConsumerBind";
+
+ /** Holds the default value of the receivers consumer flag. */
+ public static final boolean RECEIVER_CONSUMER_BIND_DEFAULT = true;
+
+ /** Holds the name of the property to get the publishers consumer active flag from. */
+ public static final String PUBLISHER_CONSUMER_ACTIVE_PROPNAME = "publisherConsumerActive";
+
+ /** Holds the default value of the publishers consumer active flag. */
+ public static final boolean PUBLISHER_CONSUMER_ACTIVE_DEFAULT = true;
+
+ /** Holds the name of the property to get the receivers consumer active flag from. */
+ public static final String RECEIVER_CONSUMER_ACTIVE_PROPNAME = "receiverConsumerActive";
+
+ /** Holds the default value of the receivers consumer active flag. */
+ public static final boolean RECEIVER_CONSUMER_ACTIVE_DEFAULT = true;
+
+ /** Holds the name of the property to get the destination name root from. */
+ public static final String SEND_DESTINATION_NAME_ROOT_PROPNAME = "sendDestinationRoot";
+
+ /** Holds the root of the name of the default destination to send to. */
+ public static final String SEND_DESTINATION_NAME_ROOT_DEFAULT = "sendTo";
+
+ /** Holds the name of the property to get the destination name root from. */
+ public static final String RECEIVE_DESTINATION_NAME_ROOT_PROPNAME = "receiveDestinationRoot";
+
+ /** Holds the root of the name of the default destination to send to. */
+ public static final String RECEIVE_DESTINATION_NAME_ROOT_DEFAULT = "receiveFrom";
+
+ /** Holds the name of the proeprty to get the destination count from. */
+ public static final String DESTINATION_COUNT_PROPNAME = "destinationCount";
+
+ /** Defines the default number of destinations to ping. */
+ public static final int DESTINATION_COUNT_DEFAULT = 1;
+
+ /** Holds the name of the property to get the p2p or pub/sub messaging mode from. */
+ public static final String PUBSUB_PROPNAME = "pubsub";
+
+ /** Holds the pub/sub mode default, true means ping a topic, false means ping a queue. */
+ public static final boolean PUBSUB_DEFAULT = false;
+
+ // ====================== JMS Options and Flags =================================
+
+ /** Holds the name of the property to get the test delivery mode from. */
+ public static final String PERSISTENT_MODE_PROPNAME = "persistent";
+
+ /** Holds the message delivery mode to use for the test. */
+ public static final boolean PERSISTENT_MODE_DEFAULT = false;
+
+ /** Holds the name of the property to get the test transactional mode from. */
+ public static final String TRANSACTED_PUBLISHER_PROPNAME = "transactedPublisher";
+
+ /** Holds the transactional mode to use for the test. */
+ public static final boolean TRANSACTED_PUBLISHER_DEFAULT = false;
+
+ /** Holds the name of the property to get the test transactional mode from. */
+ public static final String TRANSACTED_RECEIVER_PROPNAME = "transactedReceiver";
+
+ /** Holds the transactional mode to use for the test. */
+ public static final boolean TRANSACTED_RECEIVER_DEFAULT = false;
+
+ /** Holds the name of the property to set the no local flag from. */
+ public static final String NO_LOCAL_PROPNAME = "noLocal";
+
+ /** Defines the default value of the no local flag to use when consuming messages. */
+ public static final boolean NO_LOCAL_DEFAULT = false;
+
+ /** Holds the name of the property to get the message acknowledgement mode from. */
+ public static final String ACK_MODE_PROPNAME = "ackMode";
+
+ /** Defines the default message acknowledgement mode. */
+ public static final int ACK_MODE_DEFAULT = Session.AUTO_ACKNOWLEDGE;
+
+ /** Holds the name of the property to get the durable subscriptions flag from, when doing pub/sub messaging. */
+ public static final String DURABLE_SUBSCRIPTION_PROPNAME = "durableSubscription";
+
+ /** Defines the default value of the durable subscriptions flag. */
+ public static final boolean DURABLE_SUBSCRIPTION_DEFAULT = false;
+
+ // ====================== Qpid/AMQP Options and Flags ================================
+
+ /** Holds the name of the property to set the exclusive flag from. */
+ public static final String EXCLUSIVE_PROPNAME = "exclusive";
+
+ /** Defines the default value of the exclusive flag to use when consuming messages. */
+ public static final boolean EXCLUSIVE_DEFAULT = false;
+
+ /** Holds the name of the property to set the immediate flag from. */
+ public static final String IMMEDIATE_PROPNAME = "immediate";
+
+ /** Defines the default value of the immediate flag to use when sending messages. */
+ public static final boolean IMMEDIATE_DEFAULT = false;
+
+ /** Holds the name of the property to set the mandatory flag from. */
+ public static final String MANDATORY_PROPNAME = "mandatory";
+
+ /** Defines the default value of the mandatory flag to use when sending messages. */
+ public static final boolean MANDATORY_DEFAULT = false;
+
+ /** Holds the name of the property to get the durable destinations flag from. */
+ public static final String DURABLE_DESTS_PROPNAME = "durableDests";
+
+ /** Default value for the durable destinations flag. */
+ public static final boolean DURABLE_DESTS_DEFAULT = false;
+
+ /** Holds the name of the property to set the prefetch size from. */
+ public static final String PREFETCH_PROPNAME = "prefetch";
+
+ /** Defines the default prefetch size to use when consuming messages. */
+ public static final int PREFETCH_DEFAULT = 100;
+
+ // ====================== Common Test Parameters ================================
+
+ /** Holds the name of the property to get the test message size from. */
+ public static final String MESSAGE_SIZE_PROPNAME = "messageSize";
+
+ /** Used to set up a default message size. */
+ public static final int MESSAGE_SIZE_DEAFULT = 0;
+
+ /** Holds the name of the property to get the message rate from. */
+ public static final String RATE_PROPNAME = "rate";
+
+ /** Defines the default rate (in pings per second) to send pings at. 0 means as fast as possible, no restriction. */
+ public static final int RATE_DEFAULT = 0;
+
+ /** Holds the name of the proeprty to get the. */
+ public static final String SELECTOR_PROPNAME = "selector";
+
+ /** Holds the default message selector. */
+ public static final String SELECTOR_DEFAULT = "";
+
+ /** Holds the name of the property to get the waiting timeout for response messages. */
+ public static final String TIMEOUT_PROPNAME = "timeout";
+
+ /** Default time to wait before assuming that a ping has timed out. */
+ public static final long TIMEOUT_DEFAULT = 30000;
+
+ /** Holds the name of the property to get the commit batch size from. */
+ public static final String TX_BATCH_SIZE_PROPNAME = "commitBatchSize";
+
+ /** Defines the default number of pings to send in each transaction when running transactionally. */
+ public static final int TX_BATCH_SIZE_DEFAULT = 1;
+
+ /** Holds the name of the property to set the maximum amount of pending message data for a producer to hold. */
+ public static final String MAX_PENDING_PROPNAME = "maxPending";
+
+ /** Defines the default maximum quantity of pending message data to allow producers to hold. */
+ public static final int MAX_PENDING_DEFAULT = 0;
+
+ /** Holds the name of the property to get the publisher rollback flag from. */
+ public static final String ROLLBACK_PUBLISHER_PROPNAME = "rollbackPublisher";
+
+ /** Holds the default publisher roll back setting. */
+ public static final boolean ROLLBACK_PUBLISHER_DEFAULT = false;
+
+ /** Holds the name of the property to get the publisher rollback flag from. */
+ public static final String ROLLBACK_RECEIVER_PROPNAME = "rollbackReceiver";
+
+ /** Holds the default publisher roll back setting. */
+ public static final boolean ROLLBACK_RECEIVER_DEFAULT = false;
+
+ // ====================== Options that control the bahviour of the test framework. =========================
+
+ /** Holds the name of the property to get the behavioural mode of not applicable assertions. */
+ public static final String NOT_APPLICABLE_ASSERTION_PROPNAME = "notApplicableAssertion";
+
+ /** Holds the default behavioral mode of not applicable assertions, which is logging them as a warning. */
+ public static final String NOT_APPLICABLE_ASSERTION_DEFAULT = "warn";
+
+ /** Holds the name of the property to get the verbose mode proeprty from. */
+ public static final String VERBOSE_PROPNAME = "verbose";
+
+ /** Holds the default verbose mode. */
+ public static final boolean VERBOSE_DEFAULT = false;
+
+ /** Holds the default configuration properties. */
+ public static ParsedProperties defaults = new ParsedProperties();
+
+ static
+ {
+ defaults.setPropertyIfNull(INITIAL_CONTEXT_FACTORY_PROPNAME, INITIAL_CONTEXT_FACTORY_DEFAULT);
+ defaults.setPropertyIfNull(BROKER_PROPNAME, BROKER_DEFAULT);
+ defaults.setPropertyIfNull(VIRTUAL_HOST_PROPNAME, VIRTUAL_HOST_DEFAULT);
+ defaults.setPropertyIfNull(USERNAME_PROPNAME, USERNAME_DEFAULT);
+ defaults.setPropertyIfNull(PASSWORD_PROPNAME, PASSWORD_DEFAULT);
+
+ defaults.setPropertyIfNull(PUBLISHER_PRODUCER_BIND_PROPNAME, PUBLISHER_PRODUCER_BIND_DEFAULT);
+ defaults.setPropertyIfNull(PUBLISHER_CONSUMER_BIND_PROPNAME, PUBLISHER_CONSUMER_BIND_DEFAULT);
+ defaults.setPropertyIfNull(RECEIVER_PRODUCER_BIND_PROPNAME, RECEIVER_PRODUCER_BIND_DEFAULT);
+ defaults.setPropertyIfNull(RECEIVER_CONSUMER_BIND_PROPNAME, RECEIVER_CONSUMER_BIND_DEFAULT);
+ defaults.setPropertyIfNull(PUBLISHER_CONSUMER_ACTIVE_PROPNAME, PUBLISHER_CONSUMER_ACTIVE_DEFAULT);
+ defaults.setPropertyIfNull(RECEIVER_CONSUMER_ACTIVE_PROPNAME, RECEIVER_CONSUMER_ACTIVE_DEFAULT);
+ defaults.setPropertyIfNull(SEND_DESTINATION_NAME_ROOT_PROPNAME, SEND_DESTINATION_NAME_ROOT_DEFAULT);
+ defaults.setPropertyIfNull(RECEIVE_DESTINATION_NAME_ROOT_PROPNAME, RECEIVE_DESTINATION_NAME_ROOT_DEFAULT);
+ defaults.setPropertyIfNull(DESTINATION_COUNT_PROPNAME, DESTINATION_COUNT_DEFAULT);
+ defaults.setPropertyIfNull(PUBSUB_PROPNAME, PUBSUB_DEFAULT);
+
+ defaults.setPropertyIfNull(PERSISTENT_MODE_PROPNAME, PERSISTENT_MODE_DEFAULT);
+ defaults.setPropertyIfNull(TRANSACTED_PUBLISHER_PROPNAME, TRANSACTED_PUBLISHER_DEFAULT);
+ defaults.setPropertyIfNull(TRANSACTED_RECEIVER_PROPNAME, TRANSACTED_RECEIVER_DEFAULT);
+ defaults.setPropertyIfNull(NO_LOCAL_PROPNAME, NO_LOCAL_DEFAULT);
+ defaults.setPropertyIfNull(ACK_MODE_PROPNAME, ACK_MODE_DEFAULT);
+ defaults.setPropertyIfNull(DURABLE_SUBSCRIPTION_PROPNAME, DURABLE_SUBSCRIPTION_DEFAULT);
+
+ defaults.setPropertyIfNull(EXCLUSIVE_PROPNAME, EXCLUSIVE_DEFAULT);
+ defaults.setPropertyIfNull(IMMEDIATE_PROPNAME, IMMEDIATE_DEFAULT);
+ defaults.setPropertyIfNull(MANDATORY_PROPNAME, MANDATORY_DEFAULT);
+ defaults.setPropertyIfNull(DURABLE_DESTS_PROPNAME, DURABLE_DESTS_DEFAULT);
+ defaults.setPropertyIfNull(PREFETCH_PROPNAME, PREFETCH_DEFAULT);
+
+ defaults.setPropertyIfNull(MESSAGE_SIZE_PROPNAME, MESSAGE_SIZE_DEAFULT);
+ defaults.setPropertyIfNull(RATE_PROPNAME, RATE_DEFAULT);
+ defaults.setPropertyIfNull(SELECTOR_PROPNAME, SELECTOR_DEFAULT);
+ defaults.setPropertyIfNull(TIMEOUT_PROPNAME, TIMEOUT_DEFAULT);
+ defaults.setPropertyIfNull(TX_BATCH_SIZE_PROPNAME, TX_BATCH_SIZE_DEFAULT);
+ defaults.setPropertyIfNull(MAX_PENDING_PROPNAME, MAX_PENDING_DEFAULT);
+ defaults.setPropertyIfNull(ROLLBACK_PUBLISHER_PROPNAME, ROLLBACK_PUBLISHER_DEFAULT);
+ defaults.setPropertyIfNull(ROLLBACK_RECEIVER_PROPNAME, ROLLBACK_RECEIVER_DEFAULT);
+
+ defaults.setPropertyIfNull(NOT_APPLICABLE_ASSERTION_PROPNAME, NOT_APPLICABLE_ASSERTION_DEFAULT);
+ defaults.setPropertyIfNull(VERBOSE_PROPNAME, VERBOSE_DEFAULT);
+ }
+
+ /**
+ * Creates a test configuration based on the defaults.
+ */
+ public MessagingTestConfigProperties()
+ {
+ super(defaults);
+ }
+
+ /**
+ * Creates a test configuration based on the supplied properties.
+ *
+ * @param properties The test configuration.
+ */
+ public MessagingTestConfigProperties(Properties properties)
+ {
+ super(properties);
+ }
+
+ /**
+ * The size of test messages to send.
+ *
+ * @return The size of test messages to send.
+ */
+ public int getMessageSize()
+ {
+ return getPropertyAsInteger(MESSAGE_SIZE_PROPNAME);
+ }
+
+ /**
+ * Flag to indicate that the publishing producer should be set up to publish to a destination.
+ *
+ * @return Flag to indicate that the publishing producer should be set up to publish to a destination.
+ */
+ public boolean getPublisherProducerBind()
+ {
+ return getPropertyAsBoolean(PUBLISHER_PRODUCER_BIND_PROPNAME);
+ }
+
+ /**
+ * Flag to indicate that the publishing consumer should be set up to receive from a destination.
+ *
+ * @return Flag to indicate that the publishing consumer should be set up to receive from a destination.
+ */
+ public boolean getPublisherConsumerBind()
+ {
+ return getPropertyAsBoolean(PUBLISHER_CONSUMER_BIND_PROPNAME);
+ }
+
+ /**
+ * Flag to indicate that the receiving producer should be set up to publish to a destination.
+ *
+ * @return Flag to indicate that the receiving producer should be set up to publish to a destination.
+ */
+ public boolean getReceiverProducerBind()
+ {
+ return getPropertyAsBoolean(RECEIVER_PRODUCER_BIND_PROPNAME);
+ }
+
+ /**
+ * Flag to indicate that the receiving consumer should be set up to receive from a destination.
+ *
+ * @return Flag to indicate that the receiving consumer should be set up to receive from a destination.
+ */
+ public boolean getReceiverConsumerBind()
+ {
+ return getPropertyAsBoolean(RECEIVER_CONSUMER_BIND_PROPNAME);
+ }
+
+ /**
+ * Flag to indicate that the publishing consumer should be created and actively listening.
+ *
+ * @return Flag to indicate that the publishing consumer should be created.
+ */
+ public boolean getPublisherConsumerActive()
+ {
+ return getPropertyAsBoolean(PUBLISHER_CONSUMER_ACTIVE_PROPNAME);
+ }
+
+ /**
+ * Flag to indicate that the receiving consumers should be created and actively listening.
+ *
+ * @return Flag to indicate that the receiving consumers should be created and actively listening.
+ */
+ public boolean getReceiverConsumerActive()
+ {
+ return getPropertyAsBoolean(RECEIVER_CONSUMER_ACTIVE_PROPNAME);
+ }
+
+ /**
+ * A root to create all test destination names from.
+ *
+ * @return A root to create all test destination names from.
+ */
+ public String getSendDestinationNameRoot()
+ {
+ return getProperty(SEND_DESTINATION_NAME_ROOT_PROPNAME);
+ }
+
+ /**
+ * A root to create all receiving destination names from.
+ *
+ * @return A root to create all receiving destination names from.
+ */
+ public String getReceiveDestinationNameRoot()
+ {
+ return getProperty(RECEIVE_DESTINATION_NAME_ROOT_PROPNAME);
+ }
+
+ /**
+ * Flag to indicate that persistent messages should be used.
+ *
+ * @return Flag to indicate that persistent messages should be used.
+ */
+ public boolean getPersistentMode()
+ {
+ return getPropertyAsBoolean(PERSISTENT_MODE_PROPNAME);
+ }
+
+ /**
+ * Flag to indicate that transactional messages should be sent by the publisher.
+ *
+ * @return Flag to indicate that transactional messages should be sent by the publisher.
+ */
+ public boolean getPublisherTransacted()
+ {
+ return getPropertyAsBoolean(TRANSACTED_PUBLISHER_PROPNAME);
+ }
+
+ /**
+ * Flag to indicate that transactional receives should be used by the receiver.
+ *
+ * @return Flag to indicate that transactional receives should be used by the receiver.
+ */
+ public boolean getReceiverTransacted()
+ {
+ return getPropertyAsBoolean(TRANSACTED_PUBLISHER_PROPNAME);
+ }
+
+ /**
+ * The name of the virtual host to run all tests over.
+ *
+ * @return The name of the virtual host to run all tests over.
+ */
+ public String getVirtualHost()
+ {
+ return getProperty(VIRTUAL_HOST_PROPNAME);
+ }
+
+ /**
+ * Limiting rate for each sender in messages per second, or zero for unlimited.
+ *
+ * @return Limiting rate for each sender in messages per second, or zero for unlimited.
+ */
+ public String getRate()
+ {
+ return getProperty(RATE_PROPNAME);
+ }
+
+ /**
+ * Flag to indicate that test messages should be received publish/subscribe style by all receivers.
+ *
+ * @return Flag to indicate that test messages should be received publish/subscribe style by all receivers.
+ */
+ public boolean getPubsub()
+ {
+ return getPropertyAsBoolean(PUBSUB_PROPNAME);
+ }
+
+ /**
+ * The username credentials to run tests with.
+ *
+ * @return The username credentials to run tests with.
+ */
+ public String getUsername()
+ {
+ return getProperty(USERNAME_PROPNAME);
+ }
+
+ /**
+ * The password credentials to run tests with.
+ *
+ * @return The password credentials to run tests with.
+ */
+ public String getPassword()
+ {
+ return getProperty(PASSWORD_PROPNAME);
+ }
+
+ /**
+ * The timeout duration to fail tests on, should they receive no messages within it.
+ *
+ * @return The timeout duration to fail tests on, should they receive no messages within it.
+ */
+ public long getTimeout()
+ {
+ return getPropertyAsLong(TIMEOUT_PROPNAME);
+ }
+
+ /**
+ * The number of messages to batch into each transaction in transational tests.
+ *
+ * @return The number of messages to batch into each transaction in transational tests.
+ */
+ public int getTxBatchSize()
+ {
+ return getPropertyAsInteger(TX_BATCH_SIZE_PROPNAME);
+ }
+
+ /**
+ * Flag to indicate that tests should use durable destinations.
+ *
+ * @return Flag to indicate that tests should use durable destinations.
+ */
+ public boolean getDurableDests()
+ {
+ return getPropertyAsBoolean(DURABLE_DESTS_PROPNAME);
+ }
+
+ /**
+ * The ack mode for message receivers to use.
+ *
+ * @return The ack mode for message receivers to use.
+ */
+ public int getAckMode()
+ {
+ return getPropertyAsInteger(ACK_MODE_PROPNAME);
+ }
+
+ /**
+ * Flag to indicate that tests should use durable subscriptions.
+ *
+ * @return Flag to indicate that tests should use durable subscriptions.
+ */
+ public boolean getDurableSubscription()
+ {
+ return getPropertyAsBoolean(DURABLE_SUBSCRIPTION_PROPNAME);
+ }
+
+ /**
+ * The maximum amount of in-flight data, in bytes, that tests should send at any time.
+ *
+ * @return The maximum amount of in-flight data, in bytes, that tests should send at any time.
+ */
+ public int getMaxPending()
+ {
+ return getPropertyAsInteger(MAX_PENDING_PROPNAME);
+ }
+
+ /**
+ * The size of the prefetch queue to use.
+ *
+ * @return The size of the prefetch queue to use.
+ */
+ public int getPrefetch()
+ {
+ return getPropertyAsInteger(PREFETCH_PROPNAME);
+ }
+
+ /**
+ * Flag to indicate that subscriptions should be no-local.
+ *
+ * @return Flag to indicate that subscriptions should be no-local.
+ */
+ public boolean getNoLocal()
+ {
+ return getPropertyAsBoolean(NO_LOCAL_PROPNAME);
+ }
+
+ /**
+ * Flag to indicate that subscriptions should be exclusive.
+ *
+ * @return Flag to indicate that subscriptions should be exclusive.
+ */
+ public boolean getExclusive()
+ {
+ return getPropertyAsBoolean(EXCLUSIVE_PROPNAME);
+ }
+
+ /**
+ * Flag to indicate that messages must be delivered immediately.
+ *
+ * @return Flag to indicate that messages must be delivered immediately.
+ */
+ public boolean getImmediate()
+ {
+ return getPropertyAsBoolean(IMMEDIATE_PROPNAME);
+ }
+
+ /**
+ * Flag to indicate that messages must be routable.
+ *
+ * @return Flag to indicate that messages must be routable.
+ */
+ public boolean getMandatory()
+ {
+ return getPropertyAsBoolean(MANDATORY_PROPNAME);
+ }
+
+ /**
+ * Gets the value of a flag to indicate that the publisher should rollback all messages sent.
+ *
+ * @return A flag to indicate that the publisher should rollback all messages sent.
+ */
+ public boolean getRollbackPublisher()
+ {
+ return getPropertyAsBoolean(ROLLBACK_PUBLISHER_PROPNAME);
+ }
+
+ /**
+ * Gets the value of a flag to indicate that the receiver should rollback all messages received, then receive them
+ * again.
+ *
+ * @return A flag to indicate that the publisher should rollback all messages received.
+ */
+ public boolean getRollbackReceiver()
+ {
+ return getPropertyAsBoolean(ROLLBACK_RECEIVER_PROPNAME);
+ }
+
+ /**
+ * Gets the behavioural mode of not applicable assertions. Should be one of 'quiet', 'warn' or 'fail'.
+ *
+ * @return The behavioural mode of not applicable assertions.
+ */
+ public String getNotApplicableAssertionMode()
+ {
+ return getProperty(NOT_APPLICABLE_ASSERTION_PROPNAME);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/NotApplicableAssertion.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/NotApplicableAssertion.java
new file mode 100644
index 0000000000..2a20be12d6
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/NotApplicableAssertion.java
@@ -0,0 +1,112 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+/**
+ * NotApplicableAssertion is a messaging assertion that can be used when an assertion requested by a test-case is not
+ * applicable to the testing scenario. For example an assertion may relate to AMQP functionality, but a test case may be
+ * being run over a non-AMQP JMS implementation, in which case the request to create the assertion may return this
+ * instead of the proper assertion. The test framework is configurable to quietly drop these assertions, log them
+ * as warnings to the console, or raise them as test failures.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Quitely pass.
+ * <tr><td> Log a warning.
+ * <tr><td> Raise a test failure.
+ * </table>
+ */
+public class NotApplicableAssertion implements Assertion
+{
+ /** Used for logging to the console. */
+ private static final Logger console = Logger.getLogger("CONSOLE." + NotApplicableAssertion.class.getName());
+
+ /** The possible behavioural modes of this assertion. */
+ private enum Mode
+ {
+ /** Quietly ignore the assertion by passing. */
+ Quiet,
+
+ /** Ignore the assertion by passing but log a warning about it. */
+ Warn,
+
+ /** Fail the assertion. */
+ Fail;
+ }
+
+ /** The behavioural mode of the assertion. */
+ private Mode mode;
+
+ /**
+ * Creates an assertion that is driven by the value of the 'notApplicableAssertion' property of the test
+ * configuration. Its value should match one of 'quiet', 'warn' or 'fail' and if it does not it is automatically
+ * read as 'fail'.
+ *
+ * @param testProperties The test configuration properties.
+ */
+ public NotApplicableAssertion(ParsedProperties testProperties)
+ {
+ // Cast the test properties into a typed interface for convenience.
+ MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProperties);
+
+ String modeName = props.getNotApplicableAssertionMode();
+
+ if ("quiet".equals(modeName))
+ {
+ mode = Mode.Quiet;
+ }
+ else if ("warn".equals(modeName))
+ {
+ mode = Mode.Warn;
+ }
+ else
+ {
+ mode = Mode.Fail;
+ }
+ }
+
+ /**
+ * Applies the assertion.
+ *
+ * @return <tt>true</tt> if the assertion passes, <tt>false</tt> if it fails.
+ */
+ public boolean apply()
+ {
+ switch (mode)
+ {
+ case Quiet:
+ return true;
+
+ case Warn:
+ console.warn("Warning: Not applicable assertion being ignored.");
+
+ return true;
+
+ case Fail:
+ default:
+ return false;
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/Publisher.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/Publisher.java
new file mode 100644
index 0000000000..2c8be4f787
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/Publisher.java
@@ -0,0 +1,74 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+/**
+ * A Publisher represents the status of the publishing side of a test circuit. Its main purpose is to provide assertions
+ * that can be applied to test the behaviour of the publishers.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities
+ * <tr><td> Provide assertion that the publishers received no exceptions.
+ * </table>
+ *
+ * @todo There are mixtures of AMQP and JMS assertions in this interface. Either keep them here, but quietly (or with a
+ * warning or error) drop them from test cases where they are not relevant, or push them down into sub-classes.
+ * I am tempted to go with the dropping/warning/error approach, that would imply that it makes sense to pull
+ * the assertions back from AMQPPublisher to here.
+ */
+public interface Publisher
+{
+ // Assertions that are meaningfull to AMQP and to JMS.
+
+ /**
+ * Provides an assertion that the publisher encountered no exceptions.
+ *
+ * @param testProps The test configuration properties.
+ *
+ * @return An assertion that the publisher encountered no exceptions.
+ */
+ public Assertion noExceptionsAssertion(ParsedProperties testProps);
+
+ // Assertions that are meaningfull only to AMQP.
+
+ /**
+ * Provides an assertion that the AMQP channel was forcibly closed by an error condition.
+ *
+ * @param testProps The test configuration properties.
+ *
+ * @return An assertion that the AMQP channel was forcibly closed by an error condition.
+ */
+ public Assertion channelClosedAssertion(ParsedProperties testProps);
+
+ // Assertions that are meaningfull only to Java/JMS.
+
+ /**
+ * Provides an assertion that the publisher got a given exception during the test.
+ *
+ * @param testProps The test configuration properties.
+ * @param exceptionClass The exception class to check for.
+ *
+ * @return An assertion that the publisher got a given exception during the test.
+ */
+ public Assertion exceptionAssertion(ParsedProperties testProps, Class<? extends Exception> exceptionClass);
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/Receiver.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/Receiver.java
new file mode 100644
index 0000000000..19dc4d90e7
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/Receiver.java
@@ -0,0 +1,92 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+/**
+ * A Receiver is a {@link CircuitEnd} that represents the status of the receiving side of a test circuit. Its main
+ * purpose is to provide assertions that can be applied to check the behaviour of the receivers.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities
+ * <tr><td> Provide assertion that the receivers received no exceptions.
+ * <tr><td> Provide assertion that the receivers received all test messages sent to it.
+ * </table>
+ *
+ * @todo There are mixtures of AMQP and JMS assertions in this interface. Either keep them here, but quietly (or with a
+ * warning or error) drop them from test cases where they are not relevant, or push them down into sub-classes.
+ * I am tempted to go with the dropping/warning/error approach.
+ */
+public interface Receiver
+{
+ // Assertions that are meaningfull to AMQP and to JMS.
+
+ /**
+ * Provides an assertion that the receivers encountered no exceptions.
+ *
+ * @param testProps The test configuration properties.
+ *
+ * @return An assertion that the receivers encountered no exceptions.
+ */
+ public Assertion noExceptionsAssertion(ParsedProperties testProps);
+
+ /**
+ * Provides an assertion that the receivers got all messages that were sent to it.
+ *
+ * @param testProps The test configuration properties.
+ *
+ * @return An assertion that the receivers got all messages that were sent to it.
+ */
+ public Assertion allMessagesReceivedAssertion(ParsedProperties testProps);
+
+ /**
+ * Provides an assertion that the receivers got none of the messages that were sent to it.
+ *
+ * @param testProps The test configuration properties.
+ *
+ * @return An assertion that the receivers got none of the messages that were sent to it.
+ */
+ public Assertion noMessagesReceivedAssertion(ParsedProperties testProps);
+
+ // Assertions that are meaningfull only to AMQP.
+
+ /**
+ * Provides an assertion that the AMQP channel was forcibly closed by an error condition.
+ *
+ * @param testProps The test configuration properties.
+ *
+ * @return An assertion that the AMQP channel was forcibly closed by an error condition.
+ */
+ public Assertion channelClosedAssertion(ParsedProperties testProps);
+
+ // Assertions that are meaningfull only to Java/JMS.
+
+ /**
+ * Provides an assertion that the receiver got a given exception during the test.
+ *
+ * @param testProps The test configuration properties.
+ * @param exceptionClass The exception class to check for.
+ *
+ * @return An assertion that the receiver got a given exception during the test.
+ */
+ public Assertion exceptionAssertion(ParsedProperties testProps, Class<? extends Exception> exceptionClass);
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/TestCaseVector.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/TestCaseVector.java
new file mode 100644
index 0000000000..ad1e70f6f7
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/TestCaseVector.java
@@ -0,0 +1,88 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+/**
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td>
+ * </table>
+ */
+public class TestCaseVector
+{
+ /** The test case name. */
+ private String testCase;
+
+ /** The test cycle number within the test case. */
+ private int testCycleNumber;
+
+ public TestCaseVector(String testCase, int testCycleNumber)
+ {
+ this.testCase = testCase;
+ this.testCycleNumber = testCycleNumber;
+ }
+
+ public String getTestCase()
+ {
+ return testCase;
+ }
+
+ public int getTestCycleNumber()
+ {
+ return testCycleNumber;
+ }
+
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+
+ if ((o == null) || (getClass() != o.getClass()))
+ {
+ return false;
+ }
+
+ TestCaseVector that = (TestCaseVector) o;
+
+ if (testCycleNumber != that.testCycleNumber)
+ {
+ return false;
+ }
+
+ if ((testCase != null) ? (!testCase.equals(that.testCase)) : (that.testCase != null))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ public int hashCode()
+ {
+ int result;
+ result = ((testCase != null) ? testCase.hashCode() : 0);
+ result = (31 * result) + testCycleNumber;
+
+ return result;
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/TestClientDetails.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/TestClientDetails.java
new file mode 100644
index 0000000000..7498f2b6b5
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/TestClientDetails.java
@@ -0,0 +1,86 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+/**
+ * TestClientDetails is used to encapsulate information about an interop test client. It pairs together the unique
+ * name of the client, and the route on which it listens to its control messages.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Record test clients control addresses together with their names.
+ * </table>
+ */
+public class TestClientDetails
+{
+ /** The test clients name. */
+ public String clientName;
+
+ /* The test clients unique sequence number. Not currently used. */
+
+ /** The routing key of the test clients control topic. */
+ public String privateControlKey;
+
+ /**
+ * Two TestClientDetails are considered to be equal, iff they have the same client name.
+ *
+ * @param o The object to compare to.
+ *
+ * @return <tt>If the object to compare to is a TestClientDetails equal to this one, <tt>false</tt> otherwise.
+ */
+ public boolean equals(Object o)
+ {
+ if (this == o)
+ {
+ return true;
+ }
+
+ if (!(o instanceof TestClientDetails))
+ {
+ return false;
+ }
+
+ final TestClientDetails testClientDetails = (TestClientDetails) o;
+
+ return !((clientName != null) ? (!clientName.equals(testClientDetails.clientName))
+ : (testClientDetails.clientName != null));
+ }
+
+ /**
+ * Computes a hash code compatible with the equals method; based on the client name alone.
+ *
+ * @return A hash code for this.
+ */
+ public int hashCode()
+ {
+ return ((clientName != null) ? clientName.hashCode() : 0);
+ }
+
+ /**
+ * Outputs the client name and address details. Mostly used for debugging purposes.
+ *
+ * @return The client name and address.
+ */
+ public String toString()
+ {
+ return "TestClientDetails: [ clientName = " + clientName + ", privateControlKey = " + privateControlKey + " ]";
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/TestUtils.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/TestUtils.java
new file mode 100644
index 0000000000..f1adeead80
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/TestUtils.java
@@ -0,0 +1,192 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework;
+
+import org.apache.log4j.Logger;
+
+import static org.apache.qpid.test.framework.MessagingTestConfigProperties.*;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+import javax.jms.*;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+import java.util.Map;
+
+/**
+ * TestUtils provides static helper methods that are usefull for writing tests against QPid.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Create connections from test properties. <td> {@link MessagingTestConfigProperties}
+ * <tr><td> Create test messages.
+ * <tr><td> Inject a short pause in a test.
+ * <tr><td> Serialize properties into a message.
+ * </table>
+ */
+public class TestUtils
+{
+ /** Used for debugging. */
+ private static Logger log = Logger.getLogger(TestUtils.class);
+
+ /** Some dummy data to stuff all test messages with. */
+ private static final byte[] MESSAGE_DATA_BYTES =
+ "Test Message -- Test Message -- Test Message -- Test Message -- Test Message -- Test Message -- Test Message -- "
+ .getBytes();
+
+ /**
+ * Establishes a JMS connection using a set of properties and qpids built in JNDI implementation. This is a simple
+ * convenience method for code that does not anticipate handling connection failures. All exceptions that indicate
+ * that the connection has failed, are wrapped as rutime exceptions, presumably handled by a top level failure
+ * handler.
+ *
+ * <p/>This utility makes use of the following test parameters from {@link MessagingTestConfigProperties} to control
+ * the connection creation:
+ *
+ * <p/><table>
+ * <tr><td> {@link MessagingTestConfigProperties#USERNAME_PROPNAME} <td> The username.
+ * <tr><td> {@link MessagingTestConfigProperties#PASSWORD_PROPNAME} <td> The password.
+ * <tr><td> {@link MessagingTestConfigProperties#VIRTUAL_HOST_PROPNAME} <td> The virtual host name.
+ * <tr><td> {@link MessagingTestConfigProperties#BROKER_PROPNAME} <td> The broker URL.
+ * <tr><td> {@link MessagingTestConfigProperties#CONNECTION_NAME} <td> The broker name in the initial context.
+ *
+ * @param messagingProps Connection properties as defined in {@link MessagingTestConfigProperties}.
+ *
+ * @return A JMS conneciton.
+ */
+ public static Connection createConnection(ParsedProperties messagingProps)
+ {
+ log.debug("public static Connection createConnection(ParsedProperties messagingProps = " + messagingProps
+ + "): called");
+
+ try
+ {
+ // Extract the configured connection properties from the test configuration.
+ String conUsername = messagingProps.getProperty(USERNAME_PROPNAME);
+ String conPassword = messagingProps.getProperty(PASSWORD_PROPNAME);
+ String virtualHost = messagingProps.getProperty(VIRTUAL_HOST_PROPNAME);
+ String brokerUrl = messagingProps.getProperty(BROKER_PROPNAME);
+
+ // Create the broker connection url.
+ String connectionString =
+ "amqp://" + conUsername + ":" + conPassword + "@clientid/" + ((virtualHost != null) ? virtualHost : "")
+ + "?brokerlist='" + brokerUrl + "'";
+
+ // Create properties to create the initial context from, and inject the connection factory configuration
+ // for the defined connection name into it.
+ messagingProps.setProperty("connectionfactory." + CONNECTION_NAME, connectionString);
+
+ Context ctx = new InitialContext(messagingProps);
+
+ ConnectionFactory cf = (ConnectionFactory) ctx.lookup(CONNECTION_NAME);
+
+ return cf.createConnection();
+ }
+ catch (NamingException e)
+ {
+ throw new RuntimeException("Got JNDI NamingException whilst looking up the connection factory.", e);
+ }
+ catch (JMSException e)
+ {
+ throw new RuntimeException("Could not establish connection due to JMSException.", e);
+ }
+ }
+
+ /**
+ * Creates a test message of the specified size, on the given JMS session.
+ *
+ * @param session The JMS session.
+ * @param size The size of the message in bytes.
+ *
+ * @return A bytes message, of the specified size, filled with dummy data.
+ *
+ * @throws JMSException Any underlying JMSExceptions are allowed to fall through.
+ */
+ public static Message createTestMessageOfSize(Session session, int size) throws JMSException
+ {
+ BytesMessage message = session.createBytesMessage();
+
+ if (size > 0)
+ {
+ int div = size / MESSAGE_DATA_BYTES.length;
+ int mod = size % MESSAGE_DATA_BYTES.length;
+
+ for (int i = 0; i < div; i++)
+ {
+ message.writeBytes(MESSAGE_DATA_BYTES);
+ }
+
+ if (mod != 0)
+ {
+ message.writeBytes(MESSAGE_DATA_BYTES, 0, mod);
+ }
+ }
+
+ return message;
+ }
+
+ /**
+ * Pauses for the specified length of time. In the event of failing to pause for at least that length of time
+ * due to interuption of the thread, a RutimeException is raised to indicate the failure. The interupted status
+ * of the thread is restores in that case. This method should only be used when it is expected that the pause
+ * will be succesfull, for example in test code that relies on inejecting a pause.
+ *
+ * @param t The minimum time to pause for in milliseconds.
+ */
+ public static void pause(long t)
+ {
+ try
+ {
+ Thread.sleep(t);
+ }
+ catch (InterruptedException e)
+ {
+ // Restore the interrupted status
+ Thread.currentThread().interrupt();
+
+ throw new RuntimeException("Failed to generate the requested pause length.", e);
+ }
+ }
+
+ /**
+ * Sets properties of different types on a JMS Message.
+ *
+ * @param message The message to set properties on.
+ * @param properties The property name/value pairs to set.
+ *
+ * @throws javax.jms.JMSException All underlying JMSExceptions are allowed to fall through.
+ *
+ * @todo Move this helper method somewhere else. For example, TestUtils.
+ */
+ public static void setPropertiesOnMessage(Message message, Map<Object, Object> properties) throws JMSException
+ {
+ for (Map.Entry<Object, Object> entry : properties.entrySet())
+ {
+ String name = entry.getKey().toString();
+ Object value = entry.getValue();
+
+ message.setObjectProperty(name, value);
+ }
+ }
+}
+
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchFailureException.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchFailureException.java
new file mode 100644
index 0000000000..00cc2d8966
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchFailureException.java
@@ -0,0 +1,45 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.clocksynch;
+
+/**
+ * ClockSynchFailureException represents failure of a {@link ClockSynchronizer} to achieve synchronization. For example,
+ * this could be because a reference signal is not available, or because a desired accurracy cannot be attained.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Represent failure to achieve synchronization.
+ * </table>
+ */
+public class ClockSynchFailureException extends Exception
+{
+ /**
+ * Creates a clock synch failure exception.
+ *
+ * @param message The detail message (which is saved for later retrieval by the {@link #getMessage()} method).
+ * @param cause The cause (which is saved for later retrieval by the {@link #getCause()} method). (A <tt>null</tt>
+ * value is permitted, and indicates that the cause is nonexistent or unknown.)
+ */
+ public ClockSynchFailureException(String message, Throwable cause)
+ {
+ super(message, cause);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchThread.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchThread.java
new file mode 100644
index 0000000000..3d4c4f7d12
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchThread.java
@@ -0,0 +1,124 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.clocksynch;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.junit.extensions.ShutdownHookable;
+import org.apache.qpid.junit.extensions.Throttle;
+
+/**
+ * ClockSynchThread is a convenient utility for running a thread that periodically synchronizes the clock against
+ * a reference. Supply it with a {@link ClockSynchronizer} and a {@link Throttle} and it will continually keep the
+ * clock up-to-date at a rate determined by the throttle.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Continually sychronize the clock at a throttled rate.
+ * </table>
+ */
+public class ClockSynchThread extends Thread implements ShutdownHookable
+{
+ /** Used for debugging. */
+ private static final Logger log = Logger.getLogger(ClockSynchThread.class);
+
+ /** Holds the clock syncher for the synch thread. */
+ private ClockSynchronizer clockSyncher;
+
+ /** Holds the throttle to limit the synch rate. */
+ private Throttle throttle;
+
+ /** Flag to indicate that the periodic clock syncher should keep running. */
+ boolean doSynch = true;
+
+ /**
+ * Creates a clock synchronizer thread from a clock synchronizer and a throttle.
+ *
+ * @param syncher The clock synchronizer.
+ * @param throttle The throttle.
+ */
+ public ClockSynchThread(ClockSynchronizer syncher, Throttle throttle)
+ {
+ this.clockSyncher = syncher;
+ this.throttle = throttle;
+ }
+
+ /**
+ * Terminates the synchronization thread.
+ */
+ public void terminate()
+ {
+ doSynch = false;
+ }
+
+ /**
+ * Continually updates the clock, until {@link #terminate()} is called.
+ */
+ public void run()
+ {
+ while (doSynch)
+ {
+ // Perform a clock clockSynch.
+ try
+ {
+ // Wait controlled by the throttle before doing the next synch.
+ throttle.throttle();
+
+ clockSyncher.synch();
+ log.debug("Clock synched, delta = " + clockSyncher.getDelta() + ", epsilon = " + clockSyncher.getEpsilon()
+ + ".");
+ }
+ // Terminate the synch thread if the synchronization cannot be achieved.
+ catch (ClockSynchFailureException e)
+ {
+ log.debug("Cannot synchronize the clock (reference service may be down). Terminating the synch thread.");
+ doSynch = false;
+ }
+ }
+ }
+
+ /**
+ * Gets the clock synchronizer that is kept continually up to date.
+ *
+ * @return The clock synchronizer that is kept continually up to date.
+ */
+ public ClockSynchronizer getClockSyncher()
+ {
+ return clockSyncher;
+ }
+
+ /**
+ * Supplies a shutdown hook, that terminates the synching thread.
+ *
+ * @return The shut down hook.
+ */
+ public Thread getShutdownHook()
+ {
+ return new Thread(new Runnable()
+ {
+ public void run()
+ {
+ doSynch = false;
+ }
+ });
+ }
+}
+
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchronizer.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchronizer.java
new file mode 100644
index 0000000000..a92c551bc2
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/ClockSynchronizer.java
@@ -0,0 +1,69 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.clocksynch;
+
+/**
+ * ClockSynchronizer provides an interface through which two nodes may synchronize their clocks. It is expected that one
+ * node will act as the reference clock, to which no delta need be applied, and the other node will act as the slave,
+ * and which must apply a delta to its local clock to get a clock synchronized with the reference.
+ *
+ * <p/>The slave side will initiate the computation of a clock delta by calling the {@link #synch} method. This method
+ * will not return until the delta has been computed, at which point there is a method to return its value, as well as
+ * an estimate of the likely error (usually one standard deviation), in the synchronization. For convenience there is a
+ * {@link #nanoTime} method to return the value of System.nanoTime() with the delta added in.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Trigger a clock synchronization.
+ * <tr><td> Compute a clock delta to apply to the local clock.
+ * <tr><td> Estimate the error in the synchronzation.
+ * </table>
+ */
+public interface ClockSynchronizer
+{
+ /**
+ * The slave side should call this to copute a clock delta with the reference.
+ *
+ * @throws ClockSynchFailureException If synchronization cannot be achieved.
+ */
+ public void synch() throws ClockSynchFailureException;
+
+ /**
+ * Gets the clock delta in nano seconds.
+ *
+ * @return The clock delta in nano seconds.
+ */
+ public long getDelta();
+
+ /**
+ * Gets an estimate of the clock error in nan seconds.
+ *
+ * @return An estimate of the clock error in nan seconds.
+ */
+ public long getEpsilon();
+
+ /**
+ * Gets the local clock time with any computed delta added in.
+ *
+ * @return The local clock time with any computed delta added in.
+ */
+ public long nanoTime();
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/LocalClockSynchronizer.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/LocalClockSynchronizer.java
new file mode 100644
index 0000000000..f448d5f23c
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/LocalClockSynchronizer.java
@@ -0,0 +1,73 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.clocksynch;
+
+/**
+ * LocalClockSynchronizer is a fake {@link ClockSynchronizer} that simply calls System.nanoTime(). It exists so that
+ * the same tests can be run distributed or locally, taking timings against the ClockSynchronizer interface without
+ * being aware of how they are being run.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Supply the local clock with no delta.
+ * </table>
+ */
+public class LocalClockSynchronizer implements ClockSynchronizer
+{
+ /**
+ * The slave side should call this to copute a clock delta with the reference.
+ *
+ * @throws org.apache.qpid.test.framework.clocksynch.ClockSynchFailureException
+ * If synchronization cannot be achieved.
+ */
+ public void synch() throws ClockSynchFailureException
+ { }
+
+ /**
+ * Gets the clock delta in nano seconds.
+ *
+ * @return The clock delta in nano seconds.
+ */
+ public long getDelta()
+ {
+ return 0L;
+ }
+
+ /**
+ * Gets an estimate of the clock error in nan seconds.
+ *
+ * @return An estimate of the clock error in nan seconds.
+ */
+ public long getEpsilon()
+ {
+ return 0L;
+ }
+
+ /**
+ * Gets the local clock time with any computed delta added in.
+ *
+ * @return The local clock time with any computed delta added in.
+ */
+ public long nanoTime()
+ {
+ return System.nanoTime();
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockReference.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockReference.java
new file mode 100644
index 0000000000..8bce752f68
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockReference.java
@@ -0,0 +1,165 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.clocksynch;
+
+import org.apache.qpid.junit.extensions.ShutdownHookable;
+
+import java.io.IOException;
+import java.net.*;
+import java.nio.ByteBuffer;
+
+/**
+ * UDPClockReference supplies a refernce clock signal (generated from System.nanoTime()).
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Supply a reference clock signal.
+ * </table>
+ *
+ * @todo Port hard coded. Make configurable.
+ *
+ * @todo Errors rethrown as runtimes, or silently terminate the service. Could add better error handling if needed.
+ */
+public class UDPClockReference implements Runnable, ShutdownHookable
+{
+ /** Used for debugging. */
+ // private static final Logger log = Logger.getLogger(UDPClockReference.class);
+
+ /** Defines the timeout to use when polling the socket for time requests. */
+ private static final int TIMEOUT = 200;
+
+ /** Defines the port to run the clock reference on. */
+ public static final int REFERENCE_PORT = 4444;
+
+ /** Holds the socket to receive clock reference requests on. */
+ protected DatagramSocket socket = null;
+
+ /** Flag used to indicate that the time server should keep running. Set to false to terminate. */
+ protected boolean publish = true;
+
+ /**
+ * Creates a clock reference service on the standard port.
+ */
+ public UDPClockReference()
+ {
+ try
+ {
+ socket = new DatagramSocket(REFERENCE_PORT);
+ socket.setSoTimeout(TIMEOUT);
+ }
+ catch (SocketException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Implements the run loop for this reference time server. This waits for incoming time requests, and replies to
+ * any, with a message with the local time stamp in it. Periodically (controlled by {@link #TIMEOUT}), the run
+ * loop will check if the {@link #publish} flag has been cleared, and terminate the reference time service if so.
+ */
+ public void run()
+ {
+ byte[] buf = new byte[256];
+ ByteBuffer bbuf = ByteBuffer.wrap(buf);
+
+ while (publish)
+ {
+ try
+ {
+ // Wait for a reference time request.
+ DatagramPacket packet = new DatagramPacket(buf, buf.length);
+ boolean timedOut = false;
+
+ try
+ {
+ socket.receive(packet);
+ }
+ catch (SocketTimeoutException e)
+ {
+ timedOut = true;
+ }
+
+ if (!timedOut)
+ {
+ // Work out from the received packet, where to reply to.
+ InetAddress address = packet.getAddress();
+ int port = packet.getPort();
+
+ // Respond to the time request by sending back the local clock as the reference time.
+ bbuf.putLong(System.nanoTime());
+ bbuf.flip();
+ packet = new DatagramPacket(bbuf.array(), bbuf.capacity(), address, port);
+
+ socket.send(packet);
+ }
+ }
+ catch (IOException e)
+ {
+ publish = false;
+ }
+ }
+
+ socket.close();
+ }
+
+ /**
+ * Supplies a shutdown hook.
+ *
+ * @return The shut down hook.
+ */
+ public Thread getShutdownHook()
+ {
+ return new Thread(new Runnable()
+ {
+ public void run()
+ {
+ publish = false;
+ }
+ });
+ }
+
+ /**
+ * For testing purposes. Runs a reference clock on the default port.
+ *
+ * @param args None.
+ */
+ public static void main(String[] args)
+ {
+ try
+ {
+ // Create the clock reference service.
+ UDPClockReference clock = new UDPClockReference();
+
+ // Set up a shutdown hook for it.
+ Runtime.getRuntime().addShutdownHook(clock.getShutdownHook());
+
+ // Start the service.
+ clock.run();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+}
+
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockSynchronizer.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockSynchronizer.java
new file mode 100644
index 0000000000..c89112eff8
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/clocksynch/UDPClockSynchronizer.java
@@ -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.
+ *
+ */
+package org.apache.qpid.test.framework.clocksynch;
+
+import org.apache.qpid.junit.extensions.util.CommandLineParser;
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+import java.io.IOException;
+import java.net.*;
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ * UDPClockSynchronizer is a {@link ClockSynchronizer} that sends pings as UDP datagrams, and uses the following simple
+ * algorithm to perform clock synchronization:
+ *
+ * <ol>
+ * <li>Slave initiates synchronization with a Reference clock.</li>
+ * <li>Slave stamps current local time on a "time request" message and sends to the Reference.</li>
+ * <li>Upon receipt by Reference, Reference stamps Reference-time and returns.</li>
+ * <li>Upon receipt by Slave, Slave subtracts current time from sent time and divides by two to compute latency. It
+ * subtracts current time from Reference time to determine Slave-Reference time delta and adds in the
+ * half-latency to get the correct clock delta.</li>
+ * <li>The first result is immediately used to update the clock since it will get the local clock into at least
+ * the right ballpark.</li>
+ * <li>The Slave repeats steps 2 through 4, 15 more times.</li>
+ * <li>The results of the packet receipts are accumulated and sorted in lowest-latency to highest-latency order. The
+ * median latency is determined by picking the mid-point sample from this ordered list.</li>
+ * <li>All samples outside 1 standard-deviation from the median are discarded and the remaining samples
+ * are averaged using an arithmetic mean.</li>
+ * </ol>
+ *
+ * <p/>The use of UDP datagrams, instead of TCP based communication eliminates the hidden delays that TCP can introduce,
+ * as it can transparently re-order or re-send packets, or introduce delays as packets are naggled.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Trigger a clock synchronziation.
+ * <tr><td> Compute a clock delta to apply to the local clock.
+ * <tr><td> Estimate the error in the synchronzation.
+ * </table>
+ */
+public class UDPClockSynchronizer implements ClockSynchronizer
+{
+ /** Used for debugging. */
+ // private static final Logger log = Logger.getLogger(UDPClockSynchronizer.class);
+
+ /** Defines the timeout to use when waiting for responses to time requests. */
+ private static final int TIMEOUT = 50;
+
+ /** The clock delta. */
+ private long delta = 0L;
+
+ /** Holds an estimate of the clock error relative to the reference clock. */
+ private long epsilon = 0L;
+
+ /** Holds the address of the reference clock. */
+ private InetAddress referenceAddress;
+
+ /** Holds the socket to communicate with the reference service over. */
+ private DatagramSocket socket;
+
+ /** Used to control the shutdown in the main test loop. */
+ private static boolean doSynch = true;
+
+ /**
+ * Creates a clock synchronizer against the specified address for the reference.
+ *
+ * @param address The address of the reference service.
+ */
+ public UDPClockSynchronizer(String address)
+ {
+ try
+ {
+ referenceAddress = InetAddress.getByName(address);
+ }
+ catch (UnknownHostException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * The slave side should call this to compute a clock delta with the reference.
+ *
+ * @throws ClockSynchFailureException If synchronization cannot be achieved, due to unavailability of the reference
+ * time service.
+ */
+ public void synch() throws ClockSynchFailureException
+ {
+ try
+ {
+ socket = new DatagramSocket();
+ socket.setSoTimeout(TIMEOUT);
+
+ // Synchronize on a single ping, to get the clock into the right ball-park.
+ synch(1);
+
+ // Synchronize on 15 pings.
+ synch(15);
+
+ // And again, for greater accuracy, on 31.
+ synch(31);
+
+ socket.close();
+ }
+ catch (SocketException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Updates the synchronization delta by performing the specified number of reference clock requests.
+ *
+ * @param n The number of reference clock request cycles to perform.
+ *
+ * @throws ClockSynchFailureException If synchronization cannot be achieved, due to unavailability of the reference
+ * time service.
+ */
+ protected void synch(int n) throws ClockSynchFailureException
+ {
+ // log.debug("protected void synch(int n = " + n + "): called");
+
+ // Create an array of deltas by performing n reference pings.
+ long[] delta = new long[n];
+
+ for (int i = 0; i < n; i++)
+ {
+ delta[i] = ping();
+ }
+
+ // Reject any deltas that are larger than 1 s.d. above the median.
+ long median = median(delta);
+ long sd = standardDeviation(delta);
+
+ // log.debug("median = " + median);
+ // log.debug("sd = " + sd);
+
+ long[] tempDeltas = new long[n];
+ int count = 0;
+
+ for (int i = 0; i < n; i++)
+ {
+ if ((delta[i] <= (median + sd)) && (delta[i] >= (median - sd)))
+ {
+ tempDeltas[count] = delta[i];
+ count++;
+ }
+ else
+ {
+ // log.debug("Rejected: " + delta[i]);
+ }
+ }
+
+ System.arraycopy(tempDeltas, 0, delta, 0, count);
+
+ // Estimate the delta as the mean of the remaining deltas.
+ this.delta += mean(delta);
+
+ // Estimate the error as the standard deviation of the remaining deltas.
+ this.epsilon = standardDeviation(delta);
+
+ // log.debug("this.delta = " + this.delta);
+ // log.debug("this.epsilon = " + this.epsilon);
+ }
+
+ /**
+ * Performs a single reference clock request cycle and returns the estimated delta relative to the local clock.
+ * This is computed as the half-latency of the requst cycle, plus the reference clock, minus the local clock.
+ *
+ * @return The estimated clock delta.
+ *
+ * @throws ClockSynchFailureException If the reference service is not responding.
+ */
+ protected long ping() throws ClockSynchFailureException
+ {
+ // log.debug("protected long ping(): called");
+
+ try
+ {
+ byte[] buf = new byte[256];
+
+ boolean timedOut = false;
+ long start = 0L;
+ long refTime = 0L;
+ long localTime = 0L;
+ long latency = 0L;
+ int failCount = 0;
+
+ // Keep trying the ping until it gets a response, or 10 tries in a row all time out.
+ do
+ {
+ // Start timing the request latency.
+ start = nanoTime();
+
+ // Get the reference time.
+ DatagramPacket packet =
+ new DatagramPacket(buf, buf.length, referenceAddress, UDPClockReference.REFERENCE_PORT);
+ socket.send(packet);
+ packet = new DatagramPacket(buf, buf.length);
+
+ timedOut = false;
+
+ try
+ {
+ socket.receive(packet);
+ }
+ catch (SocketTimeoutException e)
+ {
+ timedOut = true;
+ failCount++;
+
+ continue;
+ }
+
+ ByteBuffer bbuf = ByteBuffer.wrap(packet.getData());
+ refTime = bbuf.getLong();
+
+ // Stop timing the request latency.
+ localTime = nanoTime();
+ latency = localTime - start;
+
+ // log.debug("refTime = " + refTime);
+ // log.debug("localTime = " + localTime);
+ // log.debug("start = " + start);
+ // log.debug("latency = " + latency);
+ // log.debug("delta = " + ((latency / 2) + (refTime - localTime)));
+
+ }
+ while (timedOut && (failCount < 10));
+
+ // Fail completely if the fail count is too high.
+ if (failCount >= 10)
+ {
+ throw new ClockSynchFailureException("Clock reference not responding.", null);
+ }
+
+ // Estimate delta as (ref clock + half-latency) - local clock.
+ return (latency / 2) + (refTime - localTime);
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Gets the clock delta in nano seconds.
+ *
+ * @return The clock delta in nano seconds.
+ */
+ public long getDelta()
+ {
+ return delta;
+ }
+
+ /**
+ * Gets an estimate of the clock error in nan seconds.
+ *
+ * @return An estimate of the clock error in nan seconds.
+ */
+ public long getEpsilon()
+ {
+ return epsilon;
+ }
+
+ /**
+ * Gets the local clock time with any computed delta added in.
+ *
+ * @return The local clock time with any computed delta added in.
+ */
+ public long nanoTime()
+ {
+ return System.nanoTime() + delta;
+ }
+
+ /**
+ * Computes the median of a series of values.
+ *
+ * @param values The values.
+ *
+ * @return The median.
+ */
+ public static long median(long[] values)
+ {
+ // log.debug("public static long median(long[] values = " + Arrays.toString(values) + "): called");
+
+ long median;
+
+ // Order the list of values.
+ long[] orderedValues = new long[values.length];
+ System.arraycopy(values, 0, orderedValues, 0, values.length);
+ Arrays.sort(orderedValues);
+
+ // Check if the median is computed from a pair of middle value.
+ if ((orderedValues.length % 2) == 0)
+ {
+ int middle = orderedValues.length / 2;
+
+ median = (orderedValues[middle] + orderedValues[middle - 1]) / 2;
+ }
+ // The median is computed from a single middle value.
+ else
+ {
+ median = orderedValues[orderedValues.length / 2];
+ }
+
+ // log.debug("median = " + median);
+
+ return median;
+ }
+
+ /**
+ * Computes the mean of a series of values.
+ *
+ * @param values The values.
+ *
+ * @return The mean.
+ */
+ public static long mean(long[] values)
+ {
+ // log.debug("public static long mean(long[] values = " + Arrays.toString(values) + "): called");
+
+ long total = 0L;
+
+ for (long value : values)
+ {
+ total += value;
+ }
+
+ long mean = total / values.length;
+
+ // log.debug("mean = " + mean);
+
+ return mean;
+ }
+
+ /**
+ * Computes the variance of series of values.
+ *
+ * @param values The values.
+ *
+ * @return The variance of the values.
+ */
+ public static long variance(long[] values)
+ {
+ // log.debug("public static long variance(long[] values = " + Arrays.toString(values) + "): called");
+
+ long mean = mean(values);
+
+ long totalVariance = 0;
+
+ for (long value : values)
+ {
+ long diff = (value - mean);
+ totalVariance += diff * diff;
+ }
+
+ long variance = totalVariance / values.length;
+
+ // log.debug("variance = " + variance);
+
+ return variance;
+ }
+
+ /**
+ * Computes the standard deviation of a series of values.
+ *
+ * @param values The values.
+ *
+ * @return The standard deviation.
+ */
+ public static long standardDeviation(long[] values)
+ {
+ // log.debug("public static long standardDeviation(long[] values = " + Arrays.toString(values) + "): called");
+
+ long sd = Double.valueOf(Math.sqrt(variance(values))).longValue();
+
+ // log.debug("sd = " + sd);
+
+ return sd;
+ }
+
+ /**
+ * For testing purposes. Supply address of reference clock as arg 1.
+ *
+ * @param args Address of reference clock as arg 1.
+ */
+ public static void main(String[] args)
+ {
+ ParsedProperties options =
+ new ParsedProperties(CommandLineParser.processCommandLine(args,
+ new CommandLineParser(
+ new String[][]
+ {
+ { "1", "Address of clock reference service.", "address", "true" }
+ }), System.getProperties()));
+
+ String address = options.getProperty("1");
+
+ // Create a clock synchronizer.
+ UDPClockSynchronizer clockSyncher = new UDPClockSynchronizer(address);
+
+ // Set up a shutdown hook for it.
+ Runtime.getRuntime().addShutdownHook(new Thread(new Runnable()
+ {
+ public void run()
+ {
+ doSynch = false;
+ }
+ }));
+
+ // Repeat the clock synching until the user kills the progam.
+ while (doSynch)
+ {
+ // Perform a clock clockSynch.
+ try
+ {
+ clockSyncher.synch();
+
+ // Print out the clock delta and estimate of the error.
+ System.out.println("Delta = " + clockSyncher.getDelta());
+ System.out.println("Epsilon = " + clockSyncher.getEpsilon());
+
+ try
+ {
+ Thread.sleep(250);
+ }
+ catch (InterruptedException e)
+ {
+ // Restore the interrupted status and terminate the loop.
+ Thread.currentThread().interrupt();
+ doSynch = false;
+ }
+ }
+ // Terminate if the reference time service is unavailable.
+ catch (ClockSynchFailureException e)
+ {
+ doSynch = false;
+ }
+ }
+ }
+}
+
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedCircuitImpl.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedCircuitImpl.java
new file mode 100644
index 0000000000..f375eda4d1
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedCircuitImpl.java
@@ -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.
+ *
+ */
+package org.apache.qpid.test.framework.distributedcircuit;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.test.framework.*;
+import org.apache.qpid.test.utils.ConversationFactory;
+
+import org.apache.qpid.junit.extensions.TimingController;
+import org.apache.qpid.junit.extensions.TimingControllerAware;
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.Session;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * DistributedCircuitImpl is a distributed implementation of the test {@link Circuit}. Many publishers and receivers
+ * accross multiple machines may be combined to form a single test circuit. The test circuit extracts reports from
+ * all of its publishers and receivers, and applies its assertions to these reports.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Supply the publishing and receiving ends of a test messaging circuit.
+ * <tr><td> Start the circuit running.
+ * <tr><td> Close the circuit down.
+ * <tr><td> Take a reading of the circuits state.
+ * <tr><td> Apply assertions against the circuits state.
+ * <tr><td> Send test messages over the circuit.
+ * <tr><td> Perform the default test procedue on the circuit.
+ * </table>
+ *
+ * @todo There is a short pause after receiving sender reports before asking for receiver reports, because receivers may
+ * not have finished receiving all their test messages before the report request arrives. This is going to be a
+ * problem for taking test timings and needs to be eliminiated. Suggested solution: have receiver send back reports
+ * asynchronously, on test batch size boundaries, and do so automatically rather than having to have the report
+ * request sent to them. Number each test run, or otherwise uniquely identify it, when a receiver does not get
+ * any more messages on a test run for more than a timeout, it can assume the test is complete and send a final
+ * report. On the coordinator end a future will need to be created to wait for all final reports to come in, and
+ * to register results and timings for the test. This must work in such a way that a new test cycle can be started
+ * without waiting for the results of the old one to come in.
+ *
+ * @todo Add in setting of timing controller, from timing aware test cases.
+ */
+public class DistributedCircuitImpl implements Circuit, TimingControllerAware
+{
+ /** Used for debugging purposes. */
+ private static final Logger log = Logger.getLogger(DistributedCircuitImpl.class);
+
+ /** Holds the conversation factory over which to coordinate the test. */
+ protected ConversationFactory conversationFactory;
+
+ /** Holds the controlSession over which to hold the control conversation. */
+ protected Session controlSession;
+
+ /** Holds the sender nodes in the test circuit. */
+ protected List<TestClientDetails> senders;
+
+ /** Holds the receiver nodes in the test circuit. */
+ protected List<TestClientDetails> receivers;
+
+ /** Holds the sender control conversations. */
+ protected ConversationFactory.Conversation[] senderConversation;
+
+ /** Holds the receiver control conversations. */
+ protected ConversationFactory.Conversation[] receiverConversation;
+
+ /** Holds the control topics for the senders in the test circuit. */
+ protected Destination[] senderControlTopic;
+
+ /** Holds the control topics for the receivers in the test circuit. */
+ protected Destination[] receiverControlTopic;
+
+ /** Holds the number of messages to send per test run. */
+ protected int numMessages;
+
+ /**
+ * Holds the timing controller for the circuit. This is used to log test times asynchronously, when reciever nodes
+ * return their reports after senders have completed a test case.
+ */
+ TimingController timingController;
+
+ /**
+ * Creates a distributed test circuit on the specified senders and receivers.
+ *
+ * @param session The controlSession for all control conversations.
+ * @param senders The senders.
+ * @param receivers The receivers.
+ * @param senderConversation A control conversation with the senders.
+ * @param receiverConversation A control conversation with the receivers.
+ * @param senderControlTopic The senders control topic.
+ * @param receiverControlTopic The receivers control topic.
+ */
+ protected DistributedCircuitImpl(Session session, List<TestClientDetails> senders, List<TestClientDetails> receivers,
+ ConversationFactory.Conversation[] senderConversation, ConversationFactory.Conversation[] receiverConversation,
+ Destination[] senderControlTopic, Destination[] receiverControlTopic)
+ {
+ this.controlSession = session;
+ this.senders = senders;
+ this.receivers = receivers;
+ this.senderConversation = senderConversation;
+ this.receiverConversation = receiverConversation;
+ this.senderControlTopic = senderControlTopic;
+ this.receiverControlTopic = receiverControlTopic;
+ }
+
+ /**
+ * Creates a distributed test circuit from the specified test parameters, on the senders and receivers
+ * given.
+ *
+ * @param testProps The test parameters.
+ * @param senders The sender ends in the test circuit.
+ * @param receivers The receiver ends in the test circuit.
+ * @param conversationFactory A conversation factory for creating the control conversations with senders and receivers.
+ *
+ * @return A connected and ready to start, test circuit.
+ */
+ public static Circuit createCircuit(ParsedProperties testProps, List<TestClientDetails> senders,
+ List<TestClientDetails> receivers, ConversationFactory conversationFactory)
+ {
+ log.debug("public static Circuit createCircuit(ParsedProperties testProps, List<TestClientDetails> senders, "
+ + " List<TestClientDetails> receivers, ConversationFactory conversationFactory)");
+
+ try
+ {
+ Session session = conversationFactory.getSession();
+
+ // Create control conversations with each of the senders.
+ ConversationFactory.Conversation[] senderConversation = new ConversationFactory.Conversation[senders.size()];
+ Destination[] senderControlTopic = new Destination[senders.size()];
+
+ for (int i = 0; i < senders.size(); i++)
+ {
+ TestClientDetails sender = senders.get(i);
+
+ senderControlTopic[i] = session.createTopic(sender.privateControlKey);
+ senderConversation[i] = conversationFactory.startConversation();
+ }
+
+ log.debug("Sender conversations created.");
+
+ // Create control conversations with each of the receivers.
+ ConversationFactory.Conversation[] receiverConversation = new ConversationFactory.Conversation[receivers.size()];
+ Destination[] receiverControlTopic = new Destination[receivers.size()];
+
+ for (int i = 0; i < receivers.size(); i++)
+ {
+ TestClientDetails receiver = receivers.get(i);
+
+ receiverControlTopic[i] = session.createTopic(receiver.privateControlKey);
+ receiverConversation[i] = conversationFactory.startConversation();
+ }
+
+ log.debug("Receiver conversations created.");
+
+ // Assign the sender role to each of the sending test clients.
+ for (int i = 0; i < senders.size(); i++)
+ {
+ TestClientDetails sender = senders.get(i);
+
+ Message assignSender = conversationFactory.getSession().createMessage();
+ TestUtils.setPropertiesOnMessage(assignSender, testProps);
+ assignSender.setStringProperty("CONTROL_TYPE", "ASSIGN_ROLE");
+ assignSender.setStringProperty("ROLE", "SENDER");
+
+ senderConversation[i].send(senderControlTopic[i], assignSender);
+ }
+
+ log.debug("Sender role assignments sent.");
+
+ // Assign the receivers role to each of the receiving test clients.
+ for (int i = 0; i < receivers.size(); i++)
+ {
+ TestClientDetails receiver = receivers.get(i);
+
+ Message assignReceiver = session.createMessage();
+ TestUtils.setPropertiesOnMessage(assignReceiver, testProps);
+ assignReceiver.setStringProperty("CONTROL_TYPE", "ASSIGN_ROLE");
+ assignReceiver.setStringProperty("ROLE", "RECEIVER");
+
+ receiverConversation[i].send(receiverControlTopic[i], assignReceiver);
+ }
+
+ log.debug("Receiver role assignments sent.");
+
+ // Wait for the senders and receivers to confirm their roles.
+ for (int i = 0; i < senders.size(); i++)
+ {
+ senderConversation[i].receive();
+ }
+
+ log.debug("Got all sender role confirmations");
+
+ for (int i = 0; i < receivers.size(); i++)
+ {
+ receiverConversation[i].receive();
+ }
+
+ log.debug("Got all receiver role confirmations");
+
+ // Package everything up as a circuit.
+ return new DistributedCircuitImpl(session, senders, receivers, senderConversation, receiverConversation,
+ senderControlTopic, receiverControlTopic);
+ }
+ catch (JMSException e)
+ {
+ throw new RuntimeException("JMSException not handled.");
+ }
+ }
+
+ /**
+ * Used by tests cases that can supply a {@link org.apache.qpid.junit.extensions.TimingController} to set the
+ * controller on an aware test.
+ *
+ * @param controller The timing controller.
+ */
+ public void setTimingController(TimingController controller)
+ {
+ this.timingController = controller;
+ }
+
+ /**
+ * Gets the interface on the publishing end of the circuit.
+ *
+ * @return The publishing end of the circuit.
+ */
+ public Publisher getPublisher()
+ {
+ throw new RuntimeException("Not Implemented.");
+ }
+
+ /**
+ * Gets the interface on the receiving end of the circuit.
+ *
+ * @return The receiving end of the circuit.
+ */
+ public Receiver getReceiver()
+ {
+ throw new RuntimeException("Not Implemented.");
+ }
+
+ /**
+ * Connects and starts the circuit. After this method is called the circuit is ready to send messages.
+ */
+ public void start()
+ {
+ log.debug("public void start(): called");
+
+ try
+ {
+ // Start the test on each of the senders.
+ Message start = controlSession.createMessage();
+ start.setStringProperty("CONTROL_TYPE", "START");
+ start.setIntProperty("MESSAGE_COUNT", numMessages);
+
+ for (int i = 0; i < senders.size(); i++)
+ {
+ senderConversation[i].send(senderControlTopic[i], start);
+ }
+
+ log.debug("All senders told to start their tests.");
+ }
+ catch (JMSException e)
+ {
+ throw new RuntimeException("Unhandled JMSException.", e);
+ }
+ }
+
+ /**
+ * Checks the test circuit. The effect of this is to gather the circuits state, for both ends of the circuit,
+ * into a report, against which assertions may be checked.
+ *
+ * @todo Replace the asynch receiver report thread with a choice of direct or asynch executor, so that asynch
+ * or synch logging of test timings is optional. Also need to provide an onMessage method that is capable
+ * of receiving timing reports that receivers will generate during an ongoing test, on the test sample
+ * size boundaries. The message timing logging code should be factored out as a common method that can
+ * be called in response to the final report responses, or the onMessage method. Another alternative is
+ * to abandon the final report request altogether and just use the onMessage method? I think the two
+ * differ though, as the final report is used to apply assertions, and the ongoing report is just for
+ * periodic timing results... In which case, maybe there needs to be a way for the onMessage method
+ * to process just some of the incoming messages, and forward the rest on to the conversion helper, as
+ * a sort of pre-conversation helper filter? Make conversation expose its onMessage method (it should
+ * already) and allow another delivery thread to filter the incoming messages to the conversation.
+ */
+ public void check()
+ {
+ log.debug("public void check(): called");
+
+ try
+ {
+ // Wait for all the test senders to return their reports.
+ for (int i = 0; i < senders.size(); i++)
+ {
+ Message senderReport = senderConversation[i].receive();
+ log.debug("Sender " + senderReport.getStringProperty("CLIENT_NAME") + " reports message count: "
+ + senderReport.getIntProperty("MESSAGE_COUNT"));
+ log.debug("Sender " + senderReport.getStringProperty("CLIENT_NAME") + " reports message time: "
+ + senderReport.getLongProperty("TEST_TIME"));
+ }
+
+ log.debug("Got all sender test reports.");
+
+ // Apply sender assertions to pass/fail the tests.
+
+ // Inject a short pause to give the receivers time to finish receiving their test messages.
+ TestUtils.pause(500);
+
+ // Ask the receivers for their reports.
+ Message statusRequest = controlSession.createMessage();
+ statusRequest.setStringProperty("CONTROL_TYPE", "STATUS_REQUEST");
+
+ for (int i = 0; i < receivers.size(); i++)
+ {
+ receiverConversation[i].send(receiverControlTopic[i], statusRequest);
+ }
+
+ log.debug("All receiver test reports requested.");
+
+ // Wait for all receiver reports to come in, but do so asynchronously.
+ Runnable gatherAllReceiverReports =
+ new Runnable()
+ {
+ public void run()
+ {
+ try
+ {
+ // Wait for all the receivers to send their reports.
+ for (int i = 0; i < receivers.size(); i++)
+ {
+ Message receiverReport = receiverConversation[i].receive();
+
+ String clientName = receiverReport.getStringProperty("CLIENT_NAME");
+ int messageCount = receiverReport.getIntProperty("MESSAGE_COUNT");
+ long testTime = receiverReport.getLongProperty("TEST_TIME");
+
+ log.debug("Receiver " + clientName + " reports message count: " + messageCount);
+ log.debug("Receiver " + receiverReport.getStringProperty("CLIENT_NAME")
+ + " reports message time: " + testTime);
+
+ // Apply receiver assertions to pass/fail the tests.
+
+ // Log the test timings on the asynchronous test timing controller.
+ /*try
+ {
+ timingController.completeTest(true, messageCount, testTime);
+ }
+ // The timing controll can throw InterruptedException is the current test is to be
+ // interrupted.
+ catch (InterruptedException e)
+ {
+ e.printStackTrace();
+ }*/
+ }
+
+ log.debug("All receiver test reports received.");
+ }
+ catch (JMSException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ };
+
+ Thread receiverReportsThread = new Thread(gatherAllReceiverReports);
+ receiverReportsThread.start();
+
+ // return new Message[] { senderReport, receiverReport };
+
+ }
+ catch (JMSException e)
+ {
+ throw new RuntimeException("Unhandled JMSException.", e);
+ }
+ }
+
+ /**
+ * Closes the circuit. All associated resources are closed.
+ */
+ public void close()
+ {
+ log.debug("public void close(): called");
+
+ // End the current test on all senders and receivers.
+ }
+
+ /**
+ * Applies a list of assertions against the test circuit. The {@link #check()} method should be called before doing
+ * this, to ensure that the circuit has gathered its state into a report to assert against.
+ *
+ * @param assertions The list of assertions to apply.
+ *
+ * @return Any assertions that failed.
+ */
+ public List<Assertion> applyAssertions(List<Assertion> assertions)
+ {
+ log.debug("public List<Assertion> applyAssertions(List<Assertion> assertions = " + assertions + "): called");
+
+ List<Assertion> failures = new LinkedList<Assertion>();
+
+ for (Assertion assertion : assertions)
+ {
+ if (!assertion.apply())
+ {
+ failures.add(assertion);
+ }
+ }
+
+ return failures;
+ }
+
+ /**
+ * Runs the default test procedure against the circuit, and checks that all of the specified assertions hold.
+ *
+ * @param numMessages The number of messages to send using the default test procedure.
+ * @param assertions The list of assertions to apply.
+ *
+ * @return Any assertions that failed.
+ *
+ * @todo From check onwards needs to be handled as a future. The future must call back onto the test case to
+ * report results asynchronously.
+ */
+ public List<Assertion> test(int numMessages, List<Assertion> assertions)
+ {
+ log.debug("public List<Assertion> test(int numMessages = " + numMessages + ", List<Assertion> assertions = "
+ + assertions + "): called");
+
+ // Keep the number of messages to send per test run, where the send method can reference it.
+ this.numMessages = numMessages;
+
+ // Start the test running on all sender circuit ends.
+ start();
+
+ // Request status reports to be handed in.
+ check();
+
+ // Assert conditions on the publishing end of the circuit.
+ // Assert conditions on the receiving end of the circuit.
+ List<Assertion> failures = applyAssertions(assertions);
+
+ // Close the circuit ending the current test case.
+ close();
+
+ // Pass with no failed assertions or fail with a list of failed assertions.
+ return failures;
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedPublisherImpl.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedPublisherImpl.java
new file mode 100644
index 0000000000..c51f710494
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedPublisherImpl.java
@@ -0,0 +1,95 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.distributedcircuit;
+
+import org.apache.qpid.test.framework.Assertion;
+import org.apache.qpid.test.framework.Publisher;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+/**
+ * DistributedPublisherImpl represents the status of the publishing side of a test circuit. Its main purpose is to
+ * provide assertions that can be applied to verify the behaviour of a non-local publisher.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Provide assertion that the publishers received no exceptions.
+ * <tr><td> Provide assertion that the publishers received a no consumers error code on every message.
+ * <tr><td> Provide assertion that the publishers received a no route error code on every message.
+ * </table>
+ */
+public class DistributedPublisherImpl implements Publisher
+{
+ /**
+ * Provides an assertion that the publisher encountered no exceptions.
+ *
+ * @return An assertion that the publisher encountered no exceptions.
+ * @param testProps
+ */
+ public Assertion noExceptionsAssertion(ParsedProperties testProps)
+ {
+ throw new RuntimeException("Not implemented.");
+ }
+
+ /**
+ * Provides an assertion that the publisher got a no consumers exception on every message.
+ *
+ * @return An assertion that the publisher got a no consumers exception on every message.
+ */
+ public Assertion noConsumersAssertion()
+ {
+ throw new RuntimeException("Not implemented.");
+ }
+
+ /**
+ * Provides an assertion that the publisher got a no rout exception on every message.
+ *
+ * @return An assertion that the publisher got a no rout exception on every message.
+ */
+ public Assertion noRouteAssertion()
+ {
+ throw new RuntimeException("Not implemented.");
+ }
+
+ /**
+ * Provides an assertion that the AMQP channel was forcibly closed by an error condition.
+ *
+ * @param testProps The test configuration properties.
+ * @return An assertion that the AMQP channel was forcibly closed by an error condition.
+ */
+ public Assertion channelClosedAssertion(ParsedProperties testProps)
+ {
+ throw new RuntimeException("Not implemented.");
+ }
+
+ /**
+ * Provides an assertion that the publisher got a given exception during the test.
+ *
+ * @param testProps The test configuration properties.
+ * @param exceptionClass The exception class to check for.
+ * @return An assertion that the publisher got a given exception during the test.
+ */
+ public Assertion exceptionAssertion(ParsedProperties testProps, Class<? extends Exception> exceptionClass)
+ {
+ throw new RuntimeException("Not implemented.");
+ }
+}
+
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedReceiverImpl.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedReceiverImpl.java
new file mode 100644
index 0000000000..863921e387
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/DistributedReceiverImpl.java
@@ -0,0 +1,95 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.distributedcircuit;
+
+import org.apache.qpid.test.framework.Assertion;
+import org.apache.qpid.test.framework.Receiver;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+/**
+ * DistributedReceiverImpl represents the status of the receiving side of a test circuit. Its main purpose is to
+ * provide assertions that can be applied to verify the behaviour of a non-local receiver.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Provide assertion that the receivers received no exceptions.
+ * <tr><td> Provide assertion that the receivers received all test messages sent to it.
+ * </table>
+ */
+public class DistributedReceiverImpl implements Receiver
+{
+ /**
+ * Provides an assertion that the receivers encountered no exceptions.
+ *
+ * @return An assertion that the receivers encountered no exceptions.
+ * @param testProps
+ */
+ public Assertion noExceptionsAssertion(ParsedProperties testProps)
+ {
+ throw new RuntimeException("Not implemented.");
+ }
+
+ /**
+ * Provides an assertion that the receivers got all messages that were sent to it.
+ *
+ * @return An assertion that the receivers got all messages that were sent to it.
+ * @param testProps
+ */
+ public Assertion allMessagesReceivedAssertion(ParsedProperties testProps)
+ {
+ throw new RuntimeException("Not implemented.");
+ }
+
+ /**
+ * Provides an assertion that the receivers got none of the messages that were sent to it.
+ *
+ * @return An assertion that the receivers got none of the messages that were sent to it.
+ * @param testProps
+ */
+ public Assertion noMessagesReceivedAssertion(ParsedProperties testProps)
+ {
+ throw new RuntimeException("Not implemented.");
+ }
+
+ /**
+ * Provides an assertion that the AMQP channel was forcibly closed by an error condition.
+ *
+ * @param testProps The test configuration properties.
+ * @return An assertion that the AMQP channel was forcibly closed by an error condition.
+ */
+ public Assertion channelClosedAssertion(ParsedProperties testProps)
+ {
+ throw new RuntimeException("Not implemented.");
+ }
+
+ /**
+ * Provides an assertion that the receiver got a given exception during the test.
+ *
+ * @param testProps
+ *@param exceptionClass The exception class to check for. @return An assertion that the receiver got a given exception during the test.
+ */
+ public Assertion exceptionAssertion(ParsedProperties testProps, Class<? extends Exception> exceptionClass)
+ {
+ throw new RuntimeException("Not implemented.");
+ }
+}
+
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/TestClientCircuitEnd.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/TestClientCircuitEnd.java
new file mode 100644
index 0000000000..dce2706bc4
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedcircuit/TestClientCircuitEnd.java
@@ -0,0 +1,324 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.distributedcircuit;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.test.framework.*;
+import org.apache.qpid.test.framework.distributedtesting.TestClientControlledTest;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+import org.apache.qpid.junit.extensions.util.TestContextProperties;
+
+import javax.jms.*;
+
+/**
+ * A TestClientCircuitEnd is a {@link CircuitEnd} that may be controlled from a
+ * {@link org.apache.qpid.test.framework.distributedtesting.TestClient}, and that forms a single publishing or
+ * receiving end point in a distributed test {@link org.apache.qpid.test.framework.Circuit}.
+ *
+ * <p/>When operating in the SENDER role, this circuit end is capable of acting as part of the default circuit test
+ * procedure (described in the class comment for {@link org.apache.qpid.test.framework.Circuit}). That is, it will
+ * send the number of test messages required, using the test configuration parameters given in the test invite, and
+ * return a report on its activities to the circuit controller.
+ *
+ * <p/>When operation in the RECEIVER role, this circuit end acts as part of the default circuit test procedure. It will
+ * receive test messages, on the setup specified in the test configuration parameters, and keep count of the messages
+ * received, and time taken to receive them. When requested by the circuit controller to provide a report, it will
+ * return this report of its activities.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Provide a message producer for sending messages.
+ * <td> {@link CircuitEnd}, {@link LocalCircuitFactory}, {@link TestUtils}
+ * <tr><td> Provide a message consumer for receiving messages.
+ * <td> {@link CircuitEnd}, {@link LocalCircuitFactory}, {@link TestUtils}
+ * <tr><td> Supply the name of the test case that this implements.
+ * <tr><td> Accept/Reject invites based on test parameters. <td> {@link MessagingTestConfigProperties}
+ * <tr><td> Adapt to assigned roles. <td> {@link TestClientControlledTest.Roles}
+ * <tr><td> Perform test case actions. <td> {@link MessageMonitor}
+ * <tr><td> Generate test reports. <td> {@link MessageMonitor}
+ * </table>
+ */
+public class TestClientCircuitEnd implements CircuitEnd, TestClientControlledTest
+{
+ /** Used for debugging. */
+ private static final Logger log = Logger.getLogger(TestClientCircuitEnd.class);
+
+ /** Holds the test parameters. */
+ ParsedProperties testProps;
+
+ /** The number of test messages to send. */
+ private int numMessages;
+
+ /** The role to be played by the test. */
+ private Roles role;
+
+ /** The connection to send the test messages on. */
+ private Connection connection;
+
+ /** Holds the circuit end for this test. */
+ CircuitEnd circuitEnd;
+
+ /**
+ * Holds a message monitor for this circuit end, either the monitor on the consumer when in RECEIVER more, or
+ * a monitor updated on every message sent, when acting as a SENDER.
+ */
+ MessageMonitor messageMonitor;
+
+ /**
+ * Should provide the name of the test case that this class implements. The exact names are defined in the
+ * interop testing spec.
+ *
+ * @return The name of the test case that this implements.
+ */
+ public String getName()
+ {
+ return "DEFAULT_CIRCUIT_TEST";
+ }
+
+ /**
+ * Determines whether the test invite that matched this test case is acceptable.
+ *
+ * @param inviteMessage The invitation to accept or reject.
+ * @return <tt>true</tt> to accept the invitation, <tt>false</tt> to reject it.
+ *
+ * @throws JMSException Any JMSException resulting from reading the message are allowed to fall through.
+ */
+ public boolean acceptInvite(Message inviteMessage) throws JMSException
+ {
+ log.debug("public boolean acceptInvite(Message inviteMessage): called");
+
+ // Populate the test parameters from the invitation.
+ testProps = TestContextProperties.getInstance(MessagingTestConfigProperties.defaults);
+
+ for (Object key : testProps.keySet())
+ {
+ String propName = (String) key;
+
+ // If the test parameters is overridden by the invitation, use it instead.
+ String inviteValue = inviteMessage.getStringProperty(propName);
+
+ if (inviteValue != null)
+ {
+ testProps.setProperty(propName, inviteValue);
+ log.debug("Test invite supplied override to " + propName + " of " + inviteValue);
+ }
+
+ }
+
+ // Accept the invitation.
+ return true;
+ }
+
+ /**
+ * Assigns the role to be played by this test case. The test parameters are fully specified in the
+ * assignment message. When this method return the test case will be ready to execute.
+ *
+ * @param role The role to be played; sender or receivers.
+ * @param assignRoleMessage The role assingment message, contains the full test parameters.
+ *
+ * @throws JMSException Any JMSException resulting from reading the message are allowed to fall through.
+ */
+ public void assignRole(Roles role, Message assignRoleMessage) throws JMSException
+ {
+ log.debug("public void assignRole(Roles role, Message assignRoleMessage): called");
+
+ // Take note of the role to be played.
+ this.role = role;
+
+ // Extract and retain the test parameters.
+ numMessages = 1; // assignRoleMessage.getIntProperty("NUM_MESSAGES");
+
+ // Connect using the test parameters.
+ connection = TestUtils.createConnection(testProps);
+
+ // Create a circuit end that matches the assigned role and test parameters.
+ LocalCircuitFactory circuitFactory = new LocalCircuitFactory();
+
+ switch (role)
+ {
+ // Check if the sender role is being assigned, and set up a message producer if so.
+ case SENDER:
+
+ // Set up the publisher.
+ circuitEnd = circuitFactory.createPublisherCircuitEnd(connection, testProps, 0L);
+
+ // Create a custom message monitor that will be updated on every message sent.
+ messageMonitor = new MessageMonitor();
+
+ break;
+
+ // Otherwise the receivers role is being assigned, so set this up to listen for messages.
+ case RECEIVER:
+
+ // Set up the receiver.
+ circuitEnd = circuitFactory.createReceiverCircuitEnd(connection, testProps, 0L);
+
+ // Use the message monitor from the consumer for stats.
+ messageMonitor = getMessageMonitor();
+
+ break;
+ }
+
+ // Reset all messaging stats for the report.
+ messageMonitor.reset();
+
+ connection.start();
+ }
+
+ /**
+ * Performs the test case actions. Returning from here, indicates that the sending role has completed its test.
+ *
+ * @param numMessages The number of test messages to send.
+ *
+ * @throws JMSException Any JMSException resulting from reading the message are allowed to fall through.
+ *
+ * @todo Add round robin on destinations where multiple destinations being used.
+ *
+ * @todo Add rate limiting when rate limit specified on publishers.
+ *
+ * @todo Add Max pending message size protection. The receiver will have to send back some acks once in a while,
+ * to notify the publisher that its messages are being consumed. This makes the safety valve harder to
+ * implement than in the single VM case. For example, if the limit is 1000 messages, might want to get back
+ * an ack every 500, to notify the publisher that it can keep sending. What about pub/sub tests? Will it be
+ * necessary to wait for an ack from every receiver? This will have the effect of rate limiting to slow
+ * consumers too.
+ *
+ * @todo Add commits on every commit batch size boundary.
+ */
+ public void start(int numMessages) throws JMSException
+ {
+ log.debug("public void start(): called");
+
+ // If in the SENDER role, send the specified number of test messages to the circuit destinations.
+ if (role.equals(Roles.SENDER))
+ {
+ Message testMessage = getSession().createMessage();
+
+ for (int i = 0; i < numMessages; i++)
+ {
+ getProducer().send(testMessage);
+
+ // Increment the message count and timings.
+ messageMonitor.onMessage(testMessage);
+ }
+ }
+ }
+
+ /**
+ * Gets a report on the actions performed by the test case in its assigned role.
+ *
+ * @param session The controlSession to create the report message in.
+ * @return The report message.
+ *
+ * @throws JMSException Any JMSExceptions resulting from creating the report are allowed to fall through.
+ */
+ public Message getReport(Session session) throws JMSException
+ {
+ Message report = session.createMessage();
+ report.setStringProperty("CONTROL_TYPE", "REPORT");
+
+ // Add the count of messages sent/received to the report.
+ report.setIntProperty("MESSAGE_COUNT", messageMonitor.getNumMessage());
+
+ // Add the time to send/receive messages to the report.
+ report.setLongProperty("TEST_TIME", messageMonitor.getTime());
+
+ // Add any exceptions detected to the report.
+
+ return report;
+ }
+
+ /**
+ * Gets the message producer at this circuit end point.
+ *
+ * @return The message producer at with this circuit end point.
+ */
+ public MessageProducer getProducer()
+ {
+ return circuitEnd.getProducer();
+ }
+
+ /**
+ * Gets the message consumer at this circuit end point.
+ *
+ * @return The message consumer at this circuit end point.
+ */
+ public MessageConsumer getConsumer()
+ {
+ return circuitEnd.getConsumer();
+ }
+
+ /**
+ * Send the specified message over the producer at this end point.
+ *
+ * @param message The message to send.
+ *
+ * @throws JMSException Any JMS exception occuring during the send is allowed to fall through.
+ */
+ public void send(Message message) throws JMSException
+ {
+ // Send the message on the circuit ends producer.
+ circuitEnd.send(message);
+ }
+
+ /**
+ * Gets the JMS Session associated with this circuit end point.
+ *
+ * @return The JMS Session associated with this circuit end point.
+ */
+ public Session getSession()
+ {
+ return circuitEnd.getSession();
+ }
+
+ /**
+ * Closes the message producers and consumers and the sessions, associated with this circuit end point.
+ *
+ * @throws JMSException Any JMSExceptions occurring during the close are allowed to fall through.
+ */
+ public void close() throws JMSException
+ {
+ // Close the producer and consumer.
+ circuitEnd.close();
+ }
+
+ /**
+ * Returns the message monitor for reporting on received messages on this circuit end.
+ *
+ * @return The message monitor for this circuit end.
+ */
+ public MessageMonitor getMessageMonitor()
+ {
+ return circuitEnd.getMessageMonitor();
+ }
+
+ /**
+ * Returns the exception monitor for reporting on exceptions received on this circuit end.
+ *
+ * @return The exception monitor for this circuit end.
+ */
+ public ExceptionMonitor getExceptionMonitor()
+ {
+ return circuitEnd.getExceptionMonitor();
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/Coordinator.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/Coordinator.java
new file mode 100644
index 0000000000..d532109dc3
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/Coordinator.java
@@ -0,0 +1,539 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.distributedtesting;
+
+import java.net.InetAddress;
+import java.util.*;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import javax.jms.*;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+import junit.framework.TestSuite;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.NDC;
+import org.apache.qpid.test.framework.FrameworkBaseCase;
+import org.apache.qpid.test.framework.MessagingTestConfigProperties;
+import org.apache.qpid.test.framework.TestClientDetails;
+import org.apache.qpid.test.framework.TestUtils;
+import org.apache.qpid.test.framework.clocksynch.UDPClockReference;
+import org.apache.qpid.test.utils.ConversationFactory;
+
+import org.apache.qpid.junit.extensions.TKTestRunner;
+import org.apache.qpid.junit.extensions.WrappedSuiteTestDecorator;
+import org.apache.qpid.junit.extensions.util.CommandLineParser;
+import org.apache.qpid.junit.extensions.util.MathUtils;
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+import org.apache.qpid.junit.extensions.util.TestContextProperties;
+
+/**
+ * <p/>Implements the coordinator client described in the interop testing specification
+ * (http://cwiki.apache.org/confluence/display/qpid/Interop+Testing+Specification). This coordinator is built on
+ * top of the JUnit testing framework.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Find out what test clients are available. <td> {@link ConversationFactory}
+ * <tr><td> Decorate available tests to run on all available clients. <td> {@link DistributedTestDecorator}
+ * <tr><td> Attach XML test result logger.
+ * <tr><td> Terminate the interop testing framework.
+ * </table>
+ *
+ * @todo Should accumulate failures over all tests, and return with success or fail code based on all results. May need
+ * to write a special TestResult to do this properly. At the moment only the last one used will be tested for
+ * errors, as the start method creates a fresh one for each test case run.
+ */
+public class Coordinator extends TKTestRunner
+{
+ /** Used for debugging. */
+ private static final Logger log = Logger.getLogger(Coordinator.class);
+
+ /** Used for reporting to the console. */
+ private static final Logger console = Logger.getLogger("CONSOLE");
+
+ /** Defines the possible distributed test engines available to run coordinated test cases with. */
+ public enum TestEngine
+ {
+ /** Specifies the interop test engine. This tests all available clients in pairs. */
+ INTEROP,
+
+ /** Specifies the fanout test engine. This sets up one publisher role, and many reciever roles. */
+ FANOUT
+ }
+
+ /**
+ * Holds the test context properties that provides the default test parameters, plus command line overrides.
+ * This is initialized with the default test parameters, to which command line overrides may be applied.
+ */
+ protected static ParsedProperties testContextProperties =
+ TestContextProperties.getInstance(MessagingTestConfigProperties.defaults);
+
+ /** Holds the URL of the broker to coordinate the tests on. */
+ protected String brokerUrl;
+
+ /** Holds the virtual host to coordinate the tests on. If <tt>null</tt>, then the default virtual host is used. */
+ protected String virtualHost;
+
+ /** Holds the list of all clients that enlisted, when the compulsory invite was issued. */
+ protected Set<TestClientDetails> enlistedClients = new HashSet<TestClientDetails>();
+
+ /** Holds the conversation helper for the control conversation. */
+ protected ConversationFactory conversationFactory;
+
+ /** Holds the connection that the coordinating messages are sent over. */
+ protected Connection connection;
+
+ /** Holds the path of the directory to output test results too, if one is defined. */
+ protected String reportDir;
+
+ /** Holds the coordinating test engine type to run the tests through. */
+ protected TestEngine engine;
+
+ /** Flag that indicates that all test clients should be terminated upon completion of the test cases. */
+ protected boolean terminate;
+
+ /**
+ * Creates an interop test coordinator on the specified broker and virtual host.
+ *
+ * @param repetitions The number of times to repeat the test, or test batch size.
+ * @param duration The length of time to run the tests for. -1 means no duration has been set.
+ * @param threads The concurrency levels to ramp up to.
+ * @param delay A delay in milliseconds between test runs.
+ * @param params The sets of 'size' parameters to pass to test.
+ * @param testCaseName The name of the test case to run.
+ * @param reportDir The directory to output the test results to.
+ * @param runName The name of the test run; used to name the output file.
+ * @param verbose Whether to print comments during test run.
+ * @param brokerUrl The URL of the broker to connect to.
+ * @param virtualHost The virtual host to run all tests on. Optional, may be <tt>null</tt>.
+ * @param engine The distributed test engine type to run the tests with.
+ * @param terminate <tt>true</tt> if test client nodes should be terminated at the end of the tests.
+ * @param csv <tt>true</tt> if the CSV results listener should be attached.
+ * @param xml <tt>true</tt> if the XML results listener should be attached.
+ * @param decoratorFactories List of factories for user specified decorators.
+ */
+ public Coordinator(Integer repetitions, Long duration, int[] threads, int delay, int[] params, String testCaseName,
+ String reportDir, String runName, boolean verbose, String brokerUrl, String virtualHost, TestEngine engine,
+ boolean terminate, boolean csv, boolean xml, List<TestDecoratorFactory> decoratorFactories)
+ {
+ super(repetitions, duration, threads, delay, params, testCaseName, reportDir, runName, csv, xml, decoratorFactories);
+
+ log.debug("public Coordinator(Integer repetitions = " + repetitions + " , Long duration = " + duration
+ + ", int[] threads = " + Arrays.toString(threads) + ", int delay = " + delay + ", int[] params = "
+ + Arrays.toString(params) + ", String testCaseName = " + testCaseName + ", String reportDir = " + reportDir
+ + ", String runName = " + runName + ", boolean verbose = " + verbose + ", String brokerUrl = " + brokerUrl
+ + ", String virtualHost =" + virtualHost + ", TestEngine engine = " + engine + ", boolean terminate = "
+ + terminate + ", boolean csv = " + csv + ", boolean xml = " + xml + "): called");
+
+ // Retain the connection parameters.
+ this.brokerUrl = brokerUrl;
+ this.virtualHost = virtualHost;
+ this.reportDir = reportDir;
+ this.engine = engine;
+ this.terminate = terminate;
+ }
+
+ /**
+ * The entry point for the interop test coordinator. This client accepts the following command line arguments:
+ *
+ * <p/><table>
+ * <tr><td> -b <td> The broker URL. <td> Mandatory.
+ * <tr><td> -h <td> The virtual host. <td> Optional.
+ * <tr><td> -o <td> The directory to output test results to. <td> Optional.
+ * <tr><td> -e <td> The type of test distribution engine to use. <td> Optional. One of: interop, fanout.
+ * <tr><td> ... <td> Free arguments. The distributed test cases to run.
+ * <td> Mandatory. At least one must be defined.
+ * <tr><td> name=value <td> Trailing argument define name/value pairs. Added to the test contenxt properties.
+ * <td> Optional.
+ * </table>
+ *
+ * @param args The command line arguments.
+ */
+ public static void main(String[] args)
+ {
+ NDC.push("coordinator");
+ log.debug("public static void main(String[] args = " + Arrays.toString(args) + "): called");
+ console.info("Qpid Distributed Test Coordinator.");
+
+ // Override the default broker url to be localhost:5672.
+ testContextProperties.setProperty(MessagingTestConfigProperties.BROKER_PROPNAME, "tcp://localhost:5672");
+
+ try
+ {
+ // Use the command line parser to evaluate the command line with standard handling behaviour (print errors
+ // and usage then exist if there are errors).
+ // Any options and trailing name=value pairs are also injected into the test context properties object,
+ // to override any defaults that may have been set up.
+ ParsedProperties options =
+ new ParsedProperties(CommandLineParser.processCommandLine(args,
+ new CommandLineParser(
+ new String[][]
+ {
+ { "b", "The broker URL.", "broker", "false" },
+ { "h", "The virtual host to use.", "virtual host", "false" },
+ { "o", "The name of the directory to output test timings to.", "dir", "false" },
+ {
+ "e", "The test execution engine to use. Default is interop.", "engine", "interop",
+ "^interop$|^fanout$", "true"
+ },
+ { "t", "Terminate test clients on completion of tests.", null, "false" },
+ { "-csv", "Output test results in CSV format.", null, "false" },
+ { "-xml", "Output test results in XML format.", null, "false" },
+ {
+ "-trefaddr", "To specify an alternative to hostname for time singal reference.",
+ "address", "false"
+ },
+ {
+ "c", "The number of tests to run concurrently.", "num", "false",
+ MathUtils.SEQUENCE_REGEXP
+ },
+ { "r", "The number of times to repeat each test.", "num", "false" },
+ {
+ "d", "The length of time to run the tests for.", "duration", "false",
+ MathUtils.DURATION_REGEXP
+ },
+ {
+ "f", "The maximum rate to call the tests at.", "frequency", "false",
+ "^([1-9][0-9]*)/([1-9][0-9]*)$"
+ },
+ { "s", "The size parameter to run tests with.", "size", "false", MathUtils.SEQUENCE_REGEXP },
+ { "v", "Verbose mode.", null, "false" },
+ { "n", "A name for this test run, used to name the output file.", "name", "true" },
+ {
+ "X:decorators", "A list of additional test decorators to wrap the tests in.",
+ "\"class.name[:class.name]*\"", "false"
+ }
+ }), testContextProperties));
+
+ // Extract the command line options.
+ String brokerUrl = options.getProperty("b");
+ String virtualHost = options.getProperty("h");
+ String reportDir = options.getProperty("o");
+ reportDir = (reportDir == null) ? "." : reportDir;
+ String testEngine = options.getProperty("e");
+ TestEngine engine = "fanout".equals(testEngine) ? TestEngine.FANOUT : TestEngine.INTEROP;
+ boolean terminate = options.getPropertyAsBoolean("t");
+ boolean csvResults = options.getPropertyAsBoolean("-csv");
+ boolean xmlResults = options.getPropertyAsBoolean("-xml");
+ String threadsString = options.getProperty("c");
+ Integer repetitions = options.getPropertyAsInteger("r");
+ String durationString = options.getProperty("d");
+ String paramsString = options.getProperty("s");
+ boolean verbose = options.getPropertyAsBoolean("v");
+ String testRunName = options.getProperty("n");
+ String decorators = options.getProperty("X:decorators");
+
+ int[] threads = (threadsString == null) ? null : MathUtils.parseSequence(threadsString);
+ int[] params = (paramsString == null) ? null : MathUtils.parseSequence(paramsString);
+ Long duration = (durationString == null) ? null : MathUtils.parseDuration(durationString);
+
+ // If broker or virtual host settings were specified as command line options, override the defaults in the
+ // test context properties with them.
+
+ // Collection all of the test cases to be run.
+ Collection<Class<? extends FrameworkBaseCase>> testCaseClasses =
+ new ArrayList<Class<? extends FrameworkBaseCase>>();
+
+ // Create a list of test decorator factories for use specified decorators to be applied.
+ List<TestDecoratorFactory> decoratorFactories = parseDecorators(decorators);
+
+ // Scan for available test cases using a classpath scanner.
+ // ClasspathScanner.getMatches(DistributedTestCase.class, "^Test.*", true);
+
+ // Hard code the test classes till the classpath scanner is fixed.
+ // Collections.addAll(testCaseClasses, InteropTestCase1DummyRun.class, InteropTestCase2BasicP2P.class,
+ // InteropTestCase3BasicPubSub.class);
+
+ // Parse all of the free arguments as test cases to run.
+ for (int i = 1; true; i++)
+ {
+ String nextFreeArg = options.getProperty(Integer.toString(i));
+
+ // Terminate the loop once all free arguments have been consumed.
+ if (nextFreeArg == null)
+ {
+ break;
+ }
+
+ try
+ {
+ Class nextClass = Class.forName(nextFreeArg);
+
+ if (FrameworkBaseCase.class.isAssignableFrom(nextClass))
+ {
+ testCaseClasses.add(nextClass);
+ console.info("Found distributed test case: " + nextFreeArg);
+ }
+ }
+ catch (ClassNotFoundException e)
+ {
+ console.info("Unable to instantiate the test case: " + nextFreeArg + ".");
+ }
+ }
+
+ // Check that some test classes were actually found.
+ if (testCaseClasses.isEmpty())
+ {
+ throw new RuntimeException(
+ "No test cases implementing FrameworkBaseCase were specified on the command line.");
+ }
+
+ // Extract the names of all the test classes, to pass to the start method.
+ int i = 0;
+ String[] testClassNames = new String[testCaseClasses.size()];
+
+ for (Class testClass : testCaseClasses)
+ {
+ testClassNames[i++] = testClass.getName();
+ }
+
+ // Create a coordinator and begin its test procedure.
+ Coordinator coordinator =
+ new Coordinator(repetitions, duration, threads, 0, params, null, reportDir, testRunName, verbose, brokerUrl,
+ virtualHost, engine, terminate, csvResults, xmlResults, decoratorFactories);
+
+ TestResult testResult = coordinator.start(testClassNames);
+
+ // Return different error codes, depending on whether or not there were test failures.
+ if (testResult.failureCount() > 0)
+ {
+ System.exit(FAILURE_EXIT);
+ }
+ else
+ {
+ System.exit(SUCCESS_EXIT);
+ }
+ }
+ catch (Exception e)
+ {
+ log.debug("Top level handler caught execption.", e);
+ console.info(e.getMessage());
+ e.printStackTrace();
+ System.exit(EXCEPTION_EXIT);
+ }
+ }
+
+ /**
+ * Starts all of the test classes to be run by this coordinator.
+ *
+ * @param testClassNames An array of all the coordinating test case implementations.
+ *
+ * @return A JUnit TestResult to run the tests with.
+ *
+ * @throws Exception Any underlying exceptions are allowed to fall through, and fail the test process.
+ */
+ public TestResult start(String[] testClassNames) throws Exception
+ {
+ log.debug("public TestResult start(String[] testClassNames = " + Arrays.toString(testClassNames) + ": called");
+
+ // Connect to the broker.
+ connection = TestUtils.createConnection(TestContextProperties.getInstance());
+ Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ Destination controlTopic = session.createTopic("iop.control");
+ Destination responseQueue = session.createQueue("coordinator");
+
+ conversationFactory = new ConversationFactory(connection, responseQueue, LinkedBlockingQueue.class);
+ ConversationFactory.Conversation conversation = conversationFactory.startConversation();
+
+ connection.start();
+
+ // Broadcast the compulsory invitation to find out what clients are available to test.
+ Message invite = session.createMessage();
+ invite.setStringProperty("CONTROL_TYPE", "INVITE");
+ invite.setJMSReplyTo(responseQueue);
+
+ conversation.send(controlTopic, invite);
+
+ // Wait for a short time, to give test clients an opportunity to reply to the invitation.
+ Collection<Message> enlists = conversation.receiveAll(0, 500);
+ enlistedClients = extractEnlists(enlists);
+
+ for (TestClientDetails client : enlistedClients)
+ {
+ log.debug("Got enlisted test client: " + client);
+ console.info("Test node " + client.clientName + " available.");
+ }
+
+ // Start the clock reference service running.
+ UDPClockReference clockReference = new UDPClockReference();
+ Thread clockRefThread = new Thread(clockReference);
+ registerShutdownHook(clockReference);
+ clockRefThread.start();
+
+ // Broadcast to all clients to synchronize their clocks against the coordinators clock reference.
+ Message clockSynchRequest = session.createMessage();
+ clockSynchRequest.setStringProperty("CONTROL_TYPE", "CLOCK_SYNCH");
+
+ String localAddress = InetAddress.getByName(InetAddress.getLocalHost().getHostName()).getHostAddress();
+ clockSynchRequest.setStringProperty("ADDRESS", localAddress);
+
+ conversation.send(controlTopic, clockSynchRequest);
+
+ // Run the test in the suite using JUnit.
+ TestResult result = null;
+
+ for (String testClassName : testClassNames)
+ {
+ // Record the current test class, so that the test results can be output to a file incorporating this name.
+ this.currentTestClassName = testClassName;
+
+ result = super.start(new String[] { testClassName });
+ }
+
+ // At this point in time, all tests have completed. Broadcast the shutdown message, if the termination option
+ // was set on the command line.
+ if (terminate)
+ {
+ Message terminate = session.createMessage();
+ terminate.setStringProperty("CONTROL_TYPE", "TERMINATE");
+
+ conversation.send(controlTopic, terminate);
+ }
+
+ return result;
+ }
+
+ /**
+ * For a collection of enlist messages, this method pulls out of the client details for the enlisting clients.
+ *
+ * @param enlists The enlist messages.
+ *
+ * @return A set of enlisting clients, extracted from the enlist messages.
+ *
+ * @throws JMSException Any underlying JMSException is allowed to fall through.
+ */
+ public static Set<TestClientDetails> extractEnlists(Collection<Message> enlists) throws JMSException
+ {
+ log.debug("public static Set<TestClientDetails> extractEnlists(Collection<Message> enlists = " + enlists
+ + "): called");
+
+ Set<TestClientDetails> enlistedClients = new HashSet<TestClientDetails>();
+
+ // Retain the list of all available clients.
+ for (Message enlist : enlists)
+ {
+ TestClientDetails clientDetails = new TestClientDetails();
+ clientDetails.clientName = enlist.getStringProperty("CLIENT_NAME");
+ clientDetails.privateControlKey = enlist.getStringProperty("CLIENT_PRIVATE_CONTROL_KEY");
+
+ String replyType = enlist.getStringProperty("CONTROL_TYPE");
+
+ if ("ENLIST".equals(replyType))
+ {
+ enlistedClients.add(clientDetails);
+ }
+ else if ("DECLINE".equals(replyType))
+ {
+ log.debug("Test client " + clientDetails.clientName + " declined the invite.");
+ }
+ else
+ {
+ log.warn("Got an unknown reply type, " + replyType + ", to the invite.");
+ }
+ }
+
+ return enlistedClients;
+ }
+
+ /**
+ * Runs a test or suite of tests, using the super class implemenation. This method wraps the test to be run
+ * in any test decorators needed to add in the coordinators ability to invite test clients to participate in
+ * tests.
+ *
+ * @param test The test to run.
+ * @param wait Undocumented. Nothing in the JUnit javadocs to say what this is for.
+ *
+ * @return The results of the test run.
+ */
+ public TestResult doRun(Test test, boolean wait)
+ {
+ log.debug("public TestResult doRun(Test \"" + test + "\", boolean " + wait + "): called");
+
+ // Wrap all tests in the test suite with WrappedSuiteTestDecorators. This is quite ugly and a bit baffling,
+ // but the reason it is done is because the JUnit implementation of TestDecorator has some bugs in it.
+ WrappedSuiteTestDecorator targetTest = null;
+
+ if (test instanceof TestSuite)
+ {
+ log.debug("targetTest is a TestSuite");
+
+ TestSuite suite = (TestSuite)test;
+
+ int numTests = suite.countTestCases();
+ log.debug("There are " + numTests + " in the suite.");
+
+ for (int i = 0; i < numTests; i++)
+ {
+ Test nextTest = suite.testAt(i);
+ log.debug("suite.testAt(" + i + ") = " + nextTest);
+
+ if (nextTest instanceof FrameworkBaseCase)
+ {
+ log.debug("nextTest is a FrameworkBaseCase");
+ }
+ }
+
+ targetTest = new WrappedSuiteTestDecorator(suite);
+ log.debug("Wrapped with a WrappedSuiteTestDecorator.");
+ }
+
+ // Apply any optional user specified decorators.
+ targetTest = applyOptionalUserDecorators(targetTest);
+
+ // Wrap the tests in a suitable distributed test decorator, to perform the invite/test cycle.
+ targetTest = newTestDecorator(targetTest, enlistedClients, conversationFactory, connection);
+
+ // TestSuite suite = new TestSuite();
+ // suite.addTest(targetTest);
+
+ // Wrap the tests in a scaled test decorator to them them as a 'batch' in one thread.
+ // targetTest = new ScaledTestDecorator(targetTest, new int[] { 1 });
+
+ return super.doRun(targetTest, wait);
+ }
+
+ /**
+ * Creates a wrapped test decorator, that is capable of inviting enlisted clients to participate in a specified
+ * test. This is the test engine that sets up the roles and sequences a distributed test case.
+ *
+ * @param targetTest The test decorator to wrap.
+ * @param enlistedClients The enlisted clients available to run the test.
+ * @param conversationFactory The conversation factory used to build conversation helper over the specified connection.
+ * @param connection The connection to talk to the enlisted clients over.
+ *
+ * @return An invititing test decorator, that invites all the enlisted clients to participate in tests, in pairs.
+ */
+ protected DistributedTestDecorator newTestDecorator(WrappedSuiteTestDecorator targetTest,
+ Set<TestClientDetails> enlistedClients, ConversationFactory conversationFactory, Connection connection)
+ {
+ switch (engine)
+ {
+ case FANOUT:
+ return new FanOutTestDecorator(targetTest, enlistedClients, conversationFactory, connection);
+ case INTEROP:
+ default:
+ return new InteropTestDecorator(targetTest, enlistedClients, conversationFactory, connection);
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/DistributedTestDecorator.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/DistributedTestDecorator.java
new file mode 100644
index 0000000000..bdcfc996d6
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/DistributedTestDecorator.java
@@ -0,0 +1,166 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.distributedtesting;
+
+import junit.framework.TestResult;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.test.framework.FrameworkBaseCase;
+import org.apache.qpid.test.framework.TestClientDetails;
+import org.apache.qpid.test.framework.sequencers.CircuitFactory;
+import org.apache.qpid.test.utils.ConversationFactory;
+
+import org.apache.qpid.junit.extensions.WrappedSuiteTestDecorator;
+
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.Message;
+
+import java.util.*;
+
+/**
+ * DistributedTestDecorator is a base class for writing test decorators that invite test clients to participate in
+ * distributed test cases. It provides a helper method, {@link #signupClients}, that broadcasts an invitation and
+ * returns the set of test clients that are available to particiapte in the test.
+ *
+ * <p/>When used to wrap a {@link FrameworkBaseCase} test, it replaces the default {@link CircuitFactory} implementations
+ * with a suitable circuit factory for distributed tests. Concrete implementations can use this to configure the sending
+ * and receiving roles on the test.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Broadcast test invitations and collect enlists. <td> {@link ConversationFactory}.
+ * </table>
+ */
+public abstract class DistributedTestDecorator extends WrappedSuiteTestDecorator
+{
+ /** Used for debugging. */
+ private static final Logger log = Logger.getLogger(DistributedTestDecorator.class);
+
+ /** Holds the contact information for all test clients that are available and that may take part in the test. */
+ Set<TestClientDetails> allClients;
+
+ /** Holds the conversation helper for the control level conversation for coordinating the test through. */
+ ConversationFactory conversationFactory;
+
+ /** Holds the connection that the control conversation is held over. */
+ Connection connection;
+
+ /** Holds the underlying test suite that this decorator wraps. */
+ WrappedSuiteTestDecorator testSuite;
+
+ /** Holds the control topic, on which test invitations are broadcast. */
+ protected Destination controlTopic;
+
+ /**
+ * Creates a wrapped suite test decorator from another one.
+ *
+ * @param suite The test suite.
+ * @param availableClients The list of all clients that responded to the compulsory invite.
+ * @param controlConversation The conversation helper for the control level, test coordination conversation.
+ * @param controlConnection The connection that the coordination messages are sent over.
+ */
+ public DistributedTestDecorator(WrappedSuiteTestDecorator suite, Set<TestClientDetails> availableClients,
+ ConversationFactory controlConversation, Connection controlConnection)
+ {
+ super(suite);
+
+ log.debug("public DistributedTestDecorator(WrappedSuiteTestDecorator suite, Set<TestClientDetails> allClients = "
+ + availableClients + ", ConversationHelper controlConversation = " + controlConversation + "): called");
+
+ testSuite = suite;
+ allClients = availableClients;
+ conversationFactory = controlConversation;
+ connection = controlConnection;
+
+ // Set up the test control topic.
+ try
+ {
+ controlTopic = conversationFactory.getSession().createTopic("iop.control");
+ }
+ catch (JMSException e)
+ {
+ throw new RuntimeException("Unable to create the coordinating control topic to broadcast test invites on.", e);
+ }
+ }
+
+ /**
+ * Should run all of the tests in the wrapped test suite.
+ *
+ * @param testResult The the results object to monitor the test results with.
+ */
+ public abstract void run(TestResult testResult);
+
+ /**
+ * Should provide the distributed test sequencer to pass to {@link org.apache.qpid.test.framework.FrameworkBaseCase}
+ * tests.
+ *
+ * @return A distributed test sequencer.
+ */
+ public abstract CircuitFactory getTestSequencer();
+
+ /**
+ * Broadcasts an invitation to participate in a coordinating test case to find out what clients are available to
+ * run the test case.
+ *
+ * @param coordTest The coordinating test case to broadcast an inviate for.
+ *
+ * @return A set of test clients that accepted the invitation.
+ */
+ protected Set<TestClientDetails> signupClients(FrameworkBaseCase coordTest)
+ {
+ // Broadcast the invitation to find out what clients are available to test.
+ Set<TestClientDetails> enlists;
+ try
+ {
+ Message invite = conversationFactory.getSession().createMessage();
+
+ ConversationFactory.Conversation conversation = conversationFactory.startConversation();
+
+ invite.setStringProperty("CONTROL_TYPE", "INVITE");
+ invite.setStringProperty("TEST_NAME", coordTest.getTestCaseNameForTestMethod(coordTest.getName()));
+
+ conversation.send(controlTopic, invite);
+
+ // Wait for a short time, to give test clients an opportunity to reply to the invitation.
+ Collection<Message> replies = conversation.receiveAll(allClients.size(), 500);
+ enlists = Coordinator.extractEnlists(replies);
+ }
+ catch (JMSException e)
+ {
+ throw new RuntimeException("There was a JMSException during the invite/enlist conversation.", e);
+ }
+
+ return enlists;
+ }
+
+ /**
+ * Prints a string summarizing this test decorator, mainly for debugging purposes.
+ *
+ * @return String representation for debugging purposes.
+ */
+ public String toString()
+ {
+ return "DistributedTestDecorator: [ testSuite = " + testSuite + " ]";
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/FanOutTestDecorator.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/FanOutTestDecorator.java
new file mode 100644
index 0000000000..eed9b1f290
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/FanOutTestDecorator.java
@@ -0,0 +1,244 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.distributedtesting;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.test.framework.DropInTest;
+import org.apache.qpid.test.framework.FrameworkBaseCase;
+import org.apache.qpid.test.framework.TestClientDetails;
+import org.apache.qpid.test.framework.sequencers.CircuitFactory;
+import org.apache.qpid.test.framework.sequencers.FanOutCircuitFactory;
+import org.apache.qpid.test.utils.ConversationFactory;
+
+import org.apache.qpid.junit.extensions.WrappedSuiteTestDecorator;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * FanOutTestDecorator is an {@link DistributedTestDecorator} that runs one test client in the sender role, and the remainder
+ * in the receivers role. It also has the capability to listen for new test cases joining the test beyond the initial start
+ * point. This feature can be usefull when experimenting with adding more load, in the form of more test clients, to assess
+ * its impact on a running test.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Execute coordinated test cases. <td> {@link FrameworkBaseCase}
+ * <tr><td> Accept test clients joining a running test.
+ * </table>
+ */
+public class FanOutTestDecorator extends DistributedTestDecorator implements MessageListener
+{
+ /** Used for debugging. */
+ private static final Logger log = Logger.getLogger(FanOutTestDecorator.class);
+
+ /** Holds the currently running test case. */
+ FrameworkBaseCase currentTest = null;
+
+ /**
+ * Creates a wrapped suite test decorator from another one.
+ *
+ * @param suite The test suite.
+ * @param availableClients The list of all clients that responded to the compulsory invite.
+ * @param controlConversation The conversation helper for the control level, test coordination conversation.
+ * @param controlConnection The connection that the coordination messages are sent over.
+ */
+ public FanOutTestDecorator(WrappedSuiteTestDecorator suite, Set<TestClientDetails> availableClients,
+ ConversationFactory controlConversation, Connection controlConnection)
+ {
+ super(suite, availableClients, controlConversation, controlConnection);
+
+ log.debug("public DistributedTestDecorator(WrappedSuiteTestDecorator suite, Set<TestClientDetails> allClients = "
+ + availableClients + ", ConversationHelper controlConversation = " + controlConversation + "): called");
+
+ testSuite = suite;
+ allClients = availableClients;
+ conversationFactory = controlConversation;
+ connection = controlConnection;
+
+ // Sign available clients up to the test.
+ for (Test test : getAllUnderlyingTests())
+ {
+ FrameworkBaseCase coordTest = (FrameworkBaseCase) test;
+
+ // Get all of the clients able to participate in the test.
+ Set<TestClientDetails> enlists = signupClients(coordTest);
+
+ // Check that there were some clients available.
+ if (enlists.size() == 0)
+ {
+ throw new RuntimeException("No clients to test with");
+ }
+
+ // Create a distributed test circuit factory for the test.
+ CircuitFactory circuitFactory = getTestSequencer();
+
+ // Set up the first client in the sender role, and the remainder in the receivers role.
+ Iterator<TestClientDetails> clients = enlists.iterator();
+ circuitFactory.setSender(clients.next());
+
+ while (clients.hasNext())
+ {
+ // Set the sending and receiving client details on the test case.
+ circuitFactory.setReceiver(clients.next());
+ }
+
+ // Pass down the connection to hold the coordinating conversation over.
+ circuitFactory.setConversationFactory(conversationFactory);
+
+ // If the current test case is a drop-in test, set it up as the currently running test for late joiners to
+ // add in to. Otherwise the current test field is set to null, to indicate that late joiners are not allowed.
+ currentTest = (coordTest instanceof DropInTest) ? coordTest : null;
+
+ // Execute the test case.
+ coordTest.setCircuitFactory(circuitFactory);
+ }
+ }
+
+ /**
+ * Broadcasts a test invitation and accepts enlists from participating clients. The wrapped test cases are run
+ * with one test client in the sender role, and the remaining test clients in the receiving role.
+ *
+ * <p/>Any JMSExceptions during the invite/enlist conversation will be allowed to fall through as runtime
+ * exceptions, resulting in the non-completion of the test run.
+ *
+ * @param testResult The the results object to monitor the test results with.
+ *
+ * @todo Better error recovery for failure of the invite/enlist conversation could be added.
+ */
+ public void run(TestResult testResult)
+ {
+ log.debug("public void run(TestResult testResult): called");
+
+ // Listen for late joiners on the control topic.
+ try
+ {
+ conversationFactory.getSession().createConsumer(controlTopic).setMessageListener(this);
+ }
+ catch (JMSException e)
+ {
+ throw new RuntimeException("Unable to set up the message listener on the control topic.", e);
+ }
+
+ // Run all of the test cases in the test suite.
+ /*for (Test test : getAllUnderlyingTests())
+ {
+ FrameworkBaseCase coordTest = (FrameworkBaseCase) test;
+
+ // Get all of the clients able to participate in the test.
+ Set<TestClientDetails> enlists = signupClients(coordTest);
+
+ // Check that there were some clients available.
+ if (enlists.size() == 0)
+ {
+ throw new RuntimeException("No clients to test with");
+ }
+
+ // Create a distributed test circuit factory for the test.
+ CircuitFactory circuitFactory = getTestSequencer();
+
+ // Set up the first client in the sender role, and the remainder in the receivers role.
+ Iterator<TestClientDetails> clients = enlists.iterator();
+ circuitFactory.setSender(clients.next());
+
+ while (clients.hasNext())
+ {
+ // Set the sending and receiving client details on the test case.
+ circuitFactory.setReceiver(clients.next());
+ }
+
+ // Pass down the connection to hold the coordinating conversation over.
+ circuitFactory.setConversationFactory(conversationFactory);
+
+ // If the current test case is a drop-in test, set it up as the currently running test for late joiners to
+ // add in to. Otherwise the current test field is set to null, to indicate that late joiners are not allowed.
+ currentTest = (coordTest instanceof DropInTest) ? coordTest : null;
+
+ // Execute the test case.
+ coordTest.setCircuitFactory(circuitFactory);
+ }*/
+
+ // Run all of the test cases in the test suite.
+ for (Test test : getAllUnderlyingTests())
+ {
+ FrameworkBaseCase coordTest = (FrameworkBaseCase) test;
+
+ coordTest.run(testResult);
+
+ currentTest = null;
+ }
+ }
+
+ /**
+ * Should provide the distributed test sequencer to pass to {@link org.apache.qpid.test.framework.FrameworkBaseCase}
+ * tests.
+ *
+ * @return A distributed test sequencer.
+ */
+ public CircuitFactory getTestSequencer()
+ {
+ return new FanOutCircuitFactory();
+ }
+
+ /**
+ * Listens to incoming messages on the control topic. If the messages are 'join' messages, signalling a new
+ * test client wishing to join the current test, then the new client will be added to the current test in the
+ * receivers role.
+ *
+ * @param message The incoming control message.
+ */
+ public void onMessage(Message message)
+ {
+ try
+ {
+ // Check if the message is from a test client attempting to join a running test, and join it to the current
+ // test case if so.
+ if (message.getStringProperty("CONTROL_TYPE").equals("JOIN") && (currentTest != null))
+ {
+ ((DropInTest) currentTest).lateJoin(message);
+ }
+ }
+ // There is not a lot can be done with this error, so it is deliberately ignored.
+ catch (JMSException e)
+ {
+ log.debug("Unable to process message:" + message);
+ }
+ }
+
+ /**
+ * Prints a string summarizing this test decorator, mainly for debugging purposes.
+ *
+ * @return String representation for debugging purposes.
+ */
+ public String toString()
+ {
+ return "FanOutTestDecorator: [ testSuite = " + testSuite + " ]";
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/InteropTestDecorator.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/InteropTestDecorator.java
new file mode 100644
index 0000000000..413d5558f2
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/InteropTestDecorator.java
@@ -0,0 +1,209 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.distributedtesting;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.test.framework.FrameworkBaseCase;
+import org.apache.qpid.test.framework.TestClientDetails;
+import org.apache.qpid.test.framework.sequencers.CircuitFactory;
+import org.apache.qpid.test.framework.sequencers.InteropCircuitFactory;
+import org.apache.qpid.test.utils.ConversationFactory;
+
+import org.apache.qpid.junit.extensions.WrappedSuiteTestDecorator;
+
+import javax.jms.Connection;
+
+import java.util.*;
+
+/**
+ * DistributedTestDecorator is a test decorator, written to implement the interop test specification. Given a list
+ * of enlisted test clients, that are available to run interop tests, this decorator invites them to participate
+ * in each test in the wrapped test suite. Amongst all the clients that respond to the invite, all pairs are formed,
+ * and each pairing (in both directions, but excluding the reflexive pairings) is split into a sender and receivers
+ * role and a test case run between them. Any enlisted combinations that do not accept a test invite are automatically
+ * failed.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Broadcast test invitations and collect enlists. <td> {@link org.apache.qpid.test.utils.ConversationFactory}.
+ * <tr><td> Output test failures for clients unwilling to run the test case. <td> {@link Coordinator}
+ * <tr><td> Execute distributed test cases. <td> {@link FrameworkBaseCase}
+ * <tr><td> Fail non-participating pairings. <td> {@link OptOutTestCase}
+ * </table>
+ */
+public class InteropTestDecorator extends DistributedTestDecorator
+{
+ /** Used for debugging. */
+ private static final Logger log = Logger.getLogger(InteropTestDecorator.class);
+
+ /**
+ * Creates a wrapped suite test decorator from another one.
+ *
+ * @param suite The test suite.
+ * @param availableClients The list of all clients that responded to the compulsory invite.
+ * @param controlConversation The conversation helper for the control level, test coordination conversation.
+ * @param controlConnection The connection that the coordination messages are sent over.
+ */
+ public InteropTestDecorator(WrappedSuiteTestDecorator suite, Set<TestClientDetails> availableClients,
+ ConversationFactory controlConversation, Connection controlConnection)
+ {
+ super(suite, availableClients, controlConversation, controlConnection);
+ }
+
+ /**
+ * Broadcasts a test invitation and accetps enlisting from participating clients. The wrapped test case is
+ * then repeated for every combination of test clients (provided the wrapped test case extends
+ * {@link FrameworkBaseCase}.
+ *
+ * <p/>Any JMSExceptions during the invite/enlist conversation will be allowed to fall through as runtime exceptions,
+ * resulting in the non-completion of the test run.
+ *
+ * @todo Better error recovery for failure of the invite/enlist conversation could be added.
+ *
+ * @param testResult The the results object to monitor the test results with.
+ */
+ public void run(TestResult testResult)
+ {
+ log.debug("public void run(TestResult testResult): called");
+
+ Collection<Test> tests = testSuite.getAllUnderlyingTests();
+
+ for (Test test : getAllUnderlyingTests())
+ {
+ FrameworkBaseCase coordTest = (FrameworkBaseCase) test;
+
+ // Broadcast the invitation to find out what clients are available to test.
+ Set<TestClientDetails> enlists = signupClients(coordTest);
+
+ // Compare the list of willing clients to the list of all available.
+ Set<TestClientDetails> optOuts = new HashSet<TestClientDetails>(allClients);
+ optOuts.removeAll(enlists);
+
+ // Output test failures for clients that will not particpate in the test.
+ Set<List<TestClientDetails>> failPairs = allPairs(optOuts, allClients);
+
+ for (List<TestClientDetails> failPair : failPairs)
+ {
+ // Create a distributed test circuit factory for the test.
+ CircuitFactory circuitFactory = getTestSequencer();
+
+ // Create an automatic failure test for the opted out test pair.
+ FrameworkBaseCase failTest = new OptOutTestCase("testOptOut");
+ circuitFactory.setSender(failPair.get(0));
+ circuitFactory.setReceiver(failPair.get(1));
+ failTest.setCircuitFactory(circuitFactory);
+
+ failTest.run(testResult);
+ }
+
+ // Loop over all combinations of clients, willing to run the test.
+ Set<List<TestClientDetails>> enlistedPairs = allPairs(enlists, enlists);
+
+ for (List<TestClientDetails> enlistedPair : enlistedPairs)
+ {
+ // Create a distributed test circuit factory for the test.
+ CircuitFactory circuitFactory = getTestSequencer();
+
+ // Set the sending and receiving client details on the test circuitFactory.
+ circuitFactory.setSender(enlistedPair.get(0));
+ circuitFactory.setReceiver(enlistedPair.get(1));
+
+ // Pass down the connection to hold the coordination conversation over.
+ circuitFactory.setConversationFactory(conversationFactory);
+
+ // Execute the test case.
+ coordTest.setCircuitFactory(circuitFactory);
+ coordTest.run(testResult);
+ }
+ }
+ }
+
+ /**
+ * Should provide the distributed test sequencer to pass to {@link org.apache.qpid.test.framework.FrameworkBaseCase}
+ * tests.
+ *
+ * @return A distributed test sequencer.
+ */
+ public CircuitFactory getTestSequencer()
+ {
+ return new InteropCircuitFactory();
+ }
+
+ /**
+ * Produces all pairs of combinations of elements from two sets. The ordering of the elements in the pair is
+ * important, that is the pair <l, r> is distinct from <r, l>; both pairs are generated. For any element, i, in
+ * both the left and right sets, the reflexive pair <i, i> is not generated.
+ *
+ * @param left The left set.
+ * @param right The right set.
+ * @param <E> The type of the content of the pairs.
+ *
+ * @return All pairs formed from the permutations of all elements of the left and right sets.
+ */
+ private <E> Set<List<E>> allPairs(Set<E> left, Set<E> right)
+ {
+ log.debug("private <E> Set<List<E>> allPairs(Set<E> left = " + left + ", Set<E> right = " + right + "): called");
+
+ Set<List<E>> results = new HashSet<List<E>>();
+
+ // Form all pairs from left to right.
+ // Form all pairs from right to left.
+ for (E le : left)
+ {
+ for (E re : right)
+ {
+ if (!le.equals(re))
+ {
+ results.add(new Pair<E>(le, re));
+ results.add(new Pair<E>(re, le));
+ }
+ }
+ }
+
+ log.debug("results = " + results);
+
+ return results;
+ }
+
+ /**
+ * A simple implementation of a pair, using a list.
+ */
+ private class Pair<T> extends ArrayList<T>
+ {
+ /**
+ * Creates a new pair of elements.
+ *
+ * @param first The first element.
+ * @param second The second element.
+ */
+ public Pair(T first, T second)
+ {
+ super();
+ super.add(first);
+ super.add(second);
+ }
+ }
+}
+
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/OptOutTestCase.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/OptOutTestCase.java
new file mode 100644
index 0000000000..008b89a981
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/OptOutTestCase.java
@@ -0,0 +1,69 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.distributedtesting;
+
+import org.apache.qpid.test.framework.sequencers.CircuitFactory;
+import org.apache.qpid.test.framework.FrameworkBaseCase;
+
+/**
+ * An OptOutTestCase is a test case that automatically fails. It is used when a list of test clients has been generated
+ * from a compulsory invite, but only some of those clients have responded to a specific test case invite. The clients
+ * that did not respond, may automatically be given a fail for some tests.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Fail the test with a suitable reason.
+ * </table>
+ */
+public class OptOutTestCase extends FrameworkBaseCase
+{
+ /**
+ * Creates a new coordinating test case with the specified name.
+ *
+ * @param name The test case name.
+ */
+ public OptOutTestCase(String name)
+ {
+ super(name);
+ }
+
+ /** Generates an appropriate test failure assertion. */
+ public void testOptOut()
+ {
+ CircuitFactory circuitFactory = getCircuitFactory();
+
+ fail("One of " + circuitFactory.getSender() + " and " + getCircuitFactory().getReceivers()
+ + " opted out of the test.");
+ }
+
+ /**
+ * Should provide a translation from the junit method name of a test to its test case name as defined in the
+ * interop testing specification. For example the method "testP2P" might map onto the interop test case name
+ * "TC2_BasicP2P".
+ *
+ * @param methodName The name of the JUnit test method.
+ * @return The name of the corresponding interop test case.
+ */
+ public String getTestCaseNameForTestMethod(String methodName)
+ {
+ return "OptOutTest";
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/TestClient.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/TestClient.java
new file mode 100644
index 0000000000..33770363ce
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/TestClient.java
@@ -0,0 +1,499 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.distributedtesting;
+
+import org.apache.log4j.Logger;
+import org.apache.log4j.NDC;
+
+import org.apache.qpid.test.framework.MessagingTestConfigProperties;
+import org.apache.qpid.test.framework.TestUtils;
+import org.apache.qpid.test.framework.clocksynch.ClockSynchThread;
+import org.apache.qpid.test.framework.clocksynch.UDPClockSynchronizer;
+import org.apache.qpid.test.utils.ReflectionUtils;
+import org.apache.qpid.test.utils.ReflectionUtilsException;
+
+import org.apache.qpid.junit.extensions.SleepThrottle;
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+import org.apache.qpid.junit.extensions.util.TestContextProperties;
+
+import javax.jms.*;
+
+import java.util.*;
+
+/**
+ * Implements a test client as described in the interop testing spec
+ * (http://cwiki.apache.org/confluence/display/qpid/Interop+Testing+Specification). A test client is an agent that
+ * reacts to control message sequences send by the test {@link Coordinator}.
+ *
+ * <p/><table><caption>Messages Handled by TestClient</caption>
+ * <tr><th> Message <th> Action
+ * <tr><td> Invite(compulsory) <td> Reply with Enlist.
+ * <tr><td> Invite(test case) <td> Reply with Enlist if test case available.
+ * <tr><td> AssignRole(test case) <td> Reply with Accept Role if matches an enlisted test. Keep test parameters.
+ * <tr><td> Start <td> Send test messages defined by test parameters. Send report on messages sent.
+ * <tr><td> Status Request <td> Send report on messages received.
+ * <tr><td> Terminate <td> Terminate the test client.
+ * <tr><td> ClockSynch <td> Synch clock against the supplied UDP address.
+ * </table>
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Handle all incoming control messages. <td> {@link TestClientControlledTest}
+ * <tr><td> Configure and look up test cases by name. <td> {@link TestClientControlledTest}
+ * </table>
+ */
+public class TestClient implements MessageListener
+{
+ /** Used for debugging. */
+ private static final Logger log = Logger.getLogger(TestClient.class);
+
+ /** Used for reporting to the console. */
+ private static final Logger console = Logger.getLogger("CONSOLE");
+
+ /** Holds the default identifying name of the test client. */
+ public static final String CLIENT_NAME = "java";
+
+ /**
+ * Holds the test context properties that provides the default test parameters, plus command line overrides.
+ * This is initialized with the default test parameters, to which command line overrides may be applied.
+ */
+ public static ParsedProperties testContextProperties =
+ TestContextProperties.getInstance(MessagingTestConfigProperties.defaults);
+
+ /** Holds all the test cases loaded from the classpath. */
+ Map<String, TestClientControlledTest> testCases = new HashMap<String, TestClientControlledTest>();
+
+ /** Holds the test case currently being run by this client. */
+ protected TestClientControlledTest currentTestCase;
+
+ /** Holds the connection to the broker that the test is being coordinated on. */
+ protected Connection connection;
+
+ /** Holds the message producer to hold the test coordination over. */
+ protected MessageProducer producer;
+
+ /** Holds the JMS controlSession for the test coordination. */
+ protected Session session;
+
+ /** Holds the name of this client, with a default value. */
+ protected String clientName = CLIENT_NAME;
+
+ /** This flag indicates that the test client should attempt to join the currently running test case on start up. */
+ protected boolean join;
+
+ /** Holds the clock synchronizer for the test node. */
+ ClockSynchThread clockSynchThread;
+
+ /**
+ * Creates a new interop test client, listenting to the specified broker and virtual host, with the specified client
+ * identifying name.
+ *
+ * @param pBrokerUrl The url of the broker to connect to.
+ * @param pVirtualHost The virtual host to conect to.
+ * @param clientName The client name to use.
+ * @param join Flag to indicate that this client should attempt to join running tests.
+ */
+ public TestClient(String pBrokerUrl, String pVirtualHost, String clientName, boolean join)
+ {
+ log.debug("public TestClient(String pBrokerUrl = " + pBrokerUrl + ", String pVirtualHost = " + pVirtualHost
+ + ", String clientName = " + clientName + ", boolean join = " + join + "): called");
+
+ // Retain the connection parameters.
+ this.clientName = clientName;
+ this.join = join;
+
+ // Save properies from command line to defaults
+ if (pBrokerUrl != null)
+ {
+ testContextProperties.setProperty(MessagingTestConfigProperties.BROKER_PROPNAME, pBrokerUrl);
+ }
+ if (pVirtualHost != null)
+ {
+ testContextProperties.setProperty(MessagingTestConfigProperties.VIRTUAL_HOST_PROPNAME, pVirtualHost);
+ }
+ }
+
+ /**
+ * The entry point for the interop test coordinator. This client accepts the following command line arguments:
+ *
+ * <p/><table>
+ * <tr><td> -b <td> The broker URL. <td> Optional.
+ * <tr><td> -h <td> The virtual host. <td> Optional.
+ * <tr><td> -n <td> The test client name. <td> Optional.
+ * <tr><td> name=value <td> Trailing argument define name/value pairs. Added to system properties. <td> Optional.
+ * </table>
+ *
+ * @param args The command line arguments.
+ */
+ public static void main(String[] args)
+ {
+ log.debug("public static void main(String[] args = " + Arrays.toString(args) + "): called");
+ console.info("Qpid Distributed Test Client.");
+
+ // Override the default broker url to be localhost:5672.
+ testContextProperties.setProperty(MessagingTestConfigProperties.BROKER_PROPNAME, "tcp://localhost:5672");
+
+ // Use the command line parser to evaluate the command line with standard handling behaviour (print errors
+ // and usage then exist if there are errors).
+ // Any options and trailing name=value pairs are also injected into the test context properties object,
+ // to override any defaults that may have been set up.
+ ParsedProperties options =
+ new ParsedProperties(org.apache.qpid.junit.extensions.util.CommandLineParser.processCommandLine(args,
+ new org.apache.qpid.junit.extensions.util.CommandLineParser(
+ new String[][]
+ {
+ { "b", "The broker URL.", "broker", "false" },
+ { "h", "The virtual host to use.", "virtual host", "false" },
+ { "o", "The name of the directory to output test timings to.", "dir", "false" },
+ { "n", "The name of the test client.", "name", "false" },
+ { "j", "Join this test client to running test.", "false" }
+ }), testContextProperties));
+
+ // Extract the command line options.
+ String brokerUrl = options.getProperty("b");
+ String virtualHost = options.getProperty("h");
+ String clientName = options.getProperty("n");
+ clientName = (clientName == null) ? CLIENT_NAME : clientName;
+ boolean join = options.getPropertyAsBoolean("j");
+
+ // To distinguish logging output set up an NDC on the client name.
+ NDC.push(clientName);
+
+ // Create a test client and start it running.
+ TestClient client = new TestClient(brokerUrl, virtualHost, clientName, join);
+
+ // Use a class path scanner to find all the interop test case implementations.
+ // Hard code the test classes till the classpath scanner is fixed.
+ Collection<Class<? extends TestClientControlledTest>> testCaseClasses =
+ new ArrayList<Class<? extends TestClientControlledTest>>();
+ // ClasspathScanner.getMatches(TestClientControlledTest.class, "^TestCase.*", true);
+ testCaseClasses.addAll(loadTestCases("org.apache.qpid.interop.clienttestcases.TestCase1DummyRun",
+ "org.apache.qpid.interop.clienttestcases.TestCase2BasicP2P",
+ "org.apache.qpid.interop.clienttestcases.TestCase3BasicPubSub",
+ "org.apache.qpid.interop.clienttestcases.TestCase4P2PMessageSize",
+ "org.apache.qpid.interop.clienttestcases.TestCase5PubSubMessageSize",
+ "org.apache.qpid.test.framework.distributedcircuit.TestClientCircuitEnd"));
+
+ try
+ {
+ client.start(testCaseClasses);
+ }
+ catch (Exception e)
+ {
+ log.error("The test client was unable to start.", e);
+ console.info(e.getMessage());
+ System.exit(1);
+ }
+ }
+
+ /**
+ * Parses a list of class names, and loads them if they are available on the class path.
+ *
+ * @param classNames The names of the classes to load.
+ *
+ * @return A list of the loaded test case classes.
+ */
+ public static List<Class<? extends TestClientControlledTest>> loadTestCases(String... classNames)
+ {
+ List<Class<? extends TestClientControlledTest>> testCases =
+ new LinkedList<Class<? extends TestClientControlledTest>>();
+
+ for (String className : classNames)
+ {
+ try
+ {
+ Class<?> cls = ReflectionUtils.forName(className);
+ testCases.add((Class<? extends TestClientControlledTest>) cls);
+ }
+ catch (ReflectionUtilsException e)
+ {
+ // Ignore, class could not be found, so test not available.
+ console.warn("Requested class " + className + " cannot be found, ignoring it.");
+ }
+ catch (ClassCastException e)
+ {
+ // Ignore, class was not of correct type to be a test case.
+ console.warn("Requested class " + className + " is not an instance of TestClientControlledTest.");
+ }
+ }
+
+ return testCases;
+ }
+
+ /**
+ * Starts the interop test client running. This causes it to start listening for incoming test invites.
+ *
+ * @param testCaseClasses The classes of the available test cases. The test case names from these are used to
+ * matchin incoming test invites against.
+ *
+ * @throws JMSException Any underlying JMSExceptions are allowed to fall through.
+ */
+ protected void start(Collection<Class<? extends TestClientControlledTest>> testCaseClasses) throws JMSException
+ {
+ log.debug("protected void start(Collection<Class<? extends TestClientControlledTest>> testCaseClasses = "
+ + testCaseClasses + "): called");
+
+ // Create all the test case implementations and index them by the test names.
+ for (Class<? extends TestClientControlledTest> nextClass : testCaseClasses)
+ {
+ try
+ {
+ TestClientControlledTest testCase = nextClass.newInstance();
+ testCases.put(testCase.getName(), testCase);
+ }
+ catch (InstantiationException e)
+ {
+ log.warn("Could not instantiate test case class: " + nextClass.getName(), e);
+ // Ignored.
+ }
+ catch (IllegalAccessException e)
+ {
+ log.warn("Could not instantiate test case class due to illegal access: " + nextClass.getName(), e);
+ // Ignored.
+ }
+ }
+
+ // Open a connection to communicate with the coordinator on.
+ connection = TestUtils.createConnection(testContextProperties);
+ session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ // Set this up to listen for control messages.
+ Topic privateControlTopic = session.createTopic("iop.control." + clientName);
+ MessageConsumer consumer = session.createConsumer(privateControlTopic);
+ consumer.setMessageListener(this);
+
+ Topic controlTopic = session.createTopic("iop.control");
+ MessageConsumer consumer2 = session.createConsumer(controlTopic);
+ consumer2.setMessageListener(this);
+
+ // Create a producer to send replies with.
+ producer = session.createProducer(null);
+
+ // If the join flag was set, then broadcast a join message to notify the coordinator that a new test client
+ // is available to join the current test case, if it supports it. This message may be ignored, or it may result
+ // in this test client receiving a test invite.
+ if (join)
+ {
+ Message joinMessage = session.createMessage();
+
+ joinMessage.setStringProperty("CONTROL_TYPE", "JOIN");
+ joinMessage.setStringProperty("CLIENT_NAME", clientName);
+ joinMessage.setStringProperty("CLIENT_PRIVATE_CONTROL_KEY", "iop.control." + clientName);
+ producer.send(controlTopic, joinMessage);
+ }
+
+ // Start listening for incoming control messages.
+ connection.start();
+ }
+
+ /**
+ * Handles all incoming control messages.
+ *
+ * @param message The incoming message.
+ */
+ public void onMessage(Message message)
+ {
+ NDC.push(clientName);
+ log.debug("public void onMessage(Message message = " + message + "): called");
+
+ try
+ {
+ String controlType = message.getStringProperty("CONTROL_TYPE");
+ String testName = message.getStringProperty("TEST_NAME");
+
+ log.debug("Received control of type '" + controlType + "' for the test '" + testName + "'");
+
+ // Check if the message is a test invite.
+ if ("INVITE".equals(controlType))
+ {
+ // Flag used to indicate that an enlist should be sent. Only enlist to compulsory invites or invites
+ // for which test cases exist.
+ boolean enlist = false;
+
+ if (testName != null)
+ {
+ log.debug("Got an invite to test: " + testName);
+
+ // Check if the requested test case is available.
+ TestClientControlledTest testCase = testCases.get(testName);
+
+ if (testCase != null)
+ {
+ log.debug("Found implementing class for test '" + testName + "', enlisting for it.");
+
+ // Check if the test case will accept the invitation.
+ enlist = testCase.acceptInvite(message);
+
+ log.debug("The test case "
+ + (enlist ? " accepted the invite, enlisting for it."
+ : " did not accept the invite, not enlisting."));
+
+ // Make the requested test case the current test case.
+ currentTestCase = testCase;
+ }
+ else
+ {
+ log.debug("Received an invite to the test '" + testName + "' but this test is not known.");
+ }
+ }
+ else
+ {
+ log.debug("Got a compulsory invite, enlisting for it.");
+
+ enlist = true;
+ }
+
+ if (enlist)
+ {
+ // Reply with the client name in an Enlist message.
+ Message enlistMessage = session.createMessage();
+ enlistMessage.setStringProperty("CONTROL_TYPE", "ENLIST");
+ enlistMessage.setStringProperty("CLIENT_NAME", clientName);
+ enlistMessage.setStringProperty("CLIENT_PRIVATE_CONTROL_KEY", "iop.control." + clientName);
+ enlistMessage.setJMSCorrelationID(message.getJMSCorrelationID());
+
+ log.debug("Sending enlist message '" + enlistMessage.getJMSMessageID() + "' to " + message.getJMSReplyTo());
+
+ producer.send(message.getJMSReplyTo(), enlistMessage);
+ }
+ else
+ {
+ // Reply with the client name in an Decline message.
+ Message enlistMessage = session.createMessage();
+ enlistMessage.setStringProperty("CONTROL_TYPE", "DECLINE");
+ enlistMessage.setStringProperty("CLIENT_NAME", clientName);
+ enlistMessage.setStringProperty("CLIENT_PRIVATE_CONTROL_KEY", "iop.control." + clientName);
+ enlistMessage.setJMSCorrelationID(message.getJMSCorrelationID());
+
+ log.debug("Sending decline message '" + enlistMessage.getJMSMessageID() + "' to " + message.getJMSReplyTo());
+
+ producer.send(message.getJMSReplyTo(), enlistMessage);
+ }
+ }
+ else if ("ASSIGN_ROLE".equals(controlType))
+ {
+ // Assign the role to the current test case.
+ String roleName = message.getStringProperty("ROLE");
+
+ log.debug("Got a role assignment to role: " + roleName);
+
+ TestClientControlledTest.Roles role = Enum.valueOf(TestClientControlledTest.Roles.class, roleName);
+
+ currentTestCase.assignRole(role, message);
+
+ // Reply by accepting the role in an Accept Role message.
+ Message acceptRoleMessage = session.createMessage();
+ acceptRoleMessage.setStringProperty("CLIENT_NAME", clientName);
+ acceptRoleMessage.setStringProperty("CONTROL_TYPE", "ACCEPT_ROLE");
+ acceptRoleMessage.setJMSCorrelationID(message.getJMSCorrelationID());
+
+ log.debug("Sending accept role message '" + acceptRoleMessage.getJMSMessageID() + "' to " + message.getJMSReplyTo());
+
+ producer.send(message.getJMSReplyTo(), acceptRoleMessage);
+ }
+ else if ("START".equals(controlType) || "STATUS_REQUEST".equals(controlType))
+ {
+ if ("START".equals(controlType))
+ {
+ log.debug("Got a start notification.");
+
+ // Extract the number of test messages to send from the start notification.
+ int numMessages;
+
+ try
+ {
+ numMessages = message.getIntProperty("MESSAGE_COUNT");
+ }
+ catch (NumberFormatException e)
+ {
+ // If the number of messages is not specified, use the default of one.
+ numMessages = 1;
+ }
+
+ // Start the current test case.
+ currentTestCase.start(numMessages);
+ }
+ else
+ {
+ log.debug("Got a status request.");
+ }
+
+ // Generate the report from the test case and reply with it as a Report message.
+ Message reportMessage = currentTestCase.getReport(session);
+ reportMessage.setStringProperty("CLIENT_NAME", clientName);
+ reportMessage.setStringProperty("CONTROL_TYPE", "REPORT");
+ reportMessage.setJMSCorrelationID(message.getJMSCorrelationID());
+
+ log.debug("Sending report message '" + reportMessage.getJMSMessageID() + "' to " + message.getJMSReplyTo());
+
+ producer.send(message.getJMSReplyTo(), reportMessage);
+ }
+ else if ("TERMINATE".equals(controlType))
+ {
+ console.info("Received termination instruction from coordinator.");
+
+ // Is a cleaner shutdown needed?
+ connection.close();
+ System.exit(0);
+ }
+ else if ("CLOCK_SYNCH".equals(controlType))
+ {
+ log.debug("Received clock synch command.");
+ String address = message.getStringProperty("ADDRESS");
+
+ log.debug("address = " + address);
+
+ // Re-create (if necessary) and start the clock synch thread to synch the clock every ten seconds.
+ if (clockSynchThread != null)
+ {
+ clockSynchThread.terminate();
+ }
+
+ SleepThrottle throttle = new SleepThrottle();
+ throttle.setRate(0.1f);
+
+ clockSynchThread = new ClockSynchThread(new UDPClockSynchronizer(address), throttle);
+ clockSynchThread.start();
+ }
+ else
+ {
+ // Log a warning about this but otherwise ignore it.
+ log.warn("Got an unknown control message, controlType = " + controlType + ", message = " + message);
+ }
+ }
+ catch (JMSException e)
+ {
+ // Log a warning about this, but otherwise ignore it.
+ log.warn("Got JMSException whilst handling message: " + message, e);
+ }
+ // Log any runtimes that fall through this message handler. These are fatal errors for the test client.
+ catch (RuntimeException e)
+ {
+ log.error("The test client message handler got an unhandled exception: ", e);
+ console.info("The message handler got an unhandled exception, terminating the test client.");
+ System.exit(1);
+ }
+ finally
+ {
+ NDC.pop();
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/TestClientControlledTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/TestClientControlledTest.java
new file mode 100644
index 0000000000..30fd382333
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/distributedtesting/TestClientControlledTest.java
@@ -0,0 +1,108 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.distributedtesting;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+import javax.jms.Session;
+
+/**
+ * TestClientControlledTest provides an interface that classes implementing test cases to run on a {@link TestClient}
+ * node can use. Implementations must be Java beans, that is, to provide a default constructor and to implement the
+ * {@link #getName} method.
+ *
+ * <p/>The methods specified in this interface are called when the {@link TestClient} receives control instructions to
+ * apply to the test. There are control instructions to present the test case with the test invite, so that it may
+ * choose whether or not to participate in the test, assign the test to play the sender or receiver role, start the
+ * test and obtain the test status report.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities
+ * <tr><td> Supply the name of the test case that this implements.
+ * <tr><td> Accept/Reject invites based on test parameters.
+ * <tr><td> Adapt to assigned roles.
+ * <tr><td> Perform test case actions.
+ * <tr><td> Generate test reports.
+ * </table>
+ */
+public interface TestClientControlledTest
+{
+ /** Defines the possible test case roles that an interop test case can take on. */
+ public enum Roles
+ {
+ /** Specifies the sender role. */
+ SENDER,
+
+ /** Specifies the receivers role. */
+ RECEIVER
+ }
+
+ /**
+ * Should provide the name of the test case that this class implements. The exact names are defined in the
+ * interop testing spec.
+ *
+ * @return The name of the test case that this implements.
+ */
+ public String getName();
+
+ /**
+ * Determines whether the test invite that matched this test case is acceptable.
+ *
+ * @param inviteMessage The invitation to accept or reject.
+ *
+ * @return <tt>true</tt> to accept the invitation, <tt>false</tt> to reject it.
+ *
+ * @throws JMSException Any JMSException resulting from reading the message are allowed to fall through.
+ */
+ public boolean acceptInvite(Message inviteMessage) throws JMSException;
+
+ /**
+ * Assigns the role to be played by this test case. The test parameters are fully specified in the
+ * assignment message. When this method return the test case will be ready to execute.
+ *
+ * @param role The role to be played; sender or receivers.
+ * @param assignRoleMessage The role assingment message, contains the full test parameters.
+ *
+ * @throws JMSException Any JMSException resulting from reading the message are allowed to fall through.
+ */
+ public void assignRole(Roles role, Message assignRoleMessage) throws JMSException;
+
+ /**
+ * Performs the test case actions. Returning from here, indicates that the sending role has completed its test.
+ *
+ * @param numMessages The number of test messages to send.
+ *
+ * @throws JMSException Any JMSException resulting from reading the message are allowed to fall through.
+ */
+ public void start(int numMessages) throws JMSException;
+
+ /**
+ * Gets a report on the actions performed by the test case in its assigned role.
+ *
+ * @param session The controlSession to create the report message in.
+ *
+ * @return The report message.
+ *
+ * @throws JMSException Any JMSExceptions resulting from creating the report are allowed to fall through.
+ */
+ public Message getReport(Session session) throws JMSException;
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/listeners/XMLTestListener.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/listeners/XMLTestListener.java
new file mode 100644
index 0000000000..c79029c99a
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/listeners/XMLTestListener.java
@@ -0,0 +1,409 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.listeners;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+import junit.framework.TestCase;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.junit.extensions.ShutdownHookable;
+import org.apache.qpid.junit.extensions.listeners.TKTestListener;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.*;
+
+/**
+ * Listens for test results for a named test and outputs these in the standard JUnit XML format to the specified
+ * writer.
+ *
+ * <p/>The API for this listener accepts notifications about different aspects of a tests results through different
+ * methods, so some assumption needs to be made as to which test result a notification refers to. For example
+ * {@link #startTest} will be called, then possibly {@link #timing} will be called, even though the test instance is
+ * passed in both cases, it is not enough to distinguish a particular run of the test, as the test case instance may
+ * be being shared between multiple threads, or being run a repeated number of times, and can therfore be re-used
+ * between calls. The listeners make the assumption that, for every test, a unique thread will call {@link #startTest}
+ * and {@link #endTest} to delimit each test. All calls to set test parameters, timings, state and so on, will occur
+ * between the start and end and will be given with the same thread id as the start and end, so the thread id provides
+ * a unqiue value to identify a particular test run against.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Listen to test lifecycle notifications.
+ * <tr><td> Listen to test errors and failures.
+ * <tr><td> Listen to test timings.
+ * <tr><td> Listen to test memory usages.
+ * <tr><td> Listen to parameterized test parameters.
+ * <tr><th> Responsibilities
+ * </table>
+ *
+ * @todo Merge this class with CSV test listener, making the collection of results common to both, and only factoring
+ * out the results printing code into sub-classes. Provide a simple XML results formatter with the same format as
+ * the ant XML formatter, and a more structured one for outputing results with timings and summaries from
+ * performance tests.
+ */
+public class XMLTestListener implements TKTestListener, ShutdownHookable
+{
+ /** Used for debugging. */
+ private static final Logger log = Logger.getLogger(XMLTestListener.class);
+
+ /** The results file writer. */
+ protected Writer writer;
+
+ /** Holds the results for individual tests. */
+ // protected Map<Result, Result> results = new LinkedHashMap<Result, Result>();
+ // protected List<Result> results = new ArrayList<Result>();
+
+ /**
+ * Map for holding results on a per thread basis as they come in. A ThreadLocal is not used as sometimes an
+ * explicit thread id must be used, where notifications come from different threads than the ones that called
+ * the test method.
+ */
+ Map<Long, Result> threadLocalResults = Collections.synchronizedMap(new LinkedHashMap<Long, Result>());
+
+ /**
+ * Holds results for tests that have ended. Transferring these results here from the per-thread results map, means
+ * that the thread id is freed for the thread to generate more results.
+ */
+ List<Result> results = new ArrayList<Result>();
+
+ /** Holds the overall error count. */
+ protected int errors = 0;
+
+ /** Holds the overall failure count. */
+ protected int failures = 0;
+
+ /** Holds the overall tests run count. */
+ protected int runs = 0;
+
+ /** Holds the name of the class that tests are being run for. */
+ String testClassName;
+
+ /**
+ * Creates a new XML results output listener that writes to the specified location.
+ *
+ * @param writer The location to write results to.
+ * @param testClassName The name of the test class to include in the test results.
+ */
+ public XMLTestListener(Writer writer, String testClassName)
+ {
+ log.debug("public XMLTestListener(Writer writer, String testClassName = " + testClassName + "): called");
+
+ this.writer = writer;
+ this.testClassName = testClassName;
+ }
+
+ /**
+ * Resets the test results to the default state of time zero, memory usage zero, parameter zero, test passed.
+ *
+ * @param test The test to resest any results for.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void reset(Test test, Long threadId)
+ {
+ log.debug("public void reset(Test test = " + test + ", Long threadId = " + threadId + "): called");
+
+ XMLTestListener.Result r =
+ (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId);
+
+ r.error = null;
+ r.failure = null;
+
+ }
+
+ /**
+ * Notification that a test started.
+ *
+ * @param test The test that started.
+ */
+ public void startTest(Test test)
+ {
+ log.debug("public void startTest(Test test = " + test + "): called");
+
+ Result newResult = new Result(test.getClass().getName(), ((TestCase) test).getName());
+
+ // Initialize the thread local test results.
+ threadLocalResults.put(Thread.currentThread().getId(), newResult);
+ runs++;
+ }
+
+ /**
+ * Should be called every time a test completes with the run time of that test.
+ *
+ * @param test The name of the test.
+ * @param nanos The run time of the test in nanoseconds.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void timing(Test test, long nanos, Long threadId)
+ { }
+
+ /**
+ * Optionally called every time a test completes with the second timing test.
+ *
+ * @param test The name of the test.
+ * @param nanos The second timing information of the test in nanoseconds.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void timing2(Test test, Long nanos, Long threadId)
+ { }
+
+ /**
+ * Should be called every time a test completed with the amount of memory used before and after the test was run.
+ *
+ * @param test The test which memory was measured for.
+ * @param memStart The total JVM memory used before the test was run.
+ * @param memEnd The total JVM memory used after the test was run.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void memoryUsed(Test test, long memStart, long memEnd, Long threadId)
+ { }
+
+ /**
+ * Should be called every time a parameterized test completed with the int value of its test parameter.
+ *
+ * @param test The test which memory was measured for.
+ * @param parameter The int parameter value.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void parameterValue(Test test, int parameter, Long threadId)
+ { }
+
+ /**
+ * Should be called every time a test completes with the current number of test threads running.
+ *
+ * @param test The test for which the measurement is being generated.
+ * @param threads The number of tests being run concurrently.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void concurrencyLevel(Test test, int threads, Long threadId)
+ { }
+
+ /**
+ * Notifies listeners of the tests read/set properties.
+ *
+ * @param properties The tests read/set properties.
+ */
+ public void properties(Properties properties)
+ { }
+
+ /**
+ * Notification that a test ended.
+ *
+ * @param test The test that ended.
+ */
+ public void endTest(Test test)
+ {
+ log.debug("public void endTest(Test test = " + test + "): called");
+
+ // Move complete test results into the completed tests list.
+ Result r = threadLocalResults.get(Thread.currentThread().getId());
+ results.add(r);
+
+ // Clear all the test results for the thread.
+ threadLocalResults.remove(Thread.currentThread().getId());
+ }
+
+ /**
+ * Called when a test completes. Success, failure and errors. This method should be used when registering an
+ * end test from a different thread than the one that started the test.
+ *
+ * @param test The test which completed.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void endTest(Test test, Long threadId)
+ {
+ log.debug("public void endTest(Test test = " + test + ", Long threadId = " + threadId + "): called");
+
+ // Move complete test results into the completed tests list.
+ Result r =
+ (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId);
+ results.add(r);
+
+ // Clear all the test results for the thread.
+ threadLocalResults.remove(Thread.currentThread().getId());
+ }
+
+ /**
+ * An error occurred.
+ *
+ * @param test The test in which the error occurred.
+ * @param t The throwable that resulted from the error.
+ */
+ public void addError(Test test, Throwable t)
+ {
+ log.debug("public void addError(Test test = " + test + ", Throwable t = " + t + "): called");
+
+ Result r = threadLocalResults.get(Thread.currentThread().getId());
+ r.error = t;
+ errors++;
+ }
+
+ /**
+ * A failure occurred.
+ *
+ * @param test The test in which the failure occurred.
+ * @param t The JUnit assertions that led to the failure.
+ */
+ public void addFailure(Test test, AssertionFailedError t)
+ {
+ log.debug("public void addFailure(Test test = " + test + ", AssertionFailedError t = " + t + "): called");
+
+ Result r = threadLocalResults.get(Thread.currentThread().getId());
+ r.failure = t;
+ failures++;
+ }
+
+ /**
+ * Called when a test completes to mark it as a test fail. This method should be used when registering a
+ * failure from a different thread than the one that started the test.
+ *
+ * @param test The test which failed.
+ * @param e The assertion that failed the test.
+ * @param threadId Optional thread id if not calling from thread that started the test method. May be null.
+ */
+ public void addFailure(Test test, AssertionFailedError e, Long threadId)
+ {
+ log.debug("public void addFailure(Test test, AssertionFailedError e, Long threadId): called");
+
+ Result r =
+ (threadId == null) ? threadLocalResults.get(Thread.currentThread().getId()) : threadLocalResults.get(threadId);
+ r.failure = e;
+ failures++;
+ }
+
+ /**
+ * Notifies listeners of the start of a complete run of tests.
+ */
+ public void startBatch()
+ {
+ log.debug("public void startBatch(): called");
+
+ // Reset all results counts.
+ threadLocalResults = Collections.synchronizedMap(new HashMap<Long, Result>());
+ errors = 0;
+ failures = 0;
+ runs = 0;
+
+ // Write out the file header.
+ try
+ {
+ writer.write("<?xml version=\"1.0\" ?>\n");
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("Unable to write the test results.", e);
+ }
+ }
+
+ /**
+ * Notifies listeners of the end of a complete run of tests.
+ *
+ * @param parameters The optional test parameters to log out with the batch results.
+ */
+ public void endBatch(Properties parameters)
+ {
+ log.debug("public void endBatch(Properties parameters = " + parameters + "): called");
+
+ // Write out the results.
+ try
+ {
+ // writer.write("<?xml version=\"1.0\" ?>\n");
+ writer.write("<testsuite errors=\"" + errors + "\" failures=\"" + failures + "\" tests=\"" + runs + "\" name=\""
+ + testClassName + "\">\n");
+
+ for (Result result : results)
+ {
+ writer.write(" <testcase classname=\"" + result.testClass + "\" name=\"" + result.testName + "\">\n");
+
+ if (result.error != null)
+ {
+ writer.write(" <error type=\"" + result.error.getClass() + "\">");
+ result.error.printStackTrace(new PrintWriter(writer));
+ writer.write(" </error>");
+ }
+ else if (result.failure != null)
+ {
+ writer.write(" <failure type=\"" + result.failure.getClass() + "\">");
+ result.failure.printStackTrace(new PrintWriter(writer));
+ writer.write(" </failure>");
+ }
+
+ writer.write(" </testcase>\n");
+ }
+
+ writer.write("</testsuite>\n");
+ writer.flush();
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException("Unable to write the test results.", e);
+ }
+ }
+
+ /**
+ * Supplies the shutdown hook.
+ *
+ * @return The shut down hook.
+ */
+ public Thread getShutdownHook()
+ {
+ return new Thread(new Runnable()
+ {
+ public void run()
+ {
+ log.debug("XMLTestListener::ShutdownHook: called");
+ }
+ });
+ }
+
+ /**
+ * Used to capture the results of a particular test run.
+ */
+ protected static class Result
+ {
+ /** Holds the name of the test class. */
+ public String testClass;
+
+ /** Holds the name of the test method. */
+ public String testName;
+
+ /** Holds the exception that caused error in this test. */
+ public Throwable error;
+
+ /** Holds the assertion exception that caused failure in this test. */
+ public AssertionFailedError failure;
+
+ /**
+ * Creates a placeholder for the results of a test.
+ *
+ * @param testClass The test class.
+ * @param testName The name of the test that was run.
+ */
+ public Result(String testClass, String testName)
+ {
+ this.testClass = testClass;
+ this.testName = testName;
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalAMQPPublisherImpl.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalAMQPPublisherImpl.java
new file mode 100644
index 0000000000..4388c7fbd8
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalAMQPPublisherImpl.java
@@ -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.
+ *
+ */
+package org.apache.qpid.test.framework.localcircuit;
+
+import org.apache.qpid.client.AMQNoConsumersException;
+import org.apache.qpid.client.AMQNoRouteException;
+import org.apache.qpid.test.framework.*;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+
+/**
+ * LocalAMQPPublisherImpl is an extension of {@link LocalPublisherImpl} that adds AMQP specific features. Specifically
+ * extra assertions for AMQP features not available through generic JMS.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td>
+ * </table>
+ */
+public class LocalAMQPPublisherImpl extends LocalPublisherImpl implements AMQPPublisher
+{
+ /**
+ * Creates a circuit end point on the specified producer, consumer and controlSession. Monitors are also configured
+ * for messages and exceptions received by the circuit end.
+ *
+ * @param producer The message producer for the circuit end point.
+ * @param consumer The message consumer for the circuit end point.
+ * @param session The controlSession for the circuit end point.
+ * @param messageMonitor The monitor to notify of all messages received by the circuit end.
+ * @param exceptionMonitor The monitor to notify of all exceptions received by the circuit end.
+ */
+ public LocalAMQPPublisherImpl(MessageProducer producer, MessageConsumer consumer, Session session,
+ MessageMonitor messageMonitor, ExceptionMonitor exceptionMonitor)
+ {
+ super(producer, consumer, session, messageMonitor, exceptionMonitor);
+ }
+
+ /**
+ * Creates a circuit end point from the producer, consumer and controlSession in a circuit end base implementation.
+ *
+ * @param end The circuit end base implementation to take producers and consumers from.
+ */
+ public LocalAMQPPublisherImpl(CircuitEndBase end)
+ {
+ super(end);
+ }
+
+ /**
+ * Provides an assertion that the publisher got a no consumers exception on every message.
+ *
+ * @param testProps The test configuration properties.
+ *
+ * @return An assertion that the publisher got a no consumers exception on every message.
+ */
+ public Assertion noConsumersAssertion(ParsedProperties testProps)
+ {
+ return new AssertionBase()
+ {
+ public boolean apply()
+ {
+ boolean passed = true;
+ ExceptionMonitor connectionExceptionMonitor = circuit.getConnectionExceptionMonitor();
+
+ if (!connectionExceptionMonitor.assertOneJMSExceptionWithLinkedCause(AMQNoConsumersException.class))
+ {
+ passed = false;
+
+ addError("Was expecting linked exception type " + AMQNoConsumersException.class.getName()
+ + " on the connection.\n");
+ addError((connectionExceptionMonitor.size() > 0)
+ ? ("Actually got the following exceptions on the connection, " + connectionExceptionMonitor)
+ : "Got no exceptions on the connection.");
+ }
+
+ return passed;
+ }
+ };
+ }
+
+ /**
+ * Provides an assertion that the publisher got a no rout exception on every message.
+ *
+ * @param testProps The test configuration properties.
+ *
+ * @return An assertion that the publisher got a no rout exception on every message.
+ */
+ public Assertion noRouteAssertion(ParsedProperties testProps)
+ {
+ return new AssertionBase()
+ {
+ public boolean apply()
+ {
+ boolean passed = true;
+ ExceptionMonitor connectionExceptionMonitor = circuit.getConnectionExceptionMonitor();
+
+ if (!connectionExceptionMonitor.assertOneJMSExceptionWithLinkedCause(AMQNoRouteException.class))
+ {
+ passed = false;
+
+ addError("Was expecting linked exception type " + AMQNoRouteException.class.getName()
+ + " on the connection.\n");
+ addError((connectionExceptionMonitor.size() > 0)
+ ? ("Actually got the following exceptions on the connection, " + connectionExceptionMonitor)
+ : "Got no exceptions on the connection.");
+ }
+
+ return passed;
+ }
+ };
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalCircuitImpl.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalCircuitImpl.java
new file mode 100644
index 0000000000..391091266c
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalCircuitImpl.java
@@ -0,0 +1,306 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.localcircuit;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.test.framework.*;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+import javax.jms.*;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * LocalCircuitImpl provides an implementation of the test circuit. This is a local only circuit implementation that
+ * supports a single producer/consumer on each end of the circuit, with both ends of the circuit on the same JVM.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Supply the publishing and receiving ends of a test messaging circuit.
+ * <td> {@link LocalPublisherImpl}, {@link LocalReceiverImpl}
+ * <tr><td> Start the circuit running.
+ * <tr><td> Close the circuit down.
+ * <tr><td> Take a reading of the circuits state.
+ * <tr><td> Apply assertions against the circuits state. <td> {@link Assertion}
+ * <tr><td> Send test messages over the circuit.
+ * <tr><td> Perform the default test procedure on the circuit.
+ * <tr><td> Provide access to connection and controlSession exception monitors. <td> {@link ExceptionMonitor}
+ * </table>
+ */
+public class LocalCircuitImpl implements Circuit
+{
+ /** Used for debugging. */
+ private static final Logger log = Logger.getLogger(LocalCircuitImpl.class);
+
+ /** Holds the test configuration for the circuit. */
+ private ParsedProperties testProps;
+
+ /** Holds the publishing end of the circuit. */
+ private LocalPublisherImpl publisher;
+
+ /** Holds the receiving end of the circuit. */
+ private LocalReceiverImpl receiver;
+
+ /** Holds the connection for the publishing end of the circuit. */
+ private Connection connection;
+
+ /** Holds the exception listener for the connection on the publishing end of the circuit. */
+ private ExceptionMonitor connectionExceptionMonitor;
+
+ /** Holds the exception listener for the controlSession on the publishing end of the circuit. */
+ private ExceptionMonitor exceptionMonitor;
+
+ /**
+ * Creates a test circuit using the specified test parameters. The publisher, receivers, connection and
+ * connection monitor must already have been created, to assemble the circuit.
+ *
+ * @param testProps The test parameters.
+ * @param publisher The test publisher.
+ * @param receiver The test receivers.
+ * @param connection The connection.
+ * @param connectionExceptionMonitor The connection exception monitor.
+ */
+ public LocalCircuitImpl(ParsedProperties testProps, LocalPublisherImpl publisher, LocalReceiverImpl receiver,
+ Connection connection, ExceptionMonitor connectionExceptionMonitor)
+ {
+ this.testProps = testProps;
+ this.publisher = publisher;
+ this.receiver = receiver;
+ this.connection = connection;
+ this.connectionExceptionMonitor = connectionExceptionMonitor;
+ this.exceptionMonitor = new ExceptionMonitor();
+
+ // Set this as the parent circuit on the publisher and receivers.
+ publisher.setCircuit(this);
+ receiver.setCircuit(this);
+ }
+
+ /**
+ * Gets the interface on the publishing end of the circuit.
+ *
+ * @return The publishing end of the circuit.
+ */
+ public Publisher getPublisher()
+ {
+ return publisher;
+ }
+
+ /**
+ * Gets the local publishing circuit end, for direct manipulation.
+ *
+ * @return The local publishing circuit end.
+ */
+ public CircuitEnd getLocalPublisherCircuitEnd()
+ {
+ return publisher;
+ }
+
+ /**
+ * Gets the interface on the receiving end of the circuit.
+ *
+ * @return The receiving end of the circuit.
+ */
+ public Receiver getReceiver()
+ {
+ return receiver;
+ }
+
+ /**
+ * Gets the local receiving circuit end, for direct manipulation.
+ *
+ * @return The local receiving circuit end.
+ */
+ public CircuitEnd getLocalReceiverCircuitEnd()
+ {
+ return receiver;
+ }
+
+ /**
+ * Checks the test circuit. The effect of this is to gather the circuits state, for both ends of the circuit,
+ * into a report, against which assertions may be checked.
+ */
+ public void check()
+ { }
+
+ /**
+ * Applied a list of assertions against the test circuit. The {@link #check()} method should be called before doing
+ * this, to ensure that the circuit has gathered its state into a report to assert against.
+ *
+ * @param assertions The list of assertions to apply.
+ * @return Any assertions that failed.
+ */
+ public List<Assertion> applyAssertions(List<Assertion> assertions)
+ {
+ List<Assertion> failures = new LinkedList<Assertion>();
+
+ for (Assertion assertion : assertions)
+ {
+ if (!assertion.apply())
+ {
+ failures.add(assertion);
+ }
+ }
+
+ return failures;
+ }
+
+ /**
+ * Connects and starts the circuit. After this method is called the circuit is ready to send messages.
+ */
+ public void start()
+ { }
+
+ /**
+ * Closes the circuit. All associated resources are closed.
+ */
+ public void close()
+ {
+ try
+ {
+ publisher.close();
+ receiver.close();
+ connection.close();
+ }
+ catch (JMSException e)
+ {
+ throw new RuntimeException("Got JMSException during close:" + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Sends a message on the test circuit. The exact nature of the message sent is controlled by the test parameters.
+ */
+ protected void send()
+ {
+ // Cast the test properties into a typed interface for convenience.
+ MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps);
+
+ boolean transactional = props.getPublisherTransacted();
+ boolean rollback = props.getRollbackPublisher();
+
+ // Send a message through the publisher and log any exceptions raised.
+ try
+ {
+ CircuitEnd end = getLocalPublisherCircuitEnd();
+
+ end.send(createTestMessage(end));
+
+ if (rollback)
+ {
+ end.getSession().rollback();
+ }
+ else if (transactional)
+ {
+ end.getSession().commit();
+ }
+ }
+ catch (JMSException e)
+ {
+ exceptionMonitor.onException(e);
+ }
+ }
+
+ /**
+ * Runs the default test procedure against the circuit, and checks that all of the specified assertions hold. The
+ * outline of the default test procedure is:
+ *
+ * <p/><pre>
+ * Start the circuit.
+ * Send test messages.
+ * Request a status report.
+ * Assert conditions on the publishing end of the circuit.
+ * Assert conditions on the receiving end of the circuit.
+ * Close the circuit.
+ * Pass with no failed assertions or fail with a list of failed assertions.
+ * </pre>
+ *
+ * @param numMessages The number of messages to send using the default test procedure.
+ * @param assertions The list of assertions to apply.
+ * @return Any assertions that failed.
+ */
+ public List<Assertion> test(int numMessages, List<Assertion> assertions)
+ {
+ // Start the test circuit.
+ start();
+
+ // Send the requested number of test messages.
+ for (int i = 0; i < numMessages; i++)
+ {
+ send();
+ }
+
+ // Inject a short pause to allow time for exceptions to come back asynchronously.
+ TestUtils.pause(500L);
+
+ // Request a status report.
+ check();
+
+ // Clean up the publisher/receivers/controlSession/connections.
+ close();
+
+ // Apply all of the requested assertions, keeping record of any that fail.
+ List<Assertion> failures = applyAssertions(assertions);
+
+ // Return any failed assertions to the caller.
+ return failures;
+ }
+
+ /**
+ * Creates a message with the properties defined as per the test parameters.
+ *
+ * @param client The circuit end to create the message on.
+ *
+ * @return The test message.
+ *
+ * @throws JMSException Any JMSException occurring during creation of the message is allowed to fall through.
+ */
+ private Message createTestMessage(CircuitEnd client) throws JMSException
+ {
+ // Cast the test properties into a typed interface for convenience.
+ MessagingTestConfigProperties props = new MessagingTestConfigProperties(testProps);
+
+ return TestUtils.createTestMessageOfSize(client.getSession(), props.getMessageSize());
+ }
+
+ /**
+ * Gets the exception monitor for the publishing ends connection.
+ *
+ * @return The exception monitor for the publishing ends connection.
+ */
+ public ExceptionMonitor getConnectionExceptionMonitor()
+ {
+ return connectionExceptionMonitor;
+ }
+
+ /**
+ * Gets the exception monitor for the publishing ends controlSession.
+ *
+ * @return The exception monitor for the publishing ends controlSession.
+ */
+ public ExceptionMonitor getExceptionMonitor()
+ {
+ return exceptionMonitor;
+ }
+}
+
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalPublisherImpl.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalPublisherImpl.java
new file mode 100644
index 0000000000..3ec3f62538
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalPublisherImpl.java
@@ -0,0 +1,170 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.localcircuit;
+
+import org.apache.qpid.test.framework.*;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+
+/**
+ * Provides an implementation of the {@link Publisher} interface and wraps a single message producer and consumer on
+ * a single controlSession, as a {@link CircuitEnd}. A local publisher also acts as a circuit end, because for a locally
+ * located circuit the assertions may be applied directly, there does not need to be any inter-process messaging
+ * between the publisher and its single circuit end, in order to ascertain its status.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Provide a message producer for sending messages.
+ * <tr><td> Provide a message consumer for receiving messages.
+ * <tr><td> Provide assertion that the publisher received no exceptions.
+ * <tr><td> Provide assertion that the publisher received a no consumers error code.
+ * <tr><td> Provide assertion that the publisher received a no route error code.
+ * </table>
+ */
+public class LocalPublisherImpl extends CircuitEndBase implements Publisher
+{
+ /** Holds a reference to the containing circuit. */
+ protected LocalCircuitImpl circuit;
+
+ /**
+ * Creates a circuit end point on the specified producer, consumer and controlSession. Monitors are also configured
+ * for messages and exceptions received by the circuit end.
+ *
+ * @param producer The message producer for the circuit end point.
+ * @param consumer The message consumer for the circuit end point.
+ * @param session The controlSession for the circuit end point.
+ * @param messageMonitor The monitor to notify of all messages received by the circuit end.
+ * @param exceptionMonitor The monitor to notify of all exceptions received by the circuit end.
+ */
+ public LocalPublisherImpl(MessageProducer producer, MessageConsumer consumer, Session session,
+ MessageMonitor messageMonitor, ExceptionMonitor exceptionMonitor)
+ {
+ super(producer, consumer, session, messageMonitor, exceptionMonitor);
+ }
+
+ /**
+ * Creates a circuit end point from the producer, consumer and controlSession in a circuit end base implementation.
+ *
+ * @param end The circuit end base implementation to take producers and consumers from.
+ */
+ public LocalPublisherImpl(CircuitEndBase end)
+ {
+ super(end.getProducer(), end.getConsumer(), end.getSession(), end.getMessageMonitor(), end.getExceptionMonitor());
+ }
+
+ /**
+ * Provides an assertion that the publisher encountered no exceptions.
+ *
+ * @param testProps
+ *
+ * @return An assertion that the publisher encountered no exceptions.
+ */
+ public Assertion noExceptionsAssertion(ParsedProperties testProps)
+ {
+ return new AssertionBase()
+ {
+ public boolean apply()
+ {
+ boolean passed = true;
+ ExceptionMonitor sessionExceptionMonitor = circuit.getExceptionMonitor();
+ ExceptionMonitor connectionExceptionMonitor = circuit.getConnectionExceptionMonitor();
+
+ if (!connectionExceptionMonitor.assertNoExceptions())
+ {
+ passed = false;
+
+ addError("Was expecting no exceptions.\n");
+ addError("Got the following exceptions on the connection, "
+ + circuit.getConnectionExceptionMonitor());
+ }
+
+ if (!sessionExceptionMonitor.assertNoExceptions())
+ {
+ passed = false;
+
+ addError("Was expecting no exceptions.\n");
+ addError("Got the following exceptions on the producer, " + circuit.getExceptionMonitor());
+ }
+
+ return passed;
+ }
+ };
+ }
+
+ /**
+ * Provides an assertion that the AMQP channel was forcibly closed by an error condition.
+ *
+ * @param testProps The test configuration properties.
+ *
+ * @return An assertion that the AMQP channel was forcibly closed by an error condition.
+ */
+ public Assertion channelClosedAssertion(ParsedProperties testProps)
+ {
+ return new NotApplicableAssertion(testProps);
+ }
+
+ /**
+ * Provides an assertion that the publisher got a given exception during the test.
+ *
+ * @param testProps The test configuration properties.
+ * @param exceptionClass The exception class to check for.
+ *
+ * @return An assertion that the publisher got a given exception during the test.
+ */
+ public Assertion exceptionAssertion(ParsedProperties testProps, final Class<? extends Exception> exceptionClass)
+ {
+ return new AssertionBase()
+ {
+ public boolean apply()
+ {
+ boolean passed = true;
+ ExceptionMonitor connectionExceptionMonitor = circuit.getConnectionExceptionMonitor();
+
+ if (!connectionExceptionMonitor.assertExceptionOfType(exceptionClass))
+ {
+ passed = false;
+
+ addError("Was expecting linked exception type " + exceptionClass.getName()
+ + " on the connection.\n");
+ addError((connectionExceptionMonitor.size() > 0)
+ ? ("Actually got the following exceptions on the connection, " + connectionExceptionMonitor)
+ : "Got no exceptions on the connection.");
+ }
+
+ return passed;
+ }
+ };
+ }
+
+ /**
+ * Sets the contianing circuit.
+ *
+ * @param circuit The containing circuit.
+ */
+ public void setCircuit(LocalCircuitImpl circuit)
+ {
+ this.circuit = circuit;
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalReceiverImpl.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalReceiverImpl.java
new file mode 100644
index 0000000000..74f414c974
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/localcircuit/LocalReceiverImpl.java
@@ -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.
+ *
+ */
+package org.apache.qpid.test.framework.localcircuit;
+
+import org.apache.qpid.test.framework.*;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+
+/**
+ * Provides an implementation of the {@link Receiver} interface that wraps a single message producer and consumer on
+ * a single controlSession, as a {@link CircuitEnd}. A local receiver also acts as a circuit end, because for a locally
+ * located circuit the assertions may be applied directly, there does not need to be any inter process messaging
+ * between the publisher and its single circuit end, in order to ascertain its status.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Provide a message producer for sending messages.
+ * <tr><td> Provide a message consumer for receiving messages.
+ * <tr><td> Provide assertion that the receivers received no exceptions.
+ * <tr><td> Provide assertion that the receivers received all test messages sent to it.
+ * </table>
+ */
+public class LocalReceiverImpl extends CircuitEndBase implements Receiver
+{
+ /** Holds a reference to the containing circuit. */
+ private LocalCircuitImpl circuit;
+
+ /**
+ * Creates a circuit end point on the specified producer, consumer and controlSession. Monitors are also configured
+ * for messages and exceptions received by the circuit end.
+ *
+ * @param producer The message producer for the circuit end point.
+ * @param consumer The message consumer for the circuit end point.
+ * @param session The controlSession for the circuit end point.
+ * @param messageMonitor The monitor to notify of all messages received by the circuit end.
+ * @param exceptionMonitor The monitor to notify of all exceptions received by the circuit end.
+ */
+ public LocalReceiverImpl(MessageProducer producer, MessageConsumer consumer, Session session,
+ MessageMonitor messageMonitor, ExceptionMonitor exceptionMonitor)
+ {
+ super(producer, consumer, session, messageMonitor, exceptionMonitor);
+ }
+
+ /**
+ * Creates a circuit end point from the producer, consumer and controlSession in a circuit end base implementation.
+ *
+ * @param end The circuit end base implementation to take producers and consumers from.
+ */
+ public LocalReceiverImpl(CircuitEndBase end)
+ {
+ super(end.getProducer(), end.getConsumer(), end.getSession(), end.getMessageMonitor(), end.getExceptionMonitor());
+ }
+
+ /**
+ * Provides an assertion that the receivers encountered no exceptions.
+ *
+ * @param testProps The test configuration properties.
+ *
+ * @return An assertion that the receivers encountered no exceptions.
+ */
+ public Assertion noExceptionsAssertion(ParsedProperties testProps)
+ {
+ return new NotApplicableAssertion(testProps);
+ }
+
+ /**
+ * Provides an assertion that the AMQP channel was forcibly closed by an error condition.
+ *
+ * @param testProps The test configuration properties.
+ *
+ * @return An assertion that the AMQP channel was forcibly closed by an error condition.
+ */
+ public Assertion channelClosedAssertion(ParsedProperties testProps)
+ {
+ return new NotApplicableAssertion(testProps);
+ }
+
+ /**
+ * Provides an assertion that the receivers got all messages that were sent to it.
+ *
+ * @param testProps The test configuration properties.
+ *
+ * @return An assertion that the receivers got all messages that were sent to it.
+ */
+ public Assertion allMessagesReceivedAssertion(ParsedProperties testProps)
+ {
+ return new NotApplicableAssertion(testProps);
+ }
+
+ /**
+ * Provides an assertion that the receivers got none of the messages that were sent to it.
+ *
+ * @param testProps The test configuration properties.
+ *
+ * @return An assertion that the receivers got none of the messages that were sent to it.
+ */
+ public Assertion noMessagesReceivedAssertion(ParsedProperties testProps)
+ {
+ return new NotApplicableAssertion(testProps);
+ }
+
+ /**
+ * Provides an assertion that the receiver got a given exception during the test.
+ *
+ * @param testProps The test configuration properties.
+ * @param exceptionClass The exception class to check for. @return An assertion that the receiver got a given exception during the test.
+ */
+ public Assertion exceptionAssertion(ParsedProperties testProps, Class<? extends Exception> exceptionClass)
+ {
+ return new NotApplicableAssertion(testProps);
+ }
+
+ /**
+ * Sets the contianing circuit.
+ *
+ * @param circuit The containing circuit.
+ */
+ public void setCircuit(LocalCircuitImpl circuit)
+ {
+ this.circuit = circuit;
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/package.html b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/package.html
new file mode 100644
index 0000000000..ac4e30d312
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/package.html
@@ -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.
+
+-->
+
+<html>
+<body>
+<p/>A framework for testing Qpid, built around a standard 'test circuit' design. The idea behind this framework is the
+use of a test circuit which is configured by a set of test parameters, that may be projected onto a topology of
+test nodes, with tests scripted to run over test circuits, making as few assumptions as possible about the underlying
+topology. The standardization of the design, whilst limiting in some respectes, allows a large variety of test
+scenarios to be written with minimal amounts of coding.
+
+<p/>The standard consruction block for a test, is a test circuit. This consists of a publisher, and a receiver. The
+publisher and receiver may reside on the same machine, or may be distributed. Will use a standard set of properties to
+define the desired circuit topology.
+
+<p/>Tests are always to be controlled from the publishing side only. The receiving end of the circuit is to be exposed
+to the test code through an interface, that abstracts as much as possible the receiving end of the test. The interface
+exposes a set of 'assertions' that may be applied to the receiving end of the test circuit.
+
+<p/>In the case where the receiving end of the circuit resides on the same JVM, the assertions will call the receivers
+code locally. Where the receiving end is distributed accross one or more machines, the assertions will be applied to a
+test report gethered from all of the receivers. Test code will be written to the assertions making as few assumptions
+as possible about the exact test topology.
+</body>
+</html>
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/AMQPFeatureDecorator.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/AMQPFeatureDecorator.java
new file mode 100644
index 0000000000..c11f75e742
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/AMQPFeatureDecorator.java
@@ -0,0 +1,96 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.qpid;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+
+import org.apache.qpid.test.framework.FrameworkBaseCase;
+import org.apache.qpid.test.framework.LocalAMQPCircuitFactory;
+
+import org.apache.qpid.junit.extensions.WrappedSuiteTestDecorator;
+
+/**
+ * AMQPFeatureDecorator applies decorations to {@link FrameworkBaseCase} tests, so that they may use Qpid/AMQP specific
+ * features, not available through JMS. For example, the immediate and mandatory flags. This decorator replaces the
+ * standard test circuit factory on the base class with one that allows these features to be used.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Substitute the circuit factory with an AMQP/Qpid specific one.
+ * </table>
+ *
+ * @todo This wrapper substitutes in a LocalAMQPCircuitFactory, which is fine for local tests. For distributed tests
+ * the Fanout or Interop factories are substituted in by their decorators instead. These actually use
+ * distributed circuit static create methods to build the circuits, which should actually be changed to a factory,
+ * so that static methods do not need to be used. The distributed circuit creater delegates the circuit
+ * construction to remote test nodes. This decorator should not be used with distributed tests, or should be made
+ * aware of them, in which case it might ensure that an AMQP feature (implied already by other properties) flag
+ * is passed out to the remote test nodes, and provide a mechansim for them to decorate their circuit creation
+ * with AMQP features too. Add factory substituion/decoration mechansim for test clients, here or in a seperate
+ * class.
+ */
+public class AMQPFeatureDecorator extends WrappedSuiteTestDecorator
+{
+ /** The test suite to run. */
+ private Test test;
+
+ /**
+ * Creates a wrapped test test decorator from another one.
+ *
+ * @param test The test test.
+ */
+ public AMQPFeatureDecorator(WrappedSuiteTestDecorator test)
+ {
+ super(test);
+ this.test = test;
+ }
+
+ /**
+ * Runs the tests with a LocalAMQPCircuitFactory. Only tests that extend FrameworkBaseCase are decorated.
+ *
+ * @param testResult The the results object to monitor the test results with.
+ */
+ public void run(TestResult testResult)
+ {
+ for (Test test : getAllUnderlyingTests())
+ {
+ if (test instanceof FrameworkBaseCase)
+ {
+ FrameworkBaseCase frameworkTest = (FrameworkBaseCase) test;
+ frameworkTest.setCircuitFactory(new LocalAMQPCircuitFactory());
+ }
+ }
+
+ // Run the test.
+ test.run(testResult);
+ }
+
+ /**
+ * Prints the name of the test for debugging purposes.
+ *
+ * @return The name of the test.
+ */
+ public String toString()
+ {
+ return "AMQPFeatureDecorator: [test = \"" + test + "\"]";
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/CauseFailureDecorator.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/CauseFailureDecorator.java
new file mode 100644
index 0000000000..2708253d86
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/CauseFailureDecorator.java
@@ -0,0 +1,95 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.qpid;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+
+import org.apache.qpid.test.framework.BrokerLifecycleAware;
+import org.apache.qpid.test.framework.CauseFailureUserPrompt;
+
+import org.apache.qpid.junit.extensions.WrappedSuiteTestDecorator;
+
+/**
+ * CauseFailureDecorator applies decorations to {@link BrokerLifecycleAware} tests, so that they may use different failure
+ * mechanisms. It is capable of detecting when a test case uses in-vm brokers, and setting up an automatic failure
+ * for those tests, so that the current live broker can be shut-down by test cases. For external brokers, automatic
+ * failure could be implemented, for example by having a kill script. At the moment this sets up the failure to prompt
+ * a user interactively to cause a failure, using {@link CauseFailureUserPrompt}.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Setup automatic failures for in-vm brokers. <td> {@link CauseFailureInVM}
+ * <tr><td> Setup user generated failures for external brokers. <td> {@link CauseFailureUserPrompt}.
+ * <tr><td>
+ * </table>
+ *
+ * @todo Slight problem in that CauseFailureInVM is Qpid specific, whereas CauseFailureUserPrompt is not. Would like the
+ * failure decorator to be non-qpid specific so that it can test failure of any JMS implementation too. Either pass
+ * in class name of failure mechanism, set it up in the in-vm decorator instead of here but with prompt user as the
+ * default for when the in-vm decorator is not used?
+ */
+public class CauseFailureDecorator extends WrappedSuiteTestDecorator
+{
+ /** The test suite to run. */
+ private Test test;
+
+ /**
+ * Creates a wrapped test test decorator from another one.
+ *
+ * @param test The test test.
+ */
+ public CauseFailureDecorator(WrappedSuiteTestDecorator test)
+ {
+ super(test);
+ this.test = test;
+ }
+
+ /**
+ * Runs the tests with a LocalAMQPCircuitFactory. Only tests that extend FrameworkBaseCase are decorated.
+ *
+ * @param testResult The the results object to monitor the test results with.
+ */
+ public void run(TestResult testResult)
+ {
+ for (Test test : getAllUnderlyingTests())
+ {
+ if (test instanceof BrokerLifecycleAware)
+ {
+ BrokerLifecycleAware failureTest = (BrokerLifecycleAware) test;
+ failureTest.setFailureMechanism(new CauseFailureUserPrompt());
+ }
+ }
+
+ // Run the test.
+ test.run(testResult);
+ }
+
+ /**
+ * Prints the name of the test for debugging purposes.
+ *
+ * @return The name of the test.
+ */
+ public String toString()
+ {
+ return "CauseFailureDecorator: [test = \"" + test + "\"]";
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/CauseFailureInVM.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/CauseFailureInVM.java
new file mode 100644
index 0000000000..3e03ad0872
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/CauseFailureInVM.java
@@ -0,0 +1,70 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.qpid;
+
+import org.apache.qpid.client.transport.TransportConnection;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.test.framework.CauseFailure;
+import org.apache.qpid.test.framework.BrokerLifecycleAware;
+
+/**
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Cause messaging broker failure on the active in-vm broker.
+ * <td> {@link TransportConnection}, {@link ApplicationRegistry}
+ * </table>
+ */
+public class CauseFailureInVM implements CauseFailure
+{
+ /** Holds the in-vm broker instrumented test case to create failures for. */
+ private BrokerLifecycleAware inVMTest;
+
+ /**
+ * Creates an automated failure mechanism for testing against in-vm brokers. The test to create the mechanism
+ * for is specified, and as this failure is for in-vm brokers, the test must be {@link org.apache.qpid.test.framework.BrokerLifecycleAware}. The test
+ * must also report that it is currently being run against an in-vm broker, and it is a runtime error if it is not,
+ * as the creator of this failure mechanism should already have checked that it is.
+ *
+ * @param inVMTest The test case to create an automated failure mechanism for.
+ */
+ public CauseFailureInVM(BrokerLifecycleAware inVMTest)
+ {
+ // Check that the test is really using in-vm brokers.
+ if (!inVMTest.usingInVmBroker())
+ {
+ throw new RuntimeException(
+ "Cannot create in-vm broker failure mechanism for a test that is not using in-vm brokers.");
+ }
+
+ this.inVMTest = inVMTest;
+ }
+
+ /**
+ * Causes the active message broker to fail.
+ */
+ public void causeFailure()
+ {
+ int liveBroker = inVMTest.getLiveBroker();
+
+ TransportConnection.killVMBroker(liveBroker);
+ ApplicationRegistry.remove(liveBroker);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/InVMBrokerDecorator.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/InVMBrokerDecorator.java
new file mode 100644
index 0000000000..b92a72a654
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/qpid/InVMBrokerDecorator.java
@@ -0,0 +1,136 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.qpid;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+
+import org.apache.qpid.client.transport.TransportConnection;
+import org.apache.qpid.client.vmbroker.AMQVMBrokerCreationException;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.test.framework.BrokerLifecycleAware;
+import org.apache.qpid.test.framework.FrameworkBaseCase;
+
+import org.apache.qpid.junit.extensions.SetupTaskAware;
+import org.apache.qpid.junit.extensions.WrappedSuiteTestDecorator;
+
+/**
+ * InVMBrokerDecorator is a test decorator, that is activated when running tests against an in-vm broker only. Its
+ * purpose is to automatically create, and close and delete an in-vm broker, during the set-up and tear-down of
+ * each test case. This decorator may only be used in conjunction with tests that extend {@link FrameworkBaseCase}.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Create/Destroy an in-vm broker on every test run.
+ * </table>
+ *
+ * @todo May need to add a more fine grained injection point for the in-vm broker management, as this acts at the
+ * suite level, rather than the individual test level.
+ *
+ * @todo Management of in-vm brokers for failure testing. Failure test setups may need to set their connection url to
+ * use multiple broker (vm://:1;vm://:2), with fail-over between them. There is round-robin fail-over, but also
+ * retry? A test case using an in-vm broker needs to record which one it is using, so that it can be
+ * killed/restarted.
+ */
+public class InVMBrokerDecorator extends WrappedSuiteTestDecorator
+{
+ /** The test suite to run. */
+ private Test test;
+
+ /**
+ * Creates a wrapped test suite decorator from another one.
+ *
+ * @param test The test suite.
+ */
+ public InVMBrokerDecorator(WrappedSuiteTestDecorator test)
+ {
+ super(test);
+ this.test = test;
+ }
+
+ /**
+ * Runs the tests with in-vm broker creation and clean-up added to the tests task stack.
+ *
+ * @param testResult The the results object to monitor the test results with.
+ */
+ public void run(TestResult testResult)
+ {
+ for (Test test : getAllUnderlyingTests())
+ {
+ // Check that the test to have an in-vm broker setup/teardown task added to it, is actually a framework
+ // test that can handle setup tasks.
+ if ((test instanceof SetupTaskAware))
+ {
+ SetupTaskAware frameworkTest = (SetupTaskAware) test;
+
+ frameworkTest.chainSetupTask(new Runnable()
+ {
+ public void run()
+ {
+ // Ensure that the in-vm broker is created.
+ try
+ {
+ ApplicationRegistry.getInstance(1);
+ TransportConnection.createVMBroker(1);
+ }
+ catch (AMQVMBrokerCreationException e)
+ {
+ throw new RuntimeException("In-VM broker creation failed: " + e.getMessage(), e);
+ }
+ }
+ });
+
+ frameworkTest.chainTearDownTask(new Runnable()
+ {
+ public void run()
+ {
+ // Ensure that the in-vm broker is cleaned up so that the next test starts afresh.
+ TransportConnection.killVMBroker(1);
+ ApplicationRegistry.remove(1);
+ }
+ });
+
+ // Check if the test is aware whether or not it can control the broker life cycle, and if so provide
+ // additional instrumentation for it to control the in-vm broker through.
+ if (test instanceof BrokerLifecycleAware)
+ {
+ BrokerLifecycleAware inVMTest = (BrokerLifecycleAware) test;
+ inVMTest.setInVmBrokers();
+ inVMTest.setLiveBroker(1);
+ inVMTest.setFailureMechanism(new CauseFailureInVM(inVMTest));
+ }
+ }
+ }
+
+ // Run the test.
+ test.run(testResult);
+ }
+
+ /**
+ * Prints the name of the test for debugging purposes.
+ *
+ * @return The name of the test.
+ */
+ public String toString()
+ {
+ return "InVMBrokerDecorator: [test = " + test + "]";
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/BaseCircuitFactory.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/BaseCircuitFactory.java
new file mode 100644
index 0000000000..bd27fc3d90
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/BaseCircuitFactory.java
@@ -0,0 +1,136 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.sequencers;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.test.framework.Circuit;
+import org.apache.qpid.test.framework.TestClientDetails;
+import org.apache.qpid.test.utils.ConversationFactory;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * BaseCircuitFactory provides some functionality common to all {@link CircuitFactory}s, such as the details of
+ * all {@link org.apache.qpid.test.framework.distributedtesting.TestClient}s that make up the end-points of
+ * the circuits that the factory creates, and an active {@link ConversationFactory} that can be used to generate
+ * control conversations with those circuit end-points.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Hold the details of the sending and receiving end-points to create circuits from.
+ * <tr><td> Provide a conversation factory to create control conversations with the end-points.
+ * </table>
+ */
+public abstract class BaseCircuitFactory implements CircuitFactory
+{
+
+ /** Used for debugging. */
+ private final Logger log = Logger.getLogger(BaseCircuitFactory.class);
+
+ /** Holds the contact details for the sending test client. */
+ protected TestClientDetails sender;
+
+ /** Holds the contact details for the receving test client. */
+ protected List<TestClientDetails> receivers = new LinkedList<TestClientDetails>();
+
+ /** Holds the conversation factory over which to coordinate the test. */
+ protected ConversationFactory conversationFactory;
+
+ /**
+ * Creates a test circuit for the test, configered by the test parameters specified.
+ *
+ * @param testProperties The test parameters.
+ * @return A test circuit.
+ */
+ public Circuit createCircuit(Properties testProperties)
+ {
+ throw new RuntimeException("Not implemented.");
+ }
+
+ /**
+ * Sets the sender test client to coordinate the test with.
+ *
+ * @param sender The contact details of the sending client in the test.
+ */
+ public void setSender(TestClientDetails sender)
+ {
+ log.debug("public void setSender(TestClientDetails sender = " + sender + "): called");
+
+ this.sender = sender;
+ }
+
+ /**
+ * Sets the receiving test client to coordinate the test with.
+ *
+ * @param receiver The contact details of the sending client in the test.
+ */
+ public void setReceiver(TestClientDetails receiver)
+ {
+ log.debug("public void setReceiver(TestClientDetails receivers = " + receiver + "): called");
+
+ this.receivers.add(receiver);
+ }
+
+ /**
+ * Supplies the sending test client.
+ *
+ * @return The sending test client.
+ */
+ public TestClientDetails getSender()
+ {
+ return sender;
+ }
+
+ /**
+ * Supplies the receiving test client.
+ *
+ * @return The receiving test client.
+ */
+ public List<TestClientDetails> getReceivers()
+ {
+ return receivers;
+ }
+
+ /**
+ * Accepts the conversation factory over which to hold the test coordinating conversation.
+ *
+ * @param conversationFactory The conversation factory to coordinate the test over.
+ */
+ public void setConversationFactory(ConversationFactory conversationFactory)
+ {
+ this.conversationFactory = conversationFactory;
+ }
+
+ /**
+ * Provides the conversation factory for providing the distributed test sequencing conversations over the test
+ * connection.
+ *
+ * @return The conversation factory to create test sequencing conversations with.
+ */
+ public ConversationFactory getConversationFactory()
+ {
+ return conversationFactory;
+ }
+}
+
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/CircuitFactory.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/CircuitFactory.java
new file mode 100644
index 0000000000..e69952918d
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/CircuitFactory.java
@@ -0,0 +1,102 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.sequencers;
+
+import org.apache.qpid.test.framework.Assertion;
+import org.apache.qpid.test.framework.Circuit;
+import org.apache.qpid.test.framework.TestClientDetails;
+import org.apache.qpid.test.utils.ConversationFactory;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+import javax.jms.Connection;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * A CircuitFactory is responsibile for creating test circuits appropriate to the context that a test case is
+ * running in, and providing an implementation of a standard test procedure over a test circuit.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities
+ * <tr><td> Provide a standard test procedure over a test circuit.
+ * <tr><td> Construct test circuits appropriate to a tests context.
+ * </table>
+ */
+public interface CircuitFactory
+{
+ /**
+ * Holds a test coordinating conversation with the test clients. This should consist of assigning the test roles,
+ * begining the test, gathering the test reports from the participants, and checking for assertion failures against
+ * the test reports.
+ *
+ * @param testCircuit The test circuit.
+ * @param assertions The list of assertions to apply to the test circuit.
+ * @param testProperties The test case definition.
+ *
+ * @deprecated Use test circuits and Circuit.test instead.
+ */
+ public void sequenceTest(Circuit testCircuit, List<Assertion> assertions, Properties testProperties);
+
+ /**
+ * Creates a test circuit for the test, configered by the test parameters specified.
+ *
+ * @param testProperties The test parameters.
+ *
+ * @return A test circuit.
+ */
+ public Circuit createCircuit(Connection connection, ParsedProperties testProperties);
+
+ /**
+ * Sets the sender test client to coordinate the test with.
+ *
+ * @param sender The contact details of the sending client in the test.
+ */
+ public void setSender(TestClientDetails sender);
+
+ /**
+ * Sets the receiving test client to coordinate the test with.
+ *
+ * @param receiver The contact details of the sending client in the test.
+ */
+ public void setReceiver(TestClientDetails receiver);
+
+ /**
+ * Supplies the sending test client.
+ *
+ * @return The sending test client.
+ */
+ public TestClientDetails getSender();
+
+ /**
+ * Supplies the receiving test client.
+ *
+ * @return The receiving test client.
+ */
+ public List<TestClientDetails> getReceivers();
+
+ /**
+ * Accepts the conversation factory over which to hold the test coordinating conversation.
+ *
+ * @param conversationFactory The conversation factory to coordinate the test over.
+ */
+ public void setConversationFactory(ConversationFactory conversationFactory);
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/FanOutCircuitFactory.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/FanOutCircuitFactory.java
new file mode 100644
index 0000000000..8a9c48d8e7
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/FanOutCircuitFactory.java
@@ -0,0 +1,198 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.sequencers;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.test.framework.Assertion;
+import org.apache.qpid.test.framework.Circuit;
+import org.apache.qpid.test.framework.TestClientDetails;
+import org.apache.qpid.test.framework.TestUtils;
+import org.apache.qpid.test.framework.distributedcircuit.DistributedCircuitImpl;
+import org.apache.qpid.test.utils.ConversationFactory;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+import javax.jms.*;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * FanOutCircuitFactory is a circuit factory that creates distributed test circuits. Given a set of participating
+ * test client nodes, it assigns one node to the SENDER role and the remainder to the RECEIVER role.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Create distributed circuits from one to many test nodes, for fanout style testing.
+ * </table>
+ *
+ * @todo Adapt this to be an n*m topology circuit factory. Need to add circuit topology definitions to the test
+ * parameters. Place n senders onto the available test clients, and m receivers. Where n or m is larger than
+ * the available nodes, start stacking multiple test clients on each node. There will also be an option that
+ * indicates whether nodes can play both roles, and how many nodes out of all available may be assigned to
+ * each role.
+ *
+ * @todo The createCircuit methods on this and InteropCircuitFactory are going to be identical. This is because the
+ * partitioning into senders and receivers is already done by the test decorators. Either eliminate these factories
+ * as unnesesary, or move the partitioning functionality into the factories, in which case the test decorators
+ * can probably be merged or eliminated. There is confusion over the placement of responsibilities between the
+ * factories and the test decorators... although the test decorators may well do more than just circuit creation
+ * in the future. For example, there may have to be a special decorator for test repetition that does one circuit
+ * creation, but the runs many tests over it, in which case the handling of responsibilities becomes clearer.
+ */
+public class FanOutCircuitFactory extends BaseCircuitFactory
+{
+ /** Used for debugging. */
+ Logger log = Logger.getLogger(FanOutCircuitFactory.class);
+
+ /**
+ * Creates a test circuit for the test, configered by the test parameters specified.
+ *
+ * @param testProperties The test parameters.
+ * @return A test circuit.
+ */
+ public Circuit createCircuit(Connection connection, ParsedProperties testProperties)
+ {
+ log.debug("public Circuit createCircuit(ParsedProperties testProperties): called");
+
+ List<TestClientDetails> senders = new LinkedList<TestClientDetails>();
+ senders.add(getSender());
+ List<TestClientDetails> receivers = getReceivers();
+ ConversationFactory conversationFactory = getConversationFactory();
+
+ return DistributedCircuitImpl.createCircuit(testProperties, senders, receivers, conversationFactory);
+ }
+
+ /**
+ * Holds a test coordinating conversation with the test clients. This should consist of assigning the test roles,
+ * begining the test, gathering the test reports from the participants, and checking for assertion failures against
+ * the test reports.
+ *
+ * @param testCircuit The test circuit.
+ * @param assertions The list of assertions to apply to the test circuit.
+ * @param testProperties The test case definition.
+ *
+ * @deprecated Scheduled for removal once existing tests converted over to use test circuits.
+ */
+ public void sequenceTest(Circuit testCircuit, List<Assertion> assertions, Properties testProperties)
+ {
+ log.debug("protected Message[] sequenceTest(Object... testProperties = " + testProperties + "): called");
+
+ TestClientDetails sender = getSender();
+ List<TestClientDetails> receivers = getReceivers();
+ ConversationFactory conversationFactory = getConversationFactory();
+
+ try
+ {
+ // Create a conversation on the sender clients private control route.
+ Session session = conversationFactory.getSession();
+ Destination senderControlTopic = session.createTopic(sender.privateControlKey);
+ ConversationFactory.Conversation senderConversation = conversationFactory.startConversation();
+
+ // Assign the sender role to the sending test client.
+ Message assignSender = conversationFactory.getSession().createMessage();
+ TestUtils.setPropertiesOnMessage(assignSender, testProperties);
+ assignSender.setStringProperty("CONTROL_TYPE", "ASSIGN_ROLE");
+ assignSender.setStringProperty("ROLE", "SENDER");
+ assignSender.setStringProperty("CLIENT_NAME", "Sustained_SENDER");
+
+ senderConversation.send(senderControlTopic, assignSender);
+
+ // Wait for the sender to confirm its role.
+ senderConversation.receive();
+
+ // Assign the receivers roles.
+ for (TestClientDetails receiver : receivers)
+ {
+ assignReceiverRole(receiver, testProperties, true);
+ }
+
+ // Start the test on the sender.
+ Message start = session.createMessage();
+ start.setStringProperty("CONTROL_TYPE", "START");
+
+ senderConversation.send(senderControlTopic, start);
+
+ // Wait for the test sender to return its report.
+ Message senderReport = senderConversation.receive();
+ TestUtils.pause(500);
+
+ // Ask the receivers for their reports.
+ Message statusRequest = session.createMessage();
+ statusRequest.setStringProperty("CONTROL_TYPE", "STATUS_REQUEST");
+
+ // Gather the reports from all of the receiving clients.
+
+ // Return all of the test reports, the senders report first.
+ // return new Message[] { senderReport };
+ }
+ catch (JMSException e)
+ {
+ throw new RuntimeException("Unhandled JMSException.");
+ }
+ }
+
+ /**
+ * Assigns the receivers role to the specified test client that is to act as a receivers during the test. This method
+ * does not always wait for the receiving clients to confirm their role assignments. This is because this method
+ * may be called from an 'onMessage' method, when a client is joining the test at a later point in time, and it
+ * is not possible to do a synchronous receive during an 'onMessage' method. There is a flag to indicate whether
+ * or not to wait for role confirmations.
+ *
+ * @param receiver The test client to assign the receivers role to.
+ * @param testProperties The test parameters.
+ * @param confirm Indicates whether role confirmation should be waited for.
+ *
+ * @throws JMSException Any JMSExceptions occurring during the conversation are allowed to fall through.
+ *
+ * @deprecated Scheduled for removal once existing tests converted over to use test circuits.
+ */
+ protected void assignReceiverRole(TestClientDetails receiver, Properties testProperties, boolean confirm)
+ throws JMSException
+ {
+ log.info("assignReceiverRole(TestClientDetails receivers = " + receiver + ", Map<String, Object> testProperties = "
+ + testProperties + "): called");
+
+ ConversationFactory conversationFactory = getConversationFactory();
+
+ // Create a conversation with the receiving test client.
+ Session session = conversationFactory.getSession();
+ Destination receiverControlTopic = session.createTopic(receiver.privateControlKey);
+ ConversationFactory.Conversation receiverConversation = conversationFactory.startConversation();
+
+ // Assign the receivers role to the receiving client.
+ Message assignReceiver = session.createMessage();
+ TestUtils.setPropertiesOnMessage(assignReceiver, testProperties);
+ assignReceiver.setStringProperty("CONTROL_TYPE", "ASSIGN_ROLE");
+ assignReceiver.setStringProperty("ROLE", "RECEIVER");
+ assignReceiver.setStringProperty("CLIENT_NAME", receiver.clientName);
+
+ receiverConversation.send(receiverControlTopic, assignReceiver);
+
+ // Wait for the role confirmation to come back.
+ if (confirm)
+ {
+ receiverConversation.receive();
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/InteropCircuitFactory.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/InteropCircuitFactory.java
new file mode 100644
index 0000000000..7df80bbf10
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/framework/sequencers/InteropCircuitFactory.java
@@ -0,0 +1,152 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.framework.sequencers;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.test.framework.Assertion;
+import org.apache.qpid.test.framework.Circuit;
+import org.apache.qpid.test.framework.TestClientDetails;
+import org.apache.qpid.test.framework.TestUtils;
+import org.apache.qpid.test.framework.distributedcircuit.DistributedCircuitImpl;
+import org.apache.qpid.test.utils.ConversationFactory;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+
+import javax.jms.*;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * InteropCircuitFactory is a circuit factory that creates distributed test circuits. Given a set of participating
+ * test client nodes, it assigns one node to the SENDER role and one the RECEIVER role.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Create distributed circuits from pairs of test nodes, for interop style testing.
+ * </table>
+ *
+ * @todo The partitioning of a set of nodes into sender and receiver roles is actually done by the interop test
+ * decorator. See the todo comment in FanOutCircuitFactory about merging the factories with the decorators, or
+ * more carefully dividing up responsibilities between them.
+ *
+ * @todo The squenceTest code is deprecated, but currently still used by the interop tests. It will be removed once it
+ * have been fully replaced by the default test procedure.
+ */
+public class InteropCircuitFactory extends BaseCircuitFactory
+{
+ /** Used for debugging. */
+ Logger log = Logger.getLogger(InteropCircuitFactory.class);
+
+ /**
+ * Creates a test circuit for the test, configered by the test parameters specified.
+ *
+ * @param testProperties The test parameters.
+ * @return A test circuit.
+ */
+ public Circuit createCircuit(Connection connection, ParsedProperties testProperties)
+ {
+ log.debug("public Circuit createCircuit(ParsedProperties testProperties): called");
+
+ List<TestClientDetails> senders = new LinkedList<TestClientDetails>();
+ senders.add(getSender());
+ List<TestClientDetails> receivers = getReceivers();
+ ConversationFactory conversationFactory = getConversationFactory();
+
+ return DistributedCircuitImpl.createCircuit(testProperties, senders, receivers, conversationFactory);
+ }
+
+ /**
+ * Holds a test coordinating conversation with the test clients. This should consist of assigning the test roles,
+ * begining the test, gathering the test reports from the participants, and checking for assertion failures against
+ * the test reports.
+ *
+ * @param testCircuit The test circuit.
+ * @param assertions The list of assertions to apply to the test circuit.
+ * @param testProperties The test case definition.
+ */
+ public void sequenceTest(Circuit testCircuit, List<Assertion> assertions, Properties testProperties)
+ {
+ log.debug("protected Message[] sequenceTest(Object... testProperties = " + testProperties + "): called");
+
+ TestClientDetails sender = getSender();
+ List<TestClientDetails> receivers = getReceivers();
+ ConversationFactory conversationFactory = getConversationFactory();
+
+ try
+ {
+ Session session = conversationFactory.getSession();
+ Destination senderControlTopic = session.createTopic(sender.privateControlKey);
+ Destination receiverControlTopic = session.createTopic(receivers.get(0).privateControlKey);
+
+ ConversationFactory.Conversation senderConversation = conversationFactory.startConversation();
+ ConversationFactory.Conversation receiverConversation = conversationFactory.startConversation();
+
+ Message assignSender = conversationFactory.getSession().createMessage();
+ TestUtils.setPropertiesOnMessage(assignSender, testProperties);
+ assignSender.setStringProperty("CONTROL_TYPE", "ASSIGN_ROLE");
+ assignSender.setStringProperty("ROLE", "SENDER");
+
+ senderConversation.send(senderControlTopic, assignSender);
+
+ // Assign the receivers role the receiving client.
+ Message assignReceiver = session.createMessage();
+ TestUtils.setPropertiesOnMessage(assignReceiver, testProperties);
+ assignReceiver.setStringProperty("CONTROL_TYPE", "ASSIGN_ROLE");
+ assignReceiver.setStringProperty("ROLE", "RECEIVER");
+
+ receiverConversation.send(receiverControlTopic, assignReceiver);
+
+ // Wait for the senders and receivers to confirm their roles.
+ senderConversation.receive();
+ receiverConversation.receive();
+
+ // Start the test.
+ Message start = session.createMessage();
+ start.setStringProperty("CONTROL_TYPE", "START");
+
+ senderConversation.send(senderControlTopic, start);
+
+ // Wait for the test sender to return its report.
+ Message senderReport = senderConversation.receive();
+ TestUtils.pause(500);
+
+ // Ask the receivers for its report.
+ Message statusRequest = session.createMessage();
+ statusRequest.setStringProperty("CONTROL_TYPE", "STATUS_REQUEST");
+
+ receiverConversation.send(receiverControlTopic, statusRequest);
+
+ // Wait for the receivers to send its report.
+ Message receiverReport = receiverConversation.receive();
+
+ // return new Message[] { senderReport, receiverReport };
+
+ // Apply assertions.
+ }
+ catch (JMSException e)
+ {
+ throw new RuntimeException("JMSException not handled.");
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/testcases/FailoverTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/testcases/FailoverTest.java
new file mode 100644
index 0000000000..a5a0d4e41f
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/testcases/FailoverTest.java
@@ -0,0 +1,119 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.testcases;
+
+import org.apache.qpid.test.framework.*;
+import static org.apache.qpid.test.framework.MessagingTestConfigProperties.*;
+import org.apache.qpid.test.framework.localcircuit.LocalCircuitImpl;
+import org.apache.qpid.test.framework.sequencers.CircuitFactory;
+
+import javax.jms.JMSException;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+
+/**
+ * FailoverTest provides testing of fail-over over a local-circuit implementation. The circuit being tested may be
+ * against an in-vm broker or against an external broker, with the failure mechanism abstracted out of the test case.
+ * Automatic failures can be simulated against an in-vm broker. Currently the test must interact with the user to
+ * simulate failures on an external broker.
+ *
+ * Things to test:
+ * In tx, failure duing tx causes tx to error on subsequent sends/receives or commits/rollbacks.
+ * Outside of tx, reconnection allows msg flow to continue but there may be loss.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td>
+ * </table>
+ *
+ * @todo This test is designed to be run over a local circuit only. For in-vm using automatic failures, for external
+ * brokers by prompting the user (or maybe using a script). Enforce the local-circuit only nature of the tests as
+ * well as thinking about how other local-circuit tests might be implemented. For example, could add a method
+ * to the framework base case for local only tests to call, that allows them access to the local-circuit
+ * implementation and so on.
+ *
+ * @todo More. Need to really expand the set of fail-over tests.
+ */
+public class FailoverTest extends FrameworkBaseCase
+{
+ /* Used for debugging purposes. */
+ // private static final Logger log = Logger.getLogger(FailoverTest.class);
+
+ /**
+ * Creates a new test case with the specified name.
+ *
+ * @param name The test case name.
+ */
+ public FailoverTest(String name)
+ {
+ super(name);
+ }
+
+ /**
+ * Checks that all messages sent within a transaction are receieved despite a fail-over occuring outside of
+ * the transaction.
+ *
+ * @throws JMSException Allowed to fall through and fail test.
+ */
+ public void testTxP2PFailover() throws Exception
+ {
+ // Set up the test properties to match the test cases requirements.
+ getTestProps().setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ getTestProps().setProperty(ACK_MODE_PROPNAME, Session.AUTO_ACKNOWLEDGE);
+ getTestProps().setProperty(PUBSUB_PROPNAME, false);
+
+ // MessagingTestConfigProperties props = this.getTestParameters();
+
+ // Create the test circuit from the test configuration parameters.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), getTestProps());
+
+ // Create an assertion that all messages are received.
+ Assertion allMessagesReceived = testCircuit.getReceiver().allMessagesReceivedAssertion(getTestProps());
+
+ // This test case assumes it is using a local circuit.
+ LocalCircuitImpl localCircuit = (LocalCircuitImpl) testCircuit;
+
+ Session producerSession = localCircuit.getLocalPublisherCircuitEnd().getSession();
+ MessageProducer producer = localCircuit.getLocalPublisherCircuitEnd().getProducer();
+ // MessageConsumer consumer = localCircuit.getLocalReceiverCircuitEnd().getConsumer();
+
+ // Send some test messages.
+ for (int i = 0; i < 100; i++)
+ {
+ producer.send(TestUtils.createTestMessageOfSize(producerSession, 10));
+ producerSession.commit();
+
+ // Cause a failover.
+ if (i == 50)
+ {
+ getFailureMechanism().causeFailure();
+ }
+
+ // Wait for the reconnection to complete.
+ }
+
+ // Check that trying to send within the original transaction fails.
+
+ // Check that all messages sent were received.
+ assertTrue("All messages sent were not received back again.", allMessagesReceived.apply());
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/testcases/ImmediateMessageTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/testcases/ImmediateMessageTest.java
new file mode 100644
index 0000000000..3001211eae
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/testcases/ImmediateMessageTest.java
@@ -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.
+ *
+ */
+package org.apache.qpid.test.testcases;
+
+import org.apache.qpid.test.framework.AMQPPublisher;
+import org.apache.qpid.test.framework.Circuit;
+import org.apache.qpid.test.framework.FrameworkBaseCase;
+import org.apache.qpid.test.framework.MessagingTestConfigProperties;
+import static org.apache.qpid.test.framework.MessagingTestConfigProperties.*;
+import org.apache.qpid.test.framework.sequencers.CircuitFactory;
+
+import org.apache.qpid.junit.extensions.util.TestContextProperties;
+
+/**
+ * ImmediateMessageTest tests for the desired behaviour of immediate messages. Immediate messages are a non-JMS
+ * feature. A message may be marked with an immediate delivery flag, which means that a consumer must be connected
+ * to receive the message, through a valid route, when it is sent, or when its transaction is committed in the case
+ * of transactional messaging. If this is not the case, the broker should return the message with a NO_CONSUMERS code.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Check that an immediate message is sent succesfully not using transactions when a consumer is connected.
+ * <tr><td> Check that an immediate message is committed succesfully in a transaction when a consumer is connected.
+ * <tr><td> Check that an immediate message results in no consumers code, not using transactions, when a consumer is
+ * disconnected.
+ * <tr><td> Check that an immediate message results in no consumers code, in a transaction, when a consumer is
+ * disconnected.
+ * <tr><td> Check that an immediate message results in no route code, not using transactions, when no outgoing route is
+ * connected.
+ * <tr><td> Check that an immediate message results in no route code, upon transaction commit, when no outgoing route is
+ * connected.
+ * <tr><td> Check that an immediate message is sent succesfully not using transactions when a consumer is connected.
+ * <tr><td> Check that an immediate message is committed succesfully in a transaction when a consumer is connected.
+ * <tr><td> Check that an immediate message results in no consumers code, not using transactions, when a consumer is
+ * disconnected.
+ * <tr><td> Check that an immediate message results in no consumers code, in a transaction, when a consumer is
+ * disconnected.
+ * <tr><td> Check that an immediate message results in no route code, not using transactions, when no outgoing route is
+ * connected.
+ * <tr><td> Check that an immediate message results in no route code, upon transaction commit, when no outgoing route is
+ * connected.
+ * </table>
+ *
+ * @todo All of these test cases will be generated by a test generator that thoroughly tests all combinations of test
+ * circuits.
+ */
+public class ImmediateMessageTest extends FrameworkBaseCase
+{
+ /**
+ * Creates a new test case with the specified name.
+ *
+ * @param name The test case name.
+ */
+ public ImmediateMessageTest(String name)
+ {
+ super(name);
+ }
+
+ /** Check that an immediate message is sent succesfully not using transactions when a consumer is connected. */
+ public void test_QPID_517_ImmediateOkNoTxP2P() throws Exception
+ {
+ // Ensure transactional sessions are off.
+ getTestProps().setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ getTestProps().setProperty(PUBSUB_PROPNAME, false);
+
+ // Run the default test sequence over the test circuit checking for no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), getTestProps());
+
+ assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noExceptionsAssertion(getTestProps()))));
+ }
+
+ /** Check that an immediate message is committed succesfully in a transaction when a consumer is connected. */
+ public void test_QPID_517_ImmediateOkTxP2P() throws Exception
+ {
+ // Ensure transactional sessions are off.
+ getTestProps().setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ getTestProps().setProperty(PUBSUB_PROPNAME, false);
+
+ // Send one message with no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), getTestProps());
+
+ assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().noExceptionsAssertion(getTestProps()))));
+ }
+
+ /** Check that an immediate message results in no consumers code, not using transactions, when a consumer is disconnected. */
+ public void test_QPID_517_ImmediateFailsConsumerDisconnectedNoTxP2P() throws Exception
+ {
+ // Ensure transactional sessions are off.
+ getTestProps().setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ getTestProps().setProperty(PUBSUB_PROPNAME, false);
+
+ // Disconnect the consumer.
+ getTestProps().setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false);
+
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), getTestProps());
+
+ // Send one message and get a linked no consumers exception.
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noConsumersAssertion(getTestProps()))));
+ }
+
+ /** Check that an immediate message results in no consumers code, in a transaction, when a consumer is disconnected. */
+ public void test_QPID_517_ImmediateFailsConsumerDisconnectedTxP2P() throws Exception
+ {
+ // Ensure transactional sessions are on.
+ getTestProps().setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ getTestProps().setProperty(PUBSUB_PROPNAME, false);
+
+ // Disconnect the consumer.
+ getTestProps().setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false);
+
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), getTestProps());
+
+ // Send one message and get a linked no consumers exception.
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noConsumersAssertion(getTestProps()))));
+ }
+
+ /** Check that an immediate message results in no route code, not using transactions, when no outgoing route is connected. */
+ public void test_QPID_517_ImmediateFailsNoRouteNoTxP2P() throws Exception
+ {
+ // Ensure transactional sessions are off.
+ getTestProps().setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ getTestProps().setProperty(PUBSUB_PROPNAME, false);
+
+ // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to
+ // collect its messages).
+ getTestProps().setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false);
+
+ // Send one message and get a linked no route exception.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), getTestProps());
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(getTestProps()))));
+ }
+
+ /** Check that an immediate message results in no route code, upon transaction commit, when no outgoing route is connected. */
+ public void test_QPID_517_ImmediateFailsNoRouteTxP2P() throws Exception
+ {
+ // Ensure transactional sessions are on.
+ getTestProps().setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ getTestProps().setProperty(PUBSUB_PROPNAME, false);
+
+ // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to
+ // collect its messages).
+ getTestProps().setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false);
+
+ // Send one message and get a linked no route exception.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), getTestProps());
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(getTestProps()))));
+ }
+
+ /** Check that an immediate message is sent succesfully not using transactions when a consumer is connected. */
+ public void test_QPID_517_ImmediateOkNoTxPubSub() throws Exception
+ {
+ // Ensure transactional sessions are off.
+ getTestProps().setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ getTestProps().setProperty(PUBSUB_PROPNAME, true);
+
+ // Send one message with no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), getTestProps());
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(getTestProps()))));
+ }
+
+ /** Check that an immediate message is committed succesfully in a transaction when a consumer is connected. */
+ public void test_QPID_517_ImmediateOkTxPubSub() throws Exception
+ {
+ // Ensure transactional sessions are off.
+ getTestProps().setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ getTestProps().setProperty(PUBSUB_PROPNAME, true);
+
+ // Send one message with no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), getTestProps());
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(getTestProps()))));
+ }
+
+ /** Check that an immediate message results in no consumers code, not using transactions, when a consumer is disconnected. */
+ public void test_QPID_517_ImmediateFailsConsumerDisconnectedNoTxPubSub() throws Exception
+ {
+ // Ensure transactional sessions are off.
+ getTestProps().setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ getTestProps().setProperty(PUBSUB_PROPNAME, true);
+
+ // Use durable subscriptions, so that the route remains open with no subscribers.
+ getTestProps().setProperty(DURABLE_SUBSCRIPTION_PROPNAME, true);
+
+ // Disconnect the consumer.
+ getTestProps().setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false);
+
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), getTestProps());
+
+ // Send one message and get a linked no consumers exception.
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noConsumersAssertion(getTestProps()))));
+ }
+
+ /** Check that an immediate message results in no consumers code, in a transaction, when a consumer is disconnected. */
+ public void test_QPID_517_ImmediateFailsConsumerDisconnectedTxPubSub() throws Exception
+ {
+ // Ensure transactional sessions are on.
+ getTestProps().setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ getTestProps().setProperty(PUBSUB_PROPNAME, true);
+
+ // Use durable subscriptions, so that the route remains open with no subscribers.
+ getTestProps().setProperty(DURABLE_SUBSCRIPTION_PROPNAME, true);
+
+ // Disconnect the consumer.
+ getTestProps().setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false);
+
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), getTestProps());
+
+ // Send one message and get a linked no consumers exception.
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noConsumersAssertion(getTestProps()))));
+ }
+
+ /** Check that an immediate message results in no route code, not using transactions, when no outgoing route is connected. */
+ public void test_QPID_517_ImmediateFailsNoRouteNoTxPubSub() throws Exception
+ {
+ // Ensure transactional sessions are off.
+ getTestProps().setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ getTestProps().setProperty(PUBSUB_PROPNAME, true);
+
+ // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to
+ // collect its messages).
+ getTestProps().setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false);
+
+ // Send one message and get a linked no route exception.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), getTestProps());
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(getTestProps()))));
+ }
+
+ /** Check that an immediate message results in no route code, upon transaction commit, when no outgoing route is connected. */
+ public void test_QPID_517_ImmediateFailsNoRouteTxPubSub() throws Exception
+ {
+ // Ensure transactional sessions are on.
+ getTestProps().setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ getTestProps().setProperty(PUBSUB_PROPNAME, true);
+
+ // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to
+ // collect its messages).
+ getTestProps().setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false);
+
+ // Send one message and get a linked no route exception.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), getTestProps());
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(getTestProps()))));
+ }
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ setTestProps(TestContextProperties.getInstance(MessagingTestConfigProperties.defaults));
+
+ /** All these tests should have the immediate flag on. */
+ getTestProps().setProperty(IMMEDIATE_PROPNAME, true);
+ getTestProps().setProperty(MANDATORY_PROPNAME, false);
+
+ /** Bind the receivers consumer by default. */
+ getTestProps().setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, true);
+ getTestProps().setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, true);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/testcases/MandatoryMessageTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/testcases/MandatoryMessageTest.java
new file mode 100644
index 0000000000..b4c4eb91b4
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/testcases/MandatoryMessageTest.java
@@ -0,0 +1,321 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.testcases;
+
+import org.apache.qpid.test.framework.AMQPPublisher;
+import org.apache.qpid.test.framework.Circuit;
+import org.apache.qpid.test.framework.FrameworkBaseCase;
+import org.apache.qpid.test.framework.MessagingTestConfigProperties;
+import static org.apache.qpid.test.framework.MessagingTestConfigProperties.*;
+import org.apache.qpid.test.framework.sequencers.CircuitFactory;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+import org.apache.qpid.junit.extensions.util.TestContextProperties;
+
+/**
+ * MandatoryMessageTest tests for the desired behaviour of mandatory messages. Mandatory messages are a non-JMS
+ * feature. A message may be marked with a mandatory delivery flag, which means that a valid route for the message
+ * must exist, when it is sent, or when its transaction is committed in the case of transactional messaging. If this
+ * is not the case, the broker should return the message with a NO_CONSUMERS code.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Check that an mandatory message is sent succesfully not using transactions when a consumer is connected.
+ * <tr><td> Check that an mandatory message is committed succesfully in a transaction when a consumer is connected.
+ * <tr><td> Check that a mandatory message is sent succesfully, not using transactions, when a consumer is disconnected
+ * but the route exists.
+ * <tr><td> Check that a mandatory message is sent succesfully, in a transaction, when a consumer is disconnected but
+ * the route exists.
+ * <tr><td> Check that an mandatory message results in no route code, not using transactions, when no consumer is
+ * connected.
+ * <tr><td> Check that an mandatory message results in no route code, upon transaction commit, when a consumer is
+ * connected.
+ * <tr><td> Check that an mandatory message is sent succesfully not using transactions when a consumer is connected.
+ * <tr><td> Check that an mandatory message is committed succesfully in a transaction when a consumer is connected.
+ * <tr><td> Check that a mandatory message is sent succesfully, not using transactions, when a consumer is disconnected
+ * but the route exists.
+ * <tr><td> Check that a mandatory message is sent succesfully, in a transaction, when a consumer is disconnected but
+ * the route exists.
+ * <tr><td> Check that an mandatory message results in no route code, not using transactions, when no consumer is
+ * connected.
+ * <tr><td> Check that an mandatory message results in no route code, upon transaction commit, when a consumer is
+ * connected.
+ * </table>
+ *
+ * @todo All of these test cases will be generated by a test generator that thoroughly tests all combinations of test
+ * circuits.
+ */
+public class MandatoryMessageTest extends FrameworkBaseCase
+{
+ /** Used to read the tests configurable properties through. */
+ ParsedProperties testProps;
+
+ /**
+ * Creates a new test case with the specified name.
+ *
+ * @param name The test case name.
+ */
+ public MandatoryMessageTest(String name)
+ {
+ super(name);
+ }
+
+ /** Check that an mandatory message is sent succesfully not using transactions when a consumer is connected. */
+ public void test_QPID_508_MandatoryOkNoTxP2P() throws Exception
+ {
+ // Ensure transactional sessions are off.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ testProps.setProperty(PUBSUB_PROPNAME, false);
+
+ // Run the default test sequence over the test circuit checking for no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps))));
+ }
+
+ /** Check that an mandatory message is committed succesfully in a transaction when a consumer is connected. */
+ public void test_QPID_508_MandatoryOkTxP2P() throws Exception
+ {
+ // Ensure transactional sessions are off.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ testProps.setProperty(PUBSUB_PROPNAME, false);
+
+ // Run the default test sequence over the test circuit checking for no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps))));
+ }
+
+ /**
+ * Check that a mandatory message is sent succesfully, not using transactions, when a consumer is disconnected but
+ * the route exists.
+ */
+ public void test_QPID_517_MandatoryOkConsumerDisconnectedNoTxP2P() throws Exception
+ {
+ // Ensure transactional sessions are off.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ testProps.setProperty(PUBSUB_PROPNAME, false);
+
+ // Disconnect the consumer.
+ testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false);
+
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), testProps);
+
+ // Send one message with no errors.
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps))));
+ }
+
+ /**
+ * Check that a mandatory message is sent succesfully, in a transaction, when a consumer is disconnected but
+ * the route exists.
+ */
+ public void test_QPID_517_MandatoryOkConsumerDisconnectedTxP2P() throws Exception
+ {
+ // Ensure transactional sessions are on.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ testProps.setProperty(PUBSUB_PROPNAME, false);
+
+ // Disconnect the consumer.
+ testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false);
+
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), testProps);
+
+ // Send one message with no errors.
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps))));
+ }
+
+ /** Check that an mandatory message results in no route code, not using transactions, when no consumer is connected. */
+ public void test_QPID_508_MandatoryFailsNoRouteNoTxP2P() throws Exception
+ {
+ // Ensure transactional sessions are off.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ testProps.setProperty(PUBSUB_PROPNAME, false);
+
+ // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to
+ // collect its messages).
+ testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false);
+
+ // Send one message and get a linked no route exception.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(testProps))));
+ }
+
+ /** Check that an mandatory message results in no route code, upon transaction commit, when a consumer is connected. */
+ public void test_QPID_508_MandatoryFailsNoRouteTxP2P() throws Exception
+ {
+ // Ensure transactional sessions are on.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ testProps.setProperty(PUBSUB_PROPNAME, false);
+
+ // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to
+ // collect its messages).
+ testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false);
+
+ // Send one message and get a linked no route exception.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(testProps))));
+ }
+
+ /** Check that an mandatory message is sent succesfully not using transactions when a consumer is connected. */
+ public void test_QPID_508_MandatoryOkNoTxPubSub() throws Exception
+ {
+ // Ensure transactional sessions are off.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ testProps.setProperty(PUBSUB_PROPNAME, true);
+
+ // Run the default test sequence over the test circuit checking for no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps))));
+ }
+
+ /** Check that an mandatory message is committed succesfully in a transaction when a consumer is connected. */
+ public void test_QPID_508_MandatoryOkTxPubSub() throws Exception
+ {
+ // Ensure transactional sessions are on.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ testProps.setProperty(PUBSUB_PROPNAME, true);
+
+ // Run the default test sequence over the test circuit checking for no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps))));
+ }
+
+ /**
+ * Check that a mandatory message is sent succesfully, not using transactions, when a consumer is disconnected but
+ * the route exists.
+ */
+ public void test_QPID_517_MandatoryOkConsumerDisconnectedNoTxPubSub() throws Exception
+ {
+ // Ensure transactional sessions are off.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ testProps.setProperty(PUBSUB_PROPNAME, true);
+
+ // Use durable subscriptions, so that the route remains open with no subscribers.
+ testProps.setProperty(DURABLE_SUBSCRIPTION_PROPNAME, true);
+
+ // Disconnect the consumer.
+ testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false);
+
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), testProps);
+
+ // Send one message with no errors.
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps))));
+ }
+
+ /**
+ * Check that a mandatory message is sent succesfully, in a transaction, when a consumer is disconnected but
+ * the route exists.
+ */
+ public void test_QPID_517_MandatoryOkConsumerDisconnectedTxPubSub() throws Exception
+ {
+ // Ensure transactional sessions are on.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ testProps.setProperty(PUBSUB_PROPNAME, true);
+
+ // Use durable subscriptions, so that the route remains open with no subscribers.
+ testProps.setProperty(DURABLE_SUBSCRIPTION_PROPNAME, true);
+
+ // Disconnect the consumer.
+ testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, false);
+
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), testProps);
+
+ // Send one message with no errors.
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noExceptionsAssertion(testProps))));
+ }
+
+ /** Check that an mandatory message results in no route code, not using transactions, when no consumer is connected. */
+ public void test_QPID_508_MandatoryFailsNoRouteNoTxPubSub() throws Exception
+ {
+ // Ensure transactional sessions are off.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ testProps.setProperty(PUBSUB_PROPNAME, true);
+
+ // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to
+ // collect its messages).
+ testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false);
+
+ // Send one message and get a linked no route exception.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(testProps))));
+ }
+
+ /** Check that an mandatory message results in no route code, upon transaction commit, when a consumer is connected. */
+ public void test_QPID_508_MandatoryFailsNoRouteTxPubSub() throws Exception
+ {
+ // Ensure transactional sessions are on.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ testProps.setProperty(PUBSUB_PROPNAME, true);
+
+ // Set up the messaging topology so that only the publishers producer is bound (do not set up the receivers to
+ // collect its messages).
+ testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, false);
+
+ // Send one message and get a linked no route exception.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(((AMQPPublisher) testCircuit.getPublisher()).noRouteAssertion(testProps))));
+ }
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ testProps = TestContextProperties.getInstance(MessagingTestConfigProperties.defaults);
+
+ /** All these tests should have the mandatory flag on. */
+ testProps.setProperty(IMMEDIATE_PROPNAME, false);
+ testProps.setProperty(MANDATORY_PROPNAME, true);
+
+ /** Bind the receivers consumer by default. */
+ testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, true);
+ testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, true);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/testcases/RollbackTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/testcases/RollbackTest.java
new file mode 100644
index 0000000000..edcde796a8
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/testcases/RollbackTest.java
@@ -0,0 +1,132 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.testcases;
+
+import org.apache.qpid.test.framework.Circuit;
+import org.apache.qpid.test.framework.FrameworkBaseCase;
+import org.apache.qpid.test.framework.MessagingTestConfigProperties;
+import static org.apache.qpid.test.framework.MessagingTestConfigProperties.*;
+import org.apache.qpid.test.framework.sequencers.CircuitFactory;
+
+import org.apache.qpid.junit.extensions.util.ParsedProperties;
+import org.apache.qpid.junit.extensions.util.TestContextProperties;
+
+/**
+ * RollbackTest tests the rollback ability of transactional messaging.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Check messages sent but rolled back are never received.
+ * <tr><td> Check messages received but rolled back are redelivered on subsequent receives.
+ * <tr><td> Attempting to rollback outside of a transaction results in an IllegalStateException.
+ * </table>
+ */
+public class RollbackTest extends FrameworkBaseCase
+{
+ /** Used to read the tests configurable properties through. */
+ private ParsedProperties testProps;
+
+ /**
+ * Creates a new test case with the specified name.
+ *
+ * @param name The test case name.
+ */
+ public RollbackTest(String name)
+ {
+ super(name);
+ }
+
+ /** Check messages sent but rolled back are never received. */
+ public void testRolledbackMessageNotDelivered() throws Exception
+ {
+ // Ensure transactional sessions are on.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, true);
+ testProps.setProperty(ROLLBACK_PUBLISHER_PROPNAME, true);
+
+ // Run the default test sequence over the test circuit checking for no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(testCircuit.getPublisher().noExceptionsAssertion(testProps),
+ testCircuit.getReceiver().noMessagesReceivedAssertion(testProps))));
+ }
+
+ /** Check messages received but rolled back are redelivered on subsequent receives. */
+ public void testRolledbackMessagesSubsequentlyReceived() throws Exception
+ {
+ // Ensure transactional sessions are on.
+ testProps.setProperty(TRANSACTED_RECEIVER_PROPNAME, true);
+ testProps.setProperty(ROLLBACK_RECEIVER_PROPNAME, true);
+
+ // Run the default test sequence over the test circuit checking for no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), testProps);
+
+ assertNoFailures(testCircuit.test(1,
+ assertionList(testCircuit.getPublisher().noExceptionsAssertion(testProps),
+ testCircuit.getReceiver().allMessagesReceivedAssertion(testProps))));
+ }
+
+ /** Attempting to rollback outside of a transaction results in an IllegalStateException. */
+ public void testRollbackUnavailableOutsideTransactionPublisher() throws Exception
+ {
+ // Ensure transactional sessions are on.
+ testProps.setProperty(TRANSACTED_PUBLISHER_PROPNAME, false);
+ testProps.setProperty(ROLLBACK_PUBLISHER_PROPNAME, true);
+
+ // Run the default test sequence over the test circuit checking for no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), testProps);
+
+ assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getPublisher().channelClosedAssertion(testProps))));
+ }
+
+ /** Attempting to rollback outside of a transaction results in an IllegalStateException. */
+ public void testRollbackUnavailableOutsideTransactionReceiver() throws Exception
+ {
+ // Ensure transactional sessions are on.
+ testProps.setProperty(TRANSACTED_RECEIVER_PROPNAME, false);
+ testProps.setProperty(ROLLBACK_RECEIVER_PROPNAME, true);
+
+ // Run the default test sequence over the test circuit checking for no errors.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), testProps);
+
+ assertNoFailures(testCircuit.test(1, assertionList(testCircuit.getReceiver().channelClosedAssertion(testProps))));
+ }
+
+ /**
+ * Sets up all tests to have an active outward route and consumer by default.
+ *
+ * @throws Exception Any exceptions are allowed to fall through and fail the test.
+ */
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ testProps = TestContextProperties.getInstance(MessagingTestConfigProperties.defaults);
+
+ /** Bind the receivers consumer by default. */
+ testProps.setProperty(RECEIVER_CONSUMER_BIND_PROPNAME, true);
+ testProps.setProperty(RECEIVER_CONSUMER_ACTIVE_PROPNAME, true);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/testcases/TTLTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/testcases/TTLTest.java
new file mode 100644
index 0000000000..d4bab657d7
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/testcases/TTLTest.java
@@ -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.
+ *
+ */
+package org.apache.qpid.test.testcases;
+
+import org.apache.qpid.test.framework.Circuit;
+import org.apache.qpid.test.framework.FrameworkBaseCase;
+import static org.apache.qpid.test.framework.MessagingTestConfigProperties.ACK_MODE_PROPNAME;
+import static org.apache.qpid.test.framework.MessagingTestConfigProperties.PUBSUB_PROPNAME;
+import org.apache.qpid.test.framework.TestUtils;
+import org.apache.qpid.test.framework.localcircuit.LocalCircuitImpl;
+import org.apache.qpid.test.framework.sequencers.CircuitFactory;
+
+import javax.jms.*;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * TTLTest checks that time-to-live is applied to messages. The test sends messages with a variety of TTL stamps on them
+ * then after a pause attempts to receive those messages. Only messages with a large enough TTL to have survived the pause
+ * should be receiveable. This test case also applies an additional assertion against the broker, that the message store
+ * is empty at the end of the test.
+ *
+ * <p/>This test is designed to run over local circuits only, as it must control a timed pause between sending and receiving
+ * messages to that TTL can be applied to purge some of the messages.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td>
+ * </table>
+ *
+ * @todo Use an interface or other method to mark this test as local only.
+ *
+ * @todo Implement the message store assertion for in-vm broker. Could also be done for external broker, for example
+ * by using diagnostic exchange.
+ *
+ * @todo Implement and add a queue depth assertion too. This might already be in another test to copy from.
+ *
+ * @todo Create variations on test theme, for different ack mode and tx and message sizes etc.
+ *
+ * @todo Add an allowable margin of error to the test, as ttl may not be precise.
+ */
+public class TTLTest extends FrameworkBaseCase
+{
+ /**
+ * Creates a new test case with the specified name.
+ *
+ * @param name The test case name.
+ */
+ public TTLTest(String name)
+ {
+ super(name);
+ }
+
+ /**
+ * Checks that all messages sent with a TTL shorter than a pause between sending them and attempting to receive them
+ * will fail to arrive. Once all messages have been purged by TTL or received, check that they no longer exist on
+ * the broker.
+ *
+ * @throws javax.jms.JMSException Allowed to fall through and fail test.
+ */
+ public void testTTLP2P() throws Exception
+ {
+ String errorMessages = "";
+ Random r = new Random();
+
+ // Used to accumulate correctly received messages in.
+ List<Message> receivedMessages = new LinkedList<Message>();
+
+ // Set up the test properties to match the test case requirements.
+ getTestProps().setProperty(ACK_MODE_PROPNAME, Session.AUTO_ACKNOWLEDGE);
+ getTestProps().setProperty(PUBSUB_PROPNAME, false);
+
+ // Create the test circuit from the test configuration parameters.
+ CircuitFactory circuitFactory = getCircuitFactory();
+ Circuit testCircuit = circuitFactory.createCircuit(getConnection(), getTestProps());
+
+ // This test case assumes it is using a local circuit.
+ LocalCircuitImpl localCircuit = (LocalCircuitImpl) testCircuit;
+
+ Session producerSession = localCircuit.getLocalPublisherCircuitEnd().getSession();
+ MessageProducer producer = localCircuit.getLocalPublisherCircuitEnd().getProducer();
+ MessageConsumer consumer = localCircuit.getLocalReceiverCircuitEnd().getConsumer();
+
+ // Send some tests messages, with random TTLs, some shorter and some longer than the pause time.
+ for (int i = 0; i < 100; i++)
+ {
+ Message testMessage = TestUtils.createTestMessageOfSize(producerSession, 10);
+
+ // Set the TTL on the message and record its value in the message headers.
+ long ttl = 500 + r.nextInt(1500);
+ producer.setTimeToLive(ttl);
+ testMessage.setLongProperty("testTTL", ttl);
+
+ producer.send(testMessage);
+ // producerSession.commit();
+ }
+
+ // Inject a pause to allow some messages to be purged by TTL.
+ TestUtils.pause(1000);
+
+ // Attempt to receive back all of the messages, confirming by the message time stamps and TTLs that only
+ // those received should have avoided being purged by the TTL.
+ boolean timedOut = false;
+
+
+ Message testMessage = null;
+
+ do
+ {
+ testMessage = consumer.receive(1000);
+
+ long ttl = testMessage.getLongProperty("testTTL");
+ long timeStamp = testMessage.getJMSTimestamp();
+ long now = System.currentTimeMillis();
+
+ if ((timeStamp + ttl) < now)
+ {
+ errorMessages +=
+ "Received message [sent: " + timeStamp + ", ttl: " + ttl + ", received: " + now
+ + "] which should have been purged by its TTL.\n";
+ }
+ /*else
+ {
+ receivedMessages.add(testMessage);
+ }*/
+ } while (!timedOut && testMessage != null);
+
+ // Check that the queue and message store on the broker are empty.
+ // assertTrue("Message store is not empty.", messageStoreEmpty.apply());
+ // assertTrue("Queue is not empty.", queueEmpty.apply());
+
+ assertTrue(errorMessages, "".equals(errorMessages));
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/Acknowledge2ConsumersTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/Acknowledge2ConsumersTest.java
new file mode 100644
index 0000000000..4b45a96c20
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/Acknowledge2ConsumersTest.java
@@ -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.
+ *
+ */
+
+package org.apache.qpid.test.unit.ack;
+
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.test.utils.FailoverBaseCase;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.Queue;
+import javax.jms.Session;
+
+public class Acknowledge2ConsumersTest extends FailoverBaseCase
+{
+ protected static int NUM_MESSAGES = 100;
+ protected Connection _con;
+ protected Queue _queue;
+ private Session _producerSession;
+ private Session _consumerSession;
+ private MessageConsumer _consumerA;
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ _queue = (Queue) getInitialContext().lookup("queue");
+
+ //Create Producer put some messages on the queue
+ _con = getConnection();
+ }
+
+ private void init(boolean transacted, int mode) throws JMSException
+ {
+ _producerSession = _con.createSession(true, Session.SESSION_TRANSACTED);
+ _consumerSession = _con.createSession(transacted, mode);
+ _consumerA = _consumerSession.createConsumer(_queue);
+ _con.start();
+ }
+
+ /**
+ * Produces Messages that
+ *
+ * @param transacted
+ * @param mode
+ *
+ * @throws Exception
+ */
+ private void test2ConsumersAcking(boolean transacted, int mode) throws Exception
+ {
+ init(transacted, mode);
+
+ // These should all end up being prefetched by sessionA
+ sendMessage(_producerSession, _queue, NUM_MESSAGES / 2);
+
+ //Create a second consumer (consumerB) to consume some of the messages
+ MessageConsumer consumerB = _consumerSession.createConsumer(_queue);
+
+ // These messages should be roundrobined between A and B
+ sendMessage(_producerSession, _queue, NUM_MESSAGES / 2);
+
+ int count = 0;
+ //Use consumerB to receive messages it has
+ Message msg = consumerB.receive(1500);
+ while (msg != null)
+ {
+ if (mode == Session.CLIENT_ACKNOWLEDGE)
+ {
+ msg.acknowledge();
+ }
+ count++;
+ msg = consumerB.receive(1500);
+ }
+ if (transacted)
+ {
+ _consumerSession.commit();
+ }
+
+ // Close the consumers
+ _consumerA.close();
+ consumerB.close();
+
+ // and close the session to release any prefetched messages.
+ _consumerSession.close();
+ assertEquals("Wrong number of messages on queue", NUM_MESSAGES - count,
+ ((AMQSession) _producerSession).getQueueDepth((AMQDestination) _queue));
+
+ // Clean up messages that may be left on the queue
+ _consumerSession = _con.createSession(transacted, mode);
+ _consumerA = _consumerSession.createConsumer(_queue);
+ msg = _consumerA.receive(1500);
+ while (msg != null)
+ {
+ if (mode == Session.CLIENT_ACKNOWLEDGE)
+ {
+ msg.acknowledge();
+ }
+ msg = _consumerA.receive(1500);
+ }
+ _consumerA.close();
+ if (transacted)
+ {
+ _consumerSession.commit();
+ }
+ _consumerSession.close();
+ }
+
+ public void test2ConsumersAutoAck() throws Exception
+ {
+ test2ConsumersAcking(false, Session.AUTO_ACKNOWLEDGE);
+ }
+
+ public void test2ConsumersClientAck() throws Exception
+ {
+ test2ConsumersAcking(false, Session.CLIENT_ACKNOWLEDGE);
+ }
+
+ public void test2ConsumersTx() throws Exception
+ {
+ test2ConsumersAcking(true, Session.SESSION_TRANSACTED);
+ }
+
+
+
+//
+// /**
+// * Check that session level acknowledge does correctly ack all previous
+// * values. Send 3 messages(0,1,2) then ack 1 and 2. If session ack is
+// * working correctly then acking 1 will also ack 0. Acking 2 will not
+// * attempt to re-ack 0 and 1.
+// *
+// * @throws Exception
+// */
+// public void testSessionAck() throws Exception
+// {
+// init(false, Session.CLIENT_ACKNOWLEDGE);
+//
+// sendMessage(_producerSession, _queue, 3);
+// Message msg;
+//
+// // Drop msg 0
+// _consumerA.receive(RECEIVE_TIMEOUT);
+//
+// // Take msg 1
+// msg = _consumerA.receive(RECEIVE_TIMEOUT);
+//
+// assertNotNull("Message 1 not correctly received.", msg);
+// assertEquals("Incorrect message received", 1, msg.getIntProperty(INDEX));
+//
+// // This should also ack msg 0
+// msg.acknowledge();
+//
+// // Take msg 2
+// msg = _consumerA.receive(RECEIVE_TIMEOUT);
+//
+// assertNotNull("Message 2 not correctly received.", msg);
+// assertEquals("Incorrect message received", 2, msg.getIntProperty(INDEX));
+//
+// // This should just ack msg 2
+// msg.acknowledge();
+//
+// _consumerA.close();
+// _consumerSession.close();
+//
+// assertEquals("Queue not empty.", 0,
+// ((AMQSession) _producerSession).getQueueDepth((AMQDestination) _queue));
+// _con.close();
+//
+//
+// }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/AcknowledgeAfterFailoverOnMessageTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/AcknowledgeAfterFailoverOnMessageTest.java
new file mode 100644
index 0000000000..d73d012250
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/AcknowledgeAfterFailoverOnMessageTest.java
@@ -0,0 +1,429 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.ack;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.jms.ConnectionListener;
+import org.apache.qpid.util.FileUtils;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+import javax.jms.Session;
+import javax.jms.TransactionRolledBackException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.io.File;
+
+/**
+ * The AcknowlegeAfterFailoverOnMessageTests
+ *
+ * Extends the OnMessage AcknowledgeTests to validate that after the client has
+ * failed over that the client can still receive and ack messages.
+ *
+ * All the AcknowledgeTest ack modes are exercised here though some are disabled
+ * due to know issues (e.g. DupsOk, AutoAck : QPID-143 and the clientAck
+ * and dirtyClientAck due to QPID-1816)
+ *
+ * This class has two main test structures, overrides of AcknowledgeOnMessageTest
+ * to perform the clean acking based on session ack mode and a series of dirty
+ * ack tests that test what happends if you receive a message then try and ack
+ * AFTER you have failed over.
+ *
+ *
+ */
+public class AcknowledgeAfterFailoverOnMessageTest extends AcknowledgeOnMessageTest implements ConnectionListener
+{
+
+ protected CountDownLatch _failoverCompleted = new CountDownLatch(1);
+ private MessageListener _listener = null;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ NUM_MESSAGES = 10;
+ }
+
+ /**
+ * Override default init to add connectionListener so we can verify that
+ * failover took place
+ *
+ * @param transacted create a transacted session for this test
+ * @param mode if not transacted what ack mode to use for this test
+ *
+ * @throws Exception if a problem occured during test setup.
+ */
+ @Override
+ public void init(boolean transacted, int mode) throws Exception
+ {
+ super.init(transacted, mode);
+ ((AMQConnection) _connection).setConnectionListener(this);
+ // Override the listener for the dirtyAck testing.
+ if (_listener != null)
+ {
+ _consumer.setMessageListener(_listener);
+ }
+ }
+
+ /**
+ * Prepare the broker for the next round.
+ *
+ * Called after acknowledging the messsage this method shuts the current
+ * broker down connnects to the new broker and send a new message for the
+ * client to failover to and receive.
+ *
+ * It ends by restarting the orignal broker so that the cycle can repeat.
+ *
+ * When we are able to cluster the java broker then will not need to do the
+ * message repopulation or QPID_WORK clearing. All that we will need to do
+ * is send the initial NUM_MESSAGES during startup and then bring the
+ * brokers down at the right time to cause the client to fail between them.
+ *
+ * @param index
+ * @throws Exception
+ */
+ protected void prepBroker(int index) throws Exception
+ {
+ // Alternate killing the broker based on the message index we are at.
+
+ if (index % 2 == 0)
+ {
+ failBroker(getFailingPort());
+ // Clean up the failed broker
+ FileUtils.delete(new File(System.getProperty("QPID_WORK") + "/" + getFailingPort()), true);
+ }
+ else
+ {
+ failBroker(getPort());
+ // Clean up the failed broker
+ FileUtils.delete(new File(System.getProperty("QPID_WORK") + "/" + getPort()), true);
+ }
+
+ _failoverCompleted = new CountDownLatch(1);
+
+ _logger.info("AAFOMT: prepNewBroker for message send");
+ Connection connection = getConnection();
+
+ try
+ {
+
+ //Stop the connection whilst we repopulate the broker, or the no_ack
+ // test will drain the msgs before we can check we put the right number
+ // back on again.
+
+ Session session = connection.createSession(true, Session.SESSION_TRANSACTED);
+ // ensure destination is created.
+ session.createConsumer(_queue).close();
+
+
+ // If this is the last message then we can skip the send.
+ // But we MUST ensure that we have created the queue with the
+ // above createConsumer(_queue).close() as the test will end by
+ // testing the queue depth which will fail if we don't ensure we
+ // declare the queue.
+ // index is 0 based so we need to check +1 against NUM_MESSAGES
+ if ((index + 1) == NUM_MESSAGES)
+ {
+ return;
+ }
+
+
+ sendMessage(session, _queue, 1, index + 1, 0);
+
+ // Validate that we have the message on the queue
+ // In NoAck mode though the messasge may already have been sent to
+ // the client so we have to skip the vaildation.
+ if (_consumerSession.getAcknowledgeMode() != AMQSession.NO_ACKNOWLEDGE)
+ {
+ assertEquals("Wrong number of messages on queue", 1,
+ ((AMQSession) session).getQueueDepth((AMQDestination) _queue));
+ }
+
+
+ }
+ catch (Exception e)
+ {
+ fail("Unable to prep new broker," + e.getMessage());
+ }
+ finally
+ {
+ connection.close();
+ }
+
+ try
+ {
+
+ //Restart the broker
+ if (index % 2 == 0)
+ {
+ startBroker(getFailingPort());
+ }
+ else
+ {
+ startBroker(getPort());
+ }
+ }
+ catch (Exception e)
+ {
+ fail("Unable to start failover broker," + e.getMessage());
+ }
+
+ }
+
+ @Override
+ public void doAcknowlegement(Message msg) throws JMSException
+ {
+ //Acknowledge current message
+ super.doAcknowlegement(msg);
+
+ try
+ {
+ prepBroker(msg.getIntProperty(INDEX));
+ }
+ catch (Exception e)
+ {
+ // Provide details of what went wrong with the stack trace
+ e.printStackTrace();
+ fail("Unable to prep new broker," + e);
+ }
+ }
+
+ // Instance varilable for DirtyAcking test
+ int _msgCount = 0;
+ boolean _cleaned = false;
+
+ class DirtyAckingHandler implements MessageListener
+ {
+ /**
+ * Validate first message but do nothing with it.
+ *
+ * Failover
+ *
+ * The receive the message again
+ *
+ * @param message
+ */
+ public void onMessage(Message message)
+ {
+ // Stop processing if we have an error and had to stop running.
+ if (_receivedAll.getCount() == 0)
+ {
+ _logger.debug("Dumping msgs due to error(" + _causeOfFailure.get().getMessage() + "):" + message);
+ return;
+ }
+
+ try
+ {
+ // Check we have the next message as expected
+ assertNotNull("Message " + _msgCount + " not correctly received.", message);
+ assertEquals("Incorrect message received", _msgCount, message.getIntProperty(INDEX));
+
+ if (_msgCount == 0 && _failoverCompleted.getCount() != 0)
+ {
+ // This is the first message we've received so lets fail the broker
+
+ failBroker(getFailingPort());
+
+ repopulateBroker();
+
+ _logger.error("Received first msg so failing over");
+
+ return;
+ }
+
+ _msgCount++;
+
+ // Don't acknowlege the first message after failover so we can commit
+ // them together
+ if (_msgCount == 1)
+ {
+ _logger.error("Received first msg after failover ignoring:" + _msgCount);
+
+ // Acknowledge the first message if we are now on the cleaned pass
+ if (_cleaned)
+ {
+ _receivedAll.countDown();
+ }
+
+ return;
+ }
+
+ if (_consumerSession.getTransacted())
+ {
+ try
+ {
+ _consumerSession.commit();
+ if (!_cleaned)
+ {
+ fail("Session is dirty we should get an TransactionRolledBackException");
+ }
+ }
+ catch (TransactionRolledBackException trbe)
+ {
+ //expected path
+ }
+ }
+ else
+ {
+ try
+ {
+ message.acknowledge();
+ if (!_cleaned)
+ {
+ fail("Session is dirty we should get an IllegalStateException");
+ }
+ }
+ catch (javax.jms.IllegalStateException ise)
+ {
+ assertEquals("Incorrect Exception thrown", "has failed over", ise.getMessage());
+ // Recover the sesion and try again.
+ _consumerSession.recover();
+ }
+ }
+
+ // Acknowledge the last message if we are in a clean state
+ // this will then trigger test teardown.
+ if (_cleaned)
+ {
+ _receivedAll.countDown();
+ }
+
+ //Reset message count so we can try again.
+ _msgCount = 0;
+ _cleaned = true;
+ }
+ catch (Exception e)
+ {
+ // If something goes wrong stop and notifiy main thread.
+ fail(e);
+ }
+ }
+ }
+
+ /**
+ * Test that Acking/Committing a message received before failover causes
+ * an exception at commit/ack time.
+ *
+ * Expected behaviour is that in:
+ * * tx mode commit() throws a transacted RolledBackException
+ * * client ack mode throws an IllegalStateException
+ *
+ * @param transacted is this session trasacted
+ * @param mode What ack mode should be used if not trasacted
+ *
+ * @throws Exception if something goes wrong.
+ */
+ protected void testDirtyAcking(boolean transacted, int mode) throws Exception
+ {
+ NUM_MESSAGES = 2;
+ _listener = new DirtyAckingHandler();
+
+ super.testAcking(transacted, mode);
+ }
+
+ public void testDirtyClientAck() throws Exception
+ {
+ testDirtyAcking(false, Session.CLIENT_ACKNOWLEDGE);
+ }
+
+ public void testDirtyAckingTransacted() throws Exception
+ {
+ testDirtyAcking(true, Session.SESSION_TRANSACTED);
+ }
+
+ private void repopulateBroker() throws Exception
+ {
+ // Repopulate this new broker so we can test what happends after failover
+
+ //Get the connection to the first (main port) broker.
+ Connection connection = getConnection();
+ // Use a transaction to send messages so we can be sure they arrive.
+ Session session = connection.createSession(true, Session.SESSION_TRANSACTED);
+ // ensure destination is created.
+ session.createConsumer(_queue).close();
+
+ sendMessage(session, _queue, NUM_MESSAGES);
+
+ assertEquals("Wrong number of messages on queue", NUM_MESSAGES,
+ ((AMQSession) session).getQueueDepth((AMQDestination) _queue));
+
+ connection.close();
+ }
+
+ // AMQConnectionListener Interface.. used so we can validate that we
+ // actually failed over.
+
+ public void bytesSent(long count)
+ {
+ }
+
+ public void bytesReceived(long count)
+ {
+ }
+
+ public boolean preFailover(boolean redirect)
+ {
+ //Allow failover
+ return true;
+ }
+
+ public boolean preResubscribe()
+ {
+ //Allow failover
+ return true;
+ }
+
+ public void failoverComplete()
+ {
+ _failoverCompleted.countDown();
+ }
+
+ /**
+ * Override so we can block until failover has completd
+ *
+ * @param port
+ */
+ @Override
+ public void failBroker(int port)
+ {
+ super.failBroker(port);
+
+ try
+ {
+ if (!_failoverCompleted.await(DEFAULT_FAILOVER_TIME, TimeUnit.MILLISECONDS))
+ {
+ // Use an exception so that we use our local fail() that notifies the main thread of failure
+ throw new Exception("Failover did not occur in specified time:" + DEFAULT_FAILOVER_TIME);
+ }
+
+ }
+ catch (Exception e)
+ {
+ // Use an exception so that we use our local fail() that notifies the main thread of failure
+ fail(e);
+ }
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/AcknowledgeAfterFailoverTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/AcknowledgeAfterFailoverTest.java
new file mode 100644
index 0000000000..acc7d5a4c1
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/AcknowledgeAfterFailoverTest.java
@@ -0,0 +1,317 @@
+/*
+*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License. You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied. See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*
+*/
+package org.apache.qpid.test.unit.ack;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.jms.ConnectionListener;
+import org.apache.qpid.util.FileUtils;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.Session;
+import javax.jms.TransactionRolledBackException;
+import java.io.File;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ *
+ */
+public class AcknowledgeAfterFailoverTest extends AcknowledgeTest implements ConnectionListener
+{
+
+ protected CountDownLatch _failoverCompleted = new CountDownLatch(1);
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ // This must be even for the test to run correctly.
+ // Otherwise we will kill the standby broker
+ // not the one we are connected to.
+ // The test will still pass but it will not be exactly
+ // as described.
+ NUM_MESSAGES = 6;
+ }
+
+ /**
+ * Override default init to add connectionListener so we can verify that
+ * failover took place
+ *
+ * @param transacted create a transacted session for this test
+ * @param mode if not transacted what ack mode to use for this test
+ * @throws Exception if a problem occured during test setup.
+ */
+ @Override
+ protected void init(boolean transacted, int mode) throws Exception
+ {
+ super.init(transacted, mode);
+ ((AMQConnection) _connection).setConnectionListener(this);
+ }
+
+ protected void prepBroker(int index) throws Exception
+ {
+ // If this is the last message then we can skip the prep.
+ if (index == NUM_MESSAGES)
+ {
+ return;
+ }
+
+ if (index % 2 == 0)
+ {
+ failBroker(getFailingPort());
+ // Clean up the failed broker
+ FileUtils.delete(new File(System.getProperty("QPID_WORK") + "/" + getFailingPort()), true);
+ }
+ else
+ {
+ failBroker(getPort());
+ // Clean up the failed broker
+ FileUtils.delete(new File(System.getProperty("QPID_WORK") + "/" + getPort()), true);
+ }
+
+ // Ensure we have the right data on the broker
+ Connection connection = getConnection();
+ Session session = connection.createSession(true, Session.SESSION_TRANSACTED);
+ // ensure destination is created.
+ session.createConsumer(_queue).close();
+
+ sendMessage(session, _queue, 1, index + 1, 0);
+
+ if (_consumerSession.getAcknowledgeMode() != AMQSession.NO_ACKNOWLEDGE)
+ {
+ assertEquals("Wrong number of messages on queue", 1,
+ ((AMQSession) session).getQueueDepth((AMQDestination) _queue));
+ }
+
+ connection.close();
+
+ try
+ {
+ if (index % 2 == 0)
+ {
+ startBroker(getFailingPort());
+ }
+ else
+ {
+ startBroker(getPort());
+ }
+ }
+ catch (Exception e)
+ {
+ fail("Unable to start failover broker," + e.getMessage());
+ }
+ }
+
+ @Override
+ public void doAcknowlegement(Message msg) throws JMSException
+ {
+ //Acknowledge current message
+ super.doAcknowlegement(msg);
+
+ try
+ {
+ prepBroker(msg.getIntProperty(INDEX));
+ }
+ catch (Exception e)
+ {
+ fail("Unable to prep new broker," + e.getMessage());
+ }
+
+ }
+
+ /**
+ * Test that Acking/Committing a message received before failover causes
+ * an exception at commit/ack time.
+ * <p/>
+ * Expected behaviour is that in:
+ * * tx mode commit() throws a transacted RolledBackException
+ * * client ack mode throws an IllegalStateException
+ *
+ * @param transacted is this session trasacted
+ * @param mode What ack mode should be used if not trasacted
+ * @throws Exception if something goes wrong.
+ */
+ protected void testDirtyAcking(boolean transacted, int mode) throws Exception
+ {
+ NUM_MESSAGES = 2;
+ //Test Dirty Failover Fails
+ init(transacted, mode);
+
+ _connection.start();
+
+ Message msg = _consumer.receive(1500);
+
+ int count = 0;
+ assertNotNull("Message " + count + " not correctly received.", msg);
+ assertEquals("Incorrect message received", count, msg.getIntProperty(INDEX));
+
+ //Don't acknowledge just prep the next broker. Without changing count
+ // Prep the new broker to have all all the messages so we can validate
+ // that they can all be correctly received.
+ try
+ {
+
+ //Stop the connection so we can validate the number of message count
+ // on the queue is correct after failover
+ _connection.stop();
+ failBroker(getFailingPort());
+
+ //Get the connection to the first (main port) broker.
+ Connection connection = getConnection();//getConnectionFactory("connection1").getConnectionURL());
+ // Use a transaction to send messages so we can be sure they arrive.
+ Session session = connection.createSession(true, Session.SESSION_TRANSACTED);
+ // ensure destination is created.
+ session.createConsumer(_queue).close();
+
+ sendMessage(session, _queue, NUM_MESSAGES);
+
+ assertEquals("Wrong number of messages on queue", NUM_MESSAGES,
+ ((AMQSession) session).getQueueDepth((AMQDestination) _queue));
+
+ connection.close();
+
+ //restart connection
+ _connection.start();
+ }
+ catch (Exception e)
+ {
+ fail("Unable to prep new broker," + e.getMessage());
+ }
+
+ // Consume the next message - don't check what it is as a normal would
+ // assume it is msg 1 but as we've fallen over it is msg 0 again.
+ msg = _consumer.receive(1500);
+
+ if (_consumerSession.getTransacted())
+ {
+ try
+ {
+ _consumerSession.commit();
+ fail("Session is dirty we should get an TransactionRolledBackException");
+ }
+ catch (TransactionRolledBackException trbe)
+ {
+ //expected path
+ }
+ }
+ else
+ {
+ try
+ {
+ msg.acknowledge();
+ fail("Session is dirty we should get an IllegalStateException");
+ }
+ catch (javax.jms.IllegalStateException ise)
+ {
+ assertEquals("Incorrect Exception thrown", "has failed over", ise.getMessage());
+ // Recover the sesion and try again.
+ _consumerSession.recover();
+ }
+ }
+
+ msg = _consumer.receive(1500);
+ // Validate we now get the first message back
+ assertEquals(0, msg.getIntProperty(INDEX));
+
+ msg = _consumer.receive(1500);
+ // and the second message
+ assertEquals(1, msg.getIntProperty(INDEX));
+
+ // And now verify that we can now commit the clean session
+ if (_consumerSession.getTransacted())
+ {
+ _consumerSession.commit();
+ }
+ else
+ {
+ msg.acknowledge();
+ }
+
+ assertEquals("Wrong number of messages on queue", 0,
+ ((AMQSession) _consumerSession).getQueueDepth((AMQDestination) _queue));
+ }
+
+ public void testDirtyClientAck() throws Exception
+ {
+ testDirtyAcking(false, Session.CLIENT_ACKNOWLEDGE);
+ }
+
+ public void testDirtyAckingTransacted() throws Exception
+ {
+ testDirtyAcking(true, Session.SESSION_TRANSACTED);
+ }
+
+ // AMQConnectionListener Interface.. used so we can validate that we
+ // actually failed over.
+
+ public void bytesSent(long count)
+ {
+ }
+
+ public void bytesReceived(long count)
+ {
+ }
+
+ public boolean preFailover(boolean redirect)
+ {
+ //Allow failover
+ return true;
+ }
+
+ public boolean preResubscribe()
+ {
+ //Allow failover
+ return true;
+ }
+
+ public void failoverComplete()
+ {
+ _failoverCompleted.countDown();
+ }
+
+ /**
+ * Override so we can block until failover has completd
+ *
+ * @param port
+ */
+ @Override
+ public void failBroker(int port)
+ {
+ super.failBroker(port);
+
+ try
+ {
+ if (!_failoverCompleted.await(DEFAULT_FAILOVER_TIME, TimeUnit.MILLISECONDS))
+ {
+ fail("Failover did not occur in specified time:" + DEFAULT_FAILOVER_TIME);
+ }
+ }
+ catch (InterruptedException e)
+ {
+ fail("Failover was interrupted");
+ }
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/AcknowledgeOnMessageTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/AcknowledgeOnMessageTest.java
new file mode 100644
index 0000000000..1b4407f255
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/AcknowledgeOnMessageTest.java
@@ -0,0 +1,226 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.ack;
+
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.JMSAMQException;
+import org.apache.qpid.client.failover.FailoverException;
+
+import javax.jms.Message;
+import javax.jms.MessageListener;
+import javax.jms.Session;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * This test extends the synchronous AcknowledgeTest to use a MessageListener
+ * and receive messages asynchronously.
+ */
+public class AcknowledgeOnMessageTest extends AcknowledgeTest implements MessageListener
+{
+ protected CountDownLatch _receivedAll;
+ protected AtomicReference<Exception> _causeOfFailure = new AtomicReference<Exception>(null);
+
+ @Override
+ public void setUp() throws Exception
+ {
+ super.setUp();
+ }
+
+ /**
+ * Override the synchronous AcknowledgeTest init to provide the _receivedAll
+ * CountDownLatch init and ensure that we set the MessageListener.
+ * @param transacted
+ * @param mode
+ * @throws Exception
+ */
+ @Override
+ public void init(boolean transacted, int mode) throws Exception
+ {
+ _receivedAll = new CountDownLatch(NUM_MESSAGES);
+
+ super.init(transacted, mode);
+ _consumer.setMessageListener(this);
+ }
+
+ /**
+ * This test overrides the testAcking from the simple recieve() model to all
+ * for asynchronous receiving of messages.
+ *
+ * Again the transaction/ack mode is provided to this main test run
+ *
+ * The init method is called which will setup the listener so that we can
+ * then sit and await using the _receivedAll CountDownLatch. We wait for up
+ * to 10s if no messages have been received in the last 10s then test will
+ * fail.
+ *
+ * If the test fails then it will attempt to retrieve any exception that the
+ * asynchronous delivery thread may have recorded.
+ *
+ * @param transacted
+ * @param mode
+ *
+ * @throws Exception
+ */
+ @Override
+ protected void testAcking(boolean transacted, int mode) throws Exception
+ {
+ init(transacted, mode);
+
+ _connection.start();
+
+ // Set the lastCount to NUM_MESSAGES, this ensures that the compare
+ // against the receviedAll count is accurate.
+ int lastCount = NUM_MESSAGES;
+
+ // Wait for messages to arrive
+ boolean complete = _receivedAll.await(10000L, TimeUnit.MILLISECONDS);
+
+ // If the messasges haven't arrived
+ while (!complete)
+ {
+ // Check how many we have received
+ int currentCount = (int) _receivedAll.getCount();
+
+ // make sure we have received a message in the last cycle.
+ if (lastCount == currentCount)
+ {
+ // If we didn't receive any messages then stop.
+ // Something must have gone wrong.
+ System.err.println("Giving up waiting as we didn't receive anything.");
+ break;
+ }
+ // Remember the currentCount as the lastCount for the next cycle.
+ // so we can exit if things get locked up.
+ lastCount = currentCount;
+
+ // Wait again for messages to arrive.
+ complete = _receivedAll.await(10000L, TimeUnit.MILLISECONDS);
+ }
+
+ // If we failed to receive all the messages then fail the test.
+ if (!complete)
+ {
+ // Check to see if we ended due to an exception in the onMessage handler
+ Exception cause = _causeOfFailure.get();
+ if (cause != null)
+ {
+ cause.printStackTrace();
+ fail(cause.getMessage());
+ }
+ else
+ {
+ _logger.info("AOMT: Check QueueDepth:" + _queue);
+ long onQueue=((AMQSession) getConnection().createSession(false, Session.AUTO_ACKNOWLEDGE)).getQueueDepth((AMQDestination) _queue);
+ fail("All messages not received missing:" + _receivedAll.getCount() + "/" + NUM_MESSAGES+" On Queue:"+onQueue);
+
+ }
+ }
+
+ // Even if we received all the messages.
+ // Check to see if we ended due to an exception in the onMessage handler
+ Exception cause = _causeOfFailure.get();
+ if (cause != null)
+ {
+ cause.printStackTrace();
+ fail(cause.getMessage());
+ }
+
+ try
+ {
+ _consumer.close();
+ }
+ catch (JMSAMQException amqe)
+ {
+ if (amqe.getLinkedException() instanceof FailoverException)
+ {
+ fail("QPID-143 : Auto Ack can acknowledge message from previous session after failver. If failover occurs between deliver and ack.");
+ }
+ // else Rethrow for TestCase to catch.
+ throw amqe;
+ }
+
+ _consumerSession.close();
+
+ _logger.info("AOMT: check number of message at end of test.");
+ assertEquals("Wrong number of messages on queue", 0,
+ ((AMQSession) getConnection().createSession(false, Session.AUTO_ACKNOWLEDGE)).getQueueDepth((AMQDestination) _queue));
+ }
+
+ /**
+ * The MessageListener interface that recieves the message and counts down
+ * the _receivedAll CountDownLatch.
+ *
+ * Again like AcknowledgeTest acknowledgement is actually handled in
+ * doAcknowlegement.
+ *
+ * The message INDEX is validated to ensure the correct message order is
+ * preserved.
+ *
+ * @param message
+ */
+ public void onMessage(Message message)
+ {
+ // Log received Message for debugging
+ _logger.info("RECEIVED MESSAGE:" + message);
+
+ try
+ {
+ int count = NUM_MESSAGES - (int) _receivedAll.getCount();
+
+ assertEquals("Incorrect message received", count, message.getIntProperty(INDEX));
+
+ count++;
+ if (count < NUM_MESSAGES)
+ {
+ //Send the next message
+ _producer.send(createNextMessage(_consumerSession, count));
+ }
+
+ doAcknowlegement(message);
+
+ _receivedAll.countDown();
+ }
+ catch (Exception e)
+ {
+ // This will end the test run by counting down _receivedAll
+ fail(e);
+ }
+ }
+
+ /**
+ * Pass the given exception back to the waiting thread to fail the test run.
+ *
+ * @param e The exception that is causing the test to fail.
+ */
+ protected void fail(Exception e)
+ {
+ //record the failure
+ _causeOfFailure.set(e);
+ // End the test.
+ while (_receivedAll.getCount() != 0)
+ {
+ _receivedAll.countDown();
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/AcknowledgeTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/AcknowledgeTest.java
new file mode 100644
index 0000000000..efea57e5d2
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/AcknowledgeTest.java
@@ -0,0 +1,179 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.test.unit.ack;
+
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.test.utils.FailoverBaseCase;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.MessageProducer;
+
+/**
+ * Test the various JMS Acknowledge Modes the single testAcking method does all
+ * the work of receiving and validation of acking.
+ *
+ * The ack mode is provided from the various test methods.
+ */
+public class AcknowledgeTest extends FailoverBaseCase
+{
+ protected int NUM_MESSAGES;
+ protected Connection _connection;
+ protected Queue _queue;
+ protected Session _consumerSession;
+ protected MessageConsumer _consumer;
+ protected MessageProducer _producer;
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ NUM_MESSAGES = 5;
+
+ _queue = getTestQueue();
+
+ _logger.info("AT: setup");
+ //Create Producer put some messages on the queue
+ _connection = getConnection();
+ }
+
+ protected void init(boolean transacted, int mode) throws Exception
+ {
+ _consumerSession = _connection.createSession(transacted, mode);
+ _consumer = _consumerSession.createConsumer(_queue);
+ _producer = _consumerSession.createProducer(_queue);
+
+ // These should all end up being prefetched by session
+ sendMessage(_consumerSession, _queue, 1);
+
+ assertEquals("Wrong number of messages on queue", 1,
+ ((AMQSession) _consumerSession).getQueueDepth((AMQDestination) _queue));
+ }
+
+ /**
+ * The main test method.
+ *
+ * Receive the initial message and then proceed to send and ack messages
+ * until we have processed NUM_MESSAGES worth of messages.
+ *
+ * Each message is tagged with an INDEX value and these are used to check
+ * that the messages are received in the correct order.
+ *
+ * The test concludes by validating that the queue depth is 0 as expected.
+ *
+ * @param transacted
+ * @param mode
+ *
+ * @throws Exception
+ */
+ protected void testAcking(boolean transacted, int mode) throws Exception
+ {
+ init(transacted, mode);
+
+ _connection.start();
+
+ Message msg = _consumer.receive(1500);
+
+ int count = 0;
+ while (count < NUM_MESSAGES)
+ {
+ assertNotNull("Message " + count + " not correctly received.", msg);
+ assertEquals("Incorrect message received", count, msg.getIntProperty(INDEX));
+ count++;
+
+ if (count < NUM_MESSAGES)
+ {
+ //Send the next message
+ _producer.send(createNextMessage(_consumerSession, count));
+ }
+
+ doAcknowlegement(msg);
+
+ msg = _consumer.receive(1500);
+ }
+
+ if (_consumerSession.getTransacted())
+ {
+ //Acknowledge the last msg if we are testing transacted otherwise queueDepth will be 1
+ doAcknowlegement(msg);
+ }
+
+ assertEquals("Wrong number of messages on queue", 0,
+ ((AMQSession) _consumerSession).getQueueDepth((AMQDestination) _queue));
+ }
+
+ /**
+ * Perform the acknowledgement of messages if additionally required.
+ *
+ * @param msg
+ *
+ * @throws JMSException
+ */
+ protected void doAcknowlegement(Message msg) throws JMSException
+ {
+ if (_consumerSession.getTransacted())
+ {
+ _consumerSession.commit();
+ }
+
+ if (_consumerSession.getAcknowledgeMode() == Session.CLIENT_ACKNOWLEDGE)
+ {
+ msg.acknowledge();
+ }
+ }
+
+ public void testClientAck() throws Exception
+ {
+ testAcking(false, Session.CLIENT_ACKNOWLEDGE);
+ }
+
+ public void testAutoAck() throws Exception
+ {
+ testAcking(false, Session.AUTO_ACKNOWLEDGE);
+ }
+
+ public void testTransacted() throws Exception
+ {
+ testAcking(true, Session.SESSION_TRANSACTED);
+ }
+
+ public void testDupsOk() throws Exception
+ {
+ testAcking(false, Session.DUPS_OK_ACKNOWLEDGE);
+ }
+
+ public void testNoAck() throws Exception
+ {
+ testAcking(false, AMQSession.NO_ACKNOWLEDGE);
+ }
+
+ public void testPreAck() throws Exception
+ {
+ testAcking(false, AMQSession.PRE_ACKNOWLEDGE);
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/FailoverBeforeConsumingRecoverTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/FailoverBeforeConsumingRecoverTest.java
new file mode 100644
index 0000000000..834b17430b
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/FailoverBeforeConsumingRecoverTest.java
@@ -0,0 +1,40 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.ack;
+
+import org.apache.qpid.jms.Session;
+
+import javax.jms.Message;
+import javax.jms.Queue;
+
+public class FailoverBeforeConsumingRecoverTest extends RecoverTest
+{
+
+ @Override
+ protected void initTest() throws Exception
+ {
+ super.initTest();
+ failBroker(getFailingPort());
+
+ Queue queue = _consumerSession.createQueue(getTestQueueName());
+ sendMessage(_connection.createSession(false, Session.AUTO_ACKNOWLEDGE), queue, SENT_COUNT);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/QuickAcking.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/QuickAcking.java
new file mode 100644
index 0000000000..6c83136511
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/QuickAcking.java
@@ -0,0 +1,148 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.ack;
+
+import edu.emory.mathcs.backport.java.util.concurrent.CountDownLatch;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.jms.ConnectionListener;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+
+/**
+ * This is a quick manual test to validate acking after failover with a
+ * transacted session.
+ *
+ * Start an external broker then run this test. Std Err will print.
+ * Sent Message: 1
+ * Received Message: 1
+ *
+ * You can then restart the external broker, which will cause failover, which
+ * will be complete when the following appears.
+ *
+ * Failover Complete
+ *
+ * A second message send/receive cycle is then done to validate that the
+ * connection/session are still working.
+ *
+ */
+public class QuickAcking extends QpidBrokerTestCase implements ConnectionListener
+{
+ protected AMQConnection _connection;
+ protected Queue _queue;
+ protected Session _session;
+ protected MessageConsumer _consumer;
+ private CountDownLatch _failedOver;
+ private static final String INDEX = "INDEX";
+ private int _count = 0;
+
+ public void setUp()
+ {
+ // Prevent broker startup. Broker must be run manually.
+ }
+
+ public void test() throws Exception
+ {
+ _failedOver = new CountDownLatch(1);
+
+ _connection = new AMQConnection("amqp://guest:guest@client/test?brokerlist='localhost?retries='20'&connectdelay='2000''");
+
+ _session = _connection.createSession(true, Session.SESSION_TRANSACTED);
+ _queue = _session.createQueue("QAtest");
+ _consumer = _session.createConsumer(_queue);
+ _connection.setConnectionListener(this);
+ _connection.start();
+
+ sendAndReceive();
+
+ _failedOver.await();
+
+ sendAndReceive();
+
+ }
+
+ private void sendAndReceive()
+ throws Exception
+ {
+ sendMessage();
+
+ Message message = _consumer.receive();
+
+ if (message.getIntProperty(INDEX) != _count)
+ {
+ throw new Exception("Incorrect message recieved:" + _count);
+ }
+
+ if (_session.getTransacted())
+ {
+ _session.commit();
+ }
+ System.err.println("Recevied Message:" + _count);
+ }
+
+ private void sendMessage() throws JMSException
+ {
+ MessageProducer producer = _session.createProducer(_queue);
+ Message message = _session.createMessage();
+ _count++;
+ message.setIntProperty(INDEX, _count);
+
+ producer.send(message);
+ if (_session.getTransacted())
+ {
+ _session.commit();
+ }
+ producer.close();
+
+ System.err.println("Sent Message:" + _count);
+ }
+
+ public void bytesSent(long count)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public void bytesReceived(long count)
+ {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean preFailover(boolean redirect)
+ {
+ return true;
+ }
+
+ public boolean preResubscribe()
+ {
+ return true;
+ }
+
+ public void failoverComplete()
+ {
+ System.err.println("Failover Complete");
+ _failedOver.countDown();
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/RecoverTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/RecoverTest.java
new file mode 100644
index 0000000000..5e7ba5482d
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ack/RecoverTest.java
@@ -0,0 +1,450 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.ack;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.jms.Session;
+import org.apache.qpid.test.utils.FailoverBaseCase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.BytesMessage;
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageListener;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.TextMessage;
+
+import java.util.HashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class RecoverTest extends FailoverBaseCase
+{
+ static final Logger _logger = LoggerFactory.getLogger(RecoverTest.class);
+
+ private Exception _error;
+ private AtomicInteger count;
+
+ protected AMQConnection _connection;
+ protected Session _consumerSession;
+ protected MessageConsumer _consumer;
+ static final int SENT_COUNT = 4;
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ _error = null;
+ count = new AtomicInteger();
+ }
+
+ protected void initTest() throws Exception
+ {
+ _connection = (AMQConnection) getConnection("guest", "guest");
+
+ _consumerSession = _connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+ Queue queue = _consumerSession.createQueue(getTestQueueName());
+
+ _consumer = _consumerSession.createConsumer(queue);
+
+ _logger.info("Sending four messages");
+ sendMessage(_connection.createSession(false, Session.AUTO_ACKNOWLEDGE), queue, SENT_COUNT);
+ _logger.info("Starting connection");
+ _connection.start();
+ }
+
+ protected Message validateNextMessages(int nextCount, int startIndex) throws JMSException
+ {
+ Message message = null;
+ for (int index = 0; index < nextCount; index++)
+ {
+ message = _consumer.receive(3000);
+ assertEquals(startIndex + index, message.getIntProperty(INDEX));
+ }
+ return message;
+ }
+
+ protected void validateRemainingMessages(int remaining) throws JMSException
+ {
+ int index = SENT_COUNT - remaining;
+
+ Message message = null;
+ while (index != SENT_COUNT)
+ {
+ message = _consumer.receive(3000);
+ assertNotNull(message);
+ assertEquals(index++, message.getIntProperty(INDEX));
+ }
+
+ if (message != null)
+ {
+ _logger.info("Received redelivery of three messages. Acknowledging last message");
+ message.acknowledge();
+ }
+
+ _logger.info("Calling acknowledge with no outstanding messages");
+ // all acked so no messages to be delivered
+ _consumerSession.recover();
+
+ message = _consumer.receiveNoWait();
+ assertNull(message);
+ _logger.info("No messages redelivered as is expected");
+ }
+
+ public void testRecoverResendsMsgs() throws Exception
+ {
+ initTest();
+
+ Message message = validateNextMessages(1, 0);
+ message.acknowledge();
+ _logger.info("Received and acknowledged first message");
+
+ _consumer.receive();
+ _consumer.receive();
+ _consumer.receive();
+ _logger.info("Received all four messages. Calling recover with three outstanding messages");
+ // no ack for last three messages so when I call recover I expect to get three messages back
+
+ _consumerSession.recover();
+
+ validateRemainingMessages(3);
+ }
+
+ public void testRecoverResendsMsgsAckOnEarlier() throws Exception
+ {
+ initTest();
+
+ Message message = validateNextMessages(2, 0);
+ message.acknowledge();
+ _logger.info("Received 2 messages, acknowledge() first message, should acknowledge both");
+
+ _consumer.receive();
+ _consumer.receive();
+ _logger.info("Received all four messages. Calling recover with two outstanding messages");
+ // no ack for last three messages so when I call recover I expect to get three messages back
+ _consumerSession.recover();
+
+ Message message2 = _consumer.receive(3000);
+ assertNotNull(message2);
+ assertEquals(2, message2.getIntProperty(INDEX));
+
+ Message message3 = _consumer.receive(3000);
+ assertNotNull(message3);
+ assertEquals(3, message3.getIntProperty(INDEX));
+
+ _logger.info("Received redelivery of two messages. calling acknolwedgeThis() first of those message");
+ ((org.apache.qpid.jms.Message) message2).acknowledgeThis();
+
+ _logger.info("Calling recover");
+ // all acked so no messages to be delivered
+ _consumerSession.recover();
+
+ message3 = _consumer.receive(3000);
+ assertNotNull(message3);
+ assertEquals(3, message3.getIntProperty(INDEX));
+ ((org.apache.qpid.jms.Message) message3).acknowledgeThis();
+
+ // all acked so no messages to be delivered
+ validateRemainingMessages(0);
+ }
+
+ public void testAcknowledgePerConsumer() throws Exception
+ {
+ AMQConnection con = (AMQConnection) getConnection("guest", "guest");
+
+ Session consumerSession = con.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+ Queue queue =
+ new AMQQueue(consumerSession.getDefaultQueueExchangeName(), new AMQShortString("Q1"), new AMQShortString("Q1"),
+ false, true);
+ Queue queue2 =
+ new AMQQueue(consumerSession.getDefaultQueueExchangeName(), new AMQShortString("Q2"), new AMQShortString("Q2"),
+ false, true);
+ MessageConsumer consumer = consumerSession.createConsumer(queue);
+ MessageConsumer consumer2 = consumerSession.createConsumer(queue2);
+
+ AMQConnection con2 = (AMQConnection) getConnection("guest", "guest");
+ Session producerSession = con2.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+ MessageProducer producer = producerSession.createProducer(queue);
+ MessageProducer producer2 = producerSession.createProducer(queue2);
+
+ producer.send(producerSession.createTextMessage("msg1"));
+ producer2.send(producerSession.createTextMessage("msg2"));
+
+ con2.close();
+
+ _logger.info("Starting connection");
+ con.start();
+
+ TextMessage tm2 = (TextMessage) consumer2.receive(2000);
+ assertNotNull(tm2);
+ assertEquals("msg2", tm2.getText());
+
+ tm2.acknowledge();
+ consumerSession.recover();
+
+ TextMessage tm1 = (TextMessage) consumer.receive(2000);
+ assertNotNull(tm1);
+ assertEquals("msg1", tm1.getText());
+
+ con.close();
+
+ }
+
+ public void testRecoverInAutoAckListener() throws Exception
+ {
+ AMQConnection con = (AMQConnection) getConnection("guest", "guest");
+
+ final Session consumerSession = con.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ Queue queue =
+ new AMQQueue(consumerSession.getDefaultQueueExchangeName(), new AMQShortString("Q3"), new AMQShortString("Q3"),
+ false, true);
+ MessageConsumer consumer = consumerSession.createConsumer(queue);
+ MessageProducer producer = consumerSession.createProducer(queue);
+ producer.send(consumerSession.createTextMessage("hello"));
+
+ final Object lock = new Object();
+
+ consumer.setMessageListener(new MessageListener()
+ {
+
+ public void onMessage(Message message)
+ {
+ try
+ {
+ count.incrementAndGet();
+ if (count.get() == 1)
+ {
+ if (message.getJMSRedelivered())
+ {
+ setError(new Exception("Message marked as redelivered on what should be first delivery attempt"));
+ }
+
+ consumerSession.recover();
+ }
+ else if (count.get() == 2)
+ {
+ if (!message.getJMSRedelivered())
+ {
+ setError(
+ new Exception("Message not marked as redelivered on what should be second delivery attempt"));
+ }
+ }
+ else
+ {
+ System.err.println(message);
+ fail("Message delivered too many times!: " + count);
+ }
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Error recovering session: " + e, e);
+ setError(e);
+ }
+
+ synchronized (lock)
+ {
+ lock.notify();
+ }
+ }
+ });
+
+ con.start();
+
+ long waitTime = 30000L;
+ long waitUntilTime = System.currentTimeMillis() + waitTime;
+
+ synchronized (lock)
+ {
+ while ((count.get() <= 1) && (waitTime > 0))
+ {
+ lock.wait(waitTime);
+ if (count.get() <= 1)
+ {
+ waitTime = waitUntilTime - System.currentTimeMillis();
+ }
+ }
+ }
+
+ Thread.sleep(1000);
+
+ if (_error != null)
+ {
+ throw _error;
+ }
+
+ assertEquals("Message not received the correct number of times.",
+ 2, count.get());
+ }
+
+ private void setError(Exception e)
+ {
+ _error = e;
+ }
+
+ private void sendMessages(javax.jms.Session session,Destination dest,int count) throws Exception
+ {
+ MessageProducer prod = session.createProducer(dest);
+ for (int i=0; i<count; i++)
+ {
+ prod.send(session.createTextMessage("Msg" + i));
+ }
+ prod.close();
+ }
+
+ /**
+ * Goal : Check if ordering is preserved when doing recovery under reasonable circumstances.
+ * Refer QPID-2471 for more details.
+ * Test strategy :
+ * Send 8 messages to a topic.
+ * The consumer will call recover until it sees a message 5 times,
+ * at which point it will ack that message.
+ * It will continue the above until it acks all the messages.
+ * While doing so it will verify that the messages are not
+ * delivered out of order.
+ */
+ public void testOderingWithSyncConsumer() throws Exception
+ {
+ Connection con = (Connection) getConnection("guest", "guest");
+ javax.jms.Session session = con.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+ Destination topic = session.createTopic("myTopic");
+ MessageConsumer cons = session.createConsumer(topic);
+
+ sendMessages(session,topic,8);
+ con.start();
+
+ int messageSeen = 0;
+ int expectedMsg = 0;
+
+ long startTime = System.currentTimeMillis();
+
+ while(expectedMsg < 8)
+ {
+ // Based on historical data, on average the test takes about 6 secs to complete.
+ if (System.currentTimeMillis() - startTime > 8000)
+ {
+ fail("Test did not complete on time. Received " +
+ expectedMsg + " msgs so far. Please check the logs");
+ }
+
+ Message message = cons.receive(2000);
+ String text=((TextMessage) message).getText();
+
+ assertEquals("Received Message Out Of Order","Msg"+expectedMsg,text);
+
+ //don't ack the message until we receive it 5 times
+ if( messageSeen < 5 )
+ {
+ _logger.debug("Ignoring message " + text + " and calling recover");
+ session.recover();
+ messageSeen++;
+ }
+ else
+ {
+ messageSeen = 0;
+ expectedMsg++;
+ message.acknowledge();
+ _logger.debug("Acknowledging message " + text);
+ }
+ }
+ }
+
+ /**
+ * Goal : Same as testOderingWithSyncConsumer
+ * Test strategy :
+ * Same as testOderingWithSyncConsumer but using a
+ * Message Listener instead of a sync receive().
+ */
+ public void testOderingWithAsyncConsumer() throws Exception
+ {
+ Connection con = (Connection) getConnection("guest", "guest");
+ final javax.jms.Session session = con.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+ Destination topic = session.createTopic("myTopic");
+ MessageConsumer cons = session.createConsumer(topic);
+
+ sendMessages(session,topic,8);
+ con.start();
+
+ final Object lock = new Object();
+ final AtomicBoolean pass = new AtomicBoolean(false); //used as work around for 'final'
+ cons.setMessageListener(new MessageListener()
+ {
+ int messageSeen = 0;
+ int expectedMsg = 0;
+
+ public void onMessage(Message message)
+ {
+ try
+ {
+ String text = ((TextMessage) message).getText();
+ assertEquals("Received Message Out Of Order","Msg"+expectedMsg,text);
+
+ //don't ack the message until we receive it 5 times
+ if( messageSeen < 5 )
+ {
+ _logger.debug("Ignoring message " + text + " and calling recover");
+ session.recover();
+ messageSeen++;
+ }
+ else
+ {
+ messageSeen = 0;
+ expectedMsg++;
+ message.acknowledge();
+ _logger.debug("Acknowledging message " + text);
+ if (expectedMsg == 8)
+ {
+ pass.set(true);
+ synchronized (lock)
+ {
+ lock.notifyAll();
+ }
+ }
+ }
+ }
+ catch (JMSException e)
+ {
+ fail("Exception : " + e.getMessage());
+ synchronized (lock)
+ {
+ lock.notifyAll();
+ }
+ }
+ }
+ });
+
+ synchronized(lock)
+ {
+ // Based on historical data, on average the test takes about 6 secs to complete.
+ lock.wait(8000);
+ }
+
+ if (!pass.get())
+ {
+ fail("Test did not complete on time. Please check the logs");
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/BytesMessageTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/BytesMessageTest.java
new file mode 100644
index 0000000000..59ce64eb4f
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/BytesMessageTest.java
@@ -0,0 +1,284 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.basic;
+
+import junit.framework.Assert;
+
+import org.apache.mina.common.ByteBuffer;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.message.JMSBytesMessage;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.transport.util.Waiter;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.BytesMessage;
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+import javax.jms.MessageNotReadableException;
+import javax.jms.MessageNotWriteableException;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class BytesMessageTest extends QpidBrokerTestCase implements MessageListener
+{
+ private static final Logger _logger = LoggerFactory.getLogger(BytesMessageTest.class);
+
+ private Connection _connection;
+ private Destination _destination;
+ private Session _session;
+ private final List<JMSBytesMessage> received = new ArrayList<JMSBytesMessage>();
+ private final List<byte[]> messages = new ArrayList<byte[]>();
+ private int _count = 100;
+ public String _connectionString = "vm://:1";
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ init((AMQConnection) getConnection("guest", "guest"));
+ }
+
+ protected void tearDown() throws Exception
+ {
+ super.tearDown();
+ }
+
+ void init(AMQConnection connection) throws Exception
+ {
+ init(connection, new AMQQueue(connection, randomize("BytesMessageTest"), true));
+ }
+
+ void init(AMQConnection connection, AMQDestination destination) throws Exception
+ {
+ _connection = connection;
+ _destination = destination;
+ _session = connection.createSession(false, AMQSession.NO_ACKNOWLEDGE);
+
+ // Set up a slow consumer.
+ _session.createConsumer(destination).setMessageListener(this);
+ connection.start();
+ }
+
+ public void test() throws Exception
+ {
+ try
+ {
+ send(_count);
+ waitFor(_count);
+ check();
+ _logger.info("Completed without failure");
+ }
+ finally
+ {
+ _connection.close();
+ }
+ }
+
+ void send(int count) throws JMSException
+ {
+ // create a publisher
+ MessageProducer producer = _session.createProducer(_destination);
+ for (int i = 0; i < count; i++)
+ {
+ BytesMessage msg = _session.createBytesMessage();
+
+ try
+ {
+ msg.readFloat();
+ Assert.fail("Message should not be readable");
+ }
+ catch (MessageNotReadableException mnwe)
+ {
+ // normal execution
+ }
+
+ byte[] data = ("Message " + i).getBytes();
+ msg.writeBytes(data);
+ messages.add(data);
+ producer.send(msg);
+ }
+ }
+
+ void waitFor(int count) throws InterruptedException
+ {
+ synchronized (received)
+ {
+ Waiter w = new Waiter(received, 30000);
+ while (received.size() < count && w.hasTime())
+ {
+ w.await();
+ }
+ }
+ }
+
+ void check() throws JMSException
+ {
+ List<byte[]> actual = new ArrayList<byte[]>();
+ for (JMSBytesMessage m : received)
+ {
+ ByteBuffer buffer = m.getData();
+ byte[] data = new byte[buffer.remaining()];
+ buffer.get(data);
+ actual.add(data);
+
+ // Check Body Write Status
+ try
+ {
+ m.writeBoolean(true);
+ Assert.fail("Message should not be writeable");
+ }
+ catch (MessageNotWriteableException mnwe)
+ {
+ // normal execution
+ }
+
+ m.clearBody();
+
+ try
+ {
+ m.writeBoolean(true);
+ }
+ catch (MessageNotWriteableException mnwe)
+ {
+ Assert.fail("Message should be writeable");
+ }
+
+ // Check property write status
+ try
+ {
+ m.setStringProperty("test", "test");
+ Assert.fail("Message should not be writeable");
+ }
+ catch (MessageNotWriteableException mnwe)
+ {
+ // normal execution
+ }
+
+ m.clearProperties();
+
+ try
+ {
+ m.setStringProperty("test", "test");
+ }
+ catch (MessageNotWriteableException mnwe)
+ {
+ Assert.fail("Message should be writeable");
+ }
+
+ }
+
+ assertEqual(messages.iterator(), actual.iterator());
+ }
+
+ private static void assertEqual(Iterator expected, Iterator actual)
+ {
+ List<String> errors = new ArrayList<String>();
+ while (expected.hasNext() && actual.hasNext())
+ {
+ try
+ {
+ assertEquivalent((byte[]) expected.next(), (byte[]) actual.next());
+ }
+ catch (Exception e)
+ {
+ errors.add(e.getMessage());
+ }
+ }
+ while (expected.hasNext())
+ {
+ errors.add("Expected " + expected.next() + " but no more actual values.");
+ }
+ while (actual.hasNext())
+ {
+ errors.add("Found " + actual.next() + " but no more expected values.");
+ }
+
+ if (!errors.isEmpty())
+ {
+ throw new RuntimeException(errors.toString());
+ }
+ }
+
+ private static void assertEquivalent(byte[] expected, byte[] actual)
+ {
+ if (expected.length != actual.length)
+ {
+ throw new RuntimeException("Expected length " + expected.length + " got " + actual.length);
+ }
+
+ for (int i = 0; i < expected.length; i++)
+ {
+ if (expected[i] != actual[i])
+ {
+ throw new RuntimeException("Failed on byte " + i + " of " + expected.length);
+ }
+ }
+ }
+
+ public void onMessage(Message message)
+ {
+ synchronized (received)
+ {
+ received.add((JMSBytesMessage) message);
+ received.notify();
+ }
+ }
+
+ private static String randomize(String in)
+ {
+ return in + System.currentTimeMillis();
+ }
+
+ public static void main(String[] argv) throws Exception
+ {
+ final String connectionString;
+ final int count;
+ if (argv.length == 0)
+ {
+ connectionString = "vm://:1";
+ count = 100;
+ }
+ else
+ {
+ connectionString = argv[0];
+ count = Integer.parseInt(argv[1]);
+ }
+
+ System.out.println("connectionString = " + connectionString);
+ System.out.println("count = " + count);
+
+ BytesMessageTest test = new BytesMessageTest();
+ test._connectionString = connectionString;
+ test._count = count;
+ test.test();
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/FieldTableMessageTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/FieldTableMessageTest.java
new file mode 100644
index 0000000000..abf8da799c
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/FieldTableMessageTest.java
@@ -0,0 +1,163 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.basic;
+
+import org.apache.mina.common.ByteBuffer;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.message.JMSBytesMessage;
+import org.apache.qpid.framing.AMQFrameDecodingException;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.framing.FieldTableFactory;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.BytesMessage;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+import javax.jms.MessageProducer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class FieldTableMessageTest extends QpidBrokerTestCase implements MessageListener
+{
+ private static final Logger _logger = LoggerFactory.getLogger(FieldTableMessageTest.class);
+
+ private AMQConnection _connection;
+ private AMQDestination _destination;
+ private AMQSession _session;
+ private final ArrayList<JMSBytesMessage> received = new ArrayList<JMSBytesMessage>();
+ private FieldTable _expected;
+ private int _count = 10;
+ public String _connectionString = "vm://:1";
+ private CountDownLatch _waitForCompletion;
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ init( (AMQConnection) getConnection("guest", "guest"));
+ }
+
+ protected void tearDown() throws Exception
+ {
+ super.tearDown();
+ }
+
+ private void init(AMQConnection connection) throws Exception
+ {
+ init(connection, new AMQQueue(connection, randomize("FieldTableMessageTest"), true));
+ }
+
+ private void init(AMQConnection connection, AMQDestination destination) throws Exception
+ {
+ _connection = connection;
+ _destination = destination;
+ _session = (AMQSession) connection.createSession(false, AMQSession.NO_ACKNOWLEDGE);
+
+ // set up a slow consumer
+ _session.createConsumer(destination).setMessageListener(this);
+ connection.start();
+
+ // _expected = new FieldTableTest().load("FieldTableTest2.properties");
+ _expected = load();
+ }
+
+ private FieldTable load() throws IOException
+ {
+ FieldTable result = FieldTableFactory.newFieldTable();
+ result.setLong("one", 1L);
+ result.setLong("two", 2L);
+ result.setLong("three", 3L);
+ result.setLong("four", 4L);
+ result.setLong("five", 5L);
+
+ return result;
+ }
+
+ public void test() throws Exception
+ {
+ int count = _count;
+ _waitForCompletion = new CountDownLatch(_count);
+ send(count);
+ _waitForCompletion.await(20, TimeUnit.SECONDS);
+ check();
+ _logger.info("Completed without failure");
+ _connection.close();
+ }
+
+ void send(int count) throws JMSException, IOException
+ {
+ // create a publisher
+ MessageProducer producer = _session.createProducer(_destination);
+ for (int i = 0; i < count; i++)
+ {
+ BytesMessage msg = _session.createBytesMessage();
+ msg.writeBytes(_expected.getDataAsBytes());
+ producer.send(msg);
+ }
+ }
+
+
+ void check() throws JMSException, AMQFrameDecodingException
+ {
+ for (Object m : received)
+ {
+ ByteBuffer buffer = ((JMSBytesMessage) m).getData();
+ FieldTable actual = FieldTableFactory.newFieldTable(buffer, buffer.remaining());
+ for (String key : _expected.keys())
+ {
+ assertEquals("Values for " + key + " did not match", _expected.getObject(key), actual.getObject(key));
+ }
+ }
+ }
+
+ public void onMessage(Message message)
+ {
+ synchronized (received)
+ {
+ received.add((JMSBytesMessage) message);
+ _waitForCompletion.countDown();
+ }
+ }
+
+ private static String randomize(String in)
+ {
+ return in + System.currentTimeMillis();
+ }
+
+ public static void main(String[] argv) throws Exception
+ {
+ FieldTableMessageTest test = new FieldTableMessageTest();
+ test._connectionString = (argv.length == 0) ? "vm://:1" : argv[0];
+ test.setUp();
+ test._count = (argv.length > 1) ? Integer.parseInt(argv[1]) : 5;
+ test.test();
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/InvalidDestinationTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/InvalidDestinationTest.java
new file mode 100644
index 0000000000..c9f6a22500
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/InvalidDestinationTest.java
@@ -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.
+ *
+ */
+
+package org.apache.qpid.test.unit.basic;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import javax.jms.Session;
+import javax.jms.QueueSession;
+import javax.jms.Queue;
+import javax.jms.QueueSender;
+import javax.jms.TextMessage;
+import javax.jms.InvalidDestinationException;
+
+public class InvalidDestinationTest extends QpidBrokerTestCase
+{
+ private AMQConnection _connection;
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ _connection = (AMQConnection) getConnection("guest", "guest");
+ }
+
+ protected void tearDown() throws Exception
+ {
+ _connection.close();
+ super.tearDown();
+ }
+
+
+
+ public void testInvalidDestination() throws Exception
+ {
+ Queue invalidDestination = new AMQQueue("amq.direct","unknownQ");
+ AMQQueue validDestination = new AMQQueue("amq.direct","knownQ");
+ QueueSession queueSession = _connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ // This is the only easy way to create and bind a queue from the API :-(
+ queueSession.createConsumer(validDestination);
+
+ QueueSender sender = queueSession.createSender(invalidDestination);
+ TextMessage msg = queueSession.createTextMessage("Hello");
+ try
+ {
+ sender.send(msg);
+ fail("Expected InvalidDestinationException");
+ }
+ catch (InvalidDestinationException ex)
+ {
+ // pass
+ }
+ sender.close();
+
+ sender = queueSession.createSender(null);
+ invalidDestination = new AMQQueue("amq.direct","unknownQ");
+
+ try
+ {
+ sender.send(invalidDestination,msg);
+ fail("Expected InvalidDestinationException");
+ }
+ catch (InvalidDestinationException ex)
+ {
+ // pass
+ }
+ sender.send(validDestination,msg);
+ sender.close();
+ validDestination = new AMQQueue("amq.direct","knownQ");
+ sender = queueSession.createSender(validDestination);
+ sender.send(msg);
+
+
+
+
+ }
+
+
+ public static junit.framework.Test suite()
+ {
+
+ return new junit.framework.TestSuite(InvalidDestinationTest.class);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/LargeMessageTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/LargeMessageTest.java
new file mode 100644
index 0000000000..d97e22e024
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/LargeMessageTest.java
@@ -0,0 +1,190 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.test.unit.basic;
+
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+
+public class LargeMessageTest extends QpidBrokerTestCase
+{
+ private static final Logger _logger = LoggerFactory.getLogger(LargeMessageTest.class);
+
+ private Destination _destination;
+ private AMQSession _session;
+ private AMQConnection _connection;
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ try
+ {
+ _connection = (AMQConnection) getConnection("guest", "guest");
+ init( _connection );
+ }
+ catch (Exception e)
+ {
+ fail("Unable to initialilse connection: " + e);
+ }
+ }
+
+ protected void tearDown() throws Exception
+ {
+ _connection.close();
+ super.tearDown();
+ }
+
+ private void init(AMQConnection connection) throws Exception
+ {
+ Destination destination = new AMQQueue(connection, "LargeMessageTest", true);
+ init(connection, destination);
+ }
+
+ private void init(AMQConnection connection, Destination destination) throws Exception
+ {
+ _destination = destination;
+ _session = (AMQSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ connection.start();
+ }
+
+ // Test boundary of 1 packet to 2 packets
+ public void test64kminus9()
+ {
+ checkLargeMessage((64 * 1024) - 9);
+ }
+
+ public void test64kminus8()
+ {
+ checkLargeMessage((64 * 1024)-8);
+ }
+
+ public void test64kminus7()
+ {
+ checkLargeMessage((64 * 1024)-7);
+ }
+
+
+ public void test64kplus1()
+ {
+ checkLargeMessage((64 * 1024) + 1);
+ }
+
+ // Test packet boundary of 3 packtes
+ public void test128kminus1()
+ {
+ checkLargeMessage((128 * 1024) - 1);
+ }
+
+ public void test128k()
+ {
+ checkLargeMessage(128 * 1024);
+ }
+
+ public void test128kplus1()
+ {
+ checkLargeMessage((128 * 1024) + 1);
+ }
+
+ // Testing larger messages
+
+ public void test256k()
+ {
+ checkLargeMessage(256 * 1024);
+ }
+
+ public void test512k()
+ {
+ checkLargeMessage(512 * 1024);
+ }
+
+ public void test1024k()
+ {
+ checkLargeMessage(1024 * 1024);
+ }
+
+ protected void checkLargeMessage(int messageSize)
+ {
+ try
+ {
+ MessageConsumer consumer = _session.createConsumer(_destination);
+ MessageProducer producer = _session.createProducer(_destination);
+ _logger.info("Testing message of size:" + messageSize);
+
+ String _messageText = buildLargeMessage(messageSize);
+
+ _logger.debug("Message built");
+
+ producer.send(_session.createTextMessage(_messageText));
+
+ TextMessage result = (TextMessage) consumer.receive(10000);
+
+ assertNotNull("Null message recevied", result);
+ assertEquals("Message Size", _messageText.length(), result.getText().length());
+ assertEquals("Message content differes", _messageText, result.getText());
+ }
+ catch (JMSException e)
+ {
+ e.printStackTrace();
+ fail("Excpetion occured:" + e.getCause());
+ }
+ }
+
+ private String buildLargeMessage(int size)
+ {
+ StringBuilder builder = new StringBuilder(size);
+
+ char ch = 'a';
+
+ for (int i = 1; i <= size; i++)
+ {
+ builder.append(ch);
+
+ if ((i % 1000) == 0)
+ {
+ ch++;
+ if (ch == ('z' + 1))
+ {
+ ch = 'a';
+ }
+ }
+ }
+
+ return builder.toString();
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(LargeMessageTest.class);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/MapMessageTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/MapMessageTest.java
new file mode 100644
index 0000000000..9f13ddcfdb
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/MapMessageTest.java
@@ -0,0 +1,1271 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.basic;
+
+import junit.framework.Assert;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.message.JMSMapMessage;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.MapMessage;
+import javax.jms.Message;
+import javax.jms.MessageFormatException;
+import javax.jms.MessageListener;
+import javax.jms.MessageNotWriteableException;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class MapMessageTest extends QpidBrokerTestCase implements MessageListener
+{
+ private static final Logger _logger = LoggerFactory.getLogger(MapMessageTest.class);
+
+ private AMQConnection _connection;
+ private Destination _destination;
+ private AMQSession _session;
+ private final List<JMSMapMessage> received = new ArrayList<JMSMapMessage>();
+
+ private static final String MESSAGE = "Message ";
+ private int _count = 100;
+ public String _connectionString = "vm://:1";
+ private byte[] _bytes = { 99, 98, 97, 96, 95 };
+ private static final float _smallfloat = 100.0f;
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ try
+ {
+ init((AMQConnection) getConnection("guest", "guest"));
+ }
+ catch (Exception e)
+ {
+ fail("Unable to initialilse connection: " + e);
+ }
+ }
+
+ protected void tearDown() throws Exception
+ {
+ _logger.info("Tearing Down unit.basic.MapMessageTest");
+ super.tearDown();
+ }
+
+ private void init(AMQConnection connection) throws Exception
+ {
+ Destination destination = new AMQQueue(connection, randomize("MapMessageTest"), true);
+ init(connection, destination);
+ }
+
+ private void init(AMQConnection connection, Destination destination) throws Exception
+ {
+ _connection = connection;
+ _destination = destination;
+ _session = (AMQSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ // set up a slow consumer
+ _session.createConsumer(destination).setMessageListener(this);
+ connection.start();
+ }
+
+ public void test() throws Exception
+ {
+ int count = _count;
+ send(count);
+ waitFor(count);
+ check();
+ _connection.close();
+ }
+
+ void send(int count) throws JMSException
+ {
+ // create a publisher
+ MessageProducer producer = _session.createProducer(_destination);
+ for (int i = 0; i < count; i++)
+ {
+ MapMessage message = _session.createMapMessage();
+
+ setMapValues(message, i);
+
+ producer.send(message);
+ }
+ }
+
+ private void setMapValues(MapMessage message, int i) throws JMSException
+ {
+ message.setBoolean("odd", (i / 2) == 0);
+ message.setByte("byte",Byte.MAX_VALUE);
+ message.setBytes("bytes", _bytes);
+ message.setChar("char",'c');
+ message.setDouble("double", Double.MAX_VALUE);
+ message.setFloat("float", Float.MAX_VALUE);
+ message.setFloat("smallfloat", 100);
+ message.setInt("messageNumber", i);
+ message.setInt("int", Integer.MAX_VALUE);
+ message.setLong("long", Long.MAX_VALUE);
+ message.setShort("short", Short.MAX_VALUE);
+ message.setString("message", MESSAGE + i);
+
+ // Test Setting Object Values
+ message.setObject("object-bool", true);
+ message.setObject("object-byte", Byte.MAX_VALUE);
+ message.setObject("object-bytes", _bytes);
+ message.setObject("object-char", 'c');
+ message.setObject("object-double", Double.MAX_VALUE);
+ message.setObject("object-float", Float.MAX_VALUE);
+ message.setObject("object-int", Integer.MAX_VALUE);
+ message.setObject("object-long", Long.MAX_VALUE);
+ message.setObject("object-short", Short.MAX_VALUE);
+
+ // Set a null String value
+ message.setString("nullString", null);
+ // Highlight protocol problem
+ message.setString("emptyString", "");
+
+ }
+
+ void waitFor(int count) throws Exception
+ {
+ long waitTime = 30000L;
+ long waitUntilTime = System.currentTimeMillis() + 30000L;
+
+ synchronized (received)
+ {
+ while ((received.size() < count) && (waitTime > 0))
+ {
+ if (received.size() < count)
+ {
+ received.wait(waitTime);
+ }
+
+ if (received.size() < count)
+ {
+ waitTime = waitUntilTime - System.currentTimeMillis();
+ }
+ }
+
+ if (received.size() < count)
+ {
+ throw new Exception("Timed-out. Waiting for " + count + " only got " + received.size());
+ }
+ }
+ }
+
+ void check() throws JMSException
+ {
+ int count = 0;
+ for (JMSMapMessage m : received)
+ {
+ testMapValues(m, count);
+
+ testCorrectExceptions(m);
+
+ testMessageWriteStatus(m);
+
+ testPropertyWriteStatus(m);
+
+ count++;
+ }
+ }
+
+ private void testCorrectExceptions(JMSMapMessage m) throws JMSException
+ {
+ testBoolean(m);
+
+ testByte(m);
+
+ testBytes(m);
+
+ testChar(m);
+
+ testDouble(m);
+
+ testFloat(m);
+
+ testInt(m);
+
+ testLong(m);
+
+ testShort(m);
+
+ testString(m);
+ }
+
+ private void testString(JMSMapMessage m) throws JMSException
+ {
+
+ Assert.assertFalse(m.getBoolean("message"));
+
+ try
+ {
+ m.getByte("message");
+ fail("Exception Expected.");
+ }
+ catch (NumberFormatException nfe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getShort("message");
+ fail("Exception Expected.");
+ }
+ catch (NumberFormatException nfe)
+ {
+ // normal execution
+ }
+
+ // Try bad reads
+ try
+ {
+ m.getChar("message");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException npe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getInt("message");
+ fail("Exception Expected.");
+ }
+ catch (NumberFormatException nfe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getLong("message");
+ fail("Exception Expected.");
+ }
+ catch (NumberFormatException nfe)
+ {
+ // normal execution
+ }
+
+ // Try bad reads
+ try
+ {
+ m.getFloat("message");
+ fail("Exception Expected.");
+ }
+ catch (NumberFormatException nfe)
+ {
+ // normal execution
+ }
+ // Try bad reads
+ try
+ {
+ m.getDouble("message");
+ fail("Exception Expected.");
+ }
+ catch (NumberFormatException nfe)
+ {
+ // normal execution
+ }
+ // Try bad reads
+ try
+ {
+ m.getBytes("message");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ Assert.assertEquals(MESSAGE + m.getInt("messageNumber"), m.getString("message"));
+ }
+
+ private void testShort(JMSMapMessage m) throws JMSException
+ {
+
+ // Try bad reads
+ try
+ {
+ m.getBoolean("short");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getByte("short");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ Assert.assertEquals(Short.MAX_VALUE, m.getShort("short"));
+
+ // Try bad reads
+ try
+ {
+ m.getChar("short");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException npe)
+ {
+ // normal execution
+ }
+
+ Assert.assertEquals(Short.MAX_VALUE, m.getInt("short"));
+
+ Assert.assertEquals(Short.MAX_VALUE, m.getLong("short"));
+
+ // Try bad reads
+ try
+ {
+ m.getFloat("short");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+ // Try bad reads
+ try
+ {
+ m.getDouble("short");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+ // Try bad reads
+ try
+ {
+ m.getBytes("short");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ Assert.assertEquals("" + Short.MAX_VALUE, m.getString("short"));
+ }
+
+ private void testLong(JMSMapMessage m) throws JMSException
+ {
+
+ // Try bad reads
+ try
+ {
+ m.getBoolean("long");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getByte("long");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getShort("long");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ // Try bad reads
+ try
+ {
+ m.getChar("long");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException npe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getInt("long");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ Assert.assertEquals(Long.MAX_VALUE, m.getLong("long"));
+
+ // Try bad reads
+ try
+ {
+ m.getFloat("long");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+ // Try bad reads
+ try
+ {
+ m.getDouble("long");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+ // Try bad reads
+ try
+ {
+ m.getBytes("long");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ Assert.assertEquals("" + Long.MAX_VALUE, m.getString("long"));
+ }
+
+ private void testDouble(JMSMapMessage m) throws JMSException
+ {
+
+ // Try bad reads
+ try
+ {
+ m.getBoolean("double");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getByte("double");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getShort("double");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ // Try bad reads
+ try
+ {
+ m.getChar("double");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException npe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getInt("double");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getLong("double");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ // Try bad reads
+ try
+ {
+ m.getFloat("double");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ Assert.assertEquals(Double.MAX_VALUE, m.getDouble("double"));
+
+ // Try bad reads
+ try
+ {
+ m.getBytes("double");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ Assert.assertEquals("" + Double.MAX_VALUE, m.getString("double"));
+ }
+
+ private void testFloat(JMSMapMessage m) throws JMSException
+ {
+
+ // Try bad reads
+ try
+ {
+ m.getBoolean("float");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getByte("float");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getShort("float");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ // Try bad reads
+ try
+ {
+ m.getChar("float");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException npe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getInt("float");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getLong("float");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ Assert.assertEquals(Float.MAX_VALUE, m.getFloat("float"));
+
+ Assert.assertEquals(_smallfloat, (float) m.getDouble("smallfloat"));
+
+ // Try bad reads
+ try
+ {
+ m.getBytes("float");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ Assert.assertEquals("" + Float.MAX_VALUE, m.getString("float"));
+ }
+
+ private void testInt(JMSMapMessage m) throws JMSException
+ {
+
+ // Try bad reads
+ try
+ {
+ m.getBoolean("int");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getByte("int");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getShort("int");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ // Try bad reads
+ try
+ {
+ m.getChar("int");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException npe)
+ {
+ // normal execution
+ }
+
+ Assert.assertEquals(Integer.MAX_VALUE, m.getInt("int"));
+
+ Assert.assertEquals(Integer.MAX_VALUE, (int) m.getLong("int"));
+
+ // Try bad reads
+ try
+ {
+ m.getFloat("int");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+ // Try bad reads
+ try
+ {
+ m.getDouble("int");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+ // Try bad reads
+ try
+ {
+ m.getBytes("int");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ Assert.assertEquals("" + Integer.MAX_VALUE, m.getString("int"));
+ }
+
+ private void testChar(JMSMapMessage m) throws JMSException
+ {
+
+ // Try bad reads
+ try
+ {
+ m.getBoolean("char");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getByte("char");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getShort("char");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ Assert.assertEquals('c', m.getChar("char"));
+
+ try
+ {
+ m.getInt("char");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getLong("char");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ // Try bad reads
+ try
+ {
+ m.getFloat("char");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+ // Try bad reads
+ try
+ {
+ m.getDouble("char");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+ // Try bad reads
+ try
+ {
+ m.getBytes("char");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ Assert.assertEquals("" + 'c', m.getString("char"));
+ }
+
+ private void testBytes(JMSMapMessage m) throws JMSException
+ {
+ // Try bad reads
+ try
+ {
+ m.getBoolean("bytes");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getByte("bytes");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getShort("bytes");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ // Try bad reads
+ try
+ {
+ m.getChar("bytes");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException npe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getInt("bytes");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ try
+ {
+ m.getLong("bytes");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ // Try bad reads
+ try
+ {
+ m.getFloat("bytes");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+ // Try bad reads
+ try
+ {
+ m.getDouble("bytes");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ assertBytesEqual(_bytes, m.getBytes("bytes"));
+
+ try
+ {
+ m.getString("bytes");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ }
+
+ private void testByte(JMSMapMessage m) throws JMSException
+ {
+ // Try bad reads
+ try
+ {
+ m.getBoolean("byte");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ Assert.assertEquals(Byte.MAX_VALUE, m.getByte("byte"));
+
+ Assert.assertEquals((short) Byte.MAX_VALUE, m.getShort("byte"));
+
+ // Try bad reads
+ try
+ {
+ m.getChar("byte");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException npe)
+ {
+ // normal execution
+ }
+
+ // Reading a byte as an int is ok
+ Assert.assertEquals((short) Byte.MAX_VALUE, m.getInt("byte"));
+
+ Assert.assertEquals((short) Byte.MAX_VALUE, m.getLong("byte"));
+
+ // Try bad reads
+ try
+ {
+ m.getFloat("byte");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+ // Try bad reads
+ try
+ {
+ m.getDouble("byte");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+ // Try bad reads
+ try
+ {
+ m.getBytes("byte");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ Assert.assertEquals("" + Byte.MAX_VALUE, m.getString("byte"));
+
+ }
+
+ private void testBoolean(JMSMapMessage m) throws JMSException
+ {
+
+ Assert.assertEquals((m.getInt("messageNumber") / 2) == 0, m.getBoolean("odd"));
+
+ // Try bad reads
+ try
+ {
+ m.getByte("odd");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ // Try bad reads
+ try
+ {
+ m.getShort("odd");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+ // Try bad reads
+ try
+ {
+ m.getChar("odd");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException npe)
+ {
+ // normal execution
+ }
+ // Try bad reads
+ try
+ {
+ m.getInt("odd");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+ // Try bad reads
+ try
+ {
+ m.getLong("odd");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+ // Try bad reads
+ try
+ {
+ m.getFloat("odd");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+ // Try bad reads
+ try
+ {
+ m.getDouble("odd");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+ // Try bad reads
+ try
+ {
+ m.getBytes("odd");
+ fail("Exception Expected.");
+ }
+ catch (MessageFormatException nfe)
+ {
+ // normal execution
+ }
+
+ Assert.assertEquals("" + ((m.getInt("messageNumber") / 2) == 0), m.getString("odd"));
+ }
+
+ private void testPropertyWriteStatus(JMSMapMessage m) throws JMSException
+ {
+ // Check property write status
+ try
+ {
+ m.setStringProperty("test", "test");
+ Assert.fail("Message should not be writeable");
+ }
+ catch (MessageNotWriteableException mnwe)
+ {
+ // normal execution
+ }
+
+ m.clearProperties();
+
+ try
+ {
+ m.setStringProperty("test", "test");
+ }
+ catch (MessageNotWriteableException mnwe)
+ {
+ Assert.fail("Message should be writeable");
+ }
+ }
+
+ private void testMessageWriteStatus(JMSMapMessage m) throws JMSException
+ {
+ try
+ {
+ m.setInt("testint", 3);
+ fail("Message should not be writeable");
+ }
+ catch (MessageNotWriteableException mnwe)
+ {
+ // normal execution
+ }
+
+ m.clearBody();
+
+ try
+ {
+ m.setInt("testint", 3);
+ }
+ catch (MessageNotWriteableException mnwe)
+ {
+ Assert.fail("Message should be writeable");
+ }
+ }
+
+ private void testMapValues(JMSMapMessage m, int count) throws JMSException
+ {
+ // Test get<Primiative>
+
+ // Boolean
+ assertEqual((count / 2) == 0, m.getBoolean("odd"));
+ assertEqual("" + ((count / 2) == 0), m.getString("odd"));
+
+ // Byte
+ assertEqual(Byte.MAX_VALUE, m.getByte("byte"));
+ assertEqual("" + Byte.MAX_VALUE, m.getString("byte"));
+
+ // Bytes
+ assertBytesEqual(_bytes, m.getBytes("bytes"));
+
+ // Char
+ assertEqual('c', m.getChar("char"));
+
+ // Double
+ assertEqual(Double.MAX_VALUE, m.getDouble("double"));
+ assertEqual("" + Double.MAX_VALUE, m.getString("double"));
+
+ // Float
+ assertEqual(Float.MAX_VALUE, m.getFloat("float"));
+ assertEqual(_smallfloat, (float) m.getDouble("smallfloat"));
+ assertEqual("" + Float.MAX_VALUE, m.getString("float"));
+
+ // Integer
+ assertEqual(Integer.MAX_VALUE, m.getInt("int"));
+ assertEqual("" + Integer.MAX_VALUE, m.getString("int"));
+ assertEqual(count, m.getInt("messageNumber"));
+
+ // long
+ assertEqual(Long.MAX_VALUE, m.getLong("long"));
+ assertEqual("" + Long.MAX_VALUE, m.getString("long"));
+
+ // Short
+ assertEqual(Short.MAX_VALUE, m.getShort("short"));
+ assertEqual("" + Short.MAX_VALUE, m.getString("short"));
+ assertEqual((int) Short.MAX_VALUE, m.getInt("short"));
+
+ // String
+ assertEqual(MESSAGE + count, m.getString("message"));
+
+ // Test getObjects
+ assertEqual(true, m.getObject("object-bool"));
+ assertEqual(Byte.MAX_VALUE, m.getObject("object-byte"));
+ assertBytesEqual(_bytes, (byte[]) m.getObject("object-bytes"));
+ assertEqual('c', m.getObject("object-char"));
+ assertEqual(Double.MAX_VALUE, m.getObject("object-double"));
+ assertEqual(Float.MAX_VALUE, m.getObject("object-float"));
+ assertEqual(Integer.MAX_VALUE, m.getObject("object-int"));
+ assertEqual(Long.MAX_VALUE, m.getObject("object-long"));
+ assertEqual(Short.MAX_VALUE, m.getObject("object-short"));
+
+ // Check Special values
+ assertTrue(m.getString("nullString") == null);
+ assertEqual("", m.getString("emptyString"));
+ }
+
+ private void assertBytesEqual(byte[] expected, byte[] actual)
+ {
+ Assert.assertEquals(expected.length, actual.length);
+
+ for (int index = 0; index < expected.length; index++)
+ {
+ Assert.assertEquals(expected[index], actual[index]);
+ }
+ }
+
+ private static void assertEqual(Iterator expected, Iterator actual)
+ {
+ List<String> errors = new ArrayList<String>();
+ while (expected.hasNext() && actual.hasNext())
+ {
+ try
+ {
+ assertEqual(expected.next(), actual.next());
+ }
+ catch (Exception e)
+ {
+ errors.add(e.getMessage());
+ }
+ }
+ while (expected.hasNext())
+ {
+ errors.add("Expected " + expected.next() + " but no more actual values.");
+ }
+ while (actual.hasNext())
+ {
+ errors.add("Found " + actual.next() + " but no more expected values.");
+ }
+
+ if (!errors.isEmpty())
+ {
+ throw new RuntimeException(errors.toString());
+ }
+ }
+
+ private static void assertEqual(Object expected, Object actual)
+ {
+ if (!expected.equals(actual))
+ {
+ throw new RuntimeException("Expected '" + expected + "' found '" + actual + "'");
+ }
+ }
+
+ public void onMessage(Message message)
+ {
+ synchronized (received)
+ {
+ _logger.info("****************** Recevied Messgage:" + message);
+ received.add((JMSMapMessage) message);
+ received.notify();
+ }
+ }
+
+ private static String randomize(String in)
+ {
+ return in + System.currentTimeMillis();
+ }
+
+ public static void main(String[] argv) throws Exception
+ {
+ MapMessageTest test = new MapMessageTest();
+ test._connectionString = (argv.length == 0) ? "vm://:1" : argv[0];
+ test.setUp();
+ if (argv.length > 1)
+ {
+ test._count = Integer.parseInt(argv[1]);
+ }
+
+ test.test();
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(MapMessageTest.class);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/MultipleConnectionTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/MultipleConnectionTest.java
new file mode 100644
index 0000000000..3a5f676ca6
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/MultipleConnectionTest.java
@@ -0,0 +1,218 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.basic;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.AMQTopic;
+import org.apache.qpid.client.transport.TransportConnection;
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+
+public class MultipleConnectionTest extends QpidBrokerTestCase
+{
+ private static final Logger _logger = LoggerFactory.getLogger(MultipleConnectionTest.class);
+
+ public static final String _defaultBroker = "vm://:1";
+ public String _connectionString = _defaultBroker;
+
+ private class Receiver
+ {
+ private AMQConnection _connection;
+ private Session[] _sessions;
+ private MessageCounter[] _counters;
+
+ Receiver(String broker, AMQDestination dest, int sessions) throws Exception
+ {
+ this((AMQConnection) getConnection("guest", "guest"), dest, sessions);
+ }
+
+ Receiver(AMQConnection connection, AMQDestination dest, int sessions) throws Exception
+ {
+ _connection = connection;
+ _sessions = new AMQSession[sessions];
+ _counters = new MessageCounter[sessions];
+ for (int i = 0; i < sessions; i++)
+ {
+ _sessions[i] = _connection.createSession(false, AMQSession.NO_ACKNOWLEDGE);
+ _counters[i] = new MessageCounter(_sessions[i].toString());
+ _sessions[i].createConsumer(dest).setMessageListener(_counters[i]);
+ }
+
+ _connection.start();
+ }
+
+ void close() throws JMSException
+ {
+ _connection.close();
+ }
+ }
+
+ private class Publisher
+ {
+ private AMQConnection _connection;
+ private Session _session;
+ private MessageProducer _producer;
+
+ Publisher(String broker, AMQDestination dest) throws Exception
+ {
+ this((AMQConnection) getConnection("guest", "guest"), dest);
+ }
+
+ Publisher(AMQConnection connection, AMQDestination dest) throws Exception
+ {
+ _connection = connection;
+ _session = _connection.createSession(false, AMQSession.NO_ACKNOWLEDGE);
+ _producer = _session.createProducer(dest);
+ }
+
+ void send(String msg) throws JMSException
+ {
+ _producer.send(_session.createTextMessage(msg));
+ }
+
+ void close() throws JMSException
+ {
+ _connection.close();
+ }
+ }
+
+ private static class MessageCounter implements MessageListener
+ {
+ private final String _name;
+ private int _count;
+
+ MessageCounter(String name)
+ {
+ _name = name;
+ }
+
+ public synchronized void onMessage(Message message)
+ {
+ _count++;
+ notify();
+ }
+
+ synchronized boolean waitUntil(int expected, long maxWait) throws InterruptedException
+ {
+ long start = System.currentTimeMillis();
+ while (expected > _count)
+ {
+ long timeLeft = maxWait - timeSince(start);
+ if (timeLeft <= 0)
+ {
+ break;
+ }
+
+ wait(timeLeft);
+ }
+
+ return expected <= _count;
+ }
+
+ private long timeSince(long start)
+ {
+ return System.currentTimeMillis() - start;
+ }
+
+ public synchronized String toString()
+ {
+ return _name + ": " + _count;
+ }
+ }
+
+ private static void waitForCompletion(int expected, long wait, Receiver[] receivers) throws InterruptedException
+ {
+ for (int i = 0; i < receivers.length; i++)
+ {
+ waitForCompletion(expected, wait, receivers[i]._counters);
+ }
+ }
+
+ private static void waitForCompletion(int expected, long wait, MessageCounter[] counters) throws InterruptedException
+ {
+ for (int i = 0; i < counters.length; i++)
+ {
+ if (!counters[i].waitUntil(expected, wait))
+ {
+ throw new RuntimeException("Expected: " + expected + " got " + counters[i]);
+ }
+ }
+ }
+
+ private static String randomize(String in)
+ {
+ return in + System.currentTimeMillis();
+ }
+
+ public static void main(String[] argv) throws Exception
+ {
+ String broker = (argv.length > 0) ? argv[0] : _defaultBroker;
+
+ MultipleConnectionTest test = new MultipleConnectionTest();
+ test._connectionString = broker;
+ test.test();
+ }
+
+ public void test() throws Exception
+ {
+ String broker = _connectionString;
+ int messages = 10;
+
+ AMQTopic topic = new AMQTopic(ExchangeDefaults.TOPIC_EXCHANGE_NAME, "amq.topic");
+
+ Receiver[] receivers = new Receiver[] { new Receiver(broker, topic, 2), new Receiver(broker, topic, 14) };
+
+ Publisher publisher = new Publisher(broker, topic);
+ for (int i = 0; i < messages; i++)
+ {
+ publisher.send("Message " + (i + 1));
+ }
+
+ try
+ {
+ waitForCompletion(messages, 5000, receivers);
+ _logger.info("All receivers received all expected messages");
+ }
+ finally
+ {
+ publisher.close();
+ for (int i = 0; i < receivers.length; i++)
+ {
+ receivers[i].close();
+ }
+ }
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(MultipleConnectionTest.class);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/ObjectMessageTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/ObjectMessageTest.java
new file mode 100644
index 0000000000..c8e7368092
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/ObjectMessageTest.java
@@ -0,0 +1,278 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.basic;
+
+import junit.framework.Assert;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.message.JMSObjectMessage;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+import javax.jms.MessageNotWriteableException;
+import javax.jms.MessageProducer;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+public class ObjectMessageTest extends QpidBrokerTestCase implements MessageListener
+{
+ private static final Logger _logger = LoggerFactory.getLogger(ObjectMessageTest.class);
+
+ private AMQConnection _connection;
+ private AMQDestination _destination;
+ private AMQSession _session;
+ private final List<JMSObjectMessage> received = new ArrayList<JMSObjectMessage>();
+ private final List<Payload> messages = new ArrayList<Payload>();
+ private int _count = 100;
+ public String _connectionString = "vm://:1";
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ try
+ {
+ init( (AMQConnection) getConnection("guest", "guest"));
+ }
+ catch (Exception e)
+ {
+ fail("Uable to initialise: " + e);
+ }
+ }
+
+ protected void tearDown() throws Exception
+ {
+ super.tearDown();
+ }
+
+ private void init(AMQConnection connection) throws Exception
+ {
+ init(connection, new AMQQueue(connection, randomize("ObjectMessageTest"), true));
+ }
+
+ private void init(AMQConnection connection, AMQDestination destination) throws Exception
+ {
+ _connection = connection;
+ _destination = destination;
+ _session = (AMQSession) connection.createSession(false, AMQSession.NO_ACKNOWLEDGE);
+
+ // set up a slow consumer
+ _session.createConsumer(destination).setMessageListener(this);
+ connection.start();
+ }
+
+ public void test() throws Exception
+ {
+ int count = _count;
+ send(count);
+ waitFor(count);
+ check();
+ _logger.info("Completed without failure");
+ _connection.close();
+ }
+
+ void send(int count) throws JMSException
+ {
+ // create a publisher
+ MessageProducer producer = _session.createProducer(_destination);
+ for (int i = 0; i < count; i++)
+ {
+ Payload payload = new Payload("Message " + i);
+ messages.add(payload);
+ producer.send(_session.createObjectMessage(payload));
+ }
+ }
+
+ void waitFor(int count) throws InterruptedException
+ {
+ synchronized (received)
+ {
+ long endTime = System.currentTimeMillis() + 30000L;
+ while (received.size() < count)
+ {
+ received.wait(30000);
+ if(received.size() < count && System.currentTimeMillis() > endTime)
+ {
+ throw new RuntimeException("Only received " + received.size() + " messages, was expecting " + count);
+ }
+ }
+ }
+ }
+
+ void check() throws JMSException
+ {
+ List<Object> actual = new ArrayList<Object>();
+ for (JMSObjectMessage m : received)
+ {
+ actual.add(m.getObject());
+
+ try
+ {
+ m.setObject("Test text");
+ Assert.fail("Message should not be writeable");
+ }
+ catch (MessageNotWriteableException mnwe)
+ {
+ // normal execution
+ }
+
+ m.clearBody();
+
+ try
+ {
+ m.setObject("Test text");
+ }
+ catch (MessageNotWriteableException mnwe)
+ {
+ Assert.fail("Message should be writeable");
+ }
+
+ // Check property write status
+ try
+ {
+ m.setStringProperty("test", "test");
+ Assert.fail("Message should not be writeable");
+ }
+ catch (MessageNotWriteableException mnwe)
+ {
+ // normal execution
+ }
+
+ m.clearProperties();
+
+ try
+ {
+ m.setStringProperty("test", "test");
+ }
+ catch (MessageNotWriteableException mnwe)
+ {
+ Assert.fail("Message should be writeable");
+ }
+
+ }
+
+ assertEqual(messages.iterator(), actual.iterator());
+
+ }
+
+ private static void assertEqual(Iterator expected, Iterator actual)
+ {
+ List<String> errors = new ArrayList<String>();
+ while (expected.hasNext() && actual.hasNext())
+ {
+ try
+ {
+ assertEqual(expected.next(), actual.next());
+ }
+ catch (Exception e)
+ {
+ errors.add(e.getMessage());
+ }
+ }
+ while (expected.hasNext())
+ {
+ errors.add("Expected " + expected.next() + " but no more actual values.");
+ }
+ while (actual.hasNext())
+ {
+ errors.add("Found " + actual.next() + " but no more expected values.");
+ }
+
+ if (!errors.isEmpty())
+ {
+ throw new RuntimeException(errors.toString());
+ }
+ }
+
+ private static void assertEqual(Object expected, Object actual)
+ {
+ if (!expected.equals(actual))
+ {
+ throw new RuntimeException("Expected '" + expected + "' found '" + actual + "'");
+ }
+ }
+
+ public void onMessage(Message message)
+ {
+ synchronized (received)
+ {
+ received.add((JMSObjectMessage) message);
+ received.notify();
+ }
+ }
+
+ private static String randomize(String in)
+ {
+ return in + System.currentTimeMillis();
+ }
+
+ private static class Payload implements Serializable
+ {
+ private final String data;
+
+ Payload(String data)
+ {
+ this.data = data;
+ }
+
+ public int hashCode()
+ {
+ return data.hashCode();
+ }
+
+ public boolean equals(Object o)
+ {
+ return (o instanceof Payload) && ((Payload) o).data.equals(data);
+ }
+
+ public String toString()
+ {
+ return "Payload[" + data + "]";
+ }
+ }
+
+ public static void main(String[] argv) throws Exception
+ {
+ ObjectMessageTest test = new ObjectMessageTest();
+ test._connectionString = (argv.length == 0) ? "vm://:1" : argv[0];
+ test.setUp();
+ if (argv.length > 1)
+ {
+ test._count = Integer.parseInt(argv[1]);
+ }
+
+ test.test();
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(ObjectMessageTest.class);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/PropertyValueTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/PropertyValueTest.java
new file mode 100644
index 0000000000..3b8b4946da
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/PropertyValueTest.java
@@ -0,0 +1,408 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.basic;
+
+import junit.framework.Assert;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.message.JMSTextMessage;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.url.BindingURL;
+import org.apache.qpid.url.AMQBindingURL;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageFormatException;
+import javax.jms.MessageListener;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.net.URISyntaxException;
+
+import java.lang.reflect.*;
+
+public class PropertyValueTest extends QpidBrokerTestCase implements MessageListener
+{
+ private static final Logger _logger = LoggerFactory.getLogger(PropertyValueTest.class);
+
+ private int count = 0;
+ private AMQConnection _connection;
+ private Destination _destination;
+ private AMQSession _session;
+ private final List<JMSTextMessage> received = new ArrayList<JMSTextMessage>();
+ private final List<String> messages = new ArrayList<String>();
+ private int _count = 1;
+ public String _connectionString = "vm://:1";
+ private static final String USERNAME = "guest";
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ }
+
+ protected void tearDown() throws Exception
+ {
+ super.tearDown();
+ }
+
+ private void init(AMQConnection connection) throws Exception
+ {
+ Destination destination = new AMQQueue(connection, randomize("PropertyValueTest"), true);
+ init(connection, destination);
+ }
+
+ private void init(AMQConnection connection, Destination destination) throws Exception
+ {
+ _connection = connection;
+ _destination = destination;
+ _session = (AMQSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ // set up a slow consumer
+ _session.createConsumer(destination).setMessageListener(this);
+ connection.start();
+ }
+
+ private Message getTestMessage() throws Exception
+ {
+ Connection conn = getConnection();
+ Session ssn = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ return ssn.createTextMessage();
+ }
+
+ public void testGetNonexistent() throws Exception
+ {
+ Message m = getTestMessage();
+ String s = m.getStringProperty("nonexistent");
+ assertNull(s);
+ }
+
+ private static final String[] NAMES = {
+ "setBooleanProperty", "setByteProperty", "setShortProperty",
+ "setIntProperty", "setLongProperty", "setFloatProperty",
+ "setDoubleProperty", "setObjectProperty"
+ };
+
+ private static final Class[] TYPES = {
+ boolean.class, byte.class, short.class, int.class, long.class,
+ float.class, double.class, Object.class
+ };
+
+ private static final Object[] VALUES = {
+ true, (byte) 0, (short) 0, 0, (long) 0, (float) 0, (double) 0,
+ new Object()
+ };
+
+ public void testSetEmptyPropertyName() throws Exception
+ {
+ Message m = getTestMessage();
+
+ for (int i = 0; i < NAMES.length; i++)
+ {
+ Method meth = m.getClass().getMethod(NAMES[i], String.class, TYPES[i]);
+ try
+ {
+ meth.invoke(m, "", VALUES[i]);
+ fail("expected illegal argument exception");
+ }
+ catch (InvocationTargetException e)
+ {
+ assertEquals(e.getCause().getClass(), IllegalArgumentException.class);
+ }
+ }
+ }
+
+ public void testSetDisallowedClass() throws Exception
+ {
+ Message m = getTestMessage();
+ try
+ {
+ m.setObjectProperty("foo", new Object());
+ fail("expected a MessageFormatException");
+ }
+ catch (MessageFormatException e)
+ {
+ // pass
+ }
+ }
+
+ public void testOnce()
+ {
+ runBatch(1);
+ }
+
+ public void test50()
+ {
+ runBatch(50);
+ }
+
+ private void runBatch(int runSize)
+ {
+ try
+ {
+ int run = 0;
+ while (run < runSize)
+ {
+ _logger.error("Run Number:" + run++);
+ try
+ {
+ init( (AMQConnection) getConnection("guest", "guest"));
+ }
+ catch (Exception e)
+ {
+ _logger.error("exception:", e);
+ fail("Unable to initialilse connection: " + e);
+ }
+
+ int count = _count;
+ send(count);
+ waitFor(count);
+ check();
+ _logger.info("Completed without failure");
+
+ Thread.sleep(10);
+ _connection.close();
+
+ _logger.error("End Run Number:" + (run - 1));
+ }
+ }
+ catch (Exception e)
+ {
+ _logger.error(e.getMessage(), e);
+ e.printStackTrace();
+ }
+ }
+
+ void send(int count) throws JMSException
+ {
+ // create a publisher
+ MessageProducer producer = _session.createProducer(_destination);
+ for (int i = 0; i < count; i++)
+ {
+ String text = "Message " + i;
+ messages.add(text);
+ Message m = _session.createTextMessage(text);
+
+ m.setBooleanProperty("Bool", true);
+
+ m.setByteProperty("Byte", (byte) Byte.MAX_VALUE);
+ m.setDoubleProperty("Double", (double) Double.MAX_VALUE);
+ m.setFloatProperty("Float", (float) Float.MAX_VALUE);
+ m.setIntProperty("Int", (int) Integer.MAX_VALUE);
+
+ m.setJMSCorrelationID("Correlation");
+ // fixme the m.setJMSMessage has no effect
+ producer.setPriority(8);
+ m.setJMSPriority(3);
+
+ // Queue
+ Queue q;
+
+ if ((i / 2) == 0)
+ {
+ q = _session.createTemporaryQueue();
+ }
+ else
+ {
+ q = new AMQQueue(_connection, "TestReply");
+ }
+
+ m.setJMSReplyTo(q);
+ m.setStringProperty("TempQueue", q.toString());
+
+ _logger.debug("Message:" + m);
+
+ Assert.assertEquals("Check temp queue has been set correctly", m.getJMSReplyTo().toString(),
+ m.getStringProperty("TempQueue"));
+
+ m.setJMSType("Test");
+ m.setLongProperty("UnsignedInt", (long) 4294967295L);
+ m.setLongProperty("Long", (long) Long.MAX_VALUE);
+
+ m.setShortProperty("Short", (short) Short.MAX_VALUE);
+ m.setStringProperty("String", "Test");
+
+ _logger.debug("Sending Msg:" + m);
+ producer.send(m);
+ }
+ }
+
+ void waitFor(int count) throws InterruptedException
+ {
+ synchronized (received)
+ {
+ while (received.size() < count)
+ {
+ received.wait();
+ }
+ }
+ }
+
+ void check() throws JMSException, URISyntaxException
+ {
+ List<String> actual = new ArrayList<String>();
+ for (JMSTextMessage m : received)
+ {
+ actual.add(m.getText());
+
+ // Check Properties
+
+ Assert.assertEquals("Check Boolean properties are correctly transported", true, m.getBooleanProperty("Bool"));
+ Assert.assertEquals("Check Byte properties are correctly transported", (byte) Byte.MAX_VALUE,
+ m.getByteProperty("Byte"));
+ Assert.assertEquals("Check Double properties are correctly transported", (double) Double.MAX_VALUE,
+ m.getDoubleProperty("Double"));
+ Assert.assertEquals("Check Float properties are correctly transported", (float) Float.MAX_VALUE,
+ m.getFloatProperty("Float"));
+ Assert.assertEquals("Check Int properties are correctly transported", (int) Integer.MAX_VALUE,
+ m.getIntProperty("Int"));
+ Assert.assertEquals("Check CorrelationID properties are correctly transported", "Correlation",
+ m.getJMSCorrelationID());
+ Assert.assertEquals("Check Priority properties are correctly transported", 8, m.getJMSPriority());
+
+ // Queue
+ Assert.assertEquals("Check ReplyTo properties are correctly transported", AMQDestination.createDestination(new AMQBindingURL(m.getStringProperty("TempQueue"))),
+ m.getJMSReplyTo());
+
+ Assert.assertEquals("Check Type properties are correctly transported", "Test", m.getJMSType());
+
+ Assert.assertEquals("Check Short properties are correctly transported", (short) Short.MAX_VALUE,
+ m.getShortProperty("Short"));
+ Assert.assertEquals("Check UnsignedInt properties are correctly transported", (long) 4294967295L,
+ m.getLongProperty("UnsignedInt"));
+ Assert.assertEquals("Check Long properties are correctly transported", (long) Long.MAX_VALUE,
+ m.getLongProperty("Long"));
+ Assert.assertEquals("Check String properties are correctly transported", "Test", m.getStringProperty("String"));
+/*
+ // AMQP Tests Specific values
+
+ Assert.assertEquals("Check Timestamp properties are correctly transported", m.getStringProperty("time-str"),
+ ((AMQMessage) m).getTimestampProperty(new AMQShortString("time")).toString());
+
+ // Decimal
+ BigDecimal bd = new BigDecimal(Integer.MAX_VALUE);
+
+ Assert.assertEquals("Check decimal properties are correctly transported", bd.setScale(Byte.MAX_VALUE),
+ ((AMQMessage) m).getDecimalProperty(new AMQShortString("decimal")));
+
+ // Void
+ ((AMQMessage) m).setVoidProperty(new AMQShortString("void"));
+
+ Assert.assertTrue("Check void properties are correctly transported",
+ ((AMQMessage) m).getPropertyHeaders().containsKey("void"));
+*/
+ //JMSXUserID
+ if (m.getStringProperty("JMSXUserID") != null)
+ {
+ Assert.assertEquals("Check 'JMSXUserID' is supported ", USERNAME,
+ m.getStringProperty("JMSXUserID"));
+ }
+ }
+
+ received.clear();
+
+ assertEqual(messages.iterator(), actual.iterator());
+
+ messages.clear();
+ }
+
+ private static void assertEqual(Iterator expected, Iterator actual)
+ {
+ List<String> errors = new ArrayList<String>();
+ while (expected.hasNext() && actual.hasNext())
+ {
+ try
+ {
+ assertEqual(expected.next(), actual.next());
+ }
+ catch (Exception e)
+ {
+ errors.add(e.getMessage());
+ }
+ }
+ while (expected.hasNext())
+ {
+ errors.add("Expected " + expected.next() + " but no more actual values.");
+ }
+ while (actual.hasNext())
+ {
+ errors.add("Found " + actual.next() + " but no more expected values.");
+ }
+
+ if (!errors.isEmpty())
+ {
+ throw new RuntimeException(errors.toString());
+ }
+ }
+
+ private static void assertEqual(Object expected, Object actual)
+ {
+ if (!expected.equals(actual))
+ {
+ throw new RuntimeException("Expected '" + expected + "' found '" + actual + "'");
+ }
+ }
+
+ public void onMessage(Message message)
+ {
+ synchronized (received)
+ {
+ received.add((JMSTextMessage) message);
+ received.notify();
+ }
+ }
+
+ private static String randomize(String in)
+ {
+ return in + System.currentTimeMillis();
+ }
+
+ public static void main(String[] argv) throws Exception
+ {
+ PropertyValueTest test = new PropertyValueTest();
+ test._connectionString = (argv.length == 0) ? "vm://:1" : argv[0];
+ test.setUp();
+ if (argv.length > 1)
+ {
+ test._count = Integer.parseInt(argv[1]);
+ }
+
+ test.testOnce();
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(PropertyValueTest.class);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/PubSubTwoConnectionTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/PubSubTwoConnectionTest.java
new file mode 100644
index 0000000000..c257dacf76
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/PubSubTwoConnectionTest.java
@@ -0,0 +1,75 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.basic;
+
+import javax.jms.Connection;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+import javax.jms.Topic;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.AMQTopic;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+/**
+ * @author Apache Software Foundation
+ */
+public class PubSubTwoConnectionTest extends QpidBrokerTestCase
+{
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ }
+
+ protected void tearDown() throws Exception
+ {
+ super.tearDown();
+ }
+
+ /**
+ * This tests that a consumer is set up synchronously
+ * @throws Exception
+ */
+ public void testTwoConnections() throws Exception
+ {
+
+ AMQConnection con1 = (AMQConnection) getConnection("guest", "guest");
+
+ Topic topic = new AMQTopic(con1, "MyTopic");
+
+ Session session1 = con1.createSession(false, AMQSession.NO_ACKNOWLEDGE);
+ MessageProducer producer = session1.createProducer(topic);
+
+ Connection con2 = (AMQConnection) getConnection("guest", "guest") ;
+ Session session2 = con2.createSession(false, AMQSession.NO_ACKNOWLEDGE);
+ MessageConsumer consumer = session2.createConsumer(topic);
+ con2.start();
+ producer.send(session1.createTextMessage("Hello"));
+ TextMessage tm1 = (TextMessage) consumer.receive(2000);
+ assertNotNull(tm1);
+ assertEquals("Hello", tm1.getText());
+ con1.close();
+ con2.close();
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/ReceiveTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/ReceiveTest.java
new file mode 100644
index 0000000000..bc44617620
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/ReceiveTest.java
@@ -0,0 +1,82 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.basic;
+
+import javax.jms.MessageConsumer;
+import javax.jms.Message;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+public class ReceiveTest extends QpidBrokerTestCase
+{
+ private AMQConnection _connection;
+ private AMQDestination _destination;
+ private AMQSession _session;
+ private MessageConsumer _consumer;
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ init((AMQConnection) getConnection("guest", "guest"));
+ }
+
+ protected void tearDown() throws Exception
+ {
+ super.tearDown();
+ }
+
+ private void init(AMQConnection connection) throws Exception
+ {
+ init(connection, new AMQQueue(connection,"ReceiveTest", true));
+ }
+
+ private void init(AMQConnection connection, AMQDestination destination) throws Exception
+ {
+ _connection = connection;
+ _destination = destination;
+ _session = (AMQSession) connection.createSession(true, AMQSession.NO_ACKNOWLEDGE);
+ _consumer = _session.createConsumer(_destination);
+ _connection.start();
+ }
+
+ public void test() throws Exception
+ {
+ Message m = _consumer.receive(5000);
+ assertNull("should not have received a message", m);
+ _connection.close();
+ }
+
+
+ public static junit.framework.Test suite()
+ {
+ // TODO: note that this test doesn't use the VMBrokerSetup
+ // test helper class to create and tear down its
+ // VMBroker. This is because the main() above seems to
+ // indicate that it's also used outside of the surefire test
+ // framework. If it isn't, then this test should also be
+ // changed to use VMBrokerSetup here.
+ return new junit.framework.TestSuite(ReceiveTest.class);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/SessionStartTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/SessionStartTest.java
new file mode 100644
index 0000000000..ee837fd41a
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/SessionStartTest.java
@@ -0,0 +1,115 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.basic;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+
+public class SessionStartTest extends QpidBrokerTestCase implements MessageListener
+{
+ private static final Logger _logger = LoggerFactory.getLogger(SessionStartTest.class);
+
+ private AMQConnection _connection;
+ private AMQDestination _destination;
+ private AMQSession _session;
+ private int count;
+ public String _connectionString = "vm://:1";
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ init((AMQConnection) getConnection("guest", "guest"));
+ }
+
+ protected void tearDown() throws Exception
+ {
+ super.tearDown();
+ }
+
+ private void init(AMQConnection connection) throws Exception
+ {
+ init(connection,
+ new AMQQueue(connection.getDefaultQueueExchangeName(), new AMQShortString(randomize("SessionStartTest")), true));
+ }
+
+ private void init(AMQConnection connection, AMQDestination destination) throws Exception
+ {
+ _connection = connection;
+ _destination = destination;
+ connection.start();
+
+ _session = (AMQSession) connection.createSession(false, AMQSession.NO_ACKNOWLEDGE);
+ _session.createConsumer(destination).setMessageListener(this);
+ }
+
+ public synchronized void test() throws JMSException, InterruptedException
+ {
+ try
+ {
+ _session.createProducer(_destination).send(_session.createTextMessage("Message"));
+ _logger.info("Message sent, waiting for response...");
+ wait(1000);
+ if (count > 0)
+ {
+ _logger.info("Got message");
+ }
+ else
+ {
+ throw new RuntimeException("Did not get message!");
+ }
+ }
+ finally
+ {
+ _session.close();
+ _connection.close();
+ }
+ }
+
+ public synchronized void onMessage(Message message)
+ {
+ count++;
+ notify();
+ }
+
+ private static String randomize(String in)
+ {
+ return in + System.currentTimeMillis();
+ }
+
+ public static void main(String[] argv) throws Exception
+ {
+ SessionStartTest test = new SessionStartTest();
+ test._connectionString = (argv.length == 0) ? "localhost:5672" : argv[0];
+ test.setUp();
+ test.test();
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/TextMessageTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/TextMessageTest.java
new file mode 100644
index 0000000000..a87de8ac0c
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/TextMessageTest.java
@@ -0,0 +1,248 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.basic;
+
+import junit.framework.Assert;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.message.JMSTextMessage;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+import javax.jms.MessageNotWriteableException;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class TextMessageTest extends QpidBrokerTestCase implements MessageListener
+{
+ private static final Logger _logger = LoggerFactory.getLogger(TextMessageTest.class);
+
+ private AMQConnection _connection;
+ private Destination _destination;
+ private AMQSession _session;
+ private final List<JMSTextMessage> received = new ArrayList<JMSTextMessage>();
+ private final List<String> messages = new ArrayList<String>();
+ private int _count = 100;
+ public String _connectionString = "vm://:1";
+ private CountDownLatch _waitForCompletion;
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ try
+ {
+ init((AMQConnection) getConnection("guest", "guest"));
+ }
+ catch (Exception e)
+ {
+ fail("Unable to initialilse connection: " + e);
+ }
+ }
+
+ protected void tearDown() throws Exception
+ {
+ super.tearDown();
+ }
+
+ private void init(AMQConnection connection) throws Exception
+ {
+ Destination destination =
+ new AMQQueue(connection.getDefaultQueueExchangeName(), new AMQShortString(randomize("TextMessageTest")), true);
+ init(connection, destination);
+ }
+
+ private void init(AMQConnection connection, Destination destination) throws Exception
+ {
+ _connection = connection;
+ _destination = destination;
+ _session = (AMQSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ // set up a slow consumer
+ try
+ {
+ _session.createConsumer(destination).setMessageListener(this);
+ }
+ catch (Throwable e)
+ {
+ e.printStackTrace();
+ }
+ connection.start();
+ }
+
+ public void test() throws Exception
+ {
+ int count = _count;
+ _waitForCompletion = new CountDownLatch(_count);
+ send(count);
+ _waitForCompletion.await(20, TimeUnit.SECONDS);
+ check();
+ _logger.info("Completed without failure");
+ _connection.close();
+ }
+
+ void send(int count) throws JMSException
+ {
+ // create a publisher
+ MessageProducer producer = _session.createProducer(_destination);
+ for (int i = 0; i < count; i++)
+ {
+ String text = "Message " + i;
+ messages.add(text);
+ Message m = _session.createTextMessage(text);
+ //m.setStringProperty("String", "hello");
+
+ _logger.info("Sending Msg:" + m);
+ producer.send(m);
+ }
+ _logger.info("sent " + count + " mesages");
+ }
+
+
+ void check() throws JMSException
+ {
+ List<String> actual = new ArrayList<String>();
+ for (JMSTextMessage m : received)
+ {
+ actual.add(m.getText());
+
+ // Check body write status
+ try
+ {
+ m.setText("Test text");
+ Assert.fail("Message should not be writeable");
+ }
+ catch (MessageNotWriteableException mnwe)
+ {
+ // normal execution
+ }
+
+ m.clearBody();
+
+ try
+ {
+ m.setText("Test text");
+ }
+ catch (MessageNotWriteableException mnwe)
+ {
+ Assert.fail("Message should be writeable");
+ }
+
+ // Check property write status
+ try
+ {
+ m.setStringProperty("test", "test");
+ Assert.fail("Message should not be writeable");
+ }
+ catch (MessageNotWriteableException mnwe)
+ {
+ // normal execution
+ }
+
+ m.clearProperties();
+
+ try
+ {
+ m.setStringProperty("test", "test");
+ }
+ catch (MessageNotWriteableException mnwe)
+ {
+ Assert.fail("Message should be writeable");
+ }
+
+ }
+
+ assertEqual(messages.iterator(), actual.iterator());
+ }
+
+ private static void assertEqual(Iterator expected, Iterator actual)
+ {
+ List<String> errors = new ArrayList<String>();
+ while (expected.hasNext() && actual.hasNext())
+ {
+ try
+ {
+ assertEqual(expected.next(), actual.next());
+ }
+ catch (Exception e)
+ {
+ errors.add(e.getMessage());
+ }
+ }
+ while (expected.hasNext())
+ {
+ errors.add("Expected " + expected.next() + " but no more actual values.");
+ }
+ while (actual.hasNext())
+ {
+ errors.add("Found " + actual.next() + " but no more expected values.");
+ }
+
+ if (!errors.isEmpty())
+ {
+ throw new RuntimeException(errors.toString());
+ }
+ }
+
+ private static void assertEqual(Object expected, Object actual)
+ {
+ if (!expected.equals(actual))
+ {
+ throw new RuntimeException("Expected '" + expected + "' found '" + actual + "'");
+ }
+ }
+
+ public void onMessage(Message message)
+ {
+ synchronized (received)
+ {
+ _logger.info("===== received one message");
+ received.add((JMSTextMessage) message);
+ _waitForCompletion.countDown();
+ }
+ }
+
+ private static String randomize(String in)
+ {
+ return in + System.currentTimeMillis();
+ }
+
+
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(TextMessageTest.class);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/close/CloseTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/close/CloseTest.java
new file mode 100644
index 0000000000..c6b8069300
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/basic/close/CloseTest.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.test.unit.basic.close;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.url.AMQBindingURL;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+
+public class CloseTest extends QpidBrokerTestCase
+{
+ private static final Logger _logger = LoggerFactory.getLogger(CloseTest.class);
+
+ public void testCloseQueueReceiver() throws Exception
+ {
+ AMQConnection connection = (AMQConnection) getConnection("guest", "guest");
+
+ Session session = connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ AMQQueue queue = new AMQQueue(new AMQBindingURL("test-queue"));
+ MessageConsumer consumer = session.createConsumer(queue);
+
+ MessageProducer producer_not_used_but_created_for_testing = session.createProducer(queue);
+
+ connection.start();
+
+ _logger.info("About to close consumer");
+
+ consumer.close();
+
+ _logger.info("Closed Consumer");
+ connection.close();
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/AMQConnectionTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/AMQConnectionTest.java
new file mode 100644
index 0000000000..292bcd6039
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/AMQConnectionTest.java
@@ -0,0 +1,405 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.client;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.jms.Connection;
+import javax.jms.ExceptionListener;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.QueueSession;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+import javax.jms.TopicSession;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQConnectionDelegate_0_10;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.AMQTopic;
+import org.apache.qpid.configuration.ClientProperties;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class AMQConnectionTest extends QpidBrokerTestCase
+{
+ private static AMQConnection _connection;
+ private static AMQTopic _topic;
+ private static AMQQueue _queue;
+ private static QueueSession _queueSession;
+ private static TopicSession _topicSession;
+ protected static final Logger _logger = LoggerFactory.getLogger(AMQConnectionTest.class);
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ _connection = (AMQConnection) getConnection("guest", "guest");
+ _topic = new AMQTopic(_connection.getDefaultTopicExchangeName(), new AMQShortString("mytopic"));
+ _queue = new AMQQueue(_connection.getDefaultQueueExchangeName(), new AMQShortString("myqueue"));
+ }
+
+ protected void tearDown() throws Exception
+ {
+ _connection.close();
+ super.tearDown();
+ }
+
+ /**
+ * Simple tests to check we can create TopicSession and QueueSession ok
+ * And that they throw exceptions where appropriate as per JMS spec
+ */
+
+ public void testCreateQueueSession() throws JMSException
+ {
+ _queueSession = _connection.createQueueSession(false, AMQSession.NO_ACKNOWLEDGE);
+ }
+
+ public void testCreateTopicSession() throws JMSException
+ {
+ _topicSession = _connection.createTopicSession(false, AMQSession.NO_ACKNOWLEDGE);
+ }
+
+ public void testTopicSessionCreateBrowser() throws JMSException
+ {
+ try
+ {
+ _topicSession.createBrowser(_queue);
+ fail("expected exception did not occur");
+ }
+ catch (javax.jms.IllegalStateException s)
+ {
+ // ok
+ }
+ catch (Exception e)
+ {
+ fail("expected javax.jms.IllegalStateException, got " + e);
+ }
+ }
+
+ public void testTopicSessionCreateQueue() throws JMSException
+ {
+ try
+ {
+ _topicSession.createQueue("abc");
+ fail("expected exception did not occur");
+ }
+ catch (javax.jms.IllegalStateException s)
+ {
+ // ok
+ }
+ catch (Exception e)
+ {
+ fail("expected javax.jms.IllegalStateException, got " + e);
+ }
+ }
+
+ public void testTopicSessionCreateTemporaryQueue() throws JMSException
+ {
+ try
+ {
+ _topicSession.createTemporaryQueue();
+ fail("expected exception did not occur");
+ }
+ catch (javax.jms.IllegalStateException s)
+ {
+ // ok
+ }
+ catch (Exception e)
+ {
+ fail("expected javax.jms.IllegalStateException, got " + e);
+ }
+ }
+
+ public void testQueueSessionCreateTemporaryTopic() throws JMSException
+ {
+ try
+ {
+ _queueSession.createTemporaryTopic();
+ fail("expected exception did not occur");
+ }
+ catch (javax.jms.IllegalStateException s)
+ {
+ // ok
+ }
+ catch (Exception e)
+ {
+ fail("expected javax.jms.IllegalStateException, got " + e);
+ }
+ }
+
+ public void testQueueSessionCreateTopic() throws JMSException
+ {
+ try
+ {
+ _queueSession.createTopic("abc");
+ fail("expected exception did not occur");
+ }
+ catch (javax.jms.IllegalStateException s)
+ {
+ // ok
+ }
+ catch (Exception e)
+ {
+ fail("expected javax.jms.IllegalStateException, got " + e);
+ }
+ }
+
+ public void testQueueSessionDurableSubscriber() throws JMSException
+ {
+ try
+ {
+ _queueSession.createDurableSubscriber(_topic, "abc");
+ fail("expected exception did not occur");
+ }
+ catch (javax.jms.IllegalStateException s)
+ {
+ // ok
+ }
+ catch (Exception e)
+ {
+ fail("expected javax.jms.IllegalStateException, got " + e);
+ }
+ }
+
+ public void testQueueSessionUnsubscribe() throws JMSException
+ {
+ try
+ {
+ _queueSession.unsubscribe("abc");
+ fail("expected exception did not occur");
+ }
+ catch (javax.jms.IllegalStateException s)
+ {
+ // ok
+ }
+ catch (Exception e)
+ {
+ fail("expected javax.jms.IllegalStateException, got " + e);
+ }
+ }
+
+ public void testPrefetchSystemProperty() throws Exception
+ {
+ String oldPrefetch = System.getProperty(ClientProperties.MAX_PREFETCH_PROP_NAME);
+ try
+ {
+ _connection.close();
+ System.setProperty(ClientProperties.MAX_PREFETCH_PROP_NAME, new Integer(2).toString());
+ _connection = (AMQConnection) getConnection();
+ _connection.start();
+ // Create two consumers on different sessions
+ Session consSessA = _connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
+ MessageConsumer consumerA = consSessA.createConsumer(_queue);
+
+ Session producerSession = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ MessageProducer producer = producerSession.createProducer(_queue);
+
+ // Send 3 messages
+ for (int i = 0; i < 3; i++)
+ {
+ producer.send(producerSession.createTextMessage("test"));
+ }
+
+ MessageConsumer consumerB = null;
+ if (isBroker08())
+ {
+ Session consSessB = _connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
+ consumerB = consSessB.createConsumer(_queue);
+ }
+ else
+ {
+ consumerB = consSessA.createConsumer(_queue);
+ }
+
+ Message msg;
+ // Check that consumer A has 2 messages
+ for (int i = 0; i < 2; i++)
+ {
+ msg = consumerA.receive(1500);
+ assertNotNull("Consumer A should receive 2 messages",msg);
+ }
+
+ msg = consumerA.receive(1500);
+ assertNull("Consumer A should not have received a 3rd message",msg);
+
+ // Check that consumer B has the last message
+ msg = consumerB.receive(1500);
+ assertNotNull("Consumer B should have received the message",msg);
+ }
+ finally
+ {
+ if (oldPrefetch == null)
+ {
+ oldPrefetch = ClientProperties.MAX_PREFETCH_DEFAULT;
+ }
+ System.setProperty(ClientProperties.MAX_PREFETCH_PROP_NAME, oldPrefetch);
+ }
+ }
+
+ public void testGetChannelID() throws Exception
+ {
+ long maxChannelID = _connection.getMaximumChannelCount();
+ if (isBroker010())
+ {
+ //Usable numbers are 0 to N-1 when using 0-10
+ //and 1 to N for 0-8/0-9
+ maxChannelID = maxChannelID-1;
+ }
+ for (int j = 0; j < 3; j++)
+ {
+ int i = isBroker010() ? 0 : 1;
+ for ( ; i <= maxChannelID; i++)
+ {
+ int id = _connection.getNextChannelID();
+ assertEquals("Unexpected number on iteration "+j, i, id);
+ _connection.deregisterSession(id);
+ }
+ }
+ }
+
+ /**
+ * Test Strategy : Kill -STOP the broker and see
+ * if the client terminates the connection with a
+ * read timeout.
+ * The broker process is cleaned up in the test itself
+ * and avoids using process.waitFor() as it hangs.
+ */
+ public void testHeartBeat() throws Exception
+ {
+ boolean windows =
+ ((String) System.getProperties().get("os.name")).matches("(?i).*windows.*");
+
+ if (!isCppBroker() || windows)
+ {
+ return;
+ }
+
+ Process process = null;
+ int port = getPort(0);
+ String pid = null;
+ try
+ {
+ // close the connection and shutdown the broker started by QpidTest
+ _connection.close();
+ stopBroker(port);
+
+ System.setProperty("qpid.heartbeat", "1");
+
+ // in case this broker gets stuck, atleast the rest of the tests will not fail.
+ port = port + 200;
+ String startCmd = getBrokerCommand(port);
+
+ // start a broker using a script
+ ProcessBuilder pb = new ProcessBuilder(System.getProperty("broker.start"));
+ pb.redirectErrorStream(true);
+
+ Map<String, String> env = pb.environment();
+ env.put("BROKER_CMD",startCmd);
+ env.put("BROKER_READY",System.getProperty(BROKER_READY));
+
+ Process startScript = pb.start();
+ startScript.waitFor();
+ startScript.destroy();
+
+ Connection con =
+ new AMQConnection("amqp://guest:guest@clientid/testpath?brokerlist='tcp://localhost:" + port + "'");
+ final AtomicBoolean lock = new AtomicBoolean(false);
+
+ String cmd = "/usr/bin/pgrep -f " + port;
+ process = Runtime.getRuntime().exec("/bin/bash");
+ LineNumberReader reader = new LineNumberReader(new InputStreamReader(process.getInputStream()));
+ PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(process.getOutputStream())), true);
+ out.println(cmd);
+ pid = reader.readLine();
+ try
+ {
+ Integer.parseInt(pid);
+ }
+ catch (NumberFormatException e)
+ {
+ // Error! try to read further to gather the error msg.
+ String line;
+ _logger.debug(pid);
+ while ((line = reader.readLine()) != null )
+ {
+ _logger.debug(line);
+ }
+ throw new Exception( "Unable to get the brokers pid " + pid);
+ }
+ _logger.debug("pid : " + pid);
+
+ con.setExceptionListener(new ExceptionListener(){
+
+ public void onException(JMSException e)
+ {
+ synchronized(lock) {
+ lock.set(true);
+ lock.notifyAll();
+ }
+ }
+ });
+
+ out.println("kill -STOP " + pid);
+
+ synchronized(lock){
+ lock.wait(2500);
+ }
+ out.close();
+ reader.close();
+ assertTrue("Client did not terminate the connection, check log for details",lock.get());
+ }
+ catch(Exception e)
+ {
+ throw e;
+ }
+ finally
+ {
+ System.setProperty("qpid.heartbeat", "");
+
+ if (process != null)
+ {
+ process.destroy();
+ }
+
+ Process killScript = Runtime.getRuntime().exec(System.getProperty("broker.kill") + " " + pid);
+ killScript.waitFor();
+ killScript.destroy();
+ cleanBroker();
+ }
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(AMQConnectionTest.class);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/AMQSessionTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/AMQSessionTest.java
new file mode 100644
index 0000000000..93cceb1048
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/AMQSessionTest.java
@@ -0,0 +1,110 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.client;
+
+import javax.jms.JMSException;
+import javax.jms.QueueReceiver;
+import javax.jms.TopicSubscriber;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.AMQTopic;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+/**
+ * Tests for QueueReceiver and TopicSubscriber creation methods on AMQSession
+ */
+public class AMQSessionTest extends QpidBrokerTestCase
+{
+
+ private static AMQSession _session;
+ private static AMQTopic _topic;
+ private static AMQQueue _queue;
+ private static AMQConnection _connection;
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ _connection = (AMQConnection) getConnection("guest", "guest");
+ _topic = new AMQTopic(_connection,"mytopic");
+ _queue = new AMQQueue(_connection,"myqueue");
+ _session = (AMQSession) _connection.createSession(false, AMQSession.NO_ACKNOWLEDGE);
+ }
+
+ protected void tearDown() throws Exception
+ {
+ try
+ {
+ _connection.close();
+ }
+ catch (JMSException e)
+ {
+ //just close
+ }
+ super.tearDown();
+ }
+
+ public void testCreateSubscriber() throws JMSException
+ {
+ TopicSubscriber subscriber = _session.createSubscriber(_topic);
+ assertEquals("Topic names should match from TopicSubscriber", _topic.getTopicName(), subscriber.getTopic().getTopicName());
+
+ subscriber = _session.createSubscriber(_topic, "abc", false);
+ assertEquals("Topic names should match from TopicSubscriber with selector", _topic.getTopicName(), subscriber.getTopic().getTopicName());
+ }
+
+ public void testCreateDurableSubscriber() throws JMSException
+ {
+ TopicSubscriber subscriber = _session.createDurableSubscriber(_topic, "mysubname");
+ assertEquals("Topic names should match from durable TopicSubscriber", _topic.getTopicName(), subscriber.getTopic().getTopicName());
+
+ subscriber = _session.createDurableSubscriber(_topic, "mysubname2", "abc", false);
+ assertEquals("Topic names should match from durable TopicSubscriber with selector", _topic.getTopicName(), subscriber.getTopic().getTopicName());
+ _session.unsubscribe("mysubname");
+ _session.unsubscribe("mysubname2");
+ }
+
+ public void testCreateQueueReceiver() throws JMSException
+ {
+ QueueReceiver receiver = _session.createQueueReceiver(_queue);
+ assertEquals("Queue names should match from QueueReceiver", _queue.getQueueName(), receiver.getQueue().getQueueName());
+
+ receiver = _session.createQueueReceiver(_queue, "abc");
+ assertEquals("Queue names should match from QueueReceiver with selector", _queue.getQueueName(), receiver.getQueue().getQueueName());
+ }
+
+ public void testCreateReceiver() throws JMSException
+ {
+ QueueReceiver receiver = _session.createReceiver(_queue);
+ assertEquals("Queue names should match from QueueReceiver", _queue.getQueueName(), receiver.getQueue().getQueueName());
+
+ receiver = _session.createReceiver(_queue, "abc");
+ assertEquals("Queue names should match from QueueReceiver with selector", _queue.getQueueName(), receiver.getQueue().getQueueName());
+ }
+
+ public static void stopVmBrokers()
+ {
+ _queue = null;
+ _topic = null;
+ _session = null;
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/DynamicQueueExchangeCreateTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/DynamicQueueExchangeCreateTest.java
new file mode 100644
index 0000000000..8577fb5b6a
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/DynamicQueueExchangeCreateTest.java
@@ -0,0 +1,90 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.client;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Queue;
+import javax.jms.Session;
+
+/**
+ * QPID-155
+ *
+ * Test to validate that setting the respective qpid.declare_queues,
+ * qpid.declare_exchanges system properties functions as expected.
+ */
+public class DynamicQueueExchangeCreateTest extends QpidBrokerTestCase
+{
+ public void testQueueDeclare() throws Exception
+ {
+ setSystemProperty("qpid.declare_queues", "false");
+
+ Connection connection = getConnection();
+
+ Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ Queue queue = session.createQueue(getTestQueueName());
+
+ try
+ {
+ session.createConsumer(queue);
+ fail("JMSException should be thrown as the queue does not exist");
+ }
+ catch (JMSException e)
+ {
+ checkExceptionErrorCode(e, AMQConstant.NOT_FOUND);
+ }
+ }
+
+ public void testExchangeDeclare() throws Exception
+ {
+ setSystemProperty("qpid.declare_exchanges", "false");
+
+ Connection connection = getConnection();
+
+ Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ String EXCHANGE_TYPE = "test.direct";
+ Queue queue = session.createQueue("direct://" + EXCHANGE_TYPE + "/queue/queue");
+
+ try
+ {
+ session.createConsumer(queue);
+ fail("JMSException should be thrown as the exchange does not exist");
+ }
+ catch (JMSException e)
+ {
+ checkExceptionErrorCode(e, AMQConstant.NOT_FOUND);
+ }
+ }
+
+ private void checkExceptionErrorCode(JMSException original, AMQConstant code)
+ {
+ Exception linked = original.getLinkedException();
+ assertNotNull("Linked exception should have been set", linked);
+ assertTrue("Linked exception should be an AMQException", linked instanceof AMQException);
+ assertEquals("Error code should be " + code.getCode(), code, ((AMQException) linked).getErrorCode());
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseOkTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseOkTest.java
new file mode 100644
index 0000000000..79e2ff8148
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseOkTest.java
@@ -0,0 +1,239 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.client.channelclose;
+
+import junit.textui.TestRunner;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.transport.TransportConnection;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.Destination;
+import javax.jms.ExceptionListener;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Due to bizarre exception handling all sessions are closed if you get
+ * a channel close request and no exception listener is registered.
+ * <p/>
+ * JIRA issue IBTBLZ-10.
+ * <p/>
+ * Simulate by:
+ * <p/>
+ * 0. Create two sessions with no exception listener.
+ * 1. Publish message to queue/topic that does not exist (wrong routing key).
+ * 2. This will cause a channel close.
+ * 3. Since client does not have an exception listener, currently all sessions are
+ * closed.
+ */
+public class ChannelCloseOkTest extends QpidBrokerTestCase
+{
+ private AMQConnection _connection;
+ private Destination _destination1;
+ private Destination _destination2;
+ private Session _session1;
+ private Session _session2;
+ private final List<Message> _received1 = new ArrayList<Message>();
+ private final List<Message> _received2 = new ArrayList<Message>();
+
+ private static final Logger _log = LoggerFactory.getLogger(ChannelCloseOkTest.class);
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ _connection = (AMQConnection) getConnection("guest", "guest");
+
+ _destination1 = new AMQQueue(_connection, "q1", true);
+ _destination2 = new AMQQueue(_connection, "q2", true);
+ _session1 = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ _session1.createConsumer(_destination1).setMessageListener(new MessageListener()
+ {
+ public void onMessage(Message message)
+ {
+ _log.debug("consumer 1 got message [" + getTextMessage(message) + "]");
+ synchronized (_received1)
+ {
+ _received1.add(message);
+ _received1.notify();
+ }
+ }
+ });
+ _session2 = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ _session2.createConsumer(_destination2).setMessageListener(new MessageListener()
+ {
+ public void onMessage(Message message)
+ {
+ _log.debug("consumer 2 got message [" + getTextMessage(message) + "]");
+ synchronized (_received2)
+ {
+ _received2.add(message);
+ _received2.notify();
+ }
+ }
+ });
+
+ _connection.start();
+ }
+
+ private String getTextMessage(Message message)
+ {
+ TextMessage tm = (TextMessage) message;
+ try
+ {
+ return tm.getText();
+ }
+ catch (JMSException e)
+ {
+ return "oops " + e;
+ }
+ }
+
+ protected void tearDown() throws Exception
+ {
+ closeConnection();
+ super.tearDown();
+ }
+
+ public void closeConnection() throws JMSException
+ {
+ if (_connection != null)
+ {
+ _log.info(">>>>>>>>>>>>>>.. closing");
+ _connection.close();
+ }
+ }
+
+ public void testWithoutExceptionListener() throws Exception
+ {
+ doTest();
+ }
+
+ public void testWithExceptionListener() throws Exception
+ {
+ _connection.setExceptionListener(new ExceptionListener()
+ {
+ public void onException(JMSException jmsException)
+ {
+ _log.warn("onException - " + jmsException.getMessage());
+ }
+ });
+
+ doTest();
+ }
+
+ public void doTest() throws Exception
+ {
+ // Check both sessions are ok.
+ sendAndWait(_session1, _destination1, "first", _received1, 1);
+ sendAndWait(_session2, _destination2, "second", _received2, 1);
+ assertEquals(1, _received1.size());
+ assertEquals(1, _received2.size());
+
+ // Now send message to incorrect destination on session 1.
+ Destination destination = new AMQQueue(_connection, "incorrect");
+ send(_session1, destination, "third"); // no point waiting as message will never be received.
+
+ // Ensure both sessions are still ok.
+ // Send a bunch of messages as this give time for the sessions to be erroneously closed.
+ final int num = 300;
+ for (int i = 0; i < num; ++i)
+ {
+ send(_session1, _destination1, "" + i);
+ send(_session2, _destination2, "" + i);
+ }
+
+ waitFor(_received1, num + 1);
+ waitFor(_received2, num + 1);
+
+ // Note that the third message is never received as it is sent to an incorrect destination.
+ assertEquals(num + 1, _received1.size());
+ assertEquals(num + 1, _received2.size());
+ }
+
+ private void sendAndWait(Session session, Destination destination, String message, List<Message> received, int count)
+ throws JMSException, InterruptedException
+ {
+ send(session, destination, message);
+ waitFor(received, count);
+ }
+
+ private void send(Session session, Destination destination, String message) throws JMSException
+ {
+ _log.debug("sending message " + message);
+ MessageProducer producer1 = session.createProducer(destination);
+ producer1.send(session.createTextMessage(message));
+ }
+
+ private void waitFor(List<Message> received, int count) throws InterruptedException
+ {
+ long timeout = 20000;
+
+ synchronized (received)
+ {
+ long start = System.currentTimeMillis();
+ while (received.size() < count)
+ {
+ if (System.currentTimeMillis() - start > timeout)
+ {
+ fail("timeout expired waiting for messages");
+ }
+ try
+ {
+ received.wait(timeout);
+ }
+ catch (InterruptedException e)
+ {
+ _log.info("Interrupted: " + e);
+ throw e;
+ }
+
+ }
+ }
+ }
+
+ private static String randomize(String in)
+ {
+ return in + System.currentTimeMillis();
+ }
+
+ public static void main(String[] args)
+ {
+ TestRunner.run(ChannelCloseOkTest.class);
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(ChannelCloseOkTest.class);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseTest.java
new file mode 100644
index 0000000000..b6232b1734
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/channelclose/ChannelCloseTest.java
@@ -0,0 +1,394 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.test.unit.client.channelclose;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.failover.FailoverException;
+import org.apache.qpid.client.protocol.AMQProtocolHandler;
+import org.apache.qpid.client.transport.TransportConnection;
+import org.apache.qpid.framing.*;
+import org.apache.qpid.jms.ConnectionListener;
+import org.apache.qpid.protocol.AMQConstant;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.Connection;
+import javax.jms.ExceptionListener;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+
+public class ChannelCloseTest extends QpidBrokerTestCase implements ExceptionListener, ConnectionListener
+{
+ private static final Logger _logger = LoggerFactory.getLogger(ChannelCloseTest.class);
+
+ Connection _connection;
+ private Session _session;
+ private static final long SYNC_TIMEOUT = 500;
+ private int TEST = 0;
+
+ /**
+ * Close channel, use chanel with same id ensure error.
+ *
+ * This test is only valid for non 0-10 connection .
+ */
+ public void testReusingChannelAfterFullClosure() throws Exception
+ {
+ _connection=newConnection();
+
+ // Create Producer
+ try
+ {
+ _connection.start();
+
+ createChannelAndTest(1);
+
+ // Cause it to close
+ try
+ {
+ _logger.info("Testing invalid exchange");
+ declareExchange(1, "", "name_that_will_lookup_to_null", false);
+ fail("Exchange name is empty so this should fail ");
+ }
+ catch (AMQException e)
+ {
+ assertEquals("Exchange should not be found", AMQConstant.NOT_FOUND, e.getErrorCode());
+ }
+
+ // Check that
+ try
+ {
+ _logger.info("Testing valid exchange should fail");
+ declareExchange(1, "topic", "amq.topic", false);
+ fail("This should not succeed as the channel should be closed ");
+ }
+ catch (AMQException e)
+ {
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Exception occured was:" + e.getErrorCode());
+ }
+
+ assertEquals("Connection should be closed", AMQConstant.CHANNEL_ERROR, e.getErrorCode());
+
+ _connection=newConnection();
+ }
+
+ checkSendingMessage();
+
+ _session.close();
+ _connection.close();
+
+ }
+ catch (JMSException e)
+ {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ }
+
+ /*
+ close channel and send guff then send ok no errors
+ REMOVE TEST - The behaviour after server has sent close is undefined.
+ the server should be free to fail as it may wish to reclaim its resources
+ immediately after close.
+ */
+ /*public void testSendingMethodsAfterClose() throws Exception
+ {
+ // this is testing an 0.8 connection
+ if(isBroker08())
+ {
+ try
+ {
+ _connection=new AMQConnection("amqp://guest:guest@CCTTest/test?brokerlist='" + _brokerlist + "'");
+
+ ((AMQConnection) _connection).setConnectionListener(this);
+
+ _connection.setExceptionListener(this);
+
+ // Change the StateManager for one that doesn't respond with Close-OKs
+ AMQStateManager oldStateManager=((AMQConnection) _connection).getProtocolHandler().getStateManager();
+
+ _session=_connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+
+ _connection.start();
+
+ // Test connection
+ checkSendingMessage();
+
+ // Set StateManager to manager that ignores Close-oks
+ AMQProtocolSession protocolSession=
+ ((AMQConnection) _connection).getProtocolHandler().getProtocolSession();
+
+ MethodDispatcher d = protocolSession.getMethodDispatcher();
+
+ MethodDispatcher wrappedDispatcher = (MethodDispatcher)
+ Proxy.newProxyInstance(d.getClass().getClassLoader(),
+ d.getClass().getInterfaces(),
+ new MethodDispatcherProxyHandler(
+ (ClientMethodDispatcherImpl) d));
+
+ protocolSession.setMethodDispatcher(wrappedDispatcher);
+
+
+ AMQStateManager newStateManager=new NoCloseOKStateManager(protocolSession);
+ newStateManager.changeState(oldStateManager.getCurrentState());
+
+ ((AMQConnection) _connection).getProtocolHandler().setStateManager(newStateManager);
+
+ final int TEST_CHANNEL=1;
+ _logger.info("Testing Channel(" + TEST_CHANNEL + ") Creation");
+
+ createChannelAndTest(TEST_CHANNEL);
+
+ // Cause it to close
+ try
+ {
+ _logger.info("Closing Channel - invalid exchange");
+ declareExchange(TEST_CHANNEL, "", "name_that_will_lookup_to_null", false);
+ fail("Exchange name is empty so this should fail ");
+ }
+ catch (AMQException e)
+ {
+ assertEquals("Exchange should not be found", AMQConstant.NOT_FOUND, e.getErrorCode());
+ }
+
+ try
+ {
+ // Send other methods that should be ignored
+ // send them no wait as server will ignore them
+ _logger.info("Tested known exchange - should ignore");
+ declareExchange(TEST_CHANNEL, "topic", "amq.topic", true);
+
+ _logger.info("Tested known invalid exchange - should ignore");
+ declareExchange(TEST_CHANNEL, "", "name_that_will_lookup_to_null", true);
+
+ _logger.info("Tested known invalid exchange - should ignore");
+ declareExchange(TEST_CHANNEL, "", "name_that_will_lookup_to_null", true);
+
+ // Send sync .. server will igore and timy oue
+ _logger.info("Tested known invalid exchange - should ignore");
+ declareExchange(TEST_CHANNEL, "", "name_that_will_lookup_to_null", false);
+ }
+ catch (AMQTimeoutException te)
+ {
+ assertEquals("Request should timeout", AMQConstant.REQUEST_TIMEOUT, te.getErrorCode());
+ }
+ catch (AMQException e)
+ {
+ fail("This should not fail as all requests should be ignored");
+ }
+
+ _logger.info("Sending Close");
+ // Send Close-ok
+ sendClose(TEST_CHANNEL);
+
+ _logger.info("Re-opening channel");
+
+ createChannelAndTest(TEST_CHANNEL);
+
+ // Test connection is still ok
+
+ checkSendingMessage();
+
+ }
+ catch (JMSException e)
+ {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ catch (AMQException e)
+ {
+ fail(e.getMessage());
+
+ }
+ catch (URLSyntaxException e)
+ {
+ fail(e.getMessage());
+ }
+ finally
+ {
+ try
+ {
+ _session.close();
+ _connection.close();
+ }
+ catch (JMSException e)
+ {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ }
+ }
+ }
+*/
+ private void createChannelAndTest(int channel) throws FailoverException
+ {
+ // Create A channel
+ try
+ {
+ createChannel(channel);
+ }
+ catch (AMQException e)
+ {
+ fail(e.getMessage());
+ }
+
+ // Test it is ok
+ try
+ {
+ declareExchange(channel, "topic", "amq.topic", false);
+ _logger.info("Tested known exchange");
+ }
+ catch (AMQException e)
+ {
+ fail("This should not fail as this is the default exchange details");
+ }
+ }
+
+ private void sendClose(int channel)
+ {
+ ChannelCloseOkBody body =
+ ((AMQConnection) _connection).getProtocolHandler().getMethodRegistry().createChannelCloseOkBody();
+ AMQFrame frame = body.generateFrame(channel);
+
+ ((AMQConnection) _connection).getProtocolHandler().writeFrame(frame);
+ }
+
+ private void checkSendingMessage() throws JMSException
+ {
+ TEST++;
+ _logger.info("Test creating producer which will use channel id 1");
+
+ Queue queue = _session.createQueue("CCT_test_validation_queue" + TEST);
+
+ MessageConsumer consumer = _session.createConsumer(queue);
+
+ MessageProducer producer = _session.createProducer(queue);
+
+ final String MESSAGE = "CCT_Test_Message";
+ producer.send(_session.createTextMessage(MESSAGE));
+
+ Message msg = consumer.receive(2000);
+
+ assertNotNull("Received messages should not be null.", msg);
+ assertEquals("Message received not what we sent", MESSAGE, ((TextMessage) msg).getText());
+ }
+
+ private Connection newConnection()
+ {
+ Connection connection = null;
+ try
+ {
+ connection = getConnection();
+
+ ((AMQConnection) connection).setConnectionListener(this);
+
+ _session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+
+ connection.start();
+
+ }
+ catch (Exception e)
+ {
+ fail("Creating new connection when:" + e.getMessage());
+ }
+
+ return connection;
+ }
+
+ private void declareExchange(int channelId, String _type, String _name, boolean nowait)
+ throws AMQException, FailoverException
+ {
+ ExchangeDeclareBody body =
+ ((AMQConnection) _connection).getProtocolHandler()
+ .getMethodRegistry()
+ .createExchangeDeclareBody(0,
+ new AMQShortString(_name),
+ new AMQShortString(_type),
+ true,
+ false,
+ false,
+ false,
+ nowait,
+ null);
+ AMQFrame exchangeDeclare = body.generateFrame(channelId);
+ AMQProtocolHandler protocolHandler = ((AMQConnection) _connection).getProtocolHandler();
+
+
+ if (nowait)
+ {
+ protocolHandler.writeFrame(exchangeDeclare);
+ }
+ else
+ {
+ protocolHandler.syncWrite(exchangeDeclare, ExchangeDeclareOkBody.class, SYNC_TIMEOUT);
+ }
+
+// return null;
+// }
+// }, (AMQConnection)_connection).execute();
+
+ }
+
+ private void createChannel(int channelId) throws AMQException, FailoverException
+ {
+ ChannelOpenBody body =
+ ((AMQConnection) _connection).getProtocolHandler().getMethodRegistry().createChannelOpenBody(null);
+
+ ((AMQConnection) _connection).getProtocolHandler().syncWrite(body.generateFrame(channelId), // outOfBand
+ ChannelOpenOkBody.class);
+
+ }
+
+ public void onException(JMSException jmsException)
+ {
+ // _logger.info("CCT" + jmsException);
+ fail(jmsException.getMessage());
+ }
+
+ public void bytesSent(long count)
+ { }
+
+ public void bytesReceived(long count)
+ { }
+
+ public boolean preFailover(boolean redirect)
+ {
+ return false;
+ }
+
+ public boolean preResubscribe()
+ {
+ return false;
+ }
+
+ public void failoverComplete()
+ { }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/channelclose/CloseWithBlockingReceiveTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/channelclose/CloseWithBlockingReceiveTest.java
new file mode 100644
index 0000000000..56d03dc4a7
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/channelclose/CloseWithBlockingReceiveTest.java
@@ -0,0 +1,81 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.client.channelclose;
+
+import javax.jms.MessageConsumer;
+import javax.jms.Session;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQTopic;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+/**
+ * @author Apache Software Foundation
+ */
+public class CloseWithBlockingReceiveTest extends QpidBrokerTestCase
+{
+
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ }
+
+ protected void tearDown() throws Exception
+ {
+ super.tearDown();
+ }
+
+
+ public void testReceiveReturnsNull() throws Exception
+ {
+ final AMQConnection connection = (AMQConnection) getConnection("guest", "guest");
+ Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ MessageConsumer consumer = session.createConsumer(new AMQTopic(connection, "banana"));
+ connection.start();
+
+ Runnable r = new Runnable()
+ {
+
+ public void run()
+ {
+ try
+ {
+ Thread.sleep(1000);
+ connection.close();
+ }
+ catch (Exception e)
+ {
+ }
+ }
+ };
+ long startTime = System.currentTimeMillis();
+ new Thread(r).start();
+ consumer.receive(10000);
+ assertTrue(System.currentTimeMillis() - startTime < 10000);
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(CloseWithBlockingReceiveTest.class);
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/connection/CloseAfterConnectionFailureTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/connection/CloseAfterConnectionFailureTest.java
new file mode 100644
index 0000000000..dc2f59c384
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/connection/CloseAfterConnectionFailureTest.java
@@ -0,0 +1,124 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.client.connection;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQConnectionURL;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.url.URLSyntaxException;
+
+import javax.jms.ExceptionListener;
+import javax.jms.JMSException;
+import javax.jms.MessageConsumer;
+import javax.jms.Session;
+import java.util.concurrent.CountDownLatch;
+
+public class CloseAfterConnectionFailureTest extends QpidBrokerTestCase implements ExceptionListener
+{
+ private int sessionCount = 0;
+ AMQConnection connection;
+ Session session;
+ MessageConsumer consumer;
+ private CountDownLatch _latch = new CountDownLatch(1);
+ private JMSException _fail;
+
+ public void testNoFailover() throws URLSyntaxException, Exception,
+ InterruptedException, JMSException
+ {
+ //This test uses hard coded connection string so only runs on InVM case
+ if (!isExternalBroker())
+ {
+ String connectionString = "amqp://guest:guest@/test?brokerlist='vm://:1?connectdelay='500',retries='3'',failover='nofailover'";
+
+ AMQConnectionURL url = new AMQConnectionURL(connectionString);
+
+ try
+ {
+ //Start the connection so it will use the retries
+ connection = new AMQConnection(url, null);
+
+ connection.setExceptionListener(this);
+
+ session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ consumer = session.createConsumer(session.createQueue(this.getName()));
+
+ //Kill connection
+ stopBroker();
+
+ _latch.await();
+
+ if (_fail != null)
+ {
+ _fail.printStackTrace(System.out);
+ fail("Exception thrown:" + _fail.getMessage());
+ }
+ }
+ catch (AMQException e)
+ {
+ fail(e.getMessage());
+ }
+ }
+ }
+
+ public void onException(JMSException e)
+ {
+ System.out.println("Connection isClosed after connection Falure?:" + connection.isClosed());
+ try
+ {
+ consumer.close();
+ }
+ catch (JMSException jmse)
+ {
+ System.out.println("Consumer close failed with:" + jmse.getMessage());
+ _fail = jmse;
+ }
+ System.out.println("Connection isClosed after connection Falure?:" + connection.isClosed());
+ try
+ {
+ //Note that if we actually do session.close() we will lock up as the session will never receive a frame
+ // from the
+ ((AMQSession) session).close(10);
+ }
+ catch (JMSException jmse)
+ {
+ System.out.println("Session close failed with:" + jmse.getMessage());
+ _fail = jmse;
+ }
+ System.out.println("Connection isClosed after connection Falure?:" + connection.isClosed());
+
+ try
+ {
+ connection.close();
+ }
+ catch (JMSException jmse)
+ {
+ System.out.println("Session close failed with:" + jmse.getMessage());
+ _fail = jmse;
+ }
+ System.out.println("Connection isClosed after connection Falure?:" + connection.isClosed());
+
+ _latch.countDown();
+
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/connection/ConnectionCloseTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/connection/ConnectionCloseTest.java
new file mode 100644
index 0000000000..6d1b6de238
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/connection/ConnectionCloseTest.java
@@ -0,0 +1,111 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.client.connection;
+
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.transport.util.Logger;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jms.Connection;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+
+/**
+ * ConnectionCloseTest
+ *
+ */
+
+public class ConnectionCloseTest extends QpidBrokerTestCase
+{
+
+ private static final Logger log = Logger.get(ConnectionCloseTest.class);
+
+ public void testSendReceiveClose() throws Exception
+ {
+ Map<Thread,StackTraceElement[]> before = Thread.getAllStackTraces();
+
+ for (int i = 0; i < 50; i++)
+ {
+ if ((i % 10) == 0)
+ {
+ log.warn("%d messages sent and received", i);
+ }
+
+ Connection receiver = getConnection();
+ receiver.start();
+ Session rssn = receiver.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ Queue queue = rssn.createQueue("connection-close-test-queue");
+ MessageConsumer cons = rssn.createConsumer(queue);
+
+ Connection sender = getConnection();
+ sender.start();
+ Session sssn = sender.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ MessageProducer prod = sssn.createProducer(queue);
+ prod.send(sssn.createTextMessage("test"));
+ sender.close();
+
+ TextMessage m = (TextMessage) cons.receive(2000);
+ assertNotNull("message was lost", m);
+ assertEquals(m.getText(), "test");
+ receiver.close();
+ }
+
+ // The finalizer is notifying connector thread waiting on a selector key.
+ // This should leave the finalizer enough time to notify those threads
+ synchronized (this)
+ {
+ this.wait(10000);
+ }
+
+ Map<Thread,StackTraceElement[]> after = Thread.getAllStackTraces();
+
+ Map<Thread,StackTraceElement[]> delta = new HashMap<Thread,StackTraceElement[]>(after);
+ for (Thread t : before.keySet())
+ {
+ delta.remove(t);
+ }
+
+ dumpStacks(delta);
+
+ int deltaThreshold = (isExternalBroker()? 1 : 2) //InVM creates more thread pools in the same VM
+ * (Runtime.getRuntime().availableProcessors() + 1) + 5;
+
+ assertTrue("Spurious thread creation exceeded threshold, " +
+ delta.size() + " threads created.",
+ delta.size() < deltaThreshold);
+ }
+
+ private void dumpStacks(Map<Thread,StackTraceElement[]> map)
+ {
+ for (Map.Entry<Thread,StackTraceElement[]> entry : map.entrySet())
+ {
+ Throwable t = new Throwable();
+ t.setStackTrace(entry.getValue());
+ log.warn(t, entry.getKey().toString());
+ }
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/connection/ConnectionStartTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/connection/ConnectionStartTest.java
new file mode 100644
index 0000000000..ac14f8e50e
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/connection/ConnectionStartTest.java
@@ -0,0 +1,158 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.client.connection;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageListener;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+public class ConnectionStartTest extends QpidBrokerTestCase
+{
+
+ String _broker = "vm://:1";
+
+ AMQConnection _connection;
+ private Session _consumerSess;
+ private MessageConsumer _consumer;
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ try
+ {
+
+
+ AMQConnection pubCon = (AMQConnection) getConnection("guest", "guest");
+
+ AMQQueue queue = new AMQQueue(pubCon,"ConnectionStartTest");
+
+ Session pubSess = pubCon.createSession(false, AMQSession.AUTO_ACKNOWLEDGE);
+
+ MessageProducer pub = pubSess.createProducer(queue);
+
+ _connection = (AMQConnection) getConnection("guest", "guest");
+
+ _consumerSess = _connection.createSession(false, AMQSession.AUTO_ACKNOWLEDGE);
+
+ _consumer = _consumerSess.createConsumer(queue);
+
+ //publish after queue is created to ensure it can be routed as expected
+ pub.send(pubSess.createTextMessage("Initial Message"));
+
+ pubCon.close();
+
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("Connection to " + _broker + " should succeed. Reason: " + e);
+ }
+ }
+
+ protected void tearDown() throws Exception
+ {
+ _connection.close();
+ super.tearDown();
+ }
+
+ public void testSimpleReceiveConnection()
+ {
+ try
+ {
+ assertTrue("Connection should not be started", !_connection.started());
+ //Note that this next line will start the dispatcher in the session
+ // should really not be called before _connection start
+ //assertTrue("There should not be messages waiting for the consumer", _consumer.receiveNoWait() == null);
+ _connection.start();
+ assertTrue("There should be messages waiting for the consumer", _consumer.receive(10*1000) != null);
+ assertTrue("Connection should be started", _connection.started());
+
+ }
+ catch (JMSException e)
+ {
+ fail("An error occured during test because:" + e);
+ }
+
+ }
+
+ public void testMessageListenerConnection()
+ {
+ final CountDownLatch _gotMessage = new CountDownLatch(1);
+
+ try
+ {
+ assertTrue("Connection should not be started", !_connection.started());
+ _consumer.setMessageListener(new MessageListener()
+ {
+ public void onMessage(Message message)
+ {
+ try
+ {
+ assertTrue("Connection should be started", _connection.started());
+ assertEquals("Mesage Received", "Initial Message", ((TextMessage) message).getText());
+ _gotMessage.countDown();
+ }
+ catch (JMSException e)
+ {
+ fail("Couldn't get message text because:" + e.getCause());
+ }
+ }
+ });
+
+ assertTrue("Connection should not be started", !_connection.started());
+ _connection.start();
+ assertTrue("Connection should be started", _connection.started());
+
+ try
+ {
+ assertTrue("Listener was never called", _gotMessage.await(10 * 1000, TimeUnit.MILLISECONDS));
+ }
+ catch (InterruptedException e)
+ {
+ fail("Timed out awaiting message via onMessage");
+ }
+
+ }
+ catch (JMSException e)
+ {
+ fail("Failed because:" + e.getCause());
+ }
+
+ }
+
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(ConnectionStartTest.class);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/connection/ConnectionTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/connection/ConnectionTest.java
new file mode 100644
index 0000000000..04fc611cd1
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/connection/ConnectionTest.java
@@ -0,0 +1,301 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.client.connection;
+
+import org.apache.qpid.AMQConnectionFailureException;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.AMQUnresolvedAddressException;
+import org.apache.qpid.server.exchange.Exchange;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.client.AMQAuthenticationException;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.AMQTopic;
+import org.apache.qpid.client.AMQConnectionURL;
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.jms.Session;
+import org.apache.qpid.jms.ConnectionURL;
+import org.apache.qpid.jms.BrokerDetails;
+
+import javax.jms.Connection;
+import javax.jms.QueueSession;
+import javax.jms.TopicSession;
+import javax.naming.NamingException;
+
+public class ConnectionTest extends QpidBrokerTestCase
+{
+
+ String _broker_NotRunning = "vm://:2";
+ String _broker_BadDNS = "tcp://hg3sgaaw4lgihjs";
+
+ public void testSimpleConnection() throws Exception
+ {
+ AMQConnection conn = null;
+ try
+ {
+ conn = new AMQConnection(getBroker().toString(), "guest", "guest", "fred", "test");
+ }
+ catch (Exception e)
+ {
+ fail("Connection to " + getBroker() + " should succeed. Reason: " + e);
+ }
+ finally
+ {
+ if(conn != null)
+ {
+ conn.close();
+ }
+ }
+ }
+
+ public void testDefaultExchanges() throws Exception
+ {
+ AMQConnection conn = null;
+ try
+ {
+ BrokerDetails broker = getBroker();
+ broker.setProperty(BrokerDetails.OPTIONS_RETRY, "1");
+ ConnectionURL url = new AMQConnectionURL("amqp://guest:guest@clientid/test?brokerlist='"
+ + broker
+ + "'&defaultQueueExchange='test.direct'"
+ + "&defaultTopicExchange='test.topic'"
+ + "&temporaryQueueExchange='tmp.direct'"
+ + "&temporaryTopicExchange='tmp.topic'");
+
+ System.err.println(url.toString());
+ conn = new AMQConnection(url, null);
+
+
+ AMQSession sess = (AMQSession) conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ sess.declareExchange(new AMQShortString("test.direct"),
+ ExchangeDefaults.DIRECT_EXCHANGE_CLASS, false);
+
+ sess.declareExchange(new AMQShortString("tmp.direct"),
+ ExchangeDefaults.DIRECT_EXCHANGE_CLASS, false);
+
+ sess.declareExchange(new AMQShortString("tmp.topic"),
+ ExchangeDefaults.TOPIC_EXCHANGE_CLASS, false);
+
+ sess.declareExchange(new AMQShortString("test.topic"),
+ ExchangeDefaults.TOPIC_EXCHANGE_CLASS, false);
+
+ QueueSession queueSession = conn.createQueueSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ AMQQueue queue = (AMQQueue) queueSession.createQueue("MyQueue");
+
+ assertEquals(queue.getExchangeName().toString(), "test.direct");
+
+ AMQQueue tempQueue = (AMQQueue) queueSession.createTemporaryQueue();
+
+ assertEquals(tempQueue.getExchangeName().toString(), "tmp.direct");
+
+ queueSession.close();
+
+ TopicSession topicSession = conn.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ AMQTopic topic = (AMQTopic) topicSession.createTopic("silly.topic");
+
+ assertEquals(topic.getExchangeName().toString(), "test.topic");
+
+ AMQTopic tempTopic = (AMQTopic) topicSession.createTemporaryTopic();
+
+ assertEquals(tempTopic.getExchangeName().toString(), "tmp.topic");
+
+ topicSession.close();
+
+ }
+ catch (Exception e)
+ {
+ fail("Connection to " + getBroker() + " should succeed. Reason: " + e);
+ }
+ finally
+ {
+ conn.close();
+ }
+ }
+
+ public void testPasswordFailureConnection() throws Exception
+ {
+ AMQConnection conn = null;
+ try
+ {
+ BrokerDetails broker = getBroker();
+ broker.setProperty(BrokerDetails.OPTIONS_RETRY, "0");
+ conn = new AMQConnection("amqp://guest:rubbishpassword@clientid/test?brokerlist='" + broker + "'");
+ fail("Connection should not be established password is wrong.");
+ }
+ catch (AMQConnectionFailureException amqe)
+ {
+ assertNotNull("No cause set:" + amqe.getMessage(), amqe.getCause());
+ assertTrue("Exception was wrong type", amqe.getCause() instanceof AMQException);
+ }
+ finally
+ {
+ if (conn != null)
+ {
+ conn.close();
+ }
+ }
+ }
+
+ public void testConnectionFailure() throws Exception
+ {
+ AMQConnection conn = null;
+ try
+ {
+ conn = new AMQConnection("amqp://guest:guest@clientid/testpath?brokerlist='" + _broker_NotRunning + "?retries='0''");
+ fail("Connection should not be established");
+ }
+ catch (AMQException amqe)
+ {
+ if (!(amqe instanceof AMQConnectionFailureException))
+ {
+ fail("Correct exception not thrown. Excpected 'AMQConnectionException' got: " + amqe);
+ }
+ }
+ finally
+ {
+ if (conn != null)
+ {
+ conn.close();
+ }
+ }
+
+ }
+
+ public void testUnresolvedHostFailure() throws Exception
+ {
+ AMQConnection conn = null;
+ try
+ {
+ conn = new AMQConnection("amqp://guest:guest@clientid/testpath?brokerlist='" + _broker_BadDNS + "?retries='0''");
+ fail("Connection should not be established");
+ }
+ catch (AMQException amqe)
+ {
+ if (!(amqe instanceof AMQUnresolvedAddressException))
+ {
+ fail("Correct exception not thrown. Excpected 'AMQUnresolvedAddressException' got: " + amqe);
+ }
+ }
+ finally
+ {
+ if (conn != null)
+ {
+ conn.close();
+ }
+ }
+
+ }
+
+ public void testUnresolvedVirtualHostFailure() throws Exception
+ {
+ AMQConnection conn = null;
+ try
+ {
+ BrokerDetails broker = getBroker();
+ broker.setProperty(BrokerDetails.OPTIONS_RETRY, "0");
+ conn = new AMQConnection("amqp://guest:guest@clientid/rubbishhost?brokerlist='" + broker + "'");
+ fail("Connection should not be established");
+ }
+ catch (AMQException amqe)
+ {
+ if (!(amqe instanceof AMQConnectionFailureException))
+ {
+ fail("Correct exception not thrown. Excpected 'AMQConnectionFailureException' got: " + amqe);
+ }
+ }
+ finally
+ {
+ if (conn != null)
+ {
+ conn.close();
+ }
+ }
+ }
+
+ public void testClientIdCannotBeChanged() throws Exception
+ {
+ Connection connection = new AMQConnection(getBroker().toString(), "guest", "guest",
+ "fred", "test");
+ try
+ {
+ connection.setClientID("someClientId");
+ fail("No IllegalStateException thrown when resetting clientid");
+ }
+ catch (javax.jms.IllegalStateException e)
+ {
+ // PASS
+ }
+ finally
+ {
+ if (connection != null)
+ {
+ connection.close();
+ }
+ }
+ }
+
+ public void testClientIdIsPopulatedAutomatically() throws Exception
+ {
+ Connection connection = new AMQConnection(getBroker().toString(), "guest", "guest",
+ null, "test");
+ try
+ {
+ assertNotNull(connection.getClientID());
+ }
+ finally
+ {
+ connection.close();
+ }
+ connection.close();
+ }
+
+ public void testUnsupportedSASLMechanism() throws Exception
+ {
+ BrokerDetails broker = getBroker();
+ broker.setProperty(BrokerDetails.OPTIONS_SASL_MECHS, "MY_MECH");
+
+ try
+ {
+ Connection connection = new AMQConnection(broker.toString(), "guest", "guest",
+ null, "test");
+ connection.close();
+ fail("The client should throw a ConnectionException stating the" +
+ " broker does not support the SASL mech specified by the client");
+ }
+ catch (Exception e)
+ {
+ assertTrue("Incorrect exception thrown",
+ e.getMessage().contains("The following SASL mechanisms " +
+ "[MY_MECH]" +
+ " specified by the client are not supported by the broker"));
+ }
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(ConnectionTest.class);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/connection/ExceptionListenerTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/connection/ExceptionListenerTest.java
new file mode 100644
index 0000000000..cec9d292cf
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/connection/ExceptionListenerTest.java
@@ -0,0 +1,62 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.client.connection;
+
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import javax.jms.Connection;
+import javax.jms.ExceptionListener;
+import javax.jms.JMSException;
+
+/**
+ * ExceptionListenerTest
+ *
+ */
+
+public class ExceptionListenerTest extends QpidBrokerTestCase
+{
+
+ public void testBrokerDeath() throws Exception
+ {
+ Connection conn = getConnection("guest", "guest");
+
+ conn.start();
+
+ final CountDownLatch fired = new CountDownLatch(1);
+ conn.setExceptionListener(new ExceptionListener()
+ {
+ public void onException(JMSException e)
+ {
+ fired.countDown();
+ }
+ });
+
+ stopBroker();
+
+ if (!fired.await(3, TimeUnit.SECONDS))
+ {
+ fail("exception listener was not fired");
+ }
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/Client.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/Client.java
new file mode 100644
index 0000000000..b60fe76b76
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/Client.java
@@ -0,0 +1,143 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.client.forwardall;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+import javax.jms.MessageProducer;
+
+/**
+ * Declare a private temporary response queue,
+ * send a message to amq.direct with a well known routing key with the
+ * private response queue as the reply-to destination
+ * consume responses.
+ */
+public class Client implements MessageListener
+{
+ private static final Logger _logger = LoggerFactory.getLogger(Client.class);
+
+ private final AMQConnection _connection;
+ private final AMQSession _session;
+ private final int _expected;
+ private int _count;
+ private static QpidBrokerTestCase _qct;
+
+ Client(String broker, int expected) throws Exception
+ {
+ this(connect(broker), expected);
+ }
+
+ public static void setQTC(QpidBrokerTestCase qtc)
+ {
+ _qct = qtc;
+ }
+ Client(AMQConnection connection, int expected) throws Exception
+ {
+ _connection = connection;
+ _expected = expected;
+ _session = (AMQSession) _connection.createSession(true, AMQSession.NO_ACKNOWLEDGE);
+ AMQQueue response =
+ new AMQQueue(_connection.getDefaultQueueExchangeName(), new AMQShortString("ResponseQueue"), true);
+ _session.createConsumer(response).setMessageListener(this);
+ _connection.start();
+ // AMQQueue service = new SpecialQueue(_connection, "ServiceQueue");
+ AMQQueue service = (AMQQueue) _session.createQueue("ServiceQueue") ;
+ Message request = _session.createTextMessage("Request!");
+ request.setJMSReplyTo(response);
+ MessageProducer prod = _session.createProducer(service);
+ prod.send(request);
+ _session.commit();
+ }
+
+ void shutdownWhenComplete() throws Exception
+ {
+ waitUntilComplete();
+ _connection.close();
+ }
+
+ public synchronized void onMessage(Message response)
+ {
+
+ _logger.info("Received " + (++_count) + " of " + _expected + " responses.");
+ if (_count == _expected)
+ {
+
+ notifyAll();
+ }
+ try
+ {
+ _session.commit();
+ }
+ catch (JMSException e)
+ {
+
+ }
+
+ }
+
+ synchronized void waitUntilComplete() throws Exception
+ {
+
+ if (_count < _expected)
+ {
+ wait(60000);
+ }
+
+ if (_count < _expected)
+ {
+ throw new Exception("Didn't receive all messages... got " + _count + " expected " + _expected);
+ }
+ }
+
+ static AMQConnection connect(String broker) throws Exception
+ {
+ //return new AMQConnection(broker, "guest", "guest", "Client" + System.currentTimeMillis(), "test");
+ return (AMQConnection) _qct.getConnection("guest", "guest") ;
+ }
+
+ public static void main(String[] argv) throws Exception
+ {
+ final String connectionString;
+ final int expected;
+ if (argv.length == 0)
+ {
+ connectionString = "localhost:5672";
+ expected = 100;
+ }
+ else
+ {
+ connectionString = argv[0];
+ expected = Integer.parseInt(argv[1]);
+ }
+
+ new Client(connect(connectionString), expected).shutdownWhenComplete();
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/CombinedTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/CombinedTest.java
new file mode 100644
index 0000000000..45945eb8fc
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/CombinedTest.java
@@ -0,0 +1,69 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.client.forwardall;
+
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Runs the Service's and Client parts of the test in the same process
+ * as the broker
+ */
+public class CombinedTest extends QpidBrokerTestCase
+{
+ private static final Logger _logger = LoggerFactory.getLogger(CombinedTest.class);
+ private int run = 0;
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ Service.setQTC(this);
+ Client.setQTC(this);
+ }
+
+ protected void tearDown() throws Exception
+ {
+ ServiceCreator.closeAll();
+ super.tearDown();
+ }
+
+ public void testForwardAll() throws Exception
+ {
+ while (run < 10)
+ {
+ int services =1;
+ ServiceCreator.start("vm://:1", services);
+
+ _logger.info("Starting " + ++run + " client...");
+
+ new Client("vm://:1", services).shutdownWhenComplete();
+
+
+ _logger.info("Completed " + run + " successfully!");
+ }
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(CombinedTest.class);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/Service.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/Service.java
new file mode 100644
index 0000000000..160700bdda
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/Service.java
@@ -0,0 +1,95 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.client.forwardall;
+
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+/**
+ * Declare a queue and bind it to amq.direct with a 'well known' routing key,
+ * register a consumer for this queue and send a response to every message received.
+ */
+public class Service implements MessageListener
+{
+ private final AMQConnection _connection;
+ private final AMQSession _session;
+
+ private static QpidBrokerTestCase _qct;
+
+
+ public static void setQTC(QpidBrokerTestCase qtc)
+ {
+ _qct = qtc;
+ }
+ Service(String broker) throws Exception
+ {
+ this(connect(broker));
+ }
+
+ Service(AMQConnection connection) throws Exception
+ {
+ _connection = connection;
+ //AMQQueue queue = new SpecialQueue(connection, "ServiceQueue");
+ _session = (AMQSession) _connection.createSession(true, AMQSession.NO_ACKNOWLEDGE);
+ AMQQueue queue = (AMQQueue) _session.createQueue("ServiceQueue") ;
+ _session.createConsumer(queue).setMessageListener(this);
+ _connection.start();
+ }
+
+ public void onMessage(Message request)
+ {
+ try
+ {
+ Message response = _session.createTextMessage("Response!");
+ Destination replyTo = request.getJMSReplyTo();
+ _session.createProducer(replyTo).send(response);
+ _session.commit();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace(System.out);
+ }
+ }
+
+ public void close() throws JMSException
+ {
+ _connection.close();
+ }
+
+ static AMQConnection connect(String broker) throws Exception
+ {
+ //return new AMQConnection(broker, "guest", "guest", "Client" + System.currentTimeMillis(), "test");
+ return (AMQConnection) _qct.getConnection("guest", "guest") ;
+ }
+
+// public static void main(String[] argv) throws Exception
+// {
+// String broker = argv.length == 0? "localhost:5672" : argv[0];
+// new Service(broker);
+// }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/ServiceCreator.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/ServiceCreator.java
new file mode 100644
index 0000000000..be16f6b7ae
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/ServiceCreator.java
@@ -0,0 +1,112 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.client.forwardall;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.JMSException;
+
+public class ServiceCreator implements Runnable
+{
+ private static final Logger _logger = LoggerFactory.getLogger(ServiceCreator.class);
+
+ private static Thread[] threads;
+ private static ServiceCreator[] _services;
+
+ private final String broker;
+ private Service service;
+
+ ServiceCreator(String broker)
+ {
+ this.broker = broker;
+ }
+
+ public void run()
+ {
+ try
+ {
+ service = new Service(broker);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace(System.out);
+ }
+ }
+
+ public void closeSC() throws JMSException
+ {
+ service.close();
+ }
+
+ static void closeAll()
+ {
+ for (int i = 0; i < _services.length; i++)
+ {
+ try
+ {
+ _services[i].closeSC();
+ }
+ catch (JMSException e)
+ {
+ // ignore
+ }
+ }
+ }
+
+ static void start(String broker, int services) throws InterruptedException
+ {
+ threads = new Thread[services];
+ _services = new ServiceCreator[services];
+ ServiceCreator runner = new ServiceCreator(broker);
+ // start services
+ _logger.info("Starting " + services + " services...");
+ for (int i = 0; i < services; i++)
+ {
+ threads[i] = new Thread(runner);
+ _services[i] = runner;
+ threads[i].start();
+ }
+
+ for (int i = 0; i < threads.length; i++)
+ {
+ threads[i].join();
+ }
+ }
+
+ public static void main(String[] argv) throws Exception
+ {
+ final String connectionString;
+ final int services;
+ if (argv.length == 0)
+ {
+ connectionString = "localhost:5672";
+ services = 100;
+ }
+ else
+ {
+ connectionString = argv[0];
+ services = Integer.parseInt(argv[1]);
+ }
+
+ start(connectionString, services);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/SpecialQueue.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/SpecialQueue.java
new file mode 100644
index 0000000000..27371b0397
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/forwardall/SpecialQueue.java
@@ -0,0 +1,46 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.client.forwardall;
+
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.framing.AMQShortString;
+
+/**
+ * Queue that allows several private queues to be registered and bound
+ * to an exchange with the same routing key.
+ *
+ */
+class SpecialQueue extends AMQQueue
+{
+ private final AMQShortString name;
+
+ SpecialQueue(AMQConnection con, String name)
+ {
+ super(con, name, true);
+ this.name = new AMQShortString(name);
+ }
+
+ public AMQShortString getRoutingKey()
+ {
+ return name;
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/message/ObjectMessageTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/message/ObjectMessageTest.java
new file mode 100644
index 0000000000..fd28b86762
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/message/ObjectMessageTest.java
@@ -0,0 +1,335 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.client.message;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+import javax.jms.MessageProducer;
+import javax.jms.ObjectMessage;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+
+public class ObjectMessageTest extends QpidBrokerTestCase implements MessageListener
+{
+ private static final Logger _logger = LoggerFactory.getLogger(ObjectMessageTest.class);
+
+ private AMQConnection connection;
+ private AMQDestination destination;
+ private AMQSession session;
+ private MessageProducer producer;
+ private Serializable[] data;
+ private volatile boolean waiting;
+ private int received;
+ private final ArrayList items = new ArrayList();
+
+ private String _broker = "vm://:1";
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ connection = (AMQConnection) getConnection("guest", "guest");
+ destination = new AMQQueue(connection, randomize("LatencyTest"), true);
+ session = (AMQSession) connection.createSession(false, AMQSession.NO_ACKNOWLEDGE);
+
+ // set up a consumer
+ session.createConsumer(destination).setMessageListener(this);
+ connection.start();
+
+ // create a publisher
+ producer = session.createProducer(destination, false, false, true);
+ A a1 = new A(1, "A");
+ A a2 = new A(2, "a");
+ B b = new B(1, "B");
+ C c = new C();
+ c.put("A1", a1);
+ c.put("a2", a2);
+ c.put("B", b);
+ c.put("String", "String");
+
+ data = new Serializable[] { a1, a2, b, c, "Hello World!", new Integer(1001) };
+ }
+
+ protected void tearDown() throws Exception
+ {
+ close();
+ super.tearDown();
+ }
+
+ public ObjectMessageTest()
+ { }
+
+ ObjectMessageTest(String broker) throws Exception
+ {
+ _broker = broker;
+ }
+
+ public void testSendAndReceive() throws Exception
+ {
+ try
+ {
+ send();
+ waitUntilReceived(data.length);
+ check();
+ _logger.info("All " + data.length + " items matched.");
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("This Test should succeed but failed due to: " + e);
+ }
+ }
+
+ public void testSetObjectPropertyForString() throws Exception
+ {
+ String testStringProperty = "TestStringProperty";
+ ObjectMessage msg = session.createObjectMessage(data[0]);
+ msg.setObjectProperty("TestStringProperty", testStringProperty);
+ assertEquals(testStringProperty, msg.getObjectProperty("TestStringProperty"));
+ }
+
+ public void testSetObjectPropertyForBoolean() throws Exception
+ {
+ ObjectMessage msg = session.createObjectMessage(data[0]);
+ msg.setObjectProperty("TestBooleanProperty", Boolean.TRUE);
+ assertEquals(Boolean.TRUE, msg.getObjectProperty("TestBooleanProperty"));
+ }
+
+ public void testSetObjectPropertyForByte() throws Exception
+ {
+ ObjectMessage msg = session.createObjectMessage(data[0]);
+ msg.setObjectProperty("TestByteProperty", Byte.MAX_VALUE);
+ assertEquals(Byte.MAX_VALUE, msg.getObjectProperty("TestByteProperty"));
+ }
+
+ public void testSetObjectPropertyForShort() throws Exception
+ {
+ ObjectMessage msg = session.createObjectMessage(data[0]);
+ msg.setObjectProperty("TestShortProperty", Short.MAX_VALUE);
+ assertEquals(Short.MAX_VALUE, msg.getObjectProperty("TestShortProperty"));
+ }
+
+ public void testSetObjectPropertyForInteger() throws Exception
+ {
+ ObjectMessage msg = session.createObjectMessage(data[0]);
+ msg.setObjectProperty("TestIntegerProperty", Integer.MAX_VALUE);
+ assertEquals(Integer.MAX_VALUE, msg.getObjectProperty("TestIntegerProperty"));
+ }
+
+ public void testSetObjectPropertyForDouble() throws Exception
+ {
+ ObjectMessage msg = session.createObjectMessage(data[0]);
+ msg.setObjectProperty("TestDoubleProperty", Double.MAX_VALUE);
+ assertEquals(Double.MAX_VALUE, msg.getObjectProperty("TestDoubleProperty"));
+ }
+
+ public void testSetObjectPropertyForFloat() throws Exception
+ {
+ ObjectMessage msg = session.createObjectMessage(data[0]);
+ msg.setObjectProperty("TestFloatProperty", Float.MAX_VALUE);
+ assertEquals(Float.MAX_VALUE, msg.getObjectProperty("TestFloatProperty"));
+ }
+
+ public void testSetObjectPropertyForByteArray() throws Exception
+ {
+ byte[] array = { 1, 2, 3, 4, 5 };
+ ObjectMessage msg = session.createObjectMessage(data[0]);
+ msg.setObjectProperty("TestByteArrayProperty", array);
+ assertTrue(Arrays.equals(array, (byte[]) msg.getObjectProperty("TestByteArrayProperty")));
+ }
+
+ public void testSetObjectForNull() throws Exception
+ {
+ ObjectMessage msg = session.createObjectMessage();
+ msg.setObject(null);
+ assertNull(msg.getObject());
+ }
+
+ private void send() throws Exception
+ {
+ for (int i = 0; i < data.length; i++)
+ {
+ ObjectMessage msg;
+ if ((i % 2) == 0)
+ {
+ msg = session.createObjectMessage(data[i]);
+ }
+ else
+ {
+ msg = session.createObjectMessage();
+ msg.setObject(data[i]);
+ }
+
+ producer.send(msg);
+ }
+ }
+
+ public void check() throws Exception
+ {
+ Object[] actual = (Object[]) items.toArray();
+ if (actual.length != data.length)
+ {
+ throw new Exception("Expected " + data.length + " objects, got " + actual.length);
+ }
+
+ for (int i = 0; i < data.length; i++)
+ {
+ if (actual[i] instanceof Exception)
+ {
+ throw new Exception("Error on receive of " + data[i], ((Exception) actual[i]));
+ }
+
+ if (actual[i] == null)
+ {
+ throw new Exception("Expected " + data[i] + " got null");
+ }
+
+ if (!data[i].equals(actual[i]))
+ {
+ throw new Exception("Expected " + data[i] + " got " + actual[i]);
+ }
+ }
+ }
+
+ private void close() throws Exception
+ {
+ session.close();
+ connection.close();
+ }
+
+ private synchronized void waitUntilReceived(int count) throws InterruptedException
+ {
+ waiting = true;
+ while (received < count)
+ {
+ wait();
+ }
+
+ waiting = false;
+ }
+
+ public void onMessage(Message message)
+ {
+
+ try
+ {
+ if (message instanceof ObjectMessage)
+ {
+ items.add(((ObjectMessage) message).getObject());
+ }
+ else
+ {
+ _logger.error("ERROR: Got " + message.getClass().getName() + " not ObjectMessage");
+ items.add(message);
+ }
+ }
+ catch (JMSException e)
+ {
+ e.printStackTrace();
+ items.add(e);
+ }
+
+ synchronized (this)
+ {
+ received++;
+ notify();
+ }
+ }
+
+ public static void main(String[] argv) throws Exception
+ {
+ String broker = (argv.length > 0) ? argv[0] : "vm://:1";
+ if ("-help".equals(broker))
+ {
+ System.out.println("Usage: <broker>");
+ }
+
+ new ObjectMessageTest(broker).testSendAndReceive();
+ }
+
+ private static class A implements Serializable
+ {
+ private String sValue;
+ private int iValue;
+
+ A(int i, String s)
+ {
+ sValue = s;
+ iValue = i;
+ }
+
+ public int hashCode()
+ {
+ return iValue;
+ }
+
+ public boolean equals(Object o)
+ {
+ return (o instanceof A) && equals((A) o);
+ }
+
+ protected boolean equals(A a)
+ {
+ return areEqual(a.sValue, sValue) && (a.iValue == iValue);
+ }
+ }
+
+ private static class B extends A
+ {
+ private long time;
+
+ B(int i, String s)
+ {
+ super(i, s);
+ time = System.currentTimeMillis();
+ }
+
+ protected boolean equals(A a)
+ {
+ return super.equals(a) && (a instanceof B) && (time == ((B) a).time);
+ }
+ }
+
+ private static class C extends HashMap implements Serializable
+ { }
+
+ private static boolean areEqual(Object a, Object b)
+ {
+ return (a == null) ? (b == null) : a.equals(b);
+ }
+
+ private static String randomize(String in)
+ {
+ return in + System.currentTimeMillis();
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/protocol/AMQProtocolSessionTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/protocol/AMQProtocolSessionTest.java
new file mode 100644
index 0000000000..278b9e9c04
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/protocol/AMQProtocolSessionTest.java
@@ -0,0 +1,106 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.client.protocol;
+
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.net.UnknownHostException;
+
+import org.apache.mina.transport.vmpipe.VmPipeAddress;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.protocol.AMQProtocolHandler;
+import org.apache.qpid.client.protocol.AMQProtocolSession;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.transport.TestNetworkDriver;
+
+public class AMQProtocolSessionTest extends QpidBrokerTestCase
+{
+ private static class AMQProtSession extends AMQProtocolSession
+ {
+
+ public AMQProtSession(AMQProtocolHandler protocolHandler, AMQConnection connection)
+ {
+ super(protocolHandler,connection);
+ }
+
+ public TestNetworkDriver getNetworkDriver()
+ {
+ return (TestNetworkDriver) _protocolHandler.getNetworkDriver();
+ }
+
+ public AMQShortString genQueueName()
+ {
+ return generateQueueName();
+ }
+ }
+
+ private AMQProtSession _testSession;
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ AMQConnection con = (AMQConnection) getConnection("guest", "guest");
+ AMQProtocolHandler protocolHandler = new AMQProtocolHandler(con);
+ protocolHandler.setNetworkDriver(new TestNetworkDriver());
+
+ //don't care about the values set here apart from the dummy IoSession
+ _testSession = new AMQProtSession(protocolHandler , con);
+ }
+
+ public void testTemporaryQueueWildcard() throws UnknownHostException
+ {
+ checkTempQueueName(new InetSocketAddress(1234), "tmp_0_0_0_0_0_0_0_0_1234_1");
+ }
+
+ public void testTemporaryQueueLocalhostAddr() throws UnknownHostException
+ {
+ checkTempQueueName(new InetSocketAddress(InetAddress.getByName("127.0.0.1"), 1234), "tmp_127_0_0_1_1234_1");
+ }
+
+ public void testTemporaryQueueLocalhostName() throws UnknownHostException
+ {
+ checkTempQueueName(new InetSocketAddress(InetAddress.getByName("localhost"), 1234), "tmp_localhost_127_0_0_1_1234_1");
+ }
+
+ public void testTemporaryQueueInet4() throws UnknownHostException
+ {
+ checkTempQueueName(new InetSocketAddress(InetAddress.getByName("192.168.1.2"), 1234), "tmp_192_168_1_2_1234_1");
+ }
+
+ public void testTemporaryQueueInet6() throws UnknownHostException
+ {
+ checkTempQueueName(new InetSocketAddress(InetAddress.getByName("1080:0:0:0:8:800:200C:417A"), 1234), "tmp_1080_0_0_0_8_800_200c_417a_1234_1");
+ }
+
+ public void testTemporaryQueuePipe() throws UnknownHostException
+ {
+ checkTempQueueName(new VmPipeAddress(1), "tmp_vm_1_1");
+ }
+
+ private void checkTempQueueName(SocketAddress address, String queueName)
+ {
+ _testSession.getNetworkDriver().setLocalAddress(address);
+ assertEquals("Wrong queue name", queueName, _testSession.genQueueName().asString());
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/temporaryqueue/TemporaryQueueTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/temporaryqueue/TemporaryQueueTest.java
new file mode 100644
index 0000000000..8c806fa2da
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/client/temporaryqueue/TemporaryQueueTest.java
@@ -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.
+ *
+ */
+
+package org.apache.qpid.test.unit.client.temporaryqueue;
+
+import javax.jms.Connection;
+import javax.jms.ExceptionListener;
+import javax.jms.JMSException;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import javax.jms.TemporaryQueue;
+import javax.jms.TextMessage;
+import junit.framework.Assert;
+
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.jms.ConnectionListener;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.LinkedList;
+
+public class TemporaryQueueTest extends QpidBrokerTestCase implements ExceptionListener
+{
+ private List<Exception> _exceptions = new ArrayList<Exception>();
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ }
+
+ protected void tearDown() throws Exception
+ {
+ super.tearDown();
+ }
+
+ protected Connection createConnection() throws Exception
+ {
+ return getConnection("guest", "guest");
+ }
+
+ public void testTemporaryQueue() throws Exception
+ {
+ Connection conn = createConnection();
+ Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ TemporaryQueue queue = session.createTemporaryQueue();
+ assertNotNull(queue);
+ MessageProducer producer = session.createProducer(queue);
+ MessageConsumer consumer = session.createConsumer(queue);
+ conn.start();
+ producer.send(session.createTextMessage("hello"));
+ TextMessage tm = (TextMessage) consumer.receive(2000);
+ assertNotNull(tm);
+ assertEquals("hello", tm.getText());
+
+ try
+ {
+ queue.delete();
+ fail("Expected JMSException : should not be able to delete while there are active consumers");
+ }
+ catch (JMSException je)
+ {
+ ; //pass
+ }
+
+ consumer.close();
+
+ try
+ {
+ queue.delete();
+ }
+ catch (JMSException je)
+ {
+ fail("Unexpected Exception: " + je.getMessage());
+ }
+
+ conn.close();
+ }
+
+ public void tUniqueness() throws Exception
+ {
+ int numProcs = Runtime.getRuntime().availableProcessors();
+ final int threadsProc = 5;
+
+ runUniqueness(1, 10);
+ runUniqueness(numProcs * threadsProc, 10);
+ runUniqueness(numProcs * threadsProc, 100);
+ runUniqueness(numProcs * threadsProc, 500);
+ }
+
+ void runUniqueness(int makers, int queues) throws Exception
+ {
+ Connection connection = createConnection();
+
+ Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+
+ List<TempQueueMaker> tqList = new LinkedList<TempQueueMaker>();
+
+ //Create Makers
+ for (int m = 0; m < makers; m++)
+ {
+ tqList.add(new TempQueueMaker(session, queues));
+ }
+
+
+ List<Thread> threadList = new LinkedList<Thread>();
+
+ //Create Makers
+ for (TempQueueMaker maker : tqList)
+ {
+ threadList.add(new Thread(maker));
+ }
+
+ //Start threads
+ for (Thread thread : threadList)
+ {
+ thread.start();
+ }
+
+ // Join Threads
+ for (Thread thread : threadList)
+ {
+ try
+ {
+ thread.join();
+ }
+ catch (InterruptedException e)
+ {
+ fail("Couldn't correctly join threads");
+ }
+ }
+
+
+ List<AMQQueue> list = new LinkedList<AMQQueue>();
+
+ // Test values
+ for (TempQueueMaker maker : tqList)
+ {
+ check(maker, list);
+ }
+
+ Assert.assertEquals("Not enough queues made.", makers * queues, list.size());
+
+ connection.close();
+ }
+
+ private void check(TempQueueMaker tq, List<AMQQueue> list)
+ {
+ for (AMQQueue q : tq.getList())
+ {
+ if (list.contains(q))
+ {
+ fail(q + " already exists.");
+ }
+ else
+ {
+ list.add(q);
+ }
+ }
+ }
+
+
+ class TempQueueMaker implements Runnable
+ {
+ List<AMQQueue> _queues;
+ Session _session;
+ private int _count;
+
+
+ TempQueueMaker(Session session, int queues) throws JMSException
+ {
+ _queues = new LinkedList<AMQQueue>();
+
+ _count = queues;
+
+ _session = session;
+ }
+
+ public void run()
+ {
+ int i = 0;
+ try
+ {
+ for (; i < _count; i++)
+ {
+ _queues.add((AMQQueue) _session.createTemporaryQueue());
+ }
+ }
+ catch (JMSException jmse)
+ {
+ //stop
+ }
+ }
+
+ List<AMQQueue> getList()
+ {
+ return _queues;
+ }
+ }
+
+ public void testQPID1217() throws Exception
+ {
+ Connection conA = getConnection();
+ conA.setExceptionListener(this);
+ Session sessA = conA.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ TemporaryQueue temp = sessA.createTemporaryQueue();
+
+ MessageProducer prod = sessA.createProducer(temp);
+ prod.send(sessA.createTextMessage("hi"));
+
+ Thread.sleep(500);
+ assertTrue("Exception received", _exceptions.isEmpty());
+
+ Connection conB = getConnection();
+ Session sessB = conB.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ JMSException ex = null;
+ try
+ {
+ MessageConsumer consB = sessB.createConsumer(temp);
+ }
+ catch (JMSException e)
+ {
+ ex = e;
+ }
+ assertNotNull(ex);
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(TemporaryQueueTest.class);
+ }
+
+ public void onException(JMSException arg0)
+ {
+ _exceptions.add(arg0);
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/close/CloseBeforeAckTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/close/CloseBeforeAckTest.java
new file mode 100644
index 0000000000..039a172e4d
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/close/CloseBeforeAckTest.java
@@ -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.
+ *
+ */
+package org.apache.qpid.test.unit.close;
+
+import junit.framework.Assert;
+
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.qpid.junit.concurrency.TestRunnable;
+import org.apache.qpid.junit.concurrency.ThreadTestCoordinator;
+
+import javax.jms.Connection;
+import javax.jms.Message;
+import javax.jms.MessageListener;
+import javax.jms.Session;
+
+/**
+ * This test forces the situation where a session is closed whilst a message consumer is still in its onMessage method.
+ * Running in AUTO_ACK mode, the close call ought to wait until the onMessage method completes, and the ack is sent
+ * before closing the connection.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption> <tr><th> Responsibilities <th> Collaborations <tr><td> Check that
+ * closing a connection whilst handling a message, blocks till completion of the handler. </table>
+ */
+public class CloseBeforeAckTest extends QpidBrokerTestCase
+{
+ private static final Logger log = LoggerFactory.getLogger(CloseBeforeAckTest.class);
+
+ Connection connection;
+ Session session;
+ public static final String TEST_QUEUE_NAME = "TestQueue";
+ private int TEST_COUNT = 25;
+
+ class TestThread1 extends TestRunnable implements MessageListener
+ {
+ public void runWithExceptions() throws Exception
+ {
+ // Set this up to listen for message on the test session.
+ session.createConsumer(session.createQueue(TEST_QUEUE_NAME)).setMessageListener(this);
+ }
+
+ public void onMessage(Message message)
+ {
+ // Give thread 2 permission to close the session.
+ allow(new int[] { 1 });
+
+ // Wait until thread 2 has closed the connection, or is blocked waiting for this to complete.
+ waitFor(new int[] { 1 }, true);
+ }
+ }
+
+ TestThread1 testThread1 = new TestThread1();
+
+ TestRunnable testThread2 =
+ new TestRunnable()
+ {
+ public void runWithExceptions() throws Exception
+ {
+ // Send a message to be picked up by thread 1.
+ session.createProducer(null).send(session.createQueue(TEST_QUEUE_NAME),
+ session.createTextMessage("Hi there thread 1!"));
+
+ // Wait for thread 1 to pick up the message and give permission to continue.
+ waitFor(new int[] { 0 }, false);
+
+ // Close the connection.
+ session.close();
+
+ // Allow thread 1 to continue to completion, if it is erronously still waiting.
+ allow(new int[] { 1 });
+ }
+ };
+
+ public void testCloseBeforeAutoAck_QPID_397() throws Exception
+ {
+ // Create a session in auto acknowledge mode. This problem shows up in auto acknowledge if the client acks
+ // message at the end of the onMessage method, after a close has been sent.
+ session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ ThreadTestCoordinator tt = new ThreadTestCoordinator(2);
+
+ tt.addTestThread(testThread1, 0);
+ tt.addTestThread(testThread2, 1);
+ tt.setDeadlockTimeout(500);
+ tt.run();
+
+ String errorMessage = tt.joinAndRetrieveMessages();
+
+ // Print any error messages or exceptions.
+ log.debug(errorMessage);
+
+ if (!tt.getExceptions().isEmpty())
+ {
+ for (Exception e : tt.getExceptions())
+ {
+ log.debug("Exception thrown during test thread: ", e);
+ }
+ }
+
+ Assert.assertTrue(errorMessage, "".equals(errorMessage));
+ }
+
+ public void closeBeforeAutoAckManyTimes() throws Exception
+ {
+ for (int i = 0; i < TEST_COUNT; i++)
+ {
+ testCloseBeforeAutoAck_QPID_397();
+ }
+ }
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ connection = getConnection("guest", "guest");
+ }
+
+ protected void tearDown() throws Exception
+ {
+ super.tearDown();
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/close/JavaServerCloseRaceConditionTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/close/JavaServerCloseRaceConditionTest.java
new file mode 100644
index 0000000000..6bc6c591ae
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/close/JavaServerCloseRaceConditionTest.java
@@ -0,0 +1,119 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.close;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.framing.AMQFrame;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.ExchangeDeclareBody;
+import org.apache.qpid.framing.ExchangeDeclareOkBody;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import javax.jms.Session;
+
+/** QPID-1809
+ *
+ * Race condition on error handling and close logic.
+ *
+ * See most often with SimpleACLTest as this test is the expects the server to
+ * shut the connection/channels. This sort of testing is not performed by many,
+ * if any, of the other system tests.
+ *
+ * The problem is that we have two threads
+ *
+ * MainThread Exception(Mina)Thread
+ * | |
+ * Performs |
+ * ACtion |
+ * | Receives Server
+ * | Close
+ * Blocks for |
+ * Response |
+ * | Starts To Notify
+ * | client
+ * | |
+ * | <----- Notify Main Thread
+ * Notification |
+ * wakes client |
+ * | |
+ * Client then |
+ * processes Error. |
+ * | |
+ * Potentially Attempting Close Channel/Connection
+ * Connection Close
+ *
+ * The two threads both attempt to close the connection but the main thread does
+ * so assuming that the connection is open and valid.
+ *
+ * The Exception thread must modify the connection so that no furter syncWait
+ * commands are performed.
+ *
+ * This test sends an ExchangeDeclare that is Asynchronous and will fail and
+ * so cause a ChannelClose error but we perform a syncWait so that we can be
+ * sure to test that the BlockingWaiter is correctly awoken.
+ *
+ */
+public class JavaServerCloseRaceConditionTest extends QpidBrokerTestCase
+{
+ private static final String EXCHANGE_NAME = "NewExchangeNametoFailLookup";
+
+ public void test() throws Exception
+ {
+
+ AMQConnection connection = (AMQConnection) getConnection();
+
+ AMQSession session = (AMQSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ // Set no wait true so that we block the connection
+ // Also set a different exchange class string so the attempt to declare
+ // the exchange causes an exchange.
+ ExchangeDeclareBody body = session.getMethodRegistry().createExchangeDeclareBody(session.getTicket(), new AMQShortString(EXCHANGE_NAME), null,
+ true, false, false, false, true, null);
+
+ AMQFrame exchangeDeclare = body.generateFrame(session.getChannelId());
+
+ try
+ {
+ // block our thread so that can times out
+ connection.getProtocolHandler().syncWrite(exchangeDeclare, ExchangeDeclareOkBody.class);
+ }
+ catch (Exception e)
+ {
+ assertTrue("Exception should say the exchange is not known.", e.getMessage().contains("Unknown exchange: " + EXCHANGE_NAME));
+ }
+
+ try
+ {
+ // Depending on if the notification thread has closed the connection
+ // or not we may get an exception here when we attempt to close the
+ // connection. If we do get one then it should be the same as above
+ // an AMQAuthenticationException.
+ connection.close();
+ }
+ catch (Exception e)
+ {
+ assertTrue("Exception should say the exchange is not known.", e.getMessage().contains("Unknown exchange: " + EXCHANGE_NAME));
+ }
+
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/close/MessageRequeueTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/close/MessageRequeueTest.java
new file mode 100644
index 0000000000..de092fc893
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/close/MessageRequeueTest.java
@@ -0,0 +1,373 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.test.unit.close;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.test.utils.QpidClientConnection;
+import org.apache.qpid.client.message.AbstractJMSMessage;
+import org.apache.qpid.url.URLSyntaxException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.Queue;
+import javax.jms.Session;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class MessageRequeueTest extends QpidBrokerTestCase
+{
+ private static final Logger _logger = LoggerFactory.getLogger(MessageRequeueTest.class);
+
+ protected static AtomicInteger consumerIds = new AtomicInteger(0);
+ protected final Integer numTestMessages = 150;
+
+ protected final int consumeTimeout = 3000;
+
+ protected final String queue = "direct://amq.direct//message-requeue-test-queue";
+ protected String payload = "Message:";
+
+ //protected final String BROKER = "vm://:1";
+ protected final String BROKER = "tcp://127.0.0.1:5672";
+ private boolean testReception = true;
+
+ private long[] receieved = new long[numTestMessages + 1];
+ private boolean passed = false;
+ QpidClientConnection conn;
+
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+
+ conn = new QpidClientConnection(BROKER);
+
+ conn.connect();
+ // clear queue
+ conn.consume(queue, consumeTimeout);
+ // load test data
+ _logger.info("creating test data, " + numTestMessages + " messages");
+ conn.put(queue, payload, numTestMessages);
+ // close this connection
+ conn.disconnect();
+ }
+
+ protected void tearDown() throws Exception
+ {
+
+ if (!passed) // clean up
+ {
+ QpidClientConnection conn = new QpidClientConnection(BROKER);
+
+ conn.connect();
+ // clear queue
+ conn.consume(queue, consumeTimeout);
+
+ conn.disconnect();
+ }
+
+ super.tearDown();
+ }
+
+ /**
+ * multiple consumers
+ *
+ * @throws javax.jms.JMSException if a JMS problem occurs
+ * @throws InterruptedException on timeout
+ */
+ public void testDrain() throws Exception
+ {
+ QpidClientConnection conn = new QpidClientConnection(BROKER);
+
+ conn.connect();
+
+ _logger.info("consuming queue " + queue);
+ Queue q = conn.getSession().createQueue(queue);
+
+ final MessageConsumer consumer = conn.getSession().createConsumer(q);
+ int messagesReceived = 0;
+
+ long[] messageLog = new long[numTestMessages + 1];
+
+ _logger.info("consuming...");
+ Message msg = consumer.receive(1000);
+ while (msg != null)
+ {
+ messagesReceived++;
+
+ long dt = ((AbstractJMSMessage) msg).getDeliveryTag();
+
+ int msgindex = msg.getIntProperty("index");
+ if (messageLog[msgindex] != 0)
+ {
+ _logger.error("Received Message(" + msgindex + ":" + ((AbstractJMSMessage) msg).getDeliveryTag()
+ + ") more than once.");
+ }
+
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Received Message(" + System.identityHashCode(msgindex) + ") " + "DT:" + dt + "IN:" + msgindex);
+ }
+
+ if (dt == 0)
+ {
+ _logger.error("DT is zero for msg:" + msgindex);
+ }
+
+ messageLog[msgindex] = dt;
+
+ // get Next message
+ msg = consumer.receive(1000);
+ }
+
+ _logger.info("consuming done.");
+ conn.getSession().commit();
+ consumer.close();
+
+ int index = 0;
+ StringBuilder list = new StringBuilder();
+ list.append("Failed to receive:");
+ int failed = 0;
+
+ _logger.info("consumed: " + messagesReceived);
+
+ assertEquals("number of consumed messages does not match initial data", (int) numTestMessages, messagesReceived);
+ // wit 0_10 we can have a delivery tag of 0
+ if (conn.isBroker08())
+ {
+ for (long b : messageLog)
+ {
+ if ((b == 0) && (index != 0)) // delivery tag of zero shouldn't exist
+ {
+ _logger.error("Index: " + index + " was not received.");
+ list.append(" ");
+ list.append(index);
+ list.append(":");
+ list.append(b);
+ failed++;
+ }
+
+ index++;
+ }
+
+ assertEquals(list.toString(), 0, failed);
+ }
+
+ conn.disconnect();
+ passed = true;
+ }
+
+ /** multiple consumers
+ * Based on code subbmitted by client FT-304
+ */
+ public void testTwoCompetingConsumers()
+ {
+ Consumer c1 = new Consumer();
+ Consumer c2 = new Consumer();
+ Consumer c3 = new Consumer();
+ Consumer c4 = new Consumer();
+
+ Thread t1 = new Thread(c1);
+ Thread t2 = new Thread(c2);
+ Thread t3 = new Thread(c3);
+ Thread t4 = new Thread(c4);
+
+ t1.start();
+ t2.start();
+ t3.start();
+ // t4.start();
+
+ try
+ {
+ t1.join();
+ t2.join();
+ t3.join();
+ t4.join();
+ }
+ catch (InterruptedException e)
+ {
+ fail("Unable to join to Consumer theads");
+ }
+
+ _logger.info("consumer 1 count is " + c1.getCount());
+ _logger.info("consumer 2 count is " + c2.getCount());
+ _logger.info("consumer 3 count is " + c3.getCount());
+ _logger.info("consumer 4 count is " + c4.getCount());
+
+ Integer totalConsumed = c1.getCount() + c2.getCount() + c3.getCount() + c4.getCount();
+
+ // Check all messages were correctly delivered
+ int index = 0;
+ StringBuilder list = new StringBuilder();
+ list.append("Failed to receive:");
+ int failed = 0;
+ if (conn.isBroker08())
+ {
+ for (long b : receieved)
+ {
+ if ((b == 0) && (index != 0)) // delivery tag of zero shouldn't exist (and we don't have msg 0)
+ {
+ _logger.error("Index: " + index + " was not received.");
+ list.append(" ");
+ list.append(index);
+ list.append(":");
+ list.append(b);
+ failed++;
+ }
+
+ index++;
+ }
+
+ assertEquals(list.toString() + "-" + numTestMessages + "-" + totalConsumed, 0, failed);
+ }
+ assertEquals("number of consumed messages does not match initial data", numTestMessages, totalConsumed);
+ passed = true;
+ }
+
+ class Consumer implements Runnable
+ {
+ private Integer count = 0;
+ private Integer id;
+
+ public Consumer()
+ {
+ id = consumerIds.addAndGet(1);
+ }
+
+ public void run()
+ {
+ try
+ {
+ _logger.info("consumer-" + id + ": starting");
+ QpidClientConnection conn = new QpidClientConnection(BROKER);
+
+ conn.connect();
+
+ _logger.info("consumer-" + id + ": connected, consuming...");
+ Message result;
+ do
+ {
+ result = conn.getNextMessage(queue, consumeTimeout);
+ if (result != null)
+ {
+
+ long dt = ((AbstractJMSMessage) result).getDeliveryTag();
+
+ if (testReception)
+ {
+ int msgindex = result.getIntProperty("index");
+ if (receieved[msgindex] != 0)
+ {
+ _logger.error("Received Message(" + msgindex + ":"
+ + ((AbstractJMSMessage) result).getDeliveryTag() + ") more than once.");
+ }
+
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("Received Message(" + System.identityHashCode(msgindex) + ") " + "DT:" + dt
+ + "IN:" + msgindex);
+ }
+
+ if (dt == 0)
+ {
+ _logger.error("DT is zero for msg:" + msgindex);
+ }
+
+ receieved[msgindex] = dt;
+ }
+
+ count++;
+ if ((count % 100) == 0)
+ {
+ _logger.info("consumer-" + id + ": got " + result + ", new count is " + count);
+ }
+ }
+ }
+ while (result != null);
+
+ _logger.info("consumer-" + id + ": complete");
+ conn.disconnect();
+
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ public Integer getCount()
+ {
+ return count;
+ }
+
+ public Integer getId()
+ {
+ return id;
+ }
+ }
+
+ public void testRequeue() throws JMSException, AMQException, URLSyntaxException
+ {
+ int run = 0;
+ // while (run < 10)
+ {
+ run++;
+
+ if (_logger.isInfoEnabled())
+ {
+ _logger.info("testRequeue run " + run);
+ }
+
+ String virtualHost = "/test";
+ String brokerlist = BROKER;
+ String brokerUrl = "amqp://guest:guest@" + virtualHost + "?brokerlist='" + brokerlist + "'";
+ QpidClientConnection qpc = new QpidClientConnection(BROKER);
+ qpc.connect();
+ Connection conn = qpc. getConnection();
+
+ Session session = conn.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+ Queue q = session.createQueue(queue);
+
+ _logger.debug("Create Consumer");
+ MessageConsumer consumer = session.createConsumer(q);
+
+ conn.start();
+
+ _logger.debug("Receiving msg");
+ Message msg = consumer.receive(2000);
+
+ assertNotNull("Message should not be null", msg);
+
+ // As we have not ack'd message will be requeued.
+ _logger.debug("Close Consumer");
+ consumer.close();
+
+ _logger.debug("Close Connection");
+ conn.close();
+ }
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/close/TopicPublisherCloseTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/close/TopicPublisherCloseTest.java
new file mode 100644
index 0000000000..8a6dfb86ee
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/close/TopicPublisherCloseTest.java
@@ -0,0 +1,69 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */package org.apache.qpid.test.unit.close;
+
+import javax.jms.Session;
+import javax.jms.Topic;
+import javax.jms.TopicPublisher;
+import javax.jms.TopicSession;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQTopic;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+/**
+ * @author Apache Software Foundation
+ */
+public class TopicPublisherCloseTest extends QpidBrokerTestCase
+{
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ }
+
+
+ protected void tearDown() throws Exception
+ {
+ super.tearDown();
+ }
+
+ public void testAllMethodsThrowAfterConnectionClose() throws Exception
+ {
+ // give external brokers a chance to start up
+ Thread.sleep(3000);
+
+ AMQConnection connection = (AMQConnection) getConnection("guest", "guest");
+
+ Topic destination1 = new AMQTopic(connection, "t1");
+ TopicSession session1 = connection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
+ TopicPublisher pub = session1.createPublisher(destination1);
+ connection.close();
+ try
+ {
+ pub.getDeliveryMode();
+ fail("Expected exception not thrown");
+ }
+ catch (javax.jms.IllegalStateException e)
+ {
+ // PASS
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/close/VerifyAckingOkDuringClose.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/close/VerifyAckingOkDuringClose.java
new file mode 100644
index 0000000000..3b30b7d63f
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/close/VerifyAckingOkDuringClose.java
@@ -0,0 +1,160 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.close;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQConnectionFactory;
+import org.apache.qpid.jndi.PropertiesFileInitialContextFactory;
+
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.naming.InitialContext;
+import java.util.ArrayList;
+
+/**
+ * QPID-1791
+ *
+ * The threading model in the Java broker (at least till 0.5) allows for the
+ * close to be handled immediately even if the broker is still processing state
+ * for that Session.
+ *
+ * This test verifys that QPID-1791 is has been handled.
+ *
+ * The problem was that the whilst the Session is busy processing Acks from the
+ * client the Close frame jumps in and clears the unAcknowledgeMap in an
+ * attempt to start processing them for closing the connection.
+ *
+ * If the session had a consumer consuming from a temporary queue. The closing
+ * thread dequeues and deletes the message that were on the uncknowledgedMap.
+ *
+ * However, the Acking thread currently does:
+ * queuEntry = unackedMap.get(messageID)
+ *
+ * dequeueAndDelete(queueEntry)
+ *
+ * unackedMap.remove(messageID)
+ *
+ * As a result the queueEntry is sitting in the unackedMap whilst it is being
+ * dequeuedAndDeleted which leaves the opportunity for the close thread to
+ * remove contents of the unackedMap for processing. The close thread will then
+ * dequeueAndDelete all these values one of which the acking thread is currently
+ * processing.
+ *
+ *
+ * Test Approach
+ *
+ * Send a lot of persistent messages (5000), the goal of which is to fill the
+ * pretch and to provide the broker with a lot of acks to process
+ *
+ * Using client ack and prefetch buffer of 5000 use receive to get 2500
+ * Use AMQMessage.acknowledgeThis() to send a single ack frame back to the
+ * broker per message so 2500 ack frames.
+ * This will give the broker a lot to process,
+ * Immediately send the consumer close after the acks are all gone.
+ * This will cause the remaining 2500 prefetched messages plus any that have
+ * not yet had their acks processed
+ * to be collected by the requeue() process potentially
+ */
+public class VerifyAckingOkDuringClose
+{
+
+ static final int MESSAGE_SENT = 5000;
+
+ public static void main(String[] args) throws Exception
+ {
+ //Check that we have the InitialContext Configured
+
+ if (System.getProperty(InitialContext.INITIAL_CONTEXT_FACTORY) == null)
+ {
+ System.setProperty(InitialContext.INITIAL_CONTEXT_FACTORY, PropertiesFileInitialContextFactory.class.getName());
+ }
+
+ if (System.getProperty(InitialContext.PROVIDER_URL) == null)
+ {
+ System.err.println(InitialContext.PROVIDER_URL + ": Is not set and is required to contain a 'default' ConnectionFactory.");
+ System.exit(1);
+ }
+
+ //Retreive the local factory from the properties file
+ // when used with perftest.properties this will be localhost:5672
+ AMQConnectionFactory factory = (AMQConnectionFactory) new InitialContext().lookup("default");
+
+ AMQConnection connection = (AMQConnection) factory.createConnection("guest", "guest");
+
+ //Use the AMQConnection Interface to set the prefetch to the number
+ // we are sending
+ Session session = connection.createSession(false,
+ Session.CLIENT_ACKNOWLEDGE,
+ MESSAGE_SENT);
+
+ Queue queue = session.createTemporaryQueue();
+
+ MessageConsumer consumer = session.createConsumer(queue);
+ connection.start();
+
+ MessageProducer producer = session.createProducer(queue);
+
+ Message message = session.createTextMessage("Close");
+
+ for (int i = 0; i < MESSAGE_SENT; i++)
+ {
+ message.setIntProperty("SequenceNumber", i);
+
+ producer.send(message);
+ }
+
+ // Put a reasonable about of data on the queue.
+
+ //Receive all the messags
+ ArrayList<Message> received = new ArrayList<Message>();
+
+ message = consumer.receive(2000);
+
+ while (message != null)
+ {
+ received.add(message);
+ message = consumer.receive(2000);
+ }
+
+ //Check we have all the messages
+ if (received.size() != MESSAGE_SENT)
+ {
+ System.err.println("Test Failed Not all the messages received:" + received.size());
+ System.exit(1);
+ }
+
+ //individually ack the first half then close
+ for (int i = 0; i < MESSAGE_SENT / 2; i++)
+ {
+ ((org.apache.qpid.jms.Message) received.get(i)).acknowledgeThis();
+ }
+
+ // Close the Session to force a requeue on the server of the unackedMsgs
+
+ System.out.println("Killing client to force requeue on broker");
+
+ System.exit(1);
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ct/DurableSubscriberTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ct/DurableSubscriberTest.java
new file mode 100644
index 0000000000..989ac98747
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/ct/DurableSubscriberTest.java
@@ -0,0 +1,506 @@
+/* Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.qpid.test.unit.ct;
+
+import javax.jms.Connection;
+import javax.jms.Message;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+import javax.jms.Topic;
+import javax.jms.TopicConnection;
+import javax.jms.TopicConnectionFactory;
+import javax.jms.TopicPublisher;
+import javax.jms.TopicSession;
+import javax.jms.TopicSubscriber;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.AMQTopic;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+/**
+ * Crash Recovery tests for durable subscription
+ *
+ */
+public class DurableSubscriberTest extends QpidBrokerTestCase
+{
+ private final String _topicName = "durableSubscriberTopic";
+
+ /**
+ * test strategy:
+ * create and register a durable subscriber then close it
+ * create a publisher and send a persistant message followed by a non persistant message
+ * crash and restart the broker
+ * recreate the durable subscriber and check that only the first message is received
+ */
+ public void testDurSubRestoredAfterNonPersistentMessageSent() throws Exception
+ {
+ if (isBrokerStorePersistent() || !isBroker08())
+ {
+ TopicConnectionFactory factory = getConnectionFactory();
+ Topic topic = (Topic) getInitialContext().lookup(_topicName);
+ //create and register a durable subscriber then close it
+ TopicConnection durConnection = factory.createTopicConnection("guest", "guest");
+ TopicSession durSession = durConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
+ TopicSubscriber durSub1 = durSession.createDurableSubscriber(topic, "dursub");
+ durConnection.start();
+ durSub1.close();
+ durSession.close();
+ durConnection.stop();
+
+ //create a publisher and send a persistant message followed by a non persistant message
+ TopicConnection pubConnection = factory.createTopicConnection("guest", "guest");
+ TopicSession pubSession = pubConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
+ TopicPublisher publisher = pubSession.createPublisher(topic);
+ Message message = pubSession.createMessage();
+ message.setIntProperty("count", 1);
+ publisher.publish(message, javax.jms.DeliveryMode.PERSISTENT, javax.jms.Message.DEFAULT_PRIORITY,
+ javax.jms.Message.DEFAULT_TIME_TO_LIVE);
+ message.setIntProperty("count", 2);
+ publisher.publish(message, javax.jms.DeliveryMode.NON_PERSISTENT, javax.jms.Message.DEFAULT_PRIORITY,
+ javax.jms.Message.DEFAULT_TIME_TO_LIVE);
+ publisher.close();
+ pubSession.close();
+ //now stop the server
+ try
+ {
+ restartBroker();
+ }
+ catch (Exception e)
+ {
+ _logger.error("problems restarting broker: " + e);
+ throw e;
+ }
+ //now recreate the durable subscriber and check the received messages
+ factory = getConnectionFactory();
+ topic = (Topic) getInitialContext().lookup(_topicName);
+ TopicConnection durConnection2 = factory.createTopicConnection("guest", "guest");
+ TopicSession durSession2 = durConnection2.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
+ TopicSubscriber durSub2 = durSession2.createDurableSubscriber(topic, "dursub");
+ durConnection2.start();
+ Message m1 = durSub2.receive(1000);
+ if (m1 == null)
+ {
+ assertTrue("testDurSubRestoredAfterNonPersistentMessageSent test failed. no message was returned",
+ false);
+ }
+ assertTrue("testDurSubRestoredAfterNonPersistentMessageSent test failed. Wrong message was returned.",
+ m1.getIntProperty("count") == 1);
+ durSession2.unsubscribe("dursub");
+ durConnection2.close();
+ }
+ }
+
+ /**
+ * create and register a durable subscriber with a message selector and then close it
+ * crash the broker
+ * create a publisher and send 5 right messages and 5 wrong messages
+ * recreate the durable subscriber and check we receive the 5 expected messages
+ */
+ public void testDurSubRestoresMessageSelector() throws Exception
+ {
+ if (isBrokerStorePersistent() || !isBroker08())
+ {
+ TopicConnectionFactory factory = getConnectionFactory();
+ Topic topic = (Topic) getInitialContext().lookup(_topicName);
+ //create and register a durable subscriber with a message selector and then close it
+ TopicConnection durConnection = factory.createTopicConnection("guest", "guest");
+ TopicSession durSession = durConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
+ TopicSubscriber durSub1 = durSession.createDurableSubscriber(topic, "dursub", "testprop='true'", false);
+ durConnection.start();
+ durSub1.close();
+ durSession.close();
+ durConnection.stop();
+ //now stop the server
+ try
+ {
+ restartBroker();
+ }
+ catch (Exception e)
+ {
+ _logger.error("problems restarting broker: " + e);
+ throw e;
+ }
+ topic = (Topic) getInitialContext().lookup(_topicName);
+ factory = getConnectionFactory();
+ TopicConnection pubConnection = factory.createTopicConnection("guest", "guest");
+ TopicSession pubSession = pubConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
+ TopicPublisher publisher = pubSession.createPublisher(topic);
+ for (int i = 0; i < 5; i++)
+ {
+ Message message = pubSession.createMessage();
+ message.setStringProperty("testprop", "true");
+ publisher.publish(message);
+ message = pubSession.createMessage();
+ message.setStringProperty("testprop", "false");
+ publisher.publish(message);
+ }
+ publisher.close();
+ pubSession.close();
+
+ //now recreate the durable subscriber and check the received messages
+ TopicConnection durConnection2 = factory.createTopicConnection("guest", "guest");
+ TopicSession durSession2 = durConnection2.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
+ TopicSubscriber durSub2 = durSession2.createDurableSubscriber(topic, "dursub", "testprop='true'", false);
+ durConnection2.start();
+ for (int i = 0; i < 5; i++)
+ {
+ Message message = durSub2.receive(1000);
+ if (message == null)
+ {
+ assertTrue("testDurSubRestoresMessageSelector test failed. no message was returned", false);
+ }
+ else
+ {
+ assertTrue("testDurSubRestoresMessageSelector test failed. message selector not reset",
+ message.getStringProperty("testprop").equals("true"));
+ }
+ }
+ durSession2.unsubscribe("dursub");
+ durConnection2.close();
+ }
+ }
+
+ /**
+ * create and register a durable subscriber without a message selector and then unsubscribe it
+ * create and register a durable subscriber with a message selector and then close it
+ * restart the broker
+ * send matching and non matching messages
+ * recreate and register the durable subscriber with a message selector
+ * verify only the matching messages are received
+ */
+ public void testDurSubChangedToHaveSelectorThenRestart() throws Exception
+ {
+ if (! isBrokerStorePersistent())
+ {
+ _logger.warn("Test skipped due to requirement of a persistent store");
+ return;
+ }
+
+ final String SUB_NAME=getTestQueueName();
+
+ TopicConnectionFactory factory = getConnectionFactory();
+ Topic topic = (Topic) getInitialContext().lookup(_topicName);
+
+ //create and register a durable subscriber then unsubscribe it
+ TopicConnection durConnection = factory.createTopicConnection("guest", "guest");
+ TopicSession durSession = durConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
+ TopicSubscriber durSub1 = durSession.createDurableSubscriber(topic, SUB_NAME);
+ durConnection.start();
+ durSub1.close();
+ durSession.unsubscribe(SUB_NAME);
+ durSession.close();
+ durConnection.close();
+
+ //create and register a durable subscriber with a message selector and then close it
+ TopicConnection durConnection2 = factory.createTopicConnection("guest", "guest");
+ TopicSession durSession2 = durConnection2.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
+ TopicSubscriber durSub2 = durSession2.createDurableSubscriber(topic, SUB_NAME, "testprop='true'", false);
+ durConnection2.start();
+ durSub2.close();
+ durSession2.close();
+ durConnection2.close();
+
+ //now restart the server
+ try
+ {
+ restartBroker();
+ }
+ catch (Exception e)
+ {
+ _logger.error("problems restarting broker: " + e);
+ throw e;
+ }
+
+ //send messages matching and not matching the selector
+ TopicConnection pubConnection = factory.createTopicConnection("guest", "guest");
+ TopicSession pubSession = pubConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
+ TopicPublisher publisher = pubSession.createPublisher(topic);
+ for (int i = 0; i < 5; i++)
+ {
+ Message message = pubSession.createMessage();
+ message.setStringProperty("testprop", "true");
+ publisher.publish(message);
+ message = pubSession.createMessage();
+ message.setStringProperty("testprop", "false");
+ publisher.publish(message);
+ }
+ publisher.close();
+ pubSession.close();
+
+ //now recreate the durable subscriber with selector to check there are no exceptions generated
+ //and then verify the messages are received correctly
+ TopicConnection durConnection3 = (TopicConnection) factory.createConnection("guest", "guest");
+ TopicSession durSession3 = (TopicSession) durConnection3.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ TopicSubscriber durSub3 = durSession3.createDurableSubscriber(topic, SUB_NAME, "testprop='true'", false);
+ durConnection3.start();
+
+ for (int i = 0; i < 5; i++)
+ {
+ Message message = durSub3.receive(2000);
+ if (message == null)
+ {
+ fail("testDurSubChangedToHaveSelectorThenRestart test failed. Expected message " + i + " was not returned");
+ }
+ else
+ {
+ assertTrue("testDurSubChangedToHaveSelectorThenRestart test failed. Got message not matching selector",
+ message.getStringProperty("testprop").equals("true"));
+ }
+ }
+
+ durSub3.close();
+ durSession3.unsubscribe(SUB_NAME);
+ durSession3.close();
+ durConnection3.close();
+ }
+
+
+ /**
+ * create and register a durable subscriber with a message selector and then unsubscribe it
+ * create and register a durable subscriber without a message selector and then close it
+ * restart the broker
+ * send matching and non matching messages
+ * recreate and register the durable subscriber without a message selector
+ * verify ALL the sent messages are received
+ */
+ public void testDurSubChangedToNotHaveSelectorThenRestart() throws Exception
+ {
+ if (! isBrokerStorePersistent())
+ {
+ _logger.warn("Test skipped due to requirement of a persistent store");
+ return;
+ }
+
+ final String SUB_NAME=getTestQueueName();
+
+ TopicConnectionFactory factory = getConnectionFactory();
+ Topic topic = (Topic) getInitialContext().lookup(_topicName);
+
+ //create and register a durable subscriber with selector then unsubscribe it
+ TopicConnection durConnection = factory.createTopicConnection("guest", "guest");
+ TopicSession durSession = durConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
+ TopicSubscriber durSub1 = durSession.createDurableSubscriber(topic, SUB_NAME, "testprop='true'", false);
+ durConnection.start();
+ durSub1.close();
+ durSession.unsubscribe(SUB_NAME);
+ durSession.close();
+ durConnection.close();
+
+ //create and register a durable subscriber without the message selector and then close it
+ TopicConnection durConnection2 = factory.createTopicConnection("guest", "guest");
+ TopicSession durSession2 = durConnection2.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
+ TopicSubscriber durSub2 = durSession2.createDurableSubscriber(topic, SUB_NAME);
+ durConnection2.start();
+ durSub2.close();
+ durSession2.close();
+ durConnection2.close();
+
+ //now restart the server
+ try
+ {
+ restartBroker();
+ }
+ catch (Exception e)
+ {
+ _logger.error("problems restarting broker: " + e);
+ throw e;
+ }
+
+ //send messages matching and not matching the original used selector
+ TopicConnection pubConnection = factory.createTopicConnection("guest", "guest");
+ TopicSession pubSession = pubConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
+ TopicPublisher publisher = pubSession.createPublisher(topic);
+ for (int i = 1; i <= 5; i++)
+ {
+ Message message = pubSession.createMessage();
+ message.setStringProperty("testprop", "true");
+ publisher.publish(message);
+ message = pubSession.createMessage();
+ message.setStringProperty("testprop", "false");
+ publisher.publish(message);
+ }
+ publisher.close();
+ pubSession.close();
+
+ //now recreate the durable subscriber without selector to check there are no exceptions generated
+ //then verify ALL messages sent are received
+ TopicConnection durConnection3 = (TopicConnection) factory.createConnection("guest", "guest");
+ TopicSession durSession3 = (TopicSession) durConnection3.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ TopicSubscriber durSub3 = durSession3.createDurableSubscriber(topic, SUB_NAME);
+ durConnection3.start();
+
+ for (int i = 1; i <= 10; i++)
+ {
+ Message message = durSub3.receive(2000);
+ if (message == null)
+ {
+ fail("testDurSubChangedToNotHaveSelectorThenRestart test failed. Expected message " + i + " was not received");
+ }
+ }
+
+ durSub3.close();
+ durSession3.unsubscribe(SUB_NAME);
+ durSession3.close();
+ durConnection3.close();
+ }
+
+
+ public void testResubscribeWithChangedSelectorAndRestart() throws Exception
+ {
+ if (! isBrokerStorePersistent())
+ {
+ _logger.warn("Test skipped due to requirement of a persistent store");
+ return;
+ }
+
+ Connection conn = getConnection();
+ conn.start();
+ Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ AMQTopic topic = new AMQTopic((AMQConnection) conn, "testResubscribeWithChangedSelectorAndRestart");
+ MessageProducer producer = session.createProducer(topic);
+
+ // Create durable subscriber that matches A
+ TopicSubscriber subA = session.createDurableSubscriber(topic,
+ "testResubscribeWithChangedSelector",
+ "Match = True", false);
+
+ // Send 1 matching message and 1 non-matching message
+ TextMessage msg = session.createTextMessage("testResubscribeWithChangedSelectorAndRestart1");
+ msg.setBooleanProperty("Match", true);
+ producer.send(msg);
+ msg = session.createTextMessage("testResubscribeWithChangedSelectorAndRestart2");
+ msg.setBooleanProperty("Match", false);
+ producer.send(msg);
+
+ Message rMsg = subA.receive(1000);
+ assertNotNull(rMsg);
+ assertEquals("Content was wrong",
+ "testResubscribeWithChangedSelectorAndRestart1",
+ ((TextMessage) rMsg).getText());
+
+ // Queue has no messages left
+ AMQQueue subQueueTmp = new AMQQueue("amq.topic", "clientid" + ":" + "testResubscribeWithChangedSelectorAndRestart");
+ assertEquals("Msg count should be 0", 0, ((AMQSession<?, ?>) session).getQueueDepth(subQueueTmp));
+
+ rMsg = subA.receive(1000);
+ assertNull(rMsg);
+
+ // Send another 1 matching message and 1 non-matching message
+ msg = session.createTextMessage("testResubscribeWithChangedSelectorAndRestart1");
+ msg.setBooleanProperty("Match", true);
+ producer.send(msg);
+ msg = session.createTextMessage("testResubscribeWithChangedSelectorAndRestart2");
+ msg.setBooleanProperty("Match", false);
+ producer.send(msg);
+
+ // Disconnect subscriber without receiving the message to
+ //leave it on the underlying queue
+ subA.close();
+
+ // Reconnect with new selector that matches B
+ TopicSubscriber subB = session.createDurableSubscriber(topic,
+ "testResubscribeWithChangedSelectorAndRestart",
+ "Match = false", false);
+
+ //verify no messages are now present on the queue as changing selector should have issued
+ //an unsubscribe and thus deleted the previous durable backing queue for the subscription.
+ //check the dur sub's underlying queue now has msg count 0
+ AMQQueue subQueue = new AMQQueue("amq.topic", "clientid" + ":" + "testResubscribeWithChangedSelectorAndRestart");
+ assertEquals("Msg count should be 0", 0, ((AMQSession<?, ?>) session).getQueueDepth(subQueue));
+
+ // Check that new messages are received properly
+ msg = session.createTextMessage("testResubscribeWithChangedSelectorAndRestart1");
+ msg.setBooleanProperty("Match", true);
+ producer.send(msg);
+ msg = session.createTextMessage("testResubscribeWithChangedSelectorAndRestart2");
+ msg.setBooleanProperty("Match", false);
+ producer.send(msg);
+
+ rMsg = subB.receive(1000);
+ assertNotNull(rMsg);
+ assertEquals("Content was wrong",
+ "testResubscribeWithChangedSelectorAndRestart2",
+ ((TextMessage) rMsg).getText());
+
+ rMsg = subB.receive(1000);
+ assertNull(rMsg);
+
+ //check the dur sub's underlying queue now has msg count 0
+ subQueue = new AMQQueue("amq.topic", "clientid" + ":" + "testResubscribeWithChangedSelectorAndRestart");
+ assertEquals("Msg count should be 0", 0, ((AMQSession<?, ?>) session).getQueueDepth(subQueue));
+
+ //now restart the server
+ try
+ {
+ restartBroker();
+ }
+ catch (Exception e)
+ {
+ _logger.error("problems restarting broker: " + e);
+ throw e;
+ }
+
+ // Reconnect to broker
+ Connection connection = getConnectionFactory().createConnection("guest", "guest");
+ connection.start();
+ session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ topic = new AMQTopic((AMQConnection) connection, "testResubscribeWithChangedSelectorAndRestart");
+ producer = session.createProducer(topic);
+
+ //verify no messages now present on the queue after we restart the broker
+ //check the dur sub's underlying queue now has msg count 0
+ subQueue = new AMQQueue("amq.topic", "clientid" + ":" + "testResubscribeWithChangedSelectorAndRestart");
+ assertEquals("Msg count should be 0", 0, ((AMQSession<?, ?>) session).getQueueDepth(subQueue));
+
+ // Reconnect with new selector that matches B
+ TopicSubscriber subC = session.createDurableSubscriber(topic,
+ "testResubscribeWithChangedSelectorAndRestart",
+ "Match = False", false);
+
+ // Check that new messages are still sent and recieved properly
+ msg = session.createTextMessage("testResubscribeWithChangedSelectorAndRestart1");
+ msg.setBooleanProperty("Match", true);
+ producer.send(msg);
+ msg = session.createTextMessage("testResubscribeWithChangedSelectorAndRestart2");
+ msg.setBooleanProperty("Match", false);
+ producer.send(msg);
+
+ //check the dur sub's underlying queue now has msg count 1
+ subQueue = new AMQQueue("amq.topic", "clientid" + ":" + "testResubscribeWithChangedSelectorAndRestart");
+ assertEquals("Msg count should be 1", 1, ((AMQSession<?, ?>) session).getQueueDepth(subQueue));
+
+ rMsg = subC.receive(1000);
+ assertNotNull(rMsg);
+ assertEquals("Content was wrong",
+ "testResubscribeWithChangedSelectorAndRestart2",
+ ((TextMessage) rMsg).getText());
+
+ rMsg = subC.receive(1000);
+ assertNull(rMsg);
+
+ session.unsubscribe("testResubscribeWithChangedSelectorAndRestart");
+
+ subC.close();
+ session.close();
+ connection.close();
+ }
+}
+
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/message/JMSPropertiesTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/message/JMSPropertiesTest.java
new file mode 100644
index 0000000000..8caeaa55c0
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/message/JMSPropertiesTest.java
@@ -0,0 +1,204 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.message;
+
+import org.apache.qpid.AMQPInvalidClassException;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.message.NonQpidObjectMessage;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageFormatException;
+import javax.jms.MessageProducer;
+import javax.jms.ObjectMessage;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.Topic;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author Apache Software Foundation
+ */
+public class JMSPropertiesTest extends QpidBrokerTestCase
+{
+
+ private static final Logger _logger = LoggerFactory.getLogger(JMSPropertiesTest.class);
+
+ public String _connectionString = "vm://:1";
+
+ public static final String JMS_CORR_ID = "QPIDID_01";
+ public static final int JMS_DELIV_MODE = 1;
+ public static final String JMS_TYPE = "test.jms.type";
+ protected static final String NULL_OBJECT_PROPERTY = "NullObject";
+ protected static final String INVALID_OBJECT_PROPERTY = "InvalidObject";
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ }
+
+ protected void tearDown() throws Exception
+ {
+ super.tearDown();
+ }
+
+ public void testJMSProperties() throws Exception
+ {
+ AMQConnection con = (AMQConnection) getConnection("guest", "guest");
+ AMQSession consumerSession = (AMQSession) con.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+ Queue queue =
+ new AMQQueue(con.getDefaultQueueExchangeName(), new AMQShortString("someQ"), new AMQShortString("someQ"), false,
+ true);
+ MessageConsumer consumer = consumerSession.createConsumer(queue);
+
+ AMQConnection con2 = (AMQConnection) getConnection("guest", "guest");
+ Session producerSession = con2.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+ MessageProducer producer = producerSession.createProducer(queue);
+ Destination JMS_REPLY_TO = new AMQQueue(con2, "my.replyto");
+ // create a test message to send
+ ObjectMessage sentMsg = new NonQpidObjectMessage(producerSession);
+ sentMsg.setJMSCorrelationID(JMS_CORR_ID);
+ sentMsg.setJMSDeliveryMode(JMS_DELIV_MODE);
+ sentMsg.setJMSType(JMS_TYPE);
+ sentMsg.setJMSReplyTo(JMS_REPLY_TO);
+
+ String JMSXGroupID_VALUE = "group";
+ sentMsg.setStringProperty("JMSXGroupID", JMSXGroupID_VALUE);
+
+ int JMSXGroupSeq_VALUE = 1;
+ sentMsg.setIntProperty("JMSXGroupSeq", JMSXGroupSeq_VALUE);
+
+ try
+ {
+ sentMsg.setObjectProperty(NULL_OBJECT_PROPERTY, null);
+ fail("Null Object Property value set");
+ }
+ catch (MessageFormatException mfe)
+ {
+ // Check the error message
+ assertEquals("Incorrect error message", AMQPInvalidClassException.INVALID_OBJECT_MSG + "null", mfe.getMessage());
+ }
+
+ try
+ {
+ sentMsg.setObjectProperty(INVALID_OBJECT_PROPERTY, new Exception());
+ fail("Non primitive Object Property value set");
+ }
+ catch (MessageFormatException mfe)
+ {
+ // Check the error message
+ assertEquals("Incorrect error message: " + mfe.getMessage(), AMQPInvalidClassException.INVALID_OBJECT_MSG + Exception.class, mfe.getMessage());
+ }
+
+ // send it
+ producer.send(sentMsg);
+
+ con2.close();
+
+ con.start();
+
+ // get message and check JMS properties
+ ObjectMessage rm = (ObjectMessage) consumer.receive(2000);
+ assertNotNull(rm);
+
+ assertEquals("JMS Correlation ID mismatch", sentMsg.getJMSCorrelationID(), rm.getJMSCorrelationID());
+ // TODO: Commented out as always overwritten by send delivery mode value - prob should not set in conversion
+ // assertEquals("JMS Delivery Mode mismatch",sentMsg.getJMSDeliveryMode(),rm.getJMSDeliveryMode());
+ assertEquals("JMS Type mismatch", sentMsg.getJMSType(), rm.getJMSType());
+ assertEquals("JMS Reply To mismatch", sentMsg.getJMSReplyTo(), rm.getJMSReplyTo());
+ assertTrue("JMSMessageID Does not start ID:", rm.getJMSMessageID().startsWith("ID:"));
+ assertEquals("JMS Default priority should be 4",Message.DEFAULT_PRIORITY,rm.getJMSPriority());
+
+ //Validate that the JMSX values are correct
+ assertEquals("JMSXGroupID is not as expected:", JMSXGroupID_VALUE, rm.getStringProperty("JMSXGroupID"));
+ assertEquals("JMSXGroupSeq is not as expected:", JMSXGroupSeq_VALUE, rm.getIntProperty("JMSXGroupSeq"));
+
+ boolean JMSXGroupID_Available = false;
+ boolean JMSXGroupSeq_Available = false;
+ Enumeration props = con.getMetaData().getJMSXPropertyNames();
+ while (props.hasMoreElements())
+ {
+ String name = (String) props.nextElement();
+ if (name.equals("JMSXGroupID"))
+ {
+ JMSXGroupID_Available = true;
+ }
+ if (name.equals("JMSXGroupSeq"))
+ {
+ JMSXGroupSeq_Available = true;
+ }
+ }
+
+ assertTrue("JMSXGroupID not available.",JMSXGroupID_Available);
+ assertTrue("JMSXGroupSeq not available.",JMSXGroupSeq_Available);
+
+ // Check that the NULL_OBJECT_PROPERTY was not set or transmitted.
+ assertFalse(NULL_OBJECT_PROPERTY + " was not set.", rm.propertyExists(NULL_OBJECT_PROPERTY));
+
+ con.close();
+ }
+
+ /**
+ * Test Goal : test if the message properties can be retrieved properly with out an error
+ * and also test if unsupported properties are filtered out. See QPID-2930.
+ */
+ public void testGetPropertyNames() throws Exception
+ {
+ Connection con = getConnection("guest", "guest");
+ Session ssn = (AMQSession) con.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+ con.start();
+
+ Topic topic = ssn.createTopic("test");
+ MessageConsumer consumer = ssn.createConsumer(topic);
+ MessageProducer prod = ssn.createProducer(topic);
+ Message m = ssn.createMessage();
+ m.setObjectProperty("x-amqp-0-10.routing-key", "routing-key".getBytes());
+ m.setObjectProperty("routing-key", "routing-key");
+ prod.send(m);
+
+ Message msg = consumer.receive(1000);
+ assertNotNull(msg);
+
+ Enumeration<String> enu = msg.getPropertyNames();
+ Map<String,String> map = new HashMap<String,String>();
+ while (enu.hasMoreElements())
+ {
+ String name = enu.nextElement();
+ String value = msg.getStringProperty(name);
+ map.put(name, value);
+ }
+
+ assertFalse("Property 'x-amqp-0-10.routing-key' should have been filtered out",map.containsKey("x-amqp-0-10.routing-key"));
+ assertTrue("Property routing-key should be present",map.containsKey("routing-key"));
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/message/StreamMessageTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/message/StreamMessageTest.java
new file mode 100644
index 0000000000..0f799073b4
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/message/StreamMessageTest.java
@@ -0,0 +1,162 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.message;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQHeadersExchange;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.configuration.ClientProperties;
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.framing.FieldTable;
+import org.apache.qpid.url.AMQBindingURL;
+import org.apache.qpid.url.BindingURL;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageEOFException;
+import javax.jms.MessageListener;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import javax.jms.StreamMessage;
+
+/**
+ * @author Apache Software Foundation
+ */
+public class StreamMessageTest extends QpidBrokerTestCase
+{
+
+ private static final Logger _logger = LoggerFactory.getLogger(StreamMessageTest.class);
+
+ public String _connectionString = "vm://:1";
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ }
+
+ protected void tearDown() throws Exception
+ {
+ super.tearDown();
+ }
+
+ public void testStreamMessageEOF() throws Exception
+ {
+ Connection con = (AMQConnection) getConnection("guest", "guest");
+ AMQSession consumerSession = (AMQSession) con.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+
+ AMQHeadersExchange queue =
+ new AMQHeadersExchange(new AMQBindingURL(
+ ExchangeDefaults.HEADERS_EXCHANGE_CLASS + "://" + ExchangeDefaults.HEADERS_EXCHANGE_NAME
+ + "/test/queue1?" + BindingURL.OPTION_ROUTING_KEY + "='F0000=1'"));
+ FieldTable ft = new FieldTable();
+ ft.setString("x-match", "any");
+ ft.setString("F1000", "1");
+ MessageConsumer consumer =
+ consumerSession.createConsumer(queue, Integer.parseInt(ClientProperties.MAX_PREFETCH_DEFAULT), Integer.parseInt(ClientProperties.MAX_PREFETCH_DEFAULT), false, false, (String) null, ft);
+
+ // force synch to ensure the consumer has resulted in a bound queue
+ // ((AMQSession) consumerSession).declareExchangeSynch(ExchangeDefaults.HEADERS_EXCHANGE_NAME, ExchangeDefaults.HEADERS_EXCHANGE_CLASS);
+ // This is the default now
+
+ Connection con2 = (AMQConnection) getConnection("guest", "guest");
+
+ AMQSession producerSession = (AMQSession) con2.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+
+ // Need to start the "producer" connection in order to receive bounced messages
+ _logger.info("Starting producer connection");
+ con2.start();
+
+ MessageProducer mandatoryProducer = producerSession.createProducer(queue);
+
+ // Third test - should be routed
+ _logger.info("Sending isBound message");
+ StreamMessage msg = producerSession.createStreamMessage();
+
+ msg.setStringProperty("F1000", "1");
+
+ msg.writeByte((byte) 42);
+
+ mandatoryProducer.send(msg);
+
+ _logger.info("Starting consumer connection");
+ con.start();
+
+ StreamMessage msg2 = (StreamMessage) consumer.receive(2000);
+ assertNotNull(msg2);
+
+ msg2.readByte();
+ try
+ {
+ msg2.readByte();
+ }
+ catch (Exception e)
+ {
+ assertTrue("Expected MessageEOFException: " + e, e instanceof MessageEOFException);
+ }
+ con.close();
+ con2.close();
+ }
+
+ public void testModifyReceivedMessageExpandsBuffer() throws Exception
+ {
+ AMQConnection con = (AMQConnection) getConnection("guest", "guest");
+ AMQSession consumerSession = (AMQSession) con.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+ AMQQueue queue = new AMQQueue(con.getDefaultQueueExchangeName(), new AMQShortString("testQ"));
+ MessageConsumer consumer = consumerSession.createConsumer(queue);
+ consumer.setMessageListener(new MessageListener()
+ {
+
+ public void onMessage(Message message)
+ {
+ StreamMessage sm = (StreamMessage) message;
+ try
+ {
+ sm.clearBody();
+ sm.writeString("dfgjshfslfjshflsjfdlsjfhdsljkfhdsljkfhsd");
+ }
+ catch (JMSException e)
+ {
+ _logger.error("Error when writing large string to received msg: " + e, e);
+ fail("Error when writing large string to received msg" + e);
+ }
+ }
+ });
+
+ Connection con2 = (AMQConnection) getConnection("guest", "guest");
+ AMQSession producerSession = (AMQSession) con2.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+ MessageProducer mandatoryProducer = producerSession.createProducer(queue);
+ con.start();
+ StreamMessage sm = producerSession.createStreamMessage();
+ sm.writeInt(42);
+ mandatoryProducer.send(sm);
+ Thread.sleep(2000);
+ con.close();
+ con2.close();
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/message/UTF8En b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/message/UTF8En
new file mode 100644
index 0000000000..c9734b1988
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/message/UTF8En
@@ -0,0 +1,4 @@
+exhangeName
+queueName
+routingkey
+data \ No newline at end of file
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/message/UTF8Jp b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/message/UTF8Jp
new file mode 100644
index 0000000000..ae10752dab
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/message/UTF8Jp
@@ -0,0 +1,4 @@
+設定がそのように構成されていなければな
+的某些更新没有出现在这个 README 中。你可以访问下面的
+的发行版本包括多张光盘,其中包括安装光盘和源码光盘
+目のインストール CD は、ほとんどの最近のシス \ No newline at end of file
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/message/UTF8Test.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/message/UTF8Test.java
new file mode 100644
index 0000000000..fe929b4965
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/message/UTF8Test.java
@@ -0,0 +1,111 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.message;
+
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.transport.Connection;
+import org.apache.qpid.transport.Session;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.naming.InitialContext;
+import javax.jms.*;
+import java.util.Properties;
+import java.io.*;
+
+
+/**
+ * This test makes sure that utf8 characters can be used for
+ * specifying exchange, queue name and routing key.
+ *
+ * those tests are related to qpid-1384
+ */
+public class UTF8Test extends QpidBrokerTestCase
+{
+ private static final Logger _logger = LoggerFactory.getLogger(UTF8Test.class);
+
+ public void testPlainEn() throws Exception
+ {
+ invoke("UTF8En");
+ }
+
+
+ public void testUTF8Jp() throws Exception
+ {
+ invoke("UTF8Jp");
+ }
+
+ private void invoke(String name) throws Exception
+ {
+ String path = System.getProperties().getProperty("QPID_HOME");
+ path = path + "/../systests/src/main/java/org/apache/qpid/test/unit/message/" + name;
+ BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(path), "UTF8"));
+ runTest(in.readLine(), in.readLine(), in.readLine(), in.readLine());
+ in.close();
+ }
+
+ private void runTest(String exchangeName, String queueName, String routingKey, String data) throws Exception
+ {
+ _logger.info("Running test for exchange: " + exchangeName
+ + " queue Name: " + queueName
+ + " routing key: " + routingKey);
+ declareQueue(exchangeName, routingKey, queueName);
+
+ javax.jms.Connection con = getConnection();
+ javax.jms.Session sess = con.createSession(false, javax.jms.Session.AUTO_ACKNOWLEDGE);
+ Destination dest = getDestination(exchangeName, routingKey, queueName);
+ // Send data
+ MessageProducer msgProd = sess.createProducer(dest);
+ TextMessage message = sess.createTextMessage(data);
+ msgProd.send(message);
+ // consume data
+ MessageConsumer msgCons = sess.createConsumer(dest);
+ con.start();
+ TextMessage m = (TextMessage) msgCons.receive(RECEIVE_TIMEOUT);
+ assertNotNull(m);
+ assertEquals(m.getText(), data);
+ }
+
+ private void declareQueue(String exch, String routkey, String qname) throws Exception
+ {
+ Connection conn = new Connection();
+ conn.connect("localhost", QpidBrokerTestCase.DEFAULT_PORT, "test", "guest", "guest",false);
+ Session sess = conn.createSession(0);
+ sess.exchangeDeclare(exch, "direct", null, null);
+ sess.queueDeclare(qname, null, null);
+ sess.exchangeBind(qname, exch, routkey, null);
+ sess.sync();
+ conn.close();
+ }
+
+ private Destination getDestination(String exch, String routkey, String qname) throws Exception
+ {
+ Properties props = new Properties();
+ props.setProperty("destination.directUTF8Queue",
+ "direct://" + exch + "//" + qname + "?autodelete='false'&durable='false'"
+ + "&routingkey='" + routkey + "'");
+
+ // Get our connection context
+ InitialContext ctx = new InitialContext(props);
+ return (Destination) ctx.lookup("directUTF8Queue");
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/publish/DirtyTransactedPublishTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/publish/DirtyTransactedPublishTest.java
new file mode 100644
index 0000000000..3ec7937812
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/publish/DirtyTransactedPublishTest.java
@@ -0,0 +1,403 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.publish;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQDestination;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.jms.ConnectionListener;
+import org.apache.qpid.test.utils.FailoverBaseCase;
+
+import javax.jms.Connection;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageListener;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.TransactionRolledBackException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * QPID-1816 : Whilst testing Acknoledgement after failover this completes testing
+ * of the client after failover. When we have a dirty session we should receive
+ * an error if we attempt to publish. This test ensures that both in the synchronous
+ * and asynchronous message delivery paths we receive the expected exceptions at
+ * the expected time.
+ */
+public class DirtyTransactedPublishTest extends FailoverBaseCase implements ConnectionListener
+{
+ protected CountDownLatch _failoverCompleted = new CountDownLatch(1);
+
+ protected int NUM_MESSAGES;
+ protected Connection _connection;
+ protected Queue _queue;
+ protected Session _consumerSession;
+ protected MessageConsumer _consumer;
+ protected MessageProducer _producer;
+
+ private static final String MSG = "MSG";
+ private static final String SEND_FROM_ON_MESSAGE_TEXT = "sendFromOnMessage";
+ protected CountDownLatch _receviedAll;
+ protected AtomicReference<Exception> _causeOfFailure = new AtomicReference<Exception>(null);
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ NUM_MESSAGES = 10;
+
+ _queue = getTestQueue();
+
+ //Create Producer put some messages on the queue
+ _connection = getConnection();
+ }
+
+ /**
+ * Initialise the test variables
+ * @param transacted is this a transacted test
+ * @param mode if not trasacted then what ack mode to use
+ * @throws Exception if there is a setup issue.
+ */
+ protected void init(boolean transacted, int mode) throws Exception
+ {
+ _consumerSession = _connection.createSession(transacted, mode);
+ _consumer = _consumerSession.createConsumer(_queue);
+ _producer = _consumerSession.createProducer(_queue);
+
+ // These should all end up being prefetched by session
+ sendMessage(_consumerSession, _queue, 1);
+
+ assertEquals("Wrong number of messages on queue", 1,
+ ((AMQSession) _consumerSession).getQueueDepth((AMQDestination) _queue));
+ }
+
+ /**
+ * If a transacted session has failed over whilst it has uncommitted sent
+ * data then we need to throw a TransactedRolledbackException on commit()
+ *
+ * The alternative would be to maintain a replay buffer so that the message
+ * could be resent. This is not currently implemented
+ *
+ * @throws Exception if something goes wrong.
+ */
+ public void testDirtySendingSynchronousTransacted() throws Exception
+ {
+ Session producerSession = _connection.createSession(true, Session.SESSION_TRANSACTED);
+
+ // Ensure we get failover notifications
+ ((AMQConnection) _connection).setConnectionListener(this);
+
+ MessageProducer producer = producerSession.createProducer(_queue);
+
+ // Create and send message 0
+ Message msg = producerSession.createMessage();
+ msg.setIntProperty(INDEX, 0);
+ producer.send(msg);
+
+ // DON'T commit message .. fail connection
+
+ failBroker(getFailingPort());
+
+ // Ensure destination exists for sending
+ producerSession.createConsumer(_queue).close();
+
+ // Send the next message
+ msg.setIntProperty(INDEX, 1);
+ try
+ {
+ producer.send(msg);
+ fail("Should fail with Qpid as we provide early warning of the dirty session via a JMSException.");
+ }
+ catch (JMSException jmse)
+ {
+ assertEquals("Early warning of dirty session not correct",
+ "Failover has occurred and session is dirty so unable to send.", jmse.getMessage());
+ }
+
+ // Ignore that the session is dirty and attempt to commit to validate the
+ // exception is thrown. AND that the above failure notification did NOT
+ // clean up the session.
+
+ try
+ {
+ producerSession.commit();
+ fail("Session is dirty we should get an TransactionRolledBackException");
+ }
+ catch (TransactionRolledBackException trbe)
+ {
+ // Normal path.
+ }
+
+ // Resending of messages should now work ok as the commit was forcilbly rolledback
+ msg.setIntProperty(INDEX, 0);
+ producer.send(msg);
+ msg.setIntProperty(INDEX, 1);
+ producer.send(msg);
+
+ producerSession.commit();
+
+ assertEquals("Wrong number of messages on queue", 2,
+ ((AMQSession) producerSession).getQueueDepth((AMQDestination) _queue));
+ }
+
+ /**
+ * If a transacted session has failed over whilst it has uncommitted sent
+ * data then we need to throw a TransactedRolledbackException on commit()
+ *
+ * The alternative would be to maintain a replay buffer so that the message
+ * could be resent. This is not currently implemented
+ *
+ * @throws Exception if something goes wrong.
+ */
+ public void testDirtySendingOnMessageTransacted() throws Exception
+ {
+ NUM_MESSAGES = 1;
+ _receviedAll = new CountDownLatch(NUM_MESSAGES);
+ ((AMQConnection) _connection).setConnectionListener(this);
+
+ init(true, Session.SESSION_TRANSACTED);
+
+ _consumer.setMessageListener(new MessageListener()
+ {
+
+ public void onMessage(Message message)
+ {
+ try
+ {
+ // Create and send message 0
+ Message msg = _consumerSession.createMessage();
+ msg.setIntProperty(INDEX, 0);
+ _producer.send(msg);
+
+ // DON'T commit message .. fail connection
+
+ failBroker(getFailingPort());
+
+ // rep
+ repopulateBroker();
+
+ // Destination will exist as this failBroker will populate
+ // the queue with 1 message
+
+ // Send the next message
+ msg.setIntProperty(INDEX, 1);
+ try
+ {
+ _producer.send(msg);
+ fail("Should fail with Qpid as we provide early warning of the dirty session via a JMSException.");
+ }
+ catch (JMSException jmse)
+ {
+ assertEquals("Early warning of dirty session not correct",
+ "Failover has occurred and session is dirty so unable to send.", jmse.getMessage());
+ }
+
+ // Ignore that the session is dirty and attempt to commit to validate the
+ // exception is thrown. AND that the above failure notification did NOT
+ // clean up the session.
+
+ try
+ {
+ _consumerSession.commit();
+ fail("Session is dirty we should get an TransactionRolledBackException");
+ }
+ catch (TransactionRolledBackException trbe)
+ {
+ // Normal path.
+ }
+
+ // Resend messages
+ msg.setIntProperty(INDEX, 0);
+ msg.setStringProperty(MSG, SEND_FROM_ON_MESSAGE_TEXT);
+ _producer.send(msg);
+ msg.setIntProperty(INDEX, 1);
+ msg.setStringProperty(MSG, SEND_FROM_ON_MESSAGE_TEXT);
+ _producer.send(msg);
+
+ _consumerSession.commit();
+
+ // Stop this consumer .. can't do _consumer.stop == DEADLOCK
+ // this doesn't seem to stop dispatcher running
+ _connection.stop();
+
+ // Signal that the onMessage send part of test is complete
+ // main thread can validate that messages are correct
+ _receviedAll.countDown();
+
+ }
+ catch (Exception e)
+ {
+ fail(e);
+ }
+
+ }
+
+ });
+
+ _connection.start();
+
+ if (!_receviedAll.await(10000L, TimeUnit.MILLISECONDS))
+ {
+ // Check to see if we ended due to an exception in the onMessage handler
+ Exception cause = _causeOfFailure.get();
+ if (cause != null)
+ {
+ cause.printStackTrace();
+ fail(cause.getMessage());
+ }
+ else
+ {
+ fail("All messages not received:" + _receviedAll.getCount() + "/" + NUM_MESSAGES);
+ }
+ }
+
+ // Check to see if we ended due to an exception in the onMessage handler
+ Exception cause = _causeOfFailure.get();
+ if (cause != null)
+ {
+ cause.printStackTrace();
+ fail(cause.getMessage());
+ }
+
+ _consumer.close();
+ _consumerSession.close();
+
+ _consumerSession = _connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ _connection.start();
+
+ // Validate that we could send the messages as expected.
+ assertEquals("Wrong number of messages on queue", 3,
+ ((AMQSession) _consumerSession).getQueueDepth((AMQDestination) _queue));
+
+ MessageConsumer consumer = _consumerSession.createConsumer(_queue);
+
+ //Validate the message sent to setup the failed over broker.
+ Message message = consumer.receive(1000);
+ assertNotNull("Message " + 0 + " not received.", message);
+ assertEquals("Incorrect message received", 0, message.getIntProperty(INDEX));
+
+ // Validate the two messages sent from within the onMessage
+ for (int index = 0; index <= 1; index++)
+ {
+ message = consumer.receive(1000);
+ assertNotNull("Message " + index + " not received.", message);
+ assertEquals("Incorrect message received", index, message.getIntProperty(INDEX));
+ assertEquals("Incorrect message text for message:" + index, SEND_FROM_ON_MESSAGE_TEXT, message.getStringProperty(MSG));
+ }
+
+ assertNull("Extra message received.", consumer.receiveNoWait());
+
+ _consumerSession.close();
+
+ assertEquals("Wrong number of messages on queue", 0,
+ ((AMQSession) getConnection().createSession(false, Session.AUTO_ACKNOWLEDGE)).getQueueDepth((AMQDestination) _queue));
+ }
+
+ private void repopulateBroker() throws Exception
+ {
+ // Repopulate this new broker so we can test what happends after failover
+
+ //Get the connection to the first (main port) broker.
+ Connection connection = getConnection();
+ // Use a transaction to send messages so we can be sure they arrive.
+ Session session = connection.createSession(true, Session.SESSION_TRANSACTED);
+ // ensure destination is created.
+ session.createConsumer(_queue).close();
+
+ sendMessage(session, _queue, NUM_MESSAGES);
+
+ assertEquals("Wrong number of messages on queue", NUM_MESSAGES,
+ ((AMQSession) session).getQueueDepth((AMQDestination) _queue));
+
+ connection.close();
+ }
+
+ // AMQConnectionListener Interface.. used so we can validate that we
+ // actually failed over.
+
+ public void bytesSent(long count)
+ {
+ }
+
+ public void bytesReceived(long count)
+ {
+ }
+
+ public boolean preFailover(boolean redirect)
+ {
+ //Allow failover
+ return true;
+ }
+
+ public boolean preResubscribe()
+ {
+ //Allow failover
+ return true;
+ }
+
+ public void failoverComplete()
+ {
+ _failoverCompleted.countDown();
+ }
+
+ /**
+ * Override so we can block until failover has completd
+ *
+ * @param port int the port of the broker to fail.
+ */
+ @Override
+ public void failBroker(int port)
+ {
+ super.failBroker(port);
+
+ try
+ {
+ if (!_failoverCompleted.await(DEFAULT_FAILOVER_TIME, TimeUnit.MILLISECONDS))
+ {
+ fail("Failover did not occur in specified time:" + DEFAULT_FAILOVER_TIME);
+ }
+ }
+ catch (InterruptedException e)
+ {
+ fail("Failover was interuppted");
+ }
+ }
+
+ /**
+ * Pass the given exception back to the waiting thread to fail the test run.
+ *
+ * @param e The exception that is causing the test to fail.
+ */
+ protected void fail(Exception e)
+ {
+ _causeOfFailure.set(e);
+ // End the test.
+ while (_receviedAll.getCount() != 0)
+ {
+ _receviedAll.countDown();
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java
new file mode 100644
index 0000000000..d799b141c0
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/DurableSubscriptionTest.java
@@ -0,0 +1,844 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.topic;
+
+import java.io.IOException;
+import java.util.Set;
+
+import javax.jms.Connection;
+import javax.jms.InvalidDestinationException;
+import javax.jms.InvalidSelectorException;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+import javax.jms.Topic;
+import javax.jms.TopicSubscriber;
+import javax.management.MBeanServerConnection;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnector;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.AMQTopic;
+import org.apache.qpid.management.common.JMXConnnectionFactory;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * @todo Code to check that a consumer gets only one particular method could be factored into a re-usable method (as
+ * a static on a base test helper class, e.g. TestUtils.
+ *
+ * @todo Code to create test end-points using session per connection, or all sessions on one connection, to be factored
+ * out to make creating this test variation simpler. Want to make this variation available through LocalCircuit,
+ * driven by the test model.
+ */
+public class DurableSubscriptionTest extends QpidBrokerTestCase
+{
+ private static final Logger _logger = LoggerFactory.getLogger(DurableSubscriptionTest.class);
+
+ /** Timeout for receive() if we are expecting a message */
+ private static final long POSITIVE_RECEIVE_TIMEOUT = 2000;
+
+ /** Timeout for receive() if we are not expecting a message */
+ private static final long NEGATIVE_RECEIVE_TIMEOUT = 1000;
+
+ private JMXConnector _jmxc;
+ private MBeanServerConnection _mbsc;
+ private static final String USER = "admin";
+ private static final String PASSWORD = "admin";
+ private boolean _jmxConnected;
+
+ public void setUp() throws Exception
+ {
+ setConfigurationProperty("management.enabled", "true");
+ _jmxConnected=false;
+ super.setUp();
+ }
+
+ public void tearDown() throws Exception
+ {
+ if(_jmxConnected)
+ {
+ try
+ {
+ _jmxc.close();
+ }
+ catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ super.tearDown();
+ }
+
+ public void testUnsubscribe() throws Exception
+ {
+ AMQConnection con = (AMQConnection) getConnection("guest", "guest");
+ AMQTopic topic = new AMQTopic(con, "MyDurableSubscriptionTestTopic");
+ _logger.info("Create Session 1");
+ Session session1 = con.createSession(false, AMQSession.NO_ACKNOWLEDGE);
+ _logger.info("Create Consumer on Session 1");
+ MessageConsumer consumer1 = session1.createConsumer(topic);
+ _logger.info("Create Producer on Session 1");
+ MessageProducer producer = session1.createProducer(topic);
+
+ _logger.info("Create Session 2");
+ Session session2 = con.createSession(false, AMQSession.NO_ACKNOWLEDGE);
+ _logger.info("Create Durable Subscriber on Session 2");
+ TopicSubscriber consumer2 = session2.createDurableSubscriber(topic, "MySubscription");
+
+ _logger.info("Starting connection");
+ con.start();
+
+ _logger.info("Producer sending message A");
+ producer.send(session1.createTextMessage("A"));
+
+ ((AMQSession<?, ?>) session1).sync();
+
+ //check the dur sub's underlying queue now has msg count 1
+ AMQQueue subQueue = new AMQQueue("amq.topic", "clientid" + ":" + "MySubscription");
+ assertEquals("Msg count should be 1", 1, ((AMQSession<?, ?>) session1).getQueueDepth(subQueue));
+
+ Message msg;
+ _logger.info("Receive message on consumer 1:expecting A");
+ msg = consumer1.receive(POSITIVE_RECEIVE_TIMEOUT);
+ assertNotNull("Message should have been received",msg);
+ assertEquals("A", ((TextMessage) msg).getText());
+ _logger.info("Receive message on consumer 1 :expecting null");
+ msg = consumer1.receive(NEGATIVE_RECEIVE_TIMEOUT);
+ assertEquals(null, msg);
+
+ _logger.info("Receive message on consumer 2:expecting A");
+ msg = consumer2.receive(POSITIVE_RECEIVE_TIMEOUT);
+ assertNotNull("Message should have been received",msg);
+ assertEquals("A", ((TextMessage) msg).getText());
+ msg = consumer2.receive(NEGATIVE_RECEIVE_TIMEOUT);
+ _logger.info("Receive message on consumer 1 :expecting null");
+ assertEquals(null, msg);
+
+ ((AMQSession<?, ?>) session2).sync();
+
+ //check the dur sub's underlying queue now has msg count 0
+ assertEquals("Msg count should be 0", 0, ((AMQSession<?, ?>) session2).getQueueDepth(subQueue));
+
+ consumer2.close();
+ _logger.info("Unsubscribe session2/consumer2");
+ session2.unsubscribe("MySubscription");
+
+ ((AMQSession<?, ?>) session2).sync();
+
+ if(isJavaBroker() && isExternalBroker())
+ {
+ //Verify that the queue was deleted by querying for its JMX MBean
+ _jmxc = JMXConnnectionFactory.getJMXConnection(5000, "127.0.0.1",
+ getManagementPort(getPort()), USER, PASSWORD);
+
+ _jmxConnected = true;
+ _mbsc = _jmxc.getMBeanServerConnection();
+
+ //must replace the occurrence of ':' in queue name with '-'
+ String queueObjectNameText = "clientid" + "-" + "MySubscription";
+
+ ObjectName objName = new ObjectName("org.apache.qpid:type=VirtualHost.Queue,name="
+ + queueObjectNameText + ",*");
+
+ Set<ObjectName> objectInstances = _mbsc.queryNames(objName, null);
+
+ if(objectInstances.size() != 0)
+ {
+ fail("Queue MBean was found. Expected queue to have been deleted");
+ }
+ else
+ {
+ _logger.info("Underlying dueue for the durable subscription was confirmed deleted.");
+ }
+ }
+
+ //verify unsubscribing the durable subscriber did not affect the non-durable one
+ _logger.info("Producer sending message B");
+ producer.send(session1.createTextMessage("B"));
+
+ _logger.info("Receive message on consumer 1 :expecting B");
+ msg = consumer1.receive(POSITIVE_RECEIVE_TIMEOUT);
+ assertNotNull("Message should have been received",msg);
+ assertEquals("B", ((TextMessage) msg).getText());
+ _logger.info("Receive message on consumer 1 :expecting null");
+ msg = consumer1.receive(NEGATIVE_RECEIVE_TIMEOUT);
+ assertEquals(null, msg);
+
+ _logger.info("Close connection");
+ con.close();
+ }
+
+ public void testDurabilityNOACK() throws Exception
+ {
+ durabilityImpl(AMQSession.NO_ACKNOWLEDGE, false);
+ }
+
+ public void testDurabilityAUTOACK() throws Exception
+ {
+ durabilityImpl(Session.AUTO_ACKNOWLEDGE, false);
+ }
+
+ public void testDurabilityAUTOACKwithRestartIfPersistent() throws Exception
+ {
+ if(!isBrokerStorePersistent())
+ {
+ System.out.println("The broker store is not persistent, skipping this test.");
+ return;
+ }
+
+ durabilityImpl(Session.AUTO_ACKNOWLEDGE, true);
+ }
+
+ public void testDurabilityNOACKSessionPerConnection() throws Exception
+ {
+ durabilityImplSessionPerConnection(AMQSession.NO_ACKNOWLEDGE);
+ }
+
+ public void testDurabilityAUTOACKSessionPerConnection() throws Exception
+ {
+ durabilityImplSessionPerConnection(Session.AUTO_ACKNOWLEDGE);
+ }
+
+ private void durabilityImpl(int ackMode, boolean restartBroker) throws Exception
+ {
+ AMQConnection con = (AMQConnection) getConnection("guest", "guest");
+ AMQTopic topic = new AMQTopic(con, "MyTopic");
+ Session session1 = con.createSession(false, ackMode);
+ MessageConsumer consumer1 = session1.createConsumer(topic);
+
+ Session sessionProd = con.createSession(false, ackMode);
+ MessageProducer producer = sessionProd.createProducer(topic);
+
+ Session session2 = con.createSession(false, ackMode);
+ TopicSubscriber consumer2 = session2.createDurableSubscriber(topic, "MySubscription");
+
+ con.start();
+
+ //send message A and check both consumers receive
+ producer.send(session1.createTextMessage("A"));
+
+ Message msg;
+ _logger.info("Receive message on consumer 1 :expecting A");
+ msg = consumer1.receive(POSITIVE_RECEIVE_TIMEOUT);
+ assertNotNull("Message should have been received",msg);
+ assertEquals("A", ((TextMessage) msg).getText());
+ msg = consumer1.receive(NEGATIVE_RECEIVE_TIMEOUT);
+ assertEquals(null, msg);
+
+ _logger.info("Receive message on consumer 2 :expecting A");
+ msg = consumer2.receive(POSITIVE_RECEIVE_TIMEOUT);
+ assertNotNull("Message should have been received",msg);
+ assertEquals("A", ((TextMessage) msg).getText());
+ msg = consumer2.receive(NEGATIVE_RECEIVE_TIMEOUT);
+ assertEquals(null, msg);
+
+ //send message B, receive with consumer 1, and disconnect consumer 2 to leave the message behind (if not NO_ACK)
+ producer.send(session1.createTextMessage("B"));
+
+ _logger.info("Receive message on consumer 1 :expecting B");
+ msg = consumer1.receive(500);
+ assertNotNull("Consumer 1 should get message 'B'.", msg);
+ assertEquals("Incorrect Message received on consumer1.", "B", ((TextMessage) msg).getText());
+ _logger.info("Receive message on consumer 1 :expecting null");
+ msg = consumer1.receive(500);
+ assertNull("There should be no more messages for consumption on consumer1.", msg);
+
+ consumer2.close();
+ session2.close();
+
+ //Send message C, then connect consumer 3 to durable subscription and get
+ //message B if not using NO_ACK, then receive C with consumer 1 and 3
+ producer.send(session1.createTextMessage("C"));
+
+ Session session3 = con.createSession(false, ackMode);
+ MessageConsumer consumer3 = session3.createDurableSubscriber(topic, "MySubscription");
+
+ if(ackMode == AMQSession.NO_ACKNOWLEDGE)
+ {
+ //Do nothing if NO_ACK was used, as prefetch means the message was dropped
+ //when we didn't call receive() to get it before closing consumer 2
+ }
+ else
+ {
+ _logger.info("Receive message on consumer 3 :expecting B");
+ msg = consumer3.receive(500);
+ assertNotNull("Consumer 3 should get message 'B'.", msg);
+ assertEquals("Incorrect Message received on consumer3.", "B", ((TextMessage) msg).getText());
+ }
+
+ _logger.info("Receive message on consumer 1 :expecting C");
+ msg = consumer1.receive(500);
+ assertNotNull("Consumer 1 should get message 'C'.", msg);
+ assertEquals("Incorrect Message received on consumer1.", "C", ((TextMessage) msg).getText());
+ _logger.info("Receive message on consumer 1 :expecting null");
+ msg = consumer1.receive(500);
+ assertNull("There should be no more messages for consumption on consumer1.", msg);
+
+ _logger.info("Receive message on consumer 3 :expecting C");
+ msg = consumer3.receive(500);
+ assertNotNull("Consumer 3 should get message 'C'.", msg);
+ assertEquals("Incorrect Message received on consumer3.", "C", ((TextMessage) msg).getText());
+ _logger.info("Receive message on consumer 3 :expecting null");
+ msg = consumer3.receive(500);
+ assertNull("There should be no more messages for consumption on consumer3.", msg);
+
+ consumer1.close();
+ consumer3.close();
+
+ session3.unsubscribe("MySubscription");
+
+ con.close();
+
+ if(restartBroker)
+ {
+ try
+ {
+ restartBroker();
+ }
+ catch (Exception e)
+ {
+ fail("Error restarting the broker");
+ }
+ }
+ }
+
+ private void durabilityImplSessionPerConnection(int ackMode) throws Exception
+ {
+ Message msg;
+ // Create producer.
+ AMQConnection con0 = (AMQConnection) getConnection("guest", "guest");
+ con0.start();
+ Session session0 = con0.createSession(false, ackMode);
+
+ AMQTopic topic = new AMQTopic(con0, "MyTopic");
+
+ Session sessionProd = con0.createSession(false, ackMode);
+ MessageProducer producer = sessionProd.createProducer(topic);
+
+ // Create consumer 1.
+ AMQConnection con1 = (AMQConnection) getConnection("guest", "guest");
+ con1.start();
+ Session session1 = con1.createSession(false, ackMode);
+
+ MessageConsumer consumer1 = session1.createConsumer(topic);
+
+ // Create consumer 2.
+ AMQConnection con2 = (AMQConnection) getConnection("guest", "guest");
+ con2.start();
+ Session session2 = con2.createSession(false, ackMode);
+
+ TopicSubscriber consumer2 = session2.createDurableSubscriber(topic, "MySubscription");
+
+ // Send message and check that both consumers get it and only it.
+ producer.send(session0.createTextMessage("A"));
+
+ msg = consumer1.receive(500);
+ assertNotNull("Message should be available", msg);
+ assertEquals("Message Text doesn't match", "A", ((TextMessage) msg).getText());
+ msg = consumer1.receive(500);
+ assertNull("There should be no more messages for consumption on consumer1.", msg);
+
+ msg = consumer2.receive(POSITIVE_RECEIVE_TIMEOUT);
+ assertNotNull("Message should have been received",msg);
+ assertEquals("Consumer 2 should also received the first msg.", "A", ((TextMessage) msg).getText());
+ msg = consumer2.receive(NEGATIVE_RECEIVE_TIMEOUT);
+ assertNull("There should be no more messages for consumption on consumer2.", msg);
+
+ // Send message and receive on consumer 1.
+ producer.send(session0.createTextMessage("B"));
+
+ _logger.info("Receive message on consumer 1 :expecting B");
+ msg = consumer1.receive(POSITIVE_RECEIVE_TIMEOUT);
+ assertEquals("B", ((TextMessage) msg).getText());
+ _logger.info("Receive message on consumer 1 :expecting null");
+ msg = consumer1.receive(NEGATIVE_RECEIVE_TIMEOUT);
+ assertEquals(null, msg);
+
+ // Detach the durable subscriber.
+ consumer2.close();
+ session2.close();
+ con2.close();
+
+ // Send message C and receive on consumer 1
+ producer.send(session0.createTextMessage("C"));
+
+ _logger.info("Receive message on consumer 1 :expecting C");
+ msg = consumer1.receive(POSITIVE_RECEIVE_TIMEOUT);
+ assertEquals("C", ((TextMessage) msg).getText());
+ _logger.info("Receive message on consumer 1 :expecting null");
+ msg = consumer1.receive(NEGATIVE_RECEIVE_TIMEOUT);
+ assertEquals(null, msg);
+
+ // Re-attach a new consumer to the durable subscription, and check that it gets message B it left (if not NO_ACK)
+ // and also gets message C sent after it was disconnected.
+ AMQConnection con3 = (AMQConnection) getConnection("guest", "guest");
+ con3.start();
+ Session session3 = con3.createSession(false, ackMode);
+
+ TopicSubscriber consumer3 = session3.createDurableSubscriber(topic, "MySubscription");
+
+ if(ackMode == AMQSession.NO_ACKNOWLEDGE)
+ {
+ //Do nothing if NO_ACK was used, as prefetch means the message was dropped
+ //when we didn't call receive() to get it before closing consumer 2
+ }
+ else
+ {
+ _logger.info("Receive message on consumer 3 :expecting B");
+ msg = consumer3.receive(POSITIVE_RECEIVE_TIMEOUT);
+ assertNotNull(msg);
+ assertEquals("B", ((TextMessage) msg).getText());
+ }
+
+ _logger.info("Receive message on consumer 3 :expecting C");
+ msg = consumer3.receive(POSITIVE_RECEIVE_TIMEOUT);
+ assertNotNull("Consumer 3 should get message 'C'.", msg);
+ assertEquals("Incorrect Message recevied on consumer3.", "C", ((TextMessage) msg).getText());
+ _logger.info("Receive message on consumer 3 :expecting null");
+ msg = consumer3.receive(NEGATIVE_RECEIVE_TIMEOUT);
+ assertNull("There should be no more messages for consumption on consumer3.", msg);
+
+ consumer1.close();
+ consumer3.close();
+
+ session3.unsubscribe("MySubscription");
+
+ con0.close();
+ con1.close();
+ con3.close();
+ }
+
+ /**
+ * This tests the fix for QPID-1085
+ * Creates a durable subscriber with an invalid selector, checks that the
+ * exception is thrown correctly and that the subscription is not created.
+ * @throws Exception
+ */
+ public void testDurableWithInvalidSelector() throws Exception
+ {
+ Connection conn = getConnection();
+ conn.start();
+ Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ AMQTopic topic = new AMQTopic((AMQConnection) conn, "MyTestDurableWithInvalidSelectorTopic");
+ MessageProducer producer = session.createProducer(topic);
+ producer.send(session.createTextMessage("testDurableWithInvalidSelector1"));
+ try
+ {
+ TopicSubscriber deadSubscriber = session.createDurableSubscriber(topic, "testDurableWithInvalidSelectorSub",
+ "=TEST 'test", true);
+ assertNull("Subscriber should not have been created", deadSubscriber);
+ }
+ catch (JMSException e)
+ {
+ assertTrue("Wrong type of exception thrown", e instanceof InvalidSelectorException);
+ }
+ TopicSubscriber liveSubscriber = session.createDurableSubscriber(topic, "testDurableWithInvalidSelectorSub");
+ assertNotNull("Subscriber should have been created", liveSubscriber);
+
+ producer.send(session.createTextMessage("testDurableWithInvalidSelector2"));
+
+ Message msg = liveSubscriber.receive(POSITIVE_RECEIVE_TIMEOUT);
+ assertNotNull ("Message should have been received", msg);
+ assertEquals ("testDurableWithInvalidSelector2", ((TextMessage) msg).getText());
+ assertNull("Should not receive subsequent message", liveSubscriber.receive(200));
+ liveSubscriber.close();
+ session.unsubscribe("testDurableWithInvalidSelectorSub");
+ }
+
+ /**
+ * This tests the fix for QPID-1085
+ * Creates a durable subscriber with an invalid destination, checks that the
+ * exception is thrown correctly and that the subscription is not created.
+ * @throws Exception
+ */
+ public void testDurableWithInvalidDestination() throws Exception
+ {
+ Connection conn = getConnection();
+ conn.start();
+ Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ AMQTopic topic = new AMQTopic((AMQConnection) conn, "testDurableWithInvalidDestinationTopic");
+ try
+ {
+ TopicSubscriber deadSubscriber = session.createDurableSubscriber(null, "testDurableWithInvalidDestinationsub");
+ assertNull("Subscriber should not have been created", deadSubscriber);
+ }
+ catch (InvalidDestinationException e)
+ {
+ // This was expected
+ }
+ MessageProducer producer = session.createProducer(topic);
+ producer.send(session.createTextMessage("testDurableWithInvalidSelector1"));
+
+ TopicSubscriber liveSubscriber = session.createDurableSubscriber(topic, "testDurableWithInvalidDestinationsub");
+ assertNotNull("Subscriber should have been created", liveSubscriber);
+
+ producer.send(session.createTextMessage("testDurableWithInvalidSelector2"));
+ Message msg = liveSubscriber.receive(POSITIVE_RECEIVE_TIMEOUT);
+ assertNotNull ("Message should have been received", msg);
+ assertEquals ("testDurableWithInvalidSelector2", ((TextMessage) msg).getText());
+ assertNull("Should not receive subsequent message", liveSubscriber.receive(200));
+
+ session.unsubscribe("testDurableWithInvalidDestinationsub");
+ }
+
+ /**
+ * Creates a durable subscription with a selector, then changes that selector on resubscription
+ * <p>
+ * QPID-1202, QPID-2418
+ */
+ public void testResubscribeWithChangedSelector() throws Exception
+ {
+ Connection conn = getConnection();
+ conn.start();
+ Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ AMQTopic topic = new AMQTopic((AMQConnection) conn, "testResubscribeWithChangedSelector");
+ MessageProducer producer = session.createProducer(topic);
+
+ // Create durable subscriber that matches A
+ TopicSubscriber subA = session.createDurableSubscriber(topic,
+ "testResubscribeWithChangedSelector",
+ "Match = True", false);
+
+ // Send 1 matching message and 1 non-matching message
+ sendMatchingAndNonMatchingMessage(session, producer);
+
+ Message rMsg = subA.receive(NEGATIVE_RECEIVE_TIMEOUT);
+ assertNotNull(rMsg);
+ assertEquals("Content was wrong",
+ "testResubscribeWithChangedSelector1",
+ ((TextMessage) rMsg).getText());
+
+ rMsg = subA.receive(NEGATIVE_RECEIVE_TIMEOUT);
+ assertNull(rMsg);
+
+ // Disconnect subscriber
+ subA.close();
+
+ // Reconnect with new selector that matches B
+ TopicSubscriber subB = session.createDurableSubscriber(topic,
+ "testResubscribeWithChangedSelector","Match = False", false);
+
+ //verify no messages are now recieved.
+ rMsg = subB.receive(NEGATIVE_RECEIVE_TIMEOUT);
+ assertNull("Should not have received message as the selector was changed", rMsg);
+
+ // Check that new messages are received properly
+ sendMatchingAndNonMatchingMessage(session, producer);
+ rMsg = subB.receive(POSITIVE_RECEIVE_TIMEOUT);
+
+ assertNotNull("Message should have been received", rMsg);
+ assertEquals("Content was wrong",
+ "testResubscribeWithChangedSelector2",
+ ((TextMessage) rMsg).getText());
+
+
+ rMsg = subB.receive(NEGATIVE_RECEIVE_TIMEOUT);
+ assertNull("Message should not have been received",rMsg);
+ session.unsubscribe("testResubscribeWithChangedSelector");
+ }
+
+ public void testDurableSubscribeWithTemporaryTopic() throws Exception
+ {
+ Connection conn = getConnection();
+ conn.start();
+ Session ssn = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ Topic topic = ssn.createTemporaryTopic();
+ try
+ {
+ ssn.createDurableSubscriber(topic, "test");
+ fail("expected InvalidDestinationException");
+ }
+ catch (InvalidDestinationException ex)
+ {
+ // this is expected
+ }
+ try
+ {
+ ssn.createDurableSubscriber(topic, "test", null, false);
+ fail("expected InvalidDestinationException");
+ }
+ catch (InvalidDestinationException ex)
+ {
+ // this is expected
+ }
+ }
+
+ private void sendMatchingAndNonMatchingMessage(Session session, MessageProducer producer) throws JMSException
+ {
+ TextMessage msg = session.createTextMessage("testResubscribeWithChangedSelector1");
+ msg.setBooleanProperty("Match", true);
+ producer.send(msg);
+ msg = session.createTextMessage("testResubscribeWithChangedSelector2");
+ msg.setBooleanProperty("Match", false);
+ producer.send(msg);
+ }
+
+
+ /**
+ * create and register a durable subscriber with a message selector and then close it
+ * create a publisher and send 5 right messages and 5 wrong messages
+ * create another durable subscriber with the same selector and name
+ * check messages are still there
+ * <p>
+ * QPID-2418
+ */
+ public void testDurSubSameMessageSelector() throws Exception
+ {
+ Connection conn = getConnection();
+ conn.start();
+ Session session = conn.createSession(true, Session.SESSION_TRANSACTED);
+ AMQTopic topic = new AMQTopic((AMQConnection) conn, "sameMessageSelector");
+
+ //create and register a durable subscriber with a message selector and then close it
+ TopicSubscriber subOne = session.createDurableSubscriber(topic, "sameMessageSelector", "testprop = TRUE", false);
+ subOne.close();
+
+ MessageProducer producer = session.createProducer(topic);
+ for (int i = 0; i < 5; i++)
+ {
+ Message message = session.createMessage();
+ message.setBooleanProperty("testprop", true);
+ producer.send(message);
+ message = session.createMessage();
+ message.setBooleanProperty("testprop", false);
+ producer.send(message);
+ }
+ session.commit();
+ producer.close();
+
+ // should be 5 or 10 messages on queue now
+ // (5 for the java broker due to use of server side selectors, and 10 for the cpp broker due to client side selectors only)
+ AMQQueue queue = new AMQQueue("amq.topic", "clientid" + ":" + "sameMessageSelector");
+ assertEquals("Queue depth is wrong", isJavaBroker() ? 5 : 10, ((AMQSession<?, ?>) session).getQueueDepth(queue));
+
+ // now recreate the durable subscriber and check the received messages
+ TopicSubscriber subTwo = session.createDurableSubscriber(topic, "sameMessageSelector", "testprop = TRUE", false);
+
+ for (int i = 0; i < 5; i++)
+ {
+ Message message = subTwo.receive(1000);
+ if (message == null)
+ {
+ fail("sameMessageSelector test failed. no message was returned");
+ }
+ else
+ {
+ assertEquals("sameMessageSelector test failed. message selector not reset",
+ "true", message.getStringProperty("testprop"));
+ }
+ }
+
+ session.commit();
+
+ // Check queue has no messages
+ if (isJavaBroker())
+ {
+ assertEquals("Queue should be empty", 0, ((AMQSession<?, ?>) session).getQueueDepth(queue));
+ }
+ else
+ {
+ assertTrue("At most the queue should have only 1 message", ((AMQSession<?, ?>) session).getQueueDepth(queue) <= 1);
+ }
+
+ // Unsubscribe
+ session.unsubscribe("sameMessageSelector");
+
+ conn.close();
+ }
+
+ /**
+ * <ul>
+ * <li>create and register a durable subscriber with a message selector
+ * <li>create another durable subscriber with a different selector and same name
+ * <li>check first subscriber is now closed
+ * <li>create a publisher and send messages
+ * <li>check messages are received correctly
+ * </ul>
+ * <p>
+ * QPID-2418
+ */
+ public void testResubscribeWithChangedSelectorNoClose() throws Exception
+ {
+ Connection conn = getConnection();
+ conn.start();
+ Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ AMQTopic topic = new AMQTopic((AMQConnection) conn, "testResubscribeWithChangedSelectorNoClose");
+
+ // Create durable subscriber that matches A
+ TopicSubscriber subA = session.createDurableSubscriber(topic,
+ "testResubscribeWithChangedSelectorNoClose",
+ "Match = True", false);
+
+ // Reconnect with new selector that matches B
+ TopicSubscriber subB = session.createDurableSubscriber(topic,
+ "testResubscribeWithChangedSelectorNoClose",
+ "Match = false", false);
+
+ // First subscription has been closed
+ try
+ {
+ subA.receive(1000);
+ fail("First subscription was not closed");
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+
+ conn.stop();
+
+ // Send 1 matching message and 1 non-matching message
+ MessageProducer producer = session.createProducer(topic);
+ TextMessage msg = session.createTextMessage("testResubscribeWithChangedSelectorAndRestart1");
+ msg.setBooleanProperty("Match", true);
+ producer.send(msg);
+ msg = session.createTextMessage("testResubscribeWithChangedSelectorAndRestart2");
+ msg.setBooleanProperty("Match", false);
+ producer.send(msg);
+
+ // should be 1 or 2 messages on queue now
+ // (1 for the java broker due to use of server side selectors, and 2 for the cpp broker due to client side selectors only)
+ AMQQueue queue = new AMQQueue("amq.topic", "clientid" + ":" + "testResubscribeWithChangedSelectorNoClose");
+ assertEquals("Queue depth is wrong", isJavaBroker() ? 1 : 2, ((AMQSession<?, ?>) session).getQueueDepth(queue));
+
+ conn.start();
+
+ Message rMsg = subB.receive(1000);
+ assertNotNull(rMsg);
+ assertEquals("Content was wrong",
+ "testResubscribeWithChangedSelectorAndRestart2",
+ ((TextMessage) rMsg).getText());
+
+ rMsg = subB.receive(1000);
+ assertNull(rMsg);
+
+ // Check queue has no messages
+ assertEquals("Queue should be empty", 0, ((AMQSession<?, ?>) session).getQueueDepth(queue));
+
+ conn.close();
+ }
+
+ /**
+ * <ul>
+ * <li>create and register a durable subscriber with no message selector
+ * <li>create another durable subscriber with a selector and same name
+ * <li>check first subscriber is now closed
+ * <li>create a publisher and send messages
+ * <li>check messages are recieved correctly
+ * </ul>
+ * <p>
+ * QPID-2418
+ */
+ public void testDurSubAddMessageSelectorNoClose() throws Exception
+ {
+ Connection conn = getConnection();
+ conn.start();
+ Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ AMQTopic topic = new AMQTopic((AMQConnection) conn, "subscriptionName");
+
+ // create and register a durable subscriber with no message selector
+ TopicSubscriber subOne = session.createDurableSubscriber(topic, "subscriptionName", null, false);
+
+ // now create a durable subscriber with a selector
+ TopicSubscriber subTwo = session.createDurableSubscriber(topic, "subscriptionName", "testprop = TRUE", false);
+
+ // First subscription has been closed
+ try
+ {
+ subOne.receive(1000);
+ fail("First subscription was not closed");
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+
+ conn.stop();
+
+ // Send 1 matching message and 1 non-matching message
+ MessageProducer producer = session.createProducer(topic);
+ TextMessage msg = session.createTextMessage("testResubscribeWithChangedSelectorAndRestart1");
+ msg.setBooleanProperty("testprop", true);
+ producer.send(msg);
+ msg = session.createTextMessage("testResubscribeWithChangedSelectorAndRestart2");
+ msg.setBooleanProperty("testprop", false);
+ producer.send(msg);
+
+ // should be 1 or 2 messages on queue now
+ // (1 for the java broker due to use of server side selectors, and 2 for the cpp broker due to client side selectors only)
+ AMQQueue queue = new AMQQueue("amq.topic", "clientid" + ":" + "subscriptionName");
+ assertEquals("Queue depth is wrong", isJavaBroker() ? 1 : 2, ((AMQSession<?, ?>) session).getQueueDepth(queue));
+
+ conn.start();
+
+ Message rMsg = subTwo.receive(1000);
+ assertNotNull(rMsg);
+ assertEquals("Content was wrong",
+ "testResubscribeWithChangedSelectorAndRestart1",
+ ((TextMessage) rMsg).getText());
+
+ rMsg = subTwo.receive(1000);
+ assertNull(rMsg);
+
+ // Check queue has no messages
+ assertEquals("Queue should be empty", 0, ((AMQSession<?, ?>) session).getQueueDepth(queue));
+
+ conn.close();
+ }
+
+ /**
+ * <ul>
+ * <li>create and register a durable subscriber with no message selector
+ * <li>try to create another durable with the same name, should fail
+ * </ul>
+ * <p>
+ * QPID-2418
+ */
+ public void testDurSubNoSelectorResubscribeNoClose() throws Exception
+ {
+ Connection conn = getConnection();
+ conn.start();
+ Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE);
+ AMQTopic topic = new AMQTopic((AMQConnection) conn, "subscriptionName");
+
+ // create and register a durable subscriber with no message selector
+ session.createDurableSubscriber(topic, "subscriptionName", null, false);
+
+ // try to recreate the durable subscriber
+ try
+ {
+ session.createDurableSubscriber(topic, "subscriptionName", null, false);
+ fail("Subscription should not have been created");
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/TopicPublisherTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/TopicPublisherTest.java
new file mode 100644
index 0000000000..5874133ab1
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/TopicPublisherTest.java
@@ -0,0 +1,76 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.topic;
+
+import javax.jms.MessageConsumer;
+import javax.jms.TextMessage;
+import javax.jms.TopicPublisher;
+import javax.jms.TopicSession;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.AMQTopic;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+/**
+ * @author Apache Software Foundation
+ */
+public class TopicPublisherTest extends QpidBrokerTestCase
+{
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ }
+
+ protected void tearDown() throws Exception
+ {
+ super.tearDown();
+ }
+
+ public void testUnidentifiedProducer() throws Exception
+ {
+
+ AMQConnection con = (AMQConnection) getConnection("guest", "guest");
+ AMQTopic topic = new AMQTopic(con,"MyTopic");
+ TopicSession session1 = con.createTopicSession(false, AMQSession.NO_ACKNOWLEDGE);
+ TopicPublisher publisher = session1.createPublisher(null);
+ MessageConsumer consumer1 = session1.createConsumer(topic);
+ con.start();
+ publisher.publish(topic, session1.createTextMessage("Hello"));
+ TextMessage m = (TextMessage) consumer1.receive(2000);
+ assertNotNull(m);
+ try
+ {
+ publisher.publish(session1.createTextMessage("Goodbye"));
+ fail("Did not throw UnsupportedOperationException");
+ }
+ catch (UnsupportedOperationException e)
+ {
+ // PASS
+ }
+ con.close();
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(TopicPublisherTest.class);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/TopicSessionTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/TopicSessionTest.java
new file mode 100644
index 0000000000..eee232e113
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/topic/TopicSessionTest.java
@@ -0,0 +1,453 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.topic;
+
+import javax.jms.InvalidDestinationException;
+import javax.jms.JMSException;
+import javax.jms.MessageConsumer;
+import javax.jms.Session;
+import javax.jms.TemporaryTopic;
+import javax.jms.TextMessage;
+import javax.jms.TopicPublisher;
+import javax.jms.TopicSession;
+import javax.jms.TopicSubscriber;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.client.AMQTopic;
+import org.apache.qpid.client.AMQTopicSessionAdaptor;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+
+/** @author Apache Software Foundation */
+public class TopicSessionTest extends QpidBrokerTestCase
+{
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ }
+
+ protected void tearDown() throws Exception
+ {
+ super.tearDown();
+ }
+
+
+ public void testTopicSubscriptionUnsubscription() throws Exception
+ {
+
+ AMQConnection con = (AMQConnection) getConnection("guest", "guest");
+ AMQTopic topic = new AMQTopic(con.getDefaultTopicExchangeName(), "MyTopic");
+ TopicSession session1 = con.createTopicSession(true, AMQSession.NO_ACKNOWLEDGE);
+ TopicSubscriber sub = session1.createDurableSubscriber(topic, "subscription0");
+ TopicPublisher publisher = session1.createPublisher(topic);
+
+ con.start();
+
+ TextMessage tm = session1.createTextMessage("Hello");
+ publisher.publish(tm);
+ session1.commit();
+
+ tm = (TextMessage) sub.receive(2000);
+ assertNotNull(tm);
+ session1.commit();
+ session1.unsubscribe("subscription0");
+
+ try
+ {
+ session1.unsubscribe("not a subscription");
+ fail("expected InvalidDestinationException when unsubscribing from unknown subscription");
+ }
+ catch (InvalidDestinationException e)
+ {
+ ; // PASS
+ }
+ catch (Exception e)
+ {
+ fail("expected InvalidDestinationException when unsubscribing from unknown subscription, got: " + e);
+ }
+
+ con.close();
+ }
+
+ public void testSubscriptionNameReuseForDifferentTopicSingleConnection() throws Exception
+ {
+ subscriptionNameReuseForDifferentTopic(false);
+ }
+
+ public void testSubscriptionNameReuseForDifferentTopicTwoConnections() throws Exception
+ {
+ subscriptionNameReuseForDifferentTopic(true);
+ }
+
+ private void subscriptionNameReuseForDifferentTopic(boolean shutdown) throws Exception
+ {
+ AMQConnection con = (AMQConnection) getConnection("guest", "guest");
+ AMQTopic topic = new AMQTopic(con, "MyTopic1" + String.valueOf(shutdown));
+ AMQTopic topic2 = new AMQTopic(con, "MyOtherTopic1" + String.valueOf(shutdown));
+
+ TopicSession session1 = con.createTopicSession(true, AMQSession.AUTO_ACKNOWLEDGE);
+ TopicSubscriber sub = session1.createDurableSubscriber(topic, "subscription0");
+ TopicPublisher publisher = session1.createPublisher(null);
+
+ con.start();
+
+ publisher.publish(topic, session1.createTextMessage("hello"));
+ session1.commit();
+ TextMessage m = (TextMessage) sub.receive(2000);
+ assertNotNull(m);
+ session1.commit();
+
+ if (shutdown)
+ {
+ session1.close();
+ con.close();
+ con = (AMQConnection) getConnection("guest", "guest");
+ con.start();
+ session1 = con.createTopicSession(true, AMQSession.NO_ACKNOWLEDGE);
+ publisher = session1.createPublisher(null);
+ }
+ sub.close();
+ TopicSubscriber sub2 = session1.createDurableSubscriber(topic2, "subscription0");
+ publisher.publish(topic, session1.createTextMessage("hello"));
+ session1.commit();
+ if (!shutdown)
+ {
+ m = (TextMessage) sub2.receive(2000);
+ assertNull(m);
+ session1.commit();
+ }
+ publisher.publish(topic2, session1.createTextMessage("goodbye"));
+ session1.commit();
+ m = (TextMessage) sub2.receive(2000);
+ assertNotNull(m);
+ assertEquals("goodbye", m.getText());
+ session1.unsubscribe("subscription0");
+ con.close();
+ }
+
+ public void testUnsubscriptionAfterConnectionClose() throws Exception
+ {
+ AMQConnection con1 = (AMQConnection) getClientConnection("guest", "guest", "clientid");
+ AMQTopic topic = new AMQTopic(con1, "MyTopic3");
+
+ TopicSession session1 = con1.createTopicSession(true, AMQSession.AUTO_ACKNOWLEDGE);
+ TopicPublisher publisher = session1.createPublisher(topic);
+
+ AMQConnection con2 = (AMQConnection) getClientConnection("guest", "guest", "clientid");
+ TopicSession session2 = con2.createTopicSession(true, AMQSession.AUTO_ACKNOWLEDGE);
+ TopicSubscriber sub = session2.createDurableSubscriber(topic, "subscription0");
+
+ con2.start();
+
+ publisher.publish(session1.createTextMessage("Hello"));
+ session1.commit();
+ TextMessage tm = (TextMessage) sub.receive(2000);
+ session2.commit();
+ assertNotNull(tm);
+ con2.close();
+ publisher.publish(session1.createTextMessage("Hello2"));
+ session1.commit();
+ con2 = (AMQConnection) getClientConnection("guest", "guest", "clientid");
+ session2 = con2.createTopicSession(true, AMQSession.NO_ACKNOWLEDGE);
+ sub = session2.createDurableSubscriber(topic, "subscription0");
+ con2.start();
+ tm = (TextMessage) sub.receive(2000);
+ session2.commit();
+ assertNotNull(tm);
+ assertEquals("Hello2", tm.getText());
+ session2.unsubscribe("subscription0");
+ con1.close();
+ con2.close();
+ }
+
+ public void testTextMessageCreation() throws Exception
+ {
+
+ AMQConnection con = (AMQConnection) getConnection("guest", "guest");
+ AMQTopic topic = new AMQTopic(con, "MyTopic4");
+ TopicSession session1 = con.createTopicSession(true, AMQSession.AUTO_ACKNOWLEDGE);
+ TopicPublisher publisher = session1.createPublisher(topic);
+ MessageConsumer consumer1 = session1.createConsumer(topic);
+ con.start();
+ TextMessage tm = session1.createTextMessage("Hello");
+ publisher.publish(tm);
+ session1.commit();
+ tm = (TextMessage) consumer1.receive(10000L);
+ assertNotNull(tm);
+ String msgText = tm.getText();
+ assertEquals("Hello", msgText);
+ tm = session1.createTextMessage();
+ msgText = tm.getText();
+ assertNull(msgText);
+ publisher.publish(tm);
+ session1.commit();
+ tm = (TextMessage) consumer1.receive(10000L);
+ assertNotNull(tm);
+ session1.commit();
+ msgText = tm.getText();
+ assertNull(msgText);
+ tm.clearBody();
+ tm.setText("Now we are not null");
+ publisher.publish(tm);
+ session1.commit();
+ tm = (TextMessage) consumer1.receive(2000);
+ assertNotNull(tm);
+ session1.commit();
+ msgText = tm.getText();
+ assertEquals("Now we are not null", msgText);
+
+ tm = session1.createTextMessage("");
+ msgText = tm.getText();
+ assertEquals("Empty string not returned", "", msgText);
+ publisher.publish(tm);
+ session1.commit();
+ tm = (TextMessage) consumer1.receive(2000);
+ session1.commit();
+ assertNotNull(tm);
+ assertEquals("Empty string not returned", "", msgText);
+ con.close();
+ }
+
+ public void testSendingSameMessage() throws Exception
+ {
+ AMQConnection conn = (AMQConnection) getConnection("guest", "guest");
+ TopicSession session = conn.createTopicSession(true, Session.AUTO_ACKNOWLEDGE);
+ TemporaryTopic topic = session.createTemporaryTopic();
+ assertNotNull(topic);
+ TopicPublisher producer = session.createPublisher(topic);
+ MessageConsumer consumer = session.createConsumer(topic);
+ conn.start();
+ TextMessage sentMessage = session.createTextMessage("Test Message");
+ producer.send(sentMessage);
+ session.commit();
+ TextMessage receivedMessage = (TextMessage) consumer.receive(2000);
+ assertNotNull(receivedMessage);
+ assertEquals(sentMessage.getText(), receivedMessage.getText());
+ producer.send(sentMessage);
+ session.commit();
+ receivedMessage = (TextMessage) consumer.receive(2000);
+ assertNotNull(receivedMessage);
+ assertEquals(sentMessage.getText(), receivedMessage.getText());
+ session.commit();
+ conn.close();
+
+ }
+
+ public void testTemporaryTopic() throws Exception
+ {
+ AMQConnection conn = (AMQConnection) getConnection("guest", "guest");
+ TopicSession session = conn.createTopicSession(true, Session.AUTO_ACKNOWLEDGE);
+ TemporaryTopic topic = session.createTemporaryTopic();
+ assertNotNull(topic);
+ TopicPublisher producer = session.createPublisher(topic);
+ MessageConsumer consumer = session.createConsumer(topic);
+ conn.start();
+ producer.send(session.createTextMessage("hello"));
+ session.commit();
+ TextMessage tm = (TextMessage) consumer.receive(2000);
+ assertNotNull(tm);
+ assertEquals("hello", tm.getText());
+ session.commit();
+ try
+ {
+ topic.delete();
+ fail("Expected JMSException : should not be able to delete while there are active consumers");
+ }
+ catch (JMSException je)
+ {
+ ; //pass
+ }
+
+ consumer.close();
+
+ try
+ {
+ topic.delete();
+ }
+ catch (JMSException je)
+ {
+ fail("Unexpected Exception: " + je.getMessage());
+ }
+
+ TopicSession session2 = conn.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
+ try
+ {
+ session2.createConsumer(topic);
+ fail("Expected a JMSException when subscribing to a temporary topic created on adifferent session");
+ }
+ catch (JMSException je)
+ {
+ ; // pass
+ }
+
+
+ conn.close();
+ }
+
+
+ public void testNoLocal() throws Exception
+ {
+
+ AMQConnection con = (AMQConnection) getConnection("guest", "guest");
+
+ AMQTopic topic = new AMQTopic(con, "testNoLocal");
+
+ TopicSession session1 = con.createTopicSession(true, AMQSession.AUTO_ACKNOWLEDGE);
+ TopicSubscriber noLocal = session1.createSubscriber(topic, "", true);
+
+ TopicSubscriber select = session1.createSubscriber(topic, "Selector = 'select'", false);
+ TopicSubscriber normal = session1.createSubscriber(topic);
+
+
+ TopicPublisher publisher = session1.createPublisher(topic);
+
+ con.start();
+ TextMessage m;
+ TextMessage message;
+
+ //send message to all consumers
+ publisher.publish(session1.createTextMessage("hello-new2"));
+ session1.commit();
+ //test normal subscriber gets message
+ m = (TextMessage) normal.receive(1000);
+ assertNotNull(m);
+ session1.commit();
+
+ //test selector subscriber doesn't message
+ m = (TextMessage) select.receive(1000);
+ assertNull(m);
+ session1.commit();
+
+ //test nolocal subscriber doesn't message
+ m = (TextMessage) noLocal.receive(1000);
+ if (m != null)
+ {
+ System.out.println("Message:" + m.getText());
+ }
+ assertNull(m);
+
+ //send message to all consumers
+ message = session1.createTextMessage("hello2");
+ message.setStringProperty("Selector", "select");
+
+ publisher.publish(message);
+ session1.commit();
+
+ //test normal subscriber gets message
+ m = (TextMessage) normal.receive(1000);
+ assertNotNull(m);
+ session1.commit();
+
+ //test selector subscriber does get message
+ m = (TextMessage) select.receive(1000);
+ assertNotNull(m);
+ session1.commit();
+
+ //test nolocal subscriber doesn't message
+ m = (TextMessage) noLocal.receive(100);
+ assertNull(m);
+
+ AMQConnection con2 = (AMQConnection) getClientConnection("guest", "guest", "foo");
+ TopicSession session2 = con2.createTopicSession(true, AMQSession.AUTO_ACKNOWLEDGE);
+ TopicPublisher publisher2 = session2.createPublisher(topic);
+
+
+ message = session2.createTextMessage("hello2");
+ message.setStringProperty("Selector", "select");
+
+ publisher2.publish(message);
+ session2.commit();
+
+ //test normal subscriber gets message
+ m = (TextMessage) normal.receive(1000);
+ assertNotNull(m);
+ session1.commit();
+
+ //test selector subscriber does get message
+ m = (TextMessage) select.receive(1000);
+ assertNotNull(m);
+ session1.commit();
+
+ //test nolocal subscriber does message
+ m = (TextMessage) noLocal.receive(1000);
+ assertNotNull(m);
+
+
+ con.close();
+ con2.close();
+ }
+
+ /**
+ * This tests QPID-1191, where messages which are sent to a topic but are not consumed by a subscriber
+ * due to a selector can be leaked.
+ * @throws Exception
+ */
+ public void testNonMatchingMessagesDoNotFillQueue() throws Exception
+ {
+ AMQConnection con = (AMQConnection) getConnection("guest", "guest");
+
+ // Setup Topic
+ AMQTopic topic = new AMQTopic(con, "testNoLocal");
+
+ TopicSession session = con.createTopicSession(true, AMQSession.NO_ACKNOWLEDGE);
+
+ // Setup subscriber with selector
+ TopicSubscriber selector = session.createSubscriber(topic, "Selector = 'select'", false);
+ TopicPublisher publisher = session.createPublisher(topic);
+
+ con.start();
+ TextMessage m;
+ TextMessage message;
+
+ // Send non-matching message
+ message = session.createTextMessage("non-matching 1");
+ publisher.publish(message);
+ session.commit();
+
+ // Send and consume matching message
+ message = session.createTextMessage("hello");
+ message.setStringProperty("Selector", "select");
+
+ publisher.publish(message);
+ session.commit();
+
+ m = (TextMessage) selector.receive(1000);
+ assertNotNull("should have received message", m);
+ assertEquals("Message contents were wrong", "hello", m.getText());
+
+ // Send non-matching message
+ message = session.createTextMessage("non-matching 2");
+ publisher.publish(message);
+ session.commit();
+
+ // Assert queue count is 0
+ long depth = ((AMQTopicSessionAdaptor) session).getSession().getQueueDepth(topic);
+ assertEquals("Queue depth was wrong", 0, depth);
+
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(TopicSessionTest.class);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/CommitRollbackTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/CommitRollbackTest.java
new file mode 100644
index 0000000000..bc2cbe714f
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/CommitRollbackTest.java
@@ -0,0 +1,572 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ *
+ */
+package org.apache.qpid.test.unit.transacted;
+
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.client.AMQConnection;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.*;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class tests a number of commits and roll back scenarios
+ *
+ * Assumptions; - Assumes empty Queue
+ */
+public class CommitRollbackTest extends QpidBrokerTestCase
+{
+ protected AMQConnection conn;
+ protected String queue = "direct://amq.direct//Qpid.Client.Transacted.CommitRollback.queue";
+ protected static int testMethod = 0;
+ protected String payload = "xyzzy";
+ private Session _session;
+ private MessageProducer _publisher;
+ private Session _pubSession;
+ private MessageConsumer _consumer;
+ Queue _jmsQueue;
+
+ private static final Logger _logger = LoggerFactory.getLogger(CommitRollbackTest.class);
+ private boolean _gotone = false;
+ private boolean _gottwo = false;
+ private boolean _gottwoRedelivered = false;
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ testMethod++;
+ queue += testMethod;
+ newConnection();
+ }
+
+ private void newConnection() throws Exception
+ {
+ conn = (AMQConnection) getConnection("guest", "guest");
+
+ _session = conn.createSession(true, Session.CLIENT_ACKNOWLEDGE);
+
+ _jmsQueue = _session.createQueue(queue);
+ _consumer = _session.createConsumer(_jmsQueue);
+
+ _pubSession = conn.createSession(true, Session.CLIENT_ACKNOWLEDGE);
+
+ _publisher = _pubSession.createProducer(_pubSession.createQueue(queue));
+
+ conn.start();
+ }
+
+ protected void tearDown() throws Exception
+ {
+ conn.close();
+ super.tearDown();
+ }
+
+ /**
+ * PUT a text message, disconnect before commit, confirm it is gone.
+ *
+ * @throws Exception On error
+ */
+ public void testPutThenDisconnect() throws Exception
+ {
+ assertTrue("session is not transacted", _session.getTransacted());
+ assertTrue("session is not transacted", _pubSession.getTransacted());
+
+ _logger.info("sending test message");
+ String MESSAGE_TEXT = "testPutThenDisconnect";
+ _publisher.send(_pubSession.createTextMessage(MESSAGE_TEXT));
+
+ _logger.info("reconnecting without commit");
+ conn.close();
+
+ newConnection();
+
+ _logger.info("receiving result");
+ Message result = _consumer.receive(1000);
+
+ // commit to ensure message is removed from queue
+ _session.commit();
+
+ assertNull("test message was put and disconnected before commit, but is still present", result);
+ }
+
+ /**
+ * PUT a text message, disconnect before commit, confirm it is gone.
+ *
+ * @throws Exception On error
+ */
+ public void testPutThenCloseDisconnect() throws Exception
+ {
+ assertTrue("session is not transacted", _session.getTransacted());
+ assertTrue("session is not transacted", _pubSession.getTransacted());
+
+ _logger.info("sending test message");
+ String MESSAGE_TEXT = "testPutThenDisconnect";
+ _publisher.send(_pubSession.createTextMessage(MESSAGE_TEXT));
+
+ _logger.info("closing publisher without commit");
+ _publisher.close();
+
+ _logger.info("reconnecting without commit");
+ conn.close();
+
+ newConnection();
+
+ _logger.info("receiving result");
+ Message result = _consumer.receive(1000);
+
+ // commit to ensure message is removed from queue
+ _session.commit();
+
+ assertNull("test message was put and disconnected before commit, but is still present", result);
+ }
+
+ /**
+ * PUT a text message, rollback, confirm message is gone. The consumer is on the same connection but different
+ * session as producer
+ *
+ * @throws Exception On error
+ */
+ public void testPutThenRollback() throws Exception
+ {
+ assertTrue("session is not transacted", _session.getTransacted());
+ assertTrue("session is not transacted", _pubSession.getTransacted());
+
+ _logger.info("sending test message");
+ String MESSAGE_TEXT = "testPutThenRollback";
+ _publisher.send(_pubSession.createTextMessage(MESSAGE_TEXT));
+
+ _logger.info("rolling back");
+ _pubSession.rollback();
+
+ _logger.info("receiving result");
+ Message result = _consumer.receive(1000);
+
+ assertNull("test message was put and rolled back, but is still present", result);
+ }
+
+ /**
+ * GET a text message, disconnect before commit, confirm it is still there. The consumer is on a new connection
+ *
+ * @throws Exception On error
+ */
+ public void testGetThenDisconnect() throws Exception
+ {
+ assertTrue("session is not transacted", _session.getTransacted());
+ assertTrue("session is not transacted", _pubSession.getTransacted());
+
+ _logger.info("sending test message");
+ String MESSAGE_TEXT = "testGetThenDisconnect";
+ _publisher.send(_pubSession.createTextMessage(MESSAGE_TEXT));
+
+ _pubSession.commit();
+
+ _logger.info("getting test message");
+
+ Message msg = _consumer.receive(1000);
+ assertNotNull("retrieved message is null", msg);
+
+ _logger.info("closing connection");
+ conn.close();
+
+ newConnection();
+
+ _logger.info("receiving result");
+ Message result = _consumer.receive(1000);
+
+ _session.commit();
+
+ assertNotNull("test message was consumed and disconnected before commit, but is gone", result);
+ assertEquals("test message was correct message", MESSAGE_TEXT, ((TextMessage) result).getText());
+ }
+
+ /**
+ * GET a text message, close consumer, disconnect before commit, confirm it is still there. The consumer is on the
+ * same connection but different session as producer
+ *
+ * @throws Exception On error
+ */
+ public void testGetThenCloseDisconnect() throws Exception
+ {
+ assertTrue("session is not transacted", _session.getTransacted());
+ assertTrue("session is not transacted", _pubSession.getTransacted());
+
+ _logger.info("sending test message");
+ String MESSAGE_TEXT = "testGetThenCloseDisconnect";
+ _publisher.send(_pubSession.createTextMessage(MESSAGE_TEXT));
+
+ _pubSession.commit();
+
+ _logger.info("getting test message");
+
+ Message msg = _consumer.receive(1000);
+ assertNotNull("retrieved message is null", msg);
+ assertEquals("test message was correct message", MESSAGE_TEXT, ((TextMessage) msg).getText());
+
+ _logger.info("reconnecting without commit");
+ _consumer.close();
+ conn.close();
+
+ newConnection();
+
+ _logger.info("receiving result");
+ Message result = _consumer.receive(1000);
+
+ _session.commit();
+
+ assertNotNull("test message was consumed and disconnected before commit, but is gone", result);
+ assertEquals("test message was correct message", MESSAGE_TEXT, ((TextMessage) result).getText());
+ }
+
+ /**
+ * GET a text message, rollback, confirm it is still there. The consumer is on the same connection but differnt
+ * session to the producer
+ *
+ * @throws Exception On error
+ */
+ public void testGetThenRollback() throws Exception
+ {
+ assertTrue("session is not transacted", _session.getTransacted());
+ assertTrue("session is not transacted", _pubSession.getTransacted());
+
+ _logger.info("sending test message");
+ String MESSAGE_TEXT = "testGetThenRollback";
+ _publisher.send(_pubSession.createTextMessage(MESSAGE_TEXT));
+
+ _pubSession.commit();
+
+ _logger.info("getting test message");
+
+ Message msg = _consumer.receive(1000);
+
+ assertNotNull("retrieved message is null", msg);
+ assertEquals("test message was correct message", MESSAGE_TEXT, ((TextMessage) msg).getText());
+
+ _logger.info("rolling back");
+
+ _session.rollback();
+
+ _logger.info("receiving result");
+
+ Message result = _consumer.receive(1000);
+
+ _session.commit();
+ assertNotNull("test message was consumed and rolled back, but is gone", result);
+ assertEquals("test message was correct message", MESSAGE_TEXT, ((TextMessage) result).getText());
+ assertTrue("Message is not marked as redelivered", result.getJMSRedelivered());
+ }
+
+ /**
+ * GET a text message, close message producer, rollback, confirm it is still there. The consumer is on the same
+ * connection but different session as producer
+ *
+ * @throws Exception On error
+ */
+ public void testGetThenCloseRollback() throws Exception
+ {
+ assertTrue("session is not transacted", _session.getTransacted());
+ assertTrue("session is not transacted", _pubSession.getTransacted());
+
+ _logger.info("sending test message");
+ String MESSAGE_TEXT = "testGetThenCloseRollback";
+ _publisher.send(_pubSession.createTextMessage(MESSAGE_TEXT));
+
+ _pubSession.commit();
+
+ _logger.info("getting test message");
+
+ Message msg = _consumer.receive(1000);
+
+ assertNotNull("retrieved message is null", msg);
+ assertEquals("test message was correct message", MESSAGE_TEXT, ((TextMessage) msg).getText());
+
+ _logger.info("Closing consumer");
+ _consumer.close();
+
+ _logger.info("rolling back");
+ _session.rollback();
+
+ _logger.info("receiving result");
+
+ _consumer = _session.createConsumer(_jmsQueue);
+
+ Message result = _consumer.receive(1000);
+
+ _session.commit();
+ assertNotNull("test message was consumed and rolled back, but is gone", result);
+ assertEquals("test message was correct message", MESSAGE_TEXT, ((TextMessage) result).getText());
+ assertTrue("Message is not marked as redelivered", result.getJMSRedelivered());
+ }
+
+ /**
+ * Test that rolling back a session purges the dispatcher queue, and the messages arrive in the correct order
+ *
+ * @throws Exception On error
+ */
+ public void testSend2ThenRollback() throws Exception
+ {
+ int run = 0;
+ while (run < 10)
+ {
+ run++;
+ _logger.info("Run:" + run);
+ assertTrue("session is not transacted", _session.getTransacted());
+ assertTrue("session is not transacted", _pubSession.getTransacted());
+
+ _logger.info("sending two test messages");
+ _publisher.send(_pubSession.createTextMessage("1"));
+ _publisher.send(_pubSession.createTextMessage("2"));
+ _pubSession.commit();
+
+ _logger.info("getting test message");
+ assertEquals("1", ((TextMessage) _consumer.receive(1000)).getText());
+
+ _logger.info("rolling back");
+ _session.rollback();
+
+ _logger.info("receiving result");
+ Message result = _consumer.receive(1000);
+
+ assertNotNull("test message was consumed and rolled back, but is gone", result);
+
+ // Message Order is:
+
+ // Send 1 , 2
+ // Retrieve 1 and then rollback
+ // Receieve 1 (redelivered) , 2 (may or may not be redelivered??)
+
+ verifyMessages(result);
+
+ // Occassionally get message 2 first!
+// assertEquals("Should get message one first", "1", ((TextMessage) result).getText());
+// assertTrue("Message is not marked as redelivered", result.getJMSRedelivered());
+//
+// result = _consumer.receive(1000);
+// assertEquals("Second message should be message 2", "2", ((TextMessage) result).getText());
+// assertTrue("Message is not marked as redelivered", result.getJMSRedelivered());
+//
+// result = _consumer.receive(1000);
+// assertNull("There should be no more messages", result);
+
+ _session.commit();
+ }
+ }
+
+ private void verifyMessages(Message result) throws JMSException
+ {
+
+ if (result == null)
+ {
+ assertTrue("Didn't receive redelivered message one", _gotone);
+ assertTrue("Didn't receive message two at all", _gottwo | _gottwoRedelivered);
+ _gotone = false;
+ _gottwo = false;
+ _gottwoRedelivered = false;
+ return;
+ }
+
+ if (((TextMessage) result).getText().equals("1"))
+ {
+ _logger.info("Got 1 redelivered");
+ assertTrue("Message is not marked as redelivered", result.getJMSRedelivered());
+ assertFalse("Already received message one", _gotone);
+ _gotone = true;
+
+ }
+ else
+ {
+ assertEquals("2", ((TextMessage) result).getText());
+
+ if (result.getJMSRedelivered())
+ {
+ _logger.info("Got 2 redelivered, message was prefetched");
+ assertFalse("Already received message redelivered two", _gottwoRedelivered);
+
+ _gottwoRedelivered = true;
+ }
+ else
+ {
+ _logger.warn("Got 2, message prefetched wasn't cleared or messages was in transit when rollback occured");
+ assertFalse("Already received message two", _gottwo);
+ assertFalse("Already received message redelivered two", _gottwoRedelivered);
+ _gottwo = true;
+ }
+ }
+
+ verifyMessages(_consumer.receive(1000));
+ }
+
+ /**
+ * This test sends two messages receives on of them but doesn't ack it.
+ * The consumer is then closed
+ * the first message should be returned as redelivered.
+ * the second message should be delivered normally.
+ * @throws Exception
+ */
+ public void testSend2ThenCloseAfter1andTryAgain() throws Exception
+ {
+ assertTrue("session is not transacted", _session.getTransacted());
+ assertTrue("session is not transacted", _pubSession.getTransacted());
+
+ _logger.info("sending two test messages");
+ _publisher.send(_pubSession.createTextMessage("1"));
+ _publisher.send(_pubSession.createTextMessage("2"));
+ _pubSession.commit();
+
+ _logger.info("getting test message");
+ Message result = _consumer.receive(5000);
+
+ assertNotNull("Message received should not be null", result);
+ assertEquals("1", ((TextMessage) result).getText());
+ assertTrue("Messasge is marked as redelivered" + result, !result.getJMSRedelivered());
+
+ _logger.info("Closing Consumer");
+
+ _consumer.close();
+
+ _logger.info("Creating New consumer");
+ _consumer = _session.createConsumer(_jmsQueue);
+
+ _logger.info("receiving result");
+
+
+ // Message 2 may be marked as redelivered if it was prefetched.
+ result = _consumer.receive(5000);
+ assertNotNull("Second message was not consumed, but is gone", result);
+
+ // The first message back will be 2, message 1 has been received but not committed
+ // Closing the consumer does not commit the session.
+
+ // if this is message 1 then it should be marked as redelivered
+ if("1".equals(((TextMessage) result).getText()))
+ {
+ fail("First message was recieved again");
+ }
+
+ result = _consumer.receive(1000);
+ assertNull("test message should be null:" + result, result);
+
+ _session.commit();
+ }
+
+ public void testPutThenRollbackThenGet() throws Exception
+ {
+ assertTrue("session is not transacted", _session.getTransacted());
+ assertTrue("session is not transacted", _pubSession.getTransacted());
+
+ _logger.info("sending test message");
+ String MESSAGE_TEXT = "testPutThenRollbackThenGet";
+
+ _publisher.send(_pubSession.createTextMessage(MESSAGE_TEXT));
+ _pubSession.commit();
+
+ assertNotNull(_consumer.receive(1000));
+
+ _publisher.send(_pubSession.createTextMessage(MESSAGE_TEXT));
+
+ _logger.info("rolling back");
+ _pubSession.rollback();
+
+ _logger.info("receiving result");
+ Message result = _consumer.receive(1000);
+ assertNull("test message was put and rolled back, but is still present", result);
+
+ _publisher.send(_pubSession.createTextMessage(MESSAGE_TEXT));
+
+ _pubSession.commit();
+
+ assertNotNull(_consumer.receive(100));
+
+ _session.commit();
+ }
+
+ /**
+ * Qpid-1163
+ * Check that when commt is called inside onMessage then
+ * the last message is nor redelivered.
+ *
+ * @throws Exception
+ */
+ public void testCommitWhithinOnMessage() throws Exception
+ {
+ Queue queue = (Queue) getInitialContext().lookup("queue");
+ // create a consumer
+ MessageConsumer cons = _session.createConsumer(queue);
+ MessageProducer prod = _session.createProducer(queue);
+ Message message = _session.createTextMessage("Message");
+ message.setJMSCorrelationID("m1");
+ prod.send(message);
+ _session.commit();
+ _logger.info("Sent message to queue");
+ CountDownLatch cd = new CountDownLatch(1);
+ cons.setMessageListener(new CommitWhithinOnMessageListener(cd));
+ conn.start();
+ cd.await(30, TimeUnit.SECONDS);
+ if( cd.getCount() > 0 )
+ {
+ fail("Did not received message");
+ }
+ // Check that the message has been dequeued
+ _session.close();
+ conn.close();
+ conn = (AMQConnection) getConnection("guest", "guest");
+ conn.start();
+ Session session = conn.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+ cons = session.createConsumer(queue);
+ message = cons.receiveNoWait();
+ if(message != null)
+ {
+ if(message.getJMSCorrelationID().equals("m1"))
+ {
+ fail("received message twice");
+ }
+ else
+ {
+ fail("queue should have been empty, received message: " + message);
+ }
+ }
+ }
+
+ private class CommitWhithinOnMessageListener implements MessageListener
+ {
+ private CountDownLatch _cd;
+ private CommitWhithinOnMessageListener(CountDownLatch cd)
+ {
+ _cd = cd;
+ }
+ public void onMessage(Message message)
+ {
+ try
+ {
+ _logger.info("received message " + message);
+ assertEquals("Wrong message received", message.getJMSCorrelationID(), "m1");
+ _logger.info("commit session");
+ _session.commit();
+ _cd.countDown();
+ }
+ catch (JMSException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactedTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactedTest.java
new file mode 100644
index 0000000000..045deab052
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactedTest.java
@@ -0,0 +1,348 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.transacted;
+
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.AMQSession;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.jms.Session;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.TextMessage;
+
+public class TransactedTest extends QpidBrokerTestCase
+{
+ private AMQQueue queue1;
+
+ private AMQConnection con;
+ private Session session;
+ private MessageConsumer consumer1;
+ private MessageProducer producer2;
+
+ private AMQConnection prepCon;
+ private Session prepSession;
+ private MessageProducer prepProducer1;
+
+ private AMQConnection testCon;
+ private Session testSession;
+ private MessageConsumer testConsumer1;
+ private MessageConsumer testConsumer2;
+ private static final Logger _logger = LoggerFactory.getLogger(TransactedTest.class);
+
+ protected void setUp() throws Exception
+ {
+ try
+ {
+ super.setUp();
+ _logger.info("Create Connection");
+ con = (AMQConnection) getConnection("guest", "guest");
+ _logger.info("Create Session");
+ session = con.createSession(true, Session.SESSION_TRANSACTED);
+ _logger.info("Create Q1");
+ queue1 = new AMQQueue(session.getDefaultQueueExchangeName(), new AMQShortString("Q1"),
+ new AMQShortString("Q1"), false, true);
+ _logger.info("Create Q2");
+ AMQQueue queue2 = new AMQQueue(session.getDefaultQueueExchangeName(), new AMQShortString("Q2"), false);
+
+ _logger.info("Create Consumer of Q1");
+ consumer1 = session.createConsumer(queue1);
+ // Dummy just to create the queue.
+ _logger.info("Create Consumer of Q2");
+ MessageConsumer consumer2 = session.createConsumer(queue2);
+ _logger.info("Close Consumer of Q2");
+ consumer2.close();
+
+ _logger.info("Create producer to Q2");
+ producer2 = session.createProducer(queue2);
+
+ _logger.info("Start Connection");
+ con.start();
+
+ _logger.info("Create prep connection");
+ prepCon = (AMQConnection) getConnection("guest", "guest");
+
+ _logger.info("Create prep session");
+ prepSession = prepCon.createSession(false, AMQSession.AUTO_ACKNOWLEDGE);
+
+ _logger.info("Create prep producer to Q1");
+ prepProducer1 = prepSession.createProducer(queue1);
+
+ _logger.info("Create prep connection start");
+ prepCon.start();
+
+ _logger.info("Create test connection");
+ testCon = (AMQConnection) getConnection("guest", "guest");
+ _logger.info("Create test session");
+ testSession = testCon.createSession(false, AMQSession.AUTO_ACKNOWLEDGE);
+ _logger.info("Create test consumer of q2");
+ testConsumer2 = testSession.createConsumer(queue2);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ stopBroker();
+ throw e;
+ }
+ }
+
+ protected void tearDown() throws Exception
+ {
+ try
+ {
+ _logger.info("Close connection");
+ con.close();
+ _logger.info("Close test connection");
+ testCon.close();
+ _logger.info("Close prep connection");
+ prepCon.close();
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ finally
+ {
+ super.tearDown();
+ }
+ }
+
+ public void testCommit() throws Exception
+ {
+ try
+ {
+// add some messages
+ _logger.info("Send prep A");
+ prepProducer1.send(prepSession.createTextMessage("A"));
+ _logger.info("Send prep B");
+ prepProducer1.send(prepSession.createTextMessage("B"));
+ _logger.info("Send prep C");
+ prepProducer1.send(prepSession.createTextMessage("C"));
+
+ // send and receive some messages
+ _logger.info("Send X to Q2");
+ producer2.send(session.createTextMessage("X"));
+ _logger.info("Send Y to Q2");
+ producer2.send(session.createTextMessage("Y"));
+ _logger.info("Send Z to Q2");
+ producer2.send(session.createTextMessage("Z"));
+
+ _logger.info("Read A from Q1");
+ expect("A", consumer1.receive(1000));
+ _logger.info("Read B from Q1");
+ expect("B", consumer1.receive(1000));
+ _logger.info("Read C from Q1");
+ expect("C", consumer1.receive(1000));
+
+ // commit
+ _logger.info("session commit");
+ session.commit();
+ _logger.info("Start test Connection");
+ testCon.start();
+
+ // ensure sent messages can be received and received messages are gone
+ _logger.info("Read X from Q2");
+ expect("X", testConsumer2.receive(1000));
+ _logger.info("Read Y from Q2");
+ expect("Y", testConsumer2.receive(1000));
+ _logger.info("Read Z from Q2");
+ expect("Z", testConsumer2.receive(1000));
+
+ _logger.info("create test session on Q1");
+ testConsumer1 = testSession.createConsumer(queue1);
+ _logger.info("Read null from Q1");
+ assertTrue(null == testConsumer1.receive(1000));
+ _logger.info("Read null from Q2");
+ assertTrue(null == testConsumer2.receive(1000));
+ }
+ catch (Throwable e)
+ {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ }
+
+ public void testRollback() throws Exception
+ {
+ try
+ {
+// add some messages
+ _logger.info("Send prep RB_A");
+ prepProducer1.send(prepSession.createTextMessage("RB_A"));
+ _logger.info("Send prep RB_B");
+ prepProducer1.send(prepSession.createTextMessage("RB_B"));
+ _logger.info("Send prep RB_C");
+ prepProducer1.send(prepSession.createTextMessage("RB_C"));
+
+ _logger.info("Sending RB_X RB_Y RB_Z");
+ producer2.send(session.createTextMessage("RB_X"));
+ producer2.send(session.createTextMessage("RB_Y"));
+ producer2.send(session.createTextMessage("RB_Z"));
+ _logger.info("Receiving RB_A RB_B");
+ expect("RB_A", consumer1.receive(1000));
+ expect("RB_B", consumer1.receive(1000));
+ // Don't consume 'RB_C' leave it in the prefetch cache to ensure rollback removes it.
+ // Quick sleep to ensure 'RB_C' gets pre-fetched
+ Thread.sleep(500);
+
+ // rollback
+ _logger.info("rollback");
+ session.rollback();
+
+ _logger.info("Receiving RB_A RB_B RB_C");
+ // ensure sent messages are not visible and received messages are requeued
+ expect("RB_A", consumer1.receive(1000), true);
+ expect("RB_B", consumer1.receive(1000), true);
+ expect("RB_C", consumer1.receive(1000), true);
+ _logger.info("Starting new connection");
+ testCon.start();
+ testConsumer1 = testSession.createConsumer(queue1);
+ _logger.info("Testing we have no messages left");
+ assertTrue(null == testConsumer1.receive(1000));
+ assertTrue(null == testConsumer2.receive(1000));
+
+ session.commit();
+
+ _logger.info("Testing we have no messages left after commit");
+ assertTrue(null == testConsumer1.receive(1000));
+ assertTrue(null == testConsumer2.receive(1000));
+ }
+ catch (Throwable e)
+ {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ }
+
+ public void testResendsMsgsAfterSessionClose() throws Exception
+ {
+ try
+ {
+ AMQConnection con = (AMQConnection) getConnection("guest", "guest");
+
+ Session consumerSession = con.createSession(true, Session.SESSION_TRANSACTED);
+ AMQQueue queue3 = new AMQQueue(consumerSession.getDefaultQueueExchangeName(), new AMQShortString("Q3"), false);
+ MessageConsumer consumer = consumerSession.createConsumer(queue3);
+
+ AMQConnection con2 = (AMQConnection) getConnection("guest", "guest");
+ Session producerSession = con2.createSession(true, Session.SESSION_TRANSACTED);
+ MessageProducer producer = producerSession.createProducer(queue3);
+
+ _logger.info("Sending four messages");
+ producer.send(producerSession.createTextMessage("msg1"));
+ producer.send(producerSession.createTextMessage("msg2"));
+ producer.send(producerSession.createTextMessage("msg3"));
+ producer.send(producerSession.createTextMessage("msg4"));
+
+ producerSession.commit();
+
+ _logger.info("Starting connection");
+ con.start();
+ TextMessage tm = (TextMessage) consumer.receive();
+ assertNotNull(tm);
+ assertEquals("msg1", tm.getText());
+
+ consumerSession.commit();
+
+ _logger.info("Received and committed first message");
+ tm = (TextMessage) consumer.receive(1000);
+ assertNotNull(tm);
+ assertEquals("msg2", tm.getText());
+
+ tm = (TextMessage) consumer.receive(1000);
+ assertNotNull(tm);
+ assertEquals("msg3", tm.getText());
+
+ tm = (TextMessage) consumer.receive(1000);
+ assertNotNull(tm);
+ assertEquals("msg4", tm.getText());
+
+ _logger.info("Received all four messages. Closing connection with three outstanding messages");
+
+ consumerSession.close();
+
+ consumerSession = con.createSession(true, Session.SESSION_TRANSACTED);
+
+ consumer = consumerSession.createConsumer(queue3);
+
+ // no ack for last three messages so when I call recover I expect to get three messages back
+ tm = (TextMessage) consumer.receive(3000);
+ assertNotNull(tm);
+ assertEquals("msg2", tm.getText());
+ assertTrue("Message is not redelivered", tm.getJMSRedelivered());
+
+ tm = (TextMessage) consumer.receive(3000);
+ assertNotNull(tm);
+ assertEquals("msg3", tm.getText());
+ assertTrue("Message is not redelivered", tm.getJMSRedelivered());
+
+ tm = (TextMessage) consumer.receive(3000);
+ assertNotNull(tm);
+ assertEquals("msg4", tm.getText());
+ assertTrue("Message is not redelivered", tm.getJMSRedelivered());
+
+ _logger.info("Received redelivery of three messages. Committing");
+
+ consumerSession.commit();
+
+ _logger.info("Called commit");
+
+ tm = (TextMessage) consumer.receive(1000);
+ assertNull(tm);
+
+ _logger.info("No messages redelivered as is expected");
+
+ con.close();
+ con2.close();
+ }
+ catch (Throwable e)
+ {
+ e.printStackTrace();
+ fail(e.getMessage());
+ }
+ }
+
+ private void expect(String text, Message msg) throws JMSException
+ {
+ expect(text, msg, false);
+ }
+
+ private void expect(String text, Message msg, boolean requeued) throws JMSException
+ {
+ assertNotNull("Message should not be null", msg);
+ assertTrue("Message should be a text message", msg instanceof TextMessage);
+ assertEquals("Message content does not match expected", text, ((TextMessage) msg).getText());
+ assertEquals("Message should " + (requeued ? "" : "not") + " be requeued", requeued, msg.getJMSRedelivered());
+ }
+
+ public static junit.framework.Test suite()
+ {
+ return new junit.framework.TestSuite(TransactedTest.class);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutConfigurationTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutConfigurationTest.java
new file mode 100644
index 0000000000..36bac3b715
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutConfigurationTest.java
@@ -0,0 +1,82 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.transacted;
+
+/**
+ * This verifies that changing the {@code transactionTimeout} configuration will alter
+ * the behaviour of the transaction open and idle logging, and that when the connection
+ * will be closed.
+ */
+public class TransactionTimeoutConfigurationTest extends TransactionTimeoutTestCase
+{
+ @Override
+ protected void configure() throws Exception
+ {
+ // Setup housekeeping every second
+ setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".housekeeping.expiredMessageCheckPeriod", "100");
+
+ // Set transaction timout properties.
+ setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.openWarn", "200");
+ setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.openClose", "1000");
+ setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.idleWarn", "100");
+ setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.idleClose", "500");
+ }
+
+ public void testProducerIdleCommit() throws Exception
+ {
+ try
+ {
+ send(5, 0);
+
+ sleep(2.0f);
+
+ _psession.commit();
+ fail("should fail");
+ }
+ catch (Exception e)
+ {
+ _exception = e;
+ }
+
+ monitor(5, 0);
+
+ check(IDLE);
+ }
+
+ public void testProducerOpenCommit() throws Exception
+ {
+ try
+ {
+ send(5, 0.3f);
+
+ _psession.commit();
+ fail("should fail");
+ }
+ catch (Exception e)
+ {
+ _exception = e;
+ }
+
+ monitor(6, 3);
+
+ check(OPEN);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutDisabledTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutDisabledTest.java
new file mode 100644
index 0000000000..71b89bf911
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutDisabledTest.java
@@ -0,0 +1,72 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.transacted;
+
+/**
+ * This verifies that the default behaviour is not to time out transactions.
+ */
+public class TransactionTimeoutDisabledTest extends TransactionTimeoutTestCase
+{
+ @Override
+ protected void configure() throws Exception
+ {
+ // Setup housekeeping every second
+ setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".housekeeping.expiredMessageCheckPeriod", "100");
+ }
+
+ public void testProducerIdleCommit() throws Exception
+ {
+ try
+ {
+ send(5, 0);
+
+ sleep(2.0f);
+
+ _psession.commit();
+ }
+ catch (Exception e)
+ {
+ fail("Should have succeeded");
+ }
+
+ assertTrue("Listener should not have received exception", _caught.getCount() == 1);
+
+ monitor(0, 0);
+ }
+
+ public void testProducerOpenCommit() throws Exception
+ {
+ try
+ {
+ send(5, 0.3f);
+
+ _psession.commit();
+ }
+ catch (Exception e)
+ {
+ fail("Should have succeeded");
+ }
+
+ assertTrue("Listener should not have received exception", _caught.getCount() == 1);
+
+ monitor(0, 0);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutTest.java
new file mode 100644
index 0000000000..c912d6a323
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutTest.java
@@ -0,0 +1,335 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.transacted;
+
+/**
+ * This tests the behaviour of transactional sessions when the {@code transactionTimeout} configuration
+ * is set for a virtual host.
+ *
+ * A producer that is idle for too long or open for too long will have its connection closed and
+ * any further operations will fail with a 408 resource timeout exception. Consumers will not
+ * be affected by the transaction timeout configuration.
+ */
+public class TransactionTimeoutTest extends TransactionTimeoutTestCase
+{
+ public void testProducerIdle() throws Exception
+ {
+ try
+ {
+ sleep(2.0f);
+
+ _psession.commit();
+ }
+ catch (Exception e)
+ {
+ fail("Should have succeeded");
+ }
+
+ assertTrue("Listener should not have received exception", _caught.getCount() == 1);
+
+ monitor(0, 0);
+ }
+
+ public void testProducerIdleCommit() throws Exception
+ {
+ try
+ {
+ send(5, 0);
+
+ sleep(2.0f);
+
+ _psession.commit();
+ fail("should fail");
+ }
+ catch (Exception e)
+ {
+ _exception = e;
+ }
+
+ monitor(5, 0);
+
+ check(IDLE);
+ }
+
+ public void testProducerOpenCommit() throws Exception
+ {
+ try
+ {
+ send(6, 0.5f);
+
+ _psession.commit();
+ fail("should fail");
+ }
+ catch (Exception e)
+ {
+ _exception = e;
+ }
+
+ monitor(0, 10);
+
+ check(OPEN);
+ }
+
+ public void testProducerIdleCommitTwice() throws Exception
+ {
+ try
+ {
+ send(5, 0);
+
+ sleep(1.0f);
+
+ _psession.commit();
+
+ send(5, 0);
+
+ sleep(2.0f);
+
+ _psession.commit();
+ fail("should fail");
+ }
+ catch (Exception e)
+ {
+ _exception = e;
+ }
+
+ monitor(10, 0);
+
+ check(IDLE);
+ }
+
+ public void testProducerOpenCommitTwice() throws Exception
+ {
+ try
+ {
+ send(5, 0);
+
+ sleep(1.0f);
+
+ _psession.commit();
+
+ send(6, 0.5f);
+
+ _psession.commit();
+ fail("should fail");
+ }
+ catch (Exception e)
+ {
+ _exception = e;
+ }
+
+ // the presistent store generates more idle messages?
+ monitor(isBrokerStorePersistent() ? 10 : 5, 10);
+
+ check(OPEN);
+ }
+
+ public void testProducerIdleRollback() throws Exception
+ {
+ try
+ {
+ send(5, 0);
+
+ sleep(2.0f);
+
+ _psession.rollback();
+ fail("should fail");
+ }
+ catch (Exception e)
+ {
+ _exception = e;
+ }
+
+ monitor(5, 0);
+
+ check(IDLE);
+ }
+
+ public void testProducerIdleRollbackTwice() throws Exception
+ {
+ try
+ {
+ send(5, 0);
+
+ sleep(1.0f);
+
+ _psession.rollback();
+
+ send(5, 0);
+
+ sleep(2.0f);
+
+ _psession.rollback();
+ fail("should fail");
+ }
+ catch (Exception e)
+ {
+ _exception = e;
+ }
+
+ monitor(10, 0);
+
+ check(IDLE);
+ }
+
+ public void testConsumerCommitClose() throws Exception
+ {
+ try
+ {
+ send(1, 0);
+
+ _psession.commit();
+
+ expect(1, 0);
+
+ _csession.commit();
+
+ sleep(3.0f);
+
+ _csession.close();
+ }
+ catch (Exception e)
+ {
+ fail("should have succeeded: " + e.getMessage());
+ }
+
+ assertTrue("Listener should not have received exception", _caught.getCount() == 1);
+
+ monitor(0, 0);
+ }
+
+ public void testConsumerIdleReceiveCommit() throws Exception
+ {
+ try
+ {
+ send(1, 0);
+
+ _psession.commit();
+
+ sleep(2.0f);
+
+ expect(1, 0);
+
+ sleep(2.0f);
+
+ _csession.commit();
+ }
+ catch (Exception e)
+ {
+ fail("Should have succeeded");
+ }
+
+ assertTrue("Listener should not have received exception", _caught.getCount() == 1);
+
+ monitor(0, 0);
+ }
+
+ public void testConsumerIdleCommit() throws Exception
+ {
+ try
+ {
+ send(1, 0);
+
+ _psession.commit();
+
+ expect(1, 0);
+
+ sleep(2.0f);
+
+ _csession.commit();
+ }
+ catch (Exception e)
+ {
+ fail("Should have succeeded");
+ }
+
+ assertTrue("Listener should not have received exception", _caught.getCount() == 1);
+
+ monitor(0, 0);
+ }
+
+ public void testConsumerIdleRollback() throws Exception
+ {
+ try
+ {
+ send(1, 0);
+
+ _psession.commit();
+
+ expect(1, 0);
+
+ sleep(2.0f);
+
+ _csession.rollback();
+ }
+ catch (Exception e)
+ {
+ fail("Should have succeeded");
+ }
+
+ assertTrue("Listener should not have received exception", _caught.getCount() == 1);
+
+ monitor(0, 0);
+ }
+
+ public void testConsumerOpenCommit() throws Exception
+ {
+ try
+ {
+ send(1, 0);
+
+ _psession.commit();
+
+ sleep(3.0f);
+
+ _csession.commit();
+ }
+ catch (Exception e)
+ {
+ fail("Should have succeeded");
+ }
+
+ assertTrue("Listener should not have received exception", _caught.getCount() == 1);
+
+ monitor(0, 0);
+ }
+
+ public void testConsumerOpenRollback() throws Exception
+ {
+ try
+ {
+ send(1, 0);
+
+ _psession.commit();
+
+ sleep(3.0f);
+
+ _csession.rollback();
+ }
+ catch (Exception e)
+ {
+ fail("Should have succeeded");
+ }
+
+ assertTrue("Listener should not have received exception", _caught.getCount() == 1);
+
+ monitor(0, 0);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutTestCase.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutTestCase.java
new file mode 100644
index 0000000000..637f43fb2c
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/transacted/TransactionTimeoutTestCase.java
@@ -0,0 +1,253 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.unit.transacted;
+
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.jms.DeliveryMode;
+import javax.jms.ExceptionListener;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.TextMessage;
+
+import junit.framework.TestCase;
+
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQConnectionURL;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.framing.AMQShortString;
+import org.apache.qpid.jms.ConnectionURL;
+import org.apache.qpid.jms.Session;
+import org.apache.qpid.protocol.AMQConstant;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+import org.apache.qpid.util.LogMonitor;
+
+/**
+ * The {@link TestCase} for transaction timeout testing.
+ */
+public class TransactionTimeoutTestCase extends QpidBrokerTestCase implements ExceptionListener
+{
+ public static final String VIRTUALHOST = "test";
+ public static final String TEXT = "0123456789abcdefghiforgettherest";
+ public static final String CHN_OPEN_TXN = "CHN-1007";
+ public static final String CHN_IDLE_TXN = "CHN-1008";
+ public static final String IDLE = "Idle";
+ public static final String OPEN = "Open";
+
+ protected LogMonitor _monitor;
+ protected AMQConnection _con;
+ protected Session _psession, _csession;
+ protected Queue _queue;
+ protected MessageConsumer _consumer;
+ protected MessageProducer _producer;
+ protected CountDownLatch _caught = new CountDownLatch(1);
+ protected String _message;
+ protected Exception _exception;
+ protected AMQConstant _code;
+
+ protected void configure() throws Exception
+ {
+ // Setup housekeeping every second
+ setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".housekeeping.expiredMessageCheckPeriod", "100");
+
+ /*
+ * Set transaction timout properties. The XML in the virtualhosts configuration is as follows:
+ *
+ * <transactionTimeout>
+ * <openWarn>1000</openWarn>
+ * <openClose>2000</openClose>
+ * <idleWarn>500</idleWarn>
+ * <idleClose>1500</idleClose>
+ * </transactionTimeout>
+ */
+ setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.openWarn", "1000");
+ setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.openClose", "2000");
+ setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.idleWarn", "500");
+ setConfigurationProperty("virtualhosts.virtualhost." + VIRTUALHOST + ".transactionTimeout.idleClose", "1000");
+ }
+
+ protected void setUp() throws Exception
+ {
+ // Configure timeouts
+ configure();
+
+ // Monitor log file
+ _monitor = new LogMonitor(_outputFile);
+
+ // Start broker
+ super.setUp();
+
+ // Connect to broker
+ String broker = _broker.equals(VM) ? ("vm://:" + DEFAULT_VM_PORT) : ("tcp://localhost:" + DEFAULT_PORT);
+ ConnectionURL url = new AMQConnectionURL("amqp://guest:guest@clientid/test?brokerlist='" + broker + "'&maxprefetch='1'");
+ _con = (AMQConnection) getConnection(url);
+ _con.setExceptionListener(this);
+ _con.start();
+
+ // Create queue
+ Session qsession = _con.createSession(true, Session.SESSION_TRANSACTED);
+ AMQShortString queueName = new AMQShortString("test");
+ _queue = new AMQQueue(qsession.getDefaultQueueExchangeName(), queueName, queueName, false, true);
+ qsession.close();
+
+ // Create producer and consumer
+ producer();
+ consumer();
+ }
+
+ protected void tearDown() throws Exception
+ {
+ try
+ {
+ _con.close();
+ }
+ finally
+ {
+ super.tearDown();
+ }
+ }
+
+ /**
+ * Create a transacted persistent message producer session.
+ */
+ protected void producer() throws Exception
+ {
+ _psession = _con.createSession(true, Session.SESSION_TRANSACTED);
+ _producer = _psession.createProducer(_queue);
+ _producer.setDeliveryMode(DeliveryMode.PERSISTENT);
+ }
+
+ /**
+ * Create a transacted message consumer session.
+ */
+ protected void consumer() throws Exception
+ {
+ _csession = _con.createSession(true, Session.SESSION_TRANSACTED);
+ _consumer = _csession.createConsumer(_queue);
+ }
+
+ /**
+ * Send a number of messages to the queue, optionally pausing after each.
+ */
+ protected void send(int count, float delay) throws Exception
+ {
+ for (int i = 0; i < count; i++)
+ {
+ sleep(delay);
+ Message msg = _psession.createTextMessage(TEXT);
+ msg.setIntProperty("i", i);
+ _producer.send(msg);
+ }
+ }
+
+ /**
+ * Sleep for a number of seconds.
+ */
+ protected void sleep(float seconds) throws Exception
+ {
+ try
+ {
+ Thread.sleep((long) (seconds * 1000.0f));
+ }
+ catch (InterruptedException ie)
+ {
+ throw new RuntimeException("Interrupted");
+ }
+ }
+
+ /**
+ * Check for idle and open messages.
+ *
+ * Either exactly zero messages, or +-2 error accepted around the specified number.
+ */
+ protected void monitor(int idle, int open) throws Exception
+ {
+ List<String> idleMsgs = _monitor.findMatches(CHN_IDLE_TXN);
+ List<String> openMsgs = _monitor.findMatches(CHN_OPEN_TXN);
+
+ String idleErr = "Expected " + idle + " but found " + idleMsgs.size() + " txn idle messages";
+ String openErr = "Expected " + open + " but found " + openMsgs.size() + " txn open messages";
+
+ if (idle == 0)
+ {
+ assertTrue(idleErr, idleMsgs.isEmpty());
+ }
+ else
+ {
+ assertTrue(idleErr, idleMsgs.size() >= idle - 2 && idleMsgs.size() <= idle + 2);
+ }
+
+ if (open == 0)
+ {
+ assertTrue(openErr, openMsgs.isEmpty());
+ }
+ else
+ {
+ assertTrue(openErr, openMsgs.size() >= open - 2 && openMsgs.size() <= open + 2);
+ }
+ }
+
+ /**
+ * Receive a number of messages, optionally pausing after each.
+ */
+ protected void expect(int count, float delay) throws Exception
+ {
+ for (int i = 0; i < count; i++)
+ {
+ sleep(delay);
+ Message msg = _consumer.receive(1000);
+ assertNotNull("Message should not be null", msg);
+ assertTrue("Message should be a text message", msg instanceof TextMessage);
+ assertEquals("Message content does not match expected", TEXT, ((TextMessage) msg).getText());
+ assertEquals("Message order is incorrect", i, msg.getIntProperty("i"));
+ }
+ }
+
+ /**
+ * Checks that the correct exception was thrown and was received
+ * by the listener with a 506 error code.
+ */
+ protected void check(String reason)throws InterruptedException
+ {
+ assertTrue("Should have caught exception in listener", _caught.await(1, TimeUnit.SECONDS));
+ assertNotNull("Should have thrown exception to client", _exception);
+ assertTrue("Exception message should contain '" + reason + "': " + _message, _message.contains(reason + " transaction timed out"));
+ assertNotNull("Exception should have an error code", _code);
+ assertEquals("Error code should be 506", AMQConstant.RESOURCE_ERROR, _code);
+ }
+
+ /** @see javax.jms.ExceptionListener#onException(javax.jms.JMSException) */
+ public void onException(JMSException jmse)
+ {
+ _caught.countDown();
+ _message = jmse.getLinkedException().getMessage();
+ if (jmse.getLinkedException() instanceof AMQException)
+ {
+ _code = ((AMQException) jmse.getLinkedException()).getErrorCode();
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/xa/AbstractXATestCase.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/xa/AbstractXATestCase.java
new file mode 100644
index 0000000000..f39f640d04
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/xa/AbstractXATestCase.java
@@ -0,0 +1,132 @@
+/* Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.qpid.test.unit.xa;
+
+import org.apache.qpid.dtx.XidImpl;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import javax.transaction.xa.Xid;
+import javax.transaction.xa.XAResource;
+import javax.jms.*;
+import java.util.Random;
+
+/**
+ *
+ *
+ */
+public abstract class AbstractXATestCase extends QpidBrokerTestCase
+{
+ protected static final String _sequenceNumberPropertyName = "seqNumber";
+
+ /**
+ * the xaResource associated with the standard session
+ */
+ protected static XAResource _xaResource = null;
+
+ /**
+ * producer registered with the standard session
+ */
+ protected static MessageProducer _producer = null;
+
+ /**
+ * consumer registered with the standard session
+ */
+ protected static MessageConsumer _consumer = null;
+
+ /**
+ * a standard message
+ */
+ protected static TextMessage _message = null;
+
+ /**
+ * xid counter
+ */
+ private static int _xidCounter = (new Random()).nextInt(1000000);
+
+
+ protected void setUp() throws Exception
+ {
+ super.setUp();
+ init();
+ }
+
+ public abstract void init() throws Exception;
+
+
+
+ /**
+ * construct a new Xid
+ *
+ * @return a new Xid
+ */
+ protected Xid getNewXid()
+ {
+ byte[] branchQualifier;
+ byte[] globalTransactionID;
+ int format = _xidCounter;
+ String branchQualifierSt = "branchQualifier" + _xidCounter;
+ String globalTransactionIDSt = "globalTransactionID" + _xidCounter;
+ branchQualifier = branchQualifierSt.getBytes();
+ globalTransactionID = globalTransactionIDSt.getBytes();
+ _xidCounter++;
+ return new XidImpl(branchQualifier, format, globalTransactionID);
+ }
+
+ public void init(XASession session, Destination destination)
+ {
+ // get the xaResource
+ try
+ {
+ _xaResource = session.getXAResource();
+ }
+ catch (Exception e)
+ {
+ fail("cannot access the xa resource: " + e.getMessage());
+ }
+ // create standard producer
+ try
+ {
+ _producer = session.createProducer(destination);
+ _producer.setDeliveryMode(DeliveryMode.PERSISTENT);
+ }
+ catch (JMSException e)
+ {
+ e.printStackTrace();
+ fail("cannot create message producer: " + e.getMessage());
+ }
+ // create standard consumer
+ try
+ {
+ _consumer = session.createConsumer(destination);
+ }
+ catch (JMSException e)
+ {
+ fail("cannot create message consumer: " + e.getMessage());
+ }
+ // create a standard message
+ try
+ {
+ _message = session.createTextMessage();
+ _message.setText("test XA");
+ }
+ catch (JMSException e)
+ {
+ fail("cannot create standard message: " + e.getMessage());
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/xa/FaultTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/xa/FaultTest.java
new file mode 100644
index 0000000000..47705f8105
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/xa/FaultTest.java
@@ -0,0 +1,409 @@
+package org.apache.qpid.test.unit.xa;
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.*;
+import javax.transaction.xa.Xid;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.XAException;
+
+import junit.framework.TestSuite;
+
+
+public class FaultTest extends AbstractXATestCase
+{
+ /* this clas logger */
+ private static final Logger _logger = LoggerFactory.getLogger(FaultTest.class);
+
+ /**
+ * the queue use by all the tests
+ */
+ private static Queue _queue = null;
+ /**
+ * the queue connection factory used by all tests
+ */
+ private static XAQueueConnectionFactory _queueFactory = null;
+
+ /**
+ * standard xa queue connection
+ */
+ private static XAQueueConnection _xaqueueConnection = null;
+
+ /**
+ * standard xa queue connection
+ */
+ private static QueueConnection _queueConnection = null;
+
+
+ /**
+ * standard queue session created from the standard connection
+ */
+ private static QueueSession _nonXASession = null;
+
+ /**
+ * the queue name
+ */
+ private static final String QUEUENAME = "xaQueue";
+
+ /** ----------------------------------------------------------------------------------- **/
+ /**
+ * ----------------------------- JUnit support ----------------------------------------- *
+ */
+
+ /**
+ * Gets the test suite tests
+ *
+ * @return the test suite tests
+ */
+ public static TestSuite getSuite()
+ {
+ return new TestSuite(QueueTest.class);
+ }
+
+ /**
+ * Run the test suite.
+ *
+ * @param args Any command line arguments specified to this class.
+ */
+ public static void main(String args[])
+ {
+ junit.textui.TestRunner.run(getSuite());
+ }
+
+ public void tearDown() throws Exception
+ {
+ if (!isBroker08())
+ {
+ _xaqueueConnection.close();
+ _queueConnection.close();
+ }
+ super.tearDown();
+ }
+
+ /**
+ * Initialize standard actors
+ */
+ public void init() throws Exception
+ {
+ if (!isBroker08())
+ {
+ _queue = (Queue) getInitialContext().lookup(QUEUENAME);
+ _queueFactory = getConnectionFactory();
+ _xaqueueConnection = _queueFactory.createXAQueueConnection("guest", "guest");
+ XAQueueSession session = _xaqueueConnection.createXAQueueSession();
+ _queueConnection = _queueFactory.createQueueConnection();
+ _nonXASession = _queueConnection.createQueueSession(true, Session.AUTO_ACKNOWLEDGE);
+ init(session, _queue);
+ }
+ }
+
+ /** -------------------------------------------------------------------------------------- **/
+ /** ----------------------------- Test Suite -------------------------------------------- **/
+ /** -------------------------------------------------------------------------------------- **/
+
+ /**
+ * Strategy:
+ * Invoke start twice with the same xid on an XA resource.
+ * Check that the second
+ * invocation is throwing the expected XA exception.
+ */
+ public void testSameXID() throws Exception
+ {
+ Xid xid = getNewXid();
+ _xaResource.start(xid, XAResource.TMNOFLAGS);
+ // we now exepct this operation to fail
+ try
+ {
+ _xaResource.start(xid, XAResource.TMNOFLAGS);
+ fail("We managed to start a transaction with the same xid");
+ }
+ catch (XAException e)
+ {
+ assertEquals("Wrong error code: ", XAException.XAER_DUPID, e.errorCode);
+ }
+ }
+
+ /**
+ * Strategy:
+ * Invoke start on a XA resource with flag other than TMNOFLAGS, TMJOIN, or TMRESUME.
+ * Check that a XA Exception is thrown.
+ */
+ public void testWrongStartFlag()
+ {
+ Xid xid = getNewXid();
+ try
+ {
+ _xaResource.start(xid, XAResource.TMONEPHASE);
+ fail("We managed to start a transaction with a wrong flag");
+ }
+ catch (XAException e)
+ {
+ assertEquals("Wrong error code: ", XAException.XAER_INVAL, e.errorCode);
+ }
+ }
+
+ /**
+ * Strategy:
+ * Check that a XA exception is thrown when:
+ * A non started xid is ended
+ */
+ public void testEnd()
+ {
+ Xid xid = getNewXid();
+ try
+ {
+ _xaResource.end(xid, XAResource.TMSUCCESS);
+ fail("We managed to end a transaction before it is started");
+ }
+ catch (XAException e)
+ {
+ assertEquals("Wrong error code: ", XAException.XAER_PROTO, e.errorCode);
+ }
+ }
+
+
+ /**
+ * Strategy:
+ * Check that a XA exception is thrown when:
+ * Call forget on an unknown xid
+ * call forget on a started xid
+ * A non started xid is prepared
+ * A non ended xis is prepared
+ */
+ public void testForget()
+ {
+ Xid xid = getNewXid();
+ try
+ {
+ _xaResource.forget(xid);
+ fail("We managed to forget an unknown xid");
+ }
+ catch (XAException e)
+ {
+ // assertEquals("Wrong error code: ", XAException.XAER_NOTA, e.errorCode);
+ }
+ xid = getNewXid();
+ try
+ {
+ _xaResource.start(xid, XAResource.TMNOFLAGS);
+ _xaResource.forget(xid);
+ fail("We managed to forget a started xid");
+ }
+ catch (XAException e)
+ {
+ assertEquals("Wrong error code: ", XAException.XAER_PROTO, e.errorCode);
+ }
+ }
+
+ /**
+ * Strategy:
+ * Check that a XA exception is thrown when:
+ * A non started xid is prepared
+ * A non ended xid is prepared
+ */
+ public void testPrepare()
+ {
+ Xid xid = getNewXid();
+ try
+ {
+ _xaResource.prepare(xid);
+ fail("We managed to prepare an unknown xid");
+ }
+ catch (XAException e)
+ {
+ assertEquals("Wrong error code: ", XAException.XAER_NOTA, e.errorCode);
+ }
+ xid = getNewXid();
+ try
+ {
+ _xaResource.start(xid, XAResource.TMNOFLAGS);
+ _xaResource.prepare(xid);
+ fail("We managed to prepare a started xid");
+ }
+ catch (XAException e)
+ {
+ assertEquals("Wrong error code: ", XAException.XAER_PROTO, e.errorCode);
+ }
+ }
+
+ /**
+ * Strategy:
+ * Check that the expected XA exception is thrown when:
+ * A non started xid is committed
+ * A non ended xid is committed
+ * A non prepared xid is committed with one phase set to false.
+ * A prepared xid is committed with one phase set to true.
+ */
+ public void testCommit() throws Exception
+ {
+ Xid xid = getNewXid();
+ try
+ {
+ _xaResource.commit(xid, true);
+ fail("We managed to commit an unknown xid");
+ }
+ catch (XAException e)
+ {
+ assertEquals("Wrong error code: ", XAException.XAER_NOTA, e.errorCode);
+ }
+ xid = getNewXid();
+ try
+ {
+ _xaResource.start(xid, XAResource.TMNOFLAGS);
+ _xaResource.commit(xid, true);
+ fail("We managed to commit a not ended xid");
+ }
+ catch (XAException e)
+ {
+ assertEquals("Wrong error code: ", XAException.XAER_PROTO, e.errorCode);
+ }
+ xid = getNewXid();
+ try
+ {
+ _xaResource.start(xid, XAResource.TMNOFLAGS);
+ _xaResource.end(xid, XAResource.TMSUCCESS);
+ _xaResource.commit(xid, false);
+ fail("We managed to commit a not prepared xid");
+ }
+ catch (XAException e)
+ {
+ assertEquals("Wrong error code: ", XAException.XAER_PROTO, e.errorCode);
+ }
+ xid = getNewXid();
+ try
+ {
+ _xaResource.start(xid, XAResource.TMNOFLAGS);
+ _xaResource.end(xid, XAResource.TMSUCCESS);
+ _xaResource.prepare(xid);
+ _xaResource.commit(xid, true);
+ fail("We managed to commit a prepared xid");
+ }
+ catch (XAException e)
+ {
+ assertEquals("Wrong error code: ", XAException.XAER_PROTO, e.errorCode);
+ }
+ finally
+ {
+ _xaResource.commit(xid, false);
+ }
+ }
+
+ /**
+ * Strategy:
+ * Check that the expected XA exception is thrown when:
+ * A non started xid is rolled back
+ * A non ended xid is rolled back
+ */
+ public void testRollback()
+ {
+ Xid xid = getNewXid();
+ try
+ {
+ _xaResource.rollback(xid);
+ fail("We managed to rollback an unknown xid");
+ }
+ catch (XAException e)
+ {
+ assertEquals("Wrong error code: ", XAException.XAER_NOTA, e.errorCode);
+ }
+ xid = getNewXid();
+ try
+ {
+ _xaResource.start(xid, XAResource.TMNOFLAGS);
+ _xaResource.rollback(xid);
+ fail("We managed to rollback a not ended xid");
+ }
+ catch (XAException e)
+ {
+ assertEquals("Wrong error code: ", XAException.XAER_PROTO, e.errorCode);
+ }
+ }
+
+ /**
+ * Strategy:
+ * Check that the timeout is set correctly
+ */
+ public void testTransactionTimeoutvalue() throws Exception
+ {
+ Xid xid = getNewXid();
+ _xaResource.start(xid, XAResource.TMNOFLAGS);
+ assertEquals("Wrong timeout", _xaResource.getTransactionTimeout(), 0);
+ _xaResource.setTransactionTimeout(1000);
+ assertEquals("Wrong timeout", _xaResource.getTransactionTimeout(), 1000);
+ _xaResource.end(xid, XAResource.TMSUCCESS);
+ xid = getNewXid();
+ _xaResource.start(xid, XAResource.TMNOFLAGS);
+ assertEquals("Wrong timeout", _xaResource.getTransactionTimeout(), 1000);
+ }
+
+ /**
+ * Strategy:
+ * Check that a transaction timeout as expected
+ * - set timeout to 10ms
+ * - sleep 1000ms
+ * - call end and check that the expected exception is thrown
+ */
+ public void testTransactionTimeout() throws Exception
+ {
+ Xid xid = getNewXid();
+ try
+ {
+ _xaResource.start(xid, XAResource.TMNOFLAGS);
+ assertEquals("Wrong timeout", _xaResource.getTransactionTimeout(), 0);
+ _xaResource.setTransactionTimeout(10);
+ Thread.sleep(1000);
+ _xaResource.end(xid, XAResource.TMSUCCESS);
+ }
+ catch (XAException e)
+ {
+ assertEquals("Wrong error code: ", XAException.XA_RBTIMEOUT, e.errorCode);
+ }
+ }
+
+ /**
+ * Strategy:
+ * Set the transaction timeout to 1000
+ */
+ public void testTransactionTimeoutAfterCommit() throws Exception
+ {
+ Xid xid = getNewXid();
+
+ _xaResource.start(xid, XAResource.TMNOFLAGS);
+ _xaResource.setTransactionTimeout(1000);
+ assertEquals("Wrong timeout", 1000,_xaResource.getTransactionTimeout());
+
+ //_xaResource.prepare(xid);
+ _xaResource.end(xid, XAResource.TMSUCCESS);
+ _xaResource.commit(xid, true);
+
+ _xaResource.setTransactionTimeout(2000);
+ assertEquals("Wrong timeout", 2000,_xaResource.getTransactionTimeout());
+
+ xid = getNewXid();
+ _xaResource.start(xid, XAResource.TMNOFLAGS);
+ assertEquals("Wrong timeout", 2000, _xaResource.getTransactionTimeout());
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/xa/QueueTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/xa/QueueTest.java
new file mode 100644
index 0000000000..d2abc0eac1
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/xa/QueueTest.java
@@ -0,0 +1,657 @@
+/* Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.qpid.test.unit.xa;
+
+import javax.jms.*;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+import javax.transaction.xa.XAException;
+
+import junit.framework.TestSuite;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class QueueTest extends AbstractXATestCase
+{
+ /* this clas logger */
+ private static final Logger _logger = LoggerFactory.getLogger(QueueTest.class);
+
+ /**
+ * the queue use by all the tests
+ */
+ private static Queue _queue = null;
+ /**
+ * the queue connection factory used by all tests
+ */
+ private static XAQueueConnectionFactory _queueFactory = null;
+
+ /**
+ * standard xa queue connection
+ */
+ private static XAQueueConnection _xaqueueConnection= null;
+
+ /**
+ * standard xa queue connection
+ */
+ private static QueueConnection _queueConnection=null;
+
+
+ /**
+ * standard queue session created from the standard connection
+ */
+ private static QueueSession _nonXASession = null;
+
+ /**
+ * the queue name
+ */
+ private static final String QUEUENAME = "xaQueue";
+
+ /** ----------------------------------------------------------------------------------- **/
+ /**
+ * ----------------------------- JUnit support ----------------------------------------- *
+ */
+
+ /**
+ * Gets the test suite tests
+ *
+ * @return the test suite tests
+ */
+ public static TestSuite getSuite()
+ {
+ return new TestSuite(QueueTest.class);
+ }
+
+ /**
+ * Run the test suite.
+ *
+ * @param args Any command line arguments specified to this class.
+ */
+ public static void main(String args[])
+ {
+ junit.textui.TestRunner.run(getSuite());
+ }
+
+ public void tearDown() throws Exception
+ {
+ if (!isBroker08())
+ {
+ try
+ {
+ _xaqueueConnection.close();
+ _queueConnection.close();
+ }
+ catch (Exception e)
+ {
+ fail("Exception thrown when cleaning standard connection: " + e.getStackTrace());
+ }
+ }
+ super.tearDown();
+ }
+
+ /**
+ * Initialize standard actors
+ */
+ public void init()
+ {
+ if (!isBroker08())
+ {
+ // lookup test queue
+ try
+ {
+ _queue = (Queue) getInitialContext().lookup(QUEUENAME);
+ }
+ catch (Exception e)
+ {
+ fail("cannot lookup test queue " + e.getMessage());
+ }
+
+ // lookup connection factory
+ try
+ {
+ _queueFactory = getConnectionFactory();
+ }
+ catch (Exception e)
+ {
+ fail("enable to lookup connection factory ");
+ }
+ // create standard connection
+ try
+ {
+ _xaqueueConnection= getNewQueueXAConnection();
+ }
+ catch (JMSException e)
+ {
+ fail("cannot create queue connection: " + e.getMessage());
+ }
+ // create xa session
+ XAQueueSession session = null;
+ try
+ {
+ session = _xaqueueConnection.createXAQueueSession();
+ }
+ catch (JMSException e)
+ {
+ fail("cannot create queue session: " + e.getMessage());
+ }
+ // create a standard session
+ try
+ {
+ _queueConnection = _queueFactory.createQueueConnection();
+ _nonXASession = _queueConnection.createQueueSession(true, Session.AUTO_ACKNOWLEDGE);
+ }
+ catch (JMSException e)
+ {
+ fail("cannot create queue session: " + e.getMessage());
+ }
+ init(session, _queue);
+ }
+ }
+
+ /** -------------------------------------------------------------------------------------- **/
+ /** ----------------------------- Test Suite -------------------------------------------- **/
+ /** -------------------------------------------------------------------------------------- **/
+
+ /**
+ * Uses two transactions respectively with xid1 and xid2 that are used to send a message
+ * within xid1 and xid2. xid2 is committed and xid1 is used to receive the message that was sent within xid2.
+ * Xid is then committed and a standard transaction is used to receive the message that was sent within xid1.
+ */
+ public void testProducer()
+ {
+ if (!isBroker08())
+ {
+ _logger.debug("running testProducer");
+ Xid xid1 = getNewXid();
+ Xid xid2 = getNewXid();
+ // start the xaResource for xid1
+ try
+ {
+ _xaResource.start(xid1, XAResource.TMNOFLAGS);
+ }
+ catch (XAException e)
+ {
+ e.printStackTrace();
+ fail("cannot start the transaction with xid1: " + e.getMessage());
+ }
+ try
+ {
+ // start the connection
+ _xaqueueConnection.start();
+ // produce a message with sequence number 1
+ _message.setLongProperty(_sequenceNumberPropertyName, 1);
+ _producer.send(_message);
+ }
+ catch (JMSException e)
+ {
+ fail(" cannot send persistent message: " + e.getMessage());
+ }
+ // suspend the transaction
+ try
+ {
+ _xaResource.end(xid1, XAResource.TMSUSPEND);
+ }
+ catch (XAException e)
+ {
+ fail("Cannot end the transaction with xid1: " + e.getMessage());
+ }
+ // start the xaResource for xid2
+ try
+ {
+ _xaResource.start(xid2, XAResource.TMNOFLAGS);
+ }
+ catch (XAException e)
+ {
+ fail("cannot start the transaction with xid2: " + e.getMessage());
+ }
+ try
+ {
+ // produce a message
+ _message.setLongProperty(_sequenceNumberPropertyName, 2);
+ _producer.send(_message);
+ }
+ catch (JMSException e)
+ {
+ fail(" cannot send second persistent message: " + e.getMessage());
+ }
+ // end xid2 and start xid1
+ try
+ {
+ _xaResource.end(xid2, XAResource.TMSUCCESS);
+ _xaResource.start(xid1, XAResource.TMRESUME);
+ }
+ catch (XAException e)
+ {
+ fail("Exception when ending and starting transactions: " + e.getMessage());
+ }
+ // two phases commit transaction with xid2
+ try
+ {
+ int resPrepare = _xaResource.prepare(xid2);
+ if (resPrepare != XAResource.XA_OK)
+ {
+ fail("prepare returned: " + resPrepare);
+ }
+ _xaResource.commit(xid2, false);
+ }
+ catch (XAException e)
+ {
+ fail("Exception thrown when preparing transaction with xid2: " + e.getMessage());
+ }
+ // receive a message from queue test we expect it to be the second one
+ try
+ {
+ TextMessage message = (TextMessage) _consumer.receive(1000);
+ if (message == null)
+ {
+ fail("did not receive second message as expected ");
+ }
+ else
+ {
+ if (message.getLongProperty(_sequenceNumberPropertyName) != 2)
+ {
+ fail("receive wrong message its sequence number is: " + message
+ .getLongProperty(_sequenceNumberPropertyName));
+ }
+ }
+ }
+ catch (JMSException e)
+ {
+ fail("Exception when receiving second message: " + e.getMessage());
+ }
+ // end and one phase commit the first transaction
+ try
+ {
+ _xaResource.end(xid1, XAResource.TMSUCCESS);
+ _xaResource.commit(xid1, true);
+ }
+ catch (XAException e)
+ {
+ fail("Exception thrown when commiting transaction with xid1");
+ }
+ // We should now be able to receive the first message
+ try
+ {
+ _xaqueueConnection.close();
+ Session nonXASession = _nonXASession;
+ MessageConsumer nonXAConsumer = nonXASession.createConsumer(_queue);
+ _queueConnection.start();
+ TextMessage message1 = (TextMessage) nonXAConsumer.receive(1000);
+ if (message1 == null)
+ {
+ fail("did not receive first message as expected ");
+ }
+ else
+ {
+ if (message1.getLongProperty(_sequenceNumberPropertyName) != 1)
+ {
+ fail("receive wrong message its sequence number is: " + message1
+ .getLongProperty(_sequenceNumberPropertyName));
+ }
+ }
+ // commit that transacted session
+ nonXASession.commit();
+ // the queue should be now empty
+ message1 = (TextMessage) nonXAConsumer.receive(1000);
+ if (message1 != null)
+ {
+ fail("receive an unexpected message ");
+ }
+ }
+ catch (JMSException e)
+ {
+ fail("Exception thrown when emptying the queue: " + e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * strategy: Produce a message within Tx1 and prepare tx1. crash the server then commit tx1 and consume the message
+ */
+ public void testSendAndRecover()
+ {
+ if (!isBroker08())
+ {
+ _logger.debug("running testSendAndRecover");
+ Xid xid1 = getNewXid();
+ // start the xaResource for xid1
+ try
+ {
+ _xaResource.start(xid1, XAResource.TMNOFLAGS);
+ }
+ catch (XAException e)
+ {
+ fail("cannot start the transaction with xid1: " + e.getMessage());
+ }
+ try
+ {
+ // start the connection
+ _xaqueueConnection.start();
+ // produce a message with sequence number 1
+ _message.setLongProperty(_sequenceNumberPropertyName, 1);
+ _producer.send(_message);
+ }
+ catch (JMSException e)
+ {
+ fail(" cannot send persistent message: " + e.getMessage());
+ }
+ // suspend the transaction
+ try
+ {
+ _xaResource.end(xid1, XAResource.TMSUCCESS);
+ }
+ catch (XAException e)
+ {
+ fail("Cannot end the transaction with xid1: " + e.getMessage());
+ }
+ // prepare the transaction with xid1
+ try
+ {
+ _xaResource.prepare(xid1);
+ }
+ catch (XAException e)
+ {
+ fail("Exception when preparing xid1: " + e.getMessage());
+ }
+
+ /////// stop the server now !!
+ try
+ {
+ _logger.debug("stopping broker");
+ restartBroker();
+ init();
+ }
+ catch (Exception e)
+ {
+ fail("Exception when stopping and restarting the server");
+ }
+
+ // get the list of in doubt transactions
+ try
+ {
+ Xid[] inDoubt = _xaResource.recover(XAResource.TMSTARTRSCAN);
+ if (inDoubt == null)
+ {
+ fail("the array of in doubt transactions should not be null ");
+ }
+ // At that point we expect only two indoubt transactions:
+ if (inDoubt.length != 1)
+ {
+ fail("in doubt transaction size is diffenrent thatn 2, there are " + inDoubt.length + "in doubt transactions");
+ }
+
+ // commit them
+ for (Xid anInDoubt : inDoubt)
+ {
+ if (anInDoubt.equals(xid1))
+ {
+ System.out.println("commit xid1 ");
+ try
+ {
+ _xaResource.commit(anInDoubt, false);
+ }
+ catch (Exception e)
+ {
+ System.out.println("PB when aborted xid1");
+ }
+ }
+ else
+ {
+ fail("did not receive right xid ");
+ }
+ }
+ }
+ catch (XAException e)
+ {
+ e.printStackTrace();
+ fail("exception thrown when recovering transactions " + e.getMessage());
+ }
+ // the queue should contain the first message!
+ try
+ {
+ _xaqueueConnection.close();
+ Session nonXASession = _nonXASession;
+ MessageConsumer nonXAConsumer = nonXASession.createConsumer(_queue);
+ _queueConnection.start();
+ TextMessage message1 = (TextMessage) nonXAConsumer.receive(1000);
+
+ if (message1 == null)
+ {
+ fail("queue does not contain any message!");
+ }
+ if (message1.getLongProperty(_sequenceNumberPropertyName) != 1)
+ {
+ fail("Wrong message returned! Sequence number is " + message1
+ .getLongProperty(_sequenceNumberPropertyName));
+ }
+ nonXASession.commit();
+ }
+ catch (JMSException e)
+ {
+ fail("Exception thrown when testin that queue test is not empty: " + e.getMessage());
+ }
+ }
+ }
+
+ /**
+ * strategy: Produce a message within Tx1 and prepare tx1. Produce a standard message and consume
+ * it within tx2 and prepare tx2. Shutdown the server and get the list of in doubt transactions:
+ * we expect tx1 and tx2! Then, Tx1 is aborted and tx2 is committed so we expect the test's queue to be empty!
+ */
+ public void testRecover()
+ {
+ if (!isBroker08())
+ {
+ _logger.debug("running testRecover");
+ Xid xid1 = getNewXid();
+ Xid xid2 = getNewXid();
+ // start the xaResource for xid1
+ try
+ {
+ _xaResource.start(xid1, XAResource.TMNOFLAGS);
+ }
+ catch (XAException e)
+ {
+ fail("cannot start the transaction with xid1: " + e.getMessage());
+ }
+ try
+ {
+ // start the connection
+ _xaqueueConnection.start();
+ // produce a message with sequence number 1
+ _message.setLongProperty(_sequenceNumberPropertyName, 1);
+ _producer.send(_message);
+ }
+ catch (JMSException e)
+ {
+ fail(" cannot send persistent message: " + e.getMessage());
+ }
+ // suspend the transaction
+ try
+ {
+ _xaResource.end(xid1, XAResource.TMSUCCESS);
+ }
+ catch (XAException e)
+ {
+ fail("Cannot end the transaction with xid1: " + e.getMessage());
+ }
+ // prepare the transaction with xid1
+ try
+ {
+ _xaResource.prepare(xid1);
+ }
+ catch (XAException e)
+ {
+ fail("Exception when preparing xid1: " + e.getMessage());
+ }
+
+ // send a message using the standard session
+ try
+ {
+ Session nonXASession = _nonXASession;
+ MessageProducer nonXAProducer = nonXASession.createProducer(_queue);
+ TextMessage message2 = nonXASession.createTextMessage();
+ message2.setText("non XA ");
+ message2.setLongProperty(_sequenceNumberPropertyName, 2);
+ nonXAProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
+ nonXAProducer.send(message2);
+ // commit that transacted session
+ nonXASession.commit();
+ }
+ catch (Exception e)
+ {
+ fail("Exception thrown when emptying the queue: " + e.getMessage());
+ }
+ // start the xaResource for xid2
+ try
+ {
+ _xaResource.start(xid2, XAResource.TMNOFLAGS);
+ }
+ catch (XAException e)
+ {
+ fail("cannot start the transaction with xid1: " + e.getMessage());
+ }
+ // receive a message from queue test we expect it to be the second one
+ try
+ {
+ TextMessage message = (TextMessage) _consumer.receive(1000);
+ if (message == null || message.getLongProperty(_sequenceNumberPropertyName) != 2)
+ {
+ fail("did not receive second message as expected ");
+ }
+ }
+ catch (JMSException e)
+ {
+ fail("Exception when receiving second message: " + e.getMessage());
+ }
+ // suspend the transaction
+ try
+ {
+ _xaResource.end(xid2, XAResource.TMSUCCESS);
+ }
+ catch (XAException e)
+ {
+ fail("Cannot end the transaction with xid2: " + e.getMessage());
+ }
+ // prepare the transaction with xid1
+ try
+ {
+ _xaResource.prepare(xid2);
+ }
+ catch (XAException e)
+ {
+ fail("Exception when preparing xid2: " + e.getMessage());
+ }
+
+ /////// stop the server now !!
+ try
+ {
+ _logger.debug("stopping broker");
+ restartBroker();
+ init();
+ }
+ catch (Exception e)
+ {
+ fail("Exception when stopping and restarting the server");
+ }
+
+ // get the list of in doubt transactions
+ try
+ {
+ Xid[] inDoubt = _xaResource.recover(XAResource.TMSTARTRSCAN);
+ if (inDoubt == null)
+ {
+ fail("the array of in doubt transactions should not be null ");
+ }
+ // At that point we expect only two indoubt transactions:
+ if (inDoubt.length != 2)
+ {
+ fail("in doubt transaction size is diffenrent thatn 2, there are " + inDoubt.length + "in doubt transactions");
+ }
+
+ // commit them
+ for (Xid anInDoubt : inDoubt)
+ {
+ if (anInDoubt.equals(xid1))
+ {
+ _logger.debug("rollback xid1 ");
+ try
+ {
+ _xaResource.rollback(anInDoubt);
+ }
+ catch (Exception e)
+ {
+ System.out.println("PB when aborted xid1");
+ }
+ }
+ else if (anInDoubt.equals(xid2))
+ {
+ _logger.debug("commit xid2 ");
+ try
+ {
+ _xaResource.commit(anInDoubt, false);
+ }
+ catch (Exception e)
+ {
+ System.out.println("PB when commiting xid2");
+ }
+ }
+ }
+ }
+ catch (XAException e)
+ {
+ e.printStackTrace();
+ fail("exception thrown when recovering transactions " + e.getMessage());
+ }
+ // the queue should be empty
+ try
+ {
+ _xaqueueConnection.close();
+ Session nonXASession = _nonXASession;
+ MessageConsumer nonXAConsumer = nonXASession.createConsumer(_queue);
+ _queueConnection.start();
+ TextMessage message1 = (TextMessage) nonXAConsumer.receive(1000);
+ if (message1 != null)
+ {
+ fail("The queue is not empty! ");
+ }
+ }
+ catch (JMSException e)
+ {
+ fail("Exception thrown when testin that queue test is empty: " + e.getMessage());
+ }
+ }
+ }
+
+ /** -------------------------------------------------------------------------------------- **/
+ /** ----------------------------- Utility methods --------------------------------------- **/
+ /** -------------------------------------------------------------------------------------- **/
+
+ /**
+ * get a new queue connection
+ *
+ * @return a new queue connection
+ * @throws JMSException If the JMS provider fails to create the queue connection
+ * due to some internal error or in case of authentication failure
+ */
+ private XAQueueConnection getNewQueueXAConnection() throws JMSException
+ {
+ return _queueFactory.createXAQueueConnection("guest", "guest");
+ }
+
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/xa/TopicTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/xa/TopicTest.java
new file mode 100644
index 0000000000..99d0f0a075
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/unit/xa/TopicTest.java
@@ -0,0 +1,1711 @@
+/* Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.qpid.test.unit.xa;
+
+import javax.jms.*;
+import javax.transaction.xa.XAResource;
+import javax.transaction.xa.Xid;
+import javax.transaction.xa.XAException;
+
+import junit.framework.TestSuite;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ *
+ */
+public class TopicTest extends AbstractXATestCase
+{
+ /* this clas logger */
+ private static final Logger _logger = LoggerFactory.getLogger(TopicTest.class);
+
+ /**
+ * the topic use by all the tests
+ */
+ private static Topic _topic = null;
+
+ /**
+ * the topic connection factory used by all tests
+ */
+ private static XATopicConnectionFactory _topicFactory = null;
+
+ /**
+ * standard topic connection
+ */
+ private static XATopicConnection _topicConnection = null;
+
+ /**
+ * standard topic session created from the standard connection
+ */
+ private static XATopicSession _session = null;
+
+ private static TopicSession _nonXASession = null;
+
+ /**
+ * the topic name
+ */
+ private static final String TOPICNAME = "xaTopic";
+
+ /**
+ * Indicate that a listenere has failed
+ */
+ private static boolean _failure = false;
+
+ /** -------------------------------------------------------------------------------------- **/
+ /** ----------------------------- JUnit support ----------------------------------------- **/
+ /** -------------------------------------------------------------------------------------- **/
+
+ /**
+ * Gets the test suite tests
+ *
+ * @return the test suite tests
+ */
+ public static TestSuite getSuite()
+ {
+ return new TestSuite(TopicTest.class);
+ }
+
+ /**
+ * Run the test suite.
+ *
+ * @param args Any command line arguments specified to this class.
+ */
+ public static void main(String args[])
+ {
+ junit.textui.TestRunner.run(getSuite());
+ }
+
+ public void tearDown() throws Exception
+ {
+ if (!isBroker08())
+ {
+ try
+ {
+ _topicConnection.stop();
+ _topicConnection.close();
+ }
+ catch (Exception e)
+ {
+ fail("Exception thrown when cleaning standard connection: " + e.getStackTrace());
+ }
+ }
+ super.tearDown();
+ }
+
+ /**
+ * Initialize standard actors
+ */
+ public void init()
+ {
+ if (!isBroker08())
+ {
+ // lookup test queue
+ try
+ {
+ _topic = (Topic) getInitialContext().lookup(TOPICNAME);
+ }
+ catch (Exception e)
+ {
+ fail("cannot lookup test topic " + e.getMessage());
+ }
+ // lookup connection factory
+ try
+ {
+ _topicFactory = getConnectionFactory();
+ }
+ catch (Exception e)
+ {
+ fail("enable to lookup connection factory ");
+ }
+ // create standard connection
+ try
+ {
+ _topicConnection = getNewTopicXAConnection();
+ }
+ catch (JMSException e)
+ {
+ fail("cannot create queue connection: " + e.getMessage());
+ }
+ // create standard session
+ try
+ {
+ _session = _topicConnection.createXATopicSession();
+ }
+ catch (JMSException e)
+ {
+ fail("cannot create queue session: " + e.getMessage());
+ }
+ // create a standard session
+ try
+ {
+ _nonXASession = _topicConnection.createTopicSession(true, Session.AUTO_ACKNOWLEDGE);
+ }
+ catch (JMSException e)
+ {
+ e.printStackTrace(); //To change body of catch statement use Options | File Templates.
+ }
+ init(_session, _topic);
+ }
+ }
+
+ /** -------------------------------------------------------------------------------------- **/
+ /** ----------------------------- Test Suite -------------------------------------------- **/
+ /** -------------------------------------------------------------------------------------- **/
+
+
+ /**
+ * Uses two transactions respectively with xid1 and xid2 that are use to send a message
+ * within xid1 and xid2. xid2 is committed and xid1 is used to receive the message that was sent within xid2.
+ * Xid is then committed and a standard transaction is used to receive the message that was sent within xid1.
+ */
+ public void testProducer()
+ {
+ if (!isBroker08())
+ {
+ _logger.debug("testProducer");
+ Xid xid1 = getNewXid();
+ Xid xid2 = getNewXid();
+ try
+ {
+ Session nonXASession = _nonXASession;
+ MessageConsumer nonXAConsumer = nonXASession.createConsumer(_topic);
+ _producer.setDeliveryMode(DeliveryMode.PERSISTENT);
+ // start the xaResource for xid1
+ try
+ {
+ _logger.debug("starting tx branch xid1");
+ _xaResource.start(xid1, XAResource.TMNOFLAGS);
+ }
+ catch (XAException e)
+ {
+ e.printStackTrace();
+ fail("cannot start the transaction with xid1: " + e.getMessage());
+ }
+ try
+ {
+ // start the connection
+ _topicConnection.start();
+ _logger.debug("produce a message with sequence number 1");
+ _message.setLongProperty(_sequenceNumberPropertyName, 1);
+ _producer.send(_message);
+ }
+ catch (JMSException e)
+ {
+ fail(" cannot send persistent message: " + e.getMessage());
+ }
+ _logger.debug("suspend the transaction branch xid1");
+ try
+ {
+ _xaResource.end(xid1, XAResource.TMSUSPEND);
+ }
+ catch (XAException e)
+ {
+ fail("Cannot end the transaction with xid1: " + e.getMessage());
+ }
+ _logger.debug("start the xaResource for xid2");
+ try
+ {
+ _xaResource.start(xid2, XAResource.TMNOFLAGS);
+ }
+ catch (XAException e)
+ {
+ fail("cannot start the transaction with xid2: " + e.getMessage());
+ }
+ try
+ {
+ _logger.debug("produce a message");
+ _message.setLongProperty(_sequenceNumberPropertyName, 2);
+ _producer.send(_message);
+ }
+ catch (JMSException e)
+ {
+ fail(" cannot send second persistent message: " + e.getMessage());
+ }
+ _logger.debug("end xid2 and start xid1");
+ try
+ {
+ _xaResource.end(xid2, XAResource.TMSUCCESS);
+ _xaResource.start(xid1, XAResource.TMRESUME);
+ }
+ catch (XAException e)
+ {
+ fail("Exception when ending and starting transactions: " + e.getMessage());
+ }
+ _logger.debug("two phases commit transaction with xid2");
+ try
+ {
+ int resPrepare = _xaResource.prepare(xid2);
+ if (resPrepare != XAResource.XA_OK)
+ {
+ fail("prepare returned: " + resPrepare);
+ }
+ _xaResource.commit(xid2, false);
+ }
+ catch (XAException e)
+ {
+ fail("Exception thrown when preparing transaction with xid2: " + e.getMessage());
+ }
+ _logger.debug("receiving a message from topic test we expect it to be the second one");
+ try
+ {
+ TextMessage message = (TextMessage) _consumer.receive(1000);
+ if (message == null)
+ {
+ fail("did not receive second message as expected ");
+ }
+ else
+ {
+ if (message.getLongProperty(_sequenceNumberPropertyName) != 2)
+ {
+ fail("receive wrong message its sequence number is: " + message
+ .getLongProperty(_sequenceNumberPropertyName));
+ }
+ }
+ }
+ catch (JMSException e)
+ {
+ fail("Exception when receiving second message: " + e.getMessage());
+ }
+ _logger.debug("end and one phase commit the first transaction");
+ try
+ {
+ _xaResource.end(xid1, XAResource.TMSUCCESS);
+ _xaResource.commit(xid1, true);
+ }
+ catch (XAException e)
+ {
+ fail("Exception thrown when commiting transaction with xid1");
+ }
+ _logger.debug("We should now be able to receive the first and second message");
+ try
+ {
+ TextMessage message1 = (TextMessage) nonXAConsumer.receive(1000);
+ if (message1 == null)
+ {
+ fail("did not receive first message as expected ");
+ }
+ else
+ {
+ if (message1.getLongProperty(_sequenceNumberPropertyName) != 2)
+ {
+ fail("receive wrong message its sequence number is: " + message1
+ .getLongProperty(_sequenceNumberPropertyName));
+ }
+ }
+ message1 = (TextMessage) nonXAConsumer.receive(1000);
+ if (message1 == null)
+ {
+ fail("did not receive first message as expected ");
+ }
+ else
+ {
+ if (message1.getLongProperty(_sequenceNumberPropertyName) != 1)
+ {
+ fail("receive wrong message its sequence number is: " + message1
+ .getLongProperty(_sequenceNumberPropertyName));
+ }
+ }
+ _logger.debug("commit transacted session");
+ nonXASession.commit();
+ _logger.debug("Test that the topic is now empty");
+ message1 = (TextMessage) nonXAConsumer.receive(1000);
+ if (message1 != null)
+ {
+ fail("receive an unexpected message ");
+ }
+ }
+ catch (JMSException e)
+ {
+ fail("Exception thrown when emptying the queue: " + e.getMessage());
+ }
+ }
+ catch (JMSException e)
+ {
+ fail("cannot create standard consumer: " + e.getMessage());
+ }
+ }
+ }
+
+
+ /**
+ * strategy: Produce a message within Tx1 and commit tx1. consume this message within tx2 and abort tx2.
+ * Consume the same message within tx3 and commit it. Check that no more message is available.
+ */
+ public void testDurSub()
+ {
+ if (!isBroker08())
+ {
+ Xid xid1 = getNewXid();
+ Xid xid2 = getNewXid();
+ Xid xid3 = getNewXid();
+ Xid xid4 = getNewXid();
+ String durSubName = "xaSubDurable";
+ try
+ {
+ TopicSubscriber xaDurSub = _session.createDurableSubscriber(_topic, durSubName);
+ try
+ {
+ _topicConnection.start();
+ _logger.debug("start xid1");
+ _xaResource.start(xid1, XAResource.TMNOFLAGS);
+ // start the connection
+ _topicConnection.start();
+ _logger.debug("produce a message with sequence number 1");
+ _message.setLongProperty(_sequenceNumberPropertyName, 1);
+ _producer.send(_message);
+ _logger.debug("2 phases commit xid1");
+ _xaResource.end(xid1, XAResource.TMSUCCESS);
+ if (_xaResource.prepare(xid1) != XAResource.XA_OK)
+ {
+ fail("Problem when preparing tx1 ");
+ }
+ _xaResource.commit(xid1, false);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("Exception when working with xid1: " + e.getMessage());
+ }
+ try
+ {
+ _logger.debug("start xid2");
+ _xaResource.start(xid2, XAResource.TMNOFLAGS);
+ _logger.debug("receive the previously produced message");
+ TextMessage message = (TextMessage) xaDurSub.receive(1000);
+ if (message == null)
+ {
+ fail("no message received ");
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) != 1)
+ {
+ fail("wrong sequence number: " + message.getLongProperty(_sequenceNumberPropertyName));
+ }
+ _logger.debug("rollback xid2");
+ boolean rollbackOnFailure = false;
+ try
+ {
+ _xaResource.end(xid2, XAResource.TMFAIL);
+ }
+ catch (XAException e)
+ {
+ if (e.errorCode != XAException.XA_RBROLLBACK)
+ {
+ fail("Exception when working with xid2: " + e.getMessage());
+ }
+ rollbackOnFailure = true;
+ }
+ if (!rollbackOnFailure)
+ {
+ if (_xaResource.prepare(xid2) != XAResource.XA_OK)
+ {
+ fail("Problem when preparing tx2 ");
+ }
+ _xaResource.rollback(xid2);
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("Exception when working with xid2: " + e.getMessage());
+ }
+ try
+ {
+ _logger.debug("start xid3");
+ _xaResource.start(xid3, XAResource.TMNOFLAGS);
+ _logger.debug(" receive the previously aborted consumed message");
+ TextMessage message = (TextMessage) xaDurSub.receive(1000);
+ if (message == null)
+ {
+ fail("no message received ");
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) != 1)
+ {
+ fail("wrong sequence number: " + message.getLongProperty(_sequenceNumberPropertyName));
+ }
+ _logger.debug("commit xid3");
+ _xaResource.end(xid3, XAResource.TMSUCCESS);
+ if (_xaResource.prepare(xid3) != XAResource.XA_OK)
+ {
+ fail("Problem when preparing tx3 ");
+ }
+ _xaResource.commit(xid3, false);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("Exception when working with xid3: " + e.getMessage());
+ }
+ try
+ {
+ _logger.debug("start xid4");
+ _xaResource.start(xid4, XAResource.TMNOFLAGS);
+ _logger.debug("check that topic is empty");
+ TextMessage message = (TextMessage) xaDurSub.receive(1000);
+ if (message != null)
+ {
+ fail("An unexpected message was received ");
+ }
+ _logger.debug("commit xid4");
+ _xaResource.end(xid4, XAResource.TMSUCCESS);
+ _xaResource.commit(xid4, true);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("Exception when working with xid4: " + e.getMessage());
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("problem when creating dur sub: " + e.getMessage());
+ }
+ finally
+ {
+ try
+ {
+ _session.unsubscribe(durSubName);
+ }
+ catch (JMSException e)
+ {
+ e.printStackTrace();
+ fail("problem when unsubscribing dur sub: " + e.getMessage());
+ }
+ }
+ }
+ }
+
+ /**
+ * strategy: create a XA durable subscriber dusSub, produce 7 messages with the standard session,
+ * consume 2 messages respectively with tx1, tx2 and tx3
+ * abort tx2, we now expect to receive messages 3 and 4 first! Receive 3 messages within tx1 i.e. 34 and 7!
+ * commit tx3
+ * abort tx1: we now expect that only messages 5 and 6 are definitly consumed!
+ * start tx4 and consume messages 1 - 4 and 7
+ * commit tx4
+ * Now the topic should be empty!
+ */
+ public void testMultiMessagesDurSub()
+ {
+ if (!isBroker08())
+ {
+ Xid xid1 = getNewXid();
+ Xid xid2 = getNewXid();
+ Xid xid3 = getNewXid();
+ Xid xid4 = getNewXid();
+ Xid xid6 = getNewXid();
+ String durSubName = "xaSubDurable";
+ TextMessage message;
+ try
+ {
+ TopicSubscriber xaDurSub = _session.createDurableSubscriber(_topic, durSubName);
+ try
+ {
+ Session txSession = _nonXASession;
+ MessageProducer txProducer = txSession.createProducer(_topic);
+ _logger.debug("produce 10 persistent messages");
+ txProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
+ _topicConnection.start();
+ for (int i = 1; i <= 7; i++)
+ {
+ _message.setLongProperty(_sequenceNumberPropertyName, i);
+ txProducer.send(_message);
+ }
+ // commit txSession
+ txSession.commit();
+ }
+ catch (JMSException e)
+ {
+ e.printStackTrace();
+ fail("Exception thrown when producing messages: " + e.getMessage());
+ }
+
+ try
+ {
+ _logger.debug(" consume 2 messages respectively with tx1, tx2 and tx3");
+ //----- start xid1
+ _xaResource.start(xid1, XAResource.TMNOFLAGS);
+ // receive the 2 first messages
+ for (int i = 1; i <= 2; i++)
+ {
+ message = (TextMessage) xaDurSub.receive(1000);
+ if (message == null)
+ {
+ fail("no message received! expected: " + i);
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) != i)
+ {
+ fail("wrong sequence number: " + message.getLongProperty(_sequenceNumberPropertyName));
+ }
+ }
+ _xaResource.end(xid1, XAResource.TMSUSPEND);
+ //----- start xid2
+ _xaResource.start(xid2, XAResource.TMNOFLAGS);
+ // receive the 2 first messages
+ for (int i = 3; i <= 4; i++)
+ {
+ message = (TextMessage) xaDurSub.receive(1000);
+ if (message == null)
+ {
+ fail("no message received! expected: " + i);
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) != i)
+ {
+ fail("wrong sequence number: " + message.getLongProperty(_sequenceNumberPropertyName));
+ }
+ }
+ _xaResource.end(xid2, XAResource.TMSUSPEND);
+ //----- start xid3
+ _xaResource.start(xid3, XAResource.TMNOFLAGS);
+ // receive the 2 first messages
+ for (int i = 5; i <= 6; i++)
+ {
+ message = (TextMessage) xaDurSub.receive(1000);
+ if (message == null)
+ {
+ fail("no message received! expected: " + i);
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) != i)
+ {
+ fail("wrong sequence number: " + message.getLongProperty(_sequenceNumberPropertyName));
+ }
+ }
+ _xaResource.end(xid3, XAResource.TMSUCCESS);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("Exception thrown when consumming 6 first messages: " + e.getMessage());
+ }
+ try
+ {
+ _logger.debug("abort tx2, we now expect to receive messages 3, 4 and 7");
+ _xaResource.start(xid2, XAResource.TMRESUME);
+ _xaResource.end(xid2, XAResource.TMSUCCESS);
+ _xaResource.prepare(xid2);
+ _xaResource.rollback(xid2);
+ // receive 3 message within tx1: 3, 4 and 7
+ _xaResource.start(xid1, XAResource.TMRESUME);
+ _logger.debug(" 3, 4 and 7");
+ for (int i = 1; i <= 3; i++)
+ {
+ message = (TextMessage) xaDurSub.receive(1000);
+ if (message == null)
+ {
+ fail("no message received! expected: " + 3);
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) <= 2 || 5 == message
+ .getLongProperty(_sequenceNumberPropertyName) || message
+ .getLongProperty(_sequenceNumberPropertyName) == 6)
+ {
+ fail("wrong sequence number: " + message
+ .getLongProperty(_sequenceNumberPropertyName));
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("Exception thrown when consumming message: 3, 4 and 7: " + e.getMessage());
+ }
+
+ try
+ {
+ _xaResource.end(xid1, XAResource.TMSUCCESS);
+ _logger.debug(" commit tx3");
+ _xaResource.commit(xid3, true);
+ _logger.debug("abort tx1");
+ _xaResource.prepare(xid1);
+ _xaResource.rollback(xid1);
+ }
+ catch (XAException e)
+ {
+ e.printStackTrace();
+ fail("XAException thrown when committing tx3 or aborting tx1: " + e.getMessage());
+ }
+
+ try
+ {
+ // consume messages 1 - 4 + 7
+ //----- start xid1
+ _xaResource.start(xid4, XAResource.TMNOFLAGS);
+ for (int i = 1; i <= 5; i++)
+ {
+
+ message = (TextMessage) xaDurSub.receive(1000);
+ _logger.debug(" received message: " + message.getLongProperty(_sequenceNumberPropertyName));
+ if (message == null)
+ {
+ fail("no message received! expected: " + i);
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) == 5 || message
+ .getLongProperty(_sequenceNumberPropertyName) == 6)
+ {
+ fail("wrong sequence number: " + message.getLongProperty(_sequenceNumberPropertyName));
+ }
+ }
+ _xaResource.end(xid4, XAResource.TMSUCCESS);
+ _xaResource.prepare(xid4);
+ _xaResource.commit(xid4, false);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("Exception thrown in last phase: " + e.getMessage());
+ }
+ // now the topic should be empty!!
+ try
+ {
+ // start xid6
+ _xaResource.start(xid6, XAResource.TMNOFLAGS);
+ // should now be empty
+ message = (TextMessage) xaDurSub.receive(1000);
+ if (message != null)
+ {
+ fail("An unexpected message was received " + message
+ .getLongProperty(_sequenceNumberPropertyName));
+ }
+ // commit xid6
+ _xaResource.end(xid6, XAResource.TMSUCCESS);
+ _xaResource.commit(xid6, true);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("Exception when working with xid6: " + e.getMessage());
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("problem when creating dur sub: " + e.getMessage());
+ }
+ finally
+ {
+ try
+ {
+ _session.unsubscribe(durSubName);
+ }
+ catch (JMSException e)
+ {
+ e.printStackTrace();
+ fail("problem when unsubscribing dur sub: " + e.getMessage());
+ }
+ }
+ }
+ }
+
+ /**
+ * strategy: create a XA durable subscriber dusSub, produce 10 messages with the standard session,
+ * consume 2 messages respectively with tx1, tx2 and tx3
+ * prepare xid2 and xid3
+ * crash the server
+ * Redo the job for xid1 that has been aborted by server crash
+ * abort tx2, we now expect to receive messages 3 and 4 first! Receive 3 messages within tx1 i.e. 34 and 7!
+ * commit tx3
+ * abort tx1: we now expect that only messages 5 and 6 are definitly consumed!
+ * start tx4 and consume messages 1 - 4
+ * start tx5 and consume messages 7 - 10
+ * abort tx4
+ * consume messages 1-4 with tx5
+ * commit tx5
+ * Now the topic should be empty!
+ */
+ public void testMultiMessagesDurSubCrash()
+ {
+ if (!isBroker08())
+ {
+ Xid xid1 = getNewXid();
+ Xid xid2 = getNewXid();
+ Xid xid3 = getNewXid();
+ Xid xid4 = getNewXid();
+ Xid xid5 = getNewXid();
+ Xid xid6 = getNewXid();
+ String durSubName = "xaSubDurable";
+ TextMessage message;
+ try
+ {
+ TopicSubscriber xaDurSub = _session.createDurableSubscriber(_topic, durSubName);
+ try
+ {
+ Session txSession = _nonXASession;
+ MessageProducer txProducer = txSession.createProducer(_topic);
+ // produce 10 persistent messages
+ txProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
+ _topicConnection.start();
+ for (int i = 1; i <= 10; i++)
+ {
+ _message.setLongProperty(_sequenceNumberPropertyName, i);
+ txProducer.send(_message);
+ }
+ // commit txSession
+ txSession.commit();
+ }
+ catch (JMSException e)
+ {
+ e.printStackTrace();
+ fail("Exception thrown when producing messages: " + e.getMessage());
+ }
+ try
+ {
+ // consume 2 messages respectively with tx1, tx2 and tx3
+ //----- start xid1
+ _xaResource.start(xid1, XAResource.TMNOFLAGS);
+ // receive the 2 first messages
+ for (int i = 1; i <= 2; i++)
+ {
+ message = (TextMessage) xaDurSub.receive(1000);
+ if (message == null)
+ {
+ fail("no message received! expected: " + i);
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) != i)
+ {
+ fail("wrong sequence number: " + message.getLongProperty(_sequenceNumberPropertyName));
+ }
+ }
+ _xaResource.end(xid1, XAResource.TMSUCCESS);
+ //----- start xid2
+ _xaResource.start(xid2, XAResource.TMNOFLAGS);
+ // receive the 2 first messages
+ for (int i = 3; i <= 4; i++)
+ {
+ message = (TextMessage) xaDurSub.receive(1000);
+ if (message == null)
+ {
+ fail("no message received! expected: " + i);
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) != i)
+ {
+ fail("wrong sequence number: " + message.getLongProperty(_sequenceNumberPropertyName));
+ }
+ }
+ _xaResource.end(xid2, XAResource.TMSUCCESS);
+ //----- start xid3
+ _xaResource.start(xid3, XAResource.TMNOFLAGS);
+ // receive the 2 first messages
+ for (int i = 5; i <= 6; i++)
+ {
+ message = (TextMessage) xaDurSub.receive(1000);
+ if (message == null)
+ {
+ fail("no message received! expected: " + i);
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) != i)
+ {
+ fail("wrong sequence number: " + message.getLongProperty(_sequenceNumberPropertyName));
+ }
+ }
+ _xaResource.end(xid3, XAResource.TMSUCCESS);
+ // prepare tx2 and tx3
+
+ _xaResource.prepare(xid2);
+ _xaResource.prepare(xid3);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("Exception thrown when consumming 6 first messages: " + e.getMessage());
+ }
+ /////// stop the broker now !!
+ try
+ {
+ restartBroker();
+ init();
+ }
+ catch (Exception e)
+ {
+ fail("Exception when stopping and restarting the server");
+ }
+ // get the list of in doubt transactions
+ try
+ {
+ _topicConnection.start();
+ // reconnect to dursub!
+ xaDurSub = _session.createDurableSubscriber(_topic, durSubName);
+ Xid[] inDoubt = _xaResource.recover(XAResource.TMSTARTRSCAN);
+ if (inDoubt == null)
+ {
+ fail("the array of in doubt transactions should not be null ");
+ }
+ // At that point we expect only two indoubt transactions:
+ if (inDoubt.length != 2)
+ {
+ fail("in doubt transaction size is diffenrent than 2, there are " + inDoubt.length + "in doubt transactions");
+ }
+ }
+ catch (XAException e)
+ {
+ e.printStackTrace();
+ fail("exception thrown when recovering transactions " + e.getMessage());
+ }
+ try
+ {
+ // xid1 has been aborted redo the job!
+ // consume 2 messages with tx1
+ //----- start xid1
+ _xaResource.start(xid1, XAResource.TMNOFLAGS);
+ // receive the 2 first messages
+ for (int i = 1; i <= 2; i++)
+ {
+ message = (TextMessage) xaDurSub.receive(1000);
+ if (message == null)
+ {
+ fail("no message received! expected: " + i);
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) != i)
+ {
+ fail("wrong sequence number: " + message.getLongProperty(_sequenceNumberPropertyName));
+ }
+ }
+ _xaResource.end(xid1, XAResource.TMSUSPEND);
+ // abort tx2, we now expect to receive messages 3 and 4 first!
+ _xaResource.rollback(xid2);
+
+ // receive 3 message within tx1: 3, 4 and 7
+ _xaResource.start(xid1, XAResource.TMRESUME);
+ // receive messages 3, 4 and 7
+ message = (TextMessage) xaDurSub.receive(1000);
+ if (message == null)
+ {
+ fail("no message received! expected: " + 3);
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) != 3)
+ {
+ fail("wrong sequence number: " + message
+ .getLongProperty(_sequenceNumberPropertyName) + " 3 was expected");
+ }
+ message = (TextMessage) xaDurSub.receive(1000);
+ if (message == null)
+ {
+ fail("no message received! expected: " + 4);
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) != 4)
+ {
+ fail("wrong sequence number: " + message
+ .getLongProperty(_sequenceNumberPropertyName) + " 4 was expected");
+ }
+ message = (TextMessage) xaDurSub.receive(1000);
+ if (message == null)
+ {
+ fail("no message received! expected: " + 7);
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) != 7)
+ {
+ fail("wrong sequence number: " + message
+ .getLongProperty(_sequenceNumberPropertyName) + " 7 was expected");
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("Exception thrown when consumming message: 3, 4 and 7: " + e.getMessage());
+ }
+
+ try
+ {
+ _xaResource.end(xid1, XAResource.TMSUSPEND);
+ // commit tx3
+ _xaResource.commit(xid3, false);
+ // abort tx1
+ _xaResource.prepare(xid1);
+ _xaResource.rollback(xid1);
+ }
+ catch (XAException e)
+ {
+ e.printStackTrace();
+ fail("XAException thrown when committing tx3 or aborting tx1: " + e.getMessage());
+ }
+
+ try
+ {
+ // consume messages 1 - 4
+ //----- start xid1
+ _xaResource.start(xid4, XAResource.TMNOFLAGS);
+ for (int i = 1; i <= 4; i++)
+ {
+ message = (TextMessage) xaDurSub.receive(1000);
+ if (message == null)
+ {
+ fail("no message received! expected: " + i);
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) != i)
+ {
+ fail("wrong sequence number: " + message.getLongProperty(_sequenceNumberPropertyName));
+ }
+ }
+ _xaResource.end(xid4, XAResource.TMSUSPEND);
+ // consume messages 8 - 10
+ _xaResource.start(xid5, XAResource.TMNOFLAGS);
+ for (int i = 7; i <= 10; i++)
+ {
+ message = (TextMessage) xaDurSub.receive(1000);
+ if (message == null)
+ {
+ fail("no message received! expected: " + i);
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) != i)
+ {
+ fail("wrong sequence number: " + message.getLongProperty(_sequenceNumberPropertyName));
+ }
+ }
+ _xaResource.end(xid5, XAResource.TMSUSPEND);
+ // abort tx4
+ _xaResource.prepare(xid4);
+ _xaResource.rollback(xid4);
+ // consume messages 1-4 with tx5
+ _xaResource.start(xid5, XAResource.TMRESUME);
+ for (int i = 1; i <= 4; i++)
+ {
+ message = (TextMessage) xaDurSub.receive(1000);
+ if (message == null)
+ {
+ fail("no message received! expected: " + i);
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) != i)
+ {
+ fail("wrong sequence number: " + message.getLongProperty(_sequenceNumberPropertyName));
+ }
+ }
+ _xaResource.end(xid5, XAResource.TMSUSPEND);
+ // commit tx5
+ _xaResource.prepare(xid5);
+ _xaResource.commit(xid5, false);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("Exception thrown in last phase: " + e.getMessage());
+ }
+ // now the topic should be empty!!
+ try
+ {
+ // start xid6
+ _xaResource.start(xid6, XAResource.TMNOFLAGS);
+ // should now be empty
+ message = (TextMessage) xaDurSub.receive(1000);
+ if (message != null)
+ {
+ fail("An unexpected message was received " + message
+ .getLongProperty(_sequenceNumberPropertyName));
+ }
+ // commit xid6
+ _xaResource.end(xid6, XAResource.TMSUSPEND);
+ _xaResource.commit(xid6, true);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("Exception when working with xid6: " + e.getMessage());
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("problem when creating dur sub: " + e.getMessage());
+ }
+ finally
+ {
+ try
+ {
+ _session.unsubscribe(durSubName);
+ }
+ catch (JMSException e)
+ {
+ e.printStackTrace();
+ fail("problem when unsubscribing dur sub: " + e.getMessage());
+ }
+ }
+ }
+ }
+
+
+ /**
+ * strategy: Produce a message within Tx1 and commit tx1. a durable subscriber then receives that message within tx2
+ * that is then prepared.
+ * Shutdown the server and get the list of in doubt transactions:
+ * we expect tx2, Tx2 is aborted and the message consumed within tx3 that is committed we then check that the topic is empty.
+ */
+ public void testDurSubCrash()
+ {
+ if (!isBroker08())
+ {
+ Xid xid1 = getNewXid();
+ Xid xid2 = getNewXid();
+ Xid xid3 = getNewXid();
+ Xid xid4 = getNewXid();
+ String durSubName = "xaSubDurable";
+ try
+ {
+ TopicSubscriber xaDurSub = _session.createDurableSubscriber(_topic, durSubName);
+ try
+ {
+ _topicConnection.start();
+ //----- start xid1
+ _xaResource.start(xid1, XAResource.TMNOFLAGS);
+ // start the connection
+ _topicConnection.start();
+ // produce a message with sequence number 1
+ _message.setLongProperty(_sequenceNumberPropertyName, 1);
+ _producer.send(_message);
+ // commit
+ _xaResource.end(xid1, XAResource.TMSUCCESS);
+ if (_xaResource.prepare(xid1) != XAResource.XA_OK)
+ {
+ fail("Problem when preparing tx1 ");
+ }
+ _xaResource.commit(xid1, false);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("Exception when working with xid1: " + e.getMessage());
+ }
+ try
+ {
+ // start xid2
+ _xaResource.start(xid2, XAResource.TMNOFLAGS);
+ // receive the previously produced message
+ TextMessage message = (TextMessage) xaDurSub.receive(1000);
+ if (message == null)
+ {
+ fail("no message received ");
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) != 1)
+ {
+ fail("wrong sequence number: " + message.getLongProperty(_sequenceNumberPropertyName));
+ }
+ // prepare xid2
+ _xaResource.end(xid2, XAResource.TMSUCCESS);
+ if (_xaResource.prepare(xid2) != XAResource.XA_OK)
+ {
+ fail("Problem when preparing tx2 ");
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("Exception when working with xid2: " + e.getMessage());
+ }
+
+ /////// stop the server now !!
+ try
+ {
+ restartBroker();
+ init();
+ }
+ catch (Exception e)
+ {
+ fail("Exception when stopping and restarting the server");
+ }
+
+ // get the list of in doubt transactions
+ try
+ {
+ _topicConnection.start();
+ // reconnect to dursub!
+ xaDurSub = _session.createDurableSubscriber(_topic, durSubName);
+ Xid[] inDoubt = _xaResource.recover(XAResource.TMSTARTRSCAN);
+ if (inDoubt == null)
+ {
+ fail("the array of in doubt transactions should not be null ");
+ }
+ // At that point we expect only two indoubt transactions:
+ if (inDoubt.length != 1)
+ {
+ fail("in doubt transaction size is diffenrent than 2, there are " + inDoubt.length + "in doubt transactions");
+ }
+
+ // commit them
+ for (Xid anInDoubt : inDoubt)
+ {
+ if (anInDoubt.equals(xid2))
+ {
+ System.out.println("aborting xid2 ");
+ try
+ {
+ _xaResource.rollback(anInDoubt);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("exception when aborting xid2 ");
+ }
+ }
+ else
+ {
+ System.out.println("XID2 is not in doubt ");
+ }
+ }
+ }
+ catch (XAException e)
+ {
+ e.printStackTrace();
+ fail("exception thrown when recovering transactions " + e.getMessage());
+ }
+
+ try
+ {
+ // start xid3
+ _xaResource.start(xid3, XAResource.TMNOFLAGS);
+ // receive the previously produced message and aborted
+ TextMessage message = (TextMessage) xaDurSub.receive(1000);
+ if (message == null)
+ {
+ fail("no message received ");
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) != 1)
+ {
+ fail("wrong sequence number: " + message.getLongProperty(_sequenceNumberPropertyName));
+ }
+ // commit xid3
+ _xaResource.end(xid3, XAResource.TMSUCCESS);
+ if (_xaResource.prepare(xid3) != XAResource.XA_OK)
+ {
+ fail("Problem when preparing tx3 ");
+ }
+ _xaResource.commit(xid3, false);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("Exception when working with xid3: " + e.getMessage());
+ }
+ try
+ {
+ // start xid4
+ _xaResource.start(xid4, XAResource.TMNOFLAGS);
+ // should now be empty
+ TextMessage message = (TextMessage) xaDurSub.receive(1000);
+ if (message != null)
+ {
+ fail("An unexpected message was received " + message
+ .getLongProperty(_sequenceNumberPropertyName));
+ }
+ // commit xid4
+ _xaResource.end(xid4, XAResource.TMSUCCESS);
+ _xaResource.commit(xid4, true);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("Exception when working with xid4: " + e.getMessage());
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("problem when creating dur sub: " + e.getMessage());
+ }
+ finally
+ {
+ try
+ {
+ _session.unsubscribe(durSubName);
+ }
+ catch (JMSException e)
+ {
+ e.printStackTrace();
+ fail("problem when unsubscribing dur sub: " + e.getMessage());
+ }
+ }
+ }
+ }
+
+ /**
+ * strategy: Produce a message within Tx1 and prepare tx1. Shutdown the server and get the list of indoubt transactions:
+ * we expect tx1, Tx1 is committed so we expect the test topic not to be empty!
+ */
+ public void testRecover()
+ {
+ if (!isBroker08())
+ {
+ Xid xid1 = getNewXid();
+ String durSubName = "test1";
+ try
+ {
+ // create a dummy durable subscriber to be sure that messages are persisted!
+ _nonXASession.createDurableSubscriber(_topic, durSubName);
+ // start the xaResource for xid1
+ try
+ {
+ _xaResource.start(xid1, XAResource.TMNOFLAGS);
+ }
+ catch (XAException e)
+ {
+ fail("cannot start the transaction with xid1: " + e.getMessage());
+ }
+ try
+ {
+ // start the connection
+ _topicConnection.start();
+ // produce a message with sequence number 1
+ _message.setLongProperty(_sequenceNumberPropertyName, 1);
+ _producer.send(_message);
+ }
+ catch (JMSException e)
+ {
+ fail(" cannot send persistent message: " + e.getMessage());
+ }
+ // suspend the transaction
+ try
+ {
+ _xaResource.end(xid1, XAResource.TMSUCCESS);
+ }
+ catch (XAException e)
+ {
+ fail("Cannot end the transaction with xid1: " + e.getMessage());
+ }
+ // prepare the transaction with xid1
+ try
+ {
+ _xaResource.prepare(xid1);
+ }
+ catch (XAException e)
+ {
+ fail("Exception when preparing xid1: " + e.getMessage());
+ }
+
+ /////// stop the server now !!
+ try
+ {
+ restartBroker();
+ init();
+ }
+ catch (Exception e)
+ {
+ fail("Exception when stopping and restarting the server");
+ }
+
+ try
+ {
+ MessageConsumer nonXAConsumer = _nonXASession.createDurableSubscriber(_topic, durSubName);
+ _topicConnection.start();
+ // get the list of in doubt transactions
+ try
+ {
+ Xid[] inDoubt = _xaResource.recover(XAResource.TMSTARTRSCAN);
+ if (inDoubt == null)
+ {
+ fail("the array of in doubt transactions should not be null ");
+ }
+ // At that point we expect only two indoubt transactions:
+ if (inDoubt.length != 1)
+ {
+ fail("in doubt transaction size is diffenrent thatn 2, there are " + inDoubt.length + "in doubt transactions");
+ }
+ // commit them
+ for (Xid anInDoubt : inDoubt)
+ {
+ if (anInDoubt.equals(xid1))
+ {
+ _logger.debug("committing xid1 ");
+ try
+ {
+ _xaResource.commit(anInDoubt, false);
+ }
+ catch (Exception e)
+ {
+ _logger.debug("PB when aborted xid1");
+ e.printStackTrace();
+ fail("exception when committing xid1 ");
+ }
+ }
+ else
+ {
+ _logger.debug("XID1 is not in doubt ");
+ }
+ }
+ }
+ catch (XAException e)
+ {
+ e.printStackTrace();
+ fail("exception thrown when recovering transactions " + e.getMessage());
+ }
+ _logger.debug("the topic should not be empty");
+ TextMessage message1 = (TextMessage) nonXAConsumer.receive(1000);
+ if (message1 == null)
+ {
+ fail("The topic is empty! ");
+ }
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("Exception thrown when testin that queue test is empty: " + e.getMessage());
+ }
+ }
+ catch (JMSException e)
+ {
+ e.printStackTrace();
+ fail("cannot create dummy durable subscriber: " + e.getMessage());
+ }
+ finally
+ {
+ try
+ {
+ // unsubscribe the dummy durable subscriber
+ TopicSession nonXASession = _nonXASession;
+ nonXASession.unsubscribe(durSubName);
+ }
+ catch (JMSException e)
+ {
+ fail("cannot unsubscribe durable subscriber: " + e.getMessage());
+ }
+ }
+ }
+ }
+
+ /**
+ * strategy:
+ * create a standard durable subscriber
+ * produce 3 messages
+ * consume the first message with that durable subscriber
+ * close the standard session that deactivates the durable subscriber
+ * migrate the durable subscriber to an xa one
+ * consume the second message with that xa durable subscriber
+ * close the xa session that deactivates the durable subscriber
+ * reconnect to the durable subscriber with a standard session
+ * consume the two remaining messages and check that the topic is empty!
+ */
+ public void testMigrateDurableSubscriber()
+ {
+ if (!isBroker08())
+ {
+ Xid xid1 = getNewXid();
+ Xid xid2 = getNewXid();
+ String durSubName = "DurableSubscriberMigrate";
+ try
+ {
+ Session stSession = _nonXASession;
+ MessageProducer producer = stSession.createProducer(_topic);
+ _logger.debug("Create a standard durable subscriber!");
+ TopicSubscriber durSub = stSession.createDurableSubscriber(_topic, durSubName);
+ TopicSubscriber durSub1 = stSession.createDurableSubscriber(_topic, durSubName + "_second");
+ TextMessage message;
+ producer.setDeliveryMode(DeliveryMode.PERSISTENT);
+ _topicConnection.start();
+ _logger.debug("produce 3 messages");
+ for (int i = 1; i <= 3; i++)
+ {
+ _message.setLongProperty(_sequenceNumberPropertyName, i);
+ //producer.send( _message );
+ producer.send(_message, DeliveryMode.PERSISTENT, 9 - i, 0);
+ stSession.commit();
+ }
+ _logger.debug("consume the first message with that durable subscriber");
+ message = (TextMessage) durSub.receive(1000);
+ if (message == null)
+ {
+ fail("no message received ");
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) != 1)
+ {
+ fail("wrong sequence number: " + message.getLongProperty(_sequenceNumberPropertyName));
+ }
+ // commit the standard session
+ stSession.commit();
+ _logger.debug("first message consumed ");
+ // close the session that deactivates the durable subscriber
+ stSession.close();
+ _logger.debug("migrate the durable subscriber to an xa one");
+ _xaResource.start(xid1, XAResource.TMNOFLAGS);
+ durSub = _session.createDurableSubscriber(_topic, durSubName);
+ _logger.debug(" consume the second message with that xa durable subscriber and abort it");
+ message = (TextMessage) durSub.receive(1000);
+ if (message == null)
+ {
+ fail("no message received ");
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) != 2)
+ {
+ System.out.println("wrong sequence number, 2 expected, received: " + message
+ .getLongProperty(_sequenceNumberPropertyName));
+ }
+ _xaResource.end(xid1, XAResource.TMSUCCESS);
+ _xaResource.prepare(xid1);
+ _xaResource.rollback(xid1);
+ _logger.debug("close the session that deactivates the durable subscriber");
+ _session.close();
+ _logger.debug("create a new standard session");
+ stSession = _topicConnection.createTopicSession(true, 1);
+ _logger.debug("reconnect to the durable subscriber");
+ durSub = stSession.createDurableSubscriber(_topic, durSubName);
+ durSub1 = stSession.createDurableSubscriber(_topic, durSubName + "_second");
+ _logger.debug("Reconnected to durablse subscribers");
+ _logger.debug(" consume the 2 remaining messages");
+ message = (TextMessage) durSub.receive(1000);
+ if (message == null)
+ {
+ fail("no message received ");
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) != 2)
+ {
+ System.out.println("wrong sequence number, 2 expected, received: " + message
+ .getLongProperty(_sequenceNumberPropertyName));
+ }
+ // consume the third message with that xa durable subscriber
+ message = (TextMessage) durSub.receive(1000);
+ if (message == null)
+ {
+ fail("no message received ");
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) != 3)
+ {
+ System.out.println("wrong sequence number, 3 expected, received: " + message
+ .getLongProperty(_sequenceNumberPropertyName));
+ }
+ stSession.commit();
+ _logger.debug("the topic should be empty now");
+ message = (TextMessage) durSub.receive(1000);
+ if (message != null)
+ {
+ fail("Received unexpected message ");
+ }
+ stSession.commit();
+ _logger.debug(" use dursub1 to receive all the 3 messages");
+ for (int i = 1; i <= 3; i++)
+ {
+ message = (TextMessage) durSub1.receive(1000);
+ if (message == null)
+ {
+ _logger.debug("no message received ");
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) != i)
+ {
+ fail("wrong sequence number, " + i + " expected, received: " + message
+ .getLongProperty(_sequenceNumberPropertyName));
+ }
+ }
+ stSession.commit();
+ // send a non persistent message to check that all persistent messages are deleted
+ producer = stSession.createProducer(_topic);
+ producer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
+ producer.send(_message);
+ stSession.commit();
+ message = (TextMessage) durSub.receive(1000);
+ if (message == null)
+ {
+ fail("message not received ");
+ }
+ message = (TextMessage) durSub1.receive(1000);
+ if (message == null)
+ {
+ fail("message not received ");
+ }
+ stSession.commit();
+ stSession.close();
+ _logger.debug(" now create a standard non transacted session and reconnect to the durable xubscriber");
+ TopicConnection stConnection =
+ _topicConnection; //_topicFactory.createTopicConnection("guest", "guest");
+ TopicSession autoAclSession = stConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
+ TopicPublisher publisher = autoAclSession.createPublisher(_topic);
+ durSub = autoAclSession.createDurableSubscriber(_topic, durSubName);
+ stConnection.start();
+ // produce 3 persistent messages
+ for (int i = 1; i <= 3; i++)
+ {
+ _message.setLongProperty(_sequenceNumberPropertyName, i);
+ //producer.send( _message );
+ publisher.send(_message, DeliveryMode.PERSISTENT, 9 - i, 0);
+ }
+ _logger.debug(" use dursub to receive all the 3 messages");
+ for (int i = 1; i <= 3; i++)
+ {
+ message = (TextMessage) durSub.receive(1000);
+ if (message == null)
+ {
+ System.out.println("no message received ");
+ }
+ else if (message.getLongProperty(_sequenceNumberPropertyName) != i)
+ {
+ System.out.println("wrong sequence number, " + i + " expected, received: " + message
+ .getLongProperty(_sequenceNumberPropertyName));
+ }
+ }
+
+ _logger.debug("now set a message listener");
+ AtomicBoolean lock = new AtomicBoolean(true);
+ reset();
+ stConnection.stop();
+ durSub.setMessageListener(new TopicListener(1, 3, lock));
+ _logger.debug(" produce 3 persistent messages");
+ for (int i = 1; i <= 3; i++)
+ {
+ _message.setLongProperty(_sequenceNumberPropertyName, i);
+ //producer.send( _message );
+ publisher.send(_message, DeliveryMode.PERSISTENT, 9 - i, 0);
+ }
+ // start the connection
+ stConnection.start();
+ while (lock.get())
+ {
+ synchronized (lock)
+ {
+ lock.wait();
+ }
+ }
+ if (getFailureStatus())
+ {
+ fail("problem with message listener");
+ }
+ stConnection.stop();
+ durSub.setMessageListener(null);
+ _logger.debug(" do the same with an xa session");
+ // produce 3 persistent messages
+ for (int i = 1; i <= 3; i++)
+ {
+ _message.setLongProperty(_sequenceNumberPropertyName, i);
+ //producer.send( _message );
+ publisher.send(_message, DeliveryMode.PERSISTENT, 9 - i, 0);
+ }
+ //stConnection.close();
+ autoAclSession.close();
+ _logger.debug(" migrate the durable subscriber to an xa one");
+ _session = _topicConnection.createXATopicSession();
+ _xaResource = _session.getXAResource();
+ _xaResource.start(xid2, XAResource.TMNOFLAGS);
+ durSub = _session.createDurableSubscriber(_topic, durSubName);
+ lock = new AtomicBoolean();
+ reset();
+ _topicConnection.stop();
+ durSub.setMessageListener(new TopicListener(1, 3, lock));
+ // start the connection
+ _topicConnection.start();
+ while (lock.get())
+ {
+ synchronized (lock)
+ {
+ lock.wait();
+ }
+ }
+ if (getFailureStatus())
+ {
+ fail("problem with XA message listener");
+ }
+ _xaResource.end(xid2, XAResource.TMSUCCESS);
+ _xaResource.commit(xid2, true);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ fail("Exception thrown: " + e.getMessage());
+ }
+ finally
+ {
+ try
+ {
+ _topicConnection.createXASession().unsubscribe(durSubName);
+ _topicConnection.createXASession().unsubscribe(durSubName + "_second");
+ }
+ catch (JMSException e)
+ {
+ fail("Exception thrown when unsubscribing durable subscriber " + e.getMessage());
+ }
+ }
+ }
+ }
+
+ /** -------------------------------------------------------------------------------------- **/
+ /** ----------------------------- Utility methods --------------------------------------- **/
+ /** -------------------------------------------------------------------------------------- **/
+
+ /**
+ * get a new queue connection
+ *
+ * @return a new queue connection
+ * @throws javax.jms.JMSException If the JMS provider fails to create the queue connection
+ * due to some internal error or in case of authentication failure
+ */
+ private XATopicConnection getNewTopicXAConnection() throws JMSException
+ {
+ return _topicFactory.createXATopicConnection("guest", "guest");
+ }
+
+ public static void failure()
+ {
+ _failure = true;
+ }
+
+ public static void reset()
+ {
+ _failure = false;
+ }
+
+ public static boolean getFailureStatus()
+ {
+ return _failure;
+ }
+
+ private class TopicListener implements MessageListener
+ {
+ private long _counter;
+ private long _end;
+ private final AtomicBoolean _lock;
+
+ public TopicListener(long init, long end, AtomicBoolean lock)
+ {
+ _counter = init;
+ _end = end;
+ _lock = lock;
+ }
+
+ public void onMessage(Message message)
+ {
+ long seq = 0;
+ try
+ {
+ seq = message.getLongProperty(TopicTest._sequenceNumberPropertyName);
+ }
+ catch (JMSException e)
+ {
+ e.printStackTrace();
+ TopicTest.failure();
+ _lock.set(false);
+ synchronized (_lock)
+ {
+ _lock.notifyAll();
+ }
+ }
+ if (seq != _counter)
+ {
+ System.out.println("received message " + seq + " expected " + _counter);
+ TopicTest.failure();
+ _lock.set(false);
+ synchronized (_lock)
+ {
+ _lock.notifyAll();
+ }
+ }
+ _counter++;
+ if (_counter > _end)
+ {
+ _lock.set(false);
+ synchronized (_lock)
+ {
+ _lock.notifyAll();
+ }
+ }
+ }
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/ConversationFactory.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/ConversationFactory.java
new file mode 100644
index 0000000000..e153b2e0f5
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/ConversationFactory.java
@@ -0,0 +1,476 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.utils;
+
+import org.apache.log4j.Logger;
+import org.apache.qpid.test.utils.ReflectionUtils;
+
+import javax.jms.*;
+
+import java.util.*;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * A conversation helper, uses a message correlation id pattern to match up sent and received messages as a conversation
+ * over JMS messaging. Incoming message traffic is divided up by correlation id. Each id has a queue (behaviour dependant
+ * on the queue implementation). Clients of this de-multiplexer can wait on messages, defined by message correlation ids.
+ *
+ * <p/>One use of this is as a conversation synchronizer where multiple threads are carrying out conversations over a
+ * multiplexed messaging route. This can be usefull, as JMS sessions are not multi-threaded. Setting up the conversation
+ * with synchronous queues will allow these threads to be written in a synchronous style, but with their execution order
+ * governed by the asynchronous message flow. For example, something like the following code could run a multi-threaded
+ * conversation (the conversation methods can be called many times in parallel):
+ *
+ * <p/><pre>
+ * class Initiator
+ * {
+ * ConversationHelper conversation = new ConversationHelper(connection, null,
+ * java.util.concurrent.LinkedBlockingQueue.class);
+ *
+ * initiateConversation()
+ * {
+ * try {
+ * // Exchange greetings.
+ * conversation.send(sendDestination, conversation.getSession().createTextMessage("Hello."));
+ * Message greeting = conversation.receive();
+ *
+ * // Exchange goodbyes.
+ * conversation.send(conversation.getSession().createTextMessage("Goodbye."));
+ * Message goodbye = conversation.receive();
+ * } finally {
+ * conversation.end();
+ * }
+ * }
+ * }
+ *
+ * class Responder
+ * {
+ * ConversationHelper conversation = new ConversationHelper(connection, receiveDestination,
+ * java.util.concurrent.LinkedBlockingQueue.class);
+ *
+ * respondToConversation()
+ * {
+ * try {
+ * // Exchange greetings.
+ * Message greeting = conversation.receive();
+ * conversation.send(conversation.getSession().createTextMessage("Hello."));
+ *
+ * // Exchange goodbyes.
+ * Message goodbye = conversation.receive();
+ * conversation.send(conversation.getSession().createTextMessage("Goodbye."));
+ * } finally {
+ * conversation.end();
+ * }
+ * }
+ * }
+ * </pre>
+ *
+ * <p/>Conversation correlation id's are generated on a per thread basis.
+ *
+ * <p/>The same controlSession is shared amongst all conversations. Calls to send are therefore synchronized because JMS
+ * sessions are not multi-threaded.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Associate messages to an ongoing conversation using correlation ids.
+ * <tr><td> Auto manage sessions for conversations.
+ * <tr><td> Store messages not in a conversation in dead letter box.
+ * </table>
+ */
+public class ConversationFactory
+{
+ /** Used for debugging. */
+ private static final Logger log = Logger.getLogger(ConversationFactory.class);
+
+ /** Holds a map from correlation id's to queues. */
+ private Map<Long, BlockingQueue<Message>> idsToQueues = new HashMap<Long, BlockingQueue<Message>>();
+
+ /** Holds the connection over which the conversation is conducted. */
+ private Connection connection;
+
+ /** Holds the controlSession over which the conversation is conduxted. */
+ private Session session;
+
+ /** The message consumer for incoming messages. */
+ MessageConsumer consumer;
+
+ /** The message producer for outgoing messages. */
+ MessageProducer producer;
+
+ /** The well-known or temporary destination to receive replies on. */
+ Destination receiveDestination;
+
+ /** Holds the queue implementation class for the reply queue. */
+ Class<? extends BlockingQueue> queueClass;
+
+ /** Used to hold any replies that are received outside of the context of a conversation. */
+ BlockingQueue<Message> deadLetterBox = new LinkedBlockingQueue<Message>();
+
+ /* Used to hold conversation state on a per thread basis. */
+ /*
+ ThreadLocal<Conversation> threadLocals =
+ new ThreadLocal<Conversation>()
+ {
+ protected Conversation initialValue()
+ {
+ Conversation settings = new Conversation();
+ settings.conversationId = conversationIdGenerator.getAndIncrement();
+
+ return settings;
+ }
+ };
+ */
+
+ /** Generates new coversation id's as needed. */
+ AtomicLong conversationIdGenerator = new AtomicLong();
+
+ /**
+ * Creates a conversation helper on the specified connection with the default sending destination, and listening
+ * to the specified receiving destination.
+ *
+ * @param connection The connection to build the conversation helper on.
+ * @param receiveDestination The destination to listen to for incoming messages. This may be null to use a temporary
+ * queue.
+ * @param queueClass The queue implementation class.
+ *
+ * @throws JMSException All underlying JMSExceptions are allowed to fall through.
+ */
+ public ConversationFactory(Connection connection, Destination receiveDestination,
+ Class<? extends BlockingQueue> queueClass) throws JMSException
+ {
+ log.debug("public ConversationFactory(Connection connection, Destination receiveDestination = " + receiveDestination
+ + ", Class<? extends BlockingQueue> queueClass = " + queueClass + "): called");
+
+ this.connection = connection;
+ this.queueClass = queueClass;
+
+ session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ // Check if a well-known receive destination has been provided, or use a temporary queue if not.
+ this.receiveDestination = (receiveDestination != null) ? receiveDestination : session.createTemporaryQueue();
+
+ consumer = session.createConsumer(receiveDestination);
+ producer = session.createProducer(null);
+
+ consumer.setMessageListener(new Receiver());
+ }
+
+ /**
+ * Creates a new conversation context.
+ *
+ * @return A new conversation context.
+ */
+ public Conversation startConversation()
+ {
+ log.debug("public Conversation startConversation(): called");
+
+ Conversation conversation = new Conversation();
+ conversation.conversationId = conversationIdGenerator.getAndIncrement();
+
+ return conversation;
+ }
+
+ /**
+ * Ensures that the reply queue for a conversation exists.
+ *
+ * @param conversationId The conversation correlation id.
+ */
+ private void initQueueForId(long conversationId)
+ {
+ if (!idsToQueues.containsKey(conversationId))
+ {
+ idsToQueues.put(conversationId, ReflectionUtils.<BlockingQueue>newInstance(queueClass));
+ }
+ }
+
+ /**
+ * Clears the dead letter box, returning all messages that were in it.
+ *
+ * @return All messages in the dead letter box.
+ */
+ public Collection<Message> emptyDeadLetterBox()
+ {
+ log.debug("public Collection<Message> emptyDeadLetterBox(): called");
+
+ Collection<Message> result = new ArrayList<Message>();
+ deadLetterBox.drainTo(result);
+
+ return result;
+ }
+
+ /**
+ * Gets the controlSession over which the conversation is conducted.
+ *
+ * @return The controlSession over which the conversation is conducted.
+ */
+ public Session getSession()
+ {
+ // Conversation settings = threadLocals.get();
+
+ return session;
+ }
+
+ /**
+ * Used to hold a conversation context. This consists of a correlating id for the conversation, and a reply
+ * destination automatically updated to the last received reply-to destination.
+ */
+ public class Conversation
+ {
+ /** Holds the correlation id for the context. */
+ long conversationId;
+
+ /**
+ * Holds the send destination for the context. This will automatically be updated to the most recently received
+ * reply-to destination.
+ */
+ Destination sendDestination;
+
+ /**
+ * Sends a message to the default sending location. The correlation id of the message will be assigned by this
+ * method, overriding any previously set value.
+ *
+ * @param sendDestination The destination to send to. This may be null to use the last received reply-to
+ * destination.
+ * @param message The message to send.
+ *
+ * @throws JMSException All undelying JMSExceptions are allowed to fall through. This will also be thrown if no
+ * send destination is specified and there is no most recent reply-to destination available
+ * to use.
+ */
+ public void send(Destination sendDestination, Message message) throws JMSException
+ {
+ log.debug("public void send(Destination sendDestination = " + sendDestination + ", Message message = "
+ + message.getJMSMessageID() + "): called");
+
+ // Conversation settings = threadLocals.get();
+ // long conversationId = conversationId;
+ message.setJMSCorrelationID(Long.toString(conversationId));
+ message.setJMSReplyTo(receiveDestination);
+
+ // Ensure that the reply queue for this conversation exists.
+ initQueueForId(conversationId);
+
+ // Check if an overriding send to destination has been set or use the last reply-to if not.
+ Destination sendTo = null;
+
+ if (sendDestination != null)
+ {
+ sendTo = sendDestination;
+ }
+ else
+ {
+ throw new JMSException("The send destination was specified, and no most recent reply-to available to use.");
+ }
+
+ // Send the message.
+ synchronized (this)
+ {
+ producer.send(sendTo, message);
+ }
+ }
+
+ /**
+ * Gets the next message in an ongoing conversation. This method may block until such a message is received.
+ *
+ * @return The next incoming message in the conversation.
+ *
+ * @throws JMSException All undelying JMSExceptions are allowed to fall through. Thrown if the received message
+ * did not have its reply-to destination set up.
+ */
+ public Message receive() throws JMSException
+ {
+ log.debug("public Message receive(): called");
+
+ // Conversation settings = threadLocals.get();
+ // long conversationId = settings.conversationId;
+
+ // Ensure that the reply queue for this conversation exists.
+ initQueueForId(conversationId);
+
+ BlockingQueue<Message> queue = idsToQueues.get(conversationId);
+
+ try
+ {
+ Message result = queue.take();
+
+ // Keep the reply-to destination to send replies to.
+ sendDestination = result.getJMSReplyTo();
+
+ return result;
+ }
+ catch (InterruptedException e)
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Gets many messages in an ongoing conversation. If a limit is specified, then once that many messages are
+ * received they will be returned. If a timeout is specified, then all messages up to the limit, received within
+ * that timespan will be returned. At least one of the message count or timeout should be set to a value of
+ * 1 or greater.
+ *
+ * @param num The number of messages to receive, or all if this is less than 1.
+ * @param timeout The timeout in milliseconds to receive the messages in, or forever if this is less than 1.
+ *
+ * @return All messages received within the count limit and the timeout.
+ *
+ * @throws JMSException All undelying JMSExceptions are allowed to fall through.
+ */
+ public Collection<Message> receiveAll(int num, long timeout) throws JMSException
+ {
+ log.debug("public Collection<Message> receiveAll(int num = " + num + ", long timeout = " + timeout
+ + "): called");
+
+ // Check that a timeout or message count was set.
+ if ((num < 1) && (timeout < 1))
+ {
+ throw new IllegalArgumentException("At least one of message count (num) or timeout must be set.");
+ }
+
+ // Ensure that the reply queue for this conversation exists.
+ initQueueForId(conversationId);
+ BlockingQueue<Message> queue = idsToQueues.get(conversationId);
+
+ // Used to collect the received messages in.
+ Collection<Message> result = new ArrayList<Message>();
+
+ // Used to indicate when the timeout or message count has expired.
+ boolean receiveMore = true;
+
+ int messageCount = 0;
+
+ // Receive messages until the timeout or message count expires.
+ do
+ {
+ try
+ {
+ Message next = null;
+
+ // Try to receive the message with a timeout if one has been set.
+ if (timeout > 0)
+ {
+ next = queue.poll(timeout, TimeUnit.MILLISECONDS);
+
+ // Check if the timeout expired, and stop receiving if so.
+ if (next == null)
+ {
+ receiveMore = false;
+ }
+ }
+ // Receive the message without a timeout.
+ else
+ {
+ next = queue.take();
+ }
+
+ // Increment the message count if a message was received.
+ messageCount += (next != null) ? 1 : 0;
+
+ // Check if all the requested messages were received, and stop receiving if so.
+ if ((num > 0) && (messageCount >= num))
+ {
+ receiveMore = false;
+ }
+
+ // Keep the reply-to destination to send replies to.
+ sendDestination = (next != null) ? next.getJMSReplyTo() : sendDestination;
+
+ if (next != null)
+ {
+ result.add(next);
+ }
+ }
+ catch (InterruptedException e)
+ {
+ // Restore the threads interrupted status.
+ Thread.currentThread().interrupt();
+
+ // Stop receiving but return the messages received so far.
+ receiveMore = false;
+ }
+ }
+ while (receiveMore);
+
+ return result;
+ }
+
+ /**
+ * Completes the conversation. Any correlation id's pertaining to the conversation are no longer valid, and any
+ * incoming messages using them will go to the dead letter box.
+ */
+ public void end()
+ {
+ log.debug("public void end(): called");
+
+ // Ensure that the thread local for the current thread is cleaned up.
+ // Conversation settings = threadLocals.get();
+ // long conversationId = settings.conversationId;
+ // threadLocals.remove();
+
+ // Ensure that its queue is removed from the queue map.
+ BlockingQueue<Message> queue = idsToQueues.remove(conversationId);
+
+ // Move any outstanding messages on the threads conversation id into the dead letter box.
+ queue.drainTo(deadLetterBox);
+ }
+ }
+
+ /**
+ * Implements the message listener for this conversation handler.
+ */
+ protected class Receiver implements MessageListener
+ {
+ /**
+ * Handles all incoming messages in the ongoing conversations. These messages are split up by correaltion id
+ * and placed into queues.
+ *
+ * @param message The incoming message.
+ */
+ public void onMessage(Message message)
+ {
+ log.debug("public void onMessage(Message message = " + message + "): called");
+
+ try
+ {
+ Long conversationId = Long.parseLong(message.getJMSCorrelationID());
+
+ // Find the converstaion queue to place the message on. If there is no conversation for the message id,
+ // the the dead letter box queue is used.
+ BlockingQueue<Message> queue = idsToQueues.get(conversationId);
+ queue = (queue == null) ? deadLetterBox : queue;
+
+ queue.put(message);
+ }
+ catch (JMSException e)
+ {
+ throw new RuntimeException(e);
+ }
+ catch (InterruptedException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/FailoverBaseCase.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/FailoverBaseCase.java
new file mode 100644
index 0000000000..d3b429e315
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/FailoverBaseCase.java
@@ -0,0 +1,115 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.utils;
+
+import org.apache.qpid.util.FileUtils;
+
+import javax.naming.NamingException;
+import javax.jms.JMSException;
+import javax.naming.NamingException;
+
+import org.apache.qpid.client.AMQConnectionFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class FailoverBaseCase extends QpidBrokerTestCase
+{
+ protected static final Logger _logger = LoggerFactory.getLogger(FailoverBaseCase.class);
+
+ public static int FAILING_VM_PORT = 2;
+ public static int FAILING_PORT = Integer.parseInt(System.getProperty("test.port.alt"));
+ public static final long DEFAULT_FAILOVER_TIME = 10000L;
+
+ protected int failingPort;
+
+ protected int getFailingPort()
+ {
+ if (_broker.equals(VM))
+ {
+ return FAILING_VM_PORT;
+ }
+ else
+ {
+ return FAILING_PORT;
+ }
+ }
+
+ protected void setUp() throws java.lang.Exception
+ {
+ super.setUp();
+ startBroker(getFailingPort());
+ }
+
+ /**
+ * We are using failover factories
+ *
+ * @return a connection
+ * @throws Exception
+ */
+ @Override
+ public AMQConnectionFactory getConnectionFactory() throws NamingException
+ {
+ _logger.info("get ConnectionFactory");
+ if (_connectionFactory == null)
+ {
+ if (Boolean.getBoolean("profile.use_ssl"))
+ {
+ _connectionFactory = getConnectionFactory("failover.ssl");
+ }
+ else
+ {
+ _connectionFactory = getConnectionFactory("failover");
+ }
+ }
+ return _connectionFactory;
+ }
+
+
+ public void tearDown() throws Exception
+ {
+ try
+ {
+ super.tearDown();
+ }
+ finally
+ {
+ // Ensure we shutdown any secondary brokers, even if we are unable
+ // to cleanly tearDown the QTC.
+ stopBroker(getFailingPort());
+ FileUtils.deleteDirectory(System.getProperty("QPID_WORK") + "/" + getFailingPort());
+ }
+ }
+
+
+ public void failBroker(int port)
+ {
+ try
+ {
+ stopBroker(port);
+ }
+ catch (Exception e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/JMXTestUtils.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/JMXTestUtils.java
new file mode 100644
index 0000000000..1fde6c7c73
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/JMXTestUtils.java
@@ -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.
+ *
+ */
+package org.apache.qpid.test.utils;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import javax.management.JMException;
+import javax.management.MBeanException;
+import javax.management.MBeanServerConnection;
+import javax.management.MBeanServerInvocationHandler;
+import javax.management.ObjectName;
+import javax.management.MalformedObjectNameException;
+import javax.management.remote.JMXConnector;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.qpid.management.common.JMXConnnectionFactory;
+import org.apache.qpid.management.common.mbeans.ManagedBroker;
+import org.apache.qpid.management.common.mbeans.ManagedConnection;
+import org.apache.qpid.management.common.mbeans.ManagedExchange;
+import org.apache.qpid.management.common.mbeans.LoggingManagement;
+import org.apache.qpid.management.common.mbeans.ConfigurationManagement;
+import org.apache.qpid.management.common.mbeans.ManagedQueue;
+import org.apache.qpid.management.common.mbeans.ServerInformation;
+import org.apache.qpid.management.common.mbeans.UserManagement;
+
+/**
+ * JMX access for tests.
+ */
+public class JMXTestUtils
+{
+ QpidBrokerTestCase _test;
+ MBeanServerConnection _mbsc;
+ JMXConnector _jmxc;
+
+ private String USER;
+ private String PASSWORD;
+
+ public JMXTestUtils(QpidBrokerTestCase test, String user, String password)
+ {
+ _test = test;
+ USER = user;
+ PASSWORD = password;
+ }
+
+ public void setUp() throws IOException, ConfigurationException, Exception
+ {
+ _test.setConfigurationProperty("management.enabled", "true");
+ }
+
+ public void open() throws Exception
+ {
+ _jmxc = JMXConnnectionFactory.getJMXConnection(5000, "127.0.0.1",
+ _test.getManagementPort(_test.getPort()), USER, PASSWORD);
+
+ _mbsc = _jmxc.getMBeanServerConnection();
+ }
+
+ public void close() throws IOException
+ {
+ if(_jmxc != null)
+ {
+ _jmxc.close();
+ }
+ }
+
+ /**
+ * Create a non-durable exchange with the requested name
+ *
+ * @throws JMException if a exchange with this name already exists
+ * @throws IOException if there is a problem with the JMX Connection
+ * @throws MBeanException if there is another problem creating the exchange
+ */
+ public void createExchange(String virtualHostName, String name, String type, boolean durable)
+ throws JMException, IOException, MBeanException
+ {
+ ManagedBroker managedBroker = getManagedBroker(virtualHostName);
+
+ managedBroker.createNewExchange(name, type, durable);
+ }
+
+ /**
+ * Create a non-durable queue (with no owner) that is named after the
+ * creating test.
+ *
+ * @throws JMException if a queue with this name already exists
+ * @throws IOException if there is a problem with the JMX Connection
+ * @throws MBeanException if there is another problem creating the exchange
+ */
+ public void createQueue(String virtualHostName, String name, String owner, boolean durable)
+ throws JMException, MBeanException, IOException
+ {
+ ManagedBroker managedBroker = getManagedBroker(virtualHostName);
+
+ managedBroker.createNewQueue(name, owner, durable);
+ }
+
+ /**
+ * Unregisters all the channels, queuebindings etc and unregisters
+ * this exchange from managed objects.
+ *
+ * @throws JMException if an exchange with this name does not exist
+ * @throws IOException if there is a problem with the JMX Connection
+ * @throws MBeanException if there is another problem creating the exchange
+ */
+ public void unregisterExchange(String virtualHostName, String exchange)
+ throws IOException, JMException, MBeanException
+ {
+ ManagedBroker managedBroker = getManagedBroker(virtualHostName);
+
+ managedBroker.unregisterExchange(exchange);
+ }
+
+ /**
+ * Unregisters the Queue bindings, removes the subscriptions and unregisters
+ * from the managed objects.
+ *
+ * @throws JMException if a queue with this name does not exist
+ * @throws IOException if there is a problem with the JMX Connection
+ * @throws MBeanException if there is another problem creating the exchange
+ */
+ public void deleteQueue(String virtualHostName, String queueName)
+ throws IOException, JMException, MBeanException
+ {
+ ManagedBroker managedBroker = getManagedBroker(virtualHostName);
+
+ managedBroker.deleteQueue(queueName);
+ }
+
+ /**
+ * Sets the logging level.
+ *
+ * @throws JMException
+ * @throws IOException if there is a problem with the JMX Connection
+ * @throws MBeanException
+ */
+ public void setRuntimeLoggerLevel(String logger, String level)
+ throws IOException, JMException, MBeanException
+ {
+ LoggingManagement loggingManagement = getLoggingManagement();
+
+ loggingManagement.setRuntimeLoggerLevel(logger, level);
+ }
+
+ /**
+ * Reload logging config file.
+ *
+ * @throws JMException
+ * @throws IOException if there is a problem with the JMX Connection
+ * @throws MBeanException
+ */
+ public void reloadConfigFile()
+ throws IOException, JMException, MBeanException
+ {
+ LoggingManagement loggingManagement = getLoggingManagement();
+
+ loggingManagement.reloadConfigFile();
+ }
+
+ /**
+ * Get list of available logger levels.
+ *
+ * @throws JMException
+ * @throws IOException if there is a problem with the JMX Connection
+ * @throws MBeanException
+ */
+ public String[] getAvailableLoggerLevels()
+ throws IOException, JMException, MBeanException
+ {
+ LoggingManagement loggingManagement = getLoggingManagement();
+
+ return loggingManagement.getAvailableLoggerLevels();
+ }
+
+ /**
+ * Set root logger level.
+ *
+ * @throws JMException
+ * @throws IOException if there is a problem with the JMX Connection
+ * @throws MBeanException
+ */
+ public void setRuntimeRootLoggerLevel(String level)
+ throws IOException, JMException, MBeanException
+ {
+ LoggingManagement loggingManagement = getLoggingManagement();
+
+ loggingManagement.setRuntimeRootLoggerLevel(level);
+ }
+
+ /**
+ * Get root logger level.
+ *
+ * @throws JMException
+ * @throws IOException if there is a problem with the JMX Connection
+ * @throws MBeanException
+ */
+ public String getRuntimeRootLoggerLevel()
+ throws IOException, JMException, MBeanException
+ {
+ LoggingManagement loggingManagement = getLoggingManagement();
+
+ return loggingManagement.getRuntimeRootLoggerLevel();
+ }
+
+ /**
+ * Retrive the ObjectName for a Virtualhost.
+ *
+ * This is then used to create a proxy to the ManagedBroker MBean.
+ *
+ * @param virtualHostName the VirtualHost to retrieve
+ * @return the ObjectName for the VirtualHost
+ */
+ @SuppressWarnings("static-access")
+ public ObjectName getVirtualHostManagerObjectName(String vhostName)
+ {
+ // Get the name of the test manager
+ String query = "org.apache.qpid:type=VirtualHost.VirtualHostManager,VirtualHost="
+ + ObjectName.quote(vhostName) + ",*";
+
+ Set<ObjectName> objectNames = queryObjects(query);
+
+ _test.assertNotNull("Null ObjectName Set returned", objectNames);
+ _test.assertEquals("Incorrect number test vhosts returned", 1, objectNames.size());
+
+ // We have verified we have only one value in objectNames so return it
+ ObjectName objectName = objectNames.iterator().next();
+ _test.getLogger().info("Loading: " + objectName);
+ return objectName;
+ }
+
+ /**
+ * Retrive the ObjectName for the given Queue on a Virtualhost.
+ *
+ * This is then used to create a proxy to the ManagedQueue MBean.
+ *
+ * @param virtualHostName the VirtualHost the Queue is on
+ * @param queue The Queue to retireve
+ * @return the ObjectName for the given queue on the VirtualHost
+ */
+ @SuppressWarnings("static-access")
+ public ObjectName getQueueObjectName(String virtualHostName, String queue)
+ {
+ // Get the name of the test manager
+ String query = "org.apache.qpid:type=VirtualHost.Queue,VirtualHost="
+ + ObjectName.quote(virtualHostName) + ",name="
+ + ObjectName.quote(queue) + ",*";
+
+ Set<ObjectName> objectNames = queryObjects(query);
+
+ _test.assertNotNull("Null ObjectName Set returned", objectNames);
+ _test.assertEquals("Incorrect number of queues with name '" + queue + "' returned", 1, objectNames.size());
+
+ // We have verified we have only one value in objectNames so return it
+ ObjectName objectName = objectNames.iterator().next();
+ _test.getLogger().info("Loading: " + objectName);
+ return objectName;
+ }
+
+ /**
+ * Retrive the ObjectName for the given Exchange on a VirtualHost.
+ *
+ * This is then used to create a proxy to the ManagedExchange MBean.
+ *
+ * @param virtualHostName the VirtualHost the Exchange is on
+ * @param exchange the Exchange to retireve e.g. 'direct'
+ * @return the ObjectName for the given Exchange on the VirtualHost
+ */
+ @SuppressWarnings("static-access")
+ public ObjectName getExchangeObjectName(String virtualHostName, String exchange)
+ {
+ // Get the name of the test manager
+ String query = "org.apache.qpid:type=VirtualHost.Exchange,VirtualHost="
+ + ObjectName.quote(virtualHostName) + ",name="
+ + ObjectName.quote(exchange) + ",*";
+
+ Set<ObjectName> objectNames = queryObjects(query);
+
+ _test.assertNotNull("Null ObjectName Set returned", objectNames);
+ _test.assertEquals("Incorrect number of exchange with name '" + exchange + "' returned", 1, objectNames.size());
+
+ // We have verified we have only one value in objectNames so return it
+ ObjectName objectName = objectNames.iterator().next();
+ _test.getLogger().info("Loading: " + objectName);
+ return objectName;
+ }
+
+ @SuppressWarnings("static-access")
+ public <T> T getManagedObject(Class<T> managedClass, String query)
+ {
+ Set<ObjectName> objectNames = queryObjects(query);
+
+ _test.assertNotNull("Null ObjectName Set returned", objectNames);
+ _test.assertEquals("More than one " + managedClass + " returned", 1, objectNames.size());
+
+ ObjectName objectName = objectNames.iterator().next();
+ _test.getLogger().info("Loading: " + objectName);
+ return getManagedObject(managedClass, objectName);
+ }
+
+ public <T> T getManagedObject(Class<T> managedClass, ObjectName objectName)
+ {
+ return MBeanServerInvocationHandler.newProxyInstance(_mbsc, objectName, managedClass, false);
+ }
+
+ public <T> List<T> getManagedObjectList(Class<T> managedClass, Set<ObjectName> objectNames)
+ {
+ List<T> objects = new ArrayList<T>();
+ for (ObjectName name : objectNames)
+ {
+ objects.add(getManagedObject(managedClass, name));
+ }
+ return objects;
+ }
+
+ public ManagedBroker getManagedBroker(String virtualHost)
+ {
+ return getManagedObject(ManagedBroker.class, getVirtualHostManagerObjectName(virtualHost));
+ }
+
+ public ManagedExchange getManagedExchange(String exchangeName)
+ {
+ ObjectName objectName = getExchangeObjectName("test", exchangeName);
+ return MBeanServerInvocationHandler.newProxyInstance(_mbsc, objectName, ManagedExchange.class, false);
+ }
+
+ public ManagedQueue getManagedQueue(String queueName)
+ {
+ ObjectName objectName = getQueueObjectName("test", queueName);
+ return getManagedObject(ManagedQueue.class, objectName);
+ }
+
+ public LoggingManagement getLoggingManagement() throws MalformedObjectNameException
+ {
+ ObjectName objectName = new ObjectName("org.apache.qpid:type=LoggingManagement,name=LoggingManagement");
+ return getManagedObject(LoggingManagement.class, objectName);
+ }
+
+ public ConfigurationManagement getConfigurationManagement() throws MalformedObjectNameException
+ {
+ ObjectName objectName = new ObjectName("org.apache.qpid:type=ConfigurationManagement,name=ConfigurationManagement");
+ return getManagedObject(ConfigurationManagement.class, objectName);
+ }
+
+ public UserManagement getUserManagement() throws MalformedObjectNameException
+ {
+ ObjectName objectName = new ObjectName("org.apache.qpid:type=UserManagement,name=UserManagement");
+ return getManagedObject(UserManagement.class, objectName);
+ }
+
+ /**
+ * Retrive {@link ServerInformation} JMX MBean.
+ */
+ public ServerInformation getServerInformation()
+ {
+ // Get the name of the test manager
+ String query = "org.apache.qpid:type=ServerInformation,name=ServerInformation,*";
+
+ Set<ObjectName> objectNames = queryObjects(query);
+
+ TestCase.assertNotNull("Null ObjectName Set returned", objectNames);
+ TestCase.assertEquals("Incorrect number of objects returned", 1, objectNames.size());
+
+ // We have verified we have only one value in objectNames so return it
+ return getManagedObject(ServerInformation.class, objectNames.iterator().next());
+ }
+
+ /**
+ * Retrive all {@link ManagedConnection} objects.
+ */
+ public List<ManagedConnection> getAllManagedConnections()
+ {
+ // Get the name of the test manager
+ String query = "org.apache.qpid:type=VirtualHost.Connection,VirtualHost=*,name=*";
+
+ Set<ObjectName> objectNames = queryObjects(query);
+
+ TestCase.assertNotNull("Null ObjectName Set returned", objectNames);
+
+ return getManagedObjectList(ManagedConnection.class, objectNames);
+ }
+
+ /**
+ * Retrive all {@link ManagedConnection} objects for a particular virtual host.
+ */
+ public List<ManagedConnection> getManagedConnections(String vhost)
+ {
+ // Get the name of the test manager
+ String query = "org.apache.qpid:type=VirtualHost.Connection,VirtualHost=" + ObjectName.quote(vhost) + ",name=*";
+
+ Set<ObjectName> objectNames = queryObjects(query);
+
+ TestCase.assertNotNull("Null ObjectName Set returned", objectNames);
+
+ return getManagedObjectList(ManagedConnection.class, objectNames);
+ }
+
+ /**
+ * Returns the Set of ObjectNames returned by the broker for the given query,
+ * or null if there is problem while performing the query.
+ */
+ private Set<ObjectName> queryObjects(String query)
+ {
+ try
+ {
+ return _mbsc.queryNames(new ObjectName(query), null);
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ return null;
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidBrokerTestCase.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidBrokerTestCase.java
new file mode 100644
index 0000000000..6fcde7e185
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidBrokerTestCase.java
@@ -0,0 +1,1363 @@
+/* Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.qpid.test.utils;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.PrintStream;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import javax.jms.BytesMessage;
+import javax.jms.Connection;
+import javax.jms.Destination;
+import javax.jms.JMSException;
+import javax.jms.MapMessage;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.ObjectMessage;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.StreamMessage;
+import javax.jms.TextMessage;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+
+import org.apache.commons.configuration.ConfigurationException;
+import org.apache.commons.configuration.XMLConfiguration;
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.qpid.AMQException;
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQConnectionFactory;
+import org.apache.qpid.client.AMQQueue;
+import org.apache.qpid.client.transport.TransportConnection;
+import org.apache.qpid.exchange.ExchangeDefaults;
+import org.apache.qpid.jms.BrokerDetails;
+import org.apache.qpid.jms.ConnectionURL;
+import org.apache.qpid.management.common.mbeans.ConfigurationManagement;
+import org.apache.qpid.server.configuration.ServerConfiguration;
+import org.apache.qpid.server.registry.ApplicationRegistry;
+import org.apache.qpid.server.registry.ConfigurationFileApplicationRegistry;
+import org.apache.qpid.server.store.DerbyMessageStore;
+import org.apache.qpid.url.URLSyntaxException;
+import org.apache.qpid.util.FileUtils;
+import org.apache.qpid.util.LogMonitor;
+
+/**
+ * Qpid base class for system testing test cases.
+ */
+public class QpidBrokerTestCase extends QpidTestCase
+{
+ protected final String QpidHome = System.getProperty("QPID_HOME");
+ protected File _configFile = new File(System.getProperty("broker.config"));
+
+ protected static final Logger _logger = Logger.getLogger(QpidBrokerTestCase.class);
+ protected static final int LOGMONITOR_TIMEOUT = 5000;
+
+ protected long RECEIVE_TIMEOUT = 1000l;
+
+ private Map<String, String> _propertiesSetForTestOnly = new HashMap<String, String>();
+ private Map<String, String> _propertiesSetForBroker = new HashMap<String, String>();
+ private Map<Logger, Level> _loggerLevelSetForTest = new HashMap<Logger, Level>();
+
+ private XMLConfiguration _testConfiguration = new XMLConfiguration();
+ private XMLConfiguration _testVirtualhosts = new XMLConfiguration();
+
+ protected static final String INDEX = "index";
+ protected static final String CONTENT = "content";
+
+ private static final String DEFAULT_INITIAL_CONTEXT = "org.apache.qpid.jndi.PropertiesFileInitialContextFactory";
+
+ static
+ {
+ String initialContext = System.getProperty(InitialContext.INITIAL_CONTEXT_FACTORY);
+
+ if (initialContext == null || initialContext.length() == 0)
+ {
+ System.setProperty(InitialContext.INITIAL_CONTEXT_FACTORY, DEFAULT_INITIAL_CONTEXT);
+ }
+ }
+
+ // system properties
+ private static final String BROKER_LANGUAGE = "broker.language";
+ private static final String BROKER = "broker";
+ private static final String BROKER_CLEAN = "broker.clean";
+ private static final String BROKER_CLEAN_BETWEEN_TESTS = "broker.clean.between.tests";
+ private static final String BROKER_EXISTING_QPID_WORK = "broker.existing.qpid.work";
+ private static final String BROKER_VERSION = "broker.version";
+ protected static final String BROKER_READY = "broker.ready";
+ private static final String BROKER_STOPPED = "broker.stopped";
+ private static final String TEST_OUTPUT = "test.output";
+ private static final String BROKER_LOG_INTERLEAVE = "broker.log.interleave";
+ private static final String BROKER_LOG_PREFIX = "broker.log.prefix";
+ private static final String BROKER_PERSITENT = "broker.persistent";
+
+ // values
+ protected static final String JAVA = "java";
+ protected static final String CPP = "cpp";
+ protected static final String VM = "vm";
+ protected static final String EXTERNAL = "external";
+ private static final String VERSION_08 = "0-8";
+ private static final String VERSION_010 = "0-10";
+
+ protected static final String QPID_HOME = "QPID_HOME";
+
+ public static final int DEFAULT_VM_PORT = 1;
+ public static final int DEFAULT_PORT = Integer.getInteger("test.port", ServerConfiguration.DEFAULT_PORT);
+ public static final int DEFAULT_MANAGEMENT_PORT = Integer.getInteger("test.mport", ServerConfiguration.DEFAULT_JMXPORT);
+ public static final int DEFAULT_SSL_PORT = Integer.getInteger("test.sslport", ServerConfiguration.DEFAULT_SSL_PORT);
+
+ protected String _brokerLanguage = System.getProperty(BROKER_LANGUAGE, JAVA);
+ protected String _broker = System.getProperty(BROKER, VM);
+ private String _brokerClean = System.getProperty(BROKER_CLEAN, null);
+ private Boolean _brokerCleanBetweenTests = Boolean.getBoolean(BROKER_CLEAN_BETWEEN_TESTS);
+ private String _brokerVersion = System.getProperty(BROKER_VERSION, VERSION_08);
+ protected String _output = System.getProperty(TEST_OUTPUT);
+ protected Boolean _brokerPersistent = Boolean.getBoolean(BROKER_PERSITENT);
+
+ protected static String _brokerLogPrefix = System.getProperty(BROKER_LOG_PREFIX,"BROKER: ");
+ protected static boolean _interleaveBrokerLog = Boolean.getBoolean(BROKER_LOG_INTERLEAVE);
+
+ protected File _outputFile;
+
+ protected PrintStream _brokerOutputStream;
+
+ protected Map<Integer, Process> _brokers = new HashMap<Integer, Process>();
+
+ protected InitialContext _initialContext;
+ protected AMQConnectionFactory _connectionFactory;
+
+ protected String _testName;
+
+ // the connections created for a given test
+ protected List<Connection> _connections = new ArrayList<Connection>();
+ public static final String QUEUE = "queue";
+ public static final String TOPIC = "topic";
+
+ /** Map to hold test defined environment properties */
+ private Map<String, String> _env;
+
+ /** Ensure our messages have some sort of size */
+ protected static final int DEFAULT_MESSAGE_SIZE = 1024;
+
+ /** Size to create our message*/
+ private int _messageSize = DEFAULT_MESSAGE_SIZE;
+ /** Type of message*/
+ protected enum MessageType
+ {
+ BYTES,
+ MAP,
+ OBJECT,
+ STREAM,
+ TEXT
+ }
+ private MessageType _messageType = MessageType.TEXT;
+
+ public QpidBrokerTestCase(String name)
+ {
+ super(name);
+ }
+
+ public QpidBrokerTestCase()
+ {
+ super();
+ }
+
+ public Logger getLogger()
+ {
+ return QpidBrokerTestCase._logger;
+ }
+
+ public void runBare() throws Throwable
+ {
+ _testName = getClass().getSimpleName() + "." + getName();
+ String qname = getClass().getName() + "." + getName();
+
+ // Initialize this for each test run
+ _env = new HashMap<String, String>();
+
+ PrintStream oldOut = System.out;
+ PrintStream oldErr = System.err;
+ PrintStream out = null;
+ PrintStream err = null;
+
+ boolean redirected = _output != null && _output.length() > 0;
+ if (redirected)
+ {
+ _outputFile = new File(String.format("%s/TEST-%s.out", _output, qname));
+ out = new PrintStream(_outputFile);
+ err = new PrintStream(String.format("%s/TEST-%s.err", _output, qname));
+ System.setOut(out);
+ System.setErr(err);
+
+ if (_interleaveBrokerLog)
+ {
+ _brokerOutputStream = out;
+ }
+ else
+ {
+ _brokerOutputStream = new PrintStream(new FileOutputStream(String
+ .format("%s/TEST-%s.broker.out", _output, qname)), true);
+ }
+ }
+
+ _logger.info("========== start " + _testName + " ==========");
+ try
+ {
+ super.runBare();
+ }
+ catch (Exception e)
+ {
+ _logger.error("exception", e);
+ throw e;
+ }
+ finally
+ {
+ try
+ {
+ stopBroker();
+ }
+ catch (Exception e)
+ {
+ _logger.error("exception stopping broker", e);
+ }
+
+ if(_brokerCleanBetweenTests)
+ {
+ try
+ {
+ cleanBroker();
+ }
+ catch (Exception e)
+ {
+ _logger.error("exception cleaning up broker", e);
+ }
+ }
+
+ _logger.info("========== stop " + _testName + " ==========");
+
+ if (redirected)
+ {
+ System.setErr(oldErr);
+ System.setOut(oldOut);
+ err.close();
+ out.close();
+ if (!_interleaveBrokerLog)
+ {
+ _brokerOutputStream.close();
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void setUp() throws Exception
+ {
+ if (!_configFile.exists())
+ {
+ fail("Unable to test without config file:" + _configFile);
+ }
+
+ String existingQpidWorkPath = System.getProperty(BROKER_EXISTING_QPID_WORK);
+ if(existingQpidWorkPath != null && !existingQpidWorkPath.equals(""))
+ {
+ cleanBroker();
+
+ File existing = new File(existingQpidWorkPath);
+ File qpidWork = new File(getQpidWork(_broker, getPort()));
+ FileUtils.copyRecursive(existing, qpidWork);
+ }
+
+ startBroker();
+ }
+
+ private static final class Piper extends Thread
+ {
+
+ private LineNumberReader in;
+ private PrintStream out;
+ private String ready;
+ private CountDownLatch latch;
+ private boolean seenReady;
+ private String stopped;
+ private String stopLine;
+
+ public Piper(InputStream in, PrintStream out, String ready)
+ {
+ this(in, out, ready, null);
+ }
+
+ public Piper(InputStream in, PrintStream out, String ready, String stopped)
+ {
+ this.in = new LineNumberReader(new InputStreamReader(in));
+ this.out = out;
+ this.ready = ready;
+ this.stopped = stopped;
+ this.seenReady = false;
+
+ if (this.ready != null && !this.ready.equals(""))
+ {
+ this.latch = new CountDownLatch(1);
+ }
+ else
+ {
+ this.latch = null;
+ }
+ }
+
+ public Piper(InputStream in, PrintStream out)
+ {
+ this(in, out, null);
+ }
+
+ public boolean await(long timeout, TimeUnit unit) throws InterruptedException
+ {
+ if (latch == null)
+ {
+ return true;
+ }
+ else
+ {
+ latch.await(timeout, unit);
+ return seenReady;
+ }
+ }
+
+ public void run()
+ {
+ try
+ {
+ String line;
+ while ((line = in.readLine()) != null)
+ {
+ if (_interleaveBrokerLog)
+ {
+ line = _brokerLogPrefix + line;
+ }
+ out.println(line);
+
+ if (latch != null && line.contains(ready))
+ {
+ seenReady = true;
+ latch.countDown();
+ }
+
+ if (!seenReady && line.contains(stopped))
+ {
+ stopLine = line;
+ }
+ }
+ }
+ catch (IOException e)
+ {
+ // this seems to happen regularly even when
+ // exits are normal
+ }
+ finally
+ {
+ if (latch != null)
+ {
+ latch.countDown();
+ }
+ }
+ }
+
+ public String getStopLine()
+ {
+ return stopLine;
+ }
+ }
+
+ public void startBroker() throws Exception
+ {
+ startBroker(0);
+ }
+
+ /**
+ * Return the management portin use by the broker on this main port
+ *
+ * @param mainPort the broker's main port.
+ *
+ * @return the management port that corresponds to the broker on the given port
+ */
+ protected int getManagementPort(int mainPort)
+ {
+ return mainPort + (DEFAULT_MANAGEMENT_PORT - (_broker.equals(VM) ? DEFAULT_VM_PORT : DEFAULT_PORT));
+ }
+
+ /**
+ * Get the Port that is use by the current broker
+ *
+ * @return the current port
+ */
+ protected int getPort()
+ {
+ return getPort(0);
+ }
+
+ protected int getPort(int port)
+ {
+ if (_broker.equals(VM))
+ {
+ return port == 0 ? DEFAULT_VM_PORT : port;
+ }
+ else if (!_broker.equals(EXTERNAL))
+ {
+ return port == 0 ? DEFAULT_PORT : port;
+ }
+ else
+ {
+ return port;
+ }
+ }
+
+ protected String getBrokerCommand(int port) throws MalformedURLException
+ {
+ return _broker
+ .replace("@PORT", "" + port)
+ .replace("@SSL_PORT", "" + (port - 1))
+ .replace("@MPORT", "" + getManagementPort(port))
+ .replace("@CONFIG_FILE", _configFile.toString());
+ }
+
+ public void startBroker(int port) throws Exception
+ {
+ port = getPort(port);
+
+ // Save any configuration changes that have been made
+ saveTestConfiguration();
+ saveTestVirtualhosts();
+
+ Process process = null;
+ if (_broker.equals(VM))
+ {
+ setConfigurationProperty("management.jmxport", String.valueOf(getManagementPort(port)));
+ setConfigurationProperty(ServerConfiguration.MGMT_CUSTOM_REGISTRY_SOCKET, String.valueOf(false));
+ saveTestConfiguration();
+
+ // create an in_VM broker
+ final ConfigurationFileApplicationRegistry registry = new ConfigurationFileApplicationRegistry(_configFile);
+ try
+ {
+ ApplicationRegistry.initialise(registry, port);
+ }
+ catch (Exception e)
+ {
+ _logger.error("Broker initialise failed due to:",e);
+ try
+ {
+ registry.close();
+ }
+ catch (Throwable closeE)
+ {
+ closeE.printStackTrace();
+ }
+ throw e;
+ }
+ TransportConnection.createVMBroker(port);
+ }
+ else if (!_broker.equals(EXTERNAL))
+ {
+ String cmd = getBrokerCommand(port);
+ _logger.info("starting broker: " + cmd);
+ ProcessBuilder pb = new ProcessBuilder(cmd.split("\\s+"));
+ pb.redirectErrorStream(true);
+
+ Map<String, String> env = pb.environment();
+
+ String qpidHome = System.getProperty(QPID_HOME);
+ env.put(QPID_HOME, qpidHome);
+
+ //Augment Path with bin directory in QPID_HOME.
+ env.put("PATH", env.get("PATH").concat(File.pathSeparator + qpidHome + "/bin"));
+
+ //Add the test name to the broker run.
+ // DON'T change PNAME, qpid.stop needs this value.
+ env.put("QPID_PNAME", "-DPNAME=QPBRKR -DTNAME=\"" + _testName + "\"");
+ // Add the port to QPID_WORK to ensure unique working dirs for multi broker tests
+ env.put("QPID_WORK", getQpidWork(_broker, port));
+
+
+ // Use the environment variable to set amqj.logging.level for the broker
+ // The value used is a 'server' value in the test configuration to
+ // allow a differentiation between the client and broker logging levels.
+ if (System.getProperty("amqj.server.logging.level") != null)
+ {
+ setBrokerEnvironment("AMQJ_LOGGING_LEVEL", System.getProperty("amqj.server.logging.level"));
+ }
+
+ // Add all the environment settings the test requested
+ if (!_env.isEmpty())
+ {
+ for (Map.Entry<String, String> entry : _env.entrySet())
+ {
+ env.put(entry.getKey(), entry.getValue());
+ }
+ }
+
+
+ // Add default test logging levels that are used by the log4j-test
+ // Use the convenience methods to push the current logging setting
+ // in to the external broker's QPID_OPTS string.
+ if (System.getProperty("amqj.protocol.logging.level") != null)
+ {
+ setSystemProperty("amqj.protocol.logging.level");
+ }
+ if (System.getProperty("root.logging.level") != null)
+ {
+ setSystemProperty("root.logging.level");
+ }
+
+
+ String QPID_OPTS = " ";
+ // Add all the specified system properties to QPID_OPTS
+ if (!_propertiesSetForBroker.isEmpty())
+ {
+ for (String key : _propertiesSetForBroker.keySet())
+ {
+ QPID_OPTS += "-D" + key + "=" + _propertiesSetForBroker.get(key) + " ";
+ }
+
+ if (env.containsKey("QPID_OPTS"))
+ {
+ env.put("QPID_OPTS", env.get("QPID_OPTS") + QPID_OPTS);
+ }
+ else
+ {
+ env.put("QPID_OPTS", QPID_OPTS);
+ }
+ }
+
+ process = pb.start();
+
+ Piper p = new Piper(process.getInputStream(),
+ _brokerOutputStream,
+ System.getProperty(BROKER_READY),
+ System.getProperty(BROKER_STOPPED));
+
+ p.start();
+
+ if (!p.await(30, TimeUnit.SECONDS))
+ {
+ _logger.info("broker failed to become ready (" + p.ready + "):" + p.getStopLine());
+ //Ensure broker has stopped
+ process.destroy();
+ cleanBroker();
+ throw new RuntimeException("broker failed to become ready:"
+ + p.getStopLine());
+ }
+
+ try
+ {
+ int exit = process.exitValue();
+ _logger.info("broker aborted: " + exit);
+ cleanBroker();
+ throw new RuntimeException("broker aborted: " + exit);
+ }
+ catch (IllegalThreadStateException e)
+ {
+ // this is expect if the broker started succesfully
+ }
+ }
+
+ _brokers.put(port, process);
+ }
+
+ private String getQpidWork(String broker, int port)
+ {
+ if (broker.equals(VM))
+ {
+ return System.getProperty("QPID_WORK");
+ }
+ else if (!broker.equals(EXTERNAL))
+ {
+ return System.getProperty("QPID_WORK")+ "/" + port;
+ }
+
+ return System.getProperty("QPID_WORK");
+ }
+
+ public String getTestConfigFile()
+ {
+ String path = _output == null ? System.getProperty("java.io.tmpdir") : _output;
+ return path + "/" + getTestQueueName() + "-config.xml";
+ }
+
+ public String getTestVirtualhostsFile()
+ {
+ String path = _output == null ? System.getProperty("java.io.tmpdir") : _output;
+ return path + "/" + getTestQueueName() + "-virtualhosts.xml";
+ }
+
+ protected void saveTestConfiguration() throws ConfigurationException
+ {
+ // Specifiy the test config file
+ String testConfig = getTestConfigFile();
+ setSystemProperty("test.config", testConfig);
+
+ // Create the file if configuration does not exist
+ if (_testConfiguration.isEmpty())
+ {
+ _testConfiguration.addProperty("__ignore", "true");
+ }
+ _testConfiguration.save(testConfig);
+ }
+
+ protected void saveTestVirtualhosts() throws ConfigurationException
+ {
+ // Specifiy the test virtualhosts file
+ String testVirtualhosts = getTestVirtualhostsFile();
+ setSystemProperty("test.virtualhosts", testVirtualhosts);
+
+ // Create the file if configuration does not exist
+ if (_testVirtualhosts.isEmpty())
+ {
+ _testVirtualhosts.addProperty("__ignore", "true");
+ }
+ _testVirtualhosts.save(testVirtualhosts);
+ }
+
+ public void cleanBroker()
+ {
+ if (_brokerClean != null)
+ {
+ _logger.info("clean: " + _brokerClean);
+
+ try
+ {
+ ProcessBuilder pb = new ProcessBuilder(_brokerClean.split("\\s+"));
+ pb.redirectErrorStream(true);
+ Process clean = pb.start();
+ new Piper(clean.getInputStream(),_brokerOutputStream).start();
+
+ clean.waitFor();
+
+ _logger.info("clean exited: " + clean.exitValue());
+ }
+ catch (IOException e)
+ {
+ throw new RuntimeException(e);
+ }
+ catch (InterruptedException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+
+ public void stopBroker() throws Exception
+ {
+ stopBroker(0);
+ }
+
+ public void stopBroker(int port) throws Exception
+ {
+ port = getPort(port);
+
+ _logger.info("stopping broker: " + getBrokerCommand(port));
+ Process process = _brokers.remove(port);
+ if (process != null)
+ {
+ process.destroy();
+ process.waitFor();
+ _logger.info("broker exited: " + process.exitValue());
+ }
+ else if (_broker.equals(VM))
+ {
+ TransportConnection.killVMBroker(port);
+ ApplicationRegistry.remove(port);
+ }
+ }
+
+ /**
+ * Attempt to set the Java Broker to use the BDBMessageStore for persistence
+ * Falling back to the DerbyMessageStore if
+ *
+ * @param virtualhost - The virtualhost to modify
+ *
+ * @throws ConfigurationException - when reading/writing existing configuration
+ * @throws IOException - When creating a temporary file.
+ */
+ protected void makeVirtualHostPersistent(String virtualhost)
+ throws ConfigurationException, IOException
+ {
+ Class<?> storeClass = null;
+ try
+ {
+ // Try and lookup the BDB class
+ storeClass = Class.forName("org.apache.qpid.server.store.berkeleydb.BDBMessageStore");
+ }
+ catch (ClassNotFoundException e)
+ {
+ // No BDB store, we'll use Derby instead.
+ storeClass = DerbyMessageStore.class;
+ }
+
+
+ setConfigurationProperty("virtualhosts.virtualhost." + virtualhost + ".store.class",
+ storeClass.getName());
+ setConfigurationProperty("virtualhosts.virtualhost." + virtualhost + ".store." + DerbyMessageStore.ENVIRONMENT_PATH_PROPERTY,
+ "${QPID_WORK}/" + virtualhost);
+ }
+
+ /**
+ * Get a property value from the current configuration file.
+ *
+ * @param property the property to lookup
+ *
+ * @return the requested String Value
+ *
+ * @throws org.apache.commons.configuration.ConfigurationException
+ *
+ */
+ protected String getConfigurationStringProperty(String property) throws ConfigurationException
+ {
+ // Call save Configuration to be sure we have saved the test specific
+ // file. As the optional status
+ saveTestConfiguration();
+ saveTestVirtualhosts();
+
+ ServerConfiguration configuration = new ServerConfiguration(_configFile);
+ // Don't need to configuration.configure() here as we are just pulling
+ // values directly by String.
+ return configuration.getConfig().getString(property);
+ }
+
+ /**
+ * Set a configuration Property for this test run.
+ *
+ * This creates a new configuration based on the current configuration
+ * with the specified property change.
+ *
+ * Multiple calls to this method will result in multiple temporary
+ * configuration files being created.
+ *
+ * @param property the configuration property to set
+ * @param value the new value
+ *
+ * @throws ConfigurationException when loading the current config file
+ * @throws IOException when writing the new config file
+ */
+ protected void setConfigurationProperty(String property, String value)
+ throws ConfigurationException, IOException
+ {
+ // Choose which file to write the property to based on prefix.
+ if (property.startsWith("virtualhosts"))
+ {
+ _testVirtualhosts.setProperty(StringUtils.substringAfter(property, "virtualhosts."), value);
+ }
+ else
+ {
+ _testConfiguration.setProperty(property, value);
+ }
+ }
+
+ /**
+ * Set a System property that is to be applied only to the external test
+ * broker.
+ *
+ * This is a convenience method to enable the setting of a -Dproperty=value
+ * entry in QPID_OPTS
+ *
+ * This is only useful for the External Java Broker tests.
+ *
+ * @param property the property name
+ * @param value the value to set the property to
+ */
+ protected void setBrokerOnlySystemProperty(String property, String value)
+ {
+ if (!_propertiesSetForBroker.containsKey(property))
+ {
+ _propertiesSetForBroker.put(property, value);
+ }
+
+ }
+
+ /**
+ * Set a System (-D) property for this test run.
+ *
+ * This convenience method copies the current VMs System Property
+ * for the external VM Broker.
+ *
+ * @param property the System property to set
+ */
+ protected void setSystemProperty(String property)
+ {
+ setSystemProperty(property, System.getProperty(property));
+ }
+
+ /**
+ * Set a System property for the duration of this test.
+ *
+ * When the test run is complete the value will be reverted.
+ *
+ * The values set using this method will also be propogated to the external
+ * Java Broker via a -D value defined in QPID_OPTS.
+ *
+ * If the value should not be set on the broker then use
+ * setTestClientSystemProperty().
+ *
+ * @param property the property to set
+ * @param value the new value to use
+ */
+ protected void setSystemProperty(String property, String value)
+ {
+ // Record the value for the external broker
+ _propertiesSetForBroker.put(property, value);
+
+ //Set the value for the test client vm aswell.
+ setTestClientSystemProperty(property, value);
+ }
+
+ /**
+ * Set a System (-D) property for the external Broker of this test.
+ *
+ * @param property The property to set
+ * @param value the value to set it to.
+ */
+ protected void setTestClientSystemProperty(String property, String value)
+ {
+ if (!_propertiesSetForTestOnly.containsKey(property))
+ {
+ // Record the current value so we can revert it later.
+ _propertiesSetForTestOnly.put(property, System.getProperty(property));
+ }
+
+ System.setProperty(property, value);
+ }
+
+ /**
+ * Restore the System property values that were set before this test run.
+ */
+ protected void revertSystemProperties()
+ {
+ for (String key : _propertiesSetForTestOnly.keySet())
+ {
+ String value = _propertiesSetForTestOnly.get(key);
+ if (value != null)
+ {
+ System.setProperty(key, value);
+ }
+ else
+ {
+ System.clearProperty(key);
+ }
+ }
+
+ _propertiesSetForTestOnly.clear();
+
+ // We don't change the current VMs settings for Broker only properties
+ // so we can just clear this map
+ _propertiesSetForBroker.clear();
+ }
+
+ /**
+ * Add an environtmen variable for the external broker environment
+ *
+ * @param property the property to set
+ * @param value the value to set it to
+ */
+ protected void setBrokerEnvironment(String property, String value)
+ {
+ _env.put(property, value);
+ }
+
+ /**
+ * Adjust the VMs Log4j Settings just for this test run
+ *
+ * @param logger the logger to change
+ * @param level the level to set
+ */
+ protected void setLoggerLevel(Logger logger, Level level)
+ {
+ assertNotNull("Cannot set level of null logger", logger);
+ assertNotNull("Cannot set Logger("+logger.getName()+") to null level.",level);
+
+ if (!_loggerLevelSetForTest.containsKey(logger))
+ {
+ // Record the current value so we can revert it later.
+ _loggerLevelSetForTest.put(logger, logger.getLevel());
+ }
+
+ logger.setLevel(level);
+ }
+
+ /**
+ * Restore the logging levels defined by this test.
+ */
+ protected void revertLoggingLevels()
+ {
+ for (Logger logger : _loggerLevelSetForTest.keySet())
+ {
+ logger.setLevel(_loggerLevelSetForTest.get(logger));
+ }
+
+ _loggerLevelSetForTest.clear();
+
+ }
+
+ /**
+ * Check whether the broker is an 0.8
+ *
+ * @return true if the broker is an 0_8 version, false otherwise.
+ */
+ public boolean isBroker08()
+ {
+ return _brokerVersion.equals(VERSION_08);
+ }
+
+ public boolean isBroker010()
+ {
+ return _brokerVersion.equals(VERSION_010);
+ }
+
+ protected boolean isJavaBroker()
+ {
+ return _brokerLanguage.equals("java") || _broker.equals("vm");
+ }
+
+ protected boolean isCppBroker()
+ {
+ return _brokerLanguage.equals("cpp");
+ }
+
+ protected boolean isExternalBroker()
+ {
+ return !_broker.equals("vm");
+ }
+
+ protected boolean isBrokerStorePersistent()
+ {
+ return _brokerPersistent;
+ }
+
+ public void restartBroker() throws Exception
+ {
+ restartBroker(0);
+ }
+
+ public void restartBroker(int port) throws Exception
+ {
+ stopBroker(port);
+ startBroker(port);
+ }
+
+ /**
+ * we assume that the environment is correctly set
+ * i.e. -Djava.naming.provider.url="..//example010.properties"
+ * TODO should be a way of setting that through maven
+ *
+ * @return an initial context
+ *
+ * @throws NamingException if there is an error getting the context
+ */
+ public InitialContext getInitialContext() throws NamingException
+ {
+ _logger.info("get InitialContext");
+ if (_initialContext == null)
+ {
+ _initialContext = new InitialContext();
+ }
+ return _initialContext;
+ }
+
+ /**
+ * Get the default connection factory for the currently used broker
+ * Default factory is "local"
+ *
+ * @return A conection factory
+ *
+ * @throws Exception if there is an error getting the tactory
+ */
+ public AMQConnectionFactory getConnectionFactory() throws NamingException
+ {
+ _logger.info("get ConnectionFactory");
+ if (_connectionFactory == null)
+ {
+ if (Boolean.getBoolean("profile.use_ssl"))
+ {
+ _connectionFactory = getConnectionFactory("default.ssl");
+ }
+ else
+ {
+ _connectionFactory = getConnectionFactory("default");
+ }
+ }
+ return _connectionFactory;
+ }
+
+ /**
+ * Get a connection factory for the currently used broker
+ *
+ * @param factoryName The factory name
+ *
+ * @return A conection factory
+ *
+ * @throws Exception if there is an error getting the tactory
+ */
+ public AMQConnectionFactory getConnectionFactory(String factoryName) throws NamingException
+ {
+ if (_broker.equals(VM))
+ {
+ factoryName += ".vm";
+ }
+
+ return (AMQConnectionFactory) getInitialContext().lookup(factoryName);
+ }
+
+ public Connection getConnection() throws JMSException, NamingException
+ {
+ return getConnection("guest", "guest");
+ }
+
+ public Connection getConnection(ConnectionURL url) throws JMSException
+ {
+ _logger.info(url.getURL());
+ Connection connection = new AMQConnectionFactory(url).createConnection(url.getUsername(), url.getPassword());
+
+ _connections.add(connection);
+
+ return connection;
+ }
+
+ /**
+ * Get a connection (remote or in-VM)
+ *
+ * @param username The user name
+ * @param password The user password
+ *
+ * @return a newly created connection
+ *
+ * @throws Exception if there is an error getting the connection
+ */
+ public Connection getConnection(String username, String password) throws JMSException, NamingException
+ {
+ _logger.info("get connection");
+ Connection con = getConnectionFactory().createConnection(username, password);
+ //add the connection in the lis of connections
+ _connections.add(con);
+ return con;
+ }
+
+ public Connection getClientConnection(String username, String password, String id) throws JMSException, URLSyntaxException, AMQException, NamingException
+ {
+ _logger.info("get Connection");
+ Connection con;
+ if (_broker.equals(VM))
+ {
+ con = new AMQConnection("vm://:1", username, password, id, "test");
+ }
+ else
+ {
+ con = getConnectionFactory().createConnection(username, password, id);
+ }
+ //add the connection in the lis of connections
+ _connections.add(con);
+ return con;
+ }
+
+ /**
+ * Return a uniqueName for this test.
+ * In this case it returns a queue Named by the TestCase and TestName
+ *
+ * @return String name for a queue
+ */
+ protected String getTestQueueName()
+ {
+ return getClass().getSimpleName() + "-" + getName();
+ }
+
+ /**
+ * Return a Queue specific for this test.
+ * Uses getTestQueueName() as the name of the queue
+ * @return
+ */
+ public Queue getTestQueue()
+ {
+ return new AMQQueue(ExchangeDefaults.DIRECT_EXCHANGE_NAME, getTestQueueName());
+ }
+
+
+ protected void tearDown() throws java.lang.Exception
+ {
+ try
+ {
+ // close all the connections used by this test.
+ for (Connection c : _connections)
+ {
+ c.close();
+ }
+ }
+ finally{
+ // Ensure any problems with close does not interfer with property resets
+ revertSystemProperties();
+ revertLoggingLevels();
+ }
+ }
+
+ /**
+ * Consume all the messages in the specified queue. Helper to ensure
+ * persistent tests don't leave data behind.
+ *
+ * @param queue the queue to purge
+ *
+ * @return the count of messages drained
+ *
+ * @throws Exception if a problem occurs
+ */
+ protected int drainQueue(Queue queue) throws Exception
+ {
+ Connection connection = getConnection();
+
+ Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+
+ MessageConsumer consumer = session.createConsumer(queue);
+
+ connection.start();
+
+ int count = 0;
+ while (consumer.receive(1000) != null)
+ {
+ count++;
+ }
+
+ connection.close();
+
+ return count;
+ }
+
+ /**
+ * Send messages to the given destination.
+ *
+ * If session is transacted then messages will be commited before returning
+ *
+ * @param session the session to use for sending
+ * @param destination where to send them to
+ * @param count no. of messages to send
+ *
+ * @return the sent messges
+ *
+ * @throws Exception
+ */
+ public List<Message> sendMessage(Session session, Destination destination,
+ int count) throws Exception
+ {
+ return sendMessage(session, destination, count, 0, 0);
+ }
+
+ /**
+ * Send messages to the given destination.
+ *
+ * If session is transacted then messages will be commited before returning
+ *
+ * @param session the session to use for sending
+ * @param destination where to send them to
+ * @param count no. of messages to send
+ *
+ * @param batchSize the batchSize in which to commit, 0 means no batching,
+ * but a single commit at the end
+ * @return the sent messgse
+ *
+ * @throws Exception
+ */
+ public List<Message> sendMessage(Session session, Destination destination,
+ int count, int batchSize) throws Exception
+ {
+ return sendMessage(session, destination, count, 0, batchSize);
+ }
+
+ /**
+ * Send messages to the given destination.
+ *
+ * If session is transacted then messages will be commited before returning
+ *
+ * @param session the session to use for sending
+ * @param destination where to send them to
+ * @param count no. of messages to send
+ *
+ * @param offset offset allows the INDEX value of the message to be adjusted.
+ * @param batchSize the batchSize in which to commit, 0 means no batching,
+ * but a single commit at the end
+ * @return the sent messgse
+ *
+ * @throws Exception
+ */
+ public List<Message> sendMessage(Session session, Destination destination,
+ int count, int offset, int batchSize) throws Exception
+ {
+ List<Message> messages = new ArrayList<Message>(count);
+
+ MessageProducer producer = session.createProducer(destination);
+
+ int i = offset;
+ for (; i < (count + offset); i++)
+ {
+ Message next = createNextMessage(session, i);
+
+ producer.send(next);
+
+ if (session.getTransacted() && batchSize > 0)
+ {
+ if (i % batchSize == 0)
+ {
+ session.commit();
+ }
+
+ }
+
+ messages.add(next);
+ }
+
+ // Ensure we commit the last messages
+ // Commit the session if we are transacted and
+ // we have no batchSize or
+ // our count is not divible by batchSize.
+ if (session.getTransacted() &&
+ ( batchSize == 0 || (i-1) % batchSize != 0))
+ {
+ session.commit();
+ }
+
+ return messages;
+ }
+
+ public Message createNextMessage(Session session, int msgCount) throws JMSException
+ {
+ Message message = createMessage(session, _messageSize);
+ message.setIntProperty(INDEX, msgCount);
+
+ return message;
+
+ }
+
+ public Message createMessage(Session session, int messageSize) throws JMSException
+ {
+ String payload = new String(new byte[messageSize]);
+
+ Message message;
+
+ switch (_messageType)
+ {
+ case BYTES:
+ message = session.createBytesMessage();
+ ((BytesMessage) message).writeUTF(payload);
+ break;
+ case MAP:
+ message = session.createMapMessage();
+ ((MapMessage) message).setString(CONTENT, payload);
+ break;
+ default: // To keep the compiler happy
+ case TEXT:
+ message = session.createTextMessage();
+ ((TextMessage) message).setText(payload);
+ break;
+ case OBJECT:
+ message = session.createObjectMessage();
+ ((ObjectMessage) message).setObject(payload);
+ break;
+ case STREAM:
+ message = session.createStreamMessage();
+ ((StreamMessage) message).writeString(payload);
+ break;
+ }
+
+ return message;
+ }
+
+ protected int getMessageSize()
+ {
+ return _messageSize;
+ }
+
+ protected void setMessageSize(int byteSize)
+ {
+ _messageSize = byteSize;
+ }
+
+ public ConnectionURL getConnectionURL() throws NamingException
+ {
+ return getConnectionFactory().getConnectionURL();
+ }
+
+ public BrokerDetails getBroker()
+ {
+ try
+ {
+ if (getConnectionFactory().getConnectionURL().getBrokerCount() > 0)
+ {
+ return getConnectionFactory().getConnectionURL().getBrokerDetails(0);
+ }
+ else
+ {
+ fail("No broker details are available.");
+ }
+ }
+ catch (NamingException e)
+ {
+ fail(e.getMessage());
+ }
+
+ //keep compiler happy
+ return null;
+ }
+
+ /**
+ * Reloads the broker security configuration using the ApplicationRegistry (InVM brokers) or the
+ * ConfigurationManagementMBean via the JMX interface (Standalone brokers, management must be
+ * enabled before calling the method).
+ */
+ public void reloadBrokerSecurityConfig() throws Exception
+ {
+ if (_broker.equals(VM))
+ {
+ ApplicationRegistry.getInstance().getConfiguration().reparseConfigFileSecuritySections();
+ }
+ else
+ {
+ JMXTestUtils jmxu = new JMXTestUtils(this, "admin" , "admin");
+ jmxu.open();
+
+ try
+ {
+ ConfigurationManagement configMBean = jmxu.getConfigurationManagement();
+ configMBean.reloadSecurityConfiguration();
+ }
+ finally
+ {
+ jmxu.close();
+ }
+
+ LogMonitor _monitor = new LogMonitor(_outputFile);
+ assertTrue("The expected server security configuration reload did not occur",
+ _monitor.waitForMessage(ServerConfiguration.SECURITY_CONFIG_RELOADED, LOGMONITOR_TIMEOUT));
+
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidClientConnection.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidClientConnection.java
new file mode 100644
index 0000000000..16f7bfd305
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidClientConnection.java
@@ -0,0 +1,289 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.test.utils;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.JMSAMQException;
+import org.apache.qpid.test.utils.QpidBrokerTestCase;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jms.Connection;
+import javax.jms.ExceptionListener;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+
+public class QpidClientConnection extends QpidBrokerTestCase implements ExceptionListener
+{
+ private static final Logger _logger = LoggerFactory.getLogger(QpidClientConnection.class);
+
+ private boolean transacted = true;
+ private int ackMode = Session.CLIENT_ACKNOWLEDGE;
+ private Connection connection;
+
+ private String virtualHost;
+ private String brokerlist;
+ private int prefetch;
+ protected Session session;
+ protected boolean connected;
+
+ public QpidClientConnection(String broker)
+ {
+ super();
+ setVirtualHost("/test");
+ setBrokerList(broker);
+ setPrefetch(5000);
+ }
+
+
+ public Connection getConnection()
+ {
+ return connection;
+ }
+
+ public void connect() throws JMSException
+ {
+ if (!connected)
+ {
+ /*
+ * amqp://[user:pass@][clientid]/virtualhost?
+ * brokerlist='[transport://]host[:port][?option='value'[&option='value']];'
+ * [&failover='method[?option='value'[&option='value']]']
+ * [&option='value']"
+ */
+ String brokerUrl = "amqp://guest:guest@" + virtualHost + "?brokerlist='" + brokerlist + "'";
+ try
+ {
+ _logger.info("connecting to Qpid :" + brokerUrl);
+ connection = getConnection("guest", "guest") ;
+ // register exception listener
+ connection.setExceptionListener(this);
+
+ session = ((AMQConnection) connection).createSession(transacted, ackMode, prefetch);
+
+ _logger.info("starting connection");
+ connection.start();
+
+ connected = true;
+ }
+ catch (Exception e)
+ {
+ throw new JMSAMQException("URL syntax error in [" + brokerUrl + "]: " + e.getMessage(), e);
+ }
+ }
+ }
+
+ public void disconnect() throws Exception
+ {
+ if (connected)
+ {
+ session.commit();
+ session.close();
+ connection.close();
+ connected = false;
+ _logger.info("disconnected");
+ }
+ }
+
+ public void disconnectWithoutCommit() throws JMSException
+ {
+ if (connected)
+ {
+ session.close();
+ connection.close();
+ connected = false;
+ _logger.info("disconnected without commit");
+ }
+ }
+
+ public String getBrokerList()
+ {
+ return brokerlist;
+ }
+
+ public void setBrokerList(String brokerlist)
+ {
+ this.brokerlist = brokerlist;
+ }
+
+ public String getVirtualHost()
+ {
+ return virtualHost;
+ }
+
+ public void setVirtualHost(String virtualHost)
+ {
+ this.virtualHost = virtualHost;
+ }
+
+ public void setPrefetch(int prefetch)
+ {
+ this.prefetch = prefetch;
+ }
+
+ /** override as necessary */
+ public void onException(JMSException exception)
+ {
+ _logger.info("ExceptionListener event: error " + exception.getErrorCode() + ", message: " + exception.getMessage());
+ }
+
+ public boolean isConnected()
+ {
+ return connected;
+ }
+
+ public Session getSession()
+ {
+ return session;
+ }
+
+ /**
+ * Put a String as a text messages, repeat n times. A null payload will result in a null message.
+ *
+ * @param queueName The queue name to put to
+ * @param payload the content of the payload
+ * @param copies the number of messages to put
+ *
+ * @throws javax.jms.JMSException any exception that occurs
+ */
+ public void put(String queueName, String payload, int copies) throws JMSException
+ {
+ if (!connected)
+ {
+ connect();
+ }
+
+ _logger.info("putting to queue " + queueName);
+ Queue queue = session.createQueue(queueName);
+
+ final MessageProducer sender = session.createProducer(queue);
+
+ for (int i = 0; i < copies; i++)
+ {
+ Message m = session.createTextMessage(payload + i);
+ m.setIntProperty("index", i + 1);
+ sender.send(m);
+ }
+
+ session.commit();
+ sender.close();
+ _logger.info("put " + copies + " copies");
+ }
+
+ /**
+ * GET the top message on a queue. Consumes the message. Accepts timeout value.
+ *
+ * @param queueName The quename to get from
+ * @param readTimeout The timeout to use
+ *
+ * @return the content of the text message if any
+ *
+ * @throws javax.jms.JMSException any exception that occured
+ */
+ public Message getNextMessage(String queueName, long readTimeout) throws JMSException
+ {
+ if (!connected)
+ {
+ connect();
+ }
+
+ Queue queue = session.createQueue(queueName);
+
+ final MessageConsumer consumer = session.createConsumer(queue);
+
+ Message message = consumer.receive(readTimeout);
+ session.commit();
+ consumer.close();
+
+ Message result;
+
+ // all messages we consume should be TextMessages
+ if (message instanceof TextMessage)
+ {
+ result = ((TextMessage) message);
+ }
+ else if (null == message)
+ {
+ result = null;
+ }
+ else
+ {
+ _logger.info("warning: received non-text message");
+ result = message;
+ }
+
+ return result;
+ }
+
+ /**
+ * GET the top message on a queue. Consumes the message.
+ *
+ * @param queueName The Queuename to get from
+ *
+ * @return The string content of the text message, if any received
+ *
+ * @throws javax.jms.JMSException any exception that occurs
+ */
+ public Message getNextMessage(String queueName) throws JMSException
+ {
+ return getNextMessage(queueName, 0);
+ }
+
+ /**
+ * Completely clears a queue. For readTimeout behaviour see Javadocs for javax.jms.MessageConsumer.
+ *
+ * @param queueName The Queue name to consume from
+ * @param readTimeout The timeout for each consume
+ *
+ * @throws javax.jms.JMSException Any exception that occurs during the consume
+ * @throws InterruptedException If the consume thread was interrupted during a consume.
+ */
+ public void consume(String queueName, int readTimeout) throws JMSException, InterruptedException
+ {
+ if (!connected)
+ {
+ connect();
+ }
+
+ _logger.info("consuming queue " + queueName);
+ Queue queue = session.createQueue(queueName);
+
+ final MessageConsumer consumer = session.createConsumer(queue);
+ int messagesReceived = 0;
+
+ _logger.info("consuming...");
+ while ((consumer.receive(readTimeout)) != null)
+ {
+ messagesReceived++;
+ }
+
+ session.commit();
+ consumer.close();
+ _logger.info("consumed: " + messagesReceived);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidClientConnectionHelper.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidClientConnectionHelper.java
new file mode 100644
index 0000000000..72003ed7d7
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/QpidClientConnectionHelper.java
@@ -0,0 +1,295 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+
+package org.apache.qpid.test.utils;
+
+import org.apache.log4j.Logger;
+
+import org.apache.qpid.client.AMQConnection;
+import org.apache.qpid.client.AMQConnectionFactory;
+import org.apache.qpid.client.AMQConnectionURL;
+import org.apache.qpid.client.JMSAMQException;
+import org.apache.qpid.url.URLSyntaxException;
+
+import javax.jms.Connection;
+import javax.jms.ExceptionListener;
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+import javax.jms.TextMessage;
+
+/**
+ * @todo This was originally cut and paste from the client module leading to a duplicate class, then altered very
+ * slightly. To avoid the duplicate class the name was altered slightly to have 'Helper' on the end in order
+ * to distinguish it from the original. Delete this class and use the original instead, just upgrade it to
+ * provide the new features needed.
+ */
+public class QpidClientConnectionHelper implements ExceptionListener
+{
+
+ private static final Logger _logger = Logger.getLogger(QpidClientConnectionHelper.class);
+
+ private boolean transacted = true;
+ private int ackMode = Session.CLIENT_ACKNOWLEDGE;
+ private Connection connection;
+
+ private String virtualHost;
+ private String brokerlist;
+ private int prefetch;
+ protected Session session;
+ protected boolean connected;
+
+ public QpidClientConnectionHelper(String broker)
+ {
+ super();
+ setVirtualHost("/test");
+ setBrokerList(broker);
+ setPrefetch(5000);
+ }
+
+ public void connect() throws JMSException
+ {
+ if (!connected)
+ {
+ /*
+ * amqp://[user:pass@][clientid]/virtualhost?
+ * brokerlist='[transport://]host[:port][?option='value'[&option='value']];'
+ * [&failover='method[?option='value'[&option='value']]']
+ * [&option='value']"
+ */
+ String brokerUrl = "amqp://guest:guest@" + virtualHost + "?brokerlist='" + brokerlist + "'";
+ try
+ {
+ AMQConnectionFactory factory = new AMQConnectionFactory(new AMQConnectionURL(brokerUrl));
+ _logger.info("connecting to Qpid :" + brokerUrl);
+ connection = factory.createConnection();
+
+ // register exception listener
+ connection.setExceptionListener(this);
+
+ session = ((AMQConnection) connection).createSession(transacted, ackMode, prefetch);
+
+ _logger.info("starting connection");
+ connection.start();
+
+ connected = true;
+ }
+ catch (URLSyntaxException e)
+ {
+ throw new JMSAMQException("URL syntax error in [" + brokerUrl + "]: " + e.getMessage(), e);
+ }
+ }
+ }
+
+ public void disconnect() throws JMSException
+ {
+ if (connected)
+ {
+ session.commit();
+ session.close();
+ connection.close();
+ connected = false;
+ _logger.info("disconnected");
+ }
+ }
+
+ public void disconnectWithoutCommit() throws JMSException
+ {
+ if (connected)
+ {
+ session.close();
+ connection.close();
+ connected = false;
+ _logger.info("disconnected without commit");
+ }
+ }
+
+ public String getBrokerList()
+ {
+ return brokerlist;
+ }
+
+ public void setBrokerList(String brokerlist)
+ {
+ this.brokerlist = brokerlist;
+ }
+
+ public String getVirtualHost()
+ {
+ return virtualHost;
+ }
+
+ public void setVirtualHost(String virtualHost)
+ {
+ this.virtualHost = virtualHost;
+ }
+
+ public void setPrefetch(int prefetch)
+ {
+ this.prefetch = prefetch;
+ }
+
+ /** override as necessary */
+ public void onException(JMSException exception)
+ {
+ _logger.info("ExceptionListener event: error " + exception.getErrorCode() + ", message: " + exception.getMessage());
+ }
+
+ public boolean isConnected()
+ {
+ return connected;
+ }
+
+ public Session getSession()
+ {
+ return session;
+ }
+
+ /**
+ * Put a String as a text messages, repeat n times. A null payload will result in a null message.
+ *
+ * @param queueName The queue name to put to
+ * @param payload the content of the payload
+ * @param copies the number of messages to put
+ *
+ * @throws javax.jms.JMSException any exception that occurs
+ */
+ public void put(String queueName, String payload, int copies, int deliveryMode) throws JMSException
+ {
+ if (!connected)
+ {
+ connect();
+ }
+
+ _logger.info("putting to queue " + queueName);
+ Queue queue = session.createQueue(queueName);
+
+ final MessageProducer sender = session.createProducer(queue);
+
+ sender.setDeliveryMode(deliveryMode);
+
+ for (int i = 0; i < copies; i++)
+ {
+ Message m = session.createTextMessage(payload + i);
+ m.setIntProperty("index", i + 1);
+ sender.send(m);
+ }
+
+ session.commit();
+ sender.close();
+ _logger.info("put " + copies + " copies");
+ }
+
+ /**
+ * GET the top message on a queue. Consumes the message. Accepts timeout value.
+ *
+ * @param queueName The quename to get from
+ * @param readTimeout The timeout to use
+ *
+ * @return the content of the text message if any
+ *
+ * @throws javax.jms.JMSException any exception that occured
+ */
+ public Message getNextMessage(String queueName, long readTimeout) throws JMSException
+ {
+ if (!connected)
+ {
+ connect();
+ }
+
+ Queue queue = session.createQueue(queueName);
+
+ final MessageConsumer consumer = session.createConsumer(queue);
+
+ Message message = consumer.receive(readTimeout);
+ session.commit();
+ consumer.close();
+
+ Message result;
+
+ // all messages we consume should be TextMessages
+ if (message instanceof TextMessage)
+ {
+ result = ((TextMessage) message);
+ }
+ else if (null == message)
+ {
+ result = null;
+ }
+ else
+ {
+ _logger.info("warning: received non-text message");
+ result = message;
+ }
+
+ return result;
+ }
+
+ /**
+ * GET the top message on a queue. Consumes the message.
+ *
+ * @param queueName The Queuename to get from
+ *
+ * @return The string content of the text message, if any received
+ *
+ * @throws javax.jms.JMSException any exception that occurs
+ */
+ public Message getNextMessage(String queueName) throws JMSException
+ {
+ return getNextMessage(queueName, 0);
+ }
+
+ /**
+ * Completely clears a queue. For readTimeout behaviour see Javadocs for javax.jms.MessageConsumer.
+ *
+ * @param queueName The Queue name to consume from
+ * @param readTimeout The timeout for each consume
+ *
+ * @throws javax.jms.JMSException Any exception that occurs during the consume
+ * @throws InterruptedException If the consume thread was interrupted during a consume.
+ */
+ public void consume(String queueName, int readTimeout) throws JMSException, InterruptedException
+ {
+ if (!connected)
+ {
+ connect();
+ }
+
+ _logger.info("consuming queue " + queueName);
+ Queue queue = session.createQueue(queueName);
+
+ final MessageConsumer consumer = session.createConsumer(queue);
+ int messagesReceived = 0;
+
+ _logger.info("consuming...");
+ while ((consumer.receive(readTimeout)) != null)
+ {
+ messagesReceived++;
+ }
+
+ session.commit();
+ consumer.close();
+ _logger.info("consumed: " + messagesReceived);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/ReflectionUtils.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/ReflectionUtils.java
new file mode 100644
index 0000000000..7946c6a6d1
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/ReflectionUtils.java
@@ -0,0 +1,228 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.utils;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * Provides helper methods for operating on classes and methods using reflection. Reflection methods tend to return
+ * a lot of checked exception so writing code to use them can be tedious and harder to read, especially when such errors
+ * are not expected to occur. This class always works with {@link ReflectionUtilsException}, which is a runtime exception,
+ * to wrap the checked exceptions raised by the standard Java reflection methods. Code using it does not normally
+ * expect these errors to occur, usually does not have a recovery mechanism for them when they do, but is cleaner,
+ * quicker to write and easier to read in the majority of cases.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Look up Classes by name.
+ * <tr><td> Instantiate Classes by no-arg constructor.
+ * </table>
+ */
+public class ReflectionUtils
+{
+ /**
+ * Gets the Class object for a named class.
+ *
+ * @param className The class to get the Class object for.
+ *
+ * @return The Class object for the named class.
+ */
+ public static Class<?> forName(String className)
+ {
+ try
+ {
+ return Class.forName(className);
+ }
+ catch (ClassNotFoundException e)
+ {
+ throw new ReflectionUtilsException("ClassNotFoundException whilst finding class.", e);
+ }
+ }
+
+ /**
+ * Creates an instance of a Class, instantiated through its no-args constructor.
+ *
+ * @param cls The Class to instantiate.
+ * @param <T> The Class type.
+ *
+ * @return An instance of the class.
+ */
+ public static <T> T newInstance(Class<? extends T> cls)
+ {
+ try
+ {
+ return cls.newInstance();
+ }
+ catch (InstantiationException e)
+ {
+ throw new ReflectionUtilsException("InstantiationException whilst instantiating class.", e);
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new ReflectionUtilsException("IllegalAccessException whilst instantiating class.", e);
+ }
+ }
+
+ /**
+ * Calls a named method on an object with a specified set of parameters, any Java access modifier are overridden.
+ *
+ * @param o The object to call.
+ * @param method The method name to call.
+ * @param params The parameters to pass.
+ * @param paramClasses The argument types.
+ *
+ * @return The return value from the method call.
+ */
+ public static Object callMethodOverridingIllegalAccess(Object o, String method, Object[] params, Class[] paramClasses)
+ {
+ // Get the objects class.
+ Class cls = o.getClass();
+
+ // Get the classes of the parameters.
+ /*Class[] paramClasses = new Class[params.length];
+
+ for (int i = 0; i < params.length; i++)
+ {
+ paramClasses[i] = params[i].getClass();
+ }*/
+
+ try
+ {
+ // Try to find the matching method on the class.
+ Method m = cls.getDeclaredMethod(method, paramClasses);
+
+ // Make it accessible.
+ m.setAccessible(true);
+
+ // Invoke it with the parameters.
+ return m.invoke(o, params);
+ }
+ catch (NoSuchMethodException e)
+ {
+ throw new ReflectionUtilsException("NoSuchMethodException.", e);
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new ReflectionUtilsException("IllegalAccessException.", e);
+ }
+ catch (InvocationTargetException e)
+ {
+ throw new ReflectionUtilsException("InvocationTargetException", e);
+ }
+ }
+
+ /**
+ * Calls a named method on an object with a specified set of parameters.
+ *
+ * @param o The object to call.
+ * @param method The method name to call.
+ * @param params The parameters to pass.
+ *
+ * @return The return value from the method call.
+ */
+ public static Object callMethod(Object o, String method, Object[] params)
+ {
+ // Get the objects class.
+ Class cls = o.getClass();
+
+ // Get the classes of the parameters.
+ Class[] paramClasses = new Class[params.length];
+
+ for (int i = 0; i < params.length; i++)
+ {
+ paramClasses[i] = params[i].getClass();
+ }
+
+ try
+ {
+ // Try to find the matching method on the class.
+ Method m = cls.getMethod(method, paramClasses);
+
+ // Invoke it with the parameters.
+ return m.invoke(o, params);
+ }
+ catch (NoSuchMethodException e)
+ {
+ throw new ReflectionUtilsException("NoSuchMethodException.", e);
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new ReflectionUtilsException("IllegalAccessException", e);
+ }
+ catch (InvocationTargetException e)
+ {
+ throw new ReflectionUtilsException("InvocationTargetException", e);
+ }
+ }
+
+ /**
+ * Calls a constuctor witht the specified arguments.
+ *
+ * @param constructor The constructor.
+ * @param args The arguments.
+ * @param <T> The Class type.
+ *
+ * @return An instance of the class that the constructor is for.
+ */
+ public static <T> T newInstance(Constructor<T> constructor, Object[] args)
+ {
+ try
+ {
+ return constructor.newInstance(args);
+ }
+ catch (InstantiationException e)
+ {
+ throw new ReflectionUtilsException("InstantiationException", e);
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new ReflectionUtilsException("IllegalAccessException", e);
+ }
+ catch (InvocationTargetException e)
+ {
+ throw new ReflectionUtilsException("InvocationTargetException", e);
+ }
+ }
+
+ /**
+ * Gets the constructor of a class that takes the specified set of arguments if any matches. If no matching
+ * constructor is found then a runtime exception is raised.
+ *
+ * @param cls The class to get a constructor from.
+ * @param args The arguments to match.
+ * @param <T> The class type.
+ *
+ * @return The constructor.
+ */
+ public static <T> Constructor<T> getConstructor(Class<T> cls, Class[] args)
+ {
+ try
+ {
+ return cls.getConstructor(args);
+ }
+ catch (NoSuchMethodException e)
+ {
+ throw new ReflectionUtilsException("NoSuchMethodException", e);
+ }
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/ReflectionUtilsException.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/ReflectionUtilsException.java
new file mode 100644
index 0000000000..838828598b
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/ReflectionUtilsException.java
@@ -0,0 +1,44 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.test.utils;
+
+/**
+ * Wraps a checked exception that occurs when {@link ReflectionUtils} encounters checked exceptions using standard
+ * Java reflection methods.
+ *
+ * <p/><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Wrap a checked reflection exception.
+ * </table>
+ */
+public class ReflectionUtilsException extends RuntimeException
+{
+ /**
+ * Creates a runtime reflection exception, from a checked one.
+ *
+ * @param message The message.
+ * @param cause The causing exception.
+ */
+ public ReflectionUtilsException(String message, Throwable cause)
+ {
+ super(message, cause);
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/protocol/TestIoSession.java b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/protocol/TestIoSession.java
new file mode 100644
index 0000000000..f1eb8159b6
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/test/utils/protocol/TestIoSession.java
@@ -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.
+ *
+ */
+package org.apache.qpid.test.utils.protocol;
+
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+
+import org.apache.mina.common.IoFilterChain;
+import org.apache.mina.common.IoHandler;
+import org.apache.mina.common.IoService;
+import org.apache.mina.common.IoServiceConfig;
+import org.apache.mina.common.IoSessionConfig;
+import org.apache.mina.common.TransportType;
+import org.apache.mina.common.support.BaseIoSession;
+
+public class TestIoSession extends BaseIoSession {
+
+ private String _stringLocalAddress;
+ private int _localPort;
+
+ public SocketAddress getLocalAddress()
+ {
+ //create a new address for testing purposes using member variables
+ return new InetSocketAddress(_stringLocalAddress,_localPort);
+ }
+
+ protected void updateTrafficMask() {
+ //dummy
+ }
+
+ public IoService getService() {
+ return null;
+ }
+
+ public IoServiceConfig getServiceConfig() {
+ return null;
+ }
+
+ public IoHandler getHandler() {
+ return null;
+ }
+
+ public IoSessionConfig getConfig() {
+ return null;
+ }
+
+ public IoFilterChain getFilterChain() {
+ return null;
+ }
+
+ public TransportType getTransportType() {
+ return null;
+ }
+
+ public SocketAddress getRemoteAddress() {
+ return null;
+ }
+
+ public SocketAddress getServiceAddress() {
+ return null;
+ }
+
+ public int getScheduledWriteRequests() {
+ return 0;
+ }
+
+ public int getScheduledWriteBytes() {
+ return 0;
+ }
+
+ public String getStringLocalAddress() {
+ return _stringLocalAddress;
+ }
+
+ public void setStringLocalAddress(String _stringLocalAddress) {
+ this._stringLocalAddress = _stringLocalAddress;
+ }
+
+ public int getLocalPort() {
+ return _localPort;
+ }
+
+ public void setLocalPort(int _localPort) {
+ this._localPort = _localPort;
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/util/ClasspathScanner.java b/qpid/java/systests/src/main/java/org/apache/qpid/util/ClasspathScanner.java
new file mode 100644
index 0000000000..8cae846a39
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/util/ClasspathScanner.java
@@ -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.
+ *
+ */
+package org.apache.qpid.util;
+
+import java.io.File;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.log4j.Logger;
+
+/**
+ * An ClasspathScanner scans the classpath for classes that implement an interface or extend a base class and have names
+ * that match a regular expression.
+ *
+ * <p/>In order to test whether a class implements an interface or extends a class, the class must be loaded (unless
+ * the class files were to be scanned directly). Using this collector can cause problems when it scans the classpath,
+ * because loading classes will initialize their statics, which in turn may cause undesired side effects. For this
+ * reason, the collector should always be used with a regular expression, through which the class file names are
+ * filtered, and only those that pass this filter will be tested. For example, if you define tests in classes that
+ * end with the keyword "Test" then use the regular expression "Test$" to match this.
+ *
+ * <p><table id="crc"><caption>CRC Card</caption>
+ * <tr><th> Responsibilities <th> Collaborations
+ * <tr><td> Find all classes matching type and name pattern on the classpath.
+ * </table>
+ *
+ * @todo Add logic to scan jars as well as directories.
+ */
+public class ClasspathScanner
+{
+ private static final Logger log = Logger.getLogger(ClasspathScanner.class);
+
+ /**
+ * Scans the classpath and returns all classes that extend a specified class and match a specified name.
+ * There is an flag that can be used to indicate that only Java Beans will be matched (that is, only those classes
+ * that have a default constructor).
+ *
+ * @param matchingClass The class or interface to match.
+ * @param matchingRegexp The regular expression to match against the class name.
+ * @param beanOnly Flag to indicate that onyl classes with default constructors should be matched.
+ *
+ * @return All the classes that match this collector.
+ */
+ public static <T> Collection<Class<? extends T>> getMatches(Class<T> matchingClass, String matchingRegexp,
+ boolean beanOnly)
+ {
+ log.debug("public static <T> Collection<Class<? extends T>> getMatches(Class<T> matchingClass = " + matchingClass
+ + ", String matchingRegexp = " + matchingRegexp + ", boolean beanOnly = " + beanOnly + "): called");
+
+ // Build a compiled regular expression from the pattern to match.
+ Pattern matchPattern = Pattern.compile(matchingRegexp);
+
+ String classPath = System.getProperty("java.class.path");
+ Map<String, Class<? extends T>> result = new HashMap<String, Class<? extends T>>();
+
+ log.debug("classPath = " + classPath);
+
+ // Find matching classes starting from all roots in the classpath.
+ for (String path : splitClassPath(classPath))
+ {
+ gatherFiles(new File(path), "", result, matchPattern, matchingClass);
+ }
+
+ return result.values();
+ }
+
+ /**
+ * Finds all matching classes rooted at a given location in the file system. If location is a directory it
+ * is recursively examined.
+ *
+ * @param classRoot The root of the current point in the file system being examined.
+ * @param classFileName The name of the current file or directory to examine.
+ * @param result The accumulated mapping from class names to classes that match the scan.
+ *
+ * @todo Recursion ok as file system depth is not likely to exhaust the stack. Might be better to replace with
+ * iteration.
+ */
+ private static <T> void gatherFiles(File classRoot, String classFileName, Map<String, Class<? extends T>> result,
+ Pattern matchPattern, Class<? extends T> matchClass)
+ {
+ log.debug("private static <T> void gatherFiles(File classRoot = " + classRoot + ", String classFileName = "
+ + classFileName + ", Map<String, Class<? extends T>> result, Pattern matchPattern = " + matchPattern
+ + ", Class<? extends T> matchClass = " + matchClass + "): called");
+
+ File thisRoot = new File(classRoot, classFileName);
+
+ // If the current location is a file, check if it is a matching class.
+ if (thisRoot.isFile())
+ {
+ // Check that the file has a matching name.
+ if (matchesName(thisRoot.getName(), matchPattern))
+ {
+ String className = classNameFromFile(thisRoot.getName());
+
+ // Check that the class has matching type.
+ try
+ {
+ Class<?> candidateClass = Class.forName(className);
+
+ Class matchedClass = matchesClass(candidateClass, matchClass);
+
+ if (matchedClass != null)
+ {
+ result.put(className, matchedClass);
+ }
+ }
+ catch (ClassNotFoundException e)
+ {
+ // Ignore this. The matching class could not be loaded.
+ log.debug("Got ClassNotFoundException, ignoring.", e);
+ }
+ }
+
+ return;
+ }
+ // Otherwise the current location is a directory, so examine all of its contents.
+ else
+ {
+ String[] contents = thisRoot.list();
+
+ if (contents != null)
+ {
+ for (String content : contents)
+ {
+ gatherFiles(classRoot, classFileName + File.separatorChar + content, result, matchPattern, matchClass);
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks if the specified class file name corresponds to a class with name matching the specified regular expression.
+ *
+ * @param classFileName The class file name.
+ * @param matchPattern The regular expression pattern to match.
+ *
+ * @return <tt>true</tt> if the class name matches, <tt>false</tt> otherwise.
+ */
+ private static boolean matchesName(String classFileName, Pattern matchPattern)
+ {
+ String className = classNameFromFile(classFileName);
+ Matcher matcher = matchPattern.matcher(className);
+
+ return matcher.matches();
+ }
+
+ /**
+ * Checks if the specified class to compare extends the base class being scanned for.
+ *
+ * @param matchingClass The base class to match against.
+ * @param toMatch The class to match against the base class.
+ *
+ * @return The class to check, cast as an instance of the class to match if the class extends the base class, or
+ * <tt>null</tt> otherwise.
+ */
+ private static <T> Class<? extends T> matchesClass(Class<?> matchingClass, Class<? extends T> toMatch)
+ {
+ try
+ {
+ return matchingClass.asSubclass(toMatch);
+ }
+ catch (ClassCastException e)
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Takes a classpath (which is a series of paths) and splits it into its component paths.
+ *
+ * @param classPath The classpath to split.
+ *
+ * @return A list of the component paths that make up the class path.
+ */
+ private static List<String> splitClassPath(String classPath)
+ {
+ List<String> result = new LinkedList<String>();
+ String separator = System.getProperty("path.separator");
+ StringTokenizer tokenizer = new StringTokenizer(classPath, separator);
+
+ while (tokenizer.hasMoreTokens())
+ {
+ result.add(tokenizer.nextToken());
+ }
+
+ return result;
+ }
+
+ /**
+ * Translates from the filename of a class to its fully qualified classname. Files are named using forward slash
+ * seperators and end in ".class", whereas fully qualified class names use "." sperators and no ".class" ending.
+ *
+ * @param classFileName The filename of the class to translate to a class name.
+ *
+ * @return The fully qualified class name.
+ */
+ private static String classNameFromFile(String classFileName)
+ {
+ log.debug("private static String classNameFromFile(String classFileName = " + classFileName + "): called");
+
+ // Remove the .class ending.
+ String s = classFileName.substring(0, classFileName.length() - ".class".length());
+
+ // Turn / seperators in . seperators.
+ String s2 = s.replace(File.separatorChar, '.');
+
+ // Knock off any leading . caused by a leading /.
+ if (s2.startsWith("."))
+ {
+ return s2.substring(1);
+ }
+
+ return s2;
+ }
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitor.java b/qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitor.java
new file mode 100644
index 0000000000..a5e2b80f64
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitor.java
@@ -0,0 +1,238 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.util;
+
+import org.apache.log4j.FileAppender;
+import org.apache.log4j.Logger;
+import org.apache.log4j.SimpleLayout;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.LinkedList;
+
+/**
+ * Utility to simplify the monitoring of Log4j file output
+ *
+ * Monitoring of a given log file can be done alternatively the Monitor will
+ * add a new log4j FileAppender to the root Logger to gather all the available
+ * logging for monitoring
+ */
+public class LogMonitor
+{
+ // The file that the log statements will be written to.
+ private File _logfile;
+
+ // The appender we added to the get messages
+ private FileAppender _appender;
+
+ /**
+ * Create a new LogMonitor that creates a new Log4j Appender and monitors
+ * all log4j output via the current configuration.
+ *
+ * @throws IOException if there is a problem creating the temporary file.
+ */
+ public LogMonitor() throws IOException
+ {
+ this(null);
+ }
+
+ /**
+ * Create a new LogMonitor on the specified file if the file does not exist
+ * or the value is null then a new Log4j appender will be added and
+ * monitoring set up on that appender.
+ *
+ * NOTE: for the appender to receive any value the RootLogger will need to
+ * have the level correctly configured.ng
+ *
+ * @param file the file to monitor
+ *
+ * @throws IOException if there is a problem creating a temporary file
+ */
+ public LogMonitor(File file) throws IOException
+ {
+ if (file != null && file.exists())
+ {
+ _logfile = file;
+ }
+ else
+ {
+ // This is mostly for running the test outside of the ant setup
+ _logfile = File.createTempFile("LogMonitor", ".log");
+ _appender = new FileAppender(new SimpleLayout(),
+ _logfile.getAbsolutePath());
+ _appender.setFile(_logfile.getAbsolutePath());
+ _appender.setImmediateFlush(true);
+ Logger.getRootLogger().addAppender(_appender);
+ }
+ }
+
+ /**
+ * Checks the log file for a given message to appear and returns all
+ * instances of that appearance.
+ *
+ * @param message the message to wait for in the log
+ * @param wait the time in ms to wait for the message to occur
+ * @return true if the message was found
+ *
+ * @throws java.io.FileNotFoundException if the Log file can nolonger be found
+ * @throws IOException thrown when reading the log file
+ */
+ public List<String> waitAndFindMatches(String message, long wait)
+ throws FileNotFoundException, IOException
+ {
+ if (waitForMessage(message, wait, true))
+ {
+ return findMatches(message);
+ }
+ else
+ {
+ return new LinkedList<String>();
+ }
+ }
+
+ /**
+ * Checks the log for instances of the search string.
+ *
+ * The pattern parameter can take any valid argument used in String.contains()
+ *
+ * {@see String.contains(CharSequences)}
+ *
+ * @param pattern the search string
+ *
+ * @return a list of matching lines from the log
+ *
+ * @throws IOException if there is a problem with the file
+ */
+ public List<String> findMatches(String pattern) throws IOException
+ {
+ return FileUtils.searchFile(_logfile, pattern);
+ }
+
+ /**
+ * Checks the log file for a given message to appear.
+ *
+ * @param message the message to wait for in the log
+ * @param wait the time in ms to wait for the message to occur
+ *
+ * @param printFileOnFailure should we print the contents that have been
+ * read if we fail ot find the message.
+ * @return true if the message was found
+ *
+ * @throws java.io.FileNotFoundException if the Log file can nolonger be found
+ * @throws IOException thrown when reading the log file
+ */
+ public boolean waitForMessage(String message, long wait, boolean printFileOnFailure)
+ throws FileNotFoundException, IOException
+ {
+ // Loop through alerts until we're done or wait ms seconds have passed,
+ // just in case the logfile takes a while to flush.
+ BufferedReader reader = new BufferedReader(new FileReader(_logfile));
+ boolean found = false;
+ long endtime = System.currentTimeMillis() + wait;
+ ArrayList<String> contents = new ArrayList<String>();
+ while (!found && System.currentTimeMillis() < endtime)
+ {
+ while (reader.ready())
+ {
+ String line = reader.readLine();
+ contents.add(line);
+ if (line.contains(message))
+ {
+ found = true;
+ }
+ }
+ }
+ if (!found && printFileOnFailure)
+ {
+ for (String line : contents)
+ {
+ System.out.println(line);
+ }
+ }
+ return found;
+ }
+
+
+ public boolean waitForMessage(String message, long alertLogWaitPeriod) throws FileNotFoundException, IOException
+ {
+ return waitForMessage(message, alertLogWaitPeriod, true);
+ }
+
+
+ /**
+ * Read the log file in to memory as a String
+ *
+ * @return the current contents of the log file
+ *
+ * @throws java.io.FileNotFoundException if the Log file can nolonger be found
+ * @throws IOException thrown when reading the log file
+ */
+ public String readFile() throws FileNotFoundException, IOException
+ {
+ return FileUtils.readFileAsString(_logfile);
+ }
+
+ /**
+ * Return a File reference to the monitored file
+ *
+ * @return the file being monitored
+ */
+ public File getMonitoredFile()
+ {
+ return _logfile;
+ }
+
+ /**
+ * Clears the log file and writes: 'Log Monitor Reset' at the start of the file
+ *
+ * @throws java.io.FileNotFoundException if the Log file can nolonger be found
+ * @throws IOException thrown if there is a problem with the log file
+ */
+ public void reset() throws FileNotFoundException, IOException
+ {
+ new FileOutputStream(_logfile).getChannel().truncate(0);
+ }
+
+ /**
+ * Stop monitoring this file.
+ *
+ * This is required to be called incase we added a new logger.
+ *
+ * If we don't call close then the new logger will continue to get log entries
+ * after our desired test has finished.
+ */
+ public void close()
+ {
+ //Remove the custom appender we added for this logger
+ if (_appender != null)
+ {
+ Logger.getRootLogger().removeAppender(_appender);
+ }
+ }
+
+}
diff --git a/qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitorTest.java b/qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitorTest.java
new file mode 100644
index 0000000000..a99abe4b94
--- /dev/null
+++ b/qpid/java/systests/src/main/java/org/apache/qpid/util/LogMonitorTest.java
@@ -0,0 +1,275 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+package org.apache.qpid.util;
+
+import junit.framework.TestCase;
+import org.apache.log4j.Logger;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+public class LogMonitorTest extends TestCase
+{
+
+ private LogMonitor _monitor;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ _monitor = new LogMonitor();
+ _monitor.getMonitoredFile().deleteOnExit(); // Make sure we clean up
+ }
+
+ /**
+ * Test that a new file is created when attempting to set up a monitor with
+ * the default constructor.
+ */
+ public void testMonitor()
+ {
+ //Validate that the monitor is now running on a new file
+ assertTrue("New file does not have correct name:" + _monitor.
+ getMonitoredFile().getName(),
+ _monitor.getMonitoredFile().getName().contains("LogMonitor"));
+ }
+
+ /**
+ * Test that creation of a monitor on an existing file is possible
+ *
+ * This also tests taht getMonitoredFile works
+ *
+ * @throws IOException if there is a problem creating the temporary file
+ */
+ public void testMonitorNormalFile() throws IOException
+ {
+ File testFile = File.createTempFile("testMonitorFile", ".log");
+ testFile.deleteOnExit();
+
+ //Ensure that we can create a monitor on a file
+ try
+ {
+ _monitor = new LogMonitor(testFile);
+ assertEquals(testFile, _monitor.getMonitoredFile());
+ }
+ catch (IOException ioe)
+ {
+ fail("IOE thrown:" + ioe);
+ }
+
+ }
+
+ /**
+ * Test that a new file is created when attempting to set up a monitor on
+ * a null input value.
+ */
+ public void testMonitorNullFile()
+ {
+ // Validate that a NPE is thrown with null input
+ try
+ {
+ LogMonitor montior = new LogMonitor(null);
+ //Validte that the monitor is now running on a new file
+ assertTrue("New file does not have correct name:" + montior.
+ getMonitoredFile().getName(),
+ montior.getMonitoredFile().getName().contains("LogMonitor"));
+ }
+ catch (IOException ioe)
+ {
+ fail("IOE thrown:" + ioe);
+ }
+ }
+
+ /**
+ * Test that a new file is created when attempting to set up a monitor on
+ * a non existing file.
+ *
+ * @throws IOException if there is a problem setting up the nonexistent file
+ */
+ public void testMonitorNonExistentFile() throws IOException
+ {
+ //Validate that we get a FileNotFound if the file does not exist
+
+ File nonexist = File.createTempFile("nonexist", ".out");
+
+ assertTrue("Unable to delete file for our test", nonexist.delete());
+
+ assertFalse("Unable to test as our test file exists.", nonexist.exists());
+
+ try
+ {
+ LogMonitor montior = new LogMonitor(nonexist);
+ //Validte that the monitor is now running on a new file
+ assertTrue("New file does not have correct name:" + montior.
+ getMonitoredFile().getName(),
+ montior.getMonitoredFile().getName().contains("LogMonitor"));
+ }
+ catch (IOException ioe)
+ {
+ fail("IOE thrown:" + ioe);
+ }
+ }
+
+ /**
+ * Test that Log file matches logged messages.
+ *
+ * @throws java.io.IOException if there is a problem creating LogMontior
+ */
+ public void testFindMatches_Match() throws IOException
+ {
+
+ String message = getName() + ": Test Message";
+
+ Logger.getRootLogger().warn(message);
+
+ validateLogContainsMessage(_monitor, message);
+ }
+
+ /**
+ * Test that Log file does not match a message not logged.
+ *
+ * @throws java.io.IOException if there is a problem creating LogMontior
+ */
+ public void testFindMatches_NoMatch() throws IOException
+ {
+ String message = getName() + ": Test Message";
+
+ Logger.getRootLogger().warn(message);
+
+ String notLogged = "This text was not logged";
+
+ validateLogDoesNotContainsMessage(_monitor, notLogged);
+ }
+
+ public void testWaitForMessage_Timeout() throws IOException
+ {
+ String message = getName() + ": Test Message";
+
+ long TIME_OUT = 2000;
+
+ logMessageWithDelay(message, TIME_OUT);
+
+ // Verify that we can time out waiting for a message
+ assertFalse("Message was logged ",
+ _monitor.waitForMessage(message, TIME_OUT / 2, false));
+
+ // Verify that the message did eventually get logged.
+ assertTrue("Message was never logged.",
+ _monitor.waitForMessage(message, TIME_OUT));
+ }
+
+ public void testReset() throws IOException
+ {
+ String message = getName() + ": Test Message";
+
+ Logger.getRootLogger().warn(message);
+
+ validateLogContainsMessage(_monitor, message);
+
+ String LOG_RESET_TEXT = "Log Monitor Reset";
+
+ validateLogDoesNotContainsMessage(_monitor, LOG_RESET_TEXT);
+
+ _monitor.reset();
+
+ assertEquals("", _monitor.readFile());
+ }
+
+ public void testRead() throws IOException
+ {
+ String message = getName() + ": Test Message";
+
+ Logger.getRootLogger().warn(message);
+
+ String fileContents = _monitor.readFile();
+
+ assertTrue("Logged message not found when reading file.",
+ fileContents.contains(message));
+ }
+
+ /****************** Helpers ******************/
+
+ /**
+ * Validate that the LogMonitor does not match the given string in the log
+ *
+ * @param log The LogMonitor to check
+ * @param message The message to check for
+ *
+ * @throws IOException if a problems occurs
+ */
+ protected void validateLogDoesNotContainsMessage(LogMonitor log, String message)
+ throws IOException
+ {
+ List<String> results = log.findMatches(message);
+
+ assertNotNull("Null results returned.", results);
+
+ assertEquals("Incorrect result set size", 0, results.size());
+ }
+
+ /**
+ * Validate that the LogMonitor can match the given string in the log
+ *
+ * @param log The LogMonitor to check
+ * @param message The message to check for
+ *
+ * @throws IOException if a problems occurs
+ */
+ protected void validateLogContainsMessage(LogMonitor log, String message)
+ throws IOException
+ {
+ List<String> results = log.findMatches(message);
+
+ assertNotNull("Null results returned.", results);
+
+ assertEquals("Incorrect result set size", 1, results.size());
+
+ assertTrue("Logged Message'" + message + "' not present in results:"
+ + results.get(0), results.get(0).contains(message));
+ }
+
+ /**
+ * Create a new thread to log the given message after the set delay
+ *
+ * @param message the messasge to log
+ * @param delay the delay (ms) to wait before logging
+ */
+ private void logMessageWithDelay(final String message, final long delay)
+ {
+ new Thread(new Runnable()
+ {
+
+ public void run()
+ {
+ try
+ {
+ Thread.sleep(delay);
+ }
+ catch (InterruptedException e)
+ {
+ //ignore
+ }
+
+ Logger.getRootLogger().warn(message);
+ }
+ }).start();
+ }
+
+}