/* * Copyright 2013 Sylvain LAURENT * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package ch.sla.jdbcperflogger.console.net; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketTimeoutException; import java.util.Set; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ch.sla.jdbcperflogger.console.db.LogRepositoryUpdate; public class ServerLogReceiver extends AbstractLogReceiver { final static Logger LOGGER = LoggerFactory.getLogger(ServerLogReceiver.class); private final Set<AbstractLogReceiver> childReceivers = new CopyOnWriteArraySet<>(); private final LogRepositoryUpdate logRepository; private int listenPort; @Nullable private volatile ServerSocket serverSocket; private final CountDownLatch serverStartedLatch = new CountDownLatch(1); public ServerLogReceiver(final int listenPort, final LogRepositoryUpdate logRepository) { this.listenPort = listenPort; this.logRepository = logRepository; } @Override public void dispose() { super.dispose(); try { final ServerSocket serverSocketLocalVar = serverSocket; if (serverSocketLocalVar != null) { serverSocketLocalVar.close(); } } catch (final IOException e) { LOGGER.error("error while closing socket", e); } try { this.join(); } catch (final InterruptedException e) { // ignore } } // visible for testing protected int getListenPort() { return listenPort; } @Override public int getConnectionsCount() { int cnt = 0; // it's thread-safe to iterate over childReceivers because it's a CopyOnWriteArraySet for (final AbstractLogReceiver receiver : childReceivers) { cnt += receiver.getConnectionsCount(); } return cnt; } @Override public void run() { try (ServerSocket serverSocketLocalVar = new ServerSocket(listenPort)) { // if listenPort is 0, a port has been chosen by the OS listenPort = serverSocketLocalVar.getLocalPort(); serverSocketLocalVar.setSoTimeout((int) TimeUnit.MINUTES.toMillis(5)); serverSocket = serverSocketLocalVar; this.setName("ServerLogReceiver " + listenPort); try (LogPersister logPersister = new LogPersister(logRepository)) { logPersister.start(); // signal threads that might be waiting for the server to be ready serverStartedLatch.countDown(); while (!disposed) { try { LOGGER.debug("Waiting for client connections on " + serverSocketLocalVar); @NonNull final Socket socket = serverSocketLocalVar.accept(); LOGGER.debug("Got client connection from " + socket); final AbstractLogReceiver logReceiver = new AbstractLogReceiver() { @Override public void run() { try { handleConnection(socket, logPersister); } catch (final IOException e) { LOGGER.error("error while receiving logs from " + socket.getRemoteSocketAddress(), e); } finally { childReceivers.remove(this); } } @Override public boolean isServerMode() { return true; } }; logReceiver.setName("LogReceiver " + socket.getRemoteSocketAddress()); if (isPaused()) { logReceiver.pauseReceivingLogs(); } childReceivers.add(logReceiver); logReceiver.start(); } catch (final SocketTimeoutException e) { LOGGER.debug("timeout while accepting socket", e); } catch (final IOException e) { if (!disposed) { LOGGER.error("error while accepting socket", e); } else { LOGGER.debug("error while accepting socket, the server has been closed", e); } } } } finally { LOGGER.debug("Closing server socket " + serverSocketLocalVar); if (!serverSocketLocalVar.isClosed()) { try { serverSocketLocalVar.close(); } catch (final IOException e) { LOGGER.error("error while closing socket", e); } } } } catch (final IOException e) { lastConnectionError = e; throw new RuntimeException(e); } } @Override public boolean isServerMode() { return true; } @Override public void pauseReceivingLogs() { super.pauseReceivingLogs(); for (final AbstractLogReceiver child : childReceivers) { child.pauseReceivingLogs(); } } @Override public void resumeReceivingLogs() { super.resumeReceivingLogs(); for (final AbstractLogReceiver child : childReceivers) { child.resumeReceivingLogs(); } } // visible for testing void waitUntilServerIsReady() throws InterruptedException { serverStartedLatch.await(); } }