/******************************************************************************* * Copyright (c) 2014 Bruno Medeiros and other Contributors. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Bruno Medeiros - initial API and implementation *******************************************************************************/ package dtool.genie; import static melnorme.utilbox.core.Assert.AssertNamespace.assertFail; import static melnorme.utilbox.core.Assert.AssertNamespace.assertTrue; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Semaphore; import dtool.genie.GenieServer.GenieConnectionHandler; import melnorme.utilbox.core.CommonException; /** * Helper for Genie Server, manages a basic TCP socket server. */ public abstract class AbstractSocketServer { protected static final int MAX_CONNECTIONS = 256; protected final ServerSocket serverSocket; protected final int portNumber; protected final CountDownLatch terminationLatch = new CountDownLatch(1); protected final Semaphore connectionsSemaphore = new Semaphore(MAX_CONNECTIONS); public AbstractSocketServer(int portNumber) throws CommonException { this.serverSocket = createServerSocket(portNumber); this.portNumber = serverSocket.getLocalPort(); } protected ServerSocket createServerSocket(int portNumber) throws CommonException { try { return new ServerSocket(portNumber); } catch (IOException ioe) { throw new CommonException("Error creating socket on port " + portNumber + ".", ioe); } } public int getServerPortNumber() { return portNumber; } public abstract void logMessage(String message); public abstract void logError(String message, Throwable throwable); public abstract void handleInternalError(Throwable throwable); public void runServer() { try { while(true) { Socket clientSocket = serverSocket.accept(); logMessage("New connection from: " + clientSocket.getRemoteSocketAddress().toString()); handleNewClientConnection(clientSocket); } } catch (SocketException se) { assertTrue(serverSocket.isClosed()); } catch (IOException ioe) { logError("Unexpected exception during socket accept: " , ioe); } finally { closeServerSocket(); logMessage("Server socket closed."); terminationLatch.countDown(); } } protected void closeServerSocket() { try { logMessage("Closing server socket."); serverSocket.close(); } catch (IOException e) { logError("Error trying to close socket: ", e); } } public int getActiveConnections() { return MAX_CONNECTIONS - connectionsSemaphore.availablePermits(); } protected void handleNewClientConnection(Socket clientSocket) { connectionsSemaphore.acquireUninterruptibly(); ConnectionHandlerRunnable genieConnectionHandler = createConnectionHandlerRunnable(clientSocket); createHandlerThread(genieConnectionHandler).start(); } protected Thread createHandlerThread(ConnectionHandlerRunnable genieConnectionHandler) { return new Thread(genieConnectionHandler); } protected abstract GenieConnectionHandler createConnectionHandlerRunnable(Socket clientSocket); public abstract class SafeRunnable implements Runnable { @Override public final void run() { try { safeRun(); } catch(RuntimeException e) { handleUncaughtException(e); } } protected abstract void safeRun(); protected abstract void handleUncaughtException(RuntimeException e); } public abstract class ConnectionHandlerRunnable extends SafeRunnable { protected final Socket clientSocket; public ConnectionHandlerRunnable(Socket clientSocket) { this.clientSocket = clientSocket; } @Override protected void handleUncaughtException(RuntimeException e) { handleInternalError(e); assertFail(); } @Override protected void safeRun() { handleConnection(); } public void handleConnection() { try { handleConnectionStream(); } catch (IOException e) { logError("IO error during connection handling: ", e); } finally { logMessage("Closing client connection : " + clientSocket.getRemoteSocketAddress().toString()); try { clientSocket.close(); } catch (IOException e) { logError("Error trying to close client socket: ", e); } connectionsSemaphore.release(); } } protected abstract void handleConnectionStream() throws IOException; } }