// 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 java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.junit.Test;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.GetResponse;
import com.rabbitmq.client.ReturnListener;
import com.rabbitmq.client.test.BrokerTestCase;
public class AlternateExchange extends BrokerTestCase
{
static private final String[] resources = new String[]{"x","u","v"};
static private final String[] keys = new String[]{"x","u","v","z"};
static private final boolean[] unrouted = new boolean[] {false, false, false};
private final AtomicBoolean gotReturn = new AtomicBoolean();
/**
* Determine which of the queues in our test configuration we
* expect a message with routing key <code>key</code> to get
* delivered to: the queue (if any) named <code>key</code>.
*
* @param key the routing key of the message
* @return an array of booleans that when zipped with {@link
* #resources} indicates whether the messages is expected to be
* routed to the respective queue
*/
private static boolean[] expected(String key) {
boolean[] expected = new boolean[resources.length];
for (int i = 0; i < resources.length; i++) {
expected[i] = resources[i].equals(key);
}
return expected;
}
@Override public void setUp() throws IOException, TimeoutException {
super.setUp();
channel.addReturnListener(new ReturnListener() {
public void handleReturn(int replyCode,
String replyText,
String exchange,
String routingKey,
AMQP.BasicProperties properties,
byte[] body)
throws IOException {
gotReturn.set(true);
}
});
}
@Override protected void createResources() throws IOException {
for (String q : resources) {
channel.queueDeclare(q, false, false, false, null);
}
}
@Override protected void releaseResources() throws IOException {
for (String r : resources) {
channel.queueDelete(r);
// declared by setupRouting
channel.exchangeDelete(r);
}
}
/**
* Declare an direct exchange <code>name</code> with an
* alternate-exchange <code>ae</code> and bind the queue
* <code>name</code> to it with a binding key of
* <code>name</code>.
*
* @param name the name of the exchange to be created, and queue
* to be bound
* @param ae the name of the alternate-exchage
*/
protected void setupRouting(String name, String ae) throws IOException {
Map<String, Object> args = new HashMap<String, Object>();
if (ae != null) args.put("alternate-exchange", ae);
channel.exchangeDelete(name);
channel.exchangeDeclare(name, "direct", false, false, args);
channel.queueBind(name, name, name);
}
protected void setupRouting() throws IOException {
setupRouting("x", "u");
setupRouting("u", "v");
setupRouting("v", "x");
}
protected void cleanup() throws IOException {
for (String e : resources) {
channel.exchangeDelete(e);
}
}
/**
* Perform an auto-acking 'basic.get' on each of the queues named
* in {@link #resources} and check whether a message can be
* retrieved when expected.
*
* @param expected an array of booleans that is zipped with {@link
* #resources} and indicates whether a messages is expected
* to be retrievable from the respective queue
*/
protected void checkGet(boolean[] expected) throws IOException {
for (int i = 0; i < resources.length; i++) {
String q = resources[i];
GetResponse r = channel.basicGet(q, true);
assertEquals("check " + q , expected[i], r != null);
}
}
/**
* Test whether a message is routed as expected.
*
* We publish a message to exchange 'x' with a routing key of
* <code>key</code>, check whether the message (actually, any
* message) can be retrieved from the queues named in {@link
* #resources} when expected, and whether a 'basic.return' is
* received when expected.
*
* @param key the routing key of the message to be sent
* @param mandatory whether the message should be marked as 'mandatory'
* @param expected indicates which queues we expect the message to
* get routed to
* @param ret whether a 'basic.return' is expected
*
* @see #checkGet(boolean[])
*/
protected void check(String key, boolean mandatory, boolean[] expected,
boolean ret)
throws IOException {
gotReturn.set(false);
channel.basicPublish("x", key, mandatory, false, null,
"ae-test".getBytes());
checkGet(expected);
assertEquals(ret, gotReturn.get());
}
protected void check(String key, boolean[] expected, boolean ret)
throws IOException {
check(key, false, expected, ret);
}
protected void check(String key, boolean mandatory, boolean ret)
throws IOException {
check(key, mandatory, expected(key), ret);
}
protected void check(String key, boolean ret) throws IOException {
check(key, false, ret);
}
/**
* check various cases of missing AEs - we expect to see some
* warnings in the server logs
*/
@Test public void missing() throws IOException {
setupRouting("x", "u");
check("x", false); //no warning
check("u", unrouted, false); //warning
setupRouting("u", "v");
check("u", false); //no warning
check("v", unrouted, false); //warning
setupRouting("v", null);
check("v", false); //no warning
check("z", unrouted, false); //no warning
cleanup();
}
@Test public void ae() throws IOException {
setupRouting();
for (String k : keys) {
//ordinary
check(k, false);
//mandatory
check(k, true, k.equals("z"));
}
cleanup();
}
@Test public void cycleBreaking() throws IOException {
setupRouting();
check("z", false);
cleanup();
}
}