/*
* 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.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.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Test the client subscriptions and message delivery.
*
* @author <a href="http://escoffier.me">Clement Escoffier</a>
*/
public class SubscriptionsUsingQueueTest {
private Vertx vertx;
private StompServer server;
private List<StompClient> clients = new ArrayList<>();
@Before
public void setUp() {
AsyncLock<StompServer> lock = new AsyncLock<>();
vertx = Vertx.vertx();
server =StompServer.create(vertx)
.handler(StompServerHandler.create(vertx).destinationFactory(Destination::queue))
.listen(lock.handler());
lock.waitForSuccess();
}
@After
public void tearDown() {
AsyncLock<Void> lock = new AsyncLock<>();
clients.forEach(StompClient::close);
clients.clear();
server.close(lock.handler());
lock.waitForSuccess();
lock = new AsyncLock<>();
vertx.close(lock.handler());
lock.waitForSuccess();
}
@Test
public void testSubscriptionAndReceptionUsingQueue() {
List<Frame> frames = new CopyOnWriteArrayList<>();
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.subscribe("/queue", (frames::add));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> Helper.hasDestination(server.stompHandler().getDestinations(), "/queue"));
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.send("/queue", Buffer.buffer("Hello"));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> !frames.isEmpty());
assertThat(frames).hasSize(1);
assertThat(frames.get(0).getBodyAsString()).isEqualTo("Hello");
assertThat(frames.get(0).getCommand()).isEqualTo(Frame.Command.MESSAGE);
assertThat(frames.get(0).getHeader(Frame.MESSAGE_ID)).isNotNull().isNotEmpty();
assertThat(frames.get(0).getHeader(Frame.SUBSCRIPTION)).isEqualTo("/queue");
assertThat(frames.get(0).getHeader(Frame.DESTINATION)).isNotNull().isNotEmpty();
}
@Test
public void testThatCustomHeadersArePropagatedWhenUsingQueue() {
List<Frame> frames = new CopyOnWriteArrayList<>();
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.subscribe("/queue", (frames::add));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> Helper.hasDestination(server.stompHandler().getDestinations(), "/queue"));
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.send("/queue", Headers.create("foo", "bar", "toto", "titi"), Buffer.buffer("Hello"));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> !frames.isEmpty());
assertThat(frames).hasSize(1);
assertThat(frames.get(0).getBodyAsString()).isEqualTo("Hello");
assertThat(frames.get(0).getCommand()).isEqualTo(Frame.Command.MESSAGE);
assertThat(frames.get(0).getHeader(Frame.MESSAGE_ID)).isNotNull().isNotEmpty();
assertThat(frames.get(0).getHeader(Frame.SUBSCRIPTION)).isEqualTo("/queue");
assertThat(frames.get(0).getHeader(Frame.DESTINATION)).isNotNull().isNotEmpty();
assertThat(frames.get(0).getHeader("foo")).isEqualTo("bar");
assertThat(frames.get(0).getHeader("toto")).isEqualTo("titi");
}
@Test
public void testSubscriptionAndTwoReceptions() {
List<Frame> frames1 = new CopyOnWriteArrayList<>();
List<Frame> frames2 = new CopyOnWriteArrayList<>();
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.subscribe("/queue", frames1::add);
}));
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.subscribe("/queue", frames2::add);
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> server.stompHandler().getDestination("/queue").numberOfSubscriptions() == 2);
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.send("/queue", Buffer.buffer("Hello"));
connection.send("/queue", Buffer.buffer("vert.x"));
connection.send("/queue", Buffer.buffer("Hello"));
connection.send("/queue", Buffer.buffer("vert.x"));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() ->
frames1.size() == 2 && frames2.size() == 2
);
List<Frame> helloList;
List<Frame> vertxList;
if (frames1.get(0).getBodyAsString().equalsIgnoreCase("Hello")) {
helloList = frames1;
vertxList = frames2;
} else {
helloList = frames2;
vertxList = frames1;
}
assertThat(helloList.get(0).getBodyAsString()).isEqualTo("Hello");
assertThat(helloList.get(0).getCommand()).isEqualTo(Frame.Command.MESSAGE);
assertThat(helloList.get(0).getHeader(Frame.MESSAGE_ID)).isNotNull().isNotEmpty();
assertThat(helloList.get(0).getHeader(Frame.SUBSCRIPTION)).isEqualTo("/queue");
assertThat(helloList.get(0).getHeader(Frame.DESTINATION)).isNotNull().isNotEmpty();
assertThat(helloList.get(1).getBodyAsString()).isEqualTo("Hello");
assertThat(helloList.get(1).getCommand()).isEqualTo(Frame.Command.MESSAGE);
assertThat(helloList.get(1).getHeader(Frame.MESSAGE_ID)).isNotNull().isNotEmpty();
assertThat(helloList.get(1).getHeader(Frame.SUBSCRIPTION)).isEqualTo("/queue");
assertThat(helloList.get(1).getHeader(Frame.DESTINATION)).isNotNull().isNotEmpty();
assertThat(vertxList.get(0).getBodyAsString()).isEqualTo("vert.x");
assertThat(vertxList.get(0).getCommand()).isEqualTo(Frame.Command.MESSAGE);
assertThat(vertxList.get(0).getHeader(Frame.MESSAGE_ID)).isNotNull().isNotEmpty();
assertThat(vertxList.get(0).getHeader(Frame.SUBSCRIPTION)).isEqualTo("/queue");
assertThat(vertxList.get(0).getHeader(Frame.DESTINATION)).isNotNull().isNotEmpty();
assertThat(vertxList.get(1).getBodyAsString()).isEqualTo("vert.x");
assertThat(vertxList.get(1).getCommand()).isEqualTo(Frame.Command.MESSAGE);
assertThat(vertxList.get(1).getHeader(Frame.MESSAGE_ID)).isNotNull().isNotEmpty();
assertThat(vertxList.get(1).getHeader(Frame.SUBSCRIPTION)).isEqualTo("/queue");
assertThat(vertxList.get(1).getHeader(Frame.DESTINATION)).isNotNull().isNotEmpty();
}
@Test
public void testWhenNoSubscriptionsWhenUsingQueue() {
server.options().setSendErrorOnNoSubscriptions(true);
List<Frame> frames = new CopyOnWriteArrayList<>();
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.subscribe("/queue2", (frames::add));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> Helper.hasDestination(server.stompHandler().getDestinations(), "/queue2"));
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.send("/queue", Buffer.buffer("Hello"));
connection.errorHandler(frames::add);
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> !frames.isEmpty());
assertThat(frames).hasSize(1);
assertThat(frames.get(0).getCommand()).isEqualTo(Frame.Command.ERROR);
assertThat(frames.get(0).getHeader(Frame.DESTINATION)).isEqualTo("/queue");
assertThat(frames.get(0).getBodyAsString()).contains("no subscriptions");
}
@Test
public void testMultipleSubscriptionsWithIdsOnQueues() {
server.options().setSendErrorOnNoSubscriptions(true);
Map<String, Frame> frames = new HashMap<>();
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.subscribe("/queue", Headers.create().add(Frame.ID, "0"), f -> frames.put("/queue", f));
connection.subscribe("/queue2", Headers.create().add(Frame.ID, "1"), f -> frames.put("/queue2", f));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> Helper.hasDestination(server.stompHandler().getDestinations(), "/queue2"));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> Helper.hasDestination(server.stompHandler().getDestinations(), "/queue"));
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.send("/queue", Buffer.buffer("Hello"));
connection.send("/queue2", Buffer.buffer("World"));
connection.errorHandler(f -> frames.put("error", f));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> frames.size() >= 2);
assertThat(frames).hasSize(2);
assertThat(frames).doesNotContainKeys("error");
Frame frame1 = frames.get("/queue");
assertThat(frame1.getCommand()).isEqualTo(Frame.Command.MESSAGE);
assertThat(frame1.getHeader(Frame.DESTINATION)).isEqualTo("/queue");
assertThat(frame1.getHeader(Frame.SUBSCRIPTION)).isEqualTo("0");
assertThat(frame1.getBodyAsString()).isEqualTo("Hello");
frame1 = frames.get("/queue2");
assertThat(frame1.getCommand()).isEqualTo(Frame.Command.MESSAGE);
assertThat(frame1.getHeader(Frame.DESTINATION)).isEqualTo("/queue2");
assertThat(frame1.getHeader(Frame.SUBSCRIPTION)).isEqualTo("1");
assertThat(frame1.getBodyAsString()).isEqualTo("World");
}
@Test
public void testUnsubscriptionWithDefaultIdUsingQueue() {
server.options().setSendErrorOnNoSubscriptions(true);
List<Frame> frames = new CopyOnWriteArrayList<>();
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.subscribe("/queue", frame -> {
frames.add(frame);
connection.unsubscribe("/queue");
});
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> Helper.hasDestination(server.stompHandler().getDestinations(), "/queue"));
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.errorHandler(frames::add);
connection.send("/queue", Buffer.buffer("Hello"));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> !Helper.hasDestination(server.stompHandler().getDestinations(), "/queue"));
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.errorHandler(frames::add);
connection.send("/queue", Buffer.buffer("Hello"));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> frames.size() == 2 && frames.get(1).getCommand() == Frame.Command.ERROR);
}
@Test
public void testUnsubscriptionWithCustomId() {
server.options().setSendErrorOnNoSubscriptions(true);
List<Frame> frames = new CopyOnWriteArrayList<>();
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.subscribe("/queue", Headers.create(Frame.ID, "0"), frame -> {
frames.add(frame);
connection.unsubscribe("/queue", Headers.create(Frame.ID, "0"));
});
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> Helper.hasDestination(server.stompHandler().getDestinations(), "/queue"));
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.send("/queue", Buffer.buffer("Hello"));
connection.errorHandler(frames::add);
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> !Helper.hasDestination(server.stompHandler().getDestinations(), "/queue"));
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.send("/queue", Buffer.buffer("Hello"));
connection.errorHandler(frames::add);
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> frames.size() == 2 && frames.get(1).getCommand() == Frame.Command.ERROR);
}
@Test
public void testMultipleConnectionAndClosing() {
for (int i = 0; i < 20; i++) {
testClosingConnection();
}
}
@Test
public void testClosingConnection() {
List<Frame> frames = new CopyOnWriteArrayList<>();
StompClient client = StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.subscribe("/queue", (frames::add));
connection.subscribe("/queue2", (frames::add));
});
clients.add(client);
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> server.stompHandler().getDestinations().size() == 2);
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.send("/queue", Buffer.buffer("Hello"));
}));
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.send("/queue2", Buffer.buffer("World"));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> frames.size() == 2);
client.close();
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> server.stompHandler().getDestinations().size() == 0);
}
@Test
public void testLeavingSubscriptions() {
List<Frame> frames1 = new CopyOnWriteArrayList<>();
List<Frame> frames2 = new CopyOnWriteArrayList<>();
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.subscribe("/queue", frames1::add);
}));
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
connection.subscribe("/queue", frame -> {
frames2.add(frame);
if (frames2.size() == 2) {
connection.unsubscribe("/queue");
}
});
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> server.stompHandler().getDestination("/queue") != null &&
server.stompHandler().getDestination("/queue").numberOfSubscriptions() == 2);
AtomicReference<StompClientConnection> reference = new AtomicReference<>();
clients.add(StompClient.create(vertx).connect(ar -> {
final StompClientConnection connection = ar.result();
reference.set(connection);
connection.send("/queue", Buffer.buffer("1"));
connection.send("/queue", Buffer.buffer("2"));
connection.send("/queue", Buffer.buffer("3"));
connection.send("/queue", Buffer.buffer("4"));
}));
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> server.stompHandler().getDestination("/queue") != null &&
server.stompHandler().getDestination("/queue").numberOfSubscriptions() == 1);
vertx.runOnContext(v -> {
reference.get().send("/queue", Buffer.buffer("5"));
reference.get().send("/queue", Buffer.buffer("6"));
});
Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> frames1.size() == 4 && frames2.size() == 2);
}
}