package eu.hgross.blaubot.core;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import eu.hgross.blaubot.core.acceptor.IBlaubotConnectionListener;
import eu.hgross.blaubot.core.acceptor.IBlaubotIncomingConnectionListener;
import eu.hgross.blaubot.core.acceptor.discovery.IBlaubotBeaconStore;
import eu.hgross.blaubot.core.connector.IBlaubotConnector;
import eu.hgross.blaubot.util.Log;
/**
* This class connects to BlaubotServer instances.
* It consists of a BeaconStore holding the immutable connection params for the server and
* a set of connectors to be used to connect to the given connection params.
*
* It is designed to be started from a lifecycle listener and should be activated/deactivated by
* the connect/disconnect events.
*/
public class BlaubotServerConnector {
private static final String LOG_TAG = "BlaubotServerConnector";
/**
* Min Interval between timer task executions that check the server status
*/
private static final long CHECK_CONNECTIVITY_INTERVAL = 1000;
private static final long SHUTDOWN_TERMINATION_TIMEOUT = 6000;
/**
* Connectors to be used to connect to the server
*/
private final List<IBlaubotConnector> connectors;
/**
* The beacon store containing connection information to connect to the serverDevice
*/
private final IBlaubotBeaconStore beaconStore;
/**
* The server device to connect to
*/
private final IBlaubotDevice serverDevice;
/**
* Connection to the server.
* If not null, a connection to the server was successfully established
*/
private volatile IBlaubotConnection serverConnection;
/**
* Locks acccess to the serverConnection
*/
private Object serverConnectionLock = new Object();
/**
* Schedules the connectTimerTask.
* Is created and shut down on activate/deactivate.
*/
private volatile ScheduledExecutorService scheduledExecutorService;
/**
* This is the switch which activates or deactivates the server connection.
* It can be set via setDoConnect(true/false).
* If not active, the connection will not be established.
* If active the connector tries to connect to the server and allows the network to use
* this connection as the relay to the server.
*/
private volatile boolean doConnect = true;
/**
* A TimerTask that checks if a connection exists and if not tries to connect to the
* server.
*/
private Runnable connectTimerTask = new Runnable() {
@Override
public void run() {
try {
if (Log.logDebugMessages()) {
// Log.d(LOG_TAG, "ConnectTimerTask running ...");
}
if(!doConnect) {
// we don't want to connect (or act as relay)
return;
}
if (!isConnected()) {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Not connected. Connecting ...");
}
boolean result = connectToServer();
if (result) {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Now connected to " + serverConnection.getRemoteDevice());
}
} else {
if (Log.logWarningMessages()) {
Log.w(LOG_TAG, "Failed to connect to " + serverDevice);
}
}
}
if (Log.logDebugMessages()) {
// Log.d(LOG_TAG, "ConnectTimerTask done.");
}
} catch (Throwable t) {
if (Log.logErrorMessages()) {
Log.e(LOG_TAG, "Task failed", t);
}
t.printStackTrace();
}
}
};
/**
* A listener that is attached by a user to get informed about new connections established by
* the connector.
* May be null.
*/
private IBlaubotIncomingConnectionListener incomingConnectionListener;
/**
* @param serverUniqueDeviceId the server's static unqiueDevice id
* @param beaconStore the beacon store to be used. Should already contain connection meta data to conncet to serverUniqueDeviceId
* @param connectors the list of connectors to be used to establish a connection to the server device
*/
public BlaubotServerConnector(String serverUniqueDeviceId, IBlaubotBeaconStore beaconStore, List<IBlaubotConnector> connectors) {
this.serverDevice = new BlaubotDevice(serverUniqueDeviceId);
this.beaconStore = beaconStore;
this.connectors = connectors;
for (IBlaubotConnector connector : connectors) {
connector.setBeaconStore(this.beaconStore);
connector.setIncomingConnectionListener(connectorListener);
}
}
/**
* The configured server's unique device id
* @return server's uniqueDeviceId
*/
public String getServerUniqueDeviceId() {
return serverDevice.getUniqueDeviceID();
}
/**
* Gets called if a conneciton was established by one of the attached connectors
*/
private final IBlaubotIncomingConnectionListener connectorListener = new IBlaubotIncomingConnectionListener() {
@Override
public void onConnectionEstablished(IBlaubotConnection connection) {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "ConnectionEstablished: " + connection);
}
synchronized (serverConnectionLock) {
serverConnection = connection;
}
// if a user attached a listener, call
if (incomingConnectionListener != null) {
incomingConnectionListener.onConnectionEstablished(connection);
}
}
};
/**
* Tries to connect to the server device using all connectors sequentially until a connection could
* be established.
*
* @return true iff connected to the server
*/
private boolean connectToServer() {
if (isConnected()) {
return true;
}
// try to connect to the server
for (IBlaubotConnector connector : connectors) {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Trying to connect to server " + serverDevice + " with connector " + connector);
}
IBlaubotConnection connection = connector.connectToBlaubotDevice(serverDevice);
boolean connected = connection != null;
if (connected) {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Got a connection, attaching close listener ... ");
}
connection.addConnectionListener(new IBlaubotConnectionListener() {
@Override
public void onConnectionClosed(IBlaubotConnection connection) {
synchronized (serverConnectionLock) {
if(BlaubotServerConnector.this.serverConnection == connection) {
BlaubotServerConnector.this.serverConnection = null;
}
}
}
});
return true;
} else {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Connection with connector " + connector + " failed.");
}
}
}
if (Log.logWarningMessages()) {
Log.w(LOG_TAG, "Failed to connect to server " + serverDevice);
}
return false;
}
/**
* Indicates whether a connection to the server is available or not.
* If true, the connection is retrievable via getServerConnection()
*
* @return true iff a connection to the server is available
*/
public boolean isConnected() {
synchronized (serverConnectionLock) {
return serverConnection != null;
}
}
/**
* Returns the connection to the server, if available
*
* @return the connection to the server or null, if not connected
*/
public IBlaubotConnection getServerConnection() {
return serverConnection;
}
/**
* Activates the connector.
* The connector will poll the connectivity state and initiate a connection to the server if
* not currently connected.
*/
public void activateServerConnector() {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Activating server connector");
}
if (this.scheduledExecutorService != null) {
return; // already started
}
this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
this.scheduledExecutorService.scheduleAtFixedRate(connectTimerTask, 0, CHECK_CONNECTIVITY_INTERVAL, TimeUnit.MILLISECONDS);
}
/**
* Deactivates the connector and the connectivity polling.
*/
public void deactivateServerConnector() {
Log.d(LOG_TAG, "Deactivating server connector");
if (this.scheduledExecutorService != null) {
this.scheduledExecutorService.shutdownNow();
final boolean timedOut;
try {
timedOut = !this.scheduledExecutorService.awaitTermination(SHUTDOWN_TERMINATION_TIMEOUT, TimeUnit.MILLISECONDS);
if (timedOut && Log.logErrorMessages()) {
Log.e(LOG_TAG, "ExecutorService termination timeout");
}
} catch (InterruptedException e) {
} finally {
this.scheduledExecutorService = null;
}
}
}
/**
* Sets the incoming connection listener which is called if a connection to the server was established.
*
* @param incomingConnectionListener the listener to be set
*/
public void setIncomingConnectionListener(IBlaubotIncomingConnectionListener incomingConnectionListener) {
this.incomingConnectionListener = incomingConnectionListener;
}
@Override
public String toString() {
final StringBuffer sb = new StringBuffer("BlaubotServerConnector{");
sb.append("connectors=").append(connectors);
sb.append(", beaconStore=").append(beaconStore);
sb.append(", serverDevice=").append(serverDevice);
sb.append('}');
return sb.toString();
}
public static void main(String[] args) {
/**
* Sample usage of the connector.
* Use the main of BlaubotServer as server.
*/
/*
// client device
IBlaubotDevice ownDevice = new BlaubotDevice("A_Client");
// server data
final String SERVER_UNIQUE_DEVICE_ID = "Server1";
WebsocketConnectionMetaDataDTO connectionMetaData = new WebsocketConnectionMetaDataDTO("127.0.0.1", "/blaubot", 8080);
// supply connectors by creating adapters
BlaubotWebsocketAdapter websocketAdapter = new BlaubotWebsocketAdapter(ownDevice, "0.0.0.0", 8080);
ArrayList<IBlaubotConnector> connectors = new ArrayList<>();
connectors.add(websocketAdapter.getConnector());
// provide a beacon store with the server's connect meta data inside
BlaubotBeaconStore beaconStore = new BlaubotBeaconStore();
List<ConnectionMetaDataDTO> connectionMetaDataList = new ArrayList<>();
connectionMetaDataList.add(connectionMetaData);
beaconStore.putConnectionMetaData(SERVER_UNIQUE_DEVICE_ID, connectionMetaDataList);
// create the server connector
BlaubotServerConnector bsc = new BlaubotServerConnector(SERVER_UNIQUE_DEVICE_ID, beaconStore, connectors);
// start it
bsc.activateServerConnector();
*/
}
/**
* This is the switch which activates or deactivates the server connection.
* If not active, the connection will not be established.
* If active the connector tries to connect to the server and allows the network to use
* this connection as the relay to the server.
* If changing from active to inactive, (doConnect == false), then a possibly established connection
* will be disconnected.
* @param doConnect true if the connector should connect, false if not.
*/
public void setDoConnect(boolean doConnect) {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Changed doConnect to " + doConnect);
}
final boolean changed = this.doConnect != doConnect;
if (changed) {
this.doConnect = doConnect;
synchronized (serverConnectionLock) {
if (this.serverConnection != null) {
this.serverConnection.disconnect();
}
}
}
}
/**
*
* @return true iff the serverConnector is trying to establish a connection
*/
public boolean getDoConnect() {
return doConnect;
}
}