package io.nucleo.net; import io.nucleo.net.proto.ContainerMessage; import io.nucleo.net.proto.ControlMessage; import io.nucleo.net.proto.Message; import io.nucleo.net.proto.exceptions.ConnectionException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.*; import java.net.Socket; import java.net.SocketTimeoutException; import java.util.Collection; import java.util.LinkedList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; public abstract class Connection implements Closeable { private static final Logger log = LoggerFactory.getLogger(Connection.class); private final Socket socket; private final ObjectOutputStream out; private final ObjectInputStream in; private final LinkedList<ConnectionListener> connectionListeners; private final String peer; private boolean running; private final AtomicBoolean available; private final AtomicBoolean listening; private final ExecutorService executorService; private final InputStreamListener inputStreamListener; private final AtomicBoolean heartBeating; public Connection(String peer, Socket socket) throws IOException { // LookAheadObjectInputStream not needed here as the class it not used in Bitsquare (used to test the library) this(peer, socket, Node.prepareOOSForSocket(socket), new ObjectInputStream(socket.getInputStream())); } Connection(String peer, Socket socket, ObjectOutputStream out, ObjectInputStream in) { log.debug("Initiating new connection"); this.available = new AtomicBoolean(false); this.peer = peer; this.socket = socket; this.in = in; this.out = out; running = true; listening = new AtomicBoolean(false); heartBeating = new AtomicBoolean(false); this.connectionListeners = new LinkedList<>(); this.inputStreamListener = new InputStreamListener(); executorService = Executors.newCachedThreadPool(); } public abstract boolean isIncoming(); public void addMessageListener(ConnectionListener listener) { synchronized (connectionListeners) { connectionListeners.add(listener); } } protected void setConnectionListeners(Collection<ConnectionListener> listeners) { synchronized (listeners) { this.connectionListeners.clear(); this.connectionListeners.addAll(listeners); } } public void removeMessageListener(ConnectionListener listener) { synchronized (connectionListeners) { connectionListeners.remove(listener); } } void sendMsg(Message msg) throws IOException { out.writeObject(msg); out.flush(); } public void sendMessage(ContainerMessage msg) throws IOException { if (!available.get()) throw new IOException("Connection is not yet available!"); sendMsg(msg); } protected void onMessage(Message msg) throws IOException { log.debug("RXD: " + msg.toString()); if (msg instanceof ContainerMessage) { synchronized (connectionListeners) { for (ConnectionListener l : connectionListeners) l.onMessage(this, (ContainerMessage) msg); } } else { if (msg instanceof ControlMessage) { switch ((ControlMessage) msg) { case DISCONNECT: close(false, PredefinedDisconnectReason.createReason(PredefinedDisconnectReason.CONNECTION_CLOSED, true)); break; case AVAILABLE: startHeartbeat(); onReady(); break; default: break; } } } } protected void onReady() { if (!available.getAndSet(true)) { synchronized (connectionListeners) { for (ConnectionListener l : connectionListeners) { l.onReady(this); } } } } protected abstract void onDisconnect(); private void onDisconn(DisconnectReason reason) { onDisconnect(); synchronized (connectionListeners) { for (ConnectionListener l : connectionListeners) { l.onDisconnect(this, reason); } } } private void onTimeout() { try { close(false, PredefinedDisconnectReason.TIMEOUT); } catch (IOException e1) { } } protected void onError(Exception e) { synchronized (connectionListeners) { for (ConnectionListener l : connectionListeners) { l.onError(this, new ConnectionException(e)); } } } public void close() throws IOException { close(true, PredefinedDisconnectReason.createReason(PredefinedDisconnectReason.CONNECTION_CLOSED, false)); } private void close(boolean graceful, DisconnectReason reason) throws IOException { running = false; onDisconn(reason); if (graceful) { try { sendMsg(ControlMessage.DISCONNECT); } catch (Exception e) { onError(e); } } out.close(); in.close(); socket.close(); } public String getPeer() { return peer; } void startHeartbeat() { if (!heartBeating.getAndSet(true)) { log.debug("Starting Heartbeat"); executorService.submit(new Runnable() { public void run() { try { Thread.sleep(30000); while (running) { try { log.debug("TX Heartbeat"); sendMsg(ControlMessage.HEARTBEAT); Thread.sleep(30000); } catch (IOException e) { e.printStackTrace(); } } } catch (InterruptedException e) { } } }); } } public void listen() throws ConnectionException { if (listening.getAndSet(true)) throw new ConnectionException("Already Listening!"); executorService.submit(inputStreamListener); } private class InputStreamListener implements Runnable { @Override public void run() { while (running) { try { Message msg = (Message) in.readObject(); onMessage(msg); } catch (ClassNotFoundException | IOException e) { if (e instanceof SocketTimeoutException) { onTimeout(); } else { if (running) { onError(new ConnectionException(e)); if (e instanceof EOFException) { try { close(false, PredefinedDisconnectReason.RESET); } catch (IOException e1) { e1.printStackTrace(); } } } } } } } } }