package eu.hgross.blaubot.core;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import eu.hgross.blaubot.admin.AbstractAdminMessage;
import eu.hgross.blaubot.admin.CloseRelayConnectionAdminMessage;
import eu.hgross.blaubot.admin.RelayAdminMessage;
import eu.hgross.blaubot.admin.ServerConnectionAvailableAdminMessage;
import eu.hgross.blaubot.admin.ServerConnectionDownAdminMessage;
import eu.hgross.blaubot.core.acceptor.IBlaubotConnectionAcceptor;
import eu.hgross.blaubot.core.acceptor.IBlaubotConnectionListener;
import eu.hgross.blaubot.core.acceptor.IBlaubotIncomingConnectionListener;
import eu.hgross.blaubot.core.connector.IBlaubotConnector;
import eu.hgross.blaubot.messaging.BlaubotChannelManager;
import eu.hgross.blaubot.messaging.BlaubotMessage;
import eu.hgross.blaubot.messaging.BlaubotMessageReceiver;
import eu.hgross.blaubot.messaging.IBlaubotAdminMessageListener;
import eu.hgross.blaubot.messaging.IBlaubotMessageListener;
import eu.hgross.blaubot.mock.BlaubotConnectionQueueMock;
import eu.hgross.blaubot.util.Log;
/**
* Manages the connection to the BlaubotServer (if set).
* It makes use of the BlaubotChannelManager to listen and send admin messages to other devices.
*
* It operates in two modes:
* Client and Master (as the BlaubotChannelManager).
*
* It always sends admin messages if a connection to the server is available or down.
*
* In client mode it listens to RelayAdminMessages and sends them to the available server connection (if any).
*
* In server mode it listens to the Available/Down messages and collects possible connections to the
* server as there could be more than just one.
*/
public class ServerConnectionManager extends LifecycleListenerAdapter {
private static final String LOG_TAG = "ServerConnectionManager";
/**
* The period to check if a connection path to the server exists and needs to be chosen
* (master mode only).
*/
private static final long CONNECTION_SELECT_INTERVAL = 1500;
/**
* Milliseconds to await termination of the connect scheduler
*/
private static final long SHUTDOWN_TERMINATION_TIMEOUT = 2500;
/**
* The main blaubot channel manager
*/
private final BlaubotChannelManager channelManager;
/**
* our own device
*/
private final IBlaubotDevice ownDevice;
/**
* The MAIN blaubot connection manager (the manager used for the actual blaubot network).
* Don't confuse this with the connectionManager used for serverConnector connections.
*/
private final BlaubotConnectionManager mainBlaubotConnectionManager;
/**
* The connection manager holding the connections established from serverConnectors.
* Don't confuse this with the mainBlaubotConnectionManager.
* The connections stored here are simple connections and have to be upgraded to KingdomConnections
* before usage.
*/
private final BlaubotConnectionManager connectionManager;
/**
* The serverConnector injected via the getter. May be null.
*/
private BlaubotServerConnector serverConnector;
/**
* Indicates whether this manager operates in master mode or not
*/
private volatile boolean isMaster = false;
/**
* The facade connection added to the main channel manager if operating as master. May be null.
*/
private volatile IBlaubotConnection facadeConnection;
/**
* The current king device set by the LifecycleListener.
* May be null.
*/
private IBlaubotDevice currentKingDevice;
/**
* Current connection to the server, if in Master mode. May be null.
*/
private BlaubotKingdomConnection currentServerConnection;
/**
* synchronizes the creation and shutdowns of the RelayMessageMediator
*/
private Object serverConnectionLock = new Object();
/**
* Current RelayMessageMediator in client mode. May be null.
* Takes a connection and uses the connection to speak to the server.
* Takes admin relay messages and sends them through the server connection.
* Receives data from the server, wraps them into RelayMessages and sends them
* to the king as admin message.
* On the King side, they will be made available as bytestream to the usual
* channel manager.
*/
private RelayMessageMediator relayMessageMediator;
private Object relayMessageMediatorLock = new Object();
/**
* Schedules the connectionSelectionTask.
* Is created and shut down on setMaster(false/true)
*/
private volatile ScheduledExecutorService connectionSelectionExecutorService;
/**
* A TimerTask that checks if a connection exists and if not tries to connect to the
* server.
*/
private Runnable connectionSelectionTask = new Runnable() {
@Override
public void run() {
try {
if(!isMaster) {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Not master, nothing to do.");
}
return;
}
synchronized (serverConnectionLock) {
final boolean connected = currentServerConnection != null;
if (!connected) {
if (Log.logDebugMessages()) {
// Log.d(LOG_TAG, "Currently no connection to the server available. Selecting ...");
}
List<IBlaubotConnection> allConnections = connectionManager.getAllConnections();
if (allConnections.isEmpty()) {
if (Log.logDebugMessages()) {
// Log.d(LOG_TAG, "No connection path to the server known ... could not select a connection.");
}
return;
}
// -- we have connections
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "There are " + allConnections.size() + " connections to the server available, selecting one out of these: " + allConnections);
}
// choose and add disconnection handling
/**
* TODO: prioritize a connection from ourselve (king), because it is a direct connection. If we don'T have a direct connection prioritize peasant relay connections over prince relay connections!
*/
IBlaubotConnection chosenConnection = allConnections.get(0);
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Chosen connection: " + chosenConnection);
}
// upgrade to KingdomConnection
final BlaubotKingdomConnection kingdomConnection = BlaubotKingdomConnection.createFromOutboundConnection(chosenConnection, ownDevice.getUniqueDeviceID());
currentServerConnection = kingdomConnection;
kingdomConnection.addConnectionListener(new IBlaubotConnectionListener() {
@Override
public void onConnectionClosed(IBlaubotConnection connection) {
synchronized (serverConnectionLock) {
if (currentServerConnection == kingdomConnection) {
currentServerConnection = null;
}
}
}
});
// add to the main blaubot connection manager, it will bobble up from there to the channel manager
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Adding the kingdom connection to the Blaubot connection manager");
}
mainBlaubotConnectionManager.addConnection(kingdomConnection);
}
}
} catch (Throwable t) {
if (Log.logErrorMessages()) {
Log.e(LOG_TAG, "Task failed", t);
}
t.printStackTrace();
}
}
};
/**
*
* @param channelManager the main blaubot channel manager
* @param ownDevice our own device
* @param mainBlaubotConnectionManager the main blaubot connection manager used by the blaubot instance itself
*/
public ServerConnectionManager(final BlaubotChannelManager channelManager, IBlaubotDevice ownDevice, final BlaubotConnectionManager mainBlaubotConnectionManager) {
this.ownDevice = ownDevice;
this.mainBlaubotConnectionManager = mainBlaubotConnectionManager;
this.connectionManager = new BlaubotConnectionManager(new ArrayList<IBlaubotConnectionAcceptor>(), new ArrayList<IBlaubotConnector>());
this.channelManager = channelManager;
this.channelManager.addAdminMessageListener(new IBlaubotAdminMessageListener() {
@Override
public void onAdminMessage(AbstractAdminMessage adminMessage) {
if (adminMessage instanceof ServerConnectionAvailableAdminMessage) {
// create relay connection if master
if(isMaster) {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "A server connection is available through mediator device " + ((ServerConnectionAvailableAdminMessage) adminMessage).getMediatorUniqueDeviceId());
}
final String mediatorUniqueDeviceId = ((ServerConnectionAvailableAdminMessage) adminMessage).getMediatorUniqueDeviceId();
final String recipientUniqueDeviceId = ((ServerConnectionAvailableAdminMessage) adminMessage).getRecipientUniqueDeviceId();
final BlaubotServerRelayConnection conn = new BlaubotServerRelayConnection(mediatorUniqueDeviceId, recipientUniqueDeviceId);
// remember the connection
onConnectionAvailable(conn);
}
} else if (adminMessage instanceof ServerConnectionDownAdminMessage) {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "A server connection is NOT available anymore through mediator device " + ((ServerConnectionDownAdminMessage) adminMessage).getMediatorUniqueDeviceId());
}
// only interesting for the master
// the master is only interested in this information, if he is using the connection actively or if he wants to know, which devices have a connection to the server
// all this is handled through the usual blaubot connection manager (this.connectionManager) and the wrapping connection
// BlaubotServerRelayConnection, which is also listening to this admin message to trigger te onDisconnect listeners.
// so nothing to do here
} else if (adminMessage instanceof RelayAdminMessage || adminMessage instanceof CloseRelayConnectionAdminMessage) {
if(!isMaster) {
/**
* If we are not master, we have the mediator role.
* important here:
* Do we already have a mediator?
* if yes -> do nothing, the mediator should do the rest
* if no -> Get a serverconnection from the ConnectionManager. There has to be a connection because RelayAdminMessages and CloseRelayConnectionAdminMessage
* are only send after we sent ServerConnectionAvailable but of course there could be some timing problems and the connection is not there.
* Then create the mediator.
*/
synchronized (relayMessageMediatorLock) {
if(relayMessageMediator == null) {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Got a RelayAdminMessage and had no relayMessageMediator. Creating one.");
}
List<IBlaubotConnection> allServerConnections = ServerConnectionManager.this.connectionManager.getAllConnections();
if(!allServerConnections.isEmpty()) {
IBlaubotConnection connection = allServerConnections.get(0);
if(connection instanceof BlaubotServerRelayConnection) {
// TODO: concurreny problem here on change to master mode (got a websocket connection here)
// TODO sync isMaster flag and setMaster()
throw new RuntimeException(""+allServerConnections);
}
final RelayMessageMediator mediator = new RelayMessageMediator(connection);
// maintain reference
connection.addConnectionListener(new IBlaubotConnectionListener() {
@Override
public void onConnectionClosed(IBlaubotConnection connection) {
if(relayMessageMediator == mediator) {
relayMessageMediator = null;
}
}
});
relayMessageMediator = mediator;
mediator.activate();
// kick the current admin message in
mediator.onAdminMessage(adminMessage);
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Created a RelayMessageMediator for connection " + connection);
}
} else {
if (Log.logErrorMessages()) {
Log.e(LOG_TAG, "No mediator to resend the received RelayAdminMessage.");
}
}
}
}
}
}
}
});
}
/**
* Handles everything if we get aware of a available connection
* @param connection the newly available connection
*/
private void onConnectionAvailable(IBlaubotConnection connection) {
if(!isMaster) {
// send up message to king, if we are not the king
ServerConnectionAvailableAdminMessage availableAdminMessage = new ServerConnectionAvailableAdminMessage(ownDevice.getUniqueDeviceID(), connection.getRemoteDevice().getUniqueDeviceID());
channelManager.publishToAllConnections(availableAdminMessage.toBlaubotMessage());
connectionManager.addConnection(connection);
} else {
// if king, we store the connection in the connection manager, where it will be automatically removed, if not available anymore.
connectionManager.addConnection(connection);
}
}
/**
* sets the server connector
*
* @param serverConnector the connector
*/
public synchronized void setServerConnector(BlaubotServerConnector serverConnector) {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "ServerConnector was injected: " + serverConnector);
}
if (this.serverConnector != null) {
// -- there is already a serverConnector
// shut it down first
BlaubotServerConnector curServerConnector = this.serverConnector;
this.serverConnector = null;
curServerConnector.setIncomingConnectionListener(null);
curServerConnector.deactivateServerConnector();
final IBlaubotConnection serverConnection = curServerConnector.getServerConnection();
if (serverConnection != null) {
serverConnection.disconnect();
}
}
this.serverConnector = serverConnector;
this.serverConnector.setIncomingConnectionListener(new IBlaubotIncomingConnectionListener() {
@Override
public void onConnectionEstablished(IBlaubotConnection connection) {
// add the listener to remove everything if the connections is disconnected
connection.addConnectionListener(new IBlaubotConnectionListener() {
@Override
public void onConnectionClosed(IBlaubotConnection connection) {
if(!isMaster) {
// send down message to king, if we are not king/master to let him handle any dependencies on this connection (through RelayConnection for example)
ServerConnectionDownAdminMessage downAdminMessage = new ServerConnectionDownAdminMessage(ownDevice.getUniqueDeviceID());
channelManager.publishToAllConnections(downAdminMessage.toBlaubotMessage());
}
}
});
// add the connection to the connectionManager (only the server connections)
onConnectionAvailable(connection);
}
});
if (isConnected) {
this.serverConnector.activateServerConnector();
}
}
/**
* @return the serverConnector or null, if never set by the user
*/
public BlaubotServerConnector getServerConnector() {
return serverConnector;
}
/**
* Indicates, whether we are connected to a blaubot network or not.
* Mainly used to activate a ServerConnector, that is attached after blaubot was already
* started.
*/
private volatile boolean isConnected = false;
@Override
public void onConnected() {
isConnected = true;
if (this.serverConnector != null) {
this.serverConnector.activateServerConnector();
}
}
@Override
public void onDisconnected() {
isConnected = false;
if (this.serverConnector != null) {
this.serverConnector.deactivateServerConnector();
}
clear(); // disconnects the connection, if any
currentKingDevice = null;
}
@Override
public void onDeviceLeft(IBlaubotDevice blaubotDevice) {
if(isMaster) {
// send an on connection down for this device to myself to be sure, that it is handled properly
ServerConnectionDownAdminMessage downAdminMessage = new ServerConnectionDownAdminMessage(blaubotDevice.getUniqueDeviceID());
channelManager.publishToSingleDevice(downAdminMessage.toBlaubotMessage(), ownDevice.getUniqueDeviceID());
}
}
@Override
public void onKingDeviceChanged(IBlaubotDevice oldKing, IBlaubotDevice newKing) {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "King device changed. Clearing state and disconnecting all connections.");
}
clear();
this.currentKingDevice = newKing;
}
/**
* clears the state and disconnects all server connections
*/
private void clear() {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Clearing state: Close connections");
}
for(IBlaubotConnection conn : this.connectionManager.getAllConnections()) {
conn.disconnect();
}
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Clearing state: RelayMessageMediator");
}
synchronized (relayMessageMediatorLock) {
if(this.relayMessageMediator != null) {
relayMessageMediator.serverConnection.disconnect();
this.relayMessageMediator.deactivate();
this.relayMessageMediator = null;
}
}
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Clearing state: ServerConnection");
}
synchronized (serverConnectionLock) {
if(this.currentServerConnection != null) {
this.currentServerConnection.disconnect();
this.currentServerConnection = null;
}
}
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "State cleared.");
}
}
/**
* Sets whether this manager operates in master mode or not
* @param isMaster true iff operating as master
*/
public void setMaster(final boolean isMaster) {
// TODO possible concurrency problems here. Maybe use a single threaded queue to synchronize this things
final boolean changed = isMaster != this.isMaster;
if(changed) {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Changed operating mode to " + (isMaster ? "master":"client"));
}
clear();
if(isMaster) {
// if there was a mediator from a former peasant/prince state, shut it down
synchronized (relayMessageMediatorLock) {
if(relayMessageMediator != null) {
relayMessageMediator.serverConnection.disconnect();
relayMessageMediator.deactivate();
relayMessageMediator = null;
}
}
// activate scheduler
if (this.connectionSelectionExecutorService != null) {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Executer already started ...");
}
return; // already started
}
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Starting up the server connection selection task scheduler.");
}
this.connectionSelectionExecutorService = Executors.newSingleThreadScheduledExecutor();
this.connectionSelectionExecutorService.scheduleAtFixedRate(connectionSelectionTask, 0, CONNECTION_SELECT_INTERVAL, TimeUnit.MILLISECONDS);
} else {
// deactivate scheduler
if (this.connectionSelectionExecutorService != null) {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Shutting down the executor service ...");
}
this.connectionSelectionExecutorService.shutdownNow();
try {
final boolean timedOut = !this.connectionSelectionExecutorService.awaitTermination(SHUTDOWN_TERMINATION_TIMEOUT, TimeUnit.MILLISECONDS);
if (timedOut && Log.logErrorMessages()) {
Log.e(LOG_TAG, "ExecutorService termination timeout");
}
} catch (InterruptedException e) {
} finally {
this.connectionSelectionExecutorService = null;
}
}
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Disconnecting all server connections.");
}
// disconnect all connections
for(IBlaubotConnection conn : this.connectionManager.getAllConnections()) {
conn.disconnect();
}
}
}
this.isMaster = isMaster;
}
/**
* @return the currently USED server connection by the channel manager or null, if no connection exists
*/
public BlaubotKingdomConnection getCurrentlyUsedServerConnection() {
synchronized (serverConnectionLock) {
return currentServerConnection;
}
}
/**
*
* @return list of blaubotconnections the server collected by the manager
*/
public List<IBlaubotConnection> getConnectionsToServer() {
return new ArrayList<>(connectionManager.getAllConnections());
}
/**
* The mediator that actually sends the relay messages if not in master mode.
* Is created for one kingdom connection.
* Note that it registers itself to the channel manager in the constructor and therefore misses all
* previously received admin messages. The initial admin message has therefore to be injected
* via onAdminMessage().
*
* It manages the real connection to the server (the direct connection).
* To do that, it uses a MessageSender and MessageReceiver pair (for chunking).
*/
private class RelayMessageMediator implements IBlaubotAdminMessageListener {
private static final String LOG_TAG = "RelayMessageMediator";
private final IBlaubotConnection serverConnection;
/**
* receives messages from the server
*/
private final BlaubotMessageReceiver messageReceiver;
/**
* @param serverConnection the serverConnection to be relayed
*/
public RelayMessageMediator(final IBlaubotConnection serverConnection) {
this.serverConnection = serverConnection;
this.messageReceiver = new BlaubotMessageReceiver(serverConnection);
this.messageReceiver.setForwardChunks(true); // forward chunked messages (don't inspect them)
// handles broken connections
serverConnection.addConnectionListener(new IBlaubotConnectionListener() {
@Override
public void onConnectionClosed(IBlaubotConnection connection) {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Server connection closed. Deactivating messageReceiver.");
}
messageReceiver.deactivate(null);
channelManager.removeAdminMessageListener(RelayMessageMediator.this);
}
});
// listen to messages from the server
this.messageReceiver.addMessageListener(new IBlaubotMessageListener() {
@Override
public void onMessage(BlaubotMessage blaubotMessage) {
// if (Log.logDebugMessages()) {
// Log.d(LOG_TAG, "Got a message from the server connection, dispatching it via RelayMessage");
// }
// got a message from the server, relay to the king
RelayAdminMessage relayAdminMessage = new RelayAdminMessage(blaubotMessage.toBytes());
channelManager.publishToAllConnections(relayAdminMessage.toBlaubotMessage());
}
});
// listens to messages from the king
channelManager.addAdminMessageListener(this);
}
public void activate() {
messageReceiver.activate();
}
public void deactivate() {
messageReceiver.deactivate(null);
}
@Override
public void onAdminMessage(AbstractAdminMessage adminMessage) {
if (adminMessage instanceof RelayAdminMessage) {
if(Log.logDebugMessages()) {
BlaubotMessage unwrappedMessage = ((RelayAdminMessage) adminMessage).getAsBlaubotMessage();
//if (!unwrappedMessage.getMessageType().isKeepAliveMessage()) {
// Log.d(LOG_TAG, "Dispatching a relay message to the server connection: " + unwrappedMessage);
//}
}
byte[] messageBytes = ((RelayAdminMessage) adminMessage).getMessageBytes();
try {
serverConnection.write(messageBytes);
} catch (IOException e) {
// handled by the connection manager
}
} else if(adminMessage instanceof CloseRelayConnectionAdminMessage) {
String mediatorUniqueDeviceId = ((CloseRelayConnectionAdminMessage) adminMessage).getMediatorUniqueDeviceId();
if (mediatorUniqueDeviceId.equals(ownDevice.getUniqueDeviceID())) {
if(Log.logDebugMessages()) {
Log.d(LOG_TAG, "Got instructions to close the relay connection. Disconnecting direct connection to the server ...");
}
serverConnection.disconnect();
// will notify listeners and finally send an down admin message to the king ...
}
}
}
}
/**
* A relay connection is a connection that uses admin messages to transport bytes from
* A to C using an intermediate participant B (mediator).
* There are connections A to B and B to C and the relay connection uses an established
* network to make the transitive connection between A and C (recipient).
*
* The BlaubotServerRelayConnection is only created on A (the king).
* It needs the RelayMessageMediator instance running on B.
* B needs to have a IBlaubotConnection to C.
*
* Note that it only seems like the connection is capable of sending arbitrary bytes through
* it but since we are using MessageReceivers internally it is only capable of sending and
* receiving full blaubot message byte packages!
*/
public class BlaubotServerRelayConnection extends BlaubotConnectionQueueMock implements IBlaubotAdminMessageListener {
private UUID uuid = UUID.randomUUID();
private static final String LOG_TAG = "BlaubotServerRelayConnection";
/**
* The unique device id of the device over which the relay messages are relayed
*/
private final String mediatorUniqueDeviceId;
/**
* The final destination where data is sent to on write(*).. calls.
*/
private final String recipientUniqueDeviceId;
/**
* Everything thaht is written to this connection via it's write(*) methods will be received
* by this receiver locally and then be wrapped into a RelayMessage to be send as admin
* message via the ChannelManager.
*/
private final BlaubotMessageReceiver messageReceiver;
/**
* Note: starts a thread!
* @param mediatorUniqueDeviceId the uniqueDeviceId to which the data should be send (via relay message) when using the write(*) methods.
* @param recipientUniqueDeviceId the recipient's unique device id (Server/King)
*/
public BlaubotServerRelayConnection(final String mediatorUniqueDeviceId, String recipientUniqueDeviceId) {
super(new BlaubotDevice(recipientUniqueDeviceId));
/*
* This connection can read the things written via write() to this connection instance.
*/
final BlaubotConnectionQueueMock dummyConnection = getOtherEndpointConnection(new BlaubotDevice("Internal BlaubotServerRelayConnection DummyDevice "));
this.messageReceiver = new BlaubotMessageReceiver(dummyConnection);
this.messageReceiver.setForwardChunks(true); // we want to just forward junks (not collect them as whole to be chunked again later)
this.messageReceiver.activate();
this.messageReceiver.addMessageListener(new IBlaubotMessageListener() {
@Override
public void onMessage(BlaubotMessage blaubotMessage) {
// wrap the message into a relay admin message
final RelayAdminMessage relayAdminMessage = new RelayAdminMessage(blaubotMessage.toBytes());
// create a blaubot mesasge from the relay admin message (= blaubot message containing which is a relay admin message, which contains a blaubotmessage ;-))
final BlaubotMessage msg = relayAdminMessage.toBlaubotMessage();
// if (!blaubotMessage.getMessageType().isKeepAliveMessage()) {
// Log.d(LOG_TAG, "Dispatching ... Sending RelayAdminMessage to mediator: " + msg);
// }
// send it to the mediator
channelManager.publishToSingleDevice(msg, mediatorUniqueDeviceId);
}
});
this.recipientUniqueDeviceId = recipientUniqueDeviceId;
this.mediatorUniqueDeviceId = mediatorUniqueDeviceId;
// listener handling (cleanup and wiring)
channelManager.addAdminMessageListener(this);
this.addConnectionListener(new IBlaubotConnectionListener() {
@Override
public void onConnectionClosed(IBlaubotConnection connection) {
channelManager.removeAdminMessageListener(BlaubotServerRelayConnection.this);
}
});
addConnectionListener(new IBlaubotConnectionListener() {
@Override
public void onConnectionClosed(IBlaubotConnection connection) {
messageReceiver.deactivate(null);
}
});
}
@Override
public void onAdminMessage(AbstractAdminMessage adminMessage) {
if (adminMessage instanceof ServerConnectionDownAdminMessage) {
// this block only happens on the master
if(((ServerConnectionDownAdminMessage) adminMessage).getMediatorUniqueDeviceId().equals(mediatorUniqueDeviceId)) {
if(Log.logDebugMessages()) {
Log.d(LOG_TAG, "A relay connection is down (" + mediatorUniqueDeviceId + ") ");
}
// if the mediator has no active connection to the server anymore, we disconnect the connection.
// note that this message will also be received if an onDeviceLeft() for this device occurs.
_disconnect(); // triggers its own listeners
}
} else if (adminMessage instanceof RelayAdminMessage) {
// if(Log.logDebugMessages()) {
// Log.d(LOG_TAG, "Dispatching relay admin message of " + ((RelayAdminMessage) adminMessage).getMessageBytes().length + " bytes to input stream of the relay connection");
// }
// put data to the queue. This bytes can then be read via the read(*) methods.
byte[] messageBytes = ((RelayAdminMessage) adminMessage).getMessageBytes();
writeMockDataToInputStream(messageBytes);
}
}
private volatile boolean notifiedDisconnect = false;
@Override
protected void notifyDisconnected() {
if (notifiedDisconnect) {
return;
}
super.notifyDisconnected();
notifiedDisconnect = true;
}
@Override
public void disconnect() {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "disconnect() called, sending CloseRelayConnectionAdminMessage");
}
// send disconnect instruction to mediator
CloseRelayConnectionAdminMessage adminMessage = new CloseRelayConnectionAdminMessage(mediatorUniqueDeviceId);
final boolean sent = channelManager.publishToSingleDevice(adminMessage.toBlaubotMessage(), mediatorUniqueDeviceId);
if (!sent) {
if (Log.logWarningMessages()) {
Log.w(LOG_TAG, "CloseRelayConnectionAdminMessage could not be send on disconnect(). Assuming connectivity broken and notifying listener that this connection is disconnected.");
}
// if we are not able to send this instruction, disconnect locally
_disconnect();
}
// the actual disconnect will be announced by an admin message (either on device left or a direct down message)
// and then call _disconnect();
}
private void _disconnect() {
super.disconnect();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
BlaubotServerRelayConnection that = (BlaubotServerRelayConnection) o;
if (uuid != null ? !uuid.equals(that.uuid) : that.uuid != null) return false;
return true;
}
@Override
public int hashCode() {
int result = super.hashCode();
result = 31 * result + (uuid != null ? uuid.hashCode() : 0);
return result;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("BlaubotServerRelayConnection{");
sb.append("mediatorUniqueDeviceId='").append(mediatorUniqueDeviceId).append('\'');
sb.append(", recipientUniqueDeviceId='").append(recipientUniqueDeviceId).append('\'');
sb.append('}');
return sb.toString();
}
public String getMediatorUniqueDeviceId() {
return mediatorUniqueDeviceId;
}
public String getRecipientUniqueDeviceId() {
return recipientUniqueDeviceId;
}
}
}