JDK-8210696-branch: push initial fix change JDK-8210696-branch
authorxyin
Wed, 01 May 2019 00:06:22 -0700
branchJDK-8210696-branch
changeset 57345 ff884a2f247b
parent 57343 9a11a7e1c035
child 57346 3efc6cb7ffdb
JDK-8210696-branch: push initial fix change
test/jdk/com/sun/jndi/ldap/DisconnectNPETest.java
test/jdk/com/sun/jndi/ldap/LdapName/EmptyNameSearch.java
test/jdk/com/sun/jndi/ldap/NoWaitForReplyTest.java
test/jdk/com/sun/jndi/ldap/RemoveNamingListenerTest.java
test/jdk/com/sun/jndi/ldap/lib/BaseLdapServer.java
test/jdk/com/sun/jndi/ldap/lib/ConnectionHandler.java
test/jdk/com/sun/jndi/ldap/lib/LDAPTestUtils.java
test/jdk/com/sun/jndi/ldap/lib/LdapMessage.java
test/jdk/com/sun/jndi/ldap/lib/LdapPlaybackServer.java
test/jdk/com/sun/jndi/ldap/lib/RequestHandler.java
test/jdk/com/sun/jndi/ldap/lib/SessionHandler.java
--- a/test/jdk/com/sun/jndi/ldap/DisconnectNPETest.java	Tue Apr 30 12:52:23 2019 +0100
+++ b/test/jdk/com/sun/jndi/ldap/DisconnectNPETest.java	Wed May 01 00:06:22 2019 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -25,13 +25,9 @@
 import javax.naming.NamingException;
 import javax.naming.directory.DirContext;
 import javax.naming.directory.InitialDirContext;
-import java.io.Closeable;
 import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
 import java.net.InetAddress;
 import java.net.ServerSocket;
-import java.net.Socket;
 import java.util.Hashtable;
 
 /*
@@ -41,6 +37,7 @@
  *          the LDAP directory server sends an (unsolicited)
  *          "Notice of Disconnection", make sure client handle it correctly,
  *          no NPE been thrown.
+ * @library lib/
  * @run main/othervm DisconnectNPETest
  */
 
@@ -49,18 +46,27 @@
     // case, we set repeat count to 1000 here.
     private static final int REPEAT_COUNT = 1000;
 
+    // "Notice of Disconnection" message
+    private static final byte[] DISCONNECT_MSG = { 0x30, 0x4C, 0x02, 0x01, 0x00,
+            0x78, 0x47, 0x0A, 0x01, 0x34, 0x04, 0x00, 0x04, 0x28, 0x55, 0x4E,
+            0x41, 0x56, 0x41, 0x49, 0x4C, 0x41, 0x42, 0x4C, 0x45, 0x3A, 0x20,
+            0x54, 0x68, 0x65, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20,
+            0x77, 0x69, 0x6C, 0x6C, 0x20, 0x64, 0x69, 0x73, 0x63, 0x6F, 0x6E,
+            0x6E, 0x65, 0x63, 0x74, 0x21, (byte) 0x8A, 0x16, 0x31, 0x2E, 0x33,
+            0x2E, 0x36, 0x2E, 0x31, 0x2E, 0x34, 0x2E, 0x31, 0x2E, 0x31, 0x34,
+            0x36, 0x36, 0x2E, 0x32, 0x30, 0x30, 0x33, 0x36 };
+    private static final byte[] BIND_RESPONSE = { 0x30, 0x0C, 0x02, 0x01, 0x01,
+            0x61, 0x07, 0x0A, 0x01, 0x00, 0x04, 0x00, 0x04, 0x00 };
+
     public static void main(String[] args) throws IOException {
         new DisconnectNPETest().run();
     }
 
     private ServerSocket serverSocket;
     private Hashtable<Object, Object> env;
-    private TestLDAPServer server;
 
     private void initRes() throws IOException {
         serverSocket = new ServerSocket(0, 0, InetAddress.getLoopbackAddress());
-        server = new TestLDAPServer();
-        server.start();
     }
 
     private void initTest() {
@@ -80,7 +86,14 @@
         initRes();
         initTest();
         int count = 0;
-        try {
+        try (BaseLdapServer ignored = new BaseLdapServer(serverSocket)
+                .setCommonRequestHandler((msg, out) -> {
+                    if (msg.getOperation()
+                            == LdapMessage.Operation.BIND_REQUEST) {
+                        out.write(BIND_RESPONSE);
+                        out.write(DISCONNECT_MSG);
+                    }
+                }).startServer()) {
             while (count < REPEAT_COUNT) {
                 count++;
                 InitialDirContext context = null;
@@ -97,17 +110,9 @@
             }
         } finally {
             System.out.println("Test count: " + count + "/" + REPEAT_COUNT);
-            cleanupTest();
         }
     }
 
-    private void cleanupTest() {
-        if (server != null) {
-            server.stopServer();
-        }
-        cleanupClosableRes(serverSocket);
-    }
-
     private void cleanupContext(DirContext context) {
         if (context != null) {
             try {
@@ -117,77 +122,4 @@
             }
         }
     }
-
-    private static void cleanupClosableRes(Closeable res) {
-        if (res != null) {
-            try {
-                res.close();
-            } catch (Exception e) {
-                // ignore
-            }
-        }
-    }
-
-    class TestLDAPServer extends Thread {
-        private volatile boolean isRunning;
-
-        TestLDAPServer() {
-            isRunning = true;
-        }
-
-        private void stopServer() {
-            isRunning = false;
-        }
-
-        @Override
-        public void run() {
-            try {
-                while (isRunning) {
-                    Socket clientSocket = serverSocket.accept();
-                    Thread handler = new Thread(
-                            new LDAPServerHandler(clientSocket));
-                    handler.start();
-                }
-            } catch (IOException e) {
-                if (isRunning) {
-                    throw new RuntimeException(e);
-                }
-            }
-        }
-    }
-
-    static class LDAPServerHandler implements Runnable {
-        // "Notice of Disconnection" message
-        private static final byte[] DISCONNECT_MSG = { 0x30, 0x4C, 0x02, 0x01,
-                0x00, 0x78, 0x47, 0x0A, 0x01, 0x34, 0x04, 0x00, 0x04, 0x28,
-                0x55, 0x4E, 0x41, 0x56, 0x41, 0x49, 0x4C, 0x41, 0x42, 0x4C,
-                0x45, 0x3A, 0x20, 0x54, 0x68, 0x65, 0x20, 0x73, 0x65, 0x72,
-                0x76, 0x65, 0x72, 0x20, 0x77, 0x69, 0x6C, 0x6C, 0x20, 0x64,
-                0x69, 0x73, 0x63, 0x6F, 0x6E, 0x6E, 0x65, 0x63, 0x74, 0x21,
-                (byte) 0x8A, 0x16, 0x31, 0x2E, 0x33, 0x2E, 0x36, 0x2E, 0x31,
-                0x2E, 0x34, 0x2E, 0x31, 0x2E, 0x31, 0x34, 0x36, 0x36, 0x2E,
-                0x32, 0x30, 0x30, 0x33, 0x36 };
-        private static final byte[] BIND_RESPONSE = { 0x30, 0x0C, 0x02, 0x01,
-                0x01, 0x61, 0x07, 0x0A, 0x01, 0x00, 0x04, 0x00, 0x04, 0x00 };
-        private final Socket clientSocket;
-
-        private LDAPServerHandler(final Socket clientSocket) {
-            this.clientSocket = clientSocket;
-        }
-
-        @Override
-        public void run() {
-            try (clientSocket;
-                    OutputStream out = clientSocket.getOutputStream();
-                    InputStream in = clientSocket.getInputStream()) {
-                if (in.read() > 0) {
-                    in.skip(in.available());
-                    out.write(BIND_RESPONSE);
-                    out.write(DISCONNECT_MSG);
-                }
-            } catch (IOException e) {
-                e.printStackTrace();
-            }
-        }
-    }
 }
