// 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);
}
}