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