package eu.hgross.blaubot.core; import java.lang.reflect.Constructor; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; import java.util.Enumeration; import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.CountDownLatch; import eu.hgross.blaubot.core.acceptor.ConnectionMetaDataDTO; import eu.hgross.blaubot.core.acceptor.IBlaubotConnectionAcceptor; import eu.hgross.blaubot.core.acceptor.discovery.BlaubotBeaconStore; import eu.hgross.blaubot.core.acceptor.discovery.IBlaubotBeacon; import eu.hgross.blaubot.core.connector.IBlaubotConnector; import eu.hgross.blaubot.ethernet.BlaubotBonjourBeacon; import eu.hgross.blaubot.ethernet.BlaubotEthernetAdapter; import eu.hgross.blaubot.ethernet.BlaubotEthernetFixedDeviceSetBeacon; import eu.hgross.blaubot.ethernet.BlaubotEthernetMulticastBeacon; import eu.hgross.blaubot.ethernet.FixedDeviceSetHelper; import eu.hgross.blaubot.geobeacon.GeoBeaconConstants; import eu.hgross.blaubot.geobeacon.GeoBeaconServer; import eu.hgross.blaubot.geobeacon.GeoLocationBeacon; import eu.hgross.blaubot.util.Log; /** * A factory that creates Blaubot, adapter, beacon, BlaubotServer and BlaubotServerConnector instances. * * @author Henning Gross {@literal (mail.to@henning-gross.de)} */ public class BlaubotFactory { private static final String LOG_TAG = "BlaubotFactory"; abstract class CreateRunnable implements Runnable { public Blaubot mBlaubot; public Exception mFailException; } /** * Creates a ethernet blaubot instance with the default ports 17171, 17172, 17173 * and does some dirty stuff to get around android's no network on main thread * policy. * * @param appUUID the app's uuid * @return the blaubot instance */ public static Blaubot createEthernetBlaubot(final UUID appUUID) { final CountDownLatch cdl = new CountDownLatch(1); CreateRunnable cr = new BlaubotFactory().new CreateRunnable() { @Override public void run() { InetAddress inetAddr = getLocalIpAddress(); if (inetAddr == null) { mFailException = new RuntimeException("Failed to get local ip address. Check your permissions and connectivity."); cdl.countDown(); return; } try { mBlaubot = createEthernetBlaubot(appUUID, 17171, 17172, 17173, inetAddr); } catch (Exception e) { mFailException = e; } finally { cdl.countDown(); } } }; new Thread(cr).start(); try { cdl.await(); if (cr.mFailException != null) { throw new RuntimeException(cr.mFailException); } } catch (InterruptedException e) { throw new RuntimeException(e); } return cr.mBlaubot; } /** * Creates a blaubot instance using the bonjour beacon. * Note: requires broadcast capable network * * @param appUUID the app's uuid * @param acceptorPort the port of the connector's accepting socket * @param beaconPort the port for the beacon to accept connections on * @param ownInetAddress the own {@link InetAddress} of the network to act on * @return the blaubot instance */ public static Blaubot createEthernetBlaubotWithBonjourBeacon(UUID appUUID, int acceptorPort, int beaconPort, InetAddress ownInetAddress) { if (ownInetAddress == null || appUUID == null) { throw new NullPointerException("InetAddress or appUUID was null."); } IBlaubotDevice ownDevice = new BlaubotDevice(UUID.randomUUID().toString()); BlaubotEthernetAdapter ethernetAdapter = new BlaubotEthernetAdapter(ownDevice, acceptorPort, ownInetAddress); BlaubotBonjourBeacon blaubotBonjourBeacon = new BlaubotBonjourBeacon(ownInetAddress, beaconPort); return createBlaubot(appUUID, ownDevice, ethernetAdapter, blaubotBonjourBeacon); } /** * Creates a blaubot instance using an existing network and the tcp socket adapter with the multicast beacon. * Note: requires broadcast capable network * * @param appUUID the app's uuid * @param acceptorPort the port of the connector's accepting socket * @param beaconPort the port of the beacon's accepting socket * @param beaconBroadcastPort the broadcast port. Has to be the same for all instances. * @param ownInetAddress the own {@link InetAddress} of the network to act on * @return the blaubot instance */ public static Blaubot createEthernetBlaubot(UUID appUUID, int acceptorPort, int beaconPort, int beaconBroadcastPort, InetAddress ownInetAddress) { if (ownInetAddress == null || appUUID == null) { throw new NullPointerException("InetAddress or appUUID was null."); } IBlaubotDevice ownDevice = new BlaubotDevice(UUID.randomUUID().toString()); BlaubotEthernetAdapter ethernetAdapter = new BlaubotEthernetAdapter(ownDevice, acceptorPort, ownInetAddress); BlaubotEthernetMulticastBeacon multicastBeacon = new BlaubotEthernetMulticastBeacon(beaconPort, beaconBroadcastPort); return createBlaubot(appUUID, ownDevice, ethernetAdapter, multicastBeacon); } /** * Creates a blaubot instance using an existing network and the websocket adapter with the multicast beacon. * Note: requires broadcast capable network * * @param appUUID the app's uuid * @param websocketPort the port for the websocket acceptor to listen on * @param beaconPort the port of the beacon's accepting socket * @param beaconBroadcastPort the broadcast port. Has to be the same for all instances. * @param ownInetAddress the ip of this device to bind the websocket to * @return the blaubot instance * @throws ClassNotFoundException if the blaubot-websocket dependency could not be resolved */ public static Blaubot createWebSocketBlaubotWithMulticastBeacon(UUID appUUID, int websocketPort, int beaconPort, int beaconBroadcastPort, InetAddress ownInetAddress) throws ClassNotFoundException { if (appUUID == null) { throw new NullPointerException("appUUID was null."); } IBlaubotDevice ownDevice = new BlaubotDevice(UUID.randomUUID().toString()); IBlaubotAdapter websocketAdapter = createBlaubotWebsocketAdapter(ownDevice, ownInetAddress.getHostAddress(), websocketPort); BlaubotEthernetMulticastBeacon multicastBeacon = new BlaubotEthernetMulticastBeacon(beaconPort, beaconBroadcastPort); return createBlaubot(appUUID, ownDevice, websocketAdapter, multicastBeacon); } /** * Creates a blaubot instance using an existing network and the websocket adapter with the bonjour beacon. * Note: requires broadcast capable network * * @param appUUID the app's uuid * @param websocketPort the port for the websocket acceptor to listen on * @param beaconPort the port of the beacon's accepting socket * @param ownInetAddress the own {@link InetAddress} of the network to act on * @return the blaubot instance * @throws ClassNotFoundException if the blaubot-websocket dependency could not be resolved */ public static Blaubot createWebSocketBlaubotWithBonjourBeacon(UUID appUUID, int websocketPort, int beaconPort, InetAddress ownInetAddress) throws ClassNotFoundException { if (appUUID == null) { throw new NullPointerException("appUUID was null."); } IBlaubotDevice ownDevice = new BlaubotDevice(UUID.randomUUID().toString()); IBlaubotAdapter websocketAdapter = createBlaubotWebsocketAdapter(ownDevice, ownInetAddress.getHostAddress(), websocketPort); IBlaubotBeacon bonjourBeacon = new BlaubotBonjourBeacon(ownInetAddress, beaconPort); return createBlaubot(appUUID, ownDevice, websocketAdapter, bonjourBeacon); } /** * Creates a blaubot instance using the ethernet adapter. * This instance will only operate on the given fixedDevicesSet and does not do any discovery. * * @param appUUID the app's uuid * @param ownDevice the own device unique id. Note that it's uniqueId has to be present in the fixed device set * @param acceptorPort the port of the connector's accepting socket * @param beaconPort the port of the beacon's accepting socket * @param ownInetAddress the own {@link InetAddress} of the network to act on * @param fixedDevicesSet a set of fixed devices to connect to (represented as uniqueIdStrings) * @return the blaubot instance * @throws UnknownHostException if inetAddress was invalid */ public static Blaubot createEthernetBlaubotWithFixedDevicesBeacon(UUID appUUID, IBlaubotDevice ownDevice, int acceptorPort, int beaconPort, InetAddress ownInetAddress, Set<String> fixedDevicesSet) throws UnknownHostException { if (ownInetAddress == null || appUUID == null) throw new NullPointerException("InetAddress or appUUID was null."); BlaubotEthernetAdapter ethernetAdapter = new BlaubotEthernetAdapter(ownDevice, acceptorPort, ownInetAddress); final Set<BlaubotEthernetFixedDeviceSetBeacon.FixedDeviceSetBlaubotDevice> fixedDeviceSetInstances = FixedDeviceSetHelper.createFixedDeviceSetInstances(fixedDevicesSet); final BlaubotEthernetFixedDeviceSetBeacon fixedDeviceSetBeacon = new BlaubotEthernetFixedDeviceSetBeacon(fixedDeviceSetInstances, beaconPort); return createBlaubot(appUUID, ownDevice, ethernetAdapter, fixedDeviceSetBeacon); } /** * Creates a Blaubot instance from a given adapter and multiple beacons. * * @param appUuid the app's unique uuid * @param ownDevice the own device containing this device's unique identifier * @param adapter the adapter to be used (Sockets, WebSockets, Bluetooth, ...) * @param beacons the becaons to be used (Bluetooth, NFC, Multicast, Bonjour, ...) * @return the blaubot instance */ public static Blaubot createBlaubot(UUID appUuid, IBlaubotDevice ownDevice, IBlaubotAdapter adapter, IBlaubotBeacon... beacons) { BlaubotUUIDSet uuidSet = new BlaubotUUIDSet(appUuid); List<IBlaubotAdapter> adapters = new ArrayList<>(); adapters.add(adapter); Blaubot blaubot = new Blaubot(ownDevice, uuidSet, adapters, Arrays.asList(beacons)); return blaubot; } /** * Creates a Blaubot instance from a given adapter and multiple beacons using a random unique device id for the ownDevice. * * @param appUuid the app's unique uuid * @param adapter the adapter to be used (Sockets, WebSockets, Bluetooth, ...) * @param beacons the becaons to be used (Bluetooth, NFC, Multicast, Bonjour, ...) * @return the blaubot instance */ public static Blaubot createBlaubot(UUID appUuid, IBlaubotAdapter adapter, IBlaubotBeacon... beacons) { return createBlaubot(appUuid, new BlaubotDevice(), adapter, beacons); } /** * TODO: move to another location * * @return the most likely SiteLocal inetAddress or null, if everything went wrong */ public static InetAddress getLocalIpAddress() { try { if (Log.logDebugMessages()) { Log.d(LOG_TAG, "Searching interface to use."); } for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) { NetworkInterface intf = en.nextElement(); if (intf.isLoopback() || !intf.isUp()) { continue; } for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) { InetAddress inetAddress = enumIpAddr.nextElement(); if (!inetAddress.isLoopbackAddress() && !inetAddress.isAnyLocalAddress() && inetAddress.isSiteLocalAddress()) { if (Log.logDebugMessages()) { Log.d(LOG_TAG, "Using interface " + intf + " (IP: " + inetAddress + ")"); } return inetAddress; } } } } catch (SocketException ex) { Log.e(LOG_TAG, ex.toString()); } return null; } /** * Sets up a default {@link Blaubot} instance using a Jsr82 bluetooth stack for connections and a multicast beacon (ip network) for discovery. * * Note that you have to include a Jsr82 compliant implementation into your classpath (i.e. bluecove (windows, mac); bluecove-gpl (linux); bluez) * * @param appUUID the app's unique uuid * @return blaubot instance * @throws RuntimeException if no Jsr82 implementation is available at runtime or cannot access the bluetooth stack * @throws ClassNotFoundException if the blaubot-jsr82 jar is not available from the classpath */ public static Blaubot createJsr82BluetoothBlaubot(UUID appUUID) throws RuntimeException, ClassNotFoundException { IBlaubotDevice ownDevice = new BlaubotDevice(); BlaubotUUIDSet uuidSet = new BlaubotUUIDSet(appUUID); IBlaubotAdapter adapter = createJsr82Adapter(uuidSet, ownDevice); BlaubotEthernetMulticastBeacon multicastBeacon = new BlaubotEthernetMulticastBeacon(17173, 17175); return createBlaubot(appUUID, ownDevice, adapter, multicastBeacon); } /** * Creates a JSR82 adapter that can be used on Windows, Mac and Linux, if a jsr82 implementation is available. * * @param uuidSet uuid set (created from the app uid) * @param ownDevice the own device * @return the adapter instance * @throws ClassNotFoundException if the blaubot-jsr82 jar is not in the classpath */ public static final IBlaubotAdapter createJsr82Adapter(BlaubotUUIDSet uuidSet, IBlaubotDevice ownDevice) throws ClassNotFoundException { // create adapter // IBlaubotAdapter jsr82BluetoothAdapter = new BlaubotJsr82BluetoothAdapter(uuidSet, ownDevice); final Class<?> adapterClass = Class.forName("eu.hgross.blaubot.bluetooth.BlaubotJsr82BluetoothAdapter"); IBlaubotAdapter adapter = null; try { final Constructor<?> constructor = adapterClass.getConstructor(BlaubotUUIDSet.class, IBlaubotDevice.class); adapter = (IBlaubotAdapter) constructor.newInstance(uuidSet, ownDevice); return adapter; } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } } /** * Creates a blaubot server using the websocket acceptor * * @param ownDevice the own device * @param host the interfaces to bind the websockets to. * @param port the port to open the websocket server on * @return the server instance * @throws ClassNotFoundException if the websocket jar is not in the classpath */ public static BlaubotServer createBlaubotWebsocketServer(IBlaubotDevice ownDevice, String host, int port) throws ClassNotFoundException { // we need an acceptor, so we create an adapter and use it's acceptor IBlaubotAdapter adapter = createBlaubotWebsocketAdapter(ownDevice, host, port); // create and start the Blaubot server BlaubotServer server = new BlaubotServer(ownDevice, adapter.getConnectionAcceptor()); return server; } /** * Create a websocket adapter via reflection * * @param ownDevice the own device (and uniqueDeviceId) * @param host the own host address (interfaces to bind to) * @param port the port for the acceptor to accept connections on * @return adapter instance * @throws ClassNotFoundException if the websocket jar is not in the classpath */ public static final IBlaubotAdapter createBlaubotWebsocketAdapter(IBlaubotDevice ownDevice, String host, int port) throws ClassNotFoundException { // IBlaubotAdapter adapter = new BlaubotWebsocketAdapter(ownDevice, host, port); IBlaubotAdapter adapter = null; final Class<?> adapterClass = Class.forName("eu.hgross.blaubot.websocket.BlaubotWebsocketAdapter"); try { final Constructor<?> constructor = adapterClass.getConstructor(IBlaubotDevice.class, String.class, Integer.TYPE); adapter = (IBlaubotAdapter) constructor.newInstance(ownDevice, host, port); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } return adapter; } /** * This shortcut method creates a websocket meta data object that can be used to provide endpoint * information when using a websocket based BlaubotServer or GeoBeaconServer. * * @param host the hostname to connect to * @param path the path to connect the websocket with (with leading slash) * @param port the port * @return the connection metadata * @throws ClassNotFoundException if the websocket jar is not in the classpath */ public static final ConnectionMetaDataDTO createWebSocketMetaDataDTO(String host, String path, int port) throws ClassNotFoundException { ConnectionMetaDataDTO data = null; final Class<?> adapterClass = Class.forName("eu.hgross.blaubot.websocket.WebsocketConnectionMetaDataDTO"); try { final Constructor<?> constructor = adapterClass.getConstructor(String.class, String.class, Integer.TYPE); data = (ConnectionMetaDataDTO) constructor.newInstance(host, path, port); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException(e); } return data; } /** * Creates a blaubot server using websockets, the default port 8080 on all interfaces (0.0.0.0). * * @param ownDevice the own device * @return the blaubot server * @throws ClassNotFoundException if the websocket jar is not in the classpath */ public static BlaubotServer createBlaubotWebsocketServer(IBlaubotDevice ownDevice) throws ClassNotFoundException { return createBlaubotWebsocketServer(ownDevice, "0.0.0.0", 8080); } /** * Creates a blaubot server using websockets on all interfaces (0.0.0.0) * using the given httpPort. * * @param ownDevice the own device * @param httpPort the httpPort for the http server to server requests * @return the blaubot server * @throws ClassNotFoundException if the websocket jar is not in the classpath */ public static BlaubotServer createBlaubotWebsocketServer(IBlaubotDevice ownDevice, int httpPort) throws ClassNotFoundException { return createBlaubotWebsocketServer(ownDevice, "0.0.0.0", httpPort); } /** * Creates a WebSocket ServerConnector. * * @param host the server's hostname or IPv4-Address * @param port the server's port * @param websocketPath the uri path used by the websocket acceptor * @param ownDevice the OWN device of the connecting blaubot instance * @param serverDeviceUniqueDeviceId the server's unique device id. * @return the server connector ready to be attached to the blaubot instance * @throws ClassNotFoundException if the websocket jar is not in the classpath */ public static BlaubotServerConnector createWebSocketServerConnector(String host, int port, String websocketPath, IBlaubotDevice ownDevice, String serverDeviceUniqueDeviceId) throws ClassNotFoundException { ConnectionMetaDataDTO connectionMetaData = createWebSocketMetaDataDTO(host, websocketPath, port); // supply connectors by creating adapters final IBlaubotAdapter websocketAdapter = createBlaubotWebsocketAdapter(ownDevice, "0.0.0.0", port); 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(serverDeviceUniqueDeviceId, connectionMetaDataList); // create the server connector and attach it to blaubot BlaubotServerConnector bsc = new BlaubotServerConnector(serverDeviceUniqueDeviceId, beaconStore, connectors); return bsc; } /** * Creates a GeoLocationBeacon using WebSockets. * Note: You have to inject a {@link eu.hgross.blaubot.geobeacon.GeoData} object to set the the * current location. * * @param ownDevice the ownDevice (Blaubot-Instance device) * @param beaconServerHost the hostname or ip of the beacon server * @param beaconServerPort the port on which the beacon server listens * @return the geolocation beacon * @throws ClassNotFoundException if blaubot-websockets is not in the classpath */ public static GeoLocationBeacon createWebSocketGeoLocationBeacon(IBlaubotDevice ownDevice, String beaconServerHost, int beaconServerPort) throws ClassNotFoundException { IBlaubotAdapter websocketAdapter = createBlaubotWebsocketAdapter(ownDevice, "0.0.0.0", beaconServerPort); ConnectionMetaDataDTO webSocketMetaDataDTO = createWebSocketMetaDataDTO(beaconServerHost, "/blaubot", beaconServerPort); BlaubotBeaconStore beaconStore = new BlaubotBeaconStore(); beaconStore.putConnectionMetaData(GeoBeaconConstants.GEO_BEACON_SERVER_UNIQUE_DEVICE_ID, webSocketMetaDataDTO); GeoLocationBeacon geoLocationBeacon = new GeoLocationBeacon(beaconStore) { }; return geoLocationBeacon; } /** * Creates a standalone GeoBeaconServer which uses WebSockets as an endpoint for a GeoLocationBeacon. * * @param webSocketAcceptPort the websocket port to serve on * @param geoRadius the geo radius in km used to inform nearby beacons * @return the geo beacon server * @throws ClassNotFoundException if blaubot-websockets is not in the classpath */ public static GeoBeaconServer createWebSocketGeoBeaconServer(int webSocketAcceptPort, double geoRadius) throws ClassNotFoundException { final IBlaubotAdapter blaubotWebsocketAdapter = createBlaubotWebsocketAdapter(new BlaubotDevice(GeoBeaconConstants.GEO_BEACON_SERVER_UNIQUE_DEVICE_ID), "0.0.0.0", webSocketAcceptPort); final IBlaubotConnectionAcceptor beaconServerAcceptor = blaubotWebsocketAdapter.getConnectionAcceptor(); GeoBeaconServer geoBeaconServer = new GeoBeaconServer(geoRadius, beaconServerAcceptor); return geoBeaconServer; } }