/** * * @author greg (at) myrobotlab.org * * This file is part of MyRobotLab (http://myrobotlab.org). * * MyRobotLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version (subject to the "Classpath" exception * as provided in the LICENSE.txt file that accompanied this code). * * MyRobotLab is distributed in the hope that it will be useful or fun, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * All libraries in thirdParty bundle are subject to their own license * requirements - please refer to http://myrobotlab.org/libraries for * details. * * Enjoy ! * * */ package org.myrobotlab.service; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.io.Reader; import java.io.StringReader; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.atmosphere.wasync.Client; import org.atmosphere.wasync.ClientFactory; import org.atmosphere.wasync.Decoder; import org.atmosphere.wasync.Encoder; import org.atmosphere.wasync.Event; import org.atmosphere.wasync.Function; import org.atmosphere.wasync.Request; import org.atmosphere.wasync.RequestBuilder; import org.myrobotlab.framework.Message; import org.myrobotlab.framework.Service; import org.myrobotlab.framework.ServiceType; import org.myrobotlab.framework.Status; import org.myrobotlab.logging.Level; import org.myrobotlab.logging.LoggerFactory; import org.myrobotlab.logging.Logging; import org.myrobotlab.logging.LoggingFactory; import org.myrobotlab.net.Connection; import org.myrobotlab.net.Scanner; import org.myrobotlab.net.TcpServer; import org.myrobotlab.net.UdpServer; import org.myrobotlab.service.interfaces.Gateway; import org.myrobotlab.service.interfaces.ServiceInterface; import org.slf4j.Logger; /** * * RemoteAdapter - Multi-node / distributed myrobotlab support. * * A RemoteAdapter allows other instances of MyRobotLab to connect. Services and * resources can be shared by 2 or more joined instances. The default * communication listener is a UDP server listening on all addresses on port * 6767. * * */ public class RemoteAdapter extends Service implements Gateway { // TODO - global address book of discovered connection private static final long serialVersionUID = 1L; public final static Logger log = LoggerFactory.getLogger(RemoteAdapter.class); public String lastProtocolKey; private String defaultPrefix = null; private HashMap<String, String> prefixMap = new HashMap<String, String>(); // FIXME - needs to be self contained !! - to have multiple servers // FIXME !!! - Server interface send - onMsg onConnect onDisconnect(nop for // udp) || websockets transient TcpServer tcpServer = null; transient UdpServer udpServer = null; private Integer udpPort; private Integer tcpPort; boolean isListening = false; boolean isScanning = false; /** * scanners to scan for other mrl instances TODO - multiple scanners for * parallel port/broadcast scanning */ transient Scanner scanner; /** * used as a data interface to all the non-serializable network objects - it * will report stats and states */ public HashMap<URI, Connection> connections = new HashMap<URI, Connection>(); public RemoteAdapter(String n) { super(n); defaultPrefix = String.format("%s.", n); tcpServer = new TcpServer(this); udpServer = new UdpServer(this); // addLocalTask(5 * 1000, "broadcastHeartbeat"); } @Override public void addConnectionListener(String name) { addListener("publishConnection", name, "onNewConnection"); } @Override // TODO refactor with boolean - lower level error(problem) to put into // framework /** * connects and sends register message to remote system * connection depends on url schema */ public void connect(String uri) throws URISyntaxException { log.info("{}.connecting {}", getName(), uri); Message msg = createMessage(null, "register", null); sendRemote(uri, msg); } @Override public HashMap<URI, Connection> getClients() { return connections; } /** * important initial communication function related to discovery a broadcast * goes out and replies must include details of communication so that a * viable connection can be created * * @param client */ // global access keys (all gateways) - or just this gateway ??? // if it's all then this could be a function of runtime which is probably // the best @Override public List<Connection> getConnections(URI clientKey) { ArrayList<Connection> conns = new ArrayList<Connection>(); try { // FIXME - dorky - probably fix with template method // FIXME - do "global" next ArrayList<ServiceInterface> services = Runtime.getServicesFromInterface(Gateway.class); // ArrayList<Gateway> gateways = new ArrayList<Gateway>(); // if GLOBAL for (int i = 0; i < services.size(); ++i) { // Gateway // gateways.add((Gateway) services.get(i)); } // else LOCAL // add (this) services connections List<String> addr = Runtime.getLocalAddresses(); for (int i = 0; i < addr.size(); ++i) { Connection tcpConn = new Connection(); // theoretically you could advertise udp too (and others) // tcpConn.protocolKey = new // URI(String.format("mrl://%s/tcp://%s:%d", getName(), // addr.get(i), getTcpPort())); tcpConn.protocolKey = new URI(String.format("tcp://%s:%d", addr.get(i), getTcpPort())); // we dont fill in our own name // FIXME FIXME FIXME - DO THE CORRECT WAY !!! // / tcpConn.protocolKey = new URI(String.format("tcp://%s:%d", // addr.get(i), getTcpPort())); // tcpKey.prefix = suggestion // tcpKey.prefix = prefix; tcpConn.platform = Runtime.getInstance().getPlatform(); tcpConn.prefix = Runtime.getInstance().getName();// calls // getPrefix // under // hood conns.add(tcpConn); } // ?? // tcpKey.uri = } catch (Exception e) { Logging.logError(e); } return conns; } @Override public String getPrefix(URI protocolKey) { if (defaultPrefix != null) { return defaultPrefix; } else { return "";// important - return "" not null } } public Integer getTcpPort() { return tcpPort; } public Integer getUdpPort() { return udpPort; } public boolean isListening() { return isListening; } @Override public boolean isReady() { return tcpServer.isReady(); } public boolean isScanning() { return isScanning; } public Connection onHeartbeat(Connection data) { return data; } /** * NOT USED - just left as an example of a consumer asynchronous return of * access key request * * @param keys * @return */ public Connection onNewConnection(Connection conn) { return conn; } // publishing point @Override public Connection publishConnect(Connection conn) { if (!connections.containsKey(conn.protocolKey)) { // uri will now become my uri connections.put(conn.protocolKey, conn); broadcastState(); } else { info("%d scanning no new connections", System.currentTimeMillis()); } return conn; } public void scan() { if (scanner != null) { stopScanning(); } scanner = new Scanner(this); scanner.start(); isScanning = true; } @Override public void sendRemote(String uri, Message msg) throws URISyntaxException { sendRemote(new URI(uri), msg); } /** * TODO - support * <pre> * SCHEMES * tcp tcps * upd dtls * ws wss * * SERIALIZATIONS * JSON * binary - native * Protobuff * </pre> */ @Override synchronized public void sendRemote(URI uri, Message msg) { log.info("sendRemote {}", uri); String scheme = uri.getScheme(); lastProtocolKey = uri.toString(); if ("tcp".equals(scheme)) { sendRemoteTCP(uri, msg); } else if ("udp".equals(scheme)) { sendRemoteUdp(uri, msg); } else { error(String.format("%s not supported", uri.toString())); return; } } public void sendRemoteTCP(URI uri, Message msg) { tcpServer.sendTcp(uri, msg); } public void sendRemoteUdp(URI uri, Message msg) { try { // FIXME - could use some optimization e.g. .reset() DatagramSocket s = new DatagramSocket(); ByteArrayOutputStream b_out = new ByteArrayOutputStream(); ObjectOutputStream o_out = new ObjectOutputStream(b_out); o_out.writeObject(msg); o_out.flush(); b_out.flush(); byte[] b = b_out.toByteArray(); InetAddress hostAddress = InetAddress.getByName(uri.getHost()); DatagramPacket dgram = new DatagramPacket(b, b.length, hostAddress, uri.getPort()); s.send(dgram); // dgram.se // TODO - send the damn packet??? // close the datagram to avoid resource leaks s.close(); } catch (Exception e) { Logging.logError(e); } } public String setDefaultPrefix(String prefix) { defaultPrefix = prefix; return prefix; } public void setPrefix(String source, String prefix) { prefixMap.put(source, prefix); } public void setTcpPort(Integer tcpPort) { this.tcpPort = tcpPort; } public void setUdpPort(Integer udpPort) { this.udpPort = udpPort; } public void startListening() { startListening(6767); } public void startListening(int port) { udpPort = tcpPort = port; udpServer.start(port); tcpServer.start(port); isListening = true; broadcastState(); } public void stopListening() { udpServer.stop(); tcpServer.stop(); isListening = false; broadcastState(); } public void stopScanning() { // scanner.isScanning = false; if (scanner != null) { scanner.stopScanning(); } isScanning = false; scanner = null; } @Override public void stopService() { super.stopService(); stopListening(); } public void startService() { super.startService(); } public void websocket(String url) throws IOException { Client client = ClientFactory.getDefault().newClient(); // "http://async-io.org" // http://localhost:8888/api/messages RequestBuilder request = client.newRequestBuilder().method(Request.METHOD.GET).uri(url).encoder(new Encoder<String, Reader>() { // Stream // the // request // body @Override public Reader encode(String s) { return new StringReader(s); } }).decoder(new Decoder<String, Reader>() { @Override public Reader decode(Event type, String s) { return new StringReader(s); } }).transport(Request.TRANSPORT.WEBSOCKET) // Try WebSocket .transport(Request.TRANSPORT.LONG_POLLING); // Fallback to // Long-Polling org.atmosphere.wasync.Socket socket = client.create(); socket.on(new Function<Reader>() { @Override public void on(Reader r) { // Read the response } }).on(new Function<IOException>() { @Override public void on(IOException arg0) { // TODO Auto-generated method stub } }).open(request.build()).fire("/api/").fire("bong"); } /** * This static method returns all the details of the class without it having * to be constructed. It has description, categories, dependencies, and peer * definitions. * * @return ServiceType - returns all the data * */ static public ServiceType getMetaData() { ServiceType meta = new ServiceType(RemoteAdapter.class.getCanonicalName()); meta.addDescription("allows remote communication between applets, or remote instances of myrobotlab"); meta.addCategory("connectivity", "network", "framework"); meta.addDependency("org.atmosphere.nettosphere", "2.3.0"); return meta; } public static void main(String[] args) { LoggingFactory.init(Level.INFO); try { RemoteAdapter remote = (RemoteAdapter) Runtime.start("remote", "RemoteAdapter"); // remote.connect("tcp://demo.myrobotlab.org:6767"); // remote.websocket("http://demo.myrobotlab.org:8888/api/messages"); Runtime.start("gui", "GUIService"); remote.startListening(); } catch (Exception e) { Logging.logError(e); } } @Override public String publishConnect() { // TODO Auto-generated method stub return null; } @Override public String publishDisconnect() { // TODO Auto-generated method stub return null; } @Override public Status publishError() { // TODO Auto-generated method stub return null; } }