package de.tum.in.www1.jReto.module.remoteP2P;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.concurrent.Executor;
import javax.websocket.ClientEndpointConfig;
import javax.websocket.CloseReason;
import javax.websocket.DeploymentException;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.SendHandler;
import javax.websocket.SendResult;
import javax.websocket.Session;
import org.glassfish.tyrus.client.ClientManager;
import de.tum.in.www1.jReto.module.api.Connection;
public class RemoteP2PConnection implements Connection {
private final Executor executor;
private Connection.Handler handler;
private URI serverUri;
private boolean awaitConfirmation;
private Session dataSession;
public RemoteP2PConnection(Executor executor, URI serverUri, boolean awaitConfirmation) {
this.executor = executor;
this.serverUri = serverUri;
this.awaitConfirmation = awaitConfirmation;
}
@Override
public void setHandler(Handler handler) {
this.handler = handler;
}
@Override
public Handler getHandler() {
return this.handler;
}
@Override
public boolean isConnected() {
return this.dataSession != null && this.dataSession.isOpen() && !this.awaitConfirmation;
}
@Override
public int getRecommendedPacketSize() {
return 2048;
}
@Override
public void writeData(ByteBuffer data) {
if (!this.isConnected()) {
System.err.println("attempted to write before connection is open.");
return;
}
new Thread(() ->
this.dataSession.getAsyncRemote().sendBinary(data, new SendHandler() {
@Override
public void onResult(SendResult arg0) {
RemoteP2PConnection.this.executor.execute(new Runnable() {
@Override
public void run() {
if (RemoteP2PConnection.this.handler != null) RemoteP2PConnection.this.handler.onDataSent(RemoteP2PConnection.this);
}
});
}
})).start();
}
private void onOpen() {
if (this.handler != null && this.isConnected()) this.handler.onConnect(RemoteP2PConnection.this);
}
private void onMessage(ByteBuffer data) {
data.order(ByteOrder.LITTLE_ENDIAN);
if (this.awaitConfirmation) {
int remaining = data.remaining();
int result = 0;
if (remaining == 4) result = data.getInt();
if (remaining != 4 || result != 1) {
System.err.println("Expected web socket connection open confirmation, received invalid data: length: "+remaining+" result: "+result);
RemoteP2PConnection.this.close();
} else {
RemoteP2PConnection.this.awaitConfirmation = false;
if (RemoteP2PConnection.this.handler != null) this.handler.onConnect(this);
}
} else {
if (RemoteP2PConnection.this.handler != null) RemoteP2PConnection.this.handler.onDataReceived(RemoteP2PConnection.this, data);
}
}
@Override
public void connect() {
if (this.dataSession != null) return;
// client.connect is blocking, therefore we need to start a new thread.
new Thread(new Runnable() {
@Override
public void run() {
ClientEndpointConfig configuration = ClientEndpointConfig.Builder.create().build();
ClientManager client = ClientManager.createClient();
try {
client.connectToServer(new Endpoint() {
@Override
public void onOpen(final Session session, EndpointConfig config) {
RemoteP2PConnection.this.dataSession = session;
RemoteP2PConnection.this.executor.execute(new Runnable() {
@Override
public void run() {
RemoteP2PConnection.this.onOpen();
}
});
session.addMessageHandler(new MessageHandler.Whole<ByteBuffer>() {
@Override
public void onMessage(final ByteBuffer data) {
RemoteP2PConnection.this.executor.execute(new Runnable() {
@Override
public void run() {
RemoteP2PConnection.this.onMessage(data);
}
});
}
});
}
@Override
public void onClose(Session session, final CloseReason closeReason) {
RemoteP2PConnection.this.executor.execute(new Runnable() {
@Override
public void run() {
System.err.println("WebSocket data connection closed with reason: "+closeReason.getCloseCode()+" / "+closeReason.getReasonPhrase());
RemoteP2PConnection.this.dataSession = null;
if (RemoteP2PConnection.this.handler != null) RemoteP2PConnection.this.handler.onClose(RemoteP2PConnection.this);
}
});
}
@Override
public void onError(Session session, final Throwable thr) {
RemoteP2PConnection.this.executor.execute(new Runnable() {
@Override
public void run() {
System.err.println("WebSocket data connection closed with error: "+thr);
RemoteP2PConnection.this.dataSession = null;
if (RemoteP2PConnection.this.handler != null) RemoteP2PConnection.this.handler.onClose(RemoteP2PConnection.this);
}
});
}
}, configuration, RemoteP2PConnection.this.serverUri);
} catch (DeploymentException | IOException e) {
System.err.println("Failed to establish web socket data connection: "+e);
}
}
}).start();
}
@Override
public void close() {
new Thread(() -> {
try {
this.dataSession.close();
this.dataSession = null;
} catch (IOException e) {
System.err.println("Failed to close data websocket connection: "+e);
}
}).start();
}
}