18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import java.io.ByteArrayOutputStream; 25 import java.io.Closeable; 26 import java.io.IOException; 27 import java.io.InputStream; 28 import java.io.OutputStream; 29 import java.net.InetAddress; 30 import java.net.ServerSocket; 31 import java.net.Socket; 32 import java.util.ArrayList; 33 import java.util.Arrays; 34 import java.util.List; 35 import java.util.Objects; 36 import java.util.concurrent.ExecutorService; 37 import java.util.concurrent.Executors; 38 import java.util.concurrent.RejectedExecutionException; 39 40 import static java.lang.System.Logger.Level.INFO; 41 42 /* 43 * A bare-bones (testing aid) server for LDAP scenarios. 44 * 45 * Override the following methods to provide customized behavior 46 * 47 * * beforeConnectionHandled 48 * * handleRequest 49 * 50 * Instances of this class are safe for use by multiple threads. 51 */ 52 public class BaseLdapServer implements Closeable { 53 54 private static final System.Logger logger = System.getLogger("BaseLdapServer"); 55 56 private final Thread acceptingThread = new Thread(this::acceptConnections); 57 private final ServerSocket serverSocket; 58 private final List<Socket> socketList = new ArrayList<>(); 59 private final ExecutorService connectionsPool; 60 61 private final Object lock = new Object(); 62 /* 63 * 3-valued state to detect restarts and other programming errors. 64 */ 65 private State state = State.NEW; 66 67 private enum State { 68 NEW, 69 STARTED, 70 STOPPED 71 } 72 73 public BaseLdapServer() throws IOException { 74 this(new ServerSocket(0, 0, InetAddress.getLoopbackAddress())); 75 } 76 77 public BaseLdapServer(ServerSocket serverSocket) { 78 this.serverSocket = Objects.requireNonNull(serverSocket); 79 this.connectionsPool = Executors.newCachedThreadPool(); 80 } 81 82 private void acceptConnections() { 83 logger().log(INFO, "Server is accepting connections at port {0}", 84 getPort()); 85 try { 86 while (isRunning()) { 87 Socket socket = serverSocket.accept(); 88 logger().log(INFO, "Accepted new connection at {0}", socket); 89 synchronized (lock) { 90 // Recheck if the server is still running 91 // as someone has to close the `socket` 92 if (isRunning()) { 93 socketList.add(socket); 94 } else { 95 closeSilently(socket); 96 } 97 } 98 connectionsPool.submit(() -> handleConnection(socket)); 99 } 100 } catch (IOException | RejectedExecutionException e) { 101 if (isRunning()) { 102 throw new RuntimeException( 103 "Unexpected exception while accepting connections", e); 104 } 105 } finally { 106 logger().log(INFO, "Server stopped accepting connections at port {0}", 107 getPort()); 108 } 109 } 110 111 /* 112 * A "Template Method" describing how a connection (represented by a socket) 113 * is handled. 114 * 115 * The socket is closed immediately before the method returns (normally or 116 * abruptly). 117 */ 118 private void handleConnection(Socket socket) { 119 // No need to close socket's streams separately, they will be closed 120 // automatically when `socket.close()` is called 121 beforeConnectionHandled(socket); 122 try (socket) { 123 OutputStream out = socket.getOutputStream(); 124 InputStream in = socket.getInputStream(); 125 byte[] inBuffer = new byte[1024]; 126 int count; 127 byte[] request; 128 129 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 130 int msgLen = -1; 131 223 if (state == State.STOPPED) { 224 return; 225 } 226 state = State.STOPPED; 227 logger().log(INFO, "Stopping server at port {0}", getPort()); 228 acceptingThread.interrupt(); 229 closeSilently(serverSocket); 230 // It's important to signal an interruption so that overridden 231 // methods have a chance to return if they use 232 // interruption-sensitive blocking operations. However, blocked I/O 233 // operations on the socket will NOT react on that, hence the socket 234 // also has to be closed to propagate shutting down. 235 connectionsPool.shutdownNow(); 236 socketList.forEach(BaseLdapServer.this::closeSilently); 237 } 238 } 239 240 /** 241 * Returns the local port this server is listening at. 242 * 243 * @return the port this server is listening at 244 */ 245 public int getPort() { 246 return serverSocket.getLocalPort(); 247 } 248 249 /* 250 * Returns a flag to indicate whether this server is running or not. 251 * 252 * @return {@code true} if this server is running, {@code false} otherwise. 253 */ 254 public boolean isRunning() { 255 synchronized (lock) { 256 return state == State.STARTED; 257 } 258 } 259 260 /* 261 * To be used by subclasses. 262 */ 263 protected final void closeSilently(Closeable resource) { 264 try { 265 resource.close(); 266 } catch (IOException ignored) { } 267 } 268 } | 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import java.io.ByteArrayOutputStream; 25 import java.io.Closeable; 26 import java.io.IOException; 27 import java.io.InputStream; 28 import java.io.OutputStream; 29 import java.net.InetAddress; 30 import java.net.ServerSocket; 31 import java.net.Socket; 32 import java.util.ArrayList; 33 import java.util.Arrays; 34 import java.util.List; 35 import java.util.Objects; 36 import java.util.concurrent.ExecutorService; 37 import java.util.concurrent.Executors; 38 39 import static java.lang.System.Logger.Level.INFO; 40 41 /* 42 * A bare-bones (testing aid) server for LDAP scenarios. 43 * 44 * Override the following methods to provide customized behavior 45 * 46 * * beforeAcceptingConnections 47 * * beforeConnectionHandled 48 * * handleRequest 49 * 50 * Instances of this class are safe for use by multiple threads. 51 */ 52 public class BaseLdapServer implements Closeable { 53 54 private static final System.Logger logger = System.getLogger("BaseLdapServer"); 55 56 private final Thread acceptingThread = new Thread(this::acceptConnections); 57 private final ServerSocket serverSocket; 58 private final List<Socket> socketList = new ArrayList<>(); 59 private final ExecutorService connectionsPool; 60 61 private final Object lock = new Object(); 62 /* 63 * 3-valued state to detect restarts and other programming errors. 64 */ 65 private State state = State.NEW; 66 67 private enum State { 68 NEW, 69 STARTED, 70 STOPPED 71 } 72 73 public BaseLdapServer() throws IOException { 74 this(new ServerSocket(0, 0, InetAddress.getLoopbackAddress())); 75 } 76 77 public BaseLdapServer(ServerSocket serverSocket) { 78 this.serverSocket = Objects.requireNonNull(serverSocket); 79 this.connectionsPool = Executors.newCachedThreadPool(); 80 } 81 82 private void acceptConnections() { 83 logger().log(INFO, "Server is accepting connections at port {0}", 84 getPort()); 85 try { 86 beforeAcceptingConnections(); 87 while (isRunning()) { 88 Socket socket = serverSocket.accept(); 89 logger().log(INFO, "Accepted new connection at {0}", socket); 90 synchronized (lock) { 91 // Recheck if the server is still running 92 // as someone has to close the `socket` 93 if (isRunning()) { 94 socketList.add(socket); 95 } else { 96 closeSilently(socket); 97 } 98 } 99 connectionsPool.submit(() -> handleConnection(socket)); 100 } 101 } catch (Throwable t) { 102 if (isRunning()) { 103 throw new RuntimeException( 104 "Unexpected exception while accepting connections", t); 105 } 106 } finally { 107 logger().log(INFO, "Server stopped accepting connections at port {0}", 108 getPort()); 109 } 110 } 111 112 /* 113 * Called once immediately preceding the server accepting connections. 114 * 115 * Override to customize the behavior. 116 */ 117 protected void beforeAcceptingConnections() { } 118 119 /* 120 * A "Template Method" describing how a connection (represented by a socket) 121 * is handled. 122 * 123 * The socket is closed immediately before the method returns (normally or 124 * abruptly). 125 */ 126 private void handleConnection(Socket socket) { 127 // No need to close socket's streams separately, they will be closed 128 // automatically when `socket.close()` is called 129 beforeConnectionHandled(socket); 130 try (socket) { 131 OutputStream out = socket.getOutputStream(); 132 InputStream in = socket.getInputStream(); 133 byte[] inBuffer = new byte[1024]; 134 int count; 135 byte[] request; 136 137 ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 138 int msgLen = -1; 139 231 if (state == State.STOPPED) { 232 return; 233 } 234 state = State.STOPPED; 235 logger().log(INFO, "Stopping server at port {0}", getPort()); 236 acceptingThread.interrupt(); 237 closeSilently(serverSocket); 238 // It's important to signal an interruption so that overridden 239 // methods have a chance to return if they use 240 // interruption-sensitive blocking operations. However, blocked I/O 241 // operations on the socket will NOT react on that, hence the socket 242 // also has to be closed to propagate shutting down. 243 connectionsPool.shutdownNow(); 244 socketList.forEach(BaseLdapServer.this::closeSilently); 245 } 246 } 247 248 /** 249 * Returns the local port this server is listening at. 250 * 251 * This method can be called at any time. 252 * 253 * @return the port this server is listening at 254 */ 255 public int getPort() { 256 return serverSocket.getLocalPort(); 257 } 258 259 /** 260 * Returns the address this server is listening at. 261 * 262 * This method can be called at any time. 263 * 264 * @return the address 265 */ 266 public InetAddress getInetAddress() { 267 return serverSocket.getInetAddress(); 268 } 269 270 /* 271 * Returns a flag to indicate whether this server is running or not. 272 * 273 * @return {@code true} if this server is running, {@code false} otherwise. 274 */ 275 public boolean isRunning() { 276 synchronized (lock) { 277 return state == State.STARTED; 278 } 279 } 280 281 /* 282 * To be used by subclasses. 283 */ 284 protected final void closeSilently(Closeable resource) { 285 try { 286 resource.close(); 287 } catch (IOException ignored) { } 288 } 289 } |