/**
   * Serves the incoming connections.
   *
   * @throws IOException
   * @throws DirectoryException
   */
  private void serveIncomingConnections() throws IOException, DirectoryException {
    int selectorState = selector.select();

    // We can't rely on return value of select to determine if any keys
    // are ready.
    // see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4850373
    for (Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
        iterator.hasNext(); ) {
      SelectionKey key = iterator.next();
      iterator.remove();
      if (key.isAcceptable()) {
        // Accept the new client connection.
        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
        SocketChannel clientChannel = serverChannel.accept();
        if (clientChannel != null) {
          acceptConnection(clientChannel);
        }
      }

      if (selectorState == 0 && enabled && !shutdownRequested && logger.isTraceEnabled()) {
        // Selected keys was non empty but select() returned 0.
        // Log warning and hope it blocks on the next select() call.
        logger.trace(
            "Selector.select() returned 0. "
                + "Selected Keys: %d, Interest Ops: %d, Ready Ops: %d ",
            selector.selectedKeys().size(), key.interestOps(), key.readyOps());
      }
    }
  }
  /**
   * Cleans up the contents of the selector, closing any server socket channels that might be
   * associated with it. Any connections that might have been established through those channels
   * should not be impacted.
   */
  private void cleanUpSelector() {
    try {
      for (SelectionKey key : selector.keys()) {
        try {
          key.cancel();
        } catch (Exception e) {
          logger.traceException(e);
        }

        try {
          key.channel().close();
        } catch (Exception e) {
          logger.traceException(e);
        }
      }
    } catch (Exception e) {
      logger.traceException(e);
    }
  }
  /** {@inheritDoc} */
  @Override
  public void finalizeConnectionHandler(LocalizableMessage finalizeReason) {
    shutdownRequested = true;
    currentConfig.removeLDAPChangeListener(this);

    if (connMonitor != null) {
      DirectoryServer.deregisterMonitorProvider(connMonitor);
    }

    if (statTracker != null) {
      DirectoryServer.deregisterMonitorProvider(statTracker);
    }

    DirectoryServer.deregisterSupportedLDAPVersion(2, this);
    DirectoryServer.deregisterSupportedLDAPVersion(3, this);

    try {
      selector.wakeup();
    } catch (Exception e) {
      logger.traceException(e);
    }

    for (LDAPRequestHandler requestHandler : requestHandlers) {
      requestHandler.processServerShutdown(finalizeReason);
    }

    // Shutdown the connection finalizer and ensure that any pending
    // unclosed connections are closed.
    synchronized (connectionFinalizerLock) {
      connectionFinalizer.shutdown();
      connectionFinalizer = null;

      Runnable r = new ConnectionFinalizerRunnable();
      r.run(); // Flush active queue.
      r.run(); // Flush pending queue.
    }
  }
  /**
   * Operates in a loop, accepting new connections and ensuring that requests on those connections
   * are handled properly.
   */
  @Override
  public void run() {
    setName(handlerName);
    boolean listening = false;
    boolean starting = true;

    while (!shutdownRequested) {
      // If this connection handler is not enabled, then just sleep
      // for a bit and check again.
      if (!enabled) {
        if (listening) {
          cleanUpSelector();
          listening = false;

          logger.info(NOTE_CONNHANDLER_STOPPED_LISTENING, handlerName);
        }

        if (starting) {
          // This may happen if there was an initialisation error
          // which led to disable the connector.
          // The main thread is waiting for the connector to listen
          // on its port, which will not occur yet,
          // so notify here to allow the server startup to complete.
          synchronized (waitListen) {
            starting = false;
            waitListen.notify();
          }
        }

        StaticUtils.sleep(1000);
        continue;
      }

      // If we have gotten here, then we are about to start listening
      // for the first time since startup or since we were previously
      // disabled. Make sure to start with a clean selector and then
      // create all the listeners.
      try {
        cleanUpSelector();

        int numRegistered = registerChannels();

        // At this point, the connection Handler either started
        // correctly or failed to start but the start process
        // should be notified and resume its work in any cases.
        synchronized (waitListen) {
          waitListen.notify();
        }

        // If none of the listeners were created successfully, then
        // consider the connection handler disabled and require
        // administrative action before trying again.
        if (numRegistered == 0) {
          logger.error(ERR_LDAP_CONNHANDLER_NO_ACCEPTORS, currentConfig.dn());

          enabled = false;
          continue;
        }

        listening = true;

        // Enter a loop, waiting for new connections to arrive and
        // then accepting them as they come in.
        boolean lastIterationFailed = false;
        while (enabled && !shutdownRequested) {
          try {
            serveIncomingConnections();

            lastIterationFailed = false;
          } catch (Exception e) {
            logger.traceException(e);
            logger.error(
                ERR_CONNHANDLER_CANNOT_ACCEPT_CONNECTION,
                friendlyName,
                currentConfig.dn(),
                getExceptionMessage(e));

            if (lastIterationFailed) {
              // The last time through the accept loop we also
              // encountered a failure. Rather than enter a potential
              // infinite loop of failures, disable this acceptor and
              // log an error.
              LocalizableMessage message =
                  ERR_CONNHANDLER_CONSECUTIVE_ACCEPT_FAILURES.get(
                      friendlyName, currentConfig.dn(), stackTraceToSingleLineString(e));
              logger.error(message);

              DirectoryServer.sendAlertNotification(
                  this, ALERT_TYPE_LDAP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES, message);

              cleanUpSelector();
              enabled = false;
            } else {
              lastIterationFailed = true;
            }
          }
        }

        if (shutdownRequested) {
          cleanUpSelector();
          selector.close();
          listening = false;
          enabled = false;
        }

      } catch (Exception e) {
        logger.traceException(e);

        // This is very bad because we failed outside the loop. The
        // only thing we can do here is log a message, send an alert,
        // and disable the selector until an administrator can figure
        // out what's going on.
        LocalizableMessage message =
            ERR_LDAP_CONNHANDLER_UNCAUGHT_ERROR.get(
                currentConfig.dn(), stackTraceToSingleLineString(e));
        logger.error(message);

        DirectoryServer.sendAlertNotification(
            this, ALERT_TYPE_LDAP_CONNECTION_HANDLER_UNCAUGHT_ERROR, message);

        cleanUpSelector();
        enabled = false;
      }
    }
  }
  /** {@inheritDoc} */
  @Override
  public void initializeConnectionHandler(LDAPConnectionHandlerCfg config)
      throws ConfigException, InitializationException {
    if (friendlyName == null) {
      friendlyName = config.dn().rdn().getAttributeValue(0).toString();
    }

    // Open the selector.
    try {
      selector = Selector.open();
    } catch (Exception e) {
      logger.traceException(e);

      LocalizableMessage message =
          ERR_LDAP_CONNHANDLER_OPEN_SELECTOR_FAILED.get(
              config.dn(), stackTraceToSingleLineString(e));
      throw new InitializationException(message, e);
    }

    // Save this configuration for future reference.
    currentConfig = config;
    enabled = config.isEnabled();
    requestHandlerIndex = 0;
    allowedClients = config.getAllowedClient();
    deniedClients = config.getDeniedClient();

    // Configure SSL if needed.
    try {
      // This call may disable the connector if wrong SSL settings
      configureSSL(config);
    } catch (DirectoryException e) {
      logger.traceException(e);
      throw new InitializationException(e.getMessageObject());
    }

    // Save properties that cannot be dynamically modified.
    allowReuseAddress = config.isAllowTCPReuseAddress();
    backlog = config.getAcceptBacklog();
    listenAddresses = config.getListenAddress();
    listenPort = config.getListenPort();
    numRequestHandlers = getNumRequestHandlers(config.getNumRequestHandlers(), friendlyName);

    // Construct a unique name for this connection handler, and put
    // together the set of listeners.
    listeners = new LinkedList<>();
    StringBuilder nameBuffer = new StringBuilder();
    nameBuffer.append(friendlyName);
    for (InetAddress a : listenAddresses) {
      listeners.add(new HostPort(a.getHostAddress(), listenPort));
      nameBuffer.append(" ");
      nameBuffer.append(a.getHostAddress());
    }
    nameBuffer.append(" port ");
    nameBuffer.append(listenPort);
    handlerName = nameBuffer.toString();

    // Attempt to bind to the listen port on all configured addresses to
    // verify whether the connection handler will be able to start.
    LocalizableMessage errorMessage =
        checkAnyListenAddressInUse(listenAddresses, listenPort, allowReuseAddress, config.dn());
    if (errorMessage != null) {
      logger.error(errorMessage);
      throw new InitializationException(errorMessage);
    }

    // Create a system property to store the LDAP(S) port the server is
    // listening to. This information can be displayed with jinfo.
    System.setProperty(protocol + "_port", String.valueOf(listenPort));

    // Create and start a connection finalizer thread for this
    // connection handler.
    connectionFinalizer =
        Executors.newSingleThreadScheduledExecutor(
            new DirectoryThread.Factory(
                "LDAP Connection Finalizer for connection handler " + toString()));

    connectionFinalizerActiveJobQueue = new ArrayList<>();
    connectionFinalizerPendingJobQueue = new ArrayList<>();

    connectionFinalizer.scheduleWithFixedDelay(
        new ConnectionFinalizerRunnable(), 100, 100, TimeUnit.MILLISECONDS);

    // Create and start the request handlers.
    requestHandlers = new LDAPRequestHandler[numRequestHandlers];
    for (int i = 0; i < numRequestHandlers; i++) {
      requestHandlers[i] = new LDAPRequestHandler(this, i);
    }

    for (int i = 0; i < numRequestHandlers; i++) {
      requestHandlers[i].start();
    }

    // Register the set of supported LDAP versions.
    DirectoryServer.registerSupportedLDAPVersion(3, this);
    if (config.isAllowLDAPV2()) {
      DirectoryServer.registerSupportedLDAPVersion(2, this);
    }

    // Create and register monitors.
    statTracker = new LDAPStatistics(handlerName + " Statistics");
    DirectoryServer.registerMonitorProvider(statTracker);

    connMonitor = new ClientConnectionMonitorProvider(this);
    DirectoryServer.registerMonitorProvider(connMonitor);

    // Register this as a change listener.
    config.addLDAPChangeListener(this);
  }