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
150      * This is useful 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      * @param latch
162      */
163     void setLatch(CountDownLatch latch) {
164         this.latch = latch;
165     }
166 
167     /**
168       * Used for testing purposes
169       */
170     public CountDownLatch getLatch() {
171         return latch;
172     }
173 
174     public boolean isClosed() {
175         return closed;
176     }
177 
178     public void close() {
179         closed = true;
180         if (serverSocket != null) {
181             try {
182                 serverSocket.close();
183             } catch (IOException e) {
184                 logger.error("Failed to close serverSocket", e);
185             } finally {
186                 serverSocket = null;
187             }
188         }
189 
190         logger.info("closing this server");
191         synchronized (socketNodeList) {
192             for (SocketNode sn : socketNodeList) {
193                 sn.close();
194             }
195         }
196         if (socketNodeList.size() != 0) {
197             logger.warn("Was expecting a 0-sized socketNodeList after server shutdown");
198         }
199 
200     }
201 
202     public void socketNodeClosing(SocketNode sn) {
203         logger.debug("Removing {}", sn);
204 
205         // don't allow simultaneous access to the socketNodeList
206         // (e.g. removal whole iterating on the list causes
207         // java.util.ConcurrentModificationException
208         synchronized (socketNodeList) {
209             socketNodeList.remove(sn);
210         }
211     }
212 
213     static void usage(String msg) {
214         System.err.println(msg);
215         System.err.println("Usage: java " + SimpleSocketServer.class.getName() + " port configFile");
216         System.exit(1);
217     }
218 
219     static int parsePortNumber(String portStr) {
220         try {
221             return Integer.parseInt(portStr);
222         } catch (java.lang.NumberFormatException e) {
223             e.printStackTrace();
224             usage("Could not interpret port number [" + portStr + "].");
225             // we won't get here
226             return -1;
227         }
228     }
229 
230     static public void configureLC(LoggerContext lc, String configFile) throws JoranException {
231         JoranConfigurator configurator = new JoranConfigurator();
232         lc.reset();
233         configurator.setContext(lc);
234         configurator.doConfigure(configFile);
235     }
236 }