// Copyright (c) 2007-Present Pivotal Software, Inc. All rights reserved. // // This software, the RabbitMQ Java client library, is triple-licensed under the // Mozilla Public License 1.1 ("MPL"), the GNU General Public License version 2 // ("GPL") and the Apache License version 2 ("ASL"). For the MPL, please see // LICENSE-MPL-RabbitMQ. For the GPL, please see LICENSE-GPL2. For the ASL, // please see LICENSE-APACHE2. // // This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, // either express or implied. See the LICENSE file for specific language governing // rights and limitations of this software. // // If you have any questions regarding licensing, please contact us at // info@rabbitmq.com. package com.rabbitmq.client.test.functional; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; import java.io.IOException; import org.junit.Test; import com.rabbitmq.client.AMQP; import com.rabbitmq.client.GetResponse; import com.rabbitmq.client.QueueingConsumer; import com.rabbitmq.client.test.BrokerTestCase; public abstract class TTLHandling extends BrokerTestCase { protected static final String TTL_EXCHANGE = "ttl.exchange"; protected static final String TTL_QUEUE_NAME = "queue.ttl"; protected static final String TTL_INVALID_QUEUE_NAME = "invalid.queue.ttl"; protected static final String[] MSG = {"one", "two", "three"}; @Override protected void createResources() throws IOException { this.channel.exchangeDeclare(TTL_EXCHANGE, "direct"); } @Override protected void releaseResources() throws IOException { this.channel.exchangeDelete(TTL_EXCHANGE); } @Test public void multipleTTLTypes() throws IOException { final Object[] args = { (((byte)200) & (0xff)), (short)200, 200, 200L }; for (Object ttl : args) { try { declareAndBindQueue(ttl); publishAndSync(MSG[0]); } catch(IOException ex) { fail("Should be able to use " + ttl.getClass().getName() + " when setting TTL"); } } } @Test public void invalidTypeUsedInTTL() throws Exception { try { declareAndBindQueue("foobar"); publishAndSync(MSG[0]); fail("Should not be able to set TTL using non-numeric values"); } catch (IOException e) { checkShutdownSignal(AMQP.PRECONDITION_FAILED, e); } } @Test public void trailingCharsUsedInTTL() throws Exception { try { declareAndBindQueue("10000foobar"); publishAndSync(MSG[0]); fail("Should not be able to set TTL using non-numeric values"); } catch (IOException e) { checkShutdownSignal(AMQP.PRECONDITION_FAILED, e); } } @Test public void tTLMustBePositive() throws Exception { try { declareAndBindQueue(-10); publishAndSync(MSG[0]); fail("Should not be able to set TTL using negative values"); } catch (IOException e) { checkShutdownSignal(AMQP.PRECONDITION_FAILED, e); } } @Test public void tTLAllowZero() throws Exception { try { declareQueue(0); publishAndSync(MSG[0]); } catch (IOException e) { fail("Should be able to set ttl to zero"); } } @Test public void messagesExpireWhenUsingBasicGet() throws Exception { declareAndBindQueue(200); publish(MSG[0]); Thread.sleep(1000); String what = get(); assertNull("expected message " + what + " to have been removed", what); } @Test public void publishAndGetWithExpiry() throws Exception { declareAndBindQueue(200); publish(MSG[0]); Thread.sleep(150); publish(MSG[1]); Thread.sleep(100); publish(MSG[2]); assertEquals(MSG[1], get()); assertEquals(MSG[2], get()); assertNull(get()); } @Test public void transactionalPublishWithGet() throws Exception { declareAndBindQueue(100); this.channel.txSelect(); publish(MSG[0]); Thread.sleep(150); publish(MSG[1]); this.channel.txCommit(); Thread.sleep(50); assertEquals(MSG[0], get()); Thread.sleep(80); assertNull(get()); } @Test public void expiryWithRequeue() throws Exception { declareAndBindQueue(200); publish(MSG[0]); Thread.sleep(100); publish(MSG[1]); publish(MSG[2]); expectBodyAndRemainingMessages(MSG[0], 2); expectBodyAndRemainingMessages(MSG[1], 1); closeChannel(); openChannel(); Thread.sleep(110); expectBodyAndRemainingMessages(MSG[1], 1); expectBodyAndRemainingMessages(MSG[2], 0); } /* * Test expiry of re-queued messages after being consumed instantly */ @Test public void expiryWithReQueueAfterConsume() throws Exception { declareAndBindQueue(100); QueueingConsumer c = new QueueingConsumer(channel); channel.basicConsume(TTL_QUEUE_NAME, c); publish(MSG[0]); assertNotNull(c.nextDelivery(100)); closeChannel(); Thread.sleep(150); openChannel(); assertNull("Re-queued message not expired", get()); } @Test public void zeroTTLDelivery() throws Exception { declareAndBindQueue(0); // when there is no consumer, message should expire publish(MSG[0]); assertNull(get()); // when there is a consumer, message should be delivered QueueingConsumer c = new QueueingConsumer(channel); channel.basicConsume(TTL_QUEUE_NAME, c); publish(MSG[0]); QueueingConsumer.Delivery d = c.nextDelivery(100); assertNotNull(d); // requeued messages should expire channel.basicReject(d.getEnvelope().getDeliveryTag(), true); assertNull(c.nextDelivery(100)); } protected void expectBodyAndRemainingMessages(String body, int messagesLeft) throws IOException { GetResponse response = channel.basicGet(TTL_QUEUE_NAME, false); assertNotNull(response); assertEquals(body, new String(response.getBody())); assertEquals(messagesLeft, response.getMessageCount()); } protected void declareAndBindQueue(Object ttlValue) throws IOException { declareQueue(ttlValue); bindQueue(); } protected void bindQueue() throws IOException { this.channel.queueBind(TTL_QUEUE_NAME, TTL_EXCHANGE, TTL_QUEUE_NAME); } protected AMQP.Queue.DeclareOk declareQueue(Object ttlValue) throws IOException { return declareQueue(TTL_QUEUE_NAME, ttlValue); } protected abstract AMQP.Queue.DeclareOk declareQueue(String name, Object ttlValue) throws IOException; protected String get() throws IOException { GetResponse response = basicGet(TTL_QUEUE_NAME); return response == null ? null : new String(response.getBody()); } protected void publish(final String msg) throws IOException { basicPublishVolatile(msg.getBytes(), TTL_EXCHANGE, TTL_QUEUE_NAME); } protected void publishAndSync(String msg) throws IOException { publish(msg); this.channel.basicQos(0); } }