/* * 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 io.vertx.ext.unit.Async; import io.vertx.ext.unit.TestContext; import io.vertx.ext.unit.junit.VertxUnitRunner; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; /** * Checks the {@code ACK} and {@code NACK} handling. * * @author <a href="http://escoffier.me">Clement Escoffier</a> */ @RunWith(VertxUnitRunner.class) public class AckTest { private Vertx vertx; private StompServer server; private List<StompClient> clients = new ArrayList<>(); private List<Frame> acked = new CopyOnWriteArrayList<>(); private List<Frame> nacked = new CopyOnWriteArrayList<>(); @Before public void setUp(TestContext context) throws InterruptedException { AsyncLock<StompServer> lock = new AsyncLock<>(); vertx = Vertx.vertx(); server = StompServer.create(vertx) .handler(StompServerHandler.create(vertx) .destinationFactory(new QueueManagingAcknowledgmentsFactory()) .onAckHandler(acknowledgement -> acked.addAll(acknowledgement.frames())) .onNackHandler(acknowledgement -> nacked.addAll(acknowledgement.frames()))) .listen(lock.handler()); lock.waitForSuccess(); } @After public void tearDown(TestContext context) { clients.forEach(StompClient::close); clients.clear(); AsyncLock<Void> lock = new AsyncLock<>(); server.close(lock.handler()); lock.waitForSuccess(); lock = new AsyncLock<>(); vertx.close(lock.handler()); lock.waitForSuccess(); } @Test public void testSimpleAck() { clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/queue", Headers.create(Frame.ACK, "client"), frame -> connection.ack(frame.getAck())); })); 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(() -> !acked.isEmpty()); } @Test public void testSimpleNack() { clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/queue", Headers.create(Frame.ACK, "client"), frame -> connection.nack(frame.getAck())); })); 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(() -> !nacked.isEmpty()); assertThat(acked).isEmpty(); } @Test public void testCumulativeAck() { List<Frame> frames = new CopyOnWriteArrayList<>(); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/queue", Headers.create(Frame.ACK, "client"), (frame) -> { frames.add(frame); if (frames.size() == 3) { connection.ack(frame.getAck()); } }); })); 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("/queue", Buffer.buffer("World")); connection.send("/queue", Buffer.buffer("!!!")); connection.send("/queue", Buffer.buffer("not acknowledged")); })); Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> acked.size() == 3); } @Test public void testCumulativeNack() { List<Frame> frames = new CopyOnWriteArrayList<>(); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/queue", Headers.create(Frame.ACK, "client"), (frame) -> { frames.add(frame); if (frames.size() == 3) { connection.nack(frame.getAck()); } }); })); 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("/queue", Buffer.buffer("World")); connection.send("/queue", Buffer.buffer("!!!")); connection.send("/queue", Buffer.buffer("not acknowledged")); })); Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> nacked.size() == 3); } @Test public void testIndividualAck() { List<Frame> frames = new CopyOnWriteArrayList<>(); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/queue", Headers.create(Frame.ACK, "client-individual"), (frame) -> { frames.add(frame); if (frames.size() == 3) { for (Frame f : frames) { connection.ack(f.getAck()); } } }); })); 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("/queue", Buffer.buffer("World")); connection.send("/queue", Buffer.buffer("!!!")); })); Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> acked.size() == 3); } @Test public void testIndividualNack() { List<Frame> frames = new CopyOnWriteArrayList<>(); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/queue", Headers.create(Frame.ACK, "client-individual"), (frame) -> { frames.add(frame); if (frames.size() == 3) { for (Frame f : frames) { connection.nack(f.getAck()); } } }); })); 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("/queue", Buffer.buffer("World")); connection.send("/queue", Buffer.buffer("!!!")); })); Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> nacked.size() == 3); } @Test public void testAckInTransaction() { List<Frame> frames = new CopyOnWriteArrayList<>(); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/queue", Headers.create(Frame.ACK, "client"), frame -> { if (frames.isEmpty()) { connection.beginTX("my-tx"); } frames.add(frame); connection.ack(frame.getAck(), "my-tx"); if (frames.size() == 3) { connection.commit("my-tx"); } } ); })); 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")); })); assertThat(acked.isEmpty()); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.send("/queue", Buffer.buffer("Hello")); })); assertThat(acked.isEmpty()); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.send("/queue", Buffer.buffer("Hello")); })); Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> acked.size() == 3); } @Test public void testNackInTransaction() { List<Frame> frames = new CopyOnWriteArrayList<>(); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/queue", Headers.create(Frame.ACK, "client"), frame -> { if (frames.isEmpty()) { connection.beginTX("my-tx"); } frames.add(frame); connection.nack(frame.getAck(), "my-tx"); if (frames.size() == 3) { connection.commit("my-tx"); } } ); })); 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")); })); assertThat(nacked.isEmpty()); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.send("/queue", Buffer.buffer("Hello")); })); assertThat(nacked.isEmpty()); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.send("/queue", Buffer.buffer("Hello")); })); Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> nacked.size() == 3); } @Test public void testUnknownMessageInAck(TestContext context) { Async async = context.async(); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.errorHandler(frame -> context.fail("unexpected error")); connection.ack("unknown", frame -> { async.complete(); }); })); Async async2 = context.async(); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.errorHandler(frame -> context.fail("unexpected error")); connection.nack("unknown", frame -> { async2.complete(); }); })); } @Test public void testWrongTransactionIdInAckAndNack(TestContext context) { Async async = context.async(); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.errorHandler(frame -> async.complete()); connection.ack("id", "unknown", frame -> context.fail("unexpected receipt")); })); Async async2 = context.async(); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.errorHandler(frame -> async2.complete()); connection.ack("id", "unknown", frame -> context.fail("unexpected receipt")); })); } @Test public void testSubscriptionAndTwoReceptionsWithNackInClientMode() { 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", Headers.create("ack", "client"), frame -> { frames1.add(frame); if (frames1.size() == 2) { connection.nack(frame.getAck()); } }); })); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/queue", Headers.create("ack", "client"), frames2::add); })); Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> { final Destination destination = server.stompHandler().getDestination("/queue"); return destination != null && destination.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(() -> frames2.size() == 4 ); } @Test public void testSubscriptionAndTwoReceptionsWithNack() { List<Frame> frames2 = new CopyOnWriteArrayList<>(); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/queue", Headers.create("ack", "client-individual"), frame -> { connection.nack(frame.getAck()); }); })); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.subscribe("/queue", Headers.create("ack", "client-individual"), frames2::add); })); Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> { final Destination destination = server.stompHandler().getDestination("/queue"); return destination != null && destination.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")); })); Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> frames2.size() == 2 ); } }