summaryrefslogtreecommitdiff
path: root/qpid/java/systests/src/main/java/org/apache/qpid/test
diff options
context:
space:
mode:
Diffstat (limited to 'qpid/java/systests/src/main/java/org/apache/qpid/test')
-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
155 files changed, 34932 insertions, 0 deletions
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;
+ }
+}