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