mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Yannick Lecaillez
25.07.2016 464f6dd52a1eeeef0f7b38d4dc1840501818a36f
opendj-server-legacy/src/main/java/org/forgerock/opendj/reactive/LDAPConnectionHandler2.java
@@ -92,1025 +92,859 @@
import com.forgerock.reactive.Stream;
/**
 * This class defines a connection handler that will be used for communicating
 * with clients over LDAP. It is actually implemented in two parts: as a
 * connection handler and one or more request handlers. The connection handler
 * is responsible for accepting new connections and registering each of them
 * with a request handler. The request handlers then are responsible for reading
 * requests from the clients and parsing them as operations. A single request
 * handler may be used, but having multiple handlers might provide better
 * performance in a multi-CPU system.
 * This class defines a connection handler that will be used for communicating with clients over LDAP. It is actually
 * implemented in two parts: as a connection handler and one or more request handlers. The connection handler is
 * responsible for accepting new connections and registering each of them with a request handler. The request handlers
 * then are responsible for reading requests from the clients and parsing them as operations. A single request handler
 * may be used, but having multiple handlers might provide better performance in a multi-CPU system.
 */
public final class LDAPConnectionHandler2 extends
    ConnectionHandler<LDAPConnectionHandlerCfg> implements
    ConfigurationChangeListener<LDAPConnectionHandlerCfg>,
    ServerShutdownListener, AlertGenerator
{
  /** Task run periodically by the connection finalizer. */
  private final class ConnectionFinalizerRunnable implements Runnable
  {
public final class LDAPConnectionHandler2 extends ConnectionHandler<LDAPConnectionHandlerCfg> implements
        ConfigurationChangeListener<LDAPConnectionHandlerCfg>, ServerShutdownListener, AlertGenerator {
    /** Task run periodically by the connection finalizer. */
    private final class ConnectionFinalizerRunnable implements Runnable {
        @Override
        public void run() {
            if (!connectionFinalizerActiveJobQueue.isEmpty()) {
                for (Runnable r : connectionFinalizerActiveJobQueue) {
                    r.run();
                }
                connectionFinalizerActiveJobQueue.clear();
            }
            // Switch the lists.
            synchronized (connectionFinalizerLock) {
                List<Runnable> tmp = connectionFinalizerActiveJobQueue;
                connectionFinalizerActiveJobQueue = connectionFinalizerPendingJobQueue;
                connectionFinalizerPendingJobQueue = tmp;
            }
        }
    }
    private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
    /** Default friendly name for the LDAP connection handler. */
    private static final String DEFAULT_FRIENDLY_NAME = "LDAP Connection Handler";
    /** SSL instance name used in context creation. */
    private static final String SSL_CONTEXT_INSTANCE_NAME = "TLS";
    private GrizzlyLDAPListener listener;
    /** The current configuration state. */
    private LDAPConnectionHandlerCfg currentConfig;
    /* Properties that cannot be modified dynamically */
    /** The set of addresses on which to listen for new connections. */
    private Set<InetSocketAddress> listenAddresses;
    /** The SSL client auth policy used by this connection handler. */
    private SSLClientAuthPolicy sslClientAuthPolicy;
    /** The backlog that will be used for the accept queue. */
    private int backlog;
    /** Indicates whether to allow the reuse address socket option. */
    private boolean allowReuseAddress;
    /** Indicates whether the Directory Server is in the process of shutting down. */
    private volatile boolean shutdownRequested;
    /* Internal LDAP connection handler state */
    /** Indicates whether this connection handler is enabled. */
    private boolean enabled;
    /** The set of clients that are explicitly allowed access to the server. */
    private Collection<AddressMask> allowedClients;
    /** The set of clients that have been explicitly denied access to the server. */
    private Collection<AddressMask> deniedClients;
    /** The set of listeners for this connection handler. */
    private List<HostPort> listeners;
    /** The set of statistics collected for this connection handler. */
    private LDAPStatistics statTracker;
    /** The client connection monitor provider associated with this connection handler. */
    private ClientConnectionMonitorProvider connMonitor;
    /** The unique name assigned to this connection handler. */
    private String handlerName;
    /** The protocol used by this connection handler. */
    private String protocol;
    /** Queueing strategy. */
    private final QueueingStrategy queueingStrategy;
    /**
     * The condition variable that will be used by the start method to wait for the socket port to be opened and ready
     * to process requests before returning.
     */
    private final Object waitListen = new Object();
    /** The friendly name of this connection handler. */
    private String friendlyName;
    /**
     * SSL context.
     *
     * @see LDAPConnectionHandler2#sslEngine
     */
    private SSLContext sslContext;
    /** The SSL engine is used for obtaining default SSL parameters. */
    private SSLEngine sslEngine;
    /**
     * Connection finalizer thread.
     * <p>
     * This thread is defers closing clients for approximately 100ms. This gives the client a chance to close the
     * connection themselves before the server thus avoiding leaving the server side in the TIME WAIT state.
     */
    private final Object connectionFinalizerLock = new Object();
    private ScheduledExecutorService connectionFinalizer;
    private List<Runnable> connectionFinalizerActiveJobQueue;
    private List<Runnable> connectionFinalizerPendingJobQueue;
    private final List<ClientConnection> connectionList = Collections
            .synchronizedList(new ArrayList<ClientConnection>());
    /**
     * Creates a new instance of this LDAP connection handler. It must be initialized before it may be used.
     */
    public LDAPConnectionHandler2() {
        this(new WorkQueueStrategy(), null); // Use name from configuration.
    }
    /**
     * Creates a new instance of this LDAP connection handler, using a queueing strategy. It must be initialized before
     * it may be used.
     *
     * @param strategy
     *            Request handling strategy.
     * @param friendlyName
     *            The name of of this connection handler, or {@code null} if the name should be taken from the
     *            configuration.
     */
    public LDAPConnectionHandler2(QueueingStrategy strategy, String friendlyName) {
        super(friendlyName != null ? friendlyName : DEFAULT_FRIENDLY_NAME + " Thread");
        this.friendlyName = friendlyName;
        this.queueingStrategy = strategy;
    }
    /**
     * Indicates whether this connection handler should allow interaction with LDAPv2 clients.
     *
     * @return <CODE>true</CODE> if LDAPv2 is allowed, or <CODE>false</CODE> if not.
     */
    public boolean allowLDAPv2() {
        return currentConfig.isAllowLDAPV2();
    }
    /**
     * Indicates whether this connection handler should allow the use of the StartTLS extended operation.
     *
     * @return <CODE>true</CODE> if StartTLS is allowed, or <CODE>false</CODE> if not.
     */
    public boolean allowStartTLS() {
        return currentConfig.isAllowStartTLS() && !currentConfig.isUseSSL();
    }
    @Override
    public void run()
    {
      if (!connectionFinalizerActiveJobQueue.isEmpty())
      {
        for (Runnable r : connectionFinalizerActiveJobQueue)
        {
          r.run();
    public ConfigChangeResult applyConfigurationChange(LDAPConnectionHandlerCfg config) {
        final ConfigChangeResult ccr = new ConfigChangeResult();
        // Note that the following properties cannot be modified:
        // * listen port and addresses
        // * use ssl
        // * ssl policy
        // * ssl cert nickname
        // * accept backlog
        // * tcp reuse address
        // * num request handler
        // Clear the stat tracker if LDAPv2 is being enabled.
        if (currentConfig.isAllowLDAPV2() != config.isAllowLDAPV2() && config.isAllowLDAPV2()) {
            statTracker.clearStatistics();
        }
        connectionFinalizerActiveJobQueue.clear();
      }
      // Switch the lists.
      synchronized (connectionFinalizerLock)
      {
        List<Runnable> tmp = connectionFinalizerActiveJobQueue;
        connectionFinalizerActiveJobQueue = connectionFinalizerPendingJobQueue;
        connectionFinalizerPendingJobQueue = tmp;
      }
    }
  }
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
        // Apply the changes.
        currentConfig = config;
        enabled = config.isEnabled();
        allowedClients = config.getAllowedClient();
        deniedClients = config.getDeniedClient();
  /** Default friendly name for the LDAP connection handler. */
  private static final String DEFAULT_FRIENDLY_NAME = "LDAP Connection Handler";
        // Reconfigure SSL if needed.
        try {
            configureSSL(config);
        } catch (DirectoryException e) {
            logger.traceException(e);
            ccr.setResultCode(e.getResultCode());
            ccr.addMessage(e.getMessageObject());
            return ccr;
        }
  /** SSL instance name used in context creation. */
  private static final String SSL_CONTEXT_INSTANCE_NAME = "TLS";
        if (config.isAllowLDAPV2()) {
            DirectoryServer.registerSupportedLDAPVersion(2, this);
        } else {
            DirectoryServer.deregisterSupportedLDAPVersion(2, this);
        }
  private GrizzlyLDAPListener listener;
  /** The current configuration state. */
  private LDAPConnectionHandlerCfg currentConfig;
  /* Properties that cannot be modified dynamically */
  /** The set of addresses on which to listen for new connections. */
  private Set<InetSocketAddress> listenAddresses;
  /** The SSL client auth policy used by this connection handler. */
  private SSLClientAuthPolicy sslClientAuthPolicy;
  /** The backlog that will be used for the accept queue. */
  private int backlog;
  /** Indicates whether to allow the reuse address socket option. */
  private boolean allowReuseAddress;
  /** Indicates whether the Directory Server is in the process of shutting down. */
  private volatile boolean shutdownRequested;
  /* Internal LDAP connection handler state */
  /** Indicates whether this connection handler is enabled. */
  private boolean enabled;
  /** The set of clients that are explicitly allowed access to the server. */
  private Collection<AddressMask> allowedClients;
  /** The set of clients that have been explicitly denied access to the server. */
  private Collection<AddressMask> deniedClients;
  /** The set of listeners for this connection handler. */
  private List<HostPort> listeners;
  /** The set of statistics collected for this connection handler. */
  private LDAPStatistics statTracker;
  /** The client connection monitor provider associated with this connection handler. */
  private ClientConnectionMonitorProvider connMonitor;
  /** The unique name assigned to this connection handler. */
  private String handlerName;
  /** The protocol used by this connection handler. */
  private String protocol;
  /** Queueing strategy. */
  private final QueueingStrategy queueingStrategy;
  /**
   * The condition variable that will be used by the start method to wait for
   * the socket port to be opened and ready to process requests before
   * returning.
   */
  private final Object waitListen = new Object();
  /** The friendly name of this connection handler. */
  private String friendlyName;
  /**
   * SSL context.
   *
   * @see LDAPConnectionHandler2#sslEngine
   */
  private SSLContext sslContext;
  /** The SSL engine is used for obtaining default SSL parameters. */
  private SSLEngine sslEngine;
  /**
   * Connection finalizer thread.
   * <p>
   * This thread is defers closing clients for approximately 100ms. This gives
   * the client a chance to close the connection themselves before the server
   * thus avoiding leaving the server side in the TIME WAIT state.
   */
  private final Object connectionFinalizerLock = new Object();
  private ScheduledExecutorService connectionFinalizer;
  private List<Runnable> connectionFinalizerActiveJobQueue;
  private List<Runnable> connectionFinalizerPendingJobQueue;
  private final List<ClientConnection> connectionList = Collections.synchronizedList(new ArrayList<ClientConnection>());
  /**
   * Creates a new instance of this LDAP connection handler. It must be
   * initialized before it may be used.
   */
  public LDAPConnectionHandler2()
  {
    this(new WorkQueueStrategy(), null); // Use name from configuration.
  }
  /**
   * Creates a new instance of this LDAP connection handler, using a queueing
   * strategy. It must be initialized before it may be used.
   *
   * @param strategy
   *          Request handling strategy.
   * @param friendlyName
   *          The name of of this connection handler, or {@code null} if the
   *          name should be taken from the configuration.
   */
  public LDAPConnectionHandler2(QueueingStrategy strategy, String friendlyName)
  {
    super(friendlyName != null ? friendlyName : DEFAULT_FRIENDLY_NAME + " Thread");
    this.friendlyName = friendlyName;
    this.queueingStrategy = strategy;
  }
  /**
   * Indicates whether this connection handler should allow interaction with
   * LDAPv2 clients.
   *
   * @return <CODE>true</CODE> if LDAPv2 is allowed, or <CODE>false</CODE> if
   *         not.
   */
  public boolean allowLDAPv2()
  {
    return currentConfig.isAllowLDAPV2();
  }
  /**
   * Indicates whether this connection handler should allow the use of the
   * StartTLS extended operation.
   *
   * @return <CODE>true</CODE> if StartTLS is allowed, or <CODE>false</CODE> if
   *         not.
   */
  public boolean allowStartTLS()
  {
    return currentConfig.isAllowStartTLS() && !currentConfig.isUseSSL();
  }
  @Override
  public ConfigChangeResult applyConfigurationChange(
      LDAPConnectionHandlerCfg config)
  {
    final ConfigChangeResult ccr = new ConfigChangeResult();
    // Note that the following properties cannot be modified:
    // * listen port and addresses
    // * use ssl
    // * ssl policy
    // * ssl cert nickname
    // * accept backlog
    // * tcp reuse address
    // * num request handler
    // Clear the stat tracker if LDAPv2 is being enabled.
    if (currentConfig.isAllowLDAPV2() != config.isAllowLDAPV2()
        && config.isAllowLDAPV2())
    {
      statTracker.clearStatistics();
        return ccr;
    }
    // Apply the changes.
    currentConfig = config;
    enabled = config.isEnabled();
    allowedClients = config.getAllowedClient();
    deniedClients = config.getDeniedClient();
    // Reconfigure SSL if needed.
    try
    {
      configureSSL(config);
    }
    catch (DirectoryException e)
    {
      logger.traceException(e);
      ccr.setResultCode(e.getResultCode());
      ccr.addMessage(e.getMessageObject());
      return ccr;
    private void configureSSL(LDAPConnectionHandlerCfg config) throws DirectoryException {
        protocol = config.isUseSSL() ? "LDAPS" : "LDAP";
        if (config.isUseSSL() || config.isAllowStartTLS()) {
            sslContext = createSSLContext(config);
            sslEngine = createSSLEngine(config, sslContext);
        } else {
            sslContext = null;
            sslEngine = null;
        }
    }
    if (config.isAllowLDAPV2())
    {
      DirectoryServer.registerSupportedLDAPVersion(2, this);
    }
    else
    {
      DirectoryServer.deregisterSupportedLDAPVersion(2, this);
    @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);
        // 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.
        }
    }
    return ccr;
  }
    /**
     * Retrieves information about the set of alerts that this generator may produce. The map returned should be between
     * the notification type for a particular notification and the human-readable description for that notification.
     * This alert generator must not generate any alerts with types that are not contained in this list.
     *
     * @return Information about the set of alerts that this generator may produce.
     */
    @Override
    public Map<String, String> getAlerts() {
        Map<String, String> alerts = new LinkedHashMap<>();
  private void configureSSL(LDAPConnectionHandlerCfg config)
      throws DirectoryException
  {
    protocol = config.isUseSSL() ? "LDAPS" : "LDAP";
    if (config.isUseSSL() || config.isAllowStartTLS())
    {
      sslContext = createSSLContext(config);
      sslEngine = createSSLEngine(config, sslContext);
    }
    else
    {
      sslContext = null;
      sslEngine = null;
    }
  }
        alerts.put(ALERT_TYPE_LDAP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES,
                ALERT_DESCRIPTION_LDAP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES);
        alerts.put(ALERT_TYPE_LDAP_CONNECTION_HANDLER_UNCAUGHT_ERROR,
                ALERT_DESCRIPTION_LDAP_CONNECTION_HANDLER_UNCAUGHT_ERROR);
  @Override
  public void finalizeConnectionHandler(LocalizableMessage finalizeReason)
  {
    shutdownRequested = true;
    currentConfig.removeLDAPChangeListener(this);
    if (connMonitor != null)
    {
      DirectoryServer.deregisterMonitorProvider(connMonitor);
        return alerts;
    }
    if (statTracker != null)
    {
      DirectoryServer.deregisterMonitorProvider(statTracker);
    /**
     * Retrieves the fully-qualified name of the Java class for this alert generator implementation.
     *
     * @return The fully-qualified name of the Java class for this alert generator implementation.
     */
    @Override
    public String getClassName() {
        return LDAPConnectionHandler2.class.getName();
    }
    DirectoryServer.deregisterSupportedLDAPVersion(2, this);
    DirectoryServer.deregisterSupportedLDAPVersion(3, this);
    // 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.
    }
  }
  /**
   * Retrieves information about the set of alerts that this generator may
   * produce. The map returned should be between the notification type for a
   * particular notification and the human-readable description for that
   * notification. This alert generator must not generate any alerts with types
   * that are not contained in this list.
   *
   * @return Information about the set of alerts that this generator may
   *         produce.
   */
  @Override
  public Map<String, String> getAlerts()
  {
    Map<String, String> alerts = new LinkedHashMap<>();
    alerts.put(ALERT_TYPE_LDAP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES,
        ALERT_DESCRIPTION_LDAP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES);
    alerts.put(ALERT_TYPE_LDAP_CONNECTION_HANDLER_UNCAUGHT_ERROR,
        ALERT_DESCRIPTION_LDAP_CONNECTION_HANDLER_UNCAUGHT_ERROR);
    return alerts;
  }
  /**
   * Retrieves the fully-qualified name of the Java class for this alert
   * generator implementation.
   *
   * @return The fully-qualified name of the Java class for this alert generator
   *         implementation.
   */
  @Override
  public String getClassName()
  {
    return LDAPConnectionHandler2.class.getName();
  }
  /**
   * Retrieves the set of active client connections that have been established
   * through this connection handler.
   *
   * @return The set of active client connections that have been established
   *         through this connection handler.
   */
  @Override
  public Collection<ClientConnection> getClientConnections()
  {
    return connectionList;
  }
  /**
   * Retrieves the DN of the configuration entry with which this alert generator
   * is associated.
   *
   * @return The DN of the configuration entry with which this alert generator
   *         is associated.
   */
  @Override
  public DN getComponentEntryDN()
  {
    return currentConfig.dn();
  }
  @Override
  public String getConnectionHandlerName()
  {
    return handlerName;
  }
  @Override
  public Collection<String> getEnabledSSLCipherSuites()
  {
    final SSLEngine engine = sslEngine;
    if (engine != null)
    {
      return Arrays.asList(engine.getEnabledCipherSuites());
    }
    return super.getEnabledSSLCipherSuites();
  }
  @Override
  public Collection<String> getEnabledSSLProtocols()
  {
    final SSLEngine engine = sslEngine;
    if (engine != null)
    {
      return Arrays.asList(engine.getEnabledProtocols());
    }
    return super.getEnabledSSLProtocols();
  }
  @Override
  public Collection<HostPort> getListeners()
  {
    return listeners;
  }
  /**
   * Retrieves the maximum length of time in milliseconds that attempts to write
   * to LDAP client connections should be allowed to block.
   *
   * @return The maximum length of time in milliseconds that attempts to write
   *         to LDAP client connections should be allowed to block, or zero if
   *         there should not be any limit imposed.
   */
  public long getMaxBlockedWriteTimeLimit()
  {
    return currentConfig.getMaxBlockedWriteTimeLimit();
  }
  /**
   * Retrieves the maximum ASN.1 element value length that will be allowed by
   * this connection handler.
   *
   * @return The maximum ASN.1 element value length that will be allowed by this
   *         connection handler.
   */
  public int getMaxRequestSize()
  {
    return (int) currentConfig.getMaxRequestSize();
  }
  /**
   * Retrieves the size in bytes of the LDAP response message write buffer
   * defined for this connection handler.
   *
   * @return The size in bytes of the LDAP response message write buffer.
   */
  public int getBufferSize()
  {
    return (int) currentConfig.getBufferSize();
  }
  @Override
  public String getProtocol()
  {
    return protocol;
  }
  @Override
  public String getShutdownListenerName()
  {
    return handlerName;
  }
  /**
   * Retrieves the SSL client authentication policy for this connection handler.
   *
   * @return The SSL client authentication policy for this connection handler.
   */
  public SSLClientAuthPolicy getSSLClientAuthPolicy()
  {
    return sslClientAuthPolicy;
  }
  /**
   * Retrieves the set of statistics maintained by this connection handler.
   *
   * @return The set of statistics maintained by this connection handler.
   */
  public LDAPStatistics getStatTracker()
  {
    return statTracker;
  }
  @Override
  public void initializeConnectionHandler(ServerContext serverContext, LDAPConnectionHandlerCfg config)
      throws ConfigException, InitializationException
  {
    if (friendlyName == null)
    {
      friendlyName = config.dn().rdn().getFirstAVA().getAttributeValue().toString();
    /**
     * Retrieves the set of active client connections that have been established through this connection handler.
     *
     * @return The set of active client connections that have been established through this connection handler.
     */
    @Override
    public Collection<ClientConnection> getClientConnections() {
        return connectionList;
    }
    // Save this configuration for future reference.
    currentConfig = config;
    enabled = config.isEnabled();
    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());
    /**
     * Retrieves the DN of the configuration entry with which this alert generator is associated.
     *
     * @return The DN of the configuration entry with which this alert generator is associated.
     */
    @Override
    public DN getComponentEntryDN() {
        return currentConfig.dn();
    }
    // Save properties that cannot be dynamically modified.
    allowReuseAddress = config.isAllowTCPReuseAddress();
    backlog = config.getAcceptBacklog();
    listenAddresses = new HashSet<>();
    for(InetAddress addr :config.getListenAddress()) {
      listenAddresses.add(new InetSocketAddress(addr, config.getListenPort()));
    @Override
    public String getConnectionHandlerName() {
        return handlerName;
    }
    // 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 (InetSocketAddress a : listenAddresses)
    {
      listeners.add(new HostPort(a.getHostName(), a.getPort()));
      nameBuffer.append(" ");
      nameBuffer.append(a.getHostName());
    }
    nameBuffer.append(" port ");
    nameBuffer.append(config.getListenPort());
    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(config.getListenAddress(), config.getListenPort(),
            allowReuseAddress, config.dn());
    if (errorMessage != null)
    {
      logger.error(errorMessage);
      throw new InitializationException(errorMessage);
    @Override
    public Collection<String> getEnabledSSLCipherSuites() {
        final SSLEngine engine = sslEngine;
        if (engine != null) {
            return Arrays.asList(engine.getEnabledCipherSuites());
        }
        return super.getEnabledSSLCipherSuites();
    }
    // 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(config.getListenPort()));
    // 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);
    // Register the set of supported LDAP versions.
    DirectoryServer.registerSupportedLDAPVersion(3, this);
    if (config.isAllowLDAPV2())
    {
      DirectoryServer.registerSupportedLDAPVersion(2, this);
    @Override
    public Collection<String> getEnabledSSLProtocols() {
        final SSLEngine engine = sslEngine;
        if (engine != null) {
            return Arrays.asList(engine.getEnabledProtocols());
        }
        return super.getEnabledSSLProtocols();
    }
    // 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);
  }
  @Override
  public boolean isConfigurationAcceptable(ConnectionHandlerCfg configuration,
      List<LocalizableMessage> unacceptableReasons)
  {
    LDAPConnectionHandlerCfg config = (LDAPConnectionHandlerCfg) configuration;
    if (currentConfig == null
        || (!currentConfig.isEnabled() && config.isEnabled()))
    {
      // 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(config.getListenAddress(), config
              .getListenPort(), config.isAllowTCPReuseAddress(), config.dn());
      if (errorMessage != null)
      {
        unacceptableReasons.add(errorMessage);
        return false;
      }
    @Override
    public Collection<HostPort> getListeners() {
        return listeners;
    }
    if (config.isEnabled()
    /**
     * Retrieves the maximum length of time in milliseconds that attempts to write to LDAP client connections should be
     * allowed to block.
     *
     * @return The maximum length of time in milliseconds that attempts to write to LDAP client connections should be
     *         allowed to block, or zero if there should not be any limit imposed.
     */
    public long getMaxBlockedWriteTimeLimit() {
        return currentConfig.getMaxBlockedWriteTimeLimit();
    }
    /**
     * Retrieves the maximum ASN.1 element value length that will be allowed by this connection handler.
     *
     * @return The maximum ASN.1 element value length that will be allowed by this connection handler.
     */
    public int getMaxRequestSize() {
        return (int) currentConfig.getMaxRequestSize();
    }
    /**
     * Retrieves the size in bytes of the LDAP response message write buffer defined for this connection handler.
     *
     * @return The size in bytes of the LDAP response message write buffer.
     */
    public int getBufferSize() {
        return (int) currentConfig.getBufferSize();
    }
    @Override
    public String getProtocol() {
        return protocol;
    }
    @Override
    public String getShutdownListenerName() {
        return handlerName;
    }
    /**
     * Retrieves the SSL client authentication policy for this connection handler.
     *
     * @return The SSL client authentication policy for this connection handler.
     */
    public SSLClientAuthPolicy getSSLClientAuthPolicy() {
        return sslClientAuthPolicy;
    }
    /**
     * Retrieves the set of statistics maintained by this connection handler.
     *
     * @return The set of statistics maintained by this connection handler.
     */
    public LDAPStatistics getStatTracker() {
        return statTracker;
    }
    @Override
    public void initializeConnectionHandler(ServerContext serverContext, LDAPConnectionHandlerCfg config)
            throws ConfigException, InitializationException {
        if (friendlyName == null) {
            friendlyName = config.dn().rdn().getFirstAVA().getAttributeValue().toString();
        }
        // Save this configuration for future reference.
        currentConfig = config;
        enabled = config.isEnabled();
        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 = new HashSet<>();
        for (InetAddress addr : config.getListenAddress()) {
            listenAddresses.add(new InetSocketAddress(addr, config.getListenPort()));
        }
        // 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 (InetSocketAddress a : listenAddresses) {
            listeners.add(new HostPort(a.getHostName(), a.getPort()));
            nameBuffer.append(" ");
            nameBuffer.append(a.getHostName());
        }
        nameBuffer.append(" port ");
        nameBuffer.append(config.getListenPort());
        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(config.getListenAddress(), config.getListenPort(),
                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(config.getListenPort()));
        // 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);
        // 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);
    }
    @Override
    public boolean isConfigurationAcceptable(ConnectionHandlerCfg configuration,
            List<LocalizableMessage> unacceptableReasons) {
        LDAPConnectionHandlerCfg config = (LDAPConnectionHandlerCfg) configuration;
        if (currentConfig == null || (!currentConfig.isEnabled() && config.isEnabled())) {
            // 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(config.getListenAddress(),
                    config.getListenPort(), config.isAllowTCPReuseAddress(), config.dn());
            if (errorMessage != null) {
                unacceptableReasons.add(errorMessage);
                return false;
            }
        }
        if (config.isEnabled()
        // Check that the SSL configuration is valid.
        && (config.isUseSSL() || config.isAllowStartTLS()))
    {
      try
      {
        createSSLEngine(config, createSSLContext(config));
      }
      catch (DirectoryException e)
      {
        logger.traceException(e);
                && (config.isUseSSL() || config.isAllowStartTLS())) {
            try {
                createSSLEngine(config, createSSLContext(config));
            } catch (DirectoryException e) {
                logger.traceException(e);
        unacceptableReasons.add(e.getMessageObject());
        return false;
      }
    }
    return true;
  }
  /**
   * Checks whether any listen address is in use for the given port. The check
   * is performed by binding to each address and port.
   *
   * @param listenAddresses
   *          the listen {@link InetAddress} to test
   * @param listenPort
   *          the listen port to test
   * @param allowReuseAddress
   *          whether addresses can be reused
   * @param configEntryDN
   *          the configuration entry DN
   * @return an error message if at least one of the address is already in use,
   *         null otherwise.
   */
  private LocalizableMessage checkAnyListenAddressInUse(
      Collection<InetAddress> listenAddresses, int listenPort,
      boolean allowReuseAddress, DN configEntryDN)
  {
    for (InetAddress a : listenAddresses)
    {
      try
      {
        if (StaticUtils.isAddressInUse(a, listenPort, allowReuseAddress))
        {
          throw new IOException(ERR_CONNHANDLER_ADDRESS_INUSE.get().toString());
                unacceptableReasons.add(e.getMessageObject());
                return false;
            }
        }
      }
      catch (IOException e)
      {
        logger.traceException(e);
        return ERR_CONNHANDLER_CANNOT_BIND.get("LDAP", configEntryDN, a.getHostAddress(), listenPort,
            getExceptionMessage(e));
      }
        return true;
    }
    return null;
  }
  @Override
  public boolean isConfigurationChangeAcceptable(
      LDAPConnectionHandlerCfg config, List<LocalizableMessage> unacceptableReasons)
  {
    return isConfigurationAcceptable(config, unacceptableReasons);
  }
  @Override
  public void processServerShutdown(LocalizableMessage reason)
  {
    shutdownRequested = true;
  }
  void stopListener()
  {
    if (listener != null)
    {
      listener.close();
      listener = null;
    /**
     * Checks whether any listen address is in use for the given port. The check is performed by binding to each address
     * and port.
     *
     * @param listenAddresses
     *            the listen {@link InetAddress} to test
     * @param listenPort
     *            the listen port to test
     * @param allowReuseAddress
     *            whether addresses can be reused
     * @param configEntryDN
     *            the configuration entry DN
     * @return an error message if at least one of the address is already in use, null otherwise.
     */
    private LocalizableMessage checkAnyListenAddressInUse(Collection<InetAddress> listenAddresses, int listenPort,
            boolean allowReuseAddress, DN configEntryDN) {
        for (InetAddress a : listenAddresses) {
            try {
                if (StaticUtils.isAddressInUse(a, listenPort, allowReuseAddress)) {
                    throw new IOException(ERR_CONNHANDLER_ADDRESS_INUSE.get().toString());
                }
            } catch (IOException e) {
                logger.traceException(e);
                return ERR_CONNHANDLER_CANNOT_BIND.get("LDAP", configEntryDN, a.getHostAddress(), listenPort,
                        getExceptionMessage(e));
            }
        }
        return null;
    }
  }
  private void startListener() throws IOException
  {
    listener = new GrizzlyLDAPListener(
            listenAddresses,
            Options.defaultOptions()
                   .set(LDAPListener.CONNECT_MAX_BACKLOG, backlog)
                   .set(LDAPListener.REQUEST_MAX_SIZE_IN_BYTES, (int) currentConfig.getMaxRequestSize()),
                   new Function<LDAPClientContext,
                                ReactiveHandler<LDAPClientContext,LdapRawMessage, Stream<Response>>,
                                LdapException>() {
    @Override
    public boolean isConfigurationChangeAcceptable(LDAPConnectionHandlerCfg config,
            List<LocalizableMessage> unacceptableReasons) {
        return isConfigurationAcceptable(config, unacceptableReasons);
    }
    @Override
    public void processServerShutdown(LocalizableMessage reason) {
        shutdownRequested = true;
    }
    void stopListener() {
        if (listener != null) {
            listener.close();
            listener = null;
        }
    }
    private void startListener() throws IOException {
        listener = new GrizzlyLDAPListener(
                listenAddresses,
                Options.defaultOptions().set(LDAPListener.CONNECT_MAX_BACKLOG, backlog)
                        .set(LDAPListener.REQUEST_MAX_SIZE_IN_BYTES, (int) currentConfig.getMaxRequestSize()),
                new Function<LDAPClientContext,
                             ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>>,
                             LdapException>() {
                    @Override
                    public ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>>
                        apply(LDAPClientContext clientContext) throws LdapException {
                    public ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>> apply(
                            LDAPClientContext clientContext) throws LdapException {
                        final LDAPClientConnection2 conn = canAccept(clientContext);
                        return new ReactiveHandler<LDAPClientContext, LdapRawMessage, Stream<Response>>() {
                            @Override
                            public Single<Stream<Response>> handle(LDAPClientContext context,
                                    LdapRawMessage request) throws Exception {
                            public Single<Stream<Response>> handle(LDAPClientContext context, LdapRawMessage request)
                                    throws Exception {
                                return conn.handle(queueingStrategy, request);
                            }
                        };
                    }
                });
  }
    }
  /**
   * Operates in a loop, accepting new connections and ensuring that requests on
   * those connections are handled properly.
   */
  @Override
  public void run()
  {
    setName(handlerName);
    boolean starting = true;
    setName(handlerName);
    /**
     * Operates in a loop, accepting new connections and ensuring that requests on those connections are handled
     * properly.
     */
    @Override
    public void run() {
        setName(handlerName);
        boolean starting = true;
        setName(handlerName);
    boolean lastIterationFailed = false;
        boolean lastIterationFailed = false;
    while (!shutdownRequested)
    {
      // If this connection handler is not enabled, then just sleep for a bit and check again.
      if (!this.enabled)
      {
        if (listener != null)
        {
          stopListener();
        while (!shutdownRequested) {
            // If this connection handler is not enabled, then just sleep for a bit and check again.
            if (!this.enabled) {
                if (listener != null) {
                    stopListener();
                }
                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 (listener != null) {
                // If already listening, then sleep for a bit and check again.
                StaticUtils.sleep(1000);
                continue;
            }
            try {
                // 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 we have gotten here, then we are about to start listening
                // for the first time since startup or since we were previously disabled.
                // Start the embedded HTTP server
                startListener();
                lastIterationFailed = false;
            } catch (Exception e) {
                // Clean up the messed up HTTP server
                stopListener();
                // Error + alert about the horked config
                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_HTTP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES, message);
                    this.enabled = false;
                } else {
                    lastIterationFailed = true;
                }
            }
        }
        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 (listener != null)
      {
        // If already listening, then sleep for a bit and check again.
        StaticUtils.sleep(1000);
        continue;
      }
      try
      {
        // 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 we have gotten here, then we are about to start listening
        // for the first time since startup or since we were previously disabled.
        // Start the embedded HTTP server
        startListener();
        lastIterationFailed = false;
      }
      catch (Exception e)
      {
        // Clean up the messed up HTTP server
        // Initiate shutdown
        stopListener();
    }
        // Error + alert about the horked config
        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_HTTP_CONNECTION_HANDLER_CONSECUTIVE_FAILURES, message);
          this.enabled = false;
    private LDAPClientConnection2 canAccept(LDAPClientContext clientContext) throws LdapException {
        // Check to see if the core server rejected the
        // connection (e.g., already too many connections
        // established).
        final LDAPClientConnection2 clientConnection = new LDAPClientConnection2(this, clientContext, getProtocol(),
                currentConfig.isKeepStats());
        if (clientConnection.getConnectionID() < 0) {
            clientConnection.disconnect(DisconnectReason.ADMIN_LIMIT_EXCEEDED, true,
                    ERR_CONNHANDLER_REJECTED_BY_SERVER.get());
            throw LdapException.newLdapException(ResultCode.ADMIN_LIMIT_EXCEEDED);
        }
        else
        {
          lastIterationFailed = true;
        InetAddress clientAddr = clientConnection.getRemoteAddress();
        // Check to see if the client is on the denied list.
        // If so, then reject it immediately.
        if (!deniedClients.isEmpty() && AddressMask.matchesAny(deniedClients, clientAddr)) {
            clientConnection.disconnect(
                    DisconnectReason.CONNECTION_REJECTED,
                    currentConfig.isSendRejectionNotice(),
                    ERR_CONNHANDLER_DENIED_CLIENT.get(clientConnection.getClientHostPort(),
                            clientConnection.getServerHostPort()));
            throw LdapException.newLdapException(ResultCode.CONSTRAINT_VIOLATION);
        }
      }
    }
        // Check to see if there is an allowed list and if
        // there is whether the client is on that list. If
        // not, then reject the connection.
        if (!allowedClients.isEmpty() && !AddressMask.matchesAny(allowedClients, clientAddr)) {
            clientConnection.disconnect(
                    DisconnectReason.CONNECTION_REJECTED,
                    currentConfig.isSendRejectionNotice(),
                    ERR_CONNHANDLER_DISALLOWED_CLIENT.get(clientConnection.getClientHostPort(),
                            clientConnection.getServerHostPort()));
            throw LdapException.newLdapException(ResultCode.CONSTRAINT_VIOLATION);
        }
    // Initiate shutdown
    stopListener();
  }
  private LDAPClientConnection2 canAccept(LDAPClientContext clientContext) throws LdapException
  {
    // Check to see if the core server rejected the
    // connection (e.g., already too many connections
    // established).
    final LDAPClientConnection2 clientConnection =
            new LDAPClientConnection2(this, clientContext, getProtocol(), currentConfig.isKeepStats());
    if (clientConnection.getConnectionID() < 0)
    {
      clientConnection.disconnect(
          DisconnectReason.ADMIN_LIMIT_EXCEEDED, true, ERR_CONNHANDLER_REJECTED_BY_SERVER.get());
      throw LdapException.newLdapException(ResultCode.ADMIN_LIMIT_EXCEEDED);
    }
    InetAddress clientAddr = clientConnection.getRemoteAddress();
    // Check to see if the client is on the denied list.
    // If so, then reject it immediately.
    if (!deniedClients.isEmpty()
        && AddressMask.matchesAny(deniedClients, clientAddr))
    {
      clientConnection.disconnect(DisconnectReason.CONNECTION_REJECTED,
          currentConfig.isSendRejectionNotice(), ERR_CONNHANDLER_DENIED_CLIENT
              .get(clientConnection.getClientHostPort(), clientConnection
                  .getServerHostPort()));
      throw LdapException.newLdapException(ResultCode.CONSTRAINT_VIOLATION);
    }
    // Check to see if there is an allowed list and if
    // there is whether the client is on that list. If
    // not, then reject the connection.
    if (!allowedClients.isEmpty()
        && !AddressMask.matchesAny(allowedClients, clientAddr))
    {
      clientConnection.disconnect(DisconnectReason.CONNECTION_REJECTED,
          currentConfig.isSendRejectionNotice(),
          ERR_CONNHANDLER_DISALLOWED_CLIENT.get(clientConnection
              .getClientHostPort(), clientConnection.getServerHostPort()));
      throw LdapException.newLdapException(ResultCode.CONSTRAINT_VIOLATION);
    }
    // If we've gotten here, then we'll take the
    // connection so invoke the post-connect plugins and
    // register the client connection with a request
    // handler.
    try
    {
      PluginConfigManager pluginManager = DirectoryServer
          .getPluginConfigManager();
      PluginResult.PostConnect pluginResult = pluginManager
          .invokePostConnectPlugins(clientConnection);
      if (!pluginResult.continueProcessing())
      {
        clientConnection.disconnect(pluginResult.getDisconnectReason(),
            pluginResult.sendDisconnectNotification(),
            pluginResult.getErrorMessage());
        throw LdapException.newLdapException(ResultCode.CONSTRAINT_VIOLATION);
      }
    }
    catch (Exception e)
    {
      logger.traceException(e);
      LocalizableMessage message =
          INFO_CONNHANDLER_UNABLE_TO_REGISTER_CLIENT.get(clientConnection
              .getClientHostPort(), clientConnection.getServerHostPort(),
              getExceptionMessage(e));
      logger.debug(message);
      clientConnection.disconnect(DisconnectReason.SERVER_ERROR,
          currentConfig.isSendRejectionNotice(), message);
      throw LdapException.newLdapException(ResultCode.OPERATIONS_ERROR);
    }
    if (useSSL()) {
        // If we've gotten here, then we'll take the
        // connection so invoke the post-connect plugins and
        // register the client connection with a request
        // handler.
        try {
            clientContext.enableTLS(createSSLEngine());
        } catch (DirectoryException e) {
            throw LdapException.newLdapException(e.getResultCode(), e);
            PluginConfigManager pluginManager = DirectoryServer.getPluginConfigManager();
            PluginResult.PostConnect pluginResult = pluginManager.invokePostConnectPlugins(clientConnection);
            if (!pluginResult.continueProcessing()) {
                clientConnection.disconnect(pluginResult.getDisconnectReason(),
                        pluginResult.sendDisconnectNotification(), pluginResult.getErrorMessage());
                throw LdapException.newLdapException(ResultCode.CONSTRAINT_VIOLATION);
            }
        } catch (Exception e) {
            logger.traceException(e);
            LocalizableMessage message = INFO_CONNHANDLER_UNABLE_TO_REGISTER_CLIENT.get(
                    clientConnection.getClientHostPort(), clientConnection.getServerHostPort(), getExceptionMessage(e));
            logger.debug(message);
            clientConnection.disconnect(DisconnectReason.SERVER_ERROR, currentConfig.isSendRejectionNotice(), message);
            throw LdapException.newLdapException(ResultCode.OPERATIONS_ERROR);
        }
        if (useSSL()) {
            try {
                clientContext.enableTLS(createSSLEngine());
            } catch (DirectoryException e) {
                throw LdapException.newLdapException(e.getResultCode(), e);
            }
        }
        return clientConnection;
    }
    /**
     * Appends a string representation of this connection handler to the provided buffer.
     *
     * @param buffer
     *            The buffer to which the information should be appended.
     */
    @Override
    public void toString(StringBuilder buffer) {
        buffer.append(handlerName);
    }
    /**
     * Indicates whether this connection handler should use SSL to communicate with clients.
     *
     * @return {@code true} if this connection handler should use SSL to communicate with clients, or {@code false} if
     *         not.
     */
    public boolean useSSL() {
        return currentConfig.isUseSSL();
    }
    SSLEngine createSSLEngine() throws DirectoryException {
        return createSSLEngine(currentConfig, sslContext);
    }
    private SSLEngine createSSLEngine(LDAPConnectionHandlerCfg config, SSLContext sslContext)
            throws DirectoryException {
        try {
            SSLEngine sslEngine = sslContext.createSSLEngine();
            sslEngine.setUseClientMode(false);
            final Set<String> protocols = config.getSSLProtocol();
            if (!protocols.isEmpty()) {
                sslEngine.setEnabledProtocols(protocols.toArray(new String[0]));
            }
            final Set<String> ciphers = config.getSSLCipherSuite();
            if (!ciphers.isEmpty()) {
                sslEngine.setEnabledCipherSuites(ciphers.toArray(new String[0]));
            }
            switch (config.getSSLClientAuthPolicy()) {
            case DISABLED:
                sslEngine.setNeedClientAuth(false);
                sslEngine.setWantClientAuth(false);
                break;
            case REQUIRED:
                sslEngine.setWantClientAuth(true);
                sslEngine.setNeedClientAuth(true);
                break;
            case OPTIONAL:
            default:
                sslEngine.setNeedClientAuth(false);
                sslEngine.setWantClientAuth(true);
                break;
            }
            return sslEngine;
        } catch (Exception e) {
            logger.traceException(e);
            ResultCode resCode = DirectoryServer.getServerErrorResultCode();
            LocalizableMessage message = ERR_CONNHANDLER_SSL_CANNOT_INITIALIZE.get(getExceptionMessage(e));
            throw new DirectoryException(resCode, message, e);
        }
    }
    return clientConnection;
  }
  /**
   * Appends a string representation of this connection handler to the provided
   * buffer.
   *
   * @param buffer
   *          The buffer to which the information should be appended.
   */
  @Override
  public void toString(StringBuilder buffer)
  {
    buffer.append(handlerName);
  }
  /**
   * Indicates whether this connection handler should use SSL to communicate
   * with clients.
   *
   * @return {@code true} if this connection handler should use SSL to
   *         communicate with clients, or {@code false} if not.
   */
  public boolean useSSL()
  {
    return currentConfig.isUseSSL();
  }
  SSLEngine createSSLEngine() throws DirectoryException {
      return createSSLEngine(currentConfig, sslContext);
  }
  private SSLEngine createSSLEngine(LDAPConnectionHandlerCfg config, SSLContext sslContext) throws DirectoryException
  {
    try
    {
      SSLEngine sslEngine = sslContext.createSSLEngine();
      sslEngine.setUseClientMode(false);
      final Set<String> protocols = config.getSSLProtocol();
      if (!protocols.isEmpty())
      {
        sslEngine.setEnabledProtocols(protocols.toArray(new String[0]));
      }
      final Set<String> ciphers = config.getSSLCipherSuite();
      if (!ciphers.isEmpty())
      {
        sslEngine.setEnabledCipherSuites(ciphers.toArray(new String[0]));
      }
      switch (config.getSSLClientAuthPolicy())
      {
      case DISABLED:
        sslEngine.setNeedClientAuth(false);
        sslEngine.setWantClientAuth(false);
        break;
      case REQUIRED:
        sslEngine.setWantClientAuth(true);
        sslEngine.setNeedClientAuth(true);
        break;
      case OPTIONAL:
      default:
        sslEngine.setNeedClientAuth(false);
        sslEngine.setWantClientAuth(true);
        break;
      }
      return sslEngine;
    }
    catch (Exception e)
    {
      logger.traceException(e);
      ResultCode resCode = DirectoryServer.getServerErrorResultCode();
      LocalizableMessage message = ERR_CONNHANDLER_SSL_CANNOT_INITIALIZE
          .get(getExceptionMessage(e));
      throw new DirectoryException(resCode, message, e);
    }
  }
  private void disableAndWarnIfUseSSL(LDAPConnectionHandlerCfg config)
  {
    if (config.isUseSSL())
    {
      logger.warn(INFO_DISABLE_CONNECTION, friendlyName);
      enabled = false;
    }
  }
  private SSLContext createSSLContext(LDAPConnectionHandlerCfg config)
      throws DirectoryException
  {
    try
    {
      DN keyMgrDN = config.getKeyManagerProviderDN();
      KeyManagerProvider<?> keyManagerProvider = DirectoryServer
          .getKeyManagerProvider(keyMgrDN);
      if (keyManagerProvider == null)
      {
        logger.error(ERR_NULL_KEY_PROVIDER_MANAGER, keyMgrDN, friendlyName);
        disableAndWarnIfUseSSL(config);
        keyManagerProvider = new NullKeyManagerProvider();
        // The SSL connection is unusable without a key manager provider
      }
      else if (! keyManagerProvider.containsAtLeastOneKey())
      {
        logger.error(ERR_INVALID_KEYSTORE, friendlyName);
        disableAndWarnIfUseSSL(config);
      }
      final SortedSet<String> aliases = new TreeSet<>(config.getSSLCertNickname());
      final KeyManager[] keyManagers;
      if (aliases.isEmpty())
      {
        keyManagers = keyManagerProvider.getKeyManagers();
      }
      else
      {
        final Iterator<String> it = aliases.iterator();
        while (it.hasNext())
        {
          if (!keyManagerProvider.containsKeyWithAlias(it.next()))
          {
            logger.error(ERR_KEYSTORE_DOES_NOT_CONTAIN_ALIAS, aliases, friendlyName);
            it.remove();
          }
    private void disableAndWarnIfUseSSL(LDAPConnectionHandlerCfg config) {
        if (config.isUseSSL()) {
            logger.warn(INFO_DISABLE_CONNECTION, friendlyName);
            enabled = false;
        }
    }
        if (aliases.isEmpty())
        {
          disableAndWarnIfUseSSL(config);
    private SSLContext createSSLContext(LDAPConnectionHandlerCfg config) throws DirectoryException {
        try {
            DN keyMgrDN = config.getKeyManagerProviderDN();
            KeyManagerProvider<?> keyManagerProvider = DirectoryServer.getKeyManagerProvider(keyMgrDN);
            if (keyManagerProvider == null) {
                logger.error(ERR_NULL_KEY_PROVIDER_MANAGER, keyMgrDN, friendlyName);
                disableAndWarnIfUseSSL(config);
                keyManagerProvider = new NullKeyManagerProvider();
                // The SSL connection is unusable without a key manager provider
            } else if (!keyManagerProvider.containsAtLeastOneKey()) {
                logger.error(ERR_INVALID_KEYSTORE, friendlyName);
                disableAndWarnIfUseSSL(config);
            }
            final SortedSet<String> aliases = new TreeSet<>(config.getSSLCertNickname());
            final KeyManager[] keyManagers;
            if (aliases.isEmpty()) {
                keyManagers = keyManagerProvider.getKeyManagers();
            } else {
                final Iterator<String> it = aliases.iterator();
                while (it.hasNext()) {
                    if (!keyManagerProvider.containsKeyWithAlias(it.next())) {
                        logger.error(ERR_KEYSTORE_DOES_NOT_CONTAIN_ALIAS, aliases, friendlyName);
                        it.remove();
                    }
                }
                if (aliases.isEmpty()) {
                    disableAndWarnIfUseSSL(config);
                }
                keyManagers = SelectableCertificateKeyManager.wrap(keyManagerProvider.getKeyManagers(), aliases,
                        friendlyName);
            }
            DN trustMgrDN = config.getTrustManagerProviderDN();
            TrustManagerProvider<?> trustManagerProvider = DirectoryServer.getTrustManagerProvider(trustMgrDN);
            if (trustManagerProvider == null) {
                trustManagerProvider = new NullTrustManagerProvider();
            }
            SSLContext sslContext = SSLContext.getInstance(SSL_CONTEXT_INSTANCE_NAME);
            sslContext.init(keyManagers, trustManagerProvider.getTrustManagers(), null);
            return sslContext;
        } catch (Exception e) {
            logger.traceException(e);
            ResultCode resCode = DirectoryServer.getServerErrorResultCode();
            LocalizableMessage message = ERR_CONNHANDLER_SSL_CANNOT_INITIALIZE.get(getExceptionMessage(e));
            throw new DirectoryException(resCode, message, e);
        }
        keyManagers = SelectableCertificateKeyManager.wrap(keyManagerProvider.getKeyManagers(), aliases, friendlyName);
      }
      DN trustMgrDN = config.getTrustManagerProviderDN();
      TrustManagerProvider<?> trustManagerProvider = DirectoryServer
          .getTrustManagerProvider(trustMgrDN);
      if (trustManagerProvider == null)
      {
        trustManagerProvider = new NullTrustManagerProvider();
      }
      SSLContext sslContext = SSLContext.getInstance(SSL_CONTEXT_INSTANCE_NAME);
      sslContext.init(keyManagers, trustManagerProvider.getTrustManagers(),
          null);
      return sslContext;
    }
    catch (Exception e)
    {
      logger.traceException(e);
      ResultCode resCode = DirectoryServer.getServerErrorResultCode();
      LocalizableMessage message = ERR_CONNHANDLER_SSL_CANNOT_INITIALIZE
          .get(getExceptionMessage(e));
      throw new DirectoryException(resCode, message, e);
    }
  }
  /**
   * Enqueue a connection finalizer which will be invoked after a short delay.
   *
   * @param r
   *          The connection finalizer runnable.
   */
  void registerConnectionFinalizer(Runnable r)
  {
    synchronized (connectionFinalizerLock)
    {
      if (connectionFinalizer != null)
      {
        connectionFinalizerPendingJobQueue.add(r);
      }
      else
      {
        // Already finalized - invoked immediately.
        r.run();
      }
    /**
     * Enqueue a connection finalizer which will be invoked after a short delay.
     *
     * @param r
     *            The connection finalizer runnable.
     */
    void registerConnectionFinalizer(Runnable r) {
        synchronized (connectionFinalizerLock) {
            if (connectionFinalizer != null) {
                connectionFinalizerPendingJobQueue.add(r);
            } else {
                // Already finalized - invoked immediately.
                r.run();
            }
        }
    }
  }
}