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