eu.hgross.blaubot.core.BlaubotConnectionManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of blaubot Show documentation
Show all versions of blaubot Show documentation
An easy to use publish/subscribe middleware to create and communicate through dynamically created adhoc networks.
package eu.hgross.blaubot.core;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import eu.hgross.blaubot.core.acceptor.ConnectionMetaDataDTO;
import eu.hgross.blaubot.core.acceptor.IBlaubotConnectionAcceptor;
import eu.hgross.blaubot.core.acceptor.IBlaubotConnectionManagerListener;
import eu.hgross.blaubot.core.acceptor.discovery.IBlaubotBeaconStore;
import eu.hgross.blaubot.core.connector.IBlaubotConnector;
import eu.hgross.blaubot.core.connector.IncompatibleBlaubotDeviceException;
import eu.hgross.blaubot.core.statemachine.BlaubotAdapterHelper;
import eu.hgross.blaubot.util.Log;
/**
* Manager to store and retrieve Connections related to {@link IBlaubotDevice} instances.
*
* @author Henning Gross
*
*/
public class BlaubotConnectionManager {
/**
* Can be used in conjunction with connectToDevice(IBlaubotDevice, int) to let the ConnectionManager
* decide how much retries to use.
*/
public static final int AUTO_MAX_RETRIES = -1;
private static final String LOG_TAG = "BlaubotConnectionManager";
private final ConcurrentHashMap> connections = new ConcurrentHashMap>();
private final List connectionListeners = new ArrayList<>();
private final List connectionAcceptors;
private final List connectionConnectors;
private final IBlaubotConnectionManagerListener connectionListener; // manager's own listener listening on
private IBlaubotBeaconStore beaconStore;
/**
* Creates a new {@link BlaubotConnectionManager} instance managing the given acceptors and connectors for incoming
* and outgoing connections.
*
* @param acceptors
* the acceptors to handle
* @param connectors
* the connectors to be used to connect to other devices
*/
public BlaubotConnectionManager(List acceptors, List connectors) {
this.connectionConnectors = connectors;
this.connectionAcceptors = acceptors;
this.connectionListener = new IBlaubotConnectionManagerListener() {
@Override
public void onConnectionEstablished(IBlaubotConnection connection) {
if(Log.logDebugMessages()) {
Log.d(LOG_TAG, "Got onConnectionEstablished: " + connection);
}
addConnection(connection);
}
@Override
public void onConnectionClosed(IBlaubotConnection connection) {
if(Log.logDebugMessages()) {
Log.d(LOG_TAG, "Got onConnectionClosed: " + connection);
}
removeConnection(connection);
}
};
// We set up a listener to each acceptor to get informed about new connections
for (IBlaubotConnectionAcceptor acceptor : connectionAcceptors) {
acceptor.setAcceptorListener(connectionListener);
}
for (IBlaubotConnector connector : connectionConnectors) {
connector.setIncomingConnectionListener(connectionListener);
}
}
protected void addConnection(IBlaubotConnection connection) {
if(Log.logDebugMessages()) {
Log.d(LOG_TAG, "Adding connection " + connection);
}
List deviceConnections = new CopyOnWriteArrayList<>();
this.connections.putIfAbsent(connection.getRemoteDevice(), deviceConnections);
deviceConnections = this.connections.get(connection.getRemoteDevice());
deviceConnections.add(connection);
connection.addConnectionListener(connectionListener);
// proxy event to our listeners
for (IBlaubotConnectionManagerListener listener : connectionListeners) {
listener.onConnectionEstablished(connection);
}
}
protected void removeConnection(IBlaubotConnection connection) {
if(Log.logDebugMessages()) {
Log.d(LOG_TAG, "Removing connection for device " + connection.getRemoteDevice());
}
List deviceConnections = this.connections.get(connection.getRemoteDevice());
if (deviceConnections == null) {
if(Log.logWarningMessages()) {
Log.w(LOG_TAG, "Tried to remove a connection for device " + connection.getRemoteDevice() + " but no connection for this device was registered.");
}
return;
}
boolean removed = deviceConnections.remove(connection);
if (!removed) {
if(Log.logWarningMessages()) {
Log.w(LOG_TAG, "Tried to remove a non existant connection for device " + connection.getRemoteDevice() + " from ConnectionManager but connection was not registered.");
}
}
// -- list was removed
// note: we leave the empty list in the map!
connection.removeConnectionListener(connectionListener);
// proxy event to our listeners
for (IBlaubotConnectionManagerListener listener : connectionListeners) {
listener.onConnectionClosed(connection);
}
}
/**
* @param blauBotDevice the list of {@link IBlaubotDevice}s of this device.
* @return the connection object (a socket, bluetoothsocket ...) or null, if nothing there for blauBotDevice
*/
public List getConnections(IBlaubotDevice blauBotDevice) {
return this.connections.get(blauBotDevice);
}
/**
* @return a list of devices with at least one active connection to our device
*/
public List getConnectedDevices() {
ArrayList devices = new ArrayList<>();
// there could be devices with no connection -> filter them out
for(IBlaubotDevice d : connections.keySet()) {
if(!connections.get(d).isEmpty()) {
devices.add(d);
}
}
return devices;
}
/**
* @return list of all active connections
*/
public List getAllConnections() {
ArrayList allConnections = new ArrayList<>();
for (List connections : this.connections.values()) {
allConnections.addAll(connections);
}
return allConnections;
}
public void addConnectionListener(IBlaubotConnectionManagerListener listener) {
this.connectionListeners.add(listener);
}
public void removeConnectionListener(IBlaubotConnectionManagerListener listener) {
this.connectionListeners.remove(listener);
}
/**
* @return list of connectors associated with this manager
*/
public List getConnectionConnectors() {
return connectionConnectors;
}
/**
* Tries to find a connector for the given uniqueDeviceId by determinig the appropriate connectors
* via the IBlaubotBeaconStores meta data (if a beaconstore was provided).
*
* @param uniqueDeviceId the unique device id to connect to
* @return the connector able to connect to one of the device's acceptors, or null, if no acceptor meta data or connector is available for this device
*/
public IBlaubotConnector getConnectorForDevice(String uniqueDeviceId) {
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Searching an appropriate connector for device" + uniqueDeviceId);
}
if (beaconStore == null) {
if (Log.logWarningMessages()) {
Log.w(LOG_TAG, "I have no BeaconStore and therefore can't connect anywhere.");
}
return null;
}
// search for metadata
final List lastKnownConnectionMetaData = beaconStore.getLastKnownConnectionMetaData(uniqueDeviceId);
if (lastKnownConnectionMetaData == null || lastKnownConnectionMetaData.isEmpty()) {
if (Log.logErrorMessages()) {
Log.e(LOG_TAG, "Never got acceptor meta data for this device: " + uniqueDeviceId + " and therefore can not chose a connector");
}
return null;
}
final List supportedConnectionTypes = BlaubotAdapterHelper.extractSupportedConnectionTypes(connectionConnectors);
if (Log.logDebugMessages()) {
Log.d(LOG_TAG, "Looking for acceptors with connection types: " + supportedConnectionTypes + "; in meta data: " + lastKnownConnectionMetaData);
}
for (ConnectionMetaDataDTO acceptorMetaData : lastKnownConnectionMetaData) {
final String connectionType = acceptorMetaData.getConnectionType();
if (supportedConnectionTypes.contains(connectionType)) {
// find the connector with this connection type
for (IBlaubotConnector connector : connectionConnectors) {
if (connector.getSupportedAcceptorTypes().contains(connectionType)) {
// choose this
return connector;
}
}
}
}
return null;
}
/**
* Tries to connect to the given {@link IBlaubotDevice}.
* If not successful after maxRetries, null will be returned.
* The retry mechanism uses the exponential backoff method which
* waiting time is configured by the {@link BlaubotAdapterConfig}
* for this device's adapter.
*
* @param device the {@link IBlaubotDevice} to connect to
* @param maxRetries max number of retries or BlaubotConnectionManager.AUTO_MAX_RETRIES to let the manager decide
* @return an {@link IBlaubotConnection} or null, if no connection could be established after maxRetries
*/
public IBlaubotConnection connectToBlaubotDevice(IBlaubotDevice device, int maxRetries) {
final IBlaubotConnector connectorForDevice = getConnectorForDevice(device.getUniqueDeviceID());
// TODO: connector could be null!
if(connectorForDevice == null) {
if(Log.logErrorMessages()) {
Log.e(LOG_TAG, "Could not retrieve connector for device " + device);
}
return null;
}
if(Log.logDebugMessages()) {
Log.d(LOG_TAG, "Using connector " + connectorForDevice + " to connect to device");
}
// use technology specific timings
final BlaubotAdapterConfig adapterConfig = connectorForDevice.getAdapter().getBlaubotAdapterConfig();
float backoffFactor = adapterConfig.getExponentialBackoffFactor();
int backoffTimeout = adapterConfig.getConnectorRetryTimeout();
// check max retries
if(maxRetries == BlaubotConnectionManager.AUTO_MAX_RETRIES) {
maxRetries = adapterConfig.getMaxConnectionRetries();
}
if(Log.logDebugMessages()) {
Log.d(LOG_TAG, "Trying to connect to device " + device + " using exponential backoff and max " + maxRetries + " retries.");
}
int outStandingRetries = maxRetries;
while (outStandingRetries-- > 0) {
IBlaubotConnection conn = connectToBlaubotDevice(device, connectorForDevice);
if(conn != null) {
return conn;
}
// backoff
try {
if(outStandingRetries==0)
break;
if(Log.logDebugMessages()) {
Log.d(LOG_TAG, "Backing of - outstanding retries: " + outStandingRetries);
}
Thread.sleep(backoffTimeout);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
backoffTimeout *= backoffFactor;
}
if(Log.logWarningMessages()) {
Log.w(LOG_TAG, "Connection to " + device + " could not be established after " + maxRetries + " retries.");
}
return null;
}
/**
* Tries to connect to the {@link IBlaubotDevice} corresponding to the given uniqueId
* by aksing all connectors if for a device object belonging to this uniqueId and trying
* to connect to this device.
*
* If not successful after maxRetries, null will be returned.
* The retry mechanism uses the exponential backoff method which
* waiting time is configured by the {@link BlaubotAdapterConfig}
* for this device's adapter.
*
* @param uniqueId the device's uniqueId
* @param maxRetries max number of retries or BlaubotConnectionManager.AUTO_MAX_RETRIES to let the manager decide
* @return an {@link IBlaubotConnection} or null, if no connection could be established after maxRetries
*/
public IBlaubotConnection connectToBlaubotDevice(String uniqueId, int maxRetries) {
IBlaubotDevice device = createBlaubotDeviceFromUniqueId(uniqueId);
if(device == null) {
return null;
}
return connectToBlaubotDevice(device, maxRetries);
}
/**
* Tries to find a connector able to connect to the device, then tries to connect.
*
* @param device the remote device to connect to
* @param connector the connector to use
* @return blaubot connection, if the connection was successful - false otherwise
*/
private IBlaubotConnection connectToBlaubotDevice(IBlaubotDevice device, IBlaubotConnector connector) {
if(Log.logDebugMessages()) {
Log.d(LOG_TAG, "Trying to connect to device " + device + " using the connector: " + connector);
}
try {
IBlaubotConnection conn = connector.connectToBlaubotDevice(device);
boolean result = conn != null;
if(Log.logDebugMessages()) {
if (result)
Log.d(LOG_TAG, "Connection was successful");
else
Log.d(LOG_TAG, "Connection failed.");
}
return conn;
} catch (IncompatibleBlaubotDeviceException e) {
if(Log.logErrorMessages()) {
Log.e(LOG_TAG, "Connector " + connector + " not compatible.");
}
}
if(Log.logErrorMessages()) {
Log.e(LOG_TAG, "Could not connect to remote device " + device + " with connector " + connector);
}
return null;
}
/**
* Goes through all connected devices and tries to find an IBlaubotDevice instance with this uniqueId.
* If the device was found, it is returned. If not, a generic BlaubotDevice instance is returned.
*
* @param uniqueDeviceId the unique device id to create a blaubot device for
* @return the corresponding {@link IBlaubotDevice} instance
*/
public IBlaubotDevice createBlaubotDeviceFromUniqueId(String uniqueDeviceId) {
if(uniqueDeviceId == null) {
throw new NullPointerException("uniqueDeviceId can't be null");
}
// check if we know this device and return or create a new instance and return
for(IBlaubotDevice device : getConnectedDevices()) {
if(device.getUniqueDeviceID().equals(uniqueDeviceId)) {
return device;
}
}
return new BlaubotDevice(uniqueDeviceId);
}
/**
* Sets the beacon store to be used to get the last beacon states and connectivity meta data
* @param beaconStore the store instance
*/
public void setBeaconStore(IBlaubotBeaconStore beaconStore) {
this.beaconStore = beaconStore;
}
}