--- a/test/jdk/com/sun/jndi/ldap/LdapName/EmptyNameSearch.java	Tue Apr 30 12:52:23 2019 +0100
+++ b/test/jdk/com/sun/jndi/ldap/LdapName/EmptyNameSearch.java	Wed May 01 00:06:22 2019 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,11 +24,10 @@
 /**
  * @test
  * @bug 6997561
+ * @library ../lib/
  * @summary A request for better error handling in JNDI
  */
 
-import java.net.Socket;
-import java.net.ServerSocket;
 import java.io.*;
 import javax.naming.*;
 import javax.naming.directory.*;
@@ -46,12 +45,12 @@
         Thread.sleep(3000);
 
         // Setup JNDI parameters
-        Hashtable env = new Hashtable();
+        Hashtable<String, Object> env = new Hashtable<>();
         env.put(Context.INITIAL_CONTEXT_FACTORY,
             "com.sun.jndi.ldap.LdapCtxFactory");
-        env.put(Context.PROVIDER_URL, "ldap://localhost:" + s.getPortNumber());
+        env.put(Context.PROVIDER_URL, "ldap://localhost:" + s.getPort());
 
-        try {
+        try (s) {
 
             // Create initial context
             System.out.println("Client: connecting...");
@@ -73,9 +72,8 @@
         }
     }
 
