/* * Copyright (c) 2011-2015 The original author or authors * ------------------------------------------------------ * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Apache License v2.0 is available at * http://www.opensource.org/licenses/apache2.0.php * * You may elect to redistribute this code under either of these licenses. */ package io.vertx.ext.stomp.impl; import com.jayway.awaitility.Awaitility; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; import io.vertx.core.eventbus.DeliveryOptions; import io.vertx.core.eventbus.Message; import io.vertx.core.eventbus.MessageConsumer; import io.vertx.core.json.JsonObject; import io.vertx.ext.bridge.PermittedOptions; import io.vertx.ext.stomp.*; import io.vertx.ext.stomp.utils.Headers; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import static org.assertj.core.api.Assertions.assertThat; /** * @author <a href="http://escoffier.me">Clement Escoffier</a> */ public class EventBusBridgeTest { private StompServer server; private Vertx vertx; private List<StompClient> clients = new ArrayList<>(); private List<MessageConsumer> consumers = new ArrayList<>(); @Before public void setUp() { AsyncLock<StompServer> lock = new AsyncLock<>(); vertx = Vertx.vertx(); server = StompServer.create(vertx) .handler(StompServerHandler.create(vertx) .bridge(new BridgeOptions() .addInboundPermitted(new PermittedOptions().setAddress("/bus")) .addOutboundPermitted(new PermittedOptions().setAddress("/bus"))) ) .listen(lock.handler()); lock.waitForSuccess(); } @After public void tearDown() { clients.forEach(StompClient::close); clients.clear(); consumers.forEach(MessageConsumer::unregister); consumers.clear(); AsyncLock<Void> lock = new AsyncLock<>(); server.close(lock.handler()); lock.waitForSuccess(); lock = new AsyncLock<>(); vertx.close(lock.handler()); lock.waitForSuccess(); } @Test public void testThatStompMessagesAreTransferredToTheEventBus() { AtomicReference<Message> reference = new AtomicReference<>(); consumers.add(vertx.eventBus().consumer("/bus", reference::set)); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.send("/bus", Headers.create("foo", "bar"), Buffer.buffer("Hello from STOMP")); })); Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> reference.get() != null); assertThat(reference.get().headers().get("foo")).isEqualTo("bar"); assertThat(reference.get().headers().get("destination")).isEqualTo("/bus"); assertThat(reference.get().headers().get("content-length")).isEqualTo("16"); assertThat(reference.get().address()).isEqualTo("/bus"); assertThat(reference.get().replyAddress()).isNullOrEmpty(); assertThat(reference.get().body().toString()).isEqualTo("Hello from STOMP"); } @Test public void testThatEventBusMessagesAreTransferredToStomp() { AtomicReference<Frame> reference = new AtomicReference<>(); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/bus", reference::set, f -> { vertx.eventBus().publish("/bus", "Hello from Vert.x", new DeliveryOptions().addHeader("foo", "bar")); } ); } )); Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> reference.get() != null); assertThat(reference.get().getHeaders().get("foo")).isEqualTo("bar"); assertThat(reference.get().getHeaders().get("destination")).isEqualTo("/bus"); assertThat(reference.get().getHeaders().get("content-length")).isEqualTo("17"); assertThat(reference.get().getBodyAsString()).isEqualTo("Hello from Vert.x"); } @Test public void testBidirectionalPingPong() { server.stompHandler().bridge(new BridgeOptions() .addInboundPermitted(new PermittedOptions().setAddressRegex("/toBu.")) .addOutboundPermitted(new PermittedOptions().setAddressRegex("/to.tomp")) ); List<Frame> stomp = new ArrayList<>(); List<Message> bus = new ArrayList<>(); consumers.add(vertx.eventBus().consumer("/toBus", msg -> { bus.add(msg); if (bus.size() < 5) { vertx.eventBus().send("/toStomp", "pong"); } })); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/toStomp", frame -> { stomp.add(frame); if (stomp.size() < 4) { connection.send("/toBus", Buffer.buffer("ping")); } }, receipt -> { connection.send("/toBus", Buffer.buffer("ping")); }); })); Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> bus.size() == 4 && stomp.size() == 4); for (Frame frame : stomp) { assertThat(frame.getBodyAsString()).isEqualTo("pong"); } for (Message message : bus) { assertThat(message.body().toString()).isEqualTo("ping"); } } @Test public void testThatEventBusMessagesContainingJsonObjectAreTransferredToStomp() { AtomicReference<Frame> reference = new AtomicReference<>(); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/bus", reference::set, f -> { vertx.eventBus().publish("/bus", new JsonObject() .put("name", "vert.x") .put("count", 1) .put("bool", true), new DeliveryOptions().addHeader("foo", "bar")); } ); } )); Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> reference.get() != null); assertThat(reference.get().getHeaders().get("foo")).isEqualTo("bar"); assertThat(reference.get().getHeaders().get("destination")).isEqualTo("/bus"); JsonObject object = new JsonObject(reference.get().getBodyAsString()); assertThat(object.getString("name")).isEqualTo("vert.x"); assertThat(object.getInteger("count")).isEqualTo(1); assertThat(object.getBoolean("bool")).isTrue(); } @Test public void testThatEventBusMessagesContainingBufferAreTransferredToStomp() { AtomicReference<Frame> reference = new AtomicReference<>(); byte[] bytes = new byte[]{0, 1, 2, 3, 4, 5, 6}; clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/bus", reference::set, f -> { vertx.eventBus().publish("/bus", Buffer.buffer(bytes), new DeliveryOptions().addHeader("foo", "bar")); } ); } )); Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> reference.get() != null); assertThat(reference.get().getHeaders().get("foo")).isEqualTo("bar"); assertThat(reference.get().getHeaders().get("destination")).isEqualTo("/bus"); byte[] body = reference.get().getBody().getBytes(); assertThat(body).containsExactly(bytes); } @Test public void testThatEventBusMessagesContainingNoBodyAreTransferredToStomp() { AtomicReference<Frame> reference = new AtomicReference<>(); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/bus", reference::set, f -> { vertx.eventBus().publish("/bus", null, new DeliveryOptions().addHeader("foo", "bar")); } ); } )); Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> reference.get() != null); assertThat(reference.get().getHeaders().get("foo")).isEqualTo("bar"); assertThat(reference.get().getHeaders().get("destination")).isEqualTo("/bus"); byte[] body = reference.get().getBody().getBytes(); assertThat(body).hasSize(0); } @Test public void testThatTwoEventBusConsumersReceiveAStompMessage() { List<Message> messages = new CopyOnWriteArrayList<>(); consumers.add(vertx.eventBus().consumer("/bus", messages::add)); consumers.add(vertx.eventBus().consumer("/bus", messages::add)); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.send("/bus", Headers.create("foo", "bar"), Buffer.buffer("Hello from STOMP")); })); Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> messages.size() == 2); } @Test public void testThatOnlyOnEventBusConsumersReceiveAStompMessageInP2P() throws InterruptedException { server.stompHandler().bridge(new BridgeOptions() .addInboundPermitted(new PermittedOptions().setAddress("/toBus")) .setPointToPoint(true) ); List<Message> messages = new CopyOnWriteArrayList<>(); consumers.add(vertx.eventBus().consumer("/toBus", messages::add)); consumers.add(vertx.eventBus().consumer("/toBus", messages::add)); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.send("/toBus", Headers.create("foo", "bar"), Buffer.buffer("Hello from STOMP")); })); Thread.sleep(500); assertThat(messages).hasSize(1); } @Test public void testThatEventBusMessagesAreTransferredToSeveralStompClients() { List<Frame> frames = new CopyOnWriteArrayList<>(); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/bus", frames::add, f -> { clients.add(StompClient.create(vertx).connect(ar2 -> { final StompClientConnection connection2 = ar2.result(); connection2.subscribe("/bus", frames::add, receipt -> { vertx.eventBus().publish("/bus", "Hello from Vert.x", new DeliveryOptions().addHeader("foo", "bar")); }); })); }); })); Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> frames.size() == 2); } @Test public void testThatEventBusMessagesAreOnlyTransferredToOneStompClientsInP2P() throws InterruptedException { List<Frame> frames = new CopyOnWriteArrayList<>(); server.stompHandler().bridge(new BridgeOptions() .addOutboundPermitted(new PermittedOptions().setAddress("/toStomp")) .setPointToPoint(true) ); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/toStomp", frames::add, f -> { clients.add(StompClient.create(vertx).connect(ar2 -> { final StompClientConnection connection2 = ar2.result(); connection2.subscribe("/toStomp", frames::add, receipt -> { vertx.eventBus().publish("/toStomp", "Hello from Vert.x", new DeliveryOptions().addHeader("foo", "bar")); }); })); }); })); Thread.sleep(500); assertThat(frames).hasSize(1); } @Test public void testThatEventBusConsumerCanReplyToStompMessages() { server.stompHandler().bridge(new BridgeOptions() .addOutboundPermitted(new PermittedOptions().setAddress("/replyTo")) .addInboundPermitted(new PermittedOptions().setAddress("/request")) .setPointToPoint(true) ); AtomicReference<Frame> response = new AtomicReference<>(); consumers.add(vertx.eventBus().consumer("/request", msg -> { msg.reply("pong"); })); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/replyTo", response::set, r1 -> { connection.send("/request", Headers.create("reply-address", "/replyTo"), Buffer.buffer("ping")); }); })); Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> response.get() != null); } @Test public void testThatStompClientCanUnsubscribe() throws InterruptedException { List<Frame> frames = new ArrayList<>(); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/bus", frame -> { frames.add(frame); connection.unsubscribe("/bus"); }, f -> { vertx.eventBus().publish("/bus", "Hello from Vert.x", new DeliveryOptions().addHeader("foo", "bar")); } ); } )); Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> frames.size() == 1); // Send another message vertx.eventBus().publish("/bus", "Hello from Vert.x", new DeliveryOptions().addHeader("foo", "bar")); Thread.sleep(500); assertThat(frames).hasSize(1); } @Test public void testThatStompClientCanCloseTheConnection() throws InterruptedException { List<Frame> frames = new ArrayList<>(); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/bus", frame -> { frames.add(frame); connection.close(); }, f -> { vertx.eventBus().publish("/bus", "Hello from Vert.x", new DeliveryOptions().addHeader("foo", "bar")); } ); } )); Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> frames.size() == 1); // Send another message vertx.eventBus().publish("/bus", "Hello from Vert.x", new DeliveryOptions().addHeader("foo", "bar")); Thread.sleep(500); assertThat(frames).hasSize(1); } @Test public void testThatStompClientCanDisconnect() throws InterruptedException { List<Frame> frames = new ArrayList<>(); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/bus", frame -> { frames.add(frame); connection.disconnect(); }, f -> { vertx.eventBus().publish("/bus", "Hello from Vert.x", new DeliveryOptions().addHeader("foo", "bar")); } ); } )); Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> frames.size() == 1); // Send another message vertx.eventBus().publish("/bus", "Hello from Vert.x", new DeliveryOptions().addHeader("foo", "bar")); Thread.sleep(500); assertThat(frames).hasSize(1); } @Test public void testThatStompFrameMatchingTheStructureAreTransferred() { tearDown(); AsyncLock<StompServer> lock = new AsyncLock<>(); vertx = Vertx.vertx(); server = StompServer.create(vertx) .handler(StompServerHandler.create(vertx) .bridge(new BridgeOptions() .addInboundPermitted(new PermittedOptions().setAddress("/bus").setMatch(new JsonObject().put("id", 1))) .addOutboundPermitted(new PermittedOptions().setAddress("/bus"))) ) .listen(lock.handler()); lock.waitForSuccess(); AtomicReference<Message> reference = new AtomicReference<>(); consumers.add(vertx.eventBus().consumer("/bus", reference::set)); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.send("/bus", Headers.create("foo", "bar"), Buffer.buffer(new JsonObject().put("id", 1).put("msg", "Hello from STOMP").toString())); })); Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> reference.get() != null); assertThat(reference.get().headers().get("foo")).isEqualTo("bar"); assertThat(reference.get().headers().get("destination")).isEqualTo("/bus"); assertThat(reference.get().address()).isEqualTo("/bus"); assertThat(reference.get().replyAddress()).isNullOrEmpty(); JsonObject json = new JsonObject(reference.get().body().toString()); assertThat(json.getString("msg")).isEqualTo("Hello from STOMP"); } @Test public void testThatStompFrameNotMatchingTheStructureAreRejected() throws InterruptedException { tearDown(); AsyncLock<StompServer> lock = new AsyncLock<>(); vertx = Vertx.vertx(); server = StompServer.create(vertx) .handler(StompServerHandler.create(vertx) .bridge(new BridgeOptions() .addInboundPermitted(new PermittedOptions().setAddress("/bus").setMatch(new JsonObject().put("id", 2))) .addOutboundPermitted(new PermittedOptions().setAddress("/bus"))) ) .listen(lock.handler()); lock.waitForSuccess(); AtomicReference<Message> reference = new AtomicReference<>(); consumers.add(vertx.eventBus().consumer("/bus", reference::set)); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.send("/bus", Headers.create("foo", "bar"), Buffer.buffer(new JsonObject().put("msg", "Hello from STOMP").toString())); })); Thread.sleep(2000); assertThat(reference.get()).isNull(); } @Test public void testThatEventBusMessagesMatchingTheStructureAreTransferredToStomp() { tearDown(); AsyncLock<StompServer> lock = new AsyncLock<>(); vertx = Vertx.vertx(); server = StompServer.create(vertx) .handler(StompServerHandler.create(vertx) .bridge(new BridgeOptions() .addInboundPermitted(new PermittedOptions().setAddress("/bus")) .addOutboundPermitted(new PermittedOptions().setAddress("/bus").setMatch(new JsonObject().put("id", 2) )))) .listen(lock.handler()); lock.waitForSuccess(); AtomicReference<Frame> reference = new AtomicReference<>(); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/bus", reference::set, f -> { JsonObject payload = new JsonObject().put("id", 2).put("message", "Hello from Vert.x"); vertx.eventBus().publish("/bus", payload, new DeliveryOptions().addHeader("foo", "bar")); } ); } )); Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> reference.get() != null); assertThat(reference.get().getHeaders().get("foo")).isEqualTo("bar"); assertThat(reference.get().getHeaders().get("destination")).isEqualTo("/bus"); JsonObject json = new JsonObject(reference.get().getBodyAsString()); assertThat(json.getString("message")).isEqualTo("Hello from Vert.x"); } @Test public void testThatEventBusMessagesNotMatchingTheStructureAreRejected() throws InterruptedException { tearDown(); AsyncLock<StompServer> lock = new AsyncLock<>(); vertx = Vertx.vertx(); server = StompServer.create(vertx) .handler(StompServerHandler.create(vertx) .bridge(new BridgeOptions() .addInboundPermitted(new PermittedOptions().setAddress("/bus")) .addOutboundPermitted(new PermittedOptions().setAddress("/bus").setMatch(new JsonObject().put("id", 2) )))) .listen(lock.handler()); lock.waitForSuccess(); AtomicReference<Frame> reference = new AtomicReference<>(); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/bus", reference::set, f -> { JsonObject payload = new JsonObject().put("id", 1).put("message", "Hello from Vert.x"); vertx.eventBus().publish("/bus", payload, new DeliveryOptions().addHeader("foo", "bar")); } ); } )); Thread.sleep(2000); assertThat(reference.get()).isNull(); } }