// 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 java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import org.junit.Test;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.AlreadyClosedException;
import com.rabbitmq.client.GetResponse;
import com.rabbitmq.client.ReturnListener;
import com.rabbitmq.client.test.BrokerTestCase;
import com.rabbitmq.utility.BlockingCell;
public class Routing extends BrokerTestCase
{
protected final String E = "MRDQ";
protected final String Q1 = "foo";
protected final String Q2 = "bar";
private volatile BlockingCell<Integer> returnCell;
protected void createResources() throws IOException {
channel.exchangeDeclare(E, "direct");
channel.queueDeclare(Q1, false, false, false, null);
channel.queueDeclare(Q2, false, false, false, null);
}
protected void releaseResources() throws IOException {
channel.queueDelete(Q1);
channel.queueDelete(Q2);
channel.exchangeDelete(E);
}
private void bind(String queue, String routingKey)
throws IOException
{
channel.queueBind(queue, E, routingKey);
}
private void check(String routingKey, boolean expectQ1, boolean expectQ2)
throws IOException
{
channel.basicPublish(E, routingKey, null, "mrdq".getBytes());
checkGet(Q1, expectQ1);
checkGet(Q2, expectQ2);
}
private void checkGet(String queue, boolean messageExpected)
throws IOException
{
GetResponse r = channel.basicGet(queue, true);
if (messageExpected) {
assertNotNull(r);
} else {
assertNull(r);
}
}
/**
* Tests the "default queue name" and "default routing key" pieces
* of the spec. See the doc for the "queue" and "routing key"
* fields of queue.bind.
*/
@Test public void mRDQRouting()
throws IOException
{
bind(Q1, "baz"); //Q1, "baz"
bind(Q1, ""); //Q1, ""
bind("", "baz"); //Q2, "baz"
bind("", ""); //Q2, Q2
check("", true, false);
check(Q1, false, false);
check(Q2, false, true);
check("baz", true, true);
}
/**
* If a queue has more than one binding to an exchange, it should
* NOT receive duplicate copies of a message that matches both
* bindings.
*/
@Test public void doubleBinding()
throws IOException
{
channel.queueBind(Q1, "amq.topic", "x.#");
channel.queueBind(Q1, "amq.topic", "#.x");
channel.basicPublish("amq.topic", "x.y", null, "x.y".getBytes());
checkGet(Q1, true);
checkGet(Q1, false);
channel.basicPublish("amq.topic", "y.x", null, "y.x".getBytes());
checkGet(Q1, true);
checkGet(Q1, false);
channel.basicPublish("amq.topic", "x.x", null, "x.x".getBytes());
checkGet(Q1, true);
checkGet(Q1, false);
}
@Test public void fanoutRouting() throws Exception {
List<String> queues = new ArrayList<String>();
for (int i = 0; i < 2; i++) {
String q = "Q-" + System.nanoTime();
channel.queueDeclare(q, false, true, true, null);
channel.queueBind(q, "amq.fanout", "");
queues.add(q);
}
channel.basicPublish("amq.fanout", System.nanoTime() + "",
null, "fanout".getBytes());
for (String q : queues) {
checkGet(q, true);
}
for (String q : queues) {
channel.queueDelete(q);
}
}
@Test public void topicRouting() throws Exception {
List<String> queues = new ArrayList<String>();
//100+ queues is the trigger point for bug20046
for (int i = 0; i < 100; i++) {
channel.queueDeclare();
AMQP.Queue.DeclareOk ok = channel.queueDeclare();
String q = ok.getQueue();
channel.queueBind(q, "amq.topic", "#");
queues.add(q);
}
channel.basicPublish("amq.topic", "", null, "topic".getBytes());
for (String q : queues) {
checkGet(q, true);
}
}
@Test public void headersRouting() throws Exception {
Map<String, Object> spec = new HashMap<String, Object>();
spec.put("h1", "12345");
spec.put("h2", "bar");
spec.put("h3", null);
spec.put("x-match", "all");
channel.queueBind(Q1, "amq.match", "", spec);
spec.put("x-match", "any");
channel.queueBind(Q2, "amq.match", "", spec);
AMQP.BasicProperties.Builder props = new AMQP.BasicProperties.Builder();
channel.basicPublish("amq.match", "", null, "0".getBytes());
channel.basicPublish("amq.match", "", props.build(), "0b".getBytes());
Map<String, Object> map = new HashMap<String, Object>();
props.headers(map);
map.clear();
map.put("h1", "12345");
channel.basicPublish("amq.match", "", props.build(), "1".getBytes());
map.clear();
map.put("h1", "12345");
channel.basicPublish("amq.match", "", props.build(), "1b".getBytes());
map.clear();
map.put("h2", "bar");
channel.basicPublish("amq.match", "", props.build(), "2".getBytes());
map.clear();
map.put("h1", "12345");
map.put("h2", "bar");
channel.basicPublish("amq.match", "", props.build(), "3".getBytes());
map.clear();
map.put("h1", "12345");
map.put("h2", "bar");
map.put("h3", null);
channel.basicPublish("amq.match", "", props.build(), "4".getBytes());
map.clear();
map.put("h1", "12345");
map.put("h2", "quux");
channel.basicPublish("amq.match", "", props.build(), "5".getBytes());
map.clear();
map.put("h1", "zot");
map.put("h2", "quux");
map.put("h3", null);
channel.basicPublish("amq.match", "", props.build(), "6".getBytes());
map.clear();
map.put("h3", null);
channel.basicPublish("amq.match", "", props.build(), "7".getBytes());
map.clear();
map.put("h1", "zot");
map.put("h2", "quux");
channel.basicPublish("amq.match", "", props.build(), "8".getBytes());
checkGet(Q1, true); // 4
checkGet(Q1, false);
checkGet(Q2, true); // 1
checkGet(Q2, true); // 2
checkGet(Q2, true); // 3
checkGet(Q2, true); // 4
checkGet(Q2, true); // 5
checkGet(Q2, true); // 6
checkGet(Q2, true); // 7
checkGet(Q2, true); // 8
checkGet(Q2, false);
}
@Test public void basicReturn() throws IOException {
channel.addReturnListener(makeReturnListener());
returnCell = new BlockingCell<Integer>();
//returned 'mandatory' publish
channel.basicPublish("", "unknown", true, false, null, "mandatory1".getBytes());
checkReturn(AMQP.NO_ROUTE);
//routed 'mandatory' publish
channel.basicPublish("", Q1, true, false, null, "mandatory2".getBytes());
assertNotNull(channel.basicGet(Q1, true));
//'immediate' publish
channel.basicPublish("", Q1, false, true, null, "immediate".getBytes());
try {
channel.basicQos(0); //flush
fail("basic.publish{immediate=true} should not be supported");
} catch (IOException ioe) {
checkShutdownSignal(AMQP.NOT_IMPLEMENTED, ioe);
} catch (AlreadyClosedException ioe) {
checkShutdownSignal(AMQP.NOT_IMPLEMENTED, ioe);
}
}
@Test public void basicReturnTransactional() throws IOException {
channel.txSelect();
channel.addReturnListener(makeReturnListener());
returnCell = new BlockingCell<Integer>();
//returned 'mandatory' publish
channel.basicPublish("", "unknown", true, false, null, "mandatory1".getBytes());
try {
returnCell.uninterruptibleGet(200);
fail("basic.return issued prior to tx.commit");
} catch (TimeoutException toe) {}
channel.txCommit();
checkReturn(AMQP.NO_ROUTE);
//routed 'mandatory' publish
channel.basicPublish("", Q1, true, false, null, "mandatory2".getBytes());
channel.txCommit();
assertNotNull(channel.basicGet(Q1, true));
//returned 'mandatory' publish when message is routable on
//publish but not on commit
channel.basicPublish("", Q1, true, false, null, "mandatory2".getBytes());
channel.queueDelete(Q1);
channel.txCommit();
checkReturn(AMQP.NO_ROUTE);
channel.queueDeclare(Q1, false, false, false, null);
}
protected ReturnListener makeReturnListener() {
return new ReturnListener() {
public void handleReturn(int replyCode,
String replyText,
String exchange,
String routingKey,
AMQP.BasicProperties properties,
byte[] body)
throws IOException {
Routing.this.returnCell.set(replyCode);
}
};
}
protected void checkReturn(int replyCode) {
assertEquals((int)returnCell.uninterruptibleGet(), AMQP.NO_ROUTE);
returnCell = new BlockingCell<Integer>();
}
}