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