1 /* 2 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 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 140 // As inBuffer.length > 0, at least 1 byte is read 141 while ((count = in.read(inBuffer)) > 0) { 142 buffer.write(inBuffer, 0, count); 143 if (msgLen <= 0) { 144 msgLen = LdapMessage.getMessageLength(buffer.toByteArray()); 145 } 146 147 if (msgLen > 0 && buffer.size() >= msgLen) { 148 if (buffer.size() > msgLen) { 149 byte[] tmpBuffer = buffer.toByteArray(); 150 request = Arrays.copyOf(tmpBuffer, msgLen); 151 buffer.reset(); 152 buffer.write(tmpBuffer, msgLen, tmpBuffer.length - msgLen); 153 } else { 154 request = buffer.toByteArray(); 155 buffer.reset(); 156 } 157 msgLen = -1; 158 } else { 159 logger.log(INFO, "Request message incomplete, " + 160 "bytes received {0}, expected {1}", buffer.size(), msgLen); 161 continue; 162 } 163 handleRequest(socket, new LdapMessage(request), out); 164 } 165 } catch (Throwable t) { 166 if (!isRunning()) { 167 logger.log(INFO, "Connection Handler exit {0}", t.getMessage()); 168 } else { 169 t.printStackTrace(); 170 } 171 } 172 } 173 174 /* 175 * Called first thing in `handleConnection()`. 176 * 177 * Override to customize the behavior. 178 */ 179 protected void beforeConnectionHandled(Socket socket) { /* empty */ } 180 181 /* 182 * Called after an LDAP request has been read in `handleConnection()`. 183 * 184 * Override to customize the behavior. 185 */ 186 protected void handleRequest(Socket socket, 187 LdapMessage request, 188 OutputStream out) 189 throws IOException 190 { 191 logger().log(INFO, "Discarding message {0} from {1}. " 192 + "Override {2}.handleRequest to change this behavior.", 193 request, socket, getClass().getName()); 194 } 195 196 /* 197 * To be used by subclasses. 198 */ 199 protected final System.Logger logger() { 200 return logger; 201 } 202 203 /* 204 * Starts this server. May be called only once. 205 */ 206 public BaseLdapServer start() { 207 synchronized (lock) { 208 if (state != State.NEW) { 209 throw new IllegalStateException(state.toString()); 210 } 211 state = State.STARTED; 212 logger().log(INFO, "Starting server at port {0}", getPort()); 213 acceptingThread.start(); 214 return this; 215 } 216 } 217 218 /* 219 * Stops this server. 220 * 221 * May be called at any time, even before a call to `start()`. In the latter 222 * case the subsequent call to `start()` will throw an exception. Repeated 223 * calls to this method have no effect. 224 * 225 * Stops accepting new connections, interrupts the threads serving already 226 * accepted connections and closes all the sockets. 227 */ 228 @Override 229 public void close() { 230 synchronized (lock) { 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 }