Improve TraceHandler, fix server system + tests:
authorNiki Roo <niki@nikiroo.be>
Mon, 27 Nov 2017 19:41:30 +0000 (20:41 +0100)
committerNiki Roo <niki@nikiroo.be>
Mon, 27 Nov 2017 19:41:30 +0000 (20:41 +0100)
- TraceHandler is easier to work with
- a Server can now send a second object (bugfix)
- more tests for the Server class (jDoc + renames incoming)

src/be/nikiroo/utils/TraceHandler.java
src/be/nikiroo/utils/serial/ConnectAction.java
src/be/nikiroo/utils/serial/Server.java
src/be/nikiroo/utils/test/SerialTest.java

index 5fa08438c3abe2e1a01e916d37f8d83b9c84ae8f..d7f604667f8976c8afe595a81620b5665d4981aa 100644 (file)
@@ -7,44 +7,32 @@ package be.nikiroo.utils;
  * @author niki
  */
 public class TraceHandler {
-       private boolean showErrorDetails;
+       private boolean showErrors;
        private boolean showTraces;
+       private boolean showErrorDetails;
 
        /**
-        * Show more details (usually equivalent to the value of DEBUG).
-        * 
-        * @return TRUE or FALSE
+        * Create a default {@link TraceHandler} that will print errors on stderr
+        * (without details) and no traces.
         */
-       public boolean isShowErrorDetails() {
-               return showErrorDetails;
+       public TraceHandler() {
+               this(true, false, false);
        }
 
        /**
-        * Show more details (usually equivalent to the value of DEBUG).
+        * Create a default {@link TraceHandler}.
         * 
+        * @param showErrors
+        *            show errors on stderr
         * @param showErrorDetails
-        *            TRUE or FALSE
-        */
-       public void setShowErrorDetails(boolean showErrorDetails) {
-               this.showErrorDetails = showErrorDetails;
-       }
-
-       /**
-        * Show DEBUG traces.
-        * 
-        * @return TRUE or FALSE
-        */
-       public boolean isShowTraces() {
-               return showTraces;
-       }
-
-       /**
-        * Show DEBUG traces.
-        * 
+        *            show more details when printing errors
         * @param showTraces
-        *            TRUE or FALSE
+        *            show traces on stdout
         */
-       public void setShowTraces(boolean showTraces) {
+       public TraceHandler(boolean showErrors, boolean showErrorDetails,
+                       boolean showTraces) {
+               this.showErrors = showErrors;
+               this.showErrorDetails = showErrorDetails;
                this.showTraces = showTraces;
        }
 
@@ -55,10 +43,12 @@ public class TraceHandler {
         *            the exception
         */
        public void error(Exception e) {
-               if (isShowErrorDetails()) {
-                       e.printStackTrace();
-               } else {
-                       error(e.getMessage());
+               if (showErrors) {
+                       if (showErrorDetails) {
+                               e.printStackTrace();
+                       } else {
+                               error(e.getMessage());
+                       }
                }
        }
 
@@ -69,7 +59,9 @@ public class TraceHandler {
         *            the error message
         */
        public void error(String message) {
-               System.err.println(message);
+               if (showErrors) {
+                       System.err.println(message);
+               }
        }
 
        /**
@@ -81,7 +73,7 @@ public class TraceHandler {
         *            the trace message
         */
        public void trace(String message) {
-               if (isShowTraces()) {
+               if (showTraces) {
                        System.out.println(message);
                }
        }
index 1f9833624c552516efa821c214cbb74765f82a55..2a25c5b1c62e9f6a15778e200b04603637fa747a 100644 (file)
@@ -42,6 +42,7 @@ abstract class ConnectAction {
                }).start();
        }
 
+       // connect, do the action (sync)
        public void connect() {
                try {
                        in = new BufferedReader(new InputStreamReader(s.getInputStream(),
@@ -81,7 +82,11 @@ abstract class ConnectAction {
                        NoSuchMethodException, ClassNotFoundException {
                synchronized (lock) {
                        String rep = sendString(new Exporter().append(data).toString(true));
-                       return new Importer().read(rep).getValue();
+                       if (rep != null) {
+                               return new Importer().read(rep).getValue();
+                       }
+
+                       return null;
                }
        }
 
index bc4e637a7d65d0e8bbdc154c6b45c32b0197a80f..e15680e5862d799291a732c52e7877b5fcad6c7a 100644 (file)
@@ -11,19 +11,34 @@ import javax.net.ssl.SSLServerSocketFactory;
 import javax.net.ssl.SSLSocket;
 import javax.net.ssl.SSLSocketFactory;
 
+import be.nikiroo.utils.TraceHandler;
 import be.nikiroo.utils.Version;
 
+/**
+ * This class implements a simple server that can listen for connections and
+ * send/receive objects.
+ * <p>
+ * Note: this {@link Server} has to be discarded after use (cannot be started
+ * twice).
+ * 
+ * @author niki
+ */
 abstract public class Server implements Runnable {
        static private final String[] ANON_CIPHERS = getAnonCiphers();
 
-       private int port;
-       private boolean ssl;
+       private final String name;
+       private final boolean ssl;
+       private final Object lock = new Object();
+       private final Object counterLock = new Object();
+
        private ServerSocket ss;
+       private int port;
+
        private boolean started;
        private boolean exiting = false;
        private int counter;
-       private Object lock = new Object();
-       private Object counterLock = new Object();
+
+       private TraceHandler tracer = new TraceHandler();
 
        @Deprecated
        public Server(@SuppressWarnings("unused") Version notUsed, int port,
@@ -31,27 +46,123 @@ abstract public class Server implements Runnable {
                this(port, ssl);
        }
 
+       /**
+        * Create a new server that will start listening on the network when
+        * {@link Server#start()} is called.
+        * 
+        * @param port
+        *            the port to listen on, or 0 to assign any unallocated port
+        *            found (which can later on be queried via
+        *            {@link Server#getPort()}
+        * @param ssl
+        *            use a SSL connection (or not)
+        * 
+        * @throws IOException
+        *             in case of I/O error
+        */
        public Server(int port, boolean ssl) throws IOException {
+               this((String) null, port, ssl);
+       }
+
+       /**
+        * Create a new server that will start listening on the network when
+        * {@link Server#start()} is called.
+        * 
+        * @param name
+        *            the server name (only used for debug info and traces)
+        * @param port
+        *            the port to listen on
+        * @param ssl
+        *            use a SSL connection (or not)
+        * 
+        * @throws IOException
+        *             in case of I/O error
+        */
+       public Server(String name, int port, boolean ssl) throws IOException {
+               this.name = name;
                this.port = port;
                this.ssl = ssl;
                this.ss = createSocketServer(port, ssl);
+
+               if (this.port == 0) {
+                       this.port = this.ss.getLocalPort();
+               }
+       }
+
+       /**
+        * The traces handler for this {@link Server}.
+        * 
+        * @return the traces handler
+        */
+       public TraceHandler getTraceHandler() {
+               return tracer;
        }
 
+       /**
+        * The traces handler for this {@link Server}.
+        * 
+        * @param tracer
+        *            the new traces handler
+        */
+       public void setTraceHandler(TraceHandler tracer) {
+               if (tracer == null) {
+                       tracer = new TraceHandler(false, false, false);
+               }
+
+               this.tracer = tracer;
+       }
+
+       /**
+        * Return the assigned port.
+        */
+       public int getPort() {
+               return port;
+       }
+
+       /**
+        * Start the server (listen on the network for new connections).
+        * <p>
+        * Can only be called once.
+        */
        public void start() {
+               boolean ok = false;
                synchronized (lock) {
-                       if (!started) {
+                       if (!started && ss != null) {
                                started = true;
                                new Thread(this).start();
+                               ok = true;
                        }
                }
+
+               if (ok) {
+                       tracer.trace(name + ": server started on port " + port);
+               } else if (ss == null) {
+                       tracer.error(name + ": cannot start server on port " + port
+                                       + ", it has already been used");
+               } else {
+                       tracer.error(name + ": cannot start server on port " + port
+                                       + ", it is already started");
+               }
        }
 
+       /**
+        * Will stop the server, synchronously and without a timeout.
+        */
        public void stop() {
+               tracer.trace(name + ": stopping server");
                stop(0, true);
        }
 
-       // wait = wait before returning (sync VS async) timeout in ms, 0 or -1 for
-       // never
+       /**
+        * Stop the server.
+        * 
+        * @param timeout
+        *            the maximum timeout to wait for existing actions to complete,
+        *            or 0 for "no timeout"
+        * @param wait
+        *            wait for the server to be stopped before returning
+        *            (synchronous) or not (asynchronous)
+        */
        public void stop(final long timeout, final boolean wait) {
                if (wait) {
                        stop(timeout);
@@ -65,7 +176,13 @@ abstract public class Server implements Runnable {
                }
        }
 
-       // timeout in ms, 0 or -1 or never
+       /**
+        * Stop the server (synchronous).
+        * 
+        * @param timeout
+        *            the maximum timeout to wait for existing actions to complete,
+        *            or 0 for "no timeout"
+        */
        private void stop(long timeout) {
                synchronized (lock) {
                        if (started && !exiting) {
@@ -105,6 +222,7 @@ abstract public class Server implements Runnable {
        @Override
        public void run() {
                try {
+                       tracer.trace(name + ": server starting on port " + port);
                        while (started && !exiting) {
                                count(1);
                                Socket s = ss.accept();
@@ -167,17 +285,47 @@ abstract public class Server implements Runnable {
                }
        }
 
+       /**
+        * This is the method that is called on each client request.
+        * <p>
+        * You are expected to react to it and return an answer (which can be NULL).
+        * 
+        * @param action
+        *            the client action
+        * @param clientVersion
+        *            the client version
+        * @param data
+        *            the data sent by the client (which can be NULL)
+        * 
+        * @return the answer to return to the client (which can be NULL)
+        * 
+        * @throws Exception
+        *             in case of an exception, the error will only be logged
+        */
        abstract protected Object onRequest(ConnectActionServer action,
                        Version clientVersion, Object data) throws Exception;
 
+       /**
+        * This method will be called on errors.
+        * <p>
+        * By default, it will only call the trace handler (so you may want to call
+        * super {@link Server#onError} if you override it).
+        * 
+        * @param e
+        *            the error
+        */
        protected void onError(Exception e) {
-               if (e == null) {
-                       e = new Exception("Unknown error");
-               }
-
-               e.printStackTrace();
+               tracer.error(e);
        }
 
+       /**
+        * Change the number of currently serviced actions.
+        * 
+        * @param change
+        *            the number to increase or decrease
+        * 
+        * @return the current number after this operation
+        */
        private int count(int change) {
                synchronized (counterLock) {
                        counter += change;
@@ -185,6 +333,21 @@ abstract public class Server implements Runnable {
                }
        }
 
+       /**
+        * Create a {@link Socket}.
+        * 
+        * @param host
+        *            the host to connect to
+        * @param port
+        *            the port to connect to
+        * @param ssl
+        *            TRUE for SSL mode (or FALSE for plain text mode)
+        * 
+        * @return the {@link Socket}
+        * 
+        * @throws IOException
+        *             in case of I/O error
+        */
        static Socket createSocket(String host, int port, boolean ssl)
                        throws IOException {
                Socket s;
@@ -198,6 +361,19 @@ abstract public class Server implements Runnable {
                return s;
        }
 
+       /**
+        * Create a {@link ServerSocket}.
+        * 
+        * @param port
+        *            the port to accept connections on
+        * @param ssl
+        *            TRUE for SSL mode (or FALSE for plain text mode)
+        * 
+        * @return the {@link ServerSocket}
+        * 
+        * @throws IOException
+        *             in case of I/O error
+        */
        static ServerSocket createSocketServer(int port, boolean ssl)
                        throws IOException {
                ServerSocket ss;
@@ -211,6 +387,11 @@ abstract public class Server implements Runnable {
                return ss;
        }
 
+       /**
+        * Return all the supported ciphers that do not use authentication.
+        * 
+        * @return the list of such supported ciphers
+        */
        private static String[] getAnonCiphers() {
                List<String> anonCiphers = new ArrayList<String>();
                for (String cipher : ((SSLSocketFactory) SSLSocketFactory.getDefault())
index 22f04c65f5f84b2777b75d3303237dc28c29f8da..4233a7bde2bd336f875ce1a29cc464e8d487acde 100644 (file)
@@ -1,6 +1,7 @@
 package be.nikiroo.utils.test;
 
 import be.nikiroo.utils.Version;
+import be.nikiroo.utils.serial.ConnectActionClient;
 import be.nikiroo.utils.serial.ConnectActionServer;
 import be.nikiroo.utils.serial.Exporter;
 import be.nikiroo.utils.serial.Importer;
@@ -26,9 +27,212 @@ class SerialTest extends TestLauncher {
                super("Serial test", null);
        }
 
+       private TestCase[] createServerTestCases(final boolean ssl) {
+               final String ssls = (ssl ? "(ssl)" : "(plain text)");
+               return new TestCase[] {
+                               new TestCase("Client/Server simple connection " + ssls) {
+                                       @Override
+                                       public void test() throws Exception {
+                                               final Object[] rec = new Object[1];
+
+                                               Server server = new Server("testy", 0, ssl) {
+                                                       @Override
+                                                       protected Object onRequest(
+                                                                       ConnectActionServer action,
+                                                                       Version clientVersion, Object data)
+                                                                       throws Exception {
+                                                               return null;
+                                                       }
+                                               };
+
+                                               assertEquals("A port should have been assigned", true,
+                                                               server.getPort() > 0);
+
+                                               server.start();
+
+                                               try {
+                                                       new ConnectActionClient(null, server.getPort(), ssl) {
+                                                               @Override
+                                                               public void action(Version serverVersion)
+                                                                               throws Exception {
+                                                                       rec[0] = true;
+                                                               }
+                                                       }.connect();
+                                               } finally {
+                                                       server.stop();
+                                               }
+
+                                               assertNotNull("The client action was not run", rec[0]);
+                                               assertEquals(true, (boolean) ((Boolean) rec[0]));
+                                       }
+                               }, //
+                               new TestCase("Client/Server simple exchange " + ssls) {
+                                       final Object[] rec = new Object[3];
+
+                                       @Override
+                                       public void test() throws Exception {
+                                               Server server = new Server("testy", 0, ssl) {
+                                                       @Override
+                                                       protected Object onRequest(
+                                                                       ConnectActionServer action,
+                                                                       Version clientVersion, Object data)
+                                                                       throws Exception {
+                                                               rec[0] = data;
+                                                               return "pong";
+                                                       }
+
+                                                       @Override
+                                                       protected void onError(Exception e) {
+                                                               super.onError(e);
+                                                               rec[2] = e;
+                                                       }
+                                               };
+
+                                               assertEquals("A port should have been assigned", true,
+                                                               server.getPort() > 0);
+
+                                               server.start();
+
+                                               try {
+                                                       new ConnectActionClient(null, server.getPort(), ssl) {
+                                                               @Override
+                                                               public void action(Version serverVersion)
+                                                                               throws Exception {
+                                                                       rec[1] = send("ping");
+                                                               }
+                                                       }.connect();
+                                               } finally {
+                                                       server.stop();
+                                               }
+
+                                               if (rec[2] != null) {
+                                                       fail("An exception was thrown: "
+                                                                       + ((Exception) rec[2]).getMessage());
+                                               }
+
+                                               assertEquals("ping", rec[0]);
+                                               assertEquals("pong", rec[1]);
+                                       }
+                               }, //
+                               new TestCase("Client/Server multiple exchanges " + ssls) {
+                                       final Object[] sent = new Object[3];
+                                       final Object[] recd = new Object[3];
+                                       final Exception[] err = new Exception[1];
+
+                                       @Override
+                                       public void test() throws Exception {
+                                               Server server = new Server("testy", 0, ssl) {
+                                                       @Override
+                                                       protected Object onRequest(
+                                                                       ConnectActionServer action,
+                                                                       Version clientVersion, Object data)
+                                                                       throws Exception {
+                                                               sent[0] = data;
+                                                               action.send("pong");
+                                                               sent[1] = action.flush();
+                                                               return "pong2";
+                                                       }
+
+                                                       @Override
+                                                       protected void onError(Exception e) {
+                                                               super.onError(e);
+                                                               err[0] = e;
+                                                       }
+                                               };
+
+                                               server.start();
+
+                                               try {
+                                                       new ConnectActionClient(null, server.getPort(), ssl) {
+                                                               @Override
+                                                               public void action(Version serverVersion)
+                                                                               throws Exception {
+                                                                       recd[0] = send("ping");
+                                                                       recd[1] = send("ping2");
+                                                               }
+                                                       }.connect();
+                                               } finally {
+                                                       server.stop();
+                                               }
+
+                                               if (err[0] != null) {
+                                                       fail("An exception was thrown: "
+                                                                       + err[0].getMessage());
+                                               }
+
+                                               assertEquals("ping", sent[0]);
+                                               assertEquals("pong", recd[0]);
+                                               assertEquals("ping2", sent[1]);
+                                               assertEquals("pong2", recd[1]);
+                                       }
+                               }, //
+                               new TestCase("Client/Server multiple call from client " + ssls) {
+                                       final Object[] sent = new Object[3];
+                                       final Object[] recd = new Object[3];
+                                       final Exception[] err = new Exception[1];
+
+                                       @Override
+                                       public void test() throws Exception {
+                                               Server server = new Server("testy", 0, ssl) {
+                                                       @Override
+                                                       protected Object onRequest(
+                                                                       ConnectActionServer action,
+                                                                       Version clientVersion, Object data)
+                                                                       throws Exception {
+                                                               sent[(Integer) data] = data;
+                                                               return ((Integer) data) * 2;
+                                                       }
+
+                                                       @Override
+                                                       protected void onError(Exception e) {
+                                                               super.onError(e);
+                                                               err[0] = e;
+                                                       }
+                                               };
+
+                                               server.start();
+
+                                               try {
+                                                       new ConnectActionClient(null, server.getPort(), ssl) {
+                                                               @Override
+                                                               public void action(Version serverVersion)
+                                                                               throws Exception {
+                                                                       for (int i = 0; i < 3; i++) {
+                                                                               recd[i] = send(i);
+                                                                       }
+                                                               }
+                                                       }.connect();
+                                               } finally {
+                                                       server.stop();
+                                               }
+
+                                               if (err[0] != null) {
+                                                       fail("An exception was thrown: "
+                                                                       + err[0].getMessage());
+                                               }
+
+                                               assertEquals(0, sent[0]);
+                                               assertEquals(0, recd[0]);
+                                               assertEquals(1, sent[1]);
+                                               assertEquals(2, recd[1]);
+                                               assertEquals(2, sent[2]);
+                                               assertEquals(4, recd[2]);
+                                       }
+                               }, //
+               };
+       }
+
        public SerialTest(String[] args) {
                super("Serial test", args);
 
+               for (TestCase test : createServerTestCases(false)) {
+                       addTest(test);
+               }
+
+               for (TestCase test : createServerTestCases(true)) {
+                       addTest(test);
+               }
+
                addTest(new TestCase("Simple class Import/Export") {
                        @Override
                        public void test() throws Exception {