remove uneeded log
[nikiroo-utils.git] / src / be / nikiroo / utils / serial / server / Server.java
CommitLineData
79ce1a49 1package be.nikiroo.utils.serial.server;
ce0974c4
NR
2
3import java.io.IOException;
4import java.net.ServerSocket;
5import java.net.Socket;
f4053377 6import java.net.UnknownHostException;
ce0974c4
NR
7import java.util.ArrayList;
8import java.util.List;
9
10import javax.net.ssl.SSLServerSocket;
11import javax.net.ssl.SSLServerSocketFactory;
12import javax.net.ssl.SSLSocket;
13import javax.net.ssl.SSLSocketFactory;
14
08a58812 15import be.nikiroo.utils.TraceHandler;
ce0974c4 16
08a58812
NR
17/**
18 * This class implements a simple server that can listen for connections and
19 * send/receive objects.
20 * <p>
21 * Note: this {@link Server} has to be discarded after use (cannot be started
22 * twice).
23 *
24 * @author niki
25 */
79ce1a49 26abstract class Server implements Runnable {
ce0974c4
NR
27 static private final String[] ANON_CIPHERS = getAnonCiphers();
28
08a58812
NR
29 private final String name;
30 private final boolean ssl;
31 private final Object lock = new Object();
32 private final Object counterLock = new Object();
33
ce0974c4 34 private ServerSocket ss;
08a58812
NR
35 private int port;
36
ce0974c4
NR
37 private boolean started;
38 private boolean exiting = false;
39 private int counter;
08a58812 40
4bb7e88e
NR
41 private long bytesReceived;
42 private long bytesSent;
43
08a58812 44 private TraceHandler tracer = new TraceHandler();
ce0974c4 45
79ce1a49
NR
46 /**
47 * Create a new {@link ConnectActionServer} to handle a request.
48 *
49 * @param s
50 * the socket to service
51 *
52 * @return the action
53 */
54 abstract ConnectActionServer createConnectActionServer(Socket s);
cd0c27d2 55
08a58812
NR
56 /**
57 * Create a new server that will start listening on the network when
58 * {@link Server#start()} is called.
59 *
60 * @param port
61 * the port to listen on, or 0 to assign any unallocated port
62 * found (which can later on be queried via
63 * {@link Server#getPort()}
64 * @param ssl
65 * use a SSL connection (or not)
66 *
67 * @throws IOException
68 * in case of I/O error
f4053377
NR
69 * @throws UnknownHostException
70 * if the IP address of the host could not be determined
71 * @throws IllegalArgumentException
72 * if the port parameter is outside the specified range of valid
73 * port values, which is between 0 and 65535, inclusive
08a58812 74 */
cd0c27d2 75 public Server(int port, boolean ssl) throws IOException {
08a58812
NR
76 this((String) null, port, ssl);
77 }
78
79 /**
80 * Create a new server that will start listening on the network when
81 * {@link Server#start()} is called.
82 *
83 * @param name
84 * the server name (only used for debug info and traces)
85 * @param port
86 * the port to listen on
87 * @param ssl
88 * use a SSL connection (or not)
89 *
90 * @throws IOException
91 * in case of I/O error
f4053377
NR
92 * @throws UnknownHostException
93 * if the IP address of the host could not be determined
94 * @throws IllegalArgumentException
95 * if the port parameter is outside the specified range of valid
96 * port values, which is between 0 and 65535, inclusive
08a58812
NR
97 */
98 public Server(String name, int port, boolean ssl) throws IOException {
99 this.name = name;
ce0974c4
NR
100 this.port = port;
101 this.ssl = ssl;
102 this.ss = createSocketServer(port, ssl);
08a58812
NR
103
104 if (this.port == 0) {
105 this.port = this.ss.getLocalPort();
106 }
107 }
108
109 /**
110 * The traces handler for this {@link Server}.
111 *
112 * @return the traces handler
113 */
114 public TraceHandler getTraceHandler() {
115 return tracer;
ce0974c4
NR
116 }
117
08a58812
NR
118 /**
119 * The traces handler for this {@link Server}.
120 *
121 * @param tracer
122 * the new traces handler
123 */
124 public void setTraceHandler(TraceHandler tracer) {
125 if (tracer == null) {
126 tracer = new TraceHandler(false, false, false);
127 }
128
129 this.tracer = tracer;
130 }
131
79ce1a49
NR
132 /**
133 * The name of this {@link Server} if any.
134 * <p>
135 * Used for traces and debug purposes only.
136 *
137 * @return the name or NULL
138 */
139 public String getName() {
140 return name;
141 }
142
08a58812
NR
143 /**
144 * Return the assigned port.
f157aed8
NR
145 *
146 * @return the assigned port
08a58812
NR
147 */
148 public int getPort() {
149 return port;
150 }
151
4bb7e88e
NR
152 /**
153 * The total amount of bytes received.
154 *
155 * @return the amount of bytes received
156 */
157 public long getBytesReceived() {
158 return bytesReceived;
159 }
160
161 /**
162 * The total amount of bytes sent.
163 *
164 * @return the amount of bytes sent
165 */
166 public long getBytesSent() {
167 return bytesSent;
168 }
169
08a58812
NR
170 /**
171 * Start the server (listen on the network for new connections).
172 * <p>
173 * Can only be called once.
b08aa70e 174 * <p>
d463d266
NR
175 * This call is asynchronous, and will just start a new {@link Thread} on
176 * itself (see {@link Server#run()}).
08a58812 177 */
ce0974c4 178 public void start() {
d463d266 179 new Thread(this).start();
b08aa70e
NR
180 }
181
182 /**
183 * Start the server (listen on the network for new connections).
184 * <p>
185 * Can only be called once.
d463d266
NR
186 * <p>
187 * You may call it via {@link Server#start()} for an asynchronous call, too.
b08aa70e 188 */
d463d266
NR
189 @Override
190 public void run() {
191 ServerSocket ss = null;
192 boolean alreadyStarted = false;
ce0974c4 193 synchronized (lock) {
d463d266 194 ss = this.ss;
08a58812 195 if (!started && ss != null) {
b08aa70e 196 started = true;
d463d266
NR
197 } else {
198 alreadyStarted = started;
ce0974c4
NR
199 }
200 }
08a58812 201
d463d266 202 if (alreadyStarted) {
08a58812
NR
203 tracer.error(name + ": cannot start server on port " + port
204 + ", it is already started");
d463d266
NR
205 return;
206 }
207
208 if (ss == null) {
209 tracer.error(name + ": cannot start server on port " + port
210 + ", it has already been used");
211 return;
212 }
213
214 try {
f4053377
NR
215 tracer.trace(name + ": server starting on port " + port + " ("
216 + (ssl ? "SSL" : "plain text") + ")");
d463d266 217
d9e2136b
N
218 String ciphers = "";
219 for (String cipher : getAnonCiphers()) {
220 if (!ciphers.isEmpty()) {
221 ciphers += ", ";
222 }
223 ciphers += cipher;
224 }
d9e2136b 225
d463d266
NR
226 while (started && !exiting) {
227 count(1);
8537d55a
NR
228 final Socket s = ss.accept();
229 new Thread(new Runnable() {
230 @Override
231 public void run() {
4bb7e88e 232 ConnectActionServer action = null;
8537d55a 233 try {
4bb7e88e
NR
234 action = createConnectActionServer(s);
235 action.connect();
8537d55a
NR
236 } finally {
237 count(-1);
4bb7e88e
NR
238 if (action != null) {
239 bytesReceived += action.getBytesReceived();
240 bytesSent += action.getBytesSent();
241 }
8537d55a
NR
242 }
243 }
244 }).start();
d463d266
NR
245 }
246
247 // Will be covered by @link{Server#stop(long)} for timeouts
248 while (counter > 0) {
249 Thread.sleep(10);
250 }
251 } catch (Exception e) {
252 if (counter > 0) {
253 onError(e);
254 }
255 } finally {
256 try {
257 ss.close();
258 } catch (Exception e) {
259 onError(e);
260 }
261
262 this.ss = null;
263
264 started = false;
265 exiting = false;
266 counter = 0;
267
268 tracer.trace(name + ": client terminated on port " + port);
08a58812 269 }
ce0974c4
NR
270 }
271
08a58812
NR
272 /**
273 * Will stop the server, synchronously and without a timeout.
274 */
ce0974c4 275 public void stop() {
08a58812 276 tracer.trace(name + ": stopping server");
ce0974c4
NR
277 stop(0, true);
278 }
279
08a58812
NR
280 /**
281 * Stop the server.
282 *
283 * @param timeout
284 * the maximum timeout to wait for existing actions to complete,
285 * or 0 for "no timeout"
286 * @param wait
287 * wait for the server to be stopped before returning
288 * (synchronous) or not (asynchronous)
289 */
ce0974c4
NR
290 public void stop(final long timeout, final boolean wait) {
291 if (wait) {
292 stop(timeout);
293 } else {
294 new Thread(new Runnable() {
cd0c27d2 295 @Override
ce0974c4
NR
296 public void run() {
297 stop(timeout);
298 }
299 }).start();
300 }
301 }
302
08a58812
NR
303 /**
304 * Stop the server (synchronous).
305 *
306 * @param timeout
307 * the maximum timeout to wait for existing actions to complete,
308 * or 0 for "no timeout"
309 */
ce0974c4 310 private void stop(long timeout) {
d463d266 311 tracer.trace(name + ": server stopping on port " + port);
ce0974c4
NR
312 synchronized (lock) {
313 if (started && !exiting) {
314 exiting = true;
315
316 try {
79ce1a49 317 new ConnectActionClientObject(createSocket(null, port, ssl))
f157aed8 318 .connect();
ce0974c4
NR
319 long time = 0;
320 while (ss != null && timeout > 0 && timeout > time) {
321 Thread.sleep(10);
322 time += 10;
323 }
324 } catch (Exception e) {
325 if (ss != null) {
326 counter = 0; // will stop the main thread
327 onError(e);
328 }
329 }
330 }
0988831f 331 }
ce0974c4 332
0988831f
NR
333 // return only when stopped
334 while (started || exiting) {
335 try {
336 Thread.sleep(10);
337 } catch (InterruptedException e) {
ce0974c4
NR
338 }
339 }
340 }
341
08a58812
NR
342 /**
343 * Change the number of currently serviced actions.
344 *
345 * @param change
346 * the number to increase or decrease
347 *
348 * @return the current number after this operation
349 */
8537d55a 350 private int count(int change) {
ce0974c4
NR
351 synchronized (counterLock) {
352 counter += change;
353 return counter;
354 }
355 }
356
8537d55a
NR
357 /**
358 * This method will be called on errors.
359 * <p>
360 * By default, it will only call the trace handler (so you may want to call
361 * super {@link Server#onError} if you override it).
362 *
363 * @param e
364 * the error
365 */
366 protected void onError(Exception e) {
367 tracer.error(e);
368 }
369
08a58812
NR
370 /**
371 * Create a {@link Socket}.
372 *
373 * @param host
374 * the host to connect to
375 * @param port
376 * the port to connect to
377 * @param ssl
378 * TRUE for SSL mode (or FALSE for plain text mode)
379 *
380 * @return the {@link Socket}
381 *
382 * @throws IOException
383 * in case of I/O error
f4053377
NR
384 * @throws UnknownHostException
385 * if the host is not known
386 * @throws IllegalArgumentException
387 * if the port parameter is outside the specified range of valid
388 * port values, which is between 0 and 65535, inclusive
08a58812 389 */
ce0974c4
NR
390 static Socket createSocket(String host, int port, boolean ssl)
391 throws IOException {
392 Socket s;
393 if (ssl) {
394 s = SSLSocketFactory.getDefault().createSocket(host, port);
0988831f
NR
395 if (s instanceof SSLSocket) {
396 // Should always be the case
397 ((SSLSocket) s).setEnabledCipherSuites(ANON_CIPHERS);
398 }
ce0974c4
NR
399 } else {
400 s = new Socket(host, port);
401 }
402
403 return s;
404 }
405
08a58812
NR
406 /**
407 * Create a {@link ServerSocket}.
408 *
409 * @param port
410 * the port to accept connections on
411 * @param ssl
412 * TRUE for SSL mode (or FALSE for plain text mode)
413 *
414 * @return the {@link ServerSocket}
415 *
416 * @throws IOException
417 * in case of I/O error
f4053377
NR
418 * @throws UnknownHostException
419 * if the IP address of the host could not be determined
420 * @throws IllegalArgumentException
421 * if the port parameter is outside the specified range of valid
422 * port values, which is between 0 and 65535, inclusive
08a58812 423 */
ce0974c4
NR
424 static ServerSocket createSocketServer(int port, boolean ssl)
425 throws IOException {
426 ServerSocket ss;
427 if (ssl) {
428 ss = SSLServerSocketFactory.getDefault().createServerSocket(port);
0988831f
NR
429 if (ss instanceof SSLServerSocket) {
430 // Should always be the case
431 ((SSLServerSocket) ss).setEnabledCipherSuites(ANON_CIPHERS);
432 }
ce0974c4
NR
433 } else {
434 ss = new ServerSocket(port);
435 }
436
437 return ss;
438 }
439
08a58812
NR
440 /**
441 * Return all the supported ciphers that do not use authentication.
442 *
443 * @return the list of such supported ciphers
444 */
d9e2136b 445 public static String[] getAnonCiphers() {
ce0974c4
NR
446 List<String> anonCiphers = new ArrayList<String>();
447 for (String cipher : ((SSLSocketFactory) SSLSocketFactory.getDefault())
448 .getSupportedCipherSuites()) {
449 if (cipher.contains("_anon_")) {
450 anonCiphers.add(cipher);
451 }
452 }
453
454 return anonCiphers.toArray(new String[] {});
455 }
456}