-    static class Server extends Thread {
+    static class Server extends BaseLdapServer {
 
-        private int serverPort = 0;
         private byte[] bindResponse = {
             0x30, 0x0C, 0x02, 0x01, 0x01, 0x61, 0x07, 0x0A,
             0x01, 0x00, 0x04, 0x00, 0x04, 0x00
@@ -85,57 +83,24 @@
             0x01, 0x02, 0x04, 0x00, 0x04, 0x00
         };
 
-        Server() {
-        }
-
-        public int getPortNumber() {
-            return serverPort;
-        }
-
-        public void run() {
-            try {
-                ServerSocket serverSock = new ServerSocket(0);
-                serverPort = serverSock.getLocalPort();
-                System.out.println("Server: listening on port " + serverPort);
-
-                Socket socket = serverSock.accept();
-                System.out.println("Server: connection accepted");
-
-                InputStream in = socket.getInputStream();
-                OutputStream out = socket.getOutputStream();
-
-                // Read the LDAP BindRequest
-                System.out.println("Server: reading request...");
-                while (in.read() != -1) {
-                    in.skip(in.available());
-                    break;
+        Server() throws IOException {
+            setDebugLevel(DebugLevel.FULL);
+            setCommonRequestHandler((msg, out) -> {
+                switch (msg.getOperation()) {
+                    case BIND_REQUEST:
+                        // Write an LDAP BindResponse
+                        debug("writing response...");
+                        out.write(bindResponse);
+                        break;
+                    case SEARCH_REQUEST:
+                        // Write an LDAP SearchResponse
+                        debug("writing response...");
+                        out.write(searchResponse);
+                        break;
+                    default:
+                        break;
                 }
-
-                // Write an LDAP BindResponse
-                System.out.println("Server: writing response...");
-                out.write(bindResponse);
-                out.flush();
-
-                // Read the LDAP SearchRequest
-                System.out.println("Server: reading request...");
-                while (in.read() != -1) {
-                    in.skip(in.available());
-                    break;
-                }
-
-                // Write an LDAP SearchResponse
-                System.out.println("Server: writing response...");
-                out.write(searchResponse);
-                out.flush();
-
-                in.close();
-                out.close();
-                socket.close();
-                serverSock.close();
-
-            } catch (IOException e) {
-                // ignore
-            }
+            });
         }
     }
 }
--- a/test/jdk/com/sun/jndi/ldap/NoWaitForReplyTest.java	Tue Apr 30 12:52:23 2019 +0100
+++ b/test/jdk/com/sun/jndi/ldap/NoWaitForReplyTest.java	Wed May 01 00:06:22 2019 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -24,12 +24,10 @@
 /**
  * @test
  * @bug 6748156
+ * @library lib/
  * @summary add an new JNDI property to control the boolean flag WaitForReply
  */
 
-import java.net.Socket;
-import java.net.ServerSocket;
-import java.io.*;
 import javax.naming.*;
 import javax.naming.directory.*;
 import java.util.Hashtable;
@@ -41,13 +39,13 @@
         boolean passed = false;
 
         // start the LDAP server
-        DummyServer ldapServer = new DummyServer();
-        ldapServer.start();
+        BaseLdapServer ldapServer = new BaseLdapServer()
+                .setDebugLevel(BaseLdapServer.DebugLevel.FULL).startServer();
 
         // Set up the environment for creating the initial context
-        Hashtable env = new Hashtable(11);
+        Hashtable<String, Object> env = new Hashtable<>(11);
         env.put(Context.PROVIDER_URL, "ldap://localhost:" +
-            ldapServer.getPortNumber());
+                ldapServer.getPort());
         env.put(Context.INITIAL_CONTEXT_FACTORY,
             "com.sun.jndi.ldap.LdapCtxFactory");
 
@@ -61,7 +59,7 @@
         env.put("java.naming.ldap.version", "3");
 
 
-        try {
+        try (ldapServer) {
 
             // Create initial context
             System.out.println("Client: connecting to the server");
@@ -70,7 +68,7 @@
             SearchControls scl = new SearchControls();
             scl.setSearchScope(SearchControls.SUBTREE_SCOPE);
             System.out.println("Client: performing search");
-            NamingEnumeration answer =
+            NamingEnumeration<?> answer =
             ctx.search("ou=People,o=JNDITutorial", "(objectClass=*)", scl);
 
             // Server will never reply: either we waited in the call above until
@@ -84,7 +82,6 @@
         } catch (NamingException e) {
             // timeout (ignore)
         }
-        ldapServer.interrupt();
 
         if (!passed) {
             throw new Exception(
@@ -92,40 +89,4 @@
         }
         System.out.println("Test PASSED");
     }
-
-    static class DummyServer extends Thread {
-
-        private final ServerSocket serverSocket;
-
-        DummyServer() throws IOException {
-            this.serverSocket = new ServerSocket(0);
-            System.out.println("Server: listening on port " + serverSocket.getLocalPort());
-        }
-
-        public int getPortNumber() {
-            return serverSocket.getLocalPort();
-        }
-
-        public void run() {
-            try (Socket socket = serverSocket.accept()) {
-                System.out.println("Server: accepted a connection");
-                InputStream in = socket.getInputStream();
-
-                while (!isInterrupted()) {
-                   in.skip(in.available());
-                }
-
-            } catch (Exception e) {
-                // ignore
-
-            } finally {
-                System.out.println("Server: shutting down");
-                try {
-                    serverSocket.close();
-                } catch (IOException e) {
-                    // ignore
-                }
-            }
-        }
-    }
 }
--- a/test/jdk/com/sun/jndi/ldap/RemoveNamingListenerTest.java	Tue Apr 30 12:52:23 2019 +0100
+++ b/test/jdk/com/sun/jndi/ldap/RemoveNamingListenerTest.java	Wed May 01 00:06:22 2019 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2011, 2017, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2011, 2019, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -20,13 +20,10 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
-import java.io.BufferedInputStream;
+
 import java.io.IOException;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
+import java.net.InetAddress;
 import java.net.ServerSocket;
-import java.net.Socket;
-import java.nio.charset.StandardCharsets;
 import java.util.ConcurrentModificationException;
 import java.util.Hashtable;
 import javax.naming.Context;
@@ -44,6 +41,7 @@
  * @summary Incorrect usage of Iterator in Java 8 In com.sun.jndi.ldap.
  * EventSupport.removeNamingListener
  * @modules java.naming
+ * @library lib/
  * @run main RemoveNamingListenerTest
  */
 public class RemoveNamingListenerTest {
@@ -130,112 +128,26 @@
     }
 }
 
