package rocks.inspectit.server.rmi;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import org.mockito.Matchers;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import com.esotericsoftware.kryonet.rmi.RemoteObject;
import rocks.inspectit.server.test.AbstractTransactionalTestNGLogSupport;
import rocks.inspectit.shared.all.kryonet.Client;
import rocks.inspectit.shared.all.kryonet.Connection;
import rocks.inspectit.shared.all.kryonet.ExtendedSerializationImpl;
import rocks.inspectit.shared.all.kryonet.IExtendedSerialization;
import rocks.inspectit.shared.all.kryonet.Listener;
import rocks.inspectit.shared.all.kryonet.Server;
import rocks.inspectit.shared.all.kryonet.rmi.ObjectSpace;
import rocks.inspectit.shared.all.serializer.IKryoProvider;
import rocks.inspectit.shared.all.serializer.provider.SerializationManagerProvider;
import rocks.inspectit.shared.all.storage.nio.stream.StreamProvider;
/**
* Tests the complete kryonet server-client communication.
*
* @author Ivan Senic
*
*/
@SuppressWarnings("PMD")
@ContextConfiguration(locations = { "classpath:spring/spring-context-global.xml", "classpath:spring/spring-context-database.xml", "classpath:spring/spring-context-beans.xml",
"classpath:spring/spring-context-processors.xml", "classpath:spring/spring-context-storage-test.xml" })
public class KryoNetIntegrationTest extends AbstractTransactionalTestNGLogSupport {
@Autowired
protected SerializationManagerProvider serializationManagerProvider;
@Autowired
protected StreamProvider streamProvider;
protected Server server;
protected Client client;
protected ObjectSpace objectSpace;
@Mock
protected Listener listener;
@Mock
protected Service service;
@BeforeClass
public void init() throws Exception {
int port = 8765;
IExtendedSerialization serialization = new ExtendedSerializationImpl(serializationManagerProvider) {
@Override
protected IKryoProvider createKryoProvider() {
// hook in to register the test service
IKryoProvider kryoProvider = super.createKryoProvider();
kryoProvider.getKryo().register(Service.class);
return kryoProvider;
}
};
server = new Server(serialization, streamProvider);
server.start();
server.bind(port);
objectSpace = new ObjectSpace();
server.addListener(new Listener() {
@Override
public void connected(Connection connection) {
objectSpace.addConnection(connection);
}
});
client = new Client(serialization, streamProvider);
client.start();
client.connect(5000, "localhost", port);
}
@BeforeMethod
public void initMocks() {
if (null != listener) {
server.removeListener(listener);
}
MockitoAnnotations.initMocks(this);
server.addListener(listener);
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
return invocation.getArguments()[0];
}
}).when(service).returnSame(Matchers.any());
objectSpace.register(1, service);
}
@AfterClass
public void closeConnections() throws IOException {
client.stop();
server.stop();
}
public class RemoteMethodInvocation extends KryoNetIntegrationTest {
@Test
public void simple() {
Service clientService = getServiceForClient();
String toSend = "toSend";
assertThat(clientService.returnSame(toSend), is(equalTo(toSend)));
verify(service).returnSame(toSend);
verifyNoMoreInteractions(service);
}
@Test(invocationCount = 10)
public void multiThreaded() throws InterruptedException, BrokenBarrierException {
Service clientService = getServiceForClient();
int numThreads = 3;
int numObjects = 1024;
Set<Object> sendingObjectsSet = getObjectToSend(numObjects);
Queue<Object> queue = new ArrayBlockingQueue<>(sendingObjectsSet.size());
queue.addAll(sendingObjectsSet);
CyclicBarrier cyclicBarrier = new CyclicBarrier(numThreads + 1);
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < numThreads; i++) {
Invoker invoker = new Invoker(cyclicBarrier, queue, clientService);
threads.add(invoker);
invoker.start();
}
cyclicBarrier.await();
for (Thread thread : threads) {
thread.join(10000);
}
for (Object toSend : sendingObjectsSet) {
verify(service).returnSame(toSend);
}
}
}
public class Send extends KryoNetIntegrationTest {
@Test
public void simple() throws IOException, InterruptedException {
String toSend = "toSend";
client.sendTCP(toSend);
// sleep as the receiving is done in another thread
Thread.sleep(100);
verify(listener).received(Matchers.<Connection> anyObject(), eq(toSend));
}
@Test(invocationCount = 10)
public void multiThreaded() throws InterruptedException, BrokenBarrierException, IOException {
int numThreads = 3;
int numObjects = 1024;
Set<Object> sendingObjectsSet = getObjectToSend(numObjects);
Queue<Object> queue = new ArrayBlockingQueue<>(numObjects);
queue.addAll(sendingObjectsSet);
CyclicBarrier cyclicBarrier = new CyclicBarrier(numThreads + 1);
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < numThreads; i++) {
Sender sender = new Sender(cyclicBarrier, queue);
threads.add(sender);
sender.start();
}
cyclicBarrier.await();
for (Thread thread : threads) {
thread.join(10000);
}
// sleep as the receiving is done in another thread
Thread.sleep(100);
for (Object toSend : sendingObjectsSet) {
verify(listener).received(Matchers.<Connection> anyObject(), eq(toSend));
}
}
}
protected Set<Object> getObjectToSend(int numObjects) {
Random random = new Random();
// use set to avoid possibility of same to send
Set<Object> sendingObjectsSet = new HashSet<>();
for (int i = 0; i < numObjects; i++) {
List<Integer> sendList = new ArrayList<>();
for (int j = 0, count = 1024; j < count; j++) {
sendList.add(Integer.valueOf(random.nextInt()));
}
sendingObjectsSet.add(sendList);
}
return sendingObjectsSet;
}
protected Service getServiceForClient() {
Service service = ObjectSpace.getRemoteObject(client, 1, Service.class);
((RemoteObject) service).setNonBlocking(false);
((RemoteObject) service).setTransmitReturnValue(true);
((RemoteObject) service).setResponseTimeout(6000000);
return service;
}
protected class Sender extends Thread {
private final CyclicBarrier cyclicBarrier;
private final Queue<Object> queue;
public Sender(CyclicBarrier cyclicBarrier, Queue<Object> queue) {
this.cyclicBarrier = cyclicBarrier;
this.queue = queue;
}
@Override
public void run() {
try {
cyclicBarrier.await();
} catch (Exception e) {
// ignore
}
Object toSend = queue.poll();
while (null != toSend) {
client.sendTCP(toSend);
toSend = queue.poll();
}
}
}
/**
* Invoker for the {@link RemoteMethodInvocation#multiThreaded()} method.
*
* @author Ivan Senic
*
*/
protected class Invoker extends Thread {
private final CyclicBarrier cyclicBarrier;
private final Queue<Object> queue;
private final Service service;
public Invoker(CyclicBarrier cyclicBarrier, Queue<Object> queue, Service service) {
this.cyclicBarrier = cyclicBarrier;
this.queue = queue;
this.service = service;
}
@Override
public void run() {
try {
cyclicBarrier.await();
} catch (Exception e) {
// ignore
}
Object toSend = queue.poll();
while (null != toSend) {
assertThat(toSend.toString(), service.returnSame(toSend), is(equalTo(toSend)));
toSend = queue.poll();
}
}
}
/**
* Test service.
*/
public interface Service {
<E> E returnSame(E o);
}
}