/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.activemq.artemis.tests.integration.amqp; import java.io.IOException; import java.util.ArrayList; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.DeliveryMode; import javax.jms.Message; import javax.jms.MessageConsumer; import javax.jms.MessageProducer; import javax.jms.Session; import org.apache.activemq.artemis.api.core.RoutingType; import org.apache.activemq.artemis.api.core.SimpleString; import org.apache.activemq.artemis.core.server.Queue; import org.apache.activemq.artemis.tests.util.Wait; import org.apache.activemq.transport.amqp.client.AmqpClient; import org.apache.activemq.transport.amqp.client.AmqpConnection; import org.apache.activemq.transport.amqp.client.AmqpMessage; import org.apache.activemq.transport.amqp.client.AmqpReceiver; import org.apache.activemq.transport.amqp.client.AmqpSender; import org.apache.activemq.transport.amqp.client.AmqpSession; import org.apache.activemq.transport.amqp.client.AmqpValidator; import org.apache.qpid.jms.JmsConnectionFactory; import org.apache.qpid.proton.amqp.messaging.Accepted; import org.apache.qpid.proton.amqp.transaction.TransactionalState; import org.apache.qpid.proton.amqp.transport.DeliveryState; import org.apache.qpid.proton.engine.Delivery; import org.apache.qpid.proton.engine.Sender; import org.junit.Assert; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Test various aspects of Transaction support. */ public class AmqpTransactionTest extends AmqpClientTestSupport { private static final Logger LOG = LoggerFactory.getLogger(AmqpTransactionTest.class); @Test(timeout = 30000) public void testBeginAndCommitTransaction() throws Exception { AmqpClient client = createAmqpClient(); AmqpConnection connection = addConnection(client.connect()); AmqpSession session = connection.createSession(); assertNotNull(session); session.begin(); assertTrue(session.isInTransaction()); session.commit(); connection.close(); } @Test(timeout = 30000) public void testCoordinatorReplenishesCredit() throws Exception { AmqpClient client = createAmqpClient(); AmqpConnection connection = addConnection(client.connect()); AmqpSession session = connection.createSession(); assertNotNull(session); for (int i = 0; i < 1000; ++i) { session.begin(); assertTrue(session.isInTransaction()); session.commit(); } connection.close(); } @Test(timeout = 30000) public void testSentTransactionalMessageIsSettleWithTransactionalDisposition() throws Exception { AmqpClient client = createAmqpClient(); AmqpConnection connection = addConnection(client.connect()); AmqpSession session = connection.createSession(); assertNotNull(session); AmqpSender sender = session.createSender(getQueueName()); sender.setStateInspector(new AmqpValidator() { @Override public void inspectDeliveryUpdate(Sender sender, Delivery delivery) { if (delivery.remotelySettled()) { DeliveryState state = delivery.getRemoteState(); if (state instanceof TransactionalState) { LOG.debug("Remote settled with TX state: {}", state); } else { LOG.warn("Remote settled with non-TX state: {}", state); markAsInvalid("Remote did not settled with TransactionState."); } } } }); session.begin(); assertTrue(session.isInTransaction()); AmqpMessage message = new AmqpMessage(); message.setText("Test-Message"); sender.send(message); session.commit(); sender.getStateInspector().assertValid(); connection.close(); } @Test(timeout = 30000) public void testBeginAndRollbackTransaction() throws Exception { AmqpClient client = createAmqpClient(); AmqpConnection connection = addConnection(client.connect()); AmqpSession session = connection.createSession(); assertNotNull(session); session.begin(); assertTrue(session.isInTransaction()); session.rollback(); connection.close(); System.err.println("Closed"); } @Test(timeout = 60000) public void testSendMessageToQueueWithCommit() throws Exception { AmqpClient client = createAmqpClient(); AmqpConnection connection = addConnection(client.connect()); AmqpSession session = connection.createSession(); AmqpSender sender = session.createSender(getQueueName()); final Queue queue = getProxyToQueue(getQueueName()); session.begin(); AmqpMessage message = new AmqpMessage(); message.setText("Test-Message"); sender.send(message); assertEquals(0, queue.getMessageCount()); session.commit(); assertTrue("Message was not queued", Wait.waitFor(() -> queue.getMessageCount() == 1)); sender.close(); connection.close(); } @Test(timeout = 60000) public void testSendMessageToQueueWithRollback() throws Exception { AmqpClient client = createAmqpClient(); AmqpConnection connection = addConnection(client.connect()); AmqpSession session = connection.createSession(); AmqpSender sender = session.createSender(getQueueName()); final Queue queue = getProxyToQueue(getQueueName()); session.begin(); AmqpMessage message = new AmqpMessage(); message.setText("Test-Message"); sender.send(message); assertEquals(0, queue.getMessageCount()); session.rollback(); assertEquals(0, queue.getMessageCount()); sender.close(); connection.close(); } @Test(timeout = 60000) public void testReceiveMessageWithCommit() throws Exception { AmqpClient client = createAmqpClient(); AmqpConnection connection = addConnection(client.connect()); AmqpSession session = connection.createSession(); AmqpSender sender = session.createSender(getQueueName()); final Queue queue = getProxyToQueue(getQueueName()); AmqpMessage message = new AmqpMessage(); message.setText("Test-Message"); sender.send(message); assertTrue("Message did not arrive", Wait.waitFor(() -> queue.getMessageCount() == 1)); AmqpReceiver receiver = session.createReceiver(getQueueName()); session.begin(); receiver.flow(1); AmqpMessage received = receiver.receive(5, TimeUnit.SECONDS); assertNotNull(received); received.accept(); session.commit(); assertEquals(0, queue.getMessageCount()); sender.close(); connection.close(); } @Test(timeout = 60000) public void testReceiveAfterConnectionClose() throws Exception { AmqpClient client = createAmqpClient(); AmqpConnection connection = addConnection(client.connect()); AmqpSession session = connection.createSession(); AmqpSender sender = session.createSender(getQueueName()); final Queue queue = getProxyToQueue(getQueueName()); AmqpMessage message = new AmqpMessage(); message.setText("Test-Message"); sender.send(message); assertTrue("Message did not arrive", Wait.waitFor(() -> queue.getMessageCount() == 1)); AmqpReceiver receiver = session.createReceiver(getQueueName()); session.begin(); receiver.flow(1); AmqpMessage received = receiver.receive(5, TimeUnit.SECONDS); assertNotNull(received); received.accept(); // this will force a rollback on the TX (It should at least) connection.close(); connection = addConnection(client.connect()); session = connection.createSession(); receiver = session.createReceiver(getQueueName()); session.begin(); receiver.flow(1); received = receiver.receive(5, TimeUnit.SECONDS); assertNotNull(received); received.accept(); session.commit(); assertEquals(0, queue.getMessageCount()); connection.close(); } @Test(timeout = 60000) public void testReceiveMessageWithRollback() throws Exception { AmqpClient client = createAmqpClient(); AmqpConnection connection = addConnection(client.connect()); AmqpSession session = connection.createSession(); AmqpSender sender = session.createSender(getQueueName()); final Queue queue = getProxyToQueue(getQueueName()); AmqpMessage message = new AmqpMessage(); message.setText("Test-Message"); sender.send(message); assertTrue("Message did not arrive", Wait.waitFor(() -> queue.getMessageCount() == 1)); AmqpReceiver receiver = session.createReceiver(getQueueName()); session.begin(); receiver.flow(1); AmqpMessage received = receiver.receive(5, TimeUnit.SECONDS); assertNotNull(received); received.accept(); session.rollback(); assertEquals(1, queue.getMessageCount()); sender.close(); connection.close(); } @Test(timeout = 60000) public void testMultipleSessionReceiversInSingleTXNWithCommit() throws Exception { AmqpClient client = createAmqpClient(); AmqpConnection connection = addConnection(client.connect()); // Load up the Queue with some messages { AmqpSession session = connection.createSession(); AmqpSender sender = session.createSender(getQueueName()); AmqpMessage message = new AmqpMessage(); message.setText("Test-Message"); sender.send(message); sender.send(message); sender.send(message); sender.close(); } // Root TXN session controls all TXN send lifetimes. AmqpSession txnSession = connection.createSession(); // Create some sender sessions AmqpSession session1 = connection.createSession(); AmqpSession session2 = connection.createSession(); AmqpSession session3 = connection.createSession(); // Sender linked to each session AmqpReceiver receiver1 = session1.createReceiver(getQueueName()); AmqpReceiver receiver2 = session2.createReceiver(getQueueName()); AmqpReceiver receiver3 = session3.createReceiver(getQueueName()); final Queue queue = getProxyToQueue(getQueueName()); assertEquals(3, queue.getMessageCount()); // Begin the transaction that all senders will operate in. txnSession.begin(); assertTrue(txnSession.isInTransaction()); receiver1.flow(1); receiver2.flow(1); receiver3.flow(1); AmqpMessage message1 = receiver1.receive(5, TimeUnit.SECONDS); AmqpMessage message2 = receiver2.receive(5, TimeUnit.SECONDS); AmqpMessage message3 = receiver3.receive(5, TimeUnit.SECONDS); message1.accept(txnSession); message2.accept(txnSession); message3.accept(txnSession); assertEquals(3, queue.getMessageCount()); txnSession.commit(); assertEquals(0, queue.getMessageCount()); } @Test(timeout = 60000) public void testMultipleSessionReceiversInSingleTXNWithRollback() throws Exception { AmqpClient client = createAmqpClient(); AmqpConnection connection = addConnection(client.connect()); // Load up the Queue with some messages { AmqpSession session = connection.createSession(); AmqpSender sender = session.createSender(getQueueName()); AmqpMessage message = new AmqpMessage(); message.setText("Test-Message"); sender.send(message); sender.send(message); sender.send(message); sender.close(); } // Root TXN session controls all TXN send lifetimes. AmqpSession txnSession = connection.createSession(); // Create some sender sessions AmqpSession session1 = connection.createSession(); AmqpSession session2 = connection.createSession(); AmqpSession session3 = connection.createSession(); // Sender linked to each session AmqpReceiver receiver1 = session1.createReceiver(getQueueName()); AmqpReceiver receiver2 = session2.createReceiver(getQueueName()); AmqpReceiver receiver3 = session3.createReceiver(getQueueName()); final Queue queue = getProxyToQueue(getQueueName()); assertEquals(3, queue.getMessageCount()); // Begin the transaction that all senders will operate in. txnSession.begin(); assertTrue(txnSession.isInTransaction()); receiver1.flow(1); receiver2.flow(1); receiver3.flow(1); AmqpMessage message1 = receiver1.receive(5, TimeUnit.SECONDS); AmqpMessage message2 = receiver2.receive(5, TimeUnit.SECONDS); AmqpMessage message3 = receiver3.receive(5, TimeUnit.SECONDS); message1.accept(txnSession); message2.accept(txnSession); message3.accept(txnSession); assertEquals(3, queue.getMessageCount()); txnSession.rollback(); assertEquals(3, queue.getMessageCount()); } @Test(timeout = 60000) public void testMultipleSessionSendersInSingleTXNWithCommit() throws Exception { AmqpClient client = createAmqpClient(); AmqpConnection connection = addConnection(client.connect()); // Root TXN session controls all TXN send lifetimes. AmqpSession txnSession = connection.createSession(); // Create some sender sessions AmqpSession session1 = connection.createSession(); AmqpSession session2 = connection.createSession(); AmqpSession session3 = connection.createSession(); // Sender linked to each session AmqpSender sender1 = session1.createSender(getQueueName()); AmqpSender sender2 = session2.createSender(getQueueName()); AmqpSender sender3 = session3.createSender(getQueueName()); final Queue queue = getProxyToQueue(getQueueName()); assertEquals(0, queue.getMessageCount()); // Begin the transaction that all senders will operate in. txnSession.begin(); AmqpMessage message = new AmqpMessage(); message.setText("Test-Message"); assertTrue(txnSession.isInTransaction()); sender1.send(message, txnSession.getTransactionId()); sender2.send(message, txnSession.getTransactionId()); sender3.send(message, txnSession.getTransactionId()); assertEquals(0, queue.getMessageCount()); txnSession.commit(); assertEquals(3, queue.getMessageCount()); } @Test(timeout = 60000) public void testMultipleSessionSendersInSingleTXNWithRollback() throws Exception { AmqpClient client = createAmqpClient(); AmqpConnection connection = addConnection(client.connect()); // Root TXN session controls all TXN send lifetimes. AmqpSession txnSession = connection.createSession(); // Create some sender sessions AmqpSession session1 = connection.createSession(); AmqpSession session2 = connection.createSession(); AmqpSession session3 = connection.createSession(); // Sender linked to each session AmqpSender sender1 = session1.createSender(getQueueName()); AmqpSender sender2 = session2.createSender(getQueueName()); AmqpSender sender3 = session3.createSender(getQueueName()); final Queue queue = getProxyToQueue(getQueueName()); assertEquals(0, queue.getMessageCount()); // Begin the transaction that all senders will operate in. txnSession.begin(); AmqpMessage message = new AmqpMessage(); message.setText("Test-Message"); assertTrue(txnSession.isInTransaction()); sender1.send(message, txnSession.getTransactionId()); sender2.send(message, txnSession.getTransactionId()); sender3.send(message, txnSession.getTransactionId()); assertEquals(0, queue.getMessageCount()); txnSession.rollback(); assertEquals(0, queue.getMessageCount()); } //----- Tests Ported from AmqpNetLite client -----------------------------// @Test(timeout = 60000) public void testSendersCommitAndRollbackWithMultipleSessionsInSingleTX() throws Exception { final int NUM_MESSAGES = 5; AmqpClient client = createAmqpClient(); AmqpConnection connection = addConnection(client.connect()); // Root TXN session controls all TXN send lifetimes. AmqpSession txnSession = connection.createSession(); // Normal Session which won't create an TXN itself AmqpSession session = connection.createSession(); AmqpSender sender = session.createSender(getQueueName()); // Commit TXN work from a sender. txnSession.begin(); for (int i = 0; i < NUM_MESSAGES; ++i) { AmqpMessage message = new AmqpMessage(); message.setText("Test-Message"); sender.send(message, txnSession.getTransactionId()); } txnSession.commit(); // Rollback an additional batch of TXN work from a sender. txnSession.begin(); for (int i = 0; i < NUM_MESSAGES; ++i) { AmqpMessage message = new AmqpMessage(); message.setText("Test-Message"); sender.send(message, txnSession.getTransactionId()); } txnSession.rollback(); // Commit more TXN work from a sender. txnSession.begin(); for (int i = 0; i < NUM_MESSAGES; ++i) { AmqpMessage message = new AmqpMessage(); message.setText("Test-Message"); sender.send(message, txnSession.getTransactionId()); } txnSession.commit(); AmqpReceiver receiver = session.createReceiver(getQueueName()); receiver.flow(NUM_MESSAGES * 2); for (int i = 0; i < NUM_MESSAGES * 2; ++i) { AmqpMessage message = receiver.receive(5, TimeUnit.SECONDS); assertNotNull(message); message.accept(txnSession); } connection.close(); } @Test(timeout = 60000) public void testReceiversCommitAndRollbackWithMultipleSessionsInSingleTX() throws Exception { final int NUM_MESSAGES = 10; AmqpClient client = createAmqpClient(); AmqpConnection connection = addConnection(client.connect()); try { // Root TXN session controls all TXN send lifetimes. AmqpSession txnSession = connection.createSession(); // Normal Session which won't create an TXN itself AmqpSession session = connection.createSession(); AmqpSender sender = session.createSender(getQueueName()); for (int i = 0; i < NUM_MESSAGES + 1; ++i) { AmqpMessage message = new AmqpMessage(); message.setText("Test-Message"); message.setApplicationProperty("msgId", i); sender.send(message, txnSession.getTransactionId()); } // Read all messages from the Queue, do not accept them yet. AmqpReceiver receiver = session.createReceiver(getQueueName()); ArrayList<AmqpMessage> messages = new ArrayList<>(NUM_MESSAGES); receiver.flow((NUM_MESSAGES + 2) * 2); for (int i = 0; i < NUM_MESSAGES; ++i) { AmqpMessage message = receiver.receive(5, TimeUnit.SECONDS); assertNotNull(message); messages.add(message); } // Commit half the consumed messages txnSession.begin(); for (int i = 0; i < NUM_MESSAGES / 2; ++i) { messages.get(i).accept(txnSession); } txnSession.commit(); // Rollback the other half the consumed messages txnSession.begin(); for (int i = NUM_MESSAGES / 2; i < NUM_MESSAGES; ++i) { messages.get(i).accept(txnSession, false); } txnSession.rollback(); { AmqpMessage message = receiver.receive(5, TimeUnit.SECONDS); assertNotNull(message); assertEquals(NUM_MESSAGES, message.getApplicationProperty("msgId")); message.release(); } // The final message should still be pending. { AmqpMessage message = receiver.receive(5, TimeUnit.SECONDS); receiver.flow(1); assertNotNull(message); assertEquals(NUM_MESSAGES, message.getApplicationProperty("msgId")); message.release(); } } finally { connection.close(); } } @Test(timeout = 60000) public void testCommitAndRollbackWithMultipleSessionsInSingleTX() throws Exception { final int NUM_MESSAGES = 10; AmqpClient client = createAmqpClient(); AmqpConnection connection = addConnection(client.connect()); // Root TXN session controls all TXN send lifetimes. AmqpSession txnSession = connection.createSession(); // Normal Session which won't create an TXN itself AmqpSession session = connection.createSession(); AmqpSender sender = session.createSender(getQueueName()); for (int i = 0; i < NUM_MESSAGES; ++i) { AmqpMessage message = new AmqpMessage(); message.setText("Test-Message"); message.setApplicationProperty("msgId", i); sender.send(message, txnSession.getTransactionId()); } // Read all messages from the Queue, do not accept them yet. AmqpReceiver receiver = session.createReceiver(getQueueName()); receiver.flow(2); AmqpMessage message1 = receiver.receive(5, TimeUnit.SECONDS); AmqpMessage message2 = receiver.receive(5, TimeUnit.SECONDS); // Accept the first one in a TXN and send a new message in that TXN as well txnSession.begin(); { message1.accept(txnSession); AmqpMessage message = new AmqpMessage(); message.setText("Test-Message"); message.setApplicationProperty("msgId", NUM_MESSAGES); sender.send(message, txnSession.getTransactionId()); } txnSession.commit(); // Accept the second one in a TXN and send a new message in that TXN as well but rollback txnSession.begin(); { message2.accept(txnSession); AmqpMessage message = new AmqpMessage(); message.setText("Test-Message"); message.setApplicationProperty("msgId", NUM_MESSAGES + 1); sender.send(message, txnSession.getTransactionId()); } txnSession.rollback(); // Variation here from .NET code, the client settles the accepted message where // the .NET client does not and instead releases here to have it redelivered. receiver.flow(NUM_MESSAGES); for (int i = 1; i <= NUM_MESSAGES; ++i) { AmqpMessage message = receiver.receive(5, TimeUnit.SECONDS); assertNotNull(message); assertEquals(i, message.getApplicationProperty("msgId")); message.accept(); } // Should be nothing left. assertNull(receiver.receive(1, TimeUnit.SECONDS)); connection.close(); } @Test(timeout = 60000) public void testReceiversCommitAndRollbackWithMultipleSessionsInSingleTXNoSettlement() throws Exception { final int NUM_MESSAGES = 10; AmqpClient client = createAmqpClient(); AmqpConnection connection = client.connect(); try { // Root TXN session controls all TXN send lifetimes. AmqpSession txnSession = connection.createSession(); // Normal Session which won't create an TXN itself AmqpSession session = connection.createSession(); AmqpSender sender = session.createSender(getQueueName()); for (int i = 0; i < NUM_MESSAGES + 1; ++i) { AmqpMessage message = new AmqpMessage(); message.setText("Test-Message"); message.setApplicationProperty("msgId", i); sender.send(message, txnSession.getTransactionId()); } // Read all messages from the Queue, do not accept them yet. AmqpReceiver receiver = session.createReceiver(getQueueName()); ArrayList<AmqpMessage> messages = new ArrayList<>(NUM_MESSAGES); receiver.flow((NUM_MESSAGES + 2) * 2); for (int i = 0; i < NUM_MESSAGES; ++i) { AmqpMessage message = receiver.receive(5, TimeUnit.SECONDS); System.out.println("Read message: " + message.getApplicationProperty("msgId")); assertNotNull(message); messages.add(message); } // Commit half the consumed messages [0, 1, 2, 3, 4] txnSession.begin(); for (int i = 0; i < NUM_MESSAGES / 2; ++i) { System.out.println("Commit: Accepting message: " + messages.get(i).getApplicationProperty("msgId")); messages.get(i).accept(txnSession, false); } txnSession.commit(); // Rollback the other half the consumed messages [5, 6, 7, 8, 9] txnSession.begin(); for (int i = NUM_MESSAGES / 2; i < NUM_MESSAGES; ++i) { System.out.println("Rollback: Accepting message: " + messages.get(i).getApplicationProperty("msgId")); messages.get(i).accept(txnSession, false); } txnSession.rollback(); // After rollback messages should still be acquired so we read last sent message [10] { AmqpMessage message = receiver.receive(5, TimeUnit.SECONDS); System.out.println("Read message: " + message.getApplicationProperty("msgId")); assertNotNull(message); assertEquals(NUM_MESSAGES, message.getApplicationProperty("msgId")); message.release(); } // Commit the other half the consumed messages [5, 6, 7, 8, 9] which should still be acquired txnSession.begin(); for (int i = NUM_MESSAGES / 2; i < NUM_MESSAGES; ++i) { messages.get(i).accept(txnSession); } txnSession.commit(); // The final message [10] should still be pending as we released it previously and committed // the previously accepted but not settled messages [5, 6, 7, 8, 9] in a new TX { receiver.flow(1); AmqpMessage message = receiver.receive(5, TimeUnit.SECONDS); System.out.println("Read message: " + message.getApplicationProperty("msgId")); assertNotNull(message); assertEquals(NUM_MESSAGES, message.getApplicationProperty("msgId")); message.accept(); } // We should have now drained the Queue receiver.flow(1); AmqpMessage message = receiver.receive(5, TimeUnit.SECONDS); if (message != null) { System.out.println("Read message: " + message.getApplicationProperty("msgId")); } assertNull(message); } finally { connection.close(); } } @Test(timeout = 60000) public void testCommitAndRollbackWithMultipleSessionsInSingleTXNoSettlement() throws Exception { final int NUM_MESSAGES = 10; AmqpClient client = createAmqpClient(); AmqpConnection connection = client.connect(); // Root TXN session controls all TXN send lifetimes. AmqpSession txnSession = connection.createSession(); // Normal Session which won't create an TXN itself AmqpSession session = connection.createSession(); AmqpSender sender = session.createSender(getQueueName()); for (int i = 0; i < NUM_MESSAGES; ++i) { AmqpMessage message = new AmqpMessage(); message.setText("Test-Message"); message.setApplicationProperty("msgId", i); sender.send(message, txnSession.getTransactionId()); } // Read all messages from the Queue, do not accept them yet. AmqpReceiver receiver = session.createReceiver(getQueueName()); receiver.flow(2); AmqpMessage message1 = receiver.receive(5, TimeUnit.SECONDS); AmqpMessage message2 = receiver.receive(5, TimeUnit.SECONDS); // Accept the first one in a TXN and send a new message in that TXN as well txnSession.begin(); { // This will result in message [0[ being consumed once we commit. message1.accept(txnSession, false); System.out.println("Commit: accepting message: " + message1.getApplicationProperty("msgId")); AmqpMessage message = new AmqpMessage(); message.setText("Test-Message"); message.setApplicationProperty("msgId", NUM_MESSAGES); sender.send(message, txnSession.getTransactionId()); } txnSession.commit(); // Accept the second one in a TXN and send a new message in that TXN as well but rollback txnSession.begin(); { message2.accept(txnSession, false); System.out.println("Rollback: accepting message: " + message2.getApplicationProperty("msgId")); AmqpMessage message = new AmqpMessage(); message.setText("Test-Message"); message.setApplicationProperty("msgId", NUM_MESSAGES + 1); sender.send(message, txnSession.getTransactionId()); } txnSession.rollback(); // This releases message [1] message2.release(); // Should be ten message available for dispatch given that we sent and committed one, and // releases another we had previously received. receiver.flow(10); for (int i = 1; i <= NUM_MESSAGES; ++i) { AmqpMessage message = receiver.receive(5, TimeUnit.SECONDS); assertNotNull("Expected a message for: " + i, message); System.out.println("Accepting message: " + message.getApplicationProperty("msgId")); assertEquals(i, message.getApplicationProperty("msgId")); message.accept(); } // Should be nothing left. receiver.flow(1); assertNull(receiver.receive(1, TimeUnit.SECONDS)); connection.close(); } @Test(timeout = 120000) public void testSendPersistentTX() throws Exception { int MESSAGE_COUNT = 2000; AtomicInteger errors = new AtomicInteger(0); server.createQueue(SimpleString.toSimpleString("q1"), RoutingType.ANYCAST, SimpleString.toSimpleString("q1"), null, true, false, 1, false, true); ConnectionFactory factory = new JmsConnectionFactory("amqp://localhost:" + AMQP_PORT); Connection sendConnection = factory.createConnection(); Connection consumerConnection = factory.createConnection(); try { Thread receiverThread = new Thread() { @Override public void run() { try { consumerConnection.start(); Session consumerSession = consumerConnection.createSession(true, Session.SESSION_TRANSACTED); javax.jms.Queue q1 = consumerSession.createQueue("q1"); MessageConsumer consumer = consumerSession.createConsumer(q1); for (int i = 1; i <= MESSAGE_COUNT; i++) { Message message = consumer.receive(5000); if (message == null) { throw new IOException("No message read in time."); } if (i % 100 == 0) { if (i % 1000 == 0) System.out.println("Read message " + i); consumerSession.commit(); } } // Assure that all messages are consumed consumerSession.commit(); } catch (Exception e) { e.printStackTrace(); errors.incrementAndGet(); } } }; receiverThread.start(); Session sendingSession = sendConnection.createSession(true, Session.SESSION_TRANSACTED); javax.jms.Queue q1 = sendingSession.createQueue("q1"); MessageProducer producer = sendingSession.createProducer(q1); producer.setDeliveryDelay(DeliveryMode.NON_PERSISTENT); for (int i = 0; i < MESSAGE_COUNT; i++) { producer.send(sendingSession.createTextMessage("message " + i), DeliveryMode.PERSISTENT, Message.DEFAULT_PRIORITY, Message.DEFAULT_TIME_TO_LIVE); if (i % 100 == 0) { if (i % 1000 == 0) System.out.println("Sending " + i); sendingSession.commit(); } } sendingSession.commit(); receiverThread.join(50000); Assert.assertFalse(receiverThread.isAlive()); Assert.assertEquals(0, errors.get()); } catch (Exception e) { e.printStackTrace(); } finally { sendConnection.close(); consumerConnection.close(); } } @Test(timeout = 30000) public void testUnsettledTXMessageGetTransactedDispostion() throws Exception { AmqpClient client = createAmqpClient(); AmqpConnection connection = addConnection(client.connect()); AmqpSession session = connection.createSession(); assertNotNull(session); AmqpSender sender = session.createSender(getQueueName()); AmqpMessage message = new AmqpMessage(); message.setText("Test-Message"); sender.send(message); AmqpReceiver receiver = session.createReceiver(getQueueName()); receiver.setStateInspector(new AmqpValidator() { @Override public void inspectDeliveryUpdate(Sender sender, Delivery delivery) { if (delivery.remotelySettled()) { LOG.info("Receiver got delivery update for: {}", delivery); if (!(delivery.getRemoteState() instanceof TransactionalState)) { markAsInvalid("Transactionally acquire work no tagged as being in a transaction."); } else { TransactionalState txState = (TransactionalState) delivery.getRemoteState(); if (!(txState.getOutcome() instanceof Accepted)) { markAsInvalid("Transaction state lacks any outcome"); } else if (txState.getTxnId() == null) { markAsInvalid("Transaction state lacks any TX Id"); } } if (!(delivery.getLocalState() instanceof TransactionalState)) { markAsInvalid("Transactionally acquire work no tagged as being in a transaction."); } else { TransactionalState txState = (TransactionalState) delivery.getLocalState(); if (!(txState.getOutcome() instanceof Accepted)) { markAsInvalid("Transaction state lacks any outcome"); } else if (txState.getTxnId() == null) { markAsInvalid("Transaction state lacks any TX Id"); } } TransactionalState localTxState = (TransactionalState) delivery.getLocalState(); TransactionalState remoteTxState = (TransactionalState) delivery.getRemoteState(); if (!localTxState.getTxnId().equals(remoteTxState)) { markAsInvalid("Message not enrolled in expected transaction"); } } } }); session.begin(); assertTrue(session.isInTransaction()); receiver.flow(1); AmqpMessage received = receiver.receive(2, TimeUnit.SECONDS); assertNotNull(received); received.accept(false); session.commit(); sender.getStateInspector().assertValid(); connection.close(); } }