/* * 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 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.AtomicBoolean; import static org.assertj.core.api.Assertions.assertThat; /** * Test the STOMP transactions. * * @author <a href="http://escoffier.me">Clement Escoffier</a> */ public class TransactionsTest { private Vertx vertx; private StompServer server; private List<StompClient> clients = new ArrayList<>(); @Before public void setUp() { vertx = Vertx.vertx(); AsyncLock<StompServer> lock = new AsyncLock<>(); vertx = Vertx.vertx(); server = StompServer.create(vertx) .handler(StompServerHandler.create(vertx)) .listen(lock.handler()); lock.waitForSuccess(); } @After public void tearDown() { 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 testBasicTransaction() { List<Frame> frames = new CopyOnWriteArrayList<>(); List<Frame> errors = 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.errorHandler(errors::add); connection.beginTX("my-tx"); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx") .setBody(Buffer.buffer("Hello"))); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx").setBody( Buffer.buffer("World"))); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx") .setBody(Buffer.buffer("!!!"))); connection.commit("my-tx"); })); Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> frames.size() == 3 && errors.isEmpty()); for (Frame frame : frames) { assertThat(frame.getHeader(Frame.TRANSACTION)).isEqualTo("my-tx"); } } @Test public void testAbortedTransaction() throws InterruptedException { List<Frame> frames = new CopyOnWriteArrayList<>(); List<Frame> errors = 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.errorHandler(errors::add); connection.beginTX("my-tx"); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx") .setBody(Buffer.buffer("Hello"))); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx").setBody( Buffer.buffer("World"))); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx") .setBody(Buffer.buffer("!!!"))); connection.abort("my-tx"); })); // Wait a few seconds to be sure messages are not sent Thread.sleep(2000); assertThat(errors).isEmpty(); assertThat(frames).isEmpty(); } @Test public void testTransactionDeliveringToTwoClients() { List<Frame> frames1 = new CopyOnWriteArrayList<>(); List<Frame> frames2 = new CopyOnWriteArrayList<>(); List<Frame> errors = 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(() -> Helper.hasDestination(server.stompHandler().getDestinations(), "/queue")); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.errorHandler(errors::add); connection.beginTX("my-tx"); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx") .setBody(Buffer.buffer("Hello"))); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx").setBody( Buffer.buffer("World"))); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx") .setBody(Buffer.buffer("!!!"))); connection.commit("my-tx"); })); Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> frames1.size() == 3 && frames2.size() == 3 && errors.isEmpty()); for (Frame frame : frames1) { assertThat(frame.getHeader(Frame.TRANSACTION)).isEqualTo("my-tx"); } for (Frame frame : frames2) { assertThat(frame.getHeader(Frame.TRANSACTION)).isEqualTo("my-tx"); } } @Test public void testThatYouCannotBeginTwoTransactionsWithTheSameId() { List<Frame> frames = new CopyOnWriteArrayList<>(); List<Frame> errors = 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.errorHandler(errors::add); connection.beginTX("my-tx"); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx") .setBody(Buffer.buffer("Hello"))); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx").setBody( Buffer.buffer("World"))); connection.beginTX("my-tx"); // Illegal call })); Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> !errors.isEmpty()); assertThat(frames.isEmpty()); assertThat(errors.get(0).getHeader("message")).containsIgnoringCase("Already existing transaction"); } @Test public void testThatTransactionIDCanBeReusedAfterCommit() { List<Frame> frames = new CopyOnWriteArrayList<>(); List<Frame> errors = 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.errorHandler(errors::add); connection.beginTX("my-tx"); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx") .setBody(Buffer.buffer("Hello"))); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx").setBody( Buffer.buffer("World"))); connection.commit("my-tx"); connection.beginTX("my-tx"); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx") .setBody(Buffer.buffer("!!!"))); connection.commit("my-tx"); })); Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> frames.size() == 3 && errors.isEmpty()); for (Frame frame : frames) { assertThat(frame.getHeader(Frame.TRANSACTION)).isEqualTo("my-tx"); } } @Test public void testAutoAbortOnClose() { List<Frame> frames = new CopyOnWriteArrayList<>(); List<Frame> errors = 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")); AtomicBoolean done = new AtomicBoolean(); clients.add(StompClient.create(vertx).connect(ar -> { final StompClientConnection connection = ar.result(); connection.errorHandler(errors::add); connection.beginTX("my-tx"); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx") .setBody(Buffer.buffer("Hello"))); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx").setBody( Buffer.buffer("World"))); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx") .setBody(Buffer.buffer("!!!"))); connection.close(); done.set(true); })); Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> Transactions.instance().getTransactionCount() == 0 && done.get()); assertThat(frames).isEmpty(); assertThat(errors).isEmpty(); } @Test public void testAutoAbortOnDisconnect() { List<Frame> frames = new CopyOnWriteArrayList<>(); List<Frame> errors = 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.errorHandler(errors::add); connection.beginTX("my-tx", f -> { connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx") .setBody(Buffer.buffer("Hello"))); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx").setBody( Buffer.buffer("World"))); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx") .setBody(Buffer.buffer("!!!"))); connection.disconnect(); }); })); Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> Transactions.instance().getTransactionCount() == 0); assertThat(frames).isEmpty(); assertThat(errors).isEmpty(); } @Test public void testCommitWithIllegalId() { List<Frame> frames = new CopyOnWriteArrayList<>(); List<Frame> errors = 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.errorHandler(errors::add); connection.beginTX("my-tx"); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx") .setBody(Buffer.buffer("Hello"))); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx").setBody( Buffer.buffer("World"))); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx") .setBody(Buffer.buffer("!!!"))); connection.commit("illegal"); })); Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> errors.size() >= 1); assertThat(frames).isEmpty(); // On error, all transactions are closed assertThat(Transactions.instance().getTransactionCount()).isEqualTo(0); assertThat(errors.get(0).toString()).containsIgnoringCase("Unknown transaction"); } @Test public void testAbortWithBadTransactionId() { List<Frame> frames = new CopyOnWriteArrayList<>(); List<Frame> errors = 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.errorHandler(errors::add); connection.beginTX("my-tx"); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx") .setBody(Buffer.buffer("Hello"))); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx").setBody( Buffer.buffer("World"))); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx") .setBody(Buffer.buffer("!!!"))); connection.abort("illegal"); })); Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> errors.size() >= 1); assertThat(frames).isEmpty(); // On error, all transactions are closed assertThat(Transactions.instance().getTransactionCount()).isEqualTo(0); assertThat(errors.get(0).toString()).containsIgnoringCase("Unknown transaction"); } @Test public void testNumberOfFramesInTransaction() { server.options().setMaxFrameInTransaction(2); List<Frame> frames = new CopyOnWriteArrayList<>(); List<Frame> errors = 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.errorHandler(errors::add); connection.beginTX("my-tx"); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx") .setBody(Buffer.buffer("Hello"))); connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx").setBody( Buffer.buffer("World"))); // Next will be dropped: connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx") .setBody(Buffer.buffer("!!!"))); connection.commit("my-tx"); })); Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> Transactions.instance().getTransactionCount() == 0); assertThat(frames).isEmpty(); assertThat(errors).hasSize(1); } @Test public void testTransactionChunk() { server.options().setTransactionChunkSize(100); server.options().setMaxFrameInTransaction(10000); 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.beginTX("my-tx"); for (int i = 0; i < 5000; i++) { connection.send(new Frame().setCommand(Frame.Command.SEND).setDestination("/queue").setTransaction("my-tx") .setBody(Buffer.buffer("Hello-" + i))); } connection.commit("my-tx"); })); Awaitility.waitAtMost(10, TimeUnit.SECONDS).until(() -> frames.size() == 5000); int i = 0; for (Frame frame : frames) { assertThat(frame.getHeader(Frame.TRANSACTION)).isEqualTo("my-tx"); assertThat(frame.getBodyAsString()).isEqualTo("Hello-" + i); i++; } } }