001 /* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at 010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE 011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE. 012 * See the License for the specific language governing permissions 013 * and limitations under the License. 014 * 015 * When distributing Covered Code, include this CDDL HEADER in each 016 * file and include the License file at 017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable, 018 * add the following below this CDDL HEADER, with the fields enclosed 019 * by brackets "[]" replaced with your own identifying information: 020 * Portions Copyright [yyyy] [name of copyright owner] 021 * 022 * CDDL HEADER END 023 * 024 * 025 * Copyright 2006-2008 Sun Microsystems, Inc. 026 */ 027 package org.opends.server.protocols.ldap; 028 import org.opends.messages.Message; 029 030 031 032 import java.io.IOException; 033 import java.nio.channels.CancelledKeyException; 034 import java.nio.channels.SelectionKey; 035 import java.nio.channels.Selector; 036 import java.nio.channels.SocketChannel; 037 import java.util.ArrayList; 038 import java.util.Collection; 039 import java.util.Iterator; 040 import java.util.concurrent.ConcurrentLinkedQueue; 041 042 import org.opends.server.api.ConnectionSecurityProvider; 043 import org.opends.server.api.DirectoryThread; 044 import org.opends.server.api.ServerShutdownListener; 045 import org.opends.server.core.DirectoryServer; 046 import org.opends.server.types.InitializationException; 047 048 import org.opends.server.types.DebugLogLevel; 049 import static org.opends.server.loggers.debug.DebugLogger.*; 050 import org.opends.server.loggers.debug.DebugTracer; 051 import org.opends.server.loggers.ErrorLogger; 052 import static org.opends.messages.ProtocolMessages.*; 053 054 import org.opends.server.types.DisconnectReason; 055 import static org.opends.server.util.StaticUtils.*; 056 057 058 059 /** 060 * This class defines an LDAP request handler, which is associated with an LDAP 061 * connection handler and is responsible for reading and decoding any requests 062 * that LDAP clients may send to the server. Multiple request handlers may be 063 * used in conjunction with a single connection handler for better performance 064 * and scalability. 065 */ 066 public class LDAPRequestHandler 067 extends DirectoryThread 068 implements ServerShutdownListener 069 { 070 /** 071 * The tracer object for the debug logger. 072 */ 073 private static final DebugTracer TRACER = getTracer(); 074 075 076 077 078 /** 079 * The buffer size in bytes to use when reading data from a client. 080 */ 081 public static final int BUFFER_SIZE = 8192; 082 083 084 085 // Indicates whether the Directory Server is in the process of shutting down. 086 private boolean shutdownRequested; 087 088 // The queue that will be used to hold the set of pending connections that 089 // need to be registered with the selector. 090 private ConcurrentLinkedQueue<LDAPClientConnection> pendingConnections; 091 092 // The connection handler with which this request handler is associated. 093 private LDAPConnectionHandler connectionHandler; 094 095 // The selector that will be used to monitor the client connections. 096 private Selector selector; 097 098 // The name to use for this request handler. 099 private String handlerName; 100 101 // Lock for preventing concurrent updates to the select keys. 102 private final Object selectorKeyLock = new Object(); 103 104 105 106 /** 107 * Creates a new LDAP request handler that will be associated with the 108 * provided connection handler. 109 * 110 * @param connectionHandler The LDAP connection handler with which this 111 * request handler is associated. 112 * @param requestHandlerID The integer value that may be used to distingush 113 * this request handler from others associated with 114 * the same connection handler. 115 * 116 * @throws InitializationException If a problem occurs while initializing 117 * this request handler. 118 */ 119 public LDAPRequestHandler(LDAPConnectionHandler connectionHandler, 120 int requestHandlerID) 121 throws InitializationException 122 { 123 super("LDAP Request Handler " + requestHandlerID + 124 " for connection handler " + connectionHandler.toString()); 125 126 127 this.connectionHandler = connectionHandler; 128 129 handlerName = getName(); 130 pendingConnections = new ConcurrentLinkedQueue<LDAPClientConnection>(); 131 132 try 133 { 134 selector = Selector.open(); 135 } 136 catch (Exception e) 137 { 138 if (debugEnabled()) 139 { 140 TRACER.debugCaught(DebugLogLevel.ERROR, e); 141 } 142 143 Message message = ERR_LDAP_REQHANDLER_OPEN_SELECTOR_FAILED.get( 144 handlerName, String.valueOf(e)); 145 throw new InitializationException(message, e); 146 } 147 148 try 149 { 150 // Check to see if we get an error while trying to perform a select. If 151 // we do, then it's likely CR 6322825 and the server won't be able to 152 // handle LDAP requests in its current state. 153 selector.selectNow(); 154 } 155 catch (IOException ioe) 156 { 157 StackTraceElement[] stackElements = ioe.getStackTrace(); 158 if ((stackElements != null) && (stackElements.length > 0)) 159 { 160 StackTraceElement ste = stackElements[0]; 161 if (ste.getClassName().equals("sun.nio.ch.DevPollArrayWrapper") && 162 (ste.getMethodName().indexOf("poll") >= 0) && 163 ioe.getMessage().equalsIgnoreCase("Invalid argument")) 164 { 165 Message message = ERR_LDAP_REQHANDLER_DETECTED_JVM_ISSUE_CR6322825. 166 get(String.valueOf(ioe)); 167 throw new InitializationException(message, ioe); 168 } 169 } 170 } 171 } 172 173 174 175 /** 176 * Operates in a loop, waiting for client requests to arrive and ensuring that 177 * they are processed properly. 178 */ 179 public void run() 180 { 181 // Operate in a loop until the server shuts down. Each time through the 182 // loop, check for new requests, then check for new connections. 183 while (! shutdownRequested) 184 { 185 int selectedKeys = 0; 186 187 try 188 { 189 selectedKeys = selector.select(); 190 } 191 catch (Exception e) 192 { 193 if (debugEnabled()) 194 { 195 TRACER.debugCaught(DebugLogLevel.ERROR, e); 196 } 197 198 // FIXME -- Should we do something else with this? 199 } 200 201 if (selectedKeys > 0) 202 { 203 Iterator<SelectionKey> iterator = selector.selectedKeys().iterator(); 204 while (iterator.hasNext()) 205 { 206 SelectionKey key = iterator.next(); 207 208 try 209 { 210 if (key.isReadable()) 211 { 212 LDAPClientConnection clientConnection = null; 213 214 try 215 { 216 clientConnection = (LDAPClientConnection) key.attachment(); 217 218 try 219 { 220 ConnectionSecurityProvider securityProvider = 221 clientConnection.getConnectionSecurityProvider(); 222 if (! securityProvider.readData()) 223 { 224 key.cancel(); 225 } 226 } 227 catch (Exception e) 228 { 229 if (debugEnabled()) 230 { 231 TRACER.debugCaught(DebugLogLevel.ERROR, e); 232 } 233 234 // Some other error occurred while we were trying to read data 235 // from the client. 236 // FIXME -- Should we log this? 237 key.cancel(); 238 clientConnection.disconnect(DisconnectReason.SERVER_ERROR, 239 false, null); 240 } 241 } 242 catch (Exception e) 243 { 244 if (debugEnabled()) 245 { 246 TRACER.debugCaught(DebugLogLevel.ERROR, e); 247 } 248 249 // We got some other kind of error. If nothing else, cancel the 250 // key, but if the client connection is available then 251 // disconnect it as well. 252 key.cancel(); 253 254 if (clientConnection != null) 255 { 256 clientConnection.disconnect(DisconnectReason.SERVER_ERROR, 257 false, null); 258 } 259 } 260 } 261 else if (! key.isValid()) 262 { 263 key.cancel(); 264 } 265 } 266 catch (CancelledKeyException cke) 267 { 268 if (debugEnabled()) 269 { 270 TRACER.debugCaught(DebugLogLevel.ERROR, cke); 271 } 272 273 // This could happen if a connection was closed between the time 274 // that select returned and the time that we try to access the 275 // associated channel. If that was the case, we don't need to do 276 // anything. 277 } 278 catch (Exception e) 279 { 280 if (debugEnabled()) 281 { 282 TRACER.debugCaught(DebugLogLevel.ERROR, e); 283 } 284 285 // This should not happen, and it would have caused our reader 286 // thread to die. Log a severe error. 287 Message message = ERR_LDAP_REQHANDLER_UNEXPECTED_SELECT_EXCEPTION. 288 get(getName(), getExceptionMessage(e)); 289 ErrorLogger.logError(message); 290 } 291 finally 292 { 293 iterator.remove(); 294 } 295 } 296 } 297 298 299 // Check to see if we have any pending connections that need to be 300 // registered with the selector. 301 while (! pendingConnections.isEmpty()) 302 { 303 LDAPClientConnection c = pendingConnections.remove(); 304 305 try 306 { 307 SocketChannel socketChannel = c.getSocketChannel(); 308 socketChannel.configureBlocking(false); 309 synchronized (selectorKeyLock) { 310 socketChannel.register(selector, SelectionKey.OP_READ, c); 311 } 312 } 313 catch (Exception e) 314 { 315 if (debugEnabled()) 316 { 317 TRACER.debugCaught(DebugLogLevel.ERROR, e); 318 } 319 320 c.disconnect(DisconnectReason.SERVER_ERROR, true, 321 ERR_LDAP_REQHANDLER_CANNOT_REGISTER.get(handlerName, 322 String.valueOf(e))); 323 } 324 } 325 } 326 } 327 328 329 330 /** 331 * Registers the provided client connection with this request handler so that 332 * any requests received from that client will be processed. 333 * 334 * @param clientConnection The client connection to be registered with this 335 * request handler. 336 * 337 * @return <CODE>true</CODE> if the client connection was properly registered 338 * with this request handler, or <CODE>false</CODE> if not. 339 */ 340 public boolean registerClient(LDAPClientConnection clientConnection) 341 { 342 // FIXME -- Need to check if the maximum client limit has been reached. 343 344 345 // If the server is in the process of shutting down, then we don't want to 346 // accept it. 347 if (shutdownRequested) 348 { 349 clientConnection.disconnect(DisconnectReason.SERVER_SHUTDOWN, true, 350 ERR_LDAP_REQHANDLER_REJECT_DUE_TO_SHUTDOWN.get()); 351 return false; 352 } 353 354 355 // Try to add the new connection to the queue. If it succeeds, then wake 356 // up the selector so it will be picked up right away. Otherwise, 357 // disconnect the client. 358 if (pendingConnections.offer(clientConnection)) 359 { 360 selector.wakeup(); 361 return true; 362 } 363 else 364 { 365 clientConnection.disconnect(DisconnectReason.ADMIN_LIMIT_EXCEEDED, true, 366 ERR_LDAP_REQHANDLER_REJECT_DUE_TO_QUEUE_FULL.get(handlerName)); 367 return false; 368 } 369 } 370 371 372 373 /** 374 * Deregisters the provided client connection from this request handler so it 375 * will no longer look for requests from that client. 376 * 377 * @param clientConnection The client connection to deregister from this 378 * request handler. 379 */ 380 public void deregisterClient(LDAPClientConnection clientConnection) 381 { 382 SelectionKey[] keyArray; 383 synchronized (selectorKeyLock) { 384 keyArray = selector.keys().toArray(new SelectionKey[0]); 385 } 386 387 for (SelectionKey key : keyArray) 388 { 389 LDAPClientConnection conn = (LDAPClientConnection) key.attachment(); 390 if (clientConnection.equals(conn)) 391 { 392 try 393 { 394 key.channel().close(); 395 } 396 catch (Exception e) 397 { 398 if (debugEnabled()) 399 { 400 TRACER.debugCaught(DebugLogLevel.ERROR, e); 401 } 402 } 403 404 try 405 { 406 key.cancel(); 407 } 408 catch (Exception e) 409 { 410 if (debugEnabled()) 411 { 412 TRACER.debugCaught(DebugLogLevel.ERROR, e); 413 } 414 } 415 } 416 } 417 } 418 419 420 421 /** 422 * Deregisters all clients associated with this request handler. 423 */ 424 public void deregisterAllClients() 425 { 426 SelectionKey[] keyArray; 427 synchronized (selectorKeyLock) { 428 keyArray = selector.keys().toArray(new SelectionKey[0]); 429 } 430 431 for (SelectionKey key : keyArray) 432 { 433 try 434 { 435 key.channel().close(); 436 } 437 catch (Exception e) 438 { 439 if (debugEnabled()) 440 { 441 TRACER.debugCaught(DebugLogLevel.ERROR, e); 442 } 443 } 444 445 try 446 { 447 key.cancel(); 448 } 449 catch (Exception e) 450 { 451 if (debugEnabled()) 452 { 453 TRACER.debugCaught(DebugLogLevel.ERROR, e); 454 } 455 } 456 } 457 } 458 459 460 461 /** 462 * Retrieves the set of all client connections that are currently registered 463 * with this request handler. 464 * 465 * @return The set of all client connections that are currently registered 466 * with this request handler. 467 */ 468 public Collection<LDAPClientConnection> getClientConnections() 469 { 470 SelectionKey[] keyArray; 471 synchronized (selectorKeyLock) { 472 keyArray = selector.keys().toArray(new SelectionKey[0]); 473 } 474 475 ArrayList<LDAPClientConnection> connList = 476 new ArrayList<LDAPClientConnection>(keyArray.length); 477 for (SelectionKey key : keyArray) 478 { 479 connList.add((LDAPClientConnection) key.attachment()); 480 } 481 482 return connList; 483 } 484 485 486 487 /** 488 * Retrieves the human-readable name for this shutdown listener. 489 * 490 * @return The human-readable name for this shutdown listener. 491 */ 492 public String getShutdownListenerName() 493 { 494 return handlerName; 495 } 496 497 498 499 /** 500 * Causes this request handler to register itself as a shutdown listener with 501 * the Directory Server. This must be called if the connection handler is 502 * shut down without closing all associated connections, otherwise the thread 503 * would not be stopped by the server. 504 */ 505 public void registerShutdownListener() 506 { 507 DirectoryServer.registerShutdownListener(this); 508 } 509 510 511 512 /** 513 * Indicates that the Directory Server has received a request to stop running 514 * and that this shutdown listener should take any action necessary to prepare 515 * for it. 516 * 517 * @param reason The human-readable reason for the shutdown. 518 */ 519 public void processServerShutdown(Message reason) 520 { 521 shutdownRequested = true; 522 523 Collection<LDAPClientConnection> clientConnections = getClientConnections(); 524 deregisterAllClients(); 525 526 if (clientConnections != null) 527 { 528 for (LDAPClientConnection c : clientConnections) 529 { 530 try 531 { 532 c.disconnect(DisconnectReason.SERVER_SHUTDOWN, true, 533 ERR_LDAP_REQHANDLER_DEREGISTER_DUE_TO_SHUTDOWN.get( 534 reason)); 535 } 536 catch (Exception e) 537 { 538 if (debugEnabled()) 539 { 540 TRACER.debugCaught(DebugLogLevel.ERROR, e); 541 } 542 } 543 } 544 } 545 546 try 547 { 548 if (selector != null) 549 { 550 selector.wakeup(); 551 } 552 } 553 catch (Exception e) 554 { 555 if (debugEnabled()) 556 { 557 TRACER.debugCaught(DebugLogLevel.ERROR, e); 558 } 559 } 560 } 561 } 562