001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.classic.net;
015
016import java.io.IOException;
017import java.net.ServerSocket;
018import java.net.Socket;
019import java.util.ArrayList;
020import java.util.List;
021import java.util.concurrent.CountDownLatch;
022
023import javax.net.ServerSocketFactory;
024
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027
028import ch.qos.logback.classic.LoggerContext;
029import ch.qos.logback.classic.joran.JoranConfigurator;
030import ch.qos.logback.core.joran.spi.JoranException;
031
032/**
033 * A simple {@link SocketNode} based server.
034 * 
035 * <pre>
036 *      &lt;b&gt;Usage:&lt;/b&gt; java ch.qos.logback.classic.net.SimpleSocketServer port configFile
037 * </pre>
038 * 
039 * where <em>port</em> is a port number where the server listens and
040 * <em>configFile</em> is an XML configuration file fed to
041 * {@link JoranConfigurator}.
042 * 
043 * </pre>
044 * 
045 * @author Ceki G&uuml;lc&uuml;
046 * @author S&eacute;bastien Pennec
047 * 
048 * @since 0.8.4
049 */
050public class SimpleSocketServer extends Thread {
051
052    Logger logger = LoggerFactory.getLogger(SimpleSocketServer.class);
053
054    private final int port;
055    private final LoggerContext lc;
056    private boolean closed = false;
057    private ServerSocket serverSocket;
058    private List<SocketNode> socketNodeList = new ArrayList<SocketNode>();
059
060    // used for testing purposes
061    private CountDownLatch latch;
062
063    public static void main(String argv[]) throws Exception {
064        doMain(SimpleSocketServer.class, argv);
065    }
066
067    protected static void doMain(Class<? extends SimpleSocketServer> serverClass, String argv[]) throws Exception {
068        int port = -1;
069        if (argv.length == 2) {
070            port = parsePortNumber(argv[0]);
071        } else {
072            usage("Wrong number of arguments.");
073        }
074
075        String configFile = argv[1];
076        LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
077        configureLC(lc, configFile);
078
079        SimpleSocketServer sss = new SimpleSocketServer(lc, port);
080        sss.start();
081    }
082
083    public SimpleSocketServer(LoggerContext lc, int port) {
084        this.lc = lc;
085        this.port = port;
086    }
087
088    public void run() {
089
090        final String oldThreadName = Thread.currentThread().getName();
091
092        try {
093
094            final String newThreadName = getServerThreadName();
095            Thread.currentThread().setName(newThreadName);
096
097            logger.info("Listening on port " + port);
098            serverSocket = getServerSocketFactory().createServerSocket(port);
099            while (!closed) {
100                logger.info("Waiting to accept a new client.");
101                signalAlmostReadiness();
102                Socket socket = serverSocket.accept();
103                logger.info("Connected to client at " + socket.getInetAddress());
104                logger.info("Starting new socket node.");
105                SocketNode newSocketNode = new SocketNode(this, socket, lc);
106                synchronized (socketNodeList) {
107                    socketNodeList.add(newSocketNode);
108                }
109                final String clientThreadName = getClientThreadName(socket);
110                new Thread(newSocketNode, clientThreadName).start();
111            }
112        } catch (Exception e) {
113            if (closed) {
114                logger.info("Exception in run method for a closed server. This is normal.");
115            } else {
116                logger.error("Unexpected failure in run method", e);
117            }
118        }
119
120        finally {
121            Thread.currentThread().setName(oldThreadName);
122        }
123    }
124
125    /**
126     * Returns the name given to the server thread.
127     */
128    protected String getServerThreadName() {
129        return String.format("Logback %s (port %d)", getClass().getSimpleName(), port);
130    }
131
132    /**
133     * Returns a name to identify each client thread.
134     */
135    protected String getClientThreadName(Socket socket) {
136        return String.format("Logback SocketNode (client: %s)", socket.getRemoteSocketAddress());
137    }
138
139    /**
140     * Gets the platform default {@link ServerSocketFactory}.
141     * <p>
142     * Subclasses may override to provide a custom server socket factory.
143     */
144    protected ServerSocketFactory getServerSocketFactory() {
145        return ServerSocketFactory.getDefault();
146    }
147
148    /**
149     * Signal another thread that we have established a connection This is useful
150     * for testing purposes.
151     */
152    void signalAlmostReadiness() {
153        if (latch != null && latch.getCount() != 0) {
154            // System.out.println("signalAlmostReadiness() with latch "+latch);
155            latch.countDown();
156        }
157    }
158
159    /**
160     * Used for testing purposes
161     * 
162     * @param latch
163     */
164    void setLatch(CountDownLatch latch) {
165        this.latch = latch;
166    }
167
168    /**
169     * Used for testing purposes
170     */
171    public CountDownLatch getLatch() {
172        return latch;
173    }
174
175    public boolean isClosed() {
176        return closed;
177    }
178
179    public void close() {
180        closed = true;
181        if (serverSocket != null) {
182            try {
183                serverSocket.close();
184            } catch (IOException e) {
185                logger.error("Failed to close serverSocket", e);
186            } finally {
187                serverSocket = null;
188            }
189        }
190
191        logger.info("closing this server");
192        synchronized (socketNodeList) {
193            for (SocketNode sn : socketNodeList) {
194                sn.close();
195            }
196        }
197        if (socketNodeList.size() != 0) {
198            logger.warn("Was expecting a 0-sized socketNodeList after server shutdown");
199        }
200
201    }
202
203    public void socketNodeClosing(SocketNode sn) {
204        logger.debug("Removing {}", sn);
205
206        // don't allow simultaneous access to the socketNodeList
207        // (e.g. removal whole iterating on the list causes
208        // java.util.ConcurrentModificationException)
209        synchronized (socketNodeList) {
210            socketNodeList.remove(sn);
211        }
212    }
213
214    static void usage(String msg) {
215        System.err.println(msg);
216        System.err.println("Usage: java " + SimpleSocketServer.class.getName() + " port configFile");
217        System.exit(1);
218    }
219
220    static int parsePortNumber(String portStr) {
221        try {
222            return Integer.parseInt(portStr);
223        } catch (java.lang.NumberFormatException e) {
224            e.printStackTrace();
225            usage("Could not interpret port number [" + portStr + "].");
226            // we won't get here
227            return -1;
228        }
229    }
230
231    static public void configureLC(LoggerContext lc, String configFile) throws JoranException {
232        JoranConfigurator configurator = new JoranConfigurator();
233        lc.reset();
234        configurator.setContext(lc);
235        configurator.doConfigure(configFile);
236    }
237}