-class TestLDAPServer extends Thread {
-
-    private final int LDAP_PORT;
-    private final ServerSocket serverSocket;
-    private volatile boolean isRunning;
+class TestLDAPServer extends BaseLdapServer {
 
-    TestLDAPServer() throws IOException {
-        serverSocket = new ServerSocket(0);
-        isRunning = true;
-        LDAP_PORT = serverSocket.getLocalPort();
-        setDaemon(true);
-    }
-
-    public int getPort() {
-        return LDAP_PORT;
-    }
+    private byte[] bindResponse = {0x30, 0x0C, 0x02, 0x01, 0x01, 0x61, 0x07, 0x0A, 0x01, 0x00, 0x04, 0x00, 0x04, 0x00};
+    private byte[] searchResponse = {0x30, 0x0C, 0x02, 0x01, 0x02, 0x65, 0x07, 0x0A, 0x01, 0x00, 0x04, 0x00, 0x04, 0x00};
 
-    public void stopServer() {
-        isRunning = false;
-        if (serverSocket != null && !serverSocket.isClosed()) {
-            try {
-                // this will cause ServerSocket.accept() to throw SocketException.
-                serverSocket.close();
-            } catch (IOException ignored) {
+    public TestLDAPServer() throws IOException {
+        super(new ServerSocket(0, 0, InetAddress.getLoopbackAddress()), true);
+        setCommonRequestHandler((msg, out) -> {
+            switch (msg.getOperation()) {
+                case BIND_REQUEST:
+                    // Write an LDAP BindResponse
+                    out.write(bindResponse);
+                    break;
+                case SEARCH_REQUEST:
+                    // Write an LDAP SearchResponse
+                    out.write(searchResponse);
+                    break;
+                default:
+                    break;
             }
-        }
-    }
-
-    @Override
-    public void run() {
-        try {
-            while (isRunning) {
-                Socket clientSocket = serverSocket.accept();
-                Thread handler = new Thread(new LDAPServerHandler(clientSocket));
-                handler.setDaemon(true);
-                handler.start();
-            }
-        } catch (IOException iOException) {
-            //do not throw exception if server is not running.
-            if (isRunning) {
-                throw new RuntimeException(iOException);
-            }
-        } finally {
-            stopServer();
-        }
+        });
     }
 }
-
-class LDAPServerHandler implements Runnable {
-
-    private final Socket clientSocket;
-
-    public LDAPServerHandler(final Socket clientSocket) {
-        this.clientSocket = clientSocket;
-    }
-
-    @Override
-    public void run() {
-        BufferedInputStream in = null;
-        PrintWriter out = null;
-        byte[] bindResponse = {0x30, 0x0C, 0x02, 0x01, 0x01, 0x61, 0x07, 0x0A, 0x01, 0x00, 0x04, 0x00, 0x04, 0x00};
-        byte[] searchResponse = {0x30, 0x0C, 0x02, 0x01, 0x02, 0x65, 0x07, 0x0A, 0x01, 0x00, 0x04, 0x00, 0x04, 0x00};
-        try {
-            in = new BufferedInputStream(clientSocket.getInputStream());
-            out = new PrintWriter(new OutputStreamWriter(
-                    clientSocket.getOutputStream(), StandardCharsets.UTF_8), true);
-            while (true) {
-
-                // Read the LDAP BindRequest
-                while (in.read() != -1) {
-                    in.skip(in.available());
-                    break;
-                }
-
-                // Write an LDAP BindResponse
-                out.write(new String(bindResponse));
-                out.flush();
-
-                // Read the LDAP SearchRequest
-                while (in.read() != -1) {
-                    in.skip(in.available());
-                    break;
-                }
-
-                // Write an LDAP SearchResponse
-                out.write(new String(searchResponse));
-                out.flush();
-            }
-        } catch (IOException iOException) {
-            throw new RuntimeException(iOException);
-        } finally {
-            if (in != null) {
-                try {
-                    in.close();
-                } catch (IOException ignored) {
-                }
-            }
-            if (out != null) {
-                out.close();
-            }
-            if (clientSocket != null) {
-                try {
-                    clientSocket.close();
-                } catch (IOException ignored) {
-                }
-            }
-        }
-    }
-}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/com/sun/jndi/ldap/lib/BaseLdapServer.java	Wed May 01 00:06:22 2019 -0700
@@ -0,0 +1,510 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Stream;
+
+import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;
+
+/**
+ * A base dummy ldap server.
+ *
+ * For any ldap tests which required a simple dummy server to support the
+ * test, may use this server directly with specifying ConnectionHandler,
+ * SessionHandler/RequestHandler, or extends to build more complex server logic.
+ *
+ * This server already extends Thread and implements AutoCloseable, so it can
+ * be started in thread and integrated with try-with-resources
+ *
+ * To initiate a instance of this server, valid ServerSocket could be supplied,
+ * it will allow the flexibility for listening address/port customization
+ * and SSL usage, for default no parameter constructor, a ServerSocket which
+ * listen on loopback address will be created.
+ *
+ * To use this dummy server in test, user could customize the processing logic
+ * in three level with below handler interface.
+ *   -ConnectionHandler provide connection level handling, server will hand
+ *                      over accepted socket and processing thread to handler.
+ *                      By default, DefaultConnectionHandler will be used if no
+ *                      specified, it reads full ldap request message then
+ *                      pass it to RequestHandler instance which returned by
+ *                      SessionHandler per session.
+ *
+ *   -SessionHandler    provide session level handling when DefaultConnectionHandler
+ *                      been used, it's to retrieve RequestHandler instance of
+ *                      current session.
+ *                      For most of tests, only one session need to be handled
+ *                      on server or all ldap request could be handled by same
+ *                      logic whatever current session is, user can use
+ *                      setCommonRequestHandler to setup one single session
+ *                      handler which will always return given RequestHandler
+ *                      instance.
+ *
+ *   -RequestHandler    provide ldap message request handling when
+ *                      DefaultConnectionHandler been used.
+ *
+ * @see ConnectionHandler
+ * @see SessionHandler
+ * @see RequestHandler
+ */
+public class BaseLdapServer extends Thread implements AutoCloseable {
+    private volatile boolean isRunning;
+    private final List<Socket> socketList = new ArrayList<>();
+    private ServerSocket serverSocket;
+    private ExecutorService workingPool;
+    private ConnectionHandler connectionHandler;
+    private SessionHandler sessionHandler;
+    private boolean useDaemonThread = false;
+
+    enum DebugLevel {
+        FULL,      // all debug message will be printed
+        NONE,      // none of debug message will be printed
+        CUSTOMIZE  // only specified class debug message will be printed
+    }
+
+    private StackWalker stackWalker = null;
+    private DebugLevel debugLevel = DebugLevel.NONE;
+    private Set<Class<?>> debugOptions = new HashSet<>();
+
+    /**
+     * BaseLdapServer overload default constructor.
+     *
+     * @throws IOException if an I/O error occurs when opening the socket.
+     */
+    public BaseLdapServer() throws IOException {
+        this(new ServerSocket(0, 0, InetAddress.getLoopbackAddress()));
+    }
+
+    /**
+     * BaseLdapServer overload constructor with given server socket.
+     *
+     * @param serverSocket given server socket
+     */
+    public BaseLdapServer(ServerSocket serverSocket) {
+        this(serverSocket, false);
+    }
+
+    /**
+     * BaseLdapServer constructor with given server socket and specify whether
+     * use daemon for each accept connection handling thread.
+     *
+     * @param serverSocket    given server socket
+     * @param useDaemonThread <tt>true</tt> if use daemon thread
+     */
+    public BaseLdapServer(ServerSocket serverSocket, boolean useDaemonThread) {
+        this.serverSocket = Objects.requireNonNull(serverSocket);
+        this.useDaemonThread = useDaemonThread;
+        if (useDaemonThread) {
+            workingPool = Executors
+                    .newCachedThreadPool(new DefaultDaemonThreadFactory());
+        } else {
+            workingPool = Executors.newCachedThreadPool();
+        }
+        try {
+            stackWalker = StackWalker.getInstance(RETAIN_CLASS_REFERENCE);
+        } catch (SecurityException se) {
+            // just ignore
+        }
+    }
+
+    @Override
+    public void run() {
+        if (getConnectionHandler() == null) {
+            debug("INFO: No connection handler been specified, try default.");
+            connectionHandler = new DefaultConnectionHandler();
+        }
+        debug("INFO: Using connection handler : " + getConnectionHandler()
+                .getClass().getName());
+        debug("INFO: LdapServer running and listening on port " + getPort());
+        try {
+            while (isRunning) {
+                Socket socket = serverSocket.accept();
+                debug("INFO: Accept new connection " + socket);
+                synchronized (socketList) {
+                    socketList.add(socket);
+                }
+                workingPool.submit(() -> getConnectionHandler()
+                        .handleConnection(socket));
+            }
+        } catch (IOException | RejectedExecutionException e) {
+            if (isRunning) {
+                throw new RuntimeException(e);
+            } else {
+                debug("INFO: Server exit.");
+            }
+        }
+    }
+
+    /*
+     * Override Thread.start()
+     */
+    @Override
+    public synchronized void start() {
+        super.start();
+        isRunning = true;
+    }
+
+    /**
+     * Start Server thread and return itself for method chaining
+     *
+     * @return current server instance
+     */
+    @SuppressWarnings("unchecked")
+    public <T extends BaseLdapServer> T startServer() {
+        start();
+        return (T) this;
+    }
+
+    /**
+     * Stop server.
+     */
+    public void stopServer() {
+        debug("INFO: Stopping Server.");
+        isRunning = false;
+        workingPool.shutdown();
+        cleanupClosableRes(serverSocket);
+        if (!useDaemonThread) {
+            // let's cleanup thread pool
+            synchronized (socketList) {
+                socketList.forEach(BaseLdapServer::cleanupClosableRes);
+            }
+            try {
+                if (!workingPool.awaitTermination(10, TimeUnit.SECONDS)) {
+                    workingPool.shutdownNow();
+                }
+            } catch (InterruptedException e) {
+                workingPool.shutdownNow();
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    /**
+     * Return local port which server is listening.
+     *
+     * @return port which server is listening
+     */
+    public int getPort() {
+        if (serverSocket != null) {
+            return serverSocket.getLocalPort();
+        } else {
+            return -1;
+        }
+    }
+
+    /**
+     * Return flag to indicate whether current server is running.
+     *
+     * @return <tt>true</tt> if current server is running.
+     */
+    public boolean isRunning() {
+        return isRunning;
+    }
+
+    /**
+     * Return ConnectionHandler instance
+     *
+     * @return ConnectionHandler instance
+     * @see ConnectionHandler
+     */
+    ConnectionHandler getConnectionHandler() {
+        return connectionHandler;
+    }
+
+    /**
+     * Set ConnectionHandler when server is not running.
+     *
+     * @param connHandler ConnectionHandler instance
+     * @return current server instance for method chaining
+     */
+    @SuppressWarnings("unchecked")
+    public <T extends BaseLdapServer> T setConnectionHandler(
+            ConnectionHandler connHandler) {
+        if (!isRunning) {
+            connectionHandler = connHandler;
+        }
+
+        return (T) this;
+    }
+
+    /**
+     * Return SessionHandler instance
+     *
+     * @return SessionHandler instance
+     * @see SessionHandler
+     */
+    SessionHandler getSessionHandler() {
+        return sessionHandler;
+    }
+
+    /**
+     * Set SessionHandler when server is not running.
+     *
+     * @param sessionHandler given SessionHandler
+     * @return current server instance for method chaining
+     */
+    @SuppressWarnings("unchecked")
+    public <T extends BaseLdapServer> T setSessionHandler(
+            SessionHandler sessionHandler) {
+        if (!isRunning) {
+            this.sessionHandler = sessionHandler;
+        }
+
+        return (T) this;
+    }
+
+    /**
+     * Set one common RequestHandler, it will be used to handle all requests
+     * whatever current session is.
+     *
+     * For most of tests, server only need to handle one session, use this
+     * method will create stateless session handler with given request handler.
+     *
+     * @param requestHandler RequestHandler instance
+     * @return current server instance for method chaining
+     */
+    @SuppressWarnings("unchecked")
+    public <T extends BaseLdapServer> T setCommonRequestHandler(
+            RequestHandler requestHandler) {
+        if (!isRunning) {
+            // ignore any session, always return fixed request handler
+            setSessionHandler(socket -> requestHandler);
+        }
+
+        return (T) this;
+    }
+
+    @Override
+    public void close() {
+        stopServer();
+    }
+
+    /**
+     * Cleanup any given closable resource
+     *
+     * @param res given closable resource
+     */
+    static void cleanupClosableRes(Closeable res) {
+        if (res != null) {
+            try {
+                res.close();
+            } catch (IOException e) {
+                // ignore
+            }
+        }
+    }
+
+    /**
+     * Set debug level to specify which kinds of debug message will be printed.
+     *
+     * @param debugLevel given debug level
+     * @param opts       given opts if debug level is DebugLevel.CUSTOMIZE
+     * @return current server instance for method chaining
+     */
+    @SuppressWarnings("unchecked")
+    public <T extends BaseLdapServer> T setDebugLevel(DebugLevel debugLevel,
+            Class<?>... opts) {
+        Objects.requireNonNull(debugLevel);
+        if (!isRunning) {
+            this.debugLevel = debugLevel;
+            if (debugLevel == DebugLevel.CUSTOMIZE) {
+                debugOptions.clear();
+                Stream.of(opts).filter(Objects::nonNull)
+                        .forEach(debugOptions::add);
+            }
+        }
+
+        return (T) this;
+    }
+
+    /**
+     * Print given message if debug enabled.
+     *
+     * @param message given message to print
+     */
+    void debug(String message) {
+        switch (debugLevel) {
+            case FULL:
+                System.out.println((stackWalker != null ?
+                        stackWalker.getCallerClass().getName() :
+                        "") + ": " + message);
+                break;
+            case CUSTOMIZE:
+                if (stackWalker != null) {
+                    if (debugOptions.contains(stackWalker.getCallerClass())) {
+                        System.out.println(
+                                stackWalker.getCallerClass().getName() + ": "
+                                        + message);
+                    }
+                }
+                break;
+            case NONE:
+            default:
+                break;
+        }
+    }
+
+    class DefaultDaemonThreadFactory implements ThreadFactory {
+
+        private ThreadFactory defaultThreadFactory = Executors
+                .defaultThreadFactory();
+
+        @Override
+        public Thread newThread(Runnable r) {
+            Thread thread = defaultThreadFactory.newThread(r);
+            thread.setDaemon(true);
+            return thread;
+        }
+    }
+
+    /**
+     * Default connection handler implementation.
+     */
+    class DefaultConnectionHandler implements ConnectionHandler {
+        @Override
+        public void handleConnection(Socket socket) {
+            try (socket;
+                    OutputStream out = socket.getOutputStream();
+                    InputStream in = socket.getInputStream()) {
+                byte[] inBuffer = new byte[1024];
+                int count;
+                byte[] request;
+
+                ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+                int msgLen = -1;
+
+                while ((count = in.read(inBuffer)) > 0) {
+                    buffer.write(inBuffer, 0, count);
+                    if (msgLen <= 0) {
+                        msgLen = getMessageLength(buffer.toByteArray());
+                    }
+
+                    if (msgLen > 0 && buffer.size() >= msgLen) {
+                        if (buffer.size() > msgLen) {
+                            byte[] tmpBuffer = buffer.toByteArray();
+                            request = Arrays.copyOf(tmpBuffer, msgLen);
+                            buffer.reset();
+                            buffer.write(tmpBuffer, msgLen,
+                                    tmpBuffer.length - msgLen);
+                        } else {
+                            request = buffer.toByteArray();
+                            buffer.reset();
+                        }
+                        msgLen = -1;
+                    } else {
+                        debug("INFO: request msg not complete, received "
+                                + buffer.size() + ", expected " + msgLen);
+                        continue;
+                    }
+
+                    if (getSessionHandler() != null) {
+                        var handler = getSessionHandler()
+                                .getRequestHandler(socket);
+                        if (handler != null) {
+                            debug("INFO: Process request. Session handler : "
+                                    + getSessionHandler()
+                                    + ", Request handler : " + handler);
+                            handler.handleRequest(new LdapMessage(request),
+                                    out);
+                        } else {
+                            debug("WARNING: no valid request handler returned from "
+                                    + getSessionHandler() + ", " + socket);
+                        }
+                    } else {
+                        debug("WARNING: no valid session handler been specified, discard request.");
+                    }
+                }
+                debug("INFO: Connection Handler exit.");
+            } catch (IOException e) {
+                if (!isRunning()) {
+                    debug("INFO: Connection Handler exit : " + e.getMessage());
+                } else {
+                    e.printStackTrace();
+                }
+            }
+        }
+
+        private int getMessageLength(byte[] encoding) {
+            if (encoding.length < 2) {
+                // no enough data to extract msg len, just return -1
+                return -1;
+            }
+
+            if (encoding[0] != 0x30) {
+                throw new RuntimeException("Error: bad LDAP encoding message: "
+                        + "expected ASN.1 SEQUENCE tag (0x30), encountered "
+                        + encoding[0]);
+            }
+
+            int len;
+            int index = 1;
+            int payloadLen = 0;
+
+            if ((encoding[1] & 0x80) == 0x80) {
+                len = (encoding[1] & 0x0F);
+                index++;
+            } else {
+                len = 1;
+            }
+
+            if (len > 4) {
+                throw new RuntimeException(
+                        "Error: LDAP encoding message payload too large");
+            }
+
+            if (encoding.length < index + len) {
+                // additional data required to extract payload len, return -1
+                return -1;
+            }
+
+            for (byte b : Arrays.copyOfRange(encoding, index, index + len)) {
+                payloadLen = payloadLen << 8 | (b & 0xFF);
+            }
+
+            if (payloadLen <= 0) {
+                throw new RuntimeException(
+                        "Error: invalid LDAP encoding message length or payload too large");
+            }
+
+            return index + len + payloadLen;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/com/sun/jndi/ldap/lib/ConnectionHandler.java	Wed May 01 00:06:22 2019 -0700
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.net.Socket;
+
+/**
+ * Interface for Connection Handler.
+ */
+public interface ConnectionHandler {
+    /**
+     * Handle given socket connection.
+     *
+     * @param socket given socket connection
+     */
+    void handleConnection(Socket socket);
+}
--- a/test/jdk/com/sun/jndi/ldap/lib/LDAPTestUtils.java	Tue Apr 30 12:52:23 2019 +0100
+++ b/test/jdk/com/sun/jndi/ldap/lib/LDAPTestUtils.java	Wed May 01 00:06:22 2019 -0700
@@ -259,14 +259,7 @@
                             + "cache file " + fileName);
         }
 
-        Thread thread = new Thread(() -> {
-            try {
-                new test.LDAPServer(serverSocket, fileName);
-            } catch (Exception e) {
-                System.out.println("Warning: LDAP server running with issue");
-                e.printStackTrace();
-            }
-        });
+        Thread thread = new LdapPlaybackServer(serverSocket, fileName);
 
         thread.start();
         return thread;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/com/sun/jndi/ldap/lib/LdapMessage.java	Wed May 01 00:06:22 2019 -0700
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+/**
+ * Class to present one Ldap message.
+ */
+public class LdapMessage {
+    private final byte[] messages;
+    private int messageID;
+    private Operation operation;
+
+    public enum Operation {
+        BIND_REQUEST(0x60, "BindRequest"), // [APPLICATION 0]
+        BIND_RESPONSE(0x61, "BindResponse"), // [APPLICATION 1]
+        UNBIND_REQUEST(0x42, "UnbindRequest"), // [APPLICATION 2]
+        SEARCH_REQUEST(0x63, "SearchRequest"), // [APPLICATION 3]
+        SEARCH_RESULT_ENTRY(0x64, "SearchResultEntry"), // [APPLICATION 4]
+        SEARCH_RESULT_DONE(0x65, "SearchResultDone"), // [APPLICATION 5]
+        MODIFY_REQUEST(0x66, "ModifyRequest"), // [APPLICATION 6]
+        MODIFY_RESPONSE(0x67, "ModifyResponse"), // [APPLICATION 7]
+        ADD_REQUEST(0x68, "AddRequest"), // [APPLICATION 8]
+        ADD_RESPONSE(0x69, "AddResponse"), // [APPLICATION 9]
+        DELETE_REQUEST(0x4A, "DeleteRequest"), // [APPLICATION 10]
+        DELETE_RESPONSE(0x6B, "DeleteResponse"), // [APPLICATION 11]
+        MODIFY_DN_REQUEST(0x6C, "ModifyDNRequest"), // [APPLICATION 12]
+        MODIFY_DN_RESPONSE(0x6D, "ModifyDNResponse"), // [APPLICATION 13]
+        COMPARE_REQUEST(0x6E, "CompareRequest"), // [APPLICATION 14]
+        COMPARE_RESPONSE(0x6F, "CompareResponse"), // [APPLICATION 15]
+        ABANDON_REQUEST(0x50, "AbandonRequest"), // [APPLICATION 16]
+        SEARCH_RESULT_REFERENCE(0x73,
+                "SearchResultReference"), // [APPLICATION 19]
+        EXTENDED_REQUEST(0x77, "ExtendedRequest"), // [APPLICATION 23]
+        EXTENDED_RESPONSE(0x78, "ExtendedResponse"), // [APPLICATION 24]
+        INTERMEDIATE_RESPONSE(0x79, "IntermediateResponse"); // [APPLICATION 25]
+
+        private final int id;
+        private final String name;
+
+        Operation(int id, String name) {
+            this.id = id;
+            this.name = name;
+        }
+
+        public int getId() {
+            return id;
+        }
+
+        @Override
+        public String toString() {
+            return name;
+        }
+
+        public static Operation fromId(int id) {
+            Optional<Operation> optional = Stream.of(Operation.values())
+                    .filter(o -> o.id == id).findFirst();
+            if (optional.isPresent()) {
+                return optional.get();
+            } else {
+                throw new RuntimeException(
+                        "Unknown id " + id + " for enum Operation.");
+            }
+        }
+    }
+
+    public LdapMessage(byte[] messages) {
+        this.messages = messages;
+        parse();
+    }
+
+    public LdapMessage(String hexString) {
+        this(parseHexBinary(hexString));
+    }
+
+    // Extracts the message ID and operation ID from an LDAP protocol encoding
+    private void parse() {
+        if (messages == null || messages.length < 2) {
+            throw new RuntimeException(
+                    "Invalid ldap messages: " + Arrays.toString(messages));
+        }
+
+        if (messages[0] != 0x30) {
+            throw new RuntimeException("Bad LDAP encoding in messages, "
+                    + "expected ASN.1 SEQUENCE tag (0x30), encountered "
+                    + messages[0]);
+        }
+
+        int index = 2;
+        if ((messages[1] & 0x80) == 0x80) {
+            index += (messages[1] & 0x0F);
+        }
+
+        if (messages[index] != 0x02) {
+            throw new RuntimeException("Bad LDAP encoding in messages, "
+                    + "expected ASN.1 INTEGER tag (0x02), encountered "
+                    + messages[index]);
+        }
+        int length = messages[index + 1];
+        index += 2;
+        messageID = new BigInteger(1,
+                Arrays.copyOfRange(messages, index, index + length)).intValue();
+        index += length;
+        int operationID = messages[index];
+        operation = Operation.fromId(operationID);
+    }
+
+    /**
+     * Return original ldap message in byte array.
+     *
+     * @return original ldap message
+     */
+    public byte[] getMessages() {
+        return Arrays.copyOf(messages, messages.length);
+    }
+
+    /**
+     * Return ldap message id.
+     *
+     * @return ldap message id.
+     */
+    public int getMessageID() {
+        return messageID;
+    }
+
+    /**
+     * Return ldap message's operation.
+     *
+     * @return ldap message's operation.
+     */
+    public Operation getOperation() {
+        return operation;
+    }
+
+    private static byte[] parseHexBinary(String s) {
+
+        final int len = s.length();
+
+        // "111" is not a valid hex encoding.
+        if (len % 2 != 0) {
+            throw new IllegalArgumentException(
+                    "hexBinary needs to be even-length: " + s);
+        }
+
+        byte[] out = new byte[len / 2];
+
+        for (int i = 0; i < len; i += 2) {
+            int h = hexToBin(s.charAt(i));
+            int l = hexToBin(s.charAt(i + 1));
+            if (h == -1 || l == -1) {
+                throw new IllegalArgumentException(
+                        "contains illegal character for hexBinary: " + s);
+            }
+
+            out[i / 2] = (byte) (h * 16 + l);
+        }
+
+        return out;
+    }
+
+    private static int hexToBin(char ch) {
+        if ('0' <= ch && ch <= '9') {
+            return ch - '0';
+        }
+        if ('A' <= ch && ch <= 'F') {
+            return ch - 'A' + 10;
+        }
+        if ('a' <= ch && ch <= 'f') {
+            return ch - 'a' + 10;
+        }
+        return -1;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/com/sun/jndi/ldap/lib/LdapPlaybackServer.java	Wed May 01 00:06:22 2019 -0700
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.ServerSocket;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Scanner;
+import java.util.regex.MatchResult;
+
+/*
+ * An enhanced dummy LDAP server which can playback captured LDAP messages.
+ *
+ * Loads a sequence of LDAP messages from a capture file into its cache.
+ * It listens for LDAP requests, finds a match in its cache and sends the
+ * corresponding LDAP responses.
+ *
+ * The capture file contains an LDAP protocol exchange in the hexadecimal
+ * dump format emitted by sun.misc.HexDumpEncoder:
+ *
+ * xxxx: 00 11 22 33 44 55 66 77   88 99 aa bb cc dd ee ff  ................
+ *
+ * Typically, LDAP protocol exchange is generated by running the LDAP client
+ * application program against a real LDAP server and setting the JNDI/LDAP
+ * environment property: com.sun.jndi.ldap.trace.ber to activate LDAP message
+ * tracing.
+ */
+public class LdapPlaybackServer extends BaseLdapServer {
+
+    /*
+     * A cache of LDAP requests and responses.
+     * Messages with the same ID are stored in a list.
+     * The first element in the list is the LDAP request,
+     * the remaining elements are the LDAP responses.
+     */
+    private final Map<Integer, List<byte[]>> cache = new HashMap<>();
+
+    private String fileName;
+
+    public LdapPlaybackServer(ServerSocket serverSocket, String fileName) {
+        super(serverSocket);
+        this.fileName = fileName;
+        setDebugLevel(DebugLevel.CUSTOMIZE, this.getClass());
+        setCommonRequestHandler(this::handleRequest);
+    }
+
+    /*
+     * Load a capture file containing an LDAP protocol exchange in the
+     * hexadecimal dump format emitted by sun.misc.HexDumpEncoder:
+     *
+     * xxxx: 00 11 22 33 44 55 66 77   88 99 aa bb cc dd ee ff  ................
+     */
+    private void loadCaptureFile(String filename) throws IOException {
+        StringBuilder hexString = new StringBuilder();
+        String pattern = "(....): (..) (..) (..) (..) (..) (..) (..) (..)   (..) (..) (..) (..) (..) (..) (..) (..).*";
+        String preLineNum = "";
+
+        try (Scanner fileScanner = new Scanner(Paths.get(filename))) {
+            while (fileScanner.hasNextLine()) {
+
+                try (Scanner lineScanner = new Scanner(
+                        fileScanner.nextLine())) {
+                    if (lineScanner.findInLine(pattern) == null) {
+                        preLineNum = "";
+                        continue;
+                    }
+                    MatchResult result = lineScanner.match();
+                    for (int i = 1; i <= result.groupCount(); i++) {
+                        String digits = result.group(i);
+                        if (digits.length() == 4) {
+                            if (digits.equals("0000") && !preLineNum
+                                    .equalsIgnoreCase(
+                                            "FFF0")) { // start-of-message
+                                if (hexString.length() > 0) {
+                                    addToCache(hexString.toString());
+                                    hexString = new StringBuilder();
+                                }
+                            }
+                            preLineNum = digits;
+                            continue;
+                        } else if (digits.equals("  ")) { // short message
+                            continue;
+                        }
+                        hexString.append(digits);
+                    }
+                }
+            }
+        }
+        if (!hexString.toString().isEmpty()) {
+            addToCache(hexString.toString());
+        }
+    }
+
+    /*
+     * Add an LDAP encoding to the cache (by messageID key).
+     */
+    private void addToCache(String hexString) {
+        LdapMessage message = new LdapMessage(hexString);
+        byte[] encoding = message.getMessages();
+        int messageID = message.getMessageID();
+        List<byte[]> encodings = cache.get(messageID);
+        if (encodings == null) {
+            encodings = new ArrayList<>();
+        }
+        debug("    adding LDAP " + message.getOperation() + " with message ID "
+                + messageID + " to the cache");
+        encodings.add(encoding);
+        cache.put(messageID, encodings);
+    }
+
+    @Override
+    public void stopServer() {
+        debug("force stopping server");
+        super.stopServer();
+    }
+
+    private void handleRequest(LdapMessage request, OutputStream out)
+            throws IOException {
+        int messageID = request.getMessageID();
+        debug("received LDAP " + request.getOperation() + "  [message ID "
+                + messageID + "]");
+
+        List<byte[]> encodings = cache.get(messageID);
+        if (encodings == null || (!Arrays
+                .equals(request.getMessages(), encodings.get(0)))) {
+            throw new RuntimeException(
+                    "LDAPServer: ERROR: received an LDAP " + request
+                            .getOperation() + " (ID=" + messageID
+                            + ") not present in cache");
+        }
+
+        for (int i = 1; i < encodings.size(); i++) {
+            // skip the request (at index 0)
+            byte[] response = encodings.get(i);
+            out.write(response, 0, response.length);
+            LdapMessage responseMsg = new LdapMessage(response);
+            debug("Sent LDAP " + responseMsg.getOperation() + "  [message ID "
+                    + responseMsg.getMessageID() + "]");
+        }
+    }
+
+    @Override
+    public void run() {
+        try {
+            debug("Loading LDAP cache from: " + fileName);
+            loadCaptureFile(fileName);
+            debug("listening on port " + getPort());
+
+            super.run();
+        } catch (Exception e) {
+            debug("ERROR: " + e);
+            e.printStackTrace();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/com/sun/jndi/ldap/lib/RequestHandler.java	Wed May 01 00:06:22 2019 -0700
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Interface for ldap request handler.
+ */
+public interface RequestHandler {
+    /**
+     * Handle given ldap request message.
+     *
+     * @param request given ldap request message
+     * @param out     given OutputStream which could be used to write response
+     * @throws IOException if an I/O error occurs when using OutputStream
+     */
+    void handleRequest(LdapMessage request, OutputStream out)
+            throws IOException;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/com/sun/jndi/ldap/lib/SessionHandler.java	Wed May 01 00:06:22 2019 -0700
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.net.Socket;
+
+/**
+ * Interface for ldap session handler.
+ */
+public interface SessionHandler {
+    /**
+     * Return request handler instance per session.
+     *
+     * @param socket given socket to identify current session
+     * @return request handler instance per session
+     */
+    RequestHandler getRequestHandler(Socket socket);
+}