View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.classic.net;
15  
16  import java.io.IOException;
17  import java.net.ServerSocket;
18  import java.net.Socket;
19  import java.util.ArrayList;
20  import java.util.List;
21  import java.util.concurrent.CountDownLatch;
22  
23  import javax.net.ServerSocketFactory;
24  
25  import org.slf4j.Logger;
26  import org.slf4j.LoggerFactory;
27  
28  import ch.qos.logback.classic.LoggerContext;
29  import ch.qos.logback.classic.joran.JoranConfigurator;
30  import ch.qos.logback.core.joran.spi.JoranException;
31  
32  /**
33   * A simple {@link SocketNode} based server.
34   * 
35   * <pre>
36   *      &lt;b&gt;Usage:&lt;/b&gt; java ch.qos.logback.classic.net.SimpleSocketServer port configFile
37   * </pre>
38   * 
39   * where <em>port</em> is a port number where the server listens and
40   * <em>configFile</em> is an XML configuration file fed to
41   * {@link JoranConfigurator}.
42   * 
43   * </pre>
44   * 
45   * @author Ceki G&uuml;lc&uuml;
46   * @author S&eacute;bastien Pennec
47   * 
48   * @since 0.8.4
49   */
50  public class SimpleSocketServer extends Thread {
51  
52      Logger logger = LoggerFactory.getLogger(SimpleSocketServer.class);
53  
54      private final int port;
55      private final LoggerContext lc;
56      private boolean closed = false;
57      private ServerSocket serverSocket;
58      private List<SocketNode> socketNodeList = new ArrayList<SocketNode>();
59  
60      // used for testing purposes
61      private CountDownLatch latch;
62  
63      public static void main(String argv[]) throws Exception {
64          doMain(SimpleSocketServer.class, argv);
65      }
66  
67      protected static void doMain(Class<? extends SimpleSocketServer> serverClass, String argv[]) throws Exception {
68          int port = -1;
69          if (argv.length == 2) {
70              port = parsePortNumber(argv[0]);
71          } else {
72              usage("Wrong number of arguments.");
73          }
74  
75          String configFile = argv[1];
76          LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
77          configureLC(lc, configFile);
78  
79          SimpleSocketServer sss = new SimpleSocketServer(lc, port);
80          sss.start();
81      }
82  
83      public SimpleSocketServer(LoggerContext lc, int port) {
84          this.lc = lc;
85          this.port = port;
86      }
87  
88      public void run() {
89  
90          final String oldThreadName = Thread.currentThread().getName();
91  
92          try {
93  
94              final String newThreadName = getServerThreadName();
95              Thread.currentThread().setName(newThreadName);
96  
97              logger.info("Listening on port " + port);
98              serverSocket = getServerSocketFactory().createServerSocket(port);
99              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 }