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