diff options
Diffstat (limited to 'qpid/java/systests/src/main/java/org/apache/qpid/test/unit')
65 files changed, 16916 insertions, 0 deletions
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(); + } + } + } + } + +} |