package eu.hgross.blaubot.core.acceptor.discovery; import java.io.IOException; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import eu.hgross.blaubot.core.BlaubotDevice; import eu.hgross.blaubot.core.IBlaubotConnection; import eu.hgross.blaubot.core.IBlaubotDevice; import eu.hgross.blaubot.core.State; import eu.hgross.blaubot.core.acceptor.ConnectionMetaDataDTO; import eu.hgross.blaubot.core.statemachine.events.AbstractBlaubotDeviceDiscoveryEvent; import eu.hgross.blaubot.core.statemachine.states.IBlaubotState; import eu.hgross.blaubot.core.statemachine.states.IBlaubotSubordinatedState; import eu.hgross.blaubot.util.Log; /** * A task that utilizes a given (and connected) {@link IBlaubotConnection} to exchange the given {@link State} with the * remote endpoint. * The remote endpoint is the BlaubotBeaconService's BeaconConnectionHandler-Runnable. * This task does the whole beacon exchange heavy lifting and can be reused in other beacon implementations. * * If the exchange is successful, the {@link IBlaubotDiscoveryEventListener} is informed about the discovered state of * the remote endpoint. * * After the execution the connection is closed. * * * @author Henning Gross {@literal (mail.to@henning-gross.de)} * */ public class ExchangeStatesTask implements Runnable { private static final ExecutorService executorService = Executors.newCachedThreadPool(); private static final String LOG_TAG = "ExchangeStatesTask"; private static final boolean LOGGING_ACTIVE = true; protected volatile IBlaubotDiscoveryEventListener eventListener; protected IBlaubotState ourState; protected IBlaubotConnection connection; protected String kingDeviceUniqueId; protected List<ConnectionMetaDataDTO> ourAcceptorMetaDataList; private IBlaubotBeaconStore beaconStore; private IBlaubotDevice ownDevice; /** * Creates a new ExchangeStatesTask to be used with a IBlaubotBeaconInterface implementation. * * @param ownDevice our own device * @param connection the connection newly created connection to the remote beacon * @param ourState our own state that is going to be exchanged with the other side's (accepting) beacon * @param ourAcceptorMetaData the list of connection meta data objects gathered from OUR OWN acceptors * @param beaconStore the beacon store to get connection meta data for other devices from * @param eventListener the event listener of our beacon, which will be called with the appropriate discovery event by this task */ public ExchangeStatesTask(IBlaubotDevice ownDevice, IBlaubotConnection connection, IBlaubotState ourState, List<ConnectionMetaDataDTO> ourAcceptorMetaData, IBlaubotBeaconStore beaconStore, IBlaubotDiscoveryEventListener eventListener) { setUp(ownDevice, connection, ourState, eventListener, ourAcceptorMetaData, beaconStore); // assert if(this.ourState instanceof IBlaubotSubordinatedState) { this.kingDeviceUniqueId = ((IBlaubotSubordinatedState)ourState).getKingUniqueId(); } } private void setUp(IBlaubotDevice ownDevice, IBlaubotConnection connection, IBlaubotState ourState, IBlaubotDiscoveryEventListener eventListener, List<ConnectionMetaDataDTO> connectionMetaDataList, IBlaubotBeaconStore beaconStore) { this.ownDevice = ownDevice; this.ourState = ourState; this.eventListener = eventListener; this.connection = connection; this.ourAcceptorMetaDataList = connectionMetaDataList; this.beaconStore = beaconStore; } private State getState() { if(ourState == null) { if(Log.logWarningMessages()) { Log.w(LOG_TAG, "ourState for the StateExchange is null! Assuming we are in StoppedState."); } return State.Stopped; } return State.getStateByStatemachineClass(ourState.getClass()); } @Override public void run() { if(LOGGING_ACTIVE && Log.logDebugMessages()) { Log.d(LOG_TAG, "Connected to " + connection.getRemoteDevice() + "'s beacon."); Log.d(LOG_TAG, "Starting state exchange."); } // first receive the other side's state and acceptor meta data via the message BeaconMessage beaconMessage; beaconMessage = BeaconMessage.fromBlaubotConnection(connection); if (beaconMessage == null) { if(Log.logWarningMessages()) { Log.w(LOG_TAG, "Something went wrong reading the beacon message from the connection!"); } connection.disconnect(); return; } if(LOGGING_ACTIVE && Log.logDebugMessages()) { Log.d(LOG_TAG, "Successfully retrieved state from " + connection.getRemoteDevice() + "'s beacon: " + beaconMessage); } // now send our state and acceptor meta data if (ourState != null) { final String ownUniqueDeviceID = ownDevice.getUniqueDeviceID(); BeaconMessage ourStateMessage; if(ourState instanceof IBlaubotSubordinatedState) { // if we have a king, we communicate the king's uniqueId and acceptor data final List<ConnectionMetaDataDTO> kingConnectionMetaData = beaconStore.getLastKnownConnectionMetaData(kingDeviceUniqueId); if(kingConnectionMetaData == null) { throw new IllegalStateException("We don't have connection meta data for the king stored, but we are in a subordinate state"); } ourStateMessage = new BeaconMessage(ownUniqueDeviceID, getState(), ourAcceptorMetaDataList, kingDeviceUniqueId, kingConnectionMetaData); } else { ourStateMessage = new BeaconMessage(ownUniqueDeviceID, getState(), ourAcceptorMetaDataList); } if(LOGGING_ACTIVE && Log.logDebugMessages()) { Log.d(LOG_TAG, "Sending our state to " + connection.getRemoteDevice() + "'s beacon: " + ourStateMessage); } try { connection.write(ourStateMessage.toBytes()); } catch (IOException e) { if(Log.logErrorMessages()) { Log.e(LOG_TAG, "Failed to send our state to beacon of " + connection.getRemoteDevice(), e); } } } connection.disconnect(); if(LOGGING_ACTIVE && Log.logDebugMessages()) { Log.d(LOG_TAG, "Got message from " + connection.getRemoteDevice() + "'s beacon: " + beaconMessage); } // dispatch THEIR state and connection info to the listener handleDiscoveredBlaubotDevice(connection.getRemoteDevice(), beaconMessage.getCurrentState(), beaconMessage.getOwnConnectionMetaDataList()); // if they have a king, dispatch this informations as well. if (beaconMessage.getCurrentState().equals(State.Prince) || beaconMessage.getCurrentState().equals(State.Peasant)) { final String remoteDeviceKingUniqueDeviceId = beaconMessage.getKingDeviceUniqueId(); final List<ConnectionMetaDataDTO> remoteDeviceKingConnectionMetaDataList = beaconMessage.getKingsConnectionMetaDataList(); if (remoteDeviceKingUniqueDeviceId != null && !remoteDeviceKingUniqueDeviceId.isEmpty() && remoteDeviceKingConnectionMetaDataList != null) { handleDiscoveredBlaubotDevice(new BlaubotDevice(remoteDeviceKingUniqueDeviceId), State.King, remoteDeviceKingConnectionMetaDataList); } } } private void handleDiscoveredBlaubotDevice(final IBlaubotDevice device, final State state, final List<ConnectionMetaDataDTO> myConnectionMetaDataList) { if (eventListener != null) { executorService.execute(new Runnable() { @Override public void run() { AbstractBlaubotDeviceDiscoveryEvent event = state.createDiscoveryEventForDevice(device, myConnectionMetaDataList); if (eventListener != null) { eventListener.onDeviceDiscoveryEvent(event); } } }); } } }