/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS 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.transport.amqp.interop; import static org.apache.activemq.transport.amqp.AmqpSupport.JMS_SELECTOR_FILTER_IDS; import static org.apache.activemq.transport.amqp.AmqpSupport.NO_LOCAL_FILTER_IDS; import static org.apache.activemq.transport.amqp.AmqpSupport.findFilter; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.activemq.broker.jmx.DestinationViewMBean; import org.apache.activemq.broker.jmx.QueueViewMBean; import org.apache.activemq.junit.ActiveMQTestRunner; import org.apache.activemq.junit.Repeat; import org.apache.activemq.transport.amqp.client.AmqpClient; import org.apache.activemq.transport.amqp.client.AmqpClientTestSupport; 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.AmqpSession; import org.apache.activemq.transport.amqp.client.AmqpUnknownFilterType; import org.apache.activemq.transport.amqp.client.AmqpValidator; import org.apache.activemq.util.Wait; import org.apache.qpid.proton.amqp.DescribedType; import org.apache.qpid.proton.amqp.Symbol; import org.apache.qpid.proton.amqp.messaging.Source; import org.apache.qpid.proton.amqp.messaging.TerminusDurability; import org.apache.qpid.proton.amqp.messaging.TerminusExpiryPolicy; import org.apache.qpid.proton.amqp.transport.ReceiverSettleMode; import org.apache.qpid.proton.amqp.transport.SenderSettleMode; import org.apache.qpid.proton.engine.Receiver; import org.apache.qpid.proton.engine.Session; import org.apache.qpid.proton.message.Message; import org.junit.Test; import org.junit.runner.RunWith; /** * Test various behaviors of AMQP receivers with the broker. */ @RunWith(ActiveMQTestRunner.class) public class AmqpReceiverTest extends AmqpClientTestSupport { @Override protected boolean isUseOpenWireConnector() { return true; } @Test(timeout = 60000) public void testReceiverCloseSendsRemoteClose() throws Exception { AmqpClient client = createAmqpClient(); assertNotNull(client); final AtomicBoolean closed = new AtomicBoolean(); client.setValidator(new AmqpValidator() { @Override public void inspectClosedResource(Session session) { LOG.info("Session closed: {}", session.getContext()); } @Override public void inspectDetachedResource(Receiver receiver) { markAsInvalid("Broker should not detach receiver linked to closed session."); } @Override public void inspectClosedResource(Receiver receiver) { LOG.info("Receiver closed: {}", receiver.getContext()); closed.set(true); } }); AmqpConnection connection = trackConnection(client.connect()); assertNotNull(connection); AmqpSession session = connection.createSession(); assertNotNull(session); AmqpReceiver receiver = session.createReceiver("queue://" + getTestName()); assertNotNull(receiver); receiver.close(); assertTrue("Did not process remote close as expected", closed.get()); connection.getStateInspector().assertValid(); connection.close(); } @Test(timeout = 60000) public void testCreateQueueReceiver() throws Exception { AmqpClient client = createAmqpClient(); AmqpConnection connection = trackConnection(client.connect()); AmqpSession session = connection.createSession(); assertEquals(0, brokerService.getAdminView().getQueues().length); AmqpReceiver receiver = session.createReceiver("queue://" + getTestName()); assertEquals(1, brokerService.getAdminView().getQueues().length); assertNotNull(getProxyToQueue(getTestName())); assertEquals(1, brokerService.getAdminView().getQueueSubscribers().length); receiver.close(); assertEquals(0, brokerService.getAdminView().getQueueSubscribers().length); connection.close(); } @Test(timeout = 60000) public void testSenderSettlementModeSettledIsHonored() throws Exception { doTestSenderSettlementModeIsHonored(SenderSettleMode.SETTLED); } @Test(timeout = 60000) public void testSenderSettlementModeUnsettledIsHonored() throws Exception { doTestSenderSettlementModeIsHonored(SenderSettleMode.UNSETTLED); } @Test(timeout = 60000) public void testSenderSettlementModeMixedIsHonored() throws Exception { doTestSenderSettlementModeIsHonored(SenderSettleMode.MIXED); } public void doTestSenderSettlementModeIsHonored(SenderSettleMode settleMode) throws Exception { AmqpClient client = createAmqpClient(); AmqpConnection connection = trackConnection(client.connect()); AmqpSession session = connection.createSession(); assertEquals(0, brokerService.getAdminView().getQueues().length); AmqpReceiver receiver = session.createReceiver("queue://" + getTestName(), settleMode, ReceiverSettleMode.FIRST); assertEquals(1, brokerService.getAdminView().getQueues().length); assertNotNull(getProxyToQueue(getTestName())); assertEquals(1, brokerService.getAdminView().getQueueSubscribers().length); assertEquals(settleMode, receiver.getEndpoint().getRemoteSenderSettleMode()); receiver.close(); assertEquals(0, brokerService.getAdminView().getQueueSubscribers().length); connection.close(); } @Test(timeout = 60000) public void testReceiverSettlementModeSetToFirst() throws Exception { doTestReceiverSettlementModeForcedToFirst(ReceiverSettleMode.FIRST); } @Test(timeout = 60000) public void testReceiverSettlementModeSetToSecond() throws Exception { doTestReceiverSettlementModeForcedToFirst(ReceiverSettleMode.SECOND); } /* * The Broker does not currently support ReceiverSettleMode of SECOND so we ensure that * it always drops that back to FIRST to let the client know. The client will need to * check and react accordingly. */ private void doTestReceiverSettlementModeForcedToFirst(ReceiverSettleMode modeToUse) throws Exception { AmqpClient client = createAmqpClient(); AmqpConnection connection = trackConnection(client.connect()); AmqpSession session = connection.createSession(); assertEquals(0, brokerService.getAdminView().getQueues().length); AmqpReceiver receiver = session.createReceiver( "queue://" + getTestName(), SenderSettleMode.MIXED, modeToUse); assertEquals(1, brokerService.getAdminView().getQueues().length); assertNotNull(getProxyToQueue(getTestName())); assertEquals(1, brokerService.getAdminView().getQueueSubscribers().length); assertEquals(ReceiverSettleMode.FIRST, receiver.getEndpoint().getRemoteReceiverSettleMode()); receiver.close(); assertEquals(0, brokerService.getAdminView().getQueueSubscribers().length); connection.close(); } @Test(timeout = 60000) public void testCreateQueueReceiverWithJMSSelector() throws Exception { AmqpClient client = createAmqpClient(); client.setValidator(new AmqpValidator() { @SuppressWarnings("unchecked") @Override public void inspectOpenedResource(Receiver receiver) { LOG.info("Receiver opened: {}", receiver); if (receiver.getRemoteSource() == null) { markAsInvalid("Link opened with null source."); } Source source = (Source) receiver.getRemoteSource(); Map<Symbol, Object> filters = source.getFilter(); if (findFilter(filters, JMS_SELECTOR_FILTER_IDS) == null) { markAsInvalid("Broker did not return the JMS Filter on Attach"); } } }); AmqpConnection connection = trackConnection(client.connect()); AmqpSession session = connection.createSession(); assertEquals(0, brokerService.getAdminView().getQueues().length); session.createReceiver("queue://" + getTestName(), "JMSPriority > 8"); assertEquals(1, brokerService.getAdminView().getQueueSubscribers().length); connection.getStateInspector().assertValid(); connection.close(); } @Test(timeout = 60000) public void testCreateQueueReceiverWithNoLocalSet() throws Exception { AmqpClient client = createAmqpClient(); client.setValidator(new AmqpValidator() { @SuppressWarnings("unchecked") @Override public void inspectOpenedResource(Receiver receiver) { LOG.info("Receiver opened: {}", receiver); if (receiver.getRemoteSource() == null) { markAsInvalid("Link opened with null source."); } Source source = (Source) receiver.getRemoteSource(); Map<Symbol, Object> filters = source.getFilter(); if (findFilter(filters, NO_LOCAL_FILTER_IDS) == null) { markAsInvalid("Broker did not return the NoLocal Filter on Attach"); } } }); AmqpConnection connection = trackConnection(client.connect()); AmqpSession session = connection.createSession(); assertEquals(0, brokerService.getAdminView().getQueues().length); session.createReceiver("queue://" + getTestName(), null, true); assertEquals(1, brokerService.getAdminView().getQueueSubscribers().length); connection.getStateInspector().assertValid(); connection.close(); } @Test(timeout = 60000) public void testCreateTopicReceiver() throws Exception { AmqpClient client = createAmqpClient(); AmqpConnection connection = trackConnection(client.connect()); AmqpSession session = connection.createSession(); assertEquals(0, brokerService.getAdminView().getTopics().length); AmqpReceiver receiver = session.createReceiver("topic://" + getTestName()); assertEquals(1, brokerService.getAdminView().getTopics().length); assertNotNull(getProxyToTopic(getTestName())); assertEquals(1, brokerService.getAdminView().getTopicSubscribers().length); receiver.close(); assertEquals(0, brokerService.getAdminView().getTopicSubscribers().length); connection.close(); } @Test(timeout = 60000) public void testQueueReceiverReadMessage() throws Exception { sendMessages(getTestName(), 1, false); AmqpClient client = createAmqpClient(); AmqpConnection connection = trackConnection(client.connect()); AmqpSession session = connection.createSession(); AmqpReceiver receiver = session.createReceiver("queue://" + getTestName()); QueueViewMBean queueView = getProxyToQueue(getTestName()); assertEquals(1, queueView.getQueueSize()); assertEquals(0, queueView.getDispatchCount()); receiver.flow(1); assertNotNull(receiver.receive(5, TimeUnit.SECONDS)); receiver.close(); assertEquals(1, queueView.getQueueSize()); connection.close(); } @Test(timeout = 60000) @Repeat(repetitions = 1) public void testPresettledReceiverReadsAllMessages() throws Exception { final int MSG_COUNT = 100; sendMessages(getTestName(), MSG_COUNT, false); AmqpClient client = createAmqpClient(); AmqpConnection connection = trackConnection(client.connect()); AmqpSession session = connection.createSession(); AmqpReceiver receiver = session.createReceiver("queue://" + getTestName(), null, false, true); QueueViewMBean queueView = getProxyToQueue(getTestName()); assertEquals(MSG_COUNT, queueView.getQueueSize()); assertEquals(0, queueView.getDispatchCount()); receiver.flow(MSG_COUNT); for (int i = 0; i < MSG_COUNT; ++i) { assertNotNull(receiver.receive(5, TimeUnit.SECONDS)); } receiver.close(); assertEquals(0, queueView.getQueueSize()); connection.close(); } @Test(timeout = 60000) @Repeat(repetitions = 1) public void testPresettledReceiverReadsAllMessagesInNonFlowBatchQueue() throws Exception { doTestPresettledReceiverReadsAllMessagesInNonFlowBatch(false); } @Test(timeout = 60000) @Repeat(repetitions = 1) public void testPresettledReceiverReadsAllMessagesInNonFlowBatchTopic() throws Exception { doTestPresettledReceiverReadsAllMessagesInNonFlowBatch(true); } private void doTestPresettledReceiverReadsAllMessagesInNonFlowBatch(boolean topic) throws Exception { final String destinationName; if (topic) { destinationName = "topic://" + getTestName(); } else { destinationName = "queue://" + getTestName(); } final int MSG_COUNT = 100; AmqpClient client = createAmqpClient(); AmqpConnection connection = trackConnection(client.connect()); AmqpSession session = connection.createSession(); AmqpReceiver receiver = session.createReceiver(destinationName, null, false, true); sendMessages(getTestName(), MSG_COUNT, topic); final DestinationViewMBean destinationView; if (topic) { destinationView = getProxyToTopic(getTestName()); } else { destinationView = getProxyToQueue(getTestName()); } assertEquals(MSG_COUNT, destinationView.getEnqueueCount()); assertEquals(0, destinationView.getDispatchCount()); receiver.flow(20); // consume less that flow for (int j=0;j<10;j++) { assertNotNull(receiver.receive(5, TimeUnit.SECONDS)); } // flow more and consume all receiver.flow(10); for (int j=0;j<20;j++) { assertNotNull(receiver.receive(5, TimeUnit.SECONDS)); } // remainder receiver.flow(70); for (int j=0;j<70;j++) { assertNotNull(receiver.receive(5, TimeUnit.SECONDS)); } receiver.close(); assertEquals(0, destinationView.getEnqueueCount() - destinationView.getDequeueCount()); connection.close(); } @Test(timeout = 60000) @Repeat(repetitions = 1) public void testTwoQueueReceiversOnSameConnectionReadMessagesNoDispositions() throws Exception { int MSG_COUNT = 4; sendMessages(getTestName(), MSG_COUNT, false); AmqpClient client = createAmqpClient(); AmqpConnection connection = trackConnection(client.connect()); AmqpSession session = connection.createSession(); AmqpReceiver receiver1 = session.createReceiver("queue://" + getTestName()); QueueViewMBean queueView = getProxyToQueue(getTestName()); assertEquals(MSG_COUNT, queueView.getQueueSize()); receiver1.flow(2); assertNotNull(receiver1.receive(5, TimeUnit.SECONDS)); assertNotNull(receiver1.receive(5, TimeUnit.SECONDS)); AmqpReceiver receiver2 = session.createReceiver("queue://" + getTestName()); assertEquals(2, brokerService.getAdminView().getQueueSubscribers().length); receiver2.flow(2); assertNotNull(receiver2.receive(5, TimeUnit.SECONDS)); assertNotNull(receiver2.receive(5, TimeUnit.SECONDS)); assertEquals(MSG_COUNT, queueView.getDispatchCount()); assertEquals(0, queueView.getDequeueCount()); receiver1.close(); receiver2.close(); assertEquals(MSG_COUNT, queueView.getQueueSize()); connection.close(); } @Test(timeout = 60000) public void testTwoQueueReceiversOnSameConnectionReadMessagesAcceptOnEach() throws Exception { int MSG_COUNT = 4; sendMessages(getTestName(), MSG_COUNT, false); AmqpClient client = createAmqpClient(); AmqpConnection connection = trackConnection(client.connect()); AmqpSession session = connection.createSession(); AmqpReceiver receiver1 = session.createReceiver("queue://" + getTestName()); final QueueViewMBean queueView = getProxyToQueue(getTestName()); assertEquals(MSG_COUNT, queueView.getQueueSize()); receiver1.flow(2); AmqpMessage message = receiver1.receive(5, TimeUnit.SECONDS); assertNotNull(message); message.accept(); message = receiver1.receive(5, TimeUnit.SECONDS); assertNotNull(message); message.accept(); assertTrue("Should have ack'd two", Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisified() throws Exception { return queueView.getDequeueCount() == 2; } }, TimeUnit.SECONDS.toMillis(5), TimeUnit.MILLISECONDS.toMillis(50))); AmqpReceiver receiver2 = session.createReceiver("queue://" + getTestName()); assertEquals(2, brokerService.getAdminView().getQueueSubscribers().length); receiver2.flow(2); message = receiver2.receive(5, TimeUnit.SECONDS); assertNotNull(message); message.accept(); message = receiver2.receive(5, TimeUnit.SECONDS); assertNotNull(message); message.accept(); assertEquals(MSG_COUNT, queueView.getDispatchCount()); assertTrue("Queue should be empty now", Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisified() throws Exception { return queueView.getDequeueCount() == 4; } }, TimeUnit.SECONDS.toMillis(15), TimeUnit.MILLISECONDS.toMillis(10))); receiver1.close(); receiver2.close(); assertEquals(0, queueView.getQueueSize()); connection.close(); } @Test(timeout = 60000) public void testSecondReceiverOnQueueGetsAllUnconsumedMessages() throws Exception { int MSG_COUNT = 20; sendMessages(getTestName(), MSG_COUNT, false); AmqpClient client = createAmqpClient(); AmqpConnection connection = trackConnection(client.connect()); AmqpSession session = connection.createSession(); AmqpReceiver receiver1 = session.createReceiver("queue://" + getTestName()); final QueueViewMBean queueView = getProxyToQueue(getTestName()); assertEquals(MSG_COUNT, queueView.getQueueSize()); receiver1.flow(20); assertTrue("Should have dispatch to prefetch", Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisified() throws Exception { return queueView.getInFlightCount() >= 2; } }, TimeUnit.SECONDS.toMillis(5), TimeUnit.MILLISECONDS.toMillis(50))); receiver1.close(); AmqpReceiver receiver2 = session.createReceiver("queue://" + getTestName()); assertEquals(1, brokerService.getAdminView().getQueueSubscribers().length); receiver2.flow(MSG_COUNT * 2); AmqpMessage message = receiver2.receive(5, TimeUnit.SECONDS); assertNotNull(message); message.accept(); message = receiver2.receive(5, TimeUnit.SECONDS); assertNotNull(message); message.accept(); assertTrue("Should have ack'd two", Wait.waitFor(new Wait.Condition() { @Override public boolean isSatisified() throws Exception { return queueView.getDequeueCount() == 2; } }, TimeUnit.SECONDS.toMillis(5), TimeUnit.MILLISECONDS.toMillis(50))); receiver2.close(); assertEquals(MSG_COUNT - 2, queueView.getQueueSize()); connection.close(); } @Test(timeout = 60000) public void testUnsupportedFiltersAreNotListedAsSupported() throws Exception { AmqpClient client = createAmqpClient(); client.setValidator(new AmqpValidator() { @SuppressWarnings("unchecked") @Override public void inspectOpenedResource(Receiver receiver) { LOG.info("Receiver opened: {}", receiver); if (receiver.getRemoteSource() == null) { markAsInvalid("Link opened with null source."); } Source source = (Source) receiver.getRemoteSource(); Map<Symbol, Object> filters = source.getFilter(); if (findFilter(filters, AmqpUnknownFilterType.UNKNOWN_FILTER_IDS) != null) { markAsInvalid("Broker should not return unsupported filter on attach."); } } }); Map<Symbol, DescribedType> filters = new HashMap<>(); filters.put(AmqpUnknownFilterType.UNKNOWN_FILTER_NAME, AmqpUnknownFilterType.UNKOWN_FILTER); Source source = new Source(); source.setAddress("queue://" + getTestName()); source.setFilter(filters); source.setDurable(TerminusDurability.NONE); source.setExpiryPolicy(TerminusExpiryPolicy.LINK_DETACH); AmqpConnection connection = trackConnection(client.connect()); AmqpSession session = connection.createSession(); assertEquals(0, brokerService.getAdminView().getQueues().length); session.createReceiver(source); assertEquals(1, brokerService.getAdminView().getQueueSubscribers().length); connection.getStateInspector().assertValid(); connection.close(); } @Test(timeout = 30000) public void testReleasedDisposition() throws Exception { sendMessages(getTestName(), 1, false); AmqpClient client = createAmqpClient(); AmqpConnection connection = trackConnection(client.connect()); AmqpSession session = connection.createSession(); AmqpReceiver receiver = session.createReceiver(getTestName()); receiver.flow(2); AmqpMessage message = receiver.receive(5, TimeUnit.SECONDS); assertNotNull("did not receive message first time", message); Message protonMessage = message.getWrappedMessage(); assertNotNull(protonMessage); assertEquals("Unexpected initial value for AMQP delivery-count", 0, protonMessage.getDeliveryCount()); message.release(); // Read the message again and validate its state message = receiver.receive(10, TimeUnit.SECONDS); assertNotNull("did not receive message again", message); message.accept(); protonMessage = message.getWrappedMessage(); assertNotNull(protonMessage); assertEquals("Unexpected updated value for AMQP delivery-count", 0, protonMessage.getDeliveryCount()); connection.close(); } @Test(timeout = 30000) public void testRejectedDisposition() throws Exception { sendMessages(getTestName(), 1, false); AmqpClient client = createAmqpClient(); AmqpConnection connection = trackConnection(client.connect()); AmqpSession session = connection.createSession(); AmqpReceiver receiver = session.createReceiver(getTestName()); receiver.flow(2); AmqpMessage message = receiver.receive(5, TimeUnit.SECONDS); assertNotNull("did not receive message first time", message); Message protonMessage = message.getWrappedMessage(); assertNotNull(protonMessage); assertEquals("Unexpected initial value for AMQP delivery-count", 0, protonMessage.getDeliveryCount()); message.reject(); // Attempt to read the message again but should not get it. message = receiver.receive(2, TimeUnit.SECONDS); assertNull("shoudl not receive message again", message); connection.close(); } @Test(timeout = 30000) public void testModifiedDispositionWithDeliveryFailedWithoutUndeliverableHereFieldsSet() throws Exception { doModifiedDispositionTestImpl(Boolean.TRUE, null); } @Test(timeout = 30000) public void testModifiedDispositionWithoutDeliveryFailedWithoutUndeliverableHereFieldsSet() throws Exception { doModifiedDispositionTestImpl(null, null); } @Test(timeout = 30000) public void testModifiedDispositionWithoutDeliveryFailedWithUndeliverableHereFieldsSet() throws Exception { doModifiedDispositionTestImpl(null, Boolean.TRUE); } @Test(timeout = 30000) public void testModifiedDispositionWithDeliveryFailedWithUndeliverableHereFieldsSet() throws Exception { doModifiedDispositionTestImpl(Boolean.TRUE, Boolean.TRUE); } private void doModifiedDispositionTestImpl(Boolean deliveryFailed, Boolean undeliverableHere) throws Exception { int msgCount = 1; sendMessages(getTestName(), msgCount, false); AmqpClient client = createAmqpClient(); AmqpConnection connection = trackConnection(client.connect()); AmqpSession session = connection.createSession(); AmqpReceiver receiver = session.createReceiver("queue://" + getTestName()); receiver.flow(2 * msgCount); AmqpMessage message = receiver.receive(5, TimeUnit.SECONDS); assertNotNull("did not receive message first time", message); Message protonMessage = message.getWrappedMessage(); assertNotNull(protonMessage); assertEquals("Unexpected initial value for AMQP delivery-count", 0, protonMessage.getDeliveryCount()); message.modified(deliveryFailed, undeliverableHere); if(Boolean.TRUE.equals(undeliverableHere)) { message = receiver.receive(250, TimeUnit.MILLISECONDS); assertNull("Should not receive message again", message); } else { message = receiver.receive(5, TimeUnit.SECONDS); assertNotNull("did not receive message again", message); int expectedDeliveryCount = 0; if(Boolean.TRUE.equals(deliveryFailed)) { expectedDeliveryCount = 1; } message.accept(); Message protonMessage2 = message.getWrappedMessage(); assertNotNull(protonMessage2); assertEquals("Unexpected updated value for AMQP delivery-count", expectedDeliveryCount, protonMessage2.getDeliveryCount()); } receiver.close(); connection.close(); } }