/*
* 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.AsyncResult;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.stomp.*;
import io.vertx.ext.stomp.utils.Headers;
import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertNotNull;
/**
* Test the STOMP client.
*
* @author <a href="http://escoffier.me">Clement Escoffier</a>
*/
public class StompClientImplTest {
private Vertx vertx;
private StompServer server;
private StompServerOptions options;
@Before
public void setUp() {
AsyncLock<StompServer> lock = new AsyncLock<>();
vertx = Vertx.vertx();
options = new StompServerOptions();
server = StompServer.create(vertx, options)
.handler(StompServerHandler.create(vertx))
.listen(lock.handler());
lock.waitForSuccess();
}
@After
public void tearDown() {
AsyncLock<Void> lock = new AsyncLock<>();
server.close(lock.handler());
lock.waitForSuccess();
lock = new AsyncLock<>();
vertx.close(lock.handler());
lock.waitForSuccess();
}
@Test
public void testConnection() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<StompClientConnection> reference = new AtomicReference<>();
StompClient client = StompClient.create(vertx);
client.connect(ar -> {
if (ar.failed()) {
reference.set(null);
} else {
reference.set(ar.result());
}
latch.countDown();
});
latch.await(1, TimeUnit.MINUTES);
assertNotNull(reference.get());
assertNotNull(reference.get().session());
assertNotNull(reference.get().server());
assertNotNull(reference.get().version());
}
@Test
public void testConnectionWithTrailingLine() throws InterruptedException {
options.setTrailingLine(true);
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<StompClientConnection> reference = new AtomicReference<>();
StompClient client = StompClient.create(vertx, new StompClientOptions().setTrailingLine(true));
client.connect(ar -> {
if (ar.failed()) {
reference.set(null);
} else {
reference.set(ar.result());
}
latch.countDown();
});
latch.await(1, TimeUnit.MINUTES);
assertNotNull(reference.get());
assertNotNull(reference.get().session());
assertNotNull(reference.get().server());
assertNotNull(reference.get().version());
}
@Test
public void testConnectionWithStompFrame() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<StompClientConnection> reference = new AtomicReference<>();
StompClient client = StompClient.create(vertx, new StompClientOptions().setUseStompFrame(true));
client.connect(ar -> {
if (ar.failed()) {
reference.set(null);
} else {
reference.set(ar.result());
}
latch.countDown();
});
latch.await(1, TimeUnit.MINUTES);
assertNotNull(reference.get());
assertNotNull(reference.get().session());
assertNotNull(reference.get().server());
assertNotNull(reference.get().version());
}
@Test
public void testConnectionWithStompFrameWithTrailingLine() throws InterruptedException {
options.setTrailingLine(true);
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<StompClientConnection> reference = new AtomicReference<>();
StompClient client = StompClient.create(vertx, new StompClientOptions().setUseStompFrame(true).setTrailingLine(true));
client.connect(ar -> {
if (ar.failed()) {
reference.set(null);
} else {
reference.set(ar.result());
}
latch.countDown();
});
latch.await(1, TimeUnit.MINUTES);
assertNotNull(reference.get());
assertNotNull(reference.get().session());
assertNotNull(reference.get().server());
assertNotNull(reference.get().version());
}
@Test
public void testSendingMessages() {
AtomicReference<Frame> ref = new AtomicReference<>();
StompClient client = StompClient.create(vertx);
client.connect(ar -> {
if (ar.failed()) {
return;
}
ar.result().send("/hello", Buffer.buffer("this is my content"), ref::set);
});
Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAtomic(ref, Matchers.notNullValue(Frame.class));
assertThat(ref.get().getDestination()).isEqualTo("/hello");
}
@Test
public void testSendingMessagesWithTrailingLine() {
options.setTrailingLine(true);
AtomicReference<Frame> ref = new AtomicReference<>();
StompClient client = StompClient.create(vertx, new StompClientOptions().setTrailingLine(true));
client.connect(ar -> {
if (ar.failed()) {
return;
}
ar.result().send("/hello", Buffer.buffer("this is my content"), ref::set);
});
Awaitility.await().atMost(5, TimeUnit.SECONDS).untilAtomic(ref, Matchers.notNullValue(Frame.class));
assertThat(ref.get().getDestination()).isEqualTo("/hello");
}
@Test
public void testConnectionAndDisconnect() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<Frame> reference = new AtomicReference<>();
StompClient client = StompClient.create(vertx, new StompClientOptions().setUseStompFrame(true));
client.connect(ar -> {
if (ar.failed()) {
reference.set(null);
latch.countDown();
} else {
ar.result().disconnect(
frame -> {
reference.set(frame);
latch.countDown();
});
}
});
latch.await(1, TimeUnit.MINUTES);
assertNotNull(reference.get());
}
@Test
public void testConnectionAndDisconnectWithCustomFrame() throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<Frame> reference = new AtomicReference<>();
StompClient client = StompClient.create(vertx, new StompClientOptions().setUseStompFrame(true));
client.connect(ar -> {
if (ar.failed()) {
reference.set(null);
latch.countDown();
} else {
ar.result().disconnect(new Frame(Frame.Command.DISCONNECT, Headers.create("message", "bye bye"), null),
frame -> {
reference.set(frame);
latch.countDown();
});
}
});
latch.await(1, TimeUnit.MINUTES);
assertNotNull(reference.get());
assertThat(reference.get().getHeader("message")).contains("bye bye");
}
@Test
public void testClientHeartbeatWhenNoServerActivity() {
AtomicReference<StompClientConnection> reference = new AtomicReference<>();
AsyncLock<Void> closeLock = new AsyncLock<>();
server.close(closeLock.handler());
closeLock.waitForSuccess();
AsyncLock<StompServer> lock = new AsyncLock<>();
server = StompServer.create(vertx,
new StompServerOptions().setHeartbeat(new JsonObject().put("x", 100).put("y", 100)))
// Disable ping frame:
.handler(StompServerHandler.create(vertx).pingHandler(v -> {
}))
.listen(lock.handler());
lock.waitForSuccess();
StompClient client = StompClient.create(vertx, new StompClientOptions().setHeartbeat(new JsonObject().put
("x", 100).put("y", 100)));
client.connect(ar -> reference.set(ar.result()));
// Wait until inactivity is detected.
Awaitility.await().atMost(1000, TimeUnit.MILLISECONDS).until(
() -> reference.get().session() == null
);
}
@Test
public void testClientHeartbeatWithServerActivity() throws InterruptedException {
AtomicReference<StompClientConnection> reference = new AtomicReference<>();
AsyncLock lock = new AsyncLock<>();
server.close(lock.handler());
lock.waitForSuccess();
lock = new AsyncLock();
server = StompServer.create(vertx,
new StompServerOptions().setHeartbeat(new JsonObject().put("x", 100).put("y", 100)))
.handler(StompServerHandler.create(vertx))
.listen(lock.handler());
lock.waitForSuccess();
StompClient client = StompClient.create(vertx, new StompClientOptions().setHeartbeat(new JsonObject()
.put("x", 100).put("y", 100)));
client.connect(ar -> reference.set(ar.result()));
Thread.sleep(1000);
assertThat(reference.get().server()).isNotNull();
}
@Test
public void testServerHeartbeatWhenNoClientActivity() {
AsyncLock<Void> closeLock = new AsyncLock<>();
AtomicReference<StompClientConnection> reference = new AtomicReference<>();
server.close(closeLock.handler());
closeLock.waitForSuccess();
AsyncLock<StompServer> lock = new AsyncLock<>();
server = StompServer.create(vertx,
new StompServerOptions().setHeartbeat(new JsonObject().put("x", 100).put("y", 100)))
.handler(StompServerHandler.create(vertx))
.listen(lock.handler());
lock.waitForSuccess();
StompClient client = StompClient.create(vertx, new StompClientOptions().setHeartbeat(new JsonObject()
.put("x", 100).put("y", 100)));
client.connect(ar -> {
reference.set(ar.result());
// Disable ping frame:
ar.result().pingHandler(connection -> {
});
});
// Wait until inactivity is detected.
Awaitility.await().atMost(1, TimeUnit.SECONDS).until(
() -> reference.get().session() == null
);
}
@Test
public void testConnectionDroppedHandler() throws InterruptedException {
AtomicBoolean flag = new AtomicBoolean(true);
AtomicBoolean dropped = new AtomicBoolean(false);
AsyncLock lock = new AsyncLock<>();
server.close(lock.handler());
lock.waitForSuccess();
lock = new AsyncLock();
server = StompServer.create(vertx,
new StompServerOptions().setHeartbeat(new JsonObject().put("x", 100).put("y", 100)))
.handler(StompServerHandler.create(vertx).pingHandler(connection -> {
if (flag.get()) {
connection.ping();
}
// When the flag is set to false, the ping are not sent anymore. We use this mechanism to mimic a
// server not sending ping anymore.
}))
.listen(lock.handler());
lock.waitForSuccess();
StompClient client = StompClient.create(vertx, new StompClientOptions().setHeartbeat(new JsonObject()
.put("x", 100).put("y", 100)));
client.connect(ar -> {
ar.result().connectionDroppedHandler(v -> {
dropped.set(true);
});
flag.set(false); // Disable the ping.
});
Awaitility.await().atMost(10, TimeUnit.SECONDS).until(dropped::get);
}
@Test
public void testReconnection() throws InterruptedException {
AtomicBoolean flag = new AtomicBoolean(true);
AtomicInteger dropped = new AtomicInteger(0);
AtomicInteger connectionCounter = new AtomicInteger();
List<ServerFrame> frames = new ArrayList<>();
AsyncLock lock = new AsyncLock<>();
server.close(lock.handler());
lock.waitForSuccess();
lock = new AsyncLock();
server = StompServer.create(vertx,
new StompServerOptions()
.setHeartbeat(new JsonObject().put("x", 1000).put("y", 1000)))
.handler(StompServerHandler.create(vertx).pingHandler(connection -> {
if (flag.get()) {
connection.ping();
}
// When the flag is set to false, the ping are not sent anymore. We use this mechanism to mimic a
// server not sending ping anymore.
}).receivedFrameHandler(frames::add))
.listen(lock.handler());
lock.waitForSuccess();
StompClient client = StompClient.create(vertx, new StompClientOptions().setHeartbeat(new JsonObject()
.put("x", 1000).put("y", 1000)));
Handler<AsyncResult<StompClientConnection>> connectionHandler = getConnectionHandler(client, flag, dropped,
connectionCounter);
client.connect(connectionHandler);
Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> dropped.get() == 1);
Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> connectionCounter.get() == 2);
Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> containsClientFrame(frames, 1)
&& containsClientFrame(frames, 2));
}
@Test
public void testReconnectionWithDeadServer() throws InterruptedException {
AtomicBoolean flag = new AtomicBoolean(true);
AtomicInteger dropped = new AtomicInteger(0);
AtomicInteger connectionCounter = new AtomicInteger();
List<ServerFrame> frames = new ArrayList<>();
AsyncLock lock = new AsyncLock<>();
server.close(lock.handler());
lock.waitForSuccess();
lock = new AsyncLock();
server = StompServer.create(vertx,
new StompServerOptions()
.setHeartbeat(new JsonObject().put("x", 1000).put("y", 1000)))
.handler(StompServerHandler.create(vertx).pingHandler(connection -> {
if (flag.get()) {
connection.ping();
} else {
server.close();
}
// When the flag is set to false, the ping are not sent anymore. We use this mechanism to mimic a
// server not sending ping anymore.
}).receivedFrameHandler(frames::add))
.listen(lock.handler());
lock.waitForSuccess();
StompClient client = StompClient.create(vertx, new StompClientOptions().setHeartbeat(new JsonObject()
.put("x", 1000).put("y", 1000)));
Handler<AsyncResult<StompClientConnection>> connectionHandler = getConnectionHandler(client, flag, dropped,
connectionCounter);
client.connect(connectionHandler);
Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> dropped.get() == 1);
Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> connectionCounter.get() == 1);
Awaitility.await().atMost(10, TimeUnit.SECONDS).until(() -> containsClientFrame(frames, 1)
&& ! containsClientFrame(frames, 2));
}
private boolean containsClientFrame(List<ServerFrame> frames, int count) {
for (ServerFrame frame : frames) {
if (frame.frame().getBody() != null && frame.frame().getBodyAsString().contains("some body " + count)) {
return true;
}
}
return false;
}
private Handler<AsyncResult<StompClientConnection>> getConnectionHandler(StompClient client, AtomicBoolean flag,
AtomicInteger dropped,
AtomicInteger connection) {
return ar -> {
if (ar.succeeded()) {
ar.result().connectionDroppedHandler(v -> {
dropped.incrementAndGet();
client.connect(getConnectionHandler(client, flag, dropped, connection));
});
int count = connection.incrementAndGet();
flag.set(false); // Disable the ping.
ar.result().send("some-address", Buffer.buffer("some body " + count));
} else {
// Connection failed.
}
};
}
@Test
public void testThatDroppedHandlerIsNotCalledWhenTheClientIsClosing() {
AsyncLock lock = new AsyncLock<>();
server.close(lock.handler());
lock.waitForSuccess();
lock = new AsyncLock();
server = StompServer.create(vertx,
new StompServerOptions()
.setHeartbeat(new JsonObject().put("x", 1000).put("y", 1000)))
.handler(StompServerHandler.create(vertx))
.listen(lock.handler());
lock.waitForSuccess();
StompClient client = StompClient.create(vertx, new StompClientOptions().setHeartbeat(new JsonObject()
.put("x", 1000).put("y", 1000)));
AtomicBoolean dropped = new AtomicBoolean();
AtomicBoolean connected = new AtomicBoolean();
client.connect(connection -> {
connection.result().connectionDroppedHandler(conn -> {
dropped.set(true);
});
connected.set(connection.succeeded());
});
Awaitility.await().atMost(10, TimeUnit.SECONDS).until(connected::get);
client.close();
AtomicBoolean done = new AtomicBoolean();
vertx.setTimer(1000, l -> {
done.set(true);
});
Awaitility.await().atMost(10, TimeUnit.SECONDS).until(done::get);
assertThat(dropped.get()).isFalse();
